From 7bc7316ac8e7208d7be5922c0b4557ca17e79684 Mon Sep 17 00:00:00 2001 From: developer4 Date: Fri, 6 Oct 2017 08:33:18 +0200 Subject: [PATCH] version 1.0.0.0 --- App.config | 6 + ClassDiagram1.cd | 2 + Program.cs | 22 + Properties/AssemblyInfo.cs | 36 + Properties/Resources.Designer.cs | 63 + Properties/Resources.resx | 117 + Properties/Settings.Designer.cs | 26 + Properties/Settings.settings | 7 + bc-sharp-crypto/bzip2/src/BZip2Constants.cs | 103 + .../bzip2/src/CBZip2InputStream.cs | 921 +++++ .../bzip2/src/CBZip2OutputStream.cs | 1709 ++++++++ bc-sharp-crypto/bzip2/src/CRC.cs | 134 + bc-sharp-crypto/src/asn1/ASN1Generator.cs | 27 + .../src/asn1/ASN1OctetStringParser.cs | 10 + .../src/asn1/ASN1SequenceParser.cs | 8 + bc-sharp-crypto/src/asn1/ASN1SetParser.cs | 8 + bc-sharp-crypto/src/asn1/ASN1StreamParser.cs | 234 ++ .../src/asn1/ASN1TaggedObjectParser.cs | 10 + bc-sharp-crypto/src/asn1/Asn1Encodable.cs | 78 + .../src/asn1/Asn1EncodableVector.cs | 93 + bc-sharp-crypto/src/asn1/Asn1Exception.cs | 30 + bc-sharp-crypto/src/asn1/Asn1InputStream.cs | 371 ++ bc-sharp-crypto/src/asn1/Asn1Null.cs | 18 + bc-sharp-crypto/src/asn1/Asn1Object.cs | 70 + bc-sharp-crypto/src/asn1/Asn1OctetString.cs | 119 + bc-sharp-crypto/src/asn1/Asn1OutputStream.cs | 35 + .../src/asn1/Asn1ParsingException.cs | 29 + bc-sharp-crypto/src/asn1/Asn1Sequence.cs | 268 ++ bc-sharp-crypto/src/asn1/Asn1Set.cs | 372 ++ bc-sharp-crypto/src/asn1/Asn1TaggedObject.cs | 188 + bc-sharp-crypto/src/asn1/Asn1Tags.cs | 36 + bc-sharp-crypto/src/asn1/BERBitString.cs | 43 + bc-sharp-crypto/src/asn1/BERGenerator.cs | 102 + .../src/asn1/BEROctetStringGenerator.cs | 133 + .../src/asn1/BEROctetStringParser.cs | 36 + .../src/asn1/BERSequenceGenerator.cs | 24 + bc-sharp-crypto/src/asn1/BERSequenceParser.cs | 24 + bc-sharp-crypto/src/asn1/BERSetGenerator.cs | 24 + bc-sharp-crypto/src/asn1/BERSetParser.cs | 24 + .../src/asn1/BERTaggedObjectParser.cs | 71 + .../src/asn1/BerApplicationSpecific.cs | 15 + .../src/asn1/BerApplicationSpecificParser.cs | 29 + bc-sharp-crypto/src/asn1/BerNull.cs | 35 + bc-sharp-crypto/src/asn1/BerOctetString.cs | 135 + bc-sharp-crypto/src/asn1/BerOutputStream.cs | 36 + bc-sharp-crypto/src/asn1/BerSequence.cs | 69 + bc-sharp-crypto/src/asn1/BerSet.cs | 70 + bc-sharp-crypto/src/asn1/BerTaggedObject.cs | 108 + .../src/asn1/ConstructedOctetStream.cs | 102 + bc-sharp-crypto/src/asn1/DERExternal.cs | 202 + bc-sharp-crypto/src/asn1/DERExternalParser.cs | 26 + bc-sharp-crypto/src/asn1/DERGenerator.cs | 107 + .../src/asn1/DEROctetStringParser.cs | 36 + .../src/asn1/DERSequenceGenerator.cs | 40 + bc-sharp-crypto/src/asn1/DERSequenceParser.cs | 24 + bc-sharp-crypto/src/asn1/DERSetGenerator.cs | 40 + bc-sharp-crypto/src/asn1/DERSetParser.cs | 24 + .../src/asn1/DefiniteLengthInputStream.cs | 100 + .../src/asn1/DerApplicationSpecific.cs | 237 ++ bc-sharp-crypto/src/asn1/DerBMPString.cs | 117 + bc-sharp-crypto/src/asn1/DerBitString.cs | 276 ++ bc-sharp-crypto/src/asn1/DerBoolean.cs | 124 + bc-sharp-crypto/src/asn1/DerEnumerated.cs | 135 + bc-sharp-crypto/src/asn1/DerGeneralString.cs | 81 + .../src/asn1/DerGeneralizedTime.cs | 320 ++ bc-sharp-crypto/src/asn1/DerGraphicString.cs | 103 + bc-sharp-crypto/src/asn1/DerIA5String.cs | 145 + bc-sharp-crypto/src/asn1/DerInteger.cs | 128 + bc-sharp-crypto/src/asn1/DerNull.cs | 41 + bc-sharp-crypto/src/asn1/DerNumericString.cs | 138 + .../src/asn1/DerObjectIdentifier.cs | 347 ++ bc-sharp-crypto/src/asn1/DerOctetString.cs | 34 + bc-sharp-crypto/src/asn1/DerOutputStream.cs | 171 + .../src/asn1/DerPrintableString.cs | 163 + bc-sharp-crypto/src/asn1/DerSequence.cs | 88 + bc-sharp-crypto/src/asn1/DerSet.cs | 111 + bc-sharp-crypto/src/asn1/DerStringBase.cs | 22 + bc-sharp-crypto/src/asn1/DerT61String.cs | 102 + bc-sharp-crypto/src/asn1/DerTaggedObject.cs | 72 + bc-sharp-crypto/src/asn1/DerUTCTime.cs | 267 ++ bc-sharp-crypto/src/asn1/DerUTF8String.cs | 98 + .../src/asn1/DerUniversalString.cs | 107 + bc-sharp-crypto/src/asn1/DerVideotexString.cs | 103 + bc-sharp-crypto/src/asn1/DerVisibleString.cs | 111 + .../asn1/IAsn1ApplicationSpecificParser.cs | 10 + bc-sharp-crypto/src/asn1/IAsn1Choice.cs | 17 + bc-sharp-crypto/src/asn1/IAsn1Convertible.cs | 7 + bc-sharp-crypto/src/asn1/IAsn1String.cs | 10 + .../src/asn1/IndefiniteLengthInputStream.cs | 170 + .../src/asn1/LazyASN1InputStream.cs | 33 + bc-sharp-crypto/src/asn1/LazyDERSequence.cs | 80 + bc-sharp-crypto/src/asn1/LazyDERSet.cs | 80 + .../src/asn1/LimitedInputStream.cs | 35 + bc-sharp-crypto/src/asn1/OidTokenizer.cs | 45 + .../src/asn1/anssi/ANSSINamedCurves.cs | 123 + .../src/asn1/anssi/ANSSIObjectIdentifiers.cs | 13 + .../src/asn1/bc/BCObjectIdentifiers.cs | 39 + .../src/asn1/cmp/CAKeyUpdAnnContent.cs | 62 + .../src/asn1/cmp/CertConfirmContent.cs | 49 + bc-sharp-crypto/src/asn1/cmp/CertOrEncCert.cs | 86 + .../src/asn1/cmp/CertRepMessage.cs | 96 + bc-sharp-crypto/src/asn1/cmp/CertResponse.cs | 116 + bc-sharp-crypto/src/asn1/cmp/CertStatus.cs | 85 + .../src/asn1/cmp/CertifiedKeyPair.cs | 115 + bc-sharp-crypto/src/asn1/cmp/Challenge.cs | 80 + .../src/asn1/cmp/CmpCertificate.cs | 81 + .../src/asn1/cmp/CmpObjectIdentifiers.cs | 106 + bc-sharp-crypto/src/asn1/cmp/CrlAnnContent.cs | 50 + .../src/asn1/cmp/ErrorMsgContent.cs | 95 + bc-sharp-crypto/src/asn1/cmp/GenMsgContent.cs | 54 + bc-sharp-crypto/src/asn1/cmp/GenRepContent.cs | 54 + .../src/asn1/cmp/InfoTypeAndValue.cs | 123 + .../src/asn1/cmp/KeyRecRepContent.cs | 117 + bc-sharp-crypto/src/asn1/cmp/OobCertHash.cs | 88 + bc-sharp-crypto/src/asn1/cmp/PKIBody.cs | 187 + .../src/asn1/cmp/PKIConfirmContent.cs | 36 + .../src/asn1/cmp/PKIFailureInfo.cs | 96 + bc-sharp-crypto/src/asn1/cmp/PKIFreeText.cs | 99 + bc-sharp-crypto/src/asn1/cmp/PKIHeader.cs | 238 ++ .../src/asn1/cmp/PKIHeaderBuilder.cs | 223 + bc-sharp-crypto/src/asn1/cmp/PKIMessage.cs | 140 + bc-sharp-crypto/src/asn1/cmp/PKIMessages.cs | 54 + bc-sharp-crypto/src/asn1/cmp/PKIStatus.cs | 63 + bc-sharp-crypto/src/asn1/cmp/PKIStatusInfo.cs | 166 + bc-sharp-crypto/src/asn1/cmp/PbmParameter.cs | 101 + .../src/asn1/cmp/PollRepContent.cs | 68 + .../src/asn1/cmp/PollReqContent.cs | 61 + .../src/asn1/cmp/PopoDecKeyChallContent.cs | 49 + .../src/asn1/cmp/PopoDecKeyRespContent.cs | 49 + bc-sharp-crypto/src/asn1/cmp/ProtectedPart.cs | 60 + bc-sharp-crypto/src/asn1/cmp/RevAnnContent.cs | 87 + bc-sharp-crypto/src/asn1/cmp/RevDetails.cs | 75 + bc-sharp-crypto/src/asn1/cmp/RevRepContent.cs | 113 + .../src/asn1/cmp/RevRepContentBuilder.cs | 55 + bc-sharp-crypto/src/asn1/cmp/RevReqContent.cs | 54 + bc-sharp-crypto/src/asn1/cms/Attribute.cs | 70 + .../src/asn1/cms/AttributeTable.cs | 231 ++ bc-sharp-crypto/src/asn1/cms/Attributes.cs | 55 + .../src/asn1/cms/AuthEnvelopedData.cs | 205 + .../src/asn1/cms/AuthEnvelopedDataParser.cs | 145 + .../src/asn1/cms/AuthenticatedData.cs | 271 ++ .../src/asn1/cms/AuthenticatedDataParser.cs | 182 + bc-sharp-crypto/src/asn1/cms/CMSAttributes.cs | 14 + .../src/asn1/cms/CMSObjectIdentifiers.cs | 28 + .../src/asn1/cms/CompressedData.cs | 96 + .../src/asn1/cms/CompressedDataParser.cs | 47 + bc-sharp-crypto/src/asn1/cms/ContentInfo.cs | 88 + .../src/asn1/cms/ContentInfoParser.cs | 40 + .../src/asn1/cms/EncryptedContentInfo.cs | 94 + .../asn1/cms/EncryptedContentInfoParser.cs | 46 + bc-sharp-crypto/src/asn1/cms/EncryptedData.cs | 97 + bc-sharp-crypto/src/asn1/cms/EnvelopedData.cs | 176 + .../src/asn1/cms/EnvelopedDataParser.cs | 107 + bc-sharp-crypto/src/asn1/cms/Evidence.cs | 49 + .../src/asn1/cms/IssuerAndSerialNumber.cs | 64 + bc-sharp-crypto/src/asn1/cms/KEKIdentifier.cs | 119 + .../src/asn1/cms/KEKRecipientInfo.cs | 106 + .../asn1/cms/KeyAgreeRecipientIdentifier.cs | 94 + .../src/asn1/cms/KeyAgreeRecipientInfo.cs | 141 + .../src/asn1/cms/KeyTransRecipientInfo.cs | 99 + bc-sharp-crypto/src/asn1/cms/MetaData.cs | 94 + .../src/asn1/cms/OriginatorIdentifierOrKey.cs | 168 + .../src/asn1/cms/OriginatorInfo.cs | 121 + .../src/asn1/cms/OriginatorPublicKey.cs | 88 + .../src/asn1/cms/OtherKeyAttribute.cs | 70 + .../src/asn1/cms/OtherRecipientInfo.cs | 83 + .../src/asn1/cms/OtherRevocationInfoFormat.cs | 77 + .../src/asn1/cms/PasswordRecipientInfo.cs | 133 + .../src/asn1/cms/RecipientEncryptedKey.cs | 90 + .../src/asn1/cms/RecipientIdentifier.cs | 89 + bc-sharp-crypto/src/asn1/cms/RecipientInfo.cs | 145 + .../src/asn1/cms/RecipientKeyIdentifier.cs | 137 + bc-sharp-crypto/src/asn1/cms/SCVPReqRes.cs | 77 + bc-sharp-crypto/src/asn1/cms/SignedData.cs | 287 ++ .../src/asn1/cms/SignedDataParser.cs | 114 + .../src/asn1/cms/SignerIdentifier.cs | 89 + bc-sharp-crypto/src/asn1/cms/SignerInfo.cs | 185 + bc-sharp-crypto/src/asn1/cms/Time.cs | 115 + .../src/asn1/cms/TimeStampAndCRL.cs | 62 + .../src/asn1/cms/TimeStampTokenEvidence.cs | 65 + .../src/asn1/cms/TimeStampedData.cs | 95 + .../src/asn1/cms/TimeStampedDataParser.cs | 76 + .../src/asn1/cms/ecc/MQVuserKeyingMaterial.cs | 105 + .../src/asn1/crmf/AttributeTypeAndValue.cs | 68 + bc-sharp-crypto/src/asn1/crmf/CertId.cs | 59 + .../src/asn1/crmf/CertReqMessages.cs | 54 + bc-sharp-crypto/src/asn1/crmf/CertReqMsg.cs | 112 + bc-sharp-crypto/src/asn1/crmf/CertRequest.cs | 82 + bc-sharp-crypto/src/asn1/crmf/CertTemplate.cs | 149 + .../src/asn1/crmf/CertTemplateBuilder.cs | 125 + bc-sharp-crypto/src/asn1/crmf/Controls.cs | 54 + .../src/asn1/crmf/CrmfObjectIdentifiers.cs | 23 + bc-sharp-crypto/src/asn1/crmf/EncKeyWithID.cs | 103 + bc-sharp-crypto/src/asn1/crmf/EncryptedKey.cs | 78 + .../src/asn1/crmf/EncryptedValue.cs | 154 + .../src/asn1/crmf/OptionalValidity.cs | 71 + .../src/asn1/crmf/PKIArchiveOptions.cs | 107 + .../src/asn1/crmf/PKIPublicationInfo.cs | 66 + bc-sharp-crypto/src/asn1/crmf/PKMacValue.cs | 90 + bc-sharp-crypto/src/asn1/crmf/PopoPrivKey.cs | 84 + .../src/asn1/crmf/PopoSigningKey.cs | 116 + .../src/asn1/crmf/PopoSigningKeyInput.cs | 116 + .../src/asn1/crmf/ProofOfPossession.cs | 100 + .../src/asn1/crmf/SinglePubInfo.cs | 59 + .../src/asn1/crmf/SubsequentMessage.cs | 27 + .../cryptopro/CryptoProObjectIdentifiers.cs | 51 + .../asn1/cryptopro/ECGOST3410NamedCurves.cs | 184 + .../cryptopro/ECGOST3410ParamSetParameters.cs | 87 + .../src/asn1/cryptopro/GOST28147Parameters.cs | 63 + .../asn1/cryptopro/GOST3410NamedParameters.cs | 123 + .../cryptopro/GOST3410ParamSetParameters.cs | 87 + .../GOST3410PublicKeyAlgParameters.cs | 99 + .../src/asn1/eac/EACObjectIdentifiers.cs | 50 + .../src/asn1/esf/CertificateValues.cs | 86 + .../src/asn1/esf/CommitmentTypeIdentifier.cs | 17 + .../src/asn1/esf/CommitmentTypeIndication.cs | 95 + .../src/asn1/esf/CommitmentTypeQualifier.cs | 119 + .../src/asn1/esf/CompleteCertificateRefs.cs | 85 + .../src/asn1/esf/CompleteRevocationRefs.cs | 85 + bc-sharp-crypto/src/asn1/esf/CrlIdentifier.cs | 111 + bc-sharp-crypto/src/asn1/esf/CrlListID.cs | 90 + bc-sharp-crypto/src/asn1/esf/CrlOcspRef.cs | 113 + .../src/asn1/esf/CrlValidatedID.cs | 91 + bc-sharp-crypto/src/asn1/esf/ESFAttributes.cs | 25 + .../src/asn1/esf/OcspIdentifier.cs | 78 + bc-sharp-crypto/src/asn1/esf/OcspListID.cs | 89 + .../src/asn1/esf/OcspResponsesID.cs | 94 + bc-sharp-crypto/src/asn1/esf/OtherCertID.cs | 94 + bc-sharp-crypto/src/asn1/esf/OtherHash.cs | 88 + .../src/asn1/esf/OtherHashAlgAndValue.cs | 95 + bc-sharp-crypto/src/asn1/esf/OtherRevRefs.cs | 80 + bc-sharp-crypto/src/asn1/esf/OtherRevVals.cs | 80 + .../src/asn1/esf/OtherSigningCertificate.cs | 139 + .../src/asn1/esf/RevocationValues.cs | 165 + .../src/asn1/esf/SigPolicyQualifierInfo.cs | 73 + .../src/asn1/esf/SignaturePolicyId.cs | 146 + .../src/asn1/esf/SignaturePolicyIdentifier.cs | 66 + .../src/asn1/esf/SignerAttribute.cs | 97 + .../src/asn1/esf/SignerLocation.cs | 144 + bc-sharp-crypto/src/asn1/ess/ContentHints.cs | 94 + .../src/asn1/ess/ContentIdentifier.cs | 67 + bc-sharp-crypto/src/asn1/ess/ESSCertID.cs | 94 + bc-sharp-crypto/src/asn1/ess/ESSCertIDv2.cs | 146 + bc-sharp-crypto/src/asn1/ess/OtherCertID.cs | 134 + .../src/asn1/ess/OtherSigningCertificate.cs | 110 + .../src/asn1/ess/SigningCertificate.cs | 109 + .../src/asn1/ess/SigningCertificateV2.cs | 113 + bc-sharp-crypto/src/asn1/gm/GMNamedCurves.cs | 157 + .../src/asn1/gm/GMObjectIdentifiers.cs | 85 + .../src/asn1/gnu/GNUObjectIdentifiers.cs | 36 + .../src/asn1/iana/IANAObjectIdentifiers.cs | 18 + .../src/asn1/icao/CscaMasterList.cs | 83 + .../src/asn1/icao/DataGroupHash.cs | 86 + .../src/asn1/icao/ICAOObjectIdentifiers.cs | 34 + .../src/asn1/icao/LDSSecurityObject.cs | 145 + .../src/asn1/icao/LDSVersionInfo.cs | 61 + .../asn1/isismtt/ISISMTTObjectIdentifiers.cs | 177 + .../src/asn1/isismtt/ocsp/CertHash.cs | 122 + .../asn1/isismtt/ocsp/RequestedCertificate.cs | 188 + .../x509/AdditionalInformationSyntax.cs | 71 + .../src/asn1/isismtt/x509/AdmissionSyntax.cs | 278 ++ .../src/asn1/isismtt/x509/Admissions.cs | 187 + .../isismtt/x509/DeclarationOfMajority.cs | 172 + .../src/asn1/isismtt/x509/MonetaryLimit.cs | 122 + .../src/asn1/isismtt/x509/NamingAuthority.cs | 215 + .../asn1/isismtt/x509/ProcurationSyntax.cs | 233 ++ .../src/asn1/isismtt/x509/ProfessionInfo.cs | 387 ++ .../src/asn1/isismtt/x509/Restriction.cs | 82 + .../src/asn1/kisa/KISAObjectIdentifiers.cs | 8 + .../microsoft/MicrosoftObjectIdentifiers.cs | 19 + .../src/asn1/misc/CAST5CBCParameters.cs | 74 + bc-sharp-crypto/src/asn1/misc/IDEACBCPar.cs | 68 + .../src/asn1/misc/MiscObjectIdentifiers.cs | 79 + .../src/asn1/misc/NetscapeCertType.cs | 54 + .../src/asn1/misc/NetscapeRevocationURL.cs | 18 + .../src/asn1/misc/VerisignCzagExtension.cs | 18 + .../src/asn1/mozilla/PublicKeyAndChallenge.cs | 68 + .../src/asn1/nist/NISTNamedCurves.cs | 102 + .../src/asn1/nist/NISTObjectIdentifiers.cs | 71 + .../src/asn1/ntt/NTTObjectIdentifiers.cs | 14 + .../src/asn1/ocsp/BasicOCSPResponse.cs | 137 + bc-sharp-crypto/src/asn1/ocsp/CertID.cs | 99 + bc-sharp-crypto/src/asn1/ocsp/CertStatus.cs | 96 + bc-sharp-crypto/src/asn1/ocsp/CrlID.cs | 82 + .../src/asn1/ocsp/OCSPObjectIdentifiers.cs | 23 + bc-sharp-crypto/src/asn1/ocsp/OCSPRequest.cs | 89 + bc-sharp-crypto/src/asn1/ocsp/OCSPResponse.cs | 90 + .../src/asn1/ocsp/OCSPResponseStatus.cs | 41 + bc-sharp-crypto/src/asn1/ocsp/Request.cs | 91 + bc-sharp-crypto/src/asn1/ocsp/ResponderID.cs | 107 + .../src/asn1/ocsp/ResponseBytes.cs | 82 + bc-sharp-crypto/src/asn1/ocsp/ResponseData.cs | 158 + bc-sharp-crypto/src/asn1/ocsp/RevokedInfo.cs | 96 + .../src/asn1/ocsp/ServiceLocator.cs | 95 + bc-sharp-crypto/src/asn1/ocsp/Signature.cs | 115 + .../src/asn1/ocsp/SingleResponse.cs | 137 + bc-sharp-crypto/src/asn1/ocsp/TBSRequest.cs | 151 + .../src/asn1/oiw/ElGamalParameter.cs | 47 + .../src/asn1/oiw/OIWObjectIdentifiers.cs | 29 + bc-sharp-crypto/src/asn1/pkcs/Attribute.cs | 79 + .../src/asn1/pkcs/AuthenticatedSafe.cs | 37 + bc-sharp-crypto/src/asn1/pkcs/CertBag.cs | 46 + .../src/asn1/pkcs/CertificationRequest.cs | 87 + .../src/asn1/pkcs/CertificationRequestInfo.cs | 137 + bc-sharp-crypto/src/asn1/pkcs/ContentInfo.cs | 74 + bc-sharp-crypto/src/asn1/pkcs/DHParameter.cs | 72 + .../src/asn1/pkcs/EncryptedData.cs | 105 + .../src/asn1/pkcs/EncryptedPrivateKeyInfo.cs | 79 + .../src/asn1/pkcs/EncryptionScheme.cs | 49 + .../src/asn1/pkcs/IssuerAndSerialNumber.cs | 72 + .../src/asn1/pkcs/KeyDerivationFunc.cs | 21 + bc-sharp-crypto/src/asn1/pkcs/MacData.cs | 96 + bc-sharp-crypto/src/asn1/pkcs/PBEParameter.cs | 60 + .../src/asn1/pkcs/PBES2Parameters.cs | 65 + bc-sharp-crypto/src/asn1/pkcs/PBKDF2Params.cs | 144 + .../src/asn1/pkcs/PKCS12PBEParams.cs | 63 + .../src/asn1/pkcs/PKCSObjectIdentifiers.cs | 293 ++ bc-sharp-crypto/src/asn1/pkcs/Pfx.cs | 65 + .../src/asn1/pkcs/PrivateKeyInfo.cs | 135 + .../src/asn1/pkcs/RC2CBCParameter.cs | 80 + .../src/asn1/pkcs/RSAESOAEPparams.cs | 146 + .../src/asn1/pkcs/RSAPrivateKeyStructure.cs | 146 + .../src/asn1/pkcs/RSASSAPSSparams.cs | 166 + bc-sharp-crypto/src/asn1/pkcs/SafeBag.cs | 70 + bc-sharp-crypto/src/asn1/pkcs/SignedData.cs | 157 + bc-sharp-crypto/src/asn1/pkcs/SignerInfo.cs | 154 + .../src/asn1/sec/ECPrivateKeyStructure.cs | 184 + .../src/asn1/sec/SECNamedCurves.cs | 1184 ++++++ .../src/asn1/sec/SECObjectIdentifiers.cs | 52 + .../src/asn1/smime/SMIMEAttributes.cs | 11 + .../src/asn1/smime/SMIMECapabilities.cs | 134 + .../asn1/smime/SMIMECapabilitiesAttribute.cs | 16 + .../src/asn1/smime/SMIMECapability.cs | 101 + .../src/asn1/smime/SMIMECapabilityVector.cs | 37 + .../SMIMEEncryptionKeyPreferenceAttribute.cs | 44 + .../asn1/teletrust/TeleTrusTNamedCurves.cs | 470 +++ .../teletrust/TeleTrusTObjectIdentifiers.cs | 45 + bc-sharp-crypto/src/asn1/tsp/Accuracy.cs | 151 + .../src/asn1/tsp/MessageImprint.cs | 75 + bc-sharp-crypto/src/asn1/tsp/TSTInfo.cs | 250 ++ bc-sharp-crypto/src/asn1/tsp/TimeStampReq.cs | 165 + bc-sharp-crypto/src/asn1/tsp/TimeStampResp.cs | 80 + bc-sharp-crypto/src/asn1/util/Asn1Dump.cs | 381 ++ bc-sharp-crypto/src/asn1/util/Dump.cs | 31 + bc-sharp-crypto/src/asn1/util/FilterStream.cs | 83 + .../src/asn1/x500/DirectoryString.cs | 77 + .../src/asn1/x509/AccessDescription.cs | 85 + .../src/asn1/x509/AlgorithmIdentifier.cs | 96 + .../src/asn1/x509/AttCertIssuer.cs | 86 + .../src/asn1/x509/AttCertValidityPeriod.cs | 78 + bc-sharp-crypto/src/asn1/x509/Attribute.cs | 82 + .../src/asn1/x509/AttributeCertificate.cs | 86 + .../src/asn1/x509/AttributeCertificateInfo.cs | 156 + .../src/asn1/x509/AttributeTable.cs | 73 + .../asn1/x509/AuthorityInformationAccess.cs | 98 + .../src/asn1/x509/AuthorityKeyIdentifier.cs | 211 + .../src/asn1/x509/BasicConstraints.cs | 133 + bc-sharp-crypto/src/asn1/x509/CRLDistPoint.cs | 93 + bc-sharp-crypto/src/asn1/x509/CRLNumber.cs | 30 + bc-sharp-crypto/src/asn1/x509/CRLReason.cs | 61 + bc-sharp-crypto/src/asn1/x509/CertPolicyId.cs | 20 + .../src/asn1/x509/CertificateList.cs | 113 + .../src/asn1/x509/CertificatePair.cs | 162 + .../src/asn1/x509/CertificatePolicies.cs | 81 + bc-sharp-crypto/src/asn1/x509/DSAParameter.cs | 78 + bc-sharp-crypto/src/asn1/x509/DigestInfo.cs | 78 + bc-sharp-crypto/src/asn1/x509/DisplayText.cs | 174 + .../src/asn1/x509/DistributionPoint.cs | 161 + .../src/asn1/x509/DistributionPointName.cs | 130 + .../src/asn1/x509/ExtendedKeyUsage.cs | 132 + bc-sharp-crypto/src/asn1/x509/GeneralName.cs | 419 ++ bc-sharp-crypto/src/asn1/x509/GeneralNames.cs | 95 + .../src/asn1/x509/GeneralSubtree.cs | 189 + bc-sharp-crypto/src/asn1/x509/Holder.cs | 259 ++ .../src/asn1/x509/IetfAttrSyntax.cs | 161 + bc-sharp-crypto/src/asn1/x509/IssuerSerial.cs | 100 + .../src/asn1/x509/IssuingDistributionPoint.cs | 247 ++ bc-sharp-crypto/src/asn1/x509/KeyPurposeId.cs | 38 + bc-sharp-crypto/src/asn1/x509/KeyUsage.cs | 78 + .../src/asn1/x509/NameConstraints.cs | 120 + .../src/asn1/x509/NoticeReference.cs | 143 + .../src/asn1/x509/ObjectDigestInfo.cs | 179 + .../src/asn1/x509/PolicyInformation.cs | 80 + .../src/asn1/x509/PolicyMappings.cs | 70 + .../src/asn1/x509/PolicyQualifierId.cs | 28 + .../src/asn1/x509/PolicyQualifierInfo.cs | 95 + .../src/asn1/x509/PrivateKeyUsagePeriod.cs | 84 + .../src/asn1/x509/RSAPublicKeyStructure.cs | 93 + bc-sharp-crypto/src/asn1/x509/ReasonFlags.cs | 45 + bc-sharp-crypto/src/asn1/x509/RoleSyntax.cs | 230 ++ .../asn1/x509/SubjectDirectoryAttributes.cs | 142 + .../src/asn1/x509/SubjectKeyIdentifier.cs | 142 + .../src/asn1/x509/SubjectPublicKeyInfo.cs | 102 + bc-sharp-crypto/src/asn1/x509/TBSCertList.cs | 275 ++ .../src/asn1/x509/TBSCertificateStructure.cs | 185 + bc-sharp-crypto/src/asn1/x509/Target.cs | 141 + .../src/asn1/x509/TargetInformation.cs | 125 + bc-sharp-crypto/src/asn1/x509/Targets.cs | 123 + bc-sharp-crypto/src/asn1/x509/Time.cs | 122 + bc-sharp-crypto/src/asn1/x509/UserNotice.cs | 130 + .../asn1/x509/V1TBSCertificateGenerator.cs | 108 + .../V2AttributeCertificateInfoGenerator.cs | 137 + bc-sharp-crypto/src/asn1/x509/V2Form.cs | 137 + .../src/asn1/x509/V2TBSCertListGenerator.cs | 201 + .../asn1/x509/V3TBSCertificateGenerator.cs | 168 + .../src/asn1/x509/X509Attributes.cs | 9 + .../src/asn1/x509/X509CertificateStructure.cs | 132 + .../asn1/x509/X509DefaultEntryConverter.cs | 63 + .../src/asn1/x509/X509Extension.cs | 79 + .../src/asn1/x509/X509Extensions.cs | 456 +++ .../src/asn1/x509/X509ExtensionsGenerator.cs | 81 + bc-sharp-crypto/src/asn1/x509/X509Name.cs | 1077 +++++ .../src/asn1/x509/X509NameEntryConverter.cs | 89 + .../src/asn1/x509/X509NameTokenizer.cs | 104 + .../src/asn1/x509/X509ObjectIdentifiers.cs | 59 + .../src/asn1/x509/qualified/BiometricData.cs | 110 + .../x509/qualified/ETSIQCObjectIdentifiers.cs | 19 + .../x509/qualified/Iso4217CurrencyCode.cs | 84 + .../src/asn1/x509/qualified/MonetaryValue.cs | 83 + .../src/asn1/x509/qualified/QCStatement.cs | 84 + .../qualified/RFC3739QCObjectIdentifiers.cs | 21 + .../x509/qualified/SemanticsInformation.cs | 124 + .../x509/qualified/TypeOfBiometricData.cs | 91 + .../src/asn1/x509/sigi/NameOrPseudonym.cs | 178 + .../src/asn1/x509/sigi/PersonalData.cs | 211 + .../asn1/x509/sigi/SigIObjectIdentifiers.cs | 49 + .../src/asn1/x9/DHDomainParameters.cs | 118 + bc-sharp-crypto/src/asn1/x9/DHPublicKey.cs | 46 + .../src/asn1/x9/DHValidationParms.cs | 64 + .../src/asn1/x9/ECNamedCurveTable.cs | 162 + .../src/asn1/x9/KeySpecificInfo.cs | 58 + bc-sharp-crypto/src/asn1/x9/OtherInfo.cs | 88 + .../src/asn1/x9/X962NamedCurves.cs | 751 ++++ bc-sharp-crypto/src/asn1/x9/X962Parameters.cs | 88 + bc-sharp-crypto/src/asn1/x9/X9Curve.cs | 146 + bc-sharp-crypto/src/asn1/x9/X9ECParameters.cs | 233 ++ .../src/asn1/x9/X9ECParametersHolder.cs | 25 + bc-sharp-crypto/src/asn1/x9/X9ECPoint.cs | 80 + bc-sharp-crypto/src/asn1/x9/X9FieldElement.cs | 69 + bc-sharp-crypto/src/asn1/x9/X9FieldID.cs | 132 + .../src/asn1/x9/X9IntegerConverter.cs | 40 + .../src/asn1/x9/X9ObjectIdentifiers.cs | 137 + .../src/bcpg/ArmoredInputStream.cs | 524 +++ .../src/bcpg/ArmoredOutputStream.cs | 375 ++ bc-sharp-crypto/src/bcpg/BcpgInputStream.cs | 363 ++ bc-sharp-crypto/src/bcpg/BcpgObject.cs | 22 + bc-sharp-crypto/src/bcpg/BcpgOutputStream.cs | 404 ++ .../src/bcpg/CompressedDataPacket.cs | 24 + .../src/bcpg/CompressionAlgorithmTags.cs | 11 + bc-sharp-crypto/src/bcpg/ContainedPacket.cs | 22 + bc-sharp-crypto/src/bcpg/Crc24.cs | 46 + bc-sharp-crypto/src/bcpg/DsaPublicBcpgKey.cs | 80 + bc-sharp-crypto/src/bcpg/DsaSecretBcpgKey.cs | 61 + bc-sharp-crypto/src/bcpg/ECDHPublicBCPGKey.cs | 102 + .../src/bcpg/ECDsaPublicBCPGKey.cs | 34 + bc-sharp-crypto/src/bcpg/ECPublicBCPGKey.cs | 97 + bc-sharp-crypto/src/bcpg/ECSecretBCPGKey.cs | 56 + .../src/bcpg/ElGamalPublicBcpgKey.cs | 71 + .../src/bcpg/ElGamalSecretBcpgKey.cs | 61 + .../src/bcpg/ExperimentalPacket.cs | 38 + bc-sharp-crypto/src/bcpg/HashAlgorithmTags.cs | 19 + bc-sharp-crypto/src/bcpg/IBcpgKey.cs | 16 + bc-sharp-crypto/src/bcpg/InputStreamPacket.cs | 20 + bc-sharp-crypto/src/bcpg/LiteralDataPacket.cs | 57 + bc-sharp-crypto/src/bcpg/MPInteger.cs | 59 + bc-sharp-crypto/src/bcpg/MarkerPacket.cs | 24 + .../src/bcpg/ModDetectionCodePacket.cs | 42 + .../src/bcpg/OnePassSignaturePacket.cs | 93 + .../src/bcpg/OutputStreamPacket.cs | 24 + bc-sharp-crypto/src/bcpg/Packet.cs | 7 + bc-sharp-crypto/src/bcpg/PacketTags.cs | 30 + .../src/bcpg/PublicKeyAlgorithmTags.cs | 32 + .../src/bcpg/PublicKeyEncSessionPacket.cs | 115 + bc-sharp-crypto/src/bcpg/PublicKeyPacket.cs | 121 + .../src/bcpg/PublicSubkeyPacket.cs | 30 + bc-sharp-crypto/src/bcpg/RsaPublicBcpgKey.cs | 66 + bc-sharp-crypto/src/bcpg/RsaSecretBcpgKey.cs | 114 + bc-sharp-crypto/src/bcpg/S2k.cs | 149 + bc-sharp-crypto/src/bcpg/SecretKeyPacket.cs | 170 + .../src/bcpg/SecretSubkeyPacket.cs | 43 + bc-sharp-crypto/src/bcpg/SignaturePacket.cs | 477 +++ .../src/bcpg/SignatureSubpacket.cs | 94 + .../src/bcpg/SignatureSubpacketTags.cs | 33 + .../src/bcpg/SignatureSubpacketsReader.cs | 128 + .../src/bcpg/SymmetricEncDataPacket.cs | 15 + .../src/bcpg/SymmetricEncIntegrityPacket.cs | 18 + .../src/bcpg/SymmetricKeyAlgorithmTags.cs | 23 + .../src/bcpg/SymmetricKeyEncSessionPacket.cs | 91 + bc-sharp-crypto/src/bcpg/TrustPacket.cs | 43 + .../src/bcpg/UserAttributePacket.cs | 61 + .../src/bcpg/UserAttributeSubpacket.cs | 90 + .../src/bcpg/UserAttributeSubpacketTags.cs | 10 + .../src/bcpg/UserAttributeSubpacketsReader.cs | 65 + bc-sharp-crypto/src/bcpg/UserIdPacket.cs | 37 + bc-sharp-crypto/src/bcpg/attr/ImageAttrib.cs | 72 + .../src/bcpg/sig/EmbeddedSignature.cs | 19 + bc-sharp-crypto/src/bcpg/sig/Exportable.cs | 46 + bc-sharp-crypto/src/bcpg/sig/Features.cs | 75 + bc-sharp-crypto/src/bcpg/sig/IssuerKeyId.cs | 62 + .../src/bcpg/sig/KeyExpirationTime.cs | 55 + bc-sharp-crypto/src/bcpg/sig/KeyFlags.cs | 75 + bc-sharp-crypto/src/bcpg/sig/NotationData.cs | 113 + .../src/bcpg/sig/PreferredAlgorithms.cs | 53 + bc-sharp-crypto/src/bcpg/sig/PrimaryUserId.cs | 47 + bc-sharp-crypto/src/bcpg/sig/Revocable.cs | 47 + bc-sharp-crypto/src/bcpg/sig/RevocationKey.cs | 63 + .../src/bcpg/sig/RevocationKeyTags.cs | 9 + .../src/bcpg/sig/RevocationReason.cs | 59 + .../src/bcpg/sig/RevocationReasonTags.cs | 14 + .../src/bcpg/sig/SignatureCreationTime.cs | 51 + .../src/bcpg/sig/SignatureExpirationTime.cs | 51 + bc-sharp-crypto/src/bcpg/sig/SignerUserId.cs | 53 + .../src/bcpg/sig/TrustSignature.cs | 44 + .../src/cms/BaseDigestCalculator.cs | 23 + .../CMSAttributeTableGenerationException.cs | 28 + .../src/cms/CMSAttributeTableGenerator.cs | 25 + .../src/cms/CMSAuthEnvelopedData.cs | 112 + .../src/cms/CMSAuthEnvelopedGenerator.cs | 16 + .../src/cms/CMSAuthenticatedData.cs | 137 + .../src/cms/CMSAuthenticatedDataGenerator.cs | 156 + .../src/cms/CMSAuthenticatedDataParser.cs | 214 + .../CMSAuthenticatedDataStreamGenerator.cs | 297 ++ .../src/cms/CMSAuthenticatedGenerator.cs | 35 + bc-sharp-crypto/src/cms/CMSCompressedData.cs | 108 + .../src/cms/CMSCompressedDataGenerator.cs | 67 + .../src/cms/CMSCompressedDataParser.cs | 57 + .../cms/CMSCompressedDataStreamGenerator.cs | 158 + .../src/cms/CMSContentInfoParser.cs | 48 + bc-sharp-crypto/src/cms/CMSEnvelopedData.cs | 115 + .../src/cms/CMSEnvelopedDataGenerator.cs | 178 + .../src/cms/CMSEnvelopedDataParser.cs | 161 + .../cms/CMSEnvelopedDataStreamGenerator.cs | 308 ++ .../src/cms/CMSEnvelopedGenerator.cs | 331 ++ bc-sharp-crypto/src/cms/CMSEnvelopedHelper.cs | 311 ++ bc-sharp-crypto/src/cms/CMSException.cs | 28 + bc-sharp-crypto/src/cms/CMSPBEKey.cs | 109 + bc-sharp-crypto/src/cms/CMSProcessable.cs | 19 + .../src/cms/CMSProcessableByteArray.cs | 36 + bc-sharp-crypto/src/cms/CMSProcessableFile.cs | 52 + .../src/cms/CMSProcessableInputStream.cs | 53 + bc-sharp-crypto/src/cms/CMSReadable.cs | 10 + bc-sharp-crypto/src/cms/CMSSecureReadable.cs | 14 + bc-sharp-crypto/src/cms/CMSSignedData.cs | 425 ++ .../src/cms/CMSSignedDataGenerator.cs | 585 +++ .../src/cms/CMSSignedDataParser.cs | 450 +++ .../src/cms/CMSSignedDataStreamGenerator.cs | 929 +++++ bc-sharp-crypto/src/cms/CMSSignedGenerator.cs | 267 ++ bc-sharp-crypto/src/cms/CMSSignedHelper.cs | 426 ++ bc-sharp-crypto/src/cms/CMSStreamException.cs | 29 + bc-sharp-crypto/src/cms/CMSTypedStream.cs | 72 + bc-sharp-crypto/src/cms/CMSUtils.cs | 186 + .../cms/CounterSignatureDigestCalculator.cs | 28 + ...ultAuthenticatedAttributeTableGenerator.cs | 90 + .../DefaultSignedAttributeTableGenerator.cs | 124 + bc-sharp-crypto/src/cms/DigOutputStream.cs | 28 + bc-sharp-crypto/src/cms/IDigestCalculator.cs | 9 + .../src/cms/KEKRecipientInfoGenerator.cs | 138 + .../src/cms/KEKRecipientInformation.cs | 62 + .../src/cms/KeyAgreeRecipientInfoGenerator.cs | 171 + .../src/cms/KeyAgreeRecipientInformation.cs | 226 ++ .../src/cms/KeyTransRecipientInfoGenerator.cs | 87 + .../src/cms/KeyTransRecipientInformation.cs | 113 + bc-sharp-crypto/src/cms/MacOutputStream.cs | 28 + bc-sharp-crypto/src/cms/OriginatorId.cs | 51 + .../src/cms/OriginatorInfoGenerator.cs | 42 + .../src/cms/OriginatorInformation.cs | 96 + bc-sharp-crypto/src/cms/PKCS5Scheme2PBEKey.cs | 64 + .../src/cms/PKCS5Scheme2UTF8PBEKey.cs | 64 + .../src/cms/PasswordRecipientInfoGenerator.cs | 70 + .../src/cms/PasswordRecipientInformation.cs | 79 + bc-sharp-crypto/src/cms/RecipientId.cs | 58 + .../src/cms/RecipientInfoGenerator.cs | 26 + .../src/cms/RecipientInformation.cs | 126 + .../src/cms/RecipientInformationStore.cs | 86 + bc-sharp-crypto/src/cms/SigOutputStream.cs | 43 + bc-sharp-crypto/src/cms/SignerId.cs | 51 + .../src/cms/SignerInfoGenerator.cs | 166 + bc-sharp-crypto/src/cms/SignerInformation.cs | 761 ++++ .../src/cms/SignerInformationStore.cs | 95 + .../src/cms/SimpleAttributeTableGenerator.cs | 28 + .../src/crypto/AsymmetricCipherKeyPair.cs | 52 + .../src/crypto/AsymmetricKeyParameter.cs | 47 + .../src/crypto/BufferedAeadBlockCipher.cs | 247 ++ .../crypto/BufferedAsymmetricBlockCipher.cs | 152 + .../src/crypto/BufferedBlockCipher.cs | 367 ++ .../src/crypto/BufferedCipherBase.cs | 113 + .../src/crypto/BufferedIesCipher.cs | 113 + .../src/crypto/BufferedStreamCipher.cs | 131 + bc-sharp-crypto/src/crypto/Check.cs | 25 + .../src/crypto/CipherKeyGenerator.cs | 83 + bc-sharp-crypto/src/crypto/CryptoException.cs | 28 + .../src/crypto/DataLengthException.cs | 42 + .../src/crypto/IAsymmetricBlockCipher.cs | 30 + .../IAsymmetricCipherKeyPairGenerator.cs | 24 + bc-sharp-crypto/src/crypto/IBasicAgreement.cs | 29 + bc-sharp-crypto/src/crypto/IBlockCipher.cs | 36 + bc-sharp-crypto/src/crypto/IBlockResult.cs | 24 + bc-sharp-crypto/src/crypto/IBufferedCipher.cs | 44 + .../src/crypto/ICipherParameters.cs | 11 + bc-sharp-crypto/src/crypto/IDSA.cs | 40 + .../src/crypto/IDerivationFunction.cs | 24 + .../src/crypto/IDerivationParameters.cs | 11 + bc-sharp-crypto/src/crypto/IDigest.cs | 61 + bc-sharp-crypto/src/crypto/IEntropySource.cs | 29 + .../src/crypto/IEntropySourceProvider.cs | 17 + bc-sharp-crypto/src/crypto/IMac.cs | 69 + .../src/crypto/ISignatureFactory.cs | 23 + bc-sharp-crypto/src/crypto/ISigner.cs | 50 + .../src/crypto/ISignerWithRecovery.cs | 37 + .../src/crypto/IStreamCalculator.cs | 23 + bc-sharp-crypto/src/crypto/IStreamCipher.cs | 45 + bc-sharp-crypto/src/crypto/IVerifier.cs | 25 + .../src/crypto/IVerifierFactory.cs | 21 + .../src/crypto/IVerifierFactoryProvider.cs | 18 + bc-sharp-crypto/src/crypto/IWrapper.cs | 18 + bc-sharp-crypto/src/crypto/IXof.cs | 31 + .../src/crypto/InvalidCipherTextException.cs | 40 + .../src/crypto/KeyGenerationParameters.cs | 55 + .../src/crypto/MaxBytesExceededException.cs | 32 + .../src/crypto/OutputLengthException.cs | 28 + .../src/crypto/PbeParametersGenerator.cs | 202 + .../src/crypto/StreamBlockCipher.cs | 109 + .../src/crypto/agreement/DHAgreement.cs | 99 + .../src/crypto/agreement/DHBasicAgreement.cs | 72 + .../src/crypto/agreement/DHStandardGroups.cs | 307 ++ .../crypto/agreement/ECDHBasicAgreement.cs | 60 + .../crypto/agreement/ECDHCBasicAgreement.cs | 68 + .../agreement/ECDHWithKdfBasicAgreement.cs | 63 + .../crypto/agreement/ECMqvBasicAgreement.cs | 93 + .../agreement/ECMqvWithKdfBasicAgreement.cs | 63 + .../agreement/jpake/JPakeParticipant.cs | 456 +++ .../agreement/jpake/JPakePrimeOrderGroup.cs | 103 + .../agreement/jpake/JPakePrimeOrderGroups.cs | 108 + .../agreement/jpake/JPakeRound1Payload.cs | 101 + .../agreement/jpake/JPakeRound2Payload.cs | 72 + .../agreement/jpake/JPakeRound3Payload.cs | 51 + .../crypto/agreement/jpake/JPakeUtilities.cs | 390 ++ .../crypto/agreement/kdf/DHKdfParameters.cs | 57 + .../crypto/agreement/kdf/DHKekGenerator.cs | 112 + .../crypto/agreement/kdf/ECDHKekGenerator.cs | 55 + .../src/crypto/agreement/srp/SRP6Client.cs | 164 + .../src/crypto/agreement/srp/SRP6Server.cs | 163 + .../agreement/srp/SRP6StandardGroups.cs | 159 + .../src/crypto/agreement/srp/SRP6Utilities.cs | 153 + .../agreement/srp/SRP6VerifierGenerator.cs | 55 + .../src/crypto/digests/DSTU7564Digest.cs | 562 +++ .../src/crypto/digests/GOST3411Digest.cs | 356 ++ .../src/crypto/digests/GOST3411_2012Digest.cs | 1036 +++++ .../crypto/digests/GOST3411_2012_256Digest.cs | 54 + .../crypto/digests/GOST3411_2012_512Digest.cs | 43 + .../src/crypto/digests/GeneralDigest.cs | 133 + .../src/crypto/digests/KeccakDigest.cs | 479 +++ .../src/crypto/digests/LongDigest.cs | 355 ++ .../src/crypto/digests/MD2Digest.cs | 269 ++ .../src/crypto/digests/MD4Digest.cs | 292 ++ .../src/crypto/digests/MD5Digest.cs | 313 ++ .../src/crypto/digests/NonMemoableDigest.cs | 62 + .../src/crypto/digests/NullDigest.cs | 49 + .../src/crypto/digests/RipeMD128Digest.cs | 484 +++ .../src/crypto/digests/RipeMD160Digest.cs | 445 ++ .../src/crypto/digests/RipeMD256Digest.cs | 430 ++ .../src/crypto/digests/RipeMD320Digest.cs | 459 +++ .../src/crypto/digests/SHA3Digest.cs | 85 + .../src/crypto/digests/SM3Digest.cs | 328 ++ .../src/crypto/digests/Sha1Digest.cs | 284 ++ .../src/crypto/digests/Sha224Digest.cs | 289 ++ .../src/crypto/digests/Sha256Digest.cs | 330 ++ .../src/crypto/digests/Sha384Digest.cs | 101 + .../src/crypto/digests/Sha512Digest.cs | 104 + .../src/crypto/digests/Sha512tDigest.cs | 200 + .../src/crypto/digests/ShakeDigest.cs | 119 + .../src/crypto/digests/ShortenedDigest.cs | 82 + .../src/crypto/digests/SkeinDigest.cs | 117 + .../src/crypto/digests/SkeinEngine.cs | 804 ++++ .../src/crypto/digests/TigerDigest.cs | 883 ++++ .../src/crypto/digests/WhirlpoolDigest.cs | 413 ++ .../src/crypto/ec/CustomNamedCurves.cs | 913 +++++ .../src/crypto/encodings/ISO9796d1Encoding.cs | 273 ++ .../src/crypto/encodings/OaepEncoding.cs | 345 ++ .../src/crypto/encodings/Pkcs1Encoding.cs | 384 ++ .../src/crypto/engines/AesEngine.cs | 610 +++ .../src/crypto/engines/AesFastEngine.cs | 948 +++++ .../src/crypto/engines/AesLightEngine.cs | 504 +++ .../src/crypto/engines/AesWrapEngine.cs | 16 + .../src/crypto/engines/BlowfishEngine.cs | 553 +++ .../src/crypto/engines/CamelliaEngine.cs | 668 +++ .../src/crypto/engines/CamelliaLightEngine.cs | 580 +++ .../src/crypto/engines/CamelliaWrapEngine.cs | 16 + .../src/crypto/engines/Cast5Engine.cs | 802 ++++ .../src/crypto/engines/Cast6Engine.cs | 279 ++ .../src/crypto/engines/ChaCha7539Engine.cs | 65 + .../src/crypto/engines/ChaChaEngine.cs | 157 + .../src/crypto/engines/DesEdeEngine.cs | 100 + .../src/crypto/engines/DesEdeWrapEngine.cs | 322 ++ .../src/crypto/engines/DesEngine.cs | 475 +++ .../src/crypto/engines/Dstu7624Engine.cs | 766 ++++ .../src/crypto/engines/Dstu7624WrapEngine.cs | 216 + .../src/crypto/engines/ElGamalEngine.cs | 178 + .../src/crypto/engines/GOST28147Engine.cs | 368 ++ .../src/crypto/engines/HC128Engine.cs | 235 ++ .../src/crypto/engines/HC256Engine.cs | 224 + .../src/crypto/engines/ISAACEngine.cs | 212 + .../src/crypto/engines/IdeaEngine.cs | 332 ++ .../src/crypto/engines/IesEngine.cs | 243 ++ .../src/crypto/engines/NaccacheSternEngine.cs | 358 ++ .../src/crypto/engines/NoekeonEngine.cs | 241 ++ .../src/crypto/engines/NullEngine.cs | 69 + .../src/crypto/engines/RC2Engine.cs | 311 ++ .../src/crypto/engines/RC2WrapEngine.cs | 370 ++ .../src/crypto/engines/RC4Engine.cs | 139 + .../src/crypto/engines/RC532Engine.cs | 294 ++ .../src/crypto/engines/RC564Engine.cs | 295 ++ .../src/crypto/engines/RC6Engine.cs | 361 ++ .../src/crypto/engines/RFC3211WrapEngine.cs | 168 + .../src/crypto/engines/RFC3394WrapEngine.cs | 178 + .../src/crypto/engines/RSABlindedEngine.cs | 128 + .../src/crypto/engines/RSABlindingEngine.cs | 139 + .../src/crypto/engines/RSACoreEngine.cs | 156 + .../src/crypto/engines/RijndaelEngine.cs | 738 ++++ .../src/crypto/engines/RsaEngine.cs | 78 + .../src/crypto/engines/SEEDEngine.cs | 360 ++ .../src/crypto/engines/SEEDWrapEngine.cs | 16 + .../src/crypto/engines/Salsa20Engine.cs | 362 ++ .../src/crypto/engines/SerpentEngine.cs | 292 ++ .../src/crypto/engines/SerpentEngineBase.cs | 469 +++ .../src/crypto/engines/SkipjackEngine.cs | 254 ++ .../src/crypto/engines/TEAEngine.cs | 166 + .../src/crypto/engines/ThreefishEngine.cs | 1491 +++++++ .../src/crypto/engines/TnepresEngine.cs | 299 ++ .../src/crypto/engines/TwofishEngine.cs | 675 ++++ .../src/crypto/engines/VMPCEngine.cs | 133 + .../src/crypto/engines/VMPCKSA3Engine.cs | 51 + .../src/crypto/engines/XSalsa20Engine.cs | 64 + .../src/crypto/engines/XTEAEngine.cs | 166 + .../src/crypto/generators/BCrypt.cs | 617 +++ .../generators/BaseKdfBytesGenerator.cs | 132 + .../generators/DHBasicKeyPairGenerator.cs | 38 + .../crypto/generators/DHKeyGeneratorHelper.cs | 72 + .../crypto/generators/DHKeyPairGenerator.cs | 38 + .../generators/DHParametersGenerator.cs | 45 + .../crypto/generators/DHParametersHelper.cs | 156 + .../crypto/generators/DesEdeKeyGenerator.cs | 67 + .../src/crypto/generators/DesKeyGenerator.cs | 57 + .../crypto/generators/DsaKeyPairGenerator.cs | 72 + .../generators/DsaParametersGenerator.cs | 355 ++ .../crypto/generators/ECKeyPairGenerator.cs | 162 + .../generators/ElGamalKeyPairGenerator.cs | 40 + .../generators/ElGamalParametersGenerator.cs | 46 + .../generators/GOST3410KeyPairGenerator.cs | 82 + .../generators/GOST3410ParametersGenerator.cs | 530 +++ .../crypto/generators/HKDFBytesGenerator.cs | 153 + .../crypto/generators/Kdf1BytesGenerator.cs | 26 + .../crypto/generators/Kdf2BytesGenerator.cs | 27 + .../crypto/generators/Mgf1BytesGenerator.cs | 117 + .../NaccacheSternKeyPairGenerator.cs | 268 ++ .../src/crypto/generators/OpenBsdBCrypt.cs | 270 ++ .../OpenSSLPBEParametersGenerator.cs | 167 + .../generators/Pkcs12ParametersGenerator.cs | 243 ++ .../generators/Pkcs5S1ParametersGenerator.cs | 160 + .../generators/Pkcs5S2ParametersGenerator.cs | 178 + .../crypto/generators/Poly1305KeyGenerator.cs | 116 + .../generators/RSABlindingFactorGenerator.cs | 69 + .../crypto/generators/RsaKeyPairGenerator.cs | 163 + .../src/crypto/generators/SCrypt.cs | 140 + bc-sharp-crypto/src/crypto/io/CipherStream.cs | 252 ++ bc-sharp-crypto/src/crypto/io/DigestStream.cs | 151 + bc-sharp-crypto/src/crypto/io/MacStream.cs | 150 + bc-sharp-crypto/src/crypto/io/SignerStream.cs | 151 + bc-sharp-crypto/src/crypto/macs/CMac.cs | 257 ++ .../src/crypto/macs/CbcBlockCipherMac.cs | 209 + .../src/crypto/macs/CfbBlockCipherMac.cs | 368 ++ .../src/crypto/macs/DSTU7564Mac.cs | 143 + .../src/crypto/macs/DSTU7624Mac.cs | 160 + bc-sharp-crypto/src/crypto/macs/GMac.cs | 112 + .../src/crypto/macs/GOST28147Mac.cs | 297 ++ bc-sharp-crypto/src/crypto/macs/HMac.cs | 154 + .../src/crypto/macs/ISO9797Alg3Mac.cs | 275 ++ bc-sharp-crypto/src/crypto/macs/Poly1305.cs | 293 ++ bc-sharp-crypto/src/crypto/macs/SipHash.cs | 199 + bc-sharp-crypto/src/crypto/macs/SkeinMac.cs | 118 + bc-sharp-crypto/src/crypto/macs/VMPCMac.cs | 173 + .../src/crypto/modes/CbcBlockCipher.cs | 241 ++ .../src/crypto/modes/CcmBlockCipher.cs | 449 +++ .../src/crypto/modes/CfbBlockCipher.cs | 224 + .../src/crypto/modes/CtsBlockCipher.cs | 253 ++ .../src/crypto/modes/EAXBlockCipher.cs | 379 ++ .../src/crypto/modes/GCMBlockCipher.cs | 594 +++ .../src/crypto/modes/GOFBBlockCipher.cs | 234 ++ .../src/crypto/modes/IAeadBlockCipher.cs | 105 + .../src/crypto/modes/KCcmBlockCipher.cs | 490 +++ .../src/crypto/modes/KCtrBlockCipher.cs | 235 ++ .../src/crypto/modes/OCBBlockCipher.cs | 565 +++ .../src/crypto/modes/OfbBlockCipher.cs | 182 + .../src/crypto/modes/OpenPgpCfbBlockCipher.cs | 337 ++ .../src/crypto/modes/SicBlockCipher.cs | 120 + .../crypto/modes/gcm/BasicGcmExponentiator.cs | 40 + .../crypto/modes/gcm/BasicGcmMultiplier.cs | 22 + .../src/crypto/modes/gcm/GcmUtilities.cs | 319 ++ .../src/crypto/modes/gcm/IGcmExponentiator.cs | 10 + .../src/crypto/modes/gcm/IGcmMultiplier.cs | 10 + .../modes/gcm/Tables1kGcmExponentiator.cs | 59 + .../modes/gcm/Tables64kGcmMultiplier.cs | 77 + .../crypto/modes/gcm/Tables8kGcmMultiplier.cs | 103 + .../src/crypto/operators/Asn1Signature.cs | 555 +++ .../src/crypto/paddings/BlockCipherPadding.cs | 43 + .../src/crypto/paddings/ISO10126d2Padding.cs | 76 + .../src/crypto/paddings/ISO7816d4Padding.cs | 79 + .../paddings/PaddedBufferedBlockCipher.cs | 285 ++ .../src/crypto/paddings/Pkcs7Padding.cs | 76 + .../src/crypto/paddings/TbcPadding.cs | 79 + .../src/crypto/paddings/X923Padding.cs | 82 + .../src/crypto/paddings/ZeroBytePadding.cs | 68 + .../src/crypto/parameters/AEADParameters.cs | 65 + .../src/crypto/parameters/CcmParameters.cs | 26 + .../parameters/DHKeyGenerationParameters.cs | 31 + .../src/crypto/parameters/DHKeyParameters.cs | 76 + .../src/crypto/parameters/DHParameters.cs | 185 + .../parameters/DHPrivateKeyParameters.cs | 60 + .../parameters/DHPublicKeyParameters.cs | 79 + .../parameters/DHValidationParameters.cs | 59 + .../DSAParameterGenerationParameters.cs | 74 + .../src/crypto/parameters/DesEdeParameters.cs | 140 + .../src/crypto/parameters/DesParameters.cs | 139 + .../parameters/DsaKeyGenerationParameters.cs | 26 + .../src/crypto/parameters/DsaKeyParameters.cs | 59 + .../src/crypto/parameters/DsaParameters.cs | 85 + .../parameters/DsaPrivateKeyParameters.cs | 53 + .../parameters/DsaPublicKeyParameters.cs | 68 + .../parameters/DsaValidationParameters.cs | 72 + .../crypto/parameters/ECDomainParameters.cs | 117 + .../parameters/ECKeyGenerationParameters.cs | 41 + .../src/crypto/parameters/ECKeyParameters.cs | 136 + .../parameters/ECPrivateKeyParameters.cs | 87 + .../parameters/ECPublicKeyParameters.cs | 101 + .../ElGamalKeyGenerationParameters.cs | 31 + .../crypto/parameters/ElGamalKeyParameters.cs | 59 + .../crypto/parameters/ElGamalParameters.cs | 81 + .../parameters/ElGamalPrivateKeyParameters.cs | 53 + .../parameters/ElGamalPublicKeyParameters.cs | 53 + .../GOST3410KeyGenerationParameters.cs | 55 + .../parameters/GOST3410KeyParameters.cs | 58 + .../crypto/parameters/GOST3410Parameters.cs | 86 + .../GOST3410PrivateKeyParameters.cs | 41 + .../parameters/GOST3410PublicKeyParameters.cs | 40 + .../GOST3410ValidationParameters.cs | 51 + .../src/crypto/parameters/HKDFParameters.cs | 119 + .../parameters/ISO18033KDFParameters.cs | 25 + .../src/crypto/parameters/IesParameters.cs | 49 + .../parameters/IesWithCipherParameters.cs | 33 + .../src/crypto/parameters/KdfParameters.cs | 33 + .../src/crypto/parameters/KeyParameter.cs | 43 + .../src/crypto/parameters/MgfParameters.cs | 31 + .../crypto/parameters/MqvPrivateParameters.cs | 64 + .../crypto/parameters/MqvPublicParameters.cs | 36 + .../NaccacheSternKeyGenerationParameters.cs | 98 + .../parameters/NaccacheSternKeyParameters.cs | 44 + .../NaccacheSternPrivateKeyParameters.cs | 79 + .../src/crypto/parameters/ParametersWithIV.cs | 43 + .../crypto/parameters/ParametersWithRandom.cs | 48 + .../crypto/parameters/ParametersWithSBox.cs | 24 + .../crypto/parameters/ParametersWithSalt.cs | 39 + .../src/crypto/parameters/RC2Parameters.cs | 47 + .../src/crypto/parameters/RC5Parameters.cs | 27 + .../parameters/RSABlindingParameters.cs | 34 + .../parameters/RsaKeyGenerationParameters.cs | 55 + .../src/crypto/parameters/RsaKeyParameters.cs | 85 + .../parameters/RsaPrivateCrtKeyParameters.cs | 104 + .../src/crypto/parameters/SkeinParameters.cs | 286 ++ .../crypto/parameters/Srp6GroupParameters.cs | 27 + .../TweakableBlockCipherParameters.cs | 40 + .../crypto/prng/BasicEntropySourceProvider.cs | 71 + .../prng/CryptoApiEntropySourceProvider.cs | 70 + .../crypto/prng/CryptoApiRandomGenerator.cs | 66 + .../src/crypto/prng/DigestRandomGenerator.cs | 127 + .../src/crypto/prng/EntropyUtilities.cs | 30 + .../src/crypto/prng/IDrbgProvider.cs | 11 + .../src/crypto/prng/IRandomGenerator.cs | 26 + .../crypto/prng/ReversedWindowGenerator.cs | 98 + .../src/crypto/prng/SP800SecureRandom.cs | 95 + .../crypto/prng/SP800SecureRandomBuilder.cs | 208 + .../src/crypto/prng/ThreadedSeedGenerator.cs | 129 + .../src/crypto/prng/VMPCRandomGenerator.cs | 114 + bc-sharp-crypto/src/crypto/prng/X931Rng.cs | 146 + .../src/crypto/prng/X931SecureRandom.cs | 70 + .../crypto/prng/X931SecureRandomBuilder.cs | 87 + .../src/crypto/prng/drbg/CtrSP800Drbg.cs | 466 +++ .../src/crypto/prng/drbg/DrbgUtilities.cs | 103 + .../src/crypto/prng/drbg/HMacSP800Drbg.cs | 186 + .../src/crypto/prng/drbg/HashSP800Drbg.cs | 287 ++ .../src/crypto/prng/drbg/ISP80090Drbg.cs | 35 + .../src/crypto/signers/DsaDigestSigner.cs | 145 + .../src/crypto/signers/DsaSigner.cs | 156 + .../src/crypto/signers/ECDsaSigner.cs | 240 ++ .../src/crypto/signers/ECGOST3410Signer.cs | 162 + .../src/crypto/signers/ECNRSigner.cs | 188 + .../crypto/signers/GOST3410DigestSigner.cs | 145 + .../src/crypto/signers/GOST3410Signer.cs | 132 + .../src/crypto/signers/GenericSigner.cs | 130 + .../src/crypto/signers/HMacDsaKCalculator.cs | 150 + .../src/crypto/signers/IDsaKCalculator.cs | 44 + .../src/crypto/signers/Iso9796d2PssSigner.cs | 619 +++ .../src/crypto/signers/Iso9796d2Signer.cs | 556 +++ .../src/crypto/signers/IsoTrailers.cs | 57 + .../src/crypto/signers/PssSigner.cs | 386 ++ .../crypto/signers/RandomDsaKCalculator.cs | 44 + .../src/crypto/signers/RsaDigestSigner.cs | 217 + .../src/crypto/signers/X931Signer.cs | 225 ++ .../tls/AbstractTlsAgreementCredentials.cs | 12 + .../crypto/tls/AbstractTlsCipherFactory.cs | 15 + .../src/crypto/tls/AbstractTlsClient.cs | 256 ++ .../src/crypto/tls/AbstractTlsContext.cs | 152 + .../src/crypto/tls/AbstractTlsCredentials.cs | 10 + .../tls/AbstractTlsEncryptionCredentials.cs | 12 + .../src/crypto/tls/AbstractTlsKeyExchange.cs | 177 + .../src/crypto/tls/AbstractTlsPeer.cs | 48 + .../src/crypto/tls/AbstractTlsServer.cs | 351 ++ .../src/crypto/tls/AbstractTlsSigner.cs | 50 + .../tls/AbstractTlsSignerCredentials.cs | 20 + .../src/crypto/tls/AlertDescription.cs | 304 ++ bc-sharp-crypto/src/crypto/tls/AlertLevel.cs | 29 + .../src/crypto/tls/BasicTlsPskIdentity.cs | 43 + .../src/crypto/tls/BulkCipherAlgorithm.cs | 25 + bc-sharp-crypto/src/crypto/tls/ByteQueue.cs | 211 + .../src/crypto/tls/ByteQueueStream.cs | 110 + .../src/crypto/tls/CertChainType.cs | 18 + bc-sharp-crypto/src/crypto/tls/Certificate.cs | 136 + .../src/crypto/tls/CertificateRequest.cs | 156 + .../src/crypto/tls/CertificateStatus.cs | 102 + .../crypto/tls/CertificateStatusRequest.cs | 95 + .../src/crypto/tls/CertificateStatusType.cs | 12 + .../src/crypto/tls/CertificateType.cs | 18 + .../src/crypto/tls/CertificateUrl.cs | 125 + .../src/crypto/tls/Chacha20Poly1305.cs | 199 + .../src/crypto/tls/ChangeCipherSpec.cs | 9 + bc-sharp-crypto/src/crypto/tls/CipherSuite.cs | 377 ++ bc-sharp-crypto/src/crypto/tls/CipherType.cs | 20 + .../crypto/tls/ClientAuthenticationType.cs | 14 + .../src/crypto/tls/ClientCertificateType.cs | 23 + .../src/crypto/tls/CombinedHash.cs | 133 + .../src/crypto/tls/CompressionMethod.cs | 22 + .../src/crypto/tls/ConnectionEnd.cs | 15 + bc-sharp-crypto/src/crypto/tls/ContentType.cs | 14 + .../src/crypto/tls/DatagramTransport.cs | 23 + .../tls/DefaultTlsAgreementCredentials.cs | 69 + .../src/crypto/tls/DefaultTlsCipherFactory.cs | 227 ++ .../src/crypto/tls/DefaultTlsClient.cs | 113 + .../tls/DefaultTlsEncryptionCredentials.cs | 52 + .../src/crypto/tls/DefaultTlsServer.cs | 166 + .../crypto/tls/DefaultTlsSignerCredentials.cs | 93 + .../crypto/tls/DefaultTlsSrpGroupVerifier.cs | 70 + .../src/crypto/tls/DeferredHash.cs | 201 + .../src/crypto/tls/DigestInputBuffer.cs | 37 + .../src/crypto/tls/DigitallySigned.cs | 70 + .../src/crypto/tls/DtlsClientProtocol.cs | 857 ++++ bc-sharp-crypto/src/crypto/tls/DtlsEpoch.cs | 51 + .../src/crypto/tls/DtlsHandshakeRetransmit.cs | 11 + .../src/crypto/tls/DtlsProtocol.cs | 92 + .../src/crypto/tls/DtlsReassembler.cs | 125 + .../src/crypto/tls/DtlsRecordLayer.cs | 530 +++ .../src/crypto/tls/DtlsReliableHandshake.cs | 434 ++ .../src/crypto/tls/DtlsReplayWindow.cs | 85 + .../src/crypto/tls/DtlsServerProtocol.cs | 696 ++++ .../src/crypto/tls/DtlsTransport.cs | 77 + bc-sharp-crypto/src/crypto/tls/ECBasisType.cs | 16 + bc-sharp-crypto/src/crypto/tls/ECCurveType.cs | 29 + .../src/crypto/tls/ECPointFormat.cs | 16 + .../src/crypto/tls/EncryptionAlgorithm.cs | 69 + .../src/crypto/tls/ExporterLabel.cs | 37 + .../src/crypto/tls/ExtensionType.cs | 128 + .../src/crypto/tls/FiniteFieldDheGroup.cs | 21 + .../src/crypto/tls/HandshakeType.cs | 40 + .../src/crypto/tls/HashAlgorithm.cs | 49 + .../src/crypto/tls/HeartbeatExtension.cs | 52 + .../src/crypto/tls/HeartbeatMessage.cs | 109 + .../src/crypto/tls/HeartbeatMessageType.cs | 18 + .../src/crypto/tls/HeartbeatMode.cs | 18 + .../src/crypto/tls/KeyExchangeAlgorithm.cs | 54 + .../src/crypto/tls/MacAlgorithm.cs | 25 + .../src/crypto/tls/MaxFragmentLength.cs | 20 + bc-sharp-crypto/src/crypto/tls/NameType.cs | 17 + bc-sharp-crypto/src/crypto/tls/NamedCurve.cs | 77 + .../src/crypto/tls/NewSessionTicket.cs | 53 + .../src/crypto/tls/OcspStatusRequest.cs | 131 + .../src/crypto/tls/PrfAlgorithm.cs | 24 + .../src/crypto/tls/ProtocolVersion.cs | 159 + .../src/crypto/tls/PskTlsClient.cs | 70 + .../src/crypto/tls/PskTlsServer.cs | 93 + .../src/crypto/tls/RecordStream.cs | 412 ++ .../src/crypto/tls/SecurityParameters.cs | 103 + .../src/crypto/tls/ServerDHParams.cs | 61 + bc-sharp-crypto/src/crypto/tls/ServerName.cs | 105 + .../src/crypto/tls/ServerNameList.cs | 105 + .../crypto/tls/ServerOnlyTlsAuthentication.cs | 15 + .../src/crypto/tls/ServerSrpParams.cs | 75 + .../src/crypto/tls/SessionParameters.cs | 165 + .../src/crypto/tls/SignatureAlgorithm.cs | 15 + .../crypto/tls/SignatureAndHashAlgorithm.cs | 94 + .../src/crypto/tls/SignerInputBuffer.cs | 37 + .../tls/SimulatedTlsSrpIdentityManager.cs | 69 + .../src/crypto/tls/SrpTlsClient.cs | 104 + .../src/crypto/tls/SrpTlsServer.cs | 121 + .../src/crypto/tls/SrtpProtectionProfile.cs | 21 + bc-sharp-crypto/src/crypto/tls/Ssl3Mac.cs | 110 + .../src/crypto/tls/SupplementalDataEntry.cs | 26 + .../src/crypto/tls/SupplementalDataType.cs | 13 + .../src/crypto/tls/TlsAeadCipher.cs | 249 ++ .../src/crypto/tls/TlsAgreementCredentials.cs | 12 + .../src/crypto/tls/TlsAuthentication.cs | 31 + .../src/crypto/tls/TlsBlockCipher.cs | 395 ++ bc-sharp-crypto/src/crypto/tls/TlsCipher.cs | 16 + .../src/crypto/tls/TlsCipherFactory.cs | 11 + bc-sharp-crypto/src/crypto/tls/TlsClient.cs | 148 + .../src/crypto/tls/TlsClientContext.cs | 11 + .../src/crypto/tls/TlsClientContextImpl.cs | 20 + .../src/crypto/tls/TlsClientProtocol.cs | 912 +++++ .../src/crypto/tls/TlsCompression.cs | 12 + bc-sharp-crypto/src/crypto/tls/TlsContext.cs | 45 + .../src/crypto/tls/TlsCredentials.cs | 9 + .../src/crypto/tls/TlsDHKeyExchange.cs | 259 ++ .../src/crypto/tls/TlsDHUtilities.cs | 462 +++ .../src/crypto/tls/TlsDeflateCompression.cs | 68 + .../src/crypto/tls/TlsDheKeyExchange.cs | 94 + .../src/crypto/tls/TlsDsaSigner.cs | 82 + .../src/crypto/tls/TlsDssSigner.cs | 26 + .../src/crypto/tls/TlsECDHKeyExchange.cs | 252 ++ .../src/crypto/tls/TlsECDheKeyExchange.cs | 131 + .../src/crypto/tls/TlsECDsaSigner.cs | 26 + .../src/crypto/tls/TlsEccUtilities.cs | 705 ++++ .../crypto/tls/TlsEncryptionCredentials.cs | 12 + .../src/crypto/tls/TlsException.cs | 14 + .../src/crypto/tls/TlsExtensionsUtilities.cs | 368 ++ .../src/crypto/tls/TlsFatalAlert.cs | 26 + .../src/crypto/tls/TlsFatalAlertReceived.cs | 21 + .../src/crypto/tls/TlsHandshakeHash.cs | 22 + .../src/crypto/tls/TlsKeyExchange.cs | 54 + bc-sharp-crypto/src/crypto/tls/TlsMac.cs | 173 + .../crypto/tls/TlsNoCloseNotifyException.cs | 23 + .../src/crypto/tls/TlsNullCipher.cs | 118 + .../src/crypto/tls/TlsNullCompression.cs | 19 + bc-sharp-crypto/src/crypto/tls/TlsPeer.cs | 62 + bc-sharp-crypto/src/crypto/tls/TlsProtocol.cs | 1450 +++++++ .../src/crypto/tls/TlsProtocolHandler.cs | 39 + .../src/crypto/tls/TlsPskIdentity.cs | 15 + .../src/crypto/tls/TlsPskIdentityManager.cs | 11 + .../src/crypto/tls/TlsPskKeyExchange.cs | 328 ++ .../src/crypto/tls/TlsRsaKeyExchange.cs | 140 + .../src/crypto/tls/TlsRsaSigner.cs | 102 + .../src/crypto/tls/TlsRsaUtilities.cs | 132 + bc-sharp-crypto/src/crypto/tls/TlsServer.cs | 93 + .../src/crypto/tls/TlsServerContext.cs | 11 + .../src/crypto/tls/TlsServerContextImpl.cs | 20 + .../src/crypto/tls/TlsServerProtocol.cs | 833 ++++ bc-sharp-crypto/src/crypto/tls/TlsSession.cs | 15 + .../src/crypto/tls/TlsSessionImpl.cs | 54 + bc-sharp-crypto/src/crypto/tls/TlsSigner.cs | 29 + .../src/crypto/tls/TlsSignerCredentials.cs | 14 + .../src/crypto/tls/TlsSrpGroupVerifier.cs | 17 + .../src/crypto/tls/TlsSrpIdentityManager.cs | 21 + .../src/crypto/tls/TlsSrpKeyExchange.cs | 285 ++ .../src/crypto/tls/TlsSrpLoginParameters.cs | 36 + .../src/crypto/tls/TlsSrpUtilities.cs | 74 + .../src/crypto/tls/TlsSrtpUtilities.cs | 62 + bc-sharp-crypto/src/crypto/tls/TlsStream.cs | 97 + .../src/crypto/tls/TlsStreamCipher.cs | 152 + .../src/crypto/tls/TlsUtilities.cs | 2398 +++++++++++ bc-sharp-crypto/src/crypto/tls/UrlAndHash.cs | 94 + bc-sharp-crypto/src/crypto/tls/UseSrtpData.cs | 56 + .../src/crypto/tls/UserMappingType.cs | 13 + bc-sharp-crypto/src/crypto/util/Pack.cs | 345 ++ bc-sharp-crypto/src/math/BigInteger.cs | 3592 +++++++++++++++++ bc-sharp-crypto/src/math/Primes.cs | 629 +++ bc-sharp-crypto/src/math/ec/ECAlgorithms.cs | 479 +++ bc-sharp-crypto/src/math/ec/ECCurve.cs | 1131 ++++++ bc-sharp-crypto/src/math/ec/ECFieldElement.cs | 928 +++++ bc-sharp-crypto/src/math/ec/ECPoint.cs | 2064 ++++++++++ bc-sharp-crypto/src/math/ec/ECPointMap.cs | 9 + bc-sharp-crypto/src/math/ec/LongArray.cs | 2201 ++++++++++ bc-sharp-crypto/src/math/ec/ScaleXPointMap.cs | 20 + bc-sharp-crypto/src/math/ec/ScaleYPointMap.cs | 20 + .../src/math/ec/abc/SimpleBigDecimal.cs | 241 ++ bc-sharp-crypto/src/math/ec/abc/Tnaf.cs | 845 ++++ .../src/math/ec/abc/ZTauElement.cs | 36 + .../src/math/ec/custom/djb/Curve25519.cs | 77 + .../src/math/ec/custom/djb/Curve25519Field.cs | 253 ++ .../ec/custom/djb/Curve25519FieldElement.cs | 233 ++ .../src/math/ec/custom/djb/Curve25519Point.cs | 313 ++ .../src/math/ec/custom/gm/SM2P256V1Curve.cs | 77 + .../src/math/ec/custom/gm/SM2P256V1Field.cs | 307 ++ .../ec/custom/gm/SM2P256V1FieldElement.cs | 211 + .../src/math/ec/custom/gm/SM2P256V1Point.cs | 279 ++ .../src/math/ec/custom/sec/SecP128R1Curve.cs | 78 + .../src/math/ec/custom/sec/SecP128R1Field.cs | 218 + .../ec/custom/sec/SecP128R1FieldElement.cs | 198 + .../src/math/ec/custom/sec/SecP128R1Point.cs | 279 ++ .../src/math/ec/custom/sec/SecP160K1Curve.cs | 74 + .../src/math/ec/custom/sec/SecP160K1Point.cs | 269 ++ .../src/math/ec/custom/sec/SecP160R1Curve.cs | 78 + .../src/math/ec/custom/sec/SecP160R1Field.cs | 186 + .../ec/custom/sec/SecP160R1FieldElement.cs | 203 + .../src/math/ec/custom/sec/SecP160R1Point.cs | 279 ++ .../src/math/ec/custom/sec/SecP160R2Curve.cs | 78 + .../src/math/ec/custom/sec/SecP160R2Field.cs | 178 + .../ec/custom/sec/SecP160R2FieldElement.cs | 218 + .../src/math/ec/custom/sec/SecP160R2Point.cs | 279 ++ .../src/math/ec/custom/sec/SecP192K1Curve.cs | 75 + .../src/math/ec/custom/sec/SecP192K1Field.cs | 178 + .../ec/custom/sec/SecP192K1FieldElement.cs | 213 + .../src/math/ec/custom/sec/SecP192K1Point.cs | 267 ++ .../src/math/ec/custom/sec/SecP192R1Curve.cs | 78 + .../src/math/ec/custom/sec/SecP192R1Field.cs | 283 ++ .../ec/custom/sec/SecP192R1FieldElement.cs | 188 + .../src/math/ec/custom/sec/SecP192R1Point.cs | 279 ++ .../src/math/ec/custom/sec/SecP224K1Curve.cs | 75 + .../src/math/ec/custom/sec/SecP224K1Field.cs | 179 + .../ec/custom/sec/SecP224K1FieldElement.cs | 242 ++ .../src/math/ec/custom/sec/SecP224K1Point.cs | 267 ++ .../src/math/ec/custom/sec/SecP224R1Curve.cs | 78 + .../src/math/ec/custom/sec/SecP224R1Field.cs | 297 ++ .../ec/custom/sec/SecP224R1FieldElement.cs | 269 ++ .../src/math/ec/custom/sec/SecP224R1Point.cs | 279 ++ .../src/math/ec/custom/sec/SecP256K1Curve.cs | 75 + .../src/math/ec/custom/sec/SecP256K1Field.cs | 180 + .../ec/custom/sec/SecP256K1FieldElement.cs | 214 + .../src/math/ec/custom/sec/SecP256K1Point.cs | 267 ++ .../src/math/ec/custom/sec/SecP256R1Curve.cs | 77 + .../src/math/ec/custom/sec/SecP256R1Field.cs | 312 ++ .../ec/custom/sec/SecP256R1FieldElement.cs | 188 + .../src/math/ec/custom/sec/SecP256R1Point.cs | 279 ++ .../src/math/ec/custom/sec/SecP384R1Curve.cs | 77 + .../src/math/ec/custom/sec/SecP384R1Field.cs | 295 ++ .../ec/custom/sec/SecP384R1FieldElement.cs | 210 + .../src/math/ec/custom/sec/SecP384R1Point.cs | 280 ++ .../src/math/ec/custom/sec/SecP521R1Curve.cs | 77 + .../src/math/ec/custom/sec/SecP521R1Field.cs | 155 + .../ec/custom/sec/SecP521R1FieldElement.cs | 167 + .../src/math/ec/custom/sec/SecP521R1Point.cs | 275 ++ .../src/math/ec/custom/sec/SecT113Field.cs | 225 ++ .../math/ec/custom/sec/SecT113FieldElement.cs | 216 + .../src/math/ec/custom/sec/SecT113R1Curve.cs | 98 + .../src/math/ec/custom/sec/SecT113R1Point.cs | 281 ++ .../src/math/ec/custom/sec/SecT113R2Curve.cs | 98 + .../src/math/ec/custom/sec/SecT113R2Point.cs | 291 ++ .../src/math/ec/custom/sec/SecT131Field.cs | 330 ++ .../math/ec/custom/sec/SecT131FieldElement.cs | 216 + .../src/math/ec/custom/sec/SecT131R1Curve.cs | 98 + .../src/math/ec/custom/sec/SecT131R1Point.cs | 287 ++ .../src/math/ec/custom/sec/SecT131R2Curve.cs | 98 + .../src/math/ec/custom/sec/SecT131R2Point.cs | 283 ++ .../src/math/ec/custom/sec/SecT163Field.cs | 340 ++ .../math/ec/custom/sec/SecT163FieldElement.cs | 216 + .../src/math/ec/custom/sec/SecT163K1Curve.cs | 104 + .../src/math/ec/custom/sec/SecT163K1Point.cs | 281 ++ .../src/math/ec/custom/sec/SecT163R1Curve.cs | 98 + .../src/math/ec/custom/sec/SecT163R1Point.cs | 283 ++ .../src/math/ec/custom/sec/SecT163R2Curve.cs | 98 + .../src/math/ec/custom/sec/SecT163R2Point.cs | 286 ++ .../src/math/ec/custom/sec/SecT193Field.cs | 305 ++ .../math/ec/custom/sec/SecT193FieldElement.cs | 216 + .../src/math/ec/custom/sec/SecT193R1Curve.cs | 98 + .../src/math/ec/custom/sec/SecT193R1Point.cs | 283 ++ .../src/math/ec/custom/sec/SecT193R2Curve.cs | 98 + .../src/math/ec/custom/sec/SecT193R2Point.cs | 283 ++ .../src/math/ec/custom/sec/SecT233Field.cs | 317 ++ .../math/ec/custom/sec/SecT233FieldElement.cs | 216 + .../src/math/ec/custom/sec/SecT233K1Curve.cs | 104 + .../src/math/ec/custom/sec/SecT233K1Point.cs | 295 ++ .../src/math/ec/custom/sec/SecT233R1Curve.cs | 98 + .../src/math/ec/custom/sec/SecT233R1Point.cs | 278 ++ .../src/math/ec/custom/sec/SecT239Field.cs | 328 ++ .../math/ec/custom/sec/SecT239FieldElement.cs | 216 + .../src/math/ec/custom/sec/SecT239K1Curve.cs | 104 + .../src/math/ec/custom/sec/SecT239K1Point.cs | 290 ++ .../src/math/ec/custom/sec/SecT283Field.cs | 402 ++ .../math/ec/custom/sec/SecT283FieldElement.cs | 216 + .../src/math/ec/custom/sec/SecT283K1Curve.cs | 104 + .../src/math/ec/custom/sec/SecT283K1Point.cs | 289 ++ .../src/math/ec/custom/sec/SecT283R1Curve.cs | 98 + .../src/math/ec/custom/sec/SecT283R1Point.cs | 278 ++ .../src/math/ec/custom/sec/SecT409Field.cs | 331 ++ .../math/ec/custom/sec/SecT409FieldElement.cs | 216 + .../src/math/ec/custom/sec/SecT409K1Curve.cs | 104 + .../src/math/ec/custom/sec/SecT409K1Point.cs | 289 ++ .../src/math/ec/custom/sec/SecT409R1Curve.cs | 98 + .../src/math/ec/custom/sec/SecT409R1Point.cs | 278 ++ .../src/math/ec/custom/sec/SecT571Field.cs | 333 ++ .../math/ec/custom/sec/SecT571FieldElement.cs | 216 + .../src/math/ec/custom/sec/SecT571K1Curve.cs | 104 + .../src/math/ec/custom/sec/SecT571K1Point.cs | 289 ++ .../src/math/ec/custom/sec/SecT571R1Curve.cs | 102 + .../src/math/ec/custom/sec/SecT571R1Point.cs | 278 ++ .../src/math/ec/endo/ECEndomorphism.cs | 11 + .../src/math/ec/endo/GlvEndomorphism.cs | 10 + .../src/math/ec/endo/GlvTypeBEndomorphism.cs | 55 + .../src/math/ec/endo/GlvTypeBParameters.cs | 60 + .../ec/multiplier/AbstractECMultiplier.cs | 24 + .../math/ec/multiplier/DoubleAddMultiplier.cs | 24 + .../src/math/ec/multiplier/ECMultiplier.cs | 18 + .../ec/multiplier/FixedPointCombMultiplier.cs | 59 + .../ec/multiplier/FixedPointPreCompInfo.cs | 34 + .../math/ec/multiplier/FixedPointUtilities.cs | 72 + .../src/math/ec/multiplier/GlvMultiplier.cs | 40 + .../ec/multiplier/MixedNafR2LMultiplier.cs | 75 + .../multiplier/MontgomeryLadderMultiplier.cs | 25 + .../math/ec/multiplier/NafL2RMultiplier.cs | 30 + .../math/ec/multiplier/NafR2LMultiplier.cs | 31 + .../src/math/ec/multiplier/PreCompInfo.cs | 11 + .../math/ec/multiplier/ReferenceMultiplier.cs | 11 + .../math/ec/multiplier/WNafL2RMultiplier.cs | 98 + .../src/math/ec/multiplier/WNafPreCompInfo.cs | 46 + .../src/math/ec/multiplier/WNafUtilities.cs | 524 +++ .../math/ec/multiplier/WTauNafMultiplier.cs | 125 + .../math/ec/multiplier/WTauNafPreCompInfo.cs | 24 + .../multiplier/ZSignedDigitL2RMultiplier.cs | 29 + .../multiplier/ZSignedDigitR2LMultiplier.cs | 30 + .../src/math/field/FiniteFields.cs | 54 + .../src/math/field/GF2Polynomial.cs | 46 + .../field/GenericPolynomialExtensionField.cs | 63 + .../src/math/field/IExtensionField.cs | 12 + .../src/math/field/IFiniteField.cs | 11 + bc-sharp-crypto/src/math/field/IPolynomial.cs | 15 + .../math/field/IPolynomialExtensionField.cs | 10 + bc-sharp-crypto/src/math/field/PrimeField.cs | 44 + bc-sharp-crypto/src/math/raw/Interleave.cs | 107 + bc-sharp-crypto/src/math/raw/Mod.cs | 186 + bc-sharp-crypto/src/math/raw/Nat.cs | 1053 +++++ bc-sharp-crypto/src/math/raw/Nat128.cs | 856 ++++ bc-sharp-crypto/src/math/raw/Nat160.cs | 874 ++++ bc-sharp-crypto/src/math/raw/Nat192.cs | 1048 +++++ bc-sharp-crypto/src/math/raw/Nat224.cs | 1176 ++++++ bc-sharp-crypto/src/math/raw/Nat256.cs | 1387 +++++++ bc-sharp-crypto/src/math/raw/Nat320.cs | 98 + bc-sharp-crypto/src/math/raw/Nat384.cs | 46 + bc-sharp-crypto/src/math/raw/Nat448.cs | 100 + bc-sharp-crypto/src/math/raw/Nat512.cs | 46 + bc-sharp-crypto/src/math/raw/Nat576.cs | 102 + bc-sharp-crypto/src/ocsp/BasicOCSPResp.cs | 220 + .../src/ocsp/BasicOCSPRespGenerator.cs | 313 ++ bc-sharp-crypto/src/ocsp/CertificateID.cs | 141 + bc-sharp-crypto/src/ocsp/CertificateStatus.cs | 9 + bc-sharp-crypto/src/ocsp/OCSPException.cs | 28 + bc-sharp-crypto/src/ocsp/OCSPReq.cs | 268 ++ bc-sharp-crypto/src/ocsp/OCSPReqGenerator.cs | 243 ++ bc-sharp-crypto/src/ocsp/OCSPResp.cs | 100 + bc-sharp-crypto/src/ocsp/OCSPRespGenerator.cs | 54 + bc-sharp-crypto/src/ocsp/OCSPRespStatus.cs | 22 + bc-sharp-crypto/src/ocsp/OCSPUtil.cs | 132 + bc-sharp-crypto/src/ocsp/Req.cs | 38 + bc-sharp-crypto/src/ocsp/RespData.cs | 60 + bc-sharp-crypto/src/ocsp/RespID.cs | 72 + bc-sharp-crypto/src/ocsp/RevokedStatus.cs | 58 + bc-sharp-crypto/src/ocsp/SingleResp.cs | 81 + bc-sharp-crypto/src/ocsp/UnknownStatus.cs | 15 + .../src/openpgp/IStreamGenerator.cs | 7 + bc-sharp-crypto/src/openpgp/PGPKeyRing.cs | 79 + bc-sharp-crypto/src/openpgp/PGPObject.cs | 9 + ...GPUserAttributeSubpacketVectorGenerator.cs | 33 + .../src/openpgp/PgpCompressedData.cs | 50 + .../src/openpgp/PgpCompressedDataGenerator.cs | 221 + .../src/openpgp/PgpDataValidationException.cs | 18 + .../src/openpgp/PgpEncryptedData.cs | 151 + .../src/openpgp/PgpEncryptedDataGenerator.cs | 598 +++ .../src/openpgp/PgpEncryptedDataList.cs | 72 + bc-sharp-crypto/src/openpgp/PgpException.cs | 22 + .../src/openpgp/PgpExperimental.cs | 16 + bc-sharp-crypto/src/openpgp/PgpKeyFlags.cs | 13 + bc-sharp-crypto/src/openpgp/PgpKeyPair.cs | 67 + .../src/openpgp/PgpKeyRingGenerator.cs | 402 ++ .../src/openpgp/PgpKeyValidationException.cs | 18 + bc-sharp-crypto/src/openpgp/PgpLiteralData.cs | 63 + .../src/openpgp/PgpLiteralDataGenerator.cs | 182 + bc-sharp-crypto/src/openpgp/PgpMarker.cs | 18 + .../src/openpgp/PgpObjectFactory.cs | 143 + .../src/openpgp/PgpOnePassSignature.cs | 179 + .../src/openpgp/PgpOnePassSignatureList.cs | 51 + bc-sharp-crypto/src/openpgp/PgpPad.cs | 45 + .../src/openpgp/PgpPbeEncryptedData.cs | 160 + bc-sharp-crypto/src/openpgp/PgpPrivateKey.cs | 51 + bc-sharp-crypto/src/openpgp/PgpPublicKey.cs | 980 +++++ .../src/openpgp/PgpPublicKeyEncryptedData.cs | 272 ++ .../src/openpgp/PgpPublicKeyRing.cs | 200 + .../src/openpgp/PgpPublicKeyRingBundle.cs | 279 ++ bc-sharp-crypto/src/openpgp/PgpSecretKey.cs | 1295 ++++++ .../src/openpgp/PgpSecretKeyRing.cs | 308 ++ .../src/openpgp/PgpSecretKeyRingBundle.cs | 280 ++ bc-sharp-crypto/src/openpgp/PgpSignature.cs | 447 ++ .../src/openpgp/PgpSignatureGenerator.cs | 393 ++ .../src/openpgp/PgpSignatureList.cs | 51 + .../openpgp/PgpSignatureSubpacketGenerator.cs | 210 + .../openpgp/PgpSignatureSubpacketVector.cs | 239 ++ .../PgpUserAttributeSubpacketVector.cs | 81 + bc-sharp-crypto/src/openpgp/PgpUtilities.cs | 518 +++ .../src/openpgp/PgpV3SignatureGenerator.cs | 199 + .../src/openpgp/Rfc6637Utilities.cs | 138 + bc-sharp-crypto/src/openpgp/SXprUtilities.cs | 102 + .../src/openpgp/WrappedGeneratorStream.cs | 37 + .../src/openssl/EncryptionException.cs | 25 + .../src/openssl/IPasswordFinder.cs | 9 + .../src/openssl/MiscPemGenerator.cs | 275 ++ bc-sharp-crypto/src/openssl/PEMException.cs | 25 + bc-sharp-crypto/src/openssl/PEMReader.cs | 401 ++ bc-sharp-crypto/src/openssl/PEMUtilities.cs | 158 + bc-sharp-crypto/src/openssl/PEMWriter.cs | 61 + .../src/openssl/PasswordException.cs | 25 + bc-sharp-crypto/src/openssl/Pkcs8Generator.cs | 111 + .../src/pkcs/AsymmetricKeyEntry.cs | 60 + .../pkcs/EncryptedPrivateKeyInfoFactory.cs | 64 + .../src/pkcs/PKCS12StoreBuilder.cs | 41 + .../src/pkcs/Pkcs10CertificationRequest.cs | 464 +++ .../Pkcs10CertificationRequestDelaySigned.cs | 150 + bc-sharp-crypto/src/pkcs/Pkcs12Entry.cs | 64 + bc-sharp-crypto/src/pkcs/Pkcs12Store.cs | 1100 +++++ bc-sharp-crypto/src/pkcs/Pkcs12Utilities.cs | 77 + .../src/pkcs/PrivateKeyInfoFactory.cs | 205 + .../src/pkcs/X509CertificateEntry.cs | 60 + bc-sharp-crypto/src/pkix/CertStatus.cs | 35 + .../src/pkix/PkixAttrCertChecker.cs | 57 + .../src/pkix/PkixAttrCertPathBuilder.cs | 215 + .../src/pkix/PkixAttrCertPathValidator.cs | 76 + .../src/pkix/PkixBuilderParameters.cs | 140 + bc-sharp-crypto/src/pkix/PkixCertPath.cs | 460 +++ .../src/pkix/PkixCertPathBuilder.cs | 205 + .../src/pkix/PkixCertPathBuilderException.cs | 22 + .../src/pkix/PkixCertPathBuilderResult.cs | 45 + .../src/pkix/PkixCertPathChecker.cs | 99 + .../src/pkix/PkixCertPathValidator.cs | 420 ++ .../pkix/PkixCertPathValidatorException.cs | 221 + .../src/pkix/PkixCertPathValidatorResult.cs | 69 + .../pkix/PkixCertPathValidatorUtilities.cs | 1194 ++++++ bc-sharp-crypto/src/pkix/PkixCrlUtilities.cs | 114 + .../src/pkix/PkixNameConstraintValidator.cs | 1939 +++++++++ .../PkixNameConstraintValidatorException.cs | 16 + bc-sharp-crypto/src/pkix/PkixParameters.cs | 893 ++++ bc-sharp-crypto/src/pkix/PkixPolicyNode.cs | 158 + bc-sharp-crypto/src/pkix/ReasonsMask.cs | 96 + .../src/pkix/Rfc3280CertPathUtilities.cs | 2448 +++++++++++ .../src/pkix/Rfc3281CertPathUtilities.cs | 608 +++ bc-sharp-crypto/src/pkix/TrustAnchor.cs | 259 ++ .../src/security/AgreementUtilities.cs | 105 + .../src/security/CipherUtilities.cs | 755 ++++ .../src/security/DigestUtilities.cs | 222 + .../src/security/DotNetUtilities.cs | 245 ++ .../src/security/GeneralSecurityException.cs | 29 + .../src/security/GeneratorUtilities.cs | 352 ++ .../src/security/InvalidKeyException.cs | 14 + .../src/security/InvalidParameterException.cs | 14 + bc-sharp-crypto/src/security/KeyException.cs | 14 + bc-sharp-crypto/src/security/MacUtilities.cs | 256 ++ .../src/security/NoSuchAlgorithmException.cs | 15 + .../src/security/ParameterUtilities.cs | 325 ++ bc-sharp-crypto/src/security/PbeUtilities.cs | 663 +++ .../src/security/PrivateKeyFactory.cs | 222 + .../src/security/PublicKeyFactory.cs | 253 ++ bc-sharp-crypto/src/security/SecureRandom.cs | 262 ++ .../src/security/SecurityUtilityException.cs | 36 + .../src/security/SignatureException.cs | 14 + .../src/security/SignerUtilities.cs | 566 +++ .../src/security/WrapperUtilities.cs | 153 + .../cert/CertificateEncodingException.cs | 14 + .../src/security/cert/CertificateException.cs | 14 + .../cert/CertificateExpiredException.cs | 14 + .../cert/CertificateNotYetValidException.cs | 14 + .../cert/CertificateParsingException.cs | 14 + .../src/security/cert/CrlException.cs | 14 + bc-sharp-crypto/src/tsp/GenTimeAccuracy.cs | 33 + bc-sharp-crypto/src/tsp/TSPAlgorithms.cs | 48 + bc-sharp-crypto/src/tsp/TSPException.cs | 28 + bc-sharp-crypto/src/tsp/TSPUtil.cs | 202 + .../src/tsp/TSPValidationException.cs | 44 + bc-sharp-crypto/src/tsp/TimeStampRequest.cs | 186 + .../src/tsp/TimeStampRequestGenerator.cs | 139 + bc-sharp-crypto/src/tsp/TimeStampResponse.cs | 184 + .../src/tsp/TimeStampResponseGenerator.cs | 209 + bc-sharp-crypto/src/tsp/TimeStampToken.cs | 305 ++ .../src/tsp/TimeStampTokenGenerator.cs | 245 ++ bc-sharp-crypto/src/tsp/TimeStampTokenInfo.cs | 107 + bc-sharp-crypto/src/util/Arrays.cs | 704 ++++ bc-sharp-crypto/src/util/BigIntegers.cs | 90 + bc-sharp-crypto/src/util/Enums.cs | 78 + bc-sharp-crypto/src/util/IMemoable.cs | 29 + bc-sharp-crypto/src/util/Integers.cs | 17 + .../src/util/MemoableResetException.cs | 27 + bc-sharp-crypto/src/util/Platform.cs | 229 ++ bc-sharp-crypto/src/util/Strings.cs | 103 + bc-sharp-crypto/src/util/Times.cs | 14 + bc-sharp-crypto/src/util/TypeExtensions.cs | 17 + .../util/collections/CollectionUtilities.cs | 64 + .../src/util/collections/EmptyEnumerable.cs | 44 + .../src/util/collections/EnumerableProxy.cs | 25 + .../src/util/collections/HashSet.cs | 99 + bc-sharp-crypto/src/util/collections/ISet.cs | 19 + .../src/util/collections/LinkedDictionary.cs | 178 + .../collections/UnmodifiableDictionary.cs | 64 + .../UnmodifiableDictionaryProxy.cs | 66 + .../src/util/collections/UnmodifiableList.cs | 67 + .../util/collections/UnmodifiableListProxy.cs | 61 + .../src/util/collections/UnmodifiableSet.cs | 59 + .../util/collections/UnmodifiableSetProxy.cs | 56 + .../src/util/date/DateTimeObject.cs | 25 + .../src/util/date/DateTimeUtilities.cs | 47 + bc-sharp-crypto/src/util/encoders/Base64.cs | 120 + .../src/util/encoders/Base64Encoder.cs | 324 ++ .../src/util/encoders/BufferedDecoder.cs | 117 + .../src/util/encoders/BufferedEncoder.cs | 117 + bc-sharp-crypto/src/util/encoders/Hex.cs | 130 + .../src/util/encoders/HexEncoder.cs | 176 + .../src/util/encoders/HexTranslator.cs | 108 + bc-sharp-crypto/src/util/encoders/IEncoder.cs | 18 + .../src/util/encoders/Translator.cs | 19 + .../src/util/encoders/UrlBase64.cs | 127 + .../src/util/encoders/UrlBase64Encoder.cs | 31 + .../src/util/io/BaseInputStream.cs | 64 + .../src/util/io/BaseOutputStream.cs | 69 + bc-sharp-crypto/src/util/io/FilterStream.cs | 78 + .../src/util/io/NullOutputStream.cs | 18 + bc-sharp-crypto/src/util/io/PushbackStream.cs | 52 + .../src/util/io/StreamOverflowException.cs | 30 + bc-sharp-crypto/src/util/io/Streams.cs | 100 + bc-sharp-crypto/src/util/io/TeeInputStream.cs | 64 + .../src/util/io/TeeOutputStream.cs | 52 + .../src/util/io/pem/PemGenerationException.cs | 29 + bc-sharp-crypto/src/util/io/pem/PemHeader.cs | 55 + bc-sharp-crypto/src/util/io/pem/PemObject.cs | 47 + .../src/util/io/pem/PemObjectGenerator.cs | 13 + .../src/util/io/pem/PemObjectParser.cs | 17 + bc-sharp-crypto/src/util/io/pem/PemReader.cs | 96 + bc-sharp-crypto/src/util/io/pem/PemWriter.cs | 120 + bc-sharp-crypto/src/util/net/IPAddress.cs | 197 + bc-sharp-crypto/src/util/zlib/Adler32.cs | 88 + bc-sharp-crypto/src/util/zlib/Deflate.cs | 1640 ++++++++ bc-sharp-crypto/src/util/zlib/InfBlocks.cs | 618 +++ bc-sharp-crypto/src/util/zlib/InfCodes.cs | 611 +++ bc-sharp-crypto/src/util/zlib/InfTree.cs | 523 +++ bc-sharp-crypto/src/util/zlib/Inflate.cs | 387 ++ bc-sharp-crypto/src/util/zlib/JZlib.cs | 73 + bc-sharp-crypto/src/util/zlib/StaticTree.cs | 152 + bc-sharp-crypto/src/util/zlib/Tree.cs | 367 ++ .../src/util/zlib/ZDeflaterOutputStream.cs | 171 + .../src/util/zlib/ZInflaterInputStream.cs | 140 + bc-sharp-crypto/src/util/zlib/ZInputStream.cs | 238 ++ .../src/util/zlib/ZOutputStream.cs | 265 ++ bc-sharp-crypto/src/util/zlib/ZStream.cs | 214 + .../src/x509/AttributeCertificateHolder.cs | 442 ++ .../src/x509/AttributeCertificateIssuer.cs | 199 + .../src/x509/IX509AttributeCertificate.cs | 57 + bc-sharp-crypto/src/x509/IX509Extension.cs | 27 + bc-sharp-crypto/src/x509/PEMParser.cs | 95 + bc-sharp-crypto/src/x509/PrincipalUtil.cs | 70 + .../src/x509/SubjectPublicKeyInfoFactory.cs | 184 + .../src/x509/X509AttrCertParser.cs | 173 + bc-sharp-crypto/src/x509/X509Attribute.cs | 76 + .../src/x509/X509CertPairParser.cs | 95 + bc-sharp-crypto/src/x509/X509Certificate.cs | 604 +++ .../src/x509/X509CertificatePair.cs | 123 + .../src/x509/X509CertificateParser.cs | 183 + bc-sharp-crypto/src/x509/X509Crl.cs | 426 ++ bc-sharp-crypto/src/x509/X509CrlEntry.cs | 201 + bc-sharp-crypto/src/x509/X509CrlParser.cs | 195 + bc-sharp-crypto/src/x509/X509ExtensionBase.cs | 82 + bc-sharp-crypto/src/x509/X509KeyUsage.cs | 59 + bc-sharp-crypto/src/x509/X509SignatureUtil.cs | 128 + bc-sharp-crypto/src/x509/X509Utilities.cs | 187 + .../src/x509/X509V1CertificateGenerator.cs | 210 + .../src/x509/X509V2AttributeCertificate.cs | 280 ++ .../X509V2AttributeCertificateGenerator.cs | 203 + .../src/x509/X509V2CRLGenerator.cs | 278 ++ .../src/x509/X509V3CertificateGenerator.cs | 344 ++ .../AuthorityKeyIdentifierStructure.cs | 102 + .../SubjectKeyIdentifierStructure.cs | 49 + .../src/x509/extension/X509ExtensionUtil.cs | 91 + .../src/x509/store/IX509Selector.cs | 15 + bc-sharp-crypto/src/x509/store/IX509Store.cs | 11 + .../src/x509/store/IX509StoreParameters.cs | 8 + .../src/x509/store/NoSuchStoreException.cs | 28 + .../x509/store/X509AttrCertStoreSelector.cs | 376 ++ .../x509/store/X509CertPairStoreSelector.cs | 92 + .../src/x509/store/X509CertStoreSelector.cs | 337 ++ .../src/x509/store/X509CollectionStore.cs | 51 + .../store/X509CollectionStoreParameters.cs | 60 + .../src/x509/store/X509CrlStoreSelector.cs | 283 ++ .../src/x509/store/X509StoreException.cs | 28 + .../src/x509/store/X509StoreFactory.cs | 62 + bin/x64/Debug/ufr-signer.exe | Bin 0 -> 2455552 bytes bin/x64/Debug/ufr-signer.exe.config | 6 + bin/x64/Release/ufr-signer.exe | Bin 0 -> 2176512 bytes bin/x64/Release/ufr-signer.exe.config | 6 + bin/x86/Release/ufr-signer.exe | Bin 0 -> 2177024 bytes bin/x86/Release/ufr-signer.exe.config | 6 + digital-signature-verifier.csproj | 1625 ++++++++ digital-signature-verifier.sln | 32 + frmMain.cs | 1304 ++++++ frmMain.designer.cs | 1155 ++++++ frmMain.resx | 120 + frmPassword.cs | 25 + frmPassword.designer.cs | 102 + frmPassword.resx | 120 + 1495 files changed, 242138 insertions(+) create mode 100644 App.config create mode 100644 ClassDiagram1.cd create mode 100644 Program.cs create mode 100644 Properties/AssemblyInfo.cs create mode 100644 Properties/Resources.Designer.cs create mode 100644 Properties/Resources.resx create mode 100644 Properties/Settings.Designer.cs create mode 100644 Properties/Settings.settings create mode 100644 bc-sharp-crypto/bzip2/src/BZip2Constants.cs create mode 100644 bc-sharp-crypto/bzip2/src/CBZip2InputStream.cs create mode 100644 bc-sharp-crypto/bzip2/src/CBZip2OutputStream.cs create mode 100644 bc-sharp-crypto/bzip2/src/CRC.cs create mode 100644 bc-sharp-crypto/src/asn1/ASN1Generator.cs create mode 100644 bc-sharp-crypto/src/asn1/ASN1OctetStringParser.cs create mode 100644 bc-sharp-crypto/src/asn1/ASN1SequenceParser.cs create mode 100644 bc-sharp-crypto/src/asn1/ASN1SetParser.cs create mode 100644 bc-sharp-crypto/src/asn1/ASN1StreamParser.cs create mode 100644 bc-sharp-crypto/src/asn1/ASN1TaggedObjectParser.cs create mode 100644 bc-sharp-crypto/src/asn1/Asn1Encodable.cs create mode 100644 bc-sharp-crypto/src/asn1/Asn1EncodableVector.cs create mode 100644 bc-sharp-crypto/src/asn1/Asn1Exception.cs create mode 100644 bc-sharp-crypto/src/asn1/Asn1InputStream.cs create mode 100644 bc-sharp-crypto/src/asn1/Asn1Null.cs create mode 100644 bc-sharp-crypto/src/asn1/Asn1Object.cs create mode 100644 bc-sharp-crypto/src/asn1/Asn1OctetString.cs create mode 100644 bc-sharp-crypto/src/asn1/Asn1OutputStream.cs create mode 100644 bc-sharp-crypto/src/asn1/Asn1ParsingException.cs create mode 100644 bc-sharp-crypto/src/asn1/Asn1Sequence.cs create mode 100644 bc-sharp-crypto/src/asn1/Asn1Set.cs create mode 100644 bc-sharp-crypto/src/asn1/Asn1TaggedObject.cs create mode 100644 bc-sharp-crypto/src/asn1/Asn1Tags.cs create mode 100644 bc-sharp-crypto/src/asn1/BERBitString.cs create mode 100644 bc-sharp-crypto/src/asn1/BERGenerator.cs create mode 100644 bc-sharp-crypto/src/asn1/BEROctetStringGenerator.cs create mode 100644 bc-sharp-crypto/src/asn1/BEROctetStringParser.cs create mode 100644 bc-sharp-crypto/src/asn1/BERSequenceGenerator.cs create mode 100644 bc-sharp-crypto/src/asn1/BERSequenceParser.cs create mode 100644 bc-sharp-crypto/src/asn1/BERSetGenerator.cs create mode 100644 bc-sharp-crypto/src/asn1/BERSetParser.cs create mode 100644 bc-sharp-crypto/src/asn1/BERTaggedObjectParser.cs create mode 100644 bc-sharp-crypto/src/asn1/BerApplicationSpecific.cs create mode 100644 bc-sharp-crypto/src/asn1/BerApplicationSpecificParser.cs create mode 100644 bc-sharp-crypto/src/asn1/BerNull.cs create mode 100644 bc-sharp-crypto/src/asn1/BerOctetString.cs create mode 100644 bc-sharp-crypto/src/asn1/BerOutputStream.cs create mode 100644 bc-sharp-crypto/src/asn1/BerSequence.cs create mode 100644 bc-sharp-crypto/src/asn1/BerSet.cs create mode 100644 bc-sharp-crypto/src/asn1/BerTaggedObject.cs create mode 100644 bc-sharp-crypto/src/asn1/ConstructedOctetStream.cs create mode 100644 bc-sharp-crypto/src/asn1/DERExternal.cs create mode 100644 bc-sharp-crypto/src/asn1/DERExternalParser.cs create mode 100644 bc-sharp-crypto/src/asn1/DERGenerator.cs create mode 100644 bc-sharp-crypto/src/asn1/DEROctetStringParser.cs create mode 100644 bc-sharp-crypto/src/asn1/DERSequenceGenerator.cs create mode 100644 bc-sharp-crypto/src/asn1/DERSequenceParser.cs create mode 100644 bc-sharp-crypto/src/asn1/DERSetGenerator.cs create mode 100644 bc-sharp-crypto/src/asn1/DERSetParser.cs create mode 100644 bc-sharp-crypto/src/asn1/DefiniteLengthInputStream.cs create mode 100644 bc-sharp-crypto/src/asn1/DerApplicationSpecific.cs create mode 100644 bc-sharp-crypto/src/asn1/DerBMPString.cs create mode 100644 bc-sharp-crypto/src/asn1/DerBitString.cs create mode 100644 bc-sharp-crypto/src/asn1/DerBoolean.cs create mode 100644 bc-sharp-crypto/src/asn1/DerEnumerated.cs create mode 100644 bc-sharp-crypto/src/asn1/DerGeneralString.cs create mode 100644 bc-sharp-crypto/src/asn1/DerGeneralizedTime.cs create mode 100644 bc-sharp-crypto/src/asn1/DerGraphicString.cs create mode 100644 bc-sharp-crypto/src/asn1/DerIA5String.cs create mode 100644 bc-sharp-crypto/src/asn1/DerInteger.cs create mode 100644 bc-sharp-crypto/src/asn1/DerNull.cs create mode 100644 bc-sharp-crypto/src/asn1/DerNumericString.cs create mode 100644 bc-sharp-crypto/src/asn1/DerObjectIdentifier.cs create mode 100644 bc-sharp-crypto/src/asn1/DerOctetString.cs create mode 100644 bc-sharp-crypto/src/asn1/DerOutputStream.cs create mode 100644 bc-sharp-crypto/src/asn1/DerPrintableString.cs create mode 100644 bc-sharp-crypto/src/asn1/DerSequence.cs create mode 100644 bc-sharp-crypto/src/asn1/DerSet.cs create mode 100644 bc-sharp-crypto/src/asn1/DerStringBase.cs create mode 100644 bc-sharp-crypto/src/asn1/DerT61String.cs create mode 100644 bc-sharp-crypto/src/asn1/DerTaggedObject.cs create mode 100644 bc-sharp-crypto/src/asn1/DerUTCTime.cs create mode 100644 bc-sharp-crypto/src/asn1/DerUTF8String.cs create mode 100644 bc-sharp-crypto/src/asn1/DerUniversalString.cs create mode 100644 bc-sharp-crypto/src/asn1/DerVideotexString.cs create mode 100644 bc-sharp-crypto/src/asn1/DerVisibleString.cs create mode 100644 bc-sharp-crypto/src/asn1/IAsn1ApplicationSpecificParser.cs create mode 100644 bc-sharp-crypto/src/asn1/IAsn1Choice.cs create mode 100644 bc-sharp-crypto/src/asn1/IAsn1Convertible.cs create mode 100644 bc-sharp-crypto/src/asn1/IAsn1String.cs create mode 100644 bc-sharp-crypto/src/asn1/IndefiniteLengthInputStream.cs create mode 100644 bc-sharp-crypto/src/asn1/LazyASN1InputStream.cs create mode 100644 bc-sharp-crypto/src/asn1/LazyDERSequence.cs create mode 100644 bc-sharp-crypto/src/asn1/LazyDERSet.cs create mode 100644 bc-sharp-crypto/src/asn1/LimitedInputStream.cs create mode 100644 bc-sharp-crypto/src/asn1/OidTokenizer.cs create mode 100644 bc-sharp-crypto/src/asn1/anssi/ANSSINamedCurves.cs create mode 100644 bc-sharp-crypto/src/asn1/anssi/ANSSIObjectIdentifiers.cs create mode 100644 bc-sharp-crypto/src/asn1/bc/BCObjectIdentifiers.cs create mode 100644 bc-sharp-crypto/src/asn1/cmp/CAKeyUpdAnnContent.cs create mode 100644 bc-sharp-crypto/src/asn1/cmp/CertConfirmContent.cs create mode 100644 bc-sharp-crypto/src/asn1/cmp/CertOrEncCert.cs create mode 100644 bc-sharp-crypto/src/asn1/cmp/CertRepMessage.cs create mode 100644 bc-sharp-crypto/src/asn1/cmp/CertResponse.cs create mode 100644 bc-sharp-crypto/src/asn1/cmp/CertStatus.cs create mode 100644 bc-sharp-crypto/src/asn1/cmp/CertifiedKeyPair.cs create mode 100644 bc-sharp-crypto/src/asn1/cmp/Challenge.cs create mode 100644 bc-sharp-crypto/src/asn1/cmp/CmpCertificate.cs create mode 100644 bc-sharp-crypto/src/asn1/cmp/CmpObjectIdentifiers.cs create mode 100644 bc-sharp-crypto/src/asn1/cmp/CrlAnnContent.cs create mode 100644 bc-sharp-crypto/src/asn1/cmp/ErrorMsgContent.cs create mode 100644 bc-sharp-crypto/src/asn1/cmp/GenMsgContent.cs create mode 100644 bc-sharp-crypto/src/asn1/cmp/GenRepContent.cs create mode 100644 bc-sharp-crypto/src/asn1/cmp/InfoTypeAndValue.cs create mode 100644 bc-sharp-crypto/src/asn1/cmp/KeyRecRepContent.cs create mode 100644 bc-sharp-crypto/src/asn1/cmp/OobCertHash.cs create mode 100644 bc-sharp-crypto/src/asn1/cmp/PKIBody.cs create mode 100644 bc-sharp-crypto/src/asn1/cmp/PKIConfirmContent.cs create mode 100644 bc-sharp-crypto/src/asn1/cmp/PKIFailureInfo.cs create mode 100644 bc-sharp-crypto/src/asn1/cmp/PKIFreeText.cs create mode 100644 bc-sharp-crypto/src/asn1/cmp/PKIHeader.cs create mode 100644 bc-sharp-crypto/src/asn1/cmp/PKIHeaderBuilder.cs create mode 100644 bc-sharp-crypto/src/asn1/cmp/PKIMessage.cs create mode 100644 bc-sharp-crypto/src/asn1/cmp/PKIMessages.cs create mode 100644 bc-sharp-crypto/src/asn1/cmp/PKIStatus.cs create mode 100644 bc-sharp-crypto/src/asn1/cmp/PKIStatusInfo.cs create mode 100644 bc-sharp-crypto/src/asn1/cmp/PbmParameter.cs create mode 100644 bc-sharp-crypto/src/asn1/cmp/PollRepContent.cs create mode 100644 bc-sharp-crypto/src/asn1/cmp/PollReqContent.cs create mode 100644 bc-sharp-crypto/src/asn1/cmp/PopoDecKeyChallContent.cs create mode 100644 bc-sharp-crypto/src/asn1/cmp/PopoDecKeyRespContent.cs create mode 100644 bc-sharp-crypto/src/asn1/cmp/ProtectedPart.cs create mode 100644 bc-sharp-crypto/src/asn1/cmp/RevAnnContent.cs create mode 100644 bc-sharp-crypto/src/asn1/cmp/RevDetails.cs create mode 100644 bc-sharp-crypto/src/asn1/cmp/RevRepContent.cs create mode 100644 bc-sharp-crypto/src/asn1/cmp/RevRepContentBuilder.cs create mode 100644 bc-sharp-crypto/src/asn1/cmp/RevReqContent.cs create mode 100644 bc-sharp-crypto/src/asn1/cms/Attribute.cs create mode 100644 bc-sharp-crypto/src/asn1/cms/AttributeTable.cs create mode 100644 bc-sharp-crypto/src/asn1/cms/Attributes.cs create mode 100644 bc-sharp-crypto/src/asn1/cms/AuthEnvelopedData.cs create mode 100644 bc-sharp-crypto/src/asn1/cms/AuthEnvelopedDataParser.cs create mode 100644 bc-sharp-crypto/src/asn1/cms/AuthenticatedData.cs create mode 100644 bc-sharp-crypto/src/asn1/cms/AuthenticatedDataParser.cs create mode 100644 bc-sharp-crypto/src/asn1/cms/CMSAttributes.cs create mode 100644 bc-sharp-crypto/src/asn1/cms/CMSObjectIdentifiers.cs create mode 100644 bc-sharp-crypto/src/asn1/cms/CompressedData.cs create mode 100644 bc-sharp-crypto/src/asn1/cms/CompressedDataParser.cs create mode 100644 bc-sharp-crypto/src/asn1/cms/ContentInfo.cs create mode 100644 bc-sharp-crypto/src/asn1/cms/ContentInfoParser.cs create mode 100644 bc-sharp-crypto/src/asn1/cms/EncryptedContentInfo.cs create mode 100644 bc-sharp-crypto/src/asn1/cms/EncryptedContentInfoParser.cs create mode 100644 bc-sharp-crypto/src/asn1/cms/EncryptedData.cs create mode 100644 bc-sharp-crypto/src/asn1/cms/EnvelopedData.cs create mode 100644 bc-sharp-crypto/src/asn1/cms/EnvelopedDataParser.cs create mode 100644 bc-sharp-crypto/src/asn1/cms/Evidence.cs create mode 100644 bc-sharp-crypto/src/asn1/cms/IssuerAndSerialNumber.cs create mode 100644 bc-sharp-crypto/src/asn1/cms/KEKIdentifier.cs create mode 100644 bc-sharp-crypto/src/asn1/cms/KEKRecipientInfo.cs create mode 100644 bc-sharp-crypto/src/asn1/cms/KeyAgreeRecipientIdentifier.cs create mode 100644 bc-sharp-crypto/src/asn1/cms/KeyAgreeRecipientInfo.cs create mode 100644 bc-sharp-crypto/src/asn1/cms/KeyTransRecipientInfo.cs create mode 100644 bc-sharp-crypto/src/asn1/cms/MetaData.cs create mode 100644 bc-sharp-crypto/src/asn1/cms/OriginatorIdentifierOrKey.cs create mode 100644 bc-sharp-crypto/src/asn1/cms/OriginatorInfo.cs create mode 100644 bc-sharp-crypto/src/asn1/cms/OriginatorPublicKey.cs create mode 100644 bc-sharp-crypto/src/asn1/cms/OtherKeyAttribute.cs create mode 100644 bc-sharp-crypto/src/asn1/cms/OtherRecipientInfo.cs create mode 100644 bc-sharp-crypto/src/asn1/cms/OtherRevocationInfoFormat.cs create mode 100644 bc-sharp-crypto/src/asn1/cms/PasswordRecipientInfo.cs create mode 100644 bc-sharp-crypto/src/asn1/cms/RecipientEncryptedKey.cs create mode 100644 bc-sharp-crypto/src/asn1/cms/RecipientIdentifier.cs create mode 100644 bc-sharp-crypto/src/asn1/cms/RecipientInfo.cs create mode 100644 bc-sharp-crypto/src/asn1/cms/RecipientKeyIdentifier.cs create mode 100644 bc-sharp-crypto/src/asn1/cms/SCVPReqRes.cs create mode 100644 bc-sharp-crypto/src/asn1/cms/SignedData.cs create mode 100644 bc-sharp-crypto/src/asn1/cms/SignedDataParser.cs create mode 100644 bc-sharp-crypto/src/asn1/cms/SignerIdentifier.cs create mode 100644 bc-sharp-crypto/src/asn1/cms/SignerInfo.cs create mode 100644 bc-sharp-crypto/src/asn1/cms/Time.cs create mode 100644 bc-sharp-crypto/src/asn1/cms/TimeStampAndCRL.cs create mode 100644 bc-sharp-crypto/src/asn1/cms/TimeStampTokenEvidence.cs create mode 100644 bc-sharp-crypto/src/asn1/cms/TimeStampedData.cs create mode 100644 bc-sharp-crypto/src/asn1/cms/TimeStampedDataParser.cs create mode 100644 bc-sharp-crypto/src/asn1/cms/ecc/MQVuserKeyingMaterial.cs create mode 100644 bc-sharp-crypto/src/asn1/crmf/AttributeTypeAndValue.cs create mode 100644 bc-sharp-crypto/src/asn1/crmf/CertId.cs create mode 100644 bc-sharp-crypto/src/asn1/crmf/CertReqMessages.cs create mode 100644 bc-sharp-crypto/src/asn1/crmf/CertReqMsg.cs create mode 100644 bc-sharp-crypto/src/asn1/crmf/CertRequest.cs create mode 100644 bc-sharp-crypto/src/asn1/crmf/CertTemplate.cs create mode 100644 bc-sharp-crypto/src/asn1/crmf/CertTemplateBuilder.cs create mode 100644 bc-sharp-crypto/src/asn1/crmf/Controls.cs create mode 100644 bc-sharp-crypto/src/asn1/crmf/CrmfObjectIdentifiers.cs create mode 100644 bc-sharp-crypto/src/asn1/crmf/EncKeyWithID.cs create mode 100644 bc-sharp-crypto/src/asn1/crmf/EncryptedKey.cs create mode 100644 bc-sharp-crypto/src/asn1/crmf/EncryptedValue.cs create mode 100644 bc-sharp-crypto/src/asn1/crmf/OptionalValidity.cs create mode 100644 bc-sharp-crypto/src/asn1/crmf/PKIArchiveOptions.cs create mode 100644 bc-sharp-crypto/src/asn1/crmf/PKIPublicationInfo.cs create mode 100644 bc-sharp-crypto/src/asn1/crmf/PKMacValue.cs create mode 100644 bc-sharp-crypto/src/asn1/crmf/PopoPrivKey.cs create mode 100644 bc-sharp-crypto/src/asn1/crmf/PopoSigningKey.cs create mode 100644 bc-sharp-crypto/src/asn1/crmf/PopoSigningKeyInput.cs create mode 100644 bc-sharp-crypto/src/asn1/crmf/ProofOfPossession.cs create mode 100644 bc-sharp-crypto/src/asn1/crmf/SinglePubInfo.cs create mode 100644 bc-sharp-crypto/src/asn1/crmf/SubsequentMessage.cs create mode 100644 bc-sharp-crypto/src/asn1/cryptopro/CryptoProObjectIdentifiers.cs create mode 100644 bc-sharp-crypto/src/asn1/cryptopro/ECGOST3410NamedCurves.cs create mode 100644 bc-sharp-crypto/src/asn1/cryptopro/ECGOST3410ParamSetParameters.cs create mode 100644 bc-sharp-crypto/src/asn1/cryptopro/GOST28147Parameters.cs create mode 100644 bc-sharp-crypto/src/asn1/cryptopro/GOST3410NamedParameters.cs create mode 100644 bc-sharp-crypto/src/asn1/cryptopro/GOST3410ParamSetParameters.cs create mode 100644 bc-sharp-crypto/src/asn1/cryptopro/GOST3410PublicKeyAlgParameters.cs create mode 100644 bc-sharp-crypto/src/asn1/eac/EACObjectIdentifiers.cs create mode 100644 bc-sharp-crypto/src/asn1/esf/CertificateValues.cs create mode 100644 bc-sharp-crypto/src/asn1/esf/CommitmentTypeIdentifier.cs create mode 100644 bc-sharp-crypto/src/asn1/esf/CommitmentTypeIndication.cs create mode 100644 bc-sharp-crypto/src/asn1/esf/CommitmentTypeQualifier.cs create mode 100644 bc-sharp-crypto/src/asn1/esf/CompleteCertificateRefs.cs create mode 100644 bc-sharp-crypto/src/asn1/esf/CompleteRevocationRefs.cs create mode 100644 bc-sharp-crypto/src/asn1/esf/CrlIdentifier.cs create mode 100644 bc-sharp-crypto/src/asn1/esf/CrlListID.cs create mode 100644 bc-sharp-crypto/src/asn1/esf/CrlOcspRef.cs create mode 100644 bc-sharp-crypto/src/asn1/esf/CrlValidatedID.cs create mode 100644 bc-sharp-crypto/src/asn1/esf/ESFAttributes.cs create mode 100644 bc-sharp-crypto/src/asn1/esf/OcspIdentifier.cs create mode 100644 bc-sharp-crypto/src/asn1/esf/OcspListID.cs create mode 100644 bc-sharp-crypto/src/asn1/esf/OcspResponsesID.cs create mode 100644 bc-sharp-crypto/src/asn1/esf/OtherCertID.cs create mode 100644 bc-sharp-crypto/src/asn1/esf/OtherHash.cs create mode 100644 bc-sharp-crypto/src/asn1/esf/OtherHashAlgAndValue.cs create mode 100644 bc-sharp-crypto/src/asn1/esf/OtherRevRefs.cs create mode 100644 bc-sharp-crypto/src/asn1/esf/OtherRevVals.cs create mode 100644 bc-sharp-crypto/src/asn1/esf/OtherSigningCertificate.cs create mode 100644 bc-sharp-crypto/src/asn1/esf/RevocationValues.cs create mode 100644 bc-sharp-crypto/src/asn1/esf/SigPolicyQualifierInfo.cs create mode 100644 bc-sharp-crypto/src/asn1/esf/SignaturePolicyId.cs create mode 100644 bc-sharp-crypto/src/asn1/esf/SignaturePolicyIdentifier.cs create mode 100644 bc-sharp-crypto/src/asn1/esf/SignerAttribute.cs create mode 100644 bc-sharp-crypto/src/asn1/esf/SignerLocation.cs create mode 100644 bc-sharp-crypto/src/asn1/ess/ContentHints.cs create mode 100644 bc-sharp-crypto/src/asn1/ess/ContentIdentifier.cs create mode 100644 bc-sharp-crypto/src/asn1/ess/ESSCertID.cs create mode 100644 bc-sharp-crypto/src/asn1/ess/ESSCertIDv2.cs create mode 100644 bc-sharp-crypto/src/asn1/ess/OtherCertID.cs create mode 100644 bc-sharp-crypto/src/asn1/ess/OtherSigningCertificate.cs create mode 100644 bc-sharp-crypto/src/asn1/ess/SigningCertificate.cs create mode 100644 bc-sharp-crypto/src/asn1/ess/SigningCertificateV2.cs create mode 100644 bc-sharp-crypto/src/asn1/gm/GMNamedCurves.cs create mode 100644 bc-sharp-crypto/src/asn1/gm/GMObjectIdentifiers.cs create mode 100644 bc-sharp-crypto/src/asn1/gnu/GNUObjectIdentifiers.cs create mode 100644 bc-sharp-crypto/src/asn1/iana/IANAObjectIdentifiers.cs create mode 100644 bc-sharp-crypto/src/asn1/icao/CscaMasterList.cs create mode 100644 bc-sharp-crypto/src/asn1/icao/DataGroupHash.cs create mode 100644 bc-sharp-crypto/src/asn1/icao/ICAOObjectIdentifiers.cs create mode 100644 bc-sharp-crypto/src/asn1/icao/LDSSecurityObject.cs create mode 100644 bc-sharp-crypto/src/asn1/icao/LDSVersionInfo.cs create mode 100644 bc-sharp-crypto/src/asn1/isismtt/ISISMTTObjectIdentifiers.cs create mode 100644 bc-sharp-crypto/src/asn1/isismtt/ocsp/CertHash.cs create mode 100644 bc-sharp-crypto/src/asn1/isismtt/ocsp/RequestedCertificate.cs create mode 100644 bc-sharp-crypto/src/asn1/isismtt/x509/AdditionalInformationSyntax.cs create mode 100644 bc-sharp-crypto/src/asn1/isismtt/x509/AdmissionSyntax.cs create mode 100644 bc-sharp-crypto/src/asn1/isismtt/x509/Admissions.cs create mode 100644 bc-sharp-crypto/src/asn1/isismtt/x509/DeclarationOfMajority.cs create mode 100644 bc-sharp-crypto/src/asn1/isismtt/x509/MonetaryLimit.cs create mode 100644 bc-sharp-crypto/src/asn1/isismtt/x509/NamingAuthority.cs create mode 100644 bc-sharp-crypto/src/asn1/isismtt/x509/ProcurationSyntax.cs create mode 100644 bc-sharp-crypto/src/asn1/isismtt/x509/ProfessionInfo.cs create mode 100644 bc-sharp-crypto/src/asn1/isismtt/x509/Restriction.cs create mode 100644 bc-sharp-crypto/src/asn1/kisa/KISAObjectIdentifiers.cs create mode 100644 bc-sharp-crypto/src/asn1/microsoft/MicrosoftObjectIdentifiers.cs create mode 100644 bc-sharp-crypto/src/asn1/misc/CAST5CBCParameters.cs create mode 100644 bc-sharp-crypto/src/asn1/misc/IDEACBCPar.cs create mode 100644 bc-sharp-crypto/src/asn1/misc/MiscObjectIdentifiers.cs create mode 100644 bc-sharp-crypto/src/asn1/misc/NetscapeCertType.cs create mode 100644 bc-sharp-crypto/src/asn1/misc/NetscapeRevocationURL.cs create mode 100644 bc-sharp-crypto/src/asn1/misc/VerisignCzagExtension.cs create mode 100644 bc-sharp-crypto/src/asn1/mozilla/PublicKeyAndChallenge.cs create mode 100644 bc-sharp-crypto/src/asn1/nist/NISTNamedCurves.cs create mode 100644 bc-sharp-crypto/src/asn1/nist/NISTObjectIdentifiers.cs create mode 100644 bc-sharp-crypto/src/asn1/ntt/NTTObjectIdentifiers.cs create mode 100644 bc-sharp-crypto/src/asn1/ocsp/BasicOCSPResponse.cs create mode 100644 bc-sharp-crypto/src/asn1/ocsp/CertID.cs create mode 100644 bc-sharp-crypto/src/asn1/ocsp/CertStatus.cs create mode 100644 bc-sharp-crypto/src/asn1/ocsp/CrlID.cs create mode 100644 bc-sharp-crypto/src/asn1/ocsp/OCSPObjectIdentifiers.cs create mode 100644 bc-sharp-crypto/src/asn1/ocsp/OCSPRequest.cs create mode 100644 bc-sharp-crypto/src/asn1/ocsp/OCSPResponse.cs create mode 100644 bc-sharp-crypto/src/asn1/ocsp/OCSPResponseStatus.cs create mode 100644 bc-sharp-crypto/src/asn1/ocsp/Request.cs create mode 100644 bc-sharp-crypto/src/asn1/ocsp/ResponderID.cs create mode 100644 bc-sharp-crypto/src/asn1/ocsp/ResponseBytes.cs create mode 100644 bc-sharp-crypto/src/asn1/ocsp/ResponseData.cs create mode 100644 bc-sharp-crypto/src/asn1/ocsp/RevokedInfo.cs create mode 100644 bc-sharp-crypto/src/asn1/ocsp/ServiceLocator.cs create mode 100644 bc-sharp-crypto/src/asn1/ocsp/Signature.cs create mode 100644 bc-sharp-crypto/src/asn1/ocsp/SingleResponse.cs create mode 100644 bc-sharp-crypto/src/asn1/ocsp/TBSRequest.cs create mode 100644 bc-sharp-crypto/src/asn1/oiw/ElGamalParameter.cs create mode 100644 bc-sharp-crypto/src/asn1/oiw/OIWObjectIdentifiers.cs create mode 100644 bc-sharp-crypto/src/asn1/pkcs/Attribute.cs create mode 100644 bc-sharp-crypto/src/asn1/pkcs/AuthenticatedSafe.cs create mode 100644 bc-sharp-crypto/src/asn1/pkcs/CertBag.cs create mode 100644 bc-sharp-crypto/src/asn1/pkcs/CertificationRequest.cs create mode 100644 bc-sharp-crypto/src/asn1/pkcs/CertificationRequestInfo.cs create mode 100644 bc-sharp-crypto/src/asn1/pkcs/ContentInfo.cs create mode 100644 bc-sharp-crypto/src/asn1/pkcs/DHParameter.cs create mode 100644 bc-sharp-crypto/src/asn1/pkcs/EncryptedData.cs create mode 100644 bc-sharp-crypto/src/asn1/pkcs/EncryptedPrivateKeyInfo.cs create mode 100644 bc-sharp-crypto/src/asn1/pkcs/EncryptionScheme.cs create mode 100644 bc-sharp-crypto/src/asn1/pkcs/IssuerAndSerialNumber.cs create mode 100644 bc-sharp-crypto/src/asn1/pkcs/KeyDerivationFunc.cs create mode 100644 bc-sharp-crypto/src/asn1/pkcs/MacData.cs create mode 100644 bc-sharp-crypto/src/asn1/pkcs/PBEParameter.cs create mode 100644 bc-sharp-crypto/src/asn1/pkcs/PBES2Parameters.cs create mode 100644 bc-sharp-crypto/src/asn1/pkcs/PBKDF2Params.cs create mode 100644 bc-sharp-crypto/src/asn1/pkcs/PKCS12PBEParams.cs create mode 100644 bc-sharp-crypto/src/asn1/pkcs/PKCSObjectIdentifiers.cs create mode 100644 bc-sharp-crypto/src/asn1/pkcs/Pfx.cs create mode 100644 bc-sharp-crypto/src/asn1/pkcs/PrivateKeyInfo.cs create mode 100644 bc-sharp-crypto/src/asn1/pkcs/RC2CBCParameter.cs create mode 100644 bc-sharp-crypto/src/asn1/pkcs/RSAESOAEPparams.cs create mode 100644 bc-sharp-crypto/src/asn1/pkcs/RSAPrivateKeyStructure.cs create mode 100644 bc-sharp-crypto/src/asn1/pkcs/RSASSAPSSparams.cs create mode 100644 bc-sharp-crypto/src/asn1/pkcs/SafeBag.cs create mode 100644 bc-sharp-crypto/src/asn1/pkcs/SignedData.cs create mode 100644 bc-sharp-crypto/src/asn1/pkcs/SignerInfo.cs create mode 100644 bc-sharp-crypto/src/asn1/sec/ECPrivateKeyStructure.cs create mode 100644 bc-sharp-crypto/src/asn1/sec/SECNamedCurves.cs create mode 100644 bc-sharp-crypto/src/asn1/sec/SECObjectIdentifiers.cs create mode 100644 bc-sharp-crypto/src/asn1/smime/SMIMEAttributes.cs create mode 100644 bc-sharp-crypto/src/asn1/smime/SMIMECapabilities.cs create mode 100644 bc-sharp-crypto/src/asn1/smime/SMIMECapabilitiesAttribute.cs create mode 100644 bc-sharp-crypto/src/asn1/smime/SMIMECapability.cs create mode 100644 bc-sharp-crypto/src/asn1/smime/SMIMECapabilityVector.cs create mode 100644 bc-sharp-crypto/src/asn1/smime/SMIMEEncryptionKeyPreferenceAttribute.cs create mode 100644 bc-sharp-crypto/src/asn1/teletrust/TeleTrusTNamedCurves.cs create mode 100644 bc-sharp-crypto/src/asn1/teletrust/TeleTrusTObjectIdentifiers.cs create mode 100644 bc-sharp-crypto/src/asn1/tsp/Accuracy.cs create mode 100644 bc-sharp-crypto/src/asn1/tsp/MessageImprint.cs create mode 100644 bc-sharp-crypto/src/asn1/tsp/TSTInfo.cs create mode 100644 bc-sharp-crypto/src/asn1/tsp/TimeStampReq.cs create mode 100644 bc-sharp-crypto/src/asn1/tsp/TimeStampResp.cs create mode 100644 bc-sharp-crypto/src/asn1/util/Asn1Dump.cs create mode 100644 bc-sharp-crypto/src/asn1/util/Dump.cs create mode 100644 bc-sharp-crypto/src/asn1/util/FilterStream.cs create mode 100644 bc-sharp-crypto/src/asn1/x500/DirectoryString.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/AccessDescription.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/AlgorithmIdentifier.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/AttCertIssuer.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/AttCertValidityPeriod.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/Attribute.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/AttributeCertificate.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/AttributeCertificateInfo.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/AttributeTable.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/AuthorityInformationAccess.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/AuthorityKeyIdentifier.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/BasicConstraints.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/CRLDistPoint.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/CRLNumber.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/CRLReason.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/CertPolicyId.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/CertificateList.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/CertificatePair.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/CertificatePolicies.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/DSAParameter.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/DigestInfo.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/DisplayText.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/DistributionPoint.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/DistributionPointName.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/ExtendedKeyUsage.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/GeneralName.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/GeneralNames.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/GeneralSubtree.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/Holder.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/IetfAttrSyntax.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/IssuerSerial.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/IssuingDistributionPoint.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/KeyPurposeId.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/KeyUsage.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/NameConstraints.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/NoticeReference.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/ObjectDigestInfo.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/PolicyInformation.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/PolicyMappings.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/PolicyQualifierId.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/PolicyQualifierInfo.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/PrivateKeyUsagePeriod.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/RSAPublicKeyStructure.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/ReasonFlags.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/RoleSyntax.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/SubjectDirectoryAttributes.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/SubjectKeyIdentifier.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/SubjectPublicKeyInfo.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/TBSCertList.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/TBSCertificateStructure.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/Target.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/TargetInformation.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/Targets.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/Time.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/UserNotice.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/V1TBSCertificateGenerator.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/V2AttributeCertificateInfoGenerator.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/V2Form.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/V2TBSCertListGenerator.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/V3TBSCertificateGenerator.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/X509Attributes.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/X509CertificateStructure.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/X509DefaultEntryConverter.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/X509Extension.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/X509Extensions.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/X509ExtensionsGenerator.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/X509Name.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/X509NameEntryConverter.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/X509NameTokenizer.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/X509ObjectIdentifiers.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/qualified/BiometricData.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/qualified/ETSIQCObjectIdentifiers.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/qualified/Iso4217CurrencyCode.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/qualified/MonetaryValue.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/qualified/QCStatement.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/qualified/RFC3739QCObjectIdentifiers.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/qualified/SemanticsInformation.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/qualified/TypeOfBiometricData.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/sigi/NameOrPseudonym.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/sigi/PersonalData.cs create mode 100644 bc-sharp-crypto/src/asn1/x509/sigi/SigIObjectIdentifiers.cs create mode 100644 bc-sharp-crypto/src/asn1/x9/DHDomainParameters.cs create mode 100644 bc-sharp-crypto/src/asn1/x9/DHPublicKey.cs create mode 100644 bc-sharp-crypto/src/asn1/x9/DHValidationParms.cs create mode 100644 bc-sharp-crypto/src/asn1/x9/ECNamedCurveTable.cs create mode 100644 bc-sharp-crypto/src/asn1/x9/KeySpecificInfo.cs create mode 100644 bc-sharp-crypto/src/asn1/x9/OtherInfo.cs create mode 100644 bc-sharp-crypto/src/asn1/x9/X962NamedCurves.cs create mode 100644 bc-sharp-crypto/src/asn1/x9/X962Parameters.cs create mode 100644 bc-sharp-crypto/src/asn1/x9/X9Curve.cs create mode 100644 bc-sharp-crypto/src/asn1/x9/X9ECParameters.cs create mode 100644 bc-sharp-crypto/src/asn1/x9/X9ECParametersHolder.cs create mode 100644 bc-sharp-crypto/src/asn1/x9/X9ECPoint.cs create mode 100644 bc-sharp-crypto/src/asn1/x9/X9FieldElement.cs create mode 100644 bc-sharp-crypto/src/asn1/x9/X9FieldID.cs create mode 100644 bc-sharp-crypto/src/asn1/x9/X9IntegerConverter.cs create mode 100644 bc-sharp-crypto/src/asn1/x9/X9ObjectIdentifiers.cs create mode 100644 bc-sharp-crypto/src/bcpg/ArmoredInputStream.cs create mode 100644 bc-sharp-crypto/src/bcpg/ArmoredOutputStream.cs create mode 100644 bc-sharp-crypto/src/bcpg/BcpgInputStream.cs create mode 100644 bc-sharp-crypto/src/bcpg/BcpgObject.cs create mode 100644 bc-sharp-crypto/src/bcpg/BcpgOutputStream.cs create mode 100644 bc-sharp-crypto/src/bcpg/CompressedDataPacket.cs create mode 100644 bc-sharp-crypto/src/bcpg/CompressionAlgorithmTags.cs create mode 100644 bc-sharp-crypto/src/bcpg/ContainedPacket.cs create mode 100644 bc-sharp-crypto/src/bcpg/Crc24.cs create mode 100644 bc-sharp-crypto/src/bcpg/DsaPublicBcpgKey.cs create mode 100644 bc-sharp-crypto/src/bcpg/DsaSecretBcpgKey.cs create mode 100644 bc-sharp-crypto/src/bcpg/ECDHPublicBCPGKey.cs create mode 100644 bc-sharp-crypto/src/bcpg/ECDsaPublicBCPGKey.cs create mode 100644 bc-sharp-crypto/src/bcpg/ECPublicBCPGKey.cs create mode 100644 bc-sharp-crypto/src/bcpg/ECSecretBCPGKey.cs create mode 100644 bc-sharp-crypto/src/bcpg/ElGamalPublicBcpgKey.cs create mode 100644 bc-sharp-crypto/src/bcpg/ElGamalSecretBcpgKey.cs create mode 100644 bc-sharp-crypto/src/bcpg/ExperimentalPacket.cs create mode 100644 bc-sharp-crypto/src/bcpg/HashAlgorithmTags.cs create mode 100644 bc-sharp-crypto/src/bcpg/IBcpgKey.cs create mode 100644 bc-sharp-crypto/src/bcpg/InputStreamPacket.cs create mode 100644 bc-sharp-crypto/src/bcpg/LiteralDataPacket.cs create mode 100644 bc-sharp-crypto/src/bcpg/MPInteger.cs create mode 100644 bc-sharp-crypto/src/bcpg/MarkerPacket.cs create mode 100644 bc-sharp-crypto/src/bcpg/ModDetectionCodePacket.cs create mode 100644 bc-sharp-crypto/src/bcpg/OnePassSignaturePacket.cs create mode 100644 bc-sharp-crypto/src/bcpg/OutputStreamPacket.cs create mode 100644 bc-sharp-crypto/src/bcpg/Packet.cs create mode 100644 bc-sharp-crypto/src/bcpg/PacketTags.cs create mode 100644 bc-sharp-crypto/src/bcpg/PublicKeyAlgorithmTags.cs create mode 100644 bc-sharp-crypto/src/bcpg/PublicKeyEncSessionPacket.cs create mode 100644 bc-sharp-crypto/src/bcpg/PublicKeyPacket.cs create mode 100644 bc-sharp-crypto/src/bcpg/PublicSubkeyPacket.cs create mode 100644 bc-sharp-crypto/src/bcpg/RsaPublicBcpgKey.cs create mode 100644 bc-sharp-crypto/src/bcpg/RsaSecretBcpgKey.cs create mode 100644 bc-sharp-crypto/src/bcpg/S2k.cs create mode 100644 bc-sharp-crypto/src/bcpg/SecretKeyPacket.cs create mode 100644 bc-sharp-crypto/src/bcpg/SecretSubkeyPacket.cs create mode 100644 bc-sharp-crypto/src/bcpg/SignaturePacket.cs create mode 100644 bc-sharp-crypto/src/bcpg/SignatureSubpacket.cs create mode 100644 bc-sharp-crypto/src/bcpg/SignatureSubpacketTags.cs create mode 100644 bc-sharp-crypto/src/bcpg/SignatureSubpacketsReader.cs create mode 100644 bc-sharp-crypto/src/bcpg/SymmetricEncDataPacket.cs create mode 100644 bc-sharp-crypto/src/bcpg/SymmetricEncIntegrityPacket.cs create mode 100644 bc-sharp-crypto/src/bcpg/SymmetricKeyAlgorithmTags.cs create mode 100644 bc-sharp-crypto/src/bcpg/SymmetricKeyEncSessionPacket.cs create mode 100644 bc-sharp-crypto/src/bcpg/TrustPacket.cs create mode 100644 bc-sharp-crypto/src/bcpg/UserAttributePacket.cs create mode 100644 bc-sharp-crypto/src/bcpg/UserAttributeSubpacket.cs create mode 100644 bc-sharp-crypto/src/bcpg/UserAttributeSubpacketTags.cs create mode 100644 bc-sharp-crypto/src/bcpg/UserAttributeSubpacketsReader.cs create mode 100644 bc-sharp-crypto/src/bcpg/UserIdPacket.cs create mode 100644 bc-sharp-crypto/src/bcpg/attr/ImageAttrib.cs create mode 100644 bc-sharp-crypto/src/bcpg/sig/EmbeddedSignature.cs create mode 100644 bc-sharp-crypto/src/bcpg/sig/Exportable.cs create mode 100644 bc-sharp-crypto/src/bcpg/sig/Features.cs create mode 100644 bc-sharp-crypto/src/bcpg/sig/IssuerKeyId.cs create mode 100644 bc-sharp-crypto/src/bcpg/sig/KeyExpirationTime.cs create mode 100644 bc-sharp-crypto/src/bcpg/sig/KeyFlags.cs create mode 100644 bc-sharp-crypto/src/bcpg/sig/NotationData.cs create mode 100644 bc-sharp-crypto/src/bcpg/sig/PreferredAlgorithms.cs create mode 100644 bc-sharp-crypto/src/bcpg/sig/PrimaryUserId.cs create mode 100644 bc-sharp-crypto/src/bcpg/sig/Revocable.cs create mode 100644 bc-sharp-crypto/src/bcpg/sig/RevocationKey.cs create mode 100644 bc-sharp-crypto/src/bcpg/sig/RevocationKeyTags.cs create mode 100644 bc-sharp-crypto/src/bcpg/sig/RevocationReason.cs create mode 100644 bc-sharp-crypto/src/bcpg/sig/RevocationReasonTags.cs create mode 100644 bc-sharp-crypto/src/bcpg/sig/SignatureCreationTime.cs create mode 100644 bc-sharp-crypto/src/bcpg/sig/SignatureExpirationTime.cs create mode 100644 bc-sharp-crypto/src/bcpg/sig/SignerUserId.cs create mode 100644 bc-sharp-crypto/src/bcpg/sig/TrustSignature.cs create mode 100644 bc-sharp-crypto/src/cms/BaseDigestCalculator.cs create mode 100644 bc-sharp-crypto/src/cms/CMSAttributeTableGenerationException.cs create mode 100644 bc-sharp-crypto/src/cms/CMSAttributeTableGenerator.cs create mode 100644 bc-sharp-crypto/src/cms/CMSAuthEnvelopedData.cs create mode 100644 bc-sharp-crypto/src/cms/CMSAuthEnvelopedGenerator.cs create mode 100644 bc-sharp-crypto/src/cms/CMSAuthenticatedData.cs create mode 100644 bc-sharp-crypto/src/cms/CMSAuthenticatedDataGenerator.cs create mode 100644 bc-sharp-crypto/src/cms/CMSAuthenticatedDataParser.cs create mode 100644 bc-sharp-crypto/src/cms/CMSAuthenticatedDataStreamGenerator.cs create mode 100644 bc-sharp-crypto/src/cms/CMSAuthenticatedGenerator.cs create mode 100644 bc-sharp-crypto/src/cms/CMSCompressedData.cs create mode 100644 bc-sharp-crypto/src/cms/CMSCompressedDataGenerator.cs create mode 100644 bc-sharp-crypto/src/cms/CMSCompressedDataParser.cs create mode 100644 bc-sharp-crypto/src/cms/CMSCompressedDataStreamGenerator.cs create mode 100644 bc-sharp-crypto/src/cms/CMSContentInfoParser.cs create mode 100644 bc-sharp-crypto/src/cms/CMSEnvelopedData.cs create mode 100644 bc-sharp-crypto/src/cms/CMSEnvelopedDataGenerator.cs create mode 100644 bc-sharp-crypto/src/cms/CMSEnvelopedDataParser.cs create mode 100644 bc-sharp-crypto/src/cms/CMSEnvelopedDataStreamGenerator.cs create mode 100644 bc-sharp-crypto/src/cms/CMSEnvelopedGenerator.cs create mode 100644 bc-sharp-crypto/src/cms/CMSEnvelopedHelper.cs create mode 100644 bc-sharp-crypto/src/cms/CMSException.cs create mode 100644 bc-sharp-crypto/src/cms/CMSPBEKey.cs create mode 100644 bc-sharp-crypto/src/cms/CMSProcessable.cs create mode 100644 bc-sharp-crypto/src/cms/CMSProcessableByteArray.cs create mode 100644 bc-sharp-crypto/src/cms/CMSProcessableFile.cs create mode 100644 bc-sharp-crypto/src/cms/CMSProcessableInputStream.cs create mode 100644 bc-sharp-crypto/src/cms/CMSReadable.cs create mode 100644 bc-sharp-crypto/src/cms/CMSSecureReadable.cs create mode 100644 bc-sharp-crypto/src/cms/CMSSignedData.cs create mode 100644 bc-sharp-crypto/src/cms/CMSSignedDataGenerator.cs create mode 100644 bc-sharp-crypto/src/cms/CMSSignedDataParser.cs create mode 100644 bc-sharp-crypto/src/cms/CMSSignedDataStreamGenerator.cs create mode 100644 bc-sharp-crypto/src/cms/CMSSignedGenerator.cs create mode 100644 bc-sharp-crypto/src/cms/CMSSignedHelper.cs create mode 100644 bc-sharp-crypto/src/cms/CMSStreamException.cs create mode 100644 bc-sharp-crypto/src/cms/CMSTypedStream.cs create mode 100644 bc-sharp-crypto/src/cms/CMSUtils.cs create mode 100644 bc-sharp-crypto/src/cms/CounterSignatureDigestCalculator.cs create mode 100644 bc-sharp-crypto/src/cms/DefaultAuthenticatedAttributeTableGenerator.cs create mode 100644 bc-sharp-crypto/src/cms/DefaultSignedAttributeTableGenerator.cs create mode 100644 bc-sharp-crypto/src/cms/DigOutputStream.cs create mode 100644 bc-sharp-crypto/src/cms/IDigestCalculator.cs create mode 100644 bc-sharp-crypto/src/cms/KEKRecipientInfoGenerator.cs create mode 100644 bc-sharp-crypto/src/cms/KEKRecipientInformation.cs create mode 100644 bc-sharp-crypto/src/cms/KeyAgreeRecipientInfoGenerator.cs create mode 100644 bc-sharp-crypto/src/cms/KeyAgreeRecipientInformation.cs create mode 100644 bc-sharp-crypto/src/cms/KeyTransRecipientInfoGenerator.cs create mode 100644 bc-sharp-crypto/src/cms/KeyTransRecipientInformation.cs create mode 100644 bc-sharp-crypto/src/cms/MacOutputStream.cs create mode 100644 bc-sharp-crypto/src/cms/OriginatorId.cs create mode 100644 bc-sharp-crypto/src/cms/OriginatorInfoGenerator.cs create mode 100644 bc-sharp-crypto/src/cms/OriginatorInformation.cs create mode 100644 bc-sharp-crypto/src/cms/PKCS5Scheme2PBEKey.cs create mode 100644 bc-sharp-crypto/src/cms/PKCS5Scheme2UTF8PBEKey.cs create mode 100644 bc-sharp-crypto/src/cms/PasswordRecipientInfoGenerator.cs create mode 100644 bc-sharp-crypto/src/cms/PasswordRecipientInformation.cs create mode 100644 bc-sharp-crypto/src/cms/RecipientId.cs create mode 100644 bc-sharp-crypto/src/cms/RecipientInfoGenerator.cs create mode 100644 bc-sharp-crypto/src/cms/RecipientInformation.cs create mode 100644 bc-sharp-crypto/src/cms/RecipientInformationStore.cs create mode 100644 bc-sharp-crypto/src/cms/SigOutputStream.cs create mode 100644 bc-sharp-crypto/src/cms/SignerId.cs create mode 100644 bc-sharp-crypto/src/cms/SignerInfoGenerator.cs create mode 100644 bc-sharp-crypto/src/cms/SignerInformation.cs create mode 100644 bc-sharp-crypto/src/cms/SignerInformationStore.cs create mode 100644 bc-sharp-crypto/src/cms/SimpleAttributeTableGenerator.cs create mode 100644 bc-sharp-crypto/src/crypto/AsymmetricCipherKeyPair.cs create mode 100644 bc-sharp-crypto/src/crypto/AsymmetricKeyParameter.cs create mode 100644 bc-sharp-crypto/src/crypto/BufferedAeadBlockCipher.cs create mode 100644 bc-sharp-crypto/src/crypto/BufferedAsymmetricBlockCipher.cs create mode 100644 bc-sharp-crypto/src/crypto/BufferedBlockCipher.cs create mode 100644 bc-sharp-crypto/src/crypto/BufferedCipherBase.cs create mode 100644 bc-sharp-crypto/src/crypto/BufferedIesCipher.cs create mode 100644 bc-sharp-crypto/src/crypto/BufferedStreamCipher.cs create mode 100644 bc-sharp-crypto/src/crypto/Check.cs create mode 100644 bc-sharp-crypto/src/crypto/CipherKeyGenerator.cs create mode 100644 bc-sharp-crypto/src/crypto/CryptoException.cs create mode 100644 bc-sharp-crypto/src/crypto/DataLengthException.cs create mode 100644 bc-sharp-crypto/src/crypto/IAsymmetricBlockCipher.cs create mode 100644 bc-sharp-crypto/src/crypto/IAsymmetricCipherKeyPairGenerator.cs create mode 100644 bc-sharp-crypto/src/crypto/IBasicAgreement.cs create mode 100644 bc-sharp-crypto/src/crypto/IBlockCipher.cs create mode 100644 bc-sharp-crypto/src/crypto/IBlockResult.cs create mode 100644 bc-sharp-crypto/src/crypto/IBufferedCipher.cs create mode 100644 bc-sharp-crypto/src/crypto/ICipherParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/IDSA.cs create mode 100644 bc-sharp-crypto/src/crypto/IDerivationFunction.cs create mode 100644 bc-sharp-crypto/src/crypto/IDerivationParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/IDigest.cs create mode 100644 bc-sharp-crypto/src/crypto/IEntropySource.cs create mode 100644 bc-sharp-crypto/src/crypto/IEntropySourceProvider.cs create mode 100644 bc-sharp-crypto/src/crypto/IMac.cs create mode 100644 bc-sharp-crypto/src/crypto/ISignatureFactory.cs create mode 100644 bc-sharp-crypto/src/crypto/ISigner.cs create mode 100644 bc-sharp-crypto/src/crypto/ISignerWithRecovery.cs create mode 100644 bc-sharp-crypto/src/crypto/IStreamCalculator.cs create mode 100644 bc-sharp-crypto/src/crypto/IStreamCipher.cs create mode 100644 bc-sharp-crypto/src/crypto/IVerifier.cs create mode 100644 bc-sharp-crypto/src/crypto/IVerifierFactory.cs create mode 100644 bc-sharp-crypto/src/crypto/IVerifierFactoryProvider.cs create mode 100644 bc-sharp-crypto/src/crypto/IWrapper.cs create mode 100644 bc-sharp-crypto/src/crypto/IXof.cs create mode 100644 bc-sharp-crypto/src/crypto/InvalidCipherTextException.cs create mode 100644 bc-sharp-crypto/src/crypto/KeyGenerationParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/MaxBytesExceededException.cs create mode 100644 bc-sharp-crypto/src/crypto/OutputLengthException.cs create mode 100644 bc-sharp-crypto/src/crypto/PbeParametersGenerator.cs create mode 100644 bc-sharp-crypto/src/crypto/StreamBlockCipher.cs create mode 100644 bc-sharp-crypto/src/crypto/agreement/DHAgreement.cs create mode 100644 bc-sharp-crypto/src/crypto/agreement/DHBasicAgreement.cs create mode 100644 bc-sharp-crypto/src/crypto/agreement/DHStandardGroups.cs create mode 100644 bc-sharp-crypto/src/crypto/agreement/ECDHBasicAgreement.cs create mode 100644 bc-sharp-crypto/src/crypto/agreement/ECDHCBasicAgreement.cs create mode 100644 bc-sharp-crypto/src/crypto/agreement/ECDHWithKdfBasicAgreement.cs create mode 100644 bc-sharp-crypto/src/crypto/agreement/ECMqvBasicAgreement.cs create mode 100644 bc-sharp-crypto/src/crypto/agreement/ECMqvWithKdfBasicAgreement.cs create mode 100644 bc-sharp-crypto/src/crypto/agreement/jpake/JPakeParticipant.cs create mode 100644 bc-sharp-crypto/src/crypto/agreement/jpake/JPakePrimeOrderGroup.cs create mode 100644 bc-sharp-crypto/src/crypto/agreement/jpake/JPakePrimeOrderGroups.cs create mode 100644 bc-sharp-crypto/src/crypto/agreement/jpake/JPakeRound1Payload.cs create mode 100644 bc-sharp-crypto/src/crypto/agreement/jpake/JPakeRound2Payload.cs create mode 100644 bc-sharp-crypto/src/crypto/agreement/jpake/JPakeRound3Payload.cs create mode 100644 bc-sharp-crypto/src/crypto/agreement/jpake/JPakeUtilities.cs create mode 100644 bc-sharp-crypto/src/crypto/agreement/kdf/DHKdfParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/agreement/kdf/DHKekGenerator.cs create mode 100644 bc-sharp-crypto/src/crypto/agreement/kdf/ECDHKekGenerator.cs create mode 100644 bc-sharp-crypto/src/crypto/agreement/srp/SRP6Client.cs create mode 100644 bc-sharp-crypto/src/crypto/agreement/srp/SRP6Server.cs create mode 100644 bc-sharp-crypto/src/crypto/agreement/srp/SRP6StandardGroups.cs create mode 100644 bc-sharp-crypto/src/crypto/agreement/srp/SRP6Utilities.cs create mode 100644 bc-sharp-crypto/src/crypto/agreement/srp/SRP6VerifierGenerator.cs create mode 100644 bc-sharp-crypto/src/crypto/digests/DSTU7564Digest.cs create mode 100644 bc-sharp-crypto/src/crypto/digests/GOST3411Digest.cs create mode 100644 bc-sharp-crypto/src/crypto/digests/GOST3411_2012Digest.cs create mode 100644 bc-sharp-crypto/src/crypto/digests/GOST3411_2012_256Digest.cs create mode 100644 bc-sharp-crypto/src/crypto/digests/GOST3411_2012_512Digest.cs create mode 100644 bc-sharp-crypto/src/crypto/digests/GeneralDigest.cs create mode 100644 bc-sharp-crypto/src/crypto/digests/KeccakDigest.cs create mode 100644 bc-sharp-crypto/src/crypto/digests/LongDigest.cs create mode 100644 bc-sharp-crypto/src/crypto/digests/MD2Digest.cs create mode 100644 bc-sharp-crypto/src/crypto/digests/MD4Digest.cs create mode 100644 bc-sharp-crypto/src/crypto/digests/MD5Digest.cs create mode 100644 bc-sharp-crypto/src/crypto/digests/NonMemoableDigest.cs create mode 100644 bc-sharp-crypto/src/crypto/digests/NullDigest.cs create mode 100644 bc-sharp-crypto/src/crypto/digests/RipeMD128Digest.cs create mode 100644 bc-sharp-crypto/src/crypto/digests/RipeMD160Digest.cs create mode 100644 bc-sharp-crypto/src/crypto/digests/RipeMD256Digest.cs create mode 100644 bc-sharp-crypto/src/crypto/digests/RipeMD320Digest.cs create mode 100644 bc-sharp-crypto/src/crypto/digests/SHA3Digest.cs create mode 100644 bc-sharp-crypto/src/crypto/digests/SM3Digest.cs create mode 100644 bc-sharp-crypto/src/crypto/digests/Sha1Digest.cs create mode 100644 bc-sharp-crypto/src/crypto/digests/Sha224Digest.cs create mode 100644 bc-sharp-crypto/src/crypto/digests/Sha256Digest.cs create mode 100644 bc-sharp-crypto/src/crypto/digests/Sha384Digest.cs create mode 100644 bc-sharp-crypto/src/crypto/digests/Sha512Digest.cs create mode 100644 bc-sharp-crypto/src/crypto/digests/Sha512tDigest.cs create mode 100644 bc-sharp-crypto/src/crypto/digests/ShakeDigest.cs create mode 100644 bc-sharp-crypto/src/crypto/digests/ShortenedDigest.cs create mode 100644 bc-sharp-crypto/src/crypto/digests/SkeinDigest.cs create mode 100644 bc-sharp-crypto/src/crypto/digests/SkeinEngine.cs create mode 100644 bc-sharp-crypto/src/crypto/digests/TigerDigest.cs create mode 100644 bc-sharp-crypto/src/crypto/digests/WhirlpoolDigest.cs create mode 100644 bc-sharp-crypto/src/crypto/ec/CustomNamedCurves.cs create mode 100644 bc-sharp-crypto/src/crypto/encodings/ISO9796d1Encoding.cs create mode 100644 bc-sharp-crypto/src/crypto/encodings/OaepEncoding.cs create mode 100644 bc-sharp-crypto/src/crypto/encodings/Pkcs1Encoding.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/AesEngine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/AesFastEngine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/AesLightEngine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/AesWrapEngine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/BlowfishEngine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/CamelliaEngine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/CamelliaLightEngine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/CamelliaWrapEngine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/Cast5Engine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/Cast6Engine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/ChaCha7539Engine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/ChaChaEngine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/DesEdeEngine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/DesEdeWrapEngine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/DesEngine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/Dstu7624Engine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/Dstu7624WrapEngine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/ElGamalEngine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/GOST28147Engine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/HC128Engine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/HC256Engine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/ISAACEngine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/IdeaEngine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/IesEngine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/NaccacheSternEngine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/NoekeonEngine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/NullEngine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/RC2Engine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/RC2WrapEngine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/RC4Engine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/RC532Engine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/RC564Engine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/RC6Engine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/RFC3211WrapEngine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/RFC3394WrapEngine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/RSABlindedEngine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/RSABlindingEngine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/RSACoreEngine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/RijndaelEngine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/RsaEngine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/SEEDEngine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/SEEDWrapEngine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/Salsa20Engine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/SerpentEngine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/SerpentEngineBase.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/SkipjackEngine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/TEAEngine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/ThreefishEngine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/TnepresEngine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/TwofishEngine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/VMPCEngine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/VMPCKSA3Engine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/XSalsa20Engine.cs create mode 100644 bc-sharp-crypto/src/crypto/engines/XTEAEngine.cs create mode 100644 bc-sharp-crypto/src/crypto/generators/BCrypt.cs create mode 100644 bc-sharp-crypto/src/crypto/generators/BaseKdfBytesGenerator.cs create mode 100644 bc-sharp-crypto/src/crypto/generators/DHBasicKeyPairGenerator.cs create mode 100644 bc-sharp-crypto/src/crypto/generators/DHKeyGeneratorHelper.cs create mode 100644 bc-sharp-crypto/src/crypto/generators/DHKeyPairGenerator.cs create mode 100644 bc-sharp-crypto/src/crypto/generators/DHParametersGenerator.cs create mode 100644 bc-sharp-crypto/src/crypto/generators/DHParametersHelper.cs create mode 100644 bc-sharp-crypto/src/crypto/generators/DesEdeKeyGenerator.cs create mode 100644 bc-sharp-crypto/src/crypto/generators/DesKeyGenerator.cs create mode 100644 bc-sharp-crypto/src/crypto/generators/DsaKeyPairGenerator.cs create mode 100644 bc-sharp-crypto/src/crypto/generators/DsaParametersGenerator.cs create mode 100644 bc-sharp-crypto/src/crypto/generators/ECKeyPairGenerator.cs create mode 100644 bc-sharp-crypto/src/crypto/generators/ElGamalKeyPairGenerator.cs create mode 100644 bc-sharp-crypto/src/crypto/generators/ElGamalParametersGenerator.cs create mode 100644 bc-sharp-crypto/src/crypto/generators/GOST3410KeyPairGenerator.cs create mode 100644 bc-sharp-crypto/src/crypto/generators/GOST3410ParametersGenerator.cs create mode 100644 bc-sharp-crypto/src/crypto/generators/HKDFBytesGenerator.cs create mode 100644 bc-sharp-crypto/src/crypto/generators/Kdf1BytesGenerator.cs create mode 100644 bc-sharp-crypto/src/crypto/generators/Kdf2BytesGenerator.cs create mode 100644 bc-sharp-crypto/src/crypto/generators/Mgf1BytesGenerator.cs create mode 100644 bc-sharp-crypto/src/crypto/generators/NaccacheSternKeyPairGenerator.cs create mode 100644 bc-sharp-crypto/src/crypto/generators/OpenBsdBCrypt.cs create mode 100644 bc-sharp-crypto/src/crypto/generators/OpenSSLPBEParametersGenerator.cs create mode 100644 bc-sharp-crypto/src/crypto/generators/Pkcs12ParametersGenerator.cs create mode 100644 bc-sharp-crypto/src/crypto/generators/Pkcs5S1ParametersGenerator.cs create mode 100644 bc-sharp-crypto/src/crypto/generators/Pkcs5S2ParametersGenerator.cs create mode 100644 bc-sharp-crypto/src/crypto/generators/Poly1305KeyGenerator.cs create mode 100644 bc-sharp-crypto/src/crypto/generators/RSABlindingFactorGenerator.cs create mode 100644 bc-sharp-crypto/src/crypto/generators/RsaKeyPairGenerator.cs create mode 100644 bc-sharp-crypto/src/crypto/generators/SCrypt.cs create mode 100644 bc-sharp-crypto/src/crypto/io/CipherStream.cs create mode 100644 bc-sharp-crypto/src/crypto/io/DigestStream.cs create mode 100644 bc-sharp-crypto/src/crypto/io/MacStream.cs create mode 100644 bc-sharp-crypto/src/crypto/io/SignerStream.cs create mode 100644 bc-sharp-crypto/src/crypto/macs/CMac.cs create mode 100644 bc-sharp-crypto/src/crypto/macs/CbcBlockCipherMac.cs create mode 100644 bc-sharp-crypto/src/crypto/macs/CfbBlockCipherMac.cs create mode 100644 bc-sharp-crypto/src/crypto/macs/DSTU7564Mac.cs create mode 100644 bc-sharp-crypto/src/crypto/macs/DSTU7624Mac.cs create mode 100644 bc-sharp-crypto/src/crypto/macs/GMac.cs create mode 100644 bc-sharp-crypto/src/crypto/macs/GOST28147Mac.cs create mode 100644 bc-sharp-crypto/src/crypto/macs/HMac.cs create mode 100644 bc-sharp-crypto/src/crypto/macs/ISO9797Alg3Mac.cs create mode 100644 bc-sharp-crypto/src/crypto/macs/Poly1305.cs create mode 100644 bc-sharp-crypto/src/crypto/macs/SipHash.cs create mode 100644 bc-sharp-crypto/src/crypto/macs/SkeinMac.cs create mode 100644 bc-sharp-crypto/src/crypto/macs/VMPCMac.cs create mode 100644 bc-sharp-crypto/src/crypto/modes/CbcBlockCipher.cs create mode 100644 bc-sharp-crypto/src/crypto/modes/CcmBlockCipher.cs create mode 100644 bc-sharp-crypto/src/crypto/modes/CfbBlockCipher.cs create mode 100644 bc-sharp-crypto/src/crypto/modes/CtsBlockCipher.cs create mode 100644 bc-sharp-crypto/src/crypto/modes/EAXBlockCipher.cs create mode 100644 bc-sharp-crypto/src/crypto/modes/GCMBlockCipher.cs create mode 100644 bc-sharp-crypto/src/crypto/modes/GOFBBlockCipher.cs create mode 100644 bc-sharp-crypto/src/crypto/modes/IAeadBlockCipher.cs create mode 100644 bc-sharp-crypto/src/crypto/modes/KCcmBlockCipher.cs create mode 100644 bc-sharp-crypto/src/crypto/modes/KCtrBlockCipher.cs create mode 100644 bc-sharp-crypto/src/crypto/modes/OCBBlockCipher.cs create mode 100644 bc-sharp-crypto/src/crypto/modes/OfbBlockCipher.cs create mode 100644 bc-sharp-crypto/src/crypto/modes/OpenPgpCfbBlockCipher.cs create mode 100644 bc-sharp-crypto/src/crypto/modes/SicBlockCipher.cs create mode 100644 bc-sharp-crypto/src/crypto/modes/gcm/BasicGcmExponentiator.cs create mode 100644 bc-sharp-crypto/src/crypto/modes/gcm/BasicGcmMultiplier.cs create mode 100644 bc-sharp-crypto/src/crypto/modes/gcm/GcmUtilities.cs create mode 100644 bc-sharp-crypto/src/crypto/modes/gcm/IGcmExponentiator.cs create mode 100644 bc-sharp-crypto/src/crypto/modes/gcm/IGcmMultiplier.cs create mode 100644 bc-sharp-crypto/src/crypto/modes/gcm/Tables1kGcmExponentiator.cs create mode 100644 bc-sharp-crypto/src/crypto/modes/gcm/Tables64kGcmMultiplier.cs create mode 100644 bc-sharp-crypto/src/crypto/modes/gcm/Tables8kGcmMultiplier.cs create mode 100644 bc-sharp-crypto/src/crypto/operators/Asn1Signature.cs create mode 100644 bc-sharp-crypto/src/crypto/paddings/BlockCipherPadding.cs create mode 100644 bc-sharp-crypto/src/crypto/paddings/ISO10126d2Padding.cs create mode 100644 bc-sharp-crypto/src/crypto/paddings/ISO7816d4Padding.cs create mode 100644 bc-sharp-crypto/src/crypto/paddings/PaddedBufferedBlockCipher.cs create mode 100644 bc-sharp-crypto/src/crypto/paddings/Pkcs7Padding.cs create mode 100644 bc-sharp-crypto/src/crypto/paddings/TbcPadding.cs create mode 100644 bc-sharp-crypto/src/crypto/paddings/X923Padding.cs create mode 100644 bc-sharp-crypto/src/crypto/paddings/ZeroBytePadding.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/AEADParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/CcmParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/DHKeyGenerationParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/DHKeyParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/DHParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/DHPrivateKeyParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/DHPublicKeyParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/DHValidationParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/DSAParameterGenerationParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/DesEdeParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/DesParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/DsaKeyGenerationParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/DsaKeyParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/DsaParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/DsaPrivateKeyParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/DsaPublicKeyParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/DsaValidationParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/ECDomainParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/ECKeyGenerationParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/ECKeyParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/ECPrivateKeyParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/ECPublicKeyParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/ElGamalKeyGenerationParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/ElGamalKeyParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/ElGamalParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/ElGamalPrivateKeyParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/ElGamalPublicKeyParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/GOST3410KeyGenerationParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/GOST3410KeyParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/GOST3410Parameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/GOST3410PrivateKeyParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/GOST3410PublicKeyParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/GOST3410ValidationParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/HKDFParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/ISO18033KDFParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/IesParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/IesWithCipherParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/KdfParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/KeyParameter.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/MgfParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/MqvPrivateParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/MqvPublicParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/NaccacheSternKeyGenerationParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/NaccacheSternKeyParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/NaccacheSternPrivateKeyParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/ParametersWithIV.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/ParametersWithRandom.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/ParametersWithSBox.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/ParametersWithSalt.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/RC2Parameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/RC5Parameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/RSABlindingParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/RsaKeyGenerationParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/RsaKeyParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/RsaPrivateCrtKeyParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/SkeinParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/Srp6GroupParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/parameters/TweakableBlockCipherParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/prng/BasicEntropySourceProvider.cs create mode 100644 bc-sharp-crypto/src/crypto/prng/CryptoApiEntropySourceProvider.cs create mode 100644 bc-sharp-crypto/src/crypto/prng/CryptoApiRandomGenerator.cs create mode 100644 bc-sharp-crypto/src/crypto/prng/DigestRandomGenerator.cs create mode 100644 bc-sharp-crypto/src/crypto/prng/EntropyUtilities.cs create mode 100644 bc-sharp-crypto/src/crypto/prng/IDrbgProvider.cs create mode 100644 bc-sharp-crypto/src/crypto/prng/IRandomGenerator.cs create mode 100644 bc-sharp-crypto/src/crypto/prng/ReversedWindowGenerator.cs create mode 100644 bc-sharp-crypto/src/crypto/prng/SP800SecureRandom.cs create mode 100644 bc-sharp-crypto/src/crypto/prng/SP800SecureRandomBuilder.cs create mode 100644 bc-sharp-crypto/src/crypto/prng/ThreadedSeedGenerator.cs create mode 100644 bc-sharp-crypto/src/crypto/prng/VMPCRandomGenerator.cs create mode 100644 bc-sharp-crypto/src/crypto/prng/X931Rng.cs create mode 100644 bc-sharp-crypto/src/crypto/prng/X931SecureRandom.cs create mode 100644 bc-sharp-crypto/src/crypto/prng/X931SecureRandomBuilder.cs create mode 100644 bc-sharp-crypto/src/crypto/prng/drbg/CtrSP800Drbg.cs create mode 100644 bc-sharp-crypto/src/crypto/prng/drbg/DrbgUtilities.cs create mode 100644 bc-sharp-crypto/src/crypto/prng/drbg/HMacSP800Drbg.cs create mode 100644 bc-sharp-crypto/src/crypto/prng/drbg/HashSP800Drbg.cs create mode 100644 bc-sharp-crypto/src/crypto/prng/drbg/ISP80090Drbg.cs create mode 100644 bc-sharp-crypto/src/crypto/signers/DsaDigestSigner.cs create mode 100644 bc-sharp-crypto/src/crypto/signers/DsaSigner.cs create mode 100644 bc-sharp-crypto/src/crypto/signers/ECDsaSigner.cs create mode 100644 bc-sharp-crypto/src/crypto/signers/ECGOST3410Signer.cs create mode 100644 bc-sharp-crypto/src/crypto/signers/ECNRSigner.cs create mode 100644 bc-sharp-crypto/src/crypto/signers/GOST3410DigestSigner.cs create mode 100644 bc-sharp-crypto/src/crypto/signers/GOST3410Signer.cs create mode 100644 bc-sharp-crypto/src/crypto/signers/GenericSigner.cs create mode 100644 bc-sharp-crypto/src/crypto/signers/HMacDsaKCalculator.cs create mode 100644 bc-sharp-crypto/src/crypto/signers/IDsaKCalculator.cs create mode 100644 bc-sharp-crypto/src/crypto/signers/Iso9796d2PssSigner.cs create mode 100644 bc-sharp-crypto/src/crypto/signers/Iso9796d2Signer.cs create mode 100644 bc-sharp-crypto/src/crypto/signers/IsoTrailers.cs create mode 100644 bc-sharp-crypto/src/crypto/signers/PssSigner.cs create mode 100644 bc-sharp-crypto/src/crypto/signers/RandomDsaKCalculator.cs create mode 100644 bc-sharp-crypto/src/crypto/signers/RsaDigestSigner.cs create mode 100644 bc-sharp-crypto/src/crypto/signers/X931Signer.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/AbstractTlsAgreementCredentials.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/AbstractTlsCipherFactory.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/AbstractTlsClient.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/AbstractTlsContext.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/AbstractTlsCredentials.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/AbstractTlsEncryptionCredentials.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/AbstractTlsKeyExchange.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/AbstractTlsPeer.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/AbstractTlsServer.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/AbstractTlsSigner.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/AbstractTlsSignerCredentials.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/AlertDescription.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/AlertLevel.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/BasicTlsPskIdentity.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/BulkCipherAlgorithm.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/ByteQueue.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/ByteQueueStream.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/CertChainType.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/Certificate.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/CertificateRequest.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/CertificateStatus.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/CertificateStatusRequest.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/CertificateStatusType.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/CertificateType.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/CertificateUrl.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/Chacha20Poly1305.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/ChangeCipherSpec.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/CipherSuite.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/CipherType.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/ClientAuthenticationType.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/ClientCertificateType.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/CombinedHash.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/CompressionMethod.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/ConnectionEnd.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/ContentType.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/DatagramTransport.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/DefaultTlsAgreementCredentials.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/DefaultTlsCipherFactory.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/DefaultTlsClient.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/DefaultTlsEncryptionCredentials.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/DefaultTlsServer.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/DefaultTlsSignerCredentials.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/DefaultTlsSrpGroupVerifier.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/DeferredHash.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/DigestInputBuffer.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/DigitallySigned.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/DtlsClientProtocol.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/DtlsEpoch.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/DtlsHandshakeRetransmit.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/DtlsProtocol.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/DtlsReassembler.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/DtlsRecordLayer.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/DtlsReliableHandshake.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/DtlsReplayWindow.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/DtlsServerProtocol.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/DtlsTransport.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/ECBasisType.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/ECCurveType.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/ECPointFormat.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/EncryptionAlgorithm.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/ExporterLabel.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/ExtensionType.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/FiniteFieldDheGroup.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/HandshakeType.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/HashAlgorithm.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/HeartbeatExtension.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/HeartbeatMessage.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/HeartbeatMessageType.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/HeartbeatMode.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/KeyExchangeAlgorithm.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/MacAlgorithm.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/MaxFragmentLength.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/NameType.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/NamedCurve.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/NewSessionTicket.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/OcspStatusRequest.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/PrfAlgorithm.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/ProtocolVersion.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/PskTlsClient.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/PskTlsServer.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/RecordStream.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/SecurityParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/ServerDHParams.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/ServerName.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/ServerNameList.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/ServerOnlyTlsAuthentication.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/ServerSrpParams.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/SessionParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/SignatureAlgorithm.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/SignatureAndHashAlgorithm.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/SignerInputBuffer.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/SimulatedTlsSrpIdentityManager.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/SrpTlsClient.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/SrpTlsServer.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/SrtpProtectionProfile.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/Ssl3Mac.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/SupplementalDataEntry.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/SupplementalDataType.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsAeadCipher.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsAgreementCredentials.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsAuthentication.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsBlockCipher.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsCipher.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsCipherFactory.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsClient.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsClientContext.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsClientContextImpl.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsClientProtocol.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsCompression.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsContext.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsCredentials.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsDHKeyExchange.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsDHUtilities.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsDeflateCompression.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsDheKeyExchange.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsDsaSigner.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsDssSigner.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsECDHKeyExchange.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsECDheKeyExchange.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsECDsaSigner.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsEccUtilities.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsEncryptionCredentials.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsException.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsExtensionsUtilities.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsFatalAlert.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsFatalAlertReceived.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsHandshakeHash.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsKeyExchange.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsMac.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsNoCloseNotifyException.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsNullCipher.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsNullCompression.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsPeer.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsProtocol.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsProtocolHandler.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsPskIdentity.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsPskIdentityManager.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsPskKeyExchange.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsRsaKeyExchange.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsRsaSigner.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsRsaUtilities.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsServer.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsServerContext.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsServerContextImpl.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsServerProtocol.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsSession.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsSessionImpl.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsSigner.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsSignerCredentials.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsSrpGroupVerifier.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsSrpIdentityManager.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsSrpKeyExchange.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsSrpLoginParameters.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsSrpUtilities.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsSrtpUtilities.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsStream.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsStreamCipher.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/TlsUtilities.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/UrlAndHash.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/UseSrtpData.cs create mode 100644 bc-sharp-crypto/src/crypto/tls/UserMappingType.cs create mode 100644 bc-sharp-crypto/src/crypto/util/Pack.cs create mode 100644 bc-sharp-crypto/src/math/BigInteger.cs create mode 100644 bc-sharp-crypto/src/math/Primes.cs create mode 100644 bc-sharp-crypto/src/math/ec/ECAlgorithms.cs create mode 100644 bc-sharp-crypto/src/math/ec/ECCurve.cs create mode 100644 bc-sharp-crypto/src/math/ec/ECFieldElement.cs create mode 100644 bc-sharp-crypto/src/math/ec/ECPoint.cs create mode 100644 bc-sharp-crypto/src/math/ec/ECPointMap.cs create mode 100644 bc-sharp-crypto/src/math/ec/LongArray.cs create mode 100644 bc-sharp-crypto/src/math/ec/ScaleXPointMap.cs create mode 100644 bc-sharp-crypto/src/math/ec/ScaleYPointMap.cs create mode 100644 bc-sharp-crypto/src/math/ec/abc/SimpleBigDecimal.cs create mode 100644 bc-sharp-crypto/src/math/ec/abc/Tnaf.cs create mode 100644 bc-sharp-crypto/src/math/ec/abc/ZTauElement.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/djb/Curve25519.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/djb/Curve25519Field.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/djb/Curve25519FieldElement.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/djb/Curve25519Point.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/gm/SM2P256V1Curve.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/gm/SM2P256V1Field.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/gm/SM2P256V1FieldElement.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/gm/SM2P256V1Point.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecP128R1Curve.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecP128R1Field.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecP128R1FieldElement.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecP128R1Point.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecP160K1Curve.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecP160K1Point.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecP160R1Curve.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecP160R1Field.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecP160R1FieldElement.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecP160R1Point.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecP160R2Curve.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecP160R2Field.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecP160R2FieldElement.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecP160R2Point.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecP192K1Curve.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecP192K1Field.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecP192K1FieldElement.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecP192K1Point.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecP192R1Curve.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecP192R1Field.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecP192R1FieldElement.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecP192R1Point.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecP224K1Curve.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecP224K1Field.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecP224K1FieldElement.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecP224K1Point.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecP224R1Curve.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecP224R1Field.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecP224R1FieldElement.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecP224R1Point.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecP256K1Curve.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecP256K1Field.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecP256K1FieldElement.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecP256K1Point.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecP256R1Curve.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecP256R1Field.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecP256R1FieldElement.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecP256R1Point.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecP384R1Curve.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecP384R1Field.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecP384R1FieldElement.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecP384R1Point.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecP521R1Curve.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecP521R1Field.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecP521R1FieldElement.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecP521R1Point.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT113Field.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT113FieldElement.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT113R1Curve.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT113R1Point.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT113R2Curve.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT113R2Point.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT131Field.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT131FieldElement.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT131R1Curve.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT131R1Point.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT131R2Curve.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT131R2Point.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT163Field.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT163FieldElement.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT163K1Curve.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT163K1Point.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT163R1Curve.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT163R1Point.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT163R2Curve.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT163R2Point.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT193Field.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT193FieldElement.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT193R1Curve.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT193R1Point.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT193R2Curve.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT193R2Point.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT233Field.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT233FieldElement.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT233K1Curve.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT233K1Point.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT233R1Curve.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT233R1Point.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT239Field.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT239FieldElement.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT239K1Curve.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT239K1Point.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT283Field.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT283FieldElement.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT283K1Curve.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT283K1Point.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT283R1Curve.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT283R1Point.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT409Field.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT409FieldElement.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT409K1Curve.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT409K1Point.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT409R1Curve.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT409R1Point.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT571Field.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT571FieldElement.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT571K1Curve.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT571K1Point.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT571R1Curve.cs create mode 100644 bc-sharp-crypto/src/math/ec/custom/sec/SecT571R1Point.cs create mode 100644 bc-sharp-crypto/src/math/ec/endo/ECEndomorphism.cs create mode 100644 bc-sharp-crypto/src/math/ec/endo/GlvEndomorphism.cs create mode 100644 bc-sharp-crypto/src/math/ec/endo/GlvTypeBEndomorphism.cs create mode 100644 bc-sharp-crypto/src/math/ec/endo/GlvTypeBParameters.cs create mode 100644 bc-sharp-crypto/src/math/ec/multiplier/AbstractECMultiplier.cs create mode 100644 bc-sharp-crypto/src/math/ec/multiplier/DoubleAddMultiplier.cs create mode 100644 bc-sharp-crypto/src/math/ec/multiplier/ECMultiplier.cs create mode 100644 bc-sharp-crypto/src/math/ec/multiplier/FixedPointCombMultiplier.cs create mode 100644 bc-sharp-crypto/src/math/ec/multiplier/FixedPointPreCompInfo.cs create mode 100644 bc-sharp-crypto/src/math/ec/multiplier/FixedPointUtilities.cs create mode 100644 bc-sharp-crypto/src/math/ec/multiplier/GlvMultiplier.cs create mode 100644 bc-sharp-crypto/src/math/ec/multiplier/MixedNafR2LMultiplier.cs create mode 100644 bc-sharp-crypto/src/math/ec/multiplier/MontgomeryLadderMultiplier.cs create mode 100644 bc-sharp-crypto/src/math/ec/multiplier/NafL2RMultiplier.cs create mode 100644 bc-sharp-crypto/src/math/ec/multiplier/NafR2LMultiplier.cs create mode 100644 bc-sharp-crypto/src/math/ec/multiplier/PreCompInfo.cs create mode 100644 bc-sharp-crypto/src/math/ec/multiplier/ReferenceMultiplier.cs create mode 100644 bc-sharp-crypto/src/math/ec/multiplier/WNafL2RMultiplier.cs create mode 100644 bc-sharp-crypto/src/math/ec/multiplier/WNafPreCompInfo.cs create mode 100644 bc-sharp-crypto/src/math/ec/multiplier/WNafUtilities.cs create mode 100644 bc-sharp-crypto/src/math/ec/multiplier/WTauNafMultiplier.cs create mode 100644 bc-sharp-crypto/src/math/ec/multiplier/WTauNafPreCompInfo.cs create mode 100644 bc-sharp-crypto/src/math/ec/multiplier/ZSignedDigitL2RMultiplier.cs create mode 100644 bc-sharp-crypto/src/math/ec/multiplier/ZSignedDigitR2LMultiplier.cs create mode 100644 bc-sharp-crypto/src/math/field/FiniteFields.cs create mode 100644 bc-sharp-crypto/src/math/field/GF2Polynomial.cs create mode 100644 bc-sharp-crypto/src/math/field/GenericPolynomialExtensionField.cs create mode 100644 bc-sharp-crypto/src/math/field/IExtensionField.cs create mode 100644 bc-sharp-crypto/src/math/field/IFiniteField.cs create mode 100644 bc-sharp-crypto/src/math/field/IPolynomial.cs create mode 100644 bc-sharp-crypto/src/math/field/IPolynomialExtensionField.cs create mode 100644 bc-sharp-crypto/src/math/field/PrimeField.cs create mode 100644 bc-sharp-crypto/src/math/raw/Interleave.cs create mode 100644 bc-sharp-crypto/src/math/raw/Mod.cs create mode 100644 bc-sharp-crypto/src/math/raw/Nat.cs create mode 100644 bc-sharp-crypto/src/math/raw/Nat128.cs create mode 100644 bc-sharp-crypto/src/math/raw/Nat160.cs create mode 100644 bc-sharp-crypto/src/math/raw/Nat192.cs create mode 100644 bc-sharp-crypto/src/math/raw/Nat224.cs create mode 100644 bc-sharp-crypto/src/math/raw/Nat256.cs create mode 100644 bc-sharp-crypto/src/math/raw/Nat320.cs create mode 100644 bc-sharp-crypto/src/math/raw/Nat384.cs create mode 100644 bc-sharp-crypto/src/math/raw/Nat448.cs create mode 100644 bc-sharp-crypto/src/math/raw/Nat512.cs create mode 100644 bc-sharp-crypto/src/math/raw/Nat576.cs create mode 100644 bc-sharp-crypto/src/ocsp/BasicOCSPResp.cs create mode 100644 bc-sharp-crypto/src/ocsp/BasicOCSPRespGenerator.cs create mode 100644 bc-sharp-crypto/src/ocsp/CertificateID.cs create mode 100644 bc-sharp-crypto/src/ocsp/CertificateStatus.cs create mode 100644 bc-sharp-crypto/src/ocsp/OCSPException.cs create mode 100644 bc-sharp-crypto/src/ocsp/OCSPReq.cs create mode 100644 bc-sharp-crypto/src/ocsp/OCSPReqGenerator.cs create mode 100644 bc-sharp-crypto/src/ocsp/OCSPResp.cs create mode 100644 bc-sharp-crypto/src/ocsp/OCSPRespGenerator.cs create mode 100644 bc-sharp-crypto/src/ocsp/OCSPRespStatus.cs create mode 100644 bc-sharp-crypto/src/ocsp/OCSPUtil.cs create mode 100644 bc-sharp-crypto/src/ocsp/Req.cs create mode 100644 bc-sharp-crypto/src/ocsp/RespData.cs create mode 100644 bc-sharp-crypto/src/ocsp/RespID.cs create mode 100644 bc-sharp-crypto/src/ocsp/RevokedStatus.cs create mode 100644 bc-sharp-crypto/src/ocsp/SingleResp.cs create mode 100644 bc-sharp-crypto/src/ocsp/UnknownStatus.cs create mode 100644 bc-sharp-crypto/src/openpgp/IStreamGenerator.cs create mode 100644 bc-sharp-crypto/src/openpgp/PGPKeyRing.cs create mode 100644 bc-sharp-crypto/src/openpgp/PGPObject.cs create mode 100644 bc-sharp-crypto/src/openpgp/PGPUserAttributeSubpacketVectorGenerator.cs create mode 100644 bc-sharp-crypto/src/openpgp/PgpCompressedData.cs create mode 100644 bc-sharp-crypto/src/openpgp/PgpCompressedDataGenerator.cs create mode 100644 bc-sharp-crypto/src/openpgp/PgpDataValidationException.cs create mode 100644 bc-sharp-crypto/src/openpgp/PgpEncryptedData.cs create mode 100644 bc-sharp-crypto/src/openpgp/PgpEncryptedDataGenerator.cs create mode 100644 bc-sharp-crypto/src/openpgp/PgpEncryptedDataList.cs create mode 100644 bc-sharp-crypto/src/openpgp/PgpException.cs create mode 100644 bc-sharp-crypto/src/openpgp/PgpExperimental.cs create mode 100644 bc-sharp-crypto/src/openpgp/PgpKeyFlags.cs create mode 100644 bc-sharp-crypto/src/openpgp/PgpKeyPair.cs create mode 100644 bc-sharp-crypto/src/openpgp/PgpKeyRingGenerator.cs create mode 100644 bc-sharp-crypto/src/openpgp/PgpKeyValidationException.cs create mode 100644 bc-sharp-crypto/src/openpgp/PgpLiteralData.cs create mode 100644 bc-sharp-crypto/src/openpgp/PgpLiteralDataGenerator.cs create mode 100644 bc-sharp-crypto/src/openpgp/PgpMarker.cs create mode 100644 bc-sharp-crypto/src/openpgp/PgpObjectFactory.cs create mode 100644 bc-sharp-crypto/src/openpgp/PgpOnePassSignature.cs create mode 100644 bc-sharp-crypto/src/openpgp/PgpOnePassSignatureList.cs create mode 100644 bc-sharp-crypto/src/openpgp/PgpPad.cs create mode 100644 bc-sharp-crypto/src/openpgp/PgpPbeEncryptedData.cs create mode 100644 bc-sharp-crypto/src/openpgp/PgpPrivateKey.cs create mode 100644 bc-sharp-crypto/src/openpgp/PgpPublicKey.cs create mode 100644 bc-sharp-crypto/src/openpgp/PgpPublicKeyEncryptedData.cs create mode 100644 bc-sharp-crypto/src/openpgp/PgpPublicKeyRing.cs create mode 100644 bc-sharp-crypto/src/openpgp/PgpPublicKeyRingBundle.cs create mode 100644 bc-sharp-crypto/src/openpgp/PgpSecretKey.cs create mode 100644 bc-sharp-crypto/src/openpgp/PgpSecretKeyRing.cs create mode 100644 bc-sharp-crypto/src/openpgp/PgpSecretKeyRingBundle.cs create mode 100644 bc-sharp-crypto/src/openpgp/PgpSignature.cs create mode 100644 bc-sharp-crypto/src/openpgp/PgpSignatureGenerator.cs create mode 100644 bc-sharp-crypto/src/openpgp/PgpSignatureList.cs create mode 100644 bc-sharp-crypto/src/openpgp/PgpSignatureSubpacketGenerator.cs create mode 100644 bc-sharp-crypto/src/openpgp/PgpSignatureSubpacketVector.cs create mode 100644 bc-sharp-crypto/src/openpgp/PgpUserAttributeSubpacketVector.cs create mode 100644 bc-sharp-crypto/src/openpgp/PgpUtilities.cs create mode 100644 bc-sharp-crypto/src/openpgp/PgpV3SignatureGenerator.cs create mode 100644 bc-sharp-crypto/src/openpgp/Rfc6637Utilities.cs create mode 100644 bc-sharp-crypto/src/openpgp/SXprUtilities.cs create mode 100644 bc-sharp-crypto/src/openpgp/WrappedGeneratorStream.cs create mode 100644 bc-sharp-crypto/src/openssl/EncryptionException.cs create mode 100644 bc-sharp-crypto/src/openssl/IPasswordFinder.cs create mode 100644 bc-sharp-crypto/src/openssl/MiscPemGenerator.cs create mode 100644 bc-sharp-crypto/src/openssl/PEMException.cs create mode 100644 bc-sharp-crypto/src/openssl/PEMReader.cs create mode 100644 bc-sharp-crypto/src/openssl/PEMUtilities.cs create mode 100644 bc-sharp-crypto/src/openssl/PEMWriter.cs create mode 100644 bc-sharp-crypto/src/openssl/PasswordException.cs create mode 100644 bc-sharp-crypto/src/openssl/Pkcs8Generator.cs create mode 100644 bc-sharp-crypto/src/pkcs/AsymmetricKeyEntry.cs create mode 100644 bc-sharp-crypto/src/pkcs/EncryptedPrivateKeyInfoFactory.cs create mode 100644 bc-sharp-crypto/src/pkcs/PKCS12StoreBuilder.cs create mode 100644 bc-sharp-crypto/src/pkcs/Pkcs10CertificationRequest.cs create mode 100644 bc-sharp-crypto/src/pkcs/Pkcs10CertificationRequestDelaySigned.cs create mode 100644 bc-sharp-crypto/src/pkcs/Pkcs12Entry.cs create mode 100644 bc-sharp-crypto/src/pkcs/Pkcs12Store.cs create mode 100644 bc-sharp-crypto/src/pkcs/Pkcs12Utilities.cs create mode 100644 bc-sharp-crypto/src/pkcs/PrivateKeyInfoFactory.cs create mode 100644 bc-sharp-crypto/src/pkcs/X509CertificateEntry.cs create mode 100644 bc-sharp-crypto/src/pkix/CertStatus.cs create mode 100644 bc-sharp-crypto/src/pkix/PkixAttrCertChecker.cs create mode 100644 bc-sharp-crypto/src/pkix/PkixAttrCertPathBuilder.cs create mode 100644 bc-sharp-crypto/src/pkix/PkixAttrCertPathValidator.cs create mode 100644 bc-sharp-crypto/src/pkix/PkixBuilderParameters.cs create mode 100644 bc-sharp-crypto/src/pkix/PkixCertPath.cs create mode 100644 bc-sharp-crypto/src/pkix/PkixCertPathBuilder.cs create mode 100644 bc-sharp-crypto/src/pkix/PkixCertPathBuilderException.cs create mode 100644 bc-sharp-crypto/src/pkix/PkixCertPathBuilderResult.cs create mode 100644 bc-sharp-crypto/src/pkix/PkixCertPathChecker.cs create mode 100644 bc-sharp-crypto/src/pkix/PkixCertPathValidator.cs create mode 100644 bc-sharp-crypto/src/pkix/PkixCertPathValidatorException.cs create mode 100644 bc-sharp-crypto/src/pkix/PkixCertPathValidatorResult.cs create mode 100644 bc-sharp-crypto/src/pkix/PkixCertPathValidatorUtilities.cs create mode 100644 bc-sharp-crypto/src/pkix/PkixCrlUtilities.cs create mode 100644 bc-sharp-crypto/src/pkix/PkixNameConstraintValidator.cs create mode 100644 bc-sharp-crypto/src/pkix/PkixNameConstraintValidatorException.cs create mode 100644 bc-sharp-crypto/src/pkix/PkixParameters.cs create mode 100644 bc-sharp-crypto/src/pkix/PkixPolicyNode.cs create mode 100644 bc-sharp-crypto/src/pkix/ReasonsMask.cs create mode 100644 bc-sharp-crypto/src/pkix/Rfc3280CertPathUtilities.cs create mode 100644 bc-sharp-crypto/src/pkix/Rfc3281CertPathUtilities.cs create mode 100644 bc-sharp-crypto/src/pkix/TrustAnchor.cs create mode 100644 bc-sharp-crypto/src/security/AgreementUtilities.cs create mode 100644 bc-sharp-crypto/src/security/CipherUtilities.cs create mode 100644 bc-sharp-crypto/src/security/DigestUtilities.cs create mode 100644 bc-sharp-crypto/src/security/DotNetUtilities.cs create mode 100644 bc-sharp-crypto/src/security/GeneralSecurityException.cs create mode 100644 bc-sharp-crypto/src/security/GeneratorUtilities.cs create mode 100644 bc-sharp-crypto/src/security/InvalidKeyException.cs create mode 100644 bc-sharp-crypto/src/security/InvalidParameterException.cs create mode 100644 bc-sharp-crypto/src/security/KeyException.cs create mode 100644 bc-sharp-crypto/src/security/MacUtilities.cs create mode 100644 bc-sharp-crypto/src/security/NoSuchAlgorithmException.cs create mode 100644 bc-sharp-crypto/src/security/ParameterUtilities.cs create mode 100644 bc-sharp-crypto/src/security/PbeUtilities.cs create mode 100644 bc-sharp-crypto/src/security/PrivateKeyFactory.cs create mode 100644 bc-sharp-crypto/src/security/PublicKeyFactory.cs create mode 100644 bc-sharp-crypto/src/security/SecureRandom.cs create mode 100644 bc-sharp-crypto/src/security/SecurityUtilityException.cs create mode 100644 bc-sharp-crypto/src/security/SignatureException.cs create mode 100644 bc-sharp-crypto/src/security/SignerUtilities.cs create mode 100644 bc-sharp-crypto/src/security/WrapperUtilities.cs create mode 100644 bc-sharp-crypto/src/security/cert/CertificateEncodingException.cs create mode 100644 bc-sharp-crypto/src/security/cert/CertificateException.cs create mode 100644 bc-sharp-crypto/src/security/cert/CertificateExpiredException.cs create mode 100644 bc-sharp-crypto/src/security/cert/CertificateNotYetValidException.cs create mode 100644 bc-sharp-crypto/src/security/cert/CertificateParsingException.cs create mode 100644 bc-sharp-crypto/src/security/cert/CrlException.cs create mode 100644 bc-sharp-crypto/src/tsp/GenTimeAccuracy.cs create mode 100644 bc-sharp-crypto/src/tsp/TSPAlgorithms.cs create mode 100644 bc-sharp-crypto/src/tsp/TSPException.cs create mode 100644 bc-sharp-crypto/src/tsp/TSPUtil.cs create mode 100644 bc-sharp-crypto/src/tsp/TSPValidationException.cs create mode 100644 bc-sharp-crypto/src/tsp/TimeStampRequest.cs create mode 100644 bc-sharp-crypto/src/tsp/TimeStampRequestGenerator.cs create mode 100644 bc-sharp-crypto/src/tsp/TimeStampResponse.cs create mode 100644 bc-sharp-crypto/src/tsp/TimeStampResponseGenerator.cs create mode 100644 bc-sharp-crypto/src/tsp/TimeStampToken.cs create mode 100644 bc-sharp-crypto/src/tsp/TimeStampTokenGenerator.cs create mode 100644 bc-sharp-crypto/src/tsp/TimeStampTokenInfo.cs create mode 100644 bc-sharp-crypto/src/util/Arrays.cs create mode 100644 bc-sharp-crypto/src/util/BigIntegers.cs create mode 100644 bc-sharp-crypto/src/util/Enums.cs create mode 100644 bc-sharp-crypto/src/util/IMemoable.cs create mode 100644 bc-sharp-crypto/src/util/Integers.cs create mode 100644 bc-sharp-crypto/src/util/MemoableResetException.cs create mode 100644 bc-sharp-crypto/src/util/Platform.cs create mode 100644 bc-sharp-crypto/src/util/Strings.cs create mode 100644 bc-sharp-crypto/src/util/Times.cs create mode 100644 bc-sharp-crypto/src/util/TypeExtensions.cs create mode 100644 bc-sharp-crypto/src/util/collections/CollectionUtilities.cs create mode 100644 bc-sharp-crypto/src/util/collections/EmptyEnumerable.cs create mode 100644 bc-sharp-crypto/src/util/collections/EnumerableProxy.cs create mode 100644 bc-sharp-crypto/src/util/collections/HashSet.cs create mode 100644 bc-sharp-crypto/src/util/collections/ISet.cs create mode 100644 bc-sharp-crypto/src/util/collections/LinkedDictionary.cs create mode 100644 bc-sharp-crypto/src/util/collections/UnmodifiableDictionary.cs create mode 100644 bc-sharp-crypto/src/util/collections/UnmodifiableDictionaryProxy.cs create mode 100644 bc-sharp-crypto/src/util/collections/UnmodifiableList.cs create mode 100644 bc-sharp-crypto/src/util/collections/UnmodifiableListProxy.cs create mode 100644 bc-sharp-crypto/src/util/collections/UnmodifiableSet.cs create mode 100644 bc-sharp-crypto/src/util/collections/UnmodifiableSetProxy.cs create mode 100644 bc-sharp-crypto/src/util/date/DateTimeObject.cs create mode 100644 bc-sharp-crypto/src/util/date/DateTimeUtilities.cs create mode 100644 bc-sharp-crypto/src/util/encoders/Base64.cs create mode 100644 bc-sharp-crypto/src/util/encoders/Base64Encoder.cs create mode 100644 bc-sharp-crypto/src/util/encoders/BufferedDecoder.cs create mode 100644 bc-sharp-crypto/src/util/encoders/BufferedEncoder.cs create mode 100644 bc-sharp-crypto/src/util/encoders/Hex.cs create mode 100644 bc-sharp-crypto/src/util/encoders/HexEncoder.cs create mode 100644 bc-sharp-crypto/src/util/encoders/HexTranslator.cs create mode 100644 bc-sharp-crypto/src/util/encoders/IEncoder.cs create mode 100644 bc-sharp-crypto/src/util/encoders/Translator.cs create mode 100644 bc-sharp-crypto/src/util/encoders/UrlBase64.cs create mode 100644 bc-sharp-crypto/src/util/encoders/UrlBase64Encoder.cs create mode 100644 bc-sharp-crypto/src/util/io/BaseInputStream.cs create mode 100644 bc-sharp-crypto/src/util/io/BaseOutputStream.cs create mode 100644 bc-sharp-crypto/src/util/io/FilterStream.cs create mode 100644 bc-sharp-crypto/src/util/io/NullOutputStream.cs create mode 100644 bc-sharp-crypto/src/util/io/PushbackStream.cs create mode 100644 bc-sharp-crypto/src/util/io/StreamOverflowException.cs create mode 100644 bc-sharp-crypto/src/util/io/Streams.cs create mode 100644 bc-sharp-crypto/src/util/io/TeeInputStream.cs create mode 100644 bc-sharp-crypto/src/util/io/TeeOutputStream.cs create mode 100644 bc-sharp-crypto/src/util/io/pem/PemGenerationException.cs create mode 100644 bc-sharp-crypto/src/util/io/pem/PemHeader.cs create mode 100644 bc-sharp-crypto/src/util/io/pem/PemObject.cs create mode 100644 bc-sharp-crypto/src/util/io/pem/PemObjectGenerator.cs create mode 100644 bc-sharp-crypto/src/util/io/pem/PemObjectParser.cs create mode 100644 bc-sharp-crypto/src/util/io/pem/PemReader.cs create mode 100644 bc-sharp-crypto/src/util/io/pem/PemWriter.cs create mode 100644 bc-sharp-crypto/src/util/net/IPAddress.cs create mode 100644 bc-sharp-crypto/src/util/zlib/Adler32.cs create mode 100644 bc-sharp-crypto/src/util/zlib/Deflate.cs create mode 100644 bc-sharp-crypto/src/util/zlib/InfBlocks.cs create mode 100644 bc-sharp-crypto/src/util/zlib/InfCodes.cs create mode 100644 bc-sharp-crypto/src/util/zlib/InfTree.cs create mode 100644 bc-sharp-crypto/src/util/zlib/Inflate.cs create mode 100644 bc-sharp-crypto/src/util/zlib/JZlib.cs create mode 100644 bc-sharp-crypto/src/util/zlib/StaticTree.cs create mode 100644 bc-sharp-crypto/src/util/zlib/Tree.cs create mode 100644 bc-sharp-crypto/src/util/zlib/ZDeflaterOutputStream.cs create mode 100644 bc-sharp-crypto/src/util/zlib/ZInflaterInputStream.cs create mode 100644 bc-sharp-crypto/src/util/zlib/ZInputStream.cs create mode 100644 bc-sharp-crypto/src/util/zlib/ZOutputStream.cs create mode 100644 bc-sharp-crypto/src/util/zlib/ZStream.cs create mode 100644 bc-sharp-crypto/src/x509/AttributeCertificateHolder.cs create mode 100644 bc-sharp-crypto/src/x509/AttributeCertificateIssuer.cs create mode 100644 bc-sharp-crypto/src/x509/IX509AttributeCertificate.cs create mode 100644 bc-sharp-crypto/src/x509/IX509Extension.cs create mode 100644 bc-sharp-crypto/src/x509/PEMParser.cs create mode 100644 bc-sharp-crypto/src/x509/PrincipalUtil.cs create mode 100644 bc-sharp-crypto/src/x509/SubjectPublicKeyInfoFactory.cs create mode 100644 bc-sharp-crypto/src/x509/X509AttrCertParser.cs create mode 100644 bc-sharp-crypto/src/x509/X509Attribute.cs create mode 100644 bc-sharp-crypto/src/x509/X509CertPairParser.cs create mode 100644 bc-sharp-crypto/src/x509/X509Certificate.cs create mode 100644 bc-sharp-crypto/src/x509/X509CertificatePair.cs create mode 100644 bc-sharp-crypto/src/x509/X509CertificateParser.cs create mode 100644 bc-sharp-crypto/src/x509/X509Crl.cs create mode 100644 bc-sharp-crypto/src/x509/X509CrlEntry.cs create mode 100644 bc-sharp-crypto/src/x509/X509CrlParser.cs create mode 100644 bc-sharp-crypto/src/x509/X509ExtensionBase.cs create mode 100644 bc-sharp-crypto/src/x509/X509KeyUsage.cs create mode 100644 bc-sharp-crypto/src/x509/X509SignatureUtil.cs create mode 100644 bc-sharp-crypto/src/x509/X509Utilities.cs create mode 100644 bc-sharp-crypto/src/x509/X509V1CertificateGenerator.cs create mode 100644 bc-sharp-crypto/src/x509/X509V2AttributeCertificate.cs create mode 100644 bc-sharp-crypto/src/x509/X509V2AttributeCertificateGenerator.cs create mode 100644 bc-sharp-crypto/src/x509/X509V2CRLGenerator.cs create mode 100644 bc-sharp-crypto/src/x509/X509V3CertificateGenerator.cs create mode 100644 bc-sharp-crypto/src/x509/extension/AuthorityKeyIdentifierStructure.cs create mode 100644 bc-sharp-crypto/src/x509/extension/SubjectKeyIdentifierStructure.cs create mode 100644 bc-sharp-crypto/src/x509/extension/X509ExtensionUtil.cs create mode 100644 bc-sharp-crypto/src/x509/store/IX509Selector.cs create mode 100644 bc-sharp-crypto/src/x509/store/IX509Store.cs create mode 100644 bc-sharp-crypto/src/x509/store/IX509StoreParameters.cs create mode 100644 bc-sharp-crypto/src/x509/store/NoSuchStoreException.cs create mode 100644 bc-sharp-crypto/src/x509/store/X509AttrCertStoreSelector.cs create mode 100644 bc-sharp-crypto/src/x509/store/X509CertPairStoreSelector.cs create mode 100644 bc-sharp-crypto/src/x509/store/X509CertStoreSelector.cs create mode 100644 bc-sharp-crypto/src/x509/store/X509CollectionStore.cs create mode 100644 bc-sharp-crypto/src/x509/store/X509CollectionStoreParameters.cs create mode 100644 bc-sharp-crypto/src/x509/store/X509CrlStoreSelector.cs create mode 100644 bc-sharp-crypto/src/x509/store/X509StoreException.cs create mode 100644 bc-sharp-crypto/src/x509/store/X509StoreFactory.cs create mode 100644 bin/x64/Debug/ufr-signer.exe create mode 100644 bin/x64/Debug/ufr-signer.exe.config create mode 100644 bin/x64/Release/ufr-signer.exe create mode 100644 bin/x64/Release/ufr-signer.exe.config create mode 100644 bin/x86/Release/ufr-signer.exe create mode 100644 bin/x86/Release/ufr-signer.exe.config create mode 100644 digital-signature-verifier.csproj create mode 100644 digital-signature-verifier.sln create mode 100644 frmMain.cs create mode 100644 frmMain.designer.cs create mode 100644 frmMain.resx create mode 100644 frmPassword.cs create mode 100644 frmPassword.designer.cs create mode 100644 frmPassword.resx diff --git a/App.config b/App.config new file mode 100644 index 0000000..8324aa6 --- /dev/null +++ b/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/ClassDiagram1.cd b/ClassDiagram1.cd new file mode 100644 index 0000000..7b89419 --- /dev/null +++ b/ClassDiagram1.cd @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..94e9cf3 --- /dev/null +++ b/Program.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace SignatureVerifier +{ + static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new frmMain()); + } + } +} diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..96d1a97 --- /dev/null +++ b/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ufr-signer")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("ufr-signer")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("0d346cc0-83c5-4117-b2e6-2a33686b3fc0")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("0.0.0.1")] +[assembly: AssemblyFileVersion("0.0.0.1")] diff --git a/Properties/Resources.Designer.cs b/Properties/Resources.Designer.cs new file mode 100644 index 0000000..a048735 --- /dev/null +++ b/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace SignatureVerifier.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("EcdsaTest.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/Properties/Resources.resx b/Properties/Resources.resx new file mode 100644 index 0000000..af7dbeb --- /dev/null +++ b/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Properties/Settings.Designer.cs b/Properties/Settings.Designer.cs new file mode 100644 index 0000000..82e72d3 --- /dev/null +++ b/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace SignatureVerifier.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/Properties/Settings.settings b/Properties/Settings.settings new file mode 100644 index 0000000..3964565 --- /dev/null +++ b/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/bc-sharp-crypto/bzip2/src/BZip2Constants.cs b/bc-sharp-crypto/bzip2/src/BZip2Constants.cs new file mode 100644 index 0000000..4a5442d --- /dev/null +++ b/bc-sharp-crypto/bzip2/src/BZip2Constants.cs @@ -0,0 +1,103 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +/* + * This package is based on the work done by Keiron Liddle, Aftex Software + * to whom the Ant project is very grateful for his + * great code. + */ + +using System; + +namespace Org.BouncyCastle.Apache.Bzip2 +{ + /** + * Base class for both the compress and decompress classes. + * Holds common arrays, and static data. + * + * @author Keiron Liddle + */ + public class BZip2Constants { + + public const int baseBlockSize = 100000; + public const int MAX_ALPHA_SIZE = 258; + public const int MAX_CODE_LEN = 23; + public const int RUNA = 0; + public const int RUNB = 1; + public const int N_GROUPS = 6; + public const int G_SIZE = 50; + public const int N_ITERS = 4; + public const int MAX_SELECTORS = (2 + (900000 / G_SIZE)); + public const int NUM_OVERSHOOT_BYTES = 20; + + public static readonly int[] rNums = { + 619, 720, 127, 481, 931, 816, 813, 233, 566, 247, + 985, 724, 205, 454, 863, 491, 741, 242, 949, 214, + 733, 859, 335, 708, 621, 574, 73, 654, 730, 472, + 419, 436, 278, 496, 867, 210, 399, 680, 480, 51, + 878, 465, 811, 169, 869, 675, 611, 697, 867, 561, + 862, 687, 507, 283, 482, 129, 807, 591, 733, 623, + 150, 238, 59, 379, 684, 877, 625, 169, 643, 105, + 170, 607, 520, 932, 727, 476, 693, 425, 174, 647, + 73, 122, 335, 530, 442, 853, 695, 249, 445, 515, + 909, 545, 703, 919, 874, 474, 882, 500, 594, 612, + 641, 801, 220, 162, 819, 984, 589, 513, 495, 799, + 161, 604, 958, 533, 221, 400, 386, 867, 600, 782, + 382, 596, 414, 171, 516, 375, 682, 485, 911, 276, + 98, 553, 163, 354, 666, 933, 424, 341, 533, 870, + 227, 730, 475, 186, 263, 647, 537, 686, 600, 224, + 469, 68, 770, 919, 190, 373, 294, 822, 808, 206, + 184, 943, 795, 384, 383, 461, 404, 758, 839, 887, + 715, 67, 618, 276, 204, 918, 873, 777, 604, 560, + 951, 160, 578, 722, 79, 804, 96, 409, 713, 940, + 652, 934, 970, 447, 318, 353, 859, 672, 112, 785, + 645, 863, 803, 350, 139, 93, 354, 99, 820, 908, + 609, 772, 154, 274, 580, 184, 79, 626, 630, 742, + 653, 282, 762, 623, 680, 81, 927, 626, 789, 125, + 411, 521, 938, 300, 821, 78, 343, 175, 128, 250, + 170, 774, 972, 275, 999, 639, 495, 78, 352, 126, + 857, 956, 358, 619, 580, 124, 737, 594, 701, 612, + 669, 112, 134, 694, 363, 992, 809, 743, 168, 974, + 944, 375, 748, 52, 600, 747, 642, 182, 862, 81, + 344, 805, 988, 739, 511, 655, 814, 334, 249, 515, + 897, 955, 664, 981, 649, 113, 974, 459, 893, 228, + 433, 837, 553, 268, 926, 240, 102, 654, 459, 51, + 686, 754, 806, 760, 493, 403, 415, 394, 687, 700, + 946, 670, 656, 610, 738, 392, 760, 799, 887, 653, + 978, 321, 576, 617, 626, 502, 894, 679, 243, 440, + 680, 879, 194, 572, 640, 724, 926, 56, 204, 700, + 707, 151, 457, 449, 797, 195, 791, 558, 945, 679, + 297, 59, 87, 824, 713, 663, 412, 693, 342, 606, + 134, 108, 571, 364, 631, 212, 174, 643, 304, 329, + 343, 97, 430, 751, 497, 314, 983, 374, 822, 928, + 140, 206, 73, 263, 980, 736, 876, 478, 430, 305, + 170, 514, 364, 692, 829, 82, 855, 953, 676, 246, + 369, 970, 294, 750, 807, 827, 150, 790, 288, 923, + 804, 378, 215, 828, 592, 281, 565, 555, 710, 82, + 896, 831, 547, 261, 524, 462, 293, 465, 502, 56, + 661, 821, 976, 991, 658, 869, 905, 758, 745, 193, + 768, 550, 608, 933, 378, 286, 215, 979, 792, 961, + 61, 688, 793, 644, 986, 403, 106, 366, 905, 644, + 372, 567, 466, 434, 645, 210, 389, 550, 919, 135, + 780, 773, 635, 389, 707, 100, 626, 958, 165, 504, + 920, 176, 193, 713, 857, 265, 203, 50, 668, 108, + 645, 990, 626, 197, 510, 357, 358, 850, 858, 364, + 936, 638 + }; + } +} \ No newline at end of file diff --git a/bc-sharp-crypto/bzip2/src/CBZip2InputStream.cs b/bc-sharp-crypto/bzip2/src/CBZip2InputStream.cs new file mode 100644 index 0000000..82ff83e --- /dev/null +++ b/bc-sharp-crypto/bzip2/src/CBZip2InputStream.cs @@ -0,0 +1,921 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +/* + * This package is based on the work done by Keiron Liddle, Aftex Software + * to whom the Ant project is very grateful for his + * great code. + */ + +using System; +using System.IO; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Apache.Bzip2 +{ + /** + * An input stream that decompresses from the BZip2 format (with the file + * header chars) to be read as any other stream. + * + * @author Keiron Liddle + * + * NB: note this class has been modified to read the leading BZ from the + * start of the BZIP2 stream to make it compatible with other PGP programs. + */ + public class CBZip2InputStream : Stream + { + private static void Cadvise() { + //System.out.Println("CRC Error"); + //throw new CCoruptionError(); + } + +// private static void BadBGLengths() { +// Cadvise(); +// } +// +// private static void BitStreamEOF() { +// Cadvise(); +// } + + private static void CompressedStreamEOF() { + Cadvise(); + } + + private void MakeMaps() { + int i; + nInUse = 0; + for (i = 0; i < 256; i++) { + if (inUse[i]) { + seqToUnseq[nInUse] = (char) i; + unseqToSeq[i] = (char) nInUse; + nInUse++; + } + } + } + + /* + index of the last char in the block, so + the block size == last + 1. + */ + private int last; + + /* + index in zptr[] of original string after sorting. + */ + private int origPtr; + + /* + always: in the range 0 .. 9. + The current block size is 100000 * this number. + */ + private int blockSize100k; + + private bool blockRandomised; + + private int bsBuff; + private int bsLive; + private CRC mCrc = new CRC(); + + private bool[] inUse = new bool[256]; + private int nInUse; + + private char[] seqToUnseq = new char[256]; + private char[] unseqToSeq = new char[256]; + + private char[] selector = new char[BZip2Constants.MAX_SELECTORS]; + private char[] selectorMtf = new char[BZip2Constants.MAX_SELECTORS]; + + private int[] tt; + private char[] ll8; + + /* + freq table collected to save a pass over the data + during decompression. + */ + private int[] unzftab = new int[256]; + + private int[][] limit = InitIntArray(BZip2Constants.N_GROUPS, BZip2Constants.MAX_ALPHA_SIZE); + private int[][] basev = InitIntArray(BZip2Constants.N_GROUPS, BZip2Constants.MAX_ALPHA_SIZE); + private int[][] perm = InitIntArray(BZip2Constants.N_GROUPS, BZip2Constants.MAX_ALPHA_SIZE); + private int[] minLens = new int[BZip2Constants.N_GROUPS]; + + private Stream bsStream; + + private bool streamEnd = false; + + private int currentChar = -1; + + private const int START_BLOCK_STATE = 1; + private const int RAND_PART_A_STATE = 2; + private const int RAND_PART_B_STATE = 3; + private const int RAND_PART_C_STATE = 4; + private const int NO_RAND_PART_A_STATE = 5; + private const int NO_RAND_PART_B_STATE = 6; + private const int NO_RAND_PART_C_STATE = 7; + + private int currentState = START_BLOCK_STATE; + + private int storedBlockCRC, storedCombinedCRC; + private int computedBlockCRC, computedCombinedCRC; + + int i2, count, chPrev, ch2; + int i, tPos; + int rNToGo = 0; + int rTPos = 0; + int j2; + char z; + + public CBZip2InputStream(Stream zStream) { + ll8 = null; + tt = null; + BsSetStream(zStream); + Initialize(); + InitBlock(); + SetupBlock(); + } + + internal static int[][] InitIntArray(int n1, int n2) { + int[][] a = new int[n1][]; + for (int k = 0; k < n1; ++k) { + a[k] = new int[n2]; + } + return a; + } + + internal static char[][] InitCharArray(int n1, int n2) { + char[][] a = new char[n1][]; + for (int k = 0; k < n1; ++k) { + a[k] = new char[n2]; + } + return a; + } + + public override int ReadByte() { + if (streamEnd) { + return -1; + } else { + int retChar = currentChar; + switch (currentState) { + case START_BLOCK_STATE: + break; + case RAND_PART_A_STATE: + break; + case RAND_PART_B_STATE: + SetupRandPartB(); + break; + case RAND_PART_C_STATE: + SetupRandPartC(); + break; + case NO_RAND_PART_A_STATE: + break; + case NO_RAND_PART_B_STATE: + SetupNoRandPartB(); + break; + case NO_RAND_PART_C_STATE: + SetupNoRandPartC(); + break; + default: + break; + } + return retChar; + } + } + + private void Initialize() { + char magic3, magic4; + magic3 = BsGetUChar(); + magic4 = BsGetUChar(); + if (magic3 != 'B' && magic4 != 'Z') + { + throw new IOException("Not a BZIP2 marked stream"); + } + magic3 = BsGetUChar(); + magic4 = BsGetUChar(); + if (magic3 != 'h' || magic4 < '1' || magic4 > '9') { + BsFinishedWithStream(); + streamEnd = true; + return; + } + + SetDecompressStructureSizes(magic4 - '0'); + computedCombinedCRC = 0; + } + + private void InitBlock() { + char magic1, magic2, magic3, magic4; + char magic5, magic6; + magic1 = BsGetUChar(); + magic2 = BsGetUChar(); + magic3 = BsGetUChar(); + magic4 = BsGetUChar(); + magic5 = BsGetUChar(); + magic6 = BsGetUChar(); + if (magic1 == 0x17 && magic2 == 0x72 && magic3 == 0x45 + && magic4 == 0x38 && magic5 == 0x50 && magic6 == 0x90) { + Complete(); + return; + } + + if (magic1 != 0x31 || magic2 != 0x41 || magic3 != 0x59 + || magic4 != 0x26 || magic5 != 0x53 || magic6 != 0x59) { + BadBlockHeader(); + streamEnd = true; + return; + } + + storedBlockCRC = BsGetInt32(); + + if (BsR(1) == 1) { + blockRandomised = true; + } else { + blockRandomised = false; + } + + // currBlockNo++; + GetAndMoveToFrontDecode(); + + mCrc.InitialiseCRC(); + currentState = START_BLOCK_STATE; + } + + private void EndBlock() { + computedBlockCRC = mCrc.GetFinalCRC(); + /* A bad CRC is considered a fatal error. */ + if (storedBlockCRC != computedBlockCRC) { + CrcError(); + } + + computedCombinedCRC = (computedCombinedCRC << 1) + | (int)(((uint)computedCombinedCRC) >> 31); + computedCombinedCRC ^= computedBlockCRC; + } + + private void Complete() { + storedCombinedCRC = BsGetInt32(); + if (storedCombinedCRC != computedCombinedCRC) { + CrcError(); + } + + BsFinishedWithStream(); + streamEnd = true; + } + + private static void BlockOverrun() { + Cadvise(); + } + + private static void BadBlockHeader() { + Cadvise(); + } + + private static void CrcError() { + Cadvise(); + } + + private void BsFinishedWithStream() { + try { + if (this.bsStream != null) { + Platform.Dispose(this.bsStream); + this.bsStream = null; + } + } catch { + //ignore + } + } + + private void BsSetStream(Stream f) { + bsStream = f; + bsLive = 0; + bsBuff = 0; + } + + private int BsR(int n) { + int v; + while (bsLive < n) { + int zzi; + char thech = '\0'; + try { + thech = (char) bsStream.ReadByte(); + } catch (IOException) { + CompressedStreamEOF(); + } + if (thech == '\uffff') { + CompressedStreamEOF(); + } + zzi = thech; + bsBuff = (bsBuff << 8) | (zzi & 0xff); + bsLive += 8; + } + + v = (bsBuff >> (bsLive - n)) & ((1 << n) - 1); + bsLive -= n; + return v; + } + + private char BsGetUChar() { + return (char) BsR(8); + } + + private int BsGetint() { + int u = 0; + u = (u << 8) | BsR(8); + u = (u << 8) | BsR(8); + u = (u << 8) | BsR(8); + u = (u << 8) | BsR(8); + return u; + } + + private int BsGetIntVS(int numBits) { + return (int) BsR(numBits); + } + + private int BsGetInt32() { + return (int) BsGetint(); + } + + private void HbCreateDecodeTables(int[] limit, int[] basev, + int[] perm, char[] length, + int minLen, int maxLen, int alphaSize) { + int pp, i, j, vec; + + pp = 0; + for (i = minLen; i <= maxLen; i++) { + for (j = 0; j < alphaSize; j++) { + if (length[j] == i) { + perm[pp] = j; + pp++; + } + } + } + + for (i = 0; i < BZip2Constants.MAX_CODE_LEN; i++) { + basev[i] = 0; + } + for (i = 0; i < alphaSize; i++) { + basev[length[i] + 1]++; + } + + for (i = 1; i < BZip2Constants.MAX_CODE_LEN; i++) { + basev[i] += basev[i - 1]; + } + + for (i = 0; i < BZip2Constants.MAX_CODE_LEN; i++) { + limit[i] = 0; + } + vec = 0; + + for (i = minLen; i <= maxLen; i++) { + vec += (basev[i + 1] - basev[i]); + limit[i] = vec - 1; + vec <<= 1; + } + for (i = minLen + 1; i <= maxLen; i++) { + basev[i] = ((limit[i - 1] + 1) << 1) - basev[i]; + } + } + + private void RecvDecodingTables() { + char[][] len = InitCharArray(BZip2Constants.N_GROUPS, BZip2Constants.MAX_ALPHA_SIZE); + int i, j, t, nGroups, nSelectors, alphaSize; + int minLen, maxLen; + bool[] inUse16 = new bool[16]; + + /* Receive the mapping table */ + for (i = 0; i < 16; i++) { + if (BsR(1) == 1) { + inUse16[i] = true; + } else { + inUse16[i] = false; + } + } + + for (i = 0; i < 256; i++) { + inUse[i] = false; + } + + for (i = 0; i < 16; i++) { + if (inUse16[i]) { + for (j = 0; j < 16; j++) { + if (BsR(1) == 1) { + inUse[i * 16 + j] = true; + } + } + } + } + + MakeMaps(); + alphaSize = nInUse + 2; + + /* Now the selectors */ + nGroups = BsR(3); + nSelectors = BsR(15); + for (i = 0; i < nSelectors; i++) { + j = 0; + while (BsR(1) == 1) { + j++; + } + selectorMtf[i] = (char) j; + } + + /* Undo the MTF values for the selectors. */ + { + char[] pos = new char[BZip2Constants.N_GROUPS]; + char tmp, v; + for (v = '\0'; v < nGroups; v++) { + pos[v] = v; + } + + for (i = 0; i < nSelectors; i++) { + v = selectorMtf[i]; + tmp = pos[v]; + while (v > 0) { + pos[v] = pos[v - 1]; + v--; + } + pos[0] = tmp; + selector[i] = tmp; + } + } + + /* Now the coding tables */ + for (t = 0; t < nGroups; t++) { + int curr = BsR(5); + for (i = 0; i < alphaSize; i++) { + while (BsR(1) == 1) { + if (BsR(1) == 0) { + curr++; + } else { + curr--; + } + } + len[t][i] = (char) curr; + } + } + + /* Create the Huffman decoding tables */ + for (t = 0; t < nGroups; t++) { + minLen = 32; + maxLen = 0; + for (i = 0; i < alphaSize; i++) { + if (len[t][i] > maxLen) { + maxLen = len[t][i]; + } + if (len[t][i] < minLen) { + minLen = len[t][i]; + } + } + HbCreateDecodeTables(limit[t], basev[t], perm[t], len[t], minLen, + maxLen, alphaSize); + minLens[t] = minLen; + } + } + + private void GetAndMoveToFrontDecode() { + char[] yy = new char[256]; + int i, j, nextSym, limitLast; + int EOB, groupNo, groupPos; + + limitLast = BZip2Constants.baseBlockSize * blockSize100k; + origPtr = BsGetIntVS(24); + + RecvDecodingTables(); + EOB = nInUse + 1; + groupNo = -1; + groupPos = 0; + + /* + Setting up the unzftab entries here is not strictly + necessary, but it does save having to do it later + in a separate pass, and so saves a block's worth of + cache misses. + */ + for (i = 0; i <= 255; i++) { + unzftab[i] = 0; + } + + for (i = 0; i <= 255; i++) { + yy[i] = (char) i; + } + + last = -1; + + { + int zt, zn, zvec, zj; + if (groupPos == 0) { + groupNo++; + groupPos = BZip2Constants.G_SIZE; + } + groupPos--; + zt = selector[groupNo]; + zn = minLens[zt]; + zvec = BsR(zn); + while (zvec > limit[zt][zn]) { + zn++; + { + { + while (bsLive < 1) { + int zzi; + char thech = '\0'; + try { + thech = (char) bsStream.ReadByte(); + } catch (IOException) { + CompressedStreamEOF(); + } + if (thech == '\uffff') { + CompressedStreamEOF(); + } + zzi = thech; + bsBuff = (bsBuff << 8) | (zzi & 0xff); + bsLive += 8; + } + } + zj = (bsBuff >> (bsLive - 1)) & 1; + bsLive--; + } + zvec = (zvec << 1) | zj; + } + nextSym = perm[zt][zvec - basev[zt][zn]]; + } + + while (true) { + + if (nextSym == EOB) { + break; + } + + if (nextSym == BZip2Constants.RUNA || nextSym == BZip2Constants.RUNB) { + char ch; + int s = -1; + int N = 1; + do { + if (nextSym == BZip2Constants.RUNA) { + s = s + (0 + 1) * N; + } else if (nextSym == BZip2Constants.RUNB) { + s = s + (1 + 1) * N; + } + N = N * 2; + { + int zt, zn, zvec, zj; + if (groupPos == 0) { + groupNo++; + groupPos = BZip2Constants.G_SIZE; + } + groupPos--; + zt = selector[groupNo]; + zn = minLens[zt]; + zvec = BsR(zn); + while (zvec > limit[zt][zn]) { + zn++; + { + { + while (bsLive < 1) { + int zzi; + char thech = '\0'; + try { + thech = (char) bsStream.ReadByte(); + } catch (IOException) { + CompressedStreamEOF(); + } + if (thech == '\uffff') { + CompressedStreamEOF(); + } + zzi = thech; + bsBuff = (bsBuff << 8) | (zzi & 0xff); + bsLive += 8; + } + } + zj = (bsBuff >> (bsLive - 1)) & 1; + bsLive--; + } + zvec = (zvec << 1) | zj; + } + nextSym = perm[zt][zvec - basev[zt][zn]]; + } + } while (nextSym == BZip2Constants.RUNA || nextSym == BZip2Constants.RUNB); + + s++; + ch = seqToUnseq[yy[0]]; + unzftab[ch] += s; + + while (s > 0) { + last++; + ll8[last] = ch; + s--; + } + + if (last >= limitLast) { + BlockOverrun(); + } + continue; + } else { + char tmp; + last++; + if (last >= limitLast) { + BlockOverrun(); + } + + tmp = yy[nextSym - 1]; + unzftab[seqToUnseq[tmp]]++; + ll8[last] = seqToUnseq[tmp]; + + /* + This loop is hammered during decompression, + hence the unrolling. + + for (j = nextSym-1; j > 0; j--) yy[j] = yy[j-1]; + */ + + j = nextSym - 1; + for (; j > 3; j -= 4) { + yy[j] = yy[j - 1]; + yy[j - 1] = yy[j - 2]; + yy[j - 2] = yy[j - 3]; + yy[j - 3] = yy[j - 4]; + } + for (; j > 0; j--) { + yy[j] = yy[j - 1]; + } + + yy[0] = tmp; + { + int zt, zn, zvec, zj; + if (groupPos == 0) { + groupNo++; + groupPos = BZip2Constants.G_SIZE; + } + groupPos--; + zt = selector[groupNo]; + zn = minLens[zt]; + zvec = BsR(zn); + while (zvec > limit[zt][zn]) { + zn++; + { + { + while (bsLive < 1) { + int zzi; + char thech = '\0'; + try { + thech = (char) bsStream.ReadByte(); + } catch (IOException) { + CompressedStreamEOF(); + } + zzi = thech; + bsBuff = (bsBuff << 8) | (zzi & 0xff); + bsLive += 8; + } + } + zj = (bsBuff >> (bsLive - 1)) & 1; + bsLive--; + } + zvec = (zvec << 1) | zj; + } + nextSym = perm[zt][zvec - basev[zt][zn]]; + } + continue; + } + } + } + + private void SetupBlock() { + int[] cftab = new int[257]; + char ch; + + cftab[0] = 0; + for (i = 1; i <= 256; i++) { + cftab[i] = unzftab[i - 1]; + } + for (i = 1; i <= 256; i++) { + cftab[i] += cftab[i - 1]; + } + + for (i = 0; i <= last; i++) { + ch = (char) ll8[i]; + tt[cftab[ch]] = i; + cftab[ch]++; + } + cftab = null; + + tPos = tt[origPtr]; + + count = 0; + i2 = 0; + ch2 = 256; /* not a char and not EOF */ + + if (blockRandomised) { + rNToGo = 0; + rTPos = 0; + SetupRandPartA(); + } else { + SetupNoRandPartA(); + } + } + + private void SetupRandPartA() { + if (i2 <= last) { + chPrev = ch2; + ch2 = ll8[tPos]; + tPos = tt[tPos]; + if (rNToGo == 0) { + rNToGo = BZip2Constants.rNums[rTPos]; + rTPos++; + if (rTPos == 512) { + rTPos = 0; + } + } + rNToGo--; + ch2 ^= (int) ((rNToGo == 1) ? 1 : 0); + i2++; + + currentChar = ch2; + currentState = RAND_PART_B_STATE; + mCrc.UpdateCRC(ch2); + } else { + EndBlock(); + InitBlock(); + SetupBlock(); + } + } + + private void SetupNoRandPartA() { + if (i2 <= last) { + chPrev = ch2; + ch2 = ll8[tPos]; + tPos = tt[tPos]; + i2++; + + currentChar = ch2; + currentState = NO_RAND_PART_B_STATE; + mCrc.UpdateCRC(ch2); + } else { + EndBlock(); + InitBlock(); + SetupBlock(); + } + } + + private void SetupRandPartB() { + if (ch2 != chPrev) { + currentState = RAND_PART_A_STATE; + count = 1; + SetupRandPartA(); + } else { + count++; + if (count >= 4) { + z = ll8[tPos]; + tPos = tt[tPos]; + if (rNToGo == 0) { + rNToGo = BZip2Constants.rNums[rTPos]; + rTPos++; + if (rTPos == 512) { + rTPos = 0; + } + } + rNToGo--; + z ^= (char)((rNToGo == 1) ? 1 : 0); + j2 = 0; + currentState = RAND_PART_C_STATE; + SetupRandPartC(); + } else { + currentState = RAND_PART_A_STATE; + SetupRandPartA(); + } + } + } + + private void SetupRandPartC() { + if (j2 < (int) z) { + currentChar = ch2; + mCrc.UpdateCRC(ch2); + j2++; + } else { + currentState = RAND_PART_A_STATE; + i2++; + count = 0; + SetupRandPartA(); + } + } + + private void SetupNoRandPartB() { + if (ch2 != chPrev) { + currentState = NO_RAND_PART_A_STATE; + count = 1; + SetupNoRandPartA(); + } else { + count++; + if (count >= 4) { + z = ll8[tPos]; + tPos = tt[tPos]; + currentState = NO_RAND_PART_C_STATE; + j2 = 0; + SetupNoRandPartC(); + } else { + currentState = NO_RAND_PART_A_STATE; + SetupNoRandPartA(); + } + } + } + + private void SetupNoRandPartC() { + if (j2 < (int) z) { + currentChar = ch2; + mCrc.UpdateCRC(ch2); + j2++; + } else { + currentState = NO_RAND_PART_A_STATE; + i2++; + count = 0; + SetupNoRandPartA(); + } + } + + private void SetDecompressStructureSizes(int newSize100k) { + if (!(0 <= newSize100k && newSize100k <= 9 && 0 <= blockSize100k + && blockSize100k <= 9)) { + // throw new IOException("Invalid block size"); + } + + blockSize100k = newSize100k; + + if (newSize100k == 0) { + return; + } + + int n = BZip2Constants.baseBlockSize * newSize100k; + ll8 = new char[n]; + tt = new int[n]; + } + + public override void Flush() { + } + + public override int Read(byte[] buffer, int offset, int count) { + int c = -1; + int k; + for (k = 0; k < count; ++k) { + c = ReadByte(); + if (c == -1) + break; + buffer[k + offset] = (byte)c; + } + return k; + } + + public override long Seek(long offset, SeekOrigin origin) { + return 0; + } + + public override void SetLength(long value) { + } + + public override void Write(byte[] buffer, int offset, int count) { + } + + public override bool CanRead { + get { + return true; + } + } + + public override bool CanSeek { + get { + return false; + } + } + + public override bool CanWrite { + get { + return false; + } + } + + public override long Length { + get { + return 0; + } + } + + public override long Position { + get { + return 0; + } + set { + } + } + } +} \ No newline at end of file diff --git a/bc-sharp-crypto/bzip2/src/CBZip2OutputStream.cs b/bc-sharp-crypto/bzip2/src/CBZip2OutputStream.cs new file mode 100644 index 0000000..ffac073 --- /dev/null +++ b/bc-sharp-crypto/bzip2/src/CBZip2OutputStream.cs @@ -0,0 +1,1709 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +/* + * This package is based on the work done by Keiron Liddle, Aftex Software + * to whom the Ant project is very grateful for his + * great code. + */ + +using System; +using System.IO; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Apache.Bzip2 +{ + /** + * An output stream that compresses into the BZip2 format (with the file + * header chars) into another stream. + * + * @author Keiron Liddle + * + * TODO: Update to BZip2 1.0.1 + * NB: note this class has been modified to add a leading BZ to the + * start of the BZIP2 stream to make it compatible with other PGP programs. + */ + public class CBZip2OutputStream : Stream + { + protected const int SETMASK = (1 << 21); + protected const int CLEARMASK = (~SETMASK); + protected const int GREATER_ICOST = 15; + protected const int LESSER_ICOST = 0; + protected const int SMALL_THRESH = 20; + protected const int DEPTH_THRESH = 10; + + /* + If you are ever unlucky/improbable enough + to get a stack overflow whilst sorting, + increase the following constant and try + again. In practice I have never seen the + stack go above 27 elems, so the following + limit seems very generous. + */ + protected const int QSORT_STACK_SIZE = 1000; + private bool finished; + + private static void Panic() { + //System.out.Println("panic"); + //throw new CError(); + } + + private void MakeMaps() { + int i; + nInUse = 0; + for (i = 0; i < 256; i++) { + if (inUse[i]) { + seqToUnseq[nInUse] = (char) i; + unseqToSeq[i] = (char) nInUse; + nInUse++; + } + } + } + + protected static void HbMakeCodeLengths(char[] len, int[] freq, + int alphaSize, int maxLen) { + /* + Nodes and heap entries run from 1. Entry 0 + for both the heap and nodes is a sentinel. + */ + int nNodes, nHeap, n1, n2, i, j, k; + bool tooLong; + + int[] heap = new int[BZip2Constants.MAX_ALPHA_SIZE + 2]; + int[] weight = new int[BZip2Constants.MAX_ALPHA_SIZE * 2]; + int[] parent = new int[BZip2Constants.MAX_ALPHA_SIZE * 2]; + + for (i = 0; i < alphaSize; i++) { + weight[i + 1] = (freq[i] == 0 ? 1 : freq[i]) << 8; + } + + while (true) { + nNodes = alphaSize; + nHeap = 0; + + heap[0] = 0; + weight[0] = 0; + parent[0] = -2; + + for (i = 1; i <= alphaSize; i++) { + parent[i] = -1; + nHeap++; + heap[nHeap] = i; + { + int zz, tmp; + zz = nHeap; + tmp = heap[zz]; + while (weight[tmp] < weight[heap[zz >> 1]]) { + heap[zz] = heap[zz >> 1]; + zz >>= 1; + } + heap[zz] = tmp; + } + } + if (!(nHeap < (BZip2Constants.MAX_ALPHA_SIZE + 2))) { + Panic(); + } + + while (nHeap > 1) { + n1 = heap[1]; + heap[1] = heap[nHeap]; + nHeap--; + { + int zz = 0, yy = 0, tmp = 0; + zz = 1; + tmp = heap[zz]; + while (true) { + yy = zz << 1; + if (yy > nHeap) { + break; + } + if (yy < nHeap + && weight[heap[yy + 1]] < weight[heap[yy]]) { + yy++; + } + if (weight[tmp] < weight[heap[yy]]) { + break; + } + heap[zz] = heap[yy]; + zz = yy; + } + heap[zz] = tmp; + } + n2 = heap[1]; + heap[1] = heap[nHeap]; + nHeap--; + { + int zz = 0, yy = 0, tmp = 0; + zz = 1; + tmp = heap[zz]; + while (true) { + yy = zz << 1; + if (yy > nHeap) { + break; + } + if (yy < nHeap + && weight[heap[yy + 1]] < weight[heap[yy]]) { + yy++; + } + if (weight[tmp] < weight[heap[yy]]) { + break; + } + heap[zz] = heap[yy]; + zz = yy; + } + heap[zz] = tmp; + } + nNodes++; + parent[n1] = parent[n2] = nNodes; + + weight[nNodes] = (int)((uint)((weight[n1] & 0xffffff00) + + (weight[n2] & 0xffffff00)) + | (uint)(1 + (((weight[n1] & 0x000000ff) > + (weight[n2] & 0x000000ff)) ? + (weight[n1] & 0x000000ff) : + (weight[n2] & 0x000000ff)))); + + parent[nNodes] = -1; + nHeap++; + heap[nHeap] = nNodes; + { + int zz = 0, tmp = 0; + zz = nHeap; + tmp = heap[zz]; + while (weight[tmp] < weight[heap[zz >> 1]]) { + heap[zz] = heap[zz >> 1]; + zz >>= 1; + } + heap[zz] = tmp; + } + } + if (!(nNodes < (BZip2Constants.MAX_ALPHA_SIZE * 2))) { + Panic(); + } + + tooLong = false; + for (i = 1; i <= alphaSize; i++) { + j = 0; + k = i; + while (parent[k] >= 0) { + k = parent[k]; + j++; + } + len[i - 1] = (char) j; + if (j > maxLen) { + tooLong = true; + } + } + + if (!tooLong) { + break; + } + + for (i = 1; i < alphaSize; i++) { + j = weight[i] >> 8; + j = 1 + (j / 2); + weight[i] = j << 8; + } + } + } + + /* + index of the last char in the block, so + the block size == last + 1. + */ + int last; + + /* + index in zptr[] of original string after sorting. + */ + int origPtr; + + /* + always: in the range 0 .. 9. + The current block size is 100000 * this number. + */ + int blockSize100k; + + bool blockRandomised; + + int bytesOut; + int bsBuff; + int bsLive; + CRC mCrc = new CRC(); + + private bool[] inUse = new bool[256]; + private int nInUse; + + private char[] seqToUnseq = new char[256]; + private char[] unseqToSeq = new char[256]; + + private char[] selector = new char[BZip2Constants.MAX_SELECTORS]; + private char[] selectorMtf = new char[BZip2Constants.MAX_SELECTORS]; + + private char[] block; + private int[] quadrant; + private int[] zptr; + private short[] szptr; + private int[] ftab; + + private int nMTF; + + private int[] mtfFreq = new int[BZip2Constants.MAX_ALPHA_SIZE]; + + /* + * Used when sorting. If too many long comparisons + * happen, we stop sorting, randomise the block + * slightly, and try again. + */ + private int workFactor; + private int workDone; + private int workLimit; + private bool firstAttempt; + private int nBlocksRandomised; + + private int currentChar = -1; + private int runLength = 0; + + public CBZip2OutputStream(Stream inStream) : this(inStream, 9) { + } + + public CBZip2OutputStream(Stream inStream, int inBlockSize) + { + block = null; + quadrant = null; + zptr = null; + ftab = null; + + inStream.WriteByte((byte)'B'); + inStream.WriteByte((byte)'Z'); + + BsSetStream(inStream); + + workFactor = 50; + if (inBlockSize > 9) { + inBlockSize = 9; + } + if (inBlockSize < 1) { + inBlockSize = 1; + } + blockSize100k = inBlockSize; + AllocateCompressStructures(); + Initialize(); + InitBlock(); + } + + /** + * + * modified by Oliver Merkel, 010128 + * + */ + public override void WriteByte(byte bv) { + int b = (256 + bv) % 256; + if (currentChar != -1) { + if (currentChar == b) { + runLength++; + if (runLength > 254) { + WriteRun(); + currentChar = -1; + runLength = 0; + } + } else { + WriteRun(); + runLength = 1; + currentChar = b; + } + } else { + currentChar = b; + runLength++; + } + } + + private void WriteRun() { + if (last < allowableBlockSize) { + inUse[currentChar] = true; + for (int i = 0; i < runLength; i++) { + mCrc.UpdateCRC((char) currentChar); + } + switch (runLength) { + case 1: + last++; + block[last + 1] = (char) currentChar; + break; + case 2: + last++; + block[last + 1] = (char) currentChar; + last++; + block[last + 1] = (char) currentChar; + break; + case 3: + last++; + block[last + 1] = (char) currentChar; + last++; + block[last + 1] = (char) currentChar; + last++; + block[last + 1] = (char) currentChar; + break; + default: + inUse[runLength - 4] = true; + last++; + block[last + 1] = (char) currentChar; + last++; + block[last + 1] = (char) currentChar; + last++; + block[last + 1] = (char) currentChar; + last++; + block[last + 1] = (char) currentChar; + last++; + block[last + 1] = (char) (runLength - 4); + break; + } + } else { + EndBlock(); + InitBlock(); + WriteRun(); + } + } + + bool closed = false; + +// protected void Finalize() { +// Close(); +// } + +#if PORTABLE + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (closed) + return; + + Finish(); + closed = true; + Platform.Dispose(this.bsStream); + } + base.Dispose(disposing); + } +#else + public override void Close() { + if (closed) + return; + + Finish(); + + closed = true; + Platform.Dispose(this.bsStream); + + base.Close(); + } +#endif + + public void Finish() { + if (finished) { + return; + } + + if (runLength > 0) { + WriteRun(); + } + currentChar = -1; + EndBlock(); + EndCompression(); + finished = true; + Flush(); + } + + public override void Flush() { + bsStream.Flush(); + } + + private int blockCRC, combinedCRC; + + private void Initialize() { + bytesOut = 0; + nBlocksRandomised = 0; + + /* Write `magic' bytes h indicating file-format == huffmanised, + followed by a digit indicating blockSize100k. + */ + BsPutUChar('h'); + BsPutUChar('0' + blockSize100k); + + combinedCRC = 0; + } + + private int allowableBlockSize; + + private void InitBlock() { + // blockNo++; + mCrc.InitialiseCRC(); + last = -1; + // ch = 0; + + for (int i = 0; i < 256; i++) { + inUse[i] = false; + } + + /* 20 is just a paranoia constant */ + allowableBlockSize = BZip2Constants.baseBlockSize * blockSize100k - 20; + } + + private void EndBlock() { + blockCRC = mCrc.GetFinalCRC(); + combinedCRC = (combinedCRC << 1) | (int)(((uint)combinedCRC) >> 31); + combinedCRC ^= blockCRC; + + /* sort the block and establish posn of original string */ + DoReversibleTransformation(); + + /* + A 6-byte block header, the value chosen arbitrarily + as 0x314159265359 :-). A 32 bit value does not really + give a strong enough guarantee that the value will not + appear by chance in the compressed datastream. Worst-case + probability of this event, for a 900k block, is about + 2.0e-3 for 32 bits, 1.0e-5 for 40 bits and 4.0e-8 for 48 bits. + For a compressed file of size 100Gb -- about 100000 blocks -- + only a 48-bit marker will do. NB: normal compression/ + decompression do *not* rely on these statistical properties. + They are only important when trying to recover blocks from + damaged files. + */ + BsPutUChar(0x31); + BsPutUChar(0x41); + BsPutUChar(0x59); + BsPutUChar(0x26); + BsPutUChar(0x53); + BsPutUChar(0x59); + + /* Now the block's CRC, so it is in a known place. */ + BsPutint(blockCRC); + + /* Now a single bit indicating randomisation. */ + if (blockRandomised) { + BsW(1, 1); + nBlocksRandomised++; + } else { + BsW(1, 0); + } + + /* Finally, block's contents proper. */ + MoveToFrontCodeAndSend(); + } + + private void EndCompression() { + /* + Now another magic 48-bit number, 0x177245385090, to + indicate the end of the last block. (Sqrt(pi), if + you want to know. I did want to use e, but it contains + too much repetition -- 27 18 28 18 28 46 -- for me + to feel statistically comfortable. Call me paranoid.) + */ + BsPutUChar(0x17); + BsPutUChar(0x72); + BsPutUChar(0x45); + BsPutUChar(0x38); + BsPutUChar(0x50); + BsPutUChar(0x90); + + BsPutint(combinedCRC); + + BsFinishedWithStream(); + } + + private void HbAssignCodes(int[] code, char[] length, int minLen, + int maxLen, int alphaSize) { + int n, vec, i; + + vec = 0; + for (n = minLen; n <= maxLen; n++) { + for (i = 0; i < alphaSize; i++) { + if (length[i] == n) { + code[i] = vec; + vec++; + } + }; + vec <<= 1; + } + } + + private void BsSetStream(Stream f) { + bsStream = f; + bsLive = 0; + bsBuff = 0; + bytesOut = 0; + } + + private void BsFinishedWithStream() { + while (bsLive > 0) { + int ch = (bsBuff >> 24); + try { + bsStream.WriteByte((byte)ch); // write 8-bit + } catch (IOException e) { + throw e; + } + bsBuff <<= 8; + bsLive -= 8; + bytesOut++; + } + } + + private void BsW(int n, int v) { + while (bsLive >= 8) { + int ch = (bsBuff >> 24); + try { + bsStream.WriteByte((byte)ch); // write 8-bit + } catch (IOException e) { + throw e; + } + bsBuff <<= 8; + bsLive -= 8; + bytesOut++; + } + bsBuff |= (v << (32 - bsLive - n)); + bsLive += n; + } + + private void BsPutUChar(int c) { + BsW(8, c); + } + + private void BsPutint(int u) { + BsW(8, (u >> 24) & 0xff); + BsW(8, (u >> 16) & 0xff); + BsW(8, (u >> 8) & 0xff); + BsW(8, u & 0xff); + } + + private void BsPutIntVS(int numBits, int c) { + BsW(numBits, c); + } + + private void SendMTFValues() { + char[][] len = CBZip2InputStream.InitCharArray(BZip2Constants.N_GROUPS, BZip2Constants.MAX_ALPHA_SIZE); + + int v, t, i, j, gs, ge, totc, bt, bc, iter; + int nSelectors = 0, alphaSize, minLen, maxLen, selCtr; + int nGroups; + + alphaSize = nInUse + 2; + for (t = 0; t < BZip2Constants.N_GROUPS; t++) { + for (v = 0; v < alphaSize; v++) { + len[t][v] = (char) GREATER_ICOST; + } + } + + /* Decide how many coding tables to use */ + if (nMTF <= 0) { + Panic(); + } + + if (nMTF < 200) { + nGroups = 2; + } else if (nMTF < 600) { + nGroups = 3; + } else if (nMTF < 1200) { + nGroups = 4; + } else if (nMTF < 2400) { + nGroups = 5; + } else { + nGroups = 6; + } + + /* Generate an initial set of coding tables */ { + int nPart, remF, tFreq, aFreq; + + nPart = nGroups; + remF = nMTF; + gs = 0; + while (nPart > 0) { + tFreq = remF / nPart; + ge = gs - 1; + aFreq = 0; + while (aFreq < tFreq && ge < alphaSize - 1) { + ge++; + aFreq += mtfFreq[ge]; + } + + if (ge > gs && nPart != nGroups && nPart != 1 + && ((nGroups - nPart) % 2 == 1)) { + aFreq -= mtfFreq[ge]; + ge--; + } + + for (v = 0; v < alphaSize; v++) { + if (v >= gs && v <= ge) { + len[nPart - 1][v] = (char) LESSER_ICOST; + } else { + len[nPart - 1][v] = (char) GREATER_ICOST; + } + } + + nPart--; + gs = ge + 1; + remF -= aFreq; + } + } + + int[][] rfreq = CBZip2InputStream.InitIntArray(BZip2Constants.N_GROUPS, BZip2Constants.MAX_ALPHA_SIZE); + int[] fave = new int[BZip2Constants.N_GROUPS]; + short[] cost = new short[BZip2Constants.N_GROUPS]; + /* + Iterate up to N_ITERS times to improve the tables. + */ + for (iter = 0; iter < BZip2Constants.N_ITERS; iter++) { + for (t = 0; t < nGroups; t++) { + fave[t] = 0; + } + + for (t = 0; t < nGroups; t++) { + for (v = 0; v < alphaSize; v++) { + rfreq[t][v] = 0; + } + } + + nSelectors = 0; + totc = 0; + gs = 0; + while (true) { + + /* Set group start & end marks. */ + if (gs >= nMTF) { + break; + } + ge = gs + BZip2Constants.G_SIZE - 1; + if (ge >= nMTF) { + ge = nMTF - 1; + } + + /* + Calculate the cost of this group as coded + by each of the coding tables. + */ + for (t = 0; t < nGroups; t++) { + cost[t] = 0; + } + + if (nGroups == 6) { + short cost0, cost1, cost2, cost3, cost4, cost5; + cost0 = cost1 = cost2 = cost3 = cost4 = cost5 = 0; + for (i = gs; i <= ge; i++) { + short icv = szptr[i]; + cost0 += (short)len[0][icv]; + cost1 += (short)len[1][icv]; + cost2 += (short)len[2][icv]; + cost3 += (short)len[3][icv]; + cost4 += (short)len[4][icv]; + cost5 += (short)len[5][icv]; + } + cost[0] = cost0; + cost[1] = cost1; + cost[2] = cost2; + cost[3] = cost3; + cost[4] = cost4; + cost[5] = cost5; + } else { + for (i = gs; i <= ge; i++) { + short icv = szptr[i]; + for (t = 0; t < nGroups; t++) { + cost[t] += (short)len[t][icv]; + } + } + } + + /* + Find the coding table which is best for this group, + and record its identity in the selector table. + */ + bc = 999999999; + bt = -1; + for (t = 0; t < nGroups; t++) { + if (cost[t] < bc) { + bc = cost[t]; + bt = t; + } + }; + totc += bc; + fave[bt]++; + selector[nSelectors] = (char) bt; + nSelectors++; + + /* + Increment the symbol frequencies for the selected table. + */ + for (i = gs; i <= ge; i++) { + rfreq[bt][szptr[i]]++; + } + + gs = ge + 1; + } + + /* + Recompute the tables based on the accumulated frequencies. + */ + for (t = 0; t < nGroups; t++) { + HbMakeCodeLengths(len[t], rfreq[t], alphaSize, 20); + } + } + + rfreq = null; + fave = null; + cost = null; + + if (!(nGroups < 8)) { + Panic(); + } + if (!(nSelectors < 32768 && nSelectors <= (2 + (900000 / BZip2Constants.G_SIZE)))) { + Panic(); + } + + + /* Compute MTF values for the selectors. */ + { + char[] pos = new char[BZip2Constants.N_GROUPS]; + char ll_i, tmp2, tmp; + for (i = 0; i < nGroups; i++) { + pos[i] = (char) i; + } + for (i = 0; i < nSelectors; i++) { + ll_i = selector[i]; + j = 0; + tmp = pos[j]; + while (ll_i != tmp) { + j++; + tmp2 = tmp; + tmp = pos[j]; + pos[j] = tmp2; + } + pos[0] = tmp; + selectorMtf[i] = (char) j; + } + } + + int[][] code = CBZip2InputStream.InitIntArray(BZip2Constants.N_GROUPS, BZip2Constants.MAX_ALPHA_SIZE); + + /* Assign actual codes for the tables. */ + for (t = 0; t < nGroups; t++) { + minLen = 32; + maxLen = 0; + for (i = 0; i < alphaSize; i++) { + if (len[t][i] > maxLen) { + maxLen = len[t][i]; + } + if (len[t][i] < minLen) { + minLen = len[t][i]; + } + } + if (maxLen > 20) { + Panic(); + } + if (minLen < 1) { + Panic(); + } + HbAssignCodes(code[t], len[t], minLen, maxLen, alphaSize); + } + + /* Transmit the mapping table. */ + { + bool[] inUse16 = new bool[16]; + for (i = 0; i < 16; i++) { + inUse16[i] = false; + for (j = 0; j < 16; j++) { + if (inUse[i * 16 + j]) { + inUse16[i] = true; + } + } + } + + for (i = 0; i < 16; i++) { + if (inUse16[i]) { + BsW(1, 1); + } else { + BsW(1, 0); + } + } + + for (i = 0; i < 16; i++) { + if (inUse16[i]) { + for (j = 0; j < 16; j++) { + if (inUse[i * 16 + j]) { + BsW(1, 1); + } else { + BsW(1, 0); + } + } + } + } + + } + + /* Now the selectors. */ + BsW(3, nGroups); + BsW(15, nSelectors); + for (i = 0; i < nSelectors; i++) { + for (j = 0; j < selectorMtf[i]; j++) { + BsW(1, 1); + } + BsW(1, 0); + } + + /* Now the coding tables. */ + for (t = 0; t < nGroups; t++) { + int curr = len[t][0]; + BsW(5, curr); + for (i = 0; i < alphaSize; i++) { + while (curr < len[t][i]) { + BsW(2, 2); + curr++; /* 10 */ + } + while (curr > len[t][i]) { + BsW(2, 3); + curr--; /* 11 */ + } + BsW(1, 0); + } + } + + /* And finally, the block data proper */ + selCtr = 0; + gs = 0; + while (true) { + if (gs >= nMTF) { + break; + } + ge = gs + BZip2Constants.G_SIZE - 1; + if (ge >= nMTF) { + ge = nMTF - 1; + } + for (i = gs; i <= ge; i++) { + BsW(len[selector[selCtr]][szptr[i]], + code[selector[selCtr]][szptr[i]]); + } + + gs = ge + 1; + selCtr++; + } + if (!(selCtr == nSelectors)) { + Panic(); + } + } + + private void MoveToFrontCodeAndSend() { + BsPutIntVS(24, origPtr); + GenerateMTFValues(); + SendMTFValues(); + } + + private Stream bsStream; + + private void SimpleSort(int lo, int hi, int d) { + int i, j, h, bigN, hp; + int v; + + bigN = hi - lo + 1; + if (bigN < 2) { + return; + } + + hp = 0; + while (incs[hp] < bigN) { + hp++; + } + hp--; + + for (; hp >= 0; hp--) { + h = incs[hp]; + + i = lo + h; + while (true) { + /* copy 1 */ + if (i > hi) { + break; + } + v = zptr[i]; + j = i; + while (FullGtU(zptr[j - h] + d, v + d)) { + zptr[j] = zptr[j - h]; + j = j - h; + if (j <= (lo + h - 1)) { + break; + } + } + zptr[j] = v; + i++; + + /* copy 2 */ + if (i > hi) { + break; + } + v = zptr[i]; + j = i; + while (FullGtU(zptr[j - h] + d, v + d)) { + zptr[j] = zptr[j - h]; + j = j - h; + if (j <= (lo + h - 1)) { + break; + } + } + zptr[j] = v; + i++; + + /* copy 3 */ + if (i > hi) { + break; + } + v = zptr[i]; + j = i; + while (FullGtU(zptr[j - h] + d, v + d)) { + zptr[j] = zptr[j - h]; + j = j - h; + if (j <= (lo + h - 1)) { + break; + } + } + zptr[j] = v; + i++; + + if (workDone > workLimit && firstAttempt) { + return; + } + } + } + } + + private void Vswap(int p1, int p2, int n) { + int temp = 0; + while (n > 0) { + temp = zptr[p1]; + zptr[p1] = zptr[p2]; + zptr[p2] = temp; + p1++; + p2++; + n--; + } + } + + private char Med3(char a, char b, char c) { + char t; + if (a > b) { + t = a; + a = b; + b = t; + } + if (b > c) { + t = b; + b = c; + c = t; + } + if (a > b) { + b = a; + } + return b; + } + + internal class StackElem { + internal int ll; + internal int hh; + internal int dd; + } + + private void QSort3(int loSt, int hiSt, int dSt) { + int unLo, unHi, ltLo, gtHi, med, n, m; + int sp, lo, hi, d; + StackElem[] stack = new StackElem[QSORT_STACK_SIZE]; + for (int count = 0; count < QSORT_STACK_SIZE; count++) { + stack[count] = new StackElem(); + } + + sp = 0; + + stack[sp].ll = loSt; + stack[sp].hh = hiSt; + stack[sp].dd = dSt; + sp++; + + while (sp > 0) { + if (sp >= QSORT_STACK_SIZE) { + Panic(); + } + + sp--; + lo = stack[sp].ll; + hi = stack[sp].hh; + d = stack[sp].dd; + + if (hi - lo < SMALL_THRESH || d > DEPTH_THRESH) { + SimpleSort(lo, hi, d); + if (workDone > workLimit && firstAttempt) { + return; + } + continue; + } + + med = Med3(block[zptr[lo] + d + 1], + block[zptr[hi ] + d + 1], + block[zptr[(lo + hi) >> 1] + d + 1]); + + unLo = ltLo = lo; + unHi = gtHi = hi; + + while (true) { + while (true) { + if (unLo > unHi) { + break; + } + n = ((int) block[zptr[unLo] + d + 1]) - med; + if (n == 0) { + int temp = 0; + temp = zptr[unLo]; + zptr[unLo] = zptr[ltLo]; + zptr[ltLo] = temp; + ltLo++; + unLo++; + continue; + }; + if (n > 0) { + break; + } + unLo++; + } + while (true) { + if (unLo > unHi) { + break; + } + n = ((int) block[zptr[unHi] + d + 1]) - med; + if (n == 0) { + int temp = 0; + temp = zptr[unHi]; + zptr[unHi] = zptr[gtHi]; + zptr[gtHi] = temp; + gtHi--; + unHi--; + continue; + }; + if (n < 0) { + break; + } + unHi--; + } + if (unLo > unHi) { + break; + } + int tempx = zptr[unLo]; + zptr[unLo] = zptr[unHi]; + zptr[unHi] = tempx; + unLo++; + unHi--; + } + + if (gtHi < ltLo) { + stack[sp].ll = lo; + stack[sp].hh = hi; + stack[sp].dd = d + 1; + sp++; + continue; + } + + n = ((ltLo - lo) < (unLo - ltLo)) ? (ltLo - lo) : (unLo - ltLo); + Vswap(lo, unLo - n, n); + m = ((hi - gtHi) < (gtHi - unHi)) ? (hi - gtHi) : (gtHi - unHi); + Vswap(unLo, hi - m + 1, m); + + n = lo + unLo - ltLo - 1; + m = hi - (gtHi - unHi) + 1; + + stack[sp].ll = lo; + stack[sp].hh = n; + stack[sp].dd = d; + sp++; + + stack[sp].ll = n + 1; + stack[sp].hh = m - 1; + stack[sp].dd = d + 1; + sp++; + + stack[sp].ll = m; + stack[sp].hh = hi; + stack[sp].dd = d; + sp++; + } + } + + private void MainSort() { + int i, j, ss, sb; + int[] runningOrder = new int[256]; + int[] copy = new int[256]; + bool[] bigDone = new bool[256]; + int c1, c2; + int numQSorted; + + /* + In the various block-sized structures, live data runs + from 0 to last+NUM_OVERSHOOT_BYTES inclusive. First, + set up the overshoot area for block. + */ + + // if (verbosity >= 4) fprintf ( stderr, " sort initialise ...\n" ); + for (i = 0; i < BZip2Constants.NUM_OVERSHOOT_BYTES; i++) { + block[last + i + 2] = block[(i % (last + 1)) + 1]; + } + for (i = 0; i <= last + BZip2Constants.NUM_OVERSHOOT_BYTES; i++) { + quadrant[i] = 0; + } + + block[0] = (char) (block[last + 1]); + + if (last < 4000) { + /* + Use SimpleSort(), since the full sorting mechanism + has quite a large constant overhead. + */ + for (i = 0; i <= last; i++) { + zptr[i] = i; + } + firstAttempt = false; + workDone = workLimit = 0; + SimpleSort(0, last, 0); + } else { + numQSorted = 0; + for (i = 0; i <= 255; i++) { + bigDone[i] = false; + } + + for (i = 0; i <= 65536; i++) { + ftab[i] = 0; + } + + c1 = block[0]; + for (i = 0; i <= last; i++) { + c2 = block[i + 1]; + ftab[(c1 << 8) + c2]++; + c1 = c2; + } + + for (i = 1; i <= 65536; i++) { + ftab[i] += ftab[i - 1]; + } + + c1 = block[1]; + for (i = 0; i < last; i++) { + c2 = block[i + 2]; + j = (c1 << 8) + c2; + c1 = c2; + ftab[j]--; + zptr[ftab[j]] = i; + } + + j = ((block[last + 1]) << 8) + (block[1]); + ftab[j]--; + zptr[ftab[j]] = last; + + /* + Now ftab contains the first loc of every small bucket. + Calculate the running order, from smallest to largest + big bucket. + */ + + for (i = 0; i <= 255; i++) { + runningOrder[i] = i; + } + + { + int vv; + int h = 1; + do { + h = 3 * h + 1; + } + while (h <= 256); + do { + h = h / 3; + for (i = h; i <= 255; i++) { + vv = runningOrder[i]; + j = i; + while ((ftab[((runningOrder[j - h]) + 1) << 8] + - ftab[(runningOrder[j - h]) << 8]) > + (ftab[((vv) + 1) << 8] - ftab[(vv) << 8])) { + runningOrder[j] = runningOrder[j - h]; + j = j - h; + if (j <= (h - 1)) { + break; + } + } + runningOrder[j] = vv; + } + } while (h != 1); + } + + /* + The main sorting loop. + */ + for (i = 0; i <= 255; i++) { + + /* + Process big buckets, starting with the least full. + */ + ss = runningOrder[i]; + + /* + Complete the big bucket [ss] by quicksorting + any unsorted small buckets [ss, j]. Hopefully + previous pointer-scanning phases have already + completed many of the small buckets [ss, j], so + we don't have to sort them at all. + */ + for (j = 0; j <= 255; j++) { + sb = (ss << 8) + j; + if (!((ftab[sb] & SETMASK) == SETMASK)) { + int lo = ftab[sb] & CLEARMASK; + int hi = (ftab[sb + 1] & CLEARMASK) - 1; + if (hi > lo) { + QSort3(lo, hi, 2); + numQSorted += (hi - lo + 1); + if (workDone > workLimit && firstAttempt) { + return; + } + } + ftab[sb] |= SETMASK; + } + } + + /* + The ss big bucket is now done. Record this fact, + and update the quadrant descriptors. Remember to + update quadrants in the overshoot area too, if + necessary. The "if (i < 255)" test merely skips + this updating for the last bucket processed, since + updating for the last bucket is pointless. + */ + bigDone[ss] = true; + + if (i < 255) { + int bbStart = ftab[ss << 8] & CLEARMASK; + int bbSize = (ftab[(ss + 1) << 8] & CLEARMASK) - bbStart; + int shifts = 0; + + while ((bbSize >> shifts) > 65534) { + shifts++; + } + + for (j = 0; j < bbSize; j++) { + int a2update = zptr[bbStart + j]; + int qVal = (j >> shifts); + quadrant[a2update] = qVal; + if (a2update < BZip2Constants.NUM_OVERSHOOT_BYTES) { + quadrant[a2update + last + 1] = qVal; + } + } + + if (!(((bbSize - 1) >> shifts) <= 65535)) { + Panic(); + } + } + + /* + Now scan this big bucket so as to synthesise the + sorted order for small buckets [t, ss] for all t != ss. + */ + for (j = 0; j <= 255; j++) { + copy[j] = ftab[(j << 8) + ss] & CLEARMASK; + } + + for (j = ftab[ss << 8] & CLEARMASK; + j < (ftab[(ss + 1) << 8] & CLEARMASK); j++) { + c1 = block[zptr[j]]; + if (!bigDone[c1]) { + zptr[copy[c1]] = zptr[j] == 0 ? last : zptr[j] - 1; + copy[c1]++; + } + } + + for (j = 0; j <= 255; j++) { + ftab[(j << 8) + ss] |= SETMASK; + } + } + } + } + + private void RandomiseBlock() { + int i; + int rNToGo = 0; + int rTPos = 0; + for (i = 0; i < 256; i++) { + inUse[i] = false; + } + + for (i = 0; i <= last; i++) { + if (rNToGo == 0) { + rNToGo = (char) BZip2Constants.rNums[rTPos]; + rTPos++; + if (rTPos == 512) { + rTPos = 0; + } + } + rNToGo--; + block[i + 1] ^= (char)((rNToGo == 1) ? 1 : 0); + // handle 16 bit signed numbers + block[i + 1] &= (char)0xFF; + + inUse[block[i + 1]] = true; + } + } + + private void DoReversibleTransformation() { + int i; + + workLimit = workFactor * last; + workDone = 0; + blockRandomised = false; + firstAttempt = true; + + MainSort(); + + if (workDone > workLimit && firstAttempt) { + RandomiseBlock(); + workLimit = workDone = 0; + blockRandomised = true; + firstAttempt = false; + MainSort(); + } + + origPtr = -1; + for (i = 0; i <= last; i++) { + if (zptr[i] == 0) { + origPtr = i; + break; + } + }; + + if (origPtr == -1) { + Panic(); + } + } + + private bool FullGtU(int i1, int i2) { + int k; + char c1, c2; + int s1, s2; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) { + return (c1 > c2); + } + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) { + return (c1 > c2); + } + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) { + return (c1 > c2); + } + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) { + return (c1 > c2); + } + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) { + return (c1 > c2); + } + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) { + return (c1 > c2); + } + i1++; + i2++; + + k = last + 1; + + do { + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) { + return (c1 > c2); + } + s1 = quadrant[i1]; + s2 = quadrant[i2]; + if (s1 != s2) { + return (s1 > s2); + } + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) { + return (c1 > c2); + } + s1 = quadrant[i1]; + s2 = quadrant[i2]; + if (s1 != s2) { + return (s1 > s2); + } + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) { + return (c1 > c2); + } + s1 = quadrant[i1]; + s2 = quadrant[i2]; + if (s1 != s2) { + return (s1 > s2); + } + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) { + return (c1 > c2); + } + s1 = quadrant[i1]; + s2 = quadrant[i2]; + if (s1 != s2) { + return (s1 > s2); + } + i1++; + i2++; + + if (i1 > last) { + i1 -= last; + i1--; + }; + if (i2 > last) { + i2 -= last; + i2--; + }; + + k -= 4; + workDone++; + } while (k >= 0); + + return false; + } + + /* + Knuth's increments seem to work better + than Incerpi-Sedgewick here. Possibly + because the number of elems to sort is + usually small, typically <= 20. + */ + private int[] incs = { 1, 4, 13, 40, 121, 364, 1093, 3280, + 9841, 29524, 88573, 265720, + 797161, 2391484 }; + + private void AllocateCompressStructures() { + int n = BZip2Constants.baseBlockSize * blockSize100k; + block = new char[(n + 1 + BZip2Constants.NUM_OVERSHOOT_BYTES)]; + quadrant = new int[(n + BZip2Constants.NUM_OVERSHOOT_BYTES)]; + zptr = new int[n]; + ftab = new int[65537]; + + if (block == null || quadrant == null || zptr == null + || ftab == null) { + //int totalDraw = (n + 1 + NUM_OVERSHOOT_BYTES) + (n + NUM_OVERSHOOT_BYTES) + n + 65537; + //compressOutOfMemory ( totalDraw, n ); + } + + /* + The back end needs a place to store the MTF values + whilst it calculates the coding tables. We could + put them in the zptr array. However, these values + will fit in a short, so we overlay szptr at the + start of zptr, in the hope of reducing the number + of cache misses induced by the multiple traversals + of the MTF values when calculating coding tables. + Seems to improve compression speed by about 1%. + */ + // szptr = zptr; + + + szptr = new short[2 * n]; + } + + private void GenerateMTFValues() { + char[] yy = new char[256]; + int i, j; + char tmp; + char tmp2; + int zPend; + int wr; + int EOB; + + MakeMaps(); + EOB = nInUse + 1; + + for (i = 0; i <= EOB; i++) { + mtfFreq[i] = 0; + } + + wr = 0; + zPend = 0; + for (i = 0; i < nInUse; i++) { + yy[i] = (char) i; + } + + + for (i = 0; i <= last; i++) { + char ll_i; + + ll_i = unseqToSeq[block[zptr[i]]]; + + j = 0; + tmp = yy[j]; + while (ll_i != tmp) { + j++; + tmp2 = tmp; + tmp = yy[j]; + yy[j] = tmp2; + }; + yy[0] = tmp; + + if (j == 0) { + zPend++; + } else { + if (zPend > 0) { + zPend--; + while (true) { + switch (zPend % 2) { + case 0: + szptr[wr] = (short) BZip2Constants.RUNA; + wr++; + mtfFreq[BZip2Constants.RUNA]++; + break; + case 1: + szptr[wr] = (short) BZip2Constants.RUNB; + wr++; + mtfFreq[BZip2Constants.RUNB]++; + break; + }; + if (zPend < 2) { + break; + } + zPend = (zPend - 2) / 2; + }; + zPend = 0; + } + szptr[wr] = (short) (j + 1); + wr++; + mtfFreq[j + 1]++; + } + } + + if (zPend > 0) { + zPend--; + while (true) { + switch (zPend % 2) { + case 0: + szptr[wr] = (short) BZip2Constants.RUNA; + wr++; + mtfFreq[BZip2Constants.RUNA]++; + break; + case 1: + szptr[wr] = (short) BZip2Constants.RUNB; + wr++; + mtfFreq[BZip2Constants.RUNB]++; + break; + } + if (zPend < 2) { + break; + } + zPend = (zPend - 2) / 2; + } + } + + szptr[wr] = (short) EOB; + wr++; + mtfFreq[EOB]++; + + nMTF = wr; + } + + public override int Read(byte[] buffer, int offset, int count) { + return 0; + } + + public override long Seek(long offset, SeekOrigin origin) { + return 0; + } + + public override void SetLength(long value) { + } + + public override void Write(byte[] buffer, int offset, int count) { + for (int k = 0; k < count; ++k) { + WriteByte(buffer[k + offset]); + } + } + + public override bool CanRead { + get { + return false; + } + } + + public override bool CanSeek { + get { + return false; + } + } + + public override bool CanWrite { + get { + return true; + } + } + + public override long Length { + get { + return 0; + } + } + + public override long Position { + get { + return 0; + } + set { + } + } + } +} \ No newline at end of file diff --git a/bc-sharp-crypto/bzip2/src/CRC.cs b/bc-sharp-crypto/bzip2/src/CRC.cs new file mode 100644 index 0000000..278a9f3 --- /dev/null +++ b/bc-sharp-crypto/bzip2/src/CRC.cs @@ -0,0 +1,134 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +/* + * This package is based on the work done by Keiron Liddle), Aftex Software + * to whom the Ant project is very grateful for his + * great code. + */ + +using System; + +namespace Org.BouncyCastle.Apache.Bzip2 +{ + /** + * A simple class the hold and calculate the CRC for sanity checking + * of the data. + * + * @author Keiron Liddle + */ + internal class CRC + { + public static readonly int[] crc32Table = { + unchecked((int)0x00000000), unchecked((int)0x04c11db7), unchecked((int)0x09823b6e), unchecked((int)0x0d4326d9), + unchecked((int)0x130476dc), unchecked((int)0x17c56b6b), unchecked((int)0x1a864db2), unchecked((int)0x1e475005), + unchecked((int)0x2608edb8), unchecked((int)0x22c9f00f), unchecked((int)0x2f8ad6d6), unchecked((int)0x2b4bcb61), + unchecked((int)0x350c9b64), unchecked((int)0x31cd86d3), unchecked((int)0x3c8ea00a), unchecked((int)0x384fbdbd), + unchecked((int)0x4c11db70), unchecked((int)0x48d0c6c7), unchecked((int)0x4593e01e), unchecked((int)0x4152fda9), + unchecked((int)0x5f15adac), unchecked((int)0x5bd4b01b), unchecked((int)0x569796c2), unchecked((int)0x52568b75), + unchecked((int)0x6a1936c8), unchecked((int)0x6ed82b7f), unchecked((int)0x639b0da6), unchecked((int)0x675a1011), + unchecked((int)0x791d4014), unchecked((int)0x7ddc5da3), unchecked((int)0x709f7b7a), unchecked((int)0x745e66cd), + unchecked((int)0x9823b6e0), unchecked((int)0x9ce2ab57), unchecked((int)0x91a18d8e), unchecked((int)0x95609039), + unchecked((int)0x8b27c03c), unchecked((int)0x8fe6dd8b), unchecked((int)0x82a5fb52), unchecked((int)0x8664e6e5), + unchecked((int)0xbe2b5b58), unchecked((int)0xbaea46ef), unchecked((int)0xb7a96036), unchecked((int)0xb3687d81), + unchecked((int)0xad2f2d84), unchecked((int)0xa9ee3033), unchecked((int)0xa4ad16ea), unchecked((int)0xa06c0b5d), + unchecked((int)0xd4326d90), unchecked((int)0xd0f37027), unchecked((int)0xddb056fe), unchecked((int)0xd9714b49), + unchecked((int)0xc7361b4c), unchecked((int)0xc3f706fb), unchecked((int)0xceb42022), unchecked((int)0xca753d95), + unchecked((int)0xf23a8028), unchecked((int)0xf6fb9d9f), unchecked((int)0xfbb8bb46), unchecked((int)0xff79a6f1), + unchecked((int)0xe13ef6f4), unchecked((int)0xe5ffeb43), unchecked((int)0xe8bccd9a), unchecked((int)0xec7dd02d), + unchecked((int)0x34867077), unchecked((int)0x30476dc0), unchecked((int)0x3d044b19), unchecked((int)0x39c556ae), + unchecked((int)0x278206ab), unchecked((int)0x23431b1c), unchecked((int)0x2e003dc5), unchecked((int)0x2ac12072), + unchecked((int)0x128e9dcf), unchecked((int)0x164f8078), unchecked((int)0x1b0ca6a1), unchecked((int)0x1fcdbb16), + unchecked((int)0x018aeb13), unchecked((int)0x054bf6a4), unchecked((int)0x0808d07d), unchecked((int)0x0cc9cdca), + unchecked((int)0x7897ab07), unchecked((int)0x7c56b6b0), unchecked((int)0x71159069), unchecked((int)0x75d48dde), + unchecked((int)0x6b93dddb), unchecked((int)0x6f52c06c), unchecked((int)0x6211e6b5), unchecked((int)0x66d0fb02), + unchecked((int)0x5e9f46bf), unchecked((int)0x5a5e5b08), unchecked((int)0x571d7dd1), unchecked((int)0x53dc6066), + unchecked((int)0x4d9b3063), unchecked((int)0x495a2dd4), unchecked((int)0x44190b0d), unchecked((int)0x40d816ba), + unchecked((int)0xaca5c697), unchecked((int)0xa864db20), unchecked((int)0xa527fdf9), unchecked((int)0xa1e6e04e), + unchecked((int)0xbfa1b04b), unchecked((int)0xbb60adfc), unchecked((int)0xb6238b25), unchecked((int)0xb2e29692), + unchecked((int)0x8aad2b2f), unchecked((int)0x8e6c3698), unchecked((int)0x832f1041), unchecked((int)0x87ee0df6), + unchecked((int)0x99a95df3), unchecked((int)0x9d684044), unchecked((int)0x902b669d), unchecked((int)0x94ea7b2a), + unchecked((int)0xe0b41de7), unchecked((int)0xe4750050), unchecked((int)0xe9362689), unchecked((int)0xedf73b3e), + unchecked((int)0xf3b06b3b), unchecked((int)0xf771768c), unchecked((int)0xfa325055), unchecked((int)0xfef34de2), + unchecked((int)0xc6bcf05f), unchecked((int)0xc27dede8), unchecked((int)0xcf3ecb31), unchecked((int)0xcbffd686), + unchecked((int)0xd5b88683), unchecked((int)0xd1799b34), unchecked((int)0xdc3abded), unchecked((int)0xd8fba05a), + unchecked((int)0x690ce0ee), unchecked((int)0x6dcdfd59), unchecked((int)0x608edb80), unchecked((int)0x644fc637), + unchecked((int)0x7a089632), unchecked((int)0x7ec98b85), unchecked((int)0x738aad5c), unchecked((int)0x774bb0eb), + unchecked((int)0x4f040d56), unchecked((int)0x4bc510e1), unchecked((int)0x46863638), unchecked((int)0x42472b8f), + unchecked((int)0x5c007b8a), unchecked((int)0x58c1663d), unchecked((int)0x558240e4), unchecked((int)0x51435d53), + unchecked((int)0x251d3b9e), unchecked((int)0x21dc2629), unchecked((int)0x2c9f00f0), unchecked((int)0x285e1d47), + unchecked((int)0x36194d42), unchecked((int)0x32d850f5), unchecked((int)0x3f9b762c), unchecked((int)0x3b5a6b9b), + unchecked((int)0x0315d626), unchecked((int)0x07d4cb91), unchecked((int)0x0a97ed48), unchecked((int)0x0e56f0ff), + unchecked((int)0x1011a0fa), unchecked((int)0x14d0bd4d), unchecked((int)0x19939b94), unchecked((int)0x1d528623), + unchecked((int)0xf12f560e), unchecked((int)0xf5ee4bb9), unchecked((int)0xf8ad6d60), unchecked((int)0xfc6c70d7), + unchecked((int)0xe22b20d2), unchecked((int)0xe6ea3d65), unchecked((int)0xeba91bbc), unchecked((int)0xef68060b), + unchecked((int)0xd727bbb6), unchecked((int)0xd3e6a601), unchecked((int)0xdea580d8), unchecked((int)0xda649d6f), + unchecked((int)0xc423cd6a), unchecked((int)0xc0e2d0dd), unchecked((int)0xcda1f604), unchecked((int)0xc960ebb3), + unchecked((int)0xbd3e8d7e), unchecked((int)0xb9ff90c9), unchecked((int)0xb4bcb610), unchecked((int)0xb07daba7), + unchecked((int)0xae3afba2), unchecked((int)0xaafbe615), unchecked((int)0xa7b8c0cc), unchecked((int)0xa379dd7b), + unchecked((int)0x9b3660c6), unchecked((int)0x9ff77d71), unchecked((int)0x92b45ba8), unchecked((int)0x9675461f), + unchecked((int)0x8832161a), unchecked((int)0x8cf30bad), unchecked((int)0x81b02d74), unchecked((int)0x857130c3), + unchecked((int)0x5d8a9099), unchecked((int)0x594b8d2e), unchecked((int)0x5408abf7), unchecked((int)0x50c9b640), + unchecked((int)0x4e8ee645), unchecked((int)0x4a4ffbf2), unchecked((int)0x470cdd2b), unchecked((int)0x43cdc09c), + unchecked((int)0x7b827d21), unchecked((int)0x7f436096), unchecked((int)0x7200464f), unchecked((int)0x76c15bf8), + unchecked((int)0x68860bfd), unchecked((int)0x6c47164a), unchecked((int)0x61043093), unchecked((int)0x65c52d24), + unchecked((int)0x119b4be9), unchecked((int)0x155a565e), unchecked((int)0x18197087), unchecked((int)0x1cd86d30), + unchecked((int)0x029f3d35), unchecked((int)0x065e2082), unchecked((int)0x0b1d065b), unchecked((int)0x0fdc1bec), + unchecked((int)0x3793a651), unchecked((int)0x3352bbe6), unchecked((int)0x3e119d3f), unchecked((int)0x3ad08088), + unchecked((int)0x2497d08d), unchecked((int)0x2056cd3a), unchecked((int)0x2d15ebe3), unchecked((int)0x29d4f654), + unchecked((int)0xc5a92679), unchecked((int)0xc1683bce), unchecked((int)0xcc2b1d17), unchecked((int)0xc8ea00a0), + unchecked((int)0xd6ad50a5), unchecked((int)0xd26c4d12), unchecked((int)0xdf2f6bcb), unchecked((int)0xdbee767c), + unchecked((int)0xe3a1cbc1), unchecked((int)0xe760d676), unchecked((int)0xea23f0af), unchecked((int)0xeee2ed18), + unchecked((int)0xf0a5bd1d), unchecked((int)0xf464a0aa), unchecked((int)0xf9278673), unchecked((int)0xfde69bc4), + unchecked((int)0x89b8fd09), unchecked((int)0x8d79e0be), unchecked((int)0x803ac667), unchecked((int)0x84fbdbd0), + unchecked((int)0x9abc8bd5), unchecked((int)0x9e7d9662), unchecked((int)0x933eb0bb), unchecked((int)0x97ffad0c), + unchecked((int)0xafb010b1), unchecked((int)0xab710d06), unchecked((int)0xa6322bdf), unchecked((int)0xa2f33668), + unchecked((int)0xbcb4666d), unchecked((int)0xb8757bda), unchecked((int)0xb5365d03), unchecked((int)0xb1f740b4) + }; + + public CRC() { + InitialiseCRC(); + } + + internal void InitialiseCRC() { + globalCrc = unchecked((int)0xffffffff); + } + + internal int GetFinalCRC() { + return ~globalCrc; + } + + internal int GetGlobalCRC() { + return globalCrc; + } + + internal void SetGlobalCRC(int newCrc) { + globalCrc = newCrc; + } + + internal void UpdateCRC(int inCh) { + int temp = (globalCrc >> 24) ^ inCh; + if (temp < 0) { + temp = 256 + temp; + } + globalCrc = (globalCrc << 8) ^ CRC.crc32Table[temp]; + } + + internal int globalCrc; + } +} \ No newline at end of file diff --git a/bc-sharp-crypto/src/asn1/ASN1Generator.cs b/bc-sharp-crypto/src/asn1/ASN1Generator.cs new file mode 100644 index 0000000..e560517 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/ASN1Generator.cs @@ -0,0 +1,27 @@ +using System.Collections; +using System.IO; + +namespace Org.BouncyCastle.Asn1 +{ + public abstract class Asn1Generator + { + private Stream _out; + + protected Asn1Generator( + Stream outStream) + { + _out = outStream; + } + + protected Stream Out + { + get { return _out; } + } + + public abstract void AddObject(Asn1Encodable obj); + + public abstract Stream GetRawOutputStream(); + + public abstract void Close(); + } +} diff --git a/bc-sharp-crypto/src/asn1/ASN1OctetStringParser.cs b/bc-sharp-crypto/src/asn1/ASN1OctetStringParser.cs new file mode 100644 index 0000000..5815aa4 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/ASN1OctetStringParser.cs @@ -0,0 +1,10 @@ +using System.IO; + +namespace Org.BouncyCastle.Asn1 +{ + public interface Asn1OctetStringParser + : IAsn1Convertible + { + Stream GetOctetStream(); + } +} diff --git a/bc-sharp-crypto/src/asn1/ASN1SequenceParser.cs b/bc-sharp-crypto/src/asn1/ASN1SequenceParser.cs new file mode 100644 index 0000000..9e88ac7 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/ASN1SequenceParser.cs @@ -0,0 +1,8 @@ +namespace Org.BouncyCastle.Asn1 +{ + public interface Asn1SequenceParser + : IAsn1Convertible + { + IAsn1Convertible ReadObject(); + } +} diff --git a/bc-sharp-crypto/src/asn1/ASN1SetParser.cs b/bc-sharp-crypto/src/asn1/ASN1SetParser.cs new file mode 100644 index 0000000..d1b9c64 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/ASN1SetParser.cs @@ -0,0 +1,8 @@ +namespace Org.BouncyCastle.Asn1 +{ + public interface Asn1SetParser + : IAsn1Convertible + { + IAsn1Convertible ReadObject(); + } +} diff --git a/bc-sharp-crypto/src/asn1/ASN1StreamParser.cs b/bc-sharp-crypto/src/asn1/ASN1StreamParser.cs new file mode 100644 index 0000000..0c6b441 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/ASN1StreamParser.cs @@ -0,0 +1,234 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Asn1 +{ + public class Asn1StreamParser + { + private readonly Stream _in; + private readonly int _limit; + + private readonly byte[][] tmpBuffers; + + public Asn1StreamParser( + Stream inStream) + : this(inStream, Asn1InputStream.FindLimit(inStream)) + { + } + + public Asn1StreamParser( + Stream inStream, + int limit) + { + if (!inStream.CanRead) + throw new ArgumentException("Expected stream to be readable", "inStream"); + + this._in = inStream; + this._limit = limit; + this.tmpBuffers = new byte[16][]; + } + + public Asn1StreamParser( + byte[] encoding) + : this(new MemoryStream(encoding, false), encoding.Length) + { + } + + internal IAsn1Convertible ReadIndef(int tagValue) + { + // Note: INDEF => CONSTRUCTED + + // TODO There are other tags that may be constructed (e.g. BIT_STRING) + switch (tagValue) + { + case Asn1Tags.External: + return new DerExternalParser(this); + case Asn1Tags.OctetString: + return new BerOctetStringParser(this); + case Asn1Tags.Sequence: + return new BerSequenceParser(this); + case Asn1Tags.Set: + return new BerSetParser(this); + default: + throw new Asn1Exception("unknown BER object encountered: 0x" + tagValue.ToString("X")); + } + } + + internal IAsn1Convertible ReadImplicit(bool constructed, int tag) + { + if (_in is IndefiniteLengthInputStream) + { + if (!constructed) + throw new IOException("indefinite length primitive encoding encountered"); + + return ReadIndef(tag); + } + + if (constructed) + { + switch (tag) + { + case Asn1Tags.Set: + return new DerSetParser(this); + case Asn1Tags.Sequence: + return new DerSequenceParser(this); + case Asn1Tags.OctetString: + return new BerOctetStringParser(this); + } + } + else + { + switch (tag) + { + case Asn1Tags.Set: + throw new Asn1Exception("sequences must use constructed encoding (see X.690 8.9.1/8.10.1)"); + case Asn1Tags.Sequence: + throw new Asn1Exception("sets must use constructed encoding (see X.690 8.11.1/8.12.1)"); + case Asn1Tags.OctetString: + return new DerOctetStringParser((DefiniteLengthInputStream)_in); + } + } + + throw new Asn1Exception("implicit tagging not implemented"); + } + + internal Asn1Object ReadTaggedObject(bool constructed, int tag) + { + if (!constructed) + { + // Note: !CONSTRUCTED => IMPLICIT + DefiniteLengthInputStream defIn = (DefiniteLengthInputStream)_in; + return new DerTaggedObject(false, tag, new DerOctetString(defIn.ToArray())); + } + + Asn1EncodableVector v = ReadVector(); + + if (_in is IndefiniteLengthInputStream) + { + return v.Count == 1 + ? new BerTaggedObject(true, tag, v[0]) + : new BerTaggedObject(false, tag, BerSequence.FromVector(v)); + } + + return v.Count == 1 + ? new DerTaggedObject(true, tag, v[0]) + : new DerTaggedObject(false, tag, DerSequence.FromVector(v)); + } + + public virtual IAsn1Convertible ReadObject() + { + int tag = _in.ReadByte(); + 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); + + bool isConstructed = (tag & Asn1Tags.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, _limit); + Asn1StreamParser sp = new Asn1StreamParser(indIn, _limit); + + if ((tag & Asn1Tags.Application) != 0) + { + return new BerApplicationSpecificParser(tagNo, sp); + } + + if ((tag & Asn1Tags.Tagged) != 0) + { + return new BerTaggedObjectParser(true, tagNo, sp); + } + + return sp.ReadIndef(tagNo); + } + else + { + DefiniteLengthInputStream defIn = new DefiniteLengthInputStream(_in, length); + + if ((tag & Asn1Tags.Application) != 0) + { + return new DerApplicationSpecific(isConstructed, tagNo, defIn.ToArray()); + } + + if ((tag & Asn1Tags.Tagged) != 0) + { + return new BerTaggedObjectParser(isConstructed, tagNo, new Asn1StreamParser(defIn)); + } + + if (isConstructed) + { + // TODO There are other tags that may be constructed (e.g. BitString) + switch (tagNo) + { + case Asn1Tags.OctetString: + // + // yes, people actually do this... + // + return new BerOctetStringParser(new Asn1StreamParser(defIn)); + case Asn1Tags.Sequence: + return new DerSequenceParser(new Asn1StreamParser(defIn)); + case Asn1Tags.Set: + return new DerSetParser(new Asn1StreamParser(defIn)); + case Asn1Tags.External: + return new DerExternalParser(new Asn1StreamParser(defIn)); + default: + throw new IOException("unknown tag " + tagNo + " encountered"); + } + } + + // Some primitive encodings can be handled by parsers too... + switch (tagNo) + { + case Asn1Tags.OctetString: + return new DerOctetStringParser(defIn); + } + + try + { + return Asn1InputStream.CreatePrimitiveDerObject(tagNo, defIn, tmpBuffers); + } + catch (ArgumentException e) + { + throw new Asn1Exception("corrupted stream detected", e); + } + } + } + + private void Set00Check( + bool enabled) + { + if (_in is IndefiniteLengthInputStream) + { + ((IndefiniteLengthInputStream) _in).SetEofOn00(enabled); + } + } + + internal Asn1EncodableVector ReadVector() + { + Asn1EncodableVector v = new Asn1EncodableVector(); + + IAsn1Convertible obj; + while ((obj = ReadObject()) != null) + { + v.Add(obj.ToAsn1Object()); + } + + return v; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/ASN1TaggedObjectParser.cs b/bc-sharp-crypto/src/asn1/ASN1TaggedObjectParser.cs new file mode 100644 index 0000000..32327a2 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/ASN1TaggedObjectParser.cs @@ -0,0 +1,10 @@ +namespace Org.BouncyCastle.Asn1 +{ + public interface Asn1TaggedObjectParser + : IAsn1Convertible + { + int TagNo { get; } + + IAsn1Convertible GetObjectParser(int tag, bool isExplicit); + } +} diff --git a/bc-sharp-crypto/src/asn1/Asn1Encodable.cs b/bc-sharp-crypto/src/asn1/Asn1Encodable.cs new file mode 100644 index 0000000..e3dd9a1 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/Asn1Encodable.cs @@ -0,0 +1,78 @@ +using System.IO; + +namespace Org.BouncyCastle.Asn1 +{ + public abstract class Asn1Encodable + : IAsn1Convertible + { + public const string Der = "DER"; + public const string Ber = "BER"; + + public byte[] GetEncoded() + { + MemoryStream bOut = new MemoryStream(); + Asn1OutputStream aOut = new Asn1OutputStream(bOut); + + aOut.WriteObject(this); + + return bOut.ToArray(); + } + + public byte[] GetEncoded( + string encoding) + { + if (encoding.Equals(Der)) + { + MemoryStream bOut = new MemoryStream(); + DerOutputStream dOut = new DerOutputStream(bOut); + + dOut.WriteObject(this); + + return bOut.ToArray(); + } + + return 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 GetEncoded(Der); + } + catch (IOException) + { + return null; + } + } + + public sealed override int GetHashCode() + { + return ToAsn1Object().CallAsn1GetHashCode(); + } + + public sealed override bool Equals( + object obj) + { + if (obj == this) + return true; + + IAsn1Convertible other = obj as IAsn1Convertible; + + if (other == null) + return false; + + Asn1Object o1 = ToAsn1Object(); + Asn1Object o2 = other.ToAsn1Object(); + + return o1 == o2 || o1.CallAsn1Equals(o2); + } + + public abstract Asn1Object ToAsn1Object(); + } +} diff --git a/bc-sharp-crypto/src/asn1/Asn1EncodableVector.cs b/bc-sharp-crypto/src/asn1/Asn1EncodableVector.cs new file mode 100644 index 0000000..49532fe --- /dev/null +++ b/bc-sharp-crypto/src/asn1/Asn1EncodableVector.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1 +{ + public class Asn1EncodableVector + : IEnumerable + { + private IList v = Platform.CreateArrayList(); + + public static Asn1EncodableVector FromEnumerable( + IEnumerable e) + { + Asn1EncodableVector v = new Asn1EncodableVector(); + foreach (Asn1Encodable obj in e) + { + v.Add(obj); + } + return v; + } + +// public Asn1EncodableVector() +// { +// } + + public Asn1EncodableVector( + params Asn1Encodable[] v) + { + Add(v); + } + +// public void Add( +// Asn1Encodable obj) +// { +// v.Add(obj); +// } + + public void Add( + params Asn1Encodable[] objs) + { + foreach (Asn1Encodable obj in objs) + { + v.Add(obj); + } + } + + public void AddOptional( + params Asn1Encodable[] objs) + { + if (objs != null) + { + foreach (Asn1Encodable obj in objs) + { + if (obj != null) + { + v.Add(obj); + } + } + } + } + + public Asn1Encodable this[ + int index] + { + get { return (Asn1Encodable) v[index]; } + } + + [Obsolete("Use 'object[index]' syntax instead")] + public Asn1Encodable Get( + int index) + { + return this[index]; + } + + [Obsolete("Use 'Count' property instead")] + public int Size + { + get { return v.Count; } + } + + public int Count + { + get { return v.Count; } + } + + public IEnumerator GetEnumerator() + { + return v.GetEnumerator(); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/Asn1Exception.cs b/bc-sharp-crypto/src/asn1/Asn1Exception.cs new file mode 100644 index 0000000..1dfe173 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/Asn1Exception.cs @@ -0,0 +1,30 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Asn1 +{ +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT || PORTABLE) + [Serializable] +#endif + public class Asn1Exception + : IOException + { + public Asn1Exception() + : base() + { + } + + public Asn1Exception( + string message) + : base(message) + { + } + + public Asn1Exception( + string message, + Exception exception) + : base(message, exception) + { + } + } +} diff --git a/bc-sharp-crypto/src/asn1/Asn1InputStream.cs b/bc-sharp-crypto/src/asn1/Asn1InputStream.cs new file mode 100644 index 0000000..a94ae52 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/Asn1InputStream.cs @@ -0,0 +1,371 @@ +using System; +using System.Diagnostics; +using System.IO; + +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Asn1 +{ + /** + * 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 + : FilterStream + { + private readonly int limit; + + private readonly byte[][] tmpBuffers; + + internal static int FindLimit(Stream input) + { + if (input is LimitedInputStream) + { + return ((LimitedInputStream)input).GetRemaining(); + } + else if (input is MemoryStream) + { + MemoryStream mem = (MemoryStream)input; + return (int)(mem.Length - mem.Position); + } + + return int.MaxValue; + } + + public Asn1InputStream( + Stream inputStream) + : this(inputStream, FindLimit(inputStream)) + { + } + + /** + * 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( + Stream inputStream, + int limit) + : base(inputStream) + { + this.limit = limit; + this.tmpBuffers = new byte[16][]; + } + + /** + * 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 MemoryStream(input, false), input.Length) + { + } + + /** + * build an object given its tag and the number of bytes to construct it from. + */ + private Asn1Object BuildObject( + int tag, + int tagNo, + int length) + { + bool isConstructed = (tag & Asn1Tags.Constructed) != 0; + + DefiniteLengthInputStream defIn = new DefiniteLengthInputStream(this.s, length); + + if ((tag & Asn1Tags.Application) != 0) + { + return new DerApplicationSpecific(isConstructed, tagNo, defIn.ToArray()); + } + + if ((tag & Asn1Tags.Tagged) != 0) + { + return new Asn1StreamParser(defIn).ReadTaggedObject(isConstructed, tagNo); + } + + if (isConstructed) + { + // TODO There are other tags that may be constructed (e.g. BitString) + switch (tagNo) + { + case Asn1Tags.OctetString: + // + // yes, people actually do this... + // + return new BerOctetString(BuildDerEncodableVector(defIn)); + case Asn1Tags.Sequence: + return CreateDerSequence(defIn); + case Asn1Tags.Set: + return CreateDerSet(defIn); + case Asn1Tags.External: + return new DerExternal(BuildDerEncodableVector(defIn)); + default: + throw new IOException("unknown tag " + tagNo + " encountered"); + } + } + + return CreatePrimitiveDerObject(tagNo, defIn, tmpBuffers); + } + + internal Asn1EncodableVector BuildEncodableVector() + { + Asn1EncodableVector v = new Asn1EncodableVector(); + + Asn1Object o; + while ((o = ReadObject()) != null) + { + v.Add(o); + } + + return v; + } + + internal virtual Asn1EncodableVector BuildDerEncodableVector( + DefiniteLengthInputStream dIn) + { + return new Asn1InputStream(dIn).BuildEncodableVector(); + } + + internal virtual DerSequence CreateDerSequence( + DefiniteLengthInputStream dIn) + { + return DerSequence.FromVector(BuildDerEncodableVector(dIn)); + } + + internal virtual DerSet CreateDerSet( + DefiniteLengthInputStream dIn) + { + return DerSet.FromVector(BuildDerEncodableVector(dIn), false); + } + + public Asn1Object ReadObject() + { + int tag = ReadByte(); + if (tag <= 0) + { + if (tag == 0) + throw new IOException("unexpected end-of-contents marker"); + + return null; + } + + // + // calculate tag number + // + int tagNo = ReadTagNumber(this.s, tag); + + bool isConstructed = (tag & Asn1Tags.Constructed) != 0; + + // + // calculate length + // + int length = ReadLength(this.s, limit); + + if (length < 0) // indefinite length method + { + if (!isConstructed) + throw new IOException("indefinite length primitive encoding encountered"); + + IndefiniteLengthInputStream indIn = new IndefiniteLengthInputStream(this.s, limit); + Asn1StreamParser sp = new Asn1StreamParser(indIn, limit); + + if ((tag & Asn1Tags.Application) != 0) + { + return new BerApplicationSpecificParser(tagNo, sp).ToAsn1Object(); + } + + if ((tag & Asn1Tags.Tagged) != 0) + { + return new BerTaggedObjectParser(true, tagNo, sp).ToAsn1Object(); + } + + // TODO There are other tags that may be constructed (e.g. BitString) + switch (tagNo) + { + case Asn1Tags.OctetString: + return new BerOctetStringParser(sp).ToAsn1Object(); + case Asn1Tags.Sequence: + return new BerSequenceParser(sp).ToAsn1Object(); + case Asn1Tags.Set: + return new BerSetParser(sp).ToAsn1Object(); + case Asn1Tags.External: + return new DerExternalParser(sp).ToAsn1Object(); + default: + throw new IOException("unknown BER object encountered"); + } + } + else + { + try + { + return BuildObject(tag, tagNo, length); + } + catch (ArgumentException e) + { + throw new Asn1Exception("corrupted stream detected", e); + } + } + } + + internal static int ReadTagNumber( + Stream s, + int tag) + { + 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.ReadByte(); + + // 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.ReadByte(); + } + + if (b < 0) + throw new EndOfStreamException("EOF found inside tag value."); + + tagNo |= (b & 0x7f); + } + + return tagNo; + } + + internal static int ReadLength( + Stream s, + int limit) + { + int length = s.ReadByte(); + if (length < 0) + throw new EndOfStreamException("EOF found when length expected"); + + if (length == 0x80) + return -1; // indefinite-length encoding + + if (length > 127) + { + int size = length & 0x7f; + + // Note: The invalid long form "0xff" (see X.690 8.1.3.5c) will be caught here + if (size > 4) + throw new IOException("DER length more than 4 bytes: " + size); + + length = 0; + for (int i = 0; i < size; i++) + { + int next = s.ReadByte(); + + if (next < 0) + throw new EndOfStreamException("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; + } + + internal static byte[] GetBuffer(DefiniteLengthInputStream defIn, byte[][] tmpBuffers) + { + int len = defIn.GetRemaining(); + if (len >= tmpBuffers.Length) + { + return defIn.ToArray(); + } + + byte[] buf = tmpBuffers[len]; + if (buf == null) + { + buf = tmpBuffers[len] = new byte[len]; + } + + defIn.ReadAllIntoByteArray(buf); + + return buf; + } + + internal static Asn1Object CreatePrimitiveDerObject( + int tagNo, + DefiniteLengthInputStream defIn, + byte[][] tmpBuffers) + { + switch (tagNo) + { + case Asn1Tags.Boolean: + return DerBoolean.FromOctetString(GetBuffer(defIn, tmpBuffers)); + case Asn1Tags.Enumerated: + return DerEnumerated.FromOctetString(GetBuffer(defIn, tmpBuffers)); + case Asn1Tags.ObjectIdentifier: + return DerObjectIdentifier.FromOctetString(GetBuffer(defIn, tmpBuffers)); + } + + byte[] bytes = defIn.ToArray(); + + switch (tagNo) + { + case Asn1Tags.BitString: + return DerBitString.FromAsn1Octets(bytes); + case Asn1Tags.BmpString: + return new DerBmpString(bytes); + case Asn1Tags.GeneralizedTime: + return new DerGeneralizedTime(bytes); + case Asn1Tags.GeneralString: + return new DerGeneralString(bytes); + case Asn1Tags.GraphicString: + return new DerGraphicString(bytes); + case Asn1Tags.IA5String: + return new DerIA5String(bytes); + case Asn1Tags.Integer: + return new DerInteger(bytes); + case Asn1Tags.Null: + return DerNull.Instance; // actual content is ignored (enforce 0 length?) + case Asn1Tags.NumericString: + return new DerNumericString(bytes); + case Asn1Tags.OctetString: + return new DerOctetString(bytes); + case Asn1Tags.PrintableString: + return new DerPrintableString(bytes); + case Asn1Tags.T61String: + return new DerT61String(bytes); + case Asn1Tags.UniversalString: + return new DerUniversalString(bytes); + case Asn1Tags.UtcTime: + return new DerUtcTime(bytes); + case Asn1Tags.Utf8String: + return new DerUtf8String(bytes); + case Asn1Tags.VideotexString: + return new DerVideotexString(bytes); + case Asn1Tags.VisibleString: + return new DerVisibleString(bytes); + default: + throw new IOException("unknown tag " + tagNo + " encountered"); + } + } + } +} diff --git a/bc-sharp-crypto/src/asn1/Asn1Null.cs b/bc-sharp-crypto/src/asn1/Asn1Null.cs new file mode 100644 index 0000000..d54019f --- /dev/null +++ b/bc-sharp-crypto/src/asn1/Asn1Null.cs @@ -0,0 +1,18 @@ +namespace Org.BouncyCastle.Asn1 +{ + /** + * A Null object. + */ + public abstract class Asn1Null + : Asn1Object + { + internal Asn1Null() + { + } + + public override string ToString() + { + return "NULL"; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/Asn1Object.cs b/bc-sharp-crypto/src/asn1/Asn1Object.cs new file mode 100644 index 0000000..4faa81a --- /dev/null +++ b/bc-sharp-crypto/src/asn1/Asn1Object.cs @@ -0,0 +1,70 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Asn1 +{ + public abstract class Asn1Object + : Asn1Encodable + { + /// Create a base ASN.1 object from a byte array. + /// The byte array to parse. + /// The base ASN.1 object represented by the byte array. + /// + /// If there is a problem parsing the data, or parsing an object did not exhaust the available data. + /// + public static Asn1Object FromByteArray( + byte[] data) + { + try + { + MemoryStream input = new MemoryStream(data, false); + Asn1InputStream asn1 = new Asn1InputStream(input, data.Length); + Asn1Object result = asn1.ReadObject(); + if (input.Position != input.Length) + throw new IOException("extra data found after object"); + return result; + } + catch (InvalidCastException) + { + throw new IOException("cannot recognise object in byte array"); + } + } + + /// Read a base ASN.1 object from a stream. + /// The stream to parse. + /// The base ASN.1 object represented by the byte array. + /// If there is a problem parsing the data. + public static Asn1Object FromStream( + Stream inStr) + { + try + { + return new Asn1InputStream(inStr).ReadObject(); + } + catch (InvalidCastException) + { + throw new IOException("cannot recognise object in stream"); + } + } + + public sealed override Asn1Object ToAsn1Object() + { + return this; + } + + internal abstract void Encode(DerOutputStream derOut); + + protected abstract bool Asn1Equals(Asn1Object asn1Object); + protected abstract int Asn1GetHashCode(); + + internal bool CallAsn1Equals(Asn1Object obj) + { + return Asn1Equals(obj); + } + + internal int CallAsn1GetHashCode() + { + return Asn1GetHashCode(); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/Asn1OctetString.cs b/bc-sharp-crypto/src/asn1/Asn1OctetString.cs new file mode 100644 index 0000000..73b6e51 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/Asn1OctetString.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Asn1 +{ + public abstract class Asn1OctetString + : Asn1Object, Asn1OctetStringParser + { + internal byte[] str; + + /** + * return an Octet string from a tagged object. + * + * @param obj the tagged object holding the object we want. + * @param explicitly true if the object is meant to be explicitly + * tagged false otherwise. + * @exception ArgumentException if the tagged object cannot + * be converted. + */ + public static Asn1OctetString GetInstance( + Asn1TaggedObject obj, + bool isExplicit) + { + Asn1Object o = obj.GetObject(); + + if (isExplicit || o is Asn1OctetString) + { + return GetInstance(o); + } + + return BerOctetString.FromSequence(Asn1Sequence.GetInstance(o)); + } + + /** + * return an Octet string from the given object. + * + * @param obj the object we want converted. + * @exception ArgumentException if the object cannot be converted. + */ + public static Asn1OctetString GetInstance(object obj) + { + if (obj == null || obj is Asn1OctetString) + { + return (Asn1OctetString)obj; + } + + // TODO: this needs to be deleted in V2 + if (obj is Asn1TaggedObject) + return GetInstance(((Asn1TaggedObject)obj).GetObject()); + + throw new ArgumentException("illegal object in GetInstance: " + Platform.GetTypeName(obj)); + } + + /** + * @param string the octets making up the octet string. + */ + internal Asn1OctetString( + byte[] str) + { + if (str == null) + throw new ArgumentNullException("str"); + + this.str = str; + } + + internal Asn1OctetString( + Asn1Encodable obj) + { + try + { + this.str = obj.GetEncoded(Asn1Encodable.Der); + } + catch (IOException e) + { + throw new ArgumentException("Error processing object : " + e.ToString()); + } + } + + public Stream GetOctetStream() + { + return new MemoryStream(str, false); + } + + public Asn1OctetStringParser Parser + { + get { return this; } + } + + public virtual byte[] GetOctets() + { + return str; + } + + protected override int Asn1GetHashCode() + { + return Arrays.GetHashCode(GetOctets()); + } + + protected override bool Asn1Equals( + Asn1Object asn1Object) + { + DerOctetString other = asn1Object as DerOctetString; + + if (other == null) + return false; + + return Arrays.AreEqual(GetOctets(), other.GetOctets()); + } + + public override string ToString() + { + return "#" + Hex.ToHexString(str); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/Asn1OutputStream.cs b/bc-sharp-crypto/src/asn1/Asn1OutputStream.cs new file mode 100644 index 0000000..39c8b1e --- /dev/null +++ b/bc-sharp-crypto/src/asn1/Asn1OutputStream.cs @@ -0,0 +1,35 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Asn1 +{ + public class Asn1OutputStream + : DerOutputStream + { + public Asn1OutputStream(Stream os) : base(os) + { + } + + [Obsolete("Use version taking an Asn1Encodable arg instead")] + public override void WriteObject( + object obj) + { + if (obj == null) + { + WriteNull(); + } + else if (obj is Asn1Object) + { + ((Asn1Object)obj).Encode(this); + } + else if (obj is Asn1Encodable) + { + ((Asn1Encodable)obj).ToAsn1Object().Encode(this); + } + else + { + throw new IOException("object not Asn1Encodable"); + } + } + } +} diff --git a/bc-sharp-crypto/src/asn1/Asn1ParsingException.cs b/bc-sharp-crypto/src/asn1/Asn1ParsingException.cs new file mode 100644 index 0000000..84cdb78 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/Asn1ParsingException.cs @@ -0,0 +1,29 @@ +using System; + +namespace Org.BouncyCastle.Asn1 +{ +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT || PORTABLE) + [Serializable] +#endif + public class Asn1ParsingException + : InvalidOperationException + { + public Asn1ParsingException() + : base() + { + } + + public Asn1ParsingException( + string message) + : base(message) + { + } + + public Asn1ParsingException( + string message, + Exception exception) + : base(message, exception) + { + } + } +} diff --git a/bc-sharp-crypto/src/asn1/Asn1Sequence.cs b/bc-sharp-crypto/src/asn1/Asn1Sequence.cs new file mode 100644 index 0000000..849f5e3 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/Asn1Sequence.cs @@ -0,0 +1,268 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; + +namespace Org.BouncyCastle.Asn1 +{ + public abstract class Asn1Sequence + : Asn1Object, IEnumerable + { + private readonly IList seq; + + /** + * return an Asn1Sequence from the given object. + * + * @param obj the object we want converted. + * @exception ArgumentException if the object cannot be converted. + */ + public static Asn1Sequence GetInstance( + object obj) + { + if (obj == null || obj is Asn1Sequence) + { + return (Asn1Sequence)obj; + } + else if (obj is Asn1SequenceParser) + { + return Asn1Sequence.GetInstance(((Asn1SequenceParser)obj).ToAsn1Object()); + } + else if (obj is byte[]) + { + try + { + return Asn1Sequence.GetInstance(FromByteArray((byte[])obj)); + } + catch (IOException e) + { + throw new ArgumentException("failed to construct sequence from byte[]: " + e.Message); + } + } + else if (obj is Asn1Encodable) + { + Asn1Object primitive = ((Asn1Encodable)obj).ToAsn1Object(); + + if (primitive is Asn1Sequence) + { + return (Asn1Sequence)primitive; + } + } + + throw new ArgumentException("Unknown object in GetInstance: " + Platform.GetTypeName(obj), "obj"); + } + + /** + * 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 explicitly true if the object is meant to be explicitly tagged, + * false otherwise. + * @exception ArgumentException if the tagged object cannot + * be converted. + */ + public static Asn1Sequence GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + Asn1Object inner = obj.GetObject(); + + if (explicitly) + { + if (!obj.IsExplicit()) + throw new ArgumentException("object implicit - explicit expected."); + + return (Asn1Sequence) inner; + } + + // + // 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 is BerTaggedObject) + { + return new BerSequence(inner); + } + + return new DerSequence(inner); + } + + if (inner is Asn1Sequence) + { + return (Asn1Sequence) inner; + } + + throw new ArgumentException("Unknown object in GetInstance: " + Platform.GetTypeName(obj), "obj"); + } + + protected internal Asn1Sequence( + int capacity) + { + seq = Platform.CreateArrayList(capacity); + } + + public virtual IEnumerator GetEnumerator() + { + return seq.GetEnumerator(); + } + + [Obsolete("Use GetEnumerator() instead")] + public IEnumerator GetObjects() + { + return GetEnumerator(); + } + + private class Asn1SequenceParserImpl + : Asn1SequenceParser + { + private readonly Asn1Sequence outer; + private readonly int max; + private int index; + + public Asn1SequenceParserImpl( + Asn1Sequence outer) + { + this.outer = outer; + this.max = outer.Count; + } + + public IAsn1Convertible ReadObject() + { + if (index == max) + return null; + + Asn1Encodable obj = outer[index++]; + + if (obj is Asn1Sequence) + return ((Asn1Sequence)obj).Parser; + + if (obj is Asn1Set) + return ((Asn1Set)obj).Parser; + + // NB: Asn1OctetString implements Asn1OctetStringParser directly +// if (obj is Asn1OctetString) +// return ((Asn1OctetString)obj).Parser; + + return obj; + } + + public Asn1Object ToAsn1Object() + { + return outer; + } + } + + public virtual Asn1SequenceParser Parser + { + get { return new Asn1SequenceParserImpl(this); } + } + + /** + * 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 virtual Asn1Encodable this[int index] + { + get { return (Asn1Encodable) seq[index]; } + } + + [Obsolete("Use 'object[index]' syntax instead")] + public Asn1Encodable GetObjectAt( + int index) + { + return this[index]; + } + + [Obsolete("Use 'Count' property instead")] + public int Size + { + get { return Count; } + } + + public virtual int Count + { + get { return seq.Count; } + } + + protected override int Asn1GetHashCode() + { + int hc = Count; + + foreach (object o in this) + { + hc *= 17; + if (o == null) + { + hc ^= DerNull.Instance.GetHashCode(); + } + else + { + hc ^= o.GetHashCode(); + } + } + + return hc; + } + + protected override bool Asn1Equals( + Asn1Object asn1Object) + { + Asn1Sequence other = asn1Object as Asn1Sequence; + + if (other == null) + return false; + + if (Count != other.Count) + return false; + + IEnumerator s1 = GetEnumerator(); + IEnumerator s2 = other.GetEnumerator(); + + while (s1.MoveNext() && s2.MoveNext()) + { + Asn1Object o1 = GetCurrent(s1).ToAsn1Object(); + Asn1Object o2 = GetCurrent(s2).ToAsn1Object(); + + if (!o1.Equals(o2)) + return false; + } + + return true; + } + + private Asn1Encodable GetCurrent(IEnumerator e) + { + Asn1Encodable encObj = (Asn1Encodable)e.Current; + + // unfortunately null was allowed as a substitute for DER null + if (encObj == null) + return DerNull.Instance; + + return encObj; + } + + protected internal void AddObject( + Asn1Encodable obj) + { + seq.Add(obj); + } + + public override string ToString() + { + return CollectionUtilities.ToString(seq); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/Asn1Set.cs b/bc-sharp-crypto/src/asn1/Asn1Set.cs new file mode 100644 index 0000000..bf83dbd --- /dev/null +++ b/bc-sharp-crypto/src/asn1/Asn1Set.cs @@ -0,0 +1,372 @@ +using System; +using System.Collections; +using System.IO; + +#if PORTABLE +using System.Collections.Generic; +using System.Linq; +#endif + +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; + +namespace Org.BouncyCastle.Asn1 +{ + abstract public class Asn1Set + : Asn1Object, IEnumerable + { + private readonly IList _set; + + /** + * return an ASN1Set from the given object. + * + * @param obj the object we want converted. + * @exception ArgumentException if the object cannot be converted. + */ + public static Asn1Set GetInstance( + object obj) + { + if (obj == null || obj is Asn1Set) + { + return (Asn1Set)obj; + } + else if (obj is Asn1SetParser) + { + return Asn1Set.GetInstance(((Asn1SetParser)obj).ToAsn1Object()); + } + else if (obj is byte[]) + { + try + { + return Asn1Set.GetInstance(FromByteArray((byte[])obj)); + } + catch (IOException e) + { + throw new ArgumentException("failed to construct set from byte[]: " + e.Message); + } + } + else if (obj is Asn1Encodable) + { + Asn1Object primitive = ((Asn1Encodable)obj).ToAsn1Object(); + + if (primitive is Asn1Set) + { + return (Asn1Set)primitive; + } + } + + throw new ArgumentException("Unknown object in GetInstance: " + Platform.GetTypeName(obj), "obj"); + } + + /** + * 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 explicitly true if the object is meant to be explicitly tagged + * false otherwise. + * @exception ArgumentException if the tagged object cannot + * be converted. + */ + public static Asn1Set GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + Asn1Object inner = obj.GetObject(); + + if (explicitly) + { + if (!obj.IsExplicit()) + throw new ArgumentException("object implicit - explicit expected."); + + return (Asn1Set) inner; + } + + // + // constructed object which appears to be explicitly tagged + // and it's really implicit means we have to add the + // surrounding sequence. + // + if (obj.IsExplicit()) + { + return new DerSet(inner); + } + + if (inner is Asn1Set) + { + return (Asn1Set) inner; + } + + // + // in this case the parser returns a sequence, convert it + // into a set. + // + if (inner is Asn1Sequence) + { + Asn1EncodableVector v = new Asn1EncodableVector(); + Asn1Sequence s = (Asn1Sequence) inner; + + foreach (Asn1Encodable ae in s) + { + v.Add(ae); + } + + // TODO Should be able to construct set directly from sequence? + return new DerSet(v, false); + } + + throw new ArgumentException("Unknown object in GetInstance: " + Platform.GetTypeName(obj), "obj"); + } + + protected internal Asn1Set( + int capacity) + { + _set = Platform.CreateArrayList(capacity); + } + + public virtual IEnumerator GetEnumerator() + { + return _set.GetEnumerator(); + } + + [Obsolete("Use GetEnumerator() instead")] + public IEnumerator GetObjects() + { + return GetEnumerator(); + } + + /** + * 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 virtual Asn1Encodable this[int index] + { + get { return (Asn1Encodable) _set[index]; } + } + + [Obsolete("Use 'object[index]' syntax instead")] + public Asn1Encodable GetObjectAt( + int index) + { + return this[index]; + } + + [Obsolete("Use 'Count' property instead")] + public int Size + { + get { return Count; } + } + + public virtual int Count + { + get { return _set.Count; } + } + + public virtual Asn1Encodable[] ToArray() + { + Asn1Encodable[] values = new Asn1Encodable[this.Count]; + for (int i = 0; i < this.Count; ++i) + { + values[i] = this[i]; + } + return values; + } + + private class Asn1SetParserImpl + : Asn1SetParser + { + private readonly Asn1Set outer; + private readonly int max; + private int index; + + public Asn1SetParserImpl( + Asn1Set outer) + { + this.outer = outer; + this.max = outer.Count; + } + + public IAsn1Convertible ReadObject() + { + if (index == max) + return null; + + Asn1Encodable obj = outer[index++]; + if (obj is Asn1Sequence) + return ((Asn1Sequence)obj).Parser; + + if (obj is Asn1Set) + return ((Asn1Set)obj).Parser; + + // NB: Asn1OctetString implements Asn1OctetStringParser directly +// if (obj is Asn1OctetString) +// return ((Asn1OctetString)obj).Parser; + + return obj; + } + + public virtual Asn1Object ToAsn1Object() + { + return outer; + } + } + + public Asn1SetParser Parser + { + get { return new Asn1SetParserImpl(this); } + } + + protected override int Asn1GetHashCode() + { + int hc = Count; + + foreach (object o in this) + { + hc *= 17; + if (o == null) + { + hc ^= DerNull.Instance.GetHashCode(); + } + else + { + hc ^= o.GetHashCode(); + } + } + + return hc; + } + + protected override bool Asn1Equals( + Asn1Object asn1Object) + { + Asn1Set other = asn1Object as Asn1Set; + + if (other == null) + return false; + + if (Count != other.Count) + { + return false; + } + + IEnumerator s1 = GetEnumerator(); + IEnumerator s2 = other.GetEnumerator(); + + while (s1.MoveNext() && s2.MoveNext()) + { + Asn1Object o1 = GetCurrent(s1).ToAsn1Object(); + Asn1Object o2 = GetCurrent(s2).ToAsn1Object(); + + if (!o1.Equals(o2)) + return false; + } + + return true; + } + + private Asn1Encodable GetCurrent(IEnumerator e) + { + Asn1Encodable encObj = (Asn1Encodable)e.Current; + + // unfortunately null was allowed as a substitute for DER null + if (encObj == null) + return DerNull.Instance; + + return encObj; + } + + protected internal void Sort() + { + if (_set.Count < 2) + return; + +#if PORTABLE + var sorted = _set.Cast() + .Select(a => new { Item = a, Key = a.GetEncoded(Asn1Encodable.Der) }) + .OrderBy(t => t.Key, new DerComparer()) + .Select(t => t.Item) + .ToList(); + + for (int i = 0; i < _set.Count; ++i) + { + _set[i] = sorted[i]; + } +#else + Asn1Encodable[] items = new Asn1Encodable[_set.Count]; + byte[][] keys = new byte[_set.Count][]; + + for (int i = 0; i < _set.Count; ++i) + { + Asn1Encodable item = (Asn1Encodable)_set[i]; + items[i] = item; + keys[i] = item.GetEncoded(Asn1Encodable.Der); + } + + Array.Sort(keys, items, new DerComparer()); + + for (int i = 0; i < _set.Count; ++i) + { + _set[i] = items[i]; + } +#endif + } + + protected internal void AddObject(Asn1Encodable obj) + { + _set.Add(obj); + } + + public override string ToString() + { + return CollectionUtilities.ToString(_set); + } + +#if PORTABLE + private class DerComparer + : IComparer + { + public int Compare(byte[] x, byte[] y) + { + byte[] a = x, b = y; +#else + private class DerComparer + : IComparer + { + public int Compare(object x, object y) + { + byte[] a = (byte[])x, b = (byte[])y; +#endif + int len = System.Math.Min(a.Length, b.Length); + for (int i = 0; i != len; ++i) + { + byte ai = a[i], bi = b[i]; + if (ai != bi) + return ai < bi ? -1 : 1; + } + if (a.Length > b.Length) + return AllZeroesFrom(a, len) ? 0 : 1; + if (a.Length < b.Length) + return AllZeroesFrom(b, len) ? 0 : -1; + return 0; + } + + private bool AllZeroesFrom(byte[] bs, int pos) + { + while (pos < bs.Length) + { + if (bs[pos++] != 0) + return false; + } + return true; + } + } + } +} diff --git a/bc-sharp-crypto/src/asn1/Asn1TaggedObject.cs b/bc-sharp-crypto/src/asn1/Asn1TaggedObject.cs new file mode 100644 index 0000000..a6d4b2c --- /dev/null +++ b/bc-sharp-crypto/src/asn1/Asn1TaggedObject.cs @@ -0,0 +1,188 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1 +{ + /** + * 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 + : Asn1Object, Asn1TaggedObjectParser + { + internal static bool IsConstructed(bool isExplicit, Asn1Object obj) + { + if (isExplicit || obj is Asn1Sequence || obj is Asn1Set) + return true; + Asn1TaggedObject tagged = obj as Asn1TaggedObject; + if (tagged == null) + return false; + return IsConstructed(tagged.IsExplicit(), tagged.GetObject()); + } + + internal int tagNo; +// internal bool empty; + internal bool explicitly = true; + internal Asn1Encodable obj; + + static public Asn1TaggedObject GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + if (explicitly) + { + return (Asn1TaggedObject) obj.GetObject(); + } + + throw new ArgumentException("implicitly tagged tagged object"); + } + + static public Asn1TaggedObject GetInstance( + object obj) + { + if (obj == null || obj is Asn1TaggedObject) + { + return (Asn1TaggedObject) obj; + } + + throw new ArgumentException("Unknown object in GetInstance: " + Platform.GetTypeName(obj), "obj"); + } + + /** + * @param tagNo the tag number for this object. + * @param obj the tagged object. + */ + protected Asn1TaggedObject( + int tagNo, + Asn1Encodable obj) + { + this.explicitly = true; + this.tagNo = tagNo; + this.obj = obj; + } + + /** + * @param explicitly true if the object is explicitly tagged. + * @param tagNo the tag number for this object. + * @param obj the tagged object. + */ + protected Asn1TaggedObject( + bool explicitly, + int tagNo, + Asn1Encodable obj) + { + // IAsn1Choice marker interface 'insists' on explicit tagging + this.explicitly = explicitly || (obj is IAsn1Choice); + this.tagNo = tagNo; + this.obj = obj; + } + + protected override bool Asn1Equals( + Asn1Object asn1Object) + { + Asn1TaggedObject other = asn1Object as Asn1TaggedObject; + + if (other == null) + return false; + + return this.tagNo == other.tagNo +// && this.empty == other.empty + && this.explicitly == other.explicitly // TODO Should this be part of equality? + && Platform.Equals(GetObject(), other.GetObject()); + } + + protected override int Asn1GetHashCode() + { + int code = tagNo.GetHashCode(); + + // 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... +// code ^= explicitly.GetHashCode(); + + if (obj != null) + { + code ^= obj.GetHashCode(); + } + + return code; + } + + public int TagNo + { + get { 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 bool IsExplicit() + { + return explicitly; + } + + public bool IsEmpty() + { + return false; //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 Asn1Object GetObject() + { + if (obj != null) + { + return obj.ToAsn1Object(); + } + + 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 IAsn1Convertible GetObjectParser( + int tag, + bool isExplicit) + { + switch (tag) + { + case Asn1Tags.Set: + return Asn1Set.GetInstance(this, isExplicit).Parser; + case Asn1Tags.Sequence: + return Asn1Sequence.GetInstance(this, isExplicit).Parser; + case Asn1Tags.OctetString: + return Asn1OctetString.GetInstance(this, isExplicit).Parser; + } + + if (isExplicit) + { + return GetObject(); + } + + throw Platform.CreateNotImplementedException("implicit tagging for tag: " + tag); + } + + public override string ToString() + { + return "[" + tagNo + "]" + obj; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/Asn1Tags.cs b/bc-sharp-crypto/src/asn1/Asn1Tags.cs new file mode 100644 index 0000000..32ac6bc --- /dev/null +++ b/bc-sharp-crypto/src/asn1/Asn1Tags.cs @@ -0,0 +1,36 @@ +namespace Org.BouncyCastle.Asn1 +{ + public class Asn1Tags + { + public const int Boolean = 0x01; + public const int Integer = 0x02; + public const int BitString = 0x03; + public const int OctetString = 0x04; + public const int Null = 0x05; + public const int ObjectIdentifier = 0x06; + public const int External = 0x08; + public const int Enumerated = 0x0a; + public const int Sequence = 0x10; + public const int SequenceOf = 0x10; // for completeness + public const int Set = 0x11; + public const int SetOf = 0x11; // for completeness + + public const int NumericString = 0x12; + public const int PrintableString = 0x13; + public const int T61String = 0x14; + public const int VideotexString = 0x15; + public const int IA5String = 0x16; + public const int UtcTime = 0x17; + public const int GeneralizedTime = 0x18; + public const int GraphicString = 0x19; + public const int VisibleString = 0x1a; + public const int GeneralString = 0x1b; + public const int UniversalString = 0x1c; + public const int BmpString = 0x1e; + public const int Utf8String = 0x0c; + + public const int Constructed = 0x20; + public const int Application = 0x40; + public const int Tagged = 0x80; + } +} diff --git a/bc-sharp-crypto/src/asn1/BERBitString.cs b/bc-sharp-crypto/src/asn1/BERBitString.cs new file mode 100644 index 0000000..d8cd003 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/BERBitString.cs @@ -0,0 +1,43 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1 +{ + public class BerBitString + : DerBitString + { + public BerBitString(byte[] data, int padBits) + : base(data, padBits) + { + } + + public BerBitString(byte[] data) + : base(data) + { + } + + public BerBitString(int namedBits) + : base(namedBits) + { + } + + public BerBitString(Asn1Encodable obj) + : base(obj) + { + } + + internal override void Encode( + DerOutputStream derOut) + { + if (derOut is Asn1OutputStream || derOut is BerOutputStream) + { + derOut.WriteEncoded(Asn1Tags.BitString, (byte)mPadBits, mData); + } + else + { + base.Encode(derOut); + } + } + } +} diff --git a/bc-sharp-crypto/src/asn1/BERGenerator.cs b/bc-sharp-crypto/src/asn1/BERGenerator.cs new file mode 100644 index 0000000..271572c --- /dev/null +++ b/bc-sharp-crypto/src/asn1/BERGenerator.cs @@ -0,0 +1,102 @@ +using System.IO; + +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Asn1 +{ + public class BerGenerator + : Asn1Generator + { + private bool _tagged = false; + private bool _isExplicit; + private int _tagNo; + + protected BerGenerator( + Stream outStream) + : base(outStream) + { + } + + public BerGenerator( + Stream outStream, + int tagNo, + bool isExplicit) + : base(outStream) + { + _tagged = true; + _isExplicit = isExplicit; + _tagNo = tagNo; + } + + public override void AddObject( + Asn1Encodable obj) + { + new BerOutputStream(Out).WriteObject(obj); + } + + public override Stream GetRawOutputStream() + { + return Out; + } + + public override void Close() + { + WriteBerEnd(); + } + + private void WriteHdr( + int tag) + { + Out.WriteByte((byte) tag); + Out.WriteByte(0x80); + } + + protected void WriteBerHeader( + int tag) + { + if (_tagged) + { + int tagNum = _tagNo | Asn1Tags.Tagged; + + if (_isExplicit) + { + WriteHdr(tagNum | Asn1Tags.Constructed); + WriteHdr(tag); + } + else + { + if ((tag & Asn1Tags.Constructed) != 0) + { + WriteHdr(tagNum | Asn1Tags.Constructed); + } + else + { + WriteHdr(tagNum); + } + } + } + else + { + WriteHdr(tag); + } + } + + protected void WriteBerBody( + Stream contentStream) + { + Streams.PipeAll(contentStream, Out); + } + + protected void WriteBerEnd() + { + Out.WriteByte(0x00); + Out.WriteByte(0x00); + + if (_tagged && _isExplicit) // write extra end for tag header + { + Out.WriteByte(0x00); + Out.WriteByte(0x00); + } + } + } +} diff --git a/bc-sharp-crypto/src/asn1/BEROctetStringGenerator.cs b/bc-sharp-crypto/src/asn1/BEROctetStringGenerator.cs new file mode 100644 index 0000000..f34538f --- /dev/null +++ b/bc-sharp-crypto/src/asn1/BEROctetStringGenerator.cs @@ -0,0 +1,133 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Asn1 +{ + public class BerOctetStringGenerator + : BerGenerator + { + public BerOctetStringGenerator(Stream outStream) + : base(outStream) + { + WriteBerHeader(Asn1Tags.Constructed | Asn1Tags.OctetString); + } + + public BerOctetStringGenerator( + Stream outStream, + int tagNo, + bool isExplicit) + : base(outStream, tagNo, isExplicit) + { + WriteBerHeader(Asn1Tags.Constructed | Asn1Tags.OctetString); + } + + public Stream GetOctetOutputStream() + { + return GetOctetOutputStream(new byte[1000]); // limit for CER encoding. + } + + public Stream GetOctetOutputStream( + int bufSize) + { + return bufSize < 1 + ? GetOctetOutputStream() + : GetOctetOutputStream(new byte[bufSize]); + } + + public Stream GetOctetOutputStream( + byte[] buf) + { + return new BufferedBerOctetStream(this, buf); + } + + private class BufferedBerOctetStream + : BaseOutputStream + { + private byte[] _buf; + private int _off; + private readonly BerOctetStringGenerator _gen; + private readonly DerOutputStream _derOut; + + internal BufferedBerOctetStream( + BerOctetStringGenerator gen, + byte[] buf) + { + _gen = gen; + _buf = buf; + _off = 0; + _derOut = new DerOutputStream(_gen.Out); + } + + public override void WriteByte( + byte b) + { + _buf[_off++] = b; + + if (_off == _buf.Length) + { + DerOctetString.Encode(_derOut, _buf, 0, _off); + _off = 0; + } + } + + public override void Write( + byte[] buf, + int offset, + int len) + { + while (len > 0) + { + int numToCopy = System.Math.Min(len, _buf.Length - _off); + + if (numToCopy == _buf.Length) + { + DerOctetString.Encode(_derOut, buf, offset, numToCopy); + } + else + { + Array.Copy(buf, offset, _buf, _off, numToCopy); + + _off += numToCopy; + if (_off < _buf.Length) + break; + + DerOctetString.Encode(_derOut, _buf, 0, _off); + _off = 0; + } + + offset += numToCopy; + len -= numToCopy; + } + } + +#if PORTABLE + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (_off != 0) + { + DerOctetString.Encode(_derOut, _buf, 0, _off); + } + + _gen.WriteBerEnd(); + } + base.Dispose(disposing); + } +#else + public override void Close() + { + if (_off != 0) + { + DerOctetString.Encode(_derOut, _buf, 0, _off); + } + + _gen.WriteBerEnd(); + base.Close(); + } +#endif + } + } +} diff --git a/bc-sharp-crypto/src/asn1/BEROctetStringParser.cs b/bc-sharp-crypto/src/asn1/BEROctetStringParser.cs new file mode 100644 index 0000000..3bfd2a9 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/BEROctetStringParser.cs @@ -0,0 +1,36 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Asn1 +{ + public class BerOctetStringParser + : Asn1OctetStringParser + { + private readonly Asn1StreamParser _parser; + + internal BerOctetStringParser( + Asn1StreamParser parser) + { + _parser = parser; + } + + public Stream GetOctetStream() + { + return new ConstructedOctetStream(_parser); + } + + public Asn1Object ToAsn1Object() + { + try + { + return new BerOctetString(Streams.ReadAll(GetOctetStream())); + } + catch (IOException e) + { + throw new Asn1ParsingException("IOException converting stream to byte array: " + e.Message, e); + } + } + } +} diff --git a/bc-sharp-crypto/src/asn1/BERSequenceGenerator.cs b/bc-sharp-crypto/src/asn1/BERSequenceGenerator.cs new file mode 100644 index 0000000..5ea2c9b --- /dev/null +++ b/bc-sharp-crypto/src/asn1/BERSequenceGenerator.cs @@ -0,0 +1,24 @@ +using System.IO; + +namespace Org.BouncyCastle.Asn1 +{ + public class BerSequenceGenerator + : BerGenerator + { + public BerSequenceGenerator( + Stream outStream) + : base(outStream) + { + WriteBerHeader(Asn1Tags.Constructed | Asn1Tags.Sequence); + } + + public BerSequenceGenerator( + Stream outStream, + int tagNo, + bool isExplicit) + : base(outStream, tagNo, isExplicit) + { + WriteBerHeader(Asn1Tags.Constructed | Asn1Tags.Sequence); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/BERSequenceParser.cs b/bc-sharp-crypto/src/asn1/BERSequenceParser.cs new file mode 100644 index 0000000..8474b8d --- /dev/null +++ b/bc-sharp-crypto/src/asn1/BERSequenceParser.cs @@ -0,0 +1,24 @@ +namespace Org.BouncyCastle.Asn1 +{ + public class BerSequenceParser + : Asn1SequenceParser + { + private readonly Asn1StreamParser _parser; + + internal BerSequenceParser( + Asn1StreamParser parser) + { + this._parser = parser; + } + + public IAsn1Convertible ReadObject() + { + return _parser.ReadObject(); + } + + public Asn1Object ToAsn1Object() + { + return new BerSequence(_parser.ReadVector()); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/BERSetGenerator.cs b/bc-sharp-crypto/src/asn1/BERSetGenerator.cs new file mode 100644 index 0000000..72b1f90 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/BERSetGenerator.cs @@ -0,0 +1,24 @@ +using System.IO; + +namespace Org.BouncyCastle.Asn1 +{ + public class BerSetGenerator + : BerGenerator + { + public BerSetGenerator( + Stream outStream) + : base(outStream) + { + WriteBerHeader(Asn1Tags.Constructed | Asn1Tags.Set); + } + + public BerSetGenerator( + Stream outStream, + int tagNo, + bool isExplicit) + : base(outStream, tagNo, isExplicit) + { + WriteBerHeader(Asn1Tags.Constructed | Asn1Tags.Set); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/BERSetParser.cs b/bc-sharp-crypto/src/asn1/BERSetParser.cs new file mode 100644 index 0000000..aa9ccbc --- /dev/null +++ b/bc-sharp-crypto/src/asn1/BERSetParser.cs @@ -0,0 +1,24 @@ +namespace Org.BouncyCastle.Asn1 +{ + public class BerSetParser + : Asn1SetParser + { + private readonly Asn1StreamParser _parser; + + internal BerSetParser( + Asn1StreamParser parser) + { + this._parser = parser; + } + + public IAsn1Convertible ReadObject() + { + return _parser.ReadObject(); + } + + public Asn1Object ToAsn1Object() + { + return new BerSet(_parser.ReadVector(), false); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/BERTaggedObjectParser.cs b/bc-sharp-crypto/src/asn1/BERTaggedObjectParser.cs new file mode 100644 index 0000000..354437a --- /dev/null +++ b/bc-sharp-crypto/src/asn1/BERTaggedObjectParser.cs @@ -0,0 +1,71 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1 +{ + public class BerTaggedObjectParser + : Asn1TaggedObjectParser + { + private bool _constructed; + private int _tagNumber; + private Asn1StreamParser _parser; + + [Obsolete] + internal BerTaggedObjectParser( + int baseTag, + int tagNumber, + Stream contentStream) + : this((baseTag & Asn1Tags.Constructed) != 0, tagNumber, new Asn1StreamParser(contentStream)) + { + } + + internal BerTaggedObjectParser( + bool constructed, + int tagNumber, + Asn1StreamParser parser) + { + _constructed = constructed; + _tagNumber = tagNumber; + _parser = parser; + } + + public bool IsConstructed + { + get { return _constructed; } + } + + public int TagNo + { + get { return _tagNumber; } + } + + public IAsn1Convertible GetObjectParser( + int tag, + bool isExplicit) + { + if (isExplicit) + { + if (!_constructed) + throw new IOException("Explicit tags must be constructed (see X.690 8.14.2)"); + + return _parser.ReadObject(); + } + + return _parser.ReadImplicit(_constructed, tag); + } + + public Asn1Object ToAsn1Object() + { + try + { + return _parser.ReadTaggedObject(_constructed, _tagNumber); + } + catch (IOException e) + { + throw new Asn1ParsingException(e.Message); + } + } + } +} diff --git a/bc-sharp-crypto/src/asn1/BerApplicationSpecific.cs b/bc-sharp-crypto/src/asn1/BerApplicationSpecific.cs new file mode 100644 index 0000000..65fbecb --- /dev/null +++ b/bc-sharp-crypto/src/asn1/BerApplicationSpecific.cs @@ -0,0 +1,15 @@ +using System; + +namespace Org.BouncyCastle.Asn1 +{ + public class BerApplicationSpecific + : DerApplicationSpecific + { + public BerApplicationSpecific( + int tagNo, + Asn1EncodableVector vec) + : base(tagNo, vec) + { + } + } +} diff --git a/bc-sharp-crypto/src/asn1/BerApplicationSpecificParser.cs b/bc-sharp-crypto/src/asn1/BerApplicationSpecificParser.cs new file mode 100644 index 0000000..7d2c4b3 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/BerApplicationSpecificParser.cs @@ -0,0 +1,29 @@ +using System; + +namespace Org.BouncyCastle.Asn1 +{ + public class BerApplicationSpecificParser + : IAsn1ApplicationSpecificParser + { + private readonly int tag; + private readonly Asn1StreamParser parser; + + internal BerApplicationSpecificParser( + int tag, + Asn1StreamParser parser) + { + this.tag = tag; + this.parser = parser; + } + + public IAsn1Convertible ReadObject() + { + return parser.ReadObject(); + } + + public Asn1Object ToAsn1Object() + { + return new BerApplicationSpecific(tag, parser.ReadVector()); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/BerNull.cs b/bc-sharp-crypto/src/asn1/BerNull.cs new file mode 100644 index 0000000..0751bba --- /dev/null +++ b/bc-sharp-crypto/src/asn1/BerNull.cs @@ -0,0 +1,35 @@ +using System; + +namespace Org.BouncyCastle.Asn1 +{ + /** + * A BER Null object. + */ + public class BerNull + : DerNull + { + public static new readonly BerNull Instance = new BerNull(0); + + [Obsolete("Use static Instance object")] + public BerNull() + { + } + + private BerNull(int dummy) : base(dummy) + { + } + + internal override void Encode( + DerOutputStream derOut) + { + if (derOut is Asn1OutputStream || derOut is BerOutputStream) + { + derOut.WriteByte(Asn1Tags.Null); + } + else + { + base.Encode(derOut); + } + } + } +} diff --git a/bc-sharp-crypto/src/asn1/BerOctetString.cs b/bc-sharp-crypto/src/asn1/BerOctetString.cs new file mode 100644 index 0000000..a7c8ad3 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/BerOctetString.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1 +{ + public class BerOctetString + : DerOctetString, IEnumerable + { + public static BerOctetString FromSequence(Asn1Sequence seq) + { + IList v = Platform.CreateArrayList(); + + foreach (Asn1Encodable obj in seq) + { + v.Add(obj); + } + + return new BerOctetString(v); + } + + private const int MaxLength = 1000; + + /** + * convert a vector of octet strings into a single byte string + */ + private static byte[] ToBytes( + IEnumerable octs) + { + MemoryStream bOut = new MemoryStream(); + foreach (DerOctetString o in octs) + { + byte[] octets = o.GetOctets(); + bOut.Write(octets, 0, octets.Length); + } + return bOut.ToArray(); + } + + private readonly IEnumerable octs; + + /// The octets making up the octet string. + public BerOctetString( + byte[] str) + : base(str) + { + } + + public BerOctetString( + IEnumerable octets) + : base(ToBytes(octets)) + { + this.octs = octets; + } + + public BerOctetString( + Asn1Object obj) + : base(obj) + { + } + + public BerOctetString( + Asn1Encodable obj) + : base(obj.ToAsn1Object()) + { + } + + public override byte[] GetOctets() + { + return str; + } + + /** + * return the DER octets that make up this string. + */ + public IEnumerator GetEnumerator() + { + if (octs == null) + { + return GenerateOcts().GetEnumerator(); + } + + return octs.GetEnumerator(); + } + + [Obsolete("Use GetEnumerator() instead")] + public IEnumerator GetObjects() + { + return GetEnumerator(); + } + + private IList GenerateOcts() + { + IList vec = Platform.CreateArrayList(); + for (int i = 0; i < str.Length; i += MaxLength) + { + int end = System.Math.Min(str.Length, i + MaxLength); + + byte[] nStr = new byte[end - i]; + + Array.Copy(str, i, nStr, 0, nStr.Length); + + vec.Add(new DerOctetString(nStr)); + } + return vec; + } + + internal override void Encode( + DerOutputStream derOut) + { + if (derOut is Asn1OutputStream || derOut is BerOutputStream) + { + derOut.WriteByte(Asn1Tags.Constructed | Asn1Tags.OctetString); + + derOut.WriteByte(0x80); + + // + // write out the octet array + // + foreach (DerOctetString oct in this) + { + derOut.WriteObject(oct); + } + + derOut.WriteByte(0x00); + derOut.WriteByte(0x00); + } + else + { + base.Encode(derOut); + } + } + } +} diff --git a/bc-sharp-crypto/src/asn1/BerOutputStream.cs b/bc-sharp-crypto/src/asn1/BerOutputStream.cs new file mode 100644 index 0000000..b3ece10 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/BerOutputStream.cs @@ -0,0 +1,36 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Asn1 +{ + // TODO Make Obsolete in favour of Asn1OutputStream? + public class BerOutputStream + : DerOutputStream + { + public BerOutputStream(Stream os) : base(os) + { + } + + [Obsolete("Use version taking an Asn1Encodable arg instead")] + public override void WriteObject( + object obj) + { + if (obj == null) + { + WriteNull(); + } + else if (obj is Asn1Object) + { + ((Asn1Object)obj).Encode(this); + } + else if (obj is Asn1Encodable) + { + ((Asn1Encodable)obj).ToAsn1Object().Encode(this); + } + else + { + throw new IOException("object not BerEncodable"); + } + } + } +} diff --git a/bc-sharp-crypto/src/asn1/BerSequence.cs b/bc-sharp-crypto/src/asn1/BerSequence.cs new file mode 100644 index 0000000..70b43fc --- /dev/null +++ b/bc-sharp-crypto/src/asn1/BerSequence.cs @@ -0,0 +1,69 @@ +namespace Org.BouncyCastle.Asn1 +{ + public class BerSequence + : DerSequence + { + public static new readonly BerSequence Empty = new BerSequence(); + + public static new BerSequence FromVector( + Asn1EncodableVector v) + { + return v.Count < 1 ? Empty : new BerSequence(v); + } + + /** + * create an empty sequence + */ + public BerSequence() + { + } + + /** + * create a sequence containing one object + */ + public BerSequence( + Asn1Encodable obj) + : base(obj) + { + } + + public BerSequence( + params Asn1Encodable[] v) + : base(v) + { + } + + /** + * create a sequence containing a vector of objects. + */ + public BerSequence( + Asn1EncodableVector v) + : base(v) + { + } + + /* + */ + internal override void Encode( + DerOutputStream derOut) + { + if (derOut is Asn1OutputStream || derOut is BerOutputStream) + { + derOut.WriteByte(Asn1Tags.Sequence | Asn1Tags.Constructed); + derOut.WriteByte(0x80); + + foreach (Asn1Encodable o in this) + { + derOut.WriteObject(o); + } + + derOut.WriteByte(0x00); + derOut.WriteByte(0x00); + } + else + { + base.Encode(derOut); + } + } + } +} diff --git a/bc-sharp-crypto/src/asn1/BerSet.cs b/bc-sharp-crypto/src/asn1/BerSet.cs new file mode 100644 index 0000000..a181e17 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/BerSet.cs @@ -0,0 +1,70 @@ +namespace Org.BouncyCastle.Asn1 +{ + public class BerSet + : DerSet + { + public static new readonly BerSet Empty = new BerSet(); + + public static new BerSet FromVector( + Asn1EncodableVector v) + { + return v.Count < 1 ? Empty : new BerSet(v); + } + + internal static new BerSet FromVector( + Asn1EncodableVector v, + bool needsSorting) + { + return v.Count < 1 ? Empty : new BerSet(v, needsSorting); + } + + /** + * create an empty sequence + */ + public BerSet() + { + } + + /** + * create a set containing one object + */ + public BerSet(Asn1Encodable obj) : base(obj) + { + } + + /** + * create a set containing a vector of objects. + */ + public BerSet(Asn1EncodableVector v) : base(v, false) + { + } + + internal BerSet(Asn1EncodableVector v, bool needsSorting) : base(v, needsSorting) + { + } + + /* + */ + internal override void Encode( + DerOutputStream derOut) + { + if (derOut is Asn1OutputStream || derOut is BerOutputStream) + { + derOut.WriteByte(Asn1Tags.Set | Asn1Tags.Constructed); + derOut.WriteByte(0x80); + + foreach (Asn1Encodable o in this) + { + derOut.WriteObject(o); + } + + derOut.WriteByte(0x00); + derOut.WriteByte(0x00); + } + else + { + base.Encode(derOut); + } + } + } +} diff --git a/bc-sharp-crypto/src/asn1/BerTaggedObject.cs b/bc-sharp-crypto/src/asn1/BerTaggedObject.cs new file mode 100644 index 0000000..fd0bdc2 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/BerTaggedObject.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1 +{ + /** + * 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 + : DerTaggedObject + { + /** + * @param tagNo the tag number for this object. + * @param obj the tagged object. + */ + public BerTaggedObject( + int tagNo, + Asn1Encodable obj) + : base(tagNo, obj) + { + } + + /** + * @param explicitly true if an explicitly tagged object. + * @param tagNo the tag number for this object. + * @param obj the tagged object. + */ + public BerTaggedObject( + bool explicitly, + int tagNo, + Asn1Encodable obj) + : base(explicitly, tagNo, obj) + { + } + + /** + * create an implicitly tagged object that contains a zero + * length sequence. + */ + public BerTaggedObject( + int tagNo) + : base(false, tagNo, BerSequence.Empty) + { + } + + internal override void Encode( + DerOutputStream derOut) + { + if (derOut is Asn1OutputStream || derOut is BerOutputStream) + { + derOut.WriteTag((byte)(Asn1Tags.Constructed | Asn1Tags.Tagged), tagNo); + derOut.WriteByte(0x80); + + if (!IsEmpty()) + { + if (!explicitly) + { + IEnumerable eObj; + if (obj is Asn1OctetString) + { + if (obj is BerOctetString) + { + eObj = (BerOctetString) obj; + } + else + { + Asn1OctetString octs = (Asn1OctetString)obj; + eObj = new BerOctetString(octs.GetOctets()); + } + } + else if (obj is Asn1Sequence) + { + eObj = (Asn1Sequence) obj; + } + else if (obj is Asn1Set) + { + eObj = (Asn1Set) obj; + } + else + { + throw Platform.CreateNotImplementedException(Platform.GetTypeName(obj)); + } + + foreach (Asn1Encodable o in eObj) + { + derOut.WriteObject(o); + } + } + else + { + derOut.WriteObject(obj); + } + } + + derOut.WriteByte(0x00); + derOut.WriteByte(0x00); + } + else + { + base.Encode(derOut); + } + } + } +} diff --git a/bc-sharp-crypto/src/asn1/ConstructedOctetStream.cs b/bc-sharp-crypto/src/asn1/ConstructedOctetStream.cs new file mode 100644 index 0000000..1773b22 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/ConstructedOctetStream.cs @@ -0,0 +1,102 @@ +using System.IO; + +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Asn1 +{ + internal class ConstructedOctetStream + : BaseInputStream + { + private readonly Asn1StreamParser _parser; + + private bool _first = true; + private Stream _currentStream; + + internal ConstructedOctetStream( + Asn1StreamParser parser) + { + _parser = parser; + } + + public override int Read(byte[] buffer, int offset, int count) + { + if (_currentStream == null) + { + if (!_first) + return 0; + + Asn1OctetStringParser s = (Asn1OctetStringParser)_parser.ReadObject(); + + if (s == null) + return 0; + + _first = false; + _currentStream = s.GetOctetStream(); + } + + int totalRead = 0; + + for (;;) + { + int numRead = _currentStream.Read(buffer, offset + totalRead, count - totalRead); + + if (numRead > 0) + { + totalRead += numRead; + + if (totalRead == count) + return totalRead; + } + else + { + Asn1OctetStringParser aos = (Asn1OctetStringParser)_parser.ReadObject(); + + if (aos == null) + { + _currentStream = null; + return totalRead; + } + + _currentStream = aos.GetOctetStream(); + } + } + } + + public override int ReadByte() + { + if (_currentStream == null) + { + if (!_first) + return 0; + + Asn1OctetStringParser s = (Asn1OctetStringParser)_parser.ReadObject(); + + if (s == null) + return 0; + + _first = false; + _currentStream = s.GetOctetStream(); + } + + for (;;) + { + int b = _currentStream.ReadByte(); + + if (b >= 0) + { + return b; + } + + Asn1OctetStringParser aos = (Asn1OctetStringParser)_parser.ReadObject(); + + if (aos == null) + { + _currentStream = null; + return -1; + } + + _currentStream = aos.GetOctetStream(); + } + } + } +} diff --git a/bc-sharp-crypto/src/asn1/DERExternal.cs b/bc-sharp-crypto/src/asn1/DERExternal.cs new file mode 100644 index 0000000..c299751 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/DERExternal.cs @@ -0,0 +1,202 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1 +{ + /** + * Class representing the DER-type External + */ + public class DerExternal + : Asn1Object + { + private DerObjectIdentifier directReference; + private DerInteger indirectReference; + private Asn1Object dataValueDescriptor; + private int encoding; + private Asn1Object externalContent; + + public DerExternal( + Asn1EncodableVector vector) + { + int offset = 0; + Asn1Object enc = GetObjFromVector(vector, offset); + if (enc is DerObjectIdentifier) + { + directReference = (DerObjectIdentifier)enc; + offset++; + enc = GetObjFromVector(vector, offset); + } + if (enc is DerInteger) + { + indirectReference = (DerInteger) enc; + offset++; + enc = GetObjFromVector(vector, offset); + } + if (!(enc is Asn1TaggedObject)) + { + dataValueDescriptor = enc; + offset++; + enc = GetObjFromVector(vector, offset); + } + + if (vector.Count != offset + 1) + throw new ArgumentException("input vector too large", "vector"); + + if (!(enc is Asn1TaggedObject)) + throw new ArgumentException("No tagged object found in vector. Structure doesn't seem to be of type External", "vector"); + + Asn1TaggedObject obj = (Asn1TaggedObject)enc; + + // Use property accessor to include check on value + Encoding = obj.TagNo; + + if (encoding < 0 || encoding > 2) + throw new InvalidOperationException("invalid encoding value"); + + 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.TagNo, externalData.ToAsn1Object()) + { + } + + /** + * 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, Asn1Object externalData) + { + DirectReference = directReference; + IndirectReference = indirectReference; + DataValueDescriptor = dataValueDescriptor; + Encoding = encoding; + ExternalContent = externalData.ToAsn1Object(); + } + + internal override void Encode(DerOutputStream derOut) + { + MemoryStream ms = new MemoryStream(); + WriteEncodable(ms, directReference); + WriteEncodable(ms, indirectReference); + WriteEncodable(ms, dataValueDescriptor); + WriteEncodable(ms, new DerTaggedObject(Asn1Tags.External, externalContent)); + + derOut.WriteEncoded(Asn1Tags.Constructed, Asn1Tags.External, ms.ToArray()); + } + + protected override int Asn1GetHashCode() + { + int ret = externalContent.GetHashCode(); + if (directReference != null) + { + ret ^= directReference.GetHashCode(); + } + if (indirectReference != null) + { + ret ^= indirectReference.GetHashCode(); + } + if (dataValueDescriptor != null) + { + ret ^= dataValueDescriptor.GetHashCode(); + } + return ret; + } + + protected override bool Asn1Equals( + Asn1Object asn1Object) + { + if (this == asn1Object) + return true; + + DerExternal other = asn1Object as DerExternal; + + if (other == null) + return false; + + return Platform.Equals(directReference, other.directReference) + && Platform.Equals(indirectReference, other.indirectReference) + && Platform.Equals(dataValueDescriptor, other.dataValueDescriptor) + && externalContent.Equals(other.externalContent); + } + + public Asn1Object DataValueDescriptor + { + get { return dataValueDescriptor; } + set { this.dataValueDescriptor = value; } + } + + public DerObjectIdentifier DirectReference + { + get { return directReference; } + set { this.directReference = value; } + } + + /** + * The encoding of the content. Valid values are + *
    + *
  • 0 single-ASN1-type
  • + *
  • 1 OCTET STRING
  • + *
  • 2 BIT STRING
  • + *
+ */ + public int Encoding + { + get + { + return encoding; + } + set + { + if (encoding < 0 || encoding > 2) + throw new InvalidOperationException("invalid encoding value: " + encoding); + + this.encoding = value; + } + } + + public Asn1Object ExternalContent + { + get { return externalContent; } + set { this.externalContent = value; } + } + + public DerInteger IndirectReference + { + get { return indirectReference; } + set { this.indirectReference = value; } + } + + private static Asn1Object GetObjFromVector(Asn1EncodableVector v, int index) + { + if (v.Count <= index) + throw new ArgumentException("too few objects in input vector", "v"); + + return v[index].ToAsn1Object(); + } + + private static void WriteEncodable(MemoryStream ms, Asn1Encodable e) + { + if (e != null) + { + byte[] bs = e.GetDerEncoded(); + ms.Write(bs, 0, bs.Length); + } + } + } +} diff --git a/bc-sharp-crypto/src/asn1/DERExternalParser.cs b/bc-sharp-crypto/src/asn1/DERExternalParser.cs new file mode 100644 index 0000000..70e426f --- /dev/null +++ b/bc-sharp-crypto/src/asn1/DERExternalParser.cs @@ -0,0 +1,26 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Asn1 +{ + public class DerExternalParser + : Asn1Encodable + { + private readonly Asn1StreamParser _parser; + + public DerExternalParser(Asn1StreamParser parser) + { + this._parser = parser; + } + + public IAsn1Convertible ReadObject() + { + return _parser.ReadObject(); + } + + public override Asn1Object ToAsn1Object() + { + return new DerExternal(_parser.ReadVector()); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/DERGenerator.cs b/bc-sharp-crypto/src/asn1/DERGenerator.cs new file mode 100644 index 0000000..aab40fe --- /dev/null +++ b/bc-sharp-crypto/src/asn1/DERGenerator.cs @@ -0,0 +1,107 @@ +using System.IO; + +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Asn1 +{ + public abstract class DerGenerator + : Asn1Generator + { + private bool _tagged = false; + private bool _isExplicit; + private int _tagNo; + + protected DerGenerator( + Stream outStream) + : base(outStream) + { + } + + protected DerGenerator( + Stream outStream, + int tagNo, + bool isExplicit) + : base(outStream) + { + _tagged = true; + _isExplicit = isExplicit; + _tagNo = tagNo; + } + + private static void WriteLength( + Stream outStr, + int length) + { + if (length > 127) + { + int size = 1; + int val = length; + + while ((val >>= 8) != 0) + { + size++; + } + + outStr.WriteByte((byte)(size | 0x80)); + + for (int i = (size - 1) * 8; i >= 0; i -= 8) + { + outStr.WriteByte((byte)(length >> i)); + } + } + else + { + outStr.WriteByte((byte)length); + } + } + + internal static void WriteDerEncoded( + Stream outStream, + int tag, + byte[] bytes) + { + outStream.WriteByte((byte) tag); + WriteLength(outStream, bytes.Length); + outStream.Write(bytes, 0, bytes.Length); + } + + internal void WriteDerEncoded( + int tag, + byte[] bytes) + { + if (_tagged) + { + int tagNum = _tagNo | Asn1Tags.Tagged; + + if (_isExplicit) + { + int newTag = _tagNo | Asn1Tags.Constructed | Asn1Tags.Tagged; + MemoryStream bOut = new MemoryStream(); + WriteDerEncoded(bOut, tag, bytes); + WriteDerEncoded(Out, newTag, bOut.ToArray()); + } + else + { + if ((tag & Asn1Tags.Constructed) != 0) + { + tagNum |= Asn1Tags.Constructed; + } + + WriteDerEncoded(Out, tagNum, bytes); + } + } + else + { + WriteDerEncoded(Out, tag, bytes); + } + } + + internal static void WriteDerEncoded( + Stream outStr, + int tag, + Stream inStr) + { + WriteDerEncoded(outStr, tag, Streams.ReadAll(inStr)); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/DEROctetStringParser.cs b/bc-sharp-crypto/src/asn1/DEROctetStringParser.cs new file mode 100644 index 0000000..b0d3ad8 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/DEROctetStringParser.cs @@ -0,0 +1,36 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Asn1 +{ + public class DerOctetStringParser + : Asn1OctetStringParser + { + private readonly DefiniteLengthInputStream stream; + + internal DerOctetStringParser( + DefiniteLengthInputStream stream) + { + this.stream = stream; + } + + public Stream GetOctetStream() + { + return stream; + } + + public Asn1Object ToAsn1Object() + { + try + { + return new DerOctetString(stream.ToArray()); + } + catch (IOException e) + { + throw new InvalidOperationException("IOException converting stream to byte array: " + e.Message, e); + } + } + } +} diff --git a/bc-sharp-crypto/src/asn1/DERSequenceGenerator.cs b/bc-sharp-crypto/src/asn1/DERSequenceGenerator.cs new file mode 100644 index 0000000..4c2bfd0 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/DERSequenceGenerator.cs @@ -0,0 +1,40 @@ +using System.IO; + +namespace Org.BouncyCastle.Asn1 +{ + public class DerSequenceGenerator + : DerGenerator + { + private readonly MemoryStream _bOut = new MemoryStream(); + + public DerSequenceGenerator( + Stream outStream) + : base(outStream) + { + } + + public DerSequenceGenerator( + Stream outStream, + int tagNo, + bool isExplicit) + : base(outStream, tagNo, isExplicit) + { + } + + public override void AddObject( + Asn1Encodable obj) + { + new DerOutputStream(_bOut).WriteObject(obj); + } + + public override Stream GetRawOutputStream() + { + return _bOut; + } + + public override void Close() + { + WriteDerEncoded(Asn1Tags.Constructed | Asn1Tags.Sequence, _bOut.ToArray()); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/DERSequenceParser.cs b/bc-sharp-crypto/src/asn1/DERSequenceParser.cs new file mode 100644 index 0000000..69c2b9b --- /dev/null +++ b/bc-sharp-crypto/src/asn1/DERSequenceParser.cs @@ -0,0 +1,24 @@ +namespace Org.BouncyCastle.Asn1 +{ + public class DerSequenceParser + : Asn1SequenceParser + { + private readonly Asn1StreamParser _parser; + + internal DerSequenceParser( + Asn1StreamParser parser) + { + this._parser = parser; + } + + public IAsn1Convertible ReadObject() + { + return _parser.ReadObject(); + } + + public Asn1Object ToAsn1Object() + { + return new DerSequence(_parser.ReadVector()); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/DERSetGenerator.cs b/bc-sharp-crypto/src/asn1/DERSetGenerator.cs new file mode 100644 index 0000000..455ca88 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/DERSetGenerator.cs @@ -0,0 +1,40 @@ +using System.IO; + +namespace Org.BouncyCastle.Asn1 +{ + public class DerSetGenerator + : DerGenerator + { + private readonly MemoryStream _bOut = new MemoryStream(); + + public DerSetGenerator( + Stream outStream) + : base(outStream) + { + } + + public DerSetGenerator( + Stream outStream, + int tagNo, + bool isExplicit) + : base(outStream, tagNo, isExplicit) + { + } + + public override void AddObject( + Asn1Encodable obj) + { + new DerOutputStream(_bOut).WriteObject(obj); + } + + public override Stream GetRawOutputStream() + { + return _bOut; + } + + public override void Close() + { + WriteDerEncoded(Asn1Tags.Constructed | Asn1Tags.Set, _bOut.ToArray()); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/DERSetParser.cs b/bc-sharp-crypto/src/asn1/DERSetParser.cs new file mode 100644 index 0000000..d67f135 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/DERSetParser.cs @@ -0,0 +1,24 @@ +namespace Org.BouncyCastle.Asn1 +{ + public class DerSetParser + : Asn1SetParser + { + private readonly Asn1StreamParser _parser; + + internal DerSetParser( + Asn1StreamParser parser) + { + this._parser = parser; + } + + public IAsn1Convertible ReadObject() + { + return _parser.ReadObject(); + } + + public Asn1Object ToAsn1Object() + { + return new DerSet(_parser.ReadVector(), false); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/DefiniteLengthInputStream.cs b/bc-sharp-crypto/src/asn1/DefiniteLengthInputStream.cs new file mode 100644 index 0000000..4ae803c --- /dev/null +++ b/bc-sharp-crypto/src/asn1/DefiniteLengthInputStream.cs @@ -0,0 +1,100 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Asn1 +{ + class DefiniteLengthInputStream + : LimitedInputStream + { + private static readonly byte[] EmptyBytes = new byte[0]; + + private readonly int _originalLength; + private int _remaining; + + internal DefiniteLengthInputStream( + Stream inStream, + int length) + : base(inStream, length) + { + if (length < 0) + throw new ArgumentException("negative lengths not allowed", "length"); + + this._originalLength = length; + this._remaining = length; + + if (length == 0) + { + SetParentEofDetect(true); + } + } + + internal int Remaining + { + get { return _remaining; } + } + + public override int ReadByte() + { + if (_remaining == 0) + return -1; + + int b = _in.ReadByte(); + + if (b < 0) + throw new EndOfStreamException("DEF length " + _originalLength + " object truncated by " + _remaining); + + if (--_remaining == 0) + { + SetParentEofDetect(true); + } + + return b; + } + + public override int Read( + byte[] buf, + int off, + int len) + { + if (_remaining == 0) + return 0; + + int toRead = System.Math.Min(len, _remaining); + int numRead = _in.Read(buf, off, toRead); + + if (numRead < 1) + throw new EndOfStreamException("DEF length " + _originalLength + " object truncated by " + _remaining); + + if ((_remaining -= numRead) == 0) + { + SetParentEofDetect(true); + } + + return numRead; + } + + internal void ReadAllIntoByteArray(byte[] buf) + { + if (_remaining != buf.Length) + throw new ArgumentException("buffer length not right for data"); + + if ((_remaining -= Streams.ReadFully(_in, buf)) != 0) + throw new EndOfStreamException("DEF length " + _originalLength + " object truncated by " + _remaining); + SetParentEofDetect(true); + } + + internal byte[] ToArray() + { + if (_remaining == 0) + return EmptyBytes; + + byte[] bytes = new byte[_remaining]; + if ((_remaining -= Streams.ReadFully(_in, bytes)) != 0) + throw new EndOfStreamException("DEF length " + _originalLength + " object truncated by " + _remaining); + SetParentEofDetect(true); + return bytes; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/DerApplicationSpecific.cs b/bc-sharp-crypto/src/asn1/DerApplicationSpecific.cs new file mode 100644 index 0000000..52467fa --- /dev/null +++ b/bc-sharp-crypto/src/asn1/DerApplicationSpecific.cs @@ -0,0 +1,237 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1 +{ + /** + * Base class for an application specific object + */ + public class DerApplicationSpecific + : Asn1Object + { + private readonly bool isConstructed; + private readonly int tag; + private readonly byte[] octets; + + internal DerApplicationSpecific( + bool 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, + Asn1Encodable obj) + : this(true, tag, obj) + { + } + + public DerApplicationSpecific( + bool isExplicit, + int tag, + Asn1Encodable obj) + { + Asn1Object asn1Obj = obj.ToAsn1Object(); + + byte[] data = asn1Obj.GetDerEncoded(); + + this.isConstructed = Asn1TaggedObject.IsConstructed(isExplicit, asn1Obj); + this.tag = tag; + + if (isExplicit) + { + this.octets = data; + } + else + { + int lenBytes = GetLengthOfHeader(data); + byte[] tmp = new byte[data.Length - lenBytes]; + Array.Copy(data, lenBytes, tmp, 0, tmp.Length); + this.octets = tmp; + } + } + + public DerApplicationSpecific( + int tagNo, + Asn1EncodableVector vec) + { + this.tag = tagNo; + this.isConstructed = true; + MemoryStream bOut = new MemoryStream(); + + for (int i = 0; i != vec.Count; i++) + { + try + { + byte[] bs = vec[i].GetDerEncoded(); + bOut.Write(bs, 0, bs.Length); + } + catch (IOException e) + { + throw new InvalidOperationException("malformed object", e); + } + } + this.octets = bOut.ToArray(); + } + + private int GetLengthOfHeader( + byte[] data) + { + int length = data[1]; // TODO: assumes 1 byte tag + + if (length == 0x80) + { + return 2; // indefinite-length encoding + } + + if (length > 127) + { + int size = length & 0x7f; + + // Note: The invalid long form "0xff" (see X.690 8.1.3.5c) will be caught here + if (size > 4) + { + throw new InvalidOperationException("DER length more than 4 bytes: " + size); + } + + return size + 2; + } + + return 2; + } + + public bool IsConstructed() + { + return isConstructed; + } + + public byte[] GetContents() + { + return octets; + } + + public int ApplicationTag + { + get { return tag; } + } + + /** + * Return the enclosed object assuming explicit tagging. + * + * @return the resulting object + * @throws IOException if reconstruction fails. + */ + public Asn1Object GetObject() + { + return FromByteArray(GetContents()); + } + + /** + * 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 Asn1Object GetObject( + int derTagNo) + { + if (derTagNo >= 0x1f) + throw new IOException("unsupported tag number"); + + byte[] orig = this.GetEncoded(); + byte[] tmp = ReplaceTagNumber(derTagNo, orig); + + if ((orig[0] & Asn1Tags.Constructed) != 0) + { + tmp[0] |= Asn1Tags.Constructed; + } + + return FromByteArray(tmp); + } + + internal override void Encode( + DerOutputStream derOut) + { + int classBits = Asn1Tags.Application; + if (isConstructed) + { + classBits |= Asn1Tags.Constructed; + } + + derOut.WriteEncoded(classBits, tag, octets); + } + + protected override bool Asn1Equals( + Asn1Object asn1Object) + { + DerApplicationSpecific other = asn1Object as DerApplicationSpecific; + + if (other == null) + return false; + + return this.isConstructed == other.isConstructed + && this.tag == other.tag + && Arrays.AreEqual(this.octets, other.octets); + } + + protected override int Asn1GetHashCode() + { + return isConstructed.GetHashCode() ^ tag.GetHashCode() ^ Arrays.GetHashCode(octets); + } + + private byte[] ReplaceTagNumber( + int newTag, + byte[] input) + { + 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 InvalidOperationException("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]; + + Array.Copy(input, index, tmp, 1, tmp.Length - 1); + + tmp[0] = (byte)newTag; + + return tmp; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/DerBMPString.cs b/bc-sharp-crypto/src/asn1/DerBMPString.cs new file mode 100644 index 0000000..33d950f --- /dev/null +++ b/bc-sharp-crypto/src/asn1/DerBMPString.cs @@ -0,0 +1,117 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1 +{ + /** + * Der BMPString object. + */ + public class DerBmpString + : DerStringBase + { + private readonly string str; + + /** + * return a BMP string from the given object. + * + * @param obj the object we want converted. + * @exception ArgumentException if the object cannot be converted. + */ + public static DerBmpString GetInstance( + object obj) + { + if (obj == null || obj is DerBmpString) + { + return (DerBmpString)obj; + } + + throw new ArgumentException("illegal object in GetInstance: " + Platform.GetTypeName(obj)); + } + + /** + * return a BMP string from a tagged object. + * + * @param obj the tagged object holding the object we want + * @param explicitly true if the object is meant to be explicitly + * tagged false otherwise. + * @exception ArgumentException if the tagged object cannot + * be converted. + */ + public static DerBmpString GetInstance( + Asn1TaggedObject obj, + bool isExplicit) + { + Asn1Object o = obj.GetObject(); + + if (isExplicit || o is DerBmpString) + { + return GetInstance(o); + } + + return new DerBmpString(Asn1OctetString.GetInstance(o).GetOctets()); + } + + /** + * basic constructor - byte encoded string. + */ + public DerBmpString( + byte[] str) + { + if (str == null) + throw new ArgumentNullException("str"); + + char[] cs = new char[str.Length / 2]; + + for (int i = 0; i != cs.Length; i++) + { + cs[i] = (char)((str[2 * i] << 8) | (str[2 * i + 1] & 0xff)); + } + + this.str = new string(cs); + } + + /** + * basic constructor + */ + public DerBmpString( + string str) + { + if (str == null) + throw new ArgumentNullException("str"); + + this.str = str; + } + + public override string GetString() + { + return str; + } + + protected override bool Asn1Equals( + Asn1Object asn1Object) + { + DerBmpString other = asn1Object as DerBmpString; + + if (other == null) + return false; + + return this.str.Equals(other.str); + } + + internal override void Encode( + DerOutputStream derOut) + { + char[] c = str.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]; + } + + derOut.WriteEncoded(Asn1Tags.BmpString, b); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/DerBitString.cs b/bc-sharp-crypto/src/asn1/DerBitString.cs new file mode 100644 index 0000000..26adc57 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/DerBitString.cs @@ -0,0 +1,276 @@ +using System; +using System.Diagnostics; +using System.Text; + +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1 +{ + public class DerBitString + : DerStringBase + { + private static readonly char[] table + = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + + protected readonly byte[] mData; + protected readonly int mPadBits; + + /** + * return a Bit string from the passed in object + * + * @exception ArgumentException if the object cannot be converted. + */ + public static DerBitString GetInstance( + object obj) + { + if (obj == null || obj is DerBitString) + { + return (DerBitString) obj; + } + if (obj is byte[]) + { + try + { + return (DerBitString)FromByteArray((byte[])obj); + } + catch (Exception e) + { + throw new ArgumentException("encoding error in GetInstance: " + e.ToString()); + } + } + + throw new ArgumentException("illegal object in GetInstance: " + Platform.GetTypeName(obj)); + } + + /** + * return a Bit string from a tagged object. + * + * @param obj the tagged object holding the object we want + * @param explicitly true if the object is meant to be explicitly + * tagged false otherwise. + * @exception ArgumentException if the tagged object cannot + * be converted. + */ + public static DerBitString GetInstance( + Asn1TaggedObject obj, + bool isExplicit) + { + Asn1Object o = obj.GetObject(); + + if (isExplicit || o is DerBitString) + { + return GetInstance(o); + } + + return FromAsn1Octets(((Asn1OctetString)o).GetOctets()); + } + + /** + * @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) + { + if (data == null) + throw new ArgumentNullException("data"); + if (padBits < 0 || padBits > 7) + throw new ArgumentException("must be in the range 0 to 7", "padBits"); + if (data.Length == 0 && padBits != 0) + throw new ArgumentException("if 'data' is empty, 'padBits' must be 0"); + + this.mData = Arrays.Clone(data); + this.mPadBits = padBits; + } + + public DerBitString( + byte[] data) + : this(data, 0) + { + } + + public DerBitString( + int namedBits) + { + if (namedBits == 0) + { + this.mData = new byte[0]; + this.mPadBits = 0; + return; + } + + int bits = BigInteger.BitLen(namedBits); + int bytes = (bits + 7) / 8; + + Debug.Assert(0 < bytes && bytes <= 4); + + byte[] data = new byte[bytes]; + --bytes; + + for (int i = 0; i < bytes; i++) + { + data[i] = (byte)namedBits; + namedBits >>= 8; + } + + Debug.Assert((namedBits & 0xFF) != 0); + + data[bytes] = (byte)namedBits; + + int padBits = 0; + while ((namedBits & (1 << padBits)) == 0) + { + ++padBits; + } + + Debug.Assert(padBits < 8); + + this.mData = data; + this.mPadBits = padBits; + } + + public DerBitString( + Asn1Encodable obj) + : this(obj.GetDerEncoded()) + { + } + + /** + * Return the octets contained in this BIT STRING, checking that this BIT STRING really + * does represent an octet aligned string. Only use this method when the standard you are + * following dictates that the BIT STRING will be octet aligned. + * + * @return a copy of the octet aligned data. + */ + public virtual byte[] GetOctets() + { + if (mPadBits != 0) + throw new InvalidOperationException("attempt to get non-octet aligned data from BIT STRING"); + + return Arrays.Clone(mData); + } + + public virtual byte[] GetBytes() + { + byte[] data = Arrays.Clone(mData); + + // DER requires pad bits be zero + if (mPadBits > 0) + { + data[data.Length - 1] &= (byte)(0xFF << mPadBits); + } + + return data; + } + + public virtual int PadBits + { + get { return mPadBits; } + } + + /** + * @return the value of the bit string as an int (truncating if necessary) + */ + public virtual int IntValue + { + get + { + int value = 0, length = System.Math.Min(4, mData.Length); + for (int i = 0; i < length; ++i) + { + value |= (int)mData[i] << (8 * i); + } + if (mPadBits > 0 && length == mData.Length) + { + int mask = (1 << mPadBits) - 1; + value &= ~(mask << (8 * (length - 1))); + } + return value; + } + } + + internal override void Encode( + DerOutputStream derOut) + { + if (mPadBits > 0) + { + int last = mData[mData.Length - 1]; + int mask = (1 << mPadBits) - 1; + int unusedBits = last & mask; + + if (unusedBits != 0) + { + byte[] contents = Arrays.Prepend(mData, (byte)mPadBits); + + /* + * X.690-0207 11.2.1: Each unused bit in the final octet of the encoding of a bit string value shall be set to zero. + */ + contents[contents.Length - 1] = (byte)(last ^ unusedBits); + + derOut.WriteEncoded(Asn1Tags.BitString, contents); + return; + } + } + + derOut.WriteEncoded(Asn1Tags.BitString, (byte)mPadBits, mData); + } + + protected override int Asn1GetHashCode() + { + return mPadBits.GetHashCode() ^ Arrays.GetHashCode(mData); + } + + protected override bool Asn1Equals( + Asn1Object asn1Object) + { + DerBitString other = asn1Object as DerBitString; + + if (other == null) + return false; + + return this.mPadBits == other.mPadBits + && Arrays.AreEqual(this.mData, other.mData); + } + + public override string GetString() + { + StringBuilder buffer = new StringBuilder("#"); + + byte[] str = GetDerEncoded(); + + for (int i = 0; i != str.Length; i++) + { + uint ubyte = str[i]; + buffer.Append(table[(ubyte >> 4) & 0xf]); + buffer.Append(table[str[i] & 0xf]); + } + + return buffer.ToString(); + } + + internal static DerBitString FromAsn1Octets(byte[] octets) + { + if (octets.Length < 1) + throw new ArgumentException("truncated BIT STRING detected", "octets"); + + int padBits = octets[0]; + byte[] data = Arrays.CopyOfRange(octets, 1, octets.Length); + + if (padBits > 0 && padBits < 8 && data.Length > 0) + { + int last = data[data.Length - 1]; + int mask = (1 << padBits) - 1; + + if ((last & mask) != 0) + { + return new BerBitString(data, padBits); + } + } + + return new DerBitString(data, padBits); + } + } +} + diff --git a/bc-sharp-crypto/src/asn1/DerBoolean.cs b/bc-sharp-crypto/src/asn1/DerBoolean.cs new file mode 100644 index 0000000..709f4dd --- /dev/null +++ b/bc-sharp-crypto/src/asn1/DerBoolean.cs @@ -0,0 +1,124 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1 +{ + public class DerBoolean + : Asn1Object + { + private readonly byte value; + + public static readonly DerBoolean False = new DerBoolean(false); + public static readonly DerBoolean True = new DerBoolean(true); + + /** + * return a bool from the passed in object. + * + * @exception ArgumentException if the object cannot be converted. + */ + public static DerBoolean GetInstance( + object obj) + { + if (obj == null || obj is DerBoolean) + { + return (DerBoolean) obj; + } + + throw new ArgumentException("illegal object in GetInstance: " + Platform.GetTypeName(obj)); + } + + /** + * return a DerBoolean from the passed in bool. + */ + public static DerBoolean GetInstance( + bool value) + { + return value ? True : False; + } + + /** + * return a Boolean from a tagged object. + * + * @param obj the tagged object holding the object we want + * @param explicitly true if the object is meant to be explicitly + * tagged false otherwise. + * @exception ArgumentException if the tagged object cannot + * be converted. + */ + public static DerBoolean GetInstance( + Asn1TaggedObject obj, + bool isExplicit) + { + Asn1Object o = obj.GetObject(); + + if (isExplicit || o is DerBoolean) + { + return GetInstance(o); + } + + return FromOctetString(((Asn1OctetString)o).GetOctets()); + } + + public DerBoolean( + byte[] val) + { + if (val.Length != 1) + throw new ArgumentException("byte value should have 1 byte in it", "val"); + + // TODO Are there any constraints on the possible byte values? + this.value = val[0]; + } + + private DerBoolean( + bool value) + { + this.value = value ? (byte)0xff : (byte)0; + } + + public bool IsTrue + { + get { return value != 0; } + } + + internal override void Encode( + DerOutputStream derOut) + { + // TODO Should we make sure the byte value is one of '0' or '0xff' here? + derOut.WriteEncoded(Asn1Tags.Boolean, new byte[]{ value }); + } + + protected override bool Asn1Equals( + Asn1Object asn1Object) + { + DerBoolean other = asn1Object as DerBoolean; + + if (other == null) + return false; + + return IsTrue == other.IsTrue; + } + + protected override int Asn1GetHashCode() + { + return IsTrue.GetHashCode(); + } + + public override string ToString() + { + return IsTrue ? "TRUE" : "FALSE"; + } + + internal static DerBoolean FromOctetString(byte[] value) + { + if (value.Length != 1) + { + throw new ArgumentException("BOOLEAN value should have 1 byte in it", "value"); + } + + byte b = value[0]; + + return b == 0 ? False : b == 0xFF ? True : new DerBoolean(value); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/DerEnumerated.cs b/bc-sharp-crypto/src/asn1/DerEnumerated.cs new file mode 100644 index 0000000..db27065 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/DerEnumerated.cs @@ -0,0 +1,135 @@ +using System; + +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1 +{ + public class DerEnumerated + : Asn1Object + { + private readonly byte[] bytes; + + /** + * return an integer from the passed in object + * + * @exception ArgumentException if the object cannot be converted. + */ + public static DerEnumerated GetInstance( + object obj) + { + if (obj == null || obj is DerEnumerated) + { + return (DerEnumerated)obj; + } + + throw new ArgumentException("illegal object in GetInstance: " + Platform.GetTypeName(obj)); + } + + /** + * return an Enumerated from a tagged object. + * + * @param obj the tagged object holding the object we want + * @param explicitly true if the object is meant to be explicitly + * tagged false otherwise. + * @exception ArgumentException if the tagged object cannot + * be converted. + */ + public static DerEnumerated GetInstance( + Asn1TaggedObject obj, + bool isExplicit) + { + Asn1Object o = obj.GetObject(); + + if (isExplicit || o is DerEnumerated) + { + return GetInstance(o); + } + + return FromOctetString(((Asn1OctetString)o).GetOctets()); + } + + public DerEnumerated( + int val) + { + bytes = BigInteger.ValueOf(val).ToByteArray(); + } + + public DerEnumerated( + BigInteger val) + { + bytes = val.ToByteArray(); + } + + public DerEnumerated( + byte[] bytes) + { + if (bytes.Length > 1) + { + if (bytes[0] == 0 && (bytes[1] & 0x80) == 0) + { + throw new ArgumentException("malformed enumerated"); + } + if (bytes[0] == (byte)0xff && (bytes[1] & 0x80) != 0) + { + throw new ArgumentException("malformed enumerated"); + } + } + this.bytes = Arrays.Clone(bytes); + } + + public BigInteger Value + { + get { return new BigInteger(bytes); } + } + + internal override void Encode( + DerOutputStream derOut) + { + derOut.WriteEncoded(Asn1Tags.Enumerated, bytes); + } + + protected override bool Asn1Equals( + Asn1Object asn1Object) + { + DerEnumerated other = asn1Object as DerEnumerated; + + if (other == null) + return false; + + return Arrays.AreEqual(this.bytes, other.bytes); + } + + protected override int Asn1GetHashCode() + { + return Arrays.GetHashCode(bytes); + } + + private static readonly DerEnumerated[] cache = new DerEnumerated[12]; + + internal static DerEnumerated FromOctetString(byte[] enc) + { + if (enc.Length == 0) + { + throw new ArgumentException("ENUMERATED has zero length", "enc"); + } + + if (enc.Length == 1) + { + int value = enc[0]; + if (value < cache.Length) + { + DerEnumerated cached = cache[value]; + if (cached != null) + { + return cached; + } + + return cache[value] = new DerEnumerated(Arrays.Clone(enc)); + } + } + + return new DerEnumerated(Arrays.Clone(enc)); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/DerGeneralString.cs b/bc-sharp-crypto/src/asn1/DerGeneralString.cs new file mode 100644 index 0000000..553b0e0 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/DerGeneralString.cs @@ -0,0 +1,81 @@ +using System; +using System.Text; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1 +{ + public class DerGeneralString + : DerStringBase + { + private readonly string str; + + public static DerGeneralString GetInstance( + object obj) + { + if (obj == null || obj is DerGeneralString) + { + return (DerGeneralString) obj; + } + + throw new ArgumentException("illegal object in GetInstance: " + + Platform.GetTypeName(obj)); + } + + public static DerGeneralString GetInstance( + Asn1TaggedObject obj, + bool isExplicit) + { + Asn1Object o = obj.GetObject(); + + if (isExplicit || o is DerGeneralString) + { + return GetInstance(o); + } + + return new DerGeneralString(((Asn1OctetString)o).GetOctets()); + } + + public DerGeneralString( + byte[] str) + : this(Strings.FromAsciiByteArray(str)) + { + } + + public DerGeneralString( + string str) + { + if (str == null) + throw new ArgumentNullException("str"); + + this.str = str; + } + + public override string GetString() + { + return str; + } + + public byte[] GetOctets() + { + return Strings.ToAsciiByteArray(str); + } + + internal override void Encode( + DerOutputStream derOut) + { + derOut.WriteEncoded(Asn1Tags.GeneralString, GetOctets()); + } + + protected override bool Asn1Equals( + Asn1Object asn1Object) + { + DerGeneralString other = asn1Object as DerGeneralString; + + if (other == null) + return false; + + return this.str.Equals(other.str); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/DerGeneralizedTime.cs b/bc-sharp-crypto/src/asn1/DerGeneralizedTime.cs new file mode 100644 index 0000000..b224ebe --- /dev/null +++ b/bc-sharp-crypto/src/asn1/DerGeneralizedTime.cs @@ -0,0 +1,320 @@ +using System; +using System.Globalization; +using System.Text; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1 +{ + /** + * Generalized time object. + */ + public class DerGeneralizedTime + : Asn1Object + { + private readonly string time; + + /** + * return a generalized time from the passed in object + * + * @exception ArgumentException if the object cannot be converted. + */ + public static DerGeneralizedTime GetInstance( + object obj) + { + if (obj == null || obj is DerGeneralizedTime) + { + return (DerGeneralizedTime)obj; + } + + throw new ArgumentException("illegal object in GetInstance: " + Platform.GetTypeName(obj), "obj"); + } + + /** + * return a Generalized Time object from a tagged object. + * + * @param obj the tagged object holding the object we want + * @param explicitly true if the object is meant to be explicitly + * tagged false otherwise. + * @exception ArgumentException if the tagged object cannot + * be converted. + */ + public static DerGeneralizedTime GetInstance( + Asn1TaggedObject obj, + bool isExplicit) + { + Asn1Object o = obj.GetObject(); + + if (isExplicit || o is DerGeneralizedTime) + { + return GetInstance(o); + } + + return new DerGeneralizedTime(((Asn1OctetString)o).GetOctets()); + } + + /** + * 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 ArgumentException if string is an illegal format. + */ + public DerGeneralizedTime( + string time) + { + this.time = time; + + try + { + ToDateTime(); + } + catch (FormatException e) + { + throw new ArgumentException("invalid date string: " + e.Message); + } + } + + /** + * base constructor from a local time object + */ + public DerGeneralizedTime( + DateTime time) + { +#if PORTABLE + this.time = time.ToUniversalTime().ToString(@"yyyyMMddHHmmss\Z"); +#else + this.time = time.ToString(@"yyyyMMddHHmmss\Z"); +#endif + } + + internal DerGeneralizedTime( + byte[] bytes) + { + // + // explicitly convert to characters + // + this.time = Strings.FromAsciiByteArray(bytes); + } + + /** + * Return the time. + * @return The time string as it appeared in the encoded object. + */ + public string TimeString + { + get { 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[time.Length - 1] == 'Z') + { + return time.Substring(0, time.Length - 1) + "GMT+00:00"; + } + else + { + int signPos = time.Length - 5; + char sign = time[signPos]; + if (sign == '-' || sign == '+') + { + return time.Substring(0, signPos) + + "GMT" + + time.Substring(signPos, 3) + + ":" + + time.Substring(signPos + 3); + } + else + { + signPos = time.Length - 3; + sign = time[signPos]; + if (sign == '-' || sign == '+') + { + return time.Substring(0, signPos) + + "GMT" + + time.Substring(signPos) + + ":00"; + } + } + } + + return time + CalculateGmtOffset(); + } + + private string CalculateGmtOffset() + { + char sign = '+'; + DateTime time = ToDateTime(); + +#if SILVERLIGHT || PORTABLE + long offset = time.Ticks - time.ToUniversalTime().Ticks; + if (offset < 0) + { + sign = '-'; + offset = -offset; + } + int hours = (int)(offset / TimeSpan.TicksPerHour); + int minutes = (int)(offset / TimeSpan.TicksPerMinute) % 60; +#else + // Note: GetUtcOffset incorporates Daylight Savings offset + TimeSpan offset = TimeZone.CurrentTimeZone.GetUtcOffset(time); + if (offset.CompareTo(TimeSpan.Zero) < 0) + { + sign = '-'; + offset = offset.Duration(); + } + int hours = offset.Hours; + int minutes = offset.Minutes; +#endif + + return "GMT" + sign + Convert(hours) + ":" + Convert(minutes); + } + + private static string Convert( + int time) + { + if (time < 10) + { + return "0" + time; + } + + return time.ToString(); + } + + public DateTime ToDateTime() + { + string formatStr; + string d = time; + bool makeUniversal = false; + + if (Platform.EndsWith(d, "Z")) + { + if (HasFractionalSeconds) + { + int fCount = d.Length - d.IndexOf('.') - 2; + formatStr = @"yyyyMMddHHmmss." + FString(fCount) + @"\Z"; + } + else + { + formatStr = @"yyyyMMddHHmmss\Z"; + } + } + else if (time.IndexOf('-') > 0 || time.IndexOf('+') > 0) + { + d = GetTime(); + makeUniversal = true; + + if (HasFractionalSeconds) + { + int fCount = Platform.IndexOf(d, "GMT") - 1 - d.IndexOf('.'); + formatStr = @"yyyyMMddHHmmss." + FString(fCount) + @"'GMT'zzz"; + } + else + { + formatStr = @"yyyyMMddHHmmss'GMT'zzz"; + } + } + else + { + if (HasFractionalSeconds) + { + int fCount = d.Length - 1 - d.IndexOf('.'); + formatStr = @"yyyyMMddHHmmss." + FString(fCount); + } + else + { + formatStr = @"yyyyMMddHHmmss"; + } + + // TODO? +// dateF.setTimeZone(new SimpleTimeZone(0, TimeZone.getDefault().getID())); + } + + return ParseDateString(d, formatStr, makeUniversal); + } + + private string FString( + int count) + { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < count; ++i) + { + sb.Append('f'); + } + return sb.ToString(); + } + + private DateTime ParseDateString(string s, string format, bool makeUniversal) + { + /* + * NOTE: DateTime.Kind and DateTimeStyles.AssumeUniversal not available in .NET 1.1 + */ + DateTimeStyles style = DateTimeStyles.None; + if (Platform.EndsWith(format, "Z")) + { + try + { + style = (DateTimeStyles)Enums.GetEnumValue(typeof(DateTimeStyles), "AssumeUniversal"); + } + catch (Exception) + { + } + + style |= DateTimeStyles.AdjustToUniversal; + } + + DateTime dt = DateTime.ParseExact(s, format, DateTimeFormatInfo.InvariantInfo, style); + + return makeUniversal ? dt.ToUniversalTime() : dt; + } + + private bool HasFractionalSeconds + { + get { return time.IndexOf('.') == 14; } + } + + private byte[] GetOctets() + { + return Strings.ToAsciiByteArray(time); + } + + internal override void Encode( + DerOutputStream derOut) + { + derOut.WriteEncoded(Asn1Tags.GeneralizedTime, GetOctets()); + } + + protected override bool Asn1Equals( + Asn1Object asn1Object) + { + DerGeneralizedTime other = asn1Object as DerGeneralizedTime; + + if (other == null) + return false; + + return this.time.Equals(other.time); + } + + protected override int Asn1GetHashCode() + { + return time.GetHashCode(); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/DerGraphicString.cs b/bc-sharp-crypto/src/asn1/DerGraphicString.cs new file mode 100644 index 0000000..f213f46 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/DerGraphicString.cs @@ -0,0 +1,103 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1 +{ + public class DerGraphicString + : DerStringBase + { + private readonly byte[] mString; + + /** + * return a Graphic String from the passed in object + * + * @param obj a DerGraphicString or an object that can be converted into one. + * @exception IllegalArgumentException if the object cannot be converted. + * @return a DerGraphicString instance, or null. + */ + public static DerGraphicString GetInstance(object obj) + { + if (obj == null || obj is DerGraphicString) + { + return (DerGraphicString)obj; + } + + if (obj is byte[]) + { + try + { + return (DerGraphicString)FromByteArray((byte[])obj); + } + catch (Exception e) + { + throw new ArgumentException("encoding error in GetInstance: " + e.ToString(), "obj"); + } + } + + throw new ArgumentException("illegal object in GetInstance: " + Platform.GetTypeName(obj), "obj"); + } + + /** + * return a Graphic 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. + * @return a DerGraphicString instance, or null. + */ + public static DerGraphicString GetInstance(Asn1TaggedObject obj, bool isExplicit) + { + Asn1Object o = obj.GetObject(); + + if (isExplicit || o is DerGraphicString) + { + return GetInstance(o); + } + + return new DerGraphicString(((Asn1OctetString)o).GetOctets()); + } + + /** + * basic constructor - with bytes. + * @param string the byte encoding of the characters making up the string. + */ + public DerGraphicString(byte[] encoding) + { + this.mString = Arrays.Clone(encoding); + } + + public override string GetString() + { + return Strings.FromByteArray(mString); + } + + public byte[] GetOctets() + { + return Arrays.Clone(mString); + } + + internal override void Encode(DerOutputStream derOut) + { + derOut.WriteEncoded(Asn1Tags.GraphicString, mString); + } + + protected override int Asn1GetHashCode() + { + return Arrays.GetHashCode(mString); + } + + protected override bool Asn1Equals( + Asn1Object asn1Object) + { + DerGraphicString other = asn1Object as DerGraphicString; + + if (other == null) + return false; + + return Arrays.AreEqual(mString, other.mString); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/DerIA5String.cs b/bc-sharp-crypto/src/asn1/DerIA5String.cs new file mode 100644 index 0000000..63e9158 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/DerIA5String.cs @@ -0,0 +1,145 @@ +using System; +using System.Text; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1 +{ + /** + * Der IA5String object - this is an ascii string. + */ + public class DerIA5String + : DerStringBase + { + private readonly string str; + + /** + * return a IA5 string from the passed in object + * + * @exception ArgumentException if the object cannot be converted. + */ + public static DerIA5String GetInstance( + object obj) + { + if (obj == null || obj is DerIA5String) + { + return (DerIA5String)obj; + } + + throw new ArgumentException("illegal object in GetInstance: " + Platform.GetTypeName(obj)); + } + + /** + * return an IA5 string from a tagged object. + * + * @param obj the tagged object holding the object we want + * @param explicitly true if the object is meant to be explicitly + * tagged false otherwise. + * @exception ArgumentException if the tagged object cannot + * be converted. + */ + public static DerIA5String GetInstance( + Asn1TaggedObject obj, + bool isExplicit) + { + Asn1Object o = obj.GetObject(); + + if (isExplicit || o is DerIA5String) + { + return GetInstance(o); + } + + return new DerIA5String(((Asn1OctetString)o).GetOctets()); + } + + /** + * basic constructor - with bytes. + */ + public DerIA5String( + byte[] str) + : this(Strings.FromAsciiByteArray(str), false) + { + } + + /** + * basic constructor - without validation. + */ + public DerIA5String( + string str) + : this(str, false) + { + } + + /** + * Constructor with optional validation. + * + * @param string the base string to wrap. + * @param validate whether or not to check the string. + * @throws ArgumentException if validate is true and the string + * contains characters that should not be in an IA5String. + */ + public DerIA5String( + string str, + bool validate) + { + if (str == null) + throw new ArgumentNullException("str"); + if (validate && !IsIA5String(str)) + throw new ArgumentException("string contains illegal characters", "str"); + + this.str = str; + } + + public override string GetString() + { + return str; + } + + public byte[] GetOctets() + { + return Strings.ToAsciiByteArray(str); + } + + internal override void Encode( + DerOutputStream derOut) + { + derOut.WriteEncoded(Asn1Tags.IA5String, GetOctets()); + } + + protected override int Asn1GetHashCode() + { + return this.str.GetHashCode(); + } + + protected override bool Asn1Equals( + Asn1Object asn1Object) + { + DerIA5String other = asn1Object as DerIA5String; + + if (other == null) + return false; + + return this.str.Equals(other.str); + } + + /** + * 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 bool IsIA5String( + string str) + { + foreach (char ch in str) + { + if (ch > 0x007f) + { + return false; + } + } + + return true; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/DerInteger.cs b/bc-sharp-crypto/src/asn1/DerInteger.cs new file mode 100644 index 0000000..5b240d2 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/DerInteger.cs @@ -0,0 +1,128 @@ +using System; + +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1 +{ + public class DerInteger + : Asn1Object + { + private readonly byte[] bytes; + + /** + * return an integer from the passed in object + * + * @exception ArgumentException if the object cannot be converted. + */ + public static DerInteger GetInstance( + object obj) + { + if (obj == null || obj is DerInteger) + { + return (DerInteger)obj; + } + + throw new ArgumentException("illegal object in GetInstance: " + Platform.GetTypeName(obj)); + } + + /** + * return an Integer from a tagged object. + * + * @param obj the tagged object holding the object we want + * @param isExplicit true if the object is meant to be explicitly + * tagged false otherwise. + * @exception ArgumentException if the tagged object cannot + * be converted. + */ + public static DerInteger GetInstance( + Asn1TaggedObject obj, + bool isExplicit) + { + if (obj == null) + throw new ArgumentNullException("obj"); + + Asn1Object o = obj.GetObject(); + + if (isExplicit || o is DerInteger) + { + return GetInstance(o); + } + + return new DerInteger(Asn1OctetString.GetInstance(o).GetOctets()); + } + + public DerInteger( + int value) + { + bytes = BigInteger.ValueOf(value).ToByteArray(); + } + + public DerInteger( + BigInteger value) + { + if (value == null) + throw new ArgumentNullException("value"); + + bytes = value.ToByteArray(); + } + + public DerInteger( + byte[] bytes) + { + if (bytes.Length > 1) + { + if (bytes[0] == 0 && (bytes[1] & 0x80) == 0) + { + throw new ArgumentException("malformed integer"); + } + if (bytes[0] == (byte)0xff && (bytes[1] & 0x80) != 0) + { + throw new ArgumentException("malformed integer"); + } + } + this.bytes = Arrays.Clone(bytes); + } + + public BigInteger Value + { + get { return new BigInteger(bytes); } + } + + /** + * in some cases positive values Get crammed into a space, + * that's not quite big enough... + */ + public BigInteger PositiveValue + { + get { return new BigInteger(1, bytes); } + } + + internal override void Encode( + DerOutputStream derOut) + { + derOut.WriteEncoded(Asn1Tags.Integer, bytes); + } + + protected override int Asn1GetHashCode() + { + return Arrays.GetHashCode(bytes); + } + + protected override bool Asn1Equals( + Asn1Object asn1Object) + { + DerInteger other = asn1Object as DerInteger; + + if (other == null) + return false; + + return Arrays.AreEqual(this.bytes, other.bytes); + } + + public override string ToString() + { + return Value.ToString(); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/DerNull.cs b/bc-sharp-crypto/src/asn1/DerNull.cs new file mode 100644 index 0000000..a802f64 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/DerNull.cs @@ -0,0 +1,41 @@ +using System; + +namespace Org.BouncyCastle.Asn1 +{ + /** + * A Null object. + */ + public class DerNull + : Asn1Null + { + public static readonly DerNull Instance = new DerNull(0); + + byte[] zeroBytes = new byte[0]; + + [Obsolete("Use static Instance object")] + public DerNull() + { + } + + protected internal DerNull(int dummy) + { + } + + internal override void Encode( + DerOutputStream derOut) + { + derOut.WriteEncoded(Asn1Tags.Null, zeroBytes); + } + + protected override bool Asn1Equals( + Asn1Object asn1Object) + { + return asn1Object is DerNull; + } + + protected override int Asn1GetHashCode() + { + return -1; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/DerNumericString.cs b/bc-sharp-crypto/src/asn1/DerNumericString.cs new file mode 100644 index 0000000..a729f9e --- /dev/null +++ b/bc-sharp-crypto/src/asn1/DerNumericString.cs @@ -0,0 +1,138 @@ +using System; +using System.Text; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1 +{ + /** + * Der NumericString object - this is an ascii string of characters {0,1,2,3,4,5,6,7,8,9, }. + */ + public class DerNumericString + : DerStringBase + { + private readonly string str; + + /** + * return a Numeric string from the passed in object + * + * @exception ArgumentException if the object cannot be converted. + */ + public static DerNumericString GetInstance( + object obj) + { + if (obj == null || obj is DerNumericString) + { + return (DerNumericString)obj; + } + + throw new ArgumentException("illegal object in GetInstance: " + Platform.GetTypeName(obj)); + } + + /** + * return an Numeric string from a tagged object. + * + * @param obj the tagged object holding the object we want + * @param explicitly true if the object is meant to be explicitly + * tagged false otherwise. + * @exception ArgumentException if the tagged object cannot + * be converted. + */ + public static DerNumericString GetInstance( + Asn1TaggedObject obj, + bool isExplicit) + { + Asn1Object o = obj.GetObject(); + + if (isExplicit || o is DerNumericString) + { + return GetInstance(o); + } + + return new DerNumericString(Asn1OctetString.GetInstance(o).GetOctets()); + } + + /** + * basic constructor - with bytes. + */ + public DerNumericString( + byte[] str) + : this(Strings.FromAsciiByteArray(str), false) + { + } + + /** + * basic constructor - without validation.. + */ + public DerNumericString( + string str) + : this(str, false) + { + } + + /** + * Constructor with optional validation. + * + * @param string the base string to wrap. + * @param validate whether or not to check the string. + * @throws ArgumentException if validate is true and the string + * contains characters that should not be in a NumericString. + */ + public DerNumericString( + string str, + bool validate) + { + if (str == null) + throw new ArgumentNullException("str"); + if (validate && !IsNumericString(str)) + throw new ArgumentException("string contains illegal characters", "str"); + + this.str = str; + } + + public override string GetString() + { + return str; + } + + public byte[] GetOctets() + { + return Strings.ToAsciiByteArray(str); + } + + internal override void Encode( + DerOutputStream derOut) + { + derOut.WriteEncoded(Asn1Tags.NumericString, GetOctets()); + } + + protected override bool Asn1Equals( + Asn1Object asn1Object) + { + DerNumericString other = asn1Object as DerNumericString; + + if (other == null) + return false; + + return this.str.Equals(other.str); + } + + /** + * 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 bool IsNumericString( + string str) + { + foreach (char ch in str) + { + if (ch > 0x007f || (ch != ' ' && !char.IsDigit(ch))) + return false; + } + + return true; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/DerObjectIdentifier.cs b/bc-sharp-crypto/src/asn1/DerObjectIdentifier.cs new file mode 100644 index 0000000..6ac2b7e --- /dev/null +++ b/bc-sharp-crypto/src/asn1/DerObjectIdentifier.cs @@ -0,0 +1,347 @@ +using System; +using System.IO; +using System.Text; +using System.Text.RegularExpressions; + +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1 +{ + public class DerObjectIdentifier + : Asn1Object + { + private readonly string identifier; + + private byte[] body = null; + + /** + * return an Oid from the passed in object + * + * @exception ArgumentException if the object cannot be converted. + */ + public static DerObjectIdentifier GetInstance(object obj) + { + if (obj == null || obj is DerObjectIdentifier) + return (DerObjectIdentifier) obj; + if (obj is byte[]) + return FromOctetString((byte[])obj); + throw new ArgumentException("illegal object in GetInstance: " + Platform.GetTypeName(obj), "obj"); + } + + /** + * return an object Identifier from a tagged object. + * + * @param obj the tagged object holding the object we want + * @param explicitly true if the object is meant to be explicitly + * tagged false otherwise. + * @exception ArgumentException if the tagged object cannot + * be converted. + */ + public static DerObjectIdentifier GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(obj.GetObject()); + } + + public DerObjectIdentifier( + string identifier) + { + if (identifier == null) + throw new ArgumentNullException("identifier"); + if (!IsValidIdentifier(identifier)) + throw new FormatException("string " + identifier + " not an OID"); + + this.identifier = identifier; + } + + internal DerObjectIdentifier(DerObjectIdentifier oid, string branchID) + { + if (!IsValidBranchID(branchID, 0)) + throw new ArgumentException("string " + branchID + " not a valid OID branch", "branchID"); + + this.identifier = oid.Id + "." + branchID; + } + + // TODO Change to ID? + public string Id + { + get { return identifier; } + } + + public virtual DerObjectIdentifier Branch(string branchID) + { + return new DerObjectIdentifier(this, branchID); + } + + /** + * Return true if this oid is an extension of the passed in branch, stem. + * @param stem the arc or branch that is a possible parent. + * @return true if the branch is on the passed in stem, false otherwise. + */ + public virtual bool On(DerObjectIdentifier stem) + { + string id = Id, stemId = stem.Id; + return id.Length > stemId.Length && id[stemId.Length] == '.' && Platform.StartsWith(id, stemId); + } + + internal DerObjectIdentifier(byte[] bytes) + { + this.identifier = MakeOidStringFromBytes(bytes); + this.body = Arrays.Clone(bytes); + } + + private void WriteField( + Stream outputStream, + long fieldValue) + { + byte[] result = new byte[9]; + int pos = 8; + result[pos] = (byte)(fieldValue & 0x7f); + while (fieldValue >= (1L << 7)) + { + fieldValue >>= 7; + result[--pos] = (byte)((fieldValue & 0x7f) | 0x80); + } + outputStream.Write(result, pos, 9 - pos); + } + + private void WriteField( + Stream outputStream, + BigInteger fieldValue) + { + int byteCount = (fieldValue.BitLength + 6) / 7; + if (byteCount == 0) + { + outputStream.WriteByte(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; + outputStream.Write(tmp, 0, tmp.Length); + } + } + + private void DoOutput(MemoryStream bOut) + { + OidTokenizer tok = new OidTokenizer(identifier); + + string token = tok.NextToken(); + int first = int.Parse(token) * 40; + + token = tok.NextToken(); + if (token.Length <= 18) + { + WriteField(bOut, first + Int64.Parse(token)); + } + else + { + WriteField(bOut, new BigInteger(token).Add(BigInteger.ValueOf(first))); + } + + while (tok.HasMoreTokens) + { + token = tok.NextToken(); + if (token.Length <= 18) + { + WriteField(bOut, Int64.Parse(token)); + } + else + { + WriteField(bOut, new BigInteger(token)); + } + } + } + + internal byte[] GetBody() + { + lock (this) + { + if (body == null) + { + MemoryStream bOut = new MemoryStream(); + DoOutput(bOut); + body = bOut.ToArray(); + } + } + + return body; + } + + internal override void Encode( + DerOutputStream derOut) + { + derOut.WriteEncoded(Asn1Tags.ObjectIdentifier, GetBody()); + } + + protected override int Asn1GetHashCode() + { + return identifier.GetHashCode(); + } + + protected override bool Asn1Equals( + Asn1Object asn1Object) + { + DerObjectIdentifier other = asn1Object as DerObjectIdentifier; + + if (other == null) + return false; + + return this.identifier.Equals(other.identifier); + } + + public override string ToString() + { + return identifier; + } + + private static bool IsValidBranchID( + String branchID, int start) + { + bool periodAllowed = false; + + int pos = branchID.Length; + while (--pos >= start) + { + char ch = branchID[pos]; + + // TODO Leading zeroes? + if ('0' <= ch && ch <= '9') + { + periodAllowed = true; + continue; + } + + if (ch == '.') + { + if (!periodAllowed) + return false; + + periodAllowed = false; + continue; + } + + return false; + } + + return periodAllowed; + } + + private static bool IsValidIdentifier(string identifier) + { + if (identifier.Length < 3 || identifier[1] != '.') + return false; + + char first = identifier[0]; + if (first < '0' || first > '2') + return false; + + return IsValidBranchID(identifier, 2); + } + + private const long LONG_LIMIT = (long.MaxValue >> 7) - 0x7f; + + private static string MakeOidStringFromBytes( + byte[] bytes) + { + StringBuilder objId = new StringBuilder(); + long value = 0; + BigInteger bigValue = null; + bool first = true; + + for (int i = 0; i != bytes.Length; i++) + { + int b = bytes[i]; + + if (value <= LONG_LIMIT) + { + value += (b & 0x7f); + if ((b & 0x80) == 0) // end of number reached + { + if (first) + { + if (value < 40) + { + objId.Append('0'); + } + else if (value < 80) + { + objId.Append('1'); + value -= 40; + } + else + { + objId.Append('2'); + value -= 80; + } + first = false; + } + + objId.Append('.'); + objId.Append(value); + value = 0; + } + else + { + value <<= 7; + } + } + else + { + if (bigValue == null) + { + bigValue = BigInteger.ValueOf(value); + } + bigValue = bigValue.Or(BigInteger.ValueOf(b & 0x7f)); + if ((b & 0x80) == 0) + { + if (first) + { + objId.Append('2'); + bigValue = bigValue.Subtract(BigInteger.ValueOf(80)); + first = false; + } + + objId.Append('.'); + objId.Append(bigValue); + bigValue = null; + value = 0; + } + else + { + bigValue = bigValue.ShiftLeft(7); + } + } + } + + return objId.ToString(); + } + + private static readonly DerObjectIdentifier[] cache = new DerObjectIdentifier[1024]; + + internal static DerObjectIdentifier FromOctetString(byte[] enc) + { + int hashCode = Arrays.GetHashCode(enc); + int first = hashCode & 1023; + + lock (cache) + { + DerObjectIdentifier entry = cache[first]; + if (entry != null && Arrays.AreEqual(enc, entry.GetBody())) + { + return entry; + } + + return cache[first] = new DerObjectIdentifier(enc); + } + } + } +} diff --git a/bc-sharp-crypto/src/asn1/DerOctetString.cs b/bc-sharp-crypto/src/asn1/DerOctetString.cs new file mode 100644 index 0000000..c046c94 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/DerOctetString.cs @@ -0,0 +1,34 @@ +namespace Org.BouncyCastle.Asn1 +{ + public class DerOctetString + : Asn1OctetString + { + /// The octets making up the octet string. + public DerOctetString( + byte[] str) + : base(str) + { + } + + public DerOctetString( + Asn1Encodable obj) + : base(obj) + { + } + + internal override void Encode( + DerOutputStream derOut) + { + derOut.WriteEncoded(Asn1Tags.OctetString, str); + } + + internal static void Encode( + DerOutputStream derOut, + byte[] bytes, + int offset, + int length) + { + derOut.WriteEncoded(Asn1Tags.OctetString, bytes, offset, length); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/DerOutputStream.cs b/bc-sharp-crypto/src/asn1/DerOutputStream.cs new file mode 100644 index 0000000..69d5d5f --- /dev/null +++ b/bc-sharp-crypto/src/asn1/DerOutputStream.cs @@ -0,0 +1,171 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Asn1 +{ + public class DerOutputStream + : FilterStream + { + public DerOutputStream(Stream os) + : base(os) + { + } + + private void WriteLength( + int length) + { + if (length > 127) + { + int size = 1; + uint val = (uint)length; + + while ((val >>= 8) != 0) + { + size++; + } + + WriteByte((byte)(size | 0x80)); + + for (int i = (size - 1) * 8; i >= 0; i -= 8) + { + WriteByte((byte)(length >> i)); + } + } + else + { + WriteByte((byte)length); + } + } + + internal void WriteEncoded( + int tag, + byte[] bytes) + { + WriteByte((byte)tag); + WriteLength(bytes.Length); + Write(bytes, 0, bytes.Length); + } + + internal void WriteEncoded( + int tag, + byte first, + byte[] bytes) + { + WriteByte((byte)tag); + WriteLength(bytes.Length + 1); + WriteByte(first); + Write(bytes, 0, bytes.Length); + } + + internal void WriteEncoded( + int tag, + byte[] bytes, + int offset, + int length) + { + WriteByte((byte)tag); + WriteLength(length); + Write(bytes, offset, length); + } + + internal void WriteTag( + int flags, + int tagNo) + { + if (tagNo < 31) + { + WriteByte((byte)(flags | tagNo)); + } + else + { + WriteByte((byte)(flags | 0x1f)); + if (tagNo < 128) + { + WriteByte((byte)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); + } + } + } + + internal void WriteEncoded( + int flags, + int tagNo, + byte[] bytes) + { + WriteTag(flags, tagNo); + WriteLength(bytes.Length); + Write(bytes, 0, bytes.Length); + } + + protected void WriteNull() + { + WriteByte(Asn1Tags.Null); + WriteByte(0x00); + } + + [Obsolete("Use version taking an Asn1Encodable arg instead")] + public virtual void WriteObject( + object obj) + { + if (obj == null) + { + WriteNull(); + } + else if (obj is Asn1Object) + { + ((Asn1Object)obj).Encode(this); + } + else if (obj is Asn1Encodable) + { + ((Asn1Encodable)obj).ToAsn1Object().Encode(this); + } + else + { + throw new IOException("object not Asn1Object"); + } + } + + public virtual void WriteObject( + Asn1Encodable obj) + { + if (obj == null) + { + WriteNull(); + } + else + { + obj.ToAsn1Object().Encode(this); + } + } + + public virtual void WriteObject( + Asn1Object obj) + { + if (obj == null) + { + WriteNull(); + } + else + { + obj.Encode(this); + } + } + } +} diff --git a/bc-sharp-crypto/src/asn1/DerPrintableString.cs b/bc-sharp-crypto/src/asn1/DerPrintableString.cs new file mode 100644 index 0000000..e179734 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/DerPrintableString.cs @@ -0,0 +1,163 @@ +using System; +using System.Text; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1 +{ + /** + * Der PrintableString object. + */ + public class DerPrintableString + : DerStringBase + { + private readonly string str; + + /** + * return a printable string from the passed in object. + * + * @exception ArgumentException if the object cannot be converted. + */ + public static DerPrintableString GetInstance( + object obj) + { + if (obj == null || obj is DerPrintableString) + { + return (DerPrintableString)obj; + } + + throw new ArgumentException("illegal object in GetInstance: " + Platform.GetTypeName(obj)); + } + + /** + * return a Printable string from a tagged object. + * + * @param obj the tagged object holding the object we want + * @param explicitly true if the object is meant to be explicitly + * tagged false otherwise. + * @exception ArgumentException if the tagged object cannot + * be converted. + */ + public static DerPrintableString GetInstance( + Asn1TaggedObject obj, + bool isExplicit) + { + Asn1Object o = obj.GetObject(); + + if (isExplicit || o is DerPrintableString) + { + return GetInstance(o); + } + + return new DerPrintableString(Asn1OctetString.GetInstance(o).GetOctets()); + } + + /** + * basic constructor - byte encoded string. + */ + public DerPrintableString( + byte[] str) + : this(Strings.FromAsciiByteArray(str), false) + { + } + + /** + * basic constructor - this does not validate the string + */ + public DerPrintableString( + string str) + : this(str, false) + { + } + + /** + * Constructor with optional validation. + * + * @param string the base string to wrap. + * @param validate whether or not to check the string. + * @throws ArgumentException if validate is true and the string + * contains characters that should not be in a PrintableString. + */ + public DerPrintableString( + string str, + bool validate) + { + if (str == null) + throw new ArgumentNullException("str"); + if (validate && !IsPrintableString(str)) + throw new ArgumentException("string contains illegal characters", "str"); + + this.str = str; + } + + public override string GetString() + { + return str; + } + + public byte[] GetOctets() + { + return Strings.ToAsciiByteArray(str); + } + + internal override void Encode( + DerOutputStream derOut) + { + derOut.WriteEncoded(Asn1Tags.PrintableString, GetOctets()); + } + + protected override bool Asn1Equals( + Asn1Object asn1Object) + { + DerPrintableString other = asn1Object as DerPrintableString; + + if (other == null) + return false; + + return this.str.Equals(other.str); + } + + /** + * 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 bool IsPrintableString( + string str) + { + foreach (char ch in str) + { + if (ch > 0x007f) + return false; + + if (char.IsLetterOrDigit(ch)) + continue; + +// if (char.IsPunctuation(ch)) +// continue; + + switch (ch) + { + case ' ': + case '\'': + case '(': + case ')': + case '+': + case '-': + case '.': + case ':': + case '=': + case '?': + case '/': + case ',': + continue; + } + + return false; + } + + return true; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/DerSequence.cs b/bc-sharp-crypto/src/asn1/DerSequence.cs new file mode 100644 index 0000000..a76cf28 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/DerSequence.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1 +{ + public class DerSequence + : Asn1Sequence + { + public static readonly DerSequence Empty = new DerSequence(); + + public static DerSequence FromVector( + Asn1EncodableVector v) + { + return v.Count < 1 ? Empty : new DerSequence(v); + } + + /** + * create an empty sequence + */ + public DerSequence() + : base(0) + { + } + + /** + * create a sequence containing one object + */ + public DerSequence( + Asn1Encodable obj) + : base(1) + { + AddObject(obj); + } + + public DerSequence( + params Asn1Encodable[] v) + : base(v.Length) + { + foreach (Asn1Encodable ae in v) + { + AddObject(ae); + } + } + + /** + * create a sequence containing a vector of objects. + */ + public DerSequence( + Asn1EncodableVector v) + : base(v.Count) + { + foreach (Asn1Encodable ae in v) + { + AddObject(ae); + } + } + + /* + * 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. + */ + internal override void Encode( + DerOutputStream derOut) + { + // TODO Intermediate buffer could be avoided if we could calculate expected length + MemoryStream bOut = new MemoryStream(); + DerOutputStream dOut = new DerOutputStream(bOut); + + foreach (Asn1Encodable obj in this) + { + dOut.WriteObject(obj); + } + + Platform.Dispose(dOut); + + byte[] bytes = bOut.ToArray(); + + derOut.WriteEncoded(Asn1Tags.Sequence | Asn1Tags.Constructed, bytes); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/DerSet.cs b/bc-sharp-crypto/src/asn1/DerSet.cs new file mode 100644 index 0000000..3df1a67 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/DerSet.cs @@ -0,0 +1,111 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1 +{ + /** + * A Der encoded set object + */ + public class DerSet + : Asn1Set + { + public static readonly DerSet Empty = new DerSet(); + + public static DerSet FromVector( + Asn1EncodableVector v) + { + return v.Count < 1 ? Empty : new DerSet(v); + } + + internal static DerSet FromVector( + Asn1EncodableVector v, + bool needsSorting) + { + return v.Count < 1 ? Empty : new DerSet(v, needsSorting); + } + + /** + * create an empty set + */ + public DerSet() + : base(0) + { + } + + /** + * @param obj - a single object that makes up the set. + */ + public DerSet( + Asn1Encodable obj) + : base(1) + { + AddObject(obj); + } + + public DerSet( + params Asn1Encodable[] v) + : base(v.Length) + { + foreach (Asn1Encodable o in v) + { + AddObject(o); + } + + Sort(); + } + + /** + * @param v - a vector of objects making up the set. + */ + public DerSet( + Asn1EncodableVector v) + : this(v, true) + { + } + + internal DerSet( + Asn1EncodableVector v, + bool needsSorting) + : base(v.Count) + { + foreach (Asn1Encodable o in v) + { + AddObject(o); + } + + if (needsSorting) + { + 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. + */ + internal override void Encode( + DerOutputStream derOut) + { + // TODO Intermediate buffer could be avoided if we could calculate expected length + MemoryStream bOut = new MemoryStream(); + DerOutputStream dOut = new DerOutputStream(bOut); + + foreach (Asn1Encodable obj in this) + { + dOut.WriteObject(obj); + } + + Platform.Dispose(dOut); + + byte[] bytes = bOut.ToArray(); + + derOut.WriteEncoded(Asn1Tags.Set | Asn1Tags.Constructed, bytes); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/DerStringBase.cs b/bc-sharp-crypto/src/asn1/DerStringBase.cs new file mode 100644 index 0000000..2a5fb04 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/DerStringBase.cs @@ -0,0 +1,22 @@ +namespace Org.BouncyCastle.Asn1 +{ + public abstract class DerStringBase + : Asn1Object, IAsn1String + { + protected DerStringBase() + { + } + + public abstract string GetString(); + + public override string ToString() + { + return GetString(); + } + + protected override int Asn1GetHashCode() + { + return GetString().GetHashCode(); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/DerT61String.cs b/bc-sharp-crypto/src/asn1/DerT61String.cs new file mode 100644 index 0000000..746ccfe --- /dev/null +++ b/bc-sharp-crypto/src/asn1/DerT61String.cs @@ -0,0 +1,102 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1 +{ + /** + * Der T61String (also the teletex string) - 8-bit characters + */ + public class DerT61String + : DerStringBase + { + private readonly string str; + + /** + * return a T61 string from the passed in object. + * + * @exception ArgumentException if the object cannot be converted. + */ + public static DerT61String GetInstance( + object obj) + { + if (obj == null || obj is DerT61String) + { + return (DerT61String)obj; + } + + throw new ArgumentException("illegal object in GetInstance: " + Platform.GetTypeName(obj)); + } + + /** + * return an T61 string from a tagged object. + * + * @param obj the tagged object holding the object we want + * @param explicitly true if the object is meant to be explicitly + * tagged false otherwise. + * @exception ArgumentException if the tagged object cannot + * be converted. + */ + public static DerT61String GetInstance( + Asn1TaggedObject obj, + bool isExplicit) + { + Asn1Object o = obj.GetObject(); + + if (isExplicit || o is DerT61String) + { + return GetInstance(o); + } + + return new DerT61String(Asn1OctetString.GetInstance(o).GetOctets()); + } + + /** + * basic constructor - with bytes. + */ + public DerT61String( + byte[] str) + : this(Strings.FromByteArray(str)) + { + } + + /** + * basic constructor - with string. + */ + public DerT61String( + string str) + { + if (str == null) + throw new ArgumentNullException("str"); + + this.str = str; + } + + public override string GetString() + { + return str; + } + + internal override void Encode( + DerOutputStream derOut) + { + derOut.WriteEncoded(Asn1Tags.T61String, GetOctets()); + } + + public byte[] GetOctets() + { + return Strings.ToByteArray(str); + } + + protected override bool Asn1Equals( + Asn1Object asn1Object) + { + DerT61String other = asn1Object as DerT61String; + + if (other == null) + return false; + + return this.str.Equals(other.str); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/DerTaggedObject.cs b/bc-sharp-crypto/src/asn1/DerTaggedObject.cs new file mode 100644 index 0000000..717d724 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/DerTaggedObject.cs @@ -0,0 +1,72 @@ +namespace Org.BouncyCastle.Asn1 +{ + /** + * 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 + : Asn1TaggedObject + { + /** + * @param tagNo the tag number for this object. + * @param obj the tagged object. + */ + public DerTaggedObject( + int tagNo, + Asn1Encodable obj) + : base(tagNo, obj) + { + } + + /** + * @param explicitly true if an explicitly tagged object. + * @param tagNo the tag number for this object. + * @param obj the tagged object. + */ + public DerTaggedObject( + bool explicitly, + int tagNo, + Asn1Encodable obj) + : base(explicitly, tagNo, obj) + { + } + + /** + * create an implicitly tagged object that contains a zero + * length sequence. + */ + public DerTaggedObject( + int tagNo) + : base(false, tagNo, DerSequence.Empty) + { + } + + internal override void Encode( + DerOutputStream derOut) + { + if (!IsEmpty()) + { + byte[] bytes = obj.GetDerEncoded(); + + if (explicitly) + { + derOut.WriteEncoded(Asn1Tags.Constructed | Asn1Tags.Tagged, tagNo, bytes); + } + else + { + // + // need to mark constructed types... (preserve Constructed tag) + // + int flags = (bytes[0] & Asn1Tags.Constructed) | Asn1Tags.Tagged; + derOut.WriteTag(flags, tagNo); + derOut.Write(bytes, 1, bytes.Length - 1); + } + } + else + { + derOut.WriteEncoded(Asn1Tags.Constructed | Asn1Tags.Tagged, tagNo, new byte[0]); + } + } + } +} diff --git a/bc-sharp-crypto/src/asn1/DerUTCTime.cs b/bc-sharp-crypto/src/asn1/DerUTCTime.cs new file mode 100644 index 0000000..99af8bf --- /dev/null +++ b/bc-sharp-crypto/src/asn1/DerUTCTime.cs @@ -0,0 +1,267 @@ +using System; +using System.Globalization; +using System.Text; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1 +{ + /** + * UTC time object. + */ + public class DerUtcTime + : Asn1Object + { + private readonly string time; + + /** + * return an UTC Time from the passed in object. + * + * @exception ArgumentException if the object cannot be converted. + */ + public static DerUtcTime GetInstance( + object obj) + { + if (obj == null || obj is DerUtcTime) + { + return (DerUtcTime)obj; + } + + throw new ArgumentException("illegal object in GetInstance: " + Platform.GetTypeName(obj)); + } + + /** + * return an UTC Time from a tagged object. + * + * @param obj the tagged object holding the object we want + * @param explicitly true if the object is meant to be explicitly + * tagged false otherwise. + * @exception ArgumentException if the tagged object cannot + * be converted. + */ + public static DerUtcTime GetInstance( + Asn1TaggedObject obj, + bool isExplicit) + { + Asn1Object o = obj.GetObject(); + + if (isExplicit || o is DerUtcTime) + { + return GetInstance(o); + } + + return new DerUtcTime(((Asn1OctetString)o).GetOctets()); + } + + /** + * The correct format for this is YYMMDDHHMMSSZ (it used to be that seconds were + * never encoded. When you're creating one of these objects from scratch, that's + * what you want to use, otherwise we'll try to deal with whatever Gets read from + * the input stream... (this is why the input format is different from the GetTime() + * method output). + *

+ * @param time the time string.

+ */ + public DerUtcTime( + string time) + { + if (time == null) + throw new ArgumentNullException("time"); + + this.time = time; + + try + { + ToDateTime(); + } + catch (FormatException e) + { + throw new ArgumentException("invalid date string: " + e.Message); + } + } + + /** + * base constructor from a DateTime object + */ + public DerUtcTime( + DateTime time) + { +#if PORTABLE + this.time = time.ToUniversalTime().ToString("yyMMddHHmmss", CultureInfo.InvariantCulture) + "Z"; +#else + this.time = time.ToString("yyMMddHHmmss", CultureInfo.InvariantCulture) + "Z"; +#endif + } + + internal DerUtcTime( + byte[] bytes) + { + // + // explicitly convert to characters + // + this.time = Strings.FromAsciiByteArray(bytes); + } + +// public DateTime ToDateTime() +// { +// string tm = this.AdjustedTimeString; +// +// return new DateTime( +// Int16.Parse(tm.Substring(0, 4)), +// Int16.Parse(tm.Substring(4, 2)), +// Int16.Parse(tm.Substring(6, 2)), +// Int16.Parse(tm.Substring(8, 2)), +// Int16.Parse(tm.Substring(10, 2)), +// Int16.Parse(tm.Substring(12, 2))); +// } + + /** + * return the time as a date based on whatever a 2 digit year will return. For + * standardised processing use ToAdjustedDateTime(). + * + * @return the resulting date + * @exception ParseException if the date string cannot be parsed. + */ + public DateTime ToDateTime() + { + return ParseDateString(TimeString, @"yyMMddHHmmss'GMT'zzz"); + } + + /** + * 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 DateTime ToAdjustedDateTime() + { + return ParseDateString(AdjustedTimeString, @"yyyyMMddHHmmss'GMT'zzz"); + } + + private DateTime ParseDateString( + string dateStr, + string formatStr) + { + DateTime dt = DateTime.ParseExact( + dateStr, + formatStr, + DateTimeFormatInfo.InvariantInfo); + + return dt.ToUniversalTime(); + } + + /** + * 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 TimeString + { + get + { + // + // 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, 3) + ":" + d.Substring(13, 2); + } + else + { + return d.Substring(0, 12) + "GMT" + d.Substring(12, 3) + ":" + d.Substring(15, 2); + } + } + } + } + + [Obsolete("Use 'AdjustedTimeString' property instead")] + public string AdjustedTime + { + get { return AdjustedTimeString; } + } + + /// + /// Return a time string as an adjusted date with a 4 digit year. + /// This goes in the range of 1950 - 2049. + /// + public string AdjustedTimeString + { + get + { + string d = TimeString; + string c = d[0] < '5' ? "20" : "19"; + + return c + d; + } + } + + private byte[] GetOctets() + { + return Strings.ToAsciiByteArray(time); + } + + internal override void Encode( + DerOutputStream derOut) + { + derOut.WriteEncoded(Asn1Tags.UtcTime, GetOctets()); + } + + protected override bool Asn1Equals( + Asn1Object asn1Object) + { + DerUtcTime other = asn1Object as DerUtcTime; + + if (other == null) + return false; + + return this.time.Equals(other.time); + } + + protected override int Asn1GetHashCode() + { + return time.GetHashCode(); + } + + public override string ToString() + { + return time; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/DerUTF8String.cs b/bc-sharp-crypto/src/asn1/DerUTF8String.cs new file mode 100644 index 0000000..758a506 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/DerUTF8String.cs @@ -0,0 +1,98 @@ +using System; +using System.Text; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1 +{ + /** + * Der UTF8String object. + */ + public class DerUtf8String + : DerStringBase + { + private readonly string str; + + /** + * return an UTF8 string from the passed in object. + * + * @exception ArgumentException if the object cannot be converted. + */ + public static DerUtf8String GetInstance( + object obj) + { + if (obj == null || obj is DerUtf8String) + { + return (DerUtf8String)obj; + } + + throw new ArgumentException("illegal object in GetInstance: " + Platform.GetTypeName(obj)); + } + + /** + * return an UTF8 string from a tagged object. + * + * @param obj the tagged object holding the object we want + * @param explicitly true if the object is meant to be explicitly + * tagged false otherwise. + * @exception ArgumentException if the tagged object cannot + * be converted. + */ + public static DerUtf8String GetInstance( + Asn1TaggedObject obj, + bool isExplicit) + { + Asn1Object o = obj.GetObject(); + + if (isExplicit || o is DerUtf8String) + { + return GetInstance(o); + } + + return new DerUtf8String(Asn1OctetString.GetInstance(o).GetOctets()); + } + + /** + * basic constructor - byte encoded string. + */ + public DerUtf8String( + byte[] str) + : this(Encoding.UTF8.GetString(str, 0, str.Length)) + { + } + + /** + * basic constructor + */ + public DerUtf8String( + string str) + { + if (str == null) + throw new ArgumentNullException("str"); + + this.str = str; + } + + public override string GetString() + { + return str; + } + + protected override bool Asn1Equals( + Asn1Object asn1Object) + { + DerUtf8String other = asn1Object as DerUtf8String; + + if (other == null) + return false; + + return this.str.Equals(other.str); + } + + internal override void Encode( + DerOutputStream derOut) + { + derOut.WriteEncoded(Asn1Tags.Utf8String, Encoding.UTF8.GetBytes(str)); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/DerUniversalString.cs b/bc-sharp-crypto/src/asn1/DerUniversalString.cs new file mode 100644 index 0000000..284d0f8 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/DerUniversalString.cs @@ -0,0 +1,107 @@ +using System; +using System.Text; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1 +{ + /** + * Der UniversalString object. + */ + public class DerUniversalString + : DerStringBase + { + private static readonly char[] table = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + + private readonly byte[] str; + + /** + * return a Universal string from the passed in object. + * + * @exception ArgumentException if the object cannot be converted. + */ + public static DerUniversalString GetInstance( + object obj) + { + if (obj == null || obj is DerUniversalString) + { + return (DerUniversalString)obj; + } + + throw new ArgumentException("illegal object in GetInstance: " + Platform.GetTypeName(obj)); + } + + /** + * return a Universal string from a tagged object. + * + * @param obj the tagged object holding the object we want + * @param explicitly true if the object is meant to be explicitly + * tagged false otherwise. + * @exception ArgumentException if the tagged object cannot + * be converted. + */ + public static DerUniversalString GetInstance( + Asn1TaggedObject obj, + bool isExplicit) + { + Asn1Object o = obj.GetObject(); + + if (isExplicit || o is DerUniversalString) + { + return GetInstance(o); + } + + return new DerUniversalString(Asn1OctetString.GetInstance(o).GetOctets()); + } + + /** + * basic constructor - byte encoded string. + */ + public DerUniversalString( + byte[] str) + { + if (str == null) + throw new ArgumentNullException("str"); + + this.str = str; + } + + public override string GetString() + { + StringBuilder buffer = new StringBuilder("#"); + byte[] enc = GetDerEncoded(); + + for (int i = 0; i != enc.Length; i++) + { + uint ubyte = enc[i]; + buffer.Append(table[(ubyte >> 4) & 0xf]); + buffer.Append(table[enc[i] & 0xf]); + } + + return buffer.ToString(); + } + + public byte[] GetOctets() + { + return (byte[]) str.Clone(); + } + + internal override void Encode( + DerOutputStream derOut) + { + derOut.WriteEncoded(Asn1Tags.UniversalString, this.str); + } + + protected override bool Asn1Equals( + Asn1Object asn1Object) + { + DerUniversalString other = asn1Object as DerUniversalString; + + if (other == null) + return false; + +// return this.GetString().Equals(other.GetString()); + return Arrays.AreEqual(this.str, other.str); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/DerVideotexString.cs b/bc-sharp-crypto/src/asn1/DerVideotexString.cs new file mode 100644 index 0000000..b254010 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/DerVideotexString.cs @@ -0,0 +1,103 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1 +{ + public class DerVideotexString + : DerStringBase + { + private readonly byte[] mString; + + /** + * return a Videotex String from the passed in object + * + * @param obj a DERVideotexString or an object that can be converted into one. + * @exception IllegalArgumentException if the object cannot be converted. + * @return a DERVideotexString instance, or null. + */ + public static DerVideotexString GetInstance(object obj) + { + if (obj == null || obj is DerVideotexString) + { + return (DerVideotexString)obj; + } + + if (obj is byte[]) + { + try + { + return (DerVideotexString)FromByteArray((byte[])obj); + } + catch (Exception e) + { + throw new ArgumentException("encoding error in GetInstance: " + e.ToString(), "obj"); + } + } + + throw new ArgumentException("illegal object in GetInstance: " + Platform.GetTypeName(obj), "obj"); + } + + /** + * return a Videotex 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. + * @return a DERVideotexString instance, or null. + */ + public static DerVideotexString GetInstance(Asn1TaggedObject obj, bool isExplicit) + { + Asn1Object o = obj.GetObject(); + + if (isExplicit || o is DerVideotexString) + { + return GetInstance(o); + } + + return new DerVideotexString(((Asn1OctetString)o).GetOctets()); + } + + /** + * basic constructor - with bytes. + * @param string the byte encoding of the characters making up the string. + */ + public DerVideotexString(byte[] encoding) + { + this.mString = Arrays.Clone(encoding); + } + + public override string GetString() + { + return Strings.FromByteArray(mString); + } + + public byte[] GetOctets() + { + return Arrays.Clone(mString); + } + + internal override void Encode(DerOutputStream derOut) + { + derOut.WriteEncoded(Asn1Tags.VideotexString, mString); + } + + protected override int Asn1GetHashCode() + { + return Arrays.GetHashCode(mString); + } + + protected override bool Asn1Equals( + Asn1Object asn1Object) + { + DerVideotexString other = asn1Object as DerVideotexString; + + if (other == null) + return false; + + return Arrays.AreEqual(mString, other.mString); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/DerVisibleString.cs b/bc-sharp-crypto/src/asn1/DerVisibleString.cs new file mode 100644 index 0000000..e111220 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/DerVisibleString.cs @@ -0,0 +1,111 @@ +using System; +using System.Text; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1 +{ + /** + * Der VisibleString object. + */ + public class DerVisibleString + : DerStringBase + { + private readonly string str; + + /** + * return a Visible string from the passed in object. + * + * @exception ArgumentException if the object cannot be converted. + */ + public static DerVisibleString GetInstance( + object obj) + { + if (obj == null || obj is DerVisibleString) + { + return (DerVisibleString)obj; + } + + if (obj is Asn1OctetString) + { + return new DerVisibleString(((Asn1OctetString)obj).GetOctets()); + } + + if (obj is Asn1TaggedObject) + { + return GetInstance(((Asn1TaggedObject)obj).GetObject()); + } + + throw new ArgumentException("illegal object in GetInstance: " + Platform.GetTypeName(obj)); + } + + /** + * return a Visible string from a tagged object. + * + * @param obj the tagged object holding the object we want + * @param explicitly true if the object is meant to be explicitly + * tagged false otherwise. + * @exception ArgumentException if the tagged object cannot + * be converted. + */ + public static DerVisibleString GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(obj.GetObject()); + } + + /** + * basic constructor - byte encoded string. + */ + public DerVisibleString( + byte[] str) + : this(Strings.FromAsciiByteArray(str)) + { + } + + /** + * basic constructor + */ + public DerVisibleString( + string str) + { + if (str == null) + throw new ArgumentNullException("str"); + + this.str = str; + } + + public override string GetString() + { + return str; + } + + public byte[] GetOctets() + { + return Strings.ToAsciiByteArray(str); + } + + internal override void Encode( + DerOutputStream derOut) + { + derOut.WriteEncoded(Asn1Tags.VisibleString, GetOctets()); + } + + protected override bool Asn1Equals( + Asn1Object asn1Object) + { + DerVisibleString other = asn1Object as DerVisibleString; + + if (other == null) + return false; + + return this.str.Equals(other.str); + } + + protected override int Asn1GetHashCode() + { + return this.str.GetHashCode(); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/IAsn1ApplicationSpecificParser.cs b/bc-sharp-crypto/src/asn1/IAsn1ApplicationSpecificParser.cs new file mode 100644 index 0000000..89cf64c --- /dev/null +++ b/bc-sharp-crypto/src/asn1/IAsn1ApplicationSpecificParser.cs @@ -0,0 +1,10 @@ +using System; + +namespace Org.BouncyCastle.Asn1 +{ + public interface IAsn1ApplicationSpecificParser + : IAsn1Convertible + { + IAsn1Convertible ReadObject(); + } +} diff --git a/bc-sharp-crypto/src/asn1/IAsn1Choice.cs b/bc-sharp-crypto/src/asn1/IAsn1Choice.cs new file mode 100644 index 0000000..ecd76e4 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/IAsn1Choice.cs @@ -0,0 +1,17 @@ + +namespace Org.BouncyCastle.Asn1 +{ + /** + * Marker interface for CHOICE objects - if you implement this in a roll-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 IAsn1Choice + { + // marker interface + } +} diff --git a/bc-sharp-crypto/src/asn1/IAsn1Convertible.cs b/bc-sharp-crypto/src/asn1/IAsn1Convertible.cs new file mode 100644 index 0000000..d3f83af --- /dev/null +++ b/bc-sharp-crypto/src/asn1/IAsn1Convertible.cs @@ -0,0 +1,7 @@ +namespace Org.BouncyCastle.Asn1 +{ + public interface IAsn1Convertible + { + Asn1Object ToAsn1Object(); + } +} diff --git a/bc-sharp-crypto/src/asn1/IAsn1String.cs b/bc-sharp-crypto/src/asn1/IAsn1String.cs new file mode 100644 index 0000000..cbc2635 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/IAsn1String.cs @@ -0,0 +1,10 @@ +namespace Org.BouncyCastle.Asn1 +{ + /** + * basic interface for Der string objects. + */ + public interface IAsn1String + { + string GetString(); + } +} diff --git a/bc-sharp-crypto/src/asn1/IndefiniteLengthInputStream.cs b/bc-sharp-crypto/src/asn1/IndefiniteLengthInputStream.cs new file mode 100644 index 0000000..09d0e3a --- /dev/null +++ b/bc-sharp-crypto/src/asn1/IndefiniteLengthInputStream.cs @@ -0,0 +1,170 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Asn1 +{ + class IndefiniteLengthInputStream + : LimitedInputStream + { + private int _lookAhead; + private bool _eofOn00 = true; + + internal IndefiniteLengthInputStream( + Stream inStream, + int limit) + : base(inStream, limit) + { + _lookAhead = RequireByte(); + CheckForEof(); + } + + internal void SetEofOn00( + bool eofOn00) + { + _eofOn00 = eofOn00; + if (_eofOn00) + { + CheckForEof(); + } + } + + private bool CheckForEof() + { + if (_lookAhead == 0x00) + { + int extra = RequireByte(); + if (extra != 0) + { + throw new IOException("malformed end-of-contents marker"); + } + + _lookAhead = -1; + SetParentEofDetect(true); + return true; + } + return _lookAhead < 0; + } + + public override int Read( + byte[] buffer, + int offset, + int count) + { + // Only use this optimisation if we aren't checking for 00 + if (_eofOn00 || count <= 1) + return base.Read(buffer, offset, count); + + if (_lookAhead < 0) + return 0; + + int numRead = _in.Read(buffer, offset + 1, count - 1); + + if (numRead <= 0) + { + // Corrupted stream + throw new EndOfStreamException(); + } + + buffer[offset] = (byte)_lookAhead; + _lookAhead = RequireByte(); + + return numRead + 1; + } + + public override int ReadByte() + { + if (_eofOn00 && CheckForEof()) + return -1; + + int result = _lookAhead; + _lookAhead = RequireByte(); + return result; + } + + private int RequireByte() + { + int b = _in.ReadByte(); + if (b < 0) + { + // Corrupted stream + throw new EndOfStreamException(); + } + return b; + } + } +} + +//using System; +//using System.IO; + +//namespace Org.BouncyCastle.Asn1 +//{ +// class IndefiniteLengthInputStream +// : LimitedInputStream +// { +// private bool _eofReached = false; +// private bool _eofOn00 = true; + +// internal IndefiniteLengthInputStream( +// Stream inStream, +// int limit) +// : base(inStream, limit) +// { +// } + +// internal void SetEofOn00( +// bool eofOn00) +// { +// _eofOn00 = eofOn00; +// } + +// public override int Read( +// byte[] buffer, +// int offset, +// int count) +// { +// if (_eofReached) +// return 0; + +// if (_eofOn00) +// return base.Read(buffer, offset, count); + +// int numRead = _in.Read(buffer, offset, count); + +// if (numRead <= 0) +// throw new EndOfStreamException(); + +// return numRead; +// } + +// public override int ReadByte() +// { +// if (_eofReached) +// return -1; + +// int b1 = _in.ReadByte(); + +// if (b1 < 0) +// throw new EndOfStreamException(); + +// if (b1 == 0 && _eofOn00) +// { +// int b2 = _in.ReadByte(); + +// if (b2 < 0) +// throw new EndOfStreamException(); + +// if (b2 == 0) +// { +// _eofReached = true; +// SetParentEofDetect(true); +// return -1; +// } + +// throw new InvalidDataException(); +// } + +// return b1; +// } +// } +//} diff --git a/bc-sharp-crypto/src/asn1/LazyASN1InputStream.cs b/bc-sharp-crypto/src/asn1/LazyASN1InputStream.cs new file mode 100644 index 0000000..4cf2305 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/LazyASN1InputStream.cs @@ -0,0 +1,33 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Asn1 +{ + public class LazyAsn1InputStream + : Asn1InputStream + { + public LazyAsn1InputStream( + byte[] input) + : base(input) + { + } + + public LazyAsn1InputStream( + Stream inputStream) + : base(inputStream) + { + } + + internal override DerSequence CreateDerSequence( + DefiniteLengthInputStream dIn) + { + return new LazyDerSequence(dIn.ToArray()); + } + + internal override DerSet CreateDerSet( + DefiniteLengthInputStream dIn) + { + return new LazyDerSet(dIn.ToArray()); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/LazyDERSequence.cs b/bc-sharp-crypto/src/asn1/LazyDERSequence.cs new file mode 100644 index 0000000..7301bc1 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/LazyDERSequence.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections; +using System.Diagnostics; + +namespace Org.BouncyCastle.Asn1 +{ + internal class LazyDerSequence + : DerSequence + { + private byte[] encoded; + + internal LazyDerSequence( + byte[] encoded) + { + this.encoded = encoded; + } + + private void Parse() + { + lock (this) + { + if (encoded != null) + { + Asn1InputStream e = new LazyAsn1InputStream(encoded); + + Asn1Object o; + while ((o = e.ReadObject()) != null) + { + AddObject(o); + } + + encoded = null; + } + } + } + + public override Asn1Encodable this[int index] + { + get + { + Parse(); + + return base[index]; + } + } + + public override IEnumerator GetEnumerator() + { + Parse(); + + return base.GetEnumerator(); + } + + public override int Count + { + get + { + Parse(); + + return base.Count; + } + } + + internal override void Encode( + DerOutputStream derOut) + { + lock (this) + { + if (encoded == null) + { + base.Encode(derOut); + } + else + { + derOut.WriteEncoded(Asn1Tags.Sequence | Asn1Tags.Constructed, encoded); + } + } + } + } +} diff --git a/bc-sharp-crypto/src/asn1/LazyDERSet.cs b/bc-sharp-crypto/src/asn1/LazyDERSet.cs new file mode 100644 index 0000000..e6c9319 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/LazyDERSet.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections; +using System.Diagnostics; + +namespace Org.BouncyCastle.Asn1 +{ + internal class LazyDerSet + : DerSet + { + private byte[] encoded; + + internal LazyDerSet( + byte[] encoded) + { + this.encoded = encoded; + } + + private void Parse() + { + lock (this) + { + if (encoded != null) + { + Asn1InputStream e = new LazyAsn1InputStream(encoded); + + Asn1Object o; + while ((o = e.ReadObject()) != null) + { + AddObject(o); + } + + encoded = null; + } + } + } + + public override Asn1Encodable this[int index] + { + get + { + Parse(); + + return base[index]; + } + } + + public override IEnumerator GetEnumerator() + { + Parse(); + + return base.GetEnumerator(); + } + + public override int Count + { + get + { + Parse(); + + return base.Count; + } + } + + internal override void Encode( + DerOutputStream derOut) + { + lock (this) + { + if (encoded == null) + { + base.Encode(derOut); + } + else + { + derOut.WriteEncoded(Asn1Tags.Set | Asn1Tags.Constructed, encoded); + } + } + } + } +} diff --git a/bc-sharp-crypto/src/asn1/LimitedInputStream.cs b/bc-sharp-crypto/src/asn1/LimitedInputStream.cs new file mode 100644 index 0000000..62486aa --- /dev/null +++ b/bc-sharp-crypto/src/asn1/LimitedInputStream.cs @@ -0,0 +1,35 @@ +using System.IO; + +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Asn1 +{ + internal abstract class LimitedInputStream + : BaseInputStream + { + protected readonly Stream _in; + private int _limit; + + internal LimitedInputStream( + Stream inStream, + int limit) + { + this._in = inStream; + this._limit = limit; + } + + internal virtual int GetRemaining() + { + // TODO: maybe one day this can become more accurate + return _limit; + } + + protected virtual void SetParentEofDetect(bool on) + { + if (_in is IndefiniteLengthInputStream) + { + ((IndefiniteLengthInputStream)_in).SetEofOn00(on); + } + } + } +} diff --git a/bc-sharp-crypto/src/asn1/OidTokenizer.cs b/bc-sharp-crypto/src/asn1/OidTokenizer.cs new file mode 100644 index 0000000..6e76e8c --- /dev/null +++ b/bc-sharp-crypto/src/asn1/OidTokenizer.cs @@ -0,0 +1,45 @@ +namespace Org.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; + } + + public bool HasMoreTokens + { + get { return index != -1; } + } + + public string NextToken() + { + if (index == -1) + { + return null; + } + + int end = oid.IndexOf('.', index); + if (end == -1) + { + string lastToken = oid.Substring(index); + index = -1; + return lastToken; + } + + string nextToken = oid.Substring(index, end - index); + index = end + 1; + return nextToken; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/anssi/ANSSINamedCurves.cs b/bc-sharp-crypto/src/asn1/anssi/ANSSINamedCurves.cs new file mode 100644 index 0000000..d0c90eb --- /dev/null +++ b/bc-sharp-crypto/src/asn1/anssi/ANSSINamedCurves.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Math.EC; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Asn1.Anssi +{ + public class AnssiNamedCurves + { + private static ECCurve ConfigureCurve(ECCurve curve) + { + return curve; + } + + private static BigInteger FromHex(string hex) + { + return new BigInteger(1, Hex.Decode(hex)); + } + + /* + * FRP256v1 + */ + internal class Frp256v1Holder + : X9ECParametersHolder + { + private Frp256v1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new Frp256v1Holder(); + + protected override X9ECParameters CreateParameters() + { + BigInteger p = FromHex("F1FD178C0B3AD58F10126DE8CE42435B3961ADBCABC8CA6DE8FCF353D86E9C03"); + BigInteger a = FromHex("F1FD178C0B3AD58F10126DE8CE42435B3961ADBCABC8CA6DE8FCF353D86E9C00"); + BigInteger b = FromHex("EE353FCA5428A9300D4ABA754A44C00FDFEC0C9AE4B1A1803075ED967B7BB73F"); + byte[] S = null; + BigInteger n = FromHex("F1FD178C0B3AD58F10126DE8CE42435B53DC67E140D2BF941FFDD459C6D655E1"); + BigInteger h = BigInteger.One; + + ECCurve curve = ConfigureCurve(new FpCurve(p, a, b, n, h)); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "B6B3D4C356C139EB31183D4749D423958C27D2DCAF98B70164C97A2DD98F5CFF" + + "6142E0F7C8B204911F9271F0F3ECEF8C2701C307E8E4C9E183115A1554062CFB")); + + return new X9ECParameters(curve, G, n, h, S); + } + }; + + + private static readonly IDictionary objIds = Platform.CreateHashtable(); + private static readonly IDictionary curves = Platform.CreateHashtable(); + private static readonly IDictionary names = Platform.CreateHashtable(); + + private static void DefineCurve( + string name, + DerObjectIdentifier oid, + X9ECParametersHolder holder) + { + objIds.Add(Platform.ToUpperInvariant(name), oid); + names.Add(oid, name); + curves.Add(oid, holder); + } + + static AnssiNamedCurves() + { + DefineCurve("FRP256v1", AnssiObjectIdentifiers.FRP256v1, Frp256v1Holder.Instance); + } + + public static X9ECParameters GetByName( + string name) + { + DerObjectIdentifier oid = GetOid(name); + return oid == null ? null : GetByOid(oid); + } + + /** + * 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[oid]; + return holder == null ? null : holder.Parameters; + } + + /** + * 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[Platform.ToUpperInvariant(name)]; + } + + /** + * return the named curve name represented by the given object identifier. + */ + public static string GetName( + DerObjectIdentifier oid) + { + return (string)names[oid]; + } + + /** + * returns an enumeration containing the name strings for curves + * contained in this structure. + */ + public static IEnumerable Names + { + get { return new EnumerableProxy(names.Values); } + } + } +} diff --git a/bc-sharp-crypto/src/asn1/anssi/ANSSIObjectIdentifiers.cs b/bc-sharp-crypto/src/asn1/anssi/ANSSIObjectIdentifiers.cs new file mode 100644 index 0000000..d230832 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/anssi/ANSSIObjectIdentifiers.cs @@ -0,0 +1,13 @@ +using System; + +namespace Org.BouncyCastle.Asn1.Anssi +{ + public sealed class AnssiObjectIdentifiers + { + private AnssiObjectIdentifiers() + { + } + + public static readonly DerObjectIdentifier FRP256v1 = new DerObjectIdentifier("1.2.250.1.223.101.256.1"); + } +} diff --git a/bc-sharp-crypto/src/asn1/bc/BCObjectIdentifiers.cs b/bc-sharp-crypto/src/asn1/bc/BCObjectIdentifiers.cs new file mode 100644 index 0000000..075e538 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/bc/BCObjectIdentifiers.cs @@ -0,0 +1,39 @@ +using System; + +namespace Org.BouncyCastle.Asn1.BC +{ + public abstract class BCObjectIdentifiers + { + // iso.org.dod.internet.private.enterprise.legion-of-the-bouncy-castle + // 1.3.6.1.4.1.22554 + public static readonly DerObjectIdentifier bc = new DerObjectIdentifier("1.3.6.1.4.1.22554"); + + // pbe(1) algorithms + public static readonly DerObjectIdentifier bc_pbe = new DerObjectIdentifier(bc + ".1"); + + // SHA-1(1) + public static readonly DerObjectIdentifier bc_pbe_sha1 = new DerObjectIdentifier(bc_pbe + ".1"); + + // SHA-2(2) . (SHA-256(1)|SHA-384(2)|SHA-512(3)|SHA-224(4)) + public static readonly DerObjectIdentifier bc_pbe_sha256 = new DerObjectIdentifier(bc_pbe + ".2.1"); + public static readonly DerObjectIdentifier bc_pbe_sha384 = new DerObjectIdentifier(bc_pbe + ".2.2"); + public static readonly DerObjectIdentifier bc_pbe_sha512 = new DerObjectIdentifier(bc_pbe + ".2.3"); + public static readonly DerObjectIdentifier bc_pbe_sha224 = new DerObjectIdentifier(bc_pbe + ".2.4"); + + // PKCS-5(1)|PKCS-12(2) + public static readonly DerObjectIdentifier bc_pbe_sha1_pkcs5 = new DerObjectIdentifier(bc_pbe_sha1 + ".1"); + public static readonly DerObjectIdentifier bc_pbe_sha1_pkcs12 = new DerObjectIdentifier(bc_pbe_sha1 + ".2"); + + public static readonly DerObjectIdentifier bc_pbe_sha256_pkcs5 = new DerObjectIdentifier(bc_pbe_sha256 + ".1"); + public static readonly DerObjectIdentifier bc_pbe_sha256_pkcs12 = new DerObjectIdentifier(bc_pbe_sha256 + ".2"); + + // AES(1) . (CBC-128(2)|CBC-192(22)|CBC-256(42)) + public static readonly DerObjectIdentifier bc_pbe_sha1_pkcs12_aes128_cbc = new DerObjectIdentifier(bc_pbe_sha1_pkcs12 + ".1.2"); + public static readonly DerObjectIdentifier bc_pbe_sha1_pkcs12_aes192_cbc = new DerObjectIdentifier(bc_pbe_sha1_pkcs12 + ".1.22"); + public static readonly DerObjectIdentifier bc_pbe_sha1_pkcs12_aes256_cbc = new DerObjectIdentifier(bc_pbe_sha1_pkcs12 + ".1.42"); + + public static readonly DerObjectIdentifier bc_pbe_sha256_pkcs12_aes128_cbc = new DerObjectIdentifier(bc_pbe_sha256_pkcs12 + ".1.2"); + public static readonly DerObjectIdentifier bc_pbe_sha256_pkcs12_aes192_cbc = new DerObjectIdentifier(bc_pbe_sha256_pkcs12 + ".1.22"); + public static readonly DerObjectIdentifier bc_pbe_sha256_pkcs12_aes256_cbc = new DerObjectIdentifier(bc_pbe_sha256_pkcs12 + ".1.42"); + } +} \ No newline at end of file diff --git a/bc-sharp-crypto/src/asn1/cmp/CAKeyUpdAnnContent.cs b/bc-sharp-crypto/src/asn1/cmp/CAKeyUpdAnnContent.cs new file mode 100644 index 0000000..b74bac8 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cmp/CAKeyUpdAnnContent.cs @@ -0,0 +1,62 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cmp +{ + public class CAKeyUpdAnnContent + : Asn1Encodable + { + private readonly CmpCertificate oldWithNew; + private readonly CmpCertificate newWithOld; + private readonly CmpCertificate newWithNew; + + private CAKeyUpdAnnContent(Asn1Sequence seq) + { + oldWithNew = CmpCertificate.GetInstance(seq[0]); + newWithOld = CmpCertificate.GetInstance(seq[1]); + newWithNew = CmpCertificate.GetInstance(seq[2]); + } + + public static CAKeyUpdAnnContent GetInstance(object obj) + { + if (obj is CAKeyUpdAnnContent) + return (CAKeyUpdAnnContent)obj; + + if (obj is Asn1Sequence) + return new CAKeyUpdAnnContent((Asn1Sequence)obj); + + throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj"); + } + + public virtual CmpCertificate OldWithNew + { + get { return oldWithNew; } + } + + public virtual CmpCertificate NewWithOld + { + get { return newWithOld; } + } + + public virtual CmpCertificate NewWithNew + { + get { 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 override Asn1Object ToAsn1Object() + { + return new DerSequence(oldWithNew, newWithOld, newWithNew); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cmp/CertConfirmContent.cs b/bc-sharp-crypto/src/asn1/cmp/CertConfirmContent.cs new file mode 100644 index 0000000..370a9e7 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cmp/CertConfirmContent.cs @@ -0,0 +1,49 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cmp +{ + public class CertConfirmContent + : Asn1Encodable + { + private readonly Asn1Sequence content; + + private CertConfirmContent(Asn1Sequence seq) + { + content = seq; + } + + public static CertConfirmContent GetInstance(object obj) + { + if (obj is CertConfirmContent) + return (CertConfirmContent)obj; + + if (obj is Asn1Sequence) + return new CertConfirmContent((Asn1Sequence)obj); + + throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj"); + } + + public virtual CertStatus[] ToCertStatusArray() + { + CertStatus[] result = new CertStatus[content.Count]; + for (int i = 0; i != result.Length; i++) + { + result[i] = CertStatus.GetInstance(content[i]); + } + return result; + } + + /** + *
+		 * CertConfirmContent ::= SEQUENCE OF CertStatus
+		 * 
+ * @return a basic ASN.1 object representation. + */ + public override Asn1Object ToAsn1Object() + { + return content; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cmp/CertOrEncCert.cs b/bc-sharp-crypto/src/asn1/cmp/CertOrEncCert.cs new file mode 100644 index 0000000..eb200e1 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cmp/CertOrEncCert.cs @@ -0,0 +1,86 @@ +using System; + +using Org.BouncyCastle.Asn1.Crmf; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cmp +{ + public class CertOrEncCert + : Asn1Encodable, IAsn1Choice + { + private readonly CmpCertificate certificate; + private readonly EncryptedValue encryptedCert; + + private CertOrEncCert(Asn1TaggedObject tagged) + { + if (tagged.TagNo == 0) + { + certificate = CmpCertificate.GetInstance(tagged.GetObject()); + } + else if (tagged.TagNo == 1) + { + encryptedCert = EncryptedValue.GetInstance(tagged.GetObject()); + } + else + { + throw new ArgumentException("unknown tag: " + tagged.TagNo, "tagged"); + } + } + + public static CertOrEncCert GetInstance(object obj) + { + if (obj is CertOrEncCert) + return (CertOrEncCert)obj; + + if (obj is Asn1TaggedObject) + return new CertOrEncCert((Asn1TaggedObject)obj); + + throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj"); + } + + public CertOrEncCert(CmpCertificate certificate) + { + if (certificate == null) + throw new ArgumentNullException("certificate"); + + this.certificate = certificate; + } + + public CertOrEncCert(EncryptedValue encryptedCert) + { + if (encryptedCert == null) + throw new ArgumentNullException("encryptedCert"); + + this.encryptedCert = encryptedCert; + } + + public virtual CmpCertificate Certificate + { + get { return certificate; } + } + + public virtual EncryptedValue EncryptedCert + { + get { return encryptedCert; } + } + + /** + *
+		 * CertOrEncCert ::= CHOICE {
+		 *                      certificate     [0] CMPCertificate,
+		 *                      encryptedCert   [1] EncryptedValue
+		 *           }
+		 * 
+ * @return a basic ASN.1 object representation. + */ + public override Asn1Object ToAsn1Object() + { + if (certificate != null) + { + return new DerTaggedObject(true, 0, certificate); + } + + return new DerTaggedObject(true, 1, encryptedCert); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cmp/CertRepMessage.cs b/bc-sharp-crypto/src/asn1/cmp/CertRepMessage.cs new file mode 100644 index 0000000..8286978 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cmp/CertRepMessage.cs @@ -0,0 +1,96 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cmp +{ + public class CertRepMessage + : Asn1Encodable + { + private readonly Asn1Sequence caPubs; + private readonly Asn1Sequence response; + + private CertRepMessage(Asn1Sequence seq) + { + int index = 0; + + if (seq.Count > 1) + { + caPubs = Asn1Sequence.GetInstance((Asn1TaggedObject)seq[index++], true); + } + + response = Asn1Sequence.GetInstance(seq[index]); + } + + public static CertRepMessage GetInstance(object obj) + { + if (obj is CertRepMessage) + return (CertRepMessage)obj; + + if (obj is Asn1Sequence) + return new CertRepMessage((Asn1Sequence)obj); + + throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj"); + } + + public CertRepMessage(CmpCertificate[] caPubs, CertResponse[] response) + { + if (response == null) + throw new ArgumentNullException("response"); + + if (caPubs != null) + { + this.caPubs = new DerSequence(caPubs); + } + + this.response = new DerSequence(response); + } + + public virtual CmpCertificate[] GetCAPubs() + { + if (caPubs == null) + return null; + + CmpCertificate[] results = new CmpCertificate[caPubs.Count]; + for (int i = 0; i != results.Length; ++i) + { + results[i] = CmpCertificate.GetInstance(caPubs[i]); + } + return results; + } + + public virtual CertResponse[] GetResponse() + { + CertResponse[] results = new CertResponse[response.Count]; + for (int i = 0; i != results.Length; ++i) + { + results[i] = CertResponse.GetInstance(response[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 override Asn1Object 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/bc-sharp-crypto/src/asn1/cmp/CertResponse.cs b/bc-sharp-crypto/src/asn1/cmp/CertResponse.cs new file mode 100644 index 0000000..843fd92 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cmp/CertResponse.cs @@ -0,0 +1,116 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cmp +{ + public class CertResponse + : Asn1Encodable + { + private readonly DerInteger certReqId; + private readonly PkiStatusInfo status; + private readonly CertifiedKeyPair certifiedKeyPair; + private readonly Asn1OctetString rspInfo; + + private CertResponse(Asn1Sequence seq) + { + certReqId = DerInteger.GetInstance(seq[0]); + status = PkiStatusInfo.GetInstance(seq[1]); + + if (seq.Count >= 3) + { + if (seq.Count == 3) + { + Asn1Encodable o = seq[2]; + if (o is Asn1OctetString) + { + rspInfo = Asn1OctetString.GetInstance(o); + } + else + { + certifiedKeyPair = CertifiedKeyPair.GetInstance(o); + } + } + else + { + certifiedKeyPair = CertifiedKeyPair.GetInstance(seq[2]); + rspInfo = Asn1OctetString.GetInstance(seq[3]); + } + } + } + + public static CertResponse GetInstance(object obj) + { + if (obj is CertResponse) + return (CertResponse)obj; + + if (obj is Asn1Sequence) + return new CertResponse((Asn1Sequence)obj); + + throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj"); + } + + public CertResponse( + DerInteger certReqId, + PkiStatusInfo status) + : this(certReqId, status, null, null) + { + } + + public CertResponse( + DerInteger certReqId, + PkiStatusInfo status, + CertifiedKeyPair certifiedKeyPair, + Asn1OctetString rspInfo) + { + if (certReqId == null) + throw new ArgumentNullException("certReqId"); + + if (status == null) + throw new ArgumentNullException("status"); + + this.certReqId = certReqId; + this.status = status; + this.certifiedKeyPair = certifiedKeyPair; + this.rspInfo = rspInfo; + } + + public virtual DerInteger CertReqID + { + get { return certReqId; } + } + + public virtual PkiStatusInfo Status + { + get { return status; } + } + + public virtual CertifiedKeyPair CertifiedKeyPair + { + get { 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 override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(certReqId, status); + v.AddOptional(certifiedKeyPair, rspInfo); + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cmp/CertStatus.cs b/bc-sharp-crypto/src/asn1/cmp/CertStatus.cs new file mode 100644 index 0000000..d437b57 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cmp/CertStatus.cs @@ -0,0 +1,85 @@ +using System; + +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cmp +{ + public class CertStatus + : Asn1Encodable + { + private readonly Asn1OctetString certHash; + private readonly DerInteger certReqId; + private readonly PkiStatusInfo statusInfo; + + private CertStatus(Asn1Sequence seq) + { + certHash = Asn1OctetString.GetInstance(seq[0]); + certReqId = DerInteger.GetInstance(seq[1]); + + if (seq.Count > 2) + { + statusInfo = PkiStatusInfo.GetInstance(seq[2]); + } + } + + public CertStatus(byte[] certHash, BigInteger certReqId) + { + this.certHash = new DerOctetString(certHash); + this.certReqId = new DerInteger(certReqId); + } + + public CertStatus(byte[] certHash, BigInteger certReqId, PkiStatusInfo statusInfo) + { + this.certHash = new DerOctetString(certHash); + this.certReqId = new DerInteger(certReqId); + this.statusInfo = statusInfo; + } + + public static CertStatus GetInstance(object obj) + { + if (obj is CertStatus) + return (CertStatus)obj; + + if (obj is Asn1Sequence) + return new CertStatus((Asn1Sequence)obj); + + throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj"); + } + + public virtual Asn1OctetString CertHash + { + get { return certHash; } + } + + public virtual DerInteger CertReqID + { + get { return certReqId; } + } + + public virtual PkiStatusInfo StatusInfo + { + get { 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 override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(certHash, certReqId); + v.AddOptional(statusInfo); + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cmp/CertifiedKeyPair.cs b/bc-sharp-crypto/src/asn1/cmp/CertifiedKeyPair.cs new file mode 100644 index 0000000..c06f000 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cmp/CertifiedKeyPair.cs @@ -0,0 +1,115 @@ +using System; + +using Org.BouncyCastle.Asn1.Crmf; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cmp +{ + public class CertifiedKeyPair + : Asn1Encodable + { + private readonly CertOrEncCert certOrEncCert; + private readonly EncryptedValue privateKey; + private readonly PkiPublicationInfo publicationInfo; + + private CertifiedKeyPair(Asn1Sequence seq) + { + certOrEncCert = CertOrEncCert.GetInstance(seq[0]); + + if (seq.Count >= 2) + { + if (seq.Count == 2) + { + Asn1TaggedObject tagged = Asn1TaggedObject.GetInstance(seq[1]); + if (tagged.TagNo == 0) + { + privateKey = EncryptedValue.GetInstance(tagged.GetObject()); + } + else + { + publicationInfo = PkiPublicationInfo.GetInstance(tagged.GetObject()); + } + } + else + { + privateKey = EncryptedValue.GetInstance(Asn1TaggedObject.GetInstance(seq[1])); + publicationInfo = PkiPublicationInfo.GetInstance(Asn1TaggedObject.GetInstance(seq[2])); + } + } + } + + public static CertifiedKeyPair GetInstance(object obj) + { + if (obj is CertifiedKeyPair) + return (CertifiedKeyPair)obj; + + if (obj is Asn1Sequence) + return new CertifiedKeyPair((Asn1Sequence)obj); + + throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj"); + } + + public CertifiedKeyPair( + CertOrEncCert certOrEncCert) + : this(certOrEncCert, null, null) + { + } + + public CertifiedKeyPair( + CertOrEncCert certOrEncCert, + EncryptedValue privateKey, + PkiPublicationInfo publicationInfo + ) + { + if (certOrEncCert == null) + throw new ArgumentNullException("certOrEncCert"); + + this.certOrEncCert = certOrEncCert; + this.privateKey = privateKey; + this.publicationInfo = publicationInfo; + } + + public virtual CertOrEncCert CertOrEncCert + { + get { return certOrEncCert; } + } + + public virtual EncryptedValue PrivateKey + { + get { return privateKey; } + } + + public virtual PkiPublicationInfo PublicationInfo + { + get { 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 override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(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/bc-sharp-crypto/src/asn1/cmp/Challenge.cs b/bc-sharp-crypto/src/asn1/cmp/Challenge.cs new file mode 100644 index 0000000..5c78c2a --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cmp/Challenge.cs @@ -0,0 +1,80 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cmp +{ + public class Challenge + : Asn1Encodable + { + private readonly AlgorithmIdentifier owf; + private readonly Asn1OctetString witness; + private readonly Asn1OctetString challenge; + + private Challenge(Asn1Sequence seq) + { + int index = 0; + + if (seq.Count == 3) + { + owf = AlgorithmIdentifier.GetInstance(seq[index++]); + } + + witness = Asn1OctetString.GetInstance(seq[index++]); + challenge = Asn1OctetString.GetInstance(seq[index]); + } + + public static Challenge GetInstance(object obj) + { + if (obj is Challenge) + return (Challenge)obj; + + if (obj is Asn1Sequence) + return new Challenge((Asn1Sequence)obj); + + throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj"); + } + + public virtual AlgorithmIdentifier Owf + { + get { 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 override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(); + v.AddOptional(owf); + v.Add(witness); + v.Add(challenge); + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cmp/CmpCertificate.cs b/bc-sharp-crypto/src/asn1/cmp/CmpCertificate.cs new file mode 100644 index 0000000..33356b4 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cmp/CmpCertificate.cs @@ -0,0 +1,81 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cmp +{ + public class CmpCertificate + : Asn1Encodable, IAsn1Choice + { + private readonly X509CertificateStructure x509v3PKCert; + private readonly AttributeCertificate x509v2AttrCert; + + /** + * Note: the addition of attribute certificates is a BC extension. + */ + public CmpCertificate(AttributeCertificate x509v2AttrCert) + { + this.x509v2AttrCert = x509v2AttrCert; + } + + public CmpCertificate(X509CertificateStructure x509v3PKCert) + { + if (x509v3PKCert.Version != 3) + throw new ArgumentException("only version 3 certificates allowed", "x509v3PKCert"); + + this.x509v3PKCert = x509v3PKCert; + } + + public static CmpCertificate GetInstance(object obj) + { + if (obj is CmpCertificate) + return (CmpCertificate)obj; + + if (obj is Asn1Sequence) + return new CmpCertificate(X509CertificateStructure.GetInstance(obj)); + + if (obj is Asn1TaggedObject) + return new CmpCertificate(AttributeCertificate.GetInstance(((Asn1TaggedObject)obj).GetObject())); + + throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj"); + } + + public virtual bool IsX509v3PKCert + { + get { return x509v3PKCert != null; } + } + + public virtual X509CertificateStructure X509v3PKCert + { + get { return x509v3PKCert; } + } + + public virtual AttributeCertificate X509v2AttrCert + { + get { return x509v2AttrCert; } + } + + /** + *
+         * CMPCertificate ::= CHOICE {
+         *            x509v3PKCert        Certificate
+         *            x509v2AttrCert      [1] AttributeCertificate
+         *  }
+         * 
+ * Note: the addition of attribute certificates is a BC extension. + * + * @return a basic ASN.1 object representation. + */ + public override Asn1Object ToAsn1Object() + { + if (x509v2AttrCert != null) + { + // explicit following CMP conventions + return new DerTaggedObject(true, 1, x509v2AttrCert); + } + + return x509v3PKCert.ToAsn1Object(); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cmp/CmpObjectIdentifiers.cs b/bc-sharp-crypto/src/asn1/cmp/CmpObjectIdentifiers.cs new file mode 100644 index 0000000..7e82741 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cmp/CmpObjectIdentifiers.cs @@ -0,0 +1,106 @@ +using System; + +namespace Org.BouncyCastle.Asn1.Cmp +{ + public abstract class CmpObjectIdentifiers + { + // RFC 4210 + + // id-PasswordBasedMac OBJECT IDENTIFIER ::= {1 2 840 113533 7 66 13} + public static readonly DerObjectIdentifier passwordBasedMac = new DerObjectIdentifier("1.2.840.113533.7.66.13"); + + // id-DHBasedMac OBJECT IDENTIFIER ::= {1 2 840 113533 7 66 30} + public static readonly DerObjectIdentifier dhBasedMac = new DerObjectIdentifier("1.2.840.113533.7.66.30"); + + // Example InfoTypeAndValue contents include, but are not limited + // to, the following (un-comment in this ASN.1 module and use as + // appropriate for a given environment): + // + // id-it-caProtEncCert OBJECT IDENTIFIER ::= {id-it 1} + // CAProtEncCertValue ::= CMPCertificate + // id-it-signKeyPairTypes OBJECT IDENTIFIER ::= {id-it 2} + // SignKeyPairTypesValue ::= SEQUENCE OF AlgorithmIdentifier + // id-it-encKeyPairTypes OBJECT IDENTIFIER ::= {id-it 3} + // EncKeyPairTypesValue ::= SEQUENCE OF AlgorithmIdentifier + // id-it-preferredSymmAlg OBJECT IDENTIFIER ::= {id-it 4} + // PreferredSymmAlgValue ::= AlgorithmIdentifier + // id-it-caKeyUpdateInfo OBJECT IDENTIFIER ::= {id-it 5} + // CAKeyUpdateInfoValue ::= CAKeyUpdAnnContent + // id-it-currentCRL OBJECT IDENTIFIER ::= {id-it 6} + // CurrentCRLValue ::= CertificateList + // id-it-unsupportedOIDs OBJECT IDENTIFIER ::= {id-it 7} + // UnsupportedOIDsValue ::= SEQUENCE OF OBJECT IDENTIFIER + // id-it-keyPairParamReq OBJECT IDENTIFIER ::= {id-it 10} + // KeyPairParamReqValue ::= OBJECT IDENTIFIER + // id-it-keyPairParamRep OBJECT IDENTIFIER ::= {id-it 11} + // KeyPairParamRepValue ::= AlgorithmIdentifer + // id-it-revPassphrase OBJECT IDENTIFIER ::= {id-it 12} + // RevPassphraseValue ::= EncryptedValue + // id-it-implicitConfirm OBJECT IDENTIFIER ::= {id-it 13} + // ImplicitConfirmValue ::= NULL + // id-it-confirmWaitTime OBJECT IDENTIFIER ::= {id-it 14} + // ConfirmWaitTimeValue ::= GeneralizedTime + // id-it-origPKIMessage OBJECT IDENTIFIER ::= {id-it 15} + // OrigPKIMessageValue ::= PKIMessages + // id-it-suppLangTags OBJECT IDENTIFIER ::= {id-it 16} + // SuppLangTagsValue ::= SEQUENCE OF UTF8String + // + // where + // + // id-pkix OBJECT IDENTIFIER ::= { + // iso(1) identified-organization(3) + // dod(6) internet(1) security(5) mechanisms(5) pkix(7)} + // and + // id-it OBJECT IDENTIFIER ::= {id-pkix 4} + public static readonly DerObjectIdentifier it_caProtEncCert = new DerObjectIdentifier("1.3.6.1.5.5.7.4.1"); + public static readonly DerObjectIdentifier it_signKeyPairTypes = new DerObjectIdentifier("1.3.6.1.5.5.7.4.2"); + public static readonly DerObjectIdentifier it_encKeyPairTypes = new DerObjectIdentifier("1.3.6.1.5.5.7.4.3"); + public static readonly DerObjectIdentifier it_preferredSymAlg = new DerObjectIdentifier("1.3.6.1.5.5.7.4.4"); + public static readonly DerObjectIdentifier it_caKeyUpdateInfo = new DerObjectIdentifier("1.3.6.1.5.5.7.4.5"); + public static readonly DerObjectIdentifier it_currentCRL = new DerObjectIdentifier("1.3.6.1.5.5.7.4.6"); + public static readonly DerObjectIdentifier it_unsupportedOIDs = new DerObjectIdentifier("1.3.6.1.5.5.7.4.7"); + public static readonly DerObjectIdentifier it_keyPairParamReq = new DerObjectIdentifier("1.3.6.1.5.5.7.4.10"); + public static readonly DerObjectIdentifier it_keyPairParamRep = new DerObjectIdentifier("1.3.6.1.5.5.7.4.11"); + public static readonly DerObjectIdentifier it_revPassphrase = new DerObjectIdentifier("1.3.6.1.5.5.7.4.12"); + public static readonly DerObjectIdentifier it_implicitConfirm = new DerObjectIdentifier("1.3.6.1.5.5.7.4.13"); + public static readonly DerObjectIdentifier it_confirmWaitTime = new DerObjectIdentifier("1.3.6.1.5.5.7.4.14"); + public static readonly DerObjectIdentifier it_origPKIMessage = new DerObjectIdentifier("1.3.6.1.5.5.7.4.15"); + public static readonly DerObjectIdentifier it_suppLangTags = new DerObjectIdentifier("1.3.6.1.5.5.7.4.16"); + + // RFC 4211 + + // id-pkix OBJECT IDENTIFIER ::= { iso(1) identified-organization(3) + // dod(6) internet(1) security(5) mechanisms(5) pkix(7) } + // + // arc for Internet X.509 PKI protocols and their components + // id-pkip OBJECT IDENTIFIER :: { id-pkix pkip(5) } + // + // arc for Registration Controls in CRMF + // id-regCtrl OBJECT IDENTIFIER ::= { id-pkip regCtrl(1) } + // + // arc for Registration Info in CRMF + // id-regInfo OBJECT IDENTIFIER ::= { id-pkip id-regInfo(2) } + + public static readonly DerObjectIdentifier regCtrl_regToken = new DerObjectIdentifier("1.3.6.1.5.5.7.5.1.1"); + public static readonly DerObjectIdentifier regCtrl_authenticator = new DerObjectIdentifier("1.3.6.1.5.5.7.5.1.2"); + public static readonly DerObjectIdentifier regCtrl_pkiPublicationInfo = new DerObjectIdentifier("1.3.6.1.5.5.7.5.1.3"); + public static readonly DerObjectIdentifier regCtrl_pkiArchiveOptions = new DerObjectIdentifier("1.3.6.1.5.5.7.5.1.4"); + public static readonly DerObjectIdentifier regCtrl_oldCertID = new DerObjectIdentifier("1.3.6.1.5.5.7.5.1.5"); + public static readonly DerObjectIdentifier regCtrl_protocolEncrKey = new DerObjectIdentifier("1.3.6.1.5.5.7.5.1.6"); + + // From RFC4210: + // id-regCtrl-altCertTemplate OBJECT IDENTIFIER ::= {id-regCtrl 7} + public static readonly DerObjectIdentifier regCtrl_altCertTemplate = new DerObjectIdentifier("1.3.6.1.5.5.7.5.1.7"); + + public static readonly DerObjectIdentifier regInfo_utf8Pairs = new DerObjectIdentifier("1.3.6.1.5.5.7.5.2.1"); + public static readonly DerObjectIdentifier regInfo_certReq = new DerObjectIdentifier("1.3.6.1.5.5.7.5.2.2"); + + // id-smime OBJECT IDENTIFIER ::= { iso(1) member-body(2) + // us(840) rsadsi(113549) pkcs(1) pkcs9(9) 16 } + // + // id-ct OBJECT IDENTIFIER ::= { id-smime 1 } -- content types + // + // id-ct-encKeyWithID OBJECT IDENTIFIER ::= {id-ct 21} + public static readonly DerObjectIdentifier ct_encKeyWithID = new DerObjectIdentifier("1.2.840.113549.1.9.16.1.21"); + } +} diff --git a/bc-sharp-crypto/src/asn1/cmp/CrlAnnContent.cs b/bc-sharp-crypto/src/asn1/cmp/CrlAnnContent.cs new file mode 100644 index 0000000..db8ecfa --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cmp/CrlAnnContent.cs @@ -0,0 +1,50 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cmp +{ + public class CrlAnnContent + : Asn1Encodable + { + private readonly Asn1Sequence content; + + private CrlAnnContent(Asn1Sequence seq) + { + content = seq; + } + + public static CrlAnnContent GetInstance(object obj) + { + if (obj is CrlAnnContent) + return (CrlAnnContent)obj; + + if (obj is Asn1Sequence) + return new CrlAnnContent((Asn1Sequence)obj); + + throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj"); + } + + public virtual CertificateList[] ToCertificateListArray() + { + CertificateList[] result = new CertificateList[content.Count]; + for (int i = 0; i != result.Length; ++ i) + { + result[i] = CertificateList.GetInstance(content[i]); + } + return result; + } + + /** + *
+		 * CrlAnnContent ::= SEQUENCE OF CertificateList
+		 * 
+ * @return a basic ASN.1 object representation. + */ + public override Asn1Object ToAsn1Object() + { + return content; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cmp/ErrorMsgContent.cs b/bc-sharp-crypto/src/asn1/cmp/ErrorMsgContent.cs new file mode 100644 index 0000000..5d2132b --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cmp/ErrorMsgContent.cs @@ -0,0 +1,95 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cmp +{ + public class ErrorMsgContent + : Asn1Encodable + { + private readonly PkiStatusInfo pkiStatusInfo; + private readonly DerInteger errorCode; + private readonly PkiFreeText errorDetails; + + private ErrorMsgContent(Asn1Sequence seq) + { + pkiStatusInfo = PkiStatusInfo.GetInstance(seq[0]); + + for (int pos = 1; pos < seq.Count; ++pos) + { + Asn1Encodable ae = seq[pos]; + if (ae is DerInteger) + { + errorCode = DerInteger.GetInstance(ae); + } + else + { + errorDetails = PkiFreeText.GetInstance(ae); + } + } + } + + public static ErrorMsgContent GetInstance(object obj) + { + if (obj is ErrorMsgContent) + return (ErrorMsgContent)obj; + + if (obj is Asn1Sequence) + return new ErrorMsgContent((Asn1Sequence)obj); + + throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj"); + } + + public ErrorMsgContent(PkiStatusInfo pkiStatusInfo) + : this(pkiStatusInfo, null, null) + { + } + + public ErrorMsgContent( + PkiStatusInfo pkiStatusInfo, + DerInteger errorCode, + PkiFreeText errorDetails) + { + if (pkiStatusInfo == null) + throw new ArgumentNullException("pkiStatusInfo"); + + this.pkiStatusInfo = pkiStatusInfo; + this.errorCode = errorCode; + this.errorDetails = errorDetails; + } + + public virtual PkiStatusInfo PkiStatusInfo + { + get { return pkiStatusInfo; } + } + + public virtual DerInteger ErrorCode + { + get { return errorCode; } + } + + public virtual PkiFreeText ErrorDetails + { + get { 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 override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(pkiStatusInfo); + v.AddOptional(errorCode, errorDetails); + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cmp/GenMsgContent.cs b/bc-sharp-crypto/src/asn1/cmp/GenMsgContent.cs new file mode 100644 index 0000000..f3142b5 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cmp/GenMsgContent.cs @@ -0,0 +1,54 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cmp +{ + public class GenMsgContent + : Asn1Encodable + { + private readonly Asn1Sequence content; + + private GenMsgContent(Asn1Sequence seq) + { + content = seq; + } + + public static GenMsgContent GetInstance(object obj) + { + if (obj is GenMsgContent) + return (GenMsgContent)obj; + + if (obj is Asn1Sequence) + return new GenMsgContent((Asn1Sequence)obj); + + throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj"); + } + + public GenMsgContent(params InfoTypeAndValue[] itv) + { + content = new DerSequence(itv); + } + + public virtual InfoTypeAndValue[] ToInfoTypeAndValueArray() + { + InfoTypeAndValue[] result = new InfoTypeAndValue[content.Count]; + for (int i = 0; i != result.Length; ++i) + { + result[i] = InfoTypeAndValue.GetInstance(content[i]); + } + return result; + } + + /** + *
+		 * GenMsgContent ::= SEQUENCE OF InfoTypeAndValue
+		 * 
+ * @return a basic ASN.1 object representation. + */ + public override Asn1Object ToAsn1Object() + { + return content; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cmp/GenRepContent.cs b/bc-sharp-crypto/src/asn1/cmp/GenRepContent.cs new file mode 100644 index 0000000..3c3573e --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cmp/GenRepContent.cs @@ -0,0 +1,54 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cmp +{ + public class GenRepContent + : Asn1Encodable + { + private readonly Asn1Sequence content; + + private GenRepContent(Asn1Sequence seq) + { + content = seq; + } + + public static GenRepContent GetInstance(object obj) + { + if (obj is GenRepContent) + return (GenRepContent)obj; + + if (obj is Asn1Sequence) + return new GenRepContent((Asn1Sequence)obj); + + throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj"); + } + + public GenRepContent(params InfoTypeAndValue[] itv) + { + content = new DerSequence(itv); + } + + public virtual InfoTypeAndValue[] ToInfoTypeAndValueArray() + { + InfoTypeAndValue[] result = new InfoTypeAndValue[content.Count]; + for (int i = 0; i != result.Length; ++i) + { + result[i] = InfoTypeAndValue.GetInstance(content[i]); + } + return result; + } + + /** + *
+		 * GenRepContent ::= SEQUENCE OF InfoTypeAndValue
+		 * 
+ * @return a basic ASN.1 object representation. + */ + public override Asn1Object ToAsn1Object() + { + return content; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cmp/InfoTypeAndValue.cs b/bc-sharp-crypto/src/asn1/cmp/InfoTypeAndValue.cs new file mode 100644 index 0000000..0ce6f73 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cmp/InfoTypeAndValue.cs @@ -0,0 +1,123 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cmp +{ + /** + * 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 + : Asn1Encodable + { + private readonly DerObjectIdentifier infoType; + private readonly Asn1Encodable infoValue; + + private InfoTypeAndValue(Asn1Sequence seq) + { + infoType = DerObjectIdentifier.GetInstance(seq[0]); + + if (seq.Count > 1) + { + infoValue = (Asn1Encodable)seq[1]; + } + } + + public static InfoTypeAndValue GetInstance(object obj) + { + if (obj is InfoTypeAndValue) + return (InfoTypeAndValue)obj; + + if (obj is Asn1Sequence) + return new InfoTypeAndValue((Asn1Sequence)obj); + + throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj"); + } + + public InfoTypeAndValue( + DerObjectIdentifier infoType) + { + this.infoType = infoType; + this.infoValue = null; + } + + public InfoTypeAndValue( + DerObjectIdentifier infoType, + Asn1Encodable optionalValue) + { + this.infoType = infoType; + this.infoValue = optionalValue; + } + + public virtual DerObjectIdentifier InfoType + { + get { return infoType; } + } + + public virtual Asn1Encodable InfoValue + { + get { return infoValue; } + } + + /** + *
+         * InfoTypeAndValue ::= SEQUENCE {
+         *                         infoType               OBJECT IDENTIFIER,
+         *                         infoValue              ANY DEFINED BY infoType  OPTIONAL
+         * }
+         * 
+ * @return a basic ASN.1 object representation. + */ + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(infoType); + + if (infoValue != null) + { + v.Add(infoValue); + } + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cmp/KeyRecRepContent.cs b/bc-sharp-crypto/src/asn1/cmp/KeyRecRepContent.cs new file mode 100644 index 0000000..00c4612 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cmp/KeyRecRepContent.cs @@ -0,0 +1,117 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cmp +{ + public class KeyRecRepContent + : Asn1Encodable + { + private readonly PkiStatusInfo status; + private readonly CmpCertificate newSigCert; + private readonly Asn1Sequence caCerts; + private readonly Asn1Sequence keyPairHist; + + private KeyRecRepContent(Asn1Sequence seq) + { + status = PkiStatusInfo.GetInstance(seq[0]); + + for (int pos = 1; pos < seq.Count; ++pos) + { + Asn1TaggedObject tObj = Asn1TaggedObject.GetInstance(seq[pos]); + + switch (tObj.TagNo) + { + 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 ArgumentException("unknown tag number: " + tObj.TagNo, "seq"); + } + } + } + + public static KeyRecRepContent GetInstance(object obj) + { + if (obj is KeyRecRepContent) + return (KeyRecRepContent)obj; + + if (obj is Asn1Sequence) + return new KeyRecRepContent((Asn1Sequence)obj); + + throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj"); + } + + public virtual PkiStatusInfo Status + { + get { return status; } + } + + public virtual CmpCertificate NewSigCert + { + get { return newSigCert; } + } + + public virtual CmpCertificate[] GetCACerts() + { + if (caCerts == null) + return null; + + CmpCertificate[] results = new CmpCertificate[caCerts.Count]; + for (int i = 0; i != results.Length; ++i) + { + results[i] = CmpCertificate.GetInstance(caCerts[i]); + } + return results; + } + + public virtual CertifiedKeyPair[] GetKeyPairHist() + { + if (keyPairHist == null) + return null; + + CertifiedKeyPair[] results = new CertifiedKeyPair[keyPairHist.Count]; + for (int i = 0; i != results.Length; ++i) + { + results[i] = CertifiedKeyPair.GetInstance(keyPairHist[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 override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(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/bc-sharp-crypto/src/asn1/cmp/OobCertHash.cs b/bc-sharp-crypto/src/asn1/cmp/OobCertHash.cs new file mode 100644 index 0000000..cd8192b --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cmp/OobCertHash.cs @@ -0,0 +1,88 @@ +using System; + +using Org.BouncyCastle.Asn1.Crmf; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cmp +{ + public class OobCertHash + : Asn1Encodable + { + private readonly AlgorithmIdentifier hashAlg; + private readonly CertId certId; + private readonly DerBitString hashVal; + + private OobCertHash(Asn1Sequence seq) + { + int index = seq.Count - 1; + + hashVal = DerBitString.GetInstance(seq[index--]); + + for (int i = index; i >= 0; i--) + { + Asn1TaggedObject tObj = (Asn1TaggedObject)seq[i]; + + if (tObj.TagNo == 0) + { + hashAlg = AlgorithmIdentifier.GetInstance(tObj, true); + } + else + { + certId = CertId.GetInstance(tObj, true); + } + } + } + + public static OobCertHash GetInstance(object obj) + { + if (obj is OobCertHash) + return (OobCertHash)obj; + + if (obj is Asn1Sequence) + return new OobCertHash((Asn1Sequence)obj); + + throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj"); + } + + public virtual AlgorithmIdentifier HashAlg + { + get { return hashAlg; } + } + + public virtual CertId CertID + { + get { 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 override Asn1Object 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/bc-sharp-crypto/src/asn1/cmp/PKIBody.cs b/bc-sharp-crypto/src/asn1/cmp/PKIBody.cs new file mode 100644 index 0000000..f17eed6 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cmp/PKIBody.cs @@ -0,0 +1,187 @@ +using System; + +using Org.BouncyCastle.Asn1.Crmf; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cmp +{ + public class PkiBody + : Asn1Encodable, IAsn1Choice + { + public const int TYPE_INIT_REQ = 0; + public const int TYPE_INIT_REP = 1; + public const int TYPE_CERT_REQ = 2; + public const int TYPE_CERT_REP = 3; + public const int TYPE_P10_CERT_REQ = 4; + public const int TYPE_POPO_CHALL = 5; + public const int TYPE_POPO_REP = 6; + public const int TYPE_KEY_UPDATE_REQ = 7; + public const int TYPE_KEY_UPDATE_REP = 8; + public const int TYPE_KEY_RECOVERY_REQ = 9; + public const int TYPE_KEY_RECOVERY_REP = 10; + public const int TYPE_REVOCATION_REQ = 11; + public const int TYPE_REVOCATION_REP = 12; + public const int TYPE_CROSS_CERT_REQ = 13; + public const int TYPE_CROSS_CERT_REP = 14; + public const int TYPE_CA_KEY_UPDATE_ANN = 15; + public const int TYPE_CERT_ANN = 16; + public const int TYPE_REVOCATION_ANN = 17; + public const int TYPE_CRL_ANN = 18; + public const int TYPE_CONFIRM = 19; + public const int TYPE_NESTED = 20; + public const int TYPE_GEN_MSG = 21; + public const int TYPE_GEN_REP = 22; + public const int TYPE_ERROR = 23; + public const int TYPE_CERT_CONFIRM = 24; + public const int TYPE_POLL_REQ = 25; + public const int TYPE_POLL_REP = 26; + + private int tagNo; + private Asn1Encodable body; + + public static PkiBody GetInstance(object obj) + { + if (obj is PkiBody) + return (PkiBody)obj; + + if (obj is Asn1TaggedObject) + return new PkiBody((Asn1TaggedObject)obj); + + throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj"); + } + + private PkiBody(Asn1TaggedObject tagged) + { + tagNo = tagged.TagNo; + body = GetBodyForType(tagNo, tagged.GetObject()); + } + + /** + * Creates a new PkiBody. + * @param type one of the TYPE_* constants + * @param content message content + */ + public PkiBody( + int type, + Asn1Encodable content) + { + tagNo = type; + body = GetBodyForType(type, content); + } + + private static Asn1Encodable GetBodyForType( + int type, + Asn1Encodable o) + { + switch (type) + { + case TYPE_INIT_REQ: + return CertReqMessages.GetInstance(o); + case TYPE_INIT_REP: + return CertRepMessage.GetInstance(o); + case TYPE_CERT_REQ: + return CertReqMessages.GetInstance(o); + case TYPE_CERT_REP: + return CertRepMessage.GetInstance(o); + case TYPE_P10_CERT_REQ: + return CertificationRequest.GetInstance(o); + case TYPE_POPO_CHALL: + return PopoDecKeyChallContent.GetInstance(o); + case TYPE_POPO_REP: + return PopoDecKeyRespContent.GetInstance(o); + case TYPE_KEY_UPDATE_REQ: + return CertReqMessages.GetInstance(o); + case TYPE_KEY_UPDATE_REP: + return CertRepMessage.GetInstance(o); + case TYPE_KEY_RECOVERY_REQ: + return CertReqMessages.GetInstance(o); + case TYPE_KEY_RECOVERY_REP: + return KeyRecRepContent.GetInstance(o); + case TYPE_REVOCATION_REQ: + return RevReqContent.GetInstance(o); + case TYPE_REVOCATION_REP: + return RevRepContent.GetInstance(o); + case TYPE_CROSS_CERT_REQ: + return CertReqMessages.GetInstance(o); + case TYPE_CROSS_CERT_REP: + return CertRepMessage.GetInstance(o); + case TYPE_CA_KEY_UPDATE_ANN: + return CAKeyUpdAnnContent.GetInstance(o); + case TYPE_CERT_ANN: + return CmpCertificate.GetInstance(o); + case TYPE_REVOCATION_ANN: + return RevAnnContent.GetInstance(o); + case TYPE_CRL_ANN: + return CrlAnnContent.GetInstance(o); + case TYPE_CONFIRM: + return PkiConfirmContent.GetInstance(o); + case TYPE_NESTED: + return PkiMessages.GetInstance(o); + case TYPE_GEN_MSG: + return GenMsgContent.GetInstance(o); + case TYPE_GEN_REP: + return GenRepContent.GetInstance(o); + case TYPE_ERROR: + return ErrorMsgContent.GetInstance(o); + case TYPE_CERT_CONFIRM: + return CertConfirmContent.GetInstance(o); + case TYPE_POLL_REQ: + return PollReqContent.GetInstance(o); + case TYPE_POLL_REP: + return PollRepContent.GetInstance(o); + default: + throw new ArgumentException("unknown tag number: " + type, "type"); + } + } + + public virtual int Type + { + get { return tagNo; } + } + + public virtual Asn1Encodable Content + { + get { return body; } + } + + /** + *
+         * 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 override Asn1Object ToAsn1Object() + { + return new DerTaggedObject(true, tagNo, body); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cmp/PKIConfirmContent.cs b/bc-sharp-crypto/src/asn1/cmp/PKIConfirmContent.cs new file mode 100644 index 0000000..d154427 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cmp/PKIConfirmContent.cs @@ -0,0 +1,36 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cmp +{ + public class PkiConfirmContent + : Asn1Encodable + { + public static PkiConfirmContent GetInstance(object obj) + { + if (obj is PkiConfirmContent) + return (PkiConfirmContent)obj; + + if (obj is Asn1Null) + return new PkiConfirmContent(); + + throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj"); + } + + public PkiConfirmContent() + { + } + + /** + *
+		 * PkiConfirmContent ::= NULL
+		 * 
+ * @return a basic ASN.1 object representation. + */ + public override Asn1Object ToAsn1Object() + { + return DerNull.Instance; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cmp/PKIFailureInfo.cs b/bc-sharp-crypto/src/asn1/cmp/PKIFailureInfo.cs new file mode 100644 index 0000000..75a3ff0 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cmp/PKIFailureInfo.cs @@ -0,0 +1,96 @@ +using System; + +namespace Org.BouncyCastle.Asn1.Cmp +{ + /** + *
+     * 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
+     * certRevoked         (10),
+     * certConfirmed       (11),
+     * wrongIntegrity      (12),
+     * badRecipientNonce   (13), 
+     * timeNotAvailable    (14),
+     *   -- the TSA's time source is not available
+     * unacceptedPolicy    (15),
+     *   -- the requested TSA policy is not supported by the TSA
+     * unacceptedExtension (16),
+     *   -- the requested extension is not supported by the TSA
+     * addInfoNotAvailable (17)
+     *   -- the additional information requested could not be understood
+     *   -- or is not available
+     * badSenderNonce      (18),
+     * badCertTemplate     (19),
+     * signerNotTrusted    (20),
+     * transactionIdInUse  (21),
+     * unsupportedVersion  (22),
+     * notAuthorized       (23),
+     * systemUnavail       (24),    
+     * systemFailure       (25),
+     *   -- the request cannot be handled due to system failure
+     * duplicateCertReq    (26) 
+     * 
+ */ + public class PkiFailureInfo + : DerBitString + { + public const int BadAlg = (1 << 7); // unrecognized or unsupported Algorithm Identifier + public const int BadMessageCheck = (1 << 6); // integrity check failed (e.g., signature did not verify) + public const int BadRequest = (1 << 5); + public const int BadTime = (1 << 4); // -- messageTime was not sufficiently close to the system time, as defined by local policy + public const int BadCertId = (1 << 3); // no certificate could be found matching the provided criteria + public const int BadDataFormat = (1 << 2); + public const int WrongAuthority = (1 << 1); // the authority indicated in the request is different from the one creating the response token + public const int IncorrectData = 1; // the requester's data is incorrect (for notary services) + public const int MissingTimeStamp = (1 << 15); // when the timestamp is missing but should be there (by policy) + public const int BadPop = (1 << 14); // the proof-of-possession failed + public const int CertRevoked = (1 << 13); + public const int CertConfirmed = (1 << 12); + public const int WrongIntegrity = (1 << 11); + public const int BadRecipientNonce = (1 << 10); + public const int TimeNotAvailable = (1 << 9); // the TSA's time source is not available + public const int UnacceptedPolicy = (1 << 8); // the requested TSA policy is not supported by the TSA + public const int UnacceptedExtension = (1 << 23); //the requested extension is not supported by the TSA + public const int AddInfoNotAvailable = (1 << 22); //the additional information requested could not be understood or is not available + public const int BadSenderNonce = (1 << 21); + public const int BadCertTemplate = (1 << 20); + public const int SignerNotTrusted = (1 << 19); + public const int TransactionIdInUse = (1 << 18); + public const int UnsupportedVersion = (1 << 17); + public const int NotAuthorized = (1 << 16); + public const int SystemUnavail = (1 << 31); + public const int SystemFailure = (1 << 30); //the request cannot be handled due to system failure + public const int DuplicateCertReq = (1 << 29); + + /** + * Basic constructor. + */ + public PkiFailureInfo(int info) + : base(info) + { + } + + public PkiFailureInfo( + DerBitString info) + : base(info.GetBytes(), info.PadBits) + { + } + + public override string ToString() + { + return "PkiFailureInfo: 0x" + this.IntValue.ToString("X"); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cmp/PKIFreeText.cs b/bc-sharp-crypto/src/asn1/cmp/PKIFreeText.cs new file mode 100644 index 0000000..fef5254 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cmp/PKIFreeText.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cmp +{ + public class PkiFreeText + : Asn1Encodable + { + internal Asn1Sequence strings; + + public static PkiFreeText GetInstance( + Asn1TaggedObject obj, + bool isExplicit) + { + return GetInstance(Asn1Sequence.GetInstance(obj, isExplicit)); + } + + public static PkiFreeText GetInstance( + object obj) + { + if (obj is PkiFreeText) + { + return (PkiFreeText)obj; + } + else if (obj is Asn1Sequence) + { + return new PkiFreeText((Asn1Sequence)obj); + } + + throw new ArgumentException("Unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + public PkiFreeText( + Asn1Sequence seq) + { + foreach (object o in seq) + { + if (!(o is DerUtf8String)) + { + throw new ArgumentException("attempt to insert non UTF8 STRING into PkiFreeText"); + } + } + + this.strings = seq; + } + + public PkiFreeText( + DerUtf8String p) + { + strings = new DerSequence(p); + } + + /** + * Return the number of string elements present. + * + * @return number of elements present. + */ + [Obsolete("Use 'Count' property instead")] + public int Size + { + get { return strings.Count; } + } + + public int Count + { + get { return strings.Count; } + } + + /** + * Return the UTF8STRING at index. + * + * @param index index of the string of interest + * @return the string at index. + */ + public DerUtf8String this[int index] + { + get { return (DerUtf8String) strings[index]; } + } + + [Obsolete("Use 'object[index]' syntax instead")] + public DerUtf8String GetStringAt( + int index) + { + return this[index]; + } + + /** + *
+		 * PkiFreeText ::= SEQUENCE SIZE (1..MAX) OF UTF8String
+		 * 
+ */ + public override Asn1Object ToAsn1Object() + { + return strings; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cmp/PKIHeader.cs b/bc-sharp-crypto/src/asn1/cmp/PKIHeader.cs new file mode 100644 index 0000000..577cb45 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cmp/PKIHeader.cs @@ -0,0 +1,238 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cmp +{ + public class PkiHeader + : Asn1Encodable + { + /** + * Value for a "null" recipient or sender. + */ + public static readonly GeneralName NULL_NAME = new GeneralName(X509Name.GetInstance(new DerSequence())); + + public static readonly int CMP_1999 = 1; + public static readonly int CMP_2000 = 2; + + private readonly DerInteger pvno; + private readonly GeneralName sender; + private readonly GeneralName recipient; + private readonly DerGeneralizedTime messageTime; + private readonly AlgorithmIdentifier protectionAlg; + private readonly Asn1OctetString senderKID; // KeyIdentifier + private readonly Asn1OctetString recipKID; // KeyIdentifier + private readonly Asn1OctetString transactionID; + private readonly Asn1OctetString senderNonce; + private readonly Asn1OctetString recipNonce; + private readonly PkiFreeText freeText; + private readonly Asn1Sequence generalInfo; + + private PkiHeader(Asn1Sequence seq) + { + pvno = DerInteger.GetInstance(seq[0]); + sender = GeneralName.GetInstance(seq[1]); + recipient = GeneralName.GetInstance(seq[2]); + + for (int pos = 3; pos < seq.Count; ++pos) + { + Asn1TaggedObject tObj = (Asn1TaggedObject)seq[pos]; + + switch (tObj.TagNo) + { + 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 ArgumentException("unknown tag number: " + tObj.TagNo, "seq"); + } + } + } + + public static PkiHeader GetInstance(object obj) + { + if (obj is PkiHeader) + return (PkiHeader)obj; + + if (obj is Asn1Sequence) + return new PkiHeader((Asn1Sequence)obj); + + throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj"); + } + + public PkiHeader( + int pvno, + GeneralName sender, + GeneralName recipient) + : this(new DerInteger(pvno), sender, recipient) + { + } + + private PkiHeader( + DerInteger pvno, + GeneralName sender, + GeneralName recipient) + { + this.pvno = pvno; + this.sender = sender; + this.recipient = recipient; + } + + public virtual DerInteger Pvno + { + get { return pvno; } + } + + public virtual GeneralName Sender + { + get { return sender; } + } + + public virtual GeneralName Recipient + { + get { return recipient; } + } + + public virtual DerGeneralizedTime MessageTime + { + get { return messageTime; } + } + + public virtual AlgorithmIdentifier ProtectionAlg + { + get { return protectionAlg; } + } + + public virtual Asn1OctetString SenderKID + { + get { return senderKID; } + } + + public virtual Asn1OctetString RecipKID + { + get { return recipKID; } + } + + public virtual Asn1OctetString TransactionID + { + get { return transactionID; } + } + + public virtual Asn1OctetString SenderNonce + { + get { return senderNonce; } + } + + public virtual Asn1OctetString RecipNonce + { + get { return recipNonce; } + } + + public virtual PkiFreeText FreeText + { + get { return freeText; } + } + + public virtual InfoTypeAndValue[] GetGeneralInfo() + { + if (generalInfo == null) + { + return null; + } + InfoTypeAndValue[] results = new InfoTypeAndValue[generalInfo.Count]; + for (int i = 0; i < results.Length; i++) + { + results[i] = InfoTypeAndValue.GetInstance(generalInfo[i]); + } + return results; + } + + /** + *
+         *  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 override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(pvno, sender, 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 static void AddOptional(Asn1EncodableVector v, int tagNo, Asn1Encodable obj) + { + if (obj != null) + { + v.Add(new DerTaggedObject(true, tagNo, obj)); + } + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cmp/PKIHeaderBuilder.cs b/bc-sharp-crypto/src/asn1/cmp/PKIHeaderBuilder.cs new file mode 100644 index 0000000..00073c0 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cmp/PKIHeaderBuilder.cs @@ -0,0 +1,223 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; + +namespace Org.BouncyCastle.Asn1.Cmp +{ + public class PkiHeaderBuilder + { + 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; + + public PkiHeaderBuilder( + int pvno, + GeneralName sender, + GeneralName recipient) + : this(new DerInteger(pvno), sender, recipient) + { + } + + private PkiHeaderBuilder( + DerInteger pvno, + GeneralName sender, + GeneralName recipient) + { + this.pvno = pvno; + this.sender = sender; + this.recipient = recipient; + } + + public virtual PkiHeaderBuilder SetMessageTime(DerGeneralizedTime time) + { + messageTime = time; + return this; + } + + public virtual PkiHeaderBuilder SetProtectionAlg(AlgorithmIdentifier aid) + { + protectionAlg = aid; + return this; + } + + public virtual PkiHeaderBuilder SetSenderKID(byte[] kid) + { + return SetSenderKID(kid == null ? null : new DerOctetString(kid)); + } + + public virtual PkiHeaderBuilder SetSenderKID(Asn1OctetString kid) + { + senderKID = kid; + return this; + } + + public virtual PkiHeaderBuilder SetRecipKID(byte[] kid) + { + return SetRecipKID(kid == null ? null : new DerOctetString(kid)); + } + + public virtual PkiHeaderBuilder SetRecipKID(DerOctetString kid) + { + recipKID = kid; + return this; + } + + public virtual PkiHeaderBuilder SetTransactionID(byte[] tid) + { + return SetTransactionID(tid == null ? null : new DerOctetString(tid)); + } + + public virtual PkiHeaderBuilder SetTransactionID(Asn1OctetString tid) + { + transactionID = tid; + return this; + } + + public virtual PkiHeaderBuilder SetSenderNonce(byte[] nonce) + { + return SetSenderNonce(nonce == null ? null : new DerOctetString(nonce)); + } + + public virtual PkiHeaderBuilder SetSenderNonce(Asn1OctetString nonce) + { + senderNonce = nonce; + return this; + } + + public virtual PkiHeaderBuilder SetRecipNonce(byte[] nonce) + { + return SetRecipNonce(nonce == null ? null : new DerOctetString(nonce)); + } + + public virtual PkiHeaderBuilder SetRecipNonce(Asn1OctetString nonce) + { + recipNonce = nonce; + return this; + } + + public virtual PkiHeaderBuilder SetFreeText(PkiFreeText text) + { + freeText = text; + return this; + } + + public virtual PkiHeaderBuilder SetGeneralInfo(InfoTypeAndValue genInfo) + { + return SetGeneralInfo(MakeGeneralInfoSeq(genInfo)); + } + + public virtual PkiHeaderBuilder SetGeneralInfo(InfoTypeAndValue[] genInfos) + { + return SetGeneralInfo(MakeGeneralInfoSeq(genInfos)); + } + + public virtual PkiHeaderBuilder SetGeneralInfo(Asn1Sequence seqOfInfoTypeAndValue) + { + generalInfo = seqOfInfoTypeAndValue; + return this; + } + + private static Asn1Sequence MakeGeneralInfoSeq( + InfoTypeAndValue generalInfo) + { + return new DerSequence(generalInfo); + } + + private static Asn1Sequence MakeGeneralInfoSeq( + InfoTypeAndValue[] generalInfos) + { + Asn1Sequence genInfoSeq = null; + if (generalInfos != null) + { + Asn1EncodableVector v = new Asn1EncodableVector(); + for (int i = 0; i < generalInfos.Length; ++i) + { + v.Add(generalInfos[i]); + } + genInfoSeq = new DerSequence(v); + } + return genInfoSeq; + } + + /** + *
+		 *  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 virtual PkiHeader Build() + { + Asn1EncodableVector v = new Asn1EncodableVector(pvno, sender, recipient); + AddOptional(v, 0, messageTime); + AddOptional(v, 1, protectionAlg); + AddOptional(v, 2, senderKID); + AddOptional(v, 3, recipKID); + AddOptional(v, 4, transactionID); + AddOptional(v, 5, senderNonce); + AddOptional(v, 6, recipNonce); + AddOptional(v, 7, freeText); + AddOptional(v, 8, generalInfo); + + messageTime = null; + protectionAlg = null; + senderKID = null; + recipKID = null; + transactionID = null; + senderNonce = null; + recipNonce = null; + freeText = null; + generalInfo = null; + + return PkiHeader.GetInstance(new DerSequence(v)); + } + + private void AddOptional(Asn1EncodableVector v, int tagNo, Asn1Encodable obj) + { + if (obj != null) + { + v.Add(new DerTaggedObject(true, tagNo, obj)); + } + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cmp/PKIMessage.cs b/bc-sharp-crypto/src/asn1/cmp/PKIMessage.cs new file mode 100644 index 0000000..086a2d9 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cmp/PKIMessage.cs @@ -0,0 +1,140 @@ +using System; + +namespace Org.BouncyCastle.Asn1.Cmp +{ + public class PkiMessage + : Asn1Encodable + { + private readonly PkiHeader header; + private readonly PkiBody body; + private readonly DerBitString protection; + private readonly Asn1Sequence extraCerts; + + private PkiMessage(Asn1Sequence seq) + { + header = PkiHeader.GetInstance(seq[0]); + body = PkiBody.GetInstance(seq[1]); + + for (int pos = 2; pos < seq.Count; ++pos) + { + Asn1TaggedObject tObj = (Asn1TaggedObject)seq[pos].ToAsn1Object(); + + if (tObj.TagNo == 0) + { + protection = DerBitString.GetInstance(tObj, true); + } + else + { + extraCerts = Asn1Sequence.GetInstance(tObj, true); + } + } + } + + public static PkiMessage GetInstance(object obj) + { + if (obj is PkiMessage) + return (PkiMessage)obj; + + if (obj != null) + return new PkiMessage(Asn1Sequence.GetInstance(obj)); + + return null; + } + + /** + * Creates a new PkiMessage. + * + * @param header message header + * @param body message body + * @param protection message protection (may be null) + * @param extraCerts extra certificates (may be null) + */ + public PkiMessage( + PkiHeader header, + PkiBody body, + DerBitString protection, + CmpCertificate[] extraCerts) + { + this.header = header; + this.body = body; + this.protection = protection; + if (extraCerts != null) + { + this.extraCerts = new DerSequence(extraCerts); + } + } + + public PkiMessage( + PkiHeader header, + PkiBody body, + DerBitString protection) + : this(header, body, protection, null) + { + } + + public PkiMessage( + PkiHeader header, + PkiBody body) + : this(header, body, null, null) + { + } + + public virtual PkiHeader Header + { + get { return header; } + } + + public virtual PkiBody Body + { + get { return body; } + } + + public virtual DerBitString Protection + { + get { return protection; } + } + + public virtual CmpCertificate[] GetExtraCerts() + { + if (extraCerts == null) + return null; + + CmpCertificate[] results = new CmpCertificate[extraCerts.Count]; + for (int i = 0; i < results.Length; ++i) + { + results[i] = CmpCertificate.GetInstance(extraCerts[i]); + } + return results; + } + + /** + *
+         * 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 override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(header, body); + + AddOptional(v, 0, protection); + AddOptional(v, 1, extraCerts); + + return new DerSequence(v); + } + + private static void AddOptional(Asn1EncodableVector v, int tagNo, Asn1Encodable obj) + { + if (obj != null) + { + v.Add(new DerTaggedObject(true, tagNo, obj)); + } + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cmp/PKIMessages.cs b/bc-sharp-crypto/src/asn1/cmp/PKIMessages.cs new file mode 100644 index 0000000..eb01e54 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cmp/PKIMessages.cs @@ -0,0 +1,54 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cmp +{ + public class PkiMessages + : Asn1Encodable + { + private Asn1Sequence content; + + private PkiMessages(Asn1Sequence seq) + { + content = seq; + } + + public static PkiMessages GetInstance(object obj) + { + if (obj is PkiMessages) + return (PkiMessages)obj; + + if (obj is Asn1Sequence) + return new PkiMessages((Asn1Sequence)obj); + + throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj"); + } + + public PkiMessages(params PkiMessage[] msgs) + { + content = new DerSequence(msgs); + } + + public virtual PkiMessage[] ToPkiMessageArray() + { + PkiMessage[] result = new PkiMessage[content.Count]; + for (int i = 0; i != result.Length; ++i) + { + result[i] = PkiMessage.GetInstance(content[i]); + } + return result; + } + + /** + *
+         * PkiMessages ::= SEQUENCE SIZE (1..MAX) OF PkiMessage
+         * 
+ * @return a basic ASN.1 object representation. + */ + public override Asn1Object ToAsn1Object() + { + return content; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cmp/PKIStatus.cs b/bc-sharp-crypto/src/asn1/cmp/PKIStatus.cs new file mode 100644 index 0000000..ba757df --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cmp/PKIStatus.cs @@ -0,0 +1,63 @@ +using System; + +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cmp +{ + public enum PkiStatus + { + Granted = 0, + GrantedWithMods = 1, + Rejection = 2, + Waiting = 3, + RevocationWarning = 4, + RevocationNotification = 5, + KeyUpdateWarning = 6, + } + + public class PkiStatusEncodable + : Asn1Encodable + { + public static readonly PkiStatusEncodable granted = new PkiStatusEncodable(PkiStatus.Granted); + public static readonly PkiStatusEncodable grantedWithMods = new PkiStatusEncodable(PkiStatus.GrantedWithMods); + public static readonly PkiStatusEncodable rejection = new PkiStatusEncodable(PkiStatus.Rejection); + public static readonly PkiStatusEncodable waiting = new PkiStatusEncodable(PkiStatus.Waiting); + public static readonly PkiStatusEncodable revocationWarning = new PkiStatusEncodable(PkiStatus.RevocationWarning); + public static readonly PkiStatusEncodable revocationNotification = new PkiStatusEncodable(PkiStatus.RevocationNotification); + public static readonly PkiStatusEncodable keyUpdateWaiting = new PkiStatusEncodable(PkiStatus.KeyUpdateWarning); + + private readonly DerInteger status; + + private PkiStatusEncodable(PkiStatus status) + : this(new DerInteger((int)status)) + { + } + + private PkiStatusEncodable(DerInteger status) + { + this.status = status; + } + + public static PkiStatusEncodable GetInstance(object obj) + { + if (obj is PkiStatusEncodable) + return (PkiStatusEncodable)obj; + + if (obj is DerInteger) + return new PkiStatusEncodable((DerInteger)obj); + + throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj"); + } + + public virtual BigInteger Value + { + get { return status.Value; } + } + + public override Asn1Object ToAsn1Object() + { + return status; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cmp/PKIStatusInfo.cs b/bc-sharp-crypto/src/asn1/cmp/PKIStatusInfo.cs new file mode 100644 index 0000000..b19bf74 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cmp/PKIStatusInfo.cs @@ -0,0 +1,166 @@ +using System; + +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cmp +{ + public class PkiStatusInfo + : Asn1Encodable + { + DerInteger status; + PkiFreeText statusString; + DerBitString failInfo; + + public static PkiStatusInfo GetInstance( + Asn1TaggedObject obj, + bool isExplicit) + { + return GetInstance(Asn1Sequence.GetInstance(obj, isExplicit)); + } + + public static PkiStatusInfo GetInstance( + object obj) + { + if (obj is PkiStatusInfo) + { + return (PkiStatusInfo)obj; + } + else if (obj is Asn1Sequence) + { + return new PkiStatusInfo((Asn1Sequence)obj); + } + + throw new ArgumentException("Unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + public PkiStatusInfo( + Asn1Sequence seq) + { + this.status = DerInteger.GetInstance(seq[0]); + + this.statusString = null; + this.failInfo = null; + + if (seq.Count > 2) + { + this.statusString = PkiFreeText.GetInstance(seq[1]); + this.failInfo = DerBitString.GetInstance(seq[2]); + } + else if (seq.Count > 1) + { + object obj = seq[1]; + if (obj is 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 Status + { + get + { + return status.Value; + } + } + + public PkiFreeText StatusString + { + get + { + return statusString; + } + } + + public DerBitString FailInfo + { + get + { + 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 override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(status); + + if (statusString != null) + { + v.Add(statusString); + } + + if (failInfo!= null) + { + v.Add(failInfo); + } + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cmp/PbmParameter.cs b/bc-sharp-crypto/src/asn1/cmp/PbmParameter.cs new file mode 100644 index 0000000..206b89b --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cmp/PbmParameter.cs @@ -0,0 +1,101 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cmp +{ + public class PbmParameter + : Asn1Encodable + { + private Asn1OctetString salt; + private AlgorithmIdentifier owf; + private DerInteger iterationCount; + private AlgorithmIdentifier mac; + + private PbmParameter(Asn1Sequence seq) + { + salt = Asn1OctetString.GetInstance(seq[0]); + owf = AlgorithmIdentifier.GetInstance(seq[1]); + iterationCount = DerInteger.GetInstance(seq[2]); + mac = AlgorithmIdentifier.GetInstance(seq[3]); + } + + public static PbmParameter GetInstance(object obj) + { + if (obj is PbmParameter) + return (PbmParameter)obj; + + if (obj is Asn1Sequence) + return new PbmParameter((Asn1Sequence)obj); + + throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj"); + } + + public PbmParameter( + byte[] salt, + AlgorithmIdentifier owf, + int iterationCount, + AlgorithmIdentifier mac) + : this(new DerOctetString(salt), owf, new DerInteger(iterationCount), mac) + { + } + + public PbmParameter( + Asn1OctetString salt, + AlgorithmIdentifier owf, + DerInteger iterationCount, + AlgorithmIdentifier mac) + { + this.salt = salt; + this.owf = owf; + this.iterationCount = iterationCount; + this.mac = mac; + } + + public virtual Asn1OctetString Salt + { + get { return salt; } + } + + public virtual AlgorithmIdentifier Owf + { + get { return owf; } + } + + public virtual DerInteger IterationCount + { + get { return iterationCount; } + } + + public virtual AlgorithmIdentifier Mac + { + get { 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 override Asn1Object ToAsn1Object() + { + return new DerSequence(salt, owf, iterationCount, mac); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cmp/PollRepContent.cs b/bc-sharp-crypto/src/asn1/cmp/PollRepContent.cs new file mode 100644 index 0000000..f8bb098 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cmp/PollRepContent.cs @@ -0,0 +1,68 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cmp +{ + public class PollRepContent + : Asn1Encodable + { + private readonly DerInteger certReqId; + private readonly DerInteger checkAfter; + private readonly PkiFreeText reason; + + private PollRepContent(Asn1Sequence seq) + { + certReqId = DerInteger.GetInstance(seq[0]); + checkAfter = DerInteger.GetInstance(seq[1]); + + if (seq.Count > 2) + { + reason = PkiFreeText.GetInstance(seq[2]); + } + } + + public static PollRepContent GetInstance(object obj) + { + if (obj is PollRepContent) + return (PollRepContent)obj; + + if (obj is Asn1Sequence) + return new PollRepContent((Asn1Sequence)obj); + + throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj"); + } + + public virtual DerInteger CertReqID + { + get { return certReqId; } + } + + public virtual DerInteger CheckAfter + { + get { return checkAfter; } + } + + public virtual PkiFreeText Reason + { + get { return reason; } + } + + /** + *
+		 * PollRepContent ::= SEQUENCE OF SEQUENCE {
+		 *         certReqId              INTEGER,
+		 *         checkAfter             INTEGER,  -- time in seconds
+		 *         reason                 PKIFreeText OPTIONAL
+		 *     }
+		 * 
+ * @return a basic ASN.1 object representation. + */ + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(certReqId, checkAfter); + v.AddOptional(reason); + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cmp/PollReqContent.cs b/bc-sharp-crypto/src/asn1/cmp/PollReqContent.cs new file mode 100644 index 0000000..dd9b0c3 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cmp/PollReqContent.cs @@ -0,0 +1,61 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cmp +{ + public class PollReqContent + : Asn1Encodable + { + private readonly Asn1Sequence content; + + private PollReqContent(Asn1Sequence seq) + { + content = seq; + } + + public static PollReqContent GetInstance(object obj) + { + if (obj is PollReqContent) + return (PollReqContent)obj; + + if (obj is Asn1Sequence) + return new PollReqContent((Asn1Sequence)obj); + + throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj"); + } + + public virtual DerInteger[][] GetCertReqIDs() + { + DerInteger[][] result = new DerInteger[content.Count][]; + for (int i = 0; i != result.Length; ++i) + { + result[i] = SequenceToDerIntegerArray((Asn1Sequence)content[i]); + } + return result; + } + + private static DerInteger[] SequenceToDerIntegerArray(Asn1Sequence seq) + { + DerInteger[] result = new DerInteger[seq.Count]; + for (int i = 0; i != result.Length; ++i) + { + result[i] = DerInteger.GetInstance(seq[i]); + } + return result; + } + + /** + *
+		 * PollReqContent ::= SEQUENCE OF SEQUENCE {
+		 *                        certReqId              INTEGER
+		 * }
+		 * 
+ * @return a basic ASN.1 object representation. + */ + public override Asn1Object ToAsn1Object() + { + return content; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cmp/PopoDecKeyChallContent.cs b/bc-sharp-crypto/src/asn1/cmp/PopoDecKeyChallContent.cs new file mode 100644 index 0000000..03a13a5 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cmp/PopoDecKeyChallContent.cs @@ -0,0 +1,49 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cmp +{ + public class PopoDecKeyChallContent + : Asn1Encodable + { + private readonly Asn1Sequence content; + + private PopoDecKeyChallContent(Asn1Sequence seq) + { + content = seq; + } + + public static PopoDecKeyChallContent GetInstance(object obj) + { + if (obj is PopoDecKeyChallContent) + return (PopoDecKeyChallContent)obj; + + if (obj is Asn1Sequence) + return new PopoDecKeyChallContent((Asn1Sequence)obj); + + throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj"); + } + + public virtual Challenge[] ToChallengeArray() + { + Challenge[] result = new Challenge[content.Count]; + for (int i = 0; i != result.Length; ++i) + { + result[i] = Challenge.GetInstance(content[i]); + } + return result; + } + + /** + *
+	     * PopoDecKeyChallContent ::= SEQUENCE OF Challenge
+	     * 
+ * @return a basic ASN.1 object representation. + */ + public override Asn1Object ToAsn1Object() + { + return content; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cmp/PopoDecKeyRespContent.cs b/bc-sharp-crypto/src/asn1/cmp/PopoDecKeyRespContent.cs new file mode 100644 index 0000000..73f59b7 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cmp/PopoDecKeyRespContent.cs @@ -0,0 +1,49 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cmp +{ + public class PopoDecKeyRespContent + : Asn1Encodable + { + private readonly Asn1Sequence content; + + private PopoDecKeyRespContent(Asn1Sequence seq) + { + content = seq; + } + + public static PopoDecKeyRespContent GetInstance(object obj) + { + if (obj is PopoDecKeyRespContent) + return (PopoDecKeyRespContent)obj; + + if (obj is Asn1Sequence) + return new PopoDecKeyRespContent((Asn1Sequence)obj); + + throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj"); + } + + public virtual DerInteger[] ToDerIntegerArray() + { + DerInteger[] result = new DerInteger[content.Count]; + for (int i = 0; i != result.Length; ++i) + { + result[i] = DerInteger.GetInstance(content[i]); + } + return result; + } + + /** + *
+		 * PopoDecKeyRespContent ::= SEQUENCE OF INTEGER
+		 * 
+ * @return a basic ASN.1 object representation. + */ + public override Asn1Object ToAsn1Object() + { + return content; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cmp/ProtectedPart.cs b/bc-sharp-crypto/src/asn1/cmp/ProtectedPart.cs new file mode 100644 index 0000000..ed90708 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cmp/ProtectedPart.cs @@ -0,0 +1,60 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cmp +{ + public class ProtectedPart + : Asn1Encodable + { + private readonly PkiHeader header; + private readonly PkiBody body; + + private ProtectedPart(Asn1Sequence seq) + { + header = PkiHeader.GetInstance(seq[0]); + body = PkiBody.GetInstance(seq[1]); + } + + public static ProtectedPart GetInstance(object obj) + { + if (obj is ProtectedPart) + return (ProtectedPart)obj; + + if (obj is Asn1Sequence) + return new ProtectedPart((Asn1Sequence)obj); + + throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj"); + } + + public ProtectedPart(PkiHeader header, PkiBody body) + { + this.header = header; + this.body = body; + } + + public virtual PkiHeader Header + { + get { return header; } + } + + public virtual PkiBody Body + { + get { return body; } + } + + /** + *
+		 * ProtectedPart ::= SEQUENCE {
+		 *                    header    PKIHeader,
+		 *                    body      PKIBody
+		 * }
+		 * 
+ * @return a basic ASN.1 object representation. + */ + public override Asn1Object ToAsn1Object() + { + return new DerSequence(header, body); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cmp/RevAnnContent.cs b/bc-sharp-crypto/src/asn1/cmp/RevAnnContent.cs new file mode 100644 index 0000000..d5d4262 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cmp/RevAnnContent.cs @@ -0,0 +1,87 @@ +using System; + +using Org.BouncyCastle.Asn1.Crmf; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cmp +{ + public class RevAnnContent + : Asn1Encodable + { + private readonly PkiStatusEncodable status; + private readonly CertId certId; + private readonly DerGeneralizedTime willBeRevokedAt; + private readonly DerGeneralizedTime badSinceDate; + private readonly X509Extensions crlDetails; + + private RevAnnContent(Asn1Sequence seq) + { + status = PkiStatusEncodable.GetInstance(seq[0]); + certId = CertId.GetInstance(seq[1]); + willBeRevokedAt = DerGeneralizedTime.GetInstance(seq[2]); + badSinceDate = DerGeneralizedTime.GetInstance(seq[3]); + + if (seq.Count > 4) + { + crlDetails = X509Extensions.GetInstance(seq[4]); + } + } + + public static RevAnnContent GetInstance(object obj) + { + if (obj is RevAnnContent) + return (RevAnnContent)obj; + + if (obj is Asn1Sequence) + return new RevAnnContent((Asn1Sequence)obj); + + throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj"); + } + + public virtual PkiStatusEncodable Status + { + get { return status; } + } + + public virtual CertId CertID + { + get { return certId; } + } + + public virtual DerGeneralizedTime WillBeRevokedAt + { + get { return willBeRevokedAt; } + } + + public virtual DerGeneralizedTime BadSinceDate + { + get { return badSinceDate; } + } + + public virtual X509Extensions CrlDetails + { + get { 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 override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(status, certId, willBeRevokedAt, badSinceDate); + v.AddOptional(crlDetails); + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cmp/RevDetails.cs b/bc-sharp-crypto/src/asn1/cmp/RevDetails.cs new file mode 100644 index 0000000..7d2a65a --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cmp/RevDetails.cs @@ -0,0 +1,75 @@ +using System; + +using Org.BouncyCastle.Asn1.Crmf; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cmp +{ + public class RevDetails + : Asn1Encodable + { + private readonly CertTemplate certDetails; + private readonly X509Extensions crlEntryDetails; + + private RevDetails(Asn1Sequence seq) + { + certDetails = CertTemplate.GetInstance(seq[0]); + crlEntryDetails = seq.Count <= 1 + ? null + : X509Extensions.GetInstance(seq[1]); + } + + public static RevDetails GetInstance(object obj) + { + if (obj is RevDetails) + return (RevDetails)obj; + + if (obj is Asn1Sequence) + return new RevDetails((Asn1Sequence)obj); + + throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj"); + } + + public RevDetails(CertTemplate certDetails) + : this(certDetails, null) + { + } + + public RevDetails(CertTemplate certDetails, X509Extensions crlEntryDetails) + { + this.certDetails = certDetails; + this.crlEntryDetails = crlEntryDetails; + } + + public virtual CertTemplate CertDetails + { + get { return certDetails; } + } + + public virtual X509Extensions CrlEntryDetails + { + get { 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 override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(certDetails); + v.AddOptional(crlEntryDetails); + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cmp/RevRepContent.cs b/bc-sharp-crypto/src/asn1/cmp/RevRepContent.cs new file mode 100644 index 0000000..8e382a6 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cmp/RevRepContent.cs @@ -0,0 +1,113 @@ +using System; + +using Org.BouncyCastle.Asn1.Crmf; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cmp +{ + public class RevRepContent + : Asn1Encodable + { + private readonly Asn1Sequence status; + private readonly Asn1Sequence revCerts; + private readonly Asn1Sequence crls; + + private RevRepContent(Asn1Sequence seq) + { + status = Asn1Sequence.GetInstance(seq[0]); + + for (int pos = 1; pos < seq.Count; ++pos) + { + Asn1TaggedObject tObj = Asn1TaggedObject.GetInstance(seq[pos]); + + if (tObj.TagNo == 0) + { + revCerts = Asn1Sequence.GetInstance(tObj, true); + } + else + { + crls = Asn1Sequence.GetInstance(tObj, true); + } + } + } + + public static RevRepContent GetInstance(object obj) + { + if (obj is RevRepContent) + return (RevRepContent)obj; + + if (obj is Asn1Sequence) + return new RevRepContent((Asn1Sequence)obj); + + throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj"); + } + + public virtual PkiStatusInfo[] GetStatus() + { + PkiStatusInfo[] results = new PkiStatusInfo[status.Count]; + for (int i = 0; i != results.Length; ++i) + { + results[i] = PkiStatusInfo.GetInstance(status[i]); + } + return results; + } + + public virtual CertId[] GetRevCerts() + { + if (revCerts == null) + return null; + + CertId[] results = new CertId[revCerts.Count]; + for (int i = 0; i != results.Length; ++i) + { + results[i] = CertId.GetInstance(revCerts[i]); + } + return results; + } + + public virtual CertificateList[] GetCrls() + { + if (crls == null) + return null; + + CertificateList[] results = new CertificateList[crls.Count]; + for (int i = 0; i != results.Length; ++i) + { + results[i] = CertificateList.GetInstance(crls[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 override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(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/bc-sharp-crypto/src/asn1/cmp/RevRepContentBuilder.cs b/bc-sharp-crypto/src/asn1/cmp/RevRepContentBuilder.cs new file mode 100644 index 0000000..cc17d1d --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cmp/RevRepContentBuilder.cs @@ -0,0 +1,55 @@ +using System; + +using Org.BouncyCastle.Asn1.Crmf; +using Org.BouncyCastle.Asn1.X509; + +namespace Org.BouncyCastle.Asn1.Cmp +{ + public class RevRepContentBuilder + { + private readonly Asn1EncodableVector status = new Asn1EncodableVector(); + private readonly Asn1EncodableVector revCerts = new Asn1EncodableVector(); + private readonly Asn1EncodableVector crls = new Asn1EncodableVector(); + + public virtual RevRepContentBuilder Add(PkiStatusInfo status) + { + this.status.Add(status); + return this; + } + + public virtual RevRepContentBuilder Add(PkiStatusInfo status, CertId certId) + { + if (this.status.Count != this.revCerts.Count) + throw new InvalidOperationException("status and revCerts sequence must be in common order"); + + this.status.Add(status); + this.revCerts.Add(certId); + return this; + } + + public virtual RevRepContentBuilder AddCrl(CertificateList crl) + { + this.crls.Add(crl); + return this; + } + + public virtual RevRepContent Build() + { + Asn1EncodableVector v = new Asn1EncodableVector(); + + v.Add(new DerSequence(status)); + + if (revCerts.Count != 0) + { + v.Add(new DerTaggedObject(true, 0, new DerSequence(revCerts))); + } + + if (crls.Count != 0) + { + v.Add(new DerTaggedObject(true, 1, new DerSequence(crls))); + } + + return RevRepContent.GetInstance(new DerSequence(v)); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cmp/RevReqContent.cs b/bc-sharp-crypto/src/asn1/cmp/RevReqContent.cs new file mode 100644 index 0000000..1522d37 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cmp/RevReqContent.cs @@ -0,0 +1,54 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cmp +{ + public class RevReqContent + : Asn1Encodable + { + private readonly Asn1Sequence content; + + private RevReqContent(Asn1Sequence seq) + { + content = seq; + } + + public static RevReqContent GetInstance(object obj) + { + if (obj is RevReqContent) + return (RevReqContent)obj; + + if (obj is Asn1Sequence) + return new RevReqContent((Asn1Sequence)obj); + + throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj"); + } + + public RevReqContent(params RevDetails[] revDetails) + { + this.content = new DerSequence(revDetails); + } + + public virtual RevDetails[] ToRevDetailsArray() + { + RevDetails[] result = new RevDetails[content.Count]; + for (int i = 0; i != result.Length; ++i) + { + result[i] = RevDetails.GetInstance(content[i]); + } + return result; + } + + /** + *
+		 * RevReqContent ::= SEQUENCE OF RevDetails
+		 * 
+ * @return a basic ASN.1 object representation. + */ + public override Asn1Object ToAsn1Object() + { + return content; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cms/Attribute.cs b/bc-sharp-crypto/src/asn1/cms/Attribute.cs new file mode 100644 index 0000000..69ac441 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cms/Attribute.cs @@ -0,0 +1,70 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cms +{ + public class Attribute + : Asn1Encodable + { + private DerObjectIdentifier attrType; + private Asn1Set attrValues; + + /** + * return an Attribute object from the given object. + * + * @param o the object we want converted. + * @exception ArgumentException if the object cannot be converted. + */ + public static Attribute GetInstance( + object obj) + { + if (obj == null || obj is Attribute) + return (Attribute) obj; + + if (obj is Asn1Sequence) + return new Attribute((Asn1Sequence) obj); + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + public Attribute( + Asn1Sequence seq) + { + attrType = (DerObjectIdentifier)seq[0]; + attrValues = (Asn1Set)seq[1]; + } + + public Attribute( + DerObjectIdentifier attrType, + Asn1Set attrValues) + { + this.attrType = attrType; + this.attrValues = attrValues; + } + + public DerObjectIdentifier AttrType + { + get { return attrType; } + } + + public Asn1Set AttrValues + { + get { return attrValues; } + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *
+        * Attribute ::= SEQUENCE {
+        *     attrType OBJECT IDENTIFIER,
+        *     attrValues SET OF AttributeValue
+        * }
+        * 
+ */ + public override Asn1Object ToAsn1Object() + { + return new DerSequence(attrType, attrValues); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cms/AttributeTable.cs b/bc-sharp-crypto/src/asn1/cms/AttributeTable.cs new file mode 100644 index 0000000..8d357f1 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cms/AttributeTable.cs @@ -0,0 +1,231 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cms +{ + public class AttributeTable + { + private readonly IDictionary attributes; + +#if !(SILVERLIGHT || PORTABLE) + [Obsolete] + public AttributeTable( + Hashtable attrs) + { + this.attributes = Platform.CreateHashtable(attrs); + } +#endif + + public AttributeTable( + IDictionary attrs) + { + this.attributes = Platform.CreateHashtable(attrs); + } + + public AttributeTable( + Asn1EncodableVector v) + { + this.attributes = Platform.CreateHashtable(v.Count); + + foreach (Asn1Encodable o in v) + { + Attribute a = Attribute.GetInstance(o); + + AddAttribute(a); + } + } + + public AttributeTable( + Asn1Set s) + { + this.attributes = Platform.CreateHashtable(s.Count); + + for (int i = 0; i != s.Count; i++) + { + Attribute a = Attribute.GetInstance(s[i]); + + AddAttribute(a); + } + } + + public AttributeTable( + Attributes attrs) + : this(Asn1Set.GetInstance(attrs.ToAsn1Object())) + { + } + + private void AddAttribute( + Attribute a) + { + DerObjectIdentifier oid = a.AttrType; + object obj = attributes[oid]; + + if (obj == null) + { + attributes[oid] = a; + } + else + { + IList v; + + if (obj is Attribute) + { + v = Platform.CreateArrayList(); + + v.Add(obj); + v.Add(a); + } + else + { + v = (IList) obj; + + v.Add(a); + } + + attributes[oid] = v; + } + } + + /// Return the first attribute matching the given OBJECT IDENTIFIER + public Attribute this[DerObjectIdentifier oid] + { + get + { + object obj = attributes[oid]; + + if (obj is IList) + { + return (Attribute)((IList)obj)[0]; + } + + return (Attribute) obj; + } + } + + [Obsolete("Use 'object[oid]' syntax instead")] + public Attribute Get( + DerObjectIdentifier oid) + { + return this[oid]; + } + + /** + * 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 obj = attributes[oid]; + + if (obj is IList) + { + foreach (Attribute a in (IList)obj) + { + v.Add(a); + } + } + else if (obj != null) + { + v.Add((Attribute) obj); + } + + return v; + } + + public int Count + { + get + { + int total = 0; + + foreach (object o in attributes.Values) + { + if (o is IList) + { + total += ((IList)o).Count; + } + else + { + ++total; + } + } + + return total; + } + } + + public IDictionary ToDictionary() + { + return Platform.CreateHashtable(attributes); + } + +#if !(SILVERLIGHT || PORTABLE) + [Obsolete("Use 'ToDictionary' instead")] + public Hashtable ToHashtable() + { + return new Hashtable(attributes); + } +#endif + + public Asn1EncodableVector ToAsn1EncodableVector() + { + Asn1EncodableVector v = new Asn1EncodableVector(); + + foreach (object obj in attributes.Values) + { + if (obj is IList) + { + foreach (object el in (IList)obj) + { + v.Add(Attribute.GetInstance(el)); + } + } + else + { + v.Add(Attribute.GetInstance(obj)); + } + } + + return v; + } + + public Attributes ToAttributes() + { + return new Attributes(this.ToAsn1EncodableVector()); + } + + /** + * Return a new table with the passed in attribute added. + * + * @param attrType + * @param attrValue + * @return + */ + public AttributeTable Add(DerObjectIdentifier attrType, Asn1Encodable attrValue) + { + AttributeTable newTable = new AttributeTable(attributes); + + newTable.AddAttribute(new Attribute(attrType, new DerSet(attrValue))); + + return newTable; + } + + public AttributeTable Remove(DerObjectIdentifier attrType) + { + AttributeTable newTable = new AttributeTable(attributes); + + newTable.attributes.Remove(attrType); + + return newTable; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cms/Attributes.cs b/bc-sharp-crypto/src/asn1/cms/Attributes.cs new file mode 100644 index 0000000..5b6b130 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cms/Attributes.cs @@ -0,0 +1,55 @@ +using System; + +namespace Org.BouncyCastle.Asn1.Cms +{ + public class Attributes + : Asn1Encodable + { + private readonly Asn1Set attributes; + + private Attributes(Asn1Set attributes) + { + this.attributes = attributes; + } + + public Attributes(Asn1EncodableVector v) + { + attributes = new BerSet(v); + } + + public static Attributes GetInstance(object obj) + { + if (obj is Attributes) + return (Attributes)obj; + + if (obj != null) + return new Attributes(Asn1Set.GetInstance(obj)); + + return null; + } + + public virtual Attribute[] GetAttributes() + { + Attribute[] rv = new Attribute[attributes.Count]; + + for (int i = 0; i != rv.Length; i++) + { + rv[i] = Attribute.GetInstance(attributes[i]); + } + + return rv; + } + + /** + *
+         * Attributes ::=
+         *   SET SIZE(1..MAX) OF Attribute -- according to RFC 5652
+         * 
+ * @return + */ + public override Asn1Object ToAsn1Object() + { + return attributes; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cms/AuthEnvelopedData.cs b/bc-sharp-crypto/src/asn1/cms/AuthEnvelopedData.cs new file mode 100644 index 0000000..c30ec6b --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cms/AuthEnvelopedData.cs @@ -0,0 +1,205 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cms +{ + public class AuthEnvelopedData + : 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; + } + + private AuthEnvelopedData( + Asn1Sequence seq) + { + int index = 0; + + // TODO + // "It MUST be set to 0." + Asn1Object tmp = seq[index++].ToAsn1Object(); + version = (DerInteger)tmp; + + tmp = seq[index++].ToAsn1Object(); + if (tmp is Asn1TaggedObject) + { + originatorInfo = OriginatorInfo.GetInstance((Asn1TaggedObject)tmp, false); + tmp = seq[index++].ToAsn1Object(); + } + + // TODO + // "There MUST be at least one element in the collection." + recipientInfos = Asn1Set.GetInstance(tmp); + + tmp = seq[index++].ToAsn1Object(); + authEncryptedContentInfo = EncryptedContentInfo.GetInstance(tmp); + + tmp = seq[index++].ToAsn1Object(); + if (tmp is Asn1TaggedObject) + { + authAttrs = Asn1Set.GetInstance((Asn1TaggedObject)tmp, false); + tmp = seq[index++].ToAsn1Object(); + } + else + { + // TODO + // "The authAttrs MUST be present if the content type carried in + // EncryptedContentInfo is not id-data." + } + + mac = Asn1OctetString.GetInstance(tmp); + + if (seq.Count > index) + { + tmp = seq[index++].ToAsn1Object(); + 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 isExplicit true if the object is meant to be explicitly + * tagged false otherwise. + * @throws ArgumentException if the object held by the + * tagged object cannot be converted. + */ + public static AuthEnvelopedData GetInstance( + Asn1TaggedObject obj, + bool isExplicit) + { + return GetInstance(Asn1Sequence.GetInstance(obj, isExplicit)); + } + + /** + * return an AuthEnvelopedData object from the given object. + * + * @param obj the object we want converted. + * @throws ArgumentException if the object cannot be converted. + */ + public static AuthEnvelopedData GetInstance( + object obj) + { + if (obj == null || obj is AuthEnvelopedData) + return (AuthEnvelopedData)obj; + + if (obj is Asn1Sequence) + return new AuthEnvelopedData((Asn1Sequence)obj); + + throw new ArgumentException("Invalid AuthEnvelopedData: " + Platform.GetTypeName(obj)); + } + + public DerInteger Version + { + get { return version; } + } + + public OriginatorInfo OriginatorInfo + { + get { return originatorInfo; } + } + + public Asn1Set RecipientInfos + { + get { return recipientInfos; } + } + + public EncryptedContentInfo AuthEncryptedContentInfo + { + get { return authEncryptedContentInfo; } + } + + public Asn1Set AuthAttrs + { + get { return authAttrs; } + } + + public Asn1OctetString Mac + { + get { return mac; } + } + + public Asn1Set UnauthAttrs + { + get { 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 override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(version); + + if (originatorInfo != null) + { + v.Add(new DerTaggedObject(false, 0, originatorInfo)); + } + + v.Add(recipientInfos, 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/bc-sharp-crypto/src/asn1/cms/AuthEnvelopedDataParser.cs b/bc-sharp-crypto/src/asn1/cms/AuthEnvelopedDataParser.cs new file mode 100644 index 0000000..35cb3bf --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cms/AuthEnvelopedDataParser.cs @@ -0,0 +1,145 @@ +using System; + +namespace Org.BouncyCastle.Asn1.Cms +{ + /** + * 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 IAsn1Convertible nextObject; + private bool originatorInfoCalled; + + public AuthEnvelopedDataParser( + Asn1SequenceParser seq) + { + this.seq = seq; + + // TODO + // "It MUST be set to 0." + this.version = (DerInteger)seq.ReadObject(); + } + + public DerInteger Version + { + get { return version; } + } + + public OriginatorInfo GetOriginatorInfo() + { + originatorInfoCalled = true; + + if (nextObject == null) + { + nextObject = seq.ReadObject(); + } + + if (nextObject is Asn1TaggedObjectParser && ((Asn1TaggedObjectParser)nextObject).TagNo == 0) + { + Asn1SequenceParser originatorInfo = (Asn1SequenceParser) ((Asn1TaggedObjectParser)nextObject).GetObjectParser(Asn1Tags.Sequence, false); + nextObject = null; + return OriginatorInfo.GetInstance(originatorInfo.ToAsn1Object()); + } + + return null; + } + + public Asn1SetParser GetRecipientInfos() + { + if (!originatorInfoCalled) + { + GetOriginatorInfo(); + } + + if (nextObject == null) + { + nextObject = seq.ReadObject(); + } + + Asn1SetParser recipientInfos = (Asn1SetParser)nextObject; + nextObject = null; + return recipientInfos; + } + + public EncryptedContentInfoParser GetAuthEncryptedContentInfo() + { + if (nextObject == null) + { + nextObject = seq.ReadObject(); + } + + if (nextObject != null) + { + Asn1SequenceParser o = (Asn1SequenceParser) nextObject; + nextObject = null; + return new EncryptedContentInfoParser(o); + } + + return null; + } + + public Asn1SetParser GetAuthAttrs() + { + if (nextObject == null) + { + nextObject = seq.ReadObject(); + } + + if (nextObject is Asn1TaggedObjectParser) + { + IAsn1Convertible o = nextObject; + nextObject = null; + return (Asn1SetParser)((Asn1TaggedObjectParser)o).GetObjectParser(Asn1Tags.Set, false); + } + + // TODO + // "The authAttrs MUST be present if the content type carried in + // EncryptedContentInfo is not id-data." + + return null; + } + + public Asn1OctetString GetMac() + { + if (nextObject == null) + { + nextObject = seq.ReadObject(); + } + + IAsn1Convertible o = nextObject; + nextObject = null; + + return Asn1OctetString.GetInstance(o.ToAsn1Object()); + } + + public Asn1SetParser GetUnauthAttrs() + { + if (nextObject == null) + { + nextObject = seq.ReadObject(); + } + + if (nextObject != null) + { + IAsn1Convertible o = nextObject; + nextObject = null; + return (Asn1SetParser)((Asn1TaggedObjectParser)o).GetObjectParser(Asn1Tags.Set, false); + } + + return null; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cms/AuthenticatedData.cs b/bc-sharp-crypto/src/asn1/cms/AuthenticatedData.cs new file mode 100644 index 0000000..6f13a6f --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cms/AuthenticatedData.cs @@ -0,0 +1,271 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cms +{ + public class AuthenticatedData + : 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 ArgumentException("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; + } + + private AuthenticatedData( + Asn1Sequence seq) + { + int index = 0; + + version = (DerInteger)seq[index++]; + + Asn1Encodable tmp = seq[index++]; + if (tmp is Asn1TaggedObject) + { + originatorInfo = OriginatorInfo.GetInstance((Asn1TaggedObject)tmp, false); + tmp = seq[index++]; + } + + recipientInfos = Asn1Set.GetInstance(tmp); + macAlgorithm = AlgorithmIdentifier.GetInstance(seq[index++]); + + tmp = seq[index++]; + if (tmp is Asn1TaggedObject) + { + digestAlgorithm = AlgorithmIdentifier.GetInstance((Asn1TaggedObject)tmp, false); + tmp = seq[index++]; + } + + encapsulatedContentInfo = ContentInfo.GetInstance(tmp); + + tmp = seq[index++]; + if (tmp is Asn1TaggedObject) + { + authAttrs = Asn1Set.GetInstance((Asn1TaggedObject)tmp, false); + tmp = seq[index++]; + } + + mac = Asn1OctetString.GetInstance(tmp); + + if (seq.Count > index) + { + unauthAttrs = Asn1Set.GetInstance((Asn1TaggedObject)seq[index], false); + } + } + + /** + * return an AuthenticatedData object from a tagged object. + * + * @param obj the tagged object holding the object we want. + * @param isExplicit true if the object is meant to be explicitly + * tagged false otherwise. + * @throws ArgumentException if the object held by the + * tagged object cannot be converted. + */ + public static AuthenticatedData GetInstance( + Asn1TaggedObject obj, + bool isExplicit) + { + return GetInstance(Asn1Sequence.GetInstance(obj, isExplicit)); + } + + /** + * return an AuthenticatedData object from the given object. + * + * @param obj the object we want converted. + * @throws ArgumentException if the object cannot be converted. + */ + public static AuthenticatedData GetInstance( + object obj) + { + if (obj == null || obj is AuthenticatedData) + { + return (AuthenticatedData)obj; + } + + if (obj is Asn1Sequence) + { + return new AuthenticatedData((Asn1Sequence)obj); + } + + throw new ArgumentException("Invalid AuthenticatedData: " + Platform.GetTypeName(obj)); + } + + public DerInteger Version + { + get { return version; } + } + + public OriginatorInfo OriginatorInfo + { + get { return originatorInfo; } + } + + public Asn1Set RecipientInfos + { + get { return recipientInfos; } + } + + public AlgorithmIdentifier MacAlgorithm + { + get { return macAlgorithm; } + } + + public AlgorithmIdentifier DigestAlgorithm + { + get { return digestAlgorithm; } + } + + public ContentInfo EncapsulatedContentInfo + { + get { return encapsulatedContentInfo; } + } + + public Asn1Set AuthAttrs + { + get { return authAttrs; } + } + + public Asn1OctetString Mac + { + get { return mac; } + } + + public Asn1Set UnauthAttrs + { + get { 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 override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(version); + + if (originatorInfo != null) + { + v.Add(new DerTaggedObject(false, 0, originatorInfo)); + } + + v.Add(recipientInfos, 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; + + int ver = 0; + + foreach (object obj in origInfo.Certificates) + { + if (obj is Asn1TaggedObject) + { + Asn1TaggedObject tag = (Asn1TaggedObject)obj; + + if (tag.TagNo == 2) + { + ver = 1; + } + else if (tag.TagNo == 3) + { + ver = 3; + break; + } + } + } + + foreach (object obj in origInfo.Crls) + { + if (obj is Asn1TaggedObject) + { + Asn1TaggedObject tag = (Asn1TaggedObject)obj; + + if (tag.TagNo == 1) + { + ver = 3; + break; + } + } + } + + return ver; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cms/AuthenticatedDataParser.cs b/bc-sharp-crypto/src/asn1/cms/AuthenticatedDataParser.cs new file mode 100644 index 0000000..4b80d1b --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cms/AuthenticatedDataParser.cs @@ -0,0 +1,182 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; + +namespace Org.BouncyCastle.Asn1.Cms +{ + /** + * 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 IAsn1Convertible nextObject; + private bool originatorInfoCalled; + + public AuthenticatedDataParser( + Asn1SequenceParser seq) + { + this.seq = seq; + this.version = (DerInteger)seq.ReadObject(); + } + + public DerInteger Version + { + get { return version; } + } + + public OriginatorInfo GetOriginatorInfo() + { + originatorInfoCalled = true; + + if (nextObject == null) + { + nextObject = seq.ReadObject(); + } + + if (nextObject is Asn1TaggedObjectParser && ((Asn1TaggedObjectParser)nextObject).TagNo == 0) + { + Asn1SequenceParser originatorInfo = (Asn1SequenceParser) ((Asn1TaggedObjectParser)nextObject).GetObjectParser(Asn1Tags.Sequence, false); + nextObject = null; + return OriginatorInfo.GetInstance(originatorInfo.ToAsn1Object()); + } + + return null; + } + + public Asn1SetParser GetRecipientInfos() + { + if (!originatorInfoCalled) + { + GetOriginatorInfo(); + } + + if (nextObject == null) + { + nextObject = seq.ReadObject(); + } + + Asn1SetParser recipientInfos = (Asn1SetParser)nextObject; + nextObject = null; + return recipientInfos; + } + + public AlgorithmIdentifier GetMacAlgorithm() + { + if (nextObject == null) + { + nextObject = seq.ReadObject(); + } + + if (nextObject != null) + { + Asn1SequenceParser o = (Asn1SequenceParser)nextObject; + nextObject = null; + return AlgorithmIdentifier.GetInstance(o.ToAsn1Object()); + } + + return null; + } + + public AlgorithmIdentifier GetDigestAlgorithm() + { + if (nextObject == null) + { + nextObject = seq.ReadObject(); + } + + if (nextObject is Asn1TaggedObjectParser) + { + AlgorithmIdentifier obj = AlgorithmIdentifier.GetInstance( + (Asn1TaggedObject)nextObject.ToAsn1Object(), false); + nextObject = null; + return obj; + } + + return null; + } + + public ContentInfoParser GetEnapsulatedContentInfo() + { + if (nextObject == null) + { + nextObject = seq.ReadObject(); + } + + if (nextObject != null) + { + Asn1SequenceParser o = (Asn1SequenceParser)nextObject; + nextObject = null; + return new ContentInfoParser(o); + } + + return null; + } + + public Asn1SetParser GetAuthAttrs() + { + if (nextObject == null) + { + nextObject = seq.ReadObject(); + } + + if (nextObject is Asn1TaggedObjectParser) + { + IAsn1Convertible o = nextObject; + nextObject = null; + return (Asn1SetParser)((Asn1TaggedObjectParser)o).GetObjectParser(Asn1Tags.Set, false); + } + + return null; + } + + public Asn1OctetString GetMac() + { + if (nextObject == null) + { + nextObject = seq.ReadObject(); + } + + IAsn1Convertible o = nextObject; + nextObject = null; + + return Asn1OctetString.GetInstance(o.ToAsn1Object()); + } + + public Asn1SetParser GetUnauthAttrs() + { + if (nextObject == null) + { + nextObject = seq.ReadObject(); + } + + if (nextObject != null) + { + IAsn1Convertible o = nextObject; + nextObject = null; + return (Asn1SetParser)((Asn1TaggedObjectParser)o).GetObjectParser(Asn1Tags.Set, false); + } + + return null; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cms/CMSAttributes.cs b/bc-sharp-crypto/src/asn1/cms/CMSAttributes.cs new file mode 100644 index 0000000..fca2b67 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cms/CMSAttributes.cs @@ -0,0 +1,14 @@ +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Pkcs; + +namespace Org.BouncyCastle.Asn1.Cms +{ + public abstract class CmsAttributes + { + public static readonly DerObjectIdentifier ContentType = PkcsObjectIdentifiers.Pkcs9AtContentType; + public static readonly DerObjectIdentifier MessageDigest = PkcsObjectIdentifiers.Pkcs9AtMessageDigest; + public static readonly DerObjectIdentifier SigningTime = PkcsObjectIdentifiers.Pkcs9AtSigningTime; + public static readonly DerObjectIdentifier CounterSignature = PkcsObjectIdentifiers.Pkcs9AtCounterSignature; + public static readonly DerObjectIdentifier ContentHint = PkcsObjectIdentifiers.IdAAContentHint; + } +} diff --git a/bc-sharp-crypto/src/asn1/cms/CMSObjectIdentifiers.cs b/bc-sharp-crypto/src/asn1/cms/CMSObjectIdentifiers.cs new file mode 100644 index 0000000..2ad0a3c --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cms/CMSObjectIdentifiers.cs @@ -0,0 +1,28 @@ +using Org.BouncyCastle.Asn1.Pkcs; + +namespace Org.BouncyCastle.Asn1.Cms +{ + public abstract class CmsObjectIdentifiers + { + public static readonly DerObjectIdentifier Data = PkcsObjectIdentifiers.Data; + public static readonly DerObjectIdentifier SignedData = PkcsObjectIdentifiers.SignedData; + public static readonly DerObjectIdentifier EnvelopedData = PkcsObjectIdentifiers.EnvelopedData; + public static readonly DerObjectIdentifier SignedAndEnvelopedData = PkcsObjectIdentifiers.SignedAndEnvelopedData; + public static readonly DerObjectIdentifier DigestedData = PkcsObjectIdentifiers.DigestedData; + public static readonly DerObjectIdentifier EncryptedData = PkcsObjectIdentifiers.EncryptedData; + public static readonly DerObjectIdentifier AuthenticatedData = PkcsObjectIdentifiers.IdCTAuthData; + public static readonly DerObjectIdentifier CompressedData = PkcsObjectIdentifiers.IdCTCompressedData; + public static readonly DerObjectIdentifier AuthEnvelopedData = PkcsObjectIdentifiers.IdCTAuthEnvelopedData; + public static readonly DerObjectIdentifier timestampedData = PkcsObjectIdentifiers.IdCTTimestampedData; + + /** + * The other Revocation Info arc + * id-ri OBJECT IDENTIFIER ::= { iso(1) identified-organization(3) + * dod(6) internet(1) security(5) mechanisms(5) pkix(7) ri(16) } + */ + public static readonly DerObjectIdentifier id_ri = new DerObjectIdentifier("1.3.6.1.5.5.7.16"); + + public static readonly DerObjectIdentifier id_ri_ocsp_response = id_ri.Branch("2"); + public static readonly DerObjectIdentifier id_ri_scvp = id_ri.Branch("4"); + } +} diff --git a/bc-sharp-crypto/src/asn1/cms/CompressedData.cs b/bc-sharp-crypto/src/asn1/cms/CompressedData.cs new file mode 100644 index 0000000..154ed35 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cms/CompressedData.cs @@ -0,0 +1,96 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cms +{ + /** + * RFC 3274 - CMS Compressed Data. + *
+     * CompressedData ::= Sequence {
+     *  version CMSVersion,
+     *  compressionAlgorithm CompressionAlgorithmIdentifier,
+     *  encapContentInfo EncapsulatedContentInfo
+     * }
+     * 
+ */ + public class CompressedData + : 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[0]; + this.compressionAlgorithm = AlgorithmIdentifier.GetInstance(seq[1]); + this.encapContentInfo = ContentInfo.GetInstance(seq[2]); + } + + /** + * return a CompressedData object from a tagged object. + * + * @param ato the tagged object holding the object we want. + * @param explicitly true if the object is meant to be explicitly + * tagged false otherwise. + * @exception ArgumentException if the object held by the + * tagged object cannot be converted. + */ + public static CompressedData GetInstance( + Asn1TaggedObject ato, + bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(ato, explicitly)); + } + + /** + * return a CompressedData object from the given object. + * + * @param _obj the object we want converted. + * @exception ArgumentException if the object cannot be converted. + */ + public static CompressedData GetInstance( + object obj) + { + if (obj == null || obj is CompressedData) + return (CompressedData)obj; + + if (obj is Asn1Sequence) + return new CompressedData((Asn1Sequence) obj); + + throw new ArgumentException("Invalid CompressedData: " + Platform.GetTypeName(obj)); + } + + public DerInteger Version + { + get { return version; } + } + + public AlgorithmIdentifier CompressionAlgorithmIdentifier + { + get { return compressionAlgorithm; } + } + + public ContentInfo EncapContentInfo + { + get { return encapContentInfo; } + } + + public override Asn1Object ToAsn1Object() + { + return new BerSequence(version, compressionAlgorithm, encapContentInfo); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cms/CompressedDataParser.cs b/bc-sharp-crypto/src/asn1/cms/CompressedDataParser.cs new file mode 100644 index 0000000..7c53453 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cms/CompressedDataParser.cs @@ -0,0 +1,47 @@ +using System; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; + +namespace Org.BouncyCastle.Asn1.Cms +{ + /** + * 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) + { + this._version = (DerInteger)seq.ReadObject(); + this._compressionAlgorithm = AlgorithmIdentifier.GetInstance(seq.ReadObject().ToAsn1Object()); + this._encapContentInfo = new ContentInfoParser((Asn1SequenceParser)seq.ReadObject()); + } + + public DerInteger Version + { + get { return _version; } + } + + public AlgorithmIdentifier CompressionAlgorithmIdentifier + { + get { return _compressionAlgorithm; } + } + + public ContentInfoParser GetEncapContentInfo() + { + return _encapContentInfo; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cms/ContentInfo.cs b/bc-sharp-crypto/src/asn1/cms/ContentInfo.cs new file mode 100644 index 0000000..f130a4b --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cms/ContentInfo.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cms +{ + public class ContentInfo + : Asn1Encodable + { + private readonly DerObjectIdentifier contentType; + private readonly Asn1Encodable content; + + public static ContentInfo GetInstance( + object obj) + { + if (obj == null || obj is ContentInfo) + return (ContentInfo) obj; + + if (obj is Asn1Sequence) + return new ContentInfo((Asn1Sequence) obj); + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj)); + } + + public static ContentInfo GetInstance(Asn1TaggedObject obj, bool isExplicit) + { + return GetInstance(Asn1Sequence.GetInstance(obj, isExplicit)); + } + + private ContentInfo( + Asn1Sequence seq) + { + if (seq.Count < 1 || seq.Count > 2) + throw new ArgumentException("Bad sequence size: " + seq.Count, "seq"); + + contentType = (DerObjectIdentifier) seq[0]; + + if (seq.Count > 1) + { + Asn1TaggedObject tagged = (Asn1TaggedObject) seq[1]; + if (!tagged.IsExplicit() || tagged.TagNo != 0) + throw new ArgumentException("Bad tag for 'content'", "seq"); + + content = tagged.GetObject(); + } + } + + public ContentInfo( + DerObjectIdentifier contentType, + Asn1Encodable content) + { + this.contentType = contentType; + this.content = content; + } + + public DerObjectIdentifier ContentType + { + get { return contentType; } + } + + public Asn1Encodable Content + { + get { return content; } + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *
+         * ContentInfo ::= Sequence {
+         *          contentType ContentType,
+         *          content
+         *          [0] EXPLICIT ANY DEFINED BY contentType OPTIONAL }
+         * 
+ */ + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(contentType); + + if (content != null) + { + v.Add(new BerTaggedObject(0, content)); + } + + return new BerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cms/ContentInfoParser.cs b/bc-sharp-crypto/src/asn1/cms/ContentInfoParser.cs new file mode 100644 index 0000000..541cc0f --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cms/ContentInfoParser.cs @@ -0,0 +1,40 @@ +using System; + +namespace Org.BouncyCastle.Asn1.Cms +{ + /** + * 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) + { + contentType = (DerObjectIdentifier)seq.ReadObject(); + content = (Asn1TaggedObjectParser)seq.ReadObject(); + } + + public DerObjectIdentifier ContentType + { + get { return contentType; } + } + + public IAsn1Convertible GetContent( + int tag) + { + if (content == null) + return null; + + return content.GetObjectParser(tag, true); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cms/EncryptedContentInfo.cs b/bc-sharp-crypto/src/asn1/cms/EncryptedContentInfo.cs new file mode 100644 index 0000000..999f2a0 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cms/EncryptedContentInfo.cs @@ -0,0 +1,94 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cms +{ + public class EncryptedContentInfo + : 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[0]; + contentEncryptionAlgorithm = AlgorithmIdentifier.GetInstance(seq[1]); + + if (seq.Count > 2) + { + encryptedContent = Asn1OctetString.GetInstance( + (Asn1TaggedObject) seq[2], false); + } + } + + /** + * return an EncryptedContentInfo object from the given object. + * + * @param obj the object we want converted. + * @exception ArgumentException if the object cannot be converted. + */ + public static EncryptedContentInfo GetInstance( + object obj) + { + if (obj == null || obj is EncryptedContentInfo) + return (EncryptedContentInfo)obj; + + if (obj is Asn1Sequence) + return new EncryptedContentInfo((Asn1Sequence)obj); + + throw new ArgumentException("Invalid EncryptedContentInfo: " + Platform.GetTypeName(obj)); + } + + public DerObjectIdentifier ContentType + { + get { return contentType; } + } + + public AlgorithmIdentifier ContentEncryptionAlgorithm + { + get { return contentEncryptionAlgorithm; } + } + + public Asn1OctetString EncryptedContent + { + get { return encryptedContent; } + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *
+         * EncryptedContentInfo ::= Sequence {
+         *     contentType ContentType,
+         *     contentEncryptionAlgorithm ContentEncryptionAlgorithmIdentifier,
+         *     encryptedContent [0] IMPLICIT EncryptedContent OPTIONAL
+         * }
+         * 
+ */ + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector( + contentType, contentEncryptionAlgorithm); + + if (encryptedContent != null) + { + v.Add(new BerTaggedObject(false, 0, encryptedContent)); + } + + return new BerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cms/EncryptedContentInfoParser.cs b/bc-sharp-crypto/src/asn1/cms/EncryptedContentInfoParser.cs new file mode 100644 index 0000000..af748b1 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cms/EncryptedContentInfoParser.cs @@ -0,0 +1,46 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; + +namespace Org.BouncyCastle.Asn1.Cms +{ + /** + *
+	* 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) + { + _contentType = (DerObjectIdentifier)seq.ReadObject(); + _contentEncryptionAlgorithm = AlgorithmIdentifier.GetInstance(seq.ReadObject().ToAsn1Object()); + _encryptedContent = (Asn1TaggedObjectParser)seq.ReadObject(); + } + + public DerObjectIdentifier ContentType + { + get { return _contentType; } + } + + public AlgorithmIdentifier ContentEncryptionAlgorithm + { + get { return _contentEncryptionAlgorithm; } + } + + public IAsn1Convertible GetEncryptedContent( + int tag) + { + return _encryptedContent.GetObjectParser(tag, false); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cms/EncryptedData.cs b/bc-sharp-crypto/src/asn1/cms/EncryptedData.cs new file mode 100644 index 0000000..b8492d1 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cms/EncryptedData.cs @@ -0,0 +1,97 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cms +{ + public class EncryptedData + : Asn1Encodable + { + private readonly DerInteger version; + private readonly EncryptedContentInfo encryptedContentInfo; + private readonly Asn1Set unprotectedAttrs; + + public static EncryptedData GetInstance( + object obj) + { + if (obj is EncryptedData) + return (EncryptedData) obj; + + if (obj is Asn1Sequence) + return new EncryptedData((Asn1Sequence) obj); + + throw new ArgumentException("Invalid EncryptedData: " + Platform.GetTypeName(obj)); + } + + public EncryptedData( + EncryptedContentInfo encInfo) + : this(encInfo, null) + { + } + + public EncryptedData( + EncryptedContentInfo encInfo, + Asn1Set unprotectedAttrs) + { + if (encInfo == null) + throw new ArgumentNullException("encInfo"); + + this.version = new DerInteger((unprotectedAttrs == null) ? 0 : 2); + this.encryptedContentInfo = encInfo; + this.unprotectedAttrs = unprotectedAttrs; + } + + private EncryptedData( + Asn1Sequence seq) + { + if (seq == null) + throw new ArgumentNullException("seq"); + if (seq.Count < 2 || seq.Count > 3) + throw new ArgumentException("Bad sequence size: " + seq.Count, "seq"); + + this.version = DerInteger.GetInstance(seq[0]); + this.encryptedContentInfo = EncryptedContentInfo.GetInstance(seq[1]); + + if (seq.Count > 2) + { + this.unprotectedAttrs = Asn1Set.GetInstance((Asn1TaggedObject)seq[2], false); + } + } + + public virtual DerInteger Version + { + get { return version; } + } + + public virtual EncryptedContentInfo EncryptedContentInfo + { + get { return encryptedContentInfo; } + } + + public virtual Asn1Set UnprotectedAttrs + { + get { return unprotectedAttrs; } + } + + /** + *
+		*       EncryptedData ::= SEQUENCE {
+		*                     version CMSVersion,
+		*                     encryptedContentInfo EncryptedContentInfo,
+		*                     unprotectedAttrs [1] IMPLICIT UnprotectedAttributes OPTIONAL }
+		* 
+ * @return a basic ASN.1 object representation. + */ + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(version, encryptedContentInfo); + + if (unprotectedAttrs != null) + { + v.Add(new BerTaggedObject(false, 1, unprotectedAttrs)); + } + + return new BerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cms/EnvelopedData.cs b/bc-sharp-crypto/src/asn1/cms/EnvelopedData.cs new file mode 100644 index 0000000..09f291a --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cms/EnvelopedData.cs @@ -0,0 +1,176 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1; + +namespace Org.BouncyCastle.Asn1.Cms +{ + public class EnvelopedData + : 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) + { + this.version = new DerInteger(CalculateVersion(originatorInfo, recipientInfos, unprotectedAttrs)); + this.originatorInfo = originatorInfo; + this.recipientInfos = recipientInfos; + this.encryptedContentInfo = encryptedContentInfo; + this.unprotectedAttrs = unprotectedAttrs; + } + + public EnvelopedData( + OriginatorInfo originatorInfo, + Asn1Set recipientInfos, + EncryptedContentInfo encryptedContentInfo, + Attributes unprotectedAttrs) + { + this.version = new DerInteger(CalculateVersion(originatorInfo, recipientInfos, Asn1Set.GetInstance(unprotectedAttrs))); + this.originatorInfo = originatorInfo; + this.recipientInfos = recipientInfos; + this.encryptedContentInfo = encryptedContentInfo; + this.unprotectedAttrs = Asn1Set.GetInstance(unprotectedAttrs); + } + + [Obsolete("Use 'GetInstance' instead")] + public EnvelopedData( + Asn1Sequence seq) + { + int index = 0; + + version = (DerInteger) seq[index++]; + + object tmp = seq[index++]; + + if (tmp is Asn1TaggedObject) + { + originatorInfo = OriginatorInfo.GetInstance((Asn1TaggedObject) tmp, false); + tmp = seq[index++]; + } + + recipientInfos = Asn1Set.GetInstance(tmp); + encryptedContentInfo = EncryptedContentInfo.GetInstance(seq[index++]); + + if (seq.Count > index) + { + unprotectedAttrs = Asn1Set.GetInstance((Asn1TaggedObject) seq[index], false); + } + } + + /** + * return an EnvelopedData object from a tagged object. + * + * @param obj the tagged object holding the object we want. + * @param explicitly true if the object is meant to be explicitly + * tagged false otherwise. + * @exception ArgumentException if the object held by the + * tagged object cannot be converted. + */ + public static EnvelopedData GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); + } + + /** + * return an EnvelopedData object from the given object. + * + * @param obj the object we want converted. + * @exception ArgumentException if the object cannot be converted. + */ + public static EnvelopedData GetInstance( + object obj) + { + if (obj is EnvelopedData) + return (EnvelopedData)obj; + if (obj == null) + return null; + return new EnvelopedData(Asn1Sequence.GetInstance(obj)); + } + + public DerInteger Version + { + get { return version; } + } + + public OriginatorInfo OriginatorInfo + { + get { return originatorInfo; } + } + + public Asn1Set RecipientInfos + { + get { return recipientInfos; } + } + + public EncryptedContentInfo EncryptedContentInfo + { + get { return encryptedContentInfo; } + } + + public Asn1Set UnprotectedAttrs + { + get { 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 override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(version); + + if (originatorInfo != null) + { + v.Add(new DerTaggedObject(false, 0, originatorInfo)); + } + + v.Add(recipientInfos, encryptedContentInfo); + + if (unprotectedAttrs != null) + { + v.Add(new DerTaggedObject(false, 1, unprotectedAttrs)); + } + + return new BerSequence(v); + } + + public static int CalculateVersion(OriginatorInfo originatorInfo, Asn1Set recipientInfos, Asn1Set unprotectedAttrs) + { + if (originatorInfo != null || unprotectedAttrs != null) + { + return 2; + } + + foreach (object o in recipientInfos) + { + RecipientInfo ri = RecipientInfo.GetInstance(o); + + if (ri.Version.Value.IntValue != 0) + { + return 2; + } + } + + return 0; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cms/EnvelopedDataParser.cs b/bc-sharp-crypto/src/asn1/cms/EnvelopedDataParser.cs new file mode 100644 index 0000000..5993537 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cms/EnvelopedDataParser.cs @@ -0,0 +1,107 @@ +using System; + +namespace Org.BouncyCastle.Asn1.Cms +{ + /** + * 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 class EnvelopedDataParser + { + private Asn1SequenceParser _seq; + private DerInteger _version; + private IAsn1Convertible _nextObject; + private bool _originatorInfoCalled; + + public EnvelopedDataParser( + Asn1SequenceParser seq) + { + this._seq = seq; + this._version = (DerInteger)seq.ReadObject(); + } + + public DerInteger Version + { + get { return _version; } + } + + public OriginatorInfo GetOriginatorInfo() + { + _originatorInfoCalled = true; + + if (_nextObject == null) + { + _nextObject = _seq.ReadObject(); + } + + if (_nextObject is Asn1TaggedObjectParser && ((Asn1TaggedObjectParser)_nextObject).TagNo == 0) + { + Asn1SequenceParser originatorInfo = (Asn1SequenceParser) + ((Asn1TaggedObjectParser)_nextObject).GetObjectParser(Asn1Tags.Sequence, false); + _nextObject = null; + return OriginatorInfo.GetInstance(originatorInfo.ToAsn1Object()); + } + + return null; + } + + public Asn1SetParser GetRecipientInfos() + { + if (!_originatorInfoCalled) + { + GetOriginatorInfo(); + } + + if (_nextObject == null) + { + _nextObject = _seq.ReadObject(); + } + + Asn1SetParser recipientInfos = (Asn1SetParser)_nextObject; + _nextObject = null; + return recipientInfos; + } + + public EncryptedContentInfoParser GetEncryptedContentInfo() + { + if (_nextObject == null) + { + _nextObject = _seq.ReadObject(); + } + + if (_nextObject != null) + { + Asn1SequenceParser o = (Asn1SequenceParser) _nextObject; + _nextObject = null; + return new EncryptedContentInfoParser(o); + } + + return null; + } + + public Asn1SetParser GetUnprotectedAttrs() + { + if (_nextObject == null) + { + _nextObject = _seq.ReadObject(); + } + + if (_nextObject != null) + { + IAsn1Convertible o = _nextObject; + _nextObject = null; + return (Asn1SetParser)((Asn1TaggedObjectParser)o).GetObjectParser(Asn1Tags.Set, false); + } + + return null; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cms/Evidence.cs b/bc-sharp-crypto/src/asn1/cms/Evidence.cs new file mode 100644 index 0000000..8374aed --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cms/Evidence.cs @@ -0,0 +1,49 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cms +{ + public class Evidence + : Asn1Encodable, IAsn1Choice + { + private TimeStampTokenEvidence tstEvidence; + + public Evidence(TimeStampTokenEvidence tstEvidence) + { + this.tstEvidence = tstEvidence; + } + + private Evidence(Asn1TaggedObject tagged) + { + if (tagged.TagNo == 0) + { + this.tstEvidence = TimeStampTokenEvidence.GetInstance(tagged, false); + } + } + + public static Evidence GetInstance(object obj) + { + if (obj is Evidence) + return (Evidence)obj; + + if (obj is Asn1TaggedObject) + return new Evidence(Asn1TaggedObject.GetInstance(obj)); + + throw new ArgumentException("Unknown object in GetInstance: " + Platform.GetTypeName(obj), "obj"); + } + + public virtual TimeStampTokenEvidence TstEvidence + { + get { return tstEvidence; } + } + + public override Asn1Object ToAsn1Object() + { + if (tstEvidence != null) + return new DerTaggedObject(false, 0, tstEvidence); + + return null; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cms/IssuerAndSerialNumber.cs b/bc-sharp-crypto/src/asn1/cms/IssuerAndSerialNumber.cs new file mode 100644 index 0000000..b509e7e --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cms/IssuerAndSerialNumber.cs @@ -0,0 +1,64 @@ +using System; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Asn1.Cms +{ + public class IssuerAndSerialNumber + : Asn1Encodable + { + private X509Name name; + private DerInteger serialNumber; + + public static IssuerAndSerialNumber GetInstance(object obj) + { + if (obj == null) + return null; + IssuerAndSerialNumber existing = obj as IssuerAndSerialNumber; + if (existing != null) + return existing; + return new IssuerAndSerialNumber(Asn1Sequence.GetInstance(obj)); + } + + [Obsolete("Use GetInstance() instead")] + public IssuerAndSerialNumber( + Asn1Sequence seq) + { + this.name = X509Name.GetInstance(seq[0]); + this.serialNumber = (DerInteger) seq[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 Name + { + get { return name; } + } + + public DerInteger SerialNumber + { + get { return serialNumber; } + } + + public override Asn1Object ToAsn1Object() + { + return new DerSequence(name, serialNumber); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cms/KEKIdentifier.cs b/bc-sharp-crypto/src/asn1/cms/KEKIdentifier.cs new file mode 100644 index 0000000..a422174 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cms/KEKIdentifier.cs @@ -0,0 +1,119 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cms +{ + public class KekIdentifier + : 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[0]; + + switch (seq.Count) + { + case 1: + break; + case 2: + if (seq[1] is DerGeneralizedTime) + { + date = (DerGeneralizedTime) seq[1]; + } + else + { + other = OtherKeyAttribute.GetInstance(seq[2]); + } + break; + case 3: + date = (DerGeneralizedTime) seq[1]; + other = OtherKeyAttribute.GetInstance(seq[2]); + break; + default: + throw new ArgumentException("Invalid KekIdentifier"); + } + } + + /** + * return a KekIdentifier object from a tagged object. + * + * @param obj the tagged object holding the object we want. + * @param explicitly true if the object is meant to be explicitly + * tagged false otherwise. + * @exception ArgumentException if the object held by the + * tagged object cannot be converted. + */ + public static KekIdentifier GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); + } + + /** + * return a KekIdentifier object from the given object. + * + * @param obj the object we want converted. + * @exception ArgumentException if the object cannot be converted. + */ + public static KekIdentifier GetInstance( + object obj) + { + if (obj == null || obj is KekIdentifier) + return (KekIdentifier)obj; + + if (obj is Asn1Sequence) + return new KekIdentifier((Asn1Sequence)obj); + + throw new ArgumentException("Invalid KekIdentifier: " + Platform.GetTypeName(obj)); + } + + public Asn1OctetString KeyIdentifier + { + get { return keyIdentifier; } + } + + public DerGeneralizedTime Date + { + get { return date; } + } + + public OtherKeyAttribute Other + { + get { return other; } + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *
+         * KekIdentifier ::= Sequence {
+         *     keyIdentifier OCTET STRING,
+         *     date GeneralizedTime OPTIONAL,
+         *     other OtherKeyAttribute OPTIONAL
+         * }
+         * 
+ */ + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(keyIdentifier); + v.AddOptional(date, other); + return new DerSequence(v); + } + } +} + diff --git a/bc-sharp-crypto/src/asn1/cms/KEKRecipientInfo.cs b/bc-sharp-crypto/src/asn1/cms/KEKRecipientInfo.cs new file mode 100644 index 0000000..810e7fc --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cms/KEKRecipientInfo.cs @@ -0,0 +1,106 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cms +{ + public class KekRecipientInfo + : 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[0]; + kekID = KekIdentifier.GetInstance(seq[1]); + keyEncryptionAlgorithm = AlgorithmIdentifier.GetInstance(seq[2]); + encryptedKey = (Asn1OctetString) seq[3]; + } + + /** + * return a KekRecipientInfo object from a tagged object. + * + * @param obj the tagged object holding the object we want. + * @param explicitly true if the object is meant to be explicitly + * tagged false otherwise. + * @exception ArgumentException if the object held by the + * tagged object cannot be converted. + */ + public static KekRecipientInfo GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); + } + + /** + * return a KekRecipientInfo object from the given object. + * + * @param obj the object we want converted. + * @exception ArgumentException if the object cannot be converted. + */ + public static KekRecipientInfo GetInstance( + object obj) + { + if (obj == null || obj is KekRecipientInfo) + return (KekRecipientInfo)obj; + + if(obj is Asn1Sequence) + return new KekRecipientInfo((Asn1Sequence)obj); + + throw new ArgumentException("Invalid KekRecipientInfo: " + Platform.GetTypeName(obj)); + } + + public DerInteger Version + { + get { return version; } + } + + public KekIdentifier KekID + { + get { return kekID; } + } + + public AlgorithmIdentifier KeyEncryptionAlgorithm + { + get { return keyEncryptionAlgorithm; } + } + + public Asn1OctetString EncryptedKey + { + get { return encryptedKey; } + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *
+         * KekRecipientInfo ::= Sequence {
+         *     version CMSVersion,  -- always set to 4
+         *     kekID KekIdentifier,
+         *     keyEncryptionAlgorithm KeyEncryptionAlgorithmIdentifier,
+         *     encryptedKey EncryptedKey
+         * }
+         * 
+ */ + public override Asn1Object ToAsn1Object() + { + return new DerSequence(version, kekID, keyEncryptionAlgorithm, encryptedKey); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cms/KeyAgreeRecipientIdentifier.cs b/bc-sharp-crypto/src/asn1/cms/KeyAgreeRecipientIdentifier.cs new file mode 100644 index 0000000..0256c2d --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cms/KeyAgreeRecipientIdentifier.cs @@ -0,0 +1,94 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cms +{ + public class KeyAgreeRecipientIdentifier + : Asn1Encodable, IAsn1Choice + { + /** + * return an KeyAgreeRecipientIdentifier object from a tagged object. + * + * @param obj the tagged object holding the object we want. + * @param isExplicit true if the object is meant to be explicitly + * tagged false otherwise. + * @exception ArgumentException if the object held by the + * tagged object cannot be converted. + */ + public static KeyAgreeRecipientIdentifier GetInstance( + Asn1TaggedObject obj, + bool isExplicit) + { + return GetInstance(Asn1Sequence.GetInstance(obj, isExplicit)); + } + + /** + * return an KeyAgreeRecipientIdentifier object from the given object. + * + * @param obj the object we want converted. + * @exception ArgumentException if the object cannot be converted. + */ + public static KeyAgreeRecipientIdentifier GetInstance( + object obj) + { + if (obj == null || obj is KeyAgreeRecipientIdentifier) + return (KeyAgreeRecipientIdentifier)obj; + + if (obj is Asn1Sequence) + return new KeyAgreeRecipientIdentifier(IssuerAndSerialNumber.GetInstance(obj)); + + if (obj is Asn1TaggedObject && ((Asn1TaggedObject)obj).TagNo == 0) + { + return new KeyAgreeRecipientIdentifier(RecipientKeyIdentifier.GetInstance( + (Asn1TaggedObject)obj, false)); + } + + throw new ArgumentException("Invalid KeyAgreeRecipientIdentifier: " + Platform.GetTypeName(obj), "obj"); + } + + private readonly IssuerAndSerialNumber issuerSerial; + private readonly RecipientKeyIdentifier rKeyID; + + public KeyAgreeRecipientIdentifier( + IssuerAndSerialNumber issuerSerial) + { + this.issuerSerial = issuerSerial; + } + + public KeyAgreeRecipientIdentifier( + RecipientKeyIdentifier rKeyID) + { + this.rKeyID = rKeyID; + } + + public IssuerAndSerialNumber IssuerAndSerialNumber + { + get { return issuerSerial; } + } + + public RecipientKeyIdentifier RKeyID + { + get { return rKeyID; } + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *
+		 * KeyAgreeRecipientIdentifier ::= CHOICE {
+		 *     issuerAndSerialNumber IssuerAndSerialNumber,
+		 *     rKeyId [0] IMPLICIT RecipientKeyIdentifier
+		 * }
+		 * 
+ */ + public override Asn1Object ToAsn1Object() + { + if (issuerSerial != null) + { + return issuerSerial.ToAsn1Object(); + } + + return new DerTaggedObject(false, 0, rKeyID); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cms/KeyAgreeRecipientInfo.cs b/bc-sharp-crypto/src/asn1/cms/KeyAgreeRecipientInfo.cs new file mode 100644 index 0000000..62a3892 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cms/KeyAgreeRecipientInfo.cs @@ -0,0 +1,141 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cms +{ + public class KeyAgreeRecipientInfo + : 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[index++]; + originator = OriginatorIdentifierOrKey.GetInstance( + (Asn1TaggedObject) seq[index++], true); + + if (seq[index] is Asn1TaggedObject) + { + ukm = Asn1OctetString.GetInstance( + (Asn1TaggedObject) seq[index++], true); + } + + keyEncryptionAlgorithm = AlgorithmIdentifier.GetInstance( + seq[index++]); + + recipientEncryptedKeys = (Asn1Sequence) seq[index++]; + } + + /** + * return a KeyAgreeRecipientInfo object from a tagged object. + * + * @param obj the tagged object holding the object we want. + * @param explicitly true if the object is meant to be explicitly + * tagged false otherwise. + * @exception ArgumentException if the object held by the + * tagged object cannot be converted. + */ + public static KeyAgreeRecipientInfo GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); + } + + /** + * return a KeyAgreeRecipientInfo object from the given object. + * + * @param obj the object we want converted. + * @exception ArgumentException if the object cannot be converted. + */ + public static KeyAgreeRecipientInfo GetInstance( + object obj) + { + if (obj == null || obj is KeyAgreeRecipientInfo) + return (KeyAgreeRecipientInfo)obj; + + if (obj is Asn1Sequence) + return new KeyAgreeRecipientInfo((Asn1Sequence)obj); + + throw new ArgumentException( + "Illegal object in KeyAgreeRecipientInfo: " + Platform.GetTypeName(obj)); + + } + + public DerInteger Version + { + get { return version; } + } + + public OriginatorIdentifierOrKey Originator + { + get { return originator; } + } + + public Asn1OctetString UserKeyingMaterial + { + get { return ukm; } + } + + public AlgorithmIdentifier KeyEncryptionAlgorithm + { + get { return keyEncryptionAlgorithm; } + } + + public Asn1Sequence RecipientEncryptedKeys + { + get { 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
+         * }
+		 *
+		 * UserKeyingMaterial ::= OCTET STRING
+         * 
+ */ + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector( + version, new DerTaggedObject(true, 0, originator)); + + if (ukm != null) + { + v.Add(new DerTaggedObject(true, 1, ukm)); + } + + v.Add(keyEncryptionAlgorithm, recipientEncryptedKeys); + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cms/KeyTransRecipientInfo.cs b/bc-sharp-crypto/src/asn1/cms/KeyTransRecipientInfo.cs new file mode 100644 index 0000000..5e4fd22 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cms/KeyTransRecipientInfo.cs @@ -0,0 +1,99 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cms +{ + public class KeyTransRecipientInfo + : Asn1Encodable + { + private DerInteger version; + private RecipientIdentifier rid; + private AlgorithmIdentifier keyEncryptionAlgorithm; + private Asn1OctetString encryptedKey; + + public KeyTransRecipientInfo( + RecipientIdentifier rid, + AlgorithmIdentifier keyEncryptionAlgorithm, + Asn1OctetString encryptedKey) + { + if (rid.ToAsn1Object() is 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[0]; + this.rid = RecipientIdentifier.GetInstance(seq[1]); + this.keyEncryptionAlgorithm = AlgorithmIdentifier.GetInstance(seq[2]); + this.encryptedKey = (Asn1OctetString) seq[3]; + } + + /** + * return a KeyTransRecipientInfo object from the given object. + * + * @param obj the object we want converted. + * @exception ArgumentException if the object cannot be converted. + */ + public static KeyTransRecipientInfo GetInstance( + object obj) + { + if (obj == null || obj is KeyTransRecipientInfo) + return (KeyTransRecipientInfo) obj; + + if(obj is Asn1Sequence) + return new KeyTransRecipientInfo((Asn1Sequence) obj); + + throw new ArgumentException( + "Illegal object in KeyTransRecipientInfo: " + Platform.GetTypeName(obj)); + } + + public DerInteger Version + { + get { return version; } + } + + public RecipientIdentifier RecipientIdentifier + { + get { return rid; } + } + + public AlgorithmIdentifier KeyEncryptionAlgorithm + { + get { return keyEncryptionAlgorithm; } + } + + public Asn1OctetString EncryptedKey + { + get { 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 override Asn1Object ToAsn1Object() + { + return new DerSequence(version, rid, keyEncryptionAlgorithm, encryptedKey); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cms/MetaData.cs b/bc-sharp-crypto/src/asn1/cms/MetaData.cs new file mode 100644 index 0000000..ad2b5c4 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cms/MetaData.cs @@ -0,0 +1,94 @@ +using System; + +namespace Org.BouncyCastle.Asn1.Cms +{ + public class MetaData + : Asn1Encodable + { + private DerBoolean hashProtected; + private DerUtf8String fileName; + private DerIA5String mediaType; + private Attributes otherMetaData; + + public MetaData( + DerBoolean hashProtected, + DerUtf8String fileName, + DerIA5String mediaType, + Attributes otherMetaData) + { + this.hashProtected = hashProtected; + this.fileName = fileName; + this.mediaType = mediaType; + this.otherMetaData = otherMetaData; + } + + private MetaData(Asn1Sequence seq) + { + this.hashProtected = DerBoolean.GetInstance(seq[0]); + + int index = 1; + + if (index < seq.Count && seq[index] is DerUtf8String) + { + this.fileName = DerUtf8String.GetInstance(seq[index++]); + } + if (index < seq.Count && seq[index] is DerIA5String) + { + this.mediaType = DerIA5String.GetInstance(seq[index++]); + } + if (index < seq.Count) + { + this.otherMetaData = Attributes.GetInstance(seq[index++]); + } + } + + public static MetaData GetInstance(object obj) + { + if (obj is MetaData) + return (MetaData)obj; + + if (obj != null) + return new MetaData(Asn1Sequence.GetInstance(obj)); + + return null; + } + + /** + *
+		 * MetaData ::= SEQUENCE {
+		 *   hashProtected        BOOLEAN,
+		 *   fileName             UTF8String OPTIONAL,
+		 *   mediaType            IA5String OPTIONAL,
+		 *   otherMetaData        Attributes OPTIONAL
+		 * }
+		 * 
+ * @return + */ + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(hashProtected); + v.AddOptional(fileName, mediaType, otherMetaData); + return new DerSequence(v); + } + + public virtual bool IsHashProtected + { + get { return hashProtected.IsTrue; } + } + + public virtual DerUtf8String FileName + { + get { return fileName; } + } + + public virtual DerIA5String MediaType + { + get { return mediaType; } + } + + public virtual Attributes OtherMetaData + { + get { return otherMetaData; } + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cms/OriginatorIdentifierOrKey.cs b/bc-sharp-crypto/src/asn1/cms/OriginatorIdentifierOrKey.cs new file mode 100644 index 0000000..f197fe9 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cms/OriginatorIdentifierOrKey.cs @@ -0,0 +1,168 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cms +{ + public class OriginatorIdentifierOrKey + : Asn1Encodable, IAsn1Choice + { + private Asn1Encodable id; + + public OriginatorIdentifierOrKey( + IssuerAndSerialNumber id) + { + this.id = id; + } + + [Obsolete("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); + } + + [Obsolete("Use more specific version")] + public OriginatorIdentifierOrKey( + Asn1Object id) + { + this.id = id; + } + + private OriginatorIdentifierOrKey( + Asn1TaggedObject id) + { + // TODO Add validation + this.id = id; + } + + /** + * return an OriginatorIdentifierOrKey object from a tagged object. + * + * @param o the tagged object holding the object we want. + * @param explicitly true if the object is meant to be explicitly + * tagged false otherwise. + * @exception ArgumentException if the object held by the + * tagged object cannot be converted. + */ + public static OriginatorIdentifierOrKey GetInstance( + Asn1TaggedObject o, + bool explicitly) + { + if (!explicitly) + { + throw new ArgumentException( + "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 ArgumentException if the object cannot be converted. + */ + public static OriginatorIdentifierOrKey GetInstance( + object o) + { + if (o == null || o is OriginatorIdentifierOrKey) + return (OriginatorIdentifierOrKey)o; + + if (o is IssuerAndSerialNumber) + return new OriginatorIdentifierOrKey((IssuerAndSerialNumber)o); + + if (o is SubjectKeyIdentifier) + return new OriginatorIdentifierOrKey((SubjectKeyIdentifier)o); + + if (o is OriginatorPublicKey) + return new OriginatorIdentifierOrKey((OriginatorPublicKey)o); + + if (o is Asn1TaggedObject) + return new OriginatorIdentifierOrKey((Asn1TaggedObject)o); + + throw new ArgumentException("Invalid OriginatorIdentifierOrKey: " + Platform.GetTypeName(o)); + } + + public Asn1Encodable ID + { + get { return id; } + } + + public IssuerAndSerialNumber IssuerAndSerialNumber + { + get + { + if (id is IssuerAndSerialNumber) + { + return (IssuerAndSerialNumber)id; + } + + return null; + } + } + + public SubjectKeyIdentifier SubjectKeyIdentifier + { + get + { + if (id is Asn1TaggedObject && ((Asn1TaggedObject)id).TagNo == 0) + { + return SubjectKeyIdentifier.GetInstance((Asn1TaggedObject)id, false); + } + + return null; + } + } + + [Obsolete("Use 'OriginatorPublicKey' property")] + public OriginatorPublicKey OriginatorKey + { + get { return OriginatorPublicKey; } + } + + public OriginatorPublicKey OriginatorPublicKey + { + get + { + if (id is Asn1TaggedObject && ((Asn1TaggedObject)id).TagNo == 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 override Asn1Object ToAsn1Object() + { + return id.ToAsn1Object(); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cms/OriginatorInfo.cs b/bc-sharp-crypto/src/asn1/cms/OriginatorInfo.cs new file mode 100644 index 0000000..33b049e --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cms/OriginatorInfo.cs @@ -0,0 +1,121 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cms +{ + public class OriginatorInfo + : 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.Count) + { + case 0: // empty + break; + case 1: + Asn1TaggedObject o = (Asn1TaggedObject) seq[0]; + switch (o.TagNo) + { + case 0 : + certs = Asn1Set.GetInstance(o, false); + break; + case 1 : + crls = Asn1Set.GetInstance(o, false); + break; + default: + throw new ArgumentException("Bad tag in OriginatorInfo: " + o.TagNo); + } + break; + case 2: + certs = Asn1Set.GetInstance((Asn1TaggedObject) seq[0], false); + crls = Asn1Set.GetInstance((Asn1TaggedObject) seq[1], false); + break; + default: + throw new ArgumentException("OriginatorInfo too big"); + } + } + + /** + * return an OriginatorInfo object from a tagged object. + * + * @param obj the tagged object holding the object we want. + * @param explicitly true if the object is meant to be explicitly + * tagged false otherwise. + * @exception ArgumentException if the object held by the + * tagged object cannot be converted. + */ + public static OriginatorInfo GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); + } + + /** + * return an OriginatorInfo object from the given object. + * + * @param obj the object we want converted. + * @exception ArgumentException if the object cannot be converted. + */ + public static OriginatorInfo GetInstance( + object obj) + { + if (obj == null || obj is OriginatorInfo) + return (OriginatorInfo)obj; + + if (obj is Asn1Sequence) + return new OriginatorInfo((Asn1Sequence)obj); + + throw new ArgumentException("Invalid OriginatorInfo: " + Platform.GetTypeName(obj)); + } + + public Asn1Set Certificates + { + get { return certs; } + } + + public Asn1Set Crls + { + get { return crls; } + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *
+         * OriginatorInfo ::= Sequence {
+         *     certs [0] IMPLICIT CertificateSet OPTIONAL,
+         *     crls [1] IMPLICIT CertificateRevocationLists OPTIONAL
+         * }
+         * 
+ */ + public override Asn1Object 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/bc-sharp-crypto/src/asn1/cms/OriginatorPublicKey.cs b/bc-sharp-crypto/src/asn1/cms/OriginatorPublicKey.cs new file mode 100644 index 0000000..9f29c62 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cms/OriginatorPublicKey.cs @@ -0,0 +1,88 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cms +{ + public class OriginatorPublicKey + : Asn1Encodable + { + private readonly AlgorithmIdentifier mAlgorithm; + private readonly DerBitString mPublicKey; + + public OriginatorPublicKey( + AlgorithmIdentifier algorithm, + byte[] publicKey) + { + this.mAlgorithm = algorithm; + this.mPublicKey = new DerBitString(publicKey); + } + + [Obsolete("Use 'GetInstance' instead")] + public OriginatorPublicKey( + Asn1Sequence seq) + { + this.mAlgorithm = AlgorithmIdentifier.GetInstance(seq[0]); + this.mPublicKey = DerBitString.GetInstance(seq[1]); + } + + /** + * return an OriginatorPublicKey object from a tagged object. + * + * @param obj the tagged object holding the object we want. + * @param explicitly true if the object is meant to be explicitly + * tagged false otherwise. + * @exception ArgumentException if the object held by the + * tagged object cannot be converted. + */ + public static OriginatorPublicKey GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); + } + + /** + * return an OriginatorPublicKey object from the given object. + * + * @param obj the object we want converted. + * @exception ArgumentException if the object cannot be converted. + */ + public static OriginatorPublicKey GetInstance( + object obj) + { + if (obj == null || obj is OriginatorPublicKey) + return (OriginatorPublicKey)obj; + + if (obj is Asn1Sequence) + return new OriginatorPublicKey(Asn1Sequence.GetInstance(obj)); + + throw new ArgumentException("Invalid OriginatorPublicKey: " + Platform.GetTypeName(obj)); + } + + public AlgorithmIdentifier Algorithm + { + get { return mAlgorithm; } + } + + public DerBitString PublicKey + { + get { return mPublicKey; } + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *
+         * OriginatorPublicKey ::= Sequence {
+         *     algorithm AlgorithmIdentifier,
+         *     publicKey BIT STRING
+         * }
+         * 
+ */ + public override Asn1Object ToAsn1Object() + { + return new DerSequence(mAlgorithm, mPublicKey); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cms/OtherKeyAttribute.cs b/bc-sharp-crypto/src/asn1/cms/OtherKeyAttribute.cs new file mode 100644 index 0000000..285c881 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cms/OtherKeyAttribute.cs @@ -0,0 +1,70 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cms +{ + public class OtherKeyAttribute + : Asn1Encodable + { + private DerObjectIdentifier keyAttrId; + private Asn1Encodable keyAttr; + + /** + * return an OtherKeyAttribute object from the given object. + * + * @param o the object we want converted. + * @exception ArgumentException if the object cannot be converted. + */ + public static OtherKeyAttribute GetInstance( + object obj) + { + if (obj == null || obj is OtherKeyAttribute) + return (OtherKeyAttribute) obj; + + if (obj is Asn1Sequence) + return new OtherKeyAttribute((Asn1Sequence) obj); + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + public OtherKeyAttribute( + Asn1Sequence seq) + { + keyAttrId = (DerObjectIdentifier) seq[0]; + keyAttr = seq[1]; + } + + public OtherKeyAttribute( + DerObjectIdentifier keyAttrId, + Asn1Encodable keyAttr) + { + this.keyAttrId = keyAttrId; + this.keyAttr = keyAttr; + } + + public DerObjectIdentifier KeyAttrId + { + get { return keyAttrId; } + } + + public Asn1Encodable KeyAttr + { + get { return keyAttr; } + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *
+         * OtherKeyAttribute ::= Sequence {
+         *     keyAttrId OBJECT IDENTIFIER,
+         *     keyAttr ANY DEFINED BY keyAttrId OPTIONAL
+         * }
+         * 
+ */ + public override Asn1Object ToAsn1Object() + { + return new DerSequence(keyAttrId, keyAttr); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cms/OtherRecipientInfo.cs b/bc-sharp-crypto/src/asn1/cms/OtherRecipientInfo.cs new file mode 100644 index 0000000..80dd68e --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cms/OtherRecipientInfo.cs @@ -0,0 +1,83 @@ +using System; + +namespace Org.BouncyCastle.Asn1.Cms +{ + public class OtherRecipientInfo + : Asn1Encodable + { + private readonly DerObjectIdentifier oriType; + private readonly Asn1Encodable oriValue; + + public OtherRecipientInfo( + DerObjectIdentifier oriType, + Asn1Encodable oriValue) + { + this.oriType = oriType; + this.oriValue = oriValue; + } + + [Obsolete("Use GetInstance() instead")] + public OtherRecipientInfo( + Asn1Sequence seq) + { + oriType = DerObjectIdentifier.GetInstance(seq[0]); + oriValue = seq[1]; + } + + /** + * return a OtherRecipientInfo object from a tagged object. + * + * @param obj the tagged object holding the object we want. + * @param explicitly true if the object is meant to be explicitly + * tagged false otherwise. + * @exception ArgumentException if the object held by the + * tagged object cannot be converted. + */ + public static OtherRecipientInfo GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); + } + + /** + * return a OtherRecipientInfo object from the given object. + * + * @param obj the object we want converted. + * @exception ArgumentException if the object cannot be converted. + */ + public static OtherRecipientInfo GetInstance( + object obj) + { + if (obj == null) + return null; + OtherRecipientInfo existing = obj as OtherRecipientInfo; + if (existing != null) + return existing; + return new OtherRecipientInfo(Asn1Sequence.GetInstance(obj)); + } + + public virtual DerObjectIdentifier OriType + { + get { return oriType; } + } + + public virtual Asn1Encodable OriValue + { + get { return oriValue; } + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *
+         * OtherRecipientInfo ::= Sequence {
+         *    oriType OBJECT IDENTIFIER,
+         *    oriValue ANY DEFINED BY oriType }
+         * 
+ */ + public override Asn1Object ToAsn1Object() + { + return new DerSequence(oriType, oriValue); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cms/OtherRevocationInfoFormat.cs b/bc-sharp-crypto/src/asn1/cms/OtherRevocationInfoFormat.cs new file mode 100644 index 0000000..7835489 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cms/OtherRevocationInfoFormat.cs @@ -0,0 +1,77 @@ +using System; + +namespace Org.BouncyCastle.Asn1.Cms +{ + public class OtherRevocationInfoFormat + : Asn1Encodable + { + private readonly DerObjectIdentifier otherRevInfoFormat; + private readonly Asn1Encodable otherRevInfo; + + public OtherRevocationInfoFormat( + DerObjectIdentifier otherRevInfoFormat, + Asn1Encodable otherRevInfo) + { + this.otherRevInfoFormat = otherRevInfoFormat; + this.otherRevInfo = otherRevInfo; + } + + private OtherRevocationInfoFormat(Asn1Sequence seq) + { + otherRevInfoFormat = DerObjectIdentifier.GetInstance(seq[0]); + otherRevInfo = seq[1]; + } + + /** + * return a OtherRevocationInfoFormat object from a tagged object. + * + * @param obj the tagged object holding the object we want. + * @param explicit true if the object is meant to be explicitly + * tagged false otherwise. + * @exception IllegalArgumentException if the object held by the + * tagged object cannot be converted. + */ + public static OtherRevocationInfoFormat GetInstance(Asn1TaggedObject obj, bool isExplicit) + { + return GetInstance(Asn1Sequence.GetInstance(obj, isExplicit)); + } + + /** + * return a OtherRevocationInfoFormat object from the given object. + * + * @param obj the object we want converted. + * @exception IllegalArgumentException if the object cannot be converted. + */ + public static OtherRevocationInfoFormat GetInstance(object obj) + { + if (obj is OtherRevocationInfoFormat) + return (OtherRevocationInfoFormat)obj; + if (obj != null) + return new OtherRevocationInfoFormat(Asn1Sequence.GetInstance(obj)); + return null; + } + + public virtual DerObjectIdentifier InfoFormat + { + get { return otherRevInfoFormat; } + } + + public virtual Asn1Encodable Info + { + get { return otherRevInfo; } + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+         * OtherRevocationInfoFormat ::= SEQUENCE {
+         *      otherRevInfoFormat OBJECT IDENTIFIER,
+         *      otherRevInfo ANY DEFINED BY otherRevInfoFormat }
+         * 
+ */ + public override Asn1Object ToAsn1Object() + { + return new DerSequence(otherRevInfoFormat, otherRevInfo); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cms/PasswordRecipientInfo.cs b/bc-sharp-crypto/src/asn1/cms/PasswordRecipientInfo.cs new file mode 100644 index 0000000..7f275fd --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cms/PasswordRecipientInfo.cs @@ -0,0 +1,133 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cms +{ + public class PasswordRecipientInfo + : Asn1Encodable + { + private readonly DerInteger version; + private readonly AlgorithmIdentifier keyDerivationAlgorithm; + private readonly AlgorithmIdentifier keyEncryptionAlgorithm; + private readonly 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[0]; + + if (seq[1] is Asn1TaggedObject) + { + keyDerivationAlgorithm = AlgorithmIdentifier.GetInstance((Asn1TaggedObject) seq[1], false); + keyEncryptionAlgorithm = AlgorithmIdentifier.GetInstance(seq[2]); + encryptedKey = (Asn1OctetString) seq[3]; + } + else + { + keyEncryptionAlgorithm = AlgorithmIdentifier.GetInstance(seq[1]); + encryptedKey = (Asn1OctetString) seq[2]; + } + } + + /** + * return a PasswordRecipientInfo object from a tagged object. + * + * @param obj the tagged object holding the object we want. + * @param explicitly true if the object is meant to be explicitly + * tagged false otherwise. + * @exception ArgumentException if the object held by the + * tagged object cannot be converted. + */ + public static PasswordRecipientInfo GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); + } + + /** + * return a PasswordRecipientInfo object from the given object. + * + * @param obj the object we want converted. + * @exception ArgumentException if the object cannot be converted. + */ + public static PasswordRecipientInfo GetInstance( + object obj) + { + if (obj == null || obj is PasswordRecipientInfo) + return (PasswordRecipientInfo) obj; + + if (obj is Asn1Sequence) + return new PasswordRecipientInfo((Asn1Sequence) obj); + + throw new ArgumentException("Invalid PasswordRecipientInfo: " + Platform.GetTypeName(obj)); + } + + public DerInteger Version + { + get { return version; } + } + + public AlgorithmIdentifier KeyDerivationAlgorithm + { + get { return keyDerivationAlgorithm; } + } + + public AlgorithmIdentifier KeyEncryptionAlgorithm + { + get { return keyEncryptionAlgorithm; } + } + + public Asn1OctetString EncryptedKey + { + get { 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 override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(version); + + if (keyDerivationAlgorithm != null) + { + v.Add(new DerTaggedObject(false, 0, keyDerivationAlgorithm)); + } + + v.Add(keyEncryptionAlgorithm, encryptedKey); + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cms/RecipientEncryptedKey.cs b/bc-sharp-crypto/src/asn1/cms/RecipientEncryptedKey.cs new file mode 100644 index 0000000..1afba4a --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cms/RecipientEncryptedKey.cs @@ -0,0 +1,90 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cms +{ + public class RecipientEncryptedKey + : Asn1Encodable + { + private readonly KeyAgreeRecipientIdentifier identifier; + private readonly Asn1OctetString encryptedKey; + + private RecipientEncryptedKey( + Asn1Sequence seq) + { + identifier = KeyAgreeRecipientIdentifier.GetInstance(seq[0]); + encryptedKey = (Asn1OctetString) seq[1]; + } + + /** + * return an RecipientEncryptedKey object from a tagged object. + * + * @param obj the tagged object holding the object we want. + * @param isExplicit true if the object is meant to be explicitly + * tagged false otherwise. + * @exception ArgumentException if the object held by the + * tagged object cannot be converted. + */ + public static RecipientEncryptedKey GetInstance( + Asn1TaggedObject obj, + bool isExplicit) + { + return GetInstance(Asn1Sequence.GetInstance(obj, isExplicit)); + } + + /** + * return a RecipientEncryptedKey object from the given object. + * + * @param obj the object we want converted. + * @exception ArgumentException if the object cannot be converted. + */ + public static RecipientEncryptedKey GetInstance( + object obj) + { + if (obj == null || obj is RecipientEncryptedKey) + { + return (RecipientEncryptedKey) obj; + } + + if (obj is Asn1Sequence) + { + return new RecipientEncryptedKey((Asn1Sequence) obj); + } + + throw new ArgumentException("Invalid RecipientEncryptedKey: " + Platform.GetTypeName(obj), "obj"); + } + + public RecipientEncryptedKey( + KeyAgreeRecipientIdentifier id, + Asn1OctetString encryptedKey) + { + this.identifier = id; + this.encryptedKey = encryptedKey; + } + + public KeyAgreeRecipientIdentifier Identifier + { + get { return identifier; } + } + + public Asn1OctetString EncryptedKey + { + get { return encryptedKey; } + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *
+		 * RecipientEncryptedKey ::= SEQUENCE {
+		 *     rid KeyAgreeRecipientIdentifier,
+		 *     encryptedKey EncryptedKey
+		 * }
+		 * 
+ */ + public override Asn1Object ToAsn1Object() + { + return new DerSequence(identifier, encryptedKey); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cms/RecipientIdentifier.cs b/bc-sharp-crypto/src/asn1/cms/RecipientIdentifier.cs new file mode 100644 index 0000000..f29fa8d --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cms/RecipientIdentifier.cs @@ -0,0 +1,89 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cms +{ + public class RecipientIdentifier + : Asn1Encodable, IAsn1Choice + { + private Asn1Encodable id; + + public RecipientIdentifier( + IssuerAndSerialNumber id) + { + this.id = id; + } + + public RecipientIdentifier( + Asn1OctetString id) + { + this.id = new DerTaggedObject(false, 0, id); + } + + public RecipientIdentifier( + Asn1Object id) + { + this.id = id; + } + + /** + * return a RecipientIdentifier object from the given object. + * + * @param o the object we want converted. + * @exception ArgumentException if the object cannot be converted. + */ + public static RecipientIdentifier GetInstance( + object o) + { + if (o == null || o is RecipientIdentifier) + return (RecipientIdentifier)o; + + if (o is IssuerAndSerialNumber) + return new RecipientIdentifier((IssuerAndSerialNumber) o); + + if (o is Asn1OctetString) + return new RecipientIdentifier((Asn1OctetString) o); + + if (o is Asn1Object) + return new RecipientIdentifier((Asn1Object) o); + + throw new ArgumentException( + "Illegal object in RecipientIdentifier: " + Platform.GetTypeName(o)); + } + + public bool IsTagged + { + get { return (id is Asn1TaggedObject); } + } + + public Asn1Encodable ID + { + get + { + if (id is 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 override Asn1Object ToAsn1Object() + { + return id.ToAsn1Object(); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cms/RecipientInfo.cs b/bc-sharp-crypto/src/asn1/cms/RecipientInfo.cs new file mode 100644 index 0000000..c03ad90 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cms/RecipientInfo.cs @@ -0,0 +1,145 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cms +{ + public class RecipientInfo + : Asn1Encodable, IAsn1Choice + { + internal Asn1Encodable 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( + Asn1Object info) + { + this.info = info; + } + + public static RecipientInfo GetInstance( + object o) + { + if (o == null || o is RecipientInfo) + return (RecipientInfo) o; + + if (o is Asn1Sequence) + return new RecipientInfo((Asn1Sequence) o); + + if (o is Asn1TaggedObject) + return new RecipientInfo((Asn1TaggedObject) o); + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(o)); + } + + public DerInteger Version + { + get + { + if (info is Asn1TaggedObject) + { + Asn1TaggedObject o = (Asn1TaggedObject) info; + + switch (o.TagNo) + { + case 1: + return KeyAgreeRecipientInfo.GetInstance(o, false).Version; + case 2: + return GetKekInfo(o).Version; + case 3: + return PasswordRecipientInfo.GetInstance(o, false).Version; + case 4: + return new DerInteger(0); // no syntax version for OtherRecipientInfo + default: + throw new InvalidOperationException("unknown tag"); + } + } + + return KeyTransRecipientInfo.GetInstance(info).Version; + } + } + + public bool IsTagged + { + get { return info is Asn1TaggedObject; } + } + + public Asn1Encodable Info + { + get + { + if (info is Asn1TaggedObject) + { + Asn1TaggedObject o = (Asn1TaggedObject) info; + + switch (o.TagNo) + { + 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 InvalidOperationException("unknown tag"); + } + } + + return KeyTransRecipientInfo.GetInstance(info); + } + } + + private KekRecipientInfo GetKekInfo( + Asn1TaggedObject o) + { + // For compatibility with erroneous version, we don't always pass 'false' here + return KekRecipientInfo.GetInstance(o, o.IsExplicit()); + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *
+         * RecipientInfo ::= CHOICE {
+         *     ktri KeyTransRecipientInfo,
+         *     kari [1] KeyAgreeRecipientInfo,
+         *     kekri [2] KekRecipientInfo,
+         *     pwri [3] PasswordRecipientInfo,
+         *     ori [4] OtherRecipientInfo }
+         * 
+ */ + public override Asn1Object ToAsn1Object() + { + return info.ToAsn1Object(); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cms/RecipientKeyIdentifier.cs b/bc-sharp-crypto/src/asn1/cms/RecipientKeyIdentifier.cs new file mode 100644 index 0000000..995ddab --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cms/RecipientKeyIdentifier.cs @@ -0,0 +1,137 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cms +{ + public class RecipientKeyIdentifier + : 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( + byte[] subjectKeyIdentifier) + : this(subjectKeyIdentifier, null, null) + { + } + + public RecipientKeyIdentifier( + byte[] subjectKeyIdentifier, + DerGeneralizedTime date, + OtherKeyAttribute other) + { + this.subjectKeyIdentifier = new DerOctetString(subjectKeyIdentifier); + this.date = date; + this.other = other; + } + + public RecipientKeyIdentifier( + Asn1Sequence seq) + { + subjectKeyIdentifier = Asn1OctetString.GetInstance( + seq[0]); + + switch(seq.Count) + { + case 1: + break; + case 2: + if (seq[1] is DerGeneralizedTime) + { + date = (DerGeneralizedTime) seq[1]; + } + else + { + other = OtherKeyAttribute.GetInstance(seq[2]); + } + break; + case 3: + date = (DerGeneralizedTime) seq[1]; + other = OtherKeyAttribute.GetInstance(seq[2]); + break; + default: + throw new ArgumentException("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 ArgumentException if the object held by the + * tagged object cannot be converted. + */ + public static RecipientKeyIdentifier GetInstance( + Asn1TaggedObject ato, + bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(ato, explicitly)); + } + + /** + * return a RecipientKeyIdentifier object from the given object. + * + * @param _obj the object we want converted. + * @exception ArgumentException if the object cannot be converted. + */ + public static RecipientKeyIdentifier GetInstance( + object obj) + { + if (obj == null || obj is RecipientKeyIdentifier) + return (RecipientKeyIdentifier) obj; + + if (obj is Asn1Sequence) + return new RecipientKeyIdentifier((Asn1Sequence) obj); + + throw new ArgumentException("Invalid RecipientKeyIdentifier: " + Platform.GetTypeName(obj)); + } + + public Asn1OctetString SubjectKeyIdentifier + { + get { return subjectKeyIdentifier; } + } + + public DerGeneralizedTime Date + { + get { return date; } + } + + public OtherKeyAttribute OtherKeyAttribute + { + get { return other; } + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *
+         * RecipientKeyIdentifier ::= Sequence {
+         *     subjectKeyIdentifier SubjectKeyIdentifier,
+         *     date GeneralizedTime OPTIONAL,
+         *     other OtherKeyAttribute OPTIONAL
+         * }
+         *
+         * SubjectKeyIdentifier ::= OCTET STRING
+         * 
+ */ + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(subjectKeyIdentifier); + v.AddOptional(date, other); + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cms/SCVPReqRes.cs b/bc-sharp-crypto/src/asn1/cms/SCVPReqRes.cs new file mode 100644 index 0000000..486979a --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cms/SCVPReqRes.cs @@ -0,0 +1,77 @@ +using System; + +namespace Org.BouncyCastle.Asn1.Cms +{ + public class ScvpReqRes + : Asn1Encodable + { + private readonly ContentInfo request; + private readonly ContentInfo response; + + public static ScvpReqRes GetInstance(object obj) + { + if (obj is ScvpReqRes) + return (ScvpReqRes)obj; + if (obj != null) + return new ScvpReqRes(Asn1Sequence.GetInstance(obj)); + return null; + } + + private ScvpReqRes(Asn1Sequence seq) + { + if (seq[0] is Asn1TaggedObject) + { + this.request = ContentInfo.GetInstance(Asn1TaggedObject.GetInstance(seq[0]), true); + this.response = ContentInfo.GetInstance(seq[1]); + } + else + { + this.request = null; + this.response = ContentInfo.GetInstance(seq[0]); + } + } + + public ScvpReqRes(ContentInfo response) + : this(null, response) + { + } + + public ScvpReqRes(ContentInfo request, ContentInfo response) + { + this.request = request; + this.response = response; + } + + public virtual ContentInfo Request + { + get { return request; } + } + + public virtual ContentInfo Response + { + get { return response; } + } + + /** + *
+         *    ScvpReqRes ::= SEQUENCE {
+         *    request  [0] EXPLICIT ContentInfo OPTIONAL,
+         *    response     ContentInfo }
+         * 
+ * @return the ASN.1 primitive representation. + */ + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(); + + if (request != null) + { + v.Add(new DerTaggedObject(true, 0, request)); + } + + v.Add(response); + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cms/SignedData.cs b/bc-sharp-crypto/src/asn1/cms/SignedData.cs new file mode 100644 index 0000000..957b81c --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cms/SignedData.cs @@ -0,0 +1,287 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cms +{ + /** + * a signed data object. + */ + public class SignedData + : Asn1Encodable + { + private static readonly DerInteger Version1 = new DerInteger(1); + private static readonly DerInteger Version3 = new DerInteger(3); + private static readonly DerInteger Version4 = new DerInteger(4); + private static readonly DerInteger Version5 = new DerInteger(5); + + private readonly DerInteger version; + private readonly Asn1Set digestAlgorithms; + private readonly ContentInfo contentInfo; + private readonly Asn1Set certificates; + private readonly Asn1Set crls; + private readonly Asn1Set signerInfos; + private readonly bool certsBer; + private readonly bool crlsBer; + + public static SignedData GetInstance( + object obj) + { + if (obj is SignedData) + return (SignedData) obj; + + if (obj is Asn1Sequence) + return new SignedData((Asn1Sequence) obj); + + throw new ArgumentException("Unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + public SignedData( + Asn1Set digestAlgorithms, + ContentInfo contentInfo, + Asn1Set certificates, + Asn1Set crls, + Asn1Set signerInfos) + { + this.version = CalculateVersion(contentInfo.ContentType, certificates, crls, signerInfos); + this.digestAlgorithms = digestAlgorithms; + this.contentInfo = contentInfo; + this.certificates = certificates; + this.crls = crls; + this.signerInfos = signerInfos; + this.crlsBer = crls is BerSet; + this.certsBer = certificates is 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) + { + bool otherCert = false; + bool otherCrl = false; + bool attrCertV1Found = false; + bool attrCertV2Found = false; + + if (certs != null) + { + foreach (object obj in certs) + { + if (obj is Asn1TaggedObject) + { + Asn1TaggedObject tagged = (Asn1TaggedObject)obj; + + if (tagged.TagNo == 1) + { + attrCertV1Found = true; + } + else if (tagged.TagNo == 2) + { + attrCertV2Found = true; + } + else if (tagged.TagNo == 3) + { + otherCert = true; + break; + } + } + } + } + + if (otherCert) + { + return Version5; + } + + if (crls != null) + { + foreach (object obj in crls) + { + if (obj is Asn1TaggedObject) + { + otherCrl = true; + break; + } + } + } + + if (otherCrl) + { + return Version5; + } + + if (attrCertV2Found) + { + return Version4; + } + + if (attrCertV1Found || !CmsObjectIdentifiers.Data.Equals(contentOid) || CheckForVersion3(signerInfs)) + { + return Version3; + } + + return Version1; + } + + private bool CheckForVersion3( + Asn1Set signerInfs) + { + foreach (object obj in signerInfs) + { + SignerInfo s = SignerInfo.GetInstance(obj); + + if (s.Version.Value.IntValue == 3) + { + return true; + } + } + + return false; + } + + private SignedData( + Asn1Sequence seq) + { + IEnumerator e = seq.GetEnumerator(); + + e.MoveNext(); + version = (DerInteger)e.Current; + + e.MoveNext(); + digestAlgorithms = ((Asn1Set)e.Current); + + e.MoveNext(); + contentInfo = ContentInfo.GetInstance(e.Current); + + while (e.MoveNext()) + { + Asn1Object o = (Asn1Object)e.Current; + + // + // 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 is Asn1TaggedObject) + { + Asn1TaggedObject tagged = (Asn1TaggedObject)o; + + switch (tagged.TagNo) + { + case 0: + certsBer = tagged is BerTaggedObject; + certificates = Asn1Set.GetInstance(tagged, false); + break; + case 1: + crlsBer = tagged is BerTaggedObject; + crls = Asn1Set.GetInstance(tagged, false); + break; + default: + throw new ArgumentException("unknown tag value " + tagged.TagNo); + } + } + else + { + signerInfos = (Asn1Set) o; + } + } + } + + public DerInteger Version + { + get { return version; } + } + + public Asn1Set DigestAlgorithms + { + get { return digestAlgorithms; } + } + + public ContentInfo EncapContentInfo + { + get { return contentInfo; } + } + + public Asn1Set Certificates + { + get { return certificates; } + } + + public Asn1Set CRLs + { + get { return crls; } + } + + public Asn1Set SignerInfos + { + get { 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 override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector( + version, digestAlgorithms, 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/bc-sharp-crypto/src/asn1/cms/SignedDataParser.cs b/bc-sharp-crypto/src/asn1/cms/SignedDataParser.cs new file mode 100644 index 0000000..cd07f40 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cms/SignedDataParser.cs @@ -0,0 +1,114 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cms +{ + /** + *
+	* 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 bool _certsCalled; + private bool _crlsCalled; + + public static SignedDataParser GetInstance( + object o) + { + if (o is Asn1Sequence) + return new SignedDataParser(((Asn1Sequence)o).Parser); + + if (o is Asn1SequenceParser) + return new SignedDataParser((Asn1SequenceParser)o); + + throw new IOException("unknown object encountered: " + Platform.GetTypeName(o)); + } + + public SignedDataParser( + Asn1SequenceParser seq) + { + this._seq = seq; + this._version = (DerInteger)seq.ReadObject(); + } + + public DerInteger Version + { + get { return _version; } + } + + public Asn1SetParser GetDigestAlgorithms() + { + return (Asn1SetParser)_seq.ReadObject(); + } + + public ContentInfoParser GetEncapContentInfo() + { + return new ContentInfoParser((Asn1SequenceParser)_seq.ReadObject()); + } + + public Asn1SetParser GetCertificates() + { + _certsCalled = true; + _nextObject = _seq.ReadObject(); + + if (_nextObject is Asn1TaggedObjectParser && ((Asn1TaggedObjectParser)_nextObject).TagNo == 0) + { + Asn1SetParser certs = (Asn1SetParser)((Asn1TaggedObjectParser)_nextObject).GetObjectParser(Asn1Tags.Set, false); + _nextObject = null; + + return certs; + } + + return null; + } + + public Asn1SetParser GetCrls() + { + if (!_certsCalled) + throw new IOException("GetCerts() has not been called."); + + _crlsCalled = true; + + if (_nextObject == null) + { + _nextObject = _seq.ReadObject(); + } + + if (_nextObject is Asn1TaggedObjectParser && ((Asn1TaggedObjectParser)_nextObject).TagNo == 1) + { + Asn1SetParser crls = (Asn1SetParser)((Asn1TaggedObjectParser)_nextObject).GetObjectParser(Asn1Tags.Set, false); + _nextObject = null; + + return crls; + } + + return null; + } + + public Asn1SetParser GetSignerInfos() + { + 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/bc-sharp-crypto/src/asn1/cms/SignerIdentifier.cs b/bc-sharp-crypto/src/asn1/cms/SignerIdentifier.cs new file mode 100644 index 0000000..195ab74 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cms/SignerIdentifier.cs @@ -0,0 +1,89 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cms +{ + public class SignerIdentifier + : Asn1Encodable, IAsn1Choice + { + private Asn1Encodable id; + + public SignerIdentifier( + IssuerAndSerialNumber id) + { + this.id = id; + } + + public SignerIdentifier( + Asn1OctetString id) + { + this.id = new DerTaggedObject(false, 0, id); + } + + public SignerIdentifier( + Asn1Object id) + { + this.id = id; + } + + /** + * return a SignerIdentifier object from the given object. + * + * @param o the object we want converted. + * @exception ArgumentException if the object cannot be converted. + */ + public static SignerIdentifier GetInstance( + object o) + { + if (o == null || o is SignerIdentifier) + return (SignerIdentifier) o; + + if (o is IssuerAndSerialNumber) + return new SignerIdentifier((IssuerAndSerialNumber) o); + + if (o is Asn1OctetString) + return new SignerIdentifier((Asn1OctetString) o); + + if (o is Asn1Object) + return new SignerIdentifier((Asn1Object) o); + + throw new ArgumentException( + "Illegal object in SignerIdentifier: " + Platform.GetTypeName(o)); + } + + public bool IsTagged + { + get { return (id is Asn1TaggedObject); } + } + + public Asn1Encodable ID + { + get + { + if (id is 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 override Asn1Object ToAsn1Object() + { + return id.ToAsn1Object(); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cms/SignerInfo.cs b/bc-sharp-crypto/src/asn1/cms/SignerInfo.cs new file mode 100644 index 0000000..b6bd319 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cms/SignerInfo.cs @@ -0,0 +1,185 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cms +{ + public class SignerInfo + : 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 obj) + { + if (obj == null || obj is SignerInfo) + return (SignerInfo) obj; + + if (obj is Asn1Sequence) + return new SignerInfo((Asn1Sequence) obj); + + throw new ArgumentException("Unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + public SignerInfo( + SignerIdentifier sid, + AlgorithmIdentifier digAlgorithm, + Asn1Set authenticatedAttributes, + AlgorithmIdentifier digEncryptionAlgorithm, + Asn1OctetString encryptedDigest, + Asn1Set unauthenticatedAttributes) + { + this.version = new DerInteger(sid.IsTagged ? 3 : 1); + this.sid = sid; + this.digAlgorithm = digAlgorithm; + this.authenticatedAttributes = authenticatedAttributes; + this.digEncryptionAlgorithm = digEncryptionAlgorithm; + this.encryptedDigest = encryptedDigest; + this.unauthenticatedAttributes = unauthenticatedAttributes; + } + + public SignerInfo( + SignerIdentifier sid, + AlgorithmIdentifier digAlgorithm, + Attributes authenticatedAttributes, + AlgorithmIdentifier digEncryptionAlgorithm, + Asn1OctetString encryptedDigest, + Attributes unauthenticatedAttributes) + { + this.version = new DerInteger(sid.IsTagged ? 3 : 1); + this.sid = sid; + this.digAlgorithm = digAlgorithm; + this.authenticatedAttributes = Asn1Set.GetInstance(authenticatedAttributes); + this.digEncryptionAlgorithm = digEncryptionAlgorithm; + this.encryptedDigest = encryptedDigest; + this.unauthenticatedAttributes = Asn1Set.GetInstance(unauthenticatedAttributes); + } + + [Obsolete("Use 'GetInstance' instead")] + public SignerInfo( + Asn1Sequence seq) + { + IEnumerator e = seq.GetEnumerator(); + + e.MoveNext(); + version = (DerInteger) e.Current; + + e.MoveNext(); + sid = SignerIdentifier.GetInstance(e.Current); + + e.MoveNext(); + digAlgorithm = AlgorithmIdentifier.GetInstance(e.Current); + + e.MoveNext(); + object obj = e.Current; + + if (obj is Asn1TaggedObject) + { + authenticatedAttributes = Asn1Set.GetInstance((Asn1TaggedObject) obj, false); + + e.MoveNext(); + digEncryptionAlgorithm = AlgorithmIdentifier.GetInstance(e.Current); + } + else + { + authenticatedAttributes = null; + digEncryptionAlgorithm = AlgorithmIdentifier.GetInstance(obj); + } + + e.MoveNext(); + encryptedDigest = DerOctetString.GetInstance(e.Current); + + if (e.MoveNext()) + { + unauthenticatedAttributes = Asn1Set.GetInstance((Asn1TaggedObject) e.Current, false); + } + else + { + unauthenticatedAttributes = null; + } + } + + public DerInteger Version + { + get { return version; } + } + + public SignerIdentifier SignerID + { + get { return sid; } + } + + public Asn1Set AuthenticatedAttributes + { + get { return authenticatedAttributes; } + } + + public AlgorithmIdentifier DigestAlgorithm + { + get { return digAlgorithm; } + } + + public Asn1OctetString EncryptedDigest + { + get { return encryptedDigest; } + } + + public AlgorithmIdentifier DigestEncryptionAlgorithm + { + get { return digEncryptionAlgorithm; } + } + + public Asn1Set UnauthenticatedAttributes + { + get { 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 override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector( + version, sid, digAlgorithm); + + if (authenticatedAttributes != null) + { + v.Add(new DerTaggedObject(false, 0, authenticatedAttributes)); + } + + v.Add(digEncryptionAlgorithm, encryptedDigest); + + if (unauthenticatedAttributes != null) + { + v.Add(new DerTaggedObject(false, 1, unauthenticatedAttributes)); + } + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cms/Time.cs b/bc-sharp-crypto/src/asn1/cms/Time.cs new file mode 100644 index 0000000..52fb4f9 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cms/Time.cs @@ -0,0 +1,115 @@ +using System; +using System.Globalization; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cms +{ + public class Time + : Asn1Encodable, IAsn1Choice + { + private readonly Asn1Object time; + + public static Time GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(obj.GetObject()); + } + + public Time( + Asn1Object time) + { + if (time == null) + throw new ArgumentNullException("time"); + if (!(time is DerUtcTime) && !(time is DerGeneralizedTime)) + throw new ArgumentException("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( + DateTime date) + { + string d = date.ToString("yyyyMMddHHmmss", CultureInfo.InvariantCulture) + "Z"; + + int year = int.Parse(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 == null || obj is Time) + return (Time)obj; + if (obj is DerUtcTime) + return new Time((DerUtcTime)obj); + if (obj is DerGeneralizedTime) + return new Time((DerGeneralizedTime)obj); + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + public string TimeString + { + get + { + if (time is DerUtcTime) + { + return ((DerUtcTime)time).AdjustedTimeString; + } + else + { + return ((DerGeneralizedTime)time).GetTime(); + } + } + } + + public DateTime Date + { + get + { + try + { + if (time is DerUtcTime) + { + return ((DerUtcTime)time).ToAdjustedDateTime(); + } + + return ((DerGeneralizedTime)time).ToDateTime(); + } + catch (FormatException e) + { + // this should never happen + throw new InvalidOperationException("invalid date string: " + e.Message); + } + } + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *
+         * Time ::= CHOICE {
+         *             utcTime        UTCTime,
+         *             generalTime    GeneralizedTime }
+         * 
+ */ + public override Asn1Object ToAsn1Object() + { + return time; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cms/TimeStampAndCRL.cs b/bc-sharp-crypto/src/asn1/cms/TimeStampAndCRL.cs new file mode 100644 index 0000000..4cb5f2a --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cms/TimeStampAndCRL.cs @@ -0,0 +1,62 @@ +using System; + +namespace Org.BouncyCastle.Asn1.Cms +{ + public class TimeStampAndCrl + : Asn1Encodable + { + private ContentInfo timeStamp; + private X509.CertificateList crl; + + public TimeStampAndCrl(ContentInfo timeStamp) + { + this.timeStamp = timeStamp; + } + + private TimeStampAndCrl(Asn1Sequence seq) + { + this.timeStamp = ContentInfo.GetInstance(seq[0]); + if (seq.Count == 2) + { + this.crl = X509.CertificateList.GetInstance(seq[1]); + } + } + + public static TimeStampAndCrl GetInstance(object obj) + { + if (obj is TimeStampAndCrl) + return (TimeStampAndCrl)obj; + + if (obj != null) + return new TimeStampAndCrl(Asn1Sequence.GetInstance(obj)); + + return null; + } + + public virtual ContentInfo TimeStampToken + { + get { return this.timeStamp; } + } + + public virtual X509.CertificateList Crl + { + get { return this.crl; } + } + + /** + *
+		 * TimeStampAndCRL ::= SEQUENCE {
+		 *     timeStamp   TimeStampToken,          -- according to RFC 3161
+		 *     crl         CertificateList OPTIONAL -- according to RFC 5280
+		 *  }
+		 * 
+ * @return + */ + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(timeStamp); + v.AddOptional(crl); + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cms/TimeStampTokenEvidence.cs b/bc-sharp-crypto/src/asn1/cms/TimeStampTokenEvidence.cs new file mode 100644 index 0000000..8625d05 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cms/TimeStampTokenEvidence.cs @@ -0,0 +1,65 @@ +using System; + +namespace Org.BouncyCastle.Asn1.Cms +{ + public class TimeStampTokenEvidence + : Asn1Encodable + { + private TimeStampAndCrl[] timeStampAndCrls; + + public TimeStampTokenEvidence(TimeStampAndCrl[] timeStampAndCrls) + { + this.timeStampAndCrls = timeStampAndCrls; + } + + public TimeStampTokenEvidence(TimeStampAndCrl timeStampAndCrl) + { + this.timeStampAndCrls = new TimeStampAndCrl[]{ timeStampAndCrl }; + } + + private TimeStampTokenEvidence(Asn1Sequence seq) + { + this.timeStampAndCrls = new TimeStampAndCrl[seq.Count]; + + int count = 0; + + foreach (Asn1Encodable ae in seq) + { + this.timeStampAndCrls[count++] = TimeStampAndCrl.GetInstance(ae.ToAsn1Object()); + } + } + + public static TimeStampTokenEvidence GetInstance(Asn1TaggedObject tagged, bool isExplicit) + { + return GetInstance(Asn1Sequence.GetInstance(tagged, isExplicit)); + } + + public static TimeStampTokenEvidence GetInstance(object obj) + { + if (obj is TimeStampTokenEvidence) + return (TimeStampTokenEvidence)obj; + + if (obj != null) + return new TimeStampTokenEvidence(Asn1Sequence.GetInstance(obj)); + + return null; + } + + public virtual TimeStampAndCrl[] ToTimeStampAndCrlArray() + { + return (TimeStampAndCrl[])timeStampAndCrls.Clone(); + } + + /** + *
+		 * TimeStampTokenEvidence ::=
+		 *    SEQUENCE SIZE(1..MAX) OF TimeStampAndCrl
+		 * 
+ * @return + */ + public override Asn1Object ToAsn1Object() + { + return new DerSequence(timeStampAndCrls); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cms/TimeStampedData.cs b/bc-sharp-crypto/src/asn1/cms/TimeStampedData.cs new file mode 100644 index 0000000..15448a9 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cms/TimeStampedData.cs @@ -0,0 +1,95 @@ +using System; + +namespace Org.BouncyCastle.Asn1.Cms +{ + public class TimeStampedData + : Asn1Encodable + { + private DerInteger version; + private DerIA5String dataUri; + private MetaData metaData; + private Asn1OctetString content; + private Evidence temporalEvidence; + + public TimeStampedData(DerIA5String dataUri, MetaData metaData, Asn1OctetString content, + Evidence temporalEvidence) + { + this.version = new DerInteger(1); + this.dataUri = dataUri; + this.metaData = metaData; + this.content = content; + this.temporalEvidence = temporalEvidence; + } + + private TimeStampedData(Asn1Sequence seq) + { + this.version = DerInteger.GetInstance(seq[0]); + + int index = 1; + if (seq[index] is DerIA5String) + { + this.dataUri = DerIA5String.GetInstance(seq[index++]); + } + if (seq[index] is MetaData || seq[index] is Asn1Sequence) + { + this.metaData = MetaData.GetInstance(seq[index++]); + } + if (seq[index] is Asn1OctetString) + { + this.content = Asn1OctetString.GetInstance(seq[index++]); + } + this.temporalEvidence = Evidence.GetInstance(seq[index]); + } + + public static TimeStampedData GetInstance(object obj) + { + if (obj is TimeStampedData) + return (TimeStampedData)obj; + + if (obj != null) + return new TimeStampedData(Asn1Sequence.GetInstance(obj)); + + return null; + } + + public virtual DerIA5String DataUri + { + get { return dataUri; } + } + + public MetaData MetaData + { + get { return metaData; } + } + + public Asn1OctetString Content + { + get { return content; } + } + + public Evidence TemporalEvidence + { + get { return temporalEvidence; } + } + + /** + *
+		 * TimeStampedData ::= SEQUENCE {
+		 *   version              INTEGER { v1(1) },
+		 *   dataUri              IA5String OPTIONAL,
+		 *   metaData             MetaData OPTIONAL,
+		 *   content              OCTET STRING OPTIONAL,
+		 *   temporalEvidence     Evidence
+		 * }
+		 * 
+ * @return + */ + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(version); + v.AddOptional(dataUri, metaData, content); + v.Add(temporalEvidence); + return new BerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cms/TimeStampedDataParser.cs b/bc-sharp-crypto/src/asn1/cms/TimeStampedDataParser.cs new file mode 100644 index 0000000..90307bf --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cms/TimeStampedDataParser.cs @@ -0,0 +1,76 @@ +using System; + +namespace Org.BouncyCastle.Asn1.Cms +{ + public class TimeStampedDataParser + { + private DerInteger version; + private DerIA5String dataUri; + private MetaData metaData; + private Asn1OctetStringParser content; + private Evidence temporalEvidence; + private Asn1SequenceParser parser; + + private TimeStampedDataParser(Asn1SequenceParser parser) + { + this.parser = parser; + this.version = DerInteger.GetInstance(parser.ReadObject()); + + Asn1Object obj = parser.ReadObject().ToAsn1Object(); + + if (obj is DerIA5String) + { + this.dataUri = DerIA5String.GetInstance(obj); + obj = parser.ReadObject().ToAsn1Object(); + } + + if (//obj is MetaData || + obj is Asn1SequenceParser) + { + this.metaData = MetaData.GetInstance(obj.ToAsn1Object()); + obj = parser.ReadObject().ToAsn1Object(); + } + + if (obj is Asn1OctetStringParser) + { + this.content = (Asn1OctetStringParser)obj; + } + } + + public static TimeStampedDataParser GetInstance(object obj) + { + if (obj is Asn1Sequence) + return new TimeStampedDataParser(((Asn1Sequence)obj).Parser); + + if (obj is Asn1SequenceParser) + return new TimeStampedDataParser((Asn1SequenceParser)obj); + + return null; + } + + public virtual DerIA5String DataUri + { + get { return dataUri; } + } + + public virtual MetaData MetaData + { + get { return metaData; } + } + + public virtual Asn1OctetStringParser Content + { + get { return content; } + } + + public virtual Evidence GetTemporalEvidence() + { + if (temporalEvidence == null) + { + temporalEvidence = Evidence.GetInstance(parser.ReadObject().ToAsn1Object()); + } + + return temporalEvidence; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cms/ecc/MQVuserKeyingMaterial.cs b/bc-sharp-crypto/src/asn1/cms/ecc/MQVuserKeyingMaterial.cs new file mode 100644 index 0000000..dc4ac1a --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cms/ecc/MQVuserKeyingMaterial.cs @@ -0,0 +1,105 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Cms.Ecc +{ + public class MQVuserKeyingMaterial + : 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[0]); + + if (seq.Count > 1) + { + this.addedukm = Asn1OctetString.GetInstance( + (Asn1TaggedObject)seq[1], true); + } + } + + /** + * return an AuthEnvelopedData object from a tagged object. + * + * @param obj the tagged object holding the object we want. + * @param isExplicit true if the object is meant to be explicitly + * tagged false otherwise. + * @throws ArgumentException if the object held by the + * tagged object cannot be converted. + */ + public static MQVuserKeyingMaterial GetInstance( + Asn1TaggedObject obj, + bool isExplicit) + { + return GetInstance(Asn1Sequence.GetInstance(obj, isExplicit)); + } + + /** + * return an AuthEnvelopedData object from the given object. + * + * @param obj the object we want converted. + * @throws ArgumentException if the object cannot be converted. + */ + public static MQVuserKeyingMaterial GetInstance( + object obj) + { + if (obj == null || obj is MQVuserKeyingMaterial) + { + return (MQVuserKeyingMaterial)obj; + } + + if (obj is Asn1Sequence) + { + return new MQVuserKeyingMaterial((Asn1Sequence)obj); + } + + throw new ArgumentException("Invalid MQVuserKeyingMaterial: " + Platform.GetTypeName(obj)); + } + + public OriginatorPublicKey EphemeralPublicKey + { + get { return ephemeralPublicKey; } + } + + public Asn1OctetString AddedUkm + { + get { return addedukm; } + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *
+		* MQVuserKeyingMaterial ::= SEQUENCE {
+		*   ephemeralPublicKey OriginatorPublicKey,
+		*   addedukm [0] EXPLICIT UserKeyingMaterial OPTIONAL  }
+		* 
+ */ + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(ephemeralPublicKey); + + if (addedukm != null) + { + v.Add(new DerTaggedObject(true, 0, addedukm)); + } + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/crmf/AttributeTypeAndValue.cs b/bc-sharp-crypto/src/asn1/crmf/AttributeTypeAndValue.cs new file mode 100644 index 0000000..0a4b5bd --- /dev/null +++ b/bc-sharp-crypto/src/asn1/crmf/AttributeTypeAndValue.cs @@ -0,0 +1,68 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Crmf +{ + public class AttributeTypeAndValue + : Asn1Encodable + { + private readonly DerObjectIdentifier type; + private readonly Asn1Encodable value; + + private AttributeTypeAndValue(Asn1Sequence seq) + { + type = (DerObjectIdentifier)seq[0]; + value = (Asn1Encodable)seq[1]; + } + + public static AttributeTypeAndValue GetInstance(object obj) + { + if (obj is AttributeTypeAndValue) + return (AttributeTypeAndValue)obj; + + if (obj is Asn1Sequence) + return new AttributeTypeAndValue((Asn1Sequence)obj); + + throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj"); + } + + public AttributeTypeAndValue( + String oid, + Asn1Encodable value) + : this(new DerObjectIdentifier(oid), value) + { + } + + public AttributeTypeAndValue( + DerObjectIdentifier type, + Asn1Encodable value) + { + this.type = type; + this.value = value; + } + + public virtual DerObjectIdentifier Type + { + get { return type; } + } + + public virtual Asn1Encodable Value + { + get { return value; } + } + + /** + *
+         * AttributeTypeAndValue ::= SEQUENCE {
+         *           type         OBJECT IDENTIFIER,
+         *           value        ANY DEFINED BY type }
+         * 
+ * @return a basic ASN.1 object representation. + */ + public override Asn1Object ToAsn1Object() + { + return new DerSequence(type, value); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/crmf/CertId.cs b/bc-sharp-crypto/src/asn1/crmf/CertId.cs new file mode 100644 index 0000000..f0cc946 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/crmf/CertId.cs @@ -0,0 +1,59 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Crmf +{ + public class CertId + : Asn1Encodable + { + private readonly GeneralName issuer; + private readonly DerInteger serialNumber; + + private CertId(Asn1Sequence seq) + { + issuer = GeneralName.GetInstance(seq[0]); + serialNumber = DerInteger.GetInstance(seq[1]); + } + + public static CertId GetInstance(object obj) + { + if (obj is CertId) + return (CertId)obj; + + if (obj is Asn1Sequence) + return new CertId((Asn1Sequence)obj); + + throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj"); + } + + public static CertId GetInstance(Asn1TaggedObject obj, bool isExplicit) + { + return GetInstance(Asn1Sequence.GetInstance(obj, isExplicit)); + } + + public virtual GeneralName Issuer + { + get { return issuer; } + } + + public virtual DerInteger SerialNumber + { + get { return serialNumber; } + } + + /** + *
+         * CertId ::= SEQUENCE {
+         *                 issuer           GeneralName,
+         *                 serialNumber     INTEGER }
+         * 
+ * @return a basic ASN.1 object representation. + */ + public override Asn1Object ToAsn1Object() + { + return new DerSequence(issuer, serialNumber); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/crmf/CertReqMessages.cs b/bc-sharp-crypto/src/asn1/crmf/CertReqMessages.cs new file mode 100644 index 0000000..422950b --- /dev/null +++ b/bc-sharp-crypto/src/asn1/crmf/CertReqMessages.cs @@ -0,0 +1,54 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Crmf +{ + public class CertReqMessages + : Asn1Encodable + { + private readonly Asn1Sequence content; + + private CertReqMessages(Asn1Sequence seq) + { + content = seq; + } + + public static CertReqMessages GetInstance(object obj) + { + if (obj is CertReqMessages) + return (CertReqMessages)obj; + + if (obj is Asn1Sequence) + return new CertReqMessages((Asn1Sequence)obj); + + throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj"); + } + + public CertReqMessages(params CertReqMsg[] msgs) + { + content = new DerSequence(msgs); + } + + public virtual CertReqMsg[] ToCertReqMsgArray() + { + CertReqMsg[] result = new CertReqMsg[content.Count]; + for (int i = 0; i != result.Length; ++i) + { + result[i] = CertReqMsg.GetInstance(content[i]); + } + return result; + } + + /** + *
+         * CertReqMessages ::= SEQUENCE SIZE (1..MAX) OF CertReqMsg
+         * 
+ * @return a basic ASN.1 object representation. + */ + public override Asn1Object ToAsn1Object() + { + return content; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/crmf/CertReqMsg.cs b/bc-sharp-crypto/src/asn1/crmf/CertReqMsg.cs new file mode 100644 index 0000000..03ce32d --- /dev/null +++ b/bc-sharp-crypto/src/asn1/crmf/CertReqMsg.cs @@ -0,0 +1,112 @@ +using System; + +namespace Org.BouncyCastle.Asn1.Crmf +{ + public class CertReqMsg + : Asn1Encodable + { + private readonly CertRequest certReq; + private readonly ProofOfPossession popo; + private readonly Asn1Sequence regInfo; + + private CertReqMsg(Asn1Sequence seq) + { + certReq = CertRequest.GetInstance(seq[0]); + + for (int pos = 1; pos < seq.Count; ++pos) + { + object o = seq[pos]; + + if (o is Asn1TaggedObject || o is ProofOfPossession) + { + popo = ProofOfPossession.GetInstance(o); + } + else + { + regInfo = Asn1Sequence.GetInstance(o); + } + } + } + + public static CertReqMsg GetInstance(object obj) + { + if (obj is CertReqMsg) + return (CertReqMsg)obj; + + if (obj != null) + return new CertReqMsg(Asn1Sequence.GetInstance(obj)); + + return null; + } + + public static CertReqMsg GetInstance( + Asn1TaggedObject obj, + bool isExplicit) + { + return GetInstance(Asn1Sequence.GetInstance(obj, isExplicit)); + } + + /** + * Creates a new CertReqMsg. + * @param certReq CertRequest + * @param popo may be null + * @param regInfo may be null + */ + public CertReqMsg( + CertRequest certReq, + ProofOfPossession popo, + AttributeTypeAndValue[] regInfo) + { + if (certReq == null) + throw new ArgumentNullException("certReq"); + + this.certReq = certReq; + this.popo = popo; + + if (regInfo != null) + { + this.regInfo = new DerSequence(regInfo); + } + } + + public virtual CertRequest CertReq + { + get { return certReq; } + } + + public virtual ProofOfPossession Popo + { + get { return popo; } + } + + public virtual AttributeTypeAndValue[] GetRegInfo() + { + if (regInfo == null) + return null; + + AttributeTypeAndValue[] results = new AttributeTypeAndValue[regInfo.Count]; + for (int i = 0; i != results.Length; ++i) + { + results[i] = AttributeTypeAndValue.GetInstance(regInfo[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 override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(certReq); + v.AddOptional(popo, regInfo); + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/crmf/CertRequest.cs b/bc-sharp-crypto/src/asn1/crmf/CertRequest.cs new file mode 100644 index 0000000..625a9b5 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/crmf/CertRequest.cs @@ -0,0 +1,82 @@ +using System; + +namespace Org.BouncyCastle.Asn1.Crmf +{ + public class CertRequest + : Asn1Encodable + { + private readonly DerInteger certReqId; + private readonly CertTemplate certTemplate; + private readonly Controls controls; + + private CertRequest(Asn1Sequence seq) + { + certReqId = DerInteger.GetInstance(seq[0]); + certTemplate = CertTemplate.GetInstance(seq[1]); + if (seq.Count > 2) + { + controls = Controls.GetInstance(seq[2]); + } + } + + public static CertRequest GetInstance(object obj) + { + if (obj is CertRequest) + return (CertRequest)obj; + + if (obj != null) + return new CertRequest(Asn1Sequence.GetInstance(obj)); + + return null; + } + + public CertRequest( + int certReqId, + CertTemplate certTemplate, + Controls controls) + : this(new DerInteger(certReqId), certTemplate, controls) + { + } + + public CertRequest( + DerInteger certReqId, + CertTemplate certTemplate, + Controls controls) + { + this.certReqId = certReqId; + this.certTemplate = certTemplate; + this.controls = controls; + } + + public virtual DerInteger CertReqID + { + get { return certReqId; } + } + + public virtual CertTemplate CertTemplate + { + get { return certTemplate; } + } + + public virtual Controls Controls + { + get { 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 override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(certReqId, certTemplate); + v.AddOptional(controls); + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/crmf/CertTemplate.cs b/bc-sharp-crypto/src/asn1/crmf/CertTemplate.cs new file mode 100644 index 0000000..3de9f1d --- /dev/null +++ b/bc-sharp-crypto/src/asn1/crmf/CertTemplate.cs @@ -0,0 +1,149 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; + +namespace Org.BouncyCastle.Asn1.Crmf +{ + public class CertTemplate + : Asn1Encodable + { + private readonly Asn1Sequence seq; + + private readonly DerInteger version; + private readonly DerInteger serialNumber; + private readonly AlgorithmIdentifier signingAlg; + private readonly X509Name issuer; + private readonly OptionalValidity validity; + private readonly X509Name subject; + private readonly SubjectPublicKeyInfo publicKey; + private readonly DerBitString issuerUID; + private readonly DerBitString subjectUID; + private readonly X509Extensions extensions; + + private CertTemplate(Asn1Sequence seq) + { + this.seq = seq; + + foreach (Asn1TaggedObject tObj in seq) + { + switch (tObj.TagNo) + { + 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 ArgumentException("unknown tag: " + tObj.TagNo, "seq"); + } + } + } + + public static CertTemplate GetInstance(object obj) + { + if (obj is CertTemplate) + return (CertTemplate)obj; + + if (obj != null) + return new CertTemplate(Asn1Sequence.GetInstance(obj)); + + return null; + } + + public virtual int Version + { + get { return version.Value.IntValue; } + } + + public virtual DerInteger SerialNumber + { + get { return serialNumber; } + } + + public virtual AlgorithmIdentifier SigningAlg + { + get { return signingAlg; } + } + + public virtual X509Name Issuer + { + get { return issuer; } + } + + public virtual OptionalValidity Validity + { + get { return validity; } + } + + public virtual X509Name Subject + { + get { return subject; } + } + + public virtual SubjectPublicKeyInfo PublicKey + { + get { return publicKey; } + } + + public virtual DerBitString IssuerUID + { + get { return issuerUID; } + } + + public virtual DerBitString SubjectUID + { + get { return subjectUID; } + } + + public virtual X509Extensions Extensions + { + get { return extensions; } + } + + /** + *
+         *  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 override Asn1Object ToAsn1Object() + { + return seq; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/crmf/CertTemplateBuilder.cs b/bc-sharp-crypto/src/asn1/crmf/CertTemplateBuilder.cs new file mode 100644 index 0000000..51c73c4 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/crmf/CertTemplateBuilder.cs @@ -0,0 +1,125 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; + +namespace Org.BouncyCastle.Asn1.Crmf +{ + public class CertTemplateBuilder + { + 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; + + /** Sets the X.509 version. Note: for X509v3, use 2 here. */ + public virtual CertTemplateBuilder SetVersion(int ver) + { + version = new DerInteger(ver); + return this; + } + + public virtual CertTemplateBuilder SetSerialNumber(DerInteger ser) + { + serialNumber = ser; + return this; + } + + public virtual CertTemplateBuilder SetSigningAlg(AlgorithmIdentifier aid) + { + signingAlg = aid; + return this; + } + + public virtual CertTemplateBuilder SetIssuer(X509Name name) + { + issuer = name; + return this; + } + + public virtual CertTemplateBuilder SetValidity(OptionalValidity v) + { + validity = v; + return this; + } + + public virtual CertTemplateBuilder SetSubject(X509Name name) + { + subject = name; + return this; + } + + public virtual CertTemplateBuilder SetPublicKey(SubjectPublicKeyInfo spki) + { + publicKey = spki; + return this; + } + + /** Sets the issuer unique ID (deprecated in X.509v3) */ + public virtual CertTemplateBuilder SetIssuerUID(DerBitString uid) + { + issuerUID = uid; + return this; + } + + /** Sets the subject unique ID (deprecated in X.509v3) */ + public virtual CertTemplateBuilder SetSubjectUID(DerBitString uid) + { + subjectUID = uid; + return this; + } + + public virtual CertTemplateBuilder SetExtensions(X509Extensions extens) + { + extensions = extens; + return this; + } + + /** + *
+         *  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 virtual CertTemplate Build() + { + Asn1EncodableVector v = new Asn1EncodableVector(); + + AddOptional(v, 0, false, version); + AddOptional(v, 1, false, serialNumber); + AddOptional(v, 2, false, signingAlg); + AddOptional(v, 3, true, issuer); // CHOICE + AddOptional(v, 4, false, validity); + AddOptional(v, 5, true, subject); // CHOICE + AddOptional(v, 6, false, publicKey); + AddOptional(v, 7, false, issuerUID); + AddOptional(v, 8, false, subjectUID); + AddOptional(v, 9, false, extensions); + + return CertTemplate.GetInstance(new DerSequence(v)); + } + + private void AddOptional(Asn1EncodableVector v, int tagNo, bool isExplicit, Asn1Encodable obj) + { + if (obj != null) + { + v.Add(new DerTaggedObject(isExplicit, tagNo, obj)); + } + } + } +} diff --git a/bc-sharp-crypto/src/asn1/crmf/Controls.cs b/bc-sharp-crypto/src/asn1/crmf/Controls.cs new file mode 100644 index 0000000..e8b9f3d --- /dev/null +++ b/bc-sharp-crypto/src/asn1/crmf/Controls.cs @@ -0,0 +1,54 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Crmf +{ + public class Controls + : Asn1Encodable + { + private readonly Asn1Sequence content; + + private Controls(Asn1Sequence seq) + { + content = seq; + } + + public static Controls GetInstance(object obj) + { + if (obj is Controls) + return (Controls)obj; + + if (obj is Asn1Sequence) + return new Controls((Asn1Sequence)obj); + + throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj"); + } + + public Controls(params AttributeTypeAndValue[] atvs) + { + content = new DerSequence(atvs); + } + + public virtual AttributeTypeAndValue[] ToAttributeTypeAndValueArray() + { + AttributeTypeAndValue[] result = new AttributeTypeAndValue[content.Count]; + for (int i = 0; i != result.Length; ++i) + { + result[i] = AttributeTypeAndValue.GetInstance(content[i]); + } + return result; + } + + /** + *
+         * Controls  ::= SEQUENCE SIZE(1..MAX) OF AttributeTypeAndValue
+         * 
+ * @return a basic ASN.1 object representation. + */ + public override Asn1Object ToAsn1Object() + { + return content; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/crmf/CrmfObjectIdentifiers.cs b/bc-sharp-crypto/src/asn1/crmf/CrmfObjectIdentifiers.cs new file mode 100644 index 0000000..eaa1f7b --- /dev/null +++ b/bc-sharp-crypto/src/asn1/crmf/CrmfObjectIdentifiers.cs @@ -0,0 +1,23 @@ +using System; + +using Org.BouncyCastle.Asn1.Pkcs; + +namespace Org.BouncyCastle.Asn1.Crmf +{ + public abstract class CrmfObjectIdentifiers + { + public static readonly DerObjectIdentifier id_pkix = new DerObjectIdentifier("1.3.6.1.5.5.7"); + + // arc for Internet X.509 PKI protocols and their components + + public static readonly DerObjectIdentifier id_pkip = id_pkix.Branch("5"); + + public static readonly DerObjectIdentifier id_regCtrl = id_pkip.Branch("1"); + public static readonly DerObjectIdentifier id_regCtrl_regToken = id_regCtrl.Branch("1"); + public static readonly DerObjectIdentifier id_regCtrl_authenticator = id_regCtrl.Branch("2"); + public static readonly DerObjectIdentifier id_regCtrl_pkiPublicationInfo = id_regCtrl.Branch("3"); + public static readonly DerObjectIdentifier id_regCtrl_pkiArchiveOptions = id_regCtrl.Branch("4"); + + public static readonly DerObjectIdentifier id_ct_encKeyWithID = new DerObjectIdentifier(PkcsObjectIdentifiers.IdCT + ".21"); + } +} diff --git a/bc-sharp-crypto/src/asn1/crmf/EncKeyWithID.cs b/bc-sharp-crypto/src/asn1/crmf/EncKeyWithID.cs new file mode 100644 index 0000000..6de56fa --- /dev/null +++ b/bc-sharp-crypto/src/asn1/crmf/EncKeyWithID.cs @@ -0,0 +1,103 @@ +using System; + +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.X509; + +namespace Org.BouncyCastle.Asn1.Crmf +{ + public class EncKeyWithID + : Asn1Encodable + { + private readonly PrivateKeyInfo privKeyInfo; + private readonly Asn1Encodable identifier; + + public static EncKeyWithID GetInstance(object obj) + { + if (obj is EncKeyWithID) + return (EncKeyWithID)obj; + + if (obj != null) + return new EncKeyWithID(Asn1Sequence.GetInstance(obj)); + + return null; + } + + private EncKeyWithID(Asn1Sequence seq) + { + this.privKeyInfo = PrivateKeyInfo.GetInstance(seq[0]); + + if (seq.Count > 1) + { + if (!(seq[1] is DerUtf8String)) + { + this.identifier = GeneralName.GetInstance(seq[1]); + } + else + { + this.identifier = (Asn1Encodable)seq[1]; + } + } + else + { + this.identifier = null; + } + } + + public EncKeyWithID(PrivateKeyInfo privKeyInfo) + { + this.privKeyInfo = privKeyInfo; + this.identifier = null; + } + + public EncKeyWithID(PrivateKeyInfo privKeyInfo, DerUtf8String str) + { + this.privKeyInfo = privKeyInfo; + this.identifier = str; + } + + public EncKeyWithID(PrivateKeyInfo privKeyInfo, GeneralName generalName) + { + this.privKeyInfo = privKeyInfo; + this.identifier = generalName; + } + + public virtual PrivateKeyInfo PrivateKey + { + get { return privKeyInfo; } + } + + public virtual bool HasIdentifier + { + get { return identifier != null; } + } + + public virtual bool IsIdentifierUtf8String + { + get { return identifier is DerUtf8String; } + } + + public virtual Asn1Encodable Identifier + { + get { return identifier; } + } + + /** + *
+         * EncKeyWithID ::= SEQUENCE {
+         *      privateKey           PrivateKeyInfo,
+         *      identifier CHOICE {
+         *         string               UTF8String,
+         *         generalName          GeneralName
+         *     } OPTIONAL
+         * }
+         * 
+ * @return + */ + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(privKeyInfo); + v.AddOptional(identifier); + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/crmf/EncryptedKey.cs b/bc-sharp-crypto/src/asn1/crmf/EncryptedKey.cs new file mode 100644 index 0000000..850fbd2 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/crmf/EncryptedKey.cs @@ -0,0 +1,78 @@ +using System; + +using Org.BouncyCastle.Asn1.Cms; + +namespace Org.BouncyCastle.Asn1.Crmf +{ + public class EncryptedKey + : Asn1Encodable, IAsn1Choice + { + private readonly EnvelopedData envelopedData; + private readonly EncryptedValue encryptedValue; + + public static EncryptedKey GetInstance(object o) + { + if (o is EncryptedKey) + { + return (EncryptedKey)o; + } + else if (o is Asn1TaggedObject) + { + return new EncryptedKey(EnvelopedData.GetInstance((Asn1TaggedObject)o, false)); + } + else if (o is EncryptedValue) + { + return new EncryptedKey((EncryptedValue)o); + } + else + { + return new EncryptedKey(EncryptedValue.GetInstance(o)); + } + } + + public EncryptedKey(EnvelopedData envelopedData) + { + this.envelopedData = envelopedData; + } + + public EncryptedKey(EncryptedValue encryptedValue) + { + this.encryptedValue = encryptedValue; + } + + public virtual bool IsEncryptedValue + { + get { return encryptedValue != null; } + } + + public virtual Asn1Encodable Value + { + get + { + if (encryptedValue != null) + return encryptedValue; + + return envelopedData; + } + } + + /** + *
+         *    EncryptedKey ::= CHOICE {
+         *        encryptedValue        EncryptedValue, -- deprecated
+         *        envelopedData     [0] EnvelopedData }
+         *        -- The encrypted private key MUST be placed in the envelopedData
+         *        -- encryptedContentInfo encryptedContent OCTET STRING.
+         * 
+ */ + public override Asn1Object ToAsn1Object() + { + if (encryptedValue != null) + { + return encryptedValue.ToAsn1Object(); + } + + return new DerTaggedObject(false, 0, envelopedData); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/crmf/EncryptedValue.cs b/bc-sharp-crypto/src/asn1/crmf/EncryptedValue.cs new file mode 100644 index 0000000..83122e2 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/crmf/EncryptedValue.cs @@ -0,0 +1,154 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; + +namespace Org.BouncyCastle.Asn1.Crmf +{ + public class EncryptedValue + : Asn1Encodable + { + private readonly AlgorithmIdentifier intendedAlg; + private readonly AlgorithmIdentifier symmAlg; + private readonly DerBitString encSymmKey; + private readonly AlgorithmIdentifier keyAlg; + private readonly Asn1OctetString valueHint; + private readonly DerBitString encValue; + + private EncryptedValue(Asn1Sequence seq) + { + int index = 0; + while (seq[index] is Asn1TaggedObject) + { + Asn1TaggedObject tObj = (Asn1TaggedObject)seq[index]; + + switch (tObj.TagNo) + { + 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[index]); + } + + public static EncryptedValue GetInstance(object obj) + { + if (obj is EncryptedValue) + return (EncryptedValue)obj; + + if (obj != null) + return new EncryptedValue(Asn1Sequence.GetInstance(obj)); + + return null; + } + + public EncryptedValue( + AlgorithmIdentifier intendedAlg, + AlgorithmIdentifier symmAlg, + DerBitString encSymmKey, + AlgorithmIdentifier keyAlg, + Asn1OctetString valueHint, + DerBitString encValue) + { + if (encValue == null) + { + throw new ArgumentNullException("encValue"); + } + + this.intendedAlg = intendedAlg; + this.symmAlg = symmAlg; + this.encSymmKey = encSymmKey; + this.keyAlg = keyAlg; + this.valueHint = valueHint; + this.encValue = encValue; + } + + public virtual AlgorithmIdentifier IntendedAlg + { + get { return intendedAlg; } + } + + public virtual AlgorithmIdentifier SymmAlg + { + get { return symmAlg; } + } + + public virtual DerBitString EncSymmKey + { + get { return encSymmKey; } + } + + public virtual AlgorithmIdentifier KeyAlg + { + get { return keyAlg; } + } + + public virtual Asn1OctetString ValueHint + { + get { return valueHint; } + } + + public virtual DerBitString EncValue + { + get { return encValue; } + } + + /** + *
+         * 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 override Asn1Object 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/bc-sharp-crypto/src/asn1/crmf/OptionalValidity.cs b/bc-sharp-crypto/src/asn1/crmf/OptionalValidity.cs new file mode 100644 index 0000000..d1a0f7f --- /dev/null +++ b/bc-sharp-crypto/src/asn1/crmf/OptionalValidity.cs @@ -0,0 +1,71 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; + +namespace Org.BouncyCastle.Asn1.Crmf +{ + public class OptionalValidity + : Asn1Encodable + { + private readonly Time notBefore; + private readonly Time notAfter; + + private OptionalValidity(Asn1Sequence seq) + { + foreach (Asn1TaggedObject tObj in seq) + { + if (tObj.TagNo == 0) + { + notBefore = Time.GetInstance(tObj, true); + } + else + { + notAfter = Time.GetInstance(tObj, true); + } + } + } + + public static OptionalValidity GetInstance(object obj) + { + if (obj == null || obj is OptionalValidity) + return (OptionalValidity)obj; + + return new OptionalValidity(Asn1Sequence.GetInstance(obj)); + } + + public virtual Time NotBefore + { + get { return notBefore; } + } + + public virtual Time NotAfter + { + get { return notAfter; } + } + + /** + *
+         * OptionalValidity ::= SEQUENCE {
+         *                        notBefore  [0] Time OPTIONAL,
+         *                        notAfter   [1] Time OPTIONAL } --at least one MUST be present
+         * 
+ * @return a basic ASN.1 object representation. + */ + public override Asn1Object 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/bc-sharp-crypto/src/asn1/crmf/PKIArchiveOptions.cs b/bc-sharp-crypto/src/asn1/crmf/PKIArchiveOptions.cs new file mode 100644 index 0000000..1813d87 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/crmf/PKIArchiveOptions.cs @@ -0,0 +1,107 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Crmf +{ + public class PkiArchiveOptions + : Asn1Encodable, IAsn1Choice + { + public const int encryptedPrivKey = 0; + public const int keyGenParameters = 1; + public const int archiveRemGenPrivKey = 2; + + private readonly Asn1Encodable value; + + public static PkiArchiveOptions GetInstance(object obj) + { + if (obj is PkiArchiveOptions) + return (PkiArchiveOptions)obj; + + if (obj is Asn1TaggedObject) + return new PkiArchiveOptions((Asn1TaggedObject)obj); + + throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj"); + } + + private PkiArchiveOptions(Asn1TaggedObject tagged) + { + switch (tagged.TagNo) + { + case encryptedPrivKey: + value = EncryptedKey.GetInstance(tagged.GetObject()); + break; + case keyGenParameters: + value = Asn1OctetString.GetInstance(tagged, false); + break; + case archiveRemGenPrivKey: + value = DerBoolean.GetInstance(tagged, false); + break; + default: + throw new ArgumentException("unknown tag number: " + tagged.TagNo, "tagged"); + } + } + + public PkiArchiveOptions(EncryptedKey encKey) + { + this.value = encKey; + } + + public PkiArchiveOptions(Asn1OctetString keyGenParameters) + { + this.value = keyGenParameters; + } + + public PkiArchiveOptions(bool archiveRemGenPrivKey) + { + this.value = DerBoolean.GetInstance(archiveRemGenPrivKey); + } + + public virtual int Type + { + get + { + if (value is EncryptedKey) + return encryptedPrivKey; + + if (value is Asn1OctetString) + return keyGenParameters; + + return archiveRemGenPrivKey; + } + } + + public virtual Asn1Encodable Value + { + get { return value; } + } + + /** + *
+         *  PkiArchiveOptions ::= CHOICE {
+         *      encryptedPrivKey     [0] EncryptedKey,
+         *      -- the actual value of the private key
+         *      keyGenParameters     [1] KeyGenParameters,
+         *      -- parameters which allow the private key to be re-generated
+         *      archiveRemGenPrivKey [2] BOOLEAN }
+         *      -- set to TRUE if sender wishes receiver to archive the private
+         *      -- key of a key pair that the receiver generates in response to
+         *      -- this request; set to FALSE if no archival is desired.
+         * 
+ */ + public override Asn1Object ToAsn1Object() + { + if (value is EncryptedKey) + { + return new DerTaggedObject(true, encryptedPrivKey, value); // choice + } + + if (value is Asn1OctetString) + { + return new DerTaggedObject(false, keyGenParameters, value); + } + + return new DerTaggedObject(false, archiveRemGenPrivKey, value); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/crmf/PKIPublicationInfo.cs b/bc-sharp-crypto/src/asn1/crmf/PKIPublicationInfo.cs new file mode 100644 index 0000000..a7d2bc6 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/crmf/PKIPublicationInfo.cs @@ -0,0 +1,66 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Crmf +{ + public class PkiPublicationInfo + : Asn1Encodable + { + private readonly DerInteger action; + private readonly Asn1Sequence pubInfos; + + private PkiPublicationInfo(Asn1Sequence seq) + { + action = DerInteger.GetInstance(seq[0]); + pubInfos = Asn1Sequence.GetInstance(seq[1]); + } + + public static PkiPublicationInfo GetInstance(object obj) + { + if (obj is PkiPublicationInfo) + return (PkiPublicationInfo)obj; + + if (obj is Asn1Sequence) + return new PkiPublicationInfo((Asn1Sequence)obj); + + throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj"); + } + + public virtual DerInteger Action + { + get { return action; } + } + + public virtual SinglePubInfo[] GetPubInfos() + { + if (pubInfos == null) + return null; + + SinglePubInfo[] results = new SinglePubInfo[pubInfos.Count]; + for (int i = 0; i != results.Length; ++i) + { + results[i] = SinglePubInfo.GetInstance(pubInfos[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 override Asn1Object ToAsn1Object() + { + return new DerSequence(action, pubInfos); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/crmf/PKMacValue.cs b/bc-sharp-crypto/src/asn1/crmf/PKMacValue.cs new file mode 100644 index 0000000..e104c08 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/crmf/PKMacValue.cs @@ -0,0 +1,90 @@ +using System; + +using Org.BouncyCastle.Asn1.Cmp; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Crmf +{ + /** + * Password-based MAC value for use with POPOSigningKeyInput. + */ + public class PKMacValue + : Asn1Encodable + { + private readonly AlgorithmIdentifier algID; + private readonly DerBitString macValue; + + private PKMacValue(Asn1Sequence seq) + { + this.algID = AlgorithmIdentifier.GetInstance(seq[0]); + this.macValue = DerBitString.GetInstance(seq[1]); + } + + public static PKMacValue GetInstance(object obj) + { + if (obj is PKMacValue) + return (PKMacValue)obj; + + if (obj is Asn1Sequence) + return new PKMacValue((Asn1Sequence)obj); + + throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj"); + } + + public static PKMacValue GetInstance(Asn1TaggedObject obj, bool isExplicit) + { + return GetInstance(Asn1Sequence.GetInstance(obj, isExplicit)); + } + + /** + * Creates a new PKMACValue. + * @param params parameters for password-based MAC + * @param value MAC of the DER-encoded SubjectPublicKeyInfo + */ + public PKMacValue( + PbmParameter pbmParams, + DerBitString macValue) + : this(new AlgorithmIdentifier(CmpObjectIdentifiers.passwordBasedMac, pbmParams), macValue) + { + } + + /** + * Creates a new PKMACValue. + * @param aid CMPObjectIdentifiers.passwordBasedMAC, with PBMParameter + * @param value MAC of the DER-encoded SubjectPublicKeyInfo + */ + public PKMacValue( + AlgorithmIdentifier algID, + DerBitString macValue) + { + this.algID = algID; + this.macValue = macValue; + } + + public virtual AlgorithmIdentifier AlgID + { + get { return algID; } + } + + public virtual DerBitString MacValue + { + get { return macValue; } + } + + /** + *
+         * PKMACValue ::= SEQUENCE {
+         *      algId  AlgorithmIdentifier,
+         *      -- algorithm value shall be PasswordBasedMac 1.2.840.113533.7.66.13
+         *      -- parameter value is PBMParameter
+         *      value  BIT STRING }
+         * 
+ * @return a basic ASN.1 object representation. + */ + public override Asn1Object ToAsn1Object() + { + return new DerSequence(algID, macValue); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/crmf/PopoPrivKey.cs b/bc-sharp-crypto/src/asn1/crmf/PopoPrivKey.cs new file mode 100644 index 0000000..0cedc51 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/crmf/PopoPrivKey.cs @@ -0,0 +1,84 @@ +using System; + +using Org.BouncyCastle.Asn1.Cms; + +namespace Org.BouncyCastle.Asn1.Crmf +{ + public class PopoPrivKey + : Asn1Encodable, IAsn1Choice + { + public const int thisMessage = 0; + public const int subsequentMessage = 1; + public const int dhMAC = 2; + public const int agreeMAC = 3; + public const int encryptedKey = 4; + + private readonly int tagNo; + private readonly Asn1Encodable obj; + + private PopoPrivKey(Asn1TaggedObject obj) + { + this.tagNo = obj.TagNo; + + switch (tagNo) + { + case thisMessage: + this.obj = DerBitString.GetInstance(obj, false); + break; + case subsequentMessage: + this.obj = SubsequentMessage.ValueOf(DerInteger.GetInstance(obj, false).Value.IntValue); + break; + case dhMAC: + this.obj = DerBitString.GetInstance(obj, false); + break; + case agreeMAC: + this.obj = PKMacValue.GetInstance(obj, false); + break; + case encryptedKey: + this.obj = EnvelopedData.GetInstance(obj, false); + break; + default: + throw new ArgumentException("unknown tag in PopoPrivKey", "obj"); + } + } + + public static PopoPrivKey GetInstance(Asn1TaggedObject tagged, bool isExplicit) + { + return new PopoPrivKey(Asn1TaggedObject.GetInstance(tagged.GetObject())); + } + + public PopoPrivKey(SubsequentMessage msg) + { + this.tagNo = subsequentMessage; + this.obj = msg; + } + + public virtual int Type + { + get { return tagNo; } + } + + public virtual Asn1Encodable Value + { + get { return obj; } + } + + /** + *
+         * 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 override Asn1Object ToAsn1Object() + { + return new DerTaggedObject(false, tagNo, obj); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/crmf/PopoSigningKey.cs b/bc-sharp-crypto/src/asn1/crmf/PopoSigningKey.cs new file mode 100644 index 0000000..1c24db8 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/crmf/PopoSigningKey.cs @@ -0,0 +1,116 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Crmf +{ + public class PopoSigningKey + : Asn1Encodable + { + private readonly PopoSigningKeyInput poposkInput; + private readonly AlgorithmIdentifier algorithmIdentifier; + private readonly DerBitString signature; + + private PopoSigningKey(Asn1Sequence seq) + { + int index = 0; + + if (seq[index] is Asn1TaggedObject) + { + Asn1TaggedObject tagObj + = (Asn1TaggedObject) seq[index++]; + if (tagObj.TagNo != 0) + { + throw new ArgumentException( "Unknown PopoSigningKeyInput tag: " + tagObj.TagNo, "seq"); + } + poposkInput = PopoSigningKeyInput.GetInstance(tagObj.GetObject()); + } + algorithmIdentifier = AlgorithmIdentifier.GetInstance(seq[index++]); + signature = DerBitString.GetInstance(seq[index]); + } + + public static PopoSigningKey GetInstance(object obj) + { + if (obj is PopoSigningKey) + return (PopoSigningKey)obj; + + if (obj is Asn1Sequence) + return new PopoSigningKey((Asn1Sequence)obj); + + throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj"); + } + + public static PopoSigningKey GetInstance(Asn1TaggedObject obj, bool isExplicit) + { + return GetInstance(Asn1Sequence.GetInstance(obj, isExplicit)); + } + + /** + * Creates a new Proof of Possession object for a signing key. + * @param poposkIn the PopoSigningKeyInput structure, or null if the + * CertTemplate includes both subject and publicKey values. + * @param aid the AlgorithmIdentifier used to sign the proof of possession. + * @param signature a signature over the DER-encoded value of poposkIn, + * or the DER-encoded value of certReq if poposkIn is null. + */ + public PopoSigningKey( + PopoSigningKeyInput poposkIn, + AlgorithmIdentifier aid, + DerBitString signature) + { + this.poposkInput = poposkIn; + this.algorithmIdentifier = aid; + this.signature = signature; + } + + public virtual PopoSigningKeyInput PoposkInput + { + get { return poposkInput; } + } + + public virtual AlgorithmIdentifier AlgorithmIdentifier + { + get { return algorithmIdentifier; } + } + + public virtual DerBitString Signature + { + get { return signature; } + } + + /** + *
+         * 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 override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(); + + if (poposkInput != null) + { + v.Add(new DerTaggedObject(false, 0, poposkInput)); + } + + v.Add(algorithmIdentifier); + v.Add(signature); + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/crmf/PopoSigningKeyInput.cs b/bc-sharp-crypto/src/asn1/crmf/PopoSigningKeyInput.cs new file mode 100644 index 0000000..e43fa13 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/crmf/PopoSigningKeyInput.cs @@ -0,0 +1,116 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Crmf +{ + public class PopoSigningKeyInput + : Asn1Encodable + { + private readonly GeneralName sender; + private readonly PKMacValue publicKeyMac; + private readonly SubjectPublicKeyInfo publicKey; + + private PopoSigningKeyInput(Asn1Sequence seq) + { + Asn1Encodable authInfo = (Asn1Encodable)seq[0]; + + if (authInfo is Asn1TaggedObject) + { + Asn1TaggedObject tagObj = (Asn1TaggedObject)authInfo; + if (tagObj.TagNo != 0) + { + throw new ArgumentException("Unknown authInfo tag: " + tagObj.TagNo, "seq"); + } + sender = GeneralName.GetInstance(tagObj.GetObject()); + } + else + { + publicKeyMac = PKMacValue.GetInstance(authInfo); + } + + publicKey = SubjectPublicKeyInfo.GetInstance(seq[1]); + } + + public static PopoSigningKeyInput GetInstance(object obj) + { + if (obj is PopoSigningKeyInput) + return (PopoSigningKeyInput)obj; + + if (obj is Asn1Sequence) + return new PopoSigningKeyInput((Asn1Sequence)obj); + + throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj"); + } + + /** Creates a new PopoSigningKeyInput with sender name as authInfo. */ + public PopoSigningKeyInput( + GeneralName sender, + SubjectPublicKeyInfo spki) + { + this.sender = sender; + this.publicKey = spki; + } + + /** Creates a new PopoSigningKeyInput using password-based MAC. */ + public PopoSigningKeyInput( + PKMacValue pkmac, + SubjectPublicKeyInfo spki) + { + this.publicKeyMac = pkmac; + this.publicKey = spki; + } + + /** Returns the sender field, or null if authInfo is publicKeyMac */ + public virtual GeneralName Sender + { + get { return sender; } + } + + /** Returns the publicKeyMac field, or null if authInfo is sender */ + public virtual PKMacValue PublicKeyMac + { + get { return publicKeyMac; } + } + + public virtual SubjectPublicKeyInfo PublicKey + { + get { 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 override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(); + + if (sender != null) + { + v.Add(new DerTaggedObject(false, 0, sender)); + } + else + { + v.Add(publicKeyMac); + } + + v.Add(publicKey); + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/crmf/ProofOfPossession.cs b/bc-sharp-crypto/src/asn1/crmf/ProofOfPossession.cs new file mode 100644 index 0000000..8957169 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/crmf/ProofOfPossession.cs @@ -0,0 +1,100 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Crmf +{ + public class ProofOfPossession + : Asn1Encodable, IAsn1Choice + { + public const int TYPE_RA_VERIFIED = 0; + public const int TYPE_SIGNING_KEY = 1; + public const int TYPE_KEY_ENCIPHERMENT = 2; + public const int TYPE_KEY_AGREEMENT = 3; + + private readonly int tagNo; + private readonly Asn1Encodable obj; + + private ProofOfPossession(Asn1TaggedObject tagged) + { + tagNo = tagged.TagNo; + 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 ArgumentException("unknown tag: " + tagNo, "tagged"); + } + } + + public static ProofOfPossession GetInstance(object obj) + { + if (obj is ProofOfPossession) + return (ProofOfPossession)obj; + + if (obj is Asn1TaggedObject) + return new ProofOfPossession((Asn1TaggedObject)obj); + + throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj"); + } + + /** Creates a ProofOfPossession with type raVerified. */ + public ProofOfPossession() + { + tagNo = TYPE_RA_VERIFIED; + obj = DerNull.Instance; + } + + /** Creates a ProofOfPossession for a signing key. */ + public ProofOfPossession(PopoSigningKey Poposk) + { + tagNo = TYPE_SIGNING_KEY; + obj = Poposk; + } + + /** + * Creates a ProofOfPossession for key encipherment or agreement. + * @param type one of TYPE_KEY_ENCIPHERMENT or TYPE_KEY_AGREEMENT + */ + public ProofOfPossession(int type, PopoPrivKey privkey) + { + tagNo = type; + obj = privkey; + } + + public virtual int Type + { + get { return tagNo; } + } + + public virtual Asn1Encodable Object + { + get { 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 override Asn1Object ToAsn1Object() + { + return new DerTaggedObject(false, tagNo, obj); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/crmf/SinglePubInfo.cs b/bc-sharp-crypto/src/asn1/crmf/SinglePubInfo.cs new file mode 100644 index 0000000..5205ce3 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/crmf/SinglePubInfo.cs @@ -0,0 +1,59 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Crmf +{ + public class SinglePubInfo + : Asn1Encodable + { + private readonly DerInteger pubMethod; + private readonly GeneralName pubLocation; + + private SinglePubInfo(Asn1Sequence seq) + { + pubMethod = DerInteger.GetInstance(seq[0]); + + if (seq.Count == 2) + { + pubLocation = GeneralName.GetInstance(seq[1]); + } + } + + public static SinglePubInfo GetInstance(object obj) + { + if (obj is SinglePubInfo) + return (SinglePubInfo)obj; + + if (obj is Asn1Sequence) + return new SinglePubInfo((Asn1Sequence)obj); + + throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj"); + } + + public virtual GeneralName PubLocation + { + get { 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 override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(pubMethod); + v.AddOptional(pubLocation); + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/crmf/SubsequentMessage.cs b/bc-sharp-crypto/src/asn1/crmf/SubsequentMessage.cs new file mode 100644 index 0000000..cc1c164 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/crmf/SubsequentMessage.cs @@ -0,0 +1,27 @@ +using System; + +namespace Org.BouncyCastle.Asn1.Crmf +{ + public class SubsequentMessage + : DerInteger + { + public static readonly SubsequentMessage encrCert = new SubsequentMessage(0); + public static readonly SubsequentMessage challengeResp = new SubsequentMessage(1); + + private SubsequentMessage(int value) + : base(value) + { + } + + public static SubsequentMessage ValueOf(int value) + { + if (value == 0) + return encrCert; + + if (value == 1) + return challengeResp; + + throw new ArgumentException("unknown value: " + value, "value"); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cryptopro/CryptoProObjectIdentifiers.cs b/bc-sharp-crypto/src/asn1/cryptopro/CryptoProObjectIdentifiers.cs new file mode 100644 index 0000000..e2f2c18 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cryptopro/CryptoProObjectIdentifiers.cs @@ -0,0 +1,51 @@ +using System; + +using Org.BouncyCastle.Asn1; + +namespace Org.BouncyCastle.Asn1.CryptoPro +{ + public abstract class CryptoProObjectIdentifiers + { + // GOST Algorithms OBJECT IDENTIFIERS : + // { iso(1) member-body(2) ru(643) rans(2) cryptopro(2)} + public const string GostID = "1.2.643.2.2"; + + public static readonly DerObjectIdentifier GostR3411 = new DerObjectIdentifier(GostID + ".9"); + public static readonly DerObjectIdentifier GostR3411Hmac = new DerObjectIdentifier(GostID + ".10"); + + public static readonly DerObjectIdentifier GostR28147Cbc = new DerObjectIdentifier(GostID + ".21"); + + public static readonly DerObjectIdentifier ID_Gost28147_89_CryptoPro_A_ParamSet = new DerObjectIdentifier(GostID + ".31.1"); + + public static readonly DerObjectIdentifier GostR3410x94 = new DerObjectIdentifier(GostID + ".20"); + public static readonly DerObjectIdentifier GostR3410x2001 = new DerObjectIdentifier(GostID + ".19"); + public static readonly DerObjectIdentifier GostR3411x94WithGostR3410x94 = new DerObjectIdentifier(GostID + ".4"); + public static readonly DerObjectIdentifier GostR3411x94WithGostR3410x2001 = new DerObjectIdentifier(GostID + ".3"); + + // { iso(1) member-body(2) ru(643) rans(2) cryptopro(2) hashes(30) } + public static readonly DerObjectIdentifier GostR3411x94CryptoProParamSet = new DerObjectIdentifier(GostID + ".30.1"); + + // { iso(1) member-body(2) ru(643) rans(2) cryptopro(2) signs(32) } + public static readonly DerObjectIdentifier GostR3410x94CryptoProA = new DerObjectIdentifier(GostID + ".32.2"); + public static readonly DerObjectIdentifier GostR3410x94CryptoProB = new DerObjectIdentifier(GostID + ".32.3"); + public static readonly DerObjectIdentifier GostR3410x94CryptoProC = new DerObjectIdentifier(GostID + ".32.4"); + public static readonly DerObjectIdentifier GostR3410x94CryptoProD = new DerObjectIdentifier(GostID + ".32.5"); + + // { iso(1) member-body(2) ru(643) rans(2) cryptopro(2) exchanges(33) } + public static readonly DerObjectIdentifier GostR3410x94CryptoProXchA = new DerObjectIdentifier(GostID + ".33.1"); + public static readonly DerObjectIdentifier GostR3410x94CryptoProXchB = new DerObjectIdentifier(GostID + ".33.2"); + public static readonly DerObjectIdentifier GostR3410x94CryptoProXchC = new DerObjectIdentifier(GostID + ".33.3"); + + //{ iso(1) member-body(2)ru(643) rans(2) cryptopro(2) ecc-signs(35) } + public static readonly DerObjectIdentifier GostR3410x2001CryptoProA = new DerObjectIdentifier(GostID + ".35.1"); + public static readonly DerObjectIdentifier GostR3410x2001CryptoProB = new DerObjectIdentifier(GostID + ".35.2"); + public static readonly DerObjectIdentifier GostR3410x2001CryptoProC = new DerObjectIdentifier(GostID + ".35.3"); + + // { iso(1) member-body(2) ru(643) rans(2) cryptopro(2) ecc-exchanges(36) } + public static readonly DerObjectIdentifier GostR3410x2001CryptoProXchA = new DerObjectIdentifier(GostID + ".36.0"); + public static readonly DerObjectIdentifier GostR3410x2001CryptoProXchB = new DerObjectIdentifier(GostID + ".36.1"); + + public static readonly DerObjectIdentifier GostElSgDH3410Default = new DerObjectIdentifier(GostID + ".36.0"); + public static readonly DerObjectIdentifier GostElSgDH3410x1 = new DerObjectIdentifier(GostID + ".36.1"); + } +} diff --git a/bc-sharp-crypto/src/asn1/cryptopro/ECGOST3410NamedCurves.cs b/bc-sharp-crypto/src/asn1/cryptopro/ECGOST3410NamedCurves.cs new file mode 100644 index 0000000..32d3103 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cryptopro/ECGOST3410NamedCurves.cs @@ -0,0 +1,184 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Math.EC; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; + +namespace Org.BouncyCastle.Asn1.CryptoPro +{ + /** + * table of the available named parameters for GOST 3410-2001. + */ + public sealed class ECGost3410NamedCurves + { + private ECGost3410NamedCurves() + { + } + + internal static readonly IDictionary objIds = Platform.CreateHashtable(); + internal static readonly IDictionary parameters = Platform.CreateHashtable(); + internal static readonly IDictionary names = Platform.CreateHashtable(); + + static ECGost3410NamedCurves() + { + BigInteger mod_p = new BigInteger("115792089237316195423570985008687907853269984665640564039457584007913129639319"); + BigInteger mod_q = new BigInteger("115792089237316195423570985008687907853073762908499243225378155805079068850323"); + + FpCurve curve = new FpCurve( + mod_p, // p + new BigInteger("115792089237316195423570985008687907853269984665640564039457584007913129639316"), // a + new BigInteger("166"), // b + mod_q, + BigInteger.One); + + ECDomainParameters ecParams = new ECDomainParameters( + curve, + curve.CreatePoint( + new BigInteger("1"), // x + new BigInteger("64033881142927202683649881450433473985931760268884941288852745803908878638612")), // y + mod_q); + + parameters[CryptoProObjectIdentifiers.GostR3410x2001CryptoProA] = ecParams; + + mod_p = new BigInteger("115792089237316195423570985008687907853269984665640564039457584007913129639319"); + mod_q = new BigInteger("115792089237316195423570985008687907853073762908499243225378155805079068850323"); + + curve = new FpCurve( + mod_p, // p + new BigInteger("115792089237316195423570985008687907853269984665640564039457584007913129639316"), + new BigInteger("166"), + mod_q, + BigInteger.One); + + ecParams = new ECDomainParameters( + curve, + curve.CreatePoint( + new BigInteger("1"), // x + new BigInteger("64033881142927202683649881450433473985931760268884941288852745803908878638612")), // y + mod_q); + + parameters[CryptoProObjectIdentifiers.GostR3410x2001CryptoProXchA] = ecParams; + + mod_p = new BigInteger("57896044618658097711785492504343953926634992332820282019728792003956564823193"); //p + mod_q = new BigInteger("57896044618658097711785492504343953927102133160255826820068844496087732066703"); //q + + curve = new FpCurve( + mod_p, // p + new BigInteger("57896044618658097711785492504343953926634992332820282019728792003956564823190"), // a + new BigInteger("28091019353058090096996979000309560759124368558014865957655842872397301267595"), // b + mod_q, + BigInteger.One); + + ecParams = new ECDomainParameters( + curve, + curve.CreatePoint( + new BigInteger("1"), // x + new BigInteger("28792665814854611296992347458380284135028636778229113005756334730996303888124")), // y + mod_q); // q + + parameters[CryptoProObjectIdentifiers.GostR3410x2001CryptoProB] = ecParams; + + mod_p = new BigInteger("70390085352083305199547718019018437841079516630045180471284346843705633502619"); + mod_q = new BigInteger("70390085352083305199547718019018437840920882647164081035322601458352298396601"); + + curve = new FpCurve( + mod_p, // p + new BigInteger("70390085352083305199547718019018437841079516630045180471284346843705633502616"), + new BigInteger("32858"), + mod_q, + BigInteger.One); + + ecParams = new ECDomainParameters( + curve, + curve.CreatePoint( + new BigInteger("0"), + new BigInteger("29818893917731240733471273240314769927240550812383695689146495261604565990247")), + mod_q); + + parameters[CryptoProObjectIdentifiers.GostR3410x2001CryptoProXchB] = ecParams; + + mod_p = new BigInteger("70390085352083305199547718019018437841079516630045180471284346843705633502619"); //p + mod_q = new BigInteger("70390085352083305199547718019018437840920882647164081035322601458352298396601"); //q + curve = new FpCurve( + mod_p, // p + new BigInteger("70390085352083305199547718019018437841079516630045180471284346843705633502616"), // a + new BigInteger("32858"), // b + mod_q, + BigInteger.One); + + ecParams = new ECDomainParameters( + curve, + curve.CreatePoint( + new BigInteger("0"), // x + new BigInteger("29818893917731240733471273240314769927240550812383695689146495261604565990247")), // y + mod_q); // q + + parameters[CryptoProObjectIdentifiers.GostR3410x2001CryptoProC] = ecParams; + + objIds["GostR3410-2001-CryptoPro-A"] = CryptoProObjectIdentifiers.GostR3410x2001CryptoProA; + objIds["GostR3410-2001-CryptoPro-B"] = CryptoProObjectIdentifiers.GostR3410x2001CryptoProB; + objIds["GostR3410-2001-CryptoPro-C"] = CryptoProObjectIdentifiers.GostR3410x2001CryptoProC; + objIds["GostR3410-2001-CryptoPro-XchA"] = CryptoProObjectIdentifiers.GostR3410x2001CryptoProXchA; + objIds["GostR3410-2001-CryptoPro-XchB"] = CryptoProObjectIdentifiers.GostR3410x2001CryptoProXchB; + + names[CryptoProObjectIdentifiers.GostR3410x2001CryptoProA] = "GostR3410-2001-CryptoPro-A"; + names[CryptoProObjectIdentifiers.GostR3410x2001CryptoProB] = "GostR3410-2001-CryptoPro-B"; + names[CryptoProObjectIdentifiers.GostR3410x2001CryptoProC] = "GostR3410-2001-CryptoPro-C"; + names[CryptoProObjectIdentifiers.GostR3410x2001CryptoProXchA] = "GostR3410-2001-CryptoPro-XchA"; + names[CryptoProObjectIdentifiers.GostR3410x2001CryptoProXchB] = "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) parameters[oid]; + } + + /** + * returns an enumeration containing the name strings for curves + * contained in this structure. + */ + public static IEnumerable Names + { + get { return new EnumerableProxy(names.Values); } + } + + public static ECDomainParameters GetByName( + string name) + { + DerObjectIdentifier oid = (DerObjectIdentifier) objIds[name]; + + if (oid != null) + { + return (ECDomainParameters) parameters[oid]; + } + + return null; + } + + /** + * return the named curve name represented by the given object identifier. + */ + public static string GetName( + DerObjectIdentifier oid) + { + return (string) names[oid]; + } + + public static DerObjectIdentifier GetOid( + string name) + { + return (DerObjectIdentifier) objIds[name]; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cryptopro/ECGOST3410ParamSetParameters.cs b/bc-sharp-crypto/src/asn1/cryptopro/ECGOST3410ParamSetParameters.cs new file mode 100644 index 0000000..8e568a2 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cryptopro/ECGOST3410ParamSetParameters.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.CryptoPro +{ + public class ECGost3410ParamSetParameters + : Asn1Encodable + { + internal readonly DerInteger p, q, a, b, x, y; + + public static ECGost3410ParamSetParameters GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); + } + + public static ECGost3410ParamSetParameters GetInstance( + object obj) + { + if (obj == null || obj is ECGost3410ParamSetParameters) + { + return (ECGost3410ParamSetParameters) obj; + } + + if (obj is Asn1Sequence) + { + return new ECGost3410ParamSetParameters((Asn1Sequence) obj); + } + + throw new ArgumentException("Invalid GOST3410Parameter: " + Platform.GetTypeName(obj)); + } + + 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) + { + if (seq.Count != 6) + throw new ArgumentException("Wrong number of elements in sequence", "seq"); + + this.a = DerInteger.GetInstance(seq[0]); + this.b = DerInteger.GetInstance(seq[1]); + this.p = DerInteger.GetInstance(seq[2]); + this.q = DerInteger.GetInstance(seq[3]); + this.x = DerInteger.GetInstance(seq[4]); + this.y = DerInteger.GetInstance(seq[5]); + } + + public BigInteger P + { + get { return p.PositiveValue; } + } + + public BigInteger Q + { + get { return q.PositiveValue; } + } + + public BigInteger A + { + get { return a.PositiveValue; } + } + + public override Asn1Object ToAsn1Object() + { + return new DerSequence(a, b, p, q, x, y); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cryptopro/GOST28147Parameters.cs b/bc-sharp-crypto/src/asn1/cryptopro/GOST28147Parameters.cs new file mode 100644 index 0000000..fc0d792 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cryptopro/GOST28147Parameters.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.CryptoPro +{ + public class Gost28147Parameters + : Asn1Encodable + { + private readonly Asn1OctetString iv; + private readonly DerObjectIdentifier paramSet; + + public static Gost28147Parameters GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); + } + + public static Gost28147Parameters GetInstance( + object obj) + { + if (obj == null || obj is Gost28147Parameters) + { + return (Gost28147Parameters) obj; + } + + if (obj is Asn1Sequence) + { + return new Gost28147Parameters((Asn1Sequence) obj); + } + + throw new ArgumentException("Invalid GOST3410Parameter: " + Platform.GetTypeName(obj)); + } + + private Gost28147Parameters( + Asn1Sequence seq) + { + if (seq.Count != 2) + throw new ArgumentException("Wrong number of elements in sequence", "seq"); + + this.iv = Asn1OctetString.GetInstance(seq[0]); + this.paramSet = DerObjectIdentifier.GetInstance(seq[1]); + } + + /** + *
+         * Gost28147-89-Parameters ::=
+         *               SEQUENCE {
+         *                       iv                   Gost28147-89-IV,
+         *                       encryptionParamSet   OBJECT IDENTIFIER
+         *                }
+         *
+         *   Gost28147-89-IV ::= OCTET STRING (SIZE (8))
+         * 
+ */ + public override Asn1Object ToAsn1Object() + { + return new DerSequence(iv, paramSet); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cryptopro/GOST3410NamedParameters.cs b/bc-sharp-crypto/src/asn1/cryptopro/GOST3410NamedParameters.cs new file mode 100644 index 0000000..66dba51 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cryptopro/GOST3410NamedParameters.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; + +namespace Org.BouncyCastle.Asn1.CryptoPro +{ + /** + * table of the available named parameters for GOST 3410-94. + */ + public sealed class Gost3410NamedParameters + { + private Gost3410NamedParameters() + { + } + + private static readonly IDictionary objIds = Platform.CreateHashtable(); + private static readonly IDictionary parameters = Platform.CreateHashtable(); + + private static readonly 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 + // } + // } + + ); + + private static readonly 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 + // } + // } + //} + ); + + private static readonly Gost3410ParamSetParameters cryptoProXchA = new Gost3410ParamSetParameters( + 1024, + new BigInteger("142011741597563481196368286022318089743276138395243738762872573441927459393512718973631166078467600360848946623567625795282774719212241929071046134208380636394084512691828894000571524625445295769349356752728956831541775441763139384457191755096847107846595662547942312293338483924514339614727760681880609734239"), + new BigInteger("91771529896554605945588149018382750217296858393520724172743325725474374979801"), + new BigInteger("133531813272720673433859519948319001217942375967847486899482359599369642528734712461590403327731821410328012529253871914788598993103310567744136196364803064721377826656898686468463277710150809401182608770201615324990468332931294920912776241137878030224355746606283971659376426832674269780880061631528163475887") + ); + + static Gost3410NamedParameters() + { + parameters[CryptoProObjectIdentifiers.GostR3410x94CryptoProA] = cryptoProA; + parameters[CryptoProObjectIdentifiers.GostR3410x94CryptoProB] = cryptoProB; + //parameters[CryptoProObjectIdentifiers.GostR3410x94CryptoProC] = cryptoProC; + //parameters[CryptoProObjectIdentifiers.GostR3410x94CryptoProD] = cryptoProD; + parameters[CryptoProObjectIdentifiers.GostR3410x94CryptoProXchA] = cryptoProXchA; + //parameters[CryptoProObjectIdentifiers.GostR3410x94CryptoProXchB] = cryptoProXchA; + //parameters[CryptoProObjectIdentifiers.GostR3410x94CryptoProXchC] = cryptoProXchA; + + objIds["GostR3410-94-CryptoPro-A"] = CryptoProObjectIdentifiers.GostR3410x94CryptoProA; + objIds["GostR3410-94-CryptoPro-B"] = CryptoProObjectIdentifiers.GostR3410x94CryptoProB; + objIds["GostR3410-94-CryptoPro-XchA"] = CryptoProObjectIdentifiers.GostR3410x94CryptoProXchA; + } + + /** + * 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) parameters[oid]; + } + + /** + * returns an enumeration containing the name strings for parameters + * contained in this structure. + */ + public static IEnumerable Names + { + get { return new EnumerableProxy(objIds.Keys); } + } + + public static Gost3410ParamSetParameters GetByName( + string name) + { + DerObjectIdentifier oid = (DerObjectIdentifier) objIds[name]; + + if (oid != null) + { + return (Gost3410ParamSetParameters) parameters[oid]; + } + + return null; + } + + public static DerObjectIdentifier GetOid( + string name) + { + return (DerObjectIdentifier) objIds[name]; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cryptopro/GOST3410ParamSetParameters.cs b/bc-sharp-crypto/src/asn1/cryptopro/GOST3410ParamSetParameters.cs new file mode 100644 index 0000000..b347f8d --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cryptopro/GOST3410ParamSetParameters.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.CryptoPro +{ + public class Gost3410ParamSetParameters + : Asn1Encodable + { + private readonly int keySize; + private readonly DerInteger p, q, a; + + public static Gost3410ParamSetParameters GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); + } + + public static Gost3410ParamSetParameters GetInstance( + object obj) + { + if (obj == null || obj is Gost3410ParamSetParameters) + { + return (Gost3410ParamSetParameters) obj; + } + + if (obj is Asn1Sequence) + { + return new Gost3410ParamSetParameters((Asn1Sequence) obj); + } + + throw new ArgumentException("Invalid GOST3410Parameter: " + Platform.GetTypeName(obj)); + } + + 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); + } + + private Gost3410ParamSetParameters( + Asn1Sequence seq) + { + if (seq.Count != 4) + throw new ArgumentException("Wrong number of elements in sequence", "seq"); + + this.keySize = DerInteger.GetInstance(seq[0]).Value.IntValue; + this.p = DerInteger.GetInstance(seq[1]); + this.q = DerInteger.GetInstance(seq[2]); + this.a = DerInteger.GetInstance(seq[3]); + } + + public int KeySize + { + get { return keySize; } + } + + public BigInteger P + { + get { return p.PositiveValue; } + } + + public BigInteger Q + { + get { return q.PositiveValue; } + } + + public BigInteger A + { + get { return a.PositiveValue; } + } + + public override Asn1Object ToAsn1Object() + { + return new DerSequence(new DerInteger(keySize), p, q, a); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/cryptopro/GOST3410PublicKeyAlgParameters.cs b/bc-sharp-crypto/src/asn1/cryptopro/GOST3410PublicKeyAlgParameters.cs new file mode 100644 index 0000000..10c45ba --- /dev/null +++ b/bc-sharp-crypto/src/asn1/cryptopro/GOST3410PublicKeyAlgParameters.cs @@ -0,0 +1,99 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.CryptoPro +{ + public class Gost3410PublicKeyAlgParameters + : Asn1Encodable + { + private DerObjectIdentifier publicKeyParamSet; + private DerObjectIdentifier digestParamSet; + private DerObjectIdentifier encryptionParamSet; + + public static Gost3410PublicKeyAlgParameters GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); + } + + public static Gost3410PublicKeyAlgParameters GetInstance( + object obj) + { + if (obj == null || obj is Gost3410PublicKeyAlgParameters) + { + return (Gost3410PublicKeyAlgParameters) obj; + } + + if (obj is Asn1Sequence) + { + return new Gost3410PublicKeyAlgParameters((Asn1Sequence) obj); + } + + throw new ArgumentException("Invalid GOST3410Parameter: " + Platform.GetTypeName(obj)); + } + + public Gost3410PublicKeyAlgParameters( + DerObjectIdentifier publicKeyParamSet, + DerObjectIdentifier digestParamSet) + : this (publicKeyParamSet, digestParamSet, null) + { + } + + public Gost3410PublicKeyAlgParameters( + DerObjectIdentifier publicKeyParamSet, + DerObjectIdentifier digestParamSet, + DerObjectIdentifier encryptionParamSet) + { + if (publicKeyParamSet == null) + throw new ArgumentNullException("publicKeyParamSet"); + if (digestParamSet == null) + throw new ArgumentNullException("digestParamSet"); + + this.publicKeyParamSet = publicKeyParamSet; + this.digestParamSet = digestParamSet; + this.encryptionParamSet = encryptionParamSet; + } + + public Gost3410PublicKeyAlgParameters( + Asn1Sequence seq) + { + this.publicKeyParamSet = (DerObjectIdentifier) seq[0]; + this.digestParamSet = (DerObjectIdentifier) seq[1]; + + if (seq.Count > 2) + { + this.encryptionParamSet = (DerObjectIdentifier) seq[2]; + } + } + + public DerObjectIdentifier PublicKeyParamSet + { + get { return publicKeyParamSet; } + } + + public DerObjectIdentifier DigestParamSet + { + get { return digestParamSet; } + } + + public DerObjectIdentifier EncryptionParamSet + { + get { return encryptionParamSet; } + } + + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector( + publicKeyParamSet, digestParamSet); + + if (encryptionParamSet != null) + { + v.Add(encryptionParamSet); + } + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/eac/EACObjectIdentifiers.cs b/bc-sharp-crypto/src/asn1/eac/EACObjectIdentifiers.cs new file mode 100644 index 0000000..d54ef0e --- /dev/null +++ b/bc-sharp-crypto/src/asn1/eac/EACObjectIdentifiers.cs @@ -0,0 +1,50 @@ +using System; + +using Org.BouncyCastle.Asn1; + +namespace Org.BouncyCastle.Asn1.Eac +{ + public abstract class EacObjectIdentifiers + { + // bsi-de OBJECT IDENTIFIER ::= { + // itu-t(0) identified-organization(4) etsi(0) + // reserved(127) etsi-identified-organization(0) 7 + // } + public static readonly DerObjectIdentifier bsi_de = new DerObjectIdentifier("0.4.0.127.0.7"); + + // id-PK OBJECT IDENTIFIER ::= { + // bsi-de protocols(2) smartcard(2) 1 + // } + public static readonly DerObjectIdentifier id_PK = new DerObjectIdentifier(bsi_de + ".2.2.1"); + + public static readonly DerObjectIdentifier id_PK_DH = new DerObjectIdentifier(id_PK + ".1"); + public static readonly DerObjectIdentifier id_PK_ECDH = new DerObjectIdentifier(id_PK + ".2"); + + // id-CA OBJECT IDENTIFIER ::= { + // bsi-de protocols(2) smartcard(2) 3 + // } + public static readonly DerObjectIdentifier id_CA = new DerObjectIdentifier(bsi_de + ".2.2.3"); + public static readonly DerObjectIdentifier id_CA_DH = new DerObjectIdentifier(id_CA + ".1"); + public static readonly DerObjectIdentifier id_CA_DH_3DES_CBC_CBC = new DerObjectIdentifier(id_CA_DH + ".1"); + public static readonly DerObjectIdentifier id_CA_ECDH = new DerObjectIdentifier(id_CA + ".2"); + public static readonly DerObjectIdentifier id_CA_ECDH_3DES_CBC_CBC = new DerObjectIdentifier(id_CA_ECDH + ".1"); + + // + // id-TA OBJECT IDENTIFIER ::= { + // bsi-de protocols(2) smartcard(2) 2 + // } + public static readonly DerObjectIdentifier id_TA = new DerObjectIdentifier(bsi_de + ".2.2.2"); + + public static readonly DerObjectIdentifier id_TA_RSA = new DerObjectIdentifier(id_TA + ".1"); + public static readonly DerObjectIdentifier id_TA_RSA_v1_5_SHA_1 = new DerObjectIdentifier(id_TA_RSA + ".1"); + public static readonly DerObjectIdentifier id_TA_RSA_v1_5_SHA_256 = new DerObjectIdentifier(id_TA_RSA + ".2"); + public static readonly DerObjectIdentifier id_TA_RSA_PSS_SHA_1 = new DerObjectIdentifier(id_TA_RSA + ".3"); + public static readonly DerObjectIdentifier id_TA_RSA_PSS_SHA_256 = new DerObjectIdentifier(id_TA_RSA + ".4"); + public static readonly DerObjectIdentifier id_TA_ECDSA = new DerObjectIdentifier(id_TA + ".2"); + public static readonly DerObjectIdentifier id_TA_ECDSA_SHA_1 = new DerObjectIdentifier(id_TA_ECDSA + ".1"); + public static readonly DerObjectIdentifier id_TA_ECDSA_SHA_224 = new DerObjectIdentifier(id_TA_ECDSA + ".2"); + public static readonly DerObjectIdentifier id_TA_ECDSA_SHA_256 = new DerObjectIdentifier(id_TA_ECDSA + ".3"); + public static readonly DerObjectIdentifier id_TA_ECDSA_SHA_384 = new DerObjectIdentifier(id_TA_ECDSA + ".4"); + public static readonly DerObjectIdentifier id_TA_ECDSA_SHA_512 = new DerObjectIdentifier(id_TA_ECDSA + ".5"); + } +} diff --git a/bc-sharp-crypto/src/asn1/esf/CertificateValues.cs b/bc-sharp-crypto/src/asn1/esf/CertificateValues.cs new file mode 100644 index 0000000..30a7191 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/esf/CertificateValues.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; + +namespace Org.BouncyCastle.Asn1.Esf +{ + /// + /// RFC 3126: 4.3.1 Certificate Values Attribute Definition + /// + /// CertificateValues ::= SEQUENCE OF Certificate + /// + /// + public class CertificateValues + : Asn1Encodable + { + private readonly Asn1Sequence certificates; + + public static CertificateValues GetInstance( + object obj) + { + if (obj == null || obj is CertificateValues) + return (CertificateValues) obj; + + if (obj is Asn1Sequence) + return new CertificateValues((Asn1Sequence) obj); + + throw new ArgumentException( + "Unknown object in 'CertificateValues' factory: " + + Platform.GetTypeName(obj), + "obj"); + } + + private CertificateValues( + Asn1Sequence seq) + { + if (seq == null) + throw new ArgumentNullException("seq"); + + foreach (Asn1Encodable ae in seq) + { + X509CertificateStructure.GetInstance(ae.ToAsn1Object()); + } + + this.certificates = seq; + } + + public CertificateValues( + params X509CertificateStructure[] certificates) + { + if (certificates == null) + throw new ArgumentNullException("certificates"); + + this.certificates = new DerSequence(certificates); + } + + public CertificateValues( + IEnumerable certificates) + { + if (certificates == null) + throw new ArgumentNullException("certificates"); + if (!CollectionUtilities.CheckElementsAreOfType(certificates, typeof(X509CertificateStructure))) + throw new ArgumentException("Must contain only 'X509CertificateStructure' objects", "certificates"); + + this.certificates = new DerSequence( + Asn1EncodableVector.FromEnumerable(certificates)); + } + + public X509CertificateStructure[] GetCertificates() + { + X509CertificateStructure[] result = new X509CertificateStructure[certificates.Count]; + for (int i = 0; i < certificates.Count; ++i) + { + result[i] = X509CertificateStructure.GetInstance(certificates[i]); + } + return result; + } + + public override Asn1Object ToAsn1Object() + { + return certificates; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/esf/CommitmentTypeIdentifier.cs b/bc-sharp-crypto/src/asn1/esf/CommitmentTypeIdentifier.cs new file mode 100644 index 0000000..65cd45b --- /dev/null +++ b/bc-sharp-crypto/src/asn1/esf/CommitmentTypeIdentifier.cs @@ -0,0 +1,17 @@ +using System; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Pkcs; + +namespace Org.BouncyCastle.Asn1.Esf +{ + public abstract class CommitmentTypeIdentifier + { + public static readonly DerObjectIdentifier ProofOfOrigin = PkcsObjectIdentifiers.IdCtiEtsProofOfOrigin; + public static readonly DerObjectIdentifier ProofOfReceipt = PkcsObjectIdentifiers.IdCtiEtsProofOfReceipt; + public static readonly DerObjectIdentifier ProofOfDelivery = PkcsObjectIdentifiers.IdCtiEtsProofOfDelivery; + public static readonly DerObjectIdentifier ProofOfSender = PkcsObjectIdentifiers.IdCtiEtsProofOfSender; + public static readonly DerObjectIdentifier ProofOfApproval = PkcsObjectIdentifiers.IdCtiEtsProofOfApproval; + public static readonly DerObjectIdentifier ProofOfCreation = PkcsObjectIdentifiers.IdCtiEtsProofOfCreation; + } +} diff --git a/bc-sharp-crypto/src/asn1/esf/CommitmentTypeIndication.cs b/bc-sharp-crypto/src/asn1/esf/CommitmentTypeIndication.cs new file mode 100644 index 0000000..196a613 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/esf/CommitmentTypeIndication.cs @@ -0,0 +1,95 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Esf +{ + public class CommitmentTypeIndication + : Asn1Encodable + { + private readonly DerObjectIdentifier commitmentTypeId; + private readonly Asn1Sequence commitmentTypeQualifier; + + public static CommitmentTypeIndication GetInstance( + object obj) + { + if (obj == null || obj is CommitmentTypeIndication) + return (CommitmentTypeIndication) obj; + + if (obj is Asn1Sequence) + return new CommitmentTypeIndication((Asn1Sequence) obj); + + throw new ArgumentException( + "Unknown object in 'CommitmentTypeIndication' factory: " + + Platform.GetTypeName(obj), + "obj"); + } + + public CommitmentTypeIndication( + Asn1Sequence seq) + { + if (seq == null) + throw new ArgumentNullException("seq"); + if (seq.Count < 1 || seq.Count > 2) + throw new ArgumentException("Bad sequence size: " + seq.Count, "seq"); + + this.commitmentTypeId = (DerObjectIdentifier) seq[0].ToAsn1Object(); + + if (seq.Count > 1) + { + this.commitmentTypeQualifier = (Asn1Sequence) seq[1].ToAsn1Object(); + } + } + + public CommitmentTypeIndication( + DerObjectIdentifier commitmentTypeId) + : this(commitmentTypeId, null) + { + } + + public CommitmentTypeIndication( + DerObjectIdentifier commitmentTypeId, + Asn1Sequence commitmentTypeQualifier) + { + if (commitmentTypeId == null) + throw new ArgumentNullException("commitmentTypeId"); + + this.commitmentTypeId = commitmentTypeId; + + if (commitmentTypeQualifier != null) + { + this.commitmentTypeQualifier = commitmentTypeQualifier; + } + } + + public DerObjectIdentifier CommitmentTypeID + { + get { return commitmentTypeId; } + } + + public Asn1Sequence CommitmentTypeQualifier + { + get { return commitmentTypeQualifier; } + } + + /** + *
+        * CommitmentTypeIndication ::= SEQUENCE {
+        *      commitmentTypeId   CommitmentTypeIdentifier,
+        *      commitmentTypeQualifier   SEQUENCE SIZE (1..MAX) OF
+        *              CommitmentTypeQualifier OPTIONAL }
+        * 
+ */ + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(commitmentTypeId); + + if (commitmentTypeQualifier != null) + { + v.Add(commitmentTypeQualifier); + } + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/esf/CommitmentTypeQualifier.cs b/bc-sharp-crypto/src/asn1/esf/CommitmentTypeQualifier.cs new file mode 100644 index 0000000..30bf0ed --- /dev/null +++ b/bc-sharp-crypto/src/asn1/esf/CommitmentTypeQualifier.cs @@ -0,0 +1,119 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Esf +{ + /** + * Commitment type qualifiers, used in the Commitment-Type-Indication attribute (RFC3126). + * + *
+    *   CommitmentTypeQualifier ::= SEQUENCE {
+    *       commitmentTypeIdentifier  CommitmentTypeIdentifier,
+    *       qualifier          ANY DEFINED BY commitmentTypeIdentifier OPTIONAL }
+    * 
+ */ + public class CommitmentTypeQualifier + : Asn1Encodable + { + private readonly DerObjectIdentifier commitmentTypeIdentifier; + private readonly Asn1Object 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, + Asn1Encodable qualifier) + { + if (commitmentTypeIdentifier == null) + throw new ArgumentNullException("commitmentTypeIdentifier"); + + this.commitmentTypeIdentifier = commitmentTypeIdentifier; + + if (qualifier != null) + { + this.qualifier = qualifier.ToAsn1Object(); + } + } + + /** + * Creates a new CommitmentTypeQualifier instance. + * + * @param as CommitmentTypeQualifier structure + * encoded as an Asn1Sequence. + */ + public CommitmentTypeQualifier( + Asn1Sequence seq) + { + if (seq == null) + throw new ArgumentNullException("seq"); + if (seq.Count < 1 || seq.Count > 2) + throw new ArgumentException("Bad sequence size: " + seq.Count, "seq"); + + commitmentTypeIdentifier = (DerObjectIdentifier) seq[0].ToAsn1Object(); + + if (seq.Count > 1) + { + qualifier = seq[1].ToAsn1Object(); + } + } + + public static CommitmentTypeQualifier GetInstance( + object obj) + { + if (obj == null || obj is CommitmentTypeQualifier) + return (CommitmentTypeQualifier) obj; + + if (obj is Asn1Sequence) + return new CommitmentTypeQualifier((Asn1Sequence) obj); + + throw new ArgumentException( + "Unknown object in 'CommitmentTypeQualifier' factory: " + + Platform.GetTypeName(obj), + "obj"); + } + + public DerObjectIdentifier CommitmentTypeIdentifier + { + get { return commitmentTypeIdentifier; } + } + + public Asn1Object Qualifier + { + get { return qualifier; } + } + + /** + * Returns a DER-encodable representation of this instance. + * + * @return a Asn1Object value + */ + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector( + commitmentTypeIdentifier); + + if (qualifier != null) + { + v.Add(qualifier); + } + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/esf/CompleteCertificateRefs.cs b/bc-sharp-crypto/src/asn1/esf/CompleteCertificateRefs.cs new file mode 100644 index 0000000..af93700 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/esf/CompleteCertificateRefs.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; + +namespace Org.BouncyCastle.Asn1.Esf +{ + /// + /// RFC 3126: 4.2.1 Complete Certificate Refs Attribute Definition + /// + /// CompleteCertificateRefs ::= SEQUENCE OF OtherCertID + /// + /// + public class CompleteCertificateRefs + : Asn1Encodable + { + private readonly Asn1Sequence otherCertIDs; + + public static CompleteCertificateRefs GetInstance( + object obj) + { + if (obj == null || obj is CompleteCertificateRefs) + return (CompleteCertificateRefs) obj; + + if (obj is Asn1Sequence) + return new CompleteCertificateRefs((Asn1Sequence) obj); + + throw new ArgumentException( + "Unknown object in 'CompleteCertificateRefs' factory: " + + Platform.GetTypeName(obj), + "obj"); + } + + private CompleteCertificateRefs( + Asn1Sequence seq) + { + if (seq == null) + throw new ArgumentNullException("seq"); + + foreach (Asn1Encodable ae in seq) + { + OtherCertID.GetInstance(ae.ToAsn1Object()); + } + + this.otherCertIDs = seq; + } + + public CompleteCertificateRefs( + params OtherCertID[] otherCertIDs) + { + if (otherCertIDs == null) + throw new ArgumentNullException("otherCertIDs"); + + this.otherCertIDs = new DerSequence(otherCertIDs); + } + + public CompleteCertificateRefs( + IEnumerable otherCertIDs) + { + if (otherCertIDs == null) + throw new ArgumentNullException("otherCertIDs"); + if (!CollectionUtilities.CheckElementsAreOfType(otherCertIDs, typeof(OtherCertID))) + throw new ArgumentException("Must contain only 'OtherCertID' objects", "otherCertIDs"); + + this.otherCertIDs = new DerSequence( + Asn1EncodableVector.FromEnumerable(otherCertIDs)); + } + + public OtherCertID[] GetOtherCertIDs() + { + OtherCertID[] result = new OtherCertID[otherCertIDs.Count]; + for (int i = 0; i < otherCertIDs.Count; ++i) + { + result[i] = OtherCertID.GetInstance(otherCertIDs[i].ToAsn1Object()); + } + return result; + } + + public override Asn1Object ToAsn1Object() + { + return otherCertIDs; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/esf/CompleteRevocationRefs.cs b/bc-sharp-crypto/src/asn1/esf/CompleteRevocationRefs.cs new file mode 100644 index 0000000..348e63f --- /dev/null +++ b/bc-sharp-crypto/src/asn1/esf/CompleteRevocationRefs.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; + +namespace Org.BouncyCastle.Asn1.Esf +{ + /// + /// RFC 3126: 4.2.2 Complete Revocation Refs Attribute Definition + /// + /// CompleteRevocationRefs ::= SEQUENCE OF CrlOcspRef + /// + /// + public class CompleteRevocationRefs + : Asn1Encodable + { + private readonly Asn1Sequence crlOcspRefs; + + public static CompleteRevocationRefs GetInstance( + object obj) + { + if (obj == null || obj is CompleteRevocationRefs) + return (CompleteRevocationRefs) obj; + + if (obj is Asn1Sequence) + return new CompleteRevocationRefs((Asn1Sequence) obj); + + throw new ArgumentException( + "Unknown object in 'CompleteRevocationRefs' factory: " + + Platform.GetTypeName(obj), + "obj"); + } + + private CompleteRevocationRefs( + Asn1Sequence seq) + { + if (seq == null) + throw new ArgumentNullException("seq"); + + foreach (Asn1Encodable ae in seq) + { + CrlOcspRef.GetInstance(ae.ToAsn1Object()); + } + + this.crlOcspRefs = seq; + } + + public CompleteRevocationRefs( + params CrlOcspRef[] crlOcspRefs) + { + if (crlOcspRefs == null) + throw new ArgumentNullException("crlOcspRefs"); + + this.crlOcspRefs = new DerSequence(crlOcspRefs); + } + + public CompleteRevocationRefs( + IEnumerable crlOcspRefs) + { + if (crlOcspRefs == null) + throw new ArgumentNullException("crlOcspRefs"); + if (!CollectionUtilities.CheckElementsAreOfType(crlOcspRefs, typeof(CrlOcspRef))) + throw new ArgumentException("Must contain only 'CrlOcspRef' objects", "crlOcspRefs"); + + this.crlOcspRefs = new DerSequence( + Asn1EncodableVector.FromEnumerable(crlOcspRefs)); + } + + public CrlOcspRef[] GetCrlOcspRefs() + { + CrlOcspRef[] result = new CrlOcspRef[crlOcspRefs.Count]; + for (int i = 0; i < crlOcspRefs.Count; ++i) + { + result[i] = CrlOcspRef.GetInstance(crlOcspRefs[i].ToAsn1Object()); + } + return result; + } + + public override Asn1Object ToAsn1Object() + { + return crlOcspRefs; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/esf/CrlIdentifier.cs b/bc-sharp-crypto/src/asn1/esf/CrlIdentifier.cs new file mode 100644 index 0000000..96b50e2 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/esf/CrlIdentifier.cs @@ -0,0 +1,111 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Esf +{ + /// + /// RFC 3126: 4.2.2 Complete Revocation Refs Attribute Definition + /// + /// CrlIdentifier ::= SEQUENCE + /// { + /// crlissuer Name, + /// crlIssuedTime UTCTime, + /// crlNumber INTEGER OPTIONAL + /// } + /// + /// + public class CrlIdentifier + : Asn1Encodable + { + private readonly X509Name crlIssuer; + private readonly DerUtcTime crlIssuedTime; + private readonly DerInteger crlNumber; + + public static CrlIdentifier GetInstance( + object obj) + { + if (obj == null || obj is CrlIdentifier) + return (CrlIdentifier) obj; + + if (obj is Asn1Sequence) + return new CrlIdentifier((Asn1Sequence) obj); + + throw new ArgumentException( + "Unknown object in 'CrlIdentifier' factory: " + + Platform.GetTypeName(obj), + "obj"); + } + + private CrlIdentifier( + Asn1Sequence seq) + { + if (seq == null) + throw new ArgumentNullException("seq"); + if (seq.Count < 2 || seq.Count > 3) + throw new ArgumentException("Bad sequence size: " + seq.Count, "seq"); + + this.crlIssuer = X509Name.GetInstance(seq[0]); + this.crlIssuedTime = DerUtcTime.GetInstance(seq[1]); + + if (seq.Count > 2) + { + this.crlNumber = DerInteger.GetInstance(seq[2]); + } + } + + public CrlIdentifier( + X509Name crlIssuer, + DateTime crlIssuedTime) + : this(crlIssuer, crlIssuedTime, null) + { + } + + public CrlIdentifier( + X509Name crlIssuer, + DateTime crlIssuedTime, + BigInteger crlNumber) + { + if (crlIssuer == null) + throw new ArgumentNullException("crlIssuer"); + + this.crlIssuer = crlIssuer; + this.crlIssuedTime = new DerUtcTime(crlIssuedTime); + + if (crlNumber != null) + { + this.crlNumber = new DerInteger(crlNumber); + } + } + + public X509Name CrlIssuer + { + get { return crlIssuer; } + } + + public DateTime CrlIssuedTime + { + get { return crlIssuedTime.ToAdjustedDateTime(); } + } + + public BigInteger CrlNumber + { + get { return crlNumber == null ? null : crlNumber.Value; } + } + + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector( + crlIssuer.ToAsn1Object(), crlIssuedTime); + + if (crlNumber != null) + { + v.Add(crlNumber); + } + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/esf/CrlListID.cs b/bc-sharp-crypto/src/asn1/esf/CrlListID.cs new file mode 100644 index 0000000..fbd4fb2 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/esf/CrlListID.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; + +namespace Org.BouncyCastle.Asn1.Esf +{ + /// + /// RFC 3126: 4.2.2 Complete Revocation Refs Attribute Definition + /// + /// CRLListID ::= SEQUENCE + /// { + /// crls SEQUENCE OF CrlValidatedID + /// } + /// + /// + public class CrlListID + : Asn1Encodable + { + private readonly Asn1Sequence crls; + + public static CrlListID GetInstance( + object obj) + { + if (obj == null || obj is CrlListID) + return (CrlListID) obj; + + if (obj is Asn1Sequence) + return new CrlListID((Asn1Sequence) obj); + + throw new ArgumentException( + "Unknown object in 'CrlListID' factory: " + + Platform.GetTypeName(obj), + "obj"); + } + + private CrlListID( + Asn1Sequence seq) + { + if (seq == null) + throw new ArgumentNullException("seq"); + if (seq.Count != 1) + throw new ArgumentException("Bad sequence size: " + seq.Count, "seq"); + + this.crls = (Asn1Sequence) seq[0].ToAsn1Object(); + + foreach (Asn1Encodable ae in this.crls) + { + CrlValidatedID.GetInstance(ae.ToAsn1Object()); + } + } + + public CrlListID( + params CrlValidatedID[] crls) + { + if (crls == null) + throw new ArgumentNullException("crls"); + + this.crls = new DerSequence(crls); + } + + public CrlListID( + IEnumerable crls) + { + if (crls == null) + throw new ArgumentNullException("crls"); + if (!CollectionUtilities.CheckElementsAreOfType(crls, typeof(CrlValidatedID))) + throw new ArgumentException("Must contain only 'CrlValidatedID' objects", "crls"); + + this.crls = new DerSequence( + Asn1EncodableVector.FromEnumerable(crls)); + } + + public CrlValidatedID[] GetCrls() + { + CrlValidatedID[] result = new CrlValidatedID[crls.Count]; + for (int i = 0; i < crls.Count; ++i) + { + result[i] = CrlValidatedID.GetInstance(crls[i].ToAsn1Object()); + } + return result; + } + + public override Asn1Object ToAsn1Object() + { + return new DerSequence(crls); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/esf/CrlOcspRef.cs b/bc-sharp-crypto/src/asn1/esf/CrlOcspRef.cs new file mode 100644 index 0000000..6153e0c --- /dev/null +++ b/bc-sharp-crypto/src/asn1/esf/CrlOcspRef.cs @@ -0,0 +1,113 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Esf +{ + /// + /// RFC 3126: 4.2.2 Complete Revocation Refs Attribute Definition + /// + /// CrlOcspRef ::= SEQUENCE { + /// crlids [0] CRLListID OPTIONAL, + /// ocspids [1] OcspListID OPTIONAL, + /// otherRev [2] OtherRevRefs OPTIONAL + /// } + /// + /// + public class CrlOcspRef + : Asn1Encodable + { + private readonly CrlListID crlids; + private readonly OcspListID ocspids; + private readonly OtherRevRefs otherRev; + + public static CrlOcspRef GetInstance( + object obj) + { + if (obj == null || obj is CrlOcspRef) + return (CrlOcspRef) obj; + + if (obj is Asn1Sequence) + return new CrlOcspRef((Asn1Sequence) obj); + + throw new ArgumentException( + "Unknown object in 'CrlOcspRef' factory: " + + Platform.GetTypeName(obj), + "obj"); + } + + private CrlOcspRef( + Asn1Sequence seq) + { + if (seq == null) + throw new ArgumentNullException("seq"); + + foreach (Asn1TaggedObject taggedObj in seq) + { + Asn1Object asn1Obj = taggedObj.GetObject(); + + switch (taggedObj.TagNo) + { + case 0: + this.crlids = CrlListID.GetInstance(asn1Obj); + break; + case 1: + this.ocspids = OcspListID.GetInstance(asn1Obj); + break; + case 2: + this.otherRev = OtherRevRefs.GetInstance(asn1Obj); + break; + default: + throw new ArgumentException("Illegal tag in CrlOcspRef", "seq"); + } + } + } + + public CrlOcspRef( + CrlListID crlids, + OcspListID ocspids, + OtherRevRefs otherRev) + { + this.crlids = crlids; + this.ocspids = ocspids; + this.otherRev = otherRev; + } + + public CrlListID CrlIDs + { + get { return crlids; } + } + + public OcspListID OcspIDs + { + get { return ocspids; } + } + + public OtherRevRefs OtherRev + { + get { return otherRev; } + } + + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(); + + if (crlids != null) + { + v.Add(new DerTaggedObject(true, 0, crlids.ToAsn1Object())); + } + + if (ocspids != null) + { + v.Add(new DerTaggedObject(true, 1, ocspids.ToAsn1Object())); + } + + if (otherRev != null) + { + v.Add(new DerTaggedObject(true, 2, otherRev.ToAsn1Object())); + } + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/esf/CrlValidatedID.cs b/bc-sharp-crypto/src/asn1/esf/CrlValidatedID.cs new file mode 100644 index 0000000..e8cd17a --- /dev/null +++ b/bc-sharp-crypto/src/asn1/esf/CrlValidatedID.cs @@ -0,0 +1,91 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Esf +{ + /// + /// RFC 3126: 4.2.2 Complete Revocation Refs Attribute Definition + /// + /// CrlValidatedID ::= SEQUENCE { + /// crlHash OtherHash, + /// crlIdentifier CrlIdentifier OPTIONAL} + /// + /// + public class CrlValidatedID + : Asn1Encodable + { + private readonly OtherHash crlHash; + private readonly CrlIdentifier crlIdentifier; + + public static CrlValidatedID GetInstance( + object obj) + { + if (obj == null || obj is CrlValidatedID) + return (CrlValidatedID) obj; + + if (obj is Asn1Sequence) + return new CrlValidatedID((Asn1Sequence) obj); + + throw new ArgumentException( + "Unknown object in 'CrlValidatedID' factory: " + + Platform.GetTypeName(obj), + "obj"); + } + + private CrlValidatedID( + Asn1Sequence seq) + { + if (seq == null) + throw new ArgumentNullException("seq"); + if (seq.Count < 1 || seq.Count > 2) + throw new ArgumentException("Bad sequence size: " + seq.Count, "seq"); + + this.crlHash = OtherHash.GetInstance(seq[0].ToAsn1Object()); + + if (seq.Count > 1) + { + this.crlIdentifier = CrlIdentifier.GetInstance(seq[1].ToAsn1Object()); + } + } + + public CrlValidatedID( + OtherHash crlHash) + : this(crlHash, null) + { + } + + public CrlValidatedID( + OtherHash crlHash, + CrlIdentifier crlIdentifier) + { + if (crlHash == null) + throw new ArgumentNullException("crlHash"); + + this.crlHash = crlHash; + this.crlIdentifier = crlIdentifier; + } + + public OtherHash CrlHash + { + get { return crlHash; } + } + + public CrlIdentifier CrlIdentifier + { + get { return crlIdentifier; } + } + + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(crlHash.ToAsn1Object()); + + if (crlIdentifier != null) + { + v.Add(crlIdentifier.ToAsn1Object()); + } + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/esf/ESFAttributes.cs b/bc-sharp-crypto/src/asn1/esf/ESFAttributes.cs new file mode 100644 index 0000000..9401ffb --- /dev/null +++ b/bc-sharp-crypto/src/asn1/esf/ESFAttributes.cs @@ -0,0 +1,25 @@ +using System; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Pkcs; + +namespace Org.BouncyCastle.Asn1.Esf +{ + public abstract class EsfAttributes + { + public static readonly DerObjectIdentifier SigPolicyId = PkcsObjectIdentifiers.IdAAEtsSigPolicyID; + public static readonly DerObjectIdentifier CommitmentType = PkcsObjectIdentifiers.IdAAEtsCommitmentType; + public static readonly DerObjectIdentifier SignerLocation = PkcsObjectIdentifiers.IdAAEtsSignerLocation; + public static readonly DerObjectIdentifier SignerAttr = PkcsObjectIdentifiers.IdAAEtsSignerAttr; + public static readonly DerObjectIdentifier OtherSigCert = PkcsObjectIdentifiers.IdAAEtsOtherSigCert; + public static readonly DerObjectIdentifier ContentTimestamp = PkcsObjectIdentifiers.IdAAEtsContentTimestamp; + public static readonly DerObjectIdentifier CertificateRefs = PkcsObjectIdentifiers.IdAAEtsCertificateRefs; + public static readonly DerObjectIdentifier RevocationRefs = PkcsObjectIdentifiers.IdAAEtsRevocationRefs; + public static readonly DerObjectIdentifier CertValues = PkcsObjectIdentifiers.IdAAEtsCertValues; + public static readonly DerObjectIdentifier RevocationValues = PkcsObjectIdentifiers.IdAAEtsRevocationValues; + public static readonly DerObjectIdentifier EscTimeStamp = PkcsObjectIdentifiers.IdAAEtsEscTimeStamp; + public static readonly DerObjectIdentifier CertCrlTimestamp = PkcsObjectIdentifiers.IdAAEtsCertCrlTimestamp; + public static readonly DerObjectIdentifier ArchiveTimestamp = PkcsObjectIdentifiers.IdAAEtsArchiveTimestamp; + public static readonly DerObjectIdentifier ArchiveTimestampV2 = new DerObjectIdentifier(PkcsObjectIdentifiers.IdAA + ".48"); + } +} diff --git a/bc-sharp-crypto/src/asn1/esf/OcspIdentifier.cs b/bc-sharp-crypto/src/asn1/esf/OcspIdentifier.cs new file mode 100644 index 0000000..e65f1cf --- /dev/null +++ b/bc-sharp-crypto/src/asn1/esf/OcspIdentifier.cs @@ -0,0 +1,78 @@ +using System; + +using Org.BouncyCastle.Asn1.Ocsp; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Esf +{ + /// + /// RFC 3126: 4.2.2 Complete Revocation Refs Attribute Definition + /// + /// OcspIdentifier ::= SEQUENCE { + /// ocspResponderID ResponderID, + /// -- As in OCSP response data + /// producedAt GeneralizedTime + /// -- As in OCSP response data + /// } + /// + /// + public class OcspIdentifier + : Asn1Encodable + { + private readonly ResponderID ocspResponderID; + private readonly DerGeneralizedTime producedAt; + + public static OcspIdentifier GetInstance( + object obj) + { + if (obj == null || obj is OcspIdentifier) + return (OcspIdentifier) obj; + + if (obj is Asn1Sequence) + return new OcspIdentifier((Asn1Sequence) obj); + + throw new ArgumentException( + "Unknown object in 'OcspIdentifier' factory: " + + Platform.GetTypeName(obj), + "obj"); + } + + private OcspIdentifier( + Asn1Sequence seq) + { + if (seq == null) + throw new ArgumentNullException("seq"); + if (seq.Count != 2) + throw new ArgumentException("Bad sequence size: " + seq.Count, "seq"); + + this.ocspResponderID = ResponderID.GetInstance(seq[0].ToAsn1Object()); + this.producedAt = (DerGeneralizedTime) seq[1].ToAsn1Object(); + } + + public OcspIdentifier( + ResponderID ocspResponderID, + DateTime producedAt) + { + if (ocspResponderID == null) + throw new ArgumentNullException(); + + this.ocspResponderID = ocspResponderID; + this.producedAt = new DerGeneralizedTime(producedAt); + } + + public ResponderID OcspResponderID + { + get { return ocspResponderID; } + } + + public DateTime ProducedAt + { + get { return producedAt.ToDateTime(); } + } + + public override Asn1Object ToAsn1Object() + { + return new DerSequence(ocspResponderID, producedAt); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/esf/OcspListID.cs b/bc-sharp-crypto/src/asn1/esf/OcspListID.cs new file mode 100644 index 0000000..1c8edb1 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/esf/OcspListID.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; + +namespace Org.BouncyCastle.Asn1.Esf +{ + /// + /// RFC 3126: 4.2.2 Complete Revocation Refs Attribute Definition + /// + /// OcspListID ::= SEQUENCE { + /// ocspResponses SEQUENCE OF OcspResponsesID + /// } + /// + /// + public class OcspListID + : Asn1Encodable + { + private readonly Asn1Sequence ocspResponses; + + public static OcspListID GetInstance( + object obj) + { + if (obj == null || obj is OcspListID) + return (OcspListID) obj; + + if (obj is Asn1Sequence) + return new OcspListID((Asn1Sequence) obj); + + throw new ArgumentException( + "Unknown object in 'OcspListID' factory: " + + Platform.GetTypeName(obj), + "obj"); + } + + private OcspListID( + Asn1Sequence seq) + { + if (seq == null) + throw new ArgumentNullException("seq"); + if (seq.Count != 1) + throw new ArgumentException("Bad sequence size: " + seq.Count, "seq"); + + this.ocspResponses = (Asn1Sequence) seq[0].ToAsn1Object(); + + foreach (Asn1Encodable ae in this.ocspResponses) + { + OcspResponsesID.GetInstance(ae.ToAsn1Object()); + } + } + + public OcspListID( + params OcspResponsesID[] ocspResponses) + { + if (ocspResponses == null) + throw new ArgumentNullException("ocspResponses"); + + this.ocspResponses = new DerSequence(ocspResponses); + } + + public OcspListID( + IEnumerable ocspResponses) + { + if (ocspResponses == null) + throw new ArgumentNullException("ocspResponses"); + if (!CollectionUtilities.CheckElementsAreOfType(ocspResponses, typeof(OcspResponsesID))) + throw new ArgumentException("Must contain only 'OcspResponsesID' objects", "ocspResponses"); + + this.ocspResponses = new DerSequence( + Asn1EncodableVector.FromEnumerable(ocspResponses)); + } + + public OcspResponsesID[] GetOcspResponses() + { + OcspResponsesID[] result = new OcspResponsesID[ocspResponses.Count]; + for (int i = 0; i < ocspResponses.Count; ++i) + { + result[i] = OcspResponsesID.GetInstance(ocspResponses[i].ToAsn1Object()); + } + return result; + } + + public override Asn1Object ToAsn1Object() + { + return new DerSequence(ocspResponses); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/esf/OcspResponsesID.cs b/bc-sharp-crypto/src/asn1/esf/OcspResponsesID.cs new file mode 100644 index 0000000..8718188 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/esf/OcspResponsesID.cs @@ -0,0 +1,94 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Esf +{ + /// + /// RFC 3126: 4.2.2 Complete Revocation Refs Attribute Definition + /// + /// OcspResponsesID ::= SEQUENCE { + /// ocspIdentifier OcspIdentifier, + /// ocspRepHash OtherHash OPTIONAL + /// } + /// + /// + public class OcspResponsesID + : Asn1Encodable + { + private readonly OcspIdentifier ocspIdentifier; + private readonly OtherHash ocspRepHash; + + public static OcspResponsesID GetInstance( + object obj) + { + if (obj == null || obj is OcspResponsesID) + return (OcspResponsesID) obj; + + if (obj is Asn1Sequence) + return new OcspResponsesID((Asn1Sequence) obj); + + throw new ArgumentException( + "Unknown object in 'OcspResponsesID' factory: " + + Platform.GetTypeName(obj), + "obj"); + } + + private OcspResponsesID( + Asn1Sequence seq) + { + if (seq == null) + throw new ArgumentNullException("seq"); + if (seq.Count < 1 || seq.Count > 2) + throw new ArgumentException("Bad sequence size: " + seq.Count, "seq"); + + this.ocspIdentifier = OcspIdentifier.GetInstance(seq[0].ToAsn1Object()); + + if (seq.Count > 1) + { + this.ocspRepHash = OtherHash.GetInstance(seq[1].ToAsn1Object()); + } + } + + public OcspResponsesID( + OcspIdentifier ocspIdentifier) + : this(ocspIdentifier, null) + { + } + + public OcspResponsesID( + OcspIdentifier ocspIdentifier, + OtherHash ocspRepHash) + { + if (ocspIdentifier == null) + throw new ArgumentNullException("ocspIdentifier"); + + this.ocspIdentifier = ocspIdentifier; + this.ocspRepHash = ocspRepHash; + } + + public OcspIdentifier OcspIdentifier + { + get { return ocspIdentifier; } + } + + public OtherHash OcspRepHash + { + get { return ocspRepHash; } + } + + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector( + ocspIdentifier.ToAsn1Object()); + + if (ocspRepHash != null) + { + v.Add(ocspRepHash.ToAsn1Object()); + } + + return new DerSequence(v); + } + + } +} diff --git a/bc-sharp-crypto/src/asn1/esf/OtherCertID.cs b/bc-sharp-crypto/src/asn1/esf/OtherCertID.cs new file mode 100644 index 0000000..19d173a --- /dev/null +++ b/bc-sharp-crypto/src/asn1/esf/OtherCertID.cs @@ -0,0 +1,94 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Esf +{ + /// + /// + /// OtherCertID ::= SEQUENCE { + /// otherCertHash OtherHash, + /// issuerSerial IssuerSerial OPTIONAL + /// } + /// + /// + public class OtherCertID + : Asn1Encodable + { + private readonly OtherHash otherCertHash; + private readonly IssuerSerial issuerSerial; + + public static OtherCertID GetInstance( + object obj) + { + if (obj == null || obj is OtherCertID) + return (OtherCertID) obj; + + if (obj is Asn1Sequence) + return new OtherCertID((Asn1Sequence) obj); + + throw new ArgumentException( + "Unknown object in 'OtherCertID' factory: " + + Platform.GetTypeName(obj), + "obj"); + } + + private OtherCertID( + Asn1Sequence seq) + { + if (seq == null) + throw new ArgumentNullException("seq"); + if (seq.Count < 1 || seq.Count > 2) + throw new ArgumentException("Bad sequence size: " + seq.Count, "seq"); + + this.otherCertHash = OtherHash.GetInstance(seq[0].ToAsn1Object()); + + if (seq.Count > 1) + { + this.issuerSerial = IssuerSerial.GetInstance(seq[1].ToAsn1Object()); + } + } + + public OtherCertID( + OtherHash otherCertHash) + : this(otherCertHash, null) + { + } + + public OtherCertID( + OtherHash otherCertHash, + IssuerSerial issuerSerial) + { + if (otherCertHash == null) + throw new ArgumentNullException("otherCertHash"); + + this.otherCertHash = otherCertHash; + this.issuerSerial = issuerSerial; + } + + public OtherHash OtherCertHash + { + get { return otherCertHash; } + } + + public IssuerSerial IssuerSerial + { + get { return issuerSerial; } + } + + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector( + otherCertHash.ToAsn1Object()); + + if (issuerSerial != null) + { + v.Add(issuerSerial.ToAsn1Object()); + } + + return new DerSequence(v); + } + + } +} diff --git a/bc-sharp-crypto/src/asn1/esf/OtherHash.cs b/bc-sharp-crypto/src/asn1/esf/OtherHash.cs new file mode 100644 index 0000000..2ee1624 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/esf/OtherHash.cs @@ -0,0 +1,88 @@ +using System; + +using Org.BouncyCastle.Asn1.Oiw; +using Org.BouncyCastle.Asn1.X509; + +namespace Org.BouncyCastle.Asn1.Esf +{ + /// + /// + /// OtherHash ::= CHOICE { + /// sha1Hash OtherHashValue, -- This contains a SHA-1 hash + /// otherHash OtherHashAlgAndValue + /// } + /// + /// OtherHashValue ::= OCTET STRING + /// + /// + public class OtherHash + : Asn1Encodable, IAsn1Choice + { + private readonly Asn1OctetString sha1Hash; + private readonly OtherHashAlgAndValue otherHash; + + public static OtherHash GetInstance( + object obj) + { + if (obj == null || obj is OtherHash) + return (OtherHash) obj; + + if (obj is Asn1OctetString) + return new OtherHash((Asn1OctetString) obj); + + return new OtherHash( + OtherHashAlgAndValue.GetInstance(obj)); + } + + public OtherHash( + byte[] sha1Hash) + { + if (sha1Hash == null) + throw new ArgumentNullException("sha1Hash"); + + this.sha1Hash = new DerOctetString(sha1Hash); + } + + public OtherHash( + Asn1OctetString sha1Hash) + { + if (sha1Hash == null) + throw new ArgumentNullException("sha1Hash"); + + this.sha1Hash = sha1Hash; + } + + public OtherHash( + OtherHashAlgAndValue otherHash) + { + if (otherHash == null) + throw new ArgumentNullException("otherHash"); + + this.otherHash = otherHash; + } + + public AlgorithmIdentifier HashAlgorithm + { + get + { + return otherHash == null + ? new AlgorithmIdentifier(OiwObjectIdentifiers.IdSha1) + : otherHash.HashAlgorithm; + } + } + + public byte[] GetHashValue() + { + return otherHash == null + ? sha1Hash.GetOctets() + : otherHash.GetHashValue(); + } + + public override Asn1Object ToAsn1Object() + { + return otherHash == null + ? sha1Hash + : otherHash.ToAsn1Object(); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/esf/OtherHashAlgAndValue.cs b/bc-sharp-crypto/src/asn1/esf/OtherHashAlgAndValue.cs new file mode 100644 index 0000000..00eb24c --- /dev/null +++ b/bc-sharp-crypto/src/asn1/esf/OtherHashAlgAndValue.cs @@ -0,0 +1,95 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Esf +{ + /// + /// Summary description for OtherHashAlgAndValue. + /// + /// + /// + /// OtherHashAlgAndValue ::= SEQUENCE { + /// hashAlgorithm AlgorithmIdentifier, + /// hashValue OtherHashValue + /// } + /// + /// OtherHashValue ::= OCTET STRING + /// + /// + public class OtherHashAlgAndValue + : Asn1Encodable + { + private readonly AlgorithmIdentifier hashAlgorithm; + private readonly Asn1OctetString hashValue; + + public static OtherHashAlgAndValue GetInstance( + object obj) + { + if (obj == null || obj is OtherHashAlgAndValue) + return (OtherHashAlgAndValue) obj; + + if (obj is Asn1Sequence) + return new OtherHashAlgAndValue((Asn1Sequence) obj); + + throw new ArgumentException( + "Unknown object in 'OtherHashAlgAndValue' factory: " + + Platform.GetTypeName(obj), + "obj"); + } + + private OtherHashAlgAndValue( + Asn1Sequence seq) + { + if (seq == null) + throw new ArgumentNullException("seq"); + if (seq.Count != 2) + throw new ArgumentException("Bad sequence size: " + seq.Count, "seq"); + + this.hashAlgorithm = AlgorithmIdentifier.GetInstance(seq[0].ToAsn1Object()); + this.hashValue = (Asn1OctetString) seq[1].ToAsn1Object(); + } + + public OtherHashAlgAndValue( + AlgorithmIdentifier hashAlgorithm, + byte[] hashValue) + { + if (hashAlgorithm == null) + throw new ArgumentNullException("hashAlgorithm"); + if (hashValue == null) + throw new ArgumentNullException("hashValue"); + + this.hashAlgorithm = hashAlgorithm; + this.hashValue = new DerOctetString(hashValue); + } + + public OtherHashAlgAndValue( + AlgorithmIdentifier hashAlgorithm, + Asn1OctetString hashValue) + { + if (hashAlgorithm == null) + throw new ArgumentNullException("hashAlgorithm"); + if (hashValue == null) + throw new ArgumentNullException("hashValue"); + + this.hashAlgorithm = hashAlgorithm; + this.hashValue = hashValue; + } + + public AlgorithmIdentifier HashAlgorithm + { + get { return hashAlgorithm; } + } + + public byte[] GetHashValue() + { + return hashValue.GetOctets(); + } + + public override Asn1Object ToAsn1Object() + { + return new DerSequence(hashAlgorithm, hashValue); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/esf/OtherRevRefs.cs b/bc-sharp-crypto/src/asn1/esf/OtherRevRefs.cs new file mode 100644 index 0000000..446031e --- /dev/null +++ b/bc-sharp-crypto/src/asn1/esf/OtherRevRefs.cs @@ -0,0 +1,80 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Esf +{ + /// + /// RFC 3126: 4.2.2 Complete Revocation Refs Attribute Definition + /// + /// OtherRevRefs ::= SEQUENCE + /// { + /// otherRevRefType OtherRevRefType, + /// otherRevRefs ANY DEFINED BY otherRevRefType + /// } + /// + /// OtherRevRefType ::= OBJECT IDENTIFIER + /// + /// + public class OtherRevRefs + : Asn1Encodable + { + private readonly DerObjectIdentifier otherRevRefType; + private readonly Asn1Object otherRevRefs; + + public static OtherRevRefs GetInstance( + object obj) + { + if (obj == null || obj is OtherRevRefs) + return (OtherRevRefs) obj; + + if (obj is Asn1Sequence) + return new OtherRevRefs((Asn1Sequence) obj); + + throw new ArgumentException( + "Unknown object in 'OtherRevRefs' factory: " + + Platform.GetTypeName(obj), + "obj"); + } + + private OtherRevRefs( + Asn1Sequence seq) + { + if (seq == null) + throw new ArgumentNullException("seq"); + if (seq.Count != 2) + throw new ArgumentException("Bad sequence size: " + seq.Count, "seq"); + + this.otherRevRefType = (DerObjectIdentifier) seq[0].ToAsn1Object(); + this.otherRevRefs = seq[1].ToAsn1Object(); + } + + public OtherRevRefs( + DerObjectIdentifier otherRevRefType, + Asn1Encodable otherRevRefs) + { + if (otherRevRefType == null) + throw new ArgumentNullException("otherRevRefType"); + if (otherRevRefs == null) + throw new ArgumentNullException("otherRevRefs"); + + this.otherRevRefType = otherRevRefType; + this.otherRevRefs = otherRevRefs.ToAsn1Object(); + } + + public DerObjectIdentifier OtherRevRefType + { + get { return otherRevRefType; } + } + + public Asn1Object OtherRevRefsObject + { + get { return otherRevRefs; } + } + + public override Asn1Object ToAsn1Object() + { + return new DerSequence(otherRevRefType, otherRevRefs); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/esf/OtherRevVals.cs b/bc-sharp-crypto/src/asn1/esf/OtherRevVals.cs new file mode 100644 index 0000000..7b90456 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/esf/OtherRevVals.cs @@ -0,0 +1,80 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Esf +{ + /// + /// RFC 3126: 4.3.2 Revocation Values Attribute Definition + /// + /// OtherRevVals ::= SEQUENCE + /// { + /// otherRevValType OtherRevValType, + /// otherRevVals ANY DEFINED BY otherRevValType + /// } + /// + /// OtherRevValType ::= OBJECT IDENTIFIER + /// + /// + public class OtherRevVals + : Asn1Encodable + { + private readonly DerObjectIdentifier otherRevValType; + private readonly Asn1Object otherRevVals; + + public static OtherRevVals GetInstance( + object obj) + { + if (obj == null || obj is OtherRevVals) + return (OtherRevVals) obj; + + if (obj is Asn1Sequence) + return new OtherRevVals((Asn1Sequence) obj); + + throw new ArgumentException( + "Unknown object in 'OtherRevVals' factory: " + + Platform.GetTypeName(obj), + "obj"); + } + + private OtherRevVals( + Asn1Sequence seq) + { + if (seq == null) + throw new ArgumentNullException("seq"); + if (seq.Count != 2) + throw new ArgumentException("Bad sequence size: " + seq.Count, "seq"); + + this.otherRevValType = (DerObjectIdentifier) seq[0].ToAsn1Object(); + this.otherRevVals = seq[1].ToAsn1Object(); + } + + public OtherRevVals( + DerObjectIdentifier otherRevValType, + Asn1Encodable otherRevVals) + { + if (otherRevValType == null) + throw new ArgumentNullException("otherRevValType"); + if (otherRevVals == null) + throw new ArgumentNullException("otherRevVals"); + + this.otherRevValType = otherRevValType; + this.otherRevVals = otherRevVals.ToAsn1Object(); + } + + public DerObjectIdentifier OtherRevValType + { + get { return otherRevValType; } + } + + public Asn1Object OtherRevValsObject + { + get { return otherRevVals; } + } + + public override Asn1Object ToAsn1Object() + { + return new DerSequence(otherRevValType, otherRevVals); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/esf/OtherSigningCertificate.cs b/bc-sharp-crypto/src/asn1/esf/OtherSigningCertificate.cs new file mode 100644 index 0000000..f7b9f5e --- /dev/null +++ b/bc-sharp-crypto/src/asn1/esf/OtherSigningCertificate.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; + +namespace Org.BouncyCastle.Asn1.Esf +{ + /// + /// + /// OtherSigningCertificate ::= SEQUENCE { + /// certs SEQUENCE OF OtherCertID, + /// policies SEQUENCE OF PolicyInformation OPTIONAL + /// } + /// + /// + public class OtherSigningCertificate + : Asn1Encodable + { + private readonly Asn1Sequence certs; + private readonly Asn1Sequence policies; + + public static OtherSigningCertificate GetInstance( + object obj) + { + if (obj == null || obj is OtherSigningCertificate) + return (OtherSigningCertificate) obj; + + if (obj is Asn1Sequence) + return new OtherSigningCertificate((Asn1Sequence) obj); + + throw new ArgumentException( + "Unknown object in 'OtherSigningCertificate' factory: " + + Platform.GetTypeName(obj), + "obj"); + } + + private OtherSigningCertificate( + Asn1Sequence seq) + { + if (seq == null) + throw new ArgumentNullException("seq"); + if (seq.Count < 1 || seq.Count > 2) + throw new ArgumentException("Bad sequence size: " + seq.Count, "seq"); + + this.certs = Asn1Sequence.GetInstance(seq[0].ToAsn1Object()); + + if (seq.Count > 1) + { + this.policies = Asn1Sequence.GetInstance(seq[1].ToAsn1Object()); + } + } + + public OtherSigningCertificate( + params OtherCertID[] certs) + : this(certs, null) + { + } + + public OtherSigningCertificate( + OtherCertID[] certs, + params PolicyInformation[] policies) + { + if (certs == null) + throw new ArgumentNullException("certs"); + + this.certs = new DerSequence(certs); + + if (policies != null) + { + this.policies = new DerSequence(policies); + } + } + + public OtherSigningCertificate( + IEnumerable certs) + : this(certs, null) + { + } + + public OtherSigningCertificate( + IEnumerable certs, + IEnumerable policies) + { + if (certs == null) + throw new ArgumentNullException("certs"); + if (!CollectionUtilities.CheckElementsAreOfType(certs, typeof(OtherCertID))) + throw new ArgumentException("Must contain only 'OtherCertID' objects", "certs"); + + this.certs = new DerSequence( + Asn1EncodableVector.FromEnumerable(certs)); + + if (policies != null) + { + if (!CollectionUtilities.CheckElementsAreOfType(policies, typeof(PolicyInformation))) + throw new ArgumentException("Must contain only 'PolicyInformation' objects", "policies"); + + this.policies = new DerSequence( + Asn1EncodableVector.FromEnumerable(policies)); + } + } + + public OtherCertID[] GetCerts() + { + OtherCertID[] cs = new OtherCertID[certs.Count]; + for (int i = 0; i < certs.Count; ++i) + { + cs[i] = OtherCertID.GetInstance(certs[i].ToAsn1Object()); + } + return cs; + } + + public PolicyInformation[] GetPolicies() + { + if (policies == null) + return null; + + PolicyInformation[] ps = new PolicyInformation[policies.Count]; + for (int i = 0; i < policies.Count; ++i) + { + ps[i] = PolicyInformation.GetInstance(policies[i].ToAsn1Object()); + } + return ps; + } + + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(certs); + + if (policies != null) + { + v.Add(policies); + } + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/esf/RevocationValues.cs b/bc-sharp-crypto/src/asn1/esf/RevocationValues.cs new file mode 100644 index 0000000..a7b47b4 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/esf/RevocationValues.cs @@ -0,0 +1,165 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1.Ocsp; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities.Collections; + +namespace Org.BouncyCastle.Asn1.Esf +{ + /// + /// RFC 5126: 6.3.4. revocation-values Attribute Definition + /// + /// RevocationValues ::= SEQUENCE { + /// crlVals [0] SEQUENCE OF CertificateList OPTIONAL, + /// ocspVals [1] SEQUENCE OF BasicOCSPResponse OPTIONAL, + /// otherRevVals [2] OtherRevVals OPTIONAL + /// } + /// + /// + public class RevocationValues + : Asn1Encodable + { + private readonly Asn1Sequence crlVals; + private readonly Asn1Sequence ocspVals; + private readonly OtherRevVals otherRevVals; + + public static RevocationValues GetInstance( + object obj) + { + if (obj == null || obj is RevocationValues) + return (RevocationValues) obj; + + return new RevocationValues(Asn1Sequence.GetInstance(obj)); + } + + private RevocationValues( + Asn1Sequence seq) + { + if (seq == null) + throw new ArgumentNullException("seq"); + if (seq.Count > 3) + throw new ArgumentException("Bad sequence size: " + seq.Count, "seq"); + + foreach (Asn1TaggedObject taggedObj in seq) + { + Asn1Object asn1Obj = taggedObj.GetObject(); + switch (taggedObj.TagNo) + { + case 0: + Asn1Sequence crlValsSeq = (Asn1Sequence) asn1Obj; + foreach (Asn1Encodable ae in crlValsSeq) + { + CertificateList.GetInstance(ae.ToAsn1Object()); + } + this.crlVals = crlValsSeq; + break; + case 1: + Asn1Sequence ocspValsSeq = (Asn1Sequence) asn1Obj; + foreach (Asn1Encodable ae in ocspValsSeq) + { + BasicOcspResponse.GetInstance(ae.ToAsn1Object()); + } + this.ocspVals = ocspValsSeq; + break; + case 2: + this.otherRevVals = OtherRevVals.GetInstance(asn1Obj); + break; + default: + throw new ArgumentException("Illegal tag in RevocationValues", "seq"); + } + } + } + + public RevocationValues( + CertificateList[] crlVals, + BasicOcspResponse[] ocspVals, + OtherRevVals otherRevVals) + { + if (crlVals != null) + { + this.crlVals = new DerSequence(crlVals); + } + + if (ocspVals != null) + { + this.ocspVals = new DerSequence(ocspVals); + } + + this.otherRevVals = otherRevVals; + } + + public RevocationValues( + IEnumerable crlVals, + IEnumerable ocspVals, + OtherRevVals otherRevVals) + { + if (crlVals != null) + { + if (!CollectionUtilities.CheckElementsAreOfType(crlVals, typeof(CertificateList))) + throw new ArgumentException("Must contain only 'CertificateList' objects", "crlVals"); + + this.crlVals = new DerSequence( + Asn1EncodableVector.FromEnumerable(crlVals)); + } + + if (ocspVals != null) + { + if (!CollectionUtilities.CheckElementsAreOfType(ocspVals, typeof(BasicOcspResponse))) + throw new ArgumentException("Must contain only 'BasicOcspResponse' objects", "ocspVals"); + + this.ocspVals = new DerSequence( + Asn1EncodableVector.FromEnumerable(ocspVals)); + } + + this.otherRevVals = otherRevVals; + } + + public CertificateList[] GetCrlVals() + { + CertificateList[] result = new CertificateList[crlVals.Count]; + for (int i = 0; i < crlVals.Count; ++i) + { + result[i] = CertificateList.GetInstance(crlVals[i].ToAsn1Object()); + } + return result; + } + + public BasicOcspResponse[] GetOcspVals() + { + BasicOcspResponse[] result = new BasicOcspResponse[ocspVals.Count]; + for (int i = 0; i < ocspVals.Count; ++i) + { + result[i] = BasicOcspResponse.GetInstance(ocspVals[i].ToAsn1Object()); + } + return result; + } + + public OtherRevVals OtherRevVals + { + get { return otherRevVals; } + } + + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(); + + if (crlVals != null) + { + v.Add(new DerTaggedObject(true, 0, crlVals)); + } + + if (ocspVals != null) + { + v.Add(new DerTaggedObject(true, 1, ocspVals)); + } + + if (otherRevVals != null) + { + v.Add(new DerTaggedObject(true, 2, otherRevVals.ToAsn1Object())); + } + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/esf/SigPolicyQualifierInfo.cs b/bc-sharp-crypto/src/asn1/esf/SigPolicyQualifierInfo.cs new file mode 100644 index 0000000..470c5c8 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/esf/SigPolicyQualifierInfo.cs @@ -0,0 +1,73 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Esf +{ + /// + /// + /// SigPolicyQualifierInfo ::= SEQUENCE { + /// sigPolicyQualifierId SigPolicyQualifierId, + /// sigQualifier ANY DEFINED BY sigPolicyQualifierId + /// } + /// + /// SigPolicyQualifierId ::= OBJECT IDENTIFIER + /// + /// + public class SigPolicyQualifierInfo + : Asn1Encodable + { + private readonly DerObjectIdentifier sigPolicyQualifierId; + private readonly Asn1Object sigQualifier; + + public static SigPolicyQualifierInfo GetInstance( + object obj) + { + if (obj == null || obj is SigPolicyQualifierInfo) + return (SigPolicyQualifierInfo) obj; + + if (obj is Asn1Sequence) + return new SigPolicyQualifierInfo((Asn1Sequence) obj); + + throw new ArgumentException( + "Unknown object in 'SigPolicyQualifierInfo' factory: " + + Platform.GetTypeName(obj), + "obj"); + } + + private SigPolicyQualifierInfo( + Asn1Sequence seq) + { + if (seq == null) + throw new ArgumentNullException("seq"); + if (seq.Count != 2) + throw new ArgumentException("Bad sequence size: " + seq.Count, "seq"); + + this.sigPolicyQualifierId = (DerObjectIdentifier) seq[0].ToAsn1Object(); + this.sigQualifier = seq[1].ToAsn1Object(); + } + + public SigPolicyQualifierInfo( + DerObjectIdentifier sigPolicyQualifierId, + Asn1Encodable sigQualifier) + { + this.sigPolicyQualifierId = sigPolicyQualifierId; + this.sigQualifier = sigQualifier.ToAsn1Object(); + } + + public DerObjectIdentifier SigPolicyQualifierId + { + get { return sigPolicyQualifierId; } + } + + public Asn1Object SigQualifier + { + get { return sigQualifier; } + } + + public override Asn1Object ToAsn1Object() + { + return new DerSequence(sigPolicyQualifierId, sigQualifier); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/esf/SignaturePolicyId.cs b/bc-sharp-crypto/src/asn1/esf/SignaturePolicyId.cs new file mode 100644 index 0000000..7146bb4 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/esf/SignaturePolicyId.cs @@ -0,0 +1,146 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; + +namespace Org.BouncyCastle.Asn1.Esf +{ + /// + /// + /// SignaturePolicyId ::= SEQUENCE { + /// sigPolicyIdentifier SigPolicyId, + /// sigPolicyHash SigPolicyHash, + /// sigPolicyQualifiers SEQUENCE SIZE (1..MAX) OF SigPolicyQualifierInfo OPTIONAL + /// } + /// + /// SigPolicyId ::= OBJECT IDENTIFIER + /// + /// SigPolicyHash ::= OtherHashAlgAndValue + /// + /// + public class SignaturePolicyId + : Asn1Encodable + { + private readonly DerObjectIdentifier sigPolicyIdentifier; + private readonly OtherHashAlgAndValue sigPolicyHash; + private readonly Asn1Sequence sigPolicyQualifiers; + + public static SignaturePolicyId GetInstance( + object obj) + { + if (obj == null || obj is SignaturePolicyId) + return (SignaturePolicyId) obj; + + if (obj is Asn1Sequence) + return new SignaturePolicyId((Asn1Sequence) obj); + + throw new ArgumentException( + "Unknown object in 'SignaturePolicyId' factory: " + + Platform.GetTypeName(obj), + "obj"); + } + + private SignaturePolicyId( + Asn1Sequence seq) + { + if (seq == null) + throw new ArgumentNullException("seq"); + if (seq.Count < 2 || seq.Count > 3) + throw new ArgumentException("Bad sequence size: " + seq.Count, "seq"); + + this.sigPolicyIdentifier = (DerObjectIdentifier) seq[0].ToAsn1Object(); + this.sigPolicyHash = OtherHashAlgAndValue.GetInstance(seq[1].ToAsn1Object()); + + if (seq.Count > 2) + { + this.sigPolicyQualifiers = (Asn1Sequence) seq[2].ToAsn1Object(); + } + } + + public SignaturePolicyId( + DerObjectIdentifier sigPolicyIdentifier, + OtherHashAlgAndValue sigPolicyHash) + : this(sigPolicyIdentifier, sigPolicyHash, null) + { + } + + public SignaturePolicyId( + DerObjectIdentifier sigPolicyIdentifier, + OtherHashAlgAndValue sigPolicyHash, + params SigPolicyQualifierInfo[] sigPolicyQualifiers) + { + if (sigPolicyIdentifier == null) + throw new ArgumentNullException("sigPolicyIdentifier"); + if (sigPolicyHash == null) + throw new ArgumentNullException("sigPolicyHash"); + + this.sigPolicyIdentifier = sigPolicyIdentifier; + this.sigPolicyHash = sigPolicyHash; + + if (sigPolicyQualifiers != null) + { + this.sigPolicyQualifiers = new DerSequence(sigPolicyQualifiers); + } + } + + public SignaturePolicyId( + DerObjectIdentifier sigPolicyIdentifier, + OtherHashAlgAndValue sigPolicyHash, + IEnumerable sigPolicyQualifiers) + { + if (sigPolicyIdentifier == null) + throw new ArgumentNullException("sigPolicyIdentifier"); + if (sigPolicyHash == null) + throw new ArgumentNullException("sigPolicyHash"); + + this.sigPolicyIdentifier = sigPolicyIdentifier; + this.sigPolicyHash = sigPolicyHash; + + if (sigPolicyQualifiers != null) + { + if (!CollectionUtilities.CheckElementsAreOfType(sigPolicyQualifiers, typeof(SigPolicyQualifierInfo))) + throw new ArgumentException("Must contain only 'SigPolicyQualifierInfo' objects", "sigPolicyQualifiers"); + + this.sigPolicyQualifiers = new DerSequence( + Asn1EncodableVector.FromEnumerable(sigPolicyQualifiers)); + } + } + + public DerObjectIdentifier SigPolicyIdentifier + { + get { return sigPolicyIdentifier; } + } + + public OtherHashAlgAndValue SigPolicyHash + { + get { return sigPolicyHash; } + } + + public SigPolicyQualifierInfo[] GetSigPolicyQualifiers() + { + if (sigPolicyQualifiers == null) + return null; + + SigPolicyQualifierInfo[] infos = new SigPolicyQualifierInfo[sigPolicyQualifiers.Count]; + for (int i = 0; i < sigPolicyQualifiers.Count; ++i) + { + infos[i] = SigPolicyQualifierInfo.GetInstance(sigPolicyQualifiers[i]); + } + return infos; + } + + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector( + sigPolicyIdentifier, sigPolicyHash.ToAsn1Object()); + + if (sigPolicyQualifiers != null) + { + v.Add(sigPolicyQualifiers.ToAsn1Object()); + } + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/esf/SignaturePolicyIdentifier.cs b/bc-sharp-crypto/src/asn1/esf/SignaturePolicyIdentifier.cs new file mode 100644 index 0000000..12257f2 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/esf/SignaturePolicyIdentifier.cs @@ -0,0 +1,66 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Esf +{ + /// + /// + /// SignaturePolicyIdentifier ::= CHOICE { + /// SignaturePolicyId SignaturePolicyId, + /// SignaturePolicyImplied SignaturePolicyImplied + /// } + /// + /// SignaturePolicyImplied ::= NULL + /// + /// + public class SignaturePolicyIdentifier + : Asn1Encodable, IAsn1Choice + { + private readonly SignaturePolicyId sigPolicy; + + public static SignaturePolicyIdentifier GetInstance( + object obj) + { + if (obj == null || obj is SignaturePolicyIdentifier) + return (SignaturePolicyIdentifier) obj; + + if (obj is SignaturePolicyId) + return new SignaturePolicyIdentifier((SignaturePolicyId) obj); + + if (obj is Asn1Null) + return new SignaturePolicyIdentifier(); + + throw new ArgumentException( + "Unknown object in 'SignaturePolicyIdentifier' factory: " + + Platform.GetTypeName(obj), + "obj"); + } + + public SignaturePolicyIdentifier() + { + this.sigPolicy = null; + } + + public SignaturePolicyIdentifier( + SignaturePolicyId signaturePolicyId) + { + if (signaturePolicyId == null) + throw new ArgumentNullException("signaturePolicyId"); + + this.sigPolicy = signaturePolicyId; + } + + public SignaturePolicyId SignaturePolicyId + { + get { return sigPolicy; } + } + + public override Asn1Object ToAsn1Object() + { + return sigPolicy == null + ? DerNull.Instance + : sigPolicy.ToAsn1Object(); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/esf/SignerAttribute.cs b/bc-sharp-crypto/src/asn1/esf/SignerAttribute.cs new file mode 100644 index 0000000..39bd910 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/esf/SignerAttribute.cs @@ -0,0 +1,97 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Esf +{ + public class SignerAttribute + : Asn1Encodable + { + private Asn1Sequence claimedAttributes; + private AttributeCertificate certifiedAttributes; + + public static SignerAttribute GetInstance( + object obj) + { + if (obj == null || obj is SignerAttribute) + return (SignerAttribute) obj; + + if (obj is Asn1Sequence) + return new SignerAttribute(obj); + + throw new ArgumentException( + "Unknown object in 'SignerAttribute' factory: " + + Platform.GetTypeName(obj), + "obj"); + } + + private SignerAttribute( + object obj) + { + Asn1Sequence seq = (Asn1Sequence) obj; + DerTaggedObject taggedObject = (DerTaggedObject) seq[0]; + if (taggedObject.TagNo == 0) + { + claimedAttributes = Asn1Sequence.GetInstance(taggedObject, true); + } + else if (taggedObject.TagNo == 1) + { + certifiedAttributes = AttributeCertificate.GetInstance(taggedObject); + } + else + { + throw new ArgumentException("illegal tag.", "obj"); + } + } + + public SignerAttribute( + Asn1Sequence claimedAttributes) + { + this.claimedAttributes = claimedAttributes; + } + + public SignerAttribute( + AttributeCertificate certifiedAttributes) + { + this.certifiedAttributes = certifiedAttributes; + } + + public virtual Asn1Sequence ClaimedAttributes + { + get { return claimedAttributes; } + } + + public virtual AttributeCertificate CertifiedAttributes + { + get { 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 override Asn1Object 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/bc-sharp-crypto/src/asn1/esf/SignerLocation.cs b/bc-sharp-crypto/src/asn1/esf/SignerLocation.cs new file mode 100644 index 0000000..d2cef51 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/esf/SignerLocation.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1; + +namespace Org.BouncyCastle.Asn1.Esf +{ + /** + * 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 + : Asn1Encodable + { + // TODO Should these be using DirectoryString? + private DerUtf8String countryName; + private DerUtf8String localityName; + private Asn1Sequence postalAddress; + + public SignerLocation( + Asn1Sequence seq) + { + foreach (Asn1TaggedObject obj in seq) + { + switch (obj.TagNo) + { + case 0: + this.countryName = DerUtf8String.GetInstance(obj, true); + break; + case 1: + this.localityName = DerUtf8String.GetInstance(obj, true); + break; + case 2: + bool isExplicit = obj.IsExplicit(); // handle erroneous implicitly tagged sequences + this.postalAddress = Asn1Sequence.GetInstance(obj, isExplicit); + if (postalAddress != null && postalAddress.Count > 6) + throw new ArgumentException("postal address must contain less than 6 strings"); + break; + default: + throw new ArgumentException("illegal tag"); + } + } + } + + public SignerLocation( + DerUtf8String countryName, + DerUtf8String localityName, + Asn1Sequence postalAddress) + { + if (postalAddress != null && postalAddress.Count > 6) + { + throw new ArgumentException("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) postalAddress.ToAsn1Object(); + } + } + + public static SignerLocation GetInstance( + object obj) + { + if (obj == null || obj is SignerLocation) + { + return (SignerLocation) obj; + } + + return new SignerLocation(Asn1Sequence.GetInstance(obj)); + } + + public DerUtf8String CountryName + { + get { return countryName; } + } + + public DerUtf8String LocalityName + { + get { return localityName; } + } + + public Asn1Sequence PostalAddress + { + get { 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 override Asn1Object 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); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/ess/ContentHints.cs b/bc-sharp-crypto/src/asn1/ess/ContentHints.cs new file mode 100644 index 0000000..cfd174b --- /dev/null +++ b/bc-sharp-crypto/src/asn1/ess/ContentHints.cs @@ -0,0 +1,94 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Ess +{ + public class ContentHints + : Asn1Encodable + { + private readonly DerUtf8String contentDescription; + private readonly DerObjectIdentifier contentType; + + public static ContentHints GetInstance( + object o) + { + if (o == null || o is ContentHints) + { + return (ContentHints)o; + } + + if (o is Asn1Sequence) + { + return new ContentHints((Asn1Sequence)o); + } + + throw new ArgumentException("unknown object in 'ContentHints' factory : " + + Platform.GetTypeName(o) + "."); + } + + /** + * constructor + */ + private ContentHints( + Asn1Sequence seq) + { + IAsn1Convertible field = seq[0]; + if (field.ToAsn1Object() is DerUtf8String) + { + contentDescription = DerUtf8String.GetInstance(field); + contentType = DerObjectIdentifier.GetInstance(seq[1]); + } + else + { + contentType = DerObjectIdentifier.GetInstance(seq[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 ContentType + { + get { return contentType; } + } + + public DerUtf8String ContentDescription + { + get { return contentDescription; } + } + + /** + *
+		 * ContentHints ::= SEQUENCE {
+		 *   contentDescription UTF8String (SIZE (1..MAX)) OPTIONAL,
+		 *   contentType ContentType }
+		 * 
+ */ + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(); + + if (contentDescription != null) + { + v.Add(contentDescription); + } + + v.Add(contentType); + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/ess/ContentIdentifier.cs b/bc-sharp-crypto/src/asn1/ess/ContentIdentifier.cs new file mode 100644 index 0000000..430185e --- /dev/null +++ b/bc-sharp-crypto/src/asn1/ess/ContentIdentifier.cs @@ -0,0 +1,67 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Ess +{ + public class ContentIdentifier + : Asn1Encodable + { + private Asn1OctetString value; + + public static ContentIdentifier GetInstance( + object o) + { + if (o == null || o is ContentIdentifier) + { + return (ContentIdentifier) o; + } + + if (o is Asn1OctetString) + { + return new ContentIdentifier((Asn1OctetString) o); + } + + throw new ArgumentException( + "unknown object in 'ContentIdentifier' factory : " + + Platform.GetTypeName(o) + "."); + } + + /** + * 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 Value + { + get { 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 override Asn1Object ToAsn1Object() + { + return value; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/ess/ESSCertID.cs b/bc-sharp-crypto/src/asn1/ess/ESSCertID.cs new file mode 100644 index 0000000..b4465ea --- /dev/null +++ b/bc-sharp-crypto/src/asn1/ess/ESSCertID.cs @@ -0,0 +1,94 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Ess +{ + public class EssCertID + : Asn1Encodable + { + private Asn1OctetString certHash; + private IssuerSerial issuerSerial; + + public static EssCertID GetInstance( + object o) + { + if (o == null || o is EssCertID) + { + return (EssCertID) o; + } + + if (o is Asn1Sequence) + { + return new EssCertID((Asn1Sequence) o); + } + + throw new ArgumentException( + "unknown object in 'EssCertID' factory : " + + Platform.GetTypeName(o) + "."); + } + + /** + * constructor + */ + public EssCertID( + Asn1Sequence seq) + { + if (seq.Count < 1 || seq.Count > 2) + { + throw new ArgumentException("Bad sequence size: " + seq.Count); + } + + this.certHash = Asn1OctetString.GetInstance(seq[0]); + + if (seq.Count > 1) + { + issuerSerial = IssuerSerial.GetInstance(seq[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 IssuerSerial + { + get { return issuerSerial; } + } + + /** + *
+		 * EssCertID ::= SEQUENCE {
+		 *     certHash Hash,
+		 *     issuerSerial IssuerSerial OPTIONAL }
+		 * 
+ */ + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(certHash); + + if (issuerSerial != null) + { + v.Add(issuerSerial); + } + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/ess/ESSCertIDv2.cs b/bc-sharp-crypto/src/asn1/ess/ESSCertIDv2.cs new file mode 100644 index 0000000..35ce699 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/ess/ESSCertIDv2.cs @@ -0,0 +1,146 @@ +using System; + +using Org.BouncyCastle.Asn1.Nist; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Ess +{ + public class EssCertIDv2 + : Asn1Encodable + { + private readonly AlgorithmIdentifier hashAlgorithm; + private readonly byte[] certHash; + private readonly IssuerSerial issuerSerial; + + private static readonly AlgorithmIdentifier DefaultAlgID = new AlgorithmIdentifier( + NistObjectIdentifiers.IdSha256); + + public static EssCertIDv2 GetInstance(object obj) + { + if (obj == null) + return null; + EssCertIDv2 existing = obj as EssCertIDv2; + if (existing != null) + return existing; + return new EssCertIDv2(Asn1Sequence.GetInstance(obj)); + } + + private EssCertIDv2( + Asn1Sequence seq) + { + if (seq.Count > 3) + throw new ArgumentException("Bad sequence size: " + seq.Count, "seq"); + + int count = 0; + + if (seq[0] is Asn1OctetString) + { + // Default value + this.hashAlgorithm = DefaultAlgID; + } + else + { + this.hashAlgorithm = AlgorithmIdentifier.GetInstance(seq[count++].ToAsn1Object()); + } + + this.certHash = Asn1OctetString.GetInstance(seq[count++].ToAsn1Object()).GetOctets(); + + if (seq.Count > count) + { + this.issuerSerial = IssuerSerial.GetInstance( + Asn1Sequence.GetInstance(seq[count].ToAsn1Object())); + } + } + + public EssCertIDv2(byte[] certHash) + : this(null, certHash, null) + { + } + + public EssCertIDv2( + AlgorithmIdentifier algId, + byte[] certHash) + : this(algId, certHash, null) + { + } + + public EssCertIDv2( + byte[] certHash, + IssuerSerial issuerSerial) + : this(null, certHash, issuerSerial) + { + } + + public EssCertIDv2( + AlgorithmIdentifier algId, + byte[] certHash, + IssuerSerial issuerSerial) + { + if (algId == null) + { + // Default value + this.hashAlgorithm = DefaultAlgID; + } + else + { + this.hashAlgorithm = algId; + } + + this.certHash = certHash; + this.issuerSerial = issuerSerial; + } + + public AlgorithmIdentifier HashAlgorithm + { + get { return this.hashAlgorithm; } + } + + public byte[] GetCertHash() + { + return Arrays.Clone(certHash); + } + + public IssuerSerial IssuerSerial + { + get { 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 override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(); + + if (!hashAlgorithm.Equals(DefaultAlgID)) + { + v.Add(hashAlgorithm); + } + + v.Add(new DerOctetString(certHash).ToAsn1Object()); + + if (issuerSerial != null) + { + v.Add(issuerSerial); + } + + return new DerSequence(v); + } + + } +} diff --git a/bc-sharp-crypto/src/asn1/ess/OtherCertID.cs b/bc-sharp-crypto/src/asn1/ess/OtherCertID.cs new file mode 100644 index 0000000..7794c81 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/ess/OtherCertID.cs @@ -0,0 +1,134 @@ +using System; + +using Org.BouncyCastle.Asn1.Oiw; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Ess +{ + [Obsolete("Use version in Asn1.Esf instead")] + public class OtherCertID + : Asn1Encodable + { + private Asn1Encodable otherCertHash; + private IssuerSerial issuerSerial; + + public static OtherCertID GetInstance( + object o) + { + if (o == null || o is OtherCertID) + { + return (OtherCertID) o; + } + + if (o is Asn1Sequence) + { + return new OtherCertID((Asn1Sequence) o); + } + + throw new ArgumentException( + "unknown object in 'OtherCertID' factory : " + + Platform.GetTypeName(o) + "."); + } + + /** + * constructor + */ + public OtherCertID( + Asn1Sequence seq) + { + if (seq.Count < 1 || seq.Count > 2) + { + throw new ArgumentException("Bad sequence size: " + seq.Count); + } + + if (seq[0].ToAsn1Object() is Asn1OctetString) + { + otherCertHash = Asn1OctetString.GetInstance(seq[0]); + } + else + { + otherCertHash = DigestInfo.GetInstance(seq[0]); + } + + if (seq.Count > 1) + { + issuerSerial = IssuerSerial.GetInstance(Asn1Sequence.GetInstance(seq[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 AlgorithmHash + { + get + { + if (otherCertHash.ToAsn1Object() is Asn1OctetString) + { + // SHA-1 + return new AlgorithmIdentifier(OiwObjectIdentifiers.IdSha1); + } + + return DigestInfo.GetInstance(otherCertHash).AlgorithmID; + } + } + + public byte[] GetCertHash() + { + if (otherCertHash.ToAsn1Object() is Asn1OctetString) + { + // SHA-1 + return ((Asn1OctetString) otherCertHash.ToAsn1Object()).GetOctets(); + } + + return DigestInfo.GetInstance(otherCertHash).GetDigest(); + } + + public IssuerSerial IssuerSerial + { + get { return issuerSerial; } + } + + /** + *
+		 * OtherCertID ::= SEQUENCE {
+		 *     otherCertHash    OtherHash,
+		 *     issuerSerial     IssuerSerial OPTIONAL }
+		 *
+		 * OtherHash ::= CHOICE {
+		 *     sha1Hash     OCTET STRING,
+		 *     otherHash    OtherHashAlgAndValue }
+		 *
+		 * OtherHashAlgAndValue ::= SEQUENCE {
+		 *     hashAlgorithm    AlgorithmIdentifier,
+		 *     hashValue        OCTET STRING }
+		 *
+		 * 
+ */ + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(otherCertHash); + + if (issuerSerial != null) + { + v.Add(issuerSerial); + } + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/ess/OtherSigningCertificate.cs b/bc-sharp-crypto/src/asn1/ess/OtherSigningCertificate.cs new file mode 100644 index 0000000..6cef92b --- /dev/null +++ b/bc-sharp-crypto/src/asn1/ess/OtherSigningCertificate.cs @@ -0,0 +1,110 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Ess +{ + [Obsolete("Use version in Asn1.Esf instead")] + public class OtherSigningCertificate + : Asn1Encodable + { + private Asn1Sequence certs, policies; + + public static OtherSigningCertificate GetInstance( + object o) + { + if (o == null || o is OtherSigningCertificate) + { + return (OtherSigningCertificate) o; + } + + if (o is Asn1Sequence) + { + return new OtherSigningCertificate((Asn1Sequence) o); + } + + throw new ArgumentException( + "unknown object in 'OtherSigningCertificate' factory : " + + Platform.GetTypeName(o) + "."); + } + + /** + * constructors + */ + public OtherSigningCertificate( + Asn1Sequence seq) + { + if (seq.Count < 1 || seq.Count > 2) + { + throw new ArgumentException("Bad sequence size: " + seq.Count); + } + + this.certs = Asn1Sequence.GetInstance(seq[0]); + + if (seq.Count > 1) + { + this.policies = Asn1Sequence.GetInstance(seq[1]); + } + } + + public OtherSigningCertificate( + OtherCertID otherCertID) + { + certs = new DerSequence(otherCertID); + } + + public OtherCertID[] GetCerts() + { + OtherCertID[] cs = new OtherCertID[certs.Count]; + + for (int i = 0; i != certs.Count; ++i) + { + cs[i] = OtherCertID.GetInstance(certs[i]); + } + + return cs; + } + + public PolicyInformation[] GetPolicies() + { + if (policies == null) + { + return null; + } + + PolicyInformation[] ps = new PolicyInformation[policies.Count]; + + for (int i = 0; i != policies.Count; i++) + { + ps[i] = PolicyInformation.GetInstance(policies[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 override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(certs); + + if (policies != null) + { + v.Add(policies); + } + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/ess/SigningCertificate.cs b/bc-sharp-crypto/src/asn1/ess/SigningCertificate.cs new file mode 100644 index 0000000..51f67c1 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/ess/SigningCertificate.cs @@ -0,0 +1,109 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Ess +{ + public class SigningCertificate + : Asn1Encodable + { + private Asn1Sequence certs, policies; + + public static SigningCertificate GetInstance( + object o) + { + if (o == null || o is SigningCertificate) + { + return (SigningCertificate) o; + } + + if (o is Asn1Sequence) + { + return new SigningCertificate((Asn1Sequence) o); + } + + throw new ArgumentException( + "unknown object in 'SigningCertificate' factory : " + + Platform.GetTypeName(o) + "."); + } + + /** + * constructors + */ + public SigningCertificate( + Asn1Sequence seq) + { + if (seq.Count < 1 || seq.Count > 2) + { + throw new ArgumentException("Bad sequence size: " + seq.Count); + } + + this.certs = Asn1Sequence.GetInstance(seq[0]); + + if (seq.Count > 1) + { + this.policies = Asn1Sequence.GetInstance(seq[1]); + } + } + + public SigningCertificate( + EssCertID essCertID) + { + certs = new DerSequence(essCertID); + } + + public EssCertID[] GetCerts() + { + EssCertID[] cs = new EssCertID[certs.Count]; + + for (int i = 0; i != certs.Count; i++) + { + cs[i] = EssCertID.GetInstance(certs[i]); + } + + return cs; + } + + public PolicyInformation[] GetPolicies() + { + if (policies == null) + { + return null; + } + + PolicyInformation[] ps = new PolicyInformation[policies.Count]; + + for (int i = 0; i != policies.Count; i++) + { + ps[i] = PolicyInformation.GetInstance(policies[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 override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(certs); + + if (policies != null) + { + v.Add(policies); + } + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/ess/SigningCertificateV2.cs b/bc-sharp-crypto/src/asn1/ess/SigningCertificateV2.cs new file mode 100644 index 0000000..91eda9e --- /dev/null +++ b/bc-sharp-crypto/src/asn1/ess/SigningCertificateV2.cs @@ -0,0 +1,113 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Ess +{ + public class SigningCertificateV2 + : Asn1Encodable + { + private readonly Asn1Sequence certs; + private readonly Asn1Sequence policies; + + public static SigningCertificateV2 GetInstance( + object o) + { + if (o == null || o is SigningCertificateV2) + return (SigningCertificateV2) o; + + if (o is Asn1Sequence) + return new SigningCertificateV2((Asn1Sequence) o); + + throw new ArgumentException( + "unknown object in 'SigningCertificateV2' factory : " + + Platform.GetTypeName(o) + "."); + } + + private SigningCertificateV2( + Asn1Sequence seq) + { + if (seq.Count < 1 || seq.Count > 2) + throw new ArgumentException("Bad sequence size: " + seq.Count, "seq"); + + this.certs = Asn1Sequence.GetInstance(seq[0].ToAsn1Object()); + + if (seq.Count > 1) + { + this.policies = Asn1Sequence.GetInstance(seq[1].ToAsn1Object()); + } + } + + public SigningCertificateV2( + EssCertIDv2 cert) + { + this.certs = new DerSequence(cert); + } + + public SigningCertificateV2( + EssCertIDv2[] certs) + { + this.certs = new DerSequence(certs); + } + + public SigningCertificateV2( + EssCertIDv2[] certs, + PolicyInformation[] policies) + { + this.certs = new DerSequence(certs); + + if (policies != null) + { + this.policies = new DerSequence(policies); + } + } + + public EssCertIDv2[] GetCerts() + { + EssCertIDv2[] certIds = new EssCertIDv2[certs.Count]; + for (int i = 0; i != certs.Count; i++) + { + certIds[i] = EssCertIDv2.GetInstance(certs[i]); + } + return certIds; + } + + public PolicyInformation[] GetPolicies() + { + if (policies == null) + return null; + + PolicyInformation[] policyInformations = new PolicyInformation[policies.Count]; + for (int i = 0; i != policies.Count; i++) + { + policyInformations[i] = PolicyInformation.GetInstance(policies[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 override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(certs); + + if (policies != null) + { + v.Add(policies); + } + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/gm/GMNamedCurves.cs b/bc-sharp-crypto/src/asn1/gm/GMNamedCurves.cs new file mode 100644 index 0000000..e2ec6d8 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/gm/GMNamedCurves.cs @@ -0,0 +1,157 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Math.EC; +using Org.BouncyCastle.Math.EC.Endo; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Asn1.GM +{ + public sealed class GMNamedCurves + { + private GMNamedCurves() + { + } + + private static ECCurve ConfigureCurve(ECCurve curve) + { + return curve; + } + + private static BigInteger FromHex(string hex) + { + return new BigInteger(1, Hex.Decode(hex)); + } + + /* + * sm2p256v1 + */ + internal class SM2P256V1Holder + : X9ECParametersHolder + { + private SM2P256V1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new SM2P256V1Holder(); + + protected override X9ECParameters CreateParameters() + { + BigInteger p = FromHex("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF"); + BigInteger a = FromHex("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC"); + BigInteger b = FromHex("28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93"); + byte[] S = null; + BigInteger n = FromHex("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123"); + BigInteger h = BigInteger.One; + + ECCurve curve = ConfigureCurve(new FpCurve(p, a, b, n, h)); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7" + + "BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0")); + + return new X9ECParameters(curve, G, n, h, S); + } + } + + /* + * wapip192v1 + */ + internal class WapiP192V1Holder + : X9ECParametersHolder + { + private WapiP192V1Holder() { } + + internal static readonly X9ECParametersHolder Instance = new WapiP192V1Holder(); + + protected override X9ECParameters CreateParameters() + { + BigInteger p = FromHex("BDB6F4FE3E8B1D9E0DA8C0D46F4C318CEFE4AFE3B6B8551F"); + BigInteger a = FromHex("BB8E5E8FBC115E139FE6A814FE48AAA6F0ADA1AA5DF91985"); + BigInteger b = FromHex("1854BEBDC31B21B7AEFC80AB0ECD10D5B1B3308E6DBF11C1"); + byte[] S = null; + BigInteger n = FromHex("BDB6F4FE3E8B1D9E0DA8C0D40FC962195DFAE76F56564677"); + BigInteger h = BigInteger.One; + + ECCurve curve = ConfigureCurve(new FpCurve(p, a, b, n, h)); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "4AD5F7048DE709AD51236DE6" + "5E4D4B482C836DC6E4106640" + + "02BB3A02D4AAADACAE24817A" + "4CA3A1B014B5270432DB27D2")); + + return new X9ECParameters(curve, G, n, h, S); + } + } + + + private static readonly IDictionary objIds = Platform.CreateHashtable(); + private static readonly IDictionary curves = Platform.CreateHashtable(); + private static readonly IDictionary names = Platform.CreateHashtable(); + + private static void DefineCurve( + string name, + DerObjectIdentifier oid, + X9ECParametersHolder holder) + { + objIds.Add(Platform.ToUpperInvariant(name), oid); + names.Add(oid, name); + curves.Add(oid, holder); + } + + static GMNamedCurves() + { + DefineCurve("wapip192v1", GMObjectIdentifiers.wapip192v1, WapiP192V1Holder.Instance); + DefineCurve("sm2p256v1", GMObjectIdentifiers.sm2p256v1, SM2P256V1Holder.Instance); + } + + public static X9ECParameters GetByName( + string name) + { + DerObjectIdentifier oid = GetOid(name); + return oid == null ? null : GetByOid(oid); + } + + /** + * 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[oid]; + return holder == null ? null : holder.Parameters; + } + + /** + * 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[Platform.ToUpperInvariant(name)]; + } + + /** + * return the named curve name represented by the given object identifier. + */ + public static string GetName( + DerObjectIdentifier oid) + { + return (string)names[oid]; + } + + /** + * returns an enumeration containing the name strings for curves + * contained in this structure. + */ + public static IEnumerable Names + { + get { return new EnumerableProxy(names.Values); } + } + } +} diff --git a/bc-sharp-crypto/src/asn1/gm/GMObjectIdentifiers.cs b/bc-sharp-crypto/src/asn1/gm/GMObjectIdentifiers.cs new file mode 100644 index 0000000..edb3a41 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/gm/GMObjectIdentifiers.cs @@ -0,0 +1,85 @@ +using System; + +namespace Org.BouncyCastle.Asn1.GM +{ + public abstract class GMObjectIdentifiers + { + public static readonly DerObjectIdentifier sm_scheme = new DerObjectIdentifier("1.2.156.10197.1"); + + public static readonly DerObjectIdentifier sm6_ecb = sm_scheme.Branch("101.1"); + public static readonly DerObjectIdentifier sm6_cbc = sm_scheme.Branch("101.2"); + public static readonly DerObjectIdentifier sm6_ofb128 = sm_scheme.Branch("101.3"); + public static readonly DerObjectIdentifier sm6_cfb128 = sm_scheme.Branch("101.4"); + + public static readonly DerObjectIdentifier sm1_ecb = sm_scheme.Branch("102.1"); + public static readonly DerObjectIdentifier sm1_cbc = sm_scheme.Branch("102.2"); + public static readonly DerObjectIdentifier sm1_ofb128 = sm_scheme.Branch("102.3"); + public static readonly DerObjectIdentifier sm1_cfb128 = sm_scheme.Branch("102.4"); + public static readonly DerObjectIdentifier sm1_cfb1 = sm_scheme.Branch("102.5"); + public static readonly DerObjectIdentifier sm1_cfb8 = sm_scheme.Branch("102.6"); + + public static readonly DerObjectIdentifier ssf33_ecb = sm_scheme.Branch("103.1"); + public static readonly DerObjectIdentifier ssf33_cbc = sm_scheme.Branch("103.2"); + public static readonly DerObjectIdentifier ssf33_ofb128 = sm_scheme.Branch("103.3"); + public static readonly DerObjectIdentifier ssf33_cfb128 = sm_scheme.Branch("103.4"); + public static readonly DerObjectIdentifier ssf33_cfb1 = sm_scheme.Branch("103.5"); + public static readonly DerObjectIdentifier ssf33_cfb8 = sm_scheme.Branch("103.6"); + + public static readonly DerObjectIdentifier sms4_ecb = sm_scheme.Branch("104.1"); + public static readonly DerObjectIdentifier sms4_cbc = sm_scheme.Branch("104.2"); + public static readonly DerObjectIdentifier sms4_ofb128 = sm_scheme.Branch("104.3"); + public static readonly DerObjectIdentifier sms4_cfb128 = sm_scheme.Branch("104.4"); + public static readonly DerObjectIdentifier sms4_cfb1 = sm_scheme.Branch("104.5"); + public static readonly DerObjectIdentifier sms4_cfb8 = sm_scheme.Branch("104.6"); + public static readonly DerObjectIdentifier sms4_ctr = sm_scheme.Branch("104.7"); + public static readonly DerObjectIdentifier sms4_gcm = sm_scheme.Branch("104.8"); + public static readonly DerObjectIdentifier sms4_ccm = sm_scheme.Branch("104.9"); + public static readonly DerObjectIdentifier sms4_xts = sm_scheme.Branch("104.10"); + public static readonly DerObjectIdentifier sms4_wrap = sm_scheme.Branch("104.11"); + public static readonly DerObjectIdentifier sms4_wrap_pad = sm_scheme.Branch("104.12"); + public static readonly DerObjectIdentifier sms4_ocb = sm_scheme.Branch("104.100"); + + public static readonly DerObjectIdentifier sm5 = sm_scheme.Branch("201"); + + public static readonly DerObjectIdentifier sm2p256v1 = sm_scheme.Branch("301"); + public static readonly DerObjectIdentifier sm2sign = sm_scheme.Branch("301.1"); + public static readonly DerObjectIdentifier sm2exchange = sm_scheme.Branch("301.2"); + public static readonly DerObjectIdentifier sm2encrypt = sm_scheme.Branch("301.3"); + + public static readonly DerObjectIdentifier wapip192v1 = sm_scheme.Branch("301.101"); + + public static readonly DerObjectIdentifier sm2encrypt_recommendedParameters = sm2encrypt.Branch("1"); + public static readonly DerObjectIdentifier sm2encrypt_specifiedParameters = sm2encrypt.Branch("2"); + public static readonly DerObjectIdentifier sm2encrypt_with_sm3 = sm2encrypt.Branch("2.1"); + public static readonly DerObjectIdentifier sm2encrypt_with_sha1 = sm2encrypt.Branch("2.2"); + public static readonly DerObjectIdentifier sm2encrypt_with_sha224 = sm2encrypt.Branch("2.3"); + public static readonly DerObjectIdentifier sm2encrypt_with_sha256 = sm2encrypt.Branch("2.4"); + public static readonly DerObjectIdentifier sm2encrypt_with_sha384 = sm2encrypt.Branch("2.5"); + public static readonly DerObjectIdentifier sm2encrypt_with_sha512 = sm2encrypt.Branch("2.6"); + public static readonly DerObjectIdentifier sm2encrypt_with_rmd160 = sm2encrypt.Branch("2.7"); + public static readonly DerObjectIdentifier sm2encrypt_with_whirlpool = sm2encrypt.Branch("2.8"); + public static readonly DerObjectIdentifier sm2encrypt_with_blake2b512 = sm2encrypt.Branch("2.9"); + public static readonly DerObjectIdentifier sm2encrypt_with_blake2s256 = sm2encrypt.Branch("2.10"); + public static readonly DerObjectIdentifier sm2encrypt_with_md5 = sm2encrypt.Branch("2.11"); + + public static readonly DerObjectIdentifier id_sm9PublicKey = sm_scheme.Branch("302"); + public static readonly DerObjectIdentifier sm9sign = sm_scheme.Branch("302.1"); + public static readonly DerObjectIdentifier sm9keyagreement = sm_scheme.Branch("302.2"); + public static readonly DerObjectIdentifier sm9encrypt = sm_scheme.Branch("302.3"); + + public static readonly DerObjectIdentifier sm3 = sm_scheme.Branch("401"); + + public static readonly DerObjectIdentifier hmac_sm3 = sm3.Branch("2"); + + public static readonly DerObjectIdentifier sm2sign_with_sm3 = sm_scheme.Branch("501"); + public static readonly DerObjectIdentifier sm2sign_with_sha1 = sm_scheme.Branch("502"); + public static readonly DerObjectIdentifier sm2sign_with_sha256 = sm_scheme.Branch("503"); + public static readonly DerObjectIdentifier sm2sign_with_sha512 = sm_scheme.Branch("504"); + public static readonly DerObjectIdentifier sm2sign_with_sha224 = sm_scheme.Branch("505"); + public static readonly DerObjectIdentifier sm2sign_with_sha384 = sm_scheme.Branch("506"); + public static readonly DerObjectIdentifier sm2sign_with_rmd160 = sm_scheme.Branch("507"); + public static readonly DerObjectIdentifier sm2sign_with_whirlpool = sm_scheme.Branch("520"); + public static readonly DerObjectIdentifier sm2sign_with_blake2b512 = sm_scheme.Branch("521"); + public static readonly DerObjectIdentifier sm2sign_with_blake2s256 = sm_scheme.Branch("522"); + } +} \ No newline at end of file diff --git a/bc-sharp-crypto/src/asn1/gnu/GNUObjectIdentifiers.cs b/bc-sharp-crypto/src/asn1/gnu/GNUObjectIdentifiers.cs new file mode 100644 index 0000000..b322ef2 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/gnu/GNUObjectIdentifiers.cs @@ -0,0 +1,36 @@ +using System; + +namespace Org.BouncyCastle.Asn1.Gnu +{ + public abstract class GnuObjectIdentifiers + { + public static readonly DerObjectIdentifier Gnu = new DerObjectIdentifier("1.3.6.1.4.1.11591.1"); // GNU Radius + public static readonly DerObjectIdentifier GnuPG = new DerObjectIdentifier("1.3.6.1.4.1.11591.2"); // GnuPG (Ägypten) + public static readonly DerObjectIdentifier Notation = new DerObjectIdentifier("1.3.6.1.4.1.11591.2.1"); // notation + public static readonly DerObjectIdentifier PkaAddress = new DerObjectIdentifier("1.3.6.1.4.1.11591.2.1.1"); // pkaAddress + public static readonly DerObjectIdentifier GnuRadar = new DerObjectIdentifier("1.3.6.1.4.1.11591.3"); // GNU Radar + public static readonly DerObjectIdentifier DigestAlgorithm = new DerObjectIdentifier("1.3.6.1.4.1.11591.12"); // digestAlgorithm + public static readonly DerObjectIdentifier Tiger192 = new DerObjectIdentifier("1.3.6.1.4.1.11591.12.2"); // TIGER/192 + public static readonly DerObjectIdentifier EncryptionAlgorithm = new DerObjectIdentifier("1.3.6.1.4.1.11591.13"); // encryptionAlgorithm + public static readonly DerObjectIdentifier Serpent = new DerObjectIdentifier("1.3.6.1.4.1.11591.13.2"); // Serpent + public static readonly DerObjectIdentifier Serpent128Ecb = new DerObjectIdentifier("1.3.6.1.4.1.11591.13.2.1"); // Serpent-128-ECB + public static readonly DerObjectIdentifier Serpent128Cbc = new DerObjectIdentifier("1.3.6.1.4.1.11591.13.2.2"); // Serpent-128-CBC + public static readonly DerObjectIdentifier Serpent128Ofb = new DerObjectIdentifier("1.3.6.1.4.1.11591.13.2.3"); // Serpent-128-OFB + public static readonly DerObjectIdentifier Serpent128Cfb = new DerObjectIdentifier("1.3.6.1.4.1.11591.13.2.4"); // Serpent-128-CFB + public static readonly DerObjectIdentifier Serpent192Ecb = new DerObjectIdentifier("1.3.6.1.4.1.11591.13.2.21"); // Serpent-192-ECB + public static readonly DerObjectIdentifier Serpent192Cbc = new DerObjectIdentifier("1.3.6.1.4.1.11591.13.2.22"); // Serpent-192-CBC + public static readonly DerObjectIdentifier Serpent192Ofb = new DerObjectIdentifier("1.3.6.1.4.1.11591.13.2.23"); // Serpent-192-OFB + public static readonly DerObjectIdentifier Serpent192Cfb = new DerObjectIdentifier("1.3.6.1.4.1.11591.13.2.24"); // Serpent-192-CFB + public static readonly DerObjectIdentifier Serpent256Ecb = new DerObjectIdentifier("1.3.6.1.4.1.11591.13.2.41"); // Serpent-256-ECB + public static readonly DerObjectIdentifier Serpent256Cbc = new DerObjectIdentifier("1.3.6.1.4.1.11591.13.2.42"); // Serpent-256-CBC + public static readonly DerObjectIdentifier Serpent256Ofb = new DerObjectIdentifier("1.3.6.1.4.1.11591.13.2.43"); // Serpent-256-OFB + public static readonly DerObjectIdentifier Serpent256Cfb = new DerObjectIdentifier("1.3.6.1.4.1.11591.13.2.44"); // Serpent-256-CFB + public static readonly DerObjectIdentifier Crc = new DerObjectIdentifier("1.3.6.1.4.1.11591.14"); // CRC algorithms + public static readonly DerObjectIdentifier Crc32 = new DerObjectIdentifier("1.3.6.1.4.1.11591.14.1"); // CRC 32 + + /** 1.3.6.1.4.1.11591.15 - ellipticCurve */ + public static readonly DerObjectIdentifier EllipticCurve = new DerObjectIdentifier("1.3.6.1.4.1.11591.15"); + + public static readonly DerObjectIdentifier Ed25519 = EllipticCurve.Branch("1"); + } +} diff --git a/bc-sharp-crypto/src/asn1/iana/IANAObjectIdentifiers.cs b/bc-sharp-crypto/src/asn1/iana/IANAObjectIdentifiers.cs new file mode 100644 index 0000000..63343f5 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/iana/IANAObjectIdentifiers.cs @@ -0,0 +1,18 @@ +namespace Org.BouncyCastle.Asn1.Iana +{ + public abstract class IanaObjectIdentifiers + { + // id-SHA1 OBJECT IDENTIFIER ::= + // {iso(1) identified-organization(3) dod(6) internet(1) security(5) mechanisms(5) ipsec(8) isakmpOakley(1)} + // + + public static readonly DerObjectIdentifier IsakmpOakley = new DerObjectIdentifier("1.3.6.1.5.5.8.1"); + + public static readonly DerObjectIdentifier HmacMD5 = new DerObjectIdentifier(IsakmpOakley + ".1"); + public static readonly DerObjectIdentifier HmacSha1 = new DerObjectIdentifier(IsakmpOakley + ".2"); + + public static readonly DerObjectIdentifier HmacTiger = new DerObjectIdentifier(IsakmpOakley + ".3"); + + public static readonly DerObjectIdentifier HmacRipeMD160 = new DerObjectIdentifier(IsakmpOakley + ".4"); + } +} diff --git a/bc-sharp-crypto/src/asn1/icao/CscaMasterList.cs b/bc-sharp-crypto/src/asn1/icao/CscaMasterList.cs new file mode 100644 index 0000000..6890d8a --- /dev/null +++ b/bc-sharp-crypto/src/asn1/icao/CscaMasterList.cs @@ -0,0 +1,83 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; + +namespace Org.BouncyCastle.Asn1.Icao +{ + /** + * The CscaMasterList object. This object can be wrapped in a + * CMSSignedData to be published in LDAP. + * + *
+	 * CscaMasterList ::= SEQUENCE {
+	 *   version                CscaMasterListVersion,
+	 *   certList               SET OF Certificate }
+	 *   
+	 * CscaMasterListVersion :: INTEGER {v0(0)}
+	 * 
+ */ + public class CscaMasterList + : Asn1Encodable + { + private DerInteger version = new DerInteger(0); + private X509CertificateStructure[] certList; + + public static CscaMasterList GetInstance( + object obj) + { + if (obj is CscaMasterList) + return (CscaMasterList)obj; + + if (obj != null) + return new CscaMasterList(Asn1Sequence.GetInstance(obj)); + + return null; + } + + private CscaMasterList( + Asn1Sequence seq) + { + if (seq == null || seq.Count == 0) + throw new ArgumentException("null or empty sequence passed."); + + if (seq.Count != 2) + throw new ArgumentException("Incorrect sequence size: " + seq.Count); + + this.version = DerInteger.GetInstance(seq[0]); + + Asn1Set certSet = Asn1Set.GetInstance(seq[1]); + + this.certList = new X509CertificateStructure[certSet.Count]; + for (int i = 0; i < certList.Length; i++) + { + certList[i] = X509CertificateStructure.GetInstance(certSet[i]); + } + } + + public CscaMasterList( + X509CertificateStructure[] certStructs) + { + certList = CopyCertList(certStructs); + } + + public virtual int Version + { + get { return version.Value.IntValue; } + } + + public X509CertificateStructure[] GetCertStructs() + { + return CopyCertList(certList); + } + + private static X509CertificateStructure[] CopyCertList(X509CertificateStructure[] orig) + { + return (X509CertificateStructure[])orig.Clone(); + } + + public override Asn1Object ToAsn1Object() + { + return new DerSequence(version, new DerSet(certList)); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/icao/DataGroupHash.cs b/bc-sharp-crypto/src/asn1/icao/DataGroupHash.cs new file mode 100644 index 0000000..e0d7eee --- /dev/null +++ b/bc-sharp-crypto/src/asn1/icao/DataGroupHash.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1; + +namespace Org.BouncyCastle.Asn1.Icao +{ + /** + * 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 + : Asn1Encodable + { + private readonly DerInteger dataGroupNumber; + private readonly Asn1OctetString dataGroupHashValue; + + public static DataGroupHash GetInstance( + object obj) + { + if (obj is DataGroupHash) + return (DataGroupHash)obj; + + if (obj != null) + return new DataGroupHash(Asn1Sequence.GetInstance(obj)); + + return null; + } + + private DataGroupHash( + Asn1Sequence seq) + { + if (seq.Count != 2) + throw new ArgumentException("Wrong number of elements in sequence", "seq"); + + this.dataGroupNumber = DerInteger.GetInstance(seq[0]); + this.dataGroupHashValue = Asn1OctetString.GetInstance(seq[1]); + } + + public DataGroupHash( + int dataGroupNumber, + Asn1OctetString dataGroupHashValue) + { + this.dataGroupNumber = new DerInteger(dataGroupNumber); + this.dataGroupHashValue = dataGroupHashValue; + } + + public int DataGroupNumber + { + get { return dataGroupNumber.Value.IntValue; } + } + + public Asn1OctetString DataGroupHashValue + { + get { return dataGroupHashValue; } + } + + public override Asn1Object ToAsn1Object() + { + return new DerSequence(dataGroupNumber, dataGroupHashValue); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/icao/ICAOObjectIdentifiers.cs b/bc-sharp-crypto/src/asn1/icao/ICAOObjectIdentifiers.cs new file mode 100644 index 0000000..389d4da --- /dev/null +++ b/bc-sharp-crypto/src/asn1/icao/ICAOObjectIdentifiers.cs @@ -0,0 +1,34 @@ +using System; + +namespace Org.BouncyCastle.Asn1.Icao +{ + public abstract class IcaoObjectIdentifiers + { + // + // base id + // + public static readonly DerObjectIdentifier IdIcao = new DerObjectIdentifier("2.23.136"); + + public static readonly DerObjectIdentifier IdIcaoMrtd = IdIcao.Branch("1"); + public static readonly DerObjectIdentifier IdIcaoMrtdSecurity = IdIcaoMrtd.Branch("1"); + + // LDS security object, see ICAO Doc 9303-Volume 2-Section IV-A3.2 + public static readonly DerObjectIdentifier IdIcaoLdsSecurityObject = IdIcaoMrtdSecurity.Branch("1"); + + // CSCA master list, see TR CSCA Countersigning and Master List issuance + public static readonly DerObjectIdentifier IdIcaoCscaMasterList = IdIcaoMrtdSecurity.Branch("2"); + public static readonly DerObjectIdentifier IdIcaoCscaMasterListSigningKey = IdIcaoMrtdSecurity.Branch("3"); + + // document type list, see draft TR LDS and PKI Maintenance, par. 3.2.1 + public static readonly DerObjectIdentifier IdIcaoDocumentTypeList = IdIcaoMrtdSecurity.Branch("4"); + + // Active Authentication protocol, see draft TR LDS and PKI Maintenance, + // par. 5.2.2 + public static readonly DerObjectIdentifier IdIcaoAAProtocolObject = IdIcaoMrtdSecurity.Branch("5"); + + // CSCA name change and key reoll-over, see draft TR LDS and PKI + // Maintenance, par. 3.2.1 + public static readonly DerObjectIdentifier IdIcaoExtensions = IdIcaoMrtdSecurity.Branch("6"); + public static readonly DerObjectIdentifier IdIcaoExtensionsNamechangekeyrollover = IdIcaoExtensions.Branch("1"); + } +} diff --git a/bc-sharp-crypto/src/asn1/icao/LDSSecurityObject.cs b/bc-sharp-crypto/src/asn1/icao/LDSSecurityObject.cs new file mode 100644 index 0000000..c33ca68 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/icao/LDSSecurityObject.cs @@ -0,0 +1,145 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Asn1.Icao +{ + /** + * The LDSSecurityObject object (V1.8). + *
+	 * LDSSecurityObject ::= SEQUENCE {
+	 *   version                LDSSecurityObjectVersion,
+	 *   hashAlgorithm          DigestAlgorithmIdentifier,
+	 *   dataGroupHashValues    SEQUENCE SIZE (2..ub-DataGroups) OF DataHashGroup,
+	 *   ldsVersionInfo         LDSVersionInfo OPTIONAL
+	 *     -- if present, version MUST be v1 }
+	 *
+	 * DigestAlgorithmIdentifier ::= AlgorithmIdentifier,
+	 *
+	 * LDSSecurityObjectVersion :: INTEGER {V0(0)}
+	 * 
+ */ + public class LdsSecurityObject + : Asn1Encodable + { + public const int UBDataGroups = 16; + + private DerInteger version = new DerInteger(0); + private AlgorithmIdentifier digestAlgorithmIdentifier; + private DataGroupHash[] datagroupHash; + private LdsVersionInfo versionInfo; + + public static LdsSecurityObject GetInstance( + object obj) + { + if (obj is LdsSecurityObject) + return (LdsSecurityObject)obj; + + if (obj != null) + return new LdsSecurityObject(Asn1Sequence.GetInstance(obj)); + + return null; + } + + private LdsSecurityObject( + Asn1Sequence seq) + { + if (seq == null || seq.Count == 0) + throw new ArgumentException("null or empty sequence passed."); + + IEnumerator e = seq.GetEnumerator(); + + // version + e.MoveNext(); + version = DerInteger.GetInstance(e.Current); + // digestAlgorithmIdentifier + e.MoveNext(); + digestAlgorithmIdentifier = AlgorithmIdentifier.GetInstance(e.Current); + + e.MoveNext(); + Asn1Sequence datagroupHashSeq = Asn1Sequence.GetInstance(e.Current); + + if (version.Value.Equals(BigInteger.One)) + { + e.MoveNext(); + versionInfo = LdsVersionInfo.GetInstance(e.Current); + } + + CheckDatagroupHashSeqSize(datagroupHashSeq.Count); + + datagroupHash = new DataGroupHash[datagroupHashSeq.Count]; + for (int i= 0; i< datagroupHashSeq.Count; i++) + { + datagroupHash[i] = DataGroupHash.GetInstance(datagroupHashSeq[i]); + } + } + + public LdsSecurityObject( + AlgorithmIdentifier digestAlgorithmIdentifier, + DataGroupHash[] datagroupHash) + { + this.version = new DerInteger(0); + this.digestAlgorithmIdentifier = digestAlgorithmIdentifier; + this.datagroupHash = datagroupHash; + + CheckDatagroupHashSeqSize(datagroupHash.Length); + } + + + public LdsSecurityObject( + AlgorithmIdentifier digestAlgorithmIdentifier, + DataGroupHash[] datagroupHash, + LdsVersionInfo versionInfo) + { + this.version = new DerInteger(1); + this.digestAlgorithmIdentifier = digestAlgorithmIdentifier; + this.datagroupHash = datagroupHash; + this.versionInfo = versionInfo; + + CheckDatagroupHashSeqSize(datagroupHash.Length); + } + + private void CheckDatagroupHashSeqSize(int size) + { + if (size < 2 || size > UBDataGroups) + throw new ArgumentException("wrong size in DataGroupHashValues : not in (2.."+ UBDataGroups +")"); + } + + public BigInteger Version + { + get { return version.Value; } + } + + public AlgorithmIdentifier DigestAlgorithmIdentifier + { + get { return digestAlgorithmIdentifier; } + } + + public DataGroupHash[] GetDatagroupHash() + { + return datagroupHash; + } + + public LdsVersionInfo VersionInfo + { + get { return versionInfo; } + } + + public override Asn1Object ToAsn1Object() + { + DerSequence hashSeq = new DerSequence(datagroupHash); + + Asn1EncodableVector v = new Asn1EncodableVector(version, digestAlgorithmIdentifier, hashSeq); + + if (versionInfo != null) + { + v.Add(versionInfo); + } + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/icao/LDSVersionInfo.cs b/bc-sharp-crypto/src/asn1/icao/LDSVersionInfo.cs new file mode 100644 index 0000000..2cdcad2 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/icao/LDSVersionInfo.cs @@ -0,0 +1,61 @@ +using System; + +namespace Org.BouncyCastle.Asn1.Icao +{ + public class LdsVersionInfo + : Asn1Encodable + { + private DerPrintableString ldsVersion; + private DerPrintableString unicodeVersion; + + public LdsVersionInfo(string ldsVersion, string unicodeVersion) + { + this.ldsVersion = new DerPrintableString(ldsVersion); + this.unicodeVersion = new DerPrintableString(unicodeVersion); + } + + private LdsVersionInfo(Asn1Sequence seq) + { + if (seq.Count != 2) + throw new ArgumentException("sequence wrong size for LDSVersionInfo", "seq"); + + this.ldsVersion = DerPrintableString.GetInstance(seq[0]); + this.unicodeVersion = DerPrintableString.GetInstance(seq[1]); + } + + public static LdsVersionInfo GetInstance(object obj) + { + if (obj is LdsVersionInfo) + return (LdsVersionInfo)obj; + + if (obj != null) + return new LdsVersionInfo(Asn1Sequence.GetInstance(obj)); + + return null; + } + + public virtual string GetLdsVersion() + { + return ldsVersion.GetString(); + } + + public virtual string GetUnicodeVersion() + { + return unicodeVersion.GetString(); + } + + /** + *
+		 * LDSVersionInfo ::= SEQUENCE {
+		 *    ldsVersion PRINTABLE STRING
+		 *    unicodeVersion PRINTABLE STRING
+		 *  }
+		 * 
+ * @return + */ + public override Asn1Object ToAsn1Object() + { + return new DerSequence(ldsVersion, unicodeVersion); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/isismtt/ISISMTTObjectIdentifiers.cs b/bc-sharp-crypto/src/asn1/isismtt/ISISMTTObjectIdentifiers.cs new file mode 100644 index 0000000..af60b03 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/isismtt/ISISMTTObjectIdentifiers.cs @@ -0,0 +1,177 @@ +namespace Org.BouncyCastle.Asn1.IsisMtt +{ + public abstract class IsisMttObjectIdentifiers + { + public static readonly DerObjectIdentifier IdIsisMtt = new DerObjectIdentifier("1.3.36.8"); + + public static readonly DerObjectIdentifier IdIsisMttCP = new DerObjectIdentifier(IdIsisMtt + ".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 readonly DerObjectIdentifier IdIsisMttCPAccredited = new DerObjectIdentifier(IdIsisMttCP + ".1"); + + public static readonly DerObjectIdentifier IdIsisMttAT = new DerObjectIdentifier(IdIsisMtt + ".3"); + + /** + * Certificate extensionDate of certificate generation + * + *
+		 *		DateOfCertGenSyntax ::= GeneralizedTime
+		 * 
+ */ + public static readonly DerObjectIdentifier IdIsisMttATDateOfCertGen = new DerObjectIdentifier(IdIsisMttAT + ".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 readonly DerObjectIdentifier IdIsisMttATProcuration = new DerObjectIdentifier(IdIsisMttAT + ".2"); + + /** + * Attribute to indicate admissions to certain professions. May be used as + * attribute in attribute certificate or as extension in a certificate + */ + public static readonly DerObjectIdentifier IdIsisMttATAdmission = new DerObjectIdentifier(IdIsisMttAT + ".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 readonly DerObjectIdentifier IdIsisMttATMonetaryLimit = new DerObjectIdentifier(IdIsisMttAT + ".4"); + + /** + * A declaration of majority. May be used as attribute in attribute + * certificate or as extension in a certificate + */ + public static readonly DerObjectIdentifier IdIsisMttATDeclarationOfMajority = new DerObjectIdentifier(IdIsisMttAT + ".5"); + + /** + * + * Serial number of the smart card containing the corresponding private key + * + *
+		 *		ICCSNSyntax ::= OCTET STRING (SIZE(8..20))
+		 * 
+ */ + public static readonly DerObjectIdentifier IdIsisMttATIccsn = new DerObjectIdentifier(IdIsisMttAT + ".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 readonly DerObjectIdentifier IdIsisMttATPKReference = new DerObjectIdentifier(IdIsisMttAT + ".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 Org.BouncyCastle.Asn1.IsisMtt.X509.Restriction + */ + public static readonly DerObjectIdentifier IdIsisMttATRestriction = new DerObjectIdentifier(IdIsisMttAT + ".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 readonly DerObjectIdentifier IdIsisMttATRetrieveIfAllowed = new DerObjectIdentifier(IdIsisMttAT + ".9"); + + /** + * SingleOCSPResponse extension: The certificate requested by the client by + * inserting the RetrieveIfAllowed extension in the request, will be + * returned in this extension. + * + * @see Org.BouncyCastle.Asn1.IsisMtt.Ocsp.RequestedCertificate + */ + public static readonly DerObjectIdentifier IdIsisMttATRequestedCertificate = new DerObjectIdentifier(IdIsisMttAT + ".10"); + + /** + * Base ObjectIdentifier for naming authorities + */ + public static readonly DerObjectIdentifier IdIsisMttATNamingAuthorities = new DerObjectIdentifier(IdIsisMttAT + ".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 readonly DerObjectIdentifier IdIsisMttATCertInDirSince = new DerObjectIdentifier(IdIsisMttAT + ".12"); + + /** + * Hash of a certificate in OCSP. + * + * @see Org.BouncyCastle.Asn1.IsisMtt.Ocsp.CertHash + */ + public static readonly DerObjectIdentifier IdIsisMttATCertHash = new DerObjectIdentifier(IdIsisMttAT + ".13"); + + /** + *
+		 *		NameAtBirth ::= DirectoryString(SIZE(1..64)
+		 * 
+ * + * Used in + * {@link Org.BouncyCastle.Asn1.X509.SubjectDirectoryAttributes SubjectDirectoryAttributes} + */ + public static readonly DerObjectIdentifier IdIsisMttATNameAtBirth = new DerObjectIdentifier(IdIsisMttAT + ".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 Org.BouncyCastle.Asn1.IsisMtt.X509.AdditionalInformationSyntax + */ + public static readonly DerObjectIdentifier IdIsisMttATAdditionalInformation = new DerObjectIdentifier(IdIsisMttAT + ".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 readonly DerObjectIdentifier IdIsisMttATLiabilityLimitationFlag = new DerObjectIdentifier("0.2.262.1.10.12.0"); + } +} diff --git a/bc-sharp-crypto/src/asn1/isismtt/ocsp/CertHash.cs b/bc-sharp-crypto/src/asn1/isismtt/ocsp/CertHash.cs new file mode 100644 index 0000000..5773e1c --- /dev/null +++ b/bc-sharp-crypto/src/asn1/isismtt/ocsp/CertHash.cs @@ -0,0 +1,122 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.IsisMtt.Ocsp +{ + /** + * 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 + : Asn1Encodable + { + private readonly AlgorithmIdentifier hashAlgorithm; + private readonly byte[] certificateHash; + + public static CertHash GetInstance( + object obj) + { + if (obj == null || obj is CertHash) + { + return (CertHash) obj; + } + + if (obj is Asn1Sequence) + { + return new CertHash((Asn1Sequence) obj); + } + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + /** + * 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.Count != 2) + throw new ArgumentException("Bad sequence size: " + seq.Count); + + this.hashAlgorithm = AlgorithmIdentifier.GetInstance(seq[0]); + this.certificateHash = DerOctetString.GetInstance(seq[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) + { + if (hashAlgorithm == null) + throw new ArgumentNullException("hashAlgorithm"); + if (certificateHash == null) + throw new ArgumentNullException("certificateHash"); + + this.hashAlgorithm = hashAlgorithm; + this.certificateHash = (byte[]) certificateHash.Clone(); + } + + public AlgorithmIdentifier HashAlgorithm + { + get { return hashAlgorithm; } + } + + public byte[] CertificateHash + { + get { return (byte[]) certificateHash.Clone(); } + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *

+ * Returns: + *

+ *

+		*     CertHash ::= SEQUENCE {
+		*       hashAlgorithm AlgorithmIdentifier,
+		*       certificateHash OCTET STRING
+		*     }
+		* 
+ * + * @return an Asn1Object + */ + public override Asn1Object ToAsn1Object() + { + return new DerSequence(hashAlgorithm, new DerOctetString(certificateHash)); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/isismtt/ocsp/RequestedCertificate.cs b/bc-sharp-crypto/src/asn1/isismtt/ocsp/RequestedCertificate.cs new file mode 100644 index 0000000..413b3bd --- /dev/null +++ b/bc-sharp-crypto/src/asn1/isismtt/ocsp/RequestedCertificate.cs @@ -0,0 +1,188 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.IsisMtt.Ocsp +{ + /** + * 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 isExplicit 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 + : Asn1Encodable, IAsn1Choice + { + public enum Choice + { + Certificate = -1, + PublicKeyCertificate = 0, + AttributeCertificate = 1 + } + + private readonly X509CertificateStructure cert; + private readonly byte[] publicKeyCert; + private readonly byte[] attributeCert; + + public static RequestedCertificate GetInstance( + object obj) + { + if (obj == null || obj is RequestedCertificate) + { + return (RequestedCertificate) obj; + } + + if (obj is Asn1Sequence) + { + return new RequestedCertificate(X509CertificateStructure.GetInstance(obj)); + } + + if (obj is Asn1TaggedObject) + { + return new RequestedCertificate((Asn1TaggedObject) obj); + } + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + public static RequestedCertificate GetInstance( + Asn1TaggedObject obj, + bool isExplicit) + { + if (!isExplicit) + throw new ArgumentException("choice item must be explicitly tagged"); + + return GetInstance(obj.GetObject()); + } + + private RequestedCertificate( + Asn1TaggedObject tagged) + { + switch ((Choice) tagged.TagNo) + { + case Choice.AttributeCertificate: + this.attributeCert = Asn1OctetString.GetInstance(tagged, true).GetOctets(); + break; + case Choice.PublicKeyCertificate: + this.publicKeyCert = Asn1OctetString.GetInstance(tagged, true).GetOctets(); + break; + default: + throw new ArgumentException("unknown tag number: " + tagged.TagNo); + } + } + + /** + * 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( + Choice type, + byte[] certificateOctets) + : this(new DerTaggedObject((int) type, new DerOctetString(certificateOctets))) + { + } + + public Choice Type + { + get + { + if (cert != null) + return Choice.Certificate; + + if (publicKeyCert != null) + return Choice.PublicKeyCertificate; + + return Choice.AttributeCertificate; + } + } + + public byte[] GetCertificateBytes() + { + if (cert != null) + { + try + { + return cert.GetEncoded(); + } + catch (IOException e) + { + throw new InvalidOperationException("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 an Asn1Object + */ + public override Asn1Object ToAsn1Object() + { + if (publicKeyCert != null) + { + return new DerTaggedObject(0, new DerOctetString(publicKeyCert)); + } + + if (attributeCert != null) + { + return new DerTaggedObject(1, new DerOctetString(attributeCert)); + } + + return cert.ToAsn1Object(); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/isismtt/x509/AdditionalInformationSyntax.cs b/bc-sharp-crypto/src/asn1/isismtt/x509/AdditionalInformationSyntax.cs new file mode 100644 index 0000000..53a8e98 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/isismtt/x509/AdditionalInformationSyntax.cs @@ -0,0 +1,71 @@ +using System; + +using Org.BouncyCastle.Asn1.X500; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.IsisMtt.X509 +{ + /** + * Some other information of non-restrictive nature regarding the usage of this + * certificate. + * + *
+	*    AdditionalInformationSyntax ::= DirectoryString (SIZE(1..2048))
+	* 
+ */ + public class AdditionalInformationSyntax + : Asn1Encodable + { + private readonly DirectoryString information; + + public static AdditionalInformationSyntax GetInstance( + object obj) + { + if (obj is AdditionalInformationSyntax) + return (AdditionalInformationSyntax) obj; + + if (obj is IAsn1String) + return new AdditionalInformationSyntax(DirectoryString.GetInstance(obj)); + + throw new ArgumentException("Unknown object in GetInstance: " + Platform.GetTypeName(obj), "obj"); + } + + private AdditionalInformationSyntax( + DirectoryString information) + { + this.information = information; + } + + /** + * Constructor from a given details. + * + * @param information The describtion of the information. + */ + public AdditionalInformationSyntax( + string information) + { + this.information = new DirectoryString(information); + } + + public virtual DirectoryString Information + { + get { return information; } + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *

+ * Returns: + *

+ *

+		*   AdditionalInformationSyntax ::= DirectoryString (SIZE(1..2048))
+		* 
+ * + * @return an Asn1Object + */ + public override Asn1Object ToAsn1Object() + { + return information.ToAsn1Object(); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/isismtt/x509/AdmissionSyntax.cs b/bc-sharp-crypto/src/asn1/isismtt/x509/AdmissionSyntax.cs new file mode 100644 index 0000000..4b6264a --- /dev/null +++ b/bc-sharp-crypto/src/asn1/isismtt/x509/AdmissionSyntax.cs @@ -0,0 +1,278 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.IsisMtt.X509 +{ + /** + * 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 Org.BouncyCastle.Asn1.IsisMtt.X509.Admissions + * @see Org.BouncyCastle.Asn1.IsisMtt.X509.ProfessionInfo + * @see Org.BouncyCastle.Asn1.IsisMtt.X509.NamingAuthority + */ + public class AdmissionSyntax + : Asn1Encodable + { + private readonly GeneralName admissionAuthority; + private readonly Asn1Sequence contentsOfAdmissions; + + public static AdmissionSyntax GetInstance( + object obj) + { + if (obj == null || obj is AdmissionSyntax) + { + return (AdmissionSyntax)obj; + } + + if (obj is Asn1Sequence) + { + return new AdmissionSyntax((Asn1Sequence)obj); + } + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + /** + * 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.Count) + { + case 1: + this.contentsOfAdmissions = DerSequence.GetInstance(seq[0]); + break; + case 2: + admissionAuthority = GeneralName.GetInstance(seq[0]); + contentsOfAdmissions = DerSequence.GetInstance(seq[1]); + break; + default: + throw new ArgumentException("Bad sequence size: " + seq.Count); + } + } + + /** + * 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 an Asn1Object + */ + public override Asn1Object 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 virtual GeneralName AdmissionAuthority + { + get { return admissionAuthority; } + } + + /** + * @return Returns the contentsOfAdmissions. + */ + public virtual Admissions[] GetContentsOfAdmissions() + { + Admissions[] result = new Admissions[contentsOfAdmissions.Count]; + + for (int i = 0; i < contentsOfAdmissions.Count; ++i) + { + result[i] = Admissions.GetInstance(contentsOfAdmissions[i]); + } + + return result; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/isismtt/x509/Admissions.cs b/bc-sharp-crypto/src/asn1/isismtt/x509/Admissions.cs new file mode 100644 index 0000000..e914db0 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/isismtt/x509/Admissions.cs @@ -0,0 +1,187 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.IsisMtt.X509 +{ + /** + * An Admissions structure. + *

+ *

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

+ *

+ * + * @see Org.BouncyCastle.Asn1.IsisMtt.X509.AdmissionSyntax + * @see Org.BouncyCastle.Asn1.IsisMtt.X509.ProfessionInfo + * @see Org.BouncyCastle.Asn1.IsisMtt.X509.NamingAuthority + */ + public class Admissions + : Asn1Encodable + { + private readonly GeneralName admissionAuthority; + private readonly NamingAuthority namingAuthority; + private readonly Asn1Sequence professionInfos; + + public static Admissions GetInstance( + object obj) + { + if (obj == null || obj is Admissions) + { + return (Admissions) obj; + } + + if (obj is Asn1Sequence) + { + return new Admissions((Asn1Sequence) obj); + } + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + /** + * 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.Count > 3) + throw new ArgumentException("Bad sequence size: " + seq.Count); + + IEnumerator e = seq.GetEnumerator(); + + e.MoveNext(); + Asn1Encodable o = (Asn1Encodable) e.Current; + if (o is Asn1TaggedObject) + { + switch (((Asn1TaggedObject)o).TagNo) + { + case 0: + admissionAuthority = GeneralName.GetInstance((Asn1TaggedObject)o, true); + break; + case 1: + namingAuthority = NamingAuthority.GetInstance((Asn1TaggedObject)o, true); + break; + default: + throw new ArgumentException("Bad tag number: " + ((Asn1TaggedObject)o).TagNo); + } + e.MoveNext(); + o = (Asn1Encodable) e.Current; + } + if (o is Asn1TaggedObject) + { + switch (((Asn1TaggedObject)o).TagNo) + { + case 1: + namingAuthority = NamingAuthority.GetInstance((Asn1TaggedObject)o, true); + break; + default: + throw new ArgumentException("Bad tag number: " + ((Asn1TaggedObject)o).TagNo); + } + e.MoveNext(); + o = (Asn1Encodable) e.Current; + } + professionInfos = Asn1Sequence.GetInstance(o); + if (e.MoveNext()) + { + throw new ArgumentException("Bad object encountered: " + Platform.GetTypeName(e.Current)); + } + } + + /** + * 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 virtual GeneralName AdmissionAuthority + { + get { return admissionAuthority; } + } + + public virtual NamingAuthority NamingAuthority + { + get { return namingAuthority; } + } + + public ProfessionInfo[] GetProfessionInfos() + { + ProfessionInfo[] infos = new ProfessionInfo[professionInfos.Count]; + int count = 0; + foreach (Asn1Encodable ae in professionInfos) + { + infos[count++] = ProfessionInfo.GetInstance(ae); + } + 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 an Asn1Object + */ + public override Asn1Object 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/bc-sharp-crypto/src/asn1/isismtt/x509/DeclarationOfMajority.cs b/bc-sharp-crypto/src/asn1/isismtt/x509/DeclarationOfMajority.cs new file mode 100644 index 0000000..c4ebb2b --- /dev/null +++ b/bc-sharp-crypto/src/asn1/isismtt/x509/DeclarationOfMajority.cs @@ -0,0 +1,172 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.IsisMtt.X509 +{ + /** + * 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 + : Asn1Encodable, IAsn1Choice + { + public enum Choice + { + NotYoungerThan = 0, + FullAgeAtCountry = 1, + DateOfBirth = 2 + }; + + private readonly Asn1TaggedObject declaration; + + public DeclarationOfMajority( + int notYoungerThan) + { + declaration = new DerTaggedObject(false, 0, new DerInteger(notYoungerThan)); + } + + public DeclarationOfMajority( + bool fullAge, + string country) + { + if (country.Length > 2) + throw new ArgumentException("country can only be 2 characters"); + + DerPrintableString countryString = new DerPrintableString(country, true); + + DerSequence seq; + if (fullAge) + { + seq = new DerSequence(countryString); + } + else + { + seq = new DerSequence(DerBoolean.False, countryString); + } + + this.declaration = new DerTaggedObject(false, 1, seq); + } + + public DeclarationOfMajority( + DerGeneralizedTime dateOfBirth) + { + this.declaration = new DerTaggedObject(false, 2, dateOfBirth); + } + + public static DeclarationOfMajority GetInstance( + object obj) + { + if (obj == null || obj is DeclarationOfMajority) + { + return (DeclarationOfMajority) obj; + } + + if (obj is Asn1TaggedObject) + { + return new DeclarationOfMajority((Asn1TaggedObject) obj); + } + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + private DeclarationOfMajority( + Asn1TaggedObject o) + { + if (o.TagNo > 2) + throw new ArgumentException("Bad tag number: " + o.TagNo); + + this.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 an Asn1Object + */ + public override Asn1Object ToAsn1Object() + { + return declaration; + } + + public Choice Type + { + get { return (Choice) declaration.TagNo; } + } + + /** + * @return notYoungerThan if that's what we are, -1 otherwise + */ + public virtual int NotYoungerThan + { + get + { + switch ((Choice) declaration.TagNo) + { + case Choice.NotYoungerThan: + return DerInteger.GetInstance(declaration, false).Value.IntValue; + default: + return -1; + } + } + } + + public virtual Asn1Sequence FullAgeAtCountry + { + get + { + switch ((Choice) declaration.TagNo) + { + case Choice.FullAgeAtCountry: + return Asn1Sequence.GetInstance(declaration, false); + default: + return null; + } + } + } + + public virtual DerGeneralizedTime DateOfBirth + { + get + { + switch ((Choice) declaration.TagNo) + { + case Choice.DateOfBirth: + return DerGeneralizedTime.GetInstance(declaration, false); + default: + return null; + } + } + } + } +} diff --git a/bc-sharp-crypto/src/asn1/isismtt/x509/MonetaryLimit.cs b/bc-sharp-crypto/src/asn1/isismtt/x509/MonetaryLimit.cs new file mode 100644 index 0000000..b792fff --- /dev/null +++ b/bc-sharp-crypto/src/asn1/isismtt/x509/MonetaryLimit.cs @@ -0,0 +1,122 @@ +using System; + +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.IsisMtt.X509 +{ + /** + * 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 + : Asn1Encodable + { + private readonly DerPrintableString currency; + private readonly DerInteger amount; + private readonly DerInteger exponent; + + public static MonetaryLimit GetInstance( + object obj) + { + if (obj == null || obj is MonetaryLimit) + { + return (MonetaryLimit) obj; + } + + if (obj is Asn1Sequence) + { + return new MonetaryLimit(Asn1Sequence.GetInstance(obj)); + } + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + private MonetaryLimit( + Asn1Sequence seq) + { + if (seq.Count != 3) + throw new ArgumentException("Bad sequence size: " + seq.Count); + + currency = DerPrintableString.GetInstance(seq[0]); + amount = DerInteger.GetInstance(seq[1]); + exponent = DerInteger.GetInstance(seq[2]); + } + + /** + * 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 virtual string Currency + { + get { return currency.GetString(); } + } + + public virtual BigInteger Amount + { + get { return amount.Value; } + } + + public virtual BigInteger Exponent + { + get { return exponent.Value; } + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *

+ * Returns: + *

+ *

+		*    MonetaryLimitSyntax ::= SEQUENCE
+		*    {
+		*      currency PrintableString (SIZE(3)),
+		*      amount INTEGER,
+		*      exponent INTEGER
+		*    }
+		* 
+ * + * @return an Asn1Object + */ + public override Asn1Object ToAsn1Object() + { + return new DerSequence(currency, amount, exponent); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/isismtt/x509/NamingAuthority.cs b/bc-sharp-crypto/src/asn1/isismtt/x509/NamingAuthority.cs new file mode 100644 index 0000000..35539f4 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/isismtt/x509/NamingAuthority.cs @@ -0,0 +1,215 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1.X500; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.IsisMtt.X509 +{ + /** + * 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 Org.BouncyCastle.Asn1.IsisMtt.X509.AdmissionSyntax + * + */ + public class NamingAuthority + : 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 readonly DerObjectIdentifier IdIsisMttATNamingAuthoritiesRechtWirtschaftSteuern + = new DerObjectIdentifier(IsisMttObjectIdentifiers.IdIsisMttATNamingAuthorities + ".1"); + + private readonly DerObjectIdentifier namingAuthorityID; + private readonly string namingAuthorityUrl; + private readonly DirectoryString namingAuthorityText; + + public static NamingAuthority GetInstance( + object obj) + { + if (obj == null || obj is NamingAuthority) + { + return (NamingAuthority) obj; + } + + if (obj is Asn1Sequence) + { + return new NamingAuthority((Asn1Sequence) obj); + } + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + public static NamingAuthority GetInstance( + Asn1TaggedObject obj, + bool isExplicit) + { + return GetInstance(Asn1Sequence.GetInstance(obj, isExplicit)); + } + + /** + * 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.Count > 3) + throw new ArgumentException("Bad sequence size: " + seq.Count); + + IEnumerator e = seq.GetEnumerator(); + + if (e.MoveNext()) + { + Asn1Encodable o = (Asn1Encodable) e.Current; + if (o is DerObjectIdentifier) + { + namingAuthorityID = (DerObjectIdentifier) o; + } + else if (o is DerIA5String) + { + namingAuthorityUrl = DerIA5String.GetInstance(o).GetString(); + } + else if (o is IAsn1String) + { + namingAuthorityText = DirectoryString.GetInstance(o); + } + else + { + throw new ArgumentException("Bad object encountered: " + Platform.GetTypeName(o)); + } + } + + if (e.MoveNext()) + { + Asn1Encodable o = (Asn1Encodable) e.Current; + if (o is DerIA5String) + { + namingAuthorityUrl = DerIA5String.GetInstance(o).GetString(); + } + else if (o is IAsn1String) + { + namingAuthorityText = DirectoryString.GetInstance(o); + } + else + { + throw new ArgumentException("Bad object encountered: " + Platform.GetTypeName(o)); + } + } + + if (e.MoveNext()) + { + Asn1Encodable o = (Asn1Encodable) e.Current; + if (o is IAsn1String) + { + namingAuthorityText = DirectoryString.GetInstance(o); + } + else + { + throw new ArgumentException("Bad object encountered: " + Platform.GetTypeName(o)); + } + } + } + + /** + * @return Returns the namingAuthorityID. + */ + public virtual DerObjectIdentifier NamingAuthorityID + { + get { return namingAuthorityID; } + } + + /** + * @return Returns the namingAuthorityText. + */ + public virtual DirectoryString NamingAuthorityText + { + get { return namingAuthorityText; } + } + + /** + * @return Returns the namingAuthorityUrl. + */ + public virtual string NamingAuthorityUrl + { + get { 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 an Asn1Object + */ + public override Asn1Object 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/bc-sharp-crypto/src/asn1/isismtt/x509/ProcurationSyntax.cs b/bc-sharp-crypto/src/asn1/isismtt/x509/ProcurationSyntax.cs new file mode 100644 index 0000000..f423646 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/isismtt/x509/ProcurationSyntax.cs @@ -0,0 +1,233 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1.X500; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.IsisMtt.X509 +{ + /** + * 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 + : Asn1Encodable + { + private readonly string country; + private readonly DirectoryString typeOfSubstitution; + private readonly GeneralName thirdPerson; + private readonly IssuerSerial certRef; + + public static ProcurationSyntax GetInstance( + object obj) + { + if (obj == null || obj is ProcurationSyntax) + { + return (ProcurationSyntax) obj; + } + + if (obj is Asn1Sequence) + { + return new ProcurationSyntax((Asn1Sequence) obj); + } + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + /** + * 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.Count < 1 || seq.Count > 3) + throw new ArgumentException("Bad sequence size: " + seq.Count); + + IEnumerator e = seq.GetEnumerator(); + + while (e.MoveNext()) + { + Asn1TaggedObject o = Asn1TaggedObject.GetInstance(e.Current); + switch (o.TagNo) + { + case 1: + country = DerPrintableString.GetInstance(o, true).GetString(); + break; + case 2: + typeOfSubstitution = DirectoryString.GetInstance(o, true); + break; + case 3: + Asn1Object signingFor = o.GetObject(); + if (signingFor is Asn1TaggedObject) + { + thirdPerson = GeneralName.GetInstance(signingFor); + } + else + { + certRef = IssuerSerial.GetInstance(signingFor); + } + break; + default: + throw new ArgumentException("Bad tag number: " + o.TagNo); + } + } + } + + /** + * 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 virtual string Country + { + get { return country; } + } + + public virtual DirectoryString TypeOfSubstitution + { + get { return typeOfSubstitution; } + } + + public virtual GeneralName ThirdPerson + { + get { return thirdPerson; } + } + + public virtual IssuerSerial CertRef + { + get { 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 an Asn1Object + */ + public override Asn1Object 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/bc-sharp-crypto/src/asn1/isismtt/x509/ProfessionInfo.cs b/bc-sharp-crypto/src/asn1/isismtt/x509/ProfessionInfo.cs new file mode 100644 index 0000000..671a465 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/isismtt/x509/ProfessionInfo.cs @@ -0,0 +1,387 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1.X500; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.IsisMtt.X509 +{ + /** + * 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 Org.BouncyCastle.Asn1.IsisMtt.X509.AdmissionSyntax + */ + public class ProfessionInfo + : Asn1Encodable + { + /** + * Rechtsanw�ltin + */ + public static readonly DerObjectIdentifier Rechtsanwltin = new DerObjectIdentifier( + NamingAuthority.IdIsisMttATNamingAuthoritiesRechtWirtschaftSteuern + ".1"); + + /** + * Rechtsanwalt + */ + public static readonly DerObjectIdentifier Rechtsanwalt = new DerObjectIdentifier( + NamingAuthority.IdIsisMttATNamingAuthoritiesRechtWirtschaftSteuern + ".2"); + + /** + * Rechtsbeistand + */ + public static readonly DerObjectIdentifier Rechtsbeistand = new DerObjectIdentifier( + NamingAuthority.IdIsisMttATNamingAuthoritiesRechtWirtschaftSteuern + ".3"); + + /** + * Steuerberaterin + */ + public static readonly DerObjectIdentifier Steuerberaterin = new DerObjectIdentifier( + NamingAuthority.IdIsisMttATNamingAuthoritiesRechtWirtschaftSteuern + ".4"); + + /** + * Steuerberater + */ + public static readonly DerObjectIdentifier Steuerberater = new DerObjectIdentifier( + NamingAuthority.IdIsisMttATNamingAuthoritiesRechtWirtschaftSteuern + ".5"); + + /** + * Steuerbevollm�chtigte + */ + public static readonly DerObjectIdentifier Steuerbevollmchtigte = new DerObjectIdentifier( + NamingAuthority.IdIsisMttATNamingAuthoritiesRechtWirtschaftSteuern + ".6"); + + /** + * Steuerbevollm�chtigter + */ + public static readonly DerObjectIdentifier Steuerbevollmchtigter = new DerObjectIdentifier( + NamingAuthority.IdIsisMttATNamingAuthoritiesRechtWirtschaftSteuern + ".7"); + + /** + * Notarin + */ + public static readonly DerObjectIdentifier Notarin = new DerObjectIdentifier( + NamingAuthority.IdIsisMttATNamingAuthoritiesRechtWirtschaftSteuern + ".8"); + + /** + * Notar + */ + public static readonly DerObjectIdentifier Notar = new DerObjectIdentifier( + NamingAuthority.IdIsisMttATNamingAuthoritiesRechtWirtschaftSteuern + ".9"); + + /** + * Notarvertreterin + */ + public static readonly DerObjectIdentifier Notarvertreterin = new DerObjectIdentifier( + NamingAuthority.IdIsisMttATNamingAuthoritiesRechtWirtschaftSteuern + ".10"); + + /** + * Notarvertreter + */ + public static readonly DerObjectIdentifier Notarvertreter = new DerObjectIdentifier( + NamingAuthority.IdIsisMttATNamingAuthoritiesRechtWirtschaftSteuern + ".11"); + + /** + * Notariatsverwalterin + */ + public static readonly DerObjectIdentifier Notariatsverwalterin = new DerObjectIdentifier( + NamingAuthority.IdIsisMttATNamingAuthoritiesRechtWirtschaftSteuern + ".12"); + + /** + * Notariatsverwalter + */ + public static readonly DerObjectIdentifier Notariatsverwalter = new DerObjectIdentifier( + NamingAuthority.IdIsisMttATNamingAuthoritiesRechtWirtschaftSteuern + ".13"); + + /** + * Wirtschaftspr�ferin + */ + public static readonly DerObjectIdentifier Wirtschaftsprferin = new DerObjectIdentifier( + NamingAuthority.IdIsisMttATNamingAuthoritiesRechtWirtschaftSteuern + ".14"); + + /** + * Wirtschaftspr�fer + */ + public static readonly DerObjectIdentifier Wirtschaftsprfer = new DerObjectIdentifier( + NamingAuthority.IdIsisMttATNamingAuthoritiesRechtWirtschaftSteuern + ".15"); + + /** + * Vereidigte Buchpr�ferin + */ + public static readonly DerObjectIdentifier VereidigteBuchprferin = new DerObjectIdentifier( + NamingAuthority.IdIsisMttATNamingAuthoritiesRechtWirtschaftSteuern + ".16"); + + /** + * Vereidigter Buchpr�fer + */ + public static readonly DerObjectIdentifier VereidigterBuchprfer = new DerObjectIdentifier( + NamingAuthority.IdIsisMttATNamingAuthoritiesRechtWirtschaftSteuern + ".17"); + + /** + * Patentanw�ltin + */ + public static readonly DerObjectIdentifier Patentanwltin = new DerObjectIdentifier( + NamingAuthority.IdIsisMttATNamingAuthoritiesRechtWirtschaftSteuern + ".18"); + + /** + * Patentanwalt + */ + public static readonly DerObjectIdentifier Patentanwalt = new DerObjectIdentifier( + NamingAuthority.IdIsisMttATNamingAuthoritiesRechtWirtschaftSteuern + ".19"); + + private readonly NamingAuthority namingAuthority; + private readonly Asn1Sequence professionItems; + private readonly Asn1Sequence professionOids; + private readonly string registrationNumber; + private readonly Asn1OctetString addProfessionInfo; + + public static ProfessionInfo GetInstance( + object obj) + { + if (obj == null || obj is ProfessionInfo) + { + return (ProfessionInfo) obj; + } + + if (obj is Asn1Sequence) + { + return new ProfessionInfo((Asn1Sequence) obj); + } + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + /** + * 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.Count > 5) + throw new ArgumentException("Bad sequence size: " + seq.Count); + + IEnumerator e = seq.GetEnumerator(); + + e.MoveNext(); + Asn1Encodable o = (Asn1Encodable) e.Current; + + if (o is Asn1TaggedObject) + { + Asn1TaggedObject ato = (Asn1TaggedObject) o; + if (ato.TagNo != 0) + throw new ArgumentException("Bad tag number: " + ato.TagNo); + + namingAuthority = NamingAuthority.GetInstance(ato, true); + e.MoveNext(); + o = (Asn1Encodable) e.Current; + } + + professionItems = Asn1Sequence.GetInstance(o); + + if (e.MoveNext()) + { + o = (Asn1Encodable) e.Current; + if (o is Asn1Sequence) + { + professionOids = Asn1Sequence.GetInstance(o); + } + else if (o is DerPrintableString) + { + registrationNumber = DerPrintableString.GetInstance(o).GetString(); + } + else if (o is Asn1OctetString) + { + addProfessionInfo = Asn1OctetString.GetInstance(o); + } + else + { + throw new ArgumentException("Bad object encountered: " + Platform.GetTypeName(o)); + } + } + + if (e.MoveNext()) + { + o = (Asn1Encodable) e.Current; + if (o is DerPrintableString) + { + registrationNumber = DerPrintableString.GetInstance(o).GetString(); + } + else if (o is DerOctetString) + { + addProfessionInfo = (DerOctetString) o; + } + else + { + throw new ArgumentException("Bad object encountered: " + Platform.GetTypeName(o)); + } + } + + if (e.MoveNext()) + { + o = (Asn1Encodable) e.Current; + if (o is DerOctetString) + { + addProfessionInfo = (DerOctetString) o; + } + else + { + throw new ArgumentException("Bad object encountered: " + Platform.GetTypeName(o)); + } + } + } + + /** + * 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; + this.professionItems = new DerSequence(professionItems); + if (professionOids != null) + { + this.professionOids = new DerSequence(professionOids); + } + 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 an Asn1Object + */ + public override Asn1Object 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 virtual Asn1OctetString AddProfessionInfo + { + get { return addProfessionInfo; } + } + + /** + * @return Returns the namingAuthority. + */ + public virtual NamingAuthority NamingAuthority + { + get { return namingAuthority; } + } + + /** + * @return Returns the professionItems. + */ + public virtual DirectoryString[] GetProfessionItems() + { + DirectoryString[] result = new DirectoryString[professionItems.Count]; + + for (int i = 0; i < professionItems.Count; ++i) + { + result[i] = DirectoryString.GetInstance(professionItems[i]); + } + + return result; + } + + /** + * @return Returns the professionOids. + */ + public virtual DerObjectIdentifier[] GetProfessionOids() + { + if (professionOids == null) + { + return new DerObjectIdentifier[0]; + } + + DerObjectIdentifier[] result = new DerObjectIdentifier[professionOids.Count]; + + for (int i = 0; i < professionOids.Count; ++i) + { + result[i] = DerObjectIdentifier.GetInstance(professionOids[i]); + } + + return result; + } + + /** + * @return Returns the registrationNumber. + */ + public virtual string RegistrationNumber + { + get { return registrationNumber; } + } + } +} diff --git a/bc-sharp-crypto/src/asn1/isismtt/x509/Restriction.cs b/bc-sharp-crypto/src/asn1/isismtt/x509/Restriction.cs new file mode 100644 index 0000000..75df252 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/isismtt/x509/Restriction.cs @@ -0,0 +1,82 @@ +using System; + +using Org.BouncyCastle.Asn1.X500; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.IsisMtt.X509 +{ + /** + * Some other restriction regarding the usage of this certificate. + *

+ *

+	*  RestrictionSyntax ::= DirectoryString (SIZE(1..1024))
+	* 
+ */ + public class Restriction + : Asn1Encodable + { + private readonly DirectoryString restriction; + + public static Restriction GetInstance( + object obj) + { + if (obj is Restriction) + return (Restriction) obj; + + if (obj is IAsn1String) + return new Restriction(DirectoryString.GetInstance(obj)); + + throw new ArgumentException("Unknown object in GetInstance: " + Platform.GetTypeName(obj), "obj"); + } + + /** + * Constructor from DirectoryString. + *

+ * The DirectoryString is of type RestrictionSyntax: + *

+ *

+		*      RestrictionSyntax ::= DirectoryString (SIZE(1..1024))
+		* 
+ * + * @param restriction A IAsn1String. + */ + private Restriction( + DirectoryString restriction) + { + this.restriction = restriction; + } + + /** + * Constructor from a given details. + * + * @param restriction The description of the restriction. + */ + public Restriction( + string restriction) + { + this.restriction = new DirectoryString(restriction); + } + + public virtual DirectoryString RestrictionString + { + get { return restriction; } + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *

+ * Returns: + *

+ *

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

+ *

+ * + * @return an Asn1Object + */ + public override Asn1Object ToAsn1Object() + { + return restriction.ToAsn1Object(); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/kisa/KISAObjectIdentifiers.cs b/bc-sharp-crypto/src/asn1/kisa/KISAObjectIdentifiers.cs new file mode 100644 index 0000000..05351ec --- /dev/null +++ b/bc-sharp-crypto/src/asn1/kisa/KISAObjectIdentifiers.cs @@ -0,0 +1,8 @@ +namespace Org.BouncyCastle.Asn1.Kisa +{ + public abstract class KisaObjectIdentifiers + { + public static readonly DerObjectIdentifier IdSeedCbc = new DerObjectIdentifier("1.2.410.200004.1.4"); + public static readonly DerObjectIdentifier IdNpkiAppCmsSeedWrap = new DerObjectIdentifier("1.2.410.200004.7.1.1.1"); + } +} diff --git a/bc-sharp-crypto/src/asn1/microsoft/MicrosoftObjectIdentifiers.cs b/bc-sharp-crypto/src/asn1/microsoft/MicrosoftObjectIdentifiers.cs new file mode 100644 index 0000000..bc48c3f --- /dev/null +++ b/bc-sharp-crypto/src/asn1/microsoft/MicrosoftObjectIdentifiers.cs @@ -0,0 +1,19 @@ +using System; + +namespace Org.BouncyCastle.Asn1.Microsoft +{ + public abstract class MicrosoftObjectIdentifiers + { + // + // Microsoft + // iso(1) identified-organization(3) dod(6) internet(1) private(4) enterprise(1) Microsoft(311) + // + public static readonly DerObjectIdentifier Microsoft = new DerObjectIdentifier("1.3.6.1.4.1.311"); + public static readonly DerObjectIdentifier MicrosoftCertTemplateV1 = Microsoft.Branch("20.2"); + public static readonly DerObjectIdentifier MicrosoftCAVersion = Microsoft.Branch("21.1"); + public static readonly DerObjectIdentifier MicrosoftPrevCACertHash = Microsoft.Branch("21.2"); + public static readonly DerObjectIdentifier MicrosoftCrlNextPublish = Microsoft.Branch("21.4"); + public static readonly DerObjectIdentifier MicrosoftCertTemplateV2 = Microsoft.Branch("21.7"); + public static readonly DerObjectIdentifier MicrosoftAppPolicies = Microsoft.Branch("21.10"); + } +} diff --git a/bc-sharp-crypto/src/asn1/misc/CAST5CBCParameters.cs b/bc-sharp-crypto/src/asn1/misc/CAST5CBCParameters.cs new file mode 100644 index 0000000..51fd660 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/misc/CAST5CBCParameters.cs @@ -0,0 +1,74 @@ +using System; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Misc +{ + public class Cast5CbcParameters + : Asn1Encodable + { + private readonly DerInteger keyLength; + private readonly Asn1OctetString iv; + + public static Cast5CbcParameters GetInstance( + object o) + { + if (o is Cast5CbcParameters) + { + return (Cast5CbcParameters) o; + } + + if (o is Asn1Sequence) + { + return new Cast5CbcParameters((Asn1Sequence) o); + } + + throw new ArgumentException("unknown object in Cast5CbcParameters factory"); + } + + public Cast5CbcParameters( + byte[] iv, + int keyLength) + { + this.iv = new DerOctetString(iv); + this.keyLength = new DerInteger(keyLength); + } + + private Cast5CbcParameters( + Asn1Sequence seq) + { + if (seq.Count != 2) + throw new ArgumentException("Wrong number of elements in sequence", "seq"); + + iv = (Asn1OctetString) seq[0]; + keyLength = (DerInteger) seq[1]; + } + + public byte[] GetIV() + { + return Arrays.Clone(iv.GetOctets()); + } + + public int KeyLength + { + get { return keyLength.Value.IntValue; } + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *
+         * cast5CBCParameters ::= Sequence {
+         *                           iv         OCTET STRING DEFAULT 0,
+         *                                  -- Initialization vector
+         *                           keyLength  Integer
+         *                                  -- Key length, in bits
+         *                      }
+         * 
+ */ + public override Asn1Object ToAsn1Object() + { + return new DerSequence(iv, keyLength); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/misc/IDEACBCPar.cs b/bc-sharp-crypto/src/asn1/misc/IDEACBCPar.cs new file mode 100644 index 0000000..72a60b9 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/misc/IDEACBCPar.cs @@ -0,0 +1,68 @@ +using System; + +using Org.BouncyCastle.Asn1; + +namespace Org.BouncyCastle.Asn1.Misc +{ + public class IdeaCbcPar + : Asn1Encodable + { + internal Asn1OctetString iv; + + public static IdeaCbcPar GetInstance( + object o) + { + if (o is IdeaCbcPar) + { + return (IdeaCbcPar) o; + } + + if (o is Asn1Sequence) + { + return new IdeaCbcPar((Asn1Sequence) o); + } + + throw new ArgumentException("unknown object in IDEACBCPar factory"); + } + + public IdeaCbcPar( + byte[] iv) + { + this.iv = new DerOctetString(iv); + } + + private IdeaCbcPar( + Asn1Sequence seq) + { + if (seq.Count == 1) + { + iv = (Asn1OctetString) seq[0]; + } + } + + public byte[] GetIV() + { + return iv == null ? null : iv.GetOctets(); + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *
+         * IDEA-CBCPar ::= Sequence {
+         *                      iv    OCTET STRING OPTIONAL -- exactly 8 octets
+         *                  }
+         * 
+ */ + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(); + + if (iv != null) + { + v.Add(iv); + } + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/misc/MiscObjectIdentifiers.cs b/bc-sharp-crypto/src/asn1/misc/MiscObjectIdentifiers.cs new file mode 100644 index 0000000..8128b69 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/misc/MiscObjectIdentifiers.cs @@ -0,0 +1,79 @@ +namespace Org.BouncyCastle.Asn1.Misc +{ + public abstract class MiscObjectIdentifiers + { + // + // Netscape + // iso/itu(2) joint-assign(16) us(840) uscompany(1) Netscape(113730) cert-extensions(1) } + // + public static readonly DerObjectIdentifier Netscape = new DerObjectIdentifier("2.16.840.1.113730.1"); + public static readonly DerObjectIdentifier NetscapeCertType = Netscape.Branch("1"); + public static readonly DerObjectIdentifier NetscapeBaseUrl = Netscape.Branch("2"); + public static readonly DerObjectIdentifier NetscapeRevocationUrl = Netscape.Branch("3"); + public static readonly DerObjectIdentifier NetscapeCARevocationUrl = Netscape.Branch("4"); + public static readonly DerObjectIdentifier NetscapeRenewalUrl = Netscape.Branch("7"); + public static readonly DerObjectIdentifier NetscapeCAPolicyUrl = Netscape.Branch("8"); + public static readonly DerObjectIdentifier NetscapeSslServerName = Netscape.Branch("12"); + public static readonly DerObjectIdentifier NetscapeCertComment = Netscape.Branch("13"); + + // + // Verisign + // iso/itu(2) joint-assign(16) us(840) uscompany(1) verisign(113733) cert-extensions(1) } + // + public static readonly DerObjectIdentifier Verisign = new DerObjectIdentifier("2.16.840.1.113733.1"); + + // + // CZAG - country, zip, age, and gender + // + public static readonly DerObjectIdentifier VerisignCzagExtension = Verisign.Branch("6.3"); + + public static readonly DerObjectIdentifier VerisignPrivate_6_9 = Verisign.Branch("6.9"); + public static readonly DerObjectIdentifier VerisignOnSiteJurisdictionHash = Verisign.Branch("6.11"); + public static readonly DerObjectIdentifier VerisignBitString_6_13 = Verisign.Branch("6.13"); + + // D&B D-U-N-S number + public static readonly DerObjectIdentifier VerisignDnbDunsNumber = Verisign.Branch("6.15"); + + public static readonly DerObjectIdentifier VerisignIssStrongCrypto = Verisign.Branch("8.1"); + + // + // Novell + // iso/itu(2) country(16) us(840) organization(1) novell(113719) + // + public static readonly string Novell = "2.16.840.1.113719"; + public static readonly DerObjectIdentifier NovellSecurityAttribs = new DerObjectIdentifier(Novell + ".1.9.4.1"); + + // + // Entrust + // iso(1) member-body(16) us(840) nortelnetworks(113533) entrust(7) + // + public static readonly string Entrust = "1.2.840.113533.7"; + public static readonly DerObjectIdentifier EntrustVersionExtension = new DerObjectIdentifier(Entrust + ".65.0"); + + // + // Ascom + // + public static readonly DerObjectIdentifier as_sys_sec_alg_ideaCBC = new DerObjectIdentifier("1.3.6.1.4.1.188.7.1.1.2"); + + // + // Peter Gutmann's Cryptlib + // + public static readonly DerObjectIdentifier cryptlib = new DerObjectIdentifier("1.3.6.1.4.1.3029"); + + public static readonly DerObjectIdentifier cryptlib_algorithm = cryptlib.Branch("1"); + public static readonly DerObjectIdentifier cryptlib_algorithm_blowfish_ECB = cryptlib_algorithm.Branch("1.1"); + public static readonly DerObjectIdentifier cryptlib_algorithm_blowfish_CBC = cryptlib_algorithm.Branch("1.2"); + public static readonly DerObjectIdentifier cryptlib_algorithm_blowfish_CFB = cryptlib_algorithm.Branch("1.3"); + public static readonly DerObjectIdentifier cryptlib_algorithm_blowfish_OFB = cryptlib_algorithm.Branch("1.4"); + + // + // Blake2b + // + public static readonly DerObjectIdentifier blake2 = new DerObjectIdentifier("1.3.6.1.4.1.1722.12.2"); + + public static readonly DerObjectIdentifier id_blake2b160 = blake2.Branch("1.5"); + public static readonly DerObjectIdentifier id_blake2b256 = blake2.Branch("1.8"); + public static readonly DerObjectIdentifier id_blake2b384 = blake2.Branch("1.12"); + public static readonly DerObjectIdentifier id_blake2b512 = blake2.Branch("1.16"); + } +} diff --git a/bc-sharp-crypto/src/asn1/misc/NetscapeCertType.cs b/bc-sharp-crypto/src/asn1/misc/NetscapeCertType.cs new file mode 100644 index 0000000..d809eae --- /dev/null +++ b/bc-sharp-crypto/src/asn1/misc/NetscapeCertType.cs @@ -0,0 +1,54 @@ +using Org.BouncyCastle.Asn1; + +namespace Org.BouncyCastle.Asn1.Misc +{ + /** + * 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 + : DerBitString + { + public const int SslClient = (1 << 7); + public const int SslServer = (1 << 6); + public const int Smime = (1 << 5); + public const int ObjectSigning = (1 << 4); + public const int Reserved = (1 << 3); + public const int SslCA = (1 << 2); + public const int SmimeCA = (1 << 1); + public const 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) + : base(usage) + { + } + + public NetscapeCertType(DerBitString usage) + : base(usage.GetBytes(), usage.PadBits) + { + } + + public override string ToString() + { + byte[] data = GetBytes(); + return "NetscapeCertType: 0x" + (data[0] & 0xff).ToString("X"); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/misc/NetscapeRevocationURL.cs b/bc-sharp-crypto/src/asn1/misc/NetscapeRevocationURL.cs new file mode 100644 index 0000000..6cac031 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/misc/NetscapeRevocationURL.cs @@ -0,0 +1,18 @@ +using Org.BouncyCastle.Asn1; + +namespace Org.BouncyCastle.Asn1.Misc +{ + public class NetscapeRevocationUrl + : DerIA5String + { + public NetscapeRevocationUrl(DerIA5String str) + : base(str.GetString()) + { + } + + public override string ToString() + { + return "NetscapeRevocationUrl: " + this.GetString(); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/misc/VerisignCzagExtension.cs b/bc-sharp-crypto/src/asn1/misc/VerisignCzagExtension.cs new file mode 100644 index 0000000..1c3054b --- /dev/null +++ b/bc-sharp-crypto/src/asn1/misc/VerisignCzagExtension.cs @@ -0,0 +1,18 @@ +using Org.BouncyCastle.Asn1; + +namespace Org.BouncyCastle.Asn1.Misc +{ + public class VerisignCzagExtension + : DerIA5String + { + public VerisignCzagExtension(DerIA5String str) + : base(str.GetString()) + { + } + + public override string ToString() + { + return "VerisignCzagExtension: " + this.GetString(); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/mozilla/PublicKeyAndChallenge.cs b/bc-sharp-crypto/src/asn1/mozilla/PublicKeyAndChallenge.cs new file mode 100644 index 0000000..ff2a119 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/mozilla/PublicKeyAndChallenge.cs @@ -0,0 +1,68 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Mozilla +{ + /** + * 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 + : Asn1Encodable + { + private Asn1Sequence pkacSeq; + private SubjectPublicKeyInfo spki; + private DerIA5String challenge; + + public static PublicKeyAndChallenge GetInstance( + object obj) + { + if (obj is PublicKeyAndChallenge) + { + return (PublicKeyAndChallenge) obj; + } + + if (obj is Asn1Sequence) + { + return new PublicKeyAndChallenge((Asn1Sequence) obj); + } + + throw new ArgumentException( + "unknown object in 'PublicKeyAndChallenge' factory : " + + Platform.GetTypeName(obj) + "."); + } + + public PublicKeyAndChallenge( + Asn1Sequence seq) + { + pkacSeq = seq; + spki = SubjectPublicKeyInfo.GetInstance(seq[0]); + challenge = DerIA5String.GetInstance(seq[1]); + } + + public override Asn1Object ToAsn1Object() + { + return pkacSeq; + } + + public SubjectPublicKeyInfo SubjectPublicKeyInfo + { + get { return spki; } + } + + public DerIA5String Challenge + { + get { return challenge; } + } + } +} diff --git a/bc-sharp-crypto/src/asn1/nist/NISTNamedCurves.cs b/bc-sharp-crypto/src/asn1/nist/NISTNamedCurves.cs new file mode 100644 index 0000000..f6c1598 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/nist/NISTNamedCurves.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Sec; +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; + +namespace Org.BouncyCastle.Asn1.Nist +{ + /** + * Utility class for fetching curves using their NIST names as published in FIPS-PUB 186-3 + */ + public sealed class NistNamedCurves + { + private NistNamedCurves() + { + } + + private static readonly IDictionary objIds = Platform.CreateHashtable(); + private static readonly IDictionary names = Platform.CreateHashtable(); + + private static void DefineCurveAlias( + string name, + DerObjectIdentifier oid) + { + objIds.Add(Platform.ToUpperInvariant(name), oid); + names.Add(oid, name); + } + + static NistNamedCurves() + { + DefineCurveAlias("B-163", SecObjectIdentifiers.SecT163r2); + DefineCurveAlias("B-233", SecObjectIdentifiers.SecT233r1); + DefineCurveAlias("B-283", SecObjectIdentifiers.SecT283r1); + DefineCurveAlias("B-409", SecObjectIdentifiers.SecT409r1); + DefineCurveAlias("B-571", SecObjectIdentifiers.SecT571r1); + + DefineCurveAlias("K-163", SecObjectIdentifiers.SecT163k1); + DefineCurveAlias("K-233", SecObjectIdentifiers.SecT233k1); + DefineCurveAlias("K-283", SecObjectIdentifiers.SecT283k1); + DefineCurveAlias("K-409", SecObjectIdentifiers.SecT409k1); + DefineCurveAlias("K-571", SecObjectIdentifiers.SecT571k1); + + DefineCurveAlias("P-192", SecObjectIdentifiers.SecP192r1); + DefineCurveAlias("P-224", SecObjectIdentifiers.SecP224r1); + DefineCurveAlias("P-256", SecObjectIdentifiers.SecP256r1); + DefineCurveAlias("P-384", SecObjectIdentifiers.SecP384r1); + DefineCurveAlias("P-521", SecObjectIdentifiers.SecP521r1); + } + + public static X9ECParameters GetByName( + string name) + { + DerObjectIdentifier oid = GetOid(name); + return oid == null ? null : GetByOid(oid); + } + + /** + * 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[Platform.ToUpperInvariant(name)]; + } + + /** + * return the named curve name represented by the given object identifier. + */ + public static string GetName( + DerObjectIdentifier oid) + { + return (string) names[oid]; + } + + /** + * returns an enumeration containing the name strings for curves + * contained in this structure. + */ + public static IEnumerable Names + { + get { return new EnumerableProxy(names.Values); } + } + } +} diff --git a/bc-sharp-crypto/src/asn1/nist/NISTObjectIdentifiers.cs b/bc-sharp-crypto/src/asn1/nist/NISTObjectIdentifiers.cs new file mode 100644 index 0000000..55b9d8e --- /dev/null +++ b/bc-sharp-crypto/src/asn1/nist/NISTObjectIdentifiers.cs @@ -0,0 +1,71 @@ +using Org.BouncyCastle.Asn1; + +namespace Org.BouncyCastle.Asn1.Nist +{ + public sealed class NistObjectIdentifiers + { + private NistObjectIdentifiers() + { + } + + // + // NIST + // iso/itu(2) joint-assign(16) us(840) organization(1) gov(101) csor(3) + + // + // nistalgorithms(4) + // + public static readonly DerObjectIdentifier NistAlgorithm = new DerObjectIdentifier("2.16.840.1.101.3.4"); + + public static readonly DerObjectIdentifier HashAlgs = NistAlgorithm.Branch("2"); + + public static readonly DerObjectIdentifier IdSha256 = HashAlgs.Branch("1"); + public static readonly DerObjectIdentifier IdSha384 = HashAlgs.Branch("2"); + public static readonly DerObjectIdentifier IdSha512 = HashAlgs.Branch("3"); + public static readonly DerObjectIdentifier IdSha224 = HashAlgs.Branch("4"); + public static readonly DerObjectIdentifier IdSha512_224 = HashAlgs.Branch("5"); + public static readonly DerObjectIdentifier IdSha512_256 = HashAlgs.Branch("6"); + public static readonly DerObjectIdentifier IdSha3_224 = HashAlgs.Branch("7"); + public static readonly DerObjectIdentifier IdSha3_256 = HashAlgs.Branch("8"); + public static readonly DerObjectIdentifier IdSha3_384 = HashAlgs.Branch("9"); + public static readonly DerObjectIdentifier IdSha3_512 = HashAlgs.Branch("10"); + public static readonly DerObjectIdentifier IdShake128 = HashAlgs.Branch("11"); + public static readonly DerObjectIdentifier IdShake256 = HashAlgs.Branch("12"); + + public static readonly DerObjectIdentifier Aes = new DerObjectIdentifier(NistAlgorithm + ".1"); + + public static readonly DerObjectIdentifier IdAes128Ecb = new DerObjectIdentifier(Aes + ".1"); + public static readonly DerObjectIdentifier IdAes128Cbc = new DerObjectIdentifier(Aes + ".2"); + public static readonly DerObjectIdentifier IdAes128Ofb = new DerObjectIdentifier(Aes + ".3"); + public static readonly DerObjectIdentifier IdAes128Cfb = new DerObjectIdentifier(Aes + ".4"); + public static readonly DerObjectIdentifier IdAes128Wrap = new DerObjectIdentifier(Aes + ".5"); + public static readonly DerObjectIdentifier IdAes128Gcm = new DerObjectIdentifier(Aes + ".6"); + public static readonly DerObjectIdentifier IdAes128Ccm = new DerObjectIdentifier(Aes + ".7"); + + public static readonly DerObjectIdentifier IdAes192Ecb = new DerObjectIdentifier(Aes + ".21"); + public static readonly DerObjectIdentifier IdAes192Cbc = new DerObjectIdentifier(Aes + ".22"); + public static readonly DerObjectIdentifier IdAes192Ofb = new DerObjectIdentifier(Aes + ".23"); + public static readonly DerObjectIdentifier IdAes192Cfb = new DerObjectIdentifier(Aes + ".24"); + public static readonly DerObjectIdentifier IdAes192Wrap = new DerObjectIdentifier(Aes + ".25"); + public static readonly DerObjectIdentifier IdAes192Gcm = new DerObjectIdentifier(Aes + ".26"); + public static readonly DerObjectIdentifier IdAes192Ccm = new DerObjectIdentifier(Aes + ".27"); + + public static readonly DerObjectIdentifier IdAes256Ecb = new DerObjectIdentifier(Aes + ".41"); + public static readonly DerObjectIdentifier IdAes256Cbc = new DerObjectIdentifier(Aes + ".42"); + public static readonly DerObjectIdentifier IdAes256Ofb = new DerObjectIdentifier(Aes + ".43"); + public static readonly DerObjectIdentifier IdAes256Cfb = new DerObjectIdentifier(Aes + ".44"); + public static readonly DerObjectIdentifier IdAes256Wrap = new DerObjectIdentifier(Aes + ".45"); + public static readonly DerObjectIdentifier IdAes256Gcm = new DerObjectIdentifier(Aes + ".46"); + public static readonly DerObjectIdentifier IdAes256Ccm = new DerObjectIdentifier(Aes + ".47"); + + // + // signatures + // + public static readonly DerObjectIdentifier IdDsaWithSha2 = new DerObjectIdentifier(NistAlgorithm + ".3"); + + public static readonly DerObjectIdentifier DsaWithSha224 = new DerObjectIdentifier(IdDsaWithSha2 + ".1"); + public static readonly DerObjectIdentifier DsaWithSha256 = new DerObjectIdentifier(IdDsaWithSha2 + ".2"); + public static readonly DerObjectIdentifier DsaWithSha384 = new DerObjectIdentifier(IdDsaWithSha2 + ".3"); + public static readonly DerObjectIdentifier DsaWithSha512 = new DerObjectIdentifier(IdDsaWithSha2 + ".4"); + } +} diff --git a/bc-sharp-crypto/src/asn1/ntt/NTTObjectIdentifiers.cs b/bc-sharp-crypto/src/asn1/ntt/NTTObjectIdentifiers.cs new file mode 100644 index 0000000..cd25956 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/ntt/NTTObjectIdentifiers.cs @@ -0,0 +1,14 @@ +namespace Org.BouncyCastle.Asn1.Ntt +{ + /// From RFC 3657 + public abstract class NttObjectIdentifiers + { + public static readonly DerObjectIdentifier IdCamellia128Cbc = new DerObjectIdentifier("1.2.392.200011.61.1.1.1.2"); + public static readonly DerObjectIdentifier IdCamellia192Cbc = new DerObjectIdentifier("1.2.392.200011.61.1.1.1.3"); + public static readonly DerObjectIdentifier IdCamellia256Cbc = new DerObjectIdentifier("1.2.392.200011.61.1.1.1.4"); + + public static readonly DerObjectIdentifier IdCamellia128Wrap = new DerObjectIdentifier("1.2.392.200011.61.1.1.3.2"); + public static readonly DerObjectIdentifier IdCamellia192Wrap = new DerObjectIdentifier("1.2.392.200011.61.1.1.3.3"); + public static readonly DerObjectIdentifier IdCamellia256Wrap = new DerObjectIdentifier("1.2.392.200011.61.1.1.3.4"); + } +} diff --git a/bc-sharp-crypto/src/asn1/ocsp/BasicOCSPResponse.cs b/bc-sharp-crypto/src/asn1/ocsp/BasicOCSPResponse.cs new file mode 100644 index 0000000..e6aa1f8 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/ocsp/BasicOCSPResponse.cs @@ -0,0 +1,137 @@ +using System; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Ocsp +{ + public class BasicOcspResponse + : Asn1Encodable + { + private readonly ResponseData tbsResponseData; + private readonly AlgorithmIdentifier signatureAlgorithm; + private readonly DerBitString signature; + private readonly Asn1Sequence certs; + + public static BasicOcspResponse GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); + } + + public static BasicOcspResponse GetInstance( + object obj) + { + if (obj == null || obj is BasicOcspResponse) + { + return (BasicOcspResponse)obj; + } + + if (obj is Asn1Sequence) + { + return new BasicOcspResponse((Asn1Sequence)obj); + } + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + public BasicOcspResponse( + ResponseData tbsResponseData, + AlgorithmIdentifier signatureAlgorithm, + DerBitString signature, + Asn1Sequence certs) + { + this.tbsResponseData = tbsResponseData; + this.signatureAlgorithm = signatureAlgorithm; + this.signature = signature; + this.certs = certs; + } + + private BasicOcspResponse( + Asn1Sequence seq) + { + this.tbsResponseData = ResponseData.GetInstance(seq[0]); + this.signatureAlgorithm = AlgorithmIdentifier.GetInstance(seq[1]); + this.signature = (DerBitString)seq[2]; + + if (seq.Count > 3) + { + this.certs = Asn1Sequence.GetInstance((Asn1TaggedObject)seq[3], true); + } + } + + [Obsolete("Use TbsResponseData property instead")] + public ResponseData GetTbsResponseData() + { + return tbsResponseData; + } + + public ResponseData TbsResponseData + { + get { return tbsResponseData; } + } + + [Obsolete("Use SignatureAlgorithm property instead")] + public AlgorithmIdentifier GetSignatureAlgorithm() + { + return signatureAlgorithm; + } + + public AlgorithmIdentifier SignatureAlgorithm + { + get { return signatureAlgorithm; } + } + + [Obsolete("Use Signature property instead")] + public DerBitString GetSignature() + { + return signature; + } + + public DerBitString Signature + { + get { return signature; } + } + + public byte[] GetSignatureOctets() + { + return signature.GetOctets(); + } + + [Obsolete("Use Certs property instead")] + public Asn1Sequence GetCerts() + { + return certs; + } + + public Asn1Sequence Certs + { + get { 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 override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector( + tbsResponseData, signatureAlgorithm, signature); + + if (certs != null) + { + v.Add(new DerTaggedObject(true, 0, certs)); + } + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/ocsp/CertID.cs b/bc-sharp-crypto/src/asn1/ocsp/CertID.cs new file mode 100644 index 0000000..523f6b8 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/ocsp/CertID.cs @@ -0,0 +1,99 @@ +using System; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Ocsp +{ + public class CertID + : Asn1Encodable + { + private readonly AlgorithmIdentifier hashAlgorithm; + private readonly Asn1OctetString issuerNameHash; + private readonly Asn1OctetString issuerKeyHash; + private readonly DerInteger serialNumber; + + public static CertID GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); + } + + public static CertID GetInstance( + object obj) + { + if (obj == null || obj is CertID) + { + return (CertID)obj; + } + + if (obj is Asn1Sequence) + { + return new CertID((Asn1Sequence)obj); + } + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + public CertID( + AlgorithmIdentifier hashAlgorithm, + Asn1OctetString issuerNameHash, + Asn1OctetString issuerKeyHash, + DerInteger serialNumber) + { + this.hashAlgorithm = hashAlgorithm; + this.issuerNameHash = issuerNameHash; + this.issuerKeyHash = issuerKeyHash; + this.serialNumber = serialNumber; + } + + private CertID( + Asn1Sequence seq) + { + if (seq.Count != 4) + throw new ArgumentException("Wrong number of elements in sequence", "seq"); + + this.hashAlgorithm = AlgorithmIdentifier.GetInstance(seq[0]); + this.issuerNameHash = Asn1OctetString.GetInstance(seq[1]); + this.issuerKeyHash = Asn1OctetString.GetInstance(seq[2]); + this.serialNumber = DerInteger.GetInstance(seq[3]); + } + + public AlgorithmIdentifier HashAlgorithm + { + get { return hashAlgorithm; } + } + + public Asn1OctetString IssuerNameHash + { + get { return issuerNameHash; } + } + + public Asn1OctetString IssuerKeyHash + { + get { return issuerKeyHash; } + } + + public DerInteger SerialNumber + { + get { 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 override Asn1Object ToAsn1Object() + { + return new DerSequence(hashAlgorithm, issuerNameHash, issuerKeyHash, serialNumber); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/ocsp/CertStatus.cs b/bc-sharp-crypto/src/asn1/ocsp/CertStatus.cs new file mode 100644 index 0000000..7dd99b8 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/ocsp/CertStatus.cs @@ -0,0 +1,96 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Ocsp +{ + public class CertStatus + : Asn1Encodable, IAsn1Choice + { + private readonly int tagNo; + private readonly Asn1Encodable value; + + /** + * create a CertStatus object with a tag of zero. + */ + public CertStatus() + { + tagNo = 0; + value = DerNull.Instance; + } + + public CertStatus( + RevokedInfo info) + { + tagNo = 1; + value = info; + } + + public CertStatus( + int tagNo, + Asn1Encodable value) + { + this.tagNo = tagNo; + this.value = value; + } + + public CertStatus( + Asn1TaggedObject choice) + { + this.tagNo = choice.TagNo; + + switch (choice.TagNo) + { + case 1: + value = RevokedInfo.GetInstance(choice, false); + break; + case 0: + case 2: + value = DerNull.Instance; + break; + default: + throw new ArgumentException("Unknown tag encountered: " + choice.TagNo); + } + } + + public static CertStatus GetInstance( + object obj) + { + if (obj == null || obj is CertStatus) + { + return (CertStatus)obj; + } + + if (obj is Asn1TaggedObject) + { + return new CertStatus((Asn1TaggedObject)obj); + } + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + public int TagNo + { + get { return tagNo; } + } + + public Asn1Encodable Status + { + get { return value; } + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *
+         *  CertStatus ::= CHOICE {
+         *                  good        [0]     IMPLICIT Null,
+         *                  revoked     [1]     IMPLICIT RevokedInfo,
+         *                  unknown     [2]     IMPLICIT UnknownInfo }
+         * 
+ */ + public override Asn1Object ToAsn1Object() + { + return new DerTaggedObject(false, tagNo, value); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/ocsp/CrlID.cs b/bc-sharp-crypto/src/asn1/ocsp/CrlID.cs new file mode 100644 index 0000000..cfb3d6f --- /dev/null +++ b/bc-sharp-crypto/src/asn1/ocsp/CrlID.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections; + +namespace Org.BouncyCastle.Asn1.Ocsp +{ + public class CrlID + : Asn1Encodable + { + private readonly DerIA5String crlUrl; + private readonly DerInteger crlNum; + private readonly DerGeneralizedTime crlTime; + + // TODO Add GetInstance method(s) and amke this private? + public CrlID( + Asn1Sequence seq) + { + foreach (Asn1TaggedObject o in seq) + { + switch (o.TagNo) + { + 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 ArgumentException("unknown tag number: " + o.TagNo); + } + } + } + + public DerIA5String CrlUrl + { + get { return crlUrl; } + } + + public DerInteger CrlNum + { + get { return crlNum; } + } + + public DerGeneralizedTime CrlTime + { + get { 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 override Asn1Object 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/bc-sharp-crypto/src/asn1/ocsp/OCSPObjectIdentifiers.cs b/bc-sharp-crypto/src/asn1/ocsp/OCSPObjectIdentifiers.cs new file mode 100644 index 0000000..a37c855 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/ocsp/OCSPObjectIdentifiers.cs @@ -0,0 +1,23 @@ +using Org.BouncyCastle.Asn1; + +namespace Org.BouncyCastle.Asn1.Ocsp +{ + public abstract class OcspObjectIdentifiers + { + internal const string PkixOcspId = "1.3.6.1.5.5.7.48.1"; + + public static readonly DerObjectIdentifier PkixOcsp = new DerObjectIdentifier(PkixOcspId); + public static readonly DerObjectIdentifier PkixOcspBasic = new DerObjectIdentifier(PkixOcspId + ".1"); + + // + // extensions + // + public static readonly DerObjectIdentifier PkixOcspNonce = new DerObjectIdentifier(PkixOcsp + ".2"); + public static readonly DerObjectIdentifier PkixOcspCrl = new DerObjectIdentifier(PkixOcsp + ".3"); + + public static readonly DerObjectIdentifier PkixOcspResponse = new DerObjectIdentifier(PkixOcsp + ".4"); + public static readonly DerObjectIdentifier PkixOcspNocheck = new DerObjectIdentifier(PkixOcsp + ".5"); + public static readonly DerObjectIdentifier PkixOcspArchiveCutoff = new DerObjectIdentifier(PkixOcsp + ".6"); + public static readonly DerObjectIdentifier PkixOcspServiceLocator = new DerObjectIdentifier(PkixOcsp + ".7"); + } +} diff --git a/bc-sharp-crypto/src/asn1/ocsp/OCSPRequest.cs b/bc-sharp-crypto/src/asn1/ocsp/OCSPRequest.cs new file mode 100644 index 0000000..2407678 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/ocsp/OCSPRequest.cs @@ -0,0 +1,89 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Ocsp +{ + public class OcspRequest + : Asn1Encodable + { + private readonly TbsRequest tbsRequest; + private readonly Signature optionalSignature; + + public static OcspRequest GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); + } + + public static OcspRequest GetInstance( + object obj) + { + if (obj == null || obj is OcspRequest) + { + return (OcspRequest)obj; + } + + if (obj is Asn1Sequence) + { + return new OcspRequest((Asn1Sequence)obj); + } + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + public OcspRequest( + TbsRequest tbsRequest, + Signature optionalSignature) + { + if (tbsRequest == null) + throw new ArgumentNullException("tbsRequest"); + + this.tbsRequest = tbsRequest; + this.optionalSignature = optionalSignature; + } + + private OcspRequest( + Asn1Sequence seq) + { + tbsRequest = TbsRequest.GetInstance(seq[0]); + + if (seq.Count == 2) + { + optionalSignature = Signature.GetInstance( + (Asn1TaggedObject)seq[1], true); + } + } + + public TbsRequest TbsRequest + { + get { return tbsRequest; } + } + + public Signature OptionalSignature + { + get { return optionalSignature; } + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *
+         * OcspRequest     ::=     Sequence {
+         *     tbsRequest                  TBSRequest,
+         *     optionalSignature   [0]     EXPLICIT Signature OPTIONAL }
+         * 
+ */ + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(tbsRequest); + + if (optionalSignature != null) + { + v.Add(new DerTaggedObject(true, 0, optionalSignature)); + } + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/ocsp/OCSPResponse.cs b/bc-sharp-crypto/src/asn1/ocsp/OCSPResponse.cs new file mode 100644 index 0000000..9477b61 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/ocsp/OCSPResponse.cs @@ -0,0 +1,90 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Ocsp +{ + public class OcspResponse + : Asn1Encodable + { + private readonly OcspResponseStatus responseStatus; + private readonly ResponseBytes responseBytes; + + public static OcspResponse GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); + } + + public static OcspResponse GetInstance( + object obj) + { + if (obj == null || obj is OcspResponse) + { + return (OcspResponse)obj; + } + + if (obj is Asn1Sequence) + { + return new OcspResponse((Asn1Sequence)obj); + } + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + public OcspResponse( + OcspResponseStatus responseStatus, + ResponseBytes responseBytes) + { + if (responseStatus == null) + throw new ArgumentNullException("responseStatus"); + + this.responseStatus = responseStatus; + this.responseBytes = responseBytes; + } + + private OcspResponse( + Asn1Sequence seq) + { + responseStatus = new OcspResponseStatus( + DerEnumerated.GetInstance(seq[0])); + + if (seq.Count == 2) + { + responseBytes = ResponseBytes.GetInstance( + (Asn1TaggedObject)seq[1], true); + } + } + + public OcspResponseStatus ResponseStatus + { + get { return responseStatus; } + } + + public ResponseBytes ResponseBytes + { + get { return responseBytes; } + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *
+         * OcspResponse ::= Sequence {
+         *     responseStatus         OcspResponseStatus,
+         *     responseBytes          [0] EXPLICIT ResponseBytes OPTIONAL }
+         * 
+ */ + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(responseStatus); + + if (responseBytes != null) + { + v.Add(new DerTaggedObject(true, 0, responseBytes)); + } + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/ocsp/OCSPResponseStatus.cs b/bc-sharp-crypto/src/asn1/ocsp/OCSPResponseStatus.cs new file mode 100644 index 0000000..653317e --- /dev/null +++ b/bc-sharp-crypto/src/asn1/ocsp/OCSPResponseStatus.cs @@ -0,0 +1,41 @@ +using System; + +using Org.BouncyCastle.Asn1; + +namespace Org.BouncyCastle.Asn1.Ocsp +{ + public class OcspResponseStatus + : DerEnumerated + { + public const int Successful = 0; + public const int MalformedRequest = 1; + public const int InternalError = 2; + public const int TryLater = 3; + public const int SignatureRequired = 5; + public const 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) + : base(value) + { + } + + public OcspResponseStatus(DerEnumerated value) + : base(value.Value.IntValue) + { + } + } +} diff --git a/bc-sharp-crypto/src/asn1/ocsp/Request.cs b/bc-sharp-crypto/src/asn1/ocsp/Request.cs new file mode 100644 index 0000000..26e81ba --- /dev/null +++ b/bc-sharp-crypto/src/asn1/ocsp/Request.cs @@ -0,0 +1,91 @@ +using System; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Ocsp +{ + public class Request + : Asn1Encodable + { + private readonly CertID reqCert; + private readonly X509Extensions singleRequestExtensions; + + public static Request GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); + } + + public static Request GetInstance( + object obj) + { + if (obj == null || obj is Request) + { + return (Request)obj; + } + + if (obj is Asn1Sequence) + { + return new Request((Asn1Sequence)obj); + } + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + public Request( + CertID reqCert, + X509Extensions singleRequestExtensions) + { + if (reqCert == null) + throw new ArgumentNullException("reqCert"); + + this.reqCert = reqCert; + this.singleRequestExtensions = singleRequestExtensions; + } + + private Request( + Asn1Sequence seq) + { + reqCert = CertID.GetInstance(seq[0]); + + if (seq.Count == 2) + { + singleRequestExtensions = X509Extensions.GetInstance( + (Asn1TaggedObject)seq[1], true); + } + } + + public CertID ReqCert + { + get { return reqCert; } + } + + public X509Extensions SingleRequestExtensions + { + get { return singleRequestExtensions; } + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *
+         * Request         ::=     Sequence {
+         *     reqCert                     CertID,
+         *     singleRequestExtensions     [0] EXPLICIT Extensions OPTIONAL }
+         * 
+ */ + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(reqCert); + + if (singleRequestExtensions != null) + { + v.Add(new DerTaggedObject(true, 0, singleRequestExtensions)); + } + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/ocsp/ResponderID.cs b/bc-sharp-crypto/src/asn1/ocsp/ResponderID.cs new file mode 100644 index 0000000..143b173 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/ocsp/ResponderID.cs @@ -0,0 +1,107 @@ +using System; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; + +namespace Org.BouncyCastle.Asn1.Ocsp +{ + public class ResponderID + : Asn1Encodable, IAsn1Choice + { + private readonly Asn1Encodable id; + + public static ResponderID GetInstance( + object obj) + { + if (obj == null || obj is ResponderID) + { + return (ResponderID)obj; + } + + if (obj is DerOctetString) + { + return new ResponderID((DerOctetString)obj); + } + + if (obj is Asn1TaggedObject) + { + Asn1TaggedObject o = (Asn1TaggedObject)obj; + + if (o.TagNo == 1) + { + return new ResponderID(X509Name.GetInstance(o, true)); + } + + return new ResponderID(Asn1OctetString.GetInstance(o, true)); + } + + return new ResponderID(X509Name.GetInstance(obj)); + } + + public ResponderID( + Asn1OctetString id) + { + if (id == null) + throw new ArgumentNullException("id"); + + this.id = id; + } + + public ResponderID( + X509Name id) + { + if (id == null) + throw new ArgumentNullException("id"); + + this.id = id; + } + + public static ResponderID GetInstance( + Asn1TaggedObject obj, + bool isExplicit) + { + return GetInstance(obj.GetObject()); // must be explicitly tagged + } + + public virtual byte[] GetKeyHash() + { + if (id is Asn1OctetString) + { + return ((Asn1OctetString)id).GetOctets(); + } + + return null; + } + + public virtual X509Name Name + { + get + { + if (id is Asn1OctetString) + { + return null; + } + + return X509Name.GetInstance(id); + } + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *
+         * ResponderID ::= CHOICE {
+         *      byName          [1] Name,
+         *      byKey           [2] KeyHash }
+         * 
+ */ + public override Asn1Object ToAsn1Object() + { + if (id is Asn1OctetString) + { + return new DerTaggedObject(true, 2, id); + } + + return new DerTaggedObject(true, 1, id); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/ocsp/ResponseBytes.cs b/bc-sharp-crypto/src/asn1/ocsp/ResponseBytes.cs new file mode 100644 index 0000000..d3ea044 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/ocsp/ResponseBytes.cs @@ -0,0 +1,82 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Ocsp +{ + public class ResponseBytes + : Asn1Encodable + { + private readonly DerObjectIdentifier responseType; + private readonly Asn1OctetString response; + + public static ResponseBytes GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); + } + + public static ResponseBytes GetInstance( + object obj) + { + if (obj == null || obj is ResponseBytes) + { + return (ResponseBytes)obj; + } + + if (obj is Asn1Sequence) + { + return new ResponseBytes((Asn1Sequence)obj); + } + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + public ResponseBytes( + DerObjectIdentifier responseType, + Asn1OctetString response) + { + if (responseType == null) + throw new ArgumentNullException("responseType"); + if (response == null) + throw new ArgumentNullException("response"); + + this.responseType = responseType; + this.response = response; + } + + private ResponseBytes( + Asn1Sequence seq) + { + if (seq.Count != 2) + throw new ArgumentException("Wrong number of elements in sequence", "seq"); + + this.responseType = DerObjectIdentifier.GetInstance(seq[0]); + this.response = Asn1OctetString.GetInstance(seq[1]); + } + + public DerObjectIdentifier ResponseType + { + get { return responseType; } + } + + public Asn1OctetString Response + { + get { return response; } + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *
+         * ResponseBytes ::=       Sequence {
+         *     responseType   OBJECT IDENTIFIER,
+         *     response       OCTET STRING }
+         * 
+ */ + public override Asn1Object ToAsn1Object() + { + return new DerSequence(responseType, response); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/ocsp/ResponseData.cs b/bc-sharp-crypto/src/asn1/ocsp/ResponseData.cs new file mode 100644 index 0000000..70620cb --- /dev/null +++ b/bc-sharp-crypto/src/asn1/ocsp/ResponseData.cs @@ -0,0 +1,158 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Ocsp +{ + public class ResponseData + : Asn1Encodable + { + private static readonly DerInteger V1 = new DerInteger(0); + + private readonly bool versionPresent; + private readonly DerInteger version; + private readonly ResponderID responderID; + private readonly DerGeneralizedTime producedAt; + private readonly Asn1Sequence responses; + private readonly X509Extensions responseExtensions; + + public static ResponseData GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); + } + + public static ResponseData GetInstance( + object obj) + { + if (obj == null || obj is ResponseData) + { + return (ResponseData)obj; + } + + if (obj is Asn1Sequence) + { + return new ResponseData((Asn1Sequence)obj); + } + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + 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) + { + } + + private ResponseData( + Asn1Sequence seq) + { + int index = 0; + + Asn1Encodable enc = seq[0]; + if (enc is Asn1TaggedObject) + { + Asn1TaggedObject o = (Asn1TaggedObject)enc; + + if (o.TagNo == 0) + { + this.versionPresent = true; + this.version = DerInteger.GetInstance(o, true); + index++; + } + else + { + this.version = V1; + } + } + else + { + this.version = V1; + } + + this.responderID = ResponderID.GetInstance(seq[index++]); + this.producedAt = (DerGeneralizedTime)seq[index++]; + this.responses = (Asn1Sequence)seq[index++]; + + if (seq.Count > index) + { + this.responseExtensions = X509Extensions.GetInstance( + (Asn1TaggedObject)seq[index], true); + } + } + + public DerInteger Version + { + get { return version; } + } + + public ResponderID ResponderID + { + get { return responderID; } + } + + public DerGeneralizedTime ProducedAt + { + get { return producedAt; } + } + + public Asn1Sequence Responses + { + get { return responses; } + } + + public X509Extensions ResponseExtensions + { + get { 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 override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(); + + if (versionPresent || !version.Equals(V1)) + { + v.Add(new DerTaggedObject(true, 0, version)); + } + + v.Add(responderID, producedAt, responses); + + if (responseExtensions != null) + { + v.Add(new DerTaggedObject(true, 1, responseExtensions)); + } + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/ocsp/RevokedInfo.cs b/bc-sharp-crypto/src/asn1/ocsp/RevokedInfo.cs new file mode 100644 index 0000000..ee9e554 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/ocsp/RevokedInfo.cs @@ -0,0 +1,96 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Ocsp +{ + public class RevokedInfo + : Asn1Encodable + { + private readonly DerGeneralizedTime revocationTime; + private readonly CrlReason revocationReason; + + public static RevokedInfo GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); + } + + public static RevokedInfo GetInstance( + object obj) + { + if (obj == null || obj is RevokedInfo) + { + return (RevokedInfo) obj; + } + + if (obj is Asn1Sequence) + { + return new RevokedInfo((Asn1Sequence) obj); + } + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + public RevokedInfo( + DerGeneralizedTime revocationTime) + : this(revocationTime, null) + { + } + + public RevokedInfo( + DerGeneralizedTime revocationTime, + CrlReason revocationReason) + { + if (revocationTime == null) + throw new ArgumentNullException("revocationTime"); + + this.revocationTime = revocationTime; + this.revocationReason = revocationReason; + } + + private RevokedInfo( + Asn1Sequence seq) + { + this.revocationTime = (DerGeneralizedTime) seq[0]; + + if (seq.Count > 1) + { + this.revocationReason = new CrlReason( + DerEnumerated.GetInstance((Asn1TaggedObject) seq[1], true)); + } + } + + public DerGeneralizedTime RevocationTime + { + get { return revocationTime; } + } + + public CrlReason RevocationReason + { + get { return revocationReason; } + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *
+         * RevokedInfo ::= Sequence {
+         *      revocationTime              GeneralizedTime,
+         *      revocationReason    [0]     EXPLICIT CRLReason OPTIONAL }
+         * 
+ */ + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(revocationTime); + + if (revocationReason != null) + { + v.Add(new DerTaggedObject(true, 0, revocationReason)); + } + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/ocsp/ServiceLocator.cs b/bc-sharp-crypto/src/asn1/ocsp/ServiceLocator.cs new file mode 100644 index 0000000..4ba252b --- /dev/null +++ b/bc-sharp-crypto/src/asn1/ocsp/ServiceLocator.cs @@ -0,0 +1,95 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Ocsp +{ + public class ServiceLocator + : Asn1Encodable + { + private readonly X509Name issuer; + private readonly Asn1Object locator; + + public static ServiceLocator GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); + } + + public static ServiceLocator GetInstance( + object obj) + { + if (obj == null || obj is ServiceLocator) + { + return (ServiceLocator) obj; + } + + if (obj is Asn1Sequence) + { + return new ServiceLocator((Asn1Sequence) obj); + } + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + public ServiceLocator( + X509Name issuer) + : this(issuer, null) + { + } + + public ServiceLocator( + X509Name issuer, + Asn1Object locator) + { + if (issuer == null) + throw new ArgumentNullException("issuer"); + + this.issuer = issuer; + this.locator = locator; + } + + private ServiceLocator( + Asn1Sequence seq) + { + this.issuer = X509Name.GetInstance(seq[0]); + + if (seq.Count > 1) + { + this.locator = seq[1].ToAsn1Object(); + } + } + + public X509Name Issuer + { + get { return issuer; } + } + + public Asn1Object Locator + { + get { return locator; } + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *
+         * ServiceLocator ::= Sequence {
+         *     issuer    Name,
+         *     locator   AuthorityInfoAccessSyntax OPTIONAL }
+         * 
+ */ + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(issuer); + + if (locator != null) + { + v.Add(locator); + } + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/ocsp/Signature.cs b/bc-sharp-crypto/src/asn1/ocsp/Signature.cs new file mode 100644 index 0000000..d6b4ccf --- /dev/null +++ b/bc-sharp-crypto/src/asn1/ocsp/Signature.cs @@ -0,0 +1,115 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Ocsp +{ + public class Signature + : Asn1Encodable + { + internal AlgorithmIdentifier signatureAlgorithm; + internal DerBitString signatureValue; + internal Asn1Sequence certs; + + public static Signature GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); + } + + public static Signature GetInstance( + object obj) + { + if (obj == null || obj is Signature) + { + return (Signature)obj; + } + + if (obj is Asn1Sequence) + { + return new Signature((Asn1Sequence)obj); + } + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + public Signature( + AlgorithmIdentifier signatureAlgorithm, + DerBitString signatureValue) + : this(signatureAlgorithm, signatureValue, null) + { + } + + public Signature( + AlgorithmIdentifier signatureAlgorithm, + DerBitString signatureValue, + Asn1Sequence certs) + { + if (signatureAlgorithm == null) + throw new ArgumentException("signatureAlgorithm"); + if (signatureValue == null) + throw new ArgumentException("signatureValue"); + + this.signatureAlgorithm = signatureAlgorithm; + this.signatureValue = signatureValue; + this.certs = certs; + } + + private Signature( + Asn1Sequence seq) + { + signatureAlgorithm = AlgorithmIdentifier.GetInstance(seq[0]); + signatureValue = (DerBitString)seq[1]; + + if (seq.Count == 3) + { + certs = Asn1Sequence.GetInstance( + (Asn1TaggedObject)seq[2], true); + } + } + + public AlgorithmIdentifier SignatureAlgorithm + { + get { return signatureAlgorithm; } + } + + public DerBitString SignatureValue + { + get { return signatureValue; } + } + + public byte[] GetSignatureOctets() + { + return signatureValue.GetOctets(); + } + + public Asn1Sequence Certs + { + get { return certs; } + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *
+         * Signature       ::=     Sequence {
+         *     signatureAlgorithm      AlgorithmIdentifier,
+         *     signature               BIT STRING,
+         *     certs               [0] EXPLICIT Sequence OF Certificate OPTIONAL}
+         * 
+ */ + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector( + signatureAlgorithm, signatureValue); + + if (certs != null) + { + v.Add(new DerTaggedObject(true, 0, certs)); + } + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/ocsp/SingleResponse.cs b/bc-sharp-crypto/src/asn1/ocsp/SingleResponse.cs new file mode 100644 index 0000000..544232a --- /dev/null +++ b/bc-sharp-crypto/src/asn1/ocsp/SingleResponse.cs @@ -0,0 +1,137 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Ocsp +{ + public class SingleResponse + : Asn1Encodable + { + private readonly CertID certID; + private readonly CertStatus certStatus; + private readonly DerGeneralizedTime thisUpdate; + private readonly DerGeneralizedTime nextUpdate; + private readonly 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[0]); + this.certStatus = CertStatus.GetInstance(seq[1]); + this.thisUpdate = (DerGeneralizedTime)seq[2]; + + if (seq.Count > 4) + { + this.nextUpdate = DerGeneralizedTime.GetInstance( + (Asn1TaggedObject) seq[3], true); + this.singleExtensions = X509Extensions.GetInstance( + (Asn1TaggedObject) seq[4], true); + } + else if (seq.Count > 3) + { + Asn1TaggedObject o = (Asn1TaggedObject) seq[3]; + + if (o.TagNo == 0) + { + this.nextUpdate = DerGeneralizedTime.GetInstance(o, true); + } + else + { + this.singleExtensions = X509Extensions.GetInstance(o, true); + } + } + } + + public static SingleResponse GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); + } + + public static SingleResponse GetInstance( + object obj) + { + if (obj == null || obj is SingleResponse) + { + return (SingleResponse)obj; + } + + if (obj is Asn1Sequence) + { + return new SingleResponse((Asn1Sequence)obj); + } + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + public CertID CertId + { + get { return certID; } + } + + public CertStatus CertStatus + { + get { return certStatus; } + } + + public DerGeneralizedTime ThisUpdate + { + get { return thisUpdate; } + } + + public DerGeneralizedTime NextUpdate + { + get { return nextUpdate; } + } + + public X509Extensions SingleExtensions + { + get { 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 override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector( + certID, certStatus, 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/bc-sharp-crypto/src/asn1/ocsp/TBSRequest.cs b/bc-sharp-crypto/src/asn1/ocsp/TBSRequest.cs new file mode 100644 index 0000000..1ad8649 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/ocsp/TBSRequest.cs @@ -0,0 +1,151 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Ocsp +{ + public class TbsRequest + : Asn1Encodable + { + private static readonly DerInteger V1 = new DerInteger(0); + + private readonly DerInteger version; + private readonly GeneralName requestorName; + private readonly Asn1Sequence requestList; + private readonly X509Extensions requestExtensions; + + private bool versionSet; + + public static TbsRequest GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); + } + + public static TbsRequest GetInstance( + object obj) + { + if (obj == null || obj is TbsRequest) + { + return (TbsRequest)obj; + } + + if (obj is Asn1Sequence) + { + return new TbsRequest((Asn1Sequence)obj); + } + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + public TbsRequest( + GeneralName requestorName, + Asn1Sequence requestList, + X509Extensions requestExtensions) + { + this.version = V1; + this.requestorName = requestorName; + this.requestList = requestList; + this.requestExtensions = requestExtensions; + } + + private TbsRequest( + Asn1Sequence seq) + { + int index = 0; + + Asn1Encodable enc = seq[0]; + if (enc is Asn1TaggedObject) + { + Asn1TaggedObject o = (Asn1TaggedObject) enc; + + if (o.TagNo == 0) + { + versionSet = true; + version = DerInteger.GetInstance(o, true); + index++; + } + else + { + version = V1; + } + } + else + { + version = V1; + } + + if (seq[index] is Asn1TaggedObject) + { + requestorName = GeneralName.GetInstance((Asn1TaggedObject) seq[index++], true); + } + + requestList = (Asn1Sequence) seq[index++]; + + if (seq.Count == (index + 1)) + { + requestExtensions = X509Extensions.GetInstance((Asn1TaggedObject) seq[index], true); + } + } + + public DerInteger Version + { + get { return version; } + } + + public GeneralName RequestorName + { + get { return requestorName; } + } + + public Asn1Sequence RequestList + { + get { return requestList; } + } + + public X509Extensions RequestExtensions + { + get { 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 override Asn1Object 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/bc-sharp-crypto/src/asn1/oiw/ElGamalParameter.cs b/bc-sharp-crypto/src/asn1/oiw/ElGamalParameter.cs new file mode 100644 index 0000000..3e020f0 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/oiw/ElGamalParameter.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Asn1.Oiw +{ + public class ElGamalParameter + : Asn1Encodable + { + internal DerInteger p, g; + + public ElGamalParameter( + BigInteger p, + BigInteger g) + { + this.p = new DerInteger(p); + this.g = new DerInteger(g); + } + + public ElGamalParameter( + Asn1Sequence seq) + { + if (seq.Count != 2) + throw new ArgumentException("Wrong number of elements in sequence", "seq"); + + p = DerInteger.GetInstance(seq[0]); + g = DerInteger.GetInstance(seq[1]); + } + + public BigInteger P + { + get { return p.PositiveValue; } + } + + public BigInteger G + { + get { return g.PositiveValue; } + } + + public override Asn1Object ToAsn1Object() + { + return new DerSequence(p, g); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/oiw/OIWObjectIdentifiers.cs b/bc-sharp-crypto/src/asn1/oiw/OIWObjectIdentifiers.cs new file mode 100644 index 0000000..3da2263 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/oiw/OIWObjectIdentifiers.cs @@ -0,0 +1,29 @@ +namespace Org.BouncyCastle.Asn1.Oiw +{ + public abstract class OiwObjectIdentifiers + { + public static readonly DerObjectIdentifier MD4WithRsa = new DerObjectIdentifier("1.3.14.3.2.2"); + public static readonly DerObjectIdentifier MD5WithRsa = new DerObjectIdentifier("1.3.14.3.2.3"); + public static readonly DerObjectIdentifier MD4WithRsaEncryption = new DerObjectIdentifier("1.3.14.3.2.4"); + + public static readonly DerObjectIdentifier DesEcb = new DerObjectIdentifier("1.3.14.3.2.6"); + public static readonly DerObjectIdentifier DesCbc = new DerObjectIdentifier("1.3.14.3.2.7"); + public static readonly DerObjectIdentifier DesOfb = new DerObjectIdentifier("1.3.14.3.2.8"); + public static readonly DerObjectIdentifier DesCfb = new DerObjectIdentifier("1.3.14.3.2.9"); + + public static readonly DerObjectIdentifier DesEde = new DerObjectIdentifier("1.3.14.3.2.17"); + + // id-SHA1 OBJECT IDENTIFIER ::= + // {iso(1) identified-organization(3) oiw(14) secsig(3) algorithms(2) 26 } // + public static readonly DerObjectIdentifier IdSha1 = new DerObjectIdentifier("1.3.14.3.2.26"); + + public static readonly DerObjectIdentifier DsaWithSha1 = new DerObjectIdentifier("1.3.14.3.2.27"); + + public static readonly 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 } + // + public static readonly DerObjectIdentifier ElGamalAlgorithm = new DerObjectIdentifier("1.3.14.7.2.1.1"); + } +} diff --git a/bc-sharp-crypto/src/asn1/pkcs/Attribute.cs b/bc-sharp-crypto/src/asn1/pkcs/Attribute.cs new file mode 100644 index 0000000..1858285 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/pkcs/Attribute.cs @@ -0,0 +1,79 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Pkcs +{ + public class AttributePkcs + : Asn1Encodable + { + private readonly DerObjectIdentifier attrType; + private readonly Asn1Set attrValues; + + /** + * return an Attribute object from the given object. + * + * @param o the object we want converted. + * @exception ArgumentException if the object cannot be converted. + */ + public static AttributePkcs GetInstance( + object obj) + { + AttributePkcs attr = obj as AttributePkcs; + if (obj == null || attr != null) + { + return attr; + } + + Asn1Sequence seq = obj as Asn1Sequence; + if (seq != null) + { + return new AttributePkcs(seq); + } + + throw new ArgumentException("Unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + private AttributePkcs( + Asn1Sequence seq) + { + if (seq.Count != 2) + throw new ArgumentException("Wrong number of elements in sequence", "seq"); + + attrType = DerObjectIdentifier.GetInstance(seq[0]); + attrValues = Asn1Set.GetInstance(seq[1]); + } + + public AttributePkcs( + DerObjectIdentifier attrType, + Asn1Set attrValues) + { + this.attrType = attrType; + this.attrValues = attrValues; + } + + public DerObjectIdentifier AttrType + { + get { return attrType; } + } + + public Asn1Set AttrValues + { + get { return attrValues; } + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *
+         * Attr ::= Sequence {
+         *     attrType OBJECT IDENTIFIER,
+         *     attrValues Set OF AttributeValue
+         * }
+         * 
+ */ + public override Asn1Object ToAsn1Object() + { + return new DerSequence(attrType, attrValues); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/pkcs/AuthenticatedSafe.cs b/bc-sharp-crypto/src/asn1/pkcs/AuthenticatedSafe.cs new file mode 100644 index 0000000..f3dabb8 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/pkcs/AuthenticatedSafe.cs @@ -0,0 +1,37 @@ +using Org.BouncyCastle.Asn1; + +namespace Org.BouncyCastle.Asn1.Pkcs +{ + public class AuthenticatedSafe + : Asn1Encodable + { + private readonly ContentInfo[] info; + + public AuthenticatedSafe( + Asn1Sequence seq) + { + info = new ContentInfo[seq.Count]; + + for (int i = 0; i != info.Length; i++) + { + info[i] = ContentInfo.GetInstance(seq[i]); + } + } + + public AuthenticatedSafe( + ContentInfo[] info) + { + this.info = (ContentInfo[]) info.Clone(); + } + + public ContentInfo[] GetContentInfo() + { + return (ContentInfo[]) info.Clone(); + } + + public override Asn1Object ToAsn1Object() + { + return new BerSequence(info); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/pkcs/CertBag.cs b/bc-sharp-crypto/src/asn1/pkcs/CertBag.cs new file mode 100644 index 0000000..b6f4c8a --- /dev/null +++ b/bc-sharp-crypto/src/asn1/pkcs/CertBag.cs @@ -0,0 +1,46 @@ +using System; + +namespace Org.BouncyCastle.Asn1.Pkcs +{ + public class CertBag + : Asn1Encodable + { +// private readonly Asn1Sequence seq; + private readonly DerObjectIdentifier certID; + private readonly Asn1Object certValue; + + public CertBag( + Asn1Sequence seq) + { + if (seq.Count != 2) + throw new ArgumentException("Wrong number of elements in sequence", "seq"); + +// this.seq = seq; + this.certID = DerObjectIdentifier.GetInstance(seq[0]); + this.certValue = DerTaggedObject.GetInstance(seq[1]).GetObject(); + } + + public CertBag( + DerObjectIdentifier certID, + Asn1Object certValue) + { + this.certID = certID; + this.certValue = certValue; + } + + public DerObjectIdentifier CertID + { + get { return certID; } + } + + public Asn1Object CertValue + { + get { return certValue; } + } + + public override Asn1Object ToAsn1Object() + { + return new DerSequence(certID, new DerTaggedObject(0, certValue)); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/pkcs/CertificationRequest.cs b/bc-sharp-crypto/src/asn1/pkcs/CertificationRequest.cs new file mode 100644 index 0000000..98caa22 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/pkcs/CertificationRequest.cs @@ -0,0 +1,87 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; + +namespace Org.BouncyCastle.Asn1.Pkcs +{ + /** + * Pkcs10 Certfication request object. + *
+     * CertificationRequest ::= Sequence {
+     *   certificationRequestInfo  CertificationRequestInfo,
+     *   signatureAlgorithm        AlgorithmIdentifier{{ SignatureAlgorithms }},
+     *   signature                 BIT STRING
+     * }
+     * 
+ */ + public class CertificationRequest + : Asn1Encodable + { + protected CertificationRequestInfo reqInfo; + protected AlgorithmIdentifier sigAlgId; + protected DerBitString sigBits; + + public static CertificationRequest GetInstance( + object obj) + { + if (obj is CertificationRequest) + return (CertificationRequest)obj; + + if (obj != null) + return new CertificationRequest((Asn1Sequence)obj); + + return null; + } + + protected CertificationRequest() + { + } + + public CertificationRequest( + CertificationRequestInfo requestInfo, + AlgorithmIdentifier algorithm, + DerBitString signature) + { + this.reqInfo = requestInfo; + this.sigAlgId = algorithm; + this.sigBits = signature; + } + + [Obsolete("Use 'GetInstance' instead")] + public CertificationRequest( + Asn1Sequence seq) + { + if (seq.Count != 3) + throw new ArgumentException("Wrong number of elements in sequence", "seq"); + + reqInfo = CertificationRequestInfo.GetInstance(seq[0]); + sigAlgId = AlgorithmIdentifier.GetInstance(seq[1]); + sigBits = DerBitString.GetInstance(seq[2]); + } + + public CertificationRequestInfo GetCertificationRequestInfo() + { + return reqInfo; + } + + public AlgorithmIdentifier SignatureAlgorithm + { + get { return sigAlgId; } + } + + public DerBitString Signature + { + get { return sigBits; } + } + + public byte[] GetSignatureOctets() + { + return sigBits.GetOctets(); + } + + public override Asn1Object ToAsn1Object() + { + return new DerSequence(reqInfo, sigAlgId, sigBits); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/pkcs/CertificationRequestInfo.cs b/bc-sharp-crypto/src/asn1/pkcs/CertificationRequestInfo.cs new file mode 100644 index 0000000..6d98013 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/pkcs/CertificationRequestInfo.cs @@ -0,0 +1,137 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; + +namespace Org.BouncyCastle.Asn1.Pkcs +{ + /** + * Pkcs10 CertificationRequestInfo object. + *
+     *  CertificationRequestInfo ::= Sequence {
+     *   version             Integer { v1(0) } (v1,...),
+     *   subject             Name,
+     *   subjectPKInfo   SubjectPublicKeyInfo{{ PKInfoAlgorithms }},
+     *   attributes          [0] Attributes{{ CRIAttributes }}
+     *  }
+     *
+     *  Attributes { ATTRIBUTE:IOSet } ::= Set OF Attr{{ IOSet }}
+     *
+     *  Attr { ATTRIBUTE:IOSet } ::= Sequence {
+     *    type    ATTRIBUTE.&id({IOSet}),
+     *    values  Set SIZE(1..MAX) OF ATTRIBUTE.&Type({IOSet}{\@type})
+     *  }
+     * 
+ */ + public class CertificationRequestInfo + : Asn1Encodable + { + internal DerInteger version = new DerInteger(0); + internal X509Name subject; + internal SubjectPublicKeyInfo subjectPKInfo; + internal Asn1Set attributes; + + public static CertificationRequestInfo GetInstance(object obj) + { + if (obj is CertificationRequestInfo) + return (CertificationRequestInfo)obj; + if (obj != null) + return new CertificationRequestInfo(Asn1Sequence.GetInstance(obj)); + return null; + } + + public CertificationRequestInfo( + X509Name subject, + SubjectPublicKeyInfo pkInfo, + Asn1Set attributes) + { + this.subject = subject; + this.subjectPKInfo = pkInfo; + this.attributes = attributes; + + ValidateAttributes(attributes); + + if (subject == null || version == null || subjectPKInfo == null) + { + throw new ArgumentException( + "Not all mandatory fields set in CertificationRequestInfo generator."); + } + } + + private CertificationRequestInfo( + Asn1Sequence seq) + { + version = (DerInteger) seq[0]; + + subject = X509Name.GetInstance(seq[1]); + subjectPKInfo = SubjectPublicKeyInfo.GetInstance(seq[2]); + + // + // some CertificationRequestInfo objects seem to treat this field + // as optional. + // + if (seq.Count > 3) + { + DerTaggedObject tagobj = (DerTaggedObject) seq[3]; + attributes = Asn1Set.GetInstance(tagobj, false); + } + + ValidateAttributes(attributes); + + if (subject == null || version == null || subjectPKInfo == null) + { + throw new ArgumentException( + "Not all mandatory fields set in CertificationRequestInfo generator."); + } + } + + public DerInteger Version + { + get { return version; } + } + + public X509Name Subject + { + get { return subject; } + } + + public SubjectPublicKeyInfo SubjectPublicKeyInfo + { + get { return subjectPKInfo; } + } + + public Asn1Set Attributes + { + get { return attributes; } + } + + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector( + version, subject, subjectPKInfo); + + if (attributes != null) + { + v.Add(new DerTaggedObject(false, 0, attributes)); + } + + return new DerSequence(v); + } + + private static void ValidateAttributes(Asn1Set attributes) + { + if (attributes == null) + return; + + foreach (Asn1Encodable ae in attributes) + { + Asn1Object obj = ae.ToAsn1Object(); + AttributePkcs attr = AttributePkcs.GetInstance(obj); + if (attr.AttrType.Equals(PkcsObjectIdentifiers.Pkcs9AtChallengePassword)) + { + if (attr.AttrValues.Count != 1) + throw new ArgumentException("challengePassword attribute must have one value"); + } + } + } + } +} diff --git a/bc-sharp-crypto/src/asn1/pkcs/ContentInfo.cs b/bc-sharp-crypto/src/asn1/pkcs/ContentInfo.cs new file mode 100644 index 0000000..526a3c4 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/pkcs/ContentInfo.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1; + +namespace Org.BouncyCastle.Asn1.Pkcs +{ + public class ContentInfo + : Asn1Encodable + { + private readonly DerObjectIdentifier contentType; + private readonly Asn1Encodable content; + + public static ContentInfo GetInstance(object obj) + { + if (obj == null) + return null; + ContentInfo existing = obj as ContentInfo; + if (existing != null) + return existing; + return new ContentInfo(Asn1Sequence.GetInstance(obj)); + } + + private ContentInfo( + Asn1Sequence seq) + { + contentType = (DerObjectIdentifier) seq[0]; + + if (seq.Count > 1) + { + content = ((Asn1TaggedObject) seq[1]).GetObject(); + } + } + + public ContentInfo( + DerObjectIdentifier contentType, + Asn1Encodable content) + { + this.contentType = contentType; + this.content = content; + } + + public DerObjectIdentifier ContentType + { + get { return contentType; } + } + + public Asn1Encodable Content + { + get { return content; } + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *
+         * ContentInfo ::= Sequence {
+         *          contentType ContentType,
+         *          content
+         *          [0] EXPLICIT ANY DEFINED BY contentType OPTIONAL }
+         * 
+ */ + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(contentType); + + if (content != null) + { + v.Add(new BerTaggedObject(0, content)); + } + + return new BerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/pkcs/DHParameter.cs b/bc-sharp-crypto/src/asn1/pkcs/DHParameter.cs new file mode 100644 index 0000000..25a091a --- /dev/null +++ b/bc-sharp-crypto/src/asn1/pkcs/DHParameter.cs @@ -0,0 +1,72 @@ +using Org.BouncyCastle.Asn1; +using System; +using System.Collections; + +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Asn1.Pkcs +{ + public class DHParameter + : Asn1Encodable + { + internal 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); + } + } + + public DHParameter( + Asn1Sequence seq) + { + IEnumerator e = seq.GetEnumerator(); + + e.MoveNext(); + p = (DerInteger)e.Current; + + e.MoveNext(); + g = (DerInteger)e.Current; + + if (e.MoveNext()) + { + l = (DerInteger) e.Current; + } + } + + public BigInteger P + { + get { return p.PositiveValue; } + } + + public BigInteger G + { + get { return g.PositiveValue; } + } + + public BigInteger L + { + get { return l == null ? null : l.PositiveValue; } + } + + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(p, g); + + if (this.l != null) + { + v.Add(l); + } + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/pkcs/EncryptedData.cs b/bc-sharp-crypto/src/asn1/pkcs/EncryptedData.cs new file mode 100644 index 0000000..7e95eb5 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/pkcs/EncryptedData.cs @@ -0,0 +1,105 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Pkcs +{ + /** + * 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 + : Asn1Encodable + { + private readonly Asn1Sequence data; +// private readonly DerObjectIdentifier bagId; +// private readonly Asn1Object bagValue; + + public static EncryptedData GetInstance( + object obj) + { + if (obj is EncryptedData) + { + return (EncryptedData) obj; + } + + if (obj is Asn1Sequence) + { + return new EncryptedData((Asn1Sequence) obj); + } + + throw new ArgumentException("Unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + private EncryptedData( + Asn1Sequence seq) + { + if (seq.Count != 2) + throw new ArgumentException("Wrong number of elements in sequence", "seq"); + + int version = ((DerInteger) seq[0]).Value.IntValue; + if (version != 0) + { + throw new ArgumentException("sequence not version 0"); + } + + this.data = (Asn1Sequence) seq[1]; + } + + public EncryptedData( + DerObjectIdentifier contentType, + AlgorithmIdentifier encryptionAlgorithm, + Asn1Encodable content) + { + data = new BerSequence( + contentType, + encryptionAlgorithm.ToAsn1Object(), + new BerTaggedObject(false, 0, content)); + } + + public DerObjectIdentifier ContentType + { + get { return (DerObjectIdentifier) data[0]; } + } + + public AlgorithmIdentifier EncryptionAlgorithm + { + get { return AlgorithmIdentifier.GetInstance(data[1]); } + } + + public Asn1OctetString Content + { + get + { + if (data.Count == 3) + { + DerTaggedObject o = (DerTaggedObject) data[2]; + + return Asn1OctetString.GetInstance(o, false); + } + + return null; + } + } + + public override Asn1Object ToAsn1Object() + { + return new BerSequence(new DerInteger(0), data); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/pkcs/EncryptedPrivateKeyInfo.cs b/bc-sharp-crypto/src/asn1/pkcs/EncryptedPrivateKeyInfo.cs new file mode 100644 index 0000000..9870270 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/pkcs/EncryptedPrivateKeyInfo.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Pkcs +{ + public class EncryptedPrivateKeyInfo + : Asn1Encodable + { + private readonly AlgorithmIdentifier algId; + private readonly Asn1OctetString data; + + private EncryptedPrivateKeyInfo( + Asn1Sequence seq) + { + if (seq.Count != 2) + throw new ArgumentException("Wrong number of elements in sequence", "seq"); + + algId = AlgorithmIdentifier.GetInstance(seq[0]); + data = Asn1OctetString.GetInstance(seq[1]); + } + + public EncryptedPrivateKeyInfo( + AlgorithmIdentifier algId, + byte[] encoding) + { + this.algId = algId; + this.data = new DerOctetString(encoding); + } + + public static EncryptedPrivateKeyInfo GetInstance( + object obj) + { + if (obj is EncryptedPrivateKeyInfo) + { + return (EncryptedPrivateKeyInfo) obj; + } + + if (obj is Asn1Sequence) + { + return new EncryptedPrivateKeyInfo((Asn1Sequence) obj); + } + + throw new ArgumentException("Unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + public AlgorithmIdentifier EncryptionAlgorithm + { + get { 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 override Asn1Object ToAsn1Object() + { + return new DerSequence(algId, data); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/pkcs/EncryptionScheme.cs b/bc-sharp-crypto/src/asn1/pkcs/EncryptionScheme.cs new file mode 100644 index 0000000..7b90ece --- /dev/null +++ b/bc-sharp-crypto/src/asn1/pkcs/EncryptionScheme.cs @@ -0,0 +1,49 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Pkcs +{ + public class EncryptionScheme + : AlgorithmIdentifier + { + public EncryptionScheme( + DerObjectIdentifier objectID, + Asn1Encodable parameters) + : base(objectID, parameters) + { + } + + internal EncryptionScheme( + Asn1Sequence seq) + : this((DerObjectIdentifier)seq[0], seq[1]) + { + } + + public new static EncryptionScheme GetInstance(object obj) + { + if (obj is EncryptionScheme) + { + return (EncryptionScheme)obj; + } + + if (obj is Asn1Sequence) + { + return new EncryptionScheme((Asn1Sequence)obj); + } + + throw new ArgumentException("Unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + public Asn1Object Asn1Object + { + get { return Parameters.ToAsn1Object(); } + } + + public override Asn1Object ToAsn1Object() + { + return new DerSequence(Algorithm, Parameters); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/pkcs/IssuerAndSerialNumber.cs b/bc-sharp-crypto/src/asn1/pkcs/IssuerAndSerialNumber.cs new file mode 100644 index 0000000..da863cb --- /dev/null +++ b/bc-sharp-crypto/src/asn1/pkcs/IssuerAndSerialNumber.cs @@ -0,0 +1,72 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Pkcs +{ + public class IssuerAndSerialNumber + : Asn1Encodable + { + private readonly X509Name name; + private readonly DerInteger certSerialNumber; + + public static IssuerAndSerialNumber GetInstance( + object obj) + { + if (obj is IssuerAndSerialNumber) + { + return (IssuerAndSerialNumber) obj; + } + + if (obj is Asn1Sequence) + { + return new IssuerAndSerialNumber((Asn1Sequence) obj); + } + + throw new ArgumentException("Unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + private IssuerAndSerialNumber( + Asn1Sequence seq) + { + if (seq.Count != 2) + throw new ArgumentException("Wrong number of elements in sequence", "seq"); + + this.name = X509Name.GetInstance(seq[0]); + this.certSerialNumber = DerInteger.GetInstance(seq[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 Name + { + get { return name; } + } + + public DerInteger CertificateSerialNumber + { + get { return certSerialNumber; } + } + + public override Asn1Object ToAsn1Object() + { + return new DerSequence(name, certSerialNumber); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/pkcs/KeyDerivationFunc.cs b/bc-sharp-crypto/src/asn1/pkcs/KeyDerivationFunc.cs new file mode 100644 index 0000000..9fc8985 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/pkcs/KeyDerivationFunc.cs @@ -0,0 +1,21 @@ +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; + +namespace Org.BouncyCastle.Asn1.Pkcs +{ + public class KeyDerivationFunc + : AlgorithmIdentifier + { + internal KeyDerivationFunc(Asn1Sequence seq) + : base(seq) + { + } + + public KeyDerivationFunc( + DerObjectIdentifier id, + Asn1Encodable parameters) + : base(id, parameters) + { + } + } +} \ No newline at end of file diff --git a/bc-sharp-crypto/src/asn1/pkcs/MacData.cs b/bc-sharp-crypto/src/asn1/pkcs/MacData.cs new file mode 100644 index 0000000..c4b7df1 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/pkcs/MacData.cs @@ -0,0 +1,96 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Pkcs +{ + public class MacData + : Asn1Encodable + { + internal DigestInfo digInfo; + internal byte[] salt; + internal BigInteger iterationCount; + + public static MacData GetInstance( + object obj) + { + if (obj is MacData) + { + return (MacData) obj; + } + + if (obj is Asn1Sequence) + { + return new MacData((Asn1Sequence) obj); + } + + throw new ArgumentException("Unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + private MacData( + Asn1Sequence seq) + { + this.digInfo = DigestInfo.GetInstance(seq[0]); + this.salt = ((Asn1OctetString) seq[1]).GetOctets(); + + if (seq.Count == 3) + { + this.iterationCount = ((DerInteger) seq[2]).Value; + } + else + { + this.iterationCount = BigInteger.One; + } + } + + public MacData( + DigestInfo digInfo, + byte[] salt, + int iterationCount) + { + this.digInfo = digInfo; + this.salt = (byte[]) salt.Clone(); + this.iterationCount = BigInteger.ValueOf(iterationCount); + } + + public DigestInfo Mac + { + get { return digInfo; } + } + + public byte[] GetSalt() + { + return (byte[]) salt.Clone(); + } + + public BigInteger IterationCount + { + get { 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 override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(digInfo, new DerOctetString(salt)); + + if (!iterationCount.Equals(BigInteger.One)) + { + v.Add(new DerInteger(iterationCount)); + } + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/pkcs/PBEParameter.cs b/bc-sharp-crypto/src/asn1/pkcs/PBEParameter.cs new file mode 100644 index 0000000..56cea5f --- /dev/null +++ b/bc-sharp-crypto/src/asn1/pkcs/PBEParameter.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Pkcs +{ + public class PbeParameter + : Asn1Encodable + { + private readonly Asn1OctetString salt; + private readonly DerInteger iterationCount; + + public static PbeParameter GetInstance(object obj) + { + if (obj is PbeParameter || obj == null) + { + return (PbeParameter) obj; + } + + if (obj is Asn1Sequence) + { + return new PbeParameter((Asn1Sequence) obj); + } + + throw new ArgumentException("Unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + private PbeParameter(Asn1Sequence seq) + { + if (seq.Count != 2) + throw new ArgumentException("Wrong number of elements in sequence", "seq"); + + salt = Asn1OctetString.GetInstance(seq[0]); + iterationCount = DerInteger.GetInstance(seq[1]); + } + + public PbeParameter(byte[] salt, int iterationCount) + { + this.salt = new DerOctetString(salt); + this.iterationCount = new DerInteger(iterationCount); + } + + public byte[] GetSalt() + { + return salt.GetOctets(); + } + + public BigInteger IterationCount + { + get { return iterationCount.Value; } + } + + public override Asn1Object ToAsn1Object() + { + return new DerSequence(salt, iterationCount); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/pkcs/PBES2Parameters.cs b/bc-sharp-crypto/src/asn1/pkcs/PBES2Parameters.cs new file mode 100644 index 0000000..fc6904e --- /dev/null +++ b/bc-sharp-crypto/src/asn1/pkcs/PBES2Parameters.cs @@ -0,0 +1,65 @@ +using System; + +namespace Org.BouncyCastle.Asn1.Pkcs +{ + public class PbeS2Parameters + : Asn1Encodable + { + private readonly KeyDerivationFunc func; + private readonly EncryptionScheme scheme; + + public static PbeS2Parameters GetInstance(object obj) + { + if (obj == null) + return null; + PbeS2Parameters existing = obj as PbeS2Parameters; + if (existing != null) + return existing; + return new PbeS2Parameters(Asn1Sequence.GetInstance(obj)); + } + + public PbeS2Parameters(KeyDerivationFunc keyDevFunc, EncryptionScheme encScheme) + { + this.func = keyDevFunc; + this.scheme = encScheme; + } + + [Obsolete("Use GetInstance() instead")] + public PbeS2Parameters( + Asn1Sequence seq) + { + if (seq.Count != 2) + throw new ArgumentException("Wrong number of elements in sequence", "seq"); + + Asn1Sequence funcSeq = (Asn1Sequence)seq[0].ToAsn1Object(); + + // TODO Not sure if this special case is really necessary/appropriate + if (funcSeq[0].Equals(PkcsObjectIdentifiers.IdPbkdf2)) + { + func = new KeyDerivationFunc(PkcsObjectIdentifiers.IdPbkdf2, + Pbkdf2Params.GetInstance(funcSeq[1])); + } + else + { + func = new KeyDerivationFunc(funcSeq); + } + + scheme = EncryptionScheme.GetInstance(seq[1].ToAsn1Object()); + } + + public KeyDerivationFunc KeyDerivationFunc + { + get { return func; } + } + + public EncryptionScheme EncryptionScheme + { + get { return scheme; } + } + + public override Asn1Object ToAsn1Object() + { + return new DerSequence(func, scheme); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/pkcs/PBKDF2Params.cs b/bc-sharp-crypto/src/asn1/pkcs/PBKDF2Params.cs new file mode 100644 index 0000000..279f30d --- /dev/null +++ b/bc-sharp-crypto/src/asn1/pkcs/PBKDF2Params.cs @@ -0,0 +1,144 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Pkcs +{ + public class Pbkdf2Params + : Asn1Encodable + { + private static AlgorithmIdentifier algid_hmacWithSHA1 = new AlgorithmIdentifier(PkcsObjectIdentifiers.IdHmacWithSha1, DerNull.Instance); + + private readonly Asn1OctetString octStr; + private readonly DerInteger iterationCount, keyLength; + private readonly AlgorithmIdentifier prf; + + public static Pbkdf2Params GetInstance( + object obj) + { + if (obj == null || obj is Pbkdf2Params) + return (Pbkdf2Params)obj; + + if (obj is Asn1Sequence) + return new Pbkdf2Params((Asn1Sequence)obj); + + throw new ArgumentException("Unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + public Pbkdf2Params( + Asn1Sequence seq) + { + if (seq.Count < 2 || seq.Count > 4) + throw new ArgumentException("Wrong number of elements in sequence", "seq"); + + this.octStr = (Asn1OctetString)seq[0]; + this.iterationCount = (DerInteger)seq[1]; + + Asn1Encodable kl = null, d = null; + if (seq.Count > 3) + { + kl = seq[2]; + d = seq[3]; + } + else if (seq.Count > 2) + { + if (seq[2] is DerInteger) + { + kl = seq[2]; + } + else + { + d = seq[2]; + } + } + if (kl != null) + { + keyLength = (DerInteger)kl; + } + if (d != null) + { + prf = AlgorithmIdentifier.GetInstance(d); + } + } + + public Pbkdf2Params( + byte[] salt, + int iterationCount) + { + this.octStr = new DerOctetString(salt); + this.iterationCount = new DerInteger(iterationCount); + } + + public Pbkdf2Params( + byte[] salt, + int iterationCount, + int keyLength) + : this(salt, iterationCount) + { + this.keyLength = new DerInteger(keyLength); + } + + public Pbkdf2Params( + byte[] salt, + int iterationCount, + int keyLength, + AlgorithmIdentifier prf) + : this(salt, iterationCount, keyLength) + { + this.prf = prf; + } + + public Pbkdf2Params( + byte[] salt, + int iterationCount, + AlgorithmIdentifier prf) + : this(salt, iterationCount) + { + this.prf = prf; + } + + public byte[] GetSalt() + { + return octStr.GetOctets(); + } + + public BigInteger IterationCount + { + get { return iterationCount.Value; } + } + + public BigInteger KeyLength + { + get { return keyLength == null ? null : keyLength.Value; } + } + + public bool IsDefaultPrf + { + get { return prf == null || prf.Equals(algid_hmacWithSHA1); } + } + + public AlgorithmIdentifier Prf + { + get { return prf != null ? prf : algid_hmacWithSHA1; } + } + + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector( + octStr, iterationCount); + + if (keyLength != null) + { + v.Add(keyLength); + } + if (!IsDefaultPrf) + { + v.Add(prf); + } + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/pkcs/PKCS12PBEParams.cs b/bc-sharp-crypto/src/asn1/pkcs/PKCS12PBEParams.cs new file mode 100644 index 0000000..b41c289 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/pkcs/PKCS12PBEParams.cs @@ -0,0 +1,63 @@ +using System; + +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Pkcs +{ + public class Pkcs12PbeParams + : Asn1Encodable + { + private readonly DerInteger iterations; + private readonly Asn1OctetString iv; + + public Pkcs12PbeParams( + byte[] salt, + int iterations) + { + this.iv = new DerOctetString(salt); + this.iterations = new DerInteger(iterations); + } + + private Pkcs12PbeParams( + Asn1Sequence seq) + { + if (seq.Count != 2) + throw new ArgumentException("Wrong number of elements in sequence", "seq"); + + iv = Asn1OctetString.GetInstance(seq[0]); + iterations = DerInteger.GetInstance(seq[1]); + } + + public static Pkcs12PbeParams GetInstance( + object obj) + { + if (obj is Pkcs12PbeParams) + { + return (Pkcs12PbeParams) obj; + } + + if (obj is Asn1Sequence) + { + return new Pkcs12PbeParams((Asn1Sequence) obj); + } + + throw new ArgumentException("Unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + public BigInteger Iterations + { + get { return iterations.Value; } + } + + public byte[] GetIV() + { + return iv.GetOctets(); + } + + public override Asn1Object ToAsn1Object() + { + return new DerSequence(iv, iterations); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/pkcs/PKCSObjectIdentifiers.cs b/bc-sharp-crypto/src/asn1/pkcs/PKCSObjectIdentifiers.cs new file mode 100644 index 0000000..1a9a03e --- /dev/null +++ b/bc-sharp-crypto/src/asn1/pkcs/PKCSObjectIdentifiers.cs @@ -0,0 +1,293 @@ +using System; + +namespace Org.BouncyCastle.Asn1.Pkcs +{ + public abstract class PkcsObjectIdentifiers + { + // + // pkcs-1 OBJECT IDENTIFIER ::= { + // iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 1 } + // + public const string Pkcs1 = "1.2.840.113549.1.1"; + internal static readonly DerObjectIdentifier Pkcs1Oid = new DerObjectIdentifier(Pkcs1); + + public static readonly DerObjectIdentifier RsaEncryption = Pkcs1Oid.Branch("1"); + public static readonly DerObjectIdentifier MD2WithRsaEncryption = Pkcs1Oid.Branch("2"); + public static readonly DerObjectIdentifier MD4WithRsaEncryption = Pkcs1Oid.Branch("3"); + public static readonly DerObjectIdentifier MD5WithRsaEncryption = Pkcs1Oid.Branch("4"); + public static readonly DerObjectIdentifier Sha1WithRsaEncryption = Pkcs1Oid.Branch("5"); + public static readonly DerObjectIdentifier SrsaOaepEncryptionSet = Pkcs1Oid.Branch("6"); + public static readonly DerObjectIdentifier IdRsaesOaep = Pkcs1Oid.Branch("7"); + public static readonly DerObjectIdentifier IdMgf1 = Pkcs1Oid.Branch("8"); + public static readonly DerObjectIdentifier IdPSpecified = Pkcs1Oid.Branch("9"); + public static readonly DerObjectIdentifier IdRsassaPss = Pkcs1Oid.Branch("10"); + public static readonly DerObjectIdentifier Sha256WithRsaEncryption = Pkcs1Oid.Branch("11"); + public static readonly DerObjectIdentifier Sha384WithRsaEncryption = Pkcs1Oid.Branch("12"); + public static readonly DerObjectIdentifier Sha512WithRsaEncryption = Pkcs1Oid.Branch("13"); + public static readonly DerObjectIdentifier Sha224WithRsaEncryption = Pkcs1Oid.Branch("14"); + /** PKCS#1: 1.2.840.113549.1.1.15 */ + public static readonly DerObjectIdentifier Sha512_224WithRSAEncryption = Pkcs1Oid.Branch("15"); + /** PKCS#1: 1.2.840.113549.1.1.16 */ + public static readonly DerObjectIdentifier Sha512_256WithRSAEncryption = Pkcs1Oid.Branch("16"); + + // + // pkcs-3 OBJECT IDENTIFIER ::= { + // iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 3 } + // + public const string Pkcs3 = "1.2.840.113549.1.3"; + + public static readonly DerObjectIdentifier DhKeyAgreement = new DerObjectIdentifier(Pkcs3 + ".1"); + + // + // pkcs-5 OBJECT IDENTIFIER ::= { + // iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 5 } + // + public const string Pkcs5 = "1.2.840.113549.1.5"; + + public static readonly DerObjectIdentifier PbeWithMD2AndDesCbc = new DerObjectIdentifier(Pkcs5 + ".1"); + public static readonly DerObjectIdentifier PbeWithMD2AndRC2Cbc = new DerObjectIdentifier(Pkcs5 + ".4"); + public static readonly DerObjectIdentifier PbeWithMD5AndDesCbc = new DerObjectIdentifier(Pkcs5 + ".3"); + public static readonly DerObjectIdentifier PbeWithMD5AndRC2Cbc = new DerObjectIdentifier(Pkcs5 + ".6"); + public static readonly DerObjectIdentifier PbeWithSha1AndDesCbc = new DerObjectIdentifier(Pkcs5 + ".10"); + public static readonly DerObjectIdentifier PbeWithSha1AndRC2Cbc = new DerObjectIdentifier(Pkcs5 + ".11"); + + public static readonly DerObjectIdentifier IdPbeS2 = new DerObjectIdentifier(Pkcs5 + ".13"); + public static readonly DerObjectIdentifier IdPbkdf2 = new DerObjectIdentifier(Pkcs5 + ".12"); + + // + // encryptionAlgorithm OBJECT IDENTIFIER ::= { + // iso(1) member-body(2) us(840) rsadsi(113549) 3 } + // + public const string EncryptionAlgorithm = "1.2.840.113549.3"; + + public static readonly DerObjectIdentifier DesEde3Cbc = new DerObjectIdentifier(EncryptionAlgorithm + ".7"); + public static readonly DerObjectIdentifier RC2Cbc = new DerObjectIdentifier(EncryptionAlgorithm + ".2"); + + // + // object identifiers for digests + // + public const string DigestAlgorithm = "1.2.840.113549.2"; + + // + // md2 OBJECT IDENTIFIER ::= + // {iso(1) member-body(2) US(840) rsadsi(113549) DigestAlgorithm(2) 2} + // + public static readonly DerObjectIdentifier MD2 = new DerObjectIdentifier(DigestAlgorithm + ".2"); + + // + // md4 OBJECT IDENTIFIER ::= + // {iso(1) member-body(2) US(840) rsadsi(113549) DigestAlgorithm(2) 4} + // + public static readonly DerObjectIdentifier MD4 = new DerObjectIdentifier(DigestAlgorithm + ".4"); + + // + // md5 OBJECT IDENTIFIER ::= + // {iso(1) member-body(2) US(840) rsadsi(113549) DigestAlgorithm(2) 5} + // + public static readonly DerObjectIdentifier MD5 = new DerObjectIdentifier(DigestAlgorithm + ".5"); + + public static readonly DerObjectIdentifier IdHmacWithSha1 = new DerObjectIdentifier(DigestAlgorithm + ".7"); + public static readonly DerObjectIdentifier IdHmacWithSha224 = new DerObjectIdentifier(DigestAlgorithm + ".8"); + public static readonly DerObjectIdentifier IdHmacWithSha256 = new DerObjectIdentifier(DigestAlgorithm + ".9"); + public static readonly DerObjectIdentifier IdHmacWithSha384 = new DerObjectIdentifier(DigestAlgorithm + ".10"); + public static readonly DerObjectIdentifier IdHmacWithSha512 = new DerObjectIdentifier(DigestAlgorithm + ".11"); + + // + // pkcs-7 OBJECT IDENTIFIER ::= { + // iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 7 } + // + public const string Pkcs7 = "1.2.840.113549.1.7"; + + public static readonly DerObjectIdentifier Data = new DerObjectIdentifier(Pkcs7 + ".1"); + public static readonly DerObjectIdentifier SignedData = new DerObjectIdentifier(Pkcs7 + ".2"); + public static readonly DerObjectIdentifier EnvelopedData = new DerObjectIdentifier(Pkcs7 + ".3"); + public static readonly DerObjectIdentifier SignedAndEnvelopedData = new DerObjectIdentifier(Pkcs7 + ".4"); + public static readonly DerObjectIdentifier DigestedData = new DerObjectIdentifier(Pkcs7 + ".5"); + public static readonly DerObjectIdentifier EncryptedData = new DerObjectIdentifier(Pkcs7 + ".6"); + + // + // pkcs-9 OBJECT IDENTIFIER ::= { + // iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 9 } + // + public const string Pkcs9 = "1.2.840.113549.1.9"; + + public static readonly DerObjectIdentifier Pkcs9AtEmailAddress = new DerObjectIdentifier(Pkcs9 + ".1"); + public static readonly DerObjectIdentifier Pkcs9AtUnstructuredName = new DerObjectIdentifier(Pkcs9 + ".2"); + public static readonly DerObjectIdentifier Pkcs9AtContentType = new DerObjectIdentifier(Pkcs9 + ".3"); + public static readonly DerObjectIdentifier Pkcs9AtMessageDigest = new DerObjectIdentifier(Pkcs9 + ".4"); + public static readonly DerObjectIdentifier Pkcs9AtSigningTime = new DerObjectIdentifier(Pkcs9 + ".5"); + public static readonly DerObjectIdentifier Pkcs9AtCounterSignature = new DerObjectIdentifier(Pkcs9 + ".6"); + public static readonly DerObjectIdentifier Pkcs9AtChallengePassword = new DerObjectIdentifier(Pkcs9 + ".7"); + public static readonly DerObjectIdentifier Pkcs9AtUnstructuredAddress = new DerObjectIdentifier(Pkcs9 + ".8"); + public static readonly DerObjectIdentifier Pkcs9AtExtendedCertificateAttributes = new DerObjectIdentifier(Pkcs9 + ".9"); + public static readonly DerObjectIdentifier Pkcs9AtSigningDescription = new DerObjectIdentifier(Pkcs9 + ".13"); + public static readonly DerObjectIdentifier Pkcs9AtExtensionRequest = new DerObjectIdentifier(Pkcs9 + ".14"); + public static readonly DerObjectIdentifier Pkcs9AtSmimeCapabilities = new DerObjectIdentifier(Pkcs9 + ".15"); + public static readonly DerObjectIdentifier IdSmime = new DerObjectIdentifier(Pkcs9 + ".16"); + + public static readonly DerObjectIdentifier Pkcs9AtFriendlyName = new DerObjectIdentifier(Pkcs9 + ".20"); + public static readonly DerObjectIdentifier Pkcs9AtLocalKeyID = new DerObjectIdentifier(Pkcs9 + ".21"); + + [Obsolete("Use X509Certificate instead")] + public static readonly DerObjectIdentifier X509CertType = new DerObjectIdentifier(Pkcs9 + ".22.1"); + + public const string CertTypes = Pkcs9 + ".22"; + public static readonly DerObjectIdentifier X509Certificate = new DerObjectIdentifier(CertTypes + ".1"); + public static readonly DerObjectIdentifier SdsiCertificate = new DerObjectIdentifier(CertTypes + ".2"); + + public const string CrlTypes = Pkcs9 + ".23"; + public static readonly DerObjectIdentifier X509Crl = new DerObjectIdentifier(CrlTypes + ".1"); + + public static readonly DerObjectIdentifier IdAlg = IdSmime.Branch("3"); + + public static readonly DerObjectIdentifier IdAlgEsdh = IdAlg.Branch("5"); + public static readonly DerObjectIdentifier IdAlgCms3DesWrap = IdAlg.Branch("6"); + public static readonly DerObjectIdentifier IdAlgCmsRC2Wrap = IdAlg.Branch("7"); + public static readonly DerObjectIdentifier IdAlgPwriKek = IdAlg.Branch("9"); + public static readonly DerObjectIdentifier IdAlgSsdh = IdAlg.Branch("10"); + + /* + *
+         * -- RSA-KEM Key Transport Algorithm
+         *
+         * id-rsa-kem OID ::= {
+         *      iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1)
+         *      pkcs-9(9) smime(16) alg(3) 14
+         *   }
+         * 
+ */ + public static readonly DerObjectIdentifier IdRsaKem = IdAlg.Branch("14"); + + // + // SMIME capability sub oids. + // + public static readonly DerObjectIdentifier PreferSignedData = Pkcs9AtSmimeCapabilities.Branch("1"); + public static readonly DerObjectIdentifier CannotDecryptAny = Pkcs9AtSmimeCapabilities.Branch("2"); + public static readonly DerObjectIdentifier SmimeCapabilitiesVersions = Pkcs9AtSmimeCapabilities.Branch("3"); + + // + // other SMIME attributes + // + public static readonly DerObjectIdentifier IdAAReceiptRequest = IdSmime.Branch("2.1"); + + // + // id-ct OBJECT IDENTIFIER ::= {iso(1) member-body(2) usa(840) + // rsadsi(113549) pkcs(1) pkcs-9(9) smime(16) ct(1)} + // + public const string IdCT = "1.2.840.113549.1.9.16.1"; + + public static readonly DerObjectIdentifier IdCTAuthData = new DerObjectIdentifier(IdCT + ".2"); + public static readonly DerObjectIdentifier IdCTTstInfo = new DerObjectIdentifier(IdCT + ".4"); + public static readonly DerObjectIdentifier IdCTCompressedData = new DerObjectIdentifier(IdCT + ".9"); + public static readonly DerObjectIdentifier IdCTAuthEnvelopedData = new DerObjectIdentifier(IdCT + ".23"); + public static readonly DerObjectIdentifier IdCTTimestampedData = new DerObjectIdentifier(IdCT + ".31"); + + // + // id-cti OBJECT IDENTIFIER ::= {iso(1) member-body(2) usa(840) + // rsadsi(113549) pkcs(1) pkcs-9(9) smime(16) cti(6)} + // + public const string IdCti = "1.2.840.113549.1.9.16.6"; + + public static readonly DerObjectIdentifier IdCtiEtsProofOfOrigin = new DerObjectIdentifier(IdCti + ".1"); + public static readonly DerObjectIdentifier IdCtiEtsProofOfReceipt = new DerObjectIdentifier(IdCti + ".2"); + public static readonly DerObjectIdentifier IdCtiEtsProofOfDelivery = new DerObjectIdentifier(IdCti + ".3"); + public static readonly DerObjectIdentifier IdCtiEtsProofOfSender = new DerObjectIdentifier(IdCti + ".4"); + public static readonly DerObjectIdentifier IdCtiEtsProofOfApproval = new DerObjectIdentifier(IdCti + ".5"); + public static readonly DerObjectIdentifier IdCtiEtsProofOfCreation = new DerObjectIdentifier(IdCti + ".6"); + + // + // id-aa OBJECT IDENTIFIER ::= {iso(1) member-body(2) usa(840) + // rsadsi(113549) pkcs(1) pkcs-9(9) smime(16) attributes(2)} + // + public const string IdAA = "1.2.840.113549.1.9.16.2"; + public static readonly DerObjectIdentifier IdAAOid = new DerObjectIdentifier(IdAA); + + public static readonly DerObjectIdentifier IdAAContentHint = new DerObjectIdentifier(IdAA + ".4"); // See RFC 2634 + public static readonly DerObjectIdentifier IdAAMsgSigDigest = new DerObjectIdentifier(IdAA + ".5"); + public static readonly DerObjectIdentifier IdAAContentReference = new DerObjectIdentifier(IdAA + ".10"); + + /* + * id-aa-encrypKeyPref OBJECT IDENTIFIER ::= {id-aa 11} + * + */ + public static readonly DerObjectIdentifier IdAAEncrypKeyPref = new DerObjectIdentifier(IdAA + ".11"); + public static readonly DerObjectIdentifier IdAASigningCertificate = new DerObjectIdentifier(IdAA + ".12"); + public static readonly DerObjectIdentifier IdAASigningCertificateV2 = new DerObjectIdentifier(IdAA + ".47"); + + public static readonly DerObjectIdentifier IdAAContentIdentifier = new DerObjectIdentifier(IdAA + ".7"); // See RFC 2634 + + /* + * RFC 3126 + */ + public static readonly DerObjectIdentifier IdAASignatureTimeStampToken = new DerObjectIdentifier(IdAA + ".14"); + + public static readonly DerObjectIdentifier IdAAEtsSigPolicyID = new DerObjectIdentifier(IdAA + ".15"); + public static readonly DerObjectIdentifier IdAAEtsCommitmentType = new DerObjectIdentifier(IdAA + ".16"); + public static readonly DerObjectIdentifier IdAAEtsSignerLocation = new DerObjectIdentifier(IdAA + ".17"); + public static readonly DerObjectIdentifier IdAAEtsSignerAttr = new DerObjectIdentifier(IdAA + ".18"); + public static readonly DerObjectIdentifier IdAAEtsOtherSigCert = new DerObjectIdentifier(IdAA + ".19"); + public static readonly DerObjectIdentifier IdAAEtsContentTimestamp = new DerObjectIdentifier(IdAA + ".20"); + public static readonly DerObjectIdentifier IdAAEtsCertificateRefs = new DerObjectIdentifier(IdAA + ".21"); + public static readonly DerObjectIdentifier IdAAEtsRevocationRefs = new DerObjectIdentifier(IdAA + ".22"); + public static readonly DerObjectIdentifier IdAAEtsCertValues = new DerObjectIdentifier(IdAA + ".23"); + public static readonly DerObjectIdentifier IdAAEtsRevocationValues = new DerObjectIdentifier(IdAA + ".24"); + public static readonly DerObjectIdentifier IdAAEtsEscTimeStamp = new DerObjectIdentifier(IdAA + ".25"); + public static readonly DerObjectIdentifier IdAAEtsCertCrlTimestamp = new DerObjectIdentifier(IdAA + ".26"); + public static readonly DerObjectIdentifier IdAAEtsArchiveTimestamp = new DerObjectIdentifier(IdAA + ".27"); + + /** PKCS#9: 1.2.840.113549.1.9.16.6.2.37 - RFC 4108 */ + public static readonly DerObjectIdentifier IdAADecryptKeyID = IdAAOid.Branch("37"); + + /** PKCS#9: 1.2.840.113549.1.9.16.6.2.38 - RFC 4108 */ + public static readonly DerObjectIdentifier IdAAImplCryptoAlgs = IdAAOid.Branch("38"); + + /** PKCS#9: 1.2.840.113549.1.9.16.2.54 RFC7030*/ + public static readonly DerObjectIdentifier IdAAAsymmDecryptKeyID = IdAAOid.Branch("54"); + + /** PKCS#9: 1.2.840.113549.1.9.16.2.43 RFC7030*/ + public static readonly DerObjectIdentifier IdAAImplCompressAlgs = IdAAOid.Branch("43"); + /** PKCS#9: 1.2.840.113549.1.9.16.2.40 RFC7030*/ + public static readonly DerObjectIdentifier IdAACommunityIdentifiers = IdAAOid.Branch("40"); + + [Obsolete("Use 'IdAAEtsSigPolicyID' instead")] + public static readonly DerObjectIdentifier IdAASigPolicyID = IdAAEtsSigPolicyID; + [Obsolete("Use 'IdAAEtsCommitmentType' instead")] + public static readonly DerObjectIdentifier IdAACommitmentType = IdAAEtsCommitmentType; + [Obsolete("Use 'IdAAEtsSignerLocation' instead")] + public static readonly DerObjectIdentifier IdAASignerLocation = IdAAEtsSignerLocation; + [Obsolete("Use 'IdAAEtsOtherSigCert' instead")] + public static readonly DerObjectIdentifier IdAAOtherSigCert = IdAAEtsOtherSigCert; + + // + // id-spq OBJECT IDENTIFIER ::= {iso(1) member-body(2) usa(840) + // rsadsi(113549) pkcs(1) pkcs-9(9) smime(16) id-spq(5)} + // + public const string IdSpq = "1.2.840.113549.1.9.16.5"; + + public static readonly DerObjectIdentifier IdSpqEtsUri = new DerObjectIdentifier(IdSpq + ".1"); + public static readonly DerObjectIdentifier IdSpqEtsUNotice = new DerObjectIdentifier(IdSpq + ".2"); + + // + // pkcs-12 OBJECT IDENTIFIER ::= { + // iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 12 } + // + public const string Pkcs12 = "1.2.840.113549.1.12"; + public const string BagTypes = Pkcs12 + ".10.1"; + + public static readonly DerObjectIdentifier KeyBag = new DerObjectIdentifier(BagTypes + ".1"); + public static readonly DerObjectIdentifier Pkcs8ShroudedKeyBag = new DerObjectIdentifier(BagTypes + ".2"); + public static readonly DerObjectIdentifier CertBag = new DerObjectIdentifier(BagTypes + ".3"); + public static readonly DerObjectIdentifier CrlBag = new DerObjectIdentifier(BagTypes + ".4"); + public static readonly DerObjectIdentifier SecretBag = new DerObjectIdentifier(BagTypes + ".5"); + public static readonly DerObjectIdentifier SafeContentsBag = new DerObjectIdentifier(BagTypes + ".6"); + + public const string Pkcs12PbeIds = Pkcs12 + ".1"; + + public static readonly DerObjectIdentifier PbeWithShaAnd128BitRC4 = new DerObjectIdentifier(Pkcs12PbeIds + ".1"); + public static readonly DerObjectIdentifier PbeWithShaAnd40BitRC4 = new DerObjectIdentifier(Pkcs12PbeIds + ".2"); + public static readonly DerObjectIdentifier PbeWithShaAnd3KeyTripleDesCbc = new DerObjectIdentifier(Pkcs12PbeIds + ".3"); + public static readonly DerObjectIdentifier PbeWithShaAnd2KeyTripleDesCbc = new DerObjectIdentifier(Pkcs12PbeIds + ".4"); + public static readonly DerObjectIdentifier PbeWithShaAnd128BitRC2Cbc = new DerObjectIdentifier(Pkcs12PbeIds + ".5"); + public static readonly DerObjectIdentifier PbewithShaAnd40BitRC2Cbc = new DerObjectIdentifier(Pkcs12PbeIds + ".6"); + } +} diff --git a/bc-sharp-crypto/src/asn1/pkcs/Pfx.cs b/bc-sharp-crypto/src/asn1/pkcs/Pfx.cs new file mode 100644 index 0000000..9676f64 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/pkcs/Pfx.cs @@ -0,0 +1,65 @@ +using System; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Asn1.Pkcs +{ + /** + * the infamous Pfx from Pkcs12 + */ + public class Pfx + : Asn1Encodable + { + private ContentInfo contentInfo; + private MacData macData; + + public Pfx( + Asn1Sequence seq) + { + BigInteger version = ((DerInteger) seq[0]).Value; + if (version.IntValue != 3) + { + throw new ArgumentException("wrong version for PFX PDU"); + } + + contentInfo = ContentInfo.GetInstance(seq[1]); + + if (seq.Count == 3) + { + macData = MacData.GetInstance(seq[2]); + } + } + + public Pfx( + ContentInfo contentInfo, + MacData macData) + { + this.contentInfo = contentInfo; + this.macData = macData; + } + + public ContentInfo AuthSafe + { + get { return contentInfo; } + } + + public MacData MacData + { + get { return macData; } + } + + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector( + new DerInteger(3), contentInfo); + + if (macData != null) + { + v.Add(macData); + } + + return new BerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/pkcs/PrivateKeyInfo.cs b/bc-sharp-crypto/src/asn1/pkcs/PrivateKeyInfo.cs new file mode 100644 index 0000000..c5be7a3 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/pkcs/PrivateKeyInfo.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Asn1.Pkcs +{ + public class PrivateKeyInfo + : Asn1Encodable + { + private readonly Asn1OctetString privKey; + private readonly AlgorithmIdentifier algID; + private readonly Asn1Set attributes; + + public static PrivateKeyInfo GetInstance(Asn1TaggedObject obj, bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); + } + + public static PrivateKeyInfo GetInstance( + object obj) + { + if (obj == null) + return null; + if (obj is PrivateKeyInfo) + return (PrivateKeyInfo) obj; + return new PrivateKeyInfo(Asn1Sequence.GetInstance(obj)); + } + + public PrivateKeyInfo(AlgorithmIdentifier algID, Asn1Encodable privateKey) + : this(algID, privateKey, null) + { + } + + public PrivateKeyInfo( + AlgorithmIdentifier algID, + Asn1Encodable privateKey, + Asn1Set attributes) + { + this.algID = algID; + this.privKey = new DerOctetString(privateKey.GetEncoded(Asn1Encodable.Der)); + this.attributes = attributes; + } + + private PrivateKeyInfo(Asn1Sequence seq) + { + IEnumerator e = seq.GetEnumerator(); + + e.MoveNext(); + BigInteger version = ((DerInteger)e.Current).Value; + if (version.IntValue != 0) + { + throw new ArgumentException("wrong version for private key info: " + version.IntValue); + } + + e.MoveNext(); + algID = AlgorithmIdentifier.GetInstance(e.Current); + e.MoveNext(); + privKey = Asn1OctetString.GetInstance(e.Current); + + if (e.MoveNext()) + { + attributes = Asn1Set.GetInstance((Asn1TaggedObject)e.Current, false); + } + } + + public virtual AlgorithmIdentifier PrivateKeyAlgorithm + { + get { return algID; } + } + + [Obsolete("Use 'PrivateKeyAlgorithm' property instead")] + public virtual AlgorithmIdentifier AlgorithmID + { + get { return algID; } + } + + public virtual Asn1Object ParsePrivateKey() + { + return Asn1Object.FromByteArray(privKey.GetOctets()); + } + + [Obsolete("Use 'ParsePrivateKey' instead")] + public virtual Asn1Object PrivateKey + { + get + { + try + { + return ParsePrivateKey(); + } + catch (IOException) + { + throw new InvalidOperationException("unable to parse private key"); + } + } + } + + public virtual Asn1Set Attributes + { + get { 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 Attr
+         * 
+ */ + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(new DerInteger(0), algID, privKey); + + if (attributes != null) + { + v.Add(new DerTaggedObject(false, 0, attributes)); + } + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/pkcs/RC2CBCParameter.cs b/bc-sharp-crypto/src/asn1/pkcs/RC2CBCParameter.cs new file mode 100644 index 0000000..880ca74 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/pkcs/RC2CBCParameter.cs @@ -0,0 +1,80 @@ +using System; + +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Pkcs +{ + public class RC2CbcParameter + : Asn1Encodable + { + internal DerInteger version; + internal Asn1OctetString iv; + + public static RC2CbcParameter GetInstance( + object obj) + { + if (obj is Asn1Sequence) + { + return new RC2CbcParameter((Asn1Sequence) obj); + } + + throw new ArgumentException("Unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + public RC2CbcParameter( + byte[] iv) + { + this.iv = new DerOctetString(iv); + } + + public RC2CbcParameter( + int parameterVersion, + byte[] iv) + { + this.version = new DerInteger(parameterVersion); + this.iv = new DerOctetString(iv); + } + + private RC2CbcParameter( + Asn1Sequence seq) + { + if (seq.Count == 1) + { + iv = (Asn1OctetString)seq[0]; + } + else + { + version = (DerInteger)seq[0]; + iv = (Asn1OctetString)seq[1]; + } + } + + public BigInteger RC2ParameterVersion + { + get + { + return version == null ? null : version.Value; + } + } + + public byte[] GetIV() + { + return Arrays.Clone(iv.GetOctets()); + } + + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(); + + if (version != null) + { + v.Add(version); + } + + v.Add(iv); + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/pkcs/RSAESOAEPparams.cs b/bc-sharp-crypto/src/asn1/pkcs/RSAESOAEPparams.cs new file mode 100644 index 0000000..0cf22f8 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/pkcs/RSAESOAEPparams.cs @@ -0,0 +1,146 @@ +using System; + +using Org.BouncyCastle.Asn1.Oiw; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Pkcs +{ + public class RsaesOaepParameters + : Asn1Encodable + { + private AlgorithmIdentifier hashAlgorithm; + private AlgorithmIdentifier maskGenAlgorithm; + private AlgorithmIdentifier pSourceAlgorithm; + + public readonly static AlgorithmIdentifier DefaultHashAlgorithm = new AlgorithmIdentifier(OiwObjectIdentifiers.IdSha1, DerNull.Instance); + public readonly static AlgorithmIdentifier DefaultMaskGenFunction = new AlgorithmIdentifier(PkcsObjectIdentifiers.IdMgf1, DefaultHashAlgorithm); + public readonly static AlgorithmIdentifier DefaultPSourceAlgorithm = new AlgorithmIdentifier(PkcsObjectIdentifiers.IdPSpecified, new DerOctetString(new byte[0])); + + public static RsaesOaepParameters GetInstance( + object obj) + { + if (obj is RsaesOaepParameters) + { + return (RsaesOaepParameters)obj; + } + else if (obj is Asn1Sequence) + { + return new RsaesOaepParameters((Asn1Sequence)obj); + } + + throw new ArgumentException("Unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + /** + * The default version + */ + public RsaesOaepParameters() + { + hashAlgorithm = DefaultHashAlgorithm; + maskGenAlgorithm = DefaultMaskGenFunction; + pSourceAlgorithm = DefaultPSourceAlgorithm; + } + + public RsaesOaepParameters( + AlgorithmIdentifier hashAlgorithm, + AlgorithmIdentifier maskGenAlgorithm, + AlgorithmIdentifier pSourceAlgorithm) + { + this.hashAlgorithm = hashAlgorithm; + this.maskGenAlgorithm = maskGenAlgorithm; + this.pSourceAlgorithm = pSourceAlgorithm; + } + + public RsaesOaepParameters( + Asn1Sequence seq) + { + hashAlgorithm = DefaultHashAlgorithm; + maskGenAlgorithm = DefaultMaskGenFunction; + pSourceAlgorithm = DefaultPSourceAlgorithm; + + for (int i = 0; i != seq.Count; i++) + { + Asn1TaggedObject o = (Asn1TaggedObject)seq[i]; + + switch (o.TagNo) + { + 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 ArgumentException("unknown tag"); + } + } + } + + public AlgorithmIdentifier HashAlgorithm + { + get { return hashAlgorithm; } + } + + public AlgorithmIdentifier MaskGenAlgorithm + { + get { return maskGenAlgorithm; } + } + + public AlgorithmIdentifier PSourceAlgorithm + { + get { 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 override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(); + + if (!hashAlgorithm.Equals(DefaultHashAlgorithm)) + { + v.Add(new DerTaggedObject(true, 0, hashAlgorithm)); + } + + if (!maskGenAlgorithm.Equals(DefaultMaskGenFunction)) + { + v.Add(new DerTaggedObject(true, 1, maskGenAlgorithm)); + } + + if (!pSourceAlgorithm.Equals(DefaultPSourceAlgorithm)) + { + v.Add(new DerTaggedObject(true, 2, pSourceAlgorithm)); + } + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/pkcs/RSAPrivateKeyStructure.cs b/bc-sharp-crypto/src/asn1/pkcs/RSAPrivateKeyStructure.cs new file mode 100644 index 0000000..7212991 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/pkcs/RSAPrivateKeyStructure.cs @@ -0,0 +1,146 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Asn1.Pkcs +{ + public class RsaPrivateKeyStructure + : Asn1Encodable + { + private readonly BigInteger modulus; + private readonly BigInteger publicExponent; + private readonly BigInteger privateExponent; + private readonly BigInteger prime1; + private readonly BigInteger prime2; + private readonly BigInteger exponent1; + private readonly BigInteger exponent2; + private readonly BigInteger coefficient; + + public static RsaPrivateKeyStructure GetInstance(Asn1TaggedObject obj, bool isExplicit) + { + return GetInstance(Asn1Sequence.GetInstance(obj, isExplicit)); + } + + public static RsaPrivateKeyStructure GetInstance(object obj) + { + if (obj == null) + return null; + if (obj is RsaPrivateKeyStructure) + return (RsaPrivateKeyStructure)obj; + return new RsaPrivateKeyStructure(Asn1Sequence.GetInstance(obj)); + } + + public RsaPrivateKeyStructure( + BigInteger modulus, + BigInteger publicExponent, + BigInteger privateExponent, + BigInteger prime1, + BigInteger prime2, + BigInteger exponent1, + BigInteger exponent2, + BigInteger coefficient) + { + this.modulus = modulus; + this.publicExponent = publicExponent; + this.privateExponent = privateExponent; + this.prime1 = prime1; + this.prime2 = prime2; + this.exponent1 = exponent1; + this.exponent2 = exponent2; + this.coefficient = coefficient; + } + + [Obsolete("Use 'GetInstance' method(s) instead")] + public RsaPrivateKeyStructure( + Asn1Sequence seq) + { + BigInteger version = ((DerInteger) seq[0]).Value; + if (version.IntValue != 0) + throw new ArgumentException("wrong version for RSA private key"); + + modulus = ((DerInteger) seq[1]).Value; + publicExponent = ((DerInteger) seq[2]).Value; + privateExponent = ((DerInteger) seq[3]).Value; + prime1 = ((DerInteger) seq[4]).Value; + prime2 = ((DerInteger) seq[5]).Value; + exponent1 = ((DerInteger) seq[6]).Value; + exponent2 = ((DerInteger) seq[7]).Value; + coefficient = ((DerInteger) seq[8]).Value; + } + + public BigInteger Modulus + { + get { return modulus; } + } + + public BigInteger PublicExponent + { + get { return publicExponent; } + } + + public BigInteger PrivateExponent + { + get { return privateExponent; } + } + + public BigInteger Prime1 + { + get { return prime1; } + } + + public BigInteger Prime2 + { + get { return prime2; } + } + + public BigInteger Exponent1 + { + get { return exponent1; } + } + + public BigInteger Exponent2 + { + get { return exponent2; } + } + + public BigInteger Coefficient + { + get { 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
+         *                      }
+         *
+         *      Version ::= Integer
+         * 
+ *

This routine is written to output Pkcs1 version 0, private keys.

+ */ + public override Asn1Object ToAsn1Object() + { + return new DerSequence( + new DerInteger(0), // version + new DerInteger(Modulus), + new DerInteger(PublicExponent), + new DerInteger(PrivateExponent), + new DerInteger(Prime1), + new DerInteger(Prime2), + new DerInteger(Exponent1), + new DerInteger(Exponent2), + new DerInteger(Coefficient)); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/pkcs/RSASSAPSSparams.cs b/bc-sharp-crypto/src/asn1/pkcs/RSASSAPSSparams.cs new file mode 100644 index 0000000..85849c3 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/pkcs/RSASSAPSSparams.cs @@ -0,0 +1,166 @@ +using System; + +using Org.BouncyCastle.Asn1.Oiw; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Pkcs +{ + public class RsassaPssParameters + : Asn1Encodable + { + private AlgorithmIdentifier hashAlgorithm; + private AlgorithmIdentifier maskGenAlgorithm; + private DerInteger saltLength; + private DerInteger trailerField; + + public readonly static AlgorithmIdentifier DefaultHashAlgorithm = new AlgorithmIdentifier(OiwObjectIdentifiers.IdSha1, DerNull.Instance); + public readonly static AlgorithmIdentifier DefaultMaskGenFunction = new AlgorithmIdentifier(PkcsObjectIdentifiers.IdMgf1, DefaultHashAlgorithm); + public readonly static DerInteger DefaultSaltLength = new DerInteger(20); + public readonly static DerInteger DefaultTrailerField = new DerInteger(1); + + public static RsassaPssParameters GetInstance( + object obj) + { + if (obj == null || obj is RsassaPssParameters) + { + return (RsassaPssParameters)obj; + } + + if (obj is Asn1Sequence) + { + return new RsassaPssParameters((Asn1Sequence)obj); + } + + throw new ArgumentException("Unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + /** + * The default version + */ + public RsassaPssParameters() + { + hashAlgorithm = DefaultHashAlgorithm; + maskGenAlgorithm = DefaultMaskGenFunction; + saltLength = DefaultSaltLength; + trailerField = DefaultTrailerField; + } + + public RsassaPssParameters( + AlgorithmIdentifier hashAlgorithm, + AlgorithmIdentifier maskGenAlgorithm, + DerInteger saltLength, + DerInteger trailerField) + { + this.hashAlgorithm = hashAlgorithm; + this.maskGenAlgorithm = maskGenAlgorithm; + this.saltLength = saltLength; + this.trailerField = trailerField; + } + + public RsassaPssParameters( + Asn1Sequence seq) + { + hashAlgorithm = DefaultHashAlgorithm; + maskGenAlgorithm = DefaultMaskGenFunction; + saltLength = DefaultSaltLength; + trailerField = DefaultTrailerField; + + for (int i = 0; i != seq.Count; i++) + { + Asn1TaggedObject o = (Asn1TaggedObject)seq[i]; + + switch (o.TagNo) + { + 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 ArgumentException("unknown tag"); + } + } + } + + public AlgorithmIdentifier HashAlgorithm + { + get { return hashAlgorithm; } + } + + public AlgorithmIdentifier MaskGenAlgorithm + { + get { return maskGenAlgorithm; } + } + + public DerInteger SaltLength + { + get { return saltLength; } + } + + public DerInteger TrailerField + { + get { 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 override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(); + + if (!hashAlgorithm.Equals(DefaultHashAlgorithm)) + { + v.Add(new DerTaggedObject(true, 0, hashAlgorithm)); + } + + if (!maskGenAlgorithm.Equals(DefaultMaskGenFunction)) + { + v.Add(new DerTaggedObject(true, 1, maskGenAlgorithm)); + } + + if (!saltLength.Equals(DefaultSaltLength)) + { + v.Add(new DerTaggedObject(true, 2, saltLength)); + } + + if (!trailerField.Equals(DefaultTrailerField)) + { + v.Add(new DerTaggedObject(true, 3, trailerField)); + } + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/pkcs/SafeBag.cs b/bc-sharp-crypto/src/asn1/pkcs/SafeBag.cs new file mode 100644 index 0000000..4b9350b --- /dev/null +++ b/bc-sharp-crypto/src/asn1/pkcs/SafeBag.cs @@ -0,0 +1,70 @@ +using Org.BouncyCastle.Asn1; + +namespace Org.BouncyCastle.Asn1.Pkcs +{ + public class SafeBag + : Asn1Encodable + { + private readonly DerObjectIdentifier bagID; + private readonly Asn1Object bagValue; + private readonly Asn1Set bagAttributes; + + public SafeBag( + DerObjectIdentifier oid, + Asn1Object obj) + { + this.bagID = oid; + this.bagValue = obj; + this.bagAttributes = null; + } + + public SafeBag( + DerObjectIdentifier oid, + Asn1Object obj, + Asn1Set bagAttributes) + { + this.bagID = oid; + this.bagValue = obj; + this.bagAttributes = bagAttributes; + } + + public SafeBag( + Asn1Sequence seq) + { + this.bagID = (DerObjectIdentifier) seq[0]; + this.bagValue = ((DerTaggedObject) seq[1]).GetObject(); + if (seq.Count == 3) + { + this.bagAttributes = (Asn1Set) seq[2]; + } + } + + public DerObjectIdentifier BagID + { + get { return bagID; } + } + + public Asn1Object BagValue + { + get { return bagValue; } + } + + public Asn1Set BagAttributes + { + get { return bagAttributes; } + } + + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector( + bagID, new DerTaggedObject(0, bagValue)); + + if (bagAttributes != null) + { + v.Add(bagAttributes); + } + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/pkcs/SignedData.cs b/bc-sharp-crypto/src/asn1/pkcs/SignedData.cs new file mode 100644 index 0000000..6e72bd0 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/pkcs/SignedData.cs @@ -0,0 +1,157 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1; + +namespace Org.BouncyCastle.Asn1.Pkcs +{ + /** + * a Pkcs#7 signed data object. + */ + public class SignedData + : Asn1Encodable + { + private readonly DerInteger version; + private readonly Asn1Set digestAlgorithms; + private readonly ContentInfo contentInfo; + private readonly Asn1Set certificates; + private readonly Asn1Set crls; + private readonly Asn1Set signerInfos; + + public static SignedData GetInstance(object obj) + { + if (obj == null) + return null; + SignedData existing = obj as SignedData; + if (existing != null) + return existing; + return new SignedData(Asn1Sequence.GetInstance(obj)); + } + + 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; + } + + private SignedData( + Asn1Sequence seq) + { + IEnumerator e = seq.GetEnumerator(); + + e.MoveNext(); + version = (DerInteger) e.Current; + + e.MoveNext(); + digestAlgorithms = (Asn1Set) e.Current; + + e.MoveNext(); + contentInfo = ContentInfo.GetInstance(e.Current); + + while (e.MoveNext()) + { + Asn1Object o = (Asn1Object) e.Current; + + // + // 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 is DerTaggedObject) + { + DerTaggedObject tagged = (DerTaggedObject) o; + + switch (tagged.TagNo) + { + case 0: + certificates = Asn1Set.GetInstance(tagged, false); + break; + case 1: + crls = Asn1Set.GetInstance(tagged, false); + break; + default: + throw new ArgumentException("unknown tag value " + tagged.TagNo); + } + } + else + { + signerInfos = (Asn1Set) o; + } + } + } + + public DerInteger Version + { + get { return version; } + } + + public Asn1Set DigestAlgorithms + { + get { return digestAlgorithms; } + } + + public ContentInfo ContentInfo + { + get { return contentInfo; } + } + + public Asn1Set Certificates + { + get { return certificates; } + } + + public Asn1Set Crls + { + get { return crls; } + } + + public Asn1Set SignerInfos + { + get { 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 override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector( + version, digestAlgorithms, 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/bc-sharp-crypto/src/asn1/pkcs/SignerInfo.cs b/bc-sharp-crypto/src/asn1/pkcs/SignerInfo.cs new file mode 100644 index 0000000..a3dc48b --- /dev/null +++ b/bc-sharp-crypto/src/asn1/pkcs/SignerInfo.cs @@ -0,0 +1,154 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Pkcs +{ + /** + * a Pkcs#7 signer info object. + */ + public class SignerInfo + : 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 obj) + { + if (obj is SignerInfo) + { + return (SignerInfo) obj; + } + + if (obj is Asn1Sequence) + { + return new SignerInfo((Asn1Sequence) obj); + } + + throw new ArgumentException("Unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + 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) + { + IEnumerator e = seq.GetEnumerator(); + + e.MoveNext(); + version = (DerInteger) e.Current; + + e.MoveNext(); + issuerAndSerialNumber = IssuerAndSerialNumber.GetInstance(e.Current); + + e.MoveNext(); + digAlgorithm = AlgorithmIdentifier.GetInstance(e.Current); + + e.MoveNext(); + object obj = e.Current; + + if (obj is Asn1TaggedObject) + { + authenticatedAttributes = Asn1Set.GetInstance((Asn1TaggedObject) obj, false); + + e.MoveNext(); + digEncryptionAlgorithm = AlgorithmIdentifier.GetInstance(e.Current); + } + else + { + authenticatedAttributes = null; + digEncryptionAlgorithm = AlgorithmIdentifier.GetInstance(obj); + } + + e.MoveNext(); + encryptedDigest = DerOctetString.GetInstance(e.Current); + + if (e.MoveNext()) + { + unauthenticatedAttributes = Asn1Set.GetInstance((Asn1TaggedObject)e.Current, false); + } + else + { + unauthenticatedAttributes = null; + } + } + + public DerInteger Version { get { return version; } } + + public IssuerAndSerialNumber IssuerAndSerialNumber { get { return issuerAndSerialNumber; } } + + public Asn1Set AuthenticatedAttributes { get { return authenticatedAttributes; } } + + public AlgorithmIdentifier DigestAlgorithm { get { return digAlgorithm; } } + + public Asn1OctetString EncryptedDigest { get { return encryptedDigest; } } + + public AlgorithmIdentifier DigestEncryptionAlgorithm { get { return digEncryptionAlgorithm; } } + + public Asn1Set UnauthenticatedAttributes { get { 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 override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector( + version, issuerAndSerialNumber, digAlgorithm); + + if (authenticatedAttributes != null) + { + v.Add(new DerTaggedObject(false, 0, authenticatedAttributes)); + } + + v.Add(digEncryptionAlgorithm, encryptedDigest); + + if (unauthenticatedAttributes != null) + { + v.Add(new DerTaggedObject(false, 1, unauthenticatedAttributes)); + } + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/sec/ECPrivateKeyStructure.cs b/bc-sharp-crypto/src/asn1/sec/ECPrivateKeyStructure.cs new file mode 100644 index 0000000..32e020c --- /dev/null +++ b/bc-sharp-crypto/src/asn1/sec/ECPrivateKeyStructure.cs @@ -0,0 +1,184 @@ +using System; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Sec +{ + /** + * the elliptic curve private key object from SEC 1 + */ + public class ECPrivateKeyStructure + : Asn1Encodable + { + private readonly Asn1Sequence seq; + + public static ECPrivateKeyStructure GetInstance(object obj) + { + if (obj == null) + return null; + if (obj is ECPrivateKeyStructure) + return (ECPrivateKeyStructure)obj; + return new ECPrivateKeyStructure(Asn1Sequence.GetInstance(obj)); + } + + [Obsolete("Use 'GetInstance' instead")] + public ECPrivateKeyStructure( + Asn1Sequence seq) + { + if (seq == null) + throw new ArgumentNullException("seq"); + + this.seq = seq; + } + + [Obsolete("Use constructor which takes 'orderBitLength' instead, to guarantee correct encoding")] + public ECPrivateKeyStructure( + BigInteger key) + { + if (key == null) + throw new ArgumentNullException("key"); + + this.seq = new DerSequence( + new DerInteger(1), + new DerOctetString(key.ToByteArrayUnsigned())); + } + + public ECPrivateKeyStructure( + int orderBitLength, + BigInteger key) + { + if (key == null) + throw new ArgumentNullException("key"); + if (orderBitLength < key.BitLength) + throw new ArgumentException("must be >= key bitlength", "orderBitLength"); + + byte[] bytes = BigIntegers.AsUnsignedByteArray((orderBitLength + 7) / 8, key); + + this.seq = new DerSequence( + new DerInteger(1), + new DerOctetString(bytes)); + } + + [Obsolete("Use constructor which takes 'orderBitLength' instead, to guarantee correct encoding")] + public ECPrivateKeyStructure( + BigInteger key, + Asn1Encodable parameters) + : this(key, null, parameters) + { + } + + [Obsolete("Use constructor which takes 'orderBitLength' instead, to guarantee correct encoding")] + public ECPrivateKeyStructure( + BigInteger key, + DerBitString publicKey, + Asn1Encodable parameters) + { + if (key == null) + throw new ArgumentNullException("key"); + + Asn1EncodableVector v = new Asn1EncodableVector( + new DerInteger(1), + new DerOctetString(key.ToByteArrayUnsigned())); + + if (parameters != null) + { + v.Add(new DerTaggedObject(true, 0, parameters)); + } + + if (publicKey != null) + { + v.Add(new DerTaggedObject(true, 1, publicKey)); + } + + this.seq = new DerSequence(v); + } + + public ECPrivateKeyStructure( + int orderBitLength, + BigInteger key, + Asn1Encodable parameters) + : this(orderBitLength, key, null, parameters) + { + } + + public ECPrivateKeyStructure( + int orderBitLength, + BigInteger key, + DerBitString publicKey, + Asn1Encodable parameters) + { + if (key == null) + throw new ArgumentNullException("key"); + if (orderBitLength < key.BitLength) + throw new ArgumentException("must be >= key bitlength", "orderBitLength"); + + byte[] bytes = BigIntegers.AsUnsignedByteArray((orderBitLength + 7) / 8, key); + + Asn1EncodableVector v = new Asn1EncodableVector( + new DerInteger(1), + new DerOctetString(bytes)); + + if (parameters != null) + { + v.Add(new DerTaggedObject(true, 0, parameters)); + } + + if (publicKey != null) + { + v.Add(new DerTaggedObject(true, 1, publicKey)); + } + + this.seq = new DerSequence(v); + } + + public virtual BigInteger GetKey() + { + Asn1OctetString octs = (Asn1OctetString) seq[1]; + + return new BigInteger(1, octs.GetOctets()); + } + + public virtual DerBitString GetPublicKey() + { + return (DerBitString) GetObjectInTag(1); + } + + public virtual Asn1Object GetParameters() + { + return GetObjectInTag(0); + } + + private Asn1Object GetObjectInTag(int tagNo) + { + foreach (Asn1Encodable ae in seq) + { + Asn1Object obj = ae.ToAsn1Object(); + + if (obj is Asn1TaggedObject) + { + Asn1TaggedObject tag = (Asn1TaggedObject) obj; + if (tag.TagNo == tagNo) + { + return tag.GetObject(); + } + } + } + + return null; + } + + /** + * ECPrivateKey ::= SEQUENCE { + * version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1), + * privateKey OCTET STRING, + * parameters [0] Parameters OPTIONAL, + * publicKey [1] BIT STRING OPTIONAL } + */ + public override Asn1Object ToAsn1Object() + { + return seq; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/sec/SECNamedCurves.cs b/bc-sharp-crypto/src/asn1/sec/SECNamedCurves.cs new file mode 100644 index 0000000..b753ac5 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/sec/SECNamedCurves.cs @@ -0,0 +1,1184 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Math.EC; +using Org.BouncyCastle.Math.EC.Endo; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Asn1.Sec +{ + public sealed class SecNamedCurves + { + private SecNamedCurves() + { + } + + private static ECCurve ConfigureCurve(ECCurve curve) + { + return curve; + } + + private static ECCurve ConfigureCurveGlv(ECCurve c, GlvTypeBParameters p) + { + return c.Configure().SetEndomorphism(new GlvTypeBEndomorphism(c, p)).Create(); + } + + private static BigInteger FromHex(string hex) + { + return new BigInteger(1, Hex.Decode(hex)); + } + + /* + * secp112r1 + */ + internal class Secp112r1Holder + : X9ECParametersHolder + { + private Secp112r1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new Secp112r1Holder(); + + protected override 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.One; + + ECCurve curve = ConfigureCurve(new FpCurve(p, a, b, n, h)); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "09487239995A5EE76B55F9C2F098" + + "A89CE5AF8724C0A23E0E0FF77500")); + + return new X9ECParameters(curve, G, n, h, S); + } + } + + /* + * secp112r2 + */ + internal class Secp112r2Holder + : X9ECParametersHolder + { + private Secp112r2Holder() {} + + internal static readonly X9ECParametersHolder Instance = new Secp112r2Holder(); + + protected override 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 = ConfigureCurve(new FpCurve(p, a, b, n, h)); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "4BA30AB5E892B4E1649DD0928643" + + "ADCD46F5882E3747DEF36E956E97")); + + return new X9ECParameters(curve, G, n, h, S); + } + } + + /* + * secp128r1 + */ + internal class Secp128r1Holder + : X9ECParametersHolder + { + private Secp128r1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new Secp128r1Holder(); + + protected override 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.One; + + ECCurve curve = ConfigureCurve(new FpCurve(p, a, b, n, h)); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "161FF7528B899B2D0C28607CA52C5B86" + + "CF5AC8395BAFEB13C02DA292DDED7A83")); + + return new X9ECParameters(curve, G, n, h, S); + } + } + + /* + * secp128r2 + */ + internal class Secp128r2Holder + : X9ECParametersHolder + { + private Secp128r2Holder() {} + + internal static readonly X9ECParametersHolder Instance = new Secp128r2Holder(); + + protected override 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 = ConfigureCurve(new FpCurve(p, a, b, n, h)); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "7B6AA5D85E572983E6FB32A7CDEBC140" + + "27B6916A894D3AEE7106FE805FC34B44")); + + return new X9ECParameters(curve, G, n, h, S); + } + } + + /* + * secp160k1 + */ + internal class Secp160k1Holder + : X9ECParametersHolder + { + private Secp160k1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new Secp160k1Holder(); + + protected override 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 = BigInteger.Zero; + BigInteger b = BigInteger.ValueOf(7); + byte[] S = null; + BigInteger n = FromHex("0100000000000000000001B8FA16DFAB9ACA16B6B3"); + BigInteger h = BigInteger.One; + + GlvTypeBParameters glv = new GlvTypeBParameters( + new BigInteger("9ba48cba5ebcb9b6bd33b92830b2a2e0e192f10a", 16), + new BigInteger("c39c6c3b3a36d7701b9c71a1f5804ae5d0003f4", 16), + new BigInteger[]{ + new BigInteger("9162fbe73984472a0a9e", 16), + new BigInteger("-96341f1138933bc2f505", 16) }, + new BigInteger[]{ + new BigInteger("127971af8721782ecffa3", 16), + new BigInteger("9162fbe73984472a0a9e", 16) }, + new BigInteger("9162fbe73984472a0a9d0590", 16), + new BigInteger("96341f1138933bc2f503fd44", 16), + 176); + + ECCurve curve = ConfigureCurveGlv(new FpCurve(p, a, b, n, h), glv); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "3B4C382CE37AA192A4019E763036F4F5DD4D7EBB" + + "938CF935318FDCED6BC28286531733C3F03C4FEE")); + + return new X9ECParameters(curve, G, n, h, S); + } + } + + /* + * secp160r1 + */ + internal class Secp160r1Holder + : X9ECParametersHolder + { + private Secp160r1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new Secp160r1Holder(); + + protected override 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.One; + + ECCurve curve = ConfigureCurve(new FpCurve(p, a, b, n, h)); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "4A96B5688EF573284664698968C38BB913CBFC82" + + "23A628553168947D59DCC912042351377AC5FB32")); + + return new X9ECParameters(curve, G, n, h, S); + } + } + + /* + * secp160r2 + */ + internal class Secp160r2Holder + : X9ECParametersHolder + { + private Secp160r2Holder() {} + + internal static readonly X9ECParametersHolder Instance = new Secp160r2Holder(); + + protected override 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.One; + + ECCurve curve = ConfigureCurve(new FpCurve(p, a, b, n, h)); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "52DCB034293A117E1F4FF11B30F7199D3144CE6D" + + "FEAFFEF2E331F296E071FA0DF9982CFEA7D43F2E")); + + return new X9ECParameters(curve, G, n, h, S); + } + } + + /* + * secp192k1 + */ + internal class Secp192k1Holder + : X9ECParametersHolder + { + private Secp192k1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new Secp192k1Holder(); + + protected override X9ECParameters CreateParameters() + { + // p = 2^192 - 2^32 - 2^12 - 2^8 - 2^7 - 2^6 - 2^3 - 1 + BigInteger p = FromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFEE37"); + BigInteger a = BigInteger.Zero; + BigInteger b = BigInteger.ValueOf(3); + byte[] S = null; + BigInteger n = FromHex("FFFFFFFFFFFFFFFFFFFFFFFE26F2FC170F69466A74DEFD8D"); + BigInteger h = BigInteger.One; + + GlvTypeBParameters glv = new GlvTypeBParameters( + new BigInteger("bb85691939b869c1d087f601554b96b80cb4f55b35f433c2", 16), + new BigInteger("3d84f26c12238d7b4f3d516613c1759033b1a5800175d0b1", 16), + new BigInteger[]{ + new BigInteger("71169be7330b3038edb025f1", 16), + new BigInteger("-b3fb3400dec5c4adceb8655c", 16) }, + new BigInteger[]{ + new BigInteger("12511cfe811d0f4e6bc688b4d", 16), + new BigInteger("71169be7330b3038edb025f1", 16) }, + new BigInteger("71169be7330b3038edb025f1d0f9", 16), + new BigInteger("b3fb3400dec5c4adceb8655d4c94", 16), + 208); + + ECCurve curve = ConfigureCurveGlv(new FpCurve(p, a, b, n, h), glv); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "DB4FF10EC057E9AE26B07D0280B7F4341DA5D1B1EAE06C7D" + + "9B2F2F6D9C5628A7844163D015BE86344082AA88D95E2F9D")); + + return new X9ECParameters(curve, G, n, h, S); + } + } + + /* + * secp192r1 + */ + internal class Secp192r1Holder + : X9ECParametersHolder + { + private Secp192r1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new Secp192r1Holder(); + + protected override 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.One; + + ECCurve curve = ConfigureCurve(new FpCurve(p, a, b, n, h)); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "188DA80EB03090F67CBF20EB43A18800F4FF0AFD82FF1012" + + "07192B95FFC8DA78631011ED6B24CDD573F977A11E794811")); + + return new X9ECParameters(curve, G, n, h, S); + } + } + + /* + * secp224k1 + */ + internal class Secp224k1Holder + : X9ECParametersHolder + { + private Secp224k1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new Secp224k1Holder(); + + protected override 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 = BigInteger.Zero; + BigInteger b = BigInteger.ValueOf(5); + byte[] S = null; + BigInteger n = FromHex("010000000000000000000000000001DCE8D2EC6184CAF0A971769FB1F7"); + BigInteger h = BigInteger.One; + + GlvTypeBParameters glv = new GlvTypeBParameters( + new BigInteger("fe0e87005b4e83761908c5131d552a850b3f58b749c37cf5b84d6768", 16), + new BigInteger("60dcd2104c4cbc0be6eeefc2bdd610739ec34e317f9b33046c9e4788", 16), + new BigInteger[]{ + new BigInteger("6b8cf07d4ca75c88957d9d670591", 16), + new BigInteger("-b8adf1378a6eb73409fa6c9c637d", 16) }, + new BigInteger[]{ + new BigInteger("1243ae1b4d71613bc9f780a03690e", 16), + new BigInteger("6b8cf07d4ca75c88957d9d670591", 16) }, + new BigInteger("6b8cf07d4ca75c88957d9d67059037a4", 16), + new BigInteger("b8adf1378a6eb73409fa6c9c637ba7f5", 16), + 240); + + ECCurve curve = ConfigureCurveGlv(new FpCurve(p, a, b, n, h), glv); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "A1455B334DF099DF30FC28A169A467E9E47075A90F7E650EB6B7A45C" + + "7E089FED7FBA344282CAFBD6F7E319F7C0B0BD59E2CA4BDB556D61A5")); + + return new X9ECParameters(curve, G, n, h, S); + } + } + + /* + * secp224r1 + */ + internal class Secp224r1Holder + : X9ECParametersHolder + { + private Secp224r1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new Secp224r1Holder(); + + protected override 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.One; + + ECCurve curve = ConfigureCurve(new FpCurve(p, a, b, n, h)); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "B70E0CBD6BB4BF7F321390B94A03C1D356C21122343280D6115C1D21" + + "BD376388B5F723FB4C22DFE6CD4375A05A07476444D5819985007E34")); + + return new X9ECParameters(curve, G, n, h, S); + } + } + + /* + * secp256k1 + */ + internal class Secp256k1Holder + : X9ECParametersHolder + { + private Secp256k1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new Secp256k1Holder(); + + protected override X9ECParameters CreateParameters() + { + // p = 2^256 - 2^32 - 2^9 - 2^8 - 2^7 - 2^6 - 2^4 - 1 + BigInteger p = FromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F"); + BigInteger a = BigInteger.Zero; + BigInteger b = BigInteger.ValueOf(7); + byte[] S = null; + BigInteger n = FromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"); + BigInteger h = BigInteger.One; + + GlvTypeBParameters glv = new GlvTypeBParameters( + new BigInteger("7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee", 16), + new BigInteger("5363ad4cc05c30e0a5261c028812645a122e22ea20816678df02967c1b23bd72", 16), + new BigInteger[]{ + new BigInteger("3086d221a7d46bcde86c90e49284eb15", 16), + new BigInteger("-e4437ed6010e88286f547fa90abfe4c3", 16) }, + new BigInteger[]{ + new BigInteger("114ca50f7a8e2f3f657c1108d9d44cfd8", 16), + new BigInteger("3086d221a7d46bcde86c90e49284eb15", 16) }, + new BigInteger("3086d221a7d46bcde86c90e49284eb153dab", 16), + new BigInteger("e4437ed6010e88286f547fa90abfe4c42212", 16), + 272); + + ECCurve curve = ConfigureCurveGlv(new FpCurve(p, a, b, n, h), glv); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798" + + "483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8")); + + return new X9ECParameters(curve, G, n, h, S); + } + } + + /* + * secp256r1 + */ + internal class Secp256r1Holder + : X9ECParametersHolder + { + private Secp256r1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new Secp256r1Holder(); + + protected override 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.One; + + ECCurve curve = ConfigureCurve(new FpCurve(p, a, b, n, h)); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296" + + "4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5")); + + return new X9ECParameters(curve, G, n, h, S); + } + } + + /* + * secp384r1 + */ + internal class Secp384r1Holder + : X9ECParametersHolder + { + private Secp384r1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new Secp384r1Holder(); + + protected override 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.One; + + ECCurve curve = ConfigureCurve(new FpCurve(p, a, b, n, h)); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "AA87CA22BE8B05378EB1C71EF320AD746E1D3B628BA79B9859F741E082542A385502F25DBF55296C3A545E3872760AB7" + + "3617DE4A96262C6F5D9E98BF9292DC29F8F41DBD289A147CE9DA3113B5F0B8C00A60B1CE1D7E819D7A431D7C90EA0E5F")); + + return new X9ECParameters(curve, G, n, h, S); + } + } + + /* + * secp521r1 + */ + internal class Secp521r1Holder + : X9ECParametersHolder + { + private Secp521r1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new Secp521r1Holder(); + + protected override 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.One; + + ECCurve curve = ConfigureCurve(new FpCurve(p, a, b, n, h)); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "00C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66" + + "011839296A789A3BC0045C8A5FB42C7D1BD998F54449579B446817AFBD17273E662C97EE72995EF42640C550B9013FAD0761353C7086A272C24088BE94769FD16650")); + + return new X9ECParameters(curve, G, n, h, S); + } + } + + /* + * sect113r1 + */ + internal class Sect113r1Holder + : X9ECParametersHolder + { + private Sect113r1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new Sect113r1Holder(); + + private const int m = 113; + private const int k = 9; + + protected override X9ECParameters CreateParameters() + { + 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 F2mCurve(m, k, a, b, n, h); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "009D73616F35F4AB1407D73562C10F" + + "00A52830277958EE84D1315ED31886")); + + return new X9ECParameters(curve, G, n, h, S); + } + } + + /* + * sect113r2 + */ + internal class Sect113r2Holder + : X9ECParametersHolder + { + private Sect113r2Holder() {} + + internal static readonly X9ECParametersHolder Instance = new Sect113r2Holder(); + + private const int m = 113; + private const int k = 9; + + protected override X9ECParameters CreateParameters() + { + 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 F2mCurve(m, k, a, b, n, h); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "01A57A6A7B26CA5EF52FCDB8164797" + + "00B3ADC94ED1FE674C06E695BABA1D")); + + return new X9ECParameters(curve, G, n, h, S); + } + } + + /* + * sect131r1 + */ + internal class Sect131r1Holder + : X9ECParametersHolder + { + private Sect131r1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new Sect131r1Holder(); + + private const int m = 131; + private const int k1 = 2; + private const int k2 = 3; + private const int k3 = 8; + + protected override X9ECParameters CreateParameters() + { + 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 F2mCurve(m, k1, k2, k3, a, b, n, h); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "0081BAF91FDF9833C40F9C181343638399" + + "078C6E7EA38C001F73C8134B1B4EF9E150")); + + return new X9ECParameters(curve, G, n, h, S); + } + } + + /* + * sect131r2 + */ + internal class Sect131r2Holder + : X9ECParametersHolder + { + private Sect131r2Holder() {} + + internal static readonly X9ECParametersHolder Instance = new Sect131r2Holder(); + + private const int m = 131; + private const int k1 = 2; + private const int k2 = 3; + private const int k3 = 8; + + protected override X9ECParameters CreateParameters() + { + 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 F2mCurve(m, k1, k2, k3, a, b, n, h); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "0356DCD8F2F95031AD652D23951BB366A8" + + "0648F06D867940A5366D9E265DE9EB240F")); + + return new X9ECParameters(curve, G, n, h, S); + } + } + + /* + * sect163k1 + */ + internal class Sect163k1Holder + : X9ECParametersHolder + { + private Sect163k1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new Sect163k1Holder(); + + private const int m = 163; + private const int k1 = 3; + private const int k2 = 6; + private const int k3 = 7; + + protected override X9ECParameters CreateParameters() + { + BigInteger a = BigInteger.One; + BigInteger b = BigInteger.One; + byte[] S = null; + BigInteger n = FromHex("04000000000000000000020108A2E0CC0D99F8A5EF"); + BigInteger h = BigInteger.ValueOf(2); + + ECCurve curve = new F2mCurve(m, k1, k2, k3, a, b, n, h); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "02FE13C0537BBC11ACAA07D793DE4E6D5E5C94EEE8" + + "0289070FB05D38FF58321F2E800536D538CCDAA3D9")); + + return new X9ECParameters(curve, G, n, h, S); + } + } + + /* + * sect163r1 + */ + internal class Sect163r1Holder + : X9ECParametersHolder + { + private Sect163r1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new Sect163r1Holder(); + + private const int m = 163; + private const int k1 = 3; + private const int k2 = 6; + private const int k3 = 7; + + protected override X9ECParameters CreateParameters() + { + 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 F2mCurve(m, k1, k2, k3, a, b, n, h); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "0369979697AB43897789566789567F787A7876A654" + + "00435EDB42EFAFB2989D51FEFCE3C80988F41FF883")); + + return new X9ECParameters(curve, G, n, h, S); + } + } + + /* + * sect163r2 + */ + internal class Sect163r2Holder + : X9ECParametersHolder + { + private Sect163r2Holder() {} + + internal static readonly X9ECParametersHolder Instance = new Sect163r2Holder(); + + private const int m = 163; + private const int k1 = 3; + private const int k2 = 6; + private const int k3 = 7; + + protected override X9ECParameters CreateParameters() + { + BigInteger a = BigInteger.One; + BigInteger b = FromHex("020A601907B8C953CA1481EB10512F78744A3205FD"); + byte[] S = Hex.Decode("85E25BFE5C86226CDB12016F7553F9D0E693A268"); + BigInteger n = FromHex("040000000000000000000292FE77E70C12A4234C33"); + BigInteger h = BigInteger.ValueOf(2); + + ECCurve curve = new F2mCurve(m, k1, k2, k3, a, b, n, h); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "03F0EBA16286A2D57EA0991168D4994637E8343E36" + + "00D51FBC6C71A0094FA2CDD545B11C5C0C797324F1")); + + return new X9ECParameters(curve, G, n, h, S); + } + } + + /* + * sect193r1 + */ + internal class Sect193r1Holder + : X9ECParametersHolder + { + private Sect193r1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new Sect193r1Holder(); + + private const int m = 193; + private const int k = 15; + + protected override X9ECParameters CreateParameters() + { + 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 F2mCurve(m, k, a, b, n, h); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "01F481BC5F0FF84A74AD6CDF6FDEF4BF6179625372D8C0C5E1" + + "0025E399F2903712CCF3EA9E3A1AD17FB0B3201B6AF7CE1B05")); + + return new X9ECParameters(curve, G, n, h, S); + } + } + + /* + * sect193r2 + */ + internal class Sect193r2Holder + : X9ECParametersHolder + { + private Sect193r2Holder() {} + + internal static readonly X9ECParametersHolder Instance = new Sect193r2Holder(); + + private const int m = 193; + private const int k = 15; + + protected override X9ECParameters CreateParameters() + { + 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 F2mCurve(m, k, a, b, n, h); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "00D9B67D192E0367C803F39E1A7E82CA14A651350AAE617E8F" + + "01CE94335607C304AC29E7DEFBD9CA01F596F927224CDECF6C")); + + return new X9ECParameters(curve, G, n, h, S); + } + } + + /* + * sect233k1 + */ + internal class Sect233k1Holder + : X9ECParametersHolder + { + private Sect233k1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new Sect233k1Holder(); + + private const int m = 233; + private const int k = 74; + + protected override X9ECParameters CreateParameters() + { + BigInteger a = BigInteger.Zero; + BigInteger b = BigInteger.One; + byte[] S = null; + BigInteger n = FromHex("8000000000000000000000000000069D5BB915BCD46EFB1AD5F173ABDF"); + BigInteger h = BigInteger.ValueOf(4); + + ECCurve curve = new F2mCurve(m, k, a, b, n, h); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "017232BA853A7E731AF129F22FF4149563A419C26BF50A4C9D6EEFAD6126" + + "01DB537DECE819B7F70F555A67C427A8CD9BF18AEB9B56E0C11056FAE6A3")); + + return new X9ECParameters(curve, G, n, h, S); + } + } + + /* + * sect233r1 + */ + internal class Sect233r1Holder + : X9ECParametersHolder + { + private Sect233r1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new Sect233r1Holder(); + + private const int m = 233; + private const int k = 74; + + protected override X9ECParameters CreateParameters() + { + BigInteger a = BigInteger.One; + BigInteger b = FromHex("0066647EDE6C332C7F8C0923BB58213B333B20E9CE4281FE115F7D8F90AD"); + byte[] S = Hex.Decode("74D59FF07F6B413D0EA14B344B20A2DB049B50C3"); + BigInteger n = FromHex("01000000000000000000000000000013E974E72F8A6922031D2603CFE0D7"); + BigInteger h = BigInteger.ValueOf(2); + + ECCurve curve = new F2mCurve(m, k, a, b, n, h); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "00FAC9DFCBAC8313BB2139F1BB755FEF65BC391F8B36F8F8EB7371FD558B" + + "01006A08A41903350678E58528BEBF8A0BEFF867A7CA36716F7E01F81052")); + + return new X9ECParameters(curve, G, n, h, S); + } + } + + /* + * sect239k1 + */ + internal class Sect239k1Holder + : X9ECParametersHolder + { + private Sect239k1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new Sect239k1Holder(); + + private const int m = 239; + private const int k = 158; + + protected override X9ECParameters CreateParameters() + { + BigInteger a = BigInteger.Zero; + BigInteger b = BigInteger.One; + byte[] S = null; + BigInteger n = FromHex("2000000000000000000000000000005A79FEC67CB6E91F1C1DA800E478A5"); + BigInteger h = BigInteger.ValueOf(4); + + ECCurve curve = new F2mCurve(m, k, a, b, n, h); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "29A0B6A887A983E9730988A68727A8B2D126C44CC2CC7B2A6555193035DC" + + "76310804F12E549BDB011C103089E73510ACB275FC312A5DC6B76553F0CA")); + + return new X9ECParameters(curve, G, n, h, S); + } + } + + /* + * sect283k1 + */ + internal class Sect283k1Holder + : X9ECParametersHolder + { + private Sect283k1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new Sect283k1Holder(); + + private const int m = 283; + private const int k1 = 5; + private const int k2 = 7; + private const int k3 = 12; + + protected override X9ECParameters CreateParameters() + { + BigInteger a = BigInteger.Zero; + BigInteger b = BigInteger.One; + byte[] S = null; + BigInteger n = FromHex("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE9AE2ED07577265DFF7F94451E061E163C61"); + BigInteger h = BigInteger.ValueOf(4); + + ECCurve curve = new F2mCurve(m, k1, k2, k3, a, b, n, h); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "0503213F78CA44883F1A3B8162F188E553CD265F23C1567A16876913B0C2AC2458492836" + + "01CCDA380F1C9E318D90F95D07E5426FE87E45C0E8184698E45962364E34116177DD2259")); + + return new X9ECParameters(curve, G, n, h, S); + } + } + + /* + * sect283r1 + */ + internal class Sect283r1Holder + : X9ECParametersHolder + { + private Sect283r1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new Sect283r1Holder(); + + private const int m = 283; + private const int k1 = 5; + private const int k2 = 7; + private const int k3 = 12; + + protected override X9ECParameters CreateParameters() + { + BigInteger a = BigInteger.One; + BigInteger b = FromHex("027B680AC8B8596DA5A4AF8A19A0303FCA97FD7645309FA2A581485AF6263E313B79A2F5"); + byte[] S = Hex.Decode("77E2B07370EB0F832A6DD5B62DFC88CD06BB84BE"); + BigInteger n = FromHex("03FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEF90399660FC938A90165B042A7CEFADB307"); + BigInteger h = BigInteger.ValueOf(2); + + ECCurve curve = new F2mCurve(m, k1, k2, k3, a, b, n, h); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "05F939258DB7DD90E1934F8C70B0DFEC2EED25B8557EAC9C80E2E198F8CDBECD86B12053" + + "03676854FE24141CB98FE6D4B20D02B4516FF702350EDDB0826779C813F0DF45BE8112F4")); + + return new X9ECParameters(curve, G, n, h, S); + } + } + + /* + * sect409k1 + */ + internal class Sect409k1Holder + : X9ECParametersHolder + { + private Sect409k1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new Sect409k1Holder(); + + private const int m = 409; + private const int k = 87; + + protected override X9ECParameters CreateParameters() + { + BigInteger a = BigInteger.Zero; + BigInteger b = BigInteger.One; + byte[] S = null; + BigInteger n = FromHex("7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE5F83B2D4EA20400EC4557D5ED3E3E7CA5B4B5C83B8E01E5FCF"); + BigInteger h = BigInteger.ValueOf(4); + + ECCurve curve = new F2mCurve(m, k, a, b, n, h); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "0060F05F658F49C1AD3AB1890F7184210EFD0987E307C84C27ACCFB8F9F67CC2C460189EB5AAAA62EE222EB1B35540CFE9023746" + + "01E369050B7C4E42ACBA1DACBF04299C3460782F918EA427E6325165E9EA10E3DA5F6C42E9C55215AA9CA27A5863EC48D8E0286B")); + + return new X9ECParameters(curve, G, n, h, S); + } + } + + /* + * sect409r1 + */ + internal class Sect409r1Holder + : X9ECParametersHolder + { + private Sect409r1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new Sect409r1Holder(); + + private const int m = 409; + private const int k = 87; + + protected override X9ECParameters CreateParameters() + { + BigInteger a = BigInteger.One; + BigInteger b = FromHex("0021A5C2C8EE9FEB5C4B9A753B7B476B7FD6422EF1F3DD674761FA99D6AC27C8A9A197B272822F6CD57A55AA4F50AE317B13545F"); + byte[] S = Hex.Decode("4099B5A457F9D69F79213D094C4BCD4D4262210B"); + BigInteger n = FromHex("010000000000000000000000000000000000000000000000000001E2AAD6A612F33307BE5FA47C3C9E052F838164CD37D9A21173"); + BigInteger h = BigInteger.ValueOf(2); + + ECCurve curve = new F2mCurve(m, k, a, b, n, h); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "015D4860D088DDB3496B0C6064756260441CDE4AF1771D4DB01FFE5B34E59703DC255A868A1180515603AEAB60794E54BB7996A7" + + "0061B1CFAB6BE5F32BBFA78324ED106A7636B9C5A7BD198D0158AA4F5488D08F38514F1FDF4B4F40D2181B3681C364BA0273C706")); + + return new X9ECParameters(curve, G, n, h, S); + } + } + + /* + * sect571k1 + */ + internal class Sect571k1Holder + : X9ECParametersHolder + { + private Sect571k1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new Sect571k1Holder(); + + private const int m = 571; + private const int k1 = 2; + private const int k2 = 5; + private const int k3 = 10; + + protected override X9ECParameters CreateParameters() + { + BigInteger a = BigInteger.Zero; + BigInteger b = BigInteger.One; + byte[] S = null; + BigInteger n = FromHex("020000000000000000000000000000000000000000000000000000000000000000000000131850E1F19A63E4B391A8DB917F4138B630D84BE5D639381E91DEB45CFE778F637C1001"); + BigInteger h = BigInteger.ValueOf(4); + + ECCurve curve = new F2mCurve(m, k1, k2, k3, a, b, n, h); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "026EB7A859923FBC82189631F8103FE4AC9CA2970012D5D46024804801841CA44370958493B205E647DA304DB4CEB08CBBD1BA39494776FB988B47174DCA88C7E2945283A01C8972" + + "0349DC807F4FBF374F4AEADE3BCA95314DD58CEC9F307A54FFC61EFC006D8A2C9D4979C0AC44AEA74FBEBBB9F772AEDCB620B01A7BA7AF1B320430C8591984F601CD4C143EF1C7A3")); + + return new X9ECParameters(curve, G, n, h, S); + } + } + + /* + * sect571r1 + */ + internal class Sect571r1Holder + : X9ECParametersHolder + { + private Sect571r1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new Sect571r1Holder(); + + private const int m = 571; + private const int k1 = 2; + private const int k2 = 5; + private const int k3 = 10; + + protected override X9ECParameters CreateParameters() + { + BigInteger a = BigInteger.One; + BigInteger b = FromHex("02F40E7E2221F295DE297117B7F3D62F5C6A97FFCB8CEFF1CD6BA8CE4A9A18AD84FFABBD8EFA59332BE7AD6756A66E294AFD185A78FF12AA520E4DE739BACA0C7FFEFF7F2955727A"); + byte[] S = Hex.Decode("2AA058F73A0E33AB486B0F610410C53A7F132310"); + BigInteger n = FromHex("03FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE661CE18FF55987308059B186823851EC7DD9CA1161DE93D5174D66E8382E9BB2FE84E47"); + BigInteger h = BigInteger.ValueOf(2); + + ECCurve curve = new F2mCurve(m, k1, k2, k3, a, b, n, h); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "0303001D34B856296C16C0D40D3CD7750A93D1D2955FA80AA5F40FC8DB7B2ABDBDE53950F4C0D293CDD711A35B67FB1499AE60038614F1394ABFA3B4C850D927E1E7769C8EEC2D19" + + "037BF27342DA639B6DCCFFFEB73D69D78C6C27A6009CBBCA1980F8533921E8A684423E43BAB08A576291AF8F461BB2A8B3531D2F0485C19B16E2F1516E23DD3C1A4827AF1B8AC15B")); + + return new X9ECParameters(curve, G, n, h, S); + } + } + + + private static readonly IDictionary objIds = Platform.CreateHashtable(); + private static readonly IDictionary curves = Platform.CreateHashtable(); + private static readonly IDictionary names = Platform.CreateHashtable(); + + private static void DefineCurve( + string name, + DerObjectIdentifier oid, + X9ECParametersHolder holder) + { + objIds.Add(Platform.ToUpperInvariant(name), oid); + names.Add(oid, name); + curves.Add(oid, holder); + } + + static SecNamedCurves() + { + DefineCurve("secp112r1", SecObjectIdentifiers.SecP112r1, Secp112r1Holder.Instance); + DefineCurve("secp112r2", SecObjectIdentifiers.SecP112r2, Secp112r2Holder.Instance); + DefineCurve("secp128r1", SecObjectIdentifiers.SecP128r1, Secp128r1Holder.Instance); + DefineCurve("secp128r2", SecObjectIdentifiers.SecP128r2, Secp128r2Holder.Instance); + DefineCurve("secp160k1", SecObjectIdentifiers.SecP160k1, Secp160k1Holder.Instance); + DefineCurve("secp160r1", SecObjectIdentifiers.SecP160r1, Secp160r1Holder.Instance); + DefineCurve("secp160r2", SecObjectIdentifiers.SecP160r2, Secp160r2Holder.Instance); + DefineCurve("secp192k1", SecObjectIdentifiers.SecP192k1, Secp192k1Holder.Instance); + DefineCurve("secp192r1", SecObjectIdentifiers.SecP192r1, Secp192r1Holder.Instance); + DefineCurve("secp224k1", SecObjectIdentifiers.SecP224k1, Secp224k1Holder.Instance); + DefineCurve("secp224r1", SecObjectIdentifiers.SecP224r1, Secp224r1Holder.Instance); + DefineCurve("secp256k1", SecObjectIdentifiers.SecP256k1, Secp256k1Holder.Instance); + DefineCurve("secp256r1", SecObjectIdentifiers.SecP256r1, Secp256r1Holder.Instance); + DefineCurve("secp384r1", SecObjectIdentifiers.SecP384r1, Secp384r1Holder.Instance); + DefineCurve("secp521r1", SecObjectIdentifiers.SecP521r1, Secp521r1Holder.Instance); + + DefineCurve("sect113r1", SecObjectIdentifiers.SecT113r1, Sect113r1Holder.Instance); + DefineCurve("sect113r2", SecObjectIdentifiers.SecT113r2, Sect113r2Holder.Instance); + DefineCurve("sect131r1", SecObjectIdentifiers.SecT131r1, Sect131r1Holder.Instance); + DefineCurve("sect131r2", SecObjectIdentifiers.SecT131r2, Sect131r2Holder.Instance); + DefineCurve("sect163k1", SecObjectIdentifiers.SecT163k1, Sect163k1Holder.Instance); + DefineCurve("sect163r1", SecObjectIdentifiers.SecT163r1, Sect163r1Holder.Instance); + DefineCurve("sect163r2", SecObjectIdentifiers.SecT163r2, Sect163r2Holder.Instance); + DefineCurve("sect193r1", SecObjectIdentifiers.SecT193r1, Sect193r1Holder.Instance); + DefineCurve("sect193r2", SecObjectIdentifiers.SecT193r2, Sect193r2Holder.Instance); + DefineCurve("sect233k1", SecObjectIdentifiers.SecT233k1, Sect233k1Holder.Instance); + DefineCurve("sect233r1", SecObjectIdentifiers.SecT233r1, Sect233r1Holder.Instance); + DefineCurve("sect239k1", SecObjectIdentifiers.SecT239k1, Sect239k1Holder.Instance); + DefineCurve("sect283k1", SecObjectIdentifiers.SecT283k1, Sect283k1Holder.Instance); + DefineCurve("sect283r1", SecObjectIdentifiers.SecT283r1, Sect283r1Holder.Instance); + DefineCurve("sect409k1", SecObjectIdentifiers.SecT409k1, Sect409k1Holder.Instance); + DefineCurve("sect409r1", SecObjectIdentifiers.SecT409r1, Sect409r1Holder.Instance); + DefineCurve("sect571k1", SecObjectIdentifiers.SecT571k1, Sect571k1Holder.Instance); + DefineCurve("sect571r1", SecObjectIdentifiers.SecT571r1, Sect571r1Holder.Instance); + } + + public static X9ECParameters GetByName( + string name) + { + DerObjectIdentifier oid = GetOid(name); + return oid == null ? null : GetByOid(oid); + } + + /** + * 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[oid]; + return holder == null ? null : holder.Parameters; + } + + /** + * 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[Platform.ToUpperInvariant(name)]; + } + + /** + * return the named curve name represented by the given object identifier. + */ + public static string GetName( + DerObjectIdentifier oid) + { + return (string)names[oid]; + } + + /** + * returns an enumeration containing the name strings for curves + * contained in this structure. + */ + public static IEnumerable Names + { + get { return new EnumerableProxy(names.Values); } + } + } +} diff --git a/bc-sharp-crypto/src/asn1/sec/SECObjectIdentifiers.cs b/bc-sharp-crypto/src/asn1/sec/SECObjectIdentifiers.cs new file mode 100644 index 0000000..afc10e1 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/sec/SECObjectIdentifiers.cs @@ -0,0 +1,52 @@ +using System; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X9; + +namespace Org.BouncyCastle.Asn1.Sec +{ + public abstract class SecObjectIdentifiers + { + /** + * EllipticCurve OBJECT IDENTIFIER ::= { + * iso(1) identified-organization(3) certicom(132) curve(0) + * } + */ + public static readonly DerObjectIdentifier EllipticCurve = new DerObjectIdentifier("1.3.132.0"); + + public static readonly DerObjectIdentifier SecT163k1 = new DerObjectIdentifier(EllipticCurve + ".1"); + public static readonly DerObjectIdentifier SecT163r1 = new DerObjectIdentifier(EllipticCurve + ".2"); + public static readonly DerObjectIdentifier SecT239k1 = new DerObjectIdentifier(EllipticCurve + ".3"); + public static readonly DerObjectIdentifier SecT113r1 = new DerObjectIdentifier(EllipticCurve + ".4"); + public static readonly DerObjectIdentifier SecT113r2 = new DerObjectIdentifier(EllipticCurve + ".5"); + public static readonly DerObjectIdentifier SecP112r1 = new DerObjectIdentifier(EllipticCurve + ".6"); + public static readonly DerObjectIdentifier SecP112r2 = new DerObjectIdentifier(EllipticCurve + ".7"); + public static readonly DerObjectIdentifier SecP160r1 = new DerObjectIdentifier(EllipticCurve + ".8"); + public static readonly DerObjectIdentifier SecP160k1 = new DerObjectIdentifier(EllipticCurve + ".9"); + public static readonly DerObjectIdentifier SecP256k1 = new DerObjectIdentifier(EllipticCurve + ".10"); + public static readonly DerObjectIdentifier SecT163r2 = new DerObjectIdentifier(EllipticCurve + ".15"); + public static readonly DerObjectIdentifier SecT283k1 = new DerObjectIdentifier(EllipticCurve + ".16"); + public static readonly DerObjectIdentifier SecT283r1 = new DerObjectIdentifier(EllipticCurve + ".17"); + public static readonly DerObjectIdentifier SecT131r1 = new DerObjectIdentifier(EllipticCurve + ".22"); + public static readonly DerObjectIdentifier SecT131r2 = new DerObjectIdentifier(EllipticCurve + ".23"); + public static readonly DerObjectIdentifier SecT193r1 = new DerObjectIdentifier(EllipticCurve + ".24"); + public static readonly DerObjectIdentifier SecT193r2 = new DerObjectIdentifier(EllipticCurve + ".25"); + public static readonly DerObjectIdentifier SecT233k1 = new DerObjectIdentifier(EllipticCurve + ".26"); + public static readonly DerObjectIdentifier SecT233r1 = new DerObjectIdentifier(EllipticCurve + ".27"); + public static readonly DerObjectIdentifier SecP128r1 = new DerObjectIdentifier(EllipticCurve + ".28"); + public static readonly DerObjectIdentifier SecP128r2 = new DerObjectIdentifier(EllipticCurve + ".29"); + public static readonly DerObjectIdentifier SecP160r2 = new DerObjectIdentifier(EllipticCurve + ".30"); + public static readonly DerObjectIdentifier SecP192k1 = new DerObjectIdentifier(EllipticCurve + ".31"); + public static readonly DerObjectIdentifier SecP224k1 = new DerObjectIdentifier(EllipticCurve + ".32"); + public static readonly DerObjectIdentifier SecP224r1 = new DerObjectIdentifier(EllipticCurve + ".33"); + public static readonly DerObjectIdentifier SecP384r1 = new DerObjectIdentifier(EllipticCurve + ".34"); + public static readonly DerObjectIdentifier SecP521r1 = new DerObjectIdentifier(EllipticCurve + ".35"); + public static readonly DerObjectIdentifier SecT409k1 = new DerObjectIdentifier(EllipticCurve + ".36"); + public static readonly DerObjectIdentifier SecT409r1 = new DerObjectIdentifier(EllipticCurve + ".37"); + public static readonly DerObjectIdentifier SecT571k1 = new DerObjectIdentifier(EllipticCurve + ".38"); + public static readonly DerObjectIdentifier SecT571r1 = new DerObjectIdentifier(EllipticCurve + ".39"); + + public static readonly DerObjectIdentifier SecP192r1 = X9ObjectIdentifiers.Prime192v1; + public static readonly DerObjectIdentifier SecP256r1 = X9ObjectIdentifiers.Prime256v1; + } +} \ No newline at end of file diff --git a/bc-sharp-crypto/src/asn1/smime/SMIMEAttributes.cs b/bc-sharp-crypto/src/asn1/smime/SMIMEAttributes.cs new file mode 100644 index 0000000..e154e5e --- /dev/null +++ b/bc-sharp-crypto/src/asn1/smime/SMIMEAttributes.cs @@ -0,0 +1,11 @@ +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Pkcs; + +namespace Org.BouncyCastle.Asn1.Smime +{ + public abstract class SmimeAttributes + { + public static readonly DerObjectIdentifier SmimeCapabilities = PkcsObjectIdentifiers.Pkcs9AtSmimeCapabilities; + public static readonly DerObjectIdentifier EncrypKeyPref = PkcsObjectIdentifiers.IdAAEncrypKeyPref; + } +} diff --git a/bc-sharp-crypto/src/asn1/smime/SMIMECapabilities.cs b/bc-sharp-crypto/src/asn1/smime/SMIMECapabilities.cs new file mode 100644 index 0000000..5bf48f3 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/smime/SMIMECapabilities.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Nist; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.X509; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Smime +{ + /** + * Handler class for dealing with S/MIME Capabilities + */ + public class SmimeCapabilities + : Asn1Encodable + { + /** + * general preferences + */ + public static readonly DerObjectIdentifier PreferSignedData = PkcsObjectIdentifiers.PreferSignedData; + public static readonly DerObjectIdentifier CannotDecryptAny = PkcsObjectIdentifiers.CannotDecryptAny; + public static readonly DerObjectIdentifier SmimeCapabilitesVersions = PkcsObjectIdentifiers.SmimeCapabilitiesVersions; + + /** + * encryption algorithms preferences + */ + public static readonly DerObjectIdentifier Aes256Cbc = NistObjectIdentifiers.IdAes256Cbc; + public static readonly DerObjectIdentifier Aes192Cbc = NistObjectIdentifiers.IdAes192Cbc; + public static readonly DerObjectIdentifier Aes128Cbc = NistObjectIdentifiers.IdAes128Cbc; + public static readonly DerObjectIdentifier IdeaCbc = new DerObjectIdentifier("1.3.6.1.4.1.188.7.1.1.2"); + public static readonly DerObjectIdentifier Cast5Cbc = new DerObjectIdentifier("1.2.840.113533.7.66.10"); + public static readonly DerObjectIdentifier DesCbc = new DerObjectIdentifier("1.3.14.3.2.7"); + public static readonly DerObjectIdentifier DesEde3Cbc = PkcsObjectIdentifiers.DesEde3Cbc; + public static readonly DerObjectIdentifier RC2Cbc = PkcsObjectIdentifiers.RC2Cbc; + + private Asn1Sequence capabilities; + + /** + * return an Attr object from the given object. + * + * @param o the object we want converted. + * @exception ArgumentException if the object cannot be converted. + */ + public static SmimeCapabilities GetInstance( + object obj) + { + if (obj == null || obj is SmimeCapabilities) + { + return (SmimeCapabilities) obj; + } + + if (obj is Asn1Sequence) + { + return new SmimeCapabilities((Asn1Sequence) obj); + } + + if (obj is AttributeX509) + { + return new SmimeCapabilities( + (Asn1Sequence)(((AttributeX509) obj).AttrValues[0])); + } + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + public SmimeCapabilities( + Asn1Sequence seq) + { + capabilities = seq; + } + +#if !(SILVERLIGHT || PORTABLE) + [Obsolete("Use 'GetCapabilitiesForOid' instead")] + public ArrayList GetCapabilities( + DerObjectIdentifier capability) + { + ArrayList list = new ArrayList(); + DoGetCapabilitiesForOid(capability, list); + return list; + } +#endif + + /** + * returns an ArrayList 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 IList GetCapabilitiesForOid( + DerObjectIdentifier capability) + { + IList list = Platform.CreateArrayList(); + DoGetCapabilitiesForOid(capability, list); + return list; + } + + private void DoGetCapabilitiesForOid(DerObjectIdentifier capability, IList list) + { + if (capability == null) + { + foreach (object o in capabilities) + { + SmimeCapability cap = SmimeCapability.GetInstance(o); + + list.Add(cap); + } + } + else + { + foreach (object o in capabilities) + { + SmimeCapability cap = SmimeCapability.GetInstance(o); + + if (capability.Equals(cap.CapabilityID)) + { + list.Add(cap); + } + } + } + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *
+         * SMIMECapabilities ::= Sequence OF SMIMECapability
+         * 
+ */ + public override Asn1Object ToAsn1Object() + { + return capabilities; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/smime/SMIMECapabilitiesAttribute.cs b/bc-sharp-crypto/src/asn1/smime/SMIMECapabilitiesAttribute.cs new file mode 100644 index 0000000..310c478 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/smime/SMIMECapabilitiesAttribute.cs @@ -0,0 +1,16 @@ +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; + +namespace Org.BouncyCastle.Asn1.Smime +{ + public class SmimeCapabilitiesAttribute + : AttributeX509 + { + public SmimeCapabilitiesAttribute( + SmimeCapabilityVector capabilities) + : base(SmimeAttributes.SmimeCapabilities, + new DerSet(new DerSequence(capabilities.ToAsn1EncodableVector()))) + { + } + } +} diff --git a/bc-sharp-crypto/src/asn1/smime/SMIMECapability.cs b/bc-sharp-crypto/src/asn1/smime/SMIMECapability.cs new file mode 100644 index 0000000..5709cb8 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/smime/SMIMECapability.cs @@ -0,0 +1,101 @@ +using System; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Pkcs; + +namespace Org.BouncyCastle.Asn1.Smime +{ + public class SmimeCapability + : Asn1Encodable + { + /** + * general preferences + */ + public static readonly DerObjectIdentifier PreferSignedData = PkcsObjectIdentifiers.PreferSignedData; + public static readonly DerObjectIdentifier CannotDecryptAny = PkcsObjectIdentifiers.CannotDecryptAny; + public static readonly DerObjectIdentifier SmimeCapabilitiesVersions = PkcsObjectIdentifiers.SmimeCapabilitiesVersions; + + /** + * encryption algorithms preferences + */ + public static readonly DerObjectIdentifier DesCbc = new DerObjectIdentifier("1.3.14.3.2.7"); + public static readonly DerObjectIdentifier DesEde3Cbc = PkcsObjectIdentifiers.DesEde3Cbc; + public static readonly DerObjectIdentifier RC2Cbc = PkcsObjectIdentifiers.RC2Cbc; + + private DerObjectIdentifier capabilityID; + private Asn1Object parameters; + + public SmimeCapability( + Asn1Sequence seq) + { + capabilityID = (DerObjectIdentifier) seq[0].ToAsn1Object(); + + if (seq.Count > 1) + { + parameters = seq[1].ToAsn1Object(); + } + } + + public SmimeCapability( + DerObjectIdentifier capabilityID, + Asn1Encodable parameters) + { + if (capabilityID == null) + throw new ArgumentNullException("capabilityID"); + + this.capabilityID = capabilityID; + + if (parameters != null) + { + this.parameters = parameters.ToAsn1Object(); + } + } + + public static SmimeCapability GetInstance( + object obj) + { + if (obj == null || obj is SmimeCapability) + { + return (SmimeCapability) obj; + } + + if (obj is Asn1Sequence) + { + return new SmimeCapability((Asn1Sequence) obj); + } + + throw new ArgumentException("Invalid SmimeCapability"); + } + + public DerObjectIdentifier CapabilityID + { + get { return capabilityID; } + } + + public Asn1Object Parameters + { + get { return parameters; } + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *
+         * SMIMECapability ::= Sequence {
+         *     capabilityID OBJECT IDENTIFIER,
+         *     parameters ANY DEFINED BY capabilityID OPTIONAL
+         * }
+         * 
+ */ + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(capabilityID); + + if (parameters != null) + { + v.Add(parameters); + } + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/smime/SMIMECapabilityVector.cs b/bc-sharp-crypto/src/asn1/smime/SMIMECapabilityVector.cs new file mode 100644 index 0000000..842825b --- /dev/null +++ b/bc-sharp-crypto/src/asn1/smime/SMIMECapabilityVector.cs @@ -0,0 +1,37 @@ +using Org.BouncyCastle.Asn1; + +namespace Org.BouncyCastle.Asn1.Smime +{ + /** + * Handler for creating a vector S/MIME Capabilities + */ + public class SmimeCapabilityVector + { + private readonly Asn1EncodableVector capabilities = new Asn1EncodableVector(); + + public void AddCapability( + DerObjectIdentifier capability) + { + capabilities.Add(new DerSequence(capability)); + } + + public void AddCapability( + DerObjectIdentifier capability, + int value) + { + capabilities.Add(new DerSequence(capability, new DerInteger(value))); + } + + public void AddCapability( + DerObjectIdentifier capability, + Asn1Encodable parameters) + { + capabilities.Add(new DerSequence(capability, parameters)); + } + + public Asn1EncodableVector ToAsn1EncodableVector() + { + return capabilities; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/smime/SMIMEEncryptionKeyPreferenceAttribute.cs b/bc-sharp-crypto/src/asn1/smime/SMIMEEncryptionKeyPreferenceAttribute.cs new file mode 100644 index 0000000..19c5fd7 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/smime/SMIMEEncryptionKeyPreferenceAttribute.cs @@ -0,0 +1,44 @@ +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Cms; +using Org.BouncyCastle.Asn1.X509; + +namespace Org.BouncyCastle.Asn1.Smime +{ + /** + * The SmimeEncryptionKeyPreference object. + *
+     * SmimeEncryptionKeyPreference ::= CHOICE {
+     *     issuerAndSerialNumber   [0] IssuerAndSerialNumber,
+     *     receipentKeyId          [1] RecipientKeyIdentifier,
+     *     subjectAltKeyIdentifier [2] SubjectKeyIdentifier
+     * }
+     * 
+ */ + public class SmimeEncryptionKeyPreferenceAttribute + : AttributeX509 + { + public SmimeEncryptionKeyPreferenceAttribute( + IssuerAndSerialNumber issAndSer) + : base(SmimeAttributes.EncrypKeyPref, + new DerSet(new DerTaggedObject(false, 0, issAndSer))) + { + } + + public SmimeEncryptionKeyPreferenceAttribute( + RecipientKeyIdentifier rKeyID) + : base(SmimeAttributes.EncrypKeyPref, + new DerSet(new DerTaggedObject(false, 1, rKeyID))) + { + } + + /** + * @param sKeyId the subjectKeyIdentifier value (normally the X.509 one) + */ + public SmimeEncryptionKeyPreferenceAttribute( + Asn1OctetString sKeyID) + : base(SmimeAttributes.EncrypKeyPref, + new DerSet(new DerTaggedObject(false, 2, sKeyID))) + { + } + } +} diff --git a/bc-sharp-crypto/src/asn1/teletrust/TeleTrusTNamedCurves.cs b/bc-sharp-crypto/src/asn1/teletrust/TeleTrusTNamedCurves.cs new file mode 100644 index 0000000..9a82db3 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/teletrust/TeleTrusTNamedCurves.cs @@ -0,0 +1,470 @@ +using System.Collections; + +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Math.EC; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Asn1.TeleTrust +{ + /** + * 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 + { + private static ECCurve ConfigureCurve(ECCurve curve) + { + return curve; + } + + internal class BrainpoolP160r1Holder + : X9ECParametersHolder + { + private BrainpoolP160r1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new BrainpoolP160r1Holder(); + + protected override X9ECParameters CreateParameters() + { + BigInteger n = new BigInteger("E95E4A5F737059DC60DF5991D45029409E60FC09", 16); + BigInteger h = new BigInteger("01", 16); + + ECCurve curve = ConfigureCurve(new FpCurve( + new BigInteger("E95E4A5F737059DC60DFC7AD95B3D8139515620F", 16), // q + new BigInteger("340E7BE2A280EB74E2BE61BADA745D97E8F7C300", 16), // a + new BigInteger("1E589A8595423412134FAA2DBDEC95C8D8675E58", 16), // b + n, h)); + + return new X9ECParameters( + curve, + new X9ECPoint(curve, Hex.Decode("04BED5AF16EA3F6A4F62938C4631EB5AF7BDBCDBC31667CB477A1A8EC338F94741669C976316DA6321")), // G + n, h); + } + } + + internal class BrainpoolP160t1Holder + : X9ECParametersHolder + { + private BrainpoolP160t1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new BrainpoolP160t1Holder(); + + protected override X9ECParameters CreateParameters() + { + BigInteger n = new BigInteger("E95E4A5F737059DC60DF5991D45029409E60FC09", 16); + BigInteger h = new BigInteger("01", 16); + + ECCurve curve = ConfigureCurve(new FpCurve( + // new BigInteger("24DBFF5DEC9B986BBFE5295A29BFBAE45E0F5D0B", 16), // Z + new BigInteger("E95E4A5F737059DC60DFC7AD95B3D8139515620F", 16), // q + new BigInteger("E95E4A5F737059DC60DFC7AD95B3D8139515620C", 16), // a' + new BigInteger("7A556B6DAE535B7B51ED2C4D7DAA7A0B5C55F380", 16), // b' + n, h)); + + return new X9ECParameters( + curve, + new X9ECPoint(curve, Hex.Decode("04B199B13B9B34EFC1397E64BAEB05ACC265FF2378ADD6718B7C7C1961F0991B842443772152C9E0AD")), // G + n, h); + } + } + + internal class BrainpoolP192r1Holder + : X9ECParametersHolder + { + private BrainpoolP192r1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new BrainpoolP192r1Holder(); + + protected override X9ECParameters CreateParameters() + { + BigInteger n = new BigInteger("C302F41D932A36CDA7A3462F9E9E916B5BE8F1029AC4ACC1", 16); + BigInteger h = new BigInteger("01", 16); + + ECCurve curve = ConfigureCurve(new FpCurve( + new BigInteger("C302F41D932A36CDA7A3463093D18DB78FCE476DE1A86297", 16), // q + new BigInteger("6A91174076B1E0E19C39C031FE8685C1CAE040E5C69A28EF", 16), // a + new BigInteger("469A28EF7C28CCA3DC721D044F4496BCCA7EF4146FBF25C9", 16), // b + n, h)); + + return new X9ECParameters( + curve, + new X9ECPoint(curve, Hex.Decode("04C0A0647EAAB6A48753B033C56CB0F0900A2F5C4853375FD614B690866ABD5BB88B5F4828C1490002E6773FA2FA299B8F")), // G + n, h); + } + } + + internal class BrainpoolP192t1Holder + : X9ECParametersHolder + { + private BrainpoolP192t1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new BrainpoolP192t1Holder(); + + protected override X9ECParameters CreateParameters() + { + BigInteger n = new BigInteger("C302F41D932A36CDA7A3462F9E9E916B5BE8F1029AC4ACC1", 16); + BigInteger h = new BigInteger("01", 16); + + ECCurve curve = ConfigureCurve(new FpCurve( + //new BigInteger("1B6F5CC8DB4DC7AF19458A9CB80DC2295E5EB9C3732104CB") //Z + new BigInteger("C302F41D932A36CDA7A3463093D18DB78FCE476DE1A86297", 16), // q + new BigInteger("C302F41D932A36CDA7A3463093D18DB78FCE476DE1A86294", 16), // a' + new BigInteger("13D56FFAEC78681E68F9DEB43B35BEC2FB68542E27897B79", 16), // b' + n, h)); + + return new X9ECParameters( + curve, + new X9ECPoint(curve, Hex.Decode("043AE9E58C82F63C30282E1FE7BBF43FA72C446AF6F4618129097E2C5667C2223A902AB5CA449D0084B7E5B3DE7CCC01C9")), // G' + n, h); + } + } + + internal class BrainpoolP224r1Holder + : X9ECParametersHolder + { + private BrainpoolP224r1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new BrainpoolP224r1Holder(); + + protected override X9ECParameters CreateParameters() + { + BigInteger n = new BigInteger("D7C134AA264366862A18302575D0FB98D116BC4B6DDEBCA3A5A7939F", 16); + BigInteger h = new BigInteger("01", 16); + + ECCurve curve = ConfigureCurve(new FpCurve( + new BigInteger("D7C134AA264366862A18302575D1D787B09F075797DA89F57EC8C0FF", 16), // q + new BigInteger("68A5E62CA9CE6C1C299803A6C1530B514E182AD8B0042A59CAD29F43", 16), // a + new BigInteger("2580F63CCFE44138870713B1A92369E33E2135D266DBB372386C400B", 16), // b + n, h)); + + return new X9ECParameters( + curve, + new X9ECPoint(curve, Hex.Decode("040D9029AD2C7E5CF4340823B2A87DC68C9E4CE3174C1E6EFDEE12C07D58AA56F772C0726F24C6B89E4ECDAC24354B9E99CAA3F6D3761402CD")), // G + n, h); + } + } + + internal class BrainpoolP224t1Holder + : X9ECParametersHolder + { + private BrainpoolP224t1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new BrainpoolP224t1Holder(); + + protected override X9ECParameters CreateParameters() + { + BigInteger n = new BigInteger("D7C134AA264366862A18302575D0FB98D116BC4B6DDEBCA3A5A7939F", 16); + BigInteger h = new BigInteger("01", 16); + + ECCurve curve = ConfigureCurve(new FpCurve( + //new BigInteger("2DF271E14427A346910CF7A2E6CFA7B3F484E5C2CCE1C8B730E28B3F") //Z + new BigInteger("D7C134AA264366862A18302575D1D787B09F075797DA89F57EC8C0FF", 16), // q + new BigInteger("D7C134AA264366862A18302575D1D787B09F075797DA89F57EC8C0FC", 16), // a' + new BigInteger("4B337D934104CD7BEF271BF60CED1ED20DA14C08B3BB64F18A60888D", 16), // b' + n, h)); + + return new X9ECParameters( + curve, + new X9ECPoint(curve, Hex.Decode("046AB1E344CE25FF3896424E7FFE14762ECB49F8928AC0C76029B4D5800374E9F5143E568CD23F3F4D7C0D4B1E41C8CC0D1C6ABD5F1A46DB4C")), // G' + n, h); + } + } + + internal class BrainpoolP256r1Holder + : X9ECParametersHolder + { + private BrainpoolP256r1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new BrainpoolP256r1Holder(); + + protected override X9ECParameters CreateParameters() + { + BigInteger n = new BigInteger("A9FB57DBA1EEA9BC3E660A909D838D718C397AA3B561A6F7901E0E82974856A7", 16); + BigInteger h = new BigInteger("01", 16); + + ECCurve curve = ConfigureCurve(new FpCurve( + new BigInteger("A9FB57DBA1EEA9BC3E660A909D838D726E3BF623D52620282013481D1F6E5377", 16), // q + new BigInteger("7D5A0975FC2C3057EEF67530417AFFE7FB8055C126DC5C6CE94A4B44F330B5D9", 16), // a + new BigInteger("26DC5C6CE94A4B44F330B5D9BBD77CBF958416295CF7E1CE6BCCDC18FF8C07B6", 16), // b + n, h)); + + return new X9ECParameters( + curve, + new X9ECPoint(curve, Hex.Decode("048BD2AEB9CB7E57CB2C4B482FFC81B7AFB9DE27E1E3BD23C23A4453BD9ACE3262547EF835C3DAC4FD97F8461A14611DC9C27745132DED8E545C1D54C72F046997")), // G + n, h); + } + } + + internal class BrainpoolP256t1Holder + : X9ECParametersHolder + { + private BrainpoolP256t1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new BrainpoolP256t1Holder(); + + protected override X9ECParameters CreateParameters() + { + BigInteger n = new BigInteger("A9FB57DBA1EEA9BC3E660A909D838D718C397AA3B561A6F7901E0E82974856A7", 16); + BigInteger h = new BigInteger("01", 16); + + ECCurve curve = ConfigureCurve(new FpCurve( + //new BigInteger("3E2D4BD9597B58639AE7AA669CAB9837CF5CF20A2C852D10F655668DFC150EF0") //Z + new BigInteger("A9FB57DBA1EEA9BC3E660A909D838D726E3BF623D52620282013481D1F6E5377", 16), // q + new BigInteger("A9FB57DBA1EEA9BC3E660A909D838D726E3BF623D52620282013481D1F6E5374", 16), // a' + new BigInteger("662C61C430D84EA4FE66A7733D0B76B7BF93EBC4AF2F49256AE58101FEE92B04", 16), // b' + n, h)); + + return new X9ECParameters( + curve, + new X9ECPoint(curve, Hex.Decode("04A3E8EB3CC1CFE7B7732213B23A656149AFA142C47AAFBC2B79A191562E1305F42D996C823439C56D7F7B22E14644417E69BCB6DE39D027001DABE8F35B25C9BE")), // G' + n, h); + } + } + + internal class BrainpoolP320r1Holder + : X9ECParametersHolder + { + private BrainpoolP320r1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new BrainpoolP320r1Holder(); + + protected override X9ECParameters CreateParameters() + { + BigInteger n = new BigInteger("D35E472036BC4FB7E13C785ED201E065F98FCFA5B68F12A32D482EC7EE8658E98691555B44C59311", 16); + BigInteger h = new BigInteger("01", 16); + + ECCurve curve = ConfigureCurve(new FpCurve( + new BigInteger("D35E472036BC4FB7E13C785ED201E065F98FCFA6F6F40DEF4F92B9EC7893EC28FCD412B1F1B32E27", 16), // q + new BigInteger("3EE30B568FBAB0F883CCEBD46D3F3BB8A2A73513F5EB79DA66190EB085FFA9F492F375A97D860EB4", 16), // a + new BigInteger("520883949DFDBC42D3AD198640688A6FE13F41349554B49ACC31DCCD884539816F5EB4AC8FB1F1A6", 16), // b + n, h)); + + return new X9ECParameters( + curve, + new X9ECPoint(curve, Hex.Decode("0443BD7E9AFB53D8B85289BCC48EE5BFE6F20137D10A087EB6E7871E2A10A599C710AF8D0D39E2061114FDD05545EC1CC8AB4093247F77275E0743FFED117182EAA9C77877AAAC6AC7D35245D1692E8EE1")), // G + n, h); + } + } + + internal class BrainpoolP320t1Holder + : X9ECParametersHolder + { + private BrainpoolP320t1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new BrainpoolP320t1Holder(); + + protected override X9ECParameters CreateParameters() + { + BigInteger n = new BigInteger("D35E472036BC4FB7E13C785ED201E065F98FCFA5B68F12A32D482EC7EE8658E98691555B44C59311", 16); + BigInteger h = new BigInteger("01", 16); + + ECCurve curve = ConfigureCurve(new FpCurve( + //new BigInteger("15F75CAF668077F7E85B42EB01F0A81FF56ECD6191D55CB82B7D861458A18FEFC3E5AB7496F3C7B1") //Z + new BigInteger("D35E472036BC4FB7E13C785ED201E065F98FCFA6F6F40DEF4F92B9EC7893EC28FCD412B1F1B32E27", 16), // q + new BigInteger("D35E472036BC4FB7E13C785ED201E065F98FCFA6F6F40DEF4F92B9EC7893EC28FCD412B1F1B32E24", 16), // a' + new BigInteger("A7F561E038EB1ED560B3D147DB782013064C19F27ED27C6780AAF77FB8A547CEB5B4FEF422340353", 16), // b' + n, h)); + + return new X9ECParameters( + curve, + new X9ECPoint(curve, Hex.Decode("04925BE9FB01AFC6FB4D3E7D4990010F813408AB106C4F09CB7EE07868CC136FFF3357F624A21BED5263BA3A7A27483EBF6671DBEF7ABB30EBEE084E58A0B077AD42A5A0989D1EE71B1B9BC0455FB0D2C3")), // G' + n, h); + } + } + + internal class BrainpoolP384r1Holder + : X9ECParametersHolder + { + private BrainpoolP384r1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new BrainpoolP384r1Holder(); + + protected override X9ECParameters CreateParameters() + { + BigInteger n = new BigInteger("8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B31F166E6CAC0425A7CF3AB6AF6B7FC3103B883202E9046565", 16); + BigInteger h = new BigInteger("01", 16); + + ECCurve curve = ConfigureCurve(new FpCurve( + new BigInteger("8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B412B1DA197FB71123ACD3A729901D1A71874700133107EC53", 16), // q + new BigInteger("7BC382C63D8C150C3C72080ACE05AFA0C2BEA28E4FB22787139165EFBA91F90F8AA5814A503AD4EB04A8C7DD22CE2826", 16), // a + new BigInteger("4A8C7DD22CE28268B39B55416F0447C2FB77DE107DCD2A62E880EA53EEB62D57CB4390295DBC9943AB78696FA504C11", 16), // b + n, h)); + + return new X9ECParameters( + curve, + new X9ECPoint(curve, Hex.Decode("041D1C64F068CF45FFA2A63A81B7C13F6B8847A3E77EF14FE3DB7FCAFE0CBD10E8E826E03436D646AAEF87B2E247D4AF1E8ABE1D7520F9C2A45CB1EB8E95CFD55262B70B29FEEC5864E19C054FF99129280E4646217791811142820341263C5315")), // G + n, h); + } + } + + internal class BrainpoolP384t1Holder + : X9ECParametersHolder + { + private BrainpoolP384t1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new BrainpoolP384t1Holder(); + + protected override X9ECParameters CreateParameters() + { + BigInteger n = new BigInteger("8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B31F166E6CAC0425A7CF3AB6AF6B7FC3103B883202E9046565", 16); + BigInteger h = new BigInteger("01", 16); + + ECCurve curve = ConfigureCurve(new FpCurve( + //new BigInteger("41DFE8DD399331F7166A66076734A89CD0D2BCDB7D068E44E1F378F41ECBAE97D2D63DBC87BCCDDCCC5DA39E8589291C") //Z + new BigInteger("8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B412B1DA197FB71123ACD3A729901D1A71874700133107EC53", 16), // q + new BigInteger("8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B412B1DA197FB71123ACD3A729901D1A71874700133107EC50", 16), // a' + new BigInteger("7F519EADA7BDA81BD826DBA647910F8C4B9346ED8CCDC64E4B1ABD11756DCE1D2074AA263B88805CED70355A33B471EE", 16), // b' + n, h)); + + return new X9ECParameters( + curve, + new X9ECPoint(curve, Hex.Decode("0418DE98B02DB9A306F2AFCD7235F72A819B80AB12EBD653172476FECD462AABFFC4FF191B946A5F54D8D0AA2F418808CC25AB056962D30651A114AFD2755AD336747F93475B7A1FCA3B88F2B6A208CCFE469408584DC2B2912675BF5B9E582928")), // G' + n, h); + } + } + + internal class BrainpoolP512r1Holder + : X9ECParametersHolder + { + private BrainpoolP512r1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new BrainpoolP512r1Holder(); + + protected override X9ECParameters CreateParameters() + { + BigInteger n = new BigInteger("AADD9DB8DBE9C48B3FD4E6AE33C9FC07CB308DB3B3C9D20ED6639CCA70330870553E5C414CA92619418661197FAC10471DB1D381085DDADDB58796829CA90069", 16); + BigInteger h = new BigInteger("01", 16); + + ECCurve curve = ConfigureCurve(new FpCurve( + new BigInteger("AADD9DB8DBE9C48B3FD4E6AE33C9FC07CB308DB3B3C9D20ED6639CCA703308717D4D9B009BC66842AECDA12AE6A380E62881FF2F2D82C68528AA6056583A48F3", 16), // q + new BigInteger("7830A3318B603B89E2327145AC234CC594CBDD8D3DF91610A83441CAEA9863BC2DED5D5AA8253AA10A2EF1C98B9AC8B57F1117A72BF2C7B9E7C1AC4D77FC94CA", 16), // a + new BigInteger("3DF91610A83441CAEA9863BC2DED5D5AA8253AA10A2EF1C98B9AC8B57F1117A72BF2C7B9E7C1AC4D77FC94CADC083E67984050B75EBAE5DD2809BD638016F723", 16), // b + n, h)); + + return new X9ECParameters( + curve, + new X9ECPoint(curve, Hex.Decode("0481AEE4BDD82ED9645A21322E9C4C6A9385ED9F70B5D916C1B43B62EEF4D0098EFF3B1F78E2D0D48D50D1687B93B97D5F7C6D5047406A5E688B352209BCB9F8227DDE385D566332ECC0EABFA9CF7822FDF209F70024A57B1AA000C55B881F8111B2DCDE494A5F485E5BCA4BD88A2763AED1CA2B2FA8F0540678CD1E0F3AD80892")), // G + n, h); + } + } + + internal class BrainpoolP512t1Holder + : X9ECParametersHolder + { + private BrainpoolP512t1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new BrainpoolP512t1Holder(); + + protected override X9ECParameters CreateParameters() + { + BigInteger n = new BigInteger("AADD9DB8DBE9C48B3FD4E6AE33C9FC07CB308DB3B3C9D20ED6639CCA70330870553E5C414CA92619418661197FAC10471DB1D381085DDADDB58796829CA90069", 16); + BigInteger h = new BigInteger("01", 16); + + ECCurve curve = ConfigureCurve(new FpCurve( + //new BigInteger("12EE58E6764838B69782136F0F2D3BA06E27695716054092E60A80BEDB212B64E585D90BCE13761F85C3F1D2A64E3BE8FEA2220F01EBA5EEB0F35DBD29D922AB") //Z + new BigInteger("AADD9DB8DBE9C48B3FD4E6AE33C9FC07CB308DB3B3C9D20ED6639CCA703308717D4D9B009BC66842AECDA12AE6A380E62881FF2F2D82C68528AA6056583A48F3", 16), // q + new BigInteger("AADD9DB8DBE9C48B3FD4E6AE33C9FC07CB308DB3B3C9D20ED6639CCA703308717D4D9B009BC66842AECDA12AE6A380E62881FF2F2D82C68528AA6056583A48F0", 16), // a' + new BigInteger("7CBBBCF9441CFAB76E1890E46884EAE321F70C0BCB4981527897504BEC3E36A62BCDFA2304976540F6450085F2DAE145C22553B465763689180EA2571867423E", 16), // b' + n, h)); + + return new X9ECParameters( + curve, + new X9ECPoint(curve, Hex.Decode("04640ECE5C12788717B9C1BA06CBC2A6FEBA85842458C56DDE9DB1758D39C0313D82BA51735CDB3EA499AA77A7D6943A64F7A3F25FE26F06B51BAA2696FA9035DA5B534BD595F5AF0FA2C892376C84ACE1BB4E3019B71634C01131159CAE03CEE9D9932184BEEF216BD71DF2DADF86A627306ECFF96DBB8BACE198B61E00F8B332")), // G' + n, h); + } + } + + + private static readonly IDictionary objIds = Platform.CreateHashtable(); + private static readonly IDictionary curves = Platform.CreateHashtable(); + private static readonly IDictionary names = Platform.CreateHashtable(); + + private static void DefineCurve( + string name, + DerObjectIdentifier oid, + X9ECParametersHolder holder) + { + objIds.Add(Platform.ToUpperInvariant(name), oid); + names.Add(oid, name); + curves.Add(oid, holder); + } + + static TeleTrusTNamedCurves() + { + DefineCurve("brainpoolP160r1", TeleTrusTObjectIdentifiers.BrainpoolP160R1, BrainpoolP160r1Holder.Instance); + DefineCurve("brainpoolP160t1", TeleTrusTObjectIdentifiers.BrainpoolP160T1, BrainpoolP160t1Holder.Instance); + DefineCurve("brainpoolP192r1", TeleTrusTObjectIdentifiers.BrainpoolP192R1, BrainpoolP192r1Holder.Instance); + DefineCurve("brainpoolP192t1", TeleTrusTObjectIdentifiers.BrainpoolP192T1, BrainpoolP192t1Holder.Instance); + DefineCurve("brainpoolP224r1", TeleTrusTObjectIdentifiers.BrainpoolP224R1, BrainpoolP224r1Holder.Instance); + DefineCurve("brainpoolP224t1", TeleTrusTObjectIdentifiers.BrainpoolP224T1, BrainpoolP224t1Holder.Instance); + DefineCurve("brainpoolP256r1", TeleTrusTObjectIdentifiers.BrainpoolP256R1, BrainpoolP256r1Holder.Instance); + DefineCurve("brainpoolP256t1", TeleTrusTObjectIdentifiers.BrainpoolP256T1, BrainpoolP256t1Holder.Instance); + DefineCurve("brainpoolP320r1", TeleTrusTObjectIdentifiers.BrainpoolP320R1, BrainpoolP320r1Holder.Instance); + DefineCurve("brainpoolP320t1", TeleTrusTObjectIdentifiers.BrainpoolP320T1, BrainpoolP320t1Holder.Instance); + DefineCurve("brainpoolP384r1", TeleTrusTObjectIdentifiers.BrainpoolP384R1, BrainpoolP384r1Holder.Instance); + DefineCurve("brainpoolP384t1", TeleTrusTObjectIdentifiers.BrainpoolP384T1, BrainpoolP384t1Holder.Instance); + DefineCurve("brainpoolP512r1", TeleTrusTObjectIdentifiers.BrainpoolP512R1, BrainpoolP512r1Holder.Instance); + DefineCurve("brainpoolP512t1", TeleTrusTObjectIdentifiers.BrainpoolP512T1, BrainpoolP512t1Holder.Instance); + } + + public static X9ECParameters GetByName( + string name) + { + DerObjectIdentifier oid = GetOid(name); + return oid == null ? null : GetByOid(oid); + } + + /** + * 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[oid]; + return holder == null ? null : holder.Parameters; + } + + /** + * 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[Platform.ToUpperInvariant(name)]; + } + + /** + * return the named curve name represented by the given object identifier. + */ + public static string GetName( + DerObjectIdentifier oid) + { + return (string)names[oid]; + } + + /** + * returns an enumeration containing the name strings for curves + * contained in this structure. + */ + public static IEnumerable Names + { + get { return new EnumerableProxy(names.Values); } + } + + public static DerObjectIdentifier GetOid( + short curvesize, + bool twisted) + { + return GetOid("brainpoolP" + curvesize + (twisted ? "t" : "r") + "1"); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/teletrust/TeleTrusTObjectIdentifiers.cs b/bc-sharp-crypto/src/asn1/teletrust/TeleTrusTObjectIdentifiers.cs new file mode 100644 index 0000000..56e7084 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/teletrust/TeleTrusTObjectIdentifiers.cs @@ -0,0 +1,45 @@ +namespace Org.BouncyCastle.Asn1.TeleTrust +{ + public sealed class TeleTrusTObjectIdentifiers + { + private TeleTrusTObjectIdentifiers() + { + } + + public static readonly DerObjectIdentifier TeleTrusTAlgorithm = new DerObjectIdentifier("1.3.36.3"); + + public static readonly DerObjectIdentifier RipeMD160 = new DerObjectIdentifier(TeleTrusTAlgorithm + ".2.1"); + public static readonly DerObjectIdentifier RipeMD128 = new DerObjectIdentifier(TeleTrusTAlgorithm + ".2.2"); + public static readonly DerObjectIdentifier RipeMD256 = new DerObjectIdentifier(TeleTrusTAlgorithm + ".2.3"); + + public static readonly DerObjectIdentifier TeleTrusTRsaSignatureAlgorithm = new DerObjectIdentifier(TeleTrusTAlgorithm + ".3.1"); + + public static readonly DerObjectIdentifier RsaSignatureWithRipeMD160 = new DerObjectIdentifier(TeleTrusTRsaSignatureAlgorithm + ".2"); + public static readonly DerObjectIdentifier RsaSignatureWithRipeMD128 = new DerObjectIdentifier(TeleTrusTRsaSignatureAlgorithm + ".3"); + public static readonly DerObjectIdentifier RsaSignatureWithRipeMD256 = new DerObjectIdentifier(TeleTrusTRsaSignatureAlgorithm + ".4"); + + public static readonly DerObjectIdentifier ECSign = new DerObjectIdentifier(TeleTrusTAlgorithm + ".3.2"); + + public static readonly DerObjectIdentifier ECSignWithSha1 = new DerObjectIdentifier(ECSign + ".1"); + public static readonly DerObjectIdentifier ECSignWithRipeMD160 = new DerObjectIdentifier(ECSign + ".2"); + + public static readonly DerObjectIdentifier EccBrainpool = new DerObjectIdentifier(TeleTrusTAlgorithm + ".3.2.8"); + public static readonly DerObjectIdentifier EllipticCurve = new DerObjectIdentifier(EccBrainpool + ".1"); + public static readonly DerObjectIdentifier VersionOne = new DerObjectIdentifier(EllipticCurve + ".1"); + + public static readonly DerObjectIdentifier BrainpoolP160R1 = new DerObjectIdentifier(VersionOne + ".1"); + public static readonly DerObjectIdentifier BrainpoolP160T1 = new DerObjectIdentifier(VersionOne + ".2"); + public static readonly DerObjectIdentifier BrainpoolP192R1 = new DerObjectIdentifier(VersionOne + ".3"); + public static readonly DerObjectIdentifier BrainpoolP192T1 = new DerObjectIdentifier(VersionOne + ".4"); + public static readonly DerObjectIdentifier BrainpoolP224R1 = new DerObjectIdentifier(VersionOne + ".5"); + public static readonly DerObjectIdentifier BrainpoolP224T1 = new DerObjectIdentifier(VersionOne + ".6"); + public static readonly DerObjectIdentifier BrainpoolP256R1 = new DerObjectIdentifier(VersionOne + ".7"); + public static readonly DerObjectIdentifier BrainpoolP256T1 = new DerObjectIdentifier(VersionOne + ".8"); + public static readonly DerObjectIdentifier BrainpoolP320R1 = new DerObjectIdentifier(VersionOne + ".9"); + public static readonly DerObjectIdentifier BrainpoolP320T1 = new DerObjectIdentifier(VersionOne + ".10"); + public static readonly DerObjectIdentifier BrainpoolP384R1 = new DerObjectIdentifier(VersionOne + ".11"); + public static readonly DerObjectIdentifier BrainpoolP384T1 = new DerObjectIdentifier(VersionOne + ".12"); + public static readonly DerObjectIdentifier BrainpoolP512R1 = new DerObjectIdentifier(VersionOne + ".13"); + public static readonly DerObjectIdentifier BrainpoolP512T1 = new DerObjectIdentifier(VersionOne + ".14"); + } +} diff --git a/bc-sharp-crypto/src/asn1/tsp/Accuracy.cs b/bc-sharp-crypto/src/asn1/tsp/Accuracy.cs new file mode 100644 index 0000000..9f2c7e8 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/tsp/Accuracy.cs @@ -0,0 +1,151 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Tsp +{ + public class Accuracy + : Asn1Encodable + { + private readonly DerInteger seconds; + private readonly DerInteger millis; + private readonly DerInteger micros; + + // constants + protected const int MinMillis = 1; + protected const int MaxMillis = 999; + protected const int MinMicros = 1; + protected const int MaxMicros = 999; + + public Accuracy( + DerInteger seconds, + DerInteger millis, + DerInteger micros) + { + //Verifications + if (millis != null + && (millis.Value.IntValue < MinMillis + || millis.Value.IntValue > MaxMillis)) + { + throw new ArgumentException( + "Invalid millis field : not in (1..999)"); + } + + if (micros != null + && (micros.Value.IntValue < MinMicros + || micros.Value.IntValue > MaxMicros)) + { + throw new ArgumentException( + "Invalid micros field : not in (1..999)"); + } + + this.seconds = seconds; + this.millis = millis; + this.micros = micros; + } + + private Accuracy( + Asn1Sequence seq) + { + for (int i = 0; i < seq.Count; ++i) + { + // seconds + if (seq[i] is DerInteger) + { + seconds = (DerInteger) seq[i]; + } + else if (seq[i] is DerTaggedObject) + { + DerTaggedObject extra = (DerTaggedObject) seq[i]; + + switch (extra.TagNo) + { + case 0: + millis = DerInteger.GetInstance(extra, false); + if (millis.Value.IntValue < MinMillis + || millis.Value.IntValue > MaxMillis) + { + throw new ArgumentException( + "Invalid millis field : not in (1..999)."); + } + break; + case 1: + micros = DerInteger.GetInstance(extra, false); + if (micros.Value.IntValue < MinMicros + || micros.Value.IntValue > MaxMicros) + { + throw new ArgumentException( + "Invalid micros field : not in (1..999)."); + } + break; + default: + throw new ArgumentException("Invalig tag number"); + } + } + } + } + + public static Accuracy GetInstance( + object o) + { + if (o == null || o is Accuracy) + { + return (Accuracy) o; + } + + if (o is Asn1Sequence) + { + return new Accuracy((Asn1Sequence) o); + } + + throw new ArgumentException( + "Unknown object in 'Accuracy' factory: " + Platform.GetTypeName(o)); + } + + public DerInteger Seconds + { + get { return seconds; } + } + + public DerInteger Millis + { + get { return millis; } + } + + public DerInteger Micros + { + get { return micros; } + } + + /** + *
+		 * Accuracy ::= SEQUENCE {
+		 *             seconds        INTEGER              OPTIONAL,
+		 *             millis     [0] INTEGER  (1..999)    OPTIONAL,
+		 *             micros     [1] INTEGER  (1..999)    OPTIONAL
+		 *             }
+		 * 
+ */ + public override Asn1Object 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/bc-sharp-crypto/src/asn1/tsp/MessageImprint.cs b/bc-sharp-crypto/src/asn1/tsp/MessageImprint.cs new file mode 100644 index 0000000..44ef7d1 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/tsp/MessageImprint.cs @@ -0,0 +1,75 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Tsp +{ + public class MessageImprint + : Asn1Encodable + { + private readonly AlgorithmIdentifier hashAlgorithm; + private readonly byte[] hashedMessage; + + /** + * @param o + * @return a MessageImprint object. + */ + public static MessageImprint GetInstance( + object o) + { + if (o == null || o is MessageImprint) + { + return (MessageImprint) o; + } + + if (o is Asn1Sequence) + { + return new MessageImprint((Asn1Sequence) o); + } + + throw new ArgumentException( + "Unknown object in 'MessageImprint' factory: " + Platform.GetTypeName(o)); + } + + private MessageImprint( + Asn1Sequence seq) + { + if (seq.Count != 2) + throw new ArgumentException("Wrong number of elements in sequence", "seq"); + + this.hashAlgorithm = AlgorithmIdentifier.GetInstance(seq[0]); + this.hashedMessage = Asn1OctetString.GetInstance(seq[1]).GetOctets(); + } + + public MessageImprint( + AlgorithmIdentifier hashAlgorithm, + byte[] hashedMessage) + { + this.hashAlgorithm = hashAlgorithm; + this.hashedMessage = hashedMessage; + } + + public AlgorithmIdentifier HashAlgorithm + { + get { return hashAlgorithm; } + } + + public byte[] GetHashedMessage() + { + return hashedMessage; + } + + /** + *
+		 *    MessageImprint ::= SEQUENCE  {
+		 *       hashAlgorithm                AlgorithmIdentifier,
+		 *       hashedMessage                OCTET STRING  }
+		 * 
+ */ + public override Asn1Object ToAsn1Object() + { + return new DerSequence(hashAlgorithm, new DerOctetString(hashedMessage)); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/tsp/TSTInfo.cs b/bc-sharp-crypto/src/asn1/tsp/TSTInfo.cs new file mode 100644 index 0000000..89f3e8b --- /dev/null +++ b/bc-sharp-crypto/src/asn1/tsp/TSTInfo.cs @@ -0,0 +1,250 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Tsp +{ + public class TstInfo + : Asn1Encodable + { + private readonly DerInteger version; + private readonly DerObjectIdentifier tsaPolicyId; + private readonly MessageImprint messageImprint; + private readonly DerInteger serialNumber; + private readonly DerGeneralizedTime genTime; + private readonly Accuracy accuracy; + private readonly DerBoolean ordering; + private readonly DerInteger nonce; + private readonly GeneralName tsa; + private readonly X509Extensions extensions; + + public static TstInfo GetInstance( + object o) + { + if (o == null || o is TstInfo) + { + return (TstInfo) o; + } + + if (o is Asn1Sequence) + { + return new TstInfo((Asn1Sequence) o); + } + + if (o is Asn1OctetString) + { + try + { + byte[] octets = ((Asn1OctetString)o).GetOctets(); + return GetInstance(Asn1Object.FromByteArray(octets)); + } + catch (IOException) + { + throw new ArgumentException( + "Bad object format in 'TstInfo' factory."); + } + } + + throw new ArgumentException( + "Unknown object in 'TstInfo' factory: " + Platform.GetTypeName(o)); + } + + private TstInfo( + Asn1Sequence seq) + { + IEnumerator e = seq.GetEnumerator(); + + // version + e.MoveNext(); + version = DerInteger.GetInstance(e.Current); + + // tsaPolicy + e.MoveNext(); + tsaPolicyId = DerObjectIdentifier.GetInstance(e.Current); + + // messageImprint + e.MoveNext(); + messageImprint = MessageImprint.GetInstance(e.Current); + + // serialNumber + e.MoveNext(); + serialNumber = DerInteger.GetInstance(e.Current); + + // genTime + e.MoveNext(); + genTime = DerGeneralizedTime.GetInstance(e.Current); + + // default for ordering + ordering = DerBoolean.False; + + while (e.MoveNext()) + { + Asn1Object o = (Asn1Object) e.Current; + + if (o is Asn1TaggedObject) + { + DerTaggedObject tagged = (DerTaggedObject) o; + + switch (tagged.TagNo) + { + case 0: + tsa = GeneralName.GetInstance(tagged, true); + break; + case 1: + extensions = X509Extensions.GetInstance(tagged, false); + break; + default: + throw new ArgumentException("Unknown tag value " + tagged.TagNo); + } + } + + if (o is DerSequence) + { + accuracy = Accuracy.GetInstance(o); + } + + if (o is DerBoolean) + { + ordering = DerBoolean.GetInstance(o); + } + + if (o is 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) + { + this.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 DerInteger Version + { + get { return version; } + } + + public MessageImprint MessageImprint + { + get { return messageImprint; } + } + + public DerObjectIdentifier Policy + { + get { return tsaPolicyId; } + } + + public DerInteger SerialNumber + { + get { return serialNumber; } + } + + public Accuracy Accuracy + { + get { return accuracy; } + } + + public DerGeneralizedTime GenTime + { + get { return genTime; } + } + + public DerBoolean Ordering + { + get { return ordering; } + } + + public DerInteger Nonce + { + get { return nonce; } + } + + public GeneralName Tsa + { + get { return tsa; } + } + + public X509Extensions Extensions + { + get { 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 override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector( + version, tsaPolicyId, messageImprint, serialNumber, genTime); + + if (accuracy != null) + { + v.Add(accuracy); + } + + if (ordering != null && ordering.IsTrue) + { + v.Add(ordering); + } + + if (nonce != null) + { + v.Add(nonce); + } + + if (tsa != null) + { + v.Add(new DerTaggedObject(true, 0, tsa)); + } + + if (extensions != null) + { + v.Add(new DerTaggedObject(false, 1, extensions)); + } + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/tsp/TimeStampReq.cs b/bc-sharp-crypto/src/asn1/tsp/TimeStampReq.cs new file mode 100644 index 0000000..5b05f33 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/tsp/TimeStampReq.cs @@ -0,0 +1,165 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Tsp +{ + public class TimeStampReq + : Asn1Encodable + { + private readonly DerInteger version; + private readonly MessageImprint messageImprint; + private readonly DerObjectIdentifier tsaPolicy; + private readonly DerInteger nonce; + private readonly DerBoolean certReq; + private readonly X509Extensions extensions; + + public static TimeStampReq GetInstance( + object o) + { + if (o == null || o is TimeStampReq) + { + return (TimeStampReq) o; + } + + if (o is Asn1Sequence) + { + return new TimeStampReq((Asn1Sequence) o); + } + + throw new ArgumentException( + "Unknown object in 'TimeStampReq' factory: " + Platform.GetTypeName(o)); + } + + private TimeStampReq( + Asn1Sequence seq) + { + int nbObjects = seq.Count; + int seqStart = 0; + + // version + version = DerInteger.GetInstance(seq[seqStart++]); + + // messageImprint + messageImprint = MessageImprint.GetInstance(seq[seqStart++]); + + for (int opt = seqStart; opt < nbObjects; opt++) + { + // tsaPolicy + if (seq[opt] is DerObjectIdentifier) + { + tsaPolicy = DerObjectIdentifier.GetInstance(seq[opt]); + } + // nonce + else if (seq[opt] is DerInteger) + { + nonce = DerInteger.GetInstance(seq[opt]); + } + // certReq + else if (seq[opt] is DerBoolean) + { + certReq = DerBoolean.GetInstance(seq[opt]); + } + // extensions + else if (seq[opt] is Asn1TaggedObject) + { + Asn1TaggedObject tagged = (Asn1TaggedObject) seq[opt]; + if (tagged.TagNo == 0) + { + extensions = X509Extensions.GetInstance(tagged, false); + } + } + } + } + + public TimeStampReq( + MessageImprint messageImprint, + DerObjectIdentifier tsaPolicy, + DerInteger nonce, + DerBoolean certReq, + X509Extensions extensions) + { + // default + this.version = new DerInteger(1); + + this.messageImprint = messageImprint; + this.tsaPolicy = tsaPolicy; + this.nonce = nonce; + this.certReq = certReq; + this.extensions = extensions; + } + + public DerInteger Version + { + get { return version; } + } + + public MessageImprint MessageImprint + { + get { return messageImprint; } + } + + public DerObjectIdentifier ReqPolicy + { + get { return tsaPolicy; } + } + + public DerInteger Nonce + { + get { return nonce; } + } + + public DerBoolean CertReq + { + get { return certReq; } + } + + public X509Extensions Extensions + { + get { 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 override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector( + version, 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/bc-sharp-crypto/src/asn1/tsp/TimeStampResp.cs b/bc-sharp-crypto/src/asn1/tsp/TimeStampResp.cs new file mode 100644 index 0000000..b910260 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/tsp/TimeStampResp.cs @@ -0,0 +1,80 @@ +using System; + +using Org.BouncyCastle.Asn1.Cmp; +using Org.BouncyCastle.Asn1.Cms; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Tsp +{ + public class TimeStampResp + : Asn1Encodable + { + private readonly PkiStatusInfo pkiStatusInfo; + private readonly ContentInfo timeStampToken; + + public static TimeStampResp GetInstance( + object o) + { + if (o == null || o is TimeStampResp) + { + return (TimeStampResp) o; + } + + if (o is Asn1Sequence) + { + return new TimeStampResp((Asn1Sequence) o); + } + + throw new ArgumentException( + "Unknown object in 'TimeStampResp' factory: " + Platform.GetTypeName(o)); + } + + private TimeStampResp( + Asn1Sequence seq) + { + this.pkiStatusInfo = PkiStatusInfo.GetInstance(seq[0]); + + if (seq.Count > 1) + { + this.timeStampToken = ContentInfo.GetInstance(seq[1]); + } + } + + public TimeStampResp( + PkiStatusInfo pkiStatusInfo, + ContentInfo timeStampToken) + { + this.pkiStatusInfo = pkiStatusInfo; + this.timeStampToken = timeStampToken; + } + + public PkiStatusInfo Status + { + get { return pkiStatusInfo; } + } + + public ContentInfo TimeStampToken + { + get { return timeStampToken; } + } + + /** + *
+		 * TimeStampResp ::= SEQUENCE  {
+		 *   status                  PkiStatusInfo,
+		 *   timeStampToken          TimeStampToken     OPTIONAL  }
+		 * 
+ */ + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(pkiStatusInfo); + + if (timeStampToken != null) + { + v.Add(timeStampToken); + } + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/util/Asn1Dump.cs b/bc-sharp-crypto/src/asn1/util/Asn1Dump.cs new file mode 100644 index 0000000..6a21ee2 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/util/Asn1Dump.cs @@ -0,0 +1,381 @@ +using System; +using System.Collections; +using System.IO; +using System.Text; + +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Asn1.Utilities +{ + public sealed class Asn1Dump + { + private static readonly string NewLine = Platform.NewLine; + + private Asn1Dump() + { + } + + private const string Tab = " "; + private const int SampleSize = 32; + + /** + * dump a Der object as a formatted string with indentation + * + * @param obj the Asn1Object to be dumped out. + */ + private static void AsString( + string indent, + bool verbose, + Asn1Object obj, + StringBuilder buf) + { + if (obj is Asn1Sequence) + { + string tab = indent + Tab; + buf.Append(indent); + if (obj is BerSequence) + { + buf.Append("BER Sequence"); + } + else if (obj is DerSequence) + { + buf.Append("DER Sequence"); + } + else + { + buf.Append("Sequence"); + } + + buf.Append(NewLine); + + foreach (Asn1Encodable o in ((Asn1Sequence)obj)) + { + if (o == null || o is Asn1Null) + { + buf.Append(tab); + buf.Append("NULL"); + buf.Append(NewLine); + } + else + { + AsString(tab, verbose, o.ToAsn1Object(), buf); + } + } + } + else if (obj is DerTaggedObject) + { + string tab = indent + Tab; + buf.Append(indent); + if (obj is BerTaggedObject) + { + buf.Append("BER Tagged ["); + } + else + { + buf.Append("Tagged ["); + } + + DerTaggedObject o = (DerTaggedObject)obj; + + buf.Append(((int)o.TagNo).ToString()); + buf.Append(']'); + + if (!o.IsExplicit()) + { + buf.Append(" IMPLICIT "); + } + + buf.Append(NewLine); + + if (o.IsEmpty()) + { + buf.Append(tab); + buf.Append("EMPTY"); + buf.Append(NewLine); + } + else + { + AsString(tab, verbose, o.GetObject(), buf); + } + } + else if (obj is BerSet) + { + string tab = indent + Tab; + + buf.Append(indent); + buf.Append("BER Set"); + buf.Append(NewLine); + + foreach (Asn1Encodable o in ((Asn1Set)obj)) + { + if (o == null) + { + buf.Append(tab); + buf.Append("NULL"); + buf.Append(NewLine); + } + else + { + AsString(tab, verbose, o.ToAsn1Object(), buf); + } + } + } + else if (obj is DerSet) + { + string tab = indent + Tab; + + buf.Append(indent); + buf.Append("DER Set"); + buf.Append(NewLine); + + foreach (Asn1Encodable o in ((Asn1Set)obj)) + { + if (o == null) + { + buf.Append(tab); + buf.Append("NULL"); + buf.Append(NewLine); + } + else + { + AsString(tab, verbose, o.ToAsn1Object(), buf); + } + } + } + else if (obj is DerObjectIdentifier) + { + buf.Append(indent + "ObjectIdentifier(" + ((DerObjectIdentifier)obj).Id + ")" + NewLine); + } + else if (obj is DerBoolean) + { + buf.Append(indent + "Boolean(" + ((DerBoolean)obj).IsTrue + ")" + NewLine); + } + else if (obj is DerInteger) + { + buf.Append(indent + "Integer(" + ((DerInteger)obj).Value + ")" + NewLine); + } + else if (obj is BerOctetString) + { + byte[] octets = ((Asn1OctetString)obj).GetOctets(); + string extra = verbose ? dumpBinaryDataAsString(indent, octets) : ""; + buf.Append(indent + "BER Octet String" + "[" + octets.Length + "] " + extra + NewLine); + } + else if (obj is DerOctetString) + { + byte[] octets = ((Asn1OctetString)obj).GetOctets(); + string extra = verbose ? dumpBinaryDataAsString(indent, octets) : ""; + buf.Append(indent + "DER Octet String" + "[" + octets.Length + "] " + extra + NewLine); + } + else if (obj is DerBitString) + { + DerBitString bt = (DerBitString)obj; + byte[] bytes = bt.GetBytes(); + string extra = verbose ? dumpBinaryDataAsString(indent, bytes) : ""; + buf.Append(indent + "DER Bit String" + "[" + bytes.Length + ", " + bt.PadBits + "] " + extra + NewLine); + } + else if (obj is DerIA5String) + { + buf.Append(indent + "IA5String(" + ((DerIA5String)obj).GetString() + ") " + NewLine); + } + else if (obj is DerUtf8String) + { + buf.Append(indent + "UTF8String(" + ((DerUtf8String)obj).GetString() + ") " + NewLine); + } + else if (obj is DerPrintableString) + { + buf.Append(indent + "PrintableString(" + ((DerPrintableString)obj).GetString() + ") " + NewLine); + } + else if (obj is DerVisibleString) + { + buf.Append(indent + "VisibleString(" + ((DerVisibleString)obj).GetString() + ") " + NewLine); + } + else if (obj is DerBmpString) + { + buf.Append(indent + "BMPString(" + ((DerBmpString)obj).GetString() + ") " + NewLine); + } + else if (obj is DerT61String) + { + buf.Append(indent + "T61String(" + ((DerT61String)obj).GetString() + ") " + NewLine); + } + else if (obj is DerGraphicString) + { + buf.Append(indent + "GraphicString(" + ((DerGraphicString)obj).GetString() + ") " + NewLine); + } + else if (obj is DerVideotexString) + { + buf.Append(indent + "VideotexString(" + ((DerVideotexString)obj).GetString() + ") " + NewLine); + } + else if (obj is DerUtcTime) + { + buf.Append(indent + "UTCTime(" + ((DerUtcTime)obj).TimeString + ") " + NewLine); + } + else if (obj is DerGeneralizedTime) + { + buf.Append(indent + "GeneralizedTime(" + ((DerGeneralizedTime)obj).GetTime() + ") " + NewLine); + } + else if (obj is BerApplicationSpecific) + { + buf.Append(outputApplicationSpecific("BER", indent, verbose, (BerApplicationSpecific)obj)); + } + else if (obj is DerApplicationSpecific) + { + buf.Append(outputApplicationSpecific("DER", indent, verbose, (DerApplicationSpecific)obj)); + } + else if (obj is DerEnumerated) + { + DerEnumerated en = (DerEnumerated)obj; + buf.Append(indent + "DER Enumerated(" + en.Value + ")" + NewLine); + } + else if (obj is DerExternal) + { + DerExternal ext = (DerExternal)obj; + buf.Append(indent + "External " + NewLine); + string tab = indent + Tab; + + if (ext.DirectReference != null) + { + buf.Append(tab + "Direct Reference: " + ext.DirectReference.Id + NewLine); + } + if (ext.IndirectReference != null) + { + buf.Append(tab + "Indirect Reference: " + ext.IndirectReference.ToString() + NewLine); + } + if (ext.DataValueDescriptor != null) + { + AsString(tab, verbose, ext.DataValueDescriptor, buf); + } + buf.Append(tab + "Encoding: " + ext.Encoding + NewLine); + AsString(tab, verbose, ext.ExternalContent, buf); + } + else + { + buf.Append(indent + obj.ToString() + NewLine); + } + } + + private static string outputApplicationSpecific( + string type, + string indent, + bool verbose, + DerApplicationSpecific app) + { + StringBuilder buf = new StringBuilder(); + + if (app.IsConstructed()) + { + try + { + Asn1Sequence s = Asn1Sequence.GetInstance(app.GetObject(Asn1Tags.Sequence)); + buf.Append(indent + type + " ApplicationSpecific[" + app.ApplicationTag + "]" + NewLine); + foreach (Asn1Encodable ae in s) + { + AsString(indent + Tab, verbose, ae.ToAsn1Object(), buf); + } + } + catch (IOException e) + { + buf.Append(e); + } + return buf.ToString(); + } + + return indent + type + " ApplicationSpecific[" + app.ApplicationTag + "] (" + + Hex.ToHexString(app.GetContents()) + ")" + NewLine; + } + + [Obsolete("Use version accepting Asn1Encodable")] + public static string DumpAsString( + object obj) + { + if (obj is Asn1Encodable) + { + StringBuilder buf = new StringBuilder(); + AsString("", false, ((Asn1Encodable)obj).ToAsn1Object(), buf); + return buf.ToString(); + } + + return "unknown object type " + obj.ToString(); + } + + /** + * dump out a DER object as a formatted string, in non-verbose mode + * + * @param obj the Asn1Encodable to be dumped out. + * @return the resulting string. + */ + public static string DumpAsString( + Asn1Encodable obj) + { + return DumpAsString(obj, false); + } + + /** + * Dump out the object as a string + * + * @param obj the Asn1Encodable to be dumped out. + * @param verbose if true, dump out the contents of octet and bit strings. + * @return the resulting string. + */ + public static string DumpAsString( + Asn1Encodable obj, + bool verbose) + { + StringBuilder buf = new StringBuilder(); + AsString("", verbose, obj.ToAsn1Object(), buf); + return buf.ToString(); + } + + private static string dumpBinaryDataAsString(string indent, byte[] bytes) + { + indent += Tab; + + StringBuilder buf = new StringBuilder(NewLine); + + for (int i = 0; i < bytes.Length; i += SampleSize) + { + if (bytes.Length - i > SampleSize) + { + buf.Append(indent); + buf.Append(Hex.ToHexString(bytes, i, SampleSize)); + buf.Append(Tab); + buf.Append(calculateAscString(bytes, i, SampleSize)); + buf.Append(NewLine); + } + else + { + buf.Append(indent); + buf.Append(Hex.ToHexString(bytes, i, bytes.Length - i)); + for (int j = bytes.Length - i; j != SampleSize; j++) + { + buf.Append(" "); + } + buf.Append(Tab); + buf.Append(calculateAscString(bytes, i, bytes.Length - i)); + buf.Append(NewLine); + } + } + + return buf.ToString(); + } + + private static string calculateAscString( + byte[] bytes, + int off, + int len) + { + StringBuilder buf = new StringBuilder(); + + for (int i = off; i != off + len; i++) + { + char c = (char)bytes[i]; + if (c >= ' ' && c <= '~') + { + buf.Append(c); + } + } + + return buf.ToString(); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/util/Dump.cs b/bc-sharp-crypto/src/asn1/util/Dump.cs new file mode 100644 index 0000000..6025c72 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/util/Dump.cs @@ -0,0 +1,31 @@ +#if !PORTABLE +using System; +using System.IO; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Utilities +{ + public sealed class Dump + { + private Dump() + { + } + + /* public static void Main(string[] args) + { + FileStream fIn = File.OpenRead(args[0]); + Asn1InputStream bIn = new Asn1InputStream(fIn); + + Asn1Object obj; + while ((obj = bIn.ReadObject()) != null) + { + Console.WriteLine(Asn1Dump.DumpAsString(obj)); + } + + Platform.Dispose(bIn); + } + */ + } +} +#endif diff --git a/bc-sharp-crypto/src/asn1/util/FilterStream.cs b/bc-sharp-crypto/src/asn1/util/FilterStream.cs new file mode 100644 index 0000000..0c38c5b --- /dev/null +++ b/bc-sharp-crypto/src/asn1/util/FilterStream.cs @@ -0,0 +1,83 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.Utilities +{ + [Obsolete("Use Org.BouncyCastle.Utilities.IO.FilterStream")] + public class FilterStream : Stream + { + [Obsolete("Use Org.BouncyCastle.Utilities.IO.FilterStream")] + public FilterStream(Stream s) + { + this.s = s; + } + public override bool CanRead + { + get { return s.CanRead; } + } + public override bool CanSeek + { + get { return s.CanSeek; } + } + public override bool CanWrite + { + get { return s.CanWrite; } + } + public override long Length + { + get { return s.Length; } + } + public override long Position + { + get { return s.Position; } + set { s.Position = value; } + } +#if PORTABLE + protected override void Dispose(bool disposing) + { + if (disposing) + { + Platform.Dispose(s); + } + base.Dispose(disposing); + } +#else + public override void Close() + { + Platform.Dispose(s); + base.Close(); + } +#endif + public override void Flush() + { + s.Flush(); + } + public override long Seek(long offset, SeekOrigin origin) + { + return s.Seek(offset, origin); + } + public override void SetLength(long value) + { + s.SetLength(value); + } + public override int Read(byte[] buffer, int offset, int count) + { + return s.Read(buffer, offset, count); + } + public override int ReadByte() + { + return s.ReadByte(); + } + public override void Write(byte[] buffer, int offset, int count) + { + s.Write(buffer, offset, count); + } + public override void WriteByte(byte value) + { + s.WriteByte(value); + } + protected readonly Stream s; + } +} diff --git a/bc-sharp-crypto/src/asn1/x500/DirectoryString.cs b/bc-sharp-crypto/src/asn1/x500/DirectoryString.cs new file mode 100644 index 0000000..d907c64 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x500/DirectoryString.cs @@ -0,0 +1,77 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.X500 +{ + public class DirectoryString + : Asn1Encodable, IAsn1Choice, IAsn1String + { + private readonly DerStringBase str; + + public static DirectoryString GetInstance( + object obj) + { + if (obj is DirectoryString) + { + return (DirectoryString) obj; + } + + if (obj is DerStringBase) + { + if (obj is DerT61String + || obj is DerPrintableString + || obj is DerUniversalString + || obj is DerUtf8String + || obj is DerBmpString) + { + return new DirectoryString((DerStringBase) obj); + } + } + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + public static DirectoryString GetInstance( + Asn1TaggedObject obj, + bool isExplicit) + { + if (!isExplicit) + throw new ArgumentException("choice item must be explicitly tagged"); + + return GetInstance(obj.GetObject()); + } + + private DirectoryString( + DerStringBase str) + { + this.str = str; + } + + public DirectoryString( + string str) + { + this.str = new DerUtf8String(str); + } + + public string GetString() + { + return str.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 override Asn1Object ToAsn1Object() + { + return str.ToAsn1Object(); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/AccessDescription.cs b/bc-sharp-crypto/src/asn1/x509/AccessDescription.cs new file mode 100644 index 0000000..47374be --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/AccessDescription.cs @@ -0,0 +1,85 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.X509 +{ + /** + * The AccessDescription object. + *
+	 * AccessDescription  ::=  SEQUENCE {
+	 *       accessMethod          OBJECT IDENTIFIER,
+	 *       accessLocation        GeneralName  }
+	 * 
+ */ + public class AccessDescription + : Asn1Encodable + { + public readonly static DerObjectIdentifier IdADCAIssuers = new DerObjectIdentifier("1.3.6.1.5.5.7.48.2"); + public readonly static DerObjectIdentifier IdADOcsp = new DerObjectIdentifier("1.3.6.1.5.5.7.48.1"); + + private readonly DerObjectIdentifier accessMethod; + private readonly GeneralName accessLocation; + + public static AccessDescription GetInstance( + object obj) + { + if (obj is AccessDescription) + return (AccessDescription) obj; + + if (obj is Asn1Sequence) + return new AccessDescription((Asn1Sequence) obj); + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + private AccessDescription( + Asn1Sequence seq) + { + if (seq.Count != 2) + throw new ArgumentException("wrong number of elements in sequence"); + + accessMethod = DerObjectIdentifier.GetInstance(seq[0]); + accessLocation = GeneralName.GetInstance(seq[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 AccessMethod + { + get { return accessMethod; } + } + + /** + * + * @return the access location + */ + public GeneralName AccessLocation + { + get { return accessLocation; } + } + + public override Asn1Object ToAsn1Object() + { + return new DerSequence(accessMethod, accessLocation); + } + + public override string ToString() + { + return "AccessDescription: Oid(" + this.accessMethod.Id + ")"; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/AlgorithmIdentifier.cs b/bc-sharp-crypto/src/asn1/x509/AlgorithmIdentifier.cs new file mode 100644 index 0000000..00e7ad8 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/AlgorithmIdentifier.cs @@ -0,0 +1,96 @@ +using System; + +namespace Org.BouncyCastle.Asn1.X509 +{ + public class AlgorithmIdentifier + : Asn1Encodable + { + private readonly DerObjectIdentifier algorithm; + private readonly Asn1Encodable parameters; + + public static AlgorithmIdentifier GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); + } + + public static AlgorithmIdentifier GetInstance( + object obj) + { + if (obj == null) + return null; + if (obj is AlgorithmIdentifier) + return (AlgorithmIdentifier)obj; + return new AlgorithmIdentifier(Asn1Sequence.GetInstance(obj)); + } + + public AlgorithmIdentifier( + DerObjectIdentifier algorithm) + { + this.algorithm = algorithm; + } + + [Obsolete("Use version taking a DerObjectIdentifier")] + public AlgorithmIdentifier( + string algorithm) + { + this.algorithm = new DerObjectIdentifier(algorithm); + } + + public AlgorithmIdentifier( + DerObjectIdentifier algorithm, + Asn1Encodable parameters) + { + this.algorithm = algorithm; + this.parameters = parameters; + } + + internal AlgorithmIdentifier( + Asn1Sequence seq) + { + if (seq.Count < 1 || seq.Count > 2) + throw new ArgumentException("Bad sequence size: " + seq.Count); + + this.algorithm = DerObjectIdentifier.GetInstance(seq[0]); + this.parameters = seq.Count < 2 ? null : seq[1]; + } + + /// + /// Return the OID in the Algorithm entry of this identifier. + /// + public virtual DerObjectIdentifier Algorithm + { + get { return algorithm; } + } + + [Obsolete("Use 'Algorithm' property instead")] + public virtual DerObjectIdentifier ObjectID + { + get { return algorithm; } + } + + /// + /// Return the parameters structure in the Parameters entry of this identifier. + /// + public virtual Asn1Encodable Parameters + { + get { return parameters; } + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *
+         *      AlgorithmIdentifier ::= Sequence {
+         *                            algorithm OBJECT IDENTIFIER,
+         *                            parameters ANY DEFINED BY algorithm OPTIONAL }
+         * 
+ */ + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(algorithm); + v.AddOptional(parameters); + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/AttCertIssuer.cs b/bc-sharp-crypto/src/asn1/x509/AttCertIssuer.cs new file mode 100644 index 0000000..407c4ae --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/AttCertIssuer.cs @@ -0,0 +1,86 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.X509 +{ + public class AttCertIssuer + : Asn1Encodable, IAsn1Choice + { + internal readonly Asn1Encodable obj; + internal readonly Asn1Object choiceObj; + + public static AttCertIssuer GetInstance( + object obj) + { + if (obj is AttCertIssuer) + { + return (AttCertIssuer)obj; + } + else if (obj is V2Form) + { + return new AttCertIssuer(V2Form.GetInstance(obj)); + } + else if (obj is GeneralNames) + { + return new AttCertIssuer((GeneralNames)obj); + } + else if (obj is Asn1TaggedObject) + { + return new AttCertIssuer(V2Form.GetInstance((Asn1TaggedObject)obj, false)); + } + else if (obj is Asn1Sequence) + { + return new AttCertIssuer(GeneralNames.GetInstance(obj)); + } + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + public static AttCertIssuer GetInstance( + Asn1TaggedObject obj, + bool isExplicit) + { + 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. + /// + /// Our GeneralNames structure + public AttCertIssuer( + GeneralNames names) + { + obj = names; + choiceObj = obj.ToAsn1Object(); + } + + public AttCertIssuer( + V2Form v2Form) + { + obj = v2Form; + choiceObj = new DerTaggedObject(false, 0, obj); + } + + public Asn1Encodable Issuer + { + get { 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 override Asn1Object ToAsn1Object() + { + return choiceObj; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/AttCertValidityPeriod.cs b/bc-sharp-crypto/src/asn1/x509/AttCertValidityPeriod.cs new file mode 100644 index 0000000..d31e074 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/AttCertValidityPeriod.cs @@ -0,0 +1,78 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.X509 +{ + public class AttCertValidityPeriod + : Asn1Encodable + { + private readonly DerGeneralizedTime notBeforeTime; + private readonly DerGeneralizedTime notAfterTime; + + public static AttCertValidityPeriod GetInstance( + object obj) + { + if (obj is AttCertValidityPeriod || obj == null) + { + return (AttCertValidityPeriod) obj; + } + + if (obj is Asn1Sequence) + { + return new AttCertValidityPeriod((Asn1Sequence) obj); + } + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + public static AttCertValidityPeriod GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); + } + + private AttCertValidityPeriod( + Asn1Sequence seq) + { + if (seq.Count != 2) + throw new ArgumentException("Bad sequence size: " + seq.Count); + + notBeforeTime = DerGeneralizedTime.GetInstance(seq[0]); + notAfterTime = DerGeneralizedTime.GetInstance(seq[1]); + } + + public AttCertValidityPeriod( + DerGeneralizedTime notBeforeTime, + DerGeneralizedTime notAfterTime) + { + this.notBeforeTime = notBeforeTime; + this.notAfterTime = notAfterTime; + } + + public DerGeneralizedTime NotBeforeTime + { + get { return notBeforeTime; } + } + + public DerGeneralizedTime NotAfterTime + { + get { return notAfterTime; } + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *
+         *  AttCertValidityPeriod  ::= Sequence {
+         *       notBeforeTime  GeneralizedTime,
+         *       notAfterTime   GeneralizedTime
+         *  }
+         * 
+ */ + public override Asn1Object ToAsn1Object() + { + return new DerSequence(notBeforeTime, notAfterTime); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/Attribute.cs b/bc-sharp-crypto/src/asn1/x509/Attribute.cs new file mode 100644 index 0000000..da59b42 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/Attribute.cs @@ -0,0 +1,82 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.X509 +{ + public class AttributeX509 + : Asn1Encodable + { + private readonly DerObjectIdentifier attrType; + private readonly Asn1Set attrValues; + + /** + * return an Attr object from the given object. + * + * @param o the object we want converted. + * @exception ArgumentException if the object cannot be converted. + */ + public static AttributeX509 GetInstance( + object obj) + { + if (obj == null || obj is AttributeX509) + { + return (AttributeX509) obj; + } + + if (obj is Asn1Sequence) + { + return new AttributeX509((Asn1Sequence) obj); + } + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + private AttributeX509( + Asn1Sequence seq) + { + if (seq.Count != 2) + throw new ArgumentException("Bad sequence size: " + seq.Count); + + attrType = DerObjectIdentifier.GetInstance(seq[0]); + attrValues = Asn1Set.GetInstance(seq[1]); + } + + public AttributeX509( + DerObjectIdentifier attrType, + Asn1Set attrValues) + { + this.attrType = attrType; + this.attrValues = attrValues; + } + + public DerObjectIdentifier AttrType + { + get { return attrType; } + } + + public Asn1Encodable[] GetAttributeValues() + { + return attrValues.ToArray(); + } + + public Asn1Set AttrValues + { + get { return attrValues; } + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *
+         * Attr ::= Sequence {
+         *     attrType OBJECT IDENTIFIER,
+         *     attrValues Set OF AttributeValue
+         * }
+         * 
+ */ + public override Asn1Object ToAsn1Object() + { + return new DerSequence(attrType, attrValues); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/AttributeCertificate.cs b/bc-sharp-crypto/src/asn1/x509/AttributeCertificate.cs new file mode 100644 index 0000000..41893b6 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/AttributeCertificate.cs @@ -0,0 +1,86 @@ +using System; + +using Org.BouncyCastle.Asn1; + +namespace Org.BouncyCastle.Asn1.X509 +{ + public class AttributeCertificate + : Asn1Encodable + { + private readonly AttributeCertificateInfo acinfo; + private readonly AlgorithmIdentifier signatureAlgorithm; + private readonly DerBitString signatureValue; + + /** + * @param obj + * @return + */ + public static AttributeCertificate GetInstance( + object obj) + { + if (obj is AttributeCertificate) + return (AttributeCertificate) obj; + + if (obj != null) + return new AttributeCertificate(Asn1Sequence.GetInstance(obj)); + + return null; + } + + public AttributeCertificate( + AttributeCertificateInfo acinfo, + AlgorithmIdentifier signatureAlgorithm, + DerBitString signatureValue) + { + this.acinfo = acinfo; + this.signatureAlgorithm = signatureAlgorithm; + this.signatureValue = signatureValue; + } + + private AttributeCertificate( + Asn1Sequence seq) + { + if (seq.Count != 3) + throw new ArgumentException("Bad sequence size: " + seq.Count); + + this.acinfo = AttributeCertificateInfo.GetInstance(seq[0]); + this.signatureAlgorithm = AlgorithmIdentifier.GetInstance(seq[1]); + this.signatureValue = DerBitString.GetInstance(seq[2]); + } + + public AttributeCertificateInfo ACInfo + { + get { return acinfo; } + } + + public AlgorithmIdentifier SignatureAlgorithm + { + get { return signatureAlgorithm; } + } + + public DerBitString SignatureValue + { + get { return signatureValue; } + } + + public byte[] GetSignatureOctets() + { + return signatureValue.GetOctets(); + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *
+         *  AttributeCertificate ::= Sequence {
+         *       acinfo               AttributeCertificateInfo,
+         *       signatureAlgorithm   AlgorithmIdentifier,
+         *       signatureValue       BIT STRING
+         *  }
+         * 
+ */ + public override Asn1Object ToAsn1Object() + { + return new DerSequence(acinfo, signatureAlgorithm, signatureValue); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/AttributeCertificateInfo.cs b/bc-sharp-crypto/src/asn1/x509/AttributeCertificateInfo.cs new file mode 100644 index 0000000..526f8e6 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/AttributeCertificateInfo.cs @@ -0,0 +1,156 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.X509 +{ + public class AttributeCertificateInfo + : Asn1Encodable + { + internal readonly DerInteger version; + internal readonly Holder holder; + internal readonly AttCertIssuer issuer; + internal readonly AlgorithmIdentifier signature; + internal readonly DerInteger serialNumber; + internal readonly AttCertValidityPeriod attrCertValidityPeriod; + internal readonly Asn1Sequence attributes; + internal readonly DerBitString issuerUniqueID; + internal readonly X509Extensions extensions; + + public static AttributeCertificateInfo GetInstance( + Asn1TaggedObject obj, + bool isExplicit) + { + return GetInstance(Asn1Sequence.GetInstance(obj, isExplicit)); + } + + public static AttributeCertificateInfo GetInstance( + object obj) + { + if (obj is AttributeCertificateInfo) + { + return (AttributeCertificateInfo) obj; + } + + if (obj is Asn1Sequence) + { + return new AttributeCertificateInfo((Asn1Sequence) obj); + } + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + private AttributeCertificateInfo( + Asn1Sequence seq) + { + if (seq.Count < 7 || seq.Count > 9) + { + throw new ArgumentException("Bad sequence size: " + seq.Count); + } + + this.version = DerInteger.GetInstance(seq[0]); + this.holder = Holder.GetInstance(seq[1]); + this.issuer = AttCertIssuer.GetInstance(seq[2]); + this.signature = AlgorithmIdentifier.GetInstance(seq[3]); + this.serialNumber = DerInteger.GetInstance(seq[4]); + this.attrCertValidityPeriod = AttCertValidityPeriod.GetInstance(seq[5]); + this.attributes = Asn1Sequence.GetInstance(seq[6]); + + for (int i = 7; i < seq.Count; i++) + { + Asn1Encodable obj = (Asn1Encodable) seq[i]; + + if (obj is DerBitString) + { + this.issuerUniqueID = DerBitString.GetInstance(seq[i]); + } + else if (obj is Asn1Sequence || obj is X509Extensions) + { + this.extensions = X509Extensions.GetInstance(seq[i]); + } + } + } + + public DerInteger Version + { + get { return version; } + } + + public Holder Holder + { + get { return holder; } + } + + public AttCertIssuer Issuer + { + get { return issuer; } + } + + public AlgorithmIdentifier Signature + { + get { return signature; } + } + + public DerInteger SerialNumber + { + get { return serialNumber; } + } + + public AttCertValidityPeriod AttrCertValidityPeriod + { + get { return attrCertValidityPeriod; } + } + + public Asn1Sequence Attributes + { + get { return attributes; } + } + + public DerBitString IssuerUniqueID + { + get { return issuerUniqueID; } + } + + public X509Extensions Extensions + { + get { 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 Attr,
+         *       issuerUniqueID       UniqueIdentifier OPTIONAL,
+         *       extensions           Extensions OPTIONAL
+         *  }
+         *
+         *  AttCertVersion ::= Integer { v2(1) }
+         * 
+ */ + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector( + version, holder, issuer, signature, serialNumber, + attrCertValidityPeriod, attributes); + + if (issuerUniqueID != null) + { + v.Add(issuerUniqueID); + } + + if (extensions != null) + { + v.Add(extensions); + } + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/AttributeTable.cs b/bc-sharp-crypto/src/asn1/x509/AttributeTable.cs new file mode 100644 index 0000000..33faad6 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/AttributeTable.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.X509 +{ + public class AttributeTable + { + private readonly IDictionary attributes; + + public AttributeTable( + IDictionary attrs) + { + this.attributes = Platform.CreateHashtable(attrs); + } + +#if !(SILVERLIGHT || PORTABLE) + [Obsolete] + public AttributeTable( + Hashtable attrs) + { + this.attributes = Platform.CreateHashtable(attrs); + } +#endif + + public AttributeTable( + Asn1EncodableVector v) + { + this.attributes = Platform.CreateHashtable(v.Count); + + for (int i = 0; i != v.Count; i++) + { + AttributeX509 a = AttributeX509.GetInstance(v[i]); + + attributes.Add(a.AttrType, a); + } + } + + public AttributeTable( + Asn1Set s) + { + this.attributes = Platform.CreateHashtable(s.Count); + + for (int i = 0; i != s.Count; i++) + { + AttributeX509 a = AttributeX509.GetInstance(s[i]); + + attributes.Add(a.AttrType, a); + } + } + + public AttributeX509 Get( + DerObjectIdentifier oid) + { + return (AttributeX509) attributes[oid]; + } + +#if !(SILVERLIGHT || PORTABLE) + [Obsolete("Use 'ToDictionary' instead")] + public Hashtable ToHashtable() + { + return new Hashtable(attributes); + } +#endif + + public IDictionary ToDictionary() + { + return Platform.CreateHashtable(attributes); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/AuthorityInformationAccess.cs b/bc-sharp-crypto/src/asn1/x509/AuthorityInformationAccess.cs new file mode 100644 index 0000000..9329e2b --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/AuthorityInformationAccess.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections; +using System.Text; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.X509 +{ + /** + * 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 + : Asn1Encodable + { + private readonly AccessDescription[] descriptions; + + public static AuthorityInformationAccess GetInstance(object obj) + { + if (obj is AuthorityInformationAccess) + return (AuthorityInformationAccess)obj; + if (obj == null) + return null; + return new AuthorityInformationAccess(Asn1Sequence.GetInstance(obj)); + } + + private AuthorityInformationAccess( + Asn1Sequence seq) + { + if (seq.Count < 1) + throw new ArgumentException("sequence may not be empty"); + + this.descriptions = new AccessDescription[seq.Count]; + + for (int i = 0; i < seq.Count; ++i) + { + descriptions[i] = AccessDescription.GetInstance(seq[i]); + } + } + + public AuthorityInformationAccess( + AccessDescription description) + { + this.descriptions = new AccessDescription[]{ description }; + } + + /** + * create an AuthorityInformationAccess with the oid and location provided. + */ + public AuthorityInformationAccess(DerObjectIdentifier oid, GeneralName location) + : this(new AccessDescription(oid, location)) + { + } + + public AccessDescription[] GetAccessDescriptions() + { + return (AccessDescription[])descriptions.Clone(); + } + + public override Asn1Object ToAsn1Object() + { + return new DerSequence(descriptions); + } + + public override string ToString() + { + //return "AuthorityInformationAccess: Oid(" + this.descriptions[0].AccessMethod.Id + ")"; + + StringBuilder buf = new StringBuilder(); + string sep = Platform.NewLine; + + buf.Append("AuthorityInformationAccess:"); + buf.Append(sep); + + foreach (AccessDescription description in descriptions) + { + buf.Append(" "); + buf.Append(description); + buf.Append(sep); + } + + return buf.ToString(); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/AuthorityKeyIdentifier.cs b/bc-sharp-crypto/src/asn1/x509/AuthorityKeyIdentifier.cs new file mode 100644 index 0000000..d5a9048 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/AuthorityKeyIdentifier.cs @@ -0,0 +1,211 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.X509 +{ + /** + * 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 + : Asn1Encodable + { + internal readonly Asn1OctetString keyidentifier; + internal readonly GeneralNames certissuer; + internal readonly DerInteger certserno; + + public static AuthorityKeyIdentifier GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); + } + + public static AuthorityKeyIdentifier GetInstance( + object obj) + { + if (obj is AuthorityKeyIdentifier) + { + return (AuthorityKeyIdentifier) obj; + } + + if (obj is Asn1Sequence) + { + return new AuthorityKeyIdentifier((Asn1Sequence) obj); + } + + if (obj is X509Extension) + { + return GetInstance(X509Extension.ConvertValueToObject((X509Extension) obj)); + } + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + protected internal AuthorityKeyIdentifier( + Asn1Sequence seq) + { + foreach (Asn1TaggedObject o in seq) + { + switch (o.TagNo) + { + 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 ArgumentException("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) + { + IDigest digest = new Sha1Digest(); + byte[] resBuf = new byte[digest.GetDigestSize()]; + + byte[] bytes = spki.PublicKeyData.GetBytes(); + digest.BlockUpdate(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) + { + IDigest digest = new Sha1Digest(); + byte[] resBuf = new byte[digest.GetDigestSize()]; + + byte[] bytes = spki.PublicKeyData.GetBytes(); + digest.BlockUpdate(bytes, 0, bytes.Length); + digest.DoFinal(resBuf, 0); + + this.keyidentifier = new DerOctetString(resBuf); + this.certissuer = name; + 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 precomputed 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() + { + return keyidentifier == null ? null : keyidentifier.GetOctets(); + } + + public GeneralNames AuthorityCertIssuer + { + get { return certissuer; } + } + + public BigInteger AuthorityCertSerialNumber + { + get { return certserno == null ? null : certserno.Value; } + } + + /** + * Produce an object suitable for an Asn1OutputStream. + */ + public override Asn1Object 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 override string ToString() + { + return ("AuthorityKeyIdentifier: KeyID(" + this.keyidentifier.GetOctets() + ")"); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/BasicConstraints.cs b/bc-sharp-crypto/src/asn1/x509/BasicConstraints.cs new file mode 100644 index 0000000..098801f --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/BasicConstraints.cs @@ -0,0 +1,133 @@ +using System; + +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.X509 +{ + public class BasicConstraints + : Asn1Encodable + { + private readonly DerBoolean cA; + private readonly DerInteger pathLenConstraint; + + public static BasicConstraints GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); + } + + public static BasicConstraints GetInstance( + object obj) + { + if (obj == null || obj is BasicConstraints) + { + return (BasicConstraints) obj; + } + + if (obj is Asn1Sequence) + { + return new BasicConstraints((Asn1Sequence) obj); + } + + if (obj is X509Extension) + { + return GetInstance(X509Extension.ConvertValueToObject((X509Extension) obj)); + } + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + private BasicConstraints( + Asn1Sequence seq) + { + if (seq.Count > 0) + { + if (seq[0] is DerBoolean) + { + this.cA = DerBoolean.GetInstance(seq[0]); + } + else + { + this.pathLenConstraint = DerInteger.GetInstance(seq[0]); + } + + if (seq.Count > 1) + { + if (this.cA == null) + throw new ArgumentException("wrong sequence in constructor", "seq"); + + this.pathLenConstraint = DerInteger.GetInstance(seq[1]); + } + } + } + + public BasicConstraints( + bool cA) + { + if (cA) + { + this.cA = DerBoolean.True; + } + } + + /** + * create a cA=true object for the given path length constraint. + * + * @param pathLenConstraint + */ + public BasicConstraints( + int pathLenConstraint) + { + this.cA = DerBoolean.True; + this.pathLenConstraint = new DerInteger(pathLenConstraint); + } + + public bool IsCA() + { + return cA != null && cA.IsTrue; + } + + public BigInteger PathLenConstraint + { + get { return pathLenConstraint == null ? null : pathLenConstraint.Value; } + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *
+         * BasicConstraints := Sequence {
+         *    cA                  Boolean DEFAULT FALSE,
+         *    pathLenConstraint   Integer (0..MAX) OPTIONAL
+         * }
+         * 
+ */ + public override Asn1Object 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 override string ToString() + { + if (pathLenConstraint == null) + { + return "BasicConstraints: isCa(" + this.IsCA() + ")"; + } + + return "BasicConstraints: isCa(" + this.IsCA() + "), pathLenConstraint = " + pathLenConstraint.Value; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/CRLDistPoint.cs b/bc-sharp-crypto/src/asn1/x509/CRLDistPoint.cs new file mode 100644 index 0000000..56ba79c --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/CRLDistPoint.cs @@ -0,0 +1,93 @@ +using System; +using System.Text; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.X509 +{ + public class CrlDistPoint + : Asn1Encodable + { + internal readonly Asn1Sequence seq; + + public static CrlDistPoint GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); + } + + public static CrlDistPoint GetInstance( + object obj) + { + if (obj is CrlDistPoint || obj == null) + { + return (CrlDistPoint) obj; + } + + if (obj is Asn1Sequence) + { + return new CrlDistPoint((Asn1Sequence) obj); + } + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + private CrlDistPoint( + Asn1Sequence seq) + { + this.seq = seq; + } + + public CrlDistPoint( + DistributionPoint[] points) + { + seq = new DerSequence(points); + } + + /** + * Return the distribution points making up the sequence. + * + * @return DistributionPoint[] + */ + public DistributionPoint[] GetDistributionPoints() + { + DistributionPoint[] dp = new DistributionPoint[seq.Count]; + + for (int i = 0; i != seq.Count; ++i) + { + dp[i] = DistributionPoint.GetInstance(seq[i]); + } + + return dp; + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *
+         * CrlDistPoint ::= Sequence SIZE {1..MAX} OF DistributionPoint
+         * 
+ */ + public override Asn1Object ToAsn1Object() + { + return seq; + } + + public override string ToString() + { + StringBuilder buf = new StringBuilder(); + string sep = Platform.NewLine; + + 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/bc-sharp-crypto/src/asn1/x509/CRLNumber.cs b/bc-sharp-crypto/src/asn1/x509/CRLNumber.cs new file mode 100644 index 0000000..d744416 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/CRLNumber.cs @@ -0,0 +1,30 @@ +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Asn1.X509 +{ + /** + * The CRLNumber object. + *
+     * CRLNumber::= Integer(0..MAX)
+     * 
+ */ + public class CrlNumber + : DerInteger + { + public CrlNumber( + BigInteger number) + : base(number) + { + } + + public BigInteger Number + { + get { return PositiveValue; } + } + + public override string ToString() + { + return "CRLNumber: " + Number; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/CRLReason.cs b/bc-sharp-crypto/src/asn1/x509/CRLReason.cs new file mode 100644 index 0000000..e8eb53a --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/CRLReason.cs @@ -0,0 +1,61 @@ +namespace Org.BouncyCastle.Asn1.X509 +{ + /** + * 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 + : DerEnumerated + { + public const int Unspecified = 0; + public const int KeyCompromise = 1; + public const int CACompromise = 2; + public const int AffiliationChanged = 3; + public const int Superseded = 4; + public const int CessationOfOperation = 5; + public const int CertificateHold = 6; + // 7 -> Unknown + public const int RemoveFromCrl = 8; + public const int PrivilegeWithdrawn = 9; + public const int AACompromise = 10; + + private static readonly string[] ReasonString = new string[] + { + "Unspecified", "KeyCompromise", "CACompromise", "AffiliationChanged", + "Superseded", "CessationOfOperation", "CertificateHold", "Unknown", + "RemoveFromCrl", "PrivilegeWithdrawn", "AACompromise" + }; + + public CrlReason( + int reason) + : base(reason) + { + } + + public CrlReason( + DerEnumerated reason) + : base(reason.Value.IntValue) + { + } + + public override string ToString() + { + int reason = Value.IntValue; + string str = (reason < 0 || reason > 10) ? "Invalid" : ReasonString[reason]; + return "CrlReason: " + str; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/CertPolicyId.cs b/bc-sharp-crypto/src/asn1/x509/CertPolicyId.cs new file mode 100644 index 0000000..11cebcd --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/CertPolicyId.cs @@ -0,0 +1,20 @@ +namespace Org.BouncyCastle.Asn1.X509 +{ + /** + * CertPolicyId, used in the CertificatePolicies and PolicyMappings + * X509V3 Extensions. + * + *
+     *     CertPolicyId ::= OBJECT IDENTIFIER
+     * 
+ */ + public class CertPolicyID + : DerObjectIdentifier + { + public CertPolicyID( + string id) + : base(id) + { + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/CertificateList.cs b/bc-sharp-crypto/src/asn1/x509/CertificateList.cs new file mode 100644 index 0000000..567cf13 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/CertificateList.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1; + +namespace Org.BouncyCastle.Asn1.X509 +{ + /** + * 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 + : Asn1Encodable + { + private readonly TbsCertificateList tbsCertList; + private readonly AlgorithmIdentifier sigAlgID; + private readonly DerBitString sig; + + public static CertificateList GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); + } + + public static CertificateList GetInstance( + object obj) + { + if (obj is CertificateList) + return (CertificateList) obj; + + if (obj != null) + return new CertificateList(Asn1Sequence.GetInstance(obj)); + + return null; + } + + private CertificateList( + Asn1Sequence seq) + { + if (seq.Count != 3) + throw new ArgumentException("sequence wrong size for CertificateList", "seq"); + + tbsCertList = TbsCertificateList.GetInstance(seq[0]); + sigAlgID = AlgorithmIdentifier.GetInstance(seq[1]); + sig = DerBitString.GetInstance(seq[2]); + } + + public TbsCertificateList TbsCertList + { + get { return tbsCertList; } + } + + public CrlEntry[] GetRevokedCertificates() + { + return tbsCertList.GetRevokedCertificates(); + } + + public IEnumerable GetRevokedCertificateEnumeration() + { + return tbsCertList.GetRevokedCertificateEnumeration(); + } + + public AlgorithmIdentifier SignatureAlgorithm + { + get { return sigAlgID; } + } + + public DerBitString Signature + { + get { return sig; } + } + + public byte[] GetSignatureOctets() + { + return sig.GetOctets(); + } + + public int Version + { + get { return tbsCertList.Version; } + } + + public X509Name Issuer + { + get { return tbsCertList.Issuer; } + } + + public Time ThisUpdate + { + get { return tbsCertList.ThisUpdate; } + } + + public Time NextUpdate + { + get { return tbsCertList.NextUpdate; } + } + + public override Asn1Object ToAsn1Object() + { + return new DerSequence(tbsCertList, sigAlgID, sig); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/CertificatePair.cs b/bc-sharp-crypto/src/asn1/x509/CertificatePair.cs new file mode 100644 index 0000000..da92360 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/CertificatePair.cs @@ -0,0 +1,162 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.X509 +{ + /** + * 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 + : Asn1Encodable + { + private X509CertificateStructure forward, reverse; + + public static CertificatePair GetInstance( + object obj) + { + if (obj == null || obj is CertificatePair) + { + return (CertificatePair) obj; + } + + if (obj is Asn1Sequence) + { + return new CertificatePair((Asn1Sequence) obj); + } + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + /** + * 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.Count != 1 && seq.Count != 2) + { + throw new ArgumentException("Bad sequence size: " + seq.Count, "seq"); + } + + foreach (object obj in seq) + { + Asn1TaggedObject o = Asn1TaggedObject.GetInstance(obj); + if (o.TagNo == 0) + { + forward = X509CertificateStructure.GetInstance(o, true); + } + else if (o.TagNo == 1) + { + reverse = X509CertificateStructure.GetInstance(o, true); + } + else + { + throw new ArgumentException("Bad tag number: " + o.TagNo); + } + } + } + + /** + * 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 override Asn1Object 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 Forward + { + get { return forward; } + } + + /** + * @return Returns the reverse. + */ + public X509CertificateStructure Reverse + { + get { return reverse; } + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/CertificatePolicies.cs b/bc-sharp-crypto/src/asn1/x509/CertificatePolicies.cs new file mode 100644 index 0000000..a83565b --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/CertificatePolicies.cs @@ -0,0 +1,81 @@ +using System; +using System.Text; + +namespace Org.BouncyCastle.Asn1.X509 +{ + public class CertificatePolicies + : Asn1Encodable + { + private readonly PolicyInformation[] policyInformation; + + public static CertificatePolicies GetInstance(object obj) + { + if (obj == null || obj is CertificatePolicies) + return (CertificatePolicies)obj; + + return new CertificatePolicies(Asn1Sequence.GetInstance(obj)); + } + + public static CertificatePolicies GetInstance(Asn1TaggedObject obj, bool isExplicit) + { + return GetInstance(Asn1Sequence.GetInstance(obj, isExplicit)); + } + + /** + * Construct a CertificatePolicies object containing one PolicyInformation. + * + * @param name the name to be contained. + */ + public CertificatePolicies(PolicyInformation name) + { + this.policyInformation = new PolicyInformation[] { name }; + } + + public CertificatePolicies(PolicyInformation[] policyInformation) + { + this.policyInformation = policyInformation; + } + + private CertificatePolicies(Asn1Sequence seq) + { + this.policyInformation = new PolicyInformation[seq.Count]; + + for (int i = 0; i < seq.Count; ++i) + { + policyInformation[i] = PolicyInformation.GetInstance(seq[i]); + } + } + + public virtual PolicyInformation[] GetPolicyInformation() + { + return (PolicyInformation[])policyInformation.Clone(); + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+         * CertificatePolicies ::= SEQUENCE SIZE {1..MAX} OF PolicyInformation
+         * 
+ */ + public override Asn1Object ToAsn1Object() + { + return new DerSequence(policyInformation); + } + + public override string ToString() + { + StringBuilder sb = new StringBuilder("CertificatePolicies:"); + if (policyInformation != null && policyInformation.Length > 0) + { + sb.Append(' '); + sb.Append(policyInformation[0]); + for (int i = 1; i < policyInformation.Length; ++i) + { + sb.Append(", "); + sb.Append(policyInformation[i]); + } + } + return sb.ToString(); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/DSAParameter.cs b/bc-sharp-crypto/src/asn1/x509/DSAParameter.cs new file mode 100644 index 0000000..2eb6502 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/DSAParameter.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.X509 +{ + public class DsaParameter + : Asn1Encodable + { + internal readonly DerInteger p, q, g; + + public static DsaParameter GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); + } + + public static DsaParameter GetInstance( + object obj) + { + if(obj == null || obj is DsaParameter) + { + return (DsaParameter) obj; + } + + if(obj is Asn1Sequence) + { + return new DsaParameter((Asn1Sequence) obj); + } + + throw new ArgumentException("Invalid DsaParameter: " + Platform.GetTypeName(obj)); + } + + public DsaParameter( + BigInteger p, + BigInteger q, + BigInteger g) + { + this.p = new DerInteger(p); + this.q = new DerInteger(q); + this.g = new DerInteger(g); + } + + private DsaParameter( + Asn1Sequence seq) + { + if (seq.Count != 3) + throw new ArgumentException("Bad sequence size: " + seq.Count, "seq"); + + this.p = DerInteger.GetInstance(seq[0]); + this.q = DerInteger.GetInstance(seq[1]); + this.g = DerInteger.GetInstance(seq[2]); + } + + public BigInteger P + { + get { return p.PositiveValue; } + } + + public BigInteger Q + { + get { return q.PositiveValue; } + } + + public BigInteger G + { + get { return g.PositiveValue; } + } + + public override Asn1Object ToAsn1Object() + { + return new DerSequence(p, q, g); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/DigestInfo.cs b/bc-sharp-crypto/src/asn1/x509/DigestInfo.cs new file mode 100644 index 0000000..3ac535e --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/DigestInfo.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.X509 +{ + /** + * The DigestInfo object. + *
+     * DigestInfo::=Sequence{
+     *          digestAlgorithm  AlgorithmIdentifier,
+     *          digest OCTET STRING }
+     * 
+ */ + public class DigestInfo + : Asn1Encodable + { + private readonly byte[] digest; + private readonly AlgorithmIdentifier algID; + + public static DigestInfo GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); + } + + public static DigestInfo GetInstance( + object obj) + { + if (obj is DigestInfo) + { + return (DigestInfo) obj; + } + + if (obj is Asn1Sequence) + { + return new DigestInfo((Asn1Sequence) obj); + } + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + public DigestInfo( + AlgorithmIdentifier algID, + byte[] digest) + { + this.digest = digest; + this.algID = algID; + } + + private DigestInfo( + Asn1Sequence seq) + { + if (seq.Count != 2) + throw new ArgumentException("Wrong number of elements in sequence", "seq"); + + algID = AlgorithmIdentifier.GetInstance(seq[0]); + digest = Asn1OctetString.GetInstance(seq[1]).GetOctets(); + } + + public AlgorithmIdentifier AlgorithmID + { + get { return algID; } + } + + public byte[] GetDigest() + { + return digest; + } + + public override Asn1Object ToAsn1Object() + { + return new DerSequence(algID, new DerOctetString(digest)); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/DisplayText.cs b/bc-sharp-crypto/src/asn1/x509/DisplayText.cs new file mode 100644 index 0000000..39b3c98 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/DisplayText.cs @@ -0,0 +1,174 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.X509 +{ + /** + * 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 + : Asn1Encodable, IAsn1Choice + { + /** + * Constant corresponding to ia5String encoding. + * + */ + public const int ContentTypeIA5String = 0; + /** + * Constant corresponding to bmpString encoding. + * + */ + public const int ContentTypeBmpString = 1; + /** + * Constant corresponding to utf8String encoding. + * + */ + public const int ContentTypeUtf8String = 2; + /** + * Constant corresponding to visibleString encoding. + * + */ + public const int ContentTypeVisibleString = 3; + /** + * Describe constant DisplayTextMaximumSize here. + * + */ + public const int DisplayTextMaximumSize = 200; + + internal readonly int contentType; + internal readonly IAsn1String 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 > DisplayTextMaximumSize) + { + // RFC3280 limits these strings to 200 chars + // truncate the string + text = text.Substring(0, DisplayTextMaximumSize); + } + + contentType = type; + switch (type) + { + case ContentTypeIA5String: + contents = (IAsn1String)new DerIA5String (text); + break; + case ContentTypeUtf8String: + contents = (IAsn1String)new DerUtf8String(text); + break; + case ContentTypeVisibleString: + contents = (IAsn1String)new DerVisibleString(text); + break; + case ContentTypeBmpString: + contents = (IAsn1String)new DerBmpString(text); + break; + default: + contents = (IAsn1String)new DerUtf8String(text); + break; + } + } + +// /** +// * return true if the passed in string can be represented without +// * loss as a PrintableString, false otherwise. +// */ +// private bool CanBePrintable( +// string str) +// { +// for (int i = str.Length - 1; i >= 0; i--) +// { +// if (str[i] > 0x007f) +// { +// return false; +// } +// } +// +// return true; +// } + + /** + * 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 > DisplayTextMaximumSize) + { + text = text.Substring(0, DisplayTextMaximumSize); + } + + contentType = ContentTypeUtf8String; + contents = new DerUtf8String(text); + } + + /** + * Creates a new DisplayText instance. + *

Useful when reading back a DisplayText class + * from it's Asn1Encodable form.

+ * + * @param contents an Asn1Encodable instance. + */ + public DisplayText( + IAsn1String contents) + { + this.contents = contents; + } + + public static DisplayText GetInstance( + object obj) + { + if (obj is IAsn1String) + { + return new DisplayText((IAsn1String) obj); + } + + if (obj is DisplayText) + { + return (DisplayText) obj; + } + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + public override Asn1Object ToAsn1Object() + { + return (Asn1Object) contents; + } + + /** + * Returns the stored string object. + * + * @return the stored text as a string. + */ + public string GetString() + { + return contents.GetString(); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/DistributionPoint.cs b/bc-sharp-crypto/src/asn1/x509/DistributionPoint.cs new file mode 100644 index 0000000..40814c7 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/DistributionPoint.cs @@ -0,0 +1,161 @@ +using System; +using System.Text; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.X509 +{ + /** + * The DistributionPoint object. + *
+     * DistributionPoint ::= Sequence {
+     *      distributionPoint [0] DistributionPointName OPTIONAL,
+     *      reasons           [1] ReasonFlags OPTIONAL,
+     *      cRLIssuer         [2] GeneralNames OPTIONAL
+     * }
+     * 
+ */ + public class DistributionPoint + : Asn1Encodable + { + internal readonly DistributionPointName distributionPoint; + internal readonly ReasonFlags reasons; + internal readonly GeneralNames cRLIssuer; + + public static DistributionPoint GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); + } + + public static DistributionPoint GetInstance( + object obj) + { + if(obj == null || obj is DistributionPoint) + { + return (DistributionPoint) obj; + } + + if(obj is Asn1Sequence) + { + return new DistributionPoint((Asn1Sequence) obj); + } + + throw new ArgumentException("Invalid DistributionPoint: " + Platform.GetTypeName(obj)); + } + + private DistributionPoint( + Asn1Sequence seq) + { + for (int i = 0; i != seq.Count; i++) + { + Asn1TaggedObject t = Asn1TaggedObject.GetInstance(seq[i]); + + switch (t.TagNo) + { + 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); + break; + } + } + } + + public DistributionPoint( + DistributionPointName distributionPointName, + ReasonFlags reasons, + GeneralNames crlIssuer) + { + this.distributionPoint = distributionPointName; + this.reasons = reasons; + this.cRLIssuer = crlIssuer; + } + + public DistributionPointName DistributionPointName + { + get { return distributionPoint; } + } + + public ReasonFlags Reasons + { + get { return reasons; } + } + + public GeneralNames CrlIssuer + { + get { return cRLIssuer; } + } + + public override Asn1Object 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 override string ToString() + { + string sep = Platform.NewLine; + StringBuilder buf = new StringBuilder(); + 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( + StringBuilder buf, + string sep, + string name, + string val) + { + string indent = " "; + + buf.Append(indent); + buf.Append(name); + buf.Append(":"); + buf.Append(sep); + buf.Append(indent); + buf.Append(indent); + buf.Append(val); + buf.Append(sep); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/DistributionPointName.cs b/bc-sharp-crypto/src/asn1/x509/DistributionPointName.cs new file mode 100644 index 0000000..43fdaf5 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/DistributionPointName.cs @@ -0,0 +1,130 @@ +using System; +using System.Text; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.X509 +{ + /** + * The DistributionPointName object. + *
+     * DistributionPointName ::= CHOICE {
+     *     fullName                 [0] GeneralNames,
+     *     nameRelativeToCRLIssuer  [1] RDN
+     * }
+     * 
+ */ + public class DistributionPointName + : Asn1Encodable, IAsn1Choice + { + internal readonly Asn1Encodable name; + internal readonly int type; + + public const int FullName = 0; + public const int NameRelativeToCrlIssuer = 1; + + public static DistributionPointName GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(Asn1TaggedObject.GetInstance(obj, true)); + } + + public static DistributionPointName GetInstance( + object obj) + { + if (obj == null || obj is DistributionPointName) + { + return (DistributionPointName) obj; + } + + if (obj is Asn1TaggedObject) + { + return new DistributionPointName((Asn1TaggedObject) obj); + } + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + public DistributionPointName( + int type, + Asn1Encodable name) + { + this.type = type; + this.name = name; + } + + public DistributionPointName( + GeneralNames name) + : this(FullName, name) + { + } + + public int PointType + { + get { return type; } + } + + public Asn1Encodable Name + { + get { return name; } + } + + public DistributionPointName( + Asn1TaggedObject obj) + { + this.type = obj.TagNo; + + if (type == FullName) + { + this.name = GeneralNames.GetInstance(obj, false); + } + else + { + this.name = Asn1Set.GetInstance(obj, false); + } + } + + public override Asn1Object ToAsn1Object() + { + return new DerTaggedObject(false, type, name); + } + + public override string ToString() + { + string sep = Platform.NewLine; + StringBuilder buf = new StringBuilder(); + buf.Append("DistributionPointName: ["); + buf.Append(sep); + if (type == FullName) + { + appendObject(buf, sep, "fullName", name.ToString()); + } + else + { + appendObject(buf, sep, "nameRelativeToCRLIssuer", name.ToString()); + } + buf.Append("]"); + buf.Append(sep); + return buf.ToString(); + } + + private void appendObject( + StringBuilder buf, + string sep, + string name, + string val) + { + string indent = " "; + + buf.Append(indent); + buf.Append(name); + buf.Append(":"); + buf.Append(sep); + buf.Append(indent); + buf.Append(indent); + buf.Append(val); + buf.Append(sep); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/ExtendedKeyUsage.cs b/bc-sharp-crypto/src/asn1/x509/ExtendedKeyUsage.cs new file mode 100644 index 0000000..8f7e6a3 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/ExtendedKeyUsage.cs @@ -0,0 +1,132 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.X509 +{ + /** + * The extendedKeyUsage object. + *
+     *      extendedKeyUsage ::= Sequence SIZE (1..MAX) OF KeyPurposeId
+     * 
+ */ + public class ExtendedKeyUsage + : Asn1Encodable + { + internal readonly IDictionary usageTable = Platform.CreateHashtable(); + internal readonly Asn1Sequence seq; + + public static ExtendedKeyUsage GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); + } + + public static ExtendedKeyUsage GetInstance( + object obj) + { + if (obj is ExtendedKeyUsage) + { + return (ExtendedKeyUsage) obj; + } + + if (obj is Asn1Sequence) + { + return new ExtendedKeyUsage((Asn1Sequence) obj); + } + + if (obj is X509Extension) + { + return GetInstance(X509Extension.ConvertValueToObject((X509Extension) obj)); + } + + throw new ArgumentException("Invalid ExtendedKeyUsage: " + Platform.GetTypeName(obj)); + } + + private ExtendedKeyUsage( + Asn1Sequence seq) + { + this.seq = seq; + + foreach (object o in seq) + { + if (!(o is DerObjectIdentifier)) + throw new ArgumentException("Only DerObjectIdentifier instances allowed in ExtendedKeyUsage."); + + this.usageTable[o] = o; + } + } + + public ExtendedKeyUsage( + params KeyPurposeID[] usages) + { + this.seq = new DerSequence(usages); + + foreach (KeyPurposeID usage in usages) + { + this.usageTable[usage] = usage; + } + } + +#if !(SILVERLIGHT || PORTABLE) + [Obsolete] + public ExtendedKeyUsage( + ArrayList usages) + : this((IEnumerable)usages) + { + } +#endif + + public ExtendedKeyUsage( + IEnumerable usages) + { + Asn1EncodableVector v = new Asn1EncodableVector(); + + foreach (object usage in usages) + { + Asn1Encodable o = KeyPurposeID.GetInstance(usage); + + v.Add(o); + this.usageTable[o] = o; + } + + this.seq = new DerSequence(v); + } + + public bool HasKeyPurposeId( + KeyPurposeID keyPurposeId) + { + return usageTable.Contains(keyPurposeId); + } + +#if !(SILVERLIGHT || PORTABLE) + [Obsolete("Use 'GetAllUsages'")] + public ArrayList GetUsages() + { + return new ArrayList(usageTable.Values); + } +#endif + + /** + * Returns all extended key usages. + * The returned ArrayList contains DerObjectIdentifier instances. + * @return An ArrayList with all key purposes. + */ + public IList GetAllUsages() + { + return Platform.CreateArrayList(usageTable.Values); + } + + public int Count + { + get { return usageTable.Count; } + } + + public override Asn1Object ToAsn1Object() + { + return seq; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/GeneralName.cs b/bc-sharp-crypto/src/asn1/x509/GeneralName.cs new file mode 100644 index 0000000..b8794ea --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/GeneralName.cs @@ -0,0 +1,419 @@ +using System; +using System.Collections; +using System.Globalization; +using System.IO; +using System.Text; + +using Org.BouncyCastle.Utilities; +using NetUtils = Org.BouncyCastle.Utilities.Net; + +namespace Org.BouncyCastle.Asn1.X509 +{ + /** + * 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 }
+     * 
+ */ + public class GeneralName + : Asn1Encodable, IAsn1Choice + { + public const int OtherName = 0; + public const int Rfc822Name = 1; + public const int DnsName = 2; + public const int X400Address = 3; + public const int DirectoryName = 4; + public const int EdiPartyName = 5; + public const int UniformResourceIdentifier = 6; + public const int IPAddress = 7; + public const int RegisteredID = 8; + + internal readonly Asn1Encodable obj; + internal readonly int tag; + + public GeneralName( + X509Name directoryName) + { + this.obj = directoryName; + this.tag = 4; + } + + /** + * 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( + Asn1Object name, + int tag) + { + this.obj = name; + this.tag = tag; + } + + 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 ArgumentException 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) + throw new ArgumentException("IP Address is invalid", "name"); + + this.obj = new DerOctetString(enc); + } + else + { + throw new ArgumentException("can't process string for tag: " + tag, "tag"); + } + } + + public static GeneralName GetInstance( + object obj) + { + if (obj == null || obj is GeneralName) + { + return (GeneralName) obj; + } + + if (obj is Asn1TaggedObject) + { + Asn1TaggedObject tagObj = (Asn1TaggedObject) obj; + int tag = tagObj.TagNo; + + 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 ArgumentException("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)); + } + } + + if (obj is byte[]) + { + try + { + return GetInstance(Asn1Object.FromByteArray((byte[])obj)); + } + catch (IOException) + { + throw new ArgumentException("unable to parse encoded general name"); + } + } + + throw new ArgumentException("unknown object in GetInstance: " + Platform.GetTypeName(obj), "obj"); + } + + public static GeneralName GetInstance( + Asn1TaggedObject tagObj, + bool explicitly) + { + return GetInstance(Asn1TaggedObject.GetInstance(tagObj, true)); + } + + public int TagNo + { + get { return tag; } + } + + public Asn1Encodable Name + { + get { return obj; } + } + + public override string ToString() + { + StringBuilder buf = new StringBuilder(); + 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()); + break; + } + + return buf.ToString(); + } + + private byte[] toGeneralNameEncoding( + string ip) + { + if (NetUtils.IPAddress.IsValidIPv6WithNetmask(ip) || NetUtils.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 (NetUtils.IPAddress.IsValidIPv4WithNetmask(ip) || NetUtils.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 = Int32.Parse(mask); + + for (int i = 0; i != maskVal; i++) + { + addr[(i / 8) + offset] |= (byte)(1 << (i % 8)); + } + } + + private void parseIPv4(string ip, byte[] addr, int offset) + { + foreach (string token in ip.Split('.', '/')) + { + addr[offset++] = (byte)Int32.Parse(token); + } + } + + private int[] parseMask(string mask) + { + int[] res = new int[8]; + int maskVal = Int32.Parse(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) + { + if (Platform.StartsWith(ip, "::")) + { + ip = ip.Substring(1); + } + else if (Platform.EndsWith(ip, "::")) + { + ip = ip.Substring(0, ip.Length - 1); + } + + IEnumerator sEnum = ip.Split(':').GetEnumerator(); + + int index = 0; + int[] val = new int[8]; + + int doubleColon = -1; + + while (sEnum.MoveNext()) + { + string e = (string) sEnum.Current; + + if (e.Length == 0) + { + doubleColon = index; + val[index++] = 0; + } + else + { + if (e.IndexOf('.') < 0) + { + val[index++] = Int32.Parse(e, NumberStyles.AllowHexSpecifier); + } + else + { + string[] tokens = e.Split('.'); + + val[index++] = (Int32.Parse(tokens[0]) << 8) | Int32.Parse(tokens[1]); + val[index++] = (Int32.Parse(tokens[2]) << 8) | Int32.Parse(tokens[3]); + } + } + } + + if (index != val.Length) + { + Array.Copy(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 override Asn1Object ToAsn1Object() + { + // Explicitly tagged if DirectoryName + return new DerTaggedObject(tag == DirectoryName, tag, obj); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/GeneralNames.cs b/bc-sharp-crypto/src/asn1/x509/GeneralNames.cs new file mode 100644 index 0000000..fcd2ecb --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/GeneralNames.cs @@ -0,0 +1,95 @@ +using System; +using System.Text; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.X509 +{ + public class GeneralNames + : Asn1Encodable + { + private readonly GeneralName[] names; + + public static GeneralNames GetInstance( + object obj) + { + if (obj == null || obj is GeneralNames) + { + return (GeneralNames) obj; + } + + if (obj is Asn1Sequence) + { + return new GeneralNames((Asn1Sequence) obj); + } + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + public static GeneralNames GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); + } + + /// Construct a GeneralNames object containing one GeneralName. + /// The name to be contained. + public GeneralNames( + GeneralName name) + { + names = new GeneralName[]{ name }; + } + + public GeneralNames( + GeneralName[] names) + { + this.names = (GeneralName[])names.Clone(); + } + + private GeneralNames( + Asn1Sequence seq) + { + this.names = new GeneralName[seq.Count]; + + for (int i = 0; i != seq.Count; i++) + { + names[i] = GeneralName.GetInstance(seq[i]); + } + } + + public GeneralName[] GetNames() + { + return (GeneralName[]) names.Clone(); + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *
+		 * GeneralNames ::= Sequence SIZE {1..MAX} OF GeneralName
+		 * 
+ */ + public override Asn1Object ToAsn1Object() + { + return new DerSequence(names); + } + + public override string ToString() + { + StringBuilder buf = new StringBuilder(); + string sep = Platform.NewLine; + + buf.Append("GeneralNames:"); + buf.Append(sep); + + foreach (GeneralName name in names) + { + buf.Append(" "); + buf.Append(name); + buf.Append(sep); + } + + return buf.ToString(); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/GeneralSubtree.cs b/bc-sharp-crypto/src/asn1/x509/GeneralSubtree.cs new file mode 100644 index 0000000..e918a02 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/GeneralSubtree.cs @@ -0,0 +1,189 @@ +using System; + +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Asn1.X509 +{ + /** + * Class for containing a restriction object subtrees in NameConstraints. See + * RFC 3280. + * + *
+	 *
+	 *       GeneralSubtree ::= SEQUENCE
+	 *       {
+	 *         baseName                    GeneralName,
+	 *         minimum         [0]     BaseDistance DEFAULT 0,
+	 *         maximum         [1]     BaseDistance OPTIONAL
+	 *       }
+	 * 
+ * + * @see org.bouncycastle.asn1.x509.NameConstraints + * + */ + public class GeneralSubtree + : Asn1Encodable + { + private readonly GeneralName baseName; + private readonly DerInteger minimum; + private readonly DerInteger maximum; + + private GeneralSubtree( + Asn1Sequence seq) + { + baseName = GeneralName.GetInstance(seq[0]); + + switch (seq.Count) + { + case 1: + break; + case 2: + { + Asn1TaggedObject o = Asn1TaggedObject.GetInstance(seq[1]); + switch (o.TagNo) + { + case 0: + minimum = DerInteger.GetInstance(o, false); + break; + case 1: + maximum = DerInteger.GetInstance(o, false); + break; + default: + throw new ArgumentException("Bad tag number: " + o.TagNo); + } + break; + } + case 3: + { + { + Asn1TaggedObject oMin = Asn1TaggedObject.GetInstance(seq[1]); + if (oMin.TagNo != 0) + throw new ArgumentException("Bad tag number for 'minimum': " + oMin.TagNo); + minimum = DerInteger.GetInstance(oMin, false); + } + + { + Asn1TaggedObject oMax = Asn1TaggedObject.GetInstance(seq[2]); + if (oMax.TagNo != 1) + throw new ArgumentException("Bad tag number for 'maximum': " + oMax.TagNo); + maximum = DerInteger.GetInstance(oMax, false); + } + + break; + } + default: + throw new ArgumentException("Bad sequence size: " + seq.Count); + } + } + + /** + * 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 baseName + * A restriction. + * @param minimum + * Minimum + * + * @param maximum + * Maximum + */ + public GeneralSubtree( + GeneralName baseName, + BigInteger minimum, + BigInteger maximum) + { + this.baseName = baseName; + if (minimum != null) + { + this.minimum = new DerInteger(minimum); + } + if (maximum != null) + { + this.maximum = new DerInteger(maximum); + } + } + + public GeneralSubtree( + GeneralName baseName) + : this(baseName, null, null) + { + } + + public static GeneralSubtree GetInstance( + Asn1TaggedObject o, + bool isExplicit) + { + return new GeneralSubtree(Asn1Sequence.GetInstance(o, isExplicit)); + } + + public static GeneralSubtree GetInstance( + object obj) + { + if (obj == null) + { + return null; + } + + if (obj is GeneralSubtree) + { + return (GeneralSubtree) obj; + } + + return new GeneralSubtree(Asn1Sequence.GetInstance(obj)); + } + + public GeneralName Base + { + get { return baseName; } + } + + public BigInteger Minimum + { + get { return minimum == null ? BigInteger.Zero : minimum.Value; } + } + + public BigInteger Maximum + { + get { return maximum == null ? null : maximum.Value; } + } + + /** + * Produce an object suitable for an Asn1OutputStream. + * + * Returns: + * + *
+		 *       GeneralSubtree ::= SEQUENCE
+		 *       {
+		 *         baseName                    GeneralName,
+		 *         minimum         [0]     BaseDistance DEFAULT 0,
+		 *         maximum         [1]     BaseDistance OPTIONAL
+		 *       }
+		 * 
+ * + * @return a DERObject + */ + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(baseName); + + if (minimum != null && minimum.Value.SignValue != 0) + { + v.Add(new DerTaggedObject(false, 0, minimum)); + } + + if (maximum != null) + { + v.Add(new DerTaggedObject(false, 1, maximum)); + } + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/Holder.cs b/bc-sharp-crypto/src/asn1/x509/Holder.cs new file mode 100644 index 0000000..6e5315b --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/Holder.cs @@ -0,0 +1,259 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.X509 +{ + /** + * 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 + : Asn1Encodable + { + internal readonly IssuerSerial baseCertificateID; + internal readonly GeneralNames entityName; + internal readonly ObjectDigestInfo objectDigestInfo; + private readonly int version; + + public static Holder GetInstance( + object obj) + { + if (obj is Holder) + { + return (Holder) obj; + } + + if (obj is Asn1Sequence) + { + return new Holder((Asn1Sequence) obj); + } + + if (obj is Asn1TaggedObject) + { + return new Holder((Asn1TaggedObject) obj); + } + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + /** + * Constructor for a holder for an v1 attribute certificate. + * + * @param tagObj The ASN.1 tagged holder object. + */ + public Holder( + Asn1TaggedObject tagObj) + { + switch (tagObj.TagNo) + { + case 0: + baseCertificateID = IssuerSerial.GetInstance(tagObj, false); + break; + case 1: + entityName = GeneralNames.GetInstance(tagObj, false); + break; + default: + throw new ArgumentException("unknown tag in Holder"); + } + + this.version = 0; + } + + /** + * Constructor for a holder for an v2 attribute certificate. * + * + * @param seq The ASN.1 sequence. + */ + private Holder( + Asn1Sequence seq) + { + if (seq.Count > 3) + throw new ArgumentException("Bad sequence size: " + seq.Count); + + for (int i = 0; i != seq.Count; i++) + { + Asn1TaggedObject tObj = Asn1TaggedObject.GetInstance(seq[i]); + + switch (tObj.TagNo) + { + 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 ArgumentException("unknown tag in Holder"); + } + } + + this.version = 1; + } + + public Holder( + IssuerSerial baseCertificateID) + : this(baseCertificateID, 1) + { + } + + /** + * 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 Version + { + get { 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, 1) + { + } + + /** + * 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; + this.version = 1; + } + + public IssuerSerial BaseCertificateID + { + get { 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 EntityName + { + get { return entityName; } + } + + public ObjectDigestInfo ObjectDigestInfo + { + get { return objectDigestInfo; } + } + + /** + * The Holder object. + *
+         *  Holder ::= Sequence {
+         *        baseCertificateID   [0] IssuerSerial OPTIONAL,
+         *                 -- the issuer and serial number of
+         *                 -- the holder's Public Key Certificate
+         *        entityName          [1] GeneralNames OPTIONAL,
+         *                 -- the name of the claimant or role
+         *        objectDigestInfo    [2] ObjectDigestInfo OPTIONAL
+         *                 -- used to directly authenticate the holder,
+         *                 -- for example, an executable
+         *  }
+         * 
+ */ + public override Asn1Object 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); + } + + if (entityName != null) + { + return new DerTaggedObject(false, 1, entityName); + } + + return new DerTaggedObject(false, 0, baseCertificateID); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/IetfAttrSyntax.cs b/bc-sharp-crypto/src/asn1/x509/IetfAttrSyntax.cs new file mode 100644 index 0000000..e719865 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/IetfAttrSyntax.cs @@ -0,0 +1,161 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1; + +namespace Org.BouncyCastle.Asn1.X509 +{ + /** + * Implementation of IetfAttrSyntax as specified by RFC3281. + */ + public class IetfAttrSyntax + : Asn1Encodable + { + public const int ValueOctets = 1; + public const int ValueOid = 2; + public const int ValueUtf8 = 3; + + internal readonly GeneralNames policyAuthority; + internal readonly Asn1EncodableVector values = new Asn1EncodableVector(); + + internal int valueChoice = -1; + + /** + * + */ + public IetfAttrSyntax( + Asn1Sequence seq) + { + int i = 0; + + if (seq[0] is Asn1TaggedObject) + { + policyAuthority = GeneralNames.GetInstance(((Asn1TaggedObject)seq[0]), false); + i++; + } + else if (seq.Count == 2) + { // VOMS fix + policyAuthority = GeneralNames.GetInstance(seq[0]); + i++; + } + + if (!(seq[i] is Asn1Sequence)) + { + throw new ArgumentException("Non-IetfAttrSyntax encoding"); + } + + seq = (Asn1Sequence) seq[i]; + + foreach (Asn1Object obj in seq) + { + int type; + + if (obj is DerObjectIdentifier) + { + type = ValueOid; + } + else if (obj is DerUtf8String) + { + type = ValueUtf8; + } + else if (obj is DerOctetString) + { + type = ValueOctets; + } + else + { + throw new ArgumentException("Bad value type encoding IetfAttrSyntax"); + } + + if (valueChoice < 0) + { + valueChoice = type; + } + + if (type != valueChoice) + { + throw new ArgumentException("Mix of value types in IetfAttrSyntax"); + } + + values.Add(obj); + } + } + + public GeneralNames PolicyAuthority + { + get { return policyAuthority; } + } + + public int ValueType + { + get { return valueChoice; } + } + + public object[] GetValues() + { + if (this.ValueType == ValueOctets) + { + Asn1OctetString[] tmp = new Asn1OctetString[values.Count]; + + for (int i = 0; i != tmp.Length; i++) + { + tmp[i] = (Asn1OctetString) values[i]; + } + + return tmp; + } + + if (this.ValueType == ValueOid) + { + DerObjectIdentifier[] tmp = new DerObjectIdentifier[values.Count]; + + for (int i = 0; i != tmp.Length; i++) + { + tmp[i] = (DerObjectIdentifier) values[i]; + } + + return tmp; + } + + { + DerUtf8String[] tmp = new DerUtf8String[values.Count]; + + for (int i = 0; i != tmp.Length; i++) + { + tmp[i] = (DerUtf8String) values[i]; + } + + return tmp; + } + } + + /** + * + *
+         *
+         *  IetfAttrSyntax ::= Sequence {
+         *    policyAuthority [0] GeneralNames OPTIONAL,
+         *    values Sequence OF CHOICE {
+         *      octets OCTET STRING,
+         *      oid OBJECT IDENTIFIER,
+         *      string UTF8String
+         *    }
+         *  }
+         *
+         * 
+ */ + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(); + + if (policyAuthority != null) + { + v.Add(new DerTaggedObject(0, policyAuthority)); + } + + v.Add(new DerSequence(values)); + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/IssuerSerial.cs b/bc-sharp-crypto/src/asn1/x509/IssuerSerial.cs new file mode 100644 index 0000000..1e47e02 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/IssuerSerial.cs @@ -0,0 +1,100 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.X509 +{ + public class IssuerSerial + : Asn1Encodable + { + internal readonly GeneralNames issuer; + internal readonly DerInteger serial; + internal readonly DerBitString issuerUid; + + public static IssuerSerial GetInstance( + object obj) + { + if (obj == null || obj is IssuerSerial) + { + return (IssuerSerial) obj; + } + + if (obj is Asn1Sequence) + { + return new IssuerSerial((Asn1Sequence) obj); + } + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + public static IssuerSerial GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); + } + + private IssuerSerial( + Asn1Sequence seq) + { + if (seq.Count != 2 && seq.Count != 3) + { + throw new ArgumentException("Bad sequence size: " + seq.Count); + } + + issuer = GeneralNames.GetInstance(seq[0]); + serial = DerInteger.GetInstance(seq[1]); + + if (seq.Count == 3) + { + issuerUid = DerBitString.GetInstance(seq[2]); + } + } + + public IssuerSerial( + GeneralNames issuer, + DerInteger serial) + { + this.issuer = issuer; + this.serial = serial; + } + + public GeneralNames Issuer + { + get { return issuer; } + } + + public DerInteger Serial + { + get { return serial; } + } + + public DerBitString IssuerUid + { + get { return issuerUid; } + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *
+         *  IssuerSerial  ::=  Sequence {
+         *       issuer         GeneralNames,
+         *       serial         CertificateSerialNumber,
+         *       issuerUid      UniqueIdentifier OPTIONAL
+         *  }
+         * 
+ */ + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector( + issuer, serial); + + if (issuerUid != null) + { + v.Add(issuerUid); + } + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/IssuingDistributionPoint.cs b/bc-sharp-crypto/src/asn1/x509/IssuingDistributionPoint.cs new file mode 100644 index 0000000..8e9362b --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/IssuingDistributionPoint.cs @@ -0,0 +1,247 @@ +using System; +using System.Text; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.X509 +{ + /** + *
+	 * 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 + : Asn1Encodable + { + private readonly DistributionPointName _distributionPoint; + private readonly bool _onlyContainsUserCerts; + private readonly bool _onlyContainsCACerts; + private readonly ReasonFlags _onlySomeReasons; + private readonly bool _indirectCRL; + private readonly bool _onlyContainsAttributeCerts; + + private readonly Asn1Sequence seq; + + public static IssuingDistributionPoint GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); + } + + public static IssuingDistributionPoint GetInstance( + object obj) + { + if (obj == null || obj is IssuingDistributionPoint) + { + return (IssuingDistributionPoint) obj; + } + + if (obj is Asn1Sequence) + { + return new IssuingDistributionPoint((Asn1Sequence) obj); + } + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + /** + * 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, + bool onlyContainsUserCerts, + bool onlyContainsCACerts, + ReasonFlags onlySomeReasons, + bool indirectCRL, + bool 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, DerBoolean.True)); + } + if (onlyContainsCACerts) + { + vec.Add(new DerTaggedObject(false, 2, DerBoolean.True)); + } + if (onlySomeReasons != null) + { + vec.Add(new DerTaggedObject(false, 3, onlySomeReasons)); + } + if (indirectCRL) + { + vec.Add(new DerTaggedObject(false, 4, DerBoolean.True)); + } + if (onlyContainsAttributeCerts) + { + vec.Add(new DerTaggedObject(false, 5, DerBoolean.True)); + } + + seq = new DerSequence(vec); + } + + /** + * Constructor from Asn1Sequence + */ + private IssuingDistributionPoint( + Asn1Sequence seq) + { + this.seq = seq; + + for (int i = 0; i != seq.Count; i++) + { + Asn1TaggedObject o = Asn1TaggedObject.GetInstance(seq[i]); + + switch (o.TagNo) + { + 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 ArgumentException("unknown tag in IssuingDistributionPoint"); + } + } + } + + public bool OnlyContainsUserCerts + { + get { return _onlyContainsUserCerts; } + } + + public bool OnlyContainsCACerts + { + get { return _onlyContainsCACerts; } + } + + public bool IsIndirectCrl + { + get { return _indirectCRL; } + } + + public bool OnlyContainsAttributeCerts + { + get { return _onlyContainsAttributeCerts; } + } + + /** + * @return Returns the distributionPoint. + */ + public DistributionPointName DistributionPoint + { + get { return _distributionPoint; } + } + + /** + * @return Returns the onlySomeReasons. + */ + public ReasonFlags OnlySomeReasons + { + get { return _onlySomeReasons; } + } + + public override Asn1Object ToAsn1Object() + { + return seq; + } + + public override string ToString() + { + string sep = Platform.NewLine; + StringBuilder buf = new StringBuilder(); + + buf.Append("IssuingDistributionPoint: ["); + buf.Append(sep); + if (_distributionPoint != null) + { + appendObject(buf, sep, "distributionPoint", _distributionPoint.ToString()); + } + if (_onlyContainsUserCerts) + { + appendObject(buf, sep, "onlyContainsUserCerts", _onlyContainsUserCerts.ToString()); + } + if (_onlyContainsCACerts) + { + appendObject(buf, sep, "onlyContainsCACerts", _onlyContainsCACerts.ToString()); + } + if (_onlySomeReasons != null) + { + appendObject(buf, sep, "onlySomeReasons", _onlySomeReasons.ToString()); + } + if (_onlyContainsAttributeCerts) + { + appendObject(buf, sep, "onlyContainsAttributeCerts", _onlyContainsAttributeCerts.ToString()); + } + if (_indirectCRL) + { + appendObject(buf, sep, "indirectCRL", _indirectCRL.ToString()); + } + buf.Append("]"); + buf.Append(sep); + return buf.ToString(); + } + + private void appendObject( + StringBuilder buf, + string sep, + string name, + string val) + { + string indent = " "; + + buf.Append(indent); + buf.Append(name); + buf.Append(":"); + buf.Append(sep); + buf.Append(indent); + buf.Append(indent); + buf.Append(val); + buf.Append(sep); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/KeyPurposeId.cs b/bc-sharp-crypto/src/asn1/x509/KeyPurposeId.cs new file mode 100644 index 0000000..1a564b9 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/KeyPurposeId.cs @@ -0,0 +1,38 @@ +namespace Org.BouncyCastle.Asn1.X509 +{ + /** + * The KeyPurposeID object. + *
+     *     KeyPurposeID ::= OBJECT IDENTIFIER
+     * 
+ */ + public sealed class KeyPurposeID + : DerObjectIdentifier + { + private const string IdKP = "1.3.6.1.5.5.7.3"; + + private KeyPurposeID( + string id) + : base(id) + { + } + + public static readonly KeyPurposeID AnyExtendedKeyUsage = new KeyPurposeID(X509Extensions.ExtendedKeyUsage.Id + ".0"); + public static readonly KeyPurposeID IdKPServerAuth = new KeyPurposeID(IdKP + ".1"); + public static readonly KeyPurposeID IdKPClientAuth = new KeyPurposeID(IdKP + ".2"); + public static readonly KeyPurposeID IdKPCodeSigning = new KeyPurposeID(IdKP + ".3"); + public static readonly KeyPurposeID IdKPEmailProtection = new KeyPurposeID(IdKP + ".4"); + public static readonly KeyPurposeID IdKPIpsecEndSystem = new KeyPurposeID(IdKP + ".5"); + public static readonly KeyPurposeID IdKPIpsecTunnel = new KeyPurposeID(IdKP + ".6"); + public static readonly KeyPurposeID IdKPIpsecUser = new KeyPurposeID(IdKP + ".7"); + public static readonly KeyPurposeID IdKPTimeStamping = new KeyPurposeID(IdKP + ".8"); + public static readonly KeyPurposeID IdKPOcspSigning = new KeyPurposeID(IdKP + ".9"); + + // + // microsoft key purpose ids + // + public static readonly KeyPurposeID IdKPSmartCardLogon = new KeyPurposeID("1.3.6.1.4.1.311.20.2.2"); + + public static readonly KeyPurposeID IdKPMacAddress = new KeyPurposeID("1.3.6.1.1.1.1.22"); + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/KeyUsage.cs b/bc-sharp-crypto/src/asn1/x509/KeyUsage.cs new file mode 100644 index 0000000..aeaffb7 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/KeyUsage.cs @@ -0,0 +1,78 @@ +namespace Org.BouncyCastle.Asn1.X509 +{ + /** + * 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 + : DerBitString + { + public const int DigitalSignature = (1 << 7); + public const int NonRepudiation = (1 << 6); + public const int KeyEncipherment = (1 << 5); + public const int DataEncipherment = (1 << 4); + public const int KeyAgreement = (1 << 3); + public const int KeyCertSign = (1 << 2); + public const int CrlSign = (1 << 1); + public const int EncipherOnly = (1 << 0); + public const int DecipherOnly = (1 << 15); + + public static new KeyUsage GetInstance( + object obj) + { + if (obj is KeyUsage) + { + return (KeyUsage)obj; + } + + if (obj is X509Extension) + { + return 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) + : base(usage) + { + } + + private KeyUsage( + DerBitString usage) + : base(usage.GetBytes(), usage.PadBits) + { + } + + public override string ToString() + { + byte[] data = GetBytes(); + if (data.Length == 1) + { + return "KeyUsage: 0x" + (data[0] & 0xff).ToString("X"); + } + + return "KeyUsage: 0x" + ((data[1] & 0xff) << 8 | (data[0] & 0xff)).ToString("X"); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/NameConstraints.cs b/bc-sharp-crypto/src/asn1/x509/NameConstraints.cs new file mode 100644 index 0000000..0c5fea8 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/NameConstraints.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.X509 +{ + public class NameConstraints + : Asn1Encodable + { + private Asn1Sequence permitted, excluded; + + public static NameConstraints GetInstance( + object obj) + { + if (obj == null || obj is NameConstraints) + { + return (NameConstraints) obj; + } + + if (obj is Asn1Sequence) + { + return new NameConstraints((Asn1Sequence) obj); + } + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + public NameConstraints( + Asn1Sequence seq) + { + foreach (Asn1TaggedObject o in seq) + { + switch (o.TagNo) + { + case 0: + permitted = Asn1Sequence.GetInstance(o, false); + break; + case 1: + excluded = Asn1Sequence.GetInstance(o, false); + break; + } + } + } + +#if !(SILVERLIGHT || PORTABLE) + public NameConstraints( + ArrayList permitted, + ArrayList excluded) + : this((IList)permitted, (IList)excluded) + { + } +#endif + + /** + * Constructor from a given details. + * + *

permitted and excluded are Vectors of GeneralSubtree objects.

+ * + * @param permitted Permitted subtrees + * @param excluded Excluded subtrees + */ + public NameConstraints( + IList permitted, + IList excluded) + { + if (permitted != null) + { + this.permitted = CreateSequence(permitted); + } + + if (excluded != null) + { + this.excluded = CreateSequence(excluded); + } + } + + private DerSequence CreateSequence( + IList subtrees) + { + GeneralSubtree[] gsts = new GeneralSubtree[subtrees.Count]; + for (int i = 0; i < subtrees.Count; ++i) + { + gsts[i] = (GeneralSubtree)subtrees[i]; + } + return new DerSequence(gsts); + } + + public Asn1Sequence PermittedSubtrees + { + get { return permitted; } + } + + public Asn1Sequence ExcludedSubtrees + { + get { return excluded; } + } + + /* + * NameConstraints ::= SEQUENCE { permittedSubtrees [0] GeneralSubtrees + * OPTIONAL, excludedSubtrees [1] GeneralSubtrees OPTIONAL } + */ + public override Asn1Object 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/bc-sharp-crypto/src/asn1/x509/NoticeReference.cs b/bc-sharp-crypto/src/asn1/x509/NoticeReference.cs new file mode 100644 index 0000000..f0d3a7b --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/NoticeReference.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Asn1.X509 +{ + /** + * 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 + : Asn1Encodable + { + private readonly DisplayText organization; + private readonly Asn1Sequence noticeNumbers; + + private static Asn1EncodableVector ConvertVector(IList numbers) + { + Asn1EncodableVector av = new Asn1EncodableVector(); + + foreach (object o in numbers) + { + DerInteger di; + + if (o is BigInteger) + { + di = new DerInteger((BigInteger)o); + } + else if (o is int) + { + di = new DerInteger((int)o); + } + else + { + throw new ArgumentException(); + } + + av.Add(di); + } + return av; + } + + /** + * Creates a new NoticeReference instance. + * + * @param organization a String value + * @param numbers a Vector value + */ + public NoticeReference(string organization, IList numbers) + : this(organization, ConvertVector(numbers)) + { + } + + /** + * Creates a new NoticeReference instance. + * + * @param organization a String value + * @param noticeNumbers an ASN1EncodableVector value + */ + public NoticeReference(string organization, Asn1EncodableVector noticeNumbers) + : this(new DisplayText(organization), noticeNumbers) + { + } + + /** + * Creates a new NoticeReference instance. + * + * @param organization displayText + * @param noticeNumbers an ASN1EncodableVector value + */ + public NoticeReference(DisplayText organization, Asn1EncodableVector noticeNumbers) + { + this.organization = organization; + this.noticeNumbers = new DerSequence(noticeNumbers); + } + + /** + * 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. + */ + private NoticeReference(Asn1Sequence seq) + { + if (seq.Count != 2) + throw new ArgumentException("Bad sequence size: " + seq.Count, "seq"); + + organization = DisplayText.GetInstance(seq[0]); + noticeNumbers = Asn1Sequence.GetInstance(seq[1]); + } + + public static NoticeReference GetInstance(object obj) + { + if (obj is NoticeReference) + return (NoticeReference)obj; + if (obj == null) + return null; + return new NoticeReference(Asn1Sequence.GetInstance(obj)); + } + + public virtual DisplayText Organization + { + get { return organization; } + } + + public virtual DerInteger[] GetNoticeNumbers() + { + DerInteger[] tmp = new DerInteger[noticeNumbers.Count]; + + for (int i = 0; i != noticeNumbers.Count; ++i) + { + tmp[i] = DerInteger.GetInstance(noticeNumbers[i]); + } + + return tmp; + } + + /** + * Describe ToAsn1Object method here. + * + * @return a Asn1Object value + */ + public override Asn1Object ToAsn1Object() + { + return new DerSequence(organization, noticeNumbers); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/ObjectDigestInfo.cs b/bc-sharp-crypto/src/asn1/x509/ObjectDigestInfo.cs new file mode 100644 index 0000000..9cd9a5f --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/ObjectDigestInfo.cs @@ -0,0 +1,179 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.X509 +{ + /** + * 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 + : Asn1Encodable + { + /** + * The public key is hashed. + */ + public const int PublicKey = 0; + + /** + * The public key certificate is hashed. + */ + public const int PublicKeyCert = 1; + + /** + * An other object is hashed. + */ + public const int OtherObjectDigest = 2; + + internal readonly DerEnumerated digestedObjectType; + internal readonly DerObjectIdentifier otherObjectTypeID; + internal readonly AlgorithmIdentifier digestAlgorithm; + internal readonly DerBitString objectDigest; + + public static ObjectDigestInfo GetInstance( + object obj) + { + if (obj == null || obj is ObjectDigestInfo) + { + return (ObjectDigestInfo) obj; + } + + if (obj is Asn1Sequence) + { + return new ObjectDigestInfo((Asn1Sequence) obj); + } + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + public static ObjectDigestInfo GetInstance( + Asn1TaggedObject obj, + bool isExplicit) + { + return GetInstance(Asn1Sequence.GetInstance(obj, isExplicit)); + } + + /** + * 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.Count > 4 || seq.Count < 3) + { + throw new ArgumentException("Bad sequence size: " + seq.Count); + } + + digestedObjectType = DerEnumerated.GetInstance(seq[0]); + + int offset = 0; + + if (seq.Count == 4) + { + otherObjectTypeID = DerObjectIdentifier.GetInstance(seq[1]); + offset++; + } + + digestAlgorithm = AlgorithmIdentifier.GetInstance(seq[1 + offset]); + objectDigest = DerBitString.GetInstance(seq[2 + offset]); + } + + public DerEnumerated DigestedObjectType + { + get { return digestedObjectType; } + } + + public DerObjectIdentifier OtherObjectTypeID + { + get { return otherObjectTypeID; } + } + + public AlgorithmIdentifier DigestAlgorithm + { + get { return digestAlgorithm; } + } + + public DerBitString ObjectDigest + { + get { 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 override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(digestedObjectType); + + if (otherObjectTypeID != null) + { + v.Add(otherObjectTypeID); + } + + v.Add(digestAlgorithm, objectDigest); + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/PolicyInformation.cs b/bc-sharp-crypto/src/asn1/x509/PolicyInformation.cs new file mode 100644 index 0000000..29d2450 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/PolicyInformation.cs @@ -0,0 +1,80 @@ +using System; + +namespace Org.BouncyCastle.Asn1.X509 +{ + public class PolicyInformation + : Asn1Encodable + { + private readonly DerObjectIdentifier policyIdentifier; + private readonly Asn1Sequence policyQualifiers; + + private PolicyInformation( + Asn1Sequence seq) + { + if (seq.Count < 1 || seq.Count > 2) + { + throw new ArgumentException("Bad sequence size: " + seq.Count); + } + + policyIdentifier = DerObjectIdentifier.GetInstance(seq[0]); + + if (seq.Count > 1) + { + policyQualifiers = Asn1Sequence.GetInstance(seq[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 is PolicyInformation) + { + return (PolicyInformation) obj; + } + + return new PolicyInformation(Asn1Sequence.GetInstance(obj)); + } + + public DerObjectIdentifier PolicyIdentifier + { + get { return policyIdentifier; } + } + + public Asn1Sequence PolicyQualifiers + { + get { return policyQualifiers; } + } + + /* + * PolicyInformation ::= Sequence { + * policyIdentifier CertPolicyId, + * policyQualifiers Sequence SIZE (1..MAX) OF + * PolicyQualifierInfo OPTIONAL } + */ + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(policyIdentifier); + + if (policyQualifiers != null) + { + v.Add(policyQualifiers); + } + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/PolicyMappings.cs b/bc-sharp-crypto/src/asn1/x509/PolicyMappings.cs new file mode 100644 index 0000000..928ad13 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/PolicyMappings.cs @@ -0,0 +1,70 @@ +using System.Collections; + +namespace Org.BouncyCastle.Asn1.X509 +{ + /** + * 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 + : Asn1Encodable + { + private readonly Asn1Sequence seq; + + /** + * Creates a new PolicyMappings instance. + * + * @param seq an Asn1Sequence constructed as specified + * in RFC 3280 + */ + public PolicyMappings( + Asn1Sequence seq) + { + this.seq = seq; + } + +#if !(SILVERLIGHT || PORTABLE) + public PolicyMappings( + Hashtable mappings) + : this((IDictionary)mappings) + { + } +#endif + + /** + * Creates a new PolicyMappings instance. + * + * @param mappings a HashMap value that maps + * string oids + * to other string oids. + */ + public PolicyMappings( + IDictionary mappings) + { + Asn1EncodableVector v = new Asn1EncodableVector(); + + foreach (string idp in mappings.Keys) + { + string sdp = (string) mappings[idp]; + + v.Add( + new DerSequence( + new DerObjectIdentifier(idp), + new DerObjectIdentifier(sdp))); + } + + seq = new DerSequence(v); + } + + public override Asn1Object ToAsn1Object() + { + return seq; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/PolicyQualifierId.cs b/bc-sharp-crypto/src/asn1/x509/PolicyQualifierId.cs new file mode 100644 index 0000000..c858f08 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/PolicyQualifierId.cs @@ -0,0 +1,28 @@ +namespace Org.BouncyCastle.Asn1.X509 +{ + /** + * 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 sealed class PolicyQualifierID : DerObjectIdentifier + { + private const string IdQt = "1.3.6.1.5.5.7.2"; + + private PolicyQualifierID( + string id) + : base(id) + { + } + + public static readonly PolicyQualifierID IdQtCps = new PolicyQualifierID(IdQt + ".1"); + public static readonly PolicyQualifierID IdQtUnotice = new PolicyQualifierID(IdQt + ".2"); + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/PolicyQualifierInfo.cs b/bc-sharp-crypto/src/asn1/x509/PolicyQualifierInfo.cs new file mode 100644 index 0000000..3cf6d7e --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/PolicyQualifierInfo.cs @@ -0,0 +1,95 @@ +using System; + +namespace Org.BouncyCastle.Asn1.X509 +{ + /** + * Policy qualifiers, used in the X509V3 CertificatePolicies + * extension. + * + *
+     *   PolicyQualifierInfo ::= Sequence {
+     *       policyQualifierId  PolicyQualifierId,
+     *       qualifier          ANY DEFINED BY policyQualifierId }
+     * 
+ */ + public class PolicyQualifierInfo + : Asn1Encodable + { + private readonly DerObjectIdentifier policyQualifierId; + private readonly Asn1Encodable qualifier; + + /** + * Creates a new PolicyQualifierInfo instance. + * + * @param policyQualifierId a PolicyQualifierId value + * @param qualifier the qualifier, defined by the above field. + */ + public PolicyQualifierInfo( + DerObjectIdentifier policyQualifierId, + Asn1Encodable 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.IdQtCps; + qualifier = new DerIA5String(cps); + } + + /** + * Creates a new PolicyQualifierInfo instance. + * + * @param as PolicyQualifierInfo X509 structure + * encoded as an Asn1Sequence. + */ + private PolicyQualifierInfo( + Asn1Sequence seq) + { + if (seq.Count != 2) + throw new ArgumentException("Bad sequence size: " + seq.Count, "seq"); + + policyQualifierId = DerObjectIdentifier.GetInstance(seq[0]); + qualifier = seq[1]; + } + + public static PolicyQualifierInfo GetInstance( + object obj) + { + if (obj is PolicyQualifierInfo) + return (PolicyQualifierInfo)obj; + if (obj == null) + return null; + return new PolicyQualifierInfo(Asn1Sequence.GetInstance(obj)); + } + + public virtual DerObjectIdentifier PolicyQualifierId + { + get { return policyQualifierId; } + } + + public virtual Asn1Encodable Qualifier + { + get { return qualifier; } + } + + /** + * Returns a Der-encodable representation of this instance. + * + * @return a Asn1Object value + */ + public override Asn1Object ToAsn1Object() + { + return new DerSequence(policyQualifierId, qualifier); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/PrivateKeyUsagePeriod.cs b/bc-sharp-crypto/src/asn1/x509/PrivateKeyUsagePeriod.cs new file mode 100644 index 0000000..a3d7a36 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/PrivateKeyUsagePeriod.cs @@ -0,0 +1,84 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.X509 +{ + /// + ///
+	/// PrivateKeyUsagePeriod ::= SEQUENCE
+	/// {
+	/// notBefore       [0]     GeneralizedTime OPTIONAL,
+	/// notAfter        [1]     GeneralizedTime OPTIONAL }
+	/// 
+ ///
+ public class PrivateKeyUsagePeriod + : Asn1Encodable + { + public static PrivateKeyUsagePeriod GetInstance( + object obj) + { + if (obj is PrivateKeyUsagePeriod) + { + return (PrivateKeyUsagePeriod) obj; + } + + if (obj is Asn1Sequence) + { + return new PrivateKeyUsagePeriod((Asn1Sequence) obj); + } + + if (obj is X509Extension) + { + return GetInstance(X509Extension.ConvertValueToObject((X509Extension) obj)); + } + + throw new ArgumentException("unknown object in GetInstance: " + Platform.GetTypeName(obj), "obj"); + } + + private DerGeneralizedTime _notBefore, _notAfter; + + private PrivateKeyUsagePeriod( + Asn1Sequence seq) + { + foreach (Asn1TaggedObject tObj in seq) + { + if (tObj.TagNo == 0) + { + _notBefore = DerGeneralizedTime.GetInstance(tObj, false); + } + else if (tObj.TagNo == 1) + { + _notAfter = DerGeneralizedTime.GetInstance(tObj, false); + } + } + } + + public DerGeneralizedTime NotBefore + { + get { return _notBefore; } + } + + public DerGeneralizedTime NotAfter + { + get { return _notAfter; } + } + + public override Asn1Object 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/bc-sharp-crypto/src/asn1/x509/RSAPublicKeyStructure.cs b/bc-sharp-crypto/src/asn1/x509/RSAPublicKeyStructure.cs new file mode 100644 index 0000000..20fdd96 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/RSAPublicKeyStructure.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.X509 +{ + public class RsaPublicKeyStructure + : Asn1Encodable + { + private BigInteger modulus; + private BigInteger publicExponent; + + public static RsaPublicKeyStructure GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); + } + + public static RsaPublicKeyStructure GetInstance( + object obj) + { + if (obj == null || obj is RsaPublicKeyStructure) + { + return (RsaPublicKeyStructure) obj; + } + + if (obj is Asn1Sequence) + { + return new RsaPublicKeyStructure((Asn1Sequence) obj); + } + + throw new ArgumentException("Invalid RsaPublicKeyStructure: " + Platform.GetTypeName(obj)); + } + + public RsaPublicKeyStructure( + BigInteger modulus, + BigInteger publicExponent) + { + if (modulus == null) + throw new ArgumentNullException("modulus"); + if (publicExponent == null) + throw new ArgumentNullException("publicExponent"); + if (modulus.SignValue <= 0) + throw new ArgumentException("Not a valid RSA modulus", "modulus"); + if (publicExponent.SignValue <= 0) + throw new ArgumentException("Not a valid RSA public exponent", "publicExponent"); + + this.modulus = modulus; + this.publicExponent = publicExponent; + } + + private RsaPublicKeyStructure( + Asn1Sequence seq) + { + if (seq.Count != 2) + throw new ArgumentException("Bad sequence size: " + seq.Count); + + // Note: we are accepting technically incorrect (i.e. negative) values here + modulus = DerInteger.GetInstance(seq[0]).PositiveValue; + publicExponent = DerInteger.GetInstance(seq[1]).PositiveValue; + } + + public BigInteger Modulus + { + get { return modulus; } + } + + public BigInteger PublicExponent + { + get { return publicExponent; } + } + + /** + * This outputs the key in Pkcs1v2 format. + *
+         *      RSAPublicKey ::= Sequence {
+         *                          modulus Integer, -- n
+         *                          publicExponent Integer, -- e
+         *                      }
+         * 
+ */ + public override Asn1Object ToAsn1Object() + { + return new DerSequence( + new DerInteger(Modulus), + new DerInteger(PublicExponent)); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/ReasonFlags.cs b/bc-sharp-crypto/src/asn1/x509/ReasonFlags.cs new file mode 100644 index 0000000..ad45e84 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/ReasonFlags.cs @@ -0,0 +1,45 @@ +namespace Org.BouncyCastle.Asn1.X509 +{ + /** + * The ReasonFlags object. + *
+     * ReasonFlags ::= BIT STRING {
+     *    unused(0),
+     *    keyCompromise(1),
+     *    cACompromise(2),
+     *    affiliationChanged(3),
+     *    superseded(4),
+     *    cessationOfOperation(5),
+     *    certficateHold(6)
+     * }
+     * 
+ */ + public class ReasonFlags + : DerBitString + { + public const int Unused = (1 << 7); + public const int KeyCompromise = (1 << 6); + public const int CACompromise = (1 << 5); + public const int AffiliationChanged = (1 << 4); + public const int Superseded = (1 << 3); + public const int CessationOfOperation = (1 << 2); + public const int CertificateHold = (1 << 1); + public const int PrivilegeWithdrawn = (1 << 0); + public const 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) + : base(reasons) + { + } + + public ReasonFlags( + DerBitString reasons) + : base(reasons.GetBytes(), reasons.PadBits) + { + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/RoleSyntax.cs b/bc-sharp-crypto/src/asn1/x509/RoleSyntax.cs new file mode 100644 index 0000000..48c3c6c --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/RoleSyntax.cs @@ -0,0 +1,230 @@ +using System; +using System.Text; + +namespace Org.BouncyCastle.Asn1.X509 +{ + /** + * Implementation of the RoleSyntax object as specified by the RFC3281. + * + *
+	* RoleSyntax ::= SEQUENCE {
+	*                 roleAuthority  [0] GeneralNames OPTIONAL,
+	*                 roleName       [1] GeneralName
+	*           }
+	* 
+ */ + public class RoleSyntax + : Asn1Encodable + { + private readonly GeneralNames roleAuthority; + private readonly 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.ArgumentException if the object passed + * to the factory is not an instance of RoleSyntax or + * Asn1Sequence. + */ + public static RoleSyntax GetInstance( + object obj) + { + if (obj is RoleSyntax) + return (RoleSyntax)obj; + + if (obj != null) + return new RoleSyntax(Asn1Sequence.GetInstance(obj)); + + return null; + } + + /** + * 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.TagNo != GeneralName.UniformResourceIdentifier + || ((IAsn1String) roleName.Name).GetString().Equals("")) + { + throw new ArgumentException("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. + */ + private RoleSyntax( + Asn1Sequence seq) + { + if (seq.Count < 1 || seq.Count > 2) + { + throw new ArgumentException("Bad sequence size: " + seq.Count); + } + + for (int i = 0; i != seq.Count; i++) + { + Asn1TaggedObject taggedObject = Asn1TaggedObject.GetInstance(seq[i]); + switch (taggedObject.TagNo) + { + case 0: + roleAuthority = GeneralNames.GetInstance(taggedObject, false); + break; + case 1: + roleName = GeneralName.GetInstance(taggedObject, true); + break; + default: + throw new ArgumentException("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 RoleAuthority + { + get { return this.roleAuthority; } + } + + /** + * Gets the role name of this RoleSyntax. + * @return an instance of GeneralName holding the + * role name of this RoleSyntax. + */ + public GeneralName RoleName + { + get { return this.roleName; } + } + + /** + * Gets the role name as a java.lang.string object. + * @return the role name of this RoleSyntax represented as a + * string object. + */ + public string GetRoleNameAsString() + { + return ((IAsn1String) this.roleName.Name).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++) + { + Asn1Encodable asn1Value = names[i].Name; + if (asn1Value is IAsn1String) + { + namesString[i] = ((IAsn1String) asn1Value).GetString(); + } + else + { + namesString[i] = asn1Value.ToString(); + } + } + + return namesString; + } + + /** + * Implementation of the method ToAsn1Object as + * required by the superclass ASN1Encodable. + * + *
+		* RoleSyntax ::= SEQUENCE {
+		*                 roleAuthority  [0] GeneralNames OPTIONAL,
+		*                 roleName       [1] GeneralName
+		*           }
+		* 
+ */ + public override Asn1Object 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 override string ToString() + { + StringBuilder buff = new StringBuilder("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/bc-sharp-crypto/src/asn1/x509/SubjectDirectoryAttributes.cs b/bc-sharp-crypto/src/asn1/x509/SubjectDirectoryAttributes.cs new file mode 100644 index 0000000..77923e0 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/SubjectDirectoryAttributes.cs @@ -0,0 +1,142 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; + +namespace Org.BouncyCastle.Asn1.X509 +{ + /** + * 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 org.bouncycastle.asn1.x509.X509Name for AttributeType ObjectIdentifiers. + */ + public class SubjectDirectoryAttributes + : Asn1Encodable + { + private readonly IList attributes; + + public static SubjectDirectoryAttributes GetInstance( + object obj) + { + if (obj == null || obj is SubjectDirectoryAttributes) + { + return (SubjectDirectoryAttributes) obj; + } + + if (obj is Asn1Sequence) + { + return new SubjectDirectoryAttributes((Asn1Sequence) obj); + } + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + /** + * 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. + */ + private SubjectDirectoryAttributes( + Asn1Sequence seq) + { + this.attributes = Platform.CreateArrayList(); + foreach (object o in seq) + { + Asn1Sequence s = Asn1Sequence.GetInstance(o); + attributes.Add(AttributeX509.GetInstance(s)); + } + } + +#if !(SILVERLIGHT || PORTABLE) + [Obsolete] + public SubjectDirectoryAttributes( + ArrayList attributes) + : this((IList)attributes) + { + } +#endif + + /** + * Constructor from an ArrayList of attributes. + * + * The ArrayList consists of attributes of type {@link Attribute Attribute} + * + * @param attributes The attributes. + * + */ + public SubjectDirectoryAttributes( + IList attributes) + { + this.attributes = Platform.CreateArrayList(attributes); + } + + /** + * 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 override Asn1Object ToAsn1Object() + { + AttributeX509[] v = new AttributeX509[attributes.Count]; + for (int i = 0; i < attributes.Count; ++i) + { + v[i] = (AttributeX509)attributes[i]; + } + return new DerSequence(v); + } + + /** + * @return Returns the attributes. + */ + public IEnumerable Attributes + { + get { return new EnumerableProxy(attributes); } + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/SubjectKeyIdentifier.cs b/bc-sharp-crypto/src/asn1/x509/SubjectKeyIdentifier.cs new file mode 100644 index 0000000..f2e6cc0 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/SubjectKeyIdentifier.cs @@ -0,0 +1,142 @@ +using System; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.X509 +{ + /** + * The SubjectKeyIdentifier object. + *
+     * SubjectKeyIdentifier::= OCTET STRING
+     * 
+ */ + public class SubjectKeyIdentifier + : Asn1Encodable + { + private readonly byte[] keyIdentifier; + + public static SubjectKeyIdentifier GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(Asn1OctetString.GetInstance(obj, explicitly)); + } + + public static SubjectKeyIdentifier GetInstance( + object obj) + { + if (obj is SubjectKeyIdentifier) + { + return (SubjectKeyIdentifier) obj; + } + + if (obj is SubjectPublicKeyInfo) + { + return new SubjectKeyIdentifier((SubjectPublicKeyInfo) obj); + } + + if (obj is Asn1OctetString) + { + return new SubjectKeyIdentifier((Asn1OctetString) obj); + } + + if (obj is X509Extension) + { + return GetInstance(X509Extension.ConvertValueToObject((X509Extension) obj)); + } + + throw new ArgumentException("Invalid SubjectKeyIdentifier: " + Platform.GetTypeName(obj)); + } + + public SubjectKeyIdentifier( + byte[] keyID) + { + if (keyID == null) + throw new ArgumentNullException("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 override Asn1Object 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]; + + Array.Copy(dig, dig.Length - 8, id, 0, id.Length); + + id[0] &= 0x0f; + id[0] |= 0x40; + + return new SubjectKeyIdentifier(id); + } + + private static byte[] GetDigest( + SubjectPublicKeyInfo spki) + { + IDigest digest = new Sha1Digest(); + byte[] resBuf = new byte[digest.GetDigestSize()]; + + byte[] bytes = spki.PublicKeyData.GetBytes(); + digest.BlockUpdate(bytes, 0, bytes.Length); + digest.DoFinal(resBuf, 0); + return resBuf; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/SubjectPublicKeyInfo.cs b/bc-sharp-crypto/src/asn1/x509/SubjectPublicKeyInfo.cs new file mode 100644 index 0000000..477329b --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/SubjectPublicKeyInfo.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections; +using System.IO; + +namespace Org.BouncyCastle.Asn1.X509 +{ + /** + * 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 + : Asn1Encodable + { + private readonly AlgorithmIdentifier algID; + private readonly DerBitString keyData; + + public static SubjectPublicKeyInfo GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); + } + + public static SubjectPublicKeyInfo GetInstance( + object obj) + { + if (obj is SubjectPublicKeyInfo) + return (SubjectPublicKeyInfo) obj; + + if (obj != null) + return new SubjectPublicKeyInfo(Asn1Sequence.GetInstance(obj)); + + return null; + } + + public SubjectPublicKeyInfo( + AlgorithmIdentifier algID, + Asn1Encodable publicKey) + { + this.keyData = new DerBitString(publicKey); + this.algID = algID; + } + + public SubjectPublicKeyInfo( + AlgorithmIdentifier algID, + byte[] publicKey) + { + this.keyData = new DerBitString(publicKey); + this.algID = algID; + } + + private SubjectPublicKeyInfo( + Asn1Sequence seq) + { + if (seq.Count != 2) + throw new ArgumentException("Bad sequence size: " + seq.Count, "seq"); + + this.algID = AlgorithmIdentifier.GetInstance(seq[0]); + this.keyData = DerBitString.GetInstance(seq[1]); + } + + public AlgorithmIdentifier AlgorithmID + { + get { return algID; } + } + + /** + * for when the public key is an encoded object - if the bitstring + * can't be decoded this routine raises an IOException. + * + * @exception IOException - if the bit string doesn't represent a Der + * encoded object. + */ + public Asn1Object GetPublicKey() + { + return Asn1Object.FromByteArray(keyData.GetOctets()); + } + + /** + * for when the public key is raw bits... + */ + public DerBitString PublicKeyData + { + get { return keyData; } + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *
+         * SubjectPublicKeyInfo ::= Sequence {
+         *                          algorithm AlgorithmIdentifier,
+         *                          publicKey BIT STRING }
+         * 
+ */ + public override Asn1Object ToAsn1Object() + { + return new DerSequence(algID, keyData); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/TBSCertList.cs b/bc-sharp-crypto/src/asn1/x509/TBSCertList.cs new file mode 100644 index 0000000..5767a7f --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/TBSCertList.cs @@ -0,0 +1,275 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; + +namespace Org.BouncyCastle.Asn1.X509 +{ + public class CrlEntry + : Asn1Encodable + { + internal Asn1Sequence seq; + internal DerInteger userCertificate; + internal Time revocationDate; + internal X509Extensions crlEntryExtensions; + + public CrlEntry( + Asn1Sequence seq) + { + if (seq.Count < 2 || seq.Count > 3) + { + throw new ArgumentException("Bad sequence size: " + seq.Count); + } + + this.seq = seq; + + userCertificate = DerInteger.GetInstance(seq[0]); + revocationDate = Time.GetInstance(seq[1]); + } + + public DerInteger UserCertificate + { + get { return userCertificate; } + } + + public Time RevocationDate + { + get { return revocationDate; } + } + + public X509Extensions Extensions + { + get + { + if (crlEntryExtensions == null && seq.Count == 3) + { + crlEntryExtensions = X509Extensions.GetInstance(seq[2]); + } + + return crlEntryExtensions; + } + } + + public override Asn1Object ToAsn1Object() + { + return seq; + } + } + + /** + * 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 TbsCertificateList + : Asn1Encodable + { + private class RevokedCertificatesEnumeration + : IEnumerable + { + private readonly IEnumerable en; + + internal RevokedCertificatesEnumeration( + IEnumerable en) + { + this.en = en; + } + + public IEnumerator GetEnumerator() + { + return new RevokedCertificatesEnumerator(en.GetEnumerator()); + } + + private class RevokedCertificatesEnumerator + : IEnumerator + { + private readonly IEnumerator e; + + internal RevokedCertificatesEnumerator( + IEnumerator e) + { + this.e = e; + } + + public bool MoveNext() + { + return e.MoveNext(); + } + + public void Reset() + { + e.Reset(); + } + + public object Current + { + get { return new CrlEntry(Asn1Sequence.GetInstance(e.Current)); } + } + } + } + + internal Asn1Sequence seq; + internal DerInteger version; + internal AlgorithmIdentifier signature; + internal X509Name issuer; + internal Time thisUpdate; + internal Time nextUpdate; + internal Asn1Sequence revokedCertificates; + internal X509Extensions crlExtensions; + + public static TbsCertificateList GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); + } + + public static TbsCertificateList GetInstance( + object obj) + { + TbsCertificateList list = obj as TbsCertificateList; + + if (obj == null || list != null) + { + return list; + } + + if (obj is Asn1Sequence) + { + return new TbsCertificateList((Asn1Sequence) obj); + } + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + internal TbsCertificateList( + Asn1Sequence seq) + { + if (seq.Count < 3 || seq.Count > 7) + { + throw new ArgumentException("Bad sequence size: " + seq.Count); + } + + int seqPos = 0; + + this.seq = seq; + + if (seq[seqPos] is DerInteger) + { + version = DerInteger.GetInstance(seq[seqPos++]); + } + else + { + version = new DerInteger(0); + } + + signature = AlgorithmIdentifier.GetInstance(seq[seqPos++]); + issuer = X509Name.GetInstance(seq[seqPos++]); + thisUpdate = Time.GetInstance(seq[seqPos++]); + + if (seqPos < seq.Count + && (seq[seqPos] is DerUtcTime + || seq[seqPos] is DerGeneralizedTime + || seq[seqPos] is Time)) + { + nextUpdate = Time.GetInstance(seq[seqPos++]); + } + + if (seqPos < seq.Count + && !(seq[seqPos] is DerTaggedObject)) + { + revokedCertificates = Asn1Sequence.GetInstance(seq[seqPos++]); + } + + if (seqPos < seq.Count + && seq[seqPos] is DerTaggedObject) + { + crlExtensions = X509Extensions.GetInstance(seq[seqPos]); + } + } + + public int Version + { + get { return version.Value.IntValue + 1; } + } + + public DerInteger VersionNumber + { + get { return version; } + } + + public AlgorithmIdentifier Signature + { + get { return signature; } + } + + public X509Name Issuer + { + get { return issuer; } + } + + public Time ThisUpdate + { + get { return thisUpdate; } + } + + public Time NextUpdate + { + get { return nextUpdate; } + } + + public CrlEntry[] GetRevokedCertificates() + { + if (revokedCertificates == null) + { + return new CrlEntry[0]; + } + + CrlEntry[] entries = new CrlEntry[revokedCertificates.Count]; + + for (int i = 0; i < entries.Length; i++) + { + entries[i] = new CrlEntry(Asn1Sequence.GetInstance(revokedCertificates[i])); + } + + return entries; + } + + public IEnumerable GetRevokedCertificateEnumeration() + { + if (revokedCertificates == null) + { + return EmptyEnumerable.Instance; + } + + return new RevokedCertificatesEnumeration(revokedCertificates); + } + + public X509Extensions Extensions + { + get { return crlExtensions; } + } + + public override Asn1Object ToAsn1Object() + { + return seq; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/TBSCertificateStructure.cs b/bc-sharp-crypto/src/asn1/x509/TBSCertificateStructure.cs new file mode 100644 index 0000000..fc7c39b --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/TBSCertificateStructure.cs @@ -0,0 +1,185 @@ +using System; + +using Org.BouncyCastle.Asn1.Pkcs; + +namespace Org.BouncyCastle.Asn1.X509 +{ + /** + * 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 + : Asn1Encodable + { + internal Asn1Sequence seq; + internal DerInteger version; + internal DerInteger serialNumber; + internal AlgorithmIdentifier signature; + internal X509Name issuer; + internal Time startDate, endDate; + internal X509Name subject; + internal SubjectPublicKeyInfo subjectPublicKeyInfo; + internal DerBitString issuerUniqueID; + internal DerBitString subjectUniqueID; + internal X509Extensions extensions; + + public static TbsCertificateStructure GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); + } + + public static TbsCertificateStructure GetInstance( + object obj) + { + if (obj is TbsCertificateStructure) + return (TbsCertificateStructure) obj; + + if (obj != null) + return new TbsCertificateStructure(Asn1Sequence.GetInstance(obj)); + + return null; + } + + internal TbsCertificateStructure( + Asn1Sequence seq) + { + int seqStart = 0; + + this.seq = seq; + + // + // some certficates don't include a version number - we assume v1 + // + if (seq[0] is DerTaggedObject) + { + version = DerInteger.GetInstance((Asn1TaggedObject)seq[0], true); + } + else + { + seqStart = -1; // field 0 is missing! + version = new DerInteger(0); + } + + serialNumber = DerInteger.GetInstance(seq[seqStart + 1]); + + signature = AlgorithmIdentifier.GetInstance(seq[seqStart + 2]); + issuer = X509Name.GetInstance(seq[seqStart + 3]); + + // + // before and after dates + // + Asn1Sequence dates = (Asn1Sequence)seq[seqStart + 4]; + + startDate = Time.GetInstance(dates[0]); + endDate = Time.GetInstance(dates[1]); + + subject = X509Name.GetInstance(seq[seqStart + 5]); + + // + // public key info. + // + subjectPublicKeyInfo = SubjectPublicKeyInfo.GetInstance(seq[seqStart + 6]); + + for (int extras = seq.Count - (seqStart + 6) - 1; extras > 0; extras--) + { + DerTaggedObject extra = (DerTaggedObject) seq[seqStart + 6 + extras]; + + switch (extra.TagNo) + { + case 1: + issuerUniqueID = DerBitString.GetInstance(extra, false); + break; + case 2: + subjectUniqueID = DerBitString.GetInstance(extra, false); + break; + case 3: + extensions = X509Extensions.GetInstance(extra); + break; + } + } + } + + public int Version + { + get { return version.Value.IntValue + 1; } + } + + public DerInteger VersionNumber + { + get { return version; } + } + + public DerInteger SerialNumber + { + get { return serialNumber; } + } + + public AlgorithmIdentifier Signature + { + get { return signature; } + } + + public X509Name Issuer + { + get { return issuer; } + } + + public Time StartDate + { + get { return startDate; } + } + + public Time EndDate + { + get { return endDate; } + } + + public X509Name Subject + { + get { return subject; } + } + + public SubjectPublicKeyInfo SubjectPublicKeyInfo + { + get { return subjectPublicKeyInfo; } + } + + public DerBitString IssuerUniqueID + { + get { return issuerUniqueID; } + } + + public DerBitString SubjectUniqueID + { + get { return subjectUniqueID; } + } + + public X509Extensions Extensions + { + get { return extensions; } + } + + public override Asn1Object ToAsn1Object() + { + return seq; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/Target.cs b/bc-sharp-crypto/src/asn1/x509/Target.cs new file mode 100644 index 0000000..7c4f9db --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/Target.cs @@ -0,0 +1,141 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.X509 +{ + /** + * 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 + : Asn1Encodable, IAsn1Choice + { + public enum Choice + { + Name = 0, + Group = 1 + }; + + private readonly GeneralName targetName; + private readonly GeneralName targetGroup; + + /** + * 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 ArgumentException if the given object cannot be + * interpreted as Target. + */ + public static Target GetInstance( + object obj) + { + if (obj is Target) + { + return (Target) obj; + } + + if (obj is Asn1TaggedObject) + { + return new Target((Asn1TaggedObject) obj); + } + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + /** + * Constructor from Asn1TaggedObject. + * + * @param tagObj The tagged object. + * @throws ArgumentException if the encoding is wrong. + */ + private Target( + Asn1TaggedObject tagObj) + { + switch ((Choice) tagObj.TagNo) + { + case Choice.Name: // GeneralName is already a choice so explicit + targetName = GeneralName.GetInstance(tagObj, true); + break; + case Choice.Group: + targetGroup = GeneralName.GetInstance(tagObj, true); + break; + default: + throw new ArgumentException("unknown tag: " + tagObj.TagNo); + } + } + + /** + * 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 ArgumentException if type is invalid. + */ + public Target( + Choice type, + GeneralName name) + : this(new DerTaggedObject((int) type, name)) + { + } + + /** + * @return Returns the targetGroup. + */ + public virtual GeneralName TargetGroup + { + get { return targetGroup; } + } + + /** + * @return Returns the targetName. + */ + public virtual GeneralName TargetName + { + get { return targetName; } + } + + /** + * Produce an object suitable for an Asn1OutputStream. + * + * Returns: + * + *
+		 *     Target  ::= CHOICE {
+		 *       targetName          [0] GeneralName,
+		 *       targetGroup         [1] GeneralName,
+		 *       targetCert          [2] TargetCert
+		 *     }
+		 * 
+ * + * @return an Asn1Object + */ + public override Asn1Object ToAsn1Object() + { + // GeneralName is a choice already so most be explicitly tagged + if (targetName != null) + { + return new DerTaggedObject(true, 0, targetName); + } + + return new DerTaggedObject(true, 1, targetGroup); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/TargetInformation.cs b/bc-sharp-crypto/src/asn1/x509/TargetInformation.cs new file mode 100644 index 0000000..2bf2189 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/TargetInformation.cs @@ -0,0 +1,125 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.X509 +{ + /** + * Target information extension for attributes certificates according to RFC + * 3281. + * + *
+	 *           SEQUENCE OF Targets
+	 * 
+ * + */ + public class TargetInformation + : Asn1Encodable + { + private readonly 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 ArgumentException if the given object cannot be interpreted as TargetInformation. + */ + public static TargetInformation GetInstance( + object obj) + { + if (obj is TargetInformation) + { + return (TargetInformation) obj; + } + + if (obj is Asn1Sequence) + { + return new TargetInformation((Asn1Sequence) obj); + } + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + /** + * Constructor from a Asn1Sequence. + * + * @param seq The Asn1Sequence. + * @throws ArgumentException if the sequence does not contain + * correctly encoded Targets elements. + */ + private TargetInformation( + Asn1Sequence targets) + { + this.targets = targets; + } + + /** + * Returns the targets in this target information extension. + *

+ * The ArrayList is cloned before it is returned.

+ * + * @return Returns the targets. + */ + public virtual Targets[] GetTargetsObjects() + { + Targets[] result = new Targets[targets.Count]; + + for (int i = 0; i < targets.Count; ++i) + { + result[i] = Targets.GetInstance(targets[i]); + } + + return result; + } + + /** + * 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 Org.BouncyCastle.Asn1.Asn1Sequence} the encoding is kept.

+ * + * @return an Asn1Object + */ + public override Asn1Object ToAsn1Object() + { + return targets; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/Targets.cs b/bc-sharp-crypto/src/asn1/x509/Targets.cs new file mode 100644 index 0000000..0387e1f --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/Targets.cs @@ -0,0 +1,123 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.X509 +{ + /** + * 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 org.bouncycastle.asn1.x509.Target + * @see org.bouncycastle.asn1.x509.TargetInformation + */ + public class Targets + : Asn1Encodable + { + private readonly 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 ArgumentException if the given object cannot be interpreted as Target. + */ + public static Targets GetInstance( + object obj) + { + if (obj is Targets) + { + return (Targets) obj; + } + + if (obj is Asn1Sequence) + { + return new Targets((Asn1Sequence) obj); + } + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + /** + * Constructor from Asn1Sequence. + * + * @param targets The ASN.1 SEQUENCE. + * @throws ArgumentException if the contents of the sequence are + * invalid. + */ + private Targets( + Asn1Sequence targets) + { + this.targets = targets; + } + + /** + * Constructor from given targets. + *

+ * The ArrayList is copied.

+ * + * @param targets An ArrayList of {@link Target}s. + * @see Target + * @throws ArgumentException if the ArrayList contains not only Targets. + */ + public Targets( + Target[] targets) + { + this.targets = new DerSequence(targets); + } + + /** + * Returns the targets in an ArrayList. + *

+ * The ArrayList is cloned before it is returned.

+ * + * @return Returns the targets. + */ + public virtual Target[] GetTargets() + { + Target[] result = new Target[targets.Count]; + + for (int i = 0; i < targets.Count; ++i) + { + result[i] = Target.GetInstance(targets[i]); + } + + return result; + } + + /** + * Produce an object suitable for an Asn1OutputStream. + * + * Returns: + * + *
+		 *            Targets ::= SEQUENCE OF Target
+		 * 
+ * + * @return an Asn1Object + */ + public override Asn1Object ToAsn1Object() + { + return targets; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/Time.cs b/bc-sharp-crypto/src/asn1/x509/Time.cs new file mode 100644 index 0000000..fa3936d --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/Time.cs @@ -0,0 +1,122 @@ +using System; +using System.Globalization; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.X509 +{ + public class Time + : Asn1Encodable, IAsn1Choice + { + private readonly Asn1Object time; + + public static Time GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(obj.GetObject()); + } + + public Time( + Asn1Object time) + { + if (time == null) + throw new ArgumentNullException("time"); + if (!(time is DerUtcTime) && !(time is DerGeneralizedTime)) + throw new ArgumentException("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( + DateTime date) + { +#if PORTABLE + string d = date.ToUniversalTime().ToString("yyyyMMddHHmmss", CultureInfo.InvariantCulture) + "Z"; +#else + string d = date.ToString("yyyyMMddHHmmss", CultureInfo.InvariantCulture) + "Z"; +#endif + + int year = int.Parse(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 == null || obj is Time) + return (Time)obj; + if (obj is DerUtcTime) + return new Time((DerUtcTime)obj); + if (obj is DerGeneralizedTime) + return new Time((DerGeneralizedTime)obj); + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + public string GetTime() + { + if (time is DerUtcTime) + { + return ((DerUtcTime) time).AdjustedTimeString; + } + + return ((DerGeneralizedTime) time).GetTime(); + } + + /// + /// Return our time as DateTime. + /// + /// A date time. + public DateTime ToDateTime() + { + try + { + if (time is DerUtcTime) + { + return ((DerUtcTime)time).ToAdjustedDateTime(); + } + else + { + return ((DerGeneralizedTime)time).ToDateTime(); + } + } + catch (FormatException e) + { + // this should never happen + throw new InvalidOperationException("invalid date string: " + e.Message); + } + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *
+         * Time ::= CHOICE {
+         *             utcTime        UTCTime,
+         *             generalTime    GeneralizedTime }
+         * 
+ */ + public override Asn1Object ToAsn1Object() + { + return time; + } + + public override string ToString() + { + return GetTime(); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/UserNotice.cs b/bc-sharp-crypto/src/asn1/x509/UserNotice.cs new file mode 100644 index 0000000..f409164 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/UserNotice.cs @@ -0,0 +1,130 @@ +using System; + +namespace Org.BouncyCastle.Asn1.X509 +{ + /** + * 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 + : Asn1Encodable + { + private readonly NoticeReference noticeRef; + private readonly 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, 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.

+ */ + [Obsolete("Use GetInstance() instead")] + public UserNotice( + Asn1Sequence seq) + { + if (seq.Count == 2) + { + noticeRef = NoticeReference.GetInstance(seq[0]); + explicitText = DisplayText.GetInstance(seq[1]); + } + else if (seq.Count == 1) + { + if (seq[0].ToAsn1Object() is Asn1Sequence) + { + noticeRef = NoticeReference.GetInstance(seq[0]); + explicitText = null; + } + else + { + noticeRef = null; + explicitText = DisplayText.GetInstance(seq[0]); + } + } + else if (seq.Count == 0) + { + noticeRef = null; // neither field set! + explicitText = null; + } + else + { + throw new ArgumentException("Bad sequence size: " + seq.Count); + } + } + + public static UserNotice GetInstance(object obj) + { + if (obj is UserNotice) + return (UserNotice)obj; + if (obj == null) + return null; + return new UserNotice(Asn1Sequence.GetInstance(obj)); + } + + public virtual NoticeReference NoticeRef + { + get { return noticeRef; } + } + + public virtual DisplayText ExplicitText + { + get { return explicitText; } + } + + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector av = new Asn1EncodableVector(); + + if (noticeRef != null) + { + av.Add(noticeRef); + } + + if (explicitText != null) + { + av.Add(explicitText); + } + + return new DerSequence(av); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/V1TBSCertificateGenerator.cs b/bc-sharp-crypto/src/asn1/x509/V1TBSCertificateGenerator.cs new file mode 100644 index 0000000..20b525a --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/V1TBSCertificateGenerator.cs @@ -0,0 +1,108 @@ +using System; + +namespace Org.BouncyCastle.Asn1.X509 +{ + /** + * 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 + { + internal DerTaggedObject version = new DerTaggedObject(0, new DerInteger(0)); + internal DerInteger serialNumber; + internal AlgorithmIdentifier signature; + internal X509Name issuer; + internal Time startDate, endDate; + internal X509Name subject; + internal 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 InvalidOperationException("not all mandatory fields set in V1 TBScertificate generator"); + } + + return new TbsCertificateStructure( + new DerSequence( + //version, - not required as default value + serialNumber, + signature, + issuer, + new DerSequence(startDate, endDate), // before and after dates + subject, + subjectPublicKeyInfo)); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/V2AttributeCertificateInfoGenerator.cs b/bc-sharp-crypto/src/asn1/x509/V2AttributeCertificateInfoGenerator.cs new file mode 100644 index 0000000..02580b5 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/V2AttributeCertificateInfoGenerator.cs @@ -0,0 +1,137 @@ +using System; + +namespace Org.BouncyCastle.Asn1.X509 +{ + /** + * 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 Attr,
+     *       issuerUniqueID       UniqueIdentifier OPTIONAL,
+     *       extensions           Extensions OPTIONAL
+     * }
+     * 
+ * + */ + public class V2AttributeCertificateInfoGenerator + { + internal DerInteger version; + internal Holder holder; + internal AttCertIssuer issuer; + internal AlgorithmIdentifier signature; + internal DerInteger serialNumber; +// internal AttCertValidityPeriod attrCertValidityPeriod; + internal Asn1EncodableVector attributes; + internal DerBitString issuerUniqueID; + internal X509Extensions extensions; + internal 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 AttributeX509(new DerObjectIdentifier(oid), new DerSet(value))); + } + + /** + * @param attribute + */ + public void AddAttribute(AttributeX509 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 InvalidOperationException("not all mandatory fields set in V2 AttributeCertificateInfo generator"); + } + + Asn1EncodableVector v = new Asn1EncodableVector( + version, holder, issuer, signature, serialNumber); + + // + // before and after dates => AttCertValidityPeriod + // + v.Add(new AttCertValidityPeriod(startDate, endDate)); + + // Attributes + v.Add(new DerSequence(attributes)); + + if (issuerUniqueID != null) + { + v.Add(issuerUniqueID); + } + + if (extensions != null) + { + v.Add(extensions); + } + + return AttributeCertificateInfo.GetInstance(new DerSequence(v)); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/V2Form.cs b/bc-sharp-crypto/src/asn1/x509/V2Form.cs new file mode 100644 index 0000000..2c6e54a --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/V2Form.cs @@ -0,0 +1,137 @@ +using System; + +namespace Org.BouncyCastle.Asn1.X509 +{ + public class V2Form + : Asn1Encodable + { + internal GeneralNames issuerName; + internal IssuerSerial baseCertificateID; + internal ObjectDigestInfo objectDigestInfo; + + public static V2Form GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); + } + + public static V2Form GetInstance(object obj) + { + if (obj is V2Form) + return (V2Form)obj; + if (obj != null) + return new V2Form(Asn1Sequence.GetInstance(obj)); + return null; + } + + public V2Form(GeneralNames issuerName) + : this(issuerName, null, null) + { + } + + public V2Form(GeneralNames issuerName, IssuerSerial baseCertificateID) + : this(issuerName, baseCertificateID, null) + { + } + + public V2Form(GeneralNames issuerName, ObjectDigestInfo objectDigestInfo) + : this(issuerName, null, objectDigestInfo) + { + } + + public V2Form( + GeneralNames issuerName, + IssuerSerial baseCertificateID, + ObjectDigestInfo objectDigestInfo) + { + this.issuerName = issuerName; + this.baseCertificateID = baseCertificateID; + this.objectDigestInfo = objectDigestInfo; + } + + private V2Form( + Asn1Sequence seq) + { + if (seq.Count > 3) + { + throw new ArgumentException("Bad sequence size: " + seq.Count); + } + + int index = 0; + + if (!(seq[0] is Asn1TaggedObject)) + { + index++; + this.issuerName = GeneralNames.GetInstance(seq[0]); + } + + for (int i = index; i != seq.Count; i++) + { + Asn1TaggedObject o = Asn1TaggedObject.GetInstance(seq[i]); + if (o.TagNo == 0) + { + baseCertificateID = IssuerSerial.GetInstance(o, false); + } + else if (o.TagNo == 1) + { + objectDigestInfo = ObjectDigestInfo.GetInstance(o, false); + } + else + { + throw new ArgumentException("Bad tag number: " + o.TagNo); + } + } + } + + public GeneralNames IssuerName + { + get { return issuerName; } + } + + public IssuerSerial BaseCertificateID + { + get { return baseCertificateID; } + } + + public ObjectDigestInfo ObjectDigestInfo + { + get { 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 override Asn1Object 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/bc-sharp-crypto/src/asn1/x509/V2TBSCertListGenerator.cs b/bc-sharp-crypto/src/asn1/x509/V2TBSCertListGenerator.cs new file mode 100644 index 0000000..2c92918 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/V2TBSCertListGenerator.cs @@ -0,0 +1,201 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.X509 +{ + /** + * 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 + { + private DerInteger version = new DerInteger(1); + private AlgorithmIdentifier signature; + private X509Name issuer; + private Time thisUpdate, nextUpdate; + private X509Extensions extensions; + private IList crlEntries; + + 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 = (nextUpdate != null) + ? new Time(nextUpdate) + : null; + } + + 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 = Platform.CreateArrayList(); + } + + crlEntries.Add(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) + { + IList extOids = Platform.CreateArrayList(); + IList extValues = Platform.CreateArrayList(); + + if (reason != 0) + { + CrlReason crlReason = new CrlReason(reason); + + try + { + extOids.Add(X509Extensions.ReasonCode); + extValues.Add(new X509Extension(false, new DerOctetString(crlReason.GetEncoded()))); + } + catch (IOException e) + { + throw new ArgumentException("error encoding reason: " + e); + } + } + + if (invalidityDate != null) + { + try + { + extOids.Add(X509Extensions.InvalidityDate); + extValues.Add(new X509Extension(false, new DerOctetString(invalidityDate.GetEncoded()))); + } + catch (IOException e) + { + throw new ArgumentException("error encoding invalidityDate: " + e); + } + } + + if (extOids.Count != 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( + userCertificate, revocationDate); + + if (extensions != null) + { + v.Add(extensions); + } + + AddCrlEntry(new DerSequence(v)); + } + + public void SetExtensions( + X509Extensions extensions) + { + this.extensions = extensions; + } + + public TbsCertificateList GenerateTbsCertList() + { + if ((signature == null) || (issuer == null) || (thisUpdate == null)) + { + throw new InvalidOperationException("Not all mandatory fields set in V2 TbsCertList generator."); + } + + Asn1EncodableVector v = new Asn1EncodableVector( + version, signature, issuer, thisUpdate); + + if (nextUpdate != null) + { + v.Add(nextUpdate); + } + + // Add CRLEntries if they exist + if (crlEntries != null) + { + Asn1Sequence[] certs = new Asn1Sequence[crlEntries.Count]; + for (int i = 0; i < crlEntries.Count; ++i) + { + certs[i] = (Asn1Sequence)crlEntries[i]; + } + v.Add(new DerSequence(certs)); + } + + if (extensions != null) + { + v.Add(new DerTaggedObject(0, extensions)); + } + + return new TbsCertificateList(new DerSequence(v)); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/V3TBSCertificateGenerator.cs b/bc-sharp-crypto/src/asn1/x509/V3TBSCertificateGenerator.cs new file mode 100644 index 0000000..beb469a --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/V3TBSCertificateGenerator.cs @@ -0,0 +1,168 @@ +using System; + +namespace Org.BouncyCastle.Asn1.X509 +{ + /** + * 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 + { + internal DerTaggedObject version = new DerTaggedObject(0, new DerInteger(2)); + internal DerInteger serialNumber; + internal AlgorithmIdentifier signature; + internal X509Name issuer; + internal Time startDate, endDate; + internal X509Name subject; + internal SubjectPublicKeyInfo subjectPublicKeyInfo; + internal X509Extensions extensions; + + private bool 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 InvalidOperationException("not all mandatory fields set in V3 TBScertificate generator"); + } + + DerSequence validity = new DerSequence(startDate, endDate); // before and after dates + + Asn1EncodableVector v = new Asn1EncodableVector( + version, serialNumber, signature, issuer, validity); + + if (subject != null) + { + v.Add(subject); + } + else + { + v.Add(DerSequence.Empty); + } + + 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/bc-sharp-crypto/src/asn1/x509/X509Attributes.cs b/bc-sharp-crypto/src/asn1/x509/X509Attributes.cs new file mode 100644 index 0000000..291329a --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/X509Attributes.cs @@ -0,0 +1,9 @@ +using System; + +namespace Org.BouncyCastle.Asn1.X509 +{ + public class X509Attributes + { + public static readonly DerObjectIdentifier RoleSyntax = new DerObjectIdentifier("2.5.4.72"); + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/X509CertificateStructure.cs b/bc-sharp-crypto/src/asn1/x509/X509CertificateStructure.cs new file mode 100644 index 0000000..6e7c85d --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/X509CertificateStructure.cs @@ -0,0 +1,132 @@ +using System; + +using Org.BouncyCastle.Asn1.Pkcs; + +namespace Org.BouncyCastle.Asn1.X509 +{ + /** + * an X509Certificate structure. + *
+     *  Certificate ::= Sequence {
+     *      tbsCertificate          TbsCertificate,
+     *      signatureAlgorithm      AlgorithmIdentifier,
+     *      signature               BIT STRING
+     *  }
+     * 
+ */ + public class X509CertificateStructure + : Asn1Encodable + { + private readonly TbsCertificateStructure tbsCert; + private readonly AlgorithmIdentifier sigAlgID; + private readonly DerBitString sig; + + public static X509CertificateStructure GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); + } + + public static X509CertificateStructure GetInstance( + object obj) + { + if (obj is X509CertificateStructure) + return (X509CertificateStructure)obj; + if (obj == null) + return null; + return new X509CertificateStructure(Asn1Sequence.GetInstance(obj)); + } + + public X509CertificateStructure( + TbsCertificateStructure tbsCert, + AlgorithmIdentifier sigAlgID, + DerBitString sig) + { + if (tbsCert == null) + throw new ArgumentNullException("tbsCert"); + if (sigAlgID == null) + throw new ArgumentNullException("sigAlgID"); + if (sig == null) + throw new ArgumentNullException("sig"); + + this.tbsCert = tbsCert; + this.sigAlgID = sigAlgID; + this.sig = sig; + } + + private X509CertificateStructure( + Asn1Sequence seq) + { + if (seq.Count != 3) + throw new ArgumentException("sequence wrong size for a certificate", "seq"); + + // + // correct x509 certficate + // + tbsCert = TbsCertificateStructure.GetInstance(seq[0]); + sigAlgID = AlgorithmIdentifier.GetInstance(seq[1]); + sig = DerBitString.GetInstance(seq[2]); + } + + public TbsCertificateStructure TbsCertificate + { + get { return tbsCert; } + } + + public int Version + { + get { return tbsCert.Version; } + } + + public DerInteger SerialNumber + { + get { return tbsCert.SerialNumber; } + } + + public X509Name Issuer + { + get { return tbsCert.Issuer; } + } + + public Time StartDate + { + get { return tbsCert.StartDate; } + } + + public Time EndDate + { + get { return tbsCert.EndDate; } + } + + public X509Name Subject + { + get { return tbsCert.Subject; } + } + + public SubjectPublicKeyInfo SubjectPublicKeyInfo + { + get { return tbsCert.SubjectPublicKeyInfo; } + } + + public AlgorithmIdentifier SignatureAlgorithm + { + get { return sigAlgID; } + } + + public DerBitString Signature + { + get { return sig; } + } + + public byte[] GetSignatureOctets() + { + return sig.GetOctets(); + } + + public override Asn1Object ToAsn1Object() + { + return new DerSequence(tbsCert, sigAlgID, sig); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/X509DefaultEntryConverter.cs b/bc-sharp-crypto/src/asn1/x509/X509DefaultEntryConverter.cs new file mode 100644 index 0000000..7282ead --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/X509DefaultEntryConverter.cs @@ -0,0 +1,63 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Asn1.X509 +{ + /** + * The default converter for X509 DN entries when going from their + * string value to ASN.1 strings. + */ + public class X509DefaultEntryConverter + : X509NameEntryConverter + { + /** + * Apply default conversion 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 override Asn1Object GetConvertedValue( + DerObjectIdentifier oid, + string value) + { + if (value.Length != 0 && value[0] == '#') + { + try + { + return ConvertHexEncoded(value, 1); + } + catch (IOException) + { + throw new Exception("can't recode value for oid " + oid.Id); + } + } + + if (value.Length != 0 && value[0] == '\\') + { + value = value.Substring(1); + } + + if (oid.Equals(X509Name.EmailAddress) || oid.Equals(X509Name.DC)) + { + return new DerIA5String(value); + } + + if (oid.Equals(X509Name.DateOfBirth)) // accept time string as well as # (for compatibility) + { + return new DerGeneralizedTime(value); + } + + if (oid.Equals(X509Name.C) + || oid.Equals(X509Name.SerialNumber) + || oid.Equals(X509Name.DnQualifier) + || oid.Equals(X509Name.TelephoneNumber)) + { + return new DerPrintableString(value); + } + + return new DerUtf8String(value); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/X509Extension.cs b/bc-sharp-crypto/src/asn1/x509/X509Extension.cs new file mode 100644 index 0000000..430ce44 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/X509Extension.cs @@ -0,0 +1,79 @@ +using System; + +namespace Org.BouncyCastle.Asn1.X509 +{ + /** + * an object for the elements in the X.509 V3 extension block. + */ + public class X509Extension + { + internal bool critical; + internal Asn1OctetString value; + + public X509Extension( + DerBoolean critical, + Asn1OctetString value) + { + if (critical == null) + { + throw new ArgumentNullException("critical"); + } + + this.critical = critical.IsTrue; + this.value = value; + } + + public X509Extension( + bool critical, + Asn1OctetString value) + { + this.critical = critical; + this.value = value; + } + + public bool IsCritical { get { return critical; } } + + public Asn1OctetString Value { get { return value; } } + + public Asn1Encodable GetParsedValue() + { + return ConvertValueToObject(this); + } + + public override int GetHashCode() + { + int vh = this.Value.GetHashCode(); + + return IsCritical ? vh : ~vh; + } + + public override bool Equals( + object obj) + { + X509Extension other = obj as X509Extension; + if (other == null) + { + return false; + } + + return Value.Equals(other.Value) && IsCritical == other.IsCritical; + } + + /// Convert the value of the passed in extension to an object. + /// The extension to parse. + /// The object the value string contains. + /// If conversion is not possible. + public static Asn1Object ConvertValueToObject( + X509Extension ext) + { + try + { + return Asn1Object.FromByteArray(ext.Value.GetOctets()); + } + catch (Exception e) + { + throw new ArgumentException("can't convert extension", e); + } + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/X509Extensions.cs b/bc-sharp-crypto/src/asn1/x509/X509Extensions.cs new file mode 100644 index 0000000..049d728 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/X509Extensions.cs @@ -0,0 +1,456 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; + +namespace Org.BouncyCastle.Asn1.X509 +{ + public class X509Extensions + : Asn1Encodable + { + /** + * Subject Directory Attributes + */ + public static readonly DerObjectIdentifier SubjectDirectoryAttributes = new DerObjectIdentifier("2.5.29.9"); + + /** + * Subject Key Identifier + */ + public static readonly DerObjectIdentifier SubjectKeyIdentifier = new DerObjectIdentifier("2.5.29.14"); + + /** + * Key Usage + */ + public static readonly DerObjectIdentifier KeyUsage = new DerObjectIdentifier("2.5.29.15"); + + /** + * Private Key Usage Period + */ + public static readonly DerObjectIdentifier PrivateKeyUsagePeriod = new DerObjectIdentifier("2.5.29.16"); + + /** + * Subject Alternative Name + */ + public static readonly DerObjectIdentifier SubjectAlternativeName = new DerObjectIdentifier("2.5.29.17"); + + /** + * Issuer Alternative Name + */ + public static readonly DerObjectIdentifier IssuerAlternativeName = new DerObjectIdentifier("2.5.29.18"); + + /** + * Basic Constraints + */ + public static readonly DerObjectIdentifier BasicConstraints = new DerObjectIdentifier("2.5.29.19"); + + /** + * CRL Number + */ + public static readonly DerObjectIdentifier CrlNumber = new DerObjectIdentifier("2.5.29.20"); + + /** + * Reason code + */ + public static readonly DerObjectIdentifier ReasonCode = new DerObjectIdentifier("2.5.29.21"); + + /** + * Hold Instruction Code + */ + public static readonly DerObjectIdentifier InstructionCode = new DerObjectIdentifier("2.5.29.23"); + + /** + * Invalidity Date + */ + public static readonly DerObjectIdentifier InvalidityDate = new DerObjectIdentifier("2.5.29.24"); + + /** + * Delta CRL indicator + */ + public static readonly DerObjectIdentifier DeltaCrlIndicator = new DerObjectIdentifier("2.5.29.27"); + + /** + * Issuing Distribution Point + */ + public static readonly DerObjectIdentifier IssuingDistributionPoint = new DerObjectIdentifier("2.5.29.28"); + + /** + * Certificate Issuer + */ + public static readonly DerObjectIdentifier CertificateIssuer = new DerObjectIdentifier("2.5.29.29"); + + /** + * Name Constraints + */ + public static readonly DerObjectIdentifier NameConstraints = new DerObjectIdentifier("2.5.29.30"); + + /** + * CRL Distribution Points + */ + public static readonly DerObjectIdentifier CrlDistributionPoints = new DerObjectIdentifier("2.5.29.31"); + + /** + * Certificate Policies + */ + public static readonly DerObjectIdentifier CertificatePolicies = new DerObjectIdentifier("2.5.29.32"); + + /** + * Policy Mappings + */ + public static readonly DerObjectIdentifier PolicyMappings = new DerObjectIdentifier("2.5.29.33"); + + /** + * Authority Key Identifier + */ + public static readonly DerObjectIdentifier AuthorityKeyIdentifier = new DerObjectIdentifier("2.5.29.35"); + + /** + * Policy Constraints + */ + public static readonly DerObjectIdentifier PolicyConstraints = new DerObjectIdentifier("2.5.29.36"); + + /** + * Extended Key Usage + */ + public static readonly DerObjectIdentifier ExtendedKeyUsage = new DerObjectIdentifier("2.5.29.37"); + + /** + * Freshest CRL + */ + public static readonly DerObjectIdentifier FreshestCrl = new DerObjectIdentifier("2.5.29.46"); + + /** + * Inhibit Any Policy + */ + public static readonly DerObjectIdentifier InhibitAnyPolicy = new DerObjectIdentifier("2.5.29.54"); + + /** + * Authority Info Access + */ + public static readonly DerObjectIdentifier AuthorityInfoAccess = new DerObjectIdentifier("1.3.6.1.5.5.7.1.1"); + + /** + * Subject Info Access + */ + public static readonly DerObjectIdentifier SubjectInfoAccess = new DerObjectIdentifier("1.3.6.1.5.5.7.1.11"); + + /** + * Logo Type + */ + public static readonly DerObjectIdentifier LogoType = new DerObjectIdentifier("1.3.6.1.5.5.7.1.12"); + + /** + * BiometricInfo + */ + public static readonly DerObjectIdentifier BiometricInfo = new DerObjectIdentifier("1.3.6.1.5.5.7.1.2"); + + /** + * QCStatements + */ + public static readonly DerObjectIdentifier QCStatements = new DerObjectIdentifier("1.3.6.1.5.5.7.1.3"); + + /** + * Audit identity extension in attribute certificates. + */ + public static readonly DerObjectIdentifier AuditIdentity = new DerObjectIdentifier("1.3.6.1.5.5.7.1.4"); + + /** + * NoRevAvail extension in attribute certificates. + */ + public static readonly DerObjectIdentifier NoRevAvail = new DerObjectIdentifier("2.5.29.56"); + + /** + * TargetInformation extension in attribute certificates. + */ + public static readonly DerObjectIdentifier TargetInformation = new DerObjectIdentifier("2.5.29.55"); + + /** + * Expired Certificates on CRL extension + */ + public static readonly DerObjectIdentifier ExpiredCertsOnCrl = new DerObjectIdentifier("2.5.29.60"); + + private readonly IDictionary extensions = Platform.CreateHashtable(); + private readonly IList ordering; + + public static X509Extensions GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); + } + + public static X509Extensions GetInstance( + object obj) + { + if (obj == null || obj is X509Extensions) + { + return (X509Extensions) obj; + } + + if (obj is Asn1Sequence) + { + return new X509Extensions((Asn1Sequence) obj); + } + + if (obj is Asn1TaggedObject) + { + return GetInstance(((Asn1TaggedObject) obj).GetObject()); + } + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + /** + * Constructor from Asn1Sequence. + * + * the extensions are a list of constructed sequences, either with (Oid, OctetString) or (Oid, Boolean, OctetString) + */ + private X509Extensions( + Asn1Sequence seq) + { + this.ordering = Platform.CreateArrayList(); + + foreach (Asn1Encodable ae in seq) + { + Asn1Sequence s = Asn1Sequence.GetInstance(ae.ToAsn1Object()); + + if (s.Count < 2 || s.Count > 3) + throw new ArgumentException("Bad sequence size: " + s.Count); + + DerObjectIdentifier oid = DerObjectIdentifier.GetInstance(s[0].ToAsn1Object()); + + bool isCritical = s.Count == 3 + && DerBoolean.GetInstance(s[1].ToAsn1Object()).IsTrue; + + Asn1OctetString octets = Asn1OctetString.GetInstance(s[s.Count - 1].ToAsn1Object()); + + extensions.Add(oid, new X509Extension(isCritical, octets)); + ordering.Add(oid); + } + } + + /** + * constructor from a table of extensions. + *

+ * it's is assumed the table contains Oid/string pairs.

+ */ + public X509Extensions( + IDictionary extensions) + : this(null, extensions) + { + } + + /** + * Constructor from a table of extensions with ordering. + *

+ * It's is assumed the table contains Oid/string pairs.

+ */ + public X509Extensions( + IList ordering, + IDictionary extensions) + { + if (ordering == null) + { + this.ordering = Platform.CreateArrayList(extensions.Keys); + } + else + { + this.ordering = Platform.CreateArrayList(ordering); + } + + foreach (DerObjectIdentifier oid in this.ordering) + { + this.extensions.Add(oid, (X509Extension)extensions[oid]); + } + } + + /** + * Constructor from two vectors + * + * @param objectIDs an ArrayList of the object identifiers. + * @param values an ArrayList of the extension values. + */ + public X509Extensions( + IList oids, + IList values) + { + this.ordering = Platform.CreateArrayList(oids); + + int count = 0; + foreach (DerObjectIdentifier oid in this.ordering) + { + this.extensions.Add(oid, (X509Extension)values[count++]); + } + } + +#if !(SILVERLIGHT || PORTABLE) + /** + * constructor from a table of extensions. + *

+ * it's is assumed the table contains Oid/string pairs.

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

+ */ + [Obsolete] + public X509Extensions( + ArrayList ordering, + Hashtable extensions) + { + if (ordering == null) + { + this.ordering = Platform.CreateArrayList(extensions.Keys); + } + else + { + this.ordering = Platform.CreateArrayList(ordering); + } + + foreach (DerObjectIdentifier oid in this.ordering) + { + this.extensions.Add(oid, (X509Extension) extensions[oid]); + } + } + + /** + * Constructor from two vectors + * + * @param objectIDs an ArrayList of the object identifiers. + * @param values an ArrayList of the extension values. + */ + [Obsolete] + public X509Extensions( + ArrayList oids, + ArrayList values) + { + this.ordering = Platform.CreateArrayList(oids); + + int count = 0; + foreach (DerObjectIdentifier oid in this.ordering) + { + this.extensions.Add(oid, (X509Extension) values[count++]); + } + } +#endif + + [Obsolete("Use ExtensionOids IEnumerable property")] + public IEnumerator Oids() + { + return ExtensionOids.GetEnumerator(); + } + + /** + * return an Enumeration of the extension field's object ids. + */ + public IEnumerable ExtensionOids + { + get { return new EnumerableProxy(ordering); } + } + + /** + * 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[oid]; + } + + /** + *
+		 *     Extensions        ::=   SEQUENCE SIZE (1..MAX) OF Extension
+		 *
+		 *     Extension         ::=   SEQUENCE {
+		 *        extnId            EXTENSION.&id ({ExtensionSet}),
+		 *        critical          BOOLEAN DEFAULT FALSE,
+		 *        extnValue         OCTET STRING }
+		 * 
+ */ + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector vec = new Asn1EncodableVector(); + + foreach (DerObjectIdentifier oid in ordering) + { + X509Extension ext = (X509Extension) extensions[oid]; + Asn1EncodableVector v = new Asn1EncodableVector(oid); + + if (ext.IsCritical) + { + v.Add(DerBoolean.True); + } + + v.Add(ext.Value); + + vec.Add(new DerSequence(v)); + } + + return new DerSequence(vec); + } + + public bool Equivalent( + X509Extensions other) + { + if (extensions.Count != other.extensions.Count) + return false; + + foreach (DerObjectIdentifier oid in extensions.Keys) + { + if (!extensions[oid].Equals(other.extensions[oid])) + return false; + } + + return true; + } + + public DerObjectIdentifier[] GetExtensionOids() + { + return ToOidArray(ordering); + } + + public DerObjectIdentifier[] GetNonCriticalExtensionOids() + { + return GetExtensionOids(false); + } + + public DerObjectIdentifier[] GetCriticalExtensionOids() + { + return GetExtensionOids(true); + } + + private DerObjectIdentifier[] GetExtensionOids(bool isCritical) + { + IList oids = Platform.CreateArrayList(); + + foreach (DerObjectIdentifier oid in this.ordering) + { + X509Extension ext = (X509Extension)extensions[oid]; + if (ext.IsCritical == isCritical) + { + oids.Add(oid); + } + } + + return ToOidArray(oids); + } + + private static DerObjectIdentifier[] ToOidArray(IList oids) + { + DerObjectIdentifier[] oidArray = new DerObjectIdentifier[oids.Count]; + oids.CopyTo(oidArray, 0); + return oidArray; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/X509ExtensionsGenerator.cs b/bc-sharp-crypto/src/asn1/x509/X509ExtensionsGenerator.cs new file mode 100644 index 0000000..d6f567b --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/X509ExtensionsGenerator.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.X509 +{ + /// Generator for X.509 extensions + public class X509ExtensionsGenerator + { + private IDictionary extensions = Platform.CreateHashtable(); + private IList extOrdering = Platform.CreateArrayList(); + + /// Reset the generator + public void Reset() + { + extensions = Platform.CreateHashtable(); + extOrdering = Platform.CreateArrayList(); + } + + /// + /// Add an extension with the given oid and the passed in value to be included + /// in the OCTET STRING associated with the extension. + /// + /// OID for the extension. + /// True if critical, false otherwise. + /// The ASN.1 object to be included in the extension. + public void AddExtension( + DerObjectIdentifier oid, + bool critical, + Asn1Encodable extValue) + { + byte[] encoded; + try + { + encoded = extValue.GetDerEncoded(); + } + catch (Exception e) + { + throw new ArgumentException("error encoding value: " + e); + } + + this.AddExtension(oid, critical, encoded); + } + + /// + /// Add an extension with the given oid and the passed in byte array to be wrapped + /// in the OCTET STRING associated with the extension. + /// + /// OID for the extension. + /// True if critical, false otherwise. + /// The byte array to be wrapped. + public void AddExtension( + DerObjectIdentifier oid, + bool critical, + byte[] extValue) + { + if (extensions.Contains(oid)) + { + throw new ArgumentException("extension " + oid + " already added"); + } + + extOrdering.Add(oid); + extensions.Add(oid, new X509Extension(critical, new DerOctetString(extValue))); + } + + /// Return true if there are no extension present in this generator. + /// True if empty, false otherwise + public bool IsEmpty + { + get { return extOrdering.Count < 1; } + } + + /// Generate an X509Extensions object based on the current state of the generator. + /// An X509Extensions object + public X509Extensions Generate() + { + return new X509Extensions(extOrdering, extensions); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/X509Name.cs b/bc-sharp-crypto/src/asn1/x509/X509Name.cs new file mode 100644 index 0000000..01a7ec0 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/X509Name.cs @@ -0,0 +1,1077 @@ +using System; +using System.Collections; +using System.IO; +using System.Text; + +#if SILVERLIGHT || PORTABLE +using System.Collections.Generic; +#endif + +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Asn1.X509 +{ + /** + *
+    *     RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
+    *
+    *     RelativeDistinguishedName ::= SET SIZE (1..MAX) OF AttributeTypeAndValue
+    *
+    *     AttributeTypeAndValue ::= SEQUENCE {
+    *                                   type  OBJECT IDENTIFIER,
+    *                                   value ANY }
+    * 
+ */ + public class X509Name + : Asn1Encodable + { + /** + * country code - StringType(SIZE(2)) + */ + public static readonly DerObjectIdentifier C = new DerObjectIdentifier("2.5.4.6"); + + /** + * organization - StringType(SIZE(1..64)) + */ + public static readonly DerObjectIdentifier O = new DerObjectIdentifier("2.5.4.10"); + + /** + * organizational unit name - StringType(SIZE(1..64)) + */ + public static readonly DerObjectIdentifier OU = new DerObjectIdentifier("2.5.4.11"); + + /** + * Title + */ + public static readonly DerObjectIdentifier T = new DerObjectIdentifier("2.5.4.12"); + + /** + * common name - StringType(SIZE(1..64)) + */ + public static readonly DerObjectIdentifier CN = new DerObjectIdentifier("2.5.4.3"); + + /** + * street - StringType(SIZE(1..64)) + */ + public static readonly DerObjectIdentifier Street = new DerObjectIdentifier("2.5.4.9"); + + /** + * device serial number name - StringType(SIZE(1..64)) + */ + public static readonly DerObjectIdentifier SerialNumber = new DerObjectIdentifier("2.5.4.5"); + + /** + * locality name - StringType(SIZE(1..64)) + */ + public static readonly DerObjectIdentifier L = new DerObjectIdentifier("2.5.4.7"); + + /** + * state, or province name - StringType(SIZE(1..64)) + */ + public static readonly DerObjectIdentifier ST = new DerObjectIdentifier("2.5.4.8"); + + /** + * Naming attributes of type X520name + */ + public static readonly DerObjectIdentifier Surname = new DerObjectIdentifier("2.5.4.4"); + public static readonly DerObjectIdentifier GivenName = new DerObjectIdentifier("2.5.4.42"); + public static readonly DerObjectIdentifier Initials = new DerObjectIdentifier("2.5.4.43"); + public static readonly DerObjectIdentifier Generation = new DerObjectIdentifier("2.5.4.44"); + public static readonly DerObjectIdentifier UniqueIdentifier = new DerObjectIdentifier("2.5.4.45"); + + /** + * businessCategory - DirectoryString(SIZE(1..128) + */ + public static readonly DerObjectIdentifier BusinessCategory = new DerObjectIdentifier( + "2.5.4.15"); + + /** + * postalCode - DirectoryString(SIZE(1..40) + */ + public static readonly DerObjectIdentifier PostalCode = new DerObjectIdentifier( + "2.5.4.17"); + + /** + * dnQualifier - DirectoryString(SIZE(1..64) + */ + public static readonly DerObjectIdentifier DnQualifier = new DerObjectIdentifier( + "2.5.4.46"); + + /** + * RFC 3039 Pseudonym - DirectoryString(SIZE(1..64) + */ + public static readonly DerObjectIdentifier Pseudonym = new DerObjectIdentifier( + "2.5.4.65"); + + /** + * RFC 3039 DateOfBirth - GeneralizedTime - YYYYMMDD000000Z + */ + public static readonly DerObjectIdentifier DateOfBirth = new DerObjectIdentifier( + "1.3.6.1.5.5.7.9.1"); + + /** + * RFC 3039 PlaceOfBirth - DirectoryString(SIZE(1..128) + */ + public static readonly DerObjectIdentifier PlaceOfBirth = new DerObjectIdentifier( + "1.3.6.1.5.5.7.9.2"); + + /** + * RFC 3039 DateOfBirth - PrintableString (SIZE(1)) -- "M", "F", "m" or "f" + */ + public static readonly 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 readonly DerObjectIdentifier CountryOfCitizenship = new DerObjectIdentifier( + "1.3.6.1.5.5.7.9.4"); + + /** + * RFC 3039 CountryOfCitizenship - PrintableString (SIZE (2)) -- ISO 3166 + * codes only + */ + public static readonly DerObjectIdentifier CountryOfResidence = new DerObjectIdentifier( + "1.3.6.1.5.5.7.9.5"); + + /** + * ISIS-MTT NameAtBirth - DirectoryString(SIZE(1..64) + */ + public static readonly DerObjectIdentifier NameAtBirth = new DerObjectIdentifier("1.3.36.8.3.14"); + + /** + * RFC 3039 PostalAddress - SEQUENCE SIZE (1..6) OF + * DirectoryString(SIZE(1..30)) + */ + public static readonly DerObjectIdentifier PostalAddress = new DerObjectIdentifier("2.5.4.16"); + + /** + * RFC 2256 dmdName + */ + public static readonly DerObjectIdentifier DmdName = new DerObjectIdentifier("2.5.4.54"); + + /** + * id-at-telephoneNumber + */ + public static readonly DerObjectIdentifier TelephoneNumber = X509ObjectIdentifiers.id_at_telephoneNumber; + + /** + * id-at-name + */ + public static readonly 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 readonly DerObjectIdentifier EmailAddress = PkcsObjectIdentifiers.Pkcs9AtEmailAddress; + + /** + * more from PKCS#9 + */ + public static readonly DerObjectIdentifier UnstructuredName = PkcsObjectIdentifiers.Pkcs9AtUnstructuredName; + public static readonly DerObjectIdentifier UnstructuredAddress = PkcsObjectIdentifiers.Pkcs9AtUnstructuredAddress; + + /** + * email address in Verisign certificates + */ + public static readonly DerObjectIdentifier E = EmailAddress; + + /* + * others... + */ + public static readonly DerObjectIdentifier DC = new DerObjectIdentifier("0.9.2342.19200300.100.1.25"); + + /** + * LDAP User id. + */ + public static readonly 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 bool DefaultReverse = false; + public static bool DefaultReverse + { + get { return defaultReverse[0]; } + set { defaultReverse[0] = value; } + } + + private static readonly bool[] defaultReverse = { false }; + +#if SILVERLIGHT || PORTABLE + /** + * default look up table translating OID values into their common symbols following + * the convention in RFC 2253 with a few extras + */ + public static readonly IDictionary DefaultSymbols = Platform.CreateHashtable(); + + /** + * look up table translating OID values into their common symbols following the convention in RFC 2253 + */ + public static readonly IDictionary RFC2253Symbols = Platform.CreateHashtable(); + + /** + * look up table translating OID values into their common symbols following the convention in RFC 1779 + * + */ + public static readonly IDictionary RFC1779Symbols = Platform.CreateHashtable(); + + /** + * look up table translating common symbols into their OIDS. + */ + public static readonly IDictionary DefaultLookup = Platform.CreateHashtable(); +#else + /** + * default look up table translating OID values into their common symbols following + * the convention in RFC 2253 with a few extras + */ + public static readonly Hashtable DefaultSymbols = new Hashtable(); + + /** + * look up table translating OID values into their common symbols following the convention in RFC 2253 + */ + public static readonly Hashtable RFC2253Symbols = new Hashtable(); + + /** + * look up table translating OID values into their common symbols following the convention in RFC 1779 + * + */ + public static readonly Hashtable RFC1779Symbols = new Hashtable(); + + /** + * look up table translating common symbols into their OIDS. + */ + public static readonly Hashtable DefaultLookup = new Hashtable(); +#endif + + static X509Name() + { + DefaultSymbols.Add(C, "C"); + DefaultSymbols.Add(O, "O"); + DefaultSymbols.Add(T, "T"); + DefaultSymbols.Add(OU, "OU"); + DefaultSymbols.Add(CN, "CN"); + DefaultSymbols.Add(L, "L"); + DefaultSymbols.Add(ST, "ST"); + DefaultSymbols.Add(SerialNumber, "SERIALNUMBER"); + DefaultSymbols.Add(EmailAddress, "E"); + DefaultSymbols.Add(DC, "DC"); + DefaultSymbols.Add(UID, "UID"); + DefaultSymbols.Add(Street, "STREET"); + DefaultSymbols.Add(Surname, "SURNAME"); + DefaultSymbols.Add(GivenName, "GIVENNAME"); + DefaultSymbols.Add(Initials, "INITIALS"); + DefaultSymbols.Add(Generation, "GENERATION"); + DefaultSymbols.Add(UnstructuredAddress, "unstructuredAddress"); + DefaultSymbols.Add(UnstructuredName, "unstructuredName"); + DefaultSymbols.Add(UniqueIdentifier, "UniqueIdentifier"); + DefaultSymbols.Add(DnQualifier, "DN"); + DefaultSymbols.Add(Pseudonym, "Pseudonym"); + DefaultSymbols.Add(PostalAddress, "PostalAddress"); + DefaultSymbols.Add(NameAtBirth, "NameAtBirth"); + DefaultSymbols.Add(CountryOfCitizenship, "CountryOfCitizenship"); + DefaultSymbols.Add(CountryOfResidence, "CountryOfResidence"); + DefaultSymbols.Add(Gender, "Gender"); + DefaultSymbols.Add(PlaceOfBirth, "PlaceOfBirth"); + DefaultSymbols.Add(DateOfBirth, "DateOfBirth"); + DefaultSymbols.Add(PostalCode, "PostalCode"); + DefaultSymbols.Add(BusinessCategory, "BusinessCategory"); + DefaultSymbols.Add(TelephoneNumber, "TelephoneNumber"); + + RFC2253Symbols.Add(C, "C"); + RFC2253Symbols.Add(O, "O"); + RFC2253Symbols.Add(OU, "OU"); + RFC2253Symbols.Add(CN, "CN"); + RFC2253Symbols.Add(L, "L"); + RFC2253Symbols.Add(ST, "ST"); + RFC2253Symbols.Add(Street, "STREET"); + RFC2253Symbols.Add(DC, "DC"); + RFC2253Symbols.Add(UID, "UID"); + + RFC1779Symbols.Add(C, "C"); + RFC1779Symbols.Add(O, "O"); + RFC1779Symbols.Add(OU, "OU"); + RFC1779Symbols.Add(CN, "CN"); + RFC1779Symbols.Add(L, "L"); + RFC1779Symbols.Add(ST, "ST"); + RFC1779Symbols.Add(Street, "STREET"); + + DefaultLookup.Add("c", C); + DefaultLookup.Add("o", O); + DefaultLookup.Add("t", T); + DefaultLookup.Add("ou", OU); + DefaultLookup.Add("cn", CN); + DefaultLookup.Add("l", L); + DefaultLookup.Add("st", ST); + DefaultLookup.Add("serialnumber", SerialNumber); + DefaultLookup.Add("street", Street); + DefaultLookup.Add("emailaddress", E); + DefaultLookup.Add("dc", DC); + DefaultLookup.Add("e", E); + DefaultLookup.Add("uid", UID); + DefaultLookup.Add("surname", Surname); + DefaultLookup.Add("givenname", GivenName); + DefaultLookup.Add("initials", Initials); + DefaultLookup.Add("generation", Generation); + DefaultLookup.Add("unstructuredaddress", UnstructuredAddress); + DefaultLookup.Add("unstructuredname", UnstructuredName); + DefaultLookup.Add("uniqueidentifier", UniqueIdentifier); + DefaultLookup.Add("dn", DnQualifier); + DefaultLookup.Add("pseudonym", Pseudonym); + DefaultLookup.Add("postaladdress", PostalAddress); + DefaultLookup.Add("nameofbirth", NameAtBirth); + DefaultLookup.Add("countryofcitizenship", CountryOfCitizenship); + DefaultLookup.Add("countryofresidence", CountryOfResidence); + DefaultLookup.Add("gender", Gender); + DefaultLookup.Add("placeofbirth", PlaceOfBirth); + DefaultLookup.Add("dateofbirth", DateOfBirth); + DefaultLookup.Add("postalcode", PostalCode); + DefaultLookup.Add("businesscategory", BusinessCategory); + DefaultLookup.Add("telephonenumber", TelephoneNumber); + } + + private readonly IList ordering = Platform.CreateArrayList(); + private readonly X509NameEntryConverter converter; + + private IList values = Platform.CreateArrayList(); + private IList added = Platform.CreateArrayList(); + private Asn1Sequence seq; + + /** + * Return a X509Name based on the passed in tagged object. + * + * @param obj tag object holding name. + * @param explicitly true if explicitly tagged false otherwise. + * @return the X509Name + */ + public static X509Name GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); + } + + public static X509Name GetInstance( + object obj) + { + if (obj == null || obj is X509Name) + return (X509Name)obj; + + if (obj != null) + return new X509Name(Asn1Sequence.GetInstance(obj)); + + throw new ArgumentException("null object in factory", "obj"); + } + + protected X509Name() + { + } + + /** + * Constructor from Asn1Sequence + * + * the principal will be a list of constructed sets, each containing an (OID, string) pair. + */ + protected X509Name( + Asn1Sequence seq) + { + this.seq = seq; + + foreach (Asn1Encodable asn1Obj in seq) + { + Asn1Set asn1Set = Asn1Set.GetInstance(asn1Obj.ToAsn1Object()); + + for (int i = 0; i < asn1Set.Count; i++) + { + Asn1Sequence s = Asn1Sequence.GetInstance(asn1Set[i].ToAsn1Object()); + + if (s.Count != 2) + throw new ArgumentException("badly sized pair"); + + ordering.Add(DerObjectIdentifier.GetInstance(s[0].ToAsn1Object())); + + Asn1Object derValue = s[1].ToAsn1Object(); + if (derValue is IAsn1String && !(derValue is DerUniversalString)) + { + string v = ((IAsn1String)derValue).GetString(); + if (Platform.StartsWith(v, "#")) + { + v = "\\" + v; + } + + values.Add(v); + } + else + { + values.Add("#" + Hex.ToHexString(derValue.GetEncoded())); + } + + added.Add(i != 0); + } + } + } + + /** + * 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 ArrayList should contain the OIDs + * in the order they are meant to be encoded or printed in ToString.

+ */ + public X509Name( + IList ordering, + IDictionary 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 ArrayList 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( + IList ordering, + IDictionary attributes, + X509NameEntryConverter converter) + { + this.converter = converter; + + foreach (DerObjectIdentifier oid in ordering) + { + object attribute = attributes[oid]; + if (attribute == null) + { + throw new ArgumentException("No attribute for object id - " + oid + " - passed to distinguished name"); + } + + this.ordering.Add(oid); + this.added.Add(false); + this.values.Add(attribute); // copy the hash table + } + } + + /** + * Takes two vectors one of the oids and the other of the values. + */ + public X509Name( + IList oids, + IList 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( + IList oids, + IList values, + X509NameEntryConverter converter) + { + this.converter = converter; + + if (oids.Count != values.Count) + { + throw new ArgumentException("'oids' must be same length as 'values'."); + } + + for (int i = 0; i < oids.Count; i++) + { + this.ordering.Add(oids[i]); + this.values.Add(values[i]); + this.added.Add(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, (IDictionary)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( + bool reverse, + string dirName) + : this(reverse, (IDictionary)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( + bool 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( + bool reverse, + IDictionary lookUp, + string dirName) + : this(reverse, lookUp, dirName, new X509DefaultEntryConverter()) + { + } + + private DerObjectIdentifier DecodeOid( + string name, + IDictionary lookUp) + { + if (Platform.StartsWith(Platform.ToUpperInvariant(name), "OID.")) + { + return new DerObjectIdentifier(name.Substring(4)); + } + else if (name[0] >= '0' && name[0] <= '9') + { + return new DerObjectIdentifier(name); + } + + DerObjectIdentifier oid = (DerObjectIdentifier)lookUp[Platform.ToLowerInvariant(name)]; + if (oid == null) + { + throw new ArgumentException("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( + bool reverse, + IDictionary 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 ArgumentException("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.Add(oid); + this.values.Add(v); + this.added.Add(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.Add(DecodeOid(nm, lookUp)); + this.values.Add(vl); + this.added.Add(true); + } + } + else + { + this.ordering.Add(oid); + this.values.Add(value); + this.added.Add(false); + } + } + + if (reverse) + { +// this.ordering.Reverse(); +// this.values.Reverse(); +// this.added.Reverse(); + IList o = Platform.CreateArrayList(); + IList v = Platform.CreateArrayList(); + IList a = Platform.CreateArrayList(); + int count = 1; + + for (int i = 0; i < this.ordering.Count; i++) + { + if (!((bool) this.added[i])) + { + count = 0; + } + + int index = count++; + + o.Insert(index, this.ordering[i]); + v.Insert(index, this.values[i]); + a.Insert(index, this.added[i]); + } + + this.ordering = o; + this.values = v; + this.added = a; + } + } + + /** + * return an IList of the oids in the name, in the order they were found. + */ + public IList GetOidList() + { + return Platform.CreateArrayList(ordering); + } + + /** + * return an IList of the values found in the name, in the order they + * were found. + */ + public IList GetValueList() + { + return GetValueList(null); + } + + /** + * return an IList of the values found in the name, in the order they + * were found, with the DN label corresponding to passed in oid. + */ + public IList GetValueList(DerObjectIdentifier oid) + { + IList v = Platform.CreateArrayList(); + for (int i = 0; i != values.Count; i++) + { + if (null == oid || oid.Equals(ordering[i])) + { + string val = (string)values[i]; + + if (Platform.StartsWith(val, "\\#")) + { + val = val.Substring(1); + } + + v.Add(val); + } + } + return v; + } + + public override Asn1Object ToAsn1Object() + { + if (seq == null) + { + Asn1EncodableVector vec = new Asn1EncodableVector(); + Asn1EncodableVector sVec = new Asn1EncodableVector(); + DerObjectIdentifier lstOid = null; + + for (int i = 0; i != ordering.Count; i++) + { + DerObjectIdentifier oid = (DerObjectIdentifier)ordering[i]; + string str = (string)values[i]; + + if (lstOid == null + || ((bool)this.added[i])) + { + } + else + { + vec.Add(new DerSet(sVec)); + sVec = new Asn1EncodableVector(); + } + + sVec.Add( + new DerSequence( + oid, + converter.GetConvertedValue(oid, str))); + + lstOid = oid; + } + + vec.Add(new DerSet(sVec)); + + seq = new DerSequence(vec); + } + + return seq; + } + + /// The X509Name object to test equivalency against. + /// If true, the order of elements must be the same, + /// as well as the values associated with each element. + public bool Equivalent( + X509Name other, + bool inOrder) + { + if (!inOrder) + return this.Equivalent(other); + + if (other == null) + return false; + + if (other == this) + return true; + + int orderingSize = ordering.Count; + + if (orderingSize != other.ordering.Count) + return false; + + for (int i = 0; i < orderingSize; i++) + { + DerObjectIdentifier oid = (DerObjectIdentifier) ordering[i]; + DerObjectIdentifier oOid = (DerObjectIdentifier) other.ordering[i]; + + if (!oid.Equals(oOid)) + return false; + + string val = (string) values[i]; + string oVal = (string) other.values[i]; + + if (!equivalentStrings(val, oVal)) + return false; + } + + return true; + } + + /** + * test for equivalence - note: case is ignored. + */ + public bool Equivalent( + X509Name other) + { + if (other == null) + return false; + + if (other == this) + return true; + + int orderingSize = ordering.Count; + + if (orderingSize != other.ordering.Count) + { + return false; + } + + bool[] indexes = new bool[orderingSize]; + int start, end, delta; + + if (ordering[0].Equals(other.ordering[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) + { + bool found = false; + DerObjectIdentifier oid = (DerObjectIdentifier)ordering[i]; + string value = (string)values[i]; + + for (int j = 0; j < orderingSize; j++) + { + if (indexes[j]) + { + continue; + } + + DerObjectIdentifier oOid = (DerObjectIdentifier)other.ordering[j]; + + if (oid.Equals(oOid)) + { + string oValue = (string)other.values[j]; + + if (equivalentStrings(value, oValue)) + { + indexes[j] = true; + found = true; + break; + } + } + } + + if (!found) + { + return false; + } + } + + return true; + } + + private static bool equivalentStrings( + string s1, + string s2) + { + string v1 = canonicalize(s1); + string v2 = canonicalize(s2); + + if (!v1.Equals(v2)) + { + v1 = stripInternalSpaces(v1); + v2 = stripInternalSpaces(v2); + + if (!v1.Equals(v2)) + { + return false; + } + } + + return true; + } + + private static string canonicalize( + string s) + { + string v = Platform.ToLowerInvariant(s).Trim(); + + if (Platform.StartsWith(v, "#")) + { + Asn1Object obj = decodeObject(v); + + if (obj is IAsn1String) + { + v = Platform.ToLowerInvariant(((IAsn1String)obj).GetString()).Trim(); + } + } + + return v; + } + + private static Asn1Object decodeObject( + string v) + { + try + { + return Asn1Object.FromByteArray(Hex.Decode(v.Substring(1))); + } + catch (IOException e) + { + throw new InvalidOperationException("unknown encoding in name: " + e.Message, e); + } + } + + private static string stripInternalSpaces( + string str) + { + StringBuilder res = new StringBuilder(); + + if (str.Length != 0) + { + char c1 = str[0]; + + res.Append(c1); + + for (int k = 1; k < str.Length; k++) + { + char c2 = str[k]; + if (!(c1 == ' ' && c2 == ' ')) + { + res.Append(c2); + } + c1 = c2; + } + } + + return res.ToString(); + } + + private void AppendValue( + StringBuilder buf, + IDictionary oidSymbols, + DerObjectIdentifier oid, + string val) + { + string sym = (string)oidSymbols[oid]; + + if (sym != null) + { + buf.Append(sym); + } + else + { + buf.Append(oid.Id); + } + + buf.Append('='); + + int index = buf.Length; + + buf.Append(val); + + int end = buf.Length; + + if (Platform.StartsWith(val, "\\#")) + { + index += 2; + } + + while (index != end) + { + if ((buf[index] == ',') + || (buf[index] == '"') + || (buf[index] == '\\') + || (buf[index] == '+') + || (buf[index] == '=') + || (buf[index] == '<') + || (buf[index] == '>') + || (buf[index] == ';')) + { + buf.Insert(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( + bool reverse, + IDictionary oidSymbols) + { +#if SILVERLIGHT || PORTABLE + List components = new List(); +#else + ArrayList components = new ArrayList(); +#endif + + StringBuilder ava = null; + + for (int i = 0; i < ordering.Count; i++) + { + if ((bool) added[i]) + { + ava.Append('+'); + AppendValue(ava, oidSymbols, + (DerObjectIdentifier)ordering[i], + (string)values[i]); + } + else + { + ava = new StringBuilder(); + AppendValue(ava, oidSymbols, + (DerObjectIdentifier)ordering[i], + (string)values[i]); + components.Add(ava); + } + } + + if (reverse) + { + components.Reverse(); + } + + StringBuilder buf = new StringBuilder(); + + if (components.Count > 0) + { + buf.Append(components[0].ToString()); + + for (int i = 1; i < components.Count; ++i) + { + buf.Append(','); + buf.Append(components[i].ToString()); + } + } + + return buf.ToString(); + } + + public override string ToString() + { + return ToString(DefaultReverse, (IDictionary)DefaultSymbols); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/X509NameEntryConverter.cs b/bc-sharp-crypto/src/asn1/x509/X509NameEntryConverter.cs new file mode 100644 index 0000000..5872656 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/X509NameEntryConverter.cs @@ -0,0 +1,89 @@ +using System; +using System.Globalization; +using System.IO; +using System.Text; + +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Asn1.X509 +{ + /** + * 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
+     *     : X509NameEntryConverter
+     * {
+     *     public Asn1Object 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 Asn1Object ConvertHexEncoded( + string hexString, + int offset) + { + string str = hexString.Substring(offset); + + return Asn1Object.FromByteArray(Hex.Decode(str)); + } + + /** + * return true if the passed in string can be represented without + * loss as a PrintableString, false otherwise. + */ + protected bool 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 Asn1Object GetConvertedValue(DerObjectIdentifier oid, string value); + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/X509NameTokenizer.cs b/bc-sharp-crypto/src/asn1/x509/X509NameTokenizer.cs new file mode 100644 index 0000000..ab55295 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/X509NameTokenizer.cs @@ -0,0 +1,104 @@ +using System.Text; + +namespace Org.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 separator; + private StringBuilder buffer = new StringBuilder(); + + public X509NameTokenizer( + string oid) + : this(oid, ',') + { + } + + public X509NameTokenizer( + string oid, + char separator) + { + this.value = oid; + this.index = -1; + this.separator = separator; + } + + public bool HasMoreTokens() + { + return index != value.Length; + } + + public string NextToken() + { + if (index == value.Length) + { + return null; + } + + int end = index + 1; + bool quoted = false; + bool escaped = false; + + buffer.Remove(0, buffer.Length); + + while (end != value.Length) + { + char c = value[end]; + + if (c == '"') + { + if (!escaped) + { + quoted = !quoted; + } + else + { + buffer.Append(c); + escaped = false; + } + } + else + { + if (escaped || quoted) + { + if (c == '#' && buffer[buffer.Length - 1] == '=') + { + buffer.Append('\\'); + } + else if (c == '+' && separator != '+') + { + buffer.Append('\\'); + } + buffer.Append(c); + escaped = false; + } + else if (c == '\\') + { + escaped = true; + } + else if (c == separator) + { + break; + } + else + { + buffer.Append(c); + } + } + + end++; + } + + index = end; + + return buffer.ToString().Trim(); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/X509ObjectIdentifiers.cs b/bc-sharp-crypto/src/asn1/x509/X509ObjectIdentifiers.cs new file mode 100644 index 0000000..f00e314 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/X509ObjectIdentifiers.cs @@ -0,0 +1,59 @@ +namespace Org.BouncyCastle.Asn1.X509 +{ + public abstract class X509ObjectIdentifiers + { + // + // base id + // + internal const string ID = "2.5.4"; + + public static readonly DerObjectIdentifier CommonName = new DerObjectIdentifier(ID + ".3"); + public static readonly DerObjectIdentifier CountryName = new DerObjectIdentifier(ID + ".6"); + public static readonly DerObjectIdentifier LocalityName = new DerObjectIdentifier(ID + ".7"); + public static readonly DerObjectIdentifier StateOrProvinceName = new DerObjectIdentifier(ID + ".8"); + public static readonly DerObjectIdentifier Organization = new DerObjectIdentifier(ID + ".10"); + public static readonly DerObjectIdentifier OrganizationalUnitName = new DerObjectIdentifier(ID + ".11"); + + public static readonly DerObjectIdentifier id_at_telephoneNumber = new DerObjectIdentifier(ID + ".20"); + public static readonly DerObjectIdentifier id_at_name = new DerObjectIdentifier(ID + ".41"); + + // id-SHA1 OBJECT IDENTIFIER ::= + // {iso(1) identified-organization(3) oiw(14) secsig(3) algorithms(2) 26 } // + public static readonly DerObjectIdentifier IdSha1 = 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)} + // + public static readonly 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) } + // + public static readonly DerObjectIdentifier RipeMD160WithRsaEncryption = new DerObjectIdentifier("1.3.36.3.3.1.2"); + + public static readonly DerObjectIdentifier IdEARsa = new DerObjectIdentifier("2.5.8.1.1"); + + // id-pkix + public static readonly DerObjectIdentifier IdPkix = new DerObjectIdentifier("1.3.6.1.5.5.7"); + + // + // private internet extensions + // + public static readonly DerObjectIdentifier IdPE = new DerObjectIdentifier(IdPkix + ".1"); + + // + // authority information access + // + public static readonly DerObjectIdentifier IdAD = new DerObjectIdentifier(IdPkix + ".48"); + public static readonly DerObjectIdentifier IdADCAIssuers = new DerObjectIdentifier(IdAD + ".2"); + public static readonly DerObjectIdentifier IdADOcsp = new DerObjectIdentifier(IdAD + ".1"); + + // + // OID for ocsp and crl uri in AuthorityInformationAccess extension + // + public static readonly DerObjectIdentifier OcspAccessMethod = IdADOcsp; + public static readonly DerObjectIdentifier CrlAccessMethod = IdADCAIssuers; + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/qualified/BiometricData.cs b/bc-sharp-crypto/src/asn1/x509/qualified/BiometricData.cs new file mode 100644 index 0000000..bb70c34 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/qualified/BiometricData.cs @@ -0,0 +1,110 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.X509.Qualified +{ + /** + * The BiometricData object. + *
+    * BiometricData  ::=  SEQUENCE {
+    *       typeOfBiometricData  TypeOfBiometricData,
+    *       hashAlgorithm        AlgorithmIdentifier,
+    *       biometricDataHash    OCTET STRING,
+    *       sourceDataUri        IA5String OPTIONAL  }
+    * 
+ */ + public class BiometricData + : Asn1Encodable + { + private readonly TypeOfBiometricData typeOfBiometricData; + private readonly AlgorithmIdentifier hashAlgorithm; + private readonly Asn1OctetString biometricDataHash; + private readonly DerIA5String sourceDataUri; + + public static BiometricData GetInstance( + object obj) + { + if (obj == null || obj is BiometricData) + { + return (BiometricData)obj; + } + + if (obj is Asn1Sequence) + { + return new BiometricData(Asn1Sequence.GetInstance(obj)); + } + + throw new ArgumentException("unknown object in GetInstance: " + Platform.GetTypeName(obj), "obj"); + } + + private BiometricData( + Asn1Sequence seq) + { + typeOfBiometricData = TypeOfBiometricData.GetInstance(seq[0]); + hashAlgorithm = AlgorithmIdentifier.GetInstance(seq[1]); + biometricDataHash = Asn1OctetString.GetInstance(seq[2]); + + if (seq.Count > 3) + { + sourceDataUri = DerIA5String.GetInstance(seq[3]); + } + } + + 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 TypeOfBiometricData + { + get { return typeOfBiometricData; } + } + + public AlgorithmIdentifier HashAlgorithm + { + get { return hashAlgorithm; } + } + + public Asn1OctetString BiometricDataHash + { + get { return biometricDataHash; } + } + + public DerIA5String SourceDataUri + { + get { return sourceDataUri; } + } + + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector seq = new Asn1EncodableVector( + typeOfBiometricData, hashAlgorithm, biometricDataHash); + + if (sourceDataUri != null) + { + seq.Add(sourceDataUri); + } + + return new DerSequence(seq); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/qualified/ETSIQCObjectIdentifiers.cs b/bc-sharp-crypto/src/asn1/x509/qualified/ETSIQCObjectIdentifiers.cs new file mode 100644 index 0000000..86a4eee --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/qualified/ETSIQCObjectIdentifiers.cs @@ -0,0 +1,19 @@ +using System; + +using Org.BouncyCastle.Asn1; + +namespace Org.BouncyCastle.Asn1.X509.Qualified +{ + public abstract class EtsiQCObjectIdentifiers + { + // + // base id + // + public static readonly DerObjectIdentifier IdEtsiQcs = new DerObjectIdentifier("0.4.0.1862.1"); + + public static readonly DerObjectIdentifier IdEtsiQcsQcCompliance = new DerObjectIdentifier(IdEtsiQcs+".1"); + public static readonly DerObjectIdentifier IdEtsiQcsLimitValue = new DerObjectIdentifier(IdEtsiQcs+".2"); + public static readonly DerObjectIdentifier IdEtsiQcsRetentionPeriod = new DerObjectIdentifier(IdEtsiQcs+".3"); + public static readonly DerObjectIdentifier IdEtsiQcsQcSscd = new DerObjectIdentifier(IdEtsiQcs+".4"); + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/qualified/Iso4217CurrencyCode.cs b/bc-sharp-crypto/src/asn1/x509/qualified/Iso4217CurrencyCode.cs new file mode 100644 index 0000000..9ec88f5 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/qualified/Iso4217CurrencyCode.cs @@ -0,0 +1,84 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.X509.Qualified +{ + /** + * 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 + : Asn1Encodable, IAsn1Choice + { + internal const int AlphabeticMaxSize = 3; + internal const int NumericMinSize = 1; + internal const int NumericMaxSize = 999; + + internal Asn1Encodable obj; +// internal int numeric; + + public static Iso4217CurrencyCode GetInstance( + object obj) + { + if (obj == null || obj is Iso4217CurrencyCode) + { + return (Iso4217CurrencyCode) obj; + } + + if (obj is DerInteger) + { + DerInteger numericobj = DerInteger.GetInstance(obj); + int numeric = numericobj.Value.IntValue; + return new Iso4217CurrencyCode(numeric); + } + + if (obj is DerPrintableString) + { + DerPrintableString alphabetic = DerPrintableString.GetInstance(obj); + return new Iso4217CurrencyCode(alphabetic.GetString()); + } + + throw new ArgumentException("unknown object in GetInstance: " + Platform.GetTypeName(obj), "obj"); + } + + public Iso4217CurrencyCode( + int numeric) + { + if (numeric > NumericMaxSize || numeric < NumericMinSize) + { + throw new ArgumentException("wrong size in numeric code : not in (" + NumericMinSize + ".." + NumericMaxSize + ")"); + } + + obj = new DerInteger(numeric); + } + + public Iso4217CurrencyCode( + string alphabetic) + { + if (alphabetic.Length > AlphabeticMaxSize) + { + throw new ArgumentException("wrong size in alphabetic code : max size is " + AlphabeticMaxSize); + } + + obj = new DerPrintableString(alphabetic); + } + + public bool IsAlphabetic { get { return obj is DerPrintableString; } } + + public string Alphabetic { get { return ((DerPrintableString) obj).GetString(); } } + + public int Numeric { get { return ((DerInteger)obj).Value.IntValue; } } + + public override Asn1Object ToAsn1Object() + { + return obj.ToAsn1Object(); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/qualified/MonetaryValue.cs b/bc-sharp-crypto/src/asn1/x509/qualified/MonetaryValue.cs new file mode 100644 index 0000000..d703de9 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/qualified/MonetaryValue.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.X509.Qualified +{ + /** + * The MonetaryValue object. + *
+    * MonetaryValue  ::=  SEQUENCE {
+    *       currency              Iso4217CurrencyCode,
+    *       amount               INTEGER,
+    *       exponent             INTEGER }
+    * -- value = amount * 10^exponent
+    * 
+ */ + public class MonetaryValue + : Asn1Encodable + { + internal Iso4217CurrencyCode currency; + internal DerInteger amount; + internal DerInteger exponent; + + public static MonetaryValue GetInstance( + object obj) + { + if (obj == null || obj is MonetaryValue) + { + return (MonetaryValue) obj; + } + + if (obj is Asn1Sequence) + { + return new MonetaryValue(Asn1Sequence.GetInstance(obj)); + } + + throw new ArgumentException("unknown object in GetInstance: " + Platform.GetTypeName(obj), "obj"); + } + + private MonetaryValue( + Asn1Sequence seq) + { + if (seq.Count != 3) + throw new ArgumentException("Bad sequence size: " + seq.Count, "seq"); + + currency = Iso4217CurrencyCode.GetInstance(seq[0]); + amount = DerInteger.GetInstance(seq[1]); + exponent = DerInteger.GetInstance(seq[2]); + } + + public MonetaryValue( + Iso4217CurrencyCode currency, + int amount, + int exponent) + { + this.currency = currency; + this.amount = new DerInteger(amount); + this.exponent = new DerInteger(exponent); + } + + public Iso4217CurrencyCode Currency + { + get { return currency; } + } + + public BigInteger Amount + { + get { return amount.Value; } + } + + public BigInteger Exponent + { + get { return exponent.Value; } + } + + public override Asn1Object ToAsn1Object() + { + return new DerSequence(currency, amount, exponent); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/qualified/QCStatement.cs b/bc-sharp-crypto/src/asn1/x509/qualified/QCStatement.cs new file mode 100644 index 0000000..a8e214c --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/qualified/QCStatement.cs @@ -0,0 +1,84 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.X509.Qualified +{ + /** + * The QCStatement object. + *
+    * QCStatement ::= SEQUENCE {
+    *   statementId        OBJECT IDENTIFIER,
+    *   statementInfo      ANY DEFINED BY statementId OPTIONAL}
+    * 
+ */ + public class QCStatement + : Asn1Encodable + { + private readonly DerObjectIdentifier qcStatementId; + private readonly Asn1Encodable qcStatementInfo; + + public static QCStatement GetInstance( + object obj) + { + if (obj == null || obj is QCStatement) + { + return (QCStatement) obj; + } + + if (obj is Asn1Sequence) + { + return new QCStatement(Asn1Sequence.GetInstance(obj)); + } + + throw new ArgumentException("unknown object in GetInstance: " + Platform.GetTypeName(obj), "obj"); + } + + private QCStatement( + Asn1Sequence seq) + { + qcStatementId = DerObjectIdentifier.GetInstance(seq[0]); + + if (seq.Count > 1) + { + qcStatementInfo = seq[1]; + } + } + + public QCStatement( + DerObjectIdentifier qcStatementId) + { + this.qcStatementId = qcStatementId; + } + + public QCStatement( + DerObjectIdentifier qcStatementId, + Asn1Encodable qcStatementInfo) + { + this.qcStatementId = qcStatementId; + this.qcStatementInfo = qcStatementInfo; + } + + public DerObjectIdentifier StatementId + { + get { return qcStatementId; } + } + + public Asn1Encodable StatementInfo + { + get { return qcStatementInfo; } + } + + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector seq = new Asn1EncodableVector(qcStatementId); + + if (qcStatementInfo != null) + { + seq.Add(qcStatementInfo); + } + + return new DerSequence(seq); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/qualified/RFC3739QCObjectIdentifiers.cs b/bc-sharp-crypto/src/asn1/x509/qualified/RFC3739QCObjectIdentifiers.cs new file mode 100644 index 0000000..8ebd69e --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/qualified/RFC3739QCObjectIdentifiers.cs @@ -0,0 +1,21 @@ +using System; + +using Org.BouncyCastle.Asn1; + +namespace Org.BouncyCastle.Asn1.X509.Qualified +{ + public sealed class Rfc3739QCObjectIdentifiers + { + private Rfc3739QCObjectIdentifiers() + { + } + + // + // base id + // + public static readonly DerObjectIdentifier IdQcs = new DerObjectIdentifier("1.3.6.1.5.5.7.11"); + + public static readonly DerObjectIdentifier IdQcsPkixQCSyntaxV1 = new DerObjectIdentifier(IdQcs+".1"); + public static readonly DerObjectIdentifier IdQcsPkixQCSyntaxV2 = new DerObjectIdentifier(IdQcs+".2"); + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/qualified/SemanticsInformation.cs b/bc-sharp-crypto/src/asn1/x509/qualified/SemanticsInformation.cs new file mode 100644 index 0000000..5fe5f93 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/qualified/SemanticsInformation.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.X509.Qualified +{ + /** + * 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 + : Asn1Encodable + { + private readonly DerObjectIdentifier semanticsIdentifier; + private readonly GeneralName[] nameRegistrationAuthorities; + + public static SemanticsInformation GetInstance( + object obj) + { + if (obj == null || obj is SemanticsInformation) + { + return (SemanticsInformation) obj; + } + + if (obj is Asn1Sequence) + { + return new SemanticsInformation(Asn1Sequence.GetInstance(obj)); + } + + throw new ArgumentException("unknown object in GetInstance: " + Platform.GetTypeName(obj), "obj"); + } + + public SemanticsInformation( + Asn1Sequence seq) + { + if (seq.Count < 1) + { + throw new ArgumentException("no objects in SemanticsInformation"); + } + + IEnumerator e = seq.GetEnumerator(); + e.MoveNext(); + object obj = e.Current; + if (obj is DerObjectIdentifier) + { + semanticsIdentifier = DerObjectIdentifier.GetInstance(obj); + if (e.MoveNext()) + { + obj = e.Current; + } + else + { + obj = null; + } + } + + if (obj != null) + { + Asn1Sequence generalNameSeq = Asn1Sequence.GetInstance(obj ); + nameRegistrationAuthorities = new GeneralName[generalNameSeq.Count]; + for (int i= 0; i < generalNameSeq.Count; i++) + { + nameRegistrationAuthorities[i] = GeneralName.GetInstance(generalNameSeq[i]); + } + } + } + + public SemanticsInformation( + DerObjectIdentifier semanticsIdentifier, + GeneralName[] generalNames) + { + this.semanticsIdentifier = semanticsIdentifier; + this.nameRegistrationAuthorities = generalNames; + } + + public SemanticsInformation( + DerObjectIdentifier semanticsIdentifier) + { + this.semanticsIdentifier = semanticsIdentifier; + } + + public SemanticsInformation( + GeneralName[] generalNames) + { + this.nameRegistrationAuthorities = generalNames; + } + + public DerObjectIdentifier SemanticsIdentifier { get { return semanticsIdentifier; } } + + public GeneralName[] GetNameRegistrationAuthorities() + { + return nameRegistrationAuthorities; + } + + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector seq = new Asn1EncodableVector(); + + if (this.semanticsIdentifier != null) + { + seq.Add(semanticsIdentifier); + } + + if (this.nameRegistrationAuthorities != null) + { + seq.Add(new DerSequence(nameRegistrationAuthorities)); + } + + return new DerSequence(seq); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/qualified/TypeOfBiometricData.cs b/bc-sharp-crypto/src/asn1/x509/qualified/TypeOfBiometricData.cs new file mode 100644 index 0000000..17b7841 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/qualified/TypeOfBiometricData.cs @@ -0,0 +1,91 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.X509.Qualified +{ + /** + * The TypeOfBiometricData object. + *
+    * TypeOfBiometricData ::= CHOICE {
+    *   predefinedBiometricType   PredefinedBiometricType,
+    *   biometricDataOid          OBJECT IDENTIFIER }
+    *
+    * PredefinedBiometricType ::= INTEGER {
+    *   picture(0),handwritten-signature(1)}
+    *   (picture|handwritten-signature)
+    * 
+ */ + public class TypeOfBiometricData + : Asn1Encodable, IAsn1Choice + { + public const int Picture = 0; + public const int HandwrittenSignature = 1; + + internal Asn1Encodable obj; + + public static TypeOfBiometricData GetInstance( + object obj) + { + if (obj == null || obj is TypeOfBiometricData) + { + return (TypeOfBiometricData) obj; + } + + if (obj is DerInteger) + { + DerInteger predefinedBiometricTypeObj = DerInteger.GetInstance(obj); + int predefinedBiometricType = predefinedBiometricTypeObj.Value.IntValue; + + return new TypeOfBiometricData(predefinedBiometricType); + } + + if (obj is DerObjectIdentifier) + { + DerObjectIdentifier BiometricDataOid = DerObjectIdentifier.GetInstance(obj); + return new TypeOfBiometricData(BiometricDataOid); + } + + throw new ArgumentException("unknown object in GetInstance: " + Platform.GetTypeName(obj), "obj"); + } + + public TypeOfBiometricData( + int predefinedBiometricType) + { + if (predefinedBiometricType == Picture || predefinedBiometricType == HandwrittenSignature) + { + obj = new DerInteger(predefinedBiometricType); + } + else + { + throw new ArgumentException("unknow PredefinedBiometricType : " + predefinedBiometricType); + } + } + + public TypeOfBiometricData( + DerObjectIdentifier biometricDataOid) + { + obj = biometricDataOid; + } + + public bool IsPredefined + { + get { return obj is DerInteger; } + } + + public int PredefinedBiometricType + { + get { return ((DerInteger) obj).Value.IntValue; } + } + + public DerObjectIdentifier BiometricDataOid + { + get { return (DerObjectIdentifier) obj; } + } + + public override Asn1Object ToAsn1Object() + { + return obj.ToAsn1Object(); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/sigi/NameOrPseudonym.cs b/bc-sharp-crypto/src/asn1/x509/sigi/NameOrPseudonym.cs new file mode 100644 index 0000000..2402e38 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/sigi/NameOrPseudonym.cs @@ -0,0 +1,178 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1.X500; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.X509.SigI +{ + /** + * Structure for a name or pseudonym. + * + *
+	*       NameOrPseudonym ::= CHOICE {
+	*     	   surAndGivenName SEQUENCE {
+	*     	     surName DirectoryString,
+	*     	     givenName SEQUENCE OF DirectoryString 
+	*         },
+	*     	   pseudonym DirectoryString 
+	*       }
+	* 
+ * + * @see org.bouncycastle.asn1.x509.sigi.PersonalData + * + */ + public class NameOrPseudonym + : Asn1Encodable, IAsn1Choice + { + private readonly DirectoryString pseudonym; + private readonly DirectoryString surname; + private readonly Asn1Sequence givenName; + + public static NameOrPseudonym GetInstance( + object obj) + { + if (obj == null || obj is NameOrPseudonym) + { + return (NameOrPseudonym)obj; + } + + if (obj is IAsn1String) + { + return new NameOrPseudonym(DirectoryString.GetInstance(obj)); + } + + if (obj is Asn1Sequence) + { + return new NameOrPseudonym((Asn1Sequence) obj); + } + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + /** + * 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.Count != 2) + throw new ArgumentException("Bad sequence size: " + seq.Count); + + if (!(seq[0] is IAsn1String)) + throw new ArgumentException("Bad object encountered: " + Platform.GetTypeName(seq[0])); + + surname = DirectoryString.GetInstance(seq[0]); + givenName = Asn1Sequence.GetInstance(seq[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 Pseudonym + { + get { return pseudonym; } + } + + public DirectoryString Surname + { + get { return surname; } + } + + public DirectoryString[] GetGivenName() + { + DirectoryString[] items = new DirectoryString[givenName.Count]; + int count = 0; + foreach (object o in givenName) + { + items[count++] = DirectoryString.GetInstance(o); + } + return items; + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *

+ * Returns: + *

+ *

+		*       NameOrPseudonym ::= CHOICE {
+		*     	   surAndGivenName SEQUENCE {
+		*     	     surName DirectoryString,
+		*     	     givenName SEQUENCE OF DirectoryString
+		*         },
+		*     	   pseudonym DirectoryString
+		*       }
+		* 
+ * + * @return an Asn1Object + */ + public override Asn1Object ToAsn1Object() + { + if (pseudonym != null) + { + return pseudonym.ToAsn1Object(); + } + + return new DerSequence(surname, givenName); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x509/sigi/PersonalData.cs b/bc-sharp-crypto/src/asn1/x509/sigi/PersonalData.cs new file mode 100644 index 0000000..dba345c --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/sigi/PersonalData.cs @@ -0,0 +1,211 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1.X500; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.X509.SigI +{ + /** + * 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 org.bouncycastle.asn1.x509.sigi.NameOrPseudonym + * @see org.bouncycastle.asn1.x509.sigi.SigIObjectIdentifiers + */ + public class PersonalData + : Asn1Encodable + { + private readonly NameOrPseudonym nameOrPseudonym; + private readonly BigInteger nameDistinguisher; + private readonly DerGeneralizedTime dateOfBirth; + private readonly DirectoryString placeOfBirth; + private readonly string gender; + private readonly DirectoryString postalAddress; + + public static PersonalData GetInstance( + object obj) + { + if (obj == null || obj is PersonalData) + { + return (PersonalData) obj; + } + + if (obj is Asn1Sequence) + { + return new PersonalData((Asn1Sequence) obj); + } + + throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj"); + } + + /** + * 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.Count < 1) + throw new ArgumentException("Bad sequence size: " + seq.Count); + + IEnumerator e = seq.GetEnumerator(); + e.MoveNext(); + + nameOrPseudonym = NameOrPseudonym.GetInstance(e.Current); + + while (e.MoveNext()) + { + Asn1TaggedObject o = Asn1TaggedObject.GetInstance(e.Current); + int tag = o.TagNo; + switch (tag) + { + case 0: + nameDistinguisher = DerInteger.GetInstance(o, false).Value; + 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 ArgumentException("Bad tag number: " + o.TagNo); + } + } + } + + /** + * 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 NameOrPseudonym + { + get { return nameOrPseudonym; } + } + + public BigInteger NameDistinguisher + { + get { return nameDistinguisher; } + } + + public DerGeneralizedTime DateOfBirth + { + get { return dateOfBirth; } + } + + public DirectoryString PlaceOfBirth + { + get { return placeOfBirth; } + } + + public string Gender + { + get { return gender; } + } + + public DirectoryString PostalAddress + { + get { 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 an Asn1Object + */ + public override Asn1Object 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/bc-sharp-crypto/src/asn1/x509/sigi/SigIObjectIdentifiers.cs b/bc-sharp-crypto/src/asn1/x509/sigi/SigIObjectIdentifiers.cs new file mode 100644 index 0000000..682311a --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x509/sigi/SigIObjectIdentifiers.cs @@ -0,0 +1,49 @@ +using System; + +namespace Org.BouncyCastle.Asn1.X509.SigI +{ + /** + * Object Identifiers of SigI specifciation (German Signature Law + * Interoperability specification). + */ + public sealed class SigIObjectIdentifiers + { + private SigIObjectIdentifiers() + { + } + + public readonly static DerObjectIdentifier IdSigI = new DerObjectIdentifier("1.3.36.8"); + + /** + * Key purpose IDs for German SigI (Signature Interoperability + * Specification) + */ + public readonly static DerObjectIdentifier IdSigIKP = new DerObjectIdentifier(IdSigI + ".2"); + + /** + * Certificate policy IDs for German SigI (Signature Interoperability + * Specification) + */ + public readonly static DerObjectIdentifier IdSigICP = new DerObjectIdentifier(IdSigI + ".1"); + + /** + * Other Name IDs for German SigI (Signature Interoperability Specification) + */ + public readonly static DerObjectIdentifier IdSigION = new DerObjectIdentifier(IdSigI + ".4"); + + /** + * To be used for for the generation of directory service certificates. + */ + public static readonly DerObjectIdentifier IdSigIKPDirectoryService = new DerObjectIdentifier(IdSigIKP + ".1"); + + /** + * ID for PersonalData + */ + public static readonly DerObjectIdentifier IdSigIONPersonalData = new DerObjectIdentifier(IdSigION + ".1"); + + /** + * Certificate is conform to german signature law. + */ + public static readonly DerObjectIdentifier IdSigICPSigConform = new DerObjectIdentifier(IdSigICP + ".1"); + } +} diff --git a/bc-sharp-crypto/src/asn1/x9/DHDomainParameters.cs b/bc-sharp-crypto/src/asn1/x9/DHDomainParameters.cs new file mode 100644 index 0000000..b8c1ac0 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x9/DHDomainParameters.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.X9 +{ + public class DHDomainParameters + : Asn1Encodable + { + private readonly DerInteger p, g, q, j; + private readonly DHValidationParms validationParms; + + public static DHDomainParameters GetInstance(Asn1TaggedObject obj, bool isExplicit) + { + return GetInstance(Asn1Sequence.GetInstance(obj, isExplicit)); + } + + public static DHDomainParameters GetInstance(object obj) + { + if (obj == null || obj is DHDomainParameters) + return (DHDomainParameters)obj; + + if (obj is Asn1Sequence) + return new DHDomainParameters((Asn1Sequence)obj); + + throw new ArgumentException("Invalid DHDomainParameters: " + Platform.GetTypeName(obj), "obj"); + } + + public DHDomainParameters(DerInteger p, DerInteger g, DerInteger q, DerInteger j, + DHValidationParms validationParms) + { + if (p == null) + throw new ArgumentNullException("p"); + if (g == null) + throw new ArgumentNullException("g"); + if (q == null) + throw new ArgumentNullException("q"); + + this.p = p; + this.g = g; + this.q = q; + this.j = j; + this.validationParms = validationParms; + } + + private DHDomainParameters(Asn1Sequence seq) + { + if (seq.Count < 3 || seq.Count > 5) + throw new ArgumentException("Bad sequence size: " + seq.Count, "seq"); + + IEnumerator e = seq.GetEnumerator(); + this.p = DerInteger.GetInstance(GetNext(e)); + this.g = DerInteger.GetInstance(GetNext(e)); + this.q = DerInteger.GetInstance(GetNext(e)); + + Asn1Encodable next = GetNext(e); + + if (next != null && next is DerInteger) + { + this.j = DerInteger.GetInstance(next); + next = GetNext(e); + } + + if (next != null) + { + this.validationParms = DHValidationParms.GetInstance(next.ToAsn1Object()); + } + } + + private static Asn1Encodable GetNext(IEnumerator e) + { + return e.MoveNext() ? (Asn1Encodable)e.Current : null; + } + + public DerInteger P + { + get { return this.p; } + } + + public DerInteger G + { + get { return this.g; } + } + + public DerInteger Q + { + get { return this.q; } + } + + public DerInteger J + { + get { return this.j; } + } + + public DHValidationParms ValidationParms + { + get { return this.validationParms; } + } + + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(p, g, q); + + if (this.j != null) + { + v.Add(this.j); + } + + if (this.validationParms != null) + { + v.Add(this.validationParms); + } + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x9/DHPublicKey.cs b/bc-sharp-crypto/src/asn1/x9/DHPublicKey.cs new file mode 100644 index 0000000..74a14a2 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x9/DHPublicKey.cs @@ -0,0 +1,46 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.X9 +{ + public class DHPublicKey + : Asn1Encodable + { + private readonly DerInteger y; + + public static DHPublicKey GetInstance(Asn1TaggedObject obj, bool isExplicit) + { + return GetInstance(DerInteger.GetInstance(obj, isExplicit)); + } + + public static DHPublicKey GetInstance(object obj) + { + if (obj == null || obj is DHPublicKey) + return (DHPublicKey)obj; + + if (obj is DerInteger) + return new DHPublicKey((DerInteger)obj); + + throw new ArgumentException("Invalid DHPublicKey: " + Platform.GetTypeName(obj), "obj"); + } + + public DHPublicKey(DerInteger y) + { + if (y == null) + throw new ArgumentNullException("y"); + + this.y = y; + } + + public DerInteger Y + { + get { return this.y; } + } + + public override Asn1Object ToAsn1Object() + { + return this.y; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x9/DHValidationParms.cs b/bc-sharp-crypto/src/asn1/x9/DHValidationParms.cs new file mode 100644 index 0000000..c63c502 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x9/DHValidationParms.cs @@ -0,0 +1,64 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.X9 +{ + public class DHValidationParms + : Asn1Encodable + { + private readonly DerBitString seed; + private readonly DerInteger pgenCounter; + + public static DHValidationParms GetInstance(Asn1TaggedObject obj, bool isExplicit) + { + return GetInstance(Asn1Sequence.GetInstance(obj, isExplicit)); + } + + public static DHValidationParms GetInstance(object obj) + { + if (obj == null || obj is DHDomainParameters) + return (DHValidationParms)obj; + + if (obj is Asn1Sequence) + return new DHValidationParms((Asn1Sequence)obj); + + throw new ArgumentException("Invalid DHValidationParms: " + Platform.GetTypeName(obj), "obj"); + } + + public DHValidationParms(DerBitString seed, DerInteger pgenCounter) + { + if (seed == null) + throw new ArgumentNullException("seed"); + if (pgenCounter == null) + throw new ArgumentNullException("pgenCounter"); + + this.seed = seed; + this.pgenCounter = pgenCounter; + } + + private DHValidationParms(Asn1Sequence seq) + { + if (seq.Count != 2) + throw new ArgumentException("Bad sequence size: " + seq.Count, "seq"); + + this.seed = DerBitString.GetInstance(seq[0]); + this.pgenCounter = DerInteger.GetInstance(seq[1]); + } + + public DerBitString Seed + { + get { return this.seed; } + } + + public DerInteger PgenCounter + { + get { return this.pgenCounter; } + } + + public override Asn1Object ToAsn1Object() + { + return new DerSequence(seed, pgenCounter); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x9/ECNamedCurveTable.cs b/bc-sharp-crypto/src/asn1/x9/ECNamedCurveTable.cs new file mode 100644 index 0000000..317ef17 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x9/ECNamedCurveTable.cs @@ -0,0 +1,162 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1.Anssi; +using Org.BouncyCastle.Asn1.GM; +using Org.BouncyCastle.Asn1.Nist; +using Org.BouncyCastle.Asn1.Sec; +using Org.BouncyCastle.Asn1.TeleTrust; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; + +namespace Org.BouncyCastle.Asn1.X9 +{ + /** + * A general class that reads all X9.62 style EC curve tables. + */ + public class ECNamedCurveTable + { + /** + * return a X9ECParameters object representing the passed in named + * curve. The routine returns null if the curve is not present. + * + * @param name the name of the curve requested + * @return an X9ECParameters object or null if the curve is not available. + */ + public static X9ECParameters GetByName(string name) + { + X9ECParameters ecP = X962NamedCurves.GetByName(name); + if (ecP == null) + { + ecP = SecNamedCurves.GetByName(name); + } + if (ecP == null) + { + ecP = NistNamedCurves.GetByName(name); + } + if (ecP == null) + { + ecP = TeleTrusTNamedCurves.GetByName(name); + } + if (ecP == null) + { + ecP = AnssiNamedCurves.GetByName(name); + } + if (ecP == null) + { + ecP = GMNamedCurves.GetByName(name); + } + return ecP; + } + + public static string GetName(DerObjectIdentifier oid) + { + string name = X962NamedCurves.GetName(oid); + if (name == null) + { + name = SecNamedCurves.GetName(oid); + } + if (name == null) + { + name = NistNamedCurves.GetName(oid); + } + if (name == null) + { + name = TeleTrusTNamedCurves.GetName(oid); + } + if (name == null) + { + name = AnssiNamedCurves.GetName(oid); + } + if (name == null) + { + name = GMNamedCurves.GetName(oid); + } + return name; + } + + /** + * 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) + { + DerObjectIdentifier oid = X962NamedCurves.GetOid(name); + if (oid == null) + { + oid = SecNamedCurves.GetOid(name); + } + if (oid == null) + { + oid = NistNamedCurves.GetOid(name); + } + if (oid == null) + { + oid = TeleTrusTNamedCurves.GetOid(name); + } + if (oid == null) + { + oid = AnssiNamedCurves.GetOid(name); + } + if (oid == null) + { + oid = GMNamedCurves.GetOid(name); + } + return oid; + } + + /** + * return a X9ECParameters object representing the passed in named + * curve. + * + * @param oid the object id of the curve requested + * @return an X9ECParameters object or null if the curve is not available. + */ + public static X9ECParameters GetByOid(DerObjectIdentifier oid) + { + X9ECParameters ecP = X962NamedCurves.GetByOid(oid); + if (ecP == null) + { + ecP = SecNamedCurves.GetByOid(oid); + } + + // NOTE: All the NIST curves are currently from SEC, so no point in redundant OID lookup + + if (ecP == null) + { + ecP = TeleTrusTNamedCurves.GetByOid(oid); + } + if (ecP == null) + { + ecP = AnssiNamedCurves.GetByOid(oid); + } + if (ecP == null) + { + ecP = GMNamedCurves.GetByOid(oid); + } + return ecP; + } + + /** + * return an enumeration of the names of the available curves. + * + * @return an enumeration of the names of the available curves. + */ + public static IEnumerable Names + { + get + { + IList v = Platform.CreateArrayList(); + CollectionUtilities.AddRange(v, X962NamedCurves.Names); + CollectionUtilities.AddRange(v, SecNamedCurves.Names); + CollectionUtilities.AddRange(v, NistNamedCurves.Names); + CollectionUtilities.AddRange(v, TeleTrusTNamedCurves.Names); + CollectionUtilities.AddRange(v, AnssiNamedCurves.Names); + CollectionUtilities.AddRange(v, GMNamedCurves.Names); + return v; + } + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x9/KeySpecificInfo.cs b/bc-sharp-crypto/src/asn1/x9/KeySpecificInfo.cs new file mode 100644 index 0000000..4629864 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x9/KeySpecificInfo.cs @@ -0,0 +1,58 @@ +using System.Collections; + +namespace Org.BouncyCastle.Asn1.X9 +{ + /** + * ASN.1 def for Diffie-Hellman key exchange KeySpecificInfo structure. See + * RFC 2631, or X9.42, for further details. + */ + public class KeySpecificInfo + : Asn1Encodable + { + private DerObjectIdentifier algorithm; + private Asn1OctetString counter; + + public KeySpecificInfo( + DerObjectIdentifier algorithm, + Asn1OctetString counter) + { + this.algorithm = algorithm; + this.counter = counter; + } + + public KeySpecificInfo( + Asn1Sequence seq) + { + IEnumerator e = seq.GetEnumerator(); + + e.MoveNext(); + algorithm = (DerObjectIdentifier)e.Current; + e.MoveNext(); + counter = (Asn1OctetString)e.Current; + } + + public DerObjectIdentifier Algorithm + { + get { return algorithm; } + } + + public Asn1OctetString Counter + { + get { return counter; } + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *
+         *  KeySpecificInfo ::= Sequence {
+         *      algorithm OBJECT IDENTIFIER,
+         *      counter OCTET STRING SIZE (4..4)
+         *  }
+         * 
+ */ + public override Asn1Object ToAsn1Object() + { + return new DerSequence(algorithm, counter); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x9/OtherInfo.cs b/bc-sharp-crypto/src/asn1/x9/OtherInfo.cs new file mode 100644 index 0000000..21863bd --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x9/OtherInfo.cs @@ -0,0 +1,88 @@ +using System.Collections; + +namespace Org.BouncyCastle.Asn1.X9 +{ + /** + * ANS.1 def for Diffie-Hellman key exchange OtherInfo structure. See + * RFC 2631, or X9.42, for further details. + */ + public class OtherInfo + : 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) + { + IEnumerator e = seq.GetEnumerator(); + + e.MoveNext(); + keyInfo = new KeySpecificInfo((Asn1Sequence) e.Current); + + while (e.MoveNext()) + { + DerTaggedObject o = (DerTaggedObject) e.Current; + + if (o.TagNo == 0) + { + partyAInfo = (Asn1OctetString) o.GetObject(); + } + else if ((int) o.TagNo == 2) + { + suppPubInfo = (Asn1OctetString) o.GetObject(); + } + } + } + + public KeySpecificInfo KeyInfo + { + get { return keyInfo; } + } + + public Asn1OctetString PartyAInfo + { + get { return partyAInfo; } + } + + public Asn1OctetString SuppPubInfo + { + get { return suppPubInfo; } + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *
+         *  OtherInfo ::= Sequence {
+         *      keyInfo KeySpecificInfo,
+         *      partyAInfo [0] OCTET STRING OPTIONAL,
+         *      suppPubInfo [2] OCTET STRING
+         *  }
+         * 
+ */ + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(keyInfo); + + if (partyAInfo != null) + { + v.Add(new DerTaggedObject(0, partyAInfo)); + } + + v.Add(new DerTaggedObject(2, suppPubInfo)); + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x9/X962NamedCurves.cs b/bc-sharp-crypto/src/asn1/x9/X962NamedCurves.cs new file mode 100644 index 0000000..14f7f81 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x9/X962NamedCurves.cs @@ -0,0 +1,751 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Math.EC; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Asn1.X9 +{ + /** + * table of the current named curves defined in X.962 EC-DSA. + */ + public sealed class X962NamedCurves + { + private X962NamedCurves() + { + } + + internal class Prime192v1Holder + : X9ECParametersHolder + { + private Prime192v1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new Prime192v1Holder(); + + protected override X9ECParameters CreateParameters() + { + BigInteger n = new BigInteger("ffffffffffffffffffffffff99def836146bc9b1b4d22831", 16); + BigInteger h = BigInteger.One; + + ECCurve cFp192v1 = new FpCurve( + new BigInteger("6277101735386680763835789423207666416083908700390324961279"), + new BigInteger("fffffffffffffffffffffffffffffffefffffffffffffffc", 16), + new BigInteger("64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1", 16), + n, h); + + return new X9ECParameters( + cFp192v1, + new X9ECPoint(cFp192v1, + Hex.Decode("03188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012")), + n, h, + Hex.Decode("3045AE6FC8422f64ED579528D38120EAE12196D5")); + } + } + + internal class Prime192v2Holder + : X9ECParametersHolder + { + private Prime192v2Holder() {} + + internal static readonly X9ECParametersHolder Instance = new Prime192v2Holder(); + + protected override X9ECParameters CreateParameters() + { + BigInteger n = new BigInteger("fffffffffffffffffffffffe5fb1a724dc80418648d8dd31", 16); + BigInteger h = BigInteger.One; + + ECCurve cFp192v2 = new FpCurve( + new BigInteger("6277101735386680763835789423207666416083908700390324961279"), + new BigInteger("fffffffffffffffffffffffffffffffefffffffffffffffc", 16), + new BigInteger("cc22d6dfb95c6b25e49c0d6364a4e5980c393aa21668d953", 16), + n, h); + + return new X9ECParameters( + cFp192v2, + new X9ECPoint(cFp192v2, + Hex.Decode("03eea2bae7e1497842f2de7769cfe9c989c072ad696f48034a")), + n, h, + Hex.Decode("31a92ee2029fd10d901b113e990710f0d21ac6b6")); + } + } + + internal class Prime192v3Holder + : X9ECParametersHolder + { + private Prime192v3Holder() {} + + internal static readonly X9ECParametersHolder Instance = new Prime192v3Holder(); + + protected override X9ECParameters CreateParameters() + { + BigInteger n = new BigInteger("ffffffffffffffffffffffff7a62d031c83f4294f640ec13", 16); + BigInteger h = BigInteger.One; + + ECCurve cFp192v3 = new FpCurve( + new BigInteger("6277101735386680763835789423207666416083908700390324961279"), + new BigInteger("fffffffffffffffffffffffffffffffefffffffffffffffc", 16), + new BigInteger("22123dc2395a05caa7423daeccc94760a7d462256bd56916", 16), + n, h); + + return new X9ECParameters( + cFp192v3, + new X9ECPoint(cFp192v3, + Hex.Decode("027d29778100c65a1da1783716588dce2b8b4aee8e228f1896")), + n, h, + Hex.Decode("c469684435deb378c4b65ca9591e2a5763059a2e")); + } + } + + internal class Prime239v1Holder + : X9ECParametersHolder + { + private Prime239v1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new Prime239v1Holder(); + + protected override X9ECParameters CreateParameters() + { + BigInteger n = new BigInteger("7fffffffffffffffffffffff7fffff9e5e9a9f5d9071fbd1522688909d0b", 16); + BigInteger h = BigInteger.One; + + ECCurve cFp239v1 = new FpCurve( + new BigInteger("883423532389192164791648750360308885314476597252960362792450860609699839"), + new BigInteger("7fffffffffffffffffffffff7fffffffffff8000000000007ffffffffffc", 16), + new BigInteger("6b016c3bdcf18941d0d654921475ca71a9db2fb27d1d37796185c2942c0a", 16), + n, h); + + return new X9ECParameters( + cFp239v1, + new X9ECPoint(cFp239v1, + Hex.Decode("020ffa963cdca8816ccc33b8642bedf905c3d358573d3f27fbbd3b3cb9aaaf")), + n, h, + Hex.Decode("e43bb460f0b80cc0c0b075798e948060f8321b7d")); + } + } + + internal class Prime239v2Holder + : X9ECParametersHolder + { + private Prime239v2Holder() {} + + internal static readonly X9ECParametersHolder Instance = new Prime239v2Holder(); + + protected override X9ECParameters CreateParameters() + { + BigInteger n = new BigInteger("7fffffffffffffffffffffff800000cfa7e8594377d414c03821bc582063", 16); + BigInteger h = BigInteger.One; + + ECCurve cFp239v2 = new FpCurve( + new BigInteger("883423532389192164791648750360308885314476597252960362792450860609699839"), + new BigInteger("7fffffffffffffffffffffff7fffffffffff8000000000007ffffffffffc", 16), + new BigInteger("617fab6832576cbbfed50d99f0249c3fee58b94ba0038c7ae84c8c832f2c", 16), + n, h); + + return new X9ECParameters( + cFp239v2, + new X9ECPoint(cFp239v2, + Hex.Decode("0238af09d98727705120c921bb5e9e26296a3cdcf2f35757a0eafd87b830e7")), + n, h, + Hex.Decode("e8b4011604095303ca3b8099982be09fcb9ae616")); + } + } + + internal class Prime239v3Holder + : X9ECParametersHolder + { + private Prime239v3Holder() {} + + internal static readonly X9ECParametersHolder Instance = new Prime239v3Holder(); + + protected override X9ECParameters CreateParameters() + { + BigInteger n = new BigInteger("7fffffffffffffffffffffff7fffff975deb41b3a6057c3c432146526551", 16); + BigInteger h = BigInteger.One; + + ECCurve cFp239v3 = new FpCurve( + new BigInteger("883423532389192164791648750360308885314476597252960362792450860609699839"), + new BigInteger("7fffffffffffffffffffffff7fffffffffff8000000000007ffffffffffc", 16), + new BigInteger("255705fa2a306654b1f4cb03d6a750a30c250102d4988717d9ba15ab6d3e", 16), + n, h); + + return new X9ECParameters( + cFp239v3, + new X9ECPoint(cFp239v3, + Hex.Decode("036768ae8e18bb92cfcf005c949aa2c6d94853d0e660bbf854b1c9505fe95a")), + n, h, + Hex.Decode("7d7374168ffe3471b60a857686a19475d3bfa2ff")); + } + } + + internal class Prime256v1Holder + : X9ECParametersHolder + { + private Prime256v1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new Prime256v1Holder(); + + protected override X9ECParameters CreateParameters() + { + BigInteger n = new BigInteger("ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", 16); + BigInteger h = BigInteger.One; + + ECCurve cFp256v1 = new FpCurve( + new BigInteger("115792089210356248762697446949407573530086143415290314195533631308867097853951"), + new BigInteger("ffffffff00000001000000000000000000000000fffffffffffffffffffffffc", 16), + new BigInteger("5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b", 16), + n, h); + + return new X9ECParameters( + cFp256v1, + new X9ECPoint(cFp256v1, + Hex.Decode("036b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296")), + n, h, + Hex.Decode("c49d360886e704936a6678e1139d26b7819f7e90")); + } + } + + /* + * F2m Curves + */ + internal class C2pnb163v1Holder + : X9ECParametersHolder + { + private C2pnb163v1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new C2pnb163v1Holder(); + + protected override X9ECParameters CreateParameters() + { + BigInteger n = new BigInteger("0400000000000000000001E60FC8821CC74DAEAFC1", 16); + BigInteger h = BigInteger.Two; + + ECCurve c2m163v1 = new F2mCurve( + 163, + 1, 2, 8, + new BigInteger("072546B5435234A422E0789675F432C89435DE5242", 16), + new BigInteger("00C9517D06D5240D3CFF38C74B20B6CD4D6F9DD4D9", 16), + n, h); + + return new X9ECParameters( + c2m163v1, + new X9ECPoint(c2m163v1, + Hex.Decode("0307AF69989546103D79329FCC3D74880F33BBE803CB")), + n, h, + Hex.Decode("D2C0FB15760860DEF1EEF4D696E6768756151754")); + } + } + + internal class C2pnb163v2Holder + : X9ECParametersHolder + { + private C2pnb163v2Holder() {} + + internal static readonly X9ECParametersHolder Instance = new C2pnb163v2Holder(); + + protected override X9ECParameters CreateParameters() + { + BigInteger n = new BigInteger("03FFFFFFFFFFFFFFFFFFFDF64DE1151ADBB78F10A7", 16); + BigInteger h = BigInteger.Two; + + ECCurve c2m163v2 = new F2mCurve( + 163, + 1, 2, 8, + new BigInteger("0108B39E77C4B108BED981ED0E890E117C511CF072", 16), + new BigInteger("0667ACEB38AF4E488C407433FFAE4F1C811638DF20", 16), + n, h); + + return new X9ECParameters( + c2m163v2, + new X9ECPoint(c2m163v2, + Hex.Decode("030024266E4EB5106D0A964D92C4860E2671DB9B6CC5")), + n, h, + null); + } + } + + internal class C2pnb163v3Holder + : X9ECParametersHolder + { + private C2pnb163v3Holder() {} + + internal static readonly X9ECParametersHolder Instance = new C2pnb163v3Holder(); + + protected override X9ECParameters CreateParameters() + { + BigInteger n = new BigInteger("03FFFFFFFFFFFFFFFFFFFE1AEE140F110AFF961309", 16); + BigInteger h = BigInteger.Two; + + ECCurve c2m163v3 = new F2mCurve( + 163, + 1, 2, 8, + new BigInteger("07A526C63D3E25A256A007699F5447E32AE456B50E", 16), + new BigInteger("03F7061798EB99E238FD6F1BF95B48FEEB4854252B", 16), + n, h); + + return new X9ECParameters( + c2m163v3, + new X9ECPoint(c2m163v3, Hex.Decode("0202F9F87B7C574D0BDECF8A22E6524775F98CDEBDCB")), + n, h, + null); + } + } + + internal class C2pnb176w1Holder + : X9ECParametersHolder + { + private C2pnb176w1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new C2pnb176w1Holder(); + + protected override X9ECParameters CreateParameters() + { + BigInteger n = new BigInteger("010092537397ECA4F6145799D62B0A19CE06FE26AD", 16); + BigInteger h = BigInteger.ValueOf(0xFF6E); + + ECCurve c2m176w1 = new F2mCurve( + 176, + 1, 2, 43, + new BigInteger("00E4E6DB2995065C407D9D39B8D0967B96704BA8E9C90B", 16), + new BigInteger("005DDA470ABE6414DE8EC133AE28E9BBD7FCEC0AE0FFF2", 16), + n, h); + + return new X9ECParameters( + c2m176w1, + new X9ECPoint(c2m176w1, + Hex.Decode("038D16C2866798B600F9F08BB4A8E860F3298CE04A5798")), + n, h, + null); + } + } + + internal class C2tnb191v1Holder + : X9ECParametersHolder + { + private C2tnb191v1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new C2tnb191v1Holder(); + + protected override X9ECParameters CreateParameters() + { + BigInteger n = new BigInteger("40000000000000000000000004A20E90C39067C893BBB9A5", 16); + BigInteger h = BigInteger.Two; + + ECCurve c2m191v1 = new F2mCurve( + 191, + 9, + new BigInteger("2866537B676752636A68F56554E12640276B649EF7526267", 16), + new BigInteger("2E45EF571F00786F67B0081B9495A3D95462F5DE0AA185EC", 16), + n, h); + + return new X9ECParameters( + c2m191v1, + new X9ECPoint(c2m191v1, + Hex.Decode("0236B3DAF8A23206F9C4F299D7B21A9C369137F2C84AE1AA0D")), + n, h, + Hex.Decode("4E13CA542744D696E67687561517552F279A8C84")); + } + } + + internal class C2tnb191v2Holder + : X9ECParametersHolder + { + private C2tnb191v2Holder() {} + + internal static readonly X9ECParametersHolder Instance = new C2tnb191v2Holder(); + + protected override X9ECParameters CreateParameters() + { + BigInteger n = new BigInteger("20000000000000000000000050508CB89F652824E06B8173", 16); + BigInteger h = BigInteger.ValueOf(4); + + ECCurve c2m191v2 = new F2mCurve( + 191, + 9, + new BigInteger("401028774D7777C7B7666D1366EA432071274F89FF01E718", 16), + new BigInteger("0620048D28BCBD03B6249C99182B7C8CD19700C362C46A01", 16), + n, h); + + return new X9ECParameters( + c2m191v2, + new X9ECPoint(c2m191v2, + Hex.Decode("023809B2B7CC1B28CC5A87926AAD83FD28789E81E2C9E3BF10")), + n, h, + null); + } + } + + internal class C2tnb191v3Holder + : X9ECParametersHolder + { + private C2tnb191v3Holder() {} + + internal static readonly X9ECParametersHolder Instance = new C2tnb191v3Holder(); + + protected override X9ECParameters CreateParameters() + { + BigInteger n = new BigInteger("155555555555555555555555610C0B196812BFB6288A3EA3", 16); + BigInteger h = BigInteger.ValueOf(6); + + ECCurve c2m191v3 = new F2mCurve( + 191, + 9, + new BigInteger("6C01074756099122221056911C77D77E77A777E7E7E77FCB", 16), + new BigInteger("71FE1AF926CF847989EFEF8DB459F66394D90F32AD3F15E8", 16), + n, h); + + return new X9ECParameters( + c2m191v3, + new X9ECPoint(c2m191v3, + Hex.Decode("03375D4CE24FDE434489DE8746E71786015009E66E38A926DD")), + n, h, + null); + } + } + + internal class C2pnb208w1Holder + : X9ECParametersHolder + { + private C2pnb208w1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new C2pnb208w1Holder(); + + protected override X9ECParameters CreateParameters() + { + BigInteger n = new BigInteger("0101BAF95C9723C57B6C21DA2EFF2D5ED588BDD5717E212F9D", 16); + BigInteger h = BigInteger.ValueOf(0xFE48); + + ECCurve c2m208w1 = new F2mCurve( + 208, + 1, 2, 83, + new BigInteger("0", 16), + new BigInteger("00C8619ED45A62E6212E1160349E2BFA844439FAFC2A3FD1638F9E", 16), + n, h); + + return new X9ECParameters( + c2m208w1, + new X9ECPoint(c2m208w1, + Hex.Decode("0289FDFBE4ABE193DF9559ECF07AC0CE78554E2784EB8C1ED1A57A")), + n, h, + null); + } + } + + internal class C2tnb239v1Holder + : X9ECParametersHolder + { + private C2tnb239v1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new C2tnb239v1Holder(); + + protected override X9ECParameters CreateParameters() + { + BigInteger n = new BigInteger("2000000000000000000000000000000F4D42FFE1492A4993F1CAD666E447", 16); + BigInteger h = BigInteger.ValueOf(4); + + ECCurve c2m239v1 = new F2mCurve( + 239, + 36, + new BigInteger("32010857077C5431123A46B808906756F543423E8D27877578125778AC76", 16), + new BigInteger("790408F2EEDAF392B012EDEFB3392F30F4327C0CA3F31FC383C422AA8C16", 16), + n, h); + + return new X9ECParameters( + c2m239v1, + new X9ECPoint(c2m239v1, + Hex.Decode("0257927098FA932E7C0A96D3FD5B706EF7E5F5C156E16B7E7C86038552E91D")), + n, h, + null); + } + } + + internal class C2tnb239v2Holder + : X9ECParametersHolder + { + private C2tnb239v2Holder() {} + + internal static readonly X9ECParametersHolder Instance = new C2tnb239v2Holder(); + + protected override X9ECParameters CreateParameters() + { + BigInteger n = new BigInteger("1555555555555555555555555555553C6F2885259C31E3FCDF154624522D", 16); + BigInteger h = BigInteger.ValueOf(6); + + ECCurve c2m239v2 = new F2mCurve( + 239, + 36, + new BigInteger("4230017757A767FAE42398569B746325D45313AF0766266479B75654E65F", 16), + new BigInteger("5037EA654196CFF0CD82B2C14A2FCF2E3FF8775285B545722F03EACDB74B", 16), + n, h); + + return new X9ECParameters( + c2m239v2, + new X9ECPoint(c2m239v2, + Hex.Decode("0228F9D04E900069C8DC47A08534FE76D2B900B7D7EF31F5709F200C4CA205")), + n, h, + null); + } + } + + internal class C2tnb239v3Holder + : X9ECParametersHolder + { + private C2tnb239v3Holder() {} + + internal static readonly X9ECParametersHolder Instance = new C2tnb239v3Holder(); + + protected override X9ECParameters CreateParameters() + { + BigInteger n = new BigInteger("0CCCCCCCCCCCCCCCCCCCCCCCCCCCCCAC4912D2D9DF903EF9888B8A0E4CFF", 16); + BigInteger h = BigInteger.ValueOf(10); + + ECCurve c2m239v3 = new F2mCurve( + 239, + 36, + new BigInteger("01238774666A67766D6676F778E676B66999176666E687666D8766C66A9F", 16), + new BigInteger("6A941977BA9F6A435199ACFC51067ED587F519C5ECB541B8E44111DE1D40", 16), + n, h); + + return new X9ECParameters( + c2m239v3, + new X9ECPoint(c2m239v3, + Hex.Decode("0370F6E9D04D289C4E89913CE3530BFDE903977D42B146D539BF1BDE4E9C92")), + n, h, + null); + } + } + + internal class C2pnb272w1Holder + : X9ECParametersHolder + { + private C2pnb272w1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new C2pnb272w1Holder(); + + protected override X9ECParameters CreateParameters() + { + BigInteger n = new BigInteger("0100FAF51354E0E39E4892DF6E319C72C8161603FA45AA7B998A167B8F1E629521", 16); + BigInteger h = BigInteger.ValueOf(0xFF06); + + ECCurve c2m272w1 = new F2mCurve( + 272, + 1, 3, 56, + new BigInteger("0091A091F03B5FBA4AB2CCF49C4EDD220FB028712D42BE752B2C40094DBACDB586FB20", 16), + new BigInteger("7167EFC92BB2E3CE7C8AAAFF34E12A9C557003D7C73A6FAF003F99F6CC8482E540F7", 16), + n, h); + + return new X9ECParameters( + c2m272w1, + new X9ECPoint(c2m272w1, + Hex.Decode("026108BABB2CEEBCF787058A056CBE0CFE622D7723A289E08A07AE13EF0D10D171DD8D")), + n, h, + null); + } + } + + internal class C2pnb304w1Holder + : X9ECParametersHolder + { + private C2pnb304w1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new C2pnb304w1Holder(); + + protected override X9ECParameters CreateParameters() + { + BigInteger n = new BigInteger("0101D556572AABAC800101D556572AABAC8001022D5C91DD173F8FB561DA6899164443051D", 16); + BigInteger h = BigInteger.ValueOf(0xFE2E); + + ECCurve c2m304w1 = new F2mCurve( + 304, + 1, 2, 11, + new BigInteger("00FD0D693149A118F651E6DCE6802085377E5F882D1B510B44160074C1288078365A0396C8E681", 16), + new BigInteger("00BDDB97E555A50A908E43B01C798EA5DAA6788F1EA2794EFCF57166B8C14039601E55827340BE", 16), + n, h); + + return new X9ECParameters( + c2m304w1, + new X9ECPoint(c2m304w1, + Hex.Decode("02197B07845E9BE2D96ADB0F5F3C7F2CFFBD7A3EB8B6FEC35C7FD67F26DDF6285A644F740A2614")), + n, h, + null); + } + } + + internal class C2tnb359v1Holder + : X9ECParametersHolder + { + private C2tnb359v1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new C2tnb359v1Holder(); + + protected override X9ECParameters CreateParameters() + { + BigInteger n = new BigInteger("01AF286BCA1AF286BCA1AF286BCA1AF286BCA1AF286BC9FB8F6B85C556892C20A7EB964FE7719E74F490758D3B", 16); + BigInteger h = BigInteger.ValueOf(0x4C); + + ECCurve c2m359v1 = new F2mCurve( + 359, + 68, + new BigInteger("5667676A654B20754F356EA92017D946567C46675556F19556A04616B567D223A5E05656FB549016A96656A557", 16), + new BigInteger("2472E2D0197C49363F1FE7F5B6DB075D52B6947D135D8CA445805D39BC345626089687742B6329E70680231988", 16), + n, h); + + return new X9ECParameters( + c2m359v1, + new X9ECPoint(c2m359v1, + Hex.Decode("033C258EF3047767E7EDE0F1FDAA79DAEE3841366A132E163ACED4ED2401DF9C6BDCDE98E8E707C07A2239B1B097")), + n, h, + null); + } + } + + internal class C2pnb368w1Holder + : X9ECParametersHolder + { + private C2pnb368w1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new C2pnb368w1Holder(); + + protected override X9ECParameters CreateParameters() + { + BigInteger n = new BigInteger("010090512DA9AF72B08349D98A5DD4C7B0532ECA51CE03E2D10F3B7AC579BD87E909AE40A6F131E9CFCE5BD967", 16); + BigInteger h = BigInteger.ValueOf(0xFF70); + + ECCurve c2m368w1 = new F2mCurve( + 368, + 1, 2, 85, + new BigInteger("00E0D2EE25095206F5E2A4F9ED229F1F256E79A0E2B455970D8D0D865BD94778C576D62F0AB7519CCD2A1A906AE30D", 16), + new BigInteger("00FC1217D4320A90452C760A58EDCD30C8DD069B3C34453837A34ED50CB54917E1C2112D84D164F444F8F74786046A", 16), + n, h); + + return new X9ECParameters( + c2m368w1, + new X9ECPoint(c2m368w1, + Hex.Decode("021085E2755381DCCCE3C1557AFA10C2F0C0C2825646C5B34A394CBCFA8BC16B22E7E789E927BE216F02E1FB136A5F")), + n, h, + null); + } + } + + internal class C2tnb431r1Holder + : X9ECParametersHolder + { + private C2tnb431r1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new C2tnb431r1Holder(); + + protected override X9ECParameters CreateParameters() + { + BigInteger n = new BigInteger("0340340340340340340340340340340340340340340340340340340323C313FAB50589703B5EC68D3587FEC60D161CC149C1AD4A91", 16); + BigInteger h = BigInteger.ValueOf(0x2760); + + ECCurve c2m431r1 = new F2mCurve( + 431, + 120, + new BigInteger("1A827EF00DD6FC0E234CAF046C6A5D8A85395B236CC4AD2CF32A0CADBDC9DDF620B0EB9906D0957F6C6FEACD615468DF104DE296CD8F", 16), + new BigInteger("10D9B4A3D9047D8B154359ABFB1B7F5485B04CEB868237DDC9DEDA982A679A5A919B626D4E50A8DD731B107A9962381FB5D807BF2618", 16), + n, h); + + return new X9ECParameters( + c2m431r1, + new X9ECPoint(c2m431r1, + Hex.Decode("02120FC05D3C67A99DE161D2F4092622FECA701BE4F50F4758714E8A87BBF2A658EF8C21E7C5EFE965361F6C2999C0C247B0DBD70CE6B7")), + n, h, + null); + } + } + + private static readonly IDictionary objIds = Platform.CreateHashtable(); + private static readonly IDictionary curves = Platform.CreateHashtable(); + private static readonly IDictionary names = Platform.CreateHashtable(); + + private static void DefineCurve( + string name, + DerObjectIdentifier oid, + X9ECParametersHolder holder) + { + objIds.Add(Platform.ToUpperInvariant(name), oid); + names.Add(oid, name); + curves.Add(oid, holder); + } + + static X962NamedCurves() + { + DefineCurve("prime192v1", X9ObjectIdentifiers.Prime192v1, Prime192v1Holder.Instance); + DefineCurve("prime192v2", X9ObjectIdentifiers.Prime192v2, Prime192v2Holder.Instance); + DefineCurve("prime192v3", X9ObjectIdentifiers.Prime192v3, Prime192v3Holder.Instance); + DefineCurve("prime239v1", X9ObjectIdentifiers.Prime239v1, Prime239v1Holder.Instance); + DefineCurve("prime239v2", X9ObjectIdentifiers.Prime239v2, Prime239v2Holder.Instance); + DefineCurve("prime239v3", X9ObjectIdentifiers.Prime239v3, Prime239v3Holder.Instance); + DefineCurve("prime256v1", X9ObjectIdentifiers.Prime256v1, Prime256v1Holder.Instance); + DefineCurve("c2pnb163v1", X9ObjectIdentifiers.C2Pnb163v1, C2pnb163v1Holder.Instance); + DefineCurve("c2pnb163v2", X9ObjectIdentifiers.C2Pnb163v2, C2pnb163v2Holder.Instance); + DefineCurve("c2pnb163v3", X9ObjectIdentifiers.C2Pnb163v3, C2pnb163v3Holder.Instance); + DefineCurve("c2pnb176w1", X9ObjectIdentifiers.C2Pnb176w1, C2pnb176w1Holder.Instance); + DefineCurve("c2tnb191v1", X9ObjectIdentifiers.C2Tnb191v1, C2tnb191v1Holder.Instance); + DefineCurve("c2tnb191v2", X9ObjectIdentifiers.C2Tnb191v2, C2tnb191v2Holder.Instance); + DefineCurve("c2tnb191v3", X9ObjectIdentifiers.C2Tnb191v3, C2tnb191v3Holder.Instance); + DefineCurve("c2pnb208w1", X9ObjectIdentifiers.C2Pnb208w1, C2pnb208w1Holder.Instance); + DefineCurve("c2tnb239v1", X9ObjectIdentifiers.C2Tnb239v1, C2tnb239v1Holder.Instance); + DefineCurve("c2tnb239v2", X9ObjectIdentifiers.C2Tnb239v2, C2tnb239v2Holder.Instance); + DefineCurve("c2tnb239v3", X9ObjectIdentifiers.C2Tnb239v3, C2tnb239v3Holder.Instance); + DefineCurve("c2pnb272w1", X9ObjectIdentifiers.C2Pnb272w1, C2pnb272w1Holder.Instance); + DefineCurve("c2pnb304w1", X9ObjectIdentifiers.C2Pnb304w1, C2pnb304w1Holder.Instance); + DefineCurve("c2tnb359v1", X9ObjectIdentifiers.C2Tnb359v1, C2tnb359v1Holder.Instance); + DefineCurve("c2pnb368w1", X9ObjectIdentifiers.C2Pnb368w1, C2pnb368w1Holder.Instance); + DefineCurve("c2tnb431r1", X9ObjectIdentifiers.C2Tnb431r1, C2tnb431r1Holder.Instance); + } + + public static X9ECParameters GetByName( + string name) + { + DerObjectIdentifier oid = GetOid(name); + return oid == null ? null : GetByOid(oid); + } + + /** + * 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[oid]; + return holder == null ? null : holder.Parameters; + } + + /** + * 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[Platform.ToUpperInvariant(name)]; + } + + /** + * return the named curve name represented by the given object identifier. + */ + public static string GetName( + DerObjectIdentifier oid) + { + return (string)names[oid]; + } + + /** + * returns an enumeration containing the name strings for curves + * contained in this structure. + */ + public static IEnumerable Names + { + get { return new EnumerableProxy(names.Values); } + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x9/X962Parameters.cs b/bc-sharp-crypto/src/asn1/x9/X962Parameters.cs new file mode 100644 index 0000000..04a5c9c --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x9/X962Parameters.cs @@ -0,0 +1,88 @@ +using System; + +using Org.BouncyCastle.Asn1; + +namespace Org.BouncyCastle.Asn1.X9 +{ + public class X962Parameters + : Asn1Encodable, IAsn1Choice + { + private readonly Asn1Object _params; + + public static X962Parameters GetInstance( + object obj) + { + if (obj == null || obj is X962Parameters) + { + return (X962Parameters)obj; + } + + if (obj is Asn1Object) + { + return new X962Parameters((Asn1Object)obj); + } + + if (obj is byte[]) + { + try + { + return new X962Parameters(Asn1Object.FromByteArray((byte[])obj)); + } + catch (Exception e) + { + throw new ArgumentException("unable to parse encoded data: " + e.Message, e); + } + } + + throw new ArgumentException("unknown object in getInstance()"); + } + + public X962Parameters( + X9ECParameters ecParameters) + { + this._params = ecParameters.ToAsn1Object(); + } + + public X962Parameters( + DerObjectIdentifier namedCurve) + { + this._params = namedCurve; + } + + public X962Parameters( + Asn1Object obj) + { + this._params = obj; + } + + public bool IsNamedCurve + { + get { return (_params is DerObjectIdentifier); } + } + + public bool IsImplicitlyCA + { + get { return (_params is Asn1Null); } + } + + public Asn1Object Parameters + { + get { return _params; } + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *
+         * Parameters ::= CHOICE {
+         *    ecParameters ECParameters,
+         *    namedCurve   CURVES.&id({CurveNames}),
+         *    implicitlyCA Null
+         * }
+         * 
+ */ + public override Asn1Object ToAsn1Object() + { + return _params; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x9/X9Curve.cs b/bc-sharp-crypto/src/asn1/x9/X9Curve.cs new file mode 100644 index 0000000..f05a946 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x9/X9Curve.cs @@ -0,0 +1,146 @@ +using System; + +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Math.EC; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.X9 +{ + /** + * ASN.1 def for Elliptic-Curve Curve structure. See + * X9.62, for further details. + */ + public class X9Curve + : Asn1Encodable + { + private readonly ECCurve curve; + private readonly byte[] seed; + private readonly DerObjectIdentifier fieldIdentifier; + + public X9Curve( + ECCurve curve) + : this(curve, null) + { + } + + public X9Curve( + ECCurve curve, + byte[] seed) + { + if (curve == null) + throw new ArgumentNullException("curve"); + + this.curve = curve; + this.seed = Arrays.Clone(seed); + + if (ECAlgorithms.IsFpCurve(curve)) + { + this.fieldIdentifier = X9ObjectIdentifiers.PrimeField; + } + else if (ECAlgorithms.IsF2mCurve(curve)) + { + this.fieldIdentifier = X9ObjectIdentifiers.CharacteristicTwoField; + } + else + { + throw new ArgumentException("This type of ECCurve is not implemented"); + } + } + + public X9Curve( + X9FieldID fieldID, + Asn1Sequence seq) + { + if (fieldID == null) + throw new ArgumentNullException("fieldID"); + if (seq == null) + throw new ArgumentNullException("seq"); + + this.fieldIdentifier = fieldID.Identifier; + + if (fieldIdentifier.Equals(X9ObjectIdentifiers.PrimeField)) + { + BigInteger q = ((DerInteger) fieldID.Parameters).Value; + X9FieldElement x9A = new X9FieldElement(q, (Asn1OctetString) seq[0]); + X9FieldElement x9B = new X9FieldElement(q, (Asn1OctetString) seq[1]); + curve = new FpCurve(q, x9A.Value.ToBigInteger(), x9B.Value.ToBigInteger()); + } + else + { + if (fieldIdentifier.Equals(X9ObjectIdentifiers.CharacteristicTwoField)) + { + // Characteristic two field + DerSequence parameters = (DerSequence)fieldID.Parameters; + int m = ((DerInteger)parameters[0]).Value.IntValue; + DerObjectIdentifier representation + = (DerObjectIdentifier)parameters[1]; + + int k1 = 0; + int k2 = 0; + int k3 = 0; + if (representation.Equals(X9ObjectIdentifiers.TPBasis)) + { + // Trinomial basis representation + k1 = ((DerInteger)parameters[2]).Value.IntValue; + } + else + { + // Pentanomial basis representation + DerSequence pentanomial = (DerSequence) parameters[2]; + k1 = ((DerInteger) pentanomial[0]).Value.IntValue; + k2 = ((DerInteger) pentanomial[1]).Value.IntValue; + k3 = ((DerInteger) pentanomial[2]).Value.IntValue; + } + X9FieldElement x9A = new X9FieldElement(m, k1, k2, k3, (Asn1OctetString)seq[0]); + X9FieldElement x9B = new X9FieldElement(m, k1, k2, k3, (Asn1OctetString)seq[1]); + // TODO Is it possible to get the order (n) and cofactor(h) too? + curve = new F2mCurve(m, k1, k2, k3, x9A.Value.ToBigInteger(), x9B.Value.ToBigInteger()); + } + } + + if (seq.Count == 3) + { + seed = ((DerBitString) seq[2]).GetBytes(); + } + } + + public ECCurve Curve + { + get { return curve; } + } + + public byte[] GetSeed() + { + return Arrays.Clone(seed); + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *
+         *  Curve ::= Sequence {
+         *      a               FieldElement,
+         *      b               FieldElement,
+         *      seed            BIT STRING      OPTIONAL
+         *  }
+         * 
+ */ + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(); + + if (fieldIdentifier.Equals(X9ObjectIdentifiers.PrimeField) + || fieldIdentifier.Equals(X9ObjectIdentifiers.CharacteristicTwoField)) + { + v.Add(new X9FieldElement(curve.A).ToAsn1Object()); + v.Add(new X9FieldElement(curve.B).ToAsn1Object()); + } + + if (seed != null) + { + v.Add(new DerBitString(seed)); + } + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x9/X9ECParameters.cs b/bc-sharp-crypto/src/asn1/x9/X9ECParameters.cs new file mode 100644 index 0000000..0fa3437 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x9/X9ECParameters.cs @@ -0,0 +1,233 @@ +using System; + +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Math.EC; +using Org.BouncyCastle.Math.Field; + +namespace Org.BouncyCastle.Asn1.X9 +{ + /** + * ASN.1 def for Elliptic-Curve ECParameters structure. See + * X9.62, for further details. + */ + public class X9ECParameters + : Asn1Encodable + { + private X9FieldID fieldID; + private ECCurve curve; + private X9ECPoint g; + private BigInteger n; + private BigInteger h; + private byte[] seed; + + public static X9ECParameters GetInstance(Object obj) + { + if (obj is X9ECParameters) + { + return (X9ECParameters)obj; + } + + if (obj != null) + { + return new X9ECParameters(Asn1Sequence.GetInstance(obj)); + } + + return null; + } + + public X9ECParameters( + Asn1Sequence seq) + { + if (!(seq[0] is DerInteger) + || !((DerInteger) seq[0]).Value.Equals(BigInteger.One)) + { + throw new ArgumentException("bad version in X9ECParameters"); + } + + X9Curve x9c = new X9Curve( + X9FieldID.GetInstance(seq[1]), + Asn1Sequence.GetInstance(seq[2])); + + this.curve = x9c.Curve; + object p = seq[3]; + + if (p is X9ECPoint) + { + this.g = ((X9ECPoint)p); + } + else + { + this.g = new X9ECPoint(curve, (Asn1OctetString)p); + } + + this.n = ((DerInteger)seq[4]).Value; + this.seed = x9c.GetSeed(); + + if (seq.Count == 6) + { + this.h = ((DerInteger)seq[5]).Value; + } + } + + public X9ECParameters( + ECCurve curve, + ECPoint g, + BigInteger n) + : this(curve, g, n, null, null) + { + } + + public X9ECParameters( + ECCurve curve, + X9ECPoint g, + BigInteger n, + BigInteger h) + : this(curve, g, n, h, 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, new X9ECPoint(g), n, h, seed) + { + } + + public X9ECParameters( + ECCurve curve, + X9ECPoint g, + BigInteger n, + BigInteger h, + byte[] seed) + { + this.curve = curve; + this.g = g; + this.n = n; + this.h = h; + this.seed = seed; + + if (ECAlgorithms.IsFpCurve(curve)) + { + this.fieldID = new X9FieldID(curve.Field.Characteristic); + } + else if (ECAlgorithms.IsF2mCurve(curve)) + { + IPolynomialExtensionField field = (IPolynomialExtensionField)curve.Field; + int[] exponents = field.MinimalPolynomial.GetExponentsPresent(); + if (exponents.Length == 3) + { + this.fieldID = new X9FieldID(exponents[2], exponents[1]); + } + else if (exponents.Length == 5) + { + this.fieldID = new X9FieldID(exponents[4], exponents[1], exponents[2], exponents[3]); + } + else + { + throw new ArgumentException("Only trinomial and pentomial curves are supported"); + } + } + else + { + throw new ArgumentException("'curve' is of an unsupported type"); + } + } + + public ECCurve Curve + { + get { return curve; } + } + + public ECPoint G + { + get { return g.Point; } + } + + public BigInteger N + { + get { return n; } + } + + public BigInteger H + { + get { return h; } + } + + public byte[] GetSeed() + { + return seed; + } + + /** + * Return the ASN.1 entry representing the Curve. + * + * @return the X9Curve for the curve in these parameters. + */ + public X9Curve CurveEntry + { + get { return new X9Curve(curve, seed); } + } + + /** + * Return the ASN.1 entry representing the FieldID. + * + * @return the X9FieldID for the FieldID in these parameters. + */ + public X9FieldID FieldIDEntry + { + get { return fieldID; } + } + + /** + * Return the ASN.1 entry representing the base point G. + * + * @return the X9ECPoint for the base point in these parameters. + */ + public X9ECPoint BaseEntry + { + get { return g; } + } + + /** + * 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 override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector( + new DerInteger(BigInteger.One), + fieldID, + new X9Curve(curve, seed), + g, + new DerInteger(n)); + + if (h != null) + { + v.Add(new DerInteger(h)); + } + + return new DerSequence(v); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x9/X9ECParametersHolder.cs b/bc-sharp-crypto/src/asn1/x9/X9ECParametersHolder.cs new file mode 100644 index 0000000..e802b73 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x9/X9ECParametersHolder.cs @@ -0,0 +1,25 @@ +namespace Org.BouncyCastle.Asn1.X9 +{ + public abstract class X9ECParametersHolder + { + private X9ECParameters parameters; + + public X9ECParameters Parameters + { + get + { + lock (this) + { + if (parameters == null) + { + parameters = CreateParameters(); + } + + return parameters; + } + } + } + + protected abstract X9ECParameters CreateParameters(); + } +} diff --git a/bc-sharp-crypto/src/asn1/x9/X9ECPoint.cs b/bc-sharp-crypto/src/asn1/x9/X9ECPoint.cs new file mode 100644 index 0000000..7ef4f13 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x9/X9ECPoint.cs @@ -0,0 +1,80 @@ +using Org.BouncyCastle.Math.EC; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Asn1.X9 +{ + /** + * class for describing an ECPoint as a Der object. + */ + public class X9ECPoint + : Asn1Encodable + { + private readonly Asn1OctetString encoding; + + private ECCurve c; + private ECPoint p; + + public X9ECPoint(ECPoint p) + : this(p, false) + { + } + + public X9ECPoint(ECPoint p, bool compressed) + { + this.p = p.Normalize(); + this.encoding = new DerOctetString(p.GetEncoded(compressed)); + } + + public X9ECPoint(ECCurve c, byte[] encoding) + { + this.c = c; + this.encoding = new DerOctetString(Arrays.Clone(encoding)); + } + + public X9ECPoint(ECCurve c, Asn1OctetString s) + : this(c, s.GetOctets()) + { + } + + public byte[] GetPointEncoding() + { + return Arrays.Clone(encoding.GetOctets()); + } + + public ECPoint Point + { + get + { + if (p == null) + { + p = c.DecodePoint(encoding.GetOctets()).Normalize(); + } + + return p; + } + } + + public bool IsPointCompressed + { + get + { + byte[] octets = encoding.GetOctets(); + return octets != null && octets.Length > 0 && (octets[0] == 2 || octets[0] == 3); + } + } + + /** + * Produce an object suitable for an Asn1OutputStream. + *
+         *  ECPoint ::= OCTET STRING
+         * 
+ *

+ * Octet string produced using ECPoint.GetEncoded().

+ */ + public override Asn1Object ToAsn1Object() + { + return encoding; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x9/X9FieldElement.cs b/bc-sharp-crypto/src/asn1/x9/X9FieldElement.cs new file mode 100644 index 0000000..94bd96b --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x9/X9FieldElement.cs @@ -0,0 +1,69 @@ +using System; + +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Math.EC; + +namespace Org.BouncyCastle.Asn1.X9 +{ + /** + * Class for processing an ECFieldElement as a DER object. + */ + public class X9FieldElement + : Asn1Encodable + { + private ECFieldElement f; + + public X9FieldElement( + ECFieldElement f) + { + this.f = f; + } + + public X9FieldElement( + BigInteger p, + Asn1OctetString s) + : this(new FpFieldElement(p, new BigInteger(1, s.GetOctets()))) + { + } + + public X9FieldElement( + int m, + int k1, + int k2, + int k3, + Asn1OctetString s) + : this(new F2mFieldElement(m, k1, k2, k3, new BigInteger(1, s.GetOctets()))) + { + } + + public ECFieldElement Value + { + get { 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 override Asn1Object ToAsn1Object() + { + int byteCount = X9IntegerConverter.GetByteLength(f); + byte[] paddedBigInteger = X9IntegerConverter.IntegerToBytes(f.ToBigInteger(), byteCount); + + return new DerOctetString(paddedBigInteger); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x9/X9FieldID.cs b/bc-sharp-crypto/src/asn1/x9/X9FieldID.cs new file mode 100644 index 0000000..08d7d71 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x9/X9FieldID.cs @@ -0,0 +1,132 @@ +using System; + +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Asn1.X9 +{ + /** + * ASN.1 def for Elliptic-Curve Field ID structure. See + * X9.62, for further details. + */ + public class X9FieldID + : Asn1Encodable + { + private readonly DerObjectIdentifier id; + private readonly Asn1Object parameters; + + /** + * Constructor for elliptic curves over prime fields + * F2. + * @param primeP The prime p defining the prime field. + */ + public X9FieldID( + BigInteger primeP) + { + this.id = X9ObjectIdentifiers.PrimeField; + 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 + + * xk1 + 1 + * represents the reduction polynomial f(z). + */ + public X9FieldID(int m, int k1) + : this(m, k1, 0, 0) + { + } + + /** + * 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 = X9ObjectIdentifiers.CharacteristicTwoField; + + Asn1EncodableVector fieldIdParams = new Asn1EncodableVector(new DerInteger(m)); + + if (k2 == 0) + { + if (k3 != 0) + throw new ArgumentException("inconsistent k values"); + + fieldIdParams.Add( + X9ObjectIdentifiers.TPBasis, + new DerInteger(k1)); + } + else + { + if (k2 <= k1 || k3 <= k2) + throw new ArgumentException("inconsistent k values"); + + fieldIdParams.Add( + X9ObjectIdentifiers.PPBasis, + new DerSequence( + new DerInteger(k1), + new DerInteger(k2), + new DerInteger(k3))); + } + + this.parameters = new DerSequence(fieldIdParams); + } + + private X9FieldID(Asn1Sequence seq) + { + this.id = DerObjectIdentifier.GetInstance(seq[0]); + this.parameters = seq[1].ToAsn1Object(); + } + + public static X9FieldID GetInstance(object obj) + { + if (obj is X9FieldID) + return (X9FieldID)obj; + if (obj == null) + return null; + return new X9FieldID(Asn1Sequence.GetInstance(obj)); + } + + public DerObjectIdentifier Identifier + { + get { return id; } + } + + public Asn1Object Parameters + { + get { return parameters; } + } + + /** + * Produce a Der encoding of the following structure. + *
+         *  FieldID ::= Sequence {
+         *      fieldType       FIELD-ID.&id({IOSet}),
+         *      parameters      FIELD-ID.&Type({IOSet}{@fieldType})
+         *  }
+         * 
+ */ + public override Asn1Object ToAsn1Object() + { + return new DerSequence(id, parameters); + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x9/X9IntegerConverter.cs b/bc-sharp-crypto/src/asn1/x9/X9IntegerConverter.cs new file mode 100644 index 0000000..e8f4571 --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x9/X9IntegerConverter.cs @@ -0,0 +1,40 @@ +using System; + +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Math.EC; + +namespace Org.BouncyCastle.Asn1.X9 +{ + public abstract class X9IntegerConverter + { + public static int GetByteLength(ECFieldElement fe) + { + return (fe.FieldSize + 7) / 8; + } + + public static int GetByteLength(ECCurve c) + { + return (c.FieldSize + 7) / 8; + } + + public static byte[] IntegerToBytes(BigInteger s, int qLength) + { + byte[] bytes = s.ToByteArrayUnsigned(); + + if (qLength < bytes.Length) + { + byte[] tmp = new byte[qLength]; + Array.Copy(bytes, bytes.Length - tmp.Length, tmp, 0, tmp.Length); + return tmp; + } + else if (qLength > bytes.Length) + { + byte[] tmp = new byte[qLength]; + Array.Copy(bytes, 0, tmp, tmp.Length - bytes.Length, bytes.Length); + return tmp; + } + + return bytes; + } + } +} diff --git a/bc-sharp-crypto/src/asn1/x9/X9ObjectIdentifiers.cs b/bc-sharp-crypto/src/asn1/x9/X9ObjectIdentifiers.cs new file mode 100644 index 0000000..9d7ecae --- /dev/null +++ b/bc-sharp-crypto/src/asn1/x9/X9ObjectIdentifiers.cs @@ -0,0 +1,137 @@ +using System; + +namespace Org.BouncyCastle.Asn1.X9 +{ + public abstract class X9ObjectIdentifiers + { + // + // X9.62 + // + // ansi-X9-62 OBJECT IDENTIFIER ::= { iso(1) member-body(2) + // us(840) ansi-x962(10045) } + // + + internal const string AnsiX962 = "1.2.840.10045"; + + public static readonly DerObjectIdentifier ansi_X9_62 = new DerObjectIdentifier(AnsiX962); + + public static readonly DerObjectIdentifier IdFieldType = ansi_X9_62.Branch("1"); + + public static readonly DerObjectIdentifier PrimeField = IdFieldType.Branch("1"); + public static readonly DerObjectIdentifier CharacteristicTwoField = IdFieldType.Branch("2"); + + public static readonly DerObjectIdentifier GNBasis = CharacteristicTwoField.Branch("3.1"); + public static readonly DerObjectIdentifier TPBasis = CharacteristicTwoField.Branch("3.2"); + public static readonly DerObjectIdentifier PPBasis = CharacteristicTwoField.Branch("3.3"); + + [Obsolete("Use 'id_ecSigType' instead")] + public const string IdECSigType = AnsiX962 + ".4"; + public static readonly DerObjectIdentifier id_ecSigType = ansi_X9_62.Branch("4"); + + public static readonly DerObjectIdentifier ECDsaWithSha1 = id_ecSigType.Branch("1"); + + [Obsolete("Use 'id_publicKeyType' instead")] + public const string IdPublicKeyType = AnsiX962 + ".2"; + public static readonly DerObjectIdentifier id_publicKeyType = ansi_X9_62.Branch("2"); + + public static readonly DerObjectIdentifier IdECPublicKey = id_publicKeyType.Branch("1"); + + public static readonly DerObjectIdentifier ECDsaWithSha2 = id_ecSigType.Branch("3"); + + public static readonly DerObjectIdentifier ECDsaWithSha224 = ECDsaWithSha2.Branch("1"); + public static readonly DerObjectIdentifier ECDsaWithSha256 = ECDsaWithSha2.Branch("2"); + public static readonly DerObjectIdentifier ECDsaWithSha384 = ECDsaWithSha2.Branch("3"); + public static readonly DerObjectIdentifier ECDsaWithSha512 = ECDsaWithSha2.Branch("4"); + + + // + // named curves + // + public static readonly DerObjectIdentifier EllipticCurve = ansi_X9_62.Branch("3"); + + // + // Two Curves + // + public static readonly DerObjectIdentifier CTwoCurve = EllipticCurve.Branch("0"); + + public static readonly DerObjectIdentifier C2Pnb163v1 = CTwoCurve.Branch("1"); + public static readonly DerObjectIdentifier C2Pnb163v2 = CTwoCurve.Branch("2"); + public static readonly DerObjectIdentifier C2Pnb163v3 = CTwoCurve.Branch("3"); + public static readonly DerObjectIdentifier C2Pnb176w1 = CTwoCurve.Branch("4"); + public static readonly DerObjectIdentifier C2Tnb191v1 = CTwoCurve.Branch("5"); + public static readonly DerObjectIdentifier C2Tnb191v2 = CTwoCurve.Branch("6"); + public static readonly DerObjectIdentifier C2Tnb191v3 = CTwoCurve.Branch("7"); + public static readonly DerObjectIdentifier C2Onb191v4 = CTwoCurve.Branch("8"); + public static readonly DerObjectIdentifier C2Onb191v5 = CTwoCurve.Branch("9"); + public static readonly DerObjectIdentifier C2Pnb208w1 = CTwoCurve.Branch("10"); + public static readonly DerObjectIdentifier C2Tnb239v1 = CTwoCurve.Branch("11"); + public static readonly DerObjectIdentifier C2Tnb239v2 = CTwoCurve.Branch("12"); + public static readonly DerObjectIdentifier C2Tnb239v3 = CTwoCurve.Branch("13"); + public static readonly DerObjectIdentifier C2Onb239v4 = CTwoCurve.Branch("14"); + public static readonly DerObjectIdentifier C2Onb239v5 = CTwoCurve.Branch("15"); + public static readonly DerObjectIdentifier C2Pnb272w1 = CTwoCurve.Branch("16"); + public static readonly DerObjectIdentifier C2Pnb304w1 = CTwoCurve.Branch("17"); + public static readonly DerObjectIdentifier C2Tnb359v1 = CTwoCurve.Branch("18"); + public static readonly DerObjectIdentifier C2Pnb368w1 = CTwoCurve.Branch("19"); + public static readonly DerObjectIdentifier C2Tnb431r1 = CTwoCurve.Branch("20"); + + // + // Prime + // + public static readonly DerObjectIdentifier PrimeCurve = EllipticCurve.Branch("1"); + + public static readonly DerObjectIdentifier Prime192v1 = PrimeCurve.Branch("1"); + public static readonly DerObjectIdentifier Prime192v2 = PrimeCurve.Branch("2"); + public static readonly DerObjectIdentifier Prime192v3 = PrimeCurve.Branch("3"); + public static readonly DerObjectIdentifier Prime239v1 = PrimeCurve.Branch("4"); + public static readonly DerObjectIdentifier Prime239v2 = PrimeCurve.Branch("5"); + public static readonly DerObjectIdentifier Prime239v3 = PrimeCurve.Branch("6"); + public static readonly DerObjectIdentifier Prime256v1 = PrimeCurve.Branch("7"); + + // + // DSA + // + // dsapublicnumber OBJECT IDENTIFIER ::= { iso(1) member-body(2) + // us(840) ansi-x957(10040) number-type(4) 1 } + public static readonly DerObjectIdentifier IdDsa = 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 readonly DerObjectIdentifier IdDsaWithSha1 = new DerObjectIdentifier("1.2.840.10040.4.3"); + + /** + * X9.63 + */ + public static readonly DerObjectIdentifier X9x63Scheme = new DerObjectIdentifier("1.3.133.16.840.63.0"); + public static readonly DerObjectIdentifier DHSinglePassStdDHSha1KdfScheme = X9x63Scheme.Branch("2"); + public static readonly DerObjectIdentifier DHSinglePassCofactorDHSha1KdfScheme = X9x63Scheme.Branch("3"); + public static readonly DerObjectIdentifier MqvSinglePassSha1KdfScheme = X9x63Scheme.Branch("16"); + + /** + * X9.42 + */ + + public static readonly DerObjectIdentifier ansi_x9_42 = new DerObjectIdentifier("1.2.840.10046"); + + // + // Diffie-Hellman + // + // dhpublicnumber OBJECT IDENTIFIER ::= { iso(1) member-body(2) + // us(840) ansi-x942(10046) number-type(2) 1 } + // + public static readonly DerObjectIdentifier DHPublicNumber = ansi_x9_42.Branch("2.1"); + + public static readonly DerObjectIdentifier X9x42Schemes = ansi_x9_42.Branch("2.3"); + + public static readonly DerObjectIdentifier DHStatic = X9x42Schemes.Branch("1"); + public static readonly DerObjectIdentifier DHEphem = X9x42Schemes.Branch("2"); + public static readonly DerObjectIdentifier DHOneFlow = X9x42Schemes.Branch("3"); + public static readonly DerObjectIdentifier DHHybrid1 = X9x42Schemes.Branch("4"); + public static readonly DerObjectIdentifier DHHybrid2 = X9x42Schemes.Branch("5"); + public static readonly DerObjectIdentifier DHHybridOneFlow = X9x42Schemes.Branch("6"); + public static readonly DerObjectIdentifier Mqv2 = X9x42Schemes.Branch("7"); + public static readonly DerObjectIdentifier Mqv1 = X9x42Schemes.Branch("8"); + } +} diff --git a/bc-sharp-crypto/src/bcpg/ArmoredInputStream.cs b/bc-sharp-crypto/src/bcpg/ArmoredInputStream.cs new file mode 100644 index 0000000..d5d9f7f --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/ArmoredInputStream.cs @@ -0,0 +1,524 @@ +using System; +using System.Collections; +using System.IO; +using System.Text; + +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Bcpg +{ + /** + * 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 + : BaseInputStream + { + /* + * set up the decoding table. + */ + private readonly static byte[] decodingTable; + static ArmoredInputStream() + { + 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[] result) + { + if (in3 < 0) + { + throw new EndOfStreamException("unexpected end of file in armored stream."); + } + + int b1, b2, b3, b4; + if (in2 == '=') + { + b1 = decodingTable[in0] &0xff; + b2 = decodingTable[in1] & 0xff; + result[2] = ((b1 << 2) | (b2 >> 4)) & 0xff; + return 2; + } + else if (in3 == '=') + { + b1 = decodingTable[in0]; + b2 = decodingTable[in1]; + b3 = decodingTable[in2]; + result[1] = ((b1 << 2) | (b2 >> 4)) & 0xff; + result[2] = ((b2 << 4) | (b3 >> 2)) & 0xff; + return 1; + } + else + { + b1 = decodingTable[in0]; + b2 = decodingTable[in1]; + b3 = decodingTable[in2]; + b4 = decodingTable[in3]; + result[0] = ((b1 << 2) | (b2 >> 4)) & 0xff; + result[1] = ((b2 << 4) | (b3 >> 2)) & 0xff; + result[2] = ((b3 << 6) | b4) & 0xff; + return 0; + } + } + + Stream input; + bool start = true; + int[] outBuf = new int[3]; + int bufPtr = 3; + Crc24 crc = new Crc24(); + bool crcFound = false; + bool hasHeaders = true; + string header = null; + bool newLineFound = false; + bool clearText = false; + bool restart = false; + IList headerList= Platform.CreateArrayList(); + int lastC = 0; + bool isEndOfStream; + + /** + * Create a stream for reading a PGP armoured message, parsing up to a header + * and then reading the data that follows. + * + * @param input + */ + public ArmoredInputStream( + Stream input) + : this(input, 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 input + * @param hasHeaders true if headers are to be looked for, false otherwise. + */ + public ArmoredInputStream( + Stream input, + bool hasHeaders) + { + this.input = input; + this.hasHeaders = hasHeaders; + + if (hasHeaders) + { + ParseHeaders(); + } + + start = false; + } + + private bool ParseHeaders() + { + header = null; + + int c; + int last = 0; + bool headerFound = false; + + headerList = Platform.CreateArrayList(); + + // + // if restart we already have a header + // + if (restart) + { + headerFound = true; + } + else + { + while ((c = input.ReadByte()) >= 0) + { + if (c == '-' && (last == 0 || last == '\n' || last == '\r')) + { + headerFound = true; + break; + } + + last = c; + } + } + + if (headerFound) + { + StringBuilder Buffer = new StringBuilder("-"); + bool eolReached = false; + bool crLf = false; + + if (restart) // we've had to look ahead two '-' + { + Buffer.Append('-'); + } + + while ((c = input.ReadByte()) >= 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 = Buffer.ToString(); + if (line.Trim().Length < 1) + break; + headerList.Add(line); + Buffer.Length = 0; + } + + if (c != '\n' && c != '\r') + { + Buffer.Append((char)c); + eolReached = false; + } + else + { + if (c == '\r' || (last != '\r' && c == '\n')) + { + eolReached = true; + } + } + + last = c; + } + + if (crLf) + { + input.ReadByte(); // skip last \n + } + } + + if (headerList.Count > 0) + { + header = (string) headerList[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 bool IsClearText() + { + return clearText; + } + + /** + * @return true if the stream is actually at end of file. + */ + public bool 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.Count <= 1) + { + return null; + } + + string[] hdrs = new string[headerList.Count - 1]; + for (int i = 0; i != hdrs.Length; i++) + { + hdrs[i] = (string) headerList[i + 1]; + } + + return hdrs; + } + + private int ReadIgnoreSpace() + { + int c; + do + { + c = input.ReadByte(); + } + while (c == ' ' || c == '\t'); + + return c; + } + + private int ReadIgnoreWhitespace() + { + int c; + do + { + c = input.ReadByte(); + } + while (c == ' ' || c == '\t' || c == '\r' || c == '\n'); + + return c; + } + + private int ReadByteClearText() + { + int c = input.ReadByte(); + + if (c == '\r' || (c == '\n' && lastC != '\r')) + { + newLineFound = true; + } + else if (newLineFound && c == '-') + { + c = input.ReadByte(); + if (c == '-') // a header, not dash escaped + { + clearText = false; + start = true; + restart = true; + } + else // a space - must be a dash escape + { + c = input.ReadByte(); + } + newLineFound = false; + } + else + { + if (c != '\n' && lastC != '\r') + { + newLineFound = false; + } + } + + lastC = c; + + if (c < 0) + { + isEndOfStream = true; + } + + return c; + } + + private int ReadClearText(byte[] buffer, int offset, int count) + { + int pos = offset; + try + { + int end = offset + count; + while (pos < end) + { + int c = ReadByteClearText(); + if (c == -1) + { + break; + } + buffer[pos++] = (byte) c; + } + } + catch (IOException ioe) + { + if (pos == offset) throw ioe; + } + + return pos - offset; + } + + private int DoReadByte() + { + if (bufPtr > 2 || crcFound) + { + int c = ReadIgnoreSpace(); + if (c == '\n' || c == '\r') + { + c = ReadIgnoreWhitespace(); + if (c == '=') // crc reached + { + bufPtr = Decode(ReadIgnoreSpace(), ReadIgnoreSpace(), ReadIgnoreSpace(), ReadIgnoreSpace(), outBuf); + + if (bufPtr != 0) + { + throw new IOException("no crc found in armored message."); + } + + crcFound = true; + + int i = ((outBuf[0] & 0xff) << 16) + | ((outBuf[1] & 0xff) << 8) + | (outBuf[2] & 0xff); + + if (i != crc.Value) + { + throw new IOException("crc check failed in armored message."); + } + + return ReadByte(); + } + + if (c == '-') // end of record reached + { + while ((c = input.ReadByte()) >= 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; + } + } + + if (c < 0) + { + isEndOfStream = true; + return -1; + } + + bufPtr = Decode(c, ReadIgnoreSpace(), ReadIgnoreSpace(), ReadIgnoreSpace(), outBuf); + } + + return outBuf[bufPtr++]; + } + + public override int ReadByte() + { + if (start) + { + if (hasHeaders) + { + ParseHeaders(); + } + + crc.Reset(); + start = false; + } + + if (clearText) + { + return ReadByteClearText(); + } + + int c = DoReadByte(); + + crc.Update(c); + + return c; + } + + public override int Read(byte[] buffer, int offset, int count) + { + if (start && count > 0) + { + if (hasHeaders) + { + ParseHeaders(); + } + start = false; + } + + if (clearText) + { + return ReadClearText(buffer, offset, count); + } + + int pos = offset; + try + { + int end = offset + count; + while (pos < end) + { + int c = DoReadByte(); + crc.Update(c); + if (c == -1) + { + break; + } + buffer[pos++] = (byte) c; + } + } + catch (IOException ioe) + { + if (pos == offset) throw ioe; + } + + return pos - offset; + } + +#if PORTABLE + protected override void Dispose(bool disposing) + { + if (disposing) + { + Platform.Dispose(input); + } + base.Dispose(disposing); + } +#else + public override void Close() + { + Platform.Dispose(input); + base.Close(); + } +#endif + } +} diff --git a/bc-sharp-crypto/src/bcpg/ArmoredOutputStream.cs b/bc-sharp-crypto/src/bcpg/ArmoredOutputStream.cs new file mode 100644 index 0000000..7b39065 --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/ArmoredOutputStream.cs @@ -0,0 +1,375 @@ +using System; +using System.Collections; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Text; + +#if PORTABLE +using System.Linq; +#endif + +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Bcpg +{ + /** + * Basic output stream. + */ + public class ArmoredOutputStream + : BaseOutputStream + { + public static readonly string HeaderVersion = "Version"; + + private static readonly 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 static void Encode( + Stream outStream, + int[] data, + int len) + { + Debug.Assert(len > 0); + Debug.Assert(len < 4); + + byte[] bs = new byte[4]; + int d1 = data[0]; + bs[0] = encodingTable[(d1 >> 2) & 0x3f]; + + switch (len) + { + case 1: + { + bs[1] = encodingTable[(d1 << 4) & 0x3f]; + bs[2] = (byte)'='; + bs[3] = (byte)'='; + break; + } + case 2: + { + int d2 = data[1]; + bs[1] = encodingTable[((d1 << 4) | (d2 >> 4)) & 0x3f]; + bs[2] = encodingTable[(d2 << 2) & 0x3f]; + bs[3] = (byte)'='; + break; + } + case 3: + { + int d2 = data[1]; + int d3 = data[2]; + bs[1] = encodingTable[((d1 << 4) | (d2 >> 4)) & 0x3f]; + bs[2] = encodingTable[((d2 << 2) | (d3 >> 6)) & 0x3f]; + bs[3] = encodingTable[d3 & 0x3f]; + break; + } + } + + outStream.Write(bs, 0, bs.Length); + } + + private readonly Stream outStream; + private int[] buf = new int[3]; + private int bufPtr = 0; + private Crc24 crc = new Crc24(); + private int chunkCount = 0; + private int lastb; + + private bool start = true; + private bool clearText = false; + private bool newLine = false; + + private string type; + + private static readonly string nl = Platform.NewLine; + private static readonly string headerStart = "-----BEGIN PGP "; + private static readonly string headerTail = "-----"; + private static readonly string footerStart = "-----END PGP "; + private static readonly string footerTail = "-----"; + + private static readonly string Version = "BCPG C# v1.8.1";//AssemblyInfo.Version; + + private readonly IDictionary headers; + + public ArmoredOutputStream(Stream outStream) + { + this.outStream = outStream; + this.headers = Platform.CreateHashtable(1); + this.headers.Add(HeaderVersion, Version); + } + + public ArmoredOutputStream(Stream outStream, IDictionary headers) + { + this.outStream = outStream; + this.headers = Platform.CreateHashtable(headers); + if (!this.headers.Contains(HeaderVersion)) + { + this.headers.Add(HeaderVersion, Version); + } + } + + /** + * Set an additional header entry. A null value will clear the entry for name. + * + * @param name the name of the header entry. + * @param v the value of the header entry. + */ + public void SetHeader(string name, string v) + { + if (v == null) + { + headers.Remove(name); + } + else + { + headers[name] = v; + } + } + + /** + * Reset the headers to only contain a Version string (if one is present). + */ + public void ResetHeaders() + { + string version = (string)headers[HeaderVersion]; + + headers.Clear(); + + if (version != null) + { + headers[HeaderVersion] = Version; + } + } + + /** + * Start a clear text signed message. + * @param hashAlgorithm + */ + public void BeginClearText( + HashAlgorithmTag hashAlgorithm) + { + string hash; + + switch (hashAlgorithm) + { + case HashAlgorithmTag.Sha1: + hash = "SHA1"; + break; + case HashAlgorithmTag.Sha256: + hash = "SHA256"; + break; + case HashAlgorithmTag.Sha384: + hash = "SHA384"; + break; + case HashAlgorithmTag.Sha512: + hash = "SHA512"; + break; + case HashAlgorithmTag.MD2: + hash = "MD2"; + break; + case HashAlgorithmTag.MD5: + hash = "MD5"; + break; + case HashAlgorithmTag.RipeMD160: + hash = "RIPEMD160"; + break; + default: + throw new IOException("unknown hash algorithm tag in beginClearText: " + hashAlgorithm); + } + + DoWrite("-----BEGIN PGP SIGNED MESSAGE-----" + nl); + DoWrite("Hash: " + hash + nl + nl); + + clearText = true; + newLine = true; + lastb = 0; + } + + public void EndClearText() + { + clearText = false; + } + + public override void WriteByte( + byte b) + { + if (clearText) + { + outStream.WriteByte(b); + + if (newLine) + { + if (!(b == '\n' && lastb == '\r')) + { + newLine = false; + } + if (b == '-') + { + outStream.WriteByte((byte)' '); + outStream.WriteByte((byte)'-'); // dash escape + } + } + if (b == '\r' || (b == '\n' && lastb != '\r')) + { + newLine = true; + } + lastb = b; + return; + } + + if (start) + { + bool newPacket = (b & 0x40) != 0; + + int tag; + if (newPacket) + { + tag = b & 0x3f; + } + else + { + tag = (b & 0x3f) >> 2; + } + + switch ((PacketTag)tag) + { + case PacketTag.PublicKey: + type = "PUBLIC KEY BLOCK"; + break; + case PacketTag.SecretKey: + type = "PRIVATE KEY BLOCK"; + break; + case PacketTag.Signature: + type = "SIGNATURE"; + break; + default: + type = "MESSAGE"; + break; + } + + DoWrite(headerStart + type + headerTail + nl); + if (headers.Contains(HeaderVersion)) + { + WriteHeaderEntry(HeaderVersion, (string)headers[HeaderVersion]); + } + + foreach (DictionaryEntry de in headers) + { + string k = (string)de.Key; + if (k != HeaderVersion) + { + string v = (string)de.Value; + WriteHeaderEntry(k, v); + } + } + + DoWrite(nl); + + start = false; + } + + if (bufPtr == 3) + { + Encode(outStream, buf, bufPtr); + bufPtr = 0; + if ((++chunkCount & 0xf) == 0) + { + DoWrite(nl); + } + } + + crc.Update(b); + buf[bufPtr++] = b & 0xff; + } + + /** + * Note: Close() does not close the underlying stream. So it is possible to write + * multiple objects using armoring to a single stream. + */ +#if PORTABLE + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (type == null) + return; + + DoClose(); + + type = null; + start = true; + } + base.Dispose(disposing); + } +#else + public override void Close() + { + if (type == null) + return; + + DoClose(); + + type = null; + start = true; + + base.Close(); + } +#endif + + private void DoClose() + { + if (bufPtr > 0) + { + Encode(outStream, buf, bufPtr); + } + + DoWrite(nl + '='); + + int crcV = crc.Value; + + buf[0] = ((crcV >> 16) & 0xff); + buf[1] = ((crcV >> 8) & 0xff); + buf[2] = (crcV & 0xff); + + Encode(outStream, buf, 3); + + DoWrite(nl); + DoWrite(footerStart); + DoWrite(type); + DoWrite(footerTail); + DoWrite(nl); + + outStream.Flush(); + } + + private void WriteHeaderEntry( + string name, + string v) + { + DoWrite(name + ": " + v + nl); + } + + private void DoWrite( + string s) + { + byte[] bs = Strings.ToAsciiByteArray(s); + outStream.Write(bs, 0, bs.Length); + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/BcpgInputStream.cs b/bc-sharp-crypto/src/bcpg/BcpgInputStream.cs new file mode 100644 index 0000000..f9627fd --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/BcpgInputStream.cs @@ -0,0 +1,363 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Bcpg +{ + /// Reader for PGP objects. + public class BcpgInputStream + : BaseInputStream + { + private Stream m_in; + private bool next = false; + private int nextB; + + internal static BcpgInputStream Wrap( + Stream inStr) + { + if (inStr is BcpgInputStream) + { + return (BcpgInputStream) inStr; + } + + return new BcpgInputStream(inStr); + } + + private BcpgInputStream( + Stream inputStream) + { + this.m_in = inputStream; + } + + public override int ReadByte() + { + if (next) + { + next = false; + return nextB; + } + + return m_in.ReadByte(); + } + + public override int Read( + byte[] buffer, + int offset, + int count) + { + // Strangely, when count == 0, we should still attempt to read a byte +// if (count == 0) +// return 0; + + if (!next) + return m_in.Read(buffer, offset, count); + + // We have next byte waiting, so return it + + if (nextB < 0) + return 0; // EndOfStream + + if (buffer == null) + throw new ArgumentNullException("buffer"); + + buffer[offset] = (byte) nextB; + next = false; + + return 1; + } + + public byte[] ReadAll() + { + return Streams.ReadAll(this); + } + + public void ReadFully( + byte[] buffer, + int off, + int len) + { + if (Streams.ReadFully(this, buffer, off, len) < len) + throw new EndOfStreamException(); + } + + public void ReadFully( + byte[] buffer) + { + ReadFully(buffer, 0, buffer.Length); + } + + /// Returns the next packet tag in the stream. + public PacketTag NextPacketTag() + { + if (!next) + { + try + { + nextB = m_in.ReadByte(); + } + catch (EndOfStreamException) + { + nextB = -1; + } + + next = true; + } + + if (nextB < 0) + return (PacketTag)nextB; + + int maskB = nextB & 0x3f; + if ((nextB & 0x40) == 0) // old + { + maskB >>= 2; + } + return (PacketTag)maskB; + } + + public Packet ReadPacket() + { + int hdr = this.ReadByte(); + + if (hdr < 0) + { + return null; + } + + if ((hdr & 0x80) == 0) + { + throw new IOException("invalid header encountered"); + } + + bool newPacket = (hdr & 0x40) != 0; + PacketTag tag = 0; + int bodyLen = 0; + bool partial = false; + + if (newPacket) + { + tag = (PacketTag)(hdr & 0x3f); + + int l = this.ReadByte(); + + if (l < 192) + { + bodyLen = l; + } + else if (l <= 223) + { + int b = m_in.ReadByte(); + bodyLen = ((l - 192) << 8) + (b) + 192; + } + else if (l == 255) + { + bodyLen = (m_in.ReadByte() << 24) | (m_in.ReadByte() << 16) + | (m_in.ReadByte() << 8) | m_in.ReadByte(); + } + else + { + partial = true; + bodyLen = 1 << (l & 0x1f); + } + } + else + { + int lengthType = hdr & 0x3; + + tag = (PacketTag)((hdr & 0x3f) >> 2); + + switch (lengthType) + { + case 0: + bodyLen = this.ReadByte(); + break; + case 1: + bodyLen = (this.ReadByte() << 8) | this.ReadByte(); + break; + case 2: + bodyLen = (this.ReadByte() << 24) | (this.ReadByte() << 16) + | (this.ReadByte() << 8) | this.ReadByte(); + break; + case 3: + partial = true; + break; + default: + throw new IOException("unknown length type encountered"); + } + } + + BcpgInputStream objStream; + if (bodyLen == 0 && partial) + { + objStream = this; + } + else + { + PartialInputStream pis = new PartialInputStream(this, partial, bodyLen); + objStream = new BcpgInputStream(pis); + } + + switch (tag) + { + case PacketTag.Reserved: + return new InputStreamPacket(objStream); + case PacketTag.PublicKeyEncryptedSession: + return new PublicKeyEncSessionPacket(objStream); + case PacketTag.Signature: + return new SignaturePacket(objStream); + case PacketTag.SymmetricKeyEncryptedSessionKey: + return new SymmetricKeyEncSessionPacket(objStream); + case PacketTag.OnePassSignature: + return new OnePassSignaturePacket(objStream); + case PacketTag.SecretKey: + return new SecretKeyPacket(objStream); + case PacketTag.PublicKey: + return new PublicKeyPacket(objStream); + case PacketTag.SecretSubkey: + return new SecretSubkeyPacket(objStream); + case PacketTag.CompressedData: + return new CompressedDataPacket(objStream); + case PacketTag.SymmetricKeyEncrypted: + return new SymmetricEncDataPacket(objStream); + case PacketTag.Marker: + return new MarkerPacket(objStream); + case PacketTag.LiteralData: + return new LiteralDataPacket(objStream); + case PacketTag.Trust: + return new TrustPacket(objStream); + case PacketTag.UserId: + return new UserIdPacket(objStream); + case PacketTag.UserAttribute: + return new UserAttributePacket(objStream); + case PacketTag.PublicSubkey: + return new PublicSubkeyPacket(objStream); + case PacketTag.SymmetricEncryptedIntegrityProtected: + return new SymmetricEncIntegrityPacket(objStream); + case PacketTag.ModificationDetectionCode: + return new ModDetectionCodePacket(objStream); + case PacketTag.Experimental1: + case PacketTag.Experimental2: + case PacketTag.Experimental3: + case PacketTag.Experimental4: + return new ExperimentalPacket(tag, objStream); + default: + throw new IOException("unknown packet type encountered: " + tag); + } + } + +#if PORTABLE + protected override void Dispose(bool disposing) + { + if (disposing) + { + Platform.Dispose(m_in); + } + base.Dispose(disposing); + } +#else + public override void Close() + { + Platform.Dispose(m_in); + base.Close(); + } +#endif + + /// + /// 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 class PartialInputStream + : BaseInputStream + { + private BcpgInputStream m_in; + private bool partial; + private int dataLength; + + internal PartialInputStream( + BcpgInputStream bcpgIn, + bool partial, + int dataLength) + { + this.m_in = bcpgIn; + this.partial = partial; + this.dataLength = dataLength; + } + + public override int ReadByte() + { + do + { + if (dataLength != 0) + { + int ch = m_in.ReadByte(); + if (ch < 0) + { + throw new EndOfStreamException("Premature end of stream in PartialInputStream"); + } + dataLength--; + return ch; + } + } + while (partial && ReadPartialDataLength() >= 0); + + return -1; + } + + public override int Read(byte[] buffer, int offset, int count) + { + do + { + if (dataLength != 0) + { + int readLen = (dataLength > count || dataLength < 0) ? count : dataLength; + int len = m_in.Read(buffer, offset, readLen); + if (len < 1) + { + throw new EndOfStreamException("Premature end of stream in PartialInputStream"); + } + dataLength -= len; + return len; + } + } + while (partial && ReadPartialDataLength() >= 0); + + return 0; + } + + private int ReadPartialDataLength() + { + int l = m_in.ReadByte(); + + if (l < 0) + { + return -1; + } + + partial = false; + + if (l < 192) + { + dataLength = l; + } + else if (l <= 223) + { + dataLength = ((l - 192) << 8) + (m_in.ReadByte()) + 192; + } + else if (l == 255) + { + dataLength = (m_in.ReadByte() << 24) | (m_in.ReadByte() << 16) + | (m_in.ReadByte() << 8) | m_in.ReadByte(); + } + else + { + partial = true; + dataLength = 1 << (l & 0x1f); + } + + return 0; + } + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/BcpgObject.cs b/bc-sharp-crypto/src/bcpg/BcpgObject.cs new file mode 100644 index 0000000..4807ad4 --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/BcpgObject.cs @@ -0,0 +1,22 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Bcpg +{ + /// Base class for a PGP object. + public abstract class BcpgObject + { + public virtual byte[] GetEncoded() + { + MemoryStream bOut = new MemoryStream(); + BcpgOutputStream pOut = new BcpgOutputStream(bOut); + + pOut.WriteObject(this); + + return bOut.ToArray(); + } + + public abstract void Encode(BcpgOutputStream bcpgOut); + } +} + diff --git a/bc-sharp-crypto/src/bcpg/BcpgOutputStream.cs b/bc-sharp-crypto/src/bcpg/BcpgOutputStream.cs new file mode 100644 index 0000000..7ab661e --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/BcpgOutputStream.cs @@ -0,0 +1,404 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Bcpg +{ + /// Basic output stream. + public class BcpgOutputStream + : BaseOutputStream + { + internal static BcpgOutputStream Wrap( + Stream outStr) + { + if (outStr is BcpgOutputStream) + { + return (BcpgOutputStream) outStr; + } + + return new BcpgOutputStream(outStr); + } + + private Stream outStr; + private byte[] partialBuffer; + private int partialBufferLength; + private int partialPower; + private int partialOffset; + private const int BufferSizePower = 16; // 2^16 size buffer on long files + + /// Create a stream representing a general packet. + /// Output stream to write to. + public BcpgOutputStream( + Stream outStr) + { + if (outStr == null) + throw new ArgumentNullException("outStr"); + + this.outStr = outStr; + } + + /// Create a stream representing an old style partial object. + /// Output stream to write to. + /// The packet tag for the object. + public BcpgOutputStream( + Stream outStr, + PacketTag tag) + { + if (outStr == null) + throw new ArgumentNullException("outStr"); + + this.outStr = outStr; + this.WriteHeader(tag, true, true, 0); + } + + /// Create a stream representing a general packet. + /// Output stream to write to. + /// Packet tag. + /// Size of chunks making up the packet. + /// If true, the header is written out in old format. + public BcpgOutputStream( + Stream outStr, + PacketTag tag, + long length, + bool oldFormat) + { + if (outStr == null) + throw new ArgumentNullException("outStr"); + + this.outStr = outStr; + + if (length > 0xFFFFFFFFL) + { + this.WriteHeader(tag, false, true, 0); + this.partialBufferLength = 1 << BufferSizePower; + this.partialBuffer = new byte[partialBufferLength]; + this.partialPower = BufferSizePower; + this.partialOffset = 0; + } + else + { + this.WriteHeader(tag, oldFormat, false, length); + } + } + + /// Create a new style partial input stream buffered into chunks. + /// Output stream to write to. + /// Packet tag. + /// Size of chunks making up the packet. + public BcpgOutputStream( + Stream outStr, + PacketTag tag, + long length) + { + if (outStr == null) + throw new ArgumentNullException("outStr"); + + this.outStr = outStr; + this.WriteHeader(tag, false, false, length); + } + + /// Create a new style partial input stream buffered into chunks. + /// Output stream to write to. + /// Packet tag. + /// Buffer to use for collecting chunks. + public BcpgOutputStream( + Stream outStr, + PacketTag tag, + byte[] buffer) + { + if (outStr == null) + throw new ArgumentNullException("outStr"); + + this.outStr = outStr; + this.WriteHeader(tag, false, true, 0); + + this.partialBuffer = buffer; + + uint length = (uint) 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) + { + if (bodyLen < 192) + { + outStr.WriteByte((byte)bodyLen); + } + else if (bodyLen <= 8383) + { + bodyLen -= 192; + + outStr.WriteByte((byte)(((bodyLen >> 8) & 0xff) + 192)); + outStr.WriteByte((byte)bodyLen); + } + else + { + outStr.WriteByte(0xff); + outStr.WriteByte((byte)(bodyLen >> 24)); + outStr.WriteByte((byte)(bodyLen >> 16)); + outStr.WriteByte((byte)(bodyLen >> 8)); + outStr.WriteByte((byte)bodyLen); + } + } + + private void WriteHeader( + PacketTag tag, + bool oldPackets, + bool partial, + long bodyLen) + { + int hdr = 0x80; + + if (partialBuffer != null) + { + PartialFlush(true); + partialBuffer = null; + } + + if (oldPackets) + { + hdr |= ((int) tag) << 2; + + if (partial) + { + this.WriteByte((byte)(hdr | 0x03)); + } + else + { + if (bodyLen <= 0xff) + { + this.WriteByte((byte) hdr); + this.WriteByte((byte)bodyLen); + } + else if (bodyLen <= 0xffff) + { + this.WriteByte((byte)(hdr | 0x01)); + this.WriteByte((byte)(bodyLen >> 8)); + this.WriteByte((byte)(bodyLen)); + } + else + { + this.WriteByte((byte)(hdr | 0x02)); + this.WriteByte((byte)(bodyLen >> 24)); + this.WriteByte((byte)(bodyLen >> 16)); + this.WriteByte((byte)(bodyLen >> 8)); + this.WriteByte((byte)bodyLen); + } + } + } + else + { + hdr |= 0x40 | (int) tag; + this.WriteByte((byte) hdr); + + if (partial) + { + partialOffset = 0; + } + else + { + this.WriteNewPacketLength(bodyLen); + } + } + } + + private void PartialFlush( + bool isLast) + { + if (isLast) + { + WriteNewPacketLength(partialOffset); + outStr.Write(partialBuffer, 0, partialOffset); + } + else + { + outStr.WriteByte((byte)(0xE0 | partialPower)); + outStr.Write(partialBuffer, 0, partialBufferLength); + } + + partialOffset = 0; + } + + private void WritePartial( + byte b) + { + if (partialOffset == partialBufferLength) + { + PartialFlush(false); + } + + partialBuffer[partialOffset++] = b; + } + + private void WritePartial( + byte[] buffer, + int off, + int len) + { + if (partialOffset == partialBufferLength) + { + PartialFlush(false); + } + + if (len <= (partialBufferLength - partialOffset)) + { + Array.Copy(buffer, off, partialBuffer, partialOffset, len); + partialOffset += len; + } + else + { + int diff = partialBufferLength - partialOffset; + Array.Copy(buffer, off, partialBuffer, partialOffset, diff); + off += diff; + len -= diff; + PartialFlush(false); + while (len > partialBufferLength) + { + Array.Copy(buffer, off, partialBuffer, 0, partialBufferLength); + off += partialBufferLength; + len -= partialBufferLength; + PartialFlush(false); + } + Array.Copy(buffer, off, partialBuffer, 0, len); + partialOffset += len; + } + } + public override void WriteByte( + byte value) + { + if (partialBuffer != null) + { + WritePartial(value); + } + else + { + outStr.WriteByte(value); + } + } + public override void Write( + byte[] buffer, + int offset, + int count) + { + if (partialBuffer != null) + { + WritePartial(buffer, offset, count); + } + else + { + outStr.Write(buffer, offset, count); + } + } + + // Additional helper methods to write primitive types + internal virtual void WriteShort( + short n) + { + this.Write( + (byte)(n >> 8), + (byte)n); + } + internal virtual void WriteInt( + int n) + { + this.Write( + (byte)(n >> 24), + (byte)(n >> 16), + (byte)(n >> 8), + (byte)n); + } + internal virtual void WriteLong( + long n) + { + this.Write( + (byte)(n >> 56), + (byte)(n >> 48), + (byte)(n >> 40), + (byte)(n >> 32), + (byte)(n >> 24), + (byte)(n >> 16), + (byte)(n >> 8), + (byte)n); + } + + public void WritePacket( + ContainedPacket p) + { + p.Encode(this); + } + + internal void WritePacket( + PacketTag tag, + byte[] body, + bool oldFormat) + { + this.WriteHeader(tag, oldFormat, false, body.Length); + this.Write(body); + } + + public void WriteObject( + BcpgObject bcpgObject) + { + bcpgObject.Encode(this); + } + + public void WriteObjects( + params BcpgObject[] v) + { + foreach (BcpgObject o in v) + { + o.Encode(this); + } + } + + /// Flush the underlying stream. + public override void Flush() + { + outStr.Flush(); + } + + /// Finish writing out the current packet without closing the underlying stream. + public void Finish() + { + if (partialBuffer != null) + { + PartialFlush(true); + partialBuffer = null; + } + } + +#if PORTABLE + protected override void Dispose(bool disposing) + { + if (disposing) + { + this.Finish(); + outStr.Flush(); + Platform.Dispose(outStr); + } + base.Dispose(disposing); + } +#else + public override void Close() + { + this.Finish(); + outStr.Flush(); + Platform.Dispose(outStr); + base.Close(); + } +#endif + } +} diff --git a/bc-sharp-crypto/src/bcpg/CompressedDataPacket.cs b/bc-sharp-crypto/src/bcpg/CompressedDataPacket.cs new file mode 100644 index 0000000..2432825 --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/CompressedDataPacket.cs @@ -0,0 +1,24 @@ +using System.IO; + +namespace Org.BouncyCastle.Bcpg +{ + /// Generic compressed data object. + public class CompressedDataPacket + : InputStreamPacket + { + private readonly CompressionAlgorithmTag algorithm; + + internal CompressedDataPacket( + BcpgInputStream bcpgIn) + : base(bcpgIn) + { + this.algorithm = (CompressionAlgorithmTag) bcpgIn.ReadByte(); + } + + /// The algorithm tag value. + public CompressionAlgorithmTag Algorithm + { + get { return algorithm; } + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/CompressionAlgorithmTags.cs b/bc-sharp-crypto/src/bcpg/CompressionAlgorithmTags.cs new file mode 100644 index 0000000..0e45229 --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/CompressionAlgorithmTags.cs @@ -0,0 +1,11 @@ +namespace Org.BouncyCastle.Bcpg +{ + /// Basic tags for compression algorithms. + public enum CompressionAlgorithmTag + { + Uncompressed = 0, // Uncompressed + Zip = 1, // ZIP (RFC 1951) + ZLib = 2, // ZLIB (RFC 1950) + BZip2 = 3, // BZ2 + } +} diff --git a/bc-sharp-crypto/src/bcpg/ContainedPacket.cs b/bc-sharp-crypto/src/bcpg/ContainedPacket.cs new file mode 100644 index 0000000..e8f387c --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/ContainedPacket.cs @@ -0,0 +1,22 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Bcpg +{ + /// Basic type for a PGP packet. + public abstract class ContainedPacket + : Packet + { + public byte[] GetEncoded() + { + MemoryStream bOut = new MemoryStream(); + BcpgOutputStream pOut = new BcpgOutputStream(bOut); + + pOut.WritePacket(this); + + return bOut.ToArray(); + } + + public abstract void Encode(BcpgOutputStream bcpgOut); + } +} diff --git a/bc-sharp-crypto/src/bcpg/Crc24.cs b/bc-sharp-crypto/src/bcpg/Crc24.cs new file mode 100644 index 0000000..97846f4 --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/Crc24.cs @@ -0,0 +1,46 @@ +using System; + +namespace Org.BouncyCastle.Bcpg +{ + public class Crc24 + { + private const int Crc24Init = 0x0b704ce; + private const int Crc24Poly = 0x1864cfb; + + private int crc = Crc24Init; + + public Crc24() + { + } + + public void Update( + int b) + { + crc ^= b << 16; + for (int i = 0; i < 8; i++) + { + crc <<= 1; + if ((crc & 0x1000000) != 0) + { + crc ^= Crc24Poly; + } + } + } + + [Obsolete("Use 'Value' property instead")] + public int GetValue() + { + return crc; + } + + public int Value + { + get { return crc; } + } + + public void Reset() + { + crc = Crc24Init; + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/DsaPublicBcpgKey.cs b/bc-sharp-crypto/src/bcpg/DsaPublicBcpgKey.cs new file mode 100644 index 0000000..11294cc --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/DsaPublicBcpgKey.cs @@ -0,0 +1,80 @@ +using System; + +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Bcpg +{ + /// Base class for a DSA public key. + public class DsaPublicBcpgKey + : BcpgObject, IBcpgKey + { + private readonly MPInteger p, q, g, y; + + /// The stream to read the packet from. + public DsaPublicBcpgKey( + BcpgInputStream bcpgIn) + { + this.p = new MPInteger(bcpgIn); + this.q = new MPInteger(bcpgIn); + this.g = new MPInteger(bcpgIn); + this.y = new MPInteger(bcpgIn); + } + + 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); + } + + /// The format, as a string, always "PGP". + public string Format + { + get { return "PGP"; } + } + + /// Return the standard PGP encoding of the key. + public override byte[] GetEncoded() + { + try + { + return base.GetEncoded(); + } + catch (Exception) + { + return null; + } + } + + public override void Encode( + BcpgOutputStream bcpgOut) + { + bcpgOut.WriteObjects(p, q, g, y); + } + + public BigInteger G + { + get { return g.Value; } + } + + public BigInteger P + { + get { return p.Value; } + } + + public BigInteger Q + { + get { return q.Value; } + } + + public BigInteger Y + { + get { return y.Value; } + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/DsaSecretBcpgKey.cs b/bc-sharp-crypto/src/bcpg/DsaSecretBcpgKey.cs new file mode 100644 index 0000000..41835d4 --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/DsaSecretBcpgKey.cs @@ -0,0 +1,61 @@ +using System; + +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Bcpg +{ + /// Base class for a DSA secret key. + public class DsaSecretBcpgKey + : BcpgObject, IBcpgKey + { + internal MPInteger x; + + /** + * @param in + */ + public DsaSecretBcpgKey( + BcpgInputStream bcpgIn) + { + this.x = new MPInteger(bcpgIn); + } + + public DsaSecretBcpgKey( + BigInteger x) + { + this.x = new MPInteger(x); + } + + /// The format, as a string, always "PGP". + public string Format + { + get { return "PGP"; } + } + + /// Return the standard PGP encoding of the key. + public override byte[] GetEncoded() + { + try + { + return base.GetEncoded(); + } + catch (Exception) + { + return null; + } + } + + public override void Encode( + BcpgOutputStream bcpgOut) + { + bcpgOut.WriteObject(x); + } + + /** + * @return x + */ + public BigInteger X + { + get { return x.Value; } + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/ECDHPublicBCPGKey.cs b/bc-sharp-crypto/src/bcpg/ECDHPublicBCPGKey.cs new file mode 100644 index 0000000..dc225e3 --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/ECDHPublicBCPGKey.cs @@ -0,0 +1,102 @@ +using System; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Math.EC; + +namespace Org.BouncyCastle.Bcpg +{ + /// Base class for an ECDH Public Key. + public class ECDHPublicBcpgKey + : ECPublicBcpgKey + { + private byte reserved; + private HashAlgorithmTag hashFunctionId; + private SymmetricKeyAlgorithmTag symAlgorithmId; + + /// The stream to read the packet from. + public ECDHPublicBcpgKey( + BcpgInputStream bcpgIn) + : base(bcpgIn) + { + int length = bcpgIn.ReadByte(); + byte[] kdfParameters = new byte[length]; + if (kdfParameters.Length != 3) + throw new InvalidOperationException("kdf parameters size of 3 expected."); + + bcpgIn.ReadFully(kdfParameters); + + reserved = kdfParameters[0]; + hashFunctionId = (HashAlgorithmTag)kdfParameters[1]; + symAlgorithmId = (SymmetricKeyAlgorithmTag)kdfParameters[2]; + + VerifyHashAlgorithm(); + VerifySymmetricKeyAlgorithm(); + } + + public ECDHPublicBcpgKey( + DerObjectIdentifier oid, + ECPoint point, + HashAlgorithmTag hashAlgorithm, + SymmetricKeyAlgorithmTag symmetricKeyAlgorithm) + : base(oid, point) + { + reserved = 1; + hashFunctionId = hashAlgorithm; + symAlgorithmId = symmetricKeyAlgorithm; + + VerifyHashAlgorithm(); + VerifySymmetricKeyAlgorithm(); + } + + public virtual byte Reserved + { + get { return reserved; } + } + + public virtual HashAlgorithmTag HashAlgorithm + { + get { return hashFunctionId; } + } + + public virtual SymmetricKeyAlgorithmTag SymmetricKeyAlgorithm + { + get { return symAlgorithmId; } + } + + public override void Encode( + BcpgOutputStream bcpgOut) + { + base.Encode(bcpgOut); + bcpgOut.WriteByte(0x3); + bcpgOut.WriteByte(reserved); + bcpgOut.WriteByte((byte)hashFunctionId); + bcpgOut.WriteByte((byte)symAlgorithmId); + } + + private void VerifyHashAlgorithm() + { + switch ((HashAlgorithmTag)hashFunctionId) + { + case HashAlgorithmTag.Sha256: + case HashAlgorithmTag.Sha384: + case HashAlgorithmTag.Sha512: + break; + default: + throw new InvalidOperationException("Hash algorithm must be SHA-256 or stronger."); + } + } + + private void VerifySymmetricKeyAlgorithm() + { + switch ((SymmetricKeyAlgorithmTag)symAlgorithmId) + { + case SymmetricKeyAlgorithmTag.Aes128: + case SymmetricKeyAlgorithmTag.Aes192: + case SymmetricKeyAlgorithmTag.Aes256: + break; + default: + throw new InvalidOperationException("Symmetric key algorithm must be AES-128 or stronger."); + } + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/ECDsaPublicBCPGKey.cs b/bc-sharp-crypto/src/bcpg/ECDsaPublicBCPGKey.cs new file mode 100644 index 0000000..5f0c8ac --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/ECDsaPublicBCPGKey.cs @@ -0,0 +1,34 @@ +using System; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Math.EC; + +namespace Org.BouncyCastle.Bcpg +{ + /// Base class for an ECDSA Public Key. + public class ECDsaPublicBcpgKey + : ECPublicBcpgKey + { + /// The stream to read the packet from. + protected internal ECDsaPublicBcpgKey( + BcpgInputStream bcpgIn) + : base(bcpgIn) + { + } + + public ECDsaPublicBcpgKey( + DerObjectIdentifier oid, + ECPoint point) + : base(oid, point) + { + } + + public ECDsaPublicBcpgKey( + DerObjectIdentifier oid, + BigInteger encodedPoint) + : base(oid, encodedPoint) + { + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/ECPublicBCPGKey.cs b/bc-sharp-crypto/src/bcpg/ECPublicBCPGKey.cs new file mode 100644 index 0000000..f328f9d --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/ECPublicBCPGKey.cs @@ -0,0 +1,97 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Math.EC; + +namespace Org.BouncyCastle.Bcpg +{ + /// Base class for an EC Public Key. + public abstract class ECPublicBcpgKey + : BcpgObject, IBcpgKey + { + internal DerObjectIdentifier oid; + internal BigInteger point; + + /// The stream to read the packet from. + protected ECPublicBcpgKey( + BcpgInputStream bcpgIn) + { + this.oid = DerObjectIdentifier.GetInstance(Asn1Object.FromByteArray(ReadBytesOfEncodedLength(bcpgIn))); + this.point = new MPInteger(bcpgIn).Value; + } + + protected ECPublicBcpgKey( + DerObjectIdentifier oid, + ECPoint point) + { + this.point = new BigInteger(1, point.GetEncoded()); + this.oid = oid; + } + + protected ECPublicBcpgKey( + DerObjectIdentifier oid, + BigInteger encodedPoint) + { + this.point = encodedPoint; + this.oid = oid; + } + + /// The format, as a string, always "PGP". + public string Format + { + get { return "PGP"; } + } + + /// Return the standard PGP encoding of the key. + public override byte[] GetEncoded() + { + try + { + return base.GetEncoded(); + } + catch (IOException) + { + return null; + } + } + + public override void Encode( + BcpgOutputStream bcpgOut) + { + byte[] oid = this.oid.GetEncoded(); + bcpgOut.Write(oid, 1, oid.Length - 1); + + MPInteger point = new MPInteger(this.point); + bcpgOut.WriteObject(point); + } + + public virtual BigInteger EncodedPoint + { + get { return point; } + } + + public virtual DerObjectIdentifier CurveOid + { + get { return oid; } + } + + protected static byte[] ReadBytesOfEncodedLength( + BcpgInputStream bcpgIn) + { + int length = bcpgIn.ReadByte(); + if (length == 0 || length == 0xFF) + { + throw new IOException("future extensions not yet implemented."); + } + + byte[] buffer = new byte[length + 2]; + bcpgIn.ReadFully(buffer, 2, buffer.Length - 2); + buffer[0] = (byte)0x06; + buffer[1] = (byte)length; + + return buffer; + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/ECSecretBCPGKey.cs b/bc-sharp-crypto/src/bcpg/ECSecretBCPGKey.cs new file mode 100644 index 0000000..22e0a34 --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/ECSecretBCPGKey.cs @@ -0,0 +1,56 @@ +using System; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Bcpg +{ + /// Base class for an EC Secret Key. + public class ECSecretBcpgKey + : BcpgObject, IBcpgKey + { + internal MPInteger x; + + public ECSecretBcpgKey( + BcpgInputStream bcpgIn) + { + this.x = new MPInteger(bcpgIn); + } + + public ECSecretBcpgKey( + BigInteger x) + { + this.x = new MPInteger(x); + } + + /// The format, as a string, always "PGP". + public string Format + { + get { return "PGP"; } + } + + /// Return the standard PGP encoding of the key. + public override byte[] GetEncoded() + { + try + { + return base.GetEncoded(); + } + catch (Exception) + { + return null; + } + } + + public override void Encode( + BcpgOutputStream bcpgOut) + { + bcpgOut.WriteObject(x); + } + + public virtual BigInteger X + { + get { return x.Value; } + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/ElGamalPublicBcpgKey.cs b/bc-sharp-crypto/src/bcpg/ElGamalPublicBcpgKey.cs new file mode 100644 index 0000000..808e427 --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/ElGamalPublicBcpgKey.cs @@ -0,0 +1,71 @@ +using System; + +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Bcpg +{ + /// Base class for an ElGamal public key. + public class ElGamalPublicBcpgKey + : BcpgObject, IBcpgKey + { + internal MPInteger p, g, y; + + public ElGamalPublicBcpgKey( + BcpgInputStream bcpgIn) + { + this.p = new MPInteger(bcpgIn); + this.g = new MPInteger(bcpgIn); + this.y = new MPInteger(bcpgIn); + } + + public ElGamalPublicBcpgKey( + BigInteger p, + BigInteger g, + BigInteger y) + { + this.p = new MPInteger(p); + this.g = new MPInteger(g); + this.y = new MPInteger(y); + } + + /// The format, as a string, always "PGP". + public string Format + { + get { return "PGP"; } + } + + /// Return the standard PGP encoding of the key. + public override byte[] GetEncoded() + { + try + { + return base.GetEncoded(); + } + catch (Exception) + { + return null; + } + } + + public BigInteger P + { + get { return p.Value; } + } + + public BigInteger G + { + get { return g.Value; } + } + + public BigInteger Y + { + get { return y.Value; } + } + + public override void Encode( + BcpgOutputStream bcpgOut) + { + bcpgOut.WriteObjects(p, g, y); + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/ElGamalSecretBcpgKey.cs b/bc-sharp-crypto/src/bcpg/ElGamalSecretBcpgKey.cs new file mode 100644 index 0000000..2d95b29 --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/ElGamalSecretBcpgKey.cs @@ -0,0 +1,61 @@ +using System; + +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Bcpg +{ + /// Base class for an ElGamal secret key. + public class ElGamalSecretBcpgKey + : BcpgObject, IBcpgKey + { + internal MPInteger x; + + /** + * @param in + */ + public ElGamalSecretBcpgKey( + BcpgInputStream bcpgIn) + { + this.x = new MPInteger(bcpgIn); + } + + /** + * @param x + */ + public ElGamalSecretBcpgKey( + BigInteger x) + { + this.x = new MPInteger(x); + } + + /// The format, as a string, always "PGP". + public string Format + { + get { return "PGP"; } + } + + public BigInteger X + { + get { return x.Value; } + } + + /// Return the standard PGP encoding of the key. + public override byte[] GetEncoded() + { + try + { + return base.GetEncoded(); + } + catch (Exception) + { + return null; + } + } + + public override void Encode( + BcpgOutputStream bcpgOut) + { + bcpgOut.WriteObject(x); + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/ExperimentalPacket.cs b/bc-sharp-crypto/src/bcpg/ExperimentalPacket.cs new file mode 100644 index 0000000..36a254b --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/ExperimentalPacket.cs @@ -0,0 +1,38 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Bcpg +{ + /// Basic packet for an experimental packet. + public class ExperimentalPacket + : ContainedPacket //, PublicKeyAlgorithmTag + { + private readonly PacketTag tag; + private readonly byte[] contents; + + internal ExperimentalPacket( + PacketTag tag, + BcpgInputStream bcpgIn) + { + this.tag = tag; + + this.contents = bcpgIn.ReadAll(); + } + + public PacketTag Tag + { + get { return tag; } + } + + public byte[] GetContents() + { + return (byte[]) contents.Clone(); + } + + public override void Encode( + BcpgOutputStream bcpgOut) + { + bcpgOut.WritePacket(tag, contents, true); + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/HashAlgorithmTags.cs b/bc-sharp-crypto/src/bcpg/HashAlgorithmTags.cs new file mode 100644 index 0000000..96c0091 --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/HashAlgorithmTags.cs @@ -0,0 +1,19 @@ +namespace Org.BouncyCastle.Bcpg +{ + /// Basic tags for hash algorithms. + public enum HashAlgorithmTag + { + MD5 = 1, // MD5 + Sha1 = 2, // SHA-1 + RipeMD160 = 3, // RIPE-MD/160 + DoubleSha = 4, // Reserved for double-width SHA (experimental) + MD2 = 5, // MD2 + Tiger192 = 6, // Reserved for TIGER/192 + Haval5pass160 = 7, // Reserved for HAVAL (5 pass, 160-bit) + + Sha256 = 8, // SHA-256 + Sha384 = 9, // SHA-384 + Sha512 = 10, // SHA-512 + Sha224 = 11, // SHA-224 + } +} diff --git a/bc-sharp-crypto/src/bcpg/IBcpgKey.cs b/bc-sharp-crypto/src/bcpg/IBcpgKey.cs new file mode 100644 index 0000000..2754617 --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/IBcpgKey.cs @@ -0,0 +1,16 @@ +using System; + +namespace Org.BouncyCastle.Bcpg +{ + /// Base interface for a PGP key. + public interface IBcpgKey + { + /// + /// 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. + /// + /// "RAW" or "PGP". + string Format { get; } + } +} diff --git a/bc-sharp-crypto/src/bcpg/InputStreamPacket.cs b/bc-sharp-crypto/src/bcpg/InputStreamPacket.cs new file mode 100644 index 0000000..c45efab --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/InputStreamPacket.cs @@ -0,0 +1,20 @@ +namespace Org.BouncyCastle.Bcpg +{ + public class InputStreamPacket + : Packet + { + private readonly BcpgInputStream bcpgIn; + + public InputStreamPacket( + BcpgInputStream bcpgIn) + { + this.bcpgIn = bcpgIn; + } + + /// Note: you can only read from this once... + public BcpgInputStream GetInputStream() + { + return bcpgIn; + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/LiteralDataPacket.cs b/bc-sharp-crypto/src/bcpg/LiteralDataPacket.cs new file mode 100644 index 0000000..63a2c6d --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/LiteralDataPacket.cs @@ -0,0 +1,57 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Bcpg +{ + /// Generic literal data packet. + public class LiteralDataPacket + : InputStreamPacket + { + private int format; + private byte[] fileName; + private long modDate; + + internal LiteralDataPacket( + BcpgInputStream bcpgIn) + : base(bcpgIn) + { + format = bcpgIn.ReadByte(); + int len = bcpgIn.ReadByte(); + + fileName = new byte[len]; + for (int i = 0; i != len; ++i) + { + fileName[i] = (byte)bcpgIn.ReadByte(); + } + + modDate = (((uint)bcpgIn.ReadByte() << 24) + | ((uint)bcpgIn.ReadByte() << 16) + | ((uint)bcpgIn.ReadByte() << 8) + | (uint)bcpgIn.ReadByte()) * 1000L; + } + + /// The format tag value. + public int Format + { + get { return format; } + } + + /// The modification time of the file in milli-seconds (since Jan 1, 1970 UTC) + public long ModificationTime + { + get { return modDate; } + } + + public string FileName + { + get { return Strings.FromUtf8ByteArray(fileName); } + } + + public byte[] GetRawFileName() + { + return Arrays.Clone(fileName); + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/MPInteger.cs b/bc-sharp-crypto/src/bcpg/MPInteger.cs new file mode 100644 index 0000000..4414072 --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/MPInteger.cs @@ -0,0 +1,59 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Bcpg +{ + /// A multiple precision integer + public class MPInteger + : BcpgObject + { + private readonly BigInteger val; + + public MPInteger( + BcpgInputStream bcpgIn) + { + if (bcpgIn == null) + throw new ArgumentNullException("bcpgIn"); + + int length = (bcpgIn.ReadByte() << 8) | bcpgIn.ReadByte(); + byte[] bytes = new byte[(length + 7) / 8]; + + bcpgIn.ReadFully(bytes); + + this.val = new BigInteger(1, bytes); + } + + public MPInteger( + BigInteger val) + { + if (val == null) + throw new ArgumentNullException("val"); + if (val.SignValue < 0) + throw new ArgumentException("Values must be positive", "val"); + + this.val = val; + } + + public BigInteger Value + { + get { return val; } + } + + public override void Encode( + BcpgOutputStream bcpgOut) + { + bcpgOut.WriteShort((short) val.BitLength); + bcpgOut.Write(val.ToByteArrayUnsigned()); + } + + internal static void Encode( + BcpgOutputStream bcpgOut, + BigInteger val) + { + bcpgOut.WriteShort((short) val.BitLength); + bcpgOut.Write(val.ToByteArrayUnsigned()); + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/MarkerPacket.cs b/bc-sharp-crypto/src/bcpg/MarkerPacket.cs new file mode 100644 index 0000000..4dc4b5a --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/MarkerPacket.cs @@ -0,0 +1,24 @@ +using System.IO; + +namespace Org.BouncyCastle.Bcpg +{ + /// Basic type for a marker packet. + public class MarkerPacket + : ContainedPacket + { + // "PGP" + byte[] marker = { (byte)0x50, (byte)0x47, (byte)0x50 }; + + public MarkerPacket( + BcpgInputStream bcpgIn) + { + bcpgIn.ReadFully(marker); + } + + public override void Encode( + BcpgOutputStream bcpgOut) + { + bcpgOut.WritePacket(PacketTag.Marker, marker, true); + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/ModDetectionCodePacket.cs b/bc-sharp-crypto/src/bcpg/ModDetectionCodePacket.cs new file mode 100644 index 0000000..6bb2364 --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/ModDetectionCodePacket.cs @@ -0,0 +1,42 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Bcpg +{ + /// Basic packet for a modification detection code packet. + public class ModDetectionCodePacket + : ContainedPacket + { + private readonly byte[] digest; + + internal ModDetectionCodePacket( + BcpgInputStream bcpgIn) + { + if (bcpgIn == null) + throw new ArgumentNullException("bcpgIn"); + + this.digest = new byte[20]; + bcpgIn.ReadFully(this.digest); + } + + public ModDetectionCodePacket( + byte[] digest) + { + if (digest == null) + throw new ArgumentNullException("digest"); + + this.digest = (byte[]) digest.Clone(); + } + + public byte[] GetDigest() + { + return (byte[]) digest.Clone(); + } + + public override void Encode( + BcpgOutputStream bcpgOut) + { + bcpgOut.WritePacket(PacketTag.ModificationDetectionCode, digest, false); + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/OnePassSignaturePacket.cs b/bc-sharp-crypto/src/bcpg/OnePassSignaturePacket.cs new file mode 100644 index 0000000..b67df0a --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/OnePassSignaturePacket.cs @@ -0,0 +1,93 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Bcpg +{ + /// Generic signature object + public class OnePassSignaturePacket + : ContainedPacket + { + private int version; + private int sigType; + private HashAlgorithmTag hashAlgorithm; + private PublicKeyAlgorithmTag keyAlgorithm; + private long keyId; + private int nested; + + internal OnePassSignaturePacket( + BcpgInputStream bcpgIn) + { + version = bcpgIn.ReadByte(); + sigType = bcpgIn.ReadByte(); + hashAlgorithm = (HashAlgorithmTag) bcpgIn.ReadByte(); + keyAlgorithm = (PublicKeyAlgorithmTag) bcpgIn.ReadByte(); + + keyId |= (long)bcpgIn.ReadByte() << 56; + keyId |= (long)bcpgIn.ReadByte() << 48; + keyId |= (long)bcpgIn.ReadByte() << 40; + keyId |= (long)bcpgIn.ReadByte() << 32; + keyId |= (long)bcpgIn.ReadByte() << 24; + keyId |= (long)bcpgIn.ReadByte() << 16; + keyId |= (long)bcpgIn.ReadByte() << 8; + keyId |= (uint)bcpgIn.ReadByte(); + + nested = bcpgIn.ReadByte(); + } + + public OnePassSignaturePacket( + int sigType, + HashAlgorithmTag hashAlgorithm, + PublicKeyAlgorithmTag keyAlgorithm, + long keyId, + bool isNested) + { + this.version = 3; + this.sigType = sigType; + this.hashAlgorithm = hashAlgorithm; + this.keyAlgorithm = keyAlgorithm; + this.keyId = keyId; + this.nested = (isNested) ? 0 : 1; + } + + public int SignatureType + { + get { return sigType; } + } + + /// The encryption algorithm tag. + public PublicKeyAlgorithmTag KeyAlgorithm + { + get { return keyAlgorithm; } + } + + /// The hash algorithm tag. + public HashAlgorithmTag HashAlgorithm + { + get { return hashAlgorithm; } + } + + public long KeyId + { + get { return keyId; } + } + + public override void Encode( + BcpgOutputStream bcpgOut) + { + MemoryStream bOut = new MemoryStream(); + BcpgOutputStream pOut = new BcpgOutputStream(bOut); + + pOut.Write( + (byte) version, + (byte) sigType, + (byte) hashAlgorithm, + (byte) keyAlgorithm); + + pOut.WriteLong(keyId); + + pOut.WriteByte((byte) nested); + + bcpgOut.WritePacket(PacketTag.OnePassSignature, bOut.ToArray(), true); + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/OutputStreamPacket.cs b/bc-sharp-crypto/src/bcpg/OutputStreamPacket.cs new file mode 100644 index 0000000..aa8316d --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/OutputStreamPacket.cs @@ -0,0 +1,24 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Bcpg +{ + public abstract class OutputStreamPacket + { + private readonly BcpgOutputStream bcpgOut; + + internal OutputStreamPacket( + BcpgOutputStream bcpgOut) + { + if (bcpgOut == null) + throw new ArgumentNullException("bcpgOut"); + + this.bcpgOut = bcpgOut; + } + + public abstract BcpgOutputStream Open(); + + public abstract void Close(); + } +} + diff --git a/bc-sharp-crypto/src/bcpg/Packet.cs b/bc-sharp-crypto/src/bcpg/Packet.cs new file mode 100644 index 0000000..83f6d1f --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/Packet.cs @@ -0,0 +1,7 @@ +namespace Org.BouncyCastle.Bcpg +{ + public class Packet + //: PacketTag + { + } +} diff --git a/bc-sharp-crypto/src/bcpg/PacketTags.cs b/bc-sharp-crypto/src/bcpg/PacketTags.cs new file mode 100644 index 0000000..5a53d4e --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/PacketTags.cs @@ -0,0 +1,30 @@ +namespace Org.BouncyCastle.Bcpg +{ + /// Basic PGP packet tag types. + public enum PacketTag + { + Reserved = 0, // Reserved - a packet tag must not have this value + PublicKeyEncryptedSession = 1, // Public-Key Encrypted Session Key Packet + Signature = 2, // Signature Packet + SymmetricKeyEncryptedSessionKey = 3, // Symmetric-Key Encrypted Session Key Packet + OnePassSignature = 4, // One-Pass Signature Packet + SecretKey = 5, // Secret Key Packet + PublicKey = 6, // Public Key Packet + SecretSubkey = 7, // Secret Subkey Packet + CompressedData = 8, // Compressed Data Packet + SymmetricKeyEncrypted = 9, // Symmetrically Encrypted Data Packet + Marker = 10, // Marker Packet + LiteralData = 11, // Literal Data Packet + Trust = 12, // Trust Packet + UserId = 13, // User ID Packet + PublicSubkey = 14, // Public Subkey Packet + UserAttribute = 17, // User attribute + SymmetricEncryptedIntegrityProtected = 18, // Symmetric encrypted, integrity protected + ModificationDetectionCode = 19, // Modification detection code + + Experimental1 = 60, // Private or Experimental Values + Experimental2 = 61, + Experimental3 = 62, + Experimental4 = 63 + } +} diff --git a/bc-sharp-crypto/src/bcpg/PublicKeyAlgorithmTags.cs b/bc-sharp-crypto/src/bcpg/PublicKeyAlgorithmTags.cs new file mode 100644 index 0000000..9e30b54 --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/PublicKeyAlgorithmTags.cs @@ -0,0 +1,32 @@ +using System; + +namespace Org.BouncyCastle.Bcpg +{ + /// Public Key Algorithm tag numbers. + public enum PublicKeyAlgorithmTag + { + RsaGeneral = 1, // RSA (Encrypt or Sign) + RsaEncrypt = 2, // RSA Encrypt-Only + RsaSign = 3, // RSA Sign-Only + ElGamalEncrypt = 16, // Elgamal (Encrypt-Only), see [ELGAMAL] + Dsa = 17, // DSA (Digital Signature Standard) + [Obsolete("Use 'ECDH' instead")] + EC = 18, // Reserved for Elliptic Curve + ECDH = 18, // Reserved for Elliptic Curve (actual algorithm name) + ECDsa = 19, // Reserved for ECDSA + ElGamalGeneral = 20, // Elgamal (Encrypt or Sign) + DiffieHellman = 21, // Reserved for Diffie-Hellman (X9.42, as defined for IETF-S/MIME) + + Experimental_1 = 100, + Experimental_2 = 101, + Experimental_3 = 102, + Experimental_4 = 103, + Experimental_5 = 104, + Experimental_6 = 105, + Experimental_7 = 106, + Experimental_8 = 107, + Experimental_9 = 108, + Experimental_10 = 109, + Experimental_11 = 110, + } +} diff --git a/bc-sharp-crypto/src/bcpg/PublicKeyEncSessionPacket.cs b/bc-sharp-crypto/src/bcpg/PublicKeyEncSessionPacket.cs new file mode 100644 index 0000000..831b5a1 --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/PublicKeyEncSessionPacket.cs @@ -0,0 +1,115 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Bcpg +{ + /// Basic packet for a PGP public key. + public class PublicKeyEncSessionPacket + : ContainedPacket //, PublicKeyAlgorithmTag + { + private int version; + private long keyId; + private PublicKeyAlgorithmTag algorithm; + private byte[][] data; + + internal PublicKeyEncSessionPacket( + BcpgInputStream bcpgIn) + { + version = bcpgIn.ReadByte(); + + keyId |= (long)bcpgIn.ReadByte() << 56; + keyId |= (long)bcpgIn.ReadByte() << 48; + keyId |= (long)bcpgIn.ReadByte() << 40; + keyId |= (long)bcpgIn.ReadByte() << 32; + keyId |= (long)bcpgIn.ReadByte() << 24; + keyId |= (long)bcpgIn.ReadByte() << 16; + keyId |= (long)bcpgIn.ReadByte() << 8; + keyId |= (uint)bcpgIn.ReadByte(); + + algorithm = (PublicKeyAlgorithmTag) bcpgIn.ReadByte(); + + switch ((PublicKeyAlgorithmTag) algorithm) + { + case PublicKeyAlgorithmTag.RsaEncrypt: + case PublicKeyAlgorithmTag.RsaGeneral: + data = new byte[][]{ new MPInteger(bcpgIn).GetEncoded() }; + break; + case PublicKeyAlgorithmTag.ElGamalEncrypt: + case PublicKeyAlgorithmTag.ElGamalGeneral: + MPInteger p = new MPInteger(bcpgIn); + MPInteger g = new MPInteger(bcpgIn); + data = new byte[][]{ + p.GetEncoded(), + g.GetEncoded(), + }; + break; + case PublicKeyAlgorithmTag.ECDH: + data = new byte[][]{ Streams.ReadAll(bcpgIn) }; + break; + default: + throw new IOException("unknown PGP public key algorithm encountered"); + } + } + + public PublicKeyEncSessionPacket( + long keyId, + PublicKeyAlgorithmTag algorithm, + byte[][] data) + { + this.version = 3; + this.keyId = keyId; + this.algorithm = algorithm; + this.data = new byte[data.Length][]; + for (int i = 0; i < data.Length; ++i) + { + this.data[i] = Arrays.Clone(data[i]); + } + } + + public int Version + { + get { return version; } + } + + public long KeyId + { + get { return keyId; } + } + + public PublicKeyAlgorithmTag Algorithm + { + get { return algorithm; } + } + + public byte[][] GetEncSessionKey() + { + return data; + } + + public override void Encode( + BcpgOutputStream bcpgOut) + { + MemoryStream bOut = new MemoryStream(); + BcpgOutputStream pOut = new BcpgOutputStream(bOut); + + pOut.WriteByte((byte) version); + + pOut.WriteLong(keyId); + + pOut.WriteByte((byte)algorithm); + + for (int i = 0; i < data.Length; ++i) + { + pOut.Write(data[i]); + } + + Platform.Dispose(pOut); + + bcpgOut.WritePacket(PacketTag.PublicKeyEncryptedSession , bOut.ToArray(), true); + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/PublicKeyPacket.cs b/bc-sharp-crypto/src/bcpg/PublicKeyPacket.cs new file mode 100644 index 0000000..bbed941 --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/PublicKeyPacket.cs @@ -0,0 +1,121 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Utilities.Date; + +namespace Org.BouncyCastle.Bcpg +{ + /// Basic packet for a PGP public key. + public class PublicKeyPacket + : ContainedPacket //, PublicKeyAlgorithmTag + { + private int version; + private long time; + private int validDays; + private PublicKeyAlgorithmTag algorithm; + private IBcpgKey key; + + internal PublicKeyPacket( + BcpgInputStream bcpgIn) + { + version = bcpgIn.ReadByte(); + + time = ((uint)bcpgIn.ReadByte() << 24) | ((uint)bcpgIn.ReadByte() << 16) + | ((uint)bcpgIn.ReadByte() << 8) | (uint)bcpgIn.ReadByte(); + + if (version <= 3) + { + validDays = (bcpgIn.ReadByte() << 8) | bcpgIn.ReadByte(); + } + + algorithm = (PublicKeyAlgorithmTag) bcpgIn.ReadByte(); + + switch ((PublicKeyAlgorithmTag) algorithm) + { + case PublicKeyAlgorithmTag.RsaEncrypt: + case PublicKeyAlgorithmTag.RsaGeneral: + case PublicKeyAlgorithmTag.RsaSign: + key = new RsaPublicBcpgKey(bcpgIn); + break; + case PublicKeyAlgorithmTag.Dsa: + key = new DsaPublicBcpgKey(bcpgIn); + break; + case PublicKeyAlgorithmTag.ElGamalEncrypt: + case PublicKeyAlgorithmTag.ElGamalGeneral: + key = new ElGamalPublicBcpgKey(bcpgIn); + break; + case PublicKeyAlgorithmTag.ECDH: + key = new ECDHPublicBcpgKey(bcpgIn); + break; + case PublicKeyAlgorithmTag.ECDsa: + key = new ECDsaPublicBcpgKey(bcpgIn); + break; + default: + throw new IOException("unknown PGP public key algorithm encountered"); + } + } + + /// Construct a version 4 public key packet. + public PublicKeyPacket( + PublicKeyAlgorithmTag algorithm, + DateTime time, + IBcpgKey key) + { + this.version = 4; + this.time = DateTimeUtilities.DateTimeToUnixMs(time) / 1000L; + this.algorithm = algorithm; + this.key = key; + } + + public virtual int Version + { + get { return version; } + } + + public virtual PublicKeyAlgorithmTag Algorithm + { + get { return algorithm; } + } + + public virtual int ValidDays + { + get { return validDays; } + } + + public virtual DateTime GetTime() + { + return DateTimeUtilities.UnixMsToDateTime(time * 1000L); + } + + public virtual IBcpgKey Key + { + get { return key; } + } + + public virtual byte[] GetEncodedContents() + { + MemoryStream bOut = new MemoryStream(); + BcpgOutputStream pOut = new BcpgOutputStream(bOut); + + pOut.WriteByte((byte) version); + pOut.WriteInt((int) time); + + if (version <= 3) + { + pOut.WriteShort((short) validDays); + } + + pOut.WriteByte((byte) algorithm); + + pOut.WriteObject((BcpgObject)key); + + return bOut.ToArray(); + } + + public override void Encode( + BcpgOutputStream bcpgOut) + { + bcpgOut.WritePacket(PacketTag.PublicKey, GetEncodedContents(), true); + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/PublicSubkeyPacket.cs b/bc-sharp-crypto/src/bcpg/PublicSubkeyPacket.cs new file mode 100644 index 0000000..6e1aeda --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/PublicSubkeyPacket.cs @@ -0,0 +1,30 @@ +using System; +using System.IO; +namespace Org.BouncyCastle.Bcpg +{ + /// Basic packet for a PGP public subkey + public class PublicSubkeyPacket + : PublicKeyPacket + { + internal PublicSubkeyPacket( + BcpgInputStream bcpgIn) + : base(bcpgIn) + { + } + + /// Construct a version 4 public subkey packet. + public PublicSubkeyPacket( + PublicKeyAlgorithmTag algorithm, + DateTime time, + IBcpgKey key) + : base(algorithm, time, key) + { + } + + public override void Encode( + BcpgOutputStream bcpgOut) + { + bcpgOut.WritePacket(PacketTag.PublicSubkey, GetEncodedContents(), true); + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/RsaPublicBcpgKey.cs b/bc-sharp-crypto/src/bcpg/RsaPublicBcpgKey.cs new file mode 100644 index 0000000..fd2313c --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/RsaPublicBcpgKey.cs @@ -0,0 +1,66 @@ +using System; + +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Bcpg +{ + /// Base class for an RSA public key. + public class RsaPublicBcpgKey + : BcpgObject, IBcpgKey + { + private readonly MPInteger n, e; + + /// Construct an RSA public key from the passed in stream. + public RsaPublicBcpgKey( + BcpgInputStream bcpgIn) + { + this.n = new MPInteger(bcpgIn); + this.e = new MPInteger(bcpgIn); + } + + /// The modulus. + /// The public exponent. + public RsaPublicBcpgKey( + BigInteger n, + BigInteger e) + { + this.n = new MPInteger(n); + this.e = new MPInteger(e); + } + + public BigInteger PublicExponent + { + get { return e.Value; } + } + + public BigInteger Modulus + { + get { return n.Value; } + } + + /// The format, as a string, always "PGP". + public string Format + { + get { return "PGP"; } + } + + /// Return the standard PGP encoding of the key. + public override byte[] GetEncoded() + { + try + { + return base.GetEncoded(); + } + catch (Exception) + { + return null; + } + } + + public override void Encode( + BcpgOutputStream bcpgOut) + { + bcpgOut.WriteObjects(n, e); + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/RsaSecretBcpgKey.cs b/bc-sharp-crypto/src/bcpg/RsaSecretBcpgKey.cs new file mode 100644 index 0000000..5c04d9f --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/RsaSecretBcpgKey.cs @@ -0,0 +1,114 @@ +using System; + +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Bcpg +{ + /// Base class for an RSA secret (or priate) key. + public class RsaSecretBcpgKey + : BcpgObject, IBcpgKey + { + private readonly MPInteger d, p, q, u; + private readonly BigInteger expP, expQ, crt; + + public RsaSecretBcpgKey( + BcpgInputStream bcpgIn) + { + this.d = new MPInteger(bcpgIn); + this.p = new MPInteger(bcpgIn); + this.q = new MPInteger(bcpgIn); + this.u = new MPInteger(bcpgIn); + + this.expP = d.Value.Remainder(p.Value.Subtract(BigInteger.One)); + this.expQ = d.Value.Remainder(q.Value.Subtract(BigInteger.One)); + this.crt = q.Value.ModInverse(p.Value); + } + + 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 ArgumentException("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)); + + this.expP = d.Remainder(p.Subtract(BigInteger.One)); + this.expQ = d.Remainder(q.Subtract(BigInteger.One)); + this.crt = q.ModInverse(p); + } + + public BigInteger Modulus + { + get { return p.Value.Multiply(q.Value); } + } + + public BigInteger PrivateExponent + { + get { return d.Value; } + } + + public BigInteger PrimeP + { + get { return p.Value; } + } + + public BigInteger PrimeQ + { + get { return q.Value; } + } + + public BigInteger PrimeExponentP + { + get { return expP; } + } + + public BigInteger PrimeExponentQ + { + get { return expQ; } + } + + public BigInteger CrtCoefficient + { + get { return crt; } + } + + /// The format, as a string, always "PGP". + public string Format + { + get { return "PGP"; } + } + + /// Return the standard PGP encoding of the key. + public override byte[] GetEncoded() + { + try + { + return base.GetEncoded(); + } + catch (Exception) + { + return null; + } + } + + public override void Encode( + BcpgOutputStream bcpgOut) + { + bcpgOut.WriteObjects(d, p, q, u); + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/S2k.cs b/bc-sharp-crypto/src/bcpg/S2k.cs new file mode 100644 index 0000000..33fd792 --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/S2k.cs @@ -0,0 +1,149 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Bcpg +{ + /// The string to key specifier class. + public class S2k + : BcpgObject + { + private const int ExpBias = 6; + + public const int Simple = 0; + public const int Salted = 1; + public const int SaltedAndIterated = 3; + public const int GnuDummyS2K = 101; + public const int GnuProtectionModeNoPrivateKey = 1; + public const int GnuProtectionModeDivertToCard = 2; + + internal int type; + internal HashAlgorithmTag algorithm; + internal byte[] iv; + internal int itCount = -1; + internal int protectionMode = -1; + + internal S2k( + Stream inStr) + { + type = inStr.ReadByte(); + algorithm = (HashAlgorithmTag) inStr.ReadByte(); + + // + // if this happens we have a dummy-S2k packet. + // + if (type != GnuDummyS2K) + { + if (type != 0) + { + iv = new byte[8]; + if (Streams.ReadFully(inStr, iv, 0, iv.Length) < iv.Length) + throw new EndOfStreamException(); + + if (type == 3) + { + itCount = inStr.ReadByte(); + } + } + } + else + { + inStr.ReadByte(); // G + inStr.ReadByte(); // N + inStr.ReadByte(); // U + protectionMode = inStr.ReadByte(); // protection mode + } + } + + public S2k( + HashAlgorithmTag algorithm) + { + this.type = 0; + this.algorithm = algorithm; + } + + public S2k( + HashAlgorithmTag algorithm, + byte[] iv) + { + this.type = 1; + this.algorithm = algorithm; + this.iv = iv; + } + + public S2k( + HashAlgorithmTag algorithm, + byte[] iv, + int itCount) + { + this.type = 3; + this.algorithm = algorithm; + this.iv = iv; + this.itCount = itCount; + } + + public virtual int Type + { + get { return type; } + } + + /// The hash algorithm. + public virtual HashAlgorithmTag HashAlgorithm + { + get { return algorithm; } + } + + /// The IV for the key generation algorithm. + public virtual byte[] GetIV() + { + return Arrays.Clone(iv); + } + + [Obsolete("Use 'IterationCount' property instead")] + public long GetIterationCount() + { + return IterationCount; + } + + /// The iteration count + public virtual long IterationCount + { + get { return (16 + (itCount & 15)) << ((itCount >> 4) + ExpBias); } + } + + /// The protection mode - only if GnuDummyS2K + public virtual int ProtectionMode + { + get { return protectionMode; } + } + + public override void Encode( + BcpgOutputStream bcpgOut) + { + bcpgOut.WriteByte((byte) type); + bcpgOut.WriteByte((byte) algorithm); + + if (type != GnuDummyS2K) + { + if (type != 0) + { + bcpgOut.Write(iv); + } + + if (type == 3) + { + bcpgOut.WriteByte((byte) itCount); + } + } + else + { + bcpgOut.WriteByte((byte) 'G'); + bcpgOut.WriteByte((byte) 'N'); + bcpgOut.WriteByte((byte) 'U'); + bcpgOut.WriteByte((byte) protectionMode); + } + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/SecretKeyPacket.cs b/bc-sharp-crypto/src/bcpg/SecretKeyPacket.cs new file mode 100644 index 0000000..d9ceab4 --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/SecretKeyPacket.cs @@ -0,0 +1,170 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Bcpg +{ + /// Basic packet for a PGP secret key. + public class SecretKeyPacket + : ContainedPacket //, PublicKeyAlgorithmTag + { + public const int UsageNone = 0x00; + public const int UsageChecksum = 0xff; + public const int UsageSha1 = 0xfe; + + private PublicKeyPacket pubKeyPacket; + private readonly byte[] secKeyData; + private int s2kUsage; + private SymmetricKeyAlgorithmTag encAlgorithm; + private S2k s2k; + private byte[] iv; + + internal SecretKeyPacket( + BcpgInputStream bcpgIn) + { + if (this is SecretSubkeyPacket) + { + pubKeyPacket = new PublicSubkeyPacket(bcpgIn); + } + else + { + pubKeyPacket = new PublicKeyPacket(bcpgIn); + } + + s2kUsage = bcpgIn.ReadByte(); + + if (s2kUsage == UsageChecksum || s2kUsage == UsageSha1) + { + encAlgorithm = (SymmetricKeyAlgorithmTag) bcpgIn.ReadByte(); + s2k = new S2k(bcpgIn); + } + else + { + encAlgorithm = (SymmetricKeyAlgorithmTag) s2kUsage; + } + + if (!(s2k != null && s2k.Type == S2k.GnuDummyS2K && s2k.ProtectionMode == 0x01)) + { + if (s2kUsage != 0) + { + if (((int) encAlgorithm) < 7) + { + iv = new byte[8]; + } + else + { + iv = new byte[16]; + } + bcpgIn.ReadFully(iv); + } + } + + secKeyData = bcpgIn.ReadAll(); + } + + public SecretKeyPacket( + PublicKeyPacket pubKeyPacket, + SymmetricKeyAlgorithmTag encAlgorithm, + S2k s2k, + byte[] iv, + byte[] secKeyData) + { + this.pubKeyPacket = pubKeyPacket; + this.encAlgorithm = encAlgorithm; + + if (encAlgorithm != SymmetricKeyAlgorithmTag.Null) + { + this.s2kUsage = UsageChecksum; + } + else + { + this.s2kUsage = UsageNone; + } + + this.s2k = s2k; + this.iv = Arrays.Clone(iv); + this.secKeyData = secKeyData; + } + + public SecretKeyPacket( + PublicKeyPacket pubKeyPacket, + SymmetricKeyAlgorithmTag encAlgorithm, + int s2kUsage, + S2k s2k, + byte[] iv, + byte[] secKeyData) + { + this.pubKeyPacket = pubKeyPacket; + this.encAlgorithm = encAlgorithm; + this.s2kUsage = s2kUsage; + this.s2k = s2k; + this.iv = Arrays.Clone(iv); + this.secKeyData = secKeyData; + } + + public SymmetricKeyAlgorithmTag EncAlgorithm + { + get { return encAlgorithm; } + } + + public int S2kUsage + { + get { return s2kUsage; } + } + + public byte[] GetIV() + { + return Arrays.Clone(iv); + } + + public S2k S2k + { + get { return s2k; } + } + + public PublicKeyPacket PublicKeyPacket + { + get { return pubKeyPacket; } + } + + public byte[] GetSecretKeyData() + { + return secKeyData; + } + + public byte[] GetEncodedContents() + { + MemoryStream bOut = new MemoryStream(); + BcpgOutputStream pOut = new BcpgOutputStream(bOut); + + pOut.Write(pubKeyPacket.GetEncodedContents()); + + pOut.WriteByte((byte) s2kUsage); + + if (s2kUsage == UsageChecksum || s2kUsage == UsageSha1) + { + pOut.WriteByte((byte) encAlgorithm); + pOut.WriteObject(s2k); + } + + if (iv != null) + { + pOut.Write(iv); + } + + if (secKeyData != null && secKeyData.Length > 0) + { + pOut.Write(secKeyData); + } + + return bOut.ToArray(); + } + + public override void Encode( + BcpgOutputStream bcpgOut) + { + bcpgOut.WritePacket(PacketTag.SecretKey, GetEncodedContents(), true); + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/SecretSubkeyPacket.cs b/bc-sharp-crypto/src/bcpg/SecretSubkeyPacket.cs new file mode 100644 index 0000000..8f17469 --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/SecretSubkeyPacket.cs @@ -0,0 +1,43 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Bcpg +{ + /// Basic packet for a PGP secret key. + public class SecretSubkeyPacket + : SecretKeyPacket + { + internal SecretSubkeyPacket( + BcpgInputStream bcpgIn) + : base(bcpgIn) + { + } + + public SecretSubkeyPacket( + PublicKeyPacket pubKeyPacket, + SymmetricKeyAlgorithmTag encAlgorithm, + S2k s2k, + byte[] iv, + byte[] secKeyData) + : base(pubKeyPacket, encAlgorithm, s2k, iv, secKeyData) + { + } + + public SecretSubkeyPacket( + PublicKeyPacket pubKeyPacket, + SymmetricKeyAlgorithmTag encAlgorithm, + int s2kUsage, + S2k s2k, + byte[] iv, + byte[] secKeyData) + : base(pubKeyPacket, encAlgorithm, s2kUsage, s2k, iv, secKeyData) + { + } + + public override void Encode( + BcpgOutputStream bcpgOut) + { + bcpgOut.WritePacket(PacketTag.SecretSubkey, GetEncodedContents(), true); + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/SignaturePacket.cs b/bc-sharp-crypto/src/bcpg/SignaturePacket.cs new file mode 100644 index 0000000..5b91c15 --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/SignaturePacket.cs @@ -0,0 +1,477 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Bcpg.Sig; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Date; + +namespace Org.BouncyCastle.Bcpg +{ + /// Generic signature packet. + public class SignaturePacket + : ContainedPacket //, PublicKeyAlgorithmTag + { + private int version; + private int signatureType; + private long creationTime; + private long keyId; + private PublicKeyAlgorithmTag keyAlgorithm; + private HashAlgorithmTag hashAlgorithm; + private MPInteger[] signature; + private byte[] fingerprint; + private SignatureSubpacket[] hashedData; + private SignatureSubpacket[] unhashedData; + private byte[] signatureEncoding; + + internal SignaturePacket( + BcpgInputStream bcpgIn) + { + version = bcpgIn.ReadByte(); + + if (version == 3 || version == 2) + { +// int l = + bcpgIn.ReadByte(); + + signatureType = bcpgIn.ReadByte(); + creationTime = (((long)bcpgIn.ReadByte() << 24) | ((long)bcpgIn.ReadByte() << 16) + | ((long)bcpgIn.ReadByte() << 8) | (uint)bcpgIn.ReadByte()) * 1000L; + + keyId |= (long)bcpgIn.ReadByte() << 56; + keyId |= (long)bcpgIn.ReadByte() << 48; + keyId |= (long)bcpgIn.ReadByte() << 40; + keyId |= (long)bcpgIn.ReadByte() << 32; + keyId |= (long)bcpgIn.ReadByte() << 24; + keyId |= (long)bcpgIn.ReadByte() << 16; + keyId |= (long)bcpgIn.ReadByte() << 8; + keyId |= (uint)bcpgIn.ReadByte(); + + keyAlgorithm = (PublicKeyAlgorithmTag) bcpgIn.ReadByte(); + hashAlgorithm = (HashAlgorithmTag) bcpgIn.ReadByte(); + } + else if (version == 4) + { + signatureType = bcpgIn.ReadByte(); + keyAlgorithm = (PublicKeyAlgorithmTag) bcpgIn.ReadByte(); + hashAlgorithm = (HashAlgorithmTag) bcpgIn.ReadByte(); + + int hashedLength = (bcpgIn.ReadByte() << 8) | bcpgIn.ReadByte(); + byte[] hashed = new byte[hashedLength]; + + bcpgIn.ReadFully(hashed); + + // + // read the signature sub packet data. + // + SignatureSubpacketsParser sIn = new SignatureSubpacketsParser( + new MemoryStream(hashed, false)); + + IList v = Platform.CreateArrayList(); + SignatureSubpacket sub; + while ((sub = sIn.ReadPacket()) != null) + { + v.Add(sub); + } + + hashedData = new SignatureSubpacket[v.Count]; + + for (int i = 0; i != hashedData.Length; i++) + { + SignatureSubpacket p = (SignatureSubpacket)v[i]; + if (p is IssuerKeyId) + { + keyId = ((IssuerKeyId)p).KeyId; + } + else if (p is SignatureCreationTime) + { + creationTime = DateTimeUtilities.DateTimeToUnixMs( + ((SignatureCreationTime)p).GetTime()); + } + + hashedData[i] = p; + } + + int unhashedLength = (bcpgIn.ReadByte() << 8) | bcpgIn.ReadByte(); + byte[] unhashed = new byte[unhashedLength]; + + bcpgIn.ReadFully(unhashed); + + sIn = new SignatureSubpacketsParser(new MemoryStream(unhashed, false)); + + v.Clear(); + + while ((sub = sIn.ReadPacket()) != null) + { + v.Add(sub); + } + + unhashedData = new SignatureSubpacket[v.Count]; + + for (int i = 0; i != unhashedData.Length; i++) + { + SignatureSubpacket p = (SignatureSubpacket)v[i]; + if (p is IssuerKeyId) + { + keyId = ((IssuerKeyId)p).KeyId; + } + + unhashedData[i] = p; + } + } + else + { + throw new Exception("unsupported version: " + version); + } + + fingerprint = new byte[2]; + bcpgIn.ReadFully(fingerprint); + + switch (keyAlgorithm) + { + case PublicKeyAlgorithmTag.RsaGeneral: + case PublicKeyAlgorithmTag.RsaSign: + MPInteger v = new MPInteger(bcpgIn); + signature = new MPInteger[]{ v }; + break; + case PublicKeyAlgorithmTag.Dsa: + MPInteger r = new MPInteger(bcpgIn); + MPInteger s = new MPInteger(bcpgIn); + signature = new MPInteger[]{ r, s }; + break; + case PublicKeyAlgorithmTag.ElGamalEncrypt: // yep, this really does happen sometimes. + case PublicKeyAlgorithmTag.ElGamalGeneral: + MPInteger p = new MPInteger(bcpgIn); + MPInteger g = new MPInteger(bcpgIn); + MPInteger y = new MPInteger(bcpgIn); + signature = new MPInteger[]{ p, g, y }; + break; + case PublicKeyAlgorithmTag.ECDsa: + MPInteger ecR = new MPInteger(bcpgIn); + MPInteger ecS = new MPInteger(bcpgIn); + signature = new MPInteger[]{ ecR, ecS }; + break; + default: + if (keyAlgorithm >= PublicKeyAlgorithmTag.Experimental_1 && keyAlgorithm <= PublicKeyAlgorithmTag.Experimental_11) + { + signature = null; + MemoryStream bOut = new MemoryStream(); + int ch; + while ((ch = bcpgIn.ReadByte()) >= 0) + { + bOut.WriteByte((byte) ch); + } + signatureEncoding = bOut.ToArray(); + } + else + { + throw new IOException("unknown signature key algorithm: " + keyAlgorithm); + } + break; + } + } + + /** + * 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, + PublicKeyAlgorithmTag keyAlgorithm, + HashAlgorithmTag 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, + PublicKeyAlgorithmTag keyAlgorithm, + HashAlgorithmTag 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, + PublicKeyAlgorithmTag keyAlgorithm, + HashAlgorithmTag 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(); + } + } + + public int Version + { + get { return version; } + } + + public int SignatureType + { + get { return signatureType; } + } + + /** + * return the keyId + * @return the keyId that created the signature. + */ + public long KeyId + { + get { 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) + { + trailer = new byte[5]; + + long time = creationTime / 1000L; + + trailer[0] = (byte)signatureType; + trailer[1] = (byte)(time >> 24); + trailer[2] = (byte)(time >> 16); + trailer[3] = (byte)(time >> 8); + trailer[4] = (byte)(time); + } + else + { + MemoryStream sOut = new MemoryStream(); + + sOut.WriteByte((byte)this.Version); + sOut.WriteByte((byte)this.SignatureType); + sOut.WriteByte((byte)this.KeyAlgorithm); + sOut.WriteByte((byte)this.HashAlgorithm); + + MemoryStream hOut = new MemoryStream(); + SignatureSubpacket[] hashed = this.GetHashedSubPackets(); + + for (int i = 0; i != hashed.Length; i++) + { + hashed[i].Encode(hOut); + } + + byte[] data = hOut.ToArray(); + + sOut.WriteByte((byte)(data.Length >> 8)); + sOut.WriteByte((byte)data.Length); + sOut.Write(data, 0, data.Length); + + byte[] hData = sOut.ToArray(); + + sOut.WriteByte((byte)this.Version); + sOut.WriteByte((byte)0xff); + sOut.WriteByte((byte)(hData.Length>> 24)); + sOut.WriteByte((byte)(hData.Length >> 16)); + sOut.WriteByte((byte)(hData.Length >> 8)); + sOut.WriteByte((byte)(hData.Length)); + + trailer = sOut.ToArray(); + } + + return trailer; + } + + public PublicKeyAlgorithmTag KeyAlgorithm + { + get { return keyAlgorithm; } + } + + public HashAlgorithmTag HashAlgorithm + { + get { 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) + { + return (byte[]) signatureEncoding.Clone(); + } + + MemoryStream bOut = new MemoryStream(); + BcpgOutputStream bcOut = new BcpgOutputStream(bOut); + + foreach (MPInteger sigObj in signature) + { + try + { + bcOut.WriteObject(sigObj); + } + catch (IOException e) + { + throw new Exception("internal error: " + e); + } + } + + return bOut.ToArray(); + } + + public SignatureSubpacket[] GetHashedSubPackets() + { + return hashedData; + } + + public SignatureSubpacket[] GetUnhashedSubPackets() + { + return unhashedData; + } + + /// Return the creation time in milliseconds since 1 Jan., 1970 UTC. + public long CreationTime + { + get { return creationTime; } + } + + public override void Encode( + BcpgOutputStream bcpgOut) + { + MemoryStream bOut = new MemoryStream(); + BcpgOutputStream pOut = new BcpgOutputStream(bOut); + + pOut.WriteByte((byte) version); + + if (version == 3 || version == 2) + { + pOut.Write( + 5, // the length of the next block + (byte) signatureType); + + pOut.WriteInt((int)(creationTime / 1000L)); + + pOut.WriteLong(keyId); + + pOut.Write( + (byte) keyAlgorithm, + (byte) hashAlgorithm); + } + else if (version == 4) + { + pOut.Write( + (byte) signatureType, + (byte) keyAlgorithm, + (byte) hashAlgorithm); + + EncodeLengthAndData(pOut, GetEncodedSubpackets(hashedData)); + + EncodeLengthAndData(pOut, GetEncodedSubpackets(unhashedData)); + } + else + { + throw new IOException("unknown version: " + version); + } + + pOut.Write(fingerprint); + + if (signature != null) + { + pOut.WriteObjects(signature); + } + else + { + pOut.Write(signatureEncoding); + } + + bcpgOut.WritePacket(PacketTag.Signature, bOut.ToArray(), true); + } + + private static void EncodeLengthAndData( + BcpgOutputStream pOut, + byte[] data) + { + pOut.WriteShort((short) data.Length); + pOut.Write(data); + } + + private static byte[] GetEncodedSubpackets( + SignatureSubpacket[] ps) + { + MemoryStream sOut = new MemoryStream(); + + foreach (SignatureSubpacket p in ps) + { + p.Encode(sOut); + } + + return sOut.ToArray(); + } + + private void setCreationTime() + { + foreach (SignatureSubpacket p in hashedData) + { + if (p is SignatureCreationTime) + { + creationTime = DateTimeUtilities.DateTimeToUnixMs( + ((SignatureCreationTime)p).GetTime()); + break; + } + } + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/SignatureSubpacket.cs b/bc-sharp-crypto/src/bcpg/SignatureSubpacket.cs new file mode 100644 index 0000000..d993155 --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/SignatureSubpacket.cs @@ -0,0 +1,94 @@ +using System.IO; + +namespace Org.BouncyCastle.Bcpg +{ + /// Basic type for a PGP Signature sub-packet. + public class SignatureSubpacket + { + private readonly SignatureSubpacketTag type; + private readonly bool critical; + private readonly bool isLongLength; + internal byte[] data; + + protected internal SignatureSubpacket( + SignatureSubpacketTag type, + bool critical, + bool isLongLength, + byte[] data) + { + this.type = type; + this.critical = critical; + this.isLongLength = isLongLength; + this.data = data; + } + + public SignatureSubpacketTag SubpacketType + { + get { return type; } + } + + public bool IsCritical() + { + return critical; + } + + public bool IsLongLength() + { + return isLongLength; + } + + /// Return the generic data making up the packet. + public byte[] GetData() + { + return (byte[]) data.Clone(); + } + + public void Encode( + Stream os) + { + int bodyLen = data.Length + 1; + + if (isLongLength) + { + os.WriteByte(0xff); + os.WriteByte((byte)(bodyLen >> 24)); + os.WriteByte((byte)(bodyLen >> 16)); + os.WriteByte((byte)(bodyLen >> 8)); + os.WriteByte((byte)bodyLen); + } + else + { + if (bodyLen < 192) + { + os.WriteByte((byte)bodyLen); + } + else if (bodyLen <= 8383) + { + bodyLen -= 192; + + os.WriteByte((byte)(((bodyLen >> 8) & 0xff) + 192)); + os.WriteByte((byte)bodyLen); + } + else + { + os.WriteByte(0xff); + os.WriteByte((byte)(bodyLen >> 24)); + os.WriteByte((byte)(bodyLen >> 16)); + os.WriteByte((byte)(bodyLen >> 8)); + os.WriteByte((byte)bodyLen); + } + } + + if (critical) + { + os.WriteByte((byte)(0x80 | (int) type)); + } + else + { + os.WriteByte((byte) type); + } + + os.Write(data, 0, data.Length); + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/SignatureSubpacketTags.cs b/bc-sharp-crypto/src/bcpg/SignatureSubpacketTags.cs new file mode 100644 index 0000000..1a8e254 --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/SignatureSubpacketTags.cs @@ -0,0 +1,33 @@ +namespace Org.BouncyCastle.Bcpg +{ + /** + * Basic PGP signature sub-packet tag types. + */ + public enum SignatureSubpacketTag + { + CreationTime = 2, // signature creation time + ExpireTime = 3, // signature expiration time + Exportable = 4, // exportable certification + TrustSig = 5, // trust signature + RegExp = 6, // regular expression + Revocable = 7, // revocable + KeyExpireTime = 9, // key expiration time + Placeholder = 10, // placeholder for backward compatibility + PreferredSymmetricAlgorithms = 11, // preferred symmetric algorithms + RevocationKey = 12, // revocation key + IssuerKeyId = 16, // issuer key ID + NotationData = 20, // notation data + PreferredHashAlgorithms = 21, // preferred hash algorithms + PreferredCompressionAlgorithms = 22, // preferred compression algorithms + KeyServerPreferences = 23, // key server preferences + PreferredKeyServer = 24, // preferred key server + PrimaryUserId = 25, // primary user id + PolicyUrl = 26, // policy URL + KeyFlags = 27, // key flags + SignerUserId = 28, // signer's user id + RevocationReason = 29, // reason for revocation + Features = 30, // features + SignatureTarget = 31, // signature target + EmbeddedSignature = 32 // embedded signature + } +} diff --git a/bc-sharp-crypto/src/bcpg/SignatureSubpacketsReader.cs b/bc-sharp-crypto/src/bcpg/SignatureSubpacketsReader.cs new file mode 100644 index 0000000..80bedb0 --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/SignatureSubpacketsReader.cs @@ -0,0 +1,128 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Bcpg.Sig; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Bcpg +{ + /** + * reader for signature sub-packets + */ + public class SignatureSubpacketsParser + { + private readonly Stream input; + + public SignatureSubpacketsParser( + Stream input) + { + this.input = input; + } + + public SignatureSubpacket ReadPacket() + { + int l = input.ReadByte(); + if (l < 0) + return null; + + int bodyLen = 0; + bool isLongLength = false; + + if (l < 192) + { + bodyLen = l; + } + else if (l <= 223) + { + bodyLen = ((l - 192) << 8) + (input.ReadByte()) + 192; + } + else if (l == 255) + { + isLongLength = true; + bodyLen = (input.ReadByte() << 24) | (input.ReadByte() << 16) + | (input.ReadByte() << 8) | input.ReadByte(); + } + else + { + throw new IOException("unexpected length header"); + } + + int tag = input.ReadByte(); + if (tag < 0) + throw new EndOfStreamException("unexpected EOF reading signature sub packet"); + + byte[] data = new byte[bodyLen - 1]; + + // + // this may seem a bit strange but it turns out some applications miscode the length + // in fixed length fields, so we check the length we do get, only throwing an exception if + // we really cannot continue + // + int bytesRead = Streams.ReadFully(input, data); + + bool isCritical = ((tag & 0x80) != 0); + SignatureSubpacketTag type = (SignatureSubpacketTag)(tag & 0x7f); + + if (bytesRead != data.Length) + { + switch (type) + { + case SignatureSubpacketTag.CreationTime: + data = CheckData(data, 4, bytesRead, "Signature Creation Time"); + break; + case SignatureSubpacketTag.IssuerKeyId: + data = CheckData(data, 8, bytesRead, "Issuer"); + break; + case SignatureSubpacketTag.KeyExpireTime: + data = CheckData(data, 4, bytesRead, "Signature Key Expiration Time"); + break; + case SignatureSubpacketTag.ExpireTime: + data = CheckData(data, 4, bytesRead, "Signature Expiration Time"); + break; + default: + throw new EndOfStreamException("truncated subpacket data."); + } + } + + switch (type) + { + case SignatureSubpacketTag.CreationTime: + return new SignatureCreationTime(isCritical, isLongLength, data); + case SignatureSubpacketTag.KeyExpireTime: + return new KeyExpirationTime(isCritical, isLongLength, data); + case SignatureSubpacketTag.ExpireTime: + return new SignatureExpirationTime(isCritical, isLongLength, data); + case SignatureSubpacketTag.Revocable: + return new Revocable(isCritical, isLongLength, data); + case SignatureSubpacketTag.Exportable: + return new Exportable(isCritical, isLongLength, data); + case SignatureSubpacketTag.IssuerKeyId: + return new IssuerKeyId(isCritical, isLongLength, data); + case SignatureSubpacketTag.TrustSig: + return new TrustSignature(isCritical, isLongLength, data); + case SignatureSubpacketTag.PreferredCompressionAlgorithms: + case SignatureSubpacketTag.PreferredHashAlgorithms: + case SignatureSubpacketTag.PreferredSymmetricAlgorithms: + return new PreferredAlgorithms(type, isCritical, isLongLength, data); + case SignatureSubpacketTag.KeyFlags: + return new KeyFlags(isCritical, isLongLength, data); + case SignatureSubpacketTag.PrimaryUserId: + return new PrimaryUserId(isCritical, isLongLength, data); + case SignatureSubpacketTag.SignerUserId: + return new SignerUserId(isCritical, isLongLength, data); + case SignatureSubpacketTag.NotationData: + return new NotationData(isCritical, isLongLength, data); + } + return new SignatureSubpacket(type, isCritical, isLongLength, data); + } + + private byte[] CheckData(byte[] data, int expected, int bytesRead, string name) + { + if (bytesRead != expected) + throw new EndOfStreamException("truncated " + name + " subpacket data."); + + return Arrays.CopyOfRange(data, 0, expected); + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/SymmetricEncDataPacket.cs b/bc-sharp-crypto/src/bcpg/SymmetricEncDataPacket.cs new file mode 100644 index 0000000..17ee55b --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/SymmetricEncDataPacket.cs @@ -0,0 +1,15 @@ +using System; + +namespace Org.BouncyCastle.Bcpg +{ + /// Basic type for a symmetric key encrypted packet. + public class SymmetricEncDataPacket + : InputStreamPacket + { + public SymmetricEncDataPacket( + BcpgInputStream bcpgIn) + : base(bcpgIn) + { + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/SymmetricEncIntegrityPacket.cs b/bc-sharp-crypto/src/bcpg/SymmetricEncIntegrityPacket.cs new file mode 100644 index 0000000..a9b6d06 --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/SymmetricEncIntegrityPacket.cs @@ -0,0 +1,18 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Bcpg +{ + public class SymmetricEncIntegrityPacket + : InputStreamPacket + { + internal readonly int version; + + internal SymmetricEncIntegrityPacket( + BcpgInputStream bcpgIn) + : base(bcpgIn) + { + version = bcpgIn.ReadByte(); + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/SymmetricKeyAlgorithmTags.cs b/bc-sharp-crypto/src/bcpg/SymmetricKeyAlgorithmTags.cs new file mode 100644 index 0000000..e05a486 --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/SymmetricKeyAlgorithmTags.cs @@ -0,0 +1,23 @@ +namespace Org.BouncyCastle.Bcpg +{ + /** + * Basic tags for symmetric key algorithms + */ + public enum SymmetricKeyAlgorithmTag + { + Null = 0, // Plaintext or unencrypted data + Idea = 1, // IDEA [IDEA] + TripleDes = 2, // Triple-DES (DES-EDE, as per spec -168 bit key derived from 192) + Cast5 = 3, // Cast5 (128 bit key, as per RFC 2144) + Blowfish = 4, // Blowfish (128 bit key, 16 rounds) [Blowfish] + Safer = 5, // Safer-SK128 (13 rounds) [Safer] + Des = 6, // Reserved for DES/SK + Aes128 = 7, // Reserved for AES with 128-bit key + Aes192 = 8, // Reserved for AES with 192-bit key + Aes256 = 9, // Reserved for AES with 256-bit key + Twofish = 10, // Reserved for Twofish + Camellia128 = 11, // Reserved for AES with 128-bit key + Camellia192 = 12, // Reserved for AES with 192-bit key + Camellia256 = 13 // Reserved for AES with 256-bit key + } +} diff --git a/bc-sharp-crypto/src/bcpg/SymmetricKeyEncSessionPacket.cs b/bc-sharp-crypto/src/bcpg/SymmetricKeyEncSessionPacket.cs new file mode 100644 index 0000000..0381fa3 --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/SymmetricKeyEncSessionPacket.cs @@ -0,0 +1,91 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Bcpg +{ + /** + * Basic type for a symmetric encrypted session key packet + */ + public class SymmetricKeyEncSessionPacket + : ContainedPacket + { + private int version; + private SymmetricKeyAlgorithmTag encAlgorithm; + private S2k s2k; + private readonly byte[] secKeyData; + + public SymmetricKeyEncSessionPacket( + BcpgInputStream bcpgIn) + { + version = bcpgIn.ReadByte(); + encAlgorithm = (SymmetricKeyAlgorithmTag) bcpgIn.ReadByte(); + + s2k = new S2k(bcpgIn); + + secKeyData = bcpgIn.ReadAll(); + } + + public SymmetricKeyEncSessionPacket( + SymmetricKeyAlgorithmTag encAlgorithm, + S2k s2k, + byte[] secKeyData) + { + this.version = 4; + this.encAlgorithm = encAlgorithm; + this.s2k = s2k; + this.secKeyData = secKeyData; + } + + /** + * @return int + */ + public SymmetricKeyAlgorithmTag EncAlgorithm + { + get { return encAlgorithm; } + } + + /** + * @return S2k + */ + public S2k S2k + { + get { return s2k; } + } + + /** + * @return byte[] + */ + public byte[] GetSecKeyData() + { + return secKeyData; + } + + /** + * @return int + */ + public int Version + { + get { return version; } + } + + public override void Encode( + BcpgOutputStream bcpgOut) + { + MemoryStream bOut = new MemoryStream(); + BcpgOutputStream pOut = new BcpgOutputStream(bOut); + + pOut.Write( + (byte) version, + (byte) encAlgorithm); + + pOut.WriteObject(s2k); + + if (secKeyData != null && secKeyData.Length > 0) + { + pOut.Write(secKeyData); + } + + bcpgOut.WritePacket(PacketTag.SymmetricKeyEncryptedSessionKey, bOut.ToArray(), true); + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/TrustPacket.cs b/bc-sharp-crypto/src/bcpg/TrustPacket.cs new file mode 100644 index 0000000..6f1969c --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/TrustPacket.cs @@ -0,0 +1,43 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Bcpg +{ + /// Basic type for a trust packet. + public class TrustPacket + : ContainedPacket + { + private readonly byte[] levelAndTrustAmount; + + public TrustPacket( + BcpgInputStream bcpgIn) + { + MemoryStream bOut = new MemoryStream(); + + int ch; + while ((ch = bcpgIn.ReadByte()) >= 0) + { + bOut.WriteByte((byte) ch); + } + + levelAndTrustAmount = bOut.ToArray(); + } + + public TrustPacket( + int trustCode) + { + this.levelAndTrustAmount = new byte[]{ (byte) trustCode }; + } + + public byte[] GetLevelAndTrustAmount() + { + return (byte[]) levelAndTrustAmount.Clone(); + } + + public override void Encode( + BcpgOutputStream bcpgOut) + { + bcpgOut.WritePacket(PacketTag.Trust, levelAndTrustAmount, true); + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/UserAttributePacket.cs b/bc-sharp-crypto/src/bcpg/UserAttributePacket.cs new file mode 100644 index 0000000..20e3598 --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/UserAttributePacket.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Bcpg +{ + /** + * Basic type for a user attribute packet. + */ + public class UserAttributePacket + : ContainedPacket + { + private readonly UserAttributeSubpacket[] subpackets; + + public UserAttributePacket( + BcpgInputStream bcpgIn) + { + UserAttributeSubpacketsParser sIn = new UserAttributeSubpacketsParser(bcpgIn); + UserAttributeSubpacket sub; + + IList v = Platform.CreateArrayList(); + while ((sub = sIn.ReadPacket()) != null) + { + v.Add(sub); + } + + subpackets = new UserAttributeSubpacket[v.Count]; + + for (int i = 0; i != subpackets.Length; i++) + { + subpackets[i] = (UserAttributeSubpacket)v[i]; + } + } + + public UserAttributePacket( + UserAttributeSubpacket[] subpackets) + { + this.subpackets = subpackets; + } + + public UserAttributeSubpacket[] GetSubpackets() + { + return subpackets; + } + + public override void Encode( + BcpgOutputStream bcpgOut) + { + MemoryStream bOut = new MemoryStream(); + + for (int i = 0; i != subpackets.Length; i++) + { + subpackets[i].Encode(bOut); + } + + bcpgOut.WritePacket(PacketTag.UserAttribute, bOut.ToArray(), false); + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/UserAttributeSubpacket.cs b/bc-sharp-crypto/src/bcpg/UserAttributeSubpacket.cs new file mode 100644 index 0000000..05f60ac --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/UserAttributeSubpacket.cs @@ -0,0 +1,90 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Bcpg +{ + /** + * Basic type for a user attribute sub-packet. + */ + public class UserAttributeSubpacket + { + internal readonly UserAttributeSubpacketTag type; + private readonly bool longLength; // we preserve this as not everyone encodes length properly. + protected readonly byte[] data; + + protected internal UserAttributeSubpacket(UserAttributeSubpacketTag type, byte[] data) + : this(type, false, data) + { + } + + protected internal UserAttributeSubpacket(UserAttributeSubpacketTag type, bool forceLongLength, byte[] data) + { + this.type = type; + this.longLength = forceLongLength; + this.data = data; + } + + public virtual UserAttributeSubpacketTag SubpacketType + { + get { return type; } + } + + /** + * return the generic data making up the packet. + */ + public virtual byte[] GetData() + { + return data; + } + + public virtual void Encode(Stream os) + { + int bodyLen = data.Length + 1; + + if (bodyLen < 192 && !longLength) + { + os.WriteByte((byte)bodyLen); + } + else if (bodyLen <= 8383 && !longLength) + { + bodyLen -= 192; + + os.WriteByte((byte)(((bodyLen >> 8) & 0xff) + 192)); + os.WriteByte((byte)bodyLen); + } + else + { + os.WriteByte(0xff); + os.WriteByte((byte)(bodyLen >> 24)); + os.WriteByte((byte)(bodyLen >> 16)); + os.WriteByte((byte)(bodyLen >> 8)); + os.WriteByte((byte)bodyLen); + } + + os.WriteByte((byte) type); + os.Write(data, 0, data.Length); + } + + public override bool Equals( + object obj) + { + if (obj == this) + return true; + + UserAttributeSubpacket other = obj as UserAttributeSubpacket; + + if (other == null) + return false; + + return type == other.type + && Arrays.AreEqual(data, other.data); + } + + public override int GetHashCode() + { + return type.GetHashCode() ^ Arrays.GetHashCode(data); + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/UserAttributeSubpacketTags.cs b/bc-sharp-crypto/src/bcpg/UserAttributeSubpacketTags.cs new file mode 100644 index 0000000..7a9cd1d --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/UserAttributeSubpacketTags.cs @@ -0,0 +1,10 @@ +namespace Org.BouncyCastle.Bcpg +{ + /** + * Basic PGP user attribute sub-packet tag types. + */ + public enum UserAttributeSubpacketTag + { + ImageAttribute = 1 + } +} diff --git a/bc-sharp-crypto/src/bcpg/UserAttributeSubpacketsReader.cs b/bc-sharp-crypto/src/bcpg/UserAttributeSubpacketsReader.cs new file mode 100644 index 0000000..f0cc1b8 --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/UserAttributeSubpacketsReader.cs @@ -0,0 +1,65 @@ +using System; +using System.IO; +using Org.BouncyCastle.Bcpg.Attr; +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Bcpg +{ + /** + * reader for user attribute sub-packets + */ + public class UserAttributeSubpacketsParser + { + private readonly Stream input; + + public UserAttributeSubpacketsParser( + Stream input) + { + this.input = input; + } + + public virtual UserAttributeSubpacket ReadPacket() + { + int l = input.ReadByte(); + if (l < 0) + return null; + + int bodyLen = 0; + bool longLength = false; + if (l < 192) + { + bodyLen = l; + } + else if (l <= 223) + { + bodyLen = ((l - 192) << 8) + (input.ReadByte()) + 192; + } + else if (l == 255) + { + bodyLen = (input.ReadByte() << 24) | (input.ReadByte() << 16) + | (input.ReadByte() << 8) | input.ReadByte(); + longLength = true; + } + else + { + throw new IOException("unrecognised length reading user attribute sub packet"); + } + + int tag = input.ReadByte(); + if (tag < 0) + throw new EndOfStreamException("unexpected EOF reading user attribute sub packet"); + + byte[] data = new byte[bodyLen - 1]; + if (Streams.ReadFully(input, data) < data.Length) + throw new EndOfStreamException(); + + UserAttributeSubpacketTag type = (UserAttributeSubpacketTag) tag; + switch (type) + { + case UserAttributeSubpacketTag.ImageAttribute: + return new ImageAttrib(longLength, data); + } + return new UserAttributeSubpacket(type, longLength, data); + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/UserIdPacket.cs b/bc-sharp-crypto/src/bcpg/UserIdPacket.cs new file mode 100644 index 0000000..a175e74 --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/UserIdPacket.cs @@ -0,0 +1,37 @@ +using System; +using System.Text; + +namespace Org.BouncyCastle.Bcpg +{ + /** + * Basic type for a user ID packet. + */ + public class UserIdPacket + : ContainedPacket + { + private readonly byte[] idData; + + public UserIdPacket( + BcpgInputStream bcpgIn) + { + this.idData = bcpgIn.ReadAll(); + } + + public UserIdPacket( + string id) + { + this.idData = Encoding.UTF8.GetBytes(id); + } + + public string GetId() + { + return Encoding.UTF8.GetString(idData, 0, idData.Length); + } + + public override void Encode( + BcpgOutputStream bcpgOut) + { + bcpgOut.WritePacket(PacketTag.UserId, idData, true); + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/attr/ImageAttrib.cs b/bc-sharp-crypto/src/bcpg/attr/ImageAttrib.cs new file mode 100644 index 0000000..2d0fef8 --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/attr/ImageAttrib.cs @@ -0,0 +1,72 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Bcpg.Attr +{ + /// Basic type for a image attribute packet. + public class ImageAttrib + : UserAttributeSubpacket + { + public enum Format : byte + { + Jpeg = 1 + } + + private static readonly byte[] Zeroes = new byte[12]; + + private int hdrLength; + private int _version; + private int _encoding; + private byte[] imageData; + + public ImageAttrib(byte[] data) + : this(false, data) + { + } + + public ImageAttrib(bool forceLongLength, byte[] data) + : base(UserAttributeSubpacketTag.ImageAttribute, forceLongLength, data) + { + hdrLength = ((data[1] & 0xff) << 8) | (data[0] & 0xff); + _version = data[2] & 0xff; + _encoding = data[3] & 0xff; + + imageData = new byte[data.Length - hdrLength]; + Array.Copy(data, hdrLength, imageData, 0, imageData.Length); + } + + public ImageAttrib( + Format imageType, + byte[] imageData) + : this(ToByteArray(imageType, imageData)) + { + } + + private static byte[] ToByteArray( + Format imageType, + byte[] imageData) + { + MemoryStream bOut = new MemoryStream(); + bOut.WriteByte(0x10); bOut.WriteByte(0x00); bOut.WriteByte(0x01); + bOut.WriteByte((byte) imageType); + bOut.Write(Zeroes, 0, Zeroes.Length); + bOut.Write(imageData, 0, imageData.Length); + return bOut.ToArray(); + } + + public virtual int Version + { + get { return _version; } + } + + public virtual int Encoding + { + get { return _encoding; } + } + + public virtual byte[] GetImageData() + { + return imageData; + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/sig/EmbeddedSignature.cs b/bc-sharp-crypto/src/bcpg/sig/EmbeddedSignature.cs new file mode 100644 index 0000000..fffdaef --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/sig/EmbeddedSignature.cs @@ -0,0 +1,19 @@ +using System; + +namespace Org.BouncyCastle.Bcpg.Sig +{ + /** + * Packet embedded signature + */ + public class EmbeddedSignature + : SignatureSubpacket + { + public EmbeddedSignature( + bool critical, + bool isLongLength, + byte[] data) + : base(SignatureSubpacketTag.EmbeddedSignature, critical, isLongLength, data) + { + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/sig/Exportable.cs b/bc-sharp-crypto/src/bcpg/sig/Exportable.cs new file mode 100644 index 0000000..4d03034 --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/sig/Exportable.cs @@ -0,0 +1,46 @@ +using System; + +namespace Org.BouncyCastle.Bcpg.Sig +{ + /** + * packet giving signature creation time. + */ + public class Exportable + : SignatureSubpacket + { + private static byte[] BooleanToByteArray(bool val) + { + byte[] data = new byte[1]; + + if (val) + { + data[0] = 1; + return data; + } + else + { + return data; + } + } + + public Exportable( + bool critical, + bool isLongLength, + byte[] data) + : base(SignatureSubpacketTag.Exportable, critical, isLongLength, data) + { + } + + public Exportable( + bool critical, + bool isExportable) + : base(SignatureSubpacketTag.Exportable, critical, false, BooleanToByteArray(isExportable)) + { + } + + public bool IsExportable() + { + return data[0] != 0; + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/sig/Features.cs b/bc-sharp-crypto/src/bcpg/sig/Features.cs new file mode 100644 index 0000000..2958423 --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/sig/Features.cs @@ -0,0 +1,75 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Bcpg.Sig +{ + /** + * packet giving signature expiration time. + */ + public class Features + : SignatureSubpacket + { + /** Identifier for the modification detection feature */ + public static readonly byte FEATURE_MODIFICATION_DETECTION = 1; + + private static byte[] FeatureToByteArray(byte feature) + { + return new byte[]{ feature }; + } + + public Features( + bool critical, + bool isLongLength, + byte[] data) + : base(SignatureSubpacketTag.Features, critical, isLongLength, data) + { + } + + public Features(bool critical, byte feature) + : base(SignatureSubpacketTag.Features, critical, false, FeatureToByteArray(feature)) + { + } + + /** + * Returns if modification detection is supported. + */ + public bool SupportsModificationDetection + { + get { return SupportsFeature(FEATURE_MODIFICATION_DETECTION); } + } + + /** + * Returns if a particular feature is supported. + */ + public bool SupportsFeature(byte feature) + { + return Array.IndexOf(data, feature) >= 0; + } + + /** + * Sets support for a particular feature. + */ + private void SetSupportsFeature(byte feature, bool support) + { + if (feature == 0) + throw new ArgumentException("cannot be 0", "feature"); + + int i = Array.IndexOf(data, feature); + if ((i >= 0) == support) + return; + + if (support) + { + data = Arrays.Append(data, feature); + } + else + { + byte[] temp = new byte[data.Length - 1]; + Array.Copy(data, 0, temp, 0, i); + Array.Copy(data, i + 1, temp, i, temp.Length - i); + data = temp; + } + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/sig/IssuerKeyId.cs b/bc-sharp-crypto/src/bcpg/sig/IssuerKeyId.cs new file mode 100644 index 0000000..627ea3e --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/sig/IssuerKeyId.cs @@ -0,0 +1,62 @@ +using System; + + + +namespace Org.BouncyCastle.Bcpg.Sig +{ + /** + * packet giving signature creation time. + */ + public class IssuerKeyId + : 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( + bool critical, + bool isLongLength, + byte[] data) + : base(SignatureSubpacketTag.IssuerKeyId, critical, isLongLength, data) + { + } + + public IssuerKeyId( + bool critical, + long keyId) + : base(SignatureSubpacketTag.IssuerKeyId, critical, false, KeyIdToBytes(keyId)) + { + } + + public long KeyId + { + get + { + 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) + | ((long)(data[5] & 0xff) << 16) + | ((long)(data[6] & 0xff) << 8) + | ((long)data[7] & 0xff); + + return keyId; + } + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/sig/KeyExpirationTime.cs b/bc-sharp-crypto/src/bcpg/sig/KeyExpirationTime.cs new file mode 100644 index 0000000..dfd3e76 --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/sig/KeyExpirationTime.cs @@ -0,0 +1,55 @@ +using System; + +namespace Org.BouncyCastle.Bcpg.Sig +{ + /** + * packet giving time after creation at which the key expires. + */ + public class KeyExpirationTime + : 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( + bool critical, + bool isLongLength, + byte[] data) + : base(SignatureSubpacketTag.KeyExpireTime, critical, isLongLength, data) + { + } + + public KeyExpirationTime( + bool critical, + long seconds) + : base(SignatureSubpacketTag.KeyExpireTime, critical, false, TimeToBytes(seconds)) + { + } + + /** + * Return the number of seconds after creation time a key is valid for. + * + * @return second count for key validity. + */ + public long Time + { + get + { + long time = ((long)(data[0] & 0xff) << 24) | ((long)(data[1] & 0xff) << 16) + | ((long)(data[2] & 0xff) << 8) | ((long)data[3] & 0xff); + + return time; + } + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/sig/KeyFlags.cs b/bc-sharp-crypto/src/bcpg/sig/KeyFlags.cs new file mode 100644 index 0000000..5b5d85a --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/sig/KeyFlags.cs @@ -0,0 +1,75 @@ +using System; + +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Bcpg.Sig +{ + /** + * Packet holding the key flag values. + */ + public class KeyFlags + : SignatureSubpacket + { + public const int CertifyOther = 0x01; + public const int SignData = 0x02; + public const int EncryptComms = 0x04; + public const int EncryptStorage = 0x08; + public const int Split = 0x10; + public const int Authentication = 0x20; + public const 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]; + Array.Copy(tmp, 0, data, 0, data.Length); + return data; + } + + public KeyFlags( + bool critical, + bool isLongLength, + byte[] data) + : base(SignatureSubpacketTag.KeyFlags, critical, isLongLength, data) + { + } + + public KeyFlags( + bool critical, + int flags) + : base(SignatureSubpacketTag.KeyFlags, critical, false, IntToByteArray(flags)) + { + } + + /// + /// Return the flag values contained in the first 4 octets (note: at the moment + /// the standard only uses the first one). + /// + public int Flags + { + get + { + int flags = 0; + + for (int i = 0; i != data.Length; i++) + { + flags |= (data[i] & 0xff) << (i * 8); + } + + return flags; + } + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/sig/NotationData.cs b/bc-sharp-crypto/src/bcpg/sig/NotationData.cs new file mode 100644 index 0000000..9ac6f89 --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/sig/NotationData.cs @@ -0,0 +1,113 @@ +using System; +using System.IO; +using System.Text; + +namespace Org.BouncyCastle.Bcpg.Sig +{ + /** + * Class provided a NotationData object according to + * RFC2440, Chapter 5.2.3.15. Notation Data + */ + public class NotationData + : SignatureSubpacket + { + public const int HeaderFlagLength = 4; + public const int HeaderNameLength = 2; + public const int HeaderValueLength = 2; + + public NotationData( + bool critical, + bool isLongLength, + byte[] data) + : base(SignatureSubpacketTag.NotationData, critical, isLongLength, data) + { + } + + public NotationData( + bool critical, + bool humanReadable, + string notationName, + string notationValue) + : base(SignatureSubpacketTag.NotationData, critical, false, + CreateData(humanReadable, notationName, notationValue)) + { + } + + private static byte[] CreateData( + bool humanReadable, + string notationName, + string notationValue) + { + MemoryStream os = new MemoryStream(); + + // (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 + os.WriteByte(humanReadable ? (byte)0x80 : (byte)0x00); + os.WriteByte(0x0); + os.WriteByte(0x0); + os.WriteByte(0x0); + + byte[] nameData, valueData = null; + int nameLength, valueLength; + + nameData = Encoding.UTF8.GetBytes(notationName); + nameLength = System.Math.Min(nameData.Length, 0xFF); + + valueData = Encoding.UTF8.GetBytes(notationValue); + valueLength = System.Math.Min(valueData.Length, 0xFF); + + // name length + os.WriteByte((byte)(nameLength >> 8)); + os.WriteByte((byte)(nameLength >> 0)); + + // value length + os.WriteByte((byte)(valueLength >> 8)); + os.WriteByte((byte)(valueLength >> 0)); + + // name + os.Write(nameData, 0, nameLength); + + // value + os.Write(valueData, 0, valueLength); + + return os.ToArray(); + } + + public bool IsHumanReadable + { + get { return data[0] == (byte)0x80; } + } + + public string GetNotationName() + { + int nameLength = ((data[HeaderFlagLength] << 8) + (data[HeaderFlagLength + 1] << 0)); + int namePos = HeaderFlagLength + HeaderNameLength + HeaderValueLength; + + return Encoding.UTF8.GetString(data, namePos, nameLength); + } + + public string GetNotationValue() + { + int nameLength = ((data[HeaderFlagLength] << 8) + (data[HeaderFlagLength + 1] << 0)); + int valueLength = ((data[HeaderFlagLength + HeaderNameLength] << 8) + (data[HeaderFlagLength + HeaderNameLength + 1] << 0)); + int valuePos = HeaderFlagLength + HeaderNameLength + HeaderValueLength + nameLength; + + return Encoding.UTF8.GetString(data, valuePos, valueLength); + } + + public byte[] GetNotationValueBytes() + { + int nameLength = ((data[HeaderFlagLength] << 8) + (data[HeaderFlagLength + 1] << 0)); + int valueLength = ((data[HeaderFlagLength + HeaderNameLength] << 8) + (data[HeaderFlagLength + HeaderNameLength + 1] << 0)); + int valuePos = HeaderFlagLength + HeaderNameLength + HeaderValueLength + nameLength; + + byte[] bytes = new byte[valueLength]; + Array.Copy(data, valuePos, bytes, 0, valueLength); + return bytes; + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/sig/PreferredAlgorithms.cs b/bc-sharp-crypto/src/bcpg/sig/PreferredAlgorithms.cs new file mode 100644 index 0000000..9514bed --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/sig/PreferredAlgorithms.cs @@ -0,0 +1,53 @@ +using System; + +namespace Org.BouncyCastle.Bcpg.Sig +{ + /** + * packet giving signature creation time. + */ + public class PreferredAlgorithms + : 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( + SignatureSubpacketTag type, + bool critical, + bool isLongLength, + byte[] data) + : base(type, critical, isLongLength, data) + { + } + + public PreferredAlgorithms( + SignatureSubpacketTag type, + bool critical, + int[] preferences) + : base(type, critical, false, IntToByteArray(preferences)) + { + } + + 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/bc-sharp-crypto/src/bcpg/sig/PrimaryUserId.cs b/bc-sharp-crypto/src/bcpg/sig/PrimaryUserId.cs new file mode 100644 index 0000000..1f16f40 --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/sig/PrimaryUserId.cs @@ -0,0 +1,47 @@ +using System; + +namespace Org.BouncyCastle.Bcpg.Sig +{ + /** + * packet giving whether or not the signature is signed using the primary user ID for the key. + */ + public class PrimaryUserId + : SignatureSubpacket + { + private static byte[] BooleanToByteArray( + bool val) + { + byte[] data = new byte[1]; + + if (val) + { + data[0] = 1; + return data; + } + else + { + return data; + } + } + + public PrimaryUserId( + bool critical, + bool isLongLength, + byte[] data) + : base(SignatureSubpacketTag.PrimaryUserId, critical, isLongLength, data) + { + } + + public PrimaryUserId( + bool critical, + bool isPrimaryUserId) + : base(SignatureSubpacketTag.PrimaryUserId, critical, false, BooleanToByteArray(isPrimaryUserId)) + { + } + + public bool IsPrimaryUserId() + { + return data[0] != 0; + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/sig/Revocable.cs b/bc-sharp-crypto/src/bcpg/sig/Revocable.cs new file mode 100644 index 0000000..7aa9139 --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/sig/Revocable.cs @@ -0,0 +1,47 @@ +using System; + +namespace Org.BouncyCastle.Bcpg.Sig +{ + /** + * packet giving whether or not is revocable. + */ + public class Revocable + : SignatureSubpacket + { + private static byte[] BooleanToByteArray( + bool value) + { + byte[] data = new byte[1]; + + if (value) + { + data[0] = 1; + return data; + } + else + { + return data; + } + } + + public Revocable( + bool critical, + bool isLongLength, + byte[] data) + : base(SignatureSubpacketTag.Revocable, critical, isLongLength, data) + { + } + + public Revocable( + bool critical, + bool isRevocable) + : base(SignatureSubpacketTag.Revocable, critical, false, BooleanToByteArray(isRevocable)) + { + } + + public bool IsRevocable() + { + return data[0] != 0; + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/sig/RevocationKey.cs b/bc-sharp-crypto/src/bcpg/sig/RevocationKey.cs new file mode 100644 index 0000000..11467d2 --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/sig/RevocationKey.cs @@ -0,0 +1,63 @@ +using System; +using System.Text; + +namespace Org.BouncyCastle.Bcpg +{ + /// + /// Represents revocation key OpenPGP signature sub packet. + /// + public class RevocationKey + : SignatureSubpacket + { + // 1 octet of class, + // 1 octet of public-key algorithm ID, + // 20 octets of fingerprint + public RevocationKey( + bool isCritical, + bool isLongLength, + byte[] data) + : base(SignatureSubpacketTag.RevocationKey, isCritical, isLongLength, data) + { + } + + public RevocationKey( + bool isCritical, + RevocationKeyTag signatureClass, + PublicKeyAlgorithmTag keyAlgorithm, + byte[] fingerprint) + : base(SignatureSubpacketTag.RevocationKey, isCritical, false, + CreateData(signatureClass, keyAlgorithm, fingerprint)) + { + } + + private static byte[] CreateData( + RevocationKeyTag signatureClass, + PublicKeyAlgorithmTag keyAlgorithm, + byte[] fingerprint) + { + byte[] data = new byte[2 + fingerprint.Length]; + data[0] = (byte)signatureClass; + data[1] = (byte)keyAlgorithm; + Array.Copy(fingerprint, 0, data, 2, fingerprint.Length); + return data; + } + + public virtual RevocationKeyTag SignatureClass + { + get { return (RevocationKeyTag)this.GetData()[0]; } + } + + public virtual PublicKeyAlgorithmTag Algorithm + { + get { return (PublicKeyAlgorithmTag)this.GetData()[1]; } + } + + public virtual byte[] GetFingerprint() + { + byte[] data = this.GetData(); + byte[] fingerprint = new byte[data.Length - 2]; + Array.Copy(data, 2, fingerprint, 0, fingerprint.Length); + return fingerprint; + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/sig/RevocationKeyTags.cs b/bc-sharp-crypto/src/bcpg/sig/RevocationKeyTags.cs new file mode 100644 index 0000000..d76d1dc --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/sig/RevocationKeyTags.cs @@ -0,0 +1,9 @@ +namespace Org.BouncyCastle.Bcpg +{ + public enum RevocationKeyTag + : byte + { + ClassDefault = 0x80, + ClassSensitive = 0x40 + } +} diff --git a/bc-sharp-crypto/src/bcpg/sig/RevocationReason.cs b/bc-sharp-crypto/src/bcpg/sig/RevocationReason.cs new file mode 100644 index 0000000..42afd5f --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/sig/RevocationReason.cs @@ -0,0 +1,59 @@ +using System; +using System.Text; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Bcpg +{ + /// + /// Represents revocation reason OpenPGP signature sub packet. + /// + public class RevocationReason + : SignatureSubpacket + { + public RevocationReason(bool isCritical, bool isLongLength, byte[] data) + : base(SignatureSubpacketTag.RevocationReason, isCritical, isLongLength, data) + { + } + + public RevocationReason( + bool isCritical, + RevocationReasonTag reason, + string description) + : base(SignatureSubpacketTag.RevocationReason, isCritical, false, CreateData(reason, description)) + { + } + + private static byte[] CreateData( + RevocationReasonTag reason, + string description) + { + byte[] descriptionBytes = Strings.ToUtf8ByteArray(description); + byte[] data = new byte[1 + descriptionBytes.Length]; + + data[0] = (byte)reason; + Array.Copy(descriptionBytes, 0, data, 1, descriptionBytes.Length); + + return data; + } + + public virtual RevocationReasonTag GetRevocationReason() + { + return (RevocationReasonTag)GetData()[0]; + } + + public virtual string GetRevocationDescription() + { + byte[] data = GetData(); + if (data.Length == 1) + { + return string.Empty; + } + + byte[] description = new byte[data.Length - 1]; + Array.Copy(data, 1, description, 0, description.Length); + + return Strings.FromUtf8ByteArray(description); + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/sig/RevocationReasonTags.cs b/bc-sharp-crypto/src/bcpg/sig/RevocationReasonTags.cs new file mode 100644 index 0000000..524a58c --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/sig/RevocationReasonTags.cs @@ -0,0 +1,14 @@ +namespace Org.BouncyCastle.Bcpg +{ + public enum RevocationReasonTag + : byte + { + NoReason = 0, // No reason specified (key revocations or cert revocations) + KeySuperseded = 1, // Key is superseded (key revocations) + KeyCompromised = 2, // Key material has been compromised (key revocations) + KeyRetired = 3, // Key is retired and no longer used (key revocations) + UserNoLongerValid = 32, // User ID information is no longer valid (cert revocations) + + // 100-110 - Private Use + } +} diff --git a/bc-sharp-crypto/src/bcpg/sig/SignatureCreationTime.cs b/bc-sharp-crypto/src/bcpg/sig/SignatureCreationTime.cs new file mode 100644 index 0000000..d172e5d --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/sig/SignatureCreationTime.cs @@ -0,0 +1,51 @@ +using System; + +using Org.BouncyCastle.Utilities.Date; + +namespace Org.BouncyCastle.Bcpg.Sig +{ + /** + * packet giving signature creation time. + */ + public class SignatureCreationTime + : SignatureSubpacket + { + protected static byte[] TimeToBytes( + DateTime time) + { + long t = DateTimeUtilities.DateTimeToUnixMs(time) / 1000L; + 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 SignatureCreationTime( + bool critical, + bool isLongLength, + byte[] data) + : base(SignatureSubpacketTag.CreationTime, critical, isLongLength, data) + { + } + + public SignatureCreationTime( + bool critical, + DateTime date) + : base(SignatureSubpacketTag.CreationTime, critical, false, TimeToBytes(date)) + { + } + + public DateTime GetTime() + { + long time = (long)( + ((uint)data[0] << 24) + | ((uint)data[1] << 16) + | ((uint)data[2] << 8) + | ((uint)data[3]) + ); + return DateTimeUtilities.UnixMsToDateTime(time * 1000L); + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/sig/SignatureExpirationTime.cs b/bc-sharp-crypto/src/bcpg/sig/SignatureExpirationTime.cs new file mode 100644 index 0000000..24f0a9f --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/sig/SignatureExpirationTime.cs @@ -0,0 +1,51 @@ +using System; + +namespace Org.BouncyCastle.Bcpg.Sig +{ + /** + * packet giving signature expiration time. + */ + public class SignatureExpirationTime + : 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( + bool critical, + bool isLongLength, + byte[] data) + : base(SignatureSubpacketTag.ExpireTime, critical, isLongLength, data) + { + } + + public SignatureExpirationTime( + bool critical, + long seconds) + : base(SignatureSubpacketTag.ExpireTime, critical, false, TimeToBytes(seconds)) + { + } + + /** + * return time in seconds before signature expires after creation time. + */ + public long Time + { + get + { + long time = ((long)(data[0] & 0xff) << 24) | ((long)(data[1] & 0xff) << 16) + | ((long)(data[2] & 0xff) << 8) | ((long)data[3] & 0xff); + + return time; + } + } + } +} diff --git a/bc-sharp-crypto/src/bcpg/sig/SignerUserId.cs b/bc-sharp-crypto/src/bcpg/sig/SignerUserId.cs new file mode 100644 index 0000000..8ab62ed --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/sig/SignerUserId.cs @@ -0,0 +1,53 @@ +using System; + + + +namespace Org.BouncyCastle.Bcpg.Sig +{ + /** + * packet giving the User ID of the signer. + */ + public class SignerUserId + : 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[i]; + } + + return idData; + } + + public SignerUserId( + bool critical, + bool isLongLength, + byte[] data) + : base(SignatureSubpacketTag.SignerUserId, critical, isLongLength, data) + { + } + + public SignerUserId( + bool critical, + string userId) + : base(SignatureSubpacketTag.SignerUserId, critical, false, 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/bc-sharp-crypto/src/bcpg/sig/TrustSignature.cs b/bc-sharp-crypto/src/bcpg/sig/TrustSignature.cs new file mode 100644 index 0000000..9145882 --- /dev/null +++ b/bc-sharp-crypto/src/bcpg/sig/TrustSignature.cs @@ -0,0 +1,44 @@ +using System; + +namespace Org.BouncyCastle.Bcpg.Sig +{ + /** + * packet giving trust. + */ + public class TrustSignature + : SignatureSubpacket + { + private static byte[] IntToByteArray( + int v1, + int v2) + { + return new byte[]{ (byte)v1, (byte)v2 }; + } + + public TrustSignature( + bool critical, + bool isLongLength, + byte[] data) + : base(SignatureSubpacketTag.TrustSig, critical, isLongLength, data) + { + } + + public TrustSignature( + bool critical, + int depth, + int trustAmount) + : base(SignatureSubpacketTag.TrustSig, critical, false, IntToByteArray(depth, trustAmount)) + { + } + + public int Depth + { + get { return data[0] & 0xff; } + } + + public int TrustAmount + { + get { return data[1] & 0xff; } + } + } +} diff --git a/bc-sharp-crypto/src/cms/BaseDigestCalculator.cs b/bc-sharp-crypto/src/cms/BaseDigestCalculator.cs new file mode 100644 index 0000000..3dcbca7 --- /dev/null +++ b/bc-sharp-crypto/src/cms/BaseDigestCalculator.cs @@ -0,0 +1,23 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Cms +{ + internal class BaseDigestCalculator + : IDigestCalculator + { + private readonly byte[] digest; + + internal BaseDigestCalculator( + byte[] digest) + { + this.digest = digest; + } + + public byte[] GetDigest() + { + return Arrays.Clone(digest); + } + } +} diff --git a/bc-sharp-crypto/src/cms/CMSAttributeTableGenerationException.cs b/bc-sharp-crypto/src/cms/CMSAttributeTableGenerationException.cs new file mode 100644 index 0000000..87dad99 --- /dev/null +++ b/bc-sharp-crypto/src/cms/CMSAttributeTableGenerationException.cs @@ -0,0 +1,28 @@ +using System; + +namespace Org.BouncyCastle.Cms +{ +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT || PORTABLE) + [Serializable] +#endif + public class CmsAttributeTableGenerationException + : CmsException + { + public CmsAttributeTableGenerationException() + { + } + + public CmsAttributeTableGenerationException( + string name) + : base(name) + { + } + + public CmsAttributeTableGenerationException( + string name, + Exception e) + : base(name, e) + { + } + } +} diff --git a/bc-sharp-crypto/src/cms/CMSAttributeTableGenerator.cs b/bc-sharp-crypto/src/cms/CMSAttributeTableGenerator.cs new file mode 100644 index 0000000..92c9a29 --- /dev/null +++ b/bc-sharp-crypto/src/cms/CMSAttributeTableGenerator.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1.Cms; + +namespace Org.BouncyCastle.Cms +{ + /// + /// The 'Signature' parameter is only available when generating unsigned attributes. + /// + public enum CmsAttributeTableParameter + { +// const string ContentType = "contentType"; +// const string Digest = "digest"; +// const string Signature = "encryptedDigest"; +// const string DigestAlgorithmIdentifier = "digestAlgID"; + + ContentType, Digest, Signature, DigestAlgorithmIdentifier + } + + public interface CmsAttributeTableGenerator + { + AttributeTable GetAttributes(IDictionary parameters); + } +} diff --git a/bc-sharp-crypto/src/cms/CMSAuthEnvelopedData.cs b/bc-sharp-crypto/src/cms/CMSAuthEnvelopedData.cs new file mode 100644 index 0000000..d35e946 --- /dev/null +++ b/bc-sharp-crypto/src/cms/CMSAuthEnvelopedData.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Cms; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto.Parameters; + +namespace Org.BouncyCastle.Cms +{ + /** + * containing class for an CMS AuthEnveloped Data object + */ + internal class CmsAuthEnvelopedData + { + internal RecipientInformationStore recipientInfoStore; + internal ContentInfo contentInfo; + + private OriginatorInfo originator; + private AlgorithmIdentifier authEncAlg; + private Asn1Set authAttrs; + private byte[] mac; + private Asn1Set unauthAttrs; + + public CmsAuthEnvelopedData( + byte[] authEnvData) + : this(CmsUtilities.ReadContentInfo(authEnvData)) + { + } + + public CmsAuthEnvelopedData( + Stream authEnvData) + : this(CmsUtilities.ReadContentInfo(authEnvData)) + { + } + + public CmsAuthEnvelopedData( + ContentInfo contentInfo) + { + this.contentInfo = contentInfo; + + AuthEnvelopedData authEnvData = AuthEnvelopedData.GetInstance(contentInfo.Content); + + this.originator = authEnvData.OriginatorInfo; + + // + // read the recipients + // + Asn1Set recipientInfos = authEnvData.RecipientInfos; + + // + // read the auth-encrypted content info + // + EncryptedContentInfo authEncInfo = authEnvData.AuthEncryptedContentInfo; + this.authEncAlg = authEncInfo.ContentEncryptionAlgorithm; + CmsSecureReadable secureReadable = new AuthEnvelopedSecureReadable(this); + + // + // build the RecipientInformationStore + // + this.recipientInfoStore = CmsEnvelopedHelper.BuildRecipientInformationStore( + recipientInfos, secureReadable); + + // FIXME These need to be passed to the AEAD cipher as AAD (Additional Authenticated Data) + this.authAttrs = authEnvData.AuthAttrs; + this.mac = authEnvData.Mac.GetOctets(); + this.unauthAttrs = authEnvData.UnauthAttrs; + } + + private class AuthEnvelopedSecureReadable : CmsSecureReadable + { + private readonly CmsAuthEnvelopedData parent; + + internal AuthEnvelopedSecureReadable(CmsAuthEnvelopedData parent) + { + this.parent = parent; + } + + public AlgorithmIdentifier Algorithm + { + get { return parent.authEncAlg; } + } + + public object CryptoObject + { + get { return null; } + } + + public CmsReadable GetReadable(KeyParameter key) + { + // TODO Create AEAD cipher instance to decrypt and calculate tag ( MAC) + throw new CmsException("AuthEnveloped data decryption not yet implemented"); + +// RFC 5084 ASN.1 Module +// -- Parameters for AlgorithmIdentifier +// +// CCMParameters ::= SEQUENCE { +// aes-nonce OCTET STRING (SIZE(7..13)), +// aes-ICVlen AES-CCM-ICVlen DEFAULT 12 } +// +// AES-CCM-ICVlen ::= INTEGER (4 | 6 | 8 | 10 | 12 | 14 | 16) +// +// GCMParameters ::= SEQUENCE { +// aes-nonce OCTET STRING, -- recommended size is 12 octets +// aes-ICVlen AES-GCM-ICVlen DEFAULT 12 } +// +// AES-GCM-ICVlen ::= INTEGER (12 | 13 | 14 | 15 | 16) + } + } + } +} diff --git a/bc-sharp-crypto/src/cms/CMSAuthEnvelopedGenerator.cs b/bc-sharp-crypto/src/cms/CMSAuthEnvelopedGenerator.cs new file mode 100644 index 0000000..4273cff --- /dev/null +++ b/bc-sharp-crypto/src/cms/CMSAuthEnvelopedGenerator.cs @@ -0,0 +1,16 @@ +using System; + +using Org.BouncyCastle.Asn1.Nist; + +namespace Org.BouncyCastle.Cms +{ + internal class CmsAuthEnvelopedGenerator + { + public static readonly string Aes128Ccm = NistObjectIdentifiers.IdAes128Ccm.Id; + public static readonly string Aes192Ccm = NistObjectIdentifiers.IdAes192Ccm.Id; + public static readonly string Aes256Ccm = NistObjectIdentifiers.IdAes256Ccm.Id; + public static readonly string Aes128Gcm = NistObjectIdentifiers.IdAes128Gcm.Id; + public static readonly string Aes192Gcm = NistObjectIdentifiers.IdAes192Gcm.Id; + public static readonly string Aes256Gcm = NistObjectIdentifiers.IdAes256Gcm.Id; + } +} diff --git a/bc-sharp-crypto/src/cms/CMSAuthenticatedData.cs b/bc-sharp-crypto/src/cms/CMSAuthenticatedData.cs new file mode 100644 index 0000000..33b4cc2 --- /dev/null +++ b/bc-sharp-crypto/src/cms/CMSAuthenticatedData.cs @@ -0,0 +1,137 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Cms; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Cms +{ + /** + * containing class for an CMS Authenticated Data object + */ + public class CmsAuthenticatedData + { + internal RecipientInformationStore recipientInfoStore; + internal ContentInfo contentInfo; + + private AlgorithmIdentifier macAlg; + private Asn1Set authAttrs; + private Asn1Set unauthAttrs; + private byte[] mac; + + public CmsAuthenticatedData( + byte[] authData) + : this(CmsUtilities.ReadContentInfo(authData)) + { + } + + public CmsAuthenticatedData( + Stream authData) + : this(CmsUtilities.ReadContentInfo(authData)) + { + } + + public CmsAuthenticatedData( + ContentInfo contentInfo) + { + this.contentInfo = contentInfo; + + AuthenticatedData authData = AuthenticatedData.GetInstance(contentInfo.Content); + + // + // read the recipients + // + Asn1Set recipientInfos = authData.RecipientInfos; + + this.macAlg = authData.MacAlgorithm; + + // + // read the authenticated content info + // + ContentInfo encInfo = authData.EncapsulatedContentInfo; + CmsReadable readable = new CmsProcessableByteArray( + Asn1OctetString.GetInstance(encInfo.Content).GetOctets()); + CmsSecureReadable secureReadable = new CmsEnvelopedHelper.CmsAuthenticatedSecureReadable( + this.macAlg, readable); + + // + // build the RecipientInformationStore + // + this.recipientInfoStore = CmsEnvelopedHelper.BuildRecipientInformationStore( + recipientInfos, secureReadable); + + this.authAttrs = authData.AuthAttrs; + this.mac = authData.Mac.GetOctets(); + this.unauthAttrs = authData.UnauthAttrs; + } + + public byte[] GetMac() + { + return Arrays.Clone(mac); + } + + public AlgorithmIdentifier MacAlgorithmID + { + get { return macAlg; } + } + + /** + * return the object identifier for the content MAC algorithm. + */ + public string MacAlgOid + { + get { return macAlg.Algorithm.Id; } + } + + /** + * return a store of the intended recipients for this message + */ + public RecipientInformationStore GetRecipientInfos() + { + return recipientInfoStore; + } + + /** + * return the ContentInfo + */ + public ContentInfo ContentInfo + { + get { return contentInfo; } + } + + /** + * return a table of the digested attributes indexed by + * the OID of the attribute. + */ + public Asn1.Cms.AttributeTable GetAuthAttrs() + { + if (authAttrs == null) + return null; + + return new Asn1.Cms.AttributeTable(authAttrs); + } + + /** + * return a table of the undigested attributes indexed by + * the OID of the attribute. + */ + public Asn1.Cms.AttributeTable GetUnauthAttrs() + { + if (unauthAttrs == null) + return null; + + return new Asn1.Cms.AttributeTable(unauthAttrs); + } + + /** + * return the ASN.1 encoded representation of this object. + */ + public byte[] GetEncoded() + { + return contentInfo.GetEncoded(); + } + } +} diff --git a/bc-sharp-crypto/src/cms/CMSAuthenticatedDataGenerator.cs b/bc-sharp-crypto/src/cms/CMSAuthenticatedDataGenerator.cs new file mode 100644 index 0000000..131a475 --- /dev/null +++ b/bc-sharp-crypto/src/cms/CMSAuthenticatedDataGenerator.cs @@ -0,0 +1,156 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Cms; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.IO; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Cms +{ + /** + * General class for generating a CMS authenticated-data message. + * + * A simple example of usage. + * + *
+	 *      CMSAuthenticatedDataGenerator  fact = new CMSAuthenticatedDataGenerator();
+	 *
+	 *      fact.addKeyTransRecipient(cert);
+	 *
+	 *      CMSAuthenticatedData         data = fact.generate(content, algorithm, "BC");
+	 * 
+ */ + public class CmsAuthenticatedDataGenerator + : CmsAuthenticatedGenerator + { + /** + * base constructor + */ + public CmsAuthenticatedDataGenerator() + { + } + + /** + * constructor allowing specific source of randomness + * @param rand instance of SecureRandom to use + */ + public CmsAuthenticatedDataGenerator( + SecureRandom rand) + : base(rand) + { + } + + /** + * generate an enveloped object that contains an CMS Enveloped Data + * object using the given provider and the passed in key generator. + */ + private CmsAuthenticatedData Generate( + CmsProcessable content, + string macOid, + CipherKeyGenerator keyGen) + { + AlgorithmIdentifier macAlgId; + KeyParameter encKey; + Asn1OctetString encContent; + Asn1OctetString macResult; + + try + { + // FIXME Will this work for macs? + byte[] encKeyBytes = keyGen.GenerateKey(); + encKey = ParameterUtilities.CreateKeyParameter(macOid, encKeyBytes); + + Asn1Encodable asn1Params = GenerateAsn1Parameters(macOid, encKeyBytes); + + ICipherParameters cipherParameters; + macAlgId = GetAlgorithmIdentifier( + macOid, encKey, asn1Params, out cipherParameters); + + IMac mac = MacUtilities.GetMac(macOid); + // TODO Confirm no ParametersWithRandom needed + // FIXME Only passing key at the moment +// mac.Init(cipherParameters); + mac.Init(encKey); + + MemoryStream bOut = new MemoryStream(); + Stream mOut = new TeeOutputStream(bOut, new MacOutputStream(mac)); + + content.Write(mOut); + + Platform.Dispose(mOut); + + encContent = new BerOctetString(bOut.ToArray()); + + byte[] macOctets = MacUtilities.DoFinal(mac); + macResult = new DerOctetString(macOctets); + } + catch (SecurityUtilityException e) + { + throw new CmsException("couldn't create cipher.", e); + } + catch (InvalidKeyException e) + { + throw new CmsException("key invalid in message.", e); + } + catch (IOException e) + { + throw new CmsException("exception decoding algorithm parameters.", e); + } + + Asn1EncodableVector recipientInfos = new Asn1EncodableVector(); + + foreach (RecipientInfoGenerator rig in recipientInfoGenerators) + { + try + { + recipientInfos.Add(rig.Generate(encKey, rand)); + } + catch (InvalidKeyException e) + { + throw new CmsException("key inappropriate for algorithm.", e); + } + catch (GeneralSecurityException e) + { + throw new CmsException("error making encrypted content.", e); + } + } + + ContentInfo eci = new ContentInfo(CmsObjectIdentifiers.Data, encContent); + + ContentInfo contentInfo = new ContentInfo( + CmsObjectIdentifiers.AuthenticatedData, + new AuthenticatedData(null, new DerSet(recipientInfos), macAlgId, null, eci, null, macResult, null)); + + return new CmsAuthenticatedData(contentInfo); + } + + /** + * generate an authenticated object that contains an CMS Authenticated Data object + */ + public CmsAuthenticatedData Generate( + CmsProcessable content, + string encryptionOid) + { + try + { + // FIXME Will this work for macs? + CipherKeyGenerator keyGen = GeneratorUtilities.GetKeyGenerator(encryptionOid); + + keyGen.Init(new KeyGenerationParameters(rand, keyGen.DefaultStrength)); + + return Generate(content, encryptionOid, keyGen); + } + catch (SecurityUtilityException e) + { + throw new CmsException("can't find key generation algorithm.", e); + } + } + } +} diff --git a/bc-sharp-crypto/src/cms/CMSAuthenticatedDataParser.cs b/bc-sharp-crypto/src/cms/CMSAuthenticatedDataParser.cs new file mode 100644 index 0000000..7defafc --- /dev/null +++ b/bc-sharp-crypto/src/cms/CMSAuthenticatedDataParser.cs @@ -0,0 +1,214 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Cms; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Cms +{ + /** + * Parsing class for an CMS Authenticated Data object from an input stream. + *

+ * Note: that because we are in a streaming mode only one recipient can be tried and it is important + * that the methods on the parser are called in the appropriate order. + *

+ *

+ * Example of use - assuming the first recipient matches the private key we have. + *

+	*      CMSAuthenticatedDataParser     ad = new CMSAuthenticatedDataParser(inputStream);
+	*
+	*      RecipientInformationStore  recipients = ad.getRecipientInfos();
+	*
+	*      Collection  c = recipients.getRecipients();
+	*      Iterator    it = c.iterator();
+	*
+	*      if (it.hasNext())
+	*      {
+	*          RecipientInformation   recipient = (RecipientInformation)it.next();
+	*
+	*          CMSTypedStream recData = recipient.getContentStream(privateKey, "BC");
+	*
+	*          processDataStream(recData.getContentStream());
+	*
+	*          if (!Arrays.equals(ad.getMac(), recipient.getMac())
+	*          {
+	*              System.err.println("Data corrupted!!!!");
+	*          }
+	*      }
+	*  
+ * Note: this class does not introduce buffering - if you are processing large files you should create + * the parser with: + *
+	*          CMSAuthenticatedDataParser     ep = new CMSAuthenticatedDataParser(new BufferedInputStream(inputStream, bufSize));
+	*  
+ * where bufSize is a suitably large buffer size. + *

+ */ + public class CmsAuthenticatedDataParser + : CmsContentInfoParser + { + internal RecipientInformationStore _recipientInfoStore; + internal AuthenticatedDataParser authData; + + private AlgorithmIdentifier macAlg; + private byte[] mac; + private Asn1.Cms.AttributeTable authAttrs; + private Asn1.Cms.AttributeTable unauthAttrs; + + private bool authAttrNotRead; + private bool unauthAttrNotRead; + + public CmsAuthenticatedDataParser( + byte[] envelopedData) + : this(new MemoryStream(envelopedData, false)) + { + } + + public CmsAuthenticatedDataParser( + Stream envelopedData) + : base(envelopedData) + { + this.authAttrNotRead = true; + this.authData = new AuthenticatedDataParser( + (Asn1SequenceParser)contentInfo.GetContent(Asn1Tags.Sequence)); + + // TODO Validate version? + //DerInteger version = this.authData.getVersion(); + + // + // read the recipients + // + Asn1Set recipientInfos = Asn1Set.GetInstance(authData.GetRecipientInfos().ToAsn1Object()); + + this.macAlg = authData.GetMacAlgorithm(); + + // + // read the authenticated content info + // + ContentInfoParser data = authData.GetEnapsulatedContentInfo(); + CmsReadable readable = new CmsProcessableInputStream( + ((Asn1OctetStringParser)data.GetContent(Asn1Tags.OctetString)).GetOctetStream()); + CmsSecureReadable secureReadable = new CmsEnvelopedHelper.CmsAuthenticatedSecureReadable( + this.macAlg, readable); + + // + // build the RecipientInformationStore + // + this._recipientInfoStore = CmsEnvelopedHelper.BuildRecipientInformationStore( + recipientInfos, secureReadable); + } + + public AlgorithmIdentifier MacAlgorithmID + { + get { return macAlg; } + } + + /** + * return the object identifier for the mac algorithm. + */ + public string MacAlgOid + { + get { return macAlg.Algorithm.Id; } + } + + + /** + * return the ASN.1 encoded encryption algorithm parameters, or null if + * there aren't any. + */ + public Asn1Object MacAlgParams + { + get + { + Asn1Encodable ae = macAlg.Parameters; + + return ae == null ? null : ae.ToAsn1Object(); + } + } + + /** + * return a store of the intended recipients for this message + */ + public RecipientInformationStore GetRecipientInfos() + { + return _recipientInfoStore; + } + + public byte[] GetMac() + { + if (mac == null) + { + GetAuthAttrs(); + mac = authData.GetMac().GetOctets(); + } + return Arrays.Clone(mac); + } + + /** + * return a table of the unauthenticated attributes indexed by + * the OID of the attribute. + * @exception java.io.IOException + */ + public Asn1.Cms.AttributeTable GetAuthAttrs() + { + if (authAttrs == null && authAttrNotRead) + { + Asn1SetParser s = authData.GetAuthAttrs(); + + authAttrNotRead = false; + + if (s != null) + { + Asn1EncodableVector v = new Asn1EncodableVector(); + + IAsn1Convertible o; + while ((o = s.ReadObject()) != null) + { + Asn1SequenceParser seq = (Asn1SequenceParser)o; + + v.Add(seq.ToAsn1Object()); + } + + authAttrs = new Asn1.Cms.AttributeTable(new DerSet(v)); + } + } + + return authAttrs; + } + + /** + * return a table of the unauthenticated attributes indexed by + * the OID of the attribute. + * @exception java.io.IOException + */ + public Asn1.Cms.AttributeTable GetUnauthAttrs() + { + if (unauthAttrs == null && unauthAttrNotRead) + { + Asn1SetParser s = authData.GetUnauthAttrs(); + + unauthAttrNotRead = false; + + if (s != null) + { + Asn1EncodableVector v = new Asn1EncodableVector(); + + IAsn1Convertible o; + while ((o = s.ReadObject()) != null) + { + Asn1SequenceParser seq = (Asn1SequenceParser)o; + + v.Add(seq.ToAsn1Object()); + } + + unauthAttrs = new Asn1.Cms.AttributeTable(new DerSet(v)); + } + } + + return unauthAttrs; + } + } +} diff --git a/bc-sharp-crypto/src/cms/CMSAuthenticatedDataStreamGenerator.cs b/bc-sharp-crypto/src/cms/CMSAuthenticatedDataStreamGenerator.cs new file mode 100644 index 0000000..4d18d10 --- /dev/null +++ b/bc-sharp-crypto/src/cms/CMSAuthenticatedDataStreamGenerator.cs @@ -0,0 +1,297 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Cms; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.IO; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Cms +{ + /** + * General class for generating a CMS authenticated-data message stream. + *

+ * A simple example of usage. + *

+	*      CMSAuthenticatedDataStreamGenerator edGen = new CMSAuthenticatedDataStreamGenerator();
+	*
+	*      edGen.addKeyTransRecipient(cert);
+	*
+	*      ByteArrayOutputStream  bOut = new ByteArrayOutputStream();
+	*
+	*      OutputStream out = edGen.open(
+	*                              bOut, CMSAuthenticatedDataGenerator.AES128_CBC, "BC");*
+	*      out.write(data);
+	*
+	*      out.close();
+	* 
+ *

+ */ + public class CmsAuthenticatedDataStreamGenerator + : CmsAuthenticatedGenerator + { + // TODO Add support +// private object _originatorInfo = null; +// private object _unprotectedAttributes = null; + private int _bufferSize; + private bool _berEncodeRecipientSet; + + /** + * base constructor + */ + public CmsAuthenticatedDataStreamGenerator() + { + } + + /** + * constructor allowing specific source of randomness + * @param rand instance of SecureRandom to use + */ + public CmsAuthenticatedDataStreamGenerator( + SecureRandom rand) + : base(rand) + { + } + + /** + * Set the underlying string size for encapsulated data + * + * @param bufferSize length of octet strings to buffer the data. + */ + public void SetBufferSize( + int bufferSize) + { + _bufferSize = bufferSize; + } + + /** + * Use a BER Set to store the recipient information + */ + public void SetBerEncodeRecipients( + bool berEncodeRecipientSet) + { + _berEncodeRecipientSet = berEncodeRecipientSet; + } + + /** + * generate an enveloped object that contains an CMS Enveloped Data + * object using the given provider and the passed in key generator. + * @throws java.io.IOException + */ + private Stream Open( + Stream outStr, + string macOid, + CipherKeyGenerator keyGen) + { + // FIXME Will this work for macs? + byte[] encKeyBytes = keyGen.GenerateKey(); + KeyParameter encKey = ParameterUtilities.CreateKeyParameter(macOid, encKeyBytes); + + Asn1Encodable asn1Params = GenerateAsn1Parameters(macOid, encKeyBytes); + + ICipherParameters cipherParameters; + AlgorithmIdentifier macAlgId = GetAlgorithmIdentifier( + macOid, encKey, asn1Params, out cipherParameters); + + Asn1EncodableVector recipientInfos = new Asn1EncodableVector(); + + foreach (RecipientInfoGenerator rig in recipientInfoGenerators) + { + try + { + recipientInfos.Add(rig.Generate(encKey, rand)); + } + catch (InvalidKeyException e) + { + throw new CmsException("key inappropriate for algorithm.", e); + } + catch (GeneralSecurityException e) + { + throw new CmsException("error making encrypted content.", e); + } + } + + // FIXME Only passing key at the moment +// return Open(outStr, macAlgId, cipherParameters, recipientInfos); + return Open(outStr, macAlgId, encKey, recipientInfos); + } + + protected Stream Open( + Stream outStr, + AlgorithmIdentifier macAlgId, + ICipherParameters cipherParameters, + Asn1EncodableVector recipientInfos) + { + try + { + // + // ContentInfo + // + BerSequenceGenerator cGen = new BerSequenceGenerator(outStr); + + cGen.AddObject(CmsObjectIdentifiers.AuthenticatedData); + + // + // Authenticated Data + // + BerSequenceGenerator authGen = new BerSequenceGenerator( + cGen.GetRawOutputStream(), 0, true); + + authGen.AddObject(new DerInteger(AuthenticatedData.CalculateVersion(null))); + + Stream authRaw = authGen.GetRawOutputStream(); + Asn1Generator recipGen = _berEncodeRecipientSet + ? (Asn1Generator) new BerSetGenerator(authRaw) + : new DerSetGenerator(authRaw); + + foreach (Asn1Encodable ae in recipientInfos) + { + recipGen.AddObject(ae); + } + + recipGen.Close(); + + authGen.AddObject(macAlgId); + + BerSequenceGenerator eiGen = new BerSequenceGenerator(authRaw); + eiGen.AddObject(CmsObjectIdentifiers.Data); + + Stream octetOutputStream = CmsUtilities.CreateBerOctetOutputStream( + eiGen.GetRawOutputStream(), 0, false, _bufferSize); + + IMac mac = MacUtilities.GetMac(macAlgId.Algorithm); + // TODO Confirm no ParametersWithRandom needed + mac.Init(cipherParameters); + Stream mOut = new TeeOutputStream(octetOutputStream, new MacOutputStream(mac)); + + return new CmsAuthenticatedDataOutputStream(mOut, mac, cGen, authGen, eiGen); + } + catch (SecurityUtilityException e) + { + throw new CmsException("couldn't create cipher.", e); + } + catch (InvalidKeyException e) + { + throw new CmsException("key invalid in message.", e); + } + catch (IOException e) + { + throw new CmsException("exception decoding algorithm parameters.", e); + } + } + + /** + * generate an enveloped object that contains an CMS Enveloped Data object + */ + public Stream Open( + Stream outStr, + string encryptionOid) + { + CipherKeyGenerator keyGen = GeneratorUtilities.GetKeyGenerator(encryptionOid); + + keyGen.Init(new KeyGenerationParameters(rand, keyGen.DefaultStrength)); + + return Open(outStr, encryptionOid, keyGen); + } + + /** + * generate an enveloped object that contains an CMS Enveloped Data object + */ + public Stream Open( + Stream outStr, + string encryptionOid, + int keySize) + { + CipherKeyGenerator keyGen = GeneratorUtilities.GetKeyGenerator(encryptionOid); + + keyGen.Init(new KeyGenerationParameters(rand, keySize)); + + return Open(outStr, encryptionOid, keyGen); + } + + private class CmsAuthenticatedDataOutputStream + : BaseOutputStream + { + private readonly Stream macStream; + private readonly IMac mac; + private readonly BerSequenceGenerator cGen; + private readonly BerSequenceGenerator authGen; + private readonly BerSequenceGenerator eiGen; + + public CmsAuthenticatedDataOutputStream( + Stream macStream, + IMac mac, + BerSequenceGenerator cGen, + BerSequenceGenerator authGen, + BerSequenceGenerator eiGen) + { + this.macStream = macStream; + this.mac = mac; + this.cGen = cGen; + this.authGen = authGen; + this.eiGen = eiGen; + } + + public override void WriteByte( + byte b) + { + macStream.WriteByte(b); + } + + public override void Write( + byte[] bytes, + int off, + int len) + { + macStream.Write(bytes, off, len); + } + +#if PORTABLE + protected override void Dispose(bool disposing) + { + if (disposing) + { + Platform.Dispose(macStream); + + // TODO Parent context(s) should really be be closed explicitly + + eiGen.Close(); + + // [TODO] auth attributes go here + byte[] macOctets = MacUtilities.DoFinal(mac); + authGen.AddObject(new DerOctetString(macOctets)); + // [TODO] unauth attributes go here + + authGen.Close(); + cGen.Close(); + } + base.Dispose(disposing); + } +#else + public override void Close() + { + Platform.Dispose(macStream); + + // TODO Parent context(s) should really be be closed explicitly + + eiGen.Close(); + + // [TODO] auth attributes go here + byte[] macOctets = MacUtilities.DoFinal(mac); + authGen.AddObject(new DerOctetString(macOctets)); + // [TODO] unauth attributes go here + + authGen.Close(); + cGen.Close(); + base.Close(); + } +#endif + } + } +} diff --git a/bc-sharp-crypto/src/cms/CMSAuthenticatedGenerator.cs b/bc-sharp-crypto/src/cms/CMSAuthenticatedGenerator.cs new file mode 100644 index 0000000..8824d19 --- /dev/null +++ b/bc-sharp-crypto/src/cms/CMSAuthenticatedGenerator.cs @@ -0,0 +1,35 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities.Date; +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Cms +{ + public class CmsAuthenticatedGenerator + : CmsEnvelopedGenerator + { + /** + * base constructor + */ + public CmsAuthenticatedGenerator() + { + } + + /** + * constructor allowing specific source of randomness + * + * @param rand instance of SecureRandom to use + */ + public CmsAuthenticatedGenerator( + SecureRandom rand) + : base(rand) + { + } + } +} diff --git a/bc-sharp-crypto/src/cms/CMSCompressedData.cs b/bc-sharp-crypto/src/cms/CMSCompressedData.cs new file mode 100644 index 0000000..21651f0 --- /dev/null +++ b/bc-sharp-crypto/src/cms/CMSCompressedData.cs @@ -0,0 +1,108 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Cms; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Zlib; + +namespace Org.BouncyCastle.Cms +{ + /** + * containing class for an CMS Compressed Data object + */ + public class CmsCompressedData + { + internal ContentInfo contentInfo; + + public CmsCompressedData( + byte[] compressedData) + : this(CmsUtilities.ReadContentInfo(compressedData)) + { + } + + public CmsCompressedData( + Stream compressedDataStream) + : this(CmsUtilities.ReadContentInfo(compressedDataStream)) + { + } + + public CmsCompressedData( + ContentInfo contentInfo) + { + this.contentInfo = contentInfo; + } + + /** + * Return the uncompressed content. + * + * @return the uncompressed content + * @throws CmsException if there is an exception uncompressing the data. + */ + public byte[] GetContent() + { + CompressedData comData = CompressedData.GetInstance(contentInfo.Content); + ContentInfo content = comData.EncapContentInfo; + + Asn1OctetString bytes = (Asn1OctetString) content.Content; + ZInputStream zIn = new ZInputStream(bytes.GetOctetStream()); + + try + { + return CmsUtilities.StreamToByteArray(zIn); + } + catch (IOException e) + { + throw new CmsException("exception reading compressed stream.", e); + } + finally + { + Platform.Dispose(zIn); + } + } + + /** + * Return the uncompressed content, throwing an exception if the data size + * is greater than the passed in limit. If the content is exceeded getCause() + * on the CMSException will contain a StreamOverflowException + * + * @param limit maximum number of bytes to read + * @return the content read + * @throws CMSException if there is an exception uncompressing the data. + */ + public byte[] GetContent(int limit) + { + CompressedData comData = CompressedData.GetInstance(contentInfo.Content); + ContentInfo content = comData.EncapContentInfo; + + Asn1OctetString bytes = (Asn1OctetString)content.Content; + + ZInputStream zIn = new ZInputStream(new MemoryStream(bytes.GetOctets(), false)); + + try + { + return CmsUtilities.StreamToByteArray(zIn, limit); + } + catch (IOException e) + { + throw new CmsException("exception reading compressed stream.", e); + } + } + + /** + * return the ContentInfo + */ + public ContentInfo ContentInfo + { + get { return contentInfo; } + } + + /** + * return the ASN.1 encoded representation of this object. + */ + public byte[] GetEncoded() + { + return contentInfo.GetEncoded(); + } + } +} diff --git a/bc-sharp-crypto/src/cms/CMSCompressedDataGenerator.cs b/bc-sharp-crypto/src/cms/CMSCompressedDataGenerator.cs new file mode 100644 index 0000000..d51de10 --- /dev/null +++ b/bc-sharp-crypto/src/cms/CMSCompressedDataGenerator.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Cms; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Zlib; + +namespace Org.BouncyCastle.Cms +{ + /** + * General class for generating a compressed CMS message. + *

+ * A simple example of usage.

+ *

+ *

+    *      CMSCompressedDataGenerator fact = new CMSCompressedDataGenerator();
+    *      CMSCompressedData data = fact.Generate(content, algorithm);
+    * 
+ *

+ */ + public class CmsCompressedDataGenerator + { + public const string ZLib = "1.2.840.113549.1.9.16.3.8"; + + public CmsCompressedDataGenerator() + { + } + + /** + * Generate an object that contains an CMS Compressed Data + */ + public CmsCompressedData Generate( + CmsProcessable content, + string compressionOid) + { + AlgorithmIdentifier comAlgId; + Asn1OctetString comOcts; + + try + { + MemoryStream bOut = new MemoryStream(); + ZOutputStream zOut = new ZOutputStream(bOut, JZlib.Z_DEFAULT_COMPRESSION); + + content.Write(zOut); + + Platform.Dispose(zOut); + + comAlgId = new AlgorithmIdentifier(new DerObjectIdentifier(compressionOid)); + comOcts = new BerOctetString(bOut.ToArray()); + } + catch (IOException e) + { + throw new CmsException("exception encoding data.", e); + } + + ContentInfo comContent = new ContentInfo(CmsObjectIdentifiers.Data, comOcts); + ContentInfo contentInfo = new ContentInfo( + CmsObjectIdentifiers.CompressedData, + new CompressedData(comAlgId, comContent)); + + return new CmsCompressedData(contentInfo); + } + } +} diff --git a/bc-sharp-crypto/src/cms/CMSCompressedDataParser.cs b/bc-sharp-crypto/src/cms/CMSCompressedDataParser.cs new file mode 100644 index 0000000..93dfa12 --- /dev/null +++ b/bc-sharp-crypto/src/cms/CMSCompressedDataParser.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Cms; +using Org.BouncyCastle.Utilities.Zlib; + +namespace Org.BouncyCastle.Cms +{ + /** + * Class for reading a CMS Compressed Data stream. + *
+    *     CMSCompressedDataParser cp = new CMSCompressedDataParser(inputStream);
+    *
+    *     process(cp.GetContent().GetContentStream());
+    * 
+ * Note: this class does not introduce buffering - if you are processing large files you should create + * the parser with: + *
+    *      CMSCompressedDataParser     ep = new CMSCompressedDataParser(new BufferedInputStream(inputStream, bufSize));
+    *  
+ * where bufSize is a suitably large buffer size. + */ + public class CmsCompressedDataParser + : CmsContentInfoParser + { + public CmsCompressedDataParser( + byte[] compressedData) + : this(new MemoryStream(compressedData, false)) + { + } + + public CmsCompressedDataParser( + Stream compressedData) + : base(compressedData) + { + } + + public CmsTypedStream GetContent() + { + try + { + CompressedDataParser comData = new CompressedDataParser((Asn1SequenceParser)this.contentInfo.GetContent(Asn1Tags.Sequence)); + ContentInfoParser content = comData.GetEncapContentInfo(); + + Asn1OctetStringParser bytes = (Asn1OctetStringParser)content.GetContent(Asn1Tags.OctetString); + + return new CmsTypedStream(content.ContentType.ToString(), new ZInputStream(bytes.GetOctetStream())); + } + catch (IOException e) + { + throw new CmsException("IOException reading compressed content.", e); + } + } + } +} diff --git a/bc-sharp-crypto/src/cms/CMSCompressedDataStreamGenerator.cs b/bc-sharp-crypto/src/cms/CMSCompressedDataStreamGenerator.cs new file mode 100644 index 0000000..0cb1bb6 --- /dev/null +++ b/bc-sharp-crypto/src/cms/CMSCompressedDataStreamGenerator.cs @@ -0,0 +1,158 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Cms; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.IO; +using Org.BouncyCastle.Utilities.Zlib; + +namespace Org.BouncyCastle.Cms +{ + /** + * General class for generating a compressed CMS message stream. + *

+ * A simple example of usage. + *

+ *
+	*      CMSCompressedDataStreamGenerator gen = new CMSCompressedDataStreamGenerator();
+	*
+	*      Stream cOut = gen.Open(outputStream, CMSCompressedDataStreamGenerator.ZLIB);
+	*
+	*      cOut.Write(data);
+	*
+	*      cOut.Close();
+	* 
+ */ + public class CmsCompressedDataStreamGenerator + { + public const string ZLib = "1.2.840.113549.1.9.16.3.8"; + + private int _bufferSize; + + /** + * base constructor + */ + public CmsCompressedDataStreamGenerator() + { + } + + /** + * Set the underlying string size for encapsulated data + * + * @param bufferSize length of octet strings to buffer the data. + */ + public void SetBufferSize( + int bufferSize) + { + _bufferSize = bufferSize; + } + + public Stream Open( + Stream outStream, + string compressionOID) + { + return Open(outStream, CmsObjectIdentifiers.Data.Id, compressionOID); + } + + public Stream Open( + Stream outStream, + string contentOID, + string compressionOID) + { + BerSequenceGenerator sGen = new BerSequenceGenerator(outStream); + + sGen.AddObject(CmsObjectIdentifiers.CompressedData); + + // + // Compressed Data + // + BerSequenceGenerator cGen = new BerSequenceGenerator( + sGen.GetRawOutputStream(), 0, true); + + // CMSVersion + cGen.AddObject(new DerInteger(0)); + + // CompressionAlgorithmIdentifier + cGen.AddObject(new AlgorithmIdentifier(new DerObjectIdentifier(ZLib))); + + // + // Encapsulated ContentInfo + // + BerSequenceGenerator eiGen = new BerSequenceGenerator(cGen.GetRawOutputStream()); + + eiGen.AddObject(new DerObjectIdentifier(contentOID)); + + Stream octetStream = CmsUtilities.CreateBerOctetOutputStream( + eiGen.GetRawOutputStream(), 0, true, _bufferSize); + + return new CmsCompressedOutputStream( + new ZOutputStream(octetStream, JZlib.Z_DEFAULT_COMPRESSION), sGen, cGen, eiGen); + } + + private class CmsCompressedOutputStream + : BaseOutputStream + { + private ZOutputStream _out; + private BerSequenceGenerator _sGen; + private BerSequenceGenerator _cGen; + private BerSequenceGenerator _eiGen; + + internal CmsCompressedOutputStream( + ZOutputStream outStream, + BerSequenceGenerator sGen, + BerSequenceGenerator cGen, + BerSequenceGenerator eiGen) + { + _out = outStream; + _sGen = sGen; + _cGen = cGen; + _eiGen = eiGen; + } + + public override void WriteByte( + byte b) + { + _out.WriteByte(b); + } + + public override void Write( + byte[] bytes, + int off, + int len) + { + _out.Write(bytes, off, len); + } + +#if PORTABLE + protected override void Dispose(bool disposing) + { + if (disposing) + { + Platform.Dispose(_out); + + // TODO Parent context(s) should really be be closed explicitly + + _eiGen.Close(); + _cGen.Close(); + _sGen.Close(); + } + base.Dispose(disposing); + } +#else + public override void Close() + { + Platform.Dispose(_out); + + // TODO Parent context(s) should really be be closed explicitly + + _eiGen.Close(); + _cGen.Close(); + _sGen.Close(); + base.Close(); + } +#endif + } + } +} diff --git a/bc-sharp-crypto/src/cms/CMSContentInfoParser.cs b/bc-sharp-crypto/src/cms/CMSContentInfoParser.cs new file mode 100644 index 0000000..a7b43f2 --- /dev/null +++ b/bc-sharp-crypto/src/cms/CMSContentInfoParser.cs @@ -0,0 +1,48 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Cms; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Cms +{ + public class CmsContentInfoParser + { + protected ContentInfoParser contentInfo; + protected Stream data; + + protected CmsContentInfoParser( + Stream data) + { + if (data == null) + throw new ArgumentNullException("data"); + + this.data = data; + + try + { + Asn1StreamParser inStream = new Asn1StreamParser(data); + + this.contentInfo = new ContentInfoParser((Asn1SequenceParser)inStream.ReadObject()); + } + catch (IOException e) + { + throw new CmsException("IOException reading content.", e); + } + catch (InvalidCastException e) + { + throw new CmsException("Unexpected object reading content.", e); + } + } + + /** + * Close the underlying data stream. + * @throws IOException if the close fails. + */ + public void Close() + { + Platform.Dispose(this.data); + } + } +} diff --git a/bc-sharp-crypto/src/cms/CMSEnvelopedData.cs b/bc-sharp-crypto/src/cms/CMSEnvelopedData.cs new file mode 100644 index 0000000..223d0ca --- /dev/null +++ b/bc-sharp-crypto/src/cms/CMSEnvelopedData.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Cms; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Cms +{ + /** + * containing class for an CMS Enveloped Data object + */ + public class CmsEnvelopedData + { + internal RecipientInformationStore recipientInfoStore; + internal ContentInfo contentInfo; + + private AlgorithmIdentifier encAlg; + private Asn1Set unprotectedAttributes; + + public CmsEnvelopedData( + byte[] envelopedData) + : this(CmsUtilities.ReadContentInfo(envelopedData)) + { + } + + public CmsEnvelopedData( + Stream envelopedData) + : this(CmsUtilities.ReadContentInfo(envelopedData)) + { + } + + public CmsEnvelopedData( + ContentInfo contentInfo) + { + this.contentInfo = contentInfo; + + EnvelopedData envData = EnvelopedData.GetInstance(contentInfo.Content); + + // + // read the recipients + // + Asn1Set recipientInfos = envData.RecipientInfos; + + // + // read the encrypted content info + // + EncryptedContentInfo encInfo = envData.EncryptedContentInfo; + this.encAlg = encInfo.ContentEncryptionAlgorithm; + CmsReadable readable = new CmsProcessableByteArray(encInfo.EncryptedContent.GetOctets()); + CmsSecureReadable secureReadable = new CmsEnvelopedHelper.CmsEnvelopedSecureReadable( + this.encAlg, readable); + + // + // build the RecipientInformationStore + // + this.recipientInfoStore = CmsEnvelopedHelper.BuildRecipientInformationStore( + recipientInfos, secureReadable); + + this.unprotectedAttributes = envData.UnprotectedAttrs; + } + + public AlgorithmIdentifier EncryptionAlgorithmID + { + get { return encAlg; } + } + + /** + * return the object identifier for the content encryption algorithm. + */ + public string EncryptionAlgOid + { + get { return encAlg.Algorithm.Id; } + } + + /** + * return a store of the intended recipients for this message + */ + public RecipientInformationStore GetRecipientInfos() + { + return recipientInfoStore; + } + + /** + * return the ContentInfo + */ + public ContentInfo ContentInfo + { + get { return contentInfo; } + } + + /** + * return a table of the unprotected attributes indexed by + * the OID of the attribute. + */ + public Asn1.Cms.AttributeTable GetUnprotectedAttributes() + { + if (unprotectedAttributes == null) + return null; + + return new Asn1.Cms.AttributeTable(unprotectedAttributes); + } + + /** + * return the ASN.1 encoded representation of this object. + */ + public byte[] GetEncoded() + { + return contentInfo.GetEncoded(); + } + } +} diff --git a/bc-sharp-crypto/src/cms/CMSEnvelopedDataGenerator.cs b/bc-sharp-crypto/src/cms/CMSEnvelopedDataGenerator.cs new file mode 100644 index 0000000..d260e99 --- /dev/null +++ b/bc-sharp-crypto/src/cms/CMSEnvelopedDataGenerator.cs @@ -0,0 +1,178 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Cms; +using Org.BouncyCastle.Asn1.Nist; +using Org.BouncyCastle.Asn1.Oiw; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.IO; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Date; +using Org.BouncyCastle.X509; + +namespace Org.BouncyCastle.Cms +{ + /// + /// General class for generating a CMS enveloped-data message. + /// + /// A simple example of usage. + /// + ///
+    ///      CmsEnvelopedDataGenerator  fact = new CmsEnvelopedDataGenerator();
+    ///
+    ///      fact.AddKeyTransRecipient(cert);
+    ///
+    ///      CmsEnvelopedData         data = fact.Generate(content, algorithm);
+    /// 
+ ///
+ public class CmsEnvelopedDataGenerator + : CmsEnvelopedGenerator + { + public CmsEnvelopedDataGenerator() + { + } + + /// Constructor allowing specific source of randomness + /// Instance of SecureRandom to use. + public CmsEnvelopedDataGenerator( + SecureRandom rand) + : base(rand) + { + } + + /// + /// Generate an enveloped object that contains a CMS Enveloped Data + /// object using the passed in key generator. + /// + private CmsEnvelopedData Generate( + CmsProcessable content, + string encryptionOid, + CipherKeyGenerator keyGen) + { + AlgorithmIdentifier encAlgId = null; + KeyParameter encKey; + Asn1OctetString encContent; + + try + { + byte[] encKeyBytes = keyGen.GenerateKey(); + encKey = ParameterUtilities.CreateKeyParameter(encryptionOid, encKeyBytes); + + Asn1Encodable asn1Params = GenerateAsn1Parameters(encryptionOid, encKeyBytes); + + ICipherParameters cipherParameters; + encAlgId = GetAlgorithmIdentifier( + encryptionOid, encKey, asn1Params, out cipherParameters); + + IBufferedCipher cipher = CipherUtilities.GetCipher(encryptionOid); + cipher.Init(true, new ParametersWithRandom(cipherParameters, rand)); + + MemoryStream bOut = new MemoryStream(); + CipherStream cOut = new CipherStream(bOut, null, cipher); + + content.Write(cOut); + + Platform.Dispose(cOut); + + encContent = new BerOctetString(bOut.ToArray()); + } + catch (SecurityUtilityException e) + { + throw new CmsException("couldn't create cipher.", e); + } + catch (InvalidKeyException e) + { + throw new CmsException("key invalid in message.", e); + } + catch (IOException e) + { + throw new CmsException("exception decoding algorithm parameters.", e); + } + + + Asn1EncodableVector recipientInfos = new Asn1EncodableVector(); + + foreach (RecipientInfoGenerator rig in recipientInfoGenerators) + { + try + { + recipientInfos.Add(rig.Generate(encKey, rand)); + } + catch (InvalidKeyException e) + { + throw new CmsException("key inappropriate for algorithm.", e); + } + catch (GeneralSecurityException e) + { + throw new CmsException("error making encrypted content.", e); + } + } + + EncryptedContentInfo eci = new EncryptedContentInfo( + CmsObjectIdentifiers.Data, + encAlgId, + encContent); + + Asn1Set unprotectedAttrSet = null; + if (unprotectedAttributeGenerator != null) + { + Asn1.Cms.AttributeTable attrTable = unprotectedAttributeGenerator.GetAttributes(Platform.CreateHashtable()); + + unprotectedAttrSet = new BerSet(attrTable.ToAsn1EncodableVector()); + } + + ContentInfo contentInfo = new ContentInfo( + CmsObjectIdentifiers.EnvelopedData, + new EnvelopedData(null, new DerSet(recipientInfos), eci, unprotectedAttrSet)); + + return new CmsEnvelopedData(contentInfo); + } + + /// Generate an enveloped object that contains an CMS Enveloped Data object. + public CmsEnvelopedData Generate( + CmsProcessable content, + string encryptionOid) + { + try + { + CipherKeyGenerator keyGen = GeneratorUtilities.GetKeyGenerator(encryptionOid); + + keyGen.Init(new KeyGenerationParameters(rand, keyGen.DefaultStrength)); + + return Generate(content, encryptionOid, keyGen); + } + catch (SecurityUtilityException e) + { + throw new CmsException("can't find key generation algorithm.", e); + } + } + + /// Generate an enveloped object that contains an CMS Enveloped Data object. + public CmsEnvelopedData Generate( + CmsProcessable content, + string encryptionOid, + int keySize) + { + try + { + CipherKeyGenerator keyGen = GeneratorUtilities.GetKeyGenerator(encryptionOid); + + keyGen.Init(new KeyGenerationParameters(rand, keySize)); + + return Generate(content, encryptionOid, keyGen); + } + catch (SecurityUtilityException e) + { + throw new CmsException("can't find key generation algorithm.", e); + } + } + } +} diff --git a/bc-sharp-crypto/src/cms/CMSEnvelopedDataParser.cs b/bc-sharp-crypto/src/cms/CMSEnvelopedDataParser.cs new file mode 100644 index 0000000..d5dfaf5 --- /dev/null +++ b/bc-sharp-crypto/src/cms/CMSEnvelopedDataParser.cs @@ -0,0 +1,161 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Cms; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; + +namespace Org.BouncyCastle.Cms +{ + /** + * Parsing class for an CMS Enveloped Data object from an input stream. + *

+ * Note: that because we are in a streaming mode only one recipient can be tried and it is important + * that the methods on the parser are called in the appropriate order. + *

+ *

+ * Example of use - assuming the first recipient matches the private key we have. + *

+	*      CmsEnvelopedDataParser     ep = new CmsEnvelopedDataParser(inputStream);
+	*
+	*      RecipientInformationStore  recipients = ep.GetRecipientInfos();
+	*
+	*      Collection  c = recipients.getRecipients();
+	*      Iterator    it = c.iterator();
+	*
+	*      if (it.hasNext())
+	*      {
+	*          RecipientInformation   recipient = (RecipientInformation)it.next();
+	*
+	*          CMSTypedStream recData = recipient.getContentStream(privateKey);
+	*
+	*          processDataStream(recData.getContentStream());
+	*      }
+	*  
+ * Note: this class does not introduce buffering - if you are processing large files you should create + * the parser with: + *
+	*          CmsEnvelopedDataParser     ep = new CmsEnvelopedDataParser(new BufferedInputStream(inputStream, bufSize));
+	*  
+ * where bufSize is a suitably large buffer size. + *

+ */ + public class CmsEnvelopedDataParser + : CmsContentInfoParser + { + internal RecipientInformationStore recipientInfoStore; + internal EnvelopedDataParser envelopedData; + + private AlgorithmIdentifier _encAlg; + private Asn1.Cms.AttributeTable _unprotectedAttributes; + private bool _attrNotRead; + + public CmsEnvelopedDataParser( + byte[] envelopedData) + : this(new MemoryStream(envelopedData, false)) + { + } + + public CmsEnvelopedDataParser( + Stream envelopedData) + : base(envelopedData) + { + this._attrNotRead = true; + this.envelopedData = new EnvelopedDataParser( + (Asn1SequenceParser)this.contentInfo.GetContent(Asn1Tags.Sequence)); + + // TODO Validate version? + //DerInteger version = this.envelopedData.Version; + + // + // read the recipients + // + Asn1Set recipientInfos = Asn1Set.GetInstance(this.envelopedData.GetRecipientInfos().ToAsn1Object()); + + // + // read the encrypted content info + // + EncryptedContentInfoParser encInfo = this.envelopedData.GetEncryptedContentInfo(); + this._encAlg = encInfo.ContentEncryptionAlgorithm; + CmsReadable readable = new CmsProcessableInputStream( + ((Asn1OctetStringParser)encInfo.GetEncryptedContent(Asn1Tags.OctetString)).GetOctetStream()); + CmsSecureReadable secureReadable = new CmsEnvelopedHelper.CmsEnvelopedSecureReadable( + this._encAlg, readable); + + // + // build the RecipientInformationStore + // + this.recipientInfoStore = CmsEnvelopedHelper.BuildRecipientInformationStore( + recipientInfos, secureReadable); + } + + public AlgorithmIdentifier EncryptionAlgorithmID + { + get { return _encAlg; } + } + + /** + * return the object identifier for the content encryption algorithm. + */ + public string EncryptionAlgOid + { + get { return _encAlg.Algorithm.Id; } + } + + /** + * return the ASN.1 encoded encryption algorithm parameters, or null if + * there aren't any. + */ + public Asn1Object EncryptionAlgParams + { + get + { + Asn1Encodable ae = _encAlg.Parameters; + + return ae == null ? null : ae.ToAsn1Object(); + } + } + + /** + * return a store of the intended recipients for this message + */ + public RecipientInformationStore GetRecipientInfos() + { + return this.recipientInfoStore; + } + + /** + * return a table of the unprotected attributes indexed by + * the OID of the attribute. + * @throws IOException + */ + public Asn1.Cms.AttributeTable GetUnprotectedAttributes() + { + if (_unprotectedAttributes == null && _attrNotRead) + { + Asn1SetParser asn1Set = this.envelopedData.GetUnprotectedAttrs(); + + _attrNotRead = false; + + if (asn1Set != null) + { + Asn1EncodableVector v = new Asn1EncodableVector(); + IAsn1Convertible o; + + while ((o = asn1Set.ReadObject()) != null) + { + Asn1SequenceParser seq = (Asn1SequenceParser)o; + + v.Add(seq.ToAsn1Object()); + } + + _unprotectedAttributes = new Asn1.Cms.AttributeTable(new DerSet(v)); + } + } + + return _unprotectedAttributes; + } + } +} diff --git a/bc-sharp-crypto/src/cms/CMSEnvelopedDataStreamGenerator.cs b/bc-sharp-crypto/src/cms/CMSEnvelopedDataStreamGenerator.cs new file mode 100644 index 0000000..8e6d272 --- /dev/null +++ b/bc-sharp-crypto/src/cms/CMSEnvelopedDataStreamGenerator.cs @@ -0,0 +1,308 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Cms; +using Org.BouncyCastle.Asn1.Nist; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.IO; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Security.Certificates; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.IO; +using Org.BouncyCastle.X509; + +namespace Org.BouncyCastle.Cms +{ + /** + * General class for generating a CMS enveloped-data message stream. + *

+ * A simple example of usage. + *

+	*      CmsEnvelopedDataStreamGenerator edGen = new CmsEnvelopedDataStreamGenerator();
+	*
+	*      edGen.AddKeyTransRecipient(cert);
+	*
+	*      MemoryStream  bOut = new MemoryStream();
+	*
+	*      Stream out = edGen.Open(
+	*                              bOut, CMSEnvelopedDataGenerator.AES128_CBC);*
+	*      out.Write(data);
+	*
+	*      out.Close();
+	* 
+ *

+ */ + public class CmsEnvelopedDataStreamGenerator + : CmsEnvelopedGenerator + { + private object _originatorInfo = null; + private object _unprotectedAttributes = null; + private int _bufferSize; + private bool _berEncodeRecipientSet; + + public CmsEnvelopedDataStreamGenerator() + { + } + + /// Constructor allowing specific source of randomness + /// Instance of SecureRandom to use. + public CmsEnvelopedDataStreamGenerator( + SecureRandom rand) + : base(rand) + { + } + + /// Set the underlying string size for encapsulated data. + /// Length of octet strings to buffer the data. + public void SetBufferSize( + int bufferSize) + { + _bufferSize = bufferSize; + } + + /// Use a BER Set to store the recipient information. + public void SetBerEncodeRecipients( + bool berEncodeRecipientSet) + { + _berEncodeRecipientSet = berEncodeRecipientSet; + } + + private DerInteger Version + { + get + { + int version = (_originatorInfo != null || _unprotectedAttributes != null) + ? 2 + : 0; + + return new DerInteger(version); + } + } + + /// + /// Generate an enveloped object that contains an CMS Enveloped Data + /// object using the passed in key generator. + /// + private Stream Open( + Stream outStream, + string encryptionOid, + CipherKeyGenerator keyGen) + { + byte[] encKeyBytes = keyGen.GenerateKey(); + KeyParameter encKey = ParameterUtilities.CreateKeyParameter(encryptionOid, encKeyBytes); + + Asn1Encodable asn1Params = GenerateAsn1Parameters(encryptionOid, encKeyBytes); + + ICipherParameters cipherParameters; + AlgorithmIdentifier encAlgID = GetAlgorithmIdentifier( + encryptionOid, encKey, asn1Params, out cipherParameters); + + Asn1EncodableVector recipientInfos = new Asn1EncodableVector(); + + foreach (RecipientInfoGenerator rig in recipientInfoGenerators) + { + try + { + recipientInfos.Add(rig.Generate(encKey, rand)); + } + catch (InvalidKeyException e) + { + throw new CmsException("key inappropriate for algorithm.", e); + } + catch (GeneralSecurityException e) + { + throw new CmsException("error making encrypted content.", e); + } + } + + return Open(outStream, encAlgID, cipherParameters, recipientInfos); + } + + private Stream Open( + Stream outStream, + AlgorithmIdentifier encAlgID, + ICipherParameters cipherParameters, + Asn1EncodableVector recipientInfos) + { + try + { + // + // ContentInfo + // + BerSequenceGenerator cGen = new BerSequenceGenerator(outStream); + + cGen.AddObject(CmsObjectIdentifiers.EnvelopedData); + + // + // Encrypted Data + // + BerSequenceGenerator envGen = new BerSequenceGenerator( + cGen.GetRawOutputStream(), 0, true); + + envGen.AddObject(this.Version); + + Stream envRaw = envGen.GetRawOutputStream(); + Asn1Generator recipGen = _berEncodeRecipientSet + ? (Asn1Generator) new BerSetGenerator(envRaw) + : new DerSetGenerator(envRaw); + + foreach (Asn1Encodable ae in recipientInfos) + { + recipGen.AddObject(ae); + } + + recipGen.Close(); + + BerSequenceGenerator eiGen = new BerSequenceGenerator(envRaw); + eiGen.AddObject(CmsObjectIdentifiers.Data); + eiGen.AddObject(encAlgID); + + Stream octetOutputStream = CmsUtilities.CreateBerOctetOutputStream( + eiGen.GetRawOutputStream(), 0, false, _bufferSize); + + IBufferedCipher cipher = CipherUtilities.GetCipher(encAlgID.Algorithm); + cipher.Init(true, new ParametersWithRandom(cipherParameters, rand)); + CipherStream cOut = new CipherStream(octetOutputStream, null, cipher); + + return new CmsEnvelopedDataOutputStream(this, cOut, cGen, envGen, eiGen); + } + catch (SecurityUtilityException e) + { + throw new CmsException("couldn't create cipher.", e); + } + catch (InvalidKeyException e) + { + throw new CmsException("key invalid in message.", e); + } + catch (IOException e) + { + throw new CmsException("exception decoding algorithm parameters.", e); + } + } + + /** + * generate an enveloped object that contains an CMS Enveloped Data object + * @throws IOException + */ + public Stream Open( + Stream outStream, + string encryptionOid) + { + CipherKeyGenerator keyGen = GeneratorUtilities.GetKeyGenerator(encryptionOid); + + keyGen.Init(new KeyGenerationParameters(rand, keyGen.DefaultStrength)); + + return Open(outStream, encryptionOid, keyGen); + } + + /** + * generate an enveloped object that contains an CMS Enveloped Data object + * @throws IOException + */ + public Stream Open( + Stream outStream, + string encryptionOid, + int keySize) + { + CipherKeyGenerator keyGen = GeneratorUtilities.GetKeyGenerator(encryptionOid); + + keyGen.Init(new KeyGenerationParameters(rand, keySize)); + + return Open(outStream, encryptionOid, keyGen); + } + + private class CmsEnvelopedDataOutputStream + : BaseOutputStream + { + private readonly CmsEnvelopedGenerator _outer; + + private readonly CipherStream _out; + private readonly BerSequenceGenerator _cGen; + private readonly BerSequenceGenerator _envGen; + private readonly BerSequenceGenerator _eiGen; + + public CmsEnvelopedDataOutputStream( + CmsEnvelopedGenerator outer, + CipherStream outStream, + BerSequenceGenerator cGen, + BerSequenceGenerator envGen, + BerSequenceGenerator eiGen) + { + _outer = outer; + _out = outStream; + _cGen = cGen; + _envGen = envGen; + _eiGen = eiGen; + } + + public override void WriteByte( + byte b) + { + _out.WriteByte(b); + } + + public override void Write( + byte[] bytes, + int off, + int len) + { + _out.Write(bytes, off, len); + } + +#if PORTABLE + protected override void Dispose(bool disposing) + { + if (disposing) + { + Platform.Dispose(_out); + + // TODO Parent context(s) should really be closed explicitly + + _eiGen.Close(); + + if (_outer.unprotectedAttributeGenerator != null) + { + Asn1.Cms.AttributeTable attrTable = _outer.unprotectedAttributeGenerator.GetAttributes(Platform.CreateHashtable()); + + Asn1Set unprotectedAttrs = new BerSet(attrTable.ToAsn1EncodableVector()); + + _envGen.AddObject(new DerTaggedObject(false, 1, unprotectedAttrs)); + } + + _envGen.Close(); + _cGen.Close(); + } + base.Dispose(disposing); + } +#else + public override void Close() + { + Platform.Dispose(_out); + + // TODO Parent context(s) should really be closed explicitly + + _eiGen.Close(); + + if (_outer.unprotectedAttributeGenerator != null) + { + Asn1.Cms.AttributeTable attrTable = _outer.unprotectedAttributeGenerator.GetAttributes(Platform.CreateHashtable()); + + Asn1Set unprotectedAttrs = new BerSet(attrTable.ToAsn1EncodableVector()); + + _envGen.AddObject(new DerTaggedObject(false, 1, unprotectedAttrs)); + } + + _envGen.Close(); + _cGen.Close(); + base.Close(); + } +#endif + } + } +} diff --git a/bc-sharp-crypto/src/cms/CMSEnvelopedGenerator.cs b/bc-sharp-crypto/src/cms/CMSEnvelopedGenerator.cs new file mode 100644 index 0000000..f92ae38 --- /dev/null +++ b/bc-sharp-crypto/src/cms/CMSEnvelopedGenerator.cs @@ -0,0 +1,331 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Cms; +using Org.BouncyCastle.Asn1.Kisa; +using Org.BouncyCastle.Asn1.Nist; +using Org.BouncyCastle.Asn1.Ntt; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.X509; + +namespace Org.BouncyCastle.Cms +{ + /** + * General class for generating a CMS enveloped-data message. + * + * A simple example of usage. + * + *
+	*      CMSEnvelopedDataGenerator  fact = new CMSEnvelopedDataGenerator();
+	*
+	*      fact.addKeyTransRecipient(cert);
+	*
+	*      CMSEnvelopedData         data = fact.generate(content, algorithm, "BC");
+	* 
+ */ + public class CmsEnvelopedGenerator + { + // Note: These tables are complementary: If rc2Table[i]==j, then rc2Ekb[j]==i + internal static readonly short[] rc2Table = + { + 0xbd, 0x56, 0xea, 0xf2, 0xa2, 0xf1, 0xac, 0x2a, 0xb0, 0x93, 0xd1, 0x9c, 0x1b, 0x33, 0xfd, 0xd0, + 0x30, 0x04, 0xb6, 0xdc, 0x7d, 0xdf, 0x32, 0x4b, 0xf7, 0xcb, 0x45, 0x9b, 0x31, 0xbb, 0x21, 0x5a, + 0x41, 0x9f, 0xe1, 0xd9, 0x4a, 0x4d, 0x9e, 0xda, 0xa0, 0x68, 0x2c, 0xc3, 0x27, 0x5f, 0x80, 0x36, + 0x3e, 0xee, 0xfb, 0x95, 0x1a, 0xfe, 0xce, 0xa8, 0x34, 0xa9, 0x13, 0xf0, 0xa6, 0x3f, 0xd8, 0x0c, + 0x78, 0x24, 0xaf, 0x23, 0x52, 0xc1, 0x67, 0x17, 0xf5, 0x66, 0x90, 0xe7, 0xe8, 0x07, 0xb8, 0x60, + 0x48, 0xe6, 0x1e, 0x53, 0xf3, 0x92, 0xa4, 0x72, 0x8c, 0x08, 0x15, 0x6e, 0x86, 0x00, 0x84, 0xfa, + 0xf4, 0x7f, 0x8a, 0x42, 0x19, 0xf6, 0xdb, 0xcd, 0x14, 0x8d, 0x50, 0x12, 0xba, 0x3c, 0x06, 0x4e, + 0xec, 0xb3, 0x35, 0x11, 0xa1, 0x88, 0x8e, 0x2b, 0x94, 0x99, 0xb7, 0x71, 0x74, 0xd3, 0xe4, 0xbf, + 0x3a, 0xde, 0x96, 0x0e, 0xbc, 0x0a, 0xed, 0x77, 0xfc, 0x37, 0x6b, 0x03, 0x79, 0x89, 0x62, 0xc6, + 0xd7, 0xc0, 0xd2, 0x7c, 0x6a, 0x8b, 0x22, 0xa3, 0x5b, 0x05, 0x5d, 0x02, 0x75, 0xd5, 0x61, 0xe3, + 0x18, 0x8f, 0x55, 0x51, 0xad, 0x1f, 0x0b, 0x5e, 0x85, 0xe5, 0xc2, 0x57, 0x63, 0xca, 0x3d, 0x6c, + 0xb4, 0xc5, 0xcc, 0x70, 0xb2, 0x91, 0x59, 0x0d, 0x47, 0x20, 0xc8, 0x4f, 0x58, 0xe0, 0x01, 0xe2, + 0x16, 0x38, 0xc4, 0x6f, 0x3b, 0x0f, 0x65, 0x46, 0xbe, 0x7e, 0x2d, 0x7b, 0x82, 0xf9, 0x40, 0xb5, + 0x1d, 0x73, 0xf8, 0xeb, 0x26, 0xc7, 0x87, 0x97, 0x25, 0x54, 0xb1, 0x28, 0xaa, 0x98, 0x9d, 0xa5, + 0x64, 0x6d, 0x7a, 0xd4, 0x10, 0x81, 0x44, 0xef, 0x49, 0xd6, 0xae, 0x2e, 0xdd, 0x76, 0x5c, 0x2f, + 0xa7, 0x1c, 0xc9, 0x09, 0x69, 0x9a, 0x83, 0xcf, 0x29, 0x39, 0xb9, 0xe9, 0x4c, 0xff, 0x43, 0xab + }; + +// internal static readonly short[] rc2Ekb = +// { +// 0x5d, 0xbe, 0x9b, 0x8b, 0x11, 0x99, 0x6e, 0x4d, 0x59, 0xf3, 0x85, 0xa6, 0x3f, 0xb7, 0x83, 0xc5, +// 0xe4, 0x73, 0x6b, 0x3a, 0x68, 0x5a, 0xc0, 0x47, 0xa0, 0x64, 0x34, 0x0c, 0xf1, 0xd0, 0x52, 0xa5, +// 0xb9, 0x1e, 0x96, 0x43, 0x41, 0xd8, 0xd4, 0x2c, 0xdb, 0xf8, 0x07, 0x77, 0x2a, 0xca, 0xeb, 0xef, +// 0x10, 0x1c, 0x16, 0x0d, 0x38, 0x72, 0x2f, 0x89, 0xc1, 0xf9, 0x80, 0xc4, 0x6d, 0xae, 0x30, 0x3d, +// 0xce, 0x20, 0x63, 0xfe, 0xe6, 0x1a, 0xc7, 0xb8, 0x50, 0xe8, 0x24, 0x17, 0xfc, 0x25, 0x6f, 0xbb, +// 0x6a, 0xa3, 0x44, 0x53, 0xd9, 0xa2, 0x01, 0xab, 0xbc, 0xb6, 0x1f, 0x98, 0xee, 0x9a, 0xa7, 0x2d, +// 0x4f, 0x9e, 0x8e, 0xac, 0xe0, 0xc6, 0x49, 0x46, 0x29, 0xf4, 0x94, 0x8a, 0xaf, 0xe1, 0x5b, 0xc3, +// 0xb3, 0x7b, 0x57, 0xd1, 0x7c, 0x9c, 0xed, 0x87, 0x40, 0x8c, 0xe2, 0xcb, 0x93, 0x14, 0xc9, 0x61, +// 0x2e, 0xe5, 0xcc, 0xf6, 0x5e, 0xa8, 0x5c, 0xd6, 0x75, 0x8d, 0x62, 0x95, 0x58, 0x69, 0x76, 0xa1, +// 0x4a, 0xb5, 0x55, 0x09, 0x78, 0x33, 0x82, 0xd7, 0xdd, 0x79, 0xf5, 0x1b, 0x0b, 0xde, 0x26, 0x21, +// 0x28, 0x74, 0x04, 0x97, 0x56, 0xdf, 0x3c, 0xf0, 0x37, 0x39, 0xdc, 0xff, 0x06, 0xa4, 0xea, 0x42, +// 0x08, 0xda, 0xb4, 0x71, 0xb0, 0xcf, 0x12, 0x7a, 0x4e, 0xfa, 0x6c, 0x1d, 0x84, 0x00, 0xc8, 0x7f, +// 0x91, 0x45, 0xaa, 0x2b, 0xc2, 0xb1, 0x8f, 0xd5, 0xba, 0xf2, 0xad, 0x19, 0xb2, 0x67, 0x36, 0xf7, +// 0x0f, 0x0a, 0x92, 0x7d, 0xe3, 0x9d, 0xe9, 0x90, 0x3e, 0x23, 0x27, 0x66, 0x13, 0xec, 0x81, 0x15, +// 0xbd, 0x22, 0xbf, 0x9f, 0x7e, 0xa9, 0x51, 0x4b, 0x4c, 0xfb, 0x02, 0xd3, 0x70, 0x86, 0x31, 0xe7, +// 0x3b, 0x05, 0x03, 0x54, 0x60, 0x48, 0x65, 0x18, 0xd2, 0xcd, 0x5f, 0x32, 0x88, 0x0e, 0x35, 0xfd +// }; + + + // TODO Create named constants for all of these + public static readonly string DesEde3Cbc = PkcsObjectIdentifiers.DesEde3Cbc.Id; + public static readonly string RC2Cbc = PkcsObjectIdentifiers.RC2Cbc.Id; + public const string IdeaCbc = "1.3.6.1.4.1.188.7.1.1.2"; + public const string Cast5Cbc = "1.2.840.113533.7.66.10"; + public static readonly string Aes128Cbc = NistObjectIdentifiers.IdAes128Cbc.Id; + public static readonly string Aes192Cbc = NistObjectIdentifiers.IdAes192Cbc.Id; + public static readonly string Aes256Cbc = NistObjectIdentifiers.IdAes256Cbc.Id; + public static readonly string Camellia128Cbc = NttObjectIdentifiers.IdCamellia128Cbc.Id; + public static readonly string Camellia192Cbc = NttObjectIdentifiers.IdCamellia192Cbc.Id; + public static readonly string Camellia256Cbc = NttObjectIdentifiers.IdCamellia256Cbc.Id; + public static readonly string SeedCbc = KisaObjectIdentifiers.IdSeedCbc.Id; + + public static readonly string DesEde3Wrap = PkcsObjectIdentifiers.IdAlgCms3DesWrap.Id; + public static readonly string Aes128Wrap = NistObjectIdentifiers.IdAes128Wrap.Id; + public static readonly string Aes192Wrap = NistObjectIdentifiers.IdAes192Wrap.Id; + public static readonly string Aes256Wrap = NistObjectIdentifiers.IdAes256Wrap.Id; + public static readonly string Camellia128Wrap = NttObjectIdentifiers.IdCamellia128Wrap.Id; + public static readonly string Camellia192Wrap = NttObjectIdentifiers.IdCamellia192Wrap.Id; + public static readonly string Camellia256Wrap = NttObjectIdentifiers.IdCamellia256Wrap.Id; + public static readonly string SeedWrap = KisaObjectIdentifiers.IdNpkiAppCmsSeedWrap.Id; + + public static readonly string ECDHSha1Kdf = X9ObjectIdentifiers.DHSinglePassStdDHSha1KdfScheme.Id; + public static readonly string ECMqvSha1Kdf = X9ObjectIdentifiers.MqvSinglePassSha1KdfScheme.Id; + + internal readonly IList recipientInfoGenerators = Platform.CreateArrayList(); + internal readonly SecureRandom rand; + + internal CmsAttributeTableGenerator unprotectedAttributeGenerator = null; + + public CmsEnvelopedGenerator() + : this(new SecureRandom()) + { + } + + /// Constructor allowing specific source of randomness + /// Instance of SecureRandom to use. + public CmsEnvelopedGenerator( + SecureRandom rand) + { + this.rand = rand; + } + + public CmsAttributeTableGenerator UnprotectedAttributeGenerator + { + get { return this.unprotectedAttributeGenerator; } + set { this.unprotectedAttributeGenerator = value; } + } + + /** + * add a recipient. + * + * @param cert recipient's public key certificate + * @exception ArgumentException if there is a problem with the certificate + */ + public void AddKeyTransRecipient( + X509Certificate cert) + { + KeyTransRecipientInfoGenerator ktrig = new KeyTransRecipientInfoGenerator(); + ktrig.RecipientCert = cert; + + recipientInfoGenerators.Add(ktrig); + } + + /** + * add a recipient + * + * @param key the public key used by the recipient + * @param subKeyId the identifier for the recipient's public key + * @exception ArgumentException if there is a problem with the key + */ + public void AddKeyTransRecipient( + AsymmetricKeyParameter pubKey, + byte[] subKeyId) + { + KeyTransRecipientInfoGenerator ktrig = new KeyTransRecipientInfoGenerator(); + ktrig.RecipientPublicKey = pubKey; + ktrig.SubjectKeyIdentifier = new DerOctetString(subKeyId); + + recipientInfoGenerators.Add(ktrig); + } + + /** + * add a KEK recipient. + * @param key the secret key to use for wrapping + * @param keyIdentifier the byte string that identifies the key + */ + public void AddKekRecipient( + string keyAlgorithm, // TODO Remove need for this parameter + KeyParameter key, + byte[] keyIdentifier) + { + AddKekRecipient(keyAlgorithm, key, new KekIdentifier(keyIdentifier, null, null)); + } + + /** + * add a KEK recipient. + * @param key the secret key to use for wrapping + * @param keyIdentifier the byte string that identifies the key + */ + public void AddKekRecipient( + string keyAlgorithm, // TODO Remove need for this parameter + KeyParameter key, + KekIdentifier kekIdentifier) + { + KekRecipientInfoGenerator kekrig = new KekRecipientInfoGenerator(); + kekrig.KekIdentifier = kekIdentifier; + kekrig.KeyEncryptionKeyOID = keyAlgorithm; + kekrig.KeyEncryptionKey = key; + + recipientInfoGenerators.Add(kekrig); + } + + public void AddPasswordRecipient( + CmsPbeKey pbeKey, + string kekAlgorithmOid) + { + Pbkdf2Params p = new Pbkdf2Params(pbeKey.Salt, pbeKey.IterationCount); + + PasswordRecipientInfoGenerator prig = new PasswordRecipientInfoGenerator(); + prig.KeyDerivationAlgorithm = new AlgorithmIdentifier(PkcsObjectIdentifiers.IdPbkdf2, p); + prig.KeyEncryptionKeyOID = kekAlgorithmOid; + prig.KeyEncryptionKey = pbeKey.GetEncoded(kekAlgorithmOid); + + recipientInfoGenerators.Add(prig); + } + + /** + * Add a key agreement based recipient. + * + * @param agreementAlgorithm key agreement algorithm to use. + * @param senderPrivateKey private key to initialise sender side of agreement with. + * @param senderPublicKey sender public key to include with message. + * @param recipientCert recipient's public key certificate. + * @param cekWrapAlgorithm OID for key wrapping algorithm to use. + * @exception SecurityUtilityException if the algorithm requested cannot be found + * @exception InvalidKeyException if the keys are inappropriate for the algorithm specified + */ + public void AddKeyAgreementRecipient( + string agreementAlgorithm, + AsymmetricKeyParameter senderPrivateKey, + AsymmetricKeyParameter senderPublicKey, + X509Certificate recipientCert, + string cekWrapAlgorithm) + { + IList recipientCerts = Platform.CreateArrayList(1); + recipientCerts.Add(recipientCert); + + AddKeyAgreementRecipients(agreementAlgorithm, senderPrivateKey, senderPublicKey, + recipientCerts, cekWrapAlgorithm); + } + + /** + * Add multiple key agreement based recipients (sharing a single KeyAgreeRecipientInfo structure). + * + * @param agreementAlgorithm key agreement algorithm to use. + * @param senderPrivateKey private key to initialise sender side of agreement with. + * @param senderPublicKey sender public key to include with message. + * @param recipientCerts recipients' public key certificates. + * @param cekWrapAlgorithm OID for key wrapping algorithm to use. + * @exception SecurityUtilityException if the algorithm requested cannot be found + * @exception InvalidKeyException if the keys are inappropriate for the algorithm specified + */ + public void AddKeyAgreementRecipients( + string agreementAlgorithm, + AsymmetricKeyParameter senderPrivateKey, + AsymmetricKeyParameter senderPublicKey, + ICollection recipientCerts, + string cekWrapAlgorithm) + { + if (!senderPrivateKey.IsPrivate) + throw new ArgumentException("Expected private key", "senderPrivateKey"); + if (senderPublicKey.IsPrivate) + throw new ArgumentException("Expected public key", "senderPublicKey"); + + /* TODO + * "a recipient X.509 version 3 certificate that contains a key usage extension MUST + * assert the keyAgreement bit." + */ + + KeyAgreeRecipientInfoGenerator karig = new KeyAgreeRecipientInfoGenerator(); + karig.KeyAgreementOID = new DerObjectIdentifier(agreementAlgorithm); + karig.KeyEncryptionOID = new DerObjectIdentifier(cekWrapAlgorithm); + karig.RecipientCerts = recipientCerts; + karig.SenderKeyPair = new AsymmetricCipherKeyPair(senderPublicKey, senderPrivateKey); + + recipientInfoGenerators.Add(karig); + } + + protected internal virtual AlgorithmIdentifier GetAlgorithmIdentifier( + string encryptionOid, + KeyParameter encKey, + Asn1Encodable asn1Params, + out ICipherParameters cipherParameters) + { + Asn1Object asn1Object; + if (asn1Params != null) + { + asn1Object = asn1Params.ToAsn1Object(); + cipherParameters = ParameterUtilities.GetCipherParameters( + encryptionOid, encKey, asn1Object); + } + else + { + asn1Object = DerNull.Instance; + cipherParameters = encKey; + } + + return new AlgorithmIdentifier( + new DerObjectIdentifier(encryptionOid), + asn1Object); + } + + protected internal virtual Asn1Encodable GenerateAsn1Parameters( + string encryptionOid, + byte[] encKeyBytes) + { + Asn1Encodable asn1Params = null; + + try + { + if (encryptionOid.Equals(RC2Cbc)) + { + byte[] iv = new byte[8]; + rand.NextBytes(iv); + + // TODO Is this detailed repeat of Java version really necessary? + int effKeyBits = encKeyBytes.Length * 8; + int parameterVersion; + + if (effKeyBits < 256) + { + parameterVersion = rc2Table[effKeyBits]; + } + else + { + parameterVersion = effKeyBits; + } + + asn1Params = new RC2CbcParameter(parameterVersion, iv); + } + else + { + asn1Params = ParameterUtilities.GenerateParameters(encryptionOid, rand); + } + } + catch (SecurityUtilityException) + { + // No problem... no parameters generated + } + + return asn1Params; + } + } +} diff --git a/bc-sharp-crypto/src/cms/CMSEnvelopedHelper.cs b/bc-sharp-crypto/src/cms/CMSEnvelopedHelper.cs new file mode 100644 index 0000000..77d2da4 --- /dev/null +++ b/bc-sharp-crypto/src/cms/CMSEnvelopedHelper.cs @@ -0,0 +1,311 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Cms; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.IO; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Cms +{ + class CmsEnvelopedHelper + { + internal static readonly CmsEnvelopedHelper Instance = new CmsEnvelopedHelper(); + + private static readonly IDictionary KeySizes = Platform.CreateHashtable(); + private static readonly IDictionary BaseCipherNames = Platform.CreateHashtable(); + + static CmsEnvelopedHelper() + { + KeySizes.Add(CmsEnvelopedGenerator.DesEde3Cbc, 192); + KeySizes.Add(CmsEnvelopedGenerator.Aes128Cbc, 128); + KeySizes.Add(CmsEnvelopedGenerator.Aes192Cbc, 192); + KeySizes.Add(CmsEnvelopedGenerator.Aes256Cbc, 256); + + BaseCipherNames.Add(CmsEnvelopedGenerator.DesEde3Cbc, "DESEDE"); + BaseCipherNames.Add(CmsEnvelopedGenerator.Aes128Cbc, "AES"); + BaseCipherNames.Add(CmsEnvelopedGenerator.Aes192Cbc, "AES"); + BaseCipherNames.Add(CmsEnvelopedGenerator.Aes256Cbc, "AES"); + } + + private string GetAsymmetricEncryptionAlgName( + string encryptionAlgOid) + { + if (Asn1.Pkcs.PkcsObjectIdentifiers.RsaEncryption.Id.Equals(encryptionAlgOid)) + { + return "RSA/ECB/PKCS1Padding"; + } + + return encryptionAlgOid; + } + + internal IBufferedCipher CreateAsymmetricCipher( + string encryptionOid) + { + string asymName = GetAsymmetricEncryptionAlgName(encryptionOid); + if (!asymName.Equals(encryptionOid)) + { + try + { + return CipherUtilities.GetCipher(asymName); + } + catch (SecurityUtilityException) + { + // Ignore + } + } + return CipherUtilities.GetCipher(encryptionOid); + } + + internal IWrapper CreateWrapper( + string encryptionOid) + { + try + { + return WrapperUtilities.GetWrapper(encryptionOid); + } + catch (SecurityUtilityException) + { + return WrapperUtilities.GetWrapper(GetAsymmetricEncryptionAlgName(encryptionOid)); + } + } + + internal string GetRfc3211WrapperName( + string oid) + { + if (oid == null) + throw new ArgumentNullException("oid"); + + string alg = (string) BaseCipherNames[oid]; + + if (alg == null) + throw new ArgumentException("no name for " + oid, "oid"); + + return alg + "RFC3211Wrap"; + } + + internal int GetKeySize( + string oid) + { + if (!KeySizes.Contains(oid)) + { + throw new ArgumentException("no keysize for " + oid, "oid"); + } + + return (int) KeySizes[oid]; + } + + internal static RecipientInformationStore BuildRecipientInformationStore( + Asn1Set recipientInfos, CmsSecureReadable secureReadable) + { + IList infos = Platform.CreateArrayList(); + for (int i = 0; i != recipientInfos.Count; i++) + { + RecipientInfo info = RecipientInfo.GetInstance(recipientInfos[i]); + + ReadRecipientInfo(infos, info, secureReadable); + } + return new RecipientInformationStore(infos); + } + + private static void ReadRecipientInfo( + IList infos, RecipientInfo info, CmsSecureReadable secureReadable) + { + Asn1Encodable recipInfo = info.Info; + if (recipInfo is KeyTransRecipientInfo) + { + infos.Add(new KeyTransRecipientInformation((KeyTransRecipientInfo)recipInfo, secureReadable)); + } + else if (recipInfo is KekRecipientInfo) + { + infos.Add(new KekRecipientInformation((KekRecipientInfo)recipInfo, secureReadable)); + } + else if (recipInfo is KeyAgreeRecipientInfo) + { + KeyAgreeRecipientInformation.ReadRecipientInfo(infos, (KeyAgreeRecipientInfo)recipInfo, secureReadable); + } + else if (recipInfo is PasswordRecipientInfo) + { + infos.Add(new PasswordRecipientInformation((PasswordRecipientInfo)recipInfo, secureReadable)); + } + } + + internal class CmsAuthenticatedSecureReadable : CmsSecureReadable + { + private AlgorithmIdentifier algorithm; + private IMac mac; + private CmsReadable readable; + + internal CmsAuthenticatedSecureReadable(AlgorithmIdentifier algorithm, CmsReadable readable) + { + this.algorithm = algorithm; + this.readable = readable; + } + + public AlgorithmIdentifier Algorithm + { + get { return this.algorithm; } + } + + public object CryptoObject + { + get { return this.mac; } + } + + public CmsReadable GetReadable(KeyParameter sKey) + { + string macAlg = this.algorithm.Algorithm.Id; +// Asn1Object sParams = this.algorithm.Parameters.ToAsn1Object(); + + try + { + this.mac = MacUtilities.GetMac(macAlg); + + // FIXME Support for MAC algorithm parameters similar to cipher parameters +// ASN1Object sParams = (ASN1Object)macAlg.getParameters(); +// +// if (sParams != null && !(sParams instanceof ASN1Null)) +// { +// AlgorithmParameters params = CMSEnvelopedHelper.INSTANCE.createAlgorithmParameters(macAlg.getObjectId().getId(), provider); +// +// params.init(sParams.getEncoded(), "ASN.1"); +// +// mac.init(sKey, params.getParameterSpec(IvParameterSpec.class)); +// } +// else + { + mac.Init(sKey); + } + +// Asn1Object asn1Params = asn1Enc == null ? null : asn1Enc.ToAsn1Object(); +// +// ICipherParameters cipherParameters = sKey; +// +// if (asn1Params != null && !(asn1Params is Asn1Null)) +// { +// cipherParameters = ParameterUtilities.GetCipherParameters( +// macAlg.Algorithm, cipherParameters, asn1Params); +// } +// else +// { +// string alg = macAlg.Algorithm.Id; +// if (alg.Equals(CmsEnvelopedDataGenerator.DesEde3Cbc) +// || alg.Equals(CmsEnvelopedDataGenerator.IdeaCbc) +// || alg.Equals(CmsEnvelopedDataGenerator.Cast5Cbc)) +// { +// cipherParameters = new ParametersWithIV(cipherParameters, new byte[8]); +// } +// } +// +// mac.Init(cipherParameters); + } + catch (SecurityUtilityException e) + { + throw new CmsException("couldn't create cipher.", e); + } + catch (InvalidKeyException e) + { + throw new CmsException("key invalid in message.", e); + } + catch (IOException e) + { + throw new CmsException("error decoding algorithm parameters.", e); + } + + try + { + return new CmsProcessableInputStream( + new TeeInputStream( + readable.GetInputStream(), + new MacOutputStream(this.mac))); + } + catch (IOException e) + { + throw new CmsException("error reading content.", e); + } + } + } + + internal class CmsEnvelopedSecureReadable : CmsSecureReadable + { + private AlgorithmIdentifier algorithm; + private IBufferedCipher cipher; + private CmsReadable readable; + + internal CmsEnvelopedSecureReadable(AlgorithmIdentifier algorithm, CmsReadable readable) + { + this.algorithm = algorithm; + this.readable = readable; + } + + public AlgorithmIdentifier Algorithm + { + get { return this.algorithm; } + } + + public object CryptoObject + { + get { return this.cipher; } + } + + public CmsReadable GetReadable(KeyParameter sKey) + { + try + { + this.cipher = CipherUtilities.GetCipher(this.algorithm.Algorithm); + + Asn1Encodable asn1Enc = this.algorithm.Parameters; + Asn1Object asn1Params = asn1Enc == null ? null : asn1Enc.ToAsn1Object(); + + ICipherParameters cipherParameters = sKey; + + if (asn1Params != null && !(asn1Params is Asn1Null)) + { + cipherParameters = ParameterUtilities.GetCipherParameters( + this.algorithm.Algorithm, cipherParameters, asn1Params); + } + else + { + string alg = this.algorithm.Algorithm.Id; + if (alg.Equals(CmsEnvelopedDataGenerator.DesEde3Cbc) + || alg.Equals(CmsEnvelopedDataGenerator.IdeaCbc) + || alg.Equals(CmsEnvelopedDataGenerator.Cast5Cbc)) + { + cipherParameters = new ParametersWithIV(cipherParameters, new byte[8]); + } + } + + cipher.Init(false, cipherParameters); + } + catch (SecurityUtilityException e) + { + throw new CmsException("couldn't create cipher.", e); + } + catch (InvalidKeyException e) + { + throw new CmsException("key invalid in message.", e); + } + catch (IOException e) + { + throw new CmsException("error decoding algorithm parameters.", e); + } + + try + { + return new CmsProcessableInputStream( + new CipherStream(readable.GetInputStream(), cipher, null)); + } + catch (IOException e) + { + throw new CmsException("error reading content.", e); + } + } + } + } +} \ No newline at end of file diff --git a/bc-sharp-crypto/src/cms/CMSException.cs b/bc-sharp-crypto/src/cms/CMSException.cs new file mode 100644 index 0000000..29fe0a6 --- /dev/null +++ b/bc-sharp-crypto/src/cms/CMSException.cs @@ -0,0 +1,28 @@ +using System; + +namespace Org.BouncyCastle.Cms +{ +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT || PORTABLE) + [Serializable] +#endif + public class CmsException + : Exception + { + public CmsException() + { + } + + public CmsException( + string msg) + : base(msg) + { + } + + public CmsException( + string msg, + Exception e) + : base(msg, e) + { + } + } +} diff --git a/bc-sharp-crypto/src/cms/CMSPBEKey.cs b/bc-sharp-crypto/src/cms/CMSPBEKey.cs new file mode 100644 index 0000000..e03307e --- /dev/null +++ b/bc-sharp-crypto/src/cms/CMSPBEKey.cs @@ -0,0 +1,109 @@ +using System; + +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Utilities; + +//import javax.crypto.interfaces.PBEKey; + +namespace Org.BouncyCastle.Cms +{ + public abstract class CmsPbeKey + // TODO Create an equivalent interface somewhere? + // : PBEKey + : ICipherParameters + { + internal readonly char[] password; + internal readonly byte[] salt; + internal readonly int iterationCount; + + [Obsolete("Use version taking 'char[]' instead")] + public CmsPbeKey( + string password, + byte[] salt, + int iterationCount) + : this(password.ToCharArray(), salt, iterationCount) + { + } + + [Obsolete("Use version taking 'char[]' instead")] + public CmsPbeKey( + string password, + AlgorithmIdentifier keyDerivationAlgorithm) + : this(password.ToCharArray(), keyDerivationAlgorithm) + { + } + + public CmsPbeKey( + char[] password, + byte[] salt, + int iterationCount) + { + this.password = (char[])password.Clone(); + this.salt = Arrays.Clone(salt); + this.iterationCount = iterationCount; + } + + public CmsPbeKey( + char[] password, + AlgorithmIdentifier keyDerivationAlgorithm) + { + if (!keyDerivationAlgorithm.Algorithm.Equals(PkcsObjectIdentifiers.IdPbkdf2)) + throw new ArgumentException("Unsupported key derivation algorithm: " + + keyDerivationAlgorithm.Algorithm); + + Pbkdf2Params kdfParams = Pbkdf2Params.GetInstance( + keyDerivationAlgorithm.Parameters.ToAsn1Object()); + + this.password = (char[])password.Clone(); + this.salt = kdfParams.GetSalt(); + this.iterationCount = kdfParams.IterationCount.IntValue; + } + + ~CmsPbeKey() + { + Array.Clear(this.password, 0, this.password.Length); + } + + [Obsolete("Will be removed")] + public string Password + { + get { return new string(password); } + } + + public byte[] Salt + { + get { return Arrays.Clone(salt); } + } + + [Obsolete("Use 'Salt' property instead")] + public byte[] GetSalt() + { + return Salt; + } + + public int IterationCount + { + get { return iterationCount; } + } + + public string Algorithm + { + get { return "PKCS5S2"; } + } + + public string Format + { + get { return "RAW"; } + } + + public byte[] GetEncoded() + { + return null; + } + + internal abstract KeyParameter GetEncoded(string algorithmOid); + } +} diff --git a/bc-sharp-crypto/src/cms/CMSProcessable.cs b/bc-sharp-crypto/src/cms/CMSProcessable.cs new file mode 100644 index 0000000..41018d1 --- /dev/null +++ b/bc-sharp-crypto/src/cms/CMSProcessable.cs @@ -0,0 +1,19 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Cms +{ + public interface CmsProcessable + { + /// + /// Generic routine to copy out the data we want processed. + /// + /// + /// This routine may be called multiple times. + /// + void Write(Stream outStream); + + [Obsolete] + object GetContent(); + } +} diff --git a/bc-sharp-crypto/src/cms/CMSProcessableByteArray.cs b/bc-sharp-crypto/src/cms/CMSProcessableByteArray.cs new file mode 100644 index 0000000..a6ab9b6 --- /dev/null +++ b/bc-sharp-crypto/src/cms/CMSProcessableByteArray.cs @@ -0,0 +1,36 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Cms +{ + /** + * a holding class for a byte array of data to be processed. + */ + public class CmsProcessableByteArray + : CmsProcessable, CmsReadable + { + private readonly byte[] bytes; + + public CmsProcessableByteArray(byte[] bytes) + { + this.bytes = bytes; + } + + public virtual Stream GetInputStream() + { + return new MemoryStream(bytes, false); + } + + public virtual void Write(Stream zOut) + { + zOut.Write(bytes, 0, bytes.Length); + } + + /// A clone of the byte array + [Obsolete] + public virtual object GetContent() + { + return bytes.Clone(); + } + } +} diff --git a/bc-sharp-crypto/src/cms/CMSProcessableFile.cs b/bc-sharp-crypto/src/cms/CMSProcessableFile.cs new file mode 100644 index 0000000..c74d2a8 --- /dev/null +++ b/bc-sharp-crypto/src/cms/CMSProcessableFile.cs @@ -0,0 +1,52 @@ +#if !PORTABLE || DOTNET +using System; +using System.IO; + +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Cms +{ + /** + * a holding class for a file of data to be processed. + */ + public class CmsProcessableFile + : CmsProcessable, CmsReadable + { + private const int DefaultBufSize = 32 * 1024; + + private readonly FileInfo _file; + private readonly int _bufSize; + + public CmsProcessableFile(FileInfo file) + : this(file, DefaultBufSize) + { + } + + public CmsProcessableFile(FileInfo file, int bufSize) + { + _file = file; + _bufSize = bufSize; + } + + public virtual Stream GetInputStream() + { + return new FileStream(_file.FullName, FileMode.Open, FileAccess.Read, FileShare.Read, _bufSize); + } + + public virtual void Write(Stream zOut) + { + Stream inStr = GetInputStream(); + Streams.PipeAll(inStr, zOut); + Platform.Dispose(inStr); + } + + /// The file handle + [Obsolete] + public virtual object GetContent() + { + return _file; + } + } +} +#endif diff --git a/bc-sharp-crypto/src/cms/CMSProcessableInputStream.cs b/bc-sharp-crypto/src/cms/CMSProcessableInputStream.cs new file mode 100644 index 0000000..b2abd6f --- /dev/null +++ b/bc-sharp-crypto/src/cms/CMSProcessableInputStream.cs @@ -0,0 +1,53 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Cms +{ + public class CmsProcessableInputStream + : CmsProcessable, CmsReadable + { + private readonly Stream input; + + private bool used = false; + + public CmsProcessableInputStream(Stream input) + { + this.input = input; + } + + public virtual Stream GetInputStream() + { + CheckSingleUsage(); + + return input; + } + + public virtual void Write(Stream output) + { + CheckSingleUsage(); + + Streams.PipeAll(input, output); + Platform.Dispose(input); + } + + [Obsolete] + public virtual object GetContent() + { + return GetInputStream(); + } + + protected virtual void CheckSingleUsage() + { + lock (this) + { + if (used) + throw new InvalidOperationException("CmsProcessableInputStream can only be used once"); + + used = true; + } + } + } +} diff --git a/bc-sharp-crypto/src/cms/CMSReadable.cs b/bc-sharp-crypto/src/cms/CMSReadable.cs new file mode 100644 index 0000000..ad83ba0 --- /dev/null +++ b/bc-sharp-crypto/src/cms/CMSReadable.cs @@ -0,0 +1,10 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Cms +{ + public interface CmsReadable + { + Stream GetInputStream(); + } +} diff --git a/bc-sharp-crypto/src/cms/CMSSecureReadable.cs b/bc-sharp-crypto/src/cms/CMSSecureReadable.cs new file mode 100644 index 0000000..5ceac24 --- /dev/null +++ b/bc-sharp-crypto/src/cms/CMSSecureReadable.cs @@ -0,0 +1,14 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto.Parameters; + +namespace Org.BouncyCastle.Cms +{ + internal interface CmsSecureReadable + { + AlgorithmIdentifier Algorithm { get; } + object CryptoObject { get; } + CmsReadable GetReadable(KeyParameter key); + } +} diff --git a/bc-sharp-crypto/src/cms/CMSSignedData.cs b/bc-sharp-crypto/src/cms/CMSSignedData.cs new file mode 100644 index 0000000..237c152 --- /dev/null +++ b/bc-sharp-crypto/src/cms/CMSSignedData.cs @@ -0,0 +1,425 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Cms; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Security.Certificates; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.X509; +using Org.BouncyCastle.X509.Store; + +namespace Org.BouncyCastle.Cms +{ + /** + * general class for handling a pkcs7-signature message. + * + * A simple example of usage - note, in the example below the validity of + * the certificate isn't verified, just the fact that one of the certs + * matches the given signer... + * + *
+	*  IX509Store              certs = s.GetCertificates();
+	*  SignerInformationStore  signers = s.GetSignerInfos();
+	*
+	*  foreach (SignerInformation signer in signers.GetSigners())
+	*  {
+	*      ArrayList       certList = new ArrayList(certs.GetMatches(signer.SignerID));
+	*      X509Certificate cert = (X509Certificate) certList[0];
+	*
+	*      if (signer.Verify(cert.GetPublicKey()))
+	*      {
+	*          verified++;
+	*      }
+	*  }
+	* 
+ */ + public class CmsSignedData + { + private static readonly CmsSignedHelper Helper = CmsSignedHelper.Instance; + + private readonly CmsProcessable signedContent; + private SignedData signedData; + private ContentInfo contentInfo; + private SignerInformationStore signerInfoStore; + private IX509Store attrCertStore; + private IX509Store certificateStore; + private IX509Store crlStore; + private IDictionary hashes; + + private CmsSignedData( + CmsSignedData c) + { + this.signedData = c.signedData; + this.contentInfo = c.contentInfo; + this.signedContent = c.signedContent; + this.signerInfoStore = c.signerInfoStore; + } + + public CmsSignedData( + byte[] sigBlock) + : this(CmsUtilities.ReadContentInfo(new MemoryStream(sigBlock, false))) + { + } + + public CmsSignedData( + CmsProcessable signedContent, + byte[] sigBlock) + : this(signedContent, CmsUtilities.ReadContentInfo(new MemoryStream(sigBlock, false))) + { + } + + /** + * Content with detached signature, digests precomputed + * + * @param hashes a map of precomputed digests for content indexed by name of hash. + * @param sigBlock the signature object. + */ + public CmsSignedData( + IDictionary hashes, + byte[] sigBlock) + : this(hashes, CmsUtilities.ReadContentInfo(sigBlock)) + { + } + + /** + * base constructor - content with detached signature. + * + * @param signedContent the content that was signed. + * @param sigData the signature object. + */ + public CmsSignedData( + CmsProcessable signedContent, + Stream sigData) + : this(signedContent, CmsUtilities.ReadContentInfo(sigData)) + { + } + + /** + * base constructor - with encapsulated content + */ + public CmsSignedData( + Stream sigData) + : this(CmsUtilities.ReadContentInfo(sigData)) + { + } + + public CmsSignedData( + CmsProcessable signedContent, + ContentInfo sigData) + { + this.signedContent = signedContent; + this.contentInfo = sigData; + this.signedData = SignedData.GetInstance(contentInfo.Content); + } + + public CmsSignedData( + IDictionary hashes, + ContentInfo sigData) + { + this.hashes = hashes; + this.contentInfo = sigData; + this.signedData = SignedData.GetInstance(contentInfo.Content); + } + + public CmsSignedData( + ContentInfo sigData) + { + this.contentInfo = sigData; + this.signedData = SignedData.GetInstance(contentInfo.Content); + + // + // this can happen if the signed message is sent simply to send a + // certificate chain. + // + if (signedData.EncapContentInfo.Content != null) + { + this.signedContent = new CmsProcessableByteArray( + ((Asn1OctetString)(signedData.EncapContentInfo.Content)).GetOctets()); + } +// else +// { +// this.signedContent = null; +// } + } + + /// Return the version number for this object. + public int Version + { + get { return signedData.Version.Value.IntValue; } + } + + /** + * return the collection of signers that are associated with the + * signatures for the message. + */ + public SignerInformationStore GetSignerInfos() + { + if (signerInfoStore == null) + { + IList signerInfos = Platform.CreateArrayList(); + Asn1Set s = signedData.SignerInfos; + + foreach (object obj in s) + { + SignerInfo info = SignerInfo.GetInstance(obj); + DerObjectIdentifier contentType = signedData.EncapContentInfo.ContentType; + + if (hashes == null) + { + signerInfos.Add(new SignerInformation(info, contentType, signedContent, null)); + } + else + { + byte[] hash = (byte[])hashes[info.DigestAlgorithm.Algorithm.Id]; + + signerInfos.Add(new SignerInformation(info, contentType, null, new BaseDigestCalculator(hash))); + } + } + + signerInfoStore = new SignerInformationStore(signerInfos); + } + + return signerInfoStore; + } + + /** + * return a X509Store containing the attribute certificates, if any, contained + * in this message. + * + * @param type type of store to create + * @return a store of attribute certificates + * @exception NoSuchStoreException if the store type isn't available. + * @exception CmsException if a general exception prevents creation of the X509Store + */ + public IX509Store GetAttributeCertificates( + string type) + { + if (attrCertStore == null) + { + attrCertStore = Helper.CreateAttributeStore(type, signedData.Certificates); + } + + return attrCertStore; + } + + /** + * return a X509Store containing the public key certificates, if any, contained + * in this message. + * + * @param type type of store to create + * @return a store of public key certificates + * @exception NoSuchStoreException if the store type isn't available. + * @exception CmsException if a general exception prevents creation of the X509Store + */ + public IX509Store GetCertificates( + string type) + { + if (certificateStore == null) + { + certificateStore = Helper.CreateCertificateStore(type, signedData.Certificates); + } + + return certificateStore; + } + + /** + * return a X509Store containing CRLs, if any, contained + * in this message. + * + * @param type type of store to create + * @return a store of CRLs + * @exception NoSuchStoreException if the store type isn't available. + * @exception CmsException if a general exception prevents creation of the X509Store + */ + public IX509Store GetCrls( + string type) + { + if (crlStore == null) + { + crlStore = Helper.CreateCrlStore(type, signedData.CRLs); + } + + return crlStore; + } + + [Obsolete("Use 'SignedContentType' property instead.")] + public string SignedContentTypeOid + { + get { return signedData.EncapContentInfo.ContentType.Id; } + } + + /// + /// Return the DerObjectIdentifier associated with the encapsulated + /// content info structure carried in the signed data. + /// + public DerObjectIdentifier SignedContentType + { + get { return signedData.EncapContentInfo.ContentType; } + } + + public CmsProcessable SignedContent + { + get { return signedContent; } + } + + /** + * return the ContentInfo + */ + public ContentInfo ContentInfo + { + get { return contentInfo; } + } + + /** + * return the ASN.1 encoded representation of this object. + */ + public byte[] GetEncoded() + { + return contentInfo.GetEncoded(); + } + + /** + * Replace the signerinformation store associated with this + * CmsSignedData object with the new one passed in. You would + * probably only want to do this if you wanted to change the unsigned + * attributes associated with a signer, or perhaps delete one. + * + * @param signedData the signed data object to be used as a base. + * @param signerInformationStore the new signer information store to use. + * @return a new signed data object. + */ + public static CmsSignedData ReplaceSigners( + CmsSignedData signedData, + SignerInformationStore signerInformationStore) + { + // + // copy + // + CmsSignedData cms = new CmsSignedData(signedData); + + // + // replace the store + // + cms.signerInfoStore = signerInformationStore; + + // + // replace the signers in the SignedData object + // + Asn1EncodableVector digestAlgs = new Asn1EncodableVector(); + Asn1EncodableVector vec = new Asn1EncodableVector(); + + foreach (SignerInformation signer in signerInformationStore.GetSigners()) + { + digestAlgs.Add(Helper.FixAlgID(signer.DigestAlgorithmID)); + vec.Add(signer.ToSignerInfo()); + } + + Asn1Set digests = new DerSet(digestAlgs); + Asn1Set signers = new DerSet(vec); + Asn1Sequence sD = (Asn1Sequence)signedData.signedData.ToAsn1Object(); + + // + // signers are the last item in the sequence. + // + vec = new Asn1EncodableVector( + sD[0], // version + digests); + + for (int i = 2; i != sD.Count - 1; i++) + { + vec.Add(sD[i]); + } + + vec.Add(signers); + + cms.signedData = SignedData.GetInstance(new BerSequence(vec)); + + // + // replace the contentInfo with the new one + // + cms.contentInfo = new ContentInfo(cms.contentInfo.ContentType, cms.signedData); + + return cms; + } + + /** + * Replace the certificate and CRL information associated with this + * CmsSignedData object with the new one passed in. + * + * @param signedData the signed data object to be used as a base. + * @param x509Certs the new certificates to be used. + * @param x509Crls the new CRLs to be used. + * @return a new signed data object. + * @exception CmsException if there is an error processing the stores + */ + public static CmsSignedData ReplaceCertificatesAndCrls( + CmsSignedData signedData, + IX509Store x509Certs, + IX509Store x509Crls, + IX509Store x509AttrCerts) + { + if (x509AttrCerts != null) + throw Platform.CreateNotImplementedException("Currently can't replace attribute certificates"); + + // + // copy + // + CmsSignedData cms = new CmsSignedData(signedData); + + // + // replace the certs and crls in the SignedData object + // + Asn1Set certs = null; + try + { + Asn1Set asn1Set = CmsUtilities.CreateBerSetFromList( + CmsUtilities.GetCertificatesFromStore(x509Certs)); + + if (asn1Set.Count != 0) + { + certs = asn1Set; + } + } + catch (X509StoreException e) + { + throw new CmsException("error getting certificates from store", e); + } + + Asn1Set crls = null; + try + { + Asn1Set asn1Set = CmsUtilities.CreateBerSetFromList( + CmsUtilities.GetCrlsFromStore(x509Crls)); + + if (asn1Set.Count != 0) + { + crls = asn1Set; + } + } + catch (X509StoreException e) + { + throw new CmsException("error getting CRLs from store", e); + } + + // + // replace the CMS structure. + // + SignedData old = signedData.signedData; + cms.signedData = new SignedData( + old.DigestAlgorithms, + old.EncapContentInfo, + certs, + crls, + old.SignerInfos); + + // + // replace the contentInfo with the new one + // + cms.contentInfo = new ContentInfo(cms.contentInfo.ContentType, cms.signedData); + + return cms; + } + } +} diff --git a/bc-sharp-crypto/src/cms/CMSSignedDataGenerator.cs b/bc-sharp-crypto/src/cms/CMSSignedDataGenerator.cs new file mode 100644 index 0000000..f63ed87 --- /dev/null +++ b/bc-sharp-crypto/src/cms/CMSSignedDataGenerator.cs @@ -0,0 +1,585 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Cms; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Security.Certificates; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.X509; +using Org.BouncyCastle.Crypto.Operators; + +namespace Org.BouncyCastle.Cms +{ + /** + * general class for generating a pkcs7-signature message. + *

+ * A simple example of usage. + * + *

+     *      IX509Store certs...
+     *      IX509Store crls...
+     *      CmsSignedDataGenerator gen = new CmsSignedDataGenerator();
+     *
+     *      gen.AddSigner(privKey, cert, CmsSignedGenerator.DigestSha1);
+     *      gen.AddCertificates(certs);
+     *      gen.AddCrls(crls);
+     *
+     *      CmsSignedData data = gen.Generate(content);
+     * 
+ *

+ */ + public class CmsSignedDataGenerator + : CmsSignedGenerator + { + private static readonly CmsSignedHelper Helper = CmsSignedHelper.Instance; + + private readonly IList signerInfs = Platform.CreateArrayList(); + + private class SignerInf + { + private readonly CmsSignedGenerator outer; + + private readonly ISignatureFactory sigCalc; + private readonly SignerIdentifier signerIdentifier; + private readonly string digestOID; + private readonly string encOID; + private readonly CmsAttributeTableGenerator sAttr; + private readonly CmsAttributeTableGenerator unsAttr; + private readonly Asn1.Cms.AttributeTable baseSignedTable; + + internal SignerInf( + CmsSignedGenerator outer, + AsymmetricKeyParameter key, + SignerIdentifier signerIdentifier, + string digestOID, + string encOID, + CmsAttributeTableGenerator sAttr, + CmsAttributeTableGenerator unsAttr, + Asn1.Cms.AttributeTable baseSignedTable) + { + string digestName = Helper.GetDigestAlgName(digestOID); + + string signatureName = digestName + "with" + Helper.GetEncryptionAlgName(encOID); + + this.outer = outer; + this.sigCalc = new Asn1SignatureFactory(signatureName, key); + this.signerIdentifier = signerIdentifier; + this.digestOID = digestOID; + this.encOID = encOID; + this.sAttr = sAttr; + this.unsAttr = unsAttr; + this.baseSignedTable = baseSignedTable; + } + + internal SignerInf( + CmsSignedGenerator outer, + ISignatureFactory sigCalc, + SignerIdentifier signerIdentifier, + CmsAttributeTableGenerator sAttr, + CmsAttributeTableGenerator unsAttr, + Asn1.Cms.AttributeTable baseSignedTable) + { + this.outer = outer; + this.sigCalc = sigCalc; + this.signerIdentifier = signerIdentifier; + this.digestOID = new DefaultDigestAlgorithmIdentifierFinder().find((AlgorithmIdentifier)sigCalc.AlgorithmDetails).Algorithm.Id; + this.encOID = ((AlgorithmIdentifier)sigCalc.AlgorithmDetails).Algorithm.Id; + this.sAttr = sAttr; + this.unsAttr = unsAttr; + this.baseSignedTable = baseSignedTable; + } + + internal AlgorithmIdentifier DigestAlgorithmID + { + get { return new AlgorithmIdentifier(new DerObjectIdentifier(digestOID), DerNull.Instance); } + } + + internal CmsAttributeTableGenerator SignedAttributes + { + get { return sAttr; } + } + + internal CmsAttributeTableGenerator UnsignedAttributes + { + get { return unsAttr; } + } + + internal SignerInfo ToSignerInfo( + DerObjectIdentifier contentType, + CmsProcessable content, + SecureRandom random) + { + AlgorithmIdentifier digAlgId = DigestAlgorithmID; + string digestName = Helper.GetDigestAlgName(digestOID); + + string signatureName = digestName + "with" + Helper.GetEncryptionAlgName(encOID); + + byte[] hash; + if (outer._digests.Contains(digestOID)) + { + hash = (byte[])outer._digests[digestOID]; + } + else + { + IDigest dig = Helper.GetDigestInstance(digestName); + if (content != null) + { + content.Write(new DigOutputStream(dig)); + } + hash = DigestUtilities.DoFinal(dig); + outer._digests.Add(digestOID, hash.Clone()); + } + + IStreamCalculator calculator = sigCalc.CreateCalculator(); + +#if NETCF_1_0 || NETCF_2_0 || SILVERLIGHT || PORTABLE + Stream sigStr = calculator.Stream; +#else + Stream sigStr = new BufferedStream(calculator.Stream); +#endif + + Asn1Set signedAttr = null; + if (sAttr != null) + { + IDictionary parameters = outer.GetBaseParameters(contentType, digAlgId, hash); + +// Asn1.Cms.AttributeTable signed = sAttr.GetAttributes(Collections.unmodifiableMap(parameters)); + Asn1.Cms.AttributeTable signed = sAttr.GetAttributes(parameters); + + if (contentType == null) //counter signature + { + if (signed != null && signed[CmsAttributes.ContentType] != null) + { + IDictionary tmpSigned = signed.ToDictionary(); + tmpSigned.Remove(CmsAttributes.ContentType); + signed = new Asn1.Cms.AttributeTable(tmpSigned); + } + } + + // TODO Validate proposed signed attributes + + signedAttr = outer.GetAttributeSet(signed); + + // sig must be composed from the DER encoding. + new DerOutputStream(sigStr).WriteObject(signedAttr); + } + else if (content != null) + { + // TODO Use raw signature of the hash value instead + content.Write(sigStr); + } + + Platform.Dispose(sigStr); + byte[] sigBytes = ((IBlockResult)calculator.GetResult()).Collect(); + + Asn1Set unsignedAttr = null; + if (unsAttr != null) + { + IDictionary baseParameters = outer.GetBaseParameters(contentType, digAlgId, hash); + baseParameters[CmsAttributeTableParameter.Signature] = sigBytes.Clone(); + +// Asn1.Cms.AttributeTable unsigned = unsAttr.GetAttributes(Collections.unmodifiableMap(baseParameters)); + Asn1.Cms.AttributeTable unsigned = unsAttr.GetAttributes(baseParameters); + + // TODO Validate proposed unsigned attributes + + unsignedAttr = outer.GetAttributeSet(unsigned); + } + + // TODO[RSAPSS] Need the ability to specify non-default parameters + Asn1Encodable sigX509Parameters = SignerUtilities.GetDefaultX509Parameters(signatureName); + AlgorithmIdentifier encAlgId = Helper.GetEncAlgorithmIdentifier( + new DerObjectIdentifier(encOID), sigX509Parameters); + + return new SignerInfo(signerIdentifier, digAlgId, + signedAttr, encAlgId, new DerOctetString(sigBytes), unsignedAttr); + } + } + + public CmsSignedDataGenerator() + { + } + + /// Constructor allowing specific source of randomness + /// Instance of SecureRandom to use. + public CmsSignedDataGenerator( + SecureRandom rand) + : base(rand) + { + } + + /** + * add a signer - no attributes other than the default ones will be + * provided here. + * + * @param key signing key to use + * @param cert certificate containing corresponding public key + * @param digestOID digest algorithm OID + */ + public void AddSigner( + AsymmetricKeyParameter privateKey, + X509Certificate cert, + string digestOID) + { + AddSigner(privateKey, cert, Helper.GetEncOid(privateKey, digestOID), digestOID); + } + + /** + * add a signer, specifying the digest encryption algorithm to use - no attributes other than the default ones will be + * provided here. + * + * @param key signing key to use + * @param cert certificate containing corresponding public key + * @param encryptionOID digest encryption algorithm OID + * @param digestOID digest algorithm OID + */ + public void AddSigner( + AsymmetricKeyParameter privateKey, + X509Certificate cert, + string encryptionOID, + string digestOID) + { + doAddSigner(privateKey, GetSignerIdentifier(cert), encryptionOID, digestOID, + new DefaultSignedAttributeTableGenerator(), null, null); + } + + /** + * add a signer - no attributes other than the default ones will be + * provided here. + */ + public void AddSigner( + AsymmetricKeyParameter privateKey, + byte[] subjectKeyID, + string digestOID) + { + AddSigner(privateKey, subjectKeyID, Helper.GetEncOid(privateKey, digestOID), digestOID); + } + + /** + * add a signer, specifying the digest encryption algorithm to use - no attributes other than the default ones will be + * provided here. + */ + public void AddSigner( + AsymmetricKeyParameter privateKey, + byte[] subjectKeyID, + string encryptionOID, + string digestOID) + { + doAddSigner(privateKey, GetSignerIdentifier(subjectKeyID), encryptionOID, digestOID, + new DefaultSignedAttributeTableGenerator(), null, null); + } + + /** + * add a signer with extra signed/unsigned attributes. + * + * @param key signing key to use + * @param cert certificate containing corresponding public key + * @param digestOID digest algorithm OID + * @param signedAttr table of attributes to be included in signature + * @param unsignedAttr table of attributes to be included as unsigned + */ + public void AddSigner( + AsymmetricKeyParameter privateKey, + X509Certificate cert, + string digestOID, + Asn1.Cms.AttributeTable signedAttr, + Asn1.Cms.AttributeTable unsignedAttr) + { + AddSigner(privateKey, cert, Helper.GetEncOid(privateKey, digestOID), digestOID, + signedAttr, unsignedAttr); + } + + /** + * add a signer, specifying the digest encryption algorithm, with extra signed/unsigned attributes. + * + * @param key signing key to use + * @param cert certificate containing corresponding public key + * @param encryptionOID digest encryption algorithm OID + * @param digestOID digest algorithm OID + * @param signedAttr table of attributes to be included in signature + * @param unsignedAttr table of attributes to be included as unsigned + */ + public void AddSigner( + AsymmetricKeyParameter privateKey, + X509Certificate cert, + string encryptionOID, + string digestOID, + Asn1.Cms.AttributeTable signedAttr, + Asn1.Cms.AttributeTable unsignedAttr) + { + doAddSigner(privateKey, GetSignerIdentifier(cert), encryptionOID, digestOID, + new DefaultSignedAttributeTableGenerator(signedAttr), + new SimpleAttributeTableGenerator(unsignedAttr), + signedAttr); + } + + /** + * add a signer with extra signed/unsigned attributes. + * + * @param key signing key to use + * @param subjectKeyID subjectKeyID of corresponding public key + * @param digestOID digest algorithm OID + * @param signedAttr table of attributes to be included in signature + * @param unsignedAttr table of attributes to be included as unsigned + */ + public void AddSigner( + AsymmetricKeyParameter privateKey, + byte[] subjectKeyID, + string digestOID, + Asn1.Cms.AttributeTable signedAttr, + Asn1.Cms.AttributeTable unsignedAttr) + { + AddSigner(privateKey, subjectKeyID, Helper.GetEncOid(privateKey, digestOID), digestOID, + signedAttr, unsignedAttr); + } + + /** + * add a signer, specifying the digest encryption algorithm, with extra signed/unsigned attributes. + * + * @param key signing key to use + * @param subjectKeyID subjectKeyID of corresponding public key + * @param encryptionOID digest encryption algorithm OID + * @param digestOID digest algorithm OID + * @param signedAttr table of attributes to be included in signature + * @param unsignedAttr table of attributes to be included as unsigned + */ + public void AddSigner( + AsymmetricKeyParameter privateKey, + byte[] subjectKeyID, + string encryptionOID, + string digestOID, + Asn1.Cms.AttributeTable signedAttr, + Asn1.Cms.AttributeTable unsignedAttr) + { + doAddSigner(privateKey, GetSignerIdentifier(subjectKeyID), encryptionOID, digestOID, + new DefaultSignedAttributeTableGenerator(signedAttr), + new SimpleAttributeTableGenerator(unsignedAttr), + signedAttr); + } + + /** + * add a signer with extra signed/unsigned attributes based on generators. + */ + public void AddSigner( + AsymmetricKeyParameter privateKey, + X509Certificate cert, + string digestOID, + CmsAttributeTableGenerator signedAttrGen, + CmsAttributeTableGenerator unsignedAttrGen) + { + AddSigner(privateKey, cert, Helper.GetEncOid(privateKey, digestOID), digestOID, + signedAttrGen, unsignedAttrGen); + } + + /** + * add a signer, specifying the digest encryption algorithm, with extra signed/unsigned attributes based on generators. + */ + public void AddSigner( + AsymmetricKeyParameter privateKey, + X509Certificate cert, + string encryptionOID, + string digestOID, + CmsAttributeTableGenerator signedAttrGen, + CmsAttributeTableGenerator unsignedAttrGen) + { + doAddSigner(privateKey, GetSignerIdentifier(cert), encryptionOID, digestOID, signedAttrGen, + unsignedAttrGen, null); + } + + /** + * add a signer with extra signed/unsigned attributes based on generators. + */ + public void AddSigner( + AsymmetricKeyParameter privateKey, + byte[] subjectKeyID, + string digestOID, + CmsAttributeTableGenerator signedAttrGen, + CmsAttributeTableGenerator unsignedAttrGen) + { + AddSigner(privateKey, subjectKeyID, Helper.GetEncOid(privateKey, digestOID), digestOID, + signedAttrGen, unsignedAttrGen); + } + + /** + * add a signer, including digest encryption algorithm, with extra signed/unsigned attributes based on generators. + */ + public void AddSigner( + AsymmetricKeyParameter privateKey, + byte[] subjectKeyID, + string encryptionOID, + string digestOID, + CmsAttributeTableGenerator signedAttrGen, + CmsAttributeTableGenerator unsignedAttrGen) + { + doAddSigner(privateKey, GetSignerIdentifier(subjectKeyID), encryptionOID, digestOID, + signedAttrGen, unsignedAttrGen, null); + } + + public void AddSignerInfoGenerator(SignerInfoGenerator signerInfoGenerator) + { + signerInfs.Add(new SignerInf(this, signerInfoGenerator.contentSigner, signerInfoGenerator.sigId, + signerInfoGenerator.signedGen, signerInfoGenerator.unsignedGen, null)); + } + + private void doAddSigner( + AsymmetricKeyParameter privateKey, + SignerIdentifier signerIdentifier, + string encryptionOID, + string digestOID, + CmsAttributeTableGenerator signedAttrGen, + CmsAttributeTableGenerator unsignedAttrGen, + Asn1.Cms.AttributeTable baseSignedTable) + { + signerInfs.Add(new SignerInf(this, privateKey, signerIdentifier, digestOID, encryptionOID, + signedAttrGen, unsignedAttrGen, baseSignedTable)); + } + + /** + * generate a signed object that for a CMS Signed Data object + */ + public CmsSignedData Generate( + CmsProcessable content) + { + return Generate(content, false); + } + + /** + * generate a signed object that for a CMS Signed Data + * object - if encapsulate is true a copy + * of the message will be included in the signature. The content type + * is set according to the OID represented by the string signedContentType. + */ + public CmsSignedData Generate( + string signedContentType, + // FIXME Avoid accessing more than once to support CmsProcessableInputStream + CmsProcessable content, + bool encapsulate) + { + Asn1EncodableVector digestAlgs = new Asn1EncodableVector(); + Asn1EncodableVector signerInfos = new Asn1EncodableVector(); + + _digests.Clear(); // clear the current preserved digest state + + // + // add the precalculated SignerInfo objects. + // + foreach (SignerInformation signer in _signers) + { + digestAlgs.Add(Helper.FixAlgID(signer.DigestAlgorithmID)); + + // TODO Verify the content type and calculated digest match the precalculated SignerInfo + signerInfos.Add(signer.ToSignerInfo()); + } + + // + // add the SignerInfo objects + // + bool isCounterSignature = (signedContentType == null); + + DerObjectIdentifier contentTypeOid = isCounterSignature + ? null + : new DerObjectIdentifier(signedContentType); + + foreach (SignerInf signer in signerInfs) + { + try + { + digestAlgs.Add(signer.DigestAlgorithmID); + signerInfos.Add(signer.ToSignerInfo(contentTypeOid, content, rand)); + } + catch (IOException e) + { + throw new CmsException("encoding error.", e); + } + catch (InvalidKeyException e) + { + throw new CmsException("key inappropriate for signature.", e); + } + catch (SignatureException e) + { + throw new CmsException("error creating signature.", e); + } + catch (CertificateEncodingException e) + { + throw new CmsException("error creating sid.", e); + } + } + + Asn1Set certificates = null; + + if (_certs.Count != 0) + { + certificates = CmsUtilities.CreateBerSetFromList(_certs); + } + + Asn1Set certrevlist = null; + + if (_crls.Count != 0) + { + certrevlist = CmsUtilities.CreateBerSetFromList(_crls); + } + + Asn1OctetString octs = null; + if (encapsulate) + { + MemoryStream bOut = new MemoryStream(); + if (content != null) + { + try + { + content.Write(bOut); + } + catch (IOException e) + { + throw new CmsException("encapsulation error.", e); + } + } + octs = new BerOctetString(bOut.ToArray()); + } + + ContentInfo encInfo = new ContentInfo(contentTypeOid, octs); + + SignedData sd = new SignedData( + new DerSet(digestAlgs), + encInfo, + certificates, + certrevlist, + new DerSet(signerInfos)); + + ContentInfo contentInfo = new ContentInfo(CmsObjectIdentifiers.SignedData, sd); + + return new CmsSignedData(content, contentInfo); + } + + /** + * generate a signed object that for a CMS Signed Data + * object - if encapsulate is true a copy + * of the message will be included in the signature with the + * default content type "data". + */ + public CmsSignedData Generate( + CmsProcessable content, + bool encapsulate) + { + return this.Generate(Data, content, encapsulate); + } + + /** + * generate a set of one or more SignerInformation objects representing counter signatures on + * the passed in SignerInformation object. + * + * @param signer the signer to be countersigned + * @param sigProvider the provider to be used for counter signing. + * @return a store containing the signers. + */ + public SignerInformationStore GenerateCounterSigners( + SignerInformation signer) + { + return this.Generate(null, new CmsProcessableByteArray(signer.GetSignature()), false).GetSignerInfos(); + } + } +} diff --git a/bc-sharp-crypto/src/cms/CMSSignedDataParser.cs b/bc-sharp-crypto/src/cms/CMSSignedDataParser.cs new file mode 100644 index 0000000..fb51ab1 --- /dev/null +++ b/bc-sharp-crypto/src/cms/CMSSignedDataParser.cs @@ -0,0 +1,450 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Cms; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.IO; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Security.Certificates; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; +using Org.BouncyCastle.Utilities.IO; +using Org.BouncyCastle.X509; +using Org.BouncyCastle.X509.Store; + +namespace Org.BouncyCastle.Cms +{ + /** + * Parsing class for an CMS Signed Data object from an input stream. + *

+ * Note: that because we are in a streaming mode only one signer can be tried and it is important + * that the methods on the parser are called in the appropriate order. + *

+ *

+ * A simple example of usage for an encapsulated signature. + *

+ *

+ * Two notes: first, in the example below the validity of + * the certificate isn't verified, just the fact that one of the certs + * matches the given signer, and, second, because we are in a streaming + * mode the order of the operations is important. + *

+ *
+	*      CmsSignedDataParser     sp = new CmsSignedDataParser(encapSigData);
+	*
+	*      sp.GetSignedContent().Drain();
+	*
+	*      IX509Store              certs = sp.GetCertificates();
+	*      SignerInformationStore  signers = sp.GetSignerInfos();
+	*
+	*      foreach (SignerInformation signer in signers.GetSigners())
+	*      {
+	*          ArrayList       certList = new ArrayList(certs.GetMatches(signer.SignerID));
+	*          X509Certificate cert = (X509Certificate) certList[0];
+	*
+	*          Console.WriteLine("verify returns: " + signer.Verify(cert));
+	*      }
+	* 
+ * Note also: this class does not introduce buffering - if you are processing large files you should create + * the parser with: + *
+	*          CmsSignedDataParser     ep = new CmsSignedDataParser(new BufferedInputStream(encapSigData, bufSize));
+	*  
+ * where bufSize is a suitably large buffer size. + */ + public class CmsSignedDataParser + : CmsContentInfoParser + { + private static readonly CmsSignedHelper Helper = CmsSignedHelper.Instance; + + private SignedDataParser _signedData; + private DerObjectIdentifier _signedContentType; + private CmsTypedStream _signedContent; + private IDictionary _digests; + private ISet _digestOids; + + private SignerInformationStore _signerInfoStore; + private Asn1Set _certSet, _crlSet; + private bool _isCertCrlParsed; + private IX509Store _attributeStore; + private IX509Store _certificateStore; + private IX509Store _crlStore; + + public CmsSignedDataParser( + byte[] sigBlock) + : this(new MemoryStream(sigBlock, false)) + { + } + + public CmsSignedDataParser( + CmsTypedStream signedContent, + byte[] sigBlock) + : this(signedContent, new MemoryStream(sigBlock, false)) + { + } + + /** + * base constructor - with encapsulated content + */ + public CmsSignedDataParser( + Stream sigData) + : this(null, sigData) + { + } + + /** + * base constructor + * + * @param signedContent the content that was signed. + * @param sigData the signature object. + */ + public CmsSignedDataParser( + CmsTypedStream signedContent, + Stream sigData) + : base(sigData) + { + try + { + this._signedContent = signedContent; + this._signedData = SignedDataParser.GetInstance(this.contentInfo.GetContent(Asn1Tags.Sequence)); + this._digests = Platform.CreateHashtable(); + this._digestOids = new HashSet(); + + Asn1SetParser digAlgs = _signedData.GetDigestAlgorithms(); + IAsn1Convertible o; + + while ((o = digAlgs.ReadObject()) != null) + { + AlgorithmIdentifier id = AlgorithmIdentifier.GetInstance(o.ToAsn1Object()); + + try + { + string digestOid = id.Algorithm.Id; + string digestName = Helper.GetDigestAlgName(digestOid); + + if (!this._digests.Contains(digestName)) + { + this._digests[digestName] = Helper.GetDigestInstance(digestName); + this._digestOids.Add(digestOid); + } + } + catch (SecurityUtilityException) + { + // TODO Should do something other than ignore it + } + } + + // + // If the message is simply a certificate chain message GetContent() may return null. + // + ContentInfoParser cont = _signedData.GetEncapContentInfo(); + Asn1OctetStringParser octs = (Asn1OctetStringParser) + cont.GetContent(Asn1Tags.OctetString); + + if (octs != null) + { + CmsTypedStream ctStr = new CmsTypedStream( + cont.ContentType.Id, octs.GetOctetStream()); + + if (_signedContent == null) + { + this._signedContent = ctStr; + } + else + { + // + // content passed in, need to read past empty encapsulated content info object if present + // + ctStr.Drain(); + } + } + + _signedContentType = _signedContent == null + ? cont.ContentType + : new DerObjectIdentifier(_signedContent.ContentType); + } + catch (IOException e) + { + throw new CmsException("io exception: " + e.Message, e); + } + } + + /** + * Return the version number for the SignedData object + * + * @return the version number + */ + public int Version + { + get { return _signedData.Version.Value.IntValue; } + } + + public ISet DigestOids + { + get { return new HashSet(_digestOids); } + } + + /** + * return the collection of signers that are associated with the + * signatures for the message. + * @throws CmsException + */ + public SignerInformationStore GetSignerInfos() + { + if (_signerInfoStore == null) + { + PopulateCertCrlSets(); + + IList signerInfos = Platform.CreateArrayList(); + IDictionary hashes = Platform.CreateHashtable(); + + foreach (object digestKey in _digests.Keys) + { + hashes[digestKey] = DigestUtilities.DoFinal( + (IDigest)_digests[digestKey]); + } + + try + { + Asn1SetParser s = _signedData.GetSignerInfos(); + IAsn1Convertible o; + + while ((o = s.ReadObject()) != null) + { + SignerInfo info = SignerInfo.GetInstance(o.ToAsn1Object()); + string digestName = Helper.GetDigestAlgName( + info.DigestAlgorithm.Algorithm.Id); + + byte[] hash = (byte[]) hashes[digestName]; + + signerInfos.Add(new SignerInformation(info, _signedContentType, null, new BaseDigestCalculator(hash))); + } + } + catch (IOException e) + { + throw new CmsException("io exception: " + e.Message, e); + } + + _signerInfoStore = new SignerInformationStore(signerInfos); + } + + return _signerInfoStore; + } + + /** + * return a X509Store containing the attribute certificates, if any, contained + * in this message. + * + * @param type type of store to create + * @return a store of attribute certificates + * @exception org.bouncycastle.x509.NoSuchStoreException if the store type isn't available. + * @exception CmsException if a general exception prevents creation of the X509Store + */ + public IX509Store GetAttributeCertificates( + string type) + { + if (_attributeStore == null) + { + PopulateCertCrlSets(); + + _attributeStore = Helper.CreateAttributeStore(type, _certSet); + } + + return _attributeStore; + } + + /** + * return a X509Store containing the public key certificates, if any, contained + * in this message. + * + * @param type type of store to create + * @return a store of public key certificates + * @exception NoSuchStoreException if the store type isn't available. + * @exception CmsException if a general exception prevents creation of the X509Store + */ + public IX509Store GetCertificates( + string type) + { + if (_certificateStore == null) + { + PopulateCertCrlSets(); + + _certificateStore = Helper.CreateCertificateStore(type, _certSet); + } + + return _certificateStore; + } + + /** + * return a X509Store containing CRLs, if any, contained + * in this message. + * + * @param type type of store to create + * @return a store of CRLs + * @exception NoSuchStoreException if the store type isn't available. + * @exception CmsException if a general exception prevents creation of the X509Store + */ + public IX509Store GetCrls( + string type) + { + if (_crlStore == null) + { + PopulateCertCrlSets(); + + _crlStore = Helper.CreateCrlStore(type, _crlSet); + } + + return _crlStore; + } + + private void PopulateCertCrlSets() + { + if (_isCertCrlParsed) + return; + + _isCertCrlParsed = true; + + try + { + // care! Streaming - Must process the GetCertificates() result before calling GetCrls() + _certSet = GetAsn1Set(_signedData.GetCertificates()); + _crlSet = GetAsn1Set(_signedData.GetCrls()); + } + catch (IOException e) + { + throw new CmsException("problem parsing cert/crl sets", e); + } + } + + /// + /// Return the DerObjectIdentifier associated with the encapsulated + /// content info structure carried in the signed data. + /// + public DerObjectIdentifier SignedContentType + { + get { return _signedContentType; } + } + + public CmsTypedStream GetSignedContent() + { + if (_signedContent == null) + { + return null; + } + + Stream digStream = _signedContent.ContentStream; + + foreach (IDigest digest in _digests.Values) + { + digStream = new DigestStream(digStream, digest, null); + } + + return new CmsTypedStream(_signedContent.ContentType, digStream); + } + + /** + * Replace the signerinformation store associated with the passed + * in message contained in the stream original with the new one passed in. + * You would probably only want to do this if you wanted to change the unsigned + * attributes associated with a signer, or perhaps delete one. + *

+ * The output stream is returned unclosed. + *

+ * @param original the signed data stream to be used as a base. + * @param signerInformationStore the new signer information store to use. + * @param out the stream to Write the new signed data object to. + * @return out. + */ + public static Stream ReplaceSigners( + Stream original, + SignerInformationStore signerInformationStore, + Stream outStr) + { + // NB: SecureRandom would be ignored since using existing signatures only + CmsSignedDataStreamGenerator gen = new CmsSignedDataStreamGenerator(); + CmsSignedDataParser parser = new CmsSignedDataParser(original); + +// gen.AddDigests(parser.DigestOids); + gen.AddSigners(signerInformationStore); + + CmsTypedStream signedContent = parser.GetSignedContent(); + bool encapsulate = (signedContent != null); + Stream contentOut = gen.Open(outStr, parser.SignedContentType.Id, encapsulate); + if (encapsulate) + { + Streams.PipeAll(signedContent.ContentStream, contentOut); + } + + gen.AddAttributeCertificates(parser.GetAttributeCertificates("Collection")); + gen.AddCertificates(parser.GetCertificates("Collection")); + gen.AddCrls(parser.GetCrls("Collection")); + +// gen.AddSigners(parser.GetSignerInfos()); + + Platform.Dispose(contentOut); + + return outStr; + } + + /** + * Replace the certificate and CRL information associated with this + * CMSSignedData object with the new one passed in. + *

+ * The output stream is returned unclosed. + *

+ * @param original the signed data stream to be used as a base. + * @param certsAndCrls the new certificates and CRLs to be used. + * @param out the stream to Write the new signed data object to. + * @return out. + * @exception CmsException if there is an error processing the CertStore + */ + public static Stream ReplaceCertificatesAndCrls( + Stream original, + IX509Store x509Certs, + IX509Store x509Crls, + IX509Store x509AttrCerts, + Stream outStr) + { + // NB: SecureRandom would be ignored since using existing signatures only + CmsSignedDataStreamGenerator gen = new CmsSignedDataStreamGenerator(); + CmsSignedDataParser parser = new CmsSignedDataParser(original); + + gen.AddDigests(parser.DigestOids); + + CmsTypedStream signedContent = parser.GetSignedContent(); + bool encapsulate = (signedContent != null); + Stream contentOut = gen.Open(outStr, parser.SignedContentType.Id, encapsulate); + if (encapsulate) + { + Streams.PipeAll(signedContent.ContentStream, contentOut); + } + +// gen.AddAttributeCertificates(parser.GetAttributeCertificates("Collection")); +// gen.AddCertificates(parser.GetCertificates("Collection")); +// gen.AddCrls(parser.GetCrls("Collection")); + if (x509AttrCerts != null) + gen.AddAttributeCertificates(x509AttrCerts); + if (x509Certs != null) + gen.AddCertificates(x509Certs); + if (x509Crls != null) + gen.AddCrls(x509Crls); + + gen.AddSigners(parser.GetSignerInfos()); + + Platform.Dispose(contentOut); + + return outStr; + } + + private static Asn1Set GetAsn1Set( + Asn1SetParser asn1SetParser) + { + return asn1SetParser == null + ? null + : Asn1Set.GetInstance(asn1SetParser.ToAsn1Object()); + } + } +} diff --git a/bc-sharp-crypto/src/cms/CMSSignedDataStreamGenerator.cs b/bc-sharp-crypto/src/cms/CMSSignedDataStreamGenerator.cs new file mode 100644 index 0000000..d0ab742 --- /dev/null +++ b/bc-sharp-crypto/src/cms/CMSSignedDataStreamGenerator.cs @@ -0,0 +1,929 @@ +using System; +using System.Collections; +using System.Diagnostics; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Cms; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.IO; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Security.Certificates; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; +using Org.BouncyCastle.Utilities.IO; +using Org.BouncyCastle.X509; + +namespace Org.BouncyCastle.Cms +{ + /** + * General class for generating a pkcs7-signature message stream. + *

+ * A simple example of usage. + *

+ *
+    *      IX509Store                   certs...
+    *      CmsSignedDataStreamGenerator gen = new CmsSignedDataStreamGenerator();
+    *
+    *      gen.AddSigner(privateKey, cert, CmsSignedDataStreamGenerator.DIGEST_SHA1);
+    *
+    *      gen.AddCertificates(certs);
+    *
+    *      Stream sigOut = gen.Open(bOut);
+    *
+    *      sigOut.Write(Encoding.UTF8.GetBytes("Hello World!"));
+    *
+    *      sigOut.Close();
+    * 
+ */ + public class CmsSignedDataStreamGenerator + : CmsSignedGenerator + { + private static readonly CmsSignedHelper Helper = CmsSignedHelper.Instance; + + private readonly IList _signerInfs = Platform.CreateArrayList(); + private readonly ISet _messageDigestOids = new HashSet(); + private readonly IDictionary _messageDigests = Platform.CreateHashtable(); + private readonly IDictionary _messageHashes = Platform.CreateHashtable(); + private bool _messageDigestsLocked; + private int _bufferSize; + + private class DigestAndSignerInfoGeneratorHolder + { + internal readonly ISignerInfoGenerator signerInf; + internal readonly string digestOID; + + internal DigestAndSignerInfoGeneratorHolder(ISignerInfoGenerator signerInf, String digestOID) + { + this.signerInf = signerInf; + this.digestOID = digestOID; + } + + internal AlgorithmIdentifier DigestAlgorithm + { + get { return new AlgorithmIdentifier(new DerObjectIdentifier(this.digestOID), DerNull.Instance); } + } + } + + private class SignerInfoGeneratorImpl : ISignerInfoGenerator + { + private readonly CmsSignedDataStreamGenerator outer; + + private readonly SignerIdentifier _signerIdentifier; + private readonly string _digestOID; + private readonly string _encOID; + private readonly CmsAttributeTableGenerator _sAttr; + private readonly CmsAttributeTableGenerator _unsAttr; + private readonly string _encName; + private readonly ISigner _sig; + + internal SignerInfoGeneratorImpl( + CmsSignedDataStreamGenerator outer, + AsymmetricKeyParameter key, + SignerIdentifier signerIdentifier, + string digestOID, + string encOID, + CmsAttributeTableGenerator sAttr, + CmsAttributeTableGenerator unsAttr) + { + this.outer = outer; + + _signerIdentifier = signerIdentifier; + _digestOID = digestOID; + _encOID = encOID; + _sAttr = sAttr; + _unsAttr = unsAttr; + _encName = Helper.GetEncryptionAlgName(_encOID); + + string digestName = Helper.GetDigestAlgName(_digestOID); + string signatureName = digestName + "with" + _encName; + + if (_sAttr != null) + { + _sig = Helper.GetSignatureInstance(signatureName); + } + else + { + // Note: Need to use raw signatures here since we have already calculated the digest + if (_encName.Equals("RSA")) + { + _sig = Helper.GetSignatureInstance("RSA"); + } + else if (_encName.Equals("DSA")) + { + _sig = Helper.GetSignatureInstance("NONEwithDSA"); + } + // TODO Add support for raw PSS +// else if (_encName.equals("RSAandMGF1")) +// { +// _sig = CMSSignedHelper.INSTANCE.getSignatureInstance("NONEWITHRSAPSS", _sigProvider); +// try +// { +// // Init the params this way to avoid having a 'raw' version of each PSS algorithm +// Signature sig2 = CMSSignedHelper.INSTANCE.getSignatureInstance(signatureName, _sigProvider); +// PSSParameterSpec spec = (PSSParameterSpec)sig2.getParameters().getParameterSpec(PSSParameterSpec.class); +// _sig.setParameter(spec); +// } +// catch (Exception e) +// { +// throw new SignatureException("algorithm: " + _encName + " could not be configured."); +// } +// } + else + { + throw new SignatureException("algorithm: " + _encName + " not supported in base signatures."); + } + } + + _sig.Init(true, new ParametersWithRandom(key, outer.rand)); + } + + public SignerInfo Generate(DerObjectIdentifier contentType, AlgorithmIdentifier digestAlgorithm, + byte[] calculatedDigest) + { + try + { + string digestName = Helper.GetDigestAlgName(_digestOID); + string signatureName = digestName + "with" + _encName; + +// AlgorithmIdentifier digAlgId = DigestAlgorithmID; +// +// byte[] hash = (byte[])outer._messageHashes[Helper.GetDigestAlgName(this._digestOID)]; +// outer._digests[_digestOID] = hash.Clone(); + + byte[] bytesToSign = calculatedDigest; + + /* RFC 3852 5.4 + * The result of the message digest calculation process depends on + * whether the signedAttrs field is present. When the field is absent, + * the result is just the message digest of the content as described + * + * above. When the field is present, however, the result is the message + * digest of the complete DER encoding of the SignedAttrs value + * contained in the signedAttrs field. + */ + Asn1Set signedAttr = null; + if (_sAttr != null) + { + IDictionary parameters = outer.GetBaseParameters(contentType, digestAlgorithm, calculatedDigest); + +// Asn1.Cms.AttributeTable signed = _sAttr.GetAttributes(Collections.unmodifiableMap(parameters)); + Asn1.Cms.AttributeTable signed = _sAttr.GetAttributes(parameters); + + if (contentType == null) //counter signature + { + if (signed != null && signed[CmsAttributes.ContentType] != null) + { + IDictionary tmpSigned = signed.ToDictionary(); + tmpSigned.Remove(CmsAttributes.ContentType); + signed = new Asn1.Cms.AttributeTable(tmpSigned); + } + } + + signedAttr = outer.GetAttributeSet(signed); + + // sig must be composed from the DER encoding. + bytesToSign = signedAttr.GetEncoded(Asn1Encodable.Der); + } + else + { + // Note: Need to use raw signatures here since we have already calculated the digest + if (_encName.Equals("RSA")) + { + DigestInfo dInfo = new DigestInfo(digestAlgorithm, calculatedDigest); + bytesToSign = dInfo.GetEncoded(Asn1Encodable.Der); + } + } + + _sig.BlockUpdate(bytesToSign, 0, bytesToSign.Length); + byte[] sigBytes = _sig.GenerateSignature(); + + Asn1Set unsignedAttr = null; + if (_unsAttr != null) + { + IDictionary parameters = outer.GetBaseParameters( + contentType, digestAlgorithm, calculatedDigest); + parameters[CmsAttributeTableParameter.Signature] = sigBytes.Clone(); + +// Asn1.Cms.AttributeTable unsigned = _unsAttr.getAttributes(Collections.unmodifiableMap(parameters)); + Asn1.Cms.AttributeTable unsigned = _unsAttr.GetAttributes(parameters); + + unsignedAttr = outer.GetAttributeSet(unsigned); + } + + // TODO[RSAPSS] Need the ability to specify non-default parameters + Asn1Encodable sigX509Parameters = SignerUtilities.GetDefaultX509Parameters(signatureName); + AlgorithmIdentifier digestEncryptionAlgorithm = Helper.GetEncAlgorithmIdentifier( + new DerObjectIdentifier(_encOID), sigX509Parameters); + + return new SignerInfo(_signerIdentifier, digestAlgorithm, + signedAttr, digestEncryptionAlgorithm, new DerOctetString(sigBytes), unsignedAttr); + } + catch (IOException e) + { + throw new CmsStreamException("encoding error.", e); + } + catch (SignatureException e) + { + throw new CmsStreamException("error creating signature.", e); + } + } + } + + public CmsSignedDataStreamGenerator() + { + } + + /// Constructor allowing specific source of randomness + /// Instance of SecureRandom to use. + public CmsSignedDataStreamGenerator( + SecureRandom rand) + : base(rand) + { + } + + /** + * Set the underlying string size for encapsulated data + * + * @param bufferSize length of octet strings to buffer the data. + */ + public void SetBufferSize( + int bufferSize) + { + _bufferSize = bufferSize; + } + + public void AddDigests( + params string[] digestOids) + { + AddDigests((IEnumerable) digestOids); + } + + public void AddDigests( + IEnumerable digestOids) + { + foreach (string digestOid in digestOids) + { + ConfigureDigest(digestOid); + } + } + + /** + * add a signer - no attributes other than the default ones will be + * provided here. + * @throws NoSuchAlgorithmException + * @throws InvalidKeyException + */ + public void AddSigner( + AsymmetricKeyParameter privateKey, + X509Certificate cert, + string digestOid) + { + AddSigner(privateKey, cert, digestOid, + new DefaultSignedAttributeTableGenerator(), null); + } + + /** + * add a signer, specifying the digest encryption algorithm - no attributes other than the default ones will be + * provided here. + * @throws NoSuchProviderException + * @throws NoSuchAlgorithmException + * @throws InvalidKeyException + */ + public void AddSigner( + AsymmetricKeyParameter privateKey, + X509Certificate cert, + string encryptionOid, + string digestOid) + { + AddSigner(privateKey, cert, encryptionOid, digestOid, + new DefaultSignedAttributeTableGenerator(), + (CmsAttributeTableGenerator)null); + } + + /** + * add a signer with extra signed/unsigned attributes. + * @throws NoSuchAlgorithmException + * @throws InvalidKeyException + */ + public void AddSigner( + AsymmetricKeyParameter privateKey, + X509Certificate cert, + string digestOid, + Asn1.Cms.AttributeTable signedAttr, + Asn1.Cms.AttributeTable unsignedAttr) + { + AddSigner(privateKey, cert, digestOid, + new DefaultSignedAttributeTableGenerator(signedAttr), + new SimpleAttributeTableGenerator(unsignedAttr)); + } + + /** + * add a signer with extra signed/unsigned attributes - specifying digest + * encryption algorithm. + * @throws NoSuchProviderException + * @throws NoSuchAlgorithmException + * @throws InvalidKeyException + */ + public void AddSigner( + AsymmetricKeyParameter privateKey, + X509Certificate cert, + string encryptionOid, + string digestOid, + Asn1.Cms.AttributeTable signedAttr, + Asn1.Cms.AttributeTable unsignedAttr) + { + AddSigner(privateKey, cert, encryptionOid, digestOid, + new DefaultSignedAttributeTableGenerator(signedAttr), + new SimpleAttributeTableGenerator(unsignedAttr)); + } + + public void AddSigner( + AsymmetricKeyParameter privateKey, + X509Certificate cert, + string digestOid, + CmsAttributeTableGenerator signedAttrGenerator, + CmsAttributeTableGenerator unsignedAttrGenerator) + { + AddSigner(privateKey, cert, Helper.GetEncOid(privateKey, digestOid), digestOid, + signedAttrGenerator, unsignedAttrGenerator); + } + + public void AddSigner( + AsymmetricKeyParameter privateKey, + X509Certificate cert, + string encryptionOid, + string digestOid, + CmsAttributeTableGenerator signedAttrGenerator, + CmsAttributeTableGenerator unsignedAttrGenerator) + { + DoAddSigner(privateKey, GetSignerIdentifier(cert), encryptionOid, digestOid, + signedAttrGenerator, unsignedAttrGenerator); + } + + /** + * add a signer - no attributes other than the default ones will be + * provided here. + * @throws NoSuchAlgorithmException + * @throws InvalidKeyException + */ + public void AddSigner( + AsymmetricKeyParameter privateKey, + byte[] subjectKeyID, + string digestOid) + { + AddSigner(privateKey, subjectKeyID, digestOid, new DefaultSignedAttributeTableGenerator(), + (CmsAttributeTableGenerator)null); + } + + /** + * add a signer - no attributes other than the default ones will be + * provided here. + * @throws NoSuchProviderException + * @throws NoSuchAlgorithmException + * @throws InvalidKeyException + */ + public void AddSigner( + AsymmetricKeyParameter privateKey, + byte[] subjectKeyID, + string encryptionOid, + string digestOid) + { + AddSigner(privateKey, subjectKeyID, encryptionOid, digestOid, + new DefaultSignedAttributeTableGenerator(), + (CmsAttributeTableGenerator)null); + } + + /** + * add a signer with extra signed/unsigned attributes. + * @throws NoSuchAlgorithmException + * @throws InvalidKeyException + */ + public void AddSigner( + AsymmetricKeyParameter privateKey, + byte[] subjectKeyID, + string digestOid, + Asn1.Cms.AttributeTable signedAttr, + Asn1.Cms.AttributeTable unsignedAttr) + { + AddSigner(privateKey, subjectKeyID, digestOid, + new DefaultSignedAttributeTableGenerator(signedAttr), + new SimpleAttributeTableGenerator(unsignedAttr)); + } + + public void AddSigner( + AsymmetricKeyParameter privateKey, + byte[] subjectKeyID, + string digestOid, + CmsAttributeTableGenerator signedAttrGenerator, + CmsAttributeTableGenerator unsignedAttrGenerator) + { + AddSigner(privateKey, subjectKeyID, Helper.GetEncOid(privateKey, digestOid), + digestOid, signedAttrGenerator, unsignedAttrGenerator); + } + + public void AddSigner( + AsymmetricKeyParameter privateKey, + byte[] subjectKeyID, + string encryptionOid, + string digestOid, + CmsAttributeTableGenerator signedAttrGenerator, + CmsAttributeTableGenerator unsignedAttrGenerator) + { + DoAddSigner(privateKey, GetSignerIdentifier(subjectKeyID), encryptionOid, digestOid, + signedAttrGenerator, unsignedAttrGenerator); + } + + private void DoAddSigner( + AsymmetricKeyParameter privateKey, + SignerIdentifier signerIdentifier, + string encryptionOid, + string digestOid, + CmsAttributeTableGenerator signedAttrGenerator, + CmsAttributeTableGenerator unsignedAttrGenerator) + { + ConfigureDigest(digestOid); + + SignerInfoGeneratorImpl signerInf = new SignerInfoGeneratorImpl(this, privateKey, + signerIdentifier, digestOid, encryptionOid, signedAttrGenerator, unsignedAttrGenerator); + + _signerInfs.Add(new DigestAndSignerInfoGeneratorHolder(signerInf, digestOid)); + } + + internal override void AddSignerCallback( + SignerInformation si) + { + // FIXME If there were parameters in si.DigestAlgorithmID.Parameters, they are lost + // NB: Would need to call FixAlgID on the DigestAlgorithmID + + // For precalculated signers, just need to register the algorithm, not configure a digest + RegisterDigestOid(si.DigestAlgorithmID.Algorithm.Id); + } + + /** + * generate a signed object that for a CMS Signed Data object + */ + public Stream Open( + Stream outStream) + { + return Open(outStream, false); + } + + /** + * generate a signed object that for a CMS Signed Data + * object - if encapsulate is true a copy + * of the message will be included in the signature with the + * default content type "data". + */ + public Stream Open( + Stream outStream, + bool encapsulate) + { + return Open(outStream, Data, encapsulate); + } + + /** + * generate a signed object that for a CMS Signed Data + * object using the given provider - if encapsulate is true a copy + * of the message will be included in the signature with the + * default content type "data". If dataOutputStream is non null the data + * being signed will be written to the stream as it is processed. + * @param out stream the CMS object is to be written to. + * @param encapsulate true if data should be encapsulated. + * @param dataOutputStream output stream to copy the data being signed to. + */ + public Stream Open( + Stream outStream, + bool encapsulate, + Stream dataOutputStream) + { + return Open(outStream, Data, encapsulate, dataOutputStream); + } + + /** + * generate a signed object that for a CMS Signed Data + * object - if encapsulate is true a copy + * of the message will be included in the signature. The content type + * is set according to the OID represented by the string signedContentType. + */ + public Stream Open( + Stream outStream, + string signedContentType, + bool encapsulate) + { + return Open(outStream, signedContentType, encapsulate, null); + } + + /** + * generate a signed object that for a CMS Signed Data + * object using the given provider - if encapsulate is true a copy + * of the message will be included in the signature. The content type + * is set according to the OID represented by the string signedContentType. + * @param out stream the CMS object is to be written to. + * @param signedContentType OID for data to be signed. + * @param encapsulate true if data should be encapsulated. + * @param dataOutputStream output stream to copy the data being signed to. + */ + public Stream Open( + Stream outStream, + string signedContentType, + bool encapsulate, + Stream dataOutputStream) + { + if (outStream == null) + throw new ArgumentNullException("outStream"); + if (!outStream.CanWrite) + throw new ArgumentException("Expected writeable stream", "outStream"); + if (dataOutputStream != null && !dataOutputStream.CanWrite) + throw new ArgumentException("Expected writeable stream", "dataOutputStream"); + + _messageDigestsLocked = true; + + // + // ContentInfo + // + BerSequenceGenerator sGen = new BerSequenceGenerator(outStream); + + sGen.AddObject(CmsObjectIdentifiers.SignedData); + + // + // Signed Data + // + BerSequenceGenerator sigGen = new BerSequenceGenerator( + sGen.GetRawOutputStream(), 0, true); + + bool isCounterSignature = (signedContentType == null); + + DerObjectIdentifier contentTypeOid = isCounterSignature + ? null + : new DerObjectIdentifier(signedContentType); + + sigGen.AddObject(CalculateVersion(contentTypeOid)); + + Asn1EncodableVector digestAlgs = new Asn1EncodableVector(); + + foreach (string digestOid in _messageDigestOids) + { + digestAlgs.Add( + new AlgorithmIdentifier(new DerObjectIdentifier(digestOid), DerNull.Instance)); + } + + { + byte[] tmp = new DerSet(digestAlgs).GetEncoded(); + sigGen.GetRawOutputStream().Write(tmp, 0, tmp.Length); + } + + BerSequenceGenerator eiGen = new BerSequenceGenerator(sigGen.GetRawOutputStream()); + eiGen.AddObject(contentTypeOid); + + // If encapsulating, add the data as an octet string in the sequence + Stream encapStream = encapsulate + ? CmsUtilities.CreateBerOctetOutputStream(eiGen.GetRawOutputStream(), 0, true, _bufferSize) + : null; + + // Also send the data to 'dataOutputStream' if necessary + Stream teeStream = GetSafeTeeOutputStream(dataOutputStream, encapStream); + + // Let all the digests see the data as it is written + Stream digStream = AttachDigestsToOutputStream(_messageDigests.Values, teeStream); + + return new CmsSignedDataOutputStream(this, digStream, signedContentType, sGen, sigGen, eiGen); + } + + private void RegisterDigestOid( + string digestOid) + { + if (_messageDigestsLocked) + { + if (!_messageDigestOids.Contains(digestOid)) + throw new InvalidOperationException("Cannot register new digest OIDs after the data stream is opened"); + } + else + { + _messageDigestOids.Add(digestOid); + } + } + + private void ConfigureDigest( + string digestOid) + { + RegisterDigestOid(digestOid); + + string digestName = Helper.GetDigestAlgName(digestOid); + IDigest dig = (IDigest)_messageDigests[digestName]; + if (dig == null) + { + if (_messageDigestsLocked) + throw new InvalidOperationException("Cannot configure new digests after the data stream is opened"); + + dig = Helper.GetDigestInstance(digestName); + _messageDigests[digestName] = dig; + } + } + + // TODO Make public? + internal void Generate( + Stream outStream, + string eContentType, + bool encapsulate, + Stream dataOutputStream, + CmsProcessable content) + { + Stream signedOut = Open(outStream, eContentType, encapsulate, dataOutputStream); + if (content != null) + { + content.Write(signedOut); + } + Platform.Dispose(signedOut); + } + + // 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) + { + bool otherCert = false; + bool otherCrl = false; + bool attrCertV1Found = false; + bool attrCertV2Found = false; + + if (_certs != null) + { + foreach (object obj in _certs) + { + if (obj is Asn1TaggedObject) + { + Asn1TaggedObject tagged = (Asn1TaggedObject) obj; + + if (tagged.TagNo == 1) + { + attrCertV1Found = true; + } + else if (tagged.TagNo == 2) + { + attrCertV2Found = true; + } + else if (tagged.TagNo == 3) + { + otherCert = true; + break; + } + } + } + } + + if (otherCert) + { + return new DerInteger(5); + } + + if (_crls != null) + { + foreach (object obj in _crls) + { + if (obj is Asn1TaggedObject) + { + otherCrl = true; + break; + } + } + } + + if (otherCrl) + { + return new DerInteger(5); + } + + if (attrCertV2Found) + { + return new DerInteger(4); + } + + if (attrCertV1Found || !CmsObjectIdentifiers.Data.Equals(contentOid) || CheckForVersion3(_signers)) + { + return new DerInteger(3); + } + + return new DerInteger(1); + } + + private bool CheckForVersion3( + IList signerInfos) + { + foreach (SignerInformation si in signerInfos) + { + SignerInfo s = SignerInfo.GetInstance(si.ToSignerInfo()); + + if (s.Version.Value.IntValue == 3) + { + return true; + } + } + + return false; + } + + private static Stream AttachDigestsToOutputStream(ICollection digests, Stream s) + { + Stream result = s; + foreach (IDigest digest in digests) + { + result = GetSafeTeeOutputStream(result, new DigOutputStream(digest)); + } + return result; + } + + private static Stream GetSafeOutputStream(Stream s) + { + if (s == null) + return new NullOutputStream(); + return s; + } + + private static Stream GetSafeTeeOutputStream(Stream s1, Stream s2) + { + if (s1 == null) + return GetSafeOutputStream(s2); + if (s2 == null) + return GetSafeOutputStream(s1); + return new TeeOutputStream(s1, s2); + } + + private class CmsSignedDataOutputStream + : BaseOutputStream + { + private readonly CmsSignedDataStreamGenerator outer; + + private Stream _out; + private DerObjectIdentifier _contentOID; + private BerSequenceGenerator _sGen; + private BerSequenceGenerator _sigGen; + private BerSequenceGenerator _eiGen; + + public CmsSignedDataOutputStream( + CmsSignedDataStreamGenerator outer, + Stream outStream, + string contentOID, + BerSequenceGenerator sGen, + BerSequenceGenerator sigGen, + BerSequenceGenerator eiGen) + { + this.outer = outer; + + _out = outStream; + _contentOID = new DerObjectIdentifier(contentOID); + _sGen = sGen; + _sigGen = sigGen; + _eiGen = eiGen; + } + + public override void WriteByte( + byte b) + { + _out.WriteByte(b); + } + + public override void Write( + byte[] bytes, + int off, + int len) + { + _out.Write(bytes, off, len); + } + +#if PORTABLE + protected override void Dispose(bool disposing) + { + if (disposing) + { + DoClose(); + } + base.Dispose(disposing); + } +#else + public override void Close() + { + DoClose(); + base.Close(); + } +#endif + + private void DoClose() + { + Platform.Dispose(_out); + + // TODO Parent context(s) should really be be closed explicitly + + _eiGen.Close(); + + outer._digests.Clear(); // clear the current preserved digest state + + if (outer._certs.Count > 0) + { + Asn1Set certs = CmsUtilities.CreateBerSetFromList(outer._certs); + + WriteToGenerator(_sigGen, new BerTaggedObject(false, 0, certs)); + } + + if (outer._crls.Count > 0) + { + Asn1Set crls = CmsUtilities.CreateBerSetFromList(outer._crls); + + WriteToGenerator(_sigGen, new BerTaggedObject(false, 1, crls)); + } + + // + // Calculate the digest hashes + // + foreach (DictionaryEntry de in outer._messageDigests) + { + outer._messageHashes.Add(de.Key, DigestUtilities.DoFinal((IDigest)de.Value)); + } + + // TODO If the digest OIDs for precalculated signers weren't mixed in with + // the others, we could fill in outer._digests here, instead of SignerInfoGenerator.Generate + + // + // collect all the SignerInfo objects + // + Asn1EncodableVector signerInfos = new Asn1EncodableVector(); + + // + // add the generated SignerInfo objects + // + { + foreach (DigestAndSignerInfoGeneratorHolder holder in outer._signerInfs) + { + AlgorithmIdentifier digestAlgorithm = holder.DigestAlgorithm; + + byte[] calculatedDigest = (byte[])outer._messageHashes[ + Helper.GetDigestAlgName(holder.digestOID)]; + outer._digests[holder.digestOID] = calculatedDigest.Clone(); + + signerInfos.Add(holder.signerInf.Generate(_contentOID, digestAlgorithm, calculatedDigest)); + } + } + + // + // add the precalculated SignerInfo objects. + // + { + foreach (SignerInformation signer in outer._signers) + { + // TODO Verify the content type and calculated digest match the precalculated SignerInfo +// if (!signer.ContentType.Equals(_contentOID)) +// { +// // TODO The precalculated content type did not match - error? +// } +// +// byte[] calculatedDigest = (byte[])outer._digests[signer.DigestAlgOid]; +// if (calculatedDigest == null) +// { +// // TODO We can't confirm this digest because we didn't calculate it - error? +// } +// else +// { +// if (!Arrays.AreEqual(signer.GetContentDigest(), calculatedDigest)) +// { +// // TODO The precalculated digest did not match - error? +// } +// } + + signerInfos.Add(signer.ToSignerInfo()); + } + } + + WriteToGenerator(_sigGen, new DerSet(signerInfos)); + + _sigGen.Close(); + _sGen.Close(); + } + + private static void WriteToGenerator( + Asn1Generator ag, + Asn1Encodable ae) + { + byte[] encoded = ae.GetEncoded(); + ag.GetRawOutputStream().Write(encoded, 0, encoded.Length); + } + } + } +} diff --git a/bc-sharp-crypto/src/cms/CMSSignedGenerator.cs b/bc-sharp-crypto/src/cms/CMSSignedGenerator.cs new file mode 100644 index 0000000..0fb1f31 --- /dev/null +++ b/bc-sharp-crypto/src/cms/CMSSignedGenerator.cs @@ -0,0 +1,267 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Cms; +using Org.BouncyCastle.Asn1.CryptoPro; +using Org.BouncyCastle.Asn1.Nist; +using Org.BouncyCastle.Asn1.Oiw; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.TeleTrust; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; +using Org.BouncyCastle.X509; +using Org.BouncyCastle.X509.Store; + +namespace Org.BouncyCastle.Cms +{ + public class DefaultDigestAlgorithmIdentifierFinder + { + private static readonly IDictionary digestOids = Platform.CreateHashtable(); + private static readonly IDictionary digestNameToOids = Platform.CreateHashtable(); + + static DefaultDigestAlgorithmIdentifierFinder() + { + // + // digests + // + digestOids.Add(OiwObjectIdentifiers.MD4WithRsaEncryption, PkcsObjectIdentifiers.MD4); + digestOids.Add(OiwObjectIdentifiers.MD4WithRsa, PkcsObjectIdentifiers.MD4); + digestOids.Add(OiwObjectIdentifiers.Sha1WithRsa, OiwObjectIdentifiers.IdSha1); + + digestOids.Add(PkcsObjectIdentifiers.Sha224WithRsaEncryption, NistObjectIdentifiers.IdSha224); + digestOids.Add(PkcsObjectIdentifiers.Sha256WithRsaEncryption, NistObjectIdentifiers.IdSha256); + digestOids.Add(PkcsObjectIdentifiers.Sha384WithRsaEncryption, NistObjectIdentifiers.IdSha384); + digestOids.Add(PkcsObjectIdentifiers.Sha512WithRsaEncryption, NistObjectIdentifiers.IdSha512); + digestOids.Add(PkcsObjectIdentifiers.MD2WithRsaEncryption, PkcsObjectIdentifiers.MD2); + digestOids.Add(PkcsObjectIdentifiers.MD4WithRsaEncryption, PkcsObjectIdentifiers.MD4); + digestOids.Add(PkcsObjectIdentifiers.MD5WithRsaEncryption, PkcsObjectIdentifiers.MD5); + digestOids.Add(PkcsObjectIdentifiers.Sha1WithRsaEncryption, OiwObjectIdentifiers.IdSha1); + + digestOids.Add(X9ObjectIdentifiers.ECDsaWithSha1, OiwObjectIdentifiers.IdSha1); + digestOids.Add(X9ObjectIdentifiers.ECDsaWithSha224, NistObjectIdentifiers.IdSha224); + digestOids.Add(X9ObjectIdentifiers.ECDsaWithSha256, NistObjectIdentifiers.IdSha256); + digestOids.Add(X9ObjectIdentifiers.ECDsaWithSha384, NistObjectIdentifiers.IdSha384); + digestOids.Add(X9ObjectIdentifiers.ECDsaWithSha512, NistObjectIdentifiers.IdSha512); + digestOids.Add(X9ObjectIdentifiers.IdDsaWithSha1, OiwObjectIdentifiers.IdSha1); + + digestOids.Add(NistObjectIdentifiers.DsaWithSha224, NistObjectIdentifiers.IdSha224); + digestOids.Add(NistObjectIdentifiers.DsaWithSha256, NistObjectIdentifiers.IdSha256); + digestOids.Add(NistObjectIdentifiers.DsaWithSha384, NistObjectIdentifiers.IdSha384); + digestOids.Add(NistObjectIdentifiers.DsaWithSha512, NistObjectIdentifiers.IdSha512); + + digestOids.Add(TeleTrusTObjectIdentifiers.RsaSignatureWithRipeMD128, TeleTrusTObjectIdentifiers.RipeMD128); + digestOids.Add(TeleTrusTObjectIdentifiers.RsaSignatureWithRipeMD160, TeleTrusTObjectIdentifiers.RipeMD160); + digestOids.Add(TeleTrusTObjectIdentifiers.RsaSignatureWithRipeMD256, TeleTrusTObjectIdentifiers.RipeMD256); + + digestOids.Add(CryptoProObjectIdentifiers.GostR3411x94WithGostR3410x94, CryptoProObjectIdentifiers.GostR3411); + digestOids.Add(CryptoProObjectIdentifiers.GostR3411x94WithGostR3410x2001, CryptoProObjectIdentifiers.GostR3411); + + digestNameToOids.Add("SHA-1", OiwObjectIdentifiers.IdSha1); + digestNameToOids.Add("SHA-224", NistObjectIdentifiers.IdSha224); + digestNameToOids.Add("SHA-256", NistObjectIdentifiers.IdSha256); + digestNameToOids.Add("SHA-384", NistObjectIdentifiers.IdSha384); + digestNameToOids.Add("SHA-512", NistObjectIdentifiers.IdSha512); + + digestNameToOids.Add("SHA1", OiwObjectIdentifiers.IdSha1); + digestNameToOids.Add("SHA224", NistObjectIdentifiers.IdSha224); + digestNameToOids.Add("SHA256", NistObjectIdentifiers.IdSha256); + digestNameToOids.Add("SHA384", NistObjectIdentifiers.IdSha384); + digestNameToOids.Add("SHA512", NistObjectIdentifiers.IdSha512); + + digestNameToOids.Add("SHA3-224", NistObjectIdentifiers.IdSha3_224); + digestNameToOids.Add("SHA3-256", NistObjectIdentifiers.IdSha3_256); + digestNameToOids.Add("SHA3-384", NistObjectIdentifiers.IdSha3_384); + digestNameToOids.Add("SHA3-512", NistObjectIdentifiers.IdSha3_512); + + digestNameToOids.Add("SHAKE-128", NistObjectIdentifiers.IdShake128); + digestNameToOids.Add("SHAKE-256", NistObjectIdentifiers.IdShake256); + + digestNameToOids.Add("GOST3411", CryptoProObjectIdentifiers.GostR3411); + + digestNameToOids.Add("MD2", PkcsObjectIdentifiers.MD2); + digestNameToOids.Add("MD4", PkcsObjectIdentifiers.MD4); + digestNameToOids.Add("MD5", PkcsObjectIdentifiers.MD5); + + digestNameToOids.Add("RIPEMD128", TeleTrusTObjectIdentifiers.RipeMD128); + digestNameToOids.Add("RIPEMD160", TeleTrusTObjectIdentifiers.RipeMD160); + digestNameToOids.Add("RIPEMD256", TeleTrusTObjectIdentifiers.RipeMD256); + } + + public AlgorithmIdentifier find(AlgorithmIdentifier sigAlgId) + { + AlgorithmIdentifier digAlgId; + + if (sigAlgId.Algorithm.Equals(PkcsObjectIdentifiers.IdRsassaPss)) + { + digAlgId = RsassaPssParameters.GetInstance(sigAlgId.Parameters).HashAlgorithm; + } + else + { + digAlgId = new AlgorithmIdentifier((DerObjectIdentifier)digestOids[sigAlgId.Algorithm], DerNull.Instance); + } + + return digAlgId; + } + + public AlgorithmIdentifier find(String digAlgName) + { + return new AlgorithmIdentifier((DerObjectIdentifier)digestNameToOids[digAlgName], DerNull.Instance); + } + } + + public class CmsSignedGenerator + { + /** + * Default type for the signed data. + */ + public static readonly string Data = CmsObjectIdentifiers.Data.Id; + + public static readonly string DigestSha1 = OiwObjectIdentifiers.IdSha1.Id; + public static readonly string DigestSha224 = NistObjectIdentifiers.IdSha224.Id; + public static readonly string DigestSha256 = NistObjectIdentifiers.IdSha256.Id; + public static readonly string DigestSha384 = NistObjectIdentifiers.IdSha384.Id; + public static readonly string DigestSha512 = NistObjectIdentifiers.IdSha512.Id; + public static readonly string DigestMD5 = PkcsObjectIdentifiers.MD5.Id; + public static readonly string DigestGost3411 = CryptoProObjectIdentifiers.GostR3411.Id; + public static readonly string DigestRipeMD128 = TeleTrusTObjectIdentifiers.RipeMD128.Id; + public static readonly string DigestRipeMD160 = TeleTrusTObjectIdentifiers.RipeMD160.Id; + public static readonly string DigestRipeMD256 = TeleTrusTObjectIdentifiers.RipeMD256.Id; + + public static readonly string EncryptionRsa = PkcsObjectIdentifiers.RsaEncryption.Id; + public static readonly string EncryptionDsa = X9ObjectIdentifiers.IdDsaWithSha1.Id; + public static readonly string EncryptionECDsa = X9ObjectIdentifiers.ECDsaWithSha1.Id; + public static readonly string EncryptionRsaPss = PkcsObjectIdentifiers.IdRsassaPss.Id; + public static readonly string EncryptionGost3410 = CryptoProObjectIdentifiers.GostR3410x94.Id; + public static readonly string EncryptionECGost3410 = CryptoProObjectIdentifiers.GostR3410x2001.Id; + + internal IList _certs = Platform.CreateArrayList(); + internal IList _crls = Platform.CreateArrayList(); + internal IList _signers = Platform.CreateArrayList(); + internal IDictionary _digests = Platform.CreateHashtable(); + + protected readonly SecureRandom rand; + + protected CmsSignedGenerator() + : this(new SecureRandom()) + { + } + + /// Constructor allowing specific source of randomness + /// Instance of SecureRandom to use. + protected CmsSignedGenerator( + SecureRandom rand) + { + this.rand = rand; + } + + internal protected virtual IDictionary GetBaseParameters( + DerObjectIdentifier contentType, + AlgorithmIdentifier digAlgId, + byte[] hash) + { + IDictionary param = Platform.CreateHashtable(); + + if (contentType != null) + { + param[CmsAttributeTableParameter.ContentType] = contentType; + } + + param[CmsAttributeTableParameter.DigestAlgorithmIdentifier] = digAlgId; + param[CmsAttributeTableParameter.Digest] = hash.Clone(); + + return param; + } + + internal protected virtual Asn1Set GetAttributeSet( + Asn1.Cms.AttributeTable attr) + { + return attr == null + ? null + : new DerSet(attr.ToAsn1EncodableVector()); + } + + public void AddCertificates( + IX509Store certStore) + { + CollectionUtilities.AddRange(_certs, CmsUtilities.GetCertificatesFromStore(certStore)); + } + + public void AddCrls( + IX509Store crlStore) + { + CollectionUtilities.AddRange(_crls, CmsUtilities.GetCrlsFromStore(crlStore)); + } + + /** + * Add the attribute certificates contained in the passed in store to the + * generator. + * + * @param store a store of Version 2 attribute certificates + * @throws CmsException if an error occurse processing the store. + */ + public void AddAttributeCertificates( + IX509Store store) + { + try + { + foreach (IX509AttributeCertificate attrCert in store.GetMatches(null)) + { + _certs.Add(new DerTaggedObject(false, 2, + AttributeCertificate.GetInstance(Asn1Object.FromByteArray(attrCert.GetEncoded())))); + } + } + catch (Exception e) + { + throw new CmsException("error processing attribute certs", e); + } + } + + /** + * Add a store of precalculated signers to the generator. + * + * @param signerStore store of signers + */ + public void AddSigners( + SignerInformationStore signerStore) + { + foreach (SignerInformation o in signerStore.GetSigners()) + { + _signers.Add(o); + AddSignerCallback(o); + } + } + + /** + * Return a map of oids and byte arrays representing the digests calculated on the content during + * the last generate. + * + * @return a map of oids (as String objects) and byte[] representing digests. + */ + public IDictionary GetGeneratedDigests() + { + return Platform.CreateHashtable(_digests); + } + + internal virtual void AddSignerCallback( + SignerInformation si) + { + } + + internal static SignerIdentifier GetSignerIdentifier(X509Certificate cert) + { + return new SignerIdentifier(CmsUtilities.GetIssuerAndSerialNumber(cert)); + } + + internal static SignerIdentifier GetSignerIdentifier(byte[] subjectKeyIdentifier) + { + return new SignerIdentifier(new DerOctetString(subjectKeyIdentifier)); + } + } +} diff --git a/bc-sharp-crypto/src/cms/CMSSignedHelper.cs b/bc-sharp-crypto/src/cms/CMSSignedHelper.cs new file mode 100644 index 0000000..5b6c93b --- /dev/null +++ b/bc-sharp-crypto/src/cms/CMSSignedHelper.cs @@ -0,0 +1,426 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.CryptoPro; +using Org.BouncyCastle.Asn1.Eac; +using Org.BouncyCastle.Asn1.Iana; +using Org.BouncyCastle.Asn1.Misc; +using Org.BouncyCastle.Asn1.Nist; +using Org.BouncyCastle.Asn1.Oiw; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.TeleTrust; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Security.Certificates; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.X509; +using Org.BouncyCastle.X509.Store; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Utilities.Collections; + +namespace Org.BouncyCastle.Cms +{ + internal class CmsSignedHelper + { + internal static readonly CmsSignedHelper Instance = new CmsSignedHelper(); + + private static readonly string EncryptionECDsaWithSha1 = X9ObjectIdentifiers.ECDsaWithSha1.Id; + private static readonly string EncryptionECDsaWithSha224 = X9ObjectIdentifiers.ECDsaWithSha224.Id; + private static readonly string EncryptionECDsaWithSha256 = X9ObjectIdentifiers.ECDsaWithSha256.Id; + private static readonly string EncryptionECDsaWithSha384 = X9ObjectIdentifiers.ECDsaWithSha384.Id; + private static readonly string EncryptionECDsaWithSha512 = X9ObjectIdentifiers.ECDsaWithSha512.Id; + + private static readonly IDictionary encryptionAlgs = Platform.CreateHashtable(); + private static readonly IDictionary digestAlgs = Platform.CreateHashtable(); + private static readonly IDictionary digestAliases = Platform.CreateHashtable(); + + private static readonly ISet noParams = new HashSet(); + private static readonly IDictionary ecAlgorithms = Platform.CreateHashtable(); + + private static void AddEntries(DerObjectIdentifier oid, string digest, string encryption) + { + string alias = oid.Id; + digestAlgs.Add(alias, digest); + encryptionAlgs.Add(alias, encryption); + } + + static CmsSignedHelper() + { + AddEntries(NistObjectIdentifiers.DsaWithSha224, "SHA224", "DSA"); + AddEntries(NistObjectIdentifiers.DsaWithSha256, "SHA256", "DSA"); + AddEntries(NistObjectIdentifiers.DsaWithSha384, "SHA384", "DSA"); + AddEntries(NistObjectIdentifiers.DsaWithSha512, "SHA512", "DSA"); + AddEntries(OiwObjectIdentifiers.DsaWithSha1, "SHA1", "DSA"); + AddEntries(OiwObjectIdentifiers.MD4WithRsa, "MD4", "RSA"); + AddEntries(OiwObjectIdentifiers.MD4WithRsaEncryption, "MD4", "RSA"); + AddEntries(OiwObjectIdentifiers.MD5WithRsa, "MD5", "RSA"); + AddEntries(OiwObjectIdentifiers.Sha1WithRsa, "SHA1", "RSA"); + AddEntries(PkcsObjectIdentifiers.MD2WithRsaEncryption, "MD2", "RSA"); + AddEntries(PkcsObjectIdentifiers.MD4WithRsaEncryption, "MD4", "RSA"); + AddEntries(PkcsObjectIdentifiers.MD5WithRsaEncryption, "MD5", "RSA"); + AddEntries(PkcsObjectIdentifiers.Sha1WithRsaEncryption, "SHA1", "RSA"); + AddEntries(PkcsObjectIdentifiers.Sha224WithRsaEncryption, "SHA224", "RSA"); + AddEntries(PkcsObjectIdentifiers.Sha256WithRsaEncryption, "SHA256", "RSA"); + AddEntries(PkcsObjectIdentifiers.Sha384WithRsaEncryption, "SHA384", "RSA"); + AddEntries(PkcsObjectIdentifiers.Sha512WithRsaEncryption, "SHA512", "RSA"); + AddEntries(X9ObjectIdentifiers.ECDsaWithSha1, "SHA1", "ECDSA"); + AddEntries(X9ObjectIdentifiers.ECDsaWithSha224, "SHA224", "ECDSA"); + AddEntries(X9ObjectIdentifiers.ECDsaWithSha256, "SHA256", "ECDSA"); + AddEntries(X9ObjectIdentifiers.ECDsaWithSha384, "SHA384", "ECDSA"); + AddEntries(X9ObjectIdentifiers.ECDsaWithSha512, "SHA512", "ECDSA"); + AddEntries(X9ObjectIdentifiers.IdDsaWithSha1, "SHA1", "DSA"); + AddEntries(EacObjectIdentifiers.id_TA_ECDSA_SHA_1, "SHA1", "ECDSA"); + AddEntries(EacObjectIdentifiers.id_TA_ECDSA_SHA_224, "SHA224", "ECDSA"); + AddEntries(EacObjectIdentifiers.id_TA_ECDSA_SHA_256, "SHA256", "ECDSA"); + AddEntries(EacObjectIdentifiers.id_TA_ECDSA_SHA_384, "SHA384", "ECDSA"); + AddEntries(EacObjectIdentifiers.id_TA_ECDSA_SHA_512, "SHA512", "ECDSA"); + AddEntries(EacObjectIdentifiers.id_TA_RSA_v1_5_SHA_1, "SHA1", "RSA"); + AddEntries(EacObjectIdentifiers.id_TA_RSA_v1_5_SHA_256, "SHA256", "RSA"); + AddEntries(EacObjectIdentifiers.id_TA_RSA_PSS_SHA_1, "SHA1", "RSAandMGF1"); + AddEntries(EacObjectIdentifiers.id_TA_RSA_PSS_SHA_256, "SHA256", "RSAandMGF1"); + + encryptionAlgs.Add(X9ObjectIdentifiers.IdDsa.Id, "DSA"); + encryptionAlgs.Add(PkcsObjectIdentifiers.RsaEncryption.Id, "RSA"); + encryptionAlgs.Add(TeleTrusTObjectIdentifiers.TeleTrusTRsaSignatureAlgorithm, "RSA"); + encryptionAlgs.Add(X509ObjectIdentifiers.IdEARsa.Id, "RSA"); + encryptionAlgs.Add(CmsSignedGenerator.EncryptionRsaPss, "RSAandMGF1"); + encryptionAlgs.Add(CryptoProObjectIdentifiers.GostR3410x94.Id, "GOST3410"); + encryptionAlgs.Add(CryptoProObjectIdentifiers.GostR3410x2001.Id, "ECGOST3410"); + encryptionAlgs.Add("1.3.6.1.4.1.5849.1.6.2", "ECGOST3410"); + encryptionAlgs.Add("1.3.6.1.4.1.5849.1.1.5", "GOST3410"); + + digestAlgs.Add(PkcsObjectIdentifiers.MD2.Id, "MD2"); + digestAlgs.Add(PkcsObjectIdentifiers.MD4.Id, "MD4"); + digestAlgs.Add(PkcsObjectIdentifiers.MD5.Id, "MD5"); + digestAlgs.Add(OiwObjectIdentifiers.IdSha1.Id, "SHA1"); + digestAlgs.Add(NistObjectIdentifiers.IdSha224.Id, "SHA224"); + digestAlgs.Add(NistObjectIdentifiers.IdSha256.Id, "SHA256"); + digestAlgs.Add(NistObjectIdentifiers.IdSha384.Id, "SHA384"); + digestAlgs.Add(NistObjectIdentifiers.IdSha512.Id, "SHA512"); + digestAlgs.Add(TeleTrusTObjectIdentifiers.RipeMD128.Id, "RIPEMD128"); + digestAlgs.Add(TeleTrusTObjectIdentifiers.RipeMD160.Id, "RIPEMD160"); + digestAlgs.Add(TeleTrusTObjectIdentifiers.RipeMD256.Id, "RIPEMD256"); + digestAlgs.Add(CryptoProObjectIdentifiers.GostR3411.Id, "GOST3411"); + digestAlgs.Add("1.3.6.1.4.1.5849.1.2.1", "GOST3411"); + + digestAliases.Add("SHA1", new string[] { "SHA-1" }); + digestAliases.Add("SHA224", new string[] { "SHA-224" }); + digestAliases.Add("SHA256", new string[] { "SHA-256" }); + digestAliases.Add("SHA384", new string[] { "SHA-384" }); + digestAliases.Add("SHA512", new string[] { "SHA-512" }); + + noParams.Add(CmsSignedGenerator.EncryptionDsa); + // noParams.Add(EncryptionECDsa); + noParams.Add(EncryptionECDsaWithSha1); + noParams.Add(EncryptionECDsaWithSha224); + noParams.Add(EncryptionECDsaWithSha256); + noParams.Add(EncryptionECDsaWithSha384); + noParams.Add(EncryptionECDsaWithSha512); + + ecAlgorithms.Add(CmsSignedGenerator.DigestSha1, EncryptionECDsaWithSha1); + ecAlgorithms.Add(CmsSignedGenerator.DigestSha224, EncryptionECDsaWithSha224); + ecAlgorithms.Add(CmsSignedGenerator.DigestSha256, EncryptionECDsaWithSha256); + ecAlgorithms.Add(CmsSignedGenerator.DigestSha384, EncryptionECDsaWithSha384); + ecAlgorithms.Add(CmsSignedGenerator.DigestSha512, EncryptionECDsaWithSha512); + } + + /** + * Return the digest algorithm using one of the standard JCA string + * representations rather than the algorithm identifier (if possible). + */ + internal string GetDigestAlgName( + string digestAlgOid) + { + string algName = (string)digestAlgs[digestAlgOid]; + + if (algName != null) + { + return algName; + } + + return digestAlgOid; + } + + internal AlgorithmIdentifier GetEncAlgorithmIdentifier( + DerObjectIdentifier encOid, + Asn1Encodable sigX509Parameters) + { + if (noParams.Contains(encOid.Id)) + { + return new AlgorithmIdentifier(encOid); + } + + return new AlgorithmIdentifier(encOid, sigX509Parameters); + } + + internal string[] GetDigestAliases( + string algName) + { + string[] aliases = (string[]) digestAliases[algName]; + + return aliases == null ? new String[0] : (string[]) aliases.Clone(); + } + + /** + * Return the digest encryption algorithm using one of the standard + * JCA string representations rather than the algorithm identifier (if + * possible). + */ + internal string GetEncryptionAlgName( + string encryptionAlgOid) + { + string algName = (string) encryptionAlgs[encryptionAlgOid]; + + if (algName != null) + { + return algName; + } + + return encryptionAlgOid; + } + + internal IDigest GetDigestInstance( + string algorithm) + { + try + { + return DigestUtilities.GetDigest(algorithm); + } + catch (SecurityUtilityException e) + { + // This is probably superfluous on C#, since no provider infrastructure, + // assuming DigestUtilities already knows all the aliases + foreach (string alias in GetDigestAliases(algorithm)) + { + try { return DigestUtilities.GetDigest(alias); } + catch (SecurityUtilityException) {} + } + throw e; + } + } + + internal ISigner GetSignatureInstance( + string algorithm) + { + return SignerUtilities.GetSigner(algorithm); + } + + internal IX509Store CreateAttributeStore( + string type, + Asn1Set certSet) + { + IList certs = Platform.CreateArrayList(); + + if (certSet != null) + { + foreach (Asn1Encodable ae in certSet) + { + try + { + Asn1Object obj = ae.ToAsn1Object(); + + if (obj is Asn1TaggedObject) + { + Asn1TaggedObject tagged = (Asn1TaggedObject)obj; + + if (tagged.TagNo == 2) + { + certs.Add( + new X509V2AttributeCertificate( + Asn1Sequence.GetInstance(tagged, false).GetEncoded())); + } + } + } + catch (Exception ex) + { + throw new CmsException("can't re-encode attribute certificate!", ex); + } + } + } + + try + { + return X509StoreFactory.Create( + "AttributeCertificate/" + type, + new X509CollectionStoreParameters(certs)); + } + catch (ArgumentException e) + { + throw new CmsException("can't setup the X509Store", e); + } + } + + internal IX509Store CreateCertificateStore( + string type, + Asn1Set certSet) + { + IList certs = Platform.CreateArrayList(); + + if (certSet != null) + { + AddCertsFromSet(certs, certSet); + } + + try + { + return X509StoreFactory.Create( + "Certificate/" + type, + new X509CollectionStoreParameters(certs)); + } + catch (ArgumentException e) + { + throw new CmsException("can't setup the X509Store", e); + } + } + + internal IX509Store CreateCrlStore( + string type, + Asn1Set crlSet) + { + IList crls = Platform.CreateArrayList(); + + if (crlSet != null) + { + AddCrlsFromSet(crls, crlSet); + } + + try + { + return X509StoreFactory.Create( + "CRL/" + type, + new X509CollectionStoreParameters(crls)); + } + catch (ArgumentException e) + { + throw new CmsException("can't setup the X509Store", e); + } + } + + private void AddCertsFromSet( + IList certs, + Asn1Set certSet) + { + X509CertificateParser cf = new X509CertificateParser(); + + foreach (Asn1Encodable ae in certSet) + { + try + { + Asn1Object obj = ae.ToAsn1Object(); + + if (obj is Asn1Sequence) + { + // TODO Build certificate directly from sequence? + certs.Add(cf.ReadCertificate(obj.GetEncoded())); + } + } + catch (Exception ex) + { + throw new CmsException("can't re-encode certificate!", ex); + } + } + } + + private void AddCrlsFromSet( + IList crls, + Asn1Set crlSet) + { + X509CrlParser cf = new X509CrlParser(); + + foreach (Asn1Encodable ae in crlSet) + { + try + { + // TODO Build CRL directly from ae.ToAsn1Object()? + crls.Add(cf.ReadCrl(ae.GetEncoded())); + } + catch (Exception ex) + { + throw new CmsException("can't re-encode CRL!", ex); + } + } + } + + internal AlgorithmIdentifier FixAlgID( + AlgorithmIdentifier algId) + { + if (algId.Parameters == null) + return new AlgorithmIdentifier(algId.Algorithm, DerNull.Instance); + + return algId; + } + + internal string GetEncOid( + AsymmetricKeyParameter key, + string digestOID) + { + string encOID = null; + + if (key is RsaKeyParameters) + { + if (!((RsaKeyParameters)key).IsPrivate) + throw new ArgumentException("Expected RSA private key"); + + encOID = CmsSignedGenerator.EncryptionRsa; + } + else if (key is DsaPrivateKeyParameters) + { + if (digestOID.Equals(CmsSignedGenerator.DigestSha1)) + { + encOID = CmsSignedGenerator.EncryptionDsa; + } + else if (digestOID.Equals(CmsSignedGenerator.DigestSha224)) + { + encOID = NistObjectIdentifiers.DsaWithSha224.Id; + } + else if (digestOID.Equals(CmsSignedGenerator.DigestSha256)) + { + encOID = NistObjectIdentifiers.DsaWithSha256.Id; + } + else if (digestOID.Equals(CmsSignedGenerator.DigestSha384)) + { + encOID = NistObjectIdentifiers.DsaWithSha384.Id; + } + else if (digestOID.Equals(CmsSignedGenerator.DigestSha512)) + { + encOID = NistObjectIdentifiers.DsaWithSha512.Id; + } + else + { + throw new ArgumentException("can't mix DSA with anything but SHA1/SHA2"); + } + } + else if (key is ECPrivateKeyParameters) + { + ECPrivateKeyParameters ecPrivKey = (ECPrivateKeyParameters)key; + string algName = ecPrivKey.AlgorithmName; + + if (algName == "ECGOST3410") + { + encOID = CmsSignedGenerator.EncryptionECGost3410; + } + else + { + // TODO Should we insist on algName being one of "EC" or "ECDSA", as Java does? + encOID = (string)ecAlgorithms[digestOID]; + + if (encOID == null) + throw new ArgumentException("can't mix ECDSA with anything but SHA family digests"); + } + } + else if (key is Gost3410PrivateKeyParameters) + { + encOID = CmsSignedGenerator.EncryptionGost3410; + } + else + { + throw new ArgumentException("Unknown algorithm in CmsSignedGenerator.GetEncOid"); + } + + return encOID; + } + } +} diff --git a/bc-sharp-crypto/src/cms/CMSStreamException.cs b/bc-sharp-crypto/src/cms/CMSStreamException.cs new file mode 100644 index 0000000..68a8be0 --- /dev/null +++ b/bc-sharp-crypto/src/cms/CMSStreamException.cs @@ -0,0 +1,29 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Cms +{ +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT || PORTABLE) + [Serializable] +#endif + public class CmsStreamException + : IOException + { + public CmsStreamException() + { + } + + public CmsStreamException( + string name) + : base(name) + { + } + + public CmsStreamException( + string name, + Exception e) + : base(name, e) + { + } + } +} diff --git a/bc-sharp-crypto/src/cms/CMSTypedStream.cs b/bc-sharp-crypto/src/cms/CMSTypedStream.cs new file mode 100644 index 0000000..6815837 --- /dev/null +++ b/bc-sharp-crypto/src/cms/CMSTypedStream.cs @@ -0,0 +1,72 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Cms +{ + public class CmsTypedStream + { + private const int BufferSize = 32 * 1024; + + private readonly string _oid; + private readonly Stream _in; + + public CmsTypedStream( + Stream inStream) + : this(PkcsObjectIdentifiers.Data.Id, inStream, BufferSize) + { + } + + public CmsTypedStream( + string oid, + Stream inStream) + : this(oid, inStream, BufferSize) + { + } + + public CmsTypedStream( + string oid, + Stream inStream, + int bufSize) + { + _oid = oid; +#if NETCF_1_0 || NETCF_2_0 || SILVERLIGHT || PORTABLE + _in = new FullReaderStream(inStream); +#else + _in = new FullReaderStream(new BufferedStream(inStream, bufSize)); +#endif + } + + public string ContentType + { + get { return _oid; } + } + + public Stream ContentStream + { + get { return _in; } + } + + public void Drain() + { + Streams.Drain(_in); + Platform.Dispose(_in); + } + + private class FullReaderStream : FilterStream + { + internal FullReaderStream(Stream input) + : base(input) + { + } + + public override int Read(byte[] buf, int off, int len) + { + return Streams.ReadFully(base.s, buf, off, len); + } + } + } +} diff --git a/bc-sharp-crypto/src/cms/CMSUtils.cs b/bc-sharp-crypto/src/cms/CMSUtils.cs new file mode 100644 index 0000000..95d7106 --- /dev/null +++ b/bc-sharp-crypto/src/cms/CMSUtils.cs @@ -0,0 +1,186 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Cms; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Security.Certificates; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.IO; +using Org.BouncyCastle.X509; +using Org.BouncyCastle.X509.Store; + +namespace Org.BouncyCastle.Cms +{ + internal class CmsUtilities + { + // TODO Is there a .NET equivalent to this? +// private static readonly Runtime RUNTIME = Runtime.getRuntime(); + + internal static int MaximumMemory + { + get + { + // TODO Is there a .NET equivalent to this? + long maxMem = int.MaxValue;//RUNTIME.maxMemory(); + + if (maxMem > int.MaxValue) + { + return int.MaxValue; + } + + return (int)maxMem; + } + } + + internal static ContentInfo ReadContentInfo( + byte[] input) + { + // enforce limit checking as from a byte array + return ReadContentInfo(new Asn1InputStream(input)); + } + + internal static ContentInfo ReadContentInfo( + Stream input) + { + // enforce some limit checking + return ReadContentInfo(new Asn1InputStream(input, MaximumMemory)); + } + + private static ContentInfo ReadContentInfo( + Asn1InputStream aIn) + { + try + { + return ContentInfo.GetInstance(aIn.ReadObject()); + } + catch (IOException e) + { + throw new CmsException("IOException reading content.", e); + } + catch (InvalidCastException e) + { + throw new CmsException("Malformed content.", e); + } + catch (ArgumentException e) + { + throw new CmsException("Malformed content.", e); + } + } + + public static byte[] StreamToByteArray( + Stream inStream) + { + return Streams.ReadAll(inStream); + } + + public static byte[] StreamToByteArray( + Stream inStream, + int limit) + { + return Streams.ReadAllLimited(inStream, limit); + } + + public static IList GetCertificatesFromStore( + IX509Store certStore) + { + try + { + IList certs = Platform.CreateArrayList(); + + if (certStore != null) + { + foreach (X509Certificate c in certStore.GetMatches(null)) + { + certs.Add( + X509CertificateStructure.GetInstance( + Asn1Object.FromByteArray(c.GetEncoded()))); + } + } + + return certs; + } + catch (CertificateEncodingException e) + { + throw new CmsException("error encoding certs", e); + } + catch (Exception e) + { + throw new CmsException("error processing certs", e); + } + } + + public static IList GetCrlsFromStore( + IX509Store crlStore) + { + try + { + IList crls = Platform.CreateArrayList(); + + if (crlStore != null) + { + foreach (X509Crl c in crlStore.GetMatches(null)) + { + crls.Add( + CertificateList.GetInstance( + Asn1Object.FromByteArray(c.GetEncoded()))); + } + } + + return crls; + } + catch (CrlException e) + { + throw new CmsException("error encoding crls", e); + } + catch (Exception e) + { + throw new CmsException("error processing crls", e); + } + } + + public static Asn1Set CreateBerSetFromList( + IList berObjects) + { + Asn1EncodableVector v = new Asn1EncodableVector(); + + foreach (Asn1Encodable ae in berObjects) + { + v.Add(ae); + } + + return new BerSet(v); + } + + public static Asn1Set CreateDerSetFromList( + IList derObjects) + { + Asn1EncodableVector v = new Asn1EncodableVector(); + + foreach (Asn1Encodable ae in derObjects) + { + v.Add(ae); + } + + return new DerSet(v); + } + + internal static Stream CreateBerOctetOutputStream(Stream s, int tagNo, bool isExplicit, int bufferSize) + { + BerOctetStringGenerator octGen = new BerOctetStringGenerator(s, tagNo, isExplicit); + return octGen.GetOctetOutputStream(bufferSize); + } + + internal static TbsCertificateStructure GetTbsCertificateStructure(X509Certificate cert) + { + return TbsCertificateStructure.GetInstance(Asn1Object.FromByteArray(cert.GetTbsCertificate())); + } + + internal static IssuerAndSerialNumber GetIssuerAndSerialNumber(X509Certificate cert) + { + TbsCertificateStructure tbsCert = GetTbsCertificateStructure(cert); + return new IssuerAndSerialNumber(tbsCert.Issuer, tbsCert.SerialNumber.Value); + } + } +} diff --git a/bc-sharp-crypto/src/cms/CounterSignatureDigestCalculator.cs b/bc-sharp-crypto/src/cms/CounterSignatureDigestCalculator.cs new file mode 100644 index 0000000..6f8bf65 --- /dev/null +++ b/bc-sharp-crypto/src/cms/CounterSignatureDigestCalculator.cs @@ -0,0 +1,28 @@ +using System; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Cms +{ + internal class CounterSignatureDigestCalculator + : IDigestCalculator + { + private readonly string alg; + private readonly byte[] data; + + internal CounterSignatureDigestCalculator( + string alg, + byte[] data) + { + this.alg = alg; + this.data = data; + } + + public byte[] GetDigest() + { + IDigest digest = CmsSignedHelper.Instance.GetDigestInstance(alg); + return DigestUtilities.DoFinal(digest, data); + } + } +} diff --git a/bc-sharp-crypto/src/cms/DefaultAuthenticatedAttributeTableGenerator.cs b/bc-sharp-crypto/src/cms/DefaultAuthenticatedAttributeTableGenerator.cs new file mode 100644 index 0000000..d49b1d9 --- /dev/null +++ b/bc-sharp-crypto/src/cms/DefaultAuthenticatedAttributeTableGenerator.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Cms; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Cms +{ + /** + * Default authenticated attributes generator. + */ + public class DefaultAuthenticatedAttributeTableGenerator + : CmsAttributeTableGenerator + { + private readonly IDictionary table; + + /** + * Initialise to use all defaults + */ + public DefaultAuthenticatedAttributeTableGenerator() + { + table = Platform.CreateHashtable(); + } + + /** + * Initialise with some extra attributes or overrides. + * + * @param attributeTable initial attribute table to use. + */ + public DefaultAuthenticatedAttributeTableGenerator( + AttributeTable attributeTable) + { + if (attributeTable != null) + { + table = attributeTable.ToDictionary(); + } + else + { + table = Platform.CreateHashtable(); + } + } + + /** + * Create a standard attribute table from the passed in parameters - this will + * normally include contentType and messageDigest. If the constructor + * using an AttributeTable was used, entries in it for contentType and + * messageDigest will override the generated ones. + * + * @param parameters source parameters for table generation. + * + * @return a filled in IDictionary of attributes. + */ + protected virtual IDictionary CreateStandardAttributeTable( + IDictionary parameters) + { + IDictionary std = Platform.CreateHashtable(table); + + if (!std.Contains(CmsAttributes.ContentType)) + { + DerObjectIdentifier contentType = (DerObjectIdentifier) + parameters[CmsAttributeTableParameter.ContentType]; + Asn1.Cms.Attribute attr = new Asn1.Cms.Attribute(CmsAttributes.ContentType, + new DerSet(contentType)); + std[attr.AttrType] = attr; + } + + if (!std.Contains(CmsAttributes.MessageDigest)) + { + byte[] messageDigest = (byte[])parameters[CmsAttributeTableParameter.Digest]; + Asn1.Cms.Attribute attr = new Asn1.Cms.Attribute(CmsAttributes.MessageDigest, + new DerSet(new DerOctetString(messageDigest))); + std[attr.AttrType] = attr; + } + + return std; + } + + /** + * @param parameters source parameters + * @return the populated attribute table + */ + public virtual AttributeTable GetAttributes( + IDictionary parameters) + { + IDictionary table = CreateStandardAttributeTable(parameters); + return new AttributeTable(table); + } + } +} diff --git a/bc-sharp-crypto/src/cms/DefaultSignedAttributeTableGenerator.cs b/bc-sharp-crypto/src/cms/DefaultSignedAttributeTableGenerator.cs new file mode 100644 index 0000000..925a98a --- /dev/null +++ b/bc-sharp-crypto/src/cms/DefaultSignedAttributeTableGenerator.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Cms; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Cms +{ + /** + * Default signed attributes generator. + */ + public class DefaultSignedAttributeTableGenerator + : CmsAttributeTableGenerator + { + private readonly IDictionary table; + + /** + * Initialise to use all defaults + */ + public DefaultSignedAttributeTableGenerator() + { + table = Platform.CreateHashtable(); + } + + /** + * Initialise with some extra attributes or overrides. + * + * @param attributeTable initial attribute table to use. + */ + public DefaultSignedAttributeTableGenerator( + AttributeTable attributeTable) + { + if (attributeTable != null) + { + table = attributeTable.ToDictionary(); + } + else + { + table = Platform.CreateHashtable(); + } + } + +#if SILVERLIGHT || PORTABLE + /** + * Create a standard attribute table from the passed in parameters - this will + * normally include contentType, signingTime, and messageDigest. If the constructor + * using an AttributeTable was used, entries in it for contentType, signingTime, and + * messageDigest will override the generated ones. + * + * @param parameters source parameters for table generation. + * + * @return a filled in Hashtable of attributes. + */ + protected virtual IDictionary createStandardAttributeTable( + IDictionary parameters) + { + IDictionary std = Platform.CreateHashtable(table); + DoCreateStandardAttributeTable(parameters, std); + return std; + } +#else + /** + * Create a standard attribute table from the passed in parameters - this will + * normally include contentType, signingTime, and messageDigest. If the constructor + * using an AttributeTable was used, entries in it for contentType, signingTime, and + * messageDigest will override the generated ones. + * + * @param parameters source parameters for table generation. + * + * @return a filled in Hashtable of attributes. + */ + protected virtual Hashtable createStandardAttributeTable( + IDictionary parameters) + { + Hashtable std = new Hashtable(table); + DoCreateStandardAttributeTable(parameters, std); + return std; + } +#endif + + private void DoCreateStandardAttributeTable(IDictionary parameters, IDictionary std) + { + // contentType will be absent if we're trying to generate a counter signature. + if (parameters.Contains(CmsAttributeTableParameter.ContentType)) + { + if (!std.Contains(CmsAttributes.ContentType)) + { + DerObjectIdentifier contentType = (DerObjectIdentifier) + parameters[CmsAttributeTableParameter.ContentType]; + Asn1.Cms.Attribute attr = new Asn1.Cms.Attribute(CmsAttributes.ContentType, + new DerSet(contentType)); + std[attr.AttrType] = attr; + } + } + + if (!std.Contains(CmsAttributes.SigningTime)) + { + Asn1.Cms.Attribute attr = new Asn1.Cms.Attribute(CmsAttributes.SigningTime, + new DerSet(new Time(DateTime.UtcNow))); + std[attr.AttrType] = attr; + } + + if (!std.Contains(CmsAttributes.MessageDigest)) + { + byte[] messageDigest = (byte[])parameters[CmsAttributeTableParameter.Digest]; + Asn1.Cms.Attribute attr = new Asn1.Cms.Attribute(CmsAttributes.MessageDigest, + new DerSet(new DerOctetString(messageDigest))); + std[attr.AttrType] = attr; + } + } + + /** + * @param parameters source parameters + * @return the populated attribute table + */ + public virtual AttributeTable GetAttributes( + IDictionary parameters) + { + IDictionary table = createStandardAttributeTable(parameters); + return new AttributeTable(table); + } + } +} diff --git a/bc-sharp-crypto/src/cms/DigOutputStream.cs b/bc-sharp-crypto/src/cms/DigOutputStream.cs new file mode 100644 index 0000000..103b45c --- /dev/null +++ b/bc-sharp-crypto/src/cms/DigOutputStream.cs @@ -0,0 +1,28 @@ +using System; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Cms +{ + internal class DigOutputStream + : BaseOutputStream + { + private readonly IDigest dig; + + internal DigOutputStream(IDigest dig) + { + this.dig = dig; + } + + public override void WriteByte(byte b) + { + dig.Update(b); + } + + public override void Write(byte[] b, int off, int len) + { + dig.BlockUpdate(b, off, len); + } + } +} diff --git a/bc-sharp-crypto/src/cms/IDigestCalculator.cs b/bc-sharp-crypto/src/cms/IDigestCalculator.cs new file mode 100644 index 0000000..3661e40 --- /dev/null +++ b/bc-sharp-crypto/src/cms/IDigestCalculator.cs @@ -0,0 +1,9 @@ +using System; + +namespace Org.BouncyCastle.Cms +{ + internal interface IDigestCalculator + { + byte[] GetDigest(); + } +} diff --git a/bc-sharp-crypto/src/cms/KEKRecipientInfoGenerator.cs b/bc-sharp-crypto/src/cms/KEKRecipientInfoGenerator.cs new file mode 100644 index 0000000..6f34fec --- /dev/null +++ b/bc-sharp-crypto/src/cms/KEKRecipientInfoGenerator.cs @@ -0,0 +1,138 @@ +using System; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Cms; +using Org.BouncyCastle.Asn1.Kisa; +using Org.BouncyCastle.Asn1.Nist; +using Org.BouncyCastle.Asn1.Ntt; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Cms +{ + internal class KekRecipientInfoGenerator : RecipientInfoGenerator + { + private static readonly CmsEnvelopedHelper Helper = CmsEnvelopedHelper.Instance; + + private KeyParameter keyEncryptionKey; + // TODO Can get this from keyEncryptionKey? + private string keyEncryptionKeyOID; + private KekIdentifier kekIdentifier; + + // Derived + private AlgorithmIdentifier keyEncryptionAlgorithm; + + internal KekRecipientInfoGenerator() + { + } + + internal KekIdentifier KekIdentifier + { + set { this.kekIdentifier = value; } + } + + internal KeyParameter KeyEncryptionKey + { + set + { + this.keyEncryptionKey = value; + this.keyEncryptionAlgorithm = DetermineKeyEncAlg(keyEncryptionKeyOID, keyEncryptionKey); + } + } + + internal string KeyEncryptionKeyOID + { + set { this.keyEncryptionKeyOID = value; } + } + + public RecipientInfo Generate(KeyParameter contentEncryptionKey, SecureRandom random) + { + byte[] keyBytes = contentEncryptionKey.GetKey(); + + IWrapper keyWrapper = Helper.CreateWrapper(keyEncryptionAlgorithm.Algorithm.Id); + keyWrapper.Init(true, new ParametersWithRandom(keyEncryptionKey, random)); + Asn1OctetString encryptedKey = new DerOctetString( + keyWrapper.Wrap(keyBytes, 0, keyBytes.Length)); + + return new RecipientInfo(new KekRecipientInfo(kekIdentifier, keyEncryptionAlgorithm, encryptedKey)); + } + + private static AlgorithmIdentifier DetermineKeyEncAlg( + string algorithm, KeyParameter key) + { + if (Platform.StartsWith(algorithm, "DES")) + { + return new AlgorithmIdentifier( + PkcsObjectIdentifiers.IdAlgCms3DesWrap, + DerNull.Instance); + } + else if (Platform.StartsWith(algorithm, "RC2")) + { + return new AlgorithmIdentifier( + PkcsObjectIdentifiers.IdAlgCmsRC2Wrap, + new DerInteger(58)); + } + else if (Platform.StartsWith(algorithm, "AES")) + { + int length = key.GetKey().Length * 8; + DerObjectIdentifier wrapOid; + + if (length == 128) + { + wrapOid = NistObjectIdentifiers.IdAes128Wrap; + } + else if (length == 192) + { + wrapOid = NistObjectIdentifiers.IdAes192Wrap; + } + else if (length == 256) + { + wrapOid = NistObjectIdentifiers.IdAes256Wrap; + } + else + { + throw new ArgumentException("illegal keysize in AES"); + } + + return new AlgorithmIdentifier(wrapOid); // parameters absent + } + else if (Platform.StartsWith(algorithm, "SEED")) + { + // parameters absent + return new AlgorithmIdentifier(KisaObjectIdentifiers.IdNpkiAppCmsSeedWrap); + } + else if (Platform.StartsWith(algorithm, "CAMELLIA")) + { + int length = key.GetKey().Length * 8; + DerObjectIdentifier wrapOid; + + if (length == 128) + { + wrapOid = NttObjectIdentifiers.IdCamellia128Wrap; + } + else if (length == 192) + { + wrapOid = NttObjectIdentifiers.IdCamellia192Wrap; + } + else if (length == 256) + { + wrapOid = NttObjectIdentifiers.IdCamellia256Wrap; + } + else + { + throw new ArgumentException("illegal keysize in Camellia"); + } + + return new AlgorithmIdentifier(wrapOid); // parameters must be absent + } + else + { + throw new ArgumentException("unknown algorithm"); + } + } + } +} diff --git a/bc-sharp-crypto/src/cms/KEKRecipientInformation.cs b/bc-sharp-crypto/src/cms/KEKRecipientInformation.cs new file mode 100644 index 0000000..871dc76 --- /dev/null +++ b/bc-sharp-crypto/src/cms/KEKRecipientInformation.cs @@ -0,0 +1,62 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Asn1.Cms; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Cms +{ + /** + * the RecipientInfo class for a recipient who has been sent a message + * encrypted using a secret key known to the other side. + */ + public class KekRecipientInformation + : RecipientInformation + { + private KekRecipientInfo info; + + internal KekRecipientInformation( + KekRecipientInfo info, + CmsSecureReadable secureReadable) + : base(info.KeyEncryptionAlgorithm, secureReadable) + { + this.info = info; + this.rid = new RecipientID(); + + KekIdentifier kekId = info.KekID; + + rid.KeyIdentifier = kekId.KeyIdentifier.GetOctets(); + } + + /** + * decrypt the content and return an input stream. + */ + public override CmsTypedStream GetContentStream( + ICipherParameters key) + { + try + { + byte[] encryptedKey = info.EncryptedKey.GetOctets(); + IWrapper keyWrapper = WrapperUtilities.GetWrapper(keyEncAlg.Algorithm.Id); + + keyWrapper.Init(false, key); + + KeyParameter sKey = ParameterUtilities.CreateKeyParameter( + GetContentAlgorithmName(), keyWrapper.Unwrap(encryptedKey, 0, encryptedKey.Length)); + + return GetContentFromSessionKey(sKey); + } + catch (SecurityUtilityException e) + { + throw new CmsException("couldn't create cipher.", e); + } + catch (InvalidKeyException e) + { + throw new CmsException("key invalid in message.", e); + } + } + } +} diff --git a/bc-sharp-crypto/src/cms/KeyAgreeRecipientInfoGenerator.cs b/bc-sharp-crypto/src/cms/KeyAgreeRecipientInfoGenerator.cs new file mode 100644 index 0000000..6bd2cea --- /dev/null +++ b/bc-sharp-crypto/src/cms/KeyAgreeRecipientInfoGenerator.cs @@ -0,0 +1,171 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Cms; +using Org.BouncyCastle.Asn1.Cms.Ecc; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.X509; + +namespace Org.BouncyCastle.Cms +{ + internal class KeyAgreeRecipientInfoGenerator : RecipientInfoGenerator + { + private static readonly CmsEnvelopedHelper Helper = CmsEnvelopedHelper.Instance; + + private DerObjectIdentifier keyAgreementOID; + private DerObjectIdentifier keyEncryptionOID; + private IList recipientCerts; + private AsymmetricCipherKeyPair senderKeyPair; + + internal KeyAgreeRecipientInfoGenerator() + { + } + + internal DerObjectIdentifier KeyAgreementOID + { + set { this.keyAgreementOID = value; } + } + + internal DerObjectIdentifier KeyEncryptionOID + { + set { this.keyEncryptionOID = value; } + } + + internal ICollection RecipientCerts + { + set { this.recipientCerts = Platform.CreateArrayList(value); } + } + + internal AsymmetricCipherKeyPair SenderKeyPair + { + set { this.senderKeyPair = value; } + } + + public RecipientInfo Generate(KeyParameter contentEncryptionKey, SecureRandom random) + { + byte[] keyBytes = contentEncryptionKey.GetKey(); + + AsymmetricKeyParameter senderPublicKey = senderKeyPair.Public; + ICipherParameters senderPrivateParams = senderKeyPair.Private; + + + OriginatorIdentifierOrKey originator; + try + { + originator = new OriginatorIdentifierOrKey( + CreateOriginatorPublicKey(senderPublicKey)); + } + catch (IOException e) + { + throw new InvalidKeyException("cannot extract originator public key: " + e); + } + + + Asn1OctetString ukm = null; + if (keyAgreementOID.Id.Equals(CmsEnvelopedGenerator.ECMqvSha1Kdf)) + { + try + { + IAsymmetricCipherKeyPairGenerator ephemKPG = + GeneratorUtilities.GetKeyPairGenerator(keyAgreementOID); + ephemKPG.Init( + ((ECPublicKeyParameters)senderPublicKey).CreateKeyGenerationParameters(random)); + + AsymmetricCipherKeyPair ephemKP = ephemKPG.GenerateKeyPair(); + + ukm = new DerOctetString( + new MQVuserKeyingMaterial( + CreateOriginatorPublicKey(ephemKP.Public), null)); + + senderPrivateParams = new MqvPrivateParameters( + (ECPrivateKeyParameters)senderPrivateParams, + (ECPrivateKeyParameters)ephemKP.Private, + (ECPublicKeyParameters)ephemKP.Public); + } + catch (IOException e) + { + throw new InvalidKeyException("cannot extract MQV ephemeral public key: " + e); + } + catch (SecurityUtilityException e) + { + throw new InvalidKeyException("cannot determine MQV ephemeral key pair parameters from public key: " + e); + } + } + + + DerSequence paramSeq = new DerSequence( + keyEncryptionOID, + DerNull.Instance); + AlgorithmIdentifier keyEncAlg = new AlgorithmIdentifier(keyAgreementOID, paramSeq); + + + Asn1EncodableVector recipientEncryptedKeys = new Asn1EncodableVector(); + foreach (X509Certificate recipientCert in recipientCerts) + { + TbsCertificateStructure tbsCert; + try + { + tbsCert = TbsCertificateStructure.GetInstance( + Asn1Object.FromByteArray(recipientCert.GetTbsCertificate())); + } + catch (Exception) + { + throw new ArgumentException("can't extract TBS structure from certificate"); + } + + // TODO Should there be a SubjectKeyIdentifier-based alternative? + IssuerAndSerialNumber issuerSerial = new IssuerAndSerialNumber( + tbsCert.Issuer, tbsCert.SerialNumber.Value); + KeyAgreeRecipientIdentifier karid = new KeyAgreeRecipientIdentifier(issuerSerial); + + ICipherParameters recipientPublicParams = recipientCert.GetPublicKey(); + if (keyAgreementOID.Id.Equals(CmsEnvelopedGenerator.ECMqvSha1Kdf)) + { + recipientPublicParams = new MqvPublicParameters( + (ECPublicKeyParameters)recipientPublicParams, + (ECPublicKeyParameters)recipientPublicParams); + } + + // Use key agreement to choose a wrap key for this recipient + IBasicAgreement keyAgreement = AgreementUtilities.GetBasicAgreementWithKdf( + keyAgreementOID, keyEncryptionOID.Id); + keyAgreement.Init(new ParametersWithRandom(senderPrivateParams, random)); + BigInteger agreedValue = keyAgreement.CalculateAgreement(recipientPublicParams); + + int keyEncryptionKeySize = GeneratorUtilities.GetDefaultKeySize(keyEncryptionOID) / 8; + byte[] keyEncryptionKeyBytes = X9IntegerConverter.IntegerToBytes(agreedValue, keyEncryptionKeySize); + KeyParameter keyEncryptionKey = ParameterUtilities.CreateKeyParameter( + keyEncryptionOID, keyEncryptionKeyBytes); + + // Wrap the content encryption key with the agreement key + IWrapper keyWrapper = Helper.CreateWrapper(keyEncryptionOID.Id); + keyWrapper.Init(true, new ParametersWithRandom(keyEncryptionKey, random)); + byte[] encryptedKeyBytes = keyWrapper.Wrap(keyBytes, 0, keyBytes.Length); + + Asn1OctetString encryptedKey = new DerOctetString(encryptedKeyBytes); + + recipientEncryptedKeys.Add(new RecipientEncryptedKey(karid, encryptedKey)); + } + + return new RecipientInfo(new KeyAgreeRecipientInfo(originator, ukm, keyEncAlg, + new DerSequence(recipientEncryptedKeys))); + } + + private static OriginatorPublicKey CreateOriginatorPublicKey( + AsymmetricKeyParameter publicKey) + { + SubjectPublicKeyInfo spki = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(publicKey); + return new OriginatorPublicKey( + new AlgorithmIdentifier(spki.AlgorithmID.Algorithm, DerNull.Instance), + spki.PublicKeyData.GetBytes()); + } + } +} diff --git a/bc-sharp-crypto/src/cms/KeyAgreeRecipientInformation.cs b/bc-sharp-crypto/src/cms/KeyAgreeRecipientInformation.cs new file mode 100644 index 0000000..73e57a7 --- /dev/null +++ b/bc-sharp-crypto/src/cms/KeyAgreeRecipientInformation.cs @@ -0,0 +1,226 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Cms; +using Org.BouncyCastle.Asn1.Cms.Ecc; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.Utilities; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Pkcs; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.X509; + +namespace Org.BouncyCastle.Cms +{ + /** + * the RecipientInfo class for a recipient who has been sent a message + * encrypted using key agreement. + */ + public class KeyAgreeRecipientInformation + : RecipientInformation + { + private KeyAgreeRecipientInfo info; + private Asn1OctetString encryptedKey; + + internal static void ReadRecipientInfo(IList infos, KeyAgreeRecipientInfo info, + CmsSecureReadable secureReadable) + { + try + { + foreach (Asn1Encodable rek in info.RecipientEncryptedKeys) + { + RecipientEncryptedKey id = RecipientEncryptedKey.GetInstance(rek.ToAsn1Object()); + + RecipientID rid = new RecipientID(); + + Asn1.Cms.KeyAgreeRecipientIdentifier karid = id.Identifier; + + Asn1.Cms.IssuerAndSerialNumber iAndSN = karid.IssuerAndSerialNumber; + if (iAndSN != null) + { + rid.Issuer = iAndSN.Name; + rid.SerialNumber = iAndSN.SerialNumber.Value; + } + else + { + Asn1.Cms.RecipientKeyIdentifier rKeyID = karid.RKeyID; + + // Note: 'date' and 'other' fields of RecipientKeyIdentifier appear to be only informational + + rid.SubjectKeyIdentifier = rKeyID.SubjectKeyIdentifier.GetOctets(); + } + + infos.Add(new KeyAgreeRecipientInformation(info, rid, id.EncryptedKey, + secureReadable)); + } + } + catch (IOException e) + { + throw new ArgumentException("invalid rid in KeyAgreeRecipientInformation", e); + } + } + + internal KeyAgreeRecipientInformation( + KeyAgreeRecipientInfo info, + RecipientID rid, + Asn1OctetString encryptedKey, + CmsSecureReadable secureReadable) + : base(info.KeyEncryptionAlgorithm, secureReadable) + { + this.info = info; + this.rid = rid; + this.encryptedKey = encryptedKey; + } + + private AsymmetricKeyParameter GetSenderPublicKey( + AsymmetricKeyParameter receiverPrivateKey, + OriginatorIdentifierOrKey originator) + { + OriginatorPublicKey opk = originator.OriginatorPublicKey; + if (opk != null) + { + return GetPublicKeyFromOriginatorPublicKey(receiverPrivateKey, opk); + } + + OriginatorID origID = new OriginatorID(); + + Asn1.Cms.IssuerAndSerialNumber iAndSN = originator.IssuerAndSerialNumber; + if (iAndSN != null) + { + origID.Issuer = iAndSN.Name; + origID.SerialNumber = iAndSN.SerialNumber.Value; + } + else + { + SubjectKeyIdentifier ski = originator.SubjectKeyIdentifier; + + origID.SubjectKeyIdentifier = ski.GetKeyIdentifier(); + } + + return GetPublicKeyFromOriginatorID(origID); + } + + private AsymmetricKeyParameter GetPublicKeyFromOriginatorPublicKey( + AsymmetricKeyParameter receiverPrivateKey, + OriginatorPublicKey originatorPublicKey) + { + PrivateKeyInfo privInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(receiverPrivateKey); + SubjectPublicKeyInfo pubInfo = new SubjectPublicKeyInfo( + privInfo.PrivateKeyAlgorithm, + originatorPublicKey.PublicKey.GetBytes()); + return PublicKeyFactory.CreateKey(pubInfo); + } + + private AsymmetricKeyParameter GetPublicKeyFromOriginatorID( + OriginatorID origID) + { + // TODO Support all alternatives for OriginatorIdentifierOrKey + // see RFC 3852 6.2.2 + throw new CmsException("No support for 'originator' as IssuerAndSerialNumber or SubjectKeyIdentifier"); + } + + private KeyParameter CalculateAgreedWrapKey( + string wrapAlg, + AsymmetricKeyParameter senderPublicKey, + AsymmetricKeyParameter receiverPrivateKey) + { + DerObjectIdentifier agreeAlgID = keyEncAlg.Algorithm; + + ICipherParameters senderPublicParams = senderPublicKey; + ICipherParameters receiverPrivateParams = receiverPrivateKey; + + if (agreeAlgID.Id.Equals(CmsEnvelopedGenerator.ECMqvSha1Kdf)) + { + byte[] ukmEncoding = info.UserKeyingMaterial.GetOctets(); + MQVuserKeyingMaterial ukm = MQVuserKeyingMaterial.GetInstance( + Asn1Object.FromByteArray(ukmEncoding)); + + AsymmetricKeyParameter ephemeralKey = GetPublicKeyFromOriginatorPublicKey( + receiverPrivateKey, ukm.EphemeralPublicKey); + + senderPublicParams = new MqvPublicParameters( + (ECPublicKeyParameters)senderPublicParams, + (ECPublicKeyParameters)ephemeralKey); + receiverPrivateParams = new MqvPrivateParameters( + (ECPrivateKeyParameters)receiverPrivateParams, + (ECPrivateKeyParameters)receiverPrivateParams); + } + + IBasicAgreement agreement = AgreementUtilities.GetBasicAgreementWithKdf( + agreeAlgID, wrapAlg); + agreement.Init(receiverPrivateParams); + BigInteger agreedValue = agreement.CalculateAgreement(senderPublicParams); + + int wrapKeySize = GeneratorUtilities.GetDefaultKeySize(wrapAlg) / 8; + byte[] wrapKeyBytes = X9IntegerConverter.IntegerToBytes(agreedValue, wrapKeySize); + return ParameterUtilities.CreateKeyParameter(wrapAlg, wrapKeyBytes); + } + + private KeyParameter UnwrapSessionKey( + string wrapAlg, + KeyParameter agreedKey) + { + byte[] encKeyOctets = encryptedKey.GetOctets(); + + IWrapper keyCipher = WrapperUtilities.GetWrapper(wrapAlg); + keyCipher.Init(false, agreedKey); + byte[] sKeyBytes = keyCipher.Unwrap(encKeyOctets, 0, encKeyOctets.Length); + return ParameterUtilities.CreateKeyParameter(GetContentAlgorithmName(), sKeyBytes); + } + + internal KeyParameter GetSessionKey( + AsymmetricKeyParameter receiverPrivateKey) + { + try + { + string wrapAlg = DerObjectIdentifier.GetInstance( + Asn1Sequence.GetInstance(keyEncAlg.Parameters)[0]).Id; + + AsymmetricKeyParameter senderPublicKey = GetSenderPublicKey( + receiverPrivateKey, info.Originator); + + KeyParameter agreedWrapKey = CalculateAgreedWrapKey(wrapAlg, + senderPublicKey, receiverPrivateKey); + + return UnwrapSessionKey(wrapAlg, agreedWrapKey); + } + catch (SecurityUtilityException e) + { + throw new CmsException("couldn't create cipher.", e); + } + catch (InvalidKeyException e) + { + throw new CmsException("key invalid in message.", e); + } + catch (Exception e) + { + throw new CmsException("originator key invalid.", e); + } + } + + /** + * decrypt the content and return an input stream. + */ + public override CmsTypedStream GetContentStream( + ICipherParameters key) + { + if (!(key is AsymmetricKeyParameter)) + throw new ArgumentException("KeyAgreement requires asymmetric key", "key"); + + AsymmetricKeyParameter receiverPrivateKey = (AsymmetricKeyParameter) key; + + if (!receiverPrivateKey.IsPrivate) + throw new ArgumentException("Expected private key", "key"); + + KeyParameter sKey = GetSessionKey(receiverPrivateKey); + + return GetContentFromSessionKey(sKey); + } + } +} diff --git a/bc-sharp-crypto/src/cms/KeyTransRecipientInfoGenerator.cs b/bc-sharp-crypto/src/cms/KeyTransRecipientInfoGenerator.cs new file mode 100644 index 0000000..a1d8fbf --- /dev/null +++ b/bc-sharp-crypto/src/cms/KeyTransRecipientInfoGenerator.cs @@ -0,0 +1,87 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Cms; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.X509; + +namespace Org.BouncyCastle.Cms +{ + internal class KeyTransRecipientInfoGenerator : RecipientInfoGenerator + { + private static readonly CmsEnvelopedHelper Helper = CmsEnvelopedHelper.Instance; + + private TbsCertificateStructure recipientTbsCert; + private AsymmetricKeyParameter recipientPublicKey; + private Asn1OctetString subjectKeyIdentifier; + + // Derived fields + private SubjectPublicKeyInfo info; + + internal KeyTransRecipientInfoGenerator() + { + } + + internal X509Certificate RecipientCert + { + set + { + this.recipientTbsCert = CmsUtilities.GetTbsCertificateStructure(value); + this.recipientPublicKey = value.GetPublicKey(); + this.info = recipientTbsCert.SubjectPublicKeyInfo; + } + } + + internal AsymmetricKeyParameter RecipientPublicKey + { + set + { + this.recipientPublicKey = value; + + try + { + info = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo( + recipientPublicKey); + } + catch (IOException) + { + throw new ArgumentException("can't extract key algorithm from this key"); + } + } + } + + internal Asn1OctetString SubjectKeyIdentifier + { + set { this.subjectKeyIdentifier = value; } + } + + public RecipientInfo Generate(KeyParameter contentEncryptionKey, SecureRandom random) + { + byte[] keyBytes = contentEncryptionKey.GetKey(); + AlgorithmIdentifier keyEncryptionAlgorithm = info.AlgorithmID; + + IWrapper keyWrapper = Helper.CreateWrapper(keyEncryptionAlgorithm.Algorithm.Id); + keyWrapper.Init(true, new ParametersWithRandom(recipientPublicKey, random)); + byte[] encryptedKeyBytes = keyWrapper.Wrap(keyBytes, 0, keyBytes.Length); + + RecipientIdentifier recipId; + if (recipientTbsCert != null) + { + IssuerAndSerialNumber issuerAndSerial = new IssuerAndSerialNumber( + recipientTbsCert.Issuer, recipientTbsCert.SerialNumber.Value); + recipId = new RecipientIdentifier(issuerAndSerial); + } + else + { + recipId = new RecipientIdentifier(subjectKeyIdentifier); + } + + return new RecipientInfo(new KeyTransRecipientInfo(recipId, keyEncryptionAlgorithm, + new DerOctetString(encryptedKeyBytes))); + } + } +} diff --git a/bc-sharp-crypto/src/cms/KeyTransRecipientInformation.cs b/bc-sharp-crypto/src/cms/KeyTransRecipientInformation.cs new file mode 100644 index 0000000..3b1ea7b --- /dev/null +++ b/bc-sharp-crypto/src/cms/KeyTransRecipientInformation.cs @@ -0,0 +1,113 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Cms; +using Asn1Pkcs = Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.X509; + +namespace Org.BouncyCastle.Cms +{ + /** + * the KeyTransRecipientInformation class for a recipient who has been sent a secret + * key encrypted using their public key that needs to be used to + * extract the message. + */ + public class KeyTransRecipientInformation + : RecipientInformation + { + private KeyTransRecipientInfo info; + + internal KeyTransRecipientInformation( + KeyTransRecipientInfo info, + CmsSecureReadable secureReadable) + : base(info.KeyEncryptionAlgorithm, secureReadable) + { + this.info = info; + this.rid = new RecipientID(); + + RecipientIdentifier r = info.RecipientIdentifier; + + try + { + if (r.IsTagged) + { + Asn1OctetString octs = Asn1OctetString.GetInstance(r.ID); + + rid.SubjectKeyIdentifier = octs.GetOctets(); + } + else + { + IssuerAndSerialNumber iAnds = IssuerAndSerialNumber.GetInstance(r.ID); + + rid.Issuer = iAnds.Name; + rid.SerialNumber = iAnds.SerialNumber.Value; + } + } + catch (IOException) + { + throw new ArgumentException("invalid rid in KeyTransRecipientInformation"); + } + } + + private string GetExchangeEncryptionAlgorithmName( + DerObjectIdentifier oid) + { + if (Asn1Pkcs.PkcsObjectIdentifiers.RsaEncryption.Equals(oid)) + { + return "RSA//PKCS1Padding"; + } + + return oid.Id; + } + + internal KeyParameter UnwrapKey(ICipherParameters key) + { + byte[] encryptedKey = info.EncryptedKey.GetOctets(); + string keyExchangeAlgorithm = GetExchangeEncryptionAlgorithmName(keyEncAlg.Algorithm); + + try + { + IWrapper keyWrapper = WrapperUtilities.GetWrapper(keyExchangeAlgorithm); + keyWrapper.Init(false, key); + + // FIXME Support for MAC algorithm parameters similar to cipher parameters + return ParameterUtilities.CreateKeyParameter( + GetContentAlgorithmName(), keyWrapper.Unwrap(encryptedKey, 0, encryptedKey.Length)); + } + catch (SecurityUtilityException e) + { + throw new CmsException("couldn't create cipher.", e); + } + catch (InvalidKeyException e) + { + throw new CmsException("key invalid in message.", e); + } +// catch (IllegalBlockSizeException e) + catch (DataLengthException e) + { + throw new CmsException("illegal blocksize in message.", e); + } +// catch (BadPaddingException e) + catch (InvalidCipherTextException e) + { + throw new CmsException("bad padding in message.", e); + } + } + + /** + * decrypt the content and return it as a byte array. + */ + public override CmsTypedStream GetContentStream( + ICipherParameters key) + { + KeyParameter sKey = UnwrapKey(key); + + return GetContentFromSessionKey(sKey); + } + } +} diff --git a/bc-sharp-crypto/src/cms/MacOutputStream.cs b/bc-sharp-crypto/src/cms/MacOutputStream.cs new file mode 100644 index 0000000..8891dbc --- /dev/null +++ b/bc-sharp-crypto/src/cms/MacOutputStream.cs @@ -0,0 +1,28 @@ +using System; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Cms +{ + internal class MacOutputStream + : BaseOutputStream + { + private readonly IMac mac; + + internal MacOutputStream(IMac mac) + { + this.mac = mac; + } + + public override void Write(byte[] b, int off, int len) + { + mac.BlockUpdate(b, off, len); + } + + public override void WriteByte(byte b) + { + mac.Update(b); + } + } +} diff --git a/bc-sharp-crypto/src/cms/OriginatorId.cs b/bc-sharp-crypto/src/cms/OriginatorId.cs new file mode 100644 index 0000000..5a3b737 --- /dev/null +++ b/bc-sharp-crypto/src/cms/OriginatorId.cs @@ -0,0 +1,51 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.X509.Store; + +namespace Org.BouncyCastle.Cms +{ + /** + * a basic index for an originator. + */ + public class OriginatorID + : X509CertStoreSelector + { + public override int GetHashCode() + { + int code = Arrays.GetHashCode(this.SubjectKeyIdentifier); + + BigInteger serialNumber = this.SerialNumber; + if (serialNumber != null) + { + code ^= serialNumber.GetHashCode(); + } + + X509Name issuer = this.Issuer; + if (issuer != null) + { + code ^= issuer.GetHashCode(); + } + + return code; + } + + public override bool Equals( + object obj) + { + if (obj == this) + return false; + + OriginatorID id = obj as OriginatorID; + + if (id == null) + return false; + + return Arrays.AreEqual(SubjectKeyIdentifier, id.SubjectKeyIdentifier) + && Platform.Equals(SerialNumber, id.SerialNumber) + && IssuersMatch(Issuer, id.Issuer); + } + } +} diff --git a/bc-sharp-crypto/src/cms/OriginatorInfoGenerator.cs b/bc-sharp-crypto/src/cms/OriginatorInfoGenerator.cs new file mode 100644 index 0000000..6bf1087 --- /dev/null +++ b/bc-sharp-crypto/src/cms/OriginatorInfoGenerator.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Cms; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.X509; +using Org.BouncyCastle.X509.Store; + +namespace Org.BouncyCastle.Cms +{ + public class OriginatorInfoGenerator + { + private readonly IList origCerts; + private readonly IList origCrls; + + public OriginatorInfoGenerator(X509Certificate origCert) + { + this.origCerts = Platform.CreateArrayList(1); + this.origCrls = null; + origCerts.Add(origCert.CertificateStructure); + } + + public OriginatorInfoGenerator(IX509Store origCerts) + : this(origCerts, null) + { + } + + public OriginatorInfoGenerator(IX509Store origCerts, IX509Store origCrls) + { + this.origCerts = CmsUtilities.GetCertificatesFromStore(origCerts); + this.origCrls = origCrls == null ? null : CmsUtilities.GetCrlsFromStore(origCrls); + } + + public virtual OriginatorInfo Generate() + { + Asn1Set certSet = CmsUtilities.CreateDerSetFromList(origCerts); + Asn1Set crlSet = origCrls == null ? null : CmsUtilities.CreateDerSetFromList(origCrls); + return new OriginatorInfo(certSet, crlSet); + } + } +} diff --git a/bc-sharp-crypto/src/cms/OriginatorInformation.cs b/bc-sharp-crypto/src/cms/OriginatorInformation.cs new file mode 100644 index 0000000..618add6 --- /dev/null +++ b/bc-sharp-crypto/src/cms/OriginatorInformation.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Cms; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.X509; +using Org.BouncyCastle.X509.Store; + +namespace Org.BouncyCastle.Cms +{ + public class OriginatorInformation + { + private readonly OriginatorInfo originatorInfo; + + internal OriginatorInformation(OriginatorInfo originatorInfo) + { + this.originatorInfo = originatorInfo; + } + + /** + * Return the certificates stored in the underlying OriginatorInfo object. + * + * @return a Store of X509CertificateHolder objects. + */ + public virtual IX509Store GetCertificates() + { + Asn1Set certSet = originatorInfo.Certificates; + + if (certSet != null) + { + IList certList = Platform.CreateArrayList(certSet.Count); + + foreach (Asn1Encodable enc in certSet) + { + Asn1Object obj = enc.ToAsn1Object(); + if (obj is Asn1Sequence) + { + certList.Add(new X509Certificate(X509CertificateStructure.GetInstance(obj))); + } + } + + return X509StoreFactory.Create( + "Certificate/Collection", + new X509CollectionStoreParameters(certList)); + } + + return X509StoreFactory.Create( + "Certificate/Collection", + new X509CollectionStoreParameters(Platform.CreateArrayList())); + } + + /** + * Return the CRLs stored in the underlying OriginatorInfo object. + * + * @return a Store of X509CRLHolder objects. + */ + public virtual IX509Store GetCrls() + { + Asn1Set crlSet = originatorInfo.Certificates; + + if (crlSet != null) + { + IList crlList = Platform.CreateArrayList(crlSet.Count); + + foreach (Asn1Encodable enc in crlSet) + { + Asn1Object obj = enc.ToAsn1Object(); + if (obj is Asn1Sequence) + { + crlList.Add(new X509Crl(CertificateList.GetInstance(obj))); + } + } + + return X509StoreFactory.Create( + "CRL/Collection", + new X509CollectionStoreParameters(crlList)); + } + + return X509StoreFactory.Create( + "CRL/Collection", + new X509CollectionStoreParameters(Platform.CreateArrayList())); + } + + /** + * Return the underlying ASN.1 object defining this SignerInformation object. + * + * @return a OriginatorInfo. + */ + public virtual OriginatorInfo ToAsn1Structure() + { + return originatorInfo; + } + } +} diff --git a/bc-sharp-crypto/src/cms/PKCS5Scheme2PBEKey.cs b/bc-sharp-crypto/src/cms/PKCS5Scheme2PBEKey.cs new file mode 100644 index 0000000..08b8518 --- /dev/null +++ b/bc-sharp-crypto/src/cms/PKCS5Scheme2PBEKey.cs @@ -0,0 +1,64 @@ +using System; + +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Parameters; + +namespace Org.BouncyCastle.Cms +{ + /// + /// PKCS5 scheme-2 - password converted to bytes assuming ASCII. + /// + public class Pkcs5Scheme2PbeKey + : CmsPbeKey + { + [Obsolete("Use version taking 'char[]' instead")] + public Pkcs5Scheme2PbeKey( + string password, + byte[] salt, + int iterationCount) + : this(password.ToCharArray(), salt, iterationCount) + { + } + + [Obsolete("Use version taking 'char[]' instead")] + public Pkcs5Scheme2PbeKey( + string password, + AlgorithmIdentifier keyDerivationAlgorithm) + : this(password.ToCharArray(), keyDerivationAlgorithm) + { + } + + public Pkcs5Scheme2PbeKey( + char[] password, + byte[] salt, + int iterationCount) + : base(password, salt, iterationCount) + { + } + + public Pkcs5Scheme2PbeKey( + char[] password, + AlgorithmIdentifier keyDerivationAlgorithm) + : base(password, keyDerivationAlgorithm) + { + } + + internal override KeyParameter GetEncoded( + string algorithmOid) + { + Pkcs5S2ParametersGenerator gen = new Pkcs5S2ParametersGenerator(); + + gen.Init( + PbeParametersGenerator.Pkcs5PasswordToBytes(password), + salt, + iterationCount); + + return (KeyParameter) gen.GenerateDerivedParameters( + algorithmOid, + CmsEnvelopedHelper.Instance.GetKeySize(algorithmOid)); + } + } +} diff --git a/bc-sharp-crypto/src/cms/PKCS5Scheme2UTF8PBEKey.cs b/bc-sharp-crypto/src/cms/PKCS5Scheme2UTF8PBEKey.cs new file mode 100644 index 0000000..7aecc29 --- /dev/null +++ b/bc-sharp-crypto/src/cms/PKCS5Scheme2UTF8PBEKey.cs @@ -0,0 +1,64 @@ +using System; + +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Parameters; + +namespace Org.BouncyCastle.Cms +{ + /** + * PKCS5 scheme-2 - password converted to bytes using UTF-8. + */ + public class Pkcs5Scheme2Utf8PbeKey + : CmsPbeKey + { + [Obsolete("Use version taking 'char[]' instead")] + public Pkcs5Scheme2Utf8PbeKey( + string password, + byte[] salt, + int iterationCount) + : this(password.ToCharArray(), salt, iterationCount) + { + } + + [Obsolete("Use version taking 'char[]' instead")] + public Pkcs5Scheme2Utf8PbeKey( + string password, + AlgorithmIdentifier keyDerivationAlgorithm) + : this(password.ToCharArray(), keyDerivationAlgorithm) + { + } + + public Pkcs5Scheme2Utf8PbeKey( + char[] password, + byte[] salt, + int iterationCount) + : base(password, salt, iterationCount) + { + } + + public Pkcs5Scheme2Utf8PbeKey( + char[] password, + AlgorithmIdentifier keyDerivationAlgorithm) + : base(password, keyDerivationAlgorithm) + { + } + + internal override KeyParameter GetEncoded( + string algorithmOid) + { + Pkcs5S2ParametersGenerator gen = new Pkcs5S2ParametersGenerator(); + + gen.Init( + PbeParametersGenerator.Pkcs5PasswordToUtf8Bytes(password), + salt, + iterationCount); + + return (KeyParameter) gen.GenerateDerivedParameters( + algorithmOid, + CmsEnvelopedHelper.Instance.GetKeySize(algorithmOid)); + } + } +} diff --git a/bc-sharp-crypto/src/cms/PasswordRecipientInfoGenerator.cs b/bc-sharp-crypto/src/cms/PasswordRecipientInfoGenerator.cs new file mode 100644 index 0000000..9916edf --- /dev/null +++ b/bc-sharp-crypto/src/cms/PasswordRecipientInfoGenerator.cs @@ -0,0 +1,70 @@ +using System; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Cms; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Cms +{ + internal class PasswordRecipientInfoGenerator : RecipientInfoGenerator + { + private static readonly CmsEnvelopedHelper Helper = CmsEnvelopedHelper.Instance; + + private AlgorithmIdentifier keyDerivationAlgorithm; + private KeyParameter keyEncryptionKey; + // TODO Can get this from keyEncryptionKey? + private string keyEncryptionKeyOID; + + internal PasswordRecipientInfoGenerator() + { + } + + internal AlgorithmIdentifier KeyDerivationAlgorithm + { + set { this.keyDerivationAlgorithm = value; } + } + + internal KeyParameter KeyEncryptionKey + { + set { this.keyEncryptionKey = value; } + } + + internal string KeyEncryptionKeyOID + { + set { this.keyEncryptionKeyOID = value; } + } + + public RecipientInfo Generate(KeyParameter contentEncryptionKey, SecureRandom random) + { + byte[] keyBytes = contentEncryptionKey.GetKey(); + + string rfc3211WrapperName = Helper.GetRfc3211WrapperName(keyEncryptionKeyOID); + IWrapper keyWrapper = Helper.CreateWrapper(rfc3211WrapperName); + + // Note: In Java build, the IV is automatically generated in JCE layer + int ivLength = Platform.StartsWith(rfc3211WrapperName, "DESEDE") ? 8 : 16; + byte[] iv = new byte[ivLength]; + random.NextBytes(iv); + + ICipherParameters parameters = new ParametersWithIV(keyEncryptionKey, iv); + keyWrapper.Init(true, new ParametersWithRandom(parameters, random)); + Asn1OctetString encryptedKey = new DerOctetString( + keyWrapper.Wrap(keyBytes, 0, keyBytes.Length)); + + DerSequence seq = new DerSequence( + new DerObjectIdentifier(keyEncryptionKeyOID), + new DerOctetString(iv)); + + AlgorithmIdentifier keyEncryptionAlgorithm = new AlgorithmIdentifier( + PkcsObjectIdentifiers.IdAlgPwriKek, seq); + + return new RecipientInfo(new PasswordRecipientInfo( + keyDerivationAlgorithm, keyEncryptionAlgorithm, encryptedKey)); + } + } +} diff --git a/bc-sharp-crypto/src/cms/PasswordRecipientInformation.cs b/bc-sharp-crypto/src/cms/PasswordRecipientInformation.cs new file mode 100644 index 0000000..f629cab --- /dev/null +++ b/bc-sharp-crypto/src/cms/PasswordRecipientInformation.cs @@ -0,0 +1,79 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Cms; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Cms +{ + /** + * the RecipientInfo class for a recipient who has been sent a message + * encrypted using a password. + */ + public class PasswordRecipientInformation + : RecipientInformation + { + private readonly PasswordRecipientInfo info; + + internal PasswordRecipientInformation( + PasswordRecipientInfo info, + CmsSecureReadable secureReadable) + : base(info.KeyEncryptionAlgorithm, secureReadable) + { + this.info = info; + this.rid = new RecipientID(); + } + + /** + * return the object identifier for the key derivation algorithm, or null + * if there is none present. + * + * @return OID for key derivation algorithm, if present. + */ + public virtual AlgorithmIdentifier KeyDerivationAlgorithm + { + get { return info.KeyDerivationAlgorithm; } + } + + /** + * decrypt the content and return an input stream. + */ + public override CmsTypedStream GetContentStream( + ICipherParameters key) + { + try + { + AlgorithmIdentifier kekAlg = AlgorithmIdentifier.GetInstance(info.KeyEncryptionAlgorithm); + Asn1Sequence kekAlgParams = (Asn1Sequence)kekAlg.Parameters; + byte[] encryptedKey = info.EncryptedKey.GetOctets(); + string kekAlgName = DerObjectIdentifier.GetInstance(kekAlgParams[0]).Id; + string cName = CmsEnvelopedHelper.Instance.GetRfc3211WrapperName(kekAlgName); + IWrapper keyWrapper = WrapperUtilities.GetWrapper(cName); + + byte[] iv = Asn1OctetString.GetInstance(kekAlgParams[1]).GetOctets(); + + ICipherParameters parameters = ((CmsPbeKey)key).GetEncoded(kekAlgName); + parameters = new ParametersWithIV(parameters, iv); + + keyWrapper.Init(false, parameters); + + KeyParameter sKey = ParameterUtilities.CreateKeyParameter( + GetContentAlgorithmName(), keyWrapper.Unwrap(encryptedKey, 0, encryptedKey.Length)); + + return GetContentFromSessionKey(sKey); + } + catch (SecurityUtilityException e) + { + throw new CmsException("couldn't create cipher.", e); + } + catch (InvalidKeyException e) + { + throw new CmsException("key invalid in message.", e); + } + } + } +} diff --git a/bc-sharp-crypto/src/cms/RecipientId.cs b/bc-sharp-crypto/src/cms/RecipientId.cs new file mode 100644 index 0000000..9b6eb09 --- /dev/null +++ b/bc-sharp-crypto/src/cms/RecipientId.cs @@ -0,0 +1,58 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.X509.Store; + +namespace Org.BouncyCastle.Cms +{ + public class RecipientID + : X509CertStoreSelector + { + private byte[] keyIdentifier; + + public byte[] KeyIdentifier + { + get { return Arrays.Clone(keyIdentifier); } + set { keyIdentifier = Arrays.Clone(value); } + } + + public override int GetHashCode() + { + int code = Arrays.GetHashCode(keyIdentifier) + ^ Arrays.GetHashCode(this.SubjectKeyIdentifier); + + BigInteger serialNumber = this.SerialNumber; + if (serialNumber != null) + { + code ^= serialNumber.GetHashCode(); + } + + X509Name issuer = this.Issuer; + if (issuer != null) + { + code ^= issuer.GetHashCode(); + } + + return code; + } + + public override bool Equals( + object obj) + { + if (obj == this) + return true; + + RecipientID id = obj as RecipientID; + + if (id == null) + return false; + + return Arrays.AreEqual(keyIdentifier, id.keyIdentifier) + && Arrays.AreEqual(SubjectKeyIdentifier, id.SubjectKeyIdentifier) + && Platform.Equals(SerialNumber, id.SerialNumber) + && IssuersMatch(Issuer, id.Issuer); + } + } +} diff --git a/bc-sharp-crypto/src/cms/RecipientInfoGenerator.cs b/bc-sharp-crypto/src/cms/RecipientInfoGenerator.cs new file mode 100644 index 0000000..c41db61 --- /dev/null +++ b/bc-sharp-crypto/src/cms/RecipientInfoGenerator.cs @@ -0,0 +1,26 @@ +using System; + +using Org.BouncyCastle.Asn1.Cms; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Cms +{ + interface RecipientInfoGenerator + { + /// + /// Generate a RecipientInfo object for the given key. + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + /// + RecipientInfo Generate(KeyParameter contentEncryptionKey, SecureRandom random); + } +} diff --git a/bc-sharp-crypto/src/cms/RecipientInformation.cs b/bc-sharp-crypto/src/cms/RecipientInformation.cs new file mode 100644 index 0000000..272b841 --- /dev/null +++ b/bc-sharp-crypto/src/cms/RecipientInformation.cs @@ -0,0 +1,126 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Nist; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.IO; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; + +namespace Org.BouncyCastle.Cms +{ + public abstract class RecipientInformation + { + internal RecipientID rid = new RecipientID(); + internal AlgorithmIdentifier keyEncAlg; + internal CmsSecureReadable secureReadable; + + private byte[] resultMac; + + internal RecipientInformation( + AlgorithmIdentifier keyEncAlg, + CmsSecureReadable secureReadable) + { + this.keyEncAlg = keyEncAlg; + this.secureReadable = secureReadable; + } + + internal string GetContentAlgorithmName() + { + AlgorithmIdentifier algorithm = secureReadable.Algorithm; +// return CmsEnvelopedHelper.Instance.GetSymmetricCipherName(algorithm.Algorithm.Id); + return algorithm.Algorithm.Id; + } + + public RecipientID RecipientID + { + get { return rid; } + } + + public AlgorithmIdentifier KeyEncryptionAlgorithmID + { + get { return keyEncAlg; } + } + + /** + * return the object identifier for the key encryption algorithm. + * + * @return OID for key encryption algorithm. + */ + public string KeyEncryptionAlgOid + { + get { return keyEncAlg.Algorithm.Id; } + } + + /** + * return the ASN.1 encoded key encryption algorithm parameters, or null if + * there aren't any. + * + * @return ASN.1 encoding of key encryption algorithm parameters. + */ + public Asn1Object KeyEncryptionAlgParams + { + get + { + Asn1Encodable ae = keyEncAlg.Parameters; + + return ae == null ? null : ae.ToAsn1Object(); + } + } + + internal CmsTypedStream GetContentFromSessionKey( + KeyParameter sKey) + { + CmsReadable readable = secureReadable.GetReadable(sKey); + + try + { + return new CmsTypedStream(readable.GetInputStream()); + } + catch (IOException e) + { + throw new CmsException("error getting .", e); + } + } + + public byte[] GetContent( + ICipherParameters key) + { + try + { + return CmsUtilities.StreamToByteArray(GetContentStream(key).ContentStream); + } + catch (IOException e) + { + throw new Exception("unable to parse internal stream: " + e); + } + } + + /** + * Return the MAC calculated for the content stream. Note: this call is only meaningful once all + * the content has been read. + * + * @return byte array containing the mac. + */ + public byte[] GetMac() + { + if (resultMac == null) + { + object cryptoObject = secureReadable.CryptoObject; + if (cryptoObject is IMac) + { + resultMac = MacUtilities.DoFinal((IMac)cryptoObject); + } + } + + return Arrays.Clone(resultMac); + } + + public abstract CmsTypedStream GetContentStream(ICipherParameters key); + } +} diff --git a/bc-sharp-crypto/src/cms/RecipientInformationStore.cs b/bc-sharp-crypto/src/cms/RecipientInformationStore.cs new file mode 100644 index 0000000..33b472f --- /dev/null +++ b/bc-sharp-crypto/src/cms/RecipientInformationStore.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Cms +{ + public class RecipientInformationStore + { + private readonly IList all; //ArrayList[RecipientInformation] + private readonly IDictionary table = Platform.CreateHashtable(); // Hashtable[RecipientID, ArrayList[RecipientInformation]] + + public RecipientInformationStore( + ICollection recipientInfos) + { + foreach (RecipientInformation recipientInformation in recipientInfos) + { + RecipientID rid = recipientInformation.RecipientID; + IList list = (IList)table[rid]; + + if (list == null) + { + table[rid] = list = Platform.CreateArrayList(1); + } + + list.Add(recipientInformation); + } + + this.all = Platform.CreateArrayList(recipientInfos); + } + + public RecipientInformation this[RecipientID selector] + { + get { return GetFirstRecipient(selector); } + } + + /** + * Return the first RecipientInformation object that matches the + * passed in selector. Null if there are no matches. + * + * @param selector to identify a recipient + * @return a single RecipientInformation object. Null if none matches. + */ + public RecipientInformation GetFirstRecipient( + RecipientID selector) + { + IList list = (IList) table[selector]; + + return list == null ? null : (RecipientInformation) list[0]; + } + + /** + * Return the number of recipients in the collection. + * + * @return number of recipients identified. + */ + public int Count + { + get { return all.Count; } + } + + /** + * Return all recipients in the collection + * + * @return a collection of recipients. + */ + public ICollection GetRecipients() + { + return Platform.CreateArrayList(all); + } + + /** + * Return possible empty collection with recipients matching the passed in RecipientID + * + * @param selector a recipient id to select against. + * @return a collection of RecipientInformation objects. + */ + public ICollection GetRecipients( + RecipientID selector) + { + IList list = (IList)table[selector]; + + return list == null ? Platform.CreateArrayList() : Platform.CreateArrayList(list); + } + } +} diff --git a/bc-sharp-crypto/src/cms/SigOutputStream.cs b/bc-sharp-crypto/src/cms/SigOutputStream.cs new file mode 100644 index 0000000..a807fa7 --- /dev/null +++ b/bc-sharp-crypto/src/cms/SigOutputStream.cs @@ -0,0 +1,43 @@ +using System; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Utilities.IO; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Cms +{ + internal class SigOutputStream + : BaseOutputStream + { + private readonly ISigner sig; + + internal SigOutputStream(ISigner sig) + { + this.sig = sig; + } + + public override void WriteByte(byte b) + { + try + { + sig.Update(b); + } + catch (SignatureException e) + { + throw new CmsStreamException("signature problem: " + e); + } + } + + public override void Write(byte[] b, int off, int len) + { + try + { + sig.BlockUpdate(b, off, len); + } + catch (SignatureException e) + { + throw new CmsStreamException("signature problem: " + e); + } + } + } +} \ No newline at end of file diff --git a/bc-sharp-crypto/src/cms/SignerId.cs b/bc-sharp-crypto/src/cms/SignerId.cs new file mode 100644 index 0000000..baac936 --- /dev/null +++ b/bc-sharp-crypto/src/cms/SignerId.cs @@ -0,0 +1,51 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.X509.Store; + +namespace Org.BouncyCastle.Cms +{ + /** + * a basic index for a signer. + */ + public class SignerID + : X509CertStoreSelector + { + public override int GetHashCode() + { + int code = Arrays.GetHashCode(this.SubjectKeyIdentifier); + + BigInteger serialNumber = this.SerialNumber; + if (serialNumber != null) + { + code ^= serialNumber.GetHashCode(); + } + + X509Name issuer = this.Issuer; + if (issuer != null) + { + code ^= issuer.GetHashCode(); + } + + return code; + } + + public override bool Equals( + object obj) + { + if (obj == this) + return false; + + SignerID id = obj as SignerID; + + if (id == null) + return false; + + return Arrays.AreEqual(SubjectKeyIdentifier, id.SubjectKeyIdentifier) + && Platform.Equals(SerialNumber, id.SerialNumber) + && IssuersMatch(Issuer, id.Issuer); + } + } +} diff --git a/bc-sharp-crypto/src/cms/SignerInfoGenerator.cs b/bc-sharp-crypto/src/cms/SignerInfoGenerator.cs new file mode 100644 index 0000000..7b9318c --- /dev/null +++ b/bc-sharp-crypto/src/cms/SignerInfoGenerator.cs @@ -0,0 +1,166 @@ +using System; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Cms; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.X509; + +namespace Org.BouncyCastle.Cms +{ + internal interface ISignerInfoGenerator + { + SignerInfo Generate(DerObjectIdentifier contentType, AlgorithmIdentifier digestAlgorithm, + byte[] calculatedDigest); + } + + public class SignerInfoGenerator + { + internal X509Certificate certificate; + internal ISignatureFactory contentSigner; + internal SignerIdentifier sigId; + internal CmsAttributeTableGenerator signedGen; + internal CmsAttributeTableGenerator unsignedGen; + private bool isDirectSignature; + + internal SignerInfoGenerator(SignerIdentifier sigId, ISignatureFactory signerFactory): this(sigId, signerFactory, false) + { + + } + + internal SignerInfoGenerator(SignerIdentifier sigId, ISignatureFactory signerFactory, bool isDirectSignature) + { + this.sigId = sigId; + this.contentSigner = signerFactory; + this.isDirectSignature = isDirectSignature; + if (this.isDirectSignature) + { + this.signedGen = null; + this.unsignedGen = null; + } + else + { + this.signedGen = new DefaultSignedAttributeTableGenerator(); + this.unsignedGen = null; + } + } + + internal SignerInfoGenerator(SignerIdentifier sigId, ISignatureFactory contentSigner, CmsAttributeTableGenerator signedGen, CmsAttributeTableGenerator unsignedGen) + { + this.sigId = sigId; + this.contentSigner = contentSigner; + this.signedGen = signedGen; + this.unsignedGen = unsignedGen; + this.isDirectSignature = false; + } + + internal void setAssociatedCertificate(X509Certificate certificate) + { + this.certificate = certificate; + } + } + + public class SignerInfoGeneratorBuilder + { + private bool directSignature; + private CmsAttributeTableGenerator signedGen; + private CmsAttributeTableGenerator unsignedGen; + + public SignerInfoGeneratorBuilder() + { + } + + /** + * If the passed in flag is true, the signer signature will be based on the data, not + * a collection of signed attributes, and no signed attributes will be included. + * + * @return the builder object + */ + public SignerInfoGeneratorBuilder SetDirectSignature(bool hasNoSignedAttributes) + { + this.directSignature = hasNoSignedAttributes; + + return this; + } + + /** + * Provide a custom signed attribute generator. + * + * @param signedGen a generator of signed attributes. + * @return the builder object + */ + public SignerInfoGeneratorBuilder WithSignedAttributeGenerator(CmsAttributeTableGenerator signedGen) + { + this.signedGen = signedGen; + + return this; + } + + /** + * Provide a generator of unsigned attributes. + * + * @param unsignedGen a generator for signed attributes. + * @return the builder object + */ + public SignerInfoGeneratorBuilder WithUnsignedAttributeGenerator(CmsAttributeTableGenerator unsignedGen) + { + this.unsignedGen = unsignedGen; + + return this; + } + + /** + * Build a generator with the passed in certHolder issuer and serial number as the signerIdentifier. + * + * @param contentSigner operator for generating the final signature in the SignerInfo with. + * @param certHolder carrier for the X.509 certificate related to the contentSigner. + * @return a SignerInfoGenerator + * @throws OperatorCreationException if the generator cannot be built. + */ + public SignerInfoGenerator Build(ISignatureFactory contentSigner, X509Certificate certificate) + { + SignerIdentifier sigId = new SignerIdentifier(new IssuerAndSerialNumber(certificate.IssuerDN, new DerInteger(certificate.SerialNumber))); + + SignerInfoGenerator sigInfoGen = CreateGenerator(contentSigner, sigId); + + sigInfoGen.setAssociatedCertificate(certificate); + + return sigInfoGen; + } + + /** + * Build a generator with the passed in subjectKeyIdentifier as the signerIdentifier. If used you should + * try to follow the calculation described in RFC 5280 section 4.2.1.2. + * + * @param signerFactory operator factory for generating the final signature in the SignerInfo with. + * @param subjectKeyIdentifier key identifier to identify the public key for verifying the signature. + * @return a SignerInfoGenerator + */ + public SignerInfoGenerator Build(ISignatureFactory signerFactory, byte[] subjectKeyIdentifier) + { + SignerIdentifier sigId = new SignerIdentifier(new DerOctetString(subjectKeyIdentifier)); + + return CreateGenerator(signerFactory, sigId); + } + + private SignerInfoGenerator CreateGenerator(ISignatureFactory contentSigner, SignerIdentifier sigId) + { + if (directSignature) + { + return new SignerInfoGenerator(sigId, contentSigner, true); + } + + if (signedGen != null || unsignedGen != null) + { + if (signedGen == null) + { + signedGen = new DefaultSignedAttributeTableGenerator(); + } + + return new SignerInfoGenerator(sigId, contentSigner, signedGen, unsignedGen); + } + + return new SignerInfoGenerator(sigId, contentSigner); + } + } +} diff --git a/bc-sharp-crypto/src/cms/SignerInformation.cs b/bc-sharp-crypto/src/cms/SignerInformation.cs new file mode 100644 index 0000000..dad1282 --- /dev/null +++ b/bc-sharp-crypto/src/cms/SignerInformation.cs @@ -0,0 +1,761 @@ +using System; +using System.Collections; +using System.Diagnostics; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Cms; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Signers; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.X509; + +namespace Org.BouncyCastle.Cms +{ + /** + * an expanded SignerInfo block from a CMS Signed message + */ + public class SignerInformation + { + private static readonly CmsSignedHelper Helper = CmsSignedHelper.Instance; + + private SignerID sid; + private SignerInfo info; + private AlgorithmIdentifier digestAlgorithm; + private AlgorithmIdentifier encryptionAlgorithm; + private readonly Asn1Set signedAttributeSet; + private readonly Asn1Set unsignedAttributeSet; + private CmsProcessable content; + private byte[] signature; + private DerObjectIdentifier contentType; + private IDigestCalculator digestCalculator; + private byte[] resultDigest; + + // Derived + private Asn1.Cms.AttributeTable signedAttributeTable; + private Asn1.Cms.AttributeTable unsignedAttributeTable; + private readonly bool isCounterSignature; + + internal SignerInformation( + SignerInfo info, + DerObjectIdentifier contentType, + CmsProcessable content, + IDigestCalculator digestCalculator) + { + this.info = info; + this.sid = new SignerID(); + this.contentType = contentType; + this.isCounterSignature = contentType == null; + + try + { + SignerIdentifier s = info.SignerID; + + if (s.IsTagged) + { + Asn1OctetString octs = Asn1OctetString.GetInstance(s.ID); + + sid.SubjectKeyIdentifier = octs.GetEncoded(); + } + else + { + Asn1.Cms.IssuerAndSerialNumber iAnds = + Asn1.Cms.IssuerAndSerialNumber.GetInstance(s.ID); + + sid.Issuer = iAnds.Name; + sid.SerialNumber = iAnds.SerialNumber.Value; + } + } + catch (IOException) + { + throw new ArgumentException("invalid sid in SignerInfo"); + } + + this.digestAlgorithm = info.DigestAlgorithm; + this.signedAttributeSet = info.AuthenticatedAttributes; + this.unsignedAttributeSet = info.UnauthenticatedAttributes; + this.encryptionAlgorithm = info.DigestEncryptionAlgorithm; + this.signature = info.EncryptedDigest.GetOctets(); + + this.content = content; + this.digestCalculator = digestCalculator; + } + + public bool IsCounterSignature + { + get { return isCounterSignature; } + } + + public DerObjectIdentifier ContentType + { + get { return contentType; } + } + + public SignerID SignerID + { + get { return sid; } + } + + /** + * return the version number for this objects underlying SignerInfo structure. + */ + public int Version + { + get { return info.Version.Value.IntValue; } + } + + public AlgorithmIdentifier DigestAlgorithmID + { + get { return digestAlgorithm; } + } + + /** + * return the object identifier for the signature. + */ + public string DigestAlgOid + { + get { return digestAlgorithm.Algorithm.Id; } + } + + /** + * return the signature parameters, or null if there aren't any. + */ + public Asn1Object DigestAlgParams + { + get + { + Asn1Encodable ae = digestAlgorithm.Parameters; + + return ae == null ? null : ae.ToAsn1Object(); + } + } + + /** + * return the content digest that was calculated during verification. + */ + public byte[] GetContentDigest() + { + if (resultDigest == null) + { + throw new InvalidOperationException("method can only be called after verify."); + } + + return (byte[])resultDigest.Clone(); + } + + public AlgorithmIdentifier EncryptionAlgorithmID + { + get { return encryptionAlgorithm; } + } + + /** + * return the object identifier for the signature. + */ + public string EncryptionAlgOid + { + get { return encryptionAlgorithm.Algorithm.Id; } + } + + /** + * return the signature/encryption algorithm parameters, or null if + * there aren't any. + */ + public Asn1Object EncryptionAlgParams + { + get + { + Asn1Encodable ae = encryptionAlgorithm.Parameters; + + return ae == null ? null : ae.ToAsn1Object(); + } + } + + /** + * return a table of the signed attributes - indexed by + * the OID of the attribute. + */ + public Asn1.Cms.AttributeTable SignedAttributes + { + get + { + if (signedAttributeSet != null && signedAttributeTable == null) + { + signedAttributeTable = new Asn1.Cms.AttributeTable(signedAttributeSet); + } + return signedAttributeTable; + } + } + + /** + * return a table of the unsigned attributes indexed by + * the OID of the attribute. + */ + public Asn1.Cms.AttributeTable UnsignedAttributes + { + get + { + if (unsignedAttributeSet != null && unsignedAttributeTable == null) + { + unsignedAttributeTable = new Asn1.Cms.AttributeTable(unsignedAttributeSet); + } + return unsignedAttributeTable; + } + } + + /** + * return the encoded signature + */ + public byte[] GetSignature() + { + return (byte[]) signature.Clone(); + } + + /** + * Return a SignerInformationStore containing the counter signatures attached to this + * signer. If no counter signatures are present an empty store is returned. + */ + public SignerInformationStore GetCounterSignatures() + { + // TODO There are several checks implied by the RFC3852 comments that are missing + + /* + The countersignature attribute MUST be an unsigned attribute; it MUST + NOT be a signed attribute, an authenticated attribute, an + unauthenticated attribute, or an unprotected attribute. + */ + Asn1.Cms.AttributeTable unsignedAttributeTable = UnsignedAttributes; + if (unsignedAttributeTable == null) + { + return new SignerInformationStore(Platform.CreateArrayList(0)); + } + + IList counterSignatures = Platform.CreateArrayList(); + + /* + The UnsignedAttributes syntax is defined as a SET OF Attributes. The + UnsignedAttributes in a signerInfo may include multiple instances of + the countersignature attribute. + */ + Asn1EncodableVector allCSAttrs = unsignedAttributeTable.GetAll(CmsAttributes.CounterSignature); + + foreach (Asn1.Cms.Attribute counterSignatureAttribute in allCSAttrs) + { + /* + A countersignature attribute can have multiple attribute values. The + syntax is defined as a SET OF AttributeValue, and there MUST be one + or more instances of AttributeValue present. + */ + Asn1Set values = counterSignatureAttribute.AttrValues; + if (values.Count < 1) + { + // TODO Throw an appropriate exception? + } + + foreach (Asn1Encodable asn1Obj in values) + { + /* + Countersignature values have the same meaning as SignerInfo values + for ordinary signatures, except that: + + 1. The signedAttributes field MUST NOT contain a content-type + attribute; there is no content type for countersignatures. + + 2. The signedAttributes field MUST contain a message-digest + attribute if it contains any other attributes. + + 3. The input to the message-digesting process is the contents + octets of the DER encoding of the signatureValue field of the + SignerInfo value with which the attribute is associated. + */ + SignerInfo si = SignerInfo.GetInstance(asn1Obj.ToAsn1Object()); + + string digestName = CmsSignedHelper.Instance.GetDigestAlgName(si.DigestAlgorithm.Algorithm.Id); + + counterSignatures.Add(new SignerInformation(si, null, null, new CounterSignatureDigestCalculator(digestName, GetSignature()))); + } + } + + return new SignerInformationStore(counterSignatures); + } + + /** + * return the DER encoding of the signed attributes. + * @throws IOException if an encoding error occurs. + */ + public byte[] GetEncodedSignedAttributes() + { + return signedAttributeSet == null + ? null + : signedAttributeSet.GetEncoded(Asn1Encodable.Der); + } + + private bool DoVerify( + AsymmetricKeyParameter key) + { + string digestName = Helper.GetDigestAlgName(this.DigestAlgOid); + IDigest digest = Helper.GetDigestInstance(digestName); + + DerObjectIdentifier sigAlgOid = this.encryptionAlgorithm.Algorithm; + Asn1Encodable sigParams = this.encryptionAlgorithm.Parameters; + ISigner sig; + + if (sigAlgOid.Equals(Asn1.Pkcs.PkcsObjectIdentifiers.IdRsassaPss)) + { + // RFC 4056 2.2 + // When the id-RSASSA-PSS algorithm identifier is used for a signature, + // the AlgorithmIdentifier parameters field MUST contain RSASSA-PSS-params. + if (sigParams == null) + throw new CmsException("RSASSA-PSS signature must specify algorithm parameters"); + + try + { + // TODO Provide abstract configuration mechanism + // (via alternate SignerUtilities.GetSigner method taking ASN.1 params) + + Asn1.Pkcs.RsassaPssParameters pss = Asn1.Pkcs.RsassaPssParameters.GetInstance( + sigParams.ToAsn1Object()); + + if (!pss.HashAlgorithm.Algorithm.Equals(this.digestAlgorithm.Algorithm)) + throw new CmsException("RSASSA-PSS signature parameters specified incorrect hash algorithm"); + if (!pss.MaskGenAlgorithm.Algorithm.Equals(Asn1.Pkcs.PkcsObjectIdentifiers.IdMgf1)) + throw new CmsException("RSASSA-PSS signature parameters specified unknown MGF"); + + IDigest pssDigest = DigestUtilities.GetDigest(pss.HashAlgorithm.Algorithm); + int saltLength = pss.SaltLength.Value.IntValue; + byte trailerField = (byte) pss.TrailerField.Value.IntValue; + + // RFC 4055 3.1 + // The value MUST be 1, which represents the trailer field with hexadecimal value 0xBC + if (trailerField != 1) + throw new CmsException("RSASSA-PSS signature parameters must have trailerField of 1"); + + sig = new PssSigner(new RsaBlindedEngine(), pssDigest, saltLength); + } + catch (Exception e) + { + throw new CmsException("failed to set RSASSA-PSS signature parameters", e); + } + } + else + { + // TODO Probably too strong a check at the moment +// if (sigParams != null) +// throw new CmsException("unrecognised signature parameters provided"); + + string signatureName = digestName + "with" + Helper.GetEncryptionAlgName(this.EncryptionAlgOid); + + sig = Helper.GetSignatureInstance(signatureName); + + //sig = Helper.GetSignatureInstance(this.EncryptionAlgOid); + //sig = SignerUtilities.GetSigner(sigAlgOid); + } + + try + { + if (digestCalculator != null) + { + resultDigest = digestCalculator.GetDigest(); + } + else + { + if (content != null) + { + content.Write(new DigOutputStream(digest)); + } + else if (signedAttributeSet == null) + { + // TODO Get rid of this exception and just treat content==null as empty not missing? + throw new CmsException("data not encapsulated in signature - use detached constructor."); + } + + resultDigest = DigestUtilities.DoFinal(digest); + } + } + catch (IOException e) + { + throw new CmsException("can't process mime object to create signature.", e); + } + + // RFC 3852 11.1 Check the content-type attribute is correct + { + Asn1Object validContentType = GetSingleValuedSignedAttribute( + CmsAttributes.ContentType, "content-type"); + if (validContentType == null) + { + if (!isCounterSignature && signedAttributeSet != null) + throw new CmsException("The content-type attribute type MUST be present whenever signed attributes are present in signed-data"); + } + else + { + if (isCounterSignature) + throw new CmsException("[For counter signatures,] the signedAttributes field MUST NOT contain a content-type attribute"); + + if (!(validContentType is DerObjectIdentifier)) + throw new CmsException("content-type attribute value not of ASN.1 type 'OBJECT IDENTIFIER'"); + + DerObjectIdentifier signedContentType = (DerObjectIdentifier)validContentType; + + if (!signedContentType.Equals(contentType)) + throw new CmsException("content-type attribute value does not match eContentType"); + } + } + + // RFC 3852 11.2 Check the message-digest attribute is correct + { + Asn1Object validMessageDigest = GetSingleValuedSignedAttribute( + CmsAttributes.MessageDigest, "message-digest"); + if (validMessageDigest == null) + { + if (signedAttributeSet != null) + throw new CmsException("the message-digest signed attribute type MUST be present when there are any signed attributes present"); + } + else + { + if (!(validMessageDigest is Asn1OctetString)) + { + throw new CmsException("message-digest attribute value not of ASN.1 type 'OCTET STRING'"); + } + + Asn1OctetString signedMessageDigest = (Asn1OctetString)validMessageDigest; + + if (!Arrays.AreEqual(resultDigest, signedMessageDigest.GetOctets())) + throw new CmsException("message-digest attribute value does not match calculated value"); + } + } + + // RFC 3852 11.4 Validate countersignature attribute(s) + { + Asn1.Cms.AttributeTable signedAttrTable = this.SignedAttributes; + if (signedAttrTable != null + && signedAttrTable.GetAll(CmsAttributes.CounterSignature).Count > 0) + { + throw new CmsException("A countersignature attribute MUST NOT be a signed attribute"); + } + + Asn1.Cms.AttributeTable unsignedAttrTable = this.UnsignedAttributes; + if (unsignedAttrTable != null) + { + foreach (Asn1.Cms.Attribute csAttr in unsignedAttrTable.GetAll(CmsAttributes.CounterSignature)) + { + if (csAttr.AttrValues.Count < 1) + throw new CmsException("A countersignature attribute MUST contain at least one AttributeValue"); + + // Note: We don't recursively validate the countersignature value + } + } + } + + try + { + sig.Init(false, key); + + if (signedAttributeSet == null) + { + if (digestCalculator != null) + { + // need to decrypt signature and check message bytes + return VerifyDigest(resultDigest, key, this.GetSignature()); + } + else if (content != null) + { + // TODO Use raw signature of the hash value instead + content.Write(new SigOutputStream(sig)); + } + } + else + { + byte[] tmp = this.GetEncodedSignedAttributes(); + sig.BlockUpdate(tmp, 0, tmp.Length); + } + + return sig.VerifySignature(this.GetSignature()); + } + catch (InvalidKeyException e) + { + throw new CmsException("key not appropriate to signature in message.", e); + } + catch (IOException e) + { + throw new CmsException("can't process mime object to create signature.", e); + } + catch (SignatureException e) + { + throw new CmsException("invalid signature format in message: " + e.Message, e); + } + } + + private bool IsNull( + Asn1Encodable o) + { + return (o is Asn1Null) || (o == null); + } + + private DigestInfo DerDecode( + byte[] encoding) + { + if (encoding[0] != (int)(Asn1Tags.Constructed | Asn1Tags.Sequence)) + { + throw new IOException("not a digest info object"); + } + + DigestInfo digInfo = DigestInfo.GetInstance(Asn1Object.FromByteArray(encoding)); + + // length check to avoid Bleichenbacher vulnerability + + if (digInfo.GetEncoded().Length != encoding.Length) + { + throw new CmsException("malformed RSA signature"); + } + + return digInfo; + } + + private bool VerifyDigest( + byte[] digest, + AsymmetricKeyParameter key, + byte[] signature) + { + string algorithm = Helper.GetEncryptionAlgName(this.EncryptionAlgOid); + + try + { + if (algorithm.Equals("RSA")) + { + IBufferedCipher c = CmsEnvelopedHelper.Instance.CreateAsymmetricCipher("RSA/ECB/PKCS1Padding"); + + c.Init(false, key); + + byte[] decrypt = c.DoFinal(signature); + + DigestInfo digInfo = DerDecode(decrypt); + + if (!digInfo.AlgorithmID.Algorithm.Equals(digestAlgorithm.Algorithm)) + { + return false; + } + + if (!IsNull(digInfo.AlgorithmID.Parameters)) + { + return false; + } + + byte[] sigHash = digInfo.GetDigest(); + + return Arrays.ConstantTimeAreEqual(digest, sigHash); + } + else if (algorithm.Equals("DSA")) + { + ISigner sig = SignerUtilities.GetSigner("NONEwithDSA"); + + sig.Init(false, key); + + sig.BlockUpdate(digest, 0, digest.Length); + + return sig.VerifySignature(signature); + } + else + { + throw new CmsException("algorithm: " + algorithm + " not supported in base signatures."); + } + } + catch (SecurityUtilityException e) + { + throw e; + } + catch (GeneralSecurityException e) + { + throw new CmsException("Exception processing signature: " + e, e); + } + catch (IOException e) + { + throw new CmsException("Exception decoding signature: " + e, e); + } + } + + /** + * verify that the given public key successfully handles and confirms the + * signature associated with this signer. + */ + public bool Verify( + AsymmetricKeyParameter pubKey) + { + if (pubKey.IsPrivate) + throw new ArgumentException("Expected public key", "pubKey"); + + // Optional, but still need to validate if present + GetSigningTime(); + + return DoVerify(pubKey); + } + + /** + * verify that the given certificate successfully handles and confirms + * the signature associated with this signer and, if a signingTime + * attribute is available, that the certificate was valid at the time the + * signature was generated. + */ + public bool Verify( + X509Certificate cert) + { + Asn1.Cms.Time signingTime = GetSigningTime(); + if (signingTime != null) + { + cert.CheckValidity(signingTime.Date); + } + + return DoVerify(cert.GetPublicKey()); + } + + /** + * Return the base ASN.1 CMS structure that this object contains. + * + * @return an object containing a CMS SignerInfo structure. + */ + public SignerInfo ToSignerInfo() + { + return info; + } + + private Asn1Object GetSingleValuedSignedAttribute( + DerObjectIdentifier attrOID, string printableName) + { + + Asn1.Cms.AttributeTable unsignedAttrTable = this.UnsignedAttributes; + if (unsignedAttrTable != null + && unsignedAttrTable.GetAll(attrOID).Count > 0) + { + throw new CmsException("The " + printableName + + " attribute MUST NOT be an unsigned attribute"); + } + + Asn1.Cms.AttributeTable signedAttrTable = this.SignedAttributes; + if (signedAttrTable == null) + { + return null; + } + + Asn1EncodableVector v = signedAttrTable.GetAll(attrOID); + switch (v.Count) + { + case 0: + return null; + case 1: + Asn1.Cms.Attribute t = (Asn1.Cms.Attribute) v[0]; + Asn1Set attrValues = t.AttrValues; + + if (attrValues.Count != 1) + throw new CmsException("A " + printableName + + " attribute MUST have a single attribute value"); + + return attrValues[0].ToAsn1Object(); + default: + throw new CmsException("The SignedAttributes in a signerInfo MUST NOT include multiple instances of the " + + printableName + " attribute"); + } + } + + private Asn1.Cms.Time GetSigningTime() + { + Asn1Object validSigningTime = GetSingleValuedSignedAttribute( + CmsAttributes.SigningTime, "signing-time"); + + if (validSigningTime == null) + return null; + + try + { + return Asn1.Cms.Time.GetInstance(validSigningTime); + } + catch (ArgumentException) + { + throw new CmsException("signing-time attribute value not a valid 'Time' structure"); + } + } + + /** + * Return a signer information object with the passed in unsigned + * attributes replacing the ones that are current associated with + * the object passed in. + * + * @param signerInformation the signerInfo to be used as the basis. + * @param unsignedAttributes the unsigned attributes to add. + * @return a copy of the original SignerInformationObject with the changed attributes. + */ + public static SignerInformation ReplaceUnsignedAttributes( + SignerInformation signerInformation, + Asn1.Cms.AttributeTable unsignedAttributes) + { + SignerInfo sInfo = signerInformation.info; + Asn1Set unsignedAttr = null; + + if (unsignedAttributes != null) + { + unsignedAttr = new DerSet(unsignedAttributes.ToAsn1EncodableVector()); + } + + return new SignerInformation( + new SignerInfo( + sInfo.SignerID, + sInfo.DigestAlgorithm, + sInfo.AuthenticatedAttributes, + sInfo.DigestEncryptionAlgorithm, + sInfo.EncryptedDigest, + unsignedAttr), + signerInformation.contentType, + signerInformation.content, + null); + } + + /** + * Return a signer information object with passed in SignerInformationStore representing counter + * signatures attached as an unsigned attribute. + * + * @param signerInformation the signerInfo to be used as the basis. + * @param counterSigners signer info objects carrying counter signature. + * @return a copy of the original SignerInformationObject with the changed attributes. + */ + public static SignerInformation AddCounterSigners( + SignerInformation signerInformation, + SignerInformationStore counterSigners) + { + // TODO Perform checks from RFC 3852 11.4 + + SignerInfo sInfo = signerInformation.info; + Asn1.Cms.AttributeTable unsignedAttr = signerInformation.UnsignedAttributes; + Asn1EncodableVector v; + + if (unsignedAttr != null) + { + v = unsignedAttr.ToAsn1EncodableVector(); + } + else + { + v = new Asn1EncodableVector(); + } + + Asn1EncodableVector sigs = new Asn1EncodableVector(); + + foreach (SignerInformation sigInf in counterSigners.GetSigners()) + { + sigs.Add(sigInf.ToSignerInfo()); + } + + v.Add(new Asn1.Cms.Attribute(CmsAttributes.CounterSignature, new DerSet(sigs))); + + return new SignerInformation( + new SignerInfo( + sInfo.SignerID, + sInfo.DigestAlgorithm, + sInfo.AuthenticatedAttributes, + sInfo.DigestEncryptionAlgorithm, + sInfo.EncryptedDigest, + new DerSet(v)), + signerInformation.contentType, + signerInformation.content, + null); + } + } +} diff --git a/bc-sharp-crypto/src/cms/SignerInformationStore.cs b/bc-sharp-crypto/src/cms/SignerInformationStore.cs new file mode 100644 index 0000000..2794086 --- /dev/null +++ b/bc-sharp-crypto/src/cms/SignerInformationStore.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Cms +{ + public class SignerInformationStore + { + private readonly IList all; //ArrayList[SignerInformation] + private readonly IDictionary table = Platform.CreateHashtable(); // Hashtable[SignerID, ArrayList[SignerInformation]] + + /** + * Create a store containing a single SignerInformation object. + * + * @param signerInfo the signer information to contain. + */ + public SignerInformationStore( + SignerInformation signerInfo) + { + this.all = Platform.CreateArrayList(1); + this.all.Add(signerInfo); + + SignerID sid = signerInfo.SignerID; + + table[sid] = all; + } + + /** + * Create a store containing a collection of SignerInformation objects. + * + * @param signerInfos a collection signer information objects to contain. + */ + public SignerInformationStore( + ICollection signerInfos) + { + foreach (SignerInformation signer in signerInfos) + { + SignerID sid = signer.SignerID; + IList list = (IList)table[sid]; + + if (list == null) + { + table[sid] = list = Platform.CreateArrayList(1); + } + + list.Add(signer); + } + + this.all = Platform.CreateArrayList(signerInfos); + } + + /** + * Return the first SignerInformation object that matches the + * passed in selector. Null if there are no matches. + * + * @param selector to identify a signer + * @return a single SignerInformation object. Null if none matches. + */ + public SignerInformation GetFirstSigner( + SignerID selector) + { + IList list = (IList) table[selector]; + + return list == null ? null : (SignerInformation) list[0]; + } + + /// The number of signers in the collection. + public int Count + { + get { return all.Count; } + } + + /// An ICollection of all signers in the collection + public ICollection GetSigners() + { + return Platform.CreateArrayList(all); + } + + /** + * Return possible empty collection with signers matching the passed in SignerID + * + * @param selector a signer id to select against. + * @return a collection of SignerInformation objects. + */ + public ICollection GetSigners( + SignerID selector) + { + IList list = (IList) table[selector]; + + return list == null ? Platform.CreateArrayList() : Platform.CreateArrayList(list); + } + } +} diff --git a/bc-sharp-crypto/src/cms/SimpleAttributeTableGenerator.cs b/bc-sharp-crypto/src/cms/SimpleAttributeTableGenerator.cs new file mode 100644 index 0000000..b3df21c --- /dev/null +++ b/bc-sharp-crypto/src/cms/SimpleAttributeTableGenerator.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1.Cms; + +namespace Org.BouncyCastle.Cms +{ + /** + * Basic generator that just returns a preconstructed attribute table + */ + public class SimpleAttributeTableGenerator + : CmsAttributeTableGenerator + { + private readonly AttributeTable attributes; + + public SimpleAttributeTableGenerator( + AttributeTable attributes) + { + this.attributes = attributes; + } + + public virtual AttributeTable GetAttributes( + IDictionary parameters) + { + return attributes; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/AsymmetricCipherKeyPair.cs b/bc-sharp-crypto/src/crypto/AsymmetricCipherKeyPair.cs new file mode 100644 index 0000000..b00a3dc --- /dev/null +++ b/bc-sharp-crypto/src/crypto/AsymmetricCipherKeyPair.cs @@ -0,0 +1,52 @@ +using System; + +namespace Org.BouncyCastle.Crypto +{ + /** + * a holding class for public/private parameter pairs. + */ + public class AsymmetricCipherKeyPair + { + private readonly AsymmetricKeyParameter publicParameter; + private readonly AsymmetricKeyParameter privateParameter; + + /** + * basic constructor. + * + * @param publicParam a public key parameters object. + * @param privateParam the corresponding private key parameters. + */ + public AsymmetricCipherKeyPair( + AsymmetricKeyParameter publicParameter, + AsymmetricKeyParameter privateParameter) + { + if (publicParameter.IsPrivate) + throw new ArgumentException("Expected a public key", "publicParameter"); + if (!privateParameter.IsPrivate) + throw new ArgumentException("Expected a private key", "privateParameter"); + + this.publicParameter = publicParameter; + this.privateParameter = privateParameter; + } + + /** + * return the public key parameters. + * + * @return the public key parameters. + */ + public AsymmetricKeyParameter Public + { + get { return publicParameter; } + } + + /** + * return the private key parameters. + * + * @return the private key parameters. + */ + public AsymmetricKeyParameter Private + { + get { return privateParameter; } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/AsymmetricKeyParameter.cs b/bc-sharp-crypto/src/crypto/AsymmetricKeyParameter.cs new file mode 100644 index 0000000..7502ee3 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/AsymmetricKeyParameter.cs @@ -0,0 +1,47 @@ +using System; + +using Org.BouncyCastle.Crypto; + +namespace Org.BouncyCastle.Crypto +{ + public abstract class AsymmetricKeyParameter + : ICipherParameters + { + private readonly bool privateKey; + + protected AsymmetricKeyParameter( + bool privateKey) + { + this.privateKey = privateKey; + } + + public bool IsPrivate + { + get { return privateKey; } + } + + public override bool Equals( + object obj) + { + AsymmetricKeyParameter other = obj as AsymmetricKeyParameter; + + if (other == null) + { + return false; + } + + return Equals(other); + } + + protected bool Equals( + AsymmetricKeyParameter other) + { + return privateKey == other.privateKey; + } + + public override int GetHashCode() + { + return privateKey.GetHashCode(); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/BufferedAeadBlockCipher.cs b/bc-sharp-crypto/src/crypto/BufferedAeadBlockCipher.cs new file mode 100644 index 0000000..7ba4109 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/BufferedAeadBlockCipher.cs @@ -0,0 +1,247 @@ +using System; + +using Org.BouncyCastle.Crypto.Modes; +using Org.BouncyCastle.Crypto.Parameters; + +namespace Org.BouncyCastle.Crypto +{ + /** + * The AEAD block ciphers already handle buffering internally, so this class + * just takes care of implementing IBufferedCipher methods. + */ + public class BufferedAeadBlockCipher + : BufferedCipherBase + { + private readonly IAeadBlockCipher cipher; + + public BufferedAeadBlockCipher( + IAeadBlockCipher cipher) + { + if (cipher == null) + throw new ArgumentNullException("cipher"); + + this.cipher = cipher; + } + + public override string AlgorithmName + { + get { return cipher.AlgorithmName; } + } + + /** + * 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. + * @exception ArgumentException if the parameters argument is + * inappropriate. + */ + public override void Init( + bool forEncryption, + ICipherParameters parameters) + { + if (parameters is ParametersWithRandom) + { + parameters = ((ParametersWithRandom) parameters).Parameters; + } + + cipher.Init(forEncryption, parameters); + } + + /** + * return the blocksize for the underlying cipher. + * + * @return the blocksize for the underlying cipher. + */ + public override 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 override int GetUpdateOutputSize( + int length) + { + return cipher.GetUpdateOutputSize(length); + } + + /** + * 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 override int GetOutputSize( + int length) + { + return cipher.GetOutputSize(length); + } + + /** + * process a single byte, producing an output block if necessary. + * + * @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 InvalidOperationException if the cipher isn't initialised. + */ + public override int ProcessByte( + byte input, + byte[] output, + int outOff) + { + return cipher.ProcessByte(input, output, outOff); + } + + public override byte[] ProcessByte( + byte input) + { + int outLength = GetUpdateOutputSize(1); + + byte[] outBytes = outLength > 0 ? new byte[outLength] : null; + + int pos = ProcessByte(input, outBytes, 0); + + if (outLength > 0 && pos < outLength) + { + byte[] tmp = new byte[pos]; + Array.Copy(outBytes, 0, tmp, 0, pos); + outBytes = tmp; + } + + return outBytes; + } + + public override byte[] ProcessBytes( + byte[] input, + int inOff, + int length) + { + if (input == null) + throw new ArgumentNullException("input"); + if (length < 1) + return null; + + int outLength = GetUpdateOutputSize(length); + + byte[] outBytes = outLength > 0 ? new byte[outLength] : null; + + int pos = ProcessBytes(input, inOff, length, outBytes, 0); + + if (outLength > 0 && pos < outLength) + { + byte[] tmp = new byte[pos]; + Array.Copy(outBytes, 0, tmp, 0, pos); + outBytes = tmp; + } + + return outBytes; + } + + /** + * 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 InvalidOperationException if the cipher isn't initialised. + */ + public override int ProcessBytes( + byte[] input, + int inOff, + int length, + byte[] output, + int outOff) + { + return cipher.ProcessBytes(input, inOff, length, output, outOff); + } + + public override byte[] DoFinal() + { + byte[] outBytes = new byte[GetOutputSize(0)]; + + int pos = DoFinal(outBytes, 0); + + if (pos < outBytes.Length) + { + byte[] tmp = new byte[pos]; + Array.Copy(outBytes, 0, tmp, 0, pos); + outBytes = tmp; + } + + return outBytes; + } + + public override byte[] DoFinal( + byte[] input, + int inOff, + int inLen) + { + if (input == null) + throw new ArgumentNullException("input"); + + byte[] outBytes = new byte[GetOutputSize(inLen)]; + + int pos = (inLen > 0) + ? ProcessBytes(input, inOff, inLen, outBytes, 0) + : 0; + + pos += DoFinal(outBytes, pos); + + if (pos < outBytes.Length) + { + byte[] tmp = new byte[pos]; + Array.Copy(outBytes, 0, tmp, 0, pos); + outBytes = tmp; + } + + return outBytes; + } + + /** + * 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 InvalidOperationException 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 override int DoFinal( + byte[] output, + int outOff) + { + return cipher.DoFinal(output, outOff); + } + + /** + * 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 override void Reset() + { + cipher.Reset(); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/BufferedAsymmetricBlockCipher.cs b/bc-sharp-crypto/src/crypto/BufferedAsymmetricBlockCipher.cs new file mode 100644 index 0000000..09ec59f --- /dev/null +++ b/bc-sharp-crypto/src/crypto/BufferedAsymmetricBlockCipher.cs @@ -0,0 +1,152 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Crypto.Engines; + +namespace Org.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 + : BufferedCipherBase + { + private readonly IAsymmetricBlockCipher cipher; + + private byte[] buffer; + private int bufOff; + + /** + * base constructor. + * + * @param cipher the cipher this buffering object wraps. + */ + public BufferedAsymmetricBlockCipher( + IAsymmetricBlockCipher cipher) + { + this.cipher = cipher; + } + + /** + * return the amount of data sitting in the buffer. + * + * @return the amount of data sitting in the buffer. + */ + internal int GetBufferPosition() + { + return bufOff; + } + + public override string AlgorithmName + { + get { return cipher.AlgorithmName; } + } + + public override int GetBlockSize() + { + return cipher.GetInputBlockSize(); + } + + public override int GetOutputSize( + int length) + { + return cipher.GetOutputBlockSize(); + } + + public override int GetUpdateOutputSize( + int length) + { + return 0; + } + + /** + * initialise the buffer and the underlying 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 override void Init( + bool forEncryption, + ICipherParameters parameters) + { + Reset(); + + cipher.Init(forEncryption, parameters); + + // + // we allow for an extra byte where people are using their own padding + // mechanisms on a raw cipher. + // + this.buffer = new byte[cipher.GetInputBlockSize() + (forEncryption ? 1 : 0)]; + this.bufOff = 0; + } + + public override byte[] ProcessByte( + byte input) + { + if (bufOff >= buffer.Length) + throw new DataLengthException("attempt to process message to long for cipher"); + + buffer[bufOff++] = input; + return null; + } + + public override byte[] ProcessBytes( + byte[] input, + int inOff, + int length) + { + if (length < 1) + return null; + + if (input == null) + throw new ArgumentNullException("input"); + if (bufOff + length > buffer.Length) + throw new DataLengthException("attempt to process message to long for cipher"); + + Array.Copy(input, inOff, buffer, bufOff, length); + bufOff += length; + return null; + } + + /** + * 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 override byte[] DoFinal() + { + byte[] outBytes = bufOff > 0 + ? cipher.ProcessBlock(buffer, 0, bufOff) + : EmptyBuffer; + + Reset(); + + return outBytes; + } + + public override byte[] DoFinal( + byte[] input, + int inOff, + int length) + { + ProcessBytes(input, inOff, length); + return DoFinal(); + } + + /// Reset the buffer + public override void Reset() + { + if (buffer != null) + { + Array.Clear(buffer, 0, buffer.Length); + bufOff = 0; + } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/BufferedBlockCipher.cs b/bc-sharp-crypto/src/crypto/BufferedBlockCipher.cs new file mode 100644 index 0000000..c87d2da --- /dev/null +++ b/bc-sharp-crypto/src/crypto/BufferedBlockCipher.cs @@ -0,0 +1,367 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Crypto.Parameters; + +namespace Org.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 + : BufferedCipherBase + { + internal byte[] buf; + internal int bufOff; + internal bool forEncryption; + internal IBlockCipher cipher; + + /** + * constructor for subclasses + */ + protected BufferedBlockCipher() + { + } + + /** + * Create a buffered block cipher without padding. + * + * @param cipher the underlying block cipher this buffering object wraps. + * false otherwise. + */ + public BufferedBlockCipher( + IBlockCipher cipher) + { + if (cipher == null) + throw new ArgumentNullException("cipher"); + + this.cipher = cipher; + buf = new byte[cipher.GetBlockSize()]; + bufOff = 0; + } + + public override string AlgorithmName + { + get { return cipher.AlgorithmName; } + } + + /** + * 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. + * @exception ArgumentException if the parameters argument is + * inappropriate. + */ + // Note: This doubles as the Init in the event that this cipher is being used as an IWrapper + public override void Init( + bool forEncryption, + ICipherParameters parameters) + { + this.forEncryption = forEncryption; + + ParametersWithRandom pwr = parameters as ParametersWithRandom; + if (pwr != null) + parameters = pwr.Parameters; + + Reset(); + + cipher.Init(forEncryption, parameters); + } + + /** + * return the blocksize for the underlying cipher. + * + * @return the blocksize for the underlying cipher. + */ + public override 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 override int GetUpdateOutputSize( + int length) + { + int total = length + bufOff; + int 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 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 override int GetOutputSize( + int length) + { + // Note: Can assume IsPartialBlockOkay is true for purposes of this calculation + return length + bufOff; + } + + /** + * process a single byte, producing an output block if necessary. + * + * @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 InvalidOperationException if the cipher isn't initialised. + */ + public override int ProcessByte( + byte input, + byte[] output, + int outOff) + { + buf[bufOff++] = input; + + if (bufOff == buf.Length) + { + if ((outOff + buf.Length) > output.Length) + throw new DataLengthException("output buffer too short"); + + bufOff = 0; + return cipher.ProcessBlock(buf, 0, output, outOff); + } + + return 0; + } + + public override byte[] ProcessByte( + byte input) + { + int outLength = GetUpdateOutputSize(1); + + byte[] outBytes = outLength > 0 ? new byte[outLength] : null; + + int pos = ProcessByte(input, outBytes, 0); + + if (outLength > 0 && pos < outLength) + { + byte[] tmp = new byte[pos]; + Array.Copy(outBytes, 0, tmp, 0, pos); + outBytes = tmp; + } + + return outBytes; + } + + public override byte[] ProcessBytes( + byte[] input, + int inOff, + int length) + { + if (input == null) + throw new ArgumentNullException("input"); + if (length < 1) + return null; + + int outLength = GetUpdateOutputSize(length); + + byte[] outBytes = outLength > 0 ? new byte[outLength] : null; + + int pos = ProcessBytes(input, inOff, length, outBytes, 0); + + if (outLength > 0 && pos < outLength) + { + byte[] tmp = new byte[pos]; + Array.Copy(outBytes, 0, tmp, 0, pos); + outBytes = tmp; + } + + return outBytes; + } + + /** + * 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 InvalidOperationException if the cipher isn't initialised. + */ + public override int ProcessBytes( + byte[] input, + int inOff, + int length, + byte[] output, + int outOff) + { + if (length < 1) + { + if (length < 0) + throw new ArgumentException("Can't have a negative input length!"); + + return 0; + } + + int blockSize = GetBlockSize(); + int outLength = GetUpdateOutputSize(length); + + if (outLength > 0) + { + Check.OutputLength(output, outOff, outLength, "output buffer too short"); + } + + int resultLen = 0; + int gapLen = buf.Length - bufOff; + if (length > gapLen) + { + Array.Copy(input, inOff, buf, bufOff, gapLen); + resultLen += cipher.ProcessBlock(buf, 0, output, outOff); + bufOff = 0; + length -= gapLen; + inOff += gapLen; + while (length > buf.Length) + { + resultLen += cipher.ProcessBlock(input, inOff, output, outOff + resultLen); + length -= blockSize; + inOff += blockSize; + } + } + Array.Copy(input, inOff, buf, bufOff, length); + bufOff += length; + if (bufOff == buf.Length) + { + resultLen += cipher.ProcessBlock(buf, 0, output, outOff + resultLen); + bufOff = 0; + } + return resultLen; + } + + public override byte[] DoFinal() + { + byte[] outBytes = EmptyBuffer; + + int length = GetOutputSize(0); + if (length > 0) + { + outBytes = new byte[length]; + + int pos = DoFinal(outBytes, 0); + if (pos < outBytes.Length) + { + byte[] tmp = new byte[pos]; + Array.Copy(outBytes, 0, tmp, 0, pos); + outBytes = tmp; + } + } + else + { + Reset(); + } + + return outBytes; + } + + public override byte[] DoFinal( + byte[] input, + int inOff, + int inLen) + { + if (input == null) + throw new ArgumentNullException("input"); + + int length = GetOutputSize(inLen); + + byte[] outBytes = EmptyBuffer; + + if (length > 0) + { + outBytes = new byte[length]; + + int pos = (inLen > 0) + ? ProcessBytes(input, inOff, inLen, outBytes, 0) + : 0; + + pos += DoFinal(outBytes, pos); + + if (pos < outBytes.Length) + { + byte[] tmp = new byte[pos]; + Array.Copy(outBytes, 0, tmp, 0, pos); + outBytes = tmp; + } + } + else + { + Reset(); + } + + return outBytes; + } + + /** + * 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 InvalidOperationException 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 override int DoFinal( + byte[] output, + int outOff) + { + try + { + if (bufOff != 0) + { + Check.DataLength(!cipher.IsPartialBlockOkay, "data not block size aligned"); + Check.OutputLength(output, outOff, bufOff, "output buffer too short for DoFinal()"); + + // NB: Can't copy directly, or we may write too much output + cipher.ProcessBlock(buf, 0, buf, 0); + Array.Copy(buf, 0, output, outOff, bufOff); + } + + return bufOff; + } + finally + { + Reset(); + } + } + + /** + * 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 override void Reset() + { + Array.Clear(buf, 0, buf.Length); + bufOff = 0; + + cipher.Reset(); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/BufferedCipherBase.cs b/bc-sharp-crypto/src/crypto/BufferedCipherBase.cs new file mode 100644 index 0000000..9d86102 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/BufferedCipherBase.cs @@ -0,0 +1,113 @@ +using System; + +namespace Org.BouncyCastle.Crypto +{ + public abstract class BufferedCipherBase + : IBufferedCipher + { + protected static readonly byte[] EmptyBuffer = new byte[0]; + + public abstract string AlgorithmName { get; } + + public abstract void Init(bool forEncryption, ICipherParameters parameters); + + public abstract int GetBlockSize(); + + public abstract int GetOutputSize(int inputLen); + public abstract int GetUpdateOutputSize(int inputLen); + + public abstract byte[] ProcessByte(byte input); + + public virtual int ProcessByte( + byte input, + byte[] output, + int outOff) + { + byte[] outBytes = ProcessByte(input); + if (outBytes == null) + return 0; + if (outOff + outBytes.Length > output.Length) + throw new DataLengthException("output buffer too short"); + outBytes.CopyTo(output, outOff); + return outBytes.Length; + } + + public virtual byte[] ProcessBytes( + byte[] input) + { + return ProcessBytes(input, 0, input.Length); + } + + public abstract byte[] ProcessBytes(byte[] input, int inOff, int length); + + public virtual int ProcessBytes( + byte[] input, + byte[] output, + int outOff) + { + return ProcessBytes(input, 0, input.Length, output, outOff); + } + + public virtual int ProcessBytes( + byte[] input, + int inOff, + int length, + byte[] output, + int outOff) + { + byte[] outBytes = ProcessBytes(input, inOff, length); + if (outBytes == null) + return 0; + if (outOff + outBytes.Length > output.Length) + throw new DataLengthException("output buffer too short"); + outBytes.CopyTo(output, outOff); + return outBytes.Length; + } + + public abstract byte[] DoFinal(); + + public virtual byte[] DoFinal( + byte[] input) + { + return DoFinal(input, 0, input.Length); + } + + public abstract byte[] DoFinal( + byte[] input, + int inOff, + int length); + + public virtual int DoFinal( + byte[] output, + int outOff) + { + byte[] outBytes = DoFinal(); + if (outOff + outBytes.Length > output.Length) + throw new DataLengthException("output buffer too short"); + outBytes.CopyTo(output, outOff); + return outBytes.Length; + } + + public virtual int DoFinal( + byte[] input, + byte[] output, + int outOff) + { + return DoFinal(input, 0, input.Length, output, outOff); + } + + public virtual int DoFinal( + byte[] input, + int inOff, + int length, + byte[] output, + int outOff) + { + int len = ProcessBytes(input, inOff, length, output, outOff); + len += DoFinal(output, outOff + len); + return len; + } + + public abstract void Reset(); + } +} diff --git a/bc-sharp-crypto/src/crypto/BufferedIesCipher.cs b/bc-sharp-crypto/src/crypto/BufferedIesCipher.cs new file mode 100644 index 0000000..6dab4ae --- /dev/null +++ b/bc-sharp-crypto/src/crypto/BufferedIesCipher.cs @@ -0,0 +1,113 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto +{ + public class BufferedIesCipher + : BufferedCipherBase + { + private readonly IesEngine engine; + private bool forEncryption; + private MemoryStream buffer = new MemoryStream(); + + public BufferedIesCipher( + IesEngine engine) + { + if (engine == null) + throw new ArgumentNullException("engine"); + + this.engine = engine; + } + + public override string AlgorithmName + { + // TODO Create IESEngine.AlgorithmName + get { return "IES"; } + } + + public override void Init( + bool forEncryption, + ICipherParameters parameters) + { + this.forEncryption = forEncryption; + + // TODO + throw Platform.CreateNotImplementedException("IES"); + } + + public override int GetBlockSize() + { + return 0; + } + + public override int GetOutputSize( + int inputLen) + { + if (engine == null) + throw new InvalidOperationException("cipher not initialised"); + + int baseLen = inputLen + (int) buffer.Length; + return forEncryption + ? baseLen + 20 + : baseLen - 20; + } + + public override int GetUpdateOutputSize( + int inputLen) + { + return 0; + } + + public override byte[] ProcessByte( + byte input) + { + buffer.WriteByte(input); + return null; + } + + public override byte[] ProcessBytes( + byte[] input, + int inOff, + int length) + { + if (input == null) + throw new ArgumentNullException("input"); + if (inOff < 0) + throw new ArgumentException("inOff"); + if (length < 0) + throw new ArgumentException("length"); + if (inOff + length > input.Length) + throw new ArgumentException("invalid offset/length specified for input array"); + + buffer.Write(input, inOff, length); + return null; + } + + public override byte[] DoFinal() + { + byte[] buf = buffer.ToArray(); + + Reset(); + + return engine.ProcessBlock(buf, 0, buf.Length); + } + + public override byte[] DoFinal( + byte[] input, + int inOff, + int length) + { + ProcessBytes(input, inOff, length); + return DoFinal(); + } + + public override void Reset() + { + buffer.SetLength(0); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/BufferedStreamCipher.cs b/bc-sharp-crypto/src/crypto/BufferedStreamCipher.cs new file mode 100644 index 0000000..2d4987b --- /dev/null +++ b/bc-sharp-crypto/src/crypto/BufferedStreamCipher.cs @@ -0,0 +1,131 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; + +namespace Org.BouncyCastle.Crypto +{ + public class BufferedStreamCipher + : BufferedCipherBase + { + private readonly IStreamCipher cipher; + + public BufferedStreamCipher( + IStreamCipher cipher) + { + if (cipher == null) + throw new ArgumentNullException("cipher"); + + this.cipher = cipher; + } + + public override string AlgorithmName + { + get { return cipher.AlgorithmName; } + } + + public override void Init( + bool forEncryption, + ICipherParameters parameters) + { + if (parameters is ParametersWithRandom) + { + parameters = ((ParametersWithRandom) parameters).Parameters; + } + + cipher.Init(forEncryption, parameters); + } + + public override int GetBlockSize() + { + return 0; + } + + public override int GetOutputSize( + int inputLen) + { + return inputLen; + } + + public override int GetUpdateOutputSize( + int inputLen) + { + return inputLen; + } + + public override byte[] ProcessByte( + byte input) + { + return new byte[]{ cipher.ReturnByte(input) }; + } + + public override int ProcessByte( + byte input, + byte[] output, + int outOff) + { + if (outOff >= output.Length) + throw new DataLengthException("output buffer too short"); + + output[outOff] = cipher.ReturnByte(input); + return 1; + } + + public override byte[] ProcessBytes( + byte[] input, + int inOff, + int length) + { + if (length < 1) + return null; + + byte[] output = new byte[length]; + cipher.ProcessBytes(input, inOff, length, output, 0); + return output; + } + + public override int ProcessBytes( + byte[] input, + int inOff, + int length, + byte[] output, + int outOff) + { + if (length < 1) + return 0; + + if (length > 0) + { + cipher.ProcessBytes(input, inOff, length, output, outOff); + } + + return length; + } + + public override byte[] DoFinal() + { + Reset(); + + return EmptyBuffer; + } + + public override byte[] DoFinal( + byte[] input, + int inOff, + int length) + { + if (length < 1) + return EmptyBuffer; + + byte[] output = ProcessBytes(input, inOff, length); + + Reset(); + + return output; + } + + public override void Reset() + { + cipher.Reset(); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/Check.cs b/bc-sharp-crypto/src/crypto/Check.cs new file mode 100644 index 0000000..96a05c6 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/Check.cs @@ -0,0 +1,25 @@ +using System; + +namespace Org.BouncyCastle.Crypto +{ + internal class Check + { + internal static void DataLength(bool condition, string msg) + { + if (condition) + throw new DataLengthException(msg); + } + + internal static void DataLength(byte[] buf, int off, int len, string msg) + { + if (off + len > buf.Length) + throw new DataLengthException(msg); + } + + internal static void OutputLength(byte[] buf, int off, int len, string msg) + { + if (off + len > buf.Length) + throw new OutputLengthException(msg); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/CipherKeyGenerator.cs b/bc-sharp-crypto/src/crypto/CipherKeyGenerator.cs new file mode 100644 index 0000000..d8d9b29 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/CipherKeyGenerator.cs @@ -0,0 +1,83 @@ +using System; + +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto +{ + /** + * The base class for symmetric, or secret, cipher key generators. + */ + public class CipherKeyGenerator + { + protected internal SecureRandom random; + protected internal int strength; + private bool uninitialised = true; + private int defaultStrength; + + public CipherKeyGenerator() + { + } + + internal CipherKeyGenerator( + int defaultStrength) + { + if (defaultStrength < 1) + throw new ArgumentException("strength must be a positive value", "defaultStrength"); + + this.defaultStrength = defaultStrength; + } + + public int DefaultStrength + { + get { return defaultStrength; } + } + + /** + * initialise the key generator. + * + * @param param the parameters to be used for key generation + */ + public void Init( + KeyGenerationParameters parameters) + { + if (parameters == null) + throw new ArgumentNullException("parameters"); + + this.uninitialised = false; + + engineInit(parameters); + } + + protected virtual void engineInit( + KeyGenerationParameters parameters) + { + this.random = parameters.Random; + this.strength = (parameters.Strength + 7) / 8; + } + + /** + * Generate a secret key. + * + * @return a byte array containing the key value. + */ + public byte[] GenerateKey() + { + if (uninitialised) + { + if (defaultStrength < 1) + throw new InvalidOperationException("Generator has not been initialised"); + + uninitialised = false; + + engineInit(new KeyGenerationParameters(new SecureRandom(), defaultStrength)); + } + + return engineGenerateKey(); + } + + protected virtual byte[] engineGenerateKey() + { + return SecureRandom.GetNextBytes(random, strength); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/CryptoException.cs b/bc-sharp-crypto/src/crypto/CryptoException.cs new file mode 100644 index 0000000..73d450b --- /dev/null +++ b/bc-sharp-crypto/src/crypto/CryptoException.cs @@ -0,0 +1,28 @@ +using System; + +namespace Org.BouncyCastle.Crypto +{ +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT || PORTABLE) + [Serializable] +#endif + public class CryptoException + : Exception + { + public CryptoException() + { + } + + public CryptoException( + string message) + : base(message) + { + } + + public CryptoException( + string message, + Exception exception) + : base(message, exception) + { + } + } +} diff --git a/bc-sharp-crypto/src/crypto/DataLengthException.cs b/bc-sharp-crypto/src/crypto/DataLengthException.cs new file mode 100644 index 0000000..447ff2a --- /dev/null +++ b/bc-sharp-crypto/src/crypto/DataLengthException.cs @@ -0,0 +1,42 @@ +using System; + +namespace Org.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. + */ +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT || PORTABLE) + [Serializable] +#endif + public class DataLengthException + : CryptoException + { + /** + * 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) + : base(message) + { + } + + public DataLengthException( + string message, + Exception exception) + : base(message, exception) + { + } + } +} diff --git a/bc-sharp-crypto/src/crypto/IAsymmetricBlockCipher.cs b/bc-sharp-crypto/src/crypto/IAsymmetricBlockCipher.cs new file mode 100644 index 0000000..455cfaa --- /dev/null +++ b/bc-sharp-crypto/src/crypto/IAsymmetricBlockCipher.cs @@ -0,0 +1,30 @@ +using System; + +namespace Org.BouncyCastle.Crypto +{ + /// Base interface for a public/private key block cipher. + public interface IAsymmetricBlockCipher + { + /// The name of the algorithm this cipher implements. + string AlgorithmName { get; } + + /// Initialise the cipher. + /// Initialise for encryption if true, for decryption if false. + /// The key or other data required by the cipher. + void Init(bool forEncryption, ICipherParameters parameters); + + /// The maximum size, in bytes, an input block may be. + int GetInputBlockSize(); + + /// The maximum size, in bytes, an output block will be. + int GetOutputBlockSize(); + + /// Process a block. + /// The input buffer. + /// The offset into inBuf that the input block begins. + /// The length of the input block. + /// Input decrypts improperly. + /// Input is too large for the cipher. + byte[] ProcessBlock(byte[] inBuf, int inOff, int inLen); + } +} diff --git a/bc-sharp-crypto/src/crypto/IAsymmetricCipherKeyPairGenerator.cs b/bc-sharp-crypto/src/crypto/IAsymmetricCipherKeyPairGenerator.cs new file mode 100644 index 0000000..9ec5dfa --- /dev/null +++ b/bc-sharp-crypto/src/crypto/IAsymmetricCipherKeyPairGenerator.cs @@ -0,0 +1,24 @@ +using System; + +namespace Org.BouncyCastle.Crypto +{ + /** + * interface that a public/private key pair generator should conform to. + */ + public interface IAsymmetricCipherKeyPairGenerator + { + /** + * intialise the key pair generator. + * + * @param the parameters the key pair is to be initialised with. + */ + void Init(KeyGenerationParameters parameters); + + /** + * return an AsymmetricCipherKeyPair containing the Generated keys. + * + * @return an AsymmetricCipherKeyPair containing the Generated keys. + */ + AsymmetricCipherKeyPair GenerateKeyPair(); + } +} diff --git a/bc-sharp-crypto/src/crypto/IBasicAgreement.cs b/bc-sharp-crypto/src/crypto/IBasicAgreement.cs new file mode 100644 index 0000000..7dfc618 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/IBasicAgreement.cs @@ -0,0 +1,29 @@ +using System; +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Crypto +{ + /** + * The basic interface that basic Diffie-Hellman implementations + * conforms to. + */ + public interface IBasicAgreement + { + /** + * initialise the agreement engine. + */ + void Init(ICipherParameters parameters); + + /** + * return the field size for the agreement algorithm in bytes. + */ + int GetFieldSize(); + + /** + * given a public key from a given party calculate the next + * message in the agreement sequence. + */ + BigInteger CalculateAgreement(ICipherParameters pubKey); + } + +} diff --git a/bc-sharp-crypto/src/crypto/IBlockCipher.cs b/bc-sharp-crypto/src/crypto/IBlockCipher.cs new file mode 100644 index 0000000..a3ad6d6 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/IBlockCipher.cs @@ -0,0 +1,36 @@ +using System; + +namespace Org.BouncyCastle.Crypto +{ + /// Base interface for a symmetric key block cipher. + public interface IBlockCipher + { + /// The name of the algorithm this cipher implements. + string AlgorithmName { get; } + + /// Initialise the cipher. + /// Initialise for encryption if true, for decryption if false. + /// The key or other data required by the cipher. + void Init(bool forEncryption, ICipherParameters parameters); + + /// The block size for this cipher, in bytes. + int GetBlockSize(); + + /// Indicates whether this cipher can handle partial blocks. + bool IsPartialBlockOkay { get; } + + /// Process a block. + /// The input buffer. + /// The offset into inBuf that the input block begins. + /// The output buffer. + /// The offset into outBuf to write the output block. + /// If input block is wrong size, or outBuf too small. + /// The number of bytes processed and produced. + int ProcessBlock(byte[] inBuf, int inOff, byte[] outBuf, int outOff); + + /// + /// Reset the cipher to the same state as it was after the last init (if there was one). + /// + void Reset(); + } +} diff --git a/bc-sharp-crypto/src/crypto/IBlockResult.cs b/bc-sharp-crypto/src/crypto/IBlockResult.cs new file mode 100644 index 0000000..0f054fe --- /dev/null +++ b/bc-sharp-crypto/src/crypto/IBlockResult.cs @@ -0,0 +1,24 @@ + +namespace Org.BouncyCastle.Crypto +{ + /// + /// Operators that reduce their input to a single block return an object + /// of this type. + /// + public interface IBlockResult + { + /// + /// Return the final result of the operation. + /// + /// A block of bytes, representing the result of an operation. + byte[] Collect(); + + /// + /// Store the final result of the operation by copying it into the destination array. + /// + /// The number of bytes copied into destination. + /// The byte array to copy the result into. + /// The offset into destination to start copying the result at. + int Collect(byte[] destination, int offset); + } +} diff --git a/bc-sharp-crypto/src/crypto/IBufferedCipher.cs b/bc-sharp-crypto/src/crypto/IBufferedCipher.cs new file mode 100644 index 0000000..69dec95 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/IBufferedCipher.cs @@ -0,0 +1,44 @@ +using System; + +namespace Org.BouncyCastle.Crypto +{ + /// Block cipher engines are expected to conform to this interface. + public interface IBufferedCipher + { + /// The name of the algorithm this cipher implements. + string AlgorithmName { get; } + + /// Initialise the cipher. + /// If true the cipher is initialised for encryption, + /// if false for decryption. + /// The key and other data required by the cipher. + void Init(bool forEncryption, ICipherParameters parameters); + + int GetBlockSize(); + + int GetOutputSize(int inputLen); + + int GetUpdateOutputSize(int inputLen); + + byte[] ProcessByte(byte input); + int ProcessByte(byte input, byte[] output, int outOff); + + byte[] ProcessBytes(byte[] input); + byte[] ProcessBytes(byte[] input, int inOff, int length); + int ProcessBytes(byte[] input, byte[] output, int outOff); + int ProcessBytes(byte[] input, int inOff, int length, byte[] output, int outOff); + + byte[] DoFinal(); + byte[] DoFinal(byte[] input); + byte[] DoFinal(byte[] input, int inOff, int length); + int DoFinal(byte[] output, int outOff); + int DoFinal(byte[] input, byte[] output, int outOff); + int DoFinal(byte[] input, int inOff, int length, byte[] output, int outOff); + + /// + /// Reset the cipher. After resetting the cipher is in the same state + /// as it was after the last init (if there was one). + /// + void Reset(); + } +} diff --git a/bc-sharp-crypto/src/crypto/ICipherParameters.cs b/bc-sharp-crypto/src/crypto/ICipherParameters.cs new file mode 100644 index 0000000..fff0941 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/ICipherParameters.cs @@ -0,0 +1,11 @@ +using System; + +namespace Org.BouncyCastle.Crypto +{ + /** + * all parameter classes implement this. + */ + public interface ICipherParameters + { + } +} diff --git a/bc-sharp-crypto/src/crypto/IDSA.cs b/bc-sharp-crypto/src/crypto/IDSA.cs new file mode 100644 index 0000000..46056d8 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/IDSA.cs @@ -0,0 +1,40 @@ +using System; +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Crypto +{ + /** + * interface for classes implementing the Digital Signature Algorithm + */ + public interface IDsa + { + string AlgorithmName { get; } + + /** + * 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. + */ + void Init(bool forSigning, ICipherParameters parameters); + + /** + * 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. + */ + 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. + */ + bool VerifySignature(byte[] message, BigInteger r, BigInteger s); + } +} diff --git a/bc-sharp-crypto/src/crypto/IDerivationFunction.cs b/bc-sharp-crypto/src/crypto/IDerivationFunction.cs new file mode 100644 index 0000000..7f289f7 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/IDerivationFunction.cs @@ -0,0 +1,24 @@ +using System; + +namespace Org.BouncyCastle.Crypto +{ + /** + * base interface for general purpose byte derivation functions. + */ + public interface IDerivationFunction + { + void Init(IDerivationParameters parameters); + + /** + * return the message digest used as the basis for the function + */ + IDigest Digest + { + get; + } + + int GenerateBytes(byte[] output, int outOff, int length); + //throws DataLengthException, ArgumentException; + } + +} diff --git a/bc-sharp-crypto/src/crypto/IDerivationParameters.cs b/bc-sharp-crypto/src/crypto/IDerivationParameters.cs new file mode 100644 index 0000000..f1c8485 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/IDerivationParameters.cs @@ -0,0 +1,11 @@ +using System; + +namespace Org.BouncyCastle.Crypto +{ + /** + * Parameters for key/byte stream derivation classes + */ + public interface IDerivationParameters + { + } +} diff --git a/bc-sharp-crypto/src/crypto/IDigest.cs b/bc-sharp-crypto/src/crypto/IDigest.cs new file mode 100644 index 0000000..6769dcc --- /dev/null +++ b/bc-sharp-crypto/src/crypto/IDigest.cs @@ -0,0 +1,61 @@ +using System; + +namespace Org.BouncyCastle.Crypto +{ + /** + * interface that a message digest conforms to. + */ + public interface IDigest + { + /** + * return the algorithm name + * + * @return the algorithm name + */ + string AlgorithmName { get; } + + /** + * 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. + */ + int GetDigestSize(); + + /** + * return the size, in bytes, of the internal buffer used by this digest. + * + * @return the size, in bytes, of the internal buffer used by this digest. + */ + int GetByteLength(); + + /** + * update the message digest with a single byte. + * + * @param inByte the input byte to be entered. + */ + void Update(byte input); + + /** + * update the message digest with a block of bytes. + * + * @param input 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. + */ + void BlockUpdate(byte[] input, int inOff, int length); + + /** + * Close the digest, producing the final digest value. The doFinal + * call leaves the digest reset. + * + * @param output the array the digest is to be copied into. + * @param outOff the offset into the out array the digest is to start at. + */ + int DoFinal(byte[] output, int outOff); + + /** + * reset the digest back to it's initial state. + */ + void Reset(); + } +} diff --git a/bc-sharp-crypto/src/crypto/IEntropySource.cs b/bc-sharp-crypto/src/crypto/IEntropySource.cs new file mode 100644 index 0000000..62e3bc7 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/IEntropySource.cs @@ -0,0 +1,29 @@ +using System; + +namespace Org.BouncyCastle.Crypto +{ + /// + /// Base interface describing an entropy source for a DRBG. + /// + public interface IEntropySource + { + /// + /// Return whether or not this entropy source is regarded as prediction resistant. + /// + /// true if this instance is prediction resistant; otherwise, false. + bool IsPredictionResistant { get; } + + /// + /// Return a byte array of entropy. + /// + /// The entropy bytes. + byte[] GetEntropy(); + + /// + /// Return the number of bits of entropy this source can produce. + /// + /// The size, in bits, of the return value of getEntropy. + int EntropySize { get; } + } +} + diff --git a/bc-sharp-crypto/src/crypto/IEntropySourceProvider.cs b/bc-sharp-crypto/src/crypto/IEntropySourceProvider.cs new file mode 100644 index 0000000..7564141 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/IEntropySourceProvider.cs @@ -0,0 +1,17 @@ +using System; + +namespace Org.BouncyCastle.Crypto +{ + /// + /// Base interface describing a provider of entropy sources. + /// + public interface IEntropySourceProvider + { + /// + /// Return an entropy source providing a block of entropy. + /// + /// The size of the block of entropy required. + /// An entropy source providing bitsRequired blocks of entropy. + IEntropySource Get(int bitsRequired); + } +} diff --git a/bc-sharp-crypto/src/crypto/IMac.cs b/bc-sharp-crypto/src/crypto/IMac.cs new file mode 100644 index 0000000..03a86e8 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/IMac.cs @@ -0,0 +1,69 @@ +using System; + +namespace Org.BouncyCastle.Crypto +{ + /** + * The base interface for implementations of message authentication codes (MACs). + */ + public interface IMac + { + /** + * Initialise the MAC. + * + * @param param the key and other data required by the MAC. + * @exception ArgumentException if the parameters argument is + * inappropriate. + */ + void Init(ICipherParameters parameters); + + /** + * Return the name of the algorithm the MAC implements. + * + * @return the name of the algorithm the MAC implements. + */ + string AlgorithmName { get; } + + /** + * Return the block size for this MAC (in bytes). + * + * @return the block size for this MAC in bytes. + */ + int GetMacSize(); + + /** + * add a single byte to the mac for processing. + * + * @param in the byte to be processed. + * @exception InvalidOperationException if the MAC is not initialised. + */ + void Update(byte input); + + /** + * @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 InvalidOperationException if the MAC is not initialised. + * @exception DataLengthException if there isn't enough data in in. + */ + void BlockUpdate(byte[] input, int inOff, int len); + + /** + * 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 InvalidOperationException if the MAC is not initialised. + */ + int DoFinal(byte[] output, int outOff); + + /** + * 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). + */ + void Reset(); + } +} diff --git a/bc-sharp-crypto/src/crypto/ISignatureFactory.cs b/bc-sharp-crypto/src/crypto/ISignatureFactory.cs new file mode 100644 index 0000000..cbca7d1 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/ISignatureFactory.cs @@ -0,0 +1,23 @@ +using System; + +namespace Org.BouncyCastle.Crypto +{ + /// + /// Base interface for operators that serve as stream-based signature calculators. + /// + public interface ISignatureFactory + { + /// The algorithm details object for this calculator. + Object AlgorithmDetails { get ; } + + /// + /// Create a stream calculator for this signature calculator. The stream + /// calculator is used for the actual operation of entering the data to be signed + /// and producing the signature block. + /// + /// A calculator producing an IBlockResult with a signature in it. + IStreamCalculator CreateCalculator(); + } +} + + diff --git a/bc-sharp-crypto/src/crypto/ISigner.cs b/bc-sharp-crypto/src/crypto/ISigner.cs new file mode 100644 index 0000000..e03bbf4 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/ISigner.cs @@ -0,0 +1,50 @@ + +using System; +using System.Text; + +namespace Org.BouncyCastle.Crypto +{ + public interface ISigner + { + /** + * Return the name of the algorithm the signer implements. + * + * @return the name of the algorithm the signer implements. + */ + string AlgorithmName { get; } + + /** + * Initialise the signer for signing or verification. + * + * @param forSigning true if for signing, false otherwise + * @param param necessary parameters. + */ + void Init(bool forSigning, ICipherParameters parameters); + + /** + * update the internal digest with the byte b + */ + void Update(byte input); + + /** + * update the internal digest with the byte array in + */ + void BlockUpdate(byte[] input, int inOff, int length); + + /** + * Generate a signature for the message we've been loaded with using + * the key we were initialised with. + */ + byte[] GenerateSignature(); + /** + * return true if the internal state represents the signature described + * in the passed in array. + */ + bool VerifySignature(byte[] signature); + + /** + * reset the internal state + */ + void Reset(); + } +} diff --git a/bc-sharp-crypto/src/crypto/ISignerWithRecovery.cs b/bc-sharp-crypto/src/crypto/ISignerWithRecovery.cs new file mode 100644 index 0000000..024f5ce --- /dev/null +++ b/bc-sharp-crypto/src/crypto/ISignerWithRecovery.cs @@ -0,0 +1,37 @@ +using System; +using System.Text; + +namespace Org.BouncyCastle.Crypto +{ + /** + * Signer with message recovery. + */ + public interface ISignerWithRecovery + : ISigner + { + /** + * Returns true if the signer has recovered the full message as + * part of signature verification. + * + * @return true if full message recovered. + */ + bool HasFullMessage(); + + /** + * Returns a reference to what message was recovered (if any). + * + * @return full/partial message, null if nothing. + */ + byte[] GetRecoveredMessage(); + + /** + * Perform an update with the recovered message before adding any other data. This must + * be the first update method called, and calling it will result in the signer assuming + * that further calls to update will include message content past what is recoverable. + * + * @param signature the signature that we are in the process of verifying. + * @throws IllegalStateException + */ + void UpdateWithRecoveredMessage(byte[] signature); + } +} diff --git a/bc-sharp-crypto/src/crypto/IStreamCalculator.cs b/bc-sharp-crypto/src/crypto/IStreamCalculator.cs new file mode 100644 index 0000000..19a5428 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/IStreamCalculator.cs @@ -0,0 +1,23 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Crypto +{ + /// + /// Base interface for cryptographic operations such as Hashes, MACs, and Signatures which reduce a stream of data + /// to a single value. + /// + public interface IStreamCalculator + { + /// Return a "sink" stream which only exists to update the implementing object. + /// A stream to write to in order to update the implementing object. + Stream Stream { get; } + + /// + /// Return the result of processing the stream. This value is only available once the stream + /// has been closed. + /// + /// The result of processing the stream. + Object GetResult(); + } +} diff --git a/bc-sharp-crypto/src/crypto/IStreamCipher.cs b/bc-sharp-crypto/src/crypto/IStreamCipher.cs new file mode 100644 index 0000000..8e575a7 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/IStreamCipher.cs @@ -0,0 +1,45 @@ +using System; + +namespace Org.BouncyCastle.Crypto +{ + /// The interface stream ciphers conform to. + public interface IStreamCipher + { + /// The name of the algorithm this cipher implements. + string AlgorithmName { get; } + + /// Initialise the cipher. + /// If true the cipher is initialised for encryption, + /// if false for decryption. + /// The key and other data required by the cipher. + /// + /// If the parameters argument is inappropriate. + /// + void Init(bool forEncryption, ICipherParameters parameters); + + /// encrypt/decrypt a single byte returning the result. + /// the byte to be processed. + /// the result of processing the input byte. + byte ReturnByte(byte input); + + /// + /// Process a block of bytes from input putting the result into output. + /// + /// The input byte array. + /// + /// The offset into input where the data to be processed starts. + /// + /// The number of bytes to be processed. + /// The output buffer the processed bytes go into. + /// + /// The offset into output the processed data starts at. + /// + /// If the output buffer is too small. + void ProcessBytes(byte[] input, int inOff, int length, byte[] output, int outOff); + + /// + /// Reset the cipher to the same state as it was after the last init (if there was one). + /// + void Reset(); + } +} diff --git a/bc-sharp-crypto/src/crypto/IVerifier.cs b/bc-sharp-crypto/src/crypto/IVerifier.cs new file mode 100644 index 0000000..560cabf --- /dev/null +++ b/bc-sharp-crypto/src/crypto/IVerifier.cs @@ -0,0 +1,25 @@ +namespace Org.BouncyCastle.Crypto +{ + /// + /// Operators that reduce their input to the validation of a signature produce this type. + /// + public interface IVerifier + { + /// + /// Return true if the passed in data matches what is expected by the verification result. + /// + /// The bytes representing the signature. + /// true if the signature verifies, false otherwise. + bool IsVerified(byte[] data); + + /// + /// Return true if the length bytes from off in the source array match the signature + /// expected by the verification result. + /// + /// Byte array containing the signature. + /// The offset into the source array where the signature starts. + /// The number of bytes in source making up the signature. + /// true if the signature verifies, false otherwise. + bool IsVerified(byte[] source, int off, int length); + } +} diff --git a/bc-sharp-crypto/src/crypto/IVerifierFactory.cs b/bc-sharp-crypto/src/crypto/IVerifierFactory.cs new file mode 100644 index 0000000..9502b14 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/IVerifierFactory.cs @@ -0,0 +1,21 @@ +using System; + +namespace Org.BouncyCastle.Crypto +{ + /// + /// Base interface for operators that serve as stream-based signature verifiers. + /// + public interface IVerifierFactory + { + /// The algorithm details object for this verifier. + Object AlgorithmDetails { get ; } + + /// + /// Create a stream calculator for this verifier. The stream + /// calculator is used for the actual operation of entering the data to be verified + /// and producing a result which can be used to verify the original signature. + /// + /// A calculator producing an IVerifier which can verify the signature. + IStreamCalculator CreateCalculator(); + } +} diff --git a/bc-sharp-crypto/src/crypto/IVerifierFactoryProvider.cs b/bc-sharp-crypto/src/crypto/IVerifierFactoryProvider.cs new file mode 100644 index 0000000..9cfcbb2 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/IVerifierFactoryProvider.cs @@ -0,0 +1,18 @@ +using System; + +namespace Org.BouncyCastle.Crypto +{ + /// + /// Base interface for a provider to support the dynamic creation of signature verifiers. + /// + public interface IVerifierFactoryProvider + { + /// + /// Return a signature verfier for signature algorithm described in the passed in algorithm details object. + /// + /// The details of the signature algorithm verification is required for. + /// A new signature verifier. + IVerifierFactory CreateVerifierFactory (Object algorithmDetails); + } +} + diff --git a/bc-sharp-crypto/src/crypto/IWrapper.cs b/bc-sharp-crypto/src/crypto/IWrapper.cs new file mode 100644 index 0000000..58202b3 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/IWrapper.cs @@ -0,0 +1,18 @@ +using System; + +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto +{ + public interface IWrapper + { + /// The name of the algorithm this cipher implements. + string AlgorithmName { get; } + + void Init(bool forWrapping, ICipherParameters parameters); + + byte[] Wrap(byte[] input, int inOff, int length); + + byte[] Unwrap(byte[] input, int inOff, int length); + } +} diff --git a/bc-sharp-crypto/src/crypto/IXof.cs b/bc-sharp-crypto/src/crypto/IXof.cs new file mode 100644 index 0000000..f76304d --- /dev/null +++ b/bc-sharp-crypto/src/crypto/IXof.cs @@ -0,0 +1,31 @@ +using System; + +namespace Org.BouncyCastle.Crypto +{ + /// + /// With FIPS PUB 202 a new kind of message digest was announced which supported extendable output, or variable digest sizes. + /// This interface provides the extra method required to support variable output on a digest implementation. + /// + public interface IXof + : IDigest + { + /// + /// Output the results of the final calculation for this digest to outLen number of bytes. + /// + /// output array to write the output bytes to. + /// offset to start writing the bytes at. + /// the number of output bytes requested. + /// the number of bytes written + int DoFinal(byte[] output, int outOff, int outLen); + + /// + /// Start outputting the results of the final calculation for this digest. Unlike DoFinal, this method + /// will continue producing output until the Xof is explicitly reset, or signals otherwise. + /// + /// output array to write the output bytes to. + /// offset to start writing the bytes at. + /// the number of output bytes requested. + /// the number of bytes written + int DoOutput(byte[] output, int outOff, int outLen); + } +} diff --git a/bc-sharp-crypto/src/crypto/InvalidCipherTextException.cs b/bc-sharp-crypto/src/crypto/InvalidCipherTextException.cs new file mode 100644 index 0000000..0fe540d --- /dev/null +++ b/bc-sharp-crypto/src/crypto/InvalidCipherTextException.cs @@ -0,0 +1,40 @@ +using System; + +namespace Org.BouncyCastle.Crypto +{ + /** + * this exception is thrown whenever we find something we don't expect in a + * message. + */ +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT || PORTABLE) + [Serializable] +#endif + public class InvalidCipherTextException + : 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) + : base(message) + { + } + + public InvalidCipherTextException( + string message, + Exception exception) + : base(message, exception) + { + } + } +} diff --git a/bc-sharp-crypto/src/crypto/KeyGenerationParameters.cs b/bc-sharp-crypto/src/crypto/KeyGenerationParameters.cs new file mode 100644 index 0000000..0cb6b07 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/KeyGenerationParameters.cs @@ -0,0 +1,55 @@ +using System; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto +{ + /** + * 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) + { + if (random == null) + throw new ArgumentNullException("random"); + if (strength < 1) + throw new ArgumentException("strength must be a positive value", "strength"); + + this.random = random; + this.strength = strength; + } + + /** + * return the random source associated with this + * generator. + * + * @return the generators random source. + */ + public SecureRandom Random + { + get { 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 Strength + { + get { return strength; } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/MaxBytesExceededException.cs b/bc-sharp-crypto/src/crypto/MaxBytesExceededException.cs new file mode 100644 index 0000000..8992c45 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/MaxBytesExceededException.cs @@ -0,0 +1,32 @@ +using System; + +namespace Org.BouncyCastle.Crypto +{ + /// + /// This exception is thrown whenever a cipher requires a change of key, iv + /// or similar after x amount of bytes enciphered + /// +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT || PORTABLE) + [Serializable] +#endif + public class MaxBytesExceededException + : CryptoException + { + public MaxBytesExceededException() + { + } + + public MaxBytesExceededException( + string message) + : base(message) + { + } + + public MaxBytesExceededException( + string message, + Exception e) + : base(message, e) + { + } + } +} diff --git a/bc-sharp-crypto/src/crypto/OutputLengthException.cs b/bc-sharp-crypto/src/crypto/OutputLengthException.cs new file mode 100644 index 0000000..437589f --- /dev/null +++ b/bc-sharp-crypto/src/crypto/OutputLengthException.cs @@ -0,0 +1,28 @@ +using System; + +namespace Org.BouncyCastle.Crypto +{ +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT || PORTABLE) + [Serializable] +#endif + public class OutputLengthException + : DataLengthException + { + public OutputLengthException() + { + } + + public OutputLengthException( + string message) + : base(message) + { + } + + public OutputLengthException( + string message, + Exception exception) + : base(message, exception) + { + } + } +} diff --git a/bc-sharp-crypto/src/crypto/PbeParametersGenerator.cs b/bc-sharp-crypto/src/crypto/PbeParametersGenerator.cs new file mode 100644 index 0000000..97d23df --- /dev/null +++ b/bc-sharp-crypto/src/crypto/PbeParametersGenerator.cs @@ -0,0 +1,202 @@ +using System; +using System.Text; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto +{ + /** + * super class for all Password Based Encyrption (Pbe) parameter generator classes. + */ + public abstract class PbeParametersGenerator + { + protected byte[] mPassword; + protected byte[] mSalt; + protected int mIterationCount; + + /** + * 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 virtual void Init( + byte[] password, + byte[] salt, + int iterationCount) + { + if (password == null) + throw new ArgumentNullException("password"); + if (salt == null) + throw new ArgumentNullException("salt"); + + this.mPassword = Arrays.Clone(password); + this.mSalt = Arrays.Clone(salt); + this.mIterationCount = iterationCount; + } + + public virtual byte[] Password + { + get { return Arrays.Clone(mPassword); } + } + + /** + * return the password byte array. + * + * @return the password byte array. + */ + [Obsolete("Use 'Password' property")] + public byte[] GetPassword() + { + return Password; + } + + public virtual byte[] Salt + { + get { return Arrays.Clone(mSalt); } + } + + /** + * return the salt byte array. + * + * @return the salt byte array. + */ + [Obsolete("Use 'Salt' property")] + public byte[] GetSalt() + { + return Salt; + } + + /** + * return the iteration count. + * + * @return the iteration count. + */ + public virtual int IterationCount + { + get { return mIterationCount; } + } + + /** + * 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. + */ + [Obsolete("Use version with 'algorithm' parameter")] + public abstract ICipherParameters GenerateDerivedParameters(int keySize); + public abstract ICipherParameters GenerateDerivedParameters(string algorithm, 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. + */ + [Obsolete("Use version with 'algorithm' parameter")] + public abstract ICipherParameters GenerateDerivedParameters(int keySize, int ivSize); + public abstract ICipherParameters GenerateDerivedParameters(string algorithm, 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 ICipherParameters GenerateDerivedMacParameters(int keySize); + + /** + * converts a password to a byte array according to the scheme in + * Pkcs5 (ascii, no padding) + * + * @param password a character array representing the password. + * @return a byte array representing the password. + */ + public static byte[] Pkcs5PasswordToBytes( + char[] password) + { + if (password == null) + return new byte[0]; + + return Strings.ToByteArray(password); + } + + [Obsolete("Use version taking 'char[]' instead")] + public static byte[] Pkcs5PasswordToBytes( + string password) + { + if (password == null) + return new byte[0]; + + return Strings.ToByteArray(password); + } + + /** + * converts a password to a byte array according to the scheme in + * PKCS5 (UTF-8, no padding) + * + * @param password a character array representing the password. + * @return a byte array representing the password. + */ + public static byte[] Pkcs5PasswordToUtf8Bytes( + char[] password) + { + if (password == null) + return new byte[0]; + + return Encoding.UTF8.GetBytes(password); + } + + [Obsolete("Use version taking 'char[]' instead")] + public static byte[] Pkcs5PasswordToUtf8Bytes( + string password) + { + if (password == null) + return new byte[0]; + + return Encoding.UTF8.GetBytes(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) + { + return Pkcs12PasswordToBytes(password, false); + } + + public static byte[] Pkcs12PasswordToBytes( + char[] password, + bool wrongPkcs12Zero) + { + if (password == null || password.Length < 1) + { + return new byte[wrongPkcs12Zero ? 2 : 0]; + } + + // +1 for extra 2 pad bytes. + byte[] bytes = new byte[(password.Length + 1) * 2]; + + Encoding.BigEndianUnicode.GetBytes(password, 0, password.Length, bytes, 0); + + return bytes; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/StreamBlockCipher.cs b/bc-sharp-crypto/src/crypto/StreamBlockCipher.cs new file mode 100644 index 0000000..ef2a8b6 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/StreamBlockCipher.cs @@ -0,0 +1,109 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; + +namespace Org.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 + : IStreamCipher + { + private readonly IBlockCipher cipher; + private readonly byte[] oneByte = new byte[1]; + + /** + * basic constructor. + * + * @param cipher the block cipher to be wrapped. + * @exception ArgumentException if the cipher has a block size other than + * one. + */ + public StreamBlockCipher( + IBlockCipher cipher) + { + if (cipher == null) + throw new ArgumentNullException("cipher"); + if (cipher.GetBlockSize() != 1) + throw new ArgumentException("block cipher block size != 1.", "cipher"); + + this.cipher = cipher; + } + + /** + * initialise the underlying cipher. + * + * @param forEncryption true if we are setting up for encryption, false otherwise. + * @param param the necessary parameters for the underlying cipher to be initialised. + */ + public void Init( + bool forEncryption, + ICipherParameters parameters) + { + cipher.Init(forEncryption, parameters); + } + + /** + * return the name of the algorithm we are wrapping. + * + * @return the name of the algorithm we are wrapping. + */ + public string AlgorithmName + { + get { return cipher.AlgorithmName; } + } + + /** + * 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 input) + { + oneByte[0] = input; + + 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[] input, + int inOff, + int length, + byte[] output, + int outOff) + { + if (outOff + length > output.Length) + throw new DataLengthException("output buffer too small in ProcessBytes()"); + + for (int i = 0; i != length; i++) + { + cipher.ProcessBlock(input, inOff + i, output, 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/bc-sharp-crypto/src/crypto/agreement/DHAgreement.cs b/bc-sharp-crypto/src/crypto/agreement/DHAgreement.cs new file mode 100644 index 0000000..e988c0d --- /dev/null +++ b/bc-sharp-crypto/src/crypto/agreement/DHAgreement.cs @@ -0,0 +1,99 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Agreement +{ + /** + * 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( + ICipherParameters parameters) + { + AsymmetricKeyParameter kParam; + if (parameters is ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)parameters; + + this.random = rParam.Random; + kParam = (AsymmetricKeyParameter)rParam.Parameters; + } + else + { + this.random = new SecureRandom(); + kParam = (AsymmetricKeyParameter)parameters; + } + + if (!(kParam is DHPrivateKeyParameters)) + { + throw new ArgumentException("DHEngine expects DHPrivateKeyParameters"); + } + + this.key = (DHPrivateKeyParameters)kParam; + this.dhParams = key.Parameters; + } + + /** + * 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.Private).X; + + return ((DHPublicKeyParameters)dhPair.Public).Y; + } + + /** + * 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 == null) + throw new ArgumentNullException("pub"); + if (message == null) + throw new ArgumentNullException("message"); + + if (!pub.Parameters.Equals(dhParams)) + throw new ArgumentException("Diffie-Hellman public key has wrong parameters."); + + BigInteger p = dhParams.P; + + BigInteger peerY = pub.Y; + if (peerY == null || peerY.CompareTo(BigInteger.One) <= 0 || peerY.CompareTo(p.Subtract(BigInteger.One)) >= 0) + throw new ArgumentException("Diffie-Hellman public key is weak"); + + BigInteger result = peerY.ModPow(privateValue, p); + if (result.Equals(BigInteger.One)) + throw new InvalidOperationException("Shared key can't be 1"); + + return message.ModPow(key.X, p).Multiply(result).Mod(p); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/agreement/DHBasicAgreement.cs b/bc-sharp-crypto/src/crypto/agreement/DHBasicAgreement.cs new file mode 100644 index 0000000..6c3fe65 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/agreement/DHBasicAgreement.cs @@ -0,0 +1,72 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Agreement +{ + /** + * 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 + : IBasicAgreement + { + private DHPrivateKeyParameters key; + private DHParameters dhParams; + + public virtual void Init( + ICipherParameters parameters) + { + if (parameters is ParametersWithRandom) + { + parameters = ((ParametersWithRandom) parameters).Parameters; + } + + if (!(parameters is DHPrivateKeyParameters)) + { + throw new ArgumentException("DHEngine expects DHPrivateKeyParameters"); + } + + this.key = (DHPrivateKeyParameters) parameters; + this.dhParams = key.Parameters; + } + + public virtual int GetFieldSize() + { + return (key.Parameters.P.BitLength + 7) / 8; + } + + /** + * given a short term public key from a given party calculate the next + * message in the agreement sequence. + */ + public virtual BigInteger CalculateAgreement( + ICipherParameters pubKey) + { + if (this.key == null) + throw new InvalidOperationException("Agreement algorithm not initialised"); + + DHPublicKeyParameters pub = (DHPublicKeyParameters)pubKey; + + if (!pub.Parameters.Equals(dhParams)) + throw new ArgumentException("Diffie-Hellman public key has wrong parameters."); + + BigInteger p = dhParams.P; + + BigInteger peerY = pub.Y; + if (peerY == null || peerY.CompareTo(BigInteger.One) <= 0 || peerY.CompareTo(p.Subtract(BigInteger.One)) >= 0) + throw new ArgumentException("Diffie-Hellman public key is weak"); + + BigInteger result = peerY.ModPow(key.X, p); + if (result.Equals(BigInteger.One)) + throw new InvalidOperationException("Shared key can't be 1"); + + return result; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/agreement/DHStandardGroups.cs b/bc-sharp-crypto/src/crypto/agreement/DHStandardGroups.cs new file mode 100644 index 0000000..0143c63 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/agreement/DHStandardGroups.cs @@ -0,0 +1,307 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Crypto.Agreement +{ + /// Standard Diffie-Hellman groups from various IETF specifications. + public class DHStandardGroups + { + private static BigInteger FromHex(string hex) + { + return new BigInteger(1, Hex.Decode(hex)); + } + + private static DHParameters FromPG(string hexP, string hexG) + { + return new DHParameters(FromHex(hexP), FromHex(hexG)); + } + + private static DHParameters FromPGQ(string hexP, string hexG, string hexQ) + { + return new DHParameters(FromHex(hexP), FromHex(hexG), FromHex(hexQ)); + } + + private static DHParameters Rfc7919Parameters(string hexP, int l) + { + // NOTE: All the groups in RFC 7919 use safe primes, i.e. q = (p-1)/2, and generator g = 2 + BigInteger p = FromHex(hexP); + return new DHParameters(p, BigInteger.Two, p.ShiftRight(1), l); + } + + /* + * RFC 2409 + */ + private static readonly string rfc2409_768_p = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + + "E485B576625E7EC6F44C42E9A63A3620FFFFFFFFFFFFFFFF"; + private static readonly string rfc2409_768_g = "02"; + public static readonly DHParameters rfc2409_768 = FromPG(rfc2409_768_p, rfc2409_768_g); + + private static readonly string rfc2409_1024_p = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381" + + "FFFFFFFFFFFFFFFF"; + private static readonly string rfc2409_1024_g = "02"; + public static readonly DHParameters rfc2409_1024 = FromPG(rfc2409_1024_p, rfc2409_1024_g); + + /* + * RFC 3526 + */ + private static readonly string rfc3526_1536_p = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF"; + private static readonly string rfc3526_1536_g = "02"; + public static readonly DHParameters rfc3526_1536 = FromPG(rfc3526_1536_p, rfc3526_1536_g); + + private static readonly string rfc3526_2048_p = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" + + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + "15728E5A8AACAA68FFFFFFFFFFFFFFFF"; + private static readonly string rfc3526_2048_g = "02"; + public static readonly DHParameters rfc3526_2048 = FromPG(rfc3526_2048_p, rfc3526_2048_g); + + private static readonly string rfc3526_3072_p = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" + + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" + + "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" + "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" + + "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" + + "43DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF"; + private static readonly string rfc3526_3072_g = "02"; + public static readonly DHParameters rfc3526_3072 = FromPG(rfc3526_3072_p, rfc3526_3072_g); + + private static readonly string rfc3526_4096_p = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" + + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" + + "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" + "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" + + "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" + + "43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7" + "88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA" + + "2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6" + "287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED" + + "1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9" + "93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199" + + "FFFFFFFFFFFFFFFF"; + private static readonly string rfc3526_4096_g = "02"; + public static readonly DHParameters rfc3526_4096 = FromPG(rfc3526_4096_p, rfc3526_4096_g); + + private static readonly string rfc3526_6144_p = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08" + + "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B" + + "302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9" + + "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6" + + "49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8" + + "FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C" + + "180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718" + + "3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D" + + "04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D" + + "B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226" + + "1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC" + + "E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26" + + "99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB" + + "04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2" + + "233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127" + + "D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492" + + "36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406" + + "AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918" + + "DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B33205151" + + "2BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03" + + "F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97F" + + "BEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA" + + "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58B" + + "B7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632" + + "387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E" + + "6DCC4024FFFFFFFFFFFFFFFF"; + private static readonly string rfc3526_6144_g = "02"; + public static readonly DHParameters rfc3526_6144 = FromPG(rfc3526_6144_p, rfc3526_6144_g); + + private static readonly string rfc3526_8192_p = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" + + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" + + "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" + "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" + + "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" + + "43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7" + "88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA" + + "2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6" + "287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED" + + "1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9" + "93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492" + + "36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BD" + "F8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831" + + "179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1B" + "DB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF" + + "5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6" + "D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F3" + + "23A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA" + "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE328" + + "06A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55C" + "DA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE" + + "12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E4" + "38777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300" + + "741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F568" + "3423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD9" + + "22222E04A4037C0713EB57A81A23F0C73473FC646CEA306B" + "4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A" + + "062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A36" + "4597E899A0255DC164F31CC50846851DF9AB48195DED7EA1" + + "B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F92" + "4009438B481C6CD7889A002ED5EE382BC9190DA6FC026E47" + + "9558E4475677E9AA9E3050E2765694DFC81F56E880B96E71" + "60C980DD98EDD3DFFFFFFFFFFFFFFFFF"; + private static readonly string rfc3526_8192_g = "02"; + public static readonly DHParameters rfc3526_8192 = FromPG(rfc3526_8192_p, rfc3526_8192_g); + + /* + * RFC 4306 + */ + public static readonly DHParameters rfc4306_768 = rfc2409_768; + public static readonly DHParameters rfc4306_1024 = rfc2409_1024; + + /* + * RFC 5114 + */ + private static readonly string rfc5114_1024_160_p = "B10B8F96A080E01DDE92DE5EAE5D54EC52C99FBCFB06A3C6" + + "9A6A9DCA52D23B616073E28675A23D189838EF1E2EE652C0" + "13ECB4AEA906112324975C3CD49B83BFACCBDD7D90C4BD70" + + "98488E9C219A73724EFFD6FAE5644738FAA31A4FF55BCCC0" + "A151AF5F0DC8B4BD45BF37DF365C1A65E68CFDA76D4DA708" + + "DF1FB2BC2E4A4371"; + private static readonly string rfc5114_1024_160_g = "A4D1CBD5C3FD34126765A442EFB99905F8104DD258AC507F" + + "D6406CFF14266D31266FEA1E5C41564B777E690F5504F213" + "160217B4B01B886A5E91547F9E2749F4D7FBD7D3B9A92EE1" + + "909D0D2263F80A76A6A24C087A091F531DBF0A0169B6A28A" + "D662A4D18E73AFA32D779D5918D08BC8858F4DCEF97C2A24" + + "855E6EEB22B3B2E5"; + private static readonly string rfc5114_1024_160_q = "F518AA8781A8DF278ABA4E7D64B7CB9D49462353"; + + /// + /// Existence of a "hidden SNFS" backdoor cannot be ruled out. see https://eprint.iacr.org/2016/961.pdf . + /// + [Obsolete("Existence of a 'hidden SNFS' backdoor cannot be ruled out.")] + public static readonly DHParameters rfc5114_1024_160 = FromPGQ(rfc5114_1024_160_p, rfc5114_1024_160_g, + rfc5114_1024_160_q); + + private static readonly string rfc5114_2048_224_p = "AD107E1E9123A9D0D660FAA79559C51FA20D64E5683B9FD1" + + "B54B1597B61D0A75E6FA141DF95A56DBAF9A3C407BA1DF15" + "EB3D688A309C180E1DE6B85A1274A0A66D3F8152AD6AC212" + + "9037C9EDEFDA4DF8D91E8FEF55B7394B7AD5B7D0B6C12207" + "C9F98D11ED34DBF6C6BA0B2C8BBC27BE6A00E0A0B9C49708" + + "B3BF8A317091883681286130BC8985DB1602E714415D9330" + "278273C7DE31EFDC7310F7121FD5A07415987D9ADC0A486D" + + "CDF93ACC44328387315D75E198C641A480CD86A1B9E587E8" + "BE60E69CC928B2B9C52172E413042E9B23F10B0E16E79763" + + "C9B53DCF4BA80A29E3FB73C16B8E75B97EF363E2FFA31F71" + "CF9DE5384E71B81C0AC4DFFE0C10E64F"; + private static readonly string rfc5114_2048_224_g = "AC4032EF4F2D9AE39DF30B5C8FFDAC506CDEBE7B89998CAF" + + "74866A08CFE4FFE3A6824A4E10B9A6F0DD921F01A70C4AFA" + "AB739D7700C29F52C57DB17C620A8652BE5E9001A8D66AD7" + + "C17669101999024AF4D027275AC1348BB8A762D0521BC98A" + "E247150422EA1ED409939D54DA7460CDB5F6C6B250717CBE" + + "F180EB34118E98D119529A45D6F834566E3025E316A330EF" + "BB77A86F0C1AB15B051AE3D428C8F8ACB70A8137150B8EEB" + + "10E183EDD19963DDD9E263E4770589EF6AA21E7F5F2FF381" + "B539CCE3409D13CD566AFBB48D6C019181E1BCFE94B30269" + + "EDFE72FE9B6AA4BD7B5A0F1C71CFFF4C19C418E1F6EC0179" + "81BC087F2A7065B384B890D3191F2BFA"; + private static readonly string rfc5114_2048_224_q = "801C0D34C58D93FE997177101F80535A4738CEBCBF389A99B36371EB"; + + /// + /// Existence of a "hidden SNFS" backdoor cannot be ruled out. see https://eprint.iacr.org/2016/961.pdf . + /// + [Obsolete("Existence of a 'hidden SNFS' backdoor cannot be ruled out.")] + public static readonly DHParameters rfc5114_2048_224 = FromPGQ(rfc5114_2048_224_p, rfc5114_2048_224_g, + rfc5114_2048_224_q); + + private static readonly string rfc5114_2048_256_p = "87A8E61DB4B6663CFFBBD19C651959998CEEF608660DD0F2" + + "5D2CEED4435E3B00E00DF8F1D61957D4FAF7DF4561B2AA30" + "16C3D91134096FAA3BF4296D830E9A7C209E0C6497517ABD" + + "5A8A9D306BCF67ED91F9E6725B4758C022E0B1EF4275BF7B" + "6C5BFC11D45F9088B941F54EB1E59BB8BC39A0BF12307F5C" + + "4FDB70C581B23F76B63ACAE1CAA6B7902D52526735488A0E" + "F13C6D9A51BFA4AB3AD8347796524D8EF6A167B5A41825D9" + + "67E144E5140564251CCACB83E6B486F6B3CA3F7971506026" + "C0B857F689962856DED4010ABD0BE621C3A3960A54E710C3" + + "75F26375D7014103A4B54330C198AF126116D2276E11715F" + "693877FAD7EF09CADB094AE91E1A1597"; + private static readonly string rfc5114_2048_256_g = "3FB32C9B73134D0B2E77506660EDBD484CA7B18F21EF2054" + + "07F4793A1A0BA12510DBC15077BE463FFF4FED4AAC0BB555" + "BE3A6C1B0C6B47B1BC3773BF7E8C6F62901228F8C28CBB18" + + "A55AE31341000A650196F931C77A57F2DDF463E5E9EC144B" + "777DE62AAAB8A8628AC376D282D6ED3864E67982428EBC83" + + "1D14348F6F2F9193B5045AF2767164E1DFC967C1FB3F2E55" + "A4BD1BFFE83B9C80D052B985D182EA0ADB2A3B7313D3FE14" + + "C8484B1E052588B9B7D2BBD2DF016199ECD06E1557CD0915" + "B3353BBB64E0EC377FD028370DF92B52C7891428CDC67EB6" + + "184B523D1DB246C32F63078490F00EF8D647D148D4795451" + "5E2327CFEF98C582664B4C0F6CC41659"; + private static readonly string rfc5114_2048_256_q = "8CF83642A709A097B447997640129DA299B1A47D1EB3750B" + + "A308B0FE64F5FBD3"; + + /// + /// Existence of a "hidden SNFS" backdoor cannot be ruled out. see https://eprint.iacr.org/2016/961.pdf . + /// + [Obsolete("Existence of a 'hidden SNFS' backdoor cannot be ruled out.")] + public static readonly DHParameters rfc5114_2048_256 = FromPGQ(rfc5114_2048_256_p, rfc5114_2048_256_g, + rfc5114_2048_256_q); + + /* + * RFC 5996 + */ + public static readonly DHParameters rfc5996_768 = rfc4306_768; + public static readonly DHParameters rfc5996_1024 = rfc4306_1024; + + /* + * RFC 7919 + */ + private static readonly string rfc7919_ffdhe2048_p = "FFFFFFFFFFFFFFFFADF85458A2BB4A9AAFDC5620273D3CF1" + + "D8B9C583CE2D3695A9E13641146433FBCC939DCE249B3EF9" + "7D2FE363630C75D8F681B202AEC4617AD3DF1ED5D5FD6561" + + "2433F51F5F066ED0856365553DED1AF3B557135E7F57C935" + "984F0C70E0E68B77E2A689DAF3EFE8721DF158A136ADE735" + + "30ACCA4F483A797ABC0AB182B324FB61D108A94BB2C8E3FB" + "B96ADAB760D7F4681D4F42A3DE394DF4AE56EDE76372BB19" + + "0B07A7C8EE0A6D709E02FCE1CDF7E2ECC03404CD28342F61" + "9172FE9CE98583FF8E4F1232EEF28183C3FE3B1B4C6FAD73" + + "3BB5FCBC2EC22005C58EF1837D1683B2C6F34A26C1B2EFFA" + "886B423861285C97FFFFFFFFFFFFFFFF"; + public static readonly DHParameters rfc7919_ffdhe2048 = Rfc7919Parameters(rfc7919_ffdhe2048_p, 225); + + private static readonly string rfc7919_ffdhe3072_p = "FFFFFFFFFFFFFFFFADF85458A2BB4A9AAFDC5620273D3CF1" + + "D8B9C583CE2D3695A9E13641146433FBCC939DCE249B3EF9" + "7D2FE363630C75D8F681B202AEC4617AD3DF1ED5D5FD6561" + + "2433F51F5F066ED0856365553DED1AF3B557135E7F57C935" + "984F0C70E0E68B77E2A689DAF3EFE8721DF158A136ADE735" + + "30ACCA4F483A797ABC0AB182B324FB61D108A94BB2C8E3FB" + "B96ADAB760D7F4681D4F42A3DE394DF4AE56EDE76372BB19" + + "0B07A7C8EE0A6D709E02FCE1CDF7E2ECC03404CD28342F61" + "9172FE9CE98583FF8E4F1232EEF28183C3FE3B1B4C6FAD73" + + "3BB5FCBC2EC22005C58EF1837D1683B2C6F34A26C1B2EFFA" + "886B4238611FCFDCDE355B3B6519035BBC34F4DEF99C0238" + + "61B46FC9D6E6C9077AD91D2691F7F7EE598CB0FAC186D91C" + "AEFE130985139270B4130C93BC437944F4FD4452E2D74DD3" + + "64F2E21E71F54BFF5CAE82AB9C9DF69EE86D2BC522363A0D" + "ABC521979B0DEADA1DBF9A42D5C4484E0ABCD06BFA53DDEF" + + "3C1B20EE3FD59D7C25E41D2B66C62E37FFFFFFFFFFFFFFFF"; + public static readonly DHParameters rfc7919_ffdhe3072 = Rfc7919Parameters(rfc7919_ffdhe3072_p, 275); + + private static readonly string rfc7919_ffdhe4096_p = "FFFFFFFFFFFFFFFFADF85458A2BB4A9AAFDC5620273D3CF1" + + "D8B9C583CE2D3695A9E13641146433FBCC939DCE249B3EF9" + "7D2FE363630C75D8F681B202AEC4617AD3DF1ED5D5FD6561" + + "2433F51F5F066ED0856365553DED1AF3B557135E7F57C935" + "984F0C70E0E68B77E2A689DAF3EFE8721DF158A136ADE735" + + "30ACCA4F483A797ABC0AB182B324FB61D108A94BB2C8E3FB" + "B96ADAB760D7F4681D4F42A3DE394DF4AE56EDE76372BB19" + + "0B07A7C8EE0A6D709E02FCE1CDF7E2ECC03404CD28342F61" + "9172FE9CE98583FF8E4F1232EEF28183C3FE3B1B4C6FAD73" + + "3BB5FCBC2EC22005C58EF1837D1683B2C6F34A26C1B2EFFA" + "886B4238611FCFDCDE355B3B6519035BBC34F4DEF99C0238" + + "61B46FC9D6E6C9077AD91D2691F7F7EE598CB0FAC186D91C" + "AEFE130985139270B4130C93BC437944F4FD4452E2D74DD3" + + "64F2E21E71F54BFF5CAE82AB9C9DF69EE86D2BC522363A0D" + "ABC521979B0DEADA1DBF9A42D5C4484E0ABCD06BFA53DDEF" + + "3C1B20EE3FD59D7C25E41D2B669E1EF16E6F52C3164DF4FB" + "7930E9E4E58857B6AC7D5F42D69F6D187763CF1D55034004" + + "87F55BA57E31CC7A7135C886EFB4318AED6A1E012D9E6832" + "A907600A918130C46DC778F971AD0038092999A333CB8B7A" + + "1A1DB93D7140003C2A4ECEA9F98D0ACC0A8291CDCEC97DCF" + "8EC9B55A7F88A46B4DB5A851F44182E1C68A007E5E655F6A" + + "FFFFFFFFFFFFFFFF"; + public static readonly DHParameters rfc7919_ffdhe4096 = Rfc7919Parameters(rfc7919_ffdhe4096_p, 325); + + private static readonly string rfc7919_ffdhe6144_p = "FFFFFFFFFFFFFFFFADF85458A2BB4A9AAFDC5620273D3CF1" + + "D8B9C583CE2D3695A9E13641146433FBCC939DCE249B3EF9" + "7D2FE363630C75D8F681B202AEC4617AD3DF1ED5D5FD6561" + + "2433F51F5F066ED0856365553DED1AF3B557135E7F57C935" + "984F0C70E0E68B77E2A689DAF3EFE8721DF158A136ADE735" + + "30ACCA4F483A797ABC0AB182B324FB61D108A94BB2C8E3FB" + "B96ADAB760D7F4681D4F42A3DE394DF4AE56EDE76372BB19" + + "0B07A7C8EE0A6D709E02FCE1CDF7E2ECC03404CD28342F61" + "9172FE9CE98583FF8E4F1232EEF28183C3FE3B1B4C6FAD73" + + "3BB5FCBC2EC22005C58EF1837D1683B2C6F34A26C1B2EFFA" + "886B4238611FCFDCDE355B3B6519035BBC34F4DEF99C0238" + + "61B46FC9D6E6C9077AD91D2691F7F7EE598CB0FAC186D91C" + "AEFE130985139270B4130C93BC437944F4FD4452E2D74DD3" + + "64F2E21E71F54BFF5CAE82AB9C9DF69EE86D2BC522363A0D" + "ABC521979B0DEADA1DBF9A42D5C4484E0ABCD06BFA53DDEF" + + "3C1B20EE3FD59D7C25E41D2B669E1EF16E6F52C3164DF4FB" + "7930E9E4E58857B6AC7D5F42D69F6D187763CF1D55034004" + + "87F55BA57E31CC7A7135C886EFB4318AED6A1E012D9E6832" + "A907600A918130C46DC778F971AD0038092999A333CB8B7A" + + "1A1DB93D7140003C2A4ECEA9F98D0ACC0A8291CDCEC97DCF" + "8EC9B55A7F88A46B4DB5A851F44182E1C68A007E5E0DD902" + + "0BFD64B645036C7A4E677D2C38532A3A23BA4442CAF53EA6" + "3BB454329B7624C8917BDD64B1C0FD4CB38E8C334C701C3A" + + "CDAD0657FCCFEC719B1F5C3E4E46041F388147FB4CFDB477" + "A52471F7A9A96910B855322EDB6340D8A00EF092350511E3" + + "0ABEC1FFF9E3A26E7FB29F8C183023C3587E38DA0077D9B4" + "763E4E4B94B2BBC194C6651E77CAF992EEAAC0232A281BF6" + + "B3A739C1226116820AE8DB5847A67CBEF9C9091B462D538C" + "D72B03746AE77F5E62292C311562A846505DC82DB854338A" + + "E49F5235C95B91178CCF2DD5CACEF403EC9D1810C6272B04" + "5B3B71F9DC6B80D63FDD4A8E9ADB1E6962A69526D43161C1" + + "A41D570D7938DAD4A40E329CD0E40E65FFFFFFFFFFFFFFFF"; + public static readonly DHParameters rfc7919_ffdhe6144 = Rfc7919Parameters(rfc7919_ffdhe6144_p, 375); + + private static readonly string rfc7919_ffdhe8192_p = "FFFFFFFFFFFFFFFFADF85458A2BB4A9AAFDC5620273D3CF1" + + "D8B9C583CE2D3695A9E13641146433FBCC939DCE249B3EF9" + "7D2FE363630C75D8F681B202AEC4617AD3DF1ED5D5FD6561" + + "2433F51F5F066ED0856365553DED1AF3B557135E7F57C935" + "984F0C70E0E68B77E2A689DAF3EFE8721DF158A136ADE735" + + "30ACCA4F483A797ABC0AB182B324FB61D108A94BB2C8E3FB" + "B96ADAB760D7F4681D4F42A3DE394DF4AE56EDE76372BB19" + + "0B07A7C8EE0A6D709E02FCE1CDF7E2ECC03404CD28342F61" + "9172FE9CE98583FF8E4F1232EEF28183C3FE3B1B4C6FAD73" + + "3BB5FCBC2EC22005C58EF1837D1683B2C6F34A26C1B2EFFA" + "886B4238611FCFDCDE355B3B6519035BBC34F4DEF99C0238" + + "61B46FC9D6E6C9077AD91D2691F7F7EE598CB0FAC186D91C" + "AEFE130985139270B4130C93BC437944F4FD4452E2D74DD3" + + "64F2E21E71F54BFF5CAE82AB9C9DF69EE86D2BC522363A0D" + "ABC521979B0DEADA1DBF9A42D5C4484E0ABCD06BFA53DDEF" + + "3C1B20EE3FD59D7C25E41D2B669E1EF16E6F52C3164DF4FB" + "7930E9E4E58857B6AC7D5F42D69F6D187763CF1D55034004" + + "87F55BA57E31CC7A7135C886EFB4318AED6A1E012D9E6832" + "A907600A918130C46DC778F971AD0038092999A333CB8B7A" + + "1A1DB93D7140003C2A4ECEA9F98D0ACC0A8291CDCEC97DCF" + "8EC9B55A7F88A46B4DB5A851F44182E1C68A007E5E0DD902" + + "0BFD64B645036C7A4E677D2C38532A3A23BA4442CAF53EA6" + "3BB454329B7624C8917BDD64B1C0FD4CB38E8C334C701C3A" + + "CDAD0657FCCFEC719B1F5C3E4E46041F388147FB4CFDB477" + "A52471F7A9A96910B855322EDB6340D8A00EF092350511E3" + + "0ABEC1FFF9E3A26E7FB29F8C183023C3587E38DA0077D9B4" + "763E4E4B94B2BBC194C6651E77CAF992EEAAC0232A281BF6" + + "B3A739C1226116820AE8DB5847A67CBEF9C9091B462D538C" + "D72B03746AE77F5E62292C311562A846505DC82DB854338A" + + "E49F5235C95B91178CCF2DD5CACEF403EC9D1810C6272B04" + "5B3B71F9DC6B80D63FDD4A8E9ADB1E6962A69526D43161C1" + + "A41D570D7938DAD4A40E329CCFF46AAA36AD004CF600C838" + "1E425A31D951AE64FDB23FCEC9509D43687FEB69EDD1CC5E" + + "0B8CC3BDF64B10EF86B63142A3AB8829555B2F747C932665" + "CB2C0F1CC01BD70229388839D2AF05E454504AC78B758282" + + "2846C0BA35C35F5C59160CC046FD8251541FC68C9C86B022" + "BB7099876A460E7451A8A93109703FEE1C217E6C3826E52C" + + "51AA691E0E423CFC99E9E31650C1217B624816CDAD9A95F9" + "D5B8019488D9C0A0A1FE3075A577E23183F81D4A3F2FA457" + + "1EFC8CE0BA8A4FE8B6855DFE72B0A66EDED2FBABFBE58A30" + "FAFABE1C5D71A87E2F741EF8C1FE86FEA6BBFDE530677F0D" + + "97D11D49F7A8443D0822E506A9F4614E011E2A94838FF88C" + "D68C8BB7C5C6424CFFFFFFFFFFFFFFFF"; + public static readonly DHParameters rfc7919_ffdhe8192 = Rfc7919Parameters(rfc7919_ffdhe8192_p, 400); + } +} diff --git a/bc-sharp-crypto/src/crypto/agreement/ECDHBasicAgreement.cs b/bc-sharp-crypto/src/crypto/agreement/ECDHBasicAgreement.cs new file mode 100644 index 0000000..ca7b3fa --- /dev/null +++ b/bc-sharp-crypto/src/crypto/agreement/ECDHBasicAgreement.cs @@ -0,0 +1,60 @@ +using System; + +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Math.EC; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; + +namespace Org.BouncyCastle.Crypto.Agreement +{ + /** + * 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 + : IBasicAgreement + { + protected internal ECPrivateKeyParameters privKey; + + public virtual void Init( + ICipherParameters parameters) + { + if (parameters is ParametersWithRandom) + { + parameters = ((ParametersWithRandom)parameters).Parameters; + } + + this.privKey = (ECPrivateKeyParameters)parameters; + } + + public virtual int GetFieldSize() + { + return (privKey.Parameters.Curve.FieldSize + 7) / 8; + } + + public virtual BigInteger CalculateAgreement( + ICipherParameters pubKey) + { + ECPublicKeyParameters pub = (ECPublicKeyParameters) pubKey; + if (!pub.Parameters.Equals(privKey.Parameters)) + throw new InvalidOperationException("ECDH public key has wrong domain parameters"); + + ECPoint P = pub.Q.Multiply(privKey.D).Normalize(); + + if (P.IsInfinity) + throw new InvalidOperationException("Infinity is not a valid agreement value for ECDH"); + + return P.AffineXCoord.ToBigInteger(); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/agreement/ECDHCBasicAgreement.cs b/bc-sharp-crypto/src/crypto/agreement/ECDHCBasicAgreement.cs new file mode 100644 index 0000000..1c9ae45 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/agreement/ECDHCBasicAgreement.cs @@ -0,0 +1,68 @@ +using System; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Math.EC; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; + +namespace Org.BouncyCastle.Crypto.Agreement +{ + /** + * 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 + : IBasicAgreement + { + private ECPrivateKeyParameters privKey; + + public virtual void Init( + ICipherParameters parameters) + { + if (parameters is ParametersWithRandom) + { + parameters = ((ParametersWithRandom) parameters).Parameters; + } + + this.privKey = (ECPrivateKeyParameters)parameters; + } + + public virtual int GetFieldSize() + { + return (privKey.Parameters.Curve.FieldSize + 7) / 8; + } + + public virtual BigInteger CalculateAgreement( + ICipherParameters pubKey) + { + ECPublicKeyParameters pub = (ECPublicKeyParameters) pubKey; + ECDomainParameters parameters = pub.Parameters; + if (!parameters.Equals(privKey.Parameters)) + throw new InvalidOperationException("ECDHC public key has wrong domain parameters"); + + BigInteger hd = parameters.H.Multiply(privKey.D).Mod(parameters.N); + + ECPoint P = pub.Q.Multiply(hd).Normalize(); + + if (P.IsInfinity) + throw new InvalidOperationException("Infinity is not a valid agreement value for ECDHC"); + + return P.AffineXCoord.ToBigInteger(); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/agreement/ECDHWithKdfBasicAgreement.cs b/bc-sharp-crypto/src/crypto/agreement/ECDHWithKdfBasicAgreement.cs new file mode 100644 index 0000000..1de80d1 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/agreement/ECDHWithKdfBasicAgreement.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Nist; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Crypto.Agreement.Kdf; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Agreement +{ + public class ECDHWithKdfBasicAgreement + : ECDHBasicAgreement + { + private readonly string algorithm; + private readonly IDerivationFunction kdf; + + public ECDHWithKdfBasicAgreement( + string algorithm, + IDerivationFunction kdf) + { + if (algorithm == null) + throw new ArgumentNullException("algorithm"); + if (kdf == null) + throw new ArgumentNullException("kdf"); + + this.algorithm = algorithm; + this.kdf = kdf; + } + + public override BigInteger CalculateAgreement( + ICipherParameters pubKey) + { + // Note that the ec.KeyAgreement class in JCE only uses kdf in one + // of the engineGenerateSecret methods. + + BigInteger result = base.CalculateAgreement(pubKey); + + int keySize = GeneratorUtilities.GetDefaultKeySize(algorithm); + + DHKdfParameters dhKdfParams = new DHKdfParameters( + new DerObjectIdentifier(algorithm), + keySize, + BigIntToBytes(result)); + + kdf.Init(dhKdfParams); + + byte[] keyBytes = new byte[keySize / 8]; + kdf.GenerateBytes(keyBytes, 0, keyBytes.Length); + + return new BigInteger(1, keyBytes); + } + + private byte[] BigIntToBytes(BigInteger r) + { + int byteLength = X9IntegerConverter.GetByteLength(privKey.Parameters.Curve); + return X9IntegerConverter.IntegerToBytes(r, byteLength); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/agreement/ECMqvBasicAgreement.cs b/bc-sharp-crypto/src/crypto/agreement/ECMqvBasicAgreement.cs new file mode 100644 index 0000000..8d5cebb --- /dev/null +++ b/bc-sharp-crypto/src/crypto/agreement/ECMqvBasicAgreement.cs @@ -0,0 +1,93 @@ +using System; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Math.EC; + +namespace Org.BouncyCastle.Crypto.Agreement +{ + public class ECMqvBasicAgreement + : IBasicAgreement + { + protected internal MqvPrivateParameters privParams; + + public virtual void Init( + ICipherParameters parameters) + { + if (parameters is ParametersWithRandom) + { + parameters = ((ParametersWithRandom)parameters).Parameters; + } + + this.privParams = (MqvPrivateParameters)parameters; + } + + public virtual int GetFieldSize() + { + return (privParams.StaticPrivateKey.Parameters.Curve.FieldSize + 7) / 8; + } + + public virtual BigInteger CalculateAgreement( + ICipherParameters pubKey) + { + MqvPublicParameters pubParams = (MqvPublicParameters)pubKey; + + ECPrivateKeyParameters staticPrivateKey = privParams.StaticPrivateKey; + ECDomainParameters parameters = staticPrivateKey.Parameters; + + if (!parameters.Equals(pubParams.StaticPublicKey.Parameters)) + throw new InvalidOperationException("ECMQV public key components have wrong domain parameters"); + + ECPoint agreement = CalculateMqvAgreement(parameters, staticPrivateKey, + privParams.EphemeralPrivateKey, privParams.EphemeralPublicKey, + pubParams.StaticPublicKey, pubParams.EphemeralPublicKey).Normalize(); + + if (agreement.IsInfinity) + throw new InvalidOperationException("Infinity is not a valid agreement value for MQV"); + + return agreement.AffineXCoord.ToBigInteger(); + } + + // The ECMQV Primitive as described in SEC-1, 3.4 + private static ECPoint CalculateMqvAgreement( + ECDomainParameters parameters, + ECPrivateKeyParameters d1U, + ECPrivateKeyParameters d2U, + ECPublicKeyParameters Q2U, + ECPublicKeyParameters Q1V, + ECPublicKeyParameters Q2V) + { + BigInteger n = parameters.N; + int e = (n.BitLength + 1) / 2; + BigInteger powE = BigInteger.One.ShiftLeft(e); + + ECCurve curve = parameters.Curve; + + ECPoint[] points = new ECPoint[]{ + // The Q2U public key is optional - but will be calculated for us if it wasn't present + ECAlgorithms.ImportPoint(curve, Q2U.Q), + ECAlgorithms.ImportPoint(curve, Q1V.Q), + ECAlgorithms.ImportPoint(curve, Q2V.Q) + }; + + curve.NormalizeAll(points); + + ECPoint q2u = points[0], q1v = points[1], q2v = points[2]; + + BigInteger x = q2u.AffineXCoord.ToBigInteger(); + BigInteger xBar = x.Mod(powE); + BigInteger Q2UBar = xBar.SetBit(e); + BigInteger s = d1U.D.Multiply(Q2UBar).Add(d2U.D).Mod(n); + + BigInteger xPrime = q2v.AffineXCoord.ToBigInteger(); + BigInteger xPrimeBar = xPrime.Mod(powE); + BigInteger Q2VBar = xPrimeBar.SetBit(e); + + BigInteger hs = parameters.H.Multiply(s).Mod(n); + + return ECAlgorithms.SumOfTwoMultiplies( + q1v, Q2VBar.Multiply(hs).Mod(n), q2v, hs); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/agreement/ECMqvWithKdfBasicAgreement.cs b/bc-sharp-crypto/src/crypto/agreement/ECMqvWithKdfBasicAgreement.cs new file mode 100644 index 0000000..7d79fc4 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/agreement/ECMqvWithKdfBasicAgreement.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Nist; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Crypto.Agreement.Kdf; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Agreement +{ + public class ECMqvWithKdfBasicAgreement + : ECMqvBasicAgreement + { + private readonly string algorithm; + private readonly IDerivationFunction kdf; + + public ECMqvWithKdfBasicAgreement( + string algorithm, + IDerivationFunction kdf) + { + if (algorithm == null) + throw new ArgumentNullException("algorithm"); + if (kdf == null) + throw new ArgumentNullException("kdf"); + + this.algorithm = algorithm; + this.kdf = kdf; + } + + public override BigInteger CalculateAgreement( + ICipherParameters pubKey) + { + // Note that the ec.KeyAgreement class in JCE only uses kdf in one + // of the engineGenerateSecret methods. + + BigInteger result = base.CalculateAgreement(pubKey); + + int keySize = GeneratorUtilities.GetDefaultKeySize(algorithm); + + DHKdfParameters dhKdfParams = new DHKdfParameters( + new DerObjectIdentifier(algorithm), + keySize, + BigIntToBytes(result)); + + kdf.Init(dhKdfParams); + + byte[] keyBytes = new byte[keySize / 8]; + kdf.GenerateBytes(keyBytes, 0, keyBytes.Length); + + return new BigInteger(1, keyBytes); + } + + private byte[] BigIntToBytes(BigInteger r) + { + int byteLength = X9IntegerConverter.GetByteLength(privParams.StaticPrivateKey.Parameters.Curve); + return X9IntegerConverter.IntegerToBytes(r, byteLength); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/agreement/jpake/JPakeParticipant.cs b/bc-sharp-crypto/src/crypto/agreement/jpake/JPakeParticipant.cs new file mode 100644 index 0000000..7942848 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/agreement/jpake/JPakeParticipant.cs @@ -0,0 +1,456 @@ +using System; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Agreement.JPake +{ + /// + /// A participant in a Password Authenticated Key Exchange by Juggling (J-PAKE) exchange. + /// + /// The J-PAKE exchange is defined by Feng Hao and Peter Ryan in the paper + /// + /// "Password Authenticated Key Exchange by Juggling, 2008." + /// + /// The J-PAKE protocol is symmetric. + /// There is no notion of a client or server, but rather just two participants. + /// An instance of JPakeParticipant represents one participant, and + /// is the primary interface for executing the exchange. + /// + /// To execute an exchange, construct a JPakeParticipant on each end, + /// and call the following 7 methods + /// (once and only once, in the given order, for each participant, sending messages between them as described): + /// + /// CreateRound1PayloadToSend() - and send the payload to the other participant + /// ValidateRound1PayloadReceived(JPakeRound1Payload) - use the payload received from the other participant + /// CreateRound2PayloadToSend() - and send the payload to the other participant + /// ValidateRound2PayloadReceived(JPakeRound2Payload) - use the payload received from the other participant + /// CalculateKeyingMaterial() + /// CreateRound3PayloadToSend(BigInteger) - and send the payload to the other participant + /// ValidateRound3PayloadReceived(JPakeRound3Payload, BigInteger) - use the payload received from the other participant + /// + /// Each side should derive a session key from the keying material returned by CalculateKeyingMaterial(). + /// The caller is responsible for deriving the session key using a secure key derivation function (KDF). + /// + /// Round 3 is an optional key confirmation process. + /// If you do not execute round 3, then there is no assurance that both participants are using the same key. + /// (i.e. if the participants used different passwords, then their session keys will differ.) + /// + /// If the round 3 validation succeeds, then the keys are guaranteed to be the same on both sides. + /// + /// The symmetric design can easily support the asymmetric cases when one party initiates the communication. + /// e.g. Sometimes the round1 payload and round2 payload may be sent in one pass. + /// Also, in some cases, the key confirmation payload can be sent together with the round2 payload. + /// These are the trivial techniques to optimize the communication. + /// + /// The key confirmation process is implemented as specified in + /// NIST SP 800-56A Revision 1, + /// Section 8.2 Unilateral Key Confirmation for Key Agreement Schemes. + /// + /// This class is stateful and NOT threadsafe. + /// Each instance should only be used for ONE complete J-PAKE exchange + /// (i.e. a new JPakeParticipant should be constructed for each new J-PAKE exchange). + /// + public class JPakeParticipant + { + // Possible internal states. Used for state checking. + public static readonly int STATE_INITIALIZED = 0; + public static readonly int STATE_ROUND_1_CREATED = 10; + public static readonly int STATE_ROUND_1_VALIDATED = 20; + public static readonly int STATE_ROUND_2_CREATED = 30; + public static readonly int STATE_ROUND_2_VALIDATED = 40; + public static readonly int STATE_KEY_CALCULATED = 50; + public static readonly int STATE_ROUND_3_CREATED = 60; + public static readonly int STATE_ROUND_3_VALIDATED = 70; + + // Unique identifier of this participant. + // The two participants in the exchange must NOT share the same id. + private string participantId; + + // Shared secret. This only contains the secret between construction + // and the call to CalculateKeyingMaterial(). + // + // i.e. When CalculateKeyingMaterial() is called, this buffer overwritten with 0's, + // and the field is set to null. + private char[] password; + + // Digest to use during calculations. + private IDigest digest; + + // Source of secure random data. + private readonly SecureRandom random; + + private readonly BigInteger p; + private readonly BigInteger q; + private readonly BigInteger g; + + // The participantId of the other participant in this exchange. + private string partnerParticipantId; + + // Alice's x1 or Bob's x3. + private BigInteger x1; + // Alice's x2 or Bob's x4. + private BigInteger x2; + // Alice's g^x1 or Bob's g^x3. + private BigInteger gx1; + // Alice's g^x2 or Bob's g^x4. + private BigInteger gx2; + // Alice's g^x3 or Bob's g^x1. + private BigInteger gx3; + // Alice's g^x4 or Bob's g^x2. + private BigInteger gx4; + // Alice's B or Bob's A. + private BigInteger b; + + // The current state. + // See the STATE_* constants for possible values. + private int state; + + /// + /// Convenience constructor for a new JPakeParticipant that uses + /// the JPakePrimeOrderGroups#NIST_3072 prime order group, + /// a SHA-256 digest, and a default SecureRandom implementation. + /// + /// After construction, the State state will be STATE_INITIALIZED. + /// + /// Throws NullReferenceException if any argument is null. Throws + /// ArgumentException if password is empty. + /// + /// Unique identifier of this participant. + /// The two participants in the exchange must NOT share the same id. + /// Shared secret. + /// A defensive copy of this array is made (and cleared once CalculateKeyingMaterial() is called). + /// Caller should clear the input password as soon as possible. + public JPakeParticipant(string participantId, char[] password) + : this(participantId, password, JPakePrimeOrderGroups.NIST_3072) { } + + /// + /// Convenience constructor for a new JPakeParticipant that uses + /// a SHA-256 digest, and a default SecureRandom implementation. + /// + /// After construction, the State state will be STATE_INITIALIZED. + /// + /// Throws NullReferenceException if any argument is null. Throws + /// ArgumentException if password is empty. + /// + /// Unique identifier of this participant. + /// The two participants in the exchange must NOT share the same id. + /// Shared secret. + /// A defensive copy of this array is made (and cleared once CalculateKeyingMaterial() is called). + /// Caller should clear the input password as soon as possible. + /// Prime order group. See JPakePrimeOrderGroups for standard groups. + public JPakeParticipant(string participantId, char[] password, JPakePrimeOrderGroup group) + : this(participantId, password, group, new Sha256Digest(), new SecureRandom()) { } + + + /// + /// Constructor for a new JPakeParticipant. + /// + /// After construction, the State state will be STATE_INITIALIZED. + /// + /// Throws NullReferenceException if any argument is null. Throws + /// ArgumentException if password is empty. + /// + /// Unique identifier of this participant. + /// The two participants in the exchange must NOT share the same id. + /// Shared secret. + /// A defensive copy of this array is made (and cleared once CalculateKeyingMaterial() is called). + /// Caller should clear the input password as soon as possible. + /// Prime order group. See JPakePrimeOrderGroups for standard groups. + /// Digest to use during zero knowledge proofs and key confirmation + /// (SHA-256 or stronger preferred). + /// Source of secure random data for x1 and x2, and for the zero knowledge proofs. + public JPakeParticipant(string participantId, char[] password, JPakePrimeOrderGroup group, IDigest digest, SecureRandom random) + { + JPakeUtilities.ValidateNotNull(participantId, "participantId"); + JPakeUtilities.ValidateNotNull(password, "password"); + JPakeUtilities.ValidateNotNull(group, "p"); + JPakeUtilities.ValidateNotNull(digest, "digest"); + JPakeUtilities.ValidateNotNull(random, "random"); + + if (password.Length == 0) + { + throw new ArgumentException("Password must not be empty."); + } + + this.participantId = participantId; + + // Create a defensive copy so as to fully encapsulate the password. + // + // This array will contain the password for the lifetime of this + // participant BEFORE CalculateKeyingMaterial() is called. + // + // i.e. When CalculateKeyingMaterial() is called, the array will be cleared + // in order to remove the password from memory. + // + // The caller is responsible for clearing the original password array + // given as input to this constructor. + this.password = new char[password.Length]; + Array.Copy(password, this.password, password.Length); + + this.p = group.P; + this.q = group.Q; + this.g = group.G; + + this.digest = digest; + this.random = random; + + this.state = STATE_INITIALIZED; + } + + /// + /// Gets the current state of this participant. + /// See the STATE_* constants for possible values. + /// + public virtual int State + { + get { return state; } + } + + + /// + /// Creates and returns the payload to send to the other participant during round 1. + /// + /// After execution, the State state} will be STATE_ROUND_1_CREATED}. + /// + public virtual JPakeRound1Payload CreateRound1PayloadToSend() + { + if (this.state >= STATE_ROUND_1_CREATED) + throw new InvalidOperationException("Round 1 payload already created for " + this.participantId); + + this.x1 = JPakeUtilities.GenerateX1(q, random); + this.x2 = JPakeUtilities.GenerateX2(q, random); + + this.gx1 = JPakeUtilities.CalculateGx(p, g, x1); + this.gx2 = JPakeUtilities.CalculateGx(p, g, x2); + BigInteger[] knowledgeProofForX1 = JPakeUtilities.CalculateZeroKnowledgeProof(p, q, g, gx1, x1, participantId, digest, random); + BigInteger[] knowledgeProofForX2 = JPakeUtilities.CalculateZeroKnowledgeProof(p, q, g, gx2, x2, participantId, digest, random); + + this.state = STATE_ROUND_1_CREATED; + + return new JPakeRound1Payload(participantId, gx1, gx2, knowledgeProofForX1, knowledgeProofForX2); + } + + /// + /// Validates the payload received from the other participant during round 1. + /// + /// Must be called prior to CreateRound2PayloadToSend(). + /// + /// After execution, the State state will be STATE_ROUND_1_VALIDATED. + /// + /// Throws CryptoException if validation fails. Throws InvalidOperationException + /// if called multiple times. + /// + public virtual void ValidateRound1PayloadReceived(JPakeRound1Payload round1PayloadReceived) + { + if (this.state >= STATE_ROUND_1_VALIDATED) + throw new InvalidOperationException("Validation already attempted for round 1 payload for " + this.participantId); + + this.partnerParticipantId = round1PayloadReceived.ParticipantId; + this.gx3 = round1PayloadReceived.Gx1; + this.gx4 = round1PayloadReceived.Gx2; + + BigInteger[] knowledgeProofForX3 = round1PayloadReceived.KnowledgeProofForX1; + BigInteger[] knowledgeProofForX4 = round1PayloadReceived.KnowledgeProofForX2; + + JPakeUtilities.ValidateParticipantIdsDiffer(participantId, round1PayloadReceived.ParticipantId); + JPakeUtilities.ValidateGx4(gx4); + JPakeUtilities.ValidateZeroKnowledgeProof(p, q, g, gx3, knowledgeProofForX3, round1PayloadReceived.ParticipantId, digest); + JPakeUtilities.ValidateZeroKnowledgeProof(p, q, g, gx4, knowledgeProofForX4, round1PayloadReceived.ParticipantId, digest); + this.state = STATE_ROUND_1_VALIDATED; + } + + /// + /// Creates and returns the payload to send to the other participant during round 2. + /// + /// ValidateRound1PayloadReceived(JPakeRound1Payload) must be called prior to this method. + /// + /// After execution, the State state will be STATE_ROUND_2_CREATED. + /// + /// Throws InvalidOperationException if called prior to ValidateRound1PayloadReceived(JPakeRound1Payload), or multiple times + /// + public virtual JPakeRound2Payload CreateRound2PayloadToSend() + { + if (this.state >= STATE_ROUND_2_CREATED) + throw new InvalidOperationException("Round 2 payload already created for " + this.participantId); + if (this.state < STATE_ROUND_1_VALIDATED) + throw new InvalidOperationException("Round 1 payload must be validated prior to creating round 2 payload for " + this.participantId); + + BigInteger gA = JPakeUtilities.CalculateGA(p, gx1, gx3, gx4); + BigInteger s = JPakeUtilities.CalculateS(password); + BigInteger x2s = JPakeUtilities.CalculateX2s(q, x2, s); + BigInteger A = JPakeUtilities.CalculateA(p, q, gA, x2s); + BigInteger[] knowledgeProofForX2s = JPakeUtilities.CalculateZeroKnowledgeProof(p, q, gA, A, x2s, participantId, digest, random); + + this.state = STATE_ROUND_2_CREATED; + + return new JPakeRound2Payload(participantId, A, knowledgeProofForX2s); + } + + /// + /// Validates the payload received from the other participant during round 2. + /// Note that this DOES NOT detect a non-common password. + /// The only indication of a non-common password is through derivation + /// of different keys (which can be detected explicitly by executing round 3 and round 4) + /// + /// Must be called prior to CalculateKeyingMaterial(). + /// + /// After execution, the State state will be STATE_ROUND_2_VALIDATED. + /// + /// Throws CryptoException if validation fails. Throws + /// InvalidOperationException if called prior to ValidateRound1PayloadReceived(JPakeRound1Payload), or multiple times + /// + public virtual void ValidateRound2PayloadReceived(JPakeRound2Payload round2PayloadReceived) + { + if (this.state >= STATE_ROUND_2_VALIDATED) + throw new InvalidOperationException("Validation already attempted for round 2 payload for " + this.participantId); + if (this.state < STATE_ROUND_1_VALIDATED) + throw new InvalidOperationException("Round 1 payload must be validated prior to validation round 2 payload for " + this.participantId); + + BigInteger gB = JPakeUtilities.CalculateGA(p, gx3, gx1, gx2); + this.b = round2PayloadReceived.A; + BigInteger[] knowledgeProofForX4s = round2PayloadReceived.KnowledgeProofForX2s; + + JPakeUtilities.ValidateParticipantIdsDiffer(participantId, round2PayloadReceived.ParticipantId); + JPakeUtilities.ValidateParticipantIdsEqual(this.partnerParticipantId, round2PayloadReceived.ParticipantId); + JPakeUtilities.ValidateGa(gB); + JPakeUtilities.ValidateZeroKnowledgeProof(p, q, gB, b, knowledgeProofForX4s, round2PayloadReceived.ParticipantId, digest); + + this.state = STATE_ROUND_2_VALIDATED; + } + + /// + /// Calculates and returns the key material. + /// A session key must be derived from this key material using a secure key derivation function (KDF). + /// The KDF used to derive the key is handled externally (i.e. not by JPakeParticipant). + /// + /// The keying material will be identical for each participant if and only if + /// each participant's password is the same. i.e. If the participants do not + /// share the same password, then each participant will derive a different key. + /// Therefore, if you immediately start using a key derived from + /// the keying material, then you must handle detection of incorrect keys. + /// If you want to handle this detection explicitly, you can optionally perform + /// rounds 3 and 4. See JPakeParticipant for details on how to execute + /// rounds 3 and 4. + /// + /// The keying material will be in the range [0, p-1]. + /// + /// ValidateRound2PayloadReceived(JPakeRound2Payload) must be called prior to this method. + /// + /// As a side effect, the internal password array is cleared, since it is no longer needed. + /// + /// After execution, the State state will be STATE_KEY_CALCULATED. + /// + /// Throws InvalidOperationException if called prior to ValidateRound2PayloadReceived(JPakeRound2Payload), + /// or if called multiple times. + /// + public virtual BigInteger CalculateKeyingMaterial() + { + if (this.state >= STATE_KEY_CALCULATED) + throw new InvalidOperationException("Key already calculated for " + participantId); + if (this.state < STATE_ROUND_2_VALIDATED) + throw new InvalidOperationException("Round 2 payload must be validated prior to creating key for " + participantId); + + BigInteger s = JPakeUtilities.CalculateS(password); + + // Clear the password array from memory, since we don't need it anymore. + // Also set the field to null as a flag to indicate that the key has already been calculated. + Array.Clear(password, 0, password.Length); + this.password = null; + + BigInteger keyingMaterial = JPakeUtilities.CalculateKeyingMaterial(p, q, gx4, x2, s, b); + + // Clear the ephemeral private key fields as well. + // Note that we're relying on the garbage collector to do its job to clean these up. + // The old objects will hang around in memory until the garbage collector destroys them. + // + // If the ephemeral private keys x1 and x2 are leaked, + // the attacker might be able to brute-force the password. + this.x1 = null; + this.x2 = null; + this.b = null; + + // Do not clear gx* yet, since those are needed by round 3. + + this.state = STATE_KEY_CALCULATED; + + return keyingMaterial; + } + + /// + /// Creates and returns the payload to send to the other participant during round 3. + /// + /// See JPakeParticipant for more details on round 3. + /// + /// After execution, the State state} will be STATE_ROUND_3_CREATED. + /// Throws InvalidOperationException if called prior to CalculateKeyingMaterial, or multiple + /// times. + /// + /// The keying material as returned from CalculateKeyingMaterial(). + public virtual JPakeRound3Payload CreateRound3PayloadToSend(BigInteger keyingMaterial) + { + if (this.state >= STATE_ROUND_3_CREATED) + throw new InvalidOperationException("Round 3 payload already created for " + this.participantId); + if (this.state < STATE_KEY_CALCULATED) + throw new InvalidOperationException("Keying material must be calculated prior to creating round 3 payload for " + this.participantId); + + BigInteger macTag = JPakeUtilities.CalculateMacTag( + this.participantId, + this.partnerParticipantId, + this.gx1, + this.gx2, + this.gx3, + this.gx4, + keyingMaterial, + this.digest); + + this.state = STATE_ROUND_3_CREATED; + + return new JPakeRound3Payload(participantId, macTag); + } + + /// + /// Validates the payload received from the other participant during round 3. + /// + /// See JPakeParticipant for more details on round 3. + /// + /// After execution, the State state will be STATE_ROUND_3_VALIDATED. + /// + /// Throws CryptoException if validation fails. Throws InvalidOperationException if called prior to + /// CalculateKeyingMaterial or multiple times + /// + /// The round 3 payload received from the other participant. + /// The keying material as returned from CalculateKeyingMaterial(). + public virtual void ValidateRound3PayloadReceived(JPakeRound3Payload round3PayloadReceived, BigInteger keyingMaterial) + { + if (this.state >= STATE_ROUND_3_VALIDATED) + throw new InvalidOperationException("Validation already attempted for round 3 payload for " + this.participantId); + if (this.state < STATE_KEY_CALCULATED) + throw new InvalidOperationException("Keying material must be calculated prior to validating round 3 payload for " + this.participantId); + + JPakeUtilities.ValidateParticipantIdsDiffer(participantId, round3PayloadReceived.ParticipantId); + JPakeUtilities.ValidateParticipantIdsEqual(this.partnerParticipantId, round3PayloadReceived.ParticipantId); + + JPakeUtilities.ValidateMacTag( + this.participantId, + this.partnerParticipantId, + this.gx1, + this.gx2, + this.gx3, + this.gx4, + keyingMaterial, + this.digest, + round3PayloadReceived.MacTag); + + // Clear the rest of the fields. + this.gx1 = null; + this.gx2 = null; + this.gx3 = null; + this.gx4 = null; + + this.state = STATE_ROUND_3_VALIDATED; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/agreement/jpake/JPakePrimeOrderGroup.cs b/bc-sharp-crypto/src/crypto/agreement/jpake/JPakePrimeOrderGroup.cs new file mode 100644 index 0000000..08ffe1a --- /dev/null +++ b/bc-sharp-crypto/src/crypto/agreement/jpake/JPakePrimeOrderGroup.cs @@ -0,0 +1,103 @@ +using System; + +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Crypto.Agreement.JPake +{ + /// + /// A pre-computed prime order group for use during a J-PAKE exchange. + /// + /// Typically a Schnorr group is used. In general, J-PAKE can use any prime order group + /// that is suitable for public key cryptography, including elliptic curve cryptography. + /// + /// See JPakePrimeOrderGroups for convenient standard groups. + /// + /// NIST publishes + /// many groups that can be used for the desired level of security. + /// + public class JPakePrimeOrderGroup + { + private readonly BigInteger p; + private readonly BigInteger q; + private readonly BigInteger g; + + /// + /// Constructs a new JPakePrimeOrderGroup. + /// + /// In general, you should use one of the pre-approved groups from + /// JPakePrimeOrderGroups, rather than manually constructing one. + /// + /// The following basic checks are performed: + /// + /// p-1 must be evenly divisible by q + /// g must be in [2, p-1] + /// g^q mod p must equal 1 + /// p must be prime (within reasonably certainty) + /// q must be prime (within reasonably certainty) + /// + /// The prime checks are performed using BigInteger#isProbablePrime(int), + /// and are therefore subject to the same probability guarantees. + /// + /// These checks prevent trivial mistakes. + /// However, due to the small uncertainties if p and q are not prime, + /// advanced attacks are not prevented. + /// Use it at your own risk. + /// + /// Throws NullReferenceException if any argument is null. Throws + /// InvalidOperationException is any of the above validations fail. + /// + public JPakePrimeOrderGroup(BigInteger p, BigInteger q, BigInteger g) + : this(p, q, g, false) + { + // Don't skip the checks on user-specified groups. + } + + /// + /// Constructor used by the pre-approved groups in JPakePrimeOrderGroups. + /// These pre-approved groups can avoid the expensive checks. + /// User-specified groups should not use this constructor. + /// + public JPakePrimeOrderGroup(BigInteger p, BigInteger q, BigInteger g, bool skipChecks) + { + JPakeUtilities.ValidateNotNull(p, "p"); + JPakeUtilities.ValidateNotNull(q, "q"); + JPakeUtilities.ValidateNotNull(g, "g"); + + if (!skipChecks) + { + if (!p.Subtract(JPakeUtilities.One).Mod(q).Equals(JPakeUtilities.Zero)) + throw new ArgumentException("p-1 must be evenly divisible by q"); + if (g.CompareTo(BigInteger.Two) == -1 || g.CompareTo(p.Subtract(JPakeUtilities.One)) == 1) + throw new ArgumentException("g must be in [2, p-1]"); + if (!g.ModPow(q, p).Equals(JPakeUtilities.One)) + throw new ArgumentException("g^q mod p must equal 1"); + + // Note these checks do not guarantee that p and q are prime. + // We just have reasonable certainty that they are prime. + if (!p.IsProbablePrime(20)) + throw new ArgumentException("p must be prime"); + if (!q.IsProbablePrime(20)) + throw new ArgumentException("q must be prime"); + } + + this.p = p; + this.q = q; + this.g = g; + } + + public virtual BigInteger P + { + get { return p; } + } + + public virtual BigInteger Q + { + get { return q; } + } + + public virtual BigInteger G + { + get { return g; } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/agreement/jpake/JPakePrimeOrderGroups.cs b/bc-sharp-crypto/src/crypto/agreement/jpake/JPakePrimeOrderGroups.cs new file mode 100644 index 0000000..192cd2b --- /dev/null +++ b/bc-sharp-crypto/src/crypto/agreement/jpake/JPakePrimeOrderGroups.cs @@ -0,0 +1,108 @@ +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Crypto.Agreement.JPake +{ + /// + /// Standard pre-computed prime order groups for use by J-PAKE. + /// (J-PAKE can use pre-computed prime order groups, same as DSA and Diffie-Hellman.) + ///

+ /// This class contains some convenient constants for use as input for + /// constructing {@link JPAKEParticipant}s. + ///

+ /// The prime order groups below are taken from Sun's JDK JavaDoc (docs/guide/security/CryptoSpec.html#AppB), + /// and from the prime order groups + /// published by NIST. + ///

+ public class JPakePrimeOrderGroups + { + /// + /// From Sun's JDK JavaDoc (docs/guide/security/CryptoSpec.html#AppB) + /// 1024-bit p, 160-bit q and 1024-bit g for 80-bit security. + /// + public static readonly JPakePrimeOrderGroup SUN_JCE_1024 = new JPakePrimeOrderGroup( + // p + new BigInteger( + "fd7f53811d75122952df4a9c2eece4e7f611b7523cef4400c31e3f80b6512669" + + "455d402251fb593d8d58fabfc5f5ba30f6cb9b556cd7813b801d346ff26660b7" + + "6b9950a5a49f9fe8047b1022c24fbba9d7feb7c61bf83b57e7c6a8a6150f04fb" + + "83f6d3c51ec3023554135a169132f675f3ae2b61d72aeff22203199dd14801c7", 16), + // q + new BigInteger("9760508f15230bccb292b982a2eb840bf0581cf5", 16), + // g + new BigInteger( + "f7e1a085d69b3ddecbbcab5c36b857b97994afbbfa3aea82f9574c0b3d078267" + + "5159578ebad4594fe67107108180b449167123e84c281613b7cf09328cc8a6e1" + + "3c167a8b547c8d28e0a3ae1e2bb3a675916ea37f0bfa213562f1fb627a01243b" + + "cca4f1bea8519089a883dfe15ae59f06928b665e807b552564014c3bfecf492a", 16), + true + ); + + /// + /// From NIST. + /// 2048-bit p, 224-bit q and 2048-bit g for 112-bit security. + /// + public static readonly JPakePrimeOrderGroup NIST_2048 = new JPakePrimeOrderGroup( + // p + new BigInteger( + "C196BA05AC29E1F9C3C72D56DFFC6154A033F1477AC88EC37F09BE6C5BB95F51" + + "C296DD20D1A28A067CCC4D4316A4BD1DCA55ED1066D438C35AEBAABF57E7DAE4" + + "28782A95ECA1C143DB701FD48533A3C18F0FE23557EA7AE619ECACC7E0B51652" + + "A8776D02A425567DED36EABD90CA33A1E8D988F0BBB92D02D1D20290113BB562" + + "CE1FC856EEB7CDD92D33EEA6F410859B179E7E789A8F75F645FAE2E136D252BF" + + "FAFF89528945C1ABE705A38DBC2D364AADE99BE0D0AAD82E5320121496DC65B3" + + "930E38047294FF877831A16D5228418DE8AB275D7D75651CEFED65F78AFC3EA7" + + "FE4D79B35F62A0402A1117599ADAC7B269A59F353CF450E6982D3B1702D9CA83", 16), + // q + new BigInteger("90EAF4D1AF0708B1B612FF35E0A2997EB9E9D263C9CE659528945C0D", 16), + // g + new BigInteger( + "A59A749A11242C58C894E9E5A91804E8FA0AC64B56288F8D47D51B1EDC4D6544" + + "4FECA0111D78F35FC9FDD4CB1F1B79A3BA9CBEE83A3F811012503C8117F98E50" + + "48B089E387AF6949BF8784EBD9EF45876F2E6A5A495BE64B6E770409494B7FEE" + + "1DBB1E4B2BC2A53D4F893D418B7159592E4FFFDF6969E91D770DAEBD0B5CB14C" + + "00AD68EC7DC1E5745EA55C706C4A1C5C88964E34D09DEB753AD418C1AD0F4FDF" + + "D049A955E5D78491C0B7A2F1575A008CCD727AB376DB6E695515B05BD412F5B8" + + "C2F4C77EE10DA48ABD53F5DD498927EE7B692BBBCDA2FB23A516C5B4533D7398" + + "0B2A3B60E384ED200AE21B40D273651AD6060C13D97FD69AA13C5611A51B9085", 16), + true + ); + + /// + /// From NIST. + /// 3072-bit p, 256-bit q and 3072-bit g for 128-bit security. + /// + public static readonly JPakePrimeOrderGroup NIST_3072 = new JPakePrimeOrderGroup( + // p + new BigInteger( + "90066455B5CFC38F9CAA4A48B4281F292C260FEEF01FD61037E56258A7795A1C" + + "7AD46076982CE6BB956936C6AB4DCFE05E6784586940CA544B9B2140E1EB523F" + + "009D20A7E7880E4E5BFA690F1B9004A27811CD9904AF70420EEFD6EA11EF7DA1" + + "29F58835FF56B89FAA637BC9AC2EFAAB903402229F491D8D3485261CD068699B" + + "6BA58A1DDBBEF6DB51E8FE34E8A78E542D7BA351C21EA8D8F1D29F5D5D159394" + + "87E27F4416B0CA632C59EFD1B1EB66511A5A0FBF615B766C5862D0BD8A3FE7A0" + + "E0DA0FB2FE1FCB19E8F9996A8EA0FCCDE538175238FC8B0EE6F29AF7F642773E" + + "BE8CD5402415A01451A840476B2FCEB0E388D30D4B376C37FE401C2A2C2F941D" + + "AD179C540C1C8CE030D460C4D983BE9AB0B20F69144C1AE13F9383EA1C08504F" + + "B0BF321503EFE43488310DD8DC77EC5B8349B8BFE97C2C560EA878DE87C11E3D" + + "597F1FEA742D73EEC7F37BE43949EF1A0D15C3F3E3FC0A8335617055AC91328E" + + "C22B50FC15B941D3D1624CD88BC25F3E941FDDC6200689581BFEC416B4B2CB73", 16), + // q + new BigInteger("CFA0478A54717B08CE64805B76E5B14249A77A4838469DF7F7DC987EFCCFB11D", 16), + // g + new BigInteger( + "5E5CBA992E0A680D885EB903AEA78E4A45A469103D448EDE3B7ACCC54D521E37" + + "F84A4BDD5B06B0970CC2D2BBB715F7B82846F9A0C393914C792E6A923E2117AB" + + "805276A975AADB5261D91673EA9AAFFEECBFA6183DFCB5D3B7332AA19275AFA1" + + "F8EC0B60FB6F66CC23AE4870791D5982AAD1AA9485FD8F4A60126FEB2CF05DB8" + + "A7F0F09B3397F3937F2E90B9E5B9C9B6EFEF642BC48351C46FB171B9BFA9EF17" + + "A961CE96C7E7A7CC3D3D03DFAD1078BA21DA425198F07D2481622BCE45969D9C" + + "4D6063D72AB7A0F08B2F49A7CC6AF335E08C4720E31476B67299E231F8BD90B3" + + "9AC3AE3BE0C6B6CACEF8289A2E2873D58E51E029CAFBD55E6841489AB66B5B4B" + + "9BA6E2F784660896AFF387D92844CCB8B69475496DE19DA2E58259B090489AC8" + + "E62363CDF82CFD8EF2A427ABCD65750B506F56DDE3B988567A88126B914D7828" + + "E2B63A6D7ED0747EC59E0E0A23CE7D8A74C1D2C2A7AFB6A29799620F00E11C33" + + "787F7DED3B30E1A22D09F1FBDA1ABBBFBF25CAE05A13F812E34563F99410E73B", 16), + true + ); + } +} diff --git a/bc-sharp-crypto/src/crypto/agreement/jpake/JPakeRound1Payload.cs b/bc-sharp-crypto/src/crypto/agreement/jpake/JPakeRound1Payload.cs new file mode 100644 index 0000000..9e4ab7a --- /dev/null +++ b/bc-sharp-crypto/src/crypto/agreement/jpake/JPakeRound1Payload.cs @@ -0,0 +1,101 @@ +using System; + +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Crypto.Agreement.JPake +{ + /// + /// The payload sent/received during the first round of a J-PAKE exchange. + /// + /// Each JPAKEParticipant creates and sends an instance of this payload to + /// the other. The payload to send should be created via + /// JPAKEParticipant.CreateRound1PayloadToSend(). + /// + /// Each participant must also validate the payload received from the other. + /// The received payload should be validated via + /// JPAKEParticipant.ValidateRound1PayloadReceived(JPakeRound1Payload). + /// + public class JPakeRound1Payload + { + /// + /// The id of the JPAKEParticipant who created/sent this payload. + /// + private readonly string participantId; + + /// + /// The value of g^x1 + /// + private readonly BigInteger gx1; + + /// + /// The value of g^x2 + /// + private readonly BigInteger gx2; + + /// + /// The zero knowledge proof for x1. + /// + /// This is a two element array, containing {g^v, r} for x1. + /// + private readonly BigInteger[] knowledgeProofForX1; + + /// + /// The zero knowledge proof for x2. + /// + /// This is a two element array, containing {g^v, r} for x2. + /// + private readonly BigInteger[] knowledgeProofForX2; + + public JPakeRound1Payload(string participantId, BigInteger gx1, BigInteger gx2, BigInteger[] knowledgeProofForX1, BigInteger[] knowledgeProofForX2) + { + JPakeUtilities.ValidateNotNull(participantId, "participantId"); + JPakeUtilities.ValidateNotNull(gx1, "gx1"); + JPakeUtilities.ValidateNotNull(gx2, "gx2"); + JPakeUtilities.ValidateNotNull(knowledgeProofForX1, "knowledgeProofForX1"); + JPakeUtilities.ValidateNotNull(knowledgeProofForX2, "knowledgeProofForX2"); + + this.participantId = participantId; + this.gx1 = gx1; + this.gx2 = gx2; + this.knowledgeProofForX1 = new BigInteger[knowledgeProofForX1.Length]; + Array.Copy(knowledgeProofForX1, this.knowledgeProofForX1, knowledgeProofForX1.Length); + this.knowledgeProofForX2 = new BigInteger[knowledgeProofForX2.Length]; + Array.Copy(knowledgeProofForX2, this.knowledgeProofForX2, knowledgeProofForX2.Length); + } + + public virtual string ParticipantId + { + get { return participantId; } + } + + public virtual BigInteger Gx1 + { + get { return gx1; } + } + + public virtual BigInteger Gx2 + { + get { return gx2; } + } + + public virtual BigInteger[] KnowledgeProofForX1 + { + get + { + BigInteger[] kp = new BigInteger[knowledgeProofForX1.Length]; + Array.Copy(knowledgeProofForX1, kp, knowledgeProofForX1.Length); + return kp; + } + } + + public virtual BigInteger[] KnowledgeProofForX2 + { + get + { + BigInteger[] kp = new BigInteger[knowledgeProofForX2.Length]; + Array.Copy(knowledgeProofForX2, kp, knowledgeProofForX2.Length); + return kp; + } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/agreement/jpake/JPakeRound2Payload.cs b/bc-sharp-crypto/src/crypto/agreement/jpake/JPakeRound2Payload.cs new file mode 100644 index 0000000..47962cb --- /dev/null +++ b/bc-sharp-crypto/src/crypto/agreement/jpake/JPakeRound2Payload.cs @@ -0,0 +1,72 @@ +using System; + +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Agreement.JPake +{ + /// + /// The payload sent/received during the second round of a J-PAKE exchange. + /// + /// Each JPAKEParticipant creates and sends an instance + /// of this payload to the other JPAKEParticipant. + /// The payload to send should be created via + /// JPAKEParticipant#createRound2PayloadToSend() + /// + /// Each JPAKEParticipant must also validate the payload + /// received from the other JPAKEParticipant. + /// The received payload should be validated via + /// JPAKEParticipant#validateRound2PayloadReceived(JPakeRound2Payload) + /// + public class JPakeRound2Payload + { + /// + /// The id of the JPAKEParticipant who created/sent this payload. + /// + private readonly string participantId; + + /// + /// The value of A, as computed during round 2. + /// + private readonly BigInteger a; + + /// + /// The zero knowledge proof for x2 * s. + /// + /// This is a two element array, containing {g^v, r} for x2 * s. + /// + private readonly BigInteger[] knowledgeProofForX2s; + + public JPakeRound2Payload(string participantId, BigInteger a, BigInteger[] knowledgeProofForX2s) + { + JPakeUtilities.ValidateNotNull(participantId, "participantId"); + JPakeUtilities.ValidateNotNull(a, "a"); + JPakeUtilities.ValidateNotNull(knowledgeProofForX2s, "knowledgeProofForX2s"); + + this.participantId = participantId; + this.a = a; + this.knowledgeProofForX2s = new BigInteger[knowledgeProofForX2s.Length]; + knowledgeProofForX2s.CopyTo(this.knowledgeProofForX2s, 0); + } + + public virtual string ParticipantId + { + get { return participantId; } + } + + public virtual BigInteger A + { + get { return a; } + } + + public virtual BigInteger[] KnowledgeProofForX2s + { + get + { + BigInteger[] kp = new BigInteger[knowledgeProofForX2s.Length]; + Array.Copy(knowledgeProofForX2s, kp, knowledgeProofForX2s.Length); + return kp; + } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/agreement/jpake/JPakeRound3Payload.cs b/bc-sharp-crypto/src/crypto/agreement/jpake/JPakeRound3Payload.cs new file mode 100644 index 0000000..767702f --- /dev/null +++ b/bc-sharp-crypto/src/crypto/agreement/jpake/JPakeRound3Payload.cs @@ -0,0 +1,51 @@ +using System; + +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Crypto.Agreement.JPake +{ + /// + /// The payload sent/received during the optional third round of a J-PAKE exchange, + /// which is for explicit key confirmation. + /// + /// Each JPAKEParticipant creates and sends an instance + /// of this payload to the other JPAKEParticipant. + /// The payload to send should be created via + /// JPAKEParticipant#createRound3PayloadToSend(BigInteger) + /// + /// Eeach JPAKEParticipant must also validate the payload + /// received from the other JPAKEParticipant. + /// The received payload should be validated via + /// JPAKEParticipant#validateRound3PayloadReceived(JPakeRound3Payload, BigInteger) + /// + public class JPakeRound3Payload + { + /// + /// The id of the {@link JPAKEParticipant} who created/sent this payload. + /// + private readonly string participantId; + + /// + /// The value of MacTag, as computed by round 3. + /// + /// See JPAKEUtil#calculateMacTag(string, string, BigInteger, BigInteger, BigInteger, BigInteger, BigInteger, org.bouncycastle.crypto.Digest) + /// + private readonly BigInteger macTag; + + public JPakeRound3Payload(string participantId, BigInteger magTag) + { + this.participantId = participantId; + this.macTag = magTag; + } + + public virtual string ParticipantId + { + get { return participantId; } + } + + public virtual BigInteger MacTag + { + get { return macTag; } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/agreement/jpake/JPakeUtilities.cs b/bc-sharp-crypto/src/crypto/agreement/jpake/JPakeUtilities.cs new file mode 100644 index 0000000..b23518a --- /dev/null +++ b/bc-sharp-crypto/src/crypto/agreement/jpake/JPakeUtilities.cs @@ -0,0 +1,390 @@ +using System; +using System.Text; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Macs; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Utilities; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Agreement.JPake +{ + /// + /// Primitives needed for a J-PAKE exchange. + /// + /// The recommended way to perform a J-PAKE exchange is by using + /// two JPAKEParticipants. Internally, those participants + /// call these primitive operations in JPakeUtilities. + /// + /// The primitives, however, can be used without a JPAKEParticipant if needed. + /// + public abstract class JPakeUtilities + { + public static readonly BigInteger Zero = BigInteger.Zero; + public static readonly BigInteger One = BigInteger.One; + + /// + /// Return a value that can be used as x1 or x3 during round 1. + /// The returned value is a random value in the range [0, q-1]. + /// + public static BigInteger GenerateX1(BigInteger q, SecureRandom random) + { + BigInteger min = Zero; + BigInteger max = q.Subtract(One); + return BigIntegers.CreateRandomInRange(min, max, random); + } + + /// + /// Return a value that can be used as x2 or x4 during round 1. + /// The returned value is a random value in the range [1, q-1]. + /// + public static BigInteger GenerateX2(BigInteger q, SecureRandom random) + { + BigInteger min = One; + BigInteger max = q.Subtract(One); + return BigIntegers.CreateRandomInRange(min, max, random); + } + + /// + /// Converts the given password to a BigInteger + /// for use in arithmetic calculations. + /// + public static BigInteger CalculateS(char[] password) + { + return new BigInteger(Encoding.UTF8.GetBytes(password)); + } + + /// + /// Calculate g^x mod p as done in round 1. + /// + public static BigInteger CalculateGx(BigInteger p, BigInteger g, BigInteger x) + { + return g.ModPow(x, p); + } + + /// + /// Calculate ga as done in round 2. + /// + public static BigInteger CalculateGA(BigInteger p, BigInteger gx1, BigInteger gx3, BigInteger gx4) + { + // ga = g^(x1+x3+x4) = g^x1 * g^x3 * g^x4 + return gx1.Multiply(gx3).Multiply(gx4).Mod(p); + } + + /// + /// Calculate x2 * s as done in round 2. + /// + public static BigInteger CalculateX2s(BigInteger q, BigInteger x2, BigInteger s) + { + return x2.Multiply(s).Mod(q); + } + + /// + /// Calculate A as done in round 2. + /// + public static BigInteger CalculateA(BigInteger p, BigInteger q, BigInteger gA, BigInteger x2s) + { + // A = ga^(x*s) + return gA.ModPow(x2s, p); + } + + /// + /// Calculate a zero knowledge proof of x using Schnorr's signature. + /// The returned array has two elements {g^v, r = v-x*h} for x. + /// + public static BigInteger[] CalculateZeroKnowledgeProof(BigInteger p, BigInteger q, BigInteger g, + BigInteger gx, BigInteger x, string participantId, IDigest digest, SecureRandom random) + { + /* Generate a random v, and compute g^v */ + BigInteger vMin = Zero; + BigInteger vMax = q.Subtract(One); + BigInteger v = BigIntegers.CreateRandomInRange(vMin, vMax, random); + + BigInteger gv = g.ModPow(v, p); + BigInteger h = CalculateHashForZeroKnowledgeProof(g, gv, gx, participantId, digest); // h + + return new BigInteger[] + { + gv, + v.Subtract(x.Multiply(h)).Mod(q) // r = v-x*h + }; + } + + private static BigInteger CalculateHashForZeroKnowledgeProof(BigInteger g, BigInteger gr, BigInteger gx, + string participantId, IDigest digest) + { + digest.Reset(); + + UpdateDigestIncludingSize(digest, g); + + UpdateDigestIncludingSize(digest, gr); + + UpdateDigestIncludingSize(digest, gx); + + UpdateDigestIncludingSize(digest, participantId); + + byte[] output = DigestUtilities.DoFinal(digest); + + return new BigInteger(output); + } + + /// + /// Validates that g^x4 is not 1. + /// throws CryptoException if g^x4 is 1 + /// + public static void ValidateGx4(BigInteger gx4) + { + if (gx4.Equals(One)) + throw new CryptoException("g^x validation failed. g^x should not be 1."); + } + + /// + /// Validates that ga is not 1. + /// + /// As described by Feng Hao... + /// Alice could simply check ga != 1 to ensure it is a generator. + /// In fact, as we will explain in Section 3, (x1 + x3 + x4 ) is random over Zq even in the face of active attacks. + /// Hence, the probability for ga = 1 is extremely small - on the order of 2^160 for 160-bit q. + /// + /// throws CryptoException if ga is 1 + /// + public static void ValidateGa(BigInteger ga) + { + if (ga.Equals(One)) + throw new CryptoException("ga is equal to 1. It should not be. The chances of this happening are on the order of 2^160 for a 160-bit q. Try again."); + } + + /// + /// Validates the zero knowledge proof (generated by + /// calculateZeroKnowledgeProof(BigInteger, BigInteger, BigInteger, BigInteger, BigInteger, string, Digest, SecureRandom) + /// is correct. + /// + /// throws CryptoException if the zero knowledge proof is not correct + /// + public static void ValidateZeroKnowledgeProof(BigInteger p, BigInteger q, BigInteger g, + BigInteger gx, BigInteger[] zeroKnowledgeProof, string participantId, IDigest digest) + { + /* sig={g^v,r} */ + BigInteger gv = zeroKnowledgeProof[0]; + BigInteger r = zeroKnowledgeProof[1]; + + BigInteger h = CalculateHashForZeroKnowledgeProof(g, gv, gx, participantId, digest); + if (!(gx.CompareTo(Zero) == 1 && // g^x > 0 + gx.CompareTo(p) == -1 && // g^x < p + gx.ModPow(q, p).CompareTo(One) == 0 && // g^x^q mod q = 1 + /* + * Below, I took a straightforward way to compute g^r * g^x^h, + * which needs 2 exp. Using a simultaneous computation technique + * would only need 1 exp. + */ + g.ModPow(r, p).Multiply(gx.ModPow(h, p)).Mod(p).CompareTo(gv) == 0)) // g^v=g^r * g^x^h + { + throw new CryptoException("Zero-knowledge proof validation failed"); + } + } + + /// + /// Calculates the keying material, which can be done after round 2 has completed. + /// A session key must be derived from this key material using a secure key derivation function (KDF). + /// The KDF used to derive the key is handled externally (i.e. not by JPAKEParticipant). + /// + /// KeyingMaterial = (B/g^{x2*x4*s})^x2 + /// + public static BigInteger CalculateKeyingMaterial(BigInteger p, BigInteger q, + BigInteger gx4, BigInteger x2, BigInteger s, BigInteger B) + { + return gx4.ModPow(x2.Multiply(s).Negate().Mod(q), p).Multiply(B).ModPow(x2, p); + } + + /// + /// Validates that the given participant ids are not equal. + /// (For the J-PAKE exchange, each participant must use a unique id.) + /// + /// Throws CryptoException if the participantId strings are equal. + /// + public static void ValidateParticipantIdsDiffer(string participantId1, string participantId2) + { + if (participantId1.Equals(participantId2)) + { + throw new CryptoException( + "Both participants are using the same participantId (" + + participantId1 + + "). This is not allowed. " + + "Each participant must use a unique participantId."); + } + } + + /// + /// Validates that the given participant ids are equal. + /// This is used to ensure that the payloads received from + /// each round all come from the same participant. + /// + public static void ValidateParticipantIdsEqual(string expectedParticipantId, string actualParticipantId) + { + if (!expectedParticipantId.Equals(actualParticipantId)) + { + throw new CryptoException( + "Received payload from incorrect partner (" + + actualParticipantId + + "). Expected to receive payload from " + + expectedParticipantId + + "."); + } + } + + /// + /// Validates that the given object is not null. + /// throws NullReferenceException if the object is null. + /// + /// object in question + /// name of the object (to be used in exception message) + public static void ValidateNotNull(object obj, string description) + { + if (obj == null) + throw new ArgumentNullException(description); + } + + /// + /// Calculates the MacTag (to be used for key confirmation), as defined by + /// NIST SP 800-56A Revision 1, + /// Section 8.2 Unilateral Key Confirmation for Key Agreement Schemes. + /// + /// MacTag = HMAC(MacKey, MacLen, MacData) + /// MacKey = H(K || "JPAKE_KC") + /// MacData = "KC_1_U" || participantId || partnerParticipantId || gx1 || gx2 || gx3 || gx4 + /// + /// Note that both participants use "KC_1_U" because the sender of the round 3 message + /// is always the initiator for key confirmation. + /// + /// HMAC = {@link HMac} used with the given {@link Digest} + /// H = The given {@link Digest} + /// MacLen = length of MacTag + /// + public static BigInteger CalculateMacTag(string participantId, string partnerParticipantId, + BigInteger gx1, BigInteger gx2, BigInteger gx3, BigInteger gx4, BigInteger keyingMaterial, IDigest digest) + { + byte[] macKey = CalculateMacKey(keyingMaterial, digest); + + HMac mac = new HMac(digest); + mac.Init(new KeyParameter(macKey)); + Arrays.Fill(macKey, (byte)0); + + /* + * MacData = "KC_1_U" || participantId_Alice || participantId_Bob || gx1 || gx2 || gx3 || gx4. + */ + UpdateMac(mac, "KC_1_U"); + UpdateMac(mac, participantId); + UpdateMac(mac, partnerParticipantId); + UpdateMac(mac, gx1); + UpdateMac(mac, gx2); + UpdateMac(mac, gx3); + UpdateMac(mac, gx4); + + byte[] macOutput = MacUtilities.DoFinal(mac); + + return new BigInteger(macOutput); + } + + /// + /// Calculates the MacKey (i.e. the key to use when calculating the MagTag for key confirmation). + /// + /// MacKey = H(K || "JPAKE_KC") + /// + private static byte[] CalculateMacKey(BigInteger keyingMaterial, IDigest digest) + { + digest.Reset(); + + UpdateDigest(digest, keyingMaterial); + /* + * This constant is used to ensure that the macKey is NOT the same as the derived key. + */ + UpdateDigest(digest, "JPAKE_KC"); + + return DigestUtilities.DoFinal(digest); + } + + /// + /// Validates the MacTag received from the partner participant. + /// + /// throws CryptoException if the participantId strings are equal. + /// + public static void ValidateMacTag(string participantId, string partnerParticipantId, + BigInteger gx1, BigInteger gx2, BigInteger gx3, BigInteger gx4, + BigInteger keyingMaterial, IDigest digest, BigInteger partnerMacTag) + { + /* + * Calculate the expected MacTag using the parameters as the partner + * would have used when the partner called calculateMacTag. + * + * i.e. basically all the parameters are reversed. + * participantId <-> partnerParticipantId + * x1 <-> x3 + * x2 <-> x4 + */ + BigInteger expectedMacTag = CalculateMacTag(partnerParticipantId, participantId, gx3, gx4, gx1, gx2, keyingMaterial, digest); + + if (!expectedMacTag.Equals(partnerMacTag)) + { + throw new CryptoException( + "Partner MacTag validation failed. " + + "Therefore, the password, MAC, or digest algorithm of each participant does not match."); + } + } + + private static void UpdateDigest(IDigest digest, BigInteger bigInteger) + { + UpdateDigest(digest, BigIntegers.AsUnsignedByteArray(bigInteger)); + } + + private static void UpdateDigest(IDigest digest, string str) + { + UpdateDigest(digest, Encoding.UTF8.GetBytes(str)); + } + + private static void UpdateDigest(IDigest digest, byte[] bytes) + { + digest.BlockUpdate(bytes, 0, bytes.Length); + Arrays.Fill(bytes, (byte)0); + } + + private static void UpdateDigestIncludingSize(IDigest digest, BigInteger bigInteger) + { + UpdateDigestIncludingSize(digest, BigIntegers.AsUnsignedByteArray(bigInteger)); + } + + private static void UpdateDigestIncludingSize(IDigest digest, string str) + { + UpdateDigestIncludingSize(digest, Encoding.UTF8.GetBytes(str)); + } + + private static void UpdateDigestIncludingSize(IDigest digest, byte[] bytes) + { + digest.BlockUpdate(IntToByteArray(bytes.Length), 0, 4); + digest.BlockUpdate(bytes, 0, bytes.Length); + Arrays.Fill(bytes, (byte)0); + } + + private static void UpdateMac(IMac mac, BigInteger bigInteger) + { + UpdateMac(mac, BigIntegers.AsUnsignedByteArray(bigInteger)); + } + + private static void UpdateMac(IMac mac, string str) + { + UpdateMac(mac, Encoding.UTF8.GetBytes(str)); + } + + private static void UpdateMac(IMac mac, byte[] bytes) + { + mac.BlockUpdate(bytes, 0, bytes.Length); + Arrays.Fill(bytes, (byte)0); + } + + private static byte[] IntToByteArray(int value) + { + return Pack.UInt32_To_BE((uint)value); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/agreement/kdf/DHKdfParameters.cs b/bc-sharp-crypto/src/crypto/agreement/kdf/DHKdfParameters.cs new file mode 100644 index 0000000..f6c9e60 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/agreement/kdf/DHKdfParameters.cs @@ -0,0 +1,57 @@ +using System; + +using Org.BouncyCastle.Asn1; + +namespace Org.BouncyCastle.Crypto.Agreement.Kdf +{ + public class DHKdfParameters + : IDerivationParameters + { + private readonly DerObjectIdentifier algorithm; + private readonly int keySize; + private readonly byte[] z; + private readonly byte[] extraInfo; + + public DHKdfParameters( + DerObjectIdentifier algorithm, + int keySize, + byte[] z) + : this(algorithm, keySize, z, null) + { + } + + public DHKdfParameters( + DerObjectIdentifier algorithm, + int keySize, + byte[] z, + byte[] extraInfo) + { + this.algorithm = algorithm; + this.keySize = keySize; + this.z = z; // TODO Clone? + this.extraInfo = extraInfo; + } + + public DerObjectIdentifier Algorithm + { + get { return algorithm; } + } + + public int KeySize + { + get { return keySize; } + } + + public byte[] GetZ() + { + // TODO Clone? + return z; + } + + public byte[] GetExtraInfo() + { + // TODO Clone? + return extraInfo; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/agreement/kdf/DHKekGenerator.cs b/bc-sharp-crypto/src/crypto/agreement/kdf/DHKekGenerator.cs new file mode 100644 index 0000000..259e21e --- /dev/null +++ b/bc-sharp-crypto/src/crypto/agreement/kdf/DHKekGenerator.cs @@ -0,0 +1,112 @@ +using System; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Crypto.Utilities; + +namespace Org.BouncyCastle.Crypto.Agreement.Kdf +{ + /** + * RFC 2631 Diffie-hellman KEK derivation function. + */ + public class DHKekGenerator + : IDerivationFunction + { + private readonly IDigest digest; + + private DerObjectIdentifier algorithm; + private int keySize; + private byte[] z; + private byte[] partyAInfo; + + public DHKekGenerator(IDigest digest) + { + this.digest = digest; + } + + public virtual void Init(IDerivationParameters param) + { + DHKdfParameters parameters = (DHKdfParameters)param; + + this.algorithm = parameters.Algorithm; + this.keySize = parameters.KeySize; + this.z = parameters.GetZ(); // TODO Clone? + this.partyAInfo = parameters.GetExtraInfo(); // TODO Clone? + } + + public virtual IDigest Digest + { + get { return digest; } + } + + public virtual int GenerateBytes(byte[] outBytes, int outOff, int len) + { + if ((outBytes.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 ArgumentException("Output length too large"); + } + + int cThreshold = (int)((oBytes + outLen - 1) / outLen); + + byte[] dig = new byte[digest.GetDigestSize()]; + + uint counter = 1; + + for (int i = 0; i < cThreshold; i++) + { + digest.BlockUpdate(z, 0, z.Length); + + // KeySpecificInfo + DerSequence keyInfo = new DerSequence( + algorithm, + new DerOctetString(Pack.UInt32_To_BE(counter))); + + // OtherInfo + Asn1EncodableVector v1 = new Asn1EncodableVector(keyInfo); + + if (partyAInfo != null) + { + v1.Add(new DerTaggedObject(true, 0, new DerOctetString(partyAInfo))); + } + + v1.Add(new DerTaggedObject(true, 2, new DerOctetString(Pack.UInt32_To_BE((uint)keySize)))); + + byte[] other = new DerSequence(v1).GetDerEncoded(); + + digest.BlockUpdate(other, 0, other.Length); + + digest.DoFinal(dig, 0); + + if (len > outLen) + { + Array.Copy(dig, 0, outBytes, outOff, outLen); + outOff += outLen; + len -= outLen; + } + else + { + Array.Copy(dig, 0, outBytes, outOff, len); + } + + counter++; + } + + digest.Reset(); + + return (int)oBytes; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/agreement/kdf/ECDHKekGenerator.cs b/bc-sharp-crypto/src/crypto/agreement/kdf/ECDHKekGenerator.cs new file mode 100644 index 0000000..7446457 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/agreement/kdf/ECDHKekGenerator.cs @@ -0,0 +1,55 @@ +using System; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Utilities; + +namespace Org.BouncyCastle.Crypto.Agreement.Kdf +{ + /** + * X9.63 based key derivation function for ECDH CMS. + */ + public class ECDHKekGenerator + : IDerivationFunction + { + private readonly IDerivationFunction kdf; + + private DerObjectIdentifier algorithm; + private int keySize; + private byte[] z; + + public ECDHKekGenerator(IDigest digest) + { + this.kdf = new Kdf2BytesGenerator(digest); + } + + public virtual void Init(IDerivationParameters param) + { + DHKdfParameters parameters = (DHKdfParameters)param; + + this.algorithm = parameters.Algorithm; + this.keySize = parameters.KeySize; + this.z = parameters.GetZ(); // TODO Clone? + } + + public virtual IDigest Digest + { + get { return kdf.Digest; } + } + + public virtual int GenerateBytes(byte[] outBytes, int outOff, int len) + { + // TODO Create an ASN.1 class for this (RFC3278) + // ECC-CMS-SharedInfo + DerSequence s = new DerSequence( + new AlgorithmIdentifier(algorithm, DerNull.Instance), + new DerTaggedObject(true, 2, new DerOctetString(Pack.UInt32_To_BE((uint)keySize)))); + + kdf.Init(new KdfParameters(z, s.GetDerEncoded())); + + return kdf.GenerateBytes(outBytes, outOff, len); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/agreement/srp/SRP6Client.cs b/bc-sharp-crypto/src/crypto/agreement/srp/SRP6Client.cs new file mode 100644 index 0000000..f075d7a --- /dev/null +++ b/bc-sharp-crypto/src/crypto/agreement/srp/SRP6Client.cs @@ -0,0 +1,164 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Agreement.Srp +{ + /** + * 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 privA; + protected BigInteger pubA; + + protected BigInteger B; + + protected BigInteger x; + protected BigInteger u; + protected BigInteger S; + + protected BigInteger M1; + protected BigInteger M2; + protected BigInteger Key; + + protected IDigest 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 virtual void Init(BigInteger N, BigInteger g, IDigest digest, SecureRandom random) + { + this.N = N; + this.g = g; + this.digest = digest; + this.random = random; + } + + public virtual void Init(Srp6GroupParameters group, IDigest digest, SecureRandom random) + { + Init(group.N, group.G, digest, 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 virtual BigInteger GenerateClientCredentials(byte[] salt, byte[] identity, byte[] password) + { + this.x = Srp6Utilities.CalculateX(digest, N, salt, identity, password); + this.privA = SelectPrivateValue(); + this.pubA = g.ModPow(privA, N); + + return pubA; + } + + /** + * 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 virtual BigInteger CalculateSecret(BigInteger serverB) + { + this.B = Srp6Utilities.ValidatePublicValue(N, serverB); + this.u = Srp6Utilities.CalculateU(digest, N, pubA, B); + this.S = CalculateS(); + + return S; + } + + protected virtual BigInteger SelectPrivateValue() + { + return Srp6Utilities.GeneratePrivateValue(digest, N, g, random); + } + + private BigInteger CalculateS() + { + BigInteger k = Srp6Utilities.CalculateK(digest, N, g); + BigInteger exp = u.Multiply(x).Add(privA); + BigInteger tmp = g.ModPow(x, N).Multiply(k).Mod(N); + return B.Subtract(tmp).Mod(N).ModPow(exp, N); + } + + /** + * Computes the client evidence message M1 using the previously received values. + * To be called after calculating the secret S. + * @return M1: the client side generated evidence message + * @throws CryptoException + */ + public virtual BigInteger CalculateClientEvidenceMessage() + { + // Verify pre-requirements + if (this.pubA == null || this.B == null || this.S == null) + { + throw new CryptoException("Impossible to compute M1: " + + "some data are missing from the previous operations (A,B,S)"); + } + // compute the client evidence message 'M1' + this.M1 = Srp6Utilities.CalculateM1(digest, N, pubA, B, S); + return M1; + } + + /** Authenticates the server evidence message M2 received and saves it only if correct. + * @param M2: the server side generated evidence message + * @return A boolean indicating if the server message M2 was the expected one. + * @throws CryptoException + */ + public virtual bool VerifyServerEvidenceMessage(BigInteger serverM2) + { + // Verify pre-requirements + if (this.pubA == null || this.M1 == null || this.S == null) + { + throw new CryptoException("Impossible to compute and verify M2: " + + "some data are missing from the previous operations (A,M1,S)"); + } + + // Compute the own server evidence message 'M2' + BigInteger computedM2 = Srp6Utilities.CalculateM2(digest, N, pubA, M1, S); + if (computedM2.Equals(serverM2)) + { + this.M2 = serverM2; + return true; + } + return false; + } + + /** + * Computes the final session key as a result of the SRP successful mutual authentication + * To be called after verifying the server evidence message M2. + * @return Key: the mutually authenticated symmetric session key + * @throws CryptoException + */ + public virtual BigInteger CalculateSessionKey() + { + // Verify pre-requirements (here we enforce a previous calculation of M1 and M2) + if (this.S == null || this.M1 == null || this.M2 == null) + { + throw new CryptoException("Impossible to compute Key: " + + "some data are missing from the previous operations (S,M1,M2)"); + } + this.Key = Srp6Utilities.CalculateKey(digest, N, S); + return Key; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/agreement/srp/SRP6Server.cs b/bc-sharp-crypto/src/crypto/agreement/srp/SRP6Server.cs new file mode 100644 index 0000000..fd0c9f1 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/agreement/srp/SRP6Server.cs @@ -0,0 +1,163 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Agreement.Srp +{ + /** + * 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 IDigest digest; + + protected BigInteger A; + + protected BigInteger privB; + protected BigInteger pubB; + + protected BigInteger u; + protected BigInteger S; + protected BigInteger M1; + protected BigInteger M2; + protected BigInteger Key; + + 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 virtual void Init(BigInteger N, BigInteger g, BigInteger v, IDigest digest, SecureRandom random) + { + this.N = N; + this.g = g; + this.v = v; + + this.random = random; + this.digest = digest; + } + + public virtual void Init(Srp6GroupParameters group, BigInteger v, IDigest digest, SecureRandom random) + { + Init(group.N, group.G, v, digest, random); + } + + /** + * Generates the server's credentials that are to be sent to the client. + * @return The server's public value to the client + */ + public virtual BigInteger GenerateServerCredentials() + { + BigInteger k = Srp6Utilities.CalculateK(digest, N, g); + this.privB = SelectPrivateValue(); + this.pubB = k.Multiply(v).Mod(N).Add(g.ModPow(privB, N)).Mod(N); + + return pubB; + } + + /** + * 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 virtual BigInteger CalculateSecret(BigInteger clientA) + { + this.A = Srp6Utilities.ValidatePublicValue(N, clientA); + this.u = Srp6Utilities.CalculateU(digest, N, A, pubB); + this.S = CalculateS(); + + return S; + } + + protected virtual BigInteger SelectPrivateValue() + { + return Srp6Utilities.GeneratePrivateValue(digest, N, g, random); + } + + private BigInteger CalculateS() + { + return v.ModPow(u, N).Multiply(A).Mod(N).ModPow(privB, N); + } + + /** + * Authenticates the received client evidence message M1 and saves it only if correct. + * To be called after calculating the secret S. + * @param M1: the client side generated evidence message + * @return A boolean indicating if the client message M1 was the expected one. + * @throws CryptoException + */ + public virtual bool VerifyClientEvidenceMessage(BigInteger clientM1) + { + // Verify pre-requirements + if (this.A == null || this.pubB == null || this.S == null) + { + throw new CryptoException("Impossible to compute and verify M1: " + + "some data are missing from the previous operations (A,B,S)"); + } + + // Compute the own client evidence message 'M1' + BigInteger computedM1 = Srp6Utilities.CalculateM1(digest, N, A, pubB, S); + if (computedM1.Equals(clientM1)) + { + this.M1 = clientM1; + return true; + } + return false; + } + + /** + * Computes the server evidence message M2 using the previously verified values. + * To be called after successfully verifying the client evidence message M1. + * @return M2: the server side generated evidence message + * @throws CryptoException + */ + public virtual BigInteger CalculateServerEvidenceMessage() + { + // Verify pre-requirements + if (this.A == null || this.M1 == null || this.S == null) + { + throw new CryptoException("Impossible to compute M2: " + + "some data are missing from the previous operations (A,M1,S)"); + } + + // Compute the server evidence message 'M2' + this.M2 = Srp6Utilities.CalculateM2(digest, N, A, M1, S); + return M2; + } + + /** + * Computes the final session key as a result of the SRP successful mutual authentication + * To be called after calculating the server evidence message M2. + * @return Key: the mutual authenticated symmetric session key + * @throws CryptoException + */ + public virtual BigInteger CalculateSessionKey() + { + // Verify pre-requirements + if (this.S == null || this.M1 == null || this.M2 == null) + { + throw new CryptoException("Impossible to compute Key: " + + "some data are missing from the previous operations (S,M1,M2)"); + } + this.Key = Srp6Utilities.CalculateKey(digest, N, S); + return Key; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/agreement/srp/SRP6StandardGroups.cs b/bc-sharp-crypto/src/crypto/agreement/srp/SRP6StandardGroups.cs new file mode 100644 index 0000000..36f4aba --- /dev/null +++ b/bc-sharp-crypto/src/crypto/agreement/srp/SRP6StandardGroups.cs @@ -0,0 +1,159 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Crypto.Agreement.Srp +{ + public class Srp6StandardGroups + { + private static BigInteger FromHex(string hex) + { + return new BigInteger(1, Hex.Decode(hex)); + } + + private static Srp6GroupParameters FromNG(string hexN, string hexG) + { + return new Srp6GroupParameters(FromHex(hexN), FromHex(hexG)); + } + + /* + * RFC 5054 + */ + private const string rfc5054_1024_N = "EEAF0AB9ADB38DD69C33F80AFA8FC5E86072618775FF3C0B9EA2314C" + + "9C256576D674DF7496EA81D3383B4813D692C6E0E0D5D8E250B98BE4" + + "8E495C1D6089DAD15DC7D7B46154D6B6CE8EF4AD69B15D4982559B29" + + "7BCF1885C529F566660E57EC68EDBC3C05726CC02FD4CBF4976EAA9A" + "FD5138FE8376435B9FC61D2FC0EB06E3"; + private const string rfc5054_1024_g = "02"; + public static readonly Srp6GroupParameters rfc5054_1024 = FromNG(rfc5054_1024_N, rfc5054_1024_g); + + private const string rfc5054_1536_N = "9DEF3CAFB939277AB1F12A8617A47BBBDBA51DF499AC4C80BEEEA961" + + "4B19CC4D5F4F5F556E27CBDE51C6A94BE4607A291558903BA0D0F843" + + "80B655BB9A22E8DCDF028A7CEC67F0D08134B1C8B97989149B609E0B" + + "E3BAB63D47548381DBC5B1FC764E3F4B53DD9DA1158BFD3E2B9C8CF5" + + "6EDF019539349627DB2FD53D24B7C48665772E437D6C7F8CE442734A" + + "F7CCB7AE837C264AE3A9BEB87F8A2FE9B8B5292E5A021FFF5E91479E" + + "8CE7A28C2442C6F315180F93499A234DCF76E3FED135F9BB"; + private const string rfc5054_1536_g = "02"; + public static readonly Srp6GroupParameters rfc5054_1536 = FromNG(rfc5054_1536_N, rfc5054_1536_g); + + private const string rfc5054_2048_N = "AC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC319294" + + "3DB56050A37329CBB4A099ED8193E0757767A13DD52312AB4B03310D" + + "CD7F48A9DA04FD50E8083969EDB767B0CF6095179A163AB3661A05FB" + + "D5FAAAE82918A9962F0B93B855F97993EC975EEAA80D740ADBF4FF74" + + "7359D041D5C33EA71D281E446B14773BCA97B43A23FB801676BD207A" + + "436C6481F1D2B9078717461A5B9D32E688F87748544523B524B0D57D" + + "5EA77A2775D2ECFA032CFBDBF52FB3786160279004E57AE6AF874E73" + + "03CE53299CCC041C7BC308D82A5698F3A8D0C38271AE35F8E9DBFBB6" + + "94B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F" + "9E4AFF73"; + private const string rfc5054_2048_g = "02"; + public static readonly Srp6GroupParameters rfc5054_2048 = FromNG(rfc5054_2048_N, rfc5054_2048_g); + + private const string rfc5054_3072_N = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08" + + "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B" + + "302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9" + + "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6" + + "49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8" + + "FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C" + + "180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718" + + "3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D" + + "04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D" + + "B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226" + + "1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC" + "E0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF"; + private const string rfc5054_3072_g = "05"; + public static readonly Srp6GroupParameters rfc5054_3072 = FromNG(rfc5054_3072_N, rfc5054_3072_g); + + private const string rfc5054_4096_N = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08" + + "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B" + + "302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9" + + "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6" + + "49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8" + + "FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C" + + "180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718" + + "3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D" + + "04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D" + + "B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226" + + "1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC" + + "E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26" + + "99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB" + + "04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2" + + "233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127" + + "D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199" + "FFFFFFFFFFFFFFFF"; + private const string rfc5054_4096_g = "05"; + public static readonly Srp6GroupParameters rfc5054_4096 = FromNG(rfc5054_4096_N, rfc5054_4096_g); + + private const string rfc5054_6144_N = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08" + + "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B" + + "302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9" + + "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6" + + "49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8" + + "FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C" + + "180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718" + + "3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D" + + "04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D" + + "B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226" + + "1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC" + + "E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26" + + "99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB" + + "04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2" + + "233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127" + + "D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492" + + "36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406" + + "AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918" + + "DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B33205151" + + "2BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03" + + "F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97F" + + "BEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA" + + "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58B" + + "B7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632" + + "387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E" + "6DCC4024FFFFFFFFFFFFFFFF"; + private const string rfc5054_6144_g = "05"; + public static readonly Srp6GroupParameters rfc5054_6144 = FromNG(rfc5054_6144_N, rfc5054_6144_g); + + private const string rfc5054_8192_N = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08" + + "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B" + + "302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9" + + "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6" + + "49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8" + + "FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C" + + "180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718" + + "3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D" + + "04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D" + + "B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226" + + "1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC" + + "E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26" + + "99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB" + + "04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2" + + "233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127" + + "D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492" + + "36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406" + + "AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918" + + "DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B33205151" + + "2BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03" + + "F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97F" + + "BEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA" + + "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58B" + + "B7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632" + + "387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E" + + "6DBE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA" + + "3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C" + + "5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD9" + + "22222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC886" + + "2F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A6" + + "6D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC5" + + "0846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268" + + "359046F4EB879F924009438B481C6CD7889A002ED5EE382BC9190DA6" + + "FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E71" + "60C980DD98EDD3DFFFFFFFFFFFFFFFFF"; + private const string rfc5054_8192_g = "13"; + public static readonly Srp6GroupParameters rfc5054_8192 = FromNG(rfc5054_8192_N, rfc5054_8192_g); + } +} diff --git a/bc-sharp-crypto/src/crypto/agreement/srp/SRP6Utilities.cs b/bc-sharp-crypto/src/crypto/agreement/srp/SRP6Utilities.cs new file mode 100644 index 0000000..ef6d8f2 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/agreement/srp/SRP6Utilities.cs @@ -0,0 +1,153 @@ +using System; + +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Agreement.Srp +{ + public class Srp6Utilities + { + public static BigInteger CalculateK(IDigest digest, BigInteger N, BigInteger g) + { + return HashPaddedPair(digest, N, N, g); + } + + public static BigInteger CalculateU(IDigest digest, BigInteger N, BigInteger A, BigInteger B) + { + return HashPaddedPair(digest, N, A, B); + } + + public static BigInteger CalculateX(IDigest digest, BigInteger N, byte[] salt, byte[] identity, byte[] password) + { + byte[] output = new byte[digest.GetDigestSize()]; + + digest.BlockUpdate(identity, 0, identity.Length); + digest.Update((byte)':'); + digest.BlockUpdate(password, 0, password.Length); + digest.DoFinal(output, 0); + + digest.BlockUpdate(salt, 0, salt.Length); + digest.BlockUpdate(output, 0, output.Length); + digest.DoFinal(output, 0); + + return new BigInteger(1, output); + } + + public static BigInteger GeneratePrivateValue(IDigest digest, BigInteger N, BigInteger g, SecureRandom random) + { + int minBits = System.Math.Min(256, N.BitLength / 2); + BigInteger min = BigInteger.One.ShiftLeft(minBits - 1); + BigInteger max = N.Subtract(BigInteger.One); + + return BigIntegers.CreateRandomInRange(min, max, random); + } + + public static BigInteger ValidatePublicValue(BigInteger N, BigInteger val) + { + val = val.Mod(N); + + // Check that val % N != 0 + if (val.Equals(BigInteger.Zero)) + throw new CryptoException("Invalid public value: 0"); + + return val; + } + + /** + * Computes the client evidence message (M1) according to the standard routine: + * M1 = H( A | B | S ) + * @param digest The Digest used as the hashing function H + * @param N Modulus used to get the pad length + * @param A The public client value + * @param B The public server value + * @param S The secret calculated by both sides + * @return M1 The calculated client evidence message + */ + public static BigInteger CalculateM1(IDigest digest, BigInteger N, BigInteger A, BigInteger B, BigInteger S) + { + BigInteger M1 = HashPaddedTriplet(digest, N, A, B, S); + return M1; + } + + /** + * Computes the server evidence message (M2) according to the standard routine: + * M2 = H( A | M1 | S ) + * @param digest The Digest used as the hashing function H + * @param N Modulus used to get the pad length + * @param A The public client value + * @param M1 The client evidence message + * @param S The secret calculated by both sides + * @return M2 The calculated server evidence message + */ + public static BigInteger CalculateM2(IDigest digest, BigInteger N, BigInteger A, BigInteger M1, BigInteger S) + { + BigInteger M2 = HashPaddedTriplet(digest, N, A, M1, S); + return M2; + } + + /** + * Computes the final Key according to the standard routine: Key = H(S) + * @param digest The Digest used as the hashing function H + * @param N Modulus used to get the pad length + * @param S The secret calculated by both sides + * @return + */ + public static BigInteger CalculateKey(IDigest digest, BigInteger N, BigInteger S) + { + int padLength = (N.BitLength + 7) / 8; + byte[] _S = GetPadded(S, padLength); + digest.BlockUpdate(_S, 0, _S.Length); + + byte[] output = new byte[digest.GetDigestSize()]; + digest.DoFinal(output, 0); + return new BigInteger(1, output); + } + + private static BigInteger HashPaddedTriplet(IDigest digest, BigInteger N, BigInteger n1, BigInteger n2, BigInteger n3) + { + int padLength = (N.BitLength + 7) / 8; + + byte[] n1_bytes = GetPadded(n1, padLength); + byte[] n2_bytes = GetPadded(n2, padLength); + byte[] n3_bytes = GetPadded(n3, padLength); + + digest.BlockUpdate(n1_bytes, 0, n1_bytes.Length); + digest.BlockUpdate(n2_bytes, 0, n2_bytes.Length); + digest.BlockUpdate(n3_bytes, 0, n3_bytes.Length); + + byte[] output = new byte[digest.GetDigestSize()]; + digest.DoFinal(output, 0); + + return new BigInteger(1, output); + } + + private static BigInteger HashPaddedPair(IDigest 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.BlockUpdate(n1_bytes, 0, n1_bytes.Length); + digest.BlockUpdate(n2_bytes, 0, n2_bytes.Length); + + byte[] output = new byte[digest.GetDigestSize()]; + digest.DoFinal(output, 0); + + return new BigInteger(1, output); + } + + private static byte[] GetPadded(BigInteger n, int length) + { + byte[] bs = BigIntegers.AsUnsignedByteArray(n); + if (bs.Length < length) + { + byte[] tmp = new byte[length]; + Array.Copy(bs, 0, tmp, length - bs.Length, bs.Length); + bs = tmp; + } + return bs; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/agreement/srp/SRP6VerifierGenerator.cs b/bc-sharp-crypto/src/crypto/agreement/srp/SRP6VerifierGenerator.cs new file mode 100644 index 0000000..9569735 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/agreement/srp/SRP6VerifierGenerator.cs @@ -0,0 +1,55 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Crypto.Agreement.Srp +{ + /** + * Generates new SRP verifier for user + */ + public class Srp6VerifierGenerator + { + protected BigInteger N; + protected BigInteger g; + protected IDigest 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 virtual void Init(BigInteger N, BigInteger g, IDigest digest) + { + this.N = N; + this.g = g; + this.digest = digest; + } + + public virtual void Init(Srp6GroupParameters group, IDigest digest) + { + Init(group.N, group.G, 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 virtual BigInteger GenerateVerifier(byte[] salt, byte[] identity, byte[] password) + { + BigInteger x = Srp6Utilities.CalculateX(digest, N, salt, identity, password); + + return g.ModPow(x, N); + } + } +} + diff --git a/bc-sharp-crypto/src/crypto/digests/DSTU7564Digest.cs b/bc-sharp-crypto/src/crypto/digests/DSTU7564Digest.cs new file mode 100644 index 0000000..9de41dd --- /dev/null +++ b/bc-sharp-crypto/src/crypto/digests/DSTU7564Digest.cs @@ -0,0 +1,562 @@ +using System; + +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Utilities; +//using Org.BouncyCastle.Utilities; + + +using Org.BouncyCastle.Utilities.Encoders; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Digests +{ + /** + * implementation of Ukrainian DSTU 7564 hash function + */ + public class Dstu7564Digest : IDigest, IMemoable + { + private const int ROWS = 8; + private const int REDUCTION_POLYNOMIAL = 0x011d; + private const int BITS_IN_BYTE = 8; + + + private const int NB_512 = 8; //Number of 8-byte words in state for <=256-bit hash code. + private const int NB_1024 = 16; //Number of 8-byte words in state for <=512-bit hash code. + + private const int NR_512 = 10; //Number of rounds for 512-bit state. + private const int NR_1024 = 14; //Number of rounds for 1024-bit state. + + private const int STATE_BYTE_SIZE_512 = ROWS * NB_512; + private const int STATE_BYTE_SIZE_1024 = ROWS * NB_1024; + + private int hashSize; + private int blockSize; + + + + private int columns; + private int rounds; + + + private byte[] padded_; + + private byte[][] state_; + + private ulong inputLength; + private int bufOff; + private byte[] buf; + + public Dstu7564Digest(Dstu7564Digest digest) + { + copyIn(digest); + } + + private void copyIn(Dstu7564Digest digest) + { + this.hashSize = digest.hashSize; + this.blockSize = digest.blockSize; + + this.columns = digest.columns; + this.rounds = digest.rounds; + + this.padded_ = Arrays.Clone(digest.padded_); + this.state_ = new byte[digest.state_.Length][]; + for (int i = 0; i != this.state_.Length; i++) + { + this.state_[i] = Arrays.Clone(digest.state_[i]); + } + + this.inputLength = digest.inputLength; + this.bufOff = digest.bufOff; + this.buf = Arrays.Clone(digest.buf); + } + + public Dstu7564Digest(int hashSizeBits) + { + if (hashSizeBits == 256 || hashSizeBits == 384 || hashSizeBits == 512) + { + this.hashSize = hashSizeBits / 8; + } + else + { + throw new ArgumentException("Hash size is not recommended. Use 256 or 384 or 512 size"); + } + + if (hashSizeBits > 256) + { + this.blockSize = 1024 / 8; + this.columns = NB_1024; + this.rounds = NR_1024; + this.state_ = new byte[STATE_BYTE_SIZE_1024][]; + + } + else + { + this.blockSize = 512 / 8; + this.columns = NB_512; + this.rounds = NR_512; + this.state_ = new byte[STATE_BYTE_SIZE_512][]; + + } + + //Console.WriteLine("length: " + state_.Length); + + for (int i = 0; i < state_.Length; i++) + { + this.state_[i] = new byte[columns]; + } + + this.state_[0][0] = (byte)state_.Length; + + this.hashSize = hashSizeBits / 8; + + this.padded_ = null; + this.buf = new byte[blockSize]; + } + + public string AlgorithmName + { + get { return "DSTU7564"; } + } + + + public virtual void BlockUpdate(byte[] input, int inOff, int length) + { + while (bufOff != 0 && length > 0) + { + Update(input[inOff++]); + length--; + } + + if (length > 0) + { + while (length > blockSize) + { + ProcessBlock(input, inOff); + inOff += blockSize; + inputLength += (ulong)blockSize; + length -= blockSize; + } + + while (length > 0) + { + Update(input[inOff++]); + length--; + } + } + } + + protected byte[] Pad(byte[] input, int inOff, int length) + { + //Console.WriteLine(length); + + byte[] padded; + if (blockSize - length < 13) // terminator byte + 96 bits of length + { + padded = new byte[2 * blockSize]; + } + else + { + padded = new byte[blockSize]; + } + + + Array.Copy(input, inOff, padded, 0, length); + padded[length] = 0x80; + Pack.UInt64_To_LE(inputLength * 8, padded, padded.Length - 12); + + return padded; + } + + protected void ProcessBlock(byte[] input, int inOff) + { + byte[][] temp1 = new byte[STATE_BYTE_SIZE_1024][]; + byte[][] temp2 = new byte[STATE_BYTE_SIZE_1024][]; + + for (int i = 0; i < state_.Length; i++) + { + temp1[i] = new byte[ROWS]; + temp2[i] = new byte[ROWS]; + } + + for (int i = 0; i < ROWS; ++i) + { + for (int j = 0; j < columns; ++j) + { + //Console.WriteLine("row = {0}, column = {1}", i, j); + + temp1[j][i] = (byte)(state_[j][i] ^ input[j * ROWS + i + inOff]); + temp2[j][i] = input[j * ROWS + i + inOff]; + + } + + } + + P(temp1); + + Q(temp2); + + for (int i = 0; i < ROWS; ++i) + { + for (int j = 0; j < columns; ++j) + { + state_[j][i] ^= (byte)(temp1[j][i] ^ temp2[j][i]); + + } + + } + } + + public int DoFinal(byte[] output, int outOff) + { + padded_ = Pad(buf, 0, bufOff); + + int paddedLen = padded_.Length; + int paddedOff = 0; + + while (paddedLen != 0) + { + ProcessBlock(padded_, paddedOff); + paddedOff += blockSize; + paddedLen -= blockSize; + } + + + //Console.WriteLine(stateLine.Length); + + byte[][] temp = new byte[STATE_BYTE_SIZE_1024][]; + for (int i = 0; i < state_.Length; i++) + { + temp[i] = new byte[ROWS]; + Array.Copy(state_[i], temp[i], ROWS); + } + + P(temp); + + for (int i = 0; i < ROWS; ++i) + { + for (int j = 0; j < columns; ++j) + { + state_[j][i] ^= temp[j][i]; + //Console.Write("{0:x} ", state_[j][i]); + } + //Console.WriteLine(); + } + + byte[] stateLine = new byte[ROWS * columns]; + int stateLineIndex = 0; + for (int j = 0; j < columns; ++j) + { + for (int i = 0; i < ROWS; ++i) + { + + stateLine[stateLineIndex] = state_[j][i]; + stateLineIndex++; + + //Console.WriteLine("index = {0}, row = {1}, column = {2}", stateLineIndex, i, j); + + } + } + + //Console.WriteLine("final: " + Hex.ToHexString(stateLine)); + //Console.WriteLine(stateLine.Length); + + Array.Copy(stateLine, stateLine.Length - hashSize, output, outOff, hashSize); + + Reset(); + + return hashSize; + } + + public void Reset() + { + for (int bufferIndex = 0; bufferIndex < state_.Length; bufferIndex++) + { + state_[bufferIndex] = new byte[columns]; + } + + state_[0][0] = (byte)state_.Length; + + inputLength = 0; + bufOff = 0; + + Arrays.Fill(buf, (byte)0); + + if (padded_ != null) + { + Arrays.Fill(padded_, (byte)0); + } + } + + public int GetDigestSize() + { + return hashSize; + } + + public int GetByteLength() + { + return blockSize; + } + + public void Update(byte input) + { + buf[bufOff++] = input; + if (bufOff == blockSize) + { + ProcessBlock(buf, 0); + bufOff = 0; + } + inputLength++; + } + + void SubBytes(byte[][] state) + { + int i, j; + for (i = 0; i < ROWS; ++i) + { + for (j = 0; j < columns; ++j) + { + state[j][i] = sBoxes[i % 4][state[j][i]]; + } + } + } + + void ShiftBytes(byte[][] state) + { + int i, j; + byte[] temp = new byte[NB_1024]; + int shift = -1; + for (i = 0; i < ROWS; ++i) + { + if ((i == ROWS - 1) && (columns == NB_1024)) + { + shift = 11; + } + else + { + ++shift; + } + for (j = 0; j < columns; ++j) + { + temp[(j + shift) % columns] = state[j][i]; + } + for (j = 0; j < columns; ++j) + { + state[j][i] = temp[j]; + } + } + } + + byte MultiplyGF(byte x, byte y) + { + int i; + byte r = 0; + byte hbit = 0; + for (i = 0; i < BITS_IN_BYTE; ++i) + { + if ((y & 0x1) == 1) + { + r ^= x; + } + + hbit = (byte)(x & 0x80); + + x <<= 1; + + if (hbit == 0x80) + { + x = (byte)((int)x ^ REDUCTION_POLYNOMIAL); + } + + y >>= 1; + } + return r; + } + + private void MixColumns(byte[][] state) + { + int i, row, col, b; + byte product; + byte[] result = new byte[ROWS]; + + for (col = 0; col < columns; ++col) + { + Array.Clear(result, 0, ROWS); + for (row = ROWS - 1; row >= 0; --row) + { + product = 0; + for (b = ROWS - 1; b >= 0; --b) + { + product ^= MultiplyGF(state[col][b], mds_matrix[row][b]); + } + result[row] = product; + } + for (i = 0; i < ROWS; ++i) + { + state[col][i] = result[i]; + } + } + } + + void AddRoundConstantP(byte[][] state, int round) + { + int i; + for (i = 0; i < columns; ++i) + { + state[i][0] ^= (byte)((i * 0x10) ^ round); + } + } + + void AddRoundConstantQ(byte[][] state, int round) + { + int j; + UInt64[] s = new UInt64[columns]; + + for (j = 0; j < columns; j++) + { + s[j] = Pack.LE_To_UInt64(state[j]); + + s[j] = s[j] + (0x00F0F0F0F0F0F0F3UL ^ ((((UInt64)(columns - j - 1) * 0x10UL) ^ (UInt64)round) << (7 * 8))); + + state[j] = Pack.UInt64_To_LE(s[j]); + } + } + + void P(byte[][] state) + { + int i; + for (i = 0; i < rounds; ++i) + { + AddRoundConstantP(state, i); + SubBytes(state); + ShiftBytes(state); + MixColumns(state); + } + } + + void Q(byte[][] state) + { + int i; + for (i = 0; i < rounds; ++i) + { + AddRoundConstantQ(state, i); + SubBytes(state); + ShiftBytes(state); + MixColumns(state); + } + } + + public IMemoable Copy() + { + return new Dstu7564Digest(this); + } + + public void Reset(IMemoable other) + { + Dstu7564Digest d = (Dstu7564Digest)other; + + copyIn(d); + } + + private readonly byte[][] mds_matrix = new byte[][] + { + new byte[] {0x01, 0x01, 0x05, 0x01, 0x08, 0x06, 0x07, 0x04}, + new byte[] {0x04, 0x01, 0x01, 0x05, 0x01, 0x08, 0x06, 0x07}, + new byte[] {0x07, 0x04, 0x01, 0x01, 0x05, 0x01, 0x08, 0x06}, + new byte[] {0x06, 0x07, 0x04, 0x01, 0x01, 0x05, 0x01, 0x08}, + new byte[] {0x08, 0x06, 0x07, 0x04, 0x01, 0x01, 0x05, 0x01}, + new byte[] {0x01, 0x08, 0x06, 0x07, 0x04, 0x01, 0x01, 0x05}, + new byte[] {0x05, 0x01, 0x08, 0x06, 0x07, 0x04, 0x01, 0x01}, + new byte[] {0x01, 0x05, 0x01, 0x08, 0x06, 0x07, 0x04, 0x01} + }; + + + + + private readonly byte[][] sBoxes = new byte[][] + { + new byte[] + { + 0xa8, 0x43, 0x5f, 0x06, 0x6b, 0x75, 0x6c, 0x59, 0x71, 0xdf, 0x87, 0x95, 0x17, 0xf0, 0xd8, 0x09, + 0x6d, 0xf3, 0x1d, 0xcb, 0xc9, 0x4d, 0x2c, 0xaf, 0x79, 0xe0, 0x97, 0xfd, 0x6f, 0x4b, 0x45, 0x39, + 0x3e, 0xdd, 0xa3, 0x4f, 0xb4, 0xb6, 0x9a, 0x0e, 0x1f, 0xbf, 0x15, 0xe1, 0x49, 0xd2, 0x93, 0xc6, + 0x92, 0x72, 0x9e, 0x61, 0xd1, 0x63, 0xfa, 0xee, 0xf4, 0x19, 0xd5, 0xad, 0x58, 0xa4, 0xbb, 0xa1, + 0xdc, 0xf2, 0x83, 0x37, 0x42, 0xe4, 0x7a, 0x32, 0x9c, 0xcc, 0xab, 0x4a, 0x8f, 0x6e, 0x04, 0x27, + 0x2e, 0xe7, 0xe2, 0x5a, 0x96, 0x16, 0x23, 0x2b, 0xc2, 0x65, 0x66, 0x0f, 0xbc, 0xa9, 0x47, 0x41, + 0x34, 0x48, 0xfc, 0xb7, 0x6a, 0x88, 0xa5, 0x53, 0x86, 0xf9, 0x5b, 0xdb, 0x38, 0x7b, 0xc3, 0x1e, + 0x22, 0x33, 0x24, 0x28, 0x36, 0xc7, 0xb2, 0x3b, 0x8e, 0x77, 0xba, 0xf5, 0x14, 0x9f, 0x08, 0x55, + 0x9b, 0x4c, 0xfe, 0x60, 0x5c, 0xda, 0x18, 0x46, 0xcd, 0x7d, 0x21, 0xb0, 0x3f, 0x1b, 0x89, 0xff, + 0xeb, 0x84, 0x69, 0x3a, 0x9d, 0xd7, 0xd3, 0x70, 0x67, 0x40, 0xb5, 0xde, 0x5d, 0x30, 0x91, 0xb1, + 0x78, 0x11, 0x01, 0xe5, 0x00, 0x68, 0x98, 0xa0, 0xc5, 0x02, 0xa6, 0x74, 0x2d, 0x0b, 0xa2, 0x76, + 0xb3, 0xbe, 0xce, 0xbd, 0xae, 0xe9, 0x8a, 0x31, 0x1c, 0xec, 0xf1, 0x99, 0x94, 0xaa, 0xf6, 0x26, + 0x2f, 0xef, 0xe8, 0x8c, 0x35, 0x03, 0xd4, 0x7f, 0xfb, 0x05, 0xc1, 0x5e, 0x90, 0x20, 0x3d, 0x82, + 0xf7, 0xea, 0x0a, 0x0d, 0x7e, 0xf8, 0x50, 0x1a, 0xc4, 0x07, 0x57, 0xb8, 0x3c, 0x62, 0xe3, 0xc8, + 0xac, 0x52, 0x64, 0x10, 0xd0, 0xd9, 0x13, 0x0c, 0x12, 0x29, 0x51, 0xb9, 0xcf, 0xd6, 0x73, 0x8d, + 0x81, 0x54, 0xc0, 0xed, 0x4e, 0x44, 0xa7, 0x2a, 0x85, 0x25, 0xe6, 0xca, 0x7c, 0x8b, 0x56, 0x80 + }, + + new byte[] + { + 0xce, 0xbb, 0xeb, 0x92, 0xea, 0xcb, 0x13, 0xc1, 0xe9, 0x3a, 0xd6, 0xb2, 0xd2, 0x90, 0x17, 0xf8, + 0x42, 0x15, 0x56, 0xb4, 0x65, 0x1c, 0x88, 0x43, 0xc5, 0x5c, 0x36, 0xba, 0xf5, 0x57, 0x67, 0x8d, + 0x31, 0xf6, 0x64, 0x58, 0x9e, 0xf4, 0x22, 0xaa, 0x75, 0x0f, 0x02, 0xb1, 0xdf, 0x6d, 0x73, 0x4d, + 0x7c, 0x26, 0x2e, 0xf7, 0x08, 0x5d, 0x44, 0x3e, 0x9f, 0x14, 0xc8, 0xae, 0x54, 0x10, 0xd8, 0xbc, + 0x1a, 0x6b, 0x69, 0xf3, 0xbd, 0x33, 0xab, 0xfa, 0xd1, 0x9b, 0x68, 0x4e, 0x16, 0x95, 0x91, 0xee, + 0x4c, 0x63, 0x8e, 0x5b, 0xcc, 0x3c, 0x19, 0xa1, 0x81, 0x49, 0x7b, 0xd9, 0x6f, 0x37, 0x60, 0xca, + 0xe7, 0x2b, 0x48, 0xfd, 0x96, 0x45, 0xfc, 0x41, 0x12, 0x0d, 0x79, 0xe5, 0x89, 0x8c, 0xe3, 0x20, + 0x30, 0xdc, 0xb7, 0x6c, 0x4a, 0xb5, 0x3f, 0x97, 0xd4, 0x62, 0x2d, 0x06, 0xa4, 0xa5, 0x83, 0x5f, + 0x2a, 0xda, 0xc9, 0x00, 0x7e, 0xa2, 0x55, 0xbf, 0x11, 0xd5, 0x9c, 0xcf, 0x0e, 0x0a, 0x3d, 0x51, + 0x7d, 0x93, 0x1b, 0xfe, 0xc4, 0x47, 0x09, 0x86, 0x0b, 0x8f, 0x9d, 0x6a, 0x07, 0xb9, 0xb0, 0x98, + 0x18, 0x32, 0x71, 0x4b, 0xef, 0x3b, 0x70, 0xa0, 0xe4, 0x40, 0xff, 0xc3, 0xa9, 0xe6, 0x78, 0xf9, + 0x8b, 0x46, 0x80, 0x1e, 0x38, 0xe1, 0xb8, 0xa8, 0xe0, 0x0c, 0x23, 0x76, 0x1d, 0x25, 0x24, 0x05, + 0xf1, 0x6e, 0x94, 0x28, 0x9a, 0x84, 0xe8, 0xa3, 0x4f, 0x77, 0xd3, 0x85, 0xe2, 0x52, 0xf2, 0x82, + 0x50, 0x7a, 0x2f, 0x74, 0x53, 0xb3, 0x61, 0xaf, 0x39, 0x35, 0xde, 0xcd, 0x1f, 0x99, 0xac, 0xad, + 0x72, 0x2c, 0xdd, 0xd0, 0x87, 0xbe, 0x5e, 0xa6, 0xec, 0x04, 0xc6, 0x03, 0x34, 0xfb, 0xdb, 0x59, + 0xb6, 0xc2, 0x01, 0xf0, 0x5a, 0xed, 0xa7, 0x66, 0x21, 0x7f, 0x8a, 0x27, 0xc7, 0xc0, 0x29, 0xd7 + }, + + new byte[] + { + 0x93, 0xd9, 0x9a, 0xb5, 0x98, 0x22, 0x45, 0xfc, 0xba, 0x6a, 0xdf, 0x02, 0x9f, 0xdc, 0x51, 0x59, + 0x4a, 0x17, 0x2b, 0xc2, 0x94, 0xf4, 0xbb, 0xa3, 0x62, 0xe4, 0x71, 0xd4, 0xcd, 0x70, 0x16, 0xe1, + 0x49, 0x3c, 0xc0, 0xd8, 0x5c, 0x9b, 0xad, 0x85, 0x53, 0xa1, 0x7a, 0xc8, 0x2d, 0xe0, 0xd1, 0x72, + 0xa6, 0x2c, 0xc4, 0xe3, 0x76, 0x78, 0xb7, 0xb4, 0x09, 0x3b, 0x0e, 0x41, 0x4c, 0xde, 0xb2, 0x90, + 0x25, 0xa5, 0xd7, 0x03, 0x11, 0x00, 0xc3, 0x2e, 0x92, 0xef, 0x4e, 0x12, 0x9d, 0x7d, 0xcb, 0x35, + 0x10, 0xd5, 0x4f, 0x9e, 0x4d, 0xa9, 0x55, 0xc6, 0xd0, 0x7b, 0x18, 0x97, 0xd3, 0x36, 0xe6, 0x48, + 0x56, 0x81, 0x8f, 0x77, 0xcc, 0x9c, 0xb9, 0xe2, 0xac, 0xb8, 0x2f, 0x15, 0xa4, 0x7c, 0xda, 0x38, + 0x1e, 0x0b, 0x05, 0xd6, 0x14, 0x6e, 0x6c, 0x7e, 0x66, 0xfd, 0xb1, 0xe5, 0x60, 0xaf, 0x5e, 0x33, + 0x87, 0xc9, 0xf0, 0x5d, 0x6d, 0x3f, 0x88, 0x8d, 0xc7, 0xf7, 0x1d, 0xe9, 0xec, 0xed, 0x80, 0x29, + 0x27, 0xcf, 0x99, 0xa8, 0x50, 0x0f, 0x37, 0x24, 0x28, 0x30, 0x95, 0xd2, 0x3e, 0x5b, 0x40, 0x83, + 0xb3, 0x69, 0x57, 0x1f, 0x07, 0x1c, 0x8a, 0xbc, 0x20, 0xeb, 0xce, 0x8e, 0xab, 0xee, 0x31, 0xa2, + 0x73, 0xf9, 0xca, 0x3a, 0x1a, 0xfb, 0x0d, 0xc1, 0xfe, 0xfa, 0xf2, 0x6f, 0xbd, 0x96, 0xdd, 0x43, + 0x52, 0xb6, 0x08, 0xf3, 0xae, 0xbe, 0x19, 0x89, 0x32, 0x26, 0xb0, 0xea, 0x4b, 0x64, 0x84, 0x82, + 0x6b, 0xf5, 0x79, 0xbf, 0x01, 0x5f, 0x75, 0x63, 0x1b, 0x23, 0x3d, 0x68, 0x2a, 0x65, 0xe8, 0x91, + 0xf6, 0xff, 0x13, 0x58, 0xf1, 0x47, 0x0a, 0x7f, 0xc5, 0xa7, 0xe7, 0x61, 0x5a, 0x06, 0x46, 0x44, + 0x42, 0x04, 0xa0, 0xdb, 0x39, 0x86, 0x54, 0xaa, 0x8c, 0x34, 0x21, 0x8b, 0xf8, 0x0c, 0x74, 0x67 + }, + + new byte[] + { + 0x68, 0x8d, 0xca, 0x4d, 0x73, 0x4b, 0x4e, 0x2a, 0xd4, 0x52, 0x26, 0xb3, 0x54, 0x1e, 0x19, 0x1f, + 0x22, 0x03, 0x46, 0x3d, 0x2d, 0x4a, 0x53, 0x83, 0x13, 0x8a, 0xb7, 0xd5, 0x25, 0x79, 0xf5, 0xbd, + 0x58, 0x2f, 0x0d, 0x02, 0xed, 0x51, 0x9e, 0x11, 0xf2, 0x3e, 0x55, 0x5e, 0xd1, 0x16, 0x3c, 0x66, + 0x70, 0x5d, 0xf3, 0x45, 0x40, 0xcc, 0xe8, 0x94, 0x56, 0x08, 0xce, 0x1a, 0x3a, 0xd2, 0xe1, 0xdf, + 0xb5, 0x38, 0x6e, 0x0e, 0xe5, 0xf4, 0xf9, 0x86, 0xe9, 0x4f, 0xd6, 0x85, 0x23, 0xcf, 0x32, 0x99, + 0x31, 0x14, 0xae, 0xee, 0xc8, 0x48, 0xd3, 0x30, 0xa1, 0x92, 0x41, 0xb1, 0x18, 0xc4, 0x2c, 0x71, + 0x72, 0x44, 0x15, 0xfd, 0x37, 0xbe, 0x5f, 0xaa, 0x9b, 0x88, 0xd8, 0xab, 0x89, 0x9c, 0xfa, 0x60, + 0xea, 0xbc, 0x62, 0x0c, 0x24, 0xa6, 0xa8, 0xec, 0x67, 0x20, 0xdb, 0x7c, 0x28, 0xdd, 0xac, 0x5b, + 0x34, 0x7e, 0x10, 0xf1, 0x7b, 0x8f, 0x63, 0xa0, 0x05, 0x9a, 0x43, 0x77, 0x21, 0xbf, 0x27, 0x09, + 0xc3, 0x9f, 0xb6, 0xd7, 0x29, 0xc2, 0xeb, 0xc0, 0xa4, 0x8b, 0x8c, 0x1d, 0xfb, 0xff, 0xc1, 0xb2, + 0x97, 0x2e, 0xf8, 0x65, 0xf6, 0x75, 0x07, 0x04, 0x49, 0x33, 0xe4, 0xd9, 0xb9, 0xd0, 0x42, 0xc7, + 0x6c, 0x90, 0x00, 0x8e, 0x6f, 0x50, 0x01, 0xc5, 0xda, 0x47, 0x3f, 0xcd, 0x69, 0xa2, 0xe2, 0x7a, + 0xa7, 0xc6, 0x93, 0x0f, 0x0a, 0x06, 0xe6, 0x2b, 0x96, 0xa3, 0x1c, 0xaf, 0x6a, 0x12, 0x84, 0x39, + 0xe7, 0xb0, 0x82, 0xf7, 0xfe, 0x9d, 0x87, 0x5c, 0x81, 0x35, 0xde, 0xb4, 0xa5, 0xfc, 0x80, 0xef, + 0xcb, 0xbb, 0x6b, 0x76, 0xba, 0x5a, 0x7d, 0x78, 0x0b, 0x95, 0xe3, 0xad, 0x74, 0x98, 0x3b, 0x36, + 0x64, 0x6d, 0xdc, 0xf0, 0x59, 0xa9, 0x4c, 0x17, 0x7f, 0x91, 0xb8, 0xc9, 0x57, 0x1b, 0xe0, 0x61 + } + }; + + + } +} diff --git a/bc-sharp-crypto/src/crypto/digests/GOST3411Digest.cs b/bc-sharp-crypto/src/crypto/digests/GOST3411Digest.cs new file mode 100644 index 0000000..218adf6 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/digests/GOST3411Digest.cs @@ -0,0 +1,356 @@ +using System; + +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Utilities; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Digests +{ + /** + * implementation of GOST R 34.11-94 + */ + public class Gost3411Digest + : IDigest, IMemoable + { + private const 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 = MakeC(); + + private byte[] xBuf = new byte[32]; + private int xBufOff; + private ulong byteCount; + + private readonly IBlockCipher cipher = new Gost28147Engine(); + private byte[] sBox; + + private static byte[][] MakeC() + { + byte[][] c = new byte[4][]; + for (int i = 0; i < 4; ++i) + { + c[i] = new byte[32]; + } + return c; + } + + /** + * Standard constructor + */ + public Gost3411Digest() + { + sBox = Gost28147Engine.GetSBox("D-A"); + cipher.Init(true, new ParametersWithSBox(null, sBox)); + + Reset(); + } + + /** + * Constructor to allow use of a particular sbox with GOST28147 + * @see GOST28147Engine#getSBox(String) + */ + public Gost3411Digest(byte[] sBoxParam) + { + sBox = Arrays.Clone(sBoxParam); + cipher.Init(true, new ParametersWithSBox(null, sBox)); + + Reset(); + } + + /** + * Copy constructor. This will copy the state of the provided + * message digest. + */ + public Gost3411Digest(Gost3411Digest t) + { + Reset(t); + } + + public string AlgorithmName + { + get { return "Gost3411"; } + } + + public int GetDigestSize() + { + return DIGEST_LENGTH; + } + + public void Update( + byte input) + { + xBuf[xBufOff++] = input; + if (xBufOff == xBuf.Length) + { + sumByteArray(xBuf); // calc sum M + processBlock(xBuf, 0); + xBufOff = 0; + } + byteCount++; + } + + public void BlockUpdate( + byte[] input, + int inOff, + int length) + { + while ((xBufOff != 0) && (length > 0)) + { + Update(input[inOff]); + inOff++; + length--; + } + + while (length > xBuf.Length) + { + Array.Copy(input, inOff, xBuf, 0, xBuf.Length); + + sumByteArray(xBuf); // calc sum M + processBlock(xBuf, 0); + inOff += xBuf.Length; + length -= xBuf.Length; + byteCount += (uint)xBuf.Length; + } + + // load in the remainder. + while (length > 0) + { + Update(input[inOff]); + inOff++; + length--; + } + } + + // (i + 1 + 4(k - 1)) = 8i + k i = 0-3, k = 1-8 + private byte[] K = new byte[32]; + + private byte[] P(byte[] input) + { + int fourK = 0; + for(int k = 0; k < 8; k++) + { + K[fourK++] = input[k]; + K[fourK++] = input[8 + k]; + K[fourK++] = input[16 + k]; + K[fourK++] = input[24 + k]; + } + + return K; + } + + //A (x) = (x0 ^ x1) || x3 || x2 || x1 + byte[] a = new byte[8]; + private byte[] A(byte[] input) + { + for(int j=0; j<8; j++) + { + a[j]=(byte)(input[j] ^ input[j+8]); + } + + Array.Copy(input, 8, input, 0, 24); + Array.Copy(a, 0, input, 24, 8); + + return input; + } + + //Encrypt function, ECB mode + private void E(byte[] key, byte[] s, int sOff, byte[] input, int inOff) + { + cipher.Init(true, new KeyParameter(key)); + + cipher.ProcessBlock(input, inOff, s, sOff); + } + + // (in:) n16||..||n1 ==> (out:) n1^n2^n3^n4^n13^n16||n16||..||n2 + internal short[] wS = new short[16], w_S = new short[16]; + + private void fw(byte[] input) + { + cpyBytesToShort(input, wS); + w_S[15] = (short)(wS[0] ^ wS[1] ^ wS[2] ^ wS[3] ^ wS[12] ^ wS[15]); + Array.Copy(wS, 1, w_S, 0, 15); + cpyShortToBytes(w_S, input); + } + + // block processing + internal byte[] S = new byte[32], U = new byte[32], V = new byte[32], W = new byte[32]; + + private void processBlock(byte[] input, int inOff) + { + Array.Copy(input, inOff, M, 0, 32); + + //key step 1 + + // H = h3 || h2 || h1 || h0 + // S = s3 || s2 || s1 || s0 + H.CopyTo(U, 0); + M.CopyTo(V, 0); + 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); + } + Array.Copy(S, 0, H, 0, H.Length); + } + + private void finish() + { + ulong bitCount = byteCount * 8; + Pack.UInt64_To_LE(bitCount, L); + + while (xBufOff != 0) + { + Update((byte)0); + } + + processBlock(L, 0); + processBlock(Sum, 0); + } + + public int DoFinal( + byte[] output, + int outOff) + { + finish(); + + H.CopyTo(output, outOff); + + Reset(); + + return DIGEST_LENGTH; + } + + /** + * reset the chaining variables to the IV values. + */ + private static readonly 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; + + Array.Clear(H, 0, H.Length); + Array.Clear(L, 0, L.Length); + Array.Clear(M, 0, M.Length); + Array.Clear(C[1], 0, C[1].Length); // real index C = +1 because index array with 0. + Array.Clear(C[3], 0, C[3].Length); + Array.Clear(Sum, 0, Sum.Length); + Array.Clear(xBuf, 0, xBuf.Length); + + C2.CopyTo(C[2], 0); + } + + // 256 bitsblock modul -> (Sum + a mod (2^256)) + private void sumByteArray( + byte[] input) + { + int carry = 0; + + for (int i = 0; i != Sum.Length; i++) + { + int sum = (Sum[i] & 0xff) + (input[i] & 0xff) + carry; + + Sum[i] = (byte)sum; + + carry = sum >> 8; + } + } + + private static void cpyBytesToShort(byte[] S, short[] wS) + { + for(int i = 0; i < S.Length / 2; i++) + { + wS[i] = (short)(((S[i*2+1]<<8)&0xFF00)|(S[i*2]&0xFF)); + } + } + + private static void cpyShortToBytes(short[] wS, byte[] S) + { + for(int i=0; i> 8); + S[i*2] = (byte)wS[i]; + } + } + + public int GetByteLength() + { + return 32; + } + + public IMemoable Copy() + { + return new Gost3411Digest(this); + } + + public void Reset(IMemoable other) + { + Gost3411Digest t = (Gost3411Digest)other; + + this.sBox = t.sBox; + cipher.Init(true, new ParametersWithSBox(null, sBox)); + + Reset(); + + Array.Copy(t.H, 0, this.H, 0, t.H.Length); + Array.Copy(t.L, 0, this.L, 0, t.L.Length); + Array.Copy(t.M, 0, this.M, 0, t.M.Length); + Array.Copy(t.Sum, 0, this.Sum, 0, t.Sum.Length); + Array.Copy(t.C[1], 0, this.C[1], 0, t.C[1].Length); + Array.Copy(t.C[2], 0, this.C[2], 0, t.C[2].Length); + Array.Copy(t.C[3], 0, this.C[3], 0, t.C[3].Length); + Array.Copy(t.xBuf, 0, this.xBuf, 0, t.xBuf.Length); + + this.xBufOff = t.xBufOff; + this.byteCount = t.byteCount; + } + } + +} diff --git a/bc-sharp-crypto/src/crypto/digests/GOST3411_2012Digest.cs b/bc-sharp-crypto/src/crypto/digests/GOST3411_2012Digest.cs new file mode 100644 index 0000000..4395129 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/digests/GOST3411_2012Digest.cs @@ -0,0 +1,1036 @@ +using System; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Digests +{ + public abstract class GOST3411_2012Digest:IDigest,IMemoable + { + private readonly byte[] IV = new byte[64]; + private readonly byte[] N = new byte[64]; + private readonly byte[] Sigma = new byte[64]; + private readonly byte[] Ki = new byte[64]; + private readonly byte[] m = new byte[64]; + private readonly byte[] h = new byte[64]; + + // Temporary buffers + private readonly byte[] tmp = new byte[64]; + private readonly byte[] block = new byte[64]; + + private int bOff = 64; + + protected GOST3411_2012Digest(byte[] IV) + { + System.Array.Copy(IV,this.IV,64); + System.Array.Copy(IV, h, 64); + } + + public abstract string AlgorithmName { get; } + + public abstract IMemoable Copy(); + + public virtual int DoFinal(byte[] output, int outOff) + { + int lenM = 64 - bOff; + + // At this point it is certain that lenM is smaller than 64 + for (int i = 0; i != 64 - lenM; i++) + { + m[i] = 0; + } + + m[63 - lenM] = 1; + + if (bOff != 64) + { + System.Array.Copy(block, bOff, m, 64 - lenM, lenM); + } + + g_N(h, N, m); + addMod512(N, lenM * 8); + addMod512(Sigma, m); + g_N(h, Zero, N); + g_N(h, Zero, Sigma); + + reverse(h, tmp); + + Array.Copy(tmp, 0, output, outOff, 64); + + Reset(); + return 64; + } + + public int GetByteLength() + { + return 64; + } + + public abstract int GetDigestSize(); + + + public void Reset() + { + bOff = 64; + Arrays.Fill(N, (byte)0); + Arrays.Fill(Sigma, (byte)0); + System.Array.Copy(IV, 0, h, 0, 64); + Arrays.Fill(block, (byte)0); + } + + public void Reset(IMemoable other) + { + GOST3411_2012Digest o = (GOST3411_2012Digest)other; + + System.Array.Copy(o.IV, 0, this.IV, 0, 64); + System.Array.Copy(o.N, 0, this.N, 0, 64); + System.Array.Copy(o.Sigma, 0, this.Sigma, 0, 64); + System.Array.Copy(o.Ki, 0, this.Ki, 0, 64); + System.Array.Copy(o.m, 0, this.m, 0, 64); + System.Array.Copy(o.h, 0, this.h, 0, 64); + + System.Array.Copy(o.block, 0, this.block, 0, 64); + this.bOff = o.bOff; + } + + public void Update(byte input) + { + block[--bOff] = input; + if (bOff == 0) + { + g_N(h, N, block); + addMod512(N, 512); + addMod512(Sigma, block); + bOff = 64; + } + } + + + public void BlockUpdate(byte[] input, int inOff, int len) + { + while (bOff != 64 && len > 0) + { + Update(input[inOff++]); + len--; + } + while (len >= 64) + { + System.Array.Copy(input, inOff, tmp, 0, 64); + reverse(tmp, block); + g_N(h, N, block); + addMod512(N, 512); + addMod512(Sigma, block); + + len -= 64; + inOff += 64; + } + while (len > 0) + { + Update(input[inOff++]); + len--; + } + } + + + + + private void F(byte[] V) + { + ulong[] res = new ulong[8]; + ulong r; + + r = 0; + r ^= T[0][(V[56] & 0xFF)]; + r ^= T[1][(V[48] & 0xFF)]; + r ^= T[2][(V[40] & 0xFF)]; + r ^= T[3][(V[32] & 0xFF)]; + r ^= T[4][(V[24] & 0xFF)]; + r ^= T[5][(V[16] & 0xFF)]; + r ^= T[6][(V[8] & 0xFF)]; + r ^= T[7][(V[0] & 0xFF)]; + res[0] = r; + + r = 0; + r ^= T[0][(V[57] & 0xFF)]; + r ^= T[1][(V[49] & 0xFF)]; + r ^= T[2][(V[41] & 0xFF)]; + r ^= T[3][(V[33] & 0xFF)]; + r ^= T[4][(V[25] & 0xFF)]; + r ^= T[5][(V[17] & 0xFF)]; + r ^= T[6][(V[9] & 0xFF)]; + r ^= T[7][(V[1] & 0xFF)]; + res[1] = r; + + r = 0; + r ^= T[0][(V[58] & 0xFF)]; + r ^= T[1][(V[50] & 0xFF)]; + r ^= T[2][(V[42] & 0xFF)]; + r ^= T[3][(V[34] & 0xFF)]; + r ^= T[4][(V[26] & 0xFF)]; + r ^= T[5][(V[18] & 0xFF)]; + r ^= T[6][(V[10] & 0xFF)]; + r ^= T[7][(V[2] & 0xFF)]; + res[2] = r; + + r = 0; + r ^= T[0][(V[59] & 0xFF)]; + r ^= T[1][(V[51] & 0xFF)]; + r ^= T[2][(V[43] & 0xFF)]; + r ^= T[3][(V[35] & 0xFF)]; + r ^= T[4][(V[27] & 0xFF)]; + r ^= T[5][(V[19] & 0xFF)]; + r ^= T[6][(V[11] & 0xFF)]; + r ^= T[7][(V[3] & 0xFF)]; + res[3] = r; + + r = 0; + r ^= T[0][(V[60] & 0xFF)]; + r ^= T[1][(V[52] & 0xFF)]; + r ^= T[2][(V[44] & 0xFF)]; + r ^= T[3][(V[36] & 0xFF)]; + r ^= T[4][(V[28] & 0xFF)]; + r ^= T[5][(V[20] & 0xFF)]; + r ^= T[6][(V[12] & 0xFF)]; + r ^= T[7][(V[4] & 0xFF)]; + res[4] = r; + + r = 0; + r ^= T[0][(V[61] & 0xFF)]; + r ^= T[1][(V[53] & 0xFF)]; + r ^= T[2][(V[45] & 0xFF)]; + r ^= T[3][(V[37] & 0xFF)]; + r ^= T[4][(V[29] & 0xFF)]; + r ^= T[5][(V[21] & 0xFF)]; + r ^= T[6][(V[13] & 0xFF)]; + r ^= T[7][(V[5] & 0xFF)]; + res[5] = r; + + r = 0; + r ^= T[0][(V[62] & 0xFF)]; + r ^= T[1][(V[54] & 0xFF)]; + r ^= T[2][(V[46] & 0xFF)]; + r ^= T[3][(V[38] & 0xFF)]; + r ^= T[4][(V[30] & 0xFF)]; + r ^= T[5][(V[22] & 0xFF)]; + r ^= T[6][(V[14] & 0xFF)]; + r ^= T[7][(V[6] & 0xFF)]; + res[6] = r; + + r = 0; + r ^= T[0][(V[63] & 0xFF)]; + r ^= T[1][(V[55] & 0xFF)]; + r ^= T[2][(V[47] & 0xFF)]; + r ^= T[3][(V[39] & 0xFF)]; + r ^= T[4][(V[31] & 0xFF)]; + r ^= T[5][(V[23] & 0xFF)]; + r ^= T[6][(V[15] & 0xFF)]; + r ^= T[7][(V[7] & 0xFF)]; + res[7] = r; + + r = res[0]; + V[7] = (byte)(r >> 56); + V[6] = (byte)(r >> 48); + V[5] = (byte)(r >> 40); + V[4] = (byte)(r >> 32); + V[3] = (byte)(r >> 24); + V[2] = (byte)(r >> 16); + V[1] = (byte)(r >> 8); + V[0] = (byte)(r); + + r = res[1]; + V[15] = (byte)(r >> 56); + V[14] = (byte)(r >> 48); + V[13] = (byte)(r >> 40); + V[12] = (byte)(r >> 32); + V[11] = (byte)(r >> 24); + V[10] = (byte)(r >> 16); + V[9] = (byte)(r >> 8); + V[8] = (byte)(r); + + r = res[2]; + V[23] = (byte)(r >> 56); + V[22] = (byte)(r >> 48); + V[21] = (byte)(r >> 40); + V[20] = (byte)(r >> 32); + V[19] = (byte)(r >> 24); + V[18] = (byte)(r >> 16); + V[17] = (byte)(r >> 8); + V[16] = (byte)(r); + + r = res[3]; + V[31] = (byte)(r >> 56); + V[30] = (byte)(r >> 48); + V[29] = (byte)(r >> 40); + V[28] = (byte)(r >> 32); + V[27] = (byte)(r >> 24); + V[26] = (byte)(r >> 16); + V[25] = (byte)(r >> 8); + V[24] = (byte)(r); + + r = res[4]; + V[39] = (byte)(r >> 56); + V[38] = (byte)(r >> 48); + V[37] = (byte)(r >> 40); + V[36] = (byte)(r >> 32); + V[35] = (byte)(r >> 24); + V[34] = (byte)(r >> 16); + V[33] = (byte)(r >> 8); + V[32] = (byte)(r); + + r = res[5]; + V[47] = (byte)(r >> 56); + V[46] = (byte)(r >> 48); + V[45] = (byte)(r >> 40); + V[44] = (byte)(r >> 32); + V[43] = (byte)(r >> 24); + V[42] = (byte)(r >> 16); + V[41] = (byte)(r >> 8); + V[40] = (byte)(r); + + r = res[6]; + V[55] = (byte)(r >> 56); + V[54] = (byte)(r >> 48); + V[53] = (byte)(r >> 40); + V[52] = (byte)(r >> 32); + V[51] = (byte)(r >> 24); + V[50] = (byte)(r >> 16); + V[49] = (byte)(r >> 8); + V[48] = (byte)(r); + + r = res[7]; + V[63] = (byte)(r >> 56); + V[62] = (byte)(r >> 48); + V[61] = (byte)(r >> 40); + V[60] = (byte)(r >> 32); + V[59] = (byte)(r >> 24); + V[58] = (byte)(r >> 16); + V[57] = (byte)(r >> 8); + V[56] = (byte)(r); + } + + private void xor512(byte[] A, byte[] B) + { + for (int i = 0; i < 64; ++i) + { + A[i] ^= B[i]; + } + } + + private void E(byte[] K, byte[] m) + { + System.Array.Copy(K, 0, Ki, 0, 64); + xor512(K, m); + F(K); + for (int i = 0; i < 11; ++i) + { + xor512(Ki, C[i]); + F(Ki); + xor512(K, Ki); + F(K); + } + xor512(Ki, C[11]); + F(Ki); + xor512(K, Ki); + } + + private void g_N(byte[] h, byte[] N, byte[] m) + { + System.Array.Copy(h, 0, tmp, 0, 64); + + xor512(h, N); + F(h); + + E(h, m); + xor512(h, tmp); + xor512(h, m); + } + + private void addMod512(byte[] A, int num) + { + int c; + c = (A[63] & 0xFF) + (num & 0xFF); + A[63] = (byte)c; + + c = (A[62] & 0xFF) + ((num >> 8) & 0xFF) + (c >> 8); + A[62] = (byte)c; + + for (int i = 61; (i >= 0) && (c > 0); --i) + { + c = (A[i] & 0xFF) + (c >> 8); + A[i] = (byte)c; + } + } + + private void addMod512(byte[] A, byte[] B) + { + for (int c = 0, i = 63; i >= 0; --i) + { + c = (A[i] & 0xFF) + (B[i] & 0xFF) + (c >> 8); + A[i] = (byte)c; + } + } + + private void reverse(byte[] src, byte[] dst) + { + int len = src.Length; + for (int i = 0; i < len; i++) + { + dst[len - 1 - i] = src[i]; + } + } + + private static readonly byte[][] C = new byte[][]{ new byte[]{ + (byte)0xb1, (byte)0x08, (byte)0x5b, (byte)0xda, (byte)0x1e, (byte)0xca, (byte)0xda, (byte)0xe9, + (byte)0xeb, (byte)0xcb, (byte)0x2f, (byte)0x81, (byte)0xc0, (byte)0x65, (byte)0x7c, (byte)0x1f, + (byte)0x2f, (byte)0x6a, (byte)0x76, (byte)0x43, (byte)0x2e, (byte)0x45, (byte)0xd0, (byte)0x16, + (byte)0x71, (byte)0x4e, (byte)0xb8, (byte)0x8d, (byte)0x75, (byte)0x85, (byte)0xc4, (byte)0xfc, + (byte)0x4b, (byte)0x7c, (byte)0xe0, (byte)0x91, (byte)0x92, (byte)0x67, (byte)0x69, (byte)0x01, + (byte)0xa2, (byte)0x42, (byte)0x2a, (byte)0x08, (byte)0xa4, (byte)0x60, (byte)0xd3, (byte)0x15, + (byte)0x05, (byte)0x76, (byte)0x74, (byte)0x36, (byte)0xcc, (byte)0x74, (byte)0x4d, (byte)0x23, + (byte)0xdd, (byte)0x80, (byte)0x65, (byte)0x59, (byte)0xf2, (byte)0xa6, (byte)0x45, (byte)0x07}, + + new byte[]{ + (byte)0x6f, (byte)0xa3, (byte)0xb5, (byte)0x8a, (byte)0xa9, (byte)0x9d, (byte)0x2f, (byte)0x1a, + (byte)0x4f, (byte)0xe3, (byte)0x9d, (byte)0x46, (byte)0x0f, (byte)0x70, (byte)0xb5, (byte)0xd7, + (byte)0xf3, (byte)0xfe, (byte)0xea, (byte)0x72, (byte)0x0a, (byte)0x23, (byte)0x2b, (byte)0x98, + (byte)0x61, (byte)0xd5, (byte)0x5e, (byte)0x0f, (byte)0x16, (byte)0xb5, (byte)0x01, (byte)0x31, + (byte)0x9a, (byte)0xb5, (byte)0x17, (byte)0x6b, (byte)0x12, (byte)0xd6, (byte)0x99, (byte)0x58, + (byte)0x5c, (byte)0xb5, (byte)0x61, (byte)0xc2, (byte)0xdb, (byte)0x0a, (byte)0xa7, (byte)0xca, + (byte)0x55, (byte)0xdd, (byte)0xa2, (byte)0x1b, (byte)0xd7, (byte)0xcb, (byte)0xcd, (byte)0x56, + (byte)0xe6, (byte)0x79, (byte)0x04, (byte)0x70, (byte)0x21, (byte)0xb1, (byte)0x9b, (byte)0xb7}, + new byte[]{ + (byte)0xf5, (byte)0x74, (byte)0xdc, (byte)0xac, (byte)0x2b, (byte)0xce, (byte)0x2f, (byte)0xc7, + (byte)0x0a, (byte)0x39, (byte)0xfc, (byte)0x28, (byte)0x6a, (byte)0x3d, (byte)0x84, (byte)0x35, + (byte)0x06, (byte)0xf1, (byte)0x5e, (byte)0x5f, (byte)0x52, (byte)0x9c, (byte)0x1f, (byte)0x8b, + (byte)0xf2, (byte)0xea, (byte)0x75, (byte)0x14, (byte)0xb1, (byte)0x29, (byte)0x7b, (byte)0x7b, + (byte)0xd3, (byte)0xe2, (byte)0x0f, (byte)0xe4, (byte)0x90, (byte)0x35, (byte)0x9e, (byte)0xb1, + (byte)0xc1, (byte)0xc9, (byte)0x3a, (byte)0x37, (byte)0x60, (byte)0x62, (byte)0xdb, (byte)0x09, + (byte)0xc2, (byte)0xb6, (byte)0xf4, (byte)0x43, (byte)0x86, (byte)0x7a, (byte)0xdb, (byte)0x31, + (byte)0x99, (byte)0x1e, (byte)0x96, (byte)0xf5, (byte)0x0a, (byte)0xba, (byte)0x0a, (byte)0xb2}, + new byte[]{ + (byte)0xef, (byte)0x1f, (byte)0xdf, (byte)0xb3, (byte)0xe8, (byte)0x15, (byte)0x66, (byte)0xd2, + (byte)0xf9, (byte)0x48, (byte)0xe1, (byte)0xa0, (byte)0x5d, (byte)0x71, (byte)0xe4, (byte)0xdd, + (byte)0x48, (byte)0x8e, (byte)0x85, (byte)0x7e, (byte)0x33, (byte)0x5c, (byte)0x3c, (byte)0x7d, + (byte)0x9d, (byte)0x72, (byte)0x1c, (byte)0xad, (byte)0x68, (byte)0x5e, (byte)0x35, (byte)0x3f, + (byte)0xa9, (byte)0xd7, (byte)0x2c, (byte)0x82, (byte)0xed, (byte)0x03, (byte)0xd6, (byte)0x75, + (byte)0xd8, (byte)0xb7, (byte)0x13, (byte)0x33, (byte)0x93, (byte)0x52, (byte)0x03, (byte)0xbe, + (byte)0x34, (byte)0x53, (byte)0xea, (byte)0xa1, (byte)0x93, (byte)0xe8, (byte)0x37, (byte)0xf1, + (byte)0x22, (byte)0x0c, (byte)0xbe, (byte)0xbc, (byte)0x84, (byte)0xe3, (byte)0xd1, (byte)0x2e}, + new byte[] { + (byte)0x4b, (byte)0xea, (byte)0x6b, (byte)0xac, (byte)0xad, (byte)0x47, (byte)0x47, (byte)0x99, + (byte)0x9a, (byte)0x3f, (byte)0x41, (byte)0x0c, (byte)0x6c, (byte)0xa9, (byte)0x23, (byte)0x63, + (byte)0x7f, (byte)0x15, (byte)0x1c, (byte)0x1f, (byte)0x16, (byte)0x86, (byte)0x10, (byte)0x4a, + (byte)0x35, (byte)0x9e, (byte)0x35, (byte)0xd7, (byte)0x80, (byte)0x0f, (byte)0xff, (byte)0xbd, + (byte)0xbf, (byte)0xcd, (byte)0x17, (byte)0x47, (byte)0x25, (byte)0x3a, (byte)0xf5, (byte)0xa3, + (byte)0xdf, (byte)0xff, (byte)0x00, (byte)0xb7, (byte)0x23, (byte)0x27, (byte)0x1a, (byte)0x16, + (byte)0x7a, (byte)0x56, (byte)0xa2, (byte)0x7e, (byte)0xa9, (byte)0xea, (byte)0x63, (byte)0xf5, + (byte)0x60, (byte)0x17, (byte)0x58, (byte)0xfd, (byte)0x7c, (byte)0x6c, (byte)0xfe, (byte)0x57}, + new byte[]{ + (byte)0xae, (byte)0x4f, (byte)0xae, (byte)0xae, (byte)0x1d, (byte)0x3a, (byte)0xd3, (byte)0xd9, + (byte)0x6f, (byte)0xa4, (byte)0xc3, (byte)0x3b, (byte)0x7a, (byte)0x30, (byte)0x39, (byte)0xc0, + (byte)0x2d, (byte)0x66, (byte)0xc4, (byte)0xf9, (byte)0x51, (byte)0x42, (byte)0xa4, (byte)0x6c, + (byte)0x18, (byte)0x7f, (byte)0x9a, (byte)0xb4, (byte)0x9a, (byte)0xf0, (byte)0x8e, (byte)0xc6, + (byte)0xcf, (byte)0xfa, (byte)0xa6, (byte)0xb7, (byte)0x1c, (byte)0x9a, (byte)0xb7, (byte)0xb4, + (byte)0x0a, (byte)0xf2, (byte)0x1f, (byte)0x66, (byte)0xc2, (byte)0xbe, (byte)0xc6, (byte)0xb6, + (byte)0xbf, (byte)0x71, (byte)0xc5, (byte)0x72, (byte)0x36, (byte)0x90, (byte)0x4f, (byte)0x35, + (byte)0xfa, (byte)0x68, (byte)0x40, (byte)0x7a, (byte)0x46, (byte)0x64, (byte)0x7d, (byte)0x6e}, + new byte[] { + (byte)0xf4, (byte)0xc7, (byte)0x0e, (byte)0x16, (byte)0xee, (byte)0xaa, (byte)0xc5, (byte)0xec, + (byte)0x51, (byte)0xac, (byte)0x86, (byte)0xfe, (byte)0xbf, (byte)0x24, (byte)0x09, (byte)0x54, + (byte)0x39, (byte)0x9e, (byte)0xc6, (byte)0xc7, (byte)0xe6, (byte)0xbf, (byte)0x87, (byte)0xc9, + (byte)0xd3, (byte)0x47, (byte)0x3e, (byte)0x33, (byte)0x19, (byte)0x7a, (byte)0x93, (byte)0xc9, + (byte)0x09, (byte)0x92, (byte)0xab, (byte)0xc5, (byte)0x2d, (byte)0x82, (byte)0x2c, (byte)0x37, + (byte)0x06, (byte)0x47, (byte)0x69, (byte)0x83, (byte)0x28, (byte)0x4a, (byte)0x05, (byte)0x04, + (byte)0x35, (byte)0x17, (byte)0x45, (byte)0x4c, (byte)0xa2, (byte)0x3c, (byte)0x4a, (byte)0xf3, + (byte)0x88, (byte)0x86, (byte)0x56, (byte)0x4d, (byte)0x3a, (byte)0x14, (byte)0xd4, (byte)0x93}, + new byte[] { + (byte)0x9b, (byte)0x1f, (byte)0x5b, (byte)0x42, (byte)0x4d, (byte)0x93, (byte)0xc9, (byte)0xa7, + (byte)0x03, (byte)0xe7, (byte)0xaa, (byte)0x02, (byte)0x0c, (byte)0x6e, (byte)0x41, (byte)0x41, + (byte)0x4e, (byte)0xb7, (byte)0xf8, (byte)0x71, (byte)0x9c, (byte)0x36, (byte)0xde, (byte)0x1e, + (byte)0x89, (byte)0xb4, (byte)0x44, (byte)0x3b, (byte)0x4d, (byte)0xdb, (byte)0xc4, (byte)0x9a, + (byte)0xf4, (byte)0x89, (byte)0x2b, (byte)0xcb, (byte)0x92, (byte)0x9b, (byte)0x06, (byte)0x90, + (byte)0x69, (byte)0xd1, (byte)0x8d, (byte)0x2b, (byte)0xd1, (byte)0xa5, (byte)0xc4, (byte)0x2f, + (byte)0x36, (byte)0xac, (byte)0xc2, (byte)0x35, (byte)0x59, (byte)0x51, (byte)0xa8, (byte)0xd9, + (byte)0xa4, (byte)0x7f, (byte)0x0d, (byte)0xd4, (byte)0xbf, (byte)0x02, (byte)0xe7, (byte)0x1e}, + new byte[]{ + (byte)0x37, (byte)0x8f, (byte)0x5a, (byte)0x54, (byte)0x16, (byte)0x31, (byte)0x22, (byte)0x9b, + (byte)0x94, (byte)0x4c, (byte)0x9a, (byte)0xd8, (byte)0xec, (byte)0x16, (byte)0x5f, (byte)0xde, + (byte)0x3a, (byte)0x7d, (byte)0x3a, (byte)0x1b, (byte)0x25, (byte)0x89, (byte)0x42, (byte)0x24, + (byte)0x3c, (byte)0xd9, (byte)0x55, (byte)0xb7, (byte)0xe0, (byte)0x0d, (byte)0x09, (byte)0x84, + (byte)0x80, (byte)0x0a, (byte)0x44, (byte)0x0b, (byte)0xdb, (byte)0xb2, (byte)0xce, (byte)0xb1, + (byte)0x7b, (byte)0x2b, (byte)0x8a, (byte)0x9a, (byte)0xa6, (byte)0x07, (byte)0x9c, (byte)0x54, + (byte)0x0e, (byte)0x38, (byte)0xdc, (byte)0x92, (byte)0xcb, (byte)0x1f, (byte)0x2a, (byte)0x60, + (byte)0x72, (byte)0x61, (byte)0x44, (byte)0x51, (byte)0x83, (byte)0x23, (byte)0x5a, (byte)0xdb}, + new byte[] { + (byte)0xab, (byte)0xbe, (byte)0xde, (byte)0xa6, (byte)0x80, (byte)0x05, (byte)0x6f, (byte)0x52, + (byte)0x38, (byte)0x2a, (byte)0xe5, (byte)0x48, (byte)0xb2, (byte)0xe4, (byte)0xf3, (byte)0xf3, + (byte)0x89, (byte)0x41, (byte)0xe7, (byte)0x1c, (byte)0xff, (byte)0x8a, (byte)0x78, (byte)0xdb, + (byte)0x1f, (byte)0xff, (byte)0xe1, (byte)0x8a, (byte)0x1b, (byte)0x33, (byte)0x61, (byte)0x03, + (byte)0x9f, (byte)0xe7, (byte)0x67, (byte)0x02, (byte)0xaf, (byte)0x69, (byte)0x33, (byte)0x4b, + (byte)0x7a, (byte)0x1e, (byte)0x6c, (byte)0x30, (byte)0x3b, (byte)0x76, (byte)0x52, (byte)0xf4, + (byte)0x36, (byte)0x98, (byte)0xfa, (byte)0xd1, (byte)0x15, (byte)0x3b, (byte)0xb6, (byte)0xc3, + (byte)0x74, (byte)0xb4, (byte)0xc7, (byte)0xfb, (byte)0x98, (byte)0x45, (byte)0x9c, (byte)0xed}, + new byte[] { + (byte)0x7b, (byte)0xcd, (byte)0x9e, (byte)0xd0, (byte)0xef, (byte)0xc8, (byte)0x89, (byte)0xfb, + (byte)0x30, (byte)0x02, (byte)0xc6, (byte)0xcd, (byte)0x63, (byte)0x5a, (byte)0xfe, (byte)0x94, + (byte)0xd8, (byte)0xfa, (byte)0x6b, (byte)0xbb, (byte)0xeb, (byte)0xab, (byte)0x07, (byte)0x61, + (byte)0x20, (byte)0x01, (byte)0x80, (byte)0x21, (byte)0x14, (byte)0x84, (byte)0x66, (byte)0x79, + (byte)0x8a, (byte)0x1d, (byte)0x71, (byte)0xef, (byte)0xea, (byte)0x48, (byte)0xb9, (byte)0xca, + (byte)0xef, (byte)0xba, (byte)0xcd, (byte)0x1d, (byte)0x7d, (byte)0x47, (byte)0x6e, (byte)0x98, + (byte)0xde, (byte)0xa2, (byte)0x59, (byte)0x4a, (byte)0xc0, (byte)0x6f, (byte)0xd8, (byte)0x5d, + (byte)0x6b, (byte)0xca, (byte)0xa4, (byte)0xcd, (byte)0x81, (byte)0xf3, (byte)0x2d, (byte)0x1b}, + new byte[] { + (byte)0x37, (byte)0x8e, (byte)0xe7, (byte)0x67, (byte)0xf1, (byte)0x16, (byte)0x31, (byte)0xba, + (byte)0xd2, (byte)0x13, (byte)0x80, (byte)0xb0, (byte)0x04, (byte)0x49, (byte)0xb1, (byte)0x7a, + (byte)0xcd, (byte)0xa4, (byte)0x3c, (byte)0x32, (byte)0xbc, (byte)0xdf, (byte)0x1d, (byte)0x77, + (byte)0xf8, (byte)0x20, (byte)0x12, (byte)0xd4, (byte)0x30, (byte)0x21, (byte)0x9f, (byte)0x9b, + (byte)0x5d, (byte)0x80, (byte)0xef, (byte)0x9d, (byte)0x18, (byte)0x91, (byte)0xcc, (byte)0x86, + (byte)0xe7, (byte)0x1d, (byte)0xa4, (byte)0xaa, (byte)0x88, (byte)0xe1, (byte)0x28, (byte)0x52, + (byte)0xfa, (byte)0xf4, (byte)0x17, (byte)0xd5, (byte)0xd9, (byte)0xb2, (byte)0x1b, (byte)0x99, + (byte)0x48, (byte)0xbc, (byte)0x92, (byte)0x4a, (byte)0xf1, (byte)0x1b, (byte)0xd7, (byte)0x20} + }; + + private static readonly byte[] Zero = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + private readonly static ulong[][] T = { + new ulong[] { + 0xE6F87E5C5B711FD0L, 0x258377800924FA16L, 0xC849E07E852EA4A8L, 0x5B4686A18F06C16AL, + 0x0B32E9A2D77B416EL, 0xABDA37A467815C66L, 0xF61796A81A686676L, 0xF5DC0B706391954BL, + 0x4862F38DB7E64BF1L, 0xFF5C629A68BD85C5L, 0xCB827DA6FCD75795L, 0x66D36DAF69B9F089L, + 0x356C9F74483D83B0L, 0x7CBCECB1238C99A1L, 0x36A702AC31C4708DL, 0x9EB6A8D02FBCDFD6L, + 0x8B19FA51E5B3AE37L, 0x9CCFB5408A127D0BL, 0xBC0C78B508208F5AL, 0xE533E3842288ECEDL, + 0xCEC2C7D377C15FD2L, 0xEC7817B6505D0F5EL, 0xB94CC2C08336871DL, 0x8C205DB4CB0B04ADL, + 0x763C855B28A0892FL, 0x588D1B79F6FF3257L, 0x3FECF69E4311933EL, 0x0FC0D39F803A18C9L, + 0xEE010A26F5F3AD83L, 0x10EFE8F4411979A6L, 0x5DCDA10C7DE93A10L, 0x4A1BEE1D1248E92CL, + 0x53BFF2DB21847339L, 0xB4F50CCFA6A23D09L, 0x5FB4BC9CD84798CDL, 0xE88A2D8B071C56F9L, + 0x7F7771695A756A9CL, 0xC5F02E71A0BA1EBCL, 0xA663F9AB4215E672L, 0x2EB19E22DE5FBB78L, + 0x0DB9CE0F2594BA14L, 0x82520E6397664D84L, 0x2F031E6A0208EA98L, 0x5C7F2144A1BE6BF0L, + 0x7A37CB1CD16362DBL, 0x83E08E2B4B311C64L, 0xCF70479BAB960E32L, 0x856BA986B9DEE71EL, + 0xB5478C877AF56CE9L, 0xB8FE42885F61D6FDL, 0x1BDD0156966238C8L, 0x622157923EF8A92EL, + 0xFC97FF42114476F8L, 0x9D7D350856452CEBL, 0x4C90C9B0E0A71256L, 0x2308502DFBCB016CL, + 0x2D7A03FAA7A64845L, 0xF46E8B38BFC6C4ABL, 0xBDBEF8FDD477DEBAL, 0x3AAC4CEBC8079B79L, + 0xF09CB105E8879D0CL, 0x27FA6A10AC8A58CBL, 0x8960E7C1401D0CEAL, 0x1A6F811E4A356928L, + 0x90C4FB0773D196FFL, 0x43501A2F609D0A9FL, 0xF7A516E0C63F3796L, 0x1CE4A6B3B8DA9252L, + 0x1324752C38E08A9BL, 0xA5A864733BEC154FL, 0x2BF124575549B33FL, 0xD766DB15440DC5C7L, + 0xA7D179E39E42B792L, 0xDADF151A61997FD3L, 0x86A0345EC0271423L, 0x38D5517B6DA939A4L, + 0x6518F077104003B4L, 0x02791D90A5AEA2DDL, 0x88D267899C4A5D0AL, 0x930F66DF0A2865C2L, + 0x4EE9D4204509B08BL, 0x325538916685292AL, 0x412907BFC533A842L, 0xB27E2B62544DC673L, + 0x6C5304456295E007L, 0x5AF406E95351908AL, 0x1F2F3B6BC123616FL, 0xC37B09DC5255E5C6L, + 0x3967D133B1FE6844L, 0x298839C7F0E711E2L, 0x409B87F71964F9A2L, 0xE938ADC3DB4B0719L, + 0x0C0B4E47F9C3EBF4L, 0x5534D576D36B8843L, 0x4610A05AEB8B02D8L, 0x20C3CDF58232F251L, + 0x6DE1840DBEC2B1E7L, 0xA0E8DE06B0FA1D08L, 0x7B854B540D34333BL, 0x42E29A67BCCA5B7FL, + 0xD8A6088AC437DD0EL, 0xC63BB3A9D943ED81L, 0x21714DBD5E65A3B1L, 0x6761EDE7B5EEA169L, + 0x2431F7C8D573ABF6L, 0xD51FC685E1A3671AL, 0x5E063CD40410C92DL, 0x283AB98F2CB04002L, + 0x8FEBC06CB2F2F790L, 0x17D64F116FA1D33CL, 0xE07359F1A99EE4AAL, 0x784ED68C74CDC006L, + 0x6E2A19D5C73B42DAL, 0x8712B4161C7045C3L, 0x371582E4ED93216DL, 0xACE390414939F6FCL, + 0x7EC5F12186223B7CL, 0xC0B094042BAC16FBL, 0xF9D745379A527EBFL, 0x737C3F2EA3B68168L, + 0x33E7B8D9BAD278CAL, 0xA9A32A34C22FFEBBL, 0xE48163CCFEDFBD0DL, 0x8E5940246EA5A670L, + 0x51C6EF4B842AD1E4L, 0x22BAD065279C508CL, 0xD91488C218608CEEL, 0x319EA5491F7CDA17L, + 0xD394E128134C9C60L, 0x094BF43272D5E3B3L, 0x9BF612A5A4AAD791L, 0xCCBBDA43D26FFD0FL, + 0x34DE1F3C946AD250L, 0x4F5B5468995EE16BL, 0xDF9FAF6FEA8F7794L, 0x2648EA5870DD092BL, + 0xBFC7E56D71D97C67L, 0xDDE6B2FF4F21D549L, 0x3C276B463AE86003L, 0x91767B4FAF86C71FL, + 0x68A13E7835D4B9A0L, 0xB68C115F030C9FD4L, 0x141DD2C916582001L, 0x983D8F7DDD5324ACL, + 0x64AA703FCC175254L, 0xC2C989948E02B426L, 0x3E5E76D69F46C2DEL, 0x50746F03587D8004L, + 0x45DB3D829272F1E5L, 0x60584A029B560BF3L, 0xFBAE58A73FFCDC62L, 0xA15A5E4E6CAD4CE8L, + 0x4BA96E55CE1FB8CCL, 0x08F9747AAE82B253L, 0xC102144CF7FB471BL, 0x9F042898F3EB8E36L, + 0x068B27ADF2EFFB7AL, 0xEDCA97FE8C0A5EBEL, 0x778E0513F4F7D8CFL, 0x302C2501C32B8BF7L, + 0x8D92DDFC175C554DL, 0xF865C57F46052F5FL, 0xEAF3301BA2B2F424L, 0xAA68B7ECBBD60D86L, + 0x998F0F350104754CL, 0x0000000000000000L, 0xF12E314D34D0CCECL, 0x710522BE061823B5L, + 0xAF280D9930C005C1L, 0x97FD5CE25D693C65L, 0x19A41CC633CC9A15L, 0x95844172F8C79EB8L, + 0xDC5432B7937684A9L, 0x9436C13A2490CF58L, 0x802B13F332C8EF59L, 0xC442AE397CED4F5CL, + 0xFA1CD8EFE3AB8D82L, 0xF2E5AC954D293FD1L, 0x6AD823E8907A1B7DL, 0x4D2249F83CF043B6L, + 0x03CB9DD879F9F33DL, 0xDE2D2F2736D82674L, 0x2A43A41F891EE2DFL, 0x6F98999D1B6C133AL, + 0xD4AD46CD3DF436FAL, 0xBB35DF50269825C0L, 0x964FDCAA813E6D85L, 0xEB41B0537EE5A5C4L, + 0x0540BA758B160847L, 0xA41AE43BE7BB44AFL, 0xE3B8C429D0671797L, 0x819993BBEE9FBEB9L, + 0xAE9A8DD1EC975421L, 0xF3572CDD917E6E31L, 0x6393D7DAE2AFF8CEL, 0x47A2201237DC5338L, + 0xA32343DEC903EE35L, 0x79FC56C4A89A91E6L, 0x01B28048DC5751E0L, 0x1296F564E4B7DB7BL, + 0x75F7188351597A12L, 0xDB6D9552BDCE2E33L, 0x1E9DBB231D74308FL, 0x520D7293FDD322D9L, + 0xE20A44610C304677L, 0xFEEEE2D2B4EAD425L, 0xCA30FDEE20800675L, 0x61EACA4A47015A13L, + 0xE74AFE1487264E30L, 0x2CC883B27BF119A5L, 0x1664CF59B3F682DCL, 0xA811AA7C1E78AF5BL, + 0x1D5626FB648DC3B2L, 0xB73E9117DF5BCE34L, 0xD05F7CF06AB56F5DL, 0xFD257F0ACD132718L, + 0x574DC8E676C52A9EL, 0x0739A7E52EB8AA9AL, 0x5486553E0F3CD9A3L, 0x56FF48AEAA927B7EL, + 0xBE756525AD8E2D87L, 0x7D0E6CF9FFDBC841L, 0x3B1ECCA31450CA99L, 0x6913BE30E983E840L, + 0xAD511009956EA71CL, 0xB1B5B6BA2DB4354EL, 0x4469BDCA4E25A005L, 0x15AF5281CA0F71E1L, + 0x744598CB8D0E2BF2L, 0x593F9B312AA863B7L, 0xEFB38A6E29A4FC63L, 0x6B6AA3A04C2D4A9DL, + 0x3D95EB0EE6BF31E3L, 0xA291C3961554BFD5L, 0x18169C8EEF9BCBF5L, 0x115D68BC9D4E2846L, + 0xBA875F18FACF7420L, 0xD1EDFCB8B6E23EBDL, 0xB00736F2F1E364AEL, 0x84D929CE6589B6FEL, + 0x70B7A2F6DA4F7255L, 0x0E7253D75C6D4929L, 0x04F23A3D574159A7L, 0x0A8069EA0B2C108EL, + 0x49D073C56BB11A11L, 0x8AAB7A1939E4FFD7L, 0xCD095A0B0E38ACEFL, 0xC9FB60365979F548L, + 0x92BDE697D67F3422L, 0xC78933E10514BC61L, 0xE1C1D9B975C9B54AL, 0xD2266160CF1BCD80L, + 0x9A4492ED78FD8671L, 0xB3CCAB2A881A9793L, 0x72CEBF667FE1D088L, 0xD6D45B5D985A9427L + }, + new ulong[]{ + 0xC811A8058C3F55DEL, 0x65F5B43196B50619L, 0xF74F96B1D6706E43L, 0x859D1E8BCB43D336L, + 0x5AAB8A85CCFA3D84L, 0xF9C7BF99C295FCFDL, 0xA21FD5A1DE4B630FL, 0xCDB3EF763B8B456DL, + 0x803F59F87CF7C385L, 0xB27C73BE5F31913CL, 0x98E3AC6633B04821L, 0xBF61674C26B8F818L, + 0x0FFBC995C4C130C8L, 0xAAA0862010761A98L, 0x6057F342210116AAL, 0xF63C760C0654CC35L, + 0x2DDB45CC667D9042L, 0xBCF45A964BD40382L, 0x68E8A0C3EF3C6F3DL, 0xA7BD92D269FF73BCL, + 0x290AE20201ED2287L, 0xB7DE34CDE885818FL, 0xD901EEA7DD61059BL, 0xD6FA273219A03553L, + 0xD56F1AE874CCCEC9L, 0xEA31245C2E83F554L, 0x7034555DA07BE499L, 0xCE26D2AC56E7BEF7L, + 0xFD161857A5054E38L, 0x6A0E7DA4527436D1L, 0x5BD86A381CDE9FF2L, 0xCAF7756231770C32L, + 0xB09AAED9E279C8D0L, 0x5DEF1091C60674DBL, 0x111046A2515E5045L, 0x23536CE4729802FCL, + 0xC50CBCF7F5B63CFAL, 0x73A16887CD171F03L, 0x7D2941AFD9F28DBDL, 0x3F5E3EB45A4F3B9DL, + 0x84EEFE361B677140L, 0x3DB8E3D3E7076271L, 0x1A3A28F9F20FD248L, 0x7EBC7C75B49E7627L, + 0x74E5F293C7EB565CL, 0x18DCF59E4F478BA4L, 0x0C6EF44FA9ADCB52L, 0xC699812D98DAC760L, + 0x788B06DC6E469D0EL, 0xFC65F8EA7521EC4EL, 0x30A5F7219E8E0B55L, 0x2BEC3F65BCA57B6BL, + 0xDDD04969BAF1B75EL, 0x99904CDBE394EA57L, 0x14B201D1E6EA40F6L, 0xBBB0C08241284ADDL, + 0x50F20463BF8F1DFFL, 0xE8D7F93B93CBACB8L, 0x4D8CB68E477C86E8L, 0xC1DD1B3992268E3FL, + 0x7C5AA11209D62FCBL, 0x2F3D98ABDB35C9AEL, 0x671369562BFD5FF5L, 0x15C1E16C36CEE280L, + 0x1D7EB2EDF8F39B17L, 0xDA94D37DB00DFE01L, 0x877BC3EC760B8ADAL, 0xCB8495DFE153AE44L, + 0x05A24773B7B410B3L, 0x12857B783C32ABDFL, 0x8EB770D06812513BL, 0x536739B9D2E3E665L, + 0x584D57E271B26468L, 0xD789C78FC9849725L, 0xA935BBFA7D1AE102L, 0x8B1537A3DFA64188L, + 0xD0CD5D9BC378DE7AL, 0x4AC82C9A4D80CFB7L, 0x42777F1B83BDB620L, 0x72D2883A1D33BD75L, + 0x5E7A2D4BAB6A8F41L, 0xF4DAAB6BBB1C95D9L, 0x905CFFE7FD8D31B6L, 0x83AA6422119B381FL, + 0xC0AEFB8442022C49L, 0xA0F908C663033AE3L, 0xA428AF0804938826L, 0xADE41C341A8A53C7L, + 0xAE7121EE77E6A85DL, 0xC47F5C4A25929E8CL, 0xB538E9AA55CDD863L, 0x06377AA9DAD8EB29L, + 0xA18AE87BB3279895L, 0x6EDFDA6A35E48414L, 0x6B7D9D19825094A7L, 0xD41CFA55A4E86CBFL, + 0xE5CAEDC9EA42C59CL, 0xA36C351C0E6FC179L, 0x5181E4DE6FABBF89L, 0xFFF0C530184D17D4L, + 0x9D41EB1584045892L, 0x1C0D525028D73961L, 0xF178EC180CA8856AL, 0x9A0571018EF811CDL, + 0x4091A27C3EF5EFCCL, 0x19AF15239F6329D2L, 0x347450EFF91EB990L, 0xE11B4A078DD27759L, + 0xB9561DE5FC601331L, 0x912F1F5A2DA993C0L, 0x1654DCB65BA2191AL, 0x3E2DDE098A6B99EBL, + 0x8A66D71E0F82E3FEL, 0x8C51ADB7D55A08D7L, 0x4533E50F8941FF7FL, 0x02E6DD67BD4859ECL, + 0xE068AABA5DF6D52FL, 0xC24826E3FF4A75A5L, 0x6C39070D88ACDDF8L, 0x6486548C4691A46FL, + 0xD1BEBD26135C7C0CL, 0xB30F93038F15334AL, 0x82D9849FC1BF9A69L, 0x9C320BA85420FAE4L, + 0xFA528243AFF90767L, 0x9ED4D6CFE968A308L, 0xB825FD582C44B147L, 0x9B7691BC5EDCB3BBL, + 0xC7EA619048FE6516L, 0x1063A61F817AF233L, 0x47D538683409A693L, 0x63C2CE984C6DED30L, + 0x2A9FDFD86C81D91DL, 0x7B1E3B06032A6694L, 0x666089EBFBD9FD83L, 0x0A598EE67375207BL, + 0x07449A140AFC495FL, 0x2CA8A571B6593234L, 0x1F986F8A45BBC2FBL, 0x381AA4A050B372C2L, + 0x5423A3ADD81FAF3AL, 0x17273C0B8B86BB6CL, 0xFE83258DC869B5A2L, 0x287902BFD1C980F1L, + 0xF5A94BD66B3837AFL, 0x88800A79B2CABA12L, 0x55504310083B0D4CL, 0xDF36940E07B9EEB2L, + 0x04D1A7CE6790B2C5L, 0x612413FFF125B4DCL, 0x26F12B97C52C124FL, 0x86082351A62F28ACL, + 0xEF93632F9937E5E7L, 0x3507B052293A1BE6L, 0xE72C30AE570A9C70L, 0xD3586041AE1425E0L, + 0xDE4574B3D79D4CC4L, 0x92BA228040C5685AL, 0xF00B0CA5DC8C271CL, 0xBE1287F1F69C5A6EL, + 0xF39E317FB1E0DC86L, 0x495D114020EC342DL, 0x699B407E3F18CD4BL, 0xDCA3A9D46AD51528L, + 0x0D1D14F279896924L, 0x0000000000000000L, 0x593EB75FA196C61EL, 0x2E4E78160B116BD8L, + 0x6D4AE7B058887F8EL, 0xE65FD013872E3E06L, 0x7A6DDBBBD30EC4E2L, 0xAC97FC89CAAEF1B1L, + 0x09CCB33C1E19DBE1L, 0x89F3EAC462EE1864L, 0x7770CF49AA87ADC6L, 0x56C57ECA6557F6D6L, + 0x03953DDA6D6CFB9AL, 0x36928D884456E07CL, 0x1EEB8F37959F608DL, 0x31D6179C4EAAA923L, + 0x6FAC3AD7E5C02662L, 0x43049FA653991456L, 0xABD3669DC052B8EEL, 0xAF02C153A7C20A2BL, + 0x3CCB036E3723C007L, 0x93C9C23D90E1CA2CL, 0xC33BC65E2F6ED7D3L, 0x4CFF56339758249EL, + 0xB1E94E64325D6AA6L, 0x37E16D359472420AL, 0x79F8E661BE623F78L, 0x5214D90402C74413L, + 0x482EF1FDF0C8965BL, 0x13F69BC5EC1609A9L, 0x0E88292814E592BEL, 0x4E198B542A107D72L, + 0xCCC00FCBEBAFE71BL, 0x1B49C844222B703EL, 0x2564164DA840E9D5L, 0x20C6513E1FF4F966L, + 0xBAC3203F910CE8ABL, 0xF2EDD1C261C47EF0L, 0x814CB945ACD361F3L, 0x95FEB8944A392105L, + 0x5C9CF02C1622D6ADL, 0x971865F3F77178E9L, 0xBD87BA2B9BF0A1F4L, 0x444005B259655D09L, + 0xED75BE48247FBC0BL, 0x7596122E17CFF42AL, 0xB44B091785E97A15L, 0x966B854E2755DA9FL, + 0xEEE0839249134791L, 0x32432A4623C652B9L, 0xA8465B47AD3E4374L, 0xF8B45F2412B15E8BL, + 0x2417F6F078644BA3L, 0xFB2162FE7FDDA511L, 0x4BBBCC279DA46DC1L, 0x0173E0BDD024A276L, + 0x22208C59A2BCA08AL, 0x8FC4906DB836F34DL, 0xE4B90D743A6667EAL, 0x7147B5E0705F46EFL, + 0x2782CB2A1508B039L, 0xEC065EF5F45B1E7DL, 0x21B5B183CFD05B10L, 0xDBE733C060295C77L, + 0x9FA73672394C017EL, 0xCF55321186C31C81L, 0xD8720E1A0D45A7EDL, 0x3B8F997A3DDF8958L, + 0x3AFC79C7EDFB2B2EL, 0xE9A4198643EF0ECEL, 0x5F09CDF67B4E2D37L, 0x4F6A6BE9FA34DF04L, + 0xB6ADD47038A123F9L, 0x8D224D0A057EAAA1L, 0xC96248B85C1BF7A8L, 0xE3FD9760309A2EB5L, + 0x0B2A6E5BA351820DL, 0xEB42C4E1FEA75722L, 0x948D58299A1D8373L, 0x7FCF9CC864BAD451L, + 0xA55B4FB5D4B72A50L, 0x08BF5381CE3D7997L, 0x46A6D8D5E42D04E5L, 0xD22B80FC7E308796L, + 0x57B69E77B57354A0L, 0x3969441D8097D0B4L, 0x3330CAFBF3E2F0CFL, 0xE28E77DDE0BE8CC3L, + 0x62B12E259C494F46L, 0xA6CE726FB9DBD1CAL, 0x41E242C1EED14DBAL, 0x76032FF47AA30FB0L + }, + new ulong[]{ + 0x45B268A93ACDE4CCL, 0xAF7F0BE884549D08L, 0x048354B3C1468263L, 0x925435C2C80EFED2L, + 0xEE4E37F27FDFFBA7L, 0x167A33920C60F14DL, 0xFB123B52EA03E584L, 0x4A0CAB53FDBB9007L, + 0x9DEAF6380F788A19L, 0xCB48EC558F0CB32AL, 0xB59DC4B2D6FEF7E0L, 0xDCDBCA22F4F3ECB6L, + 0x11DF5813549A9C40L, 0xE33FDEDF568ACED3L, 0xA0C1C8124322E9C3L, 0x07A56B8158FA6D0DL, + 0x77279579B1E1F3DDL, 0xD9B18B74422AC004L, 0xB8EC2D9FFFABC294L, 0xF4ACF8A82D75914FL, + 0x7BBF69B1EF2B6878L, 0xC4F62FAF487AC7E1L, 0x76CE809CC67E5D0CL, 0x6711D88F92E4C14CL, + 0x627B99D9243DEDFEL, 0x234AA5C3DFB68B51L, 0x909B1F15262DBF6DL, 0x4F66EA054B62BCB5L, + 0x1AE2CF5A52AA6AE8L, 0xBEA053FBD0CE0148L, 0xED6808C0E66314C9L, 0x43FE16CD15A82710L, + 0xCD049231A06970F6L, 0xE7BC8A6C97CC4CB0L, 0x337CE835FCB3B9C0L, 0x65DEF2587CC780F3L, + 0x52214EDE4132BB50L, 0x95F15E4390F493DFL, 0x870839625DD2E0F1L, 0x41313C1AFB8B66AFL, + 0x91720AF051B211BCL, 0x477D427ED4EEA573L, 0x2E3B4CEEF6E3BE25L, 0x82627834EB0BCC43L, + 0x9C03E3DD78E724C8L, 0x2877328AD9867DF9L, 0x14B51945E243B0F2L, 0x574B0F88F7EB97E2L, + 0x88B6FA989AA4943AL, 0x19C4F068CB168586L, 0x50EE6409AF11FAEFL, 0x7DF317D5C04EABA4L, + 0x7A567C5498B4C6A9L, 0xB6BBFB804F42188EL, 0x3CC22BCF3BC5CD0BL, 0xD04336EAAA397713L, + 0xF02FAC1BEC33132CL, 0x2506DBA7F0D3488DL, 0xD7E65D6BF2C31A1EL, 0x5EB9B2161FF820F5L, + 0x842E0650C46E0F9FL, 0x716BEB1D9E843001L, 0xA933758CAB315ED4L, 0x3FE414FDA2792265L, + 0x27C9F1701EF00932L, 0x73A4C1CA70A771BEL, 0x94184BA6E76B3D0EL, 0x40D829FF8C14C87EL, + 0x0FBEC3FAC77674CBL, 0x3616A9634A6A9572L, 0x8F139119C25EF937L, 0xF545ED4D5AEA3F9EL, + 0xE802499650BA387BL, 0x6437E7BD0B582E22L, 0xE6559F89E053E261L, 0x80AD52E305288DFCL, + 0x6DC55A23E34B9935L, 0xDE14E0F51AD0AD09L, 0xC6390578A659865EL, 0x96D7617109487CB1L, + 0xE2D6CB3A21156002L, 0x01E915E5779FAED1L, 0xADB0213F6A77DCB7L, 0x9880B76EB9A1A6ABL, + 0x5D9F8D248644CF9BL, 0xFD5E4536C5662658L, 0xF1C6B9FE9BACBDFDL, 0xEACD6341BE9979C4L, + 0xEFA7221708405576L, 0x510771ECD88E543EL, 0xC2BA51CB671F043DL, 0x0AD482AC71AF5879L, + 0xFE787A045CDAC936L, 0xB238AF338E049AEDL, 0xBD866CC94972EE26L, 0x615DA6EBBD810290L, + 0x3295FDD08B2C1711L, 0xF834046073BF0AEAL, 0xF3099329758FFC42L, 0x1CAEB13E7DCFA934L, + 0xBA2307481188832BL, 0x24EFCE42874CE65CL, 0x0E57D61FB0E9DA1AL, 0xB3D1BAD6F99B343CL, + 0xC0757B1C893C4582L, 0x2B510DB8403A9297L, 0x5C7698C1F1DB614AL, 0x3E0D0118D5E68CB4L, + 0xD60F488E855CB4CFL, 0xAE961E0DF3CB33D9L, 0x3A8E55AB14A00ED7L, 0x42170328623789C1L, + 0x838B6DD19C946292L, 0x895FEF7DED3B3AEBL, 0xCFCBB8E64E4A3149L, 0x064C7E642F65C3DCL, + 0x3D2B3E2A4C5A63DAL, 0x5BD3F340A9210C47L, 0xB474D157A1615931L, 0xAC5934DA1DE87266L, + 0x6EE365117AF7765BL, 0xC86ED36716B05C44L, 0x9BA6885C201D49C5L, 0xB905387A88346C45L, + 0x131072C4BAB9DDFFL, 0xBF49461EA751AF99L, 0xD52977BC1CE05BA1L, 0xB0F785E46027DB52L, + 0x546D30BA6E57788CL, 0x305AD707650F56AEL, 0xC987C682612FF295L, 0xA5AB8944F5FBC571L, + 0x7ED528E759F244CAL, 0x8DDCBBCE2C7DB888L, 0xAA154ABE328DB1BAL, 0x1E619BE993ECE88BL, + 0x09F2BD9EE813B717L, 0x7401AA4B285D1CB3L, 0x21858F143195CAEEL, 0x48C381841398D1B8L, + 0xFCB750D3B2F98889L, 0x39A86A998D1CE1B9L, 0x1F888E0CE473465AL, 0x7899568376978716L, + 0x02CF2AD7EE2341BFL, 0x85C713B5B3F1A14EL, 0xFF916FE12B4567E7L, 0x7C1A0230B7D10575L, + 0x0C98FCC85ECA9BA5L, 0xA3E7F720DA9E06ADL, 0x6A6031A2BBB1F438L, 0x973E74947ED7D260L, + 0x2CF4663918C0FF9AL, 0x5F50A7F368678E24L, 0x34D983B4A449D4CDL, 0x68AF1B755592B587L, + 0x7F3C3D022E6DEA1BL, 0xABFC5F5B45121F6BL, 0x0D71E92D29553574L, 0xDFFDF5106D4F03D8L, + 0x081BA87B9F8C19C6L, 0xDB7EA1A3AC0981BBL, 0xBBCA12AD66172DFAL, 0x79704366010829C7L, + 0x179326777BFF5F9CL, 0x0000000000000000L, 0xEB2476A4C906D715L, 0x724DD42F0738DF6FL, + 0xB752EE6538DDB65FL, 0x37FFBC863DF53BA3L, 0x8EFA84FCB5C157E6L, 0xE9EB5C73272596AAL, + 0x1B0BDABF2535C439L, 0x86E12C872A4D4E20L, 0x9969A28BCE3E087AL, 0xFAFB2EB79D9C4B55L, + 0x056A4156B6D92CB2L, 0x5A3AE6A5DEBEA296L, 0x22A3B026A8292580L, 0x53C85B3B36AD1581L, + 0xB11E900117B87583L, 0xC51F3A4A3FE56930L, 0xE019E1EDCF3621BDL, 0xEC811D2591FCBA18L, + 0x445B7D4C4D524A1DL, 0xA8DA6069DCAEF005L, 0x58F5CC72309DE329L, 0xD4C062596B7FF570L, + 0xCE22AD0339D59F98L, 0x591CD99747024DF8L, 0x8B90C5AA03187B54L, 0xF663D27FC356D0F0L, + 0xD8589E9135B56ED5L, 0x35309651D3D67A1CL, 0x12F96721CD26732EL, 0xD28C1C3D441A36ACL, + 0x492A946164077F69L, 0x2D1D73DC6F5F514BL, 0x6F0A70F40D68D88AL, 0x60B4B30ECA1EAC41L, + 0xD36509D83385987DL, 0x0B3D97490630F6A8L, 0x9ECCC90A96C46577L, 0xA20EE2C5AD01A87CL, + 0xE49AB55E0E70A3DEL, 0xA4429CA182646BA0L, 0xDA97B446DB962F6AL, 0xCCED87D4D7F6DE27L, + 0x2AB8185D37A53C46L, 0x9F25DCEFE15BCBA6L, 0xC19C6EF9FEA3EB53L, 0xA764A3931BD884CEL, + 0x2FD2590B817C10F4L, 0x56A21A6D80743933L, 0xE573A0BB79EF0D0FL, 0x155C0CA095DC1E23L, + 0x6C2C4FC694D437E4L, 0x10364DF623053291L, 0xDD32DFC7836C4267L, 0x03263F3299BCEF6EL, + 0x66F8CD6AE57B6F9DL, 0x8C35AE2B5BE21659L, 0x31B3C2E21290F87FL, 0x93BD2027BF915003L, + 0x69460E90220D1B56L, 0x299E276FAE19D328L, 0x63928C3C53A2432FL, 0x7082FEF8E91B9ED0L, + 0xBC6F792C3EED40F7L, 0x4C40D537D2DE53DBL, 0x75E8BFAE5FC2B262L, 0x4DA9C0D2A541FD0AL, + 0x4E8FFFE03CFD1264L, 0x2620E495696FA7E3L, 0xE1F0F408B8A98F6CL, 0xD1AA230FDDA6D9C2L, + 0xC7D0109DD1C6288FL, 0x8A79D04F7487D585L, 0x4694579BA3710BA2L, 0x38417F7CFA834F68L, + 0x1D47A4DB0A5007E5L, 0x206C9AF1460A643FL, 0xA128DDF734BD4712L, 0x8144470672B7232DL, + 0xF2E086CC02105293L, 0x182DE58DBC892B57L, 0xCAA1F9B0F8931DFBL, 0x6B892447CC2E5AE9L, + 0xF9DD11850420A43BL, 0x4BE5BEB68A243ED6L, 0x5584255F19C8D65DL, 0x3B67404E633FA006L, + 0xA68DB6766C472A1FL, 0xF78AC79AB4C97E21L, 0xC353442E1080AAECL, 0x9A4F9DB95782E714L + }, + new ulong[] { + 0x05BA7BC82C9B3220L, 0x31A54665F8B65E4FL, 0xB1B651F77547F4D4L, 0x8BFA0D857BA46682L, + 0x85A96C5AA16A98BBL, 0x990FAEF908EB79C9L, 0xA15E37A247F4A62DL, 0x76857DCD5D27741EL, + 0xF8C50B800A1820BCL, 0xBE65DCB201F7A2B4L, 0x666D1B986F9426E7L, 0x4CC921BF53C4E648L, + 0x95410A0F93D9CA42L, 0x20CDCCAA647BA4EFL, 0x429A4060890A1871L, 0x0C4EA4F69B32B38BL, + 0xCCDA362DDE354CD3L, 0x96DC23BC7C5B2FA9L, 0xC309BB68AA851AB3L, 0xD26131A73648E013L, + 0x021DC52941FC4DB2L, 0xCD5ADAB7704BE48AL, 0xA77965D984ED71E6L, 0x32386FD61734BBA4L, + 0xE82D6DD538AB7245L, 0x5C2147EA6177B4B1L, 0x5DA1AB70CF091CE8L, 0xAC907FCE72B8BDFFL, + 0x57C85DFD972278A8L, 0xA4E44C6A6B6F940DL, 0x3851995B4F1FDFE4L, 0x62578CCAED71BC9EL, + 0xD9882BB0C01D2C0AL, 0x917B9D5D113C503BL, 0xA2C31E11A87643C6L, 0xE463C923A399C1CEL, + 0xF71686C57EA876DCL, 0x87B4A973E096D509L, 0xAF0D567D9D3A5814L, 0xB40C2A3F59DCC6F4L, + 0x3602F88495D121DDL, 0xD3E1DD3D9836484AL, 0xF945E71AA46688E5L, 0x7518547EB2A591F5L, + 0x9366587450C01D89L, 0x9EA81018658C065BL, 0x4F54080CBC4603A3L, 0x2D0384C65137BF3DL, + 0xDC325078EC861E2AL, 0xEA30A8FC79573FF7L, 0x214D2030CA050CB6L, 0x65F0322B8016C30CL, + 0x69BE96DD1B247087L, 0xDB95EE9981E161B8L, 0xD1FC1814D9CA05F8L, 0x820ED2BBCC0DE729L, + 0x63D76050430F14C7L, 0x3BCCB0E8A09D3A0FL, 0x8E40764D573F54A2L, 0x39D175C1E16177BDL, + 0x12F5A37C734F1F4BL, 0xAB37C12F1FDFC26DL, 0x5648B167395CD0F1L, 0x6C04ED1537BF42A7L, + 0xED97161D14304065L, 0x7D6C67DAAB72B807L, 0xEC17FA87BA4EE83CL, 0xDFAF79CB0304FBC1L, + 0x733F060571BC463EL, 0x78D61C1287E98A27L, 0xD07CF48E77B4ADA1L, 0xB9C262536C90DD26L, + 0xE2449B5860801605L, 0x8FC09AD7F941FCFBL, 0xFAD8CEA94BE46D0EL, 0xA343F28B0608EB9FL, + 0x9B126BD04917347BL, 0x9A92874AE7699C22L, 0x1B017C42C4E69EE0L, 0x3A4C5C720EE39256L, + 0x4B6E9F5E3EA399DAL, 0x6BA353F45AD83D35L, 0xE7FEE0904C1B2425L, 0x22D009832587E95DL, + 0x842980C00F1430E2L, 0xC6B3C0A0861E2893L, 0x087433A419D729F2L, 0x341F3DADD42D6C6FL, + 0xEE0A3FAEFBB2A58EL, 0x4AEE73C490DD3183L, 0xAAB72DB5B1A16A34L, 0xA92A04065E238FDFL, + 0x7B4B35A1686B6FCCL, 0x6A23BF6EF4A6956CL, 0x191CB96B851AD352L, 0x55D598D4D6DE351AL, + 0xC9604DE5F2AE7EF3L, 0x1CA6C2A3A981E172L, 0xDE2F9551AD7A5398L, 0x3025AAFF56C8F616L, + 0x15521D9D1E2860D9L, 0x506FE31CFA45073AL, 0x189C55F12B647B0BL, 0x0180EC9AAE7EA859L, + 0x7CEC8B40050C105EL, 0x2350E5198BF94104L, 0xEF8AD33455CC0DD7L, 0x07A7BEE16D677F92L, + 0xE5E325B90DE76997L, 0x5A061591A26E637AL, 0xB611EF1618208B46L, 0x09F4DF3EB7A981ABL, + 0x1EBB078AE87DACC0L, 0xB791038CB65E231FL, 0x0FD38D4574B05660L, 0x67EDF702C1EA8EBEL, + 0xBA5F4BE0831238CDL, 0xE3C477C2CEFEBE5CL, 0x0DCE486C354C1BD2L, 0x8C5DB36416C31910L, + 0x26EA9ED1A7627324L, 0x039D29B3EF82E5EBL, 0x9F28FC82CBF2AE02L, 0xA8AAE89CF05D2786L, + 0x431AACFA2774B028L, 0xCF471F9E31B7A938L, 0x581BD0B8E3922EC8L, 0xBC78199B400BEF06L, + 0x90FB71C7BF42F862L, 0x1F3BEB1046030499L, 0x683E7A47B55AD8DEL, 0x988F4263A695D190L, + 0xD808C72A6E638453L, 0x0627527BC319D7CBL, 0xEBB04466D72997AEL, 0xE67E0C0AE2658C7CL, + 0x14D2F107B056C880L, 0x7122C32C30400B8CL, 0x8A7AE11FD5DACEDBL, 0xA0DEDB38E98A0E74L, + 0xAD109354DCC615A6L, 0x0BE91A17F655CC19L, 0x8DDD5FFEB8BDB149L, 0xBFE53028AF890AEDL, + 0xD65BA6F5B4AD7A6AL, 0x7956F0882997227EL, 0x10E8665532B352F9L, 0x0E5361DFDACEFE39L, + 0xCEC7F3049FC90161L, 0xFF62B561677F5F2EL, 0x975CCF26D22587F0L, 0x51EF0F86543BAF63L, + 0x2F1E41EF10CBF28FL, 0x52722635BBB94A88L, 0xAE8DBAE73344F04DL, 0x410769D36688FD9AL, + 0xB3AB94DE34BBB966L, 0x801317928DF1AA9BL, 0xA564A0F0C5113C54L, 0xF131D4BEBDB1A117L, + 0x7F71A2F3EA8EF5B5L, 0x40878549C8F655C3L, 0x7EF14E6944F05DECL, 0xD44663DCF55137D8L, + 0xF2ACFD0D523344FCL, 0x0000000000000000L, 0x5FBC6E598EF5515AL, 0x16CF342EF1AA8532L, + 0xB036BD6DDB395C8DL, 0x13754FE6DD31B712L, 0xBBDFA77A2D6C9094L, 0x89E7C8AC3A582B30L, + 0x3C6B0E09CDFA459DL, 0xC4AE0589C7E26521L, 0x49735A777F5FD468L, 0xCAFD64561D2C9B18L, + 0xDA1502032F9FC9E1L, 0x8867243694268369L, 0x3782141E3BAF8984L, 0x9CB5D53124704BE9L, + 0xD7DB4A6F1AD3D233L, 0xA6F989432A93D9BFL, 0x9D3539AB8A0EE3B0L, 0x53F2CAAF15C7E2D1L, + 0x6E19283C76430F15L, 0x3DEBE2936384EDC4L, 0x5E3C82C3208BF903L, 0x33B8834CB94A13FDL, + 0x6470DEB12E686B55L, 0x359FD1377A53C436L, 0x61CAA57902F35975L, 0x043A975282E59A79L, + 0xFD7F70482683129CL, 0xC52EE913699CCD78L, 0x28B9FF0E7DAC8D1DL, 0x5455744E78A09D43L, + 0xCB7D88CCB3523341L, 0x44BD121B4A13CFBAL, 0x4D49CD25FDBA4E11L, 0x3E76CB208C06082FL, + 0x3FF627BA2278A076L, 0xC28957F204FBB2EAL, 0x453DFE81E46D67E3L, 0x94C1E6953DA7621BL, + 0x2C83685CFF491764L, 0xF32C1197FC4DECA5L, 0x2B24D6BD922E68F6L, 0xB22B78449AC5113FL, + 0x48F3B6EDD1217C31L, 0x2E9EAD75BEB55AD6L, 0x174FD8B45FD42D6BL, 0x4ED4E4961238ABFAL, + 0x92E6B4EEFEBEB5D0L, 0x46A0D7320BEF8208L, 0x47203BA8A5912A51L, 0x24F75BF8E69E3E96L, + 0xF0B1382413CF094EL, 0xFEE259FBC901F777L, 0x276A724B091CDB7DL, 0xBDF8F501EE75475FL, + 0x599B3C224DEC8691L, 0x6D84018F99C1EAFEL, 0x7498B8E41CDB39ACL, 0xE0595E71217C5BB7L, + 0x2AA43A273C50C0AFL, 0xF50B43EC3F543B6EL, 0x838E3E2162734F70L, 0xC09492DB4507FF58L, + 0x72BFEA9FDFC2EE67L, 0x11688ACF9CCDFAA0L, 0x1A8190D86A9836B9L, 0x7ACBD93BC615C795L, + 0xC7332C3A286080CAL, 0x863445E94EE87D50L, 0xF6966A5FD0D6DE85L, 0xE9AD814F96D5DA1CL, + 0x70A22FB69E3EA3D5L, 0x0A69F68D582B6440L, 0xB8428EC9C2EE757FL, 0x604A49E3AC8DF12CL, + 0x5B86F90B0C10CB23L, 0xE1D9B2EB8F02F3EEL, 0x29391394D3D22544L, 0xC8E0A17F5CD0D6AAL, + 0xB58CC6A5F7A26EADL, 0x8193FB08238F02C2L, 0xD5C68F465B2F9F81L, 0xFCFF9CD288FDBAC5L, + 0x77059157F359DC47L, 0x1D262E3907FF492BL, 0xFB582233E59AC557L, 0xDDB2BCE242F8B673L, + 0x2577B76248E096CFL, 0x6F99C4A6D83DA74CL, 0xC1147E41EB795701L, 0xF48BAF76912A9337L + }, + new ulong[] { + 0x3EF29D249B2C0A19L, 0xE9E16322B6F8622FL, 0x5536994047757F7AL, 0x9F4D56D5A47B0B33L, + 0x822567466AA1174CL, 0xB8F5057DEB082FB2L, 0xCC48C10BF4475F53L, 0x373088D4275DEC3AL, + 0x968F4325180AED10L, 0x173D232CF7016151L, 0xAE4ED09F946FCC13L, 0xFD4B4741C4539873L, + 0x1B5B3F0DD9933765L, 0x2FFCB0967B644052L, 0xE02376D20A89840CL, 0xA3AE3A70329B18D7L, + 0x419CBD2335DE8526L, 0xFAFEBF115B7C3199L, 0x0397074F85AA9B0DL, 0xC58AD4FB4836B970L, + 0xBEC60BE3FC4104A8L, 0x1EFF36DC4B708772L, 0x131FDC33ED8453B6L, 0x0844E33E341764D3L, + 0x0FF11B6EAB38CD39L, 0x64351F0A7761B85AL, 0x3B5694F509CFBA0EL, 0x30857084B87245D0L, + 0x47AFB3BD2297AE3CL, 0xF2BA5C2F6F6B554AL, 0x74BDC4761F4F70E1L, 0xCFDFC64471EDC45EL, + 0xE610784C1DC0AF16L, 0x7ACA29D63C113F28L, 0x2DED411776A859AFL, 0xAC5F211E99A3D5EEL, + 0xD484F949A87EF33BL, 0x3CE36CA596E013E4L, 0xD120F0983A9D432CL, 0x6BC40464DC597563L, + 0x69D5F5E5D1956C9EL, 0x9AE95F043698BB24L, 0xC9ECC8DA66A4EF44L, 0xD69508C8A5B2EAC6L, + 0xC40C2235C0503B80L, 0x38C193BA8C652103L, 0x1CEEC75D46BC9E8FL, 0xD331011937515AD1L, + 0xD8E2E56886ECA50FL, 0xB137108D5779C991L, 0x709F3B6905CA4206L, 0x4FEB50831680CAEFL, + 0xEC456AF3241BD238L, 0x58D673AFE181ABBEL, 0x242F54E7CAD9BF8CL, 0x0211F1810DCC19FDL, + 0x90BC4DBB0F43C60AL, 0x9518446A9DA0761DL, 0xA1BFCBF13F57012AL, 0x2BDE4F8961E172B5L, + 0x27B853A84F732481L, 0xB0B1E643DF1F4B61L, 0x18CC38425C39AC68L, 0xD2B7F7D7BF37D821L, + 0x3103864A3014C720L, 0x14AA246372ABFA5CL, 0x6E600DB54EBAC574L, 0x394765740403A3F3L, + 0x09C215F0BC71E623L, 0x2A58B947E987F045L, 0x7B4CDF18B477BDD8L, 0x9709B5EB906C6FE0L, + 0x73083C268060D90BL, 0xFEDC400E41F9037EL, 0x284948C6E44BE9B8L, 0x728ECAE808065BFBL, + 0x06330E9E17492B1AL, 0x5950856169E7294EL, 0xBAE4F4FCE6C4364FL, 0xCA7BCF95E30E7449L, + 0x7D7FD186A33E96C2L, 0x52836110D85AD690L, 0x4DFAA1021B4CD312L, 0x913ABB75872544FAL, + 0xDD46ECB9140F1518L, 0x3D659A6B1E869114L, 0xC23F2CABD719109AL, 0xD713FE062DD46836L, + 0xD0A60656B2FBC1DCL, 0x221C5A79DD909496L, 0xEFD26DBCA1B14935L, 0x0E77EDA0235E4FC9L, + 0xCBFD395B6B68F6B9L, 0x0DE0EAEFA6F4D4C4L, 0x0422FF1F1A8532E7L, 0xF969B85EDED6AA94L, + 0x7F6E2007AEF28F3FL, 0x3AD0623B81A938FEL, 0x6624EE8B7AADA1A7L, 0xB682E8DDC856607BL, + 0xA78CC56F281E2A30L, 0xC79B257A45FAA08DL, 0x5B4174E0642B30B3L, 0x5F638BFF7EAE0254L, + 0x4BC9AF9C0C05F808L, 0xCE59308AF98B46AEL, 0x8FC58DA9CC55C388L, 0x803496C7676D0EB1L, + 0xF33CAAE1E70DD7BAL, 0xBB6202326EA2B4BFL, 0xD5020F87201871CBL, 0x9D5CA754A9B712CEL, + 0x841669D87DE83C56L, 0x8A6184785EB6739FL, 0x420BBA6CB0741E2BL, 0xF12D5B60EAC1CE47L, + 0x76AC35F71283691CL, 0x2C6BB7D9FECEDB5FL, 0xFCCDB18F4C351A83L, 0x1F79C012C3160582L, + 0xF0ABADAE62A74CB7L, 0xE1A5801C82EF06FCL, 0x67A21845F2CB2357L, 0x5114665F5DF04D9DL, + 0xBF40FD2D74278658L, 0xA0393D3FB73183DAL, 0x05A409D192E3B017L, 0xA9FB28CF0B4065F9L, + 0x25A9A22942BF3D7CL, 0xDB75E22703463E02L, 0xB326E10C5AB5D06CL, 0xE7968E8295A62DE6L, + 0xB973F3B3636EAD42L, 0xDF571D3819C30CE5L, 0xEE549B7229D7CBC5L, 0x12992AFD65E2D146L, + 0xF8EF4E9056B02864L, 0xB7041E134030E28BL, 0xC02EDD2ADAD50967L, 0x932B4AF48AE95D07L, + 0x6FE6FB7BC6DC4784L, 0x239AACB755F61666L, 0x401A4BEDBDB807D6L, 0x485EA8D389AF6305L, + 0xA41BC220ADB4B13DL, 0x753B32B89729F211L, 0x997E584BB3322029L, 0x1D683193CEDA1C7FL, + 0xFF5AB6C0C99F818EL, 0x16BBD5E27F67E3A1L, 0xA59D34EE25D233CDL, 0x98F8AE853B54A2D9L, + 0x6DF70AFACB105E79L, 0x795D2E99B9BBA425L, 0x8E437B6744334178L, 0x0186F6CE886682F0L, + 0xEBF092A3BB347BD2L, 0xBCD7FA62F18D1D55L, 0xADD9D7D011C5571EL, 0x0BD3E471B1BDFFDEL, + 0xAA6C2F808EEAFEF4L, 0x5EE57D31F6C880A4L, 0xF50FA47FF044FCA0L, 0x1ADDC9C351F5B595L, + 0xEA76646D3352F922L, 0x0000000000000000L, 0x85909F16F58EBEA6L, 0x46294573AAF12CCCL, + 0x0A5512BF39DB7D2EL, 0x78DBD85731DD26D5L, 0x29CFBE086C2D6B48L, 0x218B5D36583A0F9BL, + 0x152CD2ADFACD78ACL, 0x83A39188E2C795BCL, 0xC3B9DA655F7F926AL, 0x9ECBA01B2C1D89C3L, + 0x07B5F8509F2FA9EAL, 0x7EE8D6C926940DCFL, 0x36B67E1AAF3B6ECAL, 0x86079859702425ABL, + 0xFB7849DFD31AB369L, 0x4C7C57CC932A51E2L, 0xD96413A60E8A27FFL, 0x263EA566C715A671L, + 0x6C71FC344376DC89L, 0x4A4F595284637AF8L, 0xDAF314E98B20BCF2L, 0x572768C14AB96687L, + 0x1088DB7C682EC8BBL, 0x887075F9537A6A62L, 0x2E7A4658F302C2A2L, 0x619116DBE582084DL, + 0xA87DDE018326E709L, 0xDCC01A779C6997E8L, 0xEDC39C3DAC7D50C8L, 0xA60A33A1A078A8C0L, + 0xC1A82BE452B38B97L, 0x3F746BEA134A88E9L, 0xA228CCBEBAFD9A27L, 0xABEAD94E068C7C04L, + 0xF48952B178227E50L, 0x5CF48CB0FB049959L, 0x6017E0156DE48ABDL, 0x4438B4F2A73D3531L, + 0x8C528AE649FF5885L, 0xB515EF924DFCFB76L, 0x0C661C212E925634L, 0xB493195CC59A7986L, + 0x9CDA519A21D1903EL, 0x32948105B5BE5C2DL, 0x194ACE8CD45F2E98L, 0x438D4CA238129CDBL, + 0x9B6FA9CABEFE39D4L, 0x81B26009EF0B8C41L, 0xDED1EBF691A58E15L, 0x4E6DA64D9EE6481FL, + 0x54B06F8ECF13FD8AL, 0x49D85E1D01C9E1F5L, 0xAFC826511C094EE3L, 0xF698A33075EE67ADL, + 0x5AC7822EEC4DB243L, 0x8DD47C28C199DA75L, 0x89F68337DB1CE892L, 0xCDCE37C57C21DDA3L, + 0x530597DE503C5460L, 0x6A42F2AA543FF793L, 0x5D727A7E73621BA9L, 0xE232875307459DF1L, + 0x56A19E0FC2DFE477L, 0xC61DD3B4CD9C227DL, 0xE5877F03986A341BL, 0x949EB2A415C6F4EDL, + 0x6206119460289340L, 0x6380E75AE84E11B0L, 0x8BE772B6D6D0F16FL, 0x50929091D596CF6DL, + 0xE86795EC3E9EE0DFL, 0x7CF927482B581432L, 0xC86A3E14EEC26DB4L, 0x7119CDA78DACC0F6L, + 0xE40189CD100CB6EBL, 0x92ADBC3A028FDFF7L, 0xB2A017C2D2D3529CL, 0x200DABF8D05C8D6BL, + 0x34A78F9BA2F77737L, 0xE3B4719D8F231F01L, 0x45BE423C2F5BB7C1L, 0xF71E55FEFD88E55DL, + 0x6853032B59F3EE6EL, 0x65B3E9C4FF073AAAL, 0x772AC3399AE5EBECL, 0x87816E97F842A75BL, + 0x110E2DB2E0484A4BL, 0x331277CB3DD8DEDDL, 0xBD510CAC79EB9FA5L, 0x352179552A91F5C7L + }, + new ulong[] { + 0x8AB0A96846E06A6DL, 0x43C7E80B4BF0B33AL, 0x08C9B3546B161EE5L, 0x39F1C235EBA990BEL, + 0xC1BEF2376606C7B2L, 0x2C209233614569AAL, 0xEB01523B6FC3289AL, 0x946953AB935ACEDDL, + 0x272838F63E13340EL, 0x8B0455ECA12BA052L, 0x77A1B2C4978FF8A2L, 0xA55122CA13E54086L, + 0x2276135862D3F1CDL, 0xDB8DDFDE08B76CFEL, 0x5D1E12C89E4A178AL, 0x0E56816B03969867L, + 0xEE5F79953303ED59L, 0xAFED748BAB78D71DL, 0x6D929F2DF93E53EEL, 0xF5D8A8F8BA798C2AL, + 0xF619B1698E39CF6BL, 0x95DDAF2F749104E2L, 0xEC2A9C80E0886427L, 0xCE5C8FD8825B95EAL, + 0xC4E0D9993AC60271L, 0x4699C3A5173076F9L, 0x3D1B151F50A29F42L, 0x9ED505EA2BC75946L, + 0x34665ACFDC7F4B98L, 0x61B1FB53292342F7L, 0xC721C0080E864130L, 0x8693CD1696FD7B74L, + 0x872731927136B14BL, 0xD3446C8A63A1721BL, 0x669A35E8A6680E4AL, 0xCAB658F239509A16L, + 0xA4E5DE4EF42E8AB9L, 0x37A7435EE83F08D9L, 0x134E6239E26C7F96L, 0x82791A3C2DF67488L, + 0x3F6EF00A8329163CL, 0x8E5A7E42FDEB6591L, 0x5CAAEE4C7981DDB5L, 0x19F234785AF1E80DL, + 0x255DDDE3ED98BD70L, 0x50898A32A99CCCACL, 0x28CA4519DA4E6656L, 0xAE59880F4CB31D22L, + 0x0D9798FA37D6DB26L, 0x32F968F0B4FFCD1AL, 0xA00F09644F258545L, 0xFA3AD5175E24DE72L, + 0xF46C547C5DB24615L, 0x713E80FBFF0F7E20L, 0x7843CF2B73D2AAFAL, 0xBD17EA36AEDF62B4L, + 0xFD111BACD16F92CFL, 0x4ABAA7DBC72D67E0L, 0xB3416B5DAD49FAD3L, 0xBCA316B24914A88BL, + 0x15D150068AECF914L, 0xE27C1DEBE31EFC40L, 0x4FE48C759BEDA223L, 0x7EDCFD141B522C78L, + 0x4E5070F17C26681CL, 0xE696CAC15815F3BCL, 0x35D2A64B3BB481A7L, 0x800CFF29FE7DFDF6L, + 0x1ED9FAC3D5BAA4B0L, 0x6C2663A91EF599D1L, 0x03C1199134404341L, 0xF7AD4DED69F20554L, + 0xCD9D9649B61BD6ABL, 0xC8C3BDE7EADB1368L, 0xD131899FB02AFB65L, 0x1D18E352E1FAE7F1L, + 0xDA39235AEF7CA6C1L, 0xA1BBF5E0A8EE4F7AL, 0x91377805CF9A0B1EL, 0x3138716180BF8E5BL, + 0xD9F83ACBDB3CE580L, 0x0275E515D38B897EL, 0x472D3F21F0FBBCC6L, 0x2D946EB7868EA395L, + 0xBA3C248D21942E09L, 0xE7223645BFDE3983L, 0xFF64FEB902E41BB1L, 0xC97741630D10D957L, + 0xC3CB1722B58D4ECCL, 0xA27AEC719CAE0C3BL, 0x99FECB51A48C15FBL, 0x1465AC826D27332BL, + 0xE1BD047AD75EBF01L, 0x79F733AF941960C5L, 0x672EC96C41A3C475L, 0xC27FEBA6524684F3L, + 0x64EFD0FD75E38734L, 0xED9E60040743AE18L, 0xFB8E2993B9EF144DL, 0x38453EB10C625A81L, + 0x6978480742355C12L, 0x48CF42CE14A6EE9EL, 0x1CAC1FD606312DCEL, 0x7B82D6BA4792E9BBL, + 0x9D141C7B1F871A07L, 0x5616B80DC11C4A2EL, 0xB849C198F21FA777L, 0x7CA91801C8D9A506L, + 0xB1348E487EC273ADL, 0x41B20D1E987B3A44L, 0x7460AB55A3CFBBE3L, 0x84E628034576F20AL, + 0x1B87D16D897A6173L, 0x0FE27DEFE45D5258L, 0x83CDE6B8CA3DBEB7L, 0x0C23647ED01D1119L, + 0x7A362A3EA0592384L, 0xB61F40F3F1893F10L, 0x75D457D1440471DCL, 0x4558DA34237035B8L, + 0xDCA6116587FC2043L, 0x8D9B67D3C9AB26D0L, 0x2B0B5C88EE0E2517L, 0x6FE77A382AB5DA90L, + 0x269CC472D9D8FE31L, 0x63C41E46FAA8CB89L, 0xB7ABBC771642F52FL, 0x7D1DE4852F126F39L, + 0xA8C6BA3024339BA0L, 0x600507D7CEE888C8L, 0x8FEE82C61A20AFAEL, 0x57A2448926D78011L, + 0xFCA5E72836A458F0L, 0x072BCEBB8F4B4CBDL, 0x497BBE4AF36D24A1L, 0x3CAFE99BB769557DL, + 0x12FA9EBD05A7B5A9L, 0xE8C04BAA5B836BDBL, 0x4273148FAC3B7905L, 0x908384812851C121L, + 0xE557D3506C55B0FDL, 0x72FF996ACB4F3D61L, 0x3EDA0C8E64E2DC03L, 0xF0868356E6B949E9L, + 0x04EAD72ABB0B0FFCL, 0x17A4B5135967706AL, 0xE3C8E16F04D5367FL, 0xF84F30028DAF570CL, + 0x1846C8FCBD3A2232L, 0x5B8120F7F6CA9108L, 0xD46FA231ECEA3EA6L, 0x334D947453340725L, + 0x58403966C28AD249L, 0xBED6F3A79A9F21F5L, 0x68CCB483A5FE962DL, 0xD085751B57E1315AL, + 0xFED0023DE52FD18EL, 0x4B0E5B5F20E6ADDFL, 0x1A332DE96EB1AB4CL, 0xA3CE10F57B65C604L, + 0x108F7BA8D62C3CD7L, 0xAB07A3A11073D8E1L, 0x6B0DAD1291BED56CL, 0xF2F366433532C097L, + 0x2E557726B2CEE0D4L, 0x0000000000000000L, 0xCB02A476DE9B5029L, 0xE4E32FD48B9E7AC2L, + 0x734B65EE2C84F75EL, 0x6E5386BCCD7E10AFL, 0x01B4FC84E7CBCA3FL, 0xCFE8735C65905FD5L, + 0x3613BFDA0FF4C2E6L, 0x113B872C31E7F6E8L, 0x2FE18BA255052AEBL, 0xE974B72EBC48A1E4L, + 0x0ABC5641B89D979BL, 0xB46AA5E62202B66EL, 0x44EC26B0C4BBFF87L, 0xA6903B5B27A503C7L, + 0x7F680190FC99E647L, 0x97A84A3AA71A8D9CL, 0xDD12EDE16037EA7CL, 0xC554251DDD0DC84EL, + 0x88C54C7D956BE313L, 0x4D91696048662B5DL, 0xB08072CC9909B992L, 0xB5DE5962C5C97C51L, + 0x81B803AD19B637C9L, 0xB2F597D94A8230ECL, 0x0B08AAC55F565DA4L, 0xF1327FD2017283D6L, + 0xAD98919E78F35E63L, 0x6AB9519676751F53L, 0x24E921670A53774FL, 0xB9FD3D1C15D46D48L, + 0x92F66194FBDA485FL, 0x5A35DC7311015B37L, 0xDED3F4705477A93DL, 0xC00A0EB381CD0D8DL, + 0xBB88D809C65FE436L, 0x16104997BEACBA55L, 0x21B70AC95693B28CL, 0x59F4C5E225411876L, + 0xD5DB5EB50B21F499L, 0x55D7A19CF55C096FL, 0xA97246B4C3F8519FL, 0x8552D487A2BD3835L, + 0x54635D181297C350L, 0x23C2EFDC85183BF2L, 0x9F61F96ECC0C9379L, 0x534893A39DDC8FEDL, + 0x5EDF0B59AA0A54CBL, 0xAC2C6D1A9F38945CL, 0xD7AEBBA0D8AA7DE7L, 0x2ABFA00C09C5EF28L, + 0xD84CC64F3CF72FBFL, 0x2003F64DB15878B3L, 0xA724C7DFC06EC9F8L, 0x069F323F68808682L, + 0xCC296ACD51D01C94L, 0x055E2BAE5CC0C5C3L, 0x6270E2C21D6301B6L, 0x3B842720382219C0L, + 0xD2F0900E846AB824L, 0x52FC6F277A1745D2L, 0xC6953C8CE94D8B0FL, 0xE009F8FE3095753EL, + 0x655B2C7992284D0BL, 0x984A37D54347DFC4L, 0xEAB5AEBF8808E2A5L, 0x9A3FD2C090CC56BAL, + 0x9CA0E0FFF84CD038L, 0x4C2595E4AFADE162L, 0xDF6708F4B3BC6302L, 0xBF620F237D54EBCAL, + 0x93429D101C118260L, 0x097D4FD08CDDD4DAL, 0x8C2F9B572E60ECEFL, 0x708A7C7F18C4B41FL, + 0x3A30DBA4DFE9D3FFL, 0x4006F19A7FB0F07BL, 0x5F6BF7DD4DC19EF4L, 0x1F6D064732716E8FL, + 0xF9FBCC866A649D33L, 0x308C8DE567744464L, 0x8971B0F972A0292CL, 0xD61A47243F61B7D8L, + 0xEFEB8511D4C82766L, 0x961CB6BE40D147A3L, 0xAAB35F25F7B812DEL, 0x76154E407044329DL, + 0x513D76B64E570693L, 0xF3479AC7D2F90AA8L, 0x9B8B2E4477079C85L, 0x297EB99D3D85AC69L + }, + new ulong[] { + 0x7E37E62DFC7D40C3L, 0x776F25A4EE939E5BL, 0xE045C850DD8FB5ADL, 0x86ED5BA711FF1952L, + 0xE91D0BD9CF616B35L, 0x37E0AB256E408FFBL, 0x9607F6C031025A7AL, 0x0B02F5E116D23C9DL, + 0xF3D8486BFB50650CL, 0x621CFF27C40875F5L, 0x7D40CB71FA5FD34AL, 0x6DAA6616DAA29062L, + 0x9F5F354923EC84E2L, 0xEC847C3DC507C3B3L, 0x025A3668043CE205L, 0xA8BF9E6C4DAC0B19L, + 0xFA808BE2E9BEBB94L, 0xB5B99C5277C74FA3L, 0x78D9BC95F0397BCCL, 0xE332E50CDBAD2624L, + 0xC74FCE129332797EL, 0x1729ECEB2EA709ABL, 0xC2D6B9F69954D1F8L, 0x5D898CBFBAB8551AL, + 0x859A76FB17DD8ADBL, 0x1BE85886362F7FB5L, 0xF6413F8FF136CD8AL, 0xD3110FA5BBB7E35CL, + 0x0A2FEED514CC4D11L, 0xE83010EDCD7F1AB9L, 0xA1E75DE55F42D581L, 0xEEDE4A55C13B21B6L, + 0xF2F5535FF94E1480L, 0x0CC1B46D1888761EL, 0xBCE15FDB6529913BL, 0x2D25E8975A7181C2L, + 0x71817F1CE2D7A554L, 0x2E52C5CB5C53124BL, 0xF9F7A6BEEF9C281DL, 0x9E722E7D21F2F56EL, + 0xCE170D9B81DCA7E6L, 0x0E9B82051CB4941BL, 0x1E712F623C49D733L, 0x21E45CFA42F9F7DCL, + 0xCB8E7A7F8BBA0F60L, 0x8E98831A010FB646L, 0x474CCF0D8E895B23L, 0xA99285584FB27A95L, + 0x8CC2B57205335443L, 0x42D5B8E984EFF3A5L, 0x012D1B34021E718CL, 0x57A6626AAE74180BL, + 0xFF19FC06E3D81312L, 0x35BA9D4D6A7C6DFEL, 0xC9D44C178F86ED65L, 0x506523E6A02E5288L, + 0x03772D5C06229389L, 0x8B01F4FE0B691EC0L, 0xF8DABD8AED825991L, 0x4C4E3AEC985B67BEL, + 0xB10DF0827FBF96A9L, 0x6A69279AD4F8DAE1L, 0xE78689DCD3D5FF2EL, 0x812E1A2B1FA553D1L, + 0xFBAD90D6EBA0CA18L, 0x1AC543B234310E39L, 0x1604F7DF2CB97827L, 0xA6241C6951189F02L, + 0x753513CCEAAF7C5EL, 0x64F2A59FC84C4EFAL, 0x247D2B1E489F5F5AL, 0xDB64D718AB474C48L, + 0x79F4A7A1F2270A40L, 0x1573DA832A9BEBAEL, 0x3497867968621C72L, 0x514838D2A2302304L, + 0xF0AF6537FD72F685L, 0x1D06023E3A6B44BAL, 0x678588C3CE6EDD73L, 0x66A893F7CC70ACFFL, + 0xD4D24E29B5EDA9DFL, 0x3856321470EA6A6CL, 0x07C3418C0E5A4A83L, 0x2BCBB22F5635BACDL, + 0x04B46CD00878D90AL, 0x06EE5AB80C443B0FL, 0x3B211F4876C8F9E5L, 0x0958C38912EEDE98L, + 0xD14B39CDBF8B0159L, 0x397B292072F41BE0L, 0x87C0409313E168DEL, 0xAD26E98847CAA39FL, + 0x4E140C849C6785BBL, 0xD5FF551DB7F3D853L, 0xA0CA46D15D5CA40DL, 0xCD6020C787FE346FL, + 0x84B76DCF15C3FB57L, 0xDEFDA0FCA121E4CEL, 0x4B8D7B6096012D3DL, 0x9AC642AD298A2C64L, + 0x0875D8BD10F0AF14L, 0xB357C6EA7B8374ACL, 0x4D6321D89A451632L, 0xEDA96709C719B23FL, + 0xF76C24BBF328BC06L, 0xC662D526912C08F2L, 0x3CE25EC47892B366L, 0xB978283F6F4F39BDL, + 0xC08C8F9E9D6833FDL, 0x4F3917B09E79F437L, 0x593DE06FB2C08C10L, 0xD6887841B1D14BDAL, + 0x19B26EEE32139DB0L, 0xB494876675D93E2FL, 0x825937771987C058L, 0x90E9AC783D466175L, + 0xF1827E03FF6C8709L, 0x945DC0A8353EB87FL, 0x4516F9658AB5B926L, 0x3F9573987EB020EFL, + 0xB855330B6D514831L, 0x2AE6A91B542BCB41L, 0x6331E413C6160479L, 0x408F8E8180D311A0L, + 0xEFF35161C325503AL, 0xD06622F9BD9570D5L, 0x8876D9A20D4B8D49L, 0xA5533135573A0C8BL, + 0xE168D364DF91C421L, 0xF41B09E7F50A2F8FL, 0x12B09B0F24C1A12DL, 0xDA49CC2CA9593DC4L, + 0x1F5C34563E57A6BFL, 0x54D14F36A8568B82L, 0xAF7CDFE043F6419AL, 0xEA6A2685C943F8BCL, + 0xE5DCBFB4D7E91D2BL, 0xB27ADDDE799D0520L, 0x6B443CAED6E6AB6DL, 0x7BAE91C9F61BE845L, + 0x3EB868AC7CAE5163L, 0x11C7B65322E332A4L, 0xD23C1491B9A992D0L, 0x8FB5982E0311C7CAL, + 0x70AC6428E0C9D4D8L, 0x895BC2960F55FCC5L, 0x76423E90EC8DEFD7L, 0x6FF0507EDE9E7267L, + 0x3DCF45F07A8CC2EAL, 0x4AA06054941F5CB1L, 0x5810FB5BB0DEFD9CL, 0x5EFEA1E3BC9AC693L, + 0x6EDD4B4ADC8003EBL, 0x741808F8E8B10DD2L, 0x145EC1B728859A22L, 0x28BC9F7350172944L, + 0x270A06424EBDCCD3L, 0x972AEDF4331C2BF6L, 0x059977E40A66A886L, 0x2550302A4A812ED6L, + 0xDD8A8DA0A7037747L, 0xC515F87A970E9B7BL, 0x3023EAA9601AC578L, 0xB7E3AA3A73FBADA6L, + 0x0FB699311EAAE597L, 0x0000000000000000L, 0x310EF19D6204B4F4L, 0x229371A644DB6455L, + 0x0DECAF591A960792L, 0x5CA4978BB8A62496L, 0x1C2B190A38753536L, 0x41A295B582CD602CL, + 0x3279DCC16426277DL, 0xC1A194AA9F764271L, 0x139D803B26DFD0A1L, 0xAE51C4D441E83016L, + 0xD813FA44AD65DFC1L, 0xAC0BF2BC45D4D213L, 0x23BE6A9246C515D9L, 0x49D74D08923DCF38L, + 0x9D05032127D066E7L, 0x2F7FDEFF5E4D63C7L, 0xA47E2A0155247D07L, 0x99B16FF12FA8BFEDL, + 0x4661D4398C972AAFL, 0xDFD0BBC8A33F9542L, 0xDCA79694A51D06CBL, 0xB020EBB67DA1E725L, + 0xBA0F0563696DAA34L, 0xE4F1A480D5F76CA7L, 0xC438E34E9510EAF7L, 0x939E81243B64F2FCL, + 0x8DEFAE46072D25CFL, 0x2C08F3A3586FF04EL, 0xD7A56375B3CF3A56L, 0x20C947CE40E78650L, + 0x43F8A3DD86F18229L, 0x568B795EAC6A6987L, 0x8003011F1DBB225DL, 0xF53612D3F7145E03L, + 0x189F75DA300DEC3CL, 0x9570DB9C3720C9F3L, 0xBB221E576B73DBB8L, 0x72F65240E4F536DDL, + 0x443BE25188ABC8AAL, 0xE21FFE38D9B357A8L, 0xFD43CA6EE7E4F117L, 0xCAA3614B89A47EECL, + 0xFE34E732E1C6629EL, 0x83742C431B99B1D4L, 0xCF3A16AF83C2D66AL, 0xAAE5A8044990E91CL, + 0x26271D764CA3BD5FL, 0x91C4B74C3F5810F9L, 0x7C6DD045F841A2C6L, 0x7F1AFD19FE63314FL, + 0xC8F957238D989CE9L, 0xA709075D5306EE8EL, 0x55FC5402AA48FA0EL, 0x48FA563C9023BEB4L, + 0x65DFBEABCA523F76L, 0x6C877D22D8BCE1EEL, 0xCC4D3BF385E045E3L, 0xBEBB69B36115733EL, + 0x10EAAD6720FD4328L, 0xB6CEB10E71E5DC2AL, 0xBDCC44EF6737E0B7L, 0x523F158EA412B08DL, + 0x989C74C52DB6CE61L, 0x9BEB59992B945DE8L, 0x8A2CEFCA09776F4CL, 0xA3BD6B8D5B7E3784L, + 0xEB473DB1CB5D8930L, 0xC3FBA2C29B4AA074L, 0x9C28181525CE176BL, 0x683311F2D0C438E4L, + 0x5FD3BAD7BE84B71FL, 0xFC6ED15AE5FA809BL, 0x36CDB0116C5EFE77L, 0x29918447520958C8L, + 0xA29070B959604608L, 0x53120EBAA60CC101L, 0x3A0C047C74D68869L, 0x691E0AC6D2DA4968L, + 0x73DB4974E6EB4751L, 0x7A838AFDF40599C9L, 0x5A4ACD33B4E21F99L, 0x6046C94FC03497F0L, + 0xE6AB92E8D1CB8EA2L, 0x3354C7F5663856F1L, 0xD93EE170AF7BAE4DL, 0x616BD27BC22AE67CL, + 0x92B39A10397A8370L, 0xABC8B3304B8E9890L, 0xBF967287630B02B2L, 0x5B67D607B6FC6E15L + }, + new ulong[] { + 0xD031C397CE553FE6L, 0x16BA5B01B006B525L, 0xA89BADE6296E70C8L, 0x6A1F525D77D3435BL, + 0x6E103570573DFA0BL, 0x660EFB2A17FC95ABL, 0x76327A9E97634BF6L, 0x4BAD9D6462458BF5L, + 0xF1830CAEDBC3F748L, 0xC5C8F542669131FFL, 0x95044A1CDC48B0CBL, 0x892962DF3CF8B866L, + 0xB0B9E208E930C135L, 0xA14FB3F0611A767CL, 0x8D2605F21C160136L, 0xD6B71922FECC549EL, + 0x37089438A5907D8BL, 0x0B5DA38E5803D49CL, 0x5A5BCC9CEA6F3CBCL, 0xEDAE246D3B73FFE5L, + 0xD2B87E0FDE22EDCEL, 0x5E54ABB1CA8185ECL, 0x1DE7F88FE80561B9L, 0xAD5E1A870135A08CL, + 0x2F2ADBD665CECC76L, 0x5780B5A782F58358L, 0x3EDC8A2EEDE47B3FL, 0xC9D95C3506BEE70FL, + 0x83BE111D6C4E05EEL, 0xA603B90959367410L, 0x103C81B4809FDE5DL, 0x2C69B6027D0C774AL, + 0x399080D7D5C87953L, 0x09D41E16487406B4L, 0xCDD63B1826505E5FL, 0xF99DC2F49B0298E8L, + 0x9CD0540A943CB67FL, 0xBCA84B7F891F17C5L, 0x723D1DB3B78DF2A6L, 0x78AA6E71E73B4F2EL, + 0x1433E699A071670DL, 0x84F21BE454620782L, 0x98DF3327B4D20F2FL, 0xF049DCE2D3769E5CL, + 0xDB6C60199656EB7AL, 0x648746B2078B4783L, 0x32CD23598DCBADCFL, 0x1EA4955BF0C7DA85L, + 0xE9A143401B9D46B5L, 0xFD92A5D9BBEC21B8L, 0xC8138C790E0B8E1BL, 0x2EE00B9A6D7BA562L, + 0xF85712B893B7F1FCL, 0xEB28FED80BEA949DL, 0x564A65EB8A40EA4CL, 0x6C9988E8474A2823L, + 0x4535898B121D8F2DL, 0xABD8C03231ACCBF4L, 0xBA2E91CAB9867CBDL, 0x7960BE3DEF8E263AL, + 0x0C11A977602FD6F0L, 0xCB50E1AD16C93527L, 0xEAE22E94035FFD89L, 0x2866D12F5DE2CE1AL, + 0xFF1B1841AB9BF390L, 0x9F9339DE8CFE0D43L, 0x964727C8C48A0BF7L, 0x524502C6AAAE531CL, + 0x9B9C5EF3AC10B413L, 0x4FA2FA4942AB32A5L, 0x3F165A62E551122BL, 0xC74148DA76E6E3D7L, + 0x924840E5E464B2A7L, 0xD372AE43D69784DAL, 0x233B72A105E11A86L, 0xA48A04914941A638L, + 0xB4B68525C9DE7865L, 0xDDEABAACA6CF8002L, 0x0A9773C250B6BD88L, 0xC284FFBB5EBD3393L, + 0x8BA0DF472C8F6A4EL, 0x2AEF6CB74D951C32L, 0x427983722A318D41L, 0x73F7CDFFBF389BB2L, + 0x074C0AF9382C026CL, 0x8A6A0F0B243A035AL, 0x6FDAE53C5F88931FL, 0xC68B98967E538AC3L, + 0x44FF59C71AA8E639L, 0xE2FCE0CE439E9229L, 0xA20CDE2479D8CD40L, 0x19E89FA2C8EBD8E9L, + 0xF446BBCFF398270CL, 0x43B3533E2284E455L, 0xD82F0DCD8E945046L, 0x51066F12B26CE820L, + 0xE73957AF6BC5426DL, 0x081ECE5A40C16FA0L, 0x3B193D4FC5BFAB7BL, 0x7FE66488DF174D42L, + 0x0E9814EF705804D8L, 0x8137AC857C39D7C6L, 0xB1733244E185A821L, 0x695C3F896F11F867L, + 0xF6CF0657E3EFF524L, 0x1AABF276D02963D5L, 0x2DA3664E75B91E5EL, 0x0289BD981077D228L, + 0x90C1FD7DF413608FL, 0x3C5537B6FD93A917L, 0xAA12107E3919A2E0L, 0x0686DAB530996B78L, + 0xDAA6B0559EE3826EL, 0xC34E2FF756085A87L, 0x6D5358A44FFF4137L, 0xFC587595B35948ACL, + 0x7CA5095CC7D5F67EL, 0xFB147F6C8B754AC0L, 0xBFEB26AB91DDACF9L, 0x6896EFC567A49173L, + 0xCA9A31E11E7C5C33L, 0xBBE44186B13315A9L, 0x0DDB793B689ABFE4L, 0x70B4A02BA7FA208EL, + 0xE47A3A7B7307F951L, 0x8CECD5BE14A36822L, 0xEEED49B923B144D9L, 0x17708B4DB8B3DC31L, + 0x6088219F2765FED3L, 0xB3FA8FDCF1F27A09L, 0x910B2D31FCA6099BL, 0x0F52C4A378ED6DCCL, + 0x50CCBF5EBAD98134L, 0x6BD582117F662A4FL, 0x94CE9A50D4FDD9DFL, 0x2B25BCFB45207526L, + 0x67C42B661F49FCBFL, 0x492420FC723259DDL, 0x03436DD418C2BB3CL, 0x1F6E4517F872B391L, + 0xA08563BC69AF1F68L, 0xD43EA4BAEEBB86B6L, 0x01CAD04C08B56914L, 0xAC94CACB0980C998L, + 0x54C3D8739A373864L, 0x26FEC5C02DBACAC2L, 0xDEA9D778BE0D3B3EL, 0x040F672D20EEB950L, + 0xE5B0EA377BB29045L, 0xF30AB136CBB42560L, 0x62019C0737122CFBL, 0xE86B930C13282FA1L, + 0xCC1CEB542EE5374BL, 0x538FD28AA21B3A08L, 0x1B61223AD89C0AC1L, 0x36C24474AD25149FL, + 0x7A23D3E9F74C9D06L, 0xBE21F6E79968C5EDL, 0xCF5F868036278C77L, 0xF705D61BEB5A9C30L, + 0x4D2B47D152DCE08DL, 0x5F9E7BFDC234ECF8L, 0x247778583DCD18EAL, 0x867BA67C4415D5AAL, + 0x4CE1979D5A698999L, 0x0000000000000000L, 0xEC64F42133C696F1L, 0xB57C5569C16B1171L, + 0xC1C7926F467F88AFL, 0x654D96FE0F3E2E97L, 0x15F936D5A8C40E19L, 0xB8A72C52A9F1AE95L, + 0xA9517DAA21DB19DCL, 0x58D27104FA18EE94L, 0x5918A148F2AD8780L, 0x5CDD1629DAF657C4L, + 0x8274C15164FB6CFAL, 0xD1FB13DBC6E056F2L, 0x7D6FD910CF609F6AL, 0xB63F38BDD9A9AA4DL, + 0x3D9FE7FAF526C003L, 0x74BBC706871499DEL, 0xDF630734B6B8522AL, 0x3AD3ED03CD0AC26FL, + 0xFADEAF2083C023D4L, 0xC00D42234ECAE1BBL, 0x8538CBA85CD76E96L, 0xC402250E6E2458EBL, + 0x47BC3413026A5D05L, 0xAFD7A71F114272A4L, 0x978DF784CC3F62E3L, 0xB96DFC1EA144C781L, + 0x21B2CF391596C8AEL, 0x318E4E8D950916F3L, 0xCE9556CC3E92E563L, 0x385A509BDD7D1047L, + 0x358129A0B5E7AFA3L, 0xE6F387E363702B79L, 0xE0755D5653E94001L, 0x7BE903A5FFF9F412L, + 0x12B53C2C90E80C75L, 0x3307F315857EC4DBL, 0x8FAFB86A0C61D31EL, 0xD9E5DD8186213952L, + 0x77F8AAD29FD622E2L, 0x25BDA814357871FEL, 0x7571174A8FA1F0CAL, 0x137FEC60985D6561L, + 0x30449EC19DBC7FE7L, 0xA540D4DD41F4CF2CL, 0xDC206AE0AE7AE916L, 0x5B911CD0E2DA55A8L, + 0xB2305F90F947131DL, 0x344BF9ECBD52C6B7L, 0x5D17C665D2433ED0L, 0x18224FEEC05EB1FDL, + 0x9E59E992844B6457L, 0x9A568EBFA4A5DD07L, 0xA3C60E68716DA454L, 0x7E2CB4C4D7A22456L, + 0x87B176304CA0BCBEL, 0x413AEEA632F3367DL, 0x9915E36BBC67663BL, 0x40F03EEA3A465F69L, + 0x1C2D28C3E0B008ADL, 0x4E682A054A1E5BB1L, 0x05C5B761285BD044L, 0xE1BF8D1A5B5C2915L, + 0xF2C0617AC3014C74L, 0xB7F5E8F1D11CC359L, 0x63CB4C4B3FA745EFL, 0x9D1A84469C89DF6BL, + 0xE33630824B2BFB3DL, 0xD5F474F6E60EEFA2L, 0xF58C6B83FB2D4E18L, 0x4676E45F0ADF3411L, + 0x20781F751D23A1BAL, 0xBD629B3381AA7ED1L, 0xAE1D775319F71BB0L, 0xFED1C80DA32E9A84L, + 0x5509083F92825170L, 0x29AC01635557A70EL, 0xA7C9694551831D04L, 0x8E65682604D4BA0AL, + 0x11F651F8882AB749L, 0xD77DC96EF6793D8AL, 0xEF2799F52B042DCDL, 0x48EEF0B07A8730C9L, + 0x22F1A2ED0D547392L, 0x6142F1D32FD097C7L, 0x4A674D286AF0E2E1L, 0x80FD7CC9748CBED2L, + 0x717E7067AF4F499AL, 0x938290A9ECD1DBB3L, 0x88E3B293344DD172L, 0x2734158C250FA3D6L + } + }; + + + } +} diff --git a/bc-sharp-crypto/src/crypto/digests/GOST3411_2012_256Digest.cs b/bc-sharp-crypto/src/crypto/digests/GOST3411_2012_256Digest.cs new file mode 100644 index 0000000..8686851 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/digests/GOST3411_2012_256Digest.cs @@ -0,0 +1,54 @@ +using System; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Digests +{ + public class GOST3411_2012_256Digest : GOST3411_2012Digest + { + private readonly static byte[] IV = { + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 + }; + + public override string AlgorithmName + { + get { return "GOST3411-2012-256"; } + } + + public GOST3411_2012_256Digest() : base(IV) + { + + } + + public GOST3411_2012_256Digest(GOST3411_2012_256Digest other) : base(IV) + { + Reset(other); + } + + public override int GetDigestSize() + { + return 32; + } + + public override int DoFinal(byte[] output, int outOff) + { + byte[] result = new byte[64]; + base.DoFinal(result, 0); + + Array.Copy(result, 32, output, outOff, 32); + + return 32; + } + + public override IMemoable Copy() + { + return new GOST3411_2012_256Digest(this); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/digests/GOST3411_2012_512Digest.cs b/bc-sharp-crypto/src/crypto/digests/GOST3411_2012_512Digest.cs new file mode 100644 index 0000000..eb40aba --- /dev/null +++ b/bc-sharp-crypto/src/crypto/digests/GOST3411_2012_512Digest.cs @@ -0,0 +1,43 @@ +using System; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Digests +{ + public class GOST3411_2012_512Digest:GOST3411_2012Digest + { + private readonly static byte[] IV = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + public override string AlgorithmName + { + get { return "GOST3411-2012-512"; } + } + + public GOST3411_2012_512Digest():base(IV) + { + } + + public GOST3411_2012_512Digest(GOST3411_2012_512Digest other) : base(IV) + { + Reset(other); + } + + public override int GetDigestSize() + { + return 64; + } + + public override IMemoable Copy() + { + return new GOST3411_2012_512Digest(this); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/digests/GeneralDigest.cs b/bc-sharp-crypto/src/crypto/digests/GeneralDigest.cs new file mode 100644 index 0000000..d40ad28 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/digests/GeneralDigest.cs @@ -0,0 +1,133 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Digests +{ + /** + * base implementation of MD4 family style digest as outlined in + * "Handbook of Applied Cryptography", pages 344 - 347. + */ + public abstract class GeneralDigest + : IDigest, IMemoable + { + private const int BYTE_LENGTH = 64; + + private byte[] xBuf; + private int xBufOff; + + private long byteCount; + + internal GeneralDigest() + { + xBuf = new byte[4]; + } + + internal GeneralDigest(GeneralDigest t) + { + xBuf = new byte[t.xBuf.Length]; + CopyIn(t); + } + + protected void CopyIn(GeneralDigest t) + { + Array.Copy(t.xBuf, 0, xBuf, 0, t.xBuf.Length); + + xBufOff = t.xBufOff; + byteCount = t.byteCount; + } + + public void Update(byte input) + { + xBuf[xBufOff++] = input; + + if (xBufOff == xBuf.Length) + { + ProcessWord(xBuf, 0); + xBufOff = 0; + } + + byteCount++; + } + + public void BlockUpdate( + byte[] input, + int inOff, + int length) + { + length = System.Math.Max(0, length); + + // + // fill the current word + // + int i = 0; + if (xBufOff != 0) + { + while (i < length) + { + xBuf[xBufOff++] = input[inOff + i++]; + if (xBufOff == 4) + { + ProcessWord(xBuf, 0); + xBufOff = 0; + break; + } + } + } + + // + // process whole words. + // + int limit = ((length - i) & ~3) + i; + for (; i < limit; i += 4) + { + ProcessWord(input, inOff + i); + } + + // + // load in the remainder. + // + while (i < length) + { + xBuf[xBufOff++] = input[inOff + i++]; + } + + byteCount += length; + } + + public void Finish() + { + long bitLength = (byteCount << 3); + + // + // add the pad bytes. + // + Update((byte)128); + + while (xBufOff != 0) Update((byte)0); + ProcessLength(bitLength); + ProcessBlock(); + } + + public virtual void Reset() + { + byteCount = 0; + xBufOff = 0; + Array.Clear(xBuf, 0, xBuf.Length); + } + + public int GetByteLength() + { + return BYTE_LENGTH; + } + + internal abstract void ProcessWord(byte[] input, int inOff); + internal abstract void ProcessLength(long bitLength); + internal abstract void ProcessBlock(); + public abstract string AlgorithmName { get; } + public abstract int GetDigestSize(); + public abstract int DoFinal(byte[] output, int outOff); + public abstract IMemoable Copy(); + public abstract void Reset(IMemoable t); + } +} diff --git a/bc-sharp-crypto/src/crypto/digests/KeccakDigest.cs b/bc-sharp-crypto/src/crypto/digests/KeccakDigest.cs new file mode 100644 index 0000000..8b16e5d --- /dev/null +++ b/bc-sharp-crypto/src/crypto/digests/KeccakDigest.cs @@ -0,0 +1,479 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Crypto.Utilities; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Digests +{ + /// + /// Implementation of Keccak based on following KeccakNISTInterface.c from http://keccak.noekeon.org/ + /// + /// + /// Following the naming conventions used in the C source code to enable easy review of the implementation. + /// + public class KeccakDigest + : IDigest, IMemoable + { + private static readonly ulong[] KeccakRoundConstants = KeccakInitializeRoundConstants(); + + private static readonly int[] KeccakRhoOffsets = KeccakInitializeRhoOffsets(); + + private static ulong[] KeccakInitializeRoundConstants() + { + ulong[] keccakRoundConstants = new ulong[24]; + byte LFSRState = 0x01; + + for (int i = 0; i < 24; i++) + { + keccakRoundConstants[i] = 0; + for (int j = 0; j < 7; j++) + { + int bitPosition = (1 << j) - 1; + + // LFSR86540 + + bool loBit = (LFSRState & 0x01) != 0; + if (loBit) + { + keccakRoundConstants[i] ^= 1UL << bitPosition; + } + + bool hiBit = (LFSRState & 0x80) != 0; + LFSRState <<= 1; + if (hiBit) + { + LFSRState ^= 0x71; + } + + } + } + + return keccakRoundConstants; + } + + private static int[] KeccakInitializeRhoOffsets() + { + int[] keccakRhoOffsets = new int[25]; + int x, y, t, newX, newY; + + int rhoOffset = 0; + keccakRhoOffsets[0] = rhoOffset; + x = 1; + y = 0; + for (t = 1; t < 25; t++) + { + rhoOffset = (rhoOffset + t) & 63; + keccakRhoOffsets[(((x) % 5) + 5 * ((y) % 5))] = rhoOffset; + newX = (0 * x + 1 * y) % 5; + newY = (2 * x + 3 * y) % 5; + x = newX; + y = newY; + } + + return keccakRhoOffsets; + } + + private static readonly int STATE_LENGTH = (1600 / 8); + + private ulong[] state = new ulong[STATE_LENGTH / 8]; + protected byte[] dataQueue = new byte[1536 / 8]; + protected int rate; + protected int bitsInQueue; + protected int fixedOutputLength; + protected bool squeezing; + protected int bitsAvailableForSqueezing; + + public KeccakDigest() + : this(288) + { + } + + public KeccakDigest(int bitLength) + { + Init(bitLength); + } + + public KeccakDigest(KeccakDigest source) + { + CopyIn(source); + } + + private void CopyIn(KeccakDigest source) + { + Array.Copy(source.state, 0, this.state, 0, source.state.Length); + Array.Copy(source.dataQueue, 0, this.dataQueue, 0, source.dataQueue.Length); + this.rate = source.rate; + this.bitsInQueue = source.bitsInQueue; + this.fixedOutputLength = source.fixedOutputLength; + this.squeezing = source.squeezing; + this.bitsAvailableForSqueezing = source.bitsAvailableForSqueezing; + } + + public virtual string AlgorithmName + { + get { return "Keccak-" + fixedOutputLength; } + } + + public virtual int GetDigestSize() + { + return fixedOutputLength >> 3; + } + + public virtual void Update(byte input) + { + Absorb(new byte[]{ input }, 0, 1); + } + + public virtual void BlockUpdate(byte[] input, int inOff, int len) + { + Absorb(input, inOff, len); + } + + public virtual int DoFinal(byte[] output, int outOff) + { + Squeeze(output, outOff, fixedOutputLength >> 3); + + Reset(); + + return GetDigestSize(); + } + + /* + * TODO Possible API change to support partial-byte suffixes. + */ + protected virtual int DoFinal(byte[] output, int outOff, byte partialByte, int partialBits) + { + if (partialBits > 0) + { + AbsorbBits(partialByte, partialBits); + } + + Squeeze(output, outOff, fixedOutputLength >> 3); + + Reset(); + + return GetDigestSize(); + } + + public virtual void Reset() + { + Init(fixedOutputLength); + } + + /** + * Return the size of block that the compression function is applied to in bytes. + * + * @return internal byte length of a block. + */ + public virtual int GetByteLength() + { + return rate >> 3; + } + + private void Init(int bitLength) + { + switch (bitLength) + { + case 128: + case 224: + case 256: + case 288: + case 384: + case 512: + InitSponge(1600 - (bitLength << 1)); + break; + default: + throw new ArgumentException("must be one of 128, 224, 256, 288, 384, or 512.", "bitLength"); + } + } + + private void InitSponge(int rate) + { + if (rate <= 0 || rate >= 1600 || (rate & 63) != 0) + throw new InvalidOperationException("invalid rate value"); + + this.rate = rate; + Array.Clear(state, 0, state.Length); + Arrays.Fill(this.dataQueue, (byte)0); + this.bitsInQueue = 0; + this.squeezing = false; + this.bitsAvailableForSqueezing = 0; + this.fixedOutputLength = (1600 - rate) >> 1; + } + + protected void Absorb(byte[] data, int off, int len) + { + if ((bitsInQueue & 7) != 0) + throw new InvalidOperationException("attempt to absorb with odd length queue"); + if (squeezing) + throw new InvalidOperationException("attempt to absorb while squeezing"); + + int bytesInQueue = bitsInQueue >> 3; + int rateBytes = rate >> 3; + + int count = 0; + while (count < len) + { + if (bytesInQueue == 0 && count <= (len - rateBytes)) + { + do + { + KeccakAbsorb(data, off + count); + count += rateBytes; + } + while (count <= (len - rateBytes)); + } + else + { + int partialBlock = System.Math.Min(rateBytes - bytesInQueue, len - count); + Array.Copy(data, off + count, dataQueue, bytesInQueue, partialBlock); + + bytesInQueue += partialBlock; + count += partialBlock; + + if (bytesInQueue == rateBytes) + { + KeccakAbsorb(dataQueue, 0); + bytesInQueue = 0; + } + } + } + + bitsInQueue = bytesInQueue << 3; + } + + protected void AbsorbBits(int data, int bits) + { + if (bits < 1 || bits > 7) + throw new ArgumentException("must be in the range 1 to 7", "bits"); + if ((bitsInQueue & 7) != 0) + throw new InvalidOperationException("attempt to absorb with odd length queue"); + if (squeezing) + throw new InvalidOperationException("attempt to absorb while squeezing"); + + int mask = (1 << bits) - 1; + dataQueue[bitsInQueue >> 3] = (byte)(data & mask); + + // NOTE: After this, bitsInQueue is no longer a multiple of 8, so no more absorbs will work + bitsInQueue += bits; + } + + private void PadAndSwitchToSqueezingPhase() + { + Debug.Assert(bitsInQueue < rate); + + dataQueue[bitsInQueue >> 3] |= (byte)(1U << (bitsInQueue & 7)); + + if (++bitsInQueue == rate) + { + KeccakAbsorb(dataQueue, 0); + bitsInQueue = 0; + } + + { + int full = bitsInQueue >> 6, partial = bitsInQueue & 63; + int off = 0; + for (int i = 0; i < full; ++i) + { + state[i] ^= Pack.LE_To_UInt64(dataQueue, off); + off += 8; + } + if (partial > 0) + { + ulong mask = (1UL << partial) - 1UL; + state[full] ^= Pack.LE_To_UInt64(dataQueue, off) & mask; + } + state[(rate - 1) >> 6] ^= (1UL << 63); + } + + KeccakPermutation(); + KeccakExtract(); + bitsAvailableForSqueezing = rate; + + bitsInQueue = 0; + squeezing = true; + } + + protected void Squeeze(byte[] output, int off, int len) + { + if (!squeezing) + { + PadAndSwitchToSqueezingPhase(); + } + + long outputLength = (long)len << 3; + long i = 0; + while (i < outputLength) + { + if (bitsAvailableForSqueezing == 0) + { + KeccakPermutation(); + KeccakExtract(); + bitsAvailableForSqueezing = rate; + } + + int partialBlock = (int)System.Math.Min((long)bitsAvailableForSqueezing, outputLength - i); + Array.Copy(dataQueue, (rate - bitsAvailableForSqueezing) >> 3, output, off + (int)(i >> 3), partialBlock >> 3); + bitsAvailableForSqueezing -= partialBlock; + i += partialBlock; + } + } + + private void KeccakAbsorb(byte[] data, int off) + { + int count = rate >> 6; + for (int i = 0; i < count; ++i) + { + state[i] ^= Pack.LE_To_UInt64(data, off); + off += 8; + } + + KeccakPermutation(); + } + + private void KeccakExtract() + { + Pack.UInt64_To_LE(state, 0, rate >> 6, dataQueue, 0); + } + + private void KeccakPermutation() + { + for (int i = 0; i < 24; i++) + { + Theta(state); + Rho(state); + Pi(state); + Chi(state); + Iota(state, i); + } + } + + private static ulong leftRotate(ulong v, int r) + { + return (v << r) | (v >> -r); + } + + private static void Theta(ulong[] A) + { + ulong C0 = A[0 + 0] ^ A[0 + 5] ^ A[0 + 10] ^ A[0 + 15] ^ A[0 + 20]; + ulong C1 = A[1 + 0] ^ A[1 + 5] ^ A[1 + 10] ^ A[1 + 15] ^ A[1 + 20]; + ulong C2 = A[2 + 0] ^ A[2 + 5] ^ A[2 + 10] ^ A[2 + 15] ^ A[2 + 20]; + ulong C3 = A[3 + 0] ^ A[3 + 5] ^ A[3 + 10] ^ A[3 + 15] ^ A[3 + 20]; + ulong C4 = A[4 + 0] ^ A[4 + 5] ^ A[4 + 10] ^ A[4 + 15] ^ A[4 + 20]; + + ulong dX = leftRotate(C1, 1) ^ C4; + + A[0] ^= dX; + A[5] ^= dX; + A[10] ^= dX; + A[15] ^= dX; + A[20] ^= dX; + + dX = leftRotate(C2, 1) ^ C0; + + A[1] ^= dX; + A[6] ^= dX; + A[11] ^= dX; + A[16] ^= dX; + A[21] ^= dX; + + dX = leftRotate(C3, 1) ^ C1; + + A[2] ^= dX; + A[7] ^= dX; + A[12] ^= dX; + A[17] ^= dX; + A[22] ^= dX; + + dX = leftRotate(C4, 1) ^ C2; + + A[3] ^= dX; + A[8] ^= dX; + A[13] ^= dX; + A[18] ^= dX; + A[23] ^= dX; + + dX = leftRotate(C0, 1) ^ C3; + + A[4] ^= dX; + A[9] ^= dX; + A[14] ^= dX; + A[19] ^= dX; + A[24] ^= dX; + } + + private static void Rho(ulong[] A) + { + // KeccakRhoOffsets[0] == 0 + for (int x = 1; x < 25; x++) + { + A[x] = leftRotate(A[x], KeccakRhoOffsets[x]); + } + } + + private static void Pi(ulong[] A) + { + ulong a1 = A[1]; + A[1] = A[6]; + A[6] = A[9]; + A[9] = A[22]; + A[22] = A[14]; + A[14] = A[20]; + A[20] = A[2]; + A[2] = A[12]; + A[12] = A[13]; + A[13] = A[19]; + A[19] = A[23]; + A[23] = A[15]; + A[15] = A[4]; + A[4] = A[24]; + A[24] = A[21]; + A[21] = A[8]; + A[8] = A[16]; + A[16] = A[5]; + A[5] = A[3]; + A[3] = A[18]; + A[18] = A[17]; + A[17] = A[11]; + A[11] = A[7]; + A[7] = A[10]; + A[10] = a1; + } + + private static void Chi(ulong[] A) + { + ulong chiC0, chiC1, chiC2, chiC3, chiC4; + + for (int yBy5 = 0; yBy5 < 25; yBy5 += 5) + { + chiC0 = A[0 + yBy5] ^ ((~A[(((0 + 1) % 5) + yBy5)]) & A[(((0 + 2) % 5) + yBy5)]); + chiC1 = A[1 + yBy5] ^ ((~A[(((1 + 1) % 5) + yBy5)]) & A[(((1 + 2) % 5) + yBy5)]); + chiC2 = A[2 + yBy5] ^ ((~A[(((2 + 1) % 5) + yBy5)]) & A[(((2 + 2) % 5) + yBy5)]); + chiC3 = A[3 + yBy5] ^ ((~A[(((3 + 1) % 5) + yBy5)]) & A[(((3 + 2) % 5) + yBy5)]); + chiC4 = A[4 + yBy5] ^ ((~A[(((4 + 1) % 5) + yBy5)]) & A[(((4 + 2) % 5) + yBy5)]); + + A[0 + yBy5] = chiC0; + A[1 + yBy5] = chiC1; + A[2 + yBy5] = chiC2; + A[3 + yBy5] = chiC3; + A[4 + yBy5] = chiC4; + } + } + + private static void Iota(ulong[] A, int indexRound) + { + A[0] ^= KeccakRoundConstants[indexRound]; + } + + public virtual IMemoable Copy() + { + return new KeccakDigest(this); + } + + public virtual void Reset(IMemoable other) + { + CopyIn((KeccakDigest)other); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/digests/LongDigest.cs b/bc-sharp-crypto/src/crypto/digests/LongDigest.cs new file mode 100644 index 0000000..9ee9bcd --- /dev/null +++ b/bc-sharp-crypto/src/crypto/digests/LongDigest.cs @@ -0,0 +1,355 @@ +using System; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Utilities; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Digests +{ + /** + * Base class for SHA-384 and SHA-512. + */ + public abstract class LongDigest + : IDigest, IMemoable + { + private int MyByteLength = 128; + + private byte[] xBuf; + private int xBufOff; + + private long byteCount1; + private long byteCount2; + + internal ulong H1, H2, H3, H4, H5, H6, H7, H8; + + private ulong[] W = new ulong[80]; + private int wOff; + + /** + * Constructor for variable length word + */ + internal LongDigest() + { + xBuf = new byte[8]; + + Reset(); + } + + /** + * Copy constructor. We are using copy constructors in place + * of the object.Clone() interface as this interface is not + * supported by J2ME. + */ + internal LongDigest( + LongDigest t) + { + xBuf = new byte[t.xBuf.Length]; + + CopyIn(t); + } + + protected void CopyIn(LongDigest t) + { + Array.Copy(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; + + Array.Copy(t.W, 0, W, 0, t.W.Length); + wOff = t.wOff; + } + + public void Update( + byte input) + { + xBuf[xBufOff++] = input; + + if (xBufOff == xBuf.Length) + { + ProcessWord(xBuf, 0); + xBufOff = 0; + } + + byteCount1++; + } + + public void BlockUpdate( + byte[] input, + int inOff, + int length) + { + // + // fill the current word + // + while ((xBufOff != 0) && (length > 0)) + { + Update(input[inOff]); + + inOff++; + length--; + } + + // + // process whole words. + // + while (length > xBuf.Length) + { + ProcessWord(input, inOff); + + inOff += xBuf.Length; + length -= xBuf.Length; + byteCount1 += xBuf.Length; + } + + // + // load in the remainder. + // + while (length > 0) + { + Update(input[inOff]); + + inOff++; + length--; + } + } + + 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 virtual void Reset() + { + byteCount1 = 0; + byteCount2 = 0; + + xBufOff = 0; + for ( int i = 0; i < xBuf.Length; i++ ) + { + xBuf[i] = 0; + } + + wOff = 0; + Array.Clear(W, 0, W.Length); + } + + internal void ProcessWord( + byte[] input, + int inOff) + { + W[wOff] = Pack.BE_To_UInt64(input, 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 += (long) ((ulong) byteCount1 >> 61); + byteCount1 &= 0x1fffffffffffffffL; + } + } + + internal void ProcessLength( + long lowW, + long hiW) + { + if (wOff > 14) + { + ProcessBlock(); + } + + W[14] = (ulong)hiW; + W[15] = (ulong)lowW; + } + + internal void ProcessBlock() + { + AdjustByteCounts(); + + // + // expand 16 word block into 80 word blocks. + // + for (int ti = 16; ti <= 79; ++ti) + { + W[ti] = Sigma1(W[ti - 2]) + W[ti - 7] + Sigma0(W[ti - 15]) + W[ti - 16]; + } + + // + // set up working variables. + // + ulong a = H1; + ulong b = H2; + ulong c = H3; + ulong d = H4; + ulong e = H5; + ulong f = H6; + ulong g = H7; + ulong 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; + Array.Clear(W, 0, 16); + } + + /* SHA-384 and SHA-512 functions (as for SHA-256 but for longs) */ + private static ulong Ch(ulong x, ulong y, ulong z) + { + return (x & y) ^ (~x & z); + } + + private static ulong Maj(ulong x, ulong y, ulong z) + { + return (x & y) ^ (x & z) ^ (y & z); + } + + private static ulong Sum0(ulong x) + { + return ((x << 36) | (x >> 28)) ^ ((x << 30) | (x >> 34)) ^ ((x << 25) | (x >> 39)); + } + + private static ulong Sum1(ulong x) + { + return ((x << 50) | (x >> 14)) ^ ((x << 46) | (x >> 18)) ^ ((x << 23) | (x >> 41)); + } + + private static ulong Sigma0(ulong x) + { + return ((x << 63) | (x >> 1)) ^ ((x << 56) | (x >> 8)) ^ (x >> 7); + } + + private static ulong Sigma1(ulong 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) + */ + internal static readonly ulong[] K = + { + 0x428a2f98d728ae22, 0x7137449123ef65cd, 0xb5c0fbcfec4d3b2f, 0xe9b5dba58189dbbc, + 0x3956c25bf348b538, 0x59f111f1b605d019, 0x923f82a4af194f9b, 0xab1c5ed5da6d8118, + 0xd807aa98a3030242, 0x12835b0145706fbe, 0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2, + 0x72be5d74f27b896f, 0x80deb1fe3b1696b1, 0x9bdc06a725c71235, 0xc19bf174cf692694, + 0xe49b69c19ef14ad2, 0xefbe4786384f25e3, 0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65, + 0x2de92c6f592b0275, 0x4a7484aa6ea6e483, 0x5cb0a9dcbd41fbd4, 0x76f988da831153b5, + 0x983e5152ee66dfab, 0xa831c66d2db43210, 0xb00327c898fb213f, 0xbf597fc7beef0ee4, + 0xc6e00bf33da88fc2, 0xd5a79147930aa725, 0x06ca6351e003826f, 0x142929670a0e6e70, + 0x27b70a8546d22ffc, 0x2e1b21385c26c926, 0x4d2c6dfc5ac42aed, 0x53380d139d95b3df, + 0x650a73548baf63de, 0x766a0abb3c77b2a8, 0x81c2c92e47edaee6, 0x92722c851482353b, + 0xa2bfe8a14cf10364, 0xa81a664bbc423001, 0xc24b8b70d0f89791, 0xc76c51a30654be30, + 0xd192e819d6ef5218, 0xd69906245565a910, 0xf40e35855771202a, 0x106aa07032bbd1b8, + 0x19a4c116b8d2d0c8, 0x1e376c085141ab53, 0x2748774cdf8eeb99, 0x34b0bcb5e19b48a8, + 0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb, 0x5b9cca4f7763e373, 0x682e6ff3d6b2b8a3, + 0x748f82ee5defb2fc, 0x78a5636f43172f60, 0x84c87814a1f0ab72, 0x8cc702081a6439ec, + 0x90befffa23631e28, 0xa4506cebde82bde9, 0xbef9a3f7b2c67915, 0xc67178f2e372532b, + 0xca273eceea26619c, 0xd186b8c721c0c207, 0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178, + 0x06f067aa72176fba, 0x0a637dc5a2c898a6, 0x113f9804bef90dae, 0x1b710b35131c471b, + 0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc, 0x431d67c49c100d4c, + 0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, 0x5fcb6fab3ad6faec, 0x6c44198c4a475817 + }; + + public int GetByteLength() + { + return MyByteLength; + } + + public abstract string AlgorithmName { get; } + public abstract int GetDigestSize(); + public abstract int DoFinal(byte[] output, int outOff); + public abstract IMemoable Copy(); + public abstract void Reset(IMemoable t); + } +} diff --git a/bc-sharp-crypto/src/crypto/digests/MD2Digest.cs b/bc-sharp-crypto/src/crypto/digests/MD2Digest.cs new file mode 100644 index 0000000..6d90f3f --- /dev/null +++ b/bc-sharp-crypto/src/crypto/digests/MD2Digest.cs @@ -0,0 +1,269 @@ +using System; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Digests +{ + + /** + * implementation of MD2 + * as outlined in RFC1319 by B.Kaliski from RSA Laboratories April 1992 + */ + public class MD2Digest + : IDigest, IMemoable + { + private const int DigestLength = 16; + private const int BYTE_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) + { + CopyIn(t); + } + + private void CopyIn(MD2Digest t) + { + Array.Copy(t.X, 0, X, 0, t.X.Length); + xOff = t.xOff; + Array.Copy(t.M, 0, M, 0, t.M.Length); + mOff = t.mOff; + Array.Copy(t.C, 0, C, 0, t.C.Length); + COff = t.COff; + } + + /** + * return the algorithm name + * + * @return the algorithm name + */ + public string AlgorithmName + { + get { return "MD2"; } + } + + public int GetDigestSize() + { + return DigestLength; + } + + public int GetByteLength() + { + return BYTE_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[] output, int outOff) + { + // add padding + byte paddingByte = (byte)(M.Length - mOff); + for (int i=mOff;i 0)) + { + Update(input[inOff]); + inOff++; + length--; + } + + // + // process whole words. + // + while (length > 16) + { + Array.Copy(input,inOff,M,0,16); + ProcessChecksum(M); + ProcessBlock(M); + length -= 16; + inOff += 16; + } + + // + // load in the remainder. + // + while (length > 0) + { + Update(input[inOff]); + inOff++; + length--; + } + } + + internal 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]; + } + } + internal 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 readonly 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 IMemoable Copy() + { + return new MD2Digest(this); + } + + public void Reset(IMemoable other) + { + MD2Digest d = (MD2Digest)other; + + CopyIn(d); + } + + } + +} diff --git a/bc-sharp-crypto/src/crypto/digests/MD4Digest.cs b/bc-sharp-crypto/src/crypto/digests/MD4Digest.cs new file mode 100644 index 0000000..8743f7d --- /dev/null +++ b/bc-sharp-crypto/src/crypto/digests/MD4Digest.cs @@ -0,0 +1,292 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.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 compatibility + * with legacy applications, it's not secure, don't use it for anything new!

+ */ + public class MD4Digest + : GeneralDigest + { + private const int DigestLength = 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) : base(t) + { + CopyIn(t); + } + + private void CopyIn(MD4Digest t) + { + base.CopyIn(t); + H1 = t.H1; + H2 = t.H2; + H3 = t.H3; + H4 = t.H4; + + Array.Copy(t.X, 0, X, 0, t.X.Length); + xOff = t.xOff; + } + + public override string AlgorithmName + { + get { return "MD4"; } + } + + public override int GetDigestSize() + { + return DigestLength; + } + + internal override void ProcessWord( + byte[] input, + int inOff) + { + X[xOff++] = (input[inOff] & 0xff) | ((input[inOff + 1] & 0xff) << 8) + | ((input[inOff + 2] & 0xff) << 16) | ((input[inOff + 3] & 0xff) << 24); + + if (xOff == 16) + { + ProcessBlock(); + } + } + + internal override void ProcessLength( + long bitLength) + { + if (xOff > 14) + { + ProcessBlock(); + } + + X[14] = (int)(bitLength & 0xffffffff); + X[15] = (int)((ulong) bitLength >> 32); + } + + private void UnpackWord( + int word, + byte[] outBytes, + int outOff) + { + outBytes[outOff] = (byte)word; + outBytes[outOff + 1] = (byte)((uint) word >> 8); + outBytes[outOff + 2] = (byte)((uint) word >> 16); + outBytes[outOff + 3] = (byte)((uint) word >> 24); + } + + public override int DoFinal( + byte[] output, + int outOff) + { + Finish(); + + UnpackWord(H1, output, outOff); + UnpackWord(H2, output, outOff + 4); + UnpackWord(H3, output, outOff + 8); + UnpackWord(H4, output, outOff + 12); + + Reset(); + + return DigestLength; + } + + /** + * reset the chaining variables to the IV values. + */ + public override void Reset() + { + base.Reset(); + + H1 = unchecked((int) 0x67452301); + H2 = unchecked((int) 0xefcdab89); + H3 = unchecked((int) 0x98badcfe); + H4 = unchecked((int) 0x10325476); + + xOff = 0; + + for (int i = 0; i != X.Length; i++) + { + X[i] = 0; + } + } + + // + // round 1 left rotates + // + private const int S11 = 3; + private const int S12 = 7; + private const int S13 = 11; + private const int S14 = 19; + + // + // round 2 left rotates + // + private const int S21 = 3; + private const int S22 = 5; + private const int S23 = 9; + private const int S24 = 13; + + // + // round 3 left rotates + // + private const int S31 = 3; + private const int S32 = 9; + private const int S33 = 11; + private const int S34 = 15; + + /* + * rotate int x left n bits. + */ + private int RotateLeft( + int x, + int n) + { + return (x << n) | (int) ((uint) 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; + } + + internal override 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; + } + } + + public override IMemoable Copy() + { + return new MD4Digest(this); + } + + public override void Reset(IMemoable other) + { + MD4Digest d = (MD4Digest)other; + + CopyIn(d); + } + + } + +} diff --git a/bc-sharp-crypto/src/crypto/digests/MD5Digest.cs b/bc-sharp-crypto/src/crypto/digests/MD5Digest.cs new file mode 100644 index 0000000..c60ac92 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/digests/MD5Digest.cs @@ -0,0 +1,313 @@ +using System; + +using Org.BouncyCastle.Crypto.Utilities; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Digests +{ + /** + * implementation of MD5 as outlined in "Handbook of Applied Cryptography", pages 346 - 347. + */ + public class MD5Digest + : GeneralDigest + { + private const int DigestLength = 16; + + private uint H1, H2, H3, H4; // IV's + + private uint[] X = new uint[16]; + private int xOff; + + public MD5Digest() + { + Reset(); + } + + /** + * Copy constructor. This will copy the state of the provided + * message digest. + */ + public MD5Digest(MD5Digest t) + : base(t) + { + CopyIn(t); + } + + private void CopyIn(MD5Digest t) + { + base.CopyIn(t); + H1 = t.H1; + H2 = t.H2; + H3 = t.H3; + H4 = t.H4; + + Array.Copy(t.X, 0, X, 0, t.X.Length); + xOff = t.xOff; + } + + public override string AlgorithmName + { + get { return "MD5"; } + } + + public override int GetDigestSize() + { + return DigestLength; + } + + internal override void ProcessWord( + byte[] input, + int inOff) + { + X[xOff] = Pack.LE_To_UInt32(input, inOff); + + if (++xOff == 16) + { + ProcessBlock(); + } + } + + internal override void ProcessLength( + long bitLength) + { + if (xOff > 14) + { + if (xOff == 15) + X[15] = 0; + + ProcessBlock(); + } + + for (int i = xOff; i < 14; ++i) + { + X[i] = 0; + } + + X[14] = (uint)((ulong)bitLength); + X[15] = (uint)((ulong)bitLength >> 32); + } + + public override int DoFinal( + byte[] output, + int outOff) + { + Finish(); + + Pack.UInt32_To_LE(H1, output, outOff); + Pack.UInt32_To_LE(H2, output, outOff + 4); + Pack.UInt32_To_LE(H3, output, outOff + 8); + Pack.UInt32_To_LE(H4, output, outOff + 12); + + Reset(); + + return DigestLength; + } + + /** + * reset the chaining variables to the IV values. + */ + public override void Reset() + { + base.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 readonly int S11 = 7; + private static readonly int S12 = 12; + private static readonly int S13 = 17; + private static readonly int S14 = 22; + + // + // round 2 left rotates + // + private static readonly int S21 = 5; + private static readonly int S22 = 9; + private static readonly int S23 = 14; + private static readonly int S24 = 20; + + // + // round 3 left rotates + // + private static readonly int S31 = 4; + private static readonly int S32 = 11; + private static readonly int S33 = 16; + private static readonly int S34 = 23; + + // + // round 4 left rotates + // + private static readonly int S41 = 6; + private static readonly int S42 = 10; + private static readonly int S43 = 15; + private static readonly int S44 = 21; + + /* + * rotate int x left n bits. + */ + private static uint RotateLeft( + uint x, + int n) + { + return (x << n) | (x >> (32 - n)); + } + + /* + * F, G, H and I are the basic MD5 functions. + */ + private static uint F( + uint u, + uint v, + uint w) + { + return (u & v) | (~u & w); + } + + private static uint G( + uint u, + uint v, + uint w) + { + return (u & w) | (v & ~w); + } + + private static uint H( + uint u, + uint v, + uint w) + { + return u ^ v ^ w; + } + + private static uint K( + uint u, + uint v, + uint w) + { + return v ^ (u | ~w); + } + + internal override void ProcessBlock() + { + uint a = H1; + uint b = H2; + uint c = H3; + uint 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; + + xOff = 0; + } + + public override IMemoable Copy() + { + return new MD5Digest(this); + } + + public override void Reset(IMemoable other) + { + MD5Digest d = (MD5Digest)other; + + CopyIn(d); + } + + } + +} + diff --git a/bc-sharp-crypto/src/crypto/digests/NonMemoableDigest.cs b/bc-sharp-crypto/src/crypto/digests/NonMemoableDigest.cs new file mode 100644 index 0000000..02c49b8 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/digests/NonMemoableDigest.cs @@ -0,0 +1,62 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Digests +{ + /** + * Wrapper removes exposure to the IMemoable interface on an IDigest implementation. + */ + public class NonMemoableDigest + : IDigest + { + protected readonly IDigest mBaseDigest; + + /** + * Base constructor. + * + * @param baseDigest underlying digest to use. + * @exception IllegalArgumentException if baseDigest is null + */ + public NonMemoableDigest(IDigest baseDigest) + { + if (baseDigest == null) + throw new ArgumentNullException("baseDigest"); + + this.mBaseDigest = baseDigest; + } + + public virtual string AlgorithmName + { + get { return mBaseDigest.AlgorithmName; } + } + + public virtual int GetDigestSize() + { + return mBaseDigest.GetDigestSize(); + } + + public virtual void Update(byte input) + { + mBaseDigest.Update(input); + } + + public virtual void BlockUpdate(byte[] input, int inOff, int len) + { + mBaseDigest.BlockUpdate(input, inOff, len); + } + + public virtual int DoFinal(byte[] output, int outOff) + { + return mBaseDigest.DoFinal(output, outOff); + } + + public virtual void Reset() + { + mBaseDigest.Reset(); + } + + public virtual int GetByteLength() + { + return mBaseDigest.GetByteLength(); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/digests/NullDigest.cs b/bc-sharp-crypto/src/crypto/digests/NullDigest.cs new file mode 100644 index 0000000..e598cb1 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/digests/NullDigest.cs @@ -0,0 +1,49 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Digests +{ + public class NullDigest : IDigest + { + private readonly MemoryStream bOut = new MemoryStream(); + + public string AlgorithmName + { + get { return "NULL"; } + } + + public int GetByteLength() + { + // TODO Is this okay? + return 0; + } + + public int GetDigestSize() + { + return (int) bOut.Length; + } + + public void Update(byte b) + { + bOut.WriteByte(b); + } + + public void BlockUpdate(byte[] inBytes, int inOff, int len) + { + bOut.Write(inBytes, inOff, len); + } + + public int DoFinal(byte[] outBytes, int outOff) + { + byte[] res = bOut.ToArray(); + res.CopyTo(outBytes, outOff); + Reset(); + return res.Length; + } + + public void Reset() + { + bOut.SetLength(0); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/digests/RipeMD128Digest.cs b/bc-sharp-crypto/src/crypto/digests/RipeMD128Digest.cs new file mode 100644 index 0000000..e8a0331 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/digests/RipeMD128Digest.cs @@ -0,0 +1,484 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Digests +{ + /** + * implementation of RipeMD128 + */ + public class RipeMD128Digest + : GeneralDigest + { + private const int DigestLength = 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) : base(t) + { + CopyIn(t); + } + + private void CopyIn(RipeMD128Digest t) + { + base.CopyIn(t); + + H0 = t.H0; + H1 = t.H1; + H2 = t.H2; + H3 = t.H3; + + Array.Copy(t.X, 0, X, 0, t.X.Length); + xOff = t.xOff; + } + + public override string AlgorithmName + { + get { return "RIPEMD128"; } + } + + public override int GetDigestSize() + { + return DigestLength; + } + + internal override void ProcessWord( + byte[] input, + int inOff) + { + X[xOff++] = (input[inOff] & 0xff) | ((input[inOff + 1] & 0xff) << 8) + | ((input[inOff + 2] & 0xff) << 16) | ((input[inOff + 3] & 0xff) << 24); + + if (xOff == 16) + { + ProcessBlock(); + } + } + + internal override void ProcessLength( + long bitLength) + { + if (xOff > 14) + { + ProcessBlock(); + } + + X[14] = (int)(bitLength & 0xffffffff); + X[15] = (int)((ulong) bitLength >> 32); + } + + private void UnpackWord( + int word, + byte[] outBytes, + int outOff) + { + outBytes[outOff] = (byte)word; + outBytes[outOff + 1] = (byte)((uint) word >> 8); + outBytes[outOff + 2] = (byte)((uint) word >> 16); + outBytes[outOff + 3] = (byte)((uint) word >> 24); + } + + public override int DoFinal( + byte[] output, + int outOff) + { + Finish(); + + UnpackWord(H0, output, outOff); + UnpackWord(H1, output, outOff + 4); + UnpackWord(H2, output, outOff + 8); + UnpackWord(H3, output, outOff + 12); + + Reset(); + + return DigestLength; + } + + /** + * reset the chaining variables to the IV values. + */ + public override void Reset() + { + base.Reset(); + + H0 = unchecked((int) 0x67452301); + H1 = unchecked((int) 0xefcdab89); + H2 = unchecked((int) 0x98badcfe); + H3 = unchecked((int) 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) | (int) ((uint) 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 + unchecked((int) 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 + unchecked((int) 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 + unchecked((int) 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 + unchecked((int) 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 + unchecked((int) 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 + unchecked((int) 0x50a28be6), s); + } + + internal override 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; + } + } + + public override IMemoable Copy() + { + return new RipeMD128Digest(this); + } + + public override void Reset(IMemoable other) + { + RipeMD128Digest d = (RipeMD128Digest)other; + + CopyIn(d); + } + + } + +} diff --git a/bc-sharp-crypto/src/crypto/digests/RipeMD160Digest.cs b/bc-sharp-crypto/src/crypto/digests/RipeMD160Digest.cs new file mode 100644 index 0000000..af4aa44 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/digests/RipeMD160Digest.cs @@ -0,0 +1,445 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Digests +{ + /** + * implementation of RipeMD see, + * http://www.esat.kuleuven.ac.be/~bosselae/ripemd160.html + */ + public class RipeMD160Digest + : GeneralDigest + { + private const int DigestLength = 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) : base(t) + { + CopyIn(t); + } + + private void CopyIn(RipeMD160Digest t) + { + base.CopyIn(t); + + H0 = t.H0; + H1 = t.H1; + H2 = t.H2; + H3 = t.H3; + H4 = t.H4; + + Array.Copy(t.X, 0, X, 0, t.X.Length); + xOff = t.xOff; + } + + public override string AlgorithmName + { + get { return "RIPEMD160"; } + } + + public override int GetDigestSize() + { + return DigestLength; + } + + internal override void ProcessWord( + byte[] input, + int inOff) + { + X[xOff++] = (input[inOff] & 0xff) | ((input[inOff + 1] & 0xff) << 8) + | ((input[inOff + 2] & 0xff) << 16) | ((input[inOff + 3] & 0xff) << 24); + + if (xOff == 16) + { + ProcessBlock(); + } + } + + internal override void ProcessLength( + long bitLength) + { + if (xOff > 14) + { + ProcessBlock(); + } + + X[14] = (int)(bitLength & 0xffffffff); + X[15] = (int)((ulong) bitLength >> 32); + } + + private void UnpackWord( + int word, + byte[] outBytes, + int outOff) + { + outBytes[outOff] = (byte)word; + outBytes[outOff + 1] = (byte)((uint) word >> 8); + outBytes[outOff + 2] = (byte)((uint) word >> 16); + outBytes[outOff + 3] = (byte)((uint) word >> 24); + } + + public override int DoFinal( + byte[] output, + int outOff) + { + Finish(); + + UnpackWord(H0, output, outOff); + UnpackWord(H1, output, outOff + 4); + UnpackWord(H2, output, outOff + 8); + UnpackWord(H3, output, outOff + 12); + UnpackWord(H4, output, outOff + 16); + + Reset(); + + return DigestLength; + } + + /** + * reset the chaining variables to the IV values. + */ + public override void Reset() + { + base.Reset(); + + H0 = unchecked((int) 0x67452301); + H1 = unchecked((int) 0xefcdab89); + H2 = unchecked((int) 0x98badcfe); + H3 = unchecked((int) 0x10325476); + H4 = unchecked((int) 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) | (int) ((uint) 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); + } + + internal override 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] + unchecked((int) 0x50a28be6), 8) + ee; cc = RL(cc, 10); + ee = RL(ee + F5(aa,bb,cc) + X[14] + unchecked((int) 0x50a28be6), 9) + dd; bb = RL(bb, 10); + dd = RL(dd + F5(ee,aa,bb) + X[ 7] + unchecked((int) 0x50a28be6), 9) + cc; aa = RL(aa, 10); + cc = RL(cc + F5(dd,ee,aa) + X[ 0] + unchecked((int) 0x50a28be6), 11) + bb; ee = RL(ee, 10); + bb = RL(bb + F5(cc,dd,ee) + X[ 9] + unchecked((int) 0x50a28be6), 13) + aa; dd = RL(dd, 10); + aa = RL(aa + F5(bb,cc,dd) + X[ 2] + unchecked((int) 0x50a28be6), 15) + ee; cc = RL(cc, 10); + ee = RL(ee + F5(aa,bb,cc) + X[11] + unchecked((int) 0x50a28be6), 15) + dd; bb = RL(bb, 10); + dd = RL(dd + F5(ee,aa,bb) + X[ 4] + unchecked((int) 0x50a28be6), 5) + cc; aa = RL(aa, 10); + cc = RL(cc + F5(dd,ee,aa) + X[13] + unchecked((int) 0x50a28be6), 7) + bb; ee = RL(ee, 10); + bb = RL(bb + F5(cc,dd,ee) + X[ 6] + unchecked((int) 0x50a28be6), 7) + aa; dd = RL(dd, 10); + aa = RL(aa + F5(bb,cc,dd) + X[15] + unchecked((int) 0x50a28be6), 8) + ee; cc = RL(cc, 10); + ee = RL(ee + F5(aa,bb,cc) + X[ 8] + unchecked((int) 0x50a28be6), 11) + dd; bb = RL(bb, 10); + dd = RL(dd + F5(ee,aa,bb) + X[ 1] + unchecked((int) 0x50a28be6), 14) + cc; aa = RL(aa, 10); + cc = RL(cc + F5(dd,ee,aa) + X[10] + unchecked((int) 0x50a28be6), 14) + bb; ee = RL(ee, 10); + bb = RL(bb + F5(cc,dd,ee) + X[ 3] + unchecked((int) 0x50a28be6), 12) + aa; dd = RL(dd, 10); + aa = RL(aa + F5(bb,cc,dd) + X[12] + unchecked((int) 0x50a28be6), 6) + ee; cc = RL(cc, 10); + + // + // Rounds 16-31 + // + // left + e = RL(e + F2(a,b,c) + X[ 7] + unchecked((int) 0x5a827999), 7) + d; b = RL(b, 10); + d = RL(d + F2(e,a,b) + X[ 4] + unchecked((int) 0x5a827999), 6) + c; a = RL(a, 10); + c = RL(c + F2(d,e,a) + X[13] + unchecked((int) 0x5a827999), 8) + b; e = RL(e, 10); + b = RL(b + F2(c,d,e) + X[ 1] + unchecked((int) 0x5a827999), 13) + a; d = RL(d, 10); + a = RL(a + F2(b,c,d) + X[10] + unchecked((int) 0x5a827999), 11) + e; c = RL(c, 10); + e = RL(e + F2(a,b,c) + X[ 6] + unchecked((int) 0x5a827999), 9) + d; b = RL(b, 10); + d = RL(d + F2(e,a,b) + X[15] + unchecked((int) 0x5a827999), 7) + c; a = RL(a, 10); + c = RL(c + F2(d,e,a) + X[ 3] + unchecked((int) 0x5a827999), 15) + b; e = RL(e, 10); + b = RL(b + F2(c,d,e) + X[12] + unchecked((int) 0x5a827999), 7) + a; d = RL(d, 10); + a = RL(a + F2(b,c,d) + X[ 0] + unchecked((int) 0x5a827999), 12) + e; c = RL(c, 10); + e = RL(e + F2(a,b,c) + X[ 9] + unchecked((int) 0x5a827999), 15) + d; b = RL(b, 10); + d = RL(d + F2(e,a,b) + X[ 5] + unchecked((int) 0x5a827999), 9) + c; a = RL(a, 10); + c = RL(c + F2(d,e,a) + X[ 2] + unchecked((int) 0x5a827999), 11) + b; e = RL(e, 10); + b = RL(b + F2(c,d,e) + X[14] + unchecked((int) 0x5a827999), 7) + a; d = RL(d, 10); + a = RL(a + F2(b,c,d) + X[11] + unchecked((int) 0x5a827999), 13) + e; c = RL(c, 10); + e = RL(e + F2(a,b,c) + X[ 8] + unchecked((int) 0x5a827999), 12) + d; b = RL(b, 10); + + // right + ee = RL(ee + F4(aa,bb,cc) + X[ 6] + unchecked((int) 0x5c4dd124), 9) + dd; bb = RL(bb, 10); + dd = RL(dd + F4(ee,aa,bb) + X[11] + unchecked((int) 0x5c4dd124), 13) + cc; aa = RL(aa, 10); + cc = RL(cc + F4(dd,ee,aa) + X[ 3] + unchecked((int) 0x5c4dd124), 15) + bb; ee = RL(ee, 10); + bb = RL(bb + F4(cc,dd,ee) + X[ 7] + unchecked((int) 0x5c4dd124), 7) + aa; dd = RL(dd, 10); + aa = RL(aa + F4(bb,cc,dd) + X[ 0] + unchecked((int) 0x5c4dd124), 12) + ee; cc = RL(cc, 10); + ee = RL(ee + F4(aa,bb,cc) + X[13] + unchecked((int) 0x5c4dd124), 8) + dd; bb = RL(bb, 10); + dd = RL(dd + F4(ee,aa,bb) + X[ 5] + unchecked((int) 0x5c4dd124), 9) + cc; aa = RL(aa, 10); + cc = RL(cc + F4(dd,ee,aa) + X[10] + unchecked((int) 0x5c4dd124), 11) + bb; ee = RL(ee, 10); + bb = RL(bb + F4(cc,dd,ee) + X[14] + unchecked((int) 0x5c4dd124), 7) + aa; dd = RL(dd, 10); + aa = RL(aa + F4(bb,cc,dd) + X[15] + unchecked((int) 0x5c4dd124), 7) + ee; cc = RL(cc, 10); + ee = RL(ee + F4(aa,bb,cc) + X[ 8] + unchecked((int) 0x5c4dd124), 12) + dd; bb = RL(bb, 10); + dd = RL(dd + F4(ee,aa,bb) + X[12] + unchecked((int) 0x5c4dd124), 7) + cc; aa = RL(aa, 10); + cc = RL(cc + F4(dd,ee,aa) + X[ 4] + unchecked((int) 0x5c4dd124), 6) + bb; ee = RL(ee, 10); + bb = RL(bb + F4(cc,dd,ee) + X[ 9] + unchecked((int) 0x5c4dd124), 15) + aa; dd = RL(dd, 10); + aa = RL(aa + F4(bb,cc,dd) + X[ 1] + unchecked((int) 0x5c4dd124), 13) + ee; cc = RL(cc, 10); + ee = RL(ee + F4(aa,bb,cc) + X[ 2] + unchecked((int) 0x5c4dd124), 11) + dd; bb = RL(bb, 10); + + // + // Rounds 32-47 + // + // left + d = RL(d + F3(e,a,b) + X[ 3] + unchecked((int) 0x6ed9eba1), 11) + c; a = RL(a, 10); + c = RL(c + F3(d,e,a) + X[10] + unchecked((int) 0x6ed9eba1), 13) + b; e = RL(e, 10); + b = RL(b + F3(c,d,e) + X[14] + unchecked((int) 0x6ed9eba1), 6) + a; d = RL(d, 10); + a = RL(a + F3(b,c,d) + X[ 4] + unchecked((int) 0x6ed9eba1), 7) + e; c = RL(c, 10); + e = RL(e + F3(a,b,c) + X[ 9] + unchecked((int) 0x6ed9eba1), 14) + d; b = RL(b, 10); + d = RL(d + F3(e,a,b) + X[15] + unchecked((int) 0x6ed9eba1), 9) + c; a = RL(a, 10); + c = RL(c + F3(d,e,a) + X[ 8] + unchecked((int) 0x6ed9eba1), 13) + b; e = RL(e, 10); + b = RL(b + F3(c,d,e) + X[ 1] + unchecked((int) 0x6ed9eba1), 15) + a; d = RL(d, 10); + a = RL(a + F3(b,c,d) + X[ 2] + unchecked((int) 0x6ed9eba1), 14) + e; c = RL(c, 10); + e = RL(e + F3(a,b,c) + X[ 7] + unchecked((int) 0x6ed9eba1), 8) + d; b = RL(b, 10); + d = RL(d + F3(e,a,b) + X[ 0] + unchecked((int) 0x6ed9eba1), 13) + c; a = RL(a, 10); + c = RL(c + F3(d,e,a) + X[ 6] + unchecked((int) 0x6ed9eba1), 6) + b; e = RL(e, 10); + b = RL(b + F3(c,d,e) + X[13] + unchecked((int) 0x6ed9eba1), 5) + a; d = RL(d, 10); + a = RL(a + F3(b,c,d) + X[11] + unchecked((int) 0x6ed9eba1), 12) + e; c = RL(c, 10); + e = RL(e + F3(a,b,c) + X[ 5] + unchecked((int) 0x6ed9eba1), 7) + d; b = RL(b, 10); + d = RL(d + F3(e,a,b) + X[12] + unchecked((int) 0x6ed9eba1), 5) + c; a = RL(a, 10); + + // right + dd = RL(dd + F3(ee,aa,bb) + X[15] + unchecked((int) 0x6d703ef3), 9) + cc; aa = RL(aa, 10); + cc = RL(cc + F3(dd,ee,aa) + X[ 5] + unchecked((int) 0x6d703ef3), 7) + bb; ee = RL(ee, 10); + bb = RL(bb + F3(cc,dd,ee) + X[ 1] + unchecked((int) 0x6d703ef3), 15) + aa; dd = RL(dd, 10); + aa = RL(aa + F3(bb,cc,dd) + X[ 3] + unchecked((int) 0x6d703ef3), 11) + ee; cc = RL(cc, 10); + ee = RL(ee + F3(aa,bb,cc) + X[ 7] + unchecked((int) 0x6d703ef3), 8) + dd; bb = RL(bb, 10); + dd = RL(dd + F3(ee,aa,bb) + X[14] + unchecked((int) 0x6d703ef3), 6) + cc; aa = RL(aa, 10); + cc = RL(cc + F3(dd,ee,aa) + X[ 6] + unchecked((int) 0x6d703ef3), 6) + bb; ee = RL(ee, 10); + bb = RL(bb + F3(cc,dd,ee) + X[ 9] + unchecked((int) 0x6d703ef3), 14) + aa; dd = RL(dd, 10); + aa = RL(aa + F3(bb,cc,dd) + X[11] + unchecked((int) 0x6d703ef3), 12) + ee; cc = RL(cc, 10); + ee = RL(ee + F3(aa,bb,cc) + X[ 8] + unchecked((int) 0x6d703ef3), 13) + dd; bb = RL(bb, 10); + dd = RL(dd + F3(ee,aa,bb) + X[12] + unchecked((int) 0x6d703ef3), 5) + cc; aa = RL(aa, 10); + cc = RL(cc + F3(dd,ee,aa) + X[ 2] + unchecked((int) 0x6d703ef3), 14) + bb; ee = RL(ee, 10); + bb = RL(bb + F3(cc,dd,ee) + X[10] + unchecked((int) 0x6d703ef3), 13) + aa; dd = RL(dd, 10); + aa = RL(aa + F3(bb,cc,dd) + X[ 0] + unchecked((int) 0x6d703ef3), 13) + ee; cc = RL(cc, 10); + ee = RL(ee + F3(aa,bb,cc) + X[ 4] + unchecked((int) 0x6d703ef3), 7) + dd; bb = RL(bb, 10); + dd = RL(dd + F3(ee,aa,bb) + X[13] + unchecked((int) 0x6d703ef3), 5) + cc; aa = RL(aa, 10); + + // + // Rounds 48-63 + // + // left + c = RL(c + F4(d,e,a) + X[ 1] + unchecked((int) 0x8f1bbcdc), 11) + b; e = RL(e, 10); + b = RL(b + F4(c,d,e) + X[ 9] + unchecked((int) 0x8f1bbcdc), 12) + a; d = RL(d, 10); + a = RL(a + F4(b,c,d) + X[11] + unchecked((int) 0x8f1bbcdc), 14) + e; c = RL(c, 10); + e = RL(e + F4(a,b,c) + X[10] + unchecked((int) 0x8f1bbcdc), 15) + d; b = RL(b, 10); + d = RL(d + F4(e,a,b) + X[ 0] + unchecked((int) 0x8f1bbcdc), 14) + c; a = RL(a, 10); + c = RL(c + F4(d,e,a) + X[ 8] + unchecked((int) 0x8f1bbcdc), 15) + b; e = RL(e, 10); + b = RL(b + F4(c,d,e) + X[12] + unchecked((int) 0x8f1bbcdc), 9) + a; d = RL(d, 10); + a = RL(a + F4(b,c,d) + X[ 4] + unchecked((int) 0x8f1bbcdc), 8) + e; c = RL(c, 10); + e = RL(e + F4(a,b,c) + X[13] + unchecked((int) 0x8f1bbcdc), 9) + d; b = RL(b, 10); + d = RL(d + F4(e,a,b) + X[ 3] + unchecked((int) 0x8f1bbcdc), 14) + c; a = RL(a, 10); + c = RL(c + F4(d,e,a) + X[ 7] + unchecked((int) 0x8f1bbcdc), 5) + b; e = RL(e, 10); + b = RL(b + F4(c,d,e) + X[15] + unchecked((int) 0x8f1bbcdc), 6) + a; d = RL(d, 10); + a = RL(a + F4(b,c,d) + X[14] + unchecked((int) 0x8f1bbcdc), 8) + e; c = RL(c, 10); + e = RL(e + F4(a,b,c) + X[ 5] + unchecked((int) 0x8f1bbcdc), 6) + d; b = RL(b, 10); + d = RL(d + F4(e,a,b) + X[ 6] + unchecked((int) 0x8f1bbcdc), 5) + c; a = RL(a, 10); + c = RL(c + F4(d,e,a) + X[ 2] + unchecked((int) 0x8f1bbcdc), 12) + b; e = RL(e, 10); + + // right + cc = RL(cc + F2(dd,ee,aa) + X[ 8] + unchecked((int) 0x7a6d76e9), 15) + bb; ee = RL(ee, 10); + bb = RL(bb + F2(cc,dd,ee) + X[ 6] + unchecked((int) 0x7a6d76e9), 5) + aa; dd = RL(dd, 10); + aa = RL(aa + F2(bb,cc,dd) + X[ 4] + unchecked((int) 0x7a6d76e9), 8) + ee; cc = RL(cc, 10); + ee = RL(ee + F2(aa,bb,cc) + X[ 1] + unchecked((int) 0x7a6d76e9), 11) + dd; bb = RL(bb, 10); + dd = RL(dd + F2(ee,aa,bb) + X[ 3] + unchecked((int) 0x7a6d76e9), 14) + cc; aa = RL(aa, 10); + cc = RL(cc + F2(dd,ee,aa) + X[11] + unchecked((int) 0x7a6d76e9), 14) + bb; ee = RL(ee, 10); + bb = RL(bb + F2(cc,dd,ee) + X[15] + unchecked((int) 0x7a6d76e9), 6) + aa; dd = RL(dd, 10); + aa = RL(aa + F2(bb,cc,dd) + X[ 0] + unchecked((int) 0x7a6d76e9), 14) + ee; cc = RL(cc, 10); + ee = RL(ee + F2(aa,bb,cc) + X[ 5] + unchecked((int) 0x7a6d76e9), 6) + dd; bb = RL(bb, 10); + dd = RL(dd + F2(ee,aa,bb) + X[12] + unchecked((int) 0x7a6d76e9), 9) + cc; aa = RL(aa, 10); + cc = RL(cc + F2(dd,ee,aa) + X[ 2] + unchecked((int) 0x7a6d76e9), 12) + bb; ee = RL(ee, 10); + bb = RL(bb + F2(cc,dd,ee) + X[13] + unchecked((int) 0x7a6d76e9), 9) + aa; dd = RL(dd, 10); + aa = RL(aa + F2(bb,cc,dd) + X[ 9] + unchecked((int) 0x7a6d76e9), 12) + ee; cc = RL(cc, 10); + ee = RL(ee + F2(aa,bb,cc) + X[ 7] + unchecked((int) 0x7a6d76e9), 5) + dd; bb = RL(bb, 10); + dd = RL(dd + F2(ee,aa,bb) + X[10] + unchecked((int) 0x7a6d76e9), 15) + cc; aa = RL(aa, 10); + cc = RL(cc + F2(dd,ee,aa) + X[14] + unchecked((int) 0x7a6d76e9), 8) + bb; ee = RL(ee, 10); + + // + // Rounds 64-79 + // + // left + b = RL(b + F5(c,d,e) + X[ 4] + unchecked((int) 0xa953fd4e), 9) + a; d = RL(d, 10); + a = RL(a + F5(b,c,d) + X[ 0] + unchecked((int) 0xa953fd4e), 15) + e; c = RL(c, 10); + e = RL(e + F5(a,b,c) + X[ 5] + unchecked((int) 0xa953fd4e), 5) + d; b = RL(b, 10); + d = RL(d + F5(e,a,b) + X[ 9] + unchecked((int) 0xa953fd4e), 11) + c; a = RL(a, 10); + c = RL(c + F5(d,e,a) + X[ 7] + unchecked((int) 0xa953fd4e), 6) + b; e = RL(e, 10); + b = RL(b + F5(c,d,e) + X[12] + unchecked((int) 0xa953fd4e), 8) + a; d = RL(d, 10); + a = RL(a + F5(b,c,d) + X[ 2] + unchecked((int) 0xa953fd4e), 13) + e; c = RL(c, 10); + e = RL(e + F5(a,b,c) + X[10] + unchecked((int) 0xa953fd4e), 12) + d; b = RL(b, 10); + d = RL(d + F5(e,a,b) + X[14] + unchecked((int) 0xa953fd4e), 5) + c; a = RL(a, 10); + c = RL(c + F5(d,e,a) + X[ 1] + unchecked((int) 0xa953fd4e), 12) + b; e = RL(e, 10); + b = RL(b + F5(c,d,e) + X[ 3] + unchecked((int) 0xa953fd4e), 13) + a; d = RL(d, 10); + a = RL(a + F5(b,c,d) + X[ 8] + unchecked((int) 0xa953fd4e), 14) + e; c = RL(c, 10); + e = RL(e + F5(a,b,c) + X[11] + unchecked((int) 0xa953fd4e), 11) + d; b = RL(b, 10); + d = RL(d + F5(e,a,b) + X[ 6] + unchecked((int) 0xa953fd4e), 8) + c; a = RL(a, 10); + c = RL(c + F5(d,e,a) + X[15] + unchecked((int) 0xa953fd4e), 5) + b; e = RL(e, 10); + b = RL(b + F5(c,d,e) + X[13] + unchecked((int) 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; + } + } + + public override IMemoable Copy() + { + return new RipeMD160Digest(this); + } + + public override void Reset(IMemoable other) + { + RipeMD160Digest d = (RipeMD160Digest)other; + + CopyIn(d); + } + + } + +} diff --git a/bc-sharp-crypto/src/crypto/digests/RipeMD256Digest.cs b/bc-sharp-crypto/src/crypto/digests/RipeMD256Digest.cs new file mode 100644 index 0000000..3062757 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/digests/RipeMD256Digest.cs @@ -0,0 +1,430 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Digests +{ + /// + ///

Implementation of RipeMD256.

+ ///

Note: this algorithm offers the same level of security as RipeMD128.

+ ///
+ public class RipeMD256Digest + : GeneralDigest + { + public override string AlgorithmName + { + get { return "RIPEMD256"; } + } + + public override int GetDigestSize() + { + return DigestLength; + } + + private const int DigestLength = 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):base(t) + { + CopyIn(t); + } + + private void CopyIn(RipeMD256Digest t) + { + base.CopyIn(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; + + Array.Copy(t.X, 0, X, 0, t.X.Length); + xOff = t.xOff; + } + + internal override void ProcessWord( + byte[] input, + int inOff) + { + X[xOff++] = (input[inOff] & 0xff) | ((input[inOff + 1] & 0xff) << 8) + | ((input[inOff + 2] & 0xff) << 16) | ((input[inOff + 3] & 0xff) << 24); + + if (xOff == 16) + { + ProcessBlock(); + } + } + + internal override void ProcessLength( + long bitLength) + { + if (xOff > 14) + { + ProcessBlock(); + } + + X[14] = (int)(bitLength & 0xffffffff); + X[15] = (int)((ulong)bitLength >> 32); + } + + private void UnpackWord( + int word, + byte[] outBytes, + int outOff) + { + outBytes[outOff] = (byte)(uint)word; + outBytes[outOff + 1] = (byte)((uint)word >> 8); + outBytes[outOff + 2] = (byte)((uint)word >> 16); + outBytes[outOff + 3] = (byte)((uint)word >> 24); + } + + public override int DoFinal(byte[] output, int outOff) + { + Finish(); + + UnpackWord(H0, output, outOff); + UnpackWord(H1, output, outOff + 4); + UnpackWord(H2, output, outOff + 8); + UnpackWord(H3, output, outOff + 12); + UnpackWord(H4, output, outOff + 16); + UnpackWord(H5, output, outOff + 20); + UnpackWord(H6, output, outOff + 24); + UnpackWord(H7, output, outOff + 28); + + Reset(); + + return DigestLength; + } + + /// reset the chaining variables to the IV values. + public override void Reset() + { + base.Reset(); + + H0 = unchecked((int)0x67452301); + H1 = unchecked((int)0xefcdab89); + H2 = unchecked((int)0x98badcfe); + H3 = unchecked((int)0x10325476); + H4 = unchecked((int)0x76543210); + H5 = unchecked((int)0xFEDCBA98); + H6 = unchecked((int)0x89ABCDEF); + H7 = unchecked((int)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) | (int)((uint)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 + unchecked((int)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 + unchecked((int)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 + unchecked((int)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 + unchecked((int)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 + unchecked((int)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 + unchecked((int)0x50a28be6), s); + } + + internal override 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; + } + } + + public override IMemoable Copy() + { + return new RipeMD256Digest(this); + } + + public override void Reset(IMemoable other) + { + RipeMD256Digest d = (RipeMD256Digest)other; + + CopyIn(d); + } + + } +} diff --git a/bc-sharp-crypto/src/crypto/digests/RipeMD320Digest.cs b/bc-sharp-crypto/src/crypto/digests/RipeMD320Digest.cs new file mode 100644 index 0000000..767d74d --- /dev/null +++ b/bc-sharp-crypto/src/crypto/digests/RipeMD320Digest.cs @@ -0,0 +1,459 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Digests +{ + /// + ///

Implementation of RipeMD 320.

+ ///

Note: this algorithm offers the same level of security as RipeMD160.

+ ///
+ public class RipeMD320Digest + : GeneralDigest + { + public override string AlgorithmName + { + get { return "RIPEMD320"; } + } + + public override int GetDigestSize() + { + return DigestLength; + } + + private const int DigestLength = 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) + : base(t) + { + CopyIn(t); + } + + private void CopyIn(RipeMD320Digest t) + { + base.CopyIn(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; + + Array.Copy(t.X, 0, X, 0, t.X.Length); + xOff = t.xOff; + } + + internal override void ProcessWord( + byte[] input, + int inOff) + { + X[xOff++] = (input[inOff] & 0xff) | ((input[inOff + 1] & 0xff) << 8) + | ((input[inOff + 2] & 0xff) << 16) | ((input[inOff + 3] & 0xff) << 24); + + if (xOff == 16) + { + ProcessBlock(); + } + } + + internal override void ProcessLength( + long bitLength) + { + if (xOff > 14) + { + ProcessBlock(); + } + + X[14] = (int)(bitLength & 0xffffffff); + X[15] = (int)((ulong)bitLength >> 32); + } + + private void UnpackWord( + int word, + byte[] outBytes, + int outOff) + { + outBytes[outOff] = (byte)word; + outBytes[outOff + 1] = (byte)((uint)word >> 8); + outBytes[outOff + 2] = (byte)((uint)word >> 16); + outBytes[outOff + 3] = (byte)((uint)word >> 24); + } + + public override int DoFinal(byte[] output, int outOff) + { + Finish(); + + UnpackWord(H0, output, outOff); + UnpackWord(H1, output, outOff + 4); + UnpackWord(H2, output, outOff + 8); + UnpackWord(H3, output, outOff + 12); + UnpackWord(H4, output, outOff + 16); + UnpackWord(H5, output, outOff + 20); + UnpackWord(H6, output, outOff + 24); + UnpackWord(H7, output, outOff + 28); + UnpackWord(H8, output, outOff + 32); + UnpackWord(H9, output, outOff + 36); + + Reset(); + + return DigestLength; + } + + /// reset the chaining variables to the IV values. + public override void Reset() + { + base.Reset(); + + H0 = unchecked((int) 0x67452301); + H1 = unchecked((int) 0xefcdab89); + H2 = unchecked((int) 0x98badcfe); + H3 = unchecked((int) 0x10325476); + H4 = unchecked((int) 0xc3d2e1f0); + H5 = unchecked((int) 0x76543210); + H6 = unchecked((int) 0xFEDCBA98); + H7 = unchecked((int) 0x89ABCDEF); + H8 = unchecked((int) 0x01234567); + H9 = unchecked((int) 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) | (int)(((uint)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); + } + + internal override 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] + unchecked((int)0x50a28be6), 8) + ee; cc = RL(cc, 10); + ee = RL(ee + F5(aa, bb, cc) + X[14] + unchecked((int)0x50a28be6), 9) + dd; bb = RL(bb, 10); + dd = RL(dd + F5(ee, aa, bb) + X[7] + unchecked((int)0x50a28be6), 9) + cc; aa = RL(aa, 10); + cc = RL(cc + F5(dd, ee, aa) + X[0] + unchecked((int)0x50a28be6), 11) + bb; ee = RL(ee, 10); + bb = RL(bb + F5(cc, dd, ee) + X[9] + unchecked((int)0x50a28be6), 13) + aa; dd = RL(dd, 10); + aa = RL(aa + F5(bb, cc, dd) + X[2] + unchecked((int)0x50a28be6), 15) + ee; cc = RL(cc, 10); + ee = RL(ee + F5(aa, bb, cc) + X[11] + unchecked((int)0x50a28be6), 15) + dd; bb = RL(bb, 10); + dd = RL(dd + F5(ee, aa, bb) + X[4] + unchecked((int)0x50a28be6), 5) + cc; aa = RL(aa, 10); + cc = RL(cc + F5(dd, ee, aa) + X[13] + unchecked((int)0x50a28be6), 7) + bb; ee = RL(ee, 10); + bb = RL(bb + F5(cc, dd, ee) + X[6] + unchecked((int)0x50a28be6), 7) + aa; dd = RL(dd, 10); + aa = RL(aa + F5(bb, cc, dd) + X[15] + unchecked((int)0x50a28be6), 8) + ee; cc = RL(cc, 10); + ee = RL(ee + F5(aa, bb, cc) + X[8] + unchecked((int)0x50a28be6), 11) + dd; bb = RL(bb, 10); + dd = RL(dd + F5(ee, aa, bb) + X[1] + unchecked((int)0x50a28be6), 14) + cc; aa = RL(aa, 10); + cc = RL(cc + F5(dd, ee, aa) + X[10] + unchecked((int)0x50a28be6), 14) + bb; ee = RL(ee, 10); + bb = RL(bb + F5(cc, dd, ee) + X[3] + unchecked((int)0x50a28be6), 12) + aa; dd = RL(dd, 10); + aa = RL(aa + F5(bb, cc, dd) + X[12] + unchecked((int)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] + unchecked((int)0x5a827999), 7) + d; b = RL(b, 10); + d = RL(d + F2(e, a, b) + X[4] + unchecked((int)0x5a827999), 6) + c; a = RL(a, 10); + c = RL(c + F2(d, e, a) + X[13] + unchecked((int)0x5a827999), 8) + b; e = RL(e, 10); + b = RL(b + F2(c, d, e) + X[1] + unchecked((int)0x5a827999), 13) + a; d = RL(d, 10); + a = RL(a + F2(b, c, d) + X[10] + unchecked((int)0x5a827999), 11) + e; c = RL(c, 10); + e = RL(e + F2(a, b, c) + X[6] + unchecked((int)0x5a827999), 9) + d; b = RL(b, 10); + d = RL(d + F2(e, a, b) + X[15] + unchecked((int)0x5a827999), 7) + c; a = RL(a, 10); + c = RL(c + F2(d, e, a) + X[3] + unchecked((int)0x5a827999), 15) + b; e = RL(e, 10); + b = RL(b + F2(c, d, e) + X[12] + unchecked((int)0x5a827999), 7) + a; d = RL(d, 10); + a = RL(a + F2(b, c, d) + X[0] + unchecked((int)0x5a827999), 12) + e; c = RL(c, 10); + e = RL(e + F2(a, b, c) + X[9] + unchecked((int)0x5a827999), 15) + d; b = RL(b, 10); + d = RL(d + F2(e, a, b) + X[5] + unchecked((int)0x5a827999), 9) + c; a = RL(a, 10); + c = RL(c + F2(d, e, a) + X[2] + unchecked((int)0x5a827999), 11) + b; e = RL(e, 10); + b = RL(b + F2(c, d, e) + X[14] + unchecked((int)0x5a827999), 7) + a; d = RL(d, 10); + a = RL(a + F2(b, c, d) + X[11] + unchecked((int)0x5a827999), 13) + e; c = RL(c, 10); + e = RL(e + F2(a, b, c) + X[8] + unchecked((int)0x5a827999), 12) + d; b = RL(b, 10); + + // right + ee = RL(ee + F4(aa, bb, cc) + X[6] + unchecked((int)0x5c4dd124), 9) + dd; bb = RL(bb, 10); + dd = RL(dd + F4(ee, aa, bb) + X[11] + unchecked((int)0x5c4dd124), 13) + cc; aa = RL(aa, 10); + cc = RL(cc + F4(dd, ee, aa) + X[3] + unchecked((int)0x5c4dd124), 15) + bb; ee = RL(ee, 10); + bb = RL(bb + F4(cc, dd, ee) + X[7] + unchecked((int)0x5c4dd124), 7) + aa; dd = RL(dd, 10); + aa = RL(aa + F4(bb, cc, dd) + X[0] + unchecked((int)0x5c4dd124), 12) + ee; cc = RL(cc, 10); + ee = RL(ee + F4(aa, bb, cc) + X[13] + unchecked((int)0x5c4dd124), 8) + dd; bb = RL(bb, 10); + dd = RL(dd + F4(ee, aa, bb) + X[5] + unchecked((int)0x5c4dd124), 9) + cc; aa = RL(aa, 10); + cc = RL(cc + F4(dd, ee, aa) + X[10] + unchecked((int)0x5c4dd124), 11) + bb; ee = RL(ee, 10); + bb = RL(bb + F4(cc, dd, ee) + X[14] + unchecked((int)0x5c4dd124), 7) + aa; dd = RL(dd, 10); + aa = RL(aa + F4(bb, cc, dd) + X[15] + unchecked((int)0x5c4dd124), 7) + ee; cc = RL(cc, 10); + ee = RL(ee + F4(aa, bb, cc) + X[8] + unchecked((int)0x5c4dd124), 12) + dd; bb = RL(bb, 10); + dd = RL(dd + F4(ee, aa, bb) + X[12] + unchecked((int)0x5c4dd124), 7) + cc; aa = RL(aa, 10); + cc = RL(cc + F4(dd, ee, aa) + X[4] + unchecked((int)0x5c4dd124), 6) + bb; ee = RL(ee, 10); + bb = RL(bb + F4(cc, dd, ee) + X[9] + unchecked((int)0x5c4dd124), 15) + aa; dd = RL(dd, 10); + aa = RL(aa + F4(bb, cc, dd) + X[1] + unchecked((int)0x5c4dd124), 13) + ee; cc = RL(cc, 10); + ee = RL(ee + F4(aa, bb, cc) + X[2] + unchecked((int)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] + unchecked((int)0x6ed9eba1), 11) + c; a = RL(a, 10); + c = RL(c + F3(d, e, a) + X[10] + unchecked((int)0x6ed9eba1), 13) + b; e = RL(e, 10); + b = RL(b + F3(c, d, e) + X[14] + unchecked((int)0x6ed9eba1), 6) + a; d = RL(d, 10); + a = RL(a + F3(b, c, d) + X[4] + unchecked((int)0x6ed9eba1), 7) + e; c = RL(c, 10); + e = RL(e + F3(a, b, c) + X[9] + unchecked((int)0x6ed9eba1), 14) + d; b = RL(b, 10); + d = RL(d + F3(e, a, b) + X[15] + unchecked((int)0x6ed9eba1), 9) + c; a = RL(a, 10); + c = RL(c + F3(d, e, a) + X[8] + unchecked((int)0x6ed9eba1), 13) + b; e = RL(e, 10); + b = RL(b + F3(c, d, e) + X[1] + unchecked((int)0x6ed9eba1), 15) + a; d = RL(d, 10); + a = RL(a + F3(b, c, d) + X[2] + unchecked((int)0x6ed9eba1), 14) + e; c = RL(c, 10); + e = RL(e + F3(a, b, c) + X[7] + unchecked((int)0x6ed9eba1), 8) + d; b = RL(b, 10); + d = RL(d + F3(e, a, b) + X[0] + unchecked((int)0x6ed9eba1), 13) + c; a = RL(a, 10); + c = RL(c + F3(d, e, a) + X[6] + unchecked((int)0x6ed9eba1), 6) + b; e = RL(e, 10); + b = RL(b + F3(c, d, e) + X[13] + unchecked((int)0x6ed9eba1), 5) + a; d = RL(d, 10); + a = RL(a + F3(b, c, d) + X[11] + unchecked((int)0x6ed9eba1), 12) + e; c = RL(c, 10); + e = RL(e + F3(a, b, c) + X[5] + unchecked((int)0x6ed9eba1), 7) + d; b = RL(b, 10); + d = RL(d + F3(e, a, b) + X[12] + unchecked((int)0x6ed9eba1), 5) + c; a = RL(a, 10); + + // right + dd = RL(dd + F3(ee, aa, bb) + X[15] + unchecked((int)0x6d703ef3), 9) + cc; aa = RL(aa, 10); + cc = RL(cc + F3(dd, ee, aa) + X[5] + unchecked((int)0x6d703ef3), 7) + bb; ee = RL(ee, 10); + bb = RL(bb + F3(cc, dd, ee) + X[1] + unchecked((int)0x6d703ef3), 15) + aa; dd = RL(dd, 10); + aa = RL(aa + F3(bb, cc, dd) + X[3] + unchecked((int)0x6d703ef3), 11) + ee; cc = RL(cc, 10); + ee = RL(ee + F3(aa, bb, cc) + X[7] + unchecked((int)0x6d703ef3), 8) + dd; bb = RL(bb, 10); + dd = RL(dd + F3(ee, aa, bb) + X[14] + unchecked((int)0x6d703ef3), 6) + cc; aa = RL(aa, 10); + cc = RL(cc + F3(dd, ee, aa) + X[6] + unchecked((int)0x6d703ef3), 6) + bb; ee = RL(ee, 10); + bb = RL(bb + F3(cc, dd, ee) + X[9] + unchecked((int)0x6d703ef3), 14) + aa; dd = RL(dd, 10); + aa = RL(aa + F3(bb, cc, dd) + X[11] + unchecked((int)0x6d703ef3), 12) + ee; cc = RL(cc, 10); + ee = RL(ee + F3(aa, bb, cc) + X[8] + unchecked((int)0x6d703ef3), 13) + dd; bb = RL(bb, 10); + dd = RL(dd + F3(ee, aa, bb) + X[12] + unchecked((int)0x6d703ef3), 5) + cc; aa = RL(aa, 10); + cc = RL(cc + F3(dd, ee, aa) + X[2] + unchecked((int)0x6d703ef3), 14) + bb; ee = RL(ee, 10); + bb = RL(bb + F3(cc, dd, ee) + X[10] + unchecked((int)0x6d703ef3), 13) + aa; dd = RL(dd, 10); + aa = RL(aa + F3(bb, cc, dd) + X[0] + unchecked((int)0x6d703ef3), 13) + ee; cc = RL(cc, 10); + ee = RL(ee + F3(aa, bb, cc) + X[4] + unchecked((int)0x6d703ef3), 7) + dd; bb = RL(bb, 10); + dd = RL(dd + F3(ee, aa, bb) + X[13] + unchecked((int)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] + unchecked((int)0x8f1bbcdc), 11) + b; e = RL(e, 10); + b = RL(b + F4(c, d, e) + X[9] + unchecked((int)0x8f1bbcdc), 12) + a; d = RL(d, 10); + a = RL(a + F4(b, c, d) + X[11] + unchecked((int)0x8f1bbcdc), 14) + e; c = RL(c, 10); + e = RL(e + F4(a, b, c) + X[10] + unchecked((int)0x8f1bbcdc), 15) + d; b = RL(b, 10); + d = RL(d + F4(e, a, b) + X[0] + unchecked((int)0x8f1bbcdc), 14) + c; a = RL(a, 10); + c = RL(c + F4(d, e, a) + X[8] + unchecked((int)0x8f1bbcdc), 15) + b; e = RL(e, 10); + b = RL(b + F4(c, d, e) + X[12] + unchecked((int)0x8f1bbcdc), 9) + a; d = RL(d, 10); + a = RL(a + F4(b, c, d) + X[4] + unchecked((int)0x8f1bbcdc), 8) + e; c = RL(c, 10); + e = RL(e + F4(a, b, c) + X[13] + unchecked((int)0x8f1bbcdc), 9) + d; b = RL(b, 10); + d = RL(d + F4(e, a, b) + X[3] + unchecked((int)0x8f1bbcdc), 14) + c; a = RL(a, 10); + c = RL(c + F4(d, e, a) + X[7] + unchecked((int)0x8f1bbcdc), 5) + b; e = RL(e, 10); + b = RL(b + F4(c, d, e) + X[15] + unchecked((int)0x8f1bbcdc), 6) + a; d = RL(d, 10); + a = RL(a + F4(b, c, d) + X[14] + unchecked((int)0x8f1bbcdc), 8) + e; c = RL(c, 10); + e = RL(e + F4(a, b, c) + X[5] + unchecked((int)0x8f1bbcdc), 6) + d; b = RL(b, 10); + d = RL(d + F4(e, a, b) + X[6] + unchecked((int)0x8f1bbcdc), 5) + c; a = RL(a, 10); + c = RL(c + F4(d, e, a) + X[2] + unchecked((int)0x8f1bbcdc), 12) + b; e = RL(e, 10); + + // right + cc = RL(cc + F2(dd, ee, aa) + X[8] + unchecked((int)0x7a6d76e9), 15) + bb; ee = RL(ee, 10); + bb = RL(bb + F2(cc, dd, ee) + X[6] + unchecked((int)0x7a6d76e9), 5) + aa; dd = RL(dd, 10); + aa = RL(aa + F2(bb, cc, dd) + X[4] + unchecked((int)0x7a6d76e9), 8) + ee; cc = RL(cc, 10); + ee = RL(ee + F2(aa, bb, cc) + X[1] + unchecked((int)0x7a6d76e9), 11) + dd; bb = RL(bb, 10); + dd = RL(dd + F2(ee, aa, bb) + X[3] + unchecked((int)0x7a6d76e9), 14) + cc; aa = RL(aa, 10); + cc = RL(cc + F2(dd, ee, aa) + X[11] + unchecked((int)0x7a6d76e9), 14) + bb; ee = RL(ee, 10); + bb = RL(bb + F2(cc, dd, ee) + X[15] + unchecked((int)0x7a6d76e9), 6) + aa; dd = RL(dd, 10); + aa = RL(aa + F2(bb, cc, dd) + X[0] + unchecked((int)0x7a6d76e9), 14) + ee; cc = RL(cc, 10); + ee = RL(ee + F2(aa, bb, cc) + X[5] + unchecked((int)0x7a6d76e9), 6) + dd; bb = RL(bb, 10); + dd = RL(dd + F2(ee, aa, bb) + X[12] + unchecked((int)0x7a6d76e9), 9) + cc; aa = RL(aa, 10); + cc = RL(cc + F2(dd, ee, aa) + X[2] + unchecked((int)0x7a6d76e9), 12) + bb; ee = RL(ee, 10); + bb = RL(bb + F2(cc, dd, ee) + X[13] + unchecked((int)0x7a6d76e9), 9) + aa; dd = RL(dd, 10); + aa = RL(aa + F2(bb, cc, dd) + X[9] + unchecked((int)0x7a6d76e9), 12) + ee; cc = RL(cc, 10); + ee = RL(ee + F2(aa, bb, cc) + X[7] + unchecked((int)0x7a6d76e9), 5) + dd; bb = RL(bb, 10); + dd = RL(dd + F2(ee, aa, bb) + X[10] + unchecked((int)0x7a6d76e9), 15) + cc; aa = RL(aa, 10); + cc = RL(cc + F2(dd, ee, aa) + X[14] + unchecked((int)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] + unchecked((int)0xa953fd4e), 9) + a; d = RL(d, 10); + a = RL(a + F5(b, c, d) + X[0] + unchecked((int)0xa953fd4e), 15) + e; c = RL(c, 10); + e = RL(e + F5(a, b, c) + X[5] + unchecked((int)0xa953fd4e), 5) + d; b = RL(b, 10); + d = RL(d + F5(e, a, b) + X[9] + unchecked((int)0xa953fd4e), 11) + c; a = RL(a, 10); + c = RL(c + F5(d, e, a) + X[7] + unchecked((int)0xa953fd4e), 6) + b; e = RL(e, 10); + b = RL(b + F5(c, d, e) + X[12] + unchecked((int)0xa953fd4e), 8) + a; d = RL(d, 10); + a = RL(a + F5(b, c, d) + X[2] + unchecked((int)0xa953fd4e), 13) + e; c = RL(c, 10); + e = RL(e + F5(a, b, c) + X[10] + unchecked((int)0xa953fd4e), 12) + d; b = RL(b, 10); + d = RL(d + F5(e, a, b) + X[14] + unchecked((int)0xa953fd4e), 5) + c; a = RL(a, 10); + c = RL(c + F5(d, e, a) + X[1] + unchecked((int)0xa953fd4e), 12) + b; e = RL(e, 10); + b = RL(b + F5(c, d, e) + X[3] + unchecked((int)0xa953fd4e), 13) + a; d = RL(d, 10); + a = RL(a + F5(b, c, d) + X[8] + unchecked((int)0xa953fd4e), 14) + e; c = RL(c, 10); + e = RL(e + F5(a, b, c) + X[11] + unchecked((int)0xa953fd4e), 11) + d; b = RL(b, 10); + d = RL(d + F5(e, a, b) + X[6] + unchecked((int)0xa953fd4e), 8) + c; a = RL(a, 10); + c = RL(c + F5(d, e, a) + X[15] + unchecked((int)0xa953fd4e), 5) + b; e = RL(e, 10); + b = RL(b + F5(c, d, e) + X[13] + unchecked((int)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; + } + } + + public override IMemoable Copy() + { + return new RipeMD320Digest(this); + } + + public override void Reset(IMemoable other) + { + RipeMD320Digest d = (RipeMD320Digest)other; + + CopyIn(d); + } + + } +} diff --git a/bc-sharp-crypto/src/crypto/digests/SHA3Digest.cs b/bc-sharp-crypto/src/crypto/digests/SHA3Digest.cs new file mode 100644 index 0000000..4683af5 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/digests/SHA3Digest.cs @@ -0,0 +1,85 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Digests +{ + /// + /// Implementation of SHA-3 based on following KeccakNISTInterface.c from http://keccak.noekeon.org/ + /// + /// + /// Following the naming conventions used in the C source code to enable easy review of the implementation. + /// + public class Sha3Digest + : KeccakDigest + { + private static int CheckBitLength(int bitLength) + { + switch (bitLength) + { + case 224: + case 256: + case 384: + case 512: + return bitLength; + default: + throw new ArgumentException(bitLength + " not supported for SHA-3", "bitLength"); + } + } + + public Sha3Digest() + : this(256) + { + } + + public Sha3Digest(int bitLength) + : base(CheckBitLength(bitLength)) + { + } + + public Sha3Digest(Sha3Digest source) + : base(source) + { + } + + public override string AlgorithmName + { + get { return "SHA3-" + fixedOutputLength; } + } + + public override int DoFinal(byte[] output, int outOff) + { + AbsorbBits(0x02, 2); + + return base.DoFinal(output, outOff); + } + + /* + * TODO Possible API change to support partial-byte suffixes. + */ + protected override int DoFinal(byte[] output, int outOff, byte partialByte, int partialBits) + { + if (partialBits < 0 || partialBits > 7) + throw new ArgumentException("must be in the range [0,7]", "partialBits"); + + int finalInput = (partialByte & ((1 << partialBits) - 1)) | (0x02 << partialBits); + Debug.Assert(finalInput >= 0); + int finalBits = partialBits + 2; + + if (finalBits >= 8) + { + Absorb(new byte[]{ (byte)finalInput }, 0, 1); + finalBits -= 8; + finalInput >>= 8; + } + + return base.DoFinal(output, outOff, (byte)finalInput, finalBits); + } + + public override IMemoable Copy() + { + return new Sha3Digest(this); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/digests/SM3Digest.cs b/bc-sharp-crypto/src/crypto/digests/SM3Digest.cs new file mode 100644 index 0000000..d81b2dd --- /dev/null +++ b/bc-sharp-crypto/src/crypto/digests/SM3Digest.cs @@ -0,0 +1,328 @@ +using System; + +using Org.BouncyCastle.Crypto.Utilities; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Digests +{ + + /// + /// Implementation of Chinese SM3 digest as described at + /// http://tools.ietf.org/html/draft-shen-sm3-hash-00 + /// and at .... ( Chinese PDF ) + /// + /// + /// The specification says "process a bit stream", + /// but this is written to process bytes in blocks of 4, + /// meaning this will process 32-bit word groups. + /// But so do also most other digest specifications, + /// including the SHA-256 which was a origin for + /// this specification. + /// + public class SM3Digest + : GeneralDigest + { + private const int DIGEST_LENGTH = 32; // bytes + private const int BLOCK_SIZE = 64 / 4; // of 32 bit ints (16 ints) + + private uint[] V = new uint[DIGEST_LENGTH / 4]; // in 32 bit ints (8 ints) + private uint[] inwords = new uint[BLOCK_SIZE]; + private int xOff; + + // Work-bufs used within processBlock() + private uint[] W = new uint[68]; + private uint[] W1 = new uint[64]; + + // Round constant T for processBlock() which is 32 bit integer rolled left up to (63 MOD 32) bit positions. + private static readonly uint[] T = new uint[64]; + + static SM3Digest() + { + for (int i = 0; i < 16; ++i) + { + uint t = 0x79CC4519; + T[i] = (t << i) | (t >> (32 - i)); + } + for (int i = 16; i < 64; ++i) + { + int n = i % 32; + uint t = 0x7A879D8A; + T[i] = (t << n) | (t >> (32 - n)); + } + } + + + /// + /// Standard constructor + /// + public SM3Digest() + { + Reset(); + } + + /// + /// Copy constructor. This will copy the state of the provided + /// message digest. + /// + public SM3Digest(SM3Digest t) + : base(t) + { + CopyIn(t); + } + + private void CopyIn(SM3Digest t) + { + Array.Copy(t.V, 0, this.V, 0, this.V.Length); + Array.Copy(t.inwords, 0, this.inwords, 0, this.inwords.Length); + xOff = t.xOff; + } + + public override string AlgorithmName + { + get { return "SM3"; } + } + + public override int GetDigestSize() + { + return DIGEST_LENGTH; + } + + public override IMemoable Copy() + { + return new SM3Digest(this); + } + + public override void Reset(IMemoable other) + { + SM3Digest d = (SM3Digest)other; + + base.CopyIn(d); + CopyIn(d); + } + + /// + /// reset the chaining variables + /// + public override void Reset() + { + base.Reset(); + + this.V[0] = 0x7380166F; + this.V[1] = 0x4914B2B9; + this.V[2] = 0x172442D7; + this.V[3] = 0xDA8A0600; + this.V[4] = 0xA96F30BC; + this.V[5] = 0x163138AA; + this.V[6] = 0xE38DEE4D; + this.V[7] = 0xB0FB0E4E; + + this.xOff = 0; + } + + + public override int DoFinal(byte[] output, int outOff) + { + Finish(); + + Pack.UInt32_To_BE(this.V[0], output, outOff + 0); + Pack.UInt32_To_BE(this.V[1], output, outOff + 4); + Pack.UInt32_To_BE(this.V[2], output, outOff + 8); + Pack.UInt32_To_BE(this.V[3], output, outOff + 12); + Pack.UInt32_To_BE(this.V[4], output, outOff + 16); + Pack.UInt32_To_BE(this.V[5], output, outOff + 20); + Pack.UInt32_To_BE(this.V[6], output, outOff + 24); + Pack.UInt32_To_BE(this.V[7], output, outOff + 28); + + Reset(); + + return DIGEST_LENGTH; + } + + + internal override void ProcessWord(byte[] input, + int inOff) + { + uint n = Pack.BE_To_UInt32(input, inOff); + this.inwords[this.xOff] = n; + ++this.xOff; + + if (this.xOff >= 16) + { + ProcessBlock(); + } + } + + internal override void ProcessLength(long bitLength) + { + if (this.xOff > (BLOCK_SIZE - 2)) + { + // xOff == 15 --> can't fit the 64 bit length field at tail.. + this.inwords[this.xOff] = 0; // fill with zero + ++this.xOff; + + ProcessBlock(); + } + // Fill with zero words, until reach 2nd to last slot + while (this.xOff < (BLOCK_SIZE - 2)) + { + this.inwords[this.xOff] = 0; + ++this.xOff; + } + + // Store input data length in BITS + this.inwords[this.xOff++] = (uint)(bitLength >> 32); + this.inwords[this.xOff++] = (uint)(bitLength); + } + + /* + + 3.4.2. Constants + + + Tj = 79cc4519 when 0 < = j < = 15 + Tj = 7a879d8a when 16 < = j < = 63 + + 3.4.3. Boolean function + + + FFj(X;Y;Z) = X XOR Y XOR Z when 0 < = j < = 15 + = (X AND Y) OR (X AND Z) OR (Y AND Z) when 16 < = j < = 63 + + GGj(X;Y;Z) = X XOR Y XOR Z when 0 < = j < = 15 + = (X AND Y) OR (NOT X AND Z) when 16 < = j < = 63 + + The X, Y, Z in the fomular are words!GBP + + 3.4.4. Permutation function + + + P0(X) = X XOR (X <<< 9) XOR (X <<< 17) ## ROLL, not SHIFT + P1(X) = X XOR (X <<< 15) XOR (X <<< 23) ## ROLL, not SHIFT + + The X in the fomular are a word. + + ---------- + + Each ROLL converted to Java expression: + + ROLL 9 : ((x << 9) | (x >> (32-9)))) + ROLL 17 : ((x << 17) | (x >> (32-17))) + ROLL 15 : ((x << 15) | (x >> (32-15))) + ROLL 23 : ((x << 23) | (x >> (32-23))) + + */ + + private uint P0(uint x) + { + uint r9 = ((x << 9) | (x >> (32 - 9))); + uint r17 = ((x << 17) | (x >> (32 - 17))); + return (x ^ r9 ^ r17); + } + + private uint P1(uint x) + { + uint r15 = ((x << 15) | (x >> (32 - 15))); + uint r23 = ((x << 23) | (x >> (32 - 23))); + return (x ^ r15 ^ r23); + } + + private uint FF0(uint x, uint y, uint z) + { + return (x ^ y ^ z); + } + + private uint FF1(uint x, uint y, uint z) + { + return ((x & y) | (x & z) | (y & z)); + } + + private uint GG0(uint x, uint y, uint z) + { + return (x ^ y ^ z); + } + + private uint GG1(uint x, uint y, uint z) + { + return ((x & y) | ((~x) & z)); + } + + + internal override void ProcessBlock() + { + for (int j = 0; j < 16; ++j) + { + this.W[j] = this.inwords[j]; + } + for (int j = 16; j < 68; ++j) + { + uint wj3 = this.W[j - 3]; + uint r15 = ((wj3 << 15) | (wj3 >> (32 - 15))); + uint wj13 = this.W[j - 13]; + uint r7 = ((wj13 << 7) | (wj13 >> (32 - 7))); + this.W[j] = P1(this.W[j - 16] ^ this.W[j - 9] ^ r15) ^ r7 ^ this.W[j - 6]; + } + for (int j = 0; j < 64; ++j) + { + this.W1[j] = this.W[j] ^ this.W[j + 4]; + } + + uint A = this.V[0]; + uint B = this.V[1]; + uint C = this.V[2]; + uint D = this.V[3]; + uint E = this.V[4]; + uint F = this.V[5]; + uint G = this.V[6]; + uint H = this.V[7]; + + + for (int j = 0; j < 16; ++j) + { + uint a12 = ((A << 12) | (A >> (32 - 12))); + uint s1_ = a12 + E + T[j]; + uint SS1 = ((s1_ << 7) | (s1_ >> (32 - 7))); + uint SS2 = SS1 ^ a12; + uint TT1 = FF0(A, B, C) + D + SS2 + this.W1[j]; + uint TT2 = GG0(E, F, G) + H + SS1 + this.W[j]; + D = C; + C = ((B << 9) | (B >> (32 - 9))); + B = A; + A = TT1; + H = G; + G = ((F << 19) | (F >> (32 - 19))); + F = E; + E = P0(TT2); + } + + // Different FF,GG functions on rounds 16..63 + for (int j = 16; j < 64; ++j) + { + uint a12 = ((A << 12) | (A >> (32 - 12))); + uint s1_ = a12 + E + T[j]; + uint SS1 = ((s1_ << 7) | (s1_ >> (32 - 7))); + uint SS2 = SS1 ^ a12; + uint TT1 = FF1(A, B, C) + D + SS2 + this.W1[j]; + uint TT2 = GG1(E, F, G) + H + SS1 + this.W[j]; + D = C; + C = ((B << 9) | (B >> (32 - 9))); + B = A; + A = TT1; + H = G; + G = ((F << 19) | (F >> (32 - 19))); + F = E; + E = P0(TT2); + } + + this.V[0] ^= A; + this.V[1] ^= B; + this.V[2] ^= C; + this.V[3] ^= D; + this.V[4] ^= E; + this.V[5] ^= F; + this.V[6] ^= G; + this.V[7] ^= H; + + this.xOff = 0; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/digests/Sha1Digest.cs b/bc-sharp-crypto/src/crypto/digests/Sha1Digest.cs new file mode 100644 index 0000000..60ec651 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/digests/Sha1Digest.cs @@ -0,0 +1,284 @@ +using System; + +using Org.BouncyCastle.Crypto.Utilities; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Digests +{ + + /** + * 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 "endianness" of the word processing! + */ + public class Sha1Digest + : GeneralDigest + { + private const int DigestLength = 20; + + private uint H1, H2, H3, H4, H5; + + private uint[] X = new uint[80]; + private int xOff; + + public Sha1Digest() + { + Reset(); + } + + /** + * Copy constructor. This will copy the state of the provided + * message digest. + */ + public Sha1Digest(Sha1Digest t) + : base(t) + { + CopyIn(t); + } + + private void CopyIn(Sha1Digest t) + { + base.CopyIn(t); + + H1 = t.H1; + H2 = t.H2; + H3 = t.H3; + H4 = t.H4; + H5 = t.H5; + + Array.Copy(t.X, 0, X, 0, t.X.Length); + xOff = t.xOff; + } + + public override string AlgorithmName + { + get { return "SHA-1"; } + } + + public override int GetDigestSize() + { + return DigestLength; + } + + internal override void ProcessWord( + byte[] input, + int inOff) + { + X[xOff] = Pack.BE_To_UInt32(input, inOff); + + if (++xOff == 16) + { + ProcessBlock(); + } + } + + internal override void ProcessLength(long bitLength) + { + if (xOff > 14) + { + ProcessBlock(); + } + + X[14] = (uint)((ulong)bitLength >> 32); + X[15] = (uint)((ulong)bitLength); + } + + public override int DoFinal( + byte[] output, + int outOff) + { + Finish(); + + Pack.UInt32_To_BE(H1, output, outOff); + Pack.UInt32_To_BE(H2, output, outOff + 4); + Pack.UInt32_To_BE(H3, output, outOff + 8); + Pack.UInt32_To_BE(H4, output, outOff + 12); + Pack.UInt32_To_BE(H5, output, outOff + 16); + + Reset(); + + return DigestLength; + } + + /** + * reset the chaining variables + */ + public override void Reset() + { + base.Reset(); + + H1 = 0x67452301; + H2 = 0xefcdab89; + H3 = 0x98badcfe; + H4 = 0x10325476; + H5 = 0xc3d2e1f0; + + xOff = 0; + Array.Clear(X, 0, X.Length); + } + + // + // Additive constants + // + private const uint Y1 = 0x5a827999; + private const uint Y2 = 0x6ed9eba1; + private const uint Y3 = 0x8f1bbcdc; + private const uint Y4 = 0xca62c1d6; + + private static uint F(uint u, uint v, uint w) + { + return (u & v) | (~u & w); + } + + private static uint H(uint u, uint v, uint w) + { + return u ^ v ^ w; + } + + private static uint G(uint u, uint v, uint w) + { + return (u & v) | (u & w) | (v & w); + } + + internal override void ProcessBlock() + { + // + // expand 16 word block into 80 word block. + // + for (int i = 16; i < 80; i++) + { + uint t = X[i - 3] ^ X[i - 8] ^ X[i - 14] ^ X[i - 16]; + X[i] = t << 1 | t >> 31; + } + + // + // set up working variables. + // + uint A = H1; + uint B = H2; + uint C = H3; + uint D = H4; + uint 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 < 4; 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; + Array.Clear(X, 0, 16); + } + + public override IMemoable Copy() + { + return new Sha1Digest(this); + } + + public override void Reset(IMemoable other) + { + Sha1Digest d = (Sha1Digest)other; + + CopyIn(d); + } + + } +} diff --git a/bc-sharp-crypto/src/crypto/digests/Sha224Digest.cs b/bc-sharp-crypto/src/crypto/digests/Sha224Digest.cs new file mode 100644 index 0000000..b4e8537 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/digests/Sha224Digest.cs @@ -0,0 +1,289 @@ +using System; + +using Org.BouncyCastle.Crypto.Utilities; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Digests +{ + /** + * 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 + : GeneralDigest + { + private const int DigestLength = 28; + + private uint H1, H2, H3, H4, H5, H6, H7, H8; + + private uint[] X = new uint[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) + : base(t) + { + CopyIn(t); + } + + private void CopyIn(Sha224Digest t) + { + base.CopyIn(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; + + Array.Copy(t.X, 0, X, 0, t.X.Length); + xOff = t.xOff; + } + + public override string AlgorithmName + { + get { return "SHA-224"; } + } + + public override int GetDigestSize() + { + return DigestLength; + } + + internal override void ProcessWord( + byte[] input, + int inOff) + { + X[xOff] = Pack.BE_To_UInt32(input, inOff); + + if (++xOff == 16) + { + ProcessBlock(); + } + } + + internal override void ProcessLength( + long bitLength) + { + if (xOff > 14) + { + ProcessBlock(); + } + + X[14] = (uint)((ulong)bitLength >> 32); + X[15] = (uint)((ulong)bitLength); + } + + public override int DoFinal( + byte[] output, + int outOff) + { + Finish(); + + Pack.UInt32_To_BE(H1, output, outOff); + Pack.UInt32_To_BE(H2, output, outOff + 4); + Pack.UInt32_To_BE(H3, output, outOff + 8); + Pack.UInt32_To_BE(H4, output, outOff + 12); + Pack.UInt32_To_BE(H5, output, outOff + 16); + Pack.UInt32_To_BE(H6, output, outOff + 20); + Pack.UInt32_To_BE(H7, output, outOff + 24); + + Reset(); + + return DigestLength; + } + + /** + * reset the chaining variables + */ + public override void Reset() + { + base.Reset(); + + /* SHA-224 initial hash value + */ + H1 = 0xc1059ed8; + H2 = 0x367cd507; + H3 = 0x3070dd17; + H4 = 0xf70e5939; + H5 = 0xffc00b31; + H6 = 0x68581511; + H7 = 0x64f98fa7; + H8 = 0xbefa4fa4; + + xOff = 0; + Array.Clear(X, 0, X.Length); + } + + internal override void ProcessBlock() + { + // + // expand 16 word block into 64 word blocks. + // + for (int ti = 16; ti <= 63; ti++) + { + X[ti] = Theta1(X[ti - 2]) + X[ti - 7] + Theta0(X[ti - 15]) + X[ti - 16]; + } + + // + // set up working variables. + // + uint a = H1; + uint b = H2; + uint c = H3; + uint d = H4; + uint e = H5; + uint f = H6; + uint g = H7; + uint 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; + Array.Clear(X, 0, 16); + } + + /* SHA-224 functions */ + private static uint Ch(uint x, uint y, uint z) + { + return (x & y) ^ (~x & z); + } + + private static uint Maj(uint x, uint y, uint z) + { + return (x & y) ^ (x & z) ^ (y & z); + } + + private static uint Sum0(uint x) + { + return ((x >> 2) | (x << 30)) ^ ((x >> 13) | (x << 19)) ^ ((x >> 22) | (x << 10)); + } + + private static uint Sum1(uint x) + { + return ((x >> 6) | (x << 26)) ^ ((x >> 11) | (x << 21)) ^ ((x >> 25) | (x << 7)); + } + + private static uint Theta0(uint x) + { + return ((x >> 7) | (x << 25)) ^ ((x >> 18) | (x << 14)) ^ (x >> 3); + } + + private static uint Theta1(uint 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) + */ + internal static readonly uint[] 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 + }; + + public override IMemoable Copy() + { + return new Sha224Digest(this); + } + + public override void Reset(IMemoable other) + { + Sha224Digest d = (Sha224Digest)other; + + CopyIn(d); + } + + } +} diff --git a/bc-sharp-crypto/src/crypto/digests/Sha256Digest.cs b/bc-sharp-crypto/src/crypto/digests/Sha256Digest.cs new file mode 100644 index 0000000..98e10a3 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/digests/Sha256Digest.cs @@ -0,0 +1,330 @@ +using System; + +using Org.BouncyCastle.Crypto.Utilities; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Digests +{ + /** + * Draft FIPS 180-2 implementation of SHA-256. Note: As this is + * based on a draft this implementation is subject to change. + * + *
+    *         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 + : GeneralDigest + { + private const int DigestLength = 32; + + private uint H1, H2, H3, H4, H5, H6, H7, H8; + private uint[] X = new uint[64]; + private int xOff; + + public Sha256Digest() + { + initHs(); + } + + /** + * Copy constructor. This will copy the state of the provided + * message digest. + */ + public Sha256Digest(Sha256Digest t) : base(t) + { + CopyIn(t); + } + + private void CopyIn(Sha256Digest t) + { + base.CopyIn(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; + + Array.Copy(t.X, 0, X, 0, t.X.Length); + xOff = t.xOff; + } + + public override string AlgorithmName + { + get { return "SHA-256"; } + } + + public override int GetDigestSize() + { + return DigestLength; + } + + internal override void ProcessWord( + byte[] input, + int inOff) + { + X[xOff] = Pack.BE_To_UInt32(input, inOff); + + if (++xOff == 16) + { + ProcessBlock(); + } + } + + internal override void ProcessLength( + long bitLength) + { + if (xOff > 14) + { + ProcessBlock(); + } + + X[14] = (uint)((ulong)bitLength >> 32); + X[15] = (uint)((ulong)bitLength); + } + + public override int DoFinal( + byte[] output, + int outOff) + { + Finish(); + + Pack.UInt32_To_BE((uint)H1, output, outOff); + Pack.UInt32_To_BE((uint)H2, output, outOff + 4); + Pack.UInt32_To_BE((uint)H3, output, outOff + 8); + Pack.UInt32_To_BE((uint)H4, output, outOff + 12); + Pack.UInt32_To_BE((uint)H5, output, outOff + 16); + Pack.UInt32_To_BE((uint)H6, output, outOff + 20); + Pack.UInt32_To_BE((uint)H7, output, outOff + 24); + Pack.UInt32_To_BE((uint)H8, output, outOff + 28); + + Reset(); + + return DigestLength; + } + + /** + * reset the chaining variables + */ + public override void Reset() + { + base.Reset(); + + initHs(); + + xOff = 0; + Array.Clear(X, 0, X.Length); + } + + private void initHs() + { + /* 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; + } + + internal override void ProcessBlock() + { + // + // expand 16 word block into 64 word blocks. + // + for (int ti = 16; ti <= 63; ti++) + { + X[ti] = Theta1(X[ti - 2]) + X[ti - 7] + Theta0(X[ti - 15]) + X[ti - 16]; + } + + // + // set up working variables. + // + uint a = H1; + uint b = H2; + uint c = H3; + uint d = H4; + uint e = H5; + uint f = H6; + uint g = H7; + uint h = H8; + + int t = 0; + for(int i = 0; i < 8; ++i) + { + // t = 8 * i + h += Sum1Ch(e, f, g) + K[t] + X[t]; + d += h; + h += Sum0Maj(a, b, c); + ++t; + + // t = 8 * i + 1 + g += Sum1Ch(d, e, f) + K[t] + X[t]; + c += g; + g += Sum0Maj(h, a, b); + ++t; + + // t = 8 * i + 2 + f += Sum1Ch(c, d, e) + K[t] + X[t]; + b += f; + f += Sum0Maj(g, h, a); + ++t; + + // t = 8 * i + 3 + e += Sum1Ch(b, c, d) + K[t] + X[t]; + a += e; + e += Sum0Maj(f, g, h); + ++t; + + // t = 8 * i + 4 + d += Sum1Ch(a, b, c) + K[t] + X[t]; + h += d; + d += Sum0Maj(e, f, g); + ++t; + + // t = 8 * i + 5 + c += Sum1Ch(h, a, b) + K[t] + X[t]; + g += c; + c += Sum0Maj(d, e, f); + ++t; + + // t = 8 * i + 6 + b += Sum1Ch(g, h, a) + K[t] + X[t]; + f += b; + b += Sum0Maj(c, d, e); + ++t; + + // t = 8 * i + 7 + a += Sum1Ch(f, g, h) + K[t] + X[t]; + e += a; + a += Sum0Maj(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; + Array.Clear(X, 0, 16); + } + + private static uint Sum1Ch( + uint x, + uint y, + uint z) + { +// return Sum1(x) + Ch(x, y, z); + return (((x >> 6) | (x << 26)) ^ ((x >> 11) | (x << 21)) ^ ((x >> 25) | (x << 7))) + + ((x & y) ^ ((~x) & z)); + } + + private static uint Sum0Maj( + uint x, + uint y, + uint z) + { +// return Sum0(x) + Maj(x, y, z); + return (((x >> 2) | (x << 30)) ^ ((x >> 13) | (x << 19)) ^ ((x >> 22) | (x << 10))) + + ((x & y) ^ (x & z) ^ (y & z)); + } + +// /* SHA-256 functions */ +// private static uint Ch( +// uint x, +// uint y, +// uint z) +// { +// return ((x & y) ^ ((~x) & z)); +// } +// +// private static uint Maj( +// uint x, +// uint y, +// uint z) +// { +// return ((x & y) ^ (x & z) ^ (y & z)); +// } +// +// private static uint Sum0( +// uint x) +// { +// return ((x >> 2) | (x << 30)) ^ ((x >> 13) | (x << 19)) ^ ((x >> 22) | (x << 10)); +// } +// +// private static uint Sum1( +// uint x) +// { +// return ((x >> 6) | (x << 26)) ^ ((x >> 11) | (x << 21)) ^ ((x >> 25) | (x << 7)); +// } + + private static uint Theta0( + uint x) + { + return ((x >> 7) | (x << 25)) ^ ((x >> 18) | (x << 14)) ^ (x >> 3); + } + + private static uint Theta1( + uint 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) + */ + private static readonly uint[] 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 + }; + + public override IMemoable Copy() + { + return new Sha256Digest(this); + } + + public override void Reset(IMemoable other) + { + Sha256Digest d = (Sha256Digest)other; + + CopyIn(d); + } + + } +} diff --git a/bc-sharp-crypto/src/crypto/digests/Sha384Digest.cs b/bc-sharp-crypto/src/crypto/digests/Sha384Digest.cs new file mode 100644 index 0000000..e6c9a9a --- /dev/null +++ b/bc-sharp-crypto/src/crypto/digests/Sha384Digest.cs @@ -0,0 +1,101 @@ +using System; + +using Org.BouncyCastle.Crypto.Utilities; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Digests +{ + /** + * Draft FIPS 180-2 implementation of SHA-384. Note: As this is + * based on a draft this implementation is subject to change. + * + *
+     *         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 + : LongDigest + { + private const int DigestLength = 48; + + public Sha384Digest() + { + } + + /** + * Copy constructor. This will copy the state of the provided + * message digest. + */ + public Sha384Digest( + Sha384Digest t) + : base(t) + { + } + + public override string AlgorithmName + { + get { return "SHA-384"; } + } + + public override int GetDigestSize() + { + return DigestLength; + } + + public override int DoFinal( + byte[] output, + int outOff) + { + Finish(); + + Pack.UInt64_To_BE(H1, output, outOff); + Pack.UInt64_To_BE(H2, output, outOff + 8); + Pack.UInt64_To_BE(H3, output, outOff + 16); + Pack.UInt64_To_BE(H4, output, outOff + 24); + Pack.UInt64_To_BE(H5, output, outOff + 32); + Pack.UInt64_To_BE(H6, output, outOff + 40); + + Reset(); + + return DigestLength; + } + + /** + * reset the chaining variables + */ + public override void Reset() + { + base.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 = 0xcbbb9d5dc1059ed8; + H2 = 0x629a292a367cd507; + H3 = 0x9159015a3070dd17; + H4 = 0x152fecd8f70e5939; + H5 = 0x67332667ffc00b31; + H6 = 0x8eb44a8768581511; + H7 = 0xdb0c2e0d64f98fa7; + H8 = 0x47b5481dbefa4fa4; + } + + public override IMemoable Copy() + { + return new Sha384Digest(this); + } + + public override void Reset(IMemoable other) + { + Sha384Digest d = (Sha384Digest)other; + + CopyIn(d); + } + + } +} diff --git a/bc-sharp-crypto/src/crypto/digests/Sha512Digest.cs b/bc-sharp-crypto/src/crypto/digests/Sha512Digest.cs new file mode 100644 index 0000000..2a0964f --- /dev/null +++ b/bc-sharp-crypto/src/crypto/digests/Sha512Digest.cs @@ -0,0 +1,104 @@ +using System; + +using Org.BouncyCastle.Crypto.Utilities; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Digests +{ + /** + * Draft FIPS 180-2 implementation of SHA-512. Note: As this is + * based on a draft this implementation is subject to change. + * + *
+     *         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 + : LongDigest + { + private const int DigestLength = 64; + + public Sha512Digest() + { + } + + /** + * Copy constructor. This will copy the state of the provided + * message digest. + */ + public Sha512Digest( + Sha512Digest t) + : base(t) + { + } + + public override string AlgorithmName + { + get { return "SHA-512"; } + } + + public override int GetDigestSize() + { + return DigestLength; + } + + public override int DoFinal( + byte[] output, + int outOff) + { + Finish(); + + Pack.UInt64_To_BE(H1, output, outOff); + Pack.UInt64_To_BE(H2, output, outOff + 8); + Pack.UInt64_To_BE(H3, output, outOff + 16); + Pack.UInt64_To_BE(H4, output, outOff + 24); + Pack.UInt64_To_BE(H5, output, outOff + 32); + Pack.UInt64_To_BE(H6, output, outOff + 40); + Pack.UInt64_To_BE(H7, output, outOff + 48); + Pack.UInt64_To_BE(H8, output, outOff + 56); + + Reset(); + + return DigestLength; + + } + + /** + * reset the chaining variables + */ + public override void Reset() + { + base.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 = 0x6a09e667f3bcc908; + H2 = 0xbb67ae8584caa73b; + H3 = 0x3c6ef372fe94f82b; + H4 = 0xa54ff53a5f1d36f1; + H5 = 0x510e527fade682d1; + H6 = 0x9b05688c2b3e6c1f; + H7 = 0x1f83d9abfb41bd6b; + H8 = 0x5be0cd19137e2179; + } + + public override IMemoable Copy() + { + return new Sha512Digest(this); + } + + public override void Reset(IMemoable other) + { + Sha512Digest d = (Sha512Digest)other; + + CopyIn(d); + } + + } +} diff --git a/bc-sharp-crypto/src/crypto/digests/Sha512tDigest.cs b/bc-sharp-crypto/src/crypto/digests/Sha512tDigest.cs new file mode 100644 index 0000000..2caefa7 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/digests/Sha512tDigest.cs @@ -0,0 +1,200 @@ +using System; + +using Org.BouncyCastle.Crypto.Utilities; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Digests +{ + /** + * FIPS 180-4 implementation of SHA-512/t + */ + public class Sha512tDigest + : LongDigest + { + private const ulong A5 = 0xa5a5a5a5a5a5a5a5UL; + + private readonly int digestLength; + + private ulong H1t, H2t, H3t, H4t, H5t, H6t, H7t, H8t; + + /** + * Standard constructor + */ + public Sha512tDigest(int bitLength) + { + if (bitLength >= 512) + throw new ArgumentException("cannot be >= 512", "bitLength"); + if (bitLength % 8 != 0) + throw new ArgumentException("needs to be a multiple of 8", "bitLength"); + if (bitLength == 384) + throw new ArgumentException("cannot be 384 use SHA384 instead", "bitLength"); + + this.digestLength = bitLength / 8; + + tIvGenerate(digestLength * 8); + + Reset(); + } + + /** + * Copy constructor. This will copy the state of the provided + * message digest. + */ + public Sha512tDigest(Sha512tDigest t) + : base(t) + { + this.digestLength = t.digestLength; + + Reset(t); + } + + public override string AlgorithmName + { + get { return "SHA-512/" + (digestLength * 8); } + } + + public override int GetDigestSize() + { + return digestLength; + } + + public override int DoFinal(byte[] output, int outOff) + { + Finish(); + + UInt64_To_BE(H1, output, outOff, digestLength); + UInt64_To_BE(H2, output, outOff + 8, digestLength - 8); + UInt64_To_BE(H3, output, outOff + 16, digestLength - 16); + UInt64_To_BE(H4, output, outOff + 24, digestLength - 24); + UInt64_To_BE(H5, output, outOff + 32, digestLength - 32); + UInt64_To_BE(H6, output, outOff + 40, digestLength - 40); + UInt64_To_BE(H7, output, outOff + 48, digestLength - 48); + UInt64_To_BE(H8, output, outOff + 56, digestLength - 56); + + Reset(); + + return digestLength; + } + + /** + * reset the chaining variables + */ + public override void Reset() + { + base.Reset(); + + /* + * initial hash values use the iv generation algorithm for t. + */ + H1 = H1t; + H2 = H2t; + H3 = H3t; + H4 = H4t; + H5 = H5t; + H6 = H6t; + H7 = H7t; + H8 = H8t; + } + + private void tIvGenerate(int bitLength) + { + H1 = 0x6a09e667f3bcc908UL ^ A5; + H2 = 0xbb67ae8584caa73bUL ^ A5; + H3 = 0x3c6ef372fe94f82bUL ^ A5; + H4 = 0xa54ff53a5f1d36f1UL ^ A5; + H5 = 0x510e527fade682d1UL ^ A5; + H6 = 0x9b05688c2b3e6c1fUL ^ A5; + H7 = 0x1f83d9abfb41bd6bUL ^ A5; + H8 = 0x5be0cd19137e2179UL ^ A5; + + Update(0x53); + Update(0x48); + Update(0x41); + Update(0x2D); + Update(0x35); + Update(0x31); + Update(0x32); + Update(0x2F); + + if (bitLength > 100) + { + Update((byte)(bitLength / 100 + 0x30)); + bitLength = bitLength % 100; + Update((byte)(bitLength / 10 + 0x30)); + bitLength = bitLength % 10; + Update((byte)(bitLength + 0x30)); + } + else if (bitLength > 10) + { + Update((byte)(bitLength / 10 + 0x30)); + bitLength = bitLength % 10; + Update((byte)(bitLength + 0x30)); + } + else + { + Update((byte)(bitLength + 0x30)); + } + + Finish(); + + H1t = H1; + H2t = H2; + H3t = H3; + H4t = H4; + H5t = H5; + H6t = H6; + H7t = H7; + H8t = H8; + } + + private static void UInt64_To_BE(ulong n, byte[] bs, int off, int max) + { + if (max > 0) + { + UInt32_To_BE((uint)(n >> 32), bs, off, max); + + if (max > 4) + { + UInt32_To_BE((uint)n, bs, off + 4, max - 4); + } + } + } + + private static void UInt32_To_BE(uint n, byte[] bs, int off, int max) + { + int num = System.Math.Min(4, max); + while (--num >= 0) + { + int shift = 8 * (3 - num); + bs[off + num] = (byte)(n >> shift); + } + } + + public override IMemoable Copy() + { + return new Sha512tDigest(this); + } + + public override void Reset(IMemoable other) + { + Sha512tDigest t = (Sha512tDigest)other; + + if (this.digestLength != t.digestLength) + { + throw new MemoableResetException("digestLength inappropriate in other"); + } + + base.CopyIn(t); + + this.H1t = t.H1t; + this.H2t = t.H2t; + this.H3t = t.H3t; + this.H4t = t.H4t; + this.H5t = t.H5t; + this.H6t = t.H6t; + this.H7t = t.H7t; + this.H8t = t.H8t; + } + + } +} diff --git a/bc-sharp-crypto/src/crypto/digests/ShakeDigest.cs b/bc-sharp-crypto/src/crypto/digests/ShakeDigest.cs new file mode 100644 index 0000000..13e8838 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/digests/ShakeDigest.cs @@ -0,0 +1,119 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Digests +{ + /// + /// Implementation of SHAKE based on following KeccakNISTInterface.c from http://keccak.noekeon.org/ + /// + /// + /// Following the naming conventions used in the C source code to enable easy review of the implementation. + /// + public class ShakeDigest + : KeccakDigest, IXof + { + private static int CheckBitLength(int bitLength) + { + switch (bitLength) + { + case 128: + case 256: + return bitLength; + default: + throw new ArgumentException(bitLength + " not supported for SHAKE", "bitLength"); + } + } + + public ShakeDigest() + : this(128) + { + } + + public ShakeDigest(int bitLength) + : base(CheckBitLength(bitLength)) + { + } + + public ShakeDigest(ShakeDigest source) + : base(source) + { + } + + public override string AlgorithmName + { + get { return "SHAKE" + fixedOutputLength; } + } + + public override int DoFinal(byte[] output, int outOff) + { + return DoFinal(output, outOff, GetDigestSize()); + } + + public virtual int DoFinal(byte[] output, int outOff, int outLen) + { + DoOutput(output, outOff, outLen); + + Reset(); + + return outLen; + } + + public virtual int DoOutput(byte[] output, int outOff, int outLen) + { + if (!squeezing) + { + AbsorbBits(0x0F, 4); + } + + Squeeze(output, outOff, outLen); + + return outLen; + } + + /* + * TODO Possible API change to support partial-byte suffixes. + */ + protected override int DoFinal(byte[] output, int outOff, byte partialByte, int partialBits) + { + return DoFinal(output, outOff, GetDigestSize(), partialByte, partialBits); + } + + /* + * TODO Possible API change to support partial-byte suffixes. + */ + protected virtual int DoFinal(byte[] output, int outOff, int outLen, byte partialByte, int partialBits) + { + if (partialBits < 0 || partialBits > 7) + throw new ArgumentException("must be in the range [0,7]", "partialBits"); + + int finalInput = (partialByte & ((1 << partialBits) - 1)) | (0x0F << partialBits); + Debug.Assert(finalInput >= 0); + int finalBits = partialBits + 4; + + if (finalBits >= 8) + { + Absorb(new byte[]{ (byte)finalInput }, 0, 1); + finalBits -= 8; + finalInput >>= 8; + } + + if (finalBits > 0) + { + AbsorbBits(finalInput, finalBits); + } + + Squeeze(output, outOff, outLen); + + Reset(); + + return outLen; + } + + public override IMemoable Copy() + { + return new ShakeDigest(this); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/digests/ShortenedDigest.cs b/bc-sharp-crypto/src/crypto/digests/ShortenedDigest.cs new file mode 100644 index 0000000..9e4d99e --- /dev/null +++ b/bc-sharp-crypto/src/crypto/digests/ShortenedDigest.cs @@ -0,0 +1,82 @@ +using System; +using Org.BouncyCastle.Crypto; + +namespace Org.BouncyCastle.Crypto.Digests +{ + /** + * Wrapper class that reduces the output length of a particular digest to + * only the first n bytes of the digest function. + */ + public class ShortenedDigest + : IDigest + { + private IDigest baseDigest; + private int length; + + /** + * Base constructor. + * + * @param baseDigest underlying digest to use. + * @param length length in bytes of the output of doFinal. + * @exception ArgumentException if baseDigest is null, or length is greater than baseDigest.GetDigestSize(). + */ + public ShortenedDigest( + IDigest baseDigest, + int length) + { + if (baseDigest == null) + { + throw new ArgumentNullException("baseDigest"); + } + + if (length > baseDigest.GetDigestSize()) + { + throw new ArgumentException("baseDigest output not large enough to support length"); + } + + this.baseDigest = baseDigest; + this.length = length; + } + + public string AlgorithmName + { + get { return baseDigest.AlgorithmName + "(" + length * 8 + ")"; } + } + + public int GetDigestSize() + { + return length; + } + + public void Update(byte input) + { + baseDigest.Update(input); + } + + public void BlockUpdate(byte[] input, int inOff, int length) + { + baseDigest.BlockUpdate(input, inOff, length); + } + + public int DoFinal(byte[] output, int outOff) + { + byte[] tmp = new byte[baseDigest.GetDigestSize()]; + + baseDigest.DoFinal(tmp, 0); + + Array.Copy(tmp, 0, output, outOff, length); + + return length; + } + + public void Reset() + { + baseDigest.Reset(); + } + + public int GetByteLength() + { + return baseDigest.GetByteLength(); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/digests/SkeinDigest.cs b/bc-sharp-crypto/src/crypto/digests/SkeinDigest.cs new file mode 100644 index 0000000..f826ce5 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/digests/SkeinDigest.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Digests +{ + + /// + /// Implementation of the Skein parameterised hash function in 256, 512 and 1024 bit block sizes, + /// based on the Threefish tweakable block cipher. + /// + /// + /// This is the 1.3 version of Skein defined in the Skein hash function submission to the NIST SHA-3 + /// competition in October 2010. + ///

+ /// Skein was designed by Niels Ferguson - Stefan Lucks - Bruce Schneier - Doug Whiting - Mihir + /// Bellare - Tadayoshi Kohno - Jon Callas - Jesse Walker. + /// + /// + /// + public class SkeinDigest + : IDigest, IMemoable + { + ///

+ /// 256 bit block size - Skein-256 + /// + public const int SKEIN_256 = SkeinEngine.SKEIN_256; + /// + /// 512 bit block size - Skein-512 + /// + public const int SKEIN_512 = SkeinEngine.SKEIN_512; + /// + /// 1024 bit block size - Skein-1024 + /// + public const int SKEIN_1024 = SkeinEngine.SKEIN_1024; + + private readonly SkeinEngine engine; + + /// + /// Constructs a Skein digest with an internal state size and output size. + /// + /// the internal state size in bits - one of or + /// . + /// the output/digest size to produce in bits, which must be an integral number of + /// bytes. + public SkeinDigest(int stateSizeBits, int digestSizeBits) + { + this.engine = new SkeinEngine(stateSizeBits, digestSizeBits); + Init(null); + } + + public SkeinDigest(SkeinDigest digest) + { + this.engine = new SkeinEngine(digest.engine); + } + + public void Reset(IMemoable other) + { + SkeinDigest d = (SkeinDigest)other; + engine.Reset(d.engine); + } + + public IMemoable Copy() + { + return new SkeinDigest(this); + } + + public String AlgorithmName + { + get { return "Skein-" + (engine.BlockSize * 8) + "-" + (engine.OutputSize * 8); } + } + + public int GetDigestSize() + { + return engine.OutputSize; + } + + public int GetByteLength() + { + return engine.BlockSize; + } + + /// + /// Optionally initialises the Skein digest with the provided parameters. + /// + /// See for details on the parameterisation of the Skein hash function. + /// the parameters to apply to this engine, or null to use no parameters. + public void Init(SkeinParameters parameters) + { + engine.Init(parameters); + } + + public void Reset() + { + engine.Reset(); + } + + public void Update(byte inByte) + { + engine.Update(inByte); + } + + public void BlockUpdate(byte[] inBytes, int inOff, int len) + { + engine.Update(inBytes, inOff, len); + } + + public int DoFinal(byte[] outBytes, int outOff) + { + return engine.DoFinal(outBytes, outOff); + } + + } +} \ No newline at end of file diff --git a/bc-sharp-crypto/src/crypto/digests/SkeinEngine.cs b/bc-sharp-crypto/src/crypto/digests/SkeinEngine.cs new file mode 100644 index 0000000..cfedfad --- /dev/null +++ b/bc-sharp-crypto/src/crypto/digests/SkeinEngine.cs @@ -0,0 +1,804 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Digests +{ + + /// + /// Implementation of the Skein family of parameterised hash functions in 256, 512 and 1024 bit block + /// sizes, based on the Threefish tweakable block cipher. + /// + /// + /// This is the 1.3 version of Skein defined in the Skein hash function submission to the NIST SHA-3 + /// competition in October 2010. + ///

+ /// Skein was designed by Niels Ferguson - Stefan Lucks - Bruce Schneier - Doug Whiting - Mihir + /// Bellare - Tadayoshi Kohno - Jon Callas - Jesse Walker. + ///

+ /// This implementation is the basis for and , implementing the + /// parameter based configuration system that allows Skein to be adapted to multiple applications.
+ /// Initialising the engine with allows standard and arbitrary parameters to + /// be applied during the Skein hash function. + ///

+ /// Implemented: + ///

    + ///
  • 256, 512 and 1024 bit internal states.
  • + ///
  • Full 96 bit input length.
  • + ///
  • Parameters defined in the Skein specification, and arbitrary other pre and post message + /// parameters.
  • + ///
  • Arbitrary output size in 1 byte intervals.
  • + ///
+ ///

+ /// Not implemented: + ///

    + ///
  • Sub-byte length input (bit padding).
  • + ///
  • Tree hashing.
  • + ///
+ ///
+ /// + public class SkeinEngine + : IMemoable + { + /// + /// 256 bit block size - Skein-256 + /// + public const int SKEIN_256 = ThreefishEngine.BLOCKSIZE_256; + /// + /// 512 bit block size - Skein-512 + /// + public const int SKEIN_512 = ThreefishEngine.BLOCKSIZE_512; + /// + /// 1024 bit block size - Skein-1024 + /// + public const int SKEIN_1024 = ThreefishEngine.BLOCKSIZE_1024; + + // Minimal at present, but more complex when tree hashing is implemented + private class Configuration + { + private byte[] bytes = new byte[32]; + + public Configuration(long outputSizeBits) + { + // 0..3 = ASCII SHA3 + bytes[0] = (byte)'S'; + bytes[1] = (byte)'H'; + bytes[2] = (byte)'A'; + bytes[3] = (byte)'3'; + + // 4..5 = version number in LSB order + bytes[4] = 1; + bytes[5] = 0; + + // 8..15 = output length + ThreefishEngine.WordToBytes((ulong)outputSizeBits, bytes, 8); + } + + public byte[] Bytes + { + get { return bytes; } + } + + } + + public class Parameter + { + private int type; + private byte[] value; + + public Parameter(int type, byte[] value) + { + this.type = type; + this.value = value; + } + + public int Type + { + get { return type; } + } + + public byte[] Value + { + get { return value; } + } + + } + + /** + * The parameter type for the Skein key. + */ + private const int PARAM_TYPE_KEY = 0; + + /** + * The parameter type for the Skein configuration block. + */ + private const int PARAM_TYPE_CONFIG = 4; + + /** + * The parameter type for the message. + */ + private const int PARAM_TYPE_MESSAGE = 48; + + /** + * The parameter type for the output transformation. + */ + private const int PARAM_TYPE_OUTPUT = 63; + + /** + * Precalculated UBI(CFG) states for common state/output combinations without key or other + * pre-message params. + */ + private static readonly IDictionary INITIAL_STATES = Platform.CreateHashtable(); + + static SkeinEngine() + { + // From Appendix C of the Skein 1.3 NIST submission + InitialState(SKEIN_256, 128, new ulong[]{ + 0xe1111906964d7260UL, + 0x883daaa77c8d811cUL, + 0x10080df491960f7aUL, + 0xccf7dde5b45bc1c2UL}); + + InitialState(SKEIN_256, 160, new ulong[]{ + 0x1420231472825e98UL, + 0x2ac4e9a25a77e590UL, + 0xd47a58568838d63eUL, + 0x2dd2e4968586ab7dUL}); + + InitialState(SKEIN_256, 224, new ulong[]{ + 0xc6098a8c9ae5ea0bUL, + 0x876d568608c5191cUL, + 0x99cb88d7d7f53884UL, + 0x384bddb1aeddb5deUL}); + + InitialState(SKEIN_256, 256, new ulong[]{ + 0xfc9da860d048b449UL, + 0x2fca66479fa7d833UL, + 0xb33bc3896656840fUL, + 0x6a54e920fde8da69UL}); + + InitialState(SKEIN_512, 128, new ulong[]{ + 0xa8bc7bf36fbf9f52UL, + 0x1e9872cebd1af0aaUL, + 0x309b1790b32190d3UL, + 0xbcfbb8543f94805cUL, + 0x0da61bcd6e31b11bUL, + 0x1a18ebead46a32e3UL, + 0xa2cc5b18ce84aa82UL, + 0x6982ab289d46982dUL}); + + InitialState(SKEIN_512, 160, new ulong[]{ + 0x28b81a2ae013bd91UL, + 0xc2f11668b5bdf78fUL, + 0x1760d8f3f6a56f12UL, + 0x4fb747588239904fUL, + 0x21ede07f7eaf5056UL, + 0xd908922e63ed70b8UL, + 0xb8ec76ffeccb52faUL, + 0x01a47bb8a3f27a6eUL}); + + InitialState(SKEIN_512, 224, new ulong[]{ + 0xccd0616248677224UL, + 0xcba65cf3a92339efUL, + 0x8ccd69d652ff4b64UL, + 0x398aed7b3ab890b4UL, + 0x0f59d1b1457d2bd0UL, + 0x6776fe6575d4eb3dUL, + 0x99fbc70e997413e9UL, + 0x9e2cfccfe1c41ef7UL}); + + InitialState(SKEIN_512, 384, new ulong[]{ + 0xa3f6c6bf3a75ef5fUL, + 0xb0fef9ccfd84faa4UL, + 0x9d77dd663d770cfeUL, + 0xd798cbf3b468fddaUL, + 0x1bc4a6668a0e4465UL, + 0x7ed7d434e5807407UL, + 0x548fc1acd4ec44d6UL, + 0x266e17546aa18ff8UL}); + + InitialState(SKEIN_512, 512, new ulong[]{ + 0x4903adff749c51ceUL, + 0x0d95de399746df03UL, + 0x8fd1934127c79bceUL, + 0x9a255629ff352cb1UL, + 0x5db62599df6ca7b0UL, + 0xeabe394ca9d5c3f4UL, + 0x991112c71a75b523UL, + 0xae18a40b660fcc33UL}); + } + + private static void InitialState(int blockSize, int outputSize, ulong[] state) + { + INITIAL_STATES.Add(VariantIdentifier(blockSize / 8, outputSize / 8), state); + } + + private static int VariantIdentifier(int blockSizeBytes, int outputSizeBytes) + { + return (outputSizeBytes << 16) | blockSizeBytes; + } + + private class UbiTweak + { + /** + * Point at which position might overflow long, so switch to add with carry logic + */ + private const ulong LOW_RANGE = UInt64.MaxValue - UInt32.MaxValue; + + /** + * Bit 127 = final + */ + private const ulong T1_FINAL = 1UL << 63; + + /** + * Bit 126 = first + */ + private const ulong T1_FIRST = 1UL << 62; + + /** + * UBI uses a 128 bit tweak + */ + private ulong[] tweak = new ulong[2]; + + /** + * Whether 64 bit position exceeded + */ + private bool extendedPosition; + + public UbiTweak() + { + Reset(); + } + + public void Reset(UbiTweak tweak) + { + this.tweak = Arrays.Clone(tweak.tweak, this.tweak); + this.extendedPosition = tweak.extendedPosition; + } + + public void Reset() + { + tweak[0] = 0; + tweak[1] = 0; + extendedPosition = false; + First = true; + } + + public uint Type + { + get + { + return (uint)((tweak[1] >> 56) & 0x3FUL); + } + + set + { + // Bits 120..125 = type + tweak[1] = (tweak[1] & 0xFFFFFFC000000000UL) | ((value & 0x3FUL) << 56); + } + } + + public bool First + { + get + { + return ((tweak[1] & T1_FIRST) != 0); + } + set + { + if (value) + { + tweak[1] |= T1_FIRST; + } + else + { + tweak[1] &= ~T1_FIRST; + } + } + } + + public bool Final + { + get + { + return ((tweak[1] & T1_FINAL) != 0); + } + set + { + if (value) + { + tweak[1] |= T1_FINAL; + } + else + { + tweak[1] &= ~T1_FINAL; + } + } + } + + /** + * Advances the position in the tweak by the specified value. + */ + public void AdvancePosition(int advance) + { + // Bits 0..95 = position + if (extendedPosition) + { + ulong[] parts = new ulong[3]; + parts[0] = tweak[0] & 0xFFFFFFFFUL; + parts[1] = (tweak[0] >> 32) & 0xFFFFFFFFUL; + parts[2] = tweak[1] & 0xFFFFFFFFUL; + + ulong carry = (ulong)advance; + for (int i = 0; i < parts.Length; i++) + { + carry += parts[i]; + parts[i] = carry; + carry >>= 32; + } + tweak[0] = ((parts[1] & 0xFFFFFFFFUL) << 32) | (parts[0] & 0xFFFFFFFFUL); + tweak[1] = (tweak[1] & 0xFFFFFFFF00000000UL) | (parts[2] & 0xFFFFFFFFUL); + } + else + { + ulong position = tweak[0]; + position += (uint)advance; + tweak[0] = position; + if (position > LOW_RANGE) + { + extendedPosition = true; + } + } + } + + public ulong[] GetWords() + { + return tweak; + } + + public override string ToString() + { + return Type + " first: " + First + ", final: " + Final; + } + + } + + /** + * The Unique Block Iteration chaining mode. + */ + // TODO: This might be better as methods... + private class UBI + { + private readonly UbiTweak tweak = new UbiTweak(); + + private readonly SkeinEngine engine; + + /** + * Buffer for the current block of message data + */ + private byte[] currentBlock; + + /** + * Offset into the current message block + */ + private int currentOffset; + + /** + * Buffer for message words for feedback into encrypted block + */ + private ulong[] message; + + public UBI(SkeinEngine engine, int blockSize) + { + this.engine = engine; + currentBlock = new byte[blockSize]; + message = new ulong[currentBlock.Length / 8]; + } + + public void Reset(UBI ubi) + { + currentBlock = Arrays.Clone(ubi.currentBlock, currentBlock); + currentOffset = ubi.currentOffset; + message = Arrays.Clone(ubi.message, this.message); + tweak.Reset(ubi.tweak); + } + + public void Reset(int type) + { + tweak.Reset(); + tweak.Type = (uint)type; + currentOffset = 0; + } + + public void Update(byte[] value, int offset, int len, ulong[] output) + { + /* + * Buffer complete blocks for the underlying Threefish cipher, only flushing when there + * are subsequent bytes (last block must be processed in doFinal() with final=true set). + */ + int copied = 0; + while (len > copied) + { + if (currentOffset == currentBlock.Length) + { + ProcessBlock(output); + tweak.First = false; + currentOffset = 0; + } + + int toCopy = System.Math.Min((len - copied), currentBlock.Length - currentOffset); + Array.Copy(value, offset + copied, currentBlock, currentOffset, toCopy); + copied += toCopy; + currentOffset += toCopy; + tweak.AdvancePosition(toCopy); + } + } + + private void ProcessBlock(ulong[] output) + { + engine.threefish.Init(true, engine.chain, tweak.GetWords()); + for (int i = 0; i < message.Length; i++) + { + message[i] = ThreefishEngine.BytesToWord(currentBlock, i * 8); + } + + engine.threefish.ProcessBlock(message, output); + + for (int i = 0; i < output.Length; i++) + { + output[i] ^= message[i]; + } + } + + public void DoFinal(ulong[] output) + { + // Pad remainder of current block with zeroes + for (int i = currentOffset; i < currentBlock.Length; i++) + { + currentBlock[i] = 0; + } + + tweak.Final = true; + ProcessBlock(output); + } + + } + + /** + * Underlying Threefish tweakable block cipher + */ + private readonly ThreefishEngine threefish; + + /** + * Size of the digest output, in bytes + */ + private readonly int outputSizeBytes; + + /** + * The current chaining/state value + */ + private ulong[] chain; + + /** + * The initial state value + */ + private ulong[] initialState; + + /** + * The (optional) key parameter + */ + private byte[] key; + + /** + * Parameters to apply prior to the message + */ + private Parameter[] preMessageParameters; + + /** + * Parameters to apply after the message, but prior to output + */ + private Parameter[] postMessageParameters; + + /** + * The current UBI operation + */ + private readonly UBI ubi; + + /** + * Buffer for single byte update method + */ + private readonly byte[] singleByte = new byte[1]; + + /// + /// Constructs a Skein digest with an internal state size and output size. + /// + /// the internal state size in bits - one of or + /// . + /// the output/digest size to produce in bits, which must be an integral number of + /// bytes. + public SkeinEngine(int blockSizeBits, int outputSizeBits) + { + if (outputSizeBits % 8 != 0) + { + throw new ArgumentException("Output size must be a multiple of 8 bits. :" + outputSizeBits); + } + // TODO: Prevent digest sizes > block size? + this.outputSizeBytes = outputSizeBits / 8; + + this.threefish = new ThreefishEngine(blockSizeBits); + this.ubi = new UBI(this,threefish.GetBlockSize()); + } + + /// + /// Creates a SkeinEngine as an exact copy of an existing instance. + /// + public SkeinEngine(SkeinEngine engine) + : this(engine.BlockSize * 8, engine.OutputSize * 8) + { + CopyIn(engine); + } + + private void CopyIn(SkeinEngine engine) + { + this.ubi.Reset(engine.ubi); + this.chain = Arrays.Clone(engine.chain, this.chain); + this.initialState = Arrays.Clone(engine.initialState, this.initialState); + this.key = Arrays.Clone(engine.key, this.key); + this.preMessageParameters = Clone(engine.preMessageParameters, this.preMessageParameters); + this.postMessageParameters = Clone(engine.postMessageParameters, this.postMessageParameters); + } + + private static Parameter[] Clone(Parameter[] data, Parameter[] existing) + { + if (data == null) + { + return null; + } + if ((existing == null) || (existing.Length != data.Length)) + { + existing = new Parameter[data.Length]; + } + Array.Copy(data, 0, existing, 0, existing.Length); + return existing; + } + + public IMemoable Copy() + { + return new SkeinEngine(this); + } + + public void Reset(IMemoable other) + { + SkeinEngine s = (SkeinEngine)other; + if ((BlockSize != s.BlockSize) || (outputSizeBytes != s.outputSizeBytes)) + { + throw new MemoableResetException("Incompatible parameters in provided SkeinEngine."); + } + CopyIn(s); + } + + public int OutputSize + { + get { return outputSizeBytes; } + } + + public int BlockSize + { + get { return threefish.GetBlockSize (); } + } + + /// + /// Initialises the Skein engine with the provided parameters. See for + /// details on the parameterisation of the Skein hash function. + /// + /// the parameters to apply to this engine, or null to use no parameters. + public void Init(SkeinParameters parameters) + { + this.chain = null; + this.key = null; + this.preMessageParameters = null; + this.postMessageParameters = null; + + if (parameters != null) + { + byte[] key = parameters.GetKey(); + if (key.Length < 16) + { + throw new ArgumentException("Skein key must be at least 128 bits."); + } + InitParams(parameters.GetParameters()); + } + CreateInitialState(); + + // Initialise message block + UbiInit(PARAM_TYPE_MESSAGE); + } + + private void InitParams(IDictionary parameters) + { + IEnumerator keys = parameters.Keys.GetEnumerator(); + IList pre = Platform.CreateArrayList(); + IList post = Platform.CreateArrayList(); + + while (keys.MoveNext()) + { + int type = (int)keys.Current; + byte[] value = (byte[])parameters[type]; + + if (type == PARAM_TYPE_KEY) + { + this.key = value; + } + else if (type < PARAM_TYPE_MESSAGE) + { + pre.Add(new Parameter(type, value)); + } + else + { + post.Add(new Parameter(type, value)); + } + } + preMessageParameters = new Parameter[pre.Count]; + pre.CopyTo(preMessageParameters, 0); + Array.Sort(preMessageParameters); + + postMessageParameters = new Parameter[post.Count]; + post.CopyTo(postMessageParameters, 0); + Array.Sort(postMessageParameters); + } + + /** + * Calculate the initial (pre message block) chaining state. + */ + private void CreateInitialState() + { + ulong[] precalc = (ulong[])INITIAL_STATES[VariantIdentifier(BlockSize, OutputSize)]; + if ((key == null) && (precalc != null)) + { + // Precalculated UBI(CFG) + chain = Arrays.Clone(precalc); + } + else + { + // Blank initial state + chain = new ulong[BlockSize / 8]; + + // Process key block + if (key != null) + { + UbiComplete(SkeinParameters.PARAM_TYPE_KEY, key); + } + + // Process configuration block + UbiComplete(PARAM_TYPE_CONFIG, new Configuration(outputSizeBytes * 8).Bytes); + } + + // Process additional pre-message parameters + if (preMessageParameters != null) + { + for (int i = 0; i < preMessageParameters.Length; i++) + { + Parameter param = preMessageParameters[i]; + UbiComplete(param.Type, param.Value); + } + } + initialState = Arrays.Clone(chain); + } + + /// + /// Reset the engine to the initial state (with the key and any pre-message parameters , ready to + /// accept message input. + /// + public void Reset() + { + Array.Copy(initialState, 0, chain, 0, chain.Length); + + UbiInit(PARAM_TYPE_MESSAGE); + } + + private void UbiComplete(int type, byte[] value) + { + UbiInit(type); + this.ubi.Update(value, 0, value.Length, chain); + UbiFinal(); + } + + private void UbiInit(int type) + { + this.ubi.Reset(type); + } + + private void UbiFinal() + { + ubi.DoFinal(chain); + } + + private void CheckInitialised() + { + if (this.ubi == null) + { + throw new ArgumentException("Skein engine is not initialised."); + } + } + + public void Update(byte inByte) + { + singleByte[0] = inByte; + Update(singleByte, 0, 1); + } + + public void Update(byte[] inBytes, int inOff, int len) + { + CheckInitialised(); + ubi.Update(inBytes, inOff, len, chain); + } + + public int DoFinal(byte[] outBytes, int outOff) + { + CheckInitialised(); + if (outBytes.Length < (outOff + outputSizeBytes)) + { + throw new DataLengthException("Output buffer is too short to hold output"); + } + + // Finalise message block + UbiFinal(); + + // Process additional post-message parameters + if (postMessageParameters != null) + { + for (int i = 0; i < postMessageParameters.Length; i++) + { + Parameter param = postMessageParameters[i]; + UbiComplete(param.Type, param.Value); + } + } + + // Perform the output transform + int blockSize = BlockSize; + int blocksRequired = ((outputSizeBytes + blockSize - 1) / blockSize); + for (int i = 0; i < blocksRequired; i++) + { + int toWrite = System.Math.Min(blockSize, outputSizeBytes - (i * blockSize)); + Output((ulong)i, outBytes, outOff + (i * blockSize), toWrite); + } + + Reset(); + + return outputSizeBytes; + } + + private void Output(ulong outputSequence, byte[] outBytes, int outOff, int outputBytes) + { + byte[] currentBytes = new byte[8]; + ThreefishEngine.WordToBytes(outputSequence, currentBytes, 0); + + // Output is a sequence of UBI invocations all of which use and preserve the pre-output + // state + ulong[] outputWords = new ulong[chain.Length]; + UbiInit(PARAM_TYPE_OUTPUT); + this.ubi.Update(currentBytes, 0, currentBytes.Length, outputWords); + ubi.DoFinal(outputWords); + + int wordsRequired = ((outputBytes + 8 - 1) / 8); + for (int i = 0; i < wordsRequired; i++) + { + int toWrite = System.Math.Min(8, outputBytes - (i * 8)); + if (toWrite == 8) + { + ThreefishEngine.WordToBytes(outputWords[i], outBytes, outOff + (i * 8)); + } + else + { + ThreefishEngine.WordToBytes(outputWords[i], currentBytes, 0); + Array.Copy(currentBytes, 0, outBytes, outOff + (i * 8), toWrite); + } + } + } + + } +} + diff --git a/bc-sharp-crypto/src/crypto/digests/TigerDigest.cs b/bc-sharp-crypto/src/crypto/digests/TigerDigest.cs new file mode 100644 index 0000000..059232d --- /dev/null +++ b/bc-sharp-crypto/src/crypto/digests/TigerDigest.cs @@ -0,0 +1,883 @@ +using System; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Digests +{ + /** + * implementation of Tiger based on: + * + * http://www.cs.technion.ac.il/~biham/Reports/Tiger + */ + public class TigerDigest + : IDigest, IMemoable + { + private const int MyByteLength = 64; + + /* + * S-Boxes. + */ + private static readonly long[] t1 = { + unchecked((long) 0x02AAB17CF7E90C5EL) /* 0 */, unchecked((long) 0xAC424B03E243A8ECL) /* 1 */, + unchecked((long) 0x72CD5BE30DD5FCD3L) /* 2 */, unchecked((long) 0x6D019B93F6F97F3AL) /* 3 */, + unchecked((long) 0xCD9978FFD21F9193L) /* 4 */, unchecked((long) 0x7573A1C9708029E2L) /* 5 */, + unchecked((long) 0xB164326B922A83C3L) /* 6 */, unchecked((long) 0x46883EEE04915870L) /* 7 */, + unchecked((long) 0xEAACE3057103ECE6L) /* 8 */, unchecked((long) 0xC54169B808A3535CL) /* 9 */, + unchecked((long) 0x4CE754918DDEC47CL) /* 10 */, unchecked((long) 0x0AA2F4DFDC0DF40CL) /* 11 */, + unchecked((long) 0x10B76F18A74DBEFAL) /* 12 */, unchecked((long) 0xC6CCB6235AD1AB6AL) /* 13 */, + unchecked((long) 0x13726121572FE2FFL) /* 14 */, unchecked((long) 0x1A488C6F199D921EL) /* 15 */, + unchecked((long) 0x4BC9F9F4DA0007CAL) /* 16 */, unchecked((long) 0x26F5E6F6E85241C7L) /* 17 */, + unchecked((long) 0x859079DBEA5947B6L) /* 18 */, unchecked((long) 0x4F1885C5C99E8C92L) /* 19 */, + unchecked((long) 0xD78E761EA96F864BL) /* 20 */, unchecked((long) 0x8E36428C52B5C17DL) /* 21 */, + unchecked((long) 0x69CF6827373063C1L) /* 22 */, unchecked((long) 0xB607C93D9BB4C56EL) /* 23 */, + unchecked((long) 0x7D820E760E76B5EAL) /* 24 */, unchecked((long) 0x645C9CC6F07FDC42L) /* 25 */, + unchecked((long) 0xBF38A078243342E0L) /* 26 */, unchecked((long) 0x5F6B343C9D2E7D04L) /* 27 */, + unchecked((long) 0xF2C28AEB600B0EC6L) /* 28 */, unchecked((long) 0x6C0ED85F7254BCACL) /* 29 */, + unchecked((long) 0x71592281A4DB4FE5L) /* 30 */, unchecked((long) 0x1967FA69CE0FED9FL) /* 31 */, + unchecked((long) 0xFD5293F8B96545DBL) /* 32 */, unchecked((long) 0xC879E9D7F2A7600BL) /* 33 */, + unchecked((long) 0x860248920193194EL) /* 34 */, unchecked((long) 0xA4F9533B2D9CC0B3L) /* 35 */, + unchecked((long) 0x9053836C15957613L) /* 36 */, unchecked((long) 0xDB6DCF8AFC357BF1L) /* 37 */, + unchecked((long) 0x18BEEA7A7A370F57L) /* 38 */, unchecked((long) 0x037117CA50B99066L) /* 39 */, + unchecked((long) 0x6AB30A9774424A35L) /* 40 */, unchecked((long) 0xF4E92F02E325249BL) /* 41 */, + unchecked((long) 0x7739DB07061CCAE1L) /* 42 */, unchecked((long) 0xD8F3B49CECA42A05L) /* 43 */, + unchecked((long) 0xBD56BE3F51382F73L) /* 44 */, unchecked((long) 0x45FAED5843B0BB28L) /* 45 */, + unchecked((long) 0x1C813D5C11BF1F83L) /* 46 */, unchecked((long) 0x8AF0E4B6D75FA169L) /* 47 */, + unchecked((long) 0x33EE18A487AD9999L) /* 48 */, unchecked((long) 0x3C26E8EAB1C94410L) /* 49 */, + unchecked((long) 0xB510102BC0A822F9L) /* 50 */, unchecked((long) 0x141EEF310CE6123BL) /* 51 */, + unchecked((long) 0xFC65B90059DDB154L) /* 52 */, unchecked((long) 0xE0158640C5E0E607L) /* 53 */, + unchecked((long) 0x884E079826C3A3CFL) /* 54 */, unchecked((long) 0x930D0D9523C535FDL) /* 55 */, + unchecked((long) 0x35638D754E9A2B00L) /* 56 */, unchecked((long) 0x4085FCCF40469DD5L) /* 57 */, + unchecked((long) 0xC4B17AD28BE23A4CL) /* 58 */, unchecked((long) 0xCAB2F0FC6A3E6A2EL) /* 59 */, + unchecked((long) 0x2860971A6B943FCDL) /* 60 */, unchecked((long) 0x3DDE6EE212E30446L) /* 61 */, + unchecked((long) 0x6222F32AE01765AEL) /* 62 */, unchecked((long) 0x5D550BB5478308FEL) /* 63 */, + unchecked((long) 0xA9EFA98DA0EDA22AL) /* 64 */, unchecked((long) 0xC351A71686C40DA7L) /* 65 */, + unchecked((long) 0x1105586D9C867C84L) /* 66 */, unchecked((long) 0xDCFFEE85FDA22853L) /* 67 */, + unchecked((long) 0xCCFBD0262C5EEF76L) /* 68 */, unchecked((long) 0xBAF294CB8990D201L) /* 69 */, + unchecked((long) 0xE69464F52AFAD975L) /* 70 */, unchecked((long) 0x94B013AFDF133E14L) /* 71 */, + unchecked((long) 0x06A7D1A32823C958L) /* 72 */, unchecked((long) 0x6F95FE5130F61119L) /* 73 */, + unchecked((long) 0xD92AB34E462C06C0L) /* 74 */, unchecked((long) 0xED7BDE33887C71D2L) /* 75 */, + unchecked((long) 0x79746D6E6518393EL) /* 76 */, unchecked((long) 0x5BA419385D713329L) /* 77 */, + unchecked((long) 0x7C1BA6B948A97564L) /* 78 */, unchecked((long) 0x31987C197BFDAC67L) /* 79 */, + unchecked((long) 0xDE6C23C44B053D02L) /* 80 */, unchecked((long) 0x581C49FED002D64DL) /* 81 */, + unchecked((long) 0xDD474D6338261571L) /* 82 */, unchecked((long) 0xAA4546C3E473D062L) /* 83 */, + unchecked((long) 0x928FCE349455F860L) /* 84 */, unchecked((long) 0x48161BBACAAB94D9L) /* 85 */, + unchecked((long) 0x63912430770E6F68L) /* 86 */, unchecked((long) 0x6EC8A5E602C6641CL) /* 87 */, + unchecked((long) 0x87282515337DDD2BL) /* 88 */, unchecked((long) 0x2CDA6B42034B701BL) /* 89 */, + unchecked((long) 0xB03D37C181CB096DL) /* 90 */, unchecked((long) 0xE108438266C71C6FL) /* 91 */, + unchecked((long) 0x2B3180C7EB51B255L) /* 92 */, unchecked((long) 0xDF92B82F96C08BBCL) /* 93 */, + unchecked((long) 0x5C68C8C0A632F3BAL) /* 94 */, unchecked((long) 0x5504CC861C3D0556L) /* 95 */, + unchecked((long) 0xABBFA4E55FB26B8FL) /* 96 */, unchecked((long) 0x41848B0AB3BACEB4L) /* 97 */, + unchecked((long) 0xB334A273AA445D32L) /* 98 */, unchecked((long) 0xBCA696F0A85AD881L) /* 99 */, + unchecked((long) 0x24F6EC65B528D56CL) /* 100 */, unchecked((long) 0x0CE1512E90F4524AL) /* 101 */, + unchecked((long) 0x4E9DD79D5506D35AL) /* 102 */, unchecked((long) 0x258905FAC6CE9779L) /* 103 */, + unchecked((long) 0x2019295B3E109B33L) /* 104 */, unchecked((long) 0xF8A9478B73A054CCL) /* 105 */, + unchecked((long) 0x2924F2F934417EB0L) /* 106 */, unchecked((long) 0x3993357D536D1BC4L) /* 107 */, + unchecked((long) 0x38A81AC21DB6FF8BL) /* 108 */, unchecked((long) 0x47C4FBF17D6016BFL) /* 109 */, + unchecked((long) 0x1E0FAADD7667E3F5L) /* 110 */, unchecked((long) 0x7ABCFF62938BEB96L) /* 111 */, + unchecked((long) 0xA78DAD948FC179C9L) /* 112 */, unchecked((long) 0x8F1F98B72911E50DL) /* 113 */, + unchecked((long) 0x61E48EAE27121A91L) /* 114 */, unchecked((long) 0x4D62F7AD31859808L) /* 115 */, + unchecked((long) 0xECEBA345EF5CEAEBL) /* 116 */, unchecked((long) 0xF5CEB25EBC9684CEL) /* 117 */, + unchecked((long) 0xF633E20CB7F76221L) /* 118 */, unchecked((long) 0xA32CDF06AB8293E4L) /* 119 */, + unchecked((long) 0x985A202CA5EE2CA4L) /* 120 */, unchecked((long) 0xCF0B8447CC8A8FB1L) /* 121 */, + unchecked((long) 0x9F765244979859A3L) /* 122 */, unchecked((long) 0xA8D516B1A1240017L) /* 123 */, + unchecked((long) 0x0BD7BA3EBB5DC726L) /* 124 */, unchecked((long) 0xE54BCA55B86ADB39L) /* 125 */, + unchecked((long) 0x1D7A3AFD6C478063L) /* 126 */, unchecked((long) 0x519EC608E7669EDDL) /* 127 */, + unchecked((long) 0x0E5715A2D149AA23L) /* 128 */, unchecked((long) 0x177D4571848FF194L) /* 129 */, + unchecked((long) 0xEEB55F3241014C22L) /* 130 */, unchecked((long) 0x0F5E5CA13A6E2EC2L) /* 131 */, + unchecked((long) 0x8029927B75F5C361L) /* 132 */, unchecked((long) 0xAD139FABC3D6E436L) /* 133 */, + unchecked((long) 0x0D5DF1A94CCF402FL) /* 134 */, unchecked((long) 0x3E8BD948BEA5DFC8L) /* 135 */, + unchecked((long) 0xA5A0D357BD3FF77EL) /* 136 */, unchecked((long) 0xA2D12E251F74F645L) /* 137 */, + unchecked((long) 0x66FD9E525E81A082L) /* 138 */, unchecked((long) 0x2E0C90CE7F687A49L) /* 139 */, + unchecked((long) 0xC2E8BCBEBA973BC5L) /* 140 */, unchecked((long) 0x000001BCE509745FL) /* 141 */, + unchecked((long) 0x423777BBE6DAB3D6L) /* 142 */, unchecked((long) 0xD1661C7EAEF06EB5L) /* 143 */, + unchecked((long) 0xA1781F354DAACFD8L) /* 144 */, unchecked((long) 0x2D11284A2B16AFFCL) /* 145 */, + unchecked((long) 0xF1FC4F67FA891D1FL) /* 146 */, unchecked((long) 0x73ECC25DCB920ADAL) /* 147 */, + unchecked((long) 0xAE610C22C2A12651L) /* 148 */, unchecked((long) 0x96E0A810D356B78AL) /* 149 */, + unchecked((long) 0x5A9A381F2FE7870FL) /* 150 */, unchecked((long) 0xD5AD62EDE94E5530L) /* 151 */, + unchecked((long) 0xD225E5E8368D1427L) /* 152 */, unchecked((long) 0x65977B70C7AF4631L) /* 153 */, + unchecked((long) 0x99F889B2DE39D74FL) /* 154 */, unchecked((long) 0x233F30BF54E1D143L) /* 155 */, + unchecked((long) 0x9A9675D3D9A63C97L) /* 156 */, unchecked((long) 0x5470554FF334F9A8L) /* 157 */, + unchecked((long) 0x166ACB744A4F5688L) /* 158 */, unchecked((long) 0x70C74CAAB2E4AEADL) /* 159 */, + unchecked((long) 0xF0D091646F294D12L) /* 160 */, unchecked((long) 0x57B82A89684031D1L) /* 161 */, + unchecked((long) 0xEFD95A5A61BE0B6BL) /* 162 */, unchecked((long) 0x2FBD12E969F2F29AL) /* 163 */, + unchecked((long) 0x9BD37013FEFF9FE8L) /* 164 */, unchecked((long) 0x3F9B0404D6085A06L) /* 165 */, + unchecked((long) 0x4940C1F3166CFE15L) /* 166 */, unchecked((long) 0x09542C4DCDF3DEFBL) /* 167 */, + unchecked((long) 0xB4C5218385CD5CE3L) /* 168 */, unchecked((long) 0xC935B7DC4462A641L) /* 169 */, + unchecked((long) 0x3417F8A68ED3B63FL) /* 170 */, unchecked((long) 0xB80959295B215B40L) /* 171 */, + unchecked((long) 0xF99CDAEF3B8C8572L) /* 172 */, unchecked((long) 0x018C0614F8FCB95DL) /* 173 */, + unchecked((long) 0x1B14ACCD1A3ACDF3L) /* 174 */, unchecked((long) 0x84D471F200BB732DL) /* 175 */, + unchecked((long) 0xC1A3110E95E8DA16L) /* 176 */, unchecked((long) 0x430A7220BF1A82B8L) /* 177 */, + unchecked((long) 0xB77E090D39DF210EL) /* 178 */, unchecked((long) 0x5EF4BD9F3CD05E9DL) /* 179 */, + unchecked((long) 0x9D4FF6DA7E57A444L) /* 180 */, unchecked((long) 0xDA1D60E183D4A5F8L) /* 181 */, + unchecked((long) 0xB287C38417998E47L) /* 182 */, unchecked((long) 0xFE3EDC121BB31886L) /* 183 */, + unchecked((long) 0xC7FE3CCC980CCBEFL) /* 184 */, unchecked((long) 0xE46FB590189BFD03L) /* 185 */, + unchecked((long) 0x3732FD469A4C57DCL) /* 186 */, unchecked((long) 0x7EF700A07CF1AD65L) /* 187 */, + unchecked((long) 0x59C64468A31D8859L) /* 188 */, unchecked((long) 0x762FB0B4D45B61F6L) /* 189 */, + unchecked((long) 0x155BAED099047718L) /* 190 */, unchecked((long) 0x68755E4C3D50BAA6L) /* 191 */, + unchecked((long) 0xE9214E7F22D8B4DFL) /* 192 */, unchecked((long) 0x2ADDBF532EAC95F4L) /* 193 */, + unchecked((long) 0x32AE3909B4BD0109L) /* 194 */, unchecked((long) 0x834DF537B08E3450L) /* 195 */, + unchecked((long) 0xFA209DA84220728DL) /* 196 */, unchecked((long) 0x9E691D9B9EFE23F7L) /* 197 */, + unchecked((long) 0x0446D288C4AE8D7FL) /* 198 */, unchecked((long) 0x7B4CC524E169785BL) /* 199 */, + unchecked((long) 0x21D87F0135CA1385L) /* 200 */, unchecked((long) 0xCEBB400F137B8AA5L) /* 201 */, + unchecked((long) 0x272E2B66580796BEL) /* 202 */, unchecked((long) 0x3612264125C2B0DEL) /* 203 */, + unchecked((long) 0x057702BDAD1EFBB2L) /* 204 */, unchecked((long) 0xD4BABB8EACF84BE9L) /* 205 */, + unchecked((long) 0x91583139641BC67BL) /* 206 */, unchecked((long) 0x8BDC2DE08036E024L) /* 207 */, + unchecked((long) 0x603C8156F49F68EDL) /* 208 */, unchecked((long) 0xF7D236F7DBEF5111L) /* 209 */, + unchecked((long) 0x9727C4598AD21E80L) /* 210 */, unchecked((long) 0xA08A0896670A5FD7L) /* 211 */, + unchecked((long) 0xCB4A8F4309EBA9CBL) /* 212 */, unchecked((long) 0x81AF564B0F7036A1L) /* 213 */, + unchecked((long) 0xC0B99AA778199ABDL) /* 214 */, unchecked((long) 0x959F1EC83FC8E952L) /* 215 */, + unchecked((long) 0x8C505077794A81B9L) /* 216 */, unchecked((long) 0x3ACAAF8F056338F0L) /* 217 */, + unchecked((long) 0x07B43F50627A6778L) /* 218 */, unchecked((long) 0x4A44AB49F5ECCC77L) /* 219 */, + unchecked((long) 0x3BC3D6E4B679EE98L) /* 220 */, unchecked((long) 0x9CC0D4D1CF14108CL) /* 221 */, + unchecked((long) 0x4406C00B206BC8A0L) /* 222 */, unchecked((long) 0x82A18854C8D72D89L) /* 223 */, + unchecked((long) 0x67E366B35C3C432CL) /* 224 */, unchecked((long) 0xB923DD61102B37F2L) /* 225 */, + unchecked((long) 0x56AB2779D884271DL) /* 226 */, unchecked((long) 0xBE83E1B0FF1525AFL) /* 227 */, + unchecked((long) 0xFB7C65D4217E49A9L) /* 228 */, unchecked((long) 0x6BDBE0E76D48E7D4L) /* 229 */, + unchecked((long) 0x08DF828745D9179EL) /* 230 */, unchecked((long) 0x22EA6A9ADD53BD34L) /* 231 */, + unchecked((long) 0xE36E141C5622200AL) /* 232 */, unchecked((long) 0x7F805D1B8CB750EEL) /* 233 */, + unchecked((long) 0xAFE5C7A59F58E837L) /* 234 */, unchecked((long) 0xE27F996A4FB1C23CL) /* 235 */, + unchecked((long) 0xD3867DFB0775F0D0L) /* 236 */, unchecked((long) 0xD0E673DE6E88891AL) /* 237 */, + unchecked((long) 0x123AEB9EAFB86C25L) /* 238 */, unchecked((long) 0x30F1D5D5C145B895L) /* 239 */, + unchecked((long) 0xBB434A2DEE7269E7L) /* 240 */, unchecked((long) 0x78CB67ECF931FA38L) /* 241 */, + unchecked((long) 0xF33B0372323BBF9CL) /* 242 */, unchecked((long) 0x52D66336FB279C74L) /* 243 */, + unchecked((long) 0x505F33AC0AFB4EAAL) /* 244 */, unchecked((long) 0xE8A5CD99A2CCE187L) /* 245 */, + unchecked((long) 0x534974801E2D30BBL) /* 246 */, unchecked((long) 0x8D2D5711D5876D90L) /* 247 */, + unchecked((long) 0x1F1A412891BC038EL) /* 248 */, unchecked((long) 0xD6E2E71D82E56648L) /* 249 */, + unchecked((long) 0x74036C3A497732B7L) /* 250 */, unchecked((long) 0x89B67ED96361F5ABL) /* 251 */, + unchecked((long) 0xFFED95D8F1EA02A2L) /* 252 */, unchecked((long) 0xE72B3BD61464D43DL) /* 253 */, + unchecked((long) 0xA6300F170BDC4820L) /* 254 */, unchecked((long) 0xEBC18760ED78A77AL) /* 255 */, + }; + + private static readonly long[] t2 = { + unchecked((long) 0xE6A6BE5A05A12138L) /* 256 */, unchecked((long) 0xB5A122A5B4F87C98L) /* 257 */, + unchecked((long) 0x563C6089140B6990L) /* 258 */, unchecked((long) 0x4C46CB2E391F5DD5L) /* 259 */, + unchecked((long) 0xD932ADDBC9B79434L) /* 260 */, unchecked((long) 0x08EA70E42015AFF5L) /* 261 */, + unchecked((long) 0xD765A6673E478CF1L) /* 262 */, unchecked((long) 0xC4FB757EAB278D99L) /* 263 */, + unchecked((long) 0xDF11C6862D6E0692L) /* 264 */, unchecked((long) 0xDDEB84F10D7F3B16L) /* 265 */, + unchecked((long) 0x6F2EF604A665EA04L) /* 266 */, unchecked((long) 0x4A8E0F0FF0E0DFB3L) /* 267 */, + unchecked((long) 0xA5EDEEF83DBCBA51L) /* 268 */, unchecked((long) 0xFC4F0A2A0EA4371EL) /* 269 */, + unchecked((long) 0xE83E1DA85CB38429L) /* 270 */, unchecked((long) 0xDC8FF882BA1B1CE2L) /* 271 */, + unchecked((long) 0xCD45505E8353E80DL) /* 272 */, unchecked((long) 0x18D19A00D4DB0717L) /* 273 */, + unchecked((long) 0x34A0CFEDA5F38101L) /* 274 */, unchecked((long) 0x0BE77E518887CAF2L) /* 275 */, + unchecked((long) 0x1E341438B3C45136L) /* 276 */, unchecked((long) 0xE05797F49089CCF9L) /* 277 */, + unchecked((long) 0xFFD23F9DF2591D14L) /* 278 */, unchecked((long) 0x543DDA228595C5CDL) /* 279 */, + unchecked((long) 0x661F81FD99052A33L) /* 280 */, unchecked((long) 0x8736E641DB0F7B76L) /* 281 */, + unchecked((long) 0x15227725418E5307L) /* 282 */, unchecked((long) 0xE25F7F46162EB2FAL) /* 283 */, + unchecked((long) 0x48A8B2126C13D9FEL) /* 284 */, unchecked((long) 0xAFDC541792E76EEAL) /* 285 */, + unchecked((long) 0x03D912BFC6D1898FL) /* 286 */, unchecked((long) 0x31B1AAFA1B83F51BL) /* 287 */, + unchecked((long) 0xF1AC2796E42AB7D9L) /* 288 */, unchecked((long) 0x40A3A7D7FCD2EBACL) /* 289 */, + unchecked((long) 0x1056136D0AFBBCC5L) /* 290 */, unchecked((long) 0x7889E1DD9A6D0C85L) /* 291 */, + unchecked((long) 0xD33525782A7974AAL) /* 292 */, unchecked((long) 0xA7E25D09078AC09BL) /* 293 */, + unchecked((long) 0xBD4138B3EAC6EDD0L) /* 294 */, unchecked((long) 0x920ABFBE71EB9E70L) /* 295 */, + unchecked((long) 0xA2A5D0F54FC2625CL) /* 296 */, unchecked((long) 0xC054E36B0B1290A3L) /* 297 */, + unchecked((long) 0xF6DD59FF62FE932BL) /* 298 */, unchecked((long) 0x3537354511A8AC7DL) /* 299 */, + unchecked((long) 0xCA845E9172FADCD4L) /* 300 */, unchecked((long) 0x84F82B60329D20DCL) /* 301 */, + unchecked((long) 0x79C62CE1CD672F18L) /* 302 */, unchecked((long) 0x8B09A2ADD124642CL) /* 303 */, + unchecked((long) 0xD0C1E96A19D9E726L) /* 304 */, unchecked((long) 0x5A786A9B4BA9500CL) /* 305 */, + unchecked((long) 0x0E020336634C43F3L) /* 306 */, unchecked((long) 0xC17B474AEB66D822L) /* 307 */, + unchecked((long) 0x6A731AE3EC9BAAC2L) /* 308 */, unchecked((long) 0x8226667AE0840258L) /* 309 */, + unchecked((long) 0x67D4567691CAECA5L) /* 310 */, unchecked((long) 0x1D94155C4875ADB5L) /* 311 */, + unchecked((long) 0x6D00FD985B813FDFL) /* 312 */, unchecked((long) 0x51286EFCB774CD06L) /* 313 */, + unchecked((long) 0x5E8834471FA744AFL) /* 314 */, unchecked((long) 0xF72CA0AEE761AE2EL) /* 315 */, + unchecked((long) 0xBE40E4CDAEE8E09AL) /* 316 */, unchecked((long) 0xE9970BBB5118F665L) /* 317 */, + unchecked((long) 0x726E4BEB33DF1964L) /* 318 */, unchecked((long) 0x703B000729199762L) /* 319 */, + unchecked((long) 0x4631D816F5EF30A7L) /* 320 */, unchecked((long) 0xB880B5B51504A6BEL) /* 321 */, + unchecked((long) 0x641793C37ED84B6CL) /* 322 */, unchecked((long) 0x7B21ED77F6E97D96L) /* 323 */, + unchecked((long) 0x776306312EF96B73L) /* 324 */, unchecked((long) 0xAE528948E86FF3F4L) /* 325 */, + unchecked((long) 0x53DBD7F286A3F8F8L) /* 326 */, unchecked((long) 0x16CADCE74CFC1063L) /* 327 */, + unchecked((long) 0x005C19BDFA52C6DDL) /* 328 */, unchecked((long) 0x68868F5D64D46AD3L) /* 329 */, + unchecked((long) 0x3A9D512CCF1E186AL) /* 330 */, unchecked((long) 0x367E62C2385660AEL) /* 331 */, + unchecked((long) 0xE359E7EA77DCB1D7L) /* 332 */, unchecked((long) 0x526C0773749ABE6EL) /* 333 */, + unchecked((long) 0x735AE5F9D09F734BL) /* 334 */, unchecked((long) 0x493FC7CC8A558BA8L) /* 335 */, + unchecked((long) 0xB0B9C1533041AB45L) /* 336 */, unchecked((long) 0x321958BA470A59BDL) /* 337 */, + unchecked((long) 0x852DB00B5F46C393L) /* 338 */, unchecked((long) 0x91209B2BD336B0E5L) /* 339 */, + unchecked((long) 0x6E604F7D659EF19FL) /* 340 */, unchecked((long) 0xB99A8AE2782CCB24L) /* 341 */, + unchecked((long) 0xCCF52AB6C814C4C7L) /* 342 */, unchecked((long) 0x4727D9AFBE11727BL) /* 343 */, + unchecked((long) 0x7E950D0C0121B34DL) /* 344 */, unchecked((long) 0x756F435670AD471FL) /* 345 */, + unchecked((long) 0xF5ADD442615A6849L) /* 346 */, unchecked((long) 0x4E87E09980B9957AL) /* 347 */, + unchecked((long) 0x2ACFA1DF50AEE355L) /* 348 */, unchecked((long) 0xD898263AFD2FD556L) /* 349 */, + unchecked((long) 0xC8F4924DD80C8FD6L) /* 350 */, unchecked((long) 0xCF99CA3D754A173AL) /* 351 */, + unchecked((long) 0xFE477BACAF91BF3CL) /* 352 */, unchecked((long) 0xED5371F6D690C12DL) /* 353 */, + unchecked((long) 0x831A5C285E687094L) /* 354 */, unchecked((long) 0xC5D3C90A3708A0A4L) /* 355 */, + unchecked((long) 0x0F7F903717D06580L) /* 356 */, unchecked((long) 0x19F9BB13B8FDF27FL) /* 357 */, + unchecked((long) 0xB1BD6F1B4D502843L) /* 358 */, unchecked((long) 0x1C761BA38FFF4012L) /* 359 */, + unchecked((long) 0x0D1530C4E2E21F3BL) /* 360 */, unchecked((long) 0x8943CE69A7372C8AL) /* 361 */, + unchecked((long) 0xE5184E11FEB5CE66L) /* 362 */, unchecked((long) 0x618BDB80BD736621L) /* 363 */, + unchecked((long) 0x7D29BAD68B574D0BL) /* 364 */, unchecked((long) 0x81BB613E25E6FE5BL) /* 365 */, + unchecked((long) 0x071C9C10BC07913FL) /* 366 */, unchecked((long) 0xC7BEEB7909AC2D97L) /* 367 */, + unchecked((long) 0xC3E58D353BC5D757L) /* 368 */, unchecked((long) 0xEB017892F38F61E8L) /* 369 */, + unchecked((long) 0xD4EFFB9C9B1CC21AL) /* 370 */, unchecked((long) 0x99727D26F494F7ABL) /* 371 */, + unchecked((long) 0xA3E063A2956B3E03L) /* 372 */, unchecked((long) 0x9D4A8B9A4AA09C30L) /* 373 */, + unchecked((long) 0x3F6AB7D500090FB4L) /* 374 */, unchecked((long) 0x9CC0F2A057268AC0L) /* 375 */, + unchecked((long) 0x3DEE9D2DEDBF42D1L) /* 376 */, unchecked((long) 0x330F49C87960A972L) /* 377 */, + unchecked((long) 0xC6B2720287421B41L) /* 378 */, unchecked((long) 0x0AC59EC07C00369CL) /* 379 */, + unchecked((long) 0xEF4EAC49CB353425L) /* 380 */, unchecked((long) 0xF450244EEF0129D8L) /* 381 */, + unchecked((long) 0x8ACC46E5CAF4DEB6L) /* 382 */, unchecked((long) 0x2FFEAB63989263F7L) /* 383 */, + unchecked((long) 0x8F7CB9FE5D7A4578L) /* 384 */, unchecked((long) 0x5BD8F7644E634635L) /* 385 */, + unchecked((long) 0x427A7315BF2DC900L) /* 386 */, unchecked((long) 0x17D0C4AA2125261CL) /* 387 */, + unchecked((long) 0x3992486C93518E50L) /* 388 */, unchecked((long) 0xB4CBFEE0A2D7D4C3L) /* 389 */, + unchecked((long) 0x7C75D6202C5DDD8DL) /* 390 */, unchecked((long) 0xDBC295D8E35B6C61L) /* 391 */, + unchecked((long) 0x60B369D302032B19L) /* 392 */, unchecked((long) 0xCE42685FDCE44132L) /* 393 */, + unchecked((long) 0x06F3DDB9DDF65610L) /* 394 */, unchecked((long) 0x8EA4D21DB5E148F0L) /* 395 */, + unchecked((long) 0x20B0FCE62FCD496FL) /* 396 */, unchecked((long) 0x2C1B912358B0EE31L) /* 397 */, + unchecked((long) 0xB28317B818F5A308L) /* 398 */, unchecked((long) 0xA89C1E189CA6D2CFL) /* 399 */, + unchecked((long) 0x0C6B18576AAADBC8L) /* 400 */, unchecked((long) 0xB65DEAA91299FAE3L) /* 401 */, + unchecked((long) 0xFB2B794B7F1027E7L) /* 402 */, unchecked((long) 0x04E4317F443B5BEBL) /* 403 */, + unchecked((long) 0x4B852D325939D0A6L) /* 404 */, unchecked((long) 0xD5AE6BEEFB207FFCL) /* 405 */, + unchecked((long) 0x309682B281C7D374L) /* 406 */, unchecked((long) 0xBAE309A194C3B475L) /* 407 */, + unchecked((long) 0x8CC3F97B13B49F05L) /* 408 */, unchecked((long) 0x98A9422FF8293967L) /* 409 */, + unchecked((long) 0x244B16B01076FF7CL) /* 410 */, unchecked((long) 0xF8BF571C663D67EEL) /* 411 */, + unchecked((long) 0x1F0D6758EEE30DA1L) /* 412 */, unchecked((long) 0xC9B611D97ADEB9B7L) /* 413 */, + unchecked((long) 0xB7AFD5887B6C57A2L) /* 414 */, unchecked((long) 0x6290AE846B984FE1L) /* 415 */, + unchecked((long) 0x94DF4CDEACC1A5FDL) /* 416 */, unchecked((long) 0x058A5BD1C5483AFFL) /* 417 */, + unchecked((long) 0x63166CC142BA3C37L) /* 418 */, unchecked((long) 0x8DB8526EB2F76F40L) /* 419 */, + unchecked((long) 0xE10880036F0D6D4EL) /* 420 */, unchecked((long) 0x9E0523C9971D311DL) /* 421 */, + unchecked((long) 0x45EC2824CC7CD691L) /* 422 */, unchecked((long) 0x575B8359E62382C9L) /* 423 */, + unchecked((long) 0xFA9E400DC4889995L) /* 424 */, unchecked((long) 0xD1823ECB45721568L) /* 425 */, + unchecked((long) 0xDAFD983B8206082FL) /* 426 */, unchecked((long) 0xAA7D29082386A8CBL) /* 427 */, + unchecked((long) 0x269FCD4403B87588L) /* 428 */, unchecked((long) 0x1B91F5F728BDD1E0L) /* 429 */, + unchecked((long) 0xE4669F39040201F6L) /* 430 */, unchecked((long) 0x7A1D7C218CF04ADEL) /* 431 */, + unchecked((long) 0x65623C29D79CE5CEL) /* 432 */, unchecked((long) 0x2368449096C00BB1L) /* 433 */, + unchecked((long) 0xAB9BF1879DA503BAL) /* 434 */, unchecked((long) 0xBC23ECB1A458058EL) /* 435 */, + unchecked((long) 0x9A58DF01BB401ECCL) /* 436 */, unchecked((long) 0xA070E868A85F143DL) /* 437 */, + unchecked((long) 0x4FF188307DF2239EL) /* 438 */, unchecked((long) 0x14D565B41A641183L) /* 439 */, + unchecked((long) 0xEE13337452701602L) /* 440 */, unchecked((long) 0x950E3DCF3F285E09L) /* 441 */, + unchecked((long) 0x59930254B9C80953L) /* 442 */, unchecked((long) 0x3BF299408930DA6DL) /* 443 */, + unchecked((long) 0xA955943F53691387L) /* 444 */, unchecked((long) 0xA15EDECAA9CB8784L) /* 445 */, + unchecked((long) 0x29142127352BE9A0L) /* 446 */, unchecked((long) 0x76F0371FFF4E7AFBL) /* 447 */, + unchecked((long) 0x0239F450274F2228L) /* 448 */, unchecked((long) 0xBB073AF01D5E868BL) /* 449 */, + unchecked((long) 0xBFC80571C10E96C1L) /* 450 */, unchecked((long) 0xD267088568222E23L) /* 451 */, + unchecked((long) 0x9671A3D48E80B5B0L) /* 452 */, unchecked((long) 0x55B5D38AE193BB81L) /* 453 */, + unchecked((long) 0x693AE2D0A18B04B8L) /* 454 */, unchecked((long) 0x5C48B4ECADD5335FL) /* 455 */, + unchecked((long) 0xFD743B194916A1CAL) /* 456 */, unchecked((long) 0x2577018134BE98C4L) /* 457 */, + unchecked((long) 0xE77987E83C54A4ADL) /* 458 */, unchecked((long) 0x28E11014DA33E1B9L) /* 459 */, + unchecked((long) 0x270CC59E226AA213L) /* 460 */, unchecked((long) 0x71495F756D1A5F60L) /* 461 */, + unchecked((long) 0x9BE853FB60AFEF77L) /* 462 */, unchecked((long) 0xADC786A7F7443DBFL) /* 463 */, + unchecked((long) 0x0904456173B29A82L) /* 464 */, unchecked((long) 0x58BC7A66C232BD5EL) /* 465 */, + unchecked((long) 0xF306558C673AC8B2L) /* 466 */, unchecked((long) 0x41F639C6B6C9772AL) /* 467 */, + unchecked((long) 0x216DEFE99FDA35DAL) /* 468 */, unchecked((long) 0x11640CC71C7BE615L) /* 469 */, + unchecked((long) 0x93C43694565C5527L) /* 470 */, unchecked((long) 0xEA038E6246777839L) /* 471 */, + unchecked((long) 0xF9ABF3CE5A3E2469L) /* 472 */, unchecked((long) 0x741E768D0FD312D2L) /* 473 */, + unchecked((long) 0x0144B883CED652C6L) /* 474 */, unchecked((long) 0xC20B5A5BA33F8552L) /* 475 */, + unchecked((long) 0x1AE69633C3435A9DL) /* 476 */, unchecked((long) 0x97A28CA4088CFDECL) /* 477 */, + unchecked((long) 0x8824A43C1E96F420L) /* 478 */, unchecked((long) 0x37612FA66EEEA746L) /* 479 */, + unchecked((long) 0x6B4CB165F9CF0E5AL) /* 480 */, unchecked((long) 0x43AA1C06A0ABFB4AL) /* 481 */, + unchecked((long) 0x7F4DC26FF162796BL) /* 482 */, unchecked((long) 0x6CBACC8E54ED9B0FL) /* 483 */, + unchecked((long) 0xA6B7FFEFD2BB253EL) /* 484 */, unchecked((long) 0x2E25BC95B0A29D4FL) /* 485 */, + unchecked((long) 0x86D6A58BDEF1388CL) /* 486 */, unchecked((long) 0xDED74AC576B6F054L) /* 487 */, + unchecked((long) 0x8030BDBC2B45805DL) /* 488 */, unchecked((long) 0x3C81AF70E94D9289L) /* 489 */, + unchecked((long) 0x3EFF6DDA9E3100DBL) /* 490 */, unchecked((long) 0xB38DC39FDFCC8847L) /* 491 */, + unchecked((long) 0x123885528D17B87EL) /* 492 */, unchecked((long) 0xF2DA0ED240B1B642L) /* 493 */, + unchecked((long) 0x44CEFADCD54BF9A9L) /* 494 */, unchecked((long) 0x1312200E433C7EE6L) /* 495 */, + unchecked((long) 0x9FFCC84F3A78C748L) /* 496 */, unchecked((long) 0xF0CD1F72248576BBL) /* 497 */, + unchecked((long) 0xEC6974053638CFE4L) /* 498 */, unchecked((long) 0x2BA7B67C0CEC4E4CL) /* 499 */, + unchecked((long) 0xAC2F4DF3E5CE32EDL) /* 500 */, unchecked((long) 0xCB33D14326EA4C11L) /* 501 */, + unchecked((long) 0xA4E9044CC77E58BCL) /* 502 */, unchecked((long) 0x5F513293D934FCEFL) /* 503 */, + unchecked((long) 0x5DC9645506E55444L) /* 504 */, unchecked((long) 0x50DE418F317DE40AL) /* 505 */, + unchecked((long) 0x388CB31A69DDE259L) /* 506 */, unchecked((long) 0x2DB4A83455820A86L) /* 507 */, + unchecked((long) 0x9010A91E84711AE9L) /* 508 */, unchecked((long) 0x4DF7F0B7B1498371L) /* 509 */, + unchecked((long) 0xD62A2EABC0977179L) /* 510 */, unchecked((long) 0x22FAC097AA8D5C0EL) /* 511 */, + }; + + private static readonly long[] t3 = { + unchecked((long) 0xF49FCC2FF1DAF39BL) /* 512 */, unchecked((long) 0x487FD5C66FF29281L) /* 513 */, + unchecked((long) 0xE8A30667FCDCA83FL) /* 514 */, unchecked((long) 0x2C9B4BE3D2FCCE63L) /* 515 */, + unchecked((long) 0xDA3FF74B93FBBBC2L) /* 516 */, unchecked((long) 0x2FA165D2FE70BA66L) /* 517 */, + unchecked((long) 0xA103E279970E93D4L) /* 518 */, unchecked((long) 0xBECDEC77B0E45E71L) /* 519 */, + unchecked((long) 0xCFB41E723985E497L) /* 520 */, unchecked((long) 0xB70AAA025EF75017L) /* 521 */, + unchecked((long) 0xD42309F03840B8E0L) /* 522 */, unchecked((long) 0x8EFC1AD035898579L) /* 523 */, + unchecked((long) 0x96C6920BE2B2ABC5L) /* 524 */, unchecked((long) 0x66AF4163375A9172L) /* 525 */, + unchecked((long) 0x2174ABDCCA7127FBL) /* 526 */, unchecked((long) 0xB33CCEA64A72FF41L) /* 527 */, + unchecked((long) 0xF04A4933083066A5L) /* 528 */, unchecked((long) 0x8D970ACDD7289AF5L) /* 529 */, + unchecked((long) 0x8F96E8E031C8C25EL) /* 530 */, unchecked((long) 0xF3FEC02276875D47L) /* 531 */, + unchecked((long) 0xEC7BF310056190DDL) /* 532 */, unchecked((long) 0xF5ADB0AEBB0F1491L) /* 533 */, + unchecked((long) 0x9B50F8850FD58892L) /* 534 */, unchecked((long) 0x4975488358B74DE8L) /* 535 */, + unchecked((long) 0xA3354FF691531C61L) /* 536 */, unchecked((long) 0x0702BBE481D2C6EEL) /* 537 */, + unchecked((long) 0x89FB24057DEDED98L) /* 538 */, unchecked((long) 0xAC3075138596E902L) /* 539 */, + unchecked((long) 0x1D2D3580172772EDL) /* 540 */, unchecked((long) 0xEB738FC28E6BC30DL) /* 541 */, + unchecked((long) 0x5854EF8F63044326L) /* 542 */, unchecked((long) 0x9E5C52325ADD3BBEL) /* 543 */, + unchecked((long) 0x90AA53CF325C4623L) /* 544 */, unchecked((long) 0xC1D24D51349DD067L) /* 545 */, + unchecked((long) 0x2051CFEEA69EA624L) /* 546 */, unchecked((long) 0x13220F0A862E7E4FL) /* 547 */, + unchecked((long) 0xCE39399404E04864L) /* 548 */, unchecked((long) 0xD9C42CA47086FCB7L) /* 549 */, + unchecked((long) 0x685AD2238A03E7CCL) /* 550 */, unchecked((long) 0x066484B2AB2FF1DBL) /* 551 */, + unchecked((long) 0xFE9D5D70EFBF79ECL) /* 552 */, unchecked((long) 0x5B13B9DD9C481854L) /* 553 */, + unchecked((long) 0x15F0D475ED1509ADL) /* 554 */, unchecked((long) 0x0BEBCD060EC79851L) /* 555 */, + unchecked((long) 0xD58C6791183AB7F8L) /* 556 */, unchecked((long) 0xD1187C5052F3EEE4L) /* 557 */, + unchecked((long) 0xC95D1192E54E82FFL) /* 558 */, unchecked((long) 0x86EEA14CB9AC6CA2L) /* 559 */, + unchecked((long) 0x3485BEB153677D5DL) /* 560 */, unchecked((long) 0xDD191D781F8C492AL) /* 561 */, + unchecked((long) 0xF60866BAA784EBF9L) /* 562 */, unchecked((long) 0x518F643BA2D08C74L) /* 563 */, + unchecked((long) 0x8852E956E1087C22L) /* 564 */, unchecked((long) 0xA768CB8DC410AE8DL) /* 565 */, + unchecked((long) 0x38047726BFEC8E1AL) /* 566 */, unchecked((long) 0xA67738B4CD3B45AAL) /* 567 */, + unchecked((long) 0xAD16691CEC0DDE19L) /* 568 */, unchecked((long) 0xC6D4319380462E07L) /* 569 */, + unchecked((long) 0xC5A5876D0BA61938L) /* 570 */, unchecked((long) 0x16B9FA1FA58FD840L) /* 571 */, + unchecked((long) 0x188AB1173CA74F18L) /* 572 */, unchecked((long) 0xABDA2F98C99C021FL) /* 573 */, + unchecked((long) 0x3E0580AB134AE816L) /* 574 */, unchecked((long) 0x5F3B05B773645ABBL) /* 575 */, + unchecked((long) 0x2501A2BE5575F2F6L) /* 576 */, unchecked((long) 0x1B2F74004E7E8BA9L) /* 577 */, + unchecked((long) 0x1CD7580371E8D953L) /* 578 */, unchecked((long) 0x7F6ED89562764E30L) /* 579 */, + unchecked((long) 0xB15926FF596F003DL) /* 580 */, unchecked((long) 0x9F65293DA8C5D6B9L) /* 581 */, + unchecked((long) 0x6ECEF04DD690F84CL) /* 582 */, unchecked((long) 0x4782275FFF33AF88L) /* 583 */, + unchecked((long) 0xE41433083F820801L) /* 584 */, unchecked((long) 0xFD0DFE409A1AF9B5L) /* 585 */, + unchecked((long) 0x4325A3342CDB396BL) /* 586 */, unchecked((long) 0x8AE77E62B301B252L) /* 587 */, + unchecked((long) 0xC36F9E9F6655615AL) /* 588 */, unchecked((long) 0x85455A2D92D32C09L) /* 589 */, + unchecked((long) 0xF2C7DEA949477485L) /* 590 */, unchecked((long) 0x63CFB4C133A39EBAL) /* 591 */, + unchecked((long) 0x83B040CC6EBC5462L) /* 592 */, unchecked((long) 0x3B9454C8FDB326B0L) /* 593 */, + unchecked((long) 0x56F56A9E87FFD78CL) /* 594 */, unchecked((long) 0x2DC2940D99F42BC6L) /* 595 */, + unchecked((long) 0x98F7DF096B096E2DL) /* 596 */, unchecked((long) 0x19A6E01E3AD852BFL) /* 597 */, + unchecked((long) 0x42A99CCBDBD4B40BL) /* 598 */, unchecked((long) 0xA59998AF45E9C559L) /* 599 */, + unchecked((long) 0x366295E807D93186L) /* 600 */, unchecked((long) 0x6B48181BFAA1F773L) /* 601 */, + unchecked((long) 0x1FEC57E2157A0A1DL) /* 602 */, unchecked((long) 0x4667446AF6201AD5L) /* 603 */, + unchecked((long) 0xE615EBCACFB0F075L) /* 604 */, unchecked((long) 0xB8F31F4F68290778L) /* 605 */, + unchecked((long) 0x22713ED6CE22D11EL) /* 606 */, unchecked((long) 0x3057C1A72EC3C93BL) /* 607 */, + unchecked((long) 0xCB46ACC37C3F1F2FL) /* 608 */, unchecked((long) 0xDBB893FD02AAF50EL) /* 609 */, + unchecked((long) 0x331FD92E600B9FCFL) /* 610 */, unchecked((long) 0xA498F96148EA3AD6L) /* 611 */, + unchecked((long) 0xA8D8426E8B6A83EAL) /* 612 */, unchecked((long) 0xA089B274B7735CDCL) /* 613 */, + unchecked((long) 0x87F6B3731E524A11L) /* 614 */, unchecked((long) 0x118808E5CBC96749L) /* 615 */, + unchecked((long) 0x9906E4C7B19BD394L) /* 616 */, unchecked((long) 0xAFED7F7E9B24A20CL) /* 617 */, + unchecked((long) 0x6509EADEEB3644A7L) /* 618 */, unchecked((long) 0x6C1EF1D3E8EF0EDEL) /* 619 */, + unchecked((long) 0xB9C97D43E9798FB4L) /* 620 */, unchecked((long) 0xA2F2D784740C28A3L) /* 621 */, + unchecked((long) 0x7B8496476197566FL) /* 622 */, unchecked((long) 0x7A5BE3E6B65F069DL) /* 623 */, + unchecked((long) 0xF96330ED78BE6F10L) /* 624 */, unchecked((long) 0xEEE60DE77A076A15L) /* 625 */, + unchecked((long) 0x2B4BEE4AA08B9BD0L) /* 626 */, unchecked((long) 0x6A56A63EC7B8894EL) /* 627 */, + unchecked((long) 0x02121359BA34FEF4L) /* 628 */, unchecked((long) 0x4CBF99F8283703FCL) /* 629 */, + unchecked((long) 0x398071350CAF30C8L) /* 630 */, unchecked((long) 0xD0A77A89F017687AL) /* 631 */, + unchecked((long) 0xF1C1A9EB9E423569L) /* 632 */, unchecked((long) 0x8C7976282DEE8199L) /* 633 */, + unchecked((long) 0x5D1737A5DD1F7ABDL) /* 634 */, unchecked((long) 0x4F53433C09A9FA80L) /* 635 */, + unchecked((long) 0xFA8B0C53DF7CA1D9L) /* 636 */, unchecked((long) 0x3FD9DCBC886CCB77L) /* 637 */, + unchecked((long) 0xC040917CA91B4720L) /* 638 */, unchecked((long) 0x7DD00142F9D1DCDFL) /* 639 */, + unchecked((long) 0x8476FC1D4F387B58L) /* 640 */, unchecked((long) 0x23F8E7C5F3316503L) /* 641 */, + unchecked((long) 0x032A2244E7E37339L) /* 642 */, unchecked((long) 0x5C87A5D750F5A74BL) /* 643 */, + unchecked((long) 0x082B4CC43698992EL) /* 644 */, unchecked((long) 0xDF917BECB858F63CL) /* 645 */, + unchecked((long) 0x3270B8FC5BF86DDAL) /* 646 */, unchecked((long) 0x10AE72BB29B5DD76L) /* 647 */, + unchecked((long) 0x576AC94E7700362BL) /* 648 */, unchecked((long) 0x1AD112DAC61EFB8FL) /* 649 */, + unchecked((long) 0x691BC30EC5FAA427L) /* 650 */, unchecked((long) 0xFF246311CC327143L) /* 651 */, + unchecked((long) 0x3142368E30E53206L) /* 652 */, unchecked((long) 0x71380E31E02CA396L) /* 653 */, + unchecked((long) 0x958D5C960AAD76F1L) /* 654 */, unchecked((long) 0xF8D6F430C16DA536L) /* 655 */, + unchecked((long) 0xC8FFD13F1BE7E1D2L) /* 656 */, unchecked((long) 0x7578AE66004DDBE1L) /* 657 */, + unchecked((long) 0x05833F01067BE646L) /* 658 */, unchecked((long) 0xBB34B5AD3BFE586DL) /* 659 */, + unchecked((long) 0x095F34C9A12B97F0L) /* 660 */, unchecked((long) 0x247AB64525D60CA8L) /* 661 */, + unchecked((long) 0xDCDBC6F3017477D1L) /* 662 */, unchecked((long) 0x4A2E14D4DECAD24DL) /* 663 */, + unchecked((long) 0xBDB5E6D9BE0A1EEBL) /* 664 */, unchecked((long) 0x2A7E70F7794301ABL) /* 665 */, + unchecked((long) 0xDEF42D8A270540FDL) /* 666 */, unchecked((long) 0x01078EC0A34C22C1L) /* 667 */, + unchecked((long) 0xE5DE511AF4C16387L) /* 668 */, unchecked((long) 0x7EBB3A52BD9A330AL) /* 669 */, + unchecked((long) 0x77697857AA7D6435L) /* 670 */, unchecked((long) 0x004E831603AE4C32L) /* 671 */, + unchecked((long) 0xE7A21020AD78E312L) /* 672 */, unchecked((long) 0x9D41A70C6AB420F2L) /* 673 */, + unchecked((long) 0x28E06C18EA1141E6L) /* 674 */, unchecked((long) 0xD2B28CBD984F6B28L) /* 675 */, + unchecked((long) 0x26B75F6C446E9D83L) /* 676 */, unchecked((long) 0xBA47568C4D418D7FL) /* 677 */, + unchecked((long) 0xD80BADBFE6183D8EL) /* 678 */, unchecked((long) 0x0E206D7F5F166044L) /* 679 */, + unchecked((long) 0xE258A43911CBCA3EL) /* 680 */, unchecked((long) 0x723A1746B21DC0BCL) /* 681 */, + unchecked((long) 0xC7CAA854F5D7CDD3L) /* 682 */, unchecked((long) 0x7CAC32883D261D9CL) /* 683 */, + unchecked((long) 0x7690C26423BA942CL) /* 684 */, unchecked((long) 0x17E55524478042B8L) /* 685 */, + unchecked((long) 0xE0BE477656A2389FL) /* 686 */, unchecked((long) 0x4D289B5E67AB2DA0L) /* 687 */, + unchecked((long) 0x44862B9C8FBBFD31L) /* 688 */, unchecked((long) 0xB47CC8049D141365L) /* 689 */, + unchecked((long) 0x822C1B362B91C793L) /* 690 */, unchecked((long) 0x4EB14655FB13DFD8L) /* 691 */, + unchecked((long) 0x1ECBBA0714E2A97BL) /* 692 */, unchecked((long) 0x6143459D5CDE5F14L) /* 693 */, + unchecked((long) 0x53A8FBF1D5F0AC89L) /* 694 */, unchecked((long) 0x97EA04D81C5E5B00L) /* 695 */, + unchecked((long) 0x622181A8D4FDB3F3L) /* 696 */, unchecked((long) 0xE9BCD341572A1208L) /* 697 */, + unchecked((long) 0x1411258643CCE58AL) /* 698 */, unchecked((long) 0x9144C5FEA4C6E0A4L) /* 699 */, + unchecked((long) 0x0D33D06565CF620FL) /* 700 */, unchecked((long) 0x54A48D489F219CA1L) /* 701 */, + unchecked((long) 0xC43E5EAC6D63C821L) /* 702 */, unchecked((long) 0xA9728B3A72770DAFL) /* 703 */, + unchecked((long) 0xD7934E7B20DF87EFL) /* 704 */, unchecked((long) 0xE35503B61A3E86E5L) /* 705 */, + unchecked((long) 0xCAE321FBC819D504L) /* 706 */, unchecked((long) 0x129A50B3AC60BFA6L) /* 707 */, + unchecked((long) 0xCD5E68EA7E9FB6C3L) /* 708 */, unchecked((long) 0xB01C90199483B1C7L) /* 709 */, + unchecked((long) 0x3DE93CD5C295376CL) /* 710 */, unchecked((long) 0xAED52EDF2AB9AD13L) /* 711 */, + unchecked((long) 0x2E60F512C0A07884L) /* 712 */, unchecked((long) 0xBC3D86A3E36210C9L) /* 713 */, + unchecked((long) 0x35269D9B163951CEL) /* 714 */, unchecked((long) 0x0C7D6E2AD0CDB5FAL) /* 715 */, + unchecked((long) 0x59E86297D87F5733L) /* 716 */, unchecked((long) 0x298EF221898DB0E7L) /* 717 */, + unchecked((long) 0x55000029D1A5AA7EL) /* 718 */, unchecked((long) 0x8BC08AE1B5061B45L) /* 719 */, + unchecked((long) 0xC2C31C2B6C92703AL) /* 720 */, unchecked((long) 0x94CC596BAF25EF42L) /* 721 */, + unchecked((long) 0x0A1D73DB22540456L) /* 722 */, unchecked((long) 0x04B6A0F9D9C4179AL) /* 723 */, + unchecked((long) 0xEFFDAFA2AE3D3C60L) /* 724 */, unchecked((long) 0xF7C8075BB49496C4L) /* 725 */, + unchecked((long) 0x9CC5C7141D1CD4E3L) /* 726 */, unchecked((long) 0x78BD1638218E5534L) /* 727 */, + unchecked((long) 0xB2F11568F850246AL) /* 728 */, unchecked((long) 0xEDFABCFA9502BC29L) /* 729 */, + unchecked((long) 0x796CE5F2DA23051BL) /* 730 */, unchecked((long) 0xAAE128B0DC93537CL) /* 731 */, + unchecked((long) 0x3A493DA0EE4B29AEL) /* 732 */, unchecked((long) 0xB5DF6B2C416895D7L) /* 733 */, + unchecked((long) 0xFCABBD25122D7F37L) /* 734 */, unchecked((long) 0x70810B58105DC4B1L) /* 735 */, + unchecked((long) 0xE10FDD37F7882A90L) /* 736 */, unchecked((long) 0x524DCAB5518A3F5CL) /* 737 */, + unchecked((long) 0x3C9E85878451255BL) /* 738 */, unchecked((long) 0x4029828119BD34E2L) /* 739 */, + unchecked((long) 0x74A05B6F5D3CECCBL) /* 740 */, unchecked((long) 0xB610021542E13ECAL) /* 741 */, + unchecked((long) 0x0FF979D12F59E2ACL) /* 742 */, unchecked((long) 0x6037DA27E4F9CC50L) /* 743 */, + unchecked((long) 0x5E92975A0DF1847DL) /* 744 */, unchecked((long) 0xD66DE190D3E623FEL) /* 745 */, + unchecked((long) 0x5032D6B87B568048L) /* 746 */, unchecked((long) 0x9A36B7CE8235216EL) /* 747 */, + unchecked((long) 0x80272A7A24F64B4AL) /* 748 */, unchecked((long) 0x93EFED8B8C6916F7L) /* 749 */, + unchecked((long) 0x37DDBFF44CCE1555L) /* 750 */, unchecked((long) 0x4B95DB5D4B99BD25L) /* 751 */, + unchecked((long) 0x92D3FDA169812FC0L) /* 752 */, unchecked((long) 0xFB1A4A9A90660BB6L) /* 753 */, + unchecked((long) 0x730C196946A4B9B2L) /* 754 */, unchecked((long) 0x81E289AA7F49DA68L) /* 755 */, + unchecked((long) 0x64669A0F83B1A05FL) /* 756 */, unchecked((long) 0x27B3FF7D9644F48BL) /* 757 */, + unchecked((long) 0xCC6B615C8DB675B3L) /* 758 */, unchecked((long) 0x674F20B9BCEBBE95L) /* 759 */, + unchecked((long) 0x6F31238275655982L) /* 760 */, unchecked((long) 0x5AE488713E45CF05L) /* 761 */, + unchecked((long) 0xBF619F9954C21157L) /* 762 */, unchecked((long) 0xEABAC46040A8EAE9L) /* 763 */, + unchecked((long) 0x454C6FE9F2C0C1CDL) /* 764 */, unchecked((long) 0x419CF6496412691CL) /* 765 */, + unchecked((long) 0xD3DC3BEF265B0F70L) /* 766 */, unchecked((long) 0x6D0E60F5C3578A9EL) /* 767 */, + }; + + private static readonly long[] t4 = { + unchecked((long) 0x5B0E608526323C55L) /* 768 */, unchecked((long) 0x1A46C1A9FA1B59F5L) /* 769 */, + unchecked((long) 0xA9E245A17C4C8FFAL) /* 770 */, unchecked((long) 0x65CA5159DB2955D7L) /* 771 */, + unchecked((long) 0x05DB0A76CE35AFC2L) /* 772 */, unchecked((long) 0x81EAC77EA9113D45L) /* 773 */, + unchecked((long) 0x528EF88AB6AC0A0DL) /* 774 */, unchecked((long) 0xA09EA253597BE3FFL) /* 775 */, + unchecked((long) 0x430DDFB3AC48CD56L) /* 776 */, unchecked((long) 0xC4B3A67AF45CE46FL) /* 777 */, + unchecked((long) 0x4ECECFD8FBE2D05EL) /* 778 */, unchecked((long) 0x3EF56F10B39935F0L) /* 779 */, + unchecked((long) 0x0B22D6829CD619C6L) /* 780 */, unchecked((long) 0x17FD460A74DF2069L) /* 781 */, + unchecked((long) 0x6CF8CC8E8510ED40L) /* 782 */, unchecked((long) 0xD6C824BF3A6ECAA7L) /* 783 */, + unchecked((long) 0x61243D581A817049L) /* 784 */, unchecked((long) 0x048BACB6BBC163A2L) /* 785 */, + unchecked((long) 0xD9A38AC27D44CC32L) /* 786 */, unchecked((long) 0x7FDDFF5BAAF410ABL) /* 787 */, + unchecked((long) 0xAD6D495AA804824BL) /* 788 */, unchecked((long) 0xE1A6A74F2D8C9F94L) /* 789 */, + unchecked((long) 0xD4F7851235DEE8E3L) /* 790 */, unchecked((long) 0xFD4B7F886540D893L) /* 791 */, + unchecked((long) 0x247C20042AA4BFDAL) /* 792 */, unchecked((long) 0x096EA1C517D1327CL) /* 793 */, + unchecked((long) 0xD56966B4361A6685L) /* 794 */, unchecked((long) 0x277DA5C31221057DL) /* 795 */, + unchecked((long) 0x94D59893A43ACFF7L) /* 796 */, unchecked((long) 0x64F0C51CCDC02281L) /* 797 */, + unchecked((long) 0x3D33BCC4FF6189DBL) /* 798 */, unchecked((long) 0xE005CB184CE66AF1L) /* 799 */, + unchecked((long) 0xFF5CCD1D1DB99BEAL) /* 800 */, unchecked((long) 0xB0B854A7FE42980FL) /* 801 */, + unchecked((long) 0x7BD46A6A718D4B9FL) /* 802 */, unchecked((long) 0xD10FA8CC22A5FD8CL) /* 803 */, + unchecked((long) 0xD31484952BE4BD31L) /* 804 */, unchecked((long) 0xC7FA975FCB243847L) /* 805 */, + unchecked((long) 0x4886ED1E5846C407L) /* 806 */, unchecked((long) 0x28CDDB791EB70B04L) /* 807 */, + unchecked((long) 0xC2B00BE2F573417FL) /* 808 */, unchecked((long) 0x5C9590452180F877L) /* 809 */, + unchecked((long) 0x7A6BDDFFF370EB00L) /* 810 */, unchecked((long) 0xCE509E38D6D9D6A4L) /* 811 */, + unchecked((long) 0xEBEB0F00647FA702L) /* 812 */, unchecked((long) 0x1DCC06CF76606F06L) /* 813 */, + unchecked((long) 0xE4D9F28BA286FF0AL) /* 814 */, unchecked((long) 0xD85A305DC918C262L) /* 815 */, + unchecked((long) 0x475B1D8732225F54L) /* 816 */, unchecked((long) 0x2D4FB51668CCB5FEL) /* 817 */, + unchecked((long) 0xA679B9D9D72BBA20L) /* 818 */, unchecked((long) 0x53841C0D912D43A5L) /* 819 */, + unchecked((long) 0x3B7EAA48BF12A4E8L) /* 820 */, unchecked((long) 0x781E0E47F22F1DDFL) /* 821 */, + unchecked((long) 0xEFF20CE60AB50973L) /* 822 */, unchecked((long) 0x20D261D19DFFB742L) /* 823 */, + unchecked((long) 0x16A12B03062A2E39L) /* 824 */, unchecked((long) 0x1960EB2239650495L) /* 825 */, + unchecked((long) 0x251C16FED50EB8B8L) /* 826 */, unchecked((long) 0x9AC0C330F826016EL) /* 827 */, + unchecked((long) 0xED152665953E7671L) /* 828 */, unchecked((long) 0x02D63194A6369570L) /* 829 */, + unchecked((long) 0x5074F08394B1C987L) /* 830 */, unchecked((long) 0x70BA598C90B25CE1L) /* 831 */, + unchecked((long) 0x794A15810B9742F6L) /* 832 */, unchecked((long) 0x0D5925E9FCAF8C6CL) /* 833 */, + unchecked((long) 0x3067716CD868744EL) /* 834 */, unchecked((long) 0x910AB077E8D7731BL) /* 835 */, + unchecked((long) 0x6A61BBDB5AC42F61L) /* 836 */, unchecked((long) 0x93513EFBF0851567L) /* 837 */, + unchecked((long) 0xF494724B9E83E9D5L) /* 838 */, unchecked((long) 0xE887E1985C09648DL) /* 839 */, + unchecked((long) 0x34B1D3C675370CFDL) /* 840 */, unchecked((long) 0xDC35E433BC0D255DL) /* 841 */, + unchecked((long) 0xD0AAB84234131BE0L) /* 842 */, unchecked((long) 0x08042A50B48B7EAFL) /* 843 */, + unchecked((long) 0x9997C4EE44A3AB35L) /* 844 */, unchecked((long) 0x829A7B49201799D0L) /* 845 */, + unchecked((long) 0x263B8307B7C54441L) /* 846 */, unchecked((long) 0x752F95F4FD6A6CA6L) /* 847 */, + unchecked((long) 0x927217402C08C6E5L) /* 848 */, unchecked((long) 0x2A8AB754A795D9EEL) /* 849 */, + unchecked((long) 0xA442F7552F72943DL) /* 850 */, unchecked((long) 0x2C31334E19781208L) /* 851 */, + unchecked((long) 0x4FA98D7CEAEE6291L) /* 852 */, unchecked((long) 0x55C3862F665DB309L) /* 853 */, + unchecked((long) 0xBD0610175D53B1F3L) /* 854 */, unchecked((long) 0x46FE6CB840413F27L) /* 855 */, + unchecked((long) 0x3FE03792DF0CFA59L) /* 856 */, unchecked((long) 0xCFE700372EB85E8FL) /* 857 */, + unchecked((long) 0xA7BE29E7ADBCE118L) /* 858 */, unchecked((long) 0xE544EE5CDE8431DDL) /* 859 */, + unchecked((long) 0x8A781B1B41F1873EL) /* 860 */, unchecked((long) 0xA5C94C78A0D2F0E7L) /* 861 */, + unchecked((long) 0x39412E2877B60728L) /* 862 */, unchecked((long) 0xA1265EF3AFC9A62CL) /* 863 */, + unchecked((long) 0xBCC2770C6A2506C5L) /* 864 */, unchecked((long) 0x3AB66DD5DCE1CE12L) /* 865 */, + unchecked((long) 0xE65499D04A675B37L) /* 866 */, unchecked((long) 0x7D8F523481BFD216L) /* 867 */, + unchecked((long) 0x0F6F64FCEC15F389L) /* 868 */, unchecked((long) 0x74EFBE618B5B13C8L) /* 869 */, + unchecked((long) 0xACDC82B714273E1DL) /* 870 */, unchecked((long) 0xDD40BFE003199D17L) /* 871 */, + unchecked((long) 0x37E99257E7E061F8L) /* 872 */, unchecked((long) 0xFA52626904775AAAL) /* 873 */, + unchecked((long) 0x8BBBF63A463D56F9L) /* 874 */, unchecked((long) 0xF0013F1543A26E64L) /* 875 */, + unchecked((long) 0xA8307E9F879EC898L) /* 876 */, unchecked((long) 0xCC4C27A4150177CCL) /* 877 */, + unchecked((long) 0x1B432F2CCA1D3348L) /* 878 */, unchecked((long) 0xDE1D1F8F9F6FA013L) /* 879 */, + unchecked((long) 0x606602A047A7DDD6L) /* 880 */, unchecked((long) 0xD237AB64CC1CB2C7L) /* 881 */, + unchecked((long) 0x9B938E7225FCD1D3L) /* 882 */, unchecked((long) 0xEC4E03708E0FF476L) /* 883 */, + unchecked((long) 0xFEB2FBDA3D03C12DL) /* 884 */, unchecked((long) 0xAE0BCED2EE43889AL) /* 885 */, + unchecked((long) 0x22CB8923EBFB4F43L) /* 886 */, unchecked((long) 0x69360D013CF7396DL) /* 887 */, + unchecked((long) 0x855E3602D2D4E022L) /* 888 */, unchecked((long) 0x073805BAD01F784CL) /* 889 */, + unchecked((long) 0x33E17A133852F546L) /* 890 */, unchecked((long) 0xDF4874058AC7B638L) /* 891 */, + unchecked((long) 0xBA92B29C678AA14AL) /* 892 */, unchecked((long) 0x0CE89FC76CFAADCDL) /* 893 */, + unchecked((long) 0x5F9D4E0908339E34L) /* 894 */, unchecked((long) 0xF1AFE9291F5923B9L) /* 895 */, + unchecked((long) 0x6E3480F60F4A265FL) /* 896 */, unchecked((long) 0xEEBF3A2AB29B841CL) /* 897 */, + unchecked((long) 0xE21938A88F91B4ADL) /* 898 */, unchecked((long) 0x57DFEFF845C6D3C3L) /* 899 */, + unchecked((long) 0x2F006B0BF62CAAF2L) /* 900 */, unchecked((long) 0x62F479EF6F75EE78L) /* 901 */, + unchecked((long) 0x11A55AD41C8916A9L) /* 902 */, unchecked((long) 0xF229D29084FED453L) /* 903 */, + unchecked((long) 0x42F1C27B16B000E6L) /* 904 */, unchecked((long) 0x2B1F76749823C074L) /* 905 */, + unchecked((long) 0x4B76ECA3C2745360L) /* 906 */, unchecked((long) 0x8C98F463B91691BDL) /* 907 */, + unchecked((long) 0x14BCC93CF1ADE66AL) /* 908 */, unchecked((long) 0x8885213E6D458397L) /* 909 */, + unchecked((long) 0x8E177DF0274D4711L) /* 910 */, unchecked((long) 0xB49B73B5503F2951L) /* 911 */, + unchecked((long) 0x10168168C3F96B6BL) /* 912 */, unchecked((long) 0x0E3D963B63CAB0AEL) /* 913 */, + unchecked((long) 0x8DFC4B5655A1DB14L) /* 914 */, unchecked((long) 0xF789F1356E14DE5CL) /* 915 */, + unchecked((long) 0x683E68AF4E51DAC1L) /* 916 */, unchecked((long) 0xC9A84F9D8D4B0FD9L) /* 917 */, + unchecked((long) 0x3691E03F52A0F9D1L) /* 918 */, unchecked((long) 0x5ED86E46E1878E80L) /* 919 */, + unchecked((long) 0x3C711A0E99D07150L) /* 920 */, unchecked((long) 0x5A0865B20C4E9310L) /* 921 */, + unchecked((long) 0x56FBFC1FE4F0682EL) /* 922 */, unchecked((long) 0xEA8D5DE3105EDF9BL) /* 923 */, + unchecked((long) 0x71ABFDB12379187AL) /* 924 */, unchecked((long) 0x2EB99DE1BEE77B9CL) /* 925 */, + unchecked((long) 0x21ECC0EA33CF4523L) /* 926 */, unchecked((long) 0x59A4D7521805C7A1L) /* 927 */, + unchecked((long) 0x3896F5EB56AE7C72L) /* 928 */, unchecked((long) 0xAA638F3DB18F75DCL) /* 929 */, + unchecked((long) 0x9F39358DABE9808EL) /* 930 */, unchecked((long) 0xB7DEFA91C00B72ACL) /* 931 */, + unchecked((long) 0x6B5541FD62492D92L) /* 932 */, unchecked((long) 0x6DC6DEE8F92E4D5BL) /* 933 */, + unchecked((long) 0x353F57ABC4BEEA7EL) /* 934 */, unchecked((long) 0x735769D6DA5690CEL) /* 935 */, + unchecked((long) 0x0A234AA642391484L) /* 936 */, unchecked((long) 0xF6F9508028F80D9DL) /* 937 */, + unchecked((long) 0xB8E319A27AB3F215L) /* 938 */, unchecked((long) 0x31AD9C1151341A4DL) /* 939 */, + unchecked((long) 0x773C22A57BEF5805L) /* 940 */, unchecked((long) 0x45C7561A07968633L) /* 941 */, + unchecked((long) 0xF913DA9E249DBE36L) /* 942 */, unchecked((long) 0xDA652D9B78A64C68L) /* 943 */, + unchecked((long) 0x4C27A97F3BC334EFL) /* 944 */, unchecked((long) 0x76621220E66B17F4L) /* 945 */, + unchecked((long) 0x967743899ACD7D0BL) /* 946 */, unchecked((long) 0xF3EE5BCAE0ED6782L) /* 947 */, + unchecked((long) 0x409F753600C879FCL) /* 948 */, unchecked((long) 0x06D09A39B5926DB6L) /* 949 */, + unchecked((long) 0x6F83AEB0317AC588L) /* 950 */, unchecked((long) 0x01E6CA4A86381F21L) /* 951 */, + unchecked((long) 0x66FF3462D19F3025L) /* 952 */, unchecked((long) 0x72207C24DDFD3BFBL) /* 953 */, + unchecked((long) 0x4AF6B6D3E2ECE2EBL) /* 954 */, unchecked((long) 0x9C994DBEC7EA08DEL) /* 955 */, + unchecked((long) 0x49ACE597B09A8BC4L) /* 956 */, unchecked((long) 0xB38C4766CF0797BAL) /* 957 */, + unchecked((long) 0x131B9373C57C2A75L) /* 958 */, unchecked((long) 0xB1822CCE61931E58L) /* 959 */, + unchecked((long) 0x9D7555B909BA1C0CL) /* 960 */, unchecked((long) 0x127FAFDD937D11D2L) /* 961 */, + unchecked((long) 0x29DA3BADC66D92E4L) /* 962 */, unchecked((long) 0xA2C1D57154C2ECBCL) /* 963 */, + unchecked((long) 0x58C5134D82F6FE24L) /* 964 */, unchecked((long) 0x1C3AE3515B62274FL) /* 965 */, + unchecked((long) 0xE907C82E01CB8126L) /* 966 */, unchecked((long) 0xF8ED091913E37FCBL) /* 967 */, + unchecked((long) 0x3249D8F9C80046C9L) /* 968 */, unchecked((long) 0x80CF9BEDE388FB63L) /* 969 */, + unchecked((long) 0x1881539A116CF19EL) /* 970 */, unchecked((long) 0x5103F3F76BD52457L) /* 971 */, + unchecked((long) 0x15B7E6F5AE47F7A8L) /* 972 */, unchecked((long) 0xDBD7C6DED47E9CCFL) /* 973 */, + unchecked((long) 0x44E55C410228BB1AL) /* 974 */, unchecked((long) 0xB647D4255EDB4E99L) /* 975 */, + unchecked((long) 0x5D11882BB8AAFC30L) /* 976 */, unchecked((long) 0xF5098BBB29D3212AL) /* 977 */, + unchecked((long) 0x8FB5EA14E90296B3L) /* 978 */, unchecked((long) 0x677B942157DD025AL) /* 979 */, + unchecked((long) 0xFB58E7C0A390ACB5L) /* 980 */, unchecked((long) 0x89D3674C83BD4A01L) /* 981 */, + unchecked((long) 0x9E2DA4DF4BF3B93BL) /* 982 */, unchecked((long) 0xFCC41E328CAB4829L) /* 983 */, + unchecked((long) 0x03F38C96BA582C52L) /* 984 */, unchecked((long) 0xCAD1BDBD7FD85DB2L) /* 985 */, + unchecked((long) 0xBBB442C16082AE83L) /* 986 */, unchecked((long) 0xB95FE86BA5DA9AB0L) /* 987 */, + unchecked((long) 0xB22E04673771A93FL) /* 988 */, unchecked((long) 0x845358C9493152D8L) /* 989 */, + unchecked((long) 0xBE2A488697B4541EL) /* 990 */, unchecked((long) 0x95A2DC2DD38E6966L) /* 991 */, + unchecked((long) 0xC02C11AC923C852BL) /* 992 */, unchecked((long) 0x2388B1990DF2A87BL) /* 993 */, + unchecked((long) 0x7C8008FA1B4F37BEL) /* 994 */, unchecked((long) 0x1F70D0C84D54E503L) /* 995 */, + unchecked((long) 0x5490ADEC7ECE57D4L) /* 996 */, unchecked((long) 0x002B3C27D9063A3AL) /* 997 */, + unchecked((long) 0x7EAEA3848030A2BFL) /* 998 */, unchecked((long) 0xC602326DED2003C0L) /* 999 */, + unchecked((long) 0x83A7287D69A94086L) /* 1000 */, unchecked((long) 0xC57A5FCB30F57A8AL) /* 1001 */, + unchecked((long) 0xB56844E479EBE779L) /* 1002 */, unchecked((long) 0xA373B40F05DCBCE9L) /* 1003 */, + unchecked((long) 0xD71A786E88570EE2L) /* 1004 */, unchecked((long) 0x879CBACDBDE8F6A0L) /* 1005 */, + unchecked((long) 0x976AD1BCC164A32FL) /* 1006 */, unchecked((long) 0xAB21E25E9666D78BL) /* 1007 */, + unchecked((long) 0x901063AAE5E5C33CL) /* 1008 */, unchecked((long) 0x9818B34448698D90L) /* 1009 */, + unchecked((long) 0xE36487AE3E1E8ABBL) /* 1010 */, unchecked((long) 0xAFBDF931893BDCB4L) /* 1011 */, + unchecked((long) 0x6345A0DC5FBBD519L) /* 1012 */, unchecked((long) 0x8628FE269B9465CAL) /* 1013 */, + unchecked((long) 0x1E5D01603F9C51ECL) /* 1014 */, unchecked((long) 0x4DE44006A15049B7L) /* 1015 */, + unchecked((long) 0xBF6C70E5F776CBB1L) /* 1016 */, unchecked((long) 0x411218F2EF552BEDL) /* 1017 */, + unchecked((long) 0xCB0C0708705A36A3L) /* 1018 */, unchecked((long) 0xE74D14754F986044L) /* 1019 */, + unchecked((long) 0xCD56D9430EA8280EL) /* 1020 */, unchecked((long) 0xC12591D7535F5065L) /* 1021 */, + unchecked((long) 0xC83223F1720AEF96L) /* 1022 */, unchecked((long) 0xC3A0396F7363A51FL) /* 1023 */ + }; + + private const int DigestLength = 24; + + // + // registers + // + private long a, b, c; + private long byteCount; + + // + // buffers + // + private byte[] Buffer = new byte[8]; + private int bOff; + + private long[] x = new long[8]; + private int xOff; + + /** + * Standard constructor + */ + public TigerDigest() + { + Reset(); + } + + /** + * Copy constructor. This will copy the state of the provided + * message digest. + */ + public TigerDigest(TigerDigest t) + { + Reset(t); + } + + public string AlgorithmName + { + get { return "Tiger"; } + } + + public int GetDigestSize() + { + return DigestLength; + } + + public int GetByteLength() + { + return MyByteLength; + } + + 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) + | ((uint)(b[off + 0] & 0xff)); + + if (xOff == x.Length) + { + ProcessBlock(); + } + + bOff = 0; + } + + public void Update( + byte input) + { + Buffer[bOff++] = input; + + if (bOff == Buffer.Length) + { + ProcessWord(Buffer, 0); + } + + byteCount++; + } + + public void BlockUpdate( + byte[] input, + int inOff, + int length) + { + // + // fill the current word + // + while ((bOff != 0) && (length > 0)) + { + Update(input[inOff]); + + inOff++; + length--; + } + + // + // process whole words. + // + while (length > 8) + { + ProcessWord(input, inOff); + + inOff += 8; + length -= 8; + byteCount += 8; + } + + // + // load in the remainder. + // + while (length > 0) + { + Update(input[inOff]); + + inOff++; + length--; + } + } + + 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] ^ unchecked ((long) 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] ^ (long) ((ulong) (~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] ^ (long) ((ulong) (~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; + } + } + + private void UnpackWord( + long r, + byte[] output, + int outOff) + { + output[outOff + 7] = (byte)(r >> 56); + output[outOff + 6] = (byte)(r >> 48); + output[outOff + 5] = (byte)(r >> 40); + output[outOff + 4] = (byte)(r >> 32); + output[outOff + 3] = (byte)(r >> 24); + output[outOff + 2] = (byte)(r >> 16); + output[outOff + 1] = (byte)(r >> 8); + output[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[] output, + int outOff) + { + Finish(); + + UnpackWord(a, output, outOff); + UnpackWord(b, output, outOff + 8); + UnpackWord(c, output, outOff + 16); + + Reset(); + + return DigestLength; + } + + /** + * reset the chaining variables + */ + public void Reset() + { + a = unchecked((long) 0x0123456789ABCDEFL); + b = unchecked((long) 0xFEDCBA9876543210L); + c = unchecked((long) 0xF096A5B4C3B2E187L); + + xOff = 0; + for (int i = 0; i != x.Length; i++) + { + x[i] = 0; + } + + bOff = 0; + for (int i = 0; i != Buffer.Length; i++) + { + Buffer[i] = 0; + } + + byteCount = 0; + } + + public IMemoable Copy() + { + return new TigerDigest(this); + } + + public void Reset(IMemoable other) + { + TigerDigest t = (TigerDigest)other; + + a = t.a; + b = t.b; + c = t.c; + + Array.Copy(t.x, 0, x, 0, t.x.Length); + xOff = t.xOff; + + Array.Copy(t.Buffer, 0, Buffer, 0, t.Buffer.Length); + bOff = t.bOff; + + byteCount = t.byteCount; + } + + } +} diff --git a/bc-sharp-crypto/src/crypto/digests/WhirlpoolDigest.cs b/bc-sharp-crypto/src/crypto/digests/WhirlpoolDigest.cs new file mode 100644 index 0000000..55b7120 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/digests/WhirlpoolDigest.cs @@ -0,0 +1,413 @@ +using System; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Digests +{ + /** + * Implementation of WhirlpoolDigest, based on Java source published by Barreto + * and Rijmen. + * + */ + public sealed class WhirlpoolDigest + : IDigest, IMemoable + { + private const int BYTE_LENGTH = 64; + + private const int DIGEST_LENGTH_BYTES = 512 / 8; + private const int ROUNDS = 10; + private const int REDUCTION_POLYNOMIAL = 0x011d; // 2^8 + 2^4 + 2^3 + 2 + 1; + + private static readonly 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 readonly long[] C0 = new long[256]; + private static readonly long[] C1 = new long[256]; + private static readonly long[] C2 = new long[256]; + private static readonly long[] C3 = new long[256]; + private static readonly long[] C4 = new long[256]; + private static readonly long[] C5 = new long[256]; + private static readonly long[] C6 = new long[256]; + private static readonly long[] C7 = new long[256]; + + private readonly long[] _rc = new long[ROUNDS + 1]; + + /* + * 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 readonly short[] EIGHT = new short[BITCOUNT_ARRAY_SIZE]; + + static WhirlpoolDigest() + { + EIGHT[BITCOUNT_ARRAY_SIZE - 1] = 8; + + 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); + } + } + + public WhirlpoolDigest() + { + _rc[0] = 0L; + for (int r = 1; r <= ROUNDS; r++) + { + int i = 8 * (r - 1); + _rc[r] = (long)((ulong)C0[i] & 0xff00000000000000L) ^ + (C1[i + 1] & (long) 0x00ff000000000000L) ^ + (C2[i + 2] & (long) 0x0000ff0000000000L) ^ + (C3[i + 3] & (long) 0x000000ff00000000L) ^ + (C4[i + 4] & (long) 0x00000000ff000000L) ^ + (C5[i + 5] & (long) 0x0000000000ff0000L) ^ + (C6[i + 6] & (long) 0x000000000000ff00L) ^ + (C7[i + 7] & (long) 0x00000000000000ffL); + } + } + + private static 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 static 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 const int BITCOUNT_ARRAY_SIZE = 32; + private byte[] _buffer = new byte[64]; + private int _bufferPos; + 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) + { + Reset(originalDigest); + } + + public string AlgorithmName + { + get { return "Whirlpool"; } + } + + public int GetDigestSize() + { + return DIGEST_LENGTH_BYTES; + } + + public int DoFinal(byte[] output, int outOff) + { + // sets output[outOff] .. output[outOff+DIGEST_LENGTH_BYTES] + finish(); + + for (int i = 0; i < 8; i++) + { + convertLongToByteArray(_hash[i], output, outOff + (i * 8)); + } + + Reset(); + + return GetDigestSize(); + } + + /** + * Reset the chaining variables + */ + public void Reset() + { + // set variables to null, blank, whatever + _bufferPos = 0; + Array.Clear(_bitCount, 0, _bitCount.Length); + Array.Clear(_buffer, 0, _buffer.Length); + Array.Clear(_hash, 0, _hash.Length); + Array.Clear(_K, 0, _K.Length); + Array.Clear(_L, 0, _L.Length); + Array.Clear(_block, 0, _block.Length); + Array.Clear(_state, 0, _state.Length); + } + + // this takes a buffer of information and fills the block + private void processFilledBuffer() + { + // copies into the block... + for (int i = 0; i < _state.Length; i++) + { + _block[i] = bytesToLongFromBuffer(_buffer, i * 8); + } + processBlock(); + _bufferPos = 0; + Array.Clear(_buffer, 0, _buffer.Length); + } + + private static 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 static void convertLongToByteArray(long inputLong, byte[] outputArray, int offSet) + { + for (int i = 0; i < 8; i++) + { + outputArray[offSet + i] = (byte)((inputLong >> (56 - (i * 8))) & 0xff); + } + } + + private 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]; + } + + Array.Copy(_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 + Array.Copy(_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 input) + { + _buffer[_bufferPos] = input; + + //Console.WriteLine("adding to buffer = "+_buffer[_bufferPos]); + + ++_bufferPos; + + if (_bufferPos == _buffer.Length) + { + processFilledBuffer(); + } + + increment(); + } + + 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 BlockUpdate(byte[] input, int inOff, int length) + { + while (length > 0) + { + Update(input[inOff]); + ++inOff; + --length; + } + + } + + 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(); + } + + /* + * 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.... + Array.Copy(bitLength, 0, _buffer, 32, bitLength.Length); + + processFilledBuffer(); + } + + 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; + } + + public IMemoable Copy() + { + return new WhirlpoolDigest(this); + } + + public void Reset(IMemoable other) + { + WhirlpoolDigest originalDigest = (WhirlpoolDigest)other; + + Array.Copy(originalDigest._rc, 0, _rc, 0, _rc.Length); + + Array.Copy(originalDigest._buffer, 0, _buffer, 0, _buffer.Length); + + this._bufferPos = originalDigest._bufferPos; + Array.Copy(originalDigest._bitCount, 0, _bitCount, 0, _bitCount.Length); + + // -- internal hash state -- + Array.Copy(originalDigest._hash, 0, _hash, 0, _hash.Length); + Array.Copy(originalDigest._K, 0, _K, 0, _K.Length); + Array.Copy(originalDigest._L, 0, _L, 0, _L.Length); + Array.Copy(originalDigest._block, 0, _block, 0, _block.Length); + Array.Copy(originalDigest._state, 0, _state, 0, _state.Length); + } + + + } +} diff --git a/bc-sharp-crypto/src/crypto/ec/CustomNamedCurves.cs b/bc-sharp-crypto/src/crypto/ec/CustomNamedCurves.cs new file mode 100644 index 0000000..4b7600e --- /dev/null +++ b/bc-sharp-crypto/src/crypto/ec/CustomNamedCurves.cs @@ -0,0 +1,913 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.GM; +using Org.BouncyCastle.Asn1.Sec; +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Math.EC; +using Org.BouncyCastle.Math.EC.Custom.Djb; +using Org.BouncyCastle.Math.EC.Custom.GM; +using Org.BouncyCastle.Math.EC.Custom.Sec; +using Org.BouncyCastle.Math.EC.Endo; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Crypto.EC +{ + public sealed class CustomNamedCurves + { + private CustomNamedCurves() + { + } + + private static BigInteger FromHex(string hex) + { + return new BigInteger(1, Hex.Decode(hex)); + } + + private static ECCurve ConfigureCurve(ECCurve curve) + { + return curve; + } + + private static ECCurve ConfigureCurveGlv(ECCurve c, GlvTypeBParameters p) + { + return c.Configure().SetEndomorphism(new GlvTypeBEndomorphism(c, p)).Create(); + } + + /* + * curve25519 + */ + internal class Curve25519Holder + : X9ECParametersHolder + { + private Curve25519Holder() { } + + internal static readonly X9ECParametersHolder Instance = new Curve25519Holder(); + + protected override X9ECParameters CreateParameters() + { + byte[] S = null; + ECCurve curve = ConfigureCurve(new Curve25519()); + + /* + * NOTE: Curve25519 was specified in Montgomery form. Rewriting in Weierstrass form + * involves substitution of variables, so the base-point x coordinate is 9 + (486662 / 3). + * + * The Curve25519 paper doesn't say which of the two possible y values the base + * point has. The choice here is guided by language in the Ed25519 paper. + * + * (The other possible y value is 5F51E65E475F794B1FE122D388B72EB36DC2B28192839E4DD6163A5D81312C14) + */ + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD245A" + + "20AE19A1B8A086B4E01EDD2C7748D14C923D4D7E6D7C61B229E9C5A27ECED3D9")); + + return new X9ECParameters(curve, G, curve.Order, curve.Cofactor, S); + } + } + + /* + * secp128r1 + */ + internal class SecP128R1Holder + : X9ECParametersHolder + { + private SecP128R1Holder() { } + + internal static readonly X9ECParametersHolder Instance = new SecP128R1Holder(); + + protected override X9ECParameters CreateParameters() + { + byte[] S = Hex.Decode("000E0D4D696E6768756151750CC03A4473D03679"); + ECCurve curve = ConfigureCurve(new SecP128R1Curve()); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "161FF7528B899B2D0C28607CA52C5B86" + + "CF5AC8395BAFEB13C02DA292DDED7A83")); + return new X9ECParameters(curve, G, curve.Order, curve.Cofactor, S); + } + }; + + /* + * secp160k1 + */ + internal class SecP160K1Holder + : X9ECParametersHolder + { + private SecP160K1Holder() { } + + internal static readonly X9ECParametersHolder Instance = new SecP160K1Holder(); + + protected override X9ECParameters CreateParameters() + { + byte[] S = null; + GlvTypeBParameters glv = new GlvTypeBParameters( + new BigInteger("9ba48cba5ebcb9b6bd33b92830b2a2e0e192f10a", 16), + new BigInteger("c39c6c3b3a36d7701b9c71a1f5804ae5d0003f4", 16), + new BigInteger[]{ + new BigInteger("9162fbe73984472a0a9e", 16), + new BigInteger("-96341f1138933bc2f505", 16) }, + new BigInteger[]{ + new BigInteger("127971af8721782ecffa3", 16), + new BigInteger("9162fbe73984472a0a9e", 16) }, + new BigInteger("9162fbe73984472a0a9d0590", 16), + new BigInteger("96341f1138933bc2f503fd44", 16), + 176); + ECCurve curve = ConfigureCurveGlv(new SecP160K1Curve(), glv); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "3B4C382CE37AA192A4019E763036F4F5DD4D7EBB" + + "938CF935318FDCED6BC28286531733C3F03C4FEE")); + return new X9ECParameters(curve, G, curve.Order, curve.Cofactor, S); + } + }; + + /* + * secp160r1 + */ + internal class SecP160R1Holder + : X9ECParametersHolder + { + private SecP160R1Holder() { } + + internal static readonly X9ECParametersHolder Instance = new SecP160R1Holder(); + + protected override X9ECParameters CreateParameters() + { + byte[] S = Hex.Decode("1053CDE42C14D696E67687561517533BF3F83345"); + ECCurve curve = ConfigureCurve(new SecP160R1Curve()); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "4A96B5688EF573284664698968C38BB913CBFC82" + + "23A628553168947D59DCC912042351377AC5FB32")); + return new X9ECParameters(curve, G, curve.Order, curve.Cofactor, S); + } + }; + + /* + * secp160r2 + */ + internal class SecP160R2Holder + : X9ECParametersHolder + { + private SecP160R2Holder() { } + + internal static readonly X9ECParametersHolder Instance = new SecP160R2Holder(); + + protected override X9ECParameters CreateParameters() + { + byte[] S = Hex.Decode("B99B99B099B323E02709A4D696E6768756151751"); + ECCurve curve = ConfigureCurve(new SecP160R2Curve()); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "52DCB034293A117E1F4FF11B30F7199D3144CE6D" + + "FEAFFEF2E331F296E071FA0DF9982CFEA7D43F2E")); + return new X9ECParameters(curve, G, curve.Order, curve.Cofactor, S); + } + }; + + /* + * secp192k1 + */ + internal class SecP192K1Holder + : X9ECParametersHolder + { + private SecP192K1Holder() { } + + internal static readonly X9ECParametersHolder Instance = new SecP192K1Holder(); + + protected override X9ECParameters CreateParameters() + { + byte[] S = null; + GlvTypeBParameters glv = new GlvTypeBParameters( + new BigInteger("bb85691939b869c1d087f601554b96b80cb4f55b35f433c2", 16), + new BigInteger("3d84f26c12238d7b4f3d516613c1759033b1a5800175d0b1", 16), + new BigInteger[]{ + new BigInteger("71169be7330b3038edb025f1", 16), + new BigInteger("-b3fb3400dec5c4adceb8655c", 16) }, + new BigInteger[]{ + new BigInteger("12511cfe811d0f4e6bc688b4d", 16), + new BigInteger("71169be7330b3038edb025f1", 16) }, + new BigInteger("71169be7330b3038edb025f1d0f9", 16), + new BigInteger("b3fb3400dec5c4adceb8655d4c94", 16), + 208); + ECCurve curve = ConfigureCurveGlv(new SecP192K1Curve(), glv); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "DB4FF10EC057E9AE26B07D0280B7F4341DA5D1B1EAE06C7D" + + "9B2F2F6D9C5628A7844163D015BE86344082AA88D95E2F9D")); + return new X9ECParameters(curve, G, curve.Order, curve.Cofactor, S); + } + } + + /* + * secp192r1 + */ + internal class SecP192R1Holder + : X9ECParametersHolder + { + private SecP192R1Holder() { } + + internal static readonly X9ECParametersHolder Instance = new SecP192R1Holder(); + + protected override X9ECParameters CreateParameters() + { + byte[] S = Hex.Decode("3045AE6FC8422F64ED579528D38120EAE12196D5"); + ECCurve curve = ConfigureCurve(new SecP192R1Curve()); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "188DA80EB03090F67CBF20EB43A18800F4FF0AFD82FF1012" + + "07192B95FFC8DA78631011ED6B24CDD573F977A11E794811")); + return new X9ECParameters(curve, G, curve.Order, curve.Cofactor, S); + } + } + + /* + * secp224k1 + */ + internal class SecP224K1Holder + : X9ECParametersHolder + { + private SecP224K1Holder() { } + + internal static readonly X9ECParametersHolder Instance = new SecP224K1Holder(); + + protected override X9ECParameters CreateParameters() + { + byte[] S = null; + GlvTypeBParameters glv = new GlvTypeBParameters( + new BigInteger("fe0e87005b4e83761908c5131d552a850b3f58b749c37cf5b84d6768", 16), + new BigInteger("60dcd2104c4cbc0be6eeefc2bdd610739ec34e317f9b33046c9e4788", 16), + new BigInteger[]{ + new BigInteger("6b8cf07d4ca75c88957d9d670591", 16), + new BigInteger("-b8adf1378a6eb73409fa6c9c637d", 16) }, + new BigInteger[]{ + new BigInteger("1243ae1b4d71613bc9f780a03690e", 16), + new BigInteger("6b8cf07d4ca75c88957d9d670591", 16) }, + new BigInteger("6b8cf07d4ca75c88957d9d67059037a4", 16), + new BigInteger("b8adf1378a6eb73409fa6c9c637ba7f5", 16), + 240); + ECCurve curve = ConfigureCurveGlv(new SecP224K1Curve(), glv); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "A1455B334DF099DF30FC28A169A467E9E47075A90F7E650EB6B7A45C" + + "7E089FED7FBA344282CAFBD6F7E319F7C0B0BD59E2CA4BDB556D61A5")); + return new X9ECParameters(curve, G, curve.Order, curve.Cofactor, S); + } + } + + /* + * secp224r1 + */ + internal class SecP224R1Holder + : X9ECParametersHolder + { + private SecP224R1Holder() { } + + internal static readonly X9ECParametersHolder Instance = new SecP224R1Holder(); + + protected override X9ECParameters CreateParameters() + { + byte[] S = Hex.Decode("BD71344799D5C7FCDC45B59FA3B9AB8F6A948BC5"); + ECCurve curve = ConfigureCurve(new SecP224R1Curve()); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "B70E0CBD6BB4BF7F321390B94A03C1D356C21122343280D6115C1D21" + + "BD376388B5F723FB4C22DFE6CD4375A05A07476444D5819985007E34")); + return new X9ECParameters(curve, G, curve.Order, curve.Cofactor, S); + } + } + + /* + * secp256k1 + */ + internal class SecP256K1Holder + : X9ECParametersHolder + { + private SecP256K1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new SecP256K1Holder(); + + protected override X9ECParameters CreateParameters() + { + byte[] S = null; + GlvTypeBParameters glv = new GlvTypeBParameters( + new BigInteger("7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee", 16), + new BigInteger("5363ad4cc05c30e0a5261c028812645a122e22ea20816678df02967c1b23bd72", 16), + new BigInteger[]{ + new BigInteger("3086d221a7d46bcde86c90e49284eb15", 16), + new BigInteger("-e4437ed6010e88286f547fa90abfe4c3", 16) }, + new BigInteger[]{ + new BigInteger("114ca50f7a8e2f3f657c1108d9d44cfd8", 16), + new BigInteger("3086d221a7d46bcde86c90e49284eb15", 16) }, + new BigInteger("3086d221a7d46bcde86c90e49284eb153dab", 16), + new BigInteger("e4437ed6010e88286f547fa90abfe4c42212", 16), + 272); + ECCurve curve = ConfigureCurveGlv(new SecP256K1Curve(), glv); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798" + + "483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8")); + return new X9ECParameters(curve, G, curve.Order, curve.Cofactor, S); + } + } + + /* + * secp256r1 + */ + internal class SecP256R1Holder + : X9ECParametersHolder + { + private SecP256R1Holder() {} + + internal static readonly X9ECParametersHolder Instance = new SecP256R1Holder(); + + protected override X9ECParameters CreateParameters() + { + byte[] S = Hex.Decode("C49D360886E704936A6678E1139D26B7819F7E90"); + ECCurve curve = ConfigureCurve(new SecP256R1Curve()); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296" + + "4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5")); + return new X9ECParameters(curve, G, curve.Order, curve.Cofactor, S); + } + } + + /* + * secp384r1 + */ + internal class SecP384R1Holder + : X9ECParametersHolder + { + private SecP384R1Holder() { } + + internal static readonly X9ECParametersHolder Instance = new SecP384R1Holder(); + + protected override X9ECParameters CreateParameters() + { + byte[] S = Hex.Decode("A335926AA319A27A1D00896A6773A4827ACDAC73"); + ECCurve curve = ConfigureCurve(new SecP384R1Curve()); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "AA87CA22BE8B05378EB1C71EF320AD746E1D3B628BA79B9859F741E082542A385502F25DBF55296C3A545E3872760AB7" + + "3617DE4A96262C6F5D9E98BF9292DC29F8F41DBD289A147CE9DA3113B5F0B8C00A60B1CE1D7E819D7A431D7C90EA0E5F")); + return new X9ECParameters(curve, G, curve.Order, curve.Cofactor, S); + } + } + + /* + * secp521r1 + */ + internal class SecP521R1Holder + : X9ECParametersHolder + { + private SecP521R1Holder() { } + + internal static readonly X9ECParametersHolder Instance = new SecP521R1Holder(); + + protected override X9ECParameters CreateParameters() + { + byte[] S = Hex.Decode("D09E8800291CB85396CC6717393284AAA0DA64BA"); + ECCurve curve = ConfigureCurve(new SecP521R1Curve()); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "00C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66" + + "011839296A789A3BC0045C8A5FB42C7D1BD998F54449579B446817AFBD17273E662C97EE72995EF42640C550B9013FAD0761353C7086A272C24088BE94769FD16650")); + return new X9ECParameters(curve, G, curve.Order, curve.Cofactor, S); + } + } + + /* + * sect113r1 + */ + internal class SecT113R1Holder + : X9ECParametersHolder + { + private SecT113R1Holder() { } + + internal static readonly X9ECParametersHolder Instance = new SecT113R1Holder(); + + protected override X9ECParameters CreateParameters() + { + byte[] S = Hex.Decode("10E723AB14D696E6768756151756FEBF8FCB49A9"); + ECCurve curve = ConfigureCurve(new SecT113R1Curve()); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "009D73616F35F4AB1407D73562C10F" + + "00A52830277958EE84D1315ED31886")); + return new X9ECParameters(curve, G, curve.Order, curve.Cofactor, S); + } + }; + + /* + * sect113r2 + */ + internal class SecT113R2Holder + : X9ECParametersHolder + { + private SecT113R2Holder() { } + + internal static readonly X9ECParametersHolder Instance = new SecT113R2Holder(); + + protected override X9ECParameters CreateParameters() + { + byte[] S = Hex.Decode("10C0FB15760860DEF1EEF4D696E676875615175D"); + ECCurve curve = ConfigureCurve(new SecT113R2Curve()); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "01A57A6A7B26CA5EF52FCDB8164797" + + "00B3ADC94ED1FE674C06E695BABA1D")); + return new X9ECParameters(curve, G, curve.Order, curve.Cofactor, S); + } + }; + + /* + * sect131r1 + */ + internal class SecT131R1Holder + : X9ECParametersHolder + { + private SecT131R1Holder() { } + + internal static readonly X9ECParametersHolder Instance = new SecT131R1Holder(); + + protected override X9ECParameters CreateParameters() + { + byte[] S = Hex.Decode("4D696E676875615175985BD3ADBADA21B43A97E2"); + ECCurve curve = ConfigureCurve(new SecT131R1Curve()); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "0081BAF91FDF9833C40F9C181343638399" + + "078C6E7EA38C001F73C8134B1B4EF9E150")); + return new X9ECParameters(curve, G, curve.Order, curve.Cofactor, S); + } + }; + + /* + * sect131r2 + */ + internal class SecT131R2Holder + : X9ECParametersHolder + { + private SecT131R2Holder() { } + + internal static readonly X9ECParametersHolder Instance = new SecT131R2Holder(); + + protected override X9ECParameters CreateParameters() + { + byte[] S = Hex.Decode("985BD3ADBAD4D696E676875615175A21B43A97E3"); + ECCurve curve = ConfigureCurve(new SecT131R2Curve()); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "0356DCD8F2F95031AD652D23951BB366A8" + + "0648F06D867940A5366D9E265DE9EB240F")); + return new X9ECParameters(curve, G, curve.Order, curve.Cofactor, S); + } + }; + + /* + * sect163k1 + */ + internal class SecT163K1Holder + : X9ECParametersHolder + { + private SecT163K1Holder() { } + + internal static readonly X9ECParametersHolder Instance = new SecT163K1Holder(); + + protected override X9ECParameters CreateParameters() + { + byte[] S = null; + ECCurve curve = ConfigureCurve(new SecT163K1Curve()); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "02FE13C0537BBC11ACAA07D793DE4E6D5E5C94EEE8" + + "0289070FB05D38FF58321F2E800536D538CCDAA3D9")); + return new X9ECParameters(curve, G, curve.Order, curve.Cofactor, S); + } + }; + + /* + * sect163r1 + */ + internal class SecT163R1Holder + : X9ECParametersHolder + { + private SecT163R1Holder() { } + + internal static readonly X9ECParametersHolder Instance = new SecT163R1Holder(); + + protected override X9ECParameters CreateParameters() + { + byte[] S = Hex.Decode("24B7B137C8A14D696E6768756151756FD0DA2E5C"); + ECCurve curve = ConfigureCurve(new SecT163R1Curve()); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "0369979697AB43897789566789567F787A7876A654" + + "00435EDB42EFAFB2989D51FEFCE3C80988F41FF883")); + return new X9ECParameters(curve, G, curve.Order, curve.Cofactor, S); + } + }; + + /* + * sect163r2 + */ + internal class SecT163R2Holder + : X9ECParametersHolder + { + private SecT163R2Holder() { } + + internal static readonly X9ECParametersHolder Instance = new SecT163R2Holder(); + + protected override X9ECParameters CreateParameters() + { + byte[] S = Hex.Decode("85E25BFE5C86226CDB12016F7553F9D0E693A268"); + ECCurve curve = ConfigureCurve(new SecT163R2Curve()); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "03F0EBA16286A2D57EA0991168D4994637E8343E36" + + "00D51FBC6C71A0094FA2CDD545B11C5C0C797324F1")); + return new X9ECParameters(curve, G, curve.Order, curve.Cofactor, S); + } + }; + + /* + * sect193r1 + */ + internal class SecT193R1Holder + : X9ECParametersHolder + { + private SecT193R1Holder() { } + + internal static readonly X9ECParametersHolder Instance = new SecT193R1Holder(); + + protected override X9ECParameters CreateParameters() + { + byte[] S = Hex.Decode("103FAEC74D696E676875615175777FC5B191EF30"); + ECCurve curve = ConfigureCurve(new SecT193R1Curve()); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "01F481BC5F0FF84A74AD6CDF6FDEF4BF6179625372D8C0C5E1" + + "0025E399F2903712CCF3EA9E3A1AD17FB0B3201B6AF7CE1B05")); + return new X9ECParameters(curve, G, curve.Order, curve.Cofactor, S); + } + }; + + /* + * sect193r2 + */ + internal class SecT193R2Holder + : X9ECParametersHolder + { + private SecT193R2Holder() { } + + internal static readonly X9ECParametersHolder Instance = new SecT193R2Holder(); + + protected override X9ECParameters CreateParameters() + { + byte[] S = Hex.Decode("10B7B4D696E676875615175137C8A16FD0DA2211"); + ECCurve curve = ConfigureCurve(new SecT193R2Curve()); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "00D9B67D192E0367C803F39E1A7E82CA14A651350AAE617E8F" + + "01CE94335607C304AC29E7DEFBD9CA01F596F927224CDECF6C")); + return new X9ECParameters(curve, G, curve.Order, curve.Cofactor, S); + } + }; + + /* + * sect233k1 + */ + internal class SecT233K1Holder + : X9ECParametersHolder + { + private SecT233K1Holder() { } + + internal static readonly X9ECParametersHolder Instance = new SecT233K1Holder(); + + protected override X9ECParameters CreateParameters() + { + byte[] S = null; + ECCurve curve = ConfigureCurve(new SecT233K1Curve()); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "017232BA853A7E731AF129F22FF4149563A419C26BF50A4C9D6EEFAD6126" + + "01DB537DECE819B7F70F555A67C427A8CD9BF18AEB9B56E0C11056FAE6A3")); + return new X9ECParameters(curve, G, curve.Order, curve.Cofactor, S); + } + }; + + /* + * sect233r1 + */ + internal class SecT233R1Holder + : X9ECParametersHolder + { + private SecT233R1Holder() { } + + internal static readonly X9ECParametersHolder Instance = new SecT233R1Holder(); + + protected override X9ECParameters CreateParameters() + { + byte[] S = Hex.Decode("74D59FF07F6B413D0EA14B344B20A2DB049B50C3"); + ECCurve curve = ConfigureCurve(new SecT233R1Curve()); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "00FAC9DFCBAC8313BB2139F1BB755FEF65BC391F8B36F8F8EB7371FD558B" + + "01006A08A41903350678E58528BEBF8A0BEFF867A7CA36716F7E01F81052")); + return new X9ECParameters(curve, G, curve.Order, curve.Cofactor, S); + } + }; + + /* + * sect239k1 + */ + internal class SecT239K1Holder + : X9ECParametersHolder + { + private SecT239K1Holder() { } + + internal static readonly X9ECParametersHolder Instance = new SecT239K1Holder(); + + protected override X9ECParameters CreateParameters() + { + byte[] S = null; + ECCurve curve = ConfigureCurve(new SecT239K1Curve()); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "29A0B6A887A983E9730988A68727A8B2D126C44CC2CC7B2A6555193035DC" + + "76310804F12E549BDB011C103089E73510ACB275FC312A5DC6B76553F0CA")); + return new X9ECParameters(curve, G, curve.Order, curve.Cofactor, S); + } + }; + + /* + * sect283k1 + */ + internal class SecT283K1Holder + : X9ECParametersHolder + { + private SecT283K1Holder() { } + + internal static readonly X9ECParametersHolder Instance = new SecT283K1Holder(); + + protected override X9ECParameters CreateParameters() + { + byte[] S = null; + ECCurve curve = ConfigureCurve(new SecT283K1Curve()); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "0503213F78CA44883F1A3B8162F188E553CD265F23C1567A16876913B0C2AC2458492836" + + "01CCDA380F1C9E318D90F95D07E5426FE87E45C0E8184698E45962364E34116177DD2259")); + return new X9ECParameters(curve, G, curve.Order, curve.Cofactor, S); + } + }; + + /* + * sect283r1 + */ + internal class SecT283R1Holder + : X9ECParametersHolder + { + private SecT283R1Holder() { } + + internal static readonly X9ECParametersHolder Instance = new SecT283R1Holder(); + + protected override X9ECParameters CreateParameters() + { + byte[] S = Hex.Decode("77E2B07370EB0F832A6DD5B62DFC88CD06BB84BE"); + ECCurve curve = ConfigureCurve(new SecT283R1Curve()); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "05F939258DB7DD90E1934F8C70B0DFEC2EED25B8557EAC9C80E2E198F8CDBECD86B12053" + + "03676854FE24141CB98FE6D4B20D02B4516FF702350EDDB0826779C813F0DF45BE8112F4")); + return new X9ECParameters(curve, G, curve.Order, curve.Cofactor, S); + } + }; + + /* + * sect409k1 + */ + internal class SecT409K1Holder + : X9ECParametersHolder + { + private SecT409K1Holder() { } + + internal static readonly X9ECParametersHolder Instance = new SecT409K1Holder(); + + protected override X9ECParameters CreateParameters() + { + byte[] S = null; + ECCurve curve = ConfigureCurve(new SecT409K1Curve()); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "0060F05F658F49C1AD3AB1890F7184210EFD0987E307C84C27ACCFB8F9F67CC2C460189EB5AAAA62EE222EB1B35540CFE9023746" + + "01E369050B7C4E42ACBA1DACBF04299C3460782F918EA427E6325165E9EA10E3DA5F6C42E9C55215AA9CA27A5863EC48D8E0286B")); + return new X9ECParameters(curve, G, curve.Order, curve.Cofactor, S); + } + }; + + /* + * sect409r1 + */ + internal class SecT409R1Holder + : X9ECParametersHolder + { + private SecT409R1Holder() { } + + internal static readonly X9ECParametersHolder Instance = new SecT409R1Holder(); + + protected override X9ECParameters CreateParameters() + { + byte[] S = Hex.Decode("4099B5A457F9D69F79213D094C4BCD4D4262210B"); + ECCurve curve = ConfigureCurve(new SecT409R1Curve()); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "015D4860D088DDB3496B0C6064756260441CDE4AF1771D4DB01FFE5B34E59703DC255A868A1180515603AEAB60794E54BB7996A7" + + "0061B1CFAB6BE5F32BBFA78324ED106A7636B9C5A7BD198D0158AA4F5488D08F38514F1FDF4B4F40D2181B3681C364BA0273C706")); + return new X9ECParameters(curve, G, curve.Order, curve.Cofactor, S); + } + }; + + /* + * sect571k1 + */ + internal class SecT571K1Holder + : X9ECParametersHolder + { + private SecT571K1Holder() { } + + internal static readonly X9ECParametersHolder Instance = new SecT571K1Holder(); + + protected override X9ECParameters CreateParameters() + { + byte[] S = null; + ECCurve curve = ConfigureCurve(new SecT571K1Curve()); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "026EB7A859923FBC82189631F8103FE4AC9CA2970012D5D46024804801841CA44370958493B205E647DA304DB4CEB08CBBD1BA39494776FB988B47174DCA88C7E2945283A01C8972" + + "0349DC807F4FBF374F4AEADE3BCA95314DD58CEC9F307A54FFC61EFC006D8A2C9D4979C0AC44AEA74FBEBBB9F772AEDCB620B01A7BA7AF1B320430C8591984F601CD4C143EF1C7A3")); + return new X9ECParameters(curve, G, curve.Order, curve.Cofactor, S); + } + }; + + /* + * sect571r1 + */ + internal class SecT571R1Holder + : X9ECParametersHolder + { + private SecT571R1Holder() { } + + internal static readonly X9ECParametersHolder Instance = new SecT571R1Holder(); + + protected override X9ECParameters CreateParameters() + { + byte[] S = Hex.Decode("2AA058F73A0E33AB486B0F610410C53A7F132310"); + ECCurve curve = ConfigureCurve(new SecT571R1Curve()); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "0303001D34B856296C16C0D40D3CD7750A93D1D2955FA80AA5F40FC8DB7B2ABDBDE53950F4C0D293CDD711A35B67FB1499AE60038614F1394ABFA3B4C850D927E1E7769C8EEC2D19" + + "037BF27342DA639B6DCCFFFEB73D69D78C6C27A6009CBBCA1980F8533921E8A684423E43BAB08A576291AF8F461BB2A8B3531D2F0485C19B16E2F1516E23DD3C1A4827AF1B8AC15B")); + return new X9ECParameters(curve, G, curve.Order, curve.Cofactor, S); + } + }; + + /* + * sm2p256v1 + */ + internal class SM2P256V1Holder + : X9ECParametersHolder + { + private SM2P256V1Holder() { } + + internal static readonly X9ECParametersHolder Instance = new SM2P256V1Holder(); + + protected override X9ECParameters CreateParameters() + { + byte[] S = null; + ECCurve curve = ConfigureCurve(new SM2P256V1Curve()); + X9ECPoint G = new X9ECPoint(curve, Hex.Decode("04" + + "32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7" + + "BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0")); + return new X9ECParameters(curve, G, curve.Order, curve.Cofactor, S); + } + } + + + private static readonly IDictionary nameToCurve = Platform.CreateHashtable(); + private static readonly IDictionary nameToOid = Platform.CreateHashtable(); + private static readonly IDictionary oidToCurve = Platform.CreateHashtable(); + private static readonly IDictionary oidToName = Platform.CreateHashtable(); + private static readonly IList names = Platform.CreateArrayList(); + + private static void DefineCurve(string name, X9ECParametersHolder holder) + { + names.Add(name); + name = Platform.ToUpperInvariant(name); + nameToCurve.Add(name, holder); + } + + private static void DefineCurveWithOid(string name, DerObjectIdentifier oid, X9ECParametersHolder holder) + { + names.Add(name); + oidToName.Add(oid, name); + oidToCurve.Add(oid, holder); + name = Platform.ToUpperInvariant(name); + nameToOid.Add(name, oid); + nameToCurve.Add(name, holder); + } + + private static void DefineCurveAlias(string name, DerObjectIdentifier oid) + { + object curve = oidToCurve[oid]; + if (curve == null) + throw new InvalidOperationException(); + + name = Platform.ToUpperInvariant(name); + nameToOid.Add(name, oid); + nameToCurve.Add(name, curve); + } + + static CustomNamedCurves() + { + DefineCurve("curve25519", Curve25519Holder.Instance); + + //DefineCurveWithOid("secp112r1", SecObjectIdentifiers.SecP112r1, SecP112R1Holder.Instance); + //DefineCurveWithOid("secp112r2", SecObjectIdentifiers.SecP112r2, SecP112R2Holder.Instance); + DefineCurveWithOid("secp128r1", SecObjectIdentifiers.SecP128r1, SecP128R1Holder.Instance); + //DefineCurveWithOid("secp128r2", SecObjectIdentifiers.SecP128r2, SecP128R2Holder.Instance); + DefineCurveWithOid("secp160k1", SecObjectIdentifiers.SecP160k1, SecP160K1Holder.Instance); + DefineCurveWithOid("secp160r1", SecObjectIdentifiers.SecP160r1, SecP160R1Holder.Instance); + DefineCurveWithOid("secp160r2", SecObjectIdentifiers.SecP160r2, SecP160R2Holder.Instance); + DefineCurveWithOid("secp192k1", SecObjectIdentifiers.SecP192k1, SecP192K1Holder.Instance); + DefineCurveWithOid("secp192r1", SecObjectIdentifiers.SecP192r1, SecP192R1Holder.Instance); + DefineCurveWithOid("secp224k1", SecObjectIdentifiers.SecP224k1, SecP224K1Holder.Instance); + DefineCurveWithOid("secp224r1", SecObjectIdentifiers.SecP224r1, SecP224R1Holder.Instance); + DefineCurveWithOid("secp256k1", SecObjectIdentifiers.SecP256k1, SecP256K1Holder.Instance); + DefineCurveWithOid("secp256r1", SecObjectIdentifiers.SecP256r1, SecP256R1Holder.Instance); + DefineCurveWithOid("secp384r1", SecObjectIdentifiers.SecP384r1, SecP384R1Holder.Instance); + DefineCurveWithOid("secp521r1", SecObjectIdentifiers.SecP521r1, SecP521R1Holder.Instance); + + DefineCurveWithOid("sect113r1", SecObjectIdentifiers.SecT113r1, SecT113R1Holder.Instance); + DefineCurveWithOid("sect113r2", SecObjectIdentifiers.SecT113r2, SecT113R2Holder.Instance); + DefineCurveWithOid("sect131r1", SecObjectIdentifiers.SecT131r1, SecT131R1Holder.Instance); + DefineCurveWithOid("sect131r2", SecObjectIdentifiers.SecT131r2, SecT131R2Holder.Instance); + DefineCurveWithOid("sect163k1", SecObjectIdentifiers.SecT163k1, SecT163K1Holder.Instance); + DefineCurveWithOid("sect163r1", SecObjectIdentifiers.SecT163r1, SecT163R1Holder.Instance); + DefineCurveWithOid("sect163r2", SecObjectIdentifiers.SecT163r2, SecT163R2Holder.Instance); + DefineCurveWithOid("sect193r1", SecObjectIdentifiers.SecT193r1, SecT193R1Holder.Instance); + DefineCurveWithOid("sect193r2", SecObjectIdentifiers.SecT193r2, SecT193R2Holder.Instance); + DefineCurveWithOid("sect233k1", SecObjectIdentifiers.SecT233k1, SecT233K1Holder.Instance); + DefineCurveWithOid("sect233r1", SecObjectIdentifiers.SecT233r1, SecT233R1Holder.Instance); + DefineCurveWithOid("sect239k1", SecObjectIdentifiers.SecT239k1, SecT239K1Holder.Instance); + DefineCurveWithOid("sect283k1", SecObjectIdentifiers.SecT283k1, SecT283K1Holder.Instance); + DefineCurveWithOid("sect283r1", SecObjectIdentifiers.SecT283r1, SecT283R1Holder.Instance); + DefineCurveWithOid("sect409k1", SecObjectIdentifiers.SecT409k1, SecT409K1Holder.Instance); + DefineCurveWithOid("sect409r1", SecObjectIdentifiers.SecT409r1, SecT409R1Holder.Instance); + DefineCurveWithOid("sect571k1", SecObjectIdentifiers.SecT571k1, SecT571K1Holder.Instance); + DefineCurveWithOid("sect571r1", SecObjectIdentifiers.SecT571r1, SecT571R1Holder.Instance); + + DefineCurveWithOid("sm2p256v1", GMObjectIdentifiers.sm2p256v1, SM2P256V1Holder.Instance); + + DefineCurveAlias("B-163", SecObjectIdentifiers.SecT163r2); + DefineCurveAlias("B-233", SecObjectIdentifiers.SecT233r1); + DefineCurveAlias("B-283", SecObjectIdentifiers.SecT283r1); + DefineCurveAlias("B-409", SecObjectIdentifiers.SecT409r1); + DefineCurveAlias("B-571", SecObjectIdentifiers.SecT571r1); + + DefineCurveAlias("K-163", SecObjectIdentifiers.SecT163k1); + DefineCurveAlias("K-233", SecObjectIdentifiers.SecT233k1); + DefineCurveAlias("K-283", SecObjectIdentifiers.SecT283k1); + DefineCurveAlias("K-409", SecObjectIdentifiers.SecT409k1); + DefineCurveAlias("K-571", SecObjectIdentifiers.SecT571k1); + + DefineCurveAlias("P-192", SecObjectIdentifiers.SecP192r1); + DefineCurveAlias("P-224", SecObjectIdentifiers.SecP224r1); + DefineCurveAlias("P-256", SecObjectIdentifiers.SecP256r1); + DefineCurveAlias("P-384", SecObjectIdentifiers.SecP384r1); + DefineCurveAlias("P-521", SecObjectIdentifiers.SecP521r1); + } + + public static X9ECParameters GetByName(string name) + { + X9ECParametersHolder holder = (X9ECParametersHolder)nameToCurve[Platform.ToUpperInvariant(name)]; + return holder == null ? null : holder.Parameters; + } + + /** + * 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)oidToCurve[oid]; + return holder == null ? null : holder.Parameters; + } + + /** + * 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)nameToOid[Platform.ToUpperInvariant(name)]; + } + + /** + * return the named curve name represented by the given object identifier. + */ + public static string GetName(DerObjectIdentifier oid) + { + return (string)oidToName[oid]; + } + + /** + * returns an enumeration containing the name strings for curves + * contained in this structure. + */ + public static IEnumerable Names + { + get { return new EnumerableProxy(names); } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/encodings/ISO9796d1Encoding.cs b/bc-sharp-crypto/src/crypto/encodings/ISO9796d1Encoding.cs new file mode 100644 index 0000000..30e9883 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/encodings/ISO9796d1Encoding.cs @@ -0,0 +1,273 @@ +using System; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Crypto.Encodings +{ + /** + * 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 + : IAsymmetricBlockCipher + { + private static readonly BigInteger Sixteen = BigInteger.ValueOf(16); + private static readonly BigInteger Six = BigInteger.ValueOf(6); + + private static readonly byte[] shadows = { 0xe, 0x3, 0x5, 0x8, 0x9, 0x4, 0x2, 0xf, + 0x0, 0xd, 0xb, 0x6, 0x7, 0xa, 0xc, 0x1 }; + private static readonly byte[] inverse = { 0x8, 0xf, 0x6, 0x1, 0x5, 0x2, 0xb, 0xc, + 0x3, 0x4, 0xd, 0xa, 0xe, 0x9, 0x0, 0x7 }; + + private readonly IAsymmetricBlockCipher engine; + private bool forEncryption; + private int bitSize; + private int padBits = 0; + private BigInteger modulus; + + public ISO9796d1Encoding( + IAsymmetricBlockCipher cipher) + { + this.engine = cipher; + } + + public string AlgorithmName + { + get { return engine.AlgorithmName + "/ISO9796-1Padding"; } + } + + public IAsymmetricBlockCipher GetUnderlyingCipher() + { + return engine; + } + + public void Init( + bool forEncryption, + ICipherParameters parameters) + { + RsaKeyParameters kParam; + if (parameters is ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)parameters; + kParam = (RsaKeyParameters)rParam.Parameters; + } + else + { + kParam = (RsaKeyParameters)parameters; + } + + engine.Init(forEncryption, parameters); + + modulus = kParam.Modulus; + bitSize = modulus.BitLength; + + this.forEncryption = forEncryption; + } + + /** + * return the input block size. The largest message we can process + * is (key_size_in_bits + 3)/16, which in our world comes to + * 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 ArgumentException("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[] input, + int inOff, + int length) + { + if (forEncryption) + { + return EncodeBlock(input, inOff, length); + } + else + { + return DecodeBlock(input, inOff, length); + } + } + + private byte[] EncodeBlock( + byte[] input, + int inOff, + int inLen) + { + 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) + { + Array.Copy(input, inOff + inLen - (t - i), + block, block.Length - t, t - i); + } + else + { + Array.Copy(input, 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[(uint) (val & 0xff) >> 4] << 4) + | shadows[val & 0x0f]); + block[i + 1] = val; + } + + block[block.Length - 2 * z] ^= (byte) 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] &= (byte) ((ushort) 0xff >> maxBit); + block[0] |= (byte) ((ushort) 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[] input, + int inOff, + int inLen) + { + byte[] block = engine.ProcessBlock(input, inOff, inLen); + int r = 1; + int t = (bitSize + 13) / 16; + + BigInteger iS = new BigInteger(1, block); + BigInteger iR; + if (iS.Mod(Sixteen).Equals(Six)) + { + iR = iS; + } + else + { + iR = modulus.Subtract(iS); + + if (!iR.Mod(Sixteen).Equals(Six)) + throw new InvalidCipherTextException("resulting integer iS or (modulus - iS) is not congruent to 6 mod 16"); + } + + block = iR.ToByteArrayUnsigned(); + + if ((block[block.Length - 1] & 0x0f) != 0x6) + throw new InvalidCipherTextException("invalid forcing byte in block"); + + block[block.Length - 1] = + (byte)(((ushort)(block[block.Length - 1] & 0xff) >> 4) + | ((inverse[(block[block.Length - 2] & 0xff) >> 4]) << 4)); + + block[0] = (byte)((shadows[(uint) (block[1] & 0xff) >> 4] << 4) + | shadows[block[1] & 0x0f]); + + bool boundaryFound = false; + int boundary = 0; + + for (int i = block.Length - 1; i >= block.Length - 2 * t; i -= 2) + { + int val = ((shadows[(uint) (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/bc-sharp-crypto/src/crypto/encodings/OaepEncoding.cs b/bc-sharp-crypto/src/crypto/encodings/OaepEncoding.cs new file mode 100644 index 0000000..287876f --- /dev/null +++ b/bc-sharp-crypto/src/crypto/encodings/OaepEncoding.cs @@ -0,0 +1,345 @@ +using System; + +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Encodings +{ + /** + * Optimal Asymmetric Encryption Padding (OAEP) - see PKCS 1 V 2. + */ + public class OaepEncoding + : IAsymmetricBlockCipher + { + private byte[] defHash; + private IDigest mgf1Hash; + + private IAsymmetricBlockCipher engine; + private SecureRandom random; + private bool forEncryption; + + public OaepEncoding( + IAsymmetricBlockCipher cipher) + : this(cipher, new Sha1Digest(), null) + { + } + + public OaepEncoding( + IAsymmetricBlockCipher cipher, + IDigest hash) + : this(cipher, hash, null) + { + } + + public OaepEncoding( + IAsymmetricBlockCipher cipher, + IDigest hash, + byte[] encodingParams) + : this(cipher, hash, hash, encodingParams) + { + } + + public OaepEncoding( + IAsymmetricBlockCipher cipher, + IDigest hash, + IDigest mgf1Hash, + byte[] encodingParams) + { + this.engine = cipher; + this.mgf1Hash = mgf1Hash; + this.defHash = new byte[hash.GetDigestSize()]; + + hash.Reset(); + + if (encodingParams != null) + { + hash.BlockUpdate(encodingParams, 0, encodingParams.Length); + } + + hash.DoFinal(defHash, 0); + } + + public IAsymmetricBlockCipher GetUnderlyingCipher() + { + return engine; + } + + public string AlgorithmName + { + get { return engine.AlgorithmName + "/OAEPPadding"; } + } + + public void Init( + bool forEncryption, + ICipherParameters param) + { + if (param is ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)param; + this.random = rParam.Random; + } + 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[] inBytes, + int inOff, + int inLen) + { + if (forEncryption) + { + return EncodeBlock(inBytes, inOff, inLen); + } + else + { + return DecodeBlock(inBytes, inOff, inLen); + } + } + + private byte[] EncodeBlock( + byte[] inBytes, + int inOff, + int inLen) + { + Check.DataLength(inLen > GetInputBlockSize(), "input data too long"); + + byte[] block = new byte[GetInputBlockSize() + 1 + 2 * defHash.Length]; + + // + // copy in the message + // + Array.Copy(inBytes, 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. + // + Array.Copy(defHash, 0, block, defHash.Length, defHash.Length); + + // + // generate the seed. + // + byte[] seed = SecureRandom.GetNextBytes(random, defHash.Length); + + // + // 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 + // + Array.Copy(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. + */ + private byte[] DecodeBlock( + byte[] inBytes, + int inOff, + int inLen) + { + byte[] data = engine.ProcessBlock(inBytes, inOff, inLen); + byte[] block = new byte[engine.GetOutputBlockSize()]; + + // + // 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. + // + + Array.Copy(data, 0, block, block.Length - data.Length, data.Length); + + bool shortData = (block.Length < (2 * defHash.Length) + 1); + + // + // 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. + // long check to try to avoid this been a source of a timing attack. + // + bool defHashWrong = false; + + for (int i = 0; i != defHash.Length; i++) + { + if (defHash[i] != block[defHash.Length + i]) + { + defHashWrong = true; + } + } + + // + // find the data block + // + int start = block.Length; + + for (int index = 2 * defHash.Length; index != block.Length; index++) + { + if (block[index] != 0 & start == block.Length) + { + start = index; + } + } + + bool dataStartWrong = (start > (block.Length - 1) | block[start] != 1); + + start++; + + if (defHashWrong | shortData | dataStartWrong) + { + Arrays.Fill(block, 0); + throw new InvalidCipherTextException("data wrong"); + } + + // + // extract the data block + // + byte[] output = new byte[block.Length - start]; + + Array.Copy(block, start, output, 0, output.Length); + + return output; + } + + /** + * int to octet string. + */ + private void ItoOSP( + int i, + byte[] sp) + { + sp[0] = (byte)((uint)i >> 24); + sp[1] = (byte)((uint)i >> 16); + sp[2] = (byte)((uint)i >> 8); + sp[3] = (byte)((uint)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; + + mgf1Hash.Reset(); + + while (counter < (length / hashBuf.Length)) + { + ItoOSP(counter, C); + + mgf1Hash.BlockUpdate(Z, zOff, zLen); + mgf1Hash.BlockUpdate(C, 0, C.Length); + mgf1Hash.DoFinal(hashBuf, 0); + + Array.Copy(hashBuf, 0, mask, counter * hashBuf.Length, hashBuf.Length); + + counter++; + } + + if ((counter * hashBuf.Length) < length) + { + ItoOSP(counter, C); + + mgf1Hash.BlockUpdate(Z, zOff, zLen); + mgf1Hash.BlockUpdate(C, 0, C.Length); + mgf1Hash.DoFinal(hashBuf, 0); + + Array.Copy(hashBuf, 0, mask, counter * hashBuf.Length, mask.Length - (counter * hashBuf.Length)); + } + + return mask; + } + } +} + diff --git a/bc-sharp-crypto/src/crypto/encodings/Pkcs1Encoding.cs b/bc-sharp-crypto/src/crypto/encodings/Pkcs1Encoding.cs new file mode 100644 index 0000000..b2d60fe --- /dev/null +++ b/bc-sharp-crypto/src/crypto/encodings/Pkcs1Encoding.cs @@ -0,0 +1,384 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Encodings +{ + /** + * 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 + : IAsymmetricBlockCipher + { + /** + * 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. + */ + public const string StrictLengthEnabledProperty = "Org.BouncyCastle.Pkcs1.Strict"; + + private const int HeaderLength = 10; + + /** + * The same effect can be achieved by setting the static property directly + *

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

+ */ + public static bool StrictLengthEnabled + { + get { return strictLengthEnabled[0]; } + set { strictLengthEnabled[0] = value; } + } + + private static readonly bool[] strictLengthEnabled; + + static Pkcs1Encoding() + { + string strictProperty = Platform.GetEnvironmentVariable(StrictLengthEnabledProperty); + + strictLengthEnabled = new bool[]{ strictProperty == null || strictProperty.Equals("true")}; + } + + + private SecureRandom random; + private IAsymmetricBlockCipher engine; + private bool forEncryption; + private bool forPrivateKey; + private bool useStrictLength; + private int pLen = -1; + private byte[] fallback = null; + private byte[] blockBuffer = null; + + /** + * Basic constructor. + * + * @param cipher + */ + public Pkcs1Encoding( + IAsymmetricBlockCipher cipher) + { + this.engine = cipher; + this.useStrictLength = StrictLengthEnabled; + } + + /** + * Constructor for decryption with a fixed plaintext length. + * + * @param cipher The cipher to use for cryptographic operation. + * @param pLen Length of the expected plaintext. + */ + public Pkcs1Encoding(IAsymmetricBlockCipher cipher, int pLen) + { + this.engine = cipher; + this.useStrictLength = StrictLengthEnabled; + this.pLen = pLen; + } + + /** + * Constructor for decryption with a fixed plaintext length and a fallback + * value that is returned, if the padding is incorrect. + * + * @param cipher + * The cipher to use for cryptographic operation. + * @param fallback + * The fallback value, we don't to a arraycopy here. + */ + public Pkcs1Encoding(IAsymmetricBlockCipher cipher, byte[] fallback) + { + this.engine = cipher; + this.useStrictLength = StrictLengthEnabled; + this.fallback = fallback; + this.pLen = fallback.Length; + } + + public IAsymmetricBlockCipher GetUnderlyingCipher() + { + return engine; + } + + public string AlgorithmName + { + get { return engine.AlgorithmName + "/PKCS1Padding"; } + } + + public void Init(bool forEncryption, ICipherParameters parameters) + { + AsymmetricKeyParameter kParam; + if (parameters is ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)parameters; + + this.random = rParam.Random; + kParam = (AsymmetricKeyParameter)rParam.Parameters; + } + else + { + this.random = new SecureRandom(); + kParam = (AsymmetricKeyParameter)parameters; + } + + engine.Init(forEncryption, parameters); + + this.forPrivateKey = kParam.IsPrivate; + this.forEncryption = forEncryption; + this.blockBuffer = new byte[engine.GetOutputBlockSize()]; + + if (pLen > 0 && fallback == null && random == null) + throw new ArgumentException("encoder requires random"); + } + + public int GetInputBlockSize() + { + int baseBlockSize = engine.GetInputBlockSize(); + + return forEncryption + ? baseBlockSize - HeaderLength + : baseBlockSize; + } + + public int GetOutputBlockSize() + { + int baseBlockSize = engine.GetOutputBlockSize(); + + return forEncryption + ? baseBlockSize + : baseBlockSize - HeaderLength; + } + + public byte[] ProcessBlock( + byte[] input, + int inOff, + int length) + { + return forEncryption + ? EncodeBlock(input, inOff, length) + : DecodeBlock(input, inOff, length); + } + + private byte[] EncodeBlock( + byte[] input, + int inOff, + int inLen) + { + if (inLen > GetInputBlockSize()) + throw new ArgumentException("input data too large", "inLen"); + + 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 + Array.Copy(input, inOff, block, block.Length - inLen, inLen); + + return engine.ProcessBlock(block, 0, block.Length); + } + + /** + * Checks if the argument is a correctly PKCS#1.5 encoded Plaintext + * for encryption. + * + * @param encoded The Plaintext. + * @param pLen Expected length of the plaintext. + * @return Either 0, if the encoding is correct, or -1, if it is incorrect. + */ + private static int CheckPkcs1Encoding(byte[] encoded, int pLen) + { + int correct = 0; + /* + * Check if the first two bytes are 0 2 + */ + correct |= (encoded[0] ^ 2); + + /* + * Now the padding check, check for no 0 byte in the padding + */ + int plen = encoded.Length - ( + pLen /* Lenght of the PMS */ + + 1 /* Final 0-byte before PMS */ + ); + + for (int i = 1; i < plen; i++) + { + int tmp = encoded[i]; + tmp |= tmp >> 1; + tmp |= tmp >> 2; + tmp |= tmp >> 4; + correct |= (tmp & 1) - 1; + } + + /* + * Make sure the padding ends with a 0 byte. + */ + correct |= encoded[encoded.Length - (pLen + 1)]; + + /* + * Return 0 or 1, depending on the result. + */ + correct |= correct >> 1; + correct |= correct >> 2; + correct |= correct >> 4; + return ~((correct & 1) - 1); + } + + /** + * Decode PKCS#1.5 encoding, and return a random value if the padding is not correct. + * + * @param in The encrypted block. + * @param inOff Offset in the encrypted block. + * @param inLen Length of the encrypted block. + * @param pLen Length of the desired output. + * @return The plaintext without padding, or a random value if the padding was incorrect. + * @throws InvalidCipherTextException + */ + private byte[] DecodeBlockOrRandom(byte[] input, int inOff, int inLen) + { + if (!forPrivateKey) + throw new InvalidCipherTextException("sorry, this method is only for decryption, not for signing"); + + byte[] block = engine.ProcessBlock(input, inOff, inLen); + byte[] random; + if (this.fallback == null) + { + random = new byte[this.pLen]; + this.random.NextBytes(random); + } + else + { + random = fallback; + } + + byte[] data = (useStrictLength & (block.Length != engine.GetOutputBlockSize())) ? blockBuffer : block; + + /* + * Check the padding. + */ + int correct = CheckPkcs1Encoding(data, this.pLen); + + /* + * Now, to a constant time constant memory copy of the decrypted value + * or the random value, depending on the validity of the padding. + */ + byte[] result = new byte[this.pLen]; + for (int i = 0; i < this.pLen; i++) + { + result[i] = (byte)((data[i + (data.Length - pLen)] & (~correct)) | (random[i] & correct)); + } + + Arrays.Fill(data, 0); + + return result; + } + + /** + * @exception InvalidCipherTextException if the decrypted block is not in Pkcs1 format. + */ + private byte[] DecodeBlock( + byte[] input, + int inOff, + int inLen) + { + /* + * If the length of the expected plaintext is known, we use a constant-time decryption. + * If the decryption fails, we return a random value. + */ + if (this.pLen != -1) + { + return this.DecodeBlockOrRandom(input, inOff, inLen); + } + + byte[] block = engine.ProcessBlock(input, inOff, inLen); + bool incorrectLength = (useStrictLength & (block.Length != engine.GetOutputBlockSize())); + + byte[] data; + if (block.Length < GetOutputBlockSize()) + { + data = blockBuffer; + } + else + { + data = block; + } + + byte expectedType = (byte)(forPrivateKey ? 2 : 1); + byte type = data[0]; + + bool badType = (type != expectedType); + + // + // find and extract the message block. + // + int start = FindStart(type, data); + + start++; // data should start at the next byte + + if (badType | (start < HeaderLength)) + { + Arrays.Fill(data, 0); + throw new InvalidCipherTextException("block incorrect"); + } + + // if we get this far, it's likely to be a genuine encoding error + if (incorrectLength) + { + Arrays.Fill(data, 0); + throw new InvalidCipherTextException("block incorrect size"); + } + + byte[] result = new byte[data.Length - start]; + + Array.Copy(data, start, result, 0, result.Length); + + return result; + } + + private int FindStart(byte type, byte[] block) + { + int start = -1; + bool padErr = false; + + for (int i = 1; i != block.Length; i++) + { + byte pad = block[i]; + + if (pad == 0 & start < 0) + { + start = i; + } + padErr |= ((type == 1) & (start < 0) & (pad != (byte)0xff)); + } + + return padErr ? -1 : start; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/engines/AesEngine.cs b/bc-sharp-crypto/src/crypto/engines/AesEngine.cs new file mode 100644 index 0000000..91bdf69 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/AesEngine.cs @@ -0,0 +1,610 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Utilities; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Engines +{ + /** + * 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 + : IBlockCipher + { + // The S box + private static readonly byte[] S = + { + 99, 124, 119, 123, 242, 107, 111, 197, + 48, 1, 103, 43, 254, 215, 171, 118, + 202, 130, 201, 125, 250, 89, 71, 240, + 173, 212, 162, 175, 156, 164, 114, 192, + 183, 253, 147, 38, 54, 63, 247, 204, + 52, 165, 229, 241, 113, 216, 49, 21, + 4, 199, 35, 195, 24, 150, 5, 154, + 7, 18, 128, 226, 235, 39, 178, 117, + 9, 131, 44, 26, 27, 110, 90, 160, + 82, 59, 214, 179, 41, 227, 47, 132, + 83, 209, 0, 237, 32, 252, 177, 91, + 106, 203, 190, 57, 74, 76, 88, 207, + 208, 239, 170, 251, 67, 77, 51, 133, + 69, 249, 2, 127, 80, 60, 159, 168, + 81, 163, 64, 143, 146, 157, 56, 245, + 188, 182, 218, 33, 16, 255, 243, 210, + 205, 12, 19, 236, 95, 151, 68, 23, + 196, 167, 126, 61, 100, 93, 25, 115, + 96, 129, 79, 220, 34, 42, 144, 136, + 70, 238, 184, 20, 222, 94, 11, 219, + 224, 50, 58, 10, 73, 6, 36, 92, + 194, 211, 172, 98, 145, 149, 228, 121, + 231, 200, 55, 109, 141, 213, 78, 169, + 108, 86, 244, 234, 101, 122, 174, 8, + 186, 120, 37, 46, 28, 166, 180, 198, + 232, 221, 116, 31, 75, 189, 139, 138, + 112, 62, 181, 102, 72, 3, 246, 14, + 97, 53, 87, 185, 134, 193, 29, 158, + 225, 248, 152, 17, 105, 217, 142, 148, + 155, 30, 135, 233, 206, 85, 40, 223, + 140, 161, 137, 13, 191, 230, 66, 104, + 65, 153, 45, 15, 176, 84, 187, 22, + }; + + // The inverse S-box + private static readonly byte[] Si = + { + 82, 9, 106, 213, 48, 54, 165, 56, + 191, 64, 163, 158, 129, 243, 215, 251, + 124, 227, 57, 130, 155, 47, 255, 135, + 52, 142, 67, 68, 196, 222, 233, 203, + 84, 123, 148, 50, 166, 194, 35, 61, + 238, 76, 149, 11, 66, 250, 195, 78, + 8, 46, 161, 102, 40, 217, 36, 178, + 118, 91, 162, 73, 109, 139, 209, 37, + 114, 248, 246, 100, 134, 104, 152, 22, + 212, 164, 92, 204, 93, 101, 182, 146, + 108, 112, 72, 80, 253, 237, 185, 218, + 94, 21, 70, 87, 167, 141, 157, 132, + 144, 216, 171, 0, 140, 188, 211, 10, + 247, 228, 88, 5, 184, 179, 69, 6, + 208, 44, 30, 143, 202, 63, 15, 2, + 193, 175, 189, 3, 1, 19, 138, 107, + 58, 145, 17, 65, 79, 103, 220, 234, + 151, 242, 207, 206, 240, 180, 230, 115, + 150, 172, 116, 34, 231, 173, 53, 133, + 226, 249, 55, 232, 28, 117, 223, 110, + 71, 241, 26, 113, 29, 41, 197, 137, + 111, 183, 98, 14, 170, 24, 190, 27, + 252, 86, 62, 75, 198, 210, 121, 32, + 154, 219, 192, 254, 120, 205, 90, 244, + 31, 221, 168, 51, 136, 7, 199, 49, + 177, 18, 16, 89, 39, 128, 236, 95, + 96, 81, 127, 169, 25, 181, 74, 13, + 45, 229, 122, 159, 147, 201, 156, 239, + 160, 224, 59, 77, 174, 42, 245, 176, + 200, 235, 187, 60, 131, 83, 153, 97, + 23, 43, 4, 126, 186, 119, 214, 38, + 225, 105, 20, 99, 85, 33, 12, 125, + }; + + // vector used in calculating key schedule (powers of x in GF(256)) + private static readonly byte[] 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 readonly uint[] 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 readonly uint[] 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 uint Shift(uint r, int shift) + { + return (r >> shift) | (r << (32 - shift)); + } + + /* multiply four bytes in GF(2^8) by 'x' {02} in parallel */ + + private const uint m1 = 0x80808080; + private const uint m2 = 0x7f7f7f7f; + private const uint m3 = 0x0000001b; + private const uint m4 = 0xC0C0C0C0; + private const uint m5 = 0x3f3f3f3f; + + private static uint FFmulX(uint x) + { + return ((x & m2) << 1) ^ (((x & m1) >> 7) * m3); + } + + private static uint FFmulX2(uint x) + { + uint t0 = (x & m5) << 2; + uint t1 = (x & m4); + t1 ^= (t1 >> 1); + return t0 ^ (t1 >> 2) ^ (t1 >> 5); + } + + /* + 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 static uint Inv_Mcol(uint x) + { + uint t0, t1; + t0 = x; + t1 = t0 ^ Shift(t0, 8); + t0 ^= FFmulX(t1); + t1 ^= FFmulX2(t0); + t0 ^= t1 ^ Shift(t1, 16); + return t0; + } + + private static uint SubWord(uint x) + { + return (uint)S[x&255] + | (((uint)S[(x>>8)&255]) << 8) + | (((uint)S[(x>>16)&255]) << 16) + | (((uint)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 uint[][] GenerateWorkingKey(byte[] key, bool forEncryption) + { + int keyLen = key.Length; + if (keyLen < 16 || keyLen > 32 || (keyLen & 7) != 0) + throw new ArgumentException("Key length not 128/192/256 bits."); + + int KC = keyLen >> 2; + this.ROUNDS = KC + 6; // This is not always true for the generalized Rijndael that allows larger block sizes + + uint[][] W = new uint[ROUNDS + 1][]; // 4 words in a block + for (int i = 0; i <= ROUNDS; ++i) + { + W[i] = new uint[4]; + } + + switch (KC) + { + case 4: + { + uint t0 = Pack.LE_To_UInt32(key, 0); W[0][0] = t0; + uint t1 = Pack.LE_To_UInt32(key, 4); W[0][1] = t1; + uint t2 = Pack.LE_To_UInt32(key, 8); W[0][2] = t2; + uint t3 = Pack.LE_To_UInt32(key, 12); W[0][3] = t3; + + for (int i = 1; i <= 10; ++i) + { + uint u = SubWord(Shift(t3, 8)) ^ rcon[i - 1]; + t0 ^= u; W[i][0] = t0; + t1 ^= t0; W[i][1] = t1; + t2 ^= t1; W[i][2] = t2; + t3 ^= t2; W[i][3] = t3; + } + + break; + } + case 6: + { + uint t0 = Pack.LE_To_UInt32(key, 0); W[0][0] = t0; + uint t1 = Pack.LE_To_UInt32(key, 4); W[0][1] = t1; + uint t2 = Pack.LE_To_UInt32(key, 8); W[0][2] = t2; + uint t3 = Pack.LE_To_UInt32(key, 12); W[0][3] = t3; + uint t4 = Pack.LE_To_UInt32(key, 16); W[1][0] = t4; + uint t5 = Pack.LE_To_UInt32(key, 20); W[1][1] = t5; + + uint rcon = 1; + uint u = SubWord(Shift(t5, 8)) ^ rcon; rcon <<= 1; + t0 ^= u; W[1][2] = t0; + t1 ^= t0; W[1][3] = t1; + t2 ^= t1; W[2][0] = t2; + t3 ^= t2; W[2][1] = t3; + t4 ^= t3; W[2][2] = t4; + t5 ^= t4; W[2][3] = t5; + + for (int i = 3; i < 12; i += 3) + { + u = SubWord(Shift(t5, 8)) ^ rcon; rcon <<= 1; + t0 ^= u; W[i ][0] = t0; + t1 ^= t0; W[i ][1] = t1; + t2 ^= t1; W[i ][2] = t2; + t3 ^= t2; W[i ][3] = t3; + t4 ^= t3; W[i + 1][0] = t4; + t5 ^= t4; W[i + 1][1] = t5; + u = SubWord(Shift(t5, 8)) ^ rcon; rcon <<= 1; + t0 ^= u; W[i + 1][2] = t0; + t1 ^= t0; W[i + 1][3] = t1; + t2 ^= t1; W[i + 2][0] = t2; + t3 ^= t2; W[i + 2][1] = t3; + t4 ^= t3; W[i + 2][2] = t4; + t5 ^= t4; W[i + 2][3] = t5; + } + + u = SubWord(Shift(t5, 8)) ^ rcon; + t0 ^= u; W[12][0] = t0; + t1 ^= t0; W[12][1] = t1; + t2 ^= t1; W[12][2] = t2; + t3 ^= t2; W[12][3] = t3; + + break; + } + case 8: + { + uint t0 = Pack.LE_To_UInt32(key, 0); W[0][0] = t0; + uint t1 = Pack.LE_To_UInt32(key, 4); W[0][1] = t1; + uint t2 = Pack.LE_To_UInt32(key, 8); W[0][2] = t2; + uint t3 = Pack.LE_To_UInt32(key, 12); W[0][3] = t3; + uint t4 = Pack.LE_To_UInt32(key, 16); W[1][0] = t4; + uint t5 = Pack.LE_To_UInt32(key, 20); W[1][1] = t5; + uint t6 = Pack.LE_To_UInt32(key, 24); W[1][2] = t6; + uint t7 = Pack.LE_To_UInt32(key, 28); W[1][3] = t7; + + uint u, rcon = 1; + + for (int i = 2; i < 14; i += 2) + { + u = SubWord(Shift(t7, 8)) ^ rcon; rcon <<= 1; + t0 ^= u; W[i ][0] = t0; + t1 ^= t0; W[i ][1] = t1; + t2 ^= t1; W[i ][2] = t2; + t3 ^= t2; W[i ][3] = t3; + u = SubWord(t3); + t4 ^= u; W[i + 1][0] = t4; + t5 ^= t4; W[i + 1][1] = t5; + t6 ^= t5; W[i + 1][2] = t6; + t7 ^= t6; W[i + 1][3] = t7; + } + + u = SubWord(Shift(t7, 8)) ^ rcon; + t0 ^= u; W[14][0] = t0; + t1 ^= t0; W[14][1] = t1; + t2 ^= t1; W[14][2] = t2; + t3 ^= t2; W[14][3] = t3; + + break; + } + default: + { + throw new InvalidOperationException("Should never get here"); + } + } + + if (!forEncryption) + { + for (int j = 1; j < ROUNDS; j++) + { + uint[] w = W[j]; + for (int i = 0; i < 4; i++) + { + w[i] = Inv_Mcol(w[i]); + } + } + } + + return W; + } + + private int ROUNDS; + private uint[][] WorkingKey; + private uint C0, C1, C2, C3; + private bool forEncryption; + + private byte[] s; + + private const 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 parameters the parameters required to set up the cipher. + * @exception ArgumentException if the parameters argument is + * inappropriate. + */ + public virtual void Init( + bool forEncryption, + ICipherParameters parameters) + { + KeyParameter keyParameter = parameters as KeyParameter; + + if (keyParameter == null) + throw new ArgumentException("invalid parameter passed to AES init - " + + Platform.GetTypeName(parameters)); + + WorkingKey = GenerateWorkingKey(keyParameter.GetKey(), forEncryption); + + this.forEncryption = forEncryption; + this.s = Arrays.Clone(forEncryption ? S : Si); + } + + public virtual string AlgorithmName + { + get { return "AES"; } + } + + public virtual bool IsPartialBlockOkay + { + get { return false; } + } + + public virtual int GetBlockSize() + { + return BLOCK_SIZE; + } + + public virtual int ProcessBlock( + byte[] input, + int inOff, + byte[] output, + int outOff) + { + if (WorkingKey == null) + throw new InvalidOperationException("AES engine not initialised"); + + Check.DataLength(input, inOff, 16, "input buffer too short"); + Check.OutputLength(output, outOff, 16, "output buffer too short"); + + UnPackBlock(input, inOff); + + if (forEncryption) + { + EncryptBlock(WorkingKey); + } + else + { + DecryptBlock(WorkingKey); + } + + PackBlock(output, outOff); + + return BLOCK_SIZE; + } + + public virtual void Reset() + { + } + + private void UnPackBlock( + byte[] bytes, + int off) + { + C0 = Pack.LE_To_UInt32(bytes, off); + C1 = Pack.LE_To_UInt32(bytes, off + 4); + C2 = Pack.LE_To_UInt32(bytes, off + 8); + C3 = Pack.LE_To_UInt32(bytes, off + 12); + } + + private void PackBlock( + byte[] bytes, + int off) + { + Pack.UInt32_To_LE(C0, bytes, off); + Pack.UInt32_To_LE(C1, bytes, off + 4); + Pack.UInt32_To_LE(C2, bytes, off + 8); + Pack.UInt32_To_LE(C3, bytes, off + 12); + } + + private void EncryptBlock(uint[][] KW) + { + uint[] kw = KW[0]; + uint t0 = this.C0 ^ kw[0]; + uint t1 = this.C1 ^ kw[1]; + uint t2 = this.C2 ^ kw[2]; + + uint r0, r1, r2, r3 = this.C3 ^ kw[3]; + int r = 1; + while (r < ROUNDS - 1) + { + kw = KW[r++]; + r0 = T0[t0 & 255] ^ Shift(T0[(t1 >> 8) & 255], 24) ^ Shift(T0[(t2 >> 16) & 255], 16) ^ Shift(T0[(r3 >> 24) & 255], 8) ^ kw[0]; + r1 = T0[t1 & 255] ^ Shift(T0[(t2 >> 8) & 255], 24) ^ Shift(T0[(r3 >> 16) & 255], 16) ^ Shift(T0[(t0 >> 24) & 255], 8) ^ kw[1]; + r2 = T0[t2 & 255] ^ Shift(T0[(r3 >> 8) & 255], 24) ^ Shift(T0[(t0 >> 16) & 255], 16) ^ Shift(T0[(t1 >> 24) & 255], 8) ^ kw[2]; + r3 = T0[r3 & 255] ^ Shift(T0[(t0 >> 8) & 255], 24) ^ Shift(T0[(t1 >> 16) & 255], 16) ^ Shift(T0[(t2 >> 24) & 255], 8) ^ kw[3]; + kw = KW[r++]; + t0 = T0[r0 & 255] ^ Shift(T0[(r1 >> 8) & 255], 24) ^ Shift(T0[(r2 >> 16) & 255], 16) ^ Shift(T0[(r3 >> 24) & 255], 8) ^ kw[0]; + t1 = T0[r1 & 255] ^ Shift(T0[(r2 >> 8) & 255], 24) ^ Shift(T0[(r3 >> 16) & 255], 16) ^ Shift(T0[(r0 >> 24) & 255], 8) ^ kw[1]; + t2 = T0[r2 & 255] ^ Shift(T0[(r3 >> 8) & 255], 24) ^ Shift(T0[(r0 >> 16) & 255], 16) ^ Shift(T0[(r1 >> 24) & 255], 8) ^ kw[2]; + r3 = T0[r3 & 255] ^ Shift(T0[(r0 >> 8) & 255], 24) ^ Shift(T0[(r1 >> 16) & 255], 16) ^ Shift(T0[(r2 >> 24) & 255], 8) ^ kw[3]; + } + + kw = KW[r++]; + r0 = T0[t0 & 255] ^ Shift(T0[(t1 >> 8) & 255], 24) ^ Shift(T0[(t2 >> 16) & 255], 16) ^ Shift(T0[(r3 >> 24) & 255], 8) ^ kw[0]; + r1 = T0[t1 & 255] ^ Shift(T0[(t2 >> 8) & 255], 24) ^ Shift(T0[(r3 >> 16) & 255], 16) ^ Shift(T0[(t0 >> 24) & 255], 8) ^ kw[1]; + r2 = T0[t2 & 255] ^ Shift(T0[(r3 >> 8) & 255], 24) ^ Shift(T0[(t0 >> 16) & 255], 16) ^ Shift(T0[(t1 >> 24) & 255], 8) ^ kw[2]; + r3 = T0[r3 & 255] ^ Shift(T0[(t0 >> 8) & 255], 24) ^ Shift(T0[(t1 >> 16) & 255], 16) ^ Shift(T0[(t2 >> 24) & 255], 8) ^ kw[3]; + + // the final round's table is a simple function of S so we don't use a whole other four tables for it + + kw = KW[r]; + this.C0 = (uint)S[r0 & 255] ^ (((uint)S[(r1 >> 8) & 255]) << 8) ^ (((uint)s[(r2 >> 16) & 255]) << 16) ^ (((uint)s[(r3 >> 24) & 255]) << 24) ^ kw[0]; + this.C1 = (uint)s[r1 & 255] ^ (((uint)S[(r2 >> 8) & 255]) << 8) ^ (((uint)S[(r3 >> 16) & 255]) << 16) ^ (((uint)s[(r0 >> 24) & 255]) << 24) ^ kw[1]; + this.C2 = (uint)s[r2 & 255] ^ (((uint)S[(r3 >> 8) & 255]) << 8) ^ (((uint)S[(r0 >> 16) & 255]) << 16) ^ (((uint)S[(r1 >> 24) & 255]) << 24) ^ kw[2]; + this.C3 = (uint)s[r3 & 255] ^ (((uint)s[(r0 >> 8) & 255]) << 8) ^ (((uint)s[(r1 >> 16) & 255]) << 16) ^ (((uint)S[(r2 >> 24) & 255]) << 24) ^ kw[3]; + } + + private void DecryptBlock(uint[][] KW) + { + uint[] kw = KW[ROUNDS]; + uint t0 = this.C0 ^ kw[0]; + uint t1 = this.C1 ^ kw[1]; + uint t2 = this.C2 ^ kw[2]; + + uint r0, r1, r2, r3 = this.C3 ^ kw[3]; + int r = ROUNDS - 1; + while (r > 1) + { + kw = KW[r--]; + r0 = Tinv0[t0 & 255] ^ Shift(Tinv0[(r3 >> 8) & 255], 24) ^ Shift(Tinv0[(t2 >> 16) & 255], 16) ^ Shift(Tinv0[(t1 >> 24) & 255], 8) ^ kw[0]; + r1 = Tinv0[t1 & 255] ^ Shift(Tinv0[(t0 >> 8) & 255], 24) ^ Shift(Tinv0[(r3 >> 16) & 255], 16) ^ Shift(Tinv0[(t2 >> 24) & 255], 8) ^ kw[1]; + r2 = Tinv0[t2 & 255] ^ Shift(Tinv0[(t1 >> 8) & 255], 24) ^ Shift(Tinv0[(t0 >> 16) & 255], 16) ^ Shift(Tinv0[(r3 >> 24) & 255], 8) ^ kw[2]; + r3 = Tinv0[r3 & 255] ^ Shift(Tinv0[(t2 >> 8) & 255], 24) ^ Shift(Tinv0[(t1 >> 16) & 255], 16) ^ Shift(Tinv0[(t0 >> 24) & 255], 8) ^ kw[3]; + kw = KW[r--]; + t0 = Tinv0[r0 & 255] ^ Shift(Tinv0[(r3 >> 8) & 255], 24) ^ Shift(Tinv0[(r2 >> 16) & 255], 16) ^ Shift(Tinv0[(r1 >> 24) & 255], 8) ^ kw[0]; + t1 = Tinv0[r1 & 255] ^ Shift(Tinv0[(r0 >> 8) & 255], 24) ^ Shift(Tinv0[(r3 >> 16) & 255], 16) ^ Shift(Tinv0[(r2 >> 24) & 255], 8) ^ kw[1]; + t2 = Tinv0[r2 & 255] ^ Shift(Tinv0[(r1 >> 8) & 255], 24) ^ Shift(Tinv0[(r0 >> 16) & 255], 16) ^ Shift(Tinv0[(r3 >> 24) & 255], 8) ^ kw[2]; + r3 = Tinv0[r3 & 255] ^ Shift(Tinv0[(r2 >> 8) & 255], 24) ^ Shift(Tinv0[(r1 >> 16) & 255], 16) ^ Shift(Tinv0[(r0 >> 24) & 255], 8) ^ kw[3]; + } + + kw = KW[1]; + r0 = Tinv0[t0 & 255] ^ Shift(Tinv0[(r3 >> 8) & 255], 24) ^ Shift(Tinv0[(t2 >> 16) & 255], 16) ^ Shift(Tinv0[(t1 >> 24) & 255], 8) ^ kw[0]; + r1 = Tinv0[t1 & 255] ^ Shift(Tinv0[(t0 >> 8) & 255], 24) ^ Shift(Tinv0[(r3 >> 16) & 255], 16) ^ Shift(Tinv0[(t2 >> 24) & 255], 8) ^ kw[1]; + r2 = Tinv0[t2 & 255] ^ Shift(Tinv0[(t1 >> 8) & 255], 24) ^ Shift(Tinv0[(t0 >> 16) & 255], 16) ^ Shift(Tinv0[(r3 >> 24) & 255], 8) ^ kw[2]; + r3 = Tinv0[r3 & 255] ^ Shift(Tinv0[(t2 >> 8) & 255], 24) ^ Shift(Tinv0[(t1 >> 16) & 255], 16) ^ Shift(Tinv0[(t0 >> 24) & 255], 8) ^ kw[3]; + + // the final round's table is a simple function of Si so we don't use a whole other four tables for it + + kw = KW[0]; + this.C0 = (uint)Si[r0 & 255] ^ (((uint)s[(r3 >> 8) & 255]) << 8) ^ (((uint)s[(r2 >> 16) & 255]) << 16) ^ (((uint)Si[(r1 >> 24) & 255]) << 24) ^ kw[0]; + this.C1 = (uint)s[r1 & 255] ^ (((uint)s[(r0 >> 8) & 255]) << 8) ^ (((uint)Si[(r3 >> 16) & 255]) << 16) ^ (((uint)s[(r2 >> 24) & 255]) << 24) ^ kw[1]; + this.C2 = (uint)s[r2 & 255] ^ (((uint)Si[(r1 >> 8) & 255]) << 8) ^ (((uint)Si[(r0 >> 16) & 255]) << 16) ^ (((uint)s[(r3 >> 24) & 255]) << 24) ^ kw[2]; + this.C3 = (uint)Si[r3 & 255] ^ (((uint)s[(r2 >> 8) & 255]) << 8) ^ (((uint)s[(r1 >> 16) & 255]) << 16) ^ (((uint)s[(r0 >> 24) & 255]) << 24) ^ kw[3]; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/engines/AesFastEngine.cs b/bc-sharp-crypto/src/crypto/engines/AesFastEngine.cs new file mode 100644 index 0000000..9d3a86f --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/AesFastEngine.cs @@ -0,0 +1,948 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Utilities; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Engines +{ + /** + * 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 + *

+ */ + /// + /// Unfortunately this class has a few side channel issues. + /// In an environment where encryption/decryption may be closely observed it should not be used. + /// + [Obsolete("Use AesEngine instead")] + public class AesFastEngine + : IBlockCipher + { + // The S box + private static readonly byte[] S = + { + 99, 124, 119, 123, 242, 107, 111, 197, + 48, 1, 103, 43, 254, 215, 171, 118, + 202, 130, 201, 125, 250, 89, 71, 240, + 173, 212, 162, 175, 156, 164, 114, 192, + 183, 253, 147, 38, 54, 63, 247, 204, + 52, 165, 229, 241, 113, 216, 49, 21, + 4, 199, 35, 195, 24, 150, 5, 154, + 7, 18, 128, 226, 235, 39, 178, 117, + 9, 131, 44, 26, 27, 110, 90, 160, + 82, 59, 214, 179, 41, 227, 47, 132, + 83, 209, 0, 237, 32, 252, 177, 91, + 106, 203, 190, 57, 74, 76, 88, 207, + 208, 239, 170, 251, 67, 77, 51, 133, + 69, 249, 2, 127, 80, 60, 159, 168, + 81, 163, 64, 143, 146, 157, 56, 245, + 188, 182, 218, 33, 16, 255, 243, 210, + 205, 12, 19, 236, 95, 151, 68, 23, + 196, 167, 126, 61, 100, 93, 25, 115, + 96, 129, 79, 220, 34, 42, 144, 136, + 70, 238, 184, 20, 222, 94, 11, 219, + 224, 50, 58, 10, 73, 6, 36, 92, + 194, 211, 172, 98, 145, 149, 228, 121, + 231, 200, 55, 109, 141, 213, 78, 169, + 108, 86, 244, 234, 101, 122, 174, 8, + 186, 120, 37, 46, 28, 166, 180, 198, + 232, 221, 116, 31, 75, 189, 139, 138, + 112, 62, 181, 102, 72, 3, 246, 14, + 97, 53, 87, 185, 134, 193, 29, 158, + 225, 248, 152, 17, 105, 217, 142, 148, + 155, 30, 135, 233, 206, 85, 40, 223, + 140, 161, 137, 13, 191, 230, 66, 104, + 65, 153, 45, 15, 176, 84, 187, 22, + }; + + // The inverse S-box + private static readonly byte[] Si = + { + 82, 9, 106, 213, 48, 54, 165, 56, + 191, 64, 163, 158, 129, 243, 215, 251, + 124, 227, 57, 130, 155, 47, 255, 135, + 52, 142, 67, 68, 196, 222, 233, 203, + 84, 123, 148, 50, 166, 194, 35, 61, + 238, 76, 149, 11, 66, 250, 195, 78, + 8, 46, 161, 102, 40, 217, 36, 178, + 118, 91, 162, 73, 109, 139, 209, 37, + 114, 248, 246, 100, 134, 104, 152, 22, + 212, 164, 92, 204, 93, 101, 182, 146, + 108, 112, 72, 80, 253, 237, 185, 218, + 94, 21, 70, 87, 167, 141, 157, 132, + 144, 216, 171, 0, 140, 188, 211, 10, + 247, 228, 88, 5, 184, 179, 69, 6, + 208, 44, 30, 143, 202, 63, 15, 2, + 193, 175, 189, 3, 1, 19, 138, 107, + 58, 145, 17, 65, 79, 103, 220, 234, + 151, 242, 207, 206, 240, 180, 230, 115, + 150, 172, 116, 34, 231, 173, 53, 133, + 226, 249, 55, 232, 28, 117, 223, 110, + 71, 241, 26, 113, 29, 41, 197, 137, + 111, 183, 98, 14, 170, 24, 190, 27, + 252, 86, 62, 75, 198, 210, 121, 32, + 154, 219, 192, 254, 120, 205, 90, 244, + 31, 221, 168, 51, 136, 7, 199, 49, + 177, 18, 16, 89, 39, 128, 236, 95, + 96, 81, 127, 169, 25, 181, 74, 13, + 45, 229, 122, 159, 147, 201, 156, 239, + 160, 224, 59, 77, 174, 42, 245, 176, + 200, 235, 187, 60, 131, 83, 153, 97, + 23, 43, 4, 126, 186, 119, 214, 38, + 225, 105, 20, 99, 85, 33, 12, 125, + }; + + // vector used in calculating key schedule (powers of x in GF(256)) + private static readonly byte[] 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 readonly uint[] 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 readonly uint[] 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 readonly uint[] 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 readonly uint[] 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 readonly uint[] 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 readonly uint[] 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 readonly uint[] 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 readonly uint[] 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 static uint Shift(uint r, int shift) + { + return (r >> shift) | (r << (32 - shift)); + } + + /* multiply four bytes in GF(2^8) by 'x' {02} in parallel */ + + private const uint m1 = 0x80808080; + private const uint m2 = 0x7f7f7f7f; + private const uint m3 = 0x0000001b; + private const uint m4 = 0xC0C0C0C0; + private const uint m5 = 0x3f3f3f3f; + + private static uint FFmulX(uint x) + { + return ((x & m2) << 1) ^ (((x & m1) >> 7) * m3); + } + + private static uint FFmulX2(uint x) + { + uint t0 = (x & m5) << 2; + uint t1 = (x & m4); + t1 ^= (t1 >> 1); + return t0 ^ (t1 >> 2) ^ (t1 >> 5); + } + + /* + 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 static uint Inv_Mcol(uint x) + { + uint t0, t1; + t0 = x; + t1 = t0 ^ Shift(t0, 8); + t0 ^= FFmulX(t1); + t1 ^= FFmulX2(t0); + t0 ^= t1 ^ Shift(t1, 16); + return t0; + } + + private static uint SubWord(uint x) + { + return (uint)S[x&255] + | (((uint)S[(x>>8)&255]) << 8) + | (((uint)S[(x>>16)&255]) << 16) + | (((uint)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 uint[][] GenerateWorkingKey(byte[] key, bool forEncryption) + { + int keyLen = key.Length; + if (keyLen < 16 || keyLen > 32 || (keyLen & 7) != 0) + throw new ArgumentException("Key length not 128/192/256 bits."); + + int KC = keyLen >> 2; + this.ROUNDS = KC + 6; // This is not always true for the generalized Rijndael that allows larger block sizes + + uint[][] W = new uint[ROUNDS + 1][]; // 4 words in a block + for (int i = 0; i <= ROUNDS; ++i) + { + W[i] = new uint[4]; + } + + switch (KC) + { + case 4: + { + uint t0 = Pack.LE_To_UInt32(key, 0); W[0][0] = t0; + uint t1 = Pack.LE_To_UInt32(key, 4); W[0][1] = t1; + uint t2 = Pack.LE_To_UInt32(key, 8); W[0][2] = t2; + uint t3 = Pack.LE_To_UInt32(key, 12); W[0][3] = t3; + + for (int i = 1; i <= 10; ++i) + { + uint u = SubWord(Shift(t3, 8)) ^ rcon[i - 1]; + t0 ^= u; W[i][0] = t0; + t1 ^= t0; W[i][1] = t1; + t2 ^= t1; W[i][2] = t2; + t3 ^= t2; W[i][3] = t3; + } + + break; + } + case 6: + { + uint t0 = Pack.LE_To_UInt32(key, 0); W[0][0] = t0; + uint t1 = Pack.LE_To_UInt32(key, 4); W[0][1] = t1; + uint t2 = Pack.LE_To_UInt32(key, 8); W[0][2] = t2; + uint t3 = Pack.LE_To_UInt32(key, 12); W[0][3] = t3; + uint t4 = Pack.LE_To_UInt32(key, 16); W[1][0] = t4; + uint t5 = Pack.LE_To_UInt32(key, 20); W[1][1] = t5; + + uint rcon = 1; + uint u = SubWord(Shift(t5, 8)) ^ rcon; rcon <<= 1; + t0 ^= u; W[1][2] = t0; + t1 ^= t0; W[1][3] = t1; + t2 ^= t1; W[2][0] = t2; + t3 ^= t2; W[2][1] = t3; + t4 ^= t3; W[2][2] = t4; + t5 ^= t4; W[2][3] = t5; + + for (int i = 3; i < 12; i += 3) + { + u = SubWord(Shift(t5, 8)) ^ rcon; rcon <<= 1; + t0 ^= u; W[i ][0] = t0; + t1 ^= t0; W[i ][1] = t1; + t2 ^= t1; W[i ][2] = t2; + t3 ^= t2; W[i ][3] = t3; + t4 ^= t3; W[i + 1][0] = t4; + t5 ^= t4; W[i + 1][1] = t5; + u = SubWord(Shift(t5, 8)) ^ rcon; rcon <<= 1; + t0 ^= u; W[i + 1][2] = t0; + t1 ^= t0; W[i + 1][3] = t1; + t2 ^= t1; W[i + 2][0] = t2; + t3 ^= t2; W[i + 2][1] = t3; + t4 ^= t3; W[i + 2][2] = t4; + t5 ^= t4; W[i + 2][3] = t5; + } + + u = SubWord(Shift(t5, 8)) ^ rcon; + t0 ^= u; W[12][0] = t0; + t1 ^= t0; W[12][1] = t1; + t2 ^= t1; W[12][2] = t2; + t3 ^= t2; W[12][3] = t3; + + break; + } + case 8: + { + uint t0 = Pack.LE_To_UInt32(key, 0); W[0][0] = t0; + uint t1 = Pack.LE_To_UInt32(key, 4); W[0][1] = t1; + uint t2 = Pack.LE_To_UInt32(key, 8); W[0][2] = t2; + uint t3 = Pack.LE_To_UInt32(key, 12); W[0][3] = t3; + uint t4 = Pack.LE_To_UInt32(key, 16); W[1][0] = t4; + uint t5 = Pack.LE_To_UInt32(key, 20); W[1][1] = t5; + uint t6 = Pack.LE_To_UInt32(key, 24); W[1][2] = t6; + uint t7 = Pack.LE_To_UInt32(key, 28); W[1][3] = t7; + + uint u, rcon = 1; + + for (int i = 2; i < 14; i += 2) + { + u = SubWord(Shift(t7, 8)) ^ rcon; rcon <<= 1; + t0 ^= u; W[i ][0] = t0; + t1 ^= t0; W[i ][1] = t1; + t2 ^= t1; W[i ][2] = t2; + t3 ^= t2; W[i ][3] = t3; + u = SubWord(t3); + t4 ^= u; W[i + 1][0] = t4; + t5 ^= t4; W[i + 1][1] = t5; + t6 ^= t5; W[i + 1][2] = t6; + t7 ^= t6; W[i + 1][3] = t7; + } + + u = SubWord(Shift(t7, 8)) ^ rcon; + t0 ^= u; W[14][0] = t0; + t1 ^= t0; W[14][1] = t1; + t2 ^= t1; W[14][2] = t2; + t3 ^= t2; W[14][3] = t3; + + break; + } + default: + { + throw new InvalidOperationException("Should never get here"); + } + } + + if (!forEncryption) + { + for (int j = 1; j < ROUNDS; j++) + { + uint[] w = W[j]; + for (int i = 0; i < 4; i++) + { + w[i] = Inv_Mcol(w[i]); + } + } + } + + return W; + } + + private int ROUNDS; + private uint[][] WorkingKey; + private uint C0, C1, C2, C3; + private bool forEncryption; + + private const 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 parameters the parameters required to set up the cipher. + * @exception ArgumentException if the parameters argument is + * inappropriate. + */ + public virtual void Init( + bool forEncryption, + ICipherParameters parameters) + { + KeyParameter keyParameter = parameters as KeyParameter; + + if (keyParameter == null) + throw new ArgumentException("invalid parameter passed to AES init - " + + Platform.GetTypeName(parameters)); + + WorkingKey = GenerateWorkingKey(keyParameter.GetKey(), forEncryption); + + this.forEncryption = forEncryption; + } + + public virtual string AlgorithmName + { + get { return "AES"; } + } + + public virtual bool IsPartialBlockOkay + { + get { return false; } + } + + public virtual int GetBlockSize() + { + return BLOCK_SIZE; + } + + public virtual int ProcessBlock( + byte[] input, + int inOff, + byte[] output, + int outOff) + { + if (WorkingKey == null) + throw new InvalidOperationException("AES engine not initialised"); + + Check.DataLength(input, inOff, 16, "input buffer too short"); + Check.OutputLength(output, outOff, 16, "output buffer too short"); + + UnPackBlock(input, inOff); + + if (forEncryption) + { + EncryptBlock(WorkingKey); + } + else + { + DecryptBlock(WorkingKey); + } + + PackBlock(output, outOff); + + return BLOCK_SIZE; + } + + public virtual void Reset() + { + } + + private void UnPackBlock( + byte[] bytes, + int off) + { + C0 = Pack.LE_To_UInt32(bytes, off); + C1 = Pack.LE_To_UInt32(bytes, off + 4); + C2 = Pack.LE_To_UInt32(bytes, off + 8); + C3 = Pack.LE_To_UInt32(bytes, off + 12); + } + + private void PackBlock( + byte[] bytes, + int off) + { + Pack.UInt32_To_LE(C0, bytes, off); + Pack.UInt32_To_LE(C1, bytes, off + 4); + Pack.UInt32_To_LE(C2, bytes, off + 8); + Pack.UInt32_To_LE(C3, bytes, off + 12); + } + + private void EncryptBlock(uint[][] KW) + { + uint[] kw = KW[0]; + uint t0 = this.C0 ^ kw[0]; + uint t1 = this.C1 ^ kw[1]; + uint t2 = this.C2 ^ kw[2]; + + uint r0, r1, r2, r3 = this.C3 ^ kw[3]; + int r = 1; + while (r < ROUNDS - 1) + { + kw = KW[r++]; + r0 = T0[t0 & 255] ^ T1[(t1 >> 8) & 255] ^ T2[(t2 >> 16) & 255] ^ T3[r3 >> 24] ^ kw[0]; + r1 = T0[t1 & 255] ^ T1[(t2 >> 8) & 255] ^ T2[(r3 >> 16) & 255] ^ T3[t0 >> 24] ^ kw[1]; + r2 = T0[t2 & 255] ^ T1[(r3 >> 8) & 255] ^ T2[(t0 >> 16) & 255] ^ T3[t1 >> 24] ^ kw[2]; + r3 = T0[r3 & 255] ^ T1[(t0 >> 8) & 255] ^ T2[(t1 >> 16) & 255] ^ T3[t2 >> 24] ^ kw[3]; + kw = KW[r++]; + t0 = T0[r0 & 255] ^ T1[(r1 >> 8) & 255] ^ T2[(r2 >> 16) & 255] ^ T3[r3 >> 24] ^ kw[0]; + t1 = T0[r1 & 255] ^ T1[(r2 >> 8) & 255] ^ T2[(r3 >> 16) & 255] ^ T3[r0 >> 24] ^ kw[1]; + t2 = T0[r2 & 255] ^ T1[(r3 >> 8) & 255] ^ T2[(r0 >> 16) & 255] ^ T3[r1 >> 24] ^ kw[2]; + r3 = T0[r3 & 255] ^ T1[(r0 >> 8) & 255] ^ T2[(r1 >> 16) & 255] ^ T3[r2 >> 24] ^ kw[3]; + } + + kw = KW[r++]; + r0 = T0[t0 & 255] ^ T1[(t1 >> 8) & 255] ^ T2[(t2 >> 16) & 255] ^ T3[r3 >> 24] ^ kw[0]; + r1 = T0[t1 & 255] ^ T1[(t2 >> 8) & 255] ^ T2[(r3 >> 16) & 255] ^ T3[t0 >> 24] ^ kw[1]; + r2 = T0[t2 & 255] ^ T1[(r3 >> 8) & 255] ^ T2[(t0 >> 16) & 255] ^ T3[t1 >> 24] ^ kw[2]; + r3 = T0[r3 & 255] ^ T1[(t0 >> 8) & 255] ^ T2[(t1 >> 16) & 255] ^ T3[t2 >> 24] ^ kw[3]; + + // the final round's table is a simple function of S so we don't use a whole other four tables for it + + kw = KW[r]; + this.C0 = (uint)S[r0 & 255] ^ (((uint)S[(r1 >> 8) & 255]) << 8) ^ (((uint)S[(r2 >> 16) & 255]) << 16) ^ (((uint)S[r3 >> 24]) << 24) ^ kw[0]; + this.C1 = (uint)S[r1 & 255] ^ (((uint)S[(r2 >> 8) & 255]) << 8) ^ (((uint)S[(r3 >> 16) & 255]) << 16) ^ (((uint)S[r0 >> 24]) << 24) ^ kw[1]; + this.C2 = (uint)S[r2 & 255] ^ (((uint)S[(r3 >> 8) & 255]) << 8) ^ (((uint)S[(r0 >> 16) & 255]) << 16) ^ (((uint)S[r1 >> 24]) << 24) ^ kw[2]; + this.C3 = (uint)S[r3 & 255] ^ (((uint)S[(r0 >> 8) & 255]) << 8) ^ (((uint)S[(r1 >> 16) & 255]) << 16) ^ (((uint)S[r2 >> 24]) << 24) ^ kw[3]; + } + + private void DecryptBlock(uint[][] KW) + { + uint[] kw = KW[ROUNDS]; + uint t0 = this.C0 ^ kw[0]; + uint t1 = this.C1 ^ kw[1]; + uint t2 = this.C2 ^ kw[2]; + + uint r0, r1, r2, r3 = this.C3 ^ kw[3]; + int r = ROUNDS - 1; + while (r > 1) + { + kw = KW[r--]; + r0 = Tinv0[t0 & 255] ^ Tinv1[(r3 >> 8) & 255] ^ Tinv2[(t2 >> 16) & 255] ^ Tinv3[t1 >> 24] ^ kw[0]; + r1 = Tinv0[t1 & 255] ^ Tinv1[(t0 >> 8) & 255] ^ Tinv2[(r3 >> 16) & 255] ^ Tinv3[t2 >> 24] ^ kw[1]; + r2 = Tinv0[t2 & 255] ^ Tinv1[(t1 >> 8) & 255] ^ Tinv2[(t0 >> 16) & 255] ^ Tinv3[r3 >> 24] ^ kw[2]; + r3 = Tinv0[r3 & 255] ^ Tinv1[(t2 >> 8) & 255] ^ Tinv2[(t1 >> 16) & 255] ^ Tinv3[t0 >> 24] ^ kw[3]; + kw = KW[r--]; + t0 = Tinv0[r0 & 255] ^ Tinv1[(r3 >> 8) & 255] ^ Tinv2[(r2 >> 16) & 255] ^ Tinv3[r1 >> 24] ^ kw[0]; + t1 = Tinv0[r1 & 255] ^ Tinv1[(r0 >> 8) & 255] ^ Tinv2[(r3 >> 16) & 255] ^ Tinv3[r2 >> 24] ^ kw[1]; + t2 = Tinv0[r2 & 255] ^ Tinv1[(r1 >> 8) & 255] ^ Tinv2[(r0 >> 16) & 255] ^ Tinv3[r3 >> 24] ^ kw[2]; + r3 = Tinv0[r3 & 255] ^ Tinv1[(r2 >> 8) & 255] ^ Tinv2[(r1 >> 16) & 255] ^ Tinv3[r0 >> 24] ^ kw[3]; + } + + kw = KW[1]; + r0 = Tinv0[t0 & 255] ^ Tinv1[(r3 >> 8) & 255] ^ Tinv2[(t2 >> 16) & 255] ^ Tinv3[t1 >> 24] ^ kw[0]; + r1 = Tinv0[t1 & 255] ^ Tinv1[(t0 >> 8) & 255] ^ Tinv2[(r3 >> 16) & 255] ^ Tinv3[t2 >> 24] ^ kw[1]; + r2 = Tinv0[t2 & 255] ^ Tinv1[(t1 >> 8) & 255] ^ Tinv2[(t0 >> 16) & 255] ^ Tinv3[r3 >> 24] ^ kw[2]; + r3 = Tinv0[r3 & 255] ^ Tinv1[(t2 >> 8) & 255] ^ Tinv2[(t1 >> 16) & 255] ^ Tinv3[t0 >> 24] ^ kw[3]; + + // the final round's table is a simple function of Si so we don't use a whole other four tables for it + + kw = KW[0]; + this.C0 = (uint)Si[r0 & 255] ^ (((uint)Si[(r3 >> 8) & 255]) << 8) ^ (((uint)Si[(r2 >> 16) & 255]) << 16) ^ (((uint)Si[r1 >> 24]) << 24) ^ kw[0]; + this.C1 = (uint)Si[r1 & 255] ^ (((uint)Si[(r0 >> 8) & 255]) << 8) ^ (((uint)Si[(r3 >> 16) & 255]) << 16) ^ (((uint)Si[r2 >> 24]) << 24) ^ kw[1]; + this.C2 = (uint)Si[r2 & 255] ^ (((uint)Si[(r1 >> 8) & 255]) << 8) ^ (((uint)Si[(r0 >> 16) & 255]) << 16) ^ (((uint)Si[r3 >> 24]) << 24) ^ kw[2]; + this.C3 = (uint)Si[r3 & 255] ^ (((uint)Si[(r2 >> 8) & 255]) << 8) ^ (((uint)Si[(r1 >> 16) & 255]) << 16) ^ (((uint)Si[r0 >> 24]) << 24) ^ kw[3]; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/engines/AesLightEngine.cs b/bc-sharp-crypto/src/crypto/engines/AesLightEngine.cs new file mode 100644 index 0000000..9cc9c34 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/AesLightEngine.cs @@ -0,0 +1,504 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Utilities; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Engines +{ + /** + * 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 + : IBlockCipher + { + // The S box + private static readonly byte[] S = + { + 99, 124, 119, 123, 242, 107, 111, 197, + 48, 1, 103, 43, 254, 215, 171, 118, + 202, 130, 201, 125, 250, 89, 71, 240, + 173, 212, 162, 175, 156, 164, 114, 192, + 183, 253, 147, 38, 54, 63, 247, 204, + 52, 165, 229, 241, 113, 216, 49, 21, + 4, 199, 35, 195, 24, 150, 5, 154, + 7, 18, 128, 226, 235, 39, 178, 117, + 9, 131, 44, 26, 27, 110, 90, 160, + 82, 59, 214, 179, 41, 227, 47, 132, + 83, 209, 0, 237, 32, 252, 177, 91, + 106, 203, 190, 57, 74, 76, 88, 207, + 208, 239, 170, 251, 67, 77, 51, 133, + 69, 249, 2, 127, 80, 60, 159, 168, + 81, 163, 64, 143, 146, 157, 56, 245, + 188, 182, 218, 33, 16, 255, 243, 210, + 205, 12, 19, 236, 95, 151, 68, 23, + 196, 167, 126, 61, 100, 93, 25, 115, + 96, 129, 79, 220, 34, 42, 144, 136, + 70, 238, 184, 20, 222, 94, 11, 219, + 224, 50, 58, 10, 73, 6, 36, 92, + 194, 211, 172, 98, 145, 149, 228, 121, + 231, 200, 55, 109, 141, 213, 78, 169, + 108, 86, 244, 234, 101, 122, 174, 8, + 186, 120, 37, 46, 28, 166, 180, 198, + 232, 221, 116, 31, 75, 189, 139, 138, + 112, 62, 181, 102, 72, 3, 246, 14, + 97, 53, 87, 185, 134, 193, 29, 158, + 225, 248, 152, 17, 105, 217, 142, 148, + 155, 30, 135, 233, 206, 85, 40, 223, + 140, 161, 137, 13, 191, 230, 66, 104, + 65, 153, 45, 15, 176, 84, 187, 22, + }; + + // The inverse S-box + private static readonly byte[] Si = + { + 82, 9, 106, 213, 48, 54, 165, 56, + 191, 64, 163, 158, 129, 243, 215, 251, + 124, 227, 57, 130, 155, 47, 255, 135, + 52, 142, 67, 68, 196, 222, 233, 203, + 84, 123, 148, 50, 166, 194, 35, 61, + 238, 76, 149, 11, 66, 250, 195, 78, + 8, 46, 161, 102, 40, 217, 36, 178, + 118, 91, 162, 73, 109, 139, 209, 37, + 114, 248, 246, 100, 134, 104, 152, 22, + 212, 164, 92, 204, 93, 101, 182, 146, + 108, 112, 72, 80, 253, 237, 185, 218, + 94, 21, 70, 87, 167, 141, 157, 132, + 144, 216, 171, 0, 140, 188, 211, 10, + 247, 228, 88, 5, 184, 179, 69, 6, + 208, 44, 30, 143, 202, 63, 15, 2, + 193, 175, 189, 3, 1, 19, 138, 107, + 58, 145, 17, 65, 79, 103, 220, 234, + 151, 242, 207, 206, 240, 180, 230, 115, + 150, 172, 116, 34, 231, 173, 53, 133, + 226, 249, 55, 232, 28, 117, 223, 110, + 71, 241, 26, 113, 29, 41, 197, 137, + 111, 183, 98, 14, 170, 24, 190, 27, + 252, 86, 62, 75, 198, 210, 121, 32, + 154, 219, 192, 254, 120, 205, 90, 244, + 31, 221, 168, 51, 136, 7, 199, 49, + 177, 18, 16, 89, 39, 128, 236, 95, + 96, 81, 127, 169, 25, 181, 74, 13, + 45, 229, 122, 159, 147, 201, 156, 239, + 160, 224, 59, 77, 174, 42, 245, 176, + 200, 235, 187, 60, 131, 83, 153, 97, + 23, 43, 4, 126, 186, 119, 214, 38, + 225, 105, 20, 99, 85, 33, 12, 125, + }; + + // vector used in calculating key schedule (powers of x in GF(256)) + private static readonly byte[] 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 static uint Shift(uint r, int shift) + { + return (r >> shift) | (r << (32 - shift)); + } + + /* multiply four bytes in GF(2^8) by 'x' {02} in parallel */ + + private const uint m1 = 0x80808080; + private const uint m2 = 0x7f7f7f7f; + private const uint m3 = 0x0000001b; + private const uint m4 = 0xC0C0C0C0; + private const uint m5 = 0x3f3f3f3f; + + private static uint FFmulX(uint x) + { + return ((x & m2) << 1) ^ (((x & m1) >> 7) * m3); + } + + private static uint FFmulX2(uint x) + { + uint t0 = (x & m5) << 2; + uint t1 = (x & m4); + t1 ^= (t1 >> 1); + return t0 ^ (t1 >> 2) ^ (t1 >> 5); + } + + /* + 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 static uint Mcol(uint x) + { + uint t0, t1; + t0 = Shift(x, 8); + t1 = x ^ t0; + return Shift(t1, 16) ^ t0 ^ FFmulX(t1); + } + + private static uint Inv_Mcol(uint x) + { + uint t0, t1; + t0 = x; + t1 = t0 ^ Shift(t0, 8); + t0 ^= FFmulX(t1); + t1 ^= FFmulX2(t0); + t0 ^= t1 ^ Shift(t1, 16); + return t0; + } + + private static uint SubWord(uint x) + { + return (uint)S[x&255] + | (((uint)S[(x>>8)&255]) << 8) + | (((uint)S[(x>>16)&255]) << 16) + | (((uint)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 uint[][] GenerateWorkingKey(byte[] key, bool forEncryption) + { + int keyLen = key.Length; + if (keyLen < 16 || keyLen > 32 || (keyLen & 7) != 0) + throw new ArgumentException("Key length not 128/192/256 bits."); + + int KC = keyLen >> 2; + this.ROUNDS = KC + 6; // This is not always true for the generalized Rijndael that allows larger block sizes + + uint[][] W = new uint[ROUNDS + 1][]; // 4 words in a block + for (int i = 0; i <= ROUNDS; ++i) + { + W[i] = new uint[4]; + } + + switch (KC) + { + case 4: + { + uint t0 = Pack.LE_To_UInt32(key, 0); W[0][0] = t0; + uint t1 = Pack.LE_To_UInt32(key, 4); W[0][1] = t1; + uint t2 = Pack.LE_To_UInt32(key, 8); W[0][2] = t2; + uint t3 = Pack.LE_To_UInt32(key, 12); W[0][3] = t3; + + for (int i = 1; i <= 10; ++i) + { + uint u = SubWord(Shift(t3, 8)) ^ rcon[i - 1]; + t0 ^= u; W[i][0] = t0; + t1 ^= t0; W[i][1] = t1; + t2 ^= t1; W[i][2] = t2; + t3 ^= t2; W[i][3] = t3; + } + + break; + } + case 6: + { + uint t0 = Pack.LE_To_UInt32(key, 0); W[0][0] = t0; + uint t1 = Pack.LE_To_UInt32(key, 4); W[0][1] = t1; + uint t2 = Pack.LE_To_UInt32(key, 8); W[0][2] = t2; + uint t3 = Pack.LE_To_UInt32(key, 12); W[0][3] = t3; + uint t4 = Pack.LE_To_UInt32(key, 16); W[1][0] = t4; + uint t5 = Pack.LE_To_UInt32(key, 20); W[1][1] = t5; + + uint rcon = 1; + uint u = SubWord(Shift(t5, 8)) ^ rcon; rcon <<= 1; + t0 ^= u; W[1][2] = t0; + t1 ^= t0; W[1][3] = t1; + t2 ^= t1; W[2][0] = t2; + t3 ^= t2; W[2][1] = t3; + t4 ^= t3; W[2][2] = t4; + t5 ^= t4; W[2][3] = t5; + + for (int i = 3; i < 12; i += 3) + { + u = SubWord(Shift(t5, 8)) ^ rcon; rcon <<= 1; + t0 ^= u; W[i ][0] = t0; + t1 ^= t0; W[i ][1] = t1; + t2 ^= t1; W[i ][2] = t2; + t3 ^= t2; W[i ][3] = t3; + t4 ^= t3; W[i + 1][0] = t4; + t5 ^= t4; W[i + 1][1] = t5; + u = SubWord(Shift(t5, 8)) ^ rcon; rcon <<= 1; + t0 ^= u; W[i + 1][2] = t0; + t1 ^= t0; W[i + 1][3] = t1; + t2 ^= t1; W[i + 2][0] = t2; + t3 ^= t2; W[i + 2][1] = t3; + t4 ^= t3; W[i + 2][2] = t4; + t5 ^= t4; W[i + 2][3] = t5; + } + + u = SubWord(Shift(t5, 8)) ^ rcon; + t0 ^= u; W[12][0] = t0; + t1 ^= t0; W[12][1] = t1; + t2 ^= t1; W[12][2] = t2; + t3 ^= t2; W[12][3] = t3; + + break; + } + case 8: + { + uint t0 = Pack.LE_To_UInt32(key, 0); W[0][0] = t0; + uint t1 = Pack.LE_To_UInt32(key, 4); W[0][1] = t1; + uint t2 = Pack.LE_To_UInt32(key, 8); W[0][2] = t2; + uint t3 = Pack.LE_To_UInt32(key, 12); W[0][3] = t3; + uint t4 = Pack.LE_To_UInt32(key, 16); W[1][0] = t4; + uint t5 = Pack.LE_To_UInt32(key, 20); W[1][1] = t5; + uint t6 = Pack.LE_To_UInt32(key, 24); W[1][2] = t6; + uint t7 = Pack.LE_To_UInt32(key, 28); W[1][3] = t7; + + uint u, rcon = 1; + + for (int i = 2; i < 14; i += 2) + { + u = SubWord(Shift(t7, 8)) ^ rcon; rcon <<= 1; + t0 ^= u; W[i ][0] = t0; + t1 ^= t0; W[i ][1] = t1; + t2 ^= t1; W[i ][2] = t2; + t3 ^= t2; W[i ][3] = t3; + u = SubWord(t3); + t4 ^= u; W[i + 1][0] = t4; + t5 ^= t4; W[i + 1][1] = t5; + t6 ^= t5; W[i + 1][2] = t6; + t7 ^= t6; W[i + 1][3] = t7; + } + + u = SubWord(Shift(t7, 8)) ^ rcon; + t0 ^= u; W[14][0] = t0; + t1 ^= t0; W[14][1] = t1; + t2 ^= t1; W[14][2] = t2; + t3 ^= t2; W[14][3] = t3; + + break; + } + default: + { + throw new InvalidOperationException("Should never get here"); + } + } + + if (!forEncryption) + { + for (int j = 1; j < ROUNDS; j++) + { + uint[] w = W[j]; + for (int i = 0; i < 4; i++) + { + w[i] = Inv_Mcol(w[i]); + } + } + } + + return W; + } + + private int ROUNDS; + private uint[][] WorkingKey; + private uint C0, C1, C2, C3; + private bool forEncryption; + + private const 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 parameters the parameters required to set up the cipher. + * @exception ArgumentException if the parameters argument is + * inappropriate. + */ + public virtual void Init( + bool forEncryption, + ICipherParameters parameters) + { + KeyParameter keyParameter = parameters as KeyParameter; + + if (keyParameter == null) + throw new ArgumentException("invalid parameter passed to AES init - " + + Platform.GetTypeName(parameters)); + + WorkingKey = GenerateWorkingKey(keyParameter.GetKey(), forEncryption); + + this.forEncryption = forEncryption; + } + + public virtual string AlgorithmName + { + get { return "AES"; } + } + + public virtual bool IsPartialBlockOkay + { + get { return false; } + } + + public virtual int GetBlockSize() + { + return BLOCK_SIZE; + } + + public virtual int ProcessBlock( + byte[] input, + int inOff, + byte[] output, + int outOff) + { + if (WorkingKey == null) + throw new InvalidOperationException("AES engine not initialised"); + + Check.DataLength(input, inOff, 16, "input buffer too short"); + Check.OutputLength(output, outOff, 16, "output buffer too short"); + + UnPackBlock(input, inOff); + + if (forEncryption) + { + EncryptBlock(WorkingKey); + } + else + { + DecryptBlock(WorkingKey); + } + + PackBlock(output, outOff); + + return BLOCK_SIZE; + } + + public virtual void Reset() + { + } + + private void UnPackBlock( + byte[] bytes, + int off) + { + C0 = Pack.LE_To_UInt32(bytes, off); + C1 = Pack.LE_To_UInt32(bytes, off + 4); + C2 = Pack.LE_To_UInt32(bytes, off + 8); + C3 = Pack.LE_To_UInt32(bytes, off + 12); + } + + private void PackBlock( + byte[] bytes, + int off) + { + Pack.UInt32_To_LE(C0, bytes, off); + Pack.UInt32_To_LE(C1, bytes, off + 4); + Pack.UInt32_To_LE(C2, bytes, off + 8); + Pack.UInt32_To_LE(C3, bytes, off + 12); + } + + private void EncryptBlock(uint[][] KW) + { + uint[] kw = KW[0]; + uint t0 = this.C0 ^ kw[0]; + uint t1 = this.C1 ^ kw[1]; + uint t2 = this.C2 ^ kw[2]; + + uint r0, r1, r2, r3 = this.C3 ^ kw[3]; + int r = 1; + while (r < ROUNDS - 1) + { + kw = KW[r++]; + r0 = Mcol((uint)S[t0 & 255] ^ (((uint)S[(t1 >> 8) & 255]) << 8) ^ (((uint)S[(t2 >> 16) & 255]) << 16) ^ (((uint)S[(r3 >> 24) & 255]) << 24)) ^ kw[0]; + r1 = Mcol((uint)S[t1 & 255] ^ (((uint)S[(t2 >> 8) & 255]) << 8) ^ (((uint)S[(r3 >> 16) & 255]) << 16) ^ (((uint)S[(t0 >> 24) & 255]) << 24)) ^ kw[1]; + r2 = Mcol((uint)S[t2 & 255] ^ (((uint)S[(r3 >> 8) & 255]) << 8) ^ (((uint)S[(t0 >> 16) & 255]) << 16) ^ (((uint)S[(t1 >> 24) & 255]) << 24)) ^ kw[2]; + r3 = Mcol((uint)S[r3 & 255] ^ (((uint)S[(t0 >> 8) & 255]) << 8) ^ (((uint)S[(t1 >> 16) & 255]) << 16) ^ (((uint)S[(t2 >> 24) & 255]) << 24)) ^ kw[3]; + kw = KW[r++]; + t0 = Mcol((uint)S[r0 & 255] ^ (((uint)S[(r1 >> 8) & 255]) << 8) ^ (((uint)S[(r2 >> 16) & 255]) << 16) ^ (((uint)S[(r3 >> 24) & 255]) << 24)) ^ kw[0]; + t1 = Mcol((uint)S[r1 & 255] ^ (((uint)S[(r2 >> 8) & 255]) << 8) ^ (((uint)S[(r3 >> 16) & 255]) << 16) ^ (((uint)S[(r0 >> 24) & 255]) << 24)) ^ kw[1]; + t2 = Mcol((uint)S[r2 & 255] ^ (((uint)S[(r3 >> 8) & 255]) << 8) ^ (((uint)S[(r0 >> 16) & 255]) << 16) ^ (((uint)S[(r1 >> 24) & 255]) << 24)) ^ kw[2]; + r3 = Mcol((uint)S[r3 & 255] ^ (((uint)S[(r0 >> 8) & 255]) << 8) ^ (((uint)S[(r1 >> 16) & 255]) << 16) ^ (((uint)S[(r2 >> 24) & 255]) << 24)) ^ kw[3]; + } + + kw = KW[r++]; + r0 = Mcol((uint)S[t0 & 255] ^ (((uint)S[(t1 >> 8) & 255]) << 8) ^ (((uint)S[(t2 >> 16) & 255]) << 16) ^ (((uint)S[(r3 >> 24) & 255]) << 24)) ^ kw[0]; + r1 = Mcol((uint)S[t1 & 255] ^ (((uint)S[(t2 >> 8) & 255]) << 8) ^ (((uint)S[(r3 >> 16) & 255]) << 16) ^ (((uint)S[(t0 >> 24) & 255]) << 24)) ^ kw[1]; + r2 = Mcol((uint)S[t2 & 255] ^ (((uint)S[(r3 >> 8) & 255]) << 8) ^ (((uint)S[(t0 >> 16) & 255]) << 16) ^ (((uint)S[(t1 >> 24) & 255]) << 24)) ^ kw[2]; + r3 = Mcol((uint)S[r3 & 255] ^ (((uint)S[(t0 >> 8) & 255]) << 8) ^ (((uint)S[(t1 >> 16) & 255]) << 16) ^ (((uint)S[(t2 >> 24) & 255]) << 24)) ^ kw[3]; + + // the final round is a simple function of S + + kw = KW[r]; + this.C0 = (uint)S[r0 & 255] ^ (((uint)S[(r1 >> 8) & 255]) << 8) ^ (((uint)S[(r2 >> 16) & 255]) << 16) ^ (((uint)S[(r3 >> 24) & 255]) << 24) ^ kw[0]; + this.C1 = (uint)S[r1 & 255] ^ (((uint)S[(r2 >> 8) & 255]) << 8) ^ (((uint)S[(r3 >> 16) & 255]) << 16) ^ (((uint)S[(r0 >> 24) & 255]) << 24) ^ kw[1]; + this.C2 = (uint)S[r2 & 255] ^ (((uint)S[(r3 >> 8) & 255]) << 8) ^ (((uint)S[(r0 >> 16) & 255]) << 16) ^ (((uint)S[(r1 >> 24) & 255]) << 24) ^ kw[2]; + this.C3 = (uint)S[r3 & 255] ^ (((uint)S[(r0 >> 8) & 255]) << 8) ^ (((uint)S[(r1 >> 16) & 255]) << 16) ^ (((uint)S[(r2 >> 24) & 255]) << 24) ^ kw[3]; + } + + private void DecryptBlock(uint[][] KW) + { + uint[] kw = KW[ROUNDS]; + uint t0 = this.C0 ^ kw[0]; + uint t1 = this.C1 ^ kw[1]; + uint t2 = this.C2 ^ kw[2]; + + uint r0, r1, r2, r3 = this.C3 ^ kw[3]; + int r = ROUNDS - 1; + while (r > 1) + { + kw = KW[r--]; + r0 = Inv_Mcol((uint)Si[t0 & 255] ^ (((uint)Si[(r3 >> 8) & 255]) << 8) ^ (((uint)Si[(t2 >> 16) & 255]) << 16) ^ ((uint)Si[(t1 >> 24) & 255] << 24)) ^ kw[0]; + r1 = Inv_Mcol((uint)Si[t1 & 255] ^ (((uint)Si[(t0 >> 8) & 255]) << 8) ^ (((uint)Si[(r3 >> 16) & 255]) << 16) ^ ((uint)Si[(t2 >> 24) & 255] << 24)) ^ kw[1]; + r2 = Inv_Mcol((uint)Si[t2 & 255] ^ (((uint)Si[(t1 >> 8) & 255]) << 8) ^ (((uint)Si[(t0 >> 16) & 255]) << 16) ^ ((uint)Si[(r3 >> 24) & 255] << 24)) ^ kw[2]; + r3 = Inv_Mcol((uint)Si[r3 & 255] ^ (((uint)Si[(t2 >> 8) & 255]) << 8) ^ (((uint)Si[(t1 >> 16) & 255]) << 16) ^ ((uint)Si[(t0 >> 24) & 255] << 24)) ^ kw[3]; + kw = KW[r--]; + t0 = Inv_Mcol((uint)Si[r0 & 255] ^ (((uint)Si[(r3 >> 8) & 255]) << 8) ^ (((uint)Si[(r2 >> 16) & 255]) << 16) ^ ((uint)Si[(r1 >> 24) & 255] << 24)) ^ kw[0]; + t1 = Inv_Mcol((uint)Si[r1 & 255] ^ (((uint)Si[(r0 >> 8) & 255]) << 8) ^ (((uint)Si[(r3 >> 16) & 255]) << 16) ^ ((uint)Si[(r2 >> 24) & 255] << 24)) ^ kw[1]; + t2 = Inv_Mcol((uint)Si[r2 & 255] ^ (((uint)Si[(r1 >> 8) & 255]) << 8) ^ (((uint)Si[(r0 >> 16) & 255]) << 16) ^ ((uint)Si[(r3 >> 24) & 255] << 24)) ^ kw[2]; + r3 = Inv_Mcol((uint)Si[r3 & 255] ^ (((uint)Si[(r2 >> 8) & 255]) << 8) ^ (((uint)Si[(r1 >> 16) & 255]) << 16) ^ ((uint)Si[(r0 >> 24) & 255] << 24)) ^ kw[3]; + } + + kw = KW[1]; + r0 = Inv_Mcol((uint)Si[t0 & 255] ^ (((uint)Si[(r3 >> 8) & 255]) << 8) ^ (((uint)Si[(t2 >> 16) & 255]) << 16) ^ ((uint)Si[(t1 >> 24) & 255] << 24)) ^ kw[0]; + r1 = Inv_Mcol((uint)Si[t1 & 255] ^ (((uint)Si[(t0 >> 8) & 255]) << 8) ^ (((uint)Si[(r3 >> 16) & 255]) << 16) ^ ((uint)Si[(t2 >> 24) & 255] << 24)) ^ kw[1]; + r2 = Inv_Mcol((uint)Si[t2 & 255] ^ (((uint)Si[(t1 >> 8) & 255]) << 8) ^ (((uint)Si[(t0 >> 16) & 255]) << 16) ^ ((uint)Si[(r3 >> 24) & 255] << 24)) ^ kw[2]; + r3 = Inv_Mcol((uint)Si[r3 & 255] ^ (((uint)Si[(t2 >> 8) & 255]) << 8) ^ (((uint)Si[(t1 >> 16) & 255]) << 16) ^ ((uint)Si[(t0 >> 24) & 255] << 24)) ^ kw[3]; + + // the final round's table is a simple function of Si + + kw = KW[0]; + this.C0 = (uint)Si[r0 & 255] ^ (((uint)Si[(r3 >> 8) & 255]) << 8) ^ (((uint)Si[(r2 >> 16) & 255]) << 16) ^ (((uint)Si[(r1 >> 24) & 255]) << 24) ^ kw[0]; + this.C1 = (uint)Si[r1 & 255] ^ (((uint)Si[(r0 >> 8) & 255]) << 8) ^ (((uint)Si[(r3 >> 16) & 255]) << 16) ^ (((uint)Si[(r2 >> 24) & 255]) << 24) ^ kw[1]; + this.C2 = (uint)Si[r2 & 255] ^ (((uint)Si[(r1 >> 8) & 255]) << 8) ^ (((uint)Si[(r0 >> 16) & 255]) << 16) ^ (((uint)Si[(r3 >> 24) & 255]) << 24) ^ kw[2]; + this.C3 = (uint)Si[r3 & 255] ^ (((uint)Si[(r2 >> 8) & 255]) << 8) ^ (((uint)Si[(r1 >> 16) & 255]) << 16) ^ (((uint)Si[(r0 >> 24) & 255]) << 24) ^ kw[3]; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/engines/AesWrapEngine.cs b/bc-sharp-crypto/src/crypto/engines/AesWrapEngine.cs new file mode 100644 index 0000000..1ce0154 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/AesWrapEngine.cs @@ -0,0 +1,16 @@ +namespace Org.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 + : Rfc3394WrapEngine + { + public AesWrapEngine() + : base(new AesEngine()) + { + } + } +} diff --git a/bc-sharp-crypto/src/crypto/engines/BlowfishEngine.cs b/bc-sharp-crypto/src/crypto/engines/BlowfishEngine.cs new file mode 100644 index 0000000..e38f4e8 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/BlowfishEngine.cs @@ -0,0 +1,553 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Utilities; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Engines +{ + /** + * 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 sealed class BlowfishEngine + : IBlockCipher + { + private readonly static uint[] 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 readonly int ROUNDS = 16; + private const int BLOCK_SIZE = 8; // bytes = 64 bits + private static readonly int SBOX_SK = 256; + private static readonly int P_SZ = ROUNDS+2; + + private readonly uint[] S0, S1, S2, S3; // the s-boxes + private readonly uint[] P; // the p-array + + private bool encrypting; + + private byte[] workingKey; + + public BlowfishEngine() + { + S0 = new uint[SBOX_SK]; + S1 = new uint[SBOX_SK]; + S2 = new uint[SBOX_SK]; + S3 = new uint[SBOX_SK]; + P = new uint[P_SZ]; + } + + /** + * initialise a Blowfish cipher. + * + * @param forEncryption whether or not we are for encryption. + * @param parameters the parameters required to set up the cipher. + * @exception ArgumentException if the parameters argument is + * inappropriate. + */ + public void Init( + bool forEncryption, + ICipherParameters parameters) + { + if (!(parameters is KeyParameter)) + throw new ArgumentException("invalid parameter passed to Blowfish init - " + Platform.GetTypeName(parameters)); + + this.encrypting = forEncryption; + this.workingKey = ((KeyParameter)parameters).GetKey(); + SetKey(this.workingKey); + } + + public string AlgorithmName + { + get { return "Blowfish"; } + } + + public bool IsPartialBlockOkay + { + get { return false; } + } + + public int ProcessBlock( + byte[] input, + int inOff, + byte[] output, + int outOff) + { + if (workingKey == null) + throw new InvalidOperationException("Blowfish not initialised"); + + Check.DataLength(input, inOff, BLOCK_SIZE, "input buffer too short"); + Check.OutputLength(output, outOff, BLOCK_SIZE, "output buffer too short"); + + if (encrypting) + { + EncryptBlock(input, inOff, output, outOff); + } + else + { + DecryptBlock(input, inOff, output, outOff); + } + + return BLOCK_SIZE; + } + + public void Reset() + { + } + + public int GetBlockSize() + { + return BLOCK_SIZE; + } + + //================================== + // Private Implementation + //================================== + + private uint F(uint 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( + uint xl, + uint xr, + uint[] 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...) + */ + Array.Copy(KS0, 0, S0, 0, SBOX_SK); + Array.Copy(KS1, 0, S1, 0, SBOX_SK); + Array.Copy(KS2, 0, S2, 0, SBOX_SK); + Array.Copy(KS3, 0, S3, 0, SBOX_SK); + + Array.Copy(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 + uint data = 0x0000000; + for (int j=0; j < 4; j++) + { + // create a 32 bit block + data = (data << 8) | (uint)key[keyIndex++]; + + // 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) + { + uint xl = Pack.BE_To_UInt32(src, srcIndex); + uint xr = Pack.BE_To_UInt32(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]; + + Pack.UInt32_To_BE(xr, dst, dstIndex); + Pack.UInt32_To_BE(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) + { + uint xl = Pack.BE_To_UInt32(src, srcIndex); + uint xr = Pack.BE_To_UInt32(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]; + + Pack.UInt32_To_BE(xr, dst, dstIndex); + Pack.UInt32_To_BE(xl, dst, dstIndex + 4); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/engines/CamelliaEngine.cs b/bc-sharp-crypto/src/crypto/engines/CamelliaEngine.cs new file mode 100644 index 0000000..71bd1b0 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/CamelliaEngine.cs @@ -0,0 +1,668 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; + +namespace Org.BouncyCastle.Crypto.Engines +{ + /** + * Camellia - based on RFC 3713. + */ + public class CamelliaEngine + : IBlockCipher + { + private bool initialised = false; + private bool _keyIs128; + + private const int BLOCK_SIZE = 16; + + private uint[] subkey = new uint[24 * 4]; + private uint[] kw = new uint[4 * 2]; // for whitening + private uint[] ke = new uint[6 * 2]; // for FL and FL^(-1) + private uint[] state = new uint[4]; // for encryption and decryption + + private static readonly uint[] SIGMA = new uint[]{ + 0xa09e667f, 0x3bcc908b, + 0xb67ae858, 0x4caa73b2, + 0xc6ef372f, 0xe94f82be, + 0x54ff53a5, 0xf1d36f1c, + 0x10e527fa, 0xde682d1d, + 0xb05688c2, 0xb3e6c1fd + }; + + /* + * + * S-box data + * + */ + private static readonly uint[] SBOX1_1110 = new uint[]{ + 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 readonly uint[] SBOX4_4404 = new uint[]{ + 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 readonly uint[] SBOX2_0222 = new uint[]{ + 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 readonly uint[] SBOX3_3033 = new uint[]{ + 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 uint rightRotate(uint x, int s) + { + return ((x >> s) + (x << (32 - s))); + } + + private static uint leftRotate(uint x, int s) + { + return (x << s) + (x >> (32 - s)); + } + + private static void roldq(int rot, uint[] ki, int ioff, uint[] 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, uint[] ki, int ioff, uint[] 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, uint[] ki, int ioff, uint[] 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, uint[] ki, int ioff, uint[] 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 static uint bytes2uint(byte[] src, int offset) + { + uint word = 0; + for (int i = 0; i < 4; i++) + { + word = (word << 8) + (uint)src[i + offset]; + } + return word; + } + + private static void uint2bytes(uint word, byte[] dst, int offset) + { + for (int i = 0; i < 4; i++) + { + dst[(3 - i) + offset] = (byte)word; + word >>= 8; + } + } + + private static void camelliaF2(uint[] s, uint[] skey, int keyoff) + { + uint t1, t2, u, v; + + t1 = s[0] ^ skey[0 + keyoff]; + u = SBOX4_4404[(byte)t1]; + u ^= SBOX3_3033[(byte)(t1 >> 8)]; + u ^= SBOX2_0222[(byte)(t1 >> 16)]; + u ^= SBOX1_1110[(byte)(t1 >> 24)]; + t2 = s[1] ^ skey[1 + keyoff]; + v = SBOX1_1110[(byte)t2]; + v ^= SBOX4_4404[(byte)(t2 >> 8)]; + v ^= SBOX3_3033[(byte)(t2 >> 16)]; + v ^= SBOX2_0222[(byte)(t2 >> 24)]; + + s[2] ^= u ^ v; + s[3] ^= u ^ v ^ rightRotate(u, 8); + + t1 = s[2] ^ skey[2 + keyoff]; + u = SBOX4_4404[(byte)t1]; + u ^= SBOX3_3033[(byte)(t1 >> 8)]; + u ^= SBOX2_0222[(byte)(t1 >> 16)]; + u ^= SBOX1_1110[(byte)(t1 >> 24)]; + t2 = s[3] ^ skey[3 + keyoff]; + v = SBOX1_1110[(byte)t2]; + v ^= SBOX4_4404[(byte)(t2 >> 8)]; + v ^= SBOX3_3033[(byte)(t2 >> 16)]; + v ^= SBOX2_0222[(byte)(t2 >> 24)]; + + s[0] ^= u ^ v; + s[1] ^= u ^ v ^ rightRotate(u, 8); + } + + private static void camelliaFLs(uint[] s, uint[] 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(bool forEncryption, byte[] key) + { + uint[] k = new uint[8]; + uint[] ka = new uint[4]; + uint[] kb = new uint[4]; + uint[] t = new uint[4]; + + switch (key.Length) + { + case 16: + _keyIs128 = true; + k[0] = bytes2uint(key, 0); + k[1] = bytes2uint(key, 4); + k[2] = bytes2uint(key, 8); + k[3] = bytes2uint(key, 12); + k[4] = k[5] = k[6] = k[7] = 0; + break; + case 24: + k[0] = bytes2uint(key, 0); + k[1] = bytes2uint(key, 4); + k[2] = bytes2uint(key, 8); + k[3] = bytes2uint(key, 12); + k[4] = bytes2uint(key, 16); + k[5] = bytes2uint(key, 20); + k[6] = ~k[4]; + k[7] = ~k[5]; + _keyIs128 = false; + break; + case 32: + k[0] = bytes2uint(key, 0); + k[1] = bytes2uint(key, 4); + k[2] = bytes2uint(key, 8); + k[3] = bytes2uint(key, 12); + k[4] = bytes2uint(key, 16); + k[5] = bytes2uint(key, 20); + k[6] = bytes2uint(key, 24); + k[7] = bytes2uint(key, 28); + _keyIs128 = false; + break; + default: + throw new ArgumentException("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[] input, int inOff, byte[] output, int outOff) + { + for (int i = 0; i < 4; i++) + { + state[i] = bytes2uint(input, 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]; + + uint2bytes(state[2], output, outOff); + uint2bytes(state[3], output, outOff + 4); + uint2bytes(state[0], output, outOff + 8); + uint2bytes(state[1], output, outOff + 12); + + return BLOCK_SIZE; + } + + private int processBlock192or256(byte[] input, int inOff, byte[] output, int outOff) + { + for (int i = 0; i < 4; i++) + { + state[i] = bytes2uint(input, 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]; + + uint2bytes(state[2], output, outOff); + uint2bytes(state[3], output, outOff + 4); + uint2bytes(state[0], output, outOff + 8); + uint2bytes(state[1], output, outOff + 12); + return BLOCK_SIZE; + } + + public CamelliaEngine() + { + } + + public virtual void Init( + bool forEncryption, + ICipherParameters parameters) + { + if (!(parameters is KeyParameter)) + throw new ArgumentException("only simple KeyParameter expected."); + + setKey(forEncryption, ((KeyParameter)parameters).GetKey()); + + initialised = true; + } + + public virtual string AlgorithmName + { + get { return "Camellia"; } + } + + public virtual bool IsPartialBlockOkay + { + get { return false; } + } + + public virtual int GetBlockSize() + { + return BLOCK_SIZE; + } + + public virtual int ProcessBlock( + byte[] input, + int inOff, + byte[] output, + int outOff) + { + if (!initialised) + throw new InvalidOperationException("Camellia engine not initialised"); + + Check.DataLength(input, inOff, BLOCK_SIZE, "input buffer too short"); + Check.OutputLength(output, outOff, BLOCK_SIZE, "output buffer too short"); + + if (_keyIs128) + { + return processBlock128(input, inOff, output, outOff); + } + else + { + return processBlock192or256(input, inOff, output, outOff); + } + } + + public virtual void Reset() + { + // nothing + } + } +} diff --git a/bc-sharp-crypto/src/crypto/engines/CamelliaLightEngine.cs b/bc-sharp-crypto/src/crypto/engines/CamelliaLightEngine.cs new file mode 100644 index 0000000..a132227 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/CamelliaLightEngine.cs @@ -0,0 +1,580 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; + +namespace Org.BouncyCastle.Crypto.Engines +{ + /** + * Camellia - based on RFC 3713, smaller implementation, about half the size of CamelliaEngine. + */ + public class CamelliaLightEngine + : IBlockCipher + { + private const int BLOCK_SIZE = 16; +// private const int MASK8 = 0xff; + private bool initialised; + private bool _keyis128; + + private uint[] subkey = new uint[24 * 4]; + private uint[] kw = new uint[4 * 2]; // for whitening + private uint[] ke = new uint[6 * 2]; // for FL and FL^(-1) + private uint[] state = new uint[4]; // for encryption and decryption + + private static readonly uint[] SIGMA = { + 0xa09e667f, 0x3bcc908b, + 0xb67ae858, 0x4caa73b2, + 0xc6ef372f, 0xe94f82be, + 0x54ff53a5, 0xf1d36f1c, + 0x10e527fa, 0xde682d1d, + 0xb05688c2, 0xb3e6c1fd + }; + + /* + * + * S-box data + * + */ + private static readonly 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 uint rightRotate(uint x, int s) + { + return ((x >> s) + (x << (32 - s))); + } + + private static uint leftRotate(uint x, int s) + { + return (x << s) + (x >> (32 - s)); + } + + private static void roldq(int rot, uint[] ki, int ioff, uint[] 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, uint[] ki, int ioff, uint[] 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, uint[] ki, int ioff, uint[] 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, uint[] ki, int ioff, uint[] 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 static uint bytes2uint(byte[] src, int offset) + { + uint word = 0; + for (int i = 0; i < 4; i++) + { + word = (word << 8) + (uint)src[i + offset]; + } + return word; + } + + private static void uint2bytes(uint 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)(((uint)v << rot) | ((uint)v >> (8 - rot))); + } + + private uint sbox2(int x) + { + return (uint)lRot8(SBOX1[x], 1); + } + + private uint sbox3(int x) + { + return (uint)lRot8(SBOX1[x], 7); + } + + private uint sbox4(int x) + { + return (uint)SBOX1[lRot8((byte)x, 1)]; + } + + private void camelliaF2(uint[] s, uint[] skey, int keyoff) + { + uint t1, t2, u, v; + + t1 = s[0] ^ skey[0 + keyoff]; + u = sbox4((byte)t1); + u |= (sbox3((byte)(t1 >> 8)) << 8); + u |= (sbox2((byte)(t1 >> 16)) << 16); + u |= ((uint)(SBOX1[(byte)(t1 >> 24)]) << 24); + + t2 = s[1] ^ skey[1 + keyoff]; + v = (uint)SBOX1[(byte)t2]; + v |= (sbox4((byte)(t2 >> 8)) << 8); + v |= (sbox3((byte)(t2 >> 16)) << 16); + v |= (sbox2((byte)(t2 >> 24)) << 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((byte)t1); + u |= sbox3((byte)(t1 >> 8)) << 8; + u |= sbox2((byte)(t1 >> 16)) << 16; + u |= ((uint)SBOX1[(byte)(t1 >> 24)]) << 24; + + t2 = s[3] ^ skey[3 + keyoff]; + v = (uint)SBOX1[(byte)t2]; + v |= sbox4((byte)(t2 >> 8)) << 8; + v |= sbox3((byte)(t2 >> 16)) << 16; + v |= sbox2((byte)(t2 >> 24)) << 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(uint[] s, uint[] 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(bool forEncryption, byte[] key) + { + uint[] k = new uint[8]; + uint[] ka = new uint[4]; + uint[] kb = new uint[4]; + uint[] t = new uint[4]; + + switch (key.Length) + { + case 16: + _keyis128 = true; + k[0] = bytes2uint(key, 0); + k[1] = bytes2uint(key, 4); + k[2] = bytes2uint(key, 8); + k[3] = bytes2uint(key, 12); + k[4] = k[5] = k[6] = k[7] = 0; + break; + case 24: + k[0] = bytes2uint(key, 0); + k[1] = bytes2uint(key, 4); + k[2] = bytes2uint(key, 8); + k[3] = bytes2uint(key, 12); + k[4] = bytes2uint(key, 16); + k[5] = bytes2uint(key, 20); + k[6] = ~k[4]; + k[7] = ~k[5]; + _keyis128 = false; + break; + case 32: + k[0] = bytes2uint(key, 0); + k[1] = bytes2uint(key, 4); + k[2] = bytes2uint(key, 8); + k[3] = bytes2uint(key, 12); + k[4] = bytes2uint(key, 16); + k[5] = bytes2uint(key, 20); + k[6] = bytes2uint(key, 24); + k[7] = bytes2uint(key, 28); + _keyis128 = false; + break; + default: + throw new ArgumentException("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[] input, int inOff, byte[] output, int outOff) + { + for (int i = 0; i < 4; i++) + { + state[i] = bytes2uint(input, 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]; + + uint2bytes(state[2], output, outOff); + uint2bytes(state[3], output, outOff + 4); + uint2bytes(state[0], output, outOff + 8); + uint2bytes(state[1], output, outOff + 12); + + return BLOCK_SIZE; + } + + private int processBlock192or256(byte[] input, int inOff, byte[] output, int outOff) + { + for (int i = 0; i < 4; i++) + { + state[i] = bytes2uint(input, 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]; + + uint2bytes(state[2], output, outOff); + uint2bytes(state[3], output, outOff + 4); + uint2bytes(state[0], output, outOff + 8); + uint2bytes(state[1], output, outOff + 12); + return BLOCK_SIZE; + } + + public CamelliaLightEngine() + { + initialised = false; + } + + public virtual string AlgorithmName + { + get { return "Camellia"; } + } + + public virtual bool IsPartialBlockOkay + { + get { return false; } + } + + public virtual int GetBlockSize() + { + return BLOCK_SIZE; + } + + public virtual void Init( + bool forEncryption, + ICipherParameters parameters) + { + if (!(parameters is KeyParameter)) + throw new ArgumentException("only simple KeyParameter expected."); + + setKey(forEncryption, ((KeyParameter)parameters).GetKey()); + + initialised = true; + } + + public virtual int ProcessBlock( + byte[] input, + int inOff, + byte[] output, + int outOff) + { + if (!initialised) + throw new InvalidOperationException("Camellia engine not initialised"); + + Check.DataLength(input, inOff, BLOCK_SIZE, "input buffer too short"); + Check.OutputLength(output, outOff, BLOCK_SIZE, "output buffer too short"); + + if (_keyis128) + { + return processBlock128(input, inOff, output, outOff); + } + else + { + return processBlock192or256(input, inOff, output, outOff); + } + } + + public virtual void Reset() + { + } + } +} diff --git a/bc-sharp-crypto/src/crypto/engines/CamelliaWrapEngine.cs b/bc-sharp-crypto/src/crypto/engines/CamelliaWrapEngine.cs new file mode 100644 index 0000000..49dc833 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/CamelliaWrapEngine.cs @@ -0,0 +1,16 @@ +namespace Org.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 + : Rfc3394WrapEngine + { + public CamelliaWrapEngine() + : base(new CamelliaEngine()) + { + } + } +} diff --git a/bc-sharp-crypto/src/crypto/engines/Cast5Engine.cs b/bc-sharp-crypto/src/crypto/engines/Cast5Engine.cs new file mode 100644 index 0000000..53836db --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/Cast5Engine.cs @@ -0,0 +1,802 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Utilities; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Engines +{ + /** + * 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 + : IBlockCipher + { + internal static readonly uint[] 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 + //==================================== + + internal static readonly int MAX_ROUNDS = 16; + internal static readonly int RED_ROUNDS = 12; + + private const int BLOCK_SIZE = 8; // bytes = 64 bits + + private int[] _Kr = new int[17]; // the rotating round key + private uint[] _Km = new uint[17]; // the masking round key + + private bool _encrypting; + + private byte[] _workingKey; + private int _rounds = MAX_ROUNDS; + + public Cast5Engine() + { + } + + /** + * initialise a CAST cipher. + * + * @param forEncryption whether or not we are for encryption. + * @param parameters the parameters required to set up the cipher. + * @exception ArgumentException if the parameters argument is + * inappropriate. + */ + public virtual void Init( + bool forEncryption, + ICipherParameters parameters) + { + if (!(parameters is KeyParameter)) + throw new ArgumentException("Invalid parameter passed to "+ AlgorithmName +" init - " + Platform.GetTypeName(parameters)); + + _encrypting = forEncryption; + _workingKey = ((KeyParameter)parameters).GetKey(); + SetKey(_workingKey); + } + + public virtual string AlgorithmName + { + get { return "CAST5"; } + } + + public virtual bool IsPartialBlockOkay + { + get { return false; } + } + + public virtual int ProcessBlock( + byte[] input, + int inOff, + byte[] output, + int outOff) + { + int blockSize = GetBlockSize(); + if (_workingKey == null) + throw new InvalidOperationException(AlgorithmName + " not initialised"); + + Check.DataLength(input, inOff, blockSize, "input buffer too short"); + Check.OutputLength(output, outOff, blockSize, "output buffer too short"); + + if (_encrypting) + { + return EncryptBlock(input, inOff, output, outOff); + } + else + { + return DecryptBlock(input, inOff, output, outOff); + } + } + + public virtual void Reset() + { + } + + public virtual int GetBlockSize() + { + return BLOCK_SIZE; + } + + //================================== + // Private Implementation + //================================== + + /* + * Creates the subkeys using the same nomenclature + * as described in RFC2144. + * + * See section 2.4 + */ + internal virtual 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]; + + uint z03, z47, z8B, zCF; + uint x03, x47, x8B, xCF; + + /* copy the key into x */ + for (int i=0; i< key.Length; i++) + { + x[i] = (int)(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]=(int)((S5[z[0x8]]^S6[z[0x9]]^S7[z[0x7]]^S8[z[0x6]] ^ S5[z[0x2]])&0x1f); + _Kr[ 2]=(int)((S5[z[0xA]]^S6[z[0xB]]^S7[z[0x5]]^S8[z[0x4]] ^ S6[z[0x6]])&0x1f); + _Kr[ 3]=(int)((S5[z[0xC]]^S6[z[0xD]]^S7[z[0x3]]^S8[z[0x2]] ^ S7[z[0x9]])&0x1f); + _Kr[ 4]=(int)((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]=(int)((S5[x[0x3]]^S6[x[0x2]]^S7[x[0xC]]^S8[x[0xD]]^S5[x[0x8]])&0x1f); + _Kr[ 6]=(int)((S5[x[0x1]]^S6[x[0x0]]^S7[x[0xE]]^S8[x[0xF]]^S6[x[0xD]])&0x1f); + _Kr[ 7]=(int)((S5[x[0x7]]^S6[x[0x6]]^S7[x[0x8]]^S8[x[0x9]]^S7[x[0x3]])&0x1f); + _Kr[ 8]=(int)((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]=(int)((S5[z[0x3]]^S6[z[0x2]]^S7[z[0xC]]^S8[z[0xD]]^S5[z[0x9]])&0x1f); + _Kr[10]=(int)((S5[z[0x1]]^S6[z[0x0]]^S7[z[0xE]]^S8[z[0xF]]^S6[z[0xc]])&0x1f); + _Kr[11]=(int)((S5[z[0x7]]^S6[z[0x6]]^S7[z[0x8]]^S8[z[0x9]]^S7[z[0x2]])&0x1f); + _Kr[12]=(int)((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]=(int)((S5[x[0x8]]^S6[x[0x9]]^S7[x[0x7]]^S8[x[0x6]]^S5[x[0x3]])&0x1f); + _Kr[14]=(int)((S5[x[0xA]]^S6[x[0xB]]^S7[x[0x5]]^S8[x[0x4]]^S6[x[0x7]])&0x1f); + _Kr[15]=(int)((S5[x[0xC]]^S6[x[0xD]]^S7[x[0x3]]^S8[x[0x2]]^S7[x[0x8]])&0x1f); + _Kr[16]=(int)((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 + */ + internal virtual int EncryptBlock( + byte[] src, + int srcIndex, + byte[] dst, + int dstIndex) + { + // 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 + + uint L0 = Pack.BE_To_UInt32(src, srcIndex); + uint R0 = Pack.BE_To_UInt32(src, srcIndex + 4); + + uint[] result = new uint[2]; + CAST_Encipher(L0, R0, result); + + // now stuff them into the destination block + Pack.UInt32_To_BE(result[0], dst, dstIndex); + Pack.UInt32_To_BE(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 + */ + internal virtual int DecryptBlock( + byte[] src, + int srcIndex, + byte[] dst, + int dstIndex) + { + // 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 + uint L16 = Pack.BE_To_UInt32(src, srcIndex); + uint R16 = Pack.BE_To_UInt32(src, srcIndex + 4); + + uint[] result = new uint[2]; + CAST_Decipher(L16, R16, result); + + // now stuff them into the destination block + Pack.UInt32_To_BE(result[0], dst, dstIndex); + Pack.UInt32_To_BE(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 + * + */ + internal static uint F1(uint D, uint Kmi, int Kri) + { + uint 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 + * + */ + internal static uint F2(uint D, uint Kmi, int Kri) + { + uint 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 + * + */ + internal static uint F3(uint D, uint Kmi, int Kri) + { + uint 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 + */ + internal void CAST_Encipher(uint L0, uint R0, uint[] result) + { + uint Lp = L0; // the previous value, equiv to L[i-1] + uint Rp = R0; // equivalent to R[i-1] + + /* + * numbering consistent with paper to make + * checking and validating easier + */ + uint 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; + } + + internal void CAST_Decipher(uint L16, uint R16, uint[] result) + { + uint Lp = L16; // the previous value, equiv to L[i-1] + uint Rp = R16; // equivalent to R[i-1] + + /* + * numbering consistent with paper to make + * checking and validating easier + */ + uint 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; + } + + internal static void Bits32ToInts(uint inData, int[] b, int offset) + { + b[offset + 3] = (int) (inData & 0xff); + b[offset + 2] = (int) ((inData >> 8) & 0xff); + b[offset + 1] = (int) ((inData >> 16) & 0xff); + b[offset] = (int) ((inData >> 24) & 0xff); + } + + internal static uint IntsTo32bits(int[] b, int i) + { + return (uint)(((b[i] & 0xff) << 24) | + ((b[i+1] & 0xff) << 16) | + ((b[i+2] & 0xff) << 8) | + ((b[i+3] & 0xff))); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/engines/Cast6Engine.cs b/bc-sharp-crypto/src/crypto/engines/Cast6Engine.cs new file mode 100644 index 0000000..c5c419b --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/Cast6Engine.cs @@ -0,0 +1,279 @@ +using System; + +using Org.BouncyCastle.Crypto.Utilities; + +namespace Org.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 sealed class Cast6Engine + : Cast5Engine + { + //==================================== + // Useful constants + //==================================== + private const int ROUNDS = 12; + private const int BLOCK_SIZE = 16; // bytes = 128 bits + + /* + * Put the round and mask keys into an array. + * Kr0[i] => _Kr[i*4 + 0] + */ + private int []_Kr = new int[ROUNDS*4]; // the rotating round key(s) + private uint []_Km = new uint[ROUNDS*4]; // the masking round key(s) + + /* + * Key setup + */ + private int []_Tr = new int[24 * 8]; + private uint []_Tm = new uint[24 * 8]; + private uint[] _workingKey = new uint[8]; + + public Cast6Engine() + { + } + + public override string AlgorithmName + { + get { return "CAST6"; } + } + + public override void Reset() + { + } + + public override int GetBlockSize() + { + return BLOCK_SIZE; + } + + //================================== + // Private Implementation + //================================== + /* + * Creates the subkeys using the same nomenclature + * as described in RFC2612. + * + * See section 2.4 + */ + internal override void SetKey( + byte[] key) + { + uint Cm = 0x5a827999; + uint 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 += Mm; //mod 2^32; + _Tr[i*8 + j] = Cr; + Cr = (Cr + Mr) & 0x1f; // mod 32 + } + } + + byte[] tmpKey = new byte[64]; + key.CopyTo(tmpKey, 0); + + // now create ABCDEFGH + for (int i = 0; i < 8; i++) + { + _workingKey[i] = Pack.BE_To_UInt32(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] = (int)(_workingKey[0] & 0x1f); + _Kr[i*4 + 1] = (int)(_workingKey[2] & 0x1f); + _Kr[i*4 + 2] = (int)(_workingKey[4] & 0x1f); + _Kr[i*4 + 3] = (int)(_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 + */ + internal override int EncryptBlock( + byte[] src, + int srcIndex, + byte[] dst, + int dstIndex) + { + // process the input block + // batch the units up into 4x32 bit chunks and go for it + uint A = Pack.BE_To_UInt32(src, srcIndex); + uint B = Pack.BE_To_UInt32(src, srcIndex + 4); + uint C = Pack.BE_To_UInt32(src, srcIndex + 8); + uint D = Pack.BE_To_UInt32(src, srcIndex + 12); + uint[] result = new uint[4]; + CAST_Encipher(A, B, C, D, result); + // now stuff them into the destination block + Pack.UInt32_To_BE(result[0], dst, dstIndex); + Pack.UInt32_To_BE(result[1], dst, dstIndex + 4); + Pack.UInt32_To_BE(result[2], dst, dstIndex + 8); + Pack.UInt32_To_BE(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 + */ + internal override int DecryptBlock( + byte[] src, + int srcIndex, + byte[] dst, + int dstIndex) + { + // process the input block + // batch the units up into 4x32 bit chunks and go for it + uint A = Pack.BE_To_UInt32(src, srcIndex); + uint B = Pack.BE_To_UInt32(src, srcIndex + 4); + uint C = Pack.BE_To_UInt32(src, srcIndex + 8); + uint D = Pack.BE_To_UInt32(src, srcIndex + 12); + uint[] result = new uint[4]; + CAST_Decipher(A, B, C, D, result); + // now stuff them into the destination block + Pack.UInt32_To_BE(result[0], dst, dstIndex); + Pack.UInt32_To_BE(result[1], dst, dstIndex + 4); + Pack.UInt32_To_BE(result[2], dst, dstIndex + 8); + Pack.UInt32_To_BE(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 + */ + private void CAST_Encipher( + uint A, + uint B, + uint C, + uint D, + uint[] result) + { + for (int i = 0; i < 6; i++) + { + int 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++) + { + int 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 + */ + private void CAST_Decipher( + uint A, + uint B, + uint C, + uint D, + uint[] result) + { + for (int i = 0; i < 6; i++) + { + int 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++) + { + int 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/bc-sharp-crypto/src/crypto/engines/ChaCha7539Engine.cs b/bc-sharp-crypto/src/crypto/engines/ChaCha7539Engine.cs new file mode 100644 index 0000000..af4163a --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/ChaCha7539Engine.cs @@ -0,0 +1,65 @@ +using System; + +using Org.BouncyCastle.Crypto.Utilities; + +namespace Org.BouncyCastle.Crypto.Engines +{ + ///

+ /// Implementation of Daniel J. Bernstein's ChaCha stream cipher. + /// + public class ChaCha7539Engine + : Salsa20Engine + { + /// + /// Creates a 20 rounds ChaCha engine. + /// + public ChaCha7539Engine() + { + } + + public override string AlgorithmName + { + get { return "ChaCha7539" + rounds; } + } + + protected override int NonceSize + { + get { return 12; } + } + + protected override void AdvanceCounter() + { + if (++engineState[12] == 0) + throw new InvalidOperationException("attempt to increase counter past 2^32."); + } + + protected override void ResetCounter() + { + engineState[12] = 0; + } + + protected override void SetKey(byte[] keyBytes, byte[] ivBytes) + { + if (keyBytes != null) + { + if (keyBytes.Length != 32) + throw new ArgumentException(AlgorithmName + " requires 256 bit key"); + + PackTauOrSigma(keyBytes.Length, engineState, 0); + + // Key + Pack.LE_To_UInt32(keyBytes, 0, engineState, 4, 8); + } + + // IV + Pack.LE_To_UInt32(ivBytes, 0, engineState, 13, 3); + } + + protected override void GenerateKeyStream(byte[] output) + { + ChaChaEngine.ChachaCore(rounds, engineState, x); + Pack.UInt32_To_LE(x, output, 0); + } + } +} + diff --git a/bc-sharp-crypto/src/crypto/engines/ChaChaEngine.cs b/bc-sharp-crypto/src/crypto/engines/ChaChaEngine.cs new file mode 100644 index 0000000..8720504 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/ChaChaEngine.cs @@ -0,0 +1,157 @@ +using System; + +using Org.BouncyCastle.Crypto.Utilities; + +namespace Org.BouncyCastle.Crypto.Engines +{ + /// + /// Implementation of Daniel J. Bernstein's ChaCha stream cipher. + /// + public class ChaChaEngine + : Salsa20Engine + { + /// + /// Creates a 20 rounds ChaCha engine. + /// + public ChaChaEngine() + { + } + + /// + /// Creates a ChaCha engine with a specific number of rounds. + /// + /// the number of rounds (must be an even number). + public ChaChaEngine(int rounds) + : base(rounds) + { + } + + public override string AlgorithmName + { + get { return "ChaCha" + rounds; } + } + + protected override void AdvanceCounter() + { + if (++engineState[12] == 0) + { + ++engineState[13]; + } + } + + protected override void ResetCounter() + { + engineState[12] = engineState[13] = 0; + } + + protected override void SetKey(byte[] keyBytes, byte[] ivBytes) + { + if (keyBytes != null) + { + if ((keyBytes.Length != 16) && (keyBytes.Length != 32)) + throw new ArgumentException(AlgorithmName + " requires 128 bit or 256 bit key"); + + PackTauOrSigma(keyBytes.Length, engineState, 0); + + // Key + Pack.LE_To_UInt32(keyBytes, 0, engineState, 4, 4); + Pack.LE_To_UInt32(keyBytes, keyBytes.Length - 16, engineState, 8, 4); + } + + // IV + Pack.LE_To_UInt32(ivBytes, 0, engineState, 14, 2); + } + + protected override void GenerateKeyStream(byte[] output) + { + ChachaCore(rounds, engineState, x); + Pack.UInt32_To_LE(x, output, 0); + } + + /// + /// ChaCha function. + /// + /// The number of ChaCha rounds to execute + /// The input words. + /// The ChaCha state to modify. + internal static void ChachaCore(int rounds, uint[] input, uint[] x) + { + if (input.Length != 16) + throw new ArgumentException(); + if (x.Length != 16) + throw new ArgumentException(); + if (rounds % 2 != 0) + throw new ArgumentException("Number of rounds must be even"); + + uint x00 = input[ 0]; + uint x01 = input[ 1]; + uint x02 = input[ 2]; + uint x03 = input[ 3]; + uint x04 = input[ 4]; + uint x05 = input[ 5]; + uint x06 = input[ 6]; + uint x07 = input[ 7]; + uint x08 = input[ 8]; + uint x09 = input[ 9]; + uint x10 = input[10]; + uint x11 = input[11]; + uint x12 = input[12]; + uint x13 = input[13]; + uint x14 = input[14]; + uint x15 = input[15]; + + for (int i = rounds; i > 0; i -= 2) + { + x00 += x04; x12 = R(x12 ^ x00, 16); + x08 += x12; x04 = R(x04 ^ x08, 12); + x00 += x04; x12 = R(x12 ^ x00, 8); + x08 += x12; x04 = R(x04 ^ x08, 7); + x01 += x05; x13 = R(x13 ^ x01, 16); + x09 += x13; x05 = R(x05 ^ x09, 12); + x01 += x05; x13 = R(x13 ^ x01, 8); + x09 += x13; x05 = R(x05 ^ x09, 7); + x02 += x06; x14 = R(x14 ^ x02, 16); + x10 += x14; x06 = R(x06 ^ x10, 12); + x02 += x06; x14 = R(x14 ^ x02, 8); + x10 += x14; x06 = R(x06 ^ x10, 7); + x03 += x07; x15 = R(x15 ^ x03, 16); + x11 += x15; x07 = R(x07 ^ x11, 12); + x03 += x07; x15 = R(x15 ^ x03, 8); + x11 += x15; x07 = R(x07 ^ x11, 7); + x00 += x05; x15 = R(x15 ^ x00, 16); + x10 += x15; x05 = R(x05 ^ x10, 12); + x00 += x05; x15 = R(x15 ^ x00, 8); + x10 += x15; x05 = R(x05 ^ x10, 7); + x01 += x06; x12 = R(x12 ^ x01, 16); + x11 += x12; x06 = R(x06 ^ x11, 12); + x01 += x06; x12 = R(x12 ^ x01, 8); + x11 += x12; x06 = R(x06 ^ x11, 7); + x02 += x07; x13 = R(x13 ^ x02, 16); + x08 += x13; x07 = R(x07 ^ x08, 12); + x02 += x07; x13 = R(x13 ^ x02, 8); + x08 += x13; x07 = R(x07 ^ x08, 7); + x03 += x04; x14 = R(x14 ^ x03, 16); + x09 += x14; x04 = R(x04 ^ x09, 12); + x03 += x04; x14 = R(x14 ^ x03, 8); + x09 += x14; x04 = R(x04 ^ x09, 7); + } + + x[ 0] = x00 + input[ 0]; + x[ 1] = x01 + input[ 1]; + x[ 2] = x02 + input[ 2]; + x[ 3] = x03 + input[ 3]; + x[ 4] = x04 + input[ 4]; + x[ 5] = x05 + input[ 5]; + x[ 6] = x06 + input[ 6]; + x[ 7] = x07 + input[ 7]; + x[ 8] = x08 + input[ 8]; + x[ 9] = x09 + input[ 9]; + x[10] = x10 + input[10]; + x[11] = x11 + input[11]; + x[12] = x12 + input[12]; + x[13] = x13 + input[13]; + x[14] = x14 + input[14]; + x[15] = x15 + input[15]; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/engines/DesEdeEngine.cs b/bc-sharp-crypto/src/crypto/engines/DesEdeEngine.cs new file mode 100644 index 0000000..2fac24a --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/DesEdeEngine.cs @@ -0,0 +1,100 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Engines +{ + /// A class that provides a basic DESede (or Triple DES) engine. + public class DesEdeEngine + : DesEngine + { + private int[] workingKey1, workingKey2, workingKey3; + private bool forEncryption; + + /** + * initialise a DESede cipher. + * + * @param forEncryption whether or not we are for encryption. + * @param parameters the parameters required to set up the cipher. + * @exception ArgumentException if the parameters argument is + * inappropriate. + */ + public override void Init( + bool forEncryption, + ICipherParameters parameters) + { + if (!(parameters is KeyParameter)) + throw new ArgumentException("invalid parameter passed to DESede init - " + Platform.GetTypeName(parameters)); + + byte[] keyMaster = ((KeyParameter)parameters).GetKey(); + if (keyMaster.Length != 24 && keyMaster.Length != 16) + throw new ArgumentException("key size must be 16 or 24 bytes."); + + this.forEncryption = forEncryption; + + byte[] key1 = new byte[8]; + Array.Copy(keyMaster, 0, key1, 0, key1.Length); + workingKey1 = GenerateWorkingKey(forEncryption, key1); + + byte[] key2 = new byte[8]; + Array.Copy(keyMaster, 8, key2, 0, key2.Length); + workingKey2 = GenerateWorkingKey(!forEncryption, key2); + + if (keyMaster.Length == 24) + { + byte[] key3 = new byte[8]; + Array.Copy(keyMaster, 16, key3, 0, key3.Length); + workingKey3 = GenerateWorkingKey(forEncryption, key3); + } + else // 16 byte key + { + workingKey3 = workingKey1; + } + } + + public override string AlgorithmName + { + get { return "DESede"; } + } + + public override int GetBlockSize() + { + return BLOCK_SIZE; + } + + public override int ProcessBlock( + byte[] input, + int inOff, + byte[] output, + int outOff) + { + if (workingKey1 == null) + throw new InvalidOperationException("DESede engine not initialised"); + + Check.DataLength(input, inOff, BLOCK_SIZE, "input buffer too short"); + Check.OutputLength(output, outOff, BLOCK_SIZE, "output buffer too short"); + + byte[] temp = new byte[BLOCK_SIZE]; + + if (forEncryption) + { + DesFunc(workingKey1, input, inOff, temp, 0); + DesFunc(workingKey2, temp, 0, temp, 0); + DesFunc(workingKey3, temp, 0, output, outOff); + } + else + { + DesFunc(workingKey3, input, inOff, temp, 0); + DesFunc(workingKey2, temp, 0, temp, 0); + DesFunc(workingKey1, temp, 0, output, outOff); + } + + return BLOCK_SIZE; + } + + public override void Reset() + { + } + } +} diff --git a/bc-sharp-crypto/src/crypto/engines/DesEdeWrapEngine.cs b/bc-sharp-crypto/src/crypto/engines/DesEdeWrapEngine.cs new file mode 100644 index 0000000..43100a9 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/DesEdeWrapEngine.cs @@ -0,0 +1,322 @@ +using System; + +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Modes; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Engines +{ + /** + * 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 + : IWrapper + { + /** Field engine */ + private CbcBlockCipher engine; + /** Field param */ + private KeyParameter param; + /** Field paramPlusIV */ + private ParametersWithIV paramPlusIV; + /** Field iv */ + private byte[] iv; + /** Field forWrapping */ + private bool forWrapping; + /** Field IV2 */ + private static readonly byte[] IV2 = { (byte) 0x4a, (byte) 0xdd, (byte) 0xa2, + (byte) 0x2c, (byte) 0x79, (byte) 0xe8, + (byte) 0x21, (byte) 0x05 }; + + // + // checksum digest + // + private readonly IDigest sha1 = new Sha1Digest(); + private readonly byte[] digest = new byte[20]; + + /** + * Method init + * + * @param forWrapping + * @param param + */ + public virtual void Init( + bool forWrapping, + ICipherParameters parameters) + { + this.forWrapping = forWrapping; + this.engine = new CbcBlockCipher(new DesEdeEngine()); + + SecureRandom sr; + if (parameters is ParametersWithRandom) + { + ParametersWithRandom pr = (ParametersWithRandom) parameters; + parameters = pr.Parameters; + sr = pr.Random; + } + else + { + sr = new SecureRandom(); + } + + if (parameters is KeyParameter) + { + this.param = (KeyParameter) parameters; + 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 (parameters is ParametersWithIV) + { + if (!forWrapping) + throw new ArgumentException("You should not supply an IV for unwrapping"); + + this.paramPlusIV = (ParametersWithIV) parameters; + this.iv = this.paramPlusIV.GetIV(); + this.param = (KeyParameter) this.paramPlusIV.Parameters; + + if (this.iv.Length != 8) + throw new ArgumentException("IV is not 8 octets", "parameters"); + } + } + + /** + * Method GetAlgorithmName + * + * @return + */ + public virtual string AlgorithmName + { + get { return "DESede"; } + } + + /** + * Method wrap + * + * @param in + * @param inOff + * @param inLen + * @return + */ + public virtual byte[] Wrap( + byte[] input, + int inOff, + int length) + { + if (!forWrapping) + { + throw new InvalidOperationException("Not initialized for wrapping"); + } + + byte[] keyToBeWrapped = new byte[length]; + Array.Copy(input, inOff, keyToBeWrapped, 0, 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]; + Array.Copy(keyToBeWrapped, 0, WKCKS, 0, keyToBeWrapped.Length); + Array.Copy(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 InvalidOperationException("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]; + Array.Copy(this.iv, 0, TEMP2, 0, this.iv.Length); + Array.Copy(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 + * @throws InvalidCipherTextException + */ + public virtual byte[] Unwrap( + byte[] input, + int inOff, + int length) + { + if (forWrapping) + { + throw new InvalidOperationException("Not set for unwrapping"); + } + if (input == null) + { + throw new InvalidCipherTextException("Null pointer as ciphertext"); + } + + int blockSize = engine.GetBlockSize(); + + if (length % 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[length]; + + for (int currentBytePos = 0; currentBytePos != TEMP3.Length; currentBytePos += blockSize) + { + engine.ProcessBlock(input, 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]; + Array.Copy(TEMP2, 0, this.iv, 0, 8); + Array.Copy(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]; + Array.Copy(WKCKS, 0, result, 0, WKCKS.Length - 8); + Array.Copy(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 + * @throws Exception + * @see http://www.w3.org/TR/xmlenc-core/#sec-CMSKeyChecksum + */ + private byte[] CalculateCmsKeyChecksum( + byte[] key) + { + sha1.BlockUpdate(key, 0, key.Length); + sha1.DoFinal(digest, 0); + + byte[] result = new byte[8]; + Array.Copy(digest, 0, result, 0, 8); + return result; + } + + /** + * @param key + * @param checksum + * @return + * @see http://www.w3.org/TR/xmlenc-core/#sec-CMSKeyChecksum + */ + private bool 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/bc-sharp-crypto/src/crypto/engines/DesEngine.cs b/bc-sharp-crypto/src/crypto/engines/DesEngine.cs new file mode 100644 index 0000000..cfd5068 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/DesEngine.cs @@ -0,0 +1,475 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Utilities; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Engines +{ + /// A class that provides a basic DES engine. + public class DesEngine + : IBlockCipher + { + internal const int BLOCK_SIZE = 8; + + private int[] workingKey; + + public virtual int[] GetWorkingKey() + { + return workingKey; + } + + /** + * initialise a DES cipher. + * + * @param forEncryption whether or not we are for encryption. + * @param parameters the parameters required to set up the cipher. + * @exception ArgumentException if the parameters argument is + * inappropriate. + */ + public virtual void Init( + bool forEncryption, + ICipherParameters parameters) + { + if (!(parameters is KeyParameter)) + throw new ArgumentException("invalid parameter passed to DES init - " + Platform.GetTypeName(parameters)); + + workingKey = GenerateWorkingKey(forEncryption, ((KeyParameter)parameters).GetKey()); + } + + public virtual string AlgorithmName + { + get { return "DES"; } + } + + public virtual bool IsPartialBlockOkay + { + get { return false; } + } + + public virtual int GetBlockSize() + { + return BLOCK_SIZE; + } + + public virtual int ProcessBlock( + byte[] input, + int inOff, + byte[] output, + int outOff) + { + if (workingKey == null) + throw new InvalidOperationException("DES engine not initialised"); + + Check.DataLength(input, inOff, BLOCK_SIZE, "input buffer too short"); + Check.OutputLength(output, outOff, BLOCK_SIZE, "output buffer too short"); + + DesFunc(workingKey, input, inOff, output, outOff); + + return BLOCK_SIZE; + } + + public virtual 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 readonly 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 readonly short[] bytebit = + { + 128, 64, 32, 16, 8, 4, 2, 1 + }; + + private static readonly 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 readonly 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 readonly byte[] totrot = + { + 1, 2, 4, 6, 8, 10, 12, 14, + 15, 17, 19, 21, 23, 25, 27, 28 + }; + + private static readonly 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 readonly uint[] 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 readonly uint[] 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 readonly uint[] 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 readonly uint[] 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 readonly uint[] 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 readonly uint[] 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 readonly uint[] 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 readonly uint[] 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 and Phil Karn. + * (whoever, and wherever they are!). + */ + protected static int[] GenerateWorkingKey( + bool encrypting, + byte[] key) + { + int[] newKey = new int[32]; + bool[] pc1m = new bool[56]; + bool[] pcr = new bool[56]; + + for (int j = 0; j < 56; j++ ) + { + int l = pc1[j]; + + pc1m[j] = ((key[(uint) 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] = (int) ( (uint) ((i1 & 0x00fc0000) << 6) | + (uint) ((i1 & 0x00000fc0) << 10) | + ((uint) (i2 & 0x00fc0000) >> 10) | + ((uint) (i2 & 0x00000fc0) >> 6)); + + newKey[i + 1] = (int) ( (uint) ((i1 & 0x0003f000) << 12) | + (uint) ((i1 & 0x0000003f) << 16) | + ((uint) (i2 & 0x0003f000) >> 4) | + (uint) (i2 & 0x0000003f)); + } + + return newKey; + } + + /** + * the DES engine. + */ + internal static void DesFunc( + int[] wKey, + byte[] input, + int inOff, + byte[] outBytes, + int outOff) + { + uint left = Pack.BE_To_UInt32(input, inOff); + uint right = Pack.BE_To_UInt32(input, inOff + 4); + uint work; + + 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); + work = (left ^ right) & 0xaaaaaaaa; + left ^= work; + right ^= work; + left = (left << 1) | (left >> 31); + + for (int round = 0; round < 8; round++) + { + uint fval; + + work = (right << 28) | (right >> 4); + work ^= (uint)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 ^ (uint)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 ^= (uint)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 ^ (uint)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); + + Pack.UInt32_To_BE(right, outBytes, outOff); + Pack.UInt32_To_BE(left, outBytes, outOff + 4); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/engines/Dstu7624Engine.cs b/bc-sharp-crypto/src/crypto/engines/Dstu7624Engine.cs new file mode 100644 index 0000000..cdb0f50 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/Dstu7624Engine.cs @@ -0,0 +1,766 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Utilities; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Engines +{ + /** + * implementation of DSTU 7624 (Kalyna) + */ + public class Dstu7624Engine + : IBlockCipher + { + private static readonly int BITS_IN_WORD = 64; + private static readonly int BITS_IN_BYTE = 8; + + private static readonly int REDUCTION_POLYNOMIAL = 0x011d; /* x^8 + x^4 + x^3 + x^2 + 1 */ + + private ulong[] internalState; + private ulong[] workingKey; + private ulong[][] roundKeys; + + /* Number of 64-bit words in block */ + private int wordsInBlock; + + /* Number of 64-bit words in key */ + private int wordsInKey; + + /* Number of encryption rounds depending on key length */ + private static int ROUNDS_128 = 10; + private static int ROUNDS_256 = 14; + private static int ROUNDS_512 = 18; + + private int blockSizeBits; + private int roundsAmount; + + private bool forEncryption; + + private byte[] internalStateBytes; + private byte[] tempInternalStateBytes; + + public Dstu7624Engine(int blockSizeBits) + { + /* DSTU7624 supports 128 | 256 | 512 key/block sizes */ + if (blockSizeBits != 128 && blockSizeBits != 256 && blockSizeBits != 512) + { + throw new ArgumentException("Unsupported block length: only 128/256/512 are allowed"); + } + this.blockSizeBits = blockSizeBits; + + wordsInBlock = blockSizeBits / BITS_IN_WORD; + internalState = new ulong[wordsInBlock]; + + internalStateBytes = new byte[internalState.Length * 64 / BITS_IN_BYTE]; + tempInternalStateBytes = new byte[internalState.Length * 64 / BITS_IN_BYTE]; + } + + #region INITIALIZATION + public virtual void Init(bool forEncryption, ICipherParameters parameters) + { + if (parameters is KeyParameter) + { + this.forEncryption = forEncryption; + + byte[] keyBytes = ((KeyParameter)parameters).GetKey(); + int keyBitLength = keyBytes.Length * BITS_IN_BYTE; + int blockBitLength = wordsInBlock * BITS_IN_WORD; + + if (keyBitLength != 128 && keyBitLength != 256 && keyBitLength != 512) + { + throw new ArgumentException("unsupported key length: only 128/256/512 are allowed"); + } + + /* Limitations on key lengths depending on block lengths. See table 6.1 in standard */ + if (blockBitLength == 128) + { + if (keyBitLength == 512) + { + throw new ArgumentException("Unsupported key length"); + } + } + + if (blockBitLength == 256) + { + if (keyBitLength == 128) + { + throw new ArgumentException("Unsupported key length"); + } + } + + if (blockBitLength == 512) + { + if (keyBitLength != 512) + { + throw new ArgumentException("Unsupported key length"); + } + } + + switch (keyBitLength) + { + case 128: + roundsAmount = ROUNDS_128; + break; + case 256: + roundsAmount = ROUNDS_256; + break; + case 512: + roundsAmount = ROUNDS_512; + break; + } + + wordsInKey = keyBitLength / BITS_IN_WORD; + + /* +1 round key as defined in standard */ + roundKeys = new ulong[roundsAmount + 1][]; + for (int roundKeyIndex = 0; roundKeyIndex < roundKeys.Length; roundKeyIndex++) + { + roundKeys[roundKeyIndex] = new ulong[wordsInBlock]; + } + + workingKey = new ulong[wordsInKey]; + + if (keyBytes.Length != wordsInKey * BITS_IN_WORD / BITS_IN_BYTE) + { + throw new ArgumentException("Invalid key parameter passed to DSTU7624Engine init"); + } + + /* Unpack encryption key bytes to words */ + Pack.LE_To_UInt64(keyBytes, 0, workingKey); + + ulong[] kt = new ulong[wordsInBlock]; + + KeyExpandKT(workingKey, kt); + + KeyExpandEven(workingKey, kt); + + KeyExpandOdd(); + + } + else if (parameters != null) + { + throw new ArgumentException("invalid parameter passed to Dstu7624 init - " + + Platform.GetTypeName(parameters)); + } + + this.forEncryption = forEncryption; + } + + private void KeyExpandKT(ulong[] key, ulong[] kt) + { + ulong[] k0 = new ulong[wordsInBlock]; + ulong[] k1 = new ulong[wordsInBlock]; + + internalState = new ulong[wordsInBlock]; + internalState[0] += (ulong)(wordsInBlock + wordsInKey + 1); + + if (wordsInBlock == wordsInKey) + { + Array.Copy(key, k0, k0.Length); + Array.Copy(key, k1, k1.Length); + } + else + { + Array.Copy(key, 0, k0, 0, wordsInBlock); + Array.Copy(key, wordsInBlock, k1, 0, wordsInBlock); + } + + AddRoundKeyExpand(k0); + + EncryptionRound(); + + XorRoundKeyExpand(k1); + + EncryptionRound(); + + AddRoundKeyExpand(k0); + + EncryptionRound(); + + Array.Copy(internalState, kt, wordsInBlock); + } + + private void KeyExpandEven(ulong[] key, ulong[] kt) + { + ulong[] initial_data = new ulong[wordsInKey]; + + ulong[] kt_round = new ulong[wordsInBlock]; + + ulong[] tmv = new ulong[wordsInBlock]; + + int round = 0; + + Array.Copy(key, initial_data, wordsInKey); + + for (int i = 0; i < wordsInBlock; i++) + { + tmv[i] = 0x0001000100010001; + } + + while (true) + { + Array.Copy(kt, internalState, wordsInBlock); + + AddRoundKeyExpand(tmv); + + Array.Copy(internalState, kt_round, wordsInBlock); + Array.Copy(initial_data, internalState, wordsInBlock); + + AddRoundKeyExpand(kt_round); + + EncryptionRound(); + + XorRoundKeyExpand(kt_round); + + EncryptionRound(); + + AddRoundKeyExpand(kt_round); + + Array.Copy(internalState, roundKeys[round], wordsInBlock); + + if (roundsAmount == round) + { + break; + } + if (wordsInKey != wordsInBlock) + { + round += 2; + + ShiftLeft(tmv); + + Array.Copy(kt, internalState, wordsInBlock); + + AddRoundKeyExpand(tmv); + + Array.Copy(internalState, kt_round, wordsInBlock); + Array.Copy(initial_data, wordsInBlock, internalState, 0, wordsInBlock); + + AddRoundKeyExpand(kt_round); + + EncryptionRound(); + + XorRoundKeyExpand(kt_round); + + EncryptionRound(); + + AddRoundKeyExpand(kt_round); + + Array.Copy(internalState, roundKeys[round], wordsInBlock); + + if (roundsAmount == round) + { + break; + } + } + + round += 2; + ShiftLeft(tmv); + + //Rotate initial data array on 1 element left + ulong temp = initial_data[0]; + Array.Copy(initial_data, 1, initial_data, 0, initial_data.Length - 1); + initial_data[initial_data.Length - 1] = temp; + } + } + private void KeyExpandOdd() + { + for (int i = 1; i < roundsAmount; i += 2) + { + Array.Copy(roundKeys[i - 1], roundKeys[i], wordsInBlock); + RotateLeft(roundKeys[i]); + } + } + #endregion + + + public virtual int ProcessBlock(byte[] input, int inOff, byte[] output, int outOff) + { + if (workingKey == null) + throw new InvalidOperationException("Dstu7624 engine not initialised"); + + Check.DataLength(input, inOff, GetBlockSize(), "input buffer too short"); + Check.OutputLength(output, outOff, GetBlockSize(), "output buffer too short"); + + if (forEncryption) + { + Encrypt(input, inOff, output, outOff); + } + else + { + Decrypt(input, inOff, output, outOff); + } + + return GetBlockSize(); + } + + private void Encrypt(byte[] plain, int inOff, byte[] cipherText, int outOff) + { + Pack.LE_To_UInt64(plain, inOff, internalState); + + int round = 0; + AddRoundKey(round); + + while (++round < roundsAmount) + { + EncryptionRound(); + XorRoundKey(round); + } + + EncryptionRound(); + AddRoundKey(round); + + Pack.UInt64_To_LE(internalState, cipherText, outOff); + } + + private void Decrypt(byte[] cipherText, int inOff, byte[] decryptedText, int outOff) + { + Pack.LE_To_UInt64(cipherText, inOff, internalState); + + int round = roundsAmount; + SubRoundKey(round); + + while (--round > 0) + { + DecryptionRound(); + XorRoundKey(round); + } + + DecryptionRound(); + SubRoundKey(round); + + Pack.UInt64_To_LE(internalState, decryptedText, outOff); + } + + private void AddRoundKeyExpand(ulong[] value) + { + for (int i = 0; i < wordsInBlock; i++) + { + internalState[i] += value[i]; + } + } + + private void EncryptionRound() + { + SubBytes(); + ShiftRows(); + MixColumns(); + } + + private void DecryptionRound() + { + InvMixColumns(); + InvShiftRows(); + InvSubBytes(); + } + + private void RotateLeft(ulong[] state_value) + { + int rotateBytesLength = 2 * state_value.Length + 3; + int bytesLength = state_value.Length * (BITS_IN_WORD / BITS_IN_BYTE); + + byte[] bytes = Pack.UInt64_To_LE(state_value); + byte[] buffer = new byte[rotateBytesLength]; + + Array.Copy(bytes, buffer, rotateBytesLength); + + Buffer.BlockCopy(bytes, rotateBytesLength, bytes, 0, bytesLength - rotateBytesLength); + + Array.Copy(buffer, 0, bytes, bytesLength - rotateBytesLength, rotateBytesLength); + + Pack.LE_To_UInt64(bytes, 0, state_value); + } + + private void ShiftLeft(ulong[] state_value) + { + for (int i = 0; i < state_value.Length; i++) + { + state_value[i] <<= 1; + } + Array.Reverse(state_value); + } + + private void XorRoundKeyExpand(ulong[] value) + { + for (int i = 0; i < wordsInBlock; i++) + { + internalState[i] ^= value[i]; + } + } + + private void XorRoundKey(int round) + { + ulong[] roundKey = roundKeys[round]; + for (int i = 0; i < wordsInBlock; i++) + { + internalState[i] ^= roundKey[i]; + } + } + + private void ShiftRows() + { + int row, col; + int shift = -1; + + byte[] stateBytes = Pack.UInt64_To_LE(internalState); + byte[] nstate = new byte[wordsInBlock * 8]; + + for (row = 0; row < 8; row++) + { + if (row % (8 / wordsInBlock) == 0) + { + shift += 1; + } + + for (col = 0; col < wordsInBlock; col++) + { + nstate[row + ((col + shift) % wordsInBlock) * 8] = stateBytes[row + col * 8]; + } + } + + Pack.LE_To_UInt64(nstate, 0, internalState); + } + + private void InvShiftRows() + { + int row, col; + int shift = -1; + + byte[] stateBytes = Pack.UInt64_To_LE(internalState); + byte[] nstate = new byte[wordsInBlock * 8]; + + for (row = 0; row < 8; row++) + { + if (row % (8 / wordsInBlock) == 0) + { + shift += 1; + } + + for (col = 0; col < wordsInBlock; col++) + { + nstate[row + col * 8] = stateBytes[row + ((col + shift) % wordsInBlock) * 8]; + } + } + + Pack.LE_To_UInt64(nstate, 0, internalState); + } + + private void AddRoundKey(int round) + { + for (int i = 0; i < wordsInBlock; ++i) + { + internalState[i] += roundKeys[round][i]; + } + } + + private void SubRoundKey(int round) + { + for (int i = 0; i < wordsInBlock; ++i) + { + internalState[i] -= roundKeys[round][i]; + } + } + + private void MixColumns() + { + MatrixMultiply(mdsMatrix); + } + + private void InvMixColumns() + { + MatrixMultiply(mdsInvMatrix); + } + + private void MatrixMultiply(byte[][] matrix) + { + int col, row, b; + byte product; + ulong result; + byte[] stateBytes = Pack.UInt64_To_LE(internalState); + + for (col = 0; col < wordsInBlock; ++col) + { + result = 0; + for (row = 8 - 1; row >= 0; --row) + { + product = 0; + for (b = 8 - 1; b >= 0; --b) + { + product ^= MultiplyGF(stateBytes[b + col * 8], matrix[row][b]); + } + result |= (ulong)product << (row * 8); + } + internalState[col] = result; + } + } + + private byte MultiplyGF(byte x, byte y) + { + byte r = 0; + byte hbit = 0; + + for (int i = 0; i < BITS_IN_BYTE; i++) + { + if ((y & 0x01) == 1) + { + r ^= x; + } + + hbit = (byte)(x & 0x80); + + x <<= 1; + + if (hbit == 0x80) + { + x = (byte)((int)x ^ REDUCTION_POLYNOMIAL); + } + y >>= 1; + } + return r; + } + + private void SubBytes() + { + for (int i = 0; i < wordsInBlock; i++) + { + internalState[i] = sboxesForEncryption[0][internalState[i] & 0x00000000000000FF] | + ((ulong)sboxesForEncryption[1][(internalState[i] & 0x000000000000FF00) >> 8] << 8) | + ((ulong)sboxesForEncryption[2][(internalState[i] & 0x0000000000FF0000) >> 16] << 16) | + ((ulong)sboxesForEncryption[3][(internalState[i] & 0x00000000FF000000) >> 24] << 24) | + ((ulong)sboxesForEncryption[0][(internalState[i] & 0x000000FF00000000) >> 32] << 32) | + ((ulong)sboxesForEncryption[1][(internalState[i] & 0x0000FF0000000000) >> 40] << 40) | + ((ulong)sboxesForEncryption[2][(internalState[i] & 0x00FF000000000000) >> 48] << 48) | + ((ulong)sboxesForEncryption[3][(internalState[i] & 0xFF00000000000000) >> 56] << 56); + } + } + + private void InvSubBytes() + { + for (int i = 0; i < wordsInBlock; i++) + { + internalState[i] = sboxesForDecryption[0][internalState[i] & 0x00000000000000FF] | + ((ulong)sboxesForDecryption[1][(internalState[i] & 0x000000000000FF00) >> 8] << 8) | + ((ulong)sboxesForDecryption[2][(internalState[i] & 0x0000000000FF0000) >> 16] << 16) | + ((ulong)sboxesForDecryption[3][(internalState[i] & 0x00000000FF000000) >> 24] << 24) | + ((ulong)sboxesForDecryption[0][(internalState[i] & 0x000000FF00000000) >> 32] << 32) | + ((ulong)sboxesForDecryption[1][(internalState[i] & 0x0000FF0000000000) >> 40] << 40) | + ((ulong)sboxesForDecryption[2][(internalState[i] & 0x00FF000000000000) >> 48] << 48) | + ((ulong)sboxesForDecryption[3][(internalState[i] & 0xFF00000000000000) >> 56] << 56); + } + } + + #region TABLES AND S-BOXES + + private byte[][] mdsMatrix = + { + new byte[] { 0x01, 0x01, 0x05, 0x01, 0x08, 0x06, 0x07, 0x04 }, + new byte[] { 0x04, 0x01, 0x01, 0x05, 0x01, 0x08, 0x06, 0x07 }, + new byte[] { 0x07, 0x04, 0x01, 0x01, 0x05, 0x01, 0x08, 0x06 }, + new byte[] { 0x06, 0x07, 0x04, 0x01, 0x01, 0x05, 0x01, 0x08 }, + new byte[] { 0x08, 0x06, 0x07, 0x04, 0x01, 0x01, 0x05, 0x01 }, + new byte[] { 0x01, 0x08, 0x06, 0x07, 0x04, 0x01, 0x01, 0x05 }, + new byte[] { 0x05, 0x01, 0x08, 0x06, 0x07, 0x04, 0x01, 0x01 }, + new byte[] { 0x01, 0x05, 0x01, 0x08, 0x06, 0x07, 0x04, 0x01 }, + }; + + private byte[][] mdsInvMatrix = + { + new byte[] { 0xAD, 0x95, 0x76, 0xA8, 0x2F, 0x49, 0xD7, 0xCA }, + new byte[] { 0xCA, 0xAD, 0x95, 0x76, 0xA8, 0x2F, 0x49, 0xD7 }, + new byte[] { 0xD7, 0xCA, 0xAD, 0x95, 0x76, 0xA8, 0x2F, 0x49 }, + new byte[] { 0x49, 0xD7, 0xCA, 0xAD, 0x95, 0x76, 0xA8, 0x2F }, + new byte[] { 0x2F, 0x49, 0xD7, 0xCA, 0xAD, 0x95, 0x76, 0xA8 }, + new byte[] { 0xA8, 0x2F, 0x49, 0xD7, 0xCA, 0xAD, 0x95, 0x76 }, + new byte[] { 0x76, 0xA8, 0x2F, 0x49, 0xD7, 0xCA, 0xAD, 0x95 }, + new byte[] { 0x95, 0x76, 0xA8, 0x2F, 0x49, 0xD7, 0xCA, 0xAD }, + }; + + private byte[][] sboxesForEncryption = + { + new byte[] + { + 0xa8, 0x43, 0x5f, 0x06, 0x6b, 0x75, 0x6c, 0x59, 0x71, 0xdf, 0x87, 0x95, 0x17, 0xf0, 0xd8, 0x09, + 0x6d, 0xf3, 0x1d, 0xcb, 0xc9, 0x4d, 0x2c, 0xaf, 0x79, 0xe0, 0x97, 0xfd, 0x6f, 0x4b, 0x45, 0x39, + 0x3e, 0xdd, 0xa3, 0x4f, 0xb4, 0xb6, 0x9a, 0x0e, 0x1f, 0xbf, 0x15, 0xe1, 0x49, 0xd2, 0x93, 0xc6, + 0x92, 0x72, 0x9e, 0x61, 0xd1, 0x63, 0xfa, 0xee, 0xf4, 0x19, 0xd5, 0xad, 0x58, 0xa4, 0xbb, 0xa1, + 0xdc, 0xf2, 0x83, 0x37, 0x42, 0xe4, 0x7a, 0x32, 0x9c, 0xcc, 0xab, 0x4a, 0x8f, 0x6e, 0x04, 0x27, + 0x2e, 0xe7, 0xe2, 0x5a, 0x96, 0x16, 0x23, 0x2b, 0xc2, 0x65, 0x66, 0x0f, 0xbc, 0xa9, 0x47, 0x41, + 0x34, 0x48, 0xfc, 0xb7, 0x6a, 0x88, 0xa5, 0x53, 0x86, 0xf9, 0x5b, 0xdb, 0x38, 0x7b, 0xc3, 0x1e, + 0x22, 0x33, 0x24, 0x28, 0x36, 0xc7, 0xb2, 0x3b, 0x8e, 0x77, 0xba, 0xf5, 0x14, 0x9f, 0x08, 0x55, + 0x9b, 0x4c, 0xfe, 0x60, 0x5c, 0xda, 0x18, 0x46, 0xcd, 0x7d, 0x21, 0xb0, 0x3f, 0x1b, 0x89, 0xff, + 0xeb, 0x84, 0x69, 0x3a, 0x9d, 0xd7, 0xd3, 0x70, 0x67, 0x40, 0xb5, 0xde, 0x5d, 0x30, 0x91, 0xb1, + 0x78, 0x11, 0x01, 0xe5, 0x00, 0x68, 0x98, 0xa0, 0xc5, 0x02, 0xa6, 0x74, 0x2d, 0x0b, 0xa2, 0x76, + 0xb3, 0xbe, 0xce, 0xbd, 0xae, 0xe9, 0x8a, 0x31, 0x1c, 0xec, 0xf1, 0x99, 0x94, 0xaa, 0xf6, 0x26, + 0x2f, 0xef, 0xe8, 0x8c, 0x35, 0x03, 0xd4, 0x7f, 0xfb, 0x05, 0xc1, 0x5e, 0x90, 0x20, 0x3d, 0x82, + 0xf7, 0xea, 0x0a, 0x0d, 0x7e, 0xf8, 0x50, 0x1a, 0xc4, 0x07, 0x57, 0xb8, 0x3c, 0x62, 0xe3, 0xc8, + 0xac, 0x52, 0x64, 0x10, 0xd0, 0xd9, 0x13, 0x0c, 0x12, 0x29, 0x51, 0xb9, 0xcf, 0xd6, 0x73, 0x8d, + 0x81, 0x54, 0xc0, 0xed, 0x4e, 0x44, 0xa7, 0x2a, 0x85, 0x25, 0xe6, 0xca, 0x7c, 0x8b, 0x56, 0x80 + }, + + new byte[] + { + 0xce, 0xbb, 0xeb, 0x92, 0xea, 0xcb, 0x13, 0xc1, 0xe9, 0x3a, 0xd6, 0xb2, 0xd2, 0x90, 0x17, 0xf8, + 0x42, 0x15, 0x56, 0xb4, 0x65, 0x1c, 0x88, 0x43, 0xc5, 0x5c, 0x36, 0xba, 0xf5, 0x57, 0x67, 0x8d, + 0x31, 0xf6, 0x64, 0x58, 0x9e, 0xf4, 0x22, 0xaa, 0x75, 0x0f, 0x02, 0xb1, 0xdf, 0x6d, 0x73, 0x4d, + 0x7c, 0x26, 0x2e, 0xf7, 0x08, 0x5d, 0x44, 0x3e, 0x9f, 0x14, 0xc8, 0xae, 0x54, 0x10, 0xd8, 0xbc, + 0x1a, 0x6b, 0x69, 0xf3, 0xbd, 0x33, 0xab, 0xfa, 0xd1, 0x9b, 0x68, 0x4e, 0x16, 0x95, 0x91, 0xee, + 0x4c, 0x63, 0x8e, 0x5b, 0xcc, 0x3c, 0x19, 0xa1, 0x81, 0x49, 0x7b, 0xd9, 0x6f, 0x37, 0x60, 0xca, + 0xe7, 0x2b, 0x48, 0xfd, 0x96, 0x45, 0xfc, 0x41, 0x12, 0x0d, 0x79, 0xe5, 0x89, 0x8c, 0xe3, 0x20, + 0x30, 0xdc, 0xb7, 0x6c, 0x4a, 0xb5, 0x3f, 0x97, 0xd4, 0x62, 0x2d, 0x06, 0xa4, 0xa5, 0x83, 0x5f, + 0x2a, 0xda, 0xc9, 0x00, 0x7e, 0xa2, 0x55, 0xbf, 0x11, 0xd5, 0x9c, 0xcf, 0x0e, 0x0a, 0x3d, 0x51, + 0x7d, 0x93, 0x1b, 0xfe, 0xc4, 0x47, 0x09, 0x86, 0x0b, 0x8f, 0x9d, 0x6a, 0x07, 0xb9, 0xb0, 0x98, + 0x18, 0x32, 0x71, 0x4b, 0xef, 0x3b, 0x70, 0xa0, 0xe4, 0x40, 0xff, 0xc3, 0xa9, 0xe6, 0x78, 0xf9, + 0x8b, 0x46, 0x80, 0x1e, 0x38, 0xe1, 0xb8, 0xa8, 0xe0, 0x0c, 0x23, 0x76, 0x1d, 0x25, 0x24, 0x05, + 0xf1, 0x6e, 0x94, 0x28, 0x9a, 0x84, 0xe8, 0xa3, 0x4f, 0x77, 0xd3, 0x85, 0xe2, 0x52, 0xf2, 0x82, + 0x50, 0x7a, 0x2f, 0x74, 0x53, 0xb3, 0x61, 0xaf, 0x39, 0x35, 0xde, 0xcd, 0x1f, 0x99, 0xac, 0xad, + 0x72, 0x2c, 0xdd, 0xd0, 0x87, 0xbe, 0x5e, 0xa6, 0xec, 0x04, 0xc6, 0x03, 0x34, 0xfb, 0xdb, 0x59, + 0xb6, 0xc2, 0x01, 0xf0, 0x5a, 0xed, 0xa7, 0x66, 0x21, 0x7f, 0x8a, 0x27, 0xc7, 0xc0, 0x29, 0xd7 + }, + + new byte[] + { + 0x93, 0xd9, 0x9a, 0xb5, 0x98, 0x22, 0x45, 0xfc, 0xba, 0x6a, 0xdf, 0x02, 0x9f, 0xdc, 0x51, 0x59, + 0x4a, 0x17, 0x2b, 0xc2, 0x94, 0xf4, 0xbb, 0xa3, 0x62, 0xe4, 0x71, 0xd4, 0xcd, 0x70, 0x16, 0xe1, + 0x49, 0x3c, 0xc0, 0xd8, 0x5c, 0x9b, 0xad, 0x85, 0x53, 0xa1, 0x7a, 0xc8, 0x2d, 0xe0, 0xd1, 0x72, + 0xa6, 0x2c, 0xc4, 0xe3, 0x76, 0x78, 0xb7, 0xb4, 0x09, 0x3b, 0x0e, 0x41, 0x4c, 0xde, 0xb2, 0x90, + 0x25, 0xa5, 0xd7, 0x03, 0x11, 0x00, 0xc3, 0x2e, 0x92, 0xef, 0x4e, 0x12, 0x9d, 0x7d, 0xcb, 0x35, + 0x10, 0xd5, 0x4f, 0x9e, 0x4d, 0xa9, 0x55, 0xc6, 0xd0, 0x7b, 0x18, 0x97, 0xd3, 0x36, 0xe6, 0x48, + 0x56, 0x81, 0x8f, 0x77, 0xcc, 0x9c, 0xb9, 0xe2, 0xac, 0xb8, 0x2f, 0x15, 0xa4, 0x7c, 0xda, 0x38, + 0x1e, 0x0b, 0x05, 0xd6, 0x14, 0x6e, 0x6c, 0x7e, 0x66, 0xfd, 0xb1, 0xe5, 0x60, 0xaf, 0x5e, 0x33, + 0x87, 0xc9, 0xf0, 0x5d, 0x6d, 0x3f, 0x88, 0x8d, 0xc7, 0xf7, 0x1d, 0xe9, 0xec, 0xed, 0x80, 0x29, + 0x27, 0xcf, 0x99, 0xa8, 0x50, 0x0f, 0x37, 0x24, 0x28, 0x30, 0x95, 0xd2, 0x3e, 0x5b, 0x40, 0x83, + 0xb3, 0x69, 0x57, 0x1f, 0x07, 0x1c, 0x8a, 0xbc, 0x20, 0xeb, 0xce, 0x8e, 0xab, 0xee, 0x31, 0xa2, + 0x73, 0xf9, 0xca, 0x3a, 0x1a, 0xfb, 0x0d, 0xc1, 0xfe, 0xfa, 0xf2, 0x6f, 0xbd, 0x96, 0xdd, 0x43, + 0x52, 0xb6, 0x08, 0xf3, 0xae, 0xbe, 0x19, 0x89, 0x32, 0x26, 0xb0, 0xea, 0x4b, 0x64, 0x84, 0x82, + 0x6b, 0xf5, 0x79, 0xbf, 0x01, 0x5f, 0x75, 0x63, 0x1b, 0x23, 0x3d, 0x68, 0x2a, 0x65, 0xe8, 0x91, + 0xf6, 0xff, 0x13, 0x58, 0xf1, 0x47, 0x0a, 0x7f, 0xc5, 0xa7, 0xe7, 0x61, 0x5a, 0x06, 0x46, 0x44, + 0x42, 0x04, 0xa0, 0xdb, 0x39, 0x86, 0x54, 0xaa, 0x8c, 0x34, 0x21, 0x8b, 0xf8, 0x0c, 0x74, 0x67 + }, + + new byte[] + { + 0x68, 0x8d, 0xca, 0x4d, 0x73, 0x4b, 0x4e, 0x2a, 0xd4, 0x52, 0x26, 0xb3, 0x54, 0x1e, 0x19, 0x1f, + 0x22, 0x03, 0x46, 0x3d, 0x2d, 0x4a, 0x53, 0x83, 0x13, 0x8a, 0xb7, 0xd5, 0x25, 0x79, 0xf5, 0xbd, + 0x58, 0x2f, 0x0d, 0x02, 0xed, 0x51, 0x9e, 0x11, 0xf2, 0x3e, 0x55, 0x5e, 0xd1, 0x16, 0x3c, 0x66, + 0x70, 0x5d, 0xf3, 0x45, 0x40, 0xcc, 0xe8, 0x94, 0x56, 0x08, 0xce, 0x1a, 0x3a, 0xd2, 0xe1, 0xdf, + 0xb5, 0x38, 0x6e, 0x0e, 0xe5, 0xf4, 0xf9, 0x86, 0xe9, 0x4f, 0xd6, 0x85, 0x23, 0xcf, 0x32, 0x99, + 0x31, 0x14, 0xae, 0xee, 0xc8, 0x48, 0xd3, 0x30, 0xa1, 0x92, 0x41, 0xb1, 0x18, 0xc4, 0x2c, 0x71, + 0x72, 0x44, 0x15, 0xfd, 0x37, 0xbe, 0x5f, 0xaa, 0x9b, 0x88, 0xd8, 0xab, 0x89, 0x9c, 0xfa, 0x60, + 0xea, 0xbc, 0x62, 0x0c, 0x24, 0xa6, 0xa8, 0xec, 0x67, 0x20, 0xdb, 0x7c, 0x28, 0xdd, 0xac, 0x5b, + 0x34, 0x7e, 0x10, 0xf1, 0x7b, 0x8f, 0x63, 0xa0, 0x05, 0x9a, 0x43, 0x77, 0x21, 0xbf, 0x27, 0x09, + 0xc3, 0x9f, 0xb6, 0xd7, 0x29, 0xc2, 0xeb, 0xc0, 0xa4, 0x8b, 0x8c, 0x1d, 0xfb, 0xff, 0xc1, 0xb2, + 0x97, 0x2e, 0xf8, 0x65, 0xf6, 0x75, 0x07, 0x04, 0x49, 0x33, 0xe4, 0xd9, 0xb9, 0xd0, 0x42, 0xc7, + 0x6c, 0x90, 0x00, 0x8e, 0x6f, 0x50, 0x01, 0xc5, 0xda, 0x47, 0x3f, 0xcd, 0x69, 0xa2, 0xe2, 0x7a, + 0xa7, 0xc6, 0x93, 0x0f, 0x0a, 0x06, 0xe6, 0x2b, 0x96, 0xa3, 0x1c, 0xaf, 0x6a, 0x12, 0x84, 0x39, + 0xe7, 0xb0, 0x82, 0xf7, 0xfe, 0x9d, 0x87, 0x5c, 0x81, 0x35, 0xde, 0xb4, 0xa5, 0xfc, 0x80, 0xef, + 0xcb, 0xbb, 0x6b, 0x76, 0xba, 0x5a, 0x7d, 0x78, 0x0b, 0x95, 0xe3, 0xad, 0x74, 0x98, 0x3b, 0x36, + 0x64, 0x6d, 0xdc, 0xf0, 0x59, 0xa9, 0x4c, 0x17, 0x7f, 0x91, 0xb8, 0xc9, 0x57, 0x1b, 0xe0, 0x61 + } + }; + + private byte[][] sboxesForDecryption = + { + new byte[] + { + 0xa4, 0xa2, 0xa9, 0xc5, 0x4e, 0xc9, 0x03, 0xd9, 0x7e, 0x0f, 0xd2, 0xad, 0xe7, 0xd3, 0x27, 0x5b, + 0xe3, 0xa1, 0xe8, 0xe6, 0x7c, 0x2a, 0x55, 0x0c, 0x86, 0x39, 0xd7, 0x8d, 0xb8, 0x12, 0x6f, 0x28, + 0xcd, 0x8a, 0x70, 0x56, 0x72, 0xf9, 0xbf, 0x4f, 0x73, 0xe9, 0xf7, 0x57, 0x16, 0xac, 0x50, 0xc0, + 0x9d, 0xb7, 0x47, 0x71, 0x60, 0xc4, 0x74, 0x43, 0x6c, 0x1f, 0x93, 0x77, 0xdc, 0xce, 0x20, 0x8c, + 0x99, 0x5f, 0x44, 0x01, 0xf5, 0x1e, 0x87, 0x5e, 0x61, 0x2c, 0x4b, 0x1d, 0x81, 0x15, 0xf4, 0x23, + 0xd6, 0xea, 0xe1, 0x67, 0xf1, 0x7f, 0xfe, 0xda, 0x3c, 0x07, 0x53, 0x6a, 0x84, 0x9c, 0xcb, 0x02, + 0x83, 0x33, 0xdd, 0x35, 0xe2, 0x59, 0x5a, 0x98, 0xa5, 0x92, 0x64, 0x04, 0x06, 0x10, 0x4d, 0x1c, + 0x97, 0x08, 0x31, 0xee, 0xab, 0x05, 0xaf, 0x79, 0xa0, 0x18, 0x46, 0x6d, 0xfc, 0x89, 0xd4, 0xc7, + 0xff, 0xf0, 0xcf, 0x42, 0x91, 0xf8, 0x68, 0x0a, 0x65, 0x8e, 0xb6, 0xfd, 0xc3, 0xef, 0x78, 0x4c, + 0xcc, 0x9e, 0x30, 0x2e, 0xbc, 0x0b, 0x54, 0x1a, 0xa6, 0xbb, 0x26, 0x80, 0x48, 0x94, 0x32, 0x7d, + 0xa7, 0x3f, 0xae, 0x22, 0x3d, 0x66, 0xaa, 0xf6, 0x00, 0x5d, 0xbd, 0x4a, 0xe0, 0x3b, 0xb4, 0x17, + 0x8b, 0x9f, 0x76, 0xb0, 0x24, 0x9a, 0x25, 0x63, 0xdb, 0xeb, 0x7a, 0x3e, 0x5c, 0xb3, 0xb1, 0x29, + 0xf2, 0xca, 0x58, 0x6e, 0xd8, 0xa8, 0x2f, 0x75, 0xdf, 0x14, 0xfb, 0x13, 0x49, 0x88, 0xb2, 0xec, + 0xe4, 0x34, 0x2d, 0x96, 0xc6, 0x3a, 0xed, 0x95, 0x0e, 0xe5, 0x85, 0x6b, 0x40, 0x21, 0x9b, 0x09, + 0x19, 0x2b, 0x52, 0xde, 0x45, 0xa3, 0xfa, 0x51, 0xc2, 0xb5, 0xd1, 0x90, 0xb9, 0xf3, 0x37, 0xc1, + 0x0d, 0xba, 0x41, 0x11, 0x38, 0x7b, 0xbe, 0xd0, 0xd5, 0x69, 0x36, 0xc8, 0x62, 0x1b, 0x82, 0x8f + }, + + new byte[] + { + 0x83, 0xf2, 0x2a, 0xeb, 0xe9, 0xbf, 0x7b, 0x9c, 0x34, 0x96, 0x8d, 0x98, 0xb9, 0x69, 0x8c, 0x29, + 0x3d, 0x88, 0x68, 0x06, 0x39, 0x11, 0x4c, 0x0e, 0xa0, 0x56, 0x40, 0x92, 0x15, 0xbc, 0xb3, 0xdc, + 0x6f, 0xf8, 0x26, 0xba, 0xbe, 0xbd, 0x31, 0xfb, 0xc3, 0xfe, 0x80, 0x61, 0xe1, 0x7a, 0x32, 0xd2, + 0x70, 0x20, 0xa1, 0x45, 0xec, 0xd9, 0x1a, 0x5d, 0xb4, 0xd8, 0x09, 0xa5, 0x55, 0x8e, 0x37, 0x76, + 0xa9, 0x67, 0x10, 0x17, 0x36, 0x65, 0xb1, 0x95, 0x62, 0x59, 0x74, 0xa3, 0x50, 0x2f, 0x4b, 0xc8, + 0xd0, 0x8f, 0xcd, 0xd4, 0x3c, 0x86, 0x12, 0x1d, 0x23, 0xef, 0xf4, 0x53, 0x19, 0x35, 0xe6, 0x7f, + 0x5e, 0xd6, 0x79, 0x51, 0x22, 0x14, 0xf7, 0x1e, 0x4a, 0x42, 0x9b, 0x41, 0x73, 0x2d, 0xc1, 0x5c, + 0xa6, 0xa2, 0xe0, 0x2e, 0xd3, 0x28, 0xbb, 0xc9, 0xae, 0x6a, 0xd1, 0x5a, 0x30, 0x90, 0x84, 0xf9, + 0xb2, 0x58, 0xcf, 0x7e, 0xc5, 0xcb, 0x97, 0xe4, 0x16, 0x6c, 0xfa, 0xb0, 0x6d, 0x1f, 0x52, 0x99, + 0x0d, 0x4e, 0x03, 0x91, 0xc2, 0x4d, 0x64, 0x77, 0x9f, 0xdd, 0xc4, 0x49, 0x8a, 0x9a, 0x24, 0x38, + 0xa7, 0x57, 0x85, 0xc7, 0x7c, 0x7d, 0xe7, 0xf6, 0xb7, 0xac, 0x27, 0x46, 0xde, 0xdf, 0x3b, 0xd7, + 0x9e, 0x2b, 0x0b, 0xd5, 0x13, 0x75, 0xf0, 0x72, 0xb6, 0x9d, 0x1b, 0x01, 0x3f, 0x44, 0xe5, 0x87, + 0xfd, 0x07, 0xf1, 0xab, 0x94, 0x18, 0xea, 0xfc, 0x3a, 0x82, 0x5f, 0x05, 0x54, 0xdb, 0x00, 0x8b, + 0xe3, 0x48, 0x0c, 0xca, 0x78, 0x89, 0x0a, 0xff, 0x3e, 0x5b, 0x81, 0xee, 0x71, 0xe2, 0xda, 0x2c, + 0xb8, 0xb5, 0xcc, 0x6e, 0xa8, 0x6b, 0xad, 0x60, 0xc6, 0x08, 0x04, 0x02, 0xe8, 0xf5, 0x4f, 0xa4, + 0xf3, 0xc0, 0xce, 0x43, 0x25, 0x1c, 0x21, 0x33, 0x0f, 0xaf, 0x47, 0xed, 0x66, 0x63, 0x93, 0xaa + }, + + new byte[] + { + 0x45, 0xd4, 0x0b, 0x43, 0xf1, 0x72, 0xed, 0xa4, 0xc2, 0x38, 0xe6, 0x71, 0xfd, 0xb6, 0x3a, 0x95, + 0x50, 0x44, 0x4b, 0xe2, 0x74, 0x6b, 0x1e, 0x11, 0x5a, 0xc6, 0xb4, 0xd8, 0xa5, 0x8a, 0x70, 0xa3, + 0xa8, 0xfa, 0x05, 0xd9, 0x97, 0x40, 0xc9, 0x90, 0x98, 0x8f, 0xdc, 0x12, 0x31, 0x2c, 0x47, 0x6a, + 0x99, 0xae, 0xc8, 0x7f, 0xf9, 0x4f, 0x5d, 0x96, 0x6f, 0xf4, 0xb3, 0x39, 0x21, 0xda, 0x9c, 0x85, + 0x9e, 0x3b, 0xf0, 0xbf, 0xef, 0x06, 0xee, 0xe5, 0x5f, 0x20, 0x10, 0xcc, 0x3c, 0x54, 0x4a, 0x52, + 0x94, 0x0e, 0xc0, 0x28, 0xf6, 0x56, 0x60, 0xa2, 0xe3, 0x0f, 0xec, 0x9d, 0x24, 0x83, 0x7e, 0xd5, + 0x7c, 0xeb, 0x18, 0xd7, 0xcd, 0xdd, 0x78, 0xff, 0xdb, 0xa1, 0x09, 0xd0, 0x76, 0x84, 0x75, 0xbb, + 0x1d, 0x1a, 0x2f, 0xb0, 0xfe, 0xd6, 0x34, 0x63, 0x35, 0xd2, 0x2a, 0x59, 0x6d, 0x4d, 0x77, 0xe7, + 0x8e, 0x61, 0xcf, 0x9f, 0xce, 0x27, 0xf5, 0x80, 0x86, 0xc7, 0xa6, 0xfb, 0xf8, 0x87, 0xab, 0x62, + 0x3f, 0xdf, 0x48, 0x00, 0x14, 0x9a, 0xbd, 0x5b, 0x04, 0x92, 0x02, 0x25, 0x65, 0x4c, 0x53, 0x0c, + 0xf2, 0x29, 0xaf, 0x17, 0x6c, 0x41, 0x30, 0xe9, 0x93, 0x55, 0xf7, 0xac, 0x68, 0x26, 0xc4, 0x7d, + 0xca, 0x7a, 0x3e, 0xa0, 0x37, 0x03, 0xc1, 0x36, 0x69, 0x66, 0x08, 0x16, 0xa7, 0xbc, 0xc5, 0xd3, + 0x22, 0xb7, 0x13, 0x46, 0x32, 0xe8, 0x57, 0x88, 0x2b, 0x81, 0xb2, 0x4e, 0x64, 0x1c, 0xaa, 0x91, + 0x58, 0x2e, 0x9b, 0x5c, 0x1b, 0x51, 0x73, 0x42, 0x23, 0x01, 0x6e, 0xf3, 0x0d, 0xbe, 0x3d, 0x0a, + 0x2d, 0x1f, 0x67, 0x33, 0x19, 0x7b, 0x5e, 0xea, 0xde, 0x8b, 0xcb, 0xa9, 0x8c, 0x8d, 0xad, 0x49, + 0x82, 0xe4, 0xba, 0xc3, 0x15, 0xd1, 0xe0, 0x89, 0xfc, 0xb1, 0xb9, 0xb5, 0x07, 0x79, 0xb8, 0xe1 + }, + + new byte[] + { + 0xb2, 0xb6, 0x23, 0x11, 0xa7, 0x88, 0xc5, 0xa6, 0x39, 0x8f, 0xc4, 0xe8, 0x73, 0x22, 0x43, 0xc3, + 0x82, 0x27, 0xcd, 0x18, 0x51, 0x62, 0x2d, 0xf7, 0x5c, 0x0e, 0x3b, 0xfd, 0xca, 0x9b, 0x0d, 0x0f, + 0x79, 0x8c, 0x10, 0x4c, 0x74, 0x1c, 0x0a, 0x8e, 0x7c, 0x94, 0x07, 0xc7, 0x5e, 0x14, 0xa1, 0x21, + 0x57, 0x50, 0x4e, 0xa9, 0x80, 0xd9, 0xef, 0x64, 0x41, 0xcf, 0x3c, 0xee, 0x2e, 0x13, 0x29, 0xba, + 0x34, 0x5a, 0xae, 0x8a, 0x61, 0x33, 0x12, 0xb9, 0x55, 0xa8, 0x15, 0x05, 0xf6, 0x03, 0x06, 0x49, + 0xb5, 0x25, 0x09, 0x16, 0x0c, 0x2a, 0x38, 0xfc, 0x20, 0xf4, 0xe5, 0x7f, 0xd7, 0x31, 0x2b, 0x66, + 0x6f, 0xff, 0x72, 0x86, 0xf0, 0xa3, 0x2f, 0x78, 0x00, 0xbc, 0xcc, 0xe2, 0xb0, 0xf1, 0x42, 0xb4, + 0x30, 0x5f, 0x60, 0x04, 0xec, 0xa5, 0xe3, 0x8b, 0xe7, 0x1d, 0xbf, 0x84, 0x7b, 0xe6, 0x81, 0xf8, + 0xde, 0xd8, 0xd2, 0x17, 0xce, 0x4b, 0x47, 0xd6, 0x69, 0x6c, 0x19, 0x99, 0x9a, 0x01, 0xb3, 0x85, + 0xb1, 0xf9, 0x59, 0xc2, 0x37, 0xe9, 0xc8, 0xa0, 0xed, 0x4f, 0x89, 0x68, 0x6d, 0xd5, 0x26, 0x91, + 0x87, 0x58, 0xbd, 0xc9, 0x98, 0xdc, 0x75, 0xc0, 0x76, 0xf5, 0x67, 0x6b, 0x7e, 0xeb, 0x52, 0xcb, + 0xd1, 0x5b, 0x9f, 0x0b, 0xdb, 0x40, 0x92, 0x1a, 0xfa, 0xac, 0xe4, 0xe1, 0x71, 0x1f, 0x65, 0x8d, + 0x97, 0x9e, 0x95, 0x90, 0x5d, 0xb7, 0xc1, 0xaf, 0x54, 0xfb, 0x02, 0xe0, 0x35, 0xbb, 0x3a, 0x4d, + 0xad, 0x2c, 0x3d, 0x56, 0x08, 0x1b, 0x4a, 0x93, 0x6a, 0xab, 0xb8, 0x7a, 0xf2, 0x7d, 0xda, 0x3f, + 0xfe, 0x3e, 0xbe, 0xea, 0xaa, 0x44, 0xc6, 0xd0, 0x36, 0x48, 0x70, 0x96, 0x77, 0x24, 0x53, 0xdf, + 0xf3, 0x83, 0x28, 0x32, 0x45, 0x1e, 0xa4, 0xd3, 0xa2, 0x46, 0x6e, 0x9c, 0xdd, 0x63, 0xd4, 0x9d + } + }; + + #endregion + + public virtual string AlgorithmName + { + get { return "Dstu7624"; } + } + + public virtual int GetBlockSize() + { + return blockSizeBits / BITS_IN_BYTE; + } + + public virtual bool IsPartialBlockOkay + { + get { return false; } + } + + public virtual void Reset() + { + } + } +} diff --git a/bc-sharp-crypto/src/crypto/engines/Dstu7624WrapEngine.cs b/bc-sharp-crypto/src/crypto/engines/Dstu7624WrapEngine.cs new file mode 100644 index 0000000..9cb9824 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/Dstu7624WrapEngine.cs @@ -0,0 +1,216 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Utilities; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Engines +{ + public class Dstu7624WrapEngine + : IWrapper + { + private KeyParameter param; + private Dstu7624Engine engine; + private bool forWrapping; + private int blockSize; + + public Dstu7624WrapEngine(int blockSizeBits) + { + engine = new Dstu7624Engine(blockSizeBits); + param = null; + + blockSize = blockSizeBits / 8; + } + + public string AlgorithmName + { + get { return "Dstu7624WrapEngine"; } + } + + public void Init(bool forWrapping, ICipherParameters parameters) + { + this.forWrapping = forWrapping; + + if (parameters is KeyParameter) + { + this.param = (KeyParameter)parameters; + + engine.Init(forWrapping, param); + } + else + { + throw new ArgumentException("Bad parameters passed to Dstu7624WrapEngine"); + } + } + + public byte[] Wrap(byte[] input, int inOff, int length) + { + if (!forWrapping) + throw new InvalidOperationException("Not set for wrapping"); + + if (length % blockSize != 0) + throw new ArgumentException("Padding not supported"); + + int n = 2 * (1 + length / blockSize); + int V = (n - 1) * 6; + + byte[] buffer = new byte[length + blockSize]; + Array.Copy(input, inOff, buffer, 0, length); + //Console.WriteLine(Org.BouncyCastle.Utilities.Encoders.Hex.ToHexString(buffer)); + + byte[] B = new byte[blockSize / 2]; + Array.Copy(buffer, 0, B, 0, blockSize / 2); + //Console.WriteLine("B0: "+ Org.BouncyCastle.Utilities.Encoders.Hex.ToHexString(B)); + + IList bTemp = Platform.CreateArrayList(); + int bHalfBlocksLen = buffer.Length - blockSize / 2; + int bufOff = blockSize / 2; + while (bHalfBlocksLen != 0) + { + byte[] temp = new byte[blockSize / 2]; + Array.Copy(buffer, bufOff, temp, 0, blockSize / 2); + //Console.WriteLine(Org.BouncyCastle.Utilities.Encoders.Hex.ToHexString(buffer)); + //Console.WriteLine(buffer.Length); + //Console.WriteLine("b: " + Org.BouncyCastle.Utilities.Encoders.Hex.ToHexString(temp)); + + bTemp.Add(temp); + + bHalfBlocksLen -= blockSize / 2; + bufOff += blockSize / 2; + } + + for (int j = 0; j < V; j++) + { + Array.Copy(B, 0, buffer, 0, blockSize / 2); + Array.Copy((byte[])bTemp[0], 0, buffer, blockSize / 2, blockSize / 2); + + engine.ProcessBlock(buffer, 0, buffer, 0); + + byte[] intArray = Pack.UInt32_To_LE((uint)(j + 1)); + for (int byteNum = 0; byteNum < intArray.Length; byteNum++) + { + buffer[byteNum + blockSize / 2] ^= intArray[byteNum]; + } + + Array.Copy(buffer, blockSize / 2, B, 0, blockSize / 2); + + for (int i = 2; i < n; i++) + { + Array.Copy((byte[])bTemp[i - 1], 0, (byte[])bTemp[i - 2], 0, blockSize / 2); + } + + Array.Copy(buffer, 0, (byte[])bTemp[n - 2], 0, blockSize / 2); + + //Console.WriteLine("B" + j.ToString() + ": " + Org.BouncyCastle.Utilities.Encoders.Hex.ToHexString(B)); + //Console.WriteLine("b: " + Org.BouncyCastle.Utilities.Encoders.Hex.ToHexString(bTemp[0])); + //Console.WriteLine("b: " + Org.BouncyCastle.Utilities.Encoders.Hex.ToHexString(bTemp[1])); + //Console.WriteLine("b: " + Org.BouncyCastle.Utilities.Encoders.Hex.ToHexString(bTemp[2])); + + //Console.WriteLine(Org.BouncyCastle.Utilities.Encoders.Hex.ToHexString(buffer)); + } + + Array.Copy(B, 0, buffer, 0, blockSize / 2); + bufOff = blockSize / 2; + + for (int i = 0; i < n - 1; i++) + { + Array.Copy((byte[])bTemp[i], 0, buffer, bufOff, blockSize / 2); + bufOff += blockSize / 2; + } + + return buffer; + } + + public byte[] Unwrap(byte[] input, int inOff, int length) + { + if (forWrapping) + throw new InvalidOperationException("not set for unwrapping"); + + if (length % blockSize != 0) + throw new ArgumentException("Padding not supported"); + + int n = 2 * length / blockSize; + int V = (n - 1) * 6; + + byte[] buffer = new byte[length]; + Array.Copy(input, inOff, buffer, 0, length); + + byte[] B = new byte[blockSize / 2]; + Array.Copy(buffer, 0, B, 0, blockSize / 2); + //Console.WriteLine("B18: " + Org.BouncyCastle.Utilities.Encoders.Hex.ToHexString(B)); + + IList bTemp = Platform.CreateArrayList(); + + int bHalfBlocksLen = buffer.Length - blockSize / 2; + int bufOff = blockSize / 2; + while (bHalfBlocksLen != 0) + { + byte[] temp = new byte[blockSize / 2]; + Array.Copy(buffer, bufOff, temp, 0, blockSize / 2); + //Console.WriteLine(Org.BouncyCastle.Utilities.Encoders.Hex.ToHexString(buffer)); + //Console.WriteLine(buffer.Length); + //Console.WriteLine("b: " + Org.BouncyCastle.Utilities.Encoders.Hex.ToHexString(temp)); + + bTemp.Add(temp); + + bHalfBlocksLen -= blockSize / 2; + bufOff += blockSize / 2; + } + + for (int j = 0; j < V; j++) + { + Array.Copy((byte[])bTemp[n - 2], 0, buffer, 0, blockSize / 2); + Array.Copy(B, 0, buffer, blockSize / 2, blockSize / 2); + + byte[] intArray = Pack.UInt32_To_LE((uint)(V - j)); + for (int byteNum = 0; byteNum < intArray.Length; byteNum++) + { + buffer[byteNum + blockSize / 2] ^= intArray[byteNum]; + } + + //Console.WriteLine(Org.BouncyCastle.Utilities.Encoders.Hex.ToHexString(buffer)); + + engine.ProcessBlock(buffer, 0, buffer, 0); + + //Console.WriteLine(Org.BouncyCastle.Utilities.Encoders.Hex.ToHexString(buffer)); + + Array.Copy(buffer, 0, B, 0, blockSize / 2); + + for (int i = 2; i < n; i++) + { + Array.Copy((byte[])bTemp[n - i - 1], 0, (byte[])bTemp[n - i], 0, blockSize / 2); + } + + Array.Copy(buffer, blockSize / 2, (byte[])bTemp[0], 0, blockSize / 2); + + //Console.WriteLine("B" + (V - j - 1).ToString() + ": " + Org.BouncyCastle.Utilities.Encoders.Hex.ToHexString(B)); + //Console.WriteLine("b: " + Org.BouncyCastle.Utilities.Encoders.Hex.ToHexString(bTemp[0])); + //Console.WriteLine("b: " + Org.BouncyCastle.Utilities.Encoders.Hex.ToHexString(bTemp[1])); + //Console.WriteLine("b: " + Org.BouncyCastle.Utilities.Encoders.Hex.ToHexString(bTemp[2])); + + //Console.WriteLine(Org.BouncyCastle.Utilities.Encoders.Hex.ToHexString(buffer)); + } + + Array.Copy(B, 0, buffer, 0, blockSize / 2); + bufOff = blockSize / 2; + + for (int i = 0; i < n - 1; i++) + { + Array.Copy((byte[])bTemp[i], 0, buffer, bufOff, blockSize / 2); + bufOff += blockSize / 2; + } + + byte diff = 0; + for (int i = buffer.Length - blockSize; i < buffer.Length; ++i) + { + diff |= buffer[i]; + } + + if (diff != 0) + throw new InvalidCipherTextException("checksum failed"); + + return Arrays.CopyOfRange(buffer, 0, buffer.Length - blockSize); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/engines/ElGamalEngine.cs b/bc-sharp-crypto/src/crypto/engines/ElGamalEngine.cs new file mode 100644 index 0000000..197d7bc --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/ElGamalEngine.cs @@ -0,0 +1,178 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Engines +{ + /** + * this does your basic ElGamal algorithm. + */ + public class ElGamalEngine + : IAsymmetricBlockCipher + { + private ElGamalKeyParameters key; + private SecureRandom random; + private bool forEncryption; + private int bitSize; + + public virtual string AlgorithmName + { + get { return "ElGamal"; } + } + + /** + * initialise the ElGamal engine. + * + * @param forEncryption true if we are encrypting, false otherwise. + * @param param the necessary ElGamal key parameters. + */ + public virtual void Init( + bool forEncryption, + ICipherParameters parameters) + { + if (parameters is ParametersWithRandom) + { + ParametersWithRandom p = (ParametersWithRandom) parameters; + + this.key = (ElGamalKeyParameters) p.Parameters; + this.random = p.Random; + } + else + { + this.key = (ElGamalKeyParameters) parameters; + this.random = new SecureRandom(); + } + + this.forEncryption = forEncryption; + this.bitSize = key.Parameters.P.BitLength; + + if (forEncryption) + { + if (!(key is ElGamalPublicKeyParameters)) + { + throw new ArgumentException("ElGamalPublicKeyParameters are required for encryption."); + } + } + else + { + if (!(key is ElGamalPrivateKeyParameters)) + { + throw new ArgumentException("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 virtual 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 virtual 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 length the length of the data to be processed. + * @return the result of the ElGamal process. + * @exception DataLengthException the input block is too large. + */ + public virtual byte[] ProcessBlock( + byte[] input, + int inOff, + int length) + { + if (key == null) + throw new InvalidOperationException("ElGamal engine not initialised"); + + int maxLength = forEncryption + ? (bitSize - 1 + 7) / 8 + : GetInputBlockSize(); + + if (length > maxLength) + throw new DataLengthException("input too large for ElGamal cipher.\n"); + + BigInteger p = key.Parameters.P; + + byte[] output; + if (key is ElGamalPrivateKeyParameters) // decryption + { + int halfLength = length / 2; + BigInteger gamma = new BigInteger(1, input, inOff, halfLength); + BigInteger phi = new BigInteger(1, input, inOff + halfLength, halfLength); + + 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(BigInteger.One).Subtract(priv.X), p).Multiply(phi).Mod(p); + + output = m.ToByteArrayUnsigned(); + } + else // encryption + { + BigInteger tmp = new BigInteger(1, input, inOff, length); + + if (tmp.BitLength >= p.BitLength) + throw new DataLengthException("input too large for ElGamal cipher.\n"); + + + ElGamalPublicKeyParameters pub = (ElGamalPublicKeyParameters) key; + + BigInteger pSub2 = p.Subtract(BigInteger.Two); + + // TODO In theory, a series of 'k', 'g.ModPow(k, p)' and 'y.ModPow(k, p)' can be pre-calculated + BigInteger k; + do + { + k = new BigInteger(p.BitLength, random); + } + while (k.SignValue == 0 || k.CompareTo(pSub2) > 0); + + BigInteger g = key.Parameters.G; + BigInteger gamma = g.ModPow(k, p); + BigInteger phi = tmp.Multiply(pub.Y.ModPow(k, p)).Mod(p); + + output = new byte[this.GetOutputBlockSize()]; + + // TODO Add methods to allow writing BigInteger to existing byte array? + byte[] out1 = gamma.ToByteArrayUnsigned(); + byte[] out2 = phi.ToByteArrayUnsigned(); + out1.CopyTo(output, output.Length / 2 - out1.Length); + out2.CopyTo(output, output.Length - out2.Length); + } + + return output; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/engines/GOST28147Engine.cs b/bc-sharp-crypto/src/crypto/engines/GOST28147Engine.cs new file mode 100644 index 0000000..71e6d9e --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/GOST28147Engine.cs @@ -0,0 +1,368 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Engines +{ + /** + * implementation of GOST 28147-89 + */ + public class Gost28147Engine + : IBlockCipher + { + private const int BlockSize = 8; + private int[] workingKey = null; + private bool forEncryption; + + private byte[] S = Sbox_Default; + + // these are the S-boxes given in Applied Cryptography 2nd Ed., p. 333 + // This is default S-box! + private static readonly byte[] Sbox_Default = { + 0x4,0xA,0x9,0x2,0xD,0x8,0x0,0xE,0x6,0xB,0x1,0xC,0x7,0xF,0x5,0x3, + 0xE,0xB,0x4,0xC,0x6,0xD,0xF,0xA,0x2,0x3,0x8,0x1,0x0,0x7,0x5,0x9, + 0x5,0x8,0x1,0xD,0xA,0x3,0x4,0x2,0xE,0xF,0xC,0x7,0x6,0x0,0x9,0xB, + 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://tools.ietf.org/id/draft-popov-cryptopro-cpalgs-01.txt + * http://tools.ietf.org/id/draft-popov-cryptopro-cpalgs-02.txt + */ + private static readonly 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 readonly 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 readonly 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 readonly 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 readonly 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 readonly 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 readonly 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 readonly IDictionary sBoxes = Platform.CreateHashtable(); + + static Gost28147Engine() + { + AddSBox("Default", Sbox_Default); + AddSBox("E-TEST", ESbox_Test); + AddSBox("E-A", ESbox_A); + AddSBox("E-B", ESbox_B); + AddSBox("E-C", ESbox_C); + AddSBox("E-D", ESbox_D); + AddSBox("D-TEST", DSbox_Test); + AddSBox("D-A", DSbox_A); + } + + private static void AddSBox(string sBoxName, byte[] sBox) + { + sBoxes.Add(Platform.ToUpperInvariant(sBoxName), sBox); + } + + /** + * standard constructor. + */ + public Gost28147Engine() + { + } + + /** + * initialise an Gost28147 cipher. + * + * @param forEncryption whether or not we are for encryption. + * @param parameters the parameters required to set up the cipher. + * @exception ArgumentException if the parameters argument is inappropriate. + */ + public virtual void Init( + bool forEncryption, + ICipherParameters parameters) + { + if (parameters is ParametersWithSBox) + { + ParametersWithSBox param = (ParametersWithSBox)parameters; + + // + // Set the S-Box + // + byte[] sBox = param.GetSBox(); + if (sBox.Length != Sbox_Default.Length) + throw new ArgumentException("invalid S-box passed to GOST28147 init"); + + this.S = Arrays.Clone(sBox); + + // + // set key if there is one + // + if (param.Parameters != null) + { + workingKey = generateWorkingKey(forEncryption, + ((KeyParameter)param.Parameters).GetKey()); + } + } + else if (parameters is KeyParameter) + { + workingKey = generateWorkingKey(forEncryption, + ((KeyParameter)parameters).GetKey()); + } + else if (parameters != null) + { + throw new ArgumentException("invalid parameter passed to Gost28147 init - " + + Platform.GetTypeName(parameters)); + } + } + + public virtual string AlgorithmName + { + get { return "Gost28147"; } + } + + public virtual bool IsPartialBlockOkay + { + get { return false; } + } + + public virtual int GetBlockSize() + { + return BlockSize; + } + + public virtual int ProcessBlock( + byte[] input, + int inOff, + byte[] output, + int outOff) + { + if (workingKey == null) + throw new InvalidOperationException("Gost28147 engine not initialised"); + + Check.DataLength(input, inOff, BlockSize, "input buffer too short"); + Check.OutputLength(output, outOff, BlockSize, "output buffer too short"); + + Gost28147Func(workingKey, input, inOff, output, outOff); + + return BlockSize; + } + + public virtual void Reset() + { + } + + private int[] generateWorkingKey( + bool forEncryption, + byte[] userKey) + { + this.forEncryption = forEncryption; + + if (userKey.Length != 32) + { + throw new ArgumentException("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 + int omLeft = om << 11; + int omRight = (int)(((uint) om) >> (32 - 11)); // Note: Casts required to get unsigned bit rotation + + return omLeft | omRight; + } + + private void Gost28147Func( + int[] workingKey, + byte[] inBytes, + int inOff, + byte[] outBytes, + int outOff) + { + int N1, N2, tmp; //tmp -> for saving N1 + N1 = bytesToint(inBytes, inOff); + N2 = bytesToint(inBytes, inOff + 4); + + if (this.forEncryption) + { + for(int k = 0; k < 3; k++) // 1-24 steps + { + for(int j = 0; j < 8; j++) + { + tmp = N1; + int step = Gost28147_mainStep(N1, workingKey[j]); + N1 = N2 ^ step; // 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, outBytes, outOff); + intTobytes(N2, outBytes, outOff + 4); + } + + //array of bytes to type int + private static int bytesToint( + byte[] inBytes, + int inOff) + { + return (int)((inBytes[inOff + 3] << 24) & 0xff000000) + ((inBytes[inOff + 2] << 16) & 0xff0000) + + ((inBytes[inOff + 1] << 8) & 0xff00) + (inBytes[inOff] & 0xff); + } + + //int to array of bytes + private static void intTobytes( + int num, + byte[] outBytes, + int outOff) + { + outBytes[outOff + 3] = (byte)(num >> 24); + outBytes[outOff + 2] = (byte)(num >> 16); + outBytes[outOff + 1] = (byte)(num >> 8); + outBytes[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[] sBox = (byte[])sBoxes[Platform.ToUpperInvariant(sBoxName)]; + + if (sBox == null) + { + throw new ArgumentException("Unknown S-Box - possible types: " + + "\"Default\", \"E-Test\", \"E-A\", \"E-B\", \"E-C\", \"E-D\", \"D-Test\", \"D-A\"."); + } + + return Arrays.Clone(sBox); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/engines/HC128Engine.cs b/bc-sharp-crypto/src/crypto/engines/HC128Engine.cs new file mode 100644 index 0000000..b83eb70 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/HC128Engine.cs @@ -0,0 +1,235 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Utilities; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Engines +{ + /** + * 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 + : IStreamCipher + { + private uint[] p = new uint[512]; + private uint[] q = new uint[512]; + private uint cnt = 0; + + private static uint F1(uint x) + { + return RotateRight(x, 7) ^ RotateRight(x, 18) ^ (x >> 3); + } + + private static uint F2(uint x) + { + return RotateRight(x, 17) ^ RotateRight(x, 19) ^ (x >> 10); + } + + private uint G1(uint x, uint y, uint z) + { + return (RotateRight(x, 10) ^ RotateRight(z, 23)) + RotateRight(y, 8); + } + + private uint G2(uint x, uint y, uint z) + { + return (RotateLeft(x, 10) ^ RotateLeft(z, 23)) + RotateLeft(y, 8); + } + + private static uint RotateLeft(uint x, int bits) + { + return (x << bits) | (x >> -bits); + } + + private static uint RotateRight(uint x, int bits) + { + return (x >> bits) | (x << -bits); + } + + private uint H1(uint x) + { + return q[x & 0xFF] + q[((x >> 16) & 0xFF) + 256]; + } + + private uint H2(uint x) + { + return p[x & 0xFF] + p[((x >> 16) & 0xFF) + 256]; + } + + private static uint Mod1024(uint x) + { + return x & 0x3FF; + } + + private static uint Mod512(uint x) + { + return x & 0x1FF; + } + + private static uint Dim(uint x, uint y) + { + return Mod512(x - y); + } + + private uint Step() + { + uint j = Mod512(cnt); + uint 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 bool initialised; + + private void Init() + { + if (key.Length != 16) + throw new ArgumentException("The key must be 128 bits long"); + + idx = 0; + cnt = 0; + + uint[] w = new uint[1280]; + + for (int i = 0; i < 16; i++) + { + w[i >> 2] |= ((uint)key[i] << (8 * (i & 0x3))); + } + Array.Copy(w, 0, w, 4, 4); + + for (int i = 0; i < iv.Length && i < 16; i++) + { + w[(i >> 2) + 8] |= ((uint)iv[i] << (8 * (i & 0x3))); + } + Array.Copy(w, 8, w, 12, 4); + + for (uint i = 16; i < 1280; i++) + { + w[i] = F2(w[i - 2]) + w[i - 7] + F1(w[i - 15]) + w[i - 16] + i; + } + + Array.Copy(w, 256, p, 0, 512); + Array.Copy(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 virtual string AlgorithmName + { + get { 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 ArgumentException if the params argument is + * inappropriate (ie. the key is not 128 bit long). + */ + public virtual void Init( + bool forEncryption, + ICipherParameters parameters) + { + ICipherParameters keyParam = parameters; + + if (parameters is ParametersWithIV) + { + iv = ((ParametersWithIV)parameters).GetIV(); + keyParam = ((ParametersWithIV)parameters).Parameters; + } + else + { + iv = new byte[0]; + } + + if (keyParam is KeyParameter) + { + key = ((KeyParameter)keyParam).GetKey(); + Init(); + } + else + { + throw new ArgumentException( + "Invalid parameter passed to HC128 init - " + Platform.GetTypeName(parameters), + "parameters"); + } + + initialised = true; + } + + private byte[] buf = new byte[4]; + private int idx = 0; + + private byte GetByte() + { + if (idx == 0) + { + Pack.UInt32_To_LE(Step(), buf); + } + byte ret = buf[idx]; + idx = idx + 1 & 0x3; + return ret; + } + + public virtual void ProcessBytes( + byte[] input, + int inOff, + int len, + byte[] output, + int outOff) + { + if (!initialised) + throw new InvalidOperationException(AlgorithmName + " not initialised"); + + Check.DataLength(input, inOff, len, "input buffer too short"); + Check.OutputLength(output, outOff, len, "output buffer too short"); + + for (int i = 0; i < len; i++) + { + output[outOff + i] = (byte)(input[inOff + i] ^ GetByte()); + } + } + + public virtual void Reset() + { + Init(); + } + + public virtual byte ReturnByte(byte input) + { + return (byte)(input ^ GetByte()); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/engines/HC256Engine.cs b/bc-sharp-crypto/src/crypto/engines/HC256Engine.cs new file mode 100644 index 0000000..d8d83a6 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/HC256Engine.cs @@ -0,0 +1,224 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Utilities; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Engines +{ + /** + * 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 + : IStreamCipher + { + private uint[] p = new uint[1024]; + private uint[] q = new uint[1024]; + private uint cnt = 0; + + private uint Step() + { + uint j = cnt & 0x3FF; + uint ret; + if (cnt < 1024) + { + uint x = p[(j - 3 & 0x3FF)]; + uint 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 + { + uint x = q[(j - 3 & 0x3FF)]; + uint 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 bool initialised; + + private void Init() + { + if (key.Length != 32 && key.Length != 16) + throw new ArgumentException("The key must be 128/256 bits long"); + + if (iv.Length < 16) + throw new ArgumentException("The IV must be at least 128 bits long"); + + if (key.Length != 32) + { + byte[] k = new byte[32]; + + Array.Copy(key, 0, k, 0, key.Length); + Array.Copy(key, 0, k, 16, key.Length); + + key = k; + } + + if (iv.Length < 32) + { + byte[] newIV = new byte[32]; + + Array.Copy(iv, 0, newIV, 0, iv.Length); + Array.Copy(iv, 0, newIV, iv.Length, newIV.Length - iv.Length); + + iv = newIV; + } + + idx = 0; + cnt = 0; + + uint[] w = new uint[2560]; + + for (int i = 0; i < 32; i++) + { + w[i >> 2] |= ((uint)key[i] << (8 * (i & 0x3))); + } + + for (int i = 0; i < 32; i++) + { + w[(i >> 2) + 8] |= ((uint)iv[i] << (8 * (i & 0x3))); + } + + for (uint i = 16; i < 2560; i++) + { + uint x = w[i - 2]; + uint 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; + } + + Array.Copy(w, 512, p, 0, 1024); + Array.Copy(w, 1536, q, 0, 1024); + + for (int i = 0; i < 4096; i++) + { + Step(); + } + + cnt = 0; + } + + public virtual string AlgorithmName + { + get { 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 ArgumentException if the params argument is + * inappropriate (ie. the key is not 256 bit long). + */ + public virtual void Init( + bool forEncryption, + ICipherParameters parameters) + { + ICipherParameters keyParam = parameters; + + if (parameters is ParametersWithIV) + { + iv = ((ParametersWithIV)parameters).GetIV(); + keyParam = ((ParametersWithIV)parameters).Parameters; + } + else + { + iv = new byte[0]; + } + + if (keyParam is KeyParameter) + { + key = ((KeyParameter)keyParam).GetKey(); + Init(); + } + else + { + throw new ArgumentException( + "Invalid parameter passed to HC256 init - " + Platform.GetTypeName(parameters), + "parameters"); + } + + initialised = true; + } + + private byte[] buf = new byte[4]; + private int idx = 0; + + private byte GetByte() + { + if (idx == 0) + { + Pack.UInt32_To_LE(Step(), buf); + } + byte ret = buf[idx]; + idx = idx + 1 & 0x3; + return ret; + } + + public virtual void ProcessBytes( + byte[] input, + int inOff, + int len, + byte[] output, + int outOff) + { + if (!initialised) + throw new InvalidOperationException(AlgorithmName + " not initialised"); + + Check.DataLength(input, inOff, len, "input buffer too short"); + Check.OutputLength(output, outOff, len, "output buffer too short"); + + for (int i = 0; i < len; i++) + { + output[outOff + i] = (byte)(input[inOff + i] ^ GetByte()); + } + } + + public virtual void Reset() + { + Init(); + } + + public virtual byte ReturnByte(byte input) + { + return (byte)(input ^ GetByte()); + } + + private static uint RotateRight(uint x, int bits) + { + return (x >> bits) | (x << -bits); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/engines/ISAACEngine.cs b/bc-sharp-crypto/src/crypto/engines/ISAACEngine.cs new file mode 100644 index 0000000..b94ee6e --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/ISAACEngine.cs @@ -0,0 +1,212 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Utilities; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Engines +{ + /** + * Implementation of Bob Jenkin's ISAAC (Indirection Shift Accumulate Add and Count). + * see: http://www.burtleburtle.net/bob/rand/isaacafa.html + */ + public class IsaacEngine + : IStreamCipher + { + // Constants + private static readonly int sizeL = 8, + stateArraySize = sizeL<<5; // 256 + + // Cipher's internal state + private uint[] engineState = null, // mm + results = null; // randrsl + private uint 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 bool 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 ArgumentException if the params argument is + * inappropriate. + */ + public virtual void Init( + bool forEncryption, + ICipherParameters parameters) + { + if (!(parameters is KeyParameter)) + throw new ArgumentException( + "invalid parameter passed to ISAAC Init - " + Platform.GetTypeName(parameters), + "parameters"); + + /* + * ISAAC encryption and decryption is completely + * symmetrical, so the 'forEncryption' is + * irrelevant. + */ + KeyParameter p = (KeyParameter) parameters; + setKey(p.GetKey()); + } + + public virtual byte ReturnByte( + byte input) + { + if (index == 0) + { + isaac(); + keyStream = Pack.UInt32_To_BE(results); + } + + byte output = (byte)(keyStream[index]^input); + index = (index + 1) & 1023; + + return output; + } + + public virtual void ProcessBytes( + byte[] input, + int inOff, + int len, + byte[] output, + int outOff) + { + if (!initialised) + throw new InvalidOperationException(AlgorithmName + " not initialised"); + + Check.DataLength(input, inOff, len, "input buffer too short"); + Check.OutputLength(output, outOff, len, "output buffer too short"); + + for (int i = 0; i < len; i++) + { + if (index == 0) + { + isaac(); + keyStream = Pack.UInt32_To_BE(results); + } + output[i+outOff] = (byte)(keyStream[index]^input[i+inOff]); + index = (index + 1) & 1023; + } + } + + public virtual string AlgorithmName + { + get { return "ISAAC"; } + } + + public virtual void Reset() + { + setKey(workingKey); + } + + // Private implementation + private void setKey( + byte[] keyBytes) + { + workingKey = keyBytes; + + if (engineState == null) + { + engineState = new uint[stateArraySize]; + } + + if (results == null) + { + results = new uint[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)]; + Array.Copy(keyBytes, 0, t, 0, keyBytes.Length); + for (i = 0; i < t.Length; i+=4) + { + results[i >> 2] = Pack.LE_To_UInt32(t, i); + } + + // It has begun? + uint[] abcdefgh = new uint[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() + { + uint x, y; + + b += ++c; + for (int 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[(int)((uint)x >> 2) & 0xFF] + a + b; + results[i] = b = engineState[(int)((uint)y >> 10) & 0xFF] + x; + } + } + + private void mix(uint[] 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]; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/engines/IdeaEngine.cs b/bc-sharp-crypto/src/crypto/engines/IdeaEngine.cs new file mode 100644 index 0000000..18a151c --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/IdeaEngine.cs @@ -0,0 +1,332 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Engines +{ + /** + * 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 assembly. It is not included in the default distributions. + *

+ */ + public class IdeaEngine + : IBlockCipher + { + private const int BLOCK_SIZE = 8; + private int[] workingKey; + /** + * standard constructor. + */ + public IdeaEngine() + { + } + /** + * initialise an IDEA cipher. + * + * @param forEncryption whether or not we are for encryption. + * @param parameters the parameters required to set up the cipher. + * @exception ArgumentException if the parameters argument is + * inappropriate. + */ + public virtual void Init( + bool forEncryption, + ICipherParameters parameters) + { + if (!(parameters is KeyParameter)) + throw new ArgumentException("invalid parameter passed to IDEA init - " + Platform.GetTypeName(parameters)); + + workingKey = GenerateWorkingKey(forEncryption, + ((KeyParameter)parameters).GetKey()); + } + + public virtual string AlgorithmName + { + get { return "IDEA"; } + } + + public virtual bool IsPartialBlockOkay + { + get { return false; } + } + + public virtual int GetBlockSize() + { + return BLOCK_SIZE; + } + + public virtual int ProcessBlock( + byte[] input, + int inOff, + byte[] output, + int outOff) + { + if (workingKey == null) + throw new InvalidOperationException("IDEA engine not initialised"); + + Check.DataLength(input, inOff, BLOCK_SIZE, "input buffer too short"); + Check.OutputLength(output, outOff, BLOCK_SIZE, "output buffer too short"); + + IdeaFunc(workingKey, input, inOff, output, outOff); + return BLOCK_SIZE; + } + public virtual void Reset() + { + } + private static readonly int MASK = 0xffff; + private static readonly int BASE = 0x10001; + private int BytesToWord( + byte[] input, + int inOff) + { + return ((input[inOff] << 8) & 0xff00) + (input[inOff + 1] & 0xff); + } + private void WordToBytes( + int word, + byte[] outBytes, + int outOff) + { + outBytes[outOff] = (byte)((uint) word >> 8); + outBytes[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 = (int) ((uint) p >> 16); + x = y - x + ((y < x) ? 1 : 0); + } + return x & MASK; + } + private void IdeaFunc( + int[] workingKey, + byte[] input, + int inOff, + byte[] outBytes, + int outOff) + { + int x0, x1, x2, x3, t0, t1; + int keyOff = 0; + x0 = BytesToWord(input, inOff); + x1 = BytesToWord(input, inOff + 2); + x2 = BytesToWord(input, inOff + 4); + x3 = BytesToWord(input, 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++]), outBytes, outOff); + WordToBytes(x2 + workingKey[keyOff++], outBytes, outOff + 2); /* NB: Order */ + WordToBytes(x1 + workingKey[keyOff++], outBytes, outOff + 4); + WordToBytes(Mul(x3, workingKey[keyOff]), outBytes, 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]; + Array.Copy(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( + bool forEncryption, + byte[] userKey) + { + if (forEncryption) + { + return ExpandKey(userKey); + } + else + { + return InvertKey(ExpandKey(userKey)); + } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/engines/IesEngine.cs b/bc-sharp-crypto/src/crypto/engines/IesEngine.cs new file mode 100644 index 0000000..307cc7a --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/IesEngine.cs @@ -0,0 +1,243 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Engines +{ + /** + * support class for constructing intergrated encryption ciphers + * for doing basic message exchanges on top of key agreement ciphers + */ + public class IesEngine + { + private readonly IBasicAgreement agree; + private readonly IDerivationFunction kdf; + private readonly IMac mac; + private readonly BufferedBlockCipher cipher; + private readonly byte[] macBuf; + + private bool forEncryption; + private ICipherParameters privParam, pubParam; + private 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( + IBasicAgreement agree, + IDerivationFunction kdf, + IMac 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( + IBasicAgreement agree, + IDerivationFunction kdf, + IMac 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 virtual void Init( + bool forEncryption, + ICipherParameters privParameters, + ICipherParameters pubParameters, + ICipherParameters iesParameters) + { + this.forEncryption = forEncryption; + this.privParam = privParameters; + this.pubParam = pubParameters; + this.param = (IesParameters)iesParameters; + } + + private byte[] DecryptBlock( + byte[] in_enc, + int inOff, + int inLen, + byte[] z) + { + byte[] M = null; + KeyParameter macKey = null; + KdfParameters kParam = new KdfParameters(z, param.GetDerivationV()); + int macKeySize = param.MacKeySize; + + kdf.Init(kParam); + + // Ensure that the length of the input is greater than the MAC in bytes + if (inLen < mac.GetMacSize()) + throw new InvalidCipherTextException("Length of input must be greater than the MAC"); + + inLen -= mac.GetMacSize(); + + if (cipher == null) // stream mode + { + byte[] Buffer = GenerateKdfBytes(kParam, inLen + (macKeySize / 8)); + + M = new byte[inLen]; + + for (int i = 0; i != inLen; i++) + { + M[i] = (byte)(in_enc[inOff + i] ^ Buffer[i]); + } + + macKey = new KeyParameter(Buffer, inLen, (macKeySize / 8)); + } + else + { + int cipherKeySize = ((IesWithCipherParameters)param).CipherKeySize; + byte[] Buffer = GenerateKdfBytes(kParam, (cipherKeySize / 8) + (macKeySize / 8)); + + cipher.Init(false, new KeyParameter(Buffer, 0, (cipherKeySize / 8))); + + M = cipher.DoFinal(in_enc, inOff, inLen); + + macKey = new KeyParameter(Buffer, (cipherKeySize / 8), (macKeySize / 8)); + } + + byte[] macIV = param.GetEncodingV(); + + mac.Init(macKey); + mac.BlockUpdate(in_enc, inOff, inLen); + mac.BlockUpdate(macIV, 0, macIV.Length); + mac.DoFinal(macBuf, 0); + + inOff += inLen; + + byte[] T1 = Arrays.CopyOfRange(in_enc, inOff, inOff + macBuf.Length); + + if (!Arrays.ConstantTimeAreEqual(T1, macBuf)) + throw (new InvalidCipherTextException("Invalid MAC.")); + + return M; + } + + private byte[] EncryptBlock( + byte[] input, + int inOff, + int inLen, + byte[] z) + { + byte[] C = null; + KeyParameter macKey = null; + KdfParameters kParam = new KdfParameters(z, param.GetDerivationV()); + int c_text_length = 0; + int macKeySize = param.MacKeySize; + + if (cipher == null) // stream mode + { + byte[] Buffer = 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)(input[inOff + i] ^ Buffer[i]); + } + + macKey = new KeyParameter(Buffer, inLen, (macKeySize / 8)); + } + else + { + int cipherKeySize = ((IesWithCipherParameters)param).CipherKeySize; + byte[] Buffer = GenerateKdfBytes(kParam, (cipherKeySize / 8) + (macKeySize / 8)); + + cipher.Init(true, new KeyParameter(Buffer, 0, (cipherKeySize / 8))); + + c_text_length = cipher.GetOutputSize(inLen); + byte[] tmp = new byte[c_text_length]; + + int len = cipher.ProcessBytes(input, inOff, inLen, tmp, 0); + len += cipher.DoFinal(tmp, len); + + C = new byte[len + mac.GetMacSize()]; + c_text_length = len; + + Array.Copy(tmp, 0, C, 0, len); + + macKey = new KeyParameter(Buffer, (cipherKeySize / 8), (macKeySize / 8)); + } + + byte[] macIV = param.GetEncodingV(); + + mac.Init(macKey); + mac.BlockUpdate(C, 0, c_text_length); + mac.BlockUpdate(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 virtual byte[] ProcessBlock( + byte[] input, + int inOff, + int inLen) + { + agree.Init(privParam); + + BigInteger z = agree.CalculateAgreement(pubParam); + + byte[] zBytes = BigIntegers.AsUnsignedByteArray(agree.GetFieldSize(), z); + + try + { + return forEncryption + ? EncryptBlock(input, inOff, inLen, zBytes) + : DecryptBlock(input, inOff, inLen, zBytes); + } + finally + { + Array.Clear(zBytes, 0, zBytes.Length); + } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/engines/NaccacheSternEngine.cs b/bc-sharp-crypto/src/crypto/engines/NaccacheSternEngine.cs new file mode 100644 index 0000000..64665c1 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/NaccacheSternEngine.cs @@ -0,0 +1,358 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Engines +{ + /** + * NaccacheStern Engine. For details on this cipher, please see + * http://www.gemplus.com/smart/rd/publications/pdf/NS98pkcs.pdf + */ + public class NaccacheSternEngine + : IAsymmetricBlockCipher + { + private bool forEncryption; + + private NaccacheSternKeyParameters key; + + private IList[] lookup = null; + + public string AlgorithmName + { + get { return "NaccacheStern"; } + } + + /** + * Initializes this algorithm. Must be called before all other Functions. + * + * @see org.bouncycastle.crypto.AsymmetricBlockCipher#init(bool, + * org.bouncycastle.crypto.CipherParameters) + */ + public virtual void Init( + bool forEncryption, + ICipherParameters parameters) + { + this.forEncryption = forEncryption; + + if (parameters is ParametersWithRandom) + { + parameters = ((ParametersWithRandom) parameters).Parameters; + } + + key = (NaccacheSternKeyParameters)parameters; + + // construct lookup table for faster decryption if necessary + if (!this.forEncryption) + { + NaccacheSternPrivateKeyParameters priv = (NaccacheSternPrivateKeyParameters)key; + IList primes = priv.SmallPrimesList; + lookup = new IList[primes.Count]; + for (int i = 0; i < primes.Count; i++) + { + BigInteger actualPrime = (BigInteger) primes[i]; + int actualPrimeValue = actualPrime.IntValue; + + lookup[i] = Platform.CreateArrayList(actualPrimeValue); + lookup[i].Add(BigInteger.One); + + BigInteger accJ = BigInteger.Zero; + + for (int j = 1; j < actualPrimeValue; j++) + { +// BigInteger bigJ = BigInteger.ValueOf(j); +// accJ = priv.PhiN.Multiply(bigJ); + accJ = accJ.Add(priv.PhiN); + BigInteger comp = accJ.Divide(actualPrime); + lookup[i].Add(priv.G.ModPow(comp, priv.Modulus)); + } + } + } + } + + [Obsolete("Remove: no longer used")] + public virtual bool Debug + { + set {} + } + + /** + * Returns the input block size of this algorithm. + * + * @see org.bouncycastle.crypto.AsymmetricBlockCipher#GetInputBlockSize() + */ + public virtual int GetInputBlockSize() + { + if (forEncryption) + { + // We can only encrypt values up to lowerSigmaBound + return (key.LowerSigmaBound + 7) / 8 - 1; + } + else + { + // We pad to modulus-size bytes for easier decryption. +// return key.Modulus.ToByteArray().Length; + return key.Modulus.BitLength / 8 + 1; + } + } + + /** + * Returns the output block size of this algorithm. + * + * @see org.bouncycastle.crypto.AsymmetricBlockCipher#GetOutputBlockSize() + */ + public virtual int GetOutputBlockSize() + { + if (forEncryption) + { + // encrypted Data is always padded up to modulus size +// return key.Modulus.ToByteArray().Length; + return key.Modulus.BitLength / 8 + 1; + } + else + { + // decrypted Data has upper limit lowerSigmaBound + return (key.LowerSigmaBound + 7) / 8 - 1; + } + } + + /** + * Process a single Block using the Naccache-Stern algorithm. + * + * @see org.bouncycastle.crypto.AsymmetricBlockCipher#ProcessBlock(byte[], + * int, int) + */ + public virtual byte[] ProcessBlock( + byte[] inBytes, + int inOff, + int length) + { + if (key == null) + throw new InvalidOperationException("NaccacheStern engine not initialised"); + if (length > (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 (length < GetInputBlockSize()) + { + throw new InvalidCipherTextException("BlockLength does not match modulus for Naccache-Stern cipher.\n"); + } + } + + // transform input into BigInteger + BigInteger input = new BigInteger(1, inBytes, inOff, length); + + byte[] output; + if (forEncryption) + { + output = Encrypt(input); + } + else + { + IList plain = Platform.CreateArrayList(); + NaccacheSternPrivateKeyParameters priv = (NaccacheSternPrivateKeyParameters)key; + IList primes = priv.SmallPrimesList; + // Get Chinese Remainders of CipherText + for (int i = 0; i < primes.Count; i++) + { + BigInteger exp = input.ModPow(priv.PhiN.Divide((BigInteger)primes[i]), priv.Modulus); + IList al = lookup[i]; + if (lookup[i].Count != ((BigInteger)primes[i]).IntValue) + { + throw new InvalidCipherTextException("Error in lookup Array for " + + ((BigInteger)primes[i]).IntValue + + ": Size mismatch. Expected ArrayList with length " + + ((BigInteger)primes[i]).IntValue + " but found ArrayList of length " + + lookup[i].Count); + } + int lookedup = al.IndexOf(exp); + + if (lookedup == -1) + { + throw new InvalidCipherTextException("Lookup failed"); + } + plain.Add(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 ((key.G.ModPow(test, key.Modulus)).Equals(input)) { + // output = test.ToByteArray(); + // } else { + // 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 virtual 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.Modulus.ToByteArray(); +// Array.Clear(output, 0, output.Length); + byte[] output = new byte[key.Modulus.BitLength / 8 + 1]; + + byte[] tmp = key.G.ModPow(plain, key.Modulus).ToByteArray(); + Array.Copy(tmp, 0, output, output.Length - tmp.Length, tmp.Length); + 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 virtual byte[] AddCryptedBlocks( + byte[] block1, + byte[] block2) + { + // 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.Modulus); + + //byte[] output = key.Modulus.ToByteArray(); + //Array.Clear(output, 0, output.Length); + byte[] output = new byte[key.Modulus.BitLength / 8 + 1]; + + byte[] m1m2CryptBytes = m1m2Crypt.ToByteArray(); + Array.Copy(m1m2CryptBytes, 0, output, + output.Length - m1m2CryptBytes.Length, m1m2CryptBytes.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 virtual byte[] ProcessData( + byte[] data) + { + if (data.Length > GetInputBlockSize()) + { + int inBlocksize = GetInputBlockSize(); + int outBlocksize = GetOutputBlockSize(); + 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 (tmp != null) + { + tmp.CopyTo(retval, retpos); + retpos += tmp.Length; + } + else + { + throw new InvalidCipherTextException("cipher returned null"); + } + } + byte[] ret = new byte[retpos]; + Array.Copy(retval, 0, ret, 0, retpos); + return ret; + } + else + { + 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(IList congruences, IList primes) + { + BigInteger retval = BigInteger.Zero; + BigInteger all = BigInteger.One; + for (int i = 0; i < primes.Count; i++) + { + all = all.Multiply((BigInteger)primes[i]); + } + for (int i = 0; i < primes.Count; i++) + { + BigInteger a = (BigInteger)primes[i]; + BigInteger b = all.Divide(a); + BigInteger b2 = b.ModInverse(a); + BigInteger tmp = b.Multiply(b2); + tmp = tmp.Multiply((BigInteger)congruences[i]); + retval = retval.Add(tmp); + } + + return retval.Mod(all); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/engines/NoekeonEngine.cs b/bc-sharp-crypto/src/crypto/engines/NoekeonEngine.cs new file mode 100644 index 0000000..f64be50 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/NoekeonEngine.cs @@ -0,0 +1,241 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Utilities; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Engines +{ + /** + * A Noekeon engine, using direct-key mode. + */ + public class NoekeonEngine + : IBlockCipher + { + private const int GenericSize = 16; // Block and key size, as well as the amount of rounds. + + private static readonly uint[] nullVector = + { + 0x00, 0x00, 0x00, 0x00 // Used in decryption + }; + + private static readonly uint[] roundConstants = + { + 0x80, 0x1b, 0x36, 0x6c, + 0xd8, 0xab, 0x4d, 0x9a, + 0x2f, 0x5e, 0xbc, 0x63, + 0xc6, 0x97, 0x35, 0x6a, + 0xd4 + }; + + private uint[] state = new uint[4], // a + subKeys = new uint[4], // k + decryptKeys = new uint[4]; + + private bool _initialised, _forEncryption; + + /** + * Create an instance of the Noekeon encryption algorithm + * and set some defaults + */ + public NoekeonEngine() + { + _initialised = false; + } + + public virtual string AlgorithmName + { + get { return "Noekeon"; } + } + + public virtual bool IsPartialBlockOkay + { + get { return false; } + } + + public virtual 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 ArgumentException if the params argument is + * inappropriate. + */ + public virtual void Init( + bool forEncryption, + ICipherParameters parameters) + { + if (!(parameters is KeyParameter)) + throw new ArgumentException("Invalid parameters passed to Noekeon init - " + + Platform.GetTypeName(parameters), "parameters"); + + _forEncryption = forEncryption; + _initialised = true; + + KeyParameter p = (KeyParameter) parameters; + + setKey(p.GetKey()); + } + + public virtual int ProcessBlock( + byte[] input, + int inOff, + byte[] output, + int outOff) + { + if (!_initialised) + throw new InvalidOperationException(AlgorithmName + " not initialised"); + + Check.DataLength(input, inOff, GenericSize, "input buffer too short"); + Check.OutputLength(output, outOff, GenericSize, "output buffer too short"); + + return _forEncryption + ? encryptBlock(input, inOff, output, outOff) + : decryptBlock(input, inOff, output, outOff); + } + + public virtual void Reset() + { + // TODO This should do something in case the encryption is aborted + } + + /** + * Re-key the cipher. + * + * @param key the key to be used + */ + private void setKey(byte[] key) + { + subKeys[0] = Pack.BE_To_UInt32(key, 0); + subKeys[1] = Pack.BE_To_UInt32(key, 4); + subKeys[2] = Pack.BE_To_UInt32(key, 8); + subKeys[3] = Pack.BE_To_UInt32(key, 12); + } + + private int encryptBlock( + byte[] input, + int inOff, + byte[] output, + int outOff) + { + state[0] = Pack.BE_To_UInt32(input, inOff); + state[1] = Pack.BE_To_UInt32(input, inOff+4); + state[2] = Pack.BE_To_UInt32(input, inOff+8); + state[3] = Pack.BE_To_UInt32(input, 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); + + Pack.UInt32_To_BE(state[0], output, outOff); + Pack.UInt32_To_BE(state[1], output, outOff+4); + Pack.UInt32_To_BE(state[2], output, outOff+8); + Pack.UInt32_To_BE(state[3], output, outOff+12); + + return GenericSize; + } + + private int decryptBlock( + byte[] input, + int inOff, + byte[] output, + int outOff) + { + state[0] = Pack.BE_To_UInt32(input, inOff); + state[1] = Pack.BE_To_UInt32(input, inOff+4); + state[2] = Pack.BE_To_UInt32(input, inOff+8); + state[3] = Pack.BE_To_UInt32(input, inOff+12); + + Array.Copy(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]; + + Pack.UInt32_To_BE(state[0], output, outOff); + Pack.UInt32_To_BE(state[1], output, outOff+4); + Pack.UInt32_To_BE(state[2], output, outOff+8); + Pack.UInt32_To_BE(state[3], output, outOff+12); + + return GenericSize; + } + + private void gamma(uint[] a) + { + a[1] ^= ~a[3] & ~a[2]; + a[0] ^= a[2] & a[1]; + + uint 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(uint[] a, uint[] k) + { + uint 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(uint[] a) + { + a[1] = rotl(a[1], 1); + a[2] = rotl(a[2], 5); + a[3] = rotl(a[3], 2); + } + + private void pi2(uint[] a) + { + a[1] = rotl(a[1], 31); + a[2] = rotl(a[2], 27); + a[3] = rotl(a[3], 30); + } + + // Helpers + + private uint rotl(uint x, int y) + { + return (x << y) | (x >> (32-y)); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/engines/NullEngine.cs b/bc-sharp-crypto/src/crypto/engines/NullEngine.cs new file mode 100644 index 0000000..f883b7c --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/NullEngine.cs @@ -0,0 +1,69 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; + +namespace Org.BouncyCastle.Crypto.Engines +{ + /** + * The no-op engine that just copies bytes through, irrespective of whether encrypting and decrypting. + * Provided for the sake of completeness. + */ + public class NullEngine + : IBlockCipher + { + private bool initialised; + private const int BlockSize = 1; + + public NullEngine() + { + } + + public virtual void Init( + bool forEncryption, + ICipherParameters parameters) + { + // we don't mind any parameters that may come in + initialised = true; + } + + public virtual string AlgorithmName + { + get { return "Null"; } + } + + public virtual bool IsPartialBlockOkay + { + get { return true; } + } + + public virtual int GetBlockSize() + { + return BlockSize; + } + + public virtual int ProcessBlock( + byte[] input, + int inOff, + byte[] output, + int outOff) + { + if (!initialised) + throw new InvalidOperationException("Null engine not initialised"); + + Check.DataLength(input, inOff, BlockSize, "input buffer too short"); + Check.OutputLength(output, outOff, BlockSize, "output buffer too short"); + + for (int i = 0; i < BlockSize; ++i) + { + output[outOff + i] = input[inOff + i]; + } + + return BlockSize; + } + + public virtual void Reset() + { + // nothing needs to be done + } + } +} diff --git a/bc-sharp-crypto/src/crypto/engines/RC2Engine.cs b/bc-sharp-crypto/src/crypto/engines/RC2Engine.cs new file mode 100644 index 0000000..4aca189 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/RC2Engine.cs @@ -0,0 +1,311 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Engines +{ + /** + * an implementation of RC2 as described in RFC 2268 + * "A Description of the RC2(r) Encryption Algorithm" R. Rivest. + */ + public class RC2Engine + : IBlockCipher + { + // + // the values we use for key expansion (based on the digits of PI) + // + private static readonly 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 const int BLOCK_SIZE = 8; + + private int[] workingKey; + private bool 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 forEncryption whether or not we are for encryption. + * @param parameters the parameters required to set up the cipher. + * @exception ArgumentException if the parameters argument is + * inappropriate. + */ + public virtual void Init( + bool forEncryption, + ICipherParameters parameters) + { + this.encrypting = forEncryption; + + if (parameters is RC2Parameters) + { + RC2Parameters param = (RC2Parameters) parameters; + + workingKey = GenerateWorkingKey(param.GetKey(), param.EffectiveKeyBits); + } + else if (parameters is KeyParameter) + { + KeyParameter param = (KeyParameter) parameters; + byte[] key = param.GetKey(); + + workingKey = GenerateWorkingKey(key, key.Length * 8); + } + else + { + throw new ArgumentException("invalid parameter passed to RC2 init - " + Platform.GetTypeName(parameters)); + } + } + + public virtual void Reset() + { + } + + public virtual string AlgorithmName + { + get { return "RC2"; } + } + + public virtual bool IsPartialBlockOkay + { + get { return false; } + } + + public virtual int GetBlockSize() + { + return BLOCK_SIZE; + } + + public virtual int ProcessBlock( + byte[] input, + int inOff, + byte[] output, + int outOff) + { + if (workingKey == null) + throw new InvalidOperationException("RC2 engine not initialised"); + + Check.DataLength(input, inOff, BLOCK_SIZE, "input buffer too short"); + Check.OutputLength(output, outOff, BLOCK_SIZE, "output buffer too short"); + + if (encrypting) + { + EncryptBlock(input, inOff, output, outOff); + } + else + { + DecryptBlock(input, inOff, output, 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[] input, + int inOff, + byte[] outBytes, + int outOff) + { + int x76, x54, x32, x10; + + x76 = ((input[inOff + 7] & 0xff) << 8) + (input[inOff + 6] & 0xff); + x54 = ((input[inOff + 5] & 0xff) << 8) + (input[inOff + 4] & 0xff); + x32 = ((input[inOff + 3] & 0xff) << 8) + (input[inOff + 2] & 0xff); + x10 = ((input[inOff + 1] & 0xff) << 8) + (input[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); + } + + outBytes[outOff + 0] = (byte)x10; + outBytes[outOff + 1] = (byte)(x10 >> 8); + outBytes[outOff + 2] = (byte)x32; + outBytes[outOff + 3] = (byte)(x32 >> 8); + outBytes[outOff + 4] = (byte)x54; + outBytes[outOff + 5] = (byte)(x54 >> 8); + outBytes[outOff + 6] = (byte)x76; + outBytes[outOff + 7] = (byte)(x76 >> 8); + } + + private void DecryptBlock( + byte[] input, + int inOff, + byte[] outBytes, + int outOff) + { + int x76, x54, x32, x10; + + x76 = ((input[inOff + 7] & 0xff) << 8) + (input[inOff + 6] & 0xff); + x54 = ((input[inOff + 5] & 0xff) << 8) + (input[inOff + 4] & 0xff); + x32 = ((input[inOff + 3] & 0xff) << 8) + (input[inOff + 2] & 0xff); + x10 = ((input[inOff + 1] & 0xff) << 8) + (input[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 ]); + } + + outBytes[outOff + 0] = (byte)x10; + outBytes[outOff + 1] = (byte)(x10 >> 8); + outBytes[outOff + 2] = (byte)x32; + outBytes[outOff + 3] = (byte)(x32 >> 8); + outBytes[outOff + 4] = (byte)x54; + outBytes[outOff + 5] = (byte)(x54 >> 8); + outBytes[outOff + 6] = (byte)x76; + outBytes[outOff + 7] = (byte)(x76 >> 8); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/engines/RC2WrapEngine.cs b/bc-sharp-crypto/src/crypto/engines/RC2WrapEngine.cs new file mode 100644 index 0000000..5742aa8 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/RC2WrapEngine.cs @@ -0,0 +1,370 @@ +using System; + +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Modes; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Engines +{ + /** + * Wrap keys according to RFC 3217 - RC2 mechanism + */ + public class RC2WrapEngine + : IWrapper + { + /** Field engine */ + private CbcBlockCipher engine; + + /** Field param */ + private ICipherParameters parameters; + + /** Field paramPlusIV */ + private ParametersWithIV paramPlusIV; + + /** Field iv */ + private byte[] iv; + + /** Field forWrapping */ + private bool forWrapping; + + private SecureRandom sr; + + /** Field IV2 */ + private static readonly byte[] IV2 = + { + (byte) 0x4a, (byte) 0xdd, (byte) 0xa2, + (byte) 0x2c, (byte) 0x79, (byte) 0xe8, + (byte) 0x21, (byte) 0x05 + }; + + // + // checksum digest + // + IDigest sha1 = new Sha1Digest(); + byte[] digest = new byte[20]; + + /** + * Method init + * + * @param forWrapping + * @param param + */ + public virtual void Init( + bool forWrapping, + ICipherParameters parameters) + { + this.forWrapping = forWrapping; + this.engine = new CbcBlockCipher(new RC2Engine()); + + if (parameters is ParametersWithRandom) + { + ParametersWithRandom pWithR = (ParametersWithRandom)parameters; + sr = pWithR.Random; + parameters = pWithR.Parameters; + } + else + { + sr = new SecureRandom(); + } + + if (parameters is ParametersWithIV) + { + if (!forWrapping) + throw new ArgumentException("You should not supply an IV for unwrapping"); + + this.paramPlusIV = (ParametersWithIV)parameters; + this.iv = this.paramPlusIV.GetIV(); + this.parameters = this.paramPlusIV.Parameters; + + if (this.iv.Length != 8) + throw new ArgumentException("IV is not 8 octets"); + } + else + { + this.parameters = parameters; + + 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.parameters, this.iv); + } + } + } + + /** + * Method GetAlgorithmName + * + * @return + */ + public virtual string AlgorithmName + { + get { return "RC2"; } + } + + /** + * Method wrap + * + * @param in + * @param inOff + * @param inLen + * @return + */ + public virtual byte[] Wrap( + byte[] input, + int inOff, + int length) + { + if (!forWrapping) + { + throw new InvalidOperationException("Not initialized for wrapping"); + } + + int len = length + 1; + if ((len % 8) != 0) + { + len += 8 - (len % 8); + } + + byte [] keyToBeWrapped = new byte[len]; + + keyToBeWrapped[0] = (byte)length; + Array.Copy(input, inOff, keyToBeWrapped, 1, length); + + byte[] pad = new byte[keyToBeWrapped.Length - length - 1]; + + if (pad.Length > 0) + { + sr.NextBytes(pad); + Array.Copy(pad, 0, keyToBeWrapped, length + 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]; + + Array.Copy(keyToBeWrapped, 0, WKCKS, 0, keyToBeWrapped.Length); + Array.Copy(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]; + + Array.Copy(WKCKS, 0, TEMP1, 0, WKCKS.Length); + + int noOfBlocks = WKCKS.Length / engine.GetBlockSize(); + int extraBytes = WKCKS.Length % engine.GetBlockSize(); + + if (extraBytes != 0) + { + throw new InvalidOperationException("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]; + + Array.Copy(this.iv, 0, TEMP2, 0, this.iv.Length); + Array.Copy(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.parameters, 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 + * @throws InvalidCipherTextException + */ + public virtual byte[] Unwrap( + byte[] input, + int inOff, + int length) + { + if (forWrapping) + { + throw new InvalidOperationException("Not set for unwrapping"); + } + + if (input == null) + { + throw new InvalidCipherTextException("Null pointer as ciphertext"); + } + + if (length % 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.parameters, IV2); + + this.engine.Init(false, param2); + + byte [] TEMP3 = new byte[length]; + + Array.Copy(input, inOff, TEMP3, 0, length); + + 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]; + + Array.Copy(TEMP2, 0, this.iv, 0, 8); + Array.Copy(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.parameters, this.iv); + + this.engine.Init(false, this.paramPlusIV); + + byte[] LCEKPADICV = new byte[TEMP1.Length]; + + Array.Copy(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]; + + Array.Copy(LCEKPADICV, 0, result, 0, LCEKPADICV.Length - 8); + Array.Copy(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]]; + Array.Copy(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 Exception + * @see http://www.w3.org/TR/xmlenc-core/#sec-CMSKeyChecksum + */ + private byte[] CalculateCmsKeyChecksum( + byte[] key) + { + sha1.BlockUpdate(key, 0, key.Length); + sha1.DoFinal(digest, 0); + + byte[] result = new byte[8]; + Array.Copy(digest, 0, result, 0, 8); + return result; + } + + /** + * @param key + * @param checksum + * @return + * @see http://www.w3.org/TR/xmlenc-core/#sec-CMSKeyChecksum + */ + private bool CheckCmsKeyChecksum( + byte[] key, + byte[] checksum) + { + return Arrays.ConstantTimeAreEqual(CalculateCmsKeyChecksum(key), checksum); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/engines/RC4Engine.cs b/bc-sharp-crypto/src/crypto/engines/RC4Engine.cs new file mode 100644 index 0000000..a515bb0 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/RC4Engine.cs @@ -0,0 +1,139 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Engines +{ + public class RC4Engine + : IStreamCipher + { + private readonly static int STATE_LENGTH = 256; + + /* + * variables to hold the state of the RC4 engine + * during encryption and decryption + */ + + private byte[] engineState; + private int x; + private int y; + private byte[] workingKey; + + /** + * initialise a RC4 cipher. + * + * @param forEncryption whether or not we are for encryption. + * @param parameters the parameters required to set up the cipher. + * @exception ArgumentException if the parameters argument is + * inappropriate. + */ + public virtual void Init( + bool forEncryption, + ICipherParameters parameters) + { + if (parameters is KeyParameter) + { + /* + * RC4 encryption and decryption is completely + * symmetrical, so the 'forEncryption' is + * irrelevant. + */ + workingKey = ((KeyParameter)parameters).GetKey(); + SetKey(workingKey); + + return; + } + + throw new ArgumentException("invalid parameter passed to RC4 init - " + Platform.GetTypeName(parameters)); + } + + public virtual string AlgorithmName + { + get { return "RC4"; } + } + + public virtual byte ReturnByte( + byte input) + { + x = (x + 1) & 0xff; + y = (engineState[x] + y) & 0xff; + + // swap + byte tmp = engineState[x]; + engineState[x] = engineState[y]; + engineState[y] = tmp; + + // xor + return (byte)(input ^ engineState[(engineState[x] + engineState[y]) & 0xff]); + } + + public virtual void ProcessBytes( + byte[] input, + int inOff, + int length, + byte[] output, + int outOff) + { + Check.DataLength(input, inOff, length, "input buffer too short"); + Check.OutputLength(output, outOff, length, "output buffer too short"); + + for (int i = 0; i < length ; i++) + { + x = (x + 1) & 0xff; + y = (engineState[x] + y) & 0xff; + + // swap + byte tmp = engineState[x]; + engineState[x] = engineState[y]; + engineState[y] = tmp; + + // xor + output[i+outOff] = (byte)(input[i + inOff] + ^ engineState[(engineState[x] + engineState[y]) & 0xff]); + } + } + + public virtual 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/bc-sharp-crypto/src/crypto/engines/RC532Engine.cs b/bc-sharp-crypto/src/crypto/engines/RC532Engine.cs new file mode 100644 index 0000000..d1c29e6 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/RC532Engine.cs @@ -0,0 +1,294 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Engines +{ + /** + * 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.

+ */ + public class RC532Engine + : IBlockCipher + { + /* + * 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 readonly int P32 = unchecked((int) 0xb7e15163); + private static readonly int Q32 = unchecked((int) 0x9e3779b9); + + private bool forEncryption; + + /** + * Create an instance of the RC5 encryption algorithm + * and set some defaults + */ + public RC532Engine() + { + _noRounds = 12; // the default +// _S = null; + } + + public virtual string AlgorithmName + { + get { return "RC5-32"; } + } + + public virtual bool IsPartialBlockOkay + { + get { return false; } + } + + public virtual int GetBlockSize() + { + return 2 * 4; + } + + /** + * initialise a RC5-32 cipher. + * + * @param forEncryption whether or not we are for encryption. + * @param parameters the parameters required to set up the cipher. + * @exception ArgumentException if the parameters argument is + * inappropriate. + */ + public virtual void Init( + bool forEncryption, + ICipherParameters parameters) + { + if (typeof(RC5Parameters).IsInstanceOfType(parameters)) + { + RC5Parameters p = (RC5Parameters)parameters; + + _noRounds = p.Rounds; + + SetKey(p.GetKey()); + } + else if (typeof(KeyParameter).IsInstanceOfType(parameters)) + { + KeyParameter p = (KeyParameter)parameters; + + SetKey(p.GetKey()); + } + else + { + throw new ArgumentException("invalid parameter passed to RC532 init - " + Platform.GetTypeName(parameters)); + } + + this.forEncryption = forEncryption; + } + + public virtual int ProcessBlock( + byte[] input, + int inOff, + byte[] output, + int outOff) + { + return (forEncryption) + ? EncryptBlock(input, inOff, output, outOff) + : DecryptBlock(input, inOff, output, outOff); + } + + public virtual 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 ii = 0, jj = 0; + + for (int k = 0; k < iter; k++) + { + A = _S[ii] = RotateLeft(_S[ii] + A + B, 3); + B = L[jj] = RotateLeft( L[jj] + A + B, A+B); + ii = (ii+1) % _S.Length; + jj = (jj+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[] input, + int inOff, + byte[] outBytes, + int outOff) + { + int A = BytesToWord(input, inOff) + _S[0]; + int B = BytesToWord(input, 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, outBytes, outOff); + WordToBytes(B, outBytes, outOff + 4); + + return 2 * 4; + } + + private int DecryptBlock( + byte[] input, + int inOff, + byte[] outBytes, + int outOff) + { + int A = BytesToWord(input, inOff); + int B = BytesToWord(input, 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], outBytes, outOff); + WordToBytes(B - _S[1], outBytes, 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 ((int) ( (uint) (x << (y & (32-1))) | + ((uint) 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 ((int) ( ((uint) x >> (y & (32-1))) | + (uint) (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/bc-sharp-crypto/src/crypto/engines/RC564Engine.cs b/bc-sharp-crypto/src/crypto/engines/RC564Engine.cs new file mode 100644 index 0000000..097fd60 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/RC564Engine.cs @@ -0,0 +1,295 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Engines +{ + /** + * 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.

+ */ + public class RC564Engine + : IBlockCipher + { + private static readonly int wordSize = 64; + private static readonly 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 readonly long P64 = unchecked( (long) 0xb7e151628aed2a6bL); + private static readonly long Q64 = unchecked( (long) 0x9e3779b97f4a7c15L); + + private bool forEncryption; + + /** + * Create an instance of the RC5 encryption algorithm + * and set some defaults + */ + public RC564Engine() + { + _noRounds = 12; +// _S = null; + } + + public virtual string AlgorithmName + { + get { return "RC5-64"; } + } + + public virtual bool IsPartialBlockOkay + { + get { return false; } + } + + public virtual int GetBlockSize() + { + return 2 * bytesPerWord; + } + + /** + * initialise a RC5-64 cipher. + * + * @param forEncryption whether or not we are for encryption. + * @param parameters the parameters required to set up the cipher. + * @exception ArgumentException if the parameters argument is + * inappropriate. + */ + public virtual void Init( + bool forEncryption, + ICipherParameters parameters) + { + if (!(typeof(RC5Parameters).IsInstanceOfType(parameters))) + { + throw new ArgumentException("invalid parameter passed to RC564 init - " + Platform.GetTypeName(parameters)); + } + + RC5Parameters p = (RC5Parameters)parameters; + + this.forEncryption = forEncryption; + + _noRounds = p.Rounds; + + SetKey(p.GetKey()); + } + + public virtual int ProcessBlock( + byte[] input, + int inOff, + byte[] output, + int outOff) + { + return (forEncryption) ? EncryptBlock(input, inOff, output, outOff) + : DecryptBlock(input, inOff, output, outOff); + } + + public virtual 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 ii = 0, jj = 0; + + for (int k = 0; k < iter; k++) + { + A = _S[ii] = RotateLeft(_S[ii] + A + B, 3); + B = L[jj] = RotateLeft( L[jj] + A + B, A+B); + ii = (ii+1) % _S.Length; + jj = (jj+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[] input, + int inOff, + byte[] outBytes, + int outOff) + { + long A = BytesToWord(input, inOff) + _S[0]; + long B = BytesToWord(input, 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, outBytes, outOff); + WordToBytes(B, outBytes, outOff + bytesPerWord); + + return 2 * bytesPerWord; + } + + private int DecryptBlock( + byte[] input, + int inOff, + byte[] outBytes, + int outOff) + { + long A = BytesToWord(input, inOff); + long B = BytesToWord(input, 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], outBytes, outOff); + WordToBytes(B - _S[1], outBytes, 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 ((long) ( (ulong) (x << (int) (y & (wordSize-1))) | + ((ulong) x >> (int) (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 ((long) ( ((ulong) x >> (int) (y & (wordSize-1))) | + (ulong) (x << (int) (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 = (long) ((ulong) word >> 8); + } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/engines/RC6Engine.cs b/bc-sharp-crypto/src/crypto/engines/RC6Engine.cs new file mode 100644 index 0000000..9aeb1e7 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/RC6Engine.cs @@ -0,0 +1,361 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Engines +{ + /** + * An RC6 engine. + */ + public class RC6Engine + : IBlockCipher + { + private static readonly int wordSize = 32; + private static readonly int bytesPerWord = wordSize / 8; + + /* + * the number of rounds to perform + */ + private static readonly 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 readonly int P32 = unchecked((int) 0xb7e15163); + private static readonly int Q32 = unchecked((int) 0x9e3779b9); + + private static readonly int LGW = 5; // log2(32) + + private bool forEncryption; + + /** + * Create an instance of the RC6 encryption algorithm + * and set some defaults + */ + public RC6Engine() + { +// _S = null; + } + + public virtual string AlgorithmName + { + get { return "RC6"; } + } + + public virtual bool IsPartialBlockOkay + { + get { return false; } + } + + public virtual int GetBlockSize() + { + return 4 * bytesPerWord; + } + + /** + * initialise a RC5-32 cipher. + * + * @param forEncryption whether or not we are for encryption. + * @param parameters the parameters required to set up the cipher. + * @exception ArgumentException if the parameters argument is + * inappropriate. + */ + public virtual void Init( + bool forEncryption, + ICipherParameters parameters) + { + if (!(parameters is KeyParameter)) + throw new ArgumentException("invalid parameter passed to RC6 init - " + Platform.GetTypeName(parameters)); + + this.forEncryption = forEncryption; + + KeyParameter p = (KeyParameter)parameters; + SetKey(p.GetKey()); + } + + public virtual int ProcessBlock( + byte[] input, + int inOff, + byte[] output, + int outOff) + { + int blockSize = GetBlockSize(); + if (_S == null) + throw new InvalidOperationException("RC6 engine not initialised"); + + Check.DataLength(input, inOff, blockSize, "input buffer too short"); + Check.OutputLength(output, outOff, blockSize, "output buffer too short"); + + return (forEncryption) + ? EncryptBlock(input, inOff, output, outOff) + : DecryptBlock(input, inOff, output, outOff); + } + + public virtual 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 ii = 0, jj = 0; + + for (int k = 0; k < iter; k++) + { + A = _S[ii] = RotateLeft(_S[ii] + A + B, 3); + B = L[jj] = RotateLeft( L[jj] + A + B, A+B); + ii = (ii+1) % _S.Length; + jj = (jj+1) % L.Length; + } + } + + private int EncryptBlock( + byte[] input, + int inOff, + byte[] outBytes, + int outOff) + { + // load A,B,C and D registers from in. + int A = BytesToWord(input, inOff); + int B = BytesToWord(input, inOff + bytesPerWord); + int C = BytesToWord(input, inOff + bytesPerWord*2); + int D = BytesToWord(input, 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, outBytes, outOff); + WordToBytes(B, outBytes, outOff + bytesPerWord); + WordToBytes(C, outBytes, outOff + bytesPerWord*2); + WordToBytes(D, outBytes, outOff + bytesPerWord*3); + + return 4 * bytesPerWord; + } + + private int DecryptBlock( + byte[] input, + int inOff, + byte[] outBytes, + int outOff) + { + // load A,B,C and D registers from out. + int A = BytesToWord(input, inOff); + int B = BytesToWord(input, inOff + bytesPerWord); + int C = BytesToWord(input, inOff + bytesPerWord*2); + int D = BytesToWord(input, 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, outBytes, outOff); + WordToBytes(B, outBytes, outOff + bytesPerWord); + WordToBytes(C, outBytes, outOff + bytesPerWord*2); + WordToBytes(D, outBytes, 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 a power of 2. + * + * @param x word to rotate + * @param y number of bits to rotate % wordSize + */ + private int RotateLeft(int x, int y) + { + return ((int)((uint)(x << (y & (wordSize-1))) + | ((uint) 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 int RotateRight(int x, int y) + { + return ((int)(((uint) x >> (y & (wordSize-1))) + | (uint)(x << (wordSize - (y & (wordSize-1)))))); + } + + 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 = (int) ((uint) word >> 8); + } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/engines/RFC3211WrapEngine.cs b/bc-sharp-crypto/src/crypto/engines/RFC3211WrapEngine.cs new file mode 100644 index 0000000..4e3af52 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/RFC3211WrapEngine.cs @@ -0,0 +1,168 @@ +using System; + +using Org.BouncyCastle.Crypto.Modes; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Engines +{ + /** + * an implementation of the RFC 3211 Key Wrap + * Specification. + */ + public class Rfc3211WrapEngine + : IWrapper + { + private CbcBlockCipher engine; + private ParametersWithIV param; + private bool forWrapping; + private SecureRandom rand; + + public Rfc3211WrapEngine( + IBlockCipher engine) + { + this.engine = new CbcBlockCipher(engine); + } + + public virtual void Init( + bool forWrapping, + ICipherParameters param) + { + this.forWrapping = forWrapping; + + if (param is ParametersWithRandom) + { + ParametersWithRandom p = (ParametersWithRandom) param; + + this.rand = p.Random; + this.param = (ParametersWithIV) p.Parameters; + } + else + { + if (forWrapping) + { + rand = new SecureRandom(); + } + + this.param = (ParametersWithIV) param; + } + } + + public virtual string AlgorithmName + { + get { return engine.GetUnderlyingCipher().AlgorithmName + "/RFC3211Wrap"; } + } + + public virtual byte[] Wrap( + byte[] inBytes, + int inOff, + int inLen) + { + if (!forWrapping) + { + throw new InvalidOperationException("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)~inBytes[inOff]; + cekBlock[2] = (byte)~inBytes[inOff + 1]; + cekBlock[3] = (byte)~inBytes[inOff + 2]; + + Array.Copy(inBytes, inOff, cekBlock, 4, inLen); + + rand.NextBytes(cekBlock, inLen + 4, cekBlock.Length - inLen - 4); + + 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 virtual byte[] Unwrap( + byte[] inBytes, + int inOff, + int inLen) + { + if (forWrapping) + { + throw new InvalidOperationException("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]; + + Array.Copy(inBytes, inOff, cekBlock, 0, inLen); + Array.Copy(inBytes, inOff, iv, 0, iv.Length); + + engine.Init(false, new ParametersWithIV(param.Parameters, iv)); + + for (int i = blockSize; i < cekBlock.Length; i += blockSize) + { + engine.ProcessBlock(cekBlock, i, cekBlock, i); + } + + Array.Copy(cekBlock, cekBlock.Length - iv.Length, iv, 0, iv.Length); + + engine.Init(false, new ParametersWithIV(param.Parameters, 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]; + + Array.Copy(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/bc-sharp-crypto/src/crypto/engines/RFC3394WrapEngine.cs b/bc-sharp-crypto/src/crypto/engines/RFC3394WrapEngine.cs new file mode 100644 index 0000000..4bb0e21 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/RFC3394WrapEngine.cs @@ -0,0 +1,178 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Engines +{ + /// + /// 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 + : IWrapper + { + private readonly IBlockCipher engine; + + private KeyParameter param; + private bool forWrapping; + + private byte[] iv = + { + 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6 + }; + + public Rfc3394WrapEngine( + IBlockCipher engine) + { + this.engine = engine; + } + + public virtual void Init( + bool forWrapping, + ICipherParameters parameters) + { + this.forWrapping = forWrapping; + + if (parameters is ParametersWithRandom) + { + parameters = ((ParametersWithRandom) parameters).Parameters; + } + + if (parameters is KeyParameter) + { + this.param = (KeyParameter) parameters; + } + else if (parameters is ParametersWithIV) + { + ParametersWithIV pIV = (ParametersWithIV) parameters; + byte[] iv = pIV.GetIV(); + + if (iv.Length != 8) + throw new ArgumentException("IV length not equal to 8", "parameters"); + + this.iv = iv; + this.param = (KeyParameter) pIV.Parameters; + } + else + { + // TODO Throw an exception for bad parameters? + } + } + + public virtual string AlgorithmName + { + get { return engine.AlgorithmName; } + } + + public virtual byte[] Wrap( + byte[] input, + int inOff, + int inLen) + { + if (!forWrapping) + { + throw new InvalidOperationException("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]; + + Array.Copy(iv, 0, block, 0, iv.Length); + Array.Copy(input, inOff, block, iv.Length, inLen); + + engine.Init(true, param); + + for (int j = 0; j != 6; j++) + { + for (int i = 1; i <= n; i++) + { + Array.Copy(block, 0, buf, 0, iv.Length); + Array.Copy(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 = (int) ((uint)t >> 8); + } + + Array.Copy(buf, 0, block, 0, 8); + Array.Copy(buf, 8, block, 8 * i, 8); + } + } + + return block; + } + + public virtual byte[] Unwrap( + byte[] input, + int inOff, + int inLen) + { + if (forWrapping) + { + throw new InvalidOperationException("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]; + + Array.Copy(input, inOff, a, 0, iv.Length); + Array.Copy(input, inOff + 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--) + { + Array.Copy(a, 0, buf, 0, iv.Length); + Array.Copy(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 = (int) ((uint)t >> 8); + } + + engine.ProcessBlock(buf, 0, buf, 0); + Array.Copy(buf, 0, a, 0, 8); + Array.Copy(buf, 8, block, 8 * (i - 1), 8); + } + } + + if (!Arrays.ConstantTimeAreEqual(a, iv)) + throw new InvalidCipherTextException("checksum failed"); + + return block; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/engines/RSABlindedEngine.cs b/bc-sharp-crypto/src/crypto/engines/RSABlindedEngine.cs new file mode 100644 index 0000000..f95f145 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/RSABlindedEngine.cs @@ -0,0 +1,128 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Engines +{ + /** + * this does your basic RSA algorithm with blinding + */ + public class RsaBlindedEngine + : IAsymmetricBlockCipher + { + private readonly RsaCoreEngine core = new RsaCoreEngine(); + private RsaKeyParameters key; + private SecureRandom random; + + public virtual string AlgorithmName + { + get { return "RSA"; } + } + + /** + * initialise the RSA engine. + * + * @param forEncryption true if we are encrypting, false otherwise. + * @param param the necessary RSA key parameters. + */ + public virtual void Init( + bool forEncryption, + ICipherParameters param) + { + core.Init(forEncryption, param); + + if (param is ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)param; + + key = (RsaKeyParameters)rParam.Parameters; + random = rParam.Random; + } + 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 virtual 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 virtual int GetOutputBlockSize() + { + return core.GetOutputBlockSize(); + } + + /** + * Process a single block using the basic RSA algorithm. + * + * @param inBuf 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 virtual byte[] ProcessBlock( + byte[] inBuf, + int inOff, + int inLen) + { + if (key == null) + throw new InvalidOperationException("RSA engine not initialised"); + + BigInteger input = core.ConvertInput(inBuf, inOff, inLen); + + BigInteger result; + if (key is RsaPrivateCrtKeyParameters) + { + RsaPrivateCrtKeyParameters k = (RsaPrivateCrtKeyParameters)key; + BigInteger e = k.PublicExponent; + if (e != null) // can't do blinding without a public exponent + { + BigInteger m = k.Modulus; + BigInteger r = BigIntegers.CreateRandomInRange( + BigInteger.One, m.Subtract(BigInteger.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); + + // defence against Arjen Lenstras CRT attack + if (!input.Equals(result.ModPow(e, m))) + throw new InvalidOperationException("RSA engine faulty decryption/signing detected"); + } + else + { + result = core.ProcessBlock(input); + } + } + else + { + result = core.ProcessBlock(input); + } + + return core.ConvertOutput(result); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/engines/RSABlindingEngine.cs b/bc-sharp-crypto/src/crypto/engines/RSABlindingEngine.cs new file mode 100644 index 0000000..c636627 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/RSABlindingEngine.cs @@ -0,0 +1,139 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Crypto.Engines +{ + /** + * 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 + : IAsymmetricBlockCipher + { + private readonly RsaCoreEngine core = new RsaCoreEngine(); + + private RsaKeyParameters key; + private BigInteger blindingFactor; + + private bool forEncryption; + + public virtual string AlgorithmName + { + get { return "RSA"; } + } + + /** + * Initialise the blinding engine. + * + * @param forEncryption true if we are encrypting (blinding), false otherwise. + * @param param the necessary RSA key parameters. + */ + public virtual void Init( + bool forEncryption, + ICipherParameters param) + { + RsaBlindingParameters p; + + if (param is ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)param; + + p = (RsaBlindingParameters)rParam.Parameters; + } + else + { + p = (RsaBlindingParameters)param; + } + + core.Init(forEncryption, p.PublicKey); + + this.forEncryption = forEncryption; + this.key = p.PublicKey; + this.blindingFactor = p.BlindingFactor; + } + + /** + * 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 virtual 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 virtual 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 virtual byte[] ProcessBlock( + byte[] inBuf, + int inOff, + int inLen) + { + BigInteger msg = core.ConvertInput(inBuf, 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.Exponent, key.Modulus)); + blindMsg = blindMsg.Mod(key.Modulus); + + return blindMsg; + } + + /* + * Unblind the message blinded with the blind factor. + */ + private BigInteger UnblindMessage( + BigInteger blindedMsg) + { + BigInteger m = key.Modulus; + BigInteger msg = blindedMsg; + BigInteger blindFactorInverse = blindingFactor.ModInverse(m); + msg = msg.Multiply(blindFactorInverse); + msg = msg.Mod(m); + + return msg; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/engines/RSACoreEngine.cs b/bc-sharp-crypto/src/crypto/engines/RSACoreEngine.cs new file mode 100644 index 0000000..fd44e3c --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/RSACoreEngine.cs @@ -0,0 +1,156 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Engines +{ + /** + * this does your basic RSA algorithm. + */ + class RsaCoreEngine + { + private RsaKeyParameters key; + private bool forEncryption; + private int bitSize; + + /** + * initialise the RSA engine. + * + * @param forEncryption true if we are encrypting, false otherwise. + * @param param the necessary RSA key parameters. + */ + public virtual void Init( + bool forEncryption, + ICipherParameters parameters) + { + if (parameters is ParametersWithRandom) + { + parameters = ((ParametersWithRandom) parameters).Parameters; + } + + if (!(parameters is RsaKeyParameters)) + throw new InvalidKeyException("Not an RSA key"); + + this.key = (RsaKeyParameters) parameters; + this.forEncryption = forEncryption; + this.bitSize = key.Modulus.BitLength; + } + + /** + * 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 virtual int GetInputBlockSize() + { + if (forEncryption) + { + return (bitSize - 1) / 8; + } + + 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 virtual int GetOutputBlockSize() + { + if (forEncryption) + { + return (bitSize + 7) / 8; + } + + return (bitSize - 1) / 8; + } + + public virtual BigInteger ConvertInput( + byte[] inBuf, + int inOff, + int inLen) + { + int maxLength = (bitSize + 7) / 8; + + if (inLen > maxLength) + throw new DataLengthException("input too large for RSA cipher."); + + BigInteger input = new BigInteger(1, inBuf, inOff, inLen); + + if (input.CompareTo(key.Modulus) >= 0) + throw new DataLengthException("input too large for RSA cipher."); + + return input; + } + + public virtual byte[] ConvertOutput( + BigInteger result) + { + byte[] output = result.ToByteArrayUnsigned(); + + if (forEncryption) + { + int outSize = GetOutputBlockSize(); + + // TODO To avoid this, create version of BigInteger.ToByteArray that + // writes to an existing array + if (output.Length < outSize) // have ended up with less bytes than normal, lengthen + { + byte[] tmp = new byte[outSize]; + output.CopyTo(tmp, tmp.Length - output.Length); + output = tmp; + } + } + + return output; + } + + public virtual BigInteger ProcessBlock( + BigInteger input) + { + if (key is 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.P; + BigInteger q = crtKey.Q; + BigInteger dP = crtKey.DP; + BigInteger dQ = crtKey.DQ; + BigInteger qInv = crtKey.QInv; + + 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; + } + + return input.ModPow(key.Exponent, key.Modulus); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/engines/RijndaelEngine.cs b/bc-sharp-crypto/src/crypto/engines/RijndaelEngine.cs new file mode 100644 index 0000000..7025cb5 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/RijndaelEngine.cs @@ -0,0 +1,738 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Engines +{ + /** + * 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 readonly NIST publication. + *

+ */ + public class RijndaelEngine + : IBlockCipher + { + private static readonly int MAXROUNDS = 14; + + private static readonly int MAXKC = (256/4); + + private static readonly byte[] Logtable = + { + 0, 0, 25, 1, 50, 2, 26, 198, + 75, 199, 27, 104, 51, 238, 223, 3, + 100, 4, 224, 14, 52, 141, 129, 239, + 76, 113, 8, 200, 248, 105, 28, 193, + 125, 194, 29, 181, 249, 185, 39, 106, + 77, 228, 166, 114, 154, 201, 9, 120, + 101, 47, 138, 5, 33, 15, 225, 36, + 18, 240, 130, 69, 53, 147, 218, 142, + 150, 143, 219, 189, 54, 208, 206, 148, + 19, 92, 210, 241, 64, 70, 131, 56, + 102, 221, 253, 48, 191, 6, 139, 98, + 179, 37, 226, 152, 34, 136, 145, 16, + 126, 110, 72, 195, 163, 182, 30, 66, + 58, 107, 40, 84, 250, 133, 61, 186, + 43, 121, 10, 21, 155, 159, 94, 202, + 78, 212, 172, 229, 243, 115, 167, 87, + 175, 88, 168, 80, 244, 234, 214, 116, + 79, 174, 233, 213, 231, 230, 173, 232, + 44, 215, 117, 122, 235, 22, 11, 245, + 89, 203, 95, 176, 156, 169, 81, 160, + 127, 12, 246, 111, 23, 196, 73, 236, + 216, 67, 31, 45, 164, 118, 123, 183, + 204, 187, 62, 90, 251, 96, 177, 134, + 59, 82, 161, 108, 170, 85, 41, 157, + 151, 178, 135, 144, 97, 190, 220, 252, + 188, 149, 207, 205, 55, 63, 91, 209, + 83, 57, 132, 60, 65, 162, 109, 71, + 20, 42, 158, 93, 86, 242, 211, 171, + 68, 17, 146, 217, 35, 32, 46, 137, + 180, 124, 184, 38, 119, 153, 227, 165, + 103, 74, 237, 222, 197, 49, 254, 24, + 13, 99, 140, 128, 192, 247, 112, 7 + }; + + private static readonly byte[] Alogtable = + { + 0, 3, 5, 15, 17, 51, 85, 255, 26, 46, 114, 150, 161, 248, 19, 53, + 95, 225, 56, 72, 216, 115, 149, 164, 247, 2, 6, 10, 30, 34, 102, 170, + 229, 52, 92, 228, 55, 89, 235, 38, 106, 190, 217, 112, 144, 171, 230, 49, + 83, 245, 4, 12, 20, 60, 68, 204, 79, 209, 104, 184, 211, 110, 178, 205, + 76, 212, 103, 169, 224, 59, 77, 215, 98, 166, 241, 8, 24, 40, 120, 136, + 131, 158, 185, 208, 107, 189, 220, 127, 129, 152, 179, 206, 73, 219, 118, 154, + 181, 196, 87, 249, 16, 48, 80, 240, 11, 29, 39, 105, 187, 214, 97, 163, + 254, 25, 43, 125, 135, 146, 173, 236, 47, 113, 147, 174, 233, 32, 96, 160, + 251, 22, 58, 78, 210, 109, 183, 194, 93, 231, 50, 86, 250, 21, 63, 65, + 195, 94, 226, 61, 71, 201, 64, 192, 91, 237, 44, 116, 156, 191, 218, 117, + 159, 186, 213, 100, 172, 239, 42, 126, 130, 157, 188, 223, 122, 142, 137, 128, + 155, 182, 193, 88, 232, 35, 101, 175, 234, 37, 111, 177, 200, 67, 197, 84, + 252, 31, 33, 99, 165, 244, 7, 9, 27, 45, 119, 153, 176, 203, 70, 202, + 69, 207, 74, 222, 121, 139, 134, 145, 168, 227, 62, 66, 198, 81, 243, 14, + 18, 54, 90, 238, 41, 123, 141, 140, 143, 138, 133, 148, 167, 242, 13, 23, + 57, 75, 221, 124, 132, 151, 162, 253, 28, 36, 108, 180, 199, 82, 246, 1, + 3, 5, 15, 17, 51, 85, 255, 26, 46, 114, 150, 161, 248, 19, 53, + 95, 225, 56, 72, 216, 115, 149, 164, 247, 2, 6, 10, 30, 34, 102, 170, + 229, 52, 92, 228, 55, 89, 235, 38, 106, 190, 217, 112, 144, 171, 230, 49, + 83, 245, 4, 12, 20, 60, 68, 204, 79, 209, 104, 184, 211, 110, 178, 205, + 76, 212, 103, 169, 224, 59, 77, 215, 98, 166, 241, 8, 24, 40, 120, 136, + 131, 158, 185, 208, 107, 189, 220, 127, 129, 152, 179, 206, 73, 219, 118, 154, + 181, 196, 87, 249, 16, 48, 80, 240, 11, 29, 39, 105, 187, 214, 97, 163, + 254, 25, 43, 125, 135, 146, 173, 236, 47, 113, 147, 174, 233, 32, 96, 160, + 251, 22, 58, 78, 210, 109, 183, 194, 93, 231, 50, 86, 250, 21, 63, 65, + 195, 94, 226, 61, 71, 201, 64, 192, 91, 237, 44, 116, 156, 191, 218, 117, + 159, 186, 213, 100, 172, 239, 42, 126, 130, 157, 188, 223, 122, 142, 137, 128, + 155, 182, 193, 88, 232, 35, 101, 175, 234, 37, 111, 177, 200, 67, 197, 84, + 252, 31, 33, 99, 165, 244, 7, 9, 27, 45, 119, 153, 176, 203, 70, 202, + 69, 207, 74, 222, 121, 139, 134, 145, 168, 227, 62, 66, 198, 81, 243, 14, + 18, 54, 90, 238, 41, 123, 141, 140, 143, 138, 133, 148, 167, 242, 13, 23, + 57, 75, 221, 124, 132, 151, 162, 253, 28, 36, 108, 180, 199, 82, 246, 1, + }; + + private static readonly byte[] S = + { + 99, 124, 119, 123, 242, 107, 111, 197, 48, 1, 103, 43, 254, 215, 171, 118, + 202, 130, 201, 125, 250, 89, 71, 240, 173, 212, 162, 175, 156, 164, 114, 192, + 183, 253, 147, 38, 54, 63, 247, 204, 52, 165, 229, 241, 113, 216, 49, 21, + 4, 199, 35, 195, 24, 150, 5, 154, 7, 18, 128, 226, 235, 39, 178, 117, + 9, 131, 44, 26, 27, 110, 90, 160, 82, 59, 214, 179, 41, 227, 47, 132, + 83, 209, 0, 237, 32, 252, 177, 91, 106, 203, 190, 57, 74, 76, 88, 207, + 208, 239, 170, 251, 67, 77, 51, 133, 69, 249, 2, 127, 80, 60, 159, 168, + 81, 163, 64, 143, 146, 157, 56, 245, 188, 182, 218, 33, 16, 255, 243, 210, + 205, 12, 19, 236, 95, 151, 68, 23, 196, 167, 126, 61, 100, 93, 25, 115, + 96, 129, 79, 220, 34, 42, 144, 136, 70, 238, 184, 20, 222, 94, 11, 219, + 224, 50, 58, 10, 73, 6, 36, 92, 194, 211, 172, 98, 145, 149, 228, 121, + 231, 200, 55, 109, 141, 213, 78, 169, 108, 86, 244, 234, 101, 122, 174, 8, + 186, 120, 37, 46, 28, 166, 180, 198, 232, 221, 116, 31, 75, 189, 139, 138, + 112, 62, 181, 102, 72, 3, 246, 14, 97, 53, 87, 185, 134, 193, 29, 158, + 225, 248, 152, 17, 105, 217, 142, 148, 155, 30, 135, 233, 206, 85, 40, 223, + 140, 161, 137, 13, 191, 230, 66, 104, 65, 153, 45, 15, 176, 84, 187, 22, + }; + + private static readonly byte[] Si = + { + 82, 9, 106, 213, 48, 54, 165, 56, 191, 64, 163, 158, 129, 243, 215, 251, + 124, 227, 57, 130, 155, 47, 255, 135, 52, 142, 67, 68, 196, 222, 233, 203, + 84, 123, 148, 50, 166, 194, 35, 61, 238, 76, 149, 11, 66, 250, 195, 78, + 8, 46, 161, 102, 40, 217, 36, 178, 118, 91, 162, 73, 109, 139, 209, 37, + 114, 248, 246, 100, 134, 104, 152, 22, 212, 164, 92, 204, 93, 101, 182, 146, + 108, 112, 72, 80, 253, 237, 185, 218, 94, 21, 70, 87, 167, 141, 157, 132, + 144, 216, 171, 0, 140, 188, 211, 10, 247, 228, 88, 5, 184, 179, 69, 6, + 208, 44, 30, 143, 202, 63, 15, 2, 193, 175, 189, 3, 1, 19, 138, 107, + 58, 145, 17, 65, 79, 103, 220, 234, 151, 242, 207, 206, 240, 180, 230, 115, + 150, 172, 116, 34, 231, 173, 53, 133, 226, 249, 55, 232, 28, 117, 223, 110, + 71, 241, 26, 113, 29, 41, 197, 137, 111, 183, 98, 14, 170, 24, 190, 27, + 252, 86, 62, 75, 198, 210, 121, 32, 154, 219, 192, 254, 120, 205, 90, 244, + 31, 221, 168, 51, 136, 7, 199, 49, 177, 18, 16, 89, 39, 128, 236, 95, + 96, 81, 127, 169, 25, 181, 74, 13, 45, 229, 122, 159, 147, 201, 156, 239, + 160, 224, 59, 77, 174, 42, 245, 176, 200, 235, 187, 60, 131, 83, 153, 97, + 23, 43, 4, 126, 186, 119, 214, 38, 225, 105, 20, 99, 85, 33, 12, 125, + }; + + private static readonly byte[] 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 readonly byte[][] shifts0 = new byte [][] + { + new byte[]{ 0, 8, 16, 24 }, + new byte[]{ 0, 8, 16, 24 }, + new byte[]{ 0, 8, 16, 24 }, + new byte[]{ 0, 8, 16, 32 }, + new byte[]{ 0, 8, 24, 32 } + }; + + static readonly byte[][] shifts1 = + { + new byte[]{ 0, 24, 16, 8 }, + new byte[]{ 0, 32, 24, 16 }, + new byte[]{ 0, 40, 32, 24 }, + new byte[]{ 0, 48, 40, 24 }, + new byte[]{ 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 (((long)((ulong) r >> shift) | (r << (BC - shift)))) & BC_MASK; + + ulong temp = (ulong) r >> shift; + + // NB: This corrects for Mono Bug #79087 (fixed in 1.1.17) + if (shift > 31) + { + temp &= 0xFFFFFFFFUL; + } + + return ((long) temp | (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]; + long[][] W = new long[MAXROUNDS+1][]; + + for (int i = 0; i < MAXROUNDS+1; i++) W[i] = new long[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 ArgumentException("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] ^= (byte) 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 bool 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 blocksize 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 = unchecked( (long)0xffffffffffffffffL); + shifts0SC = shifts0[4]; + shifts1SC = shifts1[4]; + break; + default: + throw new ArgumentException("unknown blocksize to Rijndael"); + } + + this.blockBits = blockBits; + } + + /** + * initialise a Rijndael cipher. + * + * @param forEncryption whether or not we are for encryption. + * @param parameters the parameters required to set up the cipher. + * @exception ArgumentException if the parameters argument is + * inappropriate. + */ + public virtual void Init( + bool forEncryption, + ICipherParameters parameters) + { + if (typeof(KeyParameter).IsInstanceOfType(parameters)) + { + workingKey = GenerateWorkingKey(((KeyParameter)parameters).GetKey()); + this.forEncryption = forEncryption; + return; + } + + throw new ArgumentException("invalid parameter passed to Rijndael init - " + Platform.GetTypeName(parameters)); + } + + public virtual string AlgorithmName + { + get { return "Rijndael"; } + } + + public virtual bool IsPartialBlockOkay + { + get { return false; } + } + + public virtual int GetBlockSize() + { + return BC / 2; + } + + public virtual int ProcessBlock( + byte[] input, + int inOff, + byte[] output, + int outOff) + { + if (workingKey == null) + throw new InvalidOperationException("Rijndael engine not initialised"); + + Check.DataLength(input, inOff, (BC / 2), "input buffer too short"); + Check.OutputLength(output, outOff, (BC / 2), "output buffer too short"); + + UnPackBlock(input, inOff); + + if (forEncryption) + { + EncryptBlock(workingKey); + } + else + { + DecryptBlock(workingKey); + } + + PackBlock(output, outOff); + + return BC / 2; + } + + public virtual void Reset() + { + } + + private void UnPackBlock( + byte[] bytes, + int off) + { + int index = off; + + A0 = (long)(bytes[index++] & 0xff); + A1 = (long)(bytes[index++] & 0xff); + A2 = (long)(bytes[index++] & 0xff); + A3 = (long)(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/bc-sharp-crypto/src/crypto/engines/RsaEngine.cs b/bc-sharp-crypto/src/crypto/engines/RsaEngine.cs new file mode 100644 index 0000000..4399b44 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/RsaEngine.cs @@ -0,0 +1,78 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Engines +{ + /** + * this does your basic RSA algorithm. + */ + public class RsaEngine + : IAsymmetricBlockCipher + { + private RsaCoreEngine core; + + public virtual string AlgorithmName + { + get { return "RSA"; } + } + + /** + * initialise the RSA engine. + * + * @param forEncryption true if we are encrypting, false otherwise. + * @param param the necessary RSA key parameters. + */ + public virtual void Init( + bool forEncryption, + ICipherParameters parameters) + { + if (core == null) + core = new RsaCoreEngine(); + + core.Init(forEncryption, parameters); + } + + /** + * 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 virtual 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 virtual int GetOutputBlockSize() + { + return core.GetOutputBlockSize(); + } + + /** + * Process a single block using the basic RSA algorithm. + * + * @param inBuf 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 virtual byte[] ProcessBlock( + byte[] inBuf, + int inOff, + int inLen) + { + if (core == null) + throw new InvalidOperationException("RSA engine not initialised"); + + return core.ConvertOutput(core.ProcessBlock(core.ConvertInput(inBuf, inOff, inLen))); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/engines/SEEDEngine.cs b/bc-sharp-crypto/src/crypto/engines/SEEDEngine.cs new file mode 100644 index 0000000..f615b84 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/SEEDEngine.cs @@ -0,0 +1,360 @@ +using System; +using Org.BouncyCastle.Crypto.Parameters; + +namespace Org.BouncyCastle.Crypto.Engines +{ + /** + * Implementation of the SEED algorithm as described in RFC 4009 + */ + public class SeedEngine + : IBlockCipher + { + private const int BlockSize = 16; + + private static readonly uint[] 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 readonly uint[] 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 readonly uint[] 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 readonly uint[] 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 readonly uint[] KC = + { + 0x9e3779b9, 0x3c6ef373, 0x78dde6e6, 0xf1bbcdcc, + 0xe3779b99, 0xc6ef3733, 0x8dde6e67, 0x1bbcdccf, + 0x3779b99e, 0x6ef3733c, 0xdde6e678, 0xbbcdccf1, + 0x779b99e3, 0xef3733c6, 0xde6e678d, 0xbcdccf1b + }; + + private int[] wKey; + private bool forEncryption; + + public virtual void Init( + bool forEncryption, + ICipherParameters parameters) + { + this.forEncryption = forEncryption; + wKey = createWorkingKey(((KeyParameter)parameters).GetKey()); + } + + public virtual string AlgorithmName + { + get { return "SEED"; } + } + + public virtual bool IsPartialBlockOkay + { + get { return false; } + } + + public virtual int GetBlockSize() + { + return BlockSize; + } + + public virtual int ProcessBlock( + byte[] inBuf, + int inOff, + byte[] outBuf, + int outOff) + { + if (wKey == null) + throw new InvalidOperationException("SEED engine not initialised"); + + Check.DataLength(inBuf, inOff, BlockSize, "input buffer too short"); + Check.OutputLength(outBuf, outOff, BlockSize, "output buffer too short"); + + long l = bytesToLong(inBuf, inOff + 0); + long r = bytesToLong(inBuf, 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(outBuf, outOff + 0, r); + longToBytes(outBuf, outOff + 8, l); + + return BlockSize; + } + + public virtual 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 - (int)KC[i]); + key[2 * i + 1] = G(key1 - key3 + (int)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) | ((long)((ulong) x >> 56)); + } + + private long rotateRight8( + long x) + { + return ((long)((ulong) 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 (int)(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/bc-sharp-crypto/src/crypto/engines/SEEDWrapEngine.cs b/bc-sharp-crypto/src/crypto/engines/SEEDWrapEngine.cs new file mode 100644 index 0000000..6b71f94 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/SEEDWrapEngine.cs @@ -0,0 +1,16 @@ +namespace Org.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 + : Rfc3394WrapEngine + { + public SeedWrapEngine() + : base(new SeedEngine()) + { + } + } +} diff --git a/bc-sharp-crypto/src/crypto/engines/Salsa20Engine.cs b/bc-sharp-crypto/src/crypto/engines/Salsa20Engine.cs new file mode 100644 index 0000000..182eacd --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/Salsa20Engine.cs @@ -0,0 +1,362 @@ +using System; +using System.Text; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Utilities; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Engines +{ + ///

+ /// Implementation of Daniel J. Bernstein's Salsa20 stream cipher, Snuffle 2005 + /// + public class Salsa20Engine + : IStreamCipher + { + public static readonly int DEFAULT_ROUNDS = 20; + + /** Constants */ + private const int StateSize = 16; // 16, 32 bit ints = 64 bytes + + private readonly static uint[] TAU_SIGMA = Pack.LE_To_UInt32(Strings.ToAsciiByteArray("expand 16-byte k" + "expand 32-byte k"), 0, 8); + + internal void PackTauOrSigma(int keyLength, uint[] state, int stateOffset) + { + int tsOff = (keyLength - 16) / 4; + state[stateOffset] = TAU_SIGMA[tsOff]; + state[stateOffset + 1] = TAU_SIGMA[tsOff + 1]; + state[stateOffset + 2] = TAU_SIGMA[tsOff + 2]; + state[stateOffset + 3] = TAU_SIGMA[tsOff + 3]; + } + + [Obsolete] + protected readonly static byte[] + sigma = Strings.ToAsciiByteArray("expand 32-byte k"), + tau = Strings.ToAsciiByteArray("expand 16-byte k"); + + protected int rounds; + + /* + * variables to hold the state of the engine + * during encryption and decryption + */ + private int index = 0; + internal uint[] engineState = new uint[StateSize]; // state + internal uint[] x = new uint[StateSize]; // internal buffer + private byte[] keyStream = new byte[StateSize * 4]; // expanded state, 64 bytes + private bool initialised = false; + + /* + * internal counter + */ + private uint cW0, cW1, cW2; + + /// + /// Creates a 20 round Salsa20 engine. + /// + public Salsa20Engine() + : this(DEFAULT_ROUNDS) + { + } + + /// + /// Creates a Salsa20 engine with a specific number of rounds. + /// + /// the number of rounds (must be an even number). + public Salsa20Engine(int rounds) + { + if (rounds <= 0 || (rounds & 1) != 0) + { + throw new ArgumentException("'rounds' must be a positive, even number"); + } + + this.rounds = rounds; + } + + public virtual void Init( + bool forEncryption, + ICipherParameters parameters) + { + /* + * Salsa20 encryption and decryption is completely + * symmetrical, so the 'forEncryption' is + * irrelevant. (Like 90% of stream ciphers) + */ + + ParametersWithIV ivParams = parameters as ParametersWithIV; + if (ivParams == null) + throw new ArgumentException(AlgorithmName + " Init requires an IV", "parameters"); + + byte[] iv = ivParams.GetIV(); + if (iv == null || iv.Length != NonceSize) + throw new ArgumentException(AlgorithmName + " requires exactly " + NonceSize + " bytes of IV"); + + ICipherParameters keyParam = ivParams.Parameters; + if (keyParam == null) + { + if (!initialised) + throw new InvalidOperationException(AlgorithmName + " KeyParameter can not be null for first initialisation"); + + SetKey(null, iv); + } + else if (keyParam is KeyParameter) + { + SetKey(((KeyParameter)keyParam).GetKey(), iv); + } + else + { + throw new ArgumentException(AlgorithmName + " Init parameters must contain a KeyParameter (or null for re-init)"); + } + + Reset(); + initialised = true; + } + + protected virtual int NonceSize + { + get { return 8; } + } + + public virtual string AlgorithmName + { + get + { + string name = "Salsa20"; + if (rounds != DEFAULT_ROUNDS) + { + name += "/" + rounds; + } + return name; + } + } + + public virtual byte ReturnByte( + byte input) + { + if (LimitExceeded()) + { + throw new MaxBytesExceededException("2^70 byte limit per IV; Change IV"); + } + + if (index == 0) + { + GenerateKeyStream(keyStream); + AdvanceCounter(); + } + + byte output = (byte)(keyStream[index] ^ input); + index = (index + 1) & 63; + + return output; + } + + protected virtual void AdvanceCounter() + { + if (++engineState[8] == 0) + { + ++engineState[9]; + } + } + + public virtual void ProcessBytes( + byte[] inBytes, + int inOff, + int len, + byte[] outBytes, + int outOff) + { + if (!initialised) + throw new InvalidOperationException(AlgorithmName + " not initialised"); + + Check.DataLength(inBytes, inOff, len, "input buffer too short"); + Check.OutputLength(outBytes, outOff, len, "output buffer too short"); + + if (LimitExceeded((uint)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) + { + GenerateKeyStream(keyStream); + AdvanceCounter(); + } + outBytes[i+outOff] = (byte)(keyStream[index]^inBytes[i+inOff]); + index = (index + 1) & 63; + } + } + + public virtual void Reset() + { + index = 0; + ResetLimitCounter(); + ResetCounter(); + } + + protected virtual void ResetCounter() + { + engineState[8] = engineState[9] = 0; + } + + protected virtual void SetKey(byte[] keyBytes, byte[] ivBytes) + { + if (keyBytes != null) + { + if ((keyBytes.Length != 16) && (keyBytes.Length != 32)) + throw new ArgumentException(AlgorithmName + " requires 128 bit or 256 bit key"); + + int tsOff = (keyBytes.Length - 16) / 4; + engineState[0] = TAU_SIGMA[tsOff]; + engineState[5] = TAU_SIGMA[tsOff + 1]; + engineState[10] = TAU_SIGMA[tsOff + 2]; + engineState[15] = TAU_SIGMA[tsOff + 3]; + + // Key + Pack.LE_To_UInt32(keyBytes, 0, engineState, 1, 4); + Pack.LE_To_UInt32(keyBytes, keyBytes.Length - 16, engineState, 11, 4); + } + + // IV + Pack.LE_To_UInt32(ivBytes, 0, engineState, 6, 2); + } + + protected virtual void GenerateKeyStream(byte[] output) + { + SalsaCore(rounds, engineState, x); + Pack.UInt32_To_LE(x, output, 0); + } + + internal static void SalsaCore(int rounds, uint[] input, uint[] x) + { + if (input.Length != 16) + throw new ArgumentException(); + if (x.Length != 16) + throw new ArgumentException(); + if (rounds % 2 != 0) + throw new ArgumentException("Number of rounds must be even"); + + uint x00 = input[ 0]; + uint x01 = input[ 1]; + uint x02 = input[ 2]; + uint x03 = input[ 3]; + uint x04 = input[ 4]; + uint x05 = input[ 5]; + uint x06 = input[ 6]; + uint x07 = input[ 7]; + uint x08 = input[ 8]; + uint x09 = input[ 9]; + uint x10 = input[10]; + uint x11 = input[11]; + uint x12 = input[12]; + uint x13 = input[13]; + uint x14 = input[14]; + uint x15 = input[15]; + + for (int i = rounds; i > 0; i -= 2) + { + x04 ^= R((x00+x12), 7); + x08 ^= R((x04+x00), 9); + x12 ^= R((x08+x04),13); + x00 ^= R((x12+x08),18); + x09 ^= R((x05+x01), 7); + x13 ^= R((x09+x05), 9); + x01 ^= R((x13+x09),13); + x05 ^= R((x01+x13),18); + x14 ^= R((x10+x06), 7); + x02 ^= R((x14+x10), 9); + x06 ^= R((x02+x14),13); + x10 ^= R((x06+x02),18); + x03 ^= R((x15+x11), 7); + x07 ^= R((x03+x15), 9); + x11 ^= R((x07+x03),13); + x15 ^= R((x11+x07),18); + + x01 ^= R((x00+x03), 7); + x02 ^= R((x01+x00), 9); + x03 ^= R((x02+x01),13); + x00 ^= R((x03+x02),18); + x06 ^= R((x05+x04), 7); + x07 ^= R((x06+x05), 9); + x04 ^= R((x07+x06),13); + x05 ^= R((x04+x07),18); + x11 ^= R((x10+x09), 7); + x08 ^= R((x11+x10), 9); + x09 ^= R((x08+x11),13); + x10 ^= R((x09+x08),18); + x12 ^= R((x15+x14), 7); + x13 ^= R((x12+x15), 9); + x14 ^= R((x13+x12),13); + x15 ^= R((x14+x13),18); + } + + x[ 0] = x00 + input[ 0]; + x[ 1] = x01 + input[ 1]; + x[ 2] = x02 + input[ 2]; + x[ 3] = x03 + input[ 3]; + x[ 4] = x04 + input[ 4]; + x[ 5] = x05 + input[ 5]; + x[ 6] = x06 + input[ 6]; + x[ 7] = x07 + input[ 7]; + x[ 8] = x08 + input[ 8]; + x[ 9] = x09 + input[ 9]; + x[10] = x10 + input[10]; + x[11] = x11 + input[11]; + x[12] = x12 + input[12]; + x[13] = x13 + input[13]; + x[14] = x14 + input[14]; + x[15] = x15 + input[15]; + } + + /** + * Rotate left + * + * @param x value to rotate + * @param y amount to rotate x + * + * @return rotated x + */ + internal static uint R(uint x, int y) + { + return (x << y) | (x >> (32 - y)); + } + + private void ResetLimitCounter() + { + cW0 = 0; + cW1 = 0; + cW2 = 0; + } + + private bool LimitExceeded() + { + if (++cW0 == 0) + { + if (++cW1 == 0) + { + return (++cW2 & 0x20) != 0; // 2^(32 + 32 + 6) + } + } + + return false; + } + + /* + * this relies on the fact len will always be positive. + */ + private bool LimitExceeded( + uint len) + { + uint old = cW0; + cW0 += len; + if (cW0 < old) + { + if (++cW1 == 0) + { + return (++cW2 & 0x20) != 0; // 2^(32 + 32 + 6) + } + } + + return false; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/engines/SerpentEngine.cs b/bc-sharp-crypto/src/crypto/engines/SerpentEngine.cs new file mode 100644 index 0000000..76799f0 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/SerpentEngine.cs @@ -0,0 +1,292 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Utilities; + +namespace Org.BouncyCastle.Crypto.Engines +{ + /** + * 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 Serpent home page + *

+ */ + public sealed class SerpentEngine + : SerpentEngineBase + { + /** + * Expand a user-supplied key material into a session key. + * + * @param key The user-key bytes (multiples of 4) to use. + * @exception ArgumentException + */ + protected override int[] MakeWorkingKey(byte[] key) + { + // + // pad key to 256 bits + // + int[] kPad = new int[16]; + int off = 0; + int length = 0; + + for (off = 0; (off + 4) < key.Length; off += 4) + { + kPad[length++] = (int)Pack.LE_To_UInt32(key, off); + } + + if (off % 4 == 0) + { + kPad[length++] = (int)Pack.LE_To_UInt32(key, off); + if (length < 8) + { + kPad[length] = 1; + } + } + else + { + throw new ArgumentException("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); + } + + Array.Copy(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; + } + + /** + * Encrypt one block of plaintext. + * + * @param input the array containing the input data. + * @param inOff offset into the in array the data starts at. + * @param output the array the output data will be copied into. + * @param outOff the offset into the out array the output will start at. + */ + protected override void EncryptBlock(byte[] input, int inOff, byte[] output, int outOff) + { + X0 = (int)Pack.LE_To_UInt32(input, inOff); + X1 = (int)Pack.LE_To_UInt32(input, inOff + 4); + X2 = (int)Pack.LE_To_UInt32(input, inOff + 8); + X3 = (int)Pack.LE_To_UInt32(input, 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); + + Pack.UInt32_To_LE((uint)(wKey[128] ^ X0), output, outOff); + Pack.UInt32_To_LE((uint)(wKey[129] ^ X1), output, outOff + 4); + Pack.UInt32_To_LE((uint)(wKey[130] ^ X2), output, outOff + 8); + Pack.UInt32_To_LE((uint)(wKey[131] ^ X3), output, outOff + 12); + } + + /** + * Decrypt one block of ciphertext. + * + * @param input the array containing the input data. + * @param inOff offset into the in array the data starts at. + * @param output the array the output data will be copied into. + * @param outOff the offset into the out array the output will start at. + */ + protected override void DecryptBlock(byte[] input, int inOff, byte[] output, int outOff) + { + X0 = wKey[128] ^ (int)Pack.LE_To_UInt32(input, inOff); + X1 = wKey[129] ^ (int)Pack.LE_To_UInt32(input, inOff + 4); + X2 = wKey[130] ^ (int)Pack.LE_To_UInt32(input, inOff + 8); + X3 = wKey[131] ^ (int)Pack.LE_To_UInt32(input, 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); + + Pack.UInt32_To_LE((uint)(X0 ^ wKey[0]), output, outOff); + Pack.UInt32_To_LE((uint)(X1 ^ wKey[1]), output, outOff + 4); + Pack.UInt32_To_LE((uint)(X2 ^ wKey[2]), output, outOff + 8); + Pack.UInt32_To_LE((uint)(X3 ^ wKey[3]), output, outOff + 12); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/engines/SerpentEngineBase.cs b/bc-sharp-crypto/src/crypto/engines/SerpentEngineBase.cs new file mode 100644 index 0000000..9de5522 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/SerpentEngineBase.cs @@ -0,0 +1,469 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Engines +{ + public abstract class SerpentEngineBase + : IBlockCipher + { + protected static readonly int BlockSize = 16; + + internal const int ROUNDS = 32; + internal const int PHI = unchecked((int)0x9E3779B9); // (sqrt(5) - 1) * 2**31 + + protected bool encrypting; + protected int[] wKey; + + protected int X0, X1, X2, X3; // registers + + protected SerpentEngineBase() + { + } + + /** + * initialise a Serpent cipher. + * + * @param encrypting 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 virtual void Init(bool encrypting, ICipherParameters parameters) + { + if (!(parameters is KeyParameter)) + throw new ArgumentException("invalid parameter passed to " + AlgorithmName + " init - " + Platform.GetTypeName(parameters)); + + this.encrypting = encrypting; + this.wKey = MakeWorkingKey(((KeyParameter)parameters).GetKey()); + } + + public virtual string AlgorithmName + { + get { return "Serpent"; } + } + + public virtual bool IsPartialBlockOkay + { + get { return false; } + } + + public virtual 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. + * @return the number of bytes processed and produced. + * @throws DataLengthException if there isn't enough data in in, or + * space in out. + * @throws IllegalStateException if the cipher isn't initialised. + */ + public int ProcessBlock(byte[] input, int inOff, byte[] output, int outOff) + { + if (wKey == null) + throw new InvalidOperationException(AlgorithmName + " not initialised"); + + Check.DataLength(input, inOff, BlockSize, "input buffer too short"); + Check.OutputLength(output, outOff, BlockSize, "output buffer too short"); + + if (encrypting) + { + EncryptBlock(input, inOff, output, outOff); + } + else + { + DecryptBlock(input, inOff, output, outOff); + } + + return BlockSize; + } + + public virtual void Reset() + { + } + + protected static int RotateLeft(int x, int bits) + { + return ((x << bits) | (int) ((uint)x >> (32 - bits))); + } + + private static int RotateRight(int x, int bits) + { + return ( (int)((uint)x >> bits) | (x << (32 - bits))); + } + + /* + * 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. + */ + protected 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. + */ + protected 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. + */ + protected 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. + */ + protected 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. + */ + protected 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. + */ + protected 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. + */ + protected 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 + */ + protected 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. + */ + protected 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. + */ + protected 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. + */ + protected 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. + */ + protected 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. + */ + protected 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. + */ + protected 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. + */ + protected 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. + */ + protected 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. + */ + protected 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. + */ + protected 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); + } + + protected abstract int[] MakeWorkingKey(byte[] key); + + protected abstract void EncryptBlock(byte[] input, int inOff, byte[] output, int outOff); + + protected abstract void DecryptBlock(byte[] input, int inOff, byte[] output, int outOff); + } +} diff --git a/bc-sharp-crypto/src/crypto/engines/SkipjackEngine.cs b/bc-sharp-crypto/src/crypto/engines/SkipjackEngine.cs new file mode 100644 index 0000000..c90646c --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/SkipjackEngine.cs @@ -0,0 +1,254 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Engines +{ + /** + * a class that provides a basic SKIPJACK engine. + */ + public class SkipjackEngine + : IBlockCipher + { + const int BLOCK_SIZE = 8; + + static readonly 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 bool encrypting; + + /** + * initialise a SKIPJACK cipher. + * + * @param forEncryption whether or not we are for encryption. + * @param parameters the parameters required to set up the cipher. + * @exception ArgumentException if the parameters argument is + * inappropriate. + */ + public virtual void Init( + bool forEncryption, + ICipherParameters parameters) + { + if (!(parameters is KeyParameter)) + throw new ArgumentException("invalid parameter passed to SKIPJACK init - " + Platform.GetTypeName(parameters)); + + byte[] keyBytes = ((KeyParameter)parameters).GetKey(); + + this.encrypting = forEncryption; + 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 virtual string AlgorithmName + { + get { return "SKIPJACK"; } + } + + public virtual bool IsPartialBlockOkay + { + get { return false; } + } + + public virtual int GetBlockSize() + { + return BLOCK_SIZE; + } + + public virtual int ProcessBlock( + byte[] input, + int inOff, + byte[] output, + int outOff) + { + if (key1 == null) + throw new InvalidOperationException("SKIPJACK engine not initialised"); + + Check.DataLength(input, inOff, BLOCK_SIZE, "input buffer too short"); + Check.OutputLength(output, outOff, BLOCK_SIZE, "output buffer too short"); + + if (encrypting) + { + EncryptBlock(input, inOff, output, outOff); + } + else + { + DecryptBlock(input, inOff, output, outOff); + } + + return BLOCK_SIZE; + } + + public virtual 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 virtual int EncryptBlock( + byte[] input, + int inOff, + byte[] outBytes, + int outOff) + { + int w1 = (input[inOff + 0] << 8) + (input[inOff + 1] & 0xff); + int w2 = (input[inOff + 2] << 8) + (input[inOff + 3] & 0xff); + int w3 = (input[inOff + 4] << 8) + (input[inOff + 5] & 0xff); + int w4 = (input[inOff + 6] << 8) + (input[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++; + } + } + + outBytes[outOff + 0] = (byte)((w1 >> 8)); + outBytes[outOff + 1] = (byte)(w1); + outBytes[outOff + 2] = (byte)((w2 >> 8)); + outBytes[outOff + 3] = (byte)(w2); + outBytes[outOff + 4] = (byte)((w3 >> 8)); + outBytes[outOff + 5] = (byte)(w3); + outBytes[outOff + 6] = (byte)((w4 >> 8)); + outBytes[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 virtual int DecryptBlock( + byte[] input, + int inOff, + byte[] outBytes, + int outOff) + { + int w2 = (input[inOff + 0] << 8) + (input[inOff + 1] & 0xff); + int w1 = (input[inOff + 2] << 8) + (input[inOff + 3] & 0xff); + int w4 = (input[inOff + 4] << 8) + (input[inOff + 5] & 0xff); + int w3 = (input[inOff + 6] << 8) + (input[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--; + } + } + + outBytes[outOff + 0] = (byte)((w2 >> 8)); + outBytes[outOff + 1] = (byte)(w2); + outBytes[outOff + 2] = (byte)((w1 >> 8)); + outBytes[outOff + 3] = (byte)(w1); + outBytes[outOff + 4] = (byte)((w4 >> 8)); + outBytes[outOff + 5] = (byte)(w4); + outBytes[outOff + 6] = (byte)((w3 >> 8)); + outBytes[outOff + 7] = (byte)(w3); + + return BLOCK_SIZE; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/engines/TEAEngine.cs b/bc-sharp-crypto/src/crypto/engines/TEAEngine.cs new file mode 100644 index 0000000..7b70014 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/TEAEngine.cs @@ -0,0 +1,166 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Utilities; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Engines +{ + /** + * An TEA engine. + */ + public class TeaEngine + : IBlockCipher + { + private const int + rounds = 32, + block_size = 8; +// key_size = 16, + + private const uint + delta = 0x9E3779B9, + d_sum = 0xC6EF3720; // sum on decrypt + + /* + * the expanded key array of 4 subkeys + */ + private uint _a, _b, _c, _d; + private bool _initialised; + private bool _forEncryption; + + /** + * Create an instance of the TEA encryption algorithm + * and set some defaults + */ + public TeaEngine() + { + _initialised = false; + } + + public virtual string AlgorithmName + { + get { return "TEA"; } + } + + public virtual bool IsPartialBlockOkay + { + get { return false; } + } + + public virtual 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 ArgumentException if the params argument is + * inappropriate. + */ + public virtual void Init( + bool forEncryption, + ICipherParameters parameters) + { + if (!(parameters is KeyParameter)) + { + throw new ArgumentException("invalid parameter passed to TEA init - " + + Platform.GetTypeName(parameters)); + } + + _forEncryption = forEncryption; + _initialised = true; + + KeyParameter p = (KeyParameter) parameters; + + setKey(p.GetKey()); + } + + public virtual int ProcessBlock( + byte[] inBytes, + int inOff, + byte[] outBytes, + int outOff) + { + if (!_initialised) + throw new InvalidOperationException(AlgorithmName + " not initialised"); + + Check.DataLength(inBytes, inOff, block_size, "input buffer too short"); + Check.OutputLength(outBytes, outOff, block_size, "output buffer too short"); + + return _forEncryption + ? encryptBlock(inBytes, inOff, outBytes, outOff) + : decryptBlock(inBytes, inOff, outBytes, outOff); + } + + public virtual void Reset() + { + } + + /** + * Re-key the cipher. + * + * @param key the key to be used + */ + private void setKey( + byte[] key) + { + _a = Pack.BE_To_UInt32(key, 0); + _b = Pack.BE_To_UInt32(key, 4); + _c = Pack.BE_To_UInt32(key, 8); + _d = Pack.BE_To_UInt32(key, 12); + } + + private int encryptBlock( + byte[] inBytes, + int inOff, + byte[] outBytes, + int outOff) + { + // Pack bytes into integers + uint v0 = Pack.BE_To_UInt32(inBytes, inOff); + uint v1 = Pack.BE_To_UInt32(inBytes, inOff + 4); + + uint 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); + } + + Pack.UInt32_To_BE(v0, outBytes, outOff); + Pack.UInt32_To_BE(v1, outBytes, outOff + 4); + + return block_size; + } + + private int decryptBlock( + byte[] inBytes, + int inOff, + byte[] outBytes, + int outOff) + { + // Pack bytes into integers + uint v0 = Pack.BE_To_UInt32(inBytes, inOff); + uint v1 = Pack.BE_To_UInt32(inBytes, inOff + 4); + + uint 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; + } + + Pack.UInt32_To_BE(v0, outBytes, outOff); + Pack.UInt32_To_BE(v1, outBytes, outOff + 4); + + return block_size; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/engines/ThreefishEngine.cs b/bc-sharp-crypto/src/crypto/engines/ThreefishEngine.cs new file mode 100644 index 0000000..eade3cc --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/ThreefishEngine.cs @@ -0,0 +1,1491 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Utilities; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Crypto.Engines +{ + /// + /// Implementation of the Threefish tweakable large block cipher in 256, 512 and 1024 bit block + /// sizes. + /// + /// + /// This is the 1.3 version of Threefish defined in the Skein hash function submission to the NIST + /// SHA-3 competition in October 2010. + ///

+ /// Threefish was designed by Niels Ferguson - Stefan Lucks - Bruce Schneier - Doug Whiting - Mihir + /// Bellare - Tadayoshi Kohno - Jon Callas - Jesse Walker. + ///

+ /// This implementation inlines all round functions, unrolls 8 rounds, and uses 1.2k of static tables + /// to speed up key schedule injection.
+ /// 2 x block size state is retained by each cipher instance. + /// + public class ThreefishEngine + : IBlockCipher + { + ///

+ /// 256 bit block size - Threefish-256 + /// + public const int BLOCKSIZE_256 = 256; + /// + /// 512 bit block size - Threefish-512 + /// + public const int BLOCKSIZE_512 = 512; + /// + /// 1024 bit block size - Threefish-1024 + /// + public const int BLOCKSIZE_1024 = 1024; + + /** + * Size of the tweak in bytes (always 128 bit/16 bytes) + */ + private const int TWEAK_SIZE_BYTES = 16; + private const int TWEAK_SIZE_WORDS = TWEAK_SIZE_BYTES / 8; + + /** + * Rounds in Threefish-256 + */ + private const int ROUNDS_256 = 72; + /** + * Rounds in Threefish-512 + */ + private const int ROUNDS_512 = 72; + /** + * Rounds in Threefish-1024 + */ + private const int ROUNDS_1024 = 80; + + /** + * Max rounds of any of the variants + */ + private const int MAX_ROUNDS = ROUNDS_1024; + + /** + * Key schedule parity constant + */ + private const ulong C_240 = 0x1BD11BDAA9FC1A22L; + + /* Pre-calculated modulo arithmetic tables for key schedule lookups */ + private static readonly int[] MOD9 = new int[MAX_ROUNDS]; + private static readonly int[] MOD17 = new int[MOD9.Length]; + private static readonly int[] MOD5 = new int[MOD9.Length]; + private static readonly int[] MOD3 = new int[MOD9.Length]; + + static ThreefishEngine() + { + for (int i = 0; i < MOD9.Length; i++) + { + MOD17[i] = i % 17; + MOD9[i] = i % 9; + MOD5[i] = i % 5; + MOD3[i] = i % 3; + } + } + + /** + * Block size in bytes + */ + private readonly int blocksizeBytes; + + /** + * Block size in 64 bit words + */ + private readonly int blocksizeWords; + + /** + * Buffer for byte oriented processBytes to call internal word API + */ + private readonly ulong[] currentBlock; + + /** + * Tweak bytes (2 byte t1,t2, calculated t3 and repeat of t1,t2 for modulo free lookup + */ + private readonly ulong[] t = new ulong[5]; + + /** + * Key schedule words + */ + private readonly ulong[] kw; + + /** + * The internal cipher implementation (varies by blocksize) + */ + private readonly ThreefishCipher cipher; + + private bool forEncryption; + + /// + /// Constructs a new Threefish cipher, with a specified block size. + /// + /// the block size in bits, one of , , + /// . + public ThreefishEngine(int blocksizeBits) + { + this.blocksizeBytes = (blocksizeBits / 8); + this.blocksizeWords = (this.blocksizeBytes / 8); + this.currentBlock = new ulong[blocksizeWords]; + + /* + * Provide room for original key words, extended key word and repeat of key words for modulo + * free lookup of key schedule words. + */ + this.kw = new ulong[2 * blocksizeWords + 1]; + + switch (blocksizeBits) + { + case BLOCKSIZE_256: + cipher = new Threefish256Cipher(kw, t); + break; + case BLOCKSIZE_512: + cipher = new Threefish512Cipher(kw, t); + break; + case BLOCKSIZE_1024: + cipher = new Threefish1024Cipher(kw, t); + break; + default: + throw new ArgumentException( + "Invalid blocksize - Threefish is defined with block size of 256, 512, or 1024 bits"); + } + } + + /// + /// Initialise the engine. + /// + /// Initialise for encryption if true, for decryption if false. + /// an instance of or (to + /// use a 0 tweak) + public virtual void Init(bool forEncryption, ICipherParameters parameters) + { + byte[] keyBytes; + byte[] tweakBytes; + + if (parameters is TweakableBlockCipherParameters) + { + TweakableBlockCipherParameters tParams = (TweakableBlockCipherParameters)parameters; + keyBytes = tParams.Key.GetKey(); + tweakBytes = tParams.Tweak; + } + else if (parameters is KeyParameter) + { + keyBytes = ((KeyParameter)parameters).GetKey(); + tweakBytes = null; + } + else + { + throw new ArgumentException("Invalid parameter passed to Threefish init - " + + Platform.GetTypeName(parameters)); + } + + ulong[] keyWords = null; + ulong[] tweakWords = null; + + if (keyBytes != null) + { + if (keyBytes.Length != this.blocksizeBytes) + { + throw new ArgumentException("Threefish key must be same size as block (" + blocksizeBytes + + " bytes)"); + } + keyWords = new ulong[blocksizeWords]; + for (int i = 0; i < keyWords.Length; i++) + { + keyWords[i] = BytesToWord(keyBytes, i * 8); + } + } + if (tweakBytes != null) + { + if (tweakBytes.Length != TWEAK_SIZE_BYTES) + { + throw new ArgumentException("Threefish tweak must be " + TWEAK_SIZE_BYTES + " bytes"); + } + tweakWords = new ulong[]{BytesToWord(tweakBytes, 0), BytesToWord(tweakBytes, 8)}; + } + Init(forEncryption, keyWords, tweakWords); + } + + /// + /// Initialise the engine, specifying the key and tweak directly. + /// + /// the cipher mode. + /// the words of the key, or null to use the current key. + /// the 2 word (128 bit) tweak, or null to use the current tweak. + internal void Init(bool forEncryption, ulong[] key, ulong[] tweak) + { + this.forEncryption = forEncryption; + if (key != null) + { + SetKey(key); + } + if (tweak != null) + { + SetTweak(tweak); + } + } + + private void SetKey(ulong[] key) + { + if (key.Length != this.blocksizeWords) + { + throw new ArgumentException("Threefish key must be same size as block (" + blocksizeWords + + " words)"); + } + + /* + * Full subkey schedule is deferred to execution to avoid per cipher overhead (10k for 512, + * 20k for 1024). + * + * Key and tweak word sequences are repeated, and static MOD17/MOD9/MOD5/MOD3 calculations + * used, to avoid expensive mod computations during cipher operation. + */ + + ulong knw = C_240; + for (int i = 0; i < blocksizeWords; i++) + { + kw[i] = key[i]; + knw = knw ^ kw[i]; + } + kw[blocksizeWords] = knw; + Array.Copy(kw, 0, kw, blocksizeWords + 1, blocksizeWords); + } + + private void SetTweak(ulong[] tweak) + { + if (tweak.Length != TWEAK_SIZE_WORDS) + { + throw new ArgumentException("Tweak must be " + TWEAK_SIZE_WORDS + " words."); + } + + /* + * Tweak schedule partially repeated to avoid mod computations during cipher operation + */ + t[0] = tweak[0]; + t[1] = tweak[1]; + t[2] = t[0] ^ t[1]; + t[3] = t[0]; + t[4] = t[1]; + } + + public virtual string AlgorithmName + { + get { return "Threefish-" + (blocksizeBytes * 8); } + } + + public virtual bool IsPartialBlockOkay + { + get { return false; } + } + + public virtual int GetBlockSize() + { + return blocksizeBytes; + } + + public virtual void Reset() + { + } + + public virtual int ProcessBlock(byte[] inBytes, int inOff, byte[] outBytes, int outOff) + { + if ((outOff + blocksizeBytes) > outBytes.Length) + { + throw new DataLengthException("Output buffer too short"); + } + + if ((inOff + blocksizeBytes) > inBytes.Length) + { + throw new DataLengthException("Input buffer too short"); + } + + for (int i = 0; i < blocksizeBytes; i += 8) + { + currentBlock[i >> 3] = BytesToWord(inBytes, inOff + i); + } + ProcessBlock(this.currentBlock, this.currentBlock); + for (int i = 0; i < blocksizeBytes; i += 8) + { + WordToBytes(this.currentBlock[i >> 3], outBytes, outOff + i); + } + + return blocksizeBytes; + } + + /// + /// Process a block of data represented as 64 bit words. + /// + /// the number of 8 byte words processed (which will be the same as the block size). + /// a block sized buffer of words to process. + /// a block sized buffer of words to receive the output of the operation. + /// if either the input or output is not block sized + /// if this engine is not initialised + internal int ProcessBlock(ulong[] inWords, ulong[] outWords) + { + if (kw[blocksizeWords] == 0) + { + throw new InvalidOperationException("Threefish engine not initialised"); + } + + if (inWords.Length != blocksizeWords) + { + throw new DataLengthException("Input buffer too short"); + } + if (outWords.Length != blocksizeWords) + { + throw new DataLengthException("Output buffer too short"); + } + + if (forEncryption) + { + cipher.EncryptBlock(inWords, outWords); + } + else + { + cipher.DecryptBlock(inWords, outWords); + } + + return blocksizeWords; + } + + /// + /// Read a single 64 bit word from input in LSB first order. + /// + internal static ulong BytesToWord(byte[] bytes, int off) + { + if ((off + 8) > bytes.Length) + { + // Help the JIT avoid index checks + throw new ArgumentException(); + } + + ulong word = 0; + int index = off; + + word = (bytes[index++] & 0xffUL); + word |= (bytes[index++] & 0xffUL) << 8; + word |= (bytes[index++] & 0xffUL) << 16; + word |= (bytes[index++] & 0xffUL) << 24; + word |= (bytes[index++] & 0xffUL) << 32; + word |= (bytes[index++] & 0xffUL) << 40; + word |= (bytes[index++] & 0xffUL) << 48; + word |= (bytes[index++] & 0xffUL) << 56; + + return word; + } + + /// + /// Write a 64 bit word to output in LSB first order. + /// + internal static void WordToBytes(ulong word, byte[] bytes, int off) + { + if ((off + 8) > bytes.Length) + { + // Help the JIT avoid index checks + throw new ArgumentException(); + } + int index = off; + + bytes[index++] = (byte)word; + bytes[index++] = (byte)(word >> 8); + bytes[index++] = (byte)(word >> 16); + bytes[index++] = (byte)(word >> 24); + bytes[index++] = (byte)(word >> 32); + bytes[index++] = (byte)(word >> 40); + bytes[index++] = (byte)(word >> 48); + bytes[index++] = (byte)(word >> 56); + } + + /** + * Rotate left + xor part of the mix operation. + */ + private static ulong RotlXor(ulong x, int n, ulong xor) + { + return ((x << n) | (x >> (64 - n))) ^ xor; + } + + /** + * Rotate xor + rotate right part of the unmix operation. + */ + private static ulong XorRotr(ulong x, int n, ulong xor) + { + ulong xored = x ^ xor; + return (xored >> n) | (xored << (64 - n)); + } + + private abstract class ThreefishCipher + { + /** + * The extended + repeated tweak words + */ + protected readonly ulong[] t; + /** + * The extended + repeated key words + */ + protected readonly ulong[] kw; + + protected ThreefishCipher(ulong[] kw, ulong[] t) + { + this.kw = kw; + this.t = t; + } + + internal abstract void EncryptBlock(ulong[] block, ulong[] outWords); + + internal abstract void DecryptBlock(ulong[] block, ulong[] outWords); + + } + + private sealed class Threefish256Cipher + : ThreefishCipher + { + /** + * Mix rotation constants defined in Skein 1.3 specification + */ + private const int ROTATION_0_0 = 14, ROTATION_0_1 = 16; + private const int ROTATION_1_0 = 52, ROTATION_1_1 = 57; + private const int ROTATION_2_0 = 23, ROTATION_2_1 = 40; + private const int ROTATION_3_0 = 5, ROTATION_3_1 = 37; + + private const int ROTATION_4_0 = 25, ROTATION_4_1 = 33; + private const int ROTATION_5_0 = 46, ROTATION_5_1 = 12; + private const int ROTATION_6_0 = 58, ROTATION_6_1 = 22; + private const int ROTATION_7_0 = 32, ROTATION_7_1 = 32; + + public Threefish256Cipher(ulong[] kw, ulong[] t) + : base(kw, t) + { + } + + internal override void EncryptBlock(ulong[] block, ulong[] outWords) + { + ulong[] kw = this.kw; + ulong[] t = this.t; + int[] mod5 = MOD5; + int[] mod3 = MOD3; + + /* Help the JIT avoid index bounds checks */ + if (kw.Length != 9) + { + throw new ArgumentException(); + } + if (t.Length != 5) + { + throw new ArgumentException(); + } + + /* + * Read 4 words of plaintext data, not using arrays for cipher state + */ + ulong b0 = block[0]; + ulong b1 = block[1]; + ulong b2 = block[2]; + ulong b3 = block[3]; + + /* + * First subkey injection. + */ + b0 += kw[0]; + b1 += kw[1] + t[0]; + b2 += kw[2] + t[1]; + b3 += kw[3]; + + /* + * Rounds loop, unrolled to 8 rounds per iteration. + * + * Unrolling to multiples of 4 avoids the mod 4 check for key injection, and allows + * inlining of the permutations, which cycle every of 2 rounds (avoiding array + * index/lookup). + * + * Unrolling to multiples of 8 avoids the mod 8 rotation constant lookup, and allows + * inlining constant rotation values (avoiding array index/lookup). + */ + + for (int d = 1; d < (ROUNDS_256 / 4); d += 2) + { + int dm5 = mod5[d]; + int dm3 = mod3[d]; + + /* + * 4 rounds of mix and permute. + * + * Permute schedule has a 2 round cycle, so permutes are inlined in the mix + * operations in each 4 round block. + */ + b1 = RotlXor(b1, ROTATION_0_0, b0 += b1); + b3 = RotlXor(b3, ROTATION_0_1, b2 += b3); + + b3 = RotlXor(b3, ROTATION_1_0, b0 += b3); + b1 = RotlXor(b1, ROTATION_1_1, b2 += b1); + + b1 = RotlXor(b1, ROTATION_2_0, b0 += b1); + b3 = RotlXor(b3, ROTATION_2_1, b2 += b3); + + b3 = RotlXor(b3, ROTATION_3_0, b0 += b3); + b1 = RotlXor(b1, ROTATION_3_1, b2 += b1); + + /* + * Subkey injection for first 4 rounds. + */ + b0 += kw[dm5]; + b1 += kw[dm5 + 1] + t[dm3]; + b2 += kw[dm5 + 2] + t[dm3 + 1]; + b3 += kw[dm5 + 3] + (uint)d; + + /* + * 4 more rounds of mix/permute + */ + b1 = RotlXor(b1, ROTATION_4_0, b0 += b1); + b3 = RotlXor(b3, ROTATION_4_1, b2 += b3); + + b3 = RotlXor(b3, ROTATION_5_0, b0 += b3); + b1 = RotlXor(b1, ROTATION_5_1, b2 += b1); + + b1 = RotlXor(b1, ROTATION_6_0, b0 += b1); + b3 = RotlXor(b3, ROTATION_6_1, b2 += b3); + + b3 = RotlXor(b3, ROTATION_7_0, b0 += b3); + b1 = RotlXor(b1, ROTATION_7_1, b2 += b1); + + /* + * Subkey injection for next 4 rounds. + */ + b0 += kw[dm5 + 1]; + b1 += kw[dm5 + 2] + t[dm3 + 1]; + b2 += kw[dm5 + 3] + t[dm3 + 2]; + b3 += kw[dm5 + 4] + (uint)d + 1; + } + + /* + * Output cipher state. + */ + outWords[0] = b0; + outWords[1] = b1; + outWords[2] = b2; + outWords[3] = b3; + } + + internal override void DecryptBlock(ulong[] block, ulong[] state) + { + ulong[] kw = this.kw; + ulong[] t = this.t; + int[] mod5 = MOD5; + int[] mod3 = MOD3; + + /* Help the JIT avoid index bounds checks */ + if (kw.Length != 9) + { + throw new ArgumentException(); + } + if (t.Length != 5) + { + throw new ArgumentException(); + } + + ulong b0 = block[0]; + ulong b1 = block[1]; + ulong b2 = block[2]; + ulong b3 = block[3]; + + for (int d = (ROUNDS_256 / 4) - 1; d >= 1; d -= 2) + { + int dm5 = mod5[d]; + int dm3 = mod3[d]; + + /* Reverse key injection for second 4 rounds */ + b0 -= kw[dm5 + 1]; + b1 -= kw[dm5 + 2] + t[dm3 + 1]; + b2 -= kw[dm5 + 3] + t[dm3 + 2]; + b3 -= kw[dm5 + 4] + (uint)d + 1; + + /* Reverse second 4 mix/permute rounds */ + + b3 = XorRotr(b3, ROTATION_7_0, b0); + b0 -= b3; + b1 = XorRotr(b1, ROTATION_7_1, b2); + b2 -= b1; + + b1 = XorRotr(b1, ROTATION_6_0, b0); + b0 -= b1; + b3 = XorRotr(b3, ROTATION_6_1, b2); + b2 -= b3; + + b3 = XorRotr(b3, ROTATION_5_0, b0); + b0 -= b3; + b1 = XorRotr(b1, ROTATION_5_1, b2); + b2 -= b1; + + b1 = XorRotr(b1, ROTATION_4_0, b0); + b0 -= b1; + b3 = XorRotr(b3, ROTATION_4_1, b2); + b2 -= b3; + + /* Reverse key injection for first 4 rounds */ + b0 -= kw[dm5]; + b1 -= kw[dm5 + 1] + t[dm3]; + b2 -= kw[dm5 + 2] + t[dm3 + 1]; + b3 -= kw[dm5 + 3] + (uint)d; + + /* Reverse first 4 mix/permute rounds */ + b3 = XorRotr(b3, ROTATION_3_0, b0); + b0 -= b3; + b1 = XorRotr(b1, ROTATION_3_1, b2); + b2 -= b1; + + b1 = XorRotr(b1, ROTATION_2_0, b0); + b0 -= b1; + b3 = XorRotr(b3, ROTATION_2_1, b2); + b2 -= b3; + + b3 = XorRotr(b3, ROTATION_1_0, b0); + b0 -= b3; + b1 = XorRotr(b1, ROTATION_1_1, b2); + b2 -= b1; + + b1 = XorRotr(b1, ROTATION_0_0, b0); + b0 -= b1; + b3 = XorRotr(b3, ROTATION_0_1, b2); + b2 -= b3; + } + + /* + * First subkey uninjection. + */ + b0 -= kw[0]; + b1 -= kw[1] + t[0]; + b2 -= kw[2] + t[1]; + b3 -= kw[3]; + + /* + * Output cipher state. + */ + state[0] = b0; + state[1] = b1; + state[2] = b2; + state[3] = b3; + } + + } + + private sealed class Threefish512Cipher + : ThreefishCipher + { + /** + * Mix rotation constants defined in Skein 1.3 specification + */ + private const int ROTATION_0_0 = 46, ROTATION_0_1 = 36, ROTATION_0_2 = 19, ROTATION_0_3 = 37; + private const int ROTATION_1_0 = 33, ROTATION_1_1 = 27, ROTATION_1_2 = 14, ROTATION_1_3 = 42; + private const int ROTATION_2_0 = 17, ROTATION_2_1 = 49, ROTATION_2_2 = 36, ROTATION_2_3 = 39; + private const int ROTATION_3_0 = 44, ROTATION_3_1 = 9, ROTATION_3_2 = 54, ROTATION_3_3 = 56; + + private const int ROTATION_4_0 = 39, ROTATION_4_1 = 30, ROTATION_4_2 = 34, ROTATION_4_3 = 24; + private const int ROTATION_5_0 = 13, ROTATION_5_1 = 50, ROTATION_5_2 = 10, ROTATION_5_3 = 17; + private const int ROTATION_6_0 = 25, ROTATION_6_1 = 29, ROTATION_6_2 = 39, ROTATION_6_3 = 43; + private const int ROTATION_7_0 = 8, ROTATION_7_1 = 35, ROTATION_7_2 = 56, ROTATION_7_3 = 22; + + internal Threefish512Cipher(ulong[] kw, ulong[] t) + : base(kw, t) + { + } + + internal override void EncryptBlock(ulong[] block, ulong[] outWords) + { + ulong[] kw = this.kw; + ulong[] t = this.t; + int[] mod9 = MOD9; + int[] mod3 = MOD3; + + /* Help the JIT avoid index bounds checks */ + if (kw.Length != 17) + { + throw new ArgumentException(); + } + if (t.Length != 5) + { + throw new ArgumentException(); + } + + /* + * Read 8 words of plaintext data, not using arrays for cipher state + */ + ulong b0 = block[0]; + ulong b1 = block[1]; + ulong b2 = block[2]; + ulong b3 = block[3]; + ulong b4 = block[4]; + ulong b5 = block[5]; + ulong b6 = block[6]; + ulong b7 = block[7]; + + /* + * First subkey injection. + */ + b0 += kw[0]; + b1 += kw[1]; + b2 += kw[2]; + b3 += kw[3]; + b4 += kw[4]; + b5 += kw[5] + t[0]; + b6 += kw[6] + t[1]; + b7 += kw[7]; + + /* + * Rounds loop, unrolled to 8 rounds per iteration. + * + * Unrolling to multiples of 4 avoids the mod 4 check for key injection, and allows + * inlining of the permutations, which cycle every of 4 rounds (avoiding array + * index/lookup). + * + * Unrolling to multiples of 8 avoids the mod 8 rotation constant lookup, and allows + * inlining constant rotation values (avoiding array index/lookup). + */ + + for (int d = 1; d < (ROUNDS_512 / 4); d += 2) + { + int dm9 = mod9[d]; + int dm3 = mod3[d]; + + /* + * 4 rounds of mix and permute. + * + * Permute schedule has a 4 round cycle, so permutes are inlined in the mix + * operations in each 4 round block. + */ + b1 = RotlXor(b1, ROTATION_0_0, b0 += b1); + b3 = RotlXor(b3, ROTATION_0_1, b2 += b3); + b5 = RotlXor(b5, ROTATION_0_2, b4 += b5); + b7 = RotlXor(b7, ROTATION_0_3, b6 += b7); + + b1 = RotlXor(b1, ROTATION_1_0, b2 += b1); + b7 = RotlXor(b7, ROTATION_1_1, b4 += b7); + b5 = RotlXor(b5, ROTATION_1_2, b6 += b5); + b3 = RotlXor(b3, ROTATION_1_3, b0 += b3); + + b1 = RotlXor(b1, ROTATION_2_0, b4 += b1); + b3 = RotlXor(b3, ROTATION_2_1, b6 += b3); + b5 = RotlXor(b5, ROTATION_2_2, b0 += b5); + b7 = RotlXor(b7, ROTATION_2_3, b2 += b7); + + b1 = RotlXor(b1, ROTATION_3_0, b6 += b1); + b7 = RotlXor(b7, ROTATION_3_1, b0 += b7); + b5 = RotlXor(b5, ROTATION_3_2, b2 += b5); + b3 = RotlXor(b3, ROTATION_3_3, b4 += b3); + + /* + * Subkey injection for first 4 rounds. + */ + b0 += kw[dm9]; + b1 += kw[dm9 + 1]; + b2 += kw[dm9 + 2]; + b3 += kw[dm9 + 3]; + b4 += kw[dm9 + 4]; + b5 += kw[dm9 + 5] + t[dm3]; + b6 += kw[dm9 + 6] + t[dm3 + 1]; + b7 += kw[dm9 + 7] + (uint)d; + + /* + * 4 more rounds of mix/permute + */ + b1 = RotlXor(b1, ROTATION_4_0, b0 += b1); + b3 = RotlXor(b3, ROTATION_4_1, b2 += b3); + b5 = RotlXor(b5, ROTATION_4_2, b4 += b5); + b7 = RotlXor(b7, ROTATION_4_3, b6 += b7); + + b1 = RotlXor(b1, ROTATION_5_0, b2 += b1); + b7 = RotlXor(b7, ROTATION_5_1, b4 += b7); + b5 = RotlXor(b5, ROTATION_5_2, b6 += b5); + b3 = RotlXor(b3, ROTATION_5_3, b0 += b3); + + b1 = RotlXor(b1, ROTATION_6_0, b4 += b1); + b3 = RotlXor(b3, ROTATION_6_1, b6 += b3); + b5 = RotlXor(b5, ROTATION_6_2, b0 += b5); + b7 = RotlXor(b7, ROTATION_6_3, b2 += b7); + + b1 = RotlXor(b1, ROTATION_7_0, b6 += b1); + b7 = RotlXor(b7, ROTATION_7_1, b0 += b7); + b5 = RotlXor(b5, ROTATION_7_2, b2 += b5); + b3 = RotlXor(b3, ROTATION_7_3, b4 += b3); + + /* + * Subkey injection for next 4 rounds. + */ + b0 += kw[dm9 + 1]; + b1 += kw[dm9 + 2]; + b2 += kw[dm9 + 3]; + b3 += kw[dm9 + 4]; + b4 += kw[dm9 + 5]; + b5 += kw[dm9 + 6] + t[dm3 + 1]; + b6 += kw[dm9 + 7] + t[dm3 + 2]; + b7 += kw[dm9 + 8] + (uint)d + 1; + } + + /* + * Output cipher state. + */ + outWords[0] = b0; + outWords[1] = b1; + outWords[2] = b2; + outWords[3] = b3; + outWords[4] = b4; + outWords[5] = b5; + outWords[6] = b6; + outWords[7] = b7; + } + + internal override void DecryptBlock(ulong[] block, ulong[] state) + { + ulong[] kw = this.kw; + ulong[] t = this.t; + int[] mod9 = MOD9; + int[] mod3 = MOD3; + + /* Help the JIT avoid index bounds checks */ + if (kw.Length != 17) + { + throw new ArgumentException(); + } + if (t.Length != 5) + { + throw new ArgumentException(); + } + + ulong b0 = block[0]; + ulong b1 = block[1]; + ulong b2 = block[2]; + ulong b3 = block[3]; + ulong b4 = block[4]; + ulong b5 = block[5]; + ulong b6 = block[6]; + ulong b7 = block[7]; + + for (int d = (ROUNDS_512 / 4) - 1; d >= 1; d -= 2) + { + int dm9 = mod9[d]; + int dm3 = mod3[d]; + + /* Reverse key injection for second 4 rounds */ + b0 -= kw[dm9 + 1]; + b1 -= kw[dm9 + 2]; + b2 -= kw[dm9 + 3]; + b3 -= kw[dm9 + 4]; + b4 -= kw[dm9 + 5]; + b5 -= kw[dm9 + 6] + t[dm3 + 1]; + b6 -= kw[dm9 + 7] + t[dm3 + 2]; + b7 -= kw[dm9 + 8] + (uint)d + 1; + + /* Reverse second 4 mix/permute rounds */ + + b1 = XorRotr(b1, ROTATION_7_0, b6); + b6 -= b1; + b7 = XorRotr(b7, ROTATION_7_1, b0); + b0 -= b7; + b5 = XorRotr(b5, ROTATION_7_2, b2); + b2 -= b5; + b3 = XorRotr(b3, ROTATION_7_3, b4); + b4 -= b3; + + b1 = XorRotr(b1, ROTATION_6_0, b4); + b4 -= b1; + b3 = XorRotr(b3, ROTATION_6_1, b6); + b6 -= b3; + b5 = XorRotr(b5, ROTATION_6_2, b0); + b0 -= b5; + b7 = XorRotr(b7, ROTATION_6_3, b2); + b2 -= b7; + + b1 = XorRotr(b1, ROTATION_5_0, b2); + b2 -= b1; + b7 = XorRotr(b7, ROTATION_5_1, b4); + b4 -= b7; + b5 = XorRotr(b5, ROTATION_5_2, b6); + b6 -= b5; + b3 = XorRotr(b3, ROTATION_5_3, b0); + b0 -= b3; + + b1 = XorRotr(b1, ROTATION_4_0, b0); + b0 -= b1; + b3 = XorRotr(b3, ROTATION_4_1, b2); + b2 -= b3; + b5 = XorRotr(b5, ROTATION_4_2, b4); + b4 -= b5; + b7 = XorRotr(b7, ROTATION_4_3, b6); + b6 -= b7; + + /* Reverse key injection for first 4 rounds */ + b0 -= kw[dm9]; + b1 -= kw[dm9 + 1]; + b2 -= kw[dm9 + 2]; + b3 -= kw[dm9 + 3]; + b4 -= kw[dm9 + 4]; + b5 -= kw[dm9 + 5] + t[dm3]; + b6 -= kw[dm9 + 6] + t[dm3 + 1]; + b7 -= kw[dm9 + 7] + (uint)d; + + /* Reverse first 4 mix/permute rounds */ + b1 = XorRotr(b1, ROTATION_3_0, b6); + b6 -= b1; + b7 = XorRotr(b7, ROTATION_3_1, b0); + b0 -= b7; + b5 = XorRotr(b5, ROTATION_3_2, b2); + b2 -= b5; + b3 = XorRotr(b3, ROTATION_3_3, b4); + b4 -= b3; + + b1 = XorRotr(b1, ROTATION_2_0, b4); + b4 -= b1; + b3 = XorRotr(b3, ROTATION_2_1, b6); + b6 -= b3; + b5 = XorRotr(b5, ROTATION_2_2, b0); + b0 -= b5; + b7 = XorRotr(b7, ROTATION_2_3, b2); + b2 -= b7; + + b1 = XorRotr(b1, ROTATION_1_0, b2); + b2 -= b1; + b7 = XorRotr(b7, ROTATION_1_1, b4); + b4 -= b7; + b5 = XorRotr(b5, ROTATION_1_2, b6); + b6 -= b5; + b3 = XorRotr(b3, ROTATION_1_3, b0); + b0 -= b3; + + b1 = XorRotr(b1, ROTATION_0_0, b0); + b0 -= b1; + b3 = XorRotr(b3, ROTATION_0_1, b2); + b2 -= b3; + b5 = XorRotr(b5, ROTATION_0_2, b4); + b4 -= b5; + b7 = XorRotr(b7, ROTATION_0_3, b6); + b6 -= b7; + } + + /* + * First subkey uninjection. + */ + b0 -= kw[0]; + b1 -= kw[1]; + b2 -= kw[2]; + b3 -= kw[3]; + b4 -= kw[4]; + b5 -= kw[5] + t[0]; + b6 -= kw[6] + t[1]; + b7 -= kw[7]; + + /* + * Output cipher state. + */ + state[0] = b0; + state[1] = b1; + state[2] = b2; + state[3] = b3; + state[4] = b4; + state[5] = b5; + state[6] = b6; + state[7] = b7; + } + } + + private sealed class Threefish1024Cipher + : ThreefishCipher + { + /** + * Mix rotation constants defined in Skein 1.3 specification + */ + private const int ROTATION_0_0 = 24, ROTATION_0_1 = 13, ROTATION_0_2 = 8, ROTATION_0_3 = 47; + private const int ROTATION_0_4 = 8, ROTATION_0_5 = 17, ROTATION_0_6 = 22, ROTATION_0_7 = 37; + private const int ROTATION_1_0 = 38, ROTATION_1_1 = 19, ROTATION_1_2 = 10, ROTATION_1_3 = 55; + private const int ROTATION_1_4 = 49, ROTATION_1_5 = 18, ROTATION_1_6 = 23, ROTATION_1_7 = 52; + private const int ROTATION_2_0 = 33, ROTATION_2_1 = 4, ROTATION_2_2 = 51, ROTATION_2_3 = 13; + private const int ROTATION_2_4 = 34, ROTATION_2_5 = 41, ROTATION_2_6 = 59, ROTATION_2_7 = 17; + private const int ROTATION_3_0 = 5, ROTATION_3_1 = 20, ROTATION_3_2 = 48, ROTATION_3_3 = 41; + private const int ROTATION_3_4 = 47, ROTATION_3_5 = 28, ROTATION_3_6 = 16, ROTATION_3_7 = 25; + + private const int ROTATION_4_0 = 41, ROTATION_4_1 = 9, ROTATION_4_2 = 37, ROTATION_4_3 = 31; + private const int ROTATION_4_4 = 12, ROTATION_4_5 = 47, ROTATION_4_6 = 44, ROTATION_4_7 = 30; + private const int ROTATION_5_0 = 16, ROTATION_5_1 = 34, ROTATION_5_2 = 56, ROTATION_5_3 = 51; + private const int ROTATION_5_4 = 4, ROTATION_5_5 = 53, ROTATION_5_6 = 42, ROTATION_5_7 = 41; + private const int ROTATION_6_0 = 31, ROTATION_6_1 = 44, ROTATION_6_2 = 47, ROTATION_6_3 = 46; + private const int ROTATION_6_4 = 19, ROTATION_6_5 = 42, ROTATION_6_6 = 44, ROTATION_6_7 = 25; + private const int ROTATION_7_0 = 9, ROTATION_7_1 = 48, ROTATION_7_2 = 35, ROTATION_7_3 = 52; + private const int ROTATION_7_4 = 23, ROTATION_7_5 = 31, ROTATION_7_6 = 37, ROTATION_7_7 = 20; + + public Threefish1024Cipher(ulong[] kw, ulong[] t) + : base(kw, t) + { + } + + internal override void EncryptBlock(ulong[] block, ulong[] outWords) + { + ulong[] kw = this.kw; + ulong[] t = this.t; + int[] mod17 = MOD17; + int[] mod3 = MOD3; + + /* Help the JIT avoid index bounds checks */ + if (kw.Length != 33) + { + throw new ArgumentException(); + } + if (t.Length != 5) + { + throw new ArgumentException(); + } + + /* + * Read 16 words of plaintext data, not using arrays for cipher state + */ + ulong b0 = block[0]; + ulong b1 = block[1]; + ulong b2 = block[2]; + ulong b3 = block[3]; + ulong b4 = block[4]; + ulong b5 = block[5]; + ulong b6 = block[6]; + ulong b7 = block[7]; + ulong b8 = block[8]; + ulong b9 = block[9]; + ulong b10 = block[10]; + ulong b11 = block[11]; + ulong b12 = block[12]; + ulong b13 = block[13]; + ulong b14 = block[14]; + ulong b15 = block[15]; + + /* + * First subkey injection. + */ + b0 += kw[0]; + b1 += kw[1]; + b2 += kw[2]; + b3 += kw[3]; + b4 += kw[4]; + b5 += kw[5]; + b6 += kw[6]; + b7 += kw[7]; + b8 += kw[8]; + b9 += kw[9]; + b10 += kw[10]; + b11 += kw[11]; + b12 += kw[12]; + b13 += kw[13] + t[0]; + b14 += kw[14] + t[1]; + b15 += kw[15]; + + /* + * Rounds loop, unrolled to 8 rounds per iteration. + * + * Unrolling to multiples of 4 avoids the mod 4 check for key injection, and allows + * inlining of the permutations, which cycle every of 4 rounds (avoiding array + * index/lookup). + * + * Unrolling to multiples of 8 avoids the mod 8 rotation constant lookup, and allows + * inlining constant rotation values (avoiding array index/lookup). + */ + + for (int d = 1; d < (ROUNDS_1024 / 4); d += 2) + { + int dm17 = mod17[d]; + int dm3 = mod3[d]; + + /* + * 4 rounds of mix and permute. + * + * Permute schedule has a 4 round cycle, so permutes are inlined in the mix + * operations in each 4 round block. + */ + b1 = RotlXor(b1, ROTATION_0_0, b0 += b1); + b3 = RotlXor(b3, ROTATION_0_1, b2 += b3); + b5 = RotlXor(b5, ROTATION_0_2, b4 += b5); + b7 = RotlXor(b7, ROTATION_0_3, b6 += b7); + b9 = RotlXor(b9, ROTATION_0_4, b8 += b9); + b11 = RotlXor(b11, ROTATION_0_5, b10 += b11); + b13 = RotlXor(b13, ROTATION_0_6, b12 += b13); + b15 = RotlXor(b15, ROTATION_0_7, b14 += b15); + + b9 = RotlXor(b9, ROTATION_1_0, b0 += b9); + b13 = RotlXor(b13, ROTATION_1_1, b2 += b13); + b11 = RotlXor(b11, ROTATION_1_2, b6 += b11); + b15 = RotlXor(b15, ROTATION_1_3, b4 += b15); + b7 = RotlXor(b7, ROTATION_1_4, b10 += b7); + b3 = RotlXor(b3, ROTATION_1_5, b12 += b3); + b5 = RotlXor(b5, ROTATION_1_6, b14 += b5); + b1 = RotlXor(b1, ROTATION_1_7, b8 += b1); + + b7 = RotlXor(b7, ROTATION_2_0, b0 += b7); + b5 = RotlXor(b5, ROTATION_2_1, b2 += b5); + b3 = RotlXor(b3, ROTATION_2_2, b4 += b3); + b1 = RotlXor(b1, ROTATION_2_3, b6 += b1); + b15 = RotlXor(b15, ROTATION_2_4, b12 += b15); + b13 = RotlXor(b13, ROTATION_2_5, b14 += b13); + b11 = RotlXor(b11, ROTATION_2_6, b8 += b11); + b9 = RotlXor(b9, ROTATION_2_7, b10 += b9); + + b15 = RotlXor(b15, ROTATION_3_0, b0 += b15); + b11 = RotlXor(b11, ROTATION_3_1, b2 += b11); + b13 = RotlXor(b13, ROTATION_3_2, b6 += b13); + b9 = RotlXor(b9, ROTATION_3_3, b4 += b9); + b1 = RotlXor(b1, ROTATION_3_4, b14 += b1); + b5 = RotlXor(b5, ROTATION_3_5, b8 += b5); + b3 = RotlXor(b3, ROTATION_3_6, b10 += b3); + b7 = RotlXor(b7, ROTATION_3_7, b12 += b7); + + /* + * Subkey injection for first 4 rounds. + */ + b0 += kw[dm17]; + b1 += kw[dm17 + 1]; + b2 += kw[dm17 + 2]; + b3 += kw[dm17 + 3]; + b4 += kw[dm17 + 4]; + b5 += kw[dm17 + 5]; + b6 += kw[dm17 + 6]; + b7 += kw[dm17 + 7]; + b8 += kw[dm17 + 8]; + b9 += kw[dm17 + 9]; + b10 += kw[dm17 + 10]; + b11 += kw[dm17 + 11]; + b12 += kw[dm17 + 12]; + b13 += kw[dm17 + 13] + t[dm3]; + b14 += kw[dm17 + 14] + t[dm3 + 1]; + b15 += kw[dm17 + 15] + (uint)d; + + /* + * 4 more rounds of mix/permute + */ + b1 = RotlXor(b1, ROTATION_4_0, b0 += b1); + b3 = RotlXor(b3, ROTATION_4_1, b2 += b3); + b5 = RotlXor(b5, ROTATION_4_2, b4 += b5); + b7 = RotlXor(b7, ROTATION_4_3, b6 += b7); + b9 = RotlXor(b9, ROTATION_4_4, b8 += b9); + b11 = RotlXor(b11, ROTATION_4_5, b10 += b11); + b13 = RotlXor(b13, ROTATION_4_6, b12 += b13); + b15 = RotlXor(b15, ROTATION_4_7, b14 += b15); + + b9 = RotlXor(b9, ROTATION_5_0, b0 += b9); + b13 = RotlXor(b13, ROTATION_5_1, b2 += b13); + b11 = RotlXor(b11, ROTATION_5_2, b6 += b11); + b15 = RotlXor(b15, ROTATION_5_3, b4 += b15); + b7 = RotlXor(b7, ROTATION_5_4, b10 += b7); + b3 = RotlXor(b3, ROTATION_5_5, b12 += b3); + b5 = RotlXor(b5, ROTATION_5_6, b14 += b5); + b1 = RotlXor(b1, ROTATION_5_7, b8 += b1); + + b7 = RotlXor(b7, ROTATION_6_0, b0 += b7); + b5 = RotlXor(b5, ROTATION_6_1, b2 += b5); + b3 = RotlXor(b3, ROTATION_6_2, b4 += b3); + b1 = RotlXor(b1, ROTATION_6_3, b6 += b1); + b15 = RotlXor(b15, ROTATION_6_4, b12 += b15); + b13 = RotlXor(b13, ROTATION_6_5, b14 += b13); + b11 = RotlXor(b11, ROTATION_6_6, b8 += b11); + b9 = RotlXor(b9, ROTATION_6_7, b10 += b9); + + b15 = RotlXor(b15, ROTATION_7_0, b0 += b15); + b11 = RotlXor(b11, ROTATION_7_1, b2 += b11); + b13 = RotlXor(b13, ROTATION_7_2, b6 += b13); + b9 = RotlXor(b9, ROTATION_7_3, b4 += b9); + b1 = RotlXor(b1, ROTATION_7_4, b14 += b1); + b5 = RotlXor(b5, ROTATION_7_5, b8 += b5); + b3 = RotlXor(b3, ROTATION_7_6, b10 += b3); + b7 = RotlXor(b7, ROTATION_7_7, b12 += b7); + + /* + * Subkey injection for next 4 rounds. + */ + b0 += kw[dm17 + 1]; + b1 += kw[dm17 + 2]; + b2 += kw[dm17 + 3]; + b3 += kw[dm17 + 4]; + b4 += kw[dm17 + 5]; + b5 += kw[dm17 + 6]; + b6 += kw[dm17 + 7]; + b7 += kw[dm17 + 8]; + b8 += kw[dm17 + 9]; + b9 += kw[dm17 + 10]; + b10 += kw[dm17 + 11]; + b11 += kw[dm17 + 12]; + b12 += kw[dm17 + 13]; + b13 += kw[dm17 + 14] + t[dm3 + 1]; + b14 += kw[dm17 + 15] + t[dm3 + 2]; + b15 += kw[dm17 + 16] + (uint)d + 1; + + } + + /* + * Output cipher state. + */ + outWords[0] = b0; + outWords[1] = b1; + outWords[2] = b2; + outWords[3] = b3; + outWords[4] = b4; + outWords[5] = b5; + outWords[6] = b6; + outWords[7] = b7; + outWords[8] = b8; + outWords[9] = b9; + outWords[10] = b10; + outWords[11] = b11; + outWords[12] = b12; + outWords[13] = b13; + outWords[14] = b14; + outWords[15] = b15; + } + + internal override void DecryptBlock(ulong[] block, ulong[] state) + { + ulong[] kw = this.kw; + ulong[] t = this.t; + int[] mod17 = MOD17; + int[] mod3 = MOD3; + + /* Help the JIT avoid index bounds checks */ + if (kw.Length != 33) + { + throw new ArgumentException(); + } + if (t.Length != 5) + { + throw new ArgumentException(); + } + + ulong b0 = block[0]; + ulong b1 = block[1]; + ulong b2 = block[2]; + ulong b3 = block[3]; + ulong b4 = block[4]; + ulong b5 = block[5]; + ulong b6 = block[6]; + ulong b7 = block[7]; + ulong b8 = block[8]; + ulong b9 = block[9]; + ulong b10 = block[10]; + ulong b11 = block[11]; + ulong b12 = block[12]; + ulong b13 = block[13]; + ulong b14 = block[14]; + ulong b15 = block[15]; + + for (int d = (ROUNDS_1024 / 4) - 1; d >= 1; d -= 2) + { + int dm17 = mod17[d]; + int dm3 = mod3[d]; + + /* Reverse key injection for second 4 rounds */ + b0 -= kw[dm17 + 1]; + b1 -= kw[dm17 + 2]; + b2 -= kw[dm17 + 3]; + b3 -= kw[dm17 + 4]; + b4 -= kw[dm17 + 5]; + b5 -= kw[dm17 + 6]; + b6 -= kw[dm17 + 7]; + b7 -= kw[dm17 + 8]; + b8 -= kw[dm17 + 9]; + b9 -= kw[dm17 + 10]; + b10 -= kw[dm17 + 11]; + b11 -= kw[dm17 + 12]; + b12 -= kw[dm17 + 13]; + b13 -= kw[dm17 + 14] + t[dm3 + 1]; + b14 -= kw[dm17 + 15] + t[dm3 + 2]; + b15 -= kw[dm17 + 16] + (uint)d + 1; + + /* Reverse second 4 mix/permute rounds */ + b15 = XorRotr(b15, ROTATION_7_0, b0); + b0 -= b15; + b11 = XorRotr(b11, ROTATION_7_1, b2); + b2 -= b11; + b13 = XorRotr(b13, ROTATION_7_2, b6); + b6 -= b13; + b9 = XorRotr(b9, ROTATION_7_3, b4); + b4 -= b9; + b1 = XorRotr(b1, ROTATION_7_4, b14); + b14 -= b1; + b5 = XorRotr(b5, ROTATION_7_5, b8); + b8 -= b5; + b3 = XorRotr(b3, ROTATION_7_6, b10); + b10 -= b3; + b7 = XorRotr(b7, ROTATION_7_7, b12); + b12 -= b7; + + b7 = XorRotr(b7, ROTATION_6_0, b0); + b0 -= b7; + b5 = XorRotr(b5, ROTATION_6_1, b2); + b2 -= b5; + b3 = XorRotr(b3, ROTATION_6_2, b4); + b4 -= b3; + b1 = XorRotr(b1, ROTATION_6_3, b6); + b6 -= b1; + b15 = XorRotr(b15, ROTATION_6_4, b12); + b12 -= b15; + b13 = XorRotr(b13, ROTATION_6_5, b14); + b14 -= b13; + b11 = XorRotr(b11, ROTATION_6_6, b8); + b8 -= b11; + b9 = XorRotr(b9, ROTATION_6_7, b10); + b10 -= b9; + + b9 = XorRotr(b9, ROTATION_5_0, b0); + b0 -= b9; + b13 = XorRotr(b13, ROTATION_5_1, b2); + b2 -= b13; + b11 = XorRotr(b11, ROTATION_5_2, b6); + b6 -= b11; + b15 = XorRotr(b15, ROTATION_5_3, b4); + b4 -= b15; + b7 = XorRotr(b7, ROTATION_5_4, b10); + b10 -= b7; + b3 = XorRotr(b3, ROTATION_5_5, b12); + b12 -= b3; + b5 = XorRotr(b5, ROTATION_5_6, b14); + b14 -= b5; + b1 = XorRotr(b1, ROTATION_5_7, b8); + b8 -= b1; + + b1 = XorRotr(b1, ROTATION_4_0, b0); + b0 -= b1; + b3 = XorRotr(b3, ROTATION_4_1, b2); + b2 -= b3; + b5 = XorRotr(b5, ROTATION_4_2, b4); + b4 -= b5; + b7 = XorRotr(b7, ROTATION_4_3, b6); + b6 -= b7; + b9 = XorRotr(b9, ROTATION_4_4, b8); + b8 -= b9; + b11 = XorRotr(b11, ROTATION_4_5, b10); + b10 -= b11; + b13 = XorRotr(b13, ROTATION_4_6, b12); + b12 -= b13; + b15 = XorRotr(b15, ROTATION_4_7, b14); + b14 -= b15; + + /* Reverse key injection for first 4 rounds */ + b0 -= kw[dm17]; + b1 -= kw[dm17 + 1]; + b2 -= kw[dm17 + 2]; + b3 -= kw[dm17 + 3]; + b4 -= kw[dm17 + 4]; + b5 -= kw[dm17 + 5]; + b6 -= kw[dm17 + 6]; + b7 -= kw[dm17 + 7]; + b8 -= kw[dm17 + 8]; + b9 -= kw[dm17 + 9]; + b10 -= kw[dm17 + 10]; + b11 -= kw[dm17 + 11]; + b12 -= kw[dm17 + 12]; + b13 -= kw[dm17 + 13] + t[dm3]; + b14 -= kw[dm17 + 14] + t[dm3 + 1]; + b15 -= kw[dm17 + 15] + (uint)d; + + /* Reverse first 4 mix/permute rounds */ + b15 = XorRotr(b15, ROTATION_3_0, b0); + b0 -= b15; + b11 = XorRotr(b11, ROTATION_3_1, b2); + b2 -= b11; + b13 = XorRotr(b13, ROTATION_3_2, b6); + b6 -= b13; + b9 = XorRotr(b9, ROTATION_3_3, b4); + b4 -= b9; + b1 = XorRotr(b1, ROTATION_3_4, b14); + b14 -= b1; + b5 = XorRotr(b5, ROTATION_3_5, b8); + b8 -= b5; + b3 = XorRotr(b3, ROTATION_3_6, b10); + b10 -= b3; + b7 = XorRotr(b7, ROTATION_3_7, b12); + b12 -= b7; + + b7 = XorRotr(b7, ROTATION_2_0, b0); + b0 -= b7; + b5 = XorRotr(b5, ROTATION_2_1, b2); + b2 -= b5; + b3 = XorRotr(b3, ROTATION_2_2, b4); + b4 -= b3; + b1 = XorRotr(b1, ROTATION_2_3, b6); + b6 -= b1; + b15 = XorRotr(b15, ROTATION_2_4, b12); + b12 -= b15; + b13 = XorRotr(b13, ROTATION_2_5, b14); + b14 -= b13; + b11 = XorRotr(b11, ROTATION_2_6, b8); + b8 -= b11; + b9 = XorRotr(b9, ROTATION_2_7, b10); + b10 -= b9; + + b9 = XorRotr(b9, ROTATION_1_0, b0); + b0 -= b9; + b13 = XorRotr(b13, ROTATION_1_1, b2); + b2 -= b13; + b11 = XorRotr(b11, ROTATION_1_2, b6); + b6 -= b11; + b15 = XorRotr(b15, ROTATION_1_3, b4); + b4 -= b15; + b7 = XorRotr(b7, ROTATION_1_4, b10); + b10 -= b7; + b3 = XorRotr(b3, ROTATION_1_5, b12); + b12 -= b3; + b5 = XorRotr(b5, ROTATION_1_6, b14); + b14 -= b5; + b1 = XorRotr(b1, ROTATION_1_7, b8); + b8 -= b1; + + b1 = XorRotr(b1, ROTATION_0_0, b0); + b0 -= b1; + b3 = XorRotr(b3, ROTATION_0_1, b2); + b2 -= b3; + b5 = XorRotr(b5, ROTATION_0_2, b4); + b4 -= b5; + b7 = XorRotr(b7, ROTATION_0_3, b6); + b6 -= b7; + b9 = XorRotr(b9, ROTATION_0_4, b8); + b8 -= b9; + b11 = XorRotr(b11, ROTATION_0_5, b10); + b10 -= b11; + b13 = XorRotr(b13, ROTATION_0_6, b12); + b12 -= b13; + b15 = XorRotr(b15, ROTATION_0_7, b14); + b14 -= b15; + } + + /* + * First subkey uninjection. + */ + b0 -= kw[0]; + b1 -= kw[1]; + b2 -= kw[2]; + b3 -= kw[3]; + b4 -= kw[4]; + b5 -= kw[5]; + b6 -= kw[6]; + b7 -= kw[7]; + b8 -= kw[8]; + b9 -= kw[9]; + b10 -= kw[10]; + b11 -= kw[11]; + b12 -= kw[12]; + b13 -= kw[13] + t[0]; + b14 -= kw[14] + t[1]; + b15 -= kw[15]; + + /* + * Output cipher state. + */ + state[0] = b0; + state[1] = b1; + state[2] = b2; + state[3] = b3; + state[4] = b4; + state[5] = b5; + state[6] = b6; + state[7] = b7; + state[8] = b8; + state[9] = b9; + state[10] = b10; + state[11] = b11; + state[12] = b12; + state[13] = b13; + state[14] = b14; + state[15] = b15; + } + + } + + } +} \ No newline at end of file diff --git a/bc-sharp-crypto/src/crypto/engines/TnepresEngine.cs b/bc-sharp-crypto/src/crypto/engines/TnepresEngine.cs new file mode 100644 index 0000000..ce687d1 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/TnepresEngine.cs @@ -0,0 +1,299 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Utilities; + +namespace Org.BouncyCastle.Crypto.Engines +{ + /** + * Tnepres 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. + *

+ * Tnepres is based on Serpent which was designed by Ross Anderson, Eli Biham and Lars Knudsen as a + * candidate algorithm for the NIST AES Quest. Unfortunately there was an endianness issue + * with test vectors in the AES submission and the resulting confusion lead to the Tnepres cipher + * as well, which is a byte swapped version of Serpent. + *

+ *

+ * For full details see The Serpent home page + *

+ */ + public sealed class TnepresEngine + : SerpentEngineBase + { + public override string AlgorithmName + { + get { return "Tnepres"; } + } + + /** + * Expand a user-supplied key material into a session key. + * + * @param key The user-key bytes (multiples of 4) to use. + * @exception ArgumentException + */ + protected override int[] MakeWorkingKey(byte[] key) + { + // + // 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++] = (int)Pack.BE_To_UInt32(key, off); + } + + if (off == 0) + { + kPad[length++] = (int)Pack.BE_To_UInt32(key, 0); + if (length < 8) + { + kPad[length] = 1; + } + } + else + { + throw new ArgumentException("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); + } + + Array.Copy(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; + } + + /** + * Encrypt one block of plaintext. + * + * @param input the array containing the input data. + * @param inOff offset into the in array the data starts at. + * @param output the array the output data will be copied into. + * @param outOff the offset into the out array the output will start at. + */ + protected override void EncryptBlock(byte[] input, int inOff, byte[] output, int outOff) + { + X3 = (int)Pack.BE_To_UInt32(input, inOff); + X2 = (int)Pack.BE_To_UInt32(input, inOff + 4); + X1 = (int)Pack.BE_To_UInt32(input, inOff + 8); + X0 = (int)Pack.BE_To_UInt32(input, 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); + + Pack.UInt32_To_BE((uint)(wKey[131] ^ X3), output, outOff); + Pack.UInt32_To_BE((uint)(wKey[130] ^ X2), output, outOff + 4); + Pack.UInt32_To_BE((uint)(wKey[129] ^ X1), output, outOff + 8); + Pack.UInt32_To_BE((uint)(wKey[128] ^ X0), output, outOff + 12); + } + + /** + * Decrypt one block of ciphertext. + * + * @param input the array containing the input data. + * @param inOff offset into the in array the data starts at. + * @param output the array the output data will be copied into. + * @param outOff the offset into the out array the output will start at. + */ + protected override void DecryptBlock(byte[] input, int inOff, byte[] output, int outOff) + { + X3 = wKey[131] ^ (int)Pack.BE_To_UInt32(input, inOff); + X2 = wKey[130] ^ (int)Pack.BE_To_UInt32(input, inOff + 4); + X1 = wKey[129] ^ (int)Pack.BE_To_UInt32(input, inOff + 8); + X0 = wKey[128] ^ (int)Pack.BE_To_UInt32(input, 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); + + Pack.UInt32_To_BE((uint)(X3 ^ wKey[3]), output, outOff); + Pack.UInt32_To_BE((uint)(X2 ^ wKey[2]), output, outOff + 4); + Pack.UInt32_To_BE((uint)(X1 ^ wKey[1]), output, outOff + 8); + Pack.UInt32_To_BE((uint)(X0 ^ wKey[0]), output, outOff + 12); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/engines/TwofishEngine.cs b/bc-sharp-crypto/src/crypto/engines/TwofishEngine.cs new file mode 100644 index 0000000..71c2465 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/TwofishEngine.cs @@ -0,0 +1,675 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Engines +{ + /** + * 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 sealed class TwofishEngine + : IBlockCipher + { + private static readonly 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 const int P_00 = 1; + private const int P_01 = 0; + private const int P_02 = 0; + private const int P_03 = P_01 ^ 1; + private const int P_04 = 1; + + private const int P_10 = 0; + private const int P_11 = 0; + private const int P_12 = 1; + private const int P_13 = P_11 ^ 1; + private const int P_14 = 0; + + private const int P_20 = 1; + private const int P_21 = 1; + private const int P_22 = 0; + private const int P_23 = P_21 ^ 1; + private const int P_24 = 0; + + private const int P_30 = 0; + private const int P_31 = 1; + private const int P_32 = 1; + private const int P_33 = P_31 ^ 1; + private const int P_34 = 1; + + /* Primitive polynomial for GF(256) */ + private const int GF256_FDBK = 0x169; + private const int GF256_FDBK_2 = GF256_FDBK / 2; + private const int GF256_FDBK_4 = GF256_FDBK / 4; + + private const int RS_GF_FDBK = 0x14D; // field generator + + //==================================== + // Useful constants + //==================================== + + private const int ROUNDS = 16; + private const int MAX_ROUNDS = 16; // bytes = 128 bits + private const int BLOCK_SIZE = 16; // bytes = 128 bits + private const int MAX_KEY_BITS = 256; + + private const int INPUT_WHITEN=0; + private const int OUTPUT_WHITEN=INPUT_WHITEN+BLOCK_SIZE/4; // 4 + private const int ROUND_SUBKEYS=OUTPUT_WHITEN+BLOCK_SIZE/4;// 8 + + private const int TOTAL_SUBKEYS=ROUND_SUBKEYS+2*MAX_ROUNDS;// 40 + + private const int SK_STEP = 0x02020202; + private const int SK_BUMP = 0x01010101; + private const int SK_ROTL = 9; + + private bool encrypting; + + 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; + + private byte[] workingKey; + + 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 forEncryption whether or not we are for encryption. + * @param parameters the parameters required to set up the cipher. + * @exception ArgumentException if the parameters argument is + * inappropriate. + */ + public void Init( + bool forEncryption, + ICipherParameters parameters) + { + if (!(parameters is KeyParameter)) + throw new ArgumentException("invalid parameter passed to Twofish init - " + Platform.GetTypeName(parameters)); + + this.encrypting = forEncryption; + this.workingKey = ((KeyParameter)parameters).GetKey(); + this.k64Cnt = (this.workingKey.Length / 8); // pre-padded ? + SetKey(this.workingKey); + } + + public string AlgorithmName + { + get { return "Twofish"; } + } + + public bool IsPartialBlockOkay + { + get { return false; } + } + + public int ProcessBlock( + byte[] input, + int inOff, + byte[] output, + int outOff) + { + if (workingKey == null) + throw new InvalidOperationException("Twofish not initialised"); + + Check.DataLength(input, inOff, BLOCK_SIZE, "input buffer too short"); + Check.OutputLength(output, outOff, BLOCK_SIZE, "output buffer too short"); + + if (encrypting) + { + EncryptBlock(input, inOff, output, outOff); + } + else + { + DecryptBlock(input, inOff, output, 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 ArgumentException("Key size less than 64 bits"); + } + + if (k64Cnt > 4) + { + throw new ArgumentException("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,p=0; i> 24); + A += B; + gSubKeys[i*2] = A; + A += B; + gSubKeys[i*2 + 1] = A << SK_ROTL | (int)((uint)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 | (int) ((uint)x3 >> 31)) ^ (t0 + 2*t1 + gSubKeys[k++]); + + t0 = Fe32_0(x2); + t1 = Fe32_3(x3); + x0 ^= t0 + t1 + gSubKeys[k++]; + x0 = (int) ((uint)x0 >>1) | x0 << 31; + x1 = (x1 << 1 | (int)((uint)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 | (int)((uint) x0 >> 31)) ^ (t0 + t1 + gSubKeys[k--]); + x1 = (int) ((uint)x1 >>1) | x1 << 31; + + t0 = Fe32_0(x0); + t1 = Fe32_3(x1); + x3 ^= t0 + 2*t1 + gSubKeys[k--]; + x2 = (x2 << 1 | (int)((uint)x2 >> 31)) ^ (t0 + t1 + gSubKeys[k--]); + x3 = (int)((uint)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 = M_b0(x); + int b1 = M_b1(x); + int b2 = M_b2(x); + int b3 = M_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) ^ M_b0(k0)] ^ + gMDS1[(P[P_11,b1] & 0xff) ^ M_b1(k0)] ^ + gMDS2[(P[P_21,b2] & 0xff) ^ M_b2(k0)] ^ + gMDS3[(P[P_31,b3] & 0xff) ^ M_b3(k0)]; + break; + case 0: /* 256 bits of key */ + b0 = (P[P_04,b0] & 0xff) ^ M_b0(k3); + b1 = (P[P_14,b1] & 0xff) ^ M_b1(k3); + b2 = (P[P_24,b2] & 0xff) ^ M_b2(k3); + b3 = (P[P_34,b3] & 0xff) ^ M_b3(k3); + goto case 3; + case 3: + b0 = (P[P_03,b0] & 0xff) ^ M_b0(k2); + b1 = (P[P_13,b1] & 0xff) ^ M_b1(k2); + b2 = (P[P_23,b2] & 0xff) ^ M_b2(k2); + b3 = (P[P_33,b3] & 0xff) ^ M_b3(k2); + goto case 2; + case 2: + result = + gMDS0[(P[P_01,(P[P_02,b0]&0xff)^M_b0(k1)]&0xff)^M_b0(k0)] ^ + gMDS1[(P[P_11,(P[P_12,b1]&0xff)^M_b1(k1)]&0xff)^M_b1(k0)] ^ + gMDS2[(P[P_21,(P[P_22,b2]&0xff)^M_b2(k1)]&0xff)^M_b2(k0)] ^ + gMDS3[(P[P_31,(P[P_32,b3]&0xff)^M_b3(k1)]&0xff)^M_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 = (int) (((uint)x >> 24) & 0xff); + int g2 = ((b << 1) ^ + ((b & 0x80) != 0 ? RS_GF_FDBK : 0)) & 0xff; + int g3 = ( (int)((uint)b >> 1) ^ + ((b & 0x01) != 0 ? (int)((uint)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 M_b0(int x) + { + return x & 0xff; + } + + private int M_b1(int x) + { + return (int)((uint)x >> 8) & 0xff; + } + + private int M_b2(int x) + { + return (int)((uint)x >> 16) & 0xff; + } + + private int M_b3(int x) + { + return (int)((uint)x >> 24) & 0xff; + } + + private int Fe32_0(int x) + { + return gSBox[ 0x000 + 2*(x & 0xff) ] ^ + gSBox[ 0x001 + 2*((int)((uint)x >> 8) & 0xff) ] ^ + gSBox[ 0x200 + 2*((int)((uint)x >> 16) & 0xff) ] ^ + gSBox[ 0x201 + 2*((int)((uint)x >> 24) & 0xff) ]; + } + + private int Fe32_3(int x) + { + return gSBox[ 0x000 + 2*((int)((uint)x >> 24) & 0xff) ] ^ + gSBox[ 0x001 + 2*(x & 0xff) ] ^ + gSBox[ 0x200 + 2*((int)((uint)x >> 8) & 0xff) ] ^ + gSBox[ 0x201 + 2*((int)((uint)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 inData, byte[] b, int offset) + { + b[offset] = (byte)inData; + b[offset + 1] = (byte)(inData >> 8); + b[offset + 2] = (byte)(inData >> 16); + b[offset + 3] = (byte)(inData >> 24); + } + } + +} diff --git a/bc-sharp-crypto/src/crypto/engines/VMPCEngine.cs b/bc-sharp-crypto/src/crypto/engines/VMPCEngine.cs new file mode 100644 index 0000000..852901e --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/VMPCEngine.cs @@ -0,0 +1,133 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; + +namespace Org.BouncyCastle.Crypto.Engines +{ + public class VmpcEngine + : IStreamCipher + { + /* + * 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 virtual string AlgorithmName + { + get { 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 ArgumentException + * if the params argument is inappropriate. + */ + public virtual void Init( + bool forEncryption, + ICipherParameters parameters) + { + if (!(parameters is ParametersWithIV)) + throw new ArgumentException("VMPC Init parameters must include an IV"); + + ParametersWithIV ivParams = (ParametersWithIV) parameters; + + if (!(ivParams.Parameters is KeyParameter)) + throw new ArgumentException("VMPC Init parameters must include a key"); + + KeyParameter key = (KeyParameter)ivParams.Parameters; + + this.workingIV = ivParams.GetIV(); + + if (workingIV == null || workingIV.Length < 1 || workingIV.Length > 768) + throw new ArgumentException("VMPC requires 1 to 768 bytes of IV"); + + this.workingKey = key.GetKey(); + + InitKey(this.workingKey, this.workingIV); + } + + protected virtual 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 virtual void ProcessBytes( + byte[] input, + int inOff, + int len, + byte[] output, + int outOff) + { + Check.DataLength(input, inOff, len, "input buffer too short"); + Check.OutputLength(output, outOff, len, "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 + output[i + outOff] = (byte) (input[i + inOff] ^ z); + } + } + + public virtual void Reset() + { + InitKey(this.workingKey, this.workingIV); + } + + public virtual byte ReturnByte( + byte input) + { + 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) (input ^ z); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/engines/VMPCKSA3Engine.cs b/bc-sharp-crypto/src/crypto/engines/VMPCKSA3Engine.cs new file mode 100644 index 0000000..95b6813 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/VMPCKSA3Engine.cs @@ -0,0 +1,51 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Engines +{ + public class VmpcKsa3Engine + : VmpcEngine + { + public override string AlgorithmName + { + get { return "VMPC-KSA3"; } + } + + protected override 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/bc-sharp-crypto/src/crypto/engines/XSalsa20Engine.cs b/bc-sharp-crypto/src/crypto/engines/XSalsa20Engine.cs new file mode 100644 index 0000000..50c51a8 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/XSalsa20Engine.cs @@ -0,0 +1,64 @@ +using System; + +using Org.BouncyCastle.Crypto.Utilities; + +namespace Org.BouncyCastle.Crypto.Engines +{ + /// + /// Implementation of Daniel J. Bernstein's XSalsa20 stream cipher - Salsa20 with an extended nonce. + /// + /// + /// XSalsa20 requires a 256 bit key, and a 192 bit nonce. + /// + public class XSalsa20Engine + : Salsa20Engine + { + public override string AlgorithmName + { + get { return "XSalsa20"; } + } + + protected override int NonceSize + { + get { return 24; } + } + + /// + /// XSalsa20 key generation: process 256 bit input key and 128 bits of the input nonce + /// using a core Salsa20 function without input addition to produce 256 bit working key + /// and use that with the remaining 64 bits of nonce to initialize a standard Salsa20 engine state. + /// + protected override void SetKey(byte[] keyBytes, byte[] ivBytes) + { + if (keyBytes == null) + throw new ArgumentException(AlgorithmName + " doesn't support re-init with null key"); + + if (keyBytes.Length != 32) + throw new ArgumentException(AlgorithmName + " requires a 256 bit key"); + + // Set key for HSalsa20 + base.SetKey(keyBytes, ivBytes); + + // Pack next 64 bits of IV into engine state instead of counter + Pack.LE_To_UInt32(ivBytes, 8, engineState, 8, 2); + + // Process engine state to generate Salsa20 key + uint[] hsalsa20Out = new uint[engineState.Length]; + SalsaCore(20, engineState, hsalsa20Out); + + // Set new key, removing addition in last round of salsaCore + engineState[1] = hsalsa20Out[0] - engineState[0]; + engineState[2] = hsalsa20Out[5] - engineState[5]; + engineState[3] = hsalsa20Out[10] - engineState[10]; + engineState[4] = hsalsa20Out[15] - engineState[15]; + + engineState[11] = hsalsa20Out[6] - engineState[6]; + engineState[12] = hsalsa20Out[7] - engineState[7]; + engineState[13] = hsalsa20Out[8] - engineState[8]; + engineState[14] = hsalsa20Out[9] - engineState[9]; + + // Last 64 bits of input IV + Pack.LE_To_UInt32(ivBytes, 16, engineState, 6, 2); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/engines/XTEAEngine.cs b/bc-sharp-crypto/src/crypto/engines/XTEAEngine.cs new file mode 100644 index 0000000..5fcfa4a --- /dev/null +++ b/bc-sharp-crypto/src/crypto/engines/XTEAEngine.cs @@ -0,0 +1,166 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Utilities; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Engines +{ + /** + * An XTEA engine. + */ + public class XteaEngine + : IBlockCipher + { + private const int + rounds = 32, + block_size = 8, +// key_size = 16, + delta = unchecked((int) 0x9E3779B9); + + /* + * the expanded key array of 4 subkeys + */ + private uint[] _S = new uint[4], + _sum0 = new uint[32], + _sum1 = new uint[32]; + private bool _initialised, _forEncryption; + + /** + * Create an instance of the TEA encryption algorithm + * and set some defaults + */ + public XteaEngine() + { + _initialised = false; + } + + public virtual string AlgorithmName + { + get { return "XTEA"; } + } + + public virtual bool IsPartialBlockOkay + { + get { return false; } + } + + public virtual 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 ArgumentException if the params argument is + * inappropriate. + */ + public virtual void Init( + bool forEncryption, + ICipherParameters parameters) + { + if (!(parameters is KeyParameter)) + { + throw new ArgumentException("invalid parameter passed to TEA init - " + + Platform.GetTypeName(parameters)); + } + + _forEncryption = forEncryption; + _initialised = true; + + KeyParameter p = (KeyParameter) parameters; + + setKey(p.GetKey()); + } + + public virtual int ProcessBlock( + byte[] inBytes, + int inOff, + byte[] outBytes, + int outOff) + { + if (!_initialised) + throw new InvalidOperationException(AlgorithmName + " not initialised"); + + Check.DataLength(inBytes, inOff, block_size, "input buffer too short"); + Check.OutputLength(outBytes, outOff, block_size, "output buffer too short"); + + return _forEncryption + ? encryptBlock(inBytes, inOff, outBytes, outOff) + : decryptBlock(inBytes, inOff, outBytes, outOff); + } + + public virtual 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] = Pack.BE_To_UInt32(key, j); + } + + for (i = j = 0; i < rounds; i++) + { + _sum0[i] = ((uint)j + _S[j & 3]); + j += delta; + _sum1[i] = ((uint)j + _S[j >> 11 & 3]); + } + } + + private int encryptBlock( + byte[] inBytes, + int inOff, + byte[] outBytes, + int outOff) + { + // Pack bytes into integers + uint v0 = Pack.BE_To_UInt32(inBytes, inOff); + uint v1 = Pack.BE_To_UInt32(inBytes, 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]; + } + + Pack.UInt32_To_BE(v0, outBytes, outOff); + Pack.UInt32_To_BE(v1, outBytes, outOff + 4); + + return block_size; + } + + private int decryptBlock( + byte[] inBytes, + int inOff, + byte[] outBytes, + int outOff) + { + // Pack bytes into integers + uint v0 = Pack.BE_To_UInt32(inBytes, inOff); + uint v1 = Pack.BE_To_UInt32(inBytes, 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]; + } + + Pack.UInt32_To_BE(v0, outBytes, outOff); + Pack.UInt32_To_BE(v1, outBytes, outOff + 4); + + return block_size; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/generators/BCrypt.cs b/bc-sharp-crypto/src/crypto/generators/BCrypt.cs new file mode 100644 index 0000000..af8029a --- /dev/null +++ b/bc-sharp-crypto/src/crypto/generators/BCrypt.cs @@ -0,0 +1,617 @@ +using System; + +using Org.BouncyCastle.Crypto.Utilities; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Generators +{ + /** + * Core of password hashing scheme Bcrypt, + * designed by Niels Provos and David Mazières, + * corresponds to the C reference implementation. + *

+ * This implementation does not correspondent to the 1999 published paper + * "A Future-Adaptable Password Scheme" of Niels Provos and David Mazières, + * see: https://www.usenix.org/legacy/events/usenix99/provos/provos_html/node1.html. + * In contrast to the paper, the order of key setup and salt setup is reversed: + * state <- ExpandKey(state, 0, key) + * state %lt;- ExpandKey(state, 0, salt) + * This corresponds to the OpenBSD reference implementation of Bcrypt. + *

+ * Note: + * There is no successful cryptanalysis (status 2015), but + * the amount of memory and the band width of Bcrypt + * may be insufficient to effectively prevent attacks + * with custom hardware like FPGAs, ASICs + *

+ * This implementation uses some parts of Bouncy Castle's BlowfishEngine. + *

+ */ + public sealed class BCrypt + { + // magic String "OrpheanBeholderScryDoubt" is used as clear text for encryption + private static readonly uint[] MAGIC_STRING = + { + 0x4F727068, 0x65616E42, 0x65686F6C, + 0x64657253, 0x63727944, 0x6F756274 + }; + + internal const int MAGIC_STRING_LENGTH = 6; + + private static readonly uint[] + 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 const int ROUNDS = 16; + private const int SBOX_SK = 256; + private const int SBOX_SK2 = SBOX_SK * 2; + private const int SBOX_SK3 = SBOX_SK * 3; + private const int P_SZ = ROUNDS + 2; + + private readonly uint[] S; // the s-boxes + private readonly uint[] P; // the p-array + + private BCrypt() + { + S = new uint[SBOX_SK * 4]; + P = new uint[P_SZ]; + } + + //================================== + // Private Implementation + //================================== + + private uint F(uint x) + { + return (((S[(x >> 24)] + S[SBOX_SK + ((x >> 16) & 0xff)]) + ^ S[SBOX_SK2 + ((x >> 8) & 0xff)]) + S[SBOX_SK3 + (x & 0xff)]); + } + + /* + * apply the encryption cycle to each value pair in the table. + */ + private void ProcessTable(uint xl, uint xr, uint[] 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]; + } + } + + /* + * Initialize the S-boxes and the P-array, with a fixed string + * This string contains the hexadecimal digits of pi (3.141...) + */ + private void InitState() + { + Array.Copy(KS0, 0, S, 0, SBOX_SK); + Array.Copy(KS1, 0, S, SBOX_SK, SBOX_SK); + Array.Copy(KS2, 0, S, SBOX_SK2, SBOX_SK); + Array.Copy(KS3, 0, S, SBOX_SK3, SBOX_SK); + + Array.Copy(KP, 0, P, 0, P_SZ); + } + + /* + * XOR P with key cyclic. + * This is the first part of ExpandKey function + */ + private void CyclicXorKey(byte[] key) + { + 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 + uint data = 0x0000000; + for (int j = 0; j < 4; j++) + { + // create a 32 bit block + data = (data << 8) | key[keyIndex]; + + // 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; + } + } + + + /* + * encrypt magic String 64 times in ECB + */ + private byte[] EncryptMagicString() + { + uint[] text = { + MAGIC_STRING[0], MAGIC_STRING[1], + MAGIC_STRING[2], MAGIC_STRING[3], + MAGIC_STRING[4], MAGIC_STRING[5] + }; + for (int i = 0; i < 64; i++) + { + for (int j = 0; j < MAGIC_STRING_LENGTH; j += 2) + { + uint left = text[j]; + uint right = text[j + 1]; + + left ^= P[0]; + for (int k = 1; k < ROUNDS; k += 2) + { + right ^= F(left) ^ P[k]; + left ^= F(right) ^ P[k + 1]; + } + right ^= P[ROUNDS + 1]; + // swap values: + text[j] = right; + text[j + 1] = left; + } + } + byte[] result = new byte[24]; // holds 192 bit key + Pack.UInt32_To_BE(text, result, 0); + Array.Clear(text, 0, text.Length); + Array.Clear(P, 0, P.Length); + Array.Clear(S, 0, S.Length); + + return result; + } + + /* + * This is a part of Eksblowfish function + * + * @param table: sub-keys or working key + * @param salt32Bit: a 16 byte salt as two 32 bit words + * @param iv1: value from last proceeded table + * @param iv2: value from last proceeded table + */ + private void ProcessTableWithSalt(uint[] table, uint[] salt32Bit, uint iv1, uint iv2) + { + uint xl = iv1 ^ salt32Bit[0]; + uint xr = iv2 ^ salt32Bit[1]; + + uint yl; + uint yr; + int size = table.Length; + + for (int s = 0; s < size; s += 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]; + + table[s] = xr; + table[s + 1] = xl; + + yl = salt32Bit[2] ^ xr; + yr = salt32Bit[3] ^ xl; + + if (s + 2 >= size) // P holds 18 values + { + break; + } + + yl ^= P[0]; + for (int i = 1; i < ROUNDS; i += 2) + { + yr ^= F(yl) ^ P[i]; + yl ^= F(yr) ^ P[i + 1]; + } + yr ^= P[ROUNDS + 1]; + + table[s + 2] = yr; + table[s + 3] = yl; + + xl = salt32Bit[0] ^ yr; + xr = salt32Bit[1] ^ yl; + } + } + + /** + * Derives a raw 192 bit Bcrypt key + * + * @param cost the cost factor, treated as an exponent of 2 + * @param salt a 16 byte salt + * @param psw the password + * @return a 192 bit key + */ + private byte[] DeriveRawKey(int cost, byte[] salt, byte[] psw) + { + if (salt.Length != 16) + throw new DataLengthException("Invalid salt size: 16 bytes expected."); + if (cost < 4 || cost > 31) + throw new ArgumentException("Illegal cost factor: 4 - 31 expected.", "cost"); + + if (psw.Length == 0) + { + psw = new byte[4]; + } + + // state <- InitState() + InitState(); + + uint[] salt32Bit = new uint[4]; // holds 16 byte salt + Pack.BE_To_UInt32(salt, 0, salt32Bit); + + uint[] salt32Bit2 = new uint[salt.Length]; // swapped values + salt32Bit2[0] = salt32Bit[2]; + salt32Bit2[1] = salt32Bit[3]; + salt32Bit2[2] = salt32Bit[0]; + salt32Bit2[3] = salt32Bit[1]; + + // ExpandKey( state, salt, key): + CyclicXorKey(psw); + ProcessTableWithSalt(P, salt32Bit, 0, 0); + Array.Clear(salt32Bit, 0, salt32Bit.Length); + ProcessTableWithSalt(S, salt32Bit2, P[P.Length - 2], P[P.Length - 1]); + Array.Clear(salt32Bit2, 0, salt32Bit2.Length); + + int rounds = 1 << cost; + for (int i = 0; i != rounds; i++) // rounds may be negative if cost is 31 + { + // state <- ExpandKey(state, 0, key); + CyclicXorKey(psw); + ProcessTable(0, 0, P); + ProcessTable(P[P_SZ - 2], P[P_SZ - 1], S); + + // state <- ExpandKey(state, 0, salt); + CyclicXorKey(salt); + ProcessTable(0, 0, P); + ProcessTable(P[P_SZ - 2], P[P_SZ - 1], S); + } + + // encrypt magicString 64 times + return EncryptMagicString(); + } + + /** + * Size of the salt parameter in bytes + */ + internal const int SALT_SIZE_BYTES = 16; + + /** + * Minimum value of cost parameter, equal to log2(bytes of salt) + */ + internal const int MIN_COST = 4; + + /** + * Maximum value of cost parameter (31 == 2,147,483,648) + */ + internal const int MAX_COST = 31; + + /** + * Maximum size of password == max (unrestricted) size of Blowfish key + */ + // Blowfish spec limits keys to 448bit/56 bytes to ensure all bits of key affect all ciphertext + // bits, but technically algorithm handles 72 byte keys and most implementations support this. + internal const int MAX_PASSWORD_BYTES = 72; + + /** + * Calculates the bcrypt hash of a password. + *

+ * This implements the raw bcrypt function as defined in the bcrypt specification, not + * the crypt encoded version implemented in OpenBSD. + *

+ * @param password the password bytes (up to 72 bytes) to use for this invocation. + * @param salt the 128 bit salt to use for this invocation. + * @param cost the bcrypt cost parameter. The cost of the bcrypt function grows as + * 2^cost. Legal values are 4..31 inclusive. + * @return the output of the raw bcrypt operation: a 192 bit (24 byte) hash. + */ + public static byte[] Generate(byte[] password, byte[] salt, int cost) + { + if (password == null) + throw new ArgumentNullException("password"); + if (password.Length > MAX_PASSWORD_BYTES) + throw new ArgumentException("BCrypt password must be <= 72 bytes", "password"); + if (salt == null) + throw new ArgumentNullException("salt"); + if (salt.Length != SALT_SIZE_BYTES) + throw new ArgumentException("BCrypt salt must be 128 bits", "salt"); + if (cost < MIN_COST || cost > MAX_COST) + throw new ArgumentException("BCrypt cost must be from 4..31", "cost"); + + return new BCrypt().DeriveRawKey(cost, salt, password); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/generators/BaseKdfBytesGenerator.cs b/bc-sharp-crypto/src/crypto/generators/BaseKdfBytesGenerator.cs new file mode 100644 index 0000000..bca4207 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/generators/BaseKdfBytesGenerator.cs @@ -0,0 +1,132 @@ +using System; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Utilities; + +namespace Org.BouncyCastle.Crypto.Generators +{ + /** + * 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 + : IDerivationFunction + { + private int counterStart; + private IDigest 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. + */ + public BaseKdfBytesGenerator(int counterStart, IDigest digest) + { + this.counterStart = counterStart; + this.digest = digest; + } + + public virtual void Init(IDerivationParameters parameters) + { + if (parameters is KdfParameters) + { + KdfParameters p = (KdfParameters)parameters; + + shared = p.GetSharedSecret(); + iv = p.GetIV(); + } + else if (parameters is Iso18033KdfParameters) + { + Iso18033KdfParameters p = (Iso18033KdfParameters)parameters; + + shared = p.GetSeed(); + iv = null; + } + else + { + throw new ArgumentException("KDF parameters required for KDF Generator"); + } + } + + /** + * return the underlying digest. + */ + public virtual IDigest Digest + { + get { return digest; } + } + + /** + * fill len bytes of the output buffer with bytes generated from + * the derivation function. + * + * @throws ArgumentException if the size of the request will cause an overflow. + * @throws DataLengthException if the out buffer is too small. + */ + public virtual int GenerateBytes(byte[] output, int outOff, int length) + { + if ((output.Length - length) < outOff) + throw new DataLengthException("output buffer too small"); + + long oBytes = length; + 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 ArgumentException("Output length too large"); + + int cThreshold = (int)((oBytes + outLen - 1) / outLen); + + byte[] dig = new byte[digest.GetDigestSize()]; + + byte[] C = new byte[4]; + Pack.UInt32_To_BE((uint)counterStart, C, 0); + + uint counterBase = (uint)(counterStart & ~0xFF); + + for (int i = 0; i < cThreshold; i++) + { + digest.BlockUpdate(shared, 0, shared.Length); + digest.BlockUpdate(C, 0, 4); + + if (iv != null) + { + digest.BlockUpdate(iv, 0, iv.Length); + } + + digest.DoFinal(dig, 0); + + if (length > outLen) + { + Array.Copy(dig, 0, output, outOff, outLen); + outOff += outLen; + length -= outLen; + } + else + { + Array.Copy(dig, 0, output, outOff, length); + } + + if (++C[3] == 0) + { + counterBase += 0x100; + Pack.UInt32_To_BE(counterBase, C, 0); + } + } + + digest.Reset(); + + return (int)oBytes; + } + } +} \ No newline at end of file diff --git a/bc-sharp-crypto/src/crypto/generators/DHBasicKeyPairGenerator.cs b/bc-sharp-crypto/src/crypto/generators/DHBasicKeyPairGenerator.cs new file mode 100644 index 0000000..51b3af6 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/generators/DHBasicKeyPairGenerator.cs @@ -0,0 +1,38 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Crypto.Generators +{ + /** + * a basic Diffie-Hellman key pair generator. + * + * This generates keys consistent for use with the basic algorithm for + * Diffie-Hellman. + */ + public class DHBasicKeyPairGenerator + : IAsymmetricCipherKeyPairGenerator + { + private DHKeyGenerationParameters param; + + public virtual void Init( + KeyGenerationParameters parameters) + { + this.param = (DHKeyGenerationParameters)parameters; + } + + public virtual AsymmetricCipherKeyPair GenerateKeyPair() + { + DHKeyGeneratorHelper helper = DHKeyGeneratorHelper.Instance; + DHParameters dhp = param.Parameters; + + BigInteger x = helper.CalculatePrivate(dhp, param.Random); + BigInteger y = helper.CalculatePublic(dhp, x); + + return new AsymmetricCipherKeyPair( + new DHPublicKeyParameters(y, dhp), + new DHPrivateKeyParameters(x, dhp)); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/generators/DHKeyGeneratorHelper.cs b/bc-sharp-crypto/src/crypto/generators/DHKeyGeneratorHelper.cs new file mode 100644 index 0000000..68aba64 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/generators/DHKeyGeneratorHelper.cs @@ -0,0 +1,72 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Math.EC.Multiplier; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Generators +{ + class DHKeyGeneratorHelper + { + internal static readonly DHKeyGeneratorHelper Instance = new DHKeyGeneratorHelper(); + + private DHKeyGeneratorHelper() + { + } + + internal BigInteger CalculatePrivate( + DHParameters dhParams, + SecureRandom random) + { + int limit = dhParams.L; + + if (limit != 0) + { + int minWeight = limit >> 2; + for (;;) + { + BigInteger x = new BigInteger(limit, random).SetBit(limit - 1); + if (WNafUtilities.GetNafWeight(x) >= minWeight) + { + return x; + } + } + } + + BigInteger min = BigInteger.Two; + int m = dhParams.M; + if (m != 0) + { + min = BigInteger.One.ShiftLeft(m - 1); + } + + BigInteger q = dhParams.Q; + if (q == null) + { + q = dhParams.P; + } + BigInteger max = q.Subtract(BigInteger.Two); + + { + int minWeight = max.BitLength >> 2; + for (;;) + { + BigInteger x = BigIntegers.CreateRandomInRange(min, max, random); + if (WNafUtilities.GetNafWeight(x) >= minWeight) + { + return x; + } + } + } + } + + internal BigInteger CalculatePublic( + DHParameters dhParams, + BigInteger x) + { + return dhParams.G.ModPow(x, dhParams.P); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/generators/DHKeyPairGenerator.cs b/bc-sharp-crypto/src/crypto/generators/DHKeyPairGenerator.cs new file mode 100644 index 0000000..3bf58ba --- /dev/null +++ b/bc-sharp-crypto/src/crypto/generators/DHKeyPairGenerator.cs @@ -0,0 +1,38 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Crypto.Generators +{ + /** + * 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 + : IAsymmetricCipherKeyPairGenerator + { + private DHKeyGenerationParameters param; + + public virtual void Init( + KeyGenerationParameters parameters) + { + this.param = (DHKeyGenerationParameters)parameters; + } + + public virtual AsymmetricCipherKeyPair GenerateKeyPair() + { + DHKeyGeneratorHelper helper = DHKeyGeneratorHelper.Instance; + DHParameters dhp = param.Parameters; + + BigInteger x = helper.CalculatePrivate(dhp, param.Random); + BigInteger y = helper.CalculatePublic(dhp, x); + + return new AsymmetricCipherKeyPair( + new DHPublicKeyParameters(y, dhp), + new DHPrivateKeyParameters(x, dhp)); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/generators/DHParametersGenerator.cs b/bc-sharp-crypto/src/crypto/generators/DHParametersGenerator.cs new file mode 100644 index 0000000..e752c84 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/generators/DHParametersGenerator.cs @@ -0,0 +1,45 @@ +using System; + +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Crypto.Parameters; + +namespace Org.BouncyCastle.Crypto.Generators +{ + public class DHParametersGenerator + { + private int size; + private int certainty; + private SecureRandom random; + + public virtual 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 virtual 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, BigInteger.Two, null); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/generators/DHParametersHelper.cs b/bc-sharp-crypto/src/crypto/generators/DHParametersHelper.cs new file mode 100644 index 0000000..3856904 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/generators/DHParametersHelper.cs @@ -0,0 +1,156 @@ +using System; + +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Math.EC.Multiplier; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Generators +{ + internal class DHParametersHelper + { + private static readonly BigInteger Six = BigInteger.ValueOf(6); + + private static readonly int[][] primeLists = BigInteger.primeLists; + private static readonly int[] primeProducts = BigInteger.primeProducts; + private static readonly BigInteger[] BigPrimeProducts = ConstructBigPrimeProducts(primeProducts); + + private static BigInteger[] ConstructBigPrimeProducts(int[] primeProducts) + { + BigInteger[] bpp = new BigInteger[primeProducts.Length]; + for (int i = 0; i < bpp.Length; ++i) + { + bpp[i] = BigInteger.ValueOf(primeProducts[i]); + } + return bpp; + } + + /* + * Finds a pair of prime BigInteger's {p, q: p = 2q + 1} + * + * (see: Handbook of Applied Cryptography 4.86) + */ + internal static BigInteger[] GenerateSafePrimes(int size, int certainty, SecureRandom random) + { + BigInteger p, q; + int qLength = size - 1; + int minWeight = size >> 2; + + if (size <= 32) + { + for (;;) + { + q = new BigInteger(qLength, 2, random); + + p = q.ShiftLeft(1).Add(BigInteger.One); + + if (!p.IsProbablePrime(certainty, true)) + continue; + + if (certainty > 2 && !q.IsProbablePrime(certainty, true)) + continue; + + break; + } + } + else + { + // Note: Modified from Java version for speed + for (;;) + { + q = new BigInteger(qLength, 0, random); + + retry: + for (int i = 0; i < primeLists.Length; ++i) + { + int test = q.Remainder(BigPrimeProducts[i]).IntValue; + + if (i == 0) + { + int rem3 = test % 3; + if (rem3 != 2) + { + int diff = 2 * rem3 + 2; + q = q.Add(BigInteger.ValueOf(diff)); + test = (test + diff) % primeProducts[i]; + } + } + + int[] primeList = primeLists[i]; + for (int j = 0; j < primeList.Length; ++j) + { + int prime = primeList[j]; + int qRem = test % prime; + if (qRem == 0 || qRem == (prime >> 1)) + { + q = q.Add(Six); + goto retry; + } + } + } + + if (q.BitLength != qLength) + continue; + + if (!q.RabinMillerTest(2, random, true)) + continue; + + p = q.ShiftLeft(1).Add(BigInteger.One); + + if (!p.RabinMillerTest(certainty, random, true)) + continue; + + if (certainty > 2 && !q.RabinMillerTest(certainty - 2, random, true)) + continue; + + /* + * Require a minimum weight of the NAF representation, since low-weight primes may be + * weak against a version of the number-field-sieve for the discrete-logarithm-problem. + * + * See "The number field sieve for integers of low weight", Oliver Schirokauer. + */ + if (WNafUtilities.GetNafWeight(p) < minWeight) + continue; + + 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 (see generateSafePrimes) + */ + internal static BigInteger SelectGenerator(BigInteger p, BigInteger q, SecureRandom random) + { + BigInteger pMinusTwo = p.Subtract(BigInteger.Two); + BigInteger g; + + /* + * (see: Handbook of Applied Cryptography 4.80) + */ +// do +// { +// g = BigIntegers.CreateRandomInRange(BigInteger.Two, pMinusTwo, random); +// } +// while (g.ModPow(BigInteger.Two, p).Equals(BigInteger.One) +// || g.ModPow(q, p).Equals(BigInteger.One)); + + /* + * RFC 2631 2.2.1.2 (and see: Handbook of Applied Cryptography 4.81) + */ + do + { + BigInteger h = BigIntegers.CreateRandomInRange(BigInteger.Two, pMinusTwo, random); + + g = h.ModPow(BigInteger.Two, p); + } + while (g.Equals(BigInteger.One)); + + return g; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/generators/DesEdeKeyGenerator.cs b/bc-sharp-crypto/src/crypto/generators/DesEdeKeyGenerator.cs new file mode 100644 index 0000000..904cc71 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/generators/DesEdeKeyGenerator.cs @@ -0,0 +1,67 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; + +namespace Org.BouncyCastle.Crypto.Generators +{ + public class DesEdeKeyGenerator + : DesKeyGenerator + { + public DesEdeKeyGenerator() + { + } + + internal DesEdeKeyGenerator( + int defaultStrength) + : base(defaultStrength) + { + } + + /** + * 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 + */ + protected override void engineInit( + KeyGenerationParameters parameters) + { + this.random = parameters.Random; + this.strength = (parameters.Strength + 7) / 8; + + if (strength == 0 || strength == (168 / 8)) + { + strength = DesEdeParameters.DesEdeKeyLength; + } + else if (strength == (112 / 8)) + { + strength = 2 * DesEdeParameters.DesKeyLength; + } + else if (strength != DesEdeParameters.DesEdeKeyLength + && strength != (2 * DesEdeParameters.DesKeyLength)) + { + throw new ArgumentException("DESede key must be " + + (DesEdeParameters.DesEdeKeyLength * 8) + " or " + + (2 * 8 * DesEdeParameters.DesKeyLength) + + " bits long."); + } + } + + protected override byte[] engineGenerateKey() + { + byte[] newKey = new byte[strength]; + + do + { + random.NextBytes(newKey); + DesEdeParameters.SetOddParity(newKey); + } + while (DesEdeParameters.IsWeakKey(newKey, 0, newKey.Length) || !DesEdeParameters.IsRealEdeKey(newKey, 0)); + + return newKey; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/generators/DesKeyGenerator.cs b/bc-sharp-crypto/src/crypto/generators/DesKeyGenerator.cs new file mode 100644 index 0000000..4c2051d --- /dev/null +++ b/bc-sharp-crypto/src/crypto/generators/DesKeyGenerator.cs @@ -0,0 +1,57 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; + +namespace Org.BouncyCastle.Crypto.Generators +{ + public class DesKeyGenerator + : CipherKeyGenerator + { + public DesKeyGenerator() + { + } + + internal DesKeyGenerator( + int defaultStrength) + : base(defaultStrength) + { + } + + /** + * 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 + */ + protected override void engineInit( + KeyGenerationParameters parameters) + { + base.engineInit(parameters); + + if (strength == 0 || strength == (56 / 8)) + { + strength = DesParameters.DesKeyLength; + } + else if (strength != DesParameters.DesKeyLength) + { + throw new ArgumentException( + "DES key must be " + (DesParameters.DesKeyLength * 8) + " bits long."); + } + } + + protected override byte[] engineGenerateKey() + { + byte[] newKey = new byte[DesParameters.DesKeyLength]; + + do + { + random.NextBytes(newKey); + DesParameters.SetOddParity(newKey); + } + while (DesParameters.IsWeakKey(newKey, 0)); + + return newKey; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/generators/DsaKeyPairGenerator.cs b/bc-sharp-crypto/src/crypto/generators/DsaKeyPairGenerator.cs new file mode 100644 index 0000000..1c9ce5a --- /dev/null +++ b/bc-sharp-crypto/src/crypto/generators/DsaKeyPairGenerator.cs @@ -0,0 +1,72 @@ +using System; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Math.EC.Multiplier; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Generators +{ + /** + * 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 + : IAsymmetricCipherKeyPairGenerator + { + private static readonly BigInteger One = BigInteger.One; + + private DsaKeyGenerationParameters param; + + public void Init( + KeyGenerationParameters parameters) + { + if (parameters == null) + throw new ArgumentNullException("parameters"); + + // Note: If we start accepting instances of KeyGenerationParameters, + // must apply constraint checking on strength (see DsaParametersGenerator.Init) + + this.param = (DsaKeyGenerationParameters) parameters; + } + + public AsymmetricCipherKeyPair GenerateKeyPair() + { + DsaParameters dsaParams = param.Parameters; + + BigInteger x = GeneratePrivateKey(dsaParams.Q, param.Random); + BigInteger y = CalculatePublicKey(dsaParams.P, dsaParams.G, x); + + return new AsymmetricCipherKeyPair( + new DsaPublicKeyParameters(y, dsaParams), + new DsaPrivateKeyParameters(x, dsaParams)); + } + + private static BigInteger GeneratePrivateKey(BigInteger q, SecureRandom random) + { + // B.1.2 Key Pair Generation by Testing Candidates + int minWeight = q.BitLength >> 2; + for (;;) + { + // TODO Prefer this method? (change test cases that used fixed random) + // B.1.1 Key Pair Generation Using Extra Random Bits + //BigInteger x = new BigInteger(q.BitLength + 64, random).Mod(q.Subtract(One)).Add(One); + + BigInteger x = BigIntegers.CreateRandomInRange(One, q.Subtract(One), random); + if (WNafUtilities.GetNafWeight(x) >= minWeight) + { + return x; + } + } + } + + private static BigInteger CalculatePublicKey(BigInteger p, BigInteger g, BigInteger x) + { + return g.ModPow(x, p); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/generators/DsaParametersGenerator.cs b/bc-sharp-crypto/src/crypto/generators/DsaParametersGenerator.cs new file mode 100644 index 0000000..d7ae3ec --- /dev/null +++ b/bc-sharp-crypto/src/crypto/generators/DsaParametersGenerator.cs @@ -0,0 +1,355 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Crypto.Generators +{ + /** + * Generate suitable parameters for DSA, in line with FIPS 186-2, or FIPS 186-3. + */ + public class DsaParametersGenerator + { + private IDigest digest; + private int L, N; + private int certainty; + private SecureRandom random; + private bool use186_3; + private int usageIndex; + + public DsaParametersGenerator() + : this(new Sha1Digest()) + { + } + + public DsaParametersGenerator(IDigest digest) + { + this.digest = digest; + } + + /// Initialise the generator + /// This form can only be used for older DSA (pre-DSA2) parameters + /// the size of keys in bits (from 512 up to 1024, and a multiple of 64) + /// measure of robustness of primes (at least 80 for FIPS 186-2 compliance) + /// the source of randomness to use + public virtual void Init( + int size, + int certainty, + SecureRandom random) + { + if (!IsValidDsaStrength(size)) + throw new ArgumentException("size must be from 512 - 1024 and a multiple of 64", "size"); + + this.use186_3 = false; + this.L = size; + this.N = GetDefaultN(size); + this.certainty = certainty; + this.random = random; + } + + /// Initialise the generator for DSA 2 + /// You must use this Init method if you need to generate parameters for DSA 2 keys + /// An instance of DsaParameterGenerationParameters used to configure this generator + public virtual void Init(DsaParameterGenerationParameters parameters) + { + // TODO Should we enforce the minimum 'certainty' values as per C.3 Table C.1? + this.use186_3 = true; + this.L = parameters.L; + this.N = parameters.N; + this.certainty = parameters.Certainty; + this.random = parameters.Random; + this.usageIndex = parameters.UsageIndex; + + if ((L < 1024 || L > 3072) || L % 1024 != 0) + throw new ArgumentException("Values must be between 1024 and 3072 and a multiple of 1024", "L"); + if (L == 1024 && N != 160) + throw new ArgumentException("N must be 160 for L = 1024"); + if (L == 2048 && (N != 224 && N != 256)) + throw new ArgumentException("N must be 224 or 256 for L = 2048"); + if (L == 3072 && N != 256) + throw new ArgumentException("N must be 256 for L = 3072"); + + if (digest.GetDigestSize() * 8 < N) + throw new InvalidOperationException("Digest output size too small for value of N"); + } + + /// Generates a set of DsaParameters + /// Can take a while... + public virtual DsaParameters GenerateParameters() + { + return use186_3 + ? GenerateParameters_FIPS186_3() + : GenerateParameters_FIPS186_2(); + } + + protected virtual DsaParameters GenerateParameters_FIPS186_2() + { + byte[] seed = new byte[20]; + byte[] part1 = new byte[20]; + byte[] part2 = new byte[20]; + byte[] u = new byte[20]; + int n = (L - 1) / 160; + byte[] w = new byte[L / 8]; + + if (!(digest is Sha1Digest)) + throw new InvalidOperationException("can only use SHA-1 for generating FIPS 186-2 parameters"); + + for (;;) + { + random.NextBytes(seed); + + Hash(digest, seed, part1); + Array.Copy(seed, 0, part2, 0, seed.Length); + Inc(part2); + Hash(digest, part2, part2); + + for (int i = 0; i != u.Length; i++) + { + u[i] = (byte)(part1[i] ^ part2[i]); + } + + u[0] |= (byte)0x80; + u[19] |= (byte)0x01; + + BigInteger q = new BigInteger(1, u); + + if (!q.IsProbablePrime(certainty)) + 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(digest, offset, part1); + Array.Copy(part1, 0, w, w.Length - (k + 1) * part1.Length, part1.Length); + } + + Inc(offset); + Hash(digest, offset, part1); + Array.Copy(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(BigInteger.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)); + } + } + } + } + + protected virtual BigInteger CalculateGenerator_FIPS186_2(BigInteger p, BigInteger q, SecureRandom r) + { + BigInteger e = p.Subtract(BigInteger.One).Divide(q); + BigInteger pSub2 = p.Subtract(BigInteger.Two); + + for (;;) + { + BigInteger h = BigIntegers.CreateRandomInRange(BigInteger.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. + */ + protected virtual DsaParameters GenerateParameters_FIPS186_3() + { +// A.1.1.2 Generation of the Probable Primes p and q Using an Approved Hash Function + IDigest d = digest; + int outlen = d.GetDigestSize() * 8; + +// 1. Check that the (L, N) pair is in the list of acceptable (L, N pairs) (see Section 4.2). If +// the pair is not in the list, then return INVALID. + // Note: checked at initialisation + +// 2. If (seedlen < N), then return INVALID. + // FIXME This should be configurable (must be >= N) + int seedlen = N; + byte[] seed = new byte[seedlen / 8]; + +// 3. n = ceiling(L ⁄ outlen) – 1. + int n = (L - 1) / outlen; + +// 4. b = L – 1 – (n ∗ outlen). + int b = (L - 1) % outlen; + + byte[] output = new byte[d.GetDigestSize()]; + for (;;) + { +// 5. Get an arbitrary sequence of seedlen bits as the domain_parameter_seed. + random.NextBytes(seed); + +// 6. U = Hash (domain_parameter_seed) mod 2^(N–1). + Hash(d, seed, output); + BigInteger U = new BigInteger(1, output).Mod(BigInteger.One.ShiftLeft(N - 1)); + +// 7. q = 2^(N–1) + U + 1 – ( U mod 2). + BigInteger q = U.SetBit(0).SetBit(N - 1); + +// 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 = BigInteger.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(BigInteger.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(BigInteger.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(BigInteger.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)? + + if (usageIndex >= 0) + { + BigInteger g = CalculateGenerator_FIPS186_3_Verifiable(d, p, q, seed, usageIndex); + if (g != null) + return new DsaParameters(p, q, g, new DsaValidationParameters(seed, counter, usageIndex)); + } + + { + BigInteger g = CalculateGenerator_FIPS186_3_Unverifiable(p, q, random); + + return new DsaParameters(p, q, g, new DsaValidationParameters(seed, counter)); + } + } + +// 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. + } + } + + protected virtual BigInteger CalculateGenerator_FIPS186_3_Unverifiable(BigInteger p, BigInteger q, + SecureRandom r) + { + return CalculateGenerator_FIPS186_2(p, q, r); + } + + protected virtual BigInteger CalculateGenerator_FIPS186_3_Verifiable(IDigest d, BigInteger p, BigInteger q, + byte[] seed, int index) + { + // A.2.3 Verifiable Canonical Generation of the Generator g + BigInteger e = p.Subtract(BigInteger.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]; + Array.Copy(seed, 0, U, 0, seed.Length); + Array.Copy(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(BigInteger.Two) >= 0) + return g; + } + + return null; + } + + private static bool IsValidDsaStrength( + int strength) + { + return strength >= 512 && strength <= 1024 && strength % 64 == 0; + } + + protected static void Hash(IDigest d, byte[] input, byte[] output) + { + d.BlockUpdate(input, 0, input.Length); + d.DoFinal(output, 0); + } + + private static int GetDefaultN(int L) + { + return L > 1024 ? 256 : 160; + } + + protected static void Inc(byte[] buf) + { + for (int i = buf.Length - 1; i >= 0; --i) + { + byte b = (byte)(buf[i] + 1); + buf[i] = b; + + if (b != 0) + break; + } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/generators/ECKeyPairGenerator.cs b/bc-sharp-crypto/src/crypto/generators/ECKeyPairGenerator.cs new file mode 100644 index 0000000..26bc06e --- /dev/null +++ b/bc-sharp-crypto/src/crypto/generators/ECKeyPairGenerator.cs @@ -0,0 +1,162 @@ +using System; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Nist; +using Org.BouncyCastle.Asn1.Sec; +using Org.BouncyCastle.Asn1.TeleTrust; +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.EC; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Math.EC; +using Org.BouncyCastle.Math.EC.Multiplier; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Generators +{ + public class ECKeyPairGenerator + : IAsymmetricCipherKeyPairGenerator + { + private readonly string algorithm; + + private ECDomainParameters parameters; + private DerObjectIdentifier publicKeyParamSet; + private SecureRandom random; + + public ECKeyPairGenerator() + : this("EC") + { + } + + public ECKeyPairGenerator( + string algorithm) + { + if (algorithm == null) + throw new ArgumentNullException("algorithm"); + + this.algorithm = ECKeyParameters.VerifyAlgorithmName(algorithm); + } + + public void Init( + KeyGenerationParameters parameters) + { + if (parameters is ECKeyGenerationParameters) + { + ECKeyGenerationParameters ecP = (ECKeyGenerationParameters) parameters; + + this.publicKeyParamSet = ecP.PublicKeyParamSet; + this.parameters = ecP.DomainParameters; + } + else + { + DerObjectIdentifier oid; + switch (parameters.Strength) + { + case 192: + oid = X9ObjectIdentifiers.Prime192v1; + break; + case 224: + oid = SecObjectIdentifiers.SecP224r1; + break; + case 239: + oid = X9ObjectIdentifiers.Prime239v1; + break; + case 256: + oid = X9ObjectIdentifiers.Prime256v1; + break; + case 384: + oid = SecObjectIdentifiers.SecP384r1; + break; + case 521: + oid = SecObjectIdentifiers.SecP521r1; + break; + default: + throw new InvalidParameterException("unknown key size."); + } + + X9ECParameters ecps = FindECCurveByOid(oid); + + this.publicKeyParamSet = oid; + this.parameters = new ECDomainParameters( + ecps.Curve, ecps.G, ecps.N, ecps.H, ecps.GetSeed()); + } + + this.random = parameters.Random; + + if (this.random == null) + { + this.random = new SecureRandom(); + } + } + + /** + * 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 = parameters.N; + BigInteger d; + int minWeight = n.BitLength >> 2; + + for (;;) + { + d = new BigInteger(n.BitLength, random); + + if (d.CompareTo(BigInteger.Two) < 0 || d.CompareTo(n) >= 0) + continue; + + if (WNafUtilities.GetNafWeight(d) < minWeight) + continue; + + break; + } + + ECPoint q = CreateBasePointMultiplier().Multiply(parameters.G, d); + + if (publicKeyParamSet != null) + { + return new AsymmetricCipherKeyPair( + new ECPublicKeyParameters(algorithm, q, publicKeyParamSet), + new ECPrivateKeyParameters(algorithm, d, publicKeyParamSet)); + } + + return new AsymmetricCipherKeyPair( + new ECPublicKeyParameters(algorithm, q, parameters), + new ECPrivateKeyParameters(algorithm, d, parameters)); + } + + protected virtual ECMultiplier CreateBasePointMultiplier() + { + return new FixedPointCombMultiplier(); + } + + internal static X9ECParameters FindECCurveByOid(DerObjectIdentifier oid) + { + // TODO ECGost3410NamedCurves support (returns ECDomainParameters though) + + X9ECParameters ecP = CustomNamedCurves.GetByOid(oid); + if (ecP == null) + { + ecP = ECNamedCurveTable.GetByOid(oid); + } + return ecP; + } + + internal static ECPublicKeyParameters GetCorrespondingPublicKey( + ECPrivateKeyParameters privKey) + { + ECDomainParameters ec = privKey.Parameters; + ECPoint q = new FixedPointCombMultiplier().Multiply(ec.G, privKey.D); + + if (privKey.PublicKeyParamSet != null) + { + return new ECPublicKeyParameters(privKey.AlgorithmName, q, privKey.PublicKeyParamSet); + } + + return new ECPublicKeyParameters(privKey.AlgorithmName, q, ec); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/generators/ElGamalKeyPairGenerator.cs b/bc-sharp-crypto/src/crypto/generators/ElGamalKeyPairGenerator.cs new file mode 100644 index 0000000..227e7fe --- /dev/null +++ b/bc-sharp-crypto/src/crypto/generators/ElGamalKeyPairGenerator.cs @@ -0,0 +1,40 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Crypto.Generators +{ + /** + * 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 + : IAsymmetricCipherKeyPairGenerator + { + private ElGamalKeyGenerationParameters param; + + public void Init( + KeyGenerationParameters parameters) + { + this.param = (ElGamalKeyGenerationParameters) parameters; + } + + public AsymmetricCipherKeyPair GenerateKeyPair() + { + DHKeyGeneratorHelper helper = DHKeyGeneratorHelper.Instance; + ElGamalParameters egp = param.Parameters; + DHParameters dhp = new DHParameters(egp.P, egp.G, null, 0, egp.L); + + BigInteger x = helper.CalculatePrivate(dhp, param.Random); + BigInteger y = helper.CalculatePublic(dhp, x); + + return new AsymmetricCipherKeyPair( + new ElGamalPublicKeyParameters(y, egp), + new ElGamalPrivateKeyParameters(x, egp)); + } + } + +} diff --git a/bc-sharp-crypto/src/crypto/generators/ElGamalParametersGenerator.cs b/bc-sharp-crypto/src/crypto/generators/ElGamalParametersGenerator.cs new file mode 100644 index 0000000..8443bb0 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/generators/ElGamalParametersGenerator.cs @@ -0,0 +1,46 @@ +using System; + +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Crypto.Parameters; + +namespace Org.BouncyCastle.Crypto.Generators +{ + 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/bc-sharp-crypto/src/crypto/generators/GOST3410KeyPairGenerator.cs b/bc-sharp-crypto/src/crypto/generators/GOST3410KeyPairGenerator.cs new file mode 100644 index 0000000..520820b --- /dev/null +++ b/bc-sharp-crypto/src/crypto/generators/GOST3410KeyPairGenerator.cs @@ -0,0 +1,82 @@ +using System; + +using Org.BouncyCastle.Asn1.CryptoPro; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Math.EC.Multiplier; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Generators +{ + /** + * a GOST3410 key pair generator. + * This generates GOST3410 keys in line with the method described + * in GOST R 34.10-94. + */ + public class Gost3410KeyPairGenerator + : IAsymmetricCipherKeyPairGenerator + { + private Gost3410KeyGenerationParameters param; + + public void Init( + KeyGenerationParameters parameters) + { + if (parameters is Gost3410KeyGenerationParameters) + { + this.param = (Gost3410KeyGenerationParameters) parameters; + } + else + { + Gost3410KeyGenerationParameters kgp = new Gost3410KeyGenerationParameters( + parameters.Random, + CryptoProObjectIdentifiers.GostR3410x94CryptoProA); + + if (parameters.Strength != kgp.Parameters.P.BitLength - 1) + { + // TODO Should we complain? + } + + this.param = kgp; + } + } + + public AsymmetricCipherKeyPair GenerateKeyPair() + { + SecureRandom random = param.Random; + Gost3410Parameters gost3410Params = param.Parameters; + + BigInteger q = gost3410Params.Q, x; + + int minWeight = 64; + for (;;) + { + x = new BigInteger(256, random); + + if (x.SignValue < 1 || x.CompareTo(q) >= 0) + continue; + + if (WNafUtilities.GetNafWeight(x) < minWeight) + continue; + + break; + } + + BigInteger p = gost3410Params.P; + BigInteger a = gost3410Params.A; + + // calculate the public key. + BigInteger y = a.ModPow(x, p); + + if (param.PublicKeyParamSet != null) + { + return new AsymmetricCipherKeyPair( + new Gost3410PublicKeyParameters(y, param.PublicKeyParamSet), + new Gost3410PrivateKeyParameters(x, param.PublicKeyParamSet)); + } + + return new AsymmetricCipherKeyPair( + new Gost3410PublicKeyParameters(y, gost3410Params), + new Gost3410PrivateKeyParameters(x, gost3410Params)); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/generators/GOST3410ParametersGenerator.cs b/bc-sharp-crypto/src/crypto/generators/GOST3410ParametersGenerator.cs new file mode 100644 index 0000000..52a9f5a --- /dev/null +++ b/bc-sharp-crypto/src/crypto/generators/GOST3410ParametersGenerator.cs @@ -0,0 +1,530 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Generators +{ + /** + * generate suitable parameters for GOST3410. + */ + public class Gost3410ParametersGenerator + { + private int size; + private int typeproc; + private SecureRandom init_random; + + /** + * initialise the key generator. + * + * @param size size of the key + * @param typeProcedure type procedure A,B = 1; A',B' - else + * @param random random byte source. + */ + public void Init( + int size, + int typeProcedure, + SecureRandom random) + { + this.size = size; + this.typeproc = typeProcedure; + 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 = BigInteger.ValueOf(c); + BigInteger constA16 = BigInteger.ValueOf(19381); + + //step1 + BigInteger[] y = new BigInteger[1]; // begin length = 1 + y[0] = BigInteger.ValueOf(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]; /////////////// + Array.Copy(t,0,tmp_t,0,t.Length); // extension + t = new int[tmp_t.Length]; // array t + Array.Copy(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 t[m]) + { + goto step6; //step 12 + } + + p[m] = NByLastP.Add(BigInteger.One); + + //step13 + if (BigInteger.Two.ModPow(NByLastP, p[m]).CompareTo(BigInteger.One) == 0 + && BigInteger.Two.ModPow(N, p[m]).CompareTo(BigInteger.One) != 0) + { + break; + } + + N = N.Add(BigInteger.Two); + } + + if (--m < 0) + { + pq[0] = p[0]; + pq[1] = p[1]; + return y[0].IntValue; //return for procedure B step 2 + } + + break; //step 14 + } + } + 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 = BigInteger.ValueOf(c); + BigInteger constA32 = BigInteger.ValueOf(97781173); + + //step1 + BigInteger[] y = new BigInteger[1]; // begin length = 1 + y[0] = BigInteger.ValueOf(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]; /////////////// + Array.Copy(t,0,tmp_t,0,t.Length); // extension + t = new int[tmp_t.Length]; // array t + Array.Copy(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 t[m]) + { + goto step6; //step 12 + } + + p[m] = NByLastP.Add(BigInteger.One); + + //step13 + if (BigInteger.Two.ModPow(NByLastP, p[m]).CompareTo(BigInteger.One) == 0 + && BigInteger.Two.ModPow(N, p[m]).CompareTo(BigInteger.One) != 0) + { + break; + } + + N = N.Add(BigInteger.Two); + } + + if (--m < 0) + { + pq[0] = p[0]; + pq[1] = p[1]; + return y[0].LongValue; //return for procedure B' step 2 + } + + break; //step 14 + } + } + 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 = BigInteger.ValueOf(c); + BigInteger constA16 = BigInteger.ValueOf(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] = BigInteger.ValueOf(x0); + + const int tp = 1024; + + BigInteger qQ = q.Multiply(Q); + +step3: + for(;;) + { + //step 3 + for (int j=0; j<64; j++) + { + y[j+1] = (y[j].Multiply(constA16).Add(C)).Mod(BigInteger.Two.Pow(16)); + } + + //step 4 + BigInteger Y = BigInteger.Zero; + + for (int j=0; j<64; j++) + { + Y = Y.Add(y[j].ShiftLeft(16*j)); + } + + y[0] = y[64]; //step 5 + + //step 6 + BigInteger N = BigInteger.One.ShiftLeft(tp-1).Divide(qQ).Add( + Y.ShiftLeft(tp-1).Divide(qQ.ShiftLeft(1024))); + + if (N.TestBit(0)) + { + N = N.Add(BigInteger.One); + } + + //step 7 + + for(;;) + { + //step 11 + BigInteger qQN = qQ.Multiply(N); + + if (qQN.BitLength > tp) + { + goto step3; //step 9 + } + + p = qQN.Add(BigInteger.One); + + //step10 + if (BigInteger.Two.ModPow(qQN, p).CompareTo(BigInteger.One) == 0 + && BigInteger.Two.ModPow(q.Multiply(N), p).CompareTo(BigInteger.One) != 0) + { + pq[0] = p; + pq[1] = q; + return; + } + + N = N.Add(BigInteger.Two); + } + } + } + + //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 = BigInteger.ValueOf(c); + BigInteger constA32 = BigInteger.ValueOf(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] = BigInteger.ValueOf(x0); + + const int tp = 1024; + + BigInteger qQ = q.Multiply(Q); + +step3: + for(;;) + { + //step 3 + for (int j=0; j<32; j++) + { + y[j+1] = (y[j].Multiply(constA32).Add(C)).Mod(BigInteger.Two.Pow(32)); + } + + //step 4 + BigInteger Y = BigInteger.Zero; + for (int j=0; j<32; j++) + { + Y = Y.Add(y[j].ShiftLeft(32*j)); + } + + y[0] = y[32]; //step 5 + + //step 6 + BigInteger N = BigInteger.One.ShiftLeft(tp-1).Divide(qQ).Add( + Y.ShiftLeft(tp-1).Divide(qQ.ShiftLeft(1024))); + + if (N.TestBit(0)) + { + N = N.Add(BigInteger.One); + } + + //step 7 + + for(;;) + { + //step 11 + BigInteger qQN = qQ.Multiply(N); + + if (qQN.BitLength > tp) + { + goto step3; //step 9 + } + + p = qQN.Add(BigInteger.One); + + //step10 + if (BigInteger.Two.ModPow(qQN, p).CompareTo(BigInteger.One) == 0 + && BigInteger.Two.ModPow(q.Multiply(N), p).CompareTo(BigInteger.One) != 0) + { + pq[0] = p; + pq[1] = q; + return; + } + + N = N.Add(BigInteger.Two); + } + } + } + + + /** + * 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(BigInteger.One); + BigInteger pSub1Divq = pSub1.Divide(q); + + for(;;) + { + BigInteger d = new BigInteger(p.BitLength, init_random); + + // 1 < d < p-1 + if (d.CompareTo(BigInteger.One) > 0 && d.CompareTo(pSub1) < 0) + { + BigInteger a = d.ModPow(pSub1Divq, p); + + if (a.CompareTo(BigInteger.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 ArgumentException("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 InvalidOperationException("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/bc-sharp-crypto/src/crypto/generators/HKDFBytesGenerator.cs b/bc-sharp-crypto/src/crypto/generators/HKDFBytesGenerator.cs new file mode 100644 index 0000000..c2e667c --- /dev/null +++ b/bc-sharp-crypto/src/crypto/generators/HKDFBytesGenerator.cs @@ -0,0 +1,153 @@ +using System; + +using Org.BouncyCastle.Crypto.Macs; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Generators +{ + /** + * HMAC-based Extract-and-Expand Key Derivation Function (HKDF) implemented + * according to IETF RFC 5869, May 2010 as specified by H. Krawczyk, IBM + * Research & P. Eronen, Nokia. It uses a HMac internally to compute de OKM + * (output keying material) and is likely to have better security properties + * than KDF's based on just a hash function. + */ + public class HkdfBytesGenerator + : IDerivationFunction + { + private HMac hMacHash; + private int hashLen; + + private byte[] info; + private byte[] currentT; + + private int generatedBytes; + + /** + * Creates a HKDFBytesGenerator based on the given hash function. + * + * @param hash the digest to be used as the source of generatedBytes bytes + */ + public HkdfBytesGenerator(IDigest hash) + { + this.hMacHash = new HMac(hash); + this.hashLen = hash.GetDigestSize(); + } + + public virtual void Init(IDerivationParameters parameters) + { + if (!(parameters is HkdfParameters)) + throw new ArgumentException("HKDF parameters required for HkdfBytesGenerator", "parameters"); + + HkdfParameters hkdfParameters = (HkdfParameters)parameters; + if (hkdfParameters.SkipExtract) + { + // use IKM directly as PRK + hMacHash.Init(new KeyParameter(hkdfParameters.GetIkm())); + } + else + { + hMacHash.Init(Extract(hkdfParameters.GetSalt(), hkdfParameters.GetIkm())); + } + + info = hkdfParameters.GetInfo(); + + generatedBytes = 0; + currentT = new byte[hashLen]; + } + + /** + * Performs the extract part of the key derivation function. + * + * @param salt the salt to use + * @param ikm the input keying material + * @return the PRK as KeyParameter + */ + private KeyParameter Extract(byte[] salt, byte[] ikm) + { + hMacHash.Init(new KeyParameter(ikm)); + if (salt == null) + { + // TODO check if hashLen is indeed same as HMAC size + hMacHash.Init(new KeyParameter(new byte[hashLen])); + } + else + { + hMacHash.Init(new KeyParameter(salt)); + } + + hMacHash.BlockUpdate(ikm, 0, ikm.Length); + + byte[] prk = new byte[hashLen]; + hMacHash.DoFinal(prk, 0); + return new KeyParameter(prk); + } + + /** + * Performs the expand part of the key derivation function, using currentT + * as input and output buffer. + * + * @throws DataLengthException if the total number of bytes generated is larger than the one + * specified by RFC 5869 (255 * HashLen) + */ + private void ExpandNext() + { + int n = generatedBytes / hashLen + 1; + if (n >= 256) + { + throw new DataLengthException( + "HKDF cannot generate more than 255 blocks of HashLen size"); + } + // special case for T(0): T(0) is empty, so no update + if (generatedBytes != 0) + { + hMacHash.BlockUpdate(currentT, 0, hashLen); + } + hMacHash.BlockUpdate(info, 0, info.Length); + hMacHash.Update((byte)n); + hMacHash.DoFinal(currentT, 0); + } + + public virtual IDigest Digest + { + get { return hMacHash.GetUnderlyingDigest(); } + } + + public virtual int GenerateBytes(byte[] output, int outOff, int len) + { + if (generatedBytes + len > 255 * hashLen) + { + throw new DataLengthException( + "HKDF may only be used for 255 * HashLen bytes of output"); + } + + if (generatedBytes % hashLen == 0) + { + ExpandNext(); + } + + // copy what is left in the currentT (1..hash + int toGenerate = len; + int posInT = generatedBytes % hashLen; + int leftInT = hashLen - generatedBytes % hashLen; + int toCopy = System.Math.Min(leftInT, toGenerate); + Array.Copy(currentT, posInT, output, outOff, toCopy); + generatedBytes += toCopy; + toGenerate -= toCopy; + outOff += toCopy; + + while (toGenerate > 0) + { + ExpandNext(); + toCopy = System.Math.Min(hashLen, toGenerate); + Array.Copy(currentT, 0, output, outOff, toCopy); + generatedBytes += toCopy; + toGenerate -= toCopy; + outOff += toCopy; + } + + return len; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/generators/Kdf1BytesGenerator.cs b/bc-sharp-crypto/src/crypto/generators/Kdf1BytesGenerator.cs new file mode 100644 index 0000000..0ddf6c1 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/generators/Kdf1BytesGenerator.cs @@ -0,0 +1,26 @@ +using System; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; + +namespace Org.BouncyCastle.Crypto.Generators +{ + /** + * 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 Kdf1BytesGenerator + : BaseKdfBytesGenerator + { + /** + * Construct a KDF1 byte generator. + * + * @param digest the digest to be used as the source of derived keys. + */ + public Kdf1BytesGenerator(IDigest digest) + : base(0, digest) + { + } + } +} diff --git a/bc-sharp-crypto/src/crypto/generators/Kdf2BytesGenerator.cs b/bc-sharp-crypto/src/crypto/generators/Kdf2BytesGenerator.cs new file mode 100644 index 0000000..8a68219 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/generators/Kdf2BytesGenerator.cs @@ -0,0 +1,27 @@ +using System; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; + +namespace Org.BouncyCastle.Crypto.Generators +{ + /** + * KDF2 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 + : 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(IDigest digest) + : base(1, digest) + { + } + } +} diff --git a/bc-sharp-crypto/src/crypto/generators/Mgf1BytesGenerator.cs b/bc-sharp-crypto/src/crypto/generators/Mgf1BytesGenerator.cs new file mode 100644 index 0000000..23a3aca --- /dev/null +++ b/bc-sharp-crypto/src/crypto/generators/Mgf1BytesGenerator.cs @@ -0,0 +1,117 @@ +using System; +//using Org.BouncyCastle.Math; +//using Org.BouncyCastle.Security; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; + +namespace Org.BouncyCastle.Crypto.Generators +{ + /** + * Generator for MGF1 as defined in Pkcs 1v2 + */ + public class Mgf1BytesGenerator : IDerivationFunction + { + private IDigest digest; + private byte[] seed; + private int hLen; + + /** + * @param digest the digest to be used as the source of Generated bytes + */ + public Mgf1BytesGenerator( + IDigest digest) + { + this.digest = digest; + this.hLen = digest.GetDigestSize(); + } + + public void Init( + IDerivationParameters parameters) + { + if (!(typeof(MgfParameters).IsInstanceOfType(parameters))) + { + throw new ArgumentException("MGF parameters required for MGF1Generator"); + } + + MgfParameters p = (MgfParameters)parameters; + + seed = p.GetSeed(); + } + + /** + * return the underlying digest. + */ + public IDigest Digest + { + get + { + return digest; + } + } + + /** + * int to octet string. + */ + private void ItoOSP( + int i, + byte[] sp) + { + sp[0] = (byte)((uint) i >> 24); + sp[1] = (byte)((uint) i >> 16); + sp[2] = (byte)((uint) i >> 8); + sp[3] = (byte)((uint) 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[] output, + int outOff, + int length) + { + if ((output.Length - length) < outOff) + { + throw new DataLengthException("output buffer too small"); + } + + byte[] hashBuf = new byte[hLen]; + byte[] C = new byte[4]; + int counter = 0; + + digest.Reset(); + + if (length > hLen) + { + do + { + ItoOSP(counter, C); + + digest.BlockUpdate(seed, 0, seed.Length); + digest.BlockUpdate(C, 0, C.Length); + digest.DoFinal(hashBuf, 0); + + Array.Copy(hashBuf, 0, output, outOff + counter * hLen, hLen); + } + while (++counter < (length / hLen)); + } + + if ((counter * hLen) < length) + { + ItoOSP(counter, C); + + digest.BlockUpdate(seed, 0, seed.Length); + digest.BlockUpdate(C, 0, C.Length); + digest.DoFinal(hashBuf, 0); + + Array.Copy(hashBuf, 0, output, outOff + counter * hLen, length - (counter * hLen)); + } + + return length; + } + } + +} diff --git a/bc-sharp-crypto/src/crypto/generators/NaccacheSternKeyPairGenerator.cs b/bc-sharp-crypto/src/crypto/generators/NaccacheSternKeyPairGenerator.cs new file mode 100644 index 0000000..618ca9a --- /dev/null +++ b/bc-sharp-crypto/src/crypto/generators/NaccacheSternKeyPairGenerator.cs @@ -0,0 +1,268 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; + +namespace Org.BouncyCastle.Crypto.Generators +{ + /** + * 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 + : IAsymmetricCipherKeyPairGenerator + { + private static readonly 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; + + /* + * (non-Javadoc) + * + * @see org.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator#init(org.bouncycastle.crypto.KeyGenerationParameters) + */ + public void Init(KeyGenerationParameters parameters) + { + this.param = (NaccacheSternKeyGenerationParameters)parameters; + } + + /* + * (non-Javadoc) + * + * @see org.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator#generateKeyPair() + */ + public AsymmetricCipherKeyPair GenerateKeyPair() + { + int strength = param.Strength; + SecureRandom rand = param.Random; + int certainty = param.Certainty; + + IList smallPrimes = findFirstPrimes(param.CountSmallPrimes); + + smallPrimes = permuteList(smallPrimes, rand); + + BigInteger u = BigInteger.One; + BigInteger v = BigInteger.One; + + for (int i = 0; i < smallPrimes.Count / 2; i++) + { + u = u.Multiply((BigInteger)smallPrimes[i]); + } + for (int i = smallPrimes.Count / 2; i < smallPrimes.Count; i++) + { + v = v.Multiply((BigInteger)smallPrimes[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; + + 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(BigInteger.One); + + if (!p.IsProbablePrime(certainty, true)) + continue; + + for (;;) + { + _q = generatePrime(24, certainty, rand); + + if (_p.Equals(_q)) + continue; + + q = _q.Multiply(_2bv).Add(BigInteger.One); + + if (q.IsProbablePrime(certainty, true)) + break; + } + + if (!sigma.Gcd(_p.Multiply(_q)).Equals(BigInteger.One)) + { + //Console.WriteLine("sigma.gcd(_p.mult(_q)) != 1!\n _p: " + _p +"\n _q: "+ _q ); + continue; + } + + if (p.Multiply(q).BitLength < strength) + { + continue; + } + break; + } + + BigInteger n = p.Multiply(q); + BigInteger phi_n = p.Subtract(BigInteger.One).Multiply(q.Subtract(BigInteger.One)); + BigInteger g; + tries = 0; + + for (;;) + { + // TODO After the first loop, just regenerate one randomly-selected gPart each time? + IList gParts = Platform.CreateArrayList(); + for (int ind = 0; ind != smallPrimes.Count; ind++) + { + BigInteger i = (BigInteger)smallPrimes[ind]; + BigInteger e = phi_n.Divide(i); + + for (;;) + { + tries++; + + g = generatePrime(strength, certainty, rand); + + if (!g.ModPow(e, n).Equals(BigInteger.One)) + { + gParts.Add(g); + break; + } + } + } + g = BigInteger.One; + for (int i = 0; i < smallPrimes.Count; i++) + { + BigInteger gPart = (BigInteger) gParts[i]; + BigInteger smallPrime = (BigInteger) smallPrimes[i]; + g = g.Multiply(gPart.ModPow(sigma.Divide(smallPrime), n)).Mod(n); + } + + // make sure that g is not divisible by p_i or q_i + bool divisible = false; + for (int i = 0; i < smallPrimes.Count; i++) + { + if (g.ModPow(phi_n.Divide((BigInteger)smallPrimes[i]), n).Equals(BigInteger.One)) + { + 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(BigInteger.One)) + if (g.ModPow(phi_n.ShiftRight(2), n).Equals(BigInteger.One)) + { + continue; + } + + if (g.ModPow(phi_n.Divide(_p), n).Equals(BigInteger.One)) + { + continue; + } + if (g.ModPow(phi_n.Divide(_q), n).Equals(BigInteger.One)) + { + continue; + } + if (g.ModPow(phi_n.Divide(a), n).Equals(BigInteger.One)) + { + continue; + } + if (g.ModPow(phi_n.Divide(b), n).Equals(BigInteger.One)) + { + continue; + } + break; + } + + 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) + { + return new BigInteger(bitLength, certainty, rand); + } + + /** + * 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 IList with the permuted elements. + */ + private static IList permuteList( + IList arr, + SecureRandom rand) + { + // TODO Create a utility method for generating permutation of first 'n' integers + + IList retval = Platform.CreateArrayList(arr.Count); + + foreach (object element in arr) + { + int index = rand.Next(retval.Count + 1); + retval.Insert(index, element); + } + + return retval; + } + + /** + * 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 IList findFirstPrimes( + int count) + { + IList primes = Platform.CreateArrayList(count); + + for (int i = 0; i != count; i++) + { + primes.Add(BigInteger.ValueOf(smallPrimes[i])); + } + + return primes; + } + + } +} diff --git a/bc-sharp-crypto/src/crypto/generators/OpenBsdBCrypt.cs b/bc-sharp-crypto/src/crypto/generators/OpenBsdBCrypt.cs new file mode 100644 index 0000000..85c34d7 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/generators/OpenBsdBCrypt.cs @@ -0,0 +1,270 @@ +using System; +using System.IO; +using System.Text; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Generators +{ + /** + * Password hashing scheme BCrypt, + * designed by Niels Provos and David Mazières, using the + * String format and the Base64 encoding + * of the reference implementation on OpenBSD + */ + public class OpenBsdBCrypt + { + private static readonly byte[] EncodingTable = // the Bcrypts encoding table for OpenBSD + { + (byte)'.', (byte)'/', (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' + }; + + /* + * set up the decoding table. + */ + private static readonly byte[] DecodingTable = new byte[128]; + private static readonly string Version = "2a"; // previous version was not UTF-8 + + static OpenBsdBCrypt() + { + for (int i = 0; i < DecodingTable.Length; i++) + { + DecodingTable[i] = (byte)0xff; + } + + for (int i = 0; i < EncodingTable.Length; i++) + { + DecodingTable[EncodingTable[i]] = (byte)i; + } + } + + public OpenBsdBCrypt() + { + } + + /** + * Creates a 60 character Bcrypt String, including + * version, cost factor, salt and hash, separated by '$' + * + * @param cost the cost factor, treated as an exponent of 2 + * @param salt a 16 byte salt + * @param password the password + * @return a 60 character Bcrypt String + */ + private static string CreateBcryptString(byte[] password, byte[] salt, int cost) + { + StringBuilder sb = new StringBuilder(60); + sb.Append('$'); + sb.Append(Version); + sb.Append('$'); + sb.Append(cost < 10 ? ("0" + cost) : cost.ToString()); + sb.Append('$'); + sb.Append(EncodeData(salt)); + + byte[] key = BCrypt.Generate(password, salt, cost); + + sb.Append(EncodeData(key)); + + return sb.ToString(); + } + + /** + * Creates a 60 character Bcrypt String, including + * version, cost factor, salt and hash, separated by '$' + * + * @param cost the cost factor, treated as an exponent of 2 + * @param salt a 16 byte salt + * @param password the password + * @return a 60 character Bcrypt String + */ + public static string Generate(char[] password, byte[] salt, int cost) + { + if (password == null) + throw new ArgumentNullException("password"); + if (salt == null) + throw new ArgumentNullException("salt"); + if (salt.Length != 16) + throw new DataLengthException("16 byte salt required: " + salt.Length); + + if (cost < 4 || cost > 31) // Minimum rounds: 16, maximum 2^31 + throw new ArgumentException("Invalid cost factor.", "cost"); + + byte[] psw = Strings.ToUtf8ByteArray(password); + + // 0 termination: + + byte[] tmp = new byte[psw.Length >= 72 ? 72 : psw.Length + 1]; + int copyLen = System.Math.Min(psw.Length, tmp.Length); + Array.Copy(psw, 0, tmp, 0, copyLen); + + Array.Clear(psw, 0, psw.Length); + + string rv = CreateBcryptString(tmp, salt, cost); + + Array.Clear(tmp, 0, tmp.Length); + + return rv; + } + + /** + * Checks if a password corresponds to a 60 character Bcrypt String + * + * @param bcryptString a 60 character Bcrypt String, including + * version, cost factor, salt and hash, + * separated by '$' + * @param password the password as an array of chars + * @return true if the password corresponds to the + * Bcrypt String, otherwise false + */ + public static bool CheckPassword(string bcryptString, char[] password) + { + // validate bcryptString: + if (bcryptString.Length != 60) + throw new DataLengthException("Bcrypt String length: " + bcryptString.Length + ", 60 required."); + if (bcryptString[0] != '$' || bcryptString[3] != '$' || bcryptString[6] != '$') + throw new ArgumentException("Invalid Bcrypt String format.", "bcryptString"); + if (!bcryptString.Substring(1, 2).Equals(Version)) + throw new ArgumentException("Wrong Bcrypt version, 2a expected.", "bcryptString"); + + int cost = 0; + try + { + cost = Int32.Parse(bcryptString.Substring(4, 2)); + } + catch (Exception nfe) + { + throw new ArgumentException("Invalid cost factor: " + bcryptString.Substring(4, 2), "bcryptString"); + } + if (cost < 4 || cost > 31) + throw new ArgumentException("Invalid cost factor: " + cost + ", 4 < cost < 31 expected."); + + // check password: + if (password == null) + throw new ArgumentNullException("Missing password."); + + int start = bcryptString.LastIndexOf('$') + 1, end = bcryptString.Length - 31; + byte[] salt = DecodeSaltString(bcryptString.Substring(start, end - start)); + + string newBcryptString = Generate(password, salt, cost); + + return bcryptString.Equals(newBcryptString); + } + + /* + * encode the input data producing a Bcrypt base 64 string. + * + * @param a byte representation of the salt or the password + * @return the Bcrypt base64 string + */ + private static string EncodeData(byte[] data) + { + if (data.Length != 24 && data.Length != 16) // 192 bit key or 128 bit salt expected + throw new DataLengthException("Invalid length: " + data.Length + ", 24 for key or 16 for salt expected"); + + bool salt = false; + if (data.Length == 16)//salt + { + salt = true; + byte[] tmp = new byte[18];// zero padding + Array.Copy(data, 0, tmp, 0, data.Length); + data = tmp; + } + else // key + { + data[data.Length - 1] = (byte)0; + } + + MemoryStream mOut = new MemoryStream(); + int len = data.Length; + + uint a1, a2, a3; + int i; + for (i = 0; i < len; i += 3) + { + a1 = data[i]; + a2 = data[i + 1]; + a3 = data[i + 2]; + + mOut.WriteByte(EncodingTable[(a1 >> 2) & 0x3f]); + mOut.WriteByte(EncodingTable[((a1 << 4) | (a2 >> 4)) & 0x3f]); + mOut.WriteByte(EncodingTable[((a2 << 2) | (a3 >> 6)) & 0x3f]); + mOut.WriteByte(EncodingTable[a3 & 0x3f]); + } + + string result = Strings.FromByteArray(mOut.ToArray()); + int resultLen = salt + ? 22 // truncate padding + : result.Length - 1; + + return result.Substring(0, resultLen); + } + + + /* + * decodes the bcrypt base 64 encoded SaltString + * + * @param a 22 character Bcrypt base 64 encoded String + * @return the 16 byte salt + * @exception DataLengthException if the length + * of parameter is not 22 + * @exception InvalidArgumentException if the parameter + * contains a value other than from Bcrypts base 64 encoding table + */ + private static byte[] DecodeSaltString(string saltString) + { + char[] saltChars = saltString.ToCharArray(); + + MemoryStream mOut = new MemoryStream(16); + byte b1, b2, b3, b4; + + if (saltChars.Length != 22)// bcrypt salt must be 22 (16 bytes) + throw new DataLengthException("Invalid base64 salt length: " + saltChars.Length + " , 22 required."); + + // check string for invalid characters: + for (int i = 0; i < saltChars.Length; i++) + { + int value = saltChars[i]; + if (value > 122 || value < 46 || (value > 57 && value < 65)) + throw new ArgumentException("Salt string contains invalid character: " + value, "saltString"); + } + + // Padding: add two '\u0000' + char[] tmp = new char[22 + 2]; + Array.Copy(saltChars, 0, tmp, 0, saltChars.Length); + saltChars = tmp; + + int len = saltChars.Length; + + for (int i = 0; i < len; i += 4) + { + b1 = DecodingTable[saltChars[i]]; + b2 = DecodingTable[saltChars[i + 1]]; + b3 = DecodingTable[saltChars[i + 2]]; + b4 = DecodingTable[saltChars[i + 3]]; + + mOut.WriteByte((byte)((b1 << 2) | (b2 >> 4))); + mOut.WriteByte((byte)((b2 << 4) | (b3 >> 2))); + mOut.WriteByte((byte)((b3 << 6) | b4)); + } + + byte[] saltBytes = mOut.ToArray(); + + // truncate: + byte[] tmpSalt = new byte[16]; + Array.Copy(saltBytes, 0, tmpSalt, 0, tmpSalt.Length); + saltBytes = tmpSalt; + + return saltBytes; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/generators/OpenSSLPBEParametersGenerator.cs b/bc-sharp-crypto/src/crypto/generators/OpenSSLPBEParametersGenerator.cs new file mode 100644 index 0000000..8da5d3a --- /dev/null +++ b/bc-sharp-crypto/src/crypto/generators/OpenSSLPBEParametersGenerator.cs @@ -0,0 +1,167 @@ +using System; + +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Generators +{ + /** + * 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 + : PbeParametersGenerator + { + private readonly IDigest digest = new MD5Digest(); + + /** + * Construct a OpenSSL Parameters generator. + */ + public OpenSslPbeParametersGenerator() + { + } + + public override void Init( + byte[] password, + byte[] salt, + int iterationCount) + { + // Ignore the provided iterationCount + base.Init(password, salt, 1); + } + + /** + * Initialise - note the iteration count for this algorithm is fixed at 1. + * + * @param password password to use. + * @param salt salt to use. + */ + public virtual void Init( + byte[] password, + byte[] salt) + { + base.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.BlockUpdate(mPassword, 0, mPassword.Length); + digest.BlockUpdate(mSalt, 0, mSalt.Length); + + digest.DoFinal(buf, 0); + + int len = (bytesNeeded > buf.Length) ? buf.Length : bytesNeeded; + Array.Copy(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.BlockUpdate(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 ArgumentException if the key length larger than the base hash size. + */ + [Obsolete("Use version with 'algorithm' parameter")] + public override ICipherParameters GenerateDerivedParameters( + int keySize) + { + return GenerateDerivedMacParameters(keySize); + } + + public override ICipherParameters GenerateDerivedParameters( + string algorithm, + int keySize) + { + keySize /= 8; + + byte[] dKey = GenerateDerivedKey(keySize); + + return ParameterUtilities.CreateKeyParameter(algorithm, 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 ArgumentException if keySize + ivSize is larger than the base hash size. + */ + [Obsolete("Use version with 'algorithm' parameter")] + public override ICipherParameters 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); + } + + public override ICipherParameters GenerateDerivedParameters( + string algorithm, + int keySize, + int ivSize) + { + keySize /= 8; + ivSize /= 8; + + byte[] dKey = GenerateDerivedKey(keySize + ivSize); + KeyParameter key = ParameterUtilities.CreateKeyParameter(algorithm, dKey, 0, keySize); + + return new ParametersWithIV(key, 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 ArgumentException if the key length larger than the base hash size. + */ + public override ICipherParameters GenerateDerivedMacParameters( + int keySize) + { + keySize = keySize / 8; + + byte[] dKey = GenerateDerivedKey(keySize); + + return new KeyParameter(dKey, 0, keySize); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/generators/Pkcs12ParametersGenerator.cs b/bc-sharp-crypto/src/crypto/generators/Pkcs12ParametersGenerator.cs new file mode 100644 index 0000000..85543a0 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/generators/Pkcs12ParametersGenerator.cs @@ -0,0 +1,243 @@ +using System; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Generators +{ + /** + * 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 + : PbeParametersGenerator + { + public const int KeyMaterial = 1; + public const int IVMaterial = 2; + public const int MacMaterial = 3; + + private readonly IDigest digest; + + private readonly int u; + private readonly int v; + + /** + * Construct a Pkcs 12 Parameters generator. + * + * @param digest the digest to be used as the source of derived keys. + * @exception ArgumentException if an unknown digest is passed in. + */ + public Pkcs12ParametersGenerator( + IDigest digest) + { + this.digest = digest; + + u = digest.GetDigestSize(); + v = digest.GetByteLength(); + } + + /** + * 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 = (int) ((uint) 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 = (int) ((uint) 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 ((mSalt != null) && (mSalt.Length != 0)) + { + S = new byte[v * ((mSalt.Length + v - 1) / v)]; + + for (int i = 0; i != S.Length; i++) + { + S[i] = mSalt[i % mSalt.Length]; + } + } + else + { + S = new byte[0]; + } + + byte[] P; + + if ((mPassword != null) && (mPassword.Length != 0)) + { + P = new byte[v * ((mPassword.Length + v - 1) / v)]; + + for (int i = 0; i != P.Length; i++) + { + P[i] = mPassword[i % mPassword.Length]; + } + } + else + { + P = new byte[0]; + } + + byte[] I = new byte[S.Length + P.Length]; + + Array.Copy(S, 0, I, 0, S.Length); + Array.Copy(P, 0, I, S.Length, P.Length); + + byte[] B = new byte[v]; + int c = (n + u - 1) / u; + byte[] A = new byte[u]; + + for (int i = 1; i <= c; i++) + { + digest.BlockUpdate(D, 0, D.Length); + digest.BlockUpdate(I, 0, I.Length); + digest.DoFinal(A, 0); + + for (int j = 1; j != mIterationCount; j++) + { + digest.BlockUpdate(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) + { + Array.Copy(A, 0, dKey, (i - 1) * u, dKey.Length - ((i - 1) * u)); + } + else + { + Array.Copy(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 override ICipherParameters GenerateDerivedParameters( + int keySize) + { + keySize /= 8; + + byte[] dKey = GenerateDerivedKey(KeyMaterial, keySize); + + return new KeyParameter(dKey, 0, keySize); + } + + public override ICipherParameters GenerateDerivedParameters( + string algorithm, + int keySize) + { + keySize /= 8; + + byte[] dKey = GenerateDerivedKey(KeyMaterial, keySize); + + return ParameterUtilities.CreateKeyParameter(algorithm, 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 override ICipherParameters GenerateDerivedParameters( + int keySize, + int ivSize) + { + keySize /= 8; + ivSize /= 8; + + byte[] dKey = GenerateDerivedKey(KeyMaterial, keySize); + + byte[] iv = GenerateDerivedKey(IVMaterial, ivSize); + + return new ParametersWithIV(new KeyParameter(dKey, 0, keySize), iv, 0, ivSize); + } + + public override ICipherParameters GenerateDerivedParameters( + string algorithm, + int keySize, + int ivSize) + { + keySize /= 8; + ivSize /= 8; + + byte[] dKey = GenerateDerivedKey(KeyMaterial, keySize); + KeyParameter key = ParameterUtilities.CreateKeyParameter(algorithm, dKey, 0, keySize); + + byte[] iv = GenerateDerivedKey(IVMaterial, ivSize); + + return new ParametersWithIV(key, 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 override ICipherParameters GenerateDerivedMacParameters( + int keySize) + { + keySize /= 8; + + byte[] dKey = GenerateDerivedKey(MacMaterial, keySize); + + return new KeyParameter(dKey, 0, keySize); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/generators/Pkcs5S1ParametersGenerator.cs b/bc-sharp-crypto/src/crypto/generators/Pkcs5S1ParametersGenerator.cs new file mode 100644 index 0000000..9b39a5f --- /dev/null +++ b/bc-sharp-crypto/src/crypto/generators/Pkcs5S1ParametersGenerator.cs @@ -0,0 +1,160 @@ +using System; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Generators +{ + /** + * 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 + : PbeParametersGenerator + { + private readonly IDigest digest; + + /** + * Construct a Pkcs 5 Scheme 1 Parameters generator. + * + * @param digest the digest to be used as the source of derived keys. + */ + public Pkcs5S1ParametersGenerator( + IDigest digest) + { + this.digest = digest; + } + + /** + * the derived key function, the ith hash of the mPassword and the mSalt. + */ + private byte[] GenerateDerivedKey() + { + byte[] digestBytes = new byte[digest.GetDigestSize()]; + + digest.BlockUpdate(mPassword, 0, mPassword.Length); + digest.BlockUpdate(mSalt, 0, mSalt.Length); + + digest.DoFinal(digestBytes, 0); + for (int i = 1; i < mIterationCount; i++) + { + digest.BlockUpdate(digestBytes, 0, digestBytes.Length); + digest.DoFinal(digestBytes, 0); + } + + return digestBytes; + } + + /** + * Generate a key parameter derived from the mPassword, mSalt, and iteration + * count we are currently initialised with. + * + * @param keySize the size of the key we want (in bits) + * @return a KeyParameter object. + * @exception ArgumentException if the key length larger than the base hash size. + */ + public override ICipherParameters GenerateDerivedParameters( + int keySize) + { + return GenerateDerivedMacParameters(keySize); + } + + public override ICipherParameters GenerateDerivedParameters( + string algorithm, + int keySize) + { + keySize /= 8; + + if (keySize > digest.GetDigestSize()) + { + throw new ArgumentException( + "Can't Generate a derived key " + keySize + " bytes long."); + } + + byte[] dKey = GenerateDerivedKey(); + + return ParameterUtilities.CreateKeyParameter(algorithm, dKey, 0, keySize); + } + + /** + * Generate a key with initialisation vector parameter derived from + * the mPassword, mSalt, 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 ArgumentException if keySize + ivSize is larger than the base hash size. + */ + public override ICipherParameters GenerateDerivedParameters( + int keySize, + int ivSize) + { + keySize /= 8; + ivSize /= 8; + + if ((keySize + ivSize) > digest.GetDigestSize()) + { + throw new ArgumentException( + "Can't Generate a derived key " + (keySize + ivSize) + " bytes long."); + } + + byte[] dKey = GenerateDerivedKey(); + + return new ParametersWithIV(new KeyParameter(dKey, 0, keySize), dKey, keySize, ivSize); + } + + public override ICipherParameters GenerateDerivedParameters( + string algorithm, + int keySize, + int ivSize) + { + keySize /= 8; + ivSize /= 8; + + if ((keySize + ivSize) > digest.GetDigestSize()) + { + throw new ArgumentException( + "Can't Generate a derived key " + (keySize + ivSize) + " bytes long."); + } + + byte[] dKey = GenerateDerivedKey(); + KeyParameter key = ParameterUtilities.CreateKeyParameter(algorithm, dKey, 0, keySize); + + return new ParametersWithIV(key, dKey, keySize, ivSize); + } + + /** + * Generate a key parameter for use with a MAC derived from the mPassword, + * mSalt, and iteration count we are currently initialised with. + * + * @param keySize the size of the key we want (in bits) + * @return a KeyParameter object. + * @exception ArgumentException if the key length larger than the base hash size. + */ + public override ICipherParameters GenerateDerivedMacParameters( + int keySize) + { + keySize /= 8; + + if (keySize > digest.GetDigestSize()) + { + throw new ArgumentException( + "Can't Generate a derived key " + keySize + " bytes long."); + } + + byte[] dKey = GenerateDerivedKey(); + + return new KeyParameter(dKey, 0, keySize); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/generators/Pkcs5S2ParametersGenerator.cs b/bc-sharp-crypto/src/crypto/generators/Pkcs5S2ParametersGenerator.cs new file mode 100644 index 0000000..0b0caa0 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/generators/Pkcs5S2ParametersGenerator.cs @@ -0,0 +1,178 @@ +using System; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Macs; +using Org.BouncyCastle.Crypto.Utilities; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Generators +{ + /** + * 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 + : PbeParametersGenerator + { + private readonly IMac hMac; + private readonly byte[] state; + + /** + * construct a Pkcs5 Scheme 2 Parameters generator. + */ + public Pkcs5S2ParametersGenerator() + : this(new Sha1Digest()) + { + } + + public Pkcs5S2ParametersGenerator(IDigest digest) + { + this.hMac = new HMac(digest); + this.state = new byte[hMac.GetMacSize()]; + } + + private void F( + byte[] S, + int c, + byte[] iBuf, + byte[] outBytes, + int outOff) + { + if (c == 0) + throw new ArgumentException("iteration count must be at least 1."); + + if (S != null) + { + hMac.BlockUpdate(S, 0, S.Length); + } + + hMac.BlockUpdate(iBuf, 0, iBuf.Length); + hMac.DoFinal(state, 0); + + Array.Copy(state, 0, outBytes, outOff, state.Length); + + for (int count = 1; count < c; ++count) + { + hMac.BlockUpdate(state, 0, state.Length); + hMac.DoFinal(state, 0); + + for (int j = 0; j < state.Length; ++j) + { + outBytes[outOff + j] ^= state[j]; + } + } + } + + private byte[] GenerateDerivedKey( + int dkLen) + { + int hLen = hMac.GetMacSize(); + int l = (dkLen + hLen - 1) / hLen; + byte[] iBuf = new byte[4]; + byte[] outBytes = new byte[l * hLen]; + int outPos = 0; + + ICipherParameters param = new KeyParameter(mPassword); + + hMac.Init(param); + + for (int i = 1; i <= l; i++) + { + // Increment the value in 'iBuf' + int pos = 3; + while (++iBuf[pos] == 0) + { + --pos; + } + + F(mSalt, mIterationCount, iBuf, outBytes, outPos); + outPos += hLen; + } + + return outBytes; + } + + /** + * 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 override ICipherParameters GenerateDerivedParameters( + int keySize) + { + return GenerateDerivedMacParameters(keySize); + } + + public override ICipherParameters GenerateDerivedParameters( + string algorithm, + int keySize) + { + keySize /= 8; + + byte[] dKey = GenerateDerivedKey(keySize); + + return ParameterUtilities.CreateKeyParameter(algorithm, 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 override ICipherParameters GenerateDerivedParameters( + int keySize, + int ivSize) + { + keySize /= 8; + ivSize /= 8; + + byte[] dKey = GenerateDerivedKey(keySize + ivSize); + + return new ParametersWithIV(new KeyParameter(dKey, 0, keySize), dKey, keySize, ivSize); + } + + public override ICipherParameters GenerateDerivedParameters( + string algorithm, + int keySize, + int ivSize) + { + keySize /= 8; + ivSize /= 8; + + byte[] dKey = GenerateDerivedKey(keySize + ivSize); + KeyParameter key = ParameterUtilities.CreateKeyParameter(algorithm, dKey, 0, keySize); + + return new ParametersWithIV(key, 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 override ICipherParameters GenerateDerivedMacParameters( + int keySize) + { + keySize /= 8; + + byte[] dKey = GenerateDerivedKey(keySize); + + return new KeyParameter(dKey, 0, keySize); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/generators/Poly1305KeyGenerator.cs b/bc-sharp-crypto/src/crypto/generators/Poly1305KeyGenerator.cs new file mode 100644 index 0000000..cdb24bf --- /dev/null +++ b/bc-sharp-crypto/src/crypto/generators/Poly1305KeyGenerator.cs @@ -0,0 +1,116 @@ +using System; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Macs; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Crypto.Generators +{ + /// + /// Generates keys for the Poly1305 MAC. + /// + /// + /// Poly1305 keys are 256 bit keys consisting of a 128 bit secret key used for the underlying block + /// cipher followed by a 128 bit {@code r} value used for the polynomial portion of the Mac.
+ /// The {@code r} value has a specific format with some bits required to be cleared, resulting in an + /// effective 106 bit key.
+ /// A separately generated 256 bit key can be modified to fit the Poly1305 key format by using the + /// {@link #clamp(byte[])} method to clear the required bits. + ///
+ /// + public class Poly1305KeyGenerator + : CipherKeyGenerator + { + private const byte R_MASK_LOW_2 = (byte)0xFC; + private const byte R_MASK_HIGH_4 = (byte)0x0F; + + /// + /// Initialises the key generator. + /// + /// + /// Poly1305 keys are always 256 bits, so the key length in the provided parameters is ignored. + /// + protected override void engineInit(KeyGenerationParameters param) + { + // Poly1305 keys are always 256 bits + this.random = param.Random; + this.strength = 32; + } + + /// + /// Generates a 256 bit key in the format required for Poly1305 - e.g. + /// k[0] ... k[15], r[0] ... r[15] with the required bits in r cleared + /// as per . + /// + protected override byte[] engineGenerateKey() + { + byte[] key = base.engineGenerateKey(); + Clamp(key); + return key; + } + + /// + /// Modifies an existing 32 byte key value to comply with the requirements of the Poly1305 key by + /// clearing required bits in the r (second 16 bytes) portion of the key.
+ /// Specifically: + ///
    + ///
  • r[3], r[7], r[11], r[15] have top four bits clear (i.e., are {0, 1, . . . , 15})
  • + ///
  • r[4], r[8], r[12] have bottom two bits clear (i.e., are in {0, 4, 8, . . . , 252})
  • + ///
+ ///
+ /// a 32 byte key value k[0] ... k[15], r[0] ... r[15] + public static void Clamp(byte[] key) + { + /* + * Key is k[0] ... k[15], r[0] ... r[15] as per poly1305_aes_clamp in ref impl. + */ + if (key.Length != 32) + throw new ArgumentException("Poly1305 key must be 256 bits."); + + /* + * r[3], r[7], r[11], r[15] have top four bits clear (i.e., are {0, 1, . . . , 15}) + */ + key[3] &= R_MASK_HIGH_4; + key[7] &= R_MASK_HIGH_4; + key[11] &= R_MASK_HIGH_4; + key[15] &= R_MASK_HIGH_4; + + /* + * r[4], r[8], r[12] have bottom two bits clear (i.e., are in {0, 4, 8, . . . , 252}). + */ + key[4] &= R_MASK_LOW_2; + key[8] &= R_MASK_LOW_2; + key[12] &= R_MASK_LOW_2; + } + + /// + /// Checks a 32 byte key for compliance with the Poly1305 key requirements, e.g. + /// k[0] ... k[15], r[0] ... r[15] with the required bits in r cleared + /// as per . + /// + /// Key. + /// if the key is of the wrong length, or has invalid bits set + /// in the r portion of the key. + public static void CheckKey(byte[] key) + { + if (key.Length != 32) + throw new ArgumentException("Poly1305 key must be 256 bits."); + + CheckMask(key[3], R_MASK_HIGH_4); + CheckMask(key[7], R_MASK_HIGH_4); + CheckMask(key[11], R_MASK_HIGH_4); + CheckMask(key[15], R_MASK_HIGH_4); + + CheckMask(key[4], R_MASK_LOW_2); + CheckMask(key[8], R_MASK_LOW_2); + CheckMask(key[12], R_MASK_LOW_2); + } + + private static void CheckMask(byte b, byte mask) + { + if ((b & (~mask)) != 0) + throw new ArgumentException("Invalid format for r portion of Poly1305 key."); + } + } +} \ No newline at end of file diff --git a/bc-sharp-crypto/src/crypto/generators/RSABlindingFactorGenerator.cs b/bc-sharp-crypto/src/crypto/generators/RSABlindingFactorGenerator.cs new file mode 100644 index 0000000..e2f63fa --- /dev/null +++ b/bc-sharp-crypto/src/crypto/generators/RSABlindingFactorGenerator.cs @@ -0,0 +1,69 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Generators +{ + /** + * 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 RsaKeyParameters key; + private SecureRandom random; + + /** + * Initialise the factor generator + * + * @param param the necessary RSA key parameters. + */ + public void Init( + ICipherParameters param) + { + if (param is ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)param; + + key = (RsaKeyParameters)rParam.Parameters; + random = rParam.Random; + } + else + { + key = (RsaKeyParameters)param; + random = new SecureRandom(); + } + + if (key.IsPrivate) + throw new ArgumentException("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 InvalidOperationException("generator not initialised"); + + BigInteger m = key.Modulus; + 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.SignValue == 0 || factor.Equals(BigInteger.One) || !gcd.Equals(BigInteger.One)); + + return factor; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/generators/RsaKeyPairGenerator.cs b/bc-sharp-crypto/src/crypto/generators/RsaKeyPairGenerator.cs new file mode 100644 index 0000000..4499765 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/generators/RsaKeyPairGenerator.cs @@ -0,0 +1,163 @@ +using System; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Math.EC.Multiplier; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Generators +{ + /** + * an RSA key pair generator. + */ + public class RsaKeyPairGenerator + : IAsymmetricCipherKeyPairGenerator + { + private static readonly int[] SPECIAL_E_VALUES = new int[]{ 3, 5, 17, 257, 65537 }; + private static readonly int SPECIAL_E_HIGHEST = SPECIAL_E_VALUES[SPECIAL_E_VALUES.Length - 1]; + private static readonly int SPECIAL_E_BITS = BigInteger.ValueOf(SPECIAL_E_HIGHEST).BitLength; + + protected static readonly BigInteger One = BigInteger.One; + protected static readonly BigInteger DefaultPublicExponent = BigInteger.ValueOf(0x10001); + protected const int DefaultTests = 100; + + protected RsaKeyGenerationParameters parameters; + + public virtual void Init( + KeyGenerationParameters parameters) + { + if (parameters is RsaKeyGenerationParameters) + { + this.parameters = (RsaKeyGenerationParameters)parameters; + } + else + { + this.parameters = new RsaKeyGenerationParameters( + DefaultPublicExponent, parameters.Random, parameters.Strength, DefaultTests); + } + } + + public virtual AsymmetricCipherKeyPair GenerateKeyPair() + { + for (;;) + { + // + // p and q values should have a length of half the strength in bits + // + int strength = parameters.Strength; + int pBitlength = (strength + 1) / 2; + int qBitlength = strength - pBitlength; + int mindiffbits = strength / 3; + int minWeight = strength >> 2; + + BigInteger e = parameters.PublicExponent; + + // 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") + + BigInteger p = ChooseRandomPrime(pBitlength, e); + BigInteger q, n; + + // + // generate a modulus of the required length + // + for (;;) + { + q = ChooseRandomPrime(qBitlength, e); + + // p and q should not be too close together (or equal!) + BigInteger diff = q.Subtract(p).Abs(); + if (diff.BitLength < mindiffbits) + continue; + + // + // calculate the modulus + // + n = p.Multiply(q); + + if (n.BitLength != strength) + { + // + // if we get here our primes aren't big enough, make the largest + // of the two p and try again + // + p = p.Max(q); + continue; + } + + /* + * Require a minimum weight of the NAF representation, since low-weight composites may + * be weak against a version of the number-field-sieve for factoring. + * + * See "The number field sieve for integers of low weight", Oliver Schirokauer. + */ + if (WNafUtilities.GetNafWeight(n) < minWeight) + { + p = ChooseRandomPrime(pBitlength, e); + continue; + } + + break; + } + + if (p.CompareTo(q) < 0) + { + BigInteger tmp = p; + p = q; + q = tmp; + } + + BigInteger pSub1 = p.Subtract(One); + BigInteger qSub1 = q.Subtract(One); + //BigInteger phi = pSub1.Multiply(qSub1); + BigInteger gcd = pSub1.Gcd(qSub1); + BigInteger lcm = pSub1.Divide(gcd).Multiply(qSub1); + + // + // calculate the private exponent + // + BigInteger d = e.ModInverse(lcm); + + if (d.BitLength <= qBitlength) + continue; + + // + // calculate the CRT factors + // + BigInteger dP = d.Remainder(pSub1); + BigInteger dQ = d.Remainder(qSub1); + BigInteger qInv = q.ModInverse(p); + + return new AsymmetricCipherKeyPair( + new RsaKeyParameters(false, n, e), + new RsaPrivateCrtKeyParameters(n, e, d, p, q, dP, dQ, qInv)); + } + } + + /// Choose a random prime value for use with RSA + /// the bit-length of the returned prime + /// the RSA public exponent + /// a prime p, with (p-1) relatively prime to e + protected virtual BigInteger ChooseRandomPrime(int bitlength, BigInteger e) + { + bool eIsKnownOddPrime = (e.BitLength <= SPECIAL_E_BITS) && Arrays.Contains(SPECIAL_E_VALUES, e.IntValue); + + for (;;) + { + BigInteger p = new BigInteger(bitlength, 1, parameters.Random); + + if (p.Mod(e).Equals(One)) + continue; + + if (!p.IsProbablePrime(parameters.Certainty, true)) + continue; + + if (!eIsKnownOddPrime && !e.Gcd(p.Subtract(One)).Equals(One)) + continue; + + return p; + } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/generators/SCrypt.cs b/bc-sharp-crypto/src/crypto/generators/SCrypt.cs new file mode 100644 index 0000000..efa74d7 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/generators/SCrypt.cs @@ -0,0 +1,140 @@ +using System; +using System.Threading; + +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Utilities; + +namespace Org.BouncyCastle.Crypto.Generators +{ + public class SCrypt + { + // TODO Validate arguments + public static byte[] Generate(byte[] P, byte[] S, int N, int r, int p, int dkLen) + { + return MFcrypt(P, S, N, r, p, dkLen); + } + + private static byte[] MFcrypt(byte[] P, byte[] S, int N, int r, int p, int dkLen) + { + int MFLenBytes = r * 128; + byte[] bytes = SingleIterationPBKDF2(P, S, p * MFLenBytes); + + uint[] B = null; + + try + { + int BLen = bytes.Length >> 2; + B = new uint[BLen]; + + Pack.LE_To_UInt32(bytes, 0, B); + + int MFLenWords = MFLenBytes >> 2; + for (int BOff = 0; BOff < BLen; BOff += MFLenWords) + { + // TODO These can be done in parallel threads + SMix(B, BOff, N, r); + } + + Pack.UInt32_To_LE(B, bytes, 0); + + return SingleIterationPBKDF2(P, bytes, dkLen); + } + finally + { + ClearAll(bytes, B); + } + } + + private static byte[] SingleIterationPBKDF2(byte[] P, byte[] S, int dkLen) + { + PbeParametersGenerator pGen = new Pkcs5S2ParametersGenerator(new Sha256Digest()); + pGen.Init(P, S, 1); + KeyParameter key = (KeyParameter)pGen.GenerateDerivedMacParameters(dkLen * 8); + return key.GetKey(); + } + + private static void SMix(uint[] B, int BOff, int N, int r) + { + int BCount = r * 32; + + uint[] blockX1 = new uint[16]; + uint[] blockX2 = new uint[16]; + uint[] blockY = new uint[BCount]; + + uint[] X = new uint[BCount]; + uint[][] V = new uint[N][]; + + try + { + Array.Copy(B, BOff, X, 0, BCount); + + for (int i = 0; i < N; ++i) + { + V[i] = (uint[])X.Clone(); + BlockMix(X, blockX1, blockX2, blockY, r); + } + + uint mask = (uint)N - 1; + for (int i = 0; i < N; ++i) + { + uint j = X[BCount - 16] & mask; + Xor(X, V[j], 0, X); + BlockMix(X, blockX1, blockX2, blockY, r); + } + + Array.Copy(X, 0, B, BOff, BCount); + } + finally + { + ClearAll(V); + ClearAll(X, blockX1, blockX2, blockY); + } + } + + private static void BlockMix(uint[] B, uint[] X1, uint[] X2, uint[] Y, int r) + { + Array.Copy(B, B.Length - 16, X1, 0, 16); + + int BOff = 0, YOff = 0, halfLen = B.Length >> 1; + + for (int i = 2 * r; i > 0; --i) + { + Xor(X1, B, BOff, X2); + + Salsa20Engine.SalsaCore(8, X2, X1); + Array.Copy(X1, 0, Y, YOff, 16); + + YOff = halfLen + BOff - YOff; + BOff += 16; + } + + Array.Copy(Y, 0, B, 0, Y.Length); + } + + private static void Xor(uint[] a, uint[] b, int bOff, uint[] output) + { + for (int i = output.Length - 1; i >= 0; --i) + { + output[i] = a[i] ^ b[bOff + i]; + } + } + + private static void Clear(Array array) + { + if (array != null) + { + Array.Clear(array, 0, array.Length); + } + } + + private static void ClearAll(params Array[] arrays) + { + foreach (Array array in arrays) + { + Clear(array); + } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/io/CipherStream.cs b/bc-sharp-crypto/src/crypto/io/CipherStream.cs new file mode 100644 index 0000000..b5e6830 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/io/CipherStream.cs @@ -0,0 +1,252 @@ +using System; +using System.Diagnostics; +using System.IO; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.IO +{ + public class CipherStream + : Stream + { + internal Stream stream; + internal IBufferedCipher inCipher, outCipher; + private byte[] mInBuf; + private int mInPos; + private bool inStreamEnded; + + public CipherStream( + Stream stream, + IBufferedCipher readCipher, + IBufferedCipher writeCipher) + { + this.stream = stream; + + if (readCipher != null) + { + this.inCipher = readCipher; + mInBuf = null; + } + + if (writeCipher != null) + { + this.outCipher = writeCipher; + } + } + + public IBufferedCipher ReadCipher + { + get { return inCipher; } + } + + public IBufferedCipher WriteCipher + { + get { return outCipher; } + } + + public override int ReadByte() + { + if (inCipher == null) + return stream.ReadByte(); + + if (mInBuf == null || mInPos >= mInBuf.Length) + { + if (!FillInBuf()) + return -1; + } + + return mInBuf[mInPos++]; + } + + public override int Read( + byte[] buffer, + int offset, + int count) + { + if (inCipher == null) + return stream.Read(buffer, offset, count); + + int num = 0; + while (num < count) + { + if (mInBuf == null || mInPos >= mInBuf.Length) + { + if (!FillInBuf()) + break; + } + + int numToCopy = System.Math.Min(count - num, mInBuf.Length - mInPos); + Array.Copy(mInBuf, mInPos, buffer, offset + num, numToCopy); + mInPos += numToCopy; + num += numToCopy; + } + + return num; + } + + private bool FillInBuf() + { + if (inStreamEnded) + return false; + + mInPos = 0; + + do + { + mInBuf = ReadAndProcessBlock(); + } + while (!inStreamEnded && mInBuf == null); + + return mInBuf != null; + } + + private byte[] ReadAndProcessBlock() + { + int blockSize = inCipher.GetBlockSize(); + int readSize = (blockSize == 0) ? 256 : blockSize; + + byte[] block = new byte[readSize]; + int numRead = 0; + do + { + int count = stream.Read(block, numRead, block.Length - numRead); + if (count < 1) + { + inStreamEnded = true; + break; + } + numRead += count; + } + while (numRead < block.Length); + + Debug.Assert(inStreamEnded || numRead == block.Length); + + byte[] bytes = inStreamEnded + ? inCipher.DoFinal(block, 0, numRead) + : inCipher.ProcessBytes(block); + + if (bytes != null && bytes.Length == 0) + { + bytes = null; + } + + return bytes; + } + + public override void Write( + byte[] buffer, + int offset, + int count) + { + Debug.Assert(buffer != null); + Debug.Assert(0 <= offset && offset <= buffer.Length); + Debug.Assert(count >= 0); + + int end = offset + count; + + Debug.Assert(0 <= end && end <= buffer.Length); + + if (outCipher == null) + { + stream.Write(buffer, offset, count); + return; + } + + byte[] data = outCipher.ProcessBytes(buffer, offset, count); + if (data != null) + { + stream.Write(data, 0, data.Length); + } + } + + public override void WriteByte( + byte b) + { + if (outCipher == null) + { + stream.WriteByte(b); + return; + } + + byte[] data = outCipher.ProcessByte(b); + if (data != null) + { + stream.Write(data, 0, data.Length); + } + } + + public override bool CanRead + { + get { return stream.CanRead && (inCipher != null); } + } + + public override bool CanWrite + { + get { return stream.CanWrite && (outCipher != null); } + } + + public override bool CanSeek + { + get { return false; } + } + + public sealed override long Length + { + get { throw new NotSupportedException(); } + } + + public sealed override long Position + { + get { throw new NotSupportedException(); } + set { throw new NotSupportedException(); } + } + +#if PORTABLE + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (outCipher != null) + { + byte[] data = outCipher.DoFinal(); + stream.Write(data, 0, data.Length); + stream.Flush(); + } + Platform.Dispose(stream); + } + base.Dispose(disposing); + } +#else + public override void Close() + { + if (outCipher != null) + { + byte[] data = outCipher.DoFinal(); + stream.Write(data, 0, data.Length); + stream.Flush(); + } + Platform.Dispose(stream); + base.Close(); + } +#endif + + public override void Flush() + { + // Note: outCipher.DoFinal is only called during Close() + stream.Flush(); + } + + public sealed override long Seek( + long offset, + SeekOrigin origin) + { + throw new NotSupportedException(); + } + + public sealed override void SetLength( + long length) + { + throw new NotSupportedException(); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/io/DigestStream.cs b/bc-sharp-crypto/src/crypto/io/DigestStream.cs new file mode 100644 index 0000000..dce8757 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/io/DigestStream.cs @@ -0,0 +1,151 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.IO +{ + public class DigestStream + : Stream + { + protected readonly Stream stream; + protected readonly IDigest inDigest; + protected readonly IDigest outDigest; + + public DigestStream( + Stream stream, + IDigest readDigest, + IDigest writeDigest) + { + this.stream = stream; + this.inDigest = readDigest; + this.outDigest = writeDigest; + } + + public virtual IDigest ReadDigest() + { + return inDigest; + } + + public virtual IDigest WriteDigest() + { + return outDigest; + } + + public override int Read( + byte[] buffer, + int offset, + int count) + { + int n = stream.Read(buffer, offset, count); + if (inDigest != null) + { + if (n > 0) + { + inDigest.BlockUpdate(buffer, offset, n); + } + } + return n; + } + + public override int ReadByte() + { + int b = stream.ReadByte(); + if (inDigest != null) + { + if (b >= 0) + { + inDigest.Update((byte)b); + } + } + return b; + } + + public override void Write( + byte[] buffer, + int offset, + int count) + { + if (outDigest != null) + { + if (count > 0) + { + outDigest.BlockUpdate(buffer, offset, count); + } + } + stream.Write(buffer, offset, count); + } + + public override void WriteByte( + byte b) + { + if (outDigest != null) + { + outDigest.Update(b); + } + stream.WriteByte(b); + } + + public override bool CanRead + { + get { return stream.CanRead; } + } + + public override bool CanWrite + { + get { return stream.CanWrite; } + } + + public override bool CanSeek + { + get { return stream.CanSeek; } + } + + public override long Length + { + get { return stream.Length; } + } + + public override long Position + { + get { return stream.Position; } + set { stream.Position = value; } + } + +#if PORTABLE + protected override void Dispose(bool disposing) + { + if (disposing) + { + Platform.Dispose(stream); + } + base.Dispose(disposing); + } +#else + public override void Close() + { + Platform.Dispose(stream); + base.Close(); + } +#endif + + public override void Flush() + { + stream.Flush(); + } + + public override long Seek( + long offset, + SeekOrigin origin) + { + return stream.Seek(offset, origin); + } + + public override void SetLength( + long length) + { + stream.SetLength(length); + } + } +} + diff --git a/bc-sharp-crypto/src/crypto/io/MacStream.cs b/bc-sharp-crypto/src/crypto/io/MacStream.cs new file mode 100644 index 0000000..d9b8323 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/io/MacStream.cs @@ -0,0 +1,150 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.IO +{ + public class MacStream + : Stream + { + protected readonly Stream stream; + protected readonly IMac inMac; + protected readonly IMac outMac; + + public MacStream( + Stream stream, + IMac readMac, + IMac writeMac) + { + this.stream = stream; + this.inMac = readMac; + this.outMac = writeMac; + } + + public virtual IMac ReadMac() + { + return inMac; + } + + public virtual IMac WriteMac() + { + return outMac; + } + + public override int Read( + byte[] buffer, + int offset, + int count) + { + int n = stream.Read(buffer, offset, count); + if (inMac != null) + { + if (n > 0) + { + inMac.BlockUpdate(buffer, offset, n); + } + } + return n; + } + + public override int ReadByte() + { + int b = stream.ReadByte(); + if (inMac != null) + { + if (b >= 0) + { + inMac.Update((byte)b); + } + } + return b; + } + + public override void Write( + byte[] buffer, + int offset, + int count) + { + if (outMac != null) + { + if (count > 0) + { + outMac.BlockUpdate(buffer, offset, count); + } + } + stream.Write(buffer, offset, count); + } + + public override void WriteByte(byte b) + { + if (outMac != null) + { + outMac.Update(b); + } + stream.WriteByte(b); + } + + public override bool CanRead + { + get { return stream.CanRead; } + } + + public override bool CanWrite + { + get { return stream.CanWrite; } + } + + public override bool CanSeek + { + get { return stream.CanSeek; } + } + + public override long Length + { + get { return stream.Length; } + } + + public override long Position + { + get { return stream.Position; } + set { stream.Position = value; } + } + +#if PORTABLE + protected override void Dispose(bool disposing) + { + if (disposing) + { + Platform.Dispose(stream); + } + base.Dispose(disposing); + } +#else + public override void Close() + { + Platform.Dispose(stream); + base.Close(); + } +#endif + + public override void Flush() + { + stream.Flush(); + } + + public override long Seek( + long offset, + SeekOrigin origin) + { + return stream.Seek(offset,origin); + } + + public override void SetLength( + long length) + { + stream.SetLength(length); + } + } +} + diff --git a/bc-sharp-crypto/src/crypto/io/SignerStream.cs b/bc-sharp-crypto/src/crypto/io/SignerStream.cs new file mode 100644 index 0000000..1e37c8d --- /dev/null +++ b/bc-sharp-crypto/src/crypto/io/SignerStream.cs @@ -0,0 +1,151 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.IO +{ + public class SignerStream + : Stream + { + protected readonly Stream stream; + protected readonly ISigner inSigner; + protected readonly ISigner outSigner; + + public SignerStream( + Stream stream, + ISigner readSigner, + ISigner writeSigner) + { + this.stream = stream; + this.inSigner = readSigner; + this.outSigner = writeSigner; + } + + public virtual ISigner ReadSigner() + { + return inSigner; + } + + public virtual ISigner WriteSigner() + { + return outSigner; + } + + public override int Read( + byte[] buffer, + int offset, + int count) + { + int n = stream.Read(buffer, offset, count); + if (inSigner != null) + { + if (n > 0) + { + inSigner.BlockUpdate(buffer, offset, n); + } + } + return n; + } + + public override int ReadByte() + { + int b = stream.ReadByte(); + if (inSigner != null) + { + if (b >= 0) + { + inSigner.Update((byte)b); + } + } + return b; + } + + public override void Write( + byte[] buffer, + int offset, + int count) + { + if (outSigner != null) + { + if (count > 0) + { + outSigner.BlockUpdate(buffer, offset, count); + } + } + stream.Write(buffer, offset, count); + } + + public override void WriteByte( + byte b) + { + if (outSigner != null) + { + outSigner.Update(b); + } + stream.WriteByte(b); + } + + public override bool CanRead + { + get { return stream.CanRead; } + } + + public override bool CanWrite + { + get { return stream.CanWrite; } + } + + public override bool CanSeek + { + get { return stream.CanSeek; } + } + + public override long Length + { + get { return stream.Length; } + } + + public override long Position + { + get { return stream.Position; } + set { stream.Position = value; } + } + +#if PORTABLE + protected override void Dispose(bool disposing) + { + if (disposing) + { + Platform.Dispose(stream); + } + base.Dispose(disposing); + } +#else + public override void Close() + { + Platform.Dispose(stream); + base.Close(); + } +#endif + + public override void Flush() + { + stream.Flush(); + } + + public override long Seek( + long offset, + SeekOrigin origin) + { + return stream.Seek(offset, origin); + } + + public override void SetLength( + long length) + { + stream.SetLength(length); + } + } +} + diff --git a/bc-sharp-crypto/src/crypto/macs/CMac.cs b/bc-sharp-crypto/src/crypto/macs/CMac.cs new file mode 100644 index 0000000..682c12b --- /dev/null +++ b/bc-sharp-crypto/src/crypto/macs/CMac.cs @@ -0,0 +1,257 @@ +using System; + +using Org.BouncyCastle.Crypto.Modes; +using Org.BouncyCastle.Crypto.Paddings; +using Org.BouncyCastle.Crypto.Parameters; + +namespace Org.BouncyCastle.Crypto.Macs +{ + /** + * 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 + : IMac + { + private const byte CONSTANT_128 = (byte)0x87; + private const byte CONSTANT_64 = (byte)0x1b; + + private byte[] ZEROES; + + private byte[] mac; + + private byte[] buf; + private int bufOff; + private IBlockCipher 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( + IBlockCipher 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 @lt;= 128. + */ + public CMac( + IBlockCipher cipher, + int macSizeInBits) + { + if ((macSizeInBits % 8) != 0) + throw new ArgumentException("MAC size must be multiple of 8"); + + if (macSizeInBits > (cipher.GetBlockSize() * 8)) + { + throw new ArgumentException( + "MAC size must be less or equal to " + + (cipher.GetBlockSize() * 8)); + } + + if (cipher.GetBlockSize() != 8 && cipher.GetBlockSize() != 16) + { + throw new ArgumentException( + "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 AlgorithmName + { + get { return cipher.AlgorithmName; } + } + + private static int ShiftLeft(byte[] block, byte[] output) + { + int i = block.Length; + uint bit = 0; + while (--i >= 0) + { + uint b = block[i]; + output[i] = (byte)((b << 1) | bit); + bit = (b >> 7) & 1; + } + return (int)bit; + } + + private static byte[] DoubleLu(byte[] input) + { + byte[] ret = new byte[input.Length]; + int carry = ShiftLeft(input, ret); + int xor = input.Length == 16 ? CONSTANT_128 : CONSTANT_64; + + /* + * NOTE: This construction is an attempt at a constant-time implementation. + */ + ret[input.Length - 1] ^= (byte)(xor >> ((1 - carry) << 3)); + + return ret; + } + + public void Init( + ICipherParameters parameters) + { + if (parameters is KeyParameter) + { + cipher.Init(true, parameters); + + //initializes the L, Lu, Lu2 numbers + L = new byte[ZEROES.Length]; + cipher.ProcessBlock(ZEROES, 0, L, 0); + Lu = DoubleLu(L); + Lu2 = DoubleLu(Lu); + } + else if (parameters != null) + { + // CMAC mode does not permit IV to underlying CBC mode + throw new ArgumentException("CMac mode only permits key to be set.", "parameters"); + } + + Reset(); + } + + public int GetMacSize() + { + return macSize; + } + + public void Update( + byte input) + { + if (bufOff == buf.Length) + { + cipher.ProcessBlock(buf, 0, mac, 0); + bufOff = 0; + } + + buf[bufOff++] = input; + } + + public void BlockUpdate( + byte[] inBytes, + int inOff, + int len) + { + if (len < 0) + throw new ArgumentException("Can't have a negative input length!"); + + int blockSize = cipher.GetBlockSize(); + int gapLen = blockSize - bufOff; + + if (len > gapLen) + { + Array.Copy(inBytes, inOff, buf, bufOff, gapLen); + + cipher.ProcessBlock(buf, 0, mac, 0); + + bufOff = 0; + len -= gapLen; + inOff += gapLen; + + while (len > blockSize) + { + cipher.ProcessBlock(inBytes, inOff, mac, 0); + + len -= blockSize; + inOff += blockSize; + } + } + + Array.Copy(inBytes, inOff, buf, bufOff, len); + + bufOff += len; + } + + public int DoFinal( + byte[] outBytes, + 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); + + Array.Copy(mac, 0, outBytes, outOff, macSize); + + Reset(); + + return macSize; + } + + /** + * Reset the mac generator. + */ + public void Reset() + { + /* + * clean the buffer. + */ + Array.Clear(buf, 0, buf.Length); + bufOff = 0; + + /* + * Reset the underlying cipher. + */ + cipher.Reset(); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/macs/CbcBlockCipherMac.cs b/bc-sharp-crypto/src/crypto/macs/CbcBlockCipherMac.cs new file mode 100644 index 0000000..146e16a --- /dev/null +++ b/bc-sharp-crypto/src/crypto/macs/CbcBlockCipherMac.cs @@ -0,0 +1,209 @@ +using System; + +using Org.BouncyCastle.Crypto.Modes; +using Org.BouncyCastle.Crypto.Paddings; + +namespace Org.BouncyCastle.Crypto.Macs +{ + /** + * standard CBC Block Cipher MAC - if no padding is specified the default of + * pad of zeroes is used. + */ + public class CbcBlockCipherMac + : IMac + { + private byte[] buf; + private int bufOff; + private IBlockCipher cipher; + private IBlockCipherPadding 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( + IBlockCipher 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( + IBlockCipher cipher, + IBlockCipherPadding 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( + IBlockCipher 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( + IBlockCipher cipher, + int macSizeInBits, + IBlockCipherPadding padding) + { + if ((macSizeInBits % 8) != 0) + throw new ArgumentException("MAC size must be multiple of 8"); + + this.cipher = new CbcBlockCipher(cipher); + this.padding = padding; + this.macSize = macSizeInBits / 8; + + buf = new byte[cipher.GetBlockSize()]; + bufOff = 0; + } + + public string AlgorithmName + { + get { return cipher.AlgorithmName; } + } + + public void Init( + ICipherParameters parameters) + { + Reset(); + + cipher.Init(true, parameters); + } + + public int GetMacSize() + { + return macSize; + } + + public void Update( + byte input) + { + if (bufOff == buf.Length) + { + cipher.ProcessBlock(buf, 0, buf, 0); + bufOff = 0; + } + + buf[bufOff++] = input; + } + + public void BlockUpdate( + byte[] input, + int inOff, + int len) + { + if (len < 0) + throw new ArgumentException("Can't have a negative input length!"); + + int blockSize = cipher.GetBlockSize(); + int gapLen = blockSize - bufOff; + + if (len > gapLen) + { + Array.Copy(input, inOff, buf, bufOff, gapLen); + + cipher.ProcessBlock(buf, 0, buf, 0); + + bufOff = 0; + len -= gapLen; + inOff += gapLen; + + while (len > blockSize) + { + cipher.ProcessBlock(input, inOff, buf, 0); + + len -= blockSize; + inOff += blockSize; + } + } + + Array.Copy(input, inOff, buf, bufOff, len); + + bufOff += len; + } + + public int DoFinal( + byte[] output, + int outOff) + { + int blockSize = cipher.GetBlockSize(); + + if (padding == null) + { + // pad with zeroes + while (bufOff < blockSize) + { + buf[bufOff++] = 0; + } + } + else + { + if (bufOff == blockSize) + { + cipher.ProcessBlock(buf, 0, buf, 0); + bufOff = 0; + } + + padding.AddPadding(buf, bufOff); + } + + cipher.ProcessBlock(buf, 0, buf, 0); + + Array.Copy(buf, 0, output, outOff, macSize); + + Reset(); + + return macSize; + } + + /** + * Reset the mac generator. + */ + public void Reset() + { + // Clear the buffer. + Array.Clear(buf, 0, buf.Length); + bufOff = 0; + + // Reset the underlying cipher. + cipher.Reset(); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/macs/CfbBlockCipherMac.cs b/bc-sharp-crypto/src/crypto/macs/CfbBlockCipherMac.cs new file mode 100644 index 0000000..364cf84 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/macs/CfbBlockCipherMac.cs @@ -0,0 +1,368 @@ +using System; + +using Org.BouncyCastle.Crypto.Modes; +using Org.BouncyCastle.Crypto.Paddings; +using Org.BouncyCastle.Crypto.Parameters; + +namespace Org.BouncyCastle.Crypto.Macs +{ + /** + * implements a Cipher-FeedBack (CFB) mode on top of a simple cipher. + */ + class MacCFBBlockCipher + : IBlockCipher + { + private byte[] IV; + private byte[] cfbV; + private byte[] cfbOutV; + + private readonly int blockSize; + private readonly IBlockCipher 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 MacCFBBlockCipher( + IBlockCipher 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 ArgumentException if the parameters argument is + * inappropriate. + */ + public void Init( + bool forEncryption, + ICipherParameters parameters) + { + if (parameters is ParametersWithIV) + { + ParametersWithIV ivParam = (ParametersWithIV)parameters; + byte[] iv = ivParam.GetIV(); + + if (iv.Length < IV.Length) + { + Array.Copy(iv, 0, IV, IV.Length - iv.Length, iv.Length); + } + else + { + Array.Copy(iv, 0, IV, 0, IV.Length); + } + + parameters = ivParam.Parameters; + } + + Reset(); + + cipher.Init(true, parameters); + } + + /** + * return the algorithm name and mode. + * + * @return the name of the underlying algorithm followed by "/CFB" + * and the block size in bits. + */ + public string AlgorithmName + { + get { return cipher.AlgorithmName + "/CFB" + (blockSize * 8); } + } + + public bool IsPartialBlockOkay + { + get { return true; } + } + + /** + * 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 InvalidOperationException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + public int ProcessBlock( + byte[] input, + int inOff, + byte[] outBytes, + int outOff) + { + if ((inOff + blockSize) > input.Length) + throw new DataLengthException("input buffer too short"); + + if ((outOff + blockSize) > outBytes.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++) + { + outBytes[outOff + i] = (byte)(cfbOutV[i] ^ input[inOff + i]); + } + + // + // change over the input block. + // + Array.Copy(cfbV, blockSize, cfbV, 0, cfbV.Length - blockSize); + Array.Copy(outBytes, outOff, cfbV, cfbV.Length - blockSize, blockSize); + + return blockSize; + } + + /** + * reset the chaining vector back to the IV and reset the underlying + * cipher. + */ + public void Reset() + { + IV.CopyTo(cfbV, 0); + + cipher.Reset(); + } + + public void GetMacBlock( + byte[] mac) + { + cipher.ProcessBlock(cfbV, 0, mac, 0); + } + } + + public class CfbBlockCipherMac + : IMac + { + private byte[] mac; + private byte[] Buffer; + private int bufOff; + private MacCFBBlockCipher cipher; + private IBlockCipherPadding padding; + 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( + IBlockCipher 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( + IBlockCipher cipher, + IBlockCipherPadding 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( + IBlockCipher 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( + IBlockCipher cipher, + int cfbBitSize, + int macSizeInBits, + IBlockCipherPadding padding) + { + if ((macSizeInBits % 8) != 0) + throw new ArgumentException("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; + + Buffer = new byte[this.cipher.GetBlockSize()]; + bufOff = 0; + } + + public string AlgorithmName + { + get { return cipher.AlgorithmName; } + } + + public void Init( + ICipherParameters parameters) + { + Reset(); + + cipher.Init(true, parameters); + } + + public int GetMacSize() + { + return macSize; + } + + public void Update( + byte input) + { + if (bufOff == Buffer.Length) + { + cipher.ProcessBlock(Buffer, 0, mac, 0); + bufOff = 0; + } + + Buffer[bufOff++] = input; + } + + public void BlockUpdate( + byte[] input, + int inOff, + int len) + { + if (len < 0) + throw new ArgumentException("Can't have a negative input length!"); + + int blockSize = cipher.GetBlockSize(); + int resultLen = 0; + int gapLen = blockSize - bufOff; + + if (len > gapLen) + { + Array.Copy(input, inOff, Buffer, bufOff, gapLen); + + resultLen += cipher.ProcessBlock(Buffer, 0, mac, 0); + + bufOff = 0; + len -= gapLen; + inOff += gapLen; + + while (len > blockSize) + { + resultLen += cipher.ProcessBlock(input, inOff, mac, 0); + + len -= blockSize; + inOff += blockSize; + } + } + + Array.Copy(input, inOff, Buffer, bufOff, len); + + bufOff += len; + } + + public int DoFinal( + byte[] output, + int outOff) + { + int blockSize = cipher.GetBlockSize(); + + // pad with zeroes + if (this.padding == null) + { + while (bufOff < blockSize) + { + Buffer[bufOff++] = 0; + } + } + else + { + padding.AddPadding(Buffer, bufOff); + } + + cipher.ProcessBlock(Buffer, 0, mac, 0); + + cipher.GetMacBlock(mac); + + Array.Copy(mac, 0, output, outOff, macSize); + + Reset(); + + return macSize; + } + + /** + * Reset the mac generator. + */ + public void Reset() + { + // Clear the buffer. + Array.Clear(Buffer, 0, Buffer.Length); + bufOff = 0; + + // Reset the underlying cipher. + cipher.Reset(); + } + } + +} diff --git a/bc-sharp-crypto/src/crypto/macs/DSTU7564Mac.cs b/bc-sharp-crypto/src/crypto/macs/DSTU7564Mac.cs new file mode 100644 index 0000000..36e8641 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/macs/DSTU7564Mac.cs @@ -0,0 +1,143 @@ +using System; + +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Utilities; + +namespace Org.BouncyCastle.Crypto.Macs +{ + /// + /// Implementation of DSTU7564 mac mode + /// + public class Dstu7564Mac + : IMac + { + private Dstu7564Digest engine; + private int macSize; + + private ulong inputLength; + + byte[] paddedKey; + byte[] invertedKey; + + public string AlgorithmName + { + get { return "DSTU7564Mac"; } + } + + public Dstu7564Mac(int macSizeBits) + { + engine = new Dstu7564Digest(macSizeBits); + macSize = macSizeBits / 8; + } + + public void Init(ICipherParameters parameters) + { + if (parameters is KeyParameter) + { + byte[] key = ((KeyParameter)parameters).GetKey(); + + invertedKey = new byte[key.Length]; + + paddedKey = PadKey(key); + + for (int byteIndex = 0; byteIndex < invertedKey.Length; byteIndex++) + { + invertedKey[byteIndex] = (byte)(key[byteIndex] ^ (byte)0xFF); + } + } + else + { + throw new ArgumentException("Bad parameter passed"); + } + + engine.BlockUpdate(paddedKey, 0, paddedKey.Length); + } + + public int GetMacSize() + { + return macSize; + } + + public void BlockUpdate(byte[] input, int inOff, int len) + { + Check.DataLength(input, inOff, len, "Input buffer too short"); + + if (paddedKey == null) + throw new InvalidOperationException(AlgorithmName + " not initialised"); + + engine.BlockUpdate(input, inOff, len); + inputLength += (ulong)len; + } + + public void Update(byte input) + { + engine.Update(input); + inputLength++; + } + + public int DoFinal(byte[] output, int outOff) + { + Check.OutputLength(output, outOff, macSize, "Output buffer too short"); + + if (paddedKey == null) + throw new InvalidOperationException(AlgorithmName + " not initialised"); + + Pad(); + + engine.BlockUpdate(invertedKey, 0, invertedKey.Length); + + inputLength = 0; + + return engine.DoFinal(output, outOff); + } + + public void Reset() + { + inputLength = 0; + engine.Reset(); + if (paddedKey != null) + { + engine.BlockUpdate(paddedKey, 0, paddedKey.Length); + } + } + + private void Pad() + { + int extra = engine.GetByteLength() - (int)(inputLength % (ulong)engine.GetByteLength()); + if (extra < 13) // terminator byte + 96 bits of length + { + extra += engine.GetByteLength(); + } + + byte[] padded = new byte[extra]; + + padded[0] = (byte)0x80; // Defined in standard; + + // Defined in standard; + Pack.UInt64_To_LE(inputLength * 8, padded, padded.Length - 12); + + engine.BlockUpdate(padded, 0, padded.Length); + } + + private byte[] PadKey(byte[] input) + { + int paddedLen = ((input.Length + engine.GetByteLength() - 1) / engine.GetByteLength()) * engine.GetByteLength(); + + int extra = engine.GetByteLength() - (int)(input.Length % engine.GetByteLength()); + if (extra < 13) // terminator byte + 96 bits of length + { + paddedLen += engine.GetByteLength(); + } + + byte[] padded = new byte[paddedLen]; + + Array.Copy(input, 0, padded, 0, input.Length); + + padded[input.Length] = (byte)0x80; // Defined in standard; + Pack.UInt32_To_LE((uint)(input.Length * 8), padded, padded.Length - 12); // Defined in standard; + + return padded; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/macs/DSTU7624Mac.cs b/bc-sharp-crypto/src/crypto/macs/DSTU7624Mac.cs new file mode 100644 index 0000000..953d816 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/macs/DSTU7624Mac.cs @@ -0,0 +1,160 @@ +using System; + +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Parameters; + + +namespace Org.BouncyCastle.Crypto.Macs +{ + /** + * implementation of DSTU 7624 MAC + */ + public class Dstu7624Mac : IMac + { + private int macSize; + + private Dstu7624Engine engine; + private int blockSize; + + private byte[] c, cTemp, kDelta; + private byte[] buf; + private int bufOff; + + public Dstu7624Mac(int blockSizeBits, int q) + { + engine = new Dstu7624Engine(blockSizeBits); + + blockSize = blockSizeBits / 8; + + macSize = q / 8; + + c = new byte[blockSize]; + + cTemp = new byte[blockSize]; + + kDelta = new byte[blockSize]; + buf = new byte[blockSize]; + } + + public void Init(ICipherParameters parameters) + { + if (parameters is KeyParameter) + { + engine.Init(true, (KeyParameter)parameters); + + engine.ProcessBlock(kDelta, 0, kDelta, 0); + } + else + { + throw new ArgumentException("invalid parameter passed to Dstu7624Mac init - " + + Platform.GetTypeName(parameters)); + } + } + + public string AlgorithmName + { + get { return "Dstu7624Mac"; } + } + + public int GetMacSize() + { + return macSize; + } + + public void Update(byte input) + { + if (bufOff == buf.Length) + { + processBlock(buf, 0); + bufOff = 0; + } + + buf[bufOff++] = input; + } + + public void BlockUpdate(byte[] input, int inOff, int len) + { + if (len < 0) + { + throw new ArgumentException( + "Can't have a negative input length!"); + } + + int blockSize = engine.GetBlockSize(); + int gapLen = blockSize - bufOff; + + if (len > gapLen) + { + Array.Copy(input, inOff, buf, bufOff, gapLen); + + processBlock(buf, 0); + + bufOff = 0; + len -= gapLen; + inOff += gapLen; + + while (len > blockSize) + { + processBlock(input, inOff); + + len -= blockSize; + inOff += blockSize; + } + } + + Array.Copy(input, inOff, buf, bufOff, len); + + bufOff += len; + } + + private void processBlock(byte[] input, int inOff) + { + Xor(c, 0, input, inOff, cTemp); + + engine.ProcessBlock(cTemp, 0, c, 0); + } + + private void Xor(byte[] c, int cOff, byte[] input, int inOff, byte[] xorResult) + { + for (int byteIndex = 0; byteIndex < blockSize; byteIndex++) + { + xorResult[byteIndex] = (byte)(c[byteIndex + cOff] ^ input[byteIndex + inOff]); + } + } + + public int DoFinal(byte[] output, int outOff) + { + if (bufOff % buf.Length != 0) + { + throw new DataLengthException("Input must be a multiple of blocksize"); + } + + //Last block + Xor(c, 0, buf, 0, cTemp); + Xor(cTemp, 0, kDelta, 0, c); + engine.ProcessBlock(c, 0, c, 0); + + if (macSize + outOff > output.Length) + { + throw new DataLengthException("Output buffer too short"); + } + + Array.Copy(c, 0, output, outOff, macSize); + + return macSize; + } + + public void Reset() + { + Arrays.Fill(c, (byte)0x00); + Arrays.Fill(cTemp, (byte)0x00); + Arrays.Fill(kDelta, (byte)0x00); + Arrays.Fill(buf, (byte)0x00); + engine.Reset(); + engine.ProcessBlock(kDelta, 0, kDelta, 0); + bufOff = 0; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/macs/GMac.cs b/bc-sharp-crypto/src/crypto/macs/GMac.cs new file mode 100644 index 0000000..f2c3990 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/macs/GMac.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Modes; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Macs +{ + /// + /// The GMAC specialisation of Galois/Counter mode (GCM) detailed in NIST Special Publication + /// 800-38D. + /// + /// + /// GMac is an invocation of the GCM mode where no data is encrypted (i.e. all input data to the Mac + /// is processed as additional authenticated data with the underlying GCM block cipher). + /// + public class GMac + : IMac + { + private readonly GcmBlockCipher cipher; + private readonly int macSizeBits; + + /// + /// Creates a GMAC based on the operation of a block cipher in GCM mode. + /// + /// + /// This will produce an authentication code the length of the block size of the cipher. + /// + /// the cipher to be used in GCM mode to generate the MAC. + public GMac(GcmBlockCipher cipher) + : this(cipher, 128) + { + } + + /// + /// Creates a GMAC based on the operation of a 128 bit block cipher in GCM mode. + /// + /// + /// This will produce an authentication code the length of the block size of the cipher. + /// + /// the cipher to be used in GCM mode to generate the MAC. + /// the mac size to generate, in bits. Must be a multiple of 8, between 32 and 128 (inclusive). + /// Sizes less than 96 are not recommended, but are supported for specialized applications. + public GMac(GcmBlockCipher cipher, int macSizeBits) + { + this.cipher = cipher; + this.macSizeBits = macSizeBits; + } + + /// + /// Initialises the GMAC - requires a + /// providing a and a nonce. + /// + public void Init(ICipherParameters parameters) + { + if (parameters is ParametersWithIV) + { + ParametersWithIV param = (ParametersWithIV)parameters; + + byte[] iv = param.GetIV(); + KeyParameter keyParam = (KeyParameter)param.Parameters; + + // GCM is always operated in encrypt mode to calculate MAC + cipher.Init(true, new AeadParameters(keyParam, macSizeBits, iv)); + } + else + { + throw new ArgumentException("GMAC requires ParametersWithIV"); + } + } + + public string AlgorithmName + { + get { return cipher.GetUnderlyingCipher().AlgorithmName + "-GMAC"; } + } + + public int GetMacSize() + { + return macSizeBits / 8; + } + + public void Update(byte input) + { + cipher.ProcessAadByte(input); + } + + public void BlockUpdate(byte[] input, int inOff, int len) + { + cipher.ProcessAadBytes(input, inOff, len); + } + + public int DoFinal(byte[] output, int outOff) + { + try + { + return cipher.DoFinal(output, outOff); + } + catch (InvalidCipherTextException e) + { + // Impossible in encrypt mode + throw new InvalidOperationException(e.ToString()); + } + } + + public void Reset() + { + cipher.Reset(); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/macs/GOST28147Mac.cs b/bc-sharp-crypto/src/crypto/macs/GOST28147Mac.cs new file mode 100644 index 0000000..cc6b723 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/macs/GOST28147Mac.cs @@ -0,0 +1,297 @@ +using System; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Macs +{ + /** + * implementation of GOST 28147-89 MAC + */ + public class Gost28147Mac : IMac + { + private const int blockSize = 8; + private const int macSize = 4; + private int bufOff; + private byte[] buf; + private byte[] mac; + private bool firstStep = true; + private int[] workingKey; + + // + // 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 static int[] generateWorkingKey( + byte[] userKey) + { + if (userKey.Length != 32) + throw new ArgumentException("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( + ICipherParameters parameters) + { + Reset(); + buf = new byte[blockSize]; + if (parameters is ParametersWithSBox) + { + ParametersWithSBox param = (ParametersWithSBox)parameters; + + // + // Set the S-Box + // + param.GetSBox().CopyTo(this.S, 0); + + // + // set key if there is one + // + if (param.Parameters != null) + { + workingKey = generateWorkingKey(((KeyParameter)param.Parameters).GetKey()); + } + } + else if (parameters is KeyParameter) + { + workingKey = generateWorkingKey(((KeyParameter)parameters).GetKey()); + } + else + { + throw new ArgumentException("invalid parameter passed to Gost28147 init - " + + Platform.GetTypeName(parameters)); + } + } + + public string AlgorithmName + { + get { 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 + int omLeft = om << 11; + int omRight = (int)(((uint) om) >> (32 - 11)); // Note: Casts required to get unsigned bit rotation + + return omLeft | omRight; + } + + private void gost28147MacFunc( + int[] workingKey, + byte[] input, + int inOff, + byte[] output, + int outOff) + { + int N1, N2, tmp; //tmp -> for saving N1 + N1 = bytesToint(input, inOff); + N2 = bytesToint(input, 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, output, outOff); + intTobytes(N2, output, outOff + 4); + } + + //array of bytes to type int + private static int bytesToint( + byte[] input, + int inOff) + { + return (int)((input[inOff + 3] << 24) & 0xff000000) + ((input[inOff + 2] << 16) & 0xff0000) + + ((input[inOff + 1] << 8) & 0xff00) + (input[inOff] & 0xff); + } + + //int to array of bytes + private static void intTobytes( + int num, + byte[] output, + int outOff) + { + output[outOff + 3] = (byte)(num >> 24); + output[outOff + 2] = (byte)(num >> 16); + output[outOff + 1] = (byte)(num >> 8); + output[outOff] = (byte)num; + } + + private static byte[] CM5func( + byte[] buf, + int bufOff, + byte[] mac) + { + byte[] sum = new byte[buf.Length - bufOff]; + + Array.Copy(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 input) + { + if (bufOff == buf.Length) + { + byte[] sumbuf = new byte[buf.Length]; + Array.Copy(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++] = input; + } + + public void BlockUpdate( + byte[] input, + int inOff, + int len) + { + if (len < 0) + throw new ArgumentException("Can't have a negative input length!"); + + int gapLen = blockSize - bufOff; + + if (len > gapLen) + { + Array.Copy(input, inOff, buf, bufOff, gapLen); + + byte[] sumbuf = new byte[buf.Length]; + Array.Copy(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(input, inOff, mac); + gost28147MacFunc(workingKey, sumbuf, 0, mac, 0); + + len -= blockSize; + inOff += blockSize; + } + } + + Array.Copy(input, inOff, buf, bufOff, len); + + bufOff += len; + } + + public int DoFinal( + byte[] output, + int outOff) + { + //padding with zero + while (bufOff < blockSize) + { + buf[bufOff++] = 0; + } + + byte[] sumbuf = new byte[buf.Length]; + Array.Copy(buf, 0, sumbuf, 0, mac.Length); + + if (firstStep) + { + firstStep = false; + } + else + { + sumbuf = CM5func(buf, 0, mac); + } + + gost28147MacFunc(workingKey, sumbuf, 0, mac, 0); + + Array.Copy(mac, (mac.Length/2)-macSize, output, outOff, macSize); + + Reset(); + + return macSize; + } + + public void Reset() + { + // Clear the buffer. + Array.Clear(buf, 0, buf.Length); + bufOff = 0; + + firstStep = true; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/macs/HMac.cs b/bc-sharp-crypto/src/crypto/macs/HMac.cs new file mode 100644 index 0000000..460f3c5 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/macs/HMac.cs @@ -0,0 +1,154 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Macs +{ + /** + * HMAC implementation based on RFC2104 + * + * H(K XOR opad, H(K XOR ipad, text)) + */ + public class HMac + : IMac + { + private const byte IPAD = (byte)0x36; + private const byte OPAD = (byte)0x5C; + + private readonly IDigest digest; + private readonly int digestSize; + private readonly int blockLength; + private IMemoable ipadState; + private IMemoable opadState; + + private readonly byte[] inputPad; + private readonly byte[] outputBuf; + + public HMac(IDigest digest) + { + this.digest = digest; + this.digestSize = digest.GetDigestSize(); + this.blockLength = digest.GetByteLength(); + this.inputPad = new byte[blockLength]; + this.outputBuf = new byte[blockLength + digestSize]; + } + + public virtual string AlgorithmName + { + get { return digest.AlgorithmName + "/HMAC"; } + } + + public virtual IDigest GetUnderlyingDigest() + { + return digest; + } + + public virtual void Init(ICipherParameters parameters) + { + digest.Reset(); + + byte[] key = ((KeyParameter)parameters).GetKey(); + int keyLength = key.Length; + + if (keyLength > blockLength) + { + digest.BlockUpdate(key, 0, keyLength); + digest.DoFinal(inputPad, 0); + + keyLength = digestSize; + } + else + { + Array.Copy(key, 0, inputPad, 0, keyLength); + } + + Array.Clear(inputPad, keyLength, blockLength - keyLength); + Array.Copy(inputPad, 0, outputBuf, 0, blockLength); + + XorPad(inputPad, blockLength, IPAD); + XorPad(outputBuf, blockLength, OPAD); + + if (digest is IMemoable) + { + opadState = ((IMemoable)digest).Copy(); + + ((IDigest)opadState).BlockUpdate(outputBuf, 0, blockLength); + } + + digest.BlockUpdate(inputPad, 0, inputPad.Length); + + if (digest is IMemoable) + { + ipadState = ((IMemoable)digest).Copy(); + } + } + + public virtual int GetMacSize() + { + return digestSize; + } + + public virtual void Update(byte input) + { + digest.Update(input); + } + + public virtual void BlockUpdate(byte[] input, int inOff, int len) + { + digest.BlockUpdate(input, inOff, len); + } + + public virtual int DoFinal(byte[] output, int outOff) + { + digest.DoFinal(outputBuf, blockLength); + + if (opadState != null) + { + ((IMemoable)digest).Reset(opadState); + digest.BlockUpdate(outputBuf, blockLength, digest.GetDigestSize()); + } + else + { + digest.BlockUpdate(outputBuf, 0, outputBuf.Length); + } + + int len = digest.DoFinal(output, outOff); + + Array.Clear(outputBuf, blockLength, digestSize); + + if (ipadState != null) + { + ((IMemoable)digest).Reset(ipadState); + } + else + { + digest.BlockUpdate(inputPad, 0, inputPad.Length); + } + + return len; + } + + /** + * Reset the mac generator. + */ + public virtual void Reset() + { + // Reset underlying digest + digest.Reset(); + + // Initialise the digest + digest.BlockUpdate(inputPad, 0, inputPad.Length); + } + + private static void XorPad(byte[] pad, int len, byte n) + { + for (int i = 0; i < len; ++i) + { + pad[i] ^= n; + } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/macs/ISO9797Alg3Mac.cs b/bc-sharp-crypto/src/crypto/macs/ISO9797Alg3Mac.cs new file mode 100644 index 0000000..6fee619 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/macs/ISO9797Alg3Mac.cs @@ -0,0 +1,275 @@ +using System; + +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Modes; +using Org.BouncyCastle.Crypto.Paddings; +using Org.BouncyCastle.Crypto.Parameters; + +namespace Org.BouncyCastle.Crypto.Macs +{ + /** + * 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 : IMac + { + private byte[] mac; + private byte[] buf; + private int bufOff; + private IBlockCipher cipher; + private IBlockCipherPadding 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( + IBlockCipher 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( + IBlockCipher cipher, + IBlockCipherPadding 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( + IBlockCipher 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( + IBlockCipher cipher, + int macSizeInBits, + IBlockCipherPadding padding) + { + if ((macSizeInBits % 8) != 0) + throw new ArgumentException("MAC size must be multiple of 8"); + + if (!(cipher is DesEngine)) + throw new ArgumentException("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 AlgorithmName + { + get { return "ISO9797Alg3"; } + } + + public void Init( + ICipherParameters parameters) + { + Reset(); + + if (!(parameters is KeyParameter || parameters is ParametersWithIV)) + throw new ArgumentException("parameters must be an instance of KeyParameter or ParametersWithIV"); + + // KeyParameter must contain a double or triple length DES key, + // however the underlying cipher is a single DES. The middle and + // right key are used only in the final step. + + KeyParameter kp; + if (parameters is KeyParameter) + { + kp = (KeyParameter)parameters; + } + else + { + kp = (KeyParameter)((ParametersWithIV)parameters).Parameters; + } + + 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 ArgumentException("Key must be either 112 or 168 bit long"); + } + + if (parameters is ParametersWithIV) + { + cipher.Init(true, new ParametersWithIV(key1, ((ParametersWithIV)parameters).GetIV())); + } + else + { + cipher.Init(true, key1); + } + } + + public int GetMacSize() + { + return macSize; + } + + public void Update( + byte input) + { + if (bufOff == buf.Length) + { + cipher.ProcessBlock(buf, 0, mac, 0); + bufOff = 0; + } + + buf[bufOff++] = input; + } + + public void BlockUpdate( + byte[] input, + int inOff, + int len) + { + if (len < 0) + throw new ArgumentException("Can't have a negative input length!"); + + int blockSize = cipher.GetBlockSize(); + int resultLen = 0; + int gapLen = blockSize - bufOff; + + if (len > gapLen) + { + Array.Copy(input, inOff, buf, bufOff, gapLen); + + resultLen += cipher.ProcessBlock(buf, 0, mac, 0); + + bufOff = 0; + len -= gapLen; + inOff += gapLen; + + while (len > blockSize) + { + resultLen += cipher.ProcessBlock(input, inOff, mac, 0); + + len -= blockSize; + inOff += blockSize; + } + } + + Array.Copy(input, inOff, buf, bufOff, len); + + bufOff += len; + } + + public int DoFinal( + byte[] output, + int outOff) + { + int blockSize = cipher.GetBlockSize(); + + if (padding == null) + { + // pad with zeroes + while (bufOff < blockSize) + { + buf[bufOff++] = 0; + } + } + 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); + // **** + + Array.Copy(mac, 0, output, outOff, macSize); + + Reset(); + + return macSize; + } + + /** + * Reset the mac generator. + */ + public void Reset() + { + Array.Clear(buf, 0, buf.Length); + bufOff = 0; + + // reset the underlying cipher. + cipher.Reset(); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/macs/Poly1305.cs b/bc-sharp-crypto/src/crypto/macs/Poly1305.cs new file mode 100644 index 0000000..c0a660f --- /dev/null +++ b/bc-sharp-crypto/src/crypto/macs/Poly1305.cs @@ -0,0 +1,293 @@ +using System; + +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Utilities; + +namespace Org.BouncyCastle.Crypto.Macs +{ + + /// + /// Poly1305 message authentication code, designed by D. J. Bernstein. + /// + /// + /// Poly1305 computes a 128-bit (16 bytes) authenticator, using a 128 bit nonce and a 256 bit key + /// consisting of a 128 bit key applied to an underlying cipher, and a 128 bit key (with 106 + /// effective key bits) used in the authenticator. + /// + /// The polynomial calculation in this implementation is adapted from the public domain poly1305-donna-unrolled C implementation + /// by Andrew M (@floodyberry). + /// + /// + public class Poly1305 + : IMac + { + private const int BlockSize = 16; + + private readonly IBlockCipher cipher; + + private readonly byte[] singleByte = new byte[1]; + + // Initialised state + + /** Polynomial key */ + private uint r0, r1, r2, r3, r4; + + /** Precomputed 5 * r[1..4] */ + private uint s1, s2, s3, s4; + + /** Encrypted nonce */ + private uint k0, k1, k2, k3; + + // Accumulating state + + /** Current block of buffered input */ + private byte[] currentBlock = new byte[BlockSize]; + + /** Current offset in input buffer */ + private int currentBlockOffset = 0; + + /** Polynomial accumulator */ + private uint h0, h1, h2, h3, h4; + + /** + * Constructs a Poly1305 MAC, where the key passed to init() will be used directly. + */ + public Poly1305() + { + this.cipher = null; + } + + /** + * Constructs a Poly1305 MAC, using a 128 bit block cipher. + */ + public Poly1305(IBlockCipher cipher) + { + if (cipher.GetBlockSize() != BlockSize) + { + throw new ArgumentException("Poly1305 requires a 128 bit block cipher."); + } + this.cipher = cipher; + } + + /// + /// Initialises the Poly1305 MAC. + /// + /// a {@link ParametersWithIV} containing a 128 bit nonce and a {@link KeyParameter} with + /// a 256 bit key complying to the {@link Poly1305KeyGenerator Poly1305 key format}. + public void Init(ICipherParameters parameters) + { + byte[] nonce = null; + + if (cipher != null) + { + if (!(parameters is ParametersWithIV)) + throw new ArgumentException("Poly1305 requires an IV when used with a block cipher.", "parameters"); + + ParametersWithIV ivParams = (ParametersWithIV)parameters; + nonce = ivParams.GetIV(); + parameters = ivParams.Parameters; + } + + if (!(parameters is KeyParameter)) + throw new ArgumentException("Poly1305 requires a key."); + + KeyParameter keyParams = (KeyParameter)parameters; + + SetKey(keyParams.GetKey(), nonce); + + Reset(); + } + + private void SetKey(byte[] key, byte[] nonce) + { + if (key.Length != 32) + throw new ArgumentException("Poly1305 key must be 256 bits."); + + if (cipher != null && (nonce == null || nonce.Length != BlockSize)) + throw new ArgumentException("Poly1305 requires a 128 bit IV."); + + // Extract r portion of key (and "clamp" the values) + uint t0 = Pack.LE_To_UInt32(key, 0); + uint t1 = Pack.LE_To_UInt32(key, 4); + uint t2 = Pack.LE_To_UInt32(key, 8); + uint t3 = Pack.LE_To_UInt32(key, 12); + + // NOTE: The masks perform the key "clamping" implicitly + r0 = t0 & 0x03FFFFFFU; + r1 = ((t0 >> 26) | (t1 << 6)) & 0x03FFFF03U; + r2 = ((t1 >> 20) | (t2 << 12)) & 0x03FFC0FFU; + r3 = ((t2 >> 14) | (t3 << 18)) & 0x03F03FFFU; + r4 = (t3 >> 8) & 0x000FFFFFU; + + // Precompute multipliers + s1 = r1 * 5; + s2 = r2 * 5; + s3 = r3 * 5; + s4 = r4 * 5; + + byte[] kBytes; + int kOff; + + if (cipher == null) + { + kBytes = key; + kOff = BlockSize; + } + else + { + // Compute encrypted nonce + kBytes = new byte[BlockSize]; + kOff = 0; + + cipher.Init(true, new KeyParameter(key, BlockSize, BlockSize)); + cipher.ProcessBlock(nonce, 0, kBytes, 0); + } + + k0 = Pack.LE_To_UInt32(kBytes, kOff + 0); + k1 = Pack.LE_To_UInt32(kBytes, kOff + 4); + k2 = Pack.LE_To_UInt32(kBytes, kOff + 8); + k3 = Pack.LE_To_UInt32(kBytes, kOff + 12); + } + + public string AlgorithmName + { + get { return cipher == null ? "Poly1305" : "Poly1305-" + cipher.AlgorithmName; } + } + + public int GetMacSize() + { + return BlockSize; + } + + public void Update(byte input) + { + singleByte[0] = input; + BlockUpdate(singleByte, 0, 1); + } + + public void BlockUpdate(byte[] input, int inOff, int len) + { + int copied = 0; + while (len > copied) + { + if (currentBlockOffset == BlockSize) + { + ProcessBlock(); + currentBlockOffset = 0; + } + + int toCopy = System.Math.Min((len - copied), BlockSize - currentBlockOffset); + Array.Copy(input, copied + inOff, currentBlock, currentBlockOffset, toCopy); + copied += toCopy; + currentBlockOffset += toCopy; + } + + } + + private void ProcessBlock() + { + if (currentBlockOffset < BlockSize) + { + currentBlock[currentBlockOffset] = 1; + for (int i = currentBlockOffset + 1; i < BlockSize; i++) + { + currentBlock[i] = 0; + } + } + + ulong t0 = Pack.LE_To_UInt32(currentBlock, 0); + ulong t1 = Pack.LE_To_UInt32(currentBlock, 4); + ulong t2 = Pack.LE_To_UInt32(currentBlock, 8); + ulong t3 = Pack.LE_To_UInt32(currentBlock, 12); + + h0 += (uint)(t0 & 0x3ffffffU); + h1 += (uint)((((t1 << 32) | t0) >> 26) & 0x3ffffff); + h2 += (uint)((((t2 << 32) | t1) >> 20) & 0x3ffffff); + h3 += (uint)((((t3 << 32) | t2) >> 14) & 0x3ffffff); + h4 += (uint)(t3 >> 8); + + if (currentBlockOffset == BlockSize) + { + h4 += (1 << 24); + } + + ulong tp0 = mul32x32_64(h0,r0) + mul32x32_64(h1,s4) + mul32x32_64(h2,s3) + mul32x32_64(h3,s2) + mul32x32_64(h4,s1); + ulong tp1 = mul32x32_64(h0,r1) + mul32x32_64(h1,r0) + mul32x32_64(h2,s4) + mul32x32_64(h3,s3) + mul32x32_64(h4,s2); + ulong tp2 = mul32x32_64(h0,r2) + mul32x32_64(h1,r1) + mul32x32_64(h2,r0) + mul32x32_64(h3,s4) + mul32x32_64(h4,s3); + ulong tp3 = mul32x32_64(h0,r3) + mul32x32_64(h1,r2) + mul32x32_64(h2,r1) + mul32x32_64(h3,r0) + mul32x32_64(h4,s4); + ulong tp4 = mul32x32_64(h0,r4) + mul32x32_64(h1,r3) + mul32x32_64(h2,r2) + mul32x32_64(h3,r1) + mul32x32_64(h4,r0); + + h0 = (uint)tp0 & 0x3ffffff; tp1 += (tp0 >> 26); + h1 = (uint)tp1 & 0x3ffffff; tp2 += (tp1 >> 26); + h2 = (uint)tp2 & 0x3ffffff; tp3 += (tp2 >> 26); + h3 = (uint)tp3 & 0x3ffffff; tp4 += (tp3 >> 26); + h4 = (uint)tp4 & 0x3ffffff; + h0 += (uint)(tp4 >> 26) * 5; + h1 += (h0 >> 26); h0 &= 0x3ffffff; + } + + public int DoFinal(byte[] output, int outOff) + { + Check.DataLength(output, outOff, BlockSize, "Output buffer is too short."); + + if (currentBlockOffset > 0) + { + // Process padded block + ProcessBlock(); + } + + h1 += (h0 >> 26); h0 &= 0x3ffffff; + h2 += (h1 >> 26); h1 &= 0x3ffffff; + h3 += (h2 >> 26); h2 &= 0x3ffffff; + h4 += (h3 >> 26); h3 &= 0x3ffffff; + h0 += (h4 >> 26) * 5; h4 &= 0x3ffffff; + h1 += (h0 >> 26); h0 &= 0x3ffffff; + + uint g0, g1, g2, g3, g4, b; + g0 = h0 + 5; b = g0 >> 26; g0 &= 0x3ffffff; + g1 = h1 + b; b = g1 >> 26; g1 &= 0x3ffffff; + g2 = h2 + b; b = g2 >> 26; g2 &= 0x3ffffff; + g3 = h3 + b; b = g3 >> 26; g3 &= 0x3ffffff; + g4 = h4 + b - (1 << 26); + + b = (g4 >> 31) - 1; + uint nb = ~b; + h0 = (h0 & nb) | (g0 & b); + h1 = (h1 & nb) | (g1 & b); + h2 = (h2 & nb) | (g2 & b); + h3 = (h3 & nb) | (g3 & b); + h4 = (h4 & nb) | (g4 & b); + + ulong f0, f1, f2, f3; + f0 = ((h0 ) | (h1 << 26)) + (ulong)k0; + f1 = ((h1 >> 6 ) | (h2 << 20)) + (ulong)k1; + f2 = ((h2 >> 12) | (h3 << 14)) + (ulong)k2; + f3 = ((h3 >> 18) | (h4 << 8 )) + (ulong)k3; + + Pack.UInt32_To_LE((uint)f0, output, outOff); + f1 += (f0 >> 32); + Pack.UInt32_To_LE((uint)f1, output, outOff + 4); + f2 += (f1 >> 32); + Pack.UInt32_To_LE((uint)f2, output, outOff + 8); + f3 += (f2 >> 32); + Pack.UInt32_To_LE((uint)f3, output, outOff + 12); + + Reset(); + return BlockSize; + } + + public void Reset() + { + currentBlockOffset = 0; + + h0 = h1 = h2 = h3 = h4 = 0; + } + + private static ulong mul32x32_64(uint i1, uint i2) + { + return ((ulong)i1) * i2; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/macs/SipHash.cs b/bc-sharp-crypto/src/crypto/macs/SipHash.cs new file mode 100644 index 0000000..e1a19fa --- /dev/null +++ b/bc-sharp-crypto/src/crypto/macs/SipHash.cs @@ -0,0 +1,199 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Utilities; + +namespace Org.BouncyCastle.Crypto.Macs +{ + /// + /// Implementation of SipHash as specified in "SipHash: a fast short-input PRF", by Jean-Philippe + /// Aumasson and Daniel J. Bernstein (https://131002.net/siphash/siphash.pdf). + /// + /// + /// "SipHash is a family of PRFs SipHash-c-d where the integer parameters c and d are the number of + /// compression rounds and the number of finalization rounds. A compression round is identical to a + /// finalization round and this round function is called SipRound. Given a 128-bit key k and a + /// (possibly empty) byte string m, SipHash-c-d returns a 64-bit value..." + /// + public class SipHash + : IMac + { + protected readonly int c, d; + + protected long k0, k1; + protected long v0, v1, v2, v3; + + protected long m = 0; + protected int wordPos = 0; + protected int wordCount = 0; + + /// SipHash-2-4 + public SipHash() + : this(2, 4) + { + } + + /// SipHash-c-d + /// the number of compression rounds + /// the number of finalization rounds + public SipHash(int c, int d) + { + this.c = c; + this.d = d; + } + + public virtual string AlgorithmName + { + get { return "SipHash-" + c + "-" + d; } + } + + public virtual int GetMacSize() + { + return 8; + } + + public virtual void Init(ICipherParameters parameters) + { + KeyParameter keyParameter = parameters as KeyParameter; + if (keyParameter == null) + throw new ArgumentException("must be an instance of KeyParameter", "parameters"); + byte[] key = keyParameter.GetKey(); + if (key.Length != 16) + throw new ArgumentException("must be a 128-bit key", "parameters"); + + this.k0 = (long)Pack.LE_To_UInt64(key, 0); + this.k1 = (long)Pack.LE_To_UInt64(key, 8); + + Reset(); + } + + public virtual void Update(byte input) + { + m = (long)(((ulong)m >> 8) | ((ulong)input << 56)); + + if (++wordPos == 8) + { + ProcessMessageWord(); + wordPos = 0; + } + } + + public virtual void BlockUpdate(byte[] input, int offset, int length) + { + int i = 0, fullWords = length & ~7; + if (wordPos == 0) + { + for (; i < fullWords; i += 8) + { + m = (long)Pack.LE_To_UInt64(input, offset + i); + ProcessMessageWord(); + } + for (; i < length; ++i) + { + m = (long)(((ulong)m >> 8) | ((ulong)input[offset + i] << 56)); + } + wordPos = length - fullWords; + } + else + { + int bits = wordPos << 3; + for (; i < fullWords; i += 8) + { + ulong n = Pack.LE_To_UInt64(input, offset + i); + m = (long)((n << bits) | ((ulong)m >> -bits)); + ProcessMessageWord(); + m = (long)n; + } + for (; i < length; ++i) + { + m = (long)(((ulong)m >> 8) | ((ulong)input[offset + i] << 56)); + + if (++wordPos == 8) + { + ProcessMessageWord(); + wordPos = 0; + } + } + } + } + + public virtual long DoFinal() + { + // NOTE: 2 distinct shifts to avoid "64-bit shift" when wordPos == 0 + m = (long)((ulong)m >> ((7 - wordPos) << 3)); + m = (long)((ulong)m >> 8); + m = (long)((ulong)m | ((ulong)((wordCount << 3) + wordPos) << 56)); + + ProcessMessageWord(); + + v2 ^= 0xffL; + + ApplySipRounds(d); + + long result = v0 ^ v1 ^ v2 ^ v3; + + Reset(); + + return result; + } + + public virtual int DoFinal(byte[] output, int outOff) + { + long result = DoFinal(); + Pack.UInt64_To_LE((ulong)result, output, outOff); + return 8; + } + + public virtual void Reset() + { + v0 = k0 ^ 0x736f6d6570736575L; + v1 = k1 ^ 0x646f72616e646f6dL; + v2 = k0 ^ 0x6c7967656e657261L; + v3 = k1 ^ 0x7465646279746573L; + + m = 0; + wordPos = 0; + wordCount = 0; + } + + protected virtual void ProcessMessageWord() + { + ++wordCount; + v3 ^= m; + ApplySipRounds(c); + v0 ^= m; + } + + protected virtual void ApplySipRounds(int n) + { + long r0 = v0, r1 = v1, r2 = v2, r3 = v3; + + for (int r = 0; r < n; ++r) + { + r0 += r1; + r2 += r3; + r1 = RotateLeft(r1, 13); + r3 = RotateLeft(r3, 16); + r1 ^= r0; + r3 ^= r2; + r0 = RotateLeft(r0, 32); + r2 += r1; + r0 += r3; + r1 = RotateLeft(r1, 17); + r3 = RotateLeft(r3, 21); + r1 ^= r2; + r3 ^= r0; + r2 = RotateLeft(r2, 32); + } + + v0 = r0; v1 = r1; v2 = r2; v3 = r3; + } + + protected static long RotateLeft(long x, int n) + { + ulong ux = (ulong)x; + ux = (ux << n) | (ux >> -n); + return (long)ux; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/macs/SkeinMac.cs b/bc-sharp-crypto/src/crypto/macs/SkeinMac.cs new file mode 100644 index 0000000..07eff24 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/macs/SkeinMac.cs @@ -0,0 +1,118 @@ +using System; + +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Utilities; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Macs +{ + + /// + /// Implementation of the Skein parameterised MAC function in 256, 512 and 1024 bit block sizes, + /// based on the Threefish tweakable block cipher. + /// + /// + /// This is the 1.3 version of Skein defined in the Skein hash function submission to the NIST SHA-3 + /// competition in October 2010. + ///

+ /// Skein was designed by Niels Ferguson - Stefan Lucks - Bruce Schneier - Doug Whiting - Mihir + /// Bellare - Tadayoshi Kohno - Jon Callas - Jesse Walker. + /// + /// + /// + public class SkeinMac + : IMac + { + ///

+ /// 256 bit block size - Skein-256 + /// + public const int SKEIN_256 = SkeinEngine.SKEIN_256; + /// + /// 512 bit block size - Skein-512 + /// + public const int SKEIN_512 = SkeinEngine.SKEIN_512; + /// + /// 1024 bit block size - Skein-1024 + /// + public const int SKEIN_1024 = SkeinEngine.SKEIN_1024; + + private readonly SkeinEngine engine; + + /// + /// Constructs a Skein MAC with an internal state size and output size. + /// + /// the internal state size in bits - one of or + /// . + /// the output/MAC size to produce in bits, which must be an integral number of + /// bytes. + public SkeinMac(int stateSizeBits, int digestSizeBits) + { + this.engine = new SkeinEngine(stateSizeBits, digestSizeBits); + } + + public SkeinMac(SkeinMac mac) + { + this.engine = new SkeinEngine(mac.engine); + } + + public string AlgorithmName + { + get { return "Skein-MAC-" + (engine.BlockSize * 8) + "-" + (engine.OutputSize * 8); } + } + + /// + /// Optionally initialises the Skein digest with the provided parameters. + /// + /// See for details on the parameterisation of the Skein hash function. + /// the parameters to apply to this engine, or null to use no parameters. + public void Init(ICipherParameters parameters) + { + SkeinParameters skeinParameters; + if (parameters is SkeinParameters) + { + skeinParameters = (SkeinParameters)parameters; + } + else if (parameters is KeyParameter) + { + skeinParameters = new SkeinParameters.Builder().SetKey(((KeyParameter)parameters).GetKey()).Build(); + } + else + { + throw new ArgumentException("Invalid parameter passed to Skein MAC init - " + + Platform.GetTypeName(parameters)); + } + if (skeinParameters.GetKey() == null) + { + throw new ArgumentException("Skein MAC requires a key parameter."); + } + engine.Init(skeinParameters); + } + + public int GetMacSize() + { + return engine.OutputSize; + } + + public void Reset() + { + engine.Reset(); + } + + public void Update(byte inByte) + { + engine.Update(inByte); + } + + public void BlockUpdate(byte[] input, int inOff, int len) + { + engine.Update(input, inOff, len); + } + + public int DoFinal(byte[] output, int outOff) + { + return engine.DoFinal(output, outOff); + } + + } +} diff --git a/bc-sharp-crypto/src/crypto/macs/VMPCMac.cs b/bc-sharp-crypto/src/crypto/macs/VMPCMac.cs new file mode 100644 index 0000000..6f2da07 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/macs/VMPCMac.cs @@ -0,0 +1,173 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; + +namespace Org.BouncyCastle.Crypto.Macs +{ + public class VmpcMac + : IMac + { + 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 virtual int DoFinal(byte[] output, int outOff) + { + // 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 input 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; + } + + Array.Copy(M, 0, output, outOff, M.Length); + Reset(); + + return M.Length; + } + + public virtual string AlgorithmName + { + get { return "VMPC-MAC"; } + } + + public virtual int GetMacSize() + { + return 20; + } + + public virtual void Init(ICipherParameters parameters) + { + if (!(parameters is ParametersWithIV)) + throw new ArgumentException("VMPC-MAC Init parameters must include an IV", "parameters"); + + ParametersWithIV ivParams = (ParametersWithIV) parameters; + KeyParameter key = (KeyParameter) ivParams.Parameters; + + if (!(ivParams.Parameters is KeyParameter)) + throw new ArgumentException("VMPC-MAC Init parameters must include a key", "parameters"); + + this.workingIV = ivParams.GetIV(); + + if (workingIV == null || workingIV.Length < 1 || workingIV.Length > 768) + throw new ArgumentException("VMPC-MAC requires 1 to 768 bytes of IV", "parameters"); + + 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 virtual 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 virtual void Update(byte input) + { + s = P[(s + P[n & 0xff]) & 0xff]; + byte c = (byte) (input ^ 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 virtual void BlockUpdate(byte[] input, int inOff, int len) + { + if ((inOff + len) > input.Length) + throw new DataLengthException("input buffer too short"); + + for (int i = 0; i < len; i++) + { + Update(input[inOff + i]); + } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/modes/CbcBlockCipher.cs b/bc-sharp-crypto/src/crypto/modes/CbcBlockCipher.cs new file mode 100644 index 0000000..9345fd8 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/modes/CbcBlockCipher.cs @@ -0,0 +1,241 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; + +namespace Org.BouncyCastle.Crypto.Modes +{ + /** + * implements Cipher-Block-Chaining (CBC) mode on top of a simple cipher. + */ + public class CbcBlockCipher + : IBlockCipher + { + private byte[] IV, cbcV, cbcNextV; + private int blockSize; + private IBlockCipher cipher; + private bool encrypting; + + /** + * Basic constructor. + * + * @param cipher the block cipher to be used as the basis of chaining. + */ + public CbcBlockCipher( + IBlockCipher 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 IBlockCipher 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 forEncryption if true the cipher is initialised for + * encryption, if false for decryption. + * @param param the key and other data required by the cipher. + * @exception ArgumentException if the parameters argument is + * inappropriate. + */ + public void Init( + bool forEncryption, + ICipherParameters parameters) + { + bool oldEncrypting = this.encrypting; + + this.encrypting = forEncryption; + + if (parameters is ParametersWithIV) + { + ParametersWithIV ivParam = (ParametersWithIV)parameters; + byte[] iv = ivParam.GetIV(); + + if (iv.Length != blockSize) + { + throw new ArgumentException("initialisation vector must be the same length as block size"); + } + + Array.Copy(iv, 0, IV, 0, iv.Length); + + parameters = ivParam.Parameters; + } + + Reset(); + + // if null it's an IV changed only. + if (parameters != null) + { + cipher.Init(encrypting, parameters); + } + else if (oldEncrypting != encrypting) + { + throw new ArgumentException("cannot change encrypting state without providing key."); + } + } + + /** + * return the algorithm name and mode. + * + * @return the name of the underlying algorithm followed by "/CBC". + */ + public string AlgorithmName + { + get { return cipher.AlgorithmName + "/CBC"; } + } + + public bool IsPartialBlockOkay + { + get { return false; } + } + + /** + * 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 InvalidOperationException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + public int ProcessBlock( + byte[] input, + int inOff, + byte[] output, + int outOff) + { + return (encrypting) + ? EncryptBlock(input, inOff, output, outOff) + : DecryptBlock(input, inOff, output, outOff); + } + + /** + * reset the chaining vector back to the IV and reset the underlying + * cipher. + */ + public void Reset() + { + Array.Copy(IV, 0, cbcV, 0, IV.Length); + Array.Clear(cbcNextV, 0, cbcNextV.Length); + + 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 InvalidOperationException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + private int EncryptBlock( + byte[] input, + int inOff, + byte[] outBytes, + int outOff) + { + if ((inOff + blockSize) > input.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] ^= input[inOff + i]; + } + + int length = cipher.ProcessBlock(cbcV, 0, outBytes, outOff); + + /* + * copy ciphertext to cbcV + */ + Array.Copy(outBytes, 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 InvalidOperationException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + private int DecryptBlock( + byte[] input, + int inOff, + byte[] outBytes, + int outOff) + { + if ((inOff + blockSize) > input.Length) + { + throw new DataLengthException("input buffer too short"); + } + + Array.Copy(input, inOff, cbcNextV, 0, blockSize); + + int length = cipher.ProcessBlock(input, inOff, outBytes, outOff); + + /* + * XOR the cbcV and the output + */ + for (int i = 0; i < blockSize; i++) + { + outBytes[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/bc-sharp-crypto/src/crypto/modes/CcmBlockCipher.cs b/bc-sharp-crypto/src/crypto/modes/CcmBlockCipher.cs new file mode 100644 index 0000000..4de40d5 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/modes/CcmBlockCipher.cs @@ -0,0 +1,449 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Macs; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Modes +{ + /** + * 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 + : IAeadBlockCipher + { + private static readonly int BlockSize = 16; + + private readonly IBlockCipher cipher; + private readonly byte[] macBlock; + private bool forEncryption; + private byte[] nonce; + private byte[] initialAssociatedText; + private int macSize; + private ICipherParameters keyParam; + private readonly MemoryStream associatedText = new MemoryStream(); + private readonly MemoryStream data = new MemoryStream(); + + /** + * Basic constructor. + * + * @param cipher the block cipher to be used. + */ + public CcmBlockCipher( + IBlockCipher cipher) + { + this.cipher = cipher; + this.macBlock = new byte[BlockSize]; + + if (cipher.GetBlockSize() != BlockSize) + throw new ArgumentException("cipher required with a block size of " + BlockSize + "."); + } + + /** + * return the underlying block cipher that we are wrapping. + * + * @return the underlying block cipher that we are wrapping. + */ + public virtual IBlockCipher GetUnderlyingCipher() + { + return cipher; + } + + public virtual void Init( + bool forEncryption, + ICipherParameters parameters) + { + this.forEncryption = forEncryption; + + ICipherParameters cipherParameters; + if (parameters is AeadParameters) + { + AeadParameters param = (AeadParameters) parameters; + + nonce = param.GetNonce(); + initialAssociatedText = param.GetAssociatedText(); + macSize = param.MacSize / 8; + cipherParameters = param.Key; + } + else if (parameters is ParametersWithIV) + { + ParametersWithIV param = (ParametersWithIV) parameters; + + nonce = param.GetIV(); + initialAssociatedText = null; + macSize = macBlock.Length / 2; + cipherParameters = param.Parameters; + } + else + { + throw new ArgumentException("invalid parameters passed to CCM"); + } + + // NOTE: Very basic support for key re-use, but no performance gain from it + if (cipherParameters != null) + { + keyParam = cipherParameters; + } + + if (nonce == null || nonce.Length < 7 || nonce.Length > 13) + { + throw new ArgumentException("nonce must have length from 7 to 13 octets"); + } + + Reset(); + } + + public virtual string AlgorithmName + { + get { return cipher.AlgorithmName + "/CCM"; } + } + + public virtual int GetBlockSize() + { + return cipher.GetBlockSize(); + } + + public virtual void ProcessAadByte(byte input) + { + associatedText.WriteByte(input); + } + + public virtual void ProcessAadBytes(byte[] inBytes, int inOff, int len) + { + // TODO: Process AAD online + associatedText.Write(inBytes, inOff, len); + } + + public virtual int ProcessByte( + byte input, + byte[] outBytes, + int outOff) + { + data.WriteByte(input); + + return 0; + } + + public virtual int ProcessBytes( + byte[] inBytes, + int inOff, + int inLen, + byte[] outBytes, + int outOff) + { + Check.DataLength(inBytes, inOff, inLen, "Input buffer too short"); + + data.Write(inBytes, inOff, inLen); + + return 0; + } + + public virtual int DoFinal( + byte[] outBytes, + int outOff) + { +#if PORTABLE + byte[] input = data.ToArray(); + int inLen = input.Length; +#else + byte[] input = data.GetBuffer(); + int inLen = (int)data.Position; +#endif + + int len = ProcessPacket(input, 0, inLen, outBytes, outOff); + + Reset(); + + return len; + } + + public virtual void Reset() + { + cipher.Reset(); + associatedText.SetLength(0); + data.SetLength(0); + } + + /** + * Returns a byte array containing the mac calculated as part of the + * last encrypt or decrypt operation. + * + * @return the last mac calculated. + */ + public virtual byte[] GetMac() + { + return Arrays.CopyOfRange(macBlock, 0, macSize); + } + + public virtual int GetUpdateOutputSize( + int len) + { + return 0; + } + + public virtual int GetOutputSize( + int len) + { + int totalData = (int)data.Length + len; + + if (forEncryption) + { + return totalData + macSize; + } + + return totalData < macSize ? 0 : totalData - macSize; + } + + /** + * Process a packet of data for either CCM decryption or encryption. + * + * @param in data for processing. + * @param inOff offset at which data starts in the input array. + * @param inLen length of the data in the input array. + * @return a byte array containing the processed input.. + * @throws IllegalStateException if the cipher is not appropriately set up. + * @throws InvalidCipherTextException if the input data is truncated or the mac check fails. + */ + public virtual byte[] ProcessPacket(byte[] input, int inOff, int inLen) + { + byte[] output; + + if (forEncryption) + { + output = new byte[inLen + macSize]; + } + else + { + if (inLen < macSize) + throw new InvalidCipherTextException("data too short"); + + output = new byte[inLen - macSize]; + } + + ProcessPacket(input, inOff, inLen, output, 0); + + return output; + } + + /** + * Process a packet of data for either CCM decryption or encryption. + * + * @param in data for processing. + * @param inOff offset at which data starts in the input array. + * @param inLen length of the data in the input array. + * @param output output array. + * @param outOff offset into output array to start putting processed bytes. + * @return the number of bytes added to output. + * @throws IllegalStateException if the cipher is not appropriately set up. + * @throws InvalidCipherTextException if the input data is truncated or the mac check fails. + * @throws DataLengthException if output buffer too short. + */ + public virtual int ProcessPacket(byte[] input, int inOff, int inLen, byte[] output, int outOff) + { + // TODO: handle null keyParam (e.g. via RepeatedKeySpec) + // Need to keep the CTR and CBC Mac parts around and reset + if (keyParam == null) + throw new InvalidOperationException("CCM cipher unitialized."); + + int n = nonce.Length; + int q = 15 - n; + if (q < 4) + { + int limitLen = 1 << (8 * q); + if (inLen >= limitLen) + throw new InvalidOperationException("CCM packet too large for choice of q."); + } + + byte[] iv = new byte[BlockSize]; + iv[0] = (byte)((q - 1) & 0x7); + nonce.CopyTo(iv, 1); + + IBlockCipher ctrCipher = new SicBlockCipher(cipher); + ctrCipher.Init(forEncryption, new ParametersWithIV(keyParam, iv)); + + int outputLen; + int inIndex = inOff; + int outIndex = outOff; + + if (forEncryption) + { + outputLen = inLen + macSize; + Check.OutputLength(output, outOff, outputLen, "Output buffer too short."); + + CalculateMac(input, inOff, inLen, macBlock); + + byte[] encMac = new byte[BlockSize]; + ctrCipher.ProcessBlock(macBlock, 0, encMac, 0); // S0 + + while (inIndex < (inOff + inLen - BlockSize)) // S1... + { + ctrCipher.ProcessBlock(input, inIndex, output, outIndex); + outIndex += BlockSize; + inIndex += BlockSize; + } + + byte[] block = new byte[BlockSize]; + + Array.Copy(input, inIndex, block, 0, inLen + inOff - inIndex); + + ctrCipher.ProcessBlock(block, 0, block, 0); + + Array.Copy(block, 0, output, outIndex, inLen + inOff - inIndex); + + Array.Copy(encMac, 0, output, outOff + inLen, macSize); + } + else + { + if (inLen < macSize) + throw new InvalidCipherTextException("data too short"); + + outputLen = inLen - macSize; + Check.OutputLength(output, outOff, outputLen, "Output buffer too short."); + + Array.Copy(input, inOff + outputLen, macBlock, 0, macSize); + + ctrCipher.ProcessBlock(macBlock, 0, macBlock, 0); + + for (int i = macSize; i != macBlock.Length; i++) + { + macBlock[i] = 0; + } + + while (inIndex < (inOff + outputLen - BlockSize)) + { + ctrCipher.ProcessBlock(input, inIndex, output, outIndex); + outIndex += BlockSize; + inIndex += BlockSize; + } + + byte[] block = new byte[BlockSize]; + + Array.Copy(input, inIndex, block, 0, outputLen - (inIndex - inOff)); + + ctrCipher.ProcessBlock(block, 0, block, 0); + + Array.Copy(block, 0, output, outIndex, outputLen - (inIndex - inOff)); + + byte[] calculatedMacBlock = new byte[BlockSize]; + + CalculateMac(output, outOff, outputLen, calculatedMacBlock); + + if (!Arrays.ConstantTimeAreEqual(macBlock, calculatedMacBlock)) + throw new InvalidCipherTextException("mac check in CCM failed"); + } + + return outputLen; + } + + private int CalculateMac(byte[] data, int dataOff, int dataLen, byte[] macBlock) + { + IMac cMac = new CbcBlockCipherMac(cipher, macSize * 8); + + cMac.Init(keyParam); + + // + // build b0 + // + byte[] b0 = new byte[16]; + + if (HasAssociatedText()) + { + b0[0] |= 0x40; + } + + b0[0] |= (byte)((((cMac.GetMacSize() - 2) / 2) & 0x7) << 3); + + b0[0] |= (byte)(((15 - nonce.Length) - 1) & 0x7); + + Array.Copy(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.BlockUpdate(b0, 0, b0.Length); + + // + // process associated text + // + if (HasAssociatedText()) + { + int extra; + + int textLength = GetAssociatedTextLength(); + if (textLength < ((1 << 16) - (1 << 8))) + { + cMac.Update((byte)(textLength >> 8)); + cMac.Update((byte)textLength); + + extra = 2; + } + else // can't go any higher than 2^32 + { + cMac.Update((byte)0xff); + cMac.Update((byte)0xfe); + cMac.Update((byte)(textLength >> 24)); + cMac.Update((byte)(textLength >> 16)); + cMac.Update((byte)(textLength >> 8)); + cMac.Update((byte)textLength); + + extra = 6; + } + + if (initialAssociatedText != null) + { + cMac.BlockUpdate(initialAssociatedText, 0, initialAssociatedText.Length); + } + if (associatedText.Position > 0) + { +#if PORTABLE + byte[] input = associatedText.ToArray(); + int len = input.Length; +#else + byte[] input = associatedText.GetBuffer(); + int len = (int)associatedText.Position; +#endif + + cMac.BlockUpdate(input, 0, len); + } + + extra = (extra + textLength) % 16; + if (extra != 0) + { + for (int i = extra; i < 16; ++i) + { + cMac.Update((byte)0x00); + } + } + } + + // + // add the text + // + cMac.BlockUpdate(data, dataOff, dataLen); + + return cMac.DoFinal(macBlock, 0); + } + + private int GetAssociatedTextLength() + { + return (int)associatedText.Length + ((initialAssociatedText == null) ? 0 : initialAssociatedText.Length); + } + + private bool HasAssociatedText() + { + return GetAssociatedTextLength() > 0; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/modes/CfbBlockCipher.cs b/bc-sharp-crypto/src/crypto/modes/CfbBlockCipher.cs new file mode 100644 index 0000000..4337165 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/modes/CfbBlockCipher.cs @@ -0,0 +1,224 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; + +namespace Org.BouncyCastle.Crypto.Modes +{ + /** + * implements a Cipher-FeedBack (CFB) mode on top of a simple cipher. + */ + public class CfbBlockCipher + : IBlockCipher + { + private byte[] IV; + private byte[] cfbV; + private byte[] cfbOutV; + private bool encrypting; + + private readonly int blockSize; + private readonly IBlockCipher 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 CfbBlockCipher( + IBlockCipher 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 IBlockCipher 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 forEncryption if true the cipher is initialised for + * encryption, if false for decryption. + * @param param the key and other data required by the cipher. + * @exception ArgumentException if the parameters argument is + * inappropriate. + */ + public void Init( + bool forEncryption, + ICipherParameters parameters) + { + this.encrypting = forEncryption; + if (parameters is ParametersWithIV) + { + ParametersWithIV ivParam = (ParametersWithIV) parameters; + byte[] iv = ivParam.GetIV(); + int diff = IV.Length - iv.Length; + Array.Copy(iv, 0, IV, diff, iv.Length); + Array.Clear(IV, 0, diff); + + parameters = ivParam.Parameters; + } + Reset(); + + // if it's null, key is to be reused. + if (parameters != null) + { + cipher.Init(true, parameters); + } + } + + /** + * return the algorithm name and mode. + * + * @return the name of the underlying algorithm followed by "/CFB" + * and the block size in bits. + */ + public string AlgorithmName + { + get { return cipher.AlgorithmName + "/CFB" + (blockSize * 8); } + } + + public bool IsPartialBlockOkay + { + get { return true; } + } + + /** + * 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 InvalidOperationException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + public int ProcessBlock( + byte[] input, + int inOff, + byte[] output, + int outOff) + { + return (encrypting) + ? EncryptBlock(input, inOff, output, outOff) + : DecryptBlock(input, inOff, output, 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 InvalidOperationException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + public int EncryptBlock( + byte[] input, + int inOff, + byte[] outBytes, + int outOff) + { + if ((inOff + blockSize) > input.Length) + { + throw new DataLengthException("input buffer too short"); + } + if ((outOff + blockSize) > outBytes.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++) + { + outBytes[outOff + i] = (byte)(cfbOutV[i] ^ input[inOff + i]); + } + // + // change over the input block. + // + Array.Copy(cfbV, blockSize, cfbV, 0, cfbV.Length - blockSize); + Array.Copy(outBytes, 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 InvalidOperationException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + public int DecryptBlock( + byte[] input, + int inOff, + byte[] outBytes, + int outOff) + { + if ((inOff + blockSize) > input.Length) + { + throw new DataLengthException("input buffer too short"); + } + if ((outOff + blockSize) > outBytes.Length) + { + throw new DataLengthException("output buffer too short"); + } + cipher.ProcessBlock(cfbV, 0, cfbOutV, 0); + // + // change over the input block. + // + Array.Copy(cfbV, blockSize, cfbV, 0, cfbV.Length - blockSize); + Array.Copy(input, inOff, cfbV, cfbV.Length - blockSize, blockSize); + // + // XOR the cfbV with the ciphertext producing the plaintext + // + for (int i = 0; i < blockSize; i++) + { + outBytes[outOff + i] = (byte)(cfbOutV[i] ^ input[inOff + i]); + } + return blockSize; + } + /** + * reset the chaining vector back to the IV and reset the underlying + * cipher. + */ + public void Reset() + { + Array.Copy(IV, 0, cfbV, 0, IV.Length); + cipher.Reset(); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/modes/CtsBlockCipher.cs b/bc-sharp-crypto/src/crypto/modes/CtsBlockCipher.cs new file mode 100644 index 0000000..ff37844 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/modes/CtsBlockCipher.cs @@ -0,0 +1,253 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; + +namespace Org.BouncyCastle.Crypto.Modes +{ + /** + * A Cipher Text Stealing (CTS) mode cipher. CTS allows block ciphers to + * be used to produce cipher text which is the same outLength as the plain text. + */ + public class CtsBlockCipher + : BufferedBlockCipher + { + private readonly int blockSize; + + /** + * Create a buffered block cipher that uses Cipher Text Stealing + * + * @param cipher the underlying block cipher this buffering object wraps. + */ + public CtsBlockCipher( + IBlockCipher cipher) + { + // TODO Should this test for acceptable ones instead? + if (cipher is OfbBlockCipher || cipher is CfbBlockCipher) + throw new ArgumentException("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 of 'length' bytes. + * + * @param length the outLength of the input. + * @return the space required to accommodate a call to update + * with length bytes of input. + */ + public override int GetUpdateOutputSize( + int length) + { + int total = length + 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 length bytes. + * + * @param length the outLength of the input. + * @return the space required to accommodate a call to update and doFinal + * with length bytes of input. + */ + public override int GetOutputSize( + int length) + { + return length + bufOff; + } + + /** + * process a single byte, producing an output block if necessary. + * + * @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 InvalidOperationException if the cipher isn't initialised. + */ + public override int ProcessByte( + byte input, + byte[] output, + int outOff) + { + int resultLen = 0; + + if (bufOff == buf.Length) + { + resultLen = cipher.ProcessBlock(buf, 0, output, outOff); + Debug.Assert(resultLen == blockSize); + + Array.Copy(buf, blockSize, buf, 0, blockSize); + bufOff = blockSize; + } + + buf[bufOff++] = input; + + 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 length 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 InvalidOperationException if the cipher isn't initialised. + */ + public override int ProcessBytes( + byte[] input, + int inOff, + int length, + byte[] output, + int outOff) + { + if (length < 0) + { + throw new ArgumentException("Can't have a negative input outLength!"); + } + + int blockSize = GetBlockSize(); + int outLength = GetUpdateOutputSize(length); + + if (outLength > 0) + { + if ((outOff + outLength) > output.Length) + { + throw new DataLengthException("output buffer too short"); + } + } + + int resultLen = 0; + int gapLen = buf.Length - bufOff; + + if (length > gapLen) + { + Array.Copy(input, inOff, buf, bufOff, gapLen); + + resultLen += cipher.ProcessBlock(buf, 0, output, outOff); + Array.Copy(buf, blockSize, buf, 0, blockSize); + + bufOff = blockSize; + + length -= gapLen; + inOff += gapLen; + + while (length > blockSize) + { + Array.Copy(input, inOff, buf, bufOff, blockSize); + resultLen += cipher.ProcessBlock(buf, 0, output, outOff + resultLen); + Array.Copy(buf, blockSize, buf, 0, blockSize); + + length -= blockSize; + inOff += blockSize; + } + } + + Array.Copy(input, inOff, buf, bufOff, length); + + bufOff += length; + + 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 InvalidOperationException if the underlying cipher is not + * initialised. + * @exception InvalidCipherTextException if cipher text decrypts wrongly (in + * case the exception will never Get thrown). + */ + public override int DoFinal( + byte[] output, + int outOff) + { + if (bufOff + outOff > output.Length) + { + throw new DataLengthException("output buffer too small in doFinal"); + } + + int blockSize = cipher.GetBlockSize(); + int length = 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]; + } + + IBlockCipher c = (cipher is CbcBlockCipher) + ? ((CbcBlockCipher)cipher).GetUnderlyingCipher() + : cipher; + + c.ProcessBlock(buf, blockSize, output, outOff); + + Array.Copy(block, 0, output, outOff + blockSize, length); + } + else + { + byte[] lastBlock = new byte[blockSize]; + + IBlockCipher c = (cipher is CbcBlockCipher) + ? ((CbcBlockCipher)cipher).GetUnderlyingCipher() + : cipher; + + c.ProcessBlock(buf, 0, block, 0); + + for (int i = blockSize; i != bufOff; i++) + { + lastBlock[i - blockSize] = (byte)(block[i - blockSize] ^ buf[i]); + } + + Array.Copy(buf, blockSize, block, 0, length); + + cipher.ProcessBlock(block, 0, output, outOff); + Array.Copy(lastBlock, 0, output, outOff + blockSize, length); + } + + int offset = bufOff; + + Reset(); + + return offset; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/modes/EAXBlockCipher.cs b/bc-sharp-crypto/src/crypto/modes/EAXBlockCipher.cs new file mode 100644 index 0000000..624f385 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/modes/EAXBlockCipher.cs @@ -0,0 +1,379 @@ +using System; + +using Org.BouncyCastle.Crypto.Macs; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Modes +{ + /** + * 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 + : IAeadBlockCipher + { + private enum Tag : byte { N, H, C }; + + private SicBlockCipher cipher; + + private bool forEncryption; + + private int blockSize; + + private IMac mac; + + private byte[] nonceMac; + private byte[] associatedTextMac; + private byte[] macBlock; + + private int macSize; + private byte[] bufBlock; + private int bufOff; + + private bool cipherInitialized; + private byte[] initialAssociatedText; + + /** + * Constructor that accepts an instance of a block cipher engine. + * + * @param cipher the engine to use + */ + public EaxBlockCipher( + IBlockCipher cipher) + { + blockSize = cipher.GetBlockSize(); + mac = new CMac(cipher); + macBlock = new byte[blockSize]; + associatedTextMac = new byte[mac.GetMacSize()]; + nonceMac = new byte[mac.GetMacSize()]; + this.cipher = new SicBlockCipher(cipher); + } + + public virtual string AlgorithmName + { + get { return cipher.GetUnderlyingCipher().AlgorithmName + "/EAX"; } + } + + public virtual IBlockCipher GetUnderlyingCipher() + { + return cipher; + } + + public virtual int GetBlockSize() + { + return cipher.GetBlockSize(); + } + + public virtual void Init( + bool forEncryption, + ICipherParameters parameters) + { + this.forEncryption = forEncryption; + + byte[] nonce; + ICipherParameters keyParam; + + if (parameters is AeadParameters) + { + AeadParameters param = (AeadParameters) parameters; + + nonce = param.GetNonce(); + initialAssociatedText = param.GetAssociatedText(); + macSize = param.MacSize / 8; + keyParam = param.Key; + } + else if (parameters is ParametersWithIV) + { + ParametersWithIV param = (ParametersWithIV) parameters; + + nonce = param.GetIV(); + initialAssociatedText = null; + macSize = mac.GetMacSize() / 2; + keyParam = param.Parameters; + } + else + { + throw new ArgumentException("invalid parameters passed to EAX"); + } + + bufBlock = new byte[forEncryption ? blockSize : (blockSize + macSize)]; + + byte[] tag = new byte[blockSize]; + + // Key reuse implemented in CBC mode of underlying CMac + mac.Init(keyParam); + + tag[blockSize - 1] = (byte)Tag.N; + mac.BlockUpdate(tag, 0, blockSize); + mac.BlockUpdate(nonce, 0, nonce.Length); + mac.DoFinal(nonceMac, 0); + + // Same BlockCipher underlies this and the mac, so reuse last key on cipher + cipher.Init(true, new ParametersWithIV(null, nonceMac)); + + Reset(); + } + + private void InitCipher() + { + if (cipherInitialized) + { + return; + } + + cipherInitialized = true; + + mac.DoFinal(associatedTextMac, 0); + + byte[] tag = new byte[blockSize]; + tag[blockSize - 1] = (byte)Tag.C; + mac.BlockUpdate(tag, 0, blockSize); + } + + 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 virtual void Reset() + { + Reset(true); + } + + private void Reset( + bool clearMac) + { + cipher.Reset(); // TODO Redundant since the mac will reset it? + mac.Reset(); + + bufOff = 0; + Array.Clear(bufBlock, 0, bufBlock.Length); + + if (clearMac) + { + Array.Clear(macBlock, 0, macBlock.Length); + } + + byte[] tag = new byte[blockSize]; + tag[blockSize - 1] = (byte)Tag.H; + mac.BlockUpdate(tag, 0, blockSize); + + cipherInitialized = false; + + if (initialAssociatedText != null) + { + ProcessAadBytes(initialAssociatedText, 0, initialAssociatedText.Length); + } + } + + public virtual void ProcessAadByte(byte input) + { + if (cipherInitialized) + { + throw new InvalidOperationException("AAD data cannot be added after encryption/decryption processing has begun."); + } + mac.Update(input); + } + + public virtual void ProcessAadBytes(byte[] inBytes, int inOff, int len) + { + if (cipherInitialized) + { + throw new InvalidOperationException("AAD data cannot be added after encryption/decryption processing has begun."); + } + mac.BlockUpdate(inBytes, inOff, len); + } + + public virtual int ProcessByte( + byte input, + byte[] outBytes, + int outOff) + { + InitCipher(); + + return Process(input, outBytes, outOff); + } + + public virtual int ProcessBytes( + byte[] inBytes, + int inOff, + int len, + byte[] outBytes, + int outOff) + { + InitCipher(); + + int resultLen = 0; + + for (int i = 0; i != len; i++) + { + resultLen += Process(inBytes[inOff + i], outBytes, outOff + resultLen); + } + + return resultLen; + } + + public virtual int DoFinal( + byte[] outBytes, + int outOff) + { + InitCipher(); + + int extra = bufOff; + byte[] tmp = new byte[bufBlock.Length]; + + bufOff = 0; + + if (forEncryption) + { + Check.OutputLength(outBytes, outOff, extra + macSize, "Output buffer too short"); + + cipher.ProcessBlock(bufBlock, 0, tmp, 0); + + Array.Copy(tmp, 0, outBytes, outOff, extra); + + mac.BlockUpdate(tmp, 0, extra); + + CalculateMac(); + + Array.Copy(macBlock, 0, outBytes, outOff + extra, macSize); + + Reset(false); + + return extra + macSize; + } + else + { + if (extra < macSize) + throw new InvalidCipherTextException("data too short"); + + Check.OutputLength(outBytes, outOff, extra - macSize, "Output buffer too short"); + + if (extra > macSize) + { + mac.BlockUpdate(bufBlock, 0, extra - macSize); + + cipher.ProcessBlock(bufBlock, 0, tmp, 0); + + Array.Copy(tmp, 0, outBytes, outOff, extra - macSize); + } + + CalculateMac(); + + if (!VerifyMac(bufBlock, extra - macSize)) + throw new InvalidCipherTextException("mac check in EAX failed"); + + Reset(false); + + return extra - macSize; + } + } + + public virtual byte[] GetMac() + { + byte[] mac = new byte[macSize]; + + Array.Copy(macBlock, 0, mac, 0, macSize); + + return mac; + } + + public virtual int GetUpdateOutputSize( + int len) + { + int totalData = len + bufOff; + if (!forEncryption) + { + if (totalData < macSize) + { + return 0; + } + totalData -= macSize; + } + return totalData - totalData % blockSize; + } + + public virtual int GetOutputSize( + int len) + { + int totalData = len + bufOff; + + if (forEncryption) + { + return totalData + macSize; + } + + return totalData < macSize ? 0 : totalData - macSize; + } + + private int Process( + byte b, + byte[] outBytes, + int outOff) + { + bufBlock[bufOff++] = b; + + if (bufOff == bufBlock.Length) + { + Check.OutputLength(outBytes, outOff, blockSize, "Output buffer is too short"); + + // TODO Could move the ProcessByte(s) calls to here +// InitCipher(); + + int size; + + if (forEncryption) + { + size = cipher.ProcessBlock(bufBlock, 0, outBytes, outOff); + + mac.BlockUpdate(outBytes, outOff, blockSize); + } + else + { + mac.BlockUpdate(bufBlock, 0, blockSize); + + size = cipher.ProcessBlock(bufBlock, 0, outBytes, outOff); + } + + bufOff = 0; + if (!forEncryption) + { + Array.Copy(bufBlock, blockSize, bufBlock, 0, macSize); + bufOff = macSize; + } + + return size; + } + + return 0; + } + + private bool VerifyMac(byte[] mac, int off) + { + int nonEqual = 0; + + for (int i = 0; i < macSize; i++) + { + nonEqual |= (macBlock[i] ^ mac[off + i]); + } + + return nonEqual == 0; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/modes/GCMBlockCipher.cs b/bc-sharp-crypto/src/crypto/modes/GCMBlockCipher.cs new file mode 100644 index 0000000..a6cd004 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/modes/GCMBlockCipher.cs @@ -0,0 +1,594 @@ +using System; + +using Org.BouncyCastle.Crypto.Macs; +using Org.BouncyCastle.Crypto.Modes.Gcm; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Utilities; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Modes +{ + /// + /// Implements the Galois/Counter mode (GCM) detailed in + /// NIST Special Publication 800-38D. + /// + public class GcmBlockCipher + : IAeadBlockCipher + { + private const int BlockSize = 16; + + private readonly IBlockCipher cipher; + private readonly IGcmMultiplier multiplier; + private IGcmExponentiator exp; + + // These fields are set by Init and not modified by processing + private bool forEncryption; + private bool initialised; + private int macSize; + private byte[] lastKey; + private byte[] nonce; + private byte[] initialAssociatedText; + private byte[] H; + private byte[] J0; + + // These fields are modified during processing + private byte[] bufBlock; + private byte[] macBlock; + private byte[] S, S_at, S_atPre; + private byte[] counter; + private uint blocksRemaining; + private int bufOff; + private ulong totalLength; + private byte[] atBlock; + private int atBlockPos; + private ulong atLength; + private ulong atLengthPre; + + public GcmBlockCipher( + IBlockCipher c) + : this(c, null) + { + } + + public GcmBlockCipher( + IBlockCipher c, + IGcmMultiplier m) + { + if (c.GetBlockSize() != BlockSize) + throw new ArgumentException("cipher required with a block size of " + BlockSize + "."); + + if (m == null) + { + // TODO Consider a static property specifying default multiplier + m = new Tables8kGcmMultiplier(); + } + + this.cipher = c; + this.multiplier = m; + } + + public virtual string AlgorithmName + { + get { return cipher.AlgorithmName + "/GCM"; } + } + + public IBlockCipher GetUnderlyingCipher() + { + return cipher; + } + + public virtual int GetBlockSize() + { + return BlockSize; + } + + /// + /// MAC sizes from 32 bits to 128 bits (must be a multiple of 8) are supported. The default is 128 bits. + /// Sizes less than 96 are not recommended, but are supported for specialized applications. + /// + public virtual void Init( + bool forEncryption, + ICipherParameters parameters) + { + this.forEncryption = forEncryption; + this.macBlock = null; + this.initialised = true; + + KeyParameter keyParam; + byte[] newNonce = null; + + if (parameters is AeadParameters) + { + AeadParameters param = (AeadParameters)parameters; + + newNonce = param.GetNonce(); + initialAssociatedText = param.GetAssociatedText(); + + int macSizeBits = param.MacSize; + if (macSizeBits < 32 || macSizeBits > 128 || macSizeBits % 8 != 0) + { + throw new ArgumentException("Invalid value for MAC size: " + macSizeBits); + } + + macSize = macSizeBits / 8; + keyParam = param.Key; + } + else if (parameters is ParametersWithIV) + { + ParametersWithIV param = (ParametersWithIV)parameters; + + newNonce = param.GetIV(); + initialAssociatedText = null; + macSize = 16; + keyParam = (KeyParameter)param.Parameters; + } + else + { + throw new ArgumentException("invalid parameters passed to GCM"); + } + + int bufLength = forEncryption ? BlockSize : (BlockSize + macSize); + this.bufBlock = new byte[bufLength]; + + if (newNonce == null || newNonce.Length < 1) + { + throw new ArgumentException("IV must be at least 1 byte"); + } + + if (forEncryption) + { + if (nonce != null && Arrays.AreEqual(nonce, newNonce)) + { + if (keyParam == null) + { + throw new ArgumentException("cannot reuse nonce for GCM encryption"); + } + if (lastKey != null && Arrays.AreEqual(lastKey, keyParam.GetKey())) + { + throw new ArgumentException("cannot reuse nonce for GCM encryption"); + } + } + } + + nonce = newNonce; + if (keyParam != null) + { + lastKey = keyParam.GetKey(); + } + + // TODO Restrict macSize to 16 if nonce length not 12? + + // Cipher always used in forward mode + // if keyParam is null we're reusing the last key. + if (keyParam != null) + { + cipher.Init(true, keyParam); + + this.H = new byte[BlockSize]; + cipher.ProcessBlock(H, 0, H, 0); + + // if keyParam is null we're reusing the last key and the multiplier doesn't need re-init + multiplier.Init(H); + exp = null; + } + else if (this.H == null) + { + throw new ArgumentException("Key must be specified in initial init"); + } + + this.J0 = new byte[BlockSize]; + + if (nonce.Length == 12) + { + Array.Copy(nonce, 0, J0, 0, nonce.Length); + this.J0[BlockSize - 1] = 0x01; + } + else + { + gHASH(J0, nonce, nonce.Length); + byte[] X = new byte[BlockSize]; + Pack.UInt64_To_BE((ulong)nonce.Length * 8UL, X, 8); + gHASHBlock(J0, X); + } + + this.S = new byte[BlockSize]; + this.S_at = new byte[BlockSize]; + this.S_atPre = new byte[BlockSize]; + this.atBlock = new byte[BlockSize]; + this.atBlockPos = 0; + this.atLength = 0; + this.atLengthPre = 0; + this.counter = Arrays.Clone(J0); + this.blocksRemaining = uint.MaxValue - 1; // page 8, len(P) <= 2^39 - 256, 1 block used by tag + this.bufOff = 0; + this.totalLength = 0; + + if (initialAssociatedText != null) + { + ProcessAadBytes(initialAssociatedText, 0, initialAssociatedText.Length); + } + } + + public virtual byte[] GetMac() + { + return macBlock == null + ? new byte[macSize] + : Arrays.Clone(macBlock); + } + + public virtual int GetOutputSize( + int len) + { + int totalData = len + bufOff; + + if (forEncryption) + { + return totalData + macSize; + } + + return totalData < macSize ? 0 : totalData - macSize; + } + + public virtual int GetUpdateOutputSize( + int len) + { + int totalData = len + bufOff; + if (!forEncryption) + { + if (totalData < macSize) + { + return 0; + } + totalData -= macSize; + } + return totalData - totalData % BlockSize; + } + + public virtual void ProcessAadByte(byte input) + { + CheckStatus(); + + atBlock[atBlockPos] = input; + if (++atBlockPos == BlockSize) + { + // Hash each block as it fills + gHASHBlock(S_at, atBlock); + atBlockPos = 0; + atLength += BlockSize; + } + } + + public virtual void ProcessAadBytes(byte[] inBytes, int inOff, int len) + { + CheckStatus(); + + for (int i = 0; i < len; ++i) + { + atBlock[atBlockPos] = inBytes[inOff + i]; + if (++atBlockPos == BlockSize) + { + // Hash each block as it fills + gHASHBlock(S_at, atBlock); + atBlockPos = 0; + atLength += BlockSize; + } + } + } + + private void InitCipher() + { + if (atLength > 0) + { + Array.Copy(S_at, 0, S_atPre, 0, BlockSize); + atLengthPre = atLength; + } + + // Finish hash for partial AAD block + if (atBlockPos > 0) + { + gHASHPartial(S_atPre, atBlock, 0, atBlockPos); + atLengthPre += (uint)atBlockPos; + } + + if (atLengthPre > 0) + { + Array.Copy(S_atPre, 0, S, 0, BlockSize); + } + } + + public virtual int ProcessByte( + byte input, + byte[] output, + int outOff) + { + CheckStatus(); + + bufBlock[bufOff] = input; + if (++bufOff == bufBlock.Length) + { + OutputBlock(output, outOff); + return BlockSize; + } + return 0; + } + + public virtual int ProcessBytes( + byte[] input, + int inOff, + int len, + byte[] output, + int outOff) + { + CheckStatus(); + + if (input.Length < (inOff + len)) + throw new DataLengthException("Input buffer too short"); + + int resultLen = 0; + + for (int i = 0; i < len; ++i) + { + bufBlock[bufOff] = input[inOff + i]; + if (++bufOff == bufBlock.Length) + { + OutputBlock(output, outOff + resultLen); + resultLen += BlockSize; + } + } + + return resultLen; + } + + private void OutputBlock(byte[] output, int offset) + { + Check.OutputLength(output, offset, BlockSize, "Output buffer too short"); + if (totalLength == 0) + { + InitCipher(); + } + gCTRBlock(bufBlock, output, offset); + if (forEncryption) + { + bufOff = 0; + } + else + { + Array.Copy(bufBlock, BlockSize, bufBlock, 0, macSize); + bufOff = macSize; + } + } + + public int DoFinal(byte[] output, int outOff) + { + CheckStatus(); + + if (totalLength == 0) + { + InitCipher(); + } + + int extra = bufOff; + + if (forEncryption) + { + Check.OutputLength(output, outOff, extra + macSize, "Output buffer too short"); + } + else + { + if (extra < macSize) + throw new InvalidCipherTextException("data too short"); + + extra -= macSize; + + Check.OutputLength(output, outOff, extra, "Output buffer too short"); + } + + if (extra > 0) + { + gCTRPartial(bufBlock, 0, extra, output, outOff); + } + + atLength += (uint)atBlockPos; + + if (atLength > atLengthPre) + { + /* + * Some AAD was sent after the cipher started. We determine the difference b/w the hash value + * we actually used when the cipher started (S_atPre) and the final hash value calculated (S_at). + * Then we carry this difference forward by multiplying by H^c, where c is the number of (full or + * partial) cipher-text blocks produced, and adjust the current hash. + */ + + // Finish hash for partial AAD block + if (atBlockPos > 0) + { + gHASHPartial(S_at, atBlock, 0, atBlockPos); + } + + // Find the difference between the AAD hashes + if (atLengthPre > 0) + { + GcmUtilities.Xor(S_at, S_atPre); + } + + // Number of cipher-text blocks produced + long c = (long)(((totalLength * 8) + 127) >> 7); + + // Calculate the adjustment factor + byte[] H_c = new byte[16]; + if (exp == null) + { + exp = new Tables1kGcmExponentiator(); + exp.Init(H); + } + exp.ExponentiateX(c, H_c); + + // Carry the difference forward + GcmUtilities.Multiply(S_at, H_c); + + // Adjust the current hash + GcmUtilities.Xor(S, S_at); + } + + // Final gHASH + byte[] X = new byte[BlockSize]; + Pack.UInt64_To_BE(atLength * 8UL, X, 0); + Pack.UInt64_To_BE(totalLength * 8UL, X, 8); + + gHASHBlock(S, X); + + // T = MSBt(GCTRk(J0,S)) + byte[] tag = new byte[BlockSize]; + cipher.ProcessBlock(J0, 0, tag, 0); + GcmUtilities.Xor(tag, S); + + int resultLen = extra; + + // We place into macBlock our calculated value for T + this.macBlock = new byte[macSize]; + Array.Copy(tag, 0, macBlock, 0, macSize); + + if (forEncryption) + { + // Append T to the message + Array.Copy(macBlock, 0, output, outOff + bufOff, macSize); + resultLen += macSize; + } + else + { + // Retrieve the T value from the message and compare to calculated one + byte[] msgMac = new byte[macSize]; + Array.Copy(bufBlock, extra, msgMac, 0, macSize); + if (!Arrays.ConstantTimeAreEqual(this.macBlock, msgMac)) + throw new InvalidCipherTextException("mac check in GCM failed"); + } + + Reset(false); + + return resultLen; + } + + public virtual void Reset() + { + Reset(true); + } + + private void Reset( + bool clearMac) + { + cipher.Reset(); + + // note: we do not reset the nonce. + + S = new byte[BlockSize]; + S_at = new byte[BlockSize]; + S_atPre = new byte[BlockSize]; + atBlock = new byte[BlockSize]; + atBlockPos = 0; + atLength = 0; + atLengthPre = 0; + counter = Arrays.Clone(J0); + blocksRemaining = uint.MaxValue - 1; + bufOff = 0; + totalLength = 0; + + if (bufBlock != null) + { + Arrays.Fill(bufBlock, 0); + } + + if (clearMac) + { + macBlock = null; + } + + if (forEncryption) + { + initialised = false; + } + else + { + if (initialAssociatedText != null) + { + ProcessAadBytes(initialAssociatedText, 0, initialAssociatedText.Length); + } + } + } + + private void gCTRBlock(byte[] block, byte[] output, int outOff) + { + byte[] tmp = GetNextCounterBlock(); + + GcmUtilities.Xor(tmp, block); + Array.Copy(tmp, 0, output, outOff, BlockSize); + + gHASHBlock(S, forEncryption ? tmp : block); + + totalLength += BlockSize; + } + + private void gCTRPartial(byte[] buf, int off, int len, byte[] output, int outOff) + { + byte[] tmp = GetNextCounterBlock(); + + GcmUtilities.Xor(tmp, buf, off, len); + Array.Copy(tmp, 0, output, outOff, len); + + gHASHPartial(S, forEncryption ? tmp : buf, 0, len); + + totalLength += (uint)len; + } + + private void gHASH(byte[] Y, byte[] b, int len) + { + for (int pos = 0; pos < len; pos += BlockSize) + { + int num = System.Math.Min(len - pos, BlockSize); + gHASHPartial(Y, b, pos, num); + } + } + + private void gHASHBlock(byte[] Y, byte[] b) + { + GcmUtilities.Xor(Y, b); + multiplier.MultiplyH(Y); + } + + private void gHASHPartial(byte[] Y, byte[] b, int off, int len) + { + GcmUtilities.Xor(Y, b, off, len); + multiplier.MultiplyH(Y); + } + + private byte[] GetNextCounterBlock() + { + if (blocksRemaining == 0) + throw new InvalidOperationException("Attempt to process too many blocks"); + + blocksRemaining--; + + uint c = 1; + c += counter[15]; counter[15] = (byte)c; c >>= 8; + c += counter[14]; counter[14] = (byte)c; c >>= 8; + c += counter[13]; counter[13] = (byte)c; c >>= 8; + c += counter[12]; counter[12] = (byte)c; + + byte[] tmp = new byte[BlockSize]; + // TODO Sure would be nice if ciphers could operate on int[] + cipher.ProcessBlock(counter, 0, tmp, 0); + return tmp; + } + + private void CheckStatus() + { + if (!initialised) + { + if (forEncryption) + { + throw new InvalidOperationException("GCM cipher cannot be reused for encryption"); + } + throw new InvalidOperationException("GCM cipher needs to be initialised"); + } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/modes/GOFBBlockCipher.cs b/bc-sharp-crypto/src/crypto/modes/GOFBBlockCipher.cs new file mode 100644 index 0000000..436b58a --- /dev/null +++ b/bc-sharp-crypto/src/crypto/modes/GOFBBlockCipher.cs @@ -0,0 +1,234 @@ +using System; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; + +namespace Org.BouncyCastle.Crypto.Modes +{ + /** + * implements the GOST 28147 OFB counter mode (GCTR). + */ + public class GOfbBlockCipher + : IBlockCipher + { + private byte[] IV; + private byte[] ofbV; + private byte[] ofbOutV; + + private readonly int blockSize; + private readonly IBlockCipher cipher; + + bool firstStep = true; + int N3; + int N4; + const int C1 = 16843012; //00000001000000010000000100000100 + const 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( + IBlockCipher cipher) + { + this.cipher = cipher; + this.blockSize = cipher.GetBlockSize(); + + if (blockSize != 8) + { + throw new ArgumentException("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 IBlockCipher 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 parameters the key and other data required by the cipher. + * @exception ArgumentException if the parameters argument is inappropriate. + */ + public void Init( + bool forEncryption, //ignored by this CTR mode + ICipherParameters parameters) + { + firstStep = true; + N3 = 0; + N4 = 0; + + if (parameters is ParametersWithIV) + { + ParametersWithIV ivParam = (ParametersWithIV)parameters; + byte[] iv = ivParam.GetIV(); + + if (iv.Length < IV.Length) + { + // prepend the supplied IV with zeros (per FIPS PUB 81) + Array.Copy(iv, 0, IV, IV.Length - iv.Length, iv.Length); + for (int i = 0; i < IV.Length - iv.Length; i++) + { + IV[i] = 0; + } + } + else + { + Array.Copy(iv, 0, IV, 0, IV.Length); + } + + parameters = ivParam.Parameters; + } + + Reset(); + + // if it's null, key is to be reused. + if (parameters != null) + { + cipher.Init(true, parameters); + } + } + + /** + * return the algorithm name and mode. + * + * @return the name of the underlying algorithm followed by "/GCTR" + * and the block size in bits + */ + public string AlgorithmName + { + get { return cipher.AlgorithmName + "/GCTR"; } + } + + public bool IsPartialBlockOkay + { + get { return true; } + } + + /** + * 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 InvalidOperationException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + public int ProcessBlock( + byte[] input, + int inOff, + byte[] output, + int outOff) + { + if ((inOff + blockSize) > input.Length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + blockSize) > output.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; + if (N4 < C1) // addition is mod (2**32 - 1) + { + if (N4 > 0) + { + N4++; + } + } + 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++) + { + output[outOff + i] = (byte)(ofbOutV[i] ^ input[inOff + i]); + } + + // + // change over the input block. + // + Array.Copy(ofbV, blockSize, ofbV, 0, ofbV.Length - blockSize); + Array.Copy(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() + { + Array.Copy(IV, 0, ofbV, 0, IV.Length); + + cipher.Reset(); + } + + //array of bytes to type int + private int bytesToint( + byte[] inBytes, + int inOff) + { + return (int)((inBytes[inOff + 3] << 24) & 0xff000000) + ((inBytes[inOff + 2] << 16) & 0xff0000) + + ((inBytes[inOff + 1] << 8) & 0xff00) + (inBytes[inOff] & 0xff); + } + + //int to array of bytes + private void intTobytes( + int num, + byte[] outBytes, + int outOff) + { + outBytes[outOff + 3] = (byte)(num >> 24); + outBytes[outOff + 2] = (byte)(num >> 16); + outBytes[outOff + 1] = (byte)(num >> 8); + outBytes[outOff] = (byte)num; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/modes/IAeadBlockCipher.cs b/bc-sharp-crypto/src/crypto/modes/IAeadBlockCipher.cs new file mode 100644 index 0000000..52c4ff4 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/modes/IAeadBlockCipher.cs @@ -0,0 +1,105 @@ +using Org.BouncyCastle.Crypto.Parameters; + +namespace Org.BouncyCastle.Crypto.Modes +{ + /// + /// A block cipher mode that includes authenticated encryption with a streaming mode + /// and optional associated data. + /// + public interface IAeadBlockCipher + { + /// The name of the algorithm this cipher implements. + string AlgorithmName { get; } + + /// The block cipher underlying this algorithm. + IBlockCipher GetUnderlyingCipher(); + + /// Initialise the cipher. + /// Parameter can either be an AeadParameters or a ParametersWithIV object. + /// Initialise for encryption if true, for decryption if false. + /// The key or other data required by the cipher. + void Init(bool forEncryption, ICipherParameters parameters); + + /// The block size for this cipher, in bytes. + int GetBlockSize(); + + /// Add a single byte to the associated data check. + /// If the implementation supports it, this will be an online operation and will not retain the associated data. + /// The byte to be processed. + void ProcessAadByte(byte input); + + /// Add a sequence of bytes to the associated data check. + /// If the implementation supports it, this will be an online operation and will not retain the associated data. + /// The input byte array. + /// The offset into the input array where the data to be processed starts. + /// The number of bytes to be processed. + void ProcessAadBytes(byte[] inBytes, int inOff, int len); + + /** + * Encrypt/decrypt a single byte. + * + * @param input the byte to be processed. + * @param outBytes 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. + */ + int ProcessByte(byte input, byte[] outBytes, int outOff); + + /** + * Process a block of bytes from in putting the result into out. + * + * @param inBytes 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 outBytes 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. + */ + int ProcessBytes(byte[] inBytes, int inOff, int len, byte[] outBytes, int outOff); + + /** + * Finish the operation either appending or verifying the MAC at the end of the data. + * + * @param outBytes 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 InvalidOperationException if the cipher is in an inappropriate state. + * @throws InvalidCipherTextException if the MAC fails to match. + */ + int DoFinal(byte[] outBytes, int outOff); + + /** + * Return the value of the MAC associated with the last stream processed. + * + * @return MAC for plaintext data. + */ + 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. + */ + 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. + */ + int GetOutputSize(int len); + + /// + /// Reset the cipher to the same state as it was after the last init (if there was one). + /// + void Reset(); + } +} diff --git a/bc-sharp-crypto/src/crypto/modes/KCcmBlockCipher.cs b/bc-sharp-crypto/src/crypto/modes/KCcmBlockCipher.cs new file mode 100644 index 0000000..4f78214 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/modes/KCcmBlockCipher.cs @@ -0,0 +1,490 @@ +using System; +using System.IO; +using System.Text; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Modes +{ + public class KCcmBlockCipher: IAeadBlockCipher + { + private static readonly int BYTES_IN_INT = 4; + private static readonly int BITS_IN_BYTE = 8; + + private static readonly int MAX_MAC_BIT_LENGTH = 512; + private static readonly int MIN_MAC_BIT_LENGTH = 64; + + private IBlockCipher engine; + + private int macSize; + private bool forEncryption; + + private byte[] initialAssociatedText; + private byte[] mac; + private byte[] macBlock; + + private byte[] nonce; + + private byte[] G1; + private byte[] buffer; + + private byte[] s; + private byte[] counter; + + private readonly MemoryStream associatedText = new MemoryStream(); + private readonly MemoryStream data = new MemoryStream(); + + /* + * + * + */ + private int Nb_ = 4; + + private void setNb(int Nb) + { + if (Nb == 4 || Nb == 6 || Nb == 8) + { + Nb_ = Nb; + } + else + { + throw new ArgumentException("Nb = 4 is recommended by DSTU7624 but can be changed to only 6 or 8 in this implementation"); + } + } + + /// + /// Base constructor. Nb value is set to 4. + /// + /// base cipher to use under CCM. + public KCcmBlockCipher(IBlockCipher engine): this(engine, 4) + { + } + + /// + /// Constructor allowing Nb configuration. + /// + /// Nb is a parameter specified in CCM mode of DSTU7624 standard. + /// This parameter specifies maximum possible length of input.It should + /// be calculated as follows: Nb = 1 / 8 * (-3 + log[2]Nmax) + 1, + /// where Nmax - length of input message in bits.For practical reasons + /// Nmax usually less than 4Gb, e.g. for Nmax = 2^32 - 1, Nb = 4. + /// + /// base cipher to use under CCM. + /// Nb value to use. + public KCcmBlockCipher(IBlockCipher engine, int Nb) + { + this.engine = engine; + this.macSize = engine.GetBlockSize(); + this.nonce = new byte[engine.GetBlockSize()]; + this.initialAssociatedText = new byte[engine.GetBlockSize()]; + this.mac = new byte[engine.GetBlockSize()]; + this.macBlock = new byte[engine.GetBlockSize()]; + this.G1 = new byte[engine.GetBlockSize()]; + this.buffer = new byte[engine.GetBlockSize()]; + this.s = new byte[engine.GetBlockSize()]; + this.counter = new byte[engine.GetBlockSize()]; + setNb(Nb); + } + + public virtual void Init(bool forEncryption, ICipherParameters parameters) + { + + ICipherParameters cipherParameters; + if (parameters is AeadParameters) + { + + AeadParameters param = (AeadParameters)parameters; + + if (param.MacSize > MAX_MAC_BIT_LENGTH || param.MacSize < MIN_MAC_BIT_LENGTH || param.MacSize % 8 != 0) + { + throw new ArgumentException("Invalid mac size specified"); + } + + nonce = param.GetNonce(); + macSize = param.MacSize / BITS_IN_BYTE; + initialAssociatedText = param.GetAssociatedText(); + cipherParameters = param.Key; + } + else if (parameters is ParametersWithIV) + { + nonce = ((ParametersWithIV)parameters).GetIV(); + macSize = engine.GetBlockSize(); // use default blockSize for MAC if it is not specified + initialAssociatedText = null; + cipherParameters = ((ParametersWithIV)parameters).Parameters; + } + else + { + throw new ArgumentException("Invalid parameters specified"); + } + + this.mac = new byte[macSize]; + this.forEncryption = forEncryption; + engine.Init(true, cipherParameters); + + counter[0] = 0x01; // defined in standard + + if (initialAssociatedText != null) + { + ProcessAadBytes(initialAssociatedText, 0, initialAssociatedText.Length); + } + } + + public virtual String AlgorithmName + { + get + { + return engine.AlgorithmName + "/KCCM"; + } + } + + public virtual int GetBlockSize() + { + return engine.GetBlockSize(); + } + + public virtual IBlockCipher GetUnderlyingCipher() + { + return engine; + } + + public virtual void ProcessAadByte(byte input) + { + associatedText.WriteByte(input); + } + + public virtual void ProcessAadBytes(byte[] input, int inOff, int len) + { + associatedText.Write(input, inOff, len); + } + + private void ProcessAAD(byte[] assocText, int assocOff, int assocLen, int dataLen) + { + if (assocLen - assocOff < engine.GetBlockSize()) + { + throw new ArgumentException("authText buffer too short"); + } + if (assocLen % engine.GetBlockSize() != 0) + { + throw new ArgumentException("padding not supported"); + } + + Array.Copy(nonce, 0, G1, 0, nonce.Length - Nb_ - 1); + + intToBytes(dataLen, buffer, 0); // for G1 + + Array.Copy(buffer, 0, G1, nonce.Length - Nb_ - 1, BYTES_IN_INT); + + G1[G1.Length - 1] = getFlag(true, macSize); + + engine.ProcessBlock(G1, 0, macBlock, 0); + + intToBytes(assocLen, buffer, 0); // for G2 + + if (assocLen <= engine.GetBlockSize() - Nb_) + { + for (int byteIndex = 0; byteIndex < assocLen; byteIndex++) + { + buffer[byteIndex + Nb_] ^= assocText[assocOff + byteIndex]; + } + + for (int byteIndex = 0; byteIndex < engine.GetBlockSize(); byteIndex++) + { + macBlock[byteIndex] ^= buffer[byteIndex]; + } + + engine.ProcessBlock(macBlock, 0, macBlock, 0); + + return; + } + + for (int byteIndex = 0; byteIndex < engine.GetBlockSize(); byteIndex++) + { + macBlock[byteIndex] ^= buffer[byteIndex]; + } + + engine.ProcessBlock(macBlock, 0, macBlock, 0); + + int authLen = assocLen; + while (authLen != 0) + { + for (int byteIndex = 0; byteIndex < engine.GetBlockSize(); byteIndex++) + { + macBlock[byteIndex] ^= assocText[byteIndex + assocOff]; + } + + engine.ProcessBlock(macBlock, 0, macBlock, 0); + + assocOff += engine.GetBlockSize(); + authLen -= engine.GetBlockSize(); + } + } + + public virtual int ProcessByte(byte input, byte[] output, int outOff) + { + data.WriteByte(input); + + return 0; + } + + public virtual int ProcessBytes(byte[] input, int inOff, int inLen, byte[] output, int outOff) + { + Check.DataLength(input, inOff, inLen, "input buffer too short"); + + data.Write(input, inOff, inLen); + + return 0; + } + + public int ProcessPacket(byte[] input, int inOff, int len, byte[] output, int outOff) + { + Check.DataLength(input, inOff, len, "input buffer too short"); + Check.OutputLength(output, outOff, len, "output buffer too short"); + + if (associatedText.Length > 0) + { +#if PORTABLE + byte[] aad = associatedText.ToArray(); + int aadLen = aad.Length; +#else + byte[] aad = associatedText.GetBuffer(); + int aadLen = (int)associatedText.Length; +#endif + + int dataLen = forEncryption ? (int)data.Length : ((int)data.Length - macSize); + + ProcessAAD(aad, 0, aadLen, dataLen); + } + + if (forEncryption) + { + Check.DataLength(len % engine.GetBlockSize() != 0, "partial blocks not supported"); + + CalculateMac(input, inOff, len); + engine.ProcessBlock(nonce, 0, s, 0); + + int totalLength = len; + while (totalLength > 0) + { + ProcessBlock(input, inOff, len, output, outOff); + totalLength -= engine.GetBlockSize(); + inOff += engine.GetBlockSize(); + outOff += engine.GetBlockSize(); + } + + for (int byteIndex = 0; byteIndex inOff) + { + for (int byteIndex = 0; byteIndex 0) + { + for (int byteIndex = 0; byteIndex < engine.GetBlockSize(); byteIndex++) + { + macBlock[byteIndex] ^= authText[authOff + byteIndex]; + } + + engine.ProcessBlock(macBlock, 0, macBlock, 0); + + totalLen -= engine.GetBlockSize(); + authOff += engine.GetBlockSize(); + } + } + + public virtual int DoFinal(byte[] output, int outOff) + { +#if PORTABLE + byte[] buf = data.ToArray(); + int bufLen = buf.Length; +#else + byte[] buf = data.GetBuffer(); + int bufLen = (int)data.Length; +#endif + + int len = ProcessPacket(buf, 0, bufLen, output, outOff); + + Reset(); + + return len; + } + + public virtual byte[] GetMac() + { + return Arrays.Clone(mac); + } + + public virtual int GetUpdateOutputSize(int len) + { + return len; + } + + public virtual int GetOutputSize(int len) + { + return len + macSize; + } + + public virtual void Reset() + { + Arrays.Fill(G1, (byte)0); + Arrays.Fill(buffer, (byte)0); + Arrays.Fill(counter, (byte)0); + Arrays.Fill(macBlock, (byte)0); + + counter[0] = 0x01; + data.SetLength(0); + associatedText.SetLength(0); + + if (initialAssociatedText != null) + { + ProcessAadBytes(initialAssociatedText, 0, initialAssociatedText.Length); + } + } + + private void intToBytes( + int num, + byte[] outBytes, + int outOff) + { + outBytes[outOff + 3] = (byte)(num >> 24); + outBytes[outOff + 2] = (byte)(num >> 16); + outBytes[outOff + 1] = (byte)(num >> 8); + outBytes[outOff] = (byte)num; + } + + private byte getFlag(bool authTextPresents, int macSize) + { + StringBuilder flagByte = new StringBuilder(); + + if (authTextPresents) + { + flagByte.Append("1"); + } + else + { + flagByte.Append("0"); + } + + + switch (macSize) + { + case 8: + flagByte.Append("010"); // binary 2 + break; + case 16: + flagByte.Append("011"); // binary 3 + break; + case 32: + flagByte.Append("100"); // binary 4 + break; + case 48: + flagByte.Append("101"); // binary 5 + break; + case 64: + flagByte.Append("110"); // binary 6 + break; + } + + String binaryNb = Convert.ToString(Nb_ - 1, 2); + while (binaryNb.Length < 4) + { + binaryNb = new StringBuilder(binaryNb).Insert(0, "0").ToString(); + } + + flagByte.Append(binaryNb); + + return (byte)Convert.ToInt32(flagByte.ToString(), 2); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/modes/KCtrBlockCipher.cs b/bc-sharp-crypto/src/crypto/modes/KCtrBlockCipher.cs new file mode 100644 index 0000000..ff0249a --- /dev/null +++ b/bc-sharp-crypto/src/crypto/modes/KCtrBlockCipher.cs @@ -0,0 +1,235 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; + +namespace Org.BouncyCastle.Crypto.Modes +{ + /** + * Implements a Gamming or Counter (CTR) mode on top of a DSTU 7624 block cipher. + */ + public class KCtrBlockCipher : IStreamCipher, IBlockCipher + { + private byte[] IV; + private byte[] ofbV; + private byte[] ofbOutV; + private bool initialised; + + private int byteCount; + + private readonly int blockSize; + private readonly IBlockCipher cipher; + + /** + * Basic constructor. + * + * @param cipher the block cipher to be used as the basis of the + * feedback mode. + */ + public KCtrBlockCipher(IBlockCipher cipher) + { + this.cipher = cipher; + this.IV = new byte[cipher.GetBlockSize()]; + this.blockSize = 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 IBlockCipher 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 forEncryption if true the cipher is initialised for + * encryption, if false for decryption. + * @param param the key and other data required by the cipher. + * @exception ArgumentException if the parameters argument is + * inappropriate. + */ + public void Init( + bool forEncryption, + ICipherParameters parameters) + { + this.initialised = true; + if (parameters is ParametersWithIV) + { + ParametersWithIV ivParam = (ParametersWithIV)parameters; + byte[] iv = ivParam.GetIV(); + int diff = IV.Length - iv.Length; + + Array.Clear(IV, 0, IV.Length); + Array.Copy(iv, 0, IV, diff, iv.Length); + + parameters = ivParam.Parameters; + } + else + { + throw new ArgumentException("Invalid parameter passed"); + } + + // if it's null, key is to be reused. + if (parameters != null) + { + cipher.Init(true, parameters); + } + + Reset(); + } + + /** + * return the algorithm name and mode. + * + * @return the name of the underlying algorithm followed by "/KCTR" + * and the block size in bits. + */ + public string AlgorithmName + { + get { return cipher.AlgorithmName + "/KCTR"; } + } + + public bool IsPartialBlockOkay + { + get { return true; } + } + + /** + * return the block size we are operating at. + * + * @return the block size we are operating at (in bytes). + */ + public int GetBlockSize() + { + return cipher.GetBlockSize(); + } + + public byte ReturnByte(byte input) + { + return CalculateByte(input); + } + + public void ProcessBytes(byte[] input, int inOff, int len, byte[] output, int outOff) + { + if (outOff + len > output.Length) + { + throw new DataLengthException("Output buffer too short"); + } + + if (inOff + len > input.Length) + { + throw new DataLengthException("Input buffer too small"); + } + + int inStart = inOff; + int inEnd = inOff + len; + int outStart = outOff; + + while (inStartRFC 7253 on The OCB + * Authenticated-Encryption Algorithm, licensed per: + * + *

License for + * Open-Source Software Implementations of OCB (Jan 9, 2013) - 'License 1'
+ * Under this license, you are authorized to make, use, and distribute open-source software + * implementations of OCB. This license terminates for you if you sue someone over their open-source + * software implementation of OCB claiming that you have a patent covering their implementation. + *

+ * This is a non-binding summary of a legal document (the link above). The parameters of the license + * are specified in the license document and that document is controlling.

+ */ + public class OcbBlockCipher + : IAeadBlockCipher + { + private const int BLOCK_SIZE = 16; + + private readonly IBlockCipher hashCipher; + private readonly IBlockCipher mainCipher; + + /* + * CONFIGURATION + */ + private bool forEncryption; + private int macSize; + private byte[] initialAssociatedText; + + /* + * KEY-DEPENDENT + */ + // NOTE: elements are lazily calculated + private IList L; + private byte[] L_Asterisk, L_Dollar; + + /* + * NONCE-DEPENDENT + */ + private byte[] KtopInput = null; + private byte[] Stretch = new byte[24]; + private byte[] OffsetMAIN_0 = new byte[16]; + + /* + * PER-ENCRYPTION/DECRYPTION + */ + private byte[] hashBlock, mainBlock; + private int hashBlockPos, mainBlockPos; + private long hashBlockCount, mainBlockCount; + private byte[] OffsetHASH; + private byte[] Sum; + private byte[] OffsetMAIN = new byte[16]; + private byte[] Checksum; + + // NOTE: The MAC value is preserved after doFinal + private byte[] macBlock; + + public OcbBlockCipher(IBlockCipher hashCipher, IBlockCipher mainCipher) + { + if (hashCipher == null) + throw new ArgumentNullException("hashCipher"); + if (hashCipher.GetBlockSize() != BLOCK_SIZE) + throw new ArgumentException("must have a block size of " + BLOCK_SIZE, "hashCipher"); + if (mainCipher == null) + throw new ArgumentNullException("mainCipher"); + if (mainCipher.GetBlockSize() != BLOCK_SIZE) + throw new ArgumentException("must have a block size of " + BLOCK_SIZE, "mainCipher"); + + if (!hashCipher.AlgorithmName.Equals(mainCipher.AlgorithmName)) + throw new ArgumentException("'hashCipher' and 'mainCipher' must be the same algorithm"); + + this.hashCipher = hashCipher; + this.mainCipher = mainCipher; + } + + public virtual IBlockCipher GetUnderlyingCipher() + { + return mainCipher; + } + + public virtual string AlgorithmName + { + get { return mainCipher.AlgorithmName + "/OCB"; } + } + + public virtual void Init(bool forEncryption, ICipherParameters parameters) + { + bool oldForEncryption = this.forEncryption; + this.forEncryption = forEncryption; + this.macBlock = null; + + KeyParameter keyParameter; + + byte[] N; + if (parameters is AeadParameters) + { + AeadParameters aeadParameters = (AeadParameters) parameters; + + N = aeadParameters.GetNonce(); + initialAssociatedText = aeadParameters.GetAssociatedText(); + + int macSizeBits = aeadParameters.MacSize; + if (macSizeBits < 64 || macSizeBits > 128 || macSizeBits % 8 != 0) + throw new ArgumentException("Invalid value for MAC size: " + macSizeBits); + + macSize = macSizeBits / 8; + keyParameter = aeadParameters.Key; + } + else if (parameters is ParametersWithIV) + { + ParametersWithIV parametersWithIV = (ParametersWithIV) parameters; + + N = parametersWithIV.GetIV(); + initialAssociatedText = null; + macSize = 16; + keyParameter = (KeyParameter) parametersWithIV.Parameters; + } + else + { + throw new ArgumentException("invalid parameters passed to OCB"); + } + + this.hashBlock = new byte[16]; + this.mainBlock = new byte[forEncryption ? BLOCK_SIZE : (BLOCK_SIZE + macSize)]; + + if (N == null) + { + N = new byte[0]; + } + + if (N.Length > 15) + { + throw new ArgumentException("IV must be no more than 15 bytes"); + } + + /* + * KEY-DEPENDENT INITIALISATION + */ + + if (keyParameter != null) + { + // hashCipher always used in forward mode + hashCipher.Init(true, keyParameter); + mainCipher.Init(forEncryption, keyParameter); + KtopInput = null; + } + else if (oldForEncryption != forEncryption) + { + throw new ArgumentException("cannot change encrypting state without providing key."); + } + + this.L_Asterisk = new byte[16]; + hashCipher.ProcessBlock(L_Asterisk, 0, L_Asterisk, 0); + + this.L_Dollar = OCB_double(L_Asterisk); + + this.L = Platform.CreateArrayList(); + this.L.Add(OCB_double(L_Dollar)); + + /* + * NONCE-DEPENDENT AND PER-ENCRYPTION/DECRYPTION INITIALISATION + */ + + int bottom = ProcessNonce(N); + + int bits = bottom % 8, bytes = bottom / 8; + if (bits == 0) + { + Array.Copy(Stretch, bytes, OffsetMAIN_0, 0, 16); + } + else + { + for (int i = 0; i < 16; ++i) + { + uint b1 = Stretch[bytes]; + uint b2 = Stretch[++bytes]; + this.OffsetMAIN_0[i] = (byte) ((b1 << bits) | (b2 >> (8 - bits))); + } + } + + this.hashBlockPos = 0; + this.mainBlockPos = 0; + + this.hashBlockCount = 0; + this.mainBlockCount = 0; + + this.OffsetHASH = new byte[16]; + this.Sum = new byte[16]; + Array.Copy(OffsetMAIN_0, 0, OffsetMAIN, 0, 16); + this.Checksum = new byte[16]; + + if (initialAssociatedText != null) + { + ProcessAadBytes(initialAssociatedText, 0, initialAssociatedText.Length); + } + } + + protected virtual int ProcessNonce(byte[] N) + { + byte[] nonce = new byte[16]; + Array.Copy(N, 0, nonce, nonce.Length - N.Length, N.Length); + nonce[0] = (byte)(macSize << 4); + nonce[15 - N.Length] |= 1; + + int bottom = nonce[15] & 0x3F; + nonce[15] &= 0xC0; + + /* + * When used with incrementing nonces, the cipher is only applied once every 64 inits. + */ + if (KtopInput == null || !Arrays.AreEqual(nonce, KtopInput)) + { + byte[] Ktop = new byte[16]; + KtopInput = nonce; + hashCipher.ProcessBlock(KtopInput, 0, Ktop, 0); + Array.Copy(Ktop, 0, Stretch, 0, 16); + for (int i = 0; i < 8; ++i) + { + Stretch[16 + i] = (byte)(Ktop[i] ^ Ktop[i + 1]); + } + } + + return bottom; + } + + public virtual int GetBlockSize() + { + return BLOCK_SIZE; + } + + public virtual byte[] GetMac() + { + return macBlock == null + ? new byte[macSize] + : Arrays.Clone(macBlock); + } + + public virtual int GetOutputSize(int len) + { + int totalData = len + mainBlockPos; + if (forEncryption) + { + return totalData + macSize; + } + return totalData < macSize ? 0 : totalData - macSize; + } + + public virtual int GetUpdateOutputSize(int len) + { + int totalData = len + mainBlockPos; + if (!forEncryption) + { + if (totalData < macSize) + { + return 0; + } + totalData -= macSize; + } + return totalData - totalData % BLOCK_SIZE; + } + + public virtual void ProcessAadByte(byte input) + { + hashBlock[hashBlockPos] = input; + if (++hashBlockPos == hashBlock.Length) + { + ProcessHashBlock(); + } + } + + public virtual void ProcessAadBytes(byte[] input, int off, int len) + { + for (int i = 0; i < len; ++i) + { + hashBlock[hashBlockPos] = input[off + i]; + if (++hashBlockPos == hashBlock.Length) + { + ProcessHashBlock(); + } + } + } + + public virtual int ProcessByte(byte input, byte[] output, int outOff) + { + mainBlock[mainBlockPos] = input; + if (++mainBlockPos == mainBlock.Length) + { + ProcessMainBlock(output, outOff); + return BLOCK_SIZE; + } + return 0; + } + + public virtual int ProcessBytes(byte[] input, int inOff, int len, byte[] output, int outOff) + { + int resultLen = 0; + + for (int i = 0; i < len; ++i) + { + mainBlock[mainBlockPos] = input[inOff + i]; + if (++mainBlockPos == mainBlock.Length) + { + ProcessMainBlock(output, outOff + resultLen); + resultLen += BLOCK_SIZE; + } + } + + return resultLen; + } + + public virtual int DoFinal(byte[] output, int outOff) + { + /* + * For decryption, get the tag from the end of the message + */ + byte[] tag = null; + if (!forEncryption) { + if (mainBlockPos < macSize) + throw new InvalidCipherTextException("data too short"); + + mainBlockPos -= macSize; + tag = new byte[macSize]; + Array.Copy(mainBlock, mainBlockPos, tag, 0, macSize); + } + + /* + * HASH: Process any final partial block; compute final hash value + */ + if (hashBlockPos > 0) + { + OCB_extend(hashBlock, hashBlockPos); + UpdateHASH(L_Asterisk); + } + + /* + * OCB-ENCRYPT/OCB-DECRYPT: Process any final partial block + */ + if (mainBlockPos > 0) + { + if (forEncryption) + { + OCB_extend(mainBlock, mainBlockPos); + Xor(Checksum, mainBlock); + } + + Xor(OffsetMAIN, L_Asterisk); + + byte[] Pad = new byte[16]; + hashCipher.ProcessBlock(OffsetMAIN, 0, Pad, 0); + + Xor(mainBlock, Pad); + + Check.OutputLength(output, outOff, mainBlockPos, "Output buffer too short"); + Array.Copy(mainBlock, 0, output, outOff, mainBlockPos); + + if (!forEncryption) + { + OCB_extend(mainBlock, mainBlockPos); + Xor(Checksum, mainBlock); + } + } + + /* + * OCB-ENCRYPT/OCB-DECRYPT: Compute raw tag + */ + Xor(Checksum, OffsetMAIN); + Xor(Checksum, L_Dollar); + hashCipher.ProcessBlock(Checksum, 0, Checksum, 0); + Xor(Checksum, Sum); + + this.macBlock = new byte[macSize]; + Array.Copy(Checksum, 0, macBlock, 0, macSize); + + /* + * Validate or append tag and reset this cipher for the next run + */ + int resultLen = mainBlockPos; + + if (forEncryption) + { + Check.OutputLength(output, outOff, resultLen + macSize, "Output buffer too short"); + + // Append tag to the message + Array.Copy(macBlock, 0, output, outOff + resultLen, macSize); + resultLen += macSize; + } + else + { + // Compare the tag from the message with the calculated one + if (!Arrays.ConstantTimeAreEqual(macBlock, tag)) + throw new InvalidCipherTextException("mac check in OCB failed"); + } + + Reset(false); + + return resultLen; + } + + public virtual void Reset() + { + Reset(true); + } + + protected virtual void Clear(byte[] bs) + { + if (bs != null) + { + Array.Clear(bs, 0, bs.Length); + } + } + + protected virtual byte[] GetLSub(int n) + { + while (n >= L.Count) + { + L.Add(OCB_double((byte[]) L[L.Count - 1])); + } + return (byte[])L[n]; + } + + protected virtual void ProcessHashBlock() + { + /* + * HASH: Process any whole blocks + */ + UpdateHASH(GetLSub(OCB_ntz(++hashBlockCount))); + hashBlockPos = 0; + } + + protected virtual void ProcessMainBlock(byte[] output, int outOff) + { + Check.DataLength(output, outOff, BLOCK_SIZE, "Output buffer too short"); + + /* + * OCB-ENCRYPT/OCB-DECRYPT: Process any whole blocks + */ + + if (forEncryption) + { + Xor(Checksum, mainBlock); + mainBlockPos = 0; + } + + Xor(OffsetMAIN, GetLSub(OCB_ntz(++mainBlockCount))); + + Xor(mainBlock, OffsetMAIN); + mainCipher.ProcessBlock(mainBlock, 0, mainBlock, 0); + Xor(mainBlock, OffsetMAIN); + + Array.Copy(mainBlock, 0, output, outOff, 16); + + if (!forEncryption) + { + Xor(Checksum, mainBlock); + Array.Copy(mainBlock, BLOCK_SIZE, mainBlock, 0, macSize); + mainBlockPos = macSize; + } + } + + protected virtual void Reset(bool clearMac) + { + hashCipher.Reset(); + mainCipher.Reset(); + + Clear(hashBlock); + Clear(mainBlock); + + hashBlockPos = 0; + mainBlockPos = 0; + + hashBlockCount = 0; + mainBlockCount = 0; + + Clear(OffsetHASH); + Clear(Sum); + Array.Copy(OffsetMAIN_0, 0, OffsetMAIN, 0, 16); + Clear(Checksum); + + if (clearMac) + { + macBlock = null; + } + + if (initialAssociatedText != null) + { + ProcessAadBytes(initialAssociatedText, 0, initialAssociatedText.Length); + } + } + + protected virtual void UpdateHASH(byte[] LSub) + { + Xor(OffsetHASH, LSub); + Xor(hashBlock, OffsetHASH); + hashCipher.ProcessBlock(hashBlock, 0, hashBlock, 0); + Xor(Sum, hashBlock); + } + + protected static byte[] OCB_double(byte[] block) + { + byte[] result = new byte[16]; + int carry = ShiftLeft(block, result); + + /* + * NOTE: This construction is an attempt at a constant-time implementation. + */ + result[15] ^= (byte)(0x87 >> ((1 - carry) << 3)); + + return result; + } + + protected static void OCB_extend(byte[] block, int pos) + { + block[pos] = (byte) 0x80; + while (++pos < 16) + { + block[pos] = 0; + } + } + + protected static int OCB_ntz(long x) + { + if (x == 0) + { + return 64; + } + + int n = 0; + ulong ux = (ulong)x; + while ((ux & 1UL) == 0UL) + { + ++n; + ux >>= 1; + } + return n; + } + + protected static int ShiftLeft(byte[] block, byte[] output) + { + int i = 16; + uint bit = 0; + while (--i >= 0) + { + uint b = block[i]; + output[i] = (byte) ((b << 1) | bit); + bit = (b >> 7) & 1; + } + return (int)bit; + } + + protected static void Xor(byte[] block, byte[] val) + { + for (int i = 15; i >= 0; --i) + { + block[i] ^= val[i]; + } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/modes/OfbBlockCipher.cs b/bc-sharp-crypto/src/crypto/modes/OfbBlockCipher.cs new file mode 100644 index 0000000..a99f8c5 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/modes/OfbBlockCipher.cs @@ -0,0 +1,182 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; + +namespace Org.BouncyCastle.Crypto.Modes +{ + /** + * implements a Output-FeedBack (OFB) mode on top of a simple cipher. + */ + public class OfbBlockCipher + : IBlockCipher + { + private byte[] IV; + private byte[] ofbV; + private byte[] ofbOutV; + + private readonly int blockSize; + private readonly IBlockCipher 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( + IBlockCipher 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 IBlockCipher 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 forEncryption if true the cipher is initialised for + * encryption, if false for decryption. + * @param param the key and other data required by the cipher. + * @exception ArgumentException if the parameters argument is + * inappropriate. + */ + public void Init( + bool forEncryption, //ignored by this OFB mode + ICipherParameters parameters) + { + if (parameters is ParametersWithIV) + { + ParametersWithIV ivParam = (ParametersWithIV)parameters; + byte[] iv = ivParam.GetIV(); + + if (iv.Length < IV.Length) + { + // prepend the supplied IV with zeros (per FIPS PUB 81) + Array.Copy(iv, 0, IV, IV.Length - iv.Length, iv.Length); + for (int i = 0; i < IV.Length - iv.Length; i++) + { + IV[i] = 0; + } + } + else + { + Array.Copy(iv, 0, IV, 0, IV.Length); + } + + parameters = ivParam.Parameters; + } + + Reset(); + + // if it's null, key is to be reused. + if (parameters != null) + { + cipher.Init(true, parameters); + } + } + + /** + * return the algorithm name and mode. + * + * @return the name of the underlying algorithm followed by "/OFB" + * and the block size in bits + */ + public string AlgorithmName + { + get { return cipher.AlgorithmName + "/OFB" + (blockSize * 8); } + } + + public bool IsPartialBlockOkay + { + get { return true; } + } + + /** + * 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 InvalidOperationException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + public int ProcessBlock( + byte[] input, + int inOff, + byte[] output, + int outOff) + { + if ((inOff + blockSize) > input.Length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + blockSize) > output.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++) + { + output[outOff + i] = (byte)(ofbOutV[i] ^ input[inOff + i]); + } + + // + // change over the input block. + // + Array.Copy(ofbV, blockSize, ofbV, 0, ofbV.Length - blockSize); + Array.Copy(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() + { + Array.Copy(IV, 0, ofbV, 0, IV.Length); + + cipher.Reset(); + } + } + +} diff --git a/bc-sharp-crypto/src/crypto/modes/OpenPgpCfbBlockCipher.cs b/bc-sharp-crypto/src/crypto/modes/OpenPgpCfbBlockCipher.cs new file mode 100644 index 0000000..038ca78 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/modes/OpenPgpCfbBlockCipher.cs @@ -0,0 +1,337 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; + +namespace Org.BouncyCastle.Crypto.Modes +{ + /** + * 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 + : IBlockCipher + { + private byte[] IV; + private byte[] FR; + private byte[] FRE; + + private readonly IBlockCipher cipher; + private readonly int blockSize; + + private int count; + private bool forEncryption; + + /** + * Basic constructor. + * + * @param cipher the block cipher to be used as the basis of the + * feedback mode. + */ + public OpenPgpCfbBlockCipher( + IBlockCipher 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 IBlockCipher 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 AlgorithmName + { + get { return cipher.AlgorithmName + "/OpenPGPCFB"; } + } + + public bool IsPartialBlockOkay + { + get { return true; } + } + + /** + * 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 InvalidOperationException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + public int ProcessBlock( + byte[] input, + int inOff, + byte[] output, + int outOff) + { + return (forEncryption) ? EncryptBlock(input, inOff, output, outOff) : DecryptBlock(input, inOff, output, outOff); + } + + /** + * reset the chaining vector back to the IV and reset the underlying + * cipher. + */ + public void Reset() + { + count = 0; + + Array.Copy(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 parameters the key and other data required by the cipher. + * @exception ArgumentException if the parameters argument is + * inappropriate. + */ + public void Init( + bool forEncryption, + ICipherParameters parameters) + { + this.forEncryption = forEncryption; + + if (parameters is ParametersWithIV) + { + ParametersWithIV ivParam = (ParametersWithIV)parameters; + byte[] iv = ivParam.GetIV(); + + if (iv.Length < IV.Length) + { + // prepend the supplied IV with zeros (per FIPS PUB 81) + Array.Copy(iv, 0, IV, IV.Length - iv.Length, iv.Length); + for (int i = 0; i < IV.Length - iv.Length; i++) + { + IV[i] = 0; + } + } + else + { + Array.Copy(iv, 0, IV, 0, IV.Length); + } + + parameters = ivParam.Parameters; + } + + Reset(); + + cipher.Init(true, parameters); + } + + /** + * Encrypt one byte of data according to CFB mode. + * @param data the byte to encrypt + * @param blockOff offset in the current 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 InvalidOperationException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + private int EncryptBlock( + byte[] input, + int inOff, + byte[] outBytes, + int outOff) + { + if ((inOff + blockSize) > input.Length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + blockSize) > outBytes.Length) + { + throw new DataLengthException("output buffer too short"); + } + + if (count > blockSize) + { + FR[blockSize - 2] = outBytes[outOff] = EncryptByte(input[inOff], blockSize - 2); + FR[blockSize - 1] = outBytes[outOff + 1] = EncryptByte(input[inOff + 1], blockSize - 1); + + cipher.ProcessBlock(FR, 0, FRE, 0); + + for (int n = 2; n < blockSize; n++) + { + FR[n - 2] = outBytes[outOff + n] = EncryptByte(input[inOff + n], n - 2); + } + } + else if (count == 0) + { + cipher.ProcessBlock(FR, 0, FRE, 0); + + for (int n = 0; n < blockSize; n++) + { + FR[n] = outBytes[outOff + n] = EncryptByte(input[inOff + n], n); + } + + count += blockSize; + } + else if (count == blockSize) + { + cipher.ProcessBlock(FR, 0, FRE, 0); + + outBytes[outOff] = EncryptByte(input[inOff], 0); + outBytes[outOff + 1] = EncryptByte(input[inOff + 1], 1); + + // + // do reset + // + Array.Copy(FR, 2, FR, 0, blockSize - 2); + Array.Copy(outBytes, outOff, FR, blockSize - 2, 2); + + cipher.ProcessBlock(FR, 0, FRE, 0); + + for (int n = 2; n < blockSize; n++) + { + FR[n - 2] = outBytes[outOff + n] = EncryptByte(input[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 InvalidOperationException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + private int DecryptBlock( + byte[] input, + int inOff, + byte[] outBytes, + int outOff) + { + if ((inOff + blockSize) > input.Length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + blockSize) > outBytes.Length) + { + throw new DataLengthException("output buffer too short"); + } + + if (count > blockSize) + { + byte inVal = input[inOff]; + FR[blockSize - 2] = inVal; + outBytes[outOff] = EncryptByte(inVal, blockSize - 2); + + inVal = input[inOff + 1]; + FR[blockSize - 1] = inVal; + outBytes[outOff + 1] = EncryptByte(inVal, blockSize - 1); + + cipher.ProcessBlock(FR, 0, FRE, 0); + + for (int n = 2; n < blockSize; n++) + { + inVal = input[inOff + n]; + FR[n - 2] = inVal; + outBytes[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] = input[inOff + n]; + outBytes[n] = EncryptByte(input[inOff + n], n); + } + + count += blockSize; + } + else if (count == blockSize) + { + cipher.ProcessBlock(FR, 0, FRE, 0); + + byte inVal1 = input[inOff]; + byte inVal2 = input[inOff + 1]; + outBytes[outOff ] = EncryptByte(inVal1, 0); + outBytes[outOff + 1] = EncryptByte(inVal2, 1); + + Array.Copy(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 = input[inOff + n]; + FR[n - 2] = inVal; + outBytes[outOff + n] = EncryptByte(inVal, n - 2); + } + + count += blockSize; + } + + return blockSize; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/modes/SicBlockCipher.cs b/bc-sharp-crypto/src/crypto/modes/SicBlockCipher.cs new file mode 100644 index 0000000..0bea4a4 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/modes/SicBlockCipher.cs @@ -0,0 +1,120 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Modes +{ + /** + * Implements the Segmented Integer Counter (SIC) mode on top of a simple + * block cipher. + */ + public class SicBlockCipher + : IBlockCipher + { + private readonly IBlockCipher cipher; + private readonly int blockSize; + private readonly byte[] counter; + private readonly byte[] counterOut; + private byte[] IV; + + /** + * Basic constructor. + * + * @param c the block cipher to be used. + */ + public SicBlockCipher(IBlockCipher cipher) + { + this.cipher = cipher; + this.blockSize = cipher.GetBlockSize(); + this.counter = new byte[blockSize]; + this.counterOut = new byte[blockSize]; + this.IV = new byte[blockSize]; + } + + /** + * return the underlying block cipher that we are wrapping. + * + * @return the underlying block cipher that we are wrapping. + */ + public virtual IBlockCipher GetUnderlyingCipher() + { + return cipher; + } + + public virtual void Init( + bool forEncryption, //ignored by this CTR mode + ICipherParameters parameters) + { + ParametersWithIV ivParam = parameters as ParametersWithIV; + if (ivParam == null) + throw new ArgumentException("CTR/SIC mode requires ParametersWithIV", "parameters"); + + this.IV = Arrays.Clone(ivParam.GetIV()); + + if (blockSize < IV.Length) + throw new ArgumentException("CTR/SIC mode requires IV no greater than: " + blockSize + " bytes."); + + int maxCounterSize = System.Math.Min(8, blockSize / 2); + if (blockSize - IV.Length > maxCounterSize) + throw new ArgumentException("CTR/SIC mode requires IV of at least: " + (blockSize - maxCounterSize) + " bytes."); + + // if null it's an IV changed only. + if (ivParam.Parameters != null) + { + cipher.Init(true, ivParam.Parameters); + } + + Reset(); + } + + public virtual string AlgorithmName + { + get { return cipher.AlgorithmName + "/SIC"; } + } + + public virtual bool IsPartialBlockOkay + { + get { return true; } + } + + public virtual int GetBlockSize() + { + return cipher.GetBlockSize(); + } + + public virtual int ProcessBlock( + byte[] input, + int inOff, + byte[] output, + int outOff) + { + cipher.ProcessBlock(counter, 0, counterOut, 0); + + // + // XOR the counterOut with the plaintext producing the cipher text + // + for (int i = 0; i < counterOut.Length; i++) + { + output[outOff + i] = (byte)(counterOut[i] ^ input[inOff + i]); + } + + // Increment the counter + int j = counter.Length; + while (--j >= 0 && ++counter[j] == 0) + { + } + + return counter.Length; + } + + public virtual void Reset() + { + Arrays.Fill(counter, (byte)0); + Array.Copy(IV, 0, counter, 0, IV.Length); + cipher.Reset(); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/modes/gcm/BasicGcmExponentiator.cs b/bc-sharp-crypto/src/crypto/modes/gcm/BasicGcmExponentiator.cs new file mode 100644 index 0000000..5660a1f --- /dev/null +++ b/bc-sharp-crypto/src/crypto/modes/gcm/BasicGcmExponentiator.cs @@ -0,0 +1,40 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Modes.Gcm +{ + public class BasicGcmExponentiator + : IGcmExponentiator + { + private uint[] x; + + public void Init(byte[] x) + { + this.x = GcmUtilities.AsUints(x); + } + + public void ExponentiateX(long pow, byte[] output) + { + // Initial value is little-endian 1 + uint[] y = GcmUtilities.OneAsUints(); + + if (pow > 0) + { + uint[] powX = Arrays.Clone(x); + do + { + if ((pow & 1L) != 0) + { + GcmUtilities.Multiply(y, powX); + } + GcmUtilities.Multiply(powX, powX); + pow >>= 1; + } + while (pow > 0); + } + + GcmUtilities.AsBytes(y, output); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/modes/gcm/BasicGcmMultiplier.cs b/bc-sharp-crypto/src/crypto/modes/gcm/BasicGcmMultiplier.cs new file mode 100644 index 0000000..eb89383 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/modes/gcm/BasicGcmMultiplier.cs @@ -0,0 +1,22 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Modes.Gcm +{ + public class BasicGcmMultiplier + : IGcmMultiplier + { + private uint[] H; + + public void Init(byte[] H) + { + this.H = GcmUtilities.AsUints(H); + } + + public void MultiplyH(byte[] x) + { + uint[] t = GcmUtilities.AsUints(x); + GcmUtilities.Multiply(t, H); + GcmUtilities.AsBytes(t, x); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/modes/gcm/GcmUtilities.cs b/bc-sharp-crypto/src/crypto/modes/gcm/GcmUtilities.cs new file mode 100644 index 0000000..d8ab2ca --- /dev/null +++ b/bc-sharp-crypto/src/crypto/modes/gcm/GcmUtilities.cs @@ -0,0 +1,319 @@ +using System; + +using Org.BouncyCastle.Crypto.Utilities; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Modes.Gcm +{ + internal abstract class GcmUtilities + { + private const uint E1 = 0xe1000000; + private const ulong E1L = (ulong)E1 << 32; + + private static uint[] GenerateLookup() + { + uint[] lookup = new uint[256]; + + for (int c = 0; c < 256; ++c) + { + uint v = 0; + for (int i = 7; i >= 0; --i) + { + if ((c & (1 << i)) != 0) + { + v ^= (E1 >> (7 - i)); + } + } + lookup[c] = v; + } + + return lookup; + } + + private static readonly uint[] LOOKUP = GenerateLookup(); + + internal static byte[] OneAsBytes() + { + byte[] tmp = new byte[16]; + tmp[0] = 0x80; + return tmp; + } + + internal static uint[] OneAsUints() + { + uint[] tmp = new uint[4]; + tmp[0] = 0x80000000; + return tmp; + } + + internal static ulong[] OneAsUlongs() + { + ulong[] tmp = new ulong[2]; + tmp[0] = 1UL << 63; + return tmp; + } + + internal static byte[] AsBytes(uint[] x) + { + return Pack.UInt32_To_BE(x); + } + + internal static void AsBytes(uint[] x, byte[] z) + { + Pack.UInt32_To_BE(x, z, 0); + } + + internal static byte[] AsBytes(ulong[] x) + { + byte[] z = new byte[16]; + Pack.UInt64_To_BE(x, z, 0); + return z; + } + + internal static void AsBytes(ulong[] x, byte[] z) + { + Pack.UInt64_To_BE(x, z, 0); + } + + internal static uint[] AsUints(byte[] bs) + { + uint[] output = new uint[4]; + Pack.BE_To_UInt32(bs, 0, output); + return output; + } + + internal static void AsUints(byte[] bs, uint[] output) + { + Pack.BE_To_UInt32(bs, 0, output); + } + + internal static ulong[] AsUlongs(byte[] x) + { + ulong[] z = new ulong[2]; + Pack.BE_To_UInt64(x, 0, z); + return z; + } + + public static void AsUlongs(byte[] x, ulong[] z) + { + Pack.BE_To_UInt64(x, 0, z); + } + + internal static void Multiply(byte[] x, byte[] y) + { + uint[] t1 = GcmUtilities.AsUints(x); + uint[] t2 = GcmUtilities.AsUints(y); + GcmUtilities.Multiply(t1, t2); + GcmUtilities.AsBytes(t1, x); + } + + internal static void Multiply(uint[] x, uint[] y) + { + uint r00 = x[0], r01 = x[1], r02 = x[2], r03 = x[3]; + uint r10 = 0, r11 = 0, r12 = 0, r13 = 0; + + for (int i = 0; i < 4; ++i) + { + int bits = (int)y[i]; + for (int j = 0; j < 32; ++j) + { + uint m1 = (uint)(bits >> 31); bits <<= 1; + r10 ^= (r00 & m1); + r11 ^= (r01 & m1); + r12 ^= (r02 & m1); + r13 ^= (r03 & m1); + + uint m2 = (uint)((int)(r03 << 31) >> 8); + r03 = (r03 >> 1) | (r02 << 31); + r02 = (r02 >> 1) | (r01 << 31); + r01 = (r01 >> 1) | (r00 << 31); + r00 = (r00 >> 1) ^ (m2 & E1); + } + } + + x[0] = r10; + x[1] = r11; + x[2] = r12; + x[3] = r13; + } + + internal static void Multiply(ulong[] x, ulong[] y) + { + ulong r00 = x[0], r01 = x[1], r10 = 0, r11 = 0; + + for (int i = 0; i < 2; ++i) + { + long bits = (long)y[i]; + for (int j = 0; j < 64; ++j) + { + ulong m1 = (ulong)(bits >> 63); bits <<= 1; + r10 ^= (r00 & m1); + r11 ^= (r01 & m1); + + ulong m2 = (ulong)((long)(r01 << 63) >> 8); + r01 = (r01 >> 1) | (r00 << 63); + r00 = (r00 >> 1) ^ (m2 & E1L); + } + } + + x[0] = r10; + x[1] = r11; + } + + // P is the value with only bit i=1 set + internal static void MultiplyP(uint[] x) + { + uint m = (uint)((int)ShiftRight(x) >> 8); + x[0] ^= (m & E1); + } + + internal static void MultiplyP(uint[] x, uint[] z) + { + uint m = (uint)((int)ShiftRight(x, z) >> 8); + z[0] ^= (m & E1); + } + + internal static void MultiplyP8(uint[] x) + { +// for (int i = 8; i != 0; --i) +// { +// MultiplyP(x); +// } + + uint c = ShiftRightN(x, 8); + x[0] ^= LOOKUP[c >> 24]; + } + + internal static void MultiplyP8(uint[] x, uint[] y) + { + uint c = ShiftRightN(x, 8, y); + y[0] ^= LOOKUP[c >> 24]; + } + + internal static uint ShiftRight(uint[] x) + { + uint b = x[0]; + x[0] = b >> 1; + uint c = b << 31; + b = x[1]; + x[1] = (b >> 1) | c; + c = b << 31; + b = x[2]; + x[2] = (b >> 1) | c; + c = b << 31; + b = x[3]; + x[3] = (b >> 1) | c; + return b << 31; + } + + internal static uint ShiftRight(uint[] x, uint[] z) + { + uint b = x[0]; + z[0] = b >> 1; + uint c = b << 31; + b = x[1]; + z[1] = (b >> 1) | c; + c = b << 31; + b = x[2]; + z[2] = (b >> 1) | c; + c = b << 31; + b = x[3]; + z[3] = (b >> 1) | c; + return b << 31; + } + + internal static uint ShiftRightN(uint[] x, int n) + { + uint b = x[0]; int nInv = 32 - n; + x[0] = b >> n; + uint c = b << nInv; + b = x[1]; + x[1] = (b >> n) | c; + c = b << nInv; + b = x[2]; + x[2] = (b >> n) | c; + c = b << nInv; + b = x[3]; + x[3] = (b >> n) | c; + return b << nInv; + } + + internal static uint ShiftRightN(uint[] x, int n, uint[] z) + { + uint b = x[0]; int nInv = 32 - n; + z[0] = b >> n; + uint c = b << nInv; + b = x[1]; + z[1] = (b >> n) | c; + c = b << nInv; + b = x[2]; + z[2] = (b >> n) | c; + c = b << nInv; + b = x[3]; + z[3] = (b >> n) | c; + return b << nInv; + } + + internal static void Xor(byte[] x, byte[] y) + { + int i = 0; + do + { + x[i] ^= y[i]; ++i; + x[i] ^= y[i]; ++i; + x[i] ^= y[i]; ++i; + x[i] ^= y[i]; ++i; + } + while (i < 16); + } + + internal static void Xor(byte[] x, byte[] y, int yOff, int yLen) + { + while (--yLen >= 0) + { + x[yLen] ^= y[yOff + yLen]; + } + } + + internal static void Xor(byte[] x, byte[] y, byte[] z) + { + int i = 0; + do + { + z[i] = (byte)(x[i] ^ y[i]); ++i; + z[i] = (byte)(x[i] ^ y[i]); ++i; + z[i] = (byte)(x[i] ^ y[i]); ++i; + z[i] = (byte)(x[i] ^ y[i]); ++i; + } + while (i < 16); + } + + internal static void Xor(uint[] x, uint[] y) + { + x[0] ^= y[0]; + x[1] ^= y[1]; + x[2] ^= y[2]; + x[3] ^= y[3]; + } + + internal static void Xor(uint[] x, uint[] y, uint[] z) + { + z[0] = x[0] ^ y[0]; + z[1] = x[1] ^ y[1]; + z[2] = x[2] ^ y[2]; + z[3] = x[3] ^ y[3]; + } + + internal static void Xor(ulong[] x, ulong[] y) + { + x[0] ^= y[0]; + x[1] ^= y[1]; + } + + internal static void Xor(ulong[] x, ulong[] y, ulong[] z) + { + z[0] = x[0] ^ y[0]; + z[1] = x[1] ^ y[1]; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/modes/gcm/IGcmExponentiator.cs b/bc-sharp-crypto/src/crypto/modes/gcm/IGcmExponentiator.cs new file mode 100644 index 0000000..5b4ce9d --- /dev/null +++ b/bc-sharp-crypto/src/crypto/modes/gcm/IGcmExponentiator.cs @@ -0,0 +1,10 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Modes.Gcm +{ + public interface IGcmExponentiator + { + void Init(byte[] x); + void ExponentiateX(long pow, byte[] output); + } +} diff --git a/bc-sharp-crypto/src/crypto/modes/gcm/IGcmMultiplier.cs b/bc-sharp-crypto/src/crypto/modes/gcm/IGcmMultiplier.cs new file mode 100644 index 0000000..ec7b906 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/modes/gcm/IGcmMultiplier.cs @@ -0,0 +1,10 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Modes.Gcm +{ + public interface IGcmMultiplier + { + void Init(byte[] H); + void MultiplyH(byte[] x); + } +} diff --git a/bc-sharp-crypto/src/crypto/modes/gcm/Tables1kGcmExponentiator.cs b/bc-sharp-crypto/src/crypto/modes/gcm/Tables1kGcmExponentiator.cs new file mode 100644 index 0000000..e649d67 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/modes/gcm/Tables1kGcmExponentiator.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Modes.Gcm +{ + public class Tables1kGcmExponentiator + : IGcmExponentiator + { + // A lookup table of the power-of-two powers of 'x' + // - lookupPowX2[i] = x^(2^i) + private IList lookupPowX2; + + public void Init(byte[] x) + { + uint[] y = GcmUtilities.AsUints(x); + if (lookupPowX2 != null && Arrays.AreEqual(y, (uint[])lookupPowX2[0])) + return; + + lookupPowX2 = Platform.CreateArrayList(8); + lookupPowX2.Add(y); + } + + public void ExponentiateX(long pow, byte[] output) + { + uint[] y = GcmUtilities.OneAsUints(); + int bit = 0; + while (pow > 0) + { + if ((pow & 1L) != 0) + { + EnsureAvailable(bit); + GcmUtilities.Multiply(y, (uint[])lookupPowX2[bit]); + } + ++bit; + pow >>= 1; + } + + GcmUtilities.AsBytes(y, output); + } + + private void EnsureAvailable(int bit) + { + int count = lookupPowX2.Count; + if (count <= bit) + { + uint[] tmp = (uint[])lookupPowX2[count - 1]; + do + { + tmp = Arrays.Clone(tmp); + GcmUtilities.Multiply(tmp, tmp); + lookupPowX2.Add(tmp); + } + while (++count <= bit); + } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/modes/gcm/Tables64kGcmMultiplier.cs b/bc-sharp-crypto/src/crypto/modes/gcm/Tables64kGcmMultiplier.cs new file mode 100644 index 0000000..707b0be --- /dev/null +++ b/bc-sharp-crypto/src/crypto/modes/gcm/Tables64kGcmMultiplier.cs @@ -0,0 +1,77 @@ +using System; + +using Org.BouncyCastle.Crypto.Utilities; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Modes.Gcm +{ + public class Tables64kGcmMultiplier + : IGcmMultiplier + { + private byte[] H; + private uint[][][] M; + + public void Init(byte[] H) + { + if (M == null) + { + M = new uint[16][][]; + } + else if (Arrays.AreEqual(this.H, H)) + { + return; + } + + this.H = Arrays.Clone(H); + + M[0] = new uint[256][]; + M[0][0] = new uint[4]; + M[0][128] = GcmUtilities.AsUints(H); + for (int j = 64; j >= 1; j >>= 1) + { + uint[] tmp = (uint[])M[0][j + j].Clone(); + GcmUtilities.MultiplyP(tmp); + M[0][j] = tmp; + } + for (int i = 0; ; ) + { + for (int j = 2; j < 256; j += j) + { + for (int k = 1; k < j; ++k) + { + uint[] tmp = (uint[])M[i][j].Clone(); + GcmUtilities.Xor(tmp, M[i][k]); + M[i][j + k] = tmp; + } + } + + if (++i == 16) return; + + M[i] = new uint[256][]; + M[i][0] = new uint[4]; + for (int j = 128; j > 0; j >>= 1) + { + uint[] tmp = (uint[])M[i - 1][j].Clone(); + GcmUtilities.MultiplyP8(tmp); + M[i][j] = tmp; + } + } + } + + public void MultiplyH(byte[] x) + { + uint[] z = new uint[4]; + for (int i = 0; i != 16; ++i) + { + //GcmUtilities.Xor(z, M[i][x[i]]); + uint[] m = M[i][x[i]]; + z[0] ^= m[0]; + z[1] ^= m[1]; + z[2] ^= m[2]; + z[3] ^= m[3]; + } + + Pack.UInt32_To_BE(z, x, 0); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/modes/gcm/Tables8kGcmMultiplier.cs b/bc-sharp-crypto/src/crypto/modes/gcm/Tables8kGcmMultiplier.cs new file mode 100644 index 0000000..5f3d6c8 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/modes/gcm/Tables8kGcmMultiplier.cs @@ -0,0 +1,103 @@ +using System; + +using Org.BouncyCastle.Crypto.Utilities; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Modes.Gcm +{ + public class Tables8kGcmMultiplier + : IGcmMultiplier + { + private byte[] H; + private uint[][][] M; + + public void Init(byte[] H) + { + if (M == null) + { + M = new uint[32][][]; + } + else if (Arrays.AreEqual(this.H, H)) + { + return; + } + + this.H = Arrays.Clone(H); + + M[0] = new uint[16][]; + M[1] = new uint[16][]; + M[0][0] = new uint[4]; + M[1][0] = new uint[4]; + M[1][8] = GcmUtilities.AsUints(H); + + for (int j = 4; j >= 1; j >>= 1) + { + uint[] tmp = (uint[])M[1][j + j].Clone(); + GcmUtilities.MultiplyP(tmp); + M[1][j] = tmp; + } + + { + uint[] tmp = (uint[])M[1][1].Clone(); + GcmUtilities.MultiplyP(tmp); + M[0][8] = tmp; + } + + for (int j = 4; j >= 1; j >>= 1) + { + uint[] tmp = (uint[])M[0][j + j].Clone(); + GcmUtilities.MultiplyP(tmp); + M[0][j] = tmp; + } + + for (int i = 0; ; ) + { + for (int j = 2; j < 16; j += j) + { + for (int k = 1; k < j; ++k) + { + uint[] tmp = (uint[])M[i][j].Clone(); + GcmUtilities.Xor(tmp, M[i][k]); + M[i][j + k] = tmp; + } + } + + if (++i == 32) return; + + if (i > 1) + { + M[i] = new uint[16][]; + M[i][0] = new uint[4]; + for (int j = 8; j > 0; j >>= 1) + { + uint[] tmp = (uint[])M[i - 2][j].Clone(); + GcmUtilities.MultiplyP8(tmp); + M[i][j] = tmp; + } + } + } + } + + public void MultiplyH(byte[] x) + { + uint[] z = new uint[4]; + for (int i = 15; i >= 0; --i) + { + //GcmUtilities.Xor(z, M[i + i][x[i] & 0x0f]); + uint[] m = M[i + i][x[i] & 0x0f]; + z[0] ^= m[0]; + z[1] ^= m[1]; + z[2] ^= m[2]; + z[3] ^= m[3]; + //GcmUtilities.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.UInt32_To_BE(z, x, 0); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/operators/Asn1Signature.cs b/bc-sharp-crypto/src/crypto/operators/Asn1Signature.cs new file mode 100644 index 0000000..e023c1d --- /dev/null +++ b/bc-sharp-crypto/src/crypto/operators/Asn1Signature.cs @@ -0,0 +1,555 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.CryptoPro; +using Org.BouncyCastle.Asn1.Nist; +using Org.BouncyCastle.Asn1.Oiw; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.TeleTrust; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Crypto.IO; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; + +namespace Org.BouncyCastle.Crypto.Operators +{ + internal class X509Utilities + { + private static readonly Asn1Null derNull = DerNull.Instance; + + private static readonly IDictionary algorithms = Platform.CreateHashtable(); + private static readonly IDictionary exParams = Platform.CreateHashtable(); + private static readonly ISet noParams = new HashSet(); + + static X509Utilities() + { + algorithms.Add("MD2WITHRSAENCRYPTION", PkcsObjectIdentifiers.MD2WithRsaEncryption); + algorithms.Add("MD2WITHRSA", PkcsObjectIdentifiers.MD2WithRsaEncryption); + algorithms.Add("MD5WITHRSAENCRYPTION", PkcsObjectIdentifiers.MD5WithRsaEncryption); + algorithms.Add("MD5WITHRSA", PkcsObjectIdentifiers.MD5WithRsaEncryption); + algorithms.Add("SHA1WITHRSAENCRYPTION", PkcsObjectIdentifiers.Sha1WithRsaEncryption); + algorithms.Add("SHA1WITHRSA", PkcsObjectIdentifiers.Sha1WithRsaEncryption); + algorithms.Add("SHA224WITHRSAENCRYPTION", PkcsObjectIdentifiers.Sha224WithRsaEncryption); + algorithms.Add("SHA224WITHRSA", PkcsObjectIdentifiers.Sha224WithRsaEncryption); + algorithms.Add("SHA256WITHRSAENCRYPTION", PkcsObjectIdentifiers.Sha256WithRsaEncryption); + algorithms.Add("SHA256WITHRSA", PkcsObjectIdentifiers.Sha256WithRsaEncryption); + algorithms.Add("SHA384WITHRSAENCRYPTION", PkcsObjectIdentifiers.Sha384WithRsaEncryption); + algorithms.Add("SHA384WITHRSA", PkcsObjectIdentifiers.Sha384WithRsaEncryption); + algorithms.Add("SHA512WITHRSAENCRYPTION", PkcsObjectIdentifiers.Sha512WithRsaEncryption); + algorithms.Add("SHA512WITHRSA", PkcsObjectIdentifiers.Sha512WithRsaEncryption); + algorithms.Add("SHA1WITHRSAANDMGF1", PkcsObjectIdentifiers.IdRsassaPss); + algorithms.Add("SHA224WITHRSAANDMGF1", PkcsObjectIdentifiers.IdRsassaPss); + algorithms.Add("SHA256WITHRSAANDMGF1", PkcsObjectIdentifiers.IdRsassaPss); + algorithms.Add("SHA384WITHRSAANDMGF1", PkcsObjectIdentifiers.IdRsassaPss); + algorithms.Add("SHA512WITHRSAANDMGF1", PkcsObjectIdentifiers.IdRsassaPss); + algorithms.Add("RIPEMD160WITHRSAENCRYPTION", TeleTrusTObjectIdentifiers.RsaSignatureWithRipeMD160); + algorithms.Add("RIPEMD160WITHRSA", TeleTrusTObjectIdentifiers.RsaSignatureWithRipeMD160); + algorithms.Add("RIPEMD128WITHRSAENCRYPTION", TeleTrusTObjectIdentifiers.RsaSignatureWithRipeMD128); + algorithms.Add("RIPEMD128WITHRSA", TeleTrusTObjectIdentifiers.RsaSignatureWithRipeMD128); + algorithms.Add("RIPEMD256WITHRSAENCRYPTION", TeleTrusTObjectIdentifiers.RsaSignatureWithRipeMD256); + algorithms.Add("RIPEMD256WITHRSA", TeleTrusTObjectIdentifiers.RsaSignatureWithRipeMD256); + algorithms.Add("SHA1WITHDSA", X9ObjectIdentifiers.IdDsaWithSha1); + algorithms.Add("DSAWITHSHA1", X9ObjectIdentifiers.IdDsaWithSha1); + algorithms.Add("SHA224WITHDSA", NistObjectIdentifiers.DsaWithSha224); + algorithms.Add("SHA256WITHDSA", NistObjectIdentifiers.DsaWithSha256); + algorithms.Add("SHA384WITHDSA", NistObjectIdentifiers.DsaWithSha384); + algorithms.Add("SHA512WITHDSA", NistObjectIdentifiers.DsaWithSha512); + algorithms.Add("SHA1WITHECDSA", X9ObjectIdentifiers.ECDsaWithSha1); + algorithms.Add("ECDSAWITHSHA1", X9ObjectIdentifiers.ECDsaWithSha1); + algorithms.Add("SHA224WITHECDSA", X9ObjectIdentifiers.ECDsaWithSha224); + algorithms.Add("SHA256WITHECDSA", X9ObjectIdentifiers.ECDsaWithSha256); + algorithms.Add("SHA384WITHECDSA", X9ObjectIdentifiers.ECDsaWithSha384); + algorithms.Add("SHA512WITHECDSA", X9ObjectIdentifiers.ECDsaWithSha512); + algorithms.Add("GOST3411WITHGOST3410", CryptoProObjectIdentifiers.GostR3411x94WithGostR3410x94); + algorithms.Add("GOST3411WITHGOST3410-94", CryptoProObjectIdentifiers.GostR3411x94WithGostR3410x94); + algorithms.Add("GOST3411WITHECGOST3410", CryptoProObjectIdentifiers.GostR3411x94WithGostR3410x2001); + algorithms.Add("GOST3411WITHECGOST3410-2001", CryptoProObjectIdentifiers.GostR3411x94WithGostR3410x2001); + algorithms.Add("GOST3411WITHGOST3410-2001", CryptoProObjectIdentifiers.GostR3411x94WithGostR3410x2001); + + // + // According to RFC 3279, the ASN.1 encoding SHALL (id-dsa-with-sha1) or MUST (ecdsa-with-SHA*) omit the parameters field. + // The parameters field SHALL be NULL for RSA based signature algorithms. + // + noParams.Add(X9ObjectIdentifiers.ECDsaWithSha1); + noParams.Add(X9ObjectIdentifiers.ECDsaWithSha224); + noParams.Add(X9ObjectIdentifiers.ECDsaWithSha256); + noParams.Add(X9ObjectIdentifiers.ECDsaWithSha384); + noParams.Add(X9ObjectIdentifiers.ECDsaWithSha512); + noParams.Add(X9ObjectIdentifiers.IdDsaWithSha1); + noParams.Add(NistObjectIdentifiers.DsaWithSha224); + noParams.Add(NistObjectIdentifiers.DsaWithSha256); + noParams.Add(NistObjectIdentifiers.DsaWithSha384); + noParams.Add(NistObjectIdentifiers.DsaWithSha512); + + // + // RFC 4491 + // + noParams.Add(CryptoProObjectIdentifiers.GostR3411x94WithGostR3410x94); + noParams.Add(CryptoProObjectIdentifiers.GostR3411x94WithGostR3410x2001); + + // + // explicit params + // + AlgorithmIdentifier sha1AlgId = new AlgorithmIdentifier(OiwObjectIdentifiers.IdSha1, DerNull.Instance); + exParams.Add("SHA1WITHRSAANDMGF1", CreatePssParams(sha1AlgId, 20)); + + AlgorithmIdentifier sha224AlgId = new AlgorithmIdentifier(NistObjectIdentifiers.IdSha224, DerNull.Instance); + exParams.Add("SHA224WITHRSAANDMGF1", CreatePssParams(sha224AlgId, 28)); + + AlgorithmIdentifier sha256AlgId = new AlgorithmIdentifier(NistObjectIdentifiers.IdSha256, DerNull.Instance); + exParams.Add("SHA256WITHRSAANDMGF1", CreatePssParams(sha256AlgId, 32)); + + AlgorithmIdentifier sha384AlgId = new AlgorithmIdentifier(NistObjectIdentifiers.IdSha384, DerNull.Instance); + exParams.Add("SHA384WITHRSAANDMGF1", CreatePssParams(sha384AlgId, 48)); + + AlgorithmIdentifier sha512AlgId = new AlgorithmIdentifier(NistObjectIdentifiers.IdSha512, DerNull.Instance); + exParams.Add("SHA512WITHRSAANDMGF1", CreatePssParams(sha512AlgId, 64)); + } + + /** + * Return the digest algorithm using one of the standard JCA string + * representations rather than the algorithm identifier (if possible). + */ + private static string GetDigestAlgName( + DerObjectIdentifier digestAlgOID) + { + if (PkcsObjectIdentifiers.MD5.Equals(digestAlgOID)) + { + return "MD5"; + } + else if (OiwObjectIdentifiers.IdSha1.Equals(digestAlgOID)) + { + return "SHA1"; + } + else if (NistObjectIdentifiers.IdSha224.Equals(digestAlgOID)) + { + return "SHA224"; + } + else if (NistObjectIdentifiers.IdSha256.Equals(digestAlgOID)) + { + return "SHA256"; + } + else if (NistObjectIdentifiers.IdSha384.Equals(digestAlgOID)) + { + return "SHA384"; + } + else if (NistObjectIdentifiers.IdSha512.Equals(digestAlgOID)) + { + return "SHA512"; + } + else if (TeleTrusTObjectIdentifiers.RipeMD128.Equals(digestAlgOID)) + { + return "RIPEMD128"; + } + else if (TeleTrusTObjectIdentifiers.RipeMD160.Equals(digestAlgOID)) + { + return "RIPEMD160"; + } + else if (TeleTrusTObjectIdentifiers.RipeMD256.Equals(digestAlgOID)) + { + return "RIPEMD256"; + } + else if (CryptoProObjectIdentifiers.GostR3411.Equals(digestAlgOID)) + { + return "GOST3411"; + } + else + { + return digestAlgOID.Id; + } + } + + internal static string GetSignatureName(AlgorithmIdentifier sigAlgId) + { + Asn1Encodable parameters = sigAlgId.Parameters; + + if (parameters != null && !derNull.Equals(parameters)) + { + if (sigAlgId.Algorithm.Equals(PkcsObjectIdentifiers.IdRsassaPss)) + { + RsassaPssParameters rsaParams = RsassaPssParameters.GetInstance(parameters); + + return GetDigestAlgName(rsaParams.HashAlgorithm.Algorithm) + "withRSAandMGF1"; + } + if (sigAlgId.Algorithm.Equals(X9ObjectIdentifiers.ECDsaWithSha2)) + { + Asn1Sequence ecDsaParams = Asn1Sequence.GetInstance(parameters); + + return GetDigestAlgName((DerObjectIdentifier)ecDsaParams[0]) + "withECDSA"; + } + } + + return sigAlgId.Algorithm.Id; + } + + private static RsassaPssParameters CreatePssParams( + AlgorithmIdentifier hashAlgId, + int saltSize) + { + return new RsassaPssParameters( + hashAlgId, + new AlgorithmIdentifier(PkcsObjectIdentifiers.IdMgf1, hashAlgId), + new DerInteger(saltSize), + new DerInteger(1)); + } + + internal static DerObjectIdentifier GetAlgorithmOid( + string algorithmName) + { + algorithmName = Platform.ToUpperInvariant(algorithmName); + + if (algorithms.Contains(algorithmName)) + { + return (DerObjectIdentifier) algorithms[algorithmName]; + } + + return new DerObjectIdentifier(algorithmName); + } + + internal static AlgorithmIdentifier GetSigAlgID( + DerObjectIdentifier sigOid, + string algorithmName) + { + if (noParams.Contains(sigOid)) + { + return new AlgorithmIdentifier(sigOid); + } + + algorithmName = Platform.ToUpperInvariant(algorithmName); + + if (exParams.Contains(algorithmName)) + { + return new AlgorithmIdentifier(sigOid, (Asn1Encodable) exParams[algorithmName]); + } + + return new AlgorithmIdentifier(sigOid, DerNull.Instance); + } + + internal static IEnumerable GetAlgNames() + { + return new EnumerableProxy(algorithms.Keys); + } + } + + internal class SignerBucket + : Stream + { + protected readonly ISigner signer; + + public SignerBucket( + ISigner signer) + { + this.signer = signer; + } + + public override int Read( + byte[] buffer, + int offset, + int count) + { + throw new NotImplementedException (); + } + + public override int ReadByte() + { + throw new NotImplementedException (); + } + + public override void Write( + byte[] buffer, + int offset, + int count) + { + if (count > 0) + { + signer.BlockUpdate(buffer, offset, count); + } + } + + public override void WriteByte( + byte b) + { + signer.Update(b); + } + + public override bool CanRead + { + get { return false; } + } + + public override bool CanWrite + { + get { return true; } + } + + public override bool CanSeek + { + get { return false; } + } + + public override long Length + { + get { return 0; } + } + + public override long Position + { + get { throw new NotImplementedException (); } + set { throw new NotImplementedException (); } + } + + public override void Flush() + { + } + + public override long Seek( + long offset, + SeekOrigin origin) + { + throw new NotImplementedException (); + } + + public override void SetLength( + long length) + { + throw new NotImplementedException (); + } + } + + /// + /// Calculator factory class for signature generation in ASN.1 based profiles that use an AlgorithmIdentifier to preserve + /// signature algorithm details. + /// + public class Asn1SignatureFactory: ISignatureFactory + { + private readonly AlgorithmIdentifier algID; + private readonly string algorithm; + private readonly AsymmetricKeyParameter privateKey; + private readonly SecureRandom random; + + /// + /// Base constructor. + /// + /// The name of the signature algorithm to use. + /// The private key to be used in the signing operation. + public Asn1SignatureFactory (string algorithm, AsymmetricKeyParameter privateKey): this(algorithm, privateKey, null) + { + } + + /// + /// Constructor which also specifies a source of randomness to be used if one is required. + /// + /// The name of the signature algorithm to use. + /// The private key to be used in the signing operation. + /// The source of randomness to be used in signature calculation. + public Asn1SignatureFactory (string algorithm, AsymmetricKeyParameter privateKey, SecureRandom random) + { + DerObjectIdentifier sigOid = X509Utilities.GetAlgorithmOid (algorithm); + + this.algorithm = algorithm; + this.privateKey = privateKey; + this.random = random; + this.algID = X509Utilities.GetSigAlgID (sigOid, algorithm); + } + + public Object AlgorithmDetails + { + get { return this.algID; } + } + + public IStreamCalculator CreateCalculator() + { + ISigner sig = SignerUtilities.GetSigner(algorithm); + + if (random != null) + { + sig.Init(true, new ParametersWithRandom(privateKey, random)); + } + else + { + sig.Init(true, privateKey); + } + + return new SigCalculator(sig); + } + + /// + /// Allows enumeration of the signature names supported by the verifier provider. + /// + public static IEnumerable SignatureAlgNames + { + get { return X509Utilities.GetAlgNames(); } + } + } + + internal class SigCalculator : IStreamCalculator + { + private readonly ISigner sig; + private readonly Stream stream; + + internal SigCalculator(ISigner sig) + { + this.sig = sig; + this.stream = new SignerBucket(sig); + } + + public Stream Stream + { + get { return stream; } + } + + public object GetResult() + { + return new SigResult(sig); + } + } + + internal class SigResult : IBlockResult + { + private readonly ISigner sig; + + internal SigResult(ISigner sig) + { + this.sig = sig; + } + + public byte[] Collect() + { + return sig.GenerateSignature(); + } + + public int Collect(byte[] destination, int offset) + { + byte[] signature = Collect(); + + Array.Copy(signature, 0, destination, offset, signature.Length); + + return signature.Length; + } + } + + /// + /// Verifier class for signature verification in ASN.1 based profiles that use an AlgorithmIdentifier to preserve + /// signature algorithm details. + /// + public class Asn1VerifierFactory: IVerifierFactory + { + private readonly AlgorithmIdentifier algID; + private readonly AsymmetricKeyParameter publicKey; + + /// + /// Base constructor. + /// + /// The name of the signature algorithm to use. + /// The public key to be used in the verification operation. + public Asn1VerifierFactory (String algorithm, AsymmetricKeyParameter publicKey) + { + DerObjectIdentifier sigOid = X509Utilities.GetAlgorithmOid (algorithm); + + this.publicKey = publicKey; + this.algID = X509Utilities.GetSigAlgID (sigOid, algorithm); + } + + public Asn1VerifierFactory (AlgorithmIdentifier algorithm, AsymmetricKeyParameter publicKey) + { + this.publicKey = publicKey; + this.algID = algorithm; + } + + public Object AlgorithmDetails + { + get { return this.algID; } + } + + public IStreamCalculator CreateCalculator() + { + ISigner sig = SignerUtilities.GetSigner(X509Utilities.GetSignatureName(algID)); + + sig.Init(false, publicKey); + + return new VerifierCalculator(sig); + } + } + + internal class VerifierCalculator : IStreamCalculator + { + private readonly ISigner sig; + private readonly Stream stream; + + internal VerifierCalculator(ISigner sig) + { + this.sig = sig; + this.stream = new SignerBucket(sig); + } + + public Stream Stream + { + get { return stream; } + } + + public object GetResult() + { + return new VerifierResult(sig); + } + } + + internal class VerifierResult : IVerifier + { + private readonly ISigner sig; + + internal VerifierResult(ISigner sig) + { + this.sig = sig; + } + + public bool IsVerified(byte[] signature) + { + return sig.VerifySignature(signature); + } + + public bool IsVerified(byte[] signature, int off, int length) + { + byte[] sigBytes = new byte[length]; + + Array.Copy(signature, 0, sigBytes, off, sigBytes.Length); + + return sig.VerifySignature(signature); + } + } + + /// + /// Provider class which supports dynamic creation of signature verifiers. + /// + public class Asn1VerifierFactoryProvider: IVerifierFactoryProvider + { + private readonly AsymmetricKeyParameter publicKey; + + /// + /// Base constructor - specify the public key to be used in verification. + /// + /// The public key to be used in creating verifiers provided by this object. + public Asn1VerifierFactoryProvider(AsymmetricKeyParameter publicKey) + { + this.publicKey = publicKey; + } + + public IVerifierFactory CreateVerifierFactory(Object algorithmDetails) + { + return new Asn1VerifierFactory ((AlgorithmIdentifier)algorithmDetails, publicKey); + } + + /// + /// Allows enumeration of the signature names supported by the verifier provider. + /// + public IEnumerable SignatureAlgNames + { + get { return X509Utilities.GetAlgNames(); } + } + } +} + diff --git a/bc-sharp-crypto/src/crypto/paddings/BlockCipherPadding.cs b/bc-sharp-crypto/src/crypto/paddings/BlockCipherPadding.cs new file mode 100644 index 0000000..33a5f9f --- /dev/null +++ b/bc-sharp-crypto/src/crypto/paddings/BlockCipherPadding.cs @@ -0,0 +1,43 @@ +using System; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Security; + + +namespace Org.BouncyCastle.Crypto.Paddings +{ + /** + * Block cipher padders are expected to conform to this interface + */ + public interface IBlockCipherPadding + { + /** + * Initialise the padder. + * + * @param param parameters, if any required. + */ + void Init(SecureRandom random); + //throws ArgumentException; + + /** + * Return the name of the algorithm the cipher implements. + * + * @return the name of the algorithm the cipher implements. + */ + string PaddingName { get; } + + /** + * add the pad bytes to the passed in block, returning the + * number of bytes added. + */ + int AddPadding(byte[] input, int inOff); + + /** + * return the number of pad bytes present in the block. + * @exception InvalidCipherTextException if the padding is badly formed + * or invalid. + */ + int PadCount(byte[] input); + //throws InvalidCipherTextException; + } + +} diff --git a/bc-sharp-crypto/src/crypto/paddings/ISO10126d2Padding.cs b/bc-sharp-crypto/src/crypto/paddings/ISO10126d2Padding.cs new file mode 100644 index 0000000..e132a62 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/paddings/ISO10126d2Padding.cs @@ -0,0 +1,76 @@ +using System; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Security; + + +namespace Org.BouncyCastle.Crypto.Paddings +{ + + /** + * A padder that adds ISO10126-2 padding to a block. + */ + public class ISO10126d2Padding: IBlockCipherPadding + { + private SecureRandom random; + + /** + * Initialise the padder. + * + * @param random a SecureRandom if available. + */ + public void Init( + SecureRandom random) + //throws ArgumentException + { + this.random = (random != null) ? random : new SecureRandom(); + } + + /** + * Return the name of the algorithm the cipher implements. + * + * @return the name of the algorithm the cipher implements. + */ + public string PaddingName + { + get { return "ISO10126-2"; } + } + + /** + * add the pad bytes to the passed in block, returning the + * number of bytes added. + */ + public int AddPadding( + byte[] input, + int inOff) + { + byte code = (byte)(input.Length - inOff); + + while (inOff < (input.Length - 1)) + { + input[inOff] = (byte)random.NextInt(); + inOff++; + } + + input[inOff] = code; + + return code; + } + + /** + * return the number of pad bytes present in the block. + */ + public int PadCount(byte[] input) + //throws InvalidCipherTextException + { + int count = input[input.Length - 1] & 0xff; + + if (count > input.Length) + { + throw new InvalidCipherTextException("pad block corrupted"); + } + + return count; + } + } + +} diff --git a/bc-sharp-crypto/src/crypto/paddings/ISO7816d4Padding.cs b/bc-sharp-crypto/src/crypto/paddings/ISO7816d4Padding.cs new file mode 100644 index 0000000..016b25a --- /dev/null +++ b/bc-sharp-crypto/src/crypto/paddings/ISO7816d4Padding.cs @@ -0,0 +1,79 @@ +using System; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Paddings +{ + /** + * 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 + : IBlockCipherPadding + { + /** + * Initialise the padder. + * + * @param random - a SecureRandom if available. + */ + public void Init( + SecureRandom random) + { + // nothing to do. + } + + /** + * Return the name of the algorithm the padder implements. + * + * @return the name of the algorithm the padder implements. + */ + public string PaddingName + { + get { return "ISO7816-4"; } + } + + /** + * add the pad bytes to the passed in block, returning the + * number of bytes added. + */ + public int AddPadding( + byte[] input, + int inOff) + { + int added = (input.Length - inOff); + + input[inOff]= (byte) 0x80; + inOff ++; + + while (inOff < input.Length) + { + input[inOff] = (byte) 0; + inOff++; + } + + return added; + } + + /** + * return the number of pad bytes present in the block. + */ + public int PadCount( + byte[] input) + { + int count = input.Length - 1; + + while (count > 0 && input[count] == 0) + { + count--; + } + + if (input[count] != (byte)0x80) + { + throw new InvalidCipherTextException("pad block corrupted"); + } + + return input.Length - count; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/paddings/PaddedBufferedBlockCipher.cs b/bc-sharp-crypto/src/crypto/paddings/PaddedBufferedBlockCipher.cs new file mode 100644 index 0000000..5d2f8cf --- /dev/null +++ b/bc-sharp-crypto/src/crypto/paddings/PaddedBufferedBlockCipher.cs @@ -0,0 +1,285 @@ +using System; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Paddings +{ + /** + * 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 + : BufferedBlockCipher + { + private readonly IBlockCipherPadding 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( + IBlockCipher cipher, + IBlockCipherPadding 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( + IBlockCipher cipher) + : this(cipher, new Pkcs7Padding()) { } + + /** + * 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. + * @exception ArgumentException if the parameters argument is + * inappropriate. + */ + public override void Init( + bool forEncryption, + ICipherParameters parameters) + { + this.forEncryption = forEncryption; + + SecureRandom initRandom = null; + if (parameters is ParametersWithRandom) + { + ParametersWithRandom p = (ParametersWithRandom)parameters; + initRandom = p.Random; + parameters = p.Parameters; + } + + Reset(); + padding.Init(initRandom); + cipher.Init(forEncryption, parameters); + } + + /** + * 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 override int GetOutputSize( + int length) + { + int total = length + 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 override int GetUpdateOutputSize( + int length) + { + int total = length + 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 necessary. + * + * @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 InvalidOperationException if the cipher isn't initialised. + */ + public override int ProcessByte( + byte input, + byte[] output, + int outOff) + { + int resultLen = 0; + + if (bufOff == buf.Length) + { + resultLen = cipher.ProcessBlock(buf, 0, output, outOff); + bufOff = 0; + } + + buf[bufOff++] = input; + + 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 InvalidOperationException if the cipher isn't initialised. + */ + public override int ProcessBytes( + byte[] input, + int inOff, + int length, + byte[] output, + int outOff) + { + if (length < 0) + { + throw new ArgumentException("Can't have a negative input length!"); + } + + int blockSize = GetBlockSize(); + int outLength = GetUpdateOutputSize(length); + + if (outLength > 0) + { + Check.OutputLength(output, outOff, outLength, "output buffer too short"); + } + + int resultLen = 0; + int gapLen = buf.Length - bufOff; + + if (length > gapLen) + { + Array.Copy(input, inOff, buf, bufOff, gapLen); + + resultLen += cipher.ProcessBlock(buf, 0, output, outOff); + + bufOff = 0; + length -= gapLen; + inOff += gapLen; + + while (length > buf.Length) + { + resultLen += cipher.ProcessBlock(input, inOff, output, outOff + resultLen); + + length -= blockSize; + inOff += blockSize; + } + } + + Array.Copy(input, inOff, buf, bufOff, length); + + bufOff += length; + + 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 InvalidOperationException if the underlying cipher is not + * initialised. + * @exception InvalidCipherTextException if padding is expected and not found. + */ + public override int DoFinal( + byte[] output, + int outOff) + { + int blockSize = cipher.GetBlockSize(); + int resultLen = 0; + + if (forEncryption) + { + if (bufOff == blockSize) + { + if ((outOff + 2 * blockSize) > output.Length) + { + Reset(); + + throw new OutputLengthException("output buffer too short"); + } + + resultLen = cipher.ProcessBlock(buf, 0, output, outOff); + bufOff = 0; + } + + padding.AddPadding(buf, bufOff); + + resultLen += cipher.ProcessBlock(buf, 0, output, 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); + + Array.Copy(buf, 0, output, outOff, resultLen); + } + finally + { + Reset(); + } + } + + return resultLen; + } + } + +} diff --git a/bc-sharp-crypto/src/crypto/paddings/Pkcs7Padding.cs b/bc-sharp-crypto/src/crypto/paddings/Pkcs7Padding.cs new file mode 100644 index 0000000..1158564 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/paddings/Pkcs7Padding.cs @@ -0,0 +1,76 @@ +using System; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Paddings +{ + /** + * A padder that adds Pkcs7/Pkcs5 padding to a block. + */ + public class Pkcs7Padding + : IBlockCipherPadding + { + /** + * Initialise the padder. + * + * @param random - a SecureRandom if available. + */ + public void Init( + SecureRandom random) + { + // nothing to do. + } + + /** + * Return the name of the algorithm the cipher implements. + * + * @return the name of the algorithm the cipher implements. + */ + public string PaddingName + { + get { return "PKCS7"; } + } + + /** + * add the pad bytes to the passed in block, returning the + * number of bytes added. + */ + public int AddPadding( + byte[] input, + int inOff) + { + byte code = (byte)(input.Length - inOff); + + while (inOff < input.Length) + { + input[inOff] = code; + inOff++; + } + + return code; + } + + /** + * return the number of pad bytes present in the block. + */ + public int PadCount( + byte[] input) + { + byte countAsByte = input[input.Length - 1]; + int count = countAsByte; + + if (count < 1 || count > input.Length) + throw new InvalidCipherTextException("pad block corrupted"); + + for (int i = 2; i <= count; i++) + { + if (input[input.Length - i] != countAsByte) + throw new InvalidCipherTextException("pad block corrupted"); + } + + return count; + } + } + +} diff --git a/bc-sharp-crypto/src/crypto/paddings/TbcPadding.cs b/bc-sharp-crypto/src/crypto/paddings/TbcPadding.cs new file mode 100644 index 0000000..74b64e8 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/paddings/TbcPadding.cs @@ -0,0 +1,79 @@ +using System; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Paddings +{ + + /// A padder that adds Trailing-Bit-Compliment padding to a block. + ///

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

+ ///
+ public class TbcPadding + : IBlockCipherPadding + { + /// Return the name of the algorithm the cipher implements. + /// the name of the algorithm the cipher implements. + /// + public string PaddingName + { + get { return "TBC"; } + } + + /// Initialise the padder. + /// - a SecureRandom if available. + /// + public virtual void Init(SecureRandom random) + { + // nothing to do. + } + + /// 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 virtual int AddPadding(byte[] input, int inOff) + { + int count = input.Length - inOff; + byte code; + + if (inOff > 0) + { + code = (byte)((input[inOff - 1] & 0x01) == 0?0xff:0x00); + } + else + { + code = (byte)((input[input.Length - 1] & 0x01) == 0?0xff:0x00); + } + + while (inOff < input.Length) + { + input[inOff] = code; + inOff++; + } + + return count; + } + + /// return the number of pad bytes present in the block. + public virtual int PadCount(byte[] input) + { + byte code = input[input.Length - 1]; + + int index = input.Length - 1; + while (index > 0 && input[index - 1] == code) + { + index--; + } + + return input.Length - index; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/paddings/X923Padding.cs b/bc-sharp-crypto/src/crypto/paddings/X923Padding.cs new file mode 100644 index 0000000..cc1b52b --- /dev/null +++ b/bc-sharp-crypto/src/crypto/paddings/X923Padding.cs @@ -0,0 +1,82 @@ +using System; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Paddings +{ + /** + * 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 + : IBlockCipherPadding + { + private SecureRandom random; + + /** + * Initialise the padder. + * + * @param random a SecureRandom if one is available. + */ + public void Init( + SecureRandom random) + { + this.random = random; + } + + /** + * Return the name of the algorithm the cipher implements. + * + * @return the name of the algorithm the cipher implements. + */ + public string PaddingName + { + get { return "X9.23"; } + } + + /** + * add the pad bytes to the passed in block, returning the + * number of bytes added. + */ + public int AddPadding( + byte[] input, + int inOff) + { + byte code = (byte)(input.Length - inOff); + + while (inOff < input.Length - 1) + { + if (random == null) + { + input[inOff] = 0; + } + else + { + input[inOff] = (byte)random.NextInt(); + } + inOff++; + } + + input[inOff] = code; + + return code; + } + + /** + * return the number of pad bytes present in the block. + */ + public int PadCount( + byte[] input) + { + int count = input[input.Length - 1] & 0xff; + + if (count > input.Length) + { + throw new InvalidCipherTextException("pad block corrupted"); + } + + return count; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/paddings/ZeroBytePadding.cs b/bc-sharp-crypto/src/crypto/paddings/ZeroBytePadding.cs new file mode 100644 index 0000000..0d55ca4 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/paddings/ZeroBytePadding.cs @@ -0,0 +1,68 @@ +using System; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Paddings +{ + + /// A padder that adds Null byte padding to a block. + public class ZeroBytePadding : IBlockCipherPadding + { + /// Return the name of the algorithm the cipher implements. + /// + /// + /// the name of the algorithm the cipher implements. + /// + public string PaddingName + { + get { return "ZeroBytePadding"; } + } + + /// Initialise the padder. + /// + /// + /// - a SecureRandom if available. + /// + public void Init(SecureRandom random) + { + // nothing to do. + } + + /// add the pad bytes to the passed in block, returning the + /// number of bytes added. + /// + public int AddPadding( + byte[] input, + int inOff) + { + int added = (input.Length - inOff); + + while (inOff < input.Length) + { + input[inOff] = (byte) 0; + inOff++; + } + + return added; + } + + /// return the number of pad bytes present in the block. + public int PadCount( + byte[] input) + { + int count = input.Length; + + while (count > 0) + { + if (input[count - 1] != 0) + { + break; + } + + count--; + } + + return input.Length - count; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/AEADParameters.cs b/bc-sharp-crypto/src/crypto/parameters/AEADParameters.cs new file mode 100644 index 0000000..825d6b7 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/AEADParameters.cs @@ -0,0 +1,65 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + public class AeadParameters + : ICipherParameters + { + private readonly byte[] associatedText; + private readonly byte[] nonce; + private readonly KeyParameter key; + private readonly int macSize; + + /** + * Base constructor. + * + * @param key key to be used by underlying cipher + * @param macSize macSize in bits + * @param nonce nonce to be used + */ + public AeadParameters(KeyParameter key, int macSize, byte[] nonce) + : this(key, macSize, nonce, null) + { + } + + /** + * Base constructor. + * + * @param key key to be used by underlying cipher + * @param macSize macSize in bits + * @param nonce nonce to be used + * @param associatedText 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 virtual KeyParameter Key + { + get { return key; } + } + + public virtual int MacSize + { + get { return macSize; } + } + + public virtual byte[] GetAssociatedText() + { + return associatedText; + } + + public virtual byte[] GetNonce() + { + return nonce; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/CcmParameters.cs b/bc-sharp-crypto/src/crypto/parameters/CcmParameters.cs new file mode 100644 index 0000000..d445908 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/CcmParameters.cs @@ -0,0 +1,26 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + [Obsolete("Use AeadParameters")] + public class CcmParameters + : 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) + : base(key, macSize, nonce, associatedText) + { + } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/DHKeyGenerationParameters.cs b/bc-sharp-crypto/src/crypto/parameters/DHKeyGenerationParameters.cs new file mode 100644 index 0000000..ab3e18f --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/DHKeyGenerationParameters.cs @@ -0,0 +1,31 @@ +using System; + +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + public class DHKeyGenerationParameters + : KeyGenerationParameters + { + private readonly DHParameters parameters; + + public DHKeyGenerationParameters( + SecureRandom random, + DHParameters parameters) + : base(random, GetStrength(parameters)) + { + this.parameters = parameters; + } + + public DHParameters Parameters + { + get { return parameters; } + } + + internal static int GetStrength( + DHParameters parameters) + { + return parameters.L != 0 ? parameters.L : parameters.P.BitLength; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/DHKeyParameters.cs b/bc-sharp-crypto/src/crypto/parameters/DHKeyParameters.cs new file mode 100644 index 0000000..1a5c138 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/DHKeyParameters.cs @@ -0,0 +1,76 @@ +using System; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + public class DHKeyParameters + : AsymmetricKeyParameter + { + private readonly DHParameters parameters; + private readonly DerObjectIdentifier algorithmOid; + + protected DHKeyParameters( + bool isPrivate, + DHParameters parameters) + : this(isPrivate, parameters, PkcsObjectIdentifiers.DhKeyAgreement) + { + } + + protected DHKeyParameters( + bool isPrivate, + DHParameters parameters, + DerObjectIdentifier algorithmOid) + : base(isPrivate) + { + // TODO Should we allow parameters to be null? + this.parameters = parameters; + this.algorithmOid = algorithmOid; + } + + public DHParameters Parameters + { + get { return parameters; } + } + + public DerObjectIdentifier AlgorithmOid + { + get { return algorithmOid; } + } + + public override bool Equals( + object obj) + { + if (obj == this) + return true; + + DHKeyParameters other = obj as DHKeyParameters; + + if (other == null) + return false; + + return Equals(other); + } + + protected bool Equals( + DHKeyParameters other) + { + return Platform.Equals(parameters, other.parameters) + && base.Equals(other); + } + + public override int GetHashCode() + { + int hc = base.GetHashCode(); + + if (parameters != null) + { + hc ^= parameters.GetHashCode(); + } + + return hc; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/DHParameters.cs b/bc-sharp-crypto/src/crypto/parameters/DHParameters.cs new file mode 100644 index 0000000..bdea124 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/DHParameters.cs @@ -0,0 +1,185 @@ +using System; + +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + public class DHParameters + : ICipherParameters + { + private const int DefaultMinimumLength = 160; + + private readonly BigInteger p, g, q, j; + private readonly int m, l; + private readonly DHValidationParameters validation; + + private static int GetDefaultMParam( + int lParam) + { + if (lParam == 0) + return DefaultMinimumLength; + + return System.Math.Min(lParam, DefaultMinimumLength); + } + + 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, DefaultMinimumLength, 0, j, validation) + { + } + + public DHParameters( + BigInteger p, + BigInteger g, + BigInteger q, + int m, + int l, + BigInteger j, + DHValidationParameters validation) + { + if (p == null) + throw new ArgumentNullException("p"); + if (g == null) + throw new ArgumentNullException("g"); + if (!p.TestBit(0)) + throw new ArgumentException("field must be an odd prime", "p"); + if (g.CompareTo(BigInteger.Two) < 0 + || g.CompareTo(p.Subtract(BigInteger.Two)) > 0) + throw new ArgumentException("generator must in the range [2, p - 2]", "g"); + if (q != null && q.BitLength >= p.BitLength) + throw new ArgumentException("q too big to be a factor of (p-1)", "q"); + if (m >= p.BitLength) + throw new ArgumentException("m value must be < bitlength of p", "m"); + if (l != 0) + { + // TODO Check this against the Java version, which has 'l > p.BitLength' here + if (l >= p.BitLength) + throw new ArgumentException("when l value specified, it must be less than bitlength(p)", "l"); + if (l < m) + throw new ArgumentException("when l value specified, it may not be less than m value", "l"); + } + if (j != null && j.CompareTo(BigInteger.Two) < 0) + throw new ArgumentException("subgroup factor must be >= 2", "j"); + + // TODO If q, j both provided, validate p = jq + 1 ? + + this.p = p; + this.g = g; + this.q = q; + this.m = m; + this.l = l; + this.j = j; + this.validation = validation; + } + + public BigInteger P + { + get { return p; } + } + + public BigInteger G + { + get { return g; } + } + + public BigInteger Q + { + get { return q; } + } + + public BigInteger J + { + get { return j; } + } + + /// The minimum bitlength of the private value. + public int M + { + get { return m; } + } + + /// The bitlength of the private value. + public int L + { + get { return l; } + } + + public DHValidationParameters ValidationParameters + { + get { return validation; } + } + + public override bool Equals( + object obj) + { + if (obj == this) + return true; + + DHParameters other = obj as DHParameters; + + if (other == null) + return false; + + return Equals(other); + } + + protected virtual bool Equals( + DHParameters other) + { + return p.Equals(other.p) + && g.Equals(other.g) + && Platform.Equals(q, other.q); + } + + public override int GetHashCode() + { + int hc = p.GetHashCode() ^ g.GetHashCode(); + + if (q != null) + { + hc ^= q.GetHashCode(); + } + + return hc; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/DHPrivateKeyParameters.cs b/bc-sharp-crypto/src/crypto/parameters/DHPrivateKeyParameters.cs new file mode 100644 index 0000000..fc724df --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/DHPrivateKeyParameters.cs @@ -0,0 +1,60 @@ +using System; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + public class DHPrivateKeyParameters + : DHKeyParameters + { + private readonly BigInteger x; + + public DHPrivateKeyParameters( + BigInteger x, + DHParameters parameters) + : base(true, parameters) + { + this.x = x; + } + + public DHPrivateKeyParameters( + BigInteger x, + DHParameters parameters, + DerObjectIdentifier algorithmOid) + : base(true, parameters, algorithmOid) + { + this.x = x; + } + + public BigInteger X + { + get { return x; } + } + + public override bool Equals( + object obj) + { + if (obj == this) + return true; + + DHPrivateKeyParameters other = obj as DHPrivateKeyParameters; + + if (other == null) + return false; + + return Equals(other); + } + + protected bool Equals( + DHPrivateKeyParameters other) + { + return x.Equals(other.x) && base.Equals(other); + } + + public override int GetHashCode() + { + return x.GetHashCode() ^ base.GetHashCode(); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/DHPublicKeyParameters.cs b/bc-sharp-crypto/src/crypto/parameters/DHPublicKeyParameters.cs new file mode 100644 index 0000000..e7aeeff --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/DHPublicKeyParameters.cs @@ -0,0 +1,79 @@ +using System; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + public class DHPublicKeyParameters + : DHKeyParameters + { + private static BigInteger Validate(BigInteger y, DHParameters dhParams) + { + if (y == null) + throw new ArgumentNullException("y"); + + // TLS check + if (y.CompareTo(BigInteger.Two) < 0 || y.CompareTo(dhParams.P.Subtract(BigInteger.Two)) > 0) + throw new ArgumentException("invalid DH public key", "y"); + + // we can't validate without Q. + if (dhParams.Q != null + && !y.ModPow(dhParams.Q, dhParams.P).Equals(BigInteger.One)) + { + throw new ArgumentException("y value does not appear to be in correct group", "y"); + } + + return y; + } + + private readonly BigInteger y; + + public DHPublicKeyParameters( + BigInteger y, + DHParameters parameters) + : base(false, parameters) + { + this.y = Validate(y, parameters); + } + + public DHPublicKeyParameters( + BigInteger y, + DHParameters parameters, + DerObjectIdentifier algorithmOid) + : base(false, parameters, algorithmOid) + { + this.y = Validate(y, parameters); + } + + public virtual BigInteger Y + { + get { return y; } + } + + public override bool Equals( + object obj) + { + if (obj == this) + return true; + + DHPublicKeyParameters other = obj as DHPublicKeyParameters; + + if (other == null) + return false; + + return Equals(other); + } + + protected bool Equals( + DHPublicKeyParameters other) + { + return y.Equals(other.y) && base.Equals(other); + } + + public override int GetHashCode() + { + return y.GetHashCode() ^ base.GetHashCode(); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/DHValidationParameters.cs b/bc-sharp-crypto/src/crypto/parameters/DHValidationParameters.cs new file mode 100644 index 0000000..50c0739 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/DHValidationParameters.cs @@ -0,0 +1,59 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + public class DHValidationParameters + { + private readonly byte[] seed; + private readonly int counter; + + public DHValidationParameters( + byte[] seed, + int counter) + { + if (seed == null) + throw new ArgumentNullException("seed"); + + this.seed = (byte[]) seed.Clone(); + this.counter = counter; + } + + public byte[] GetSeed() + { + return (byte[]) seed.Clone(); + } + + public int Counter + { + get { return counter; } + } + + public override bool Equals( + object obj) + { + if (obj == this) + return true; + + DHValidationParameters other = obj as DHValidationParameters; + + if (other == null) + return false; + + return Equals(other); + } + + protected bool Equals( + DHValidationParameters other) + { + return counter == other.counter + && Arrays.AreEqual(this.seed, other.seed); + } + + public override int GetHashCode() + { + return counter.GetHashCode() ^ Arrays.GetHashCode(seed); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/DSAParameterGenerationParameters.cs b/bc-sharp-crypto/src/crypto/parameters/DSAParameterGenerationParameters.cs new file mode 100644 index 0000000..7427574 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/DSAParameterGenerationParameters.cs @@ -0,0 +1,74 @@ +using System; + +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + public class DsaParameterGenerationParameters + { + public const int DigitalSignatureUsage = 1; + public const int KeyEstablishmentUsage = 2; + + private readonly int l; + private readonly int n; + private readonly int certainty; + private readonly SecureRandom random; + private readonly int usageIndex; + + /** + * Construct without a usage index, this will do a random construction of G. + * + * @param L desired length of prime P in bits (the effective key size). + * @param N desired length of prime Q in bits. + * @param certainty certainty level for prime number generation. + * @param random the source of randomness to use. + */ + public DsaParameterGenerationParameters(int L, int N, int certainty, SecureRandom random) + : this(L, N, certainty, random, -1) + { + } + + /** + * Construct for a specific usage index - this has the effect of using verifiable canonical generation of G. + * + * @param L desired length of prime P in bits (the effective key size). + * @param N desired length of prime Q in bits. + * @param certainty certainty level for prime number generation. + * @param random the source of randomness to use. + * @param usageIndex a valid usage index. + */ + public DsaParameterGenerationParameters(int L, int N, int certainty, SecureRandom random, int usageIndex) + { + this.l = L; + this.n = N; + this.certainty = certainty; + this.random = random; + this.usageIndex = usageIndex; + } + + public virtual int L + { + get { return l; } + } + + public virtual int N + { + get { return n; } + } + + public virtual int UsageIndex + { + get { return usageIndex; } + } + + public virtual int Certainty + { + get { return certainty; } + } + + public virtual SecureRandom Random + { + get { return random; } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/DesEdeParameters.cs b/bc-sharp-crypto/src/crypto/parameters/DesEdeParameters.cs new file mode 100644 index 0000000..6be56fb --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/DesEdeParameters.cs @@ -0,0 +1,140 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + public class DesEdeParameters + : DesParameters + { + /* + * DES-EDE Key length in bytes. + */ + public const int DesEdeKeyLength = 24; + + private static byte[] FixKey( + byte[] key, + int keyOff, + int keyLen) + { + byte[] tmp = new byte[24]; + + switch (keyLen) + { + case 16: + Array.Copy(key, keyOff, tmp, 0, 16); + Array.Copy(key, keyOff, tmp, 16, 8); + break; + case 24: + Array.Copy(key, keyOff, tmp, 0, 24); + break; + default: + throw new ArgumentException("Bad length for DESede key: " + keyLen, "keyLen"); + } + + if (IsWeakKey(tmp)) + throw new ArgumentException("attempt to create weak DESede key"); + + return tmp; + } + + public DesEdeParameters( + byte[] key) + : base(FixKey(key, 0, key.Length)) + { + } + + public DesEdeParameters( + byte[] key, + int keyOff, + int keyLen) + : base(FixKey(key, keyOff, keyLen)) + { + } + + /** + * 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 bool IsWeakKey( + byte[] key, + int offset, + int length) + { + for (int i = offset; i < length; i += DesKeyLength) + { + 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 new bool IsWeakKey( + byte[] key, + int offset) + { + return IsWeakKey(key, offset, key.Length - offset); + } + + public static new bool IsWeakKey( + byte[] key) + { + return IsWeakKey(key, 0, key.Length); + } + + /** + * return true if the passed in key is a real 2/3 part DES-EDE key. + * + * @param key bytes making up the key + * @param offset offset into the byte array the key starts at + */ + public static bool IsRealEdeKey(byte[] key, int offset) + { + return key.Length == 16 ? IsReal2Key(key, offset) : IsReal3Key(key, offset); + } + + /** + * return true if the passed in key is a real 2 part DES-EDE key. + * + * @param key bytes making up the key + * @param offset offset into the byte array the key starts at + */ + public static bool IsReal2Key(byte[] key, int offset) + { + bool isValid = false; + for (int i = offset; i != offset + 8; i++) + { + isValid |= (key[i] != key[i + 8]); + } + return isValid; + } + + /** + * return true if the passed in key is a real 3 part DES-EDE key. + * + * @param key bytes making up the key + * @param offset offset into the byte array the key starts at + */ + public static bool IsReal3Key(byte[] key, int offset) + { + bool diff12 = false, diff13 = false, diff23 = false; + for (int i = offset; i != offset + 8; i++) + { + diff12 |= (key[i] != key[i + 8]); + diff13 |= (key[i] != key[i + 16]); + diff23 |= (key[i + 8] != key[i + 16]); + } + return diff12 && diff13 && diff23; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/DesParameters.cs b/bc-sharp-crypto/src/crypto/parameters/DesParameters.cs new file mode 100644 index 0000000..a1f67e2 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/DesParameters.cs @@ -0,0 +1,139 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + public class DesParameters + : KeyParameter + { + public DesParameters( + byte[] key) + : base(key) + { + if (IsWeakKey(key)) + throw new ArgumentException("attempt to create weak DES key"); + } + + public DesParameters( + byte[] key, + int keyOff, + int keyLen) + : base(key, keyOff, keyLen) + { + if (IsWeakKey(key, keyOff)) + throw new ArgumentException("attempt to create weak DES key"); + } + + /* + * DES Key Length in bytes. + */ + public const int DesKeyLength = 8; + + /* + * Table of weak and semi-weak keys taken from Schneier pp281 + */ + private const int N_DES_WEAK_KEYS = 16; + + private static readonly 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 bool IsWeakKey( + byte[] key, + int offset) + { + if (key.Length - offset < DesKeyLength) + throw new ArgumentException("key material too short."); + + //nextkey: + for (int i = 0; i < N_DES_WEAK_KEYS; i++) + { + bool unmatch = false; + for (int j = 0; j < DesKeyLength; j++) + { + if (key[j + offset] != DES_weak_keys[i * DesKeyLength + j]) + { + //continue nextkey; + unmatch = true; + break; + } + } + + if (!unmatch) + { + return true; + } + } + + return false; + } + + public static bool IsWeakKey( + byte[] key) + { + return IsWeakKey(key, 0); + } + + public static byte SetOddParity(byte b) + { + uint parity = b ^ 1U; + parity ^= (parity >> 4); + parity ^= (parity >> 2); + parity ^= (parity >> 1); + parity &= 1U; + + return (byte)(b ^ parity); + } + + /** + * 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++) + { + bytes[i] = SetOddParity(bytes[i]); + } + } + + public static void SetOddParity(byte[] bytes, int off, int len) + { + for (int i = 0; i < len; i++) + { + bytes[off + i] = SetOddParity(bytes[off + i]); + } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/DsaKeyGenerationParameters.cs b/bc-sharp-crypto/src/crypto/parameters/DsaKeyGenerationParameters.cs new file mode 100644 index 0000000..86d6f5b --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/DsaKeyGenerationParameters.cs @@ -0,0 +1,26 @@ +using System; + +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + public class DsaKeyGenerationParameters + : KeyGenerationParameters + { + private readonly DsaParameters parameters; + + public DsaKeyGenerationParameters( + SecureRandom random, + DsaParameters parameters) + : base(random, parameters.P.BitLength - 1) + { + this.parameters = parameters; + } + + public DsaParameters Parameters + { + get { return parameters; } + } + } + +} diff --git a/bc-sharp-crypto/src/crypto/parameters/DsaKeyParameters.cs b/bc-sharp-crypto/src/crypto/parameters/DsaKeyParameters.cs new file mode 100644 index 0000000..5fe6d7a --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/DsaKeyParameters.cs @@ -0,0 +1,59 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + public abstract class DsaKeyParameters + : AsymmetricKeyParameter + { + private readonly DsaParameters parameters; + + protected DsaKeyParameters( + bool isPrivate, + DsaParameters parameters) + : base(isPrivate) + { + // Note: parameters may be null + this.parameters = parameters; + } + + public DsaParameters Parameters + { + get { return parameters; } + } + + public override bool Equals( + object obj) + { + if (obj == this) + return true; + + DsaKeyParameters other = obj as DsaKeyParameters; + + if (other == null) + return false; + + return Equals(other); + } + + protected bool Equals( + DsaKeyParameters other) + { + return Platform.Equals(parameters, other.parameters) + && base.Equals(other); + } + + public override int GetHashCode() + { + int hc = base.GetHashCode(); + + if (parameters != null) + { + hc ^= parameters.GetHashCode(); + } + + return hc; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/DsaParameters.cs b/bc-sharp-crypto/src/crypto/parameters/DsaParameters.cs new file mode 100644 index 0000000..50d080e --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/DsaParameters.cs @@ -0,0 +1,85 @@ +using System; + +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + public class DsaParameters + : ICipherParameters + { + private readonly BigInteger p, q , g; + private readonly DsaValidationParameters validation; + + public DsaParameters( + BigInteger p, + BigInteger q, + BigInteger g) + : this(p, q, g, null) + { + } + + public DsaParameters( + BigInteger p, + BigInteger q, + BigInteger g, + DsaValidationParameters parameters) + { + if (p == null) + throw new ArgumentNullException("p"); + if (q == null) + throw new ArgumentNullException("q"); + if (g == null) + throw new ArgumentNullException("g"); + + this.p = p; + this.q = q; + this.g = g; + this.validation = parameters; + } + + public BigInteger P + { + get { return p; } + } + + public BigInteger Q + { + get { return q; } + } + + public BigInteger G + { + get { return g; } + } + + public DsaValidationParameters ValidationParameters + { + get { return validation; } + } + + public override bool Equals( + object obj) + { + if (obj == this) + return true; + + DsaParameters other = obj as DsaParameters; + + if (other == null) + return false; + + return Equals(other); + } + + protected bool Equals( + DsaParameters other) + { + return p.Equals(other.p) && q.Equals(other.q) && g.Equals(other.g); + } + + public override int GetHashCode() + { + return p.GetHashCode() ^ q.GetHashCode() ^ g.GetHashCode(); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/DsaPrivateKeyParameters.cs b/bc-sharp-crypto/src/crypto/parameters/DsaPrivateKeyParameters.cs new file mode 100644 index 0000000..2abdd0e --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/DsaPrivateKeyParameters.cs @@ -0,0 +1,53 @@ +using System; + +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + public class DsaPrivateKeyParameters + : DsaKeyParameters + { + private readonly BigInteger x; + + public DsaPrivateKeyParameters( + BigInteger x, + DsaParameters parameters) + : base(true, parameters) + { + if (x == null) + throw new ArgumentNullException("x"); + + this.x = x; + } + + public BigInteger X + { + get { return x; } + } + + public override bool Equals( + object obj) + { + if (obj == this) + return true; + + DsaPrivateKeyParameters other = obj as DsaPrivateKeyParameters; + + if (other == null) + return false; + + return Equals(other); + } + + protected bool Equals( + DsaPrivateKeyParameters other) + { + return x.Equals(other.x) && base.Equals(other); + } + + public override int GetHashCode() + { + return x.GetHashCode() ^ base.GetHashCode(); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/DsaPublicKeyParameters.cs b/bc-sharp-crypto/src/crypto/parameters/DsaPublicKeyParameters.cs new file mode 100644 index 0000000..3a81bfd --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/DsaPublicKeyParameters.cs @@ -0,0 +1,68 @@ +using System; + +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + public class DsaPublicKeyParameters + : DsaKeyParameters + { + private static BigInteger Validate(BigInteger y, DsaParameters parameters) + { + // we can't validate without params, fortunately we can't use the key either... + if (parameters != null) + { + if (y.CompareTo(BigInteger.Two) < 0 + || y.CompareTo(parameters.P.Subtract(BigInteger.Two)) > 0 + || !y.ModPow(parameters.Q, parameters.P).Equals(BigInteger.One)) + { + throw new ArgumentException("y value does not appear to be in correct group"); + } + } + + return y; + } + + private readonly BigInteger y; + + public DsaPublicKeyParameters( + BigInteger y, + DsaParameters parameters) + : base(false, parameters) + { + if (y == null) + throw new ArgumentNullException("y"); + + this.y = Validate(y, parameters); + } + + public BigInteger Y + { + get { return y; } + } + + public override bool Equals(object obj) + { + if (obj == this) + return true; + + DsaPublicKeyParameters other = obj as DsaPublicKeyParameters; + + if (other == null) + return false; + + return Equals(other); + } + + protected bool Equals( + DsaPublicKeyParameters other) + { + return y.Equals(other.y) && base.Equals(other); + } + + public override int GetHashCode() + { + return y.GetHashCode() ^ base.GetHashCode(); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/DsaValidationParameters.cs b/bc-sharp-crypto/src/crypto/parameters/DsaValidationParameters.cs new file mode 100644 index 0000000..c2f84c7 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/DsaValidationParameters.cs @@ -0,0 +1,72 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + public class DsaValidationParameters + { + private readonly byte[] seed; + private readonly int counter; + private readonly int usageIndex; + + public DsaValidationParameters(byte[] seed, int counter) + : this(seed, counter, -1) + { + } + + public DsaValidationParameters( + byte[] seed, + int counter, + int usageIndex) + { + if (seed == null) + throw new ArgumentNullException("seed"); + + this.seed = (byte[]) seed.Clone(); + this.counter = counter; + this.usageIndex = usageIndex; + } + + public virtual byte[] GetSeed() + { + return (byte[]) seed.Clone(); + } + + public virtual int Counter + { + get { return counter; } + } + + public virtual int UsageIndex + { + get { return usageIndex; } + } + + public override bool Equals( + object obj) + { + if (obj == this) + return true; + + DsaValidationParameters other = obj as DsaValidationParameters; + + if (other == null) + return false; + + return Equals(other); + } + + protected virtual bool Equals( + DsaValidationParameters other) + { + return counter == other.counter + && Arrays.AreEqual(seed, other.seed); + } + + public override int GetHashCode() + { + return counter.GetHashCode() ^ Arrays.GetHashCode(seed); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/ECDomainParameters.cs b/bc-sharp-crypto/src/crypto/parameters/ECDomainParameters.cs new file mode 100644 index 0000000..732fbdf --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/ECDomainParameters.cs @@ -0,0 +1,117 @@ +using System; + +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Math.EC; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + public class ECDomainParameters + { + internal ECCurve curve; + internal byte[] seed; + internal ECPoint g; + internal BigInteger n; + internal BigInteger h; + + public ECDomainParameters( + ECCurve curve, + ECPoint g, + BigInteger n) + : this(curve, g, n, BigInteger.One) + { + } + + public ECDomainParameters( + ECCurve curve, + ECPoint g, + BigInteger n, + BigInteger h) + : this(curve, g, n, h, null) + { + } + + public ECDomainParameters( + ECCurve curve, + ECPoint g, + BigInteger n, + BigInteger h, + byte[] seed) + { + if (curve == null) + throw new ArgumentNullException("curve"); + if (g == null) + throw new ArgumentNullException("g"); + if (n == null) + throw new ArgumentNullException("n"); + if (h == null) + throw new ArgumentNullException("h"); + + this.curve = curve; + this.g = g.Normalize(); + this.n = n; + this.h = h; + this.seed = Arrays.Clone(seed); + } + + public ECCurve Curve + { + get { return curve; } + } + + public ECPoint G + { + get { return g; } + } + + public BigInteger N + { + get { return n; } + } + + public BigInteger H + { + get { return h; } + } + + public byte[] GetSeed() + { + return Arrays.Clone(seed); + } + + public override bool Equals( + object obj) + { + if (obj == this) + return true; + + ECDomainParameters other = obj as ECDomainParameters; + + if (other == null) + return false; + + return Equals(other); + } + + protected virtual bool Equals( + ECDomainParameters other) + { + return curve.Equals(other.curve) + && g.Equals(other.g) + && n.Equals(other.n) + && h.Equals(other.h); + } + + public override int GetHashCode() + { + int hc = curve.GetHashCode(); + hc *= 37; + hc ^= g.GetHashCode(); + hc *= 37; + hc ^= n.GetHashCode(); + hc *= 37; + hc ^= h.GetHashCode(); + return hc; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/ECKeyGenerationParameters.cs b/bc-sharp-crypto/src/crypto/parameters/ECKeyGenerationParameters.cs new file mode 100644 index 0000000..9b2b988 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/ECKeyGenerationParameters.cs @@ -0,0 +1,41 @@ +using System; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.CryptoPro; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + public class ECKeyGenerationParameters + : KeyGenerationParameters + { + private readonly ECDomainParameters domainParams; + private readonly DerObjectIdentifier publicKeyParamSet; + + public ECKeyGenerationParameters( + ECDomainParameters domainParameters, + SecureRandom random) + : base(random, domainParameters.N.BitLength) + { + this.domainParams = domainParameters; + } + + public ECKeyGenerationParameters( + DerObjectIdentifier publicKeyParamSet, + SecureRandom random) + : this(ECKeyParameters.LookupParameters(publicKeyParamSet), random) + { + this.publicKeyParamSet = publicKeyParamSet; + } + + public ECDomainParameters DomainParameters + { + get { return domainParams; } + } + + public DerObjectIdentifier PublicKeyParamSet + { + get { return publicKeyParamSet; } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/ECKeyParameters.cs b/bc-sharp-crypto/src/crypto/parameters/ECKeyParameters.cs new file mode 100644 index 0000000..70b3543 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/ECKeyParameters.cs @@ -0,0 +1,136 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.CryptoPro; +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + public abstract class ECKeyParameters + : AsymmetricKeyParameter + { + private static readonly string[] algorithms = { "EC", "ECDSA", "ECDH", "ECDHC", "ECGOST3410", "ECMQV" }; + + private readonly string algorithm; + private readonly ECDomainParameters parameters; + private readonly DerObjectIdentifier publicKeyParamSet; + + protected ECKeyParameters( + string algorithm, + bool isPrivate, + ECDomainParameters parameters) + : base(isPrivate) + { + if (algorithm == null) + throw new ArgumentNullException("algorithm"); + if (parameters == null) + throw new ArgumentNullException("parameters"); + + this.algorithm = VerifyAlgorithmName(algorithm); + this.parameters = parameters; + } + + protected ECKeyParameters( + string algorithm, + bool isPrivate, + DerObjectIdentifier publicKeyParamSet) + : base(isPrivate) + { + if (algorithm == null) + throw new ArgumentNullException("algorithm"); + if (publicKeyParamSet == null) + throw new ArgumentNullException("publicKeyParamSet"); + + this.algorithm = VerifyAlgorithmName(algorithm); + this.parameters = LookupParameters(publicKeyParamSet); + this.publicKeyParamSet = publicKeyParamSet; + } + + public string AlgorithmName + { + get { return algorithm; } + } + + public ECDomainParameters Parameters + { + get { return parameters; } + } + + public DerObjectIdentifier PublicKeyParamSet + { + get { return publicKeyParamSet; } + } + + public override bool Equals( + object obj) + { + if (obj == this) + return true; + + ECDomainParameters other = obj as ECDomainParameters; + + if (other == null) + return false; + + return Equals(other); + } + + protected bool Equals( + ECKeyParameters other) + { + return parameters.Equals(other.parameters) && base.Equals(other); + } + + public override int GetHashCode() + { + return parameters.GetHashCode() ^ base.GetHashCode(); + } + + internal ECKeyGenerationParameters CreateKeyGenerationParameters( + SecureRandom random) + { + if (publicKeyParamSet != null) + { + return new ECKeyGenerationParameters(publicKeyParamSet, random); + } + + return new ECKeyGenerationParameters(parameters, random); + } + + internal static string VerifyAlgorithmName(string algorithm) + { + string upper = Platform.ToUpperInvariant(algorithm); + if (Array.IndexOf(algorithms, algorithm, 0, algorithms.Length) < 0) + throw new ArgumentException("unrecognised algorithm: " + algorithm, "algorithm"); + return upper; + } + + internal static ECDomainParameters LookupParameters( + DerObjectIdentifier publicKeyParamSet) + { + if (publicKeyParamSet == null) + throw new ArgumentNullException("publicKeyParamSet"); + + ECDomainParameters p = ECGost3410NamedCurves.GetByOid(publicKeyParamSet); + + if (p == null) + { + X9ECParameters x9 = ECKeyPairGenerator.FindECCurveByOid(publicKeyParamSet); + + if (x9 == null) + { + throw new ArgumentException("OID is not a valid public key parameter set", "publicKeyParamSet"); + } + + p = new ECDomainParameters(x9.Curve, x9.G, x9.N, x9.H, x9.GetSeed()); + } + + return p; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/ECPrivateKeyParameters.cs b/bc-sharp-crypto/src/crypto/parameters/ECPrivateKeyParameters.cs new file mode 100644 index 0000000..4d0fa1f --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/ECPrivateKeyParameters.cs @@ -0,0 +1,87 @@ +using System; +using System.Globalization; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + public class ECPrivateKeyParameters + : ECKeyParameters + { + private readonly BigInteger d; + + public ECPrivateKeyParameters( + BigInteger d, + ECDomainParameters parameters) + : this("EC", d, parameters) + { + } + + [Obsolete("Use version with explicit 'algorithm' parameter")] + public ECPrivateKeyParameters( + BigInteger d, + DerObjectIdentifier publicKeyParamSet) + : base("ECGOST3410", true, publicKeyParamSet) + { + if (d == null) + throw new ArgumentNullException("d"); + + this.d = d; + } + + public ECPrivateKeyParameters( + string algorithm, + BigInteger d, + ECDomainParameters parameters) + : base(algorithm, true, parameters) + { + if (d == null) + throw new ArgumentNullException("d"); + + this.d = d; + } + + public ECPrivateKeyParameters( + string algorithm, + BigInteger d, + DerObjectIdentifier publicKeyParamSet) + : base(algorithm, true, publicKeyParamSet) + { + if (d == null) + throw new ArgumentNullException("d"); + + this.d = d; + } + + public BigInteger D + { + get { return d; } + } + + public override bool Equals( + object obj) + { + if (obj == this) + return true; + + ECPrivateKeyParameters other = obj as ECPrivateKeyParameters; + + if (other == null) + return false; + + return Equals(other); + } + + protected bool Equals( + ECPrivateKeyParameters other) + { + return d.Equals(other.d) && base.Equals(other); + } + + public override int GetHashCode() + { + return d.GetHashCode() ^ base.GetHashCode(); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/ECPublicKeyParameters.cs b/bc-sharp-crypto/src/crypto/parameters/ECPublicKeyParameters.cs new file mode 100644 index 0000000..474e5d8 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/ECPublicKeyParameters.cs @@ -0,0 +1,101 @@ +using System; +using System.Globalization; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Math.EC; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + public class ECPublicKeyParameters + : ECKeyParameters + { + private static ECPoint Validate(ECPoint q) + { + if (q == null) + throw new ArgumentNullException("q"); + if (q.IsInfinity) + throw new ArgumentException("point at infinity", "q"); + + q = q.Normalize(); + + if (!q.IsValid()) + throw new ArgumentException("point not on curve", "q"); + + return q; + } + + private readonly ECPoint q; + + public ECPublicKeyParameters( + ECPoint q, + ECDomainParameters parameters) + : this("EC", q, parameters) + { + } + + [Obsolete("Use version with explicit 'algorithm' parameter")] + public ECPublicKeyParameters( + ECPoint q, + DerObjectIdentifier publicKeyParamSet) + : base("ECGOST3410", false, publicKeyParamSet) + { + if (q == null) + throw new ArgumentNullException("q"); + + this.q = Validate(q); + } + + public ECPublicKeyParameters( + string algorithm, + ECPoint q, + ECDomainParameters parameters) + : base(algorithm, false, parameters) + { + if (q == null) + throw new ArgumentNullException("q"); + + this.q = Validate(q); + } + + public ECPublicKeyParameters( + string algorithm, + ECPoint q, + DerObjectIdentifier publicKeyParamSet) + : base(algorithm, false, publicKeyParamSet) + { + if (q == null) + throw new ArgumentNullException("q"); + + this.q = Validate(q); + } + + public ECPoint Q + { + get { return q; } + } + + public override bool Equals(object obj) + { + if (obj == this) + return true; + + ECPublicKeyParameters other = obj as ECPublicKeyParameters; + + if (other == null) + return false; + + return Equals(other); + } + + protected bool Equals( + ECPublicKeyParameters other) + { + return q.Equals(other.q) && base.Equals(other); + } + + public override int GetHashCode() + { + return q.GetHashCode() ^ base.GetHashCode(); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/ElGamalKeyGenerationParameters.cs b/bc-sharp-crypto/src/crypto/parameters/ElGamalKeyGenerationParameters.cs new file mode 100644 index 0000000..40ca70d --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/ElGamalKeyGenerationParameters.cs @@ -0,0 +1,31 @@ +using System; + +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + public class ElGamalKeyGenerationParameters + : KeyGenerationParameters + { + private readonly ElGamalParameters parameters; + + public ElGamalKeyGenerationParameters( + SecureRandom random, + ElGamalParameters parameters) + : base(random, GetStrength(parameters)) + { + this.parameters = parameters; + } + + public ElGamalParameters Parameters + { + get { return parameters; } + } + + internal static int GetStrength( + ElGamalParameters parameters) + { + return parameters.L != 0 ? parameters.L : parameters.P.BitLength; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/ElGamalKeyParameters.cs b/bc-sharp-crypto/src/crypto/parameters/ElGamalKeyParameters.cs new file mode 100644 index 0000000..8b6e279 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/ElGamalKeyParameters.cs @@ -0,0 +1,59 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + public class ElGamalKeyParameters + : AsymmetricKeyParameter + { + private readonly ElGamalParameters parameters; + + protected ElGamalKeyParameters( + bool isPrivate, + ElGamalParameters parameters) + : base(isPrivate) + { + // TODO Should we allow 'parameters' to be null? + this.parameters = parameters; + } + + public ElGamalParameters Parameters + { + get { return parameters; } + } + + public override bool Equals( + object obj) + { + if (obj == this) + return true; + + ElGamalKeyParameters other = obj as ElGamalKeyParameters; + + if (other == null) + return false; + + return Equals(other); + } + + protected bool Equals( + ElGamalKeyParameters other) + { + return Platform.Equals(parameters, other.parameters) + && base.Equals(other); + } + + public override int GetHashCode() + { + int hc = base.GetHashCode(); + + if (parameters != null) + { + hc ^= parameters.GetHashCode(); + } + + return hc; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/ElGamalParameters.cs b/bc-sharp-crypto/src/crypto/parameters/ElGamalParameters.cs new file mode 100644 index 0000000..ab6d3e7 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/ElGamalParameters.cs @@ -0,0 +1,81 @@ +using System; + +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + public class ElGamalParameters + : ICipherParameters + { + private readonly BigInteger p, g; + private readonly int l; + + public ElGamalParameters( + BigInteger p, + BigInteger g) + : this(p, g, 0) + { + } + + public ElGamalParameters( + BigInteger p, + BigInteger g, + int l) + { + if (p == null) + throw new ArgumentNullException("p"); + if (g == null) + throw new ArgumentNullException("g"); + + this.p = p; + this.g = g; + this.l = l; + } + + public BigInteger P + { + get { return p; } + } + + /** + * return the generator - g + */ + public BigInteger G + { + get { return g; } + } + + /** + * return private value limit - l + */ + public int L + { + get { return l; } + } + + public override bool Equals( + object obj) + { + if (obj == this) + return true; + + ElGamalParameters other = obj as ElGamalParameters; + + if (other == null) + return false; + + return Equals(other); + } + + protected bool Equals( + ElGamalParameters other) + { + return p.Equals(other.p) && g.Equals(other.g) && l == other.l; + } + + public override int GetHashCode() + { + return p.GetHashCode() ^ g.GetHashCode() ^ l; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/ElGamalPrivateKeyParameters.cs b/bc-sharp-crypto/src/crypto/parameters/ElGamalPrivateKeyParameters.cs new file mode 100644 index 0000000..6363f2b --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/ElGamalPrivateKeyParameters.cs @@ -0,0 +1,53 @@ +using System; + +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + public class ElGamalPrivateKeyParameters + : ElGamalKeyParameters + { + private readonly BigInteger x; + + public ElGamalPrivateKeyParameters( + BigInteger x, + ElGamalParameters parameters) + : base(true, parameters) + { + if (x == null) + throw new ArgumentNullException("x"); + + this.x = x; + } + + public BigInteger X + { + get { return x; } + } + + public override bool Equals( + object obj) + { + if (obj == this) + return true; + + ElGamalPrivateKeyParameters other = obj as ElGamalPrivateKeyParameters; + + if (other == null) + return false; + + return Equals(other); + } + + protected bool Equals( + ElGamalPrivateKeyParameters other) + { + return other.x.Equals(x) && base.Equals(other); + } + + public override int GetHashCode() + { + return x.GetHashCode() ^ base.GetHashCode(); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/ElGamalPublicKeyParameters.cs b/bc-sharp-crypto/src/crypto/parameters/ElGamalPublicKeyParameters.cs new file mode 100644 index 0000000..25ac625 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/ElGamalPublicKeyParameters.cs @@ -0,0 +1,53 @@ +using System; + +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + public class ElGamalPublicKeyParameters + : ElGamalKeyParameters + { + private readonly BigInteger y; + + public ElGamalPublicKeyParameters( + BigInteger y, + ElGamalParameters parameters) + : base(false, parameters) + { + if (y == null) + throw new ArgumentNullException("y"); + + this.y = y; + } + + public BigInteger Y + { + get { return y; } + } + + public override bool Equals( + object obj) + { + if (obj == this) + return true; + + ElGamalPublicKeyParameters other = obj as ElGamalPublicKeyParameters; + + if (other == null) + return false; + + return Equals(other); + } + + protected bool Equals( + ElGamalPublicKeyParameters other) + { + return y.Equals(other.y) && base.Equals(other); + } + + public override int GetHashCode() + { + return y.GetHashCode() ^ base.GetHashCode(); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/GOST3410KeyGenerationParameters.cs b/bc-sharp-crypto/src/crypto/parameters/GOST3410KeyGenerationParameters.cs new file mode 100644 index 0000000..b06a5d8 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/GOST3410KeyGenerationParameters.cs @@ -0,0 +1,55 @@ +using System; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.CryptoPro; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + public class Gost3410KeyGenerationParameters + : KeyGenerationParameters + { + private readonly Gost3410Parameters parameters; + private readonly DerObjectIdentifier publicKeyParamSet; + + public Gost3410KeyGenerationParameters( + SecureRandom random, + Gost3410Parameters parameters) + : base(random, parameters.P.BitLength - 1) + { + this.parameters = parameters; + } + + public Gost3410KeyGenerationParameters( + SecureRandom random, + DerObjectIdentifier publicKeyParamSet) + : this(random, LookupParameters(publicKeyParamSet)) + { + this.publicKeyParamSet = publicKeyParamSet; + } + + public Gost3410Parameters Parameters + { + get { return parameters; } + } + + public DerObjectIdentifier PublicKeyParamSet + { + get { return publicKeyParamSet; } + } + + private static Gost3410Parameters LookupParameters( + DerObjectIdentifier publicKeyParamSet) + { + if (publicKeyParamSet == null) + throw new ArgumentNullException("publicKeyParamSet"); + + Gost3410ParamSetParameters p = Gost3410NamedParameters.GetByOid(publicKeyParamSet); + + if (p == null) + throw new ArgumentException("OID is not a valid CryptoPro public key parameter set", "publicKeyParamSet"); + + return new Gost3410Parameters(p.P, p.Q, p.A); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/GOST3410KeyParameters.cs b/bc-sharp-crypto/src/crypto/parameters/GOST3410KeyParameters.cs new file mode 100644 index 0000000..f771c4d --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/GOST3410KeyParameters.cs @@ -0,0 +1,58 @@ +using System; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.CryptoPro; +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + public abstract class Gost3410KeyParameters + : AsymmetricKeyParameter + { + private readonly Gost3410Parameters parameters; + private readonly DerObjectIdentifier publicKeyParamSet; + + protected Gost3410KeyParameters( + bool isPrivate, + Gost3410Parameters parameters) + : base(isPrivate) + { + this.parameters = parameters; + } + + protected Gost3410KeyParameters( + bool isPrivate, + DerObjectIdentifier publicKeyParamSet) + : base(isPrivate) + { + this.parameters = LookupParameters(publicKeyParamSet); + this.publicKeyParamSet = publicKeyParamSet; + } + + public Gost3410Parameters Parameters + { + get { return parameters; } + } + + public DerObjectIdentifier PublicKeyParamSet + { + get { return publicKeyParamSet; } + } + + // TODO Implement Equals/GetHashCode + + private static Gost3410Parameters LookupParameters( + DerObjectIdentifier publicKeyParamSet) + { + if (publicKeyParamSet == null) + throw new ArgumentNullException("publicKeyParamSet"); + + Gost3410ParamSetParameters p = Gost3410NamedParameters.GetByOid(publicKeyParamSet); + + if (p == null) + throw new ArgumentException("OID is not a valid CryptoPro public key parameter set", "publicKeyParamSet"); + + return new Gost3410Parameters(p.P, p.Q, p.A); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/GOST3410Parameters.cs b/bc-sharp-crypto/src/crypto/parameters/GOST3410Parameters.cs new file mode 100644 index 0000000..2ec167e --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/GOST3410Parameters.cs @@ -0,0 +1,86 @@ +using System; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + public class Gost3410Parameters + : ICipherParameters + { + private readonly BigInteger p, q, a; + private readonly Gost3410ValidationParameters validation; + + public Gost3410Parameters( + BigInteger p, + BigInteger q, + BigInteger a) + : this(p, q, a, null) + { + } + + public Gost3410Parameters( + BigInteger p, + BigInteger q, + BigInteger a, + Gost3410ValidationParameters validation) + { + if (p == null) + throw new ArgumentNullException("p"); + if (q == null) + throw new ArgumentNullException("q"); + if (a == null) + throw new ArgumentNullException("a"); + + this.p = p; + this.q = q; + this.a = a; + this.validation = validation; + } + + public BigInteger P + { + get { return p; } + } + + public BigInteger Q + { + get { return q; } + } + + public BigInteger A + { + get { return a; } + } + + public Gost3410ValidationParameters ValidationParameters + { + get { return validation; } + } + + public override bool Equals( + object obj) + { + if (obj == this) + return true; + + Gost3410Parameters other = obj as Gost3410Parameters; + + if (other == null) + return false; + + return Equals(other); + } + + protected bool Equals( + Gost3410Parameters other) + { + return p.Equals(other.p) && q.Equals(other.q) && a.Equals(other.a); + } + + public override int GetHashCode() + { + return p.GetHashCode() ^ q.GetHashCode() ^ a.GetHashCode(); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/GOST3410PrivateKeyParameters.cs b/bc-sharp-crypto/src/crypto/parameters/GOST3410PrivateKeyParameters.cs new file mode 100644 index 0000000..e3a613d --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/GOST3410PrivateKeyParameters.cs @@ -0,0 +1,41 @@ +using System; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.CryptoPro; +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + public class Gost3410PrivateKeyParameters + : Gost3410KeyParameters + { + private readonly BigInteger x; + + public Gost3410PrivateKeyParameters( + BigInteger x, + Gost3410Parameters parameters) + : base(true, parameters) + { + if (x.SignValue < 1 || x.BitLength > 256 || x.CompareTo(Parameters.Q) >= 0) + throw new ArgumentException("Invalid x for GOST3410 private key", "x"); + + this.x = x; + } + + public Gost3410PrivateKeyParameters( + BigInteger x, + DerObjectIdentifier publicKeyParamSet) + : base(true, publicKeyParamSet) + { + if (x.SignValue < 1 || x.BitLength > 256 || x.CompareTo(Parameters.Q) >= 0) + throw new ArgumentException("Invalid x for GOST3410 private key", "x"); + + this.x = x; + } + + public BigInteger X + { + get { return x; } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/GOST3410PublicKeyParameters.cs b/bc-sharp-crypto/src/crypto/parameters/GOST3410PublicKeyParameters.cs new file mode 100644 index 0000000..96b7e91 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/GOST3410PublicKeyParameters.cs @@ -0,0 +1,40 @@ +using System; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + public class Gost3410PublicKeyParameters + : Gost3410KeyParameters + { + private readonly BigInteger y; + + public Gost3410PublicKeyParameters( + BigInteger y, + Gost3410Parameters parameters) + : base(false, parameters) + { + if (y.SignValue < 1 || y.CompareTo(Parameters.P) >= 0) + throw new ArgumentException("Invalid y for GOST3410 public key", "y"); + + this.y = y; + } + + public Gost3410PublicKeyParameters( + BigInteger y, + DerObjectIdentifier publicKeyParamSet) + : base(false, publicKeyParamSet) + { + if (y.SignValue < 1 || y.CompareTo(Parameters.P) >= 0) + throw new ArgumentException("Invalid y for GOST3410 public key", "y"); + + this.y = y; + } + + public BigInteger Y + { + get { return y; } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/GOST3410ValidationParameters.cs b/bc-sharp-crypto/src/crypto/parameters/GOST3410ValidationParameters.cs new file mode 100644 index 0000000..21e5af8 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/GOST3410ValidationParameters.cs @@ -0,0 +1,51 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + 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 C { get { return c; } } + public int X0 { get { return x0; } } + public long CL { get { return cL; } } + public long X0L { get { return x0L; } } + + public override bool Equals( + object obj) + { + Gost3410ValidationParameters other = obj as Gost3410ValidationParameters; + + return other != null + && other.c == this.c + && other.x0 == this.x0 + && other.cL == this.cL + && other.x0L == this.x0L; + } + + public override int GetHashCode() + { + return c.GetHashCode() ^ x0.GetHashCode() ^ cL.GetHashCode() ^ x0L.GetHashCode(); + } + + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/HKDFParameters.cs b/bc-sharp-crypto/src/crypto/parameters/HKDFParameters.cs new file mode 100644 index 0000000..6d1465e --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/HKDFParameters.cs @@ -0,0 +1,119 @@ +using System; + +using Org.BouncyCastle.Crypto.Macs; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + /** + * Parameter class for the HkdfBytesGenerator class. + */ + public class HkdfParameters + : IDerivationParameters + { + private readonly byte[] ikm; + private readonly bool skipExpand; + private readonly byte[] salt; + private readonly byte[] info; + + private HkdfParameters(byte[] ikm, bool skip, byte[] salt, byte[] info) + { + if (ikm == null) + throw new ArgumentNullException("ikm"); + + this.ikm = Arrays.Clone(ikm); + this.skipExpand = skip; + + if (salt == null || salt.Length == 0) + { + this.salt = null; + } + else + { + this.salt = Arrays.Clone(salt); + } + + if (info == null) + { + this.info = new byte[0]; + } + else + { + this.info = Arrays.Clone(info); + } + } + + /** + * Generates parameters for HKDF, specifying both the optional salt and + * optional info. Step 1: Extract won't be skipped. + * + * @param ikm the input keying material or seed + * @param salt the salt to use, may be null for a salt for hashLen zeros + * @param info the info to use, may be null for an info field of zero bytes + */ + public HkdfParameters(byte[] ikm, byte[] salt, byte[] info) + : this(ikm, false, salt, info) + { + } + + /** + * Factory method that makes the HKDF skip the extract part of the key + * derivation function. + * + * @param ikm the input keying material or seed, directly used for step 2: + * Expand + * @param info the info to use, may be null for an info field of zero bytes + * @return HKDFParameters that makes the implementation skip step 1 + */ + public static HkdfParameters SkipExtractParameters(byte[] ikm, byte[] info) + { + return new HkdfParameters(ikm, true, null, info); + } + + public static HkdfParameters DefaultParameters(byte[] ikm) + { + return new HkdfParameters(ikm, false, null, null); + } + + /** + * Returns the input keying material or seed. + * + * @return the keying material + */ + public virtual byte[] GetIkm() + { + return Arrays.Clone(ikm); + } + + /** + * Returns if step 1: extract has to be skipped or not + * + * @return true for skipping, false for no skipping of step 1 + */ + public virtual bool SkipExtract + { + get { return skipExpand; } + } + + /** + * Returns the salt, or null if the salt should be generated as a byte array + * of HashLen zeros. + * + * @return the salt, or null + */ + public virtual byte[] GetSalt() + { + return Arrays.Clone(salt); + } + + /** + * Returns the info field, which may be empty (null is converted to empty). + * + * @return the info field, never null + */ + public virtual byte[] GetInfo() + { + return Arrays.Clone(info); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/ISO18033KDFParameters.cs b/bc-sharp-crypto/src/crypto/parameters/ISO18033KDFParameters.cs new file mode 100644 index 0000000..2d8fff8 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/ISO18033KDFParameters.cs @@ -0,0 +1,25 @@ +using System; +using Org.BouncyCastle.Crypto; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + /** + * parameters for Key derivation functions for ISO-18033 + */ + public class Iso18033KdfParameters + : IDerivationParameters + { + byte[] seed; + + public Iso18033KdfParameters( + byte[] seed) + { + this.seed = seed; + } + + public byte[] GetSeed() + { + return seed; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/IesParameters.cs b/bc-sharp-crypto/src/crypto/parameters/IesParameters.cs new file mode 100644 index 0000000..d306b2c --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/IesParameters.cs @@ -0,0 +1,49 @@ +using System; +using Org.BouncyCastle.Crypto; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + /** + * parameters for using an integrated cipher in stream mode. + */ + public class IesParameters : ICipherParameters + { + 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 MacKeySize + { + get + { + return macKeySize; + } + } + } + +} diff --git a/bc-sharp-crypto/src/crypto/parameters/IesWithCipherParameters.cs b/bc-sharp-crypto/src/crypto/parameters/IesWithCipherParameters.cs new file mode 100644 index 0000000..70ef55d --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/IesWithCipherParameters.cs @@ -0,0 +1,33 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + public class IesWithCipherParameters : 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) : base(derivation, encoding, macKeySize) + { + this.cipherKeySize = cipherKeySize; + } + + public int CipherKeySize + { + get + { + return cipherKeySize; + } + } + } + +} diff --git a/bc-sharp-crypto/src/crypto/parameters/KdfParameters.cs b/bc-sharp-crypto/src/crypto/parameters/KdfParameters.cs new file mode 100644 index 0000000..bc5c905 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/KdfParameters.cs @@ -0,0 +1,33 @@ +using System; +using Org.BouncyCastle.Crypto; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + /** + * parameters for Key derivation functions for IEEE P1363a + */ + public class KdfParameters : IDerivationParameters + { + 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/bc-sharp-crypto/src/crypto/parameters/KeyParameter.cs b/bc-sharp-crypto/src/crypto/parameters/KeyParameter.cs new file mode 100644 index 0000000..33dff96 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/KeyParameter.cs @@ -0,0 +1,43 @@ +using System; + +using Org.BouncyCastle.Crypto; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + public class KeyParameter + : ICipherParameters + { + private readonly byte[] key; + + public KeyParameter( + byte[] key) + { + if (key == null) + throw new ArgumentNullException("key"); + + this.key = (byte[]) key.Clone(); + } + + public KeyParameter( + byte[] key, + int keyOff, + int keyLen) + { + if (key == null) + throw new ArgumentNullException("key"); + if (keyOff < 0 || keyOff > key.Length) + throw new ArgumentOutOfRangeException("keyOff"); + if (keyLen < 0 || (keyOff + keyLen) > key.Length) + throw new ArgumentOutOfRangeException("keyLen"); + + this.key = new byte[keyLen]; + Array.Copy(key, keyOff, this.key, 0, keyLen); + } + + public byte[] GetKey() + { + return (byte[]) key.Clone(); + } + } + +} diff --git a/bc-sharp-crypto/src/crypto/parameters/MgfParameters.cs b/bc-sharp-crypto/src/crypto/parameters/MgfParameters.cs new file mode 100644 index 0000000..11983b8 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/MgfParameters.cs @@ -0,0 +1,31 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + /// Parameters for mask derivation functions. + public class MgfParameters + : IDerivationParameters + { + private readonly byte[] seed; + + public MgfParameters( + byte[] seed) + : this(seed, 0, seed.Length) + { + } + + public MgfParameters( + byte[] seed, + int off, + int len) + { + this.seed = new byte[len]; + Array.Copy(seed, off, this.seed, 0, len); + } + + public byte[] GetSeed() + { + return (byte[]) seed.Clone(); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/MqvPrivateParameters.cs b/bc-sharp-crypto/src/crypto/parameters/MqvPrivateParameters.cs new file mode 100644 index 0000000..9159cac --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/MqvPrivateParameters.cs @@ -0,0 +1,64 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + public class MqvPrivateParameters + : ICipherParameters + { + private readonly ECPrivateKeyParameters staticPrivateKey; + private readonly ECPrivateKeyParameters ephemeralPrivateKey; + private readonly ECPublicKeyParameters ephemeralPublicKey; + + public MqvPrivateParameters( + ECPrivateKeyParameters staticPrivateKey, + ECPrivateKeyParameters ephemeralPrivateKey) + : this(staticPrivateKey, ephemeralPrivateKey, null) + { + } + + public MqvPrivateParameters( + ECPrivateKeyParameters staticPrivateKey, + ECPrivateKeyParameters ephemeralPrivateKey, + ECPublicKeyParameters ephemeralPublicKey) + { + if (staticPrivateKey == null) + throw new ArgumentNullException("staticPrivateKey"); + if (ephemeralPrivateKey == null) + throw new ArgumentNullException("ephemeralPrivateKey"); + + ECDomainParameters parameters = staticPrivateKey.Parameters; + if (!parameters.Equals(ephemeralPrivateKey.Parameters)) + throw new ArgumentException("Static and ephemeral private keys have different domain parameters"); + + if (ephemeralPublicKey == null) + { + ephemeralPublicKey = new ECPublicKeyParameters( + parameters.G.Multiply(ephemeralPrivateKey.D), + parameters); + } + else if (!parameters.Equals(ephemeralPublicKey.Parameters)) + { + throw new ArgumentException("Ephemeral public key has different domain parameters"); + } + + this.staticPrivateKey = staticPrivateKey; + this.ephemeralPrivateKey = ephemeralPrivateKey; + this.ephemeralPublicKey = ephemeralPublicKey; + } + + public virtual ECPrivateKeyParameters StaticPrivateKey + { + get { return staticPrivateKey; } + } + + public virtual ECPrivateKeyParameters EphemeralPrivateKey + { + get { return ephemeralPrivateKey; } + } + + public virtual ECPublicKeyParameters EphemeralPublicKey + { + get { return ephemeralPublicKey; } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/MqvPublicParameters.cs b/bc-sharp-crypto/src/crypto/parameters/MqvPublicParameters.cs new file mode 100644 index 0000000..239afa3 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/MqvPublicParameters.cs @@ -0,0 +1,36 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + public class MqvPublicParameters + : ICipherParameters + { + private readonly ECPublicKeyParameters staticPublicKey; + private readonly ECPublicKeyParameters ephemeralPublicKey; + + public MqvPublicParameters( + ECPublicKeyParameters staticPublicKey, + ECPublicKeyParameters ephemeralPublicKey) + { + if (staticPublicKey == null) + throw new ArgumentNullException("staticPublicKey"); + if (ephemeralPublicKey == null) + throw new ArgumentNullException("ephemeralPublicKey"); + if (!staticPublicKey.Parameters.Equals(ephemeralPublicKey.Parameters)) + throw new ArgumentException("Static and ephemeral public keys have different domain parameters"); + + this.staticPublicKey = staticPublicKey; + this.ephemeralPublicKey = ephemeralPublicKey; + } + + public virtual ECPublicKeyParameters StaticPublicKey + { + get { return staticPublicKey; } + } + + public virtual ECPublicKeyParameters EphemeralPublicKey + { + get { return ephemeralPublicKey; } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/NaccacheSternKeyGenerationParameters.cs b/bc-sharp-crypto/src/crypto/parameters/NaccacheSternKeyGenerationParameters.cs new file mode 100644 index 0000000..44fc906 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/NaccacheSternKeyGenerationParameters.cs @@ -0,0 +1,98 @@ +using System; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + /** + * 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 : KeyGenerationParameters + { + // private BigInteger publicExponent; + private readonly int certainty; + private readonly int countSmallPrimes; + + /** + * 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 countSmallPrimes + * How many small key factors are desired + */ + public NaccacheSternKeyGenerationParameters( + SecureRandom random, + int strength, + int certainty, + int countSmallPrimes) + : base(random, strength) + { + if (countSmallPrimes % 2 == 1) + throw new ArgumentException("countSmallPrimes must be a multiple of 2"); + if (countSmallPrimes < 30) + throw new ArgumentException("countSmallPrimes must be >= 30 for security reasons"); + + this.certainty = certainty; + this.countSmallPrimes = countSmallPrimes; + } + + /** + * 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 + * Ignored + */ + [Obsolete("Use version without 'debug' parameter")] + public NaccacheSternKeyGenerationParameters( + SecureRandom random, + int strength, + int certainty, + int countSmallPrimes, + bool debug) + : this(random, strength, certainty, countSmallPrimes) + { + } + + /** + * @return Returns the certainty. + */ + public int Certainty + { + get { return certainty; } + } + + /** + * @return Returns the countSmallPrimes. + */ + public int CountSmallPrimes + { + get { return countSmallPrimes; } + } + + [Obsolete("Remove: always false")] + public bool IsDebug + { + get { return false; } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/NaccacheSternKeyParameters.cs b/bc-sharp-crypto/src/crypto/parameters/NaccacheSternKeyParameters.cs new file mode 100644 index 0000000..8be7ad8 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/NaccacheSternKeyParameters.cs @@ -0,0 +1,44 @@ +using System; + +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + /** + * 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 : AsymmetricKeyParameter + { + private readonly BigInteger g, n; + private readonly int lowerSigmaBound; + + /** + * @param privateKey + */ + public NaccacheSternKeyParameters(bool privateKey, BigInteger g, BigInteger n, int lowerSigmaBound) + : base(privateKey) + { + this.g = g; + this.n = n; + this.lowerSigmaBound = lowerSigmaBound; + } + + /** + * @return Returns the g. + */ + public BigInteger G { get { return g; } } + + /** + * @return Returns the lowerSigmaBound. + */ + public int LowerSigmaBound { get { return lowerSigmaBound; } } + + /** + * @return Returns the n. + */ + public BigInteger Modulus { get { return n; } } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/NaccacheSternPrivateKeyParameters.cs b/bc-sharp-crypto/src/crypto/parameters/NaccacheSternPrivateKeyParameters.cs new file mode 100644 index 0000000..42a0454 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/NaccacheSternPrivateKeyParameters.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + /** + * 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 : NaccacheSternKeyParameters + { + private readonly BigInteger phiN; + private readonly IList smallPrimes; + +#if !(SILVERLIGHT || PORTABLE) + [Obsolete] + public NaccacheSternPrivateKeyParameters( + BigInteger g, + BigInteger n, + int lowerSigmaBound, + ArrayList smallPrimes, + BigInteger phiN) + : base(true, g, n, lowerSigmaBound) + { + this.smallPrimes = smallPrimes; + this.phiN = phiN; + } +#endif + + /** + * 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, + IList smallPrimes, + BigInteger phiN) + : base(true, g, n, lowerSigmaBound) + { + this.smallPrimes = smallPrimes; + this.phiN = phiN; + } + + public BigInteger PhiN + { + get { return phiN; } + } + +#if !(SILVERLIGHT || PORTABLE) + [Obsolete("Use 'SmallPrimesList' instead")] + public ArrayList SmallPrimes + { + get { return new ArrayList(smallPrimes); } + } +#endif + + public IList SmallPrimesList + { + get { return smallPrimes; } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/ParametersWithIV.cs b/bc-sharp-crypto/src/crypto/parameters/ParametersWithIV.cs new file mode 100644 index 0000000..11a8b77 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/ParametersWithIV.cs @@ -0,0 +1,43 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + public class ParametersWithIV + : ICipherParameters + { + private readonly ICipherParameters parameters; + private readonly byte[] iv; + + public ParametersWithIV( + ICipherParameters parameters, + byte[] iv) + : this(parameters, iv, 0, iv.Length) + { + } + + public ParametersWithIV( + ICipherParameters parameters, + byte[] iv, + int ivOff, + int ivLen) + { + // NOTE: 'parameters' may be null to imply key re-use + if (iv == null) + throw new ArgumentNullException("iv"); + + this.parameters = parameters; + this.iv = new byte[ivLen]; + Array.Copy(iv, ivOff, this.iv, 0, ivLen); + } + + public byte[] GetIV() + { + return (byte[]) iv.Clone(); + } + + public ICipherParameters Parameters + { + get { return parameters; } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/ParametersWithRandom.cs b/bc-sharp-crypto/src/crypto/parameters/ParametersWithRandom.cs new file mode 100644 index 0000000..276dc26 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/ParametersWithRandom.cs @@ -0,0 +1,48 @@ +using System; + +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + public class ParametersWithRandom + : ICipherParameters + { + private readonly ICipherParameters parameters; + private readonly SecureRandom random; + + public ParametersWithRandom( + ICipherParameters parameters, + SecureRandom random) + { + if (parameters == null) + throw new ArgumentNullException("parameters"); + if (random == null) + throw new ArgumentNullException("random"); + + this.parameters = parameters; + this.random = random; + } + + public ParametersWithRandom( + ICipherParameters parameters) + : this(parameters, new SecureRandom()) + { + } + + [Obsolete("Use Random property instead")] + public SecureRandom GetRandom() + { + return Random; + } + + public SecureRandom Random + { + get { return random; } + } + + public ICipherParameters Parameters + { + get { return parameters; } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/ParametersWithSBox.cs b/bc-sharp-crypto/src/crypto/parameters/ParametersWithSBox.cs new file mode 100644 index 0000000..6473796 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/ParametersWithSBox.cs @@ -0,0 +1,24 @@ +using System; + +using Org.BouncyCastle.Crypto; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + public class ParametersWithSBox : ICipherParameters + { + private ICipherParameters parameters; + private byte[] sBox; + + public ParametersWithSBox( + ICipherParameters parameters, + byte[] sBox) + { + this.parameters = parameters; + this.sBox = sBox; + } + + public byte[] GetSBox() { return sBox; } + + public ICipherParameters Parameters { get { return parameters; } } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/ParametersWithSalt.cs b/bc-sharp-crypto/src/crypto/parameters/ParametersWithSalt.cs new file mode 100644 index 0000000..7f4cd6c --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/ParametersWithSalt.cs @@ -0,0 +1,39 @@ +using System; + +using Org.BouncyCastle.Crypto; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + + /// Cipher parameters with a fixed salt value associated with them. + public class ParametersWithSalt : ICipherParameters + { + private byte[] salt; + private ICipherParameters parameters; + + public ParametersWithSalt(ICipherParameters parameters, byte[] salt):this(parameters, salt, 0, salt.Length) + { + } + + public ParametersWithSalt(ICipherParameters parameters, byte[] salt, int saltOff, int saltLen) + { + this.salt = new byte[saltLen]; + this.parameters = parameters; + + Array.Copy(salt, saltOff, this.salt, 0, saltLen); + } + + public byte[] GetSalt() + { + return salt; + } + + public ICipherParameters Parameters + { + get + { + return parameters; + } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/RC2Parameters.cs b/bc-sharp-crypto/src/crypto/parameters/RC2Parameters.cs new file mode 100644 index 0000000..7a6d5bb --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/RC2Parameters.cs @@ -0,0 +1,47 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + public class RC2Parameters + : KeyParameter + { + private readonly int bits; + + public RC2Parameters( + byte[] key) + : this(key, (key.Length > 128) ? 1024 : (key.Length * 8)) + { + } + + public RC2Parameters( + byte[] key, + int keyOff, + int keyLen) + : this(key, keyOff, keyLen, (keyLen > 128) ? 1024 : (keyLen * 8)) + { + } + + public RC2Parameters( + byte[] key, + int bits) + : base(key) + { + this.bits = bits; + } + + public RC2Parameters( + byte[] key, + int keyOff, + int keyLen, + int bits) + : base(key, keyOff, keyLen) + { + this.bits = bits; + } + + public int EffectiveKeyBits + { + get { return bits; } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/RC5Parameters.cs b/bc-sharp-crypto/src/crypto/parameters/RC5Parameters.cs new file mode 100644 index 0000000..88a59e1 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/RC5Parameters.cs @@ -0,0 +1,27 @@ +using System; +using Org.BouncyCastle.Crypto; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + public class RC5Parameters + : KeyParameter + { + private readonly int rounds; + + public RC5Parameters( + byte[] key, + int rounds) + : base(key) + { + if (key.Length > 255) + throw new ArgumentException("RC5 key length can be no greater than 255"); + + this.rounds = rounds; + } + + public int Rounds + { + get { return rounds; } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/RSABlindingParameters.cs b/bc-sharp-crypto/src/crypto/parameters/RSABlindingParameters.cs new file mode 100644 index 0000000..49c7bcc --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/RSABlindingParameters.cs @@ -0,0 +1,34 @@ +using System; + +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + public class RsaBlindingParameters + : ICipherParameters + { + private readonly RsaKeyParameters publicKey; + private readonly BigInteger blindingFactor; + + public RsaBlindingParameters( + RsaKeyParameters publicKey, + BigInteger blindingFactor) + { + if (publicKey.IsPrivate) + throw new ArgumentException("RSA parameters should be for a public key"); + + this.publicKey = publicKey; + this.blindingFactor = blindingFactor; + } + + public RsaKeyParameters PublicKey + { + get { return publicKey; } + } + + public BigInteger BlindingFactor + { + get { return blindingFactor; } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/RsaKeyGenerationParameters.cs b/bc-sharp-crypto/src/crypto/parameters/RsaKeyGenerationParameters.cs new file mode 100644 index 0000000..619ab65 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/RsaKeyGenerationParameters.cs @@ -0,0 +1,55 @@ +using System; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + public class RsaKeyGenerationParameters + : KeyGenerationParameters + { + private readonly BigInteger publicExponent; + private readonly int certainty; + + public RsaKeyGenerationParameters( + BigInteger publicExponent, + SecureRandom random, + int strength, + int certainty) + : base(random, strength) + { + this.publicExponent = publicExponent; + this.certainty = certainty; + } + + public BigInteger PublicExponent + { + get { return publicExponent; } + } + + public int Certainty + { + get { return certainty; } + } + + public override bool Equals( + object obj) + { + RsaKeyGenerationParameters other = obj as RsaKeyGenerationParameters; + + if (other == null) + { + return false; + } + + return certainty == other.certainty + && publicExponent.Equals(other.publicExponent); + } + + public override int GetHashCode() + { + return certainty.GetHashCode() ^ publicExponent.GetHashCode(); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/RsaKeyParameters.cs b/bc-sharp-crypto/src/crypto/parameters/RsaKeyParameters.cs new file mode 100644 index 0000000..5480f05 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/RsaKeyParameters.cs @@ -0,0 +1,85 @@ +using System; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + public class RsaKeyParameters + : AsymmetricKeyParameter + { + // the value is the product of the 132 smallest primes from 3 to 751 + private static BigInteger SmallPrimesProduct = new BigInteger( + "8138E8A0FCF3A4E84A771D40FD305D7F4AA59306D7251DE54D98AF8FE95729A1" + + "F73D893FA424CD2EDC8636A6C3285E022B0E3866A565AE8108EED8591CD4FE8D" + + "2CE86165A978D719EBF647F362D33FCA29CD179FB42401CBAF3DF0C614056F9C" + + "8F3CFD51E474AFB6BC6974F78DB8ABA8E9E517FDED658591AB7502BD41849462F", + 16); + + private static BigInteger Validate(BigInteger modulus) + { + if ((modulus.IntValue & 1) == 0) + throw new ArgumentException("RSA modulus is even", "modulus"); + if (!modulus.Gcd(SmallPrimesProduct).Equals(BigInteger.One)) + throw new ArgumentException("RSA modulus has a small prime factor"); + + // TODO: add additional primePower/Composite test - expensive!! + + return modulus; + } + + private readonly BigInteger modulus; + private readonly BigInteger exponent; + + public RsaKeyParameters( + bool isPrivate, + BigInteger modulus, + BigInteger exponent) + : base(isPrivate) + { + if (modulus == null) + throw new ArgumentNullException("modulus"); + if (exponent == null) + throw new ArgumentNullException("exponent"); + if (modulus.SignValue <= 0) + throw new ArgumentException("Not a valid RSA modulus", "modulus"); + if (exponent.SignValue <= 0) + throw new ArgumentException("Not a valid RSA exponent", "exponent"); + if (!isPrivate && (exponent.IntValue & 1) == 0) + throw new ArgumentException("RSA publicExponent is even", "exponent"); + + this.modulus = Validate(modulus); + this.exponent = exponent; + } + + public BigInteger Modulus + { + get { return modulus; } + } + + public BigInteger Exponent + { + get { return exponent; } + } + + public override bool Equals( + object obj) + { + RsaKeyParameters kp = obj as RsaKeyParameters; + + if (kp == null) + { + return false; + } + + return kp.IsPrivate == this.IsPrivate + && kp.Modulus.Equals(this.modulus) + && kp.Exponent.Equals(this.exponent); + } + + public override int GetHashCode() + { + return modulus.GetHashCode() ^ exponent.GetHashCode() ^ IsPrivate.GetHashCode(); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/RsaPrivateCrtKeyParameters.cs b/bc-sharp-crypto/src/crypto/parameters/RsaPrivateCrtKeyParameters.cs new file mode 100644 index 0000000..7bd8abd --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/RsaPrivateCrtKeyParameters.cs @@ -0,0 +1,104 @@ +using System; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + public class RsaPrivateCrtKeyParameters + : RsaKeyParameters + { + private readonly BigInteger e, p, q, dP, dQ, qInv; + + public RsaPrivateCrtKeyParameters( + BigInteger modulus, + BigInteger publicExponent, + BigInteger privateExponent, + BigInteger p, + BigInteger q, + BigInteger dP, + BigInteger dQ, + BigInteger qInv) + : base(true, modulus, privateExponent) + { + ValidateValue(publicExponent, "publicExponent", "exponent"); + ValidateValue(p, "p", "P value"); + ValidateValue(q, "q", "Q value"); + ValidateValue(dP, "dP", "DP value"); + ValidateValue(dQ, "dQ", "DQ value"); + ValidateValue(qInv, "qInv", "InverseQ value"); + + this.e = publicExponent; + this.p = p; + this.q = q; + this.dP = dP; + this.dQ = dQ; + this.qInv = qInv; + } + + public BigInteger PublicExponent + { + get { return e; } + } + + public BigInteger P + { + get { return p; } + } + + public BigInteger Q + { + get { return q; } + } + + public BigInteger DP + { + get { return dP; } + } + + public BigInteger DQ + { + get { return dQ; } + } + + public BigInteger QInv + { + get { return qInv; } + } + + public override bool Equals( + object obj) + { + if (obj == this) + return true; + + RsaPrivateCrtKeyParameters kp = obj as RsaPrivateCrtKeyParameters; + + if (kp == null) + return false; + + return kp.DP.Equals(dP) + && kp.DQ.Equals(dQ) + && kp.Exponent.Equals(this.Exponent) + && kp.Modulus.Equals(this.Modulus) + && kp.P.Equals(p) + && kp.Q.Equals(q) + && kp.PublicExponent.Equals(e) + && kp.QInv.Equals(qInv); + } + + public override int GetHashCode() + { + return DP.GetHashCode() ^ DQ.GetHashCode() ^ Exponent.GetHashCode() ^ Modulus.GetHashCode() + ^ P.GetHashCode() ^ Q.GetHashCode() ^ PublicExponent.GetHashCode() ^ QInv.GetHashCode(); + } + + private static void ValidateValue(BigInteger x, string name, string desc) + { + if (x == null) + throw new ArgumentNullException(name); + if (x.SignValue <= 0) + throw new ArgumentException("Not a valid RSA " + desc, name); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/SkeinParameters.cs b/bc-sharp-crypto/src/crypto/parameters/SkeinParameters.cs new file mode 100644 index 0000000..cc57ef5 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/SkeinParameters.cs @@ -0,0 +1,286 @@ +using System; +using System.Collections; +using System.Globalization; +using System.IO; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + + /// + /// Parameters for the Skein hash function - a series of byte[] strings identified by integer tags. + /// + /// + /// Parameterised Skein can be used for: + ///
    + ///
  • MAC generation, by providing a key.
  • + ///
  • Randomised hashing, by providing a nonce.
  • + ///
  • A hash function for digital signatures, associating a + /// public key with the message digest.
  • + ///
  • A key derivation function, by providing a + /// key identifier.
  • + ///
  • Personalised hashing, by providing a + /// recommended format or + /// arbitrary personalisation string.
  • + ///
+ ///
+ /// + /// + /// + public class SkeinParameters + : ICipherParameters + { + /// + /// The parameter type for a secret key, supporting MAC or KDF functions: 0 + /// + public const int PARAM_TYPE_KEY = 0; + + /// + /// The parameter type for the Skein configuration block: 4 + /// + public const int PARAM_TYPE_CONFIG = 4; + + /// + /// The parameter type for a personalisation string: 8 + /// + public const int PARAM_TYPE_PERSONALISATION = 8; + + /// + /// The parameter type for a public key: 12 + /// + public const int PARAM_TYPE_PUBLIC_KEY = 12; + + /// + /// The parameter type for a key identifier string: 16 + /// + public const int PARAM_TYPE_KEY_IDENTIFIER = 16; + + /// + /// The parameter type for a nonce: 20 + /// + public const int PARAM_TYPE_NONCE = 20; + + /// + /// The parameter type for the message: 48 + /// + public const int PARAM_TYPE_MESSAGE = 48; + + /// + /// The parameter type for the output transformation: 63 + /// + public const int PARAM_TYPE_OUTPUT = 63; + + private IDictionary parameters; + + public SkeinParameters() + : this(Platform.CreateHashtable()) + + { + } + + private SkeinParameters(IDictionary parameters) + { + this.parameters = parameters; + } + + /// + /// Obtains a map of type (int) to value (byte[]) for the parameters tracked in this object. + /// + public IDictionary GetParameters() + { + return parameters; + } + + /// + /// Obtains the value of the key parameter, or null if not + /// set. + /// + /// The key. + public byte[] GetKey() + { + return (byte[])parameters[PARAM_TYPE_KEY]; + } + + /// + /// Obtains the value of the personalisation parameter, or + /// null if not set. + /// + public byte[] GetPersonalisation() + { + return (byte[])parameters[PARAM_TYPE_PERSONALISATION]; + } + + /// + /// Obtains the value of the public key parameter, or + /// null if not set. + /// + public byte[] GetPublicKey() + { + return (byte[])parameters[PARAM_TYPE_PUBLIC_KEY]; + } + + /// + /// Obtains the value of the key identifier parameter, or + /// null if not set. + /// + public byte[] GetKeyIdentifier() + { + return (byte[])parameters[PARAM_TYPE_KEY_IDENTIFIER]; + } + + /// + /// Obtains the value of the nonce parameter, or null if + /// not set. + /// + public byte[] GetNonce() + { + return (byte[])parameters[PARAM_TYPE_NONCE]; + } + + /// + /// A builder for . + /// + public class Builder + { + private IDictionary parameters = Platform.CreateHashtable(); + + public Builder() + { + } + + public Builder(IDictionary paramsMap) + { + IEnumerator keys = paramsMap.Keys.GetEnumerator(); + while (keys.MoveNext()) + { + int key = (int)keys.Current; + parameters.Add(key, paramsMap[key]); + } + } + + public Builder(SkeinParameters parameters) + { + IEnumerator keys = parameters.parameters.Keys.GetEnumerator(); + while (keys.MoveNext()) + { + int key = (int)keys.Current; + this.parameters.Add(key, parameters.parameters[key]); + } + } + + /// + /// Sets a parameters to apply to the Skein hash function. + /// + /// + /// Parameter types must be in the range 0,5..62, and cannot use the value 48 + /// (reserved for message body). + ///

+ /// Parameters with type < 48 are processed before + /// the message content, parameters with type > 48 + /// are processed after the message and prior to output. + /// + /// the type of the parameter, in the range 5..62. + /// the byte sequence of the parameter. + public Builder Set(int type, byte[] value) + { + if (value == null) + { + throw new ArgumentException("Parameter value must not be null."); + } + if ((type != PARAM_TYPE_KEY) + && (type <= PARAM_TYPE_CONFIG || type >= PARAM_TYPE_OUTPUT || type == PARAM_TYPE_MESSAGE)) + { + throw new ArgumentException("Parameter types must be in the range 0,5..47,49..62."); + } + if (type == PARAM_TYPE_CONFIG) + { + throw new ArgumentException("Parameter type " + PARAM_TYPE_CONFIG + + " is reserved for internal use."); + } + this.parameters.Add(type, value); + return this; + } + + ///

+ /// Sets the parameter. + /// + public Builder SetKey(byte[] key) + { + return Set(PARAM_TYPE_KEY, key); + } + + /// + /// Sets the parameter. + /// + public Builder SetPersonalisation(byte[] personalisation) + { + return Set(PARAM_TYPE_PERSONALISATION, personalisation); + } + + /// + /// Implements the recommended personalisation format for Skein defined in Section 4.11 of + /// the Skein 1.3 specification. + /// + /// + /// The format is YYYYMMDD email@address distinguisher, encoded to a byte + /// sequence using UTF-8 encoding. + /// + /// the date the personalised application of the Skein was defined. + /// the email address of the creation of the personalised application. + /// an arbitrary personalisation string distinguishing the application. + public Builder SetPersonalisation(DateTime date, string emailAddress, string distinguisher) + { + try + { + MemoryStream bout = new MemoryStream(); + StreamWriter outBytes = new StreamWriter(bout, System.Text.Encoding.UTF8); + outBytes.Write(date.ToString("YYYYMMDD", CultureInfo.InvariantCulture)); + outBytes.Write(" "); + outBytes.Write(emailAddress); + outBytes.Write(" "); + outBytes.Write(distinguisher); + Platform.Dispose(outBytes); + return Set(PARAM_TYPE_PERSONALISATION, bout.ToArray()); + } + catch (IOException e) + { + throw new InvalidOperationException("Byte I/O failed.", e); + } + } + + /// + /// Sets the parameter. + /// + public Builder SetPublicKey(byte[] publicKey) + { + return Set(PARAM_TYPE_PUBLIC_KEY, publicKey); + } + + /// + /// Sets the parameter. + /// + public Builder SetKeyIdentifier(byte[] keyIdentifier) + { + return Set(PARAM_TYPE_KEY_IDENTIFIER, keyIdentifier); + } + + /// + /// Sets the parameter. + /// + public Builder SetNonce(byte[] nonce) + { + return Set(PARAM_TYPE_NONCE, nonce); + } + + /// + /// Constructs a new instance with the parameters provided to this + /// builder. + /// + public SkeinParameters Build() + { + return new SkeinParameters(parameters); + } + } + } +} \ No newline at end of file diff --git a/bc-sharp-crypto/src/crypto/parameters/Srp6GroupParameters.cs b/bc-sharp-crypto/src/crypto/parameters/Srp6GroupParameters.cs new file mode 100644 index 0000000..6762dd3 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/Srp6GroupParameters.cs @@ -0,0 +1,27 @@ +using System; + +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + public sealed class Srp6GroupParameters + { + private readonly BigInteger n, g; + + public Srp6GroupParameters(BigInteger N, BigInteger g) + { + this.n = N; + this.g = g; + } + + public BigInteger G + { + get { return g; } + } + + public BigInteger N + { + get { return n; } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/parameters/TweakableBlockCipherParameters.cs b/bc-sharp-crypto/src/crypto/parameters/TweakableBlockCipherParameters.cs new file mode 100644 index 0000000..f757266 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/parameters/TweakableBlockCipherParameters.cs @@ -0,0 +1,40 @@ +using System; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Parameters +{ + + /// + /// Parameters for tweakable block ciphers. + /// + public class TweakableBlockCipherParameters + : ICipherParameters + { + private readonly byte[] tweak; + private readonly KeyParameter key; + + public TweakableBlockCipherParameters(KeyParameter key, byte[] tweak) + { + this.key = key; + this.tweak = Arrays.Clone(tweak); + } + + /// + /// Gets the key. + /// + /// the key to use, or null to use the current key. + public KeyParameter Key + { + get { return key; } + } + + /// + /// Gets the tweak value. + /// + /// The tweak to use, or null to use the current tweak. + public byte[] Tweak + { + get { return tweak; } + } + } +} \ No newline at end of file diff --git a/bc-sharp-crypto/src/crypto/prng/BasicEntropySourceProvider.cs b/bc-sharp-crypto/src/crypto/prng/BasicEntropySourceProvider.cs new file mode 100644 index 0000000..31a8461 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/prng/BasicEntropySourceProvider.cs @@ -0,0 +1,71 @@ +using System; + +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Prng +{ + /** + * An EntropySourceProvider where entropy generation is based on a SecureRandom output using SecureRandom.generateSeed(). + */ + public class BasicEntropySourceProvider + : IEntropySourceProvider + { + private readonly SecureRandom mSecureRandom; + private readonly bool mPredictionResistant; + + /** + * Create a entropy source provider based on the passed in SecureRandom. + * + * @param secureRandom the SecureRandom to base EntropySource construction on. + * @param isPredictionResistant boolean indicating if the SecureRandom is based on prediction resistant entropy or not (true if it is). + */ + public BasicEntropySourceProvider(SecureRandom secureRandom, bool isPredictionResistant) + { + mSecureRandom = secureRandom; + mPredictionResistant = isPredictionResistant; + } + + /** + * Return an entropy source that will create bitsRequired bits of entropy on + * each invocation of getEntropy(). + * + * @param bitsRequired size (in bits) of entropy to be created by the provided source. + * @return an EntropySource that generates bitsRequired bits of entropy on each call to its getEntropy() method. + */ + public IEntropySource Get(int bitsRequired) + { + return new BasicEntropySource(mSecureRandom, mPredictionResistant, bitsRequired); + } + + private class BasicEntropySource + : IEntropySource + { + private readonly SecureRandom mSecureRandom; + private readonly bool mPredictionResistant; + private readonly int mEntropySize; + + internal BasicEntropySource(SecureRandom secureRandom, bool predictionResistant, int entropySize) + { + this.mSecureRandom = secureRandom; + this.mPredictionResistant = predictionResistant; + this.mEntropySize = entropySize; + } + + bool IEntropySource.IsPredictionResistant + { + get { return mPredictionResistant; } + } + + byte[] IEntropySource.GetEntropy() + { + // TODO[FIPS] Not all SecureRandom implementations are considered valid entropy sources + return SecureRandom.GetNextBytes(mSecureRandom, (mEntropySize + 7) / 8); + } + + int IEntropySource.EntropySize + { + get { return mEntropySize; } + } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/prng/CryptoApiEntropySourceProvider.cs b/bc-sharp-crypto/src/crypto/prng/CryptoApiEntropySourceProvider.cs new file mode 100644 index 0000000..68579aa --- /dev/null +++ b/bc-sharp-crypto/src/crypto/prng/CryptoApiEntropySourceProvider.cs @@ -0,0 +1,70 @@ +#if !(NETCF_1_0 || PORTABLE) +using System; +using System.Security.Cryptography; + +namespace Org.BouncyCastle.Crypto.Prng +{ + public class CryptoApiEntropySourceProvider + : IEntropySourceProvider + { + private readonly RandomNumberGenerator mRng; + private readonly bool mPredictionResistant; + + public CryptoApiEntropySourceProvider() + : this(new RNGCryptoServiceProvider(), true) + { + } + + public CryptoApiEntropySourceProvider(RandomNumberGenerator rng, bool isPredictionResistant) + { + if (rng == null) + throw new ArgumentNullException("rng"); + + mRng = rng; + mPredictionResistant = isPredictionResistant; + } + + public IEntropySource Get(int bitsRequired) + { + return new CryptoApiEntropySource(mRng, mPredictionResistant, bitsRequired); + } + + private class CryptoApiEntropySource + : IEntropySource + { + private readonly RandomNumberGenerator mRng; + private readonly bool mPredictionResistant; + private readonly int mEntropySize; + + internal CryptoApiEntropySource(RandomNumberGenerator rng, bool predictionResistant, int entropySize) + { + this.mRng = rng; + this.mPredictionResistant = predictionResistant; + this.mEntropySize = entropySize; + } + + #region IEntropySource Members + + bool IEntropySource.IsPredictionResistant + { + get { return mPredictionResistant; } + } + + byte[] IEntropySource.GetEntropy() + { + byte[] result = new byte[(mEntropySize + 7) / 8]; + mRng.GetBytes(result); + return result; + } + + int IEntropySource.EntropySize + { + get { return mEntropySize; } + } + + #endregion + } + } +} + +#endif diff --git a/bc-sharp-crypto/src/crypto/prng/CryptoApiRandomGenerator.cs b/bc-sharp-crypto/src/crypto/prng/CryptoApiRandomGenerator.cs new file mode 100644 index 0000000..fa5f523 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/prng/CryptoApiRandomGenerator.cs @@ -0,0 +1,66 @@ +#if !(NETCF_1_0 || PORTABLE) + +using System; +using System.Security.Cryptography; + +namespace Org.BouncyCastle.Crypto.Prng +{ + /// + /// Uses Microsoft's RNGCryptoServiceProvider + /// + public class CryptoApiRandomGenerator + : IRandomGenerator + { + private readonly RandomNumberGenerator rndProv; + + public CryptoApiRandomGenerator() + : this(new RNGCryptoServiceProvider()) + { + } + + public CryptoApiRandomGenerator(RandomNumberGenerator rng) + { + this.rndProv = rng; + } + + #region IRandomGenerator Members + + public virtual void AddSeedMaterial(byte[] seed) + { + // We don't care about the seed + } + + public virtual void AddSeedMaterial(long seed) + { + // We don't care about the seed + } + + public virtual void NextBytes(byte[] bytes) + { + rndProv.GetBytes(bytes); + } + + public virtual void NextBytes(byte[] bytes, int start, int len) + { + if (start < 0) + throw new ArgumentException("Start offset cannot be negative", "start"); + if (bytes.Length < (start + len)) + throw new ArgumentException("Byte array too small for requested offset and length"); + + if (bytes.Length == len && start == 0) + { + NextBytes(bytes); + } + else + { + byte[] tmpBuf = new byte[len]; + NextBytes(tmpBuf); + Array.Copy(tmpBuf, 0, bytes, start, len); + } + } + + #endregion + } +} + +#endif diff --git a/bc-sharp-crypto/src/crypto/prng/DigestRandomGenerator.cs b/bc-sharp-crypto/src/crypto/prng/DigestRandomGenerator.cs new file mode 100644 index 0000000..f5a2995 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/prng/DigestRandomGenerator.cs @@ -0,0 +1,127 @@ +using System; + +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Utilities; + +namespace Org.BouncyCastle.Crypto.Prng +{ + /** + * 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 + : IRandomGenerator + { + private const long CYCLE_COUNT = 10; + + private long stateCounter; + private long seedCounter; + private IDigest digest; + private byte[] state; + private byte[] seed; + + public DigestRandomGenerator( + IDigest 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) + { + lock (this) + { + DigestUpdate(inSeed); + DigestUpdate(seed); + DigestDoFinal(seed); + } + } + + public void AddSeedMaterial( + long rSeed) + { + lock (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) + { + lock (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 seedVal) + { + byte[] bytes = new byte[8]; + Pack.UInt64_To_LE((ulong)seedVal, bytes); + digest.BlockUpdate(bytes, 0, bytes.Length); + } + + private void DigestUpdate(byte[] inSeed) + { + digest.BlockUpdate(inSeed, 0, inSeed.Length); + } + + private void DigestDoFinal(byte[] result) + { + digest.DoFinal(result, 0); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/prng/EntropyUtilities.cs b/bc-sharp-crypto/src/crypto/prng/EntropyUtilities.cs new file mode 100644 index 0000000..58c8703 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/prng/EntropyUtilities.cs @@ -0,0 +1,30 @@ +using System; + +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Prng +{ + public abstract class EntropyUtilities + { + /** + * Generate numBytes worth of entropy from the passed in entropy source. + * + * @param entropySource the entropy source to request the data from. + * @param numBytes the number of bytes of entropy requested. + * @return a byte array populated with the random data. + */ + public static byte[] GenerateSeed(IEntropySource entropySource, int numBytes) + { + byte[] bytes = new byte[numBytes]; + int count = 0; + while (count < numBytes) + { + byte[] entropy = entropySource.GetEntropy(); + int toCopy = System.Math.Min(bytes.Length, numBytes - count); + Array.Copy(entropy, 0, bytes, count, toCopy); + count += toCopy; + } + return bytes; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/prng/IDrbgProvider.cs b/bc-sharp-crypto/src/crypto/prng/IDrbgProvider.cs new file mode 100644 index 0000000..5ebf5fd --- /dev/null +++ b/bc-sharp-crypto/src/crypto/prng/IDrbgProvider.cs @@ -0,0 +1,11 @@ +using System; + +using Org.BouncyCastle.Crypto.Prng.Drbg; + +namespace Org.BouncyCastle.Crypto.Prng +{ + internal interface IDrbgProvider + { + ISP80090Drbg Get(IEntropySource entropySource); + } +} diff --git a/bc-sharp-crypto/src/crypto/prng/IRandomGenerator.cs b/bc-sharp-crypto/src/crypto/prng/IRandomGenerator.cs new file mode 100644 index 0000000..8dbe406 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/prng/IRandomGenerator.cs @@ -0,0 +1,26 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Prng +{ + /// Generic interface for objects generating random bytes. + public interface IRandomGenerator + { + /// Add more seed material to the generator. + /// A byte array to be mixed into the generator's state. + void AddSeedMaterial(byte[] seed); + + /// Add more seed material to the generator. + /// A long value to be mixed into the generator's state. + void AddSeedMaterial(long seed); + + /// Fill byte array with random values. + /// Array to be filled. + void NextBytes(byte[] bytes); + + /// Fill byte array with random values. + /// Array to receive bytes. + /// Index to start filling at. + /// Length of segment to fill. + void NextBytes(byte[] bytes, int start, int len); + } +} diff --git a/bc-sharp-crypto/src/crypto/prng/ReversedWindowGenerator.cs b/bc-sharp-crypto/src/crypto/prng/ReversedWindowGenerator.cs new file mode 100644 index 0000000..dd28c52 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/prng/ReversedWindowGenerator.cs @@ -0,0 +1,98 @@ +using System; + +namespace Org.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 + : IRandomGenerator + { + private readonly IRandomGenerator generator; + + private byte[] window; + private int windowCount; + + public ReversedWindowGenerator( + IRandomGenerator generator, + int windowSize) + { + if (generator == null) + throw new ArgumentNullException("generator"); + if (windowSize < 2) + throw new ArgumentException("Window size must be at least 2", "windowSize"); + + this.generator = generator; + this.window = new byte[windowSize]; + } + + /// Add more seed material to the generator. + /// A byte array to be mixed into the generator's state. + public virtual void AddSeedMaterial( + byte[] seed) + { + lock (this) + { + windowCount = 0; + generator.AddSeedMaterial(seed); + } + } + + /// Add more seed material to the generator. + /// A long value to be mixed into the generator's state. + public virtual void AddSeedMaterial( + long seed) + { + lock (this) + { + windowCount = 0; + generator.AddSeedMaterial(seed); + } + } + + /// Fill byte array with random values. + /// Array to be filled. + public virtual void NextBytes( + byte[] bytes) + { + doNextBytes(bytes, 0, bytes.Length); + } + + /// Fill byte array with random values. + /// Array to receive bytes. + /// Index to start filling at. + /// Length of segment to fill. + public virtual void NextBytes( + byte[] bytes, + int start, + int len) + { + doNextBytes(bytes, start, len); + } + + private void doNextBytes( + byte[] bytes, + int start, + int len) + { + lock (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/bc-sharp-crypto/src/crypto/prng/SP800SecureRandom.cs b/bc-sharp-crypto/src/crypto/prng/SP800SecureRandom.cs new file mode 100644 index 0000000..30c838c --- /dev/null +++ b/bc-sharp-crypto/src/crypto/prng/SP800SecureRandom.cs @@ -0,0 +1,95 @@ +using System; + +using Org.BouncyCastle.Crypto.Prng.Drbg; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Prng +{ + public class SP800SecureRandom + : SecureRandom + { + private readonly IDrbgProvider mDrbgProvider; + private readonly bool mPredictionResistant; + private readonly SecureRandom mRandomSource; + private readonly IEntropySource mEntropySource; + + private ISP80090Drbg mDrbg; + + internal SP800SecureRandom(SecureRandom randomSource, IEntropySource entropySource, IDrbgProvider drbgProvider, bool predictionResistant) + : base((IRandomGenerator)null) + { + this.mRandomSource = randomSource; + this.mEntropySource = entropySource; + this.mDrbgProvider = drbgProvider; + this.mPredictionResistant = predictionResistant; + } + + public override void SetSeed(byte[] seed) + { + lock (this) + { + if (mRandomSource != null) + { + this.mRandomSource.SetSeed(seed); + } + } + } + + public override void SetSeed(long seed) + { + lock (this) + { + // this will happen when SecureRandom() is created + if (mRandomSource != null) + { + this.mRandomSource.SetSeed(seed); + } + } + } + + public override void NextBytes(byte[] bytes) + { + lock (this) + { + if (mDrbg == null) + { + mDrbg = mDrbgProvider.Get(mEntropySource); + } + + // check if a reseed is required... + if (mDrbg.Generate(bytes, null, mPredictionResistant) < 0) + { + mDrbg.Reseed(null); + mDrbg.Generate(bytes, null, mPredictionResistant); + } + } + } + + public override void NextBytes(byte[] buf, int off, int len) + { + byte[] bytes = new byte[len]; + NextBytes(bytes); + Array.Copy(bytes, 0, buf, off, len); + } + + public override byte[] GenerateSeed(int numBytes) + { + return EntropyUtilities.GenerateSeed(mEntropySource, numBytes); + } + + /// Force a reseed of the DRBG. + /// optional additional input + public virtual void Reseed(byte[] additionalInput) + { + lock (this) + { + if (mDrbg == null) + { + mDrbg = mDrbgProvider.Get(mEntropySource); + } + + mDrbg.Reseed(additionalInput); + } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/prng/SP800SecureRandomBuilder.cs b/bc-sharp-crypto/src/crypto/prng/SP800SecureRandomBuilder.cs new file mode 100644 index 0000000..7199f1a --- /dev/null +++ b/bc-sharp-crypto/src/crypto/prng/SP800SecureRandomBuilder.cs @@ -0,0 +1,208 @@ +using System; + +using Org.BouncyCastle.Crypto.Prng.Drbg; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Prng +{ + /** + * Builder class for making SecureRandom objects based on SP 800-90A Deterministic Random Bit Generators (DRBG). + */ + public class SP800SecureRandomBuilder + { + private readonly SecureRandom mRandom; + private readonly IEntropySourceProvider mEntropySourceProvider; + + private byte[] mPersonalizationString = null; + private int mSecurityStrength = 256; + private int mEntropyBitsRequired = 256; + + /** + * Basic constructor, creates a builder using an EntropySourceProvider based on the default SecureRandom with + * predictionResistant set to false. + *

+ * Any SecureRandom created from a builder constructed like this will make use of input passed to SecureRandom.setSeed() if + * the default SecureRandom does for its generateSeed() call. + *

+ */ + public SP800SecureRandomBuilder() + : this(new SecureRandom(), false) + { + } + + /** + * Construct a builder with an EntropySourceProvider based on the passed in SecureRandom and the passed in value + * for prediction resistance. + *

+ * Any SecureRandom created from a builder constructed like this will make use of input passed to SecureRandom.setSeed() if + * the passed in SecureRandom does for its generateSeed() call. + *

+ * @param entropySource + * @param predictionResistant + */ + public SP800SecureRandomBuilder(SecureRandom entropySource, bool predictionResistant) + { + this.mRandom = entropySource; + this.mEntropySourceProvider = new BasicEntropySourceProvider(entropySource, predictionResistant); + } + + /** + * Create a builder which makes creates the SecureRandom objects from a specified entropy source provider. + *

+ * Note: If this constructor is used any calls to setSeed() in the resulting SecureRandom will be ignored. + *

+ * @param entropySourceProvider a provider of EntropySource objects. + */ + public SP800SecureRandomBuilder(IEntropySourceProvider entropySourceProvider) + { + this.mRandom = null; + this.mEntropySourceProvider = entropySourceProvider; + } + + /** + * Set the personalization string for DRBG SecureRandoms created by this builder + * @param personalizationString the personalisation string for the underlying DRBG. + * @return the current builder. + */ + public SP800SecureRandomBuilder SetPersonalizationString(byte[] personalizationString) + { + this.mPersonalizationString = personalizationString; + return this; + } + + /** + * Set the security strength required for DRBGs used in building SecureRandom objects. + * + * @param securityStrength the security strength (in bits) + * @return the current builder. + */ + public SP800SecureRandomBuilder SetSecurityStrength(int securityStrength) + { + this.mSecurityStrength = securityStrength; + return this; + } + + /** + * Set the amount of entropy bits required for seeding and reseeding DRBGs used in building SecureRandom objects. + * + * @param entropyBitsRequired the number of bits of entropy to be requested from the entropy source on each seed/reseed. + * @return the current builder. + */ + public SP800SecureRandomBuilder SetEntropyBitsRequired(int entropyBitsRequired) + { + this.mEntropyBitsRequired = entropyBitsRequired; + return this; + } + + /** + * Build a SecureRandom based on a SP 800-90A Hash DRBG. + * + * @param digest digest algorithm to use in the DRBG underneath the SecureRandom. + * @param nonce nonce value to use in DRBG construction. + * @param predictionResistant specify whether the underlying DRBG in the resulting SecureRandom should reseed on each request for bytes. + * @return a SecureRandom supported by a Hash DRBG. + */ + public SP800SecureRandom BuildHash(IDigest digest, byte[] nonce, bool predictionResistant) + { + return new SP800SecureRandom(mRandom, mEntropySourceProvider.Get(mEntropyBitsRequired), + new HashDrbgProvider(digest, nonce, mPersonalizationString, mSecurityStrength), predictionResistant); + } + + /** + * Build a SecureRandom based on a SP 800-90A CTR DRBG. + * + * @param cipher the block cipher to base the DRBG on. + * @param keySizeInBits key size in bits to be used with the block cipher. + * @param nonce nonce value to use in DRBG construction. + * @param predictionResistant specify whether the underlying DRBG in the resulting SecureRandom should reseed on each request for bytes. + * @return a SecureRandom supported by a CTR DRBG. + */ + public SP800SecureRandom BuildCtr(IBlockCipher cipher, int keySizeInBits, byte[] nonce, bool predictionResistant) + { + return new SP800SecureRandom(mRandom, mEntropySourceProvider.Get(mEntropyBitsRequired), + new CtrDrbgProvider(cipher, keySizeInBits, nonce, mPersonalizationString, mSecurityStrength), predictionResistant); + } + + /** + * Build a SecureRandom based on a SP 800-90A HMAC DRBG. + * + * @param hMac HMAC algorithm to use in the DRBG underneath the SecureRandom. + * @param nonce nonce value to use in DRBG construction. + * @param predictionResistant specify whether the underlying DRBG in the resulting SecureRandom should reseed on each request for bytes. + * @return a SecureRandom supported by a HMAC DRBG. + */ + public SP800SecureRandom BuildHMac(IMac hMac, byte[] nonce, bool predictionResistant) + { + return new SP800SecureRandom(mRandom, mEntropySourceProvider.Get(mEntropyBitsRequired), + new HMacDrbgProvider(hMac, nonce, mPersonalizationString, mSecurityStrength), predictionResistant); + } + + private class HashDrbgProvider + : IDrbgProvider + { + private readonly IDigest mDigest; + private readonly byte[] mNonce; + private readonly byte[] mPersonalizationString; + private readonly int mSecurityStrength; + + public HashDrbgProvider(IDigest digest, byte[] nonce, byte[] personalizationString, int securityStrength) + { + this.mDigest = digest; + this.mNonce = nonce; + this.mPersonalizationString = personalizationString; + this.mSecurityStrength = securityStrength; + } + + public ISP80090Drbg Get(IEntropySource entropySource) + { + return new HashSP800Drbg(mDigest, mSecurityStrength, entropySource, mPersonalizationString, mNonce); + } + } + + private class HMacDrbgProvider + : IDrbgProvider + { + private readonly IMac mHMac; + private readonly byte[] mNonce; + private readonly byte[] mPersonalizationString; + private readonly int mSecurityStrength; + + public HMacDrbgProvider(IMac hMac, byte[] nonce, byte[] personalizationString, int securityStrength) + { + this.mHMac = hMac; + this.mNonce = nonce; + this.mPersonalizationString = personalizationString; + this.mSecurityStrength = securityStrength; + } + + public ISP80090Drbg Get(IEntropySource entropySource) + { + return new HMacSP800Drbg(mHMac, mSecurityStrength, entropySource, mPersonalizationString, mNonce); + } + } + + private class CtrDrbgProvider + : IDrbgProvider + { + private readonly IBlockCipher mBlockCipher; + private readonly int mKeySizeInBits; + private readonly byte[] mNonce; + private readonly byte[] mPersonalizationString; + private readonly int mSecurityStrength; + + public CtrDrbgProvider(IBlockCipher blockCipher, int keySizeInBits, byte[] nonce, byte[] personalizationString, int securityStrength) + { + this.mBlockCipher = blockCipher; + this.mKeySizeInBits = keySizeInBits; + this.mNonce = nonce; + this.mPersonalizationString = personalizationString; + this.mSecurityStrength = securityStrength; + } + + public ISP80090Drbg Get(IEntropySource entropySource) + { + return new CtrSP800Drbg(mBlockCipher, mKeySizeInBits, mSecurityStrength, entropySource, mPersonalizationString, mNonce); + } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/prng/ThreadedSeedGenerator.cs b/bc-sharp-crypto/src/crypto/prng/ThreadedSeedGenerator.cs new file mode 100644 index 0000000..0a38e5f --- /dev/null +++ b/bc-sharp-crypto/src/crypto/prng/ThreadedSeedGenerator.cs @@ -0,0 +1,129 @@ +using System; +using System.Threading; + +#if NO_THREADS +using System.Threading.Tasks; +#endif + +namespace Org.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 + { +#if NETCF_1_0 + // No volatile keyword, but all fields implicitly volatile anyway + private int counter = 0; + private bool stop = false; +#else + private volatile int counter = 0; + private volatile bool stop = false; +#endif + + private void Run(object ignored) + { + while (!this.stop) + { + this.counter++; + } + } + + public byte[] GenerateSeed( + int numBytes, + bool fast) + { +#if SILVERLIGHT || PORTABLE + return DoGenerateSeed(numBytes, fast); +#else + ThreadPriority originalPriority = Thread.CurrentThread.Priority; + try + { + Thread.CurrentThread.Priority = ThreadPriority.Normal; + return DoGenerateSeed(numBytes, fast); + } + finally + { + Thread.CurrentThread.Priority = originalPriority; + } +#endif + } + + private byte[] DoGenerateSeed( + int numBytes, + bool fast) + { + this.counter = 0; + this.stop = false; + + byte[] result = new byte[numBytes]; + int last = 0; + int end = fast ? numBytes : numBytes * 8; + +#if NO_THREADS + Task.Factory.StartNew(() => Run(null), TaskCreationOptions.None); +#else + ThreadPool.QueueUserWorkItem(new WaitCallback(Run)); +#endif + + for (int i = 0; i < end; i++) + { + while (this.counter == last) + { + try + { +#if PORTABLE + new AutoResetEvent(false).WaitOne(1); +#else + Thread.Sleep(1); +#endif + } + catch (Exception) + { + // ignore + } + } + + last = this.counter; + + if (fast) + { + result[i] = (byte)last; + } + else + { + int bytepos = i / 8; + result[bytepos] = (byte)((result[bytepos] << 1) | (last & 1)); + } + } + + this.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, + bool fast) + { + return new SeedGenerator().GenerateSeed(numBytes, fast); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/prng/VMPCRandomGenerator.cs b/bc-sharp-crypto/src/crypto/prng/VMPCRandomGenerator.cs new file mode 100644 index 0000000..64f287d --- /dev/null +++ b/bc-sharp-crypto/src/crypto/prng/VMPCRandomGenerator.cs @@ -0,0 +1,114 @@ +using System; + +using Org.BouncyCastle.Crypto.Utilities; + +namespace Org.BouncyCastle.Crypto.Prng +{ + public class VmpcRandomGenerator + : IRandomGenerator + { + 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 P. + private byte s = (byte) 0xbe; + + public VmpcRandomGenerator() + { + } + + public virtual 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 virtual void AddSeedMaterial(long seed) + { + AddSeedMaterial(Pack.UInt64_To_BE((ulong)seed)); + } + + public virtual void NextBytes(byte[] bytes) + { + NextBytes(bytes, 0, bytes.Length); + } + + public virtual void NextBytes(byte[] bytes, int start, int len) + { + lock (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/bc-sharp-crypto/src/crypto/prng/X931Rng.cs b/bc-sharp-crypto/src/crypto/prng/X931Rng.cs new file mode 100644 index 0000000..2bd8e0c --- /dev/null +++ b/bc-sharp-crypto/src/crypto/prng/X931Rng.cs @@ -0,0 +1,146 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Prng +{ + internal class X931Rng + { + private const long BLOCK64_RESEED_MAX = 1L << (16 - 1); + private const long BLOCK128_RESEED_MAX = 1L << (24 - 1); + private const int BLOCK64_MAX_BITS_REQUEST = 1 << (13 - 1); + private const int BLOCK128_MAX_BITS_REQUEST = 1 << (19 - 1); + + private readonly IBlockCipher mEngine; + private readonly IEntropySource mEntropySource; + + private readonly byte[] mDT; + private readonly byte[] mI; + private readonly byte[] mR; + + private byte[] mV; + + private long mReseedCounter = 1; + + /** + * + * @param engine + * @param entropySource + */ + internal X931Rng(IBlockCipher engine, byte[] dateTimeVector, IEntropySource entropySource) + { + this.mEngine = engine; + this.mEntropySource = entropySource; + + this.mDT = new byte[engine.GetBlockSize()]; + + Array.Copy(dateTimeVector, 0, mDT, 0, mDT.Length); + + this.mI = new byte[engine.GetBlockSize()]; + this.mR = new byte[engine.GetBlockSize()]; + } + + /** + * Populate a passed in array with random data. + * + * @param output output array for generated bits. + * @param predictionResistant true if a reseed should be forced, false otherwise. + * + * @return number of bits generated, -1 if a reseed required. + */ + internal int Generate(byte[] output, bool predictionResistant) + { + if (mR.Length == 8) // 64 bit block size + { + if (mReseedCounter > BLOCK64_RESEED_MAX) + return -1; + + if (IsTooLarge(output, BLOCK64_MAX_BITS_REQUEST / 8)) + throw new ArgumentException("Number of bits per request limited to " + BLOCK64_MAX_BITS_REQUEST, "output"); + } + else + { + if (mReseedCounter > BLOCK128_RESEED_MAX) + return -1; + + if (IsTooLarge(output, BLOCK128_MAX_BITS_REQUEST / 8)) + throw new ArgumentException("Number of bits per request limited to " + BLOCK128_MAX_BITS_REQUEST, "output"); + } + + if (predictionResistant || mV == null) + { + mV = mEntropySource.GetEntropy(); + if (mV.Length != mEngine.GetBlockSize()) + throw new InvalidOperationException("Insufficient entropy returned"); + } + + int m = output.Length / mR.Length; + + for (int i = 0; i < m; i++) + { + mEngine.ProcessBlock(mDT, 0, mI, 0); + Process(mR, mI, mV); + Process(mV, mR, mI); + + Array.Copy(mR, 0, output, i * mR.Length, mR.Length); + + Increment(mDT); + } + + int bytesToCopy = (output.Length - m * mR.Length); + + if (bytesToCopy > 0) + { + mEngine.ProcessBlock(mDT, 0, mI, 0); + Process(mR, mI, mV); + Process(mV, mR, mI); + + Array.Copy(mR, 0, output, m * mR.Length, bytesToCopy); + + Increment(mDT); + } + + mReseedCounter++; + + return output.Length; + } + + /** + * Reseed the RNG. + */ + internal void Reseed() + { + mV = mEntropySource.GetEntropy(); + if (mV.Length != mEngine.GetBlockSize()) + throw new InvalidOperationException("Insufficient entropy returned"); + mReseedCounter = 1; + } + + internal IEntropySource EntropySource + { + get { return mEntropySource; } + } + + private void Process(byte[] res, byte[] a, byte[] b) + { + for (int i = 0; i != res.Length; i++) + { + res[i] = (byte)(a[i] ^ b[i]); + } + + mEngine.ProcessBlock(res, 0, res, 0); + } + + private void Increment(byte[] val) + { + for (int i = val.Length - 1; i >= 0; i--) + { + if (++val[i] != 0) + break; + } + } + + private static bool IsTooLarge(byte[] bytes, int maxBytes) + { + return bytes != null && bytes.Length > maxBytes; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/prng/X931SecureRandom.cs b/bc-sharp-crypto/src/crypto/prng/X931SecureRandom.cs new file mode 100644 index 0000000..d2e4849 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/prng/X931SecureRandom.cs @@ -0,0 +1,70 @@ +using System; + +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Prng +{ + public class X931SecureRandom + : SecureRandom + { + private readonly bool mPredictionResistant; + private readonly SecureRandom mRandomSource; + private readonly X931Rng mDrbg; + + internal X931SecureRandom(SecureRandom randomSource, X931Rng drbg, bool predictionResistant) + : base((IRandomGenerator)null) + { + this.mRandomSource = randomSource; + this.mDrbg = drbg; + this.mPredictionResistant = predictionResistant; + } + + public override void SetSeed(byte[] seed) + { + lock (this) + { + if (mRandomSource != null) + { + this.mRandomSource.SetSeed(seed); + } + } + } + + public override void SetSeed(long seed) + { + lock (this) + { + // this will happen when SecureRandom() is created + if (mRandomSource != null) + { + this.mRandomSource.SetSeed(seed); + } + } + } + + public override void NextBytes(byte[] bytes) + { + lock (this) + { + // check if a reseed is required... + if (mDrbg.Generate(bytes, mPredictionResistant) < 0) + { + mDrbg.Reseed(); + mDrbg.Generate(bytes, mPredictionResistant); + } + } + } + + public override void NextBytes(byte[] buf, int off, int len) + { + byte[] bytes = new byte[len]; + NextBytes(bytes); + Array.Copy(bytes, 0, buf, off, len); + } + + public override byte[] GenerateSeed(int numBytes) + { + return EntropyUtilities.GenerateSeed(mDrbg.EntropySource, numBytes); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/prng/X931SecureRandomBuilder.cs b/bc-sharp-crypto/src/crypto/prng/X931SecureRandomBuilder.cs new file mode 100644 index 0000000..31e9431 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/prng/X931SecureRandomBuilder.cs @@ -0,0 +1,87 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Utilities; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities.Date; + +namespace Org.BouncyCastle.Crypto.Prng +{ + public class X931SecureRandomBuilder + { + private readonly SecureRandom mRandom; // JDK 1.1 complains on final. + + private IEntropySourceProvider mEntropySourceProvider; + private byte[] mDateTimeVector; + + /** + * Basic constructor, creates a builder using an EntropySourceProvider based on the default SecureRandom with + * predictionResistant set to false. + *

+ * Any SecureRandom created from a builder constructed like this will make use of input passed to SecureRandom.setSeed() if + * the default SecureRandom does for its generateSeed() call. + *

+ */ + public X931SecureRandomBuilder() + : this(new SecureRandom(), false) + { + } + + /** + * Construct a builder with an EntropySourceProvider based on the passed in SecureRandom and the passed in value + * for prediction resistance. + *

+ * Any SecureRandom created from a builder constructed like this will make use of input passed to SecureRandom.setSeed() if + * the passed in SecureRandom does for its generateSeed() call. + *

+ * @param entropySource + * @param predictionResistant + */ + public X931SecureRandomBuilder(SecureRandom entropySource, bool predictionResistant) + { + this.mRandom = entropySource; + this.mEntropySourceProvider = new BasicEntropySourceProvider(mRandom, predictionResistant); + } + + /** + * Create a builder which makes creates the SecureRandom objects from a specified entropy source provider. + *

+ * Note: If this constructor is used any calls to setSeed() in the resulting SecureRandom will be ignored. + *

+ * @param entropySourceProvider a provider of EntropySource objects. + */ + public X931SecureRandomBuilder(IEntropySourceProvider entropySourceProvider) + { + this.mRandom = null; + this.mEntropySourceProvider = entropySourceProvider; + } + + public X931SecureRandomBuilder SetDateTimeVector(byte[] dateTimeVector) + { + this.mDateTimeVector = dateTimeVector; + return this; + } + + /** + * Construct a X9.31 secure random generator using the passed in engine and key. If predictionResistant is true the + * generator will be reseeded on each request. + * + * @param engine a block cipher to use as the operator. + * @param key the block cipher key to initialise engine with. + * @param predictionResistant true if engine to be reseeded on each use, false otherwise. + * @return a SecureRandom. + */ + public X931SecureRandom Build(IBlockCipher engine, KeyParameter key, bool predictionResistant) + { + if (mDateTimeVector == null) + { + mDateTimeVector = new byte[engine.GetBlockSize()]; + Pack.UInt64_To_BE((ulong)DateTimeUtilities.CurrentUnixMs(), mDateTimeVector, 0); + } + + engine.Init(true, key); + + return new X931SecureRandom(mRandom, new X931Rng(engine, mDateTimeVector, mEntropySourceProvider.Get(engine.GetBlockSize() * 8)), predictionResistant); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/prng/drbg/CtrSP800Drbg.cs b/bc-sharp-crypto/src/crypto/prng/drbg/CtrSP800Drbg.cs new file mode 100644 index 0000000..eca1821 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/prng/drbg/CtrSP800Drbg.cs @@ -0,0 +1,466 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Crypto.Prng.Drbg +{ + /** + * A SP800-90A CTR DRBG. + */ + public class CtrSP800Drbg + : ISP80090Drbg + { + private static readonly long TDEA_RESEED_MAX = 1L << (32 - 1); + private static readonly long AES_RESEED_MAX = 1L << (48 - 1); + private static readonly int TDEA_MAX_BITS_REQUEST = 1 << (13 - 1); + private static readonly int AES_MAX_BITS_REQUEST = 1 << (19 - 1); + + private readonly IEntropySource mEntropySource; + private readonly IBlockCipher mEngine; + private readonly int mKeySizeInBits; + private readonly int mSeedLength; + private readonly int mSecurityStrength; + + // internal state + private byte[] mKey; + private byte[] mV; + private long mReseedCounter = 0; + private bool mIsTdea = false; + + /** + * Construct a SP800-90A CTR DRBG. + *

+ * Minimum entropy requirement is the security strength requested. + *

+ * @param engine underlying block cipher to use to support DRBG + * @param keySizeInBits size of the key to use with the block cipher. + * @param securityStrength security strength required (in bits) + * @param entropySource source of entropy to use for seeding/reseeding. + * @param personalizationString personalization string to distinguish this DRBG (may be null). + * @param nonce nonce to further distinguish this DRBG (may be null). + */ + public CtrSP800Drbg(IBlockCipher engine, int keySizeInBits, int securityStrength, IEntropySource entropySource, + byte[] personalizationString, byte[] nonce) + { + if (securityStrength > 256) + throw new ArgumentException("Requested security strength is not supported by the derivation function"); + if (GetMaxSecurityStrength(engine, keySizeInBits) < securityStrength) + throw new ArgumentException("Requested security strength is not supported by block cipher and key size"); + if (entropySource.EntropySize < securityStrength) + throw new ArgumentException("Not enough entropy for security strength required"); + + mEntropySource = entropySource; + mEngine = engine; + + mKeySizeInBits = keySizeInBits; + mSecurityStrength = securityStrength; + mSeedLength = keySizeInBits + engine.GetBlockSize() * 8; + mIsTdea = IsTdea(engine); + + byte[] entropy = GetEntropy(); // Get_entropy_input + + CTR_DRBG_Instantiate_algorithm(entropy, nonce, personalizationString); + } + + private void CTR_DRBG_Instantiate_algorithm(byte[] entropy, byte[] nonce, byte[] personalisationString) + { + byte[] seedMaterial = Arrays.ConcatenateAll(entropy, nonce, personalisationString); + byte[] seed = Block_Cipher_df(seedMaterial, mSeedLength); + + int outlen = mEngine.GetBlockSize(); + + mKey = new byte[(mKeySizeInBits + 7) / 8]; + mV = new byte[outlen]; + + // mKey & mV are modified by this call + CTR_DRBG_Update(seed, mKey, mV); + + mReseedCounter = 1; + } + + private void CTR_DRBG_Update(byte[] seed, byte[] key, byte[] v) + { + byte[] temp = new byte[seed.Length]; + byte[] outputBlock = new byte[mEngine.GetBlockSize()]; + + int i = 0; + int outLen = mEngine.GetBlockSize(); + + mEngine.Init(true, new KeyParameter(ExpandKey(key))); + while (i*outLen < seed.Length) + { + AddOneTo(v); + mEngine.ProcessBlock(v, 0, outputBlock, 0); + + int bytesToCopy = ((temp.Length - i * outLen) > outLen) + ? outLen : (temp.Length - i * outLen); + + Array.Copy(outputBlock, 0, temp, i * outLen, bytesToCopy); + ++i; + } + + XOR(temp, seed, temp, 0); + + Array.Copy(temp, 0, key, 0, key.Length); + Array.Copy(temp, key.Length, v, 0, v.Length); + } + + private void CTR_DRBG_Reseed_algorithm(byte[] additionalInput) + { + byte[] seedMaterial = Arrays.Concatenate(GetEntropy(), additionalInput); + + seedMaterial = Block_Cipher_df(seedMaterial, mSeedLength); + + CTR_DRBG_Update(seedMaterial, mKey, mV); + + mReseedCounter = 1; + } + + private void XOR(byte[] output, byte[] a, byte[] b, int bOff) + { + for (int i = 0; i < output.Length; i++) + { + output[i] = (byte)(a[i] ^ b[bOff + i]); + } + } + + private void AddOneTo(byte[] longer) + { + uint carry = 1; + int i = longer.Length; + while (--i >= 0) + { + carry += longer[i]; + longer[i] = (byte)carry; + carry >>= 8; + } + } + + private byte[] GetEntropy() + { + byte[] entropy = mEntropySource.GetEntropy(); + if (entropy.Length < (mSecurityStrength + 7) / 8) + throw new InvalidOperationException("Insufficient entropy provided by entropy source"); + return entropy; + } + + // -- Internal state migration --- + + private static readonly byte[] K_BITS = Hex.Decode("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F"); + + // 1. If (number_of_bits_to_return > max_number_of_bits), then return an + // ERROR_FLAG. + // 2. L = len (input_string)/8. + // 3. N = number_of_bits_to_return/8. + // Comment: L is the bitstring represention of + // the integer resulting from len (input_string)/8. + // L shall be represented as a 32-bit integer. + // + // Comment : N is the bitstring represention of + // the integer resulting from + // number_of_bits_to_return/8. N shall be + // represented as a 32-bit integer. + // + // 4. S = L || N || input_string || 0x80. + // 5. While (len (S) mod outlen) + // Comment : Pad S with zeros, if necessary. + // 0, S = S || 0x00. + // + // Comment : Compute the starting value. + // 6. temp = the Null string. + // 7. i = 0. + // 8. K = Leftmost keylen bits of 0x00010203...1D1E1F. + // 9. While len (temp) < keylen + outlen, do + // + // IV = i || 0outlen - len (i). + // + // 9.1 + // + // temp = temp || BCC (K, (IV || S)). + // + // 9.2 + // + // i = i + 1. + // + // 9.3 + // + // Comment : i shall be represented as a 32-bit + // integer, i.e., len (i) = 32. + // + // Comment: The 32-bit integer represenation of + // i is padded with zeros to outlen bits. + // + // Comment: Compute the requested number of + // bits. + // + // 10. K = Leftmost keylen bits of temp. + // + // 11. X = Next outlen bits of temp. + // + // 12. temp = the Null string. + // + // 13. While len (temp) < number_of_bits_to_return, do + // + // 13.1 X = Block_Encrypt (K, X). + // + // 13.2 temp = temp || X. + // + // 14. requested_bits = Leftmost number_of_bits_to_return of temp. + // + // 15. Return SUCCESS and requested_bits. + private byte[] Block_Cipher_df(byte[] inputString, int bitLength) + { + int outLen = mEngine.GetBlockSize(); + int L = inputString.Length; // already in bytes + int N = bitLength / 8; + // 4 S = L || N || inputstring || 0x80 + int sLen = 4 + 4 + L + 1; + int blockLen = ((sLen + outLen - 1) / outLen) * outLen; + byte[] S = new byte[blockLen]; + copyIntToByteArray(S, L, 0); + copyIntToByteArray(S, N, 4); + Array.Copy(inputString, 0, S, 8, L); + S[8 + L] = (byte)0x80; + // S already padded with zeros + + byte[] temp = new byte[mKeySizeInBits / 8 + outLen]; + byte[] bccOut = new byte[outLen]; + + byte[] IV = new byte[outLen]; + + int i = 0; + byte[] K = new byte[mKeySizeInBits / 8]; + Array.Copy(K_BITS, 0, K, 0, K.Length); + + while (i*outLen*8 < mKeySizeInBits + outLen *8) + { + copyIntToByteArray(IV, i, 0); + BCC(bccOut, K, IV, S); + + int bytesToCopy = ((temp.Length - i * outLen) > outLen) + ? outLen + : (temp.Length - i * outLen); + + Array.Copy(bccOut, 0, temp, i * outLen, bytesToCopy); + ++i; + } + + byte[] X = new byte[outLen]; + Array.Copy(temp, 0, K, 0, K.Length); + Array.Copy(temp, K.Length, X, 0, X.Length); + + temp = new byte[bitLength / 2]; + + i = 0; + mEngine.Init(true, new KeyParameter(ExpandKey(K))); + + while (i * outLen < temp.Length) + { + mEngine.ProcessBlock(X, 0, X, 0); + + int bytesToCopy = ((temp.Length - i * outLen) > outLen) + ? outLen + : (temp.Length - i * outLen); + + Array.Copy(X, 0, temp, i * outLen, bytesToCopy); + i++; + } + + return temp; + } + + /* + * 1. chaining_value = 0^outlen + * . Comment: Set the first chaining value to outlen zeros. + * 2. n = len (data)/outlen. + * 3. Starting with the leftmost bits of data, split the data into n blocks of outlen bits + * each, forming block(1) to block(n). + * 4. For i = 1 to n do + * 4.1 input_block = chaining_value ^ block(i) . + * 4.2 chaining_value = Block_Encrypt (Key, input_block). + * 5. output_block = chaining_value. + * 6. Return output_block. + */ + private void BCC(byte[] bccOut, byte[] k, byte[] iV, byte[] data) + { + int outlen = mEngine.GetBlockSize(); + byte[] chainingValue = new byte[outlen]; // initial values = 0 + int n = data.Length / outlen; + + byte[] inputBlock = new byte[outlen]; + + mEngine.Init(true, new KeyParameter(ExpandKey(k))); + + mEngine.ProcessBlock(iV, 0, chainingValue, 0); + + for (int i = 0; i < n; i++) + { + XOR(inputBlock, chainingValue, data, i*outlen); + mEngine.ProcessBlock(inputBlock, 0, chainingValue, 0); + } + + Array.Copy(chainingValue, 0, bccOut, 0, bccOut.Length); + } + + private void copyIntToByteArray(byte[] buf, int value, int offSet) + { + buf[offSet + 0] = ((byte)(value >> 24)); + buf[offSet + 1] = ((byte)(value >> 16)); + buf[offSet + 2] = ((byte)(value >> 8)); + buf[offSet + 3] = ((byte)(value)); + } + + /** + * Return the block size (in bits) of the DRBG. + * + * @return the number of bits produced on each internal round of the DRBG. + */ + public int BlockSize + { + get { return mV.Length * 8; } + } + + /** + * Populate a passed in array with random data. + * + * @param output output array for generated bits. + * @param additionalInput additional input to be added to the DRBG in this step. + * @param predictionResistant true if a reseed should be forced, false otherwise. + * + * @return number of bits generated, -1 if a reseed required. + */ + public int Generate(byte[] output, byte[] additionalInput, bool predictionResistant) + { + if (mIsTdea) + { + if (mReseedCounter > TDEA_RESEED_MAX) + return -1; + + if (DrbgUtilities.IsTooLarge(output, TDEA_MAX_BITS_REQUEST / 8)) + throw new ArgumentException("Number of bits per request limited to " + TDEA_MAX_BITS_REQUEST, "output"); + } + else + { + if (mReseedCounter > AES_RESEED_MAX) + return -1; + + if (DrbgUtilities.IsTooLarge(output, AES_MAX_BITS_REQUEST / 8)) + throw new ArgumentException("Number of bits per request limited to " + AES_MAX_BITS_REQUEST, "output"); + } + + if (predictionResistant) + { + CTR_DRBG_Reseed_algorithm(additionalInput); + additionalInput = null; + } + + if (additionalInput != null) + { + additionalInput = Block_Cipher_df(additionalInput, mSeedLength); + CTR_DRBG_Update(additionalInput, mKey, mV); + } + else + { + additionalInput = new byte[mSeedLength]; + } + + byte[] tmp = new byte[mV.Length]; + + mEngine.Init(true, new KeyParameter(ExpandKey(mKey))); + + for (int i = 0; i <= output.Length / tmp.Length; i++) + { + int bytesToCopy = ((output.Length - i * tmp.Length) > tmp.Length) + ? tmp.Length + : (output.Length - i * mV.Length); + + if (bytesToCopy != 0) + { + AddOneTo(mV); + + mEngine.ProcessBlock(mV, 0, tmp, 0); + + Array.Copy(tmp, 0, output, i * tmp.Length, bytesToCopy); + } + } + + CTR_DRBG_Update(additionalInput, mKey, mV); + + mReseedCounter++; + + return output.Length * 8; + } + + /** + * Reseed the DRBG. + * + * @param additionalInput additional input to be added to the DRBG in this step. + */ + public void Reseed(byte[] additionalInput) + { + CTR_DRBG_Reseed_algorithm(additionalInput); + } + + private bool IsTdea(IBlockCipher cipher) + { + return cipher.AlgorithmName.Equals("DESede") || cipher.AlgorithmName.Equals("TDEA"); + } + + private int GetMaxSecurityStrength(IBlockCipher cipher, int keySizeInBits) + { + if (IsTdea(cipher) && keySizeInBits == 168) + { + return 112; + } + if (cipher.AlgorithmName.Equals("AES")) + { + return keySizeInBits; + } + + return -1; + } + + private byte[] ExpandKey(byte[] key) + { + if (mIsTdea) + { + // expand key to 192 bits. + byte[] tmp = new byte[24]; + + PadKey(key, 0, tmp, 0); + PadKey(key, 7, tmp, 8); + PadKey(key, 14, tmp, 16); + + return tmp; + } + else + { + return key; + } + } + + /** + * Pad out a key for TDEA, setting odd parity for each byte. + * + * @param keyMaster + * @param keyOff + * @param tmp + * @param tmpOff + */ + private void PadKey(byte[] keyMaster, int keyOff, byte[] tmp, int tmpOff) + { + tmp[tmpOff + 0] = (byte)(keyMaster[keyOff + 0] & 0xfe); + tmp[tmpOff + 1] = (byte)((keyMaster[keyOff + 0] << 7) | ((keyMaster[keyOff + 1] & 0xfc) >> 1)); + tmp[tmpOff + 2] = (byte)((keyMaster[keyOff + 1] << 6) | ((keyMaster[keyOff + 2] & 0xf8) >> 2)); + tmp[tmpOff + 3] = (byte)((keyMaster[keyOff + 2] << 5) | ((keyMaster[keyOff + 3] & 0xf0) >> 3)); + tmp[tmpOff + 4] = (byte)((keyMaster[keyOff + 3] << 4) | ((keyMaster[keyOff + 4] & 0xe0) >> 4)); + tmp[tmpOff + 5] = (byte)((keyMaster[keyOff + 4] << 3) | ((keyMaster[keyOff + 5] & 0xc0) >> 5)); + tmp[tmpOff + 6] = (byte)((keyMaster[keyOff + 5] << 2) | ((keyMaster[keyOff + 6] & 0x80) >> 6)); + tmp[tmpOff + 7] = (byte)(keyMaster[keyOff + 6] << 1); + + DesParameters.SetOddParity(tmp, tmpOff, 8); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/prng/drbg/DrbgUtilities.cs b/bc-sharp-crypto/src/crypto/prng/drbg/DrbgUtilities.cs new file mode 100644 index 0000000..d9a1c43 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/prng/drbg/DrbgUtilities.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Prng.Drbg +{ + internal class DrbgUtilities + { + private static readonly IDictionary maxSecurityStrengths = Platform.CreateHashtable(); + + static DrbgUtilities() + { + maxSecurityStrengths.Add("SHA-1", 128); + + maxSecurityStrengths.Add("SHA-224", 192); + maxSecurityStrengths.Add("SHA-256", 256); + maxSecurityStrengths.Add("SHA-384", 256); + maxSecurityStrengths.Add("SHA-512", 256); + + maxSecurityStrengths.Add("SHA-512/224", 192); + maxSecurityStrengths.Add("SHA-512/256", 256); + } + + internal static int GetMaxSecurityStrength(IDigest d) + { + return (int)maxSecurityStrengths[d.AlgorithmName]; + } + + internal static int GetMaxSecurityStrength(IMac m) + { + string name = m.AlgorithmName; + + return (int)maxSecurityStrengths[name.Substring(0, name.IndexOf("/"))]; + } + + /** + * Used by both Dual EC and Hash. + */ + internal static byte[] HashDF(IDigest digest, byte[] seedMaterial, int seedLength) + { + // 1. temp = the Null string. + // 2. . + // 3. counter = an 8-bit binary value representing the integer "1". + // 4. For i = 1 to len do + // Comment : In step 4.1, no_of_bits_to_return + // is used as a 32-bit string. + // 4.1 temp = temp || Hash (counter || no_of_bits_to_return || + // input_string). + // 4.2 counter = counter + 1. + // 5. requested_bits = Leftmost (no_of_bits_to_return) of temp. + // 6. Return SUCCESS and requested_bits. + byte[] temp = new byte[(seedLength + 7) / 8]; + + int len = temp.Length / digest.GetDigestSize(); + int counter = 1; + + byte[] dig = new byte[digest.GetDigestSize()]; + + for (int i = 0; i <= len; i++) + { + digest.Update((byte)counter); + + digest.Update((byte)(seedLength >> 24)); + digest.Update((byte)(seedLength >> 16)); + digest.Update((byte)(seedLength >> 8)); + digest.Update((byte)seedLength); + + digest.BlockUpdate(seedMaterial, 0, seedMaterial.Length); + + digest.DoFinal(dig, 0); + + int bytesToCopy = ((temp.Length - i * dig.Length) > dig.Length) + ? dig.Length + : (temp.Length - i * dig.Length); + Array.Copy(dig, 0, temp, i * dig.Length, bytesToCopy); + + counter++; + } + + // do a left shift to get rid of excess bits. + if (seedLength % 8 != 0) + { + int shift = 8 - (seedLength % 8); + uint carry = 0; + + for (int i = 0; i != temp.Length; i++) + { + uint b = temp[i]; + temp[i] = (byte)((b >> shift) | (carry << (8 - shift))); + carry = b; + } + } + + return temp; + } + + internal static bool IsTooLarge(byte[] bytes, int maxBytes) + { + return bytes != null && bytes.Length > maxBytes; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/prng/drbg/HMacSP800Drbg.cs b/bc-sharp-crypto/src/crypto/prng/drbg/HMacSP800Drbg.cs new file mode 100644 index 0000000..7833170 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/prng/drbg/HMacSP800Drbg.cs @@ -0,0 +1,186 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Prng.Drbg +{ + /** + * A SP800-90A HMAC DRBG. + */ + public class HMacSP800Drbg + : ISP80090Drbg + { + private readonly static long RESEED_MAX = 1L << (48 - 1); + private readonly static int MAX_BITS_REQUEST = 1 << (19 - 1); + + private readonly byte[] mK; + private readonly byte[] mV; + private readonly IEntropySource mEntropySource; + private readonly IMac mHMac; + private readonly int mSecurityStrength; + + private long mReseedCounter; + + /** + * Construct a SP800-90A Hash DRBG. + *

+ * Minimum entropy requirement is the security strength requested. + *

+ * @param hMac Hash MAC to base the DRBG on. + * @param securityStrength security strength required (in bits) + * @param entropySource source of entropy to use for seeding/reseeding. + * @param personalizationString personalization string to distinguish this DRBG (may be null). + * @param nonce nonce to further distinguish this DRBG (may be null). + */ + public HMacSP800Drbg(IMac hMac, int securityStrength, IEntropySource entropySource, byte[] personalizationString, byte[] nonce) + { + if (securityStrength > DrbgUtilities.GetMaxSecurityStrength(hMac)) + throw new ArgumentException("Requested security strength is not supported by the derivation function"); + if (entropySource.EntropySize < securityStrength) + throw new ArgumentException("Not enough entropy for security strength required"); + + mHMac = hMac; + mSecurityStrength = securityStrength; + mEntropySource = entropySource; + + byte[] entropy = GetEntropy(); + byte[] seedMaterial = Arrays.ConcatenateAll(entropy, nonce, personalizationString); + + mK = new byte[hMac.GetMacSize()]; + mV = new byte[mK.Length]; + Arrays.Fill(mV, (byte)1); + + hmac_DRBG_Update(seedMaterial); + + mReseedCounter = 1; + } + + private void hmac_DRBG_Update(byte[] seedMaterial) + { + hmac_DRBG_Update_Func(seedMaterial, (byte)0x00); + if (seedMaterial != null) + { + hmac_DRBG_Update_Func(seedMaterial, (byte)0x01); + } + } + + private void hmac_DRBG_Update_Func(byte[] seedMaterial, byte vValue) + { + mHMac.Init(new KeyParameter(mK)); + + mHMac.BlockUpdate(mV, 0, mV.Length); + mHMac.Update(vValue); + + if (seedMaterial != null) + { + mHMac.BlockUpdate(seedMaterial, 0, seedMaterial.Length); + } + + mHMac.DoFinal(mK, 0); + + mHMac.Init(new KeyParameter(mK)); + mHMac.BlockUpdate(mV, 0, mV.Length); + + mHMac.DoFinal(mV, 0); + } + + /** + * Return the block size (in bits) of the DRBG. + * + * @return the number of bits produced on each round of the DRBG. + */ + public int BlockSize + { + get { return mV.Length * 8; } + } + + /** + * Populate a passed in array with random data. + * + * @param output output array for generated bits. + * @param additionalInput additional input to be added to the DRBG in this step. + * @param predictionResistant true if a reseed should be forced, false otherwise. + * + * @return number of bits generated, -1 if a reseed required. + */ + public int Generate(byte[] output, byte[] additionalInput, bool predictionResistant) + { + int numberOfBits = output.Length * 8; + + if (numberOfBits > MAX_BITS_REQUEST) + throw new ArgumentException("Number of bits per request limited to " + MAX_BITS_REQUEST, "output"); + + if (mReseedCounter > RESEED_MAX) + { + return -1; + } + + if (predictionResistant) + { + Reseed(additionalInput); + additionalInput = null; + } + + // 2. + if (additionalInput != null) + { + hmac_DRBG_Update(additionalInput); + } + + // 3. + byte[] rv = new byte[output.Length]; + + int m = output.Length / mV.Length; + + mHMac.Init(new KeyParameter(mK)); + + for (int i = 0; i < m; i++) + { + mHMac.BlockUpdate(mV, 0, mV.Length); + mHMac.DoFinal(mV, 0); + + Array.Copy(mV, 0, rv, i * mV.Length, mV.Length); + } + + if (m * mV.Length < rv.Length) + { + mHMac.BlockUpdate(mV, 0, mV.Length); + mHMac.DoFinal(mV, 0); + + Array.Copy(mV, 0, rv, m * mV.Length, rv.Length - (m * mV.Length)); + } + + hmac_DRBG_Update(additionalInput); + + mReseedCounter++; + + Array.Copy(rv, 0, output, 0, output.Length); + + return numberOfBits; + } + + /** + * Reseed the DRBG. + * + * @param additionalInput additional input to be added to the DRBG in this step. + */ + public void Reseed(byte[] additionalInput) + { + byte[] entropy = GetEntropy(); + byte[] seedMaterial = Arrays.Concatenate(entropy, additionalInput); + + hmac_DRBG_Update(seedMaterial); + + mReseedCounter = 1; + } + + private byte[] GetEntropy() + { + byte[] entropy = mEntropySource.GetEntropy(); + if (entropy.Length < (mSecurityStrength + 7) / 8) + throw new InvalidOperationException("Insufficient entropy provided by entropy source"); + return entropy; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/prng/drbg/HashSP800Drbg.cs b/bc-sharp-crypto/src/crypto/prng/drbg/HashSP800Drbg.cs new file mode 100644 index 0000000..493da5a --- /dev/null +++ b/bc-sharp-crypto/src/crypto/prng/drbg/HashSP800Drbg.cs @@ -0,0 +1,287 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Prng.Drbg +{ + /** + * A SP800-90A Hash DRBG. + */ + public class HashSP800Drbg + : ISP80090Drbg + { + private readonly static byte[] ONE = { 0x01 }; + + private readonly static long RESEED_MAX = 1L << (48 - 1); + private readonly static int MAX_BITS_REQUEST = 1 << (19 - 1); + + private static readonly IDictionary seedlens = Platform.CreateHashtable(); + + static HashSP800Drbg() + { + seedlens.Add("SHA-1", 440); + seedlens.Add("SHA-224", 440); + seedlens.Add("SHA-256", 440); + seedlens.Add("SHA-512/256", 440); + seedlens.Add("SHA-512/224", 440); + seedlens.Add("SHA-384", 888); + seedlens.Add("SHA-512", 888); + } + + private readonly IDigest mDigest; + private readonly IEntropySource mEntropySource; + private readonly int mSecurityStrength; + private readonly int mSeedLength; + + private byte[] mV; + private byte[] mC; + private long mReseedCounter; + + /** + * Construct a SP800-90A Hash DRBG. + *

+ * Minimum entropy requirement is the security strength requested. + *

+ * @param digest source digest to use for DRB stream. + * @param securityStrength security strength required (in bits) + * @param entropySource source of entropy to use for seeding/reseeding. + * @param personalizationString personalization string to distinguish this DRBG (may be null). + * @param nonce nonce to further distinguish this DRBG (may be null). + */ + public HashSP800Drbg(IDigest digest, int securityStrength, IEntropySource entropySource, byte[] personalizationString, byte[] nonce) + { + if (securityStrength > DrbgUtilities.GetMaxSecurityStrength(digest)) + throw new ArgumentException("Requested security strength is not supported by the derivation function"); + if (entropySource.EntropySize < securityStrength) + throw new ArgumentException("Not enough entropy for security strength required"); + + mDigest = digest; + mEntropySource = entropySource; + mSecurityStrength = securityStrength; + mSeedLength = (int)seedlens[digest.AlgorithmName]; + + // 1. seed_material = entropy_input || nonce || personalization_string. + // 2. seed = Hash_df (seed_material, seedlen). + // 3. V = seed. + // 4. C = Hash_df ((0x00 || V), seedlen). Comment: Preceed V with a byte + // of zeros. + // 5. reseed_counter = 1. + // 6. Return V, C, and reseed_counter as the initial_working_state + + byte[] entropy = GetEntropy(); + byte[] seedMaterial = Arrays.ConcatenateAll(entropy, nonce, personalizationString); + byte[] seed = DrbgUtilities.HashDF(mDigest, seedMaterial, mSeedLength); + + mV = seed; + byte[] subV = new byte[mV.Length + 1]; + Array.Copy(mV, 0, subV, 1, mV.Length); + mC = DrbgUtilities.HashDF(mDigest, subV, mSeedLength); + + mReseedCounter = 1; + } + + /** + * Return the block size (in bits) of the DRBG. + * + * @return the number of bits produced on each internal round of the DRBG. + */ + public int BlockSize + { + get { return mDigest.GetDigestSize () * 8; } + } + + /** + * Populate a passed in array with random data. + * + * @param output output array for generated bits. + * @param additionalInput additional input to be added to the DRBG in this step. + * @param predictionResistant true if a reseed should be forced, false otherwise. + * + * @return number of bits generated, -1 if a reseed required. + */ + public int Generate(byte[] output, byte[] additionalInput, bool predictionResistant) + { + // 1. If reseed_counter > reseed_interval, then return an indication that a + // reseed is required. + // 2. If (additional_input != Null), then do + // 2.1 w = Hash (0x02 || V || additional_input). + // 2.2 V = (V + w) mod 2^seedlen + // . + // 3. (returned_bits) = Hashgen (requested_number_of_bits, V). + // 4. H = Hash (0x03 || V). + // 5. V = (V + H + C + reseed_counter) mod 2^seedlen + // . + // 6. reseed_counter = reseed_counter + 1. + // 7. Return SUCCESS, returned_bits, and the new values of V, C, and + // reseed_counter for the new_working_state. + int numberOfBits = output.Length * 8; + + if (numberOfBits > MAX_BITS_REQUEST) + throw new ArgumentException("Number of bits per request limited to " + MAX_BITS_REQUEST, "output"); + + if (mReseedCounter > RESEED_MAX) + return -1; + + if (predictionResistant) + { + Reseed(additionalInput); + additionalInput = null; + } + + // 2. + if (additionalInput != null) + { + byte[] newInput = new byte[1 + mV.Length + additionalInput.Length]; + newInput[0] = 0x02; + Array.Copy(mV, 0, newInput, 1, mV.Length); + // TODO: inOff / inLength + Array.Copy(additionalInput, 0, newInput, 1 + mV.Length, additionalInput.Length); + byte[] w = Hash(newInput); + + AddTo(mV, w); + } + + // 3. + byte[] rv = hashgen(mV, numberOfBits); + + // 4. + byte[] subH = new byte[mV.Length + 1]; + Array.Copy(mV, 0, subH, 1, mV.Length); + subH[0] = 0x03; + + byte[] H = Hash(subH); + + // 5. + AddTo(mV, H); + AddTo(mV, mC); + byte[] c = new byte[4]; + c[0] = (byte)(mReseedCounter >> 24); + c[1] = (byte)(mReseedCounter >> 16); + c[2] = (byte)(mReseedCounter >> 8); + c[3] = (byte)mReseedCounter; + + AddTo(mV, c); + + mReseedCounter++; + + Array.Copy(rv, 0, output, 0, output.Length); + + return numberOfBits; + } + + private byte[] GetEntropy() + { + byte[] entropy = mEntropySource.GetEntropy(); + if (entropy.Length < (mSecurityStrength + 7) / 8) + throw new InvalidOperationException("Insufficient entropy provided by entropy source"); + return entropy; + } + + // this will always add the shorter length byte array mathematically to the + // longer length byte array. + // be careful.... + private void AddTo(byte[] longer, byte[] shorter) + { + int off = longer.Length - shorter.Length; + + uint carry = 0; + int i = shorter.Length; + while (--i >= 0) + { + carry += (uint)longer[off + i] + (uint)shorter[i]; + longer[off + i] = (byte)carry; + carry >>= 8; + } + + i = off; + while (--i >= 0) + { + carry += longer[i]; + longer[i] = (byte)carry; + carry >>= 8; + } + } + + /** + * Reseed the DRBG. + * + * @param additionalInput additional input to be added to the DRBG in this step. + */ + public void Reseed(byte[] additionalInput) + { + // 1. seed_material = 0x01 || V || entropy_input || additional_input. + // + // 2. seed = Hash_df (seed_material, seedlen). + // + // 3. V = seed. + // + // 4. C = Hash_df ((0x00 || V), seedlen). + // + // 5. reseed_counter = 1. + // + // 6. Return V, C, and reseed_counter for the new_working_state. + // + // Comment: Precede with a byte of all zeros. + byte[] entropy = GetEntropy(); + byte[] seedMaterial = Arrays.ConcatenateAll(ONE, mV, entropy, additionalInput); + byte[] seed = DrbgUtilities.HashDF(mDigest, seedMaterial, mSeedLength); + + mV = seed; + byte[] subV = new byte[mV.Length + 1]; + subV[0] = 0x00; + Array.Copy(mV, 0, subV, 1, mV.Length); + mC = DrbgUtilities.HashDF(mDigest, subV, mSeedLength); + + mReseedCounter = 1; + } + + private byte[] Hash(byte[] input) + { + byte[] hash = new byte[mDigest.GetDigestSize()]; + DoHash(input, hash); + return hash; + } + + private void DoHash(byte[] input, byte[] output) + { + mDigest.BlockUpdate(input, 0, input.Length); + mDigest.DoFinal(output, 0); + } + + // 1. m = [requested_number_of_bits / outlen] + // 2. data = V. + // 3. W = the Null string. + // 4. For i = 1 to m + // 4.1 wi = Hash (data). + // 4.2 W = W || wi. + // 4.3 data = (data + 1) mod 2^seedlen + // . + // 5. returned_bits = Leftmost (requested_no_of_bits) bits of W. + private byte[] hashgen(byte[] input, int lengthInBits) + { + int digestSize = mDigest.GetDigestSize(); + int m = (lengthInBits / 8) / digestSize; + + byte[] data = new byte[input.Length]; + Array.Copy(input, 0, data, 0, input.Length); + + byte[] W = new byte[lengthInBits / 8]; + + byte[] dig = new byte[mDigest.GetDigestSize()]; + for (int i = 0; i <= m; i++) + { + DoHash(data, dig); + + int bytesToCopy = ((W.Length - i * dig.Length) > dig.Length) + ? dig.Length + : (W.Length - i * dig.Length); + Array.Copy(dig, 0, W, i * dig.Length, bytesToCopy); + + AddTo(data, ONE); + } + + return W; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/prng/drbg/ISP80090Drbg.cs b/bc-sharp-crypto/src/crypto/prng/drbg/ISP80090Drbg.cs new file mode 100644 index 0000000..0e39820 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/prng/drbg/ISP80090Drbg.cs @@ -0,0 +1,35 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Prng.Drbg +{ + /** + * Interface to SP800-90A deterministic random bit generators. + */ + public interface ISP80090Drbg + { + /** + * Return the block size of the DRBG. + * + * @return the block size (in bits) produced by each round of the DRBG. + */ + int BlockSize { get; } + + /** + * Populate a passed in array with random data. + * + * @param output output array for generated bits. + * @param additionalInput additional input to be added to the DRBG in this step. + * @param predictionResistant true if a reseed should be forced, false otherwise. + * + * @return number of bits generated, -1 if a reseed required. + */ + int Generate(byte[] output, byte[] additionalInput, bool predictionResistant); + + /** + * Reseed the DRBG. + * + * @param additionalInput additional input to be added to the DRBG in this step. + */ + void Reseed(byte[] additionalInput); + } +} diff --git a/bc-sharp-crypto/src/crypto/signers/DsaDigestSigner.cs b/bc-sharp-crypto/src/crypto/signers/DsaDigestSigner.cs new file mode 100644 index 0000000..0866014 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/signers/DsaDigestSigner.cs @@ -0,0 +1,145 @@ +using System; +using System.Collections; +using System.IO; +using System.Text; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Crypto.Signers; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Signers +{ + public class DsaDigestSigner + : ISigner + { + private readonly IDigest digest; + private readonly IDsa dsaSigner; + private bool forSigning; + + public DsaDigestSigner( + IDsa signer, + IDigest digest) + { + this.digest = digest; + this.dsaSigner = signer; + } + + public virtual string AlgorithmName + { + get { return digest.AlgorithmName + "with" + dsaSigner.AlgorithmName; } + } + + public virtual void Init( + bool forSigning, + ICipherParameters parameters) + { + this.forSigning = forSigning; + + AsymmetricKeyParameter k; + + if (parameters is ParametersWithRandom) + { + k = (AsymmetricKeyParameter)((ParametersWithRandom)parameters).Parameters; + } + else + { + k = (AsymmetricKeyParameter)parameters; + } + + if (forSigning && !k.IsPrivate) + throw new InvalidKeyException("Signing Requires Private Key."); + + if (!forSigning && k.IsPrivate) + throw new InvalidKeyException("Verification Requires Public Key."); + + Reset(); + + dsaSigner.Init(forSigning, parameters); + } + + /** + * update the internal digest with the byte b + */ + public virtual void Update( + byte input) + { + digest.Update(input); + } + + /** + * update the internal digest with the byte array in + */ + public virtual void BlockUpdate( + byte[] input, + int inOff, + int length) + { + digest.BlockUpdate(input, inOff, length); + } + + /** + * Generate a signature for the message we've been loaded with using + * the key we were initialised with. + */ + public virtual byte[] GenerateSignature() + { + if (!forSigning) + throw new InvalidOperationException("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]); + } + + /// true if the internal state represents the signature described in the passed in array. + public virtual bool VerifySignature( + byte[] signature) + { + if (forSigning) + throw new InvalidOperationException("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) + { + return false; + } + } + + /// Reset the internal state + public virtual void Reset() + { + digest.Reset(); + } + + private byte[] DerEncode( + BigInteger r, + BigInteger s) + { + return new DerSequence(new DerInteger(r), new DerInteger(s)).GetDerEncoded(); + } + + private BigInteger[] DerDecode( + byte[] encoding) + { + Asn1Sequence s = (Asn1Sequence) Asn1Object.FromByteArray(encoding); + + return new BigInteger[] + { + ((DerInteger) s[0]).Value, + ((DerInteger) s[1]).Value + }; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/signers/DsaSigner.cs b/bc-sharp-crypto/src/crypto/signers/DsaSigner.cs new file mode 100644 index 0000000..bb28add --- /dev/null +++ b/bc-sharp-crypto/src/crypto/signers/DsaSigner.cs @@ -0,0 +1,156 @@ +using System; + +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Signers +{ + /** + * The Digital Signature Algorithm - as described in "Handbook of Applied + * Cryptography", pages 452 - 453. + */ + public class DsaSigner + : IDsa + { + protected readonly IDsaKCalculator kCalculator; + + protected DsaKeyParameters key = null; + protected SecureRandom random = null; + + /** + * Default configuration, random K values. + */ + public DsaSigner() + { + this.kCalculator = new RandomDsaKCalculator(); + } + + /** + * Configuration with an alternate, possibly deterministic calculator of K. + * + * @param kCalculator a K value calculator. + */ + public DsaSigner(IDsaKCalculator kCalculator) + { + this.kCalculator = kCalculator; + } + + public virtual string AlgorithmName + { + get { return "DSA"; } + } + + public virtual void Init(bool forSigning, ICipherParameters parameters) + { + SecureRandom providedRandom = null; + + if (forSigning) + { + if (parameters is ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)parameters; + + providedRandom = rParam.Random; + parameters = rParam.Parameters; + } + + if (!(parameters is DsaPrivateKeyParameters)) + throw new InvalidKeyException("DSA private key required for signing"); + + this.key = (DsaPrivateKeyParameters)parameters; + } + else + { + if (!(parameters is DsaPublicKeyParameters)) + throw new InvalidKeyException("DSA public key required for verification"); + + this.key = (DsaPublicKeyParameters)parameters; + } + + this.random = InitSecureRandom(forSigning && !kCalculator.IsDeterministic, providedRandom); + } + + /** + * 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 virtual BigInteger[] GenerateSignature(byte[] message) + { + DsaParameters parameters = key.Parameters; + BigInteger q = parameters.Q; + BigInteger m = CalculateE(q, message); + BigInteger x = ((DsaPrivateKeyParameters)key).X; + + if (kCalculator.IsDeterministic) + { + kCalculator.Init(q, x, message); + } + else + { + kCalculator.Init(q, random); + } + + BigInteger k = kCalculator.NextK(); + + BigInteger r = parameters.G.ModPow(k, parameters.P).Mod(q); + + k = k.ModInverse(q).Multiply(m.Add(x.Multiply(r))); + + BigInteger s = k.Mod(q); + + return new BigInteger[]{ r, s }; + } + + /** + * 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 virtual bool VerifySignature(byte[] message, BigInteger r, BigInteger s) + { + DsaParameters parameters = key.Parameters; + BigInteger q = parameters.Q; + BigInteger m = CalculateE(q, message); + + if (r.SignValue <= 0 || q.CompareTo(r) <= 0) + { + return false; + } + + if (s.SignValue <= 0 || q.CompareTo(s) <= 0) + { + return false; + } + + BigInteger w = s.ModInverse(q); + + BigInteger u1 = m.Multiply(w).Mod(q); + BigInteger u2 = r.Multiply(w).Mod(q); + + BigInteger p = parameters.P; + u1 = parameters.G.ModPow(u1, p); + u2 = ((DsaPublicKeyParameters)key).Y.ModPow(u2, p); + + BigInteger v = u1.Multiply(u2).Mod(p).Mod(q); + + return v.Equals(r); + } + + protected virtual BigInteger CalculateE(BigInteger n, byte[] message) + { + int length = System.Math.Min(message.Length, n.BitLength / 8); + + return new BigInteger(1, message, 0, length); + } + + protected virtual SecureRandom InitSecureRandom(bool needed, SecureRandom provided) + { + return !needed ? null : (provided != null) ? provided : new SecureRandom(); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/signers/ECDsaSigner.cs b/bc-sharp-crypto/src/crypto/signers/ECDsaSigner.cs new file mode 100644 index 0000000..520507b --- /dev/null +++ b/bc-sharp-crypto/src/crypto/signers/ECDsaSigner.cs @@ -0,0 +1,240 @@ +using System; + +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Math.EC; +using Org.BouncyCastle.Math.EC.Multiplier; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Signers +{ + /** + * EC-DSA as described in X9.62 + */ + public class ECDsaSigner + : IDsa + { + private static readonly BigInteger Eight = BigInteger.ValueOf(8); + + protected readonly IDsaKCalculator kCalculator; + + protected ECKeyParameters key = null; + protected SecureRandom random = null; + + /** + * Default configuration, random K values. + */ + public ECDsaSigner() + { + this.kCalculator = new RandomDsaKCalculator(); + } + + /** + * Configuration with an alternate, possibly deterministic calculator of K. + * + * @param kCalculator a K value calculator. + */ + public ECDsaSigner(IDsaKCalculator kCalculator) + { + this.kCalculator = kCalculator; + } + + public virtual string AlgorithmName + { + get { return "ECDSA"; } + } + + public virtual void Init(bool forSigning, ICipherParameters parameters) + { + SecureRandom providedRandom = null; + + if (forSigning) + { + if (parameters is ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)parameters; + + providedRandom = rParam.Random; + parameters = rParam.Parameters; + } + + if (!(parameters is ECPrivateKeyParameters)) + throw new InvalidKeyException("EC private key required for signing"); + + this.key = (ECPrivateKeyParameters)parameters; + } + else + { + if (!(parameters is ECPublicKeyParameters)) + throw new InvalidKeyException("EC public key required for verification"); + + this.key = (ECPublicKeyParameters)parameters; + } + + this.random = InitSecureRandom(forSigning && !kCalculator.IsDeterministic, providedRandom); + } + + // 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 virtual BigInteger[] GenerateSignature(byte[] message) + { + ECDomainParameters ec = key.Parameters; + BigInteger n = ec.N; + BigInteger e = CalculateE(n, message); + BigInteger d = ((ECPrivateKeyParameters)key).D; + + if (kCalculator.IsDeterministic) + { + kCalculator.Init(n, d, message); + } + else + { + kCalculator.Init(n, random); + } + + BigInteger r, s; + + ECMultiplier basePointMultiplier = CreateBasePointMultiplier(); + + // 5.3.2 + do // Generate s + { + BigInteger k; + do // Generate r + { + k = kCalculator.NextK(); + + ECPoint p = basePointMultiplier.Multiply(ec.G, k).Normalize(); + + // 5.3.3 + r = p.AffineXCoord.ToBigInteger().Mod(n); + } + while (r.SignValue == 0); + + s = k.ModInverse(n).Multiply(e.Add(d.Multiply(r))).Mod(n); + } + while (s.SignValue == 0); + + return new BigInteger[]{ r, s }; + } + + // 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 virtual bool VerifySignature(byte[] message, BigInteger r, BigInteger s) + { + BigInteger n = key.Parameters.N; + + // r and s should both in the range [1,n-1] + if (r.SignValue < 1 || s.SignValue < 1 + || r.CompareTo(n) >= 0 || s.CompareTo(n) >= 0) + { + return false; + } + + BigInteger e = CalculateE(n, message); + BigInteger c = s.ModInverse(n); + + BigInteger u1 = e.Multiply(c).Mod(n); + BigInteger u2 = r.Multiply(c).Mod(n); + + ECPoint G = key.Parameters.G; + ECPoint Q = ((ECPublicKeyParameters) key).Q; + + ECPoint point = ECAlgorithms.SumOfTwoMultiplies(G, u1, Q, u2); + + if (point.IsInfinity) + return false; + + /* + * If possible, avoid normalizing the point (to save a modular inversion in the curve field). + * + * There are ~cofactor elements of the curve field that reduce (modulo the group order) to 'r'. + * If the cofactor is known and small, we generate those possible field values and project each + * of them to the same "denominator" (depending on the particular projective coordinates in use) + * as the calculated point.X. If any of the projected values matches point.X, then we have: + * (point.X / Denominator mod p) mod n == r + * as required, and verification succeeds. + * + * Based on an original idea by Gregory Maxwell (https://github.com/gmaxwell), as implemented in + * the libsecp256k1 project (https://github.com/bitcoin/secp256k1). + */ + ECCurve curve = point.Curve; + if (curve != null) + { + BigInteger cofactor = curve.Cofactor; + if (cofactor != null && cofactor.CompareTo(Eight) <= 0) + { + ECFieldElement D = GetDenominator(curve.CoordinateSystem, point); + if (D != null && !D.IsZero) + { + ECFieldElement X = point.XCoord; + while (curve.IsValidFieldElement(r)) + { + ECFieldElement R = curve.FromBigInteger(r).Multiply(D); + if (R.Equals(X)) + { + return true; + } + r = r.Add(n); + } + return false; + } + } + } + + BigInteger v = point.Normalize().AffineXCoord.ToBigInteger().Mod(n); + return v.Equals(r); + } + + protected virtual BigInteger CalculateE(BigInteger n, byte[] message) + { + int messageBitLength = message.Length * 8; + BigInteger trunc = new BigInteger(1, message); + + if (n.BitLength < messageBitLength) + { + trunc = trunc.ShiftRight(messageBitLength - n.BitLength); + } + + return trunc; + } + + protected virtual ECMultiplier CreateBasePointMultiplier() + { + return new FixedPointCombMultiplier(); + } + + protected virtual ECFieldElement GetDenominator(int coordinateSystem, ECPoint p) + { + switch (coordinateSystem) + { + case ECCurve.COORD_HOMOGENEOUS: + case ECCurve.COORD_LAMBDA_PROJECTIVE: + case ECCurve.COORD_SKEWED: + return p.GetZCoord(0); + case ECCurve.COORD_JACOBIAN: + case ECCurve.COORD_JACOBIAN_CHUDNOVSKY: + case ECCurve.COORD_JACOBIAN_MODIFIED: + return p.GetZCoord(0).Square(); + default: + return null; + } + } + + protected virtual SecureRandom InitSecureRandom(bool needed, SecureRandom provided) + { + return !needed ? null : (provided != null) ? provided : new SecureRandom(); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/signers/ECGOST3410Signer.cs b/bc-sharp-crypto/src/crypto/signers/ECGOST3410Signer.cs new file mode 100644 index 0000000..28ab79c --- /dev/null +++ b/bc-sharp-crypto/src/crypto/signers/ECGOST3410Signer.cs @@ -0,0 +1,162 @@ +using System; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Math.EC; +using Org.BouncyCastle.Math.EC.Multiplier; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Signers +{ + /** + * GOST R 34.10-2001 Signature Algorithm + */ + public class ECGost3410Signer + : IDsa + { + private ECKeyParameters key; + private SecureRandom random; + + public virtual string AlgorithmName + { + get { return "ECGOST3410"; } + } + + public virtual void Init( + bool forSigning, + ICipherParameters parameters) + { + if (forSigning) + { + if (parameters is ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)parameters; + + this.random = rParam.Random; + parameters = rParam.Parameters; + } + else + { + this.random = new SecureRandom(); + } + + if (!(parameters is ECPrivateKeyParameters)) + throw new InvalidKeyException("EC private key required for signing"); + + this.key = (ECPrivateKeyParameters) parameters; + } + else + { + if (!(parameters is ECPublicKeyParameters)) + throw new InvalidKeyException("EC public key required for verification"); + + this.key = (ECPublicKeyParameters)parameters; + } + } + + /** + * 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 virtual 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); + + ECDomainParameters ec = key.Parameters; + BigInteger n = ec.N; + BigInteger d = ((ECPrivateKeyParameters)key).D; + + BigInteger r, s = null; + + ECMultiplier basePointMultiplier = CreateBasePointMultiplier(); + + do // generate s + { + BigInteger k; + do // generate r + { + do + { + k = new BigInteger(n.BitLength, random); + } + while (k.SignValue == 0); + + ECPoint p = basePointMultiplier.Multiply(ec.G, k).Normalize(); + + r = p.AffineXCoord.ToBigInteger().Mod(n); + } + while (r.SignValue == 0); + + s = (k.Multiply(e)).Add(d.Multiply(r)).Mod(n); + } + while (s.SignValue == 0); + + return new BigInteger[]{ r, s }; + } + + /** + * 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 virtual bool 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.Parameters.N; + + // r in the range [1,n-1] + if (r.CompareTo(BigInteger.One) < 0 || r.CompareTo(n) >= 0) + { + return false; + } + + // s in the range [1,n-1] + if (s.CompareTo(BigInteger.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.Parameters.G; // P + ECPoint Q = ((ECPublicKeyParameters)key).Q; + + ECPoint point = ECAlgorithms.SumOfTwoMultiplies(G, z1, Q, z2).Normalize(); + + if (point.IsInfinity) + return false; + + BigInteger R = point.AffineXCoord.ToBigInteger().Mod(n); + + return R.Equals(r); + } + + protected virtual ECMultiplier CreateBasePointMultiplier() + { + return new FixedPointCombMultiplier(); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/signers/ECNRSigner.cs b/bc-sharp-crypto/src/crypto/signers/ECNRSigner.cs new file mode 100644 index 0000000..bb21a49 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/signers/ECNRSigner.cs @@ -0,0 +1,188 @@ +using System; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Math.EC; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Signers +{ + /** + * EC-NR as described in IEEE 1363-2000 + */ + public class ECNRSigner + : IDsa + { + private bool forSigning; + private ECKeyParameters key; + private SecureRandom random; + + public virtual string AlgorithmName + { + get { return "ECNR"; } + } + + public virtual void Init( + bool forSigning, + ICipherParameters parameters) + { + this.forSigning = forSigning; + + if (forSigning) + { + if (parameters is ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom) parameters; + + this.random = rParam.Random; + parameters = rParam.Parameters; + } + else + { + this.random = new SecureRandom(); + } + + if (!(parameters is ECPrivateKeyParameters)) + throw new InvalidKeyException("EC private key required for signing"); + + this.key = (ECPrivateKeyParameters) parameters; + } + else + { + if (!(parameters is ECPublicKeyParameters)) + throw new InvalidKeyException("EC public key required for verification"); + + this.key = (ECPublicKeyParameters) parameters; + } + } + + // 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 virtual BigInteger[] GenerateSignature( + byte[] message) + { + if (!this.forSigning) + { + // not properly initilaized... deal with it + throw new InvalidOperationException("not initialised for signing"); + } + + BigInteger n = ((ECPrivateKeyParameters) this.key).Parameters.N; + int nBitLength = n.BitLength; + + BigInteger e = new BigInteger(1, message); + 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.Parameters, this.random)); + + tempPair = keyGen.GenerateKeyPair(); + + // BigInteger Vx = tempPair.getPublic().getW().getAffineX(); + ECPublicKeyParameters V = (ECPublicKeyParameters) tempPair.Public; // get temp's public key + BigInteger Vx = V.Q.AffineXCoord.ToBigInteger(); // get the point's x coordinate + + r = Vx.Add(e).Mod(n); + } + while (r.SignValue == 0); + + // generate s + BigInteger x = privKey.D; // private key value + BigInteger u = ((ECPrivateKeyParameters) tempPair.Private).D; // temp's private key value + s = u.Subtract(r.Multiply(x)).Mod(n); + + return new BigInteger[]{ r, s }; + } + + // 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 virtual bool VerifySignature( + byte[] message, + BigInteger r, + BigInteger s) + { + if (this.forSigning) + { + // not properly initilaized... deal with it + throw new InvalidOperationException("not initialised for verifying"); + } + + ECPublicKeyParameters pubKey = (ECPublicKeyParameters)key; + BigInteger n = pubKey.Parameters.N; + int nBitLength = n.BitLength; + + BigInteger e = new BigInteger(1, message); + 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(BigInteger.One) < 0 || r.CompareTo(n) >= 0) + { + return false; + } + + // s in the range [0,n-1] NB: ECNR spec says 0 + if (s.CompareTo(BigInteger.Zero) < 0 || s.CompareTo(n) >= 0) + { + return false; + } + + // compute P = sG + rW + + ECPoint G = pubKey.Parameters.G; + ECPoint W = pubKey.Q; + // calculate P using Bouncy math + ECPoint P = ECAlgorithms.SumOfTwoMultiplies(G, s, W, r).Normalize(); + + if (P.IsInfinity) + return false; + + BigInteger x = P.AffineXCoord.ToBigInteger(); + BigInteger t = r.Subtract(x).Mod(n); + + return t.Equals(e); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/signers/GOST3410DigestSigner.cs b/bc-sharp-crypto/src/crypto/signers/GOST3410DigestSigner.cs new file mode 100644 index 0000000..bc32808 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/signers/GOST3410DigestSigner.cs @@ -0,0 +1,145 @@ +using System; +using System.Collections; +using System.IO; +using System.Text; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Crypto.Signers; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Signers +{ + public class Gost3410DigestSigner + : ISigner + { + private readonly IDigest digest; + private readonly IDsa dsaSigner; + private bool forSigning; + + public Gost3410DigestSigner( + IDsa signer, + IDigest digest) + { + this.dsaSigner = signer; + this.digest = digest; + } + + public virtual string AlgorithmName + { + get { return digest.AlgorithmName + "with" + dsaSigner.AlgorithmName; } + } + + public virtual void Init( + bool forSigning, + ICipherParameters parameters) + { + this.forSigning = forSigning; + + AsymmetricKeyParameter k; + if (parameters is ParametersWithRandom) + { + k = (AsymmetricKeyParameter)((ParametersWithRandom)parameters).Parameters; + } + else + { + k = (AsymmetricKeyParameter)parameters; + } + + if (forSigning && !k.IsPrivate) + { + throw new InvalidKeyException("Signing Requires Private Key."); + } + + if (!forSigning && k.IsPrivate) + { + throw new InvalidKeyException("Verification Requires Public Key."); + } + + Reset(); + + dsaSigner.Init(forSigning, parameters); + } + + /** + * update the internal digest with the byte b + */ + public virtual void Update( + byte input) + { + digest.Update(input); + } + + /** + * update the internal digest with the byte array in + */ + public virtual void BlockUpdate( + byte[] input, + int inOff, + int length) + { + digest.BlockUpdate(input, inOff, length); + } + + /** + * Generate a signature for the message we've been loaded with using + * the key we were initialised with. + */ + public virtual byte[] GenerateSignature() + { + if (!forSigning) + throw new InvalidOperationException("GOST3410DigestSigner not initialised for signature generation."); + + byte[] hash = new byte[digest.GetDigestSize()]; + digest.DoFinal(hash, 0); + + try + { + BigInteger[] sig = dsaSigner.GenerateSignature(hash); + byte[] sigBytes = new byte[64]; + + // TODO Add methods to allow writing BigInteger to existing byte array? + byte[] r = sig[0].ToByteArrayUnsigned(); + byte[] s = sig[1].ToByteArrayUnsigned(); + s.CopyTo(sigBytes, 32 - s.Length); + r.CopyTo(sigBytes, 64 - r.Length); + return sigBytes; + } + catch (Exception e) + { + throw new SignatureException(e.Message, e); + } + } + + /// true if the internal state represents the signature described in the passed in array. + public virtual bool VerifySignature( + byte[] signature) + { + if (forSigning) + throw new InvalidOperationException("DSADigestSigner not initialised for verification"); + + byte[] hash = new byte[digest.GetDigestSize()]; + digest.DoFinal(hash, 0); + + BigInteger R, S; + try + { + R = new BigInteger(1, signature, 32, 32); + S = new BigInteger(1, signature, 0, 32); + } + catch (Exception e) + { + throw new SignatureException("error decoding signature bytes.", e); + } + + return dsaSigner.VerifySignature(hash, R, S); + } + + /// Reset the internal state + public virtual void Reset() + { + digest.Reset(); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/signers/GOST3410Signer.cs b/bc-sharp-crypto/src/crypto/signers/GOST3410Signer.cs new file mode 100644 index 0000000..f1832ae --- /dev/null +++ b/bc-sharp-crypto/src/crypto/signers/GOST3410Signer.cs @@ -0,0 +1,132 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Signers +{ + /** + * Gost R 34.10-94 Signature Algorithm + */ + public class Gost3410Signer + : IDsa + { + private Gost3410KeyParameters key; + private SecureRandom random; + + public virtual string AlgorithmName + { + get { return "GOST3410"; } + } + + public virtual void Init( + bool forSigning, + ICipherParameters parameters) + { + if (forSigning) + { + if (parameters is ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)parameters; + + this.random = rParam.Random; + parameters = rParam.Parameters; + } + else + { + this.random = new SecureRandom(); + } + + if (!(parameters is Gost3410PrivateKeyParameters)) + throw new InvalidKeyException("GOST3410 private key required for signing"); + + this.key = (Gost3410PrivateKeyParameters) parameters; + } + else + { + if (!(parameters is Gost3410PublicKeyParameters)) + throw new InvalidKeyException("GOST3410 public key required for signing"); + + this.key = (Gost3410PublicKeyParameters) parameters; + } + } + + /** + * 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 virtual 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 parameters = key.Parameters; + BigInteger k; + + do + { + k = new BigInteger(parameters.Q.BitLength, random); + } + while (k.CompareTo(parameters.Q) >= 0); + + BigInteger r = parameters.A.ModPow(k, parameters.P).Mod(parameters.Q); + + BigInteger s = k.Multiply(m). + Add(((Gost3410PrivateKeyParameters)key).X.Multiply(r)). + Mod(parameters.Q); + + return new BigInteger[]{ r, s }; + } + + /** + * 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 virtual bool 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 parameters = key.Parameters; + + if (r.SignValue < 0 || parameters.Q.CompareTo(r) <= 0) + { + return false; + } + + if (s.SignValue < 0 || parameters.Q.CompareTo(s) <= 0) + { + return false; + } + + BigInteger v = m.ModPow(parameters.Q.Subtract(BigInteger.Two), parameters.Q); + + BigInteger z1 = s.Multiply(v).Mod(parameters.Q); + BigInteger z2 = (parameters.Q.Subtract(r)).Multiply(v).Mod(parameters.Q); + + z1 = parameters.A.ModPow(z1, parameters.P); + z2 = ((Gost3410PublicKeyParameters)key).Y.ModPow(z2, parameters.P); + + BigInteger u = z1.Multiply(z2).Mod(parameters.P).Mod(parameters.Q); + + return u.Equals(r); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/signers/GenericSigner.cs b/bc-sharp-crypto/src/crypto/signers/GenericSigner.cs new file mode 100644 index 0000000..a551217 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/signers/GenericSigner.cs @@ -0,0 +1,130 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Signers +{ + public class GenericSigner + : ISigner + { + private readonly IAsymmetricBlockCipher engine; + private readonly IDigest digest; + private bool forSigning; + + public GenericSigner( + IAsymmetricBlockCipher engine, + IDigest digest) + { + this.engine = engine; + this.digest = digest; + } + + public virtual string AlgorithmName + { + get { return "Generic(" + engine.AlgorithmName + "/" + digest.AlgorithmName + ")"; } + } + + /** + * initialise the signer for signing or verification. + * + * @param forSigning + * true if for signing, false otherwise + * @param parameters + * necessary parameters. + */ + public virtual void Init(bool forSigning, ICipherParameters parameters) + { + this.forSigning = forSigning; + + AsymmetricKeyParameter k; + if (parameters is ParametersWithRandom) + { + k = (AsymmetricKeyParameter)((ParametersWithRandom)parameters).Parameters; + } + else + { + k = (AsymmetricKeyParameter)parameters; + } + + if (forSigning && !k.IsPrivate) + throw new InvalidKeyException("Signing requires private key."); + + if (!forSigning && k.IsPrivate) + throw new InvalidKeyException("Verification requires public key."); + + Reset(); + + engine.Init(forSigning, parameters); + } + + /** + * update the internal digest with the byte b + */ + public virtual void Update(byte input) + { + digest.Update(input); + } + + /** + * update the internal digest with the byte array in + */ + public virtual void BlockUpdate(byte[] input, int inOff, int length) + { + digest.BlockUpdate(input, inOff, length); + } + + /** + * Generate a signature for the message we've been loaded with using the key + * we were initialised with. + */ + public virtual byte[] GenerateSignature() + { + if (!forSigning) + throw new InvalidOperationException("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 virtual bool VerifySignature(byte[] signature) + { + if (forSigning) + throw new InvalidOperationException("GenericSigner not initialised for verification"); + + byte[] hash = new byte[digest.GetDigestSize()]; + digest.DoFinal(hash, 0); + + try + { + byte[] sig = engine.ProcessBlock(signature, 0, signature.Length); + + // Extend with leading zeroes to match the digest size, if necessary. + if (sig.Length < hash.Length) + { + byte[] tmp = new byte[hash.Length]; + Array.Copy(sig, 0, tmp, tmp.Length - sig.Length, sig.Length); + sig = tmp; + } + + return Arrays.ConstantTimeAreEqual(sig, hash); + } + catch (Exception) + { + return false; + } + } + + public virtual void Reset() + { + digest.Reset(); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/signers/HMacDsaKCalculator.cs b/bc-sharp-crypto/src/crypto/signers/HMacDsaKCalculator.cs new file mode 100644 index 0000000..8231197 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/signers/HMacDsaKCalculator.cs @@ -0,0 +1,150 @@ +using System; + +using Org.BouncyCastle.Crypto.Macs; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Signers +{ + /** + * A deterministic K calculator based on the algorithm in section 3.2 of RFC 6979. + */ + public class HMacDsaKCalculator + : IDsaKCalculator + { + private readonly HMac hMac; + private readonly byte[] K; + private readonly byte[] V; + + private BigInteger n; + + /** + * Base constructor. + * + * @param digest digest to build the HMAC on. + */ + public HMacDsaKCalculator(IDigest digest) + { + this.hMac = new HMac(digest); + this.V = new byte[hMac.GetMacSize()]; + this.K = new byte[hMac.GetMacSize()]; + } + + public virtual bool IsDeterministic + { + get { return true; } + } + + public virtual void Init(BigInteger n, SecureRandom random) + { + throw new InvalidOperationException("Operation not supported"); + } + + public void Init(BigInteger n, BigInteger d, byte[] message) + { + this.n = n; + + Arrays.Fill(V, (byte)0x01); + Arrays.Fill(K, (byte)0); + + byte[] x = new byte[(n.BitLength + 7) / 8]; + byte[] dVal = BigIntegers.AsUnsignedByteArray(d); + + Array.Copy(dVal, 0, x, x.Length - dVal.Length, dVal.Length); + + byte[] m = new byte[(n.BitLength + 7) / 8]; + + BigInteger mInt = BitsToInt(message); + + if (mInt.CompareTo(n) >= 0) + { + mInt = mInt.Subtract(n); + } + + byte[] mVal = BigIntegers.AsUnsignedByteArray(mInt); + + Array.Copy(mVal, 0, m, m.Length - mVal.Length, mVal.Length); + + hMac.Init(new KeyParameter(K)); + + hMac.BlockUpdate(V, 0, V.Length); + hMac.Update((byte)0x00); + hMac.BlockUpdate(x, 0, x.Length); + hMac.BlockUpdate(m, 0, m.Length); + + hMac.DoFinal(K, 0); + + hMac.Init(new KeyParameter(K)); + + hMac.BlockUpdate(V, 0, V.Length); + + hMac.DoFinal(V, 0); + + hMac.BlockUpdate(V, 0, V.Length); + hMac.Update((byte)0x01); + hMac.BlockUpdate(x, 0, x.Length); + hMac.BlockUpdate(m, 0, m.Length); + + hMac.DoFinal(K, 0); + + hMac.Init(new KeyParameter(K)); + + hMac.BlockUpdate(V, 0, V.Length); + + hMac.DoFinal(V, 0); + } + + public virtual BigInteger NextK() + { + byte[] t = new byte[((n.BitLength + 7) / 8)]; + + for (;;) + { + int tOff = 0; + + while (tOff < t.Length) + { + hMac.BlockUpdate(V, 0, V.Length); + + hMac.DoFinal(V, 0); + + int len = System.Math.Min(t.Length - tOff, V.Length); + Array.Copy(V, 0, t, tOff, len); + tOff += len; + } + + BigInteger k = BitsToInt(t); + + if (k.SignValue > 0 && k.CompareTo(n) < 0) + { + return k; + } + + hMac.BlockUpdate(V, 0, V.Length); + hMac.Update((byte)0x00); + + hMac.DoFinal(K, 0); + + hMac.Init(new KeyParameter(K)); + + hMac.BlockUpdate(V, 0, V.Length); + + hMac.DoFinal(V, 0); + } + } + + private BigInteger BitsToInt(byte[] t) + { + BigInteger v = new BigInteger(1, t); + + if (t.Length * 8 > n.BitLength) + { + v = v.ShiftRight(t.Length * 8 - n.BitLength); + } + + return v; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/signers/IDsaKCalculator.cs b/bc-sharp-crypto/src/crypto/signers/IDsaKCalculator.cs new file mode 100644 index 0000000..645186d --- /dev/null +++ b/bc-sharp-crypto/src/crypto/signers/IDsaKCalculator.cs @@ -0,0 +1,44 @@ +using System; + +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Signers +{ + /** + * Interface define calculators of K values for DSA/ECDSA. + */ + public interface IDsaKCalculator + { + /** + * Return true if this calculator is deterministic, false otherwise. + * + * @return true if deterministic, otherwise false. + */ + bool IsDeterministic { get; } + + /** + * Non-deterministic initialiser. + * + * @param n the order of the DSA group. + * @param random a source of randomness. + */ + void Init(BigInteger n, SecureRandom random); + + /** + * Deterministic initialiser. + * + * @param n the order of the DSA group. + * @param d the DSA private value. + * @param message the message being signed. + */ + void Init(BigInteger n, BigInteger d, byte[] message); + + /** + * Return the next valid value of K. + * + * @return a K value. + */ + BigInteger NextK(); + } +} diff --git a/bc-sharp-crypto/src/crypto/signers/Iso9796d2PssSigner.cs b/bc-sharp-crypto/src/crypto/signers/Iso9796d2PssSigner.cs new file mode 100644 index 0000000..6b80370 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/signers/Iso9796d2PssSigner.cs @@ -0,0 +1,619 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Signers +{ + /// 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 + : ISignerWithRecovery + { + /// + /// Return a reference to the recoveredMessage message. + /// + /// The full/partial recoveredMessage message. + /// + public byte[] GetRecoveredMessage() + { + return recoveredMessage; + } + + [Obsolete("Use 'IsoTrailers' instead")] + public const int TrailerImplicit = 0xBC; + [Obsolete("Use 'IsoTrailers' instead")] + public const int TrailerRipeMD160 = 0x31CC; + [Obsolete("Use 'IsoTrailers' instead")] + public const int TrailerRipeMD128 = 0x32CC; + [Obsolete("Use 'IsoTrailers' instead")] + public const int TrailerSha1 = 0x33CC; + [Obsolete("Use 'IsoTrailers' instead")] + public const int TrailerSha256 = 0x34CC; + [Obsolete("Use 'IsoTrailers' instead")] + public const int TrailerSha512 = 0x35CC; + [Obsolete("Use 'IsoTrailers' instead")] + public const int TrailerSha384 = 0x36CC; + [Obsolete("Use 'IsoTrailers' instead")] + public const int TrailerWhirlpool = 0x37CC; + + private IDigest digest; + private IAsymmetricBlockCipher 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 readonly int saltLength; + private bool fullMessage; + private byte[] recoveredMessage; + + private byte[] preSig; + private byte[] preBlock; + private int preMStart; + private int preTLength; + + /// + /// Generate a signer with either implicit or explicit trailers for ISO9796-2, scheme 2 or 3. + /// + /// base cipher to use for signature creation/verification + /// digest to use. + /// length of salt in bytes. + /// whether or not the trailer is implicit or gives the hash. + public Iso9796d2PssSigner( + IAsymmetricBlockCipher cipher, + IDigest digest, + int saltLength, + bool isImplicit) + { + this.cipher = cipher; + this.digest = digest; + this.hLen = digest.GetDigestSize(); + this.saltLength = saltLength; + + if (isImplicit) + { + trailer = IsoTrailers.TRAILER_IMPLICIT; + } + else if (IsoTrailers.NoTrailerAvailable(digest)) + { + throw new ArgumentException("no valid trailer", "digest"); + } + else + { + trailer = IsoTrailers.GetTrailer(digest); + } + } + + /// Constructor for a signer with an explicit digest trailer. + /// + /// + /// cipher to use. + /// + /// digest to sign with. + /// + /// length of salt in bytes. + /// + public Iso9796d2PssSigner( + IAsymmetricBlockCipher cipher, + IDigest digest, + int saltLength) + : this(cipher, digest, saltLength, false) + { + } + + public virtual string AlgorithmName + { + get { return digest.AlgorithmName + "with" + "ISO9796-2S2"; } + } + + /// Initialise the signer. + /// true if for signing, false if for verification. + /// 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. + /// + /// if wrong parameter type or a fixed + /// salt is passed in which is the wrong length. + /// + public virtual void Init( + bool forSigning, + ICipherParameters parameters) + { + RsaKeyParameters kParam; + if (parameters is ParametersWithRandom) + { + ParametersWithRandom p = (ParametersWithRandom) parameters; + + kParam = (RsaKeyParameters) p.Parameters; + + if (forSigning) + { + random = p.Random; + } + } + else if (parameters is ParametersWithSalt) + { + if (!forSigning) + throw new ArgumentException("ParametersWithSalt only valid for signing", "parameters"); + + ParametersWithSalt p = (ParametersWithSalt) parameters; + + kParam = (RsaKeyParameters) p.Parameters; + standardSalt = p.GetSalt(); + + if (standardSalt.Length != saltLength) + throw new ArgumentException("Fixed salt is of wrong length"); + } + else + { + kParam = (RsaKeyParameters) parameters; + + if (forSigning) + { + random = new SecureRandom(); + } + } + + cipher.Init(forSigning, kParam); + + keyBits = kParam.Modulus.BitLength; + + block = new byte[(keyBits + 7) / 8]; + + if (trailer == IsoTrailers.TRAILER_IMPLICIT) + { + mBuf = new byte[block.Length - digest.GetDigestSize() - saltLength - 1 - 1]; + } + else + { + mBuf = new byte[block.Length - digest.GetDigestSize() - saltLength - 1 - 2]; + } + + Reset(); + } + + /// compare two byte arrays - constant time. + private bool IsSameAs(byte[] a, byte[] b) + { + if (messageLength != b.Length) + { + return false; + } + + bool isOkay = true; + + for (int i = 0; i != b.Length; i++) + { + if (a[i] != b[i]) + { + isOkay = false; + } + } + + return isOkay; + } + + /// clear possible sensitive data + private void ClearBlock( + byte[] block) + { + Array.Clear(block, 0, block.Length); + } + + public virtual void UpdateWithRecoveredMessage( + byte[] signature) + { + byte[] block = cipher.ProcessBlock(signature, 0, signature.Length); + + // + // adjust block size for leading zeroes if necessary + // + if (block.Length < (keyBits + 7) / 8) + { + byte[] tmp = new byte[(keyBits + 7) / 8]; + + Array.Copy(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); + + if (IsoTrailers.NoTrailerAvailable(digest)) + throw new ArgumentException("unrecognised hash in signature"); + + if (sigTrail != IsoTrailers.GetTrailer(digest)) + throw new InvalidOperationException("signer initialised with wrong digest for trailer " + sigTrail); + + 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; + + while (mStart < block.Length) + { + if (block[mStart++] == 0x01) + break; + } + + if (mStart >= block.Length) + { + ClearBlock(block); + } + + fullMessage = (mStart > 1); + + recoveredMessage = new byte[dbMask.Length - mStart - saltLength]; + + Array.Copy(block, mStart, recoveredMessage, 0, recoveredMessage.Length); + recoveredMessage.CopyTo(mBuf, 0); + + preSig = signature; + preBlock = block; + preMStart = mStart; + preTLength = tLength; + } + + /// update the internal digest with the byte b + public virtual void Update( + byte input) + { + if (preSig == null && messageLength < mBuf.Length) + { + mBuf[messageLength++] = input; + } + else + { + digest.Update(input); + } + } + + /// update the internal digest with the byte array in + public virtual void BlockUpdate( + byte[] input, + int inOff, + int length) + { + if (preSig == null) + { + while (length > 0 && messageLength < mBuf.Length) + { + this.Update(input[inOff]); + inOff++; + length--; + } + } + + if (length > 0) + { + digest.BlockUpdate(input, inOff, length); + } + } + + /// reset the internal state + public virtual void Reset() + { + digest.Reset(); + messageLength = 0; + if (mBuf != null) + { + ClearBlock(mBuf); + } + if (recoveredMessage != null) + { + ClearBlock(recoveredMessage); + recoveredMessage = null; + } + fullMessage = false; + if (preSig != null) + { + preSig = null; + ClearBlock(preBlock); + preBlock = null; + } + } + + /// Generate a signature for the loaded message using the key we were + /// initialised with. + /// + public virtual byte[] GenerateSignature() + { + int digSize = digest.GetDigestSize(); + byte[] m2Hash = new byte[digSize]; + digest.DoFinal(m2Hash, 0); + + byte[] C = new byte[8]; + LtoOSP(messageLength * 8, C); + + digest.BlockUpdate(C, 0, C.Length); + digest.BlockUpdate(mBuf, 0, messageLength); + digest.BlockUpdate(m2Hash, 0, m2Hash.Length); + + byte[] salt; + if (standardSalt != null) + { + salt = standardSalt; + } + else + { + salt = new byte[saltLength]; + random.NextBytes(salt); + } + + digest.BlockUpdate(salt, 0, salt.Length); + + byte[] hash = new byte[digest.GetDigestSize()]; + digest.DoFinal(hash, 0); + + int tLength = 2; + if (trailer == IsoTrailers.TRAILER_IMPLICIT) + { + tLength = 1; + } + + int off = block.Length - messageLength - salt.Length - hLen - tLength - 1; + + block[off] = (byte) (0x01); + + Array.Copy(mBuf, 0, block, off + 1, messageLength); + Array.Copy(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]; + } + + Array.Copy(hash, 0, block, block.Length - hLen - tLength, hLen); + + if (trailer == IsoTrailers.TRAILER_IMPLICIT) + { + block[block.Length - 1] = (byte)IsoTrailers.TRAILER_IMPLICIT; + } + else + { + block[block.Length - 2] = (byte) ((uint)trailer >> 8); + block[block.Length - 1] = (byte) trailer; + } + + block[0] &= (byte) (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 virtual bool VerifySignature( + byte[] signature) + { + // + // calculate H(m2) + // + byte[] m2Hash = new byte[hLen]; + digest.DoFinal(m2Hash, 0); + + byte[] block; + int tLength; + int mStart = 0; + + if (preSig == null) + { + try + { + UpdateWithRecoveredMessage(signature); + } + catch (Exception) + { + return false; + } + } + else + { + if (!Arrays.AreEqual(preSig, signature)) + { + throw new InvalidOperationException("UpdateWithRecoveredMessage called on different signature"); + } + } + + block = preBlock; + mStart = preMStart; + tLength = preTLength; + + preSig = null; + preBlock = null; + + // + // check the hashes + // + byte[] C = new byte[8]; + LtoOSP(recoveredMessage.Length * 8, C); + + digest.BlockUpdate(C, 0, C.Length); + + if (recoveredMessage.Length != 0) + { + digest.BlockUpdate(recoveredMessage, 0, recoveredMessage.Length); + } + + digest.BlockUpdate(m2Hash, 0, m2Hash.Length); + + // Update for the salt + if (standardSalt != null) + { + digest.BlockUpdate(standardSalt, 0, standardSalt.Length); + } + else + { + digest.BlockUpdate(block, mStart + recoveredMessage.Length, saltLength); + } + + byte[] hash = new byte[digest.GetDigestSize()]; + digest.DoFinal(hash, 0); + + int off = block.Length - tLength - hash.Length; + + bool isOkay = true; + + for (int i = 0; i != hash.Length; i++) + { + if (hash[i] != block[off + i]) + { + isOkay = false; + } + } + + ClearBlock(block); + ClearBlock(hash); + + if (!isOkay) + { + fullMessage = false; + messageLength = 0; + ClearBlock(recoveredMessage); + return false; + } + + // + // if they've input a message check what we've recovered against + // what was input. + // + if (messageLength != 0) + { + if (!IsSameAs(mBuf, recoveredMessage)) + { + messageLength = 0; + ClearBlock(mBuf); + return false; + } + } + + messageLength = 0; + + ClearBlock(mBuf); + return true; + } + + /// + /// Return true if the full message was recoveredMessage. + /// + /// true on full message recovery, false otherwise, or if not sure. + /// + public virtual bool HasFullMessage() + { + return fullMessage; + } + + /// int to octet string. + /// int to octet string. + private void ItoOSP( + int i, + byte[] sp) + { + sp[0] = (byte)((uint)i >> 24); + sp[1] = (byte)((uint)i >> 16); + sp[2] = (byte)((uint)i >> 8); + sp[3] = (byte)((uint)i >> 0); + } + + /// long to octet string. + private void LtoOSP(long l, byte[] sp) + { + sp[0] = (byte)((ulong)l >> 56); + sp[1] = (byte)((ulong)l >> 48); + sp[2] = (byte)((ulong)l >> 40); + sp[3] = (byte)((ulong)l >> 32); + sp[4] = (byte)((ulong)l >> 24); + sp[5] = (byte)((ulong)l >> 16); + sp[6] = (byte)((ulong)l >> 8); + sp[7] = (byte)((ulong)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(); + + do + { + ItoOSP(counter, C); + + digest.BlockUpdate(Z, zOff, zLen); + digest.BlockUpdate(C, 0, C.Length); + digest.DoFinal(hashBuf, 0); + + Array.Copy(hashBuf, 0, mask, counter * hLen, hLen); + } + while (++counter < (length / hLen)); + + if ((counter * hLen) < length) + { + ItoOSP(counter, C); + + digest.BlockUpdate(Z, zOff, zLen); + digest.BlockUpdate(C, 0, C.Length); + digest.DoFinal(hashBuf, 0); + + Array.Copy(hashBuf, 0, mask, counter * hLen, mask.Length - (counter * hLen)); + } + + return mask; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/signers/Iso9796d2Signer.cs b/bc-sharp-crypto/src/crypto/signers/Iso9796d2Signer.cs new file mode 100644 index 0000000..3039130 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/signers/Iso9796d2Signer.cs @@ -0,0 +1,556 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Signers +{ + /// ISO9796-2 - mechanism using a hash function with recovery (scheme 1) + public class Iso9796d2Signer : ISignerWithRecovery + { + /// + /// Return a reference to the recoveredMessage message. + /// + /// The full/partial recoveredMessage message. + /// + public byte[] GetRecoveredMessage() + { + return recoveredMessage; + } + + [Obsolete("Use 'IsoTrailers' instead")] + public const int TrailerImplicit = 0xBC; + [Obsolete("Use 'IsoTrailers' instead")] + public const int TrailerRipeMD160 = 0x31CC; + [Obsolete("Use 'IsoTrailers' instead")] + public const int TrailerRipeMD128 = 0x32CC; + [Obsolete("Use 'IsoTrailers' instead")] + public const int TrailerSha1 = 0x33CC; + [Obsolete("Use 'IsoTrailers' instead")] + public const int TrailerSha256 = 0x34CC; + [Obsolete("Use 'IsoTrailers' instead")] + public const int TrailerSha512 = 0x35CC; + [Obsolete("Use 'IsoTrailers' instead")] + public const int TrailerSha384 = 0x36CC; + [Obsolete("Use 'IsoTrailers' instead")] + public const int TrailerWhirlpool = 0x37CC; + + private IDigest digest; + private IAsymmetricBlockCipher cipher; + + private int trailer; + private int keyBits; + private byte[] block; + private byte[] mBuf; + private int messageLength; + private bool fullMessage; + private byte[] recoveredMessage; + + private byte[] preSig; + private byte[] preBlock; + + /// + /// Generate a signer with either implicit or explicit trailers for ISO9796-2. + /// + /// base cipher to use for signature creation/verification + /// digest to use. + /// whether or not the trailer is implicit or gives the hash. + public Iso9796d2Signer( + IAsymmetricBlockCipher cipher, + IDigest digest, + bool isImplicit) + { + this.cipher = cipher; + this.digest = digest; + + if (isImplicit) + { + trailer = IsoTrailers.TRAILER_IMPLICIT; + } + else if (IsoTrailers.NoTrailerAvailable(digest)) + { + throw new ArgumentException("no valid trailer", "digest"); + } + else + { + trailer = IsoTrailers.GetTrailer(digest); + } + } + + /// Constructor for a signer with an explicit digest trailer. + /// + /// + /// cipher to use. + /// + /// digest to sign with. + /// + public Iso9796d2Signer(IAsymmetricBlockCipher cipher, IDigest digest) + : this(cipher, digest, false) + { + } + + public virtual string AlgorithmName + { + get { return digest.AlgorithmName + "with" + "ISO9796-2S1"; } + } + + public virtual void Init(bool forSigning, ICipherParameters parameters) + { + RsaKeyParameters kParam = (RsaKeyParameters) parameters; + + cipher.Init(forSigning, kParam); + + keyBits = kParam.Modulus.BitLength; + + block = new byte[(keyBits + 7) / 8]; + if (trailer == IsoTrailers.TRAILER_IMPLICIT) + { + mBuf = new byte[block.Length - digest.GetDigestSize() - 2]; + } + else + { + mBuf = new byte[block.Length - digest.GetDigestSize() - 3]; + } + + Reset(); + } + + /// compare two byte arrays - constant time. + private bool IsSameAs(byte[] a, byte[] b) + { + int checkLen; + if (messageLength > mBuf.Length) + { + if (mBuf.Length > b.Length) + { + return false; + } + + checkLen = mBuf.Length; + } + else + { + if (messageLength != b.Length) + { + return false; + } + + checkLen = b.Length; + } + + bool isOkay = true; + + for (int i = 0; i != checkLen; i++) + { + if (a[i] != b[i]) + { + isOkay = false; + } + } + + return isOkay; + } + + /// clear possible sensitive data + private void ClearBlock( + byte[] block) + { + Array.Clear(block, 0, block.Length); + } + + public virtual void UpdateWithRecoveredMessage( + byte[] signature) + { + byte[] block = cipher.ProcessBlock(signature, 0, signature.Length); + + if (((block[0] & 0xC0) ^ 0x40) != 0) + throw new InvalidCipherTextException("malformed signature"); + + if (((block[block.Length - 1] & 0xF) ^ 0xC) != 0) + throw new InvalidCipherTextException("malformed signature"); + + int delta = 0; + + if (((block[block.Length - 1] & 0xFF) ^ 0xBC) == 0) + { + delta = 1; + } + else + { + int sigTrail = ((block[block.Length - 2] & 0xFF) << 8) | (block[block.Length - 1] & 0xFF); + + if (IsoTrailers.NoTrailerAvailable(digest)) + throw new ArgumentException("unrecognised hash in signature"); + + if (sigTrail != IsoTrailers.GetTrailer(digest)) + throw new InvalidOperationException("signer initialised with wrong digest for trailer " + sigTrail); + + delta = 2; + } + + // + // find out how much padding we've got + // + int mStart = 0; + + for (mStart = 0; mStart != block.Length; mStart++) + { + if (((block[mStart] & 0x0f) ^ 0x0a) == 0) + break; + } + + mStart++; + + int off = block.Length - delta - digest.GetDigestSize(); + + // + // there must be at least one byte of message string + // + if ((off - mStart) <= 0) + throw new InvalidCipherTextException("malformed block"); + + // + // if we contain the whole message as well, check the hash of that. + // + if ((block[0] & 0x20) == 0) + { + fullMessage = true; + + recoveredMessage = new byte[off - mStart]; + Array.Copy(block, mStart, recoveredMessage, 0, recoveredMessage.Length); + } + else + { + fullMessage = false; + + recoveredMessage = new byte[off - mStart]; + Array.Copy(block, mStart, recoveredMessage, 0, recoveredMessage.Length); + } + + preSig = signature; + preBlock = block; + + digest.BlockUpdate(recoveredMessage, 0, recoveredMessage.Length); + messageLength = recoveredMessage.Length; + recoveredMessage.CopyTo(mBuf, 0); + } + + /// update the internal digest with the byte b + public virtual void Update( + byte input) + { + digest.Update(input); + + if (messageLength < mBuf.Length) + { + mBuf[messageLength] = input; + } + + messageLength++; + } + + /// update the internal digest with the byte array in + public virtual void BlockUpdate( + byte[] input, + int inOff, + int length) + { + while (length > 0 && messageLength < mBuf.Length) + { + //for (int i = 0; i < length && (i + messageLength) < mBuf.Length; i++) + //{ + // mBuf[messageLength + i] = input[inOff + i]; + //} + this.Update(input[inOff]); + inOff++; + length--; + } + + digest.BlockUpdate(input, inOff, length); + messageLength += length; + } + + /// reset the internal state + public virtual void Reset() + { + digest.Reset(); + messageLength = 0; + ClearBlock(mBuf); + + if (recoveredMessage != null) + { + ClearBlock(recoveredMessage); + } + + recoveredMessage = null; + fullMessage = false; + + if (preSig != null) + { + preSig = null; + ClearBlock(preBlock); + preBlock = null; + } + } + + /// Generate a signature for the loaded message using the key we were + /// initialised with. + /// + public virtual byte[] GenerateSignature() + { + int digSize = digest.GetDigestSize(); + + int t = 0; + int delta = 0; + + if (trailer == IsoTrailers.TRAILER_IMPLICIT) + { + t = 8; + delta = block.Length - digSize - 1; + digest.DoFinal(block, delta); + block[block.Length - 1] = (byte)IsoTrailers.TRAILER_IMPLICIT; + } + else + { + t = 16; + delta = block.Length - digSize - 2; + digest.DoFinal(block, delta); + block[block.Length - 2] = (byte) ((uint)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 = (byte) (0x60); + + delta -= mR; + + Array.Copy(mBuf, 0, block, delta, mR); + } + else + { + header = (byte) (0x40); + delta -= messageLength; + + Array.Copy(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); + + messageLength = 0; + + ClearBlock(mBuf); + ClearBlock(block); + + return b; + } + + /// return true if the signature represents a ISO9796-2 signature + /// for the passed in message. + /// + public virtual bool VerifySignature(byte[] signature) + { + byte[] block; + + if (preSig == null) + { + try + { + block = cipher.ProcessBlock(signature, 0, signature.Length); + } + catch (Exception) + { + return false; + } + } + else + { + if (!Arrays.AreEqual(preSig, signature)) + throw new InvalidOperationException("updateWithRecoveredMessage called on different signature"); + + block = preBlock; + + preSig = null; + preBlock = null; + } + + if (((block[0] & 0xC0) ^ 0x40) != 0) + return ReturnFalse(block); + + if (((block[block.Length - 1] & 0xF) ^ 0xC) != 0) + return ReturnFalse(block); + + 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); + + if (IsoTrailers.NoTrailerAvailable(digest)) + throw new ArgumentException("unrecognised hash in signature"); + + if (sigTrail != IsoTrailers.GetTrailer(digest)) + throw new InvalidOperationException("signer initialised with wrong digest for trailer " + sigTrail); + + delta = 2; + } + + // + // find out how much padding we've got + // + int mStart = 0; + for (; 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) + { + return ReturnFalse(block); + } + + // + // if we contain the whole message as well, check the hash of that. + // + if ((block[0] & 0x20) == 0) + { + fullMessage = true; + + // check right number of bytes passed in. + if (messageLength > off - mStart) + { + return ReturnFalse(block); + } + + digest.Reset(); + digest.BlockUpdate(block, mStart, off - mStart); + digest.DoFinal(hash, 0); + + bool isOkay = true; + + for (int i = 0; i != hash.Length; i++) + { + block[off + i] ^= hash[i]; + if (block[off + i] != 0) + { + isOkay = false; + } + } + + if (!isOkay) + { + return ReturnFalse(block); + } + + recoveredMessage = new byte[off - mStart]; + Array.Copy(block, mStart, recoveredMessage, 0, recoveredMessage.Length); + } + else + { + fullMessage = false; + + digest.DoFinal(hash, 0); + + bool isOkay = true; + + for (int i = 0; i != hash.Length; i++) + { + block[off + i] ^= hash[i]; + if (block[off + i] != 0) + { + isOkay = false; + } + } + + if (!isOkay) + { + return ReturnFalse(block); + } + + recoveredMessage = new byte[off - mStart]; + Array.Copy(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)) + { + return ReturnFalse(block); + } + } + + ClearBlock(mBuf); + ClearBlock(block); + + messageLength = 0; + + return true; + } + + private bool ReturnFalse(byte[] block) + { + messageLength = 0; + + ClearBlock(mBuf); + ClearBlock(block); + + return false; + } + + /// + /// Return true if the full message was recoveredMessage. + /// + /// true on full message recovery, false otherwise. + /// + public virtual bool HasFullMessage() + { + return fullMessage; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/signers/IsoTrailers.cs b/bc-sharp-crypto/src/crypto/signers/IsoTrailers.cs new file mode 100644 index 0000000..497ffaf --- /dev/null +++ b/bc-sharp-crypto/src/crypto/signers/IsoTrailers.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; + +namespace Org.BouncyCastle.Crypto.Signers +{ + public class IsoTrailers + { + public const int TRAILER_IMPLICIT = 0xBC; + public const int TRAILER_RIPEMD160 = 0x31CC; + public const int TRAILER_RIPEMD128 = 0x32CC; + public const int TRAILER_SHA1 = 0x33CC; + public const int TRAILER_SHA256 = 0x34CC; + public const int TRAILER_SHA512 = 0x35CC; + public const int TRAILER_SHA384 = 0x36CC; + public const int TRAILER_WHIRLPOOL = 0x37CC; + public const int TRAILER_SHA224 = 0x38CC; + public const int TRAILER_SHA512_224 = 0x39CC; + public const int TRAILER_SHA512_256 = 0x40CC; + + private static IDictionary CreateTrailerMap() + { + IDictionary trailers = Platform.CreateHashtable(); + + trailers.Add("RIPEMD128", TRAILER_RIPEMD128); + trailers.Add("RIPEMD160", TRAILER_RIPEMD160); + + trailers.Add("SHA-1", TRAILER_SHA1); + trailers.Add("SHA-224", TRAILER_SHA224); + trailers.Add("SHA-256", TRAILER_SHA256); + trailers.Add("SHA-384", TRAILER_SHA384); + trailers.Add("SHA-512", TRAILER_SHA512); + trailers.Add("SHA-512/224", TRAILER_SHA512_224); + trailers.Add("SHA-512/256", TRAILER_SHA512_256); + + trailers.Add("Whirlpool", TRAILER_WHIRLPOOL); + + return CollectionUtilities.ReadOnly(trailers); + } + + // IDictionary is (string -> Int32) + private static readonly IDictionary trailerMap = CreateTrailerMap(); + + public static int GetTrailer(IDigest digest) + { + return (int)trailerMap[digest.AlgorithmName]; + } + + public static bool NoTrailerAvailable(IDigest digest) + { + return !trailerMap.Contains(digest.AlgorithmName); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/signers/PssSigner.cs b/bc-sharp-crypto/src/crypto/signers/PssSigner.cs new file mode 100644 index 0000000..23b7c0f --- /dev/null +++ b/bc-sharp-crypto/src/crypto/signers/PssSigner.cs @@ -0,0 +1,386 @@ +using System; + +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Signers +{ + /// 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 + : ISigner + { + public const byte TrailerImplicit = (byte)0xBC; + + private readonly IDigest contentDigest1, contentDigest2; + private readonly IDigest mgfDigest; + private readonly IAsymmetricBlockCipher cipher; + + private SecureRandom random; + + private int hLen; + private int mgfhLen; + private int sLen; + private bool sSet; + private int emBits; + private byte[] salt; + private byte[] mDash; + private byte[] block; + private byte trailer; + + public static PssSigner CreateRawSigner( + IAsymmetricBlockCipher cipher, + IDigest digest) + { + return new PssSigner(cipher, new NullDigest(), digest, digest, digest.GetDigestSize(), null, TrailerImplicit); + } + + public static PssSigner CreateRawSigner( + IAsymmetricBlockCipher cipher, + IDigest contentDigest, + IDigest mgfDigest, + int saltLen, + byte trailer) + { + return new PssSigner(cipher, new NullDigest(), contentDigest, mgfDigest, saltLen, null, trailer); + } + + public PssSigner( + IAsymmetricBlockCipher cipher, + IDigest digest) + : this(cipher, digest, digest.GetDigestSize()) + { + } + + /// Basic constructor + /// the asymmetric cipher to use. + /// the digest to use. + /// the length of the salt to use (in bytes). + public PssSigner( + IAsymmetricBlockCipher cipher, + IDigest digest, + int saltLen) + : this(cipher, digest, saltLen, TrailerImplicit) + { + } + + /// Basic constructor + /// the asymmetric cipher to use. + /// the digest to use. + /// the fixed salt to be used. + public PssSigner( + IAsymmetricBlockCipher cipher, + IDigest digest, + byte[] salt) + : this(cipher, digest, digest, digest, salt.Length, salt, TrailerImplicit) + { + } + + public PssSigner( + IAsymmetricBlockCipher cipher, + IDigest contentDigest, + IDigest mgfDigest, + int saltLen) + : this(cipher, contentDigest, mgfDigest, saltLen, TrailerImplicit) + { + } + + public PssSigner( + IAsymmetricBlockCipher cipher, + IDigest contentDigest, + IDigest mgfDigest, + byte[] salt) + : this(cipher, contentDigest, contentDigest, mgfDigest, salt.Length, salt, TrailerImplicit) + { + } + + public PssSigner( + IAsymmetricBlockCipher cipher, + IDigest digest, + int saltLen, + byte trailer) + : this(cipher, digest, digest, saltLen, TrailerImplicit) + { + } + + public PssSigner( + IAsymmetricBlockCipher cipher, + IDigest contentDigest, + IDigest mgfDigest, + int saltLen, + byte trailer) + : this(cipher, contentDigest, contentDigest, mgfDigest, saltLen, null, trailer) + { + } + + private PssSigner( + IAsymmetricBlockCipher cipher, + IDigest contentDigest1, + IDigest contentDigest2, + IDigest mgfDigest, + int saltLen, + byte[] salt, + byte trailer) + { + this.cipher = cipher; + this.contentDigest1 = contentDigest1; + this.contentDigest2 = contentDigest2; + this.mgfDigest = mgfDigest; + this.hLen = contentDigest2.GetDigestSize(); + this.mgfhLen = mgfDigest.GetDigestSize(); + this.sLen = saltLen; + this.sSet = salt != null; + if (sSet) + { + this.salt = salt; + } + else + { + this.salt = new byte[saltLen]; + } + this.mDash = new byte[8 + saltLen + hLen]; + this.trailer = trailer; + } + + public virtual string AlgorithmName + { + get { return mgfDigest.AlgorithmName + "withRSAandMGF1"; } + } + + public virtual void Init( + bool forSigning, + ICipherParameters parameters) + { + if (parameters is ParametersWithRandom) + { + ParametersWithRandom p = (ParametersWithRandom) parameters; + + parameters = p.Parameters; + random = p.Random; + } + else + { + if (forSigning) + { + random = new SecureRandom(); + } + } + + cipher.Init(forSigning, parameters); + + RsaKeyParameters kParam; + if (parameters is RsaBlindingParameters) + { + kParam = ((RsaBlindingParameters) parameters).PublicKey; + } + else + { + kParam = (RsaKeyParameters) parameters; + } + + emBits = kParam.Modulus.BitLength - 1; + + if (emBits < (8 * hLen + 8 * sLen + 9)) + throw new ArgumentException("key too small for specified hash and salt lengths"); + + block = new byte[(emBits + 7) / 8]; + } + + /// clear possible sensitive data + private void ClearBlock( + byte[] block) + { + Array.Clear(block, 0, block.Length); + } + + /// update the internal digest with the byte b + public virtual void Update( + byte input) + { + contentDigest1.Update(input); + } + + /// update the internal digest with the byte array in + public virtual void BlockUpdate( + byte[] input, + int inOff, + int length) + { + contentDigest1.BlockUpdate(input, inOff, length); + } + + /// reset the internal state + public virtual void Reset() + { + contentDigest1.Reset(); + } + + /// Generate a signature for the message we've been loaded with using + /// the key we were initialised with. + /// + public virtual byte[] GenerateSignature() + { + contentDigest1.DoFinal(mDash, mDash.Length - hLen - sLen); + + if (sLen != 0) + { + if (!sSet) + { + random.NextBytes(salt); + } + salt.CopyTo(mDash, mDash.Length - sLen); + } + + byte[] h = new byte[hLen]; + + contentDigest2.BlockUpdate(mDash, 0, mDash.Length); + + contentDigest2.DoFinal(h, 0); + + block[block.Length - sLen - 1 - hLen - 1] = (byte) (0x01); + salt.CopyTo(block, block.Length - sLen - hLen - 1); + + 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] &= (byte) ((0xff >> ((block.Length * 8) - emBits))); + + h.CopyTo(block, block.Length - hLen - 1); + + 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 virtual bool VerifySignature( + byte[] signature) + { + contentDigest1.DoFinal(mDash, mDash.Length - hLen - sLen); + + byte[] b = cipher.ProcessBlock(signature, 0, signature.Length); + b.CopyTo(block, block.Length - b.Length); + + 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] &= (byte) ((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; + } + + if (sSet) + { + Array.Copy(salt, 0, mDash, mDash.Length - sLen, sLen); + } + else + { + Array.Copy(block, block.Length - sLen - hLen - 1, mDash, mDash.Length - sLen, sLen); + } + + contentDigest2.BlockUpdate(mDash, 0, mDash.Length); + contentDigest2.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)((uint) i >> 24); + sp[1] = (byte)((uint) i >> 16); + sp[2] = (byte)((uint) i >> 8); + sp[3] = (byte)((uint) 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[mgfhLen]; + byte[] C = new byte[4]; + int counter = 0; + + mgfDigest.Reset(); + + while (counter < (length / mgfhLen)) + { + ItoOSP(counter, C); + + mgfDigest.BlockUpdate(Z, zOff, zLen); + mgfDigest.BlockUpdate(C, 0, C.Length); + mgfDigest.DoFinal(hashBuf, 0); + + hashBuf.CopyTo(mask, counter * mgfhLen); + ++counter; + } + + if ((counter * mgfhLen) < length) + { + ItoOSP(counter, C); + + mgfDigest.BlockUpdate(Z, zOff, zLen); + mgfDigest.BlockUpdate(C, 0, C.Length); + mgfDigest.DoFinal(hashBuf, 0); + + Array.Copy(hashBuf, 0, mask, counter * mgfhLen, mask.Length - (counter * mgfhLen)); + } + + return mask; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/signers/RandomDsaKCalculator.cs b/bc-sharp-crypto/src/crypto/signers/RandomDsaKCalculator.cs new file mode 100644 index 0000000..022cc26 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/signers/RandomDsaKCalculator.cs @@ -0,0 +1,44 @@ +using System; + +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Signers +{ + public class RandomDsaKCalculator + : IDsaKCalculator + { + private BigInteger q; + private SecureRandom random; + + public virtual bool IsDeterministic + { + get { return false; } + } + + public virtual void Init(BigInteger n, SecureRandom random) + { + this.q = n; + this.random = random; + } + + public virtual void Init(BigInteger n, BigInteger d, byte[] message) + { + throw new InvalidOperationException("Operation not supported"); + } + + public virtual BigInteger NextK() + { + int qBitLength = q.BitLength; + + BigInteger k; + do + { + k = new BigInteger(qBitLength, random); + } + while (k.SignValue < 1 || k.CompareTo(q) >= 0); + + return k; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/signers/RsaDigestSigner.cs b/bc-sharp-crypto/src/crypto/signers/RsaDigestSigner.cs new file mode 100644 index 0000000..d9b19cf --- /dev/null +++ b/bc-sharp-crypto/src/crypto/signers/RsaDigestSigner.cs @@ -0,0 +1,217 @@ +using System; +using System.Collections; +using System.IO; +using System.Text; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Nist; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.TeleTrust; +using Org.BouncyCastle.Asn1.Utilities; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Encodings; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Signers; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Signers +{ + public class RsaDigestSigner + : ISigner + { + private readonly IAsymmetricBlockCipher rsaEngine = new Pkcs1Encoding(new RsaBlindedEngine()); + private readonly AlgorithmIdentifier algId; + private readonly IDigest digest; + private bool forSigning; + + private static readonly IDictionary oidMap = Platform.CreateHashtable(); + + /// + /// Load oid table. + /// + static RsaDigestSigner() + { + oidMap["RIPEMD128"] = TeleTrusTObjectIdentifiers.RipeMD128; + oidMap["RIPEMD160"] = TeleTrusTObjectIdentifiers.RipeMD160; + oidMap["RIPEMD256"] = TeleTrusTObjectIdentifiers.RipeMD256; + + oidMap["SHA-1"] = X509ObjectIdentifiers.IdSha1; + oidMap["SHA-224"] = NistObjectIdentifiers.IdSha224; + oidMap["SHA-256"] = NistObjectIdentifiers.IdSha256; + oidMap["SHA-384"] = NistObjectIdentifiers.IdSha384; + oidMap["SHA-512"] = NistObjectIdentifiers.IdSha512; + + oidMap["MD2"] = PkcsObjectIdentifiers.MD2; + oidMap["MD4"] = PkcsObjectIdentifiers.MD4; + oidMap["MD5"] = PkcsObjectIdentifiers.MD5; + } + + public RsaDigestSigner(IDigest digest) + : this(digest, (DerObjectIdentifier)oidMap[digest.AlgorithmName]) + { + } + + public RsaDigestSigner(IDigest digest, DerObjectIdentifier digestOid) + : this(digest, new AlgorithmIdentifier(digestOid, DerNull.Instance)) + { + } + + public RsaDigestSigner(IDigest digest, AlgorithmIdentifier algId) + { + this.digest = digest; + this.algId = algId; + } + + public virtual string AlgorithmName + { + get { return digest.AlgorithmName + "withRSA"; } + } + + /** + * Initialise the signer for signing or verification. + * + * @param forSigning true if for signing, false otherwise + * @param param necessary parameters. + */ + public virtual void Init( + bool forSigning, + ICipherParameters parameters) + { + this.forSigning = forSigning; + AsymmetricKeyParameter k; + + if (parameters is ParametersWithRandom) + { + k = (AsymmetricKeyParameter)((ParametersWithRandom)parameters).Parameters; + } + else + { + k = (AsymmetricKeyParameter)parameters; + } + + if (forSigning && !k.IsPrivate) + throw new InvalidKeyException("Signing requires private key."); + + if (!forSigning && k.IsPrivate) + throw new InvalidKeyException("Verification requires public key."); + + Reset(); + + rsaEngine.Init(forSigning, parameters); + } + + /** + * update the internal digest with the byte b + */ + public virtual void Update( + byte input) + { + digest.Update(input); + } + + /** + * update the internal digest with the byte array in + */ + public virtual void BlockUpdate( + byte[] input, + int inOff, + int length) + { + digest.BlockUpdate(input, inOff, length); + } + + /** + * Generate a signature for the message we've been loaded with using + * the key we were initialised with. + */ + public virtual byte[] GenerateSignature() + { + if (!forSigning) + throw new InvalidOperationException("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 virtual bool VerifySignature( + byte[] signature) + { + if (forSigning) + throw new InvalidOperationException("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) + { + return false; + } + + if (sig.Length == expected.Length) + { + return Arrays.ConstantTimeAreEqual(sig, expected); + } + 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; + + int nonEqual = 0; + + for (int i = 0; i < hash.Length; i++) + { + nonEqual |= (sig[sigOffset + i] ^ expected[expectedOffset + i]); + } + + for (int i = 0; i < sigOffset; i++) + { + nonEqual |= (sig[i] ^ expected[i]); // check header less NULL + } + + return nonEqual == 0; + } + else + { + return false; + } + } + + public virtual void Reset() + { + digest.Reset(); + } + + private byte[] DerEncode(byte[] hash) + { + if (algId == null) + { + // For raw RSA, the DigestInfo must be prepared externally + return hash; + } + + DigestInfo dInfo = new DigestInfo(algId, hash); + + return dInfo.GetDerEncoded(); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/signers/X931Signer.cs b/bc-sharp-crypto/src/crypto/signers/X931Signer.cs new file mode 100644 index 0000000..c6e44ba --- /dev/null +++ b/bc-sharp-crypto/src/crypto/signers/X931Signer.cs @@ -0,0 +1,225 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Signers +{ + /** + * X9.31-1998 - signing using a hash. + *

+ * The message digest hash, H, is encapsulated to form a byte string as follows + *

+ *
+     * EB = 06 || PS || 0xBA || H || TRAILER
+     * 
+ * where PS is a string of bytes all of value 0xBB of length such that |EB|=|n|, and TRAILER is the ISO/IEC 10118 part number† for the digest. The byte string, EB, is converted to an integer value, the message representative, f. + */ + public class X931Signer + : ISigner + { + [Obsolete("Use 'IsoTrailers' instead")] + public const int TRAILER_IMPLICIT = 0xBC; + [Obsolete("Use 'IsoTrailers' instead")] + public const int TRAILER_RIPEMD160 = 0x31CC; + [Obsolete("Use 'IsoTrailers' instead")] + public const int TRAILER_RIPEMD128 = 0x32CC; + [Obsolete("Use 'IsoTrailers' instead")] + public const int TRAILER_SHA1 = 0x33CC; + [Obsolete("Use 'IsoTrailers' instead")] + public const int TRAILER_SHA256 = 0x34CC; + [Obsolete("Use 'IsoTrailers' instead")] + public const int TRAILER_SHA512 = 0x35CC; + [Obsolete("Use 'IsoTrailers' instead")] + public const int TRAILER_SHA384 = 0x36CC; + [Obsolete("Use 'IsoTrailers' instead")] + public const int TRAILER_WHIRLPOOL = 0x37CC; + [Obsolete("Use 'IsoTrailers' instead")] + public const int TRAILER_SHA224 = 0x38CC; + + private IDigest digest; + private IAsymmetricBlockCipher cipher; + private RsaKeyParameters kParam; + + private int trailer; + private int keyBits; + private byte[] block; + + /** + * Generate a signer with either implicit or explicit trailers for X9.31. + * + * @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 X931Signer(IAsymmetricBlockCipher cipher, IDigest digest, bool isImplicit) + { + this.cipher = cipher; + this.digest = digest; + + if (isImplicit) + { + trailer = IsoTrailers.TRAILER_IMPLICIT; + } + else if (IsoTrailers.NoTrailerAvailable(digest)) + { + throw new ArgumentException("no valid trailer", "digest"); + } + else + { + trailer = IsoTrailers.GetTrailer(digest); + } + } + + public virtual string AlgorithmName + { + get { return digest.AlgorithmName + "with" + cipher.AlgorithmName + "/X9.31"; } + } + + /** + * Constructor for a signer with an explicit digest trailer. + * + * @param cipher cipher to use. + * @param digest digest to sign with. + */ + public X931Signer(IAsymmetricBlockCipher cipher, IDigest digest) + : this(cipher, digest, false) + { + } + + public virtual void Init(bool forSigning, ICipherParameters parameters) + { + kParam = (RsaKeyParameters)parameters; + + cipher.Init(forSigning, kParam); + + keyBits = kParam.Modulus.BitLength; + + block = new byte[(keyBits + 7) / 8]; + + Reset(); + } + + /// clear possible sensitive data + private void ClearBlock(byte[] block) + { + Array.Clear(block, 0, block.Length); + } + + /** + * update the internal digest with the byte b + */ + public virtual void Update(byte b) + { + digest.Update(b); + } + + /** + * update the internal digest with the byte array in + */ + public virtual void BlockUpdate(byte[] input, int off, int len) + { + digest.BlockUpdate(input, off, len); + } + + /** + * reset the internal state + */ + public virtual void Reset() + { + digest.Reset(); + } + + /** + * generate a signature for the loaded message using the key we were + * initialised with. + */ + public virtual byte[] GenerateSignature() + { + CreateSignatureBlock(); + + BigInteger t = new BigInteger(1, cipher.ProcessBlock(block, 0, block.Length)); + ClearBlock(block); + + t = t.Min(kParam.Modulus.Subtract(t)); + + return BigIntegers.AsUnsignedByteArray((kParam.Modulus.BitLength + 7) / 8, t); + } + + private void CreateSignatureBlock() + { + int digSize = digest.GetDigestSize(); + + int delta; + if (trailer == IsoTrailers.TRAILER_IMPLICIT) + { + delta = block.Length - digSize - 1; + digest.DoFinal(block, delta); + block[block.Length - 1] = (byte)IsoTrailers.TRAILER_IMPLICIT; + } + else + { + delta = block.Length - digSize - 2; + digest.DoFinal(block, delta); + block[block.Length - 2] = (byte)(trailer >> 8); + block[block.Length - 1] = (byte)trailer; + } + + block[0] = 0x6b; + for (int i = delta - 2; i != 0; i--) + { + block[i] = (byte)0xbb; + } + block[delta - 1] = (byte)0xba; + } + + /** + * return true if the signature represents a ISO9796-2 signature + * for the passed in message. + */ + public virtual bool VerifySignature(byte[] signature) + { + try + { + block = cipher.ProcessBlock(signature, 0, signature.Length); + } + catch (Exception) + { + return false; + } + + BigInteger t = new BigInteger(1, block); + BigInteger f; + + if ((t.IntValue & 15) == 12) + { + f = t; + } + else + { + t = kParam.Modulus.Subtract(t); + if ((t.IntValue & 15) == 12) + { + f = t; + } + else + { + return false; + } + } + + CreateSignatureBlock(); + + byte[] fBlock = BigIntegers.AsUnsignedByteArray(block.Length, f); + + bool rv = Arrays.ConstantTimeAreEqual(block, fBlock); + + ClearBlock(block); + ClearBlock(fBlock); + + return rv; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/AbstractTlsAgreementCredentials.cs b/bc-sharp-crypto/src/crypto/tls/AbstractTlsAgreementCredentials.cs new file mode 100644 index 0000000..2d7af80 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/AbstractTlsAgreementCredentials.cs @@ -0,0 +1,12 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public abstract class AbstractTlsAgreementCredentials + : AbstractTlsCredentials, TlsAgreementCredentials + { + /// + public abstract byte[] GenerateAgreement(AsymmetricKeyParameter peerPublicKey); + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/AbstractTlsCipherFactory.cs b/bc-sharp-crypto/src/crypto/tls/AbstractTlsCipherFactory.cs new file mode 100644 index 0000000..141ee65 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/AbstractTlsCipherFactory.cs @@ -0,0 +1,15 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class AbstractTlsCipherFactory + : TlsCipherFactory + { + /// + public virtual TlsCipher CreateCipher(TlsContext context, int encryptionAlgorithm, int macAlgorithm) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/AbstractTlsClient.cs b/bc-sharp-crypto/src/crypto/tls/AbstractTlsClient.cs new file mode 100644 index 0000000..be4702e --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/AbstractTlsClient.cs @@ -0,0 +1,256 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public abstract class AbstractTlsClient + : AbstractTlsPeer, TlsClient + { + protected TlsCipherFactory mCipherFactory; + + protected TlsClientContext mContext; + + protected IList mSupportedSignatureAlgorithms; + protected int[] mNamedCurves; + protected byte[] mClientECPointFormats, mServerECPointFormats; + + protected int mSelectedCipherSuite; + protected short mSelectedCompressionMethod; + + public AbstractTlsClient() + : this(new DefaultTlsCipherFactory()) + { + } + + public AbstractTlsClient(TlsCipherFactory cipherFactory) + { + this.mCipherFactory = cipherFactory; + } + + protected virtual bool AllowUnexpectedServerExtension(int extensionType, byte[] extensionData) + { + switch (extensionType) + { + case ExtensionType.elliptic_curves: + /* + * Exception added based on field reports that some servers do send this, although the + * Supported Elliptic Curves Extension is clearly intended to be client-only. If + * present, we still require that it is a valid EllipticCurveList. + */ + TlsEccUtilities.ReadSupportedEllipticCurvesExtension(extensionData); + return true; + default: + return false; + } + } + + protected virtual void CheckForUnexpectedServerExtension(IDictionary serverExtensions, int extensionType) + { + byte[] extensionData = TlsUtilities.GetExtensionData(serverExtensions, extensionType); + if (extensionData != null && !AllowUnexpectedServerExtension(extensionType, extensionData)) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + } + + public virtual void Init(TlsClientContext context) + { + this.mContext = context; + } + + public virtual TlsSession GetSessionToResume() + { + return null; + } + + public virtual ProtocolVersion ClientHelloRecordLayerVersion + { + get + { + // "{03,00}" + //return ProtocolVersion.SSLv3; + + // "the lowest version number supported by the client" + //return MinimumVersion; + + // "the value of ClientHello.client_version" + return ClientVersion; + } + } + + public virtual ProtocolVersion ClientVersion + { + get { return ProtocolVersion.TLSv12; } + } + + public virtual bool IsFallback + { + /* + * RFC 7507 4. The TLS_FALLBACK_SCSV cipher suite value is meant for use by clients that + * repeat a connection attempt with a downgraded protocol (perform a "fallback retry") in + * order to work around interoperability problems with legacy servers. + */ + get { return false; } + } + + public virtual IDictionary GetClientExtensions() + { + IDictionary clientExtensions = null; + + ProtocolVersion clientVersion = mContext.ClientVersion; + + /* + * RFC 5246 7.4.1.4.1. Note: this extension is not meaningful for TLS versions prior to 1.2. + * Clients MUST NOT offer it if they are offering prior versions. + */ + if (TlsUtilities.IsSignatureAlgorithmsExtensionAllowed(clientVersion)) + { + // TODO Provide a way for the user to specify the acceptable hash/signature algorithms. + + this.mSupportedSignatureAlgorithms = TlsUtilities.GetDefaultSupportedSignatureAlgorithms(); + + clientExtensions = TlsExtensionsUtilities.EnsureExtensionsInitialised(clientExtensions); + + TlsUtilities.AddSignatureAlgorithmsExtension(clientExtensions, mSupportedSignatureAlgorithms); + } + + if (TlsEccUtilities.ContainsEccCipherSuites(GetCipherSuites())) + { + /* + * RFC 4492 5.1. A client that proposes ECC cipher suites in its ClientHello message + * appends these extensions (along with any others), enumerating the curves it supports + * and the point formats it can parse. Clients SHOULD send both the Supported Elliptic + * Curves Extension and the Supported Point Formats Extension. + */ + /* + * TODO Could just add all the curves since we support them all, but users may not want + * to use unnecessarily large fields. Need configuration options. + */ + this.mNamedCurves = new int[]{ NamedCurve.secp256r1, NamedCurve.secp384r1 }; + this.mClientECPointFormats = new byte[]{ ECPointFormat.uncompressed, + ECPointFormat.ansiX962_compressed_prime, ECPointFormat.ansiX962_compressed_char2, }; + + clientExtensions = TlsExtensionsUtilities.EnsureExtensionsInitialised(clientExtensions); + + TlsEccUtilities.AddSupportedEllipticCurvesExtension(clientExtensions, mNamedCurves); + TlsEccUtilities.AddSupportedPointFormatsExtension(clientExtensions, mClientECPointFormats); + } + + return clientExtensions; + } + + public virtual ProtocolVersion MinimumVersion + { + get { return ProtocolVersion.TLSv10; } + } + + public virtual void NotifyServerVersion(ProtocolVersion serverVersion) + { + if (!MinimumVersion.IsEqualOrEarlierVersionOf(serverVersion)) + throw new TlsFatalAlert(AlertDescription.protocol_version); + } + + public abstract int[] GetCipherSuites(); + + public virtual byte[] GetCompressionMethods() + { + return new byte[]{ CompressionMethod.cls_null }; + } + + public virtual void NotifySessionID(byte[] sessionID) + { + // Currently ignored + } + + public virtual void NotifySelectedCipherSuite(int selectedCipherSuite) + { + this.mSelectedCipherSuite = selectedCipherSuite; + } + + public virtual void NotifySelectedCompressionMethod(byte selectedCompressionMethod) + { + this.mSelectedCompressionMethod = selectedCompressionMethod; + } + + public virtual void ProcessServerExtensions(IDictionary serverExtensions) + { + /* + * TlsProtocol implementation validates that any server extensions received correspond to + * client extensions sent. By default, we don't send any, and this method is not called. + */ + if (serverExtensions != null) + { + /* + * RFC 5246 7.4.1.4.1. Servers MUST NOT send this extension. + */ + CheckForUnexpectedServerExtension(serverExtensions, ExtensionType.signature_algorithms); + + CheckForUnexpectedServerExtension(serverExtensions, ExtensionType.elliptic_curves); + + if (TlsEccUtilities.IsEccCipherSuite(this.mSelectedCipherSuite)) + { + this.mServerECPointFormats = TlsEccUtilities.GetSupportedPointFormatsExtension(serverExtensions); + } + else + { + CheckForUnexpectedServerExtension(serverExtensions, ExtensionType.ec_point_formats); + } + + /* + * RFC 7685 3. The server MUST NOT echo the extension. + */ + CheckForUnexpectedServerExtension(serverExtensions, ExtensionType.padding); + } + } + + public virtual void ProcessServerSupplementalData(IList serverSupplementalData) + { + if (serverSupplementalData != null) + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + public abstract TlsKeyExchange GetKeyExchange(); + + public abstract TlsAuthentication GetAuthentication(); + + public virtual IList GetClientSupplementalData() + { + return null; + } + + public override TlsCompression GetCompression() + { + switch (mSelectedCompressionMethod) + { + case CompressionMethod.cls_null: + return new TlsNullCompression(); + + case CompressionMethod.DEFLATE: + return new TlsDeflateCompression(); + + default: + /* + * Note: internal error here; the TlsProtocol implementation verifies that the + * server-selected compression method was in the list of client-offered compression + * methods, so if we now can't produce an implementation, we shouldn't have offered it! + */ + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + public override TlsCipher GetCipher() + { + int encryptionAlgorithm = TlsUtilities.GetEncryptionAlgorithm(mSelectedCipherSuite); + int macAlgorithm = TlsUtilities.GetMacAlgorithm(mSelectedCipherSuite); + + return mCipherFactory.CreateCipher(mContext, encryptionAlgorithm, macAlgorithm); + } + + public virtual void NotifyNewSessionTicket(NewSessionTicket newSessionTicket) + { + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/AbstractTlsContext.cs b/bc-sharp-crypto/src/crypto/tls/AbstractTlsContext.cs new file mode 100644 index 0000000..ae7efc6 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/AbstractTlsContext.cs @@ -0,0 +1,152 @@ +using System; +using System.Threading; + +using Org.BouncyCastle.Crypto.Prng; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + internal abstract class AbstractTlsContext + : TlsContext + { + private static long counter = Times.NanoTime(); + +#if NETCF_1_0 + private static object counterLock = new object(); + private static long NextCounterValue() + { + lock (counterLock) + { + return ++counter; + } + } +#else + private static long NextCounterValue() + { + return Interlocked.Increment(ref counter); + } +#endif + + private readonly IRandomGenerator mNonceRandom; + private readonly SecureRandom mSecureRandom; + private readonly SecurityParameters mSecurityParameters; + + private ProtocolVersion mClientVersion = null; + private ProtocolVersion mServerVersion = null; + private TlsSession mSession = null; + private object mUserObject = null; + + internal AbstractTlsContext(SecureRandom secureRandom, SecurityParameters securityParameters) + { + IDigest d = TlsUtilities.CreateHash(HashAlgorithm.sha256); + byte[] seed = new byte[d.GetDigestSize()]; + secureRandom.NextBytes(seed); + + this.mNonceRandom = new DigestRandomGenerator(d); + mNonceRandom.AddSeedMaterial(NextCounterValue()); + mNonceRandom.AddSeedMaterial(Times.NanoTime()); + mNonceRandom.AddSeedMaterial(seed); + + this.mSecureRandom = secureRandom; + this.mSecurityParameters = securityParameters; + } + + public virtual IRandomGenerator NonceRandomGenerator + { + get { return mNonceRandom; } + } + + public virtual SecureRandom SecureRandom + { + get { return mSecureRandom; } + } + + public virtual SecurityParameters SecurityParameters + { + get { return mSecurityParameters; } + } + + public abstract bool IsServer { get; } + + public virtual ProtocolVersion ClientVersion + { + get { return mClientVersion; } + } + + internal virtual void SetClientVersion(ProtocolVersion clientVersion) + { + this.mClientVersion = clientVersion; + } + + public virtual ProtocolVersion ServerVersion + { + get { return mServerVersion; } + } + + internal virtual void SetServerVersion(ProtocolVersion serverVersion) + { + this.mServerVersion = serverVersion; + } + + public virtual TlsSession ResumableSession + { + get { return mSession; } + } + + internal virtual void SetResumableSession(TlsSession session) + { + this.mSession = session; + } + + public virtual object UserObject + { + get { return mUserObject; } + set { this.mUserObject = value; } + } + + public virtual byte[] ExportKeyingMaterial(string asciiLabel, byte[] context_value, int length) + { + /* + * TODO[session-hash] + * + * draft-ietf-tls-session-hash-04 5.4. If a client or server chooses to continue with a full + * handshake without the extended master secret extension, [..] the client or server MUST + * NOT export any key material based on the new master secret for any subsequent + * application-level authentication. In particular, it MUST disable [RFC5705] [..]. + */ + + if (context_value != null && !TlsUtilities.IsValidUint16(context_value.Length)) + throw new ArgumentException("must have length less than 2^16 (or be null)", "context_value"); + + SecurityParameters sp = SecurityParameters; + byte[] cr = sp.ClientRandom, sr = sp.ServerRandom; + + int seedLength = cr.Length + sr.Length; + if (context_value != null) + { + seedLength += (2 + context_value.Length); + } + + byte[] seed = new byte[seedLength]; + int seedPos = 0; + + Array.Copy(cr, 0, seed, seedPos, cr.Length); + seedPos += cr.Length; + Array.Copy(sr, 0, seed, seedPos, sr.Length); + seedPos += sr.Length; + if (context_value != null) + { + TlsUtilities.WriteUint16(context_value.Length, seed, seedPos); + seedPos += 2; + Array.Copy(context_value, 0, seed, seedPos, context_value.Length); + seedPos += context_value.Length; + } + + if (seedPos != seedLength) + throw new InvalidOperationException("error in calculation of seed for export"); + + return TlsUtilities.PRF(this, sp.MasterSecret, asciiLabel, seed, length); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/AbstractTlsCredentials.cs b/bc-sharp-crypto/src/crypto/tls/AbstractTlsCredentials.cs new file mode 100644 index 0000000..6411b81 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/AbstractTlsCredentials.cs @@ -0,0 +1,10 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public abstract class AbstractTlsCredentials + : TlsCredentials + { + public abstract Certificate Certificate { get; } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/AbstractTlsEncryptionCredentials.cs b/bc-sharp-crypto/src/crypto/tls/AbstractTlsEncryptionCredentials.cs new file mode 100644 index 0000000..05b129c --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/AbstractTlsEncryptionCredentials.cs @@ -0,0 +1,12 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public abstract class AbstractTlsEncryptionCredentials + : AbstractTlsCredentials, TlsEncryptionCredentials + { + /// + public abstract byte[] DecryptPreMasterSecret(byte[] encryptedPreMasterSecret); + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/AbstractTlsKeyExchange.cs b/bc-sharp-crypto/src/crypto/tls/AbstractTlsKeyExchange.cs new file mode 100644 index 0000000..294b249 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/AbstractTlsKeyExchange.cs @@ -0,0 +1,177 @@ +using System; +using System.Collections; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public abstract class AbstractTlsKeyExchange + : TlsKeyExchange + { + protected readonly int mKeyExchange; + protected IList mSupportedSignatureAlgorithms; + + protected TlsContext mContext; + + protected AbstractTlsKeyExchange(int keyExchange, IList supportedSignatureAlgorithms) + { + this.mKeyExchange = keyExchange; + this.mSupportedSignatureAlgorithms = supportedSignatureAlgorithms; + } + + protected virtual DigitallySigned ParseSignature(Stream input) + { + DigitallySigned signature = DigitallySigned.Parse(mContext, input); + SignatureAndHashAlgorithm signatureAlgorithm = signature.Algorithm; + if (signatureAlgorithm != null) + { + TlsUtilities.VerifySupportedSignatureAlgorithm(mSupportedSignatureAlgorithms, signatureAlgorithm); + } + return signature; + } + + public virtual void Init(TlsContext context) + { + this.mContext = context; + + ProtocolVersion clientVersion = context.ClientVersion; + + if (TlsUtilities.IsSignatureAlgorithmsExtensionAllowed(clientVersion)) + { + /* + * RFC 5246 7.4.1.4.1. If the client does not send the signature_algorithms extension, + * the server MUST do the following: + * + * - If the negotiated key exchange algorithm is one of (RSA, DHE_RSA, DH_RSA, RSA_PSK, + * ECDH_RSA, ECDHE_RSA), behave as if client had sent the value {sha1,rsa}. + * + * - If the negotiated key exchange algorithm is one of (DHE_DSS, DH_DSS), behave as if + * the client had sent the value {sha1,dsa}. + * + * - If the negotiated key exchange algorithm is one of (ECDH_ECDSA, ECDHE_ECDSA), + * behave as if the client had sent value {sha1,ecdsa}. + */ + if (this.mSupportedSignatureAlgorithms == null) + { + switch (mKeyExchange) + { + case KeyExchangeAlgorithm.DH_DSS: + case KeyExchangeAlgorithm.DHE_DSS: + case KeyExchangeAlgorithm.SRP_DSS: + { + this.mSupportedSignatureAlgorithms = TlsUtilities.GetDefaultDssSignatureAlgorithms(); + break; + } + + case KeyExchangeAlgorithm.ECDH_ECDSA: + case KeyExchangeAlgorithm.ECDHE_ECDSA: + { + this.mSupportedSignatureAlgorithms = TlsUtilities.GetDefaultECDsaSignatureAlgorithms(); + break; + } + + case KeyExchangeAlgorithm.DH_RSA: + case KeyExchangeAlgorithm.DHE_RSA: + case KeyExchangeAlgorithm.ECDH_RSA: + case KeyExchangeAlgorithm.ECDHE_RSA: + case KeyExchangeAlgorithm.RSA: + case KeyExchangeAlgorithm.RSA_PSK: + case KeyExchangeAlgorithm.SRP_RSA: + { + this.mSupportedSignatureAlgorithms = TlsUtilities.GetDefaultRsaSignatureAlgorithms(); + break; + } + + case KeyExchangeAlgorithm.DHE_PSK: + case KeyExchangeAlgorithm.ECDHE_PSK: + case KeyExchangeAlgorithm.PSK: + case KeyExchangeAlgorithm.SRP: + break; + + default: + throw new InvalidOperationException("unsupported key exchange algorithm"); + } + } + + } + else if (this.mSupportedSignatureAlgorithms != null) + { + throw new InvalidOperationException("supported_signature_algorithms not allowed for " + clientVersion); + } + } + + public abstract void SkipServerCredentials(); + + public virtual void ProcessServerCertificate(Certificate serverCertificate) + { + if (mSupportedSignatureAlgorithms == null) + { + /* + * TODO RFC 2246 7.4.2. Unless otherwise specified, the signing algorithm for the + * certificate must be the same as the algorithm for the certificate key. + */ + } + else + { + /* + * TODO RFC 5246 7.4.2. If the client provided a "signature_algorithms" extension, then + * all certificates provided by the server MUST be signed by a hash/signature algorithm + * pair that appears in that extension. + */ + } + } + + public virtual void ProcessServerCredentials(TlsCredentials serverCredentials) + { + ProcessServerCertificate(serverCredentials.Certificate); + } + + public virtual bool RequiresServerKeyExchange + { + get { return false; } + } + + public virtual byte[] GenerateServerKeyExchange() + { + if (RequiresServerKeyExchange) + throw new TlsFatalAlert(AlertDescription.internal_error); + + return null; + } + + public virtual void SkipServerKeyExchange() + { + if (RequiresServerKeyExchange) + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + public virtual void ProcessServerKeyExchange(Stream input) + { + if (!RequiresServerKeyExchange) + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + } + + public abstract void ValidateCertificateRequest(CertificateRequest certificateRequest); + + public virtual void SkipClientCredentials() + { + } + + public abstract void ProcessClientCredentials(TlsCredentials clientCredentials); + + public virtual void ProcessClientCertificate(Certificate clientCertificate) + { + } + + public abstract void GenerateClientKeyExchange(Stream output); + + public virtual void ProcessClientKeyExchange(Stream input) + { + // Key exchange implementation MUST support client key exchange + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + public abstract byte[] GeneratePremasterSecret(); + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/AbstractTlsPeer.cs b/bc-sharp-crypto/src/crypto/tls/AbstractTlsPeer.cs new file mode 100644 index 0000000..81a5338 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/AbstractTlsPeer.cs @@ -0,0 +1,48 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public abstract class AbstractTlsPeer + : TlsPeer + { + public virtual bool ShouldUseGmtUnixTime() + { + /* + * draft-mathewson-no-gmtunixtime-00 2. For the reasons we discuss above, we recommend that + * TLS implementors MUST by default set the entire value the ClientHello.Random and + * ServerHello.Random fields, including gmt_unix_time, to a cryptographically random + * sequence. + */ + return false; + } + + public virtual void NotifySecureRenegotiation(bool secureRenegotiation) + { + if (!secureRenegotiation) + { + /* + * RFC 5746 3.4/3.6. In this case, some clients/servers may want to terminate the handshake instead + * of continuing; see Section 4.1/4.3 for discussion. + */ + throw new TlsFatalAlert(AlertDescription.handshake_failure); + } + } + + public abstract TlsCompression GetCompression(); + + public abstract TlsCipher GetCipher(); + + public virtual void NotifyAlertRaised(byte alertLevel, byte alertDescription, string message, Exception cause) + { + } + + public virtual void NotifyAlertReceived(byte alertLevel, byte alertDescription) + { + } + + public virtual void NotifyHandshakeComplete() + { + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/AbstractTlsServer.cs b/bc-sharp-crypto/src/crypto/tls/AbstractTlsServer.cs new file mode 100644 index 0000000..52a79c9 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/AbstractTlsServer.cs @@ -0,0 +1,351 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public abstract class AbstractTlsServer + : AbstractTlsPeer, TlsServer + { + protected TlsCipherFactory mCipherFactory; + + protected TlsServerContext mContext; + + protected ProtocolVersion mClientVersion; + protected int[] mOfferedCipherSuites; + protected byte[] mOfferedCompressionMethods; + protected IDictionary mClientExtensions; + + protected bool mEncryptThenMacOffered; + protected short mMaxFragmentLengthOffered; + protected bool mTruncatedHMacOffered; + protected IList mSupportedSignatureAlgorithms; + protected bool mEccCipherSuitesOffered; + protected int[] mNamedCurves; + protected byte[] mClientECPointFormats, mServerECPointFormats; + + protected ProtocolVersion mServerVersion; + protected int mSelectedCipherSuite; + protected byte mSelectedCompressionMethod; + protected IDictionary mServerExtensions; + + public AbstractTlsServer() + : this(new DefaultTlsCipherFactory()) + { + } + + public AbstractTlsServer(TlsCipherFactory cipherFactory) + { + this.mCipherFactory = cipherFactory; + } + + protected virtual bool AllowEncryptThenMac + { + get { return true; } + } + + protected virtual bool AllowTruncatedHMac + { + get { return false; } + } + + protected virtual IDictionary CheckServerExtensions() + { + return this.mServerExtensions = TlsExtensionsUtilities.EnsureExtensionsInitialised(this.mServerExtensions); + } + + protected abstract int[] GetCipherSuites(); + + protected byte[] GetCompressionMethods() + { + return new byte[] { CompressionMethod.cls_null }; + } + + protected virtual ProtocolVersion MaximumVersion + { + get { return ProtocolVersion.TLSv11; } + } + + protected virtual ProtocolVersion MinimumVersion + { + get { return ProtocolVersion.TLSv10; } + } + + protected virtual bool SupportsClientEccCapabilities(int[] namedCurves, byte[] ecPointFormats) + { + // NOTE: BC supports all the current set of point formats so we don't check them here + + if (namedCurves == null) + { + /* + * RFC 4492 4. A client that proposes ECC cipher suites may choose not to include these + * extensions. In this case, the server is free to choose any one of the elliptic curves + * or point formats [...]. + */ + return TlsEccUtilities.HasAnySupportedNamedCurves(); + } + + for (int i = 0; i < namedCurves.Length; ++i) + { + int namedCurve = namedCurves[i]; + if (NamedCurve.IsValid(namedCurve) + && (!NamedCurve.RefersToASpecificNamedCurve(namedCurve) || TlsEccUtilities.IsSupportedNamedCurve(namedCurve))) + { + return true; + } + } + + return false; + } + + public virtual void Init(TlsServerContext context) + { + this.mContext = context; + } + + public virtual void NotifyClientVersion(ProtocolVersion clientVersion) + { + this.mClientVersion = clientVersion; + } + + public virtual void NotifyFallback(bool isFallback) + { + /* + * RFC 7507 3. If TLS_FALLBACK_SCSV appears in ClientHello.cipher_suites and the highest + * protocol version supported by the server is higher than the version indicated in + * ClientHello.client_version, the server MUST respond with a fatal inappropriate_fallback + * alert [..]. + */ + if (isFallback && MaximumVersion.IsLaterVersionOf(mClientVersion)) + throw new TlsFatalAlert(AlertDescription.inappropriate_fallback); + } + + public virtual void NotifyOfferedCipherSuites(int[] offeredCipherSuites) + { + this.mOfferedCipherSuites = offeredCipherSuites; + this.mEccCipherSuitesOffered = TlsEccUtilities.ContainsEccCipherSuites(this.mOfferedCipherSuites); + } + + public virtual void NotifyOfferedCompressionMethods(byte[] offeredCompressionMethods) + { + this.mOfferedCompressionMethods = offeredCompressionMethods; + } + + public virtual void ProcessClientExtensions(IDictionary clientExtensions) + { + this.mClientExtensions = clientExtensions; + + if (clientExtensions != null) + { + this.mEncryptThenMacOffered = TlsExtensionsUtilities.HasEncryptThenMacExtension(clientExtensions); + + this.mMaxFragmentLengthOffered = TlsExtensionsUtilities.GetMaxFragmentLengthExtension(clientExtensions); + if (mMaxFragmentLengthOffered >= 0 && !MaxFragmentLength.IsValid((byte)mMaxFragmentLengthOffered)) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + + this.mTruncatedHMacOffered = TlsExtensionsUtilities.HasTruncatedHMacExtension(clientExtensions); + + this.mSupportedSignatureAlgorithms = TlsUtilities.GetSignatureAlgorithmsExtension(clientExtensions); + if (this.mSupportedSignatureAlgorithms != null) + { + /* + * RFC 5246 7.4.1.4.1. Note: this extension is not meaningful for TLS versions prior + * to 1.2. Clients MUST NOT offer it if they are offering prior versions. + */ + if (!TlsUtilities.IsSignatureAlgorithmsExtensionAllowed(mClientVersion)) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + + this.mNamedCurves = TlsEccUtilities.GetSupportedEllipticCurvesExtension(clientExtensions); + this.mClientECPointFormats = TlsEccUtilities.GetSupportedPointFormatsExtension(clientExtensions); + } + + /* + * RFC 4429 4. The client MUST NOT include these extensions in the ClientHello message if it + * does not propose any ECC cipher suites. + * + * NOTE: This was overly strict as there may be ECC cipher suites that we don't recognize. + * Also, draft-ietf-tls-negotiated-ff-dhe will be overloading the 'elliptic_curves' + * extension to explicitly allow FFDHE (i.e. non-ECC) groups. + */ + //if (!this.mEccCipherSuitesOffered && (this.mNamedCurves != null || this.mClientECPointFormats != null)) + // throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + + public virtual ProtocolVersion GetServerVersion() + { + if (MinimumVersion.IsEqualOrEarlierVersionOf(mClientVersion)) + { + ProtocolVersion maximumVersion = MaximumVersion; + if (mClientVersion.IsEqualOrEarlierVersionOf(maximumVersion)) + { + return mServerVersion = mClientVersion; + } + if (mClientVersion.IsLaterVersionOf(maximumVersion)) + { + return mServerVersion = maximumVersion; + } + } + throw new TlsFatalAlert(AlertDescription.protocol_version); + } + + public virtual int GetSelectedCipherSuite() + { + /* + * RFC 5246 7.4.3. In order to negotiate correctly, the server MUST check any candidate + * cipher suites against the "signature_algorithms" extension before selecting them. This is + * somewhat inelegant but is a compromise designed to minimize changes to the original + * cipher suite design. + */ + IList sigAlgs = TlsUtilities.GetUsableSignatureAlgorithms(this.mSupportedSignatureAlgorithms); + + /* + * RFC 4429 5.1. A server that receives a ClientHello containing one or both of these + * extensions MUST use the client's enumerated capabilities to guide its selection of an + * appropriate cipher suite. One of the proposed ECC cipher suites must be negotiated only + * if the server can successfully complete the handshake while using the curves and point + * formats supported by the client [...]. + */ + bool eccCipherSuitesEnabled = SupportsClientEccCapabilities(this.mNamedCurves, this.mClientECPointFormats); + + int[] cipherSuites = GetCipherSuites(); + for (int i = 0; i < cipherSuites.Length; ++i) + { + int cipherSuite = cipherSuites[i]; + + if (Arrays.Contains(this.mOfferedCipherSuites, cipherSuite) + && (eccCipherSuitesEnabled || !TlsEccUtilities.IsEccCipherSuite(cipherSuite)) + && TlsUtilities.IsValidCipherSuiteForVersion(cipherSuite, mServerVersion) + && TlsUtilities.IsValidCipherSuiteForSignatureAlgorithms(cipherSuite, sigAlgs)) + { + return this.mSelectedCipherSuite = cipherSuite; + } + } + throw new TlsFatalAlert(AlertDescription.handshake_failure); + } + + public virtual byte GetSelectedCompressionMethod() + { + byte[] compressionMethods = GetCompressionMethods(); + for (int i = 0; i < compressionMethods.Length; ++i) + { + if (Arrays.Contains(mOfferedCompressionMethods, compressionMethods[i])) + { + return this.mSelectedCompressionMethod = compressionMethods[i]; + } + } + throw new TlsFatalAlert(AlertDescription.handshake_failure); + } + + // IDictionary is (Int32 -> byte[]) + public virtual IDictionary GetServerExtensions() + { + if (this.mEncryptThenMacOffered && AllowEncryptThenMac) + { + /* + * RFC 7366 3. If a server receives an encrypt-then-MAC request extension from a client + * and then selects a stream or Authenticated Encryption with Associated Data (AEAD) + * ciphersuite, it MUST NOT send an encrypt-then-MAC response extension back to the + * client. + */ + if (TlsUtilities.IsBlockCipherSuite(this.mSelectedCipherSuite)) + { + TlsExtensionsUtilities.AddEncryptThenMacExtension(CheckServerExtensions()); + } + } + + if (this.mMaxFragmentLengthOffered >= 0 + && TlsUtilities.IsValidUint8(mMaxFragmentLengthOffered) + && MaxFragmentLength.IsValid((byte)mMaxFragmentLengthOffered)) + { + TlsExtensionsUtilities.AddMaxFragmentLengthExtension(CheckServerExtensions(), (byte)mMaxFragmentLengthOffered); + } + + if (this.mTruncatedHMacOffered && AllowTruncatedHMac) + { + TlsExtensionsUtilities.AddTruncatedHMacExtension(CheckServerExtensions()); + } + + if (this.mClientECPointFormats != null && TlsEccUtilities.IsEccCipherSuite(this.mSelectedCipherSuite)) + { + /* + * RFC 4492 5.2. A server that selects an ECC cipher suite in response to a ClientHello + * message including a Supported Point Formats Extension appends this extension (along + * with others) to its ServerHello message, enumerating the point formats it can parse. + */ + this.mServerECPointFormats = new byte[]{ ECPointFormat.uncompressed, + ECPointFormat.ansiX962_compressed_prime, ECPointFormat.ansiX962_compressed_char2, }; + + TlsEccUtilities.AddSupportedPointFormatsExtension(CheckServerExtensions(), mServerECPointFormats); + } + + return mServerExtensions; + } + + public virtual IList GetServerSupplementalData() + { + return null; + } + + public abstract TlsCredentials GetCredentials(); + + public virtual CertificateStatus GetCertificateStatus() + { + return null; + } + + public abstract TlsKeyExchange GetKeyExchange(); + + public virtual CertificateRequest GetCertificateRequest() + { + return null; + } + + public virtual void ProcessClientSupplementalData(IList clientSupplementalData) + { + if (clientSupplementalData != null) + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + public virtual void NotifyClientCertificate(Certificate clientCertificate) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + public override TlsCompression GetCompression() + { + switch (mSelectedCompressionMethod) + { + case CompressionMethod.cls_null: + return new TlsNullCompression(); + + default: + /* + * Note: internal error here; we selected the compression method, so if we now can't + * produce an implementation, we shouldn't have chosen it! + */ + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + public override TlsCipher GetCipher() + { + int encryptionAlgorithm = TlsUtilities.GetEncryptionAlgorithm(mSelectedCipherSuite); + int macAlgorithm = TlsUtilities.GetMacAlgorithm(mSelectedCipherSuite); + + return mCipherFactory.CreateCipher(mContext, encryptionAlgorithm, macAlgorithm); + } + + public virtual NewSessionTicket GetNewSessionTicket() + { + /* + * RFC 5077 3.3. If the server determines that it does not want to include a ticket after it + * has included the SessionTicket extension in the ServerHello, then it sends a zero-length + * ticket in the NewSessionTicket handshake message. + */ + return new NewSessionTicket(0L, TlsUtilities.EmptyBytes); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/AbstractTlsSigner.cs b/bc-sharp-crypto/src/crypto/tls/AbstractTlsSigner.cs new file mode 100644 index 0000000..1f4aabf --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/AbstractTlsSigner.cs @@ -0,0 +1,50 @@ +using System; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public abstract class AbstractTlsSigner + : TlsSigner + { + protected TlsContext mContext; + + public virtual void Init(TlsContext context) + { + this.mContext = context; + } + + public virtual byte[] GenerateRawSignature(AsymmetricKeyParameter privateKey, byte[] md5AndSha1) + { + return GenerateRawSignature(null, privateKey, md5AndSha1); + } + + public abstract byte[] GenerateRawSignature(SignatureAndHashAlgorithm algorithm, + AsymmetricKeyParameter privateKey, byte[] hash); + + public virtual bool VerifyRawSignature(byte[] sigBytes, AsymmetricKeyParameter publicKey, byte[] md5AndSha1) + { + return VerifyRawSignature(null, sigBytes, publicKey, md5AndSha1); + } + + public abstract bool VerifyRawSignature(SignatureAndHashAlgorithm algorithm, byte[] sigBytes, + AsymmetricKeyParameter publicKey, byte[] hash); + + public virtual ISigner CreateSigner(AsymmetricKeyParameter privateKey) + { + return CreateSigner(null, privateKey); + } + + public abstract ISigner CreateSigner(SignatureAndHashAlgorithm algorithm, AsymmetricKeyParameter privateKey); + + public virtual ISigner CreateVerifyer(AsymmetricKeyParameter publicKey) + { + return CreateVerifyer(null, publicKey); + } + + public abstract ISigner CreateVerifyer(SignatureAndHashAlgorithm algorithm, AsymmetricKeyParameter publicKey); + + public abstract bool IsValidPublicKey(AsymmetricKeyParameter publicKey); + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/AbstractTlsSignerCredentials.cs b/bc-sharp-crypto/src/crypto/tls/AbstractTlsSignerCredentials.cs new file mode 100644 index 0000000..886c46c --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/AbstractTlsSignerCredentials.cs @@ -0,0 +1,20 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public abstract class AbstractTlsSignerCredentials + : AbstractTlsCredentials, TlsSignerCredentials + { + /// + public abstract byte[] GenerateCertificateSignature(byte[] hash); + + public virtual SignatureAndHashAlgorithm SignatureAndHashAlgorithm + { + get + { + throw new InvalidOperationException("TlsSignerCredentials implementation does not support (D)TLS 1.2+"); + } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/AlertDescription.cs b/bc-sharp-crypto/src/crypto/tls/AlertDescription.cs new file mode 100644 index 0000000..4e2464b --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/AlertDescription.cs @@ -0,0 +1,304 @@ +namespace Org.BouncyCastle.Crypto.Tls +{ + /// + /// RFC 5246 7.2 + /// + public abstract class AlertDescription + { + /** + * This message notifies the recipient that the sender will not send any more messages on this + * connection. Note that as of TLS 1.1, failure to properly close a connection no longer + * requires that a session not be resumed. This is a change from TLS 1.0 ("The session becomes + * unresumable if any connection is terminated without proper close_notify messages with level + * equal to warning.") to conform with widespread implementation practice. + */ + public const byte close_notify = 0; + + /** + * An inappropriate message was received. This alert is always fatal and should never be + * observed in communication between proper implementations. + */ + public const byte unexpected_message = 10; + + /** + * This alert is returned if a record is received with an incorrect MAC. This alert also MUST be + * returned if an alert is sent because a TLSCiphertext decrypted in an invalid way: either it + * wasn't an even multiple of the block length, or its padding values, when checked, weren't + * correct. This message is always fatal and should never be observed in communication between + * proper implementations (except when messages were corrupted in the network). + */ + public const byte bad_record_mac = 20; + + /** + * This alert was used in some earlier versions of TLS, and may have permitted certain attacks + * against the CBC mode [CBCATT]. It MUST NOT be sent by compliant implementations. + */ + public const byte decryption_failed = 21; + + /** + * A TLSCiphertext record was received that had a length more than 2^14+2048 bytes, or a record + * decrypted to a TLSCompressed record with more than 2^14+1024 bytes. This message is always + * fatal and should never be observed in communication between proper implementations (except + * when messages were corrupted in the network). + */ + public const byte record_overflow = 22; + + /** + * The decompression function received improper input (e.g., data that would expand to excessive + * length). This message is always fatal and should never be observed in communication between + * proper implementations. + */ + public const byte decompression_failure = 30; + + /** + * Reception of a handshake_failure alert message indicates that the sender was unable to + * negotiate an acceptable set of security parameters given the options available. This is a + * fatal error. + */ + public const byte handshake_failure = 40; + + /** + * This alert was used in SSLv3 but not any version of TLS. It MUST NOT be sent by compliant + * implementations. + */ + public const byte no_certificate = 41; + + /** + * A certificate was corrupt, contained signatures that did not verify correctly, etc. + */ + public const byte bad_certificate = 42; + + /** + * A certificate was of an unsupported type. + */ + public const byte unsupported_certificate = 43; + + /** + * A certificate was revoked by its signer. + */ + public const byte certificate_revoked = 44; + + /** + * A certificate has expired or is not currently valid. + */ + public const byte certificate_expired = 45; + + /** + * Some other (unspecified) issue arose in processing the certificate, rendering it + * unacceptable. + */ + public const byte certificate_unknown = 46; + + /** + * A field in the handshake was out of range or inconsistent with other fields. This message is + * always fatal. + */ + public const byte illegal_parameter = 47; + + /** + * A valid certificate chain or partial chain was received, but the certificate was not accepted + * because the CA certificate could not be located or couldn't be matched with a known, trusted + * CA. This message is always fatal. + */ + public const byte unknown_ca = 48; + + /** + * A valid certificate was received, but when access control was applied, the sender decided not + * to proceed with negotiation. This message is always fatal. + */ + public const byte access_denied = 49; + + /** + * A message could not be decoded because some field was out of the specified range or the + * length of the message was incorrect. This message is always fatal and should never be + * observed in communication between proper implementations (except when messages were corrupted + * in the network). + */ + public const byte decode_error = 50; + + /** + * A handshake cryptographic operation failed, including being unable to correctly verify a + * signature or validate a Finished message. This message is always fatal. + */ + public const byte decrypt_error = 51; + + /** + * This alert was used in some earlier versions of TLS. It MUST NOT be sent by compliant + * implementations. + */ + public const byte export_restriction = 60; + + /** + * The protocol version the client has attempted to negotiate is recognized but not supported. + * (For example, old protocol versions might be avoided for security reasons.) This message is + * always fatal. + */ + public const byte protocol_version = 70; + + /** + * Returned instead of handshake_failure when a negotiation has failed specifically because the + * server requires ciphers more secure than those supported by the client. This message is + * always fatal. + */ + public const byte insufficient_security = 71; + + /** + * An internal error unrelated to the peer or the correctness of the protocol (such as a memory + * allocation failure) makes it impossible to continue. This message is always fatal. + */ + public const byte internal_error = 80; + + /** + * This handshake is being canceled for some reason unrelated to a protocol failure. If the user + * cancels an operation after the handshake is complete, just closing the connection by sending + * a close_notify is more appropriate. This alert should be followed by a close_notify. This + * message is generally a warning. + */ + public const byte user_canceled = 90; + + /** + * Sent by the client in response to a hello request or by the server in response to a client + * hello after initial handshaking. Either of these would normally lead to renegotiation; when + * that is not appropriate, the recipient should respond with this alert. At that point, the + * original requester can decide whether to proceed with the connection. One case where this + * would be appropriate is where a server has spawned a process to satisfy a request; the + * process might receive security parameters (key length, authentication, etc.) at startup, and + * it might be difficult to communicate changes to these parameters after that point. This + * message is always a warning. + */ + public const byte no_renegotiation = 100; + + /** + * Sent by clients that receive an extended server hello containing an extension that they did + * not put in the corresponding client hello. This message is always fatal. + */ + public const byte unsupported_extension = 110; + + /* + * RFC 3546 + */ + + /** + * This alert is sent by servers who are unable to retrieve a certificate chain from the URL + * supplied by the client (see Section 3.3). This message MAY be fatal - for example if client + * authentication is required by the server for the handshake to continue and the server is + * unable to retrieve the certificate chain, it may send a fatal alert. + */ + public const byte certificate_unobtainable = 111; + + /** + * This alert is sent by servers that receive a server_name extension request, but do not + * recognize the server name. This message MAY be fatal. + */ + public const byte unrecognized_name = 112; + + /** + * This alert is sent by clients that receive an invalid certificate status response (see + * Section 3.6). This message is always fatal. + */ + public const byte bad_certificate_status_response = 113; + + /** + * This alert is sent by servers when a certificate hash does not match a client provided + * certificate_hash. This message is always fatal. + */ + public const byte bad_certificate_hash_value = 114; + + /* + * RFC 4279 + */ + + /** + * If the server does not recognize the PSK identity, it MAY respond with an + * "unknown_psk_identity" alert message. + */ + public const byte unknown_psk_identity = 115; + + /* + * RFC 7507 + */ + + /** + * If TLS_FALLBACK_SCSV appears in ClientHello.cipher_suites and the highest protocol version + * supported by the server is higher than the version indicated in ClientHello.client_version, + * the server MUST respond with a fatal inappropriate_fallback alert [..]. + */ + public const byte inappropriate_fallback = 86; + + public static string GetName(byte alertDescription) + { + switch (alertDescription) + { + case close_notify: + return "close_notify"; + case unexpected_message: + return "unexpected_message"; + case bad_record_mac: + return "bad_record_mac"; + case decryption_failed: + return "decryption_failed"; + case record_overflow: + return "record_overflow"; + case decompression_failure: + return "decompression_failure"; + case handshake_failure: + return "handshake_failure"; + case no_certificate: + return "no_certificate"; + case bad_certificate: + return "bad_certificate"; + case unsupported_certificate: + return "unsupported_certificate"; + case certificate_revoked: + return "certificate_revoked"; + case certificate_expired: + return "certificate_expired"; + case certificate_unknown: + return "certificate_unknown"; + case illegal_parameter: + return "illegal_parameter"; + case unknown_ca: + return "unknown_ca"; + case access_denied: + return "access_denied"; + case decode_error: + return "decode_error"; + case decrypt_error: + return "decrypt_error"; + case export_restriction: + return "export_restriction"; + case protocol_version: + return "protocol_version"; + case insufficient_security: + return "insufficient_security"; + case internal_error: + return "internal_error"; + case user_canceled: + return "user_canceled"; + case no_renegotiation: + return "no_renegotiation"; + case unsupported_extension: + return "unsupported_extension"; + case certificate_unobtainable: + return "certificate_unobtainable"; + case unrecognized_name: + return "unrecognized_name"; + case bad_certificate_status_response: + return "bad_certificate_status_response"; + case bad_certificate_hash_value: + return "bad_certificate_hash_value"; + case unknown_psk_identity: + return "unknown_psk_identity"; + case inappropriate_fallback: + return "inappropriate_fallback"; + default: + return "UNKNOWN"; + } + } + + public static string GetText(byte alertDescription) + { + return GetName(alertDescription) + "(" + alertDescription + ")"; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/AlertLevel.cs b/bc-sharp-crypto/src/crypto/tls/AlertLevel.cs new file mode 100644 index 0000000..9461a0b --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/AlertLevel.cs @@ -0,0 +1,29 @@ +namespace Org.BouncyCastle.Crypto.Tls +{ + /// + /// RFC 5246 7.2 + /// + public abstract class AlertLevel + { + public const byte warning = 1; + public const byte fatal = 2; + + public static string GetName(byte alertDescription) + { + switch (alertDescription) + { + case warning: + return "warning"; + case fatal: + return "fatal"; + default: + return "UNKNOWN"; + } + } + + public static string GetText(byte alertDescription) + { + return GetName(alertDescription) + "(" + alertDescription + ")"; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/BasicTlsPskIdentity.cs b/bc-sharp-crypto/src/crypto/tls/BasicTlsPskIdentity.cs new file mode 100644 index 0000000..db59544 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/BasicTlsPskIdentity.cs @@ -0,0 +1,43 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class BasicTlsPskIdentity + : TlsPskIdentity + { + protected byte[] mIdentity; + protected byte[] mPsk; + + public BasicTlsPskIdentity(byte[] identity, byte[] psk) + { + this.mIdentity = Arrays.Clone(identity); + this.mPsk = Arrays.Clone(psk); + } + + public BasicTlsPskIdentity(string identity, byte[] psk) + { + this.mIdentity = Strings.ToUtf8ByteArray(identity); + this.mPsk = Arrays.Clone(psk); + } + + public virtual void SkipIdentityHint() + { + } + + public virtual void NotifyIdentityHint(byte[] psk_identity_hint) + { + } + + public virtual byte[] GetPskIdentity() + { + return mIdentity; + } + + public virtual byte[] GetPsk() + { + return mPsk; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/BulkCipherAlgorithm.cs b/bc-sharp-crypto/src/crypto/tls/BulkCipherAlgorithm.cs new file mode 100644 index 0000000..07ff8dc --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/BulkCipherAlgorithm.cs @@ -0,0 +1,25 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /// RFC 2246 + /// + /// Note that the values here are implementation-specific and arbitrary. It is recommended not to + /// depend on the particular values (e.g. serialization). + /// + public abstract class BulkCipherAlgorithm + { + public const int cls_null = 0; + public const int rc4 = 1; + public const int rc2 = 2; + public const int des = 3; + public const int cls_3des = 4; + public const int des40 = 5; + + /* + * RFC 4346 + */ + public const int aes = 6; + public const int idea = 7; + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/ByteQueue.cs b/bc-sharp-crypto/src/crypto/tls/ByteQueue.cs new file mode 100644 index 0000000..b4df685 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/ByteQueue.cs @@ -0,0 +1,211 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /// + /// A queue for bytes. + ///

+ /// This file could be more optimized. + ///

+ ///
+ public class ByteQueue + { + /// The smallest number which can be written as 2^x which is bigger than i. + public static 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 const int DefaultCapacity = 1024; + + /** + * The buffer where we store our data. + */ + private byte[] databuf; + + /** + * 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; + + private bool readOnlyBuf = false; + + public ByteQueue() + : this(DefaultCapacity) + { + } + + public ByteQueue(int capacity) + { + this.databuf = capacity == 0 ? TlsUtilities.EmptyBytes : new byte[capacity]; + } + + public ByteQueue(byte[] buf, int off, int len) + { + this.databuf = buf; + this.skipped = off; + this.available = len; + this.readOnlyBuf = true; + } + + /// Add some data to our buffer. + /// A byte-array to read data from. + /// How many bytes to skip at the beginning of the array. + /// How many bytes to read from the array. + public void AddData( + byte[] data, + int offset, + int len) + { + if (readOnlyBuf) + throw new InvalidOperationException("Cannot add data to read-only buffer"); + + if ((skipped + available + len) > databuf.Length) + { + int desiredSize = ByteQueue.NextTwoPow(available + len); + if (desiredSize > databuf.Length) + { + byte[] tmp = new byte[desiredSize]; + Array.Copy(databuf, skipped, tmp, 0, available); + databuf = tmp; + } + else + { + Array.Copy(databuf, skipped, databuf, 0, available); + } + skipped = 0; + } + + Array.Copy(data, offset, databuf, skipped + available, len); + available += len; + } + + /// The number of bytes which are available in this buffer. + public int Available + { + get { return available; } + } + + /// Copy some bytes from the beginning of the data to the provided Stream. + /// The Stream to copy the bytes to. + /// How many bytes to copy. + /// If insufficient data is available. + /// If there is a problem copying the data. + public void CopyTo(Stream output, int length) + { + if (length > available) + throw new InvalidOperationException("Cannot copy " + length + " bytes, only got " + available); + + output.Write(databuf, skipped, length); + } + + /// Read data from the buffer. + /// The buffer where the read data will be copied to. + /// How many bytes to skip at the beginning of buf. + /// How many bytes to read at all. + /// How many bytes from our data to skip. + public void Read( + byte[] buf, + int offset, + int len, + int skip) + { + if ((buf.Length - offset) < len) + { + throw new ArgumentException("Buffer size of " + buf.Length + " is too small for a read of " + len + " bytes"); + } + if ((available - skip) < len) + { + throw new InvalidOperationException("Not enough data to read"); + } + Array.Copy(databuf, skipped + skip, buf, offset, len); + } + + /// Return a MemoryStream over some bytes at the beginning of the data. + /// How many bytes will be readable. + /// A MemoryStream over the data. + /// If insufficient data is available. + public MemoryStream ReadFrom(int length) + { + if (length > available) + throw new InvalidOperationException("Cannot read " + length + " bytes, only got " + available); + + int position = skipped; + + available -= length; + skipped += length; + + return new MemoryStream(databuf, position, length, false); + } + + /// Remove some bytes from our data from the beginning. + /// How many bytes to remove. + public void RemoveData( + int i) + { + if (i > available) + { + throw new InvalidOperationException("Cannot remove " + i + " bytes, only got " + available); + } + + /* + * Skip the data. + */ + available -= i; + skipped += i; + } + + public void RemoveData(byte[] buf, int off, int len, int skip) + { + Read(buf, off, len, skip); + RemoveData(skip + len); + } + + public byte[] RemoveData(int len, int skip) + { + byte[] buf = new byte[len]; + RemoveData(buf, 0, len, skip); + return buf; + } + + public void Shrink() + { + if (available == 0) + { + databuf = TlsUtilities.EmptyBytes; + skipped = 0; + } + else + { + int desiredSize = ByteQueue.NextTwoPow(available); + if (desiredSize < databuf.Length) + { + byte[] tmp = new byte[desiredSize]; + Array.Copy(databuf, skipped, tmp, 0, available); + databuf = tmp; + skipped = 0; + } + } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/ByteQueueStream.cs b/bc-sharp-crypto/src/crypto/tls/ByteQueueStream.cs new file mode 100644 index 0000000..249e609 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/ByteQueueStream.cs @@ -0,0 +1,110 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class ByteQueueStream + : Stream + { + private readonly ByteQueue buffer; + + public ByteQueueStream() + { + this.buffer = new ByteQueue(); + } + + public virtual int Available + { + get { return buffer.Available; } + } + + public override bool CanRead + { + get { return true; } + } + + public override bool CanSeek + { + get { return false; } + } + + public override bool CanWrite + { + get { return true; } + } + + public override void Flush() + { + } + + public override long Length + { + get { throw new NotSupportedException(); } + } + + public virtual int Peek(byte[] buf) + { + int bytesToRead = System.Math.Min(buffer.Available, buf.Length); + buffer.Read(buf, 0, bytesToRead, 0); + return bytesToRead; + } + + public override long Position + { + get { throw new NotSupportedException(); } + set { throw new NotSupportedException(); } + } + + public virtual int Read(byte[] buf) + { + return Read(buf, 0, buf.Length); + } + + public override int Read(byte[] buf, int off, int len) + { + int bytesToRead = System.Math.Min(buffer.Available, len); + buffer.RemoveData(buf, off, bytesToRead, 0); + return bytesToRead; + } + + public override int ReadByte() + { + if (buffer.Available == 0) + return -1; + + return buffer.RemoveData(1, 0)[0] & 0xFF; + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } + + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + public virtual int Skip(int n) + { + int bytesToSkip = System.Math.Min(buffer.Available, n); + buffer.RemoveData(bytesToSkip); + return bytesToSkip; + } + + public virtual void Write(byte[] buf) + { + buffer.AddData(buf, 0, buf.Length); + } + + public override void Write(byte[] buf, int off, int len) + { + buffer.AddData(buf, off, len); + } + + public override void WriteByte(byte b) + { + buffer.AddData(new byte[]{ b }, 0, 1); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/CertChainType.cs b/bc-sharp-crypto/src/crypto/tls/CertChainType.cs new file mode 100644 index 0000000..cbb1834 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/CertChainType.cs @@ -0,0 +1,18 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /* + * RFC 3546 3.3. + */ + public abstract class CertChainType + { + public const byte individual_certs = 0; + public const byte pkipath = 1; + + public static bool IsValid(byte certChainType) + { + return certChainType >= individual_certs && certChainType <= pkipath; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/Certificate.cs b/bc-sharp-crypto/src/crypto/tls/Certificate.cs new file mode 100644 index 0000000..e047999 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/Certificate.cs @@ -0,0 +1,136 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /** + * Parsing and encoding of a Certificate struct from RFC 4346. + *

+ *

+     * opaque ASN.1Cert<2^24-1>;
+     *
+     * struct {
+     *     ASN.1Cert certificate_list<0..2^24-1>;
+     * } Certificate;
+     * 
+ * + * @see Org.BouncyCastle.Asn1.X509.X509CertificateStructure + */ + public class Certificate + { + public static readonly Certificate EmptyChain = new Certificate(new X509CertificateStructure[0]); + + /** + * The certificates. + */ + protected readonly X509CertificateStructure[] mCertificateList; + + public Certificate(X509CertificateStructure[] certificateList) + { + if (certificateList == null) + throw new ArgumentNullException("certificateList"); + + this.mCertificateList = certificateList; + } + + /** + * @return an array of {@link org.bouncycastle.asn1.x509.Certificate} representing a certificate + * chain. + */ + public virtual X509CertificateStructure[] GetCertificateList() + { + return CloneCertificateList(); + } + + public virtual X509CertificateStructure GetCertificateAt(int index) + { + return mCertificateList[index]; + } + + public virtual int Length + { + get { return mCertificateList.Length; } + } + + /** + * @return true if this certificate chain contains no certificates, or + * false otherwise. + */ + public virtual bool IsEmpty + { + get { return mCertificateList.Length == 0; } + } + + /** + * Encode this {@link Certificate} to a {@link Stream}. + * + * @param output the {@link Stream} to encode to. + * @throws IOException + */ + public virtual void Encode(Stream output) + { + IList derEncodings = Platform.CreateArrayList(mCertificateList.Length); + + int totalLength = 0; + foreach (Asn1Encodable asn1Cert in mCertificateList) + { + byte[] derEncoding = asn1Cert.GetEncoded(Asn1Encodable.Der); + derEncodings.Add(derEncoding); + totalLength += derEncoding.Length + 3; + } + + TlsUtilities.CheckUint24(totalLength); + TlsUtilities.WriteUint24(totalLength, output); + + foreach (byte[] derEncoding in derEncodings) + { + TlsUtilities.WriteOpaque24(derEncoding, output); + } + } + + /** + * Parse a {@link Certificate} from a {@link Stream}. + * + * @param input the {@link Stream} to parse from. + * @return a {@link Certificate} object. + * @throws IOException + */ + public static Certificate Parse(Stream input) + { + int totalLength = TlsUtilities.ReadUint24(input); + if (totalLength == 0) + { + return EmptyChain; + } + + byte[] certListData = TlsUtilities.ReadFully(totalLength, input); + + MemoryStream buf = new MemoryStream(certListData, false); + + IList certificate_list = Platform.CreateArrayList(); + while (buf.Position < buf.Length) + { + byte[] berEncoding = TlsUtilities.ReadOpaque24(buf); + Asn1Object asn1Cert = TlsUtilities.ReadAsn1Object(berEncoding); + certificate_list.Add(X509CertificateStructure.GetInstance(asn1Cert)); + } + + X509CertificateStructure[] certificateList = new X509CertificateStructure[certificate_list.Count]; + for (int i = 0; i < certificate_list.Count; ++i) + { + certificateList[i] = (X509CertificateStructure)certificate_list[i]; + } + return new Certificate(certificateList); + } + + protected virtual X509CertificateStructure[] CloneCertificateList() + { + return (X509CertificateStructure[])mCertificateList.Clone(); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/CertificateRequest.cs b/bc-sharp-crypto/src/crypto/tls/CertificateRequest.cs new file mode 100644 index 0000000..f3dcb3b --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/CertificateRequest.cs @@ -0,0 +1,156 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /** + * Parsing and encoding of a CertificateRequest struct from RFC 4346. + *

+ *

+     * struct {
+     *     ClientCertificateType certificate_types<1..2^8-1>;
+     *     DistinguishedName certificate_authorities<3..2^16-1>
+     * } CertificateRequest;
+     * 
+ * + * @see ClientCertificateType + * @see X509Name + */ + public class CertificateRequest + { + protected readonly byte[] mCertificateTypes; + protected readonly IList mSupportedSignatureAlgorithms; + protected readonly IList mCertificateAuthorities; + + /** + * @param certificateTypes see {@link ClientCertificateType} for valid constants. + * @param certificateAuthorities an {@link IList} of {@link X509Name}. + */ + public CertificateRequest(byte[] certificateTypes, IList supportedSignatureAlgorithms, + IList certificateAuthorities) + { + this.mCertificateTypes = certificateTypes; + this.mSupportedSignatureAlgorithms = supportedSignatureAlgorithms; + this.mCertificateAuthorities = certificateAuthorities; + } + + /** + * @return an array of certificate types + * @see {@link ClientCertificateType} + */ + public virtual byte[] CertificateTypes + { + get { return mCertificateTypes; } + } + + /** + * @return an {@link IList} of {@link SignatureAndHashAlgorithm} (or null before TLS 1.2). + */ + public virtual IList SupportedSignatureAlgorithms + { + get { return mSupportedSignatureAlgorithms; } + } + + /** + * @return an {@link IList} of {@link X509Name} + */ + public virtual IList CertificateAuthorities + { + get { return mCertificateAuthorities; } + } + + /** + * Encode this {@link CertificateRequest} to a {@link Stream}. + * + * @param output the {@link Stream} to encode to. + * @throws IOException + */ + public virtual void Encode(Stream output) + { + if (mCertificateTypes == null || mCertificateTypes.Length == 0) + { + TlsUtilities.WriteUint8(0, output); + } + else + { + TlsUtilities.WriteUint8ArrayWithUint8Length(mCertificateTypes, output); + } + + if (mSupportedSignatureAlgorithms != null) + { + // TODO Check whether SignatureAlgorithm.anonymous is allowed here + TlsUtilities.EncodeSupportedSignatureAlgorithms(mSupportedSignatureAlgorithms, false, output); + } + + if (mCertificateAuthorities == null || mCertificateAuthorities.Count < 1) + { + TlsUtilities.WriteUint16(0, output); + } + else + { + IList derEncodings = Platform.CreateArrayList(mCertificateAuthorities.Count); + + int totalLength = 0; + foreach (Asn1Encodable certificateAuthority in mCertificateAuthorities) + { + byte[] derEncoding = certificateAuthority.GetEncoded(Asn1Encodable.Der); + derEncodings.Add(derEncoding); + totalLength += derEncoding.Length + 2; + } + + TlsUtilities.CheckUint16(totalLength); + TlsUtilities.WriteUint16(totalLength, output); + + foreach (byte[] derEncoding in derEncodings) + { + TlsUtilities.WriteOpaque16(derEncoding, output); + } + } + } + + /** + * Parse a {@link CertificateRequest} from a {@link Stream}. + * + * @param context + * the {@link TlsContext} of the current connection. + * @param input + * the {@link Stream} to parse from. + * @return a {@link CertificateRequest} object. + * @throws IOException + */ + public static CertificateRequest Parse(TlsContext context, Stream input) + { + int numTypes = TlsUtilities.ReadUint8(input); + byte[] certificateTypes = new byte[numTypes]; + for (int i = 0; i < numTypes; ++i) + { + certificateTypes[i] = TlsUtilities.ReadUint8(input); + } + + IList supportedSignatureAlgorithms = null; + if (TlsUtilities.IsTlsV12(context)) + { + // TODO Check whether SignatureAlgorithm.anonymous is allowed here + supportedSignatureAlgorithms = TlsUtilities.ParseSupportedSignatureAlgorithms(false, input); + } + + IList certificateAuthorities = Platform.CreateArrayList(); + byte[] certAuthData = TlsUtilities.ReadOpaque16(input); + MemoryStream bis = new MemoryStream(certAuthData, false); + while (bis.Position < bis.Length) + { + byte[] derEncoding = TlsUtilities.ReadOpaque16(bis); + Asn1Object asn1 = TlsUtilities.ReadDerObject(derEncoding); + // TODO Switch to X500Name when available + certificateAuthorities.Add(X509Name.GetInstance(asn1)); + } + + return new CertificateRequest(certificateTypes, supportedSignatureAlgorithms, certificateAuthorities); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/CertificateStatus.cs b/bc-sharp-crypto/src/crypto/tls/CertificateStatus.cs new file mode 100644 index 0000000..0f95475 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/CertificateStatus.cs @@ -0,0 +1,102 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Ocsp; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class CertificateStatus + { + protected readonly byte mStatusType; + protected readonly object mResponse; + + public CertificateStatus(byte statusType, object response) + { + if (!IsCorrectType(statusType, response)) + throw new ArgumentException("not an instance of the correct type", "response"); + + this.mStatusType = statusType; + this.mResponse = response; + } + + public virtual byte StatusType + { + get { return mStatusType; } + } + + public virtual object Response + { + get { return mResponse; } + } + + public virtual OcspResponse GetOcspResponse() + { + if (!IsCorrectType(CertificateStatusType.ocsp, mResponse)) + throw new InvalidOperationException("'response' is not an OcspResponse"); + + return (OcspResponse)mResponse; + } + + /** + * Encode this {@link CertificateStatus} to a {@link Stream}. + * + * @param output + * the {@link Stream} to encode to. + * @throws IOException + */ + public virtual void Encode(Stream output) + { + TlsUtilities.WriteUint8(mStatusType, output); + + switch (mStatusType) + { + case CertificateStatusType.ocsp: + byte[] derEncoding = ((OcspResponse)mResponse).GetEncoded(Asn1Encodable.Der); + TlsUtilities.WriteOpaque24(derEncoding, output); + break; + default: + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + /** + * Parse a {@link CertificateStatus} from a {@link Stream}. + * + * @param input + * the {@link Stream} to parse from. + * @return a {@link CertificateStatus} object. + * @throws IOException + */ + public static CertificateStatus Parse(Stream input) + { + byte status_type = TlsUtilities.ReadUint8(input); + object response; + + switch (status_type) + { + case CertificateStatusType.ocsp: + { + byte[] derEncoding = TlsUtilities.ReadOpaque24(input); + response = OcspResponse.GetInstance(TlsUtilities.ReadDerObject(derEncoding)); + break; + } + default: + throw new TlsFatalAlert(AlertDescription.decode_error); + } + + return new CertificateStatus(status_type, response); + } + + protected static bool IsCorrectType(byte statusType, object response) + { + switch (statusType) + { + case CertificateStatusType.ocsp: + return response is OcspResponse; + default: + throw new ArgumentException("unsupported value", "statusType"); + } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/CertificateStatusRequest.cs b/bc-sharp-crypto/src/crypto/tls/CertificateStatusRequest.cs new file mode 100644 index 0000000..9587d7d --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/CertificateStatusRequest.cs @@ -0,0 +1,95 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class CertificateStatusRequest + { + protected readonly byte mStatusType; + protected readonly object mRequest; + + public CertificateStatusRequest(byte statusType, Object request) + { + if (!IsCorrectType(statusType, request)) + throw new ArgumentException("not an instance of the correct type", "request"); + + this.mStatusType = statusType; + this.mRequest = request; + } + + public virtual byte StatusType + { + get { return mStatusType; } + } + + public virtual object Request + { + get { return mRequest; } + } + + public virtual OcspStatusRequest GetOcspStatusRequest() + { + if (!IsCorrectType(CertificateStatusType.ocsp, mRequest)) + throw new InvalidOperationException("'request' is not an OCSPStatusRequest"); + + return (OcspStatusRequest)mRequest; + } + + /** + * Encode this {@link CertificateStatusRequest} to a {@link Stream}. + * + * @param output + * the {@link Stream} to encode to. + * @throws IOException + */ + public virtual void Encode(Stream output) + { + TlsUtilities.WriteUint8(mStatusType, output); + + switch (mStatusType) + { + case CertificateStatusType.ocsp: + ((OcspStatusRequest)mRequest).Encode(output); + break; + default: + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + /** + * Parse a {@link CertificateStatusRequest} from a {@link Stream}. + * + * @param input + * the {@link Stream} to parse from. + * @return a {@link CertificateStatusRequest} object. + * @throws IOException + */ + public static CertificateStatusRequest Parse(Stream input) + { + byte status_type = TlsUtilities.ReadUint8(input); + object result; + + switch (status_type) + { + case CertificateStatusType.ocsp: + result = OcspStatusRequest.Parse(input); + break; + default: + throw new TlsFatalAlert(AlertDescription.decode_error); + } + + return new CertificateStatusRequest(status_type, result); + } + + protected static bool IsCorrectType(byte statusType, object request) + { + switch (statusType) + { + case CertificateStatusType.ocsp: + return request is OcspStatusRequest; + default: + throw new ArgumentException("unsupported value", "statusType"); + } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/CertificateStatusType.cs b/bc-sharp-crypto/src/crypto/tls/CertificateStatusType.cs new file mode 100644 index 0000000..54b741b --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/CertificateStatusType.cs @@ -0,0 +1,12 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public abstract class CertificateStatusType + { + /* + * RFC 3546 3.6 + */ + public const byte ocsp = 1; + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/CertificateType.cs b/bc-sharp-crypto/src/crypto/tls/CertificateType.cs new file mode 100644 index 0000000..47ec05c --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/CertificateType.cs @@ -0,0 +1,18 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /** + * RFC 6091 + */ + public class CertificateType + { + public const byte X509 = 0; + public const byte OpenPGP = 1; + + /* + * RFC 7250 + */ + public const byte RawPublicKey = 2; + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/CertificateUrl.cs b/bc-sharp-crypto/src/crypto/tls/CertificateUrl.cs new file mode 100644 index 0000000..aff9995 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/CertificateUrl.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /* + * RFC 3546 3.3 + */ + public class CertificateUrl + { + protected readonly byte mType; + protected readonly IList mUrlAndHashList; + + /** + * @param type + * see {@link CertChainType} for valid constants. + * @param urlAndHashList + * a {@link IList} of {@link UrlAndHash}. + */ + public CertificateUrl(byte type, IList urlAndHashList) + { + if (!CertChainType.IsValid(type)) + throw new ArgumentException("not a valid CertChainType value", "type"); + if (urlAndHashList == null || urlAndHashList.Count < 1) + throw new ArgumentException("must have length > 0", "urlAndHashList"); + + this.mType = type; + this.mUrlAndHashList = urlAndHashList; + } + + /** + * @return {@link CertChainType} + */ + public virtual byte Type + { + get { return mType; } + } + + /** + * @return an {@link IList} of {@link UrlAndHash} + */ + public virtual IList UrlAndHashList + { + get { return mUrlAndHashList; } + } + + /** + * Encode this {@link CertificateUrl} to a {@link Stream}. + * + * @param output the {@link Stream} to encode to. + * @throws IOException + */ + public virtual void Encode(Stream output) + { + TlsUtilities.WriteUint8(this.mType, output); + + ListBuffer16 buf = new ListBuffer16(); + foreach (UrlAndHash urlAndHash in this.mUrlAndHashList) + { + urlAndHash.Encode(buf); + } + buf.EncodeTo(output); + } + + /** + * Parse a {@link CertificateUrl} from a {@link Stream}. + * + * @param context + * the {@link TlsContext} of the current connection. + * @param input + * the {@link Stream} to parse from. + * @return a {@link CertificateUrl} object. + * @throws IOException + */ + public static CertificateUrl parse(TlsContext context, Stream input) + { + byte type = TlsUtilities.ReadUint8(input); + if (!CertChainType.IsValid(type)) + throw new TlsFatalAlert(AlertDescription.decode_error); + + int totalLength = TlsUtilities.ReadUint16(input); + if (totalLength < 1) + throw new TlsFatalAlert(AlertDescription.decode_error); + + byte[] urlAndHashListData = TlsUtilities.ReadFully(totalLength, input); + + MemoryStream buf = new MemoryStream(urlAndHashListData, false); + + IList url_and_hash_list = Platform.CreateArrayList(); + while (buf.Position < buf.Length) + { + UrlAndHash url_and_hash = UrlAndHash.Parse(context, buf); + url_and_hash_list.Add(url_and_hash); + } + + return new CertificateUrl(type, url_and_hash_list); + } + + // TODO Could be more generally useful + internal class ListBuffer16 + : MemoryStream + { + internal ListBuffer16() + { + // Reserve space for length + TlsUtilities.WriteUint16(0, this); + } + + internal void EncodeTo(Stream output) + { + // Patch actual length back in + long length = Length - 2; + TlsUtilities.CheckUint16(length); + this.Position = 0; + TlsUtilities.WriteUint16((int)length, this); + Streams.WriteBufTo(this, output); + Platform.Dispose(this); + } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/Chacha20Poly1305.cs b/bc-sharp-crypto/src/crypto/tls/Chacha20Poly1305.cs new file mode 100644 index 0000000..8687803 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/Chacha20Poly1305.cs @@ -0,0 +1,199 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Macs; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Utilities; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /** + * draft-ietf-tls-chacha20-poly1305-04 + */ + public class Chacha20Poly1305 + : TlsCipher + { + private static readonly byte[] Zeroes = new byte[15]; + + protected readonly TlsContext context; + + protected readonly ChaCha7539Engine encryptCipher, decryptCipher; + protected readonly byte[] encryptIV, decryptIV; + + /// + public Chacha20Poly1305(TlsContext context) + { + if (!TlsUtilities.IsTlsV12(context)) + throw new TlsFatalAlert(AlertDescription.internal_error); + + this.context = context; + + int cipherKeySize = 32; + // TODO SecurityParameters.fixed_iv_length + int fixed_iv_length = 12; + // TODO SecurityParameters.record_iv_length = 0 + + int key_block_size = (2 * cipherKeySize) + (2 * fixed_iv_length); + + byte[] key_block = TlsUtilities.CalculateKeyBlock(context, key_block_size); + + int offset = 0; + + KeyParameter client_write_key = new KeyParameter(key_block, offset, cipherKeySize); + offset += cipherKeySize; + KeyParameter server_write_key = new KeyParameter(key_block, offset, cipherKeySize); + offset += cipherKeySize; + byte[] client_write_IV = Arrays.CopyOfRange(key_block, offset, offset + fixed_iv_length); + offset += fixed_iv_length; + byte[] server_write_IV = Arrays.CopyOfRange(key_block, offset, offset + fixed_iv_length); + offset += fixed_iv_length; + + if (offset != key_block_size) + throw new TlsFatalAlert(AlertDescription.internal_error); + + this.encryptCipher = new ChaCha7539Engine(); + this.decryptCipher = new ChaCha7539Engine(); + + KeyParameter encryptKey, decryptKey; + if (context.IsServer) + { + encryptKey = server_write_key; + decryptKey = client_write_key; + this.encryptIV = server_write_IV; + this.decryptIV = client_write_IV; + } + else + { + encryptKey = client_write_key; + decryptKey = server_write_key; + this.encryptIV = client_write_IV; + this.decryptIV = server_write_IV; + } + + this.encryptCipher.Init(true, new ParametersWithIV(encryptKey, encryptIV)); + this.decryptCipher.Init(false, new ParametersWithIV(decryptKey, decryptIV)); + } + + public virtual int GetPlaintextLimit(int ciphertextLimit) + { + return ciphertextLimit - 16; + } + + /// + public virtual byte[] EncodePlaintext(long seqNo, byte type, byte[] plaintext, int offset, int len) + { + KeyParameter macKey = InitRecord(encryptCipher, true, seqNo, encryptIV); + + byte[] output = new byte[len + 16]; + encryptCipher.ProcessBytes(plaintext, offset, len, output, 0); + + byte[] additionalData = GetAdditionalData(seqNo, type, len); + byte[] mac = CalculateRecordMac(macKey, additionalData, output, 0, len); + Array.Copy(mac, 0, output, len, mac.Length); + + return output; + } + + /// + public virtual byte[] DecodeCiphertext(long seqNo, byte type, byte[] ciphertext, int offset, int len) + { + if (GetPlaintextLimit(len) < 0) + throw new TlsFatalAlert(AlertDescription.decode_error); + + KeyParameter macKey = InitRecord(decryptCipher, false, seqNo, decryptIV); + + int plaintextLength = len - 16; + + byte[] additionalData = GetAdditionalData(seqNo, type, plaintextLength); + byte[] calculatedMac = CalculateRecordMac(macKey, additionalData, ciphertext, offset, plaintextLength); + byte[] receivedMac = Arrays.CopyOfRange(ciphertext, offset + plaintextLength, offset + len); + + if (!Arrays.ConstantTimeAreEqual(calculatedMac, receivedMac)) + throw new TlsFatalAlert(AlertDescription.bad_record_mac); + + byte[] output = new byte[plaintextLength]; + decryptCipher.ProcessBytes(ciphertext, offset, plaintextLength, output, 0); + return output; + } + + protected virtual KeyParameter InitRecord(IStreamCipher cipher, bool forEncryption, long seqNo, byte[] iv) + { + byte[] nonce = CalculateNonce(seqNo, iv); + cipher.Init(forEncryption, new ParametersWithIV(null, nonce)); + return GenerateRecordMacKey(cipher); + } + + protected virtual byte[] CalculateNonce(long seqNo, byte[] iv) + { + byte[] nonce = new byte[12]; + TlsUtilities.WriteUint64(seqNo, nonce, 4); + + for (int i = 0; i < 12; ++i) + { + nonce[i] ^= iv[i]; + } + + return nonce; + } + + protected virtual KeyParameter GenerateRecordMacKey(IStreamCipher cipher) + { + byte[] firstBlock = new byte[64]; + cipher.ProcessBytes(firstBlock, 0, firstBlock.Length, firstBlock, 0); + + KeyParameter macKey = new KeyParameter(firstBlock, 0, 32); + Arrays.Fill(firstBlock, (byte)0); + return macKey; + } + + protected virtual byte[] CalculateRecordMac(KeyParameter macKey, byte[] additionalData, byte[] buf, int off, int len) + { + IMac mac = new Poly1305(); + mac.Init(macKey); + + UpdateRecordMacText(mac, additionalData, 0, additionalData.Length); + UpdateRecordMacText(mac, buf, off, len); + UpdateRecordMacLength(mac, additionalData.Length); + UpdateRecordMacLength(mac, len); + + return MacUtilities.DoFinal(mac); + } + + protected virtual void UpdateRecordMacLength(IMac mac, int len) + { + byte[] longLen = Pack.UInt64_To_LE((ulong)len); + mac.BlockUpdate(longLen, 0, longLen.Length); + } + + protected virtual void UpdateRecordMacText(IMac mac, byte[] buf, int off, int len) + { + mac.BlockUpdate(buf, off, len); + + int partial = len % 16; + if (partial != 0) + { + mac.BlockUpdate(Zeroes, 0, 16 - partial); + } + } + + /// + protected virtual byte[] GetAdditionalData(long seqNo, byte type, int len) + { + /* + * additional_data = seq_num + TLSCompressed.type + TLSCompressed.version + + * TLSCompressed.length + */ + byte[] additional_data = new byte[13]; + TlsUtilities.WriteUint64(seqNo, additional_data, 0); + TlsUtilities.WriteUint8(type, additional_data, 8); + TlsUtilities.WriteVersion(context.ServerVersion, additional_data, 9); + TlsUtilities.WriteUint16(len, additional_data, 11); + + return additional_data; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/ChangeCipherSpec.cs b/bc-sharp-crypto/src/crypto/tls/ChangeCipherSpec.cs new file mode 100644 index 0000000..323de91 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/ChangeCipherSpec.cs @@ -0,0 +1,9 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public abstract class ChangeCipherSpec + { + public const byte change_cipher_spec = 1; + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/CipherSuite.cs b/bc-sharp-crypto/src/crypto/tls/CipherSuite.cs new file mode 100644 index 0000000..679a8be --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/CipherSuite.cs @@ -0,0 +1,377 @@ +namespace Org.BouncyCastle.Crypto.Tls +{ + /// + /// RFC 2246 A.5 + /// + public abstract class CipherSuite + { + public const int TLS_NULL_WITH_NULL_NULL = 0x0000; + public const int TLS_RSA_WITH_NULL_MD5 = 0x0001; + public const int TLS_RSA_WITH_NULL_SHA = 0x0002; + public const int TLS_RSA_EXPORT_WITH_RC4_40_MD5 = 0x0003; + public const int TLS_RSA_WITH_RC4_128_MD5 = 0x0004; + public const int TLS_RSA_WITH_RC4_128_SHA = 0x0005; + public const int TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 = 0x0006; + public const int TLS_RSA_WITH_IDEA_CBC_SHA = 0x0007; + public const int TLS_RSA_EXPORT_WITH_DES40_CBC_SHA = 0x0008; + public const int TLS_RSA_WITH_DES_CBC_SHA = 0x0009; + public const int TLS_RSA_WITH_3DES_EDE_CBC_SHA = 0x000A; + public const int TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA = 0x000B; + public const int TLS_DH_DSS_WITH_DES_CBC_SHA = 0x000C; + public const int TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA = 0x000D; + public const int TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA = 0x000E; + public const int TLS_DH_RSA_WITH_DES_CBC_SHA = 0x000F; + public const int TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA = 0x0010; + public const int TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA = 0x0011; + public const int TLS_DHE_DSS_WITH_DES_CBC_SHA = 0x0012; + public const int TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA = 0x0013; + public const int TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA = 0x0014; + public const int TLS_DHE_RSA_WITH_DES_CBC_SHA = 0x0015; + public const int TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA = 0x0016; + public const int TLS_DH_anon_EXPORT_WITH_RC4_40_MD5 = 0x0017; + public const int TLS_DH_anon_WITH_RC4_128_MD5 = 0x0018; + public const int TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA = 0x0019; + public const int TLS_DH_anon_WITH_DES_CBC_SHA = 0x001A; + public const int TLS_DH_anon_WITH_3DES_EDE_CBC_SHA = 0x001B; + + /* + * Note: The cipher suite values { 0x00, 0x1C } and { 0x00, 0x1D } are reserved to avoid + * collision with Fortezza-based cipher suites in SSL 3. + */ + + /* + * RFC 3268 + */ + public const int TLS_RSA_WITH_AES_128_CBC_SHA = 0x002F; + public const int TLS_DH_DSS_WITH_AES_128_CBC_SHA = 0x0030; + public const int TLS_DH_RSA_WITH_AES_128_CBC_SHA = 0x0031; + public const int TLS_DHE_DSS_WITH_AES_128_CBC_SHA = 0x0032; + public const int TLS_DHE_RSA_WITH_AES_128_CBC_SHA = 0x0033; + public const int TLS_DH_anon_WITH_AES_128_CBC_SHA = 0x0034; + public const int TLS_RSA_WITH_AES_256_CBC_SHA = 0x0035; + public const int TLS_DH_DSS_WITH_AES_256_CBC_SHA = 0x0036; + public const int TLS_DH_RSA_WITH_AES_256_CBC_SHA = 0x0037; + public const int TLS_DHE_DSS_WITH_AES_256_CBC_SHA = 0x0038; + public const int TLS_DHE_RSA_WITH_AES_256_CBC_SHA = 0x0039; + public const int TLS_DH_anon_WITH_AES_256_CBC_SHA = 0x003A; + + /* + * RFC 5932 + */ + public const int TLS_RSA_WITH_CAMELLIA_128_CBC_SHA = 0x0041; + public const int TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA = 0x0042; + public const int TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA = 0x0043; + public const int TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA = 0x0044; + public const int TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA = 0x0045; + public const int TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA = 0x0046; + + public const int TLS_RSA_WITH_CAMELLIA_256_CBC_SHA = 0x0084; + public const int TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA = 0x0085; + public const int TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA = 0x0086; + public const int TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA = 0x0087; + public const int TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA = 0x0088; + public const int TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA = 0x0089; + + public const int TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256 = 0x00BA; + public const int TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256 = 0x00BB; + public const int TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256 = 0x00BC; + public const int TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256 = 0x00BD; + public const int TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 = 0x00BE; + public const int TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256 = 0x00BF; + + public const int TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256 = 0x00C0; + public const int TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256 = 0x00C1; + public const int TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256 = 0x00C2; + public const int TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256 = 0x00C3; + public const int TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256 = 0x00C4; + public const int TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256 = 0x00C5; + + /* + * RFC 4162 + */ + public const int TLS_RSA_WITH_SEED_CBC_SHA = 0x0096; + public const int TLS_DH_DSS_WITH_SEED_CBC_SHA = 0x0097; + public const int TLS_DH_RSA_WITH_SEED_CBC_SHA = 0x0098; + public const int TLS_DHE_DSS_WITH_SEED_CBC_SHA = 0x0099; + public const int TLS_DHE_RSA_WITH_SEED_CBC_SHA = 0x009A; + public const int TLS_DH_anon_WITH_SEED_CBC_SHA = 0x009B; + + /* + * RFC 4279 + */ + public const int TLS_PSK_WITH_RC4_128_SHA = 0x008A; + public const int TLS_PSK_WITH_3DES_EDE_CBC_SHA = 0x008B; + public const int TLS_PSK_WITH_AES_128_CBC_SHA = 0x008C; + public const int TLS_PSK_WITH_AES_256_CBC_SHA = 0x008D; + public const int TLS_DHE_PSK_WITH_RC4_128_SHA = 0x008E; + public const int TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA = 0x008F; + public const int TLS_DHE_PSK_WITH_AES_128_CBC_SHA = 0x0090; + public const int TLS_DHE_PSK_WITH_AES_256_CBC_SHA = 0x0091; + public const int TLS_RSA_PSK_WITH_RC4_128_SHA = 0x0092; + public const int TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA = 0x0093; + public const int TLS_RSA_PSK_WITH_AES_128_CBC_SHA = 0x0094; + public const int TLS_RSA_PSK_WITH_AES_256_CBC_SHA = 0x0095; + + /* + * RFC 4492 + */ + public const int TLS_ECDH_ECDSA_WITH_NULL_SHA = 0xC001; + public const int TLS_ECDH_ECDSA_WITH_RC4_128_SHA = 0xC002; + public const int TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA = 0xC003; + public const int TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA = 0xC004; + public const int TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA = 0xC005; + public const int TLS_ECDHE_ECDSA_WITH_NULL_SHA = 0xC006; + public const int TLS_ECDHE_ECDSA_WITH_RC4_128_SHA = 0xC007; + public const int TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA = 0xC008; + public const int TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA = 0xC009; + public const int TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA = 0xC00A; + public const int TLS_ECDH_RSA_WITH_NULL_SHA = 0xC00B; + public const int TLS_ECDH_RSA_WITH_RC4_128_SHA = 0xC00C; + public const int TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA = 0xC00D; + public const int TLS_ECDH_RSA_WITH_AES_128_CBC_SHA = 0xC00E; + public const int TLS_ECDH_RSA_WITH_AES_256_CBC_SHA = 0xC00F; + public const int TLS_ECDHE_RSA_WITH_NULL_SHA = 0xC010; + public const int TLS_ECDHE_RSA_WITH_RC4_128_SHA = 0xC011; + public const int TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA = 0xC012; + public const int TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA = 0xC013; + public const int TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA = 0xC014; + public const int TLS_ECDH_anon_WITH_NULL_SHA = 0xC015; + public const int TLS_ECDH_anon_WITH_RC4_128_SHA = 0xC016; + public const int TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA = 0xC017; + public const int TLS_ECDH_anon_WITH_AES_128_CBC_SHA = 0xC018; + public const int TLS_ECDH_anon_WITH_AES_256_CBC_SHA = 0xC019; + + /* + * RFC 4785 + */ + public const int TLS_PSK_WITH_NULL_SHA = 0x002C; + public const int TLS_DHE_PSK_WITH_NULL_SHA = 0x002D; + public const int TLS_RSA_PSK_WITH_NULL_SHA = 0x002E; + + /* + * RFC 5054 + */ + public const int TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA = 0xC01A; + public const int TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA = 0xC01B; + public const int TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA = 0xC01C; + public const int TLS_SRP_SHA_WITH_AES_128_CBC_SHA = 0xC01D; + public const int TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA = 0xC01E; + public const int TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA = 0xC01F; + public const int TLS_SRP_SHA_WITH_AES_256_CBC_SHA = 0xC020; + public const int TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA = 0xC021; + public const int TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA = 0xC022; + + /* + * RFC 5246 + */ + public const int TLS_RSA_WITH_NULL_SHA256 = 0x003B; + public const int TLS_RSA_WITH_AES_128_CBC_SHA256 = 0x003C; + public const int TLS_RSA_WITH_AES_256_CBC_SHA256 = 0x003D; + public const int TLS_DH_DSS_WITH_AES_128_CBC_SHA256 = 0x003E; + public const int TLS_DH_RSA_WITH_AES_128_CBC_SHA256 = 0x003F; + public const int TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 = 0x0040; + public const int TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 = 0x0067; + public const int TLS_DH_DSS_WITH_AES_256_CBC_SHA256 = 0x0068; + public const int TLS_DH_RSA_WITH_AES_256_CBC_SHA256 = 0x0069; + public const int TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 = 0x006A; + public const int TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 = 0x006B; + public const int TLS_DH_anon_WITH_AES_128_CBC_SHA256 = 0x006C; + public const int TLS_DH_anon_WITH_AES_256_CBC_SHA256 = 0x006D; + + /* + * RFC 5288 + */ + public const int TLS_RSA_WITH_AES_128_GCM_SHA256 = 0x009C; + public const int TLS_RSA_WITH_AES_256_GCM_SHA384 = 0x009D; + public const int TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 = 0x009E; + public const int TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 = 0x009F; + public const int TLS_DH_RSA_WITH_AES_128_GCM_SHA256 = 0x00A0; + public const int TLS_DH_RSA_WITH_AES_256_GCM_SHA384 = 0x00A1; + public const int TLS_DHE_DSS_WITH_AES_128_GCM_SHA256 = 0x00A2; + public const int TLS_DHE_DSS_WITH_AES_256_GCM_SHA384 = 0x00A3; + public const int TLS_DH_DSS_WITH_AES_128_GCM_SHA256 = 0x00A4; + public const int TLS_DH_DSS_WITH_AES_256_GCM_SHA384 = 0x00A5; + public const int TLS_DH_anon_WITH_AES_128_GCM_SHA256 = 0x00A6; + public const int TLS_DH_anon_WITH_AES_256_GCM_SHA384 = 0x00A7; + + /* + * RFC 5289 + */ + public const int TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 = 0xC023; + public const int TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 = 0xC024; + public const int TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256 = 0xC025; + public const int TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384 = 0xC026; + public const int TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 = 0xC027; + public const int TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 = 0xC028; + public const int TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256 = 0xC029; + public const int TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384 = 0xC02A; + public const int TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 = 0xC02B; + public const int TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 = 0xC02C; + public const int TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256 = 0xC02D; + public const int TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384 = 0xC02E; + public const int TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 = 0xC02F; + public const int TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 = 0xC030; + public const int TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256 = 0xC031; + public const int TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384 = 0xC032; + + /* + * RFC 5487 + */ + public const int TLS_PSK_WITH_AES_128_GCM_SHA256 = 0x00A8; + public const int TLS_PSK_WITH_AES_256_GCM_SHA384 = 0x00A9; + public const int TLS_DHE_PSK_WITH_AES_128_GCM_SHA256 = 0x00AA; + public const int TLS_DHE_PSK_WITH_AES_256_GCM_SHA384 = 0x00AB; + public const int TLS_RSA_PSK_WITH_AES_128_GCM_SHA256 = 0x00AC; + public const int TLS_RSA_PSK_WITH_AES_256_GCM_SHA384 = 0x00AD; + public const int TLS_PSK_WITH_AES_128_CBC_SHA256 = 0x00AE; + public const int TLS_PSK_WITH_AES_256_CBC_SHA384 = 0x00AF; + public const int TLS_PSK_WITH_NULL_SHA256 = 0x00B0; + public const int TLS_PSK_WITH_NULL_SHA384 = 0x00B1; + public const int TLS_DHE_PSK_WITH_AES_128_CBC_SHA256 = 0x00B2; + public const int TLS_DHE_PSK_WITH_AES_256_CBC_SHA384 = 0x00B3; + public const int TLS_DHE_PSK_WITH_NULL_SHA256 = 0x00B4; + public const int TLS_DHE_PSK_WITH_NULL_SHA384 = 0x00B5; + public const int TLS_RSA_PSK_WITH_AES_128_CBC_SHA256 = 0x00B6; + public const int TLS_RSA_PSK_WITH_AES_256_CBC_SHA384 = 0x00B7; + public const int TLS_RSA_PSK_WITH_NULL_SHA256 = 0x00B8; + public const int TLS_RSA_PSK_WITH_NULL_SHA384 = 0x00B9; + + /* + * RFC 5489 + */ + public const int TLS_ECDHE_PSK_WITH_RC4_128_SHA = 0xC033; + public const int TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA = 0xC034; + public const int TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA = 0xC035; + public const int TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA = 0xC036; + public const int TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256 = 0xC037; + public const int TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384 = 0xC038; + public const int TLS_ECDHE_PSK_WITH_NULL_SHA = 0xC039; + public const int TLS_ECDHE_PSK_WITH_NULL_SHA256 = 0xC03A; + public const int TLS_ECDHE_PSK_WITH_NULL_SHA384 = 0xC03B; + + /* + * RFC 5746 + */ + public const int TLS_EMPTY_RENEGOTIATION_INFO_SCSV = 0x00FF; + + /* + * RFC 6367 + */ + public const int TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 = 0xC072; + public const int TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 = 0xC073; + public const int TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 = 0xC074; + public const int TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 = 0xC075; + public const int TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 = 0xC076; + public const int TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384 = 0xC077; + public const int TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256 = 0xC078; + public const int TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384 = 0xC079; + + public const int TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256 = 0xC07A; + public const int TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384 = 0xC07B; + public const int TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256 = 0xC07C; + public const int TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384 = 0xC07D; + public const int TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256 = 0xC07E; + public const int TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384 = 0xC07F; + public const int TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256 = 0xC080; + public const int TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384 = 0xC081; + public const int TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256 = 0xC082; + public const int TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384 = 0xC083; + public const int TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256 = 0xC084; + public const int TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384 = 0xC085; + public const int TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256 = 0xC086; + public const int TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384 = 0xC087; + public const int TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256 = 0xC088; + public const int TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384 = 0xC089; + public const int TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256 = 0xC08A; + public const int TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384 = 0xC08B; + public const int TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256 = 0xC08C; + public const int TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384 = 0xC08D; + + public const int TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256 = 0xC08E; + public const int TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384 = 0xC08F; + public const int TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256 = 0xC090; + public const int TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384 = 0xC091; + public const int TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256 = 0xC092; + public const int TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384 = 0xC093; + public const int TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256 = 0xC094; + public const int TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384 = 0xC095; + public const int TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 = 0xC096; + public const int TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 = 0xC097; + public const int TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256 = 0xC098; + public const int TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384 = 0xC099; + public const int TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 = 0xC09A; + public const int TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 = 0xC09B; + + /* + * RFC 6655 + */ + public const int TLS_RSA_WITH_AES_128_CCM = 0xC09C; + public const int TLS_RSA_WITH_AES_256_CCM = 0xC09D; + public const int TLS_DHE_RSA_WITH_AES_128_CCM = 0xC09E; + public const int TLS_DHE_RSA_WITH_AES_256_CCM = 0xC09F; + public const int TLS_RSA_WITH_AES_128_CCM_8 = 0xC0A0; + public const int TLS_RSA_WITH_AES_256_CCM_8 = 0xC0A1; + public const int TLS_DHE_RSA_WITH_AES_128_CCM_8 = 0xC0A2; + public const int TLS_DHE_RSA_WITH_AES_256_CCM_8 = 0xC0A3; + public const int TLS_PSK_WITH_AES_128_CCM = 0xC0A4; + public const int TLS_PSK_WITH_AES_256_CCM = 0xC0A5; + public const int TLS_DHE_PSK_WITH_AES_128_CCM = 0xC0A6; + public const int TLS_DHE_PSK_WITH_AES_256_CCM = 0xC0A7; + public const int TLS_PSK_WITH_AES_128_CCM_8 = 0xC0A8; + public const int TLS_PSK_WITH_AES_256_CCM_8 = 0xC0A9; + public const int TLS_PSK_DHE_WITH_AES_128_CCM_8 = 0xC0AA; + public const int TLS_PSK_DHE_WITH_AES_256_CCM_8 = 0xC0AB; + + /* + * RFC 7251 + */ + public const int TLS_ECDHE_ECDSA_WITH_AES_128_CCM = 0xC0AC; + public const int TLS_ECDHE_ECDSA_WITH_AES_256_CCM = 0xC0AD; + public const int TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8 = 0xC0AE; + public const int TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8 = 0xC0AF; + + /* + * RFC 7507 + */ + public const int TLS_FALLBACK_SCSV = 0x5600; + + /* + * draft-ietf-tls-chacha20-poly1305-04 + */ + public const int DRAFT_TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = 0xCCA8; + public const int DRAFT_TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 = 0xCCA9; + public const int DRAFT_TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = 0xCCAA; + public const int DRAFT_TLS_PSK_WITH_CHACHA20_POLY1305_SHA256 = 0xCCAB; + public const int DRAFT_TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256 = 0xCCAC; + public const int DRAFT_TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256 = 0xCCAD; + public const int DRAFT_TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256 = 0xCCAE; + + /* + * draft-zauner-tls-aes-ocb-04 (code points TBD) + */ + public const int DRAFT_TLS_DHE_RSA_WITH_AES_128_OCB = 0xFF00; + public const int DRAFT_TLS_DHE_RSA_WITH_AES_256_OCB = 0xFF01; + public const int DRAFT_TLS_ECDHE_RSA_WITH_AES_128_OCB = 0xFF02; + public const int DRAFT_TLS_ECDHE_RSA_WITH_AES_256_OCB = 0xFF03; + public const int DRAFT_TLS_ECDHE_ECDSA_WITH_AES_128_OCB = 0xFF04; + public const int DRAFT_TLS_ECDHE_ECDSA_WITH_AES_256_OCB = 0xFF05; + public const int DRAFT_TLS_PSK_WITH_AES_128_OCB = 0xFF10; + public const int DRAFT_TLS_PSK_WITH_AES_256_OCB = 0xFF11; + public const int DRAFT_TLS_DHE_PSK_WITH_AES_128_OCB = 0xFF12; + public const int DRAFT_TLS_DHE_PSK_WITH_AES_256_OCB = 0xFF13; + public const int DRAFT_TLS_ECDHE_PSK_WITH_AES_128_OCB = 0xFF14; + public const int DRAFT_TLS_ECDHE_PSK_WITH_AES_256_OCB = 0xFF15; + + public static bool IsScsv(int cipherSuite) + { + switch (cipherSuite) + { + case TLS_EMPTY_RENEGOTIATION_INFO_SCSV: + case TLS_FALLBACK_SCSV: + return true; + default: + return false; + } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/CipherType.cs b/bc-sharp-crypto/src/crypto/tls/CipherType.cs new file mode 100644 index 0000000..b2ad7d8 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/CipherType.cs @@ -0,0 +1,20 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /// RFC 2246 + /// + /// Note that the values here are implementation-specific and arbitrary. It is recommended not to + /// depend on the particular values (e.g. serialization). + /// + public abstract class CipherType + { + public const int stream = 0; + public const int block = 1; + + /* + * RFC 5246 + */ + public const int aead = 2; + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/ClientAuthenticationType.cs b/bc-sharp-crypto/src/crypto/tls/ClientAuthenticationType.cs new file mode 100644 index 0000000..dd248f3 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/ClientAuthenticationType.cs @@ -0,0 +1,14 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public abstract class ClientAuthenticationType + { + /* + * RFC 5077 4 + */ + public const byte anonymous = 0; + public const byte certificate_based = 1; + public const byte psk = 2; + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/ClientCertificateType.cs b/bc-sharp-crypto/src/crypto/tls/ClientCertificateType.cs new file mode 100644 index 0000000..a291a46 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/ClientCertificateType.cs @@ -0,0 +1,23 @@ +namespace Org.BouncyCastle.Crypto.Tls +{ + public abstract class ClientCertificateType + { + /* + * RFC 4346 7.4.4 + */ + public const byte rsa_sign = 1; + public const byte dss_sign = 2; + public const byte rsa_fixed_dh = 3; + public const byte dss_fixed_dh = 4; + public const byte rsa_ephemeral_dh_RESERVED = 5; + public const byte dss_ephemeral_dh_RESERVED = 6; + public const byte fortezza_dms_RESERVED = 20; + + /* + * RFC 4492 5.5 + */ + public const byte ecdsa_sign = 64; + public const byte rsa_fixed_ecdh = 65; + public const byte ecdsa_fixed_ecdh = 66; + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/CombinedHash.cs b/bc-sharp-crypto/src/crypto/tls/CombinedHash.cs new file mode 100644 index 0000000..74a52d5 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/CombinedHash.cs @@ -0,0 +1,133 @@ +using System; + +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /** + * A combined hash, which implements md5(m) || sha1(m). + */ + internal class CombinedHash + : TlsHandshakeHash + { + protected TlsContext mContext; + protected IDigest mMd5; + protected IDigest mSha1; + + internal CombinedHash() + { + this.mMd5 = TlsUtilities.CreateHash(HashAlgorithm.md5); + this.mSha1 = TlsUtilities.CreateHash(HashAlgorithm.sha1); + } + + internal CombinedHash(CombinedHash t) + { + this.mContext = t.mContext; + this.mMd5 = TlsUtilities.CloneHash(HashAlgorithm.md5, t.mMd5); + this.mSha1 = TlsUtilities.CloneHash(HashAlgorithm.sha1, t.mSha1); + } + + public virtual void Init(TlsContext context) + { + this.mContext = context; + } + + public virtual TlsHandshakeHash NotifyPrfDetermined() + { + return this; + } + + public virtual void TrackHashAlgorithm(byte hashAlgorithm) + { + throw new InvalidOperationException("CombinedHash only supports calculating the legacy PRF for handshake hash"); + } + + public virtual void SealHashAlgorithms() + { + } + + public virtual TlsHandshakeHash StopTracking() + { + return new CombinedHash(this); + } + + public virtual IDigest ForkPrfHash() + { + return new CombinedHash(this); + } + + public virtual byte[] GetFinalHash(byte hashAlgorithm) + { + throw new InvalidOperationException("CombinedHash doesn't support multiple hashes"); + } + + public virtual string AlgorithmName + { + get { return mMd5.AlgorithmName + " and " + mSha1.AlgorithmName; } + } + + public virtual int GetByteLength() + { + return System.Math.Max(mMd5.GetByteLength(), mSha1.GetByteLength()); + } + + public virtual int GetDigestSize() + { + return mMd5.GetDigestSize() + mSha1.GetDigestSize(); + } + + public virtual void Update(byte input) + { + mMd5.Update(input); + mSha1.Update(input); + } + + /** + * @see org.bouncycastle.crypto.Digest#update(byte[], int, int) + */ + public virtual void BlockUpdate(byte[] input, int inOff, int len) + { + mMd5.BlockUpdate(input, inOff, len); + mSha1.BlockUpdate(input, inOff, len); + } + + /** + * @see org.bouncycastle.crypto.Digest#doFinal(byte[], int) + */ + public virtual int DoFinal(byte[] output, int outOff) + { + if (mContext != null && TlsUtilities.IsSsl(mContext)) + { + Ssl3Complete(mMd5, Ssl3Mac.IPAD, Ssl3Mac.OPAD, 48); + Ssl3Complete(mSha1, Ssl3Mac.IPAD, Ssl3Mac.OPAD, 40); + } + + int i1 = mMd5.DoFinal(output, outOff); + int i2 = mSha1.DoFinal(output, outOff + i1); + return i1 + i2; + } + + /** + * @see org.bouncycastle.crypto.Digest#reset() + */ + public virtual void Reset() + { + mMd5.Reset(); + mSha1.Reset(); + } + + protected virtual void Ssl3Complete(IDigest d, byte[] ipad, byte[] opad, int padLength) + { + byte[] master_secret = mContext.SecurityParameters.masterSecret; + + d.BlockUpdate(master_secret, 0, master_secret.Length); + d.BlockUpdate(ipad, 0, padLength); + + byte[] tmp = DigestUtilities.DoFinal(d); + + d.BlockUpdate(master_secret, 0, master_secret.Length); + d.BlockUpdate(opad, 0, padLength); + d.BlockUpdate(tmp, 0, tmp.Length); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/CompressionMethod.cs b/bc-sharp-crypto/src/crypto/tls/CompressionMethod.cs new file mode 100644 index 0000000..89c1f5f --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/CompressionMethod.cs @@ -0,0 +1,22 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /// + /// RFC 2246 6.1 + /// + public abstract class CompressionMethod + { + public const byte cls_null = 0; + + /* + * RFC 3749 2 + */ + public const byte DEFLATE = 1; + + /* + * Values from 224 decimal (0xE0) through 255 decimal (0xFF) + * inclusive are reserved for private use. + */ + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/ConnectionEnd.cs b/bc-sharp-crypto/src/crypto/tls/ConnectionEnd.cs new file mode 100644 index 0000000..afc9460 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/ConnectionEnd.cs @@ -0,0 +1,15 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /// RFC 2246 + /// + /// Note that the values here are implementation-specific and arbitrary. It is recommended not to + /// depend on the particular values (e.g. serialization). + /// + public abstract class ConnectionEnd + { + public const int server = 0; + public const int client = 1; + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/ContentType.cs b/bc-sharp-crypto/src/crypto/tls/ContentType.cs new file mode 100644 index 0000000..d6ab438 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/ContentType.cs @@ -0,0 +1,14 @@ +namespace Org.BouncyCastle.Crypto.Tls +{ + /** + * RFC 2246 6.2.1 + */ + public abstract class ContentType + { + public const byte change_cipher_spec = 20; + public const byte alert = 21; + public const byte handshake = 22; + public const byte application_data = 23; + public const byte heartbeat = 24; + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/DatagramTransport.cs b/bc-sharp-crypto/src/crypto/tls/DatagramTransport.cs new file mode 100644 index 0000000..524a8b1 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/DatagramTransport.cs @@ -0,0 +1,23 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public interface DatagramTransport + { + /// + int GetReceiveLimit(); + + /// + int GetSendLimit(); + + /// + int Receive(byte[] buf, int off, int len, int waitMillis); + + /// + void Send(byte[] buf, int off, int len); + + /// + void Close(); + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/DefaultTlsAgreementCredentials.cs b/bc-sharp-crypto/src/crypto/tls/DefaultTlsAgreementCredentials.cs new file mode 100644 index 0000000..fab9788 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/DefaultTlsAgreementCredentials.cs @@ -0,0 +1,69 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Crypto.Agreement; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class DefaultTlsAgreementCredentials + : AbstractTlsAgreementCredentials + { + protected readonly Certificate mCertificate; + protected readonly AsymmetricKeyParameter mPrivateKey; + + protected readonly IBasicAgreement mBasicAgreement; + protected readonly bool mTruncateAgreement; + + public DefaultTlsAgreementCredentials(Certificate certificate, AsymmetricKeyParameter privateKey) + { + if (certificate == null) + throw new ArgumentNullException("certificate"); + if (certificate.IsEmpty) + throw new ArgumentException("cannot be empty", "certificate"); + if (privateKey == null) + throw new ArgumentNullException("privateKey"); + if (!privateKey.IsPrivate) + throw new ArgumentException("must be private", "privateKey"); + + if (privateKey is DHPrivateKeyParameters) + { + mBasicAgreement = new DHBasicAgreement(); + mTruncateAgreement = true; + } + else if (privateKey is ECPrivateKeyParameters) + { + mBasicAgreement = new ECDHBasicAgreement(); + mTruncateAgreement = false; + } + else + { + throw new ArgumentException("type not supported: " + Platform.GetTypeName(privateKey), "privateKey"); + } + + this.mCertificate = certificate; + this.mPrivateKey = privateKey; + } + + public override Certificate Certificate + { + get { return mCertificate; } + } + + /// + public override byte[] GenerateAgreement(AsymmetricKeyParameter peerPublicKey) + { + mBasicAgreement.Init(mPrivateKey); + BigInteger agreementValue = mBasicAgreement.CalculateAgreement(peerPublicKey); + + if (mTruncateAgreement) + { + return BigIntegers.AsUnsignedByteArray(agreementValue); + } + + return BigIntegers.AsUnsignedByteArray(mBasicAgreement.GetFieldSize(), agreementValue); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/DefaultTlsCipherFactory.cs b/bc-sharp-crypto/src/crypto/tls/DefaultTlsCipherFactory.cs new file mode 100644 index 0000000..af0ec12 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/DefaultTlsCipherFactory.cs @@ -0,0 +1,227 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Modes; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class DefaultTlsCipherFactory + : AbstractTlsCipherFactory + { + /// + public override TlsCipher CreateCipher(TlsContext context, int encryptionAlgorithm, int macAlgorithm) + { + switch (encryptionAlgorithm) + { + case EncryptionAlgorithm.cls_3DES_EDE_CBC: + return CreateDesEdeCipher(context, macAlgorithm); + case EncryptionAlgorithm.AES_128_CBC: + return CreateAESCipher(context, 16, macAlgorithm); + case EncryptionAlgorithm.AES_128_CCM: + // NOTE: Ignores macAlgorithm + return CreateCipher_Aes_Ccm(context, 16, 16); + case EncryptionAlgorithm.AES_128_CCM_8: + // NOTE: Ignores macAlgorithm + return CreateCipher_Aes_Ccm(context, 16, 8); + case EncryptionAlgorithm.AES_128_GCM: + // NOTE: Ignores macAlgorithm + return CreateCipher_Aes_Gcm(context, 16, 16); + case EncryptionAlgorithm.AES_128_OCB_TAGLEN96: + // NOTE: Ignores macAlgorithm + return CreateCipher_Aes_Ocb(context, 16, 12); + case EncryptionAlgorithm.AES_256_CBC: + return CreateAESCipher(context, 32, macAlgorithm); + case EncryptionAlgorithm.AES_256_CCM: + // NOTE: Ignores macAlgorithm + return CreateCipher_Aes_Ccm(context, 32, 16); + case EncryptionAlgorithm.AES_256_CCM_8: + // NOTE: Ignores macAlgorithm + return CreateCipher_Aes_Ccm(context, 32, 8); + case EncryptionAlgorithm.AES_256_GCM: + // NOTE: Ignores macAlgorithm + return CreateCipher_Aes_Gcm(context, 32, 16); + case EncryptionAlgorithm.AES_256_OCB_TAGLEN96: + // NOTE: Ignores macAlgorithm + return CreateCipher_Aes_Ocb(context, 32, 12); + case EncryptionAlgorithm.CAMELLIA_128_CBC: + return CreateCamelliaCipher(context, 16, macAlgorithm); + case EncryptionAlgorithm.CAMELLIA_128_GCM: + // NOTE: Ignores macAlgorithm + return CreateCipher_Camellia_Gcm(context, 16, 16); + case EncryptionAlgorithm.CAMELLIA_256_CBC: + return CreateCamelliaCipher(context, 32, macAlgorithm); + case EncryptionAlgorithm.CAMELLIA_256_GCM: + // NOTE: Ignores macAlgorithm + return CreateCipher_Camellia_Gcm(context, 32, 16); + case EncryptionAlgorithm.CHACHA20_POLY1305: + // NOTE: Ignores macAlgorithm + return CreateChaCha20Poly1305(context); + case EncryptionAlgorithm.NULL: + return CreateNullCipher(context, macAlgorithm); + case EncryptionAlgorithm.RC4_128: + return CreateRC4Cipher(context, 16, macAlgorithm); + case EncryptionAlgorithm.SEED_CBC: + return CreateSeedCipher(context, macAlgorithm); + default: + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + /// + protected virtual TlsBlockCipher CreateAESCipher(TlsContext context, int cipherKeySize, int macAlgorithm) + { + return new TlsBlockCipher(context, CreateAesBlockCipher(), CreateAesBlockCipher(), + CreateHMacDigest(macAlgorithm), CreateHMacDigest(macAlgorithm), cipherKeySize); + } + + /// + protected virtual TlsBlockCipher CreateCamelliaCipher(TlsContext context, int cipherKeySize, int macAlgorithm) + { + return new TlsBlockCipher(context, CreateCamelliaBlockCipher(), + CreateCamelliaBlockCipher(), CreateHMacDigest(macAlgorithm), + CreateHMacDigest(macAlgorithm), cipherKeySize); + } + + /// + protected virtual TlsCipher CreateChaCha20Poly1305(TlsContext context) + { + return new Chacha20Poly1305(context); + } + + /// + protected virtual TlsAeadCipher CreateCipher_Aes_Ccm(TlsContext context, int cipherKeySize, int macSize) + { + return new TlsAeadCipher(context, CreateAeadBlockCipher_Aes_Ccm(), + CreateAeadBlockCipher_Aes_Ccm(), cipherKeySize, macSize); + } + + /// + protected virtual TlsAeadCipher CreateCipher_Aes_Gcm(TlsContext context, int cipherKeySize, int macSize) + { + return new TlsAeadCipher(context, CreateAeadBlockCipher_Aes_Gcm(), + CreateAeadBlockCipher_Aes_Gcm(), cipherKeySize, macSize); + } + + /// + protected virtual TlsAeadCipher CreateCipher_Aes_Ocb(TlsContext context, int cipherKeySize, int macSize) + { + return new TlsAeadCipher(context, CreateAeadBlockCipher_Aes_Ocb(), + CreateAeadBlockCipher_Aes_Ocb(), cipherKeySize, macSize, TlsAeadCipher.NONCE_DRAFT_CHACHA20_POLY1305); + } + + /// + protected virtual TlsAeadCipher CreateCipher_Camellia_Gcm(TlsContext context, int cipherKeySize, int macSize) + { + return new TlsAeadCipher(context, CreateAeadBlockCipher_Camellia_Gcm(), + CreateAeadBlockCipher_Camellia_Gcm(), cipherKeySize, macSize); + } + + /// + protected virtual TlsBlockCipher CreateDesEdeCipher(TlsContext context, int macAlgorithm) + { + return new TlsBlockCipher(context, CreateDesEdeBlockCipher(), CreateDesEdeBlockCipher(), + CreateHMacDigest(macAlgorithm), CreateHMacDigest(macAlgorithm), 24); + } + + /// + protected virtual TlsNullCipher CreateNullCipher(TlsContext context, int macAlgorithm) + { + return new TlsNullCipher(context, CreateHMacDigest(macAlgorithm), + CreateHMacDigest(macAlgorithm)); + } + + /// + protected virtual TlsStreamCipher CreateRC4Cipher(TlsContext context, int cipherKeySize, int macAlgorithm) + { + return new TlsStreamCipher(context, CreateRC4StreamCipher(), CreateRC4StreamCipher(), + CreateHMacDigest(macAlgorithm), CreateHMacDigest(macAlgorithm), cipherKeySize, false); + } + + /// + protected virtual TlsBlockCipher CreateSeedCipher(TlsContext context, int macAlgorithm) + { + return new TlsBlockCipher(context, CreateSeedBlockCipher(), CreateSeedBlockCipher(), + CreateHMacDigest(macAlgorithm), CreateHMacDigest(macAlgorithm), 16); + } + + protected virtual IBlockCipher CreateAesEngine() + { + return new AesEngine(); + } + + protected virtual IBlockCipher CreateCamelliaEngine() + { + return new CamelliaEngine(); + } + + protected virtual IBlockCipher CreateAesBlockCipher() + { + return new CbcBlockCipher(CreateAesEngine()); + } + + protected virtual IAeadBlockCipher CreateAeadBlockCipher_Aes_Ccm() + { + return new CcmBlockCipher(CreateAesEngine()); + } + + protected virtual IAeadBlockCipher CreateAeadBlockCipher_Aes_Gcm() + { + // TODO Consider allowing custom configuration of multiplier + return new GcmBlockCipher(CreateAesEngine()); + } + + protected virtual IAeadBlockCipher CreateAeadBlockCipher_Aes_Ocb() + { + return new OcbBlockCipher(CreateAesEngine(), CreateAesEngine()); + } + + protected virtual IAeadBlockCipher CreateAeadBlockCipher_Camellia_Gcm() + { + // TODO Consider allowing custom configuration of multiplier + return new GcmBlockCipher(CreateCamelliaEngine()); + } + + protected virtual IBlockCipher CreateCamelliaBlockCipher() + { + return new CbcBlockCipher(CreateCamelliaEngine()); + } + + protected virtual IBlockCipher CreateDesEdeBlockCipher() + { + return new CbcBlockCipher(new DesEdeEngine()); + } + + protected virtual IStreamCipher CreateRC4StreamCipher() + { + return new RC4Engine(); + } + + protected virtual IBlockCipher CreateSeedBlockCipher() + { + return new CbcBlockCipher(new SeedEngine()); + } + + /// + protected virtual IDigest CreateHMacDigest(int macAlgorithm) + { + switch (macAlgorithm) + { + case MacAlgorithm.cls_null: + return null; + case MacAlgorithm.hmac_md5: + return TlsUtilities.CreateHash(HashAlgorithm.md5); + case MacAlgorithm.hmac_sha1: + return TlsUtilities.CreateHash(HashAlgorithm.sha1); + case MacAlgorithm.hmac_sha256: + return TlsUtilities.CreateHash(HashAlgorithm.sha256); + case MacAlgorithm.hmac_sha384: + return TlsUtilities.CreateHash(HashAlgorithm.sha384); + case MacAlgorithm.hmac_sha512: + return TlsUtilities.CreateHash(HashAlgorithm.sha512); + default: + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/DefaultTlsClient.cs b/bc-sharp-crypto/src/crypto/tls/DefaultTlsClient.cs new file mode 100644 index 0000000..32a86e5 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/DefaultTlsClient.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Modes; +using Org.BouncyCastle.Crypto.Parameters; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public abstract class DefaultTlsClient + : AbstractTlsClient + { + public DefaultTlsClient() + : base() + { + } + + public DefaultTlsClient(TlsCipherFactory cipherFactory) + : base(cipherFactory) + { + } + + public override int[] GetCipherSuites() + { + return new int[] + { + CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, + CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, + CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + CipherSuite.TLS_DHE_DSS_WITH_AES_128_GCM_SHA256, + CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, + CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA, + CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, + CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, + CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA, + CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256, + CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256, + CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + }; + } + + public override TlsKeyExchange GetKeyExchange() + { + int keyExchangeAlgorithm = TlsUtilities.GetKeyExchangeAlgorithm(mSelectedCipherSuite); + + switch (keyExchangeAlgorithm) + { + case KeyExchangeAlgorithm.DH_anon: + case KeyExchangeAlgorithm.DH_DSS: + case KeyExchangeAlgorithm.DH_RSA: + return CreateDHKeyExchange(keyExchangeAlgorithm); + + case KeyExchangeAlgorithm.DHE_DSS: + case KeyExchangeAlgorithm.DHE_RSA: + return CreateDheKeyExchange(keyExchangeAlgorithm); + + case KeyExchangeAlgorithm.ECDH_anon: + case KeyExchangeAlgorithm.ECDH_ECDSA: + case KeyExchangeAlgorithm.ECDH_RSA: + return CreateECDHKeyExchange(keyExchangeAlgorithm); + + case KeyExchangeAlgorithm.ECDHE_ECDSA: + case KeyExchangeAlgorithm.ECDHE_RSA: + return CreateECDheKeyExchange(keyExchangeAlgorithm); + + case KeyExchangeAlgorithm.RSA: + return CreateRsaKeyExchange(); + + default: + /* + * Note: internal error here; the TlsProtocol implementation verifies that the + * server-selected cipher suite was in the list of client-offered cipher suites, so if + * we now can't produce an implementation, we shouldn't have offered it! + */ + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + protected virtual TlsKeyExchange CreateDHKeyExchange(int keyExchange) + { + return new TlsDHKeyExchange(keyExchange, mSupportedSignatureAlgorithms, null); + } + + protected virtual TlsKeyExchange CreateDheKeyExchange(int keyExchange) + { + return new TlsDheKeyExchange(keyExchange, mSupportedSignatureAlgorithms, null); + } + + protected virtual TlsKeyExchange CreateECDHKeyExchange(int keyExchange) + { + return new TlsECDHKeyExchange(keyExchange, mSupportedSignatureAlgorithms, mNamedCurves, mClientECPointFormats, + mServerECPointFormats); + } + + protected virtual TlsKeyExchange CreateECDheKeyExchange(int keyExchange) + { + return new TlsECDheKeyExchange(keyExchange, mSupportedSignatureAlgorithms, mNamedCurves, mClientECPointFormats, + mServerECPointFormats); + } + + protected virtual TlsKeyExchange CreateRsaKeyExchange() + { + return new TlsRsaKeyExchange(mSupportedSignatureAlgorithms); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/DefaultTlsEncryptionCredentials.cs b/bc-sharp-crypto/src/crypto/tls/DefaultTlsEncryptionCredentials.cs new file mode 100644 index 0000000..5348ee8 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/DefaultTlsEncryptionCredentials.cs @@ -0,0 +1,52 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class DefaultTlsEncryptionCredentials + : AbstractTlsEncryptionCredentials + { + protected readonly TlsContext mContext; + protected readonly Certificate mCertificate; + protected readonly AsymmetricKeyParameter mPrivateKey; + + public DefaultTlsEncryptionCredentials(TlsContext context, Certificate certificate, + AsymmetricKeyParameter privateKey) + { + if (certificate == null) + throw new ArgumentNullException("certificate"); + if (certificate.IsEmpty) + throw new ArgumentException("cannot be empty", "certificate"); + if (privateKey == null) + throw new ArgumentNullException("'privateKey' cannot be null"); + if (!privateKey.IsPrivate) + throw new ArgumentException("must be private", "privateKey"); + + if (privateKey is RsaKeyParameters) + { + } + else + { + throw new ArgumentException("type not supported: " + Platform.GetTypeName(privateKey), "privateKey"); + } + + this.mContext = context; + this.mCertificate = certificate; + this.mPrivateKey = privateKey; + } + + public override Certificate Certificate + { + get { return mCertificate; } + } + + /// + public override byte[] DecryptPreMasterSecret(byte[] encryptedPreMasterSecret) + { + return TlsRsaUtilities.SafeDecryptPreMasterSecret(mContext, (RsaKeyParameters)mPrivateKey, encryptedPreMasterSecret); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/DefaultTlsServer.cs b/bc-sharp-crypto/src/crypto/tls/DefaultTlsServer.cs new file mode 100644 index 0000000..97eaa07 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/DefaultTlsServer.cs @@ -0,0 +1,166 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Crypto.Agreement; +using Org.BouncyCastle.Crypto.Parameters; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public abstract class DefaultTlsServer + : AbstractTlsServer + { + public DefaultTlsServer() + : base() + { + } + + public DefaultTlsServer(TlsCipherFactory cipherFactory) + : base(cipherFactory) + { + } + + protected virtual TlsSignerCredentials GetDsaSignerCredentials() + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + protected virtual TlsSignerCredentials GetECDsaSignerCredentials() + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + protected virtual TlsEncryptionCredentials GetRsaEncryptionCredentials() + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + protected virtual TlsSignerCredentials GetRsaSignerCredentials() + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + protected virtual DHParameters GetDHParameters() + { + return DHStandardGroups.rfc7919_ffdhe2048; + } + + protected override int[] GetCipherSuites() + { + return new int[] + { + CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, + CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, + CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + CipherSuite.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, + CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, + CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, + CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, + CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA, + CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA, + CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384, + CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256, + CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256, + CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256, + CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA, + CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + }; + } + + public override TlsCredentials GetCredentials() + { + int keyExchangeAlgorithm = TlsUtilities.GetKeyExchangeAlgorithm(mSelectedCipherSuite); + + switch (keyExchangeAlgorithm) + { + case KeyExchangeAlgorithm.DHE_DSS: + return GetDsaSignerCredentials(); + + case KeyExchangeAlgorithm.DH_anon: + case KeyExchangeAlgorithm.ECDH_anon: + return null; + + case KeyExchangeAlgorithm.ECDHE_ECDSA: + return GetECDsaSignerCredentials(); + + case KeyExchangeAlgorithm.DHE_RSA: + case KeyExchangeAlgorithm.ECDHE_RSA: + return GetRsaSignerCredentials(); + + case KeyExchangeAlgorithm.RSA: + return GetRsaEncryptionCredentials(); + + default: + /* Note: internal error here; selected a key exchange we don't implement! */ + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + public override TlsKeyExchange GetKeyExchange() + { + int keyExchangeAlgorithm = TlsUtilities.GetKeyExchangeAlgorithm(mSelectedCipherSuite); + + switch (keyExchangeAlgorithm) + { + case KeyExchangeAlgorithm.DH_anon: + case KeyExchangeAlgorithm.DH_DSS: + case KeyExchangeAlgorithm.DH_RSA: + return CreateDHKeyExchange(keyExchangeAlgorithm); + + case KeyExchangeAlgorithm.DHE_DSS: + case KeyExchangeAlgorithm.DHE_RSA: + return CreateDheKeyExchange(keyExchangeAlgorithm); + + case KeyExchangeAlgorithm.ECDH_anon: + case KeyExchangeAlgorithm.ECDH_ECDSA: + case KeyExchangeAlgorithm.ECDH_RSA: + return CreateECDHKeyExchange(keyExchangeAlgorithm); + + case KeyExchangeAlgorithm.ECDHE_ECDSA: + case KeyExchangeAlgorithm.ECDHE_RSA: + return CreateECDheKeyExchange(keyExchangeAlgorithm); + + case KeyExchangeAlgorithm.RSA: + return CreateRsaKeyExchange(); + + default: + /* + * Note: internal error here; the TlsProtocol implementation verifies that the + * server-selected cipher suite was in the list of client-offered cipher suites, so if + * we now can't produce an implementation, we shouldn't have offered it! + */ + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + protected virtual TlsKeyExchange CreateDHKeyExchange(int keyExchange) + { + return new TlsDHKeyExchange(keyExchange, mSupportedSignatureAlgorithms, GetDHParameters()); + } + + protected virtual TlsKeyExchange CreateDheKeyExchange(int keyExchange) + { + return new TlsDheKeyExchange(keyExchange, mSupportedSignatureAlgorithms, GetDHParameters()); + } + + protected virtual TlsKeyExchange CreateECDHKeyExchange(int keyExchange) + { + return new TlsECDHKeyExchange(keyExchange, mSupportedSignatureAlgorithms, mNamedCurves, mClientECPointFormats, + mServerECPointFormats); + } + + protected virtual TlsKeyExchange CreateECDheKeyExchange(int keyExchange) + { + return new TlsECDheKeyExchange(keyExchange, mSupportedSignatureAlgorithms, mNamedCurves, mClientECPointFormats, + mServerECPointFormats); + } + + protected virtual TlsKeyExchange CreateRsaKeyExchange() + { + return new TlsRsaKeyExchange(mSupportedSignatureAlgorithms); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/DefaultTlsSignerCredentials.cs b/bc-sharp-crypto/src/crypto/tls/DefaultTlsSignerCredentials.cs new file mode 100644 index 0000000..0ff732a --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/DefaultTlsSignerCredentials.cs @@ -0,0 +1,93 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class DefaultTlsSignerCredentials + : AbstractTlsSignerCredentials + { + protected readonly TlsContext mContext; + protected readonly Certificate mCertificate; + protected readonly AsymmetricKeyParameter mPrivateKey; + protected readonly SignatureAndHashAlgorithm mSignatureAndHashAlgorithm; + + protected readonly TlsSigner mSigner; + + public DefaultTlsSignerCredentials(TlsContext context, Certificate certificate, AsymmetricKeyParameter privateKey) + : this(context, certificate, privateKey, null) + { + } + + public DefaultTlsSignerCredentials(TlsContext context, Certificate certificate, AsymmetricKeyParameter privateKey, + SignatureAndHashAlgorithm signatureAndHashAlgorithm) + { + if (certificate == null) + throw new ArgumentNullException("certificate"); + if (certificate.IsEmpty) + throw new ArgumentException("cannot be empty", "clientCertificate"); + if (privateKey == null) + throw new ArgumentNullException("privateKey"); + if (!privateKey.IsPrivate) + throw new ArgumentException("must be private", "privateKey"); + if (TlsUtilities.IsTlsV12(context) && signatureAndHashAlgorithm == null) + throw new ArgumentException("cannot be null for (D)TLS 1.2+", "signatureAndHashAlgorithm"); + + if (privateKey is RsaKeyParameters) + { + mSigner = new TlsRsaSigner(); + } + else if (privateKey is DsaPrivateKeyParameters) + { + mSigner = new TlsDssSigner(); + } + else if (privateKey is ECPrivateKeyParameters) + { + mSigner = new TlsECDsaSigner(); + } + else + { + throw new ArgumentException("type not supported: " + Platform.GetTypeName(privateKey), "privateKey"); + } + + this.mSigner.Init(context); + + this.mContext = context; + this.mCertificate = certificate; + this.mPrivateKey = privateKey; + this.mSignatureAndHashAlgorithm = signatureAndHashAlgorithm; + } + + public override Certificate Certificate + { + get { return mCertificate; } + } + + /// + public override byte[] GenerateCertificateSignature(byte[] hash) + { + try + { + if (TlsUtilities.IsTlsV12(mContext)) + { + return mSigner.GenerateRawSignature(mSignatureAndHashAlgorithm, mPrivateKey, hash); + } + else + { + return mSigner.GenerateRawSignature(mPrivateKey, hash); + } + } + catch (CryptoException e) + { + throw new TlsFatalAlert(AlertDescription.internal_error, e); + } + } + + public override SignatureAndHashAlgorithm SignatureAndHashAlgorithm + { + get { return mSignatureAndHashAlgorithm; } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/DefaultTlsSrpGroupVerifier.cs b/bc-sharp-crypto/src/crypto/tls/DefaultTlsSrpGroupVerifier.cs new file mode 100644 index 0000000..cc933bf --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/DefaultTlsSrpGroupVerifier.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Crypto.Agreement.Srp; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class DefaultTlsSrpGroupVerifier + : TlsSrpGroupVerifier + { + protected static readonly IList DefaultGroups = Platform.CreateArrayList(); + + static DefaultTlsSrpGroupVerifier() + { + DefaultGroups.Add(Srp6StandardGroups.rfc5054_1024); + DefaultGroups.Add(Srp6StandardGroups.rfc5054_1536); + DefaultGroups.Add(Srp6StandardGroups.rfc5054_2048); + DefaultGroups.Add(Srp6StandardGroups.rfc5054_3072); + DefaultGroups.Add(Srp6StandardGroups.rfc5054_4096); + DefaultGroups.Add(Srp6StandardGroups.rfc5054_6144); + DefaultGroups.Add(Srp6StandardGroups.rfc5054_8192); + } + + // Vector is (SRP6GroupParameters) + protected readonly IList mGroups; + + /** + * Accept only the group parameters specified in RFC 5054 Appendix A. + */ + public DefaultTlsSrpGroupVerifier() + : this(DefaultGroups) + { + } + + /** + * Specify a custom set of acceptable group parameters. + * + * @param groups a {@link Vector} of acceptable {@link SRP6GroupParameters} + */ + public DefaultTlsSrpGroupVerifier(IList groups) + { + this.mGroups = groups; + } + + public virtual bool Accept(Srp6GroupParameters group) + { + foreach (Srp6GroupParameters entry in mGroups) + { + if (AreGroupsEqual(group, entry)) + { + return true; + } + } + return false; + } + + protected virtual bool AreGroupsEqual(Srp6GroupParameters a, Srp6GroupParameters b) + { + return a == b || (AreParametersEqual(a.N, b.N) && AreParametersEqual(a.G, b.G)); + } + + protected virtual bool AreParametersEqual(BigInteger a, BigInteger b) + { + return a == b || a.Equals(b); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/DeferredHash.cs b/bc-sharp-crypto/src/crypto/tls/DeferredHash.cs new file mode 100644 index 0000000..f402f26 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/DeferredHash.cs @@ -0,0 +1,201 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /** + * Buffers input until the hash algorithm is determined. + */ + internal class DeferredHash + : TlsHandshakeHash + { + protected const int BUFFERING_HASH_LIMIT = 4; + + protected TlsContext mContext; + + private DigestInputBuffer mBuf; + private IDictionary mHashes; + private int mPrfHashAlgorithm; + + internal DeferredHash() + { + this.mBuf = new DigestInputBuffer(); + this.mHashes = Platform.CreateHashtable(); + this.mPrfHashAlgorithm = -1; + } + + private DeferredHash(byte prfHashAlgorithm, IDigest prfHash) + { + this.mBuf = null; + this.mHashes = Platform.CreateHashtable(); + this.mPrfHashAlgorithm = prfHashAlgorithm; + mHashes[prfHashAlgorithm] = prfHash; + } + + public virtual void Init(TlsContext context) + { + this.mContext = context; + } + + public virtual TlsHandshakeHash NotifyPrfDetermined() + { + int prfAlgorithm = mContext.SecurityParameters.PrfAlgorithm; + if (prfAlgorithm == PrfAlgorithm.tls_prf_legacy) + { + CombinedHash legacyHash = new CombinedHash(); + legacyHash.Init(mContext); + mBuf.UpdateDigest(legacyHash); + return legacyHash.NotifyPrfDetermined(); + } + + this.mPrfHashAlgorithm = TlsUtilities.GetHashAlgorithmForPrfAlgorithm(prfAlgorithm); + + CheckTrackingHash((byte)mPrfHashAlgorithm); + + return this; + } + + public virtual void TrackHashAlgorithm(byte hashAlgorithm) + { + if (mBuf == null) + throw new InvalidOperationException("Too late to track more hash algorithms"); + + CheckTrackingHash(hashAlgorithm); + } + + public virtual void SealHashAlgorithms() + { + CheckStopBuffering(); + } + + public virtual TlsHandshakeHash StopTracking() + { + byte prfHashAlgorithm = (byte)mPrfHashAlgorithm; + IDigest prfHash = TlsUtilities.CloneHash(prfHashAlgorithm, (IDigest)mHashes[prfHashAlgorithm]); + if (mBuf != null) + { + mBuf.UpdateDigest(prfHash); + } + DeferredHash result = new DeferredHash(prfHashAlgorithm, prfHash); + result.Init(mContext); + return result; + } + + public virtual IDigest ForkPrfHash() + { + CheckStopBuffering(); + + byte prfHashAlgorithm = (byte)mPrfHashAlgorithm; + if (mBuf != null) + { + IDigest prfHash = TlsUtilities.CreateHash(prfHashAlgorithm); + mBuf.UpdateDigest(prfHash); + return prfHash; + } + + return TlsUtilities.CloneHash(prfHashAlgorithm, (IDigest)mHashes[prfHashAlgorithm]); + } + + public virtual byte[] GetFinalHash(byte hashAlgorithm) + { + IDigest d = (IDigest)mHashes[hashAlgorithm]; + if (d == null) + throw new InvalidOperationException("HashAlgorithm." + HashAlgorithm.GetText(hashAlgorithm) + " is not being tracked"); + + d = TlsUtilities.CloneHash(hashAlgorithm, d); + if (mBuf != null) + { + mBuf.UpdateDigest(d); + } + + return DigestUtilities.DoFinal(d); + } + + public virtual string AlgorithmName + { + get { throw new InvalidOperationException("Use Fork() to get a definite IDigest"); } + } + + public virtual int GetByteLength() + { + throw new InvalidOperationException("Use Fork() to get a definite IDigest"); + } + + public virtual int GetDigestSize() + { + throw new InvalidOperationException("Use Fork() to get a definite IDigest"); + } + + public virtual void Update(byte input) + { + if (mBuf != null) + { + mBuf.WriteByte(input); + return; + } + + foreach (IDigest hash in mHashes.Values) + { + hash.Update(input); + } + } + + public virtual void BlockUpdate(byte[] input, int inOff, int len) + { + if (mBuf != null) + { + mBuf.Write(input, inOff, len); + return; + } + + foreach (IDigest hash in mHashes.Values) + { + hash.BlockUpdate(input, inOff, len); + } + } + + public virtual int DoFinal(byte[] output, int outOff) + { + throw new InvalidOperationException("Use Fork() to get a definite IDigest"); + } + + public virtual void Reset() + { + if (mBuf != null) + { + mBuf.SetLength(0); + return; + } + + foreach (IDigest hash in mHashes.Values) + { + hash.Reset(); + } + } + + protected virtual void CheckStopBuffering() + { + if (mBuf != null && mHashes.Count <= BUFFERING_HASH_LIMIT) + { + foreach (IDigest hash in mHashes.Values) + { + mBuf.UpdateDigest(hash); + } + + this.mBuf = null; + } + } + + protected virtual void CheckTrackingHash(byte hashAlgorithm) + { + if (!mHashes.Contains(hashAlgorithm)) + { + IDigest hash = TlsUtilities.CreateHash(hashAlgorithm); + mHashes[hashAlgorithm] = hash; + } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/DigestInputBuffer.cs b/bc-sharp-crypto/src/crypto/tls/DigestInputBuffer.cs new file mode 100644 index 0000000..4435b40 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/DigestInputBuffer.cs @@ -0,0 +1,37 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + internal class DigestInputBuffer + : MemoryStream + { + internal void UpdateDigest(IDigest d) + { + Streams.WriteBufTo(this, new DigStream(d)); + } + + private class DigStream + : BaseOutputStream + { + private readonly IDigest d; + + internal DigStream(IDigest d) + { + this.d = d; + } + + public override void WriteByte(byte b) + { + d.Update(b); + } + + public override void Write(byte[] buf, int off, int len) + { + d.BlockUpdate(buf, off, len); + } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/DigitallySigned.cs b/bc-sharp-crypto/src/crypto/tls/DigitallySigned.cs new file mode 100644 index 0000000..8b7344f --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/DigitallySigned.cs @@ -0,0 +1,70 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class DigitallySigned + { + protected readonly SignatureAndHashAlgorithm mAlgorithm; + protected readonly byte[] mSignature; + + public DigitallySigned(SignatureAndHashAlgorithm algorithm, byte[] signature) + { + if (signature == null) + throw new ArgumentNullException("signature"); + + this.mAlgorithm = algorithm; + this.mSignature = signature; + } + + /** + * @return a {@link SignatureAndHashAlgorithm} (or null before TLS 1.2). + */ + public virtual SignatureAndHashAlgorithm Algorithm + { + get { return mAlgorithm; } + } + + public virtual byte[] Signature + { + get { return mSignature; } + } + + /** + * Encode this {@link DigitallySigned} to a {@link Stream}. + * + * @param output + * the {@link Stream} to encode to. + * @throws IOException + */ + public virtual void Encode(Stream output) + { + if (mAlgorithm != null) + { + mAlgorithm.Encode(output); + } + TlsUtilities.WriteOpaque16(mSignature, output); + } + + /** + * Parse a {@link DigitallySigned} from a {@link Stream}. + * + * @param context + * the {@link TlsContext} of the current connection. + * @param input + * the {@link Stream} to parse from. + * @return a {@link DigitallySigned} object. + * @throws IOException + */ + public static DigitallySigned Parse(TlsContext context, Stream input) + { + SignatureAndHashAlgorithm algorithm = null; + if (TlsUtilities.IsTlsV12(context)) + { + algorithm = SignatureAndHashAlgorithm.Parse(input); + } + byte[] signature = TlsUtilities.ReadOpaque16(input); + return new DigitallySigned(algorithm, signature); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/DtlsClientProtocol.cs b/bc-sharp-crypto/src/crypto/tls/DtlsClientProtocol.cs new file mode 100644 index 0000000..ae6e6a5 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/DtlsClientProtocol.cs @@ -0,0 +1,857 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class DtlsClientProtocol + : DtlsProtocol + { + public DtlsClientProtocol(SecureRandom secureRandom) + : base(secureRandom) + { + } + + public virtual DtlsTransport Connect(TlsClient client, DatagramTransport transport) + { + if (client == null) + throw new ArgumentNullException("client"); + if (transport == null) + throw new ArgumentNullException("transport"); + + SecurityParameters securityParameters = new SecurityParameters(); + securityParameters.entity = ConnectionEnd.client; + + ClientHandshakeState state = new ClientHandshakeState(); + state.client = client; + state.clientContext = new TlsClientContextImpl(mSecureRandom, securityParameters); + + securityParameters.clientRandom = TlsProtocol.CreateRandomBlock(client.ShouldUseGmtUnixTime(), + state.clientContext.NonceRandomGenerator); + + client.Init(state.clientContext); + + DtlsRecordLayer recordLayer = new DtlsRecordLayer(transport, state.clientContext, client, ContentType.handshake); + + TlsSession sessionToResume = state.client.GetSessionToResume(); + if (sessionToResume != null && sessionToResume.IsResumable) + { + SessionParameters sessionParameters = sessionToResume.ExportSessionParameters(); + if (sessionParameters != null) + { + state.tlsSession = sessionToResume; + state.sessionParameters = sessionParameters; + } + } + + try + { + return ClientHandshake(state, recordLayer); + } + catch (TlsFatalAlert fatalAlert) + { + AbortClientHandshake(state, recordLayer, fatalAlert.AlertDescription); + throw fatalAlert; + } + catch (IOException e) + { + AbortClientHandshake(state, recordLayer, AlertDescription.internal_error); + throw e; + } + catch (Exception e) + { + AbortClientHandshake(state, recordLayer, AlertDescription.internal_error); + throw new TlsFatalAlert(AlertDescription.internal_error, e); + } + finally + { + securityParameters.Clear(); + } + } + + internal virtual void AbortClientHandshake(ClientHandshakeState state, DtlsRecordLayer recordLayer, byte alertDescription) + { + recordLayer.Fail(alertDescription); + InvalidateSession(state); + } + + internal virtual DtlsTransport ClientHandshake(ClientHandshakeState state, DtlsRecordLayer recordLayer) + { + SecurityParameters securityParameters = state.clientContext.SecurityParameters; + DtlsReliableHandshake handshake = new DtlsReliableHandshake(state.clientContext, recordLayer); + + byte[] clientHelloBody = GenerateClientHello(state, state.client); + + recordLayer.SetWriteVersion(ProtocolVersion.DTLSv10); + + handshake.SendMessage(HandshakeType.client_hello, clientHelloBody); + + DtlsReliableHandshake.Message serverMessage = handshake.ReceiveMessage(); + + while (serverMessage.Type == HandshakeType.hello_verify_request) + { + ProtocolVersion recordLayerVersion = recordLayer.ReadVersion; + ProtocolVersion client_version = state.clientContext.ClientVersion; + + /* + * RFC 6347 4.2.1 DTLS 1.2 server implementations SHOULD use DTLS version 1.0 regardless of + * the version of TLS that is expected to be negotiated. DTLS 1.2 and 1.0 clients MUST use + * the version solely to indicate packet formatting (which is the same in both DTLS 1.2 and + * 1.0) and not as part of version negotiation. + */ + if (!recordLayerVersion.IsEqualOrEarlierVersionOf(client_version)) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + + recordLayer.ReadVersion = null; + + byte[] cookie = ProcessHelloVerifyRequest(state, serverMessage.Body); + byte[] patched = PatchClientHelloWithCookie(clientHelloBody, cookie); + + handshake.ResetHandshakeMessagesDigest(); + handshake.SendMessage(HandshakeType.client_hello, patched); + + serverMessage = handshake.ReceiveMessage(); + } + + if (serverMessage.Type == HandshakeType.server_hello) + { + ProtocolVersion recordLayerVersion = recordLayer.ReadVersion; + ReportServerVersion(state, recordLayerVersion); + recordLayer.SetWriteVersion(recordLayerVersion); + + ProcessServerHello(state, serverMessage.Body); + } + else + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + handshake.NotifyHelloComplete(); + + ApplyMaxFragmentLengthExtension(recordLayer, securityParameters.maxFragmentLength); + + if (state.resumedSession) + { + securityParameters.masterSecret = Arrays.Clone(state.sessionParameters.MasterSecret); + recordLayer.InitPendingEpoch(state.client.GetCipher()); + + // NOTE: Calculated exclusive of the actual Finished message from the server + byte[] resExpectedServerVerifyData = TlsUtilities.CalculateVerifyData(state.clientContext, ExporterLabel.server_finished, + TlsProtocol.GetCurrentPrfHash(state.clientContext, handshake.HandshakeHash, null)); + ProcessFinished(handshake.ReceiveMessageBody(HandshakeType.finished), resExpectedServerVerifyData); + + // NOTE: Calculated exclusive of the Finished message itself + byte[] resClientVerifyData = TlsUtilities.CalculateVerifyData(state.clientContext, ExporterLabel.client_finished, + TlsProtocol.GetCurrentPrfHash(state.clientContext, handshake.HandshakeHash, null)); + handshake.SendMessage(HandshakeType.finished, resClientVerifyData); + + handshake.Finish(); + + state.clientContext.SetResumableSession(state.tlsSession); + + state.client.NotifyHandshakeComplete(); + + return new DtlsTransport(recordLayer); + } + + InvalidateSession(state); + + if (state.selectedSessionID.Length > 0) + { + state.tlsSession = new TlsSessionImpl(state.selectedSessionID, null); + } + + serverMessage = handshake.ReceiveMessage(); + + if (serverMessage.Type == HandshakeType.supplemental_data) + { + ProcessServerSupplementalData(state, serverMessage.Body); + serverMessage = handshake.ReceiveMessage(); + } + else + { + state.client.ProcessServerSupplementalData(null); + } + + state.keyExchange = state.client.GetKeyExchange(); + state.keyExchange.Init(state.clientContext); + + Certificate serverCertificate = null; + + if (serverMessage.Type == HandshakeType.certificate) + { + serverCertificate = ProcessServerCertificate(state, serverMessage.Body); + serverMessage = handshake.ReceiveMessage(); + } + else + { + // Okay, Certificate is optional + state.keyExchange.SkipServerCredentials(); + } + + // TODO[RFC 3546] Check whether empty certificates is possible, allowed, or excludes CertificateStatus + if (serverCertificate == null || serverCertificate.IsEmpty) + { + state.allowCertificateStatus = false; + } + + if (serverMessage.Type == HandshakeType.certificate_status) + { + ProcessCertificateStatus(state, serverMessage.Body); + serverMessage = handshake.ReceiveMessage(); + } + else + { + // Okay, CertificateStatus is optional + } + + if (serverMessage.Type == HandshakeType.server_key_exchange) + { + ProcessServerKeyExchange(state, serverMessage.Body); + serverMessage = handshake.ReceiveMessage(); + } + else + { + // Okay, ServerKeyExchange is optional + state.keyExchange.SkipServerKeyExchange(); + } + + if (serverMessage.Type == HandshakeType.certificate_request) + { + ProcessCertificateRequest(state, serverMessage.Body); + + /* + * TODO Give the client a chance to immediately select the CertificateVerify hash + * algorithm here to avoid tracking the other hash algorithms unnecessarily? + */ + TlsUtilities.TrackHashAlgorithms(handshake.HandshakeHash, + state.certificateRequest.SupportedSignatureAlgorithms); + + serverMessage = handshake.ReceiveMessage(); + } + else + { + // Okay, CertificateRequest is optional + } + + if (serverMessage.Type == HandshakeType.server_hello_done) + { + if (serverMessage.Body.Length != 0) + { + throw new TlsFatalAlert(AlertDescription.decode_error); + } + } + else + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + handshake.HandshakeHash.SealHashAlgorithms(); + + IList clientSupplementalData = state.client.GetClientSupplementalData(); + if (clientSupplementalData != null) + { + byte[] supplementalDataBody = GenerateSupplementalData(clientSupplementalData); + handshake.SendMessage(HandshakeType.supplemental_data, supplementalDataBody); + } + + if (state.certificateRequest != null) + { + state.clientCredentials = state.authentication.GetClientCredentials(state.certificateRequest); + + /* + * RFC 5246 If no suitable certificate is available, the client MUST send a certificate + * message containing no certificates. + * + * NOTE: In previous RFCs, this was SHOULD instead of MUST. + */ + Certificate clientCertificate = null; + if (state.clientCredentials != null) + { + clientCertificate = state.clientCredentials.Certificate; + } + if (clientCertificate == null) + { + clientCertificate = Certificate.EmptyChain; + } + + byte[] certificateBody = GenerateCertificate(clientCertificate); + handshake.SendMessage(HandshakeType.certificate, certificateBody); + } + + if (state.clientCredentials != null) + { + state.keyExchange.ProcessClientCredentials(state.clientCredentials); + } + else + { + state.keyExchange.SkipClientCredentials(); + } + + byte[] clientKeyExchangeBody = GenerateClientKeyExchange(state); + handshake.SendMessage(HandshakeType.client_key_exchange, clientKeyExchangeBody); + + TlsHandshakeHash prepareFinishHash = handshake.PrepareToFinish(); + securityParameters.sessionHash = TlsProtocol.GetCurrentPrfHash(state.clientContext, prepareFinishHash, null); + + TlsProtocol.EstablishMasterSecret(state.clientContext, state.keyExchange); + recordLayer.InitPendingEpoch(state.client.GetCipher()); + + if (state.clientCredentials != null && state.clientCredentials is TlsSignerCredentials) + { + TlsSignerCredentials signerCredentials = (TlsSignerCredentials)state.clientCredentials; + + /* + * RFC 5246 4.7. digitally-signed element needs SignatureAndHashAlgorithm from TLS 1.2 + */ + SignatureAndHashAlgorithm signatureAndHashAlgorithm = TlsUtilities.GetSignatureAndHashAlgorithm( + state.clientContext, signerCredentials); + + byte[] hash; + if (signatureAndHashAlgorithm == null) + { + hash = securityParameters.SessionHash; + } + else + { + hash = prepareFinishHash.GetFinalHash(signatureAndHashAlgorithm.Hash); + } + + byte[] signature = signerCredentials.GenerateCertificateSignature(hash); + DigitallySigned certificateVerify = new DigitallySigned(signatureAndHashAlgorithm, signature); + byte[] certificateVerifyBody = GenerateCertificateVerify(state, certificateVerify); + handshake.SendMessage(HandshakeType.certificate_verify, certificateVerifyBody); + } + + // NOTE: Calculated exclusive of the Finished message itself + byte[] clientVerifyData = TlsUtilities.CalculateVerifyData(state.clientContext, ExporterLabel.client_finished, + TlsProtocol.GetCurrentPrfHash(state.clientContext, handshake.HandshakeHash, null)); + handshake.SendMessage(HandshakeType.finished, clientVerifyData); + + if (state.expectSessionTicket) + { + serverMessage = handshake.ReceiveMessage(); + if (serverMessage.Type == HandshakeType.session_ticket) + { + ProcessNewSessionTicket(state, serverMessage.Body); + } + else + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + } + + // NOTE: Calculated exclusive of the actual Finished message from the server + byte[] expectedServerVerifyData = TlsUtilities.CalculateVerifyData(state.clientContext, ExporterLabel.server_finished, + TlsProtocol.GetCurrentPrfHash(state.clientContext, handshake.HandshakeHash, null)); + ProcessFinished(handshake.ReceiveMessageBody(HandshakeType.finished), expectedServerVerifyData); + + handshake.Finish(); + + if (state.tlsSession != null) + { + state.sessionParameters = new SessionParameters.Builder() + .SetCipherSuite(securityParameters.CipherSuite) + .SetCompressionAlgorithm(securityParameters.CompressionAlgorithm) + .SetMasterSecret(securityParameters.MasterSecret) + .SetPeerCertificate(serverCertificate) + .SetPskIdentity(securityParameters.PskIdentity) + .SetSrpIdentity(securityParameters.SrpIdentity) + // TODO Consider filtering extensions that aren't relevant to resumed sessions + .SetServerExtensions(state.serverExtensions) + .Build(); + + state.tlsSession = TlsUtilities.ImportSession(state.tlsSession.SessionID, state.sessionParameters); + + state.clientContext.SetResumableSession(state.tlsSession); + } + + state.client.NotifyHandshakeComplete(); + + return new DtlsTransport(recordLayer); + } + + protected virtual byte[] GenerateCertificateVerify(ClientHandshakeState state, DigitallySigned certificateVerify) + { + MemoryStream buf = new MemoryStream(); + certificateVerify.Encode(buf); + return buf.ToArray(); + } + + protected virtual byte[] GenerateClientHello(ClientHandshakeState state, TlsClient client) + { + MemoryStream buf = new MemoryStream(); + + ProtocolVersion client_version = client.ClientVersion; + if (!client_version.IsDtls) + throw new TlsFatalAlert(AlertDescription.internal_error); + + TlsClientContextImpl context = state.clientContext; + + context.SetClientVersion(client_version); + TlsUtilities.WriteVersion(client_version, buf); + + SecurityParameters securityParameters = context.SecurityParameters; + buf.Write(securityParameters.ClientRandom, 0, securityParameters.ClientRandom.Length); + + // Session ID + byte[] session_id = TlsUtilities.EmptyBytes; + if (state.tlsSession != null) + { + session_id = state.tlsSession.SessionID; + if (session_id == null || session_id.Length > 32) + { + session_id = TlsUtilities.EmptyBytes; + } + } + TlsUtilities.WriteOpaque8(session_id, buf); + + // Cookie + TlsUtilities.WriteOpaque8(TlsUtilities.EmptyBytes, buf); + + bool fallback = client.IsFallback; + + /* + * Cipher suites + */ + state.offeredCipherSuites = client.GetCipherSuites(); + + // Integer -> byte[] + state.clientExtensions = client.GetClientExtensions(); + + // Cipher Suites (and SCSV) + { + /* + * RFC 5746 3.4. The client MUST include either an empty "renegotiation_info" extension, + * or the TLS_EMPTY_RENEGOTIATION_INFO_SCSV signaling cipher suite value in the + * ClientHello. Including both is NOT RECOMMENDED. + */ + byte[] renegExtData = TlsUtilities.GetExtensionData(state.clientExtensions, ExtensionType.renegotiation_info); + bool noRenegExt = (null == renegExtData); + + bool noRenegSCSV = !Arrays.Contains(state.offeredCipherSuites, CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV); + + if (noRenegExt && noRenegSCSV) + { + // TODO Consider whether to default to a client extension instead + state.offeredCipherSuites = Arrays.Append(state.offeredCipherSuites, CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV); + } + + /* + * RFC 7507 4. If a client sends a ClientHello.client_version containing a lower value + * than the latest (highest-valued) version supported by the client, it SHOULD include + * the TLS_FALLBACK_SCSV cipher suite value in ClientHello.cipher_suites [..]. (The + * client SHOULD put TLS_FALLBACK_SCSV after all cipher suites that it actually intends + * to negotiate.) + */ + if (fallback && !Arrays.Contains(state.offeredCipherSuites, CipherSuite.TLS_FALLBACK_SCSV)) + { + state.offeredCipherSuites = Arrays.Append(state.offeredCipherSuites, CipherSuite.TLS_FALLBACK_SCSV); + } + + TlsUtilities.WriteUint16ArrayWithUint16Length(state.offeredCipherSuites, buf); + } + + // TODO Add support for compression + // Compression methods + // state.offeredCompressionMethods = client.getCompressionMethods(); + state.offeredCompressionMethods = new byte[]{ CompressionMethod.cls_null }; + + TlsUtilities.WriteUint8ArrayWithUint8Length(state.offeredCompressionMethods, buf); + + // Extensions + if (state.clientExtensions != null) + { + TlsProtocol.WriteExtensions(buf, state.clientExtensions); + } + + return buf.ToArray(); + } + + protected virtual byte[] GenerateClientKeyExchange(ClientHandshakeState state) + { + MemoryStream buf = new MemoryStream(); + state.keyExchange.GenerateClientKeyExchange(buf); + return buf.ToArray(); + } + + protected virtual void InvalidateSession(ClientHandshakeState state) + { + if (state.sessionParameters != null) + { + state.sessionParameters.Clear(); + state.sessionParameters = null; + } + + if (state.tlsSession != null) + { + state.tlsSession.Invalidate(); + state.tlsSession = null; + } + } + + protected virtual void ProcessCertificateRequest(ClientHandshakeState state, byte[] body) + { + if (state.authentication == null) + { + /* + * RFC 2246 7.4.4. It is a fatal handshake_failure alert for an anonymous server to + * request client identification. + */ + throw new TlsFatalAlert(AlertDescription.handshake_failure); + } + + MemoryStream buf = new MemoryStream(body, false); + + state.certificateRequest = CertificateRequest.Parse(state.clientContext, buf); + + TlsProtocol.AssertEmpty(buf); + + state.keyExchange.ValidateCertificateRequest(state.certificateRequest); + } + + protected virtual void ProcessCertificateStatus(ClientHandshakeState state, byte[] body) + { + if (!state.allowCertificateStatus) + { + /* + * RFC 3546 3.6. If a server returns a "CertificateStatus" message, then the + * server MUST have included an extension of type "status_request" with empty + * "extension_data" in the extended server hello.. + */ + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + MemoryStream buf = new MemoryStream(body, false); + + state.certificateStatus = CertificateStatus.Parse(buf); + + TlsProtocol.AssertEmpty(buf); + + // TODO[RFC 3546] Figure out how to provide this to the client/authentication. + } + + protected virtual byte[] ProcessHelloVerifyRequest(ClientHandshakeState state, byte[] body) + { + MemoryStream buf = new MemoryStream(body, false); + + ProtocolVersion server_version = TlsUtilities.ReadVersion(buf); + byte[] cookie = TlsUtilities.ReadOpaque8(buf); + + TlsProtocol.AssertEmpty(buf); + + // TODO Seems this behaviour is not yet in line with OpenSSL for DTLS 1.2 + // reportServerVersion(state, server_version); + if (!server_version.IsEqualOrEarlierVersionOf(state.clientContext.ClientVersion)) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + + /* + * RFC 6347 This specification increases the cookie size limit to 255 bytes for greater + * future flexibility. The limit remains 32 for previous versions of DTLS. + */ + if (!ProtocolVersion.DTLSv12.IsEqualOrEarlierVersionOf(server_version) && cookie.Length > 32) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + + return cookie; + } + + protected virtual void ProcessNewSessionTicket(ClientHandshakeState state, byte[] body) + { + MemoryStream buf = new MemoryStream(body, false); + + NewSessionTicket newSessionTicket = NewSessionTicket.Parse(buf); + + TlsProtocol.AssertEmpty(buf); + + state.client.NotifyNewSessionTicket(newSessionTicket); + } + + protected virtual Certificate ProcessServerCertificate(ClientHandshakeState state, byte[] body) + { + MemoryStream buf = new MemoryStream(body, false); + + Certificate serverCertificate = Certificate.Parse(buf); + + TlsProtocol.AssertEmpty(buf); + + state.keyExchange.ProcessServerCertificate(serverCertificate); + state.authentication = state.client.GetAuthentication(); + state.authentication.NotifyServerCertificate(serverCertificate); + + return serverCertificate; + } + + protected virtual void ProcessServerHello(ClientHandshakeState state, byte[] body) + { + SecurityParameters securityParameters = state.clientContext.SecurityParameters; + + MemoryStream buf = new MemoryStream(body, false); + + { + ProtocolVersion server_version = TlsUtilities.ReadVersion(buf); + ReportServerVersion(state, server_version); + } + + securityParameters.serverRandom = TlsUtilities.ReadFully(32, buf); + + state.selectedSessionID = TlsUtilities.ReadOpaque8(buf); + if (state.selectedSessionID.Length > 32) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + state.client.NotifySessionID(state.selectedSessionID); + state.resumedSession = state.selectedSessionID.Length > 0 && state.tlsSession != null + && Arrays.AreEqual(state.selectedSessionID, state.tlsSession.SessionID); + + int selectedCipherSuite = TlsUtilities.ReadUint16(buf); + if (!Arrays.Contains(state.offeredCipherSuites, selectedCipherSuite) + || selectedCipherSuite == CipherSuite.TLS_NULL_WITH_NULL_NULL + || CipherSuite.IsScsv(selectedCipherSuite) + || !TlsUtilities.IsValidCipherSuiteForVersion(selectedCipherSuite, state.clientContext.ServerVersion)) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + ValidateSelectedCipherSuite(selectedCipherSuite, AlertDescription.illegal_parameter); + state.client.NotifySelectedCipherSuite(selectedCipherSuite); + + byte selectedCompressionMethod = TlsUtilities.ReadUint8(buf); + if (!Arrays.Contains(state.offeredCompressionMethods, selectedCompressionMethod)) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + state.client.NotifySelectedCompressionMethod(selectedCompressionMethod); + + /* + * RFC3546 2.2 The extended server hello message format MAY be sent in place of the server + * hello message when the client has requested extended functionality via the extended + * client hello message specified in Section 2.1. ... Note that the extended server hello + * message is only sent in response to an extended client hello message. This prevents the + * possibility that the extended server hello message could "break" existing TLS 1.0 + * clients. + */ + + /* + * TODO RFC 3546 2.3 If [...] the older session is resumed, then the server MUST ignore + * extensions appearing in the client hello, and send a server hello containing no + * extensions. + */ + + // Integer -> byte[] + state.serverExtensions = TlsProtocol.ReadExtensions(buf); + + /* + * RFC 3546 2.2 Note that the extended server hello message is only sent in response to an + * extended client hello message. However, see RFC 5746 exception below. We always include + * the SCSV, so an Extended Server Hello is always allowed. + */ + if (state.serverExtensions != null) + { + foreach (int extType in state.serverExtensions.Keys) + { + /* + * RFC 5746 3.6. Note that sending a "renegotiation_info" extension in response to a + * ClientHello containing only the SCSV is an explicit exception to the prohibition + * in RFC 5246, Section 7.4.1.4, on the server sending unsolicited extensions and is + * only allowed because the client is signaling its willingness to receive the + * extension via the TLS_EMPTY_RENEGOTIATION_INFO_SCSV SCSV. + */ + if (extType == ExtensionType.renegotiation_info) + continue; + + /* + * RFC 5246 7.4.1.4 An extension type MUST NOT appear in the ServerHello unless the + * same extension type appeared in the corresponding ClientHello. If a client + * receives an extension type in ServerHello that it did not request in the + * associated ClientHello, it MUST abort the handshake with an unsupported_extension + * fatal alert. + */ + if (null == TlsUtilities.GetExtensionData(state.clientExtensions, extType)) + throw new TlsFatalAlert(AlertDescription.unsupported_extension); + + /* + * RFC 3546 2.3. If [...] the older session is resumed, then the server MUST ignore + * extensions appearing in the client hello, and send a server hello containing no + * extensions[.] + */ + if (state.resumedSession) + { + // TODO[compat-gnutls] GnuTLS test server sends server extensions e.g. ec_point_formats + // TODO[compat-openssl] OpenSSL test server sends server extensions e.g. ec_point_formats + // TODO[compat-polarssl] PolarSSL test server sends server extensions e.g. ec_point_formats + //throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + } + } + + /* + * RFC 5746 3.4. Client Behavior: Initial Handshake + */ + { + /* + * When a ServerHello is received, the client MUST check if it includes the + * "renegotiation_info" extension: + */ + byte[] renegExtData = TlsUtilities.GetExtensionData(state.serverExtensions, ExtensionType.renegotiation_info); + if (renegExtData != null) + { + /* + * If the extension is present, set the secure_renegotiation flag to TRUE. The + * client MUST then verify that the length of the "renegotiated_connection" + * field is zero, and if it is not, MUST abort the handshake (by sending a fatal + * handshake_failure alert). + */ + state.secure_renegotiation = true; + + if (!Arrays.ConstantTimeAreEqual(renegExtData, TlsProtocol.CreateRenegotiationInfo(TlsUtilities.EmptyBytes))) + throw new TlsFatalAlert(AlertDescription.handshake_failure); + } + } + + // TODO[compat-gnutls] GnuTLS test server fails to send renegotiation_info extension when resuming + state.client.NotifySecureRenegotiation(state.secure_renegotiation); + + IDictionary sessionClientExtensions = state.clientExtensions, sessionServerExtensions = state.serverExtensions; + if (state.resumedSession) + { + if (selectedCipherSuite != state.sessionParameters.CipherSuite + || selectedCompressionMethod != state.sessionParameters.CompressionAlgorithm) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + + sessionClientExtensions = null; + sessionServerExtensions = state.sessionParameters.ReadServerExtensions(); + } + + securityParameters.cipherSuite = selectedCipherSuite; + securityParameters.compressionAlgorithm = selectedCompressionMethod; + + if (sessionServerExtensions != null) + { + { + /* + * RFC 7366 3. If a server receives an encrypt-then-MAC request extension from a client + * and then selects a stream or Authenticated Encryption with Associated Data (AEAD) + * ciphersuite, it MUST NOT send an encrypt-then-MAC response extension back to the + * client. + */ + bool serverSentEncryptThenMAC = TlsExtensionsUtilities.HasEncryptThenMacExtension(sessionServerExtensions); + if (serverSentEncryptThenMAC && !TlsUtilities.IsBlockCipherSuite(securityParameters.CipherSuite)) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + securityParameters.encryptThenMac = serverSentEncryptThenMAC; + } + + securityParameters.extendedMasterSecret = TlsExtensionsUtilities.HasExtendedMasterSecretExtension(sessionServerExtensions); + + securityParameters.maxFragmentLength = EvaluateMaxFragmentLengthExtension(state.resumedSession, + sessionClientExtensions, sessionServerExtensions, AlertDescription.illegal_parameter); + + securityParameters.truncatedHMac = TlsExtensionsUtilities.HasTruncatedHMacExtension(sessionServerExtensions); + + /* + * TODO It's surprising that there's no provision to allow a 'fresh' CertificateStatus to be + * sent in a session resumption handshake. + */ + state.allowCertificateStatus = !state.resumedSession + && TlsUtilities.HasExpectedEmptyExtensionData(sessionServerExtensions, ExtensionType.status_request, + AlertDescription.illegal_parameter); + + state.expectSessionTicket = !state.resumedSession + && TlsUtilities.HasExpectedEmptyExtensionData(sessionServerExtensions, ExtensionType.session_ticket, + AlertDescription.illegal_parameter); + } + + /* + * TODO[session-hash] + * + * draft-ietf-tls-session-hash-04 4. Clients and servers SHOULD NOT accept handshakes + * that do not use the extended master secret [..]. (and see 5.2, 5.3) + */ + + if (sessionClientExtensions != null) + { + state.client.ProcessServerExtensions(sessionServerExtensions); + } + + securityParameters.prfAlgorithm = TlsProtocol.GetPrfAlgorithm(state.clientContext, + securityParameters.CipherSuite); + + /* + * RFC 5246 7.4.9. Any cipher suite which does not explicitly specify verify_data_length has + * a verify_data_length equal to 12. This includes all existing cipher suites. + */ + securityParameters.verifyDataLength = 12; + } + + protected virtual void ProcessServerKeyExchange(ClientHandshakeState state, byte[] body) + { + MemoryStream buf = new MemoryStream(body, false); + + state.keyExchange.ProcessServerKeyExchange(buf); + + TlsProtocol.AssertEmpty(buf); + } + + protected virtual void ProcessServerSupplementalData(ClientHandshakeState state, byte[] body) + { + MemoryStream buf = new MemoryStream(body, false); + IList serverSupplementalData = TlsProtocol.ReadSupplementalDataMessage(buf); + state.client.ProcessServerSupplementalData(serverSupplementalData); + } + + protected virtual void ReportServerVersion(ClientHandshakeState state, ProtocolVersion server_version) + { + TlsClientContextImpl clientContext = state.clientContext; + ProtocolVersion currentServerVersion = clientContext.ServerVersion; + if (null == currentServerVersion) + { + clientContext.SetServerVersion(server_version); + state.client.NotifyServerVersion(server_version); + } + else if (!currentServerVersion.Equals(server_version)) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + } + + protected static byte[] PatchClientHelloWithCookie(byte[] clientHelloBody, byte[] cookie) + { + int sessionIDPos = 34; + int sessionIDLength = TlsUtilities.ReadUint8(clientHelloBody, sessionIDPos); + + int cookieLengthPos = sessionIDPos + 1 + sessionIDLength; + int cookiePos = cookieLengthPos + 1; + + byte[] patched = new byte[clientHelloBody.Length + cookie.Length]; + Array.Copy(clientHelloBody, 0, patched, 0, cookieLengthPos); + TlsUtilities.CheckUint8(cookie.Length); + TlsUtilities.WriteUint8((byte)cookie.Length, patched, cookieLengthPos); + Array.Copy(cookie, 0, patched, cookiePos, cookie.Length); + Array.Copy(clientHelloBody, cookiePos, patched, cookiePos + cookie.Length, clientHelloBody.Length - cookiePos); + + return patched; + } + + protected internal class ClientHandshakeState + { + internal TlsClient client = null; + internal TlsClientContextImpl clientContext = null; + internal TlsSession tlsSession = null; + internal SessionParameters sessionParameters = null; + internal SessionParameters.Builder sessionParametersBuilder = null; + internal int[] offeredCipherSuites = null; + internal byte[] offeredCompressionMethods = null; + internal IDictionary clientExtensions = null; + internal IDictionary serverExtensions = null; + internal byte[] selectedSessionID = null; + internal bool resumedSession = false; + internal bool secure_renegotiation = false; + internal bool allowCertificateStatus = false; + internal bool expectSessionTicket = false; + internal TlsKeyExchange keyExchange = null; + internal TlsAuthentication authentication = null; + internal CertificateStatus certificateStatus = null; + internal CertificateRequest certificateRequest = null; + internal TlsCredentials clientCredentials = null; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/DtlsEpoch.cs b/bc-sharp-crypto/src/crypto/tls/DtlsEpoch.cs new file mode 100644 index 0000000..91fffa5 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/DtlsEpoch.cs @@ -0,0 +1,51 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + internal class DtlsEpoch + { + private readonly DtlsReplayWindow mReplayWindow = new DtlsReplayWindow(); + + private readonly int mEpoch; + private readonly TlsCipher mCipher; + + private long mSequenceNumber = 0; + + internal DtlsEpoch(int epoch, TlsCipher cipher) + { + if (epoch < 0) + throw new ArgumentException("must be >= 0", "epoch"); + if (cipher == null) + throw new ArgumentNullException("cipher"); + + this.mEpoch = epoch; + this.mCipher = cipher; + } + + internal long AllocateSequenceNumber() + { + // TODO Check for overflow + return mSequenceNumber++; + } + + internal TlsCipher Cipher + { + get { return mCipher; } + } + + internal int Epoch + { + get { return mEpoch; } + } + + internal DtlsReplayWindow ReplayWindow + { + get { return mReplayWindow; } + } + + internal long SequenceNumber + { + get { return mSequenceNumber; } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/DtlsHandshakeRetransmit.cs b/bc-sharp-crypto/src/crypto/tls/DtlsHandshakeRetransmit.cs new file mode 100644 index 0000000..8bfae78 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/DtlsHandshakeRetransmit.cs @@ -0,0 +1,11 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + interface DtlsHandshakeRetransmit + { + /// + void ReceivedHandshakeRecord(int epoch, byte[] buf, int off, int len); + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/DtlsProtocol.cs b/bc-sharp-crypto/src/crypto/tls/DtlsProtocol.cs new file mode 100644 index 0000000..e4ebd43 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/DtlsProtocol.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public abstract class DtlsProtocol + { + protected readonly SecureRandom mSecureRandom; + + protected DtlsProtocol(SecureRandom secureRandom) + { + if (secureRandom == null) + throw new ArgumentNullException("secureRandom"); + + this.mSecureRandom = secureRandom; + } + + /// + protected virtual void ProcessFinished(byte[] body, byte[] expected_verify_data) + { + MemoryStream buf = new MemoryStream(body, false); + + byte[] verify_data = TlsUtilities.ReadFully(expected_verify_data.Length, buf); + + TlsProtocol.AssertEmpty(buf); + + if (!Arrays.ConstantTimeAreEqual(expected_verify_data, verify_data)) + throw new TlsFatalAlert(AlertDescription.handshake_failure); + } + + /// + internal static void ApplyMaxFragmentLengthExtension(DtlsRecordLayer recordLayer, short maxFragmentLength) + { + if (maxFragmentLength >= 0) + { + if (!MaxFragmentLength.IsValid((byte)maxFragmentLength)) + throw new TlsFatalAlert(AlertDescription.internal_error); + + int plainTextLimit = 1 << (8 + maxFragmentLength); + recordLayer.SetPlaintextLimit(plainTextLimit); + } + } + + /// + protected static short EvaluateMaxFragmentLengthExtension(bool resumedSession, IDictionary clientExtensions, + IDictionary serverExtensions, byte alertDescription) + { + short maxFragmentLength = TlsExtensionsUtilities.GetMaxFragmentLengthExtension(serverExtensions); + if (maxFragmentLength >= 0) + { + if (!MaxFragmentLength.IsValid((byte)maxFragmentLength) + || (!resumedSession && maxFragmentLength != TlsExtensionsUtilities + .GetMaxFragmentLengthExtension(clientExtensions))) + { + throw new TlsFatalAlert(alertDescription); + } + } + return maxFragmentLength; + } + + /// + protected static byte[] GenerateCertificate(Certificate certificate) + { + MemoryStream buf = new MemoryStream(); + certificate.Encode(buf); + return buf.ToArray(); + } + + /// + protected static byte[] GenerateSupplementalData(IList supplementalData) + { + MemoryStream buf = new MemoryStream(); + TlsProtocol.WriteSupplementalData(buf, supplementalData); + return buf.ToArray(); + } + + /// + protected static void ValidateSelectedCipherSuite(int selectedCipherSuite, byte alertDescription) + { + switch (TlsUtilities.GetEncryptionAlgorithm(selectedCipherSuite)) + { + case EncryptionAlgorithm.RC4_40: + case EncryptionAlgorithm.RC4_128: + throw new TlsFatalAlert(alertDescription); + } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/DtlsReassembler.cs b/bc-sharp-crypto/src/crypto/tls/DtlsReassembler.cs new file mode 100644 index 0000000..11fe609 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/DtlsReassembler.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + class DtlsReassembler + { + private readonly byte mMsgType; + private readonly byte[] mBody; + + private readonly IList mMissing = Platform.CreateArrayList(); + + internal DtlsReassembler(byte msg_type, int length) + { + this.mMsgType = msg_type; + this.mBody = new byte[length]; + this.mMissing.Add(new Range(0, length)); + } + + internal byte MsgType + { + get { return mMsgType; } + } + + internal byte[] GetBodyIfComplete() + { + return mMissing.Count == 0 ? mBody : null; + } + + internal void ContributeFragment(byte msg_type, int length, byte[] buf, int off, int fragment_offset, + int fragment_length) + { + int fragment_end = fragment_offset + fragment_length; + + if (this.mMsgType != msg_type || this.mBody.Length != length || fragment_end > length) + { + return; + } + + if (fragment_length == 0) + { + // NOTE: Empty messages still require an empty fragment to complete it + if (fragment_offset == 0 && mMissing.Count > 0) + { + Range firstRange = (Range)mMissing[0]; + if (firstRange.End == 0) + { + mMissing.RemoveAt(0); + } + } + return; + } + + for (int i = 0; i < mMissing.Count; ++i) + { + Range range = (Range)mMissing[i]; + if (range.Start >= fragment_end) + { + break; + } + if (range.End > fragment_offset) + { + + int copyStart = System.Math.Max(range.Start, fragment_offset); + int copyEnd = System.Math.Min(range.End, fragment_end); + int copyLength = copyEnd - copyStart; + + Array.Copy(buf, off + copyStart - fragment_offset, mBody, copyStart, + copyLength); + + if (copyStart == range.Start) + { + if (copyEnd == range.End) + { + mMissing.RemoveAt(i--); + } + else + { + range.Start = copyEnd; + } + } + else + { + if (copyEnd != range.End) + { + mMissing.Insert(++i, new Range(copyEnd, range.End)); + } + range.End = copyStart; + } + } + } + } + + internal void Reset() + { + this.mMissing.Clear(); + this.mMissing.Add(new Range(0, mBody.Length)); + } + + private class Range + { + private int mStart, mEnd; + + internal Range(int start, int end) + { + this.mStart = start; + this.mEnd = end; + } + + public int Start + { + get { return mStart; } + set { this.mStart = value; } + } + + public int End + { + get { return mEnd; } + set { this.mEnd = value; } + } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/DtlsRecordLayer.cs b/bc-sharp-crypto/src/crypto/tls/DtlsRecordLayer.cs new file mode 100644 index 0000000..39e0188 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/DtlsRecordLayer.cs @@ -0,0 +1,530 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Utilities.Date; + +namespace Org.BouncyCastle.Crypto.Tls +{ + internal class DtlsRecordLayer + : DatagramTransport + { + private const int RECORD_HEADER_LENGTH = 13; + private const int MAX_FRAGMENT_LENGTH = 1 << 14; + private const long TCP_MSL = 1000L * 60 * 2; + private const long RETRANSMIT_TIMEOUT = TCP_MSL * 2; + + private readonly DatagramTransport mTransport; + private readonly TlsContext mContext; + private readonly TlsPeer mPeer; + + private readonly ByteQueue mRecordQueue = new ByteQueue(); + + private volatile bool mClosed = false; + private volatile bool mFailed = false; + private volatile ProtocolVersion mReadVersion = null, mWriteVersion = null; + private volatile bool mInHandshake; + private volatile int mPlaintextLimit; + private DtlsEpoch mCurrentEpoch, mPendingEpoch; + private DtlsEpoch mReadEpoch, mWriteEpoch; + + private DtlsHandshakeRetransmit mRetransmit = null; + private DtlsEpoch mRetransmitEpoch = null; + private long mRetransmitExpiry = 0; + + internal DtlsRecordLayer(DatagramTransport transport, TlsContext context, TlsPeer peer, byte contentType) + { + this.mTransport = transport; + this.mContext = context; + this.mPeer = peer; + + this.mInHandshake = true; + + this.mCurrentEpoch = new DtlsEpoch(0, new TlsNullCipher(context)); + this.mPendingEpoch = null; + this.mReadEpoch = mCurrentEpoch; + this.mWriteEpoch = mCurrentEpoch; + + SetPlaintextLimit(MAX_FRAGMENT_LENGTH); + } + + internal virtual void SetPlaintextLimit(int plaintextLimit) + { + this.mPlaintextLimit = plaintextLimit; + } + + internal virtual int ReadEpoch + { + get { return mReadEpoch.Epoch; } + } + + internal virtual ProtocolVersion ReadVersion + { + get { return mReadVersion; } + set { this.mReadVersion = value; } + } + + internal virtual void SetWriteVersion(ProtocolVersion writeVersion) + { + this.mWriteVersion = writeVersion; + } + + internal virtual void InitPendingEpoch(TlsCipher pendingCipher) + { + if (mPendingEpoch != null) + throw new InvalidOperationException(); + + /* + * TODO "In order to ensure that any given sequence/epoch pair is unique, implementations + * MUST NOT allow the same epoch value to be reused within two times the TCP maximum segment + * lifetime." + */ + + // TODO Check for overflow + this.mPendingEpoch = new DtlsEpoch(mWriteEpoch.Epoch + 1, pendingCipher); + } + + internal virtual void HandshakeSuccessful(DtlsHandshakeRetransmit retransmit) + { + if (mReadEpoch == mCurrentEpoch || mWriteEpoch == mCurrentEpoch) + { + // TODO + throw new InvalidOperationException(); + } + + if (retransmit != null) + { + this.mRetransmit = retransmit; + this.mRetransmitEpoch = mCurrentEpoch; + this.mRetransmitExpiry = DateTimeUtilities.CurrentUnixMs() + RETRANSMIT_TIMEOUT; + } + + this.mInHandshake = false; + this.mCurrentEpoch = mPendingEpoch; + this.mPendingEpoch = null; + } + + internal virtual void ResetWriteEpoch() + { + if (mRetransmitEpoch != null) + { + this.mWriteEpoch = mRetransmitEpoch; + } + else + { + this.mWriteEpoch = mCurrentEpoch; + } + } + + public virtual int GetReceiveLimit() + { + return System.Math.Min(this.mPlaintextLimit, + mReadEpoch.Cipher.GetPlaintextLimit(mTransport.GetReceiveLimit() - RECORD_HEADER_LENGTH)); + } + + public virtual int GetSendLimit() + { + return System.Math.Min(this.mPlaintextLimit, + mWriteEpoch.Cipher.GetPlaintextLimit(mTransport.GetSendLimit() - RECORD_HEADER_LENGTH)); + } + + public virtual int Receive(byte[] buf, int off, int len, int waitMillis) + { + byte[] record = null; + + for (;;) + { + int receiveLimit = System.Math.Min(len, GetReceiveLimit()) + RECORD_HEADER_LENGTH; + if (record == null || record.Length < receiveLimit) + { + record = new byte[receiveLimit]; + } + + try + { + if (mRetransmit != null && DateTimeUtilities.CurrentUnixMs() > mRetransmitExpiry) + { + mRetransmit = null; + mRetransmitEpoch = null; + } + + int received = ReceiveRecord(record, 0, receiveLimit, waitMillis); + if (received < 0) + { + return received; + } + if (received < RECORD_HEADER_LENGTH) + { + continue; + } + int length = TlsUtilities.ReadUint16(record, 11); + if (received != (length + RECORD_HEADER_LENGTH)) + { + continue; + } + + byte type = TlsUtilities.ReadUint8(record, 0); + + // TODO Support user-specified custom protocols? + switch (type) + { + case ContentType.alert: + case ContentType.application_data: + case ContentType.change_cipher_spec: + case ContentType.handshake: + case ContentType.heartbeat: + break; + default: + // TODO Exception? + continue; + } + + int epoch = TlsUtilities.ReadUint16(record, 3); + + DtlsEpoch recordEpoch = null; + if (epoch == mReadEpoch.Epoch) + { + recordEpoch = mReadEpoch; + } + else if (type == ContentType.handshake && mRetransmitEpoch != null + && epoch == mRetransmitEpoch.Epoch) + { + recordEpoch = mRetransmitEpoch; + } + + if (recordEpoch == null) + { + continue; + } + + long seq = TlsUtilities.ReadUint48(record, 5); + if (recordEpoch.ReplayWindow.ShouldDiscard(seq)) + { + continue; + } + + ProtocolVersion version = TlsUtilities.ReadVersion(record, 1); + if (!version.IsDtls) + { + continue; + } + + if (mReadVersion != null && !mReadVersion.Equals(version)) + { + continue; + } + + byte[] plaintext = recordEpoch.Cipher.DecodeCiphertext( + GetMacSequenceNumber(recordEpoch.Epoch, seq), type, record, RECORD_HEADER_LENGTH, + received - RECORD_HEADER_LENGTH); + + recordEpoch.ReplayWindow.ReportAuthenticated(seq); + + if (plaintext.Length > this.mPlaintextLimit) + { + continue; + } + + if (mReadVersion == null) + { + mReadVersion = version; + } + + switch (type) + { + case ContentType.alert: + { + if (plaintext.Length == 2) + { + byte alertLevel = plaintext[0]; + byte alertDescription = plaintext[1]; + + mPeer.NotifyAlertReceived(alertLevel, alertDescription); + + if (alertLevel == AlertLevel.fatal) + { + Failed(); + throw new TlsFatalAlert(alertDescription); + } + + // TODO Can close_notify be a fatal alert? + if (alertDescription == AlertDescription.close_notify) + { + CloseTransport(); + } + } + + continue; + } + case ContentType.application_data: + { + if (mInHandshake) + { + // TODO Consider buffering application data for new epoch that arrives + // out-of-order with the Finished message + continue; + } + break; + } + case ContentType.change_cipher_spec: + { + // Implicitly receive change_cipher_spec and change to pending cipher state + + for (int i = 0; i < plaintext.Length; ++i) + { + byte message = TlsUtilities.ReadUint8(plaintext, i); + if (message != ChangeCipherSpec.change_cipher_spec) + { + continue; + } + + if (mPendingEpoch != null) + { + mReadEpoch = mPendingEpoch; + } + } + + continue; + } + case ContentType.handshake: + { + if (!mInHandshake) + { + if (mRetransmit != null) + { + mRetransmit.ReceivedHandshakeRecord(epoch, plaintext, 0, plaintext.Length); + } + + // TODO Consider support for HelloRequest + continue; + } + break; + } + case ContentType.heartbeat: + { + // TODO[RFC 6520] + continue; + } + } + + /* + * NOTE: If we receive any non-handshake data in the new epoch implies the peer has + * received our final flight. + */ + if (!mInHandshake && mRetransmit != null) + { + this.mRetransmit = null; + this.mRetransmitEpoch = null; + } + + Array.Copy(plaintext, 0, buf, off, plaintext.Length); + return plaintext.Length; + } + catch (IOException e) + { + // NOTE: Assume this is a timeout for the moment + throw e; + } + } + } + + /// + public virtual void Send(byte[] buf, int off, int len) + { + byte contentType = ContentType.application_data; + + if (this.mInHandshake || this.mWriteEpoch == this.mRetransmitEpoch) + { + contentType = ContentType.handshake; + + byte handshakeType = TlsUtilities.ReadUint8(buf, off); + if (handshakeType == HandshakeType.finished) + { + DtlsEpoch nextEpoch = null; + if (this.mInHandshake) + { + nextEpoch = mPendingEpoch; + } + else if (this.mWriteEpoch == this.mRetransmitEpoch) + { + nextEpoch = mCurrentEpoch; + } + + if (nextEpoch == null) + { + // TODO + throw new InvalidOperationException(); + } + + // Implicitly send change_cipher_spec and change to pending cipher state + + // TODO Send change_cipher_spec and finished records in single datagram? + byte[] data = new byte[]{ 1 }; + SendRecord(ContentType.change_cipher_spec, data, 0, data.Length); + + mWriteEpoch = nextEpoch; + } + } + + SendRecord(contentType, buf, off, len); + } + + public virtual void Close() + { + if (!mClosed) + { + if (mInHandshake) + { + Warn(AlertDescription.user_canceled, "User canceled handshake"); + } + CloseTransport(); + } + } + + internal virtual void Failed() + { + if (!mClosed) + { + mFailed = true; + + CloseTransport(); + } + } + + internal virtual void Fail(byte alertDescription) + { + if (!mClosed) + { + try + { + RaiseAlert(AlertLevel.fatal, alertDescription, null, null); + } + catch (Exception) + { + // Ignore + } + + mFailed = true; + + CloseTransport(); + } + } + + internal virtual void Warn(byte alertDescription, string message) + { + RaiseAlert(AlertLevel.warning, alertDescription, message, null); + } + + private void CloseTransport() + { + if (!mClosed) + { + /* + * RFC 5246 7.2.1. Unless some other fatal alert has been transmitted, each party is + * required to send a close_notify alert before closing the write side of the + * connection. The other party MUST respond with a close_notify alert of its own and + * close down the connection immediately, discarding any pending writes. + */ + + try + { + if (!mFailed) + { + Warn(AlertDescription.close_notify, null); + } + mTransport.Close(); + } + catch (Exception) + { + // Ignore + } + + mClosed = true; + } + } + + private void RaiseAlert(byte alertLevel, byte alertDescription, string message, Exception cause) + { + mPeer.NotifyAlertRaised(alertLevel, alertDescription, message, cause); + + byte[] error = new byte[2]; + error[0] = (byte)alertLevel; + error[1] = (byte)alertDescription; + + SendRecord(ContentType.alert, error, 0, 2); + } + + private int ReceiveRecord(byte[] buf, int off, int len, int waitMillis) + { + if (mRecordQueue.Available > 0) + { + int length = 0; + if (mRecordQueue.Available >= RECORD_HEADER_LENGTH) + { + byte[] lengthBytes = new byte[2]; + mRecordQueue.Read(lengthBytes, 0, 2, 11); + length = TlsUtilities.ReadUint16(lengthBytes, 0); + } + + int received = System.Math.Min(mRecordQueue.Available, RECORD_HEADER_LENGTH + length); + mRecordQueue.RemoveData(buf, off, received, 0); + return received; + } + + { + int received = mTransport.Receive(buf, off, len, waitMillis); + if (received >= RECORD_HEADER_LENGTH) + { + int fragmentLength = TlsUtilities.ReadUint16(buf, off + 11); + int recordLength = RECORD_HEADER_LENGTH + fragmentLength; + if (received > recordLength) + { + mRecordQueue.AddData(buf, off + recordLength, received - recordLength); + received = recordLength; + } + } + return received; + } + } + + private void SendRecord(byte contentType, byte[] buf, int off, int len) + { + // Never send anything until a valid ClientHello has been received + if (mWriteVersion == null) + return; + + if (len > this.mPlaintextLimit) + throw new TlsFatalAlert(AlertDescription.internal_error); + + /* + * RFC 5246 6.2.1 Implementations MUST NOT send zero-length fragments of Handshake, Alert, + * or ChangeCipherSpec content types. + */ + if (len < 1 && contentType != ContentType.application_data) + throw new TlsFatalAlert(AlertDescription.internal_error); + + int recordEpoch = mWriteEpoch.Epoch; + long recordSequenceNumber = mWriteEpoch.AllocateSequenceNumber(); + + byte[] ciphertext = mWriteEpoch.Cipher.EncodePlaintext( + GetMacSequenceNumber(recordEpoch, recordSequenceNumber), contentType, buf, off, len); + + // TODO Check the ciphertext length? + + byte[] record = new byte[ciphertext.Length + RECORD_HEADER_LENGTH]; + TlsUtilities.WriteUint8(contentType, record, 0); + ProtocolVersion version = mWriteVersion; + TlsUtilities.WriteVersion(version, record, 1); + TlsUtilities.WriteUint16(recordEpoch, record, 3); + TlsUtilities.WriteUint48(recordSequenceNumber, record, 5); + TlsUtilities.WriteUint16(ciphertext.Length, record, 11); + Array.Copy(ciphertext, 0, record, RECORD_HEADER_LENGTH, ciphertext.Length); + + mTransport.Send(record, 0, record.Length); + } + + private static long GetMacSequenceNumber(int epoch, long sequence_number) + { + return ((epoch & 0xFFFFFFFFL) << 48) | sequence_number; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/DtlsReliableHandshake.cs b/bc-sharp-crypto/src/crypto/tls/DtlsReliableHandshake.cs new file mode 100644 index 0000000..396ea74 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/DtlsReliableHandshake.cs @@ -0,0 +1,434 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + internal class DtlsReliableHandshake + { + private const int MaxReceiveAhead = 16; + private const int MessageHeaderLength = 12; + + private readonly DtlsRecordLayer mRecordLayer; + + private TlsHandshakeHash mHandshakeHash; + + private IDictionary mCurrentInboundFlight = Platform.CreateHashtable(); + private IDictionary mPreviousInboundFlight = null; + private IList mOutboundFlight = Platform.CreateArrayList(); + private bool mSending = true; + + private int mMessageSeq = 0, mNextReceiveSeq = 0; + + internal DtlsReliableHandshake(TlsContext context, DtlsRecordLayer transport) + { + this.mRecordLayer = transport; + this.mHandshakeHash = new DeferredHash(); + this.mHandshakeHash.Init(context); + } + + internal void NotifyHelloComplete() + { + this.mHandshakeHash = mHandshakeHash.NotifyPrfDetermined(); + } + + internal TlsHandshakeHash HandshakeHash + { + get { return mHandshakeHash; } + } + + internal TlsHandshakeHash PrepareToFinish() + { + TlsHandshakeHash result = mHandshakeHash; + this.mHandshakeHash = mHandshakeHash.StopTracking(); + return result; + } + + internal void SendMessage(byte msg_type, byte[] body) + { + TlsUtilities.CheckUint24(body.Length); + + if (!mSending) + { + CheckInboundFlight(); + mSending = true; + mOutboundFlight.Clear(); + } + + Message message = new Message(mMessageSeq++, msg_type, body); + + mOutboundFlight.Add(message); + + WriteMessage(message); + UpdateHandshakeMessagesDigest(message); + } + + internal byte[] ReceiveMessageBody(byte msg_type) + { + Message message = ReceiveMessage(); + if (message.Type != msg_type) + throw new TlsFatalAlert(AlertDescription.unexpected_message); + + return message.Body; + } + + internal Message ReceiveMessage() + { + if (mSending) + { + mSending = false; + PrepareInboundFlight(Platform.CreateHashtable()); + } + + byte[] buf = null; + + // TODO Check the conditions under which we should reset this + int readTimeoutMillis = 1000; + + for (;;) + { + try + { + for (;;) + { + Message pending = GetPendingMessage(); + if (pending != null) + return pending; + + int receiveLimit = mRecordLayer.GetReceiveLimit(); + if (buf == null || buf.Length < receiveLimit) + { + buf = new byte[receiveLimit]; + } + + int received = mRecordLayer.Receive(buf, 0, receiveLimit, readTimeoutMillis); + if (received < 0) + break; + + bool resentOutbound = ProcessRecord(MaxReceiveAhead, mRecordLayer.ReadEpoch, buf, 0, received); + if (resentOutbound) + { + readTimeoutMillis = BackOff(readTimeoutMillis); + } + } + } + catch (IOException e) + { + // NOTE: Assume this is a timeout for the moment + } + + ResendOutboundFlight(); + readTimeoutMillis = BackOff(readTimeoutMillis); + } + } + + internal void Finish() + { + DtlsHandshakeRetransmit retransmit = null; + if (!mSending) + { + CheckInboundFlight(); + } + else + { + PrepareInboundFlight(null); + + if (mPreviousInboundFlight != null) + { + /* + * RFC 6347 4.2.4. In addition, for at least twice the default MSL defined for [TCP], + * when in the FINISHED state, the node that transmits the last flight (the server in an + * ordinary handshake or the client in a resumed handshake) MUST respond to a retransmit + * of the peer's last flight with a retransmit of the last flight. + */ + retransmit = new Retransmit(this); + } + } + + mRecordLayer.HandshakeSuccessful(retransmit); + } + + internal void ResetHandshakeMessagesDigest() + { + mHandshakeHash.Reset(); + } + + private int BackOff(int timeoutMillis) + { + /* + * TODO[DTLS] implementations SHOULD back off handshake packet size during the + * retransmit backoff. + */ + return System.Math.Min(timeoutMillis * 2, 60000); + } + + /** + * Check that there are no "extra" messages left in the current inbound flight + */ + private void CheckInboundFlight() + { + foreach (int key in mCurrentInboundFlight.Keys) + { + if (key >= mNextReceiveSeq) + { + // TODO Should this be considered an error? + } + } + } + + private Message GetPendingMessage() + { + DtlsReassembler next = (DtlsReassembler)mCurrentInboundFlight[mNextReceiveSeq]; + if (next != null) + { + byte[] body = next.GetBodyIfComplete(); + if (body != null) + { + mPreviousInboundFlight = null; + return UpdateHandshakeMessagesDigest(new Message(mNextReceiveSeq++, next.MsgType, body)); + } + } + return null; + } + + private void PrepareInboundFlight(IDictionary nextFlight) + { + ResetAll(mCurrentInboundFlight); + mPreviousInboundFlight = mCurrentInboundFlight; + mCurrentInboundFlight = nextFlight; + } + + private bool ProcessRecord(int windowSize, int epoch, byte[] buf, int off, int len) + { + bool checkPreviousFlight = false; + + while (len >= MessageHeaderLength) + { + int fragment_length = TlsUtilities.ReadUint24(buf, off + 9); + int message_length = fragment_length + MessageHeaderLength; + if (len < message_length) + { + // NOTE: Truncated message - ignore it + break; + } + + int length = TlsUtilities.ReadUint24(buf, off + 1); + int fragment_offset = TlsUtilities.ReadUint24(buf, off + 6); + if (fragment_offset + fragment_length > length) + { + // NOTE: Malformed fragment - ignore it and the rest of the record + break; + } + + /* + * NOTE: This very simple epoch check will only work until we want to support + * renegotiation (and we're not likely to do that anyway). + */ + byte msg_type = TlsUtilities.ReadUint8(buf, off + 0); + int expectedEpoch = msg_type == HandshakeType.finished ? 1 : 0; + if (epoch != expectedEpoch) + { + break; + } + + int message_seq = TlsUtilities.ReadUint16(buf, off + 4); + if (message_seq >= (mNextReceiveSeq + windowSize)) + { + // NOTE: Too far ahead - ignore + } + else if (message_seq >= mNextReceiveSeq) + { + DtlsReassembler reassembler = (DtlsReassembler)mCurrentInboundFlight[message_seq]; + if (reassembler == null) + { + reassembler = new DtlsReassembler(msg_type, length); + mCurrentInboundFlight[message_seq] = reassembler; + } + + reassembler.ContributeFragment(msg_type, length, buf, off + MessageHeaderLength, fragment_offset, + fragment_length); + } + else if (mPreviousInboundFlight != null) + { + /* + * NOTE: If we receive the previous flight of incoming messages in full again, + * retransmit our last flight + */ + + DtlsReassembler reassembler = (DtlsReassembler)mPreviousInboundFlight[message_seq]; + if (reassembler != null) + { + reassembler.ContributeFragment(msg_type, length, buf, off + MessageHeaderLength, fragment_offset, + fragment_length); + checkPreviousFlight = true; + } + } + + off += message_length; + len -= message_length; + } + + bool result = checkPreviousFlight && CheckAll(mPreviousInboundFlight); + if (result) + { + ResendOutboundFlight(); + ResetAll(mPreviousInboundFlight); + } + return result; + } + + private void ResendOutboundFlight() + { + mRecordLayer.ResetWriteEpoch(); + for (int i = 0; i < mOutboundFlight.Count; ++i) + { + WriteMessage((Message)mOutboundFlight[i]); + } + } + + private Message UpdateHandshakeMessagesDigest(Message message) + { + if (message.Type != HandshakeType.hello_request) + { + byte[] body = message.Body; + byte[] buf = new byte[MessageHeaderLength]; + TlsUtilities.WriteUint8(message.Type, buf, 0); + TlsUtilities.WriteUint24(body.Length, buf, 1); + TlsUtilities.WriteUint16(message.Seq, buf, 4); + TlsUtilities.WriteUint24(0, buf, 6); + TlsUtilities.WriteUint24(body.Length, buf, 9); + mHandshakeHash.BlockUpdate(buf, 0, buf.Length); + mHandshakeHash.BlockUpdate(body, 0, body.Length); + } + return message; + } + + private void WriteMessage(Message message) + { + int sendLimit = mRecordLayer.GetSendLimit(); + int fragmentLimit = sendLimit - MessageHeaderLength; + + // TODO Support a higher minimum fragment size? + if (fragmentLimit < 1) + { + // TODO Should we be throwing an exception here? + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + int length = message.Body.Length; + + // NOTE: Must still send a fragment if body is empty + int fragment_offset = 0; + do + { + int fragment_length = System.Math.Min(length - fragment_offset, fragmentLimit); + WriteHandshakeFragment(message, fragment_offset, fragment_length); + fragment_offset += fragment_length; + } + while (fragment_offset < length); + } + + private void WriteHandshakeFragment(Message message, int fragment_offset, int fragment_length) + { + RecordLayerBuffer fragment = new RecordLayerBuffer(MessageHeaderLength + fragment_length); + TlsUtilities.WriteUint8(message.Type, fragment); + TlsUtilities.WriteUint24(message.Body.Length, fragment); + TlsUtilities.WriteUint16(message.Seq, fragment); + TlsUtilities.WriteUint24(fragment_offset, fragment); + TlsUtilities.WriteUint24(fragment_length, fragment); + fragment.Write(message.Body, fragment_offset, fragment_length); + + fragment.SendToRecordLayer(mRecordLayer); + } + + private static bool CheckAll(IDictionary inboundFlight) + { + foreach (DtlsReassembler r in inboundFlight.Values) + { + if (r.GetBodyIfComplete() == null) + { + return false; + } + } + return true; + } + + private static void ResetAll(IDictionary inboundFlight) + { + foreach (DtlsReassembler r in inboundFlight.Values) + { + r.Reset(); + } + } + + internal class Message + { + private readonly int mMessageSeq; + private readonly byte mMsgType; + private readonly byte[] mBody; + + internal Message(int message_seq, byte msg_type, byte[] body) + { + this.mMessageSeq = message_seq; + this.mMsgType = msg_type; + this.mBody = body; + } + + public int Seq + { + get { return mMessageSeq; } + } + + public byte Type + { + get { return mMsgType; } + } + + public byte[] Body + { + get { return mBody; } + } + } + + internal class RecordLayerBuffer + : MemoryStream + { + internal RecordLayerBuffer(int size) + : base(size) + { + } + + internal void SendToRecordLayer(DtlsRecordLayer recordLayer) + { +#if PORTABLE + byte[] buf = ToArray(); + int bufLen = buf.Length; +#else + byte[] buf = GetBuffer(); + int bufLen = (int)Length; +#endif + + recordLayer.Send(buf, 0, bufLen); + Platform.Dispose(this); + } + } + + internal class Retransmit + : DtlsHandshakeRetransmit + { + private readonly DtlsReliableHandshake mOuter; + + internal Retransmit(DtlsReliableHandshake outer) + { + this.mOuter = outer; + } + + public void ReceivedHandshakeRecord(int epoch, byte[] buf, int off, int len) + { + mOuter.ProcessRecord(0, epoch, buf, off, len); + } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/DtlsReplayWindow.cs b/bc-sharp-crypto/src/crypto/tls/DtlsReplayWindow.cs new file mode 100644 index 0000000..ea18e80 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/DtlsReplayWindow.cs @@ -0,0 +1,85 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /** + * RFC 4347 4.1.2.5 Anti-replay + *

+ * Support fast rejection of duplicate records by maintaining a sliding receive window + */ + internal class DtlsReplayWindow + { + private const long VALID_SEQ_MASK = 0x0000FFFFFFFFFFFFL; + + private const long WINDOW_SIZE = 64L; + + private long mLatestConfirmedSeq = -1; + private long mBitmap = 0; + + /** + * Check whether a received record with the given sequence number should be rejected as a duplicate. + * + * @param seq the 48-bit DTLSPlainText.sequence_number field of a received record. + * @return true if the record should be discarded without further processing. + */ + internal bool ShouldDiscard(long seq) + { + if ((seq & VALID_SEQ_MASK) != seq) + return true; + + if (seq <= mLatestConfirmedSeq) + { + long diff = mLatestConfirmedSeq - seq; + if (diff >= WINDOW_SIZE) + return true; + if ((mBitmap & (1L << (int)diff)) != 0) + return true; + } + + return false; + } + + /** + * Report that a received record with the given sequence number passed authentication checks. + * + * @param seq the 48-bit DTLSPlainText.sequence_number field of an authenticated record. + */ + internal void ReportAuthenticated(long seq) + { + if ((seq & VALID_SEQ_MASK) != seq) + throw new ArgumentException("out of range", "seq"); + + if (seq <= mLatestConfirmedSeq) + { + long diff = mLatestConfirmedSeq - seq; + if (diff < WINDOW_SIZE) + { + mBitmap |= (1L << (int)diff); + } + } + else + { + long diff = seq - mLatestConfirmedSeq; + if (diff >= WINDOW_SIZE) + { + mBitmap = 1; + } + else + { + mBitmap <<= (int)diff; + mBitmap |= 1; + } + mLatestConfirmedSeq = seq; + } + } + + /** + * When a new epoch begins, sequence numbers begin again at 0 + */ + internal void Reset() + { + mLatestConfirmedSeq = -1; + mBitmap = 0; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/DtlsServerProtocol.cs b/bc-sharp-crypto/src/crypto/tls/DtlsServerProtocol.cs new file mode 100644 index 0000000..3032269 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/DtlsServerProtocol.cs @@ -0,0 +1,696 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class DtlsServerProtocol + : DtlsProtocol + { + protected bool mVerifyRequests = true; + + public DtlsServerProtocol(SecureRandom secureRandom) + : base(secureRandom) + { + } + + public virtual bool VerifyRequests + { + get { return mVerifyRequests; } + set { this.mVerifyRequests = value; } + } + + public virtual DtlsTransport Accept(TlsServer server, DatagramTransport transport) + { + if (server == null) + throw new ArgumentNullException("server"); + if (transport == null) + throw new ArgumentNullException("transport"); + + SecurityParameters securityParameters = new SecurityParameters(); + securityParameters.entity = ConnectionEnd.server; + + ServerHandshakeState state = new ServerHandshakeState(); + state.server = server; + state.serverContext = new TlsServerContextImpl(mSecureRandom, securityParameters); + + securityParameters.serverRandom = TlsProtocol.CreateRandomBlock(server.ShouldUseGmtUnixTime(), + state.serverContext.NonceRandomGenerator); + + server.Init(state.serverContext); + + DtlsRecordLayer recordLayer = new DtlsRecordLayer(transport, state.serverContext, server, ContentType.handshake); + + // TODO Need to handle sending of HelloVerifyRequest without entering a full connection + + try + { + return ServerHandshake(state, recordLayer); + } + catch (TlsFatalAlert fatalAlert) + { + AbortServerHandshake(state, recordLayer, fatalAlert.AlertDescription); + throw fatalAlert; + } + catch (IOException e) + { + AbortServerHandshake(state, recordLayer, AlertDescription.internal_error); + throw e; + } + catch (Exception e) + { + AbortServerHandshake(state, recordLayer, AlertDescription.internal_error); + throw new TlsFatalAlert(AlertDescription.internal_error, e); + } + finally + { + securityParameters.Clear(); + } + } + + internal virtual void AbortServerHandshake(ServerHandshakeState state, DtlsRecordLayer recordLayer, byte alertDescription) + { + recordLayer.Fail(alertDescription); + InvalidateSession(state); + } + + internal virtual DtlsTransport ServerHandshake(ServerHandshakeState state, DtlsRecordLayer recordLayer) + { + SecurityParameters securityParameters = state.serverContext.SecurityParameters; + DtlsReliableHandshake handshake = new DtlsReliableHandshake(state.serverContext, recordLayer); + + DtlsReliableHandshake.Message clientMessage = handshake.ReceiveMessage(); + + // NOTE: DTLSRecordLayer requires any DTLS version, we don't otherwise constrain this + //ProtocolVersion recordLayerVersion = recordLayer.ReadVersion; + + if (clientMessage.Type == HandshakeType.client_hello) + { + ProcessClientHello(state, clientMessage.Body); + } + else + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + { + byte[] serverHelloBody = GenerateServerHello(state); + + ApplyMaxFragmentLengthExtension(recordLayer, securityParameters.maxFragmentLength); + + ProtocolVersion recordLayerVersion = state.serverContext.ServerVersion; + recordLayer.ReadVersion = recordLayerVersion; + recordLayer.SetWriteVersion(recordLayerVersion); + + handshake.SendMessage(HandshakeType.server_hello, serverHelloBody); + } + + handshake.NotifyHelloComplete(); + + IList serverSupplementalData = state.server.GetServerSupplementalData(); + if (serverSupplementalData != null) + { + byte[] supplementalDataBody = GenerateSupplementalData(serverSupplementalData); + handshake.SendMessage(HandshakeType.supplemental_data, supplementalDataBody); + } + + state.keyExchange = state.server.GetKeyExchange(); + state.keyExchange.Init(state.serverContext); + + state.serverCredentials = state.server.GetCredentials(); + + Certificate serverCertificate = null; + + if (state.serverCredentials == null) + { + state.keyExchange.SkipServerCredentials(); + } + else + { + state.keyExchange.ProcessServerCredentials(state.serverCredentials); + + serverCertificate = state.serverCredentials.Certificate; + byte[] certificateBody = GenerateCertificate(serverCertificate); + handshake.SendMessage(HandshakeType.certificate, certificateBody); + } + + // TODO[RFC 3546] Check whether empty certificates is possible, allowed, or excludes CertificateStatus + if (serverCertificate == null || serverCertificate.IsEmpty) + { + state.allowCertificateStatus = false; + } + + if (state.allowCertificateStatus) + { + CertificateStatus certificateStatus = state.server.GetCertificateStatus(); + if (certificateStatus != null) + { + byte[] certificateStatusBody = GenerateCertificateStatus(state, certificateStatus); + handshake.SendMessage(HandshakeType.certificate_status, certificateStatusBody); + } + } + + byte[] serverKeyExchange = state.keyExchange.GenerateServerKeyExchange(); + if (serverKeyExchange != null) + { + handshake.SendMessage(HandshakeType.server_key_exchange, serverKeyExchange); + } + + if (state.serverCredentials != null) + { + state.certificateRequest = state.server.GetCertificateRequest(); + if (state.certificateRequest != null) + { + if (TlsUtilities.IsTlsV12(state.serverContext) != (state.certificateRequest.SupportedSignatureAlgorithms != null)) + throw new TlsFatalAlert(AlertDescription.internal_error); + + state.keyExchange.ValidateCertificateRequest(state.certificateRequest); + + byte[] certificateRequestBody = GenerateCertificateRequest(state, state.certificateRequest); + handshake.SendMessage(HandshakeType.certificate_request, certificateRequestBody); + + TlsUtilities.TrackHashAlgorithms(handshake.HandshakeHash, + state.certificateRequest.SupportedSignatureAlgorithms); + } + } + + handshake.SendMessage(HandshakeType.server_hello_done, TlsUtilities.EmptyBytes); + + handshake.HandshakeHash.SealHashAlgorithms(); + + clientMessage = handshake.ReceiveMessage(); + + if (clientMessage.Type == HandshakeType.supplemental_data) + { + ProcessClientSupplementalData(state, clientMessage.Body); + clientMessage = handshake.ReceiveMessage(); + } + else + { + state.server.ProcessClientSupplementalData(null); + } + + if (state.certificateRequest == null) + { + state.keyExchange.SkipClientCredentials(); + } + else + { + if (clientMessage.Type == HandshakeType.certificate) + { + ProcessClientCertificate(state, clientMessage.Body); + clientMessage = handshake.ReceiveMessage(); + } + else + { + if (TlsUtilities.IsTlsV12(state.serverContext)) + { + /* + * RFC 5246 If no suitable certificate is available, the client MUST send a + * certificate message containing no certificates. + * + * NOTE: In previous RFCs, this was SHOULD instead of MUST. + */ + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + NotifyClientCertificate(state, Certificate.EmptyChain); + } + } + + if (clientMessage.Type == HandshakeType.client_key_exchange) + { + ProcessClientKeyExchange(state, clientMessage.Body); + } + else + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + TlsHandshakeHash prepareFinishHash = handshake.PrepareToFinish(); + securityParameters.sessionHash = TlsProtocol.GetCurrentPrfHash(state.serverContext, prepareFinishHash, null); + + TlsProtocol.EstablishMasterSecret(state.serverContext, state.keyExchange); + recordLayer.InitPendingEpoch(state.server.GetCipher()); + + /* + * RFC 5246 7.4.8 This message is only sent following a client certificate that has signing + * capability (i.e., all certificates except those containing fixed Diffie-Hellman + * parameters). + */ + if (ExpectCertificateVerifyMessage(state)) + { + byte[] certificateVerifyBody = handshake.ReceiveMessageBody(HandshakeType.certificate_verify); + ProcessCertificateVerify(state, certificateVerifyBody, prepareFinishHash); + } + + // NOTE: Calculated exclusive of the actual Finished message from the client + byte[] expectedClientVerifyData = TlsUtilities.CalculateVerifyData(state.serverContext, ExporterLabel.client_finished, + TlsProtocol.GetCurrentPrfHash(state.serverContext, handshake.HandshakeHash, null)); + ProcessFinished(handshake.ReceiveMessageBody(HandshakeType.finished), expectedClientVerifyData); + + if (state.expectSessionTicket) + { + NewSessionTicket newSessionTicket = state.server.GetNewSessionTicket(); + byte[] newSessionTicketBody = GenerateNewSessionTicket(state, newSessionTicket); + handshake.SendMessage(HandshakeType.session_ticket, newSessionTicketBody); + } + + // NOTE: Calculated exclusive of the Finished message itself + byte[] serverVerifyData = TlsUtilities.CalculateVerifyData(state.serverContext, ExporterLabel.server_finished, + TlsProtocol.GetCurrentPrfHash(state.serverContext, handshake.HandshakeHash, null)); + handshake.SendMessage(HandshakeType.finished, serverVerifyData); + + handshake.Finish(); + + state.server.NotifyHandshakeComplete(); + + return new DtlsTransport(recordLayer); + } + + protected virtual void InvalidateSession(ServerHandshakeState state) + { + if (state.sessionParameters != null) + { + state.sessionParameters.Clear(); + state.sessionParameters = null; + } + + if (state.tlsSession != null) + { + state.tlsSession.Invalidate(); + state.tlsSession = null; + } + } + + protected virtual byte[] GenerateCertificateRequest(ServerHandshakeState state, CertificateRequest certificateRequest) + { + MemoryStream buf = new MemoryStream(); + certificateRequest.Encode(buf); + return buf.ToArray(); + } + + protected virtual byte[] GenerateCertificateStatus(ServerHandshakeState state, CertificateStatus certificateStatus) + { + MemoryStream buf = new MemoryStream(); + certificateStatus.Encode(buf); + return buf.ToArray(); + } + + protected virtual byte[] GenerateNewSessionTicket(ServerHandshakeState state, NewSessionTicket newSessionTicket) + { + MemoryStream buf = new MemoryStream(); + newSessionTicket.Encode(buf); + return buf.ToArray(); + } + + protected virtual byte[] GenerateServerHello(ServerHandshakeState state) + { + SecurityParameters securityParameters = state.serverContext.SecurityParameters; + + MemoryStream buf = new MemoryStream(); + + { + ProtocolVersion server_version = state.server.GetServerVersion(); + if (!server_version.IsEqualOrEarlierVersionOf(state.serverContext.ClientVersion)) + throw new TlsFatalAlert(AlertDescription.internal_error); + + // TODO Read RFCs for guidance on the expected record layer version number + // recordStream.setReadVersion(server_version); + // recordStream.setWriteVersion(server_version); + // recordStream.setRestrictReadVersion(true); + state.serverContext.SetServerVersion(server_version); + + TlsUtilities.WriteVersion(state.serverContext.ServerVersion, buf); + } + + buf.Write(securityParameters.ServerRandom, 0, securityParameters.ServerRandom.Length); + + /* + * The server may return an empty session_id to indicate that the session will not be cached + * and therefore cannot be resumed. + */ + TlsUtilities.WriteOpaque8(TlsUtilities.EmptyBytes, buf); + + int selectedCipherSuite = state.server.GetSelectedCipherSuite(); + if (!Arrays.Contains(state.offeredCipherSuites, selectedCipherSuite) + || selectedCipherSuite == CipherSuite.TLS_NULL_WITH_NULL_NULL + || CipherSuite.IsScsv(selectedCipherSuite) + || !TlsUtilities.IsValidCipherSuiteForVersion(selectedCipherSuite, state.serverContext.ServerVersion)) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + ValidateSelectedCipherSuite(selectedCipherSuite, AlertDescription.internal_error); + securityParameters.cipherSuite = selectedCipherSuite; + + byte selectedCompressionMethod = state.server.GetSelectedCompressionMethod(); + if (!Arrays.Contains(state.offeredCompressionMethods, selectedCompressionMethod)) + throw new TlsFatalAlert(AlertDescription.internal_error); + securityParameters.compressionAlgorithm = selectedCompressionMethod; + + TlsUtilities.WriteUint16(selectedCipherSuite, buf); + TlsUtilities.WriteUint8(selectedCompressionMethod, buf); + + state.serverExtensions = state.server.GetServerExtensions(); + + /* + * RFC 5746 3.6. Server Behavior: Initial Handshake + */ + if (state.secure_renegotiation) + { + byte[] renegExtData = TlsUtilities.GetExtensionData(state.serverExtensions, ExtensionType.renegotiation_info); + bool noRenegExt = (null == renegExtData); + + if (noRenegExt) + { + /* + * Note that sending a "renegotiation_info" extension in response to a ClientHello + * containing only the SCSV is an explicit exception to the prohibition in RFC 5246, + * Section 7.4.1.4, on the server sending unsolicited extensions and is only allowed + * because the client is signaling its willingness to receive the extension via the + * TLS_EMPTY_RENEGOTIATION_INFO_SCSV SCSV. + */ + + /* + * If the secure_renegotiation flag is set to TRUE, the server MUST include an empty + * "renegotiation_info" extension in the ServerHello message. + */ + state.serverExtensions = TlsExtensionsUtilities.EnsureExtensionsInitialised(state.serverExtensions); + state.serverExtensions[ExtensionType.renegotiation_info] = TlsProtocol.CreateRenegotiationInfo(TlsUtilities.EmptyBytes); + } + } + + if (securityParameters.extendedMasterSecret) + { + state.serverExtensions = TlsExtensionsUtilities.EnsureExtensionsInitialised(state.serverExtensions); + TlsExtensionsUtilities.AddExtendedMasterSecretExtension(state.serverExtensions); + } + + /* + * TODO RFC 3546 2.3 If [...] the older session is resumed, then the server MUST ignore + * extensions appearing in the client hello, and send a server hello containing no + * extensions. + */ + + if (state.serverExtensions != null) + { + securityParameters.encryptThenMac = TlsExtensionsUtilities.HasEncryptThenMacExtension(state.serverExtensions); + + securityParameters.maxFragmentLength = EvaluateMaxFragmentLengthExtension(state.resumedSession, + state.clientExtensions, state.serverExtensions, AlertDescription.internal_error); + + securityParameters.truncatedHMac = TlsExtensionsUtilities.HasTruncatedHMacExtension(state.serverExtensions); + + /* + * TODO It's surprising that there's no provision to allow a 'fresh' CertificateStatus to be sent in + * a session resumption handshake. + */ + state.allowCertificateStatus = !state.resumedSession + && TlsUtilities.HasExpectedEmptyExtensionData(state.serverExtensions, ExtensionType.status_request, + AlertDescription.internal_error); + + state.expectSessionTicket = !state.resumedSession + && TlsUtilities.HasExpectedEmptyExtensionData(state.serverExtensions, ExtensionType.session_ticket, + AlertDescription.internal_error); + + TlsProtocol.WriteExtensions(buf, state.serverExtensions); + } + + securityParameters.prfAlgorithm = TlsProtocol.GetPrfAlgorithm(state.serverContext, + securityParameters.CipherSuite); + + /* + * RFC 5246 7.4.9. Any cipher suite which does not explicitly specify verify_data_length + * has a verify_data_length equal to 12. This includes all existing cipher suites. + */ + securityParameters.verifyDataLength = 12; + + return buf.ToArray(); + } + + protected virtual void NotifyClientCertificate(ServerHandshakeState state, Certificate clientCertificate) + { + if (state.certificateRequest == null) + throw new InvalidOperationException(); + + if (state.clientCertificate != null) + throw new TlsFatalAlert(AlertDescription.unexpected_message); + + state.clientCertificate = clientCertificate; + + if (clientCertificate.IsEmpty) + { + state.keyExchange.SkipClientCredentials(); + } + else + { + + /* + * TODO RFC 5246 7.4.6. If the certificate_authorities list in the certificate request + * message was non-empty, one of the certificates in the certificate chain SHOULD be + * issued by one of the listed CAs. + */ + + state.clientCertificateType = TlsUtilities.GetClientCertificateType(clientCertificate, + state.serverCredentials.Certificate); + + state.keyExchange.ProcessClientCertificate(clientCertificate); + } + + /* + * RFC 5246 7.4.6. If the client does not send any certificates, the server MAY at its + * discretion either continue the handshake without client authentication, or respond with a + * fatal handshake_failure alert. Also, if some aspect of the certificate chain was + * unacceptable (e.g., it was not signed by a known, trusted CA), the server MAY at its + * discretion either continue the handshake (considering the client unauthenticated) or send + * a fatal alert. + */ + state.server.NotifyClientCertificate(clientCertificate); + } + + protected virtual void ProcessClientCertificate(ServerHandshakeState state, byte[] body) + { + MemoryStream buf = new MemoryStream(body, false); + + Certificate clientCertificate = Certificate.Parse(buf); + + TlsProtocol.AssertEmpty(buf); + + NotifyClientCertificate(state, clientCertificate); + } + + protected virtual void ProcessCertificateVerify(ServerHandshakeState state, byte[] body, TlsHandshakeHash prepareFinishHash) + { + if (state.certificateRequest == null) + throw new InvalidOperationException(); + + MemoryStream buf = new MemoryStream(body, false); + + TlsServerContextImpl context = state.serverContext; + DigitallySigned clientCertificateVerify = DigitallySigned.Parse(context, buf); + + TlsProtocol.AssertEmpty(buf); + + // Verify the CertificateVerify message contains a correct signature. + try + { + SignatureAndHashAlgorithm signatureAlgorithm = clientCertificateVerify.Algorithm; + + byte[] hash; + if (TlsUtilities.IsTlsV12(context)) + { + TlsUtilities.VerifySupportedSignatureAlgorithm(state.certificateRequest.SupportedSignatureAlgorithms, signatureAlgorithm); + hash = prepareFinishHash.GetFinalHash(signatureAlgorithm.Hash); + } + else + { + hash = context.SecurityParameters.SessionHash; + } + + X509CertificateStructure x509Cert = state.clientCertificate.GetCertificateAt(0); + SubjectPublicKeyInfo keyInfo = x509Cert.SubjectPublicKeyInfo; + AsymmetricKeyParameter publicKey = PublicKeyFactory.CreateKey(keyInfo); + + TlsSigner tlsSigner = TlsUtilities.CreateTlsSigner((byte)state.clientCertificateType); + tlsSigner.Init(context); + if (!tlsSigner.VerifyRawSignature(signatureAlgorithm, clientCertificateVerify.Signature, publicKey, hash)) + throw new TlsFatalAlert(AlertDescription.decrypt_error); + } + catch (TlsFatalAlert e) + { + throw e; + } + catch (Exception e) + { + throw new TlsFatalAlert(AlertDescription.decrypt_error, e); + } + } + + protected virtual void ProcessClientHello(ServerHandshakeState state, byte[] body) + { + MemoryStream buf = new MemoryStream(body, false); + + // TODO Read RFCs for guidance on the expected record layer version number + ProtocolVersion client_version = TlsUtilities.ReadVersion(buf); + if (!client_version.IsDtls) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + + /* + * Read the client random + */ + byte[] client_random = TlsUtilities.ReadFully(32, buf); + + byte[] sessionID = TlsUtilities.ReadOpaque8(buf); + if (sessionID.Length > 32) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + + // TODO RFC 4347 has the cookie length restricted to 32, but not in RFC 6347 + byte[] cookie = TlsUtilities.ReadOpaque8(buf); + + int cipher_suites_length = TlsUtilities.ReadUint16(buf); + if (cipher_suites_length < 2 || (cipher_suites_length & 1) != 0) + { + throw new TlsFatalAlert(AlertDescription.decode_error); + } + + /* + * NOTE: "If the session_id field is not empty (implying a session resumption request) this + * vector must include at least the cipher_suite from that session." + */ + state.offeredCipherSuites = TlsUtilities.ReadUint16Array(cipher_suites_length / 2, buf); + + int compression_methods_length = TlsUtilities.ReadUint8(buf); + if (compression_methods_length < 1) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + + state.offeredCompressionMethods = TlsUtilities.ReadUint8Array(compression_methods_length, buf); + + /* + * TODO RFC 3546 2.3 If [...] the older session is resumed, then the server MUST ignore + * extensions appearing in the client hello, and send a server hello containing no + * extensions. + */ + state.clientExtensions = TlsProtocol.ReadExtensions(buf); + + TlsServerContextImpl context = state.serverContext; + SecurityParameters securityParameters = context.SecurityParameters; + + /* + * TODO[session-hash] + * + * draft-ietf-tls-session-hash-04 4. Clients and servers SHOULD NOT accept handshakes + * that do not use the extended master secret [..]. (and see 5.2, 5.3) + */ + securityParameters.extendedMasterSecret = TlsExtensionsUtilities.HasExtendedMasterSecretExtension(state.clientExtensions); + + context.SetClientVersion(client_version); + + state.server.NotifyClientVersion(client_version); + state.server.NotifyFallback(Arrays.Contains(state.offeredCipherSuites, CipherSuite.TLS_FALLBACK_SCSV)); + + securityParameters.clientRandom = client_random; + + state.server.NotifyOfferedCipherSuites(state.offeredCipherSuites); + state.server.NotifyOfferedCompressionMethods(state.offeredCompressionMethods); + + /* + * RFC 5746 3.6. Server Behavior: Initial Handshake + */ + { + /* + * RFC 5746 3.4. The client MUST include either an empty "renegotiation_info" extension, + * or the TLS_EMPTY_RENEGOTIATION_INFO_SCSV signaling cipher suite value in the + * ClientHello. Including both is NOT RECOMMENDED. + */ + + /* + * When a ClientHello is received, the server MUST check if it includes the + * TLS_EMPTY_RENEGOTIATION_INFO_SCSV SCSV. If it does, set the secure_renegotiation flag + * to TRUE. + */ + if (Arrays.Contains(state.offeredCipherSuites, CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV)) + { + state.secure_renegotiation = true; + } + + /* + * The server MUST check if the "renegotiation_info" extension is included in the + * ClientHello. + */ + byte[] renegExtData = TlsUtilities.GetExtensionData(state.clientExtensions, ExtensionType.renegotiation_info); + if (renegExtData != null) + { + /* + * If the extension is present, set secure_renegotiation flag to TRUE. The + * server MUST then verify that the length of the "renegotiated_connection" + * field is zero, and if it is not, MUST abort the handshake. + */ + state.secure_renegotiation = true; + + if (!Arrays.ConstantTimeAreEqual(renegExtData, TlsProtocol.CreateRenegotiationInfo(TlsUtilities.EmptyBytes))) + throw new TlsFatalAlert(AlertDescription.handshake_failure); + } + } + + state.server.NotifySecureRenegotiation(state.secure_renegotiation); + + if (state.clientExtensions != null) + { + // NOTE: Validates the padding extension data, if present + TlsExtensionsUtilities.GetPaddingExtension(state.clientExtensions); + + state.server.ProcessClientExtensions(state.clientExtensions); + } + } + + protected virtual void ProcessClientKeyExchange(ServerHandshakeState state, byte[] body) + { + MemoryStream buf = new MemoryStream(body, false); + + state.keyExchange.ProcessClientKeyExchange(buf); + + TlsProtocol.AssertEmpty(buf); + } + + protected virtual void ProcessClientSupplementalData(ServerHandshakeState state, byte[] body) + { + MemoryStream buf = new MemoryStream(body, false); + IList clientSupplementalData = TlsProtocol.ReadSupplementalDataMessage(buf); + state.server.ProcessClientSupplementalData(clientSupplementalData); + } + + protected virtual bool ExpectCertificateVerifyMessage(ServerHandshakeState state) + { + return state.clientCertificateType >= 0 && TlsUtilities.HasSigningCapability((byte)state.clientCertificateType); + } + + protected internal class ServerHandshakeState + { + internal TlsServer server = null; + internal TlsServerContextImpl serverContext = null; + internal TlsSession tlsSession = null; + internal SessionParameters sessionParameters = null; + internal SessionParameters.Builder sessionParametersBuilder = null; + internal int[] offeredCipherSuites = null; + internal byte[] offeredCompressionMethods = null; + internal IDictionary clientExtensions = null; + internal IDictionary serverExtensions = null; + internal bool resumedSession = false; + internal bool secure_renegotiation = false; + internal bool allowCertificateStatus = false; + internal bool expectSessionTicket = false; + internal TlsKeyExchange keyExchange = null; + internal TlsCredentials serverCredentials = null; + internal CertificateRequest certificateRequest = null; + internal short clientCertificateType = -1; + internal Certificate clientCertificate = null; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/DtlsTransport.cs b/bc-sharp-crypto/src/crypto/tls/DtlsTransport.cs new file mode 100644 index 0000000..5c60733 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/DtlsTransport.cs @@ -0,0 +1,77 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class DtlsTransport + : DatagramTransport + { + private readonly DtlsRecordLayer mRecordLayer; + + internal DtlsTransport(DtlsRecordLayer recordLayer) + { + this.mRecordLayer = recordLayer; + } + + public virtual int GetReceiveLimit() + { + return mRecordLayer.GetReceiveLimit(); + } + + public virtual int GetSendLimit() + { + return mRecordLayer.GetSendLimit(); + } + + public virtual int Receive(byte[] buf, int off, int len, int waitMillis) + { + try + { + return mRecordLayer.Receive(buf, off, len, waitMillis); + } + catch (TlsFatalAlert fatalAlert) + { + mRecordLayer.Fail(fatalAlert.AlertDescription); + throw fatalAlert; + } + catch (IOException e) + { + mRecordLayer.Fail(AlertDescription.internal_error); + throw e; + } + catch (Exception e) + { + mRecordLayer.Fail(AlertDescription.internal_error); + throw new TlsFatalAlert(AlertDescription.internal_error, e); + } + } + + public virtual void Send(byte[] buf, int off, int len) + { + try + { + mRecordLayer.Send(buf, off, len); + } + catch (TlsFatalAlert fatalAlert) + { + mRecordLayer.Fail(fatalAlert.AlertDescription); + throw fatalAlert; + } + catch (IOException e) + { + mRecordLayer.Fail(AlertDescription.internal_error); + throw e; + } + catch (Exception e) + { + mRecordLayer.Fail(AlertDescription.internal_error); + throw new TlsFatalAlert(AlertDescription.internal_error, e); + } + } + + public virtual void Close() + { + mRecordLayer.Close(); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/ECBasisType.cs b/bc-sharp-crypto/src/crypto/tls/ECBasisType.cs new file mode 100644 index 0000000..5416e17 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/ECBasisType.cs @@ -0,0 +1,16 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + ///

RFC 4492 5.4. (Errata ID: 2389) + public abstract class ECBasisType + { + public const byte ec_basis_trinomial = 1; + public const byte ec_basis_pentanomial = 2; + + public static bool IsValid(byte ecBasisType) + { + return ecBasisType >= ec_basis_trinomial && ecBasisType <= ec_basis_pentanomial; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/ECCurveType.cs b/bc-sharp-crypto/src/crypto/tls/ECCurveType.cs new file mode 100644 index 0000000..1b352e9 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/ECCurveType.cs @@ -0,0 +1,29 @@ +namespace Org.BouncyCastle.Crypto.Tls +{ + /// + /// RFC 4492 5.4 + /// + public abstract class ECCurveType + { + /** + * Indicates the elliptic curve domain parameters are conveyed verbosely, and the + * underlying finite field is a prime field. + */ + public const byte explicit_prime = 1; + + /** + * Indicates the elliptic curve domain parameters are conveyed verbosely, and the + * underlying finite field is a characteristic-2 field. + */ + public const byte explicit_char2 = 2; + + /** + * Indicates that a named curve is used. This option SHOULD be used when applicable. + */ + public const byte named_curve = 3; + + /* + * Values 248 through 255 are reserved for private use. + */ + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/ECPointFormat.cs b/bc-sharp-crypto/src/crypto/tls/ECPointFormat.cs new file mode 100644 index 0000000..21b0fdd --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/ECPointFormat.cs @@ -0,0 +1,16 @@ +namespace Org.BouncyCastle.Crypto.Tls +{ + /// + /// RFC 4492 5.1.2 + /// + public abstract class ECPointFormat + { + public const byte uncompressed = 0; + public const byte ansiX962_compressed_prime = 1; + public const byte ansiX962_compressed_char2 = 2; + + /* + * reserved (248..255) + */ + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/EncryptionAlgorithm.cs b/bc-sharp-crypto/src/crypto/tls/EncryptionAlgorithm.cs new file mode 100644 index 0000000..45eef18 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/EncryptionAlgorithm.cs @@ -0,0 +1,69 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /// RFC 2246 + /// + /// Note that the values here are implementation-specific and arbitrary. It is recommended not to + /// depend on the particular values (e.g. serialization). + /// + public abstract class EncryptionAlgorithm + { + public const int NULL = 0; + public const int RC4_40 = 1; + public const int RC4_128 = 2; + public const int RC2_CBC_40 = 3; + public const int IDEA_CBC = 4; + public const int DES40_CBC = 5; + public const int DES_CBC = 6; + public const int cls_3DES_EDE_CBC = 7; + + /* + * RFC 3268 + */ + public const int AES_128_CBC = 8; + public const int AES_256_CBC = 9; + + /* + * RFC 5289 + */ + public const int AES_128_GCM = 10; + public const int AES_256_GCM = 11; + + /* + * RFC 4132 + */ + public const int CAMELLIA_128_CBC = 12; + public const int CAMELLIA_256_CBC = 13; + + /* + * RFC 4162 + */ + public const int SEED_CBC = 14; + + /* + * RFC 6655 + */ + public const int AES_128_CCM = 15; + public const int AES_128_CCM_8 = 16; + public const int AES_256_CCM = 17; + public const int AES_256_CCM_8 = 18; + + /* + * RFC 6367 + */ + public const int CAMELLIA_128_GCM = 19; + public const int CAMELLIA_256_GCM = 20; + + /* + * RFC 7905 + */ + public const int CHACHA20_POLY1305 = 21; + + /* + * draft-zauner-tls-aes-ocb-04 + */ + public const int AES_128_OCB_TAGLEN96 = 103; + public const int AES_256_OCB_TAGLEN96 = 104; + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/ExporterLabel.cs b/bc-sharp-crypto/src/crypto/tls/ExporterLabel.cs new file mode 100644 index 0000000..5970769 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/ExporterLabel.cs @@ -0,0 +1,37 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /// RFC 5705 + public abstract class ExporterLabel + { + /* + * RFC 5246 + */ + public const string client_finished = "client finished"; + public const string server_finished = "server finished"; + public const string master_secret = "master secret"; + public const string key_expansion = "key expansion"; + + /* + * RFC 5216 + */ + public const string client_EAP_encryption = "client EAP encryption"; + + /* + * RFC 5281 + */ + public const string ttls_keying_material = "ttls keying material"; + public const string ttls_challenge = "ttls challenge"; + + /* + * RFC 5764 + */ + public const string dtls_srtp = "EXTRACTOR-dtls_srtp"; + + /* + * draft-ietf-tls-session-hash-04 + */ + public static readonly string extended_master_secret = "extended master secret"; + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/ExtensionType.cs b/bc-sharp-crypto/src/crypto/tls/ExtensionType.cs new file mode 100644 index 0000000..f17210b --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/ExtensionType.cs @@ -0,0 +1,128 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public abstract class ExtensionType + { + /* + * RFC 2546 2.3. + */ + public const int server_name = 0; + public const int max_fragment_length = 1; + public const int client_certificate_url = 2; + public const int trusted_ca_keys = 3; + public const int truncated_hmac = 4; + public const int status_request = 5; + + /* + * RFC 4681 + */ + public const int user_mapping = 6; + + /* + * RFC 5878 + */ + public const int client_authz = 7; + public const int server_authz = 8; + + /* + * RFC RFC6091 + */ + public const int cert_type = 9; + + /* + * draft-ietf-tls-negotiated-ff-dhe-10 + */ + public const int supported_groups = 10; + + /* + * RFC 4492 5.1. + */ + [Obsolete("Use 'supported_groups' instead")] + public const int elliptic_curves = supported_groups; + public const int ec_point_formats = 11; + + /* + * RFC 5054 2.8.1. + */ + public const int srp = 12; + + /* + * RFC 5246 7.4.1.4. + */ + public const int signature_algorithms = 13; + + /* + * RFC 5764 9. + */ + public const int use_srtp = 14; + + /* + * RFC 6520 6. + */ + public const int heartbeat = 15; + + /* + * RFC 7301 + */ + public const int application_layer_protocol_negotiation = 16; + + /* + * RFC 6961 + */ + public const int status_request_v2 = 17; + + /* + * RFC 6962 + */ + public const int signed_certificate_timestamp = 18; + + /* + * RFC 7250 + */ + public const int client_certificate_type = 19; + public const int server_certificate_type = 20; + + /* + * RFC 7685 + */ + public const int padding = 21; + + /* + * RFC 7366 + */ + public const int encrypt_then_mac = 22; + + /* + * RFC 7627 + */ + public const int extended_master_secret = 23; + + /* + * draft-ietf-tokbind-negotiation-08 + */ + public static readonly int DRAFT_token_binding = 24; + + /* + * RFC 7924 + */ + public const int cached_info = 25; + + /* + * RFC 5077 7. + */ + public const int session_ticket = 35; + + /* + * draft-ietf-tls-negotiated-ff-dhe-01 + * + * WARNING: Placeholder value; the real value is TBA + */ + public static readonly int negotiated_ff_dhe_groups = 101; + + /* + * RFC 5746 3.2. + */ + public const int renegotiation_info = 0xff01; + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/FiniteFieldDheGroup.cs b/bc-sharp-crypto/src/crypto/tls/FiniteFieldDheGroup.cs new file mode 100644 index 0000000..4375049 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/FiniteFieldDheGroup.cs @@ -0,0 +1,21 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /* + * draft-ietf-tls-negotiated-ff-dhe-01 + */ + public abstract class FiniteFieldDheGroup + { + public const byte ffdhe2432 = 0; + public const byte ffdhe3072 = 1; + public const byte ffdhe4096 = 2; + public const byte ffdhe6144 = 3; + public const byte ffdhe8192 = 4; + + public static bool IsValid(byte group) + { + return group >= ffdhe2432 && group <= ffdhe8192; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/HandshakeType.cs b/bc-sharp-crypto/src/crypto/tls/HandshakeType.cs new file mode 100644 index 0000000..e63042a --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/HandshakeType.cs @@ -0,0 +1,40 @@ +namespace Org.BouncyCastle.Crypto.Tls +{ + public abstract class HandshakeType + { + /* + * RFC 2246 7.4 + */ + public const byte hello_request = 0; + public const byte client_hello = 1; + public const byte server_hello = 2; + public const byte certificate = 11; + public const byte server_key_exchange = 12; + public const byte certificate_request = 13; + public const byte server_hello_done = 14; + public const byte certificate_verify = 15; + public const byte client_key_exchange = 16; + public const byte finished = 20; + + /* + * RFC 3546 2.4 + */ + public const byte certificate_url = 21; + public const byte certificate_status = 22; + + /* + * (DTLS) RFC 4347 4.3.2 + */ + public const byte hello_verify_request = 3; + + /* + * RFC 4680 + */ + public const byte supplemental_data = 23; + + /* + * RFC 5077 + */ + public const byte session_ticket = 4; + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/HashAlgorithm.cs b/bc-sharp-crypto/src/crypto/tls/HashAlgorithm.cs new file mode 100644 index 0000000..0f38e2d --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/HashAlgorithm.cs @@ -0,0 +1,49 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /// RFC 5246 7.4.1.4.1 + public abstract class HashAlgorithm + { + public const byte none = 0; + public const byte md5 = 1; + public const byte sha1 = 2; + public const byte sha224 = 3; + public const byte sha256 = 4; + public const byte sha384 = 5; + public const byte sha512 = 6; + + public static string GetName(byte hashAlgorithm) + { + switch (hashAlgorithm) + { + case none: + return "none"; + case md5: + return "md5"; + case sha1: + return "sha1"; + case sha224: + return "sha224"; + case sha256: + return "sha256"; + case sha384: + return "sha384"; + case sha512: + return "sha512"; + default: + return "UNKNOWN"; + } + } + + public static string GetText(byte hashAlgorithm) + { + return GetName(hashAlgorithm) + "(" + hashAlgorithm + ")"; + } + + public static bool IsPrivate(byte hashAlgorithm) + { + return 224 <= hashAlgorithm && hashAlgorithm <= 255; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/HeartbeatExtension.cs b/bc-sharp-crypto/src/crypto/tls/HeartbeatExtension.cs new file mode 100644 index 0000000..0498372 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/HeartbeatExtension.cs @@ -0,0 +1,52 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class HeartbeatExtension + { + protected readonly byte mMode; + + public HeartbeatExtension(byte mode) + { + if (!HeartbeatMode.IsValid(mode)) + throw new ArgumentException("not a valid HeartbeatMode value", "mode"); + + this.mMode = mode; + } + + public virtual byte Mode + { + get { return mMode; } + } + + /** + * Encode this {@link HeartbeatExtension} to a {@link Stream}. + * + * @param output + * the {@link Stream} to encode to. + * @throws IOException + */ + public virtual void Encode(Stream output) + { + TlsUtilities.WriteUint8(mMode, output); + } + + /** + * Parse a {@link HeartbeatExtension} from a {@link Stream}. + * + * @param input + * the {@link Stream} to parse from. + * @return a {@link HeartbeatExtension} object. + * @throws IOException + */ + public static HeartbeatExtension Parse(Stream input) + { + byte mode = TlsUtilities.ReadUint8(input); + if (!HeartbeatMode.IsValid(mode)) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + + return new HeartbeatExtension(mode); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/HeartbeatMessage.cs b/bc-sharp-crypto/src/crypto/tls/HeartbeatMessage.cs new file mode 100644 index 0000000..3f22f7e --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/HeartbeatMessage.cs @@ -0,0 +1,109 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class HeartbeatMessage + { + protected readonly byte mType; + protected readonly byte[] mPayload; + protected readonly int mPaddingLength; + + public HeartbeatMessage(byte type, byte[] payload, int paddingLength) + { + if (!HeartbeatMessageType.IsValid(type)) + throw new ArgumentException("not a valid HeartbeatMessageType value", "type"); + if (payload == null || payload.Length >= (1 << 16)) + throw new ArgumentException("must have length < 2^16", "payload"); + if (paddingLength < 16) + throw new ArgumentException("must be at least 16", "paddingLength"); + + this.mType = type; + this.mPayload = payload; + this.mPaddingLength = paddingLength; + } + + /** + * Encode this {@link HeartbeatMessage} to a {@link Stream}. + * + * @param output + * the {@link Stream} to encode to. + * @throws IOException + */ + public virtual void Encode(TlsContext context, Stream output) + { + TlsUtilities.WriteUint8(mType, output); + + TlsUtilities.CheckUint16(mPayload.Length); + TlsUtilities.WriteUint16(mPayload.Length, output); + output.Write(mPayload, 0, mPayload.Length); + + byte[] padding = new byte[mPaddingLength]; + context.NonceRandomGenerator.NextBytes(padding); + output.Write(padding, 0, padding.Length); + } + + /** + * Parse a {@link HeartbeatMessage} from a {@link Stream}. + * + * @param input + * the {@link Stream} to parse from. + * @return a {@link HeartbeatMessage} object. + * @throws IOException + */ + public static HeartbeatMessage Parse(Stream input) + { + byte type = TlsUtilities.ReadUint8(input); + if (!HeartbeatMessageType.IsValid(type)) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + + int payload_length = TlsUtilities.ReadUint16(input); + + PayloadBuffer buf = new PayloadBuffer(); + Streams.PipeAll(input, buf); + + byte[] payload = buf.ToTruncatedByteArray(payload_length); + if (payload == null) + { + /* + * RFC 6520 4. If the payload_length of a received HeartbeatMessage is too large, the + * received HeartbeatMessage MUST be discarded silently. + */ + return null; + } + + TlsUtilities.CheckUint16(buf.Length); + int padding_length = (int)buf.Length - payload.Length; + + /* + * RFC 6520 4. The padding of a received HeartbeatMessage message MUST be ignored + */ + return new HeartbeatMessage(type, payload, padding_length); + } + + internal class PayloadBuffer + : MemoryStream + { + internal byte[] ToTruncatedByteArray(int payloadLength) + { + /* + * RFC 6520 4. The padding_length MUST be at least 16. + */ + int minimumCount = payloadLength + 16; + if (Length < minimumCount) + return null; + +#if PORTABLE + byte[] buf = ToArray(); +#else + byte[] buf = GetBuffer(); +#endif + + return Arrays.CopyOf(buf, payloadLength); + } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/HeartbeatMessageType.cs b/bc-sharp-crypto/src/crypto/tls/HeartbeatMessageType.cs new file mode 100644 index 0000000..57a4b86 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/HeartbeatMessageType.cs @@ -0,0 +1,18 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /* + * RFC 6520 3. + */ + public abstract class HeartbeatMessageType + { + public const byte heartbeat_request = 1; + public const byte heartbeat_response = 2; + + public static bool IsValid(byte heartbeatMessageType) + { + return heartbeatMessageType >= heartbeat_request && heartbeatMessageType <= heartbeat_response; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/HeartbeatMode.cs b/bc-sharp-crypto/src/crypto/tls/HeartbeatMode.cs new file mode 100644 index 0000000..f1570a8 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/HeartbeatMode.cs @@ -0,0 +1,18 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /* + * RFC 6520 + */ + public abstract class HeartbeatMode + { + public const byte peer_allowed_to_send = 1; + public const byte peer_not_allowed_to_send = 2; + + public static bool IsValid(byte heartbeatMode) + { + return heartbeatMode >= peer_allowed_to_send && heartbeatMode <= peer_not_allowed_to_send; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/KeyExchangeAlgorithm.cs b/bc-sharp-crypto/src/crypto/tls/KeyExchangeAlgorithm.cs new file mode 100644 index 0000000..9b1b3ba --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/KeyExchangeAlgorithm.cs @@ -0,0 +1,54 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /// RFC 2246 + /// + /// Note that the values here are implementation-specific and arbitrary. It is recommended not to + /// depend on the particular values (e.g. serialization). + /// + public abstract class KeyExchangeAlgorithm + { + public const int NULL = 0; + public const int RSA = 1; + public const int RSA_EXPORT = 2; + public const int DHE_DSS = 3; + public const int DHE_DSS_EXPORT = 4; + public const int DHE_RSA = 5; + public const int DHE_RSA_EXPORT = 6; + public const int DH_DSS = 7; + public const int DH_DSS_EXPORT = 8; + public const int DH_RSA = 9; + public const int DH_RSA_EXPORT = 10; + public const int DH_anon = 11; + public const int DH_anon_EXPORT = 12; + + /* + * RFC 4279 + */ + public const int PSK = 13; + public const int DHE_PSK = 14; + public const int RSA_PSK = 15; + + /* + * RFC 4429 + */ + public const int ECDH_ECDSA = 16; + public const int ECDHE_ECDSA = 17; + public const int ECDH_RSA = 18; + public const int ECDHE_RSA = 19; + public const int ECDH_anon = 20; + + /* + * RFC 5054 + */ + public const int SRP = 21; + public const int SRP_DSS = 22; + public const int SRP_RSA = 23; + + /* + * RFC 5489 + */ + public const int ECDHE_PSK = 24; + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/MacAlgorithm.cs b/bc-sharp-crypto/src/crypto/tls/MacAlgorithm.cs new file mode 100644 index 0000000..e4aa88d --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/MacAlgorithm.cs @@ -0,0 +1,25 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /// RFC 2246 + /// + /// Note that the values here are implementation-specific and arbitrary. It is recommended not to + /// depend on the particular values (e.g. serialization). + /// + public abstract class MacAlgorithm + { + public const int cls_null = 0; + public const int md5 = 1; + public const int sha = 2; + + /* + * RFC 5246 + */ + public const int hmac_md5 = md5; + public const int hmac_sha1 = sha; + public const int hmac_sha256 = 3; + public const int hmac_sha384 = 4; + public const int hmac_sha512 = 5; + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/MaxFragmentLength.cs b/bc-sharp-crypto/src/crypto/tls/MaxFragmentLength.cs new file mode 100644 index 0000000..5b10b35 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/MaxFragmentLength.cs @@ -0,0 +1,20 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public abstract class MaxFragmentLength + { + /* + * RFC 3546 3.2. + */ + public const byte pow2_9 = 1; + public const byte pow2_10 = 2; + public const byte pow2_11 = 3; + public const byte pow2_12 = 4; + + public static bool IsValid(byte maxFragmentLength) + { + return maxFragmentLength >= pow2_9 && maxFragmentLength <= pow2_12; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/NameType.cs b/bc-sharp-crypto/src/crypto/tls/NameType.cs new file mode 100644 index 0000000..7821642 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/NameType.cs @@ -0,0 +1,17 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public abstract class NameType + { + /* + * RFC 3546 3.1. + */ + public const byte host_name = 0; + + public static bool IsValid(byte nameType) + { + return nameType == host_name; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/NamedCurve.cs b/bc-sharp-crypto/src/crypto/tls/NamedCurve.cs new file mode 100644 index 0000000..b8aa0ec --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/NamedCurve.cs @@ -0,0 +1,77 @@ +using System; + +using Org.BouncyCastle.Asn1.Sec; +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Crypto.Parameters; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /// + /// RFC 4492 5.1.1 + /// The named curves defined here are those specified in SEC 2 [13]. Note that many of + /// these curves are also recommended in ANSI X9.62 [7] and FIPS 186-2 [11]. Values 0xFE00 + /// through 0xFEFF are reserved for private use. Values 0xFF01 and 0xFF02 indicate that the + /// client supports arbitrary prime and characteristic-2 curves, respectively (the curve + /// parameters must be encoded explicitly in ECParameters). + /// + public abstract class NamedCurve + { + public const int sect163k1 = 1; + public const int sect163r1 = 2; + public const int sect163r2 = 3; + public const int sect193r1 = 4; + public const int sect193r2 = 5; + public const int sect233k1 = 6; + public const int sect233r1 = 7; + public const int sect239k1 = 8; + public const int sect283k1 = 9; + public const int sect283r1 = 10; + public const int sect409k1 = 11; + public const int sect409r1 = 12; + public const int sect571k1 = 13; + public const int sect571r1 = 14; + public const int secp160k1 = 15; + public const int secp160r1 = 16; + public const int secp160r2 = 17; + public const int secp192k1 = 18; + public const int secp192r1 = 19; + public const int secp224k1 = 20; + public const int secp224r1 = 21; + public const int secp256k1 = 22; + public const int secp256r1 = 23; + public const int secp384r1 = 24; + public const int secp521r1 = 25; + + /* + * RFC 7027 + */ + public const int brainpoolP256r1 = 26; + public const int brainpoolP384r1 = 27; + public const int brainpoolP512r1 = 28; + + /* + * reserved (0xFE00..0xFEFF) + */ + + public const int arbitrary_explicit_prime_curves = 0xFF01; + public const int arbitrary_explicit_char2_curves = 0xFF02; + + public static bool IsValid(int namedCurve) + { + return (namedCurve >= sect163k1 && namedCurve <= brainpoolP512r1) + || (namedCurve >= arbitrary_explicit_prime_curves && namedCurve <= arbitrary_explicit_char2_curves); + } + + public static bool RefersToASpecificNamedCurve(int namedCurve) + { + switch (namedCurve) + { + case arbitrary_explicit_prime_curves: + case arbitrary_explicit_char2_curves: + return false; + default: + return true; + } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/NewSessionTicket.cs b/bc-sharp-crypto/src/crypto/tls/NewSessionTicket.cs new file mode 100644 index 0000000..a84026b --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/NewSessionTicket.cs @@ -0,0 +1,53 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class NewSessionTicket + { + protected readonly long mTicketLifetimeHint; + protected readonly byte[] mTicket; + + public NewSessionTicket(long ticketLifetimeHint, byte[] ticket) + { + this.mTicketLifetimeHint = ticketLifetimeHint; + this.mTicket = ticket; + } + + public virtual long TicketLifetimeHint + { + get { return mTicketLifetimeHint; } + } + + public virtual byte[] Ticket + { + get { return mTicket; } + } + + /** + * Encode this {@link NewSessionTicket} to a {@link Stream}. + * + * @param output the {@link Stream} to encode to. + * @throws IOException + */ + public virtual void Encode(Stream output) + { + TlsUtilities.WriteUint32(mTicketLifetimeHint, output); + TlsUtilities.WriteOpaque16(mTicket, output); + } + + /** + * Parse a {@link NewSessionTicket} from a {@link Stream}. + * + * @param input the {@link Stream} to parse from. + * @return a {@link NewSessionTicket} object. + * @throws IOException + */ + public static NewSessionTicket Parse(Stream input) + { + long ticketLifetimeHint = TlsUtilities.ReadUint32(input); + byte[] ticket = TlsUtilities.ReadOpaque16(input); + return new NewSessionTicket(ticketLifetimeHint, ticket); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/OcspStatusRequest.cs b/bc-sharp-crypto/src/crypto/tls/OcspStatusRequest.cs new file mode 100644 index 0000000..d9203a3 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/OcspStatusRequest.cs @@ -0,0 +1,131 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Ocsp; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /** + * RFC 3546 3.6 + */ + public class OcspStatusRequest + { + protected readonly IList mResponderIDList; + protected readonly X509Extensions mRequestExtensions; + + /** + * @param responderIDList + * an {@link IList} of {@link ResponderID}, specifying the list of trusted OCSP + * responders. An empty list has the special meaning that the responders are + * implicitly known to the server - e.g., by prior arrangement. + * @param requestExtensions + * OCSP request extensions. A null value means that there are no extensions. + */ + public OcspStatusRequest(IList responderIDList, X509Extensions requestExtensions) + { + this.mResponderIDList = responderIDList; + this.mRequestExtensions = requestExtensions; + } + + /** + * @return an {@link IList} of {@link ResponderID} + */ + public virtual IList ResponderIDList + { + get { return mResponderIDList; } + } + + /** + * @return OCSP request extensions + */ + public virtual X509Extensions RequestExtensions + { + get { return mRequestExtensions; } + } + + /** + * Encode this {@link OcspStatusRequest} to a {@link Stream}. + * + * @param output + * the {@link Stream} to encode to. + * @throws IOException + */ + public virtual void Encode(Stream output) + { + if (mResponderIDList == null || mResponderIDList.Count < 1) + { + TlsUtilities.WriteUint16(0, output); + } + else + { + MemoryStream buf = new MemoryStream(); + for (int i = 0; i < mResponderIDList.Count; ++i) + { + ResponderID responderID = (ResponderID)mResponderIDList[i]; + byte[] derEncoding = responderID.GetEncoded(Asn1Encodable.Der); + TlsUtilities.WriteOpaque16(derEncoding, buf); + } + TlsUtilities.CheckUint16(buf.Length); + TlsUtilities.WriteUint16((int)buf.Length, output); + Streams.WriteBufTo(buf, output); + } + + if (mRequestExtensions == null) + { + TlsUtilities.WriteUint16(0, output); + } + else + { + byte[] derEncoding = mRequestExtensions.GetEncoded(Asn1Encodable.Der); + TlsUtilities.CheckUint16(derEncoding.Length); + TlsUtilities.WriteUint16(derEncoding.Length, output); + output.Write(derEncoding, 0, derEncoding.Length); + } + } + + /** + * Parse a {@link OcspStatusRequest} from a {@link Stream}. + * + * @param input + * the {@link Stream} to parse from. + * @return an {@link OcspStatusRequest} object. + * @throws IOException + */ + public static OcspStatusRequest Parse(Stream input) + { + IList responderIDList = Platform.CreateArrayList(); + { + int length = TlsUtilities.ReadUint16(input); + if (length > 0) + { + byte[] data = TlsUtilities.ReadFully(length, input); + MemoryStream buf = new MemoryStream(data, false); + do + { + byte[] derEncoding = TlsUtilities.ReadOpaque16(buf); + ResponderID responderID = ResponderID.GetInstance(TlsUtilities.ReadDerObject(derEncoding)); + responderIDList.Add(responderID); + } + while (buf.Position < buf.Length); + } + } + + X509Extensions requestExtensions = null; + { + int length = TlsUtilities.ReadUint16(input); + if (length > 0) + { + byte[] derEncoding = TlsUtilities.ReadFully(length, input); + requestExtensions = X509Extensions.GetInstance(TlsUtilities.ReadDerObject(derEncoding)); + } + } + + return new OcspStatusRequest(responderIDList, requestExtensions); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/PrfAlgorithm.cs b/bc-sharp-crypto/src/crypto/tls/PrfAlgorithm.cs new file mode 100644 index 0000000..871241b --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/PrfAlgorithm.cs @@ -0,0 +1,24 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /// RFC 5246 + /// + /// Note that the values here are implementation-specific and arbitrary. It is recommended not to + /// depend on the particular values (e.g. serialization). + /// + public abstract class PrfAlgorithm + { + /* + * Placeholder to refer to the legacy TLS algorithm + */ + public const int tls_prf_legacy = 0; + + public const int tls_prf_sha256 = 1; + + /* + * Implied by RFC 5288 + */ + public const int tls_prf_sha384 = 2; + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/ProtocolVersion.cs b/bc-sharp-crypto/src/crypto/tls/ProtocolVersion.cs new file mode 100644 index 0000000..b0d5518 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/ProtocolVersion.cs @@ -0,0 +1,159 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public sealed class ProtocolVersion + { + public static readonly ProtocolVersion SSLv3 = new ProtocolVersion(0x0300, "SSL 3.0"); + public static readonly ProtocolVersion TLSv10 = new ProtocolVersion(0x0301, "TLS 1.0"); + public static readonly ProtocolVersion TLSv11 = new ProtocolVersion(0x0302, "TLS 1.1"); + public static readonly ProtocolVersion TLSv12 = new ProtocolVersion(0x0303, "TLS 1.2"); + public static readonly ProtocolVersion DTLSv10 = new ProtocolVersion(0xFEFF, "DTLS 1.0"); + public static readonly ProtocolVersion DTLSv12 = new ProtocolVersion(0xFEFD, "DTLS 1.2"); + + private readonly int version; + private readonly String name; + + private ProtocolVersion(int v, String name) + { + this.version = v & 0xffff; + this.name = name; + } + + public int FullVersion + { + get { return version; } + } + + public int MajorVersion + { + get { return version >> 8; } + } + + public int MinorVersion + { + get { return version & 0xff; } + } + + public bool IsDtls + { + get { return MajorVersion == 0xFE; } + } + + public bool IsSsl + { + get { return this == SSLv3; } + } + + public bool IsTls + { + get { return MajorVersion == 0x03; } + } + + public ProtocolVersion GetEquivalentTLSVersion() + { + if (!IsDtls) + { + return this; + } + if (this == DTLSv10) + { + return TLSv11; + } + return TLSv12; + } + + public bool IsEqualOrEarlierVersionOf(ProtocolVersion version) + { + if (MajorVersion != version.MajorVersion) + { + return false; + } + int diffMinorVersion = version.MinorVersion - MinorVersion; + return IsDtls ? diffMinorVersion <= 0 : diffMinorVersion >= 0; + } + + public bool IsLaterVersionOf(ProtocolVersion version) + { + if (MajorVersion != version.MajorVersion) + { + return false; + } + int diffMinorVersion = version.MinorVersion - MinorVersion; + return IsDtls ? diffMinorVersion > 0 : diffMinorVersion < 0; + } + + public override bool Equals(object other) + { + return this == other || (other is ProtocolVersion && Equals((ProtocolVersion)other)); + } + + public bool Equals(ProtocolVersion other) + { + return other != null && this.version == other.version; + } + + public override int GetHashCode() + { + return version; + } + + /// + public static ProtocolVersion Get(int major, int minor) + { + switch (major) + { + case 0x03: + { + switch (minor) + { + case 0x00: + return SSLv3; + case 0x01: + return TLSv10; + case 0x02: + return TLSv11; + case 0x03: + return TLSv12; + } + return GetUnknownVersion(major, minor, "TLS"); + } + case 0xFE: + { + switch (minor) + { + case 0xFF: + return DTLSv10; + case 0xFE: + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + case 0xFD: + return DTLSv12; + } + return GetUnknownVersion(major, minor, "DTLS"); + } + default: + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + } + } + + public override string ToString() + { + return name; + } + + private static ProtocolVersion GetUnknownVersion(int major, int minor, string prefix) + { + TlsUtilities.CheckUint8(major); + TlsUtilities.CheckUint8(minor); + + int v = (major << 8) | minor; + String hex = Platform.ToUpperInvariant(Convert.ToString(0x10000 | v, 16).Substring(1)); + return new ProtocolVersion(v, prefix + " 0x" + hex); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/PskTlsClient.cs b/bc-sharp-crypto/src/crypto/tls/PskTlsClient.cs new file mode 100644 index 0000000..2ef80dc --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/PskTlsClient.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class PskTlsClient + : AbstractTlsClient + { + protected TlsPskIdentity mPskIdentity; + + public PskTlsClient(TlsPskIdentity pskIdentity) + : this(new DefaultTlsCipherFactory(), pskIdentity) + { + } + + public PskTlsClient(TlsCipherFactory cipherFactory, TlsPskIdentity pskIdentity) + : base(cipherFactory) + { + this.mPskIdentity = pskIdentity; + } + + public override int[] GetCipherSuites() + { + return new int[] + { + CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256, + CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA, + CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA256, + CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA + }; + } + + public override TlsKeyExchange GetKeyExchange() + { + int keyExchangeAlgorithm = TlsUtilities.GetKeyExchangeAlgorithm(mSelectedCipherSuite); + + switch (keyExchangeAlgorithm) + { + case KeyExchangeAlgorithm.DHE_PSK: + case KeyExchangeAlgorithm.ECDHE_PSK: + case KeyExchangeAlgorithm.PSK: + case KeyExchangeAlgorithm.RSA_PSK: + return CreatePskKeyExchange(keyExchangeAlgorithm); + + default: + /* + * Note: internal error here; the TlsProtocol implementation verifies that the + * server-selected cipher suite was in the list of client-offered cipher suites, so if + * we now can't produce an implementation, we shouldn't have offered it! + */ + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + public override TlsAuthentication GetAuthentication() + { + /* + * Note: This method is not called unless a server certificate is sent, which may be the + * case e.g. for RSA_PSK key exchange. + */ + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + protected virtual TlsKeyExchange CreatePskKeyExchange(int keyExchange) + { + return new TlsPskKeyExchange(keyExchange, mSupportedSignatureAlgorithms, mPskIdentity, null, null, mNamedCurves, + mClientECPointFormats, mServerECPointFormats); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/PskTlsServer.cs b/bc-sharp-crypto/src/crypto/tls/PskTlsServer.cs new file mode 100644 index 0000000..b0fb67c --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/PskTlsServer.cs @@ -0,0 +1,93 @@ +using System; + +using Org.BouncyCastle.Crypto.Agreement; +using Org.BouncyCastle.Crypto.Parameters; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class PskTlsServer + : AbstractTlsServer + { + protected TlsPskIdentityManager mPskIdentityManager; + + public PskTlsServer(TlsPskIdentityManager pskIdentityManager) + : this(new DefaultTlsCipherFactory(), pskIdentityManager) + { + } + + public PskTlsServer(TlsCipherFactory cipherFactory, TlsPskIdentityManager pskIdentityManager) + : base(cipherFactory) + { + this.mPskIdentityManager = pskIdentityManager; + } + + protected virtual TlsEncryptionCredentials GetRsaEncryptionCredentials() + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + protected virtual DHParameters GetDHParameters() + { + return DHStandardGroups.rfc7919_ffdhe2048; + } + + protected override int[] GetCipherSuites() + { + return new int[] + { + CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256, + CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA, + CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA256, + CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA + }; + } + + public override TlsCredentials GetCredentials() + { + int keyExchangeAlgorithm = TlsUtilities.GetKeyExchangeAlgorithm(mSelectedCipherSuite); + + switch (keyExchangeAlgorithm) + { + case KeyExchangeAlgorithm.DHE_PSK: + case KeyExchangeAlgorithm.ECDHE_PSK: + case KeyExchangeAlgorithm.PSK: + return null; + + case KeyExchangeAlgorithm.RSA_PSK: + return GetRsaEncryptionCredentials(); + + default: + /* Note: internal error here; selected a key exchange we don't implement! */ + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + public override TlsKeyExchange GetKeyExchange() + { + int keyExchangeAlgorithm = TlsUtilities.GetKeyExchangeAlgorithm(mSelectedCipherSuite); + + switch (keyExchangeAlgorithm) + { + case KeyExchangeAlgorithm.DHE_PSK: + case KeyExchangeAlgorithm.ECDHE_PSK: + case KeyExchangeAlgorithm.PSK: + case KeyExchangeAlgorithm.RSA_PSK: + return CreatePskKeyExchange(keyExchangeAlgorithm); + + default: + /* + * Note: internal error here; the TlsProtocol implementation verifies that the + * server-selected cipher suite was in the list of client-offered cipher suites, so if + * we now can't produce an implementation, we shouldn't have offered it! + */ + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + protected virtual TlsKeyExchange CreatePskKeyExchange(int keyExchange) + { + return new TlsPskKeyExchange(keyExchange, mSupportedSignatureAlgorithms, null, mPskIdentityManager, + GetDHParameters(), mNamedCurves, mClientECPointFormats, mServerECPointFormats); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/RecordStream.cs b/bc-sharp-crypto/src/crypto/tls/RecordStream.cs new file mode 100644 index 0000000..5d556ad --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/RecordStream.cs @@ -0,0 +1,412 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /// An implementation of the TLS 1.0/1.1/1.2 record layer, allowing downgrade to SSLv3. + internal class RecordStream + { + private const int DEFAULT_PLAINTEXT_LIMIT = (1 << 14); + + internal const int TLS_HEADER_SIZE = 5; + internal const int TLS_HEADER_TYPE_OFFSET = 0; + internal const int TLS_HEADER_VERSION_OFFSET = 1; + internal const int TLS_HEADER_LENGTH_OFFSET = 3; + + private TlsProtocol mHandler; + private Stream mInput; + private Stream mOutput; + private TlsCompression mPendingCompression = null, mReadCompression = null, mWriteCompression = null; + private TlsCipher mPendingCipher = null, mReadCipher = null, mWriteCipher = null; + private SequenceNumber mReadSeqNo = new SequenceNumber(), mWriteSeqNo = new SequenceNumber(); + private MemoryStream mBuffer = new MemoryStream(); + + private TlsHandshakeHash mHandshakeHash = null; + private readonly BaseOutputStream mHandshakeHashUpdater; + + private ProtocolVersion mReadVersion = null, mWriteVersion = null; + private bool mRestrictReadVersion = true; + + private int mPlaintextLimit, mCompressedLimit, mCiphertextLimit; + + internal RecordStream(TlsProtocol handler, Stream input, Stream output) + { + this.mHandler = handler; + this.mInput = input; + this.mOutput = output; + this.mReadCompression = new TlsNullCompression(); + this.mWriteCompression = this.mReadCompression; + this.mHandshakeHashUpdater = new HandshakeHashUpdateStream(this); + } + + internal virtual void Init(TlsContext context) + { + this.mReadCipher = new TlsNullCipher(context); + this.mWriteCipher = this.mReadCipher; + this.mHandshakeHash = new DeferredHash(); + this.mHandshakeHash.Init(context); + + SetPlaintextLimit(DEFAULT_PLAINTEXT_LIMIT); + } + + internal virtual int GetPlaintextLimit() + { + return mPlaintextLimit; + } + + internal virtual void SetPlaintextLimit(int plaintextLimit) + { + this.mPlaintextLimit = plaintextLimit; + this.mCompressedLimit = this.mPlaintextLimit + 1024; + this.mCiphertextLimit = this.mCompressedLimit + 1024; + } + + internal virtual ProtocolVersion ReadVersion + { + get { return mReadVersion; } + set { this.mReadVersion = value; } + } + + internal virtual void SetWriteVersion(ProtocolVersion writeVersion) + { + this.mWriteVersion = writeVersion; + } + + /** + * RFC 5246 E.1. "Earlier versions of the TLS specification were not fully clear on what the + * record layer version number (TLSPlaintext.version) should contain when sending ClientHello + * (i.e., before it is known which version of the protocol will be employed). Thus, TLS servers + * compliant with this specification MUST accept any value {03,XX} as the record layer version + * number for ClientHello." + */ + internal virtual void SetRestrictReadVersion(bool enabled) + { + this.mRestrictReadVersion = enabled; + } + + internal virtual void SetPendingConnectionState(TlsCompression tlsCompression, TlsCipher tlsCipher) + { + this.mPendingCompression = tlsCompression; + this.mPendingCipher = tlsCipher; + } + + internal virtual void SentWriteCipherSpec() + { + if (mPendingCompression == null || mPendingCipher == null) + throw new TlsFatalAlert(AlertDescription.handshake_failure); + + this.mWriteCompression = this.mPendingCompression; + this.mWriteCipher = this.mPendingCipher; + this.mWriteSeqNo = new SequenceNumber(); + } + + internal virtual void ReceivedReadCipherSpec() + { + if (mPendingCompression == null || mPendingCipher == null) + throw new TlsFatalAlert(AlertDescription.handshake_failure); + + this.mReadCompression = this.mPendingCompression; + this.mReadCipher = this.mPendingCipher; + this.mReadSeqNo = new SequenceNumber(); + } + + internal virtual void FinaliseHandshake() + { + if (mReadCompression != mPendingCompression || mWriteCompression != mPendingCompression + || mReadCipher != mPendingCipher || mWriteCipher != mPendingCipher) + { + throw new TlsFatalAlert(AlertDescription.handshake_failure); + } + this.mPendingCompression = null; + this.mPendingCipher = null; + } + + internal virtual void CheckRecordHeader(byte[] recordHeader) + { + byte type = TlsUtilities.ReadUint8(recordHeader, TLS_HEADER_TYPE_OFFSET); + + /* + * RFC 5246 6. If a TLS implementation receives an unexpected record type, it MUST send an + * unexpected_message alert. + */ + CheckType(type, AlertDescription.unexpected_message); + + if (!mRestrictReadVersion) + { + int version = TlsUtilities.ReadVersionRaw(recordHeader, TLS_HEADER_VERSION_OFFSET); + if ((version & 0xffffff00) != 0x0300) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + else + { + ProtocolVersion version = TlsUtilities.ReadVersion(recordHeader, TLS_HEADER_VERSION_OFFSET); + if (mReadVersion == null) + { + // Will be set later in 'readRecord' + } + else if (!version.Equals(mReadVersion)) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + } + + int length = TlsUtilities.ReadUint16(recordHeader, TLS_HEADER_LENGTH_OFFSET); + + CheckLength(length, mCiphertextLimit, AlertDescription.record_overflow); + } + + internal virtual bool ReadRecord() + { + byte[] recordHeader = TlsUtilities.ReadAllOrNothing(TLS_HEADER_SIZE, mInput); + if (recordHeader == null) + return false; + + byte type = TlsUtilities.ReadUint8(recordHeader, TLS_HEADER_TYPE_OFFSET); + + /* + * RFC 5246 6. If a TLS implementation receives an unexpected record type, it MUST send an + * unexpected_message alert. + */ + CheckType(type, AlertDescription.unexpected_message); + + if (!mRestrictReadVersion) + { + int version = TlsUtilities.ReadVersionRaw(recordHeader, TLS_HEADER_VERSION_OFFSET); + if ((version & 0xffffff00) != 0x0300) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + else + { + ProtocolVersion version = TlsUtilities.ReadVersion(recordHeader, TLS_HEADER_VERSION_OFFSET); + if (mReadVersion == null) + { + mReadVersion = version; + } + else if (!version.Equals(mReadVersion)) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + } + + int length = TlsUtilities.ReadUint16(recordHeader, TLS_HEADER_LENGTH_OFFSET); + + CheckLength(length, mCiphertextLimit, AlertDescription.record_overflow); + + byte[] plaintext = DecodeAndVerify(type, mInput, length); + mHandler.ProcessRecord(type, plaintext, 0, plaintext.Length); + return true; + } + + internal virtual byte[] DecodeAndVerify(byte type, Stream input, int len) + { + byte[] buf = TlsUtilities.ReadFully(len, input); + + long seqNo = mReadSeqNo.NextValue(AlertDescription.unexpected_message); + byte[] decoded = mReadCipher.DecodeCiphertext(seqNo, type, buf, 0, buf.Length); + + CheckLength(decoded.Length, mCompressedLimit, AlertDescription.record_overflow); + + /* + * TODO 5246 6.2.2. Implementation note: Decompression functions are responsible for + * ensuring that messages cannot cause internal buffer overflows. + */ + Stream cOut = mReadCompression.Decompress(mBuffer); + if (cOut != mBuffer) + { + cOut.Write(decoded, 0, decoded.Length); + cOut.Flush(); + decoded = GetBufferContents(); + } + + /* + * RFC 5246 6.2.2. If the decompression function encounters a TLSCompressed.fragment that + * would decompress to a length in excess of 2^14 bytes, it should report a fatal + * decompression failure error. + */ + CheckLength(decoded.Length, mPlaintextLimit, AlertDescription.decompression_failure); + + /* + * RFC 5246 6.2.1 Implementations MUST NOT send zero-length fragments of Handshake, Alert, + * or ChangeCipherSpec content types. + */ + if (decoded.Length < 1 && type != ContentType.application_data) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + + return decoded; + } + + internal virtual void WriteRecord(byte type, byte[] plaintext, int plaintextOffset, int plaintextLength) + { + // Never send anything until a valid ClientHello has been received + if (mWriteVersion == null) + return; + + /* + * RFC 5246 6. Implementations MUST NOT send record types not defined in this document + * unless negotiated by some extension. + */ + CheckType(type, AlertDescription.internal_error); + + /* + * RFC 5246 6.2.1 The length should not exceed 2^14. + */ + CheckLength(plaintextLength, mPlaintextLimit, AlertDescription.internal_error); + + /* + * RFC 5246 6.2.1 Implementations MUST NOT send zero-length fragments of Handshake, Alert, + * or ChangeCipherSpec content types. + */ + if (plaintextLength < 1 && type != ContentType.application_data) + throw new TlsFatalAlert(AlertDescription.internal_error); + + Stream cOut = mWriteCompression.Compress(mBuffer); + + long seqNo = mWriteSeqNo.NextValue(AlertDescription.internal_error); + + byte[] ciphertext; + if (cOut == mBuffer) + { + ciphertext = mWriteCipher.EncodePlaintext(seqNo, type, plaintext, plaintextOffset, plaintextLength); + } + else + { + cOut.Write(plaintext, plaintextOffset, plaintextLength); + cOut.Flush(); + byte[] compressed = GetBufferContents(); + + /* + * RFC 5246 6.2.2. Compression must be lossless and may not increase the content length + * by more than 1024 bytes. + */ + CheckLength(compressed.Length, plaintextLength + 1024, AlertDescription.internal_error); + + ciphertext = mWriteCipher.EncodePlaintext(seqNo, type, compressed, 0, compressed.Length); + } + + /* + * RFC 5246 6.2.3. The length may not exceed 2^14 + 2048. + */ + CheckLength(ciphertext.Length, mCiphertextLimit, AlertDescription.internal_error); + + byte[] record = new byte[ciphertext.Length + TLS_HEADER_SIZE]; + TlsUtilities.WriteUint8(type, record, TLS_HEADER_TYPE_OFFSET); + TlsUtilities.WriteVersion(mWriteVersion, record, TLS_HEADER_VERSION_OFFSET); + TlsUtilities.WriteUint16(ciphertext.Length, record, TLS_HEADER_LENGTH_OFFSET); + Array.Copy(ciphertext, 0, record, TLS_HEADER_SIZE, ciphertext.Length); + mOutput.Write(record, 0, record.Length); + mOutput.Flush(); + } + + internal virtual void NotifyHelloComplete() + { + this.mHandshakeHash = mHandshakeHash.NotifyPrfDetermined(); + } + + internal virtual TlsHandshakeHash HandshakeHash + { + get { return mHandshakeHash; } + } + + internal virtual Stream HandshakeHashUpdater + { + get { return mHandshakeHashUpdater; } + } + + internal virtual TlsHandshakeHash PrepareToFinish() + { + TlsHandshakeHash result = mHandshakeHash; + this.mHandshakeHash = mHandshakeHash.StopTracking(); + return result; + } + + internal virtual void SafeClose() + { + try + { + Platform.Dispose(mInput); + } + catch (IOException) + { + } + + try + { + Platform.Dispose(mOutput); + } + catch (IOException) + { + } + } + + internal virtual void Flush() + { + mOutput.Flush(); + } + + private byte[] GetBufferContents() + { + byte[] contents = mBuffer.ToArray(); + mBuffer.SetLength(0); + return contents; + } + + private static void CheckType(byte type, byte alertDescription) + { + switch (type) + { + case ContentType.application_data: + case ContentType.alert: + case ContentType.change_cipher_spec: + case ContentType.handshake: + //case ContentType.heartbeat: + break; + default: + throw new TlsFatalAlert(alertDescription); + } + } + + private static void CheckLength(int length, int limit, byte alertDescription) + { + if (length > limit) + throw new TlsFatalAlert(alertDescription); + } + + private class HandshakeHashUpdateStream + : BaseOutputStream + { + private readonly RecordStream mOuter; + public HandshakeHashUpdateStream(RecordStream mOuter) + { + this.mOuter = mOuter; + } + + public override void Write(byte[] buf, int off, int len) + { + mOuter.mHandshakeHash.BlockUpdate(buf, off, len); + } + } + + private class SequenceNumber + { + private long value = 0L; + private bool exhausted = false; + + internal long NextValue(byte alertDescription) + { + if (exhausted) + { + throw new TlsFatalAlert(alertDescription); + } + long result = value; + if (++value == 0) + { + exhausted = true; + } + return result; + } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/SecurityParameters.cs b/bc-sharp-crypto/src/crypto/tls/SecurityParameters.cs new file mode 100644 index 0000000..3b85158 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/SecurityParameters.cs @@ -0,0 +1,103 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class SecurityParameters + { + internal int entity = -1; + internal int cipherSuite = -1; + internal byte compressionAlgorithm = CompressionMethod.cls_null; + internal int prfAlgorithm = -1; + internal int verifyDataLength = -1; + internal byte[] masterSecret = null; + internal byte[] clientRandom = null; + internal byte[] serverRandom = null; + internal byte[] sessionHash = null; + internal byte[] pskIdentity = null; + internal byte[] srpIdentity = null; + + // TODO Keep these internal, since it's maybe not the ideal place for them + internal short maxFragmentLength = -1; + internal bool truncatedHMac = false; + internal bool encryptThenMac = false; + internal bool extendedMasterSecret = false; + + internal virtual void Clear() + { + if (this.masterSecret != null) + { + Arrays.Fill(this.masterSecret, (byte)0); + this.masterSecret = null; + } + } + + /** + * @return {@link ConnectionEnd} + */ + public virtual int Entity + { + get { return entity; } + } + + /** + * @return {@link CipherSuite} + */ + public virtual int CipherSuite + { + get { return cipherSuite; } + } + + /** + * @return {@link CompressionMethod} + */ + public byte CompressionAlgorithm + { + get { return compressionAlgorithm; } + } + + /** + * @return {@link PRFAlgorithm} + */ + public virtual int PrfAlgorithm + { + get { return prfAlgorithm; } + } + + public virtual int VerifyDataLength + { + get { return verifyDataLength; } + } + + public virtual byte[] MasterSecret + { + get { return masterSecret; } + } + + public virtual byte[] ClientRandom + { + get { return clientRandom; } + } + + public virtual byte[] ServerRandom + { + get { return serverRandom; } + } + + public virtual byte[] SessionHash + { + get { return sessionHash; } + } + + public virtual byte[] PskIdentity + { + get { return pskIdentity; } + } + + public virtual byte[] SrpIdentity + { + get { return srpIdentity; } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/ServerDHParams.cs b/bc-sharp-crypto/src/crypto/tls/ServerDHParams.cs new file mode 100644 index 0000000..b092627 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/ServerDHParams.cs @@ -0,0 +1,61 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class ServerDHParams + { + protected readonly DHPublicKeyParameters mPublicKey; + + public ServerDHParams(DHPublicKeyParameters publicKey) + { + if (publicKey == null) + throw new ArgumentNullException("publicKey"); + + this.mPublicKey = publicKey; + } + + public virtual DHPublicKeyParameters PublicKey + { + get { return mPublicKey; } + } + + /** + * Encode this {@link ServerDHParams} to a {@link Stream}. + * + * @param output + * the {@link Stream} to encode to. + * @throws IOException + */ + public virtual void Encode(Stream output) + { + DHParameters dhParameters = mPublicKey.Parameters; + BigInteger Ys = mPublicKey.Y; + + TlsDHUtilities.WriteDHParameter(dhParameters.P, output); + TlsDHUtilities.WriteDHParameter(dhParameters.G, output); + TlsDHUtilities.WriteDHParameter(Ys, output); + } + + /** + * Parse a {@link ServerDHParams} from a {@link Stream}. + * + * @param input + * the {@link Stream} to parse from. + * @return a {@link ServerDHParams} object. + * @throws IOException + */ + public static ServerDHParams Parse(Stream input) + { + BigInteger p = TlsDHUtilities.ReadDHParameter(input); + BigInteger g = TlsDHUtilities.ReadDHParameter(input); + BigInteger Ys = TlsDHUtilities.ReadDHParameter(input); + + return new ServerDHParams( + TlsDHUtilities.ValidateDHPublicKey(new DHPublicKeyParameters(Ys, new DHParameters(p, g)))); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/ServerName.cs b/bc-sharp-crypto/src/crypto/tls/ServerName.cs new file mode 100644 index 0000000..508c2dd --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/ServerName.cs @@ -0,0 +1,105 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class ServerName + { + protected readonly byte mNameType; + protected readonly object mName; + + public ServerName(byte nameType, object name) + { + if (!IsCorrectType(nameType, name)) + throw new ArgumentException("not an instance of the correct type", "name"); + + this.mNameType = nameType; + this.mName = name; + } + + public virtual byte NameType + { + get { return mNameType; } + } + + public virtual object Name + { + get { return mName; } + } + + public virtual string GetHostName() + { + if (!IsCorrectType(Tls.NameType.host_name, mName)) + throw new InvalidOperationException("'name' is not a HostName string"); + + return (string)mName; + } + + /** + * Encode this {@link ServerName} to a {@link Stream}. + * + * @param output + * the {@link Stream} to encode to. + * @throws IOException + */ + public virtual void Encode(Stream output) + { + TlsUtilities.WriteUint8(mNameType, output); + + switch (mNameType) + { + case Tls.NameType.host_name: + byte[] asciiEncoding = Strings.ToAsciiByteArray((string)mName); + if (asciiEncoding.Length < 1) + throw new TlsFatalAlert(AlertDescription.internal_error); + TlsUtilities.WriteOpaque16(asciiEncoding, output); + break; + default: + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + /** + * Parse a {@link ServerName} from a {@link Stream}. + * + * @param input + * the {@link Stream} to parse from. + * @return a {@link ServerName} object. + * @throws IOException + */ + public static ServerName Parse(Stream input) + { + byte name_type = TlsUtilities.ReadUint8(input); + object name; + + switch (name_type) + { + case Tls.NameType.host_name: + { + byte[] asciiEncoding = TlsUtilities.ReadOpaque16(input); + if (asciiEncoding.Length < 1) + throw new TlsFatalAlert(AlertDescription.decode_error); + name = Strings.FromAsciiByteArray(asciiEncoding); + break; + } + default: + throw new TlsFatalAlert(AlertDescription.decode_error); + } + + return new ServerName(name_type, name); + } + + protected static bool IsCorrectType(byte nameType, object name) + { + switch (nameType) + { + case Tls.NameType.host_name: + return name is string; + default: + throw new ArgumentException("unsupported value", "name"); + } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/ServerNameList.cs b/bc-sharp-crypto/src/crypto/tls/ServerNameList.cs new file mode 100644 index 0000000..ed4e593 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/ServerNameList.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class ServerNameList + { + protected readonly IList mServerNameList; + + /** + * @param serverNameList an {@link IList} of {@link ServerName}. + */ + public ServerNameList(IList serverNameList) + { + if (serverNameList == null) + throw new ArgumentNullException("serverNameList"); + + this.mServerNameList = serverNameList; + } + + /** + * @return an {@link IList} of {@link ServerName}. + */ + public virtual IList ServerNames + { + get { return mServerNameList; } + } + + /** + * Encode this {@link ServerNameList} to a {@link Stream}. + * + * @param output + * the {@link Stream} to encode to. + * @throws IOException + */ + public virtual void Encode(Stream output) + { + MemoryStream buf = new MemoryStream(); + + byte[] nameTypesSeen = TlsUtilities.EmptyBytes; + foreach (ServerName entry in ServerNames) + { + nameTypesSeen = CheckNameType(nameTypesSeen, entry.NameType); + if (nameTypesSeen == null) + throw new TlsFatalAlert(AlertDescription.internal_error); + + entry.Encode(buf); + } + + TlsUtilities.CheckUint16(buf.Length); + TlsUtilities.WriteUint16((int)buf.Length, output); + Streams.WriteBufTo(buf, output); + } + + /** + * Parse a {@link ServerNameList} from a {@link Stream}. + * + * @param input + * the {@link Stream} to parse from. + * @return a {@link ServerNameList} object. + * @throws IOException + */ + public static ServerNameList Parse(Stream input) + { + int length = TlsUtilities.ReadUint16(input); + if (length < 1) + throw new TlsFatalAlert(AlertDescription.decode_error); + + byte[] data = TlsUtilities.ReadFully(length, input); + + MemoryStream buf = new MemoryStream(data, false); + + byte[] nameTypesSeen = TlsUtilities.EmptyBytes; + IList server_name_list = Platform.CreateArrayList(); + while (buf.Position < buf.Length) + { + ServerName entry = ServerName.Parse(buf); + + nameTypesSeen = CheckNameType(nameTypesSeen, entry.NameType); + if (nameTypesSeen == null) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + + server_name_list.Add(entry); + } + + return new ServerNameList(server_name_list); + } + + private static byte[] CheckNameType(byte[] nameTypesSeen, byte nameType) + { + /* + * RFC 6066 3. The ServerNameList MUST NOT contain more than one name of the same + * name_type. + */ + if (!NameType.IsValid(nameType) || Arrays.Contains(nameTypesSeen, nameType)) + return null; + + return Arrays.Append(nameTypesSeen, nameType); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/ServerOnlyTlsAuthentication.cs b/bc-sharp-crypto/src/crypto/tls/ServerOnlyTlsAuthentication.cs new file mode 100644 index 0000000..4858897 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/ServerOnlyTlsAuthentication.cs @@ -0,0 +1,15 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public abstract class ServerOnlyTlsAuthentication + : TlsAuthentication + { + public abstract void NotifyServerCertificate(Certificate serverCertificate); + + public TlsCredentials GetClientCredentials(CertificateRequest certificateRequest) + { + return null; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/ServerSrpParams.cs b/bc-sharp-crypto/src/crypto/tls/ServerSrpParams.cs new file mode 100644 index 0000000..556ac53 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/ServerSrpParams.cs @@ -0,0 +1,75 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class ServerSrpParams + { + protected BigInteger m_N, m_g, m_B; + protected byte[] m_s; + + public ServerSrpParams(BigInteger N, BigInteger g, byte[] s, BigInteger B) + { + this.m_N = N; + this.m_g = g; + this.m_s = Arrays.Clone(s); + this.m_B = B; + } + + public virtual BigInteger B + { + get { return m_B; } + } + + public virtual BigInteger G + { + get { return m_g; } + } + + public virtual BigInteger N + { + get { return m_N; } + } + + public virtual byte[] S + { + get { return m_s; } + } + + /** + * Encode this {@link ServerSRPParams} to an {@link OutputStream}. + * + * @param output + * the {@link OutputStream} to encode to. + * @throws IOException + */ + public virtual void Encode(Stream output) + { + TlsSrpUtilities.WriteSrpParameter(m_N, output); + TlsSrpUtilities.WriteSrpParameter(m_g, output); + TlsUtilities.WriteOpaque8(m_s, output); + TlsSrpUtilities.WriteSrpParameter(m_B, output); + } + + /** + * Parse a {@link ServerSRPParams} from an {@link InputStream}. + * + * @param input + * the {@link InputStream} to parse from. + * @return a {@link ServerSRPParams} object. + * @throws IOException + */ + public static ServerSrpParams Parse(Stream input) + { + BigInteger N = TlsSrpUtilities.ReadSrpParameter(input); + BigInteger g = TlsSrpUtilities.ReadSrpParameter(input); + byte[] s = TlsUtilities.ReadOpaque8(input); + BigInteger B = TlsSrpUtilities.ReadSrpParameter(input); + + return new ServerSrpParams(N, g, s, B); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/SessionParameters.cs b/bc-sharp-crypto/src/crypto/tls/SessionParameters.cs new file mode 100644 index 0000000..a1eb5f2 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/SessionParameters.cs @@ -0,0 +1,165 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public sealed class SessionParameters + { + public sealed class Builder + { + private int mCipherSuite = -1; + private short mCompressionAlgorithm = -1; + private byte[] mMasterSecret = null; + private Certificate mPeerCertificate = null; + private byte[] mPskIdentity = null; + private byte[] mSrpIdentity = null; + private byte[] mEncodedServerExtensions = null; + + public Builder() + { + } + + public SessionParameters Build() + { + Validate(this.mCipherSuite >= 0, "cipherSuite"); + Validate(this.mCompressionAlgorithm >= 0, "compressionAlgorithm"); + Validate(this.mMasterSecret != null, "masterSecret"); + return new SessionParameters(mCipherSuite, (byte)mCompressionAlgorithm, mMasterSecret, mPeerCertificate, + mPskIdentity, mSrpIdentity, mEncodedServerExtensions); + } + + public Builder SetCipherSuite(int cipherSuite) + { + this.mCipherSuite = cipherSuite; + return this; + } + + public Builder SetCompressionAlgorithm(byte compressionAlgorithm) + { + this.mCompressionAlgorithm = compressionAlgorithm; + return this; + } + + public Builder SetMasterSecret(byte[] masterSecret) + { + this.mMasterSecret = masterSecret; + return this; + } + + public Builder SetPeerCertificate(Certificate peerCertificate) + { + this.mPeerCertificate = peerCertificate; + return this; + } + + public Builder SetPskIdentity(byte[] pskIdentity) + { + this.mPskIdentity = pskIdentity; + return this; + } + + public Builder SetSrpIdentity(byte[] srpIdentity) + { + this.mSrpIdentity = srpIdentity; + return this; + } + + public Builder SetServerExtensions(IDictionary serverExtensions) + { + if (serverExtensions == null) + { + mEncodedServerExtensions = null; + } + else + { + MemoryStream buf = new MemoryStream(); + TlsProtocol.WriteExtensions(buf, serverExtensions); + mEncodedServerExtensions = buf.ToArray(); + } + return this; + } + + private void Validate(bool condition, string parameter) + { + if (!condition) + throw new InvalidOperationException("Required session parameter '" + parameter + "' not configured"); + } + } + + private int mCipherSuite; + private byte mCompressionAlgorithm; + private byte[] mMasterSecret; + private Certificate mPeerCertificate; + private byte[] mPskIdentity; + private byte[] mSrpIdentity; + private byte[] mEncodedServerExtensions; + + private SessionParameters(int cipherSuite, byte compressionAlgorithm, byte[] masterSecret, + Certificate peerCertificate, byte[] pskIdentity, byte[] srpIdentity, byte[] encodedServerExtensions) + { + this.mCipherSuite = cipherSuite; + this.mCompressionAlgorithm = compressionAlgorithm; + this.mMasterSecret = Arrays.Clone(masterSecret); + this.mPeerCertificate = peerCertificate; + this.mPskIdentity = Arrays.Clone(pskIdentity); + this.mSrpIdentity = Arrays.Clone(srpIdentity); + this.mEncodedServerExtensions = encodedServerExtensions; + } + + public void Clear() + { + if (this.mMasterSecret != null) + { + Arrays.Fill(this.mMasterSecret, (byte)0); + } + } + + public SessionParameters Copy() + { + return new SessionParameters(mCipherSuite, mCompressionAlgorithm, mMasterSecret, mPeerCertificate, + mPskIdentity, mSrpIdentity, mEncodedServerExtensions); + } + + public int CipherSuite + { + get { return mCipherSuite; } + } + + public byte CompressionAlgorithm + { + get { return mCompressionAlgorithm; } + } + + public byte[] MasterSecret + { + get { return mMasterSecret; } + } + + public Certificate PeerCertificate + { + get { return mPeerCertificate; } + } + + public byte[] PskIdentity + { + get { return mPskIdentity; } + } + + public byte[] SrpIdentity + { + get { return mSrpIdentity; } + } + + public IDictionary ReadServerExtensions() + { + if (mEncodedServerExtensions == null) + return null; + + MemoryStream buf = new MemoryStream(mEncodedServerExtensions, false); + return TlsProtocol.ReadExtensions(buf); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/SignatureAlgorithm.cs b/bc-sharp-crypto/src/crypto/tls/SignatureAlgorithm.cs new file mode 100644 index 0000000..35b9617 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/SignatureAlgorithm.cs @@ -0,0 +1,15 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /** + * RFC 5246 7.4.1.4.1 (in RFC 2246, there were no specific values assigned) + */ + public abstract class SignatureAlgorithm + { + public const byte anonymous = 0; + public const byte rsa = 1; + public const byte dsa = 2; + public const byte ecdsa = 3; + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/SignatureAndHashAlgorithm.cs b/bc-sharp-crypto/src/crypto/tls/SignatureAndHashAlgorithm.cs new file mode 100644 index 0000000..f74205b --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/SignatureAndHashAlgorithm.cs @@ -0,0 +1,94 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /** + * RFC 5246 7.4.1.4.1 + */ + public class SignatureAndHashAlgorithm + { + protected readonly byte mHash; + protected readonly byte mSignature; + + /** + * @param hash {@link HashAlgorithm} + * @param signature {@link SignatureAlgorithm} + */ + public SignatureAndHashAlgorithm(byte hash, byte signature) + { + if (!TlsUtilities.IsValidUint8(hash)) + { + throw new ArgumentException("should be a uint8", "hash"); + } + if (!TlsUtilities.IsValidUint8(signature)) + { + throw new ArgumentException("should be a uint8", "signature"); + } + if (signature == SignatureAlgorithm.anonymous) + { + throw new ArgumentException("MUST NOT be \"anonymous\"", "signature"); + } + + this.mHash = hash; + this.mSignature = signature; + } + + /** + * @return {@link HashAlgorithm} + */ + public virtual byte Hash + { + get { return mHash; } + } + + /** + * @return {@link SignatureAlgorithm} + */ + public virtual byte Signature + { + get { return mSignature; } + } + + public override bool Equals(object obj) + { + if (!(obj is SignatureAndHashAlgorithm)) + { + return false; + } + SignatureAndHashAlgorithm other = (SignatureAndHashAlgorithm)obj; + return other.Hash == Hash && other.Signature == Signature; + } + + public override int GetHashCode() + { + return ((int)Hash << 16) | (int)Signature; + } + + /** + * Encode this {@link SignatureAndHashAlgorithm} to a {@link Stream}. + * + * @param output the {@link Stream} to encode to. + * @throws IOException + */ + public virtual void Encode(Stream output) + { + TlsUtilities.WriteUint8(Hash, output); + TlsUtilities.WriteUint8(Signature, output); + } + + /** + * Parse a {@link SignatureAndHashAlgorithm} from a {@link Stream}. + * + * @param input the {@link Stream} to parse from. + * @return a {@link SignatureAndHashAlgorithm} object. + * @throws IOException + */ + public static SignatureAndHashAlgorithm Parse(Stream input) + { + byte hash = TlsUtilities.ReadUint8(input); + byte signature = TlsUtilities.ReadUint8(input); + return new SignatureAndHashAlgorithm(hash, signature); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/SignerInputBuffer.cs b/bc-sharp-crypto/src/crypto/tls/SignerInputBuffer.cs new file mode 100644 index 0000000..7bc6962 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/SignerInputBuffer.cs @@ -0,0 +1,37 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + internal class SignerInputBuffer + : MemoryStream + { + internal void UpdateSigner(ISigner s) + { + Streams.WriteBufTo(this, new SigStream(s)); + } + + private class SigStream + : BaseOutputStream + { + private readonly ISigner s; + + internal SigStream(ISigner s) + { + this.s = s; + } + + public override void WriteByte(byte b) + { + s.Update(b); + } + + public override void Write(byte[] buf, int off, int len) + { + s.BlockUpdate(buf, off, len); + } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/SimulatedTlsSrpIdentityManager.cs b/bc-sharp-crypto/src/crypto/tls/SimulatedTlsSrpIdentityManager.cs new file mode 100644 index 0000000..3e9737c --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/SimulatedTlsSrpIdentityManager.cs @@ -0,0 +1,69 @@ +using System; + +using Org.BouncyCastle.Crypto.Agreement.Srp; +using Org.BouncyCastle.Crypto.Macs; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /** + * An implementation of {@link TlsSRPIdentityManager} that simulates the existence of "unknown" identities + * to obscure the fact that there is no verifier for them. + */ + public class SimulatedTlsSrpIdentityManager + : TlsSrpIdentityManager + { + private static readonly byte[] PREFIX_PASSWORD = Strings.ToByteArray("password"); + private static readonly byte[] PREFIX_SALT = Strings.ToByteArray("salt"); + + /** + * Create a {@link SimulatedTlsSRPIdentityManager} that implements the algorithm from RFC 5054 2.5.1.3 + * + * @param group the {@link SRP6GroupParameters} defining the group that SRP is operating in + * @param seedKey the secret "seed key" referred to in RFC 5054 2.5.1.3 + * @return an instance of {@link SimulatedTlsSRPIdentityManager} + */ + public static SimulatedTlsSrpIdentityManager GetRfc5054Default(Srp6GroupParameters group, byte[] seedKey) + { + Srp6VerifierGenerator verifierGenerator = new Srp6VerifierGenerator(); + verifierGenerator.Init(group, TlsUtilities.CreateHash(HashAlgorithm.sha1)); + + HMac mac = new HMac(TlsUtilities.CreateHash(HashAlgorithm.sha1)); + mac.Init(new KeyParameter(seedKey)); + + return new SimulatedTlsSrpIdentityManager(group, verifierGenerator, mac); + } + + protected readonly Srp6GroupParameters mGroup; + protected readonly Srp6VerifierGenerator mVerifierGenerator; + protected readonly IMac mMac; + + public SimulatedTlsSrpIdentityManager(Srp6GroupParameters group, Srp6VerifierGenerator verifierGenerator, IMac mac) + { + this.mGroup = group; + this.mVerifierGenerator = verifierGenerator; + this.mMac = mac; + } + + public virtual TlsSrpLoginParameters GetLoginParameters(byte[] identity) + { + mMac.BlockUpdate(PREFIX_SALT, 0, PREFIX_SALT.Length); + mMac.BlockUpdate(identity, 0, identity.Length); + + byte[] salt = new byte[mMac.GetMacSize()]; + mMac.DoFinal(salt, 0); + + mMac.BlockUpdate(PREFIX_PASSWORD, 0, PREFIX_PASSWORD.Length); + mMac.BlockUpdate(identity, 0, identity.Length); + + byte[] password = new byte[mMac.GetMacSize()]; + mMac.DoFinal(password, 0); + + BigInteger verifier = mVerifierGenerator.GenerateVerifier(salt, identity, password); + + return new TlsSrpLoginParameters(mGroup, verifier, salt); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/SrpTlsClient.cs b/bc-sharp-crypto/src/crypto/tls/SrpTlsClient.cs new file mode 100644 index 0000000..df16077 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/SrpTlsClient.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class SrpTlsClient + : AbstractTlsClient + { + protected TlsSrpGroupVerifier mGroupVerifier; + + protected byte[] mIdentity; + protected byte[] mPassword; + + public SrpTlsClient(byte[] identity, byte[] password) + : this(new DefaultTlsCipherFactory(), new DefaultTlsSrpGroupVerifier(), identity, password) + { + } + + public SrpTlsClient(TlsCipherFactory cipherFactory, byte[] identity, byte[] password) + : this(cipherFactory, new DefaultTlsSrpGroupVerifier(), identity, password) + { + } + + public SrpTlsClient(TlsCipherFactory cipherFactory, TlsSrpGroupVerifier groupVerifier, + byte[] identity, byte[] password) + : base(cipherFactory) + { + this.mGroupVerifier = groupVerifier; + this.mIdentity = Arrays.Clone(identity); + this.mPassword = Arrays.Clone(password); + } + + protected virtual bool RequireSrpServerExtension + { + // No explicit guidance in RFC 5054; by default an (empty) extension from server is optional + get { return false; } + } + + public override int[] GetCipherSuites() + { + return new int[] + { + CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA + }; + } + + public override IDictionary GetClientExtensions() + { + IDictionary clientExtensions = TlsExtensionsUtilities.EnsureExtensionsInitialised(base.GetClientExtensions()); + TlsSrpUtilities.AddSrpExtension(clientExtensions, this.mIdentity); + return clientExtensions; + } + + public override void ProcessServerExtensions(IDictionary serverExtensions) + { + if (!TlsUtilities.HasExpectedEmptyExtensionData(serverExtensions, ExtensionType.srp, + AlertDescription.illegal_parameter)) + { + if (RequireSrpServerExtension) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + + base.ProcessServerExtensions(serverExtensions); + } + + public override TlsKeyExchange GetKeyExchange() + { + int keyExchangeAlgorithm = TlsUtilities.GetKeyExchangeAlgorithm(mSelectedCipherSuite); + + switch (keyExchangeAlgorithm) + { + case KeyExchangeAlgorithm.SRP: + case KeyExchangeAlgorithm.SRP_DSS: + case KeyExchangeAlgorithm.SRP_RSA: + return CreateSrpKeyExchange(keyExchangeAlgorithm); + + default: + /* + * Note: internal error here; the TlsProtocol implementation verifies that the + * server-selected cipher suite was in the list of client-offered cipher suites, so if + * we now can't produce an implementation, we shouldn't have offered it! + */ + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + public override TlsAuthentication GetAuthentication() + { + /* + * Note: This method is not called unless a server certificate is sent, which may be the + * case e.g. for SRP_DSS or SRP_RSA key exchange. + */ + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + protected virtual TlsKeyExchange CreateSrpKeyExchange(int keyExchange) + { + return new TlsSrpKeyExchange(keyExchange, mSupportedSignatureAlgorithms, mGroupVerifier, mIdentity, mPassword); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/SrpTlsServer.cs b/bc-sharp-crypto/src/crypto/tls/SrpTlsServer.cs new file mode 100644 index 0000000..f978783 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/SrpTlsServer.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class SrpTlsServer + : AbstractTlsServer + { + protected TlsSrpIdentityManager mSrpIdentityManager; + + protected byte[] mSrpIdentity = null; + protected TlsSrpLoginParameters mLoginParameters = null; + + public SrpTlsServer(TlsSrpIdentityManager srpIdentityManager) + : this(new DefaultTlsCipherFactory(), srpIdentityManager) + { + } + + public SrpTlsServer(TlsCipherFactory cipherFactory, TlsSrpIdentityManager srpIdentityManager) + : base(cipherFactory) + { + this.mSrpIdentityManager = srpIdentityManager; + } + + protected virtual TlsSignerCredentials GetDsaSignerCredentials() + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + protected virtual TlsSignerCredentials GetRsaSignerCredentials() + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + protected override int[] GetCipherSuites() + { + return new int[] + { + CipherSuite.TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA, + CipherSuite.TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA, + CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA, + CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA, + CipherSuite.TLS_SRP_SHA_WITH_AES_256_CBC_SHA, + CipherSuite.TLS_SRP_SHA_WITH_AES_128_CBC_SHA + }; + } + + public override void ProcessClientExtensions(IDictionary clientExtensions) + { + base.ProcessClientExtensions(clientExtensions); + + this.mSrpIdentity = TlsSrpUtilities.GetSrpExtension(clientExtensions); + } + + public override int GetSelectedCipherSuite() + { + int cipherSuite = base.GetSelectedCipherSuite(); + + if (TlsSrpUtilities.IsSrpCipherSuite(cipherSuite)) + { + if (mSrpIdentity != null) + { + this.mLoginParameters = mSrpIdentityManager.GetLoginParameters(mSrpIdentity); + } + + if (mLoginParameters == null) + throw new TlsFatalAlert(AlertDescription.unknown_psk_identity); + } + + return cipherSuite; + } + + public override TlsCredentials GetCredentials() + { + int keyExchangeAlgorithm = TlsUtilities.GetKeyExchangeAlgorithm(mSelectedCipherSuite); + + switch (keyExchangeAlgorithm) + { + case KeyExchangeAlgorithm.SRP: + return null; + + case KeyExchangeAlgorithm.SRP_DSS: + return GetDsaSignerCredentials(); + + case KeyExchangeAlgorithm.SRP_RSA: + return GetRsaSignerCredentials(); + + default: + /* Note: internal error here; selected a key exchange we don't implement! */ + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + public override TlsKeyExchange GetKeyExchange() + { + int keyExchangeAlgorithm = TlsUtilities.GetKeyExchangeAlgorithm(mSelectedCipherSuite); + + switch (keyExchangeAlgorithm) + { + case KeyExchangeAlgorithm.SRP: + case KeyExchangeAlgorithm.SRP_DSS: + case KeyExchangeAlgorithm.SRP_RSA: + return CreateSrpKeyExchange(keyExchangeAlgorithm); + + default: + /* + * Note: internal error here; the TlsProtocol implementation verifies that the + * server-selected cipher suite was in the list of client-offered cipher suites, so if + * we now can't produce an implementation, we shouldn't have offered it! + */ + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + protected virtual TlsKeyExchange CreateSrpKeyExchange(int keyExchange) + { + return new TlsSrpKeyExchange(keyExchange, mSupportedSignatureAlgorithms, mSrpIdentity, mLoginParameters); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/SrtpProtectionProfile.cs b/bc-sharp-crypto/src/crypto/tls/SrtpProtectionProfile.cs new file mode 100644 index 0000000..6e9091b --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/SrtpProtectionProfile.cs @@ -0,0 +1,21 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public abstract class SrtpProtectionProfile + { + /* + * RFC 5764 4.1.2. + */ + public const int SRTP_AES128_CM_HMAC_SHA1_80 = 0x0001; + public const int SRTP_AES128_CM_HMAC_SHA1_32 = 0x0002; + public const int SRTP_NULL_HMAC_SHA1_80 = 0x0005; + public const int SRTP_NULL_HMAC_SHA1_32 = 0x0006; + + /* + * RFC 7714 14.2. + */ + public const int SRTP_AEAD_AES_128_GCM = 0x0007; + public const int SRTP_AEAD_AES_256_GCM = 0x0008; + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/Ssl3Mac.cs b/bc-sharp-crypto/src/crypto/tls/Ssl3Mac.cs new file mode 100644 index 0000000..8bdb342 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/Ssl3Mac.cs @@ -0,0 +1,110 @@ +using System; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /** + * HMAC implementation based on original internet draft for HMAC (RFC 2104) + * + * The difference is that padding is concatentated versus XORed with the key + * + * H(K + opad, H(K + ipad, text)) + */ + public class Ssl3Mac + : IMac + { + private const byte IPAD_BYTE = 0x36; + private const byte OPAD_BYTE = 0x5C; + + internal static readonly byte[] IPAD = GenPad(IPAD_BYTE, 48); + internal static readonly byte[] OPAD = GenPad(OPAD_BYTE, 48); + + private readonly IDigest digest; + private readonly int padLength; + + private byte[] secret; + + /** + * Base constructor for one of the standard digest algorithms that the byteLength of + * the algorithm is know for. Behaviour is undefined for digests other than MD5 or SHA1. + * + * @param digest the digest. + */ + public Ssl3Mac(IDigest digest) + { + this.digest = digest; + + if (digest.GetDigestSize() == 20) + { + this.padLength = 40; + } + else + { + this.padLength = 48; + } + } + + public virtual string AlgorithmName + { + get { return digest.AlgorithmName + "/SSL3MAC"; } + } + + public virtual void Init(ICipherParameters parameters) + { + secret = Arrays.Clone(((KeyParameter)parameters).GetKey()); + + Reset(); + } + + public virtual int GetMacSize() + { + return digest.GetDigestSize(); + } + + public virtual void Update(byte input) + { + digest.Update(input); + } + + public virtual void BlockUpdate(byte[] input, int inOff, int len) + { + digest.BlockUpdate(input, inOff, len); + } + + public virtual int DoFinal(byte[] output, int outOff) + { + byte[] tmp = new byte[digest.GetDigestSize()]; + digest.DoFinal(tmp, 0); + + digest.BlockUpdate(secret, 0, secret.Length); + digest.BlockUpdate(OPAD, 0, padLength); + digest.BlockUpdate(tmp, 0, tmp.Length); + + int len = digest.DoFinal(output, outOff); + + Reset(); + + return len; + } + + /** + * Reset the mac generator. + */ + public virtual void Reset() + { + digest.Reset(); + digest.BlockUpdate(secret, 0, secret.Length); + digest.BlockUpdate(IPAD, 0, padLength); + } + + private static byte[] GenPad(byte b, int count) + { + byte[] padding = new byte[count]; + Arrays.Fill(padding, b); + return padding; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/SupplementalDataEntry.cs b/bc-sharp-crypto/src/crypto/tls/SupplementalDataEntry.cs new file mode 100644 index 0000000..5adc4fa --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/SupplementalDataEntry.cs @@ -0,0 +1,26 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class SupplementalDataEntry + { + protected readonly int mDataType; + protected readonly byte[] mData; + + public SupplementalDataEntry(int dataType, byte[] data) + { + this.mDataType = dataType; + this.mData = data; + } + + public virtual int DataType + { + get { return mDataType; } + } + + public virtual byte[] Data + { + get { return mData; } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/SupplementalDataType.cs b/bc-sharp-crypto/src/crypto/tls/SupplementalDataType.cs new file mode 100644 index 0000000..79511c5 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/SupplementalDataType.cs @@ -0,0 +1,13 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /// RFC 4680 + public abstract class SupplementalDataType + { + /* + * RFC 4681 + */ + public const int user_mapping_data = 0; + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsAeadCipher.cs b/bc-sharp-crypto/src/crypto/tls/TlsAeadCipher.cs new file mode 100644 index 0000000..cc0575c --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsAeadCipher.cs @@ -0,0 +1,249 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Crypto.Modes; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class TlsAeadCipher + : TlsCipher + { + // TODO[draft-zauner-tls-aes-ocb-04] Apply data volume limit described in section 8.4 + + public const int NONCE_RFC5288 = 1; + + /* + * draft-zauner-tls-aes-ocb-04 specifies the nonce construction from draft-ietf-tls-chacha20-poly1305-04 + */ + internal const int NONCE_DRAFT_CHACHA20_POLY1305 = 2; + + protected readonly TlsContext context; + protected readonly int macSize; + // TODO SecurityParameters.record_iv_length + protected readonly int record_iv_length; + + protected readonly IAeadBlockCipher encryptCipher; + protected readonly IAeadBlockCipher decryptCipher; + + protected readonly byte[] encryptImplicitNonce, decryptImplicitNonce; + + protected readonly int nonceMode; + + /// + public TlsAeadCipher(TlsContext context, IAeadBlockCipher clientWriteCipher, IAeadBlockCipher serverWriteCipher, + int cipherKeySize, int macSize) + : this(context, clientWriteCipher, serverWriteCipher, cipherKeySize, macSize, NONCE_RFC5288) + { + } + + /// + internal TlsAeadCipher(TlsContext context, IAeadBlockCipher clientWriteCipher, IAeadBlockCipher serverWriteCipher, + int cipherKeySize, int macSize, int nonceMode) + { + if (!TlsUtilities.IsTlsV12(context)) + throw new TlsFatalAlert(AlertDescription.internal_error); + + this.nonceMode = nonceMode; + + // TODO SecurityParameters.fixed_iv_length + int fixed_iv_length; + + switch (nonceMode) + { + case NONCE_RFC5288: + fixed_iv_length = 4; + this.record_iv_length = 8; + break; + case NONCE_DRAFT_CHACHA20_POLY1305: + fixed_iv_length = 12; + this.record_iv_length = 0; + break; + default: + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + this.context = context; + this.macSize = macSize; + + int key_block_size = (2 * cipherKeySize) + (2 * fixed_iv_length); + + byte[] key_block = TlsUtilities.CalculateKeyBlock(context, key_block_size); + + int offset = 0; + + KeyParameter client_write_key = new KeyParameter(key_block, offset, cipherKeySize); + offset += cipherKeySize; + KeyParameter server_write_key = new KeyParameter(key_block, offset, cipherKeySize); + offset += cipherKeySize; + byte[] client_write_IV = Arrays.CopyOfRange(key_block, offset, offset + fixed_iv_length); + offset += fixed_iv_length; + byte[] server_write_IV = Arrays.CopyOfRange(key_block, offset, offset + fixed_iv_length); + offset += fixed_iv_length; + + if (offset != key_block_size) + throw new TlsFatalAlert(AlertDescription.internal_error); + + KeyParameter encryptKey, decryptKey; + if (context.IsServer) + { + this.encryptCipher = serverWriteCipher; + this.decryptCipher = clientWriteCipher; + this.encryptImplicitNonce = server_write_IV; + this.decryptImplicitNonce = client_write_IV; + encryptKey = server_write_key; + decryptKey = client_write_key; + } + else + { + this.encryptCipher = clientWriteCipher; + this.decryptCipher = serverWriteCipher; + this.encryptImplicitNonce = client_write_IV; + this.decryptImplicitNonce = server_write_IV; + encryptKey = client_write_key; + decryptKey = server_write_key; + } + + byte[] dummyNonce = new byte[fixed_iv_length + record_iv_length]; + + this.encryptCipher.Init(true, new AeadParameters(encryptKey, 8 * macSize, dummyNonce)); + this.decryptCipher.Init(false, new AeadParameters(decryptKey, 8 * macSize, dummyNonce)); + } + + public virtual int GetPlaintextLimit(int ciphertextLimit) + { + // TODO We ought to be able to ask the decryptCipher (independently of it's current state!) + return ciphertextLimit - macSize - record_iv_length; + } + + /// + public virtual byte[] EncodePlaintext(long seqNo, byte type, byte[] plaintext, int offset, int len) + { + byte[] nonce = new byte[encryptImplicitNonce.Length + record_iv_length]; + + switch (nonceMode) + { + case NONCE_RFC5288: + Array.Copy(encryptImplicitNonce, 0, nonce, 0, encryptImplicitNonce.Length); + // RFC 5288/6655: The nonce_explicit MAY be the 64-bit sequence number. + TlsUtilities.WriteUint64(seqNo, nonce, encryptImplicitNonce.Length); + break; + case NONCE_DRAFT_CHACHA20_POLY1305: + TlsUtilities.WriteUint64(seqNo, nonce, nonce.Length - 8); + for (int i = 0; i < encryptImplicitNonce.Length; ++i) + { + nonce[i] ^= encryptImplicitNonce[i]; + } + break; + default: + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + int plaintextOffset = offset; + int plaintextLength = len; + int ciphertextLength = encryptCipher.GetOutputSize(plaintextLength); + + byte[] output = new byte[record_iv_length + ciphertextLength]; + if (record_iv_length != 0) + { + Array.Copy(nonce, nonce.Length - record_iv_length, output, 0, record_iv_length); + } + int outputPos = record_iv_length; + + byte[] additionalData = GetAdditionalData(seqNo, type, plaintextLength); + AeadParameters parameters = new AeadParameters(null, 8 * macSize, nonce, additionalData); + + try + { + encryptCipher.Init(true, parameters); + outputPos += encryptCipher.ProcessBytes(plaintext, plaintextOffset, plaintextLength, output, outputPos); + outputPos += encryptCipher.DoFinal(output, outputPos); + } + catch (Exception e) + { + throw new TlsFatalAlert(AlertDescription.internal_error, e); + } + + if (outputPos != output.Length) + { + // NOTE: Existing AEAD cipher implementations all give exact output lengths + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + return output; + } + + /// + public virtual byte[] DecodeCiphertext(long seqNo, byte type, byte[] ciphertext, int offset, int len) + { + if (GetPlaintextLimit(len) < 0) + throw new TlsFatalAlert(AlertDescription.decode_error); + + byte[] nonce = new byte[decryptImplicitNonce.Length + record_iv_length]; + + switch (nonceMode) + { + case NONCE_RFC5288: + Array.Copy(decryptImplicitNonce, 0, nonce, 0, decryptImplicitNonce.Length); + Array.Copy(ciphertext, offset, nonce, nonce.Length - record_iv_length, record_iv_length); + break; + case NONCE_DRAFT_CHACHA20_POLY1305: + TlsUtilities.WriteUint64(seqNo, nonce, nonce.Length - 8); + for (int i = 0; i < decryptImplicitNonce.Length; ++i) + { + nonce[i] ^= decryptImplicitNonce[i]; + } + break; + default: + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + int ciphertextOffset = offset + record_iv_length; + int ciphertextLength = len - record_iv_length; + int plaintextLength = decryptCipher.GetOutputSize(ciphertextLength); + + byte[] output = new byte[plaintextLength]; + int outputPos = 0; + + byte[] additionalData = GetAdditionalData(seqNo, type, plaintextLength); + AeadParameters parameters = new AeadParameters(null, 8 * macSize, nonce, additionalData); + + try + { + decryptCipher.Init(false, parameters); + outputPos += decryptCipher.ProcessBytes(ciphertext, ciphertextOffset, ciphertextLength, output, outputPos); + outputPos += decryptCipher.DoFinal(output, outputPos); + } + catch (Exception e) + { + throw new TlsFatalAlert(AlertDescription.bad_record_mac, e); + } + + if (outputPos != output.Length) + { + // NOTE: Existing AEAD cipher implementations all give exact output lengths + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + return output; + } + + /// + protected virtual byte[] GetAdditionalData(long seqNo, byte type, int len) + { + /* + * additional_data = seq_num + TLSCompressed.type + TLSCompressed.version + + * TLSCompressed.length + */ + + byte[] additional_data = new byte[13]; + TlsUtilities.WriteUint64(seqNo, additional_data, 0); + TlsUtilities.WriteUint8(type, additional_data, 8); + TlsUtilities.WriteVersion(context.ServerVersion, additional_data, 9); + TlsUtilities.WriteUint16(len, additional_data, 11); + + return additional_data; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsAgreementCredentials.cs b/bc-sharp-crypto/src/crypto/tls/TlsAgreementCredentials.cs new file mode 100644 index 0000000..7c64072 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsAgreementCredentials.cs @@ -0,0 +1,12 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public interface TlsAgreementCredentials + : TlsCredentials + { + /// + byte[] GenerateAgreement(AsymmetricKeyParameter peerPublicKey); + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsAuthentication.cs b/bc-sharp-crypto/src/crypto/tls/TlsAuthentication.cs new file mode 100644 index 0000000..9aea5e4 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsAuthentication.cs @@ -0,0 +1,31 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public interface TlsAuthentication + { + /// + /// Called by the protocol handler to report the server certificate. + /// + /// + /// This method is responsible for certificate verification and validation + /// + /// The server received + /// + void NotifyServerCertificate(Certificate serverCertificate); + + /// + /// Return client credentials in response to server's certificate request + /// + /// + /// A containing server certificate request details + /// + /// + /// A to be used for client authentication + /// (or null for no client authentication) + /// + /// + TlsCredentials GetClientCredentials(CertificateRequest certificateRequest); + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsBlockCipher.cs b/bc-sharp-crypto/src/crypto/tls/TlsBlockCipher.cs new file mode 100644 index 0000000..76b476a --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsBlockCipher.cs @@ -0,0 +1,395 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /// + /// A generic TLS 1.0-1.2 / SSLv3 block cipher. This can be used for AES or 3DES for example. + /// + public class TlsBlockCipher + : TlsCipher + { + protected readonly TlsContext context; + protected readonly byte[] randomData; + protected readonly bool useExplicitIV; + protected readonly bool encryptThenMac; + + protected readonly IBlockCipher encryptCipher; + protected readonly IBlockCipher decryptCipher; + + protected readonly TlsMac mWriteMac; + protected readonly TlsMac mReadMac; + + public virtual TlsMac WriteMac + { + get { return mWriteMac; } + } + + public virtual TlsMac ReadMac + { + get { return mReadMac; } + } + + /// + public TlsBlockCipher(TlsContext context, IBlockCipher clientWriteCipher, IBlockCipher serverWriteCipher, + IDigest clientWriteDigest, IDigest serverWriteDigest, int cipherKeySize) + { + this.context = context; + + this.randomData = new byte[256]; + context.NonceRandomGenerator.NextBytes(randomData); + + this.useExplicitIV = TlsUtilities.IsTlsV11(context); + this.encryptThenMac = context.SecurityParameters.encryptThenMac; + + int key_block_size = (2 * cipherKeySize) + clientWriteDigest.GetDigestSize() + + serverWriteDigest.GetDigestSize(); + + // From TLS 1.1 onwards, block ciphers don't need client_write_IV + if (!useExplicitIV) + { + key_block_size += clientWriteCipher.GetBlockSize() + serverWriteCipher.GetBlockSize(); + } + + byte[] key_block = TlsUtilities.CalculateKeyBlock(context, key_block_size); + + int offset = 0; + + TlsMac clientWriteMac = new TlsMac(context, clientWriteDigest, key_block, offset, + clientWriteDigest.GetDigestSize()); + offset += clientWriteDigest.GetDigestSize(); + TlsMac serverWriteMac = new TlsMac(context, serverWriteDigest, key_block, offset, + serverWriteDigest.GetDigestSize()); + offset += serverWriteDigest.GetDigestSize(); + + KeyParameter client_write_key = new KeyParameter(key_block, offset, cipherKeySize); + offset += cipherKeySize; + KeyParameter server_write_key = new KeyParameter(key_block, offset, cipherKeySize); + offset += cipherKeySize; + + byte[] client_write_IV, server_write_IV; + if (useExplicitIV) + { + client_write_IV = new byte[clientWriteCipher.GetBlockSize()]; + server_write_IV = new byte[serverWriteCipher.GetBlockSize()]; + } + else + { + client_write_IV = Arrays.CopyOfRange(key_block, offset, offset + clientWriteCipher.GetBlockSize()); + offset += clientWriteCipher.GetBlockSize(); + server_write_IV = Arrays.CopyOfRange(key_block, offset, offset + serverWriteCipher.GetBlockSize()); + offset += serverWriteCipher.GetBlockSize(); + } + + if (offset != key_block_size) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + ICipherParameters encryptParams, decryptParams; + if (context.IsServer) + { + this.mWriteMac = serverWriteMac; + this.mReadMac = clientWriteMac; + this.encryptCipher = serverWriteCipher; + this.decryptCipher = clientWriteCipher; + encryptParams = new ParametersWithIV(server_write_key, server_write_IV); + decryptParams = new ParametersWithIV(client_write_key, client_write_IV); + } + else + { + this.mWriteMac = clientWriteMac; + this.mReadMac = serverWriteMac; + this.encryptCipher = clientWriteCipher; + this.decryptCipher = serverWriteCipher; + encryptParams = new ParametersWithIV(client_write_key, client_write_IV); + decryptParams = new ParametersWithIV(server_write_key, server_write_IV); + } + + this.encryptCipher.Init(true, encryptParams); + this.decryptCipher.Init(false, decryptParams); + } + + public virtual int GetPlaintextLimit(int ciphertextLimit) + { + int blockSize = encryptCipher.GetBlockSize(); + int macSize = mWriteMac.Size; + + int plaintextLimit = ciphertextLimit; + + // An explicit IV consumes 1 block + if (useExplicitIV) + { + plaintextLimit -= blockSize; + } + + // Leave room for the MAC, and require block-alignment + if (encryptThenMac) + { + plaintextLimit -= macSize; + plaintextLimit -= plaintextLimit % blockSize; + } + else + { + plaintextLimit -= plaintextLimit % blockSize; + plaintextLimit -= macSize; + } + + // Minimum 1 byte of padding + --plaintextLimit; + + return plaintextLimit; + } + + public virtual byte[] EncodePlaintext(long seqNo, byte type, byte[] plaintext, int offset, int len) + { + int blockSize = encryptCipher.GetBlockSize(); + int macSize = mWriteMac.Size; + + ProtocolVersion version = context.ServerVersion; + + int enc_input_length = len; + if (!encryptThenMac) + { + enc_input_length += macSize; + } + + int padding_length = blockSize - 1 - (enc_input_length % blockSize); + + /* + * Don't use variable-length padding with truncated MACs. + * + * See "Tag Size Does Matter: Attacks and Proofs for the TLS Record Protocol", Paterson, + * Ristenpart, Shrimpton. + */ + if (encryptThenMac || !context.SecurityParameters.truncatedHMac) + { + // TODO[DTLS] Consider supporting in DTLS (without exceeding send limit though) + if (!version.IsDtls && !version.IsSsl) + { + // Add a random number of extra blocks worth of padding + int maxExtraPadBlocks = (255 - padding_length) / blockSize; + int actualExtraPadBlocks = ChooseExtraPadBlocks(context.SecureRandom, maxExtraPadBlocks); + padding_length += actualExtraPadBlocks * blockSize; + } + } + + int totalSize = len + macSize + padding_length + 1; + if (useExplicitIV) + { + totalSize += blockSize; + } + + byte[] outBuf = new byte[totalSize]; + int outOff = 0; + + if (useExplicitIV) + { + byte[] explicitIV = new byte[blockSize]; + context.NonceRandomGenerator.NextBytes(explicitIV); + + encryptCipher.Init(true, new ParametersWithIV(null, explicitIV)); + + Array.Copy(explicitIV, 0, outBuf, outOff, blockSize); + outOff += blockSize; + } + + int blocks_start = outOff; + + Array.Copy(plaintext, offset, outBuf, outOff, len); + outOff += len; + + if (!encryptThenMac) + { + byte[] mac = mWriteMac.CalculateMac(seqNo, type, plaintext, offset, len); + Array.Copy(mac, 0, outBuf, outOff, mac.Length); + outOff += mac.Length; + } + + for (int i = 0; i <= padding_length; i++) + { + outBuf[outOff++] = (byte)padding_length; + } + + for (int i = blocks_start; i < outOff; i += blockSize) + { + encryptCipher.ProcessBlock(outBuf, i, outBuf, i); + } + + if (encryptThenMac) + { + byte[] mac = mWriteMac.CalculateMac(seqNo, type, outBuf, 0, outOff); + Array.Copy(mac, 0, outBuf, outOff, mac.Length); + outOff += mac.Length; + } + + // assert outBuf.length == outOff; + + return outBuf; + } + + /// + public virtual byte[] DecodeCiphertext(long seqNo, byte type, byte[] ciphertext, int offset, int len) + { + int blockSize = decryptCipher.GetBlockSize(); + int macSize = mReadMac.Size; + + int minLen = blockSize; + if (encryptThenMac) + { + minLen += macSize; + } + else + { + minLen = System.Math.Max(minLen, macSize + 1); + } + + if (useExplicitIV) + { + minLen += blockSize; + } + + if (len < minLen) + throw new TlsFatalAlert(AlertDescription.decode_error); + + int blocks_length = len; + if (encryptThenMac) + { + blocks_length -= macSize; + } + + if (blocks_length % blockSize != 0) + throw new TlsFatalAlert(AlertDescription.decryption_failed); + + if (encryptThenMac) + { + int end = offset + len; + byte[] receivedMac = Arrays.CopyOfRange(ciphertext, end - macSize, end); + byte[] calculatedMac = mReadMac.CalculateMac(seqNo, type, ciphertext, offset, len - macSize); + + bool badMacEtm = !Arrays.ConstantTimeAreEqual(calculatedMac, receivedMac); + if (badMacEtm) + { + /* + * RFC 7366 3. The MAC SHALL be evaluated before any further processing such as + * decryption is performed, and if the MAC verification fails, then processing SHALL + * terminate immediately. For TLS, a fatal bad_record_mac MUST be generated [2]. For + * DTLS, the record MUST be discarded, and a fatal bad_record_mac MAY be generated + * [4]. This immediate response to a bad MAC eliminates any timing channels that may + * be available through the use of manipulated packet data. + */ + throw new TlsFatalAlert(AlertDescription.bad_record_mac); + } + } + + if (useExplicitIV) + { + decryptCipher.Init(false, new ParametersWithIV(null, ciphertext, offset, blockSize)); + + offset += blockSize; + blocks_length -= blockSize; + } + + for (int i = 0; i < blocks_length; i += blockSize) + { + decryptCipher.ProcessBlock(ciphertext, offset + i, ciphertext, offset + i); + } + + // If there's anything wrong with the padding, this will return zero + int totalPad = CheckPaddingConstantTime(ciphertext, offset, blocks_length, blockSize, encryptThenMac ? 0 : macSize); + bool badMac = (totalPad == 0); + + int dec_output_length = blocks_length - totalPad; + + if (!encryptThenMac) + { + dec_output_length -= macSize; + int macInputLen = dec_output_length; + int macOff = offset + macInputLen; + byte[] receivedMac = Arrays.CopyOfRange(ciphertext, macOff, macOff + macSize); + byte[] calculatedMac = mReadMac.CalculateMacConstantTime(seqNo, type, ciphertext, offset, macInputLen, + blocks_length - macSize, randomData); + + badMac |= !Arrays.ConstantTimeAreEqual(calculatedMac, receivedMac); + } + + if (badMac) + throw new TlsFatalAlert(AlertDescription.bad_record_mac); + + return Arrays.CopyOfRange(ciphertext, offset, offset + dec_output_length); + } + + protected virtual int CheckPaddingConstantTime(byte[] buf, int off, int len, int blockSize, int macSize) + { + int end = off + len; + byte lastByte = buf[end - 1]; + int padlen = lastByte & 0xff; + int totalPad = padlen + 1; + + int dummyIndex = 0; + byte padDiff = 0; + + if ((TlsUtilities.IsSsl(context) && totalPad > blockSize) || (macSize + totalPad > len)) + { + totalPad = 0; + } + else + { + int padPos = end - totalPad; + do + { + padDiff |= (byte)(buf[padPos++] ^ lastByte); + } + while (padPos < end); + + dummyIndex = totalPad; + + if (padDiff != 0) + { + totalPad = 0; + } + } + + // Run some extra dummy checks so the number of checks is always constant + { + byte[] dummyPad = randomData; + while (dummyIndex < 256) + { + padDiff |= (byte)(dummyPad[dummyIndex++] ^ lastByte); + } + // Ensure the above loop is not eliminated + dummyPad[0] ^= padDiff; + } + + return totalPad; + } + + protected virtual int ChooseExtraPadBlocks(SecureRandom r, int max) + { + // return r.NextInt(max + 1); + + int x = r.NextInt(); + int n = LowestBitSet(x); + return System.Math.Min(n, max); + } + + protected virtual int LowestBitSet(int x) + { + if (x == 0) + return 32; + + uint ux = (uint)x; + int n = 0; + while ((ux & 1U) == 0) + { + ++n; + ux >>= 1; + } + return n; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsCipher.cs b/bc-sharp-crypto/src/crypto/tls/TlsCipher.cs new file mode 100644 index 0000000..7bd8573 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsCipher.cs @@ -0,0 +1,16 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public interface TlsCipher + { + int GetPlaintextLimit(int ciphertextLimit); + + /// + byte[] EncodePlaintext(long seqNo, byte type, byte[] plaintext, int offset, int len); + + /// + byte[] DecodeCiphertext(long seqNo, byte type, byte[] ciphertext, int offset, int len); + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsCipherFactory.cs b/bc-sharp-crypto/src/crypto/tls/TlsCipherFactory.cs new file mode 100644 index 0000000..4e1fe0e --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsCipherFactory.cs @@ -0,0 +1,11 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public interface TlsCipherFactory + { + /// + TlsCipher CreateCipher(TlsContext context, int encryptionAlgorithm, int macAlgorithm); + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsClient.cs b/bc-sharp-crypto/src/crypto/tls/TlsClient.cs new file mode 100644 index 0000000..73f1690 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsClient.cs @@ -0,0 +1,148 @@ +using System; +using System.Collections; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public interface TlsClient + : TlsPeer + { + /// + /// Called at the start of a new TLS session, before any other methods. + /// + /// + /// A + /// + void Init(TlsClientContext context); + + /// Return the session this client wants to resume, if any. + /// Note that the peer's certificate chain for the session (if any) may need to be periodically revalidated. + /// + /// A representing the resumable session to be used for this connection, + /// or null to use a new session. + /// + TlsSession GetSessionToResume(); + + /// + /// Return the to use for the TLSPlaintext.version field prior to + /// receiving the server version. NOTE: This method is not called for DTLS. + /// + /// + /// See RFC 5246 E.1.: "TLS clients that wish to negotiate with older servers MAY send any value + /// {03,XX} as the record layer version number. Typical values would be {03,00}, the lowest + /// version number supported by the client, and the value of ClientHello.client_version. No + /// single value will guarantee interoperability with all old servers, but this is a complex + /// topic beyond the scope of this document." + /// + /// The to use. + ProtocolVersion ClientHelloRecordLayerVersion { get; } + + ProtocolVersion ClientVersion { get; } + + bool IsFallback { get; } + + /// + /// Get the list of cipher suites that this client supports. + /// + /// + /// An array of values, each specifying a supported cipher suite. + /// + int[] GetCipherSuites(); + + /// + /// Get the list of compression methods that this client supports. + /// + /// + /// An array of values, each specifying a supported compression method. + /// + byte[] GetCompressionMethods(); + + /// + /// Get the (optional) table of client extensions to be included in (extended) client hello. + /// + /// + /// A (Int32 -> byte[]). May be null. + /// + /// + IDictionary GetClientExtensions(); + + /// + void NotifyServerVersion(ProtocolVersion selectedVersion); + + /// + /// Notifies the client of the session_id sent in the ServerHello. + /// + /// An array of + void NotifySessionID(byte[] sessionID); + + /// + /// Report the cipher suite that was selected by the server. + /// + /// + /// The protocol handler validates this value against the offered cipher suites + /// + /// + /// + /// A + /// + void NotifySelectedCipherSuite(int selectedCipherSuite); + + /// + /// Report the compression method that was selected by the server. + /// + /// + /// The protocol handler validates this value against the offered compression methods + /// + /// + /// + /// A + /// + void NotifySelectedCompressionMethod(byte selectedCompressionMethod); + + /// + /// Report the extensions from an extended server hello. + /// + /// + /// Will only be called if we returned a non-null result from . + /// + /// + /// A (Int32 -> byte[]) + /// + void ProcessServerExtensions(IDictionary serverExtensions); + + /// A list of + /// + void ProcessServerSupplementalData(IList serverSupplementalData); + + /// + /// Return an implementation of to negotiate the key exchange + /// part of the protocol. + /// + /// + /// A + /// + /// + TlsKeyExchange GetKeyExchange(); + + /// + /// Return an implementation of to handle authentication + /// part of the protocol. + /// + /// + TlsAuthentication GetAuthentication(); + + /// A list of + /// + IList GetClientSupplementalData(); + + /// RFC 5077 3.3. NewSessionTicket Handshake Message + /// + /// This method will be called (only) when a NewSessionTicket handshake message is received. The + /// ticket is opaque to the client and clients MUST NOT examine the ticket under the assumption + /// that it complies with e.g. RFC 5077 4. Recommended Ticket Construction. + /// + /// The ticket + /// + void NotifyNewSessionTicket(NewSessionTicket newSessionTicket); + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsClientContext.cs b/bc-sharp-crypto/src/crypto/tls/TlsClientContext.cs new file mode 100644 index 0000000..b077d0a --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsClientContext.cs @@ -0,0 +1,11 @@ +using System; + +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public interface TlsClientContext + : TlsContext + { + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsClientContextImpl.cs b/bc-sharp-crypto/src/crypto/tls/TlsClientContextImpl.cs new file mode 100644 index 0000000..674d689 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsClientContextImpl.cs @@ -0,0 +1,20 @@ +using System; + +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Tls +{ + internal class TlsClientContextImpl + : AbstractTlsContext, TlsClientContext + { + internal TlsClientContextImpl(SecureRandom secureRandom, SecurityParameters securityParameters) + : base(secureRandom, securityParameters) + { + } + + public override bool IsServer + { + get { return false; } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsClientProtocol.cs b/bc-sharp-crypto/src/crypto/tls/TlsClientProtocol.cs new file mode 100644 index 0000000..0ea84c0 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsClientProtocol.cs @@ -0,0 +1,912 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class TlsClientProtocol + : TlsProtocol + { + protected TlsClient mTlsClient = null; + internal TlsClientContextImpl mTlsClientContext = null; + + protected byte[] mSelectedSessionID = null; + + protected TlsKeyExchange mKeyExchange = null; + protected TlsAuthentication mAuthentication = null; + + protected CertificateStatus mCertificateStatus = null; + protected CertificateRequest mCertificateRequest = null; + + /** + * Constructor for blocking mode. + * @param stream The bi-directional stream of data to/from the server + * @param secureRandom Random number generator for various cryptographic functions + */ + public TlsClientProtocol(Stream stream, SecureRandom secureRandom) + : base(stream, secureRandom) + { + } + + /** + * Constructor for blocking mode. + * @param input The stream of data from the server + * @param output The stream of data to the server + * @param secureRandom Random number generator for various cryptographic functions + */ + public TlsClientProtocol(Stream input, Stream output, SecureRandom secureRandom) + : base(input, output, secureRandom) + { + } + + /** + * Constructor for non-blocking mode.
+ *
+ * When data is received, use {@link #offerInput(java.nio.ByteBuffer)} to + * provide the received ciphertext, then use + * {@link #readInput(byte[], int, int)} to read the corresponding cleartext.
+ *
+ * Similarly, when data needs to be sent, use + * {@link #offerOutput(byte[], int, int)} to provide the cleartext, then use + * {@link #readOutput(byte[], int, int)} to get the corresponding + * ciphertext. + * + * @param secureRandom + * Random number generator for various cryptographic functions + */ + public TlsClientProtocol(SecureRandom secureRandom) + : base(secureRandom) + { + } + + /** + * Initiates a TLS handshake in the role of client.
+ *
+ * In blocking mode, this will not return until the handshake is complete. + * In non-blocking mode, use {@link TlsPeer#NotifyHandshakeComplete()} to + * receive a callback when the handshake is complete. + * + * @param tlsClient The {@link TlsClient} to use for the handshake. + * @throws IOException If in blocking mode and handshake was not successful. + */ + public virtual void Connect(TlsClient tlsClient) + { + if (tlsClient == null) + throw new ArgumentNullException("tlsClient"); + if (this.mTlsClient != null) + throw new InvalidOperationException("'Connect' can only be called once"); + + this.mTlsClient = tlsClient; + + this.mSecurityParameters = new SecurityParameters(); + this.mSecurityParameters.entity = ConnectionEnd.client; + + this.mTlsClientContext = new TlsClientContextImpl(mSecureRandom, mSecurityParameters); + + this.mSecurityParameters.clientRandom = CreateRandomBlock(tlsClient.ShouldUseGmtUnixTime(), + mTlsClientContext.NonceRandomGenerator); + + this.mTlsClient.Init(mTlsClientContext); + this.mRecordStream.Init(mTlsClientContext); + + TlsSession sessionToResume = tlsClient.GetSessionToResume(); + if (sessionToResume != null && sessionToResume.IsResumable) + { + SessionParameters sessionParameters = sessionToResume.ExportSessionParameters(); + if (sessionParameters != null) + { + this.mTlsSession = sessionToResume; + this.mSessionParameters = sessionParameters; + } + } + + SendClientHelloMessage(); + this.mConnectionState = CS_CLIENT_HELLO; + + BlockForHandshake(); + } + + protected override void CleanupHandshake() + { + base.CleanupHandshake(); + + this.mSelectedSessionID = null; + this.mKeyExchange = null; + this.mAuthentication = null; + this.mCertificateStatus = null; + this.mCertificateRequest = null; + } + + protected override TlsContext Context + { + get { return mTlsClientContext; } + } + + internal override AbstractTlsContext ContextAdmin + { + get { return mTlsClientContext; } + } + + protected override TlsPeer Peer + { + get { return mTlsClient; } + } + + protected override void HandleHandshakeMessage(byte type, MemoryStream buf) + { + if (this.mResumedSession) + { + if (type != HandshakeType.finished || this.mConnectionState != CS_SERVER_HELLO) + throw new TlsFatalAlert(AlertDescription.unexpected_message); + + ProcessFinishedMessage(buf); + this.mConnectionState = CS_SERVER_FINISHED; + + SendFinishedMessage(); + this.mConnectionState = CS_CLIENT_FINISHED; + + CompleteHandshake(); + return; + } + + switch (type) + { + case HandshakeType.certificate: + { + switch (this.mConnectionState) + { + case CS_SERVER_HELLO: + case CS_SERVER_SUPPLEMENTAL_DATA: + { + if (this.mConnectionState == CS_SERVER_HELLO) + { + HandleSupplementalData(null); + } + + // Parse the Certificate message and Send to cipher suite + + this.mPeerCertificate = Certificate.Parse(buf); + + AssertEmpty(buf); + + // TODO[RFC 3546] Check whether empty certificates is possible, allowed, or excludes CertificateStatus + if (this.mPeerCertificate == null || this.mPeerCertificate.IsEmpty) + { + this.mAllowCertificateStatus = false; + } + + this.mKeyExchange.ProcessServerCertificate(this.mPeerCertificate); + + this.mAuthentication = mTlsClient.GetAuthentication(); + this.mAuthentication.NotifyServerCertificate(this.mPeerCertificate); + + break; + } + default: + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + this.mConnectionState = CS_SERVER_CERTIFICATE; + break; + } + case HandshakeType.certificate_status: + { + switch (this.mConnectionState) + { + case CS_SERVER_CERTIFICATE: + { + if (!this.mAllowCertificateStatus) + { + /* + * RFC 3546 3.6. If a server returns a "CertificateStatus" message, then the + * server MUST have included an extension of type "status_request" with empty + * "extension_data" in the extended server hello.. + */ + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + this.mCertificateStatus = CertificateStatus.Parse(buf); + + AssertEmpty(buf); + + // TODO[RFC 3546] Figure out how to provide this to the client/authentication. + + this.mConnectionState = CS_CERTIFICATE_STATUS; + break; + } + default: + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + break; + } + case HandshakeType.finished: + { + switch (this.mConnectionState) + { + case CS_CLIENT_FINISHED: + case CS_SERVER_SESSION_TICKET: + { + if (this.mConnectionState == CS_CLIENT_FINISHED && this.mExpectSessionTicket) + { + /* + * RFC 5077 3.3. This message MUST be sent if the server included a + * SessionTicket extension in the ServerHello. + */ + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + ProcessFinishedMessage(buf); + this.mConnectionState = CS_SERVER_FINISHED; + + CompleteHandshake(); + break; + } + default: + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + break; + } + case HandshakeType.server_hello: + { + switch (this.mConnectionState) + { + case CS_CLIENT_HELLO: + { + ReceiveServerHelloMessage(buf); + this.mConnectionState = CS_SERVER_HELLO; + + this.mRecordStream.NotifyHelloComplete(); + + ApplyMaxFragmentLengthExtension(); + + if (this.mResumedSession) + { + this.mSecurityParameters.masterSecret = Arrays.Clone(this.mSessionParameters.MasterSecret); + this.mRecordStream.SetPendingConnectionState(Peer.GetCompression(), Peer.GetCipher()); + + SendChangeCipherSpecMessage(); + } + else + { + InvalidateSession(); + + if (this.mSelectedSessionID.Length > 0) + { + this.mTlsSession = new TlsSessionImpl(this.mSelectedSessionID, null); + } + } + + break; + } + default: + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + break; + } + case HandshakeType.supplemental_data: + { + switch (this.mConnectionState) + { + case CS_SERVER_HELLO: + { + HandleSupplementalData(ReadSupplementalDataMessage(buf)); + break; + } + default: + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + break; + } + case HandshakeType.server_hello_done: + { + switch (this.mConnectionState) + { + case CS_SERVER_HELLO: + case CS_SERVER_SUPPLEMENTAL_DATA: + case CS_SERVER_CERTIFICATE: + case CS_CERTIFICATE_STATUS: + case CS_SERVER_KEY_EXCHANGE: + case CS_CERTIFICATE_REQUEST: + { + if (mConnectionState < CS_SERVER_SUPPLEMENTAL_DATA) + { + HandleSupplementalData(null); + } + + if (mConnectionState < CS_SERVER_CERTIFICATE) + { + // There was no server certificate message; check it's OK + this.mKeyExchange.SkipServerCredentials(); + this.mAuthentication = null; + } + + if (mConnectionState < CS_SERVER_KEY_EXCHANGE) + { + // There was no server key exchange message; check it's OK + this.mKeyExchange.SkipServerKeyExchange(); + } + + AssertEmpty(buf); + + this.mConnectionState = CS_SERVER_HELLO_DONE; + + this.mRecordStream.HandshakeHash.SealHashAlgorithms(); + + IList clientSupplementalData = mTlsClient.GetClientSupplementalData(); + if (clientSupplementalData != null) + { + SendSupplementalDataMessage(clientSupplementalData); + } + this.mConnectionState = CS_CLIENT_SUPPLEMENTAL_DATA; + + TlsCredentials clientCreds = null; + if (mCertificateRequest == null) + { + this.mKeyExchange.SkipClientCredentials(); + } + else + { + clientCreds = this.mAuthentication.GetClientCredentials(mCertificateRequest); + + if (clientCreds == null) + { + this.mKeyExchange.SkipClientCredentials(); + + /* + * RFC 5246 If no suitable certificate is available, the client MUST Send a + * certificate message containing no certificates. + * + * NOTE: In previous RFCs, this was SHOULD instead of MUST. + */ + SendCertificateMessage(Certificate.EmptyChain); + } + else + { + this.mKeyExchange.ProcessClientCredentials(clientCreds); + + SendCertificateMessage(clientCreds.Certificate); + } + } + + this.mConnectionState = CS_CLIENT_CERTIFICATE; + + /* + * Send the client key exchange message, depending on the key exchange we are using + * in our CipherSuite. + */ + SendClientKeyExchangeMessage(); + this.mConnectionState = CS_CLIENT_KEY_EXCHANGE; + + if (TlsUtilities.IsSsl(Context)) + { + EstablishMasterSecret(Context, mKeyExchange); + } + + TlsHandshakeHash prepareFinishHash = mRecordStream.PrepareToFinish(); + this.mSecurityParameters.sessionHash = GetCurrentPrfHash(Context, prepareFinishHash, null); + + if (!TlsUtilities.IsSsl(Context)) + { + EstablishMasterSecret(Context, mKeyExchange); + } + + mRecordStream.SetPendingConnectionState(Peer.GetCompression(), Peer.GetCipher()); + + if (clientCreds != null && clientCreds is TlsSignerCredentials) + { + TlsSignerCredentials signerCredentials = (TlsSignerCredentials)clientCreds; + + /* + * RFC 5246 4.7. digitally-signed element needs SignatureAndHashAlgorithm from TLS 1.2 + */ + SignatureAndHashAlgorithm signatureAndHashAlgorithm = TlsUtilities.GetSignatureAndHashAlgorithm( + Context, signerCredentials); + + byte[] hash; + if (signatureAndHashAlgorithm == null) + { + hash = mSecurityParameters.SessionHash; + } + else + { + hash = prepareFinishHash.GetFinalHash(signatureAndHashAlgorithm.Hash); + } + + byte[] signature = signerCredentials.GenerateCertificateSignature(hash); + DigitallySigned certificateVerify = new DigitallySigned(signatureAndHashAlgorithm, signature); + SendCertificateVerifyMessage(certificateVerify); + + this.mConnectionState = CS_CERTIFICATE_VERIFY; + } + + SendChangeCipherSpecMessage(); + SendFinishedMessage(); + break; + } + default: + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + this.mConnectionState = CS_CLIENT_FINISHED; + break; + } + case HandshakeType.server_key_exchange: + { + switch (this.mConnectionState) + { + case CS_SERVER_HELLO: + case CS_SERVER_SUPPLEMENTAL_DATA: + case CS_SERVER_CERTIFICATE: + case CS_CERTIFICATE_STATUS: + { + if (mConnectionState < CS_SERVER_SUPPLEMENTAL_DATA) + { + HandleSupplementalData(null); + } + + if (mConnectionState < CS_SERVER_CERTIFICATE) + { + // There was no server certificate message; check it's OK + this.mKeyExchange.SkipServerCredentials(); + this.mAuthentication = null; + } + + this.mKeyExchange.ProcessServerKeyExchange(buf); + + AssertEmpty(buf); + break; + } + default: + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + this.mConnectionState = CS_SERVER_KEY_EXCHANGE; + break; + } + case HandshakeType.certificate_request: + { + switch (this.mConnectionState) + { + case CS_SERVER_CERTIFICATE: + case CS_CERTIFICATE_STATUS: + case CS_SERVER_KEY_EXCHANGE: + { + if (this.mConnectionState != CS_SERVER_KEY_EXCHANGE) + { + // There was no server key exchange message; check it's OK + this.mKeyExchange.SkipServerKeyExchange(); + } + + if (this.mAuthentication == null) + { + /* + * RFC 2246 7.4.4. It is a fatal handshake_failure alert for an anonymous server + * to request client identification. + */ + throw new TlsFatalAlert(AlertDescription.handshake_failure); + } + + this.mCertificateRequest = CertificateRequest.Parse(Context, buf); + + AssertEmpty(buf); + + this.mKeyExchange.ValidateCertificateRequest(this.mCertificateRequest); + + /* + * TODO Give the client a chance to immediately select the CertificateVerify hash + * algorithm here to avoid tracking the other hash algorithms unnecessarily? + */ + TlsUtilities.TrackHashAlgorithms(this.mRecordStream.HandshakeHash, + this.mCertificateRequest.SupportedSignatureAlgorithms); + + break; + } + default: + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + this.mConnectionState = CS_CERTIFICATE_REQUEST; + break; + } + case HandshakeType.session_ticket: + { + switch (this.mConnectionState) + { + case CS_CLIENT_FINISHED: + { + if (!this.mExpectSessionTicket) + { + /* + * RFC 5077 3.3. This message MUST NOT be sent if the server did not include a + * SessionTicket extension in the ServerHello. + */ + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + /* + * RFC 5077 3.4. If the client receives a session ticket from the server, then it + * discards any Session ID that was sent in the ServerHello. + */ + InvalidateSession(); + + ReceiveNewSessionTicketMessage(buf); + break; + } + default: + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + this.mConnectionState = CS_SERVER_SESSION_TICKET; + break; + } + case HandshakeType.hello_request: + { + AssertEmpty(buf); + + /* + * RFC 2246 7.4.1.1 Hello request This message will be ignored by the client if the + * client is currently negotiating a session. This message may be ignored by the client + * if it does not wish to renegotiate a session, or the client may, if it wishes, + * respond with a no_renegotiation alert. + */ + if (this.mConnectionState == CS_END) + { + RefuseRenegotiation(); + } + break; + } + case HandshakeType.client_hello: + case HandshakeType.client_key_exchange: + case HandshakeType.certificate_verify: + case HandshakeType.hello_verify_request: + default: + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + } + + protected virtual void HandleSupplementalData(IList serverSupplementalData) + { + this.mTlsClient.ProcessServerSupplementalData(serverSupplementalData); + this.mConnectionState = CS_SERVER_SUPPLEMENTAL_DATA; + + this.mKeyExchange = mTlsClient.GetKeyExchange(); + this.mKeyExchange.Init(Context); + } + + protected virtual void ReceiveNewSessionTicketMessage(MemoryStream buf) + { + NewSessionTicket newSessionTicket = NewSessionTicket.Parse(buf); + + AssertEmpty(buf); + + mTlsClient.NotifyNewSessionTicket(newSessionTicket); + } + + protected virtual void ReceiveServerHelloMessage(MemoryStream buf) + { + { + ProtocolVersion server_version = TlsUtilities.ReadVersion(buf); + if (server_version.IsDtls) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + + // Check that this matches what the server is Sending in the record layer + if (!server_version.Equals(this.mRecordStream.ReadVersion)) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + + ProtocolVersion client_version = Context.ClientVersion; + if (!server_version.IsEqualOrEarlierVersionOf(client_version)) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + + this.mRecordStream.SetWriteVersion(server_version); + ContextAdmin.SetServerVersion(server_version); + this.mTlsClient.NotifyServerVersion(server_version); + } + + /* + * Read the server random + */ + this.mSecurityParameters.serverRandom = TlsUtilities.ReadFully(32, buf); + + this.mSelectedSessionID = TlsUtilities.ReadOpaque8(buf); + if (this.mSelectedSessionID.Length > 32) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + this.mTlsClient.NotifySessionID(this.mSelectedSessionID); + this.mResumedSession = this.mSelectedSessionID.Length > 0 && this.mTlsSession != null + && Arrays.AreEqual(this.mSelectedSessionID, this.mTlsSession.SessionID); + + /* + * Find out which CipherSuite the server has chosen and check that it was one of the offered + * ones, and is a valid selection for the negotiated version. + */ + int selectedCipherSuite = TlsUtilities.ReadUint16(buf); + if (!Arrays.Contains(this.mOfferedCipherSuites, selectedCipherSuite) + || selectedCipherSuite == CipherSuite.TLS_NULL_WITH_NULL_NULL + || CipherSuite.IsScsv(selectedCipherSuite) + || !TlsUtilities.IsValidCipherSuiteForVersion(selectedCipherSuite, Context.ServerVersion)) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + this.mTlsClient.NotifySelectedCipherSuite(selectedCipherSuite); + + /* + * Find out which CompressionMethod the server has chosen and check that it was one of the + * offered ones. + */ + byte selectedCompressionMethod = TlsUtilities.ReadUint8(buf); + if (!Arrays.Contains(this.mOfferedCompressionMethods, selectedCompressionMethod)) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + this.mTlsClient.NotifySelectedCompressionMethod(selectedCompressionMethod); + + /* + * RFC3546 2.2 The extended server hello message format MAY be sent in place of the server + * hello message when the client has requested extended functionality via the extended + * client hello message specified in Section 2.1. ... Note that the extended server hello + * message is only sent in response to an extended client hello message. This prevents the + * possibility that the extended server hello message could "break" existing TLS 1.0 + * clients. + */ + this.mServerExtensions = ReadExtensions(buf); + + /* + * RFC 3546 2.2 Note that the extended server hello message is only sent in response to an + * extended client hello message. + * + * However, see RFC 5746 exception below. We always include the SCSV, so an Extended Server + * Hello is always allowed. + */ + if (this.mServerExtensions != null) + { + foreach (int extType in this.mServerExtensions.Keys) + { + /* + * RFC 5746 3.6. Note that Sending a "renegotiation_info" extension in response to a + * ClientHello containing only the SCSV is an explicit exception to the prohibition + * in RFC 5246, Section 7.4.1.4, on the server Sending unsolicited extensions and is + * only allowed because the client is signaling its willingness to receive the + * extension via the TLS_EMPTY_RENEGOTIATION_INFO_SCSV SCSV. + */ + if (extType == ExtensionType.renegotiation_info) + continue; + + /* + * RFC 5246 7.4.1.4 An extension type MUST NOT appear in the ServerHello unless the + * same extension type appeared in the corresponding ClientHello. If a client + * receives an extension type in ServerHello that it did not request in the + * associated ClientHello, it MUST abort the handshake with an unsupported_extension + * fatal alert. + */ + if (null == TlsUtilities.GetExtensionData(this.mClientExtensions, extType)) + throw new TlsFatalAlert(AlertDescription.unsupported_extension); + + /* + * RFC 3546 2.3. If [...] the older session is resumed, then the server MUST ignore + * extensions appearing in the client hello, and Send a server hello containing no + * extensions[.] + */ + if (this.mResumedSession) + { + // TODO[compat-gnutls] GnuTLS test server Sends server extensions e.g. ec_point_formats + // TODO[compat-openssl] OpenSSL test server Sends server extensions e.g. ec_point_formats + // TODO[compat-polarssl] PolarSSL test server Sends server extensions e.g. ec_point_formats + // throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + } + } + + /* + * RFC 5746 3.4. Client Behavior: Initial Handshake + */ + { + /* + * When a ServerHello is received, the client MUST check if it includes the + * "renegotiation_info" extension: + */ + byte[] renegExtData = TlsUtilities.GetExtensionData(this.mServerExtensions, ExtensionType.renegotiation_info); + if (renegExtData != null) + { + /* + * If the extension is present, set the secure_renegotiation flag to TRUE. The + * client MUST then verify that the length of the "renegotiated_connection" + * field is zero, and if it is not, MUST abort the handshake (by Sending a fatal + * handshake_failure alert). + */ + this.mSecureRenegotiation = true; + + if (!Arrays.ConstantTimeAreEqual(renegExtData, CreateRenegotiationInfo(TlsUtilities.EmptyBytes))) + throw new TlsFatalAlert(AlertDescription.handshake_failure); + } + } + + // TODO[compat-gnutls] GnuTLS test server fails to Send renegotiation_info extension when resuming + this.mTlsClient.NotifySecureRenegotiation(this.mSecureRenegotiation); + + IDictionary sessionClientExtensions = mClientExtensions, sessionServerExtensions = mServerExtensions; + if (this.mResumedSession) + { + if (selectedCipherSuite != this.mSessionParameters.CipherSuite + || selectedCompressionMethod != this.mSessionParameters.CompressionAlgorithm) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + + sessionClientExtensions = null; + sessionServerExtensions = this.mSessionParameters.ReadServerExtensions(); + } + + this.mSecurityParameters.cipherSuite = selectedCipherSuite; + this.mSecurityParameters.compressionAlgorithm = selectedCompressionMethod; + + if (sessionServerExtensions != null) + { + { + /* + * RFC 7366 3. If a server receives an encrypt-then-MAC request extension from a client + * and then selects a stream or Authenticated Encryption with Associated Data (AEAD) + * ciphersuite, it MUST NOT send an encrypt-then-MAC response extension back to the + * client. + */ + bool serverSentEncryptThenMAC = TlsExtensionsUtilities.HasEncryptThenMacExtension(sessionServerExtensions); + if (serverSentEncryptThenMAC && !TlsUtilities.IsBlockCipherSuite(selectedCipherSuite)) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + + this.mSecurityParameters.encryptThenMac = serverSentEncryptThenMAC; + } + + this.mSecurityParameters.extendedMasterSecret = TlsExtensionsUtilities.HasExtendedMasterSecretExtension(sessionServerExtensions); + + this.mSecurityParameters.maxFragmentLength = ProcessMaxFragmentLengthExtension(sessionClientExtensions, + sessionServerExtensions, AlertDescription.illegal_parameter); + + this.mSecurityParameters.truncatedHMac = TlsExtensionsUtilities.HasTruncatedHMacExtension(sessionServerExtensions); + + /* + * TODO It's surprising that there's no provision to allow a 'fresh' CertificateStatus to be sent in + * a session resumption handshake. + */ + this.mAllowCertificateStatus = !this.mResumedSession + && TlsUtilities.HasExpectedEmptyExtensionData(sessionServerExtensions, ExtensionType.status_request, + AlertDescription.illegal_parameter); + + this.mExpectSessionTicket = !this.mResumedSession + && TlsUtilities.HasExpectedEmptyExtensionData(sessionServerExtensions, ExtensionType.session_ticket, + AlertDescription.illegal_parameter); + } + + /* + * TODO[session-hash] + * + * draft-ietf-tls-session-hash-04 4. Clients and servers SHOULD NOT accept handshakes + * that do not use the extended master secret [..]. (and see 5.2, 5.3) + */ + + if (sessionClientExtensions != null) + { + this.mTlsClient.ProcessServerExtensions(sessionServerExtensions); + } + + this.mSecurityParameters.prfAlgorithm = GetPrfAlgorithm(Context, this.mSecurityParameters.CipherSuite); + + /* + * RFC 5246 7.4.9. Any cipher suite which does not explicitly specify + * verify_data_length has a verify_data_length equal to 12. This includes all + * existing cipher suites. + */ + this.mSecurityParameters.verifyDataLength = 12; + } + + protected virtual void SendCertificateVerifyMessage(DigitallySigned certificateVerify) + { + HandshakeMessage message = new HandshakeMessage(HandshakeType.certificate_verify); + + certificateVerify.Encode(message); + + message.WriteToRecordStream(this); + } + + protected virtual void SendClientHelloMessage() + { + this.mRecordStream.SetWriteVersion(this.mTlsClient.ClientHelloRecordLayerVersion); + + ProtocolVersion client_version = this.mTlsClient.ClientVersion; + if (client_version.IsDtls) + throw new TlsFatalAlert(AlertDescription.internal_error); + + ContextAdmin.SetClientVersion(client_version); + + /* + * TODO RFC 5077 3.4. When presenting a ticket, the client MAY generate and include a + * Session ID in the TLS ClientHello. + */ + byte[] session_id = TlsUtilities.EmptyBytes; + if (this.mTlsSession != null) + { + session_id = this.mTlsSession.SessionID; + if (session_id == null || session_id.Length > 32) + { + session_id = TlsUtilities.EmptyBytes; + } + } + + bool fallback = this.mTlsClient.IsFallback; + + this.mOfferedCipherSuites = this.mTlsClient.GetCipherSuites(); + + this.mOfferedCompressionMethods = this.mTlsClient.GetCompressionMethods(); + + if (session_id.Length > 0 && this.mSessionParameters != null) + { + if (!Arrays.Contains(this.mOfferedCipherSuites, mSessionParameters.CipherSuite) + || !Arrays.Contains(this.mOfferedCompressionMethods, mSessionParameters.CompressionAlgorithm)) + { + session_id = TlsUtilities.EmptyBytes; + } + } + + this.mClientExtensions = this.mTlsClient.GetClientExtensions(); + + HandshakeMessage message = new HandshakeMessage(HandshakeType.client_hello); + + TlsUtilities.WriteVersion(client_version, message); + + message.Write(this.mSecurityParameters.ClientRandom); + + TlsUtilities.WriteOpaque8(session_id, message); + + // Cipher Suites (and SCSV) + { + /* + * RFC 5746 3.4. The client MUST include either an empty "renegotiation_info" extension, + * or the TLS_EMPTY_RENEGOTIATION_INFO_SCSV signaling cipher suite value in the + * ClientHello. Including both is NOT RECOMMENDED. + */ + byte[] renegExtData = TlsUtilities.GetExtensionData(mClientExtensions, ExtensionType.renegotiation_info); + bool noRenegExt = (null == renegExtData); + + bool noRenegScsv = !Arrays.Contains(mOfferedCipherSuites, CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV); + + if (noRenegExt && noRenegScsv) + { + // TODO Consider whether to default to a client extension instead + // this.mClientExtensions = TlsExtensionsUtilities.EnsureExtensionsInitialised(this.mClientExtensions); + // this.mClientExtensions[ExtensionType.renegotiation_info] = CreateRenegotiationInfo(TlsUtilities.EmptyBytes); + this.mOfferedCipherSuites = Arrays.Append(mOfferedCipherSuites, CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV); + } + + /* + * RFC 7507 4. If a client sends a ClientHello.client_version containing a lower value + * than the latest (highest-valued) version supported by the client, it SHOULD include + * the TLS_FALLBACK_SCSV cipher suite value in ClientHello.cipher_suites [..]. (The + * client SHOULD put TLS_FALLBACK_SCSV after all cipher suites that it actually intends + * to negotiate.) + */ + if (fallback && !Arrays.Contains(mOfferedCipherSuites, CipherSuite.TLS_FALLBACK_SCSV)) + { + this.mOfferedCipherSuites = Arrays.Append(mOfferedCipherSuites, CipherSuite.TLS_FALLBACK_SCSV); + } + + TlsUtilities.WriteUint16ArrayWithUint16Length(mOfferedCipherSuites, message); + } + + TlsUtilities.WriteUint8ArrayWithUint8Length(mOfferedCompressionMethods, message); + + if (mClientExtensions != null) + { + WriteExtensions(message, mClientExtensions); + } + + message.WriteToRecordStream(this); + } + + protected virtual void SendClientKeyExchangeMessage() + { + HandshakeMessage message = new HandshakeMessage(HandshakeType.client_key_exchange); + + this.mKeyExchange.GenerateClientKeyExchange(message); + + message.WriteToRecordStream(this); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsCompression.cs b/bc-sharp-crypto/src/crypto/tls/TlsCompression.cs new file mode 100644 index 0000000..177d64b --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsCompression.cs @@ -0,0 +1,12 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public interface TlsCompression + { + Stream Compress(Stream output); + + Stream Decompress(Stream output); + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsContext.cs b/bc-sharp-crypto/src/crypto/tls/TlsContext.cs new file mode 100644 index 0000000..d066723 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsContext.cs @@ -0,0 +1,45 @@ +using System; + +using Org.BouncyCastle.Crypto.Prng; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public interface TlsContext + { + IRandomGenerator NonceRandomGenerator { get; } + + SecureRandom SecureRandom { get; } + + SecurityParameters SecurityParameters { get; } + + bool IsServer { get; } + + ProtocolVersion ClientVersion { get; } + + ProtocolVersion ServerVersion { get; } + + /** + * Used to get the resumable session, if any, used by this connection. Only available after the + * handshake has successfully completed. + * + * @return A {@link TlsSession} representing the resumable session used by this connection, or + * null if no resumable session available. + * @see TlsPeer#NotifyHandshakeComplete() + */ + TlsSession ResumableSession { get; } + + object UserObject { get; set; } + + /** + * Export keying material according to RFC 5705: "Keying Material Exporters for TLS". + * + * @param asciiLabel indicates which application will use the exported keys. + * @param context_value allows the application using the exporter to mix its own data with the TLS PRF for + * the exporter output. + * @param length the number of bytes to generate + * @return a pseudorandom bit string of 'length' bytes generated from the master_secret. + */ + byte[] ExportKeyingMaterial(string asciiLabel, byte[] context_value, int length); + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsCredentials.cs b/bc-sharp-crypto/src/crypto/tls/TlsCredentials.cs new file mode 100644 index 0000000..5c5f1c0 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsCredentials.cs @@ -0,0 +1,9 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public interface TlsCredentials + { + Certificate Certificate { get; } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsDHKeyExchange.cs b/bc-sharp-crypto/src/crypto/tls/TlsDHKeyExchange.cs new file mode 100644 index 0000000..d179068 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsDHKeyExchange.cs @@ -0,0 +1,259 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /// (D)TLS DH key exchange. + public class TlsDHKeyExchange + : AbstractTlsKeyExchange + { + protected TlsSigner mTlsSigner; + protected DHParameters mDHParameters; + + protected AsymmetricKeyParameter mServerPublicKey; + protected TlsAgreementCredentials mAgreementCredentials; + + protected DHPrivateKeyParameters mDHAgreePrivateKey; + protected DHPublicKeyParameters mDHAgreePublicKey; + + public TlsDHKeyExchange(int keyExchange, IList supportedSignatureAlgorithms, DHParameters dhParameters) + : base(keyExchange, supportedSignatureAlgorithms) + { + switch (keyExchange) + { + case KeyExchangeAlgorithm.DH_anon: + case KeyExchangeAlgorithm.DH_RSA: + case KeyExchangeAlgorithm.DH_DSS: + this.mTlsSigner = null; + break; + case KeyExchangeAlgorithm.DHE_RSA: + this.mTlsSigner = new TlsRsaSigner(); + break; + case KeyExchangeAlgorithm.DHE_DSS: + this.mTlsSigner = new TlsDssSigner(); + break; + default: + throw new InvalidOperationException("unsupported key exchange algorithm"); + } + + this.mDHParameters = dhParameters; + } + + public override void Init(TlsContext context) + { + base.Init(context); + + if (this.mTlsSigner != null) + { + this.mTlsSigner.Init(context); + } + } + + public override void SkipServerCredentials() + { + if (mKeyExchange != KeyExchangeAlgorithm.DH_anon) + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + public override void ProcessServerCertificate(Certificate serverCertificate) + { + if (mKeyExchange == KeyExchangeAlgorithm.DH_anon) + throw new TlsFatalAlert(AlertDescription.unexpected_message); + if (serverCertificate.IsEmpty) + throw new TlsFatalAlert(AlertDescription.bad_certificate); + + X509CertificateStructure x509Cert = serverCertificate.GetCertificateAt(0); + + SubjectPublicKeyInfo keyInfo = x509Cert.SubjectPublicKeyInfo; + try + { + this.mServerPublicKey = PublicKeyFactory.CreateKey(keyInfo); + } + catch (Exception e) + { + throw new TlsFatalAlert(AlertDescription.unsupported_certificate, e); + } + + if (mTlsSigner == null) + { + try + { + this.mDHAgreePublicKey = TlsDHUtilities.ValidateDHPublicKey((DHPublicKeyParameters)this.mServerPublicKey); + this.mDHParameters = ValidateDHParameters(mDHAgreePublicKey.Parameters); + } + catch (InvalidCastException e) + { + throw new TlsFatalAlert(AlertDescription.certificate_unknown, e); + } + + TlsUtilities.ValidateKeyUsage(x509Cert, KeyUsage.KeyAgreement); + } + else + { + if (!mTlsSigner.IsValidPublicKey(this.mServerPublicKey)) + { + throw new TlsFatalAlert(AlertDescription.certificate_unknown); + } + + TlsUtilities.ValidateKeyUsage(x509Cert, KeyUsage.DigitalSignature); + } + + base.ProcessServerCertificate(serverCertificate); + } + + public override bool RequiresServerKeyExchange + { + get + { + switch (mKeyExchange) + { + case KeyExchangeAlgorithm.DH_anon: + case KeyExchangeAlgorithm.DHE_DSS: + case KeyExchangeAlgorithm.DHE_RSA: + return true; + default: + return false; + } + } + } + + public override byte[] GenerateServerKeyExchange() + { + if (!RequiresServerKeyExchange) + return null; + + // DH_anon is handled here, DHE_* in a subclass + + MemoryStream buf = new MemoryStream(); + this.mDHAgreePrivateKey = TlsDHUtilities.GenerateEphemeralServerKeyExchange(mContext.SecureRandom, + this.mDHParameters, buf); + return buf.ToArray(); + } + + public override void ProcessServerKeyExchange(Stream input) + { + if (!RequiresServerKeyExchange) + throw new TlsFatalAlert(AlertDescription.unexpected_message); + + // DH_anon is handled here, DHE_* in a subclass + + ServerDHParams dhParams = ServerDHParams.Parse(input); + + this.mDHAgreePublicKey = TlsDHUtilities.ValidateDHPublicKey(dhParams.PublicKey); + this.mDHParameters = ValidateDHParameters(mDHAgreePublicKey.Parameters); + } + + public override void ValidateCertificateRequest(CertificateRequest certificateRequest) + { + if (mKeyExchange == KeyExchangeAlgorithm.DH_anon) + throw new TlsFatalAlert(AlertDescription.handshake_failure); + + byte[] types = certificateRequest.CertificateTypes; + for (int i = 0; i < types.Length; ++i) + { + switch (types[i]) + { + case ClientCertificateType.rsa_sign: + case ClientCertificateType.dss_sign: + case ClientCertificateType.rsa_fixed_dh: + case ClientCertificateType.dss_fixed_dh: + case ClientCertificateType.ecdsa_sign: + break; + default: + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + } + } + + public override void ProcessClientCredentials(TlsCredentials clientCredentials) + { + if (mKeyExchange == KeyExchangeAlgorithm.DH_anon) + throw new TlsFatalAlert(AlertDescription.internal_error); + + if (clientCredentials is TlsAgreementCredentials) + { + // TODO Validate client cert has matching parameters (see 'areCompatibleParameters')? + + this.mAgreementCredentials = (TlsAgreementCredentials)clientCredentials; + } + else if (clientCredentials is TlsSignerCredentials) + { + // OK + } + else + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + public override void GenerateClientKeyExchange(Stream output) + { + /* + * RFC 2246 7.4.7.2 If the client certificate already contains a suitable Diffie-Hellman + * key, then Yc is implicit and does not need to be sent again. In this case, the Client Key + * Exchange message will be sent, but will be empty. + */ + if (mAgreementCredentials == null) + { + this.mDHAgreePrivateKey = TlsDHUtilities.GenerateEphemeralClientKeyExchange(mContext.SecureRandom, + mDHParameters, output); + } + } + + public override void ProcessClientCertificate(Certificate clientCertificate) + { + if (mKeyExchange == KeyExchangeAlgorithm.DH_anon) + throw new TlsFatalAlert(AlertDescription.unexpected_message); + + // TODO Extract the public key + // TODO If the certificate is 'fixed', take the public key as dhAgreePublicKey + } + + public override void ProcessClientKeyExchange(Stream input) + { + if (mDHAgreePublicKey != null) + { + // For dss_fixed_dh and rsa_fixed_dh, the key arrived in the client certificate + return; + } + + BigInteger Yc = TlsDHUtilities.ReadDHParameter(input); + + this.mDHAgreePublicKey = TlsDHUtilities.ValidateDHPublicKey(new DHPublicKeyParameters(Yc, mDHParameters)); + } + + public override byte[] GeneratePremasterSecret() + { + if (mAgreementCredentials != null) + { + return mAgreementCredentials.GenerateAgreement(mDHAgreePublicKey); + } + + if (mDHAgreePrivateKey != null) + { + return TlsDHUtilities.CalculateDHBasicAgreement(mDHAgreePublicKey, mDHAgreePrivateKey); + } + + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + protected virtual int MinimumPrimeBits + { + get { return 1024; } + } + + protected virtual DHParameters ValidateDHParameters(DHParameters parameters) + { + if (parameters.P.BitLength < MinimumPrimeBits) + throw new TlsFatalAlert(AlertDescription.insufficient_security); + + return TlsDHUtilities.ValidateDHParameters(parameters); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsDHUtilities.cs b/bc-sharp-crypto/src/crypto/tls/TlsDHUtilities.cs new file mode 100644 index 0000000..6df61cb --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsDHUtilities.cs @@ -0,0 +1,462 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Crypto.Agreement; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public abstract class TlsDHUtilities + { + internal static readonly BigInteger Two = BigInteger.Two; + + /* + * TODO[draft-ietf-tls-negotiated-ff-dhe-01] Move these groups to DHStandardGroups once reaches RFC + */ + private static BigInteger FromHex(String hex) + { + return new BigInteger(1, Hex.Decode(hex)); + } + + private static DHParameters FromSafeP(String hexP) + { + BigInteger p = FromHex(hexP), q = p.ShiftRight(1); + return new DHParameters(p, Two, q); + } + + private static readonly string draft_ffdhe2432_p = + "FFFFFFFFFFFFFFFFADF85458A2BB4A9AAFDC5620273D3CF1" + + "D8B9C583CE2D3695A9E13641146433FBCC939DCE249B3EF9" + + "7D2FE363630C75D8F681B202AEC4617AD3DF1ED5D5FD6561" + + "2433F51F5F066ED0856365553DED1AF3B557135E7F57C935" + + "984F0C70E0E68B77E2A689DAF3EFE8721DF158A136ADE735" + + "30ACCA4F483A797ABC0AB182B324FB61D108A94BB2C8E3FB" + + "B96ADAB760D7F4681D4F42A3DE394DF4AE56EDE76372BB19" + + "0B07A7C8EE0A6D709E02FCE1CDF7E2ECC03404CD28342F61" + + "9172FE9CE98583FF8E4F1232EEF28183C3FE3B1B4C6FAD73" + + "3BB5FCBC2EC22005C58EF1837D1683B2C6F34A26C1B2EFFA" + + "886B4238611FCFDCDE355B3B6519035BBC34F4DEF99C0238" + + "61B46FC9D6E6C9077AD91D2691F7F7EE598CB0FAC186D91C" + + "AEFE13098533C8B3FFFFFFFFFFFFFFFF"; + internal static readonly DHParameters draft_ffdhe2432 = FromSafeP(draft_ffdhe2432_p); + + private static readonly string draft_ffdhe3072_p = + "FFFFFFFFFFFFFFFFADF85458A2BB4A9AAFDC5620273D3CF1" + + "D8B9C583CE2D3695A9E13641146433FBCC939DCE249B3EF9" + + "7D2FE363630C75D8F681B202AEC4617AD3DF1ED5D5FD6561" + + "2433F51F5F066ED0856365553DED1AF3B557135E7F57C935" + + "984F0C70E0E68B77E2A689DAF3EFE8721DF158A136ADE735" + + "30ACCA4F483A797ABC0AB182B324FB61D108A94BB2C8E3FB" + + "B96ADAB760D7F4681D4F42A3DE394DF4AE56EDE76372BB19" + + "0B07A7C8EE0A6D709E02FCE1CDF7E2ECC03404CD28342F61" + + "9172FE9CE98583FF8E4F1232EEF28183C3FE3B1B4C6FAD73" + + "3BB5FCBC2EC22005C58EF1837D1683B2C6F34A26C1B2EFFA" + + "886B4238611FCFDCDE355B3B6519035BBC34F4DEF99C0238" + + "61B46FC9D6E6C9077AD91D2691F7F7EE598CB0FAC186D91C" + + "AEFE130985139270B4130C93BC437944F4FD4452E2D74DD3" + + "64F2E21E71F54BFF5CAE82AB9C9DF69EE86D2BC522363A0D" + + "ABC521979B0DEADA1DBF9A42D5C4484E0ABCD06BFA53DDEF" + + "3C1B20EE3FD59D7C25E41D2B66C62E37FFFFFFFFFFFFFFFF"; + internal static readonly DHParameters draft_ffdhe3072 = FromSafeP(draft_ffdhe3072_p); + + private static readonly string draft_ffdhe4096_p = + "FFFFFFFFFFFFFFFFADF85458A2BB4A9AAFDC5620273D3CF1" + + "D8B9C583CE2D3695A9E13641146433FBCC939DCE249B3EF9" + + "7D2FE363630C75D8F681B202AEC4617AD3DF1ED5D5FD6561" + + "2433F51F5F066ED0856365553DED1AF3B557135E7F57C935" + + "984F0C70E0E68B77E2A689DAF3EFE8721DF158A136ADE735" + + "30ACCA4F483A797ABC0AB182B324FB61D108A94BB2C8E3FB" + + "B96ADAB760D7F4681D4F42A3DE394DF4AE56EDE76372BB19" + + "0B07A7C8EE0A6D709E02FCE1CDF7E2ECC03404CD28342F61" + + "9172FE9CE98583FF8E4F1232EEF28183C3FE3B1B4C6FAD73" + + "3BB5FCBC2EC22005C58EF1837D1683B2C6F34A26C1B2EFFA" + + "886B4238611FCFDCDE355B3B6519035BBC34F4DEF99C0238" + + "61B46FC9D6E6C9077AD91D2691F7F7EE598CB0FAC186D91C" + + "AEFE130985139270B4130C93BC437944F4FD4452E2D74DD3" + + "64F2E21E71F54BFF5CAE82AB9C9DF69EE86D2BC522363A0D" + + "ABC521979B0DEADA1DBF9A42D5C4484E0ABCD06BFA53DDEF" + + "3C1B20EE3FD59D7C25E41D2B669E1EF16E6F52C3164DF4FB" + + "7930E9E4E58857B6AC7D5F42D69F6D187763CF1D55034004" + + "87F55BA57E31CC7A7135C886EFB4318AED6A1E012D9E6832" + + "A907600A918130C46DC778F971AD0038092999A333CB8B7A" + + "1A1DB93D7140003C2A4ECEA9F98D0ACC0A8291CDCEC97DCF" + + "8EC9B55A7F88A46B4DB5A851F44182E1C68A007E5E655F6A" + + "FFFFFFFFFFFFFFFF"; + internal static readonly DHParameters draft_ffdhe4096 = FromSafeP(draft_ffdhe4096_p); + + private static readonly string draft_ffdhe6144_p = + "FFFFFFFFFFFFFFFFADF85458A2BB4A9AAFDC5620273D3CF1" + + "D8B9C583CE2D3695A9E13641146433FBCC939DCE249B3EF9" + + "7D2FE363630C75D8F681B202AEC4617AD3DF1ED5D5FD6561" + + "2433F51F5F066ED0856365553DED1AF3B557135E7F57C935" + + "984F0C70E0E68B77E2A689DAF3EFE8721DF158A136ADE735" + + "30ACCA4F483A797ABC0AB182B324FB61D108A94BB2C8E3FB" + + "B96ADAB760D7F4681D4F42A3DE394DF4AE56EDE76372BB19" + + "0B07A7C8EE0A6D709E02FCE1CDF7E2ECC03404CD28342F61" + + "9172FE9CE98583FF8E4F1232EEF28183C3FE3B1B4C6FAD73" + + "3BB5FCBC2EC22005C58EF1837D1683B2C6F34A26C1B2EFFA" + + "886B4238611FCFDCDE355B3B6519035BBC34F4DEF99C0238" + + "61B46FC9D6E6C9077AD91D2691F7F7EE598CB0FAC186D91C" + + "AEFE130985139270B4130C93BC437944F4FD4452E2D74DD3" + + "64F2E21E71F54BFF5CAE82AB9C9DF69EE86D2BC522363A0D" + + "ABC521979B0DEADA1DBF9A42D5C4484E0ABCD06BFA53DDEF" + + "3C1B20EE3FD59D7C25E41D2B669E1EF16E6F52C3164DF4FB" + + "7930E9E4E58857B6AC7D5F42D69F6D187763CF1D55034004" + + "87F55BA57E31CC7A7135C886EFB4318AED6A1E012D9E6832" + + "A907600A918130C46DC778F971AD0038092999A333CB8B7A" + + "1A1DB93D7140003C2A4ECEA9F98D0ACC0A8291CDCEC97DCF" + + "8EC9B55A7F88A46B4DB5A851F44182E1C68A007E5E0DD902" + + "0BFD64B645036C7A4E677D2C38532A3A23BA4442CAF53EA6" + + "3BB454329B7624C8917BDD64B1C0FD4CB38E8C334C701C3A" + + "CDAD0657FCCFEC719B1F5C3E4E46041F388147FB4CFDB477" + + "A52471F7A9A96910B855322EDB6340D8A00EF092350511E3" + + "0ABEC1FFF9E3A26E7FB29F8C183023C3587E38DA0077D9B4" + + "763E4E4B94B2BBC194C6651E77CAF992EEAAC0232A281BF6" + + "B3A739C1226116820AE8DB5847A67CBEF9C9091B462D538C" + + "D72B03746AE77F5E62292C311562A846505DC82DB854338A" + + "E49F5235C95B91178CCF2DD5CACEF403EC9D1810C6272B04" + + "5B3B71F9DC6B80D63FDD4A8E9ADB1E6962A69526D43161C1" + + "A41D570D7938DAD4A40E329CD0E40E65FFFFFFFFFFFFFFFF"; + internal static readonly DHParameters draft_ffdhe6144 = FromSafeP(draft_ffdhe6144_p); + + private static readonly string draft_ffdhe8192_p = + "FFFFFFFFFFFFFFFFADF85458A2BB4A9AAFDC5620273D3CF1" + + "D8B9C583CE2D3695A9E13641146433FBCC939DCE249B3EF9" + + "7D2FE363630C75D8F681B202AEC4617AD3DF1ED5D5FD6561" + + "2433F51F5F066ED0856365553DED1AF3B557135E7F57C935" + + "984F0C70E0E68B77E2A689DAF3EFE8721DF158A136ADE735" + + "30ACCA4F483A797ABC0AB182B324FB61D108A94BB2C8E3FB" + + "B96ADAB760D7F4681D4F42A3DE394DF4AE56EDE76372BB19" + + "0B07A7C8EE0A6D709E02FCE1CDF7E2ECC03404CD28342F61" + + "9172FE9CE98583FF8E4F1232EEF28183C3FE3B1B4C6FAD73" + + "3BB5FCBC2EC22005C58EF1837D1683B2C6F34A26C1B2EFFA" + + "886B4238611FCFDCDE355B3B6519035BBC34F4DEF99C0238" + + "61B46FC9D6E6C9077AD91D2691F7F7EE598CB0FAC186D91C" + + "AEFE130985139270B4130C93BC437944F4FD4452E2D74DD3" + + "64F2E21E71F54BFF5CAE82AB9C9DF69EE86D2BC522363A0D" + + "ABC521979B0DEADA1DBF9A42D5C4484E0ABCD06BFA53DDEF" + + "3C1B20EE3FD59D7C25E41D2B669E1EF16E6F52C3164DF4FB" + + "7930E9E4E58857B6AC7D5F42D69F6D187763CF1D55034004" + + "87F55BA57E31CC7A7135C886EFB4318AED6A1E012D9E6832" + + "A907600A918130C46DC778F971AD0038092999A333CB8B7A" + + "1A1DB93D7140003C2A4ECEA9F98D0ACC0A8291CDCEC97DCF" + + "8EC9B55A7F88A46B4DB5A851F44182E1C68A007E5E0DD902" + + "0BFD64B645036C7A4E677D2C38532A3A23BA4442CAF53EA6" + + "3BB454329B7624C8917BDD64B1C0FD4CB38E8C334C701C3A" + + "CDAD0657FCCFEC719B1F5C3E4E46041F388147FB4CFDB477" + + "A52471F7A9A96910B855322EDB6340D8A00EF092350511E3" + + "0ABEC1FFF9E3A26E7FB29F8C183023C3587E38DA0077D9B4" + + "763E4E4B94B2BBC194C6651E77CAF992EEAAC0232A281BF6" + + "B3A739C1226116820AE8DB5847A67CBEF9C9091B462D538C" + + "D72B03746AE77F5E62292C311562A846505DC82DB854338A" + + "E49F5235C95B91178CCF2DD5CACEF403EC9D1810C6272B04" + + "5B3B71F9DC6B80D63FDD4A8E9ADB1E6962A69526D43161C1" + + "A41D570D7938DAD4A40E329CCFF46AAA36AD004CF600C838" + + "1E425A31D951AE64FDB23FCEC9509D43687FEB69EDD1CC5E" + + "0B8CC3BDF64B10EF86B63142A3AB8829555B2F747C932665" + + "CB2C0F1CC01BD70229388839D2AF05E454504AC78B758282" + + "2846C0BA35C35F5C59160CC046FD8251541FC68C9C86B022" + + "BB7099876A460E7451A8A93109703FEE1C217E6C3826E52C" + + "51AA691E0E423CFC99E9E31650C1217B624816CDAD9A95F9" + + "D5B8019488D9C0A0A1FE3075A577E23183F81D4A3F2FA457" + + "1EFC8CE0BA8A4FE8B6855DFE72B0A66EDED2FBABFBE58A30" + + "FAFABE1C5D71A87E2F741EF8C1FE86FEA6BBFDE530677F0D" + + "97D11D49F7A8443D0822E506A9F4614E011E2A94838FF88C" + + "D68C8BB7C5C6424CFFFFFFFFFFFFFFFF"; + internal static readonly DHParameters draft_ffdhe8192 = FromSafeP(draft_ffdhe8192_p); + + + public static void AddNegotiatedDheGroupsClientExtension(IDictionary extensions, byte[] dheGroups) + { + extensions[ExtensionType.negotiated_ff_dhe_groups] = CreateNegotiatedDheGroupsClientExtension(dheGroups); + } + + public static void AddNegotiatedDheGroupsServerExtension(IDictionary extensions, byte dheGroup) + { + extensions[ExtensionType.negotiated_ff_dhe_groups] = CreateNegotiatedDheGroupsServerExtension(dheGroup); + } + + public static byte[] GetNegotiatedDheGroupsClientExtension(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.negotiated_ff_dhe_groups); + return extensionData == null ? null : ReadNegotiatedDheGroupsClientExtension(extensionData); + } + + public static short GetNegotiatedDheGroupsServerExtension(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.negotiated_ff_dhe_groups); + return extensionData == null ? (short)-1 : (short)ReadNegotiatedDheGroupsServerExtension(extensionData); + } + + public static byte[] CreateNegotiatedDheGroupsClientExtension(byte[] dheGroups) + { + if (dheGroups == null || dheGroups.Length < 1 || dheGroups.Length > 255) + throw new TlsFatalAlert(AlertDescription.internal_error); + + return TlsUtilities.EncodeUint8ArrayWithUint8Length(dheGroups); + } + + public static byte[] CreateNegotiatedDheGroupsServerExtension(byte dheGroup) + { + return TlsUtilities.EncodeUint8(dheGroup); + } + + public static byte[] ReadNegotiatedDheGroupsClientExtension(byte[] extensionData) + { + byte[] dheGroups = TlsUtilities.DecodeUint8ArrayWithUint8Length(extensionData); + if (dheGroups.Length < 1) + throw new TlsFatalAlert(AlertDescription.decode_error); + return dheGroups; + } + + public static byte ReadNegotiatedDheGroupsServerExtension(byte[] extensionData) + { + return TlsUtilities.DecodeUint8(extensionData); + } + + public static DHParameters GetParametersForDHEGroup(short dheGroup) + { + switch (dheGroup) + { + case FiniteFieldDheGroup.ffdhe2432: + return draft_ffdhe2432; + case FiniteFieldDheGroup.ffdhe3072: + return draft_ffdhe3072; + case FiniteFieldDheGroup.ffdhe4096: + return draft_ffdhe4096; + case FiniteFieldDheGroup.ffdhe6144: + return draft_ffdhe6144; + case FiniteFieldDheGroup.ffdhe8192: + return draft_ffdhe8192; + default: + return null; + } + } + + public static bool ContainsDheCipherSuites(int[] cipherSuites) + { + for (int i = 0; i < cipherSuites.Length; ++i) + { + if (IsDheCipherSuite(cipherSuites[i])) + return true; + } + return false; + } + + public static bool IsDheCipherSuite(int cipherSuite) + { + switch (cipherSuite) + { + /* + * RFC 2246 + */ + case CipherSuite.TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA: + case CipherSuite.TLS_DHE_DSS_WITH_DES_CBC_SHA: + case CipherSuite.TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_DES_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA: + + /* + * RFC 3268 + */ + case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA: + + /* + * RFC 5932 + */ + case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA: + case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA: + case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256: + + /* + * RFC 4162 + */ + case CipherSuite.TLS_DHE_DSS_WITH_SEED_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_SEED_CBC_SHA: + + /* + * RFC 4279 + */ + case CipherSuite.TLS_DHE_PSK_WITH_RC4_128_SHA: + case CipherSuite.TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA: + + /* + * RFC 4785 + */ + case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA: + + /* + * RFC 5246 + */ + case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256: + + /* + * RFC 5288 + */ + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_DHE_DSS_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_AES_256_GCM_SHA384: + + /* + * RFC 5487 + */ + case CipherSuite.TLS_DHE_PSK_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DHE_PSK_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA256: + case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA384: + + /* + * RFC 6367 + */ + case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384: + + /* + * RFC 6655 + */ + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM_8: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM_8: + case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CCM: + case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CCM: + case CipherSuite.TLS_PSK_DHE_WITH_AES_128_CCM_8: + case CipherSuite.TLS_PSK_DHE_WITH_AES_256_CCM_8: + + /* + * draft-ietf-tls-chacha20-poly1305-04 + */ + case CipherSuite.DRAFT_TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256: + case CipherSuite.DRAFT_TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256: + + /* + * draft-zauner-tls-aes-ocb-04 + */ + case CipherSuite.DRAFT_TLS_DHE_RSA_WITH_AES_128_OCB: + case CipherSuite.DRAFT_TLS_DHE_RSA_WITH_AES_256_OCB: + case CipherSuite.DRAFT_TLS_DHE_PSK_WITH_AES_128_OCB: + case CipherSuite.DRAFT_TLS_DHE_PSK_WITH_AES_256_OCB: + + return true; + + default: + return false; + } + } + + public static bool AreCompatibleParameters(DHParameters a, DHParameters b) + { + return a.P.Equals(b.P) && a.G.Equals(b.G) + && (a.Q == null || b.Q == null || a.Q.Equals(b.Q)); + } + + public static byte[] CalculateDHBasicAgreement(DHPublicKeyParameters publicKey, + DHPrivateKeyParameters privateKey) + { + DHBasicAgreement basicAgreement = new DHBasicAgreement(); + basicAgreement.Init(privateKey); + BigInteger agreementValue = basicAgreement.CalculateAgreement(publicKey); + + /* + * RFC 5246 8.1.2. Leading bytes of Z that contain all zero bits are stripped before it is + * used as the pre_master_secret. + */ + return BigIntegers.AsUnsignedByteArray(agreementValue); + } + + public static AsymmetricCipherKeyPair GenerateDHKeyPair(SecureRandom random, DHParameters dhParams) + { + DHBasicKeyPairGenerator dhGen = new DHBasicKeyPairGenerator(); + dhGen.Init(new DHKeyGenerationParameters(random, dhParams)); + return dhGen.GenerateKeyPair(); + } + + public static DHPrivateKeyParameters GenerateEphemeralClientKeyExchange(SecureRandom random, + DHParameters dhParams, Stream output) + { + AsymmetricCipherKeyPair kp = GenerateDHKeyPair(random, dhParams); + + DHPublicKeyParameters dhPublic = (DHPublicKeyParameters)kp.Public; + WriteDHParameter(dhPublic.Y, output); + + return (DHPrivateKeyParameters)kp.Private; + } + + public static DHPrivateKeyParameters GenerateEphemeralServerKeyExchange(SecureRandom random, + DHParameters dhParams, Stream output) + { + AsymmetricCipherKeyPair kp = GenerateDHKeyPair(random, dhParams); + + DHPublicKeyParameters dhPublic = (DHPublicKeyParameters)kp.Public; + new ServerDHParams(dhPublic).Encode(output); + + return (DHPrivateKeyParameters)kp.Private; + } + + public static DHParameters ValidateDHParameters(DHParameters parameters) + { + BigInteger p = parameters.P; + BigInteger g = parameters.G; + + if (!p.IsProbablePrime(2)) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + if (g.CompareTo(Two) < 0 || g.CompareTo(p.Subtract(Two)) > 0) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + + + return parameters; + } + + public static DHPublicKeyParameters ValidateDHPublicKey(DHPublicKeyParameters key) + { + DHParameters parameters = ValidateDHParameters(key.Parameters); + + BigInteger Y = key.Y; + if (Y.CompareTo(Two) < 0 || Y.CompareTo(parameters.P.Subtract(Two)) > 0) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + + // TODO See RFC 2631 for more discussion of Diffie-Hellman validation + + return key; + } + + public static BigInteger ReadDHParameter(Stream input) + { + return new BigInteger(1, TlsUtilities.ReadOpaque16(input)); + } + + public static void WriteDHParameter(BigInteger x, Stream output) + { + TlsUtilities.WriteOpaque16(BigIntegers.AsUnsignedByteArray(x), output); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsDeflateCompression.cs b/bc-sharp-crypto/src/crypto/tls/TlsDeflateCompression.cs new file mode 100644 index 0000000..9e11529 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsDeflateCompression.cs @@ -0,0 +1,68 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Utilities.Zlib; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class TlsDeflateCompression : TlsCompression + { + public const int LEVEL_NONE = JZlib.Z_NO_COMPRESSION; + public const int LEVEL_FASTEST = JZlib.Z_BEST_SPEED; + public const int LEVEL_SMALLEST = JZlib.Z_BEST_COMPRESSION; + public const int LEVEL_DEFAULT = JZlib.Z_DEFAULT_COMPRESSION; + + protected readonly ZStream zIn, zOut; + + public TlsDeflateCompression() + : this(LEVEL_DEFAULT) + { + } + + public TlsDeflateCompression(int level) + { + this.zIn = new ZStream(); + this.zIn.inflateInit(); + + this.zOut = new ZStream(); + this.zOut.deflateInit(level); + } + + public virtual Stream Compress(Stream output) + { + return new DeflateOutputStream(output, zOut, true); + } + + public virtual Stream Decompress(Stream output) + { + return new DeflateOutputStream(output, zIn, false); + } + + protected class DeflateOutputStream : ZOutputStream + { + public DeflateOutputStream(Stream output, ZStream z, bool compress) + : base(output, z) + { + this.compress = compress; + + /* + * See discussion at http://www.bolet.org/~pornin/deflate-flush.html . + */ + this.FlushMode = JZlib.Z_SYNC_FLUSH; + } + + public override void Flush() + { + /* + * TODO The inflateSyncPoint doesn't appear to work the way I hoped at the moment. + * In any case, we may like to accept PARTIAL_FLUSH input, not just SYNC_FLUSH. + * It's not clear how to check this in the Inflater. + */ + //if (!this.compress && (z == null || z.istate == null || z.istate.inflateSyncPoint(z) <= 0)) + //{ + // throw new TlsFatalAlert(AlertDescription.decompression_failure); + //} + } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsDheKeyExchange.cs b/bc-sharp-crypto/src/crypto/tls/TlsDheKeyExchange.cs new file mode 100644 index 0000000..cdd6292 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsDheKeyExchange.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class TlsDheKeyExchange + : TlsDHKeyExchange + { + protected TlsSignerCredentials mServerCredentials = null; + + public TlsDheKeyExchange(int keyExchange, IList supportedSignatureAlgorithms, DHParameters dhParameters) + : base(keyExchange, supportedSignatureAlgorithms, dhParameters) + { + } + + public override void ProcessServerCredentials(TlsCredentials serverCredentials) + { + if (!(serverCredentials is TlsSignerCredentials)) + throw new TlsFatalAlert(AlertDescription.internal_error); + + ProcessServerCertificate(serverCredentials.Certificate); + + this.mServerCredentials = (TlsSignerCredentials)serverCredentials; + } + + public override byte[] GenerateServerKeyExchange() + { + if (this.mDHParameters == null) + throw new TlsFatalAlert(AlertDescription.internal_error); + + DigestInputBuffer buf = new DigestInputBuffer(); + + this.mDHAgreePrivateKey = TlsDHUtilities.GenerateEphemeralServerKeyExchange(mContext.SecureRandom, + this.mDHParameters, buf); + + /* + * RFC 5246 4.7. digitally-signed element needs SignatureAndHashAlgorithm from TLS 1.2 + */ + SignatureAndHashAlgorithm signatureAndHashAlgorithm = TlsUtilities.GetSignatureAndHashAlgorithm( + mContext, mServerCredentials); + + IDigest d = TlsUtilities.CreateHash(signatureAndHashAlgorithm); + + SecurityParameters securityParameters = mContext.SecurityParameters; + d.BlockUpdate(securityParameters.clientRandom, 0, securityParameters.clientRandom.Length); + d.BlockUpdate(securityParameters.serverRandom, 0, securityParameters.serverRandom.Length); + buf.UpdateDigest(d); + + byte[] hash = DigestUtilities.DoFinal(d); + + byte[] signature = mServerCredentials.GenerateCertificateSignature(hash); + + DigitallySigned signed_params = new DigitallySigned(signatureAndHashAlgorithm, signature); + signed_params.Encode(buf); + + return buf.ToArray(); + } + + public override void ProcessServerKeyExchange(Stream input) + { + SecurityParameters securityParameters = mContext.SecurityParameters; + + SignerInputBuffer buf = new SignerInputBuffer(); + Stream teeIn = new TeeInputStream(input, buf); + + ServerDHParams dhParams = ServerDHParams.Parse(teeIn); + + DigitallySigned signed_params = ParseSignature(input); + + ISigner signer = InitVerifyer(mTlsSigner, signed_params.Algorithm, securityParameters); + buf.UpdateSigner(signer); + if (!signer.VerifySignature(signed_params.Signature)) + throw new TlsFatalAlert(AlertDescription.decrypt_error); + + this.mDHAgreePublicKey = TlsDHUtilities.ValidateDHPublicKey(dhParams.PublicKey); + this.mDHParameters = ValidateDHParameters(mDHAgreePublicKey.Parameters); + } + + protected virtual ISigner InitVerifyer(TlsSigner tlsSigner, SignatureAndHashAlgorithm algorithm, + SecurityParameters securityParameters) + { + ISigner signer = tlsSigner.CreateVerifyer(algorithm, this.mServerPublicKey); + signer.BlockUpdate(securityParameters.clientRandom, 0, securityParameters.clientRandom.Length); + signer.BlockUpdate(securityParameters.serverRandom, 0, securityParameters.serverRandom.Length); + return signer; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsDsaSigner.cs b/bc-sharp-crypto/src/crypto/tls/TlsDsaSigner.cs new file mode 100644 index 0000000..f0c1e94 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsDsaSigner.cs @@ -0,0 +1,82 @@ +using System; + +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Signers; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public abstract class TlsDsaSigner + : AbstractTlsSigner + { + public override byte[] GenerateRawSignature(SignatureAndHashAlgorithm algorithm, + AsymmetricKeyParameter privateKey, byte[] hash) + { + ISigner signer = MakeSigner(algorithm, true, true, + new ParametersWithRandom(privateKey, this.mContext.SecureRandom)); + if (algorithm == null) + { + // Note: Only use the SHA1 part of the (MD5/SHA1) hash + signer.BlockUpdate(hash, 16, 20); + } + else + { + signer.BlockUpdate(hash, 0, hash.Length); + } + return signer.GenerateSignature(); + } + + public override bool VerifyRawSignature(SignatureAndHashAlgorithm algorithm, byte[] sigBytes, + AsymmetricKeyParameter publicKey, byte[] hash) + { + ISigner signer = MakeSigner(algorithm, true, false, publicKey); + if (algorithm == null) + { + // Note: Only use the SHA1 part of the (MD5/SHA1) hash + signer.BlockUpdate(hash, 16, 20); + } + else + { + signer.BlockUpdate(hash, 0, hash.Length); + } + return signer.VerifySignature(sigBytes); + } + + public override ISigner CreateSigner(SignatureAndHashAlgorithm algorithm, AsymmetricKeyParameter privateKey) + { + return MakeSigner(algorithm, false, true, privateKey); + } + + public override ISigner CreateVerifyer(SignatureAndHashAlgorithm algorithm, AsymmetricKeyParameter publicKey) + { + return MakeSigner(algorithm, false, false, publicKey); + } + + protected virtual ICipherParameters MakeInitParameters(bool forSigning, ICipherParameters cp) + { + return cp; + } + + protected virtual ISigner MakeSigner(SignatureAndHashAlgorithm algorithm, bool raw, bool forSigning, + ICipherParameters cp) + { + if ((algorithm != null) != TlsUtilities.IsTlsV12(mContext)) + throw new InvalidOperationException(); + + if (algorithm != null && algorithm.Signature != SignatureAlgorithm) + throw new InvalidOperationException(); + + byte hashAlgorithm = algorithm == null ? HashAlgorithm.sha1 : algorithm.Hash; + IDigest d = raw ? new NullDigest() : TlsUtilities.CreateHash(hashAlgorithm); + + ISigner s = new DsaDigestSigner(CreateDsaImpl(hashAlgorithm), d); + s.Init(forSigning, MakeInitParameters(forSigning, cp)); + return s; + } + + protected abstract byte SignatureAlgorithm { get; } + + protected abstract IDsa CreateDsaImpl(byte hashAlgorithm); + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsDssSigner.cs b/bc-sharp-crypto/src/crypto/tls/TlsDssSigner.cs new file mode 100644 index 0000000..707ef38 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsDssSigner.cs @@ -0,0 +1,26 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Signers; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class TlsDssSigner + : TlsDsaSigner + { + public override bool IsValidPublicKey(AsymmetricKeyParameter publicKey) + { + return publicKey is DsaPublicKeyParameters; + } + + protected override IDsa CreateDsaImpl(byte hashAlgorithm) + { + return new DsaSigner(new HMacDsaKCalculator(TlsUtilities.CreateHash(hashAlgorithm))); + } + + protected override byte SignatureAlgorithm + { + get { return Tls.SignatureAlgorithm.dsa; } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsECDHKeyExchange.cs b/bc-sharp-crypto/src/crypto/tls/TlsECDHKeyExchange.cs new file mode 100644 index 0000000..c508fb9 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsECDHKeyExchange.cs @@ -0,0 +1,252 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /// (D)TLS ECDH key exchange (see RFC 4492). + public class TlsECDHKeyExchange + : AbstractTlsKeyExchange + { + protected TlsSigner mTlsSigner; + protected int[] mNamedCurves; + protected byte[] mClientECPointFormats, mServerECPointFormats; + + protected AsymmetricKeyParameter mServerPublicKey; + protected TlsAgreementCredentials mAgreementCredentials; + + protected ECPrivateKeyParameters mECAgreePrivateKey; + protected ECPublicKeyParameters mECAgreePublicKey; + + public TlsECDHKeyExchange(int keyExchange, IList supportedSignatureAlgorithms, int[] namedCurves, + byte[] clientECPointFormats, byte[] serverECPointFormats) + : base(keyExchange, supportedSignatureAlgorithms) + { + switch (keyExchange) + { + case KeyExchangeAlgorithm.ECDHE_RSA: + this.mTlsSigner = new TlsRsaSigner(); + break; + case KeyExchangeAlgorithm.ECDHE_ECDSA: + this.mTlsSigner = new TlsECDsaSigner(); + break; + case KeyExchangeAlgorithm.ECDH_anon: + case KeyExchangeAlgorithm.ECDH_RSA: + case KeyExchangeAlgorithm.ECDH_ECDSA: + this.mTlsSigner = null; + break; + default: + throw new InvalidOperationException("unsupported key exchange algorithm"); + } + + this.mNamedCurves = namedCurves; + this.mClientECPointFormats = clientECPointFormats; + this.mServerECPointFormats = serverECPointFormats; + } + + public override void Init(TlsContext context) + { + base.Init(context); + + if (this.mTlsSigner != null) + { + this.mTlsSigner.Init(context); + } + } + + public override void SkipServerCredentials() + { + if (mKeyExchange != KeyExchangeAlgorithm.ECDH_anon) + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + public override void ProcessServerCertificate(Certificate serverCertificate) + { + if (mKeyExchange == KeyExchangeAlgorithm.ECDH_anon) + throw new TlsFatalAlert(AlertDescription.unexpected_message); + if (serverCertificate.IsEmpty) + throw new TlsFatalAlert(AlertDescription.bad_certificate); + + X509CertificateStructure x509Cert = serverCertificate.GetCertificateAt(0); + + SubjectPublicKeyInfo keyInfo = x509Cert.SubjectPublicKeyInfo; + try + { + this.mServerPublicKey = PublicKeyFactory.CreateKey(keyInfo); + } + catch (Exception e) + { + throw new TlsFatalAlert(AlertDescription.unsupported_certificate, e); + } + + if (mTlsSigner == null) + { + try + { + this.mECAgreePublicKey = TlsEccUtilities.ValidateECPublicKey((ECPublicKeyParameters) this.mServerPublicKey); + } + catch (InvalidCastException e) + { + throw new TlsFatalAlert(AlertDescription.certificate_unknown, e); + } + + TlsUtilities.ValidateKeyUsage(x509Cert, KeyUsage.KeyAgreement); + } + else + { + if (!mTlsSigner.IsValidPublicKey(this.mServerPublicKey)) + throw new TlsFatalAlert(AlertDescription.certificate_unknown); + + TlsUtilities.ValidateKeyUsage(x509Cert, KeyUsage.DigitalSignature); + } + + base.ProcessServerCertificate(serverCertificate); + } + + public override bool RequiresServerKeyExchange + { + get + { + switch (mKeyExchange) + { + case KeyExchangeAlgorithm.ECDH_anon: + case KeyExchangeAlgorithm.ECDHE_ECDSA: + case KeyExchangeAlgorithm.ECDHE_RSA: + return true; + default: + return false; + } + } + } + + public override byte[] GenerateServerKeyExchange() + { + if (!RequiresServerKeyExchange) + return null; + + // ECDH_anon is handled here, ECDHE_* in a subclass + + MemoryStream buf = new MemoryStream(); + this.mECAgreePrivateKey = TlsEccUtilities.GenerateEphemeralServerKeyExchange(mContext.SecureRandom, mNamedCurves, + mClientECPointFormats, buf); + return buf.ToArray(); + } + + public override void ProcessServerKeyExchange(Stream input) + { + if (!RequiresServerKeyExchange) + throw new TlsFatalAlert(AlertDescription.unexpected_message); + + // ECDH_anon is handled here, ECDHE_* in a subclass + + ECDomainParameters curve_params = TlsEccUtilities.ReadECParameters(mNamedCurves, mClientECPointFormats, input); + + byte[] point = TlsUtilities.ReadOpaque8(input); + + this.mECAgreePublicKey = TlsEccUtilities.ValidateECPublicKey(TlsEccUtilities.DeserializeECPublicKey( + mClientECPointFormats, curve_params, point)); + } + + public override void ValidateCertificateRequest(CertificateRequest certificateRequest) + { + if (mKeyExchange == KeyExchangeAlgorithm.ECDH_anon) + throw new TlsFatalAlert(AlertDescription.handshake_failure); + + /* + * RFC 4492 3. [...] The ECDSA_fixed_ECDH and RSA_fixed_ECDH mechanisms are usable with + * ECDH_ECDSA and ECDH_RSA. Their use with ECDHE_ECDSA and ECDHE_RSA is prohibited because + * the use of a long-term ECDH client key would jeopardize the forward secrecy property of + * these algorithms. + */ + byte[] types = certificateRequest.CertificateTypes; + for (int i = 0; i < types.Length; ++i) + { + switch (types[i]) + { + case ClientCertificateType.rsa_sign: + case ClientCertificateType.dss_sign: + case ClientCertificateType.ecdsa_sign: + case ClientCertificateType.rsa_fixed_ecdh: + case ClientCertificateType.ecdsa_fixed_ecdh: + break; + default: + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + } + } + + public override void ProcessClientCredentials(TlsCredentials clientCredentials) + { + if (mKeyExchange == KeyExchangeAlgorithm.ECDH_anon) + throw new TlsFatalAlert(AlertDescription.internal_error); + + if (clientCredentials is TlsAgreementCredentials) + { + // TODO Validate client cert has matching parameters (see 'TlsEccUtilities.AreOnSameCurve')? + + this.mAgreementCredentials = (TlsAgreementCredentials)clientCredentials; + } + else if (clientCredentials is TlsSignerCredentials) + { + // OK + } + else + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + public override void GenerateClientKeyExchange(Stream output) + { + if (mAgreementCredentials == null) + { + this.mECAgreePrivateKey = TlsEccUtilities.GenerateEphemeralClientKeyExchange(mContext.SecureRandom, + mServerECPointFormats, mECAgreePublicKey.Parameters, output); + } + } + + public override void ProcessClientCertificate(Certificate clientCertificate) + { + if (mKeyExchange == KeyExchangeAlgorithm.ECDH_anon) + throw new TlsFatalAlert(AlertDescription.unexpected_message); + + // TODO Extract the public key + // TODO If the certificate is 'fixed', take the public key as mECAgreeClientPublicKey + } + + public override void ProcessClientKeyExchange(Stream input) + { + if (mECAgreePublicKey != null) + { + // For ecdsa_fixed_ecdh and rsa_fixed_ecdh, the key arrived in the client certificate + return; + } + + byte[] point = TlsUtilities.ReadOpaque8(input); + + ECDomainParameters curve_params = this.mECAgreePrivateKey.Parameters; + + this.mECAgreePublicKey = TlsEccUtilities.ValidateECPublicKey(TlsEccUtilities.DeserializeECPublicKey( + mServerECPointFormats, curve_params, point)); + } + + public override byte[] GeneratePremasterSecret() + { + if (mAgreementCredentials != null) + { + return mAgreementCredentials.GenerateAgreement(mECAgreePublicKey); + } + + if (mECAgreePrivateKey != null) + { + return TlsEccUtilities.CalculateECDHBasicAgreement(mECAgreePublicKey, mECAgreePrivateKey); + } + + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsECDheKeyExchange.cs b/bc-sharp-crypto/src/crypto/tls/TlsECDheKeyExchange.cs new file mode 100644 index 0000000..e0553b3 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsECDheKeyExchange.cs @@ -0,0 +1,131 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math.EC; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /// (D)TLS ECDHE key exchange (see RFC 4492). + public class TlsECDheKeyExchange + : TlsECDHKeyExchange + { + protected TlsSignerCredentials mServerCredentials = null; + + public TlsECDheKeyExchange(int keyExchange, IList supportedSignatureAlgorithms, int[] namedCurves, + byte[] clientECPointFormats, byte[] serverECPointFormats) + : base(keyExchange, supportedSignatureAlgorithms, namedCurves, clientECPointFormats, serverECPointFormats) + { + } + + public override void ProcessServerCredentials(TlsCredentials serverCredentials) + { + if (!(serverCredentials is TlsSignerCredentials)) + throw new TlsFatalAlert(AlertDescription.internal_error); + + ProcessServerCertificate(serverCredentials.Certificate); + + this.mServerCredentials = (TlsSignerCredentials)serverCredentials; + } + + public override byte[] GenerateServerKeyExchange() + { + DigestInputBuffer buf = new DigestInputBuffer(); + + this.mECAgreePrivateKey = TlsEccUtilities.GenerateEphemeralServerKeyExchange(mContext.SecureRandom, mNamedCurves, + mClientECPointFormats, buf); + + /* + * RFC 5246 4.7. digitally-signed element needs SignatureAndHashAlgorithm from TLS 1.2 + */ + SignatureAndHashAlgorithm signatureAndHashAlgorithm = TlsUtilities.GetSignatureAndHashAlgorithm( + mContext, mServerCredentials); + + IDigest d = TlsUtilities.CreateHash(signatureAndHashAlgorithm); + + SecurityParameters securityParameters = mContext.SecurityParameters; + d.BlockUpdate(securityParameters.clientRandom, 0, securityParameters.clientRandom.Length); + d.BlockUpdate(securityParameters.serverRandom, 0, securityParameters.serverRandom.Length); + buf.UpdateDigest(d); + + byte[] hash = DigestUtilities.DoFinal(d); + + byte[] signature = mServerCredentials.GenerateCertificateSignature(hash); + + DigitallySigned signed_params = new DigitallySigned(signatureAndHashAlgorithm, signature); + signed_params.Encode(buf); + + return buf.ToArray(); + } + + public override void ProcessServerKeyExchange(Stream input) + { + SecurityParameters securityParameters = mContext.SecurityParameters; + + SignerInputBuffer buf = new SignerInputBuffer(); + Stream teeIn = new TeeInputStream(input, buf); + + ECDomainParameters curve_params = TlsEccUtilities.ReadECParameters(mNamedCurves, mClientECPointFormats, teeIn); + + byte[] point = TlsUtilities.ReadOpaque8(teeIn); + + DigitallySigned signed_params = ParseSignature(input); + + ISigner signer = InitVerifyer(mTlsSigner, signed_params.Algorithm, securityParameters); + buf.UpdateSigner(signer); + if (!signer.VerifySignature(signed_params.Signature)) + throw new TlsFatalAlert(AlertDescription.decrypt_error); + + this.mECAgreePublicKey = TlsEccUtilities.ValidateECPublicKey(TlsEccUtilities.DeserializeECPublicKey( + mClientECPointFormats, curve_params, point)); + } + + public override void ValidateCertificateRequest(CertificateRequest certificateRequest) + { + /* + * RFC 4492 3. [...] The ECDSA_fixed_ECDH and RSA_fixed_ECDH mechanisms are usable with + * ECDH_ECDSA and ECDH_RSA. Their use with ECDHE_ECDSA and ECDHE_RSA is prohibited because + * the use of a long-term ECDH client key would jeopardize the forward secrecy property of + * these algorithms. + */ + byte[] types = certificateRequest.CertificateTypes; + for (int i = 0; i < types.Length; ++i) + { + switch (types[i]) + { + case ClientCertificateType.rsa_sign: + case ClientCertificateType.dss_sign: + case ClientCertificateType.ecdsa_sign: + break; + default: + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + } + } + + public override void ProcessClientCredentials(TlsCredentials clientCredentials) + { + if (clientCredentials is TlsSignerCredentials) + { + // OK + } + else + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + protected virtual ISigner InitVerifyer(TlsSigner tlsSigner, SignatureAndHashAlgorithm algorithm, + SecurityParameters securityParameters) + { + ISigner signer = tlsSigner.CreateVerifyer(algorithm, this.mServerPublicKey); + signer.BlockUpdate(securityParameters.clientRandom, 0, securityParameters.clientRandom.Length); + signer.BlockUpdate(securityParameters.serverRandom, 0, securityParameters.serverRandom.Length); + return signer; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsECDsaSigner.cs b/bc-sharp-crypto/src/crypto/tls/TlsECDsaSigner.cs new file mode 100644 index 0000000..fa9d0b7 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsECDsaSigner.cs @@ -0,0 +1,26 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Signers; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class TlsECDsaSigner + : TlsDsaSigner + { + public override bool IsValidPublicKey(AsymmetricKeyParameter publicKey) + { + return publicKey is ECPublicKeyParameters; + } + + protected override IDsa CreateDsaImpl(byte hashAlgorithm) + { + return new ECDsaSigner(new HMacDsaKCalculator(TlsUtilities.CreateHash(hashAlgorithm))); + } + + protected override byte SignatureAlgorithm + { + get { return Tls.SignatureAlgorithm.ecdsa; } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsEccUtilities.cs b/bc-sharp-crypto/src/crypto/tls/TlsEccUtilities.cs new file mode 100644 index 0000000..fb31e1b --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsEccUtilities.cs @@ -0,0 +1,705 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Agreement; +using Org.BouncyCastle.Crypto.EC; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Math.EC; +using Org.BouncyCastle.Math.Field; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public abstract class TlsEccUtilities + { + private static readonly string[] CurveNames = new string[] { "sect163k1", "sect163r1", "sect163r2", "sect193r1", + "sect193r2", "sect233k1", "sect233r1", "sect239k1", "sect283k1", "sect283r1", "sect409k1", "sect409r1", + "sect571k1", "sect571r1", "secp160k1", "secp160r1", "secp160r2", "secp192k1", "secp192r1", "secp224k1", + "secp224r1", "secp256k1", "secp256r1", "secp384r1", "secp521r1", + "brainpoolP256r1", "brainpoolP384r1", "brainpoolP512r1"}; + + public static void AddSupportedEllipticCurvesExtension(IDictionary extensions, int[] namedCurves) + { + extensions[ExtensionType.elliptic_curves] = CreateSupportedEllipticCurvesExtension(namedCurves); + } + + public static void AddSupportedPointFormatsExtension(IDictionary extensions, byte[] ecPointFormats) + { + extensions[ExtensionType.ec_point_formats] = CreateSupportedPointFormatsExtension(ecPointFormats); + } + + public static int[] GetSupportedEllipticCurvesExtension(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.elliptic_curves); + return extensionData == null ? null : ReadSupportedEllipticCurvesExtension(extensionData); + } + + public static byte[] GetSupportedPointFormatsExtension(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.ec_point_formats); + return extensionData == null ? null : ReadSupportedPointFormatsExtension(extensionData); + } + + public static byte[] CreateSupportedEllipticCurvesExtension(int[] namedCurves) + { + if (namedCurves == null || namedCurves.Length < 1) + throw new TlsFatalAlert(AlertDescription.internal_error); + + return TlsUtilities.EncodeUint16ArrayWithUint16Length(namedCurves); + } + + public static byte[] CreateSupportedPointFormatsExtension(byte[] ecPointFormats) + { + if (ecPointFormats == null || !Arrays.Contains(ecPointFormats, ECPointFormat.uncompressed)) + { + /* + * RFC 4492 5.1. If the Supported Point Formats Extension is indeed sent, it MUST + * contain the value 0 (uncompressed) as one of the items in the list of point formats. + */ + + // NOTE: We add it at the end (lowest preference) + ecPointFormats = Arrays.Append(ecPointFormats, ECPointFormat.uncompressed); + } + + return TlsUtilities.EncodeUint8ArrayWithUint8Length(ecPointFormats); + } + + public static int[] ReadSupportedEllipticCurvesExtension(byte[] extensionData) + { + if (extensionData == null) + throw new ArgumentNullException("extensionData"); + + MemoryStream buf = new MemoryStream(extensionData, false); + + int length = TlsUtilities.ReadUint16(buf); + if (length < 2 || (length & 1) != 0) + throw new TlsFatalAlert(AlertDescription.decode_error); + + int[] namedCurves = TlsUtilities.ReadUint16Array(length / 2, buf); + + TlsProtocol.AssertEmpty(buf); + + return namedCurves; + } + + public static byte[] ReadSupportedPointFormatsExtension(byte[] extensionData) + { + byte[] ecPointFormats = TlsUtilities.DecodeUint8ArrayWithUint8Length(extensionData); + if (!Arrays.Contains(ecPointFormats, ECPointFormat.uncompressed)) + { + /* + * RFC 4492 5.1. If the Supported Point Formats Extension is indeed sent, it MUST + * contain the value 0 (uncompressed) as one of the items in the list of point formats. + */ + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + return ecPointFormats; + } + + public static string GetNameOfNamedCurve(int namedCurve) + { + return IsSupportedNamedCurve(namedCurve) ? CurveNames[namedCurve - 1] : null; + } + + public static ECDomainParameters GetParametersForNamedCurve(int namedCurve) + { + string curveName = GetNameOfNamedCurve(namedCurve); + if (curveName == null) + return null; + + // Parameters are lazily created the first time a particular curve is accessed + + X9ECParameters ecP = CustomNamedCurves.GetByName(curveName); + if (ecP == null) + { + ecP = ECNamedCurveTable.GetByName(curveName); + if (ecP == null) + return null; + } + + // It's a bit inefficient to do this conversion every time + return new ECDomainParameters(ecP.Curve, ecP.G, ecP.N, ecP.H, ecP.GetSeed()); + } + + public static bool HasAnySupportedNamedCurves() + { + return CurveNames.Length > 0; + } + + public static bool ContainsEccCipherSuites(int[] cipherSuites) + { + for (int i = 0; i < cipherSuites.Length; ++i) + { + if (IsEccCipherSuite(cipherSuites[i])) + return true; + } + return false; + } + + public static bool IsEccCipherSuite(int cipherSuite) + { + switch (cipherSuite) + { + /* + * RFC 4492 + */ + case CipherSuite.TLS_ECDH_ECDSA_WITH_NULL_SHA: + case CipherSuite.TLS_ECDH_ECDSA_WITH_RC4_128_SHA: + case CipherSuite.TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_NULL_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDH_RSA_WITH_NULL_SHA: + case CipherSuite.TLS_ECDH_RSA_WITH_RC4_128_SHA: + case CipherSuite.TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_NULL_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_RC4_128_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDH_anon_WITH_NULL_SHA: + case CipherSuite.TLS_ECDH_anon_WITH_RC4_128_SHA: + case CipherSuite.TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDH_anon_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDH_anon_WITH_AES_256_CBC_SHA: + + /* + * RFC 5289 + */ + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384: + + /* + * RFC 5489 + */ + case CipherSuite.TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA: + case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA256: + case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA384: + case CipherSuite.TLS_ECDHE_PSK_WITH_RC4_128_SHA: + + /* + * RFC 6367 + */ + case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384: + case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384: + case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384: + + case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384: + + case CipherSuite.TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384: + + /* + * RFC 7251 + */ + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8: + + /* + * draft-ietf-tls-chacha20-poly1305-04 + */ + case CipherSuite.DRAFT_TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256: + case CipherSuite.DRAFT_TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256: + case CipherSuite.DRAFT_TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256: + + /* + * draft-zauner-tls-aes-ocb-04 + */ + case CipherSuite.DRAFT_TLS_ECDHE_RSA_WITH_AES_128_OCB: + case CipherSuite.DRAFT_TLS_ECDHE_RSA_WITH_AES_256_OCB: + case CipherSuite.DRAFT_TLS_ECDHE_ECDSA_WITH_AES_128_OCB: + case CipherSuite.DRAFT_TLS_ECDHE_ECDSA_WITH_AES_256_OCB: + case CipherSuite.DRAFT_TLS_ECDHE_PSK_WITH_AES_128_OCB: + case CipherSuite.DRAFT_TLS_ECDHE_PSK_WITH_AES_256_OCB: + + return true; + + default: + return false; + } + } + + public static bool AreOnSameCurve(ECDomainParameters a, ECDomainParameters b) + { + return a != null && a.Equals(b); + } + + public static bool IsSupportedNamedCurve(int namedCurve) + { + return (namedCurve > 0 && namedCurve <= CurveNames.Length); + } + + public static bool IsCompressionPreferred(byte[] ecPointFormats, byte compressionFormat) + { + if (ecPointFormats == null) + return false; + + for (int i = 0; i < ecPointFormats.Length; ++i) + { + byte ecPointFormat = ecPointFormats[i]; + if (ecPointFormat == ECPointFormat.uncompressed) + return false; + if (ecPointFormat == compressionFormat) + return true; + } + return false; + } + + public static byte[] SerializeECFieldElement(int fieldSize, BigInteger x) + { + return BigIntegers.AsUnsignedByteArray((fieldSize + 7) / 8, x); + } + + public static byte[] SerializeECPoint(byte[] ecPointFormats, ECPoint point) + { + ECCurve curve = point.Curve; + + /* + * RFC 4492 5.7. ...an elliptic curve point in uncompressed or compressed format. Here, the + * format MUST conform to what the server has requested through a Supported Point Formats + * Extension if this extension was used, and MUST be uncompressed if this extension was not + * used. + */ + bool compressed = false; + if (ECAlgorithms.IsFpCurve(curve)) + { + compressed = IsCompressionPreferred(ecPointFormats, ECPointFormat.ansiX962_compressed_prime); + } + else if (ECAlgorithms.IsF2mCurve(curve)) + { + compressed = IsCompressionPreferred(ecPointFormats, ECPointFormat.ansiX962_compressed_char2); + } + return point.GetEncoded(compressed); + } + + public static byte[] SerializeECPublicKey(byte[] ecPointFormats, ECPublicKeyParameters keyParameters) + { + return SerializeECPoint(ecPointFormats, keyParameters.Q); + } + + public static BigInteger DeserializeECFieldElement(int fieldSize, byte[] encoding) + { + int requiredLength = (fieldSize + 7) / 8; + if (encoding.Length != requiredLength) + throw new TlsFatalAlert(AlertDescription.decode_error); + return new BigInteger(1, encoding); + } + + public static ECPoint DeserializeECPoint(byte[] ecPointFormats, ECCurve curve, byte[] encoding) + { + if (encoding == null || encoding.Length < 1) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + + byte actualFormat; + switch (encoding[0]) + { + case 0x02: // compressed + case 0x03: // compressed + { + if (ECAlgorithms.IsF2mCurve(curve)) + { + actualFormat = ECPointFormat.ansiX962_compressed_char2; + } + else if (ECAlgorithms.IsFpCurve(curve)) + { + actualFormat = ECPointFormat.ansiX962_compressed_prime; + } + else + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + break; + } + case 0x04: // uncompressed + { + actualFormat = ECPointFormat.uncompressed; + break; + } + case 0x00: // infinity + case 0x06: // hybrid + case 0x07: // hybrid + default: + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + + if (actualFormat != ECPointFormat.uncompressed + && (ecPointFormats == null || !Arrays.Contains(ecPointFormats, actualFormat))) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + + return curve.DecodePoint(encoding); + } + + public static ECPublicKeyParameters DeserializeECPublicKey(byte[] ecPointFormats, ECDomainParameters curve_params, + byte[] encoding) + { + try + { + ECPoint Y = DeserializeECPoint(ecPointFormats, curve_params.Curve, encoding); + return new ECPublicKeyParameters(Y, curve_params); + } + catch (Exception e) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter, e); + } + } + + public static byte[] CalculateECDHBasicAgreement(ECPublicKeyParameters publicKey, ECPrivateKeyParameters privateKey) + { + ECDHBasicAgreement basicAgreement = new ECDHBasicAgreement(); + basicAgreement.Init(privateKey); + BigInteger agreementValue = basicAgreement.CalculateAgreement(publicKey); + + /* + * RFC 4492 5.10. Note that this octet string (Z in IEEE 1363 terminology) as output by + * FE2OSP, the Field Element to Octet String Conversion Primitive, has constant length for + * any given field; leading zeros found in this octet string MUST NOT be truncated. + */ + return BigIntegers.AsUnsignedByteArray(basicAgreement.GetFieldSize(), agreementValue); + } + + public static AsymmetricCipherKeyPair GenerateECKeyPair(SecureRandom random, ECDomainParameters ecParams) + { + ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator(); + keyPairGenerator.Init(new ECKeyGenerationParameters(ecParams, random)); + return keyPairGenerator.GenerateKeyPair(); + } + + public static ECPrivateKeyParameters GenerateEphemeralClientKeyExchange(SecureRandom random, byte[] ecPointFormats, + ECDomainParameters ecParams, Stream output) + { + AsymmetricCipherKeyPair kp = GenerateECKeyPair(random, ecParams); + + ECPublicKeyParameters ecPublicKey = (ECPublicKeyParameters)kp.Public; + WriteECPoint(ecPointFormats, ecPublicKey.Q, output); + + return (ECPrivateKeyParameters)kp.Private; + } + + // TODO Refactor around ServerECDHParams before making this public + internal static ECPrivateKeyParameters GenerateEphemeralServerKeyExchange(SecureRandom random, int[] namedCurves, + byte[] ecPointFormats, Stream output) + { + /* First we try to find a supported named curve from the client's list. */ + int namedCurve = -1; + if (namedCurves == null) + { + // TODO Let the peer choose the default named curve + namedCurve = NamedCurve.secp256r1; + } + else + { + for (int i = 0; i < namedCurves.Length; ++i) + { + int entry = namedCurves[i]; + if (NamedCurve.IsValid(entry) && IsSupportedNamedCurve(entry)) + { + namedCurve = entry; + break; + } + } + } + + ECDomainParameters ecParams = null; + if (namedCurve >= 0) + { + ecParams = GetParametersForNamedCurve(namedCurve); + } + else + { + /* If no named curves are suitable, check if the client supports explicit curves. */ + if (Arrays.Contains(namedCurves, NamedCurve.arbitrary_explicit_prime_curves)) + { + ecParams = GetParametersForNamedCurve(NamedCurve.secp256r1); + } + else if (Arrays.Contains(namedCurves, NamedCurve.arbitrary_explicit_char2_curves)) + { + ecParams = GetParametersForNamedCurve(NamedCurve.sect283r1); + } + } + + if (ecParams == null) + { + /* + * NOTE: We shouldn't have negotiated ECDHE key exchange since we apparently can't find + * a suitable curve. + */ + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + if (namedCurve < 0) + { + WriteExplicitECParameters(ecPointFormats, ecParams, output); + } + else + { + WriteNamedECParameters(namedCurve, output); + } + + return GenerateEphemeralClientKeyExchange(random, ecPointFormats, ecParams, output); + } + + public static ECPublicKeyParameters ValidateECPublicKey(ECPublicKeyParameters key) + { + // TODO Check RFC 4492 for validation + return key; + } + + public static int ReadECExponent(int fieldSize, Stream input) + { + BigInteger K = ReadECParameter(input); + if (K.BitLength < 32) + { + int k = K.IntValue; + if (k > 0 && k < fieldSize) + { + return k; + } + } + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + + public static BigInteger ReadECFieldElement(int fieldSize, Stream input) + { + return DeserializeECFieldElement(fieldSize, TlsUtilities.ReadOpaque8(input)); + } + + public static BigInteger ReadECParameter(Stream input) + { + // TODO Are leading zeroes okay here? + return new BigInteger(1, TlsUtilities.ReadOpaque8(input)); + } + + public static ECDomainParameters ReadECParameters(int[] namedCurves, byte[] ecPointFormats, Stream input) + { + try + { + byte curveType = TlsUtilities.ReadUint8(input); + + switch (curveType) + { + case ECCurveType.explicit_prime: + { + CheckNamedCurve(namedCurves, NamedCurve.arbitrary_explicit_prime_curves); + + BigInteger prime_p = ReadECParameter(input); + BigInteger a = ReadECFieldElement(prime_p.BitLength, input); + BigInteger b = ReadECFieldElement(prime_p.BitLength, input); + byte[] baseEncoding = TlsUtilities.ReadOpaque8(input); + BigInteger order = ReadECParameter(input); + BigInteger cofactor = ReadECParameter(input); + ECCurve curve = new FpCurve(prime_p, a, b, order, cofactor); + ECPoint basePoint = DeserializeECPoint(ecPointFormats, curve, baseEncoding); + return new ECDomainParameters(curve, basePoint, order, cofactor); + } + case ECCurveType.explicit_char2: + { + CheckNamedCurve(namedCurves, NamedCurve.arbitrary_explicit_char2_curves); + + int m = TlsUtilities.ReadUint16(input); + byte basis = TlsUtilities.ReadUint8(input); + if (!ECBasisType.IsValid(basis)) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + + int k1 = ReadECExponent(m, input), k2 = -1, k3 = -1; + if (basis == ECBasisType.ec_basis_pentanomial) + { + k2 = ReadECExponent(m, input); + k3 = ReadECExponent(m, input); + } + + BigInteger a = ReadECFieldElement(m, input); + BigInteger b = ReadECFieldElement(m, input); + byte[] baseEncoding = TlsUtilities.ReadOpaque8(input); + BigInteger order = ReadECParameter(input); + BigInteger cofactor = ReadECParameter(input); + + ECCurve curve = (basis == ECBasisType.ec_basis_pentanomial) + ? new F2mCurve(m, k1, k2, k3, a, b, order, cofactor) + : new F2mCurve(m, k1, a, b, order, cofactor); + + ECPoint basePoint = DeserializeECPoint(ecPointFormats, curve, baseEncoding); + + return new ECDomainParameters(curve, basePoint, order, cofactor); + } + case ECCurveType.named_curve: + { + int namedCurve = TlsUtilities.ReadUint16(input); + if (!NamedCurve.RefersToASpecificNamedCurve(namedCurve)) + { + /* + * RFC 4492 5.4. All those values of NamedCurve are allowed that refer to a + * specific curve. Values of NamedCurve that indicate support for a class of + * explicitly defined curves are not allowed here [...]. + */ + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + + CheckNamedCurve(namedCurves, namedCurve); + + return GetParametersForNamedCurve(namedCurve); + } + default: + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + } + catch (Exception e) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter, e); + } + } + + private static void CheckNamedCurve(int[] namedCurves, int namedCurve) + { + if (namedCurves != null && !Arrays.Contains(namedCurves, namedCurve)) + { + /* + * RFC 4492 4. [...] servers MUST NOT negotiate the use of an ECC cipher suite + * unless they can complete the handshake while respecting the choice of curves + * and compression techniques specified by the client. + */ + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + } + + public static void WriteECExponent(int k, Stream output) + { + BigInteger K = BigInteger.ValueOf(k); + WriteECParameter(K, output); + } + + public static void WriteECFieldElement(ECFieldElement x, Stream output) + { + TlsUtilities.WriteOpaque8(x.GetEncoded(), output); + } + + public static void WriteECFieldElement(int fieldSize, BigInteger x, Stream output) + { + TlsUtilities.WriteOpaque8(SerializeECFieldElement(fieldSize, x), output); + } + + public static void WriteECParameter(BigInteger x, Stream output) + { + TlsUtilities.WriteOpaque8(BigIntegers.AsUnsignedByteArray(x), output); + } + + public static void WriteExplicitECParameters(byte[] ecPointFormats, ECDomainParameters ecParameters, + Stream output) + { + ECCurve curve = ecParameters.Curve; + + if (ECAlgorithms.IsFpCurve(curve)) + { + TlsUtilities.WriteUint8(ECCurveType.explicit_prime, output); + + WriteECParameter(curve.Field.Characteristic, output); + } + else if (ECAlgorithms.IsF2mCurve(curve)) + { + IPolynomialExtensionField field = (IPolynomialExtensionField)curve.Field; + int[] exponents = field.MinimalPolynomial.GetExponentsPresent(); + + TlsUtilities.WriteUint8(ECCurveType.explicit_char2, output); + + int m = exponents[exponents.Length - 1]; + TlsUtilities.CheckUint16(m); + TlsUtilities.WriteUint16(m, output); + + if (exponents.Length == 3) + { + TlsUtilities.WriteUint8(ECBasisType.ec_basis_trinomial, output); + WriteECExponent(exponents[1], output); + } + else if (exponents.Length == 5) + { + TlsUtilities.WriteUint8(ECBasisType.ec_basis_pentanomial, output); + WriteECExponent(exponents[1], output); + WriteECExponent(exponents[2], output); + WriteECExponent(exponents[3], output); + } + else + { + throw new ArgumentException("Only trinomial and pentomial curves are supported"); + } + } + else + { + throw new ArgumentException("'ecParameters' not a known curve type"); + } + + WriteECFieldElement(curve.A, output); + WriteECFieldElement(curve.B, output); + TlsUtilities.WriteOpaque8(SerializeECPoint(ecPointFormats, ecParameters.G), output); + WriteECParameter(ecParameters.N, output); + WriteECParameter(ecParameters.H, output); + } + + public static void WriteECPoint(byte[] ecPointFormats, ECPoint point, Stream output) + { + TlsUtilities.WriteOpaque8(SerializeECPoint(ecPointFormats, point), output); + } + + public static void WriteNamedECParameters(int namedCurve, Stream output) + { + if (!NamedCurve.RefersToASpecificNamedCurve(namedCurve)) + { + /* + * RFC 4492 5.4. All those values of NamedCurve are allowed that refer to a specific + * curve. Values of NamedCurve that indicate support for a class of explicitly defined + * curves are not allowed here [...]. + */ + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + TlsUtilities.WriteUint8(ECCurveType.named_curve, output); + TlsUtilities.CheckUint16(namedCurve); + TlsUtilities.WriteUint16(namedCurve, output); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsEncryptionCredentials.cs b/bc-sharp-crypto/src/crypto/tls/TlsEncryptionCredentials.cs new file mode 100644 index 0000000..52f0070 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsEncryptionCredentials.cs @@ -0,0 +1,12 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public interface TlsEncryptionCredentials + : TlsCredentials + { + /// + byte[] DecryptPreMasterSecret(byte[] encryptedPreMasterSecret); + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsException.cs b/bc-sharp-crypto/src/crypto/tls/TlsException.cs new file mode 100644 index 0000000..cea9e3e --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsException.cs @@ -0,0 +1,14 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class TlsException + : IOException + { + public TlsException(string message, Exception cause) + : base(message, cause) + { + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsExtensionsUtilities.cs b/bc-sharp-crypto/src/crypto/tls/TlsExtensionsUtilities.cs new file mode 100644 index 0000000..4b3d9e0 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsExtensionsUtilities.cs @@ -0,0 +1,368 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public abstract class TlsExtensionsUtilities + { + public static IDictionary EnsureExtensionsInitialised(IDictionary extensions) + { + return extensions == null ? Platform.CreateHashtable() : extensions; + } + + /// + public static void AddClientCertificateTypeExtensionClient(IDictionary extensions, byte[] certificateTypes) + { + extensions[ExtensionType.client_certificate_type] = CreateCertificateTypeExtensionClient(certificateTypes); + } + + /// + public static void AddClientCertificateTypeExtensionServer(IDictionary extensions, byte certificateType) + { + extensions[ExtensionType.client_certificate_type] = CreateCertificateTypeExtensionServer(certificateType); + } + + public static void AddEncryptThenMacExtension(IDictionary extensions) + { + extensions[ExtensionType.encrypt_then_mac] = CreateEncryptThenMacExtension(); + } + + public static void AddExtendedMasterSecretExtension(IDictionary extensions) + { + extensions[ExtensionType.extended_master_secret] = CreateExtendedMasterSecretExtension(); + } + + /// + public static void AddHeartbeatExtension(IDictionary extensions, HeartbeatExtension heartbeatExtension) + { + extensions[ExtensionType.heartbeat] = CreateHeartbeatExtension(heartbeatExtension); + } + + /// + public static void AddMaxFragmentLengthExtension(IDictionary extensions, byte maxFragmentLength) + { + extensions[ExtensionType.max_fragment_length] = CreateMaxFragmentLengthExtension(maxFragmentLength); + } + + /// + public static void AddPaddingExtension(IDictionary extensions, int dataLength) + { + extensions[ExtensionType.padding] = CreatePaddingExtension(dataLength); + } + + /// + public static void AddServerCertificateTypeExtensionClient(IDictionary extensions, byte[] certificateTypes) + { + extensions[ExtensionType.server_certificate_type] = CreateCertificateTypeExtensionClient(certificateTypes); + } + + /// + public static void AddServerCertificateTypeExtensionServer(IDictionary extensions, byte certificateType) + { + extensions[ExtensionType.server_certificate_type] = CreateCertificateTypeExtensionServer(certificateType); + } + + /// + public static void AddServerNameExtension(IDictionary extensions, ServerNameList serverNameList) + { + extensions[ExtensionType.server_name] = CreateServerNameExtension(serverNameList); + } + + /// + public static void AddStatusRequestExtension(IDictionary extensions, CertificateStatusRequest statusRequest) + { + extensions[ExtensionType.status_request] = CreateStatusRequestExtension(statusRequest); + } + + public static void AddTruncatedHMacExtension(IDictionary extensions) + { + extensions[ExtensionType.truncated_hmac] = CreateTruncatedHMacExtension(); + } + + /// + public static byte[] GetClientCertificateTypeExtensionClient(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.client_certificate_type); + return extensionData == null ? null : ReadCertificateTypeExtensionClient(extensionData); + } + + /// + public static short GetClientCertificateTypeExtensionServer(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.client_certificate_type); + return extensionData == null ? (short)-1 : (short)ReadCertificateTypeExtensionServer(extensionData); + } + + /// + public static HeartbeatExtension GetHeartbeatExtension(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.heartbeat); + return extensionData == null ? null : ReadHeartbeatExtension(extensionData); + } + + /// + public static short GetMaxFragmentLengthExtension(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.max_fragment_length); + return extensionData == null ? (short)-1 : (short)ReadMaxFragmentLengthExtension(extensionData); + } + + /// + public static int GetPaddingExtension(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.padding); + return extensionData == null ? -1 : ReadPaddingExtension(extensionData); + } + + /// + public static byte[] GetServerCertificateTypeExtensionClient(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.server_certificate_type); + return extensionData == null ? null : ReadCertificateTypeExtensionClient(extensionData); + } + + /// + public static short GetServerCertificateTypeExtensionServer(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.server_certificate_type); + return extensionData == null ? (short)-1 : (short)ReadCertificateTypeExtensionServer(extensionData); + } + + /// + public static ServerNameList GetServerNameExtension(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.server_name); + return extensionData == null ? null : ReadServerNameExtension(extensionData); + } + + /// + public static CertificateStatusRequest GetStatusRequestExtension(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.status_request); + return extensionData == null ? null : ReadStatusRequestExtension(extensionData); + } + + /// + public static bool HasEncryptThenMacExtension(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.encrypt_then_mac); + return extensionData == null ? false : ReadEncryptThenMacExtension(extensionData); + } + + /// + public static bool HasExtendedMasterSecretExtension(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.extended_master_secret); + return extensionData == null ? false : ReadExtendedMasterSecretExtension(extensionData); + } + + /// + public static bool HasTruncatedHMacExtension(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.truncated_hmac); + return extensionData == null ? false : ReadTruncatedHMacExtension(extensionData); + } + + /// + public static byte[] CreateCertificateTypeExtensionClient(byte[] certificateTypes) + { + if (certificateTypes == null || certificateTypes.Length < 1 || certificateTypes.Length > 255) + throw new TlsFatalAlert(AlertDescription.internal_error); + + return TlsUtilities.EncodeUint8ArrayWithUint8Length(certificateTypes); + } + + /// + public static byte[] CreateCertificateTypeExtensionServer(byte certificateType) + { + return TlsUtilities.EncodeUint8(certificateType); + } + + public static byte[] CreateEmptyExtensionData() + { + return TlsUtilities.EmptyBytes; + } + + public static byte[] CreateEncryptThenMacExtension() + { + return CreateEmptyExtensionData(); + } + + public static byte[] CreateExtendedMasterSecretExtension() + { + return CreateEmptyExtensionData(); + } + + /// + public static byte[] CreateHeartbeatExtension(HeartbeatExtension heartbeatExtension) + { + if (heartbeatExtension == null) + throw new TlsFatalAlert(AlertDescription.internal_error); + + MemoryStream buf = new MemoryStream(); + + heartbeatExtension.Encode(buf); + + return buf.ToArray(); + } + + /// + public static byte[] CreateMaxFragmentLengthExtension(byte maxFragmentLength) + { + return TlsUtilities.EncodeUint8(maxFragmentLength); + } + + /// + public static byte[] CreatePaddingExtension(int dataLength) + { + TlsUtilities.CheckUint16(dataLength); + return new byte[dataLength]; + } + + /// + public static byte[] CreateServerNameExtension(ServerNameList serverNameList) + { + if (serverNameList == null) + throw new TlsFatalAlert(AlertDescription.internal_error); + + MemoryStream buf = new MemoryStream(); + + serverNameList.Encode(buf); + + return buf.ToArray(); + } + + /// + public static byte[] CreateStatusRequestExtension(CertificateStatusRequest statusRequest) + { + if (statusRequest == null) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + MemoryStream buf = new MemoryStream(); + + statusRequest.Encode(buf); + + return buf.ToArray(); + } + + public static byte[] CreateTruncatedHMacExtension() + { + return CreateEmptyExtensionData(); + } + + /// + private static bool ReadEmptyExtensionData(byte[] extensionData) + { + if (extensionData == null) + throw new ArgumentNullException("extensionData"); + + if (extensionData.Length != 0) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + + return true; + } + + /// + public static byte[] ReadCertificateTypeExtensionClient(byte[] extensionData) + { + byte[] certificateTypes = TlsUtilities.DecodeUint8ArrayWithUint8Length(extensionData); + if (certificateTypes.Length < 1) + throw new TlsFatalAlert(AlertDescription.decode_error); + return certificateTypes; + } + + /// + public static byte ReadCertificateTypeExtensionServer(byte[] extensionData) + { + return TlsUtilities.DecodeUint8(extensionData); + } + + /// + public static bool ReadEncryptThenMacExtension(byte[] extensionData) + { + return ReadEmptyExtensionData(extensionData); + } + + /// + public static bool ReadExtendedMasterSecretExtension(byte[] extensionData) + { + return ReadEmptyExtensionData(extensionData); + } + + /// + public static HeartbeatExtension ReadHeartbeatExtension(byte[] extensionData) + { + if (extensionData == null) + throw new ArgumentNullException("extensionData"); + + MemoryStream buf = new MemoryStream(extensionData, false); + + HeartbeatExtension heartbeatExtension = HeartbeatExtension.Parse(buf); + + TlsProtocol.AssertEmpty(buf); + + return heartbeatExtension; + } + + /// + public static byte ReadMaxFragmentLengthExtension(byte[] extensionData) + { + return TlsUtilities.DecodeUint8(extensionData); + } + + /// + public static int ReadPaddingExtension(byte[] extensionData) + { + if (extensionData == null) + throw new ArgumentNullException("extensionData"); + + for (int i = 0; i < extensionData.Length; ++i) + { + if (extensionData[i] != 0) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + return extensionData.Length; + } + + /// + public static ServerNameList ReadServerNameExtension(byte[] extensionData) + { + if (extensionData == null) + throw new ArgumentNullException("extensionData"); + + MemoryStream buf = new MemoryStream(extensionData, false); + + ServerNameList serverNameList = ServerNameList.Parse(buf); + + TlsProtocol.AssertEmpty(buf); + + return serverNameList; + } + + /// + public static CertificateStatusRequest ReadStatusRequestExtension(byte[] extensionData) + { + if (extensionData == null) + throw new ArgumentNullException("extensionData"); + + MemoryStream buf = new MemoryStream(extensionData, false); + + CertificateStatusRequest statusRequest = CertificateStatusRequest.Parse(buf); + + TlsProtocol.AssertEmpty(buf); + + return statusRequest; + } + + /// + public static bool ReadTruncatedHMacExtension(byte[] extensionData) + { + return ReadEmptyExtensionData(extensionData); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsFatalAlert.cs b/bc-sharp-crypto/src/crypto/tls/TlsFatalAlert.cs new file mode 100644 index 0000000..6f18981 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsFatalAlert.cs @@ -0,0 +1,26 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class TlsFatalAlert + : TlsException + { + private readonly byte alertDescription; + + public TlsFatalAlert(byte alertDescription) + : this(alertDescription, null) + { + } + + public TlsFatalAlert(byte alertDescription, Exception alertCause) + : base(Tls.AlertDescription.GetText(alertDescription), alertCause) + { + this.alertDescription = alertDescription; + } + + public virtual byte AlertDescription + { + get { return alertDescription; } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsFatalAlertReceived.cs b/bc-sharp-crypto/src/crypto/tls/TlsFatalAlertReceived.cs new file mode 100644 index 0000000..044fc80 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsFatalAlertReceived.cs @@ -0,0 +1,21 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class TlsFatalAlertReceived + : TlsException + { + private readonly byte alertDescription; + + public TlsFatalAlertReceived(byte alertDescription) + : base(Tls.AlertDescription.GetText(alertDescription), null) + { + this.alertDescription = alertDescription; + } + + public virtual byte AlertDescription + { + get { return alertDescription; } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsHandshakeHash.cs b/bc-sharp-crypto/src/crypto/tls/TlsHandshakeHash.cs new file mode 100644 index 0000000..7118d97 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsHandshakeHash.cs @@ -0,0 +1,22 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public interface TlsHandshakeHash + : IDigest + { + void Init(TlsContext context); + + TlsHandshakeHash NotifyPrfDetermined(); + + void TrackHashAlgorithm(byte hashAlgorithm); + + void SealHashAlgorithms(); + + TlsHandshakeHash StopTracking(); + + IDigest ForkPrfHash(); + + byte[] GetFinalHash(byte hashAlgorithm); + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsKeyExchange.cs b/bc-sharp-crypto/src/crypto/tls/TlsKeyExchange.cs new file mode 100644 index 0000000..6731f6f --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsKeyExchange.cs @@ -0,0 +1,54 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /// + /// A generic interface for key exchange implementations in (D)TLS. + /// + public interface TlsKeyExchange + { + void Init(TlsContext context); + + /// + void SkipServerCredentials(); + + /// + void ProcessServerCredentials(TlsCredentials serverCredentials); + + /// + void ProcessServerCertificate(Certificate serverCertificate); + + bool RequiresServerKeyExchange { get; } + + /// + byte[] GenerateServerKeyExchange(); + + /// + void SkipServerKeyExchange(); + + /// + void ProcessServerKeyExchange(Stream input); + + /// + void ValidateCertificateRequest(CertificateRequest certificateRequest); + + /// + void SkipClientCredentials(); + + /// + void ProcessClientCredentials(TlsCredentials clientCredentials); + + /// + void ProcessClientCertificate(Certificate clientCertificate); + + /// + void GenerateClientKeyExchange(Stream output); + + /// + void ProcessClientKeyExchange(Stream input); + + /// + byte[] GeneratePremasterSecret(); + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsMac.cs b/bc-sharp-crypto/src/crypto/tls/TlsMac.cs new file mode 100644 index 0000000..a80319a --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsMac.cs @@ -0,0 +1,173 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Macs; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /// + /// A generic TLS MAC implementation, acting as an HMAC based on some underlying Digest. + /// + public class TlsMac + { + protected readonly TlsContext context; + protected readonly byte[] secret; + protected readonly IMac mac; + protected readonly int digestBlockSize; + protected readonly int digestOverhead; + protected readonly int macLength; + + /** + * Generate a new instance of an TlsMac. + * + * @param context the TLS client context + * @param digest The digest to use. + * @param key A byte-array where the key for this MAC is located. + * @param keyOff The number of bytes to skip, before the key starts in the buffer. + * @param keyLen The length of the key. + */ + public TlsMac(TlsContext context, IDigest digest, byte[] key, int keyOff, int keyLen) + { + this.context = context; + + KeyParameter keyParameter = new KeyParameter(key, keyOff, keyLen); + + this.secret = Arrays.Clone(keyParameter.GetKey()); + + // TODO This should check the actual algorithm, not rely on the engine type + if (digest is LongDigest) + { + this.digestBlockSize = 128; + this.digestOverhead = 16; + } + else + { + this.digestBlockSize = 64; + this.digestOverhead = 8; + } + + if (TlsUtilities.IsSsl(context)) + { + this.mac = new Ssl3Mac(digest); + + // TODO This should check the actual algorithm, not assume based on the digest size + if (digest.GetDigestSize() == 20) + { + /* + * NOTE: When SHA-1 is used with the SSL 3.0 MAC, the secret + input pad is not + * digest block-aligned. + */ + this.digestOverhead = 4; + } + } + else + { + this.mac = new HMac(digest); + + // NOTE: The input pad for HMAC is always a full digest block + } + + this.mac.Init(keyParameter); + + this.macLength = mac.GetMacSize(); + if (context.SecurityParameters.truncatedHMac) + { + this.macLength = System.Math.Min(this.macLength, 10); + } + } + + /** + * @return the MAC write secret + */ + public virtual byte[] MacSecret + { + get { return this.secret; } + } + + /** + * @return The output length of this MAC. + */ + public virtual int Size + { + get { return macLength; } + } + + /** + * Calculate the MAC for some given data. + * + * @param type The message type of the message. + * @param message A byte-buffer containing the message. + * @param offset The number of bytes to skip, before the message starts. + * @param length The length of the message. + * @return A new byte-buffer containing the MAC value. + */ + public virtual byte[] CalculateMac(long seqNo, byte type, byte[] message, int offset, int length) + { + ProtocolVersion serverVersion = context.ServerVersion; + bool isSsl = serverVersion.IsSsl; + + byte[] macHeader = new byte[isSsl ? 11 : 13]; + TlsUtilities.WriteUint64(seqNo, macHeader, 0); + TlsUtilities.WriteUint8(type, macHeader, 8); + if (!isSsl) + { + TlsUtilities.WriteVersion(serverVersion, macHeader, 9); + } + TlsUtilities.WriteUint16(length, macHeader, macHeader.Length - 2); + + mac.BlockUpdate(macHeader, 0, macHeader.Length); + mac.BlockUpdate(message, offset, length); + + return Truncate(MacUtilities.DoFinal(mac)); + } + + public virtual byte[] CalculateMacConstantTime(long seqNo, byte type, byte[] message, int offset, int length, + int fullLength, byte[] dummyData) + { + /* + * Actual MAC only calculated on 'length' bytes... + */ + byte[] result = CalculateMac(seqNo, type, message, offset, length); + + /* + * ...but ensure a constant number of complete digest blocks are processed (as many as would + * be needed for 'fullLength' bytes of input). + */ + int headerLength = TlsUtilities.IsSsl(context) ? 11 : 13; + + // How many extra full blocks do we need to calculate? + int extra = GetDigestBlockCount(headerLength + fullLength) - GetDigestBlockCount(headerLength + length); + + while (--extra >= 0) + { + mac.BlockUpdate(dummyData, 0, digestBlockSize); + } + + // One more byte in case the implementation is "lazy" about processing blocks + mac.Update(dummyData[0]); + mac.Reset(); + + return result; + } + + protected virtual int GetDigestBlockCount(int inputLength) + { + // NOTE: This calculation assumes a minimum of 1 pad byte + return (inputLength + digestOverhead) / digestBlockSize; + } + + protected virtual byte[] Truncate(byte[] bs) + { + if (bs.Length <= macLength) + { + return bs; + } + + return Arrays.CopyOf(bs, macLength); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsNoCloseNotifyException.cs b/bc-sharp-crypto/src/crypto/tls/TlsNoCloseNotifyException.cs new file mode 100644 index 0000000..0bafd82 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsNoCloseNotifyException.cs @@ -0,0 +1,23 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /// + /// This exception will be thrown(only) when the connection is closed by the peer without sending a + /// close_notify warning alert. + /// + /// + /// If this happens, the TLS protocol cannot rule out truncation of the connection data (potentially + /// malicious). It may be possible to check for truncation via some property of a higher level protocol + /// built upon TLS, e.g.the Content-Length header for HTTPS. + /// + public class TlsNoCloseNotifyException + : EndOfStreamException + { + public TlsNoCloseNotifyException() + : base("No close_notify alert received before connection closed") + { + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsNullCipher.cs b/bc-sharp-crypto/src/crypto/tls/TlsNullCipher.cs new file mode 100644 index 0000000..f30ace2 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsNullCipher.cs @@ -0,0 +1,118 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /// + /// A NULL CipherSuite, with optional MAC. + /// + public class TlsNullCipher + : TlsCipher + { + protected readonly TlsContext context; + + protected readonly TlsMac writeMac; + protected readonly TlsMac readMac; + + public TlsNullCipher(TlsContext context) + { + this.context = context; + this.writeMac = null; + this.readMac = null; + } + + /// + public TlsNullCipher(TlsContext context, IDigest clientWriteDigest, IDigest serverWriteDigest) + { + if ((clientWriteDigest == null) != (serverWriteDigest == null)) + throw new TlsFatalAlert(AlertDescription.internal_error); + + this.context = context; + + TlsMac clientWriteMac = null, serverWriteMac = null; + + if (clientWriteDigest != null) + { + int key_block_size = clientWriteDigest.GetDigestSize() + + serverWriteDigest.GetDigestSize(); + byte[] key_block = TlsUtilities.CalculateKeyBlock(context, key_block_size); + + int offset = 0; + + clientWriteMac = new TlsMac(context, clientWriteDigest, key_block, offset, + clientWriteDigest.GetDigestSize()); + offset += clientWriteDigest.GetDigestSize(); + + serverWriteMac = new TlsMac(context, serverWriteDigest, key_block, offset, + serverWriteDigest.GetDigestSize()); + offset += serverWriteDigest.GetDigestSize(); + + if (offset != key_block_size) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + if (context.IsServer) + { + writeMac = serverWriteMac; + readMac = clientWriteMac; + } + else + { + writeMac = clientWriteMac; + readMac = serverWriteMac; + } + } + + public virtual int GetPlaintextLimit(int ciphertextLimit) + { + int result = ciphertextLimit; + if (writeMac != null) + { + result -= writeMac.Size; + } + return result; + } + + /// + public virtual byte[] EncodePlaintext(long seqNo, byte type, byte[] plaintext, int offset, int len) + { + if (writeMac == null) + { + return Arrays.CopyOfRange(plaintext, offset, offset + len); + } + + byte[] mac = writeMac.CalculateMac(seqNo, type, plaintext, offset, len); + byte[] ciphertext = new byte[len + mac.Length]; + Array.Copy(plaintext, offset, ciphertext, 0, len); + Array.Copy(mac, 0, ciphertext, len, mac.Length); + return ciphertext; + } + + /// + public virtual byte[] DecodeCiphertext(long seqNo, byte type, byte[] ciphertext, int offset, int len) + { + if (readMac == null) + { + return Arrays.CopyOfRange(ciphertext, offset, offset + len); + } + + int macSize = readMac.Size; + if (len < macSize) + throw new TlsFatalAlert(AlertDescription.decode_error); + + int macInputLen = len - macSize; + + byte[] receivedMac = Arrays.CopyOfRange(ciphertext, offset + macInputLen, offset + len); + byte[] computedMac = readMac.CalculateMac(seqNo, type, ciphertext, offset, macInputLen); + + if (!Arrays.ConstantTimeAreEqual(receivedMac, computedMac)) + throw new TlsFatalAlert(AlertDescription.bad_record_mac); + + return Arrays.CopyOfRange(ciphertext, offset, offset + macInputLen); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsNullCompression.cs b/bc-sharp-crypto/src/crypto/tls/TlsNullCompression.cs new file mode 100644 index 0000000..45f8fc7 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsNullCompression.cs @@ -0,0 +1,19 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class TlsNullCompression + : TlsCompression + { + public virtual Stream Compress(Stream output) + { + return output; + } + + public virtual Stream Decompress(Stream output) + { + return output; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsPeer.cs b/bc-sharp-crypto/src/crypto/tls/TlsPeer.cs new file mode 100644 index 0000000..1ae41a4 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsPeer.cs @@ -0,0 +1,62 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public interface TlsPeer + { + /// + /// draft-mathewson-no-gmtunixtime-00 2. "If existing users of a TLS implementation may rely on + /// gmt_unix_time containing the current time, we recommend that implementors MAY provide the + /// ability to set gmt_unix_time as an option only, off by default." + /// + /// + /// true if the current time should be used in the gmt_unix_time field of + /// Random, or false if gmt_unix_time should contain a cryptographically + /// random value. + /// + bool ShouldUseGmtUnixTime(); + + /// + /// Report whether the server supports secure renegotiation + /// + /// + /// The protocol handler automatically processes the relevant extensions + /// + /// + /// A , true if the server supports secure renegotiation + /// + /// + void NotifySecureRenegotiation(bool secureRenegotiation); + + /// + /// Return an implementation of to handle record compression. + /// + /// A + /// + TlsCompression GetCompression(); + + /// + /// Return an implementation of to use for encryption/decryption. + /// + /// A + /// + TlsCipher GetCipher(); + + /// This method will be called when an alert is raised by the protocol. + /// + /// + /// A human-readable message explaining what caused this alert. May be null. + /// The Exception that caused this alert to be raised. May be null. + void NotifyAlertRaised(byte alertLevel, byte alertDescription, string message, Exception cause); + + /// This method will be called when an alert is received from the remote peer. + /// + /// + void NotifyAlertReceived(byte alertLevel, byte alertDescription); + + /// Notifies the peer that the handshake has been successfully completed. + /// + void NotifyHandshakeComplete(); + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsProtocol.cs b/bc-sharp-crypto/src/crypto/tls/TlsProtocol.cs new file mode 100644 index 0000000..72151d4 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsProtocol.cs @@ -0,0 +1,1450 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Crypto.Prng; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public abstract class TlsProtocol + { + /* + * Our Connection states + */ + protected const short CS_START = 0; + protected const short CS_CLIENT_HELLO = 1; + protected const short CS_SERVER_HELLO = 2; + protected const short CS_SERVER_SUPPLEMENTAL_DATA = 3; + protected const short CS_SERVER_CERTIFICATE = 4; + protected const short CS_CERTIFICATE_STATUS = 5; + protected const short CS_SERVER_KEY_EXCHANGE = 6; + protected const short CS_CERTIFICATE_REQUEST = 7; + protected const short CS_SERVER_HELLO_DONE = 8; + protected const short CS_CLIENT_SUPPLEMENTAL_DATA = 9; + protected const short CS_CLIENT_CERTIFICATE = 10; + protected const short CS_CLIENT_KEY_EXCHANGE = 11; + protected const short CS_CERTIFICATE_VERIFY = 12; + protected const short CS_CLIENT_FINISHED = 13; + protected const short CS_SERVER_SESSION_TICKET = 14; + protected const short CS_SERVER_FINISHED = 15; + protected const short CS_END = 16; + + /* + * Different modes to handle the known IV weakness + */ + protected const short ADS_MODE_1_Nsub1 = 0; // 1/n-1 record splitting + protected const short ADS_MODE_0_N = 1; // 0/n record splitting + protected const short ADS_MODE_0_N_FIRSTONLY = 2; // 0/n record splitting on first data fragment only + + /* + * Queues for data from some protocols. + */ + private ByteQueue mApplicationDataQueue = new ByteQueue(0); + private ByteQueue mAlertQueue = new ByteQueue(2); + private ByteQueue mHandshakeQueue = new ByteQueue(0); + // private ByteQueue mHeartbeatQueue = new ByteQueue(); + + /* + * The Record Stream we use + */ + internal RecordStream mRecordStream; + protected SecureRandom mSecureRandom; + + private TlsStream mTlsStream = null; + + private volatile bool mClosed = false; + private volatile bool mFailedWithError = false; + private volatile bool mAppDataReady = false; + private volatile bool mAppDataSplitEnabled = true; + private volatile int mAppDataSplitMode = ADS_MODE_1_Nsub1; + private byte[] mExpectedVerifyData = null; + + protected TlsSession mTlsSession = null; + protected SessionParameters mSessionParameters = null; + protected SecurityParameters mSecurityParameters = null; + protected Certificate mPeerCertificate = null; + + protected int[] mOfferedCipherSuites = null; + protected byte[] mOfferedCompressionMethods = null; + protected IDictionary mClientExtensions = null; + protected IDictionary mServerExtensions = null; + + protected short mConnectionState = CS_START; + protected bool mResumedSession = false; + protected bool mReceivedChangeCipherSpec = false; + protected bool mSecureRenegotiation = false; + protected bool mAllowCertificateStatus = false; + protected bool mExpectSessionTicket = false; + + protected bool mBlocking = true; + protected ByteQueueStream mInputBuffers = null; + protected ByteQueueStream mOutputBuffer = null; + + public TlsProtocol(Stream stream, SecureRandom secureRandom) + : this(stream, stream, secureRandom) + { + } + + public TlsProtocol(Stream input, Stream output, SecureRandom secureRandom) + { + this.mRecordStream = new RecordStream(this, input, output); + this.mSecureRandom = secureRandom; + } + + public TlsProtocol(SecureRandom secureRandom) + { + this.mBlocking = false; + this.mInputBuffers = new ByteQueueStream(); + this.mOutputBuffer = new ByteQueueStream(); + this.mRecordStream = new RecordStream(this, mInputBuffers, mOutputBuffer); + this.mSecureRandom = secureRandom; + } + + protected abstract TlsContext Context { get; } + + internal abstract AbstractTlsContext ContextAdmin { get; } + + protected abstract TlsPeer Peer { get; } + + protected virtual void HandleAlertMessage(byte alertLevel, byte alertDescription) + { + Peer.NotifyAlertReceived(alertLevel, alertDescription); + + if (alertLevel == AlertLevel.warning) + { + HandleAlertWarningMessage(alertDescription); + } + else + { + HandleFailure(); + + throw new TlsFatalAlertReceived(alertDescription); + } + } + + protected virtual void HandleAlertWarningMessage(byte alertDescription) + { + /* + * RFC 5246 7.2.1. The other party MUST respond with a close_notify alert of its own + * and close down the connection immediately, discarding any pending writes. + */ + if (alertDescription == AlertDescription.close_notify) + { + if (!mAppDataReady) + throw new TlsFatalAlert(AlertDescription.handshake_failure); + + HandleClose(false); + } + } + + protected virtual void HandleChangeCipherSpecMessage() + { + } + + protected virtual void HandleClose(bool user_canceled) + { + if (!mClosed) + { + this.mClosed = true; + + if (user_canceled && !mAppDataReady) + { + RaiseAlertWarning(AlertDescription.user_canceled, "User canceled handshake"); + } + + RaiseAlertWarning(AlertDescription.close_notify, "Connection closed"); + + mRecordStream.SafeClose(); + + if (!mAppDataReady) + { + CleanupHandshake(); + } + } + } + + protected virtual void HandleException(byte alertDescription, string message, Exception cause) + { + if (!mClosed) + { + RaiseAlertFatal(alertDescription, message, cause); + + HandleFailure(); + } + } + + protected virtual void HandleFailure() + { + this.mClosed = true; + this.mFailedWithError = true; + + /* + * RFC 2246 7.2.1. The session becomes unresumable if any connection is terminated + * without proper close_notify messages with level equal to warning. + */ + // TODO This isn't quite in the right place. Also, as of TLS 1.1 the above is obsolete. + InvalidateSession(); + + mRecordStream.SafeClose(); + + if (!mAppDataReady) + { + CleanupHandshake(); + } + } + + protected abstract void HandleHandshakeMessage(byte type, MemoryStream buf); + + protected virtual void ApplyMaxFragmentLengthExtension() + { + if (mSecurityParameters.maxFragmentLength >= 0) + { + if (!MaxFragmentLength.IsValid((byte)mSecurityParameters.maxFragmentLength)) + throw new TlsFatalAlert(AlertDescription.internal_error); + + int plainTextLimit = 1 << (8 + mSecurityParameters.maxFragmentLength); + mRecordStream.SetPlaintextLimit(plainTextLimit); + } + } + + protected virtual void CheckReceivedChangeCipherSpec(bool expected) + { + if (expected != mReceivedChangeCipherSpec) + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + protected virtual void CleanupHandshake() + { + if (this.mExpectedVerifyData != null) + { + Arrays.Fill(this.mExpectedVerifyData, (byte)0); + this.mExpectedVerifyData = null; + } + + this.mSecurityParameters.Clear(); + this.mPeerCertificate = null; + + this.mOfferedCipherSuites = null; + this.mOfferedCompressionMethods = null; + this.mClientExtensions = null; + this.mServerExtensions = null; + + this.mResumedSession = false; + this.mReceivedChangeCipherSpec = false; + this.mSecureRenegotiation = false; + this.mAllowCertificateStatus = false; + this.mExpectSessionTicket = false; + } + + protected virtual void BlockForHandshake() + { + if (mBlocking) + { + while (this.mConnectionState != CS_END) + { + if (this.mClosed) + { + // NOTE: Any close during the handshake should have raised an exception. + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + SafeReadRecord(); + } + } + } + + protected virtual void CompleteHandshake() + { + try + { + this.mConnectionState = CS_END; + + this.mAlertQueue.Shrink(); + this.mHandshakeQueue.Shrink(); + + this.mRecordStream.FinaliseHandshake(); + + this.mAppDataSplitEnabled = !TlsUtilities.IsTlsV11(Context); + + /* + * If this was an initial handshake, we are now ready to send and receive application data. + */ + if (!mAppDataReady) + { + this.mAppDataReady = true; + + if (mBlocking) + { + this.mTlsStream = new TlsStream(this); + } + } + + if (this.mTlsSession != null) + { + if (this.mSessionParameters == null) + { + this.mSessionParameters = new SessionParameters.Builder() + .SetCipherSuite(this.mSecurityParameters.CipherSuite) + .SetCompressionAlgorithm(this.mSecurityParameters.CompressionAlgorithm) + .SetMasterSecret(this.mSecurityParameters.MasterSecret) + .SetPeerCertificate(this.mPeerCertificate) + .SetPskIdentity(this.mSecurityParameters.PskIdentity) + .SetSrpIdentity(this.mSecurityParameters.SrpIdentity) + // TODO Consider filtering extensions that aren't relevant to resumed sessions + .SetServerExtensions(this.mServerExtensions) + .Build(); + + this.mTlsSession = new TlsSessionImpl(this.mTlsSession.SessionID, this.mSessionParameters); + } + + ContextAdmin.SetResumableSession(this.mTlsSession); + } + + Peer.NotifyHandshakeComplete(); + } + finally + { + CleanupHandshake(); + } + } + + protected internal void ProcessRecord(byte protocol, byte[] buf, int off, int len) + { + /* + * Have a look at the protocol type, and add it to the correct queue. + */ + switch (protocol) + { + case ContentType.alert: + { + mAlertQueue.AddData(buf, off, len); + ProcessAlertQueue(); + break; + } + case ContentType.application_data: + { + if (!mAppDataReady) + throw new TlsFatalAlert(AlertDescription.unexpected_message); + + mApplicationDataQueue.AddData(buf, off, len); + ProcessApplicationDataQueue(); + break; + } + case ContentType.change_cipher_spec: + { + ProcessChangeCipherSpec(buf, off, len); + break; + } + case ContentType.handshake: + { + if (mHandshakeQueue.Available > 0) + { + mHandshakeQueue.AddData(buf, off, len); + ProcessHandshakeQueue(mHandshakeQueue); + } + else + { + ByteQueue tmpQueue = new ByteQueue(buf, off, len); + ProcessHandshakeQueue(tmpQueue); + int remaining = tmpQueue.Available; + if (remaining > 0) + { + mHandshakeQueue.AddData(buf, off + len - remaining, remaining); + } + } + break; + } + //case ContentType.heartbeat: + //{ + // if (!mAppDataReady) + // throw new TlsFatalAlert(AlertDescription.unexpected_message); + + // // TODO[RFC 6520] + // //mHeartbeatQueue.AddData(buf, offset, len); + // //ProcessHeartbeat(); + // break; + //} + default: + // Record type should already have been checked + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + private void ProcessHandshakeQueue(ByteQueue queue) + { + while (queue.Available >= 4) + { + /* + * We need the first 4 bytes, they contain type and length of the message. + */ + byte[] beginning = new byte[4]; + queue.Read(beginning, 0, 4, 0); + byte type = TlsUtilities.ReadUint8(beginning, 0); + int length = TlsUtilities.ReadUint24(beginning, 1); + int totalLength = 4 + length; + + /* + * Check if we have enough bytes in the buffer to read the full message. + */ + if (queue.Available < totalLength) + break; + + CheckReceivedChangeCipherSpec(mConnectionState == CS_END || type == HandshakeType.finished); + + /* + * RFC 2246 7.4.9. The value handshake_messages includes all handshake messages + * starting at client hello up to, but not including, this finished message. + * [..] Note: [Also,] Hello Request messages are omitted from handshake hashes. + */ + switch (type) + { + case HandshakeType.hello_request: + break; + case HandshakeType.finished: + default: + { + TlsContext ctx = Context; + if (type == HandshakeType.finished + && this.mExpectedVerifyData == null + && ctx.SecurityParameters.MasterSecret != null) + { + this.mExpectedVerifyData = CreateVerifyData(!ctx.IsServer); + } + + queue.CopyTo(mRecordStream.HandshakeHashUpdater, totalLength); + break; + } + } + + queue.RemoveData(4); + + MemoryStream buf = queue.ReadFrom(length); + + /* + * Now, parse the message. + */ + HandleHandshakeMessage(type, buf); + } + } + + private void ProcessApplicationDataQueue() + { + /* + * There is nothing we need to do here. + * + * This function could be used for callbacks when application data arrives in the future. + */ + } + + private void ProcessAlertQueue() + { + while (mAlertQueue.Available >= 2) + { + /* + * An alert is always 2 bytes. Read the alert. + */ + byte[] alert = mAlertQueue.RemoveData(2, 0); + byte alertLevel = alert[0]; + byte alertDescription = alert[1]; + + HandleAlertMessage(alertLevel, alertDescription); + } + } + + /** + * 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(byte[] buf, int off, int len) + { + for (int i = 0; i < len; ++i) + { + byte message = TlsUtilities.ReadUint8(buf, off + i); + + if (message != ChangeCipherSpec.change_cipher_spec) + throw new TlsFatalAlert(AlertDescription.decode_error); + + if (this.mReceivedChangeCipherSpec + || mAlertQueue.Available > 0 + || mHandshakeQueue.Available > 0) + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + mRecordStream.ReceivedReadCipherSpec(); + + this.mReceivedChangeCipherSpec = true; + + HandleChangeCipherSpecMessage(); + } + } + + protected internal virtual int ApplicationDataAvailable() + { + return mApplicationDataQueue.Available; + } + + /** + * 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 internal virtual int ReadApplicationData(byte[] buf, int offset, int len) + { + if (len < 1) + return 0; + + while (mApplicationDataQueue.Available == 0) + { + if (this.mClosed) + { + if (this.mFailedWithError) + throw new IOException("Cannot read application data on failed TLS connection"); + + if (!mAppDataReady) + throw new InvalidOperationException("Cannot read application data until initial handshake completed."); + + return 0; + } + + SafeReadRecord(); + } + + len = System.Math.Min(len, mApplicationDataQueue.Available); + mApplicationDataQueue.RemoveData(buf, offset, len, 0); + return len; + } + + protected virtual void SafeCheckRecordHeader(byte[] recordHeader) + { + try + { + mRecordStream.CheckRecordHeader(recordHeader); + } + catch (TlsFatalAlert e) + { + HandleException(e.AlertDescription, "Failed to read record", e); + throw e; + } + catch (IOException e) + { + HandleException(AlertDescription.internal_error, "Failed to read record", e); + throw e; + } + catch (Exception e) + { + HandleException(AlertDescription.internal_error, "Failed to read record", e); + throw new TlsFatalAlert(AlertDescription.internal_error, e); + } + } + + protected virtual void SafeReadRecord() + { + try + { + if (mRecordStream.ReadRecord()) + return; + + if (!mAppDataReady) + throw new TlsFatalAlert(AlertDescription.handshake_failure); + } + catch (TlsFatalAlertReceived e) + { + // Connection failure already handled at source + throw e; + } + catch (TlsFatalAlert e) + { + HandleException(e.AlertDescription, "Failed to read record", e); + throw e; + } + catch (IOException e) + { + HandleException(AlertDescription.internal_error, "Failed to read record", e); + throw e; + } + catch (Exception e) + { + HandleException(AlertDescription.internal_error, "Failed to read record", e); + throw new TlsFatalAlert(AlertDescription.internal_error, e); + } + + HandleFailure(); + + throw new TlsNoCloseNotifyException(); + } + + protected virtual void SafeWriteRecord(byte type, byte[] buf, int offset, int len) + { + try + { + mRecordStream.WriteRecord(type, buf, offset, len); + } + catch (TlsFatalAlert e) + { + HandleException(e.AlertDescription, "Failed to write record", e); + throw e; + } + catch (IOException e) + { + HandleException(AlertDescription.internal_error, "Failed to write record", e); + throw e; + } + catch (Exception e) + { + HandleException(AlertDescription.internal_error, "Failed to write record", e); + throw new TlsFatalAlert(AlertDescription.internal_error, e); + } + } + + /** + * 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 internal virtual void WriteData(byte[] buf, int offset, int len) + { + if (this.mClosed) + throw new IOException("Cannot write application data on closed/failed TLS connection"); + + while (len > 0) + { + /* + * RFC 5246 6.2.1. Zero-length fragments of Application data MAY be sent as they are + * potentially useful as a traffic analysis countermeasure. + * + * NOTE: Actually, implementations appear to have settled on 1/n-1 record splitting. + */ + + if (this.mAppDataSplitEnabled) + { + /* + * Protect against known IV attack! + * + * DO NOT REMOVE THIS CODE, EXCEPT YOU KNOW EXACTLY WHAT YOU ARE DOING HERE. + */ + switch (mAppDataSplitMode) + { + case ADS_MODE_0_N: + SafeWriteRecord(ContentType.application_data, TlsUtilities.EmptyBytes, 0, 0); + break; + case ADS_MODE_0_N_FIRSTONLY: + this.mAppDataSplitEnabled = false; + SafeWriteRecord(ContentType.application_data, TlsUtilities.EmptyBytes, 0, 0); + break; + case ADS_MODE_1_Nsub1: + default: + SafeWriteRecord(ContentType.application_data, buf, offset, 1); + ++offset; + --len; + break; + } + } + + if (len > 0) + { + // Fragment data according to the current fragment limit. + int toWrite = System.Math.Min(len, mRecordStream.GetPlaintextLimit()); + SafeWriteRecord(ContentType.application_data, buf, offset, toWrite); + offset += toWrite; + len -= toWrite; + } + } + } + + protected virtual void SetAppDataSplitMode(int appDataSplitMode) + { + if (appDataSplitMode < ADS_MODE_1_Nsub1 || appDataSplitMode > ADS_MODE_0_N_FIRSTONLY) + throw new ArgumentException("Illegal appDataSplitMode mode: " + appDataSplitMode, "appDataSplitMode"); + + this.mAppDataSplitMode = appDataSplitMode; + } + + protected virtual void WriteHandshakeMessage(byte[] buf, int off, int len) + { + if (len < 4) + throw new TlsFatalAlert(AlertDescription.internal_error); + + byte type = TlsUtilities.ReadUint8(buf, off); + if (type != HandshakeType.hello_request) + { + mRecordStream.HandshakeHashUpdater.Write(buf, off, len); + } + + int total = 0; + do + { + // Fragment data according to the current fragment limit. + int toWrite = System.Math.Min(len - total, mRecordStream.GetPlaintextLimit()); + SafeWriteRecord(ContentType.handshake, buf, off + total, toWrite); + total += toWrite; + } + while (total < len); + } + + ///

The secure bidirectional stream for this connection + /// Only allowed in blocking mode. + public virtual Stream Stream + { + get + { + if (!mBlocking) + throw new InvalidOperationException("Cannot use Stream in non-blocking mode! Use OfferInput()/OfferOutput() instead."); + return this.mTlsStream; + } + } + + /** + * Should be called in non-blocking mode when the input data reaches EOF. + */ + public virtual void CloseInput() + { + if (mBlocking) + throw new InvalidOperationException("Cannot use CloseInput() in blocking mode!"); + + if (mClosed) + return; + + if (mInputBuffers.Available > 0) + throw new EndOfStreamException(); + + if (!mAppDataReady) + throw new TlsFatalAlert(AlertDescription.handshake_failure); + + throw new TlsNoCloseNotifyException(); + } + + /** + * Offer input from an arbitrary source. Only allowed in non-blocking mode.
+ *
+ * After this method returns, the input buffer is "owned" by this object. Other code + * must not attempt to do anything with it.
+ *
+ * This method will decrypt and process all records that are fully available. + * If only part of a record is available, the buffer will be retained until the + * remainder of the record is offered.
+ *
+ * If any records containing application data were processed, the decrypted data + * can be obtained using {@link #readInput(byte[], int, int)}. If any records + * containing protocol data were processed, a response may have been generated. + * You should always check to see if there is any available output after calling + * this method by calling {@link #getAvailableOutputBytes()}. + * @param input The input buffer to offer + * @throws IOException If an error occurs while decrypting or processing a record + */ + public virtual void OfferInput(byte[] input) + { + if (mBlocking) + throw new InvalidOperationException("Cannot use OfferInput() in blocking mode! Use Stream instead."); + if (mClosed) + throw new IOException("Connection is closed, cannot accept any more input"); + + mInputBuffers.Write(input); + + // loop while there are enough bytes to read the length of the next record + while (mInputBuffers.Available >= RecordStream.TLS_HEADER_SIZE) + { + byte[] recordHeader = new byte[RecordStream.TLS_HEADER_SIZE]; + mInputBuffers.Peek(recordHeader); + + int totalLength = TlsUtilities.ReadUint16(recordHeader, RecordStream.TLS_HEADER_LENGTH_OFFSET) + RecordStream.TLS_HEADER_SIZE; + if (mInputBuffers.Available < totalLength) + { + // not enough bytes to read a whole record + SafeCheckRecordHeader(recordHeader); + break; + } + + SafeReadRecord(); + + if (mClosed) + { + if (mConnectionState != CS_END) + { + // NOTE: Any close during the handshake should have raised an exception. + throw new TlsFatalAlert(AlertDescription.internal_error); + } + break; + } + } + } + + /** + * Gets the amount of received application data. A call to {@link #readInput(byte[], int, int)} + * is guaranteed to be able to return at least this much data.
+ *
+ * Only allowed in non-blocking mode. + * @return The number of bytes of available application data + */ + public virtual int GetAvailableInputBytes() + { + if (mBlocking) + throw new InvalidOperationException("Cannot use GetAvailableInputBytes() in blocking mode! Use ApplicationDataAvailable() instead."); + + return ApplicationDataAvailable(); + } + + /** + * Retrieves received application data. Use {@link #getAvailableInputBytes()} to check + * how much application data is currently available. This method functions similarly to + * {@link InputStream#read(byte[], int, int)}, except that it never blocks. If no data + * is available, nothing will be copied and zero will be returned.
+ *
+ * Only allowed in non-blocking mode. + * @param buffer The buffer to hold the application data + * @param offset The start offset in the buffer at which the data is written + * @param length The maximum number of bytes to read + * @return The total number of bytes copied to the buffer. May be less than the + * length specified if the length was greater than the amount of available data. + */ + public virtual int ReadInput(byte[] buffer, int offset, int length) + { + if (mBlocking) + throw new InvalidOperationException("Cannot use ReadInput() in blocking mode! Use Stream instead."); + + return ReadApplicationData(buffer, offset, System.Math.Min(length, ApplicationDataAvailable())); + } + + /** + * Offer output from an arbitrary source. Only allowed in non-blocking mode.
+ *
+ * After this method returns, the specified section of the buffer will have been + * processed. Use {@link #readOutput(byte[], int, int)} to get the bytes to + * transmit to the other peer.
+ *
+ * This method must not be called until after the handshake is complete! Attempting + * to call it before the handshake is complete will result in an exception. + * @param buffer The buffer containing application data to encrypt + * @param offset The offset at which to begin reading data + * @param length The number of bytes of data to read + * @throws IOException If an error occurs encrypting the data, or the handshake is not complete + */ + public virtual void OfferOutput(byte[] buffer, int offset, int length) + { + if (mBlocking) + throw new InvalidOperationException("Cannot use OfferOutput() in blocking mode! Use Stream instead."); + if (!mAppDataReady) + throw new IOException("Application data cannot be sent until the handshake is complete!"); + + WriteData(buffer, offset, length); + } + + /** + * Gets the amount of encrypted data available to be sent. A call to + * {@link #readOutput(byte[], int, int)} is guaranteed to be able to return at + * least this much data.
+ *
+ * Only allowed in non-blocking mode. + * @return The number of bytes of available encrypted data + */ + public virtual int GetAvailableOutputBytes() + { + if (mBlocking) + throw new InvalidOperationException("Cannot use GetAvailableOutputBytes() in blocking mode! Use Stream instead."); + + return mOutputBuffer.Available; + } + + /** + * Retrieves encrypted data to be sent. Use {@link #getAvailableOutputBytes()} to check + * how much encrypted data is currently available. This method functions similarly to + * {@link InputStream#read(byte[], int, int)}, except that it never blocks. If no data + * is available, nothing will be copied and zero will be returned.
+ *
+ * Only allowed in non-blocking mode. + * @param buffer The buffer to hold the encrypted data + * @param offset The start offset in the buffer at which the data is written + * @param length The maximum number of bytes to read + * @return The total number of bytes copied to the buffer. May be less than the + * length specified if the length was greater than the amount of available data. + */ + public virtual int ReadOutput(byte[] buffer, int offset, int length) + { + if (mBlocking) + throw new InvalidOperationException("Cannot use ReadOutput() in blocking mode! Use Stream instead."); + + return mOutputBuffer.Read(buffer, offset, length); + } + + protected virtual void InvalidateSession() + { + if (this.mSessionParameters != null) + { + this.mSessionParameters.Clear(); + this.mSessionParameters = null; + } + + if (this.mTlsSession != null) + { + this.mTlsSession.Invalidate(); + this.mTlsSession = null; + } + } + + protected virtual void ProcessFinishedMessage(MemoryStream buf) + { + if (mExpectedVerifyData == null) + throw new TlsFatalAlert(AlertDescription.internal_error); + + byte[] verify_data = TlsUtilities.ReadFully(mExpectedVerifyData.Length, buf); + + AssertEmpty(buf); + + /* + * Compare both checksums. + */ + if (!Arrays.ConstantTimeAreEqual(mExpectedVerifyData, verify_data)) + { + /* + * Wrong checksum in the finished message. + */ + throw new TlsFatalAlert(AlertDescription.decrypt_error); + } + } + + protected virtual void RaiseAlertFatal(byte alertDescription, string message, Exception cause) + { + Peer.NotifyAlertRaised(AlertLevel.fatal, alertDescription, message, cause); + + byte[] alert = new byte[]{ AlertLevel.fatal, alertDescription }; + + try + { + mRecordStream.WriteRecord(ContentType.alert, alert, 0, 2); + } + catch (Exception) + { + // We are already processing an exception, so just ignore this + } + } + + protected virtual void RaiseAlertWarning(byte alertDescription, string message) + { + Peer.NotifyAlertRaised(AlertLevel.warning, alertDescription, message, null); + + byte[] alert = new byte[]{ AlertLevel.warning, alertDescription }; + + SafeWriteRecord(ContentType.alert, alert, 0, 2); + } + + protected virtual void SendCertificateMessage(Certificate certificate) + { + if (certificate == null) + { + certificate = Certificate.EmptyChain; + } + + if (certificate.IsEmpty) + { + TlsContext context = Context; + if (!context.IsServer) + { + ProtocolVersion serverVersion = Context.ServerVersion; + if (serverVersion.IsSsl) + { + string errorMessage = serverVersion.ToString() + " client didn't provide credentials"; + RaiseAlertWarning(AlertDescription.no_certificate, errorMessage); + return; + } + } + } + + HandshakeMessage message = new HandshakeMessage(HandshakeType.certificate); + + certificate.Encode(message); + + message.WriteToRecordStream(this); + } + + protected virtual void SendChangeCipherSpecMessage() + { + byte[] message = new byte[]{ 1 }; + SafeWriteRecord(ContentType.change_cipher_spec, message, 0, message.Length); + mRecordStream.SentWriteCipherSpec(); + } + + protected virtual void SendFinishedMessage() + { + byte[] verify_data = CreateVerifyData(Context.IsServer); + + HandshakeMessage message = new HandshakeMessage(HandshakeType.finished, verify_data.Length); + + message.Write(verify_data, 0, verify_data.Length); + + message.WriteToRecordStream(this); + } + + protected virtual void SendSupplementalDataMessage(IList supplementalData) + { + HandshakeMessage message = new HandshakeMessage(HandshakeType.supplemental_data); + + WriteSupplementalData(message, supplementalData); + + message.WriteToRecordStream(this); + } + + protected virtual byte[] CreateVerifyData(bool isServer) + { + TlsContext context = Context; + string asciiLabel = isServer ? ExporterLabel.server_finished : ExporterLabel.client_finished; + byte[] sslSender = isServer ? TlsUtilities.SSL_SERVER : TlsUtilities.SSL_CLIENT; + byte[] hash = GetCurrentPrfHash(context, mRecordStream.HandshakeHash, sslSender); + return TlsUtilities.CalculateVerifyData(context, asciiLabel, hash); + } + + /** + * Closes this connection. + * + * @throws IOException If something goes wrong during closing. + */ + public virtual void Close() + { + HandleClose(true); + } + + protected internal virtual void Flush() + { + mRecordStream.Flush(); + } + + public virtual bool IsClosed + { + get { return mClosed; } + } + + protected virtual short ProcessMaxFragmentLengthExtension(IDictionary clientExtensions, IDictionary serverExtensions, + byte alertDescription) + { + short maxFragmentLength = TlsExtensionsUtilities.GetMaxFragmentLengthExtension(serverExtensions); + if (maxFragmentLength >= 0) + { + if (!MaxFragmentLength.IsValid((byte)maxFragmentLength) + || (!this.mResumedSession && maxFragmentLength != TlsExtensionsUtilities + .GetMaxFragmentLengthExtension(clientExtensions))) + { + throw new TlsFatalAlert(alertDescription); + } + } + return maxFragmentLength; + } + + protected virtual void RefuseRenegotiation() + { + /* + * RFC 5746 4.5 SSLv3 clients that refuse renegotiation SHOULD use a fatal + * handshake_failure alert. + */ + if (TlsUtilities.IsSsl(Context)) + throw new TlsFatalAlert(AlertDescription.handshake_failure); + + RaiseAlertWarning(AlertDescription.no_renegotiation, "Renegotiation not supported"); + } + + /** + * Make sure the InputStream 'buf' now empty. Fail otherwise. + * + * @param buf The InputStream to check. + * @throws IOException If 'buf' is not empty. + */ + protected internal static void AssertEmpty(MemoryStream buf) + { + if (buf.Position < buf.Length) + throw new TlsFatalAlert(AlertDescription.decode_error); + } + + protected internal static byte[] CreateRandomBlock(bool useGmtUnixTime, IRandomGenerator randomGenerator) + { + byte[] result = new byte[32]; + randomGenerator.NextBytes(result); + + if (useGmtUnixTime) + { + TlsUtilities.WriteGmtUnixTime(result, 0); + } + + return result; + } + + protected internal static byte[] CreateRenegotiationInfo(byte[] renegotiated_connection) + { + return TlsUtilities.EncodeOpaque8(renegotiated_connection); + } + + protected internal static void EstablishMasterSecret(TlsContext context, TlsKeyExchange keyExchange) + { + byte[] pre_master_secret = keyExchange.GeneratePremasterSecret(); + + try + { + context.SecurityParameters.masterSecret = TlsUtilities.CalculateMasterSecret(context, pre_master_secret); + } + finally + { + // TODO Is there a way to ensure the data is really overwritten? + /* + * RFC 2246 8.1. The pre_master_secret should be deleted from memory once the + * master_secret has been computed. + */ + if (pre_master_secret != null) + { + Arrays.Fill(pre_master_secret, (byte)0); + } + } + } + + /** + * 'sender' only relevant to SSLv3 + */ + protected internal static byte[] GetCurrentPrfHash(TlsContext context, TlsHandshakeHash handshakeHash, byte[] sslSender) + { + IDigest d = handshakeHash.ForkPrfHash(); + + if (sslSender != null && TlsUtilities.IsSsl(context)) + { + d.BlockUpdate(sslSender, 0, sslSender.Length); + } + + return DigestUtilities.DoFinal(d); + } + + protected internal static IDictionary ReadExtensions(MemoryStream input) + { + if (input.Position >= input.Length) + return null; + + byte[] extBytes = TlsUtilities.ReadOpaque16(input); + + AssertEmpty(input); + + MemoryStream buf = new MemoryStream(extBytes, false); + + // Integer -> byte[] + IDictionary extensions = Platform.CreateHashtable(); + + while (buf.Position < buf.Length) + { + int extension_type = TlsUtilities.ReadUint16(buf); + byte[] extension_data = TlsUtilities.ReadOpaque16(buf); + + /* + * RFC 3546 2.3 There MUST NOT be more than one extension of the same type. + */ + if (extensions.Contains(extension_type)) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + + extensions.Add(extension_type, extension_data); + } + + return extensions; + } + + protected internal static IList ReadSupplementalDataMessage(MemoryStream input) + { + byte[] supp_data = TlsUtilities.ReadOpaque24(input); + + AssertEmpty(input); + + MemoryStream buf = new MemoryStream(supp_data, false); + + IList supplementalData = Platform.CreateArrayList(); + + while (buf.Position < buf.Length) + { + int supp_data_type = TlsUtilities.ReadUint16(buf); + byte[] data = TlsUtilities.ReadOpaque16(buf); + + supplementalData.Add(new SupplementalDataEntry(supp_data_type, data)); + } + + return supplementalData; + } + + protected internal static void WriteExtensions(Stream output, IDictionary extensions) + { + MemoryStream buf = new MemoryStream(); + + /* + * NOTE: There are reports of servers that don't accept a zero-length extension as the last + * one, so we write out any zero-length ones first as a best-effort workaround. + */ + WriteSelectedExtensions(buf, extensions, true); + WriteSelectedExtensions(buf, extensions, false); + + byte[] extBytes = buf.ToArray(); + + TlsUtilities.WriteOpaque16(extBytes, output); + } + + protected internal static void WriteSelectedExtensions(Stream output, IDictionary extensions, bool selectEmpty) + { + foreach (int extension_type in extensions.Keys) + { + byte[] extension_data = (byte[])extensions[extension_type]; + if (selectEmpty == (extension_data.Length == 0)) + { + TlsUtilities.CheckUint16(extension_type); + TlsUtilities.WriteUint16(extension_type, output); + TlsUtilities.WriteOpaque16(extension_data, output); + } + } + } + + protected internal static void WriteSupplementalData(Stream output, IList supplementalData) + { + MemoryStream buf = new MemoryStream(); + + foreach (SupplementalDataEntry entry in supplementalData) + { + int supp_data_type = entry.DataType; + TlsUtilities.CheckUint16(supp_data_type); + TlsUtilities.WriteUint16(supp_data_type, buf); + TlsUtilities.WriteOpaque16(entry.Data, buf); + } + + byte[] supp_data = buf.ToArray(); + + TlsUtilities.WriteOpaque24(supp_data, output); + } + + protected internal static int GetPrfAlgorithm(TlsContext context, int ciphersuite) + { + bool isTLSv12 = TlsUtilities.IsTlsV12(context); + + switch (ciphersuite) + { + case CipherSuite.TLS_DH_anon_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DH_anon_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DH_anon_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256: + case CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DH_DSS_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256: + case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DH_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256: + case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CCM: + case CipherSuite.TLS_DHE_PSK_WITH_AES_128_GCM_SHA256: + case CipherSuite.DRAFT_TLS_DHE_PSK_WITH_AES_128_OCB: + case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CCM: + case CipherSuite.DRAFT_TLS_DHE_PSK_WITH_AES_256_OCB: + case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.DRAFT_TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM_8: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.DRAFT_TLS_DHE_RSA_WITH_AES_128_OCB: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM_8: + case CipherSuite.DRAFT_TLS_DHE_RSA_WITH_AES_256_OCB: + case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256: + case CipherSuite.DRAFT_TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.DRAFT_TLS_ECDHE_ECDSA_WITH_AES_128_OCB: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8: + case CipherSuite.DRAFT_TLS_ECDHE_ECDSA_WITH_AES_256_OCB: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.DRAFT_TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256: + case CipherSuite.DRAFT_TLS_ECDHE_PSK_WITH_AES_128_OCB: + case CipherSuite.DRAFT_TLS_ECDHE_PSK_WITH_AES_256_OCB: + case CipherSuite.DRAFT_TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.DRAFT_TLS_ECDHE_RSA_WITH_AES_128_OCB: + case CipherSuite.DRAFT_TLS_ECDHE_RSA_WITH_AES_256_OCB: + case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.DRAFT_TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256: + case CipherSuite.TLS_PSK_DHE_WITH_AES_128_CCM_8: + case CipherSuite.TLS_PSK_DHE_WITH_AES_256_CCM_8: + case CipherSuite.TLS_PSK_WITH_AES_128_CCM: + case CipherSuite.TLS_PSK_WITH_AES_128_CCM_8: + case CipherSuite.TLS_PSK_WITH_AES_128_GCM_SHA256: + case CipherSuite.DRAFT_TLS_PSK_WITH_AES_128_OCB: + case CipherSuite.TLS_PSK_WITH_AES_256_CCM: + case CipherSuite.TLS_PSK_WITH_AES_256_CCM_8: + case CipherSuite.DRAFT_TLS_PSK_WITH_AES_256_OCB: + case CipherSuite.TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.DRAFT_TLS_PSK_WITH_CHACHA20_POLY1305_SHA256: + case CipherSuite.TLS_RSA_PSK_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.DRAFT_TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256: + case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_RSA_WITH_AES_128_CCM: + case CipherSuite.TLS_RSA_WITH_AES_128_CCM_8: + case CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_RSA_WITH_AES_256_CCM: + case CipherSuite.TLS_RSA_WITH_AES_256_CCM_8: + case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256: + case CipherSuite.TLS_RSA_WITH_NULL_SHA256: + { + if (isTLSv12) + { + return PrfAlgorithm.tls_prf_sha256; + } + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + + case CipherSuite.TLS_DH_anon_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_DH_DSS_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_DH_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_DHE_DSS_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_DHE_PSK_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384: + case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384: + case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_PSK_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_RSA_PSK_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384: + { + if (isTLSv12) + { + return PrfAlgorithm.tls_prf_sha384; + } + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + + case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384: + case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA384: + case CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA384: + case CipherSuite.TLS_PSK_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384: + case CipherSuite.TLS_PSK_WITH_NULL_SHA384: + case CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384: + case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA384: + { + if (isTLSv12) + { + return PrfAlgorithm.tls_prf_sha384; + } + return PrfAlgorithm.tls_prf_legacy; + } + + default: + { + if (isTLSv12) + { + return PrfAlgorithm.tls_prf_sha256; + } + return PrfAlgorithm.tls_prf_legacy; + } + } + } + + internal class HandshakeMessage + : MemoryStream + { + internal HandshakeMessage(byte handshakeType) + : this(handshakeType, 60) + { + } + + internal HandshakeMessage(byte handshakeType, int length) + : base(length + 4) + { + TlsUtilities.WriteUint8(handshakeType, this); + // Reserve space for length + TlsUtilities.WriteUint24(0, this); + } + + internal void Write(byte[] data) + { + Write(data, 0, data.Length); + } + + internal void WriteToRecordStream(TlsProtocol protocol) + { + // Patch actual length back in + long length = Length - 4; + TlsUtilities.CheckUint24(length); + this.Position = 1; + TlsUtilities.WriteUint24((int)length, this); + +#if PORTABLE + byte[] buf = ToArray(); + int bufLen = buf.Length; +#else + byte[] buf = GetBuffer(); + int bufLen = (int)Length; +#endif + + protocol.WriteHandshakeMessage(buf, 0, bufLen); + Platform.Dispose(this); + } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsProtocolHandler.cs b/bc-sharp-crypto/src/crypto/tls/TlsProtocolHandler.cs new file mode 100644 index 0000000..6f22346 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsProtocolHandler.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections; +using System.IO; +using System.Text; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto.Agreement; +using Org.BouncyCastle.Crypto.Agreement.Srp; +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Encodings; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.IO; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Prng; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Date; + +namespace Org.BouncyCastle.Crypto.Tls +{ + [Obsolete("Use 'TlsClientProtocol' instead")] + public class TlsProtocolHandler + : TlsClientProtocol + { + public TlsProtocolHandler(Stream stream, SecureRandom secureRandom) + : base(stream, stream, secureRandom) + { + } + + /// Both streams can be the same object + public TlsProtocolHandler(Stream input, Stream output, SecureRandom secureRandom) + : base(input, output, secureRandom) + { + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsPskIdentity.cs b/bc-sharp-crypto/src/crypto/tls/TlsPskIdentity.cs new file mode 100644 index 0000000..119064e --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsPskIdentity.cs @@ -0,0 +1,15 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public interface TlsPskIdentity + { + void SkipIdentityHint(); + + void NotifyIdentityHint(byte[] psk_identity_hint); + + byte[] GetPskIdentity(); + + byte[] GetPsk(); + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsPskIdentityManager.cs b/bc-sharp-crypto/src/crypto/tls/TlsPskIdentityManager.cs new file mode 100644 index 0000000..a72c229 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsPskIdentityManager.cs @@ -0,0 +1,11 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public interface TlsPskIdentityManager + { + byte[] GetHint(); + + byte[] GetPsk(byte[] identity); + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsPskKeyExchange.cs b/bc-sharp-crypto/src/crypto/tls/TlsPskKeyExchange.cs new file mode 100644 index 0000000..0af7f7a --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsPskKeyExchange.cs @@ -0,0 +1,328 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /// (D)TLS PSK key exchange (RFC 4279). + public class TlsPskKeyExchange + : AbstractTlsKeyExchange + { + protected TlsPskIdentity mPskIdentity; + protected TlsPskIdentityManager mPskIdentityManager; + + protected DHParameters mDHParameters; + protected int[] mNamedCurves; + protected byte[] mClientECPointFormats, mServerECPointFormats; + + protected byte[] mPskIdentityHint = null; + protected byte[] mPsk = null; + + protected DHPrivateKeyParameters mDHAgreePrivateKey = null; + protected DHPublicKeyParameters mDHAgreePublicKey = null; + + protected ECPrivateKeyParameters mECAgreePrivateKey = null; + protected ECPublicKeyParameters mECAgreePublicKey = null; + + protected AsymmetricKeyParameter mServerPublicKey = null; + protected RsaKeyParameters mRsaServerPublicKey = null; + protected TlsEncryptionCredentials mServerCredentials = null; + protected byte[] mPremasterSecret; + + public TlsPskKeyExchange(int keyExchange, IList supportedSignatureAlgorithms, TlsPskIdentity pskIdentity, + TlsPskIdentityManager pskIdentityManager, DHParameters dhParameters, int[] namedCurves, + byte[] clientECPointFormats, byte[] serverECPointFormats) + : base(keyExchange, supportedSignatureAlgorithms) + { + switch (keyExchange) + { + case KeyExchangeAlgorithm.DHE_PSK: + case KeyExchangeAlgorithm.ECDHE_PSK: + case KeyExchangeAlgorithm.PSK: + case KeyExchangeAlgorithm.RSA_PSK: + break; + default: + throw new InvalidOperationException("unsupported key exchange algorithm"); + } + + this.mPskIdentity = pskIdentity; + this.mPskIdentityManager = pskIdentityManager; + this.mDHParameters = dhParameters; + this.mNamedCurves = namedCurves; + this.mClientECPointFormats = clientECPointFormats; + this.mServerECPointFormats = serverECPointFormats; + } + + public override void SkipServerCredentials() + { + if (mKeyExchange == KeyExchangeAlgorithm.RSA_PSK) + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + public override void ProcessServerCredentials(TlsCredentials serverCredentials) + { + if (!(serverCredentials is TlsEncryptionCredentials)) + throw new TlsFatalAlert(AlertDescription.internal_error); + + ProcessServerCertificate(serverCredentials.Certificate); + + this.mServerCredentials = (TlsEncryptionCredentials)serverCredentials; + } + + public override byte[] GenerateServerKeyExchange() + { + this.mPskIdentityHint = mPskIdentityManager.GetHint(); + + if (this.mPskIdentityHint == null && !RequiresServerKeyExchange) + return null; + + MemoryStream buf = new MemoryStream(); + + if (this.mPskIdentityHint == null) + { + TlsUtilities.WriteOpaque16(TlsUtilities.EmptyBytes, buf); + } + else + { + TlsUtilities.WriteOpaque16(this.mPskIdentityHint, buf); + } + + if (this.mKeyExchange == KeyExchangeAlgorithm.DHE_PSK) + { + if (this.mDHParameters == null) + throw new TlsFatalAlert(AlertDescription.internal_error); + + this.mDHAgreePrivateKey = TlsDHUtilities.GenerateEphemeralServerKeyExchange(mContext.SecureRandom, + this.mDHParameters, buf); + } + else if (this.mKeyExchange == KeyExchangeAlgorithm.ECDHE_PSK) + { + this.mECAgreePrivateKey = TlsEccUtilities.GenerateEphemeralServerKeyExchange(mContext.SecureRandom, + mNamedCurves, mClientECPointFormats, buf); + } + + return buf.ToArray(); + } + + public override void ProcessServerCertificate(Certificate serverCertificate) + { + if (mKeyExchange != KeyExchangeAlgorithm.RSA_PSK) + throw new TlsFatalAlert(AlertDescription.unexpected_message); + if (serverCertificate.IsEmpty) + throw new TlsFatalAlert(AlertDescription.bad_certificate); + + X509CertificateStructure x509Cert = serverCertificate.GetCertificateAt(0); + + SubjectPublicKeyInfo keyInfo = x509Cert.SubjectPublicKeyInfo; + try + { + this.mServerPublicKey = PublicKeyFactory.CreateKey(keyInfo); + } + catch (Exception e) + { + throw new TlsFatalAlert(AlertDescription.unsupported_certificate, e); + } + + // Sanity check the PublicKeyFactory + if (this.mServerPublicKey.IsPrivate) + throw new TlsFatalAlert(AlertDescription.internal_error); + + this.mRsaServerPublicKey = ValidateRsaPublicKey((RsaKeyParameters)this.mServerPublicKey); + + TlsUtilities.ValidateKeyUsage(x509Cert, KeyUsage.KeyEncipherment); + + base.ProcessServerCertificate(serverCertificate); + } + + public override bool RequiresServerKeyExchange + { + get + { + switch (mKeyExchange) + { + case KeyExchangeAlgorithm.DHE_PSK: + case KeyExchangeAlgorithm.ECDHE_PSK: + return true; + default: + return false; + } + } + } + + public override void ProcessServerKeyExchange(Stream input) + { + this.mPskIdentityHint = TlsUtilities.ReadOpaque16(input); + + if (this.mKeyExchange == KeyExchangeAlgorithm.DHE_PSK) + { + ServerDHParams serverDHParams = ServerDHParams.Parse(input); + + this.mDHAgreePublicKey = TlsDHUtilities.ValidateDHPublicKey(serverDHParams.PublicKey); + this.mDHParameters = mDHAgreePublicKey.Parameters; + } + else if (this.mKeyExchange == KeyExchangeAlgorithm.ECDHE_PSK) + { + ECDomainParameters ecParams = TlsEccUtilities.ReadECParameters(mNamedCurves, mClientECPointFormats, input); + + byte[] point = TlsUtilities.ReadOpaque8(input); + + this.mECAgreePublicKey = TlsEccUtilities.ValidateECPublicKey(TlsEccUtilities.DeserializeECPublicKey( + mClientECPointFormats, ecParams, point)); + } + } + + public override void ValidateCertificateRequest(CertificateRequest certificateRequest) + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + public override void ProcessClientCredentials(TlsCredentials clientCredentials) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + public override void GenerateClientKeyExchange(Stream output) + { + if (mPskIdentityHint == null) + { + mPskIdentity.SkipIdentityHint(); + } + else + { + mPskIdentity.NotifyIdentityHint(mPskIdentityHint); + } + + byte[] psk_identity = mPskIdentity.GetPskIdentity(); + if (psk_identity == null) + throw new TlsFatalAlert(AlertDescription.internal_error); + + this.mPsk = mPskIdentity.GetPsk(); + if (mPsk == null) + throw new TlsFatalAlert(AlertDescription.internal_error); + + TlsUtilities.WriteOpaque16(psk_identity, output); + + mContext.SecurityParameters.pskIdentity = psk_identity; + + if (this.mKeyExchange == KeyExchangeAlgorithm.DHE_PSK) + { + this.mDHAgreePrivateKey = TlsDHUtilities.GenerateEphemeralClientKeyExchange(mContext.SecureRandom, + mDHParameters, output); + } + else if (this.mKeyExchange == KeyExchangeAlgorithm.ECDHE_PSK) + { + this.mECAgreePrivateKey = TlsEccUtilities.GenerateEphemeralClientKeyExchange(mContext.SecureRandom, + mServerECPointFormats, mECAgreePublicKey.Parameters, output); + } + else if (this.mKeyExchange == KeyExchangeAlgorithm.RSA_PSK) + { + this.mPremasterSecret = TlsRsaUtilities.GenerateEncryptedPreMasterSecret(mContext, + this.mRsaServerPublicKey, output); + } + } + + public override void ProcessClientKeyExchange(Stream input) + { + byte[] psk_identity = TlsUtilities.ReadOpaque16(input); + + this.mPsk = mPskIdentityManager.GetPsk(psk_identity); + if (mPsk == null) + throw new TlsFatalAlert(AlertDescription.unknown_psk_identity); + + mContext.SecurityParameters.pskIdentity = psk_identity; + + if (this.mKeyExchange == KeyExchangeAlgorithm.DHE_PSK) + { + BigInteger Yc = TlsDHUtilities.ReadDHParameter(input); + + this.mDHAgreePublicKey = TlsDHUtilities.ValidateDHPublicKey(new DHPublicKeyParameters(Yc, mDHParameters)); + } + else if (this.mKeyExchange == KeyExchangeAlgorithm.ECDHE_PSK) + { + byte[] point = TlsUtilities.ReadOpaque8(input); + + ECDomainParameters curve_params = this.mECAgreePrivateKey.Parameters; + + this.mECAgreePublicKey = TlsEccUtilities.ValidateECPublicKey(TlsEccUtilities.DeserializeECPublicKey( + mServerECPointFormats, curve_params, point)); + } + else if (this.mKeyExchange == KeyExchangeAlgorithm.RSA_PSK) + { + byte[] encryptedPreMasterSecret; + if (TlsUtilities.IsSsl(mContext)) + { + // TODO Do any SSLv3 clients actually include the length? + encryptedPreMasterSecret = Streams.ReadAll(input); + } + else + { + encryptedPreMasterSecret = TlsUtilities.ReadOpaque16(input); + } + + this.mPremasterSecret = mServerCredentials.DecryptPreMasterSecret(encryptedPreMasterSecret); + } + } + + public override byte[] GeneratePremasterSecret() + { + byte[] other_secret = GenerateOtherSecret(mPsk.Length); + + MemoryStream buf = new MemoryStream(4 + other_secret.Length + mPsk.Length); + TlsUtilities.WriteOpaque16(other_secret, buf); + TlsUtilities.WriteOpaque16(mPsk, buf); + + Arrays.Fill(mPsk, (byte)0); + this.mPsk = null; + + return buf.ToArray(); + } + + protected virtual byte[] GenerateOtherSecret(int pskLength) + { + if (this.mKeyExchange == KeyExchangeAlgorithm.DHE_PSK) + { + if (mDHAgreePrivateKey != null) + { + return TlsDHUtilities.CalculateDHBasicAgreement(mDHAgreePublicKey, mDHAgreePrivateKey); + } + + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + if (this.mKeyExchange == KeyExchangeAlgorithm.ECDHE_PSK) + { + if (mECAgreePrivateKey != null) + { + return TlsEccUtilities.CalculateECDHBasicAgreement(mECAgreePublicKey, mECAgreePrivateKey); + } + + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + if (this.mKeyExchange == KeyExchangeAlgorithm.RSA_PSK) + { + return this.mPremasterSecret; + } + + return new byte[pskLength]; + } + + protected virtual RsaKeyParameters ValidateRsaPublicKey(RsaKeyParameters key) + { + // TODO What is the minimum bit length required? + // key.Modulus.BitLength; + + if (!key.Exponent.IsProbablePrime(2)) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + + return key; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsRsaKeyExchange.cs b/bc-sharp-crypto/src/crypto/tls/TlsRsaKeyExchange.cs new file mode 100644 index 0000000..b02d564 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsRsaKeyExchange.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto.Encodings; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /// (D)TLS and SSLv3 RSA key exchange. + public class TlsRsaKeyExchange + : AbstractTlsKeyExchange + { + protected AsymmetricKeyParameter mServerPublicKey = null; + + protected RsaKeyParameters mRsaServerPublicKey = null; + + protected TlsEncryptionCredentials mServerCredentials = null; + + protected byte[] mPremasterSecret; + + public TlsRsaKeyExchange(IList supportedSignatureAlgorithms) + : base(KeyExchangeAlgorithm.RSA, supportedSignatureAlgorithms) + { + } + + public override void SkipServerCredentials() + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + public override void ProcessServerCredentials(TlsCredentials serverCredentials) + { + if (!(serverCredentials is TlsEncryptionCredentials)) + throw new TlsFatalAlert(AlertDescription.internal_error); + + ProcessServerCertificate(serverCredentials.Certificate); + + this.mServerCredentials = (TlsEncryptionCredentials)serverCredentials; + } + + public override void ProcessServerCertificate(Certificate serverCertificate) + { + if (serverCertificate.IsEmpty) + throw new TlsFatalAlert(AlertDescription.bad_certificate); + + X509CertificateStructure x509Cert = serverCertificate.GetCertificateAt(0); + + SubjectPublicKeyInfo keyInfo = x509Cert.SubjectPublicKeyInfo; + try + { + this.mServerPublicKey = PublicKeyFactory.CreateKey(keyInfo); + } + catch (Exception e) + { + throw new TlsFatalAlert(AlertDescription.unsupported_certificate, e); + } + + // Sanity check the PublicKeyFactory + if (this.mServerPublicKey.IsPrivate) + throw new TlsFatalAlert(AlertDescription.internal_error); + + this.mRsaServerPublicKey = ValidateRsaPublicKey((RsaKeyParameters)this.mServerPublicKey); + + TlsUtilities.ValidateKeyUsage(x509Cert, KeyUsage.KeyEncipherment); + + base.ProcessServerCertificate(serverCertificate); + } + + public override void ValidateCertificateRequest(CertificateRequest certificateRequest) + { + byte[] types = certificateRequest.CertificateTypes; + for (int i = 0; i < types.Length; ++i) + { + switch (types[i]) + { + case ClientCertificateType.rsa_sign: + case ClientCertificateType.dss_sign: + case ClientCertificateType.ecdsa_sign: + break; + default: + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + } + } + + public override void ProcessClientCredentials(TlsCredentials clientCredentials) + { + if (!(clientCredentials is TlsSignerCredentials)) + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + public override void GenerateClientKeyExchange(Stream output) + { + this.mPremasterSecret = TlsRsaUtilities.GenerateEncryptedPreMasterSecret(mContext, mRsaServerPublicKey, output); + } + + public override void ProcessClientKeyExchange(Stream input) + { + byte[] encryptedPreMasterSecret; + if (TlsUtilities.IsSsl(mContext)) + { + // TODO Do any SSLv3 clients actually include the length? + encryptedPreMasterSecret = Streams.ReadAll(input); + } + else + { + encryptedPreMasterSecret = TlsUtilities.ReadOpaque16(input); + } + + this.mPremasterSecret = mServerCredentials.DecryptPreMasterSecret(encryptedPreMasterSecret); + } + + public override byte[] GeneratePremasterSecret() + { + if (this.mPremasterSecret == null) + throw new TlsFatalAlert(AlertDescription.internal_error); + + byte[] tmp = this.mPremasterSecret; + this.mPremasterSecret = null; + return tmp; + } + + protected virtual RsaKeyParameters ValidateRsaPublicKey(RsaKeyParameters key) + { + // TODO What is the minimum bit length required? + // key.Modulus.BitLength; + + if (!key.Exponent.IsProbablePrime(2)) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + + return key; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsRsaSigner.cs b/bc-sharp-crypto/src/crypto/tls/TlsRsaSigner.cs new file mode 100644 index 0000000..1614f50 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsRsaSigner.cs @@ -0,0 +1,102 @@ +using System; + +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Encodings; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Signers; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class TlsRsaSigner + : AbstractTlsSigner + { + public override byte[] GenerateRawSignature(SignatureAndHashAlgorithm algorithm, + AsymmetricKeyParameter privateKey, byte[] hash) + { + ISigner signer = MakeSigner(algorithm, true, true, + new ParametersWithRandom(privateKey, this.mContext.SecureRandom)); + signer.BlockUpdate(hash, 0, hash.Length); + return signer.GenerateSignature(); + } + + public override bool VerifyRawSignature(SignatureAndHashAlgorithm algorithm, byte[] sigBytes, + AsymmetricKeyParameter publicKey, byte[] hash) + { + ISigner signer = MakeSigner(algorithm, true, false, publicKey); + signer.BlockUpdate(hash, 0, hash.Length); + return signer.VerifySignature(sigBytes); + } + + public override ISigner CreateSigner(SignatureAndHashAlgorithm algorithm, AsymmetricKeyParameter privateKey) + { + return MakeSigner(algorithm, false, true, new ParametersWithRandom(privateKey, this.mContext.SecureRandom)); + } + + public override ISigner CreateVerifyer(SignatureAndHashAlgorithm algorithm, AsymmetricKeyParameter publicKey) + { + return MakeSigner(algorithm, false, false, publicKey); + } + + public override bool IsValidPublicKey(AsymmetricKeyParameter publicKey) + { + return publicKey is RsaKeyParameters && !publicKey.IsPrivate; + } + + protected virtual ISigner MakeSigner(SignatureAndHashAlgorithm algorithm, bool raw, bool forSigning, + ICipherParameters cp) + { + if ((algorithm != null) != TlsUtilities.IsTlsV12(mContext)) + throw new InvalidOperationException(); + if (algorithm != null && algorithm.Signature != SignatureAlgorithm.rsa) + throw new InvalidOperationException(); + + IDigest d; + if (raw) + { + d = new NullDigest(); + } + else if (algorithm == null) + { + d = new CombinedHash(); + } + else + { + d = TlsUtilities.CreateHash(algorithm.Hash); + } + + ISigner s; + if (algorithm != null) + { + /* + * RFC 5246 4.7. In RSA signing, the opaque vector contains the signature generated + * using the RSASSA-PKCS1-v1_5 signature scheme defined in [PKCS1]. + */ + s = new RsaDigestSigner(d, TlsUtilities.GetOidForHashAlgorithm(algorithm.Hash)); + } + else + { + /* + * RFC 5246 4.7. Note that earlier versions of TLS used a different RSA signature scheme + * that did not include a DigestInfo encoding. + */ + s = new GenericSigner(CreateRsaImpl(), d); + } + s.Init(forSigning, cp); + return s; + } + + protected virtual IAsymmetricBlockCipher CreateRsaImpl() + { + /* + * RFC 5246 7.4.7.1. Implementation note: It is now known that remote timing-based attacks + * on TLS are possible, at least when the client and server are on the same LAN. + * Accordingly, implementations that use static RSA keys MUST use RSA blinding or some other + * anti-timing technique, as described in [TIMING]. + */ + return new Pkcs1Encoding(new RsaBlindedEngine()); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsRsaUtilities.cs b/bc-sharp-crypto/src/crypto/tls/TlsRsaUtilities.cs new file mode 100644 index 0000000..0e42c17 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsRsaUtilities.cs @@ -0,0 +1,132 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Crypto.Encodings; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public abstract class TlsRsaUtilities + { + /// + public static byte[] GenerateEncryptedPreMasterSecret(TlsContext context, RsaKeyParameters rsaServerPublicKey, + Stream output) + { + /* + * Choose a PremasterSecret and send it encrypted to the server + */ + byte[] premasterSecret = new byte[48]; + context.SecureRandom.NextBytes(premasterSecret); + TlsUtilities.WriteVersion(context.ClientVersion, premasterSecret, 0); + + Pkcs1Encoding encoding = new Pkcs1Encoding(new RsaBlindedEngine()); + encoding.Init(true, new ParametersWithRandom(rsaServerPublicKey, context.SecureRandom)); + + try + { + byte[] encryptedPreMasterSecret = encoding.ProcessBlock(premasterSecret, 0, premasterSecret.Length); + + if (TlsUtilities.IsSsl(context)) + { + // TODO Do any SSLv3 servers actually expect the length? + output.Write(encryptedPreMasterSecret, 0, encryptedPreMasterSecret.Length); + } + else + { + TlsUtilities.WriteOpaque16(encryptedPreMasterSecret, output); + } + } + catch (InvalidCipherTextException e) + { + /* + * This should never happen, only during decryption. + */ + throw new TlsFatalAlert(AlertDescription.internal_error, e); + } + + return premasterSecret; + } + + public static byte[] SafeDecryptPreMasterSecret(TlsContext context, RsaKeyParameters rsaServerPrivateKey, + byte[] encryptedPreMasterSecret) + { + /* + * RFC 5246 7.4.7.1. + */ + ProtocolVersion clientVersion = context.ClientVersion; + + // TODO Provide as configuration option? + bool versionNumberCheckDisabled = false; + + /* + * Generate 48 random bytes we can use as a Pre-Master-Secret, if the + * PKCS1 padding check should fail. + */ + byte[] fallback = new byte[48]; + context.SecureRandom.NextBytes(fallback); + + byte[] M = Arrays.Clone(fallback); + try + { + Pkcs1Encoding encoding = new Pkcs1Encoding(new RsaBlindedEngine(), fallback); + encoding.Init(false, + new ParametersWithRandom(rsaServerPrivateKey, context.SecureRandom)); + + M = encoding.ProcessBlock(encryptedPreMasterSecret, 0, encryptedPreMasterSecret.Length); + } + catch (Exception) + { + /* + * This should never happen since the decryption should never throw an exception + * and return a random value instead. + * + * In any case, a TLS server MUST NOT generate an alert if processing an + * RSA-encrypted premaster secret message fails, or the version number is not as + * expected. Instead, it MUST continue the handshake with a randomly generated + * premaster secret. + */ + } + + /* + * If ClientHello.client_version is TLS 1.1 or higher, server implementations MUST + * check the version number [..]. + */ + if (versionNumberCheckDisabled && clientVersion.IsEqualOrEarlierVersionOf(ProtocolVersion.TLSv10)) + { + /* + * If the version number is TLS 1.0 or earlier, server + * implementations SHOULD check the version number, but MAY have a + * configuration option to disable the check. + * + * So there is nothing to do here. + */ + } + else + { + /* + * OK, we need to compare the version number in the decrypted Pre-Master-Secret with the + * clientVersion received during the handshake. If they don't match, we replace the + * decrypted Pre-Master-Secret with a random one. + */ + int correct = (clientVersion.MajorVersion ^ (M[0] & 0xff)) + | (clientVersion.MinorVersion ^ (M[1] & 0xff)); + correct |= correct >> 1; + correct |= correct >> 2; + correct |= correct >> 4; + int mask = ~((correct & 1) - 1); + + /* + * mask will be all bits set to 0xff if the version number differed. + */ + for (int i = 0; i < 48; i++) + { + M[i] = (byte)((M[i] & (~mask)) | (fallback[i] & mask)); + } + } + return M; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsServer.cs b/bc-sharp-crypto/src/crypto/tls/TlsServer.cs new file mode 100644 index 0000000..e791f93 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsServer.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public interface TlsServer + : TlsPeer + { + void Init(TlsServerContext context); + + /// + void NotifyClientVersion(ProtocolVersion clientVersion); + + /// + void NotifyFallback(bool isFallback); + + /// + void NotifyOfferedCipherSuites(int[] offeredCipherSuites); + + /// + void NotifyOfferedCompressionMethods(byte[] offeredCompressionMethods); + + /// A (Int32 -> byte[]). Will never be null. + /// + void ProcessClientExtensions(IDictionary clientExtensions); + + /// + ProtocolVersion GetServerVersion(); + + /// + int GetSelectedCipherSuite(); + + /// + byte GetSelectedCompressionMethod(); + + /// + /// Get the (optional) table of server extensions to be included in (extended) server hello. + /// + /// + /// A (Int32 -> byte[]). May be null. + /// + /// + IDictionary GetServerExtensions(); + + /// + /// A (). May be null. + /// + /// + IList GetServerSupplementalData(); + + /// + TlsCredentials GetCredentials(); + + /// + /// This method will be called (only) if the server included an extension of type + /// "status_request" with empty "extension_data" in the extended server hello. See RFC 3546 + /// 3.6. Certificate Status Request. If a non-null is returned, it + /// is sent to the client as a handshake message of type "certificate_status". + /// + /// A to be sent to the client (or null for none). + /// + CertificateStatus GetCertificateStatus(); + + /// + TlsKeyExchange GetKeyExchange(); + + /// + CertificateRequest GetCertificateRequest(); + + /// () + /// + void ProcessClientSupplementalData(IList clientSupplementalData); + + /// + /// Called by the protocol handler to report the client certificate, only if GetCertificateRequest + /// returned non-null. + /// + /// Note: this method is responsible for certificate verification and validation. + /// the effective client certificate (may be an empty chain). + /// + void NotifyClientCertificate(Certificate clientCertificate); + + /// RFC 5077 3.3. NewSessionTicket Handshake Message. + /// + /// This method will be called (only) if a NewSessionTicket extension was sent by the server. See + /// RFC 5077 4. Recommended Ticket Construction for recommended format and protection. + /// + /// The ticket) + /// + NewSessionTicket GetNewSessionTicket(); + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsServerContext.cs b/bc-sharp-crypto/src/crypto/tls/TlsServerContext.cs new file mode 100644 index 0000000..4021571 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsServerContext.cs @@ -0,0 +1,11 @@ +using System; + +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public interface TlsServerContext + : TlsContext + { + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsServerContextImpl.cs b/bc-sharp-crypto/src/crypto/tls/TlsServerContextImpl.cs new file mode 100644 index 0000000..d56566f --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsServerContextImpl.cs @@ -0,0 +1,20 @@ +using System; + +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Tls +{ + internal class TlsServerContextImpl + : AbstractTlsContext, TlsServerContext + { + internal TlsServerContextImpl(SecureRandom secureRandom, SecurityParameters securityParameters) + : base(secureRandom, securityParameters) + { + } + + public override bool IsServer + { + get { return true; } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsServerProtocol.cs b/bc-sharp-crypto/src/crypto/tls/TlsServerProtocol.cs new file mode 100644 index 0000000..c2bfbcb --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsServerProtocol.cs @@ -0,0 +1,833 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class TlsServerProtocol + : TlsProtocol + { + protected TlsServer mTlsServer = null; + internal TlsServerContextImpl mTlsServerContext = null; + + protected TlsKeyExchange mKeyExchange = null; + protected TlsCredentials mServerCredentials = null; + protected CertificateRequest mCertificateRequest = null; + + protected short mClientCertificateType = -1; + protected TlsHandshakeHash mPrepareFinishHash = null; + + /** + * Constructor for blocking mode. + * @param stream The bi-directional stream of data to/from the client + * @param output The stream of data to the client + * @param secureRandom Random number generator for various cryptographic functions + */ + public TlsServerProtocol(Stream stream, SecureRandom secureRandom) + : base(stream, secureRandom) + { + } + + /** + * Constructor for blocking mode. + * @param input The stream of data from the client + * @param output The stream of data to the client + * @param secureRandom Random number generator for various cryptographic functions + */ + public TlsServerProtocol(Stream input, Stream output, SecureRandom secureRandom) + : base(input, output, secureRandom) + { + } + + /** + * Constructor for non-blocking mode.
+ *
+ * When data is received, use {@link #offerInput(java.nio.ByteBuffer)} to + * provide the received ciphertext, then use + * {@link #readInput(byte[], int, int)} to read the corresponding cleartext.
+ *
+ * Similarly, when data needs to be sent, use + * {@link #offerOutput(byte[], int, int)} to provide the cleartext, then use + * {@link #readOutput(byte[], int, int)} to get the corresponding + * ciphertext. + * + * @param secureRandom + * Random number generator for various cryptographic functions + */ + public TlsServerProtocol(SecureRandom secureRandom) + : base(secureRandom) + { + } + + /** + * Receives a TLS handshake in the role of server.
+ *
+ * In blocking mode, this will not return until the handshake is complete. + * In non-blocking mode, use {@link TlsPeer#notifyHandshakeComplete()} to + * receive a callback when the handshake is complete. + * + * @param tlsServer + * @throws IOException If in blocking mode and handshake was not successful. + */ + public virtual void Accept(TlsServer tlsServer) + { + if (tlsServer == null) + throw new ArgumentNullException("tlsServer"); + if (this.mTlsServer != null) + throw new InvalidOperationException("'Accept' can only be called once"); + + this.mTlsServer = tlsServer; + + this.mSecurityParameters = new SecurityParameters(); + this.mSecurityParameters.entity = ConnectionEnd.server; + + this.mTlsServerContext = new TlsServerContextImpl(mSecureRandom, mSecurityParameters); + + this.mSecurityParameters.serverRandom = CreateRandomBlock(tlsServer.ShouldUseGmtUnixTime(), + mTlsServerContext.NonceRandomGenerator); + + this.mTlsServer.Init(mTlsServerContext); + this.mRecordStream.Init(mTlsServerContext); + + this.mRecordStream.SetRestrictReadVersion(false); + + BlockForHandshake(); + } + + protected override void CleanupHandshake() + { + base.CleanupHandshake(); + + this.mKeyExchange = null; + this.mServerCredentials = null; + this.mCertificateRequest = null; + this.mPrepareFinishHash = null; + } + + protected override TlsContext Context + { + get { return mTlsServerContext; } + } + + internal override AbstractTlsContext ContextAdmin + { + get { return mTlsServerContext; } + } + + protected override TlsPeer Peer + { + get { return mTlsServer; } + } + + protected override void HandleHandshakeMessage(byte type, MemoryStream buf) + { + switch (type) + { + case HandshakeType.client_hello: + { + switch (this.mConnectionState) + { + case CS_START: + { + ReceiveClientHelloMessage(buf); + this.mConnectionState = CS_CLIENT_HELLO; + + SendServerHelloMessage(); + this.mConnectionState = CS_SERVER_HELLO; + + mRecordStream.NotifyHelloComplete(); + + IList serverSupplementalData = mTlsServer.GetServerSupplementalData(); + if (serverSupplementalData != null) + { + SendSupplementalDataMessage(serverSupplementalData); + } + this.mConnectionState = CS_SERVER_SUPPLEMENTAL_DATA; + + this.mKeyExchange = mTlsServer.GetKeyExchange(); + this.mKeyExchange.Init(Context); + + this.mServerCredentials = mTlsServer.GetCredentials(); + + Certificate serverCertificate = null; + + if (this.mServerCredentials == null) + { + this.mKeyExchange.SkipServerCredentials(); + } + else + { + this.mKeyExchange.ProcessServerCredentials(this.mServerCredentials); + + serverCertificate = this.mServerCredentials.Certificate; + SendCertificateMessage(serverCertificate); + } + this.mConnectionState = CS_SERVER_CERTIFICATE; + + // TODO[RFC 3546] Check whether empty certificates is possible, allowed, or excludes CertificateStatus + if (serverCertificate == null || serverCertificate.IsEmpty) + { + this.mAllowCertificateStatus = false; + } + + if (this.mAllowCertificateStatus) + { + CertificateStatus certificateStatus = mTlsServer.GetCertificateStatus(); + if (certificateStatus != null) + { + SendCertificateStatusMessage(certificateStatus); + } + } + + this.mConnectionState = CS_CERTIFICATE_STATUS; + + byte[] serverKeyExchange = this.mKeyExchange.GenerateServerKeyExchange(); + if (serverKeyExchange != null) + { + SendServerKeyExchangeMessage(serverKeyExchange); + } + this.mConnectionState = CS_SERVER_KEY_EXCHANGE; + + if (this.mServerCredentials != null) + { + this.mCertificateRequest = mTlsServer.GetCertificateRequest(); + if (this.mCertificateRequest != null) + { + if (TlsUtilities.IsTlsV12(Context) != (mCertificateRequest.SupportedSignatureAlgorithms != null)) + throw new TlsFatalAlert(AlertDescription.internal_error); + + this.mKeyExchange.ValidateCertificateRequest(mCertificateRequest); + + SendCertificateRequestMessage(mCertificateRequest); + + TlsUtilities.TrackHashAlgorithms(this.mRecordStream.HandshakeHash, + this.mCertificateRequest.SupportedSignatureAlgorithms); + } + } + this.mConnectionState = CS_CERTIFICATE_REQUEST; + + SendServerHelloDoneMessage(); + this.mConnectionState = CS_SERVER_HELLO_DONE; + + this.mRecordStream.HandshakeHash.SealHashAlgorithms(); + + break; + } + case CS_END: + { + RefuseRenegotiation(); + break; + } + default: + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + break; + } + case HandshakeType.supplemental_data: + { + switch (this.mConnectionState) + { + case CS_SERVER_HELLO_DONE: + { + mTlsServer.ProcessClientSupplementalData(ReadSupplementalDataMessage(buf)); + this.mConnectionState = CS_CLIENT_SUPPLEMENTAL_DATA; + break; + } + default: + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + break; + } + case HandshakeType.certificate: + { + switch (this.mConnectionState) + { + case CS_SERVER_HELLO_DONE: + case CS_CLIENT_SUPPLEMENTAL_DATA: + { + if (mConnectionState < CS_CLIENT_SUPPLEMENTAL_DATA) + { + mTlsServer.ProcessClientSupplementalData(null); + } + + if (this.mCertificateRequest == null) + throw new TlsFatalAlert(AlertDescription.unexpected_message); + + ReceiveCertificateMessage(buf); + this.mConnectionState = CS_CLIENT_CERTIFICATE; + break; + } + default: + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + break; + } + case HandshakeType.client_key_exchange: + { + switch (this.mConnectionState) + { + case CS_SERVER_HELLO_DONE: + case CS_CLIENT_SUPPLEMENTAL_DATA: + case CS_CLIENT_CERTIFICATE: + { + if (mConnectionState < CS_CLIENT_SUPPLEMENTAL_DATA) + { + mTlsServer.ProcessClientSupplementalData(null); + } + + if (mConnectionState < CS_CLIENT_CERTIFICATE) + { + if (this.mCertificateRequest == null) + { + this.mKeyExchange.SkipClientCredentials(); + } + else + { + if (TlsUtilities.IsTlsV12(Context)) + { + /* + * RFC 5246 If no suitable certificate is available, the client MUST Send a + * certificate message containing no certificates. + * + * NOTE: In previous RFCs, this was SHOULD instead of MUST. + */ + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + else if (TlsUtilities.IsSsl(Context)) + { + if (this.mPeerCertificate == null) + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + else + { + NotifyClientCertificate(Certificate.EmptyChain); + } + } + } + + ReceiveClientKeyExchangeMessage(buf); + this.mConnectionState = CS_CLIENT_KEY_EXCHANGE; + break; + } + default: + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + break; + } + case HandshakeType.certificate_verify: + { + switch (this.mConnectionState) + { + case CS_CLIENT_KEY_EXCHANGE: + { + /* + * RFC 5246 7.4.8 This message is only sent following a client certificate that has + * signing capability (i.e., all certificates except those containing fixed + * Diffie-Hellman parameters). + */ + if (!ExpectCertificateVerifyMessage()) + throw new TlsFatalAlert(AlertDescription.unexpected_message); + + ReceiveCertificateVerifyMessage(buf); + this.mConnectionState = CS_CERTIFICATE_VERIFY; + + break; + } + default: + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + break; + } + case HandshakeType.finished: + { + switch (this.mConnectionState) + { + case CS_CLIENT_KEY_EXCHANGE: + case CS_CERTIFICATE_VERIFY: + { + if (mConnectionState < CS_CERTIFICATE_VERIFY && ExpectCertificateVerifyMessage()) + throw new TlsFatalAlert(AlertDescription.unexpected_message); + + ProcessFinishedMessage(buf); + this.mConnectionState = CS_CLIENT_FINISHED; + + if (this.mExpectSessionTicket) + { + SendNewSessionTicketMessage(mTlsServer.GetNewSessionTicket()); + SendChangeCipherSpecMessage(); + } + this.mConnectionState = CS_SERVER_SESSION_TICKET; + + SendFinishedMessage(); + this.mConnectionState = CS_SERVER_FINISHED; + + CompleteHandshake(); + break; + } + default: + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + break; + } + case HandshakeType.hello_request: + case HandshakeType.hello_verify_request: + case HandshakeType.server_hello: + case HandshakeType.server_key_exchange: + case HandshakeType.certificate_request: + case HandshakeType.server_hello_done: + case HandshakeType.session_ticket: + default: + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + } + + protected override void HandleAlertWarningMessage(byte alertDescription) + { + base.HandleAlertWarningMessage(alertDescription); + + switch (alertDescription) + { + case AlertDescription.no_certificate: + { + /* + * SSL 3.0 If the server has sent a certificate request Message, the client must send + * either the certificate message or a no_certificate alert. + */ + if (TlsUtilities.IsSsl(Context) && this.mCertificateRequest != null) + { + switch (this.mConnectionState) + { + case CS_SERVER_HELLO_DONE: + case CS_CLIENT_SUPPLEMENTAL_DATA: + { + if (mConnectionState < CS_CLIENT_SUPPLEMENTAL_DATA) + { + mTlsServer.ProcessClientSupplementalData(null); + } + + NotifyClientCertificate(Certificate.EmptyChain); + this.mConnectionState = CS_CLIENT_CERTIFICATE; + return; + } + } + } + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + } + } + + protected virtual void NotifyClientCertificate(Certificate clientCertificate) + { + if (mCertificateRequest == null) + throw new InvalidOperationException(); + if (mPeerCertificate != null) + throw new TlsFatalAlert(AlertDescription.unexpected_message); + + this.mPeerCertificate = clientCertificate; + + if (clientCertificate.IsEmpty) + { + this.mKeyExchange.SkipClientCredentials(); + } + else + { + + /* + * TODO RFC 5246 7.4.6. If the certificate_authorities list in the certificate request + * message was non-empty, one of the certificates in the certificate chain SHOULD be + * issued by one of the listed CAs. + */ + + this.mClientCertificateType = TlsUtilities.GetClientCertificateType(clientCertificate, + this.mServerCredentials.Certificate); + + this.mKeyExchange.ProcessClientCertificate(clientCertificate); + } + + /* + * RFC 5246 7.4.6. If the client does not Send any certificates, the server MAY at its + * discretion either continue the handshake without client authentication, or respond with a + * fatal handshake_failure alert. Also, if some aspect of the certificate chain was + * unacceptable (e.g., it was not signed by a known, trusted CA), the server MAY at its + * discretion either continue the handshake (considering the client unauthenticated) or Send + * a fatal alert. + */ + this.mTlsServer.NotifyClientCertificate(clientCertificate); + } + + protected virtual void ReceiveCertificateMessage(MemoryStream buf) + { + Certificate clientCertificate = Certificate.Parse(buf); + + AssertEmpty(buf); + + NotifyClientCertificate(clientCertificate); + } + + protected virtual void ReceiveCertificateVerifyMessage(MemoryStream buf) + { + if (mCertificateRequest == null) + throw new InvalidOperationException(); + + DigitallySigned clientCertificateVerify = DigitallySigned.Parse(Context, buf); + + AssertEmpty(buf); + + // Verify the CertificateVerify message contains a correct signature. + try + { + SignatureAndHashAlgorithm signatureAlgorithm = clientCertificateVerify.Algorithm; + + byte[] hash; + if (TlsUtilities.IsTlsV12(Context)) + { + TlsUtilities.VerifySupportedSignatureAlgorithm(mCertificateRequest.SupportedSignatureAlgorithms, signatureAlgorithm); + hash = mPrepareFinishHash.GetFinalHash(signatureAlgorithm.Hash); + } + else + { + hash = mSecurityParameters.SessionHash; + } + + X509CertificateStructure x509Cert = mPeerCertificate.GetCertificateAt(0); + SubjectPublicKeyInfo keyInfo = x509Cert.SubjectPublicKeyInfo; + AsymmetricKeyParameter publicKey = PublicKeyFactory.CreateKey(keyInfo); + + TlsSigner tlsSigner = TlsUtilities.CreateTlsSigner((byte)mClientCertificateType); + tlsSigner.Init(Context); + if (!tlsSigner.VerifyRawSignature(signatureAlgorithm, clientCertificateVerify.Signature, publicKey, hash)) + throw new TlsFatalAlert(AlertDescription.decrypt_error); + } + catch (TlsFatalAlert e) + { + throw e; + } + catch (Exception e) + { + throw new TlsFatalAlert(AlertDescription.decrypt_error, e); + } + } + + protected virtual void ReceiveClientHelloMessage(MemoryStream buf) + { + ProtocolVersion client_version = TlsUtilities.ReadVersion(buf); + mRecordStream.SetWriteVersion(client_version); + + if (client_version.IsDtls) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + + byte[] client_random = TlsUtilities.ReadFully(32, buf); + + /* + * TODO RFC 5077 3.4. If a ticket is presented by the client, the server MUST NOT attempt to + * use the Session ID in the ClientHello for stateful session resumption. + */ + byte[] sessionID = TlsUtilities.ReadOpaque8(buf); + if (sessionID.Length > 32) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + + /* + * TODO RFC 5246 7.4.1.2. If the session_id field is not empty (implying a session + * resumption request), this vector MUST include at least the cipher_suite from that + * session. + */ + int cipher_suites_length = TlsUtilities.ReadUint16(buf); + if (cipher_suites_length < 2 || (cipher_suites_length & 1) != 0) + throw new TlsFatalAlert(AlertDescription.decode_error); + + this.mOfferedCipherSuites = TlsUtilities.ReadUint16Array(cipher_suites_length / 2, buf); + + /* + * TODO RFC 5246 7.4.1.2. If the session_id field is not empty (implying a session + * resumption request), it MUST include the compression_method from that session. + */ + int compression_methods_length = TlsUtilities.ReadUint8(buf); + if (compression_methods_length < 1) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + + this.mOfferedCompressionMethods = TlsUtilities.ReadUint8Array(compression_methods_length, buf); + + /* + * TODO RFC 3546 2.3 If [...] the older session is resumed, then the server MUST ignore + * extensions appearing in the client hello, and Send a server hello containing no + * extensions. + */ + this.mClientExtensions = ReadExtensions(buf); + + /* + * TODO[session-hash] + * + * draft-ietf-tls-session-hash-04 4. Clients and servers SHOULD NOT accept handshakes + * that do not use the extended master secret [..]. (and see 5.2, 5.3) + */ + this.mSecurityParameters.extendedMasterSecret = TlsExtensionsUtilities.HasExtendedMasterSecretExtension(mClientExtensions); + + ContextAdmin.SetClientVersion(client_version); + + mTlsServer.NotifyClientVersion(client_version); + mTlsServer.NotifyFallback(Arrays.Contains(mOfferedCipherSuites, CipherSuite.TLS_FALLBACK_SCSV)); + + mSecurityParameters.clientRandom = client_random; + + mTlsServer.NotifyOfferedCipherSuites(mOfferedCipherSuites); + mTlsServer.NotifyOfferedCompressionMethods(mOfferedCompressionMethods); + + /* + * RFC 5746 3.6. Server Behavior: Initial Handshake + */ + { + /* + * RFC 5746 3.4. The client MUST include either an empty "renegotiation_info" extension, + * or the TLS_EMPTY_RENEGOTIATION_INFO_SCSV signaling cipher suite value in the + * ClientHello. Including both is NOT RECOMMENDED. + */ + + /* + * When a ClientHello is received, the server MUST check if it includes the + * TLS_EMPTY_RENEGOTIATION_INFO_SCSV SCSV. If it does, set the secure_renegotiation flag + * to TRUE. + */ + if (Arrays.Contains(mOfferedCipherSuites, CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV)) + { + this.mSecureRenegotiation = true; + } + + /* + * The server MUST check if the "renegotiation_info" extension is included in the + * ClientHello. + */ + byte[] renegExtData = TlsUtilities.GetExtensionData(mClientExtensions, ExtensionType.renegotiation_info); + if (renegExtData != null) + { + /* + * If the extension is present, set secure_renegotiation flag to TRUE. The + * server MUST then verify that the length of the "renegotiated_connection" + * field is zero, and if it is not, MUST abort the handshake. + */ + this.mSecureRenegotiation = true; + + if (!Arrays.ConstantTimeAreEqual(renegExtData, CreateRenegotiationInfo(TlsUtilities.EmptyBytes))) + throw new TlsFatalAlert(AlertDescription.handshake_failure); + } + } + + mTlsServer.NotifySecureRenegotiation(this.mSecureRenegotiation); + + if (mClientExtensions != null) + { + // NOTE: Validates the padding extension data, if present + TlsExtensionsUtilities.GetPaddingExtension(mClientExtensions); + + mTlsServer.ProcessClientExtensions(mClientExtensions); + } + } + + protected virtual void ReceiveClientKeyExchangeMessage(MemoryStream buf) + { + mKeyExchange.ProcessClientKeyExchange(buf); + + AssertEmpty(buf); + + if (TlsUtilities.IsSsl(Context)) + { + EstablishMasterSecret(Context, mKeyExchange); + } + + this.mPrepareFinishHash = mRecordStream.PrepareToFinish(); + this.mSecurityParameters.sessionHash = GetCurrentPrfHash(Context, mPrepareFinishHash, null); + + if (!TlsUtilities.IsSsl(Context)) + { + EstablishMasterSecret(Context, mKeyExchange); + } + + mRecordStream.SetPendingConnectionState(Peer.GetCompression(), Peer.GetCipher()); + + if (!mExpectSessionTicket) + { + SendChangeCipherSpecMessage(); + } + } + + protected virtual void SendCertificateRequestMessage(CertificateRequest certificateRequest) + { + HandshakeMessage message = new HandshakeMessage(HandshakeType.certificate_request); + + certificateRequest.Encode(message); + + message.WriteToRecordStream(this); + } + + protected virtual void SendCertificateStatusMessage(CertificateStatus certificateStatus) + { + HandshakeMessage message = new HandshakeMessage(HandshakeType.certificate_status); + + certificateStatus.Encode(message); + + message.WriteToRecordStream(this); + } + + protected virtual void SendNewSessionTicketMessage(NewSessionTicket newSessionTicket) + { + if (newSessionTicket == null) + throw new TlsFatalAlert(AlertDescription.internal_error); + + HandshakeMessage message = new HandshakeMessage(HandshakeType.session_ticket); + + newSessionTicket.Encode(message); + + message.WriteToRecordStream(this); + } + + protected virtual void SendServerHelloMessage() + { + HandshakeMessage message = new HandshakeMessage(HandshakeType.server_hello); + + { + ProtocolVersion server_version = mTlsServer.GetServerVersion(); + if (!server_version.IsEqualOrEarlierVersionOf(Context.ClientVersion)) + throw new TlsFatalAlert(AlertDescription.internal_error); + + mRecordStream.ReadVersion = server_version; + mRecordStream.SetWriteVersion(server_version); + mRecordStream.SetRestrictReadVersion(true); + ContextAdmin.SetServerVersion(server_version); + + TlsUtilities.WriteVersion(server_version, message); + } + + message.Write(this.mSecurityParameters.serverRandom); + + /* + * The server may return an empty session_id to indicate that the session will not be cached + * and therefore cannot be resumed. + */ + TlsUtilities.WriteOpaque8(TlsUtilities.EmptyBytes, message); + + int selectedCipherSuite = mTlsServer.GetSelectedCipherSuite(); + if (!Arrays.Contains(mOfferedCipherSuites, selectedCipherSuite) + || selectedCipherSuite == CipherSuite.TLS_NULL_WITH_NULL_NULL + || CipherSuite.IsScsv(selectedCipherSuite) + || !TlsUtilities.IsValidCipherSuiteForVersion(selectedCipherSuite, Context.ServerVersion)) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + mSecurityParameters.cipherSuite = selectedCipherSuite; + + byte selectedCompressionMethod = mTlsServer.GetSelectedCompressionMethod(); + if (!Arrays.Contains(mOfferedCompressionMethods, selectedCompressionMethod)) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + mSecurityParameters.compressionAlgorithm = selectedCompressionMethod; + + TlsUtilities.WriteUint16(selectedCipherSuite, message); + TlsUtilities.WriteUint8(selectedCompressionMethod, message); + + this.mServerExtensions = mTlsServer.GetServerExtensions(); + + /* + * RFC 5746 3.6. Server Behavior: Initial Handshake + */ + if (this.mSecureRenegotiation) + { + byte[] renegExtData = TlsUtilities.GetExtensionData(this.mServerExtensions, ExtensionType.renegotiation_info); + bool noRenegExt = (null == renegExtData); + + if (noRenegExt) + { + /* + * Note that Sending a "renegotiation_info" extension in response to a ClientHello + * containing only the SCSV is an explicit exception to the prohibition in RFC 5246, + * Section 7.4.1.4, on the server Sending unsolicited extensions and is only allowed + * because the client is signaling its willingness to receive the extension via the + * TLS_EMPTY_RENEGOTIATION_INFO_SCSV SCSV. + */ + + /* + * If the secure_renegotiation flag is set to TRUE, the server MUST include an empty + * "renegotiation_info" extension in the ServerHello message. + */ + this.mServerExtensions = TlsExtensionsUtilities.EnsureExtensionsInitialised(mServerExtensions); + this.mServerExtensions[ExtensionType.renegotiation_info] = CreateRenegotiationInfo(TlsUtilities.EmptyBytes); + } + } + + if (mSecurityParameters.extendedMasterSecret) + { + this.mServerExtensions = TlsExtensionsUtilities.EnsureExtensionsInitialised(mServerExtensions); + TlsExtensionsUtilities.AddExtendedMasterSecretExtension(mServerExtensions); + } + + /* + * TODO RFC 3546 2.3 If [...] the older session is resumed, then the server MUST ignore + * extensions appearing in the client hello, and Send a server hello containing no + * extensions. + */ + + if (this.mServerExtensions != null) + { + this.mSecurityParameters.encryptThenMac = TlsExtensionsUtilities.HasEncryptThenMacExtension(mServerExtensions); + + this.mSecurityParameters.maxFragmentLength = ProcessMaxFragmentLengthExtension(mClientExtensions, + mServerExtensions, AlertDescription.internal_error); + + this.mSecurityParameters.truncatedHMac = TlsExtensionsUtilities.HasTruncatedHMacExtension(mServerExtensions); + + /* + * TODO It's surprising that there's no provision to allow a 'fresh' CertificateStatus to be sent in + * a session resumption handshake. + */ + this.mAllowCertificateStatus = !mResumedSession + && TlsUtilities.HasExpectedEmptyExtensionData(mServerExtensions, ExtensionType.status_request, + AlertDescription.internal_error); + + this.mExpectSessionTicket = !mResumedSession + && TlsUtilities.HasExpectedEmptyExtensionData(mServerExtensions, ExtensionType.session_ticket, + AlertDescription.internal_error); + + WriteExtensions(message, this.mServerExtensions); + } + + mSecurityParameters.prfAlgorithm = GetPrfAlgorithm(Context, mSecurityParameters.CipherSuite); + + /* + * RFC 5246 7.4.9. Any cipher suite which does not explicitly specify verify_data_length has + * a verify_data_length equal to 12. This includes all existing cipher suites. + */ + mSecurityParameters.verifyDataLength = 12; + + ApplyMaxFragmentLengthExtension(); + + message.WriteToRecordStream(this); + } + + protected virtual void SendServerHelloDoneMessage() + { + byte[] message = new byte[4]; + TlsUtilities.WriteUint8(HandshakeType.server_hello_done, message, 0); + TlsUtilities.WriteUint24(0, message, 1); + + WriteHandshakeMessage(message, 0, message.Length); + } + + protected virtual void SendServerKeyExchangeMessage(byte[] serverKeyExchange) + { + HandshakeMessage message = new HandshakeMessage(HandshakeType.server_key_exchange, serverKeyExchange.Length); + + message.Write(serverKeyExchange); + + message.WriteToRecordStream(this); + } + + protected virtual bool ExpectCertificateVerifyMessage() + { + return mClientCertificateType >= 0 && TlsUtilities.HasSigningCapability((byte)mClientCertificateType); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsSession.cs b/bc-sharp-crypto/src/crypto/tls/TlsSession.cs new file mode 100644 index 0000000..6c22991 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsSession.cs @@ -0,0 +1,15 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public interface TlsSession + { + SessionParameters ExportSessionParameters(); + + byte[] SessionID { get; } + + void Invalidate(); + + bool IsResumable { get; } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsSessionImpl.cs b/bc-sharp-crypto/src/crypto/tls/TlsSessionImpl.cs new file mode 100644 index 0000000..8663926 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsSessionImpl.cs @@ -0,0 +1,54 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + internal class TlsSessionImpl + : TlsSession + { + internal readonly byte[] mSessionID; + internal SessionParameters mSessionParameters; + + internal TlsSessionImpl(byte[] sessionID, SessionParameters sessionParameters) + { + if (sessionID == null) + throw new ArgumentNullException("sessionID"); + if (sessionID.Length < 1 || sessionID.Length > 32) + throw new ArgumentException("must have length between 1 and 32 bytes, inclusive", "sessionID"); + + this.mSessionID = Arrays.Clone(sessionID); + this.mSessionParameters = sessionParameters; + } + + public virtual SessionParameters ExportSessionParameters() + { + lock (this) + { + return this.mSessionParameters == null ? null : this.mSessionParameters.Copy(); + } + } + + public virtual byte[] SessionID + { + get { lock (this) return mSessionID; } + } + + public virtual void Invalidate() + { + lock (this) + { + if (this.mSessionParameters != null) + { + this.mSessionParameters.Clear(); + this.mSessionParameters = null; + } + } + } + + public virtual bool IsResumable + { + get { lock (this) return this.mSessionParameters != null; } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsSigner.cs b/bc-sharp-crypto/src/crypto/tls/TlsSigner.cs new file mode 100644 index 0000000..ffdd4c9 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsSigner.cs @@ -0,0 +1,29 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public interface TlsSigner + { + void Init(TlsContext context); + + byte[] GenerateRawSignature(AsymmetricKeyParameter privateKey, byte[] md5AndSha1); + + byte[] GenerateRawSignature(SignatureAndHashAlgorithm algorithm, + AsymmetricKeyParameter privateKey, byte[] hash); + + bool VerifyRawSignature(byte[] sigBytes, AsymmetricKeyParameter publicKey, byte[] md5AndSha1); + + bool VerifyRawSignature(SignatureAndHashAlgorithm algorithm, byte[] sigBytes, + AsymmetricKeyParameter publicKey, byte[] hash); + + ISigner CreateSigner(AsymmetricKeyParameter privateKey); + + ISigner CreateSigner(SignatureAndHashAlgorithm algorithm, AsymmetricKeyParameter privateKey); + + ISigner CreateVerifyer(AsymmetricKeyParameter publicKey); + + ISigner CreateVerifyer(SignatureAndHashAlgorithm algorithm, AsymmetricKeyParameter publicKey); + + bool IsValidPublicKey(AsymmetricKeyParameter publicKey); + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsSignerCredentials.cs b/bc-sharp-crypto/src/crypto/tls/TlsSignerCredentials.cs new file mode 100644 index 0000000..92ed7cc --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsSignerCredentials.cs @@ -0,0 +1,14 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public interface TlsSignerCredentials + : TlsCredentials + { + /// + byte[] GenerateCertificateSignature(byte[] hash); + + SignatureAndHashAlgorithm SignatureAndHashAlgorithm { get; } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsSrpGroupVerifier.cs b/bc-sharp-crypto/src/crypto/tls/TlsSrpGroupVerifier.cs new file mode 100644 index 0000000..185f2f5 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsSrpGroupVerifier.cs @@ -0,0 +1,17 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public interface TlsSrpGroupVerifier + { + /** + * Check whether the given SRP group parameters are acceptable for use. + * + * @param group the {@link SRP6GroupParameters} to check + * @return true if (and only if) the specified group parameters are acceptable + */ + bool Accept(Srp6GroupParameters group); + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsSrpIdentityManager.cs b/bc-sharp-crypto/src/crypto/tls/TlsSrpIdentityManager.cs new file mode 100644 index 0000000..080a0dc --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsSrpIdentityManager.cs @@ -0,0 +1,21 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public interface TlsSrpIdentityManager + { + /** + * Lookup the {@link TlsSRPLoginParameters} corresponding to the specified identity. + * + * NOTE: To avoid "identity probing", unknown identities SHOULD be handled as recommended in RFC + * 5054 2.5.1.3. {@link SimulatedTlsSRPIdentityManager} is provided for this purpose. + * + * @param identity + * the SRP identity sent by the connecting client + * @return the {@link TlsSRPLoginParameters} for the specified identity, or else 'simulated' + * parameters if the identity is not recognized. A null value is also allowed, but not + * recommended. + */ + TlsSrpLoginParameters GetLoginParameters(byte[] identity); + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsSrpKeyExchange.cs b/bc-sharp-crypto/src/crypto/tls/TlsSrpKeyExchange.cs new file mode 100644 index 0000000..09fa723 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsSrpKeyExchange.cs @@ -0,0 +1,285 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto.Agreement.Srp; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /// (D)TLS SRP key exchange (RFC 5054). + public class TlsSrpKeyExchange + : AbstractTlsKeyExchange + { + protected static TlsSigner CreateSigner(int keyExchange) + { + switch (keyExchange) + { + case KeyExchangeAlgorithm.SRP: + return null; + case KeyExchangeAlgorithm.SRP_RSA: + return new TlsRsaSigner(); + case KeyExchangeAlgorithm.SRP_DSS: + return new TlsDssSigner(); + default: + throw new ArgumentException("unsupported key exchange algorithm"); + } + } + + protected TlsSigner mTlsSigner; + protected TlsSrpGroupVerifier mGroupVerifier; + protected byte[] mIdentity; + protected byte[] mPassword; + + protected AsymmetricKeyParameter mServerPublicKey = null; + + protected Srp6GroupParameters mSrpGroup = null; + protected Srp6Client mSrpClient = null; + protected Srp6Server mSrpServer = null; + protected BigInteger mSrpPeerCredentials = null; + protected BigInteger mSrpVerifier = null; + protected byte[] mSrpSalt = null; + + protected TlsSignerCredentials mServerCredentials = null; + + [Obsolete("Use constructor taking an explicit 'groupVerifier' argument")] + public TlsSrpKeyExchange(int keyExchange, IList supportedSignatureAlgorithms, byte[] identity, byte[] password) + : this(keyExchange, supportedSignatureAlgorithms, new DefaultTlsSrpGroupVerifier(), identity, password) + { + } + + public TlsSrpKeyExchange(int keyExchange, IList supportedSignatureAlgorithms, TlsSrpGroupVerifier groupVerifier, + byte[] identity, byte[] password) + : base(keyExchange, supportedSignatureAlgorithms) + { + this.mTlsSigner = CreateSigner(keyExchange); + this.mGroupVerifier = groupVerifier; + this.mIdentity = identity; + this.mPassword = password; + this.mSrpClient = new Srp6Client(); + } + + public TlsSrpKeyExchange(int keyExchange, IList supportedSignatureAlgorithms, byte[] identity, + TlsSrpLoginParameters loginParameters) + : base(keyExchange, supportedSignatureAlgorithms) + { + this.mTlsSigner = CreateSigner(keyExchange); + this.mIdentity = identity; + this.mSrpServer = new Srp6Server(); + this.mSrpGroup = loginParameters.Group; + this.mSrpVerifier = loginParameters.Verifier; + this.mSrpSalt = loginParameters.Salt; + } + + public override void Init(TlsContext context) + { + base.Init(context); + + if (this.mTlsSigner != null) + { + this.mTlsSigner.Init(context); + } + } + + public override void SkipServerCredentials() + { + if (mTlsSigner != null) + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + public override void ProcessServerCertificate(Certificate serverCertificate) + { + if (mTlsSigner == null) + throw new TlsFatalAlert(AlertDescription.unexpected_message); + if (serverCertificate.IsEmpty) + throw new TlsFatalAlert(AlertDescription.bad_certificate); + + X509CertificateStructure x509Cert = serverCertificate.GetCertificateAt(0); + + SubjectPublicKeyInfo keyInfo = x509Cert.SubjectPublicKeyInfo; + try + { + this.mServerPublicKey = PublicKeyFactory.CreateKey(keyInfo); + } + catch (Exception e) + { + throw new TlsFatalAlert(AlertDescription.unsupported_certificate, e); + } + + if (!mTlsSigner.IsValidPublicKey(this.mServerPublicKey)) + throw new TlsFatalAlert(AlertDescription.certificate_unknown); + + TlsUtilities.ValidateKeyUsage(x509Cert, KeyUsage.DigitalSignature); + + base.ProcessServerCertificate(serverCertificate); + } + + public override void ProcessServerCredentials(TlsCredentials serverCredentials) + { + if ((mKeyExchange == KeyExchangeAlgorithm.SRP) || !(serverCredentials is TlsSignerCredentials)) + throw new TlsFatalAlert(AlertDescription.internal_error); + + ProcessServerCertificate(serverCredentials.Certificate); + + this.mServerCredentials = (TlsSignerCredentials)serverCredentials; + } + + public override bool RequiresServerKeyExchange + { + get { return true; } + } + + public override byte[] GenerateServerKeyExchange() + { + mSrpServer.Init(mSrpGroup, mSrpVerifier, TlsUtilities.CreateHash(HashAlgorithm.sha1), mContext.SecureRandom); + BigInteger B = mSrpServer.GenerateServerCredentials(); + + ServerSrpParams srpParams = new ServerSrpParams(mSrpGroup.N, mSrpGroup.G, mSrpSalt, B); + + DigestInputBuffer buf = new DigestInputBuffer(); + + srpParams.Encode(buf); + + if (mServerCredentials != null) + { + /* + * RFC 5246 4.7. digitally-signed element needs SignatureAndHashAlgorithm from TLS 1.2 + */ + SignatureAndHashAlgorithm signatureAndHashAlgorithm = TlsUtilities.GetSignatureAndHashAlgorithm( + mContext, mServerCredentials); + + IDigest d = TlsUtilities.CreateHash(signatureAndHashAlgorithm); + + SecurityParameters securityParameters = mContext.SecurityParameters; + d.BlockUpdate(securityParameters.clientRandom, 0, securityParameters.clientRandom.Length); + d.BlockUpdate(securityParameters.serverRandom, 0, securityParameters.serverRandom.Length); + buf.UpdateDigest(d); + + byte[] hash = new byte[d.GetDigestSize()]; + d.DoFinal(hash, 0); + + byte[] signature = mServerCredentials.GenerateCertificateSignature(hash); + + DigitallySigned signed_params = new DigitallySigned(signatureAndHashAlgorithm, signature); + signed_params.Encode(buf); + } + + return buf.ToArray(); + } + + public override void ProcessServerKeyExchange(Stream input) + { + SecurityParameters securityParameters = mContext.SecurityParameters; + + SignerInputBuffer buf = null; + Stream teeIn = input; + + if (mTlsSigner != null) + { + buf = new SignerInputBuffer(); + teeIn = new TeeInputStream(input, buf); + } + + ServerSrpParams srpParams = ServerSrpParams.Parse(teeIn); + + if (buf != null) + { + DigitallySigned signed_params = ParseSignature(input); + + ISigner signer = InitVerifyer(mTlsSigner, signed_params.Algorithm, securityParameters); + buf.UpdateSigner(signer); + if (!signer.VerifySignature(signed_params.Signature)) + throw new TlsFatalAlert(AlertDescription.decrypt_error); + } + + this.mSrpGroup = new Srp6GroupParameters(srpParams.N, srpParams.G); + + if (!mGroupVerifier.Accept(mSrpGroup)) + throw new TlsFatalAlert(AlertDescription.insufficient_security); + + this.mSrpSalt = srpParams.S; + + /* + * RFC 5054 2.5.3: The client MUST abort the handshake with an "illegal_parameter" alert if + * B % N = 0. + */ + try + { + this.mSrpPeerCredentials = Srp6Utilities.ValidatePublicValue(mSrpGroup.N, srpParams.B); + } + catch (CryptoException e) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter, e); + } + + this.mSrpClient.Init(mSrpGroup, TlsUtilities.CreateHash(HashAlgorithm.sha1), mContext.SecureRandom); + } + + public override void ValidateCertificateRequest(CertificateRequest certificateRequest) + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + public override void ProcessClientCredentials(TlsCredentials clientCredentials) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + public override void GenerateClientKeyExchange(Stream output) + { + BigInteger A = mSrpClient.GenerateClientCredentials(mSrpSalt, mIdentity, mPassword); + TlsSrpUtilities.WriteSrpParameter(A, output); + + mContext.SecurityParameters.srpIdentity = Arrays.Clone(mIdentity); + } + + public override void ProcessClientKeyExchange(Stream input) + { + /* + * RFC 5054 2.5.4: The server MUST abort the handshake with an "illegal_parameter" alert if + * A % N = 0. + */ + try + { + this.mSrpPeerCredentials = Srp6Utilities.ValidatePublicValue(mSrpGroup.N, TlsSrpUtilities.ReadSrpParameter(input)); + } + catch (CryptoException e) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter, e); + } + + mContext.SecurityParameters.srpIdentity = Arrays.Clone(mIdentity); + } + + public override byte[] GeneratePremasterSecret() + { + try + { + BigInteger S = mSrpServer != null + ? mSrpServer.CalculateSecret(mSrpPeerCredentials) + : mSrpClient.CalculateSecret(mSrpPeerCredentials); + + // TODO Check if this needs to be a fixed size + return BigIntegers.AsUnsignedByteArray(S); + } + catch (CryptoException e) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter, e); + } + } + + protected virtual ISigner InitVerifyer(TlsSigner tlsSigner, SignatureAndHashAlgorithm algorithm, + SecurityParameters securityParameters) + { + ISigner signer = tlsSigner.CreateVerifyer(algorithm, this.mServerPublicKey); + signer.BlockUpdate(securityParameters.clientRandom, 0, securityParameters.clientRandom.Length); + signer.BlockUpdate(securityParameters.serverRandom, 0, securityParameters.serverRandom.Length); + return signer; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsSrpLoginParameters.cs b/bc-sharp-crypto/src/crypto/tls/TlsSrpLoginParameters.cs new file mode 100644 index 0000000..5ae4641 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsSrpLoginParameters.cs @@ -0,0 +1,36 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class TlsSrpLoginParameters + { + protected readonly Srp6GroupParameters mGroup; + protected readonly BigInteger mVerifier; + protected readonly byte[] mSalt; + + public TlsSrpLoginParameters(Srp6GroupParameters group, BigInteger verifier, byte[] salt) + { + this.mGroup = group; + this.mVerifier = verifier; + this.mSalt = salt; + } + + public virtual Srp6GroupParameters Group + { + get { return mGroup; } + } + + public virtual byte[] Salt + { + get { return mSalt; } + } + + public virtual BigInteger Verifier + { + get { return mVerifier; } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsSrpUtilities.cs b/bc-sharp-crypto/src/crypto/tls/TlsSrpUtilities.cs new file mode 100644 index 0000000..873189d --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsSrpUtilities.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public abstract class TlsSrpUtilities + { + public static void AddSrpExtension(IDictionary extensions, byte[] identity) + { + extensions[ExtensionType.srp] = CreateSrpExtension(identity); + } + + public static byte[] GetSrpExtension(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.srp); + return extensionData == null ? null : ReadSrpExtension(extensionData); + } + + public static byte[] CreateSrpExtension(byte[] identity) + { + if (identity == null) + throw new TlsFatalAlert(AlertDescription.internal_error); + + return TlsUtilities.EncodeOpaque8(identity); + } + + public static byte[] ReadSrpExtension(byte[] extensionData) + { + if (extensionData == null) + throw new ArgumentNullException("extensionData"); + + MemoryStream buf = new MemoryStream(extensionData, false); + byte[] identity = TlsUtilities.ReadOpaque8(buf); + + TlsProtocol.AssertEmpty(buf); + + return identity; + } + + public static BigInteger ReadSrpParameter(Stream input) + { + return new BigInteger(1, TlsUtilities.ReadOpaque16(input)); + } + + public static void WriteSrpParameter(BigInteger x, Stream output) + { + TlsUtilities.WriteOpaque16(BigIntegers.AsUnsignedByteArray(x), output); + } + + public static bool IsSrpCipherSuite(int cipherSuite) + { + switch (cipherSuite) + { + case CipherSuite.TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_WITH_AES_256_CBC_SHA: + return true; + + default: + return false; + } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsSrtpUtilities.cs b/bc-sharp-crypto/src/crypto/tls/TlsSrtpUtilities.cs new file mode 100644 index 0000000..626c0e3 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsSrtpUtilities.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /** + * RFC 5764 DTLS Extension to Establish Keys for SRTP. + */ + public abstract class TlsSRTPUtils + { + public static void AddUseSrtpExtension(IDictionary extensions, UseSrtpData useSRTPData) + { + extensions[ExtensionType.use_srtp] = CreateUseSrtpExtension(useSRTPData); + } + + public static UseSrtpData GetUseSrtpExtension(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.use_srtp); + return extensionData == null ? null : ReadUseSrtpExtension(extensionData); + } + + public static byte[] CreateUseSrtpExtension(UseSrtpData useSrtpData) + { + if (useSrtpData == null) + throw new ArgumentNullException("useSrtpData"); + + MemoryStream buf = new MemoryStream(); + + // SRTPProtectionProfiles + TlsUtilities.WriteUint16ArrayWithUint16Length(useSrtpData.ProtectionProfiles, buf); + + // srtp_mki + TlsUtilities.WriteOpaque8(useSrtpData.Mki, buf); + + return buf.ToArray(); + } + + public static UseSrtpData ReadUseSrtpExtension(byte[] extensionData) + { + if (extensionData == null) + throw new ArgumentNullException("extensionData"); + + MemoryStream buf = new MemoryStream(extensionData, true); + + // SRTPProtectionProfiles + int length = TlsUtilities.ReadUint16(buf); + if (length < 2 || (length & 1) != 0) + { + throw new TlsFatalAlert(AlertDescription.decode_error); + } + int[] protectionProfiles = TlsUtilities.ReadUint16Array(length / 2, buf); + + // srtp_mki + byte[] mki = TlsUtilities.ReadOpaque8(buf); + + TlsProtocol.AssertEmpty(buf); + + return new UseSrtpData(protectionProfiles, mki); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsStream.cs b/bc-sharp-crypto/src/crypto/tls/TlsStream.cs new file mode 100644 index 0000000..bfd80ed --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsStream.cs @@ -0,0 +1,97 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + internal class TlsStream + : Stream + { + private readonly TlsProtocol handler; + + internal TlsStream(TlsProtocol handler) + { + this.handler = handler; + } + + public override bool CanRead + { + get { return !handler.IsClosed; } + } + + public override bool CanSeek + { + get { return false; } + } + + public override bool CanWrite + { + get { return !handler.IsClosed; } + } + +#if PORTABLE + protected override void Dispose(bool disposing) + { + if (disposing) + { + handler.Close(); + } + base.Dispose(disposing); + } +#else + public override void Close() + { + handler.Close(); + base.Close(); + } +#endif + + public override void Flush() + { + handler.Flush(); + } + + public override long Length + { + get { throw new NotSupportedException(); } + } + + public override long Position + { + get { throw new NotSupportedException(); } + set { throw new NotSupportedException(); } + } + + public override int Read(byte[] buf, int off, int len) + { + return this.handler.ReadApplicationData(buf, off, len); + } + + public override int ReadByte() + { + byte[] buf = new byte[1]; + if (this.Read(buf, 0, 1) <= 0) + return -1; + return buf[0]; + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } + + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + public override void Write(byte[] buf, int off, int len) + { + this.handler.WriteData(buf, off, len); + } + + public override void WriteByte(byte b) + { + this.handler.WriteData(new byte[] { b }, 0, 1); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsStreamCipher.cs b/bc-sharp-crypto/src/crypto/tls/TlsStreamCipher.cs new file mode 100644 index 0000000..555442e --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsStreamCipher.cs @@ -0,0 +1,152 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Tls; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class TlsStreamCipher + : TlsCipher + { + protected readonly TlsContext context; + + protected readonly IStreamCipher encryptCipher; + protected readonly IStreamCipher decryptCipher; + + protected readonly TlsMac writeMac; + protected readonly TlsMac readMac; + + protected readonly bool usesNonce; + + /// + public TlsStreamCipher(TlsContext context, IStreamCipher clientWriteCipher, + IStreamCipher serverWriteCipher, IDigest clientWriteDigest, IDigest serverWriteDigest, + int cipherKeySize, bool usesNonce) + { + bool isServer = context.IsServer; + + this.context = context; + this.usesNonce = usesNonce; + + this.encryptCipher = clientWriteCipher; + this.decryptCipher = serverWriteCipher; + + int key_block_size = (2 * cipherKeySize) + clientWriteDigest.GetDigestSize() + + serverWriteDigest.GetDigestSize(); + + byte[] key_block = TlsUtilities.CalculateKeyBlock(context, key_block_size); + + int offset = 0; + + // Init MACs + TlsMac clientWriteMac = new TlsMac(context, clientWriteDigest, key_block, offset, + clientWriteDigest.GetDigestSize()); + offset += clientWriteDigest.GetDigestSize(); + TlsMac serverWriteMac = new TlsMac(context, serverWriteDigest, key_block, offset, + serverWriteDigest.GetDigestSize()); + offset += serverWriteDigest.GetDigestSize(); + + // Build keys + KeyParameter clientWriteKey = new KeyParameter(key_block, offset, cipherKeySize); + offset += cipherKeySize; + KeyParameter serverWriteKey = new KeyParameter(key_block, offset, cipherKeySize); + offset += cipherKeySize; + + if (offset != key_block_size) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + ICipherParameters encryptParams, decryptParams; + if (isServer) + { + this.writeMac = serverWriteMac; + this.readMac = clientWriteMac; + this.encryptCipher = serverWriteCipher; + this.decryptCipher = clientWriteCipher; + encryptParams = serverWriteKey; + decryptParams = clientWriteKey; + } + else + { + this.writeMac = clientWriteMac; + this.readMac = serverWriteMac; + this.encryptCipher = clientWriteCipher; + this.decryptCipher = serverWriteCipher; + encryptParams = clientWriteKey; + decryptParams = serverWriteKey; + } + + if (usesNonce) + { + byte[] dummyNonce = new byte[8]; + encryptParams = new ParametersWithIV(encryptParams, dummyNonce); + decryptParams = new ParametersWithIV(decryptParams, dummyNonce); + } + + this.encryptCipher.Init(true, encryptParams); + this.decryptCipher.Init(false, decryptParams); + } + + public virtual int GetPlaintextLimit(int ciphertextLimit) + { + return ciphertextLimit - writeMac.Size; + } + + public virtual byte[] EncodePlaintext(long seqNo, byte type, byte[] plaintext, int offset, int len) + { + if (usesNonce) + { + UpdateIV(encryptCipher, true, seqNo); + } + + byte[] outBuf = new byte[len + writeMac.Size]; + + encryptCipher.ProcessBytes(plaintext, offset, len, outBuf, 0); + + byte[] mac = writeMac.CalculateMac(seqNo, type, plaintext, offset, len); + encryptCipher.ProcessBytes(mac, 0, mac.Length, outBuf, len); + + return outBuf; + } + + /// + public virtual byte[] DecodeCiphertext(long seqNo, byte type, byte[] ciphertext, int offset, int len) + { + if (usesNonce) + { + UpdateIV(decryptCipher, false, seqNo); + } + + int macSize = readMac.Size; + if (len < macSize) + throw new TlsFatalAlert(AlertDescription.decode_error); + + int plaintextLength = len - macSize; + + byte[] deciphered = new byte[len]; + decryptCipher.ProcessBytes(ciphertext, offset, len, deciphered, 0); + CheckMac(seqNo, type, deciphered, plaintextLength, len, deciphered, 0, plaintextLength); + return Arrays.CopyOfRange(deciphered, 0, plaintextLength); + } + + /// + protected virtual void CheckMac(long seqNo, byte type, byte[] recBuf, int recStart, int recEnd, byte[] calcBuf, int calcOff, int calcLen) + { + byte[] receivedMac = Arrays.CopyOfRange(recBuf, recStart, recEnd); + byte[] computedMac = readMac.CalculateMac(seqNo, type, calcBuf, calcOff, calcLen); + + if (!Arrays.ConstantTimeAreEqual(receivedMac, computedMac)) + throw new TlsFatalAlert(AlertDescription.bad_record_mac); + } + + protected virtual void UpdateIV(IStreamCipher cipher, bool forEncryption, long seqNo) + { + byte[] nonce = new byte[8]; + TlsUtilities.WriteUint64(seqNo, nonce, 0); + cipher.Init(forEncryption, new ParametersWithIV(null, nonce)); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/TlsUtilities.cs b/bc-sharp-crypto/src/crypto/tls/TlsUtilities.cs new file mode 100644 index 0000000..48eb9d3 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/TlsUtilities.cs @@ -0,0 +1,2398 @@ +using System; +using System.Collections; +using System.IO; +using System.Text; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Nist; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Macs; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Date; +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /// Some helper functions for MicroTLS. + public abstract class TlsUtilities + { + public static readonly byte[] EmptyBytes = new byte[0]; + public static readonly short[] EmptyShorts = new short[0]; + public static readonly int[] EmptyInts = new int[0]; + public static readonly long[] EmptyLongs = new long[0]; + + public static void CheckUint8(int i) + { + if (!IsValidUint8(i)) + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + public static void CheckUint8(long i) + { + if (!IsValidUint8(i)) + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + public static void CheckUint16(int i) + { + if (!IsValidUint16(i)) + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + public static void CheckUint16(long i) + { + if (!IsValidUint16(i)) + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + public static void CheckUint24(int i) + { + if (!IsValidUint24(i)) + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + public static void CheckUint24(long i) + { + if (!IsValidUint24(i)) + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + public static void CheckUint32(long i) + { + if (!IsValidUint32(i)) + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + public static void CheckUint48(long i) + { + if (!IsValidUint48(i)) + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + public static void CheckUint64(long i) + { + if (!IsValidUint64(i)) + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + public static bool IsValidUint8(int i) + { + return (i & 0xFF) == i; + } + + public static bool IsValidUint8(long i) + { + return (i & 0xFFL) == i; + } + + public static bool IsValidUint16(int i) + { + return (i & 0xFFFF) == i; + } + + public static bool IsValidUint16(long i) + { + return (i & 0xFFFFL) == i; + } + + public static bool IsValidUint24(int i) + { + return (i & 0xFFFFFF) == i; + } + + public static bool IsValidUint24(long i) + { + return (i & 0xFFFFFFL) == i; + } + + public static bool IsValidUint32(long i) + { + return (i & 0xFFFFFFFFL) == i; + } + + public static bool IsValidUint48(long i) + { + return (i & 0xFFFFFFFFFFFFL) == i; + } + + public static bool IsValidUint64(long i) + { + return true; + } + + public static bool IsSsl(TlsContext context) + { + return context.ServerVersion.IsSsl; + } + + public static bool IsTlsV11(ProtocolVersion version) + { + return ProtocolVersion.TLSv11.IsEqualOrEarlierVersionOf(version.GetEquivalentTLSVersion()); + } + + public static bool IsTlsV11(TlsContext context) + { + return IsTlsV11(context.ServerVersion); + } + + public static bool IsTlsV12(ProtocolVersion version) + { + return ProtocolVersion.TLSv12.IsEqualOrEarlierVersionOf(version.GetEquivalentTLSVersion()); + } + + public static bool IsTlsV12(TlsContext context) + { + return IsTlsV12(context.ServerVersion); + } + + public static void WriteUint8(byte i, Stream output) + { + output.WriteByte(i); + } + + public static void WriteUint8(byte i, byte[] buf, int offset) + { + buf[offset] = i; + } + + public static void WriteUint16(int i, Stream output) + { + output.WriteByte((byte)(i >> 8)); + output.WriteByte((byte)i); + } + + public static void WriteUint16(int i, byte[] buf, int offset) + { + buf[offset] = (byte)(i >> 8); + buf[offset + 1] = (byte)i; + } + + public static void WriteUint24(int i, Stream output) + { + output.WriteByte((byte)(i >> 16)); + output.WriteByte((byte)(i >> 8)); + output.WriteByte((byte)i); + } + + public static void WriteUint24(int i, byte[] buf, int offset) + { + buf[offset] = (byte)(i >> 16); + buf[offset + 1] = (byte)(i >> 8); + buf[offset + 2] = (byte)i; + } + + public static void WriteUint32(long i, Stream output) + { + output.WriteByte((byte)(i >> 24)); + output.WriteByte((byte)(i >> 16)); + output.WriteByte((byte)(i >> 8)); + output.WriteByte((byte)i); + } + + public 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; + } + + public static void WriteUint48(long i, Stream output) + { + output.WriteByte((byte)(i >> 40)); + output.WriteByte((byte)(i >> 32)); + output.WriteByte((byte)(i >> 24)); + output.WriteByte((byte)(i >> 16)); + output.WriteByte((byte)(i >> 8)); + output.WriteByte((byte)i); + } + + public static void WriteUint48(long i, byte[] buf, int offset) + { + buf[offset] = (byte)(i >> 40); + buf[offset + 1] = (byte)(i >> 32); + buf[offset + 2] = (byte)(i >> 24); + buf[offset + 3] = (byte)(i >> 16); + buf[offset + 4] = (byte)(i >> 8); + buf[offset + 5] = (byte)i; + } + + public static void WriteUint64(long i, Stream output) + { + output.WriteByte((byte)(i >> 56)); + output.WriteByte((byte)(i >> 48)); + output.WriteByte((byte)(i >> 40)); + output.WriteByte((byte)(i >> 32)); + output.WriteByte((byte)(i >> 24)); + output.WriteByte((byte)(i >> 16)); + output.WriteByte((byte)(i >> 8)); + output.WriteByte((byte)i); + } + + public 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; + } + + public static void WriteOpaque8(byte[] buf, Stream output) + { + WriteUint8((byte)buf.Length, output); + output.Write(buf, 0, buf.Length); + } + + public static void WriteOpaque16(byte[] buf, Stream output) + { + WriteUint16(buf.Length, output); + output.Write(buf, 0, buf.Length); + } + + public static void WriteOpaque24(byte[] buf, Stream output) + { + WriteUint24(buf.Length, output); + output.Write(buf, 0, buf.Length); + } + + public static void WriteUint8Array(byte[] uints, Stream output) + { + output.Write(uints, 0, uints.Length); + } + + public static void WriteUint8Array(byte[] uints, byte[] buf, int offset) + { + for (int i = 0; i < uints.Length; ++i) + { + WriteUint8(uints[i], buf, offset); + ++offset; + } + } + + public static void WriteUint8ArrayWithUint8Length(byte[] uints, Stream output) + { + CheckUint8(uints.Length); + WriteUint8((byte)uints.Length, output); + WriteUint8Array(uints, output); + } + + public static void WriteUint8ArrayWithUint8Length(byte[] uints, byte[] buf, int offset) + { + CheckUint8(uints.Length); + WriteUint8((byte)uints.Length, buf, offset); + WriteUint8Array(uints, buf, offset + 1); + } + + public static void WriteUint16Array(int[] uints, Stream output) + { + for (int i = 0; i < uints.Length; ++i) + { + WriteUint16(uints[i], output); + } + } + + public static void WriteUint16Array(int[] uints, byte[] buf, int offset) + { + for (int i = 0; i < uints.Length; ++i) + { + WriteUint16(uints[i], buf, offset); + offset += 2; + } + } + + public static void WriteUint16ArrayWithUint16Length(int[] uints, Stream output) + { + int length = 2 * uints.Length; + CheckUint16(length); + WriteUint16(length, output); + WriteUint16Array(uints, output); + } + + public static void WriteUint16ArrayWithUint16Length(int[] uints, byte[] buf, int offset) + { + int length = 2 * uints.Length; + CheckUint16(length); + WriteUint16(length, buf, offset); + WriteUint16Array(uints, buf, offset + 2); + } + + public static byte DecodeUint8(byte[] buf) + { + if (buf == null) + throw new ArgumentNullException("buf"); + if (buf.Length != 1) + throw new TlsFatalAlert(AlertDescription.decode_error); + return ReadUint8(buf, 0); + } + + public static byte[] DecodeUint8ArrayWithUint8Length(byte[] buf) + { + if (buf == null) + throw new ArgumentNullException("buf"); + + int count = ReadUint8(buf, 0); + if (buf.Length != (count + 1)) + throw new TlsFatalAlert(AlertDescription.decode_error); + + byte[] uints = new byte[count]; + for (int i = 0; i < count; ++i) + { + uints[i] = ReadUint8(buf, i + 1); + } + return uints; + } + + public static byte[] EncodeOpaque8(byte[] buf) + { + CheckUint8(buf.Length); + return Arrays.Prepend(buf, (byte)buf.Length); + } + + public static byte[] EncodeUint8(byte val) + { + CheckUint8(val); + + byte[] extensionData = new byte[1]; + WriteUint8(val, extensionData, 0); + return extensionData; + } + + public static byte[] EncodeUint8ArrayWithUint8Length(byte[] uints) + { + byte[] result = new byte[1 + uints.Length]; + WriteUint8ArrayWithUint8Length(uints, result, 0); + return result; + } + + public static byte[] EncodeUint16ArrayWithUint16Length(int[] uints) + { + int length = 2 * uints.Length; + byte[] result = new byte[2 + length]; + WriteUint16ArrayWithUint16Length(uints, result, 0); + return result; + } + + public static byte ReadUint8(Stream input) + { + int i = input.ReadByte(); + if (i < 0) + throw new EndOfStreamException(); + return (byte)i; + } + + public static byte ReadUint8(byte[] buf, int offset) + { + return buf[offset]; + } + + public static int ReadUint16(Stream input) + { + int i1 = input.ReadByte(); + int i2 = input.ReadByte(); + if (i2 < 0) + throw new EndOfStreamException(); + return (i1 << 8) | i2; + } + + public static int ReadUint16(byte[] buf, int offset) + { + uint n = (uint)buf[offset] << 8; + n |= (uint)buf[++offset]; + return (int)n; + } + + public static int ReadUint24(Stream input) + { + int i1 = input.ReadByte(); + int i2 = input.ReadByte(); + int i3 = input.ReadByte(); + if (i3 < 0) + throw new EndOfStreamException(); + return (i1 << 16) | (i2 << 8) | i3; + } + + public static int ReadUint24(byte[] buf, int offset) + { + uint n = (uint)buf[offset] << 16; + n |= (uint)buf[++offset] << 8; + n |= (uint)buf[++offset]; + return (int)n; + } + + public static long ReadUint32(Stream input) + { + int i1 = input.ReadByte(); + int i2 = input.ReadByte(); + int i3 = input.ReadByte(); + int i4 = input.ReadByte(); + if (i4 < 0) + throw new EndOfStreamException(); + return (long)(uint)((i1 << 24) | (i2 << 16) | (i3 << 8) | i4); + } + + public static long ReadUint32(byte[] buf, int offset) + { + uint n = (uint)buf[offset] << 24; + n |= (uint)buf[++offset] << 16; + n |= (uint)buf[++offset] << 8; + n |= (uint)buf[++offset]; + return (long)n; + } + + public static long ReadUint48(Stream input) + { + int hi = ReadUint24(input); + int lo = ReadUint24(input); + return ((long)(hi & 0xffffffffL) << 24) | (long)(lo & 0xffffffffL); + } + + public static long ReadUint48(byte[] buf, int offset) + { + int hi = ReadUint24(buf, offset); + int lo = ReadUint24(buf, offset + 3); + return ((long)(hi & 0xffffffffL) << 24) | (long)(lo & 0xffffffffL); + } + + public static byte[] ReadAllOrNothing(int length, Stream input) + { + if (length < 1) + return EmptyBytes; + byte[] buf = new byte[length]; + int read = Streams.ReadFully(input, buf); + if (read == 0) + return null; + if (read != length) + throw new EndOfStreamException(); + return buf; + } + + public static byte[] ReadFully(int length, Stream input) + { + if (length < 1) + return EmptyBytes; + byte[] buf = new byte[length]; + if (length != Streams.ReadFully(input, buf)) + throw new EndOfStreamException(); + return buf; + } + + public static void ReadFully(byte[] buf, Stream input) + { + if (Streams.ReadFully(input, buf, 0, buf.Length) < buf.Length) + throw new EndOfStreamException(); + } + + public static byte[] ReadOpaque8(Stream input) + { + byte length = ReadUint8(input); + byte[] bytes = new byte[length]; + ReadFully(bytes, input); + return bytes; + } + + public static byte[] ReadOpaque16(Stream input) + { + int length = ReadUint16(input); + byte[] bytes = new byte[length]; + ReadFully(bytes, input); + return bytes; + } + + public static byte[] ReadOpaque24(Stream input) + { + int length = ReadUint24(input); + return ReadFully(length, input); + } + + public static byte[] ReadUint8Array(int count, Stream input) + { + byte[] uints = new byte[count]; + for (int i = 0; i < count; ++i) + { + uints[i] = ReadUint8(input); + } + return uints; + } + + public static int[] ReadUint16Array(int count, Stream input) + { + int[] uints = new int[count]; + for (int i = 0; i < count; ++i) + { + uints[i] = ReadUint16(input); + } + return uints; + } + + public static ProtocolVersion ReadVersion(byte[] buf, int offset) + { + return ProtocolVersion.Get(buf[offset], buf[offset + 1]); + } + + public static ProtocolVersion ReadVersion(Stream input) + { + int i1 = input.ReadByte(); + int i2 = input.ReadByte(); + if (i2 < 0) + throw new EndOfStreamException(); + return ProtocolVersion.Get(i1, i2); + } + + public static int ReadVersionRaw(byte[] buf, int offset) + { + return (buf[offset] << 8) | buf[offset + 1]; + } + + public static int ReadVersionRaw(Stream input) + { + int i1 = input.ReadByte(); + int i2 = input.ReadByte(); + if (i2 < 0) + throw new EndOfStreamException(); + return (i1 << 8) | i2; + } + + public static Asn1Object ReadAsn1Object(byte[] encoding) + { + MemoryStream input = new MemoryStream(encoding, false); + Asn1InputStream asn1 = new Asn1InputStream(input, encoding.Length); + Asn1Object result = asn1.ReadObject(); + if (null == result) + throw new TlsFatalAlert(AlertDescription.decode_error); + if (input.Position != input.Length) + throw new TlsFatalAlert(AlertDescription.decode_error); + return result; + } + + public static Asn1Object ReadDerObject(byte[] encoding) + { + /* + * NOTE: The current ASN.1 parsing code can't enforce DER-only parsing, but since DER is + * canonical, we can check it by re-encoding the result and comparing to the original. + */ + Asn1Object result = ReadAsn1Object(encoding); + byte[] check = result.GetEncoded(Asn1Encodable.Der); + if (!Arrays.AreEqual(check, encoding)) + throw new TlsFatalAlert(AlertDescription.decode_error); + return result; + } + + public static void WriteGmtUnixTime(byte[] buf, int offset) + { + int t = (int)(DateTimeUtilities.CurrentUnixMs() / 1000L); + buf[offset] = (byte)(t >> 24); + buf[offset + 1] = (byte)(t >> 16); + buf[offset + 2] = (byte)(t >> 8); + buf[offset + 3] = (byte)t; + } + + public static void WriteVersion(ProtocolVersion version, Stream output) + { + output.WriteByte((byte)version.MajorVersion); + output.WriteByte((byte)version.MinorVersion); + } + + public static void WriteVersion(ProtocolVersion version, byte[] buf, int offset) + { + buf[offset] = (byte)version.MajorVersion; + buf[offset + 1] = (byte)version.MinorVersion; + } + + public static IList GetAllSignatureAlgorithms() + { + IList v = Platform.CreateArrayList(4); + v.Add(SignatureAlgorithm.anonymous); + v.Add(SignatureAlgorithm.rsa); + v.Add(SignatureAlgorithm.dsa); + v.Add(SignatureAlgorithm.ecdsa); + return v; + } + + public static IList GetDefaultDssSignatureAlgorithms() + { + return VectorOfOne(new SignatureAndHashAlgorithm(HashAlgorithm.sha1, SignatureAlgorithm.dsa)); + } + + public static IList GetDefaultECDsaSignatureAlgorithms() + { + return VectorOfOne(new SignatureAndHashAlgorithm(HashAlgorithm.sha1, SignatureAlgorithm.ecdsa)); + } + + public static IList GetDefaultRsaSignatureAlgorithms() + { + return VectorOfOne(new SignatureAndHashAlgorithm(HashAlgorithm.sha1, SignatureAlgorithm.rsa)); + } + + public static byte[] GetExtensionData(IDictionary extensions, int extensionType) + { + return extensions == null ? null : (byte[])extensions[extensionType]; + } + + public static IList GetDefaultSupportedSignatureAlgorithms() + { + byte[] hashAlgorithms = new byte[]{ HashAlgorithm.sha1, HashAlgorithm.sha224, HashAlgorithm.sha256, + HashAlgorithm.sha384, HashAlgorithm.sha512 }; + byte[] signatureAlgorithms = new byte[]{ SignatureAlgorithm.rsa, SignatureAlgorithm.dsa, + SignatureAlgorithm.ecdsa }; + + IList result = Platform.CreateArrayList(); + for (int i = 0; i < signatureAlgorithms.Length; ++i) + { + for (int j = 0; j < hashAlgorithms.Length; ++j) + { + result.Add(new SignatureAndHashAlgorithm(hashAlgorithms[j], signatureAlgorithms[i])); + } + } + return result; + } + + public static SignatureAndHashAlgorithm GetSignatureAndHashAlgorithm(TlsContext context, + TlsSignerCredentials signerCredentials) + { + SignatureAndHashAlgorithm signatureAndHashAlgorithm = null; + if (IsTlsV12(context)) + { + signatureAndHashAlgorithm = signerCredentials.SignatureAndHashAlgorithm; + if (signatureAndHashAlgorithm == null) + throw new TlsFatalAlert(AlertDescription.internal_error); + } + return signatureAndHashAlgorithm; + } + + public static bool HasExpectedEmptyExtensionData(IDictionary extensions, int extensionType, + byte alertDescription) + { + byte[] extension_data = GetExtensionData(extensions, extensionType); + if (extension_data == null) + return false; + if (extension_data.Length != 0) + throw new TlsFatalAlert(alertDescription); + return true; + } + + public static TlsSession ImportSession(byte[] sessionID, SessionParameters sessionParameters) + { + return new TlsSessionImpl(sessionID, sessionParameters); + } + + public static bool IsSignatureAlgorithmsExtensionAllowed(ProtocolVersion clientVersion) + { + return ProtocolVersion.TLSv12.IsEqualOrEarlierVersionOf(clientVersion.GetEquivalentTLSVersion()); + } + + /** + * Add a 'signature_algorithms' extension to existing extensions. + * + * @param extensions A {@link Hashtable} to add the extension to. + * @param supportedSignatureAlgorithms {@link Vector} containing at least 1 {@link SignatureAndHashAlgorithm}. + * @throws IOException + */ + public static void AddSignatureAlgorithmsExtension(IDictionary extensions, IList supportedSignatureAlgorithms) + { + extensions[ExtensionType.signature_algorithms] = CreateSignatureAlgorithmsExtension(supportedSignatureAlgorithms); + } + + /** + * Get a 'signature_algorithms' extension from extensions. + * + * @param extensions A {@link Hashtable} to get the extension from, if it is present. + * @return A {@link Vector} containing at least 1 {@link SignatureAndHashAlgorithm}, or null. + * @throws IOException + */ + public static IList GetSignatureAlgorithmsExtension(IDictionary extensions) + { + byte[] extensionData = GetExtensionData(extensions, ExtensionType.signature_algorithms); + return extensionData == null ? null : ReadSignatureAlgorithmsExtension(extensionData); + } + + /** + * Create a 'signature_algorithms' extension value. + * + * @param supportedSignatureAlgorithms A {@link Vector} containing at least 1 {@link SignatureAndHashAlgorithm}. + * @return A byte array suitable for use as an extension value. + * @throws IOException + */ + public static byte[] CreateSignatureAlgorithmsExtension(IList supportedSignatureAlgorithms) + { + MemoryStream buf = new MemoryStream(); + + // supported_signature_algorithms + EncodeSupportedSignatureAlgorithms(supportedSignatureAlgorithms, false, buf); + + return buf.ToArray(); + } + + /** + * Read 'signature_algorithms' extension data. + * + * @param extensionData The extension data. + * @return A {@link Vector} containing at least 1 {@link SignatureAndHashAlgorithm}. + * @throws IOException + */ + public static IList ReadSignatureAlgorithmsExtension(byte[] extensionData) + { + if (extensionData == null) + throw new ArgumentNullException("extensionData"); + + MemoryStream buf = new MemoryStream(extensionData, false); + + // supported_signature_algorithms + IList supported_signature_algorithms = ParseSupportedSignatureAlgorithms(false, buf); + + TlsProtocol.AssertEmpty(buf); + + return supported_signature_algorithms; + } + + public static void EncodeSupportedSignatureAlgorithms(IList supportedSignatureAlgorithms, bool allowAnonymous, + Stream output) + { + if (supportedSignatureAlgorithms == null) + throw new ArgumentNullException("supportedSignatureAlgorithms"); + if (supportedSignatureAlgorithms.Count < 1 || supportedSignatureAlgorithms.Count >= (1 << 15)) + throw new ArgumentException("must have length from 1 to (2^15 - 1)", "supportedSignatureAlgorithms"); + + // supported_signature_algorithms + int length = 2 * supportedSignatureAlgorithms.Count; + CheckUint16(length); + WriteUint16(length, output); + + foreach (SignatureAndHashAlgorithm entry in supportedSignatureAlgorithms) + { + if (!allowAnonymous && entry.Signature == SignatureAlgorithm.anonymous) + { + /* + * RFC 5246 7.4.1.4.1 The "anonymous" value is meaningless in this context but used + * in Section 7.4.3. It MUST NOT appear in this extension. + */ + throw new ArgumentException( + "SignatureAlgorithm.anonymous MUST NOT appear in the signature_algorithms extension"); + } + entry.Encode(output); + } + } + + public static IList ParseSupportedSignatureAlgorithms(bool allowAnonymous, Stream input) + { + // supported_signature_algorithms + int length = ReadUint16(input); + if (length < 2 || (length & 1) != 0) + throw new TlsFatalAlert(AlertDescription.decode_error); + int count = length / 2; + IList supportedSignatureAlgorithms = Platform.CreateArrayList(count); + for (int i = 0; i < count; ++i) + { + SignatureAndHashAlgorithm entry = SignatureAndHashAlgorithm.Parse(input); + if (!allowAnonymous && entry.Signature == SignatureAlgorithm.anonymous) + { + /* + * RFC 5246 7.4.1.4.1 The "anonymous" value is meaningless in this context but used + * in Section 7.4.3. It MUST NOT appear in this extension. + */ + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + supportedSignatureAlgorithms.Add(entry); + } + return supportedSignatureAlgorithms; + } + + public static void VerifySupportedSignatureAlgorithm(IList supportedSignatureAlgorithms, SignatureAndHashAlgorithm signatureAlgorithm) + { + if (supportedSignatureAlgorithms == null) + throw new ArgumentNullException("supportedSignatureAlgorithms"); + if (supportedSignatureAlgorithms.Count < 1 || supportedSignatureAlgorithms.Count >= (1 << 15)) + throw new ArgumentException("must have length from 1 to (2^15 - 1)", "supportedSignatureAlgorithms"); + if (signatureAlgorithm == null) + throw new ArgumentNullException("signatureAlgorithm"); + + if (signatureAlgorithm.Signature != SignatureAlgorithm.anonymous) + { + foreach (SignatureAndHashAlgorithm entry in supportedSignatureAlgorithms) + { + if (entry.Hash == signatureAlgorithm.Hash && entry.Signature == signatureAlgorithm.Signature) + return; + } + } + + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + + public static byte[] PRF(TlsContext context, byte[] secret, string asciiLabel, byte[] seed, int size) + { + ProtocolVersion version = context.ServerVersion; + + if (version.IsSsl) + throw new InvalidOperationException("No PRF available for SSLv3 session"); + + byte[] label = Strings.ToByteArray(asciiLabel); + byte[] labelSeed = Concat(label, seed); + + int prfAlgorithm = context.SecurityParameters.PrfAlgorithm; + + if (prfAlgorithm == PrfAlgorithm.tls_prf_legacy) + return PRF_legacy(secret, label, labelSeed, size); + + IDigest prfDigest = CreatePrfHash(prfAlgorithm); + byte[] buf = new byte[size]; + HMacHash(prfDigest, secret, labelSeed, buf); + return buf; + } + + public static byte[] PRF_legacy(byte[] secret, string asciiLabel, byte[] seed, int size) + { + byte[] label = Strings.ToByteArray(asciiLabel); + byte[] labelSeed = Concat(label, seed); + + return PRF_legacy(secret, label, labelSeed, size); + } + + internal static byte[] PRF_legacy(byte[] secret, byte[] label, byte[] labelSeed, int size) + { + int s_half = (secret.Length + 1) / 2; + byte[] s1 = new byte[s_half]; + byte[] s2 = new byte[s_half]; + Array.Copy(secret, 0, s1, 0, s_half); + Array.Copy(secret, secret.Length - s_half, s2, 0, s_half); + + byte[] b1 = new byte[size]; + byte[] b2 = new byte[size]; + HMacHash(CreateHash(HashAlgorithm.md5), s1, labelSeed, b1); + HMacHash(CreateHash(HashAlgorithm.sha1), s2, labelSeed, b2); + for (int i = 0; i < size; i++) + { + b1[i] ^= b2[i]; + } + return b1; + } + + internal static byte[] Concat(byte[] a, byte[] b) + { + byte[] c = new byte[a.Length + b.Length]; + Array.Copy(a, 0, c, 0, a.Length); + Array.Copy(b, 0, c, a.Length, b.Length); + return c; + } + + internal static void HMacHash(IDigest digest, byte[] secret, byte[] seed, byte[] output) + { + HMac mac = new HMac(digest); + mac.Init(new KeyParameter(secret)); + byte[] a = seed; + int size = digest.GetDigestSize(); + int iterations = (output.Length + size - 1) / size; + byte[] buf = new byte[mac.GetMacSize()]; + byte[] buf2 = new byte[mac.GetMacSize()]; + for (int i = 0; i < iterations; i++) + { + mac.BlockUpdate(a, 0, a.Length); + mac.DoFinal(buf, 0); + a = buf; + mac.BlockUpdate(a, 0, a.Length); + mac.BlockUpdate(seed, 0, seed.Length); + mac.DoFinal(buf2, 0); + Array.Copy(buf2, 0, output, (size * i), System.Math.Min(size, output.Length - (size * i))); + } + } + + internal static void ValidateKeyUsage(X509CertificateStructure c, int keyUsageBits) + { + X509Extensions exts = c.TbsCertificate.Extensions; + if (exts != null) + { + X509Extension ext = exts.GetExtension(X509Extensions.KeyUsage); + if (ext != null) + { + DerBitString ku = KeyUsage.GetInstance(ext); + int bits = ku.GetBytes()[0]; + if ((bits & keyUsageBits) != keyUsageBits) + throw new TlsFatalAlert(AlertDescription.certificate_unknown); + } + } + } + + internal static byte[] CalculateKeyBlock(TlsContext context, int size) + { + SecurityParameters securityParameters = context.SecurityParameters; + byte[] master_secret = securityParameters.MasterSecret; + byte[] seed = Concat(securityParameters.ServerRandom, securityParameters.ClientRandom); + + if (IsSsl(context)) + return CalculateKeyBlock_Ssl(master_secret, seed, size); + + return PRF(context, master_secret, ExporterLabel.key_expansion, seed, size); + } + + internal static byte[] CalculateKeyBlock_Ssl(byte[] master_secret, byte[] random, int size) + { + IDigest md5 = CreateHash(HashAlgorithm.md5); + IDigest sha1 = CreateHash(HashAlgorithm.sha1); + int md5Size = md5.GetDigestSize(); + byte[] shatmp = new byte[sha1.GetDigestSize()]; + byte[] tmp = new byte[size + md5Size]; + + int i = 0, pos = 0; + while (pos < size) + { + byte[] ssl3Const = SSL3_CONST[i]; + + sha1.BlockUpdate(ssl3Const, 0, ssl3Const.Length); + sha1.BlockUpdate(master_secret, 0, master_secret.Length); + sha1.BlockUpdate(random, 0, random.Length); + sha1.DoFinal(shatmp, 0); + + md5.BlockUpdate(master_secret, 0, master_secret.Length); + md5.BlockUpdate(shatmp, 0, shatmp.Length); + md5.DoFinal(tmp, pos); + + pos += md5Size; + ++i; + } + + return Arrays.CopyOfRange(tmp, 0, size); + } + + internal static byte[] CalculateMasterSecret(TlsContext context, byte[] pre_master_secret) + { + SecurityParameters securityParameters = context.SecurityParameters; + + byte[] seed = securityParameters.extendedMasterSecret + ? securityParameters.SessionHash + : Concat(securityParameters.ClientRandom, securityParameters.ServerRandom); + + if (IsSsl(context)) + return CalculateMasterSecret_Ssl(pre_master_secret, seed); + + string asciiLabel = securityParameters.extendedMasterSecret + ? ExporterLabel.extended_master_secret + : ExporterLabel.master_secret; + + return PRF(context, pre_master_secret, asciiLabel, seed, 48); + } + + internal static byte[] CalculateMasterSecret_Ssl(byte[] pre_master_secret, byte[] random) + { + IDigest md5 = CreateHash(HashAlgorithm.md5); + IDigest sha1 = CreateHash(HashAlgorithm.sha1); + int md5Size = md5.GetDigestSize(); + byte[] shatmp = new byte[sha1.GetDigestSize()]; + + byte[] rval = new byte[md5Size * 3]; + int pos = 0; + + for (int i = 0; i < 3; ++i) + { + byte[] ssl3Const = SSL3_CONST[i]; + + sha1.BlockUpdate(ssl3Const, 0, ssl3Const.Length); + sha1.BlockUpdate(pre_master_secret, 0, pre_master_secret.Length); + sha1.BlockUpdate(random, 0, random.Length); + sha1.DoFinal(shatmp, 0); + + md5.BlockUpdate(pre_master_secret, 0, pre_master_secret.Length); + md5.BlockUpdate(shatmp, 0, shatmp.Length); + md5.DoFinal(rval, pos); + + pos += md5Size; + } + + return rval; + } + + internal static byte[] CalculateVerifyData(TlsContext context, string asciiLabel, byte[] handshakeHash) + { + if (IsSsl(context)) + return handshakeHash; + + SecurityParameters securityParameters = context.SecurityParameters; + byte[] master_secret = securityParameters.MasterSecret; + int verify_data_length = securityParameters.VerifyDataLength; + + return PRF(context, master_secret, asciiLabel, handshakeHash, verify_data_length); + } + + public static IDigest CreateHash(byte hashAlgorithm) + { + switch (hashAlgorithm) + { + case HashAlgorithm.md5: + return new MD5Digest(); + case HashAlgorithm.sha1: + return new Sha1Digest(); + case HashAlgorithm.sha224: + return new Sha224Digest(); + case HashAlgorithm.sha256: + return new Sha256Digest(); + case HashAlgorithm.sha384: + return new Sha384Digest(); + case HashAlgorithm.sha512: + return new Sha512Digest(); + default: + throw new ArgumentException("unknown HashAlgorithm", "hashAlgorithm"); + } + } + + public static IDigest CreateHash(SignatureAndHashAlgorithm signatureAndHashAlgorithm) + { + return signatureAndHashAlgorithm == null + ? new CombinedHash() + : CreateHash(signatureAndHashAlgorithm.Hash); + } + + public static IDigest CloneHash(byte hashAlgorithm, IDigest hash) + { + switch (hashAlgorithm) + { + case HashAlgorithm.md5: + return new MD5Digest((MD5Digest)hash); + case HashAlgorithm.sha1: + return new Sha1Digest((Sha1Digest)hash); + case HashAlgorithm.sha224: + return new Sha224Digest((Sha224Digest)hash); + case HashAlgorithm.sha256: + return new Sha256Digest((Sha256Digest)hash); + case HashAlgorithm.sha384: + return new Sha384Digest((Sha384Digest)hash); + case HashAlgorithm.sha512: + return new Sha512Digest((Sha512Digest)hash); + default: + throw new ArgumentException("unknown HashAlgorithm", "hashAlgorithm"); + } + } + + public static IDigest CreatePrfHash(int prfAlgorithm) + { + switch (prfAlgorithm) + { + case PrfAlgorithm.tls_prf_legacy: + return new CombinedHash(); + default: + return CreateHash(GetHashAlgorithmForPrfAlgorithm(prfAlgorithm)); + } + } + + public static IDigest ClonePrfHash(int prfAlgorithm, IDigest hash) + { + switch (prfAlgorithm) + { + case PrfAlgorithm.tls_prf_legacy: + return new CombinedHash((CombinedHash)hash); + default: + return CloneHash(GetHashAlgorithmForPrfAlgorithm(prfAlgorithm), hash); + } + } + + public static byte GetHashAlgorithmForPrfAlgorithm(int prfAlgorithm) + { + switch (prfAlgorithm) + { + case PrfAlgorithm.tls_prf_legacy: + throw new ArgumentException("legacy PRF not a valid algorithm", "prfAlgorithm"); + case PrfAlgorithm.tls_prf_sha256: + return HashAlgorithm.sha256; + case PrfAlgorithm.tls_prf_sha384: + return HashAlgorithm.sha384; + default: + throw new ArgumentException("unknown PrfAlgorithm", "prfAlgorithm"); + } + } + + public static DerObjectIdentifier GetOidForHashAlgorithm(byte hashAlgorithm) + { + switch (hashAlgorithm) + { + case HashAlgorithm.md5: + return PkcsObjectIdentifiers.MD5; + case HashAlgorithm.sha1: + return X509ObjectIdentifiers.IdSha1; + case HashAlgorithm.sha224: + return NistObjectIdentifiers.IdSha224; + case HashAlgorithm.sha256: + return NistObjectIdentifiers.IdSha256; + case HashAlgorithm.sha384: + return NistObjectIdentifiers.IdSha384; + case HashAlgorithm.sha512: + return NistObjectIdentifiers.IdSha512; + default: + throw new ArgumentException("unknown HashAlgorithm", "hashAlgorithm"); + } + } + + internal static short GetClientCertificateType(Certificate clientCertificate, Certificate serverCertificate) + { + if (clientCertificate.IsEmpty) + return -1; + + X509CertificateStructure x509Cert = clientCertificate.GetCertificateAt(0); + SubjectPublicKeyInfo keyInfo = x509Cert.SubjectPublicKeyInfo; + try + { + AsymmetricKeyParameter publicKey = PublicKeyFactory.CreateKey(keyInfo); + if (publicKey.IsPrivate) + throw new TlsFatalAlert(AlertDescription.internal_error); + + /* + * TODO RFC 5246 7.4.6. The certificates MUST be signed using an acceptable hash/ + * signature algorithm pair, as described in Section 7.4.4. Note that this relaxes the + * constraints on certificate-signing algorithms found in prior versions of TLS. + */ + + /* + * RFC 5246 7.4.6. Client Certificate + */ + + /* + * RSA public key; the certificate MUST allow the key to be used for signing with the + * signature scheme and hash algorithm that will be employed in the certificate verify + * message. + */ + if (publicKey is RsaKeyParameters) + { + ValidateKeyUsage(x509Cert, KeyUsage.DigitalSignature); + return ClientCertificateType.rsa_sign; + } + + /* + * DSA public key; the certificate MUST allow the key to be used for signing with the + * hash algorithm that will be employed in the certificate verify message. + */ + if (publicKey is DsaPublicKeyParameters) + { + ValidateKeyUsage(x509Cert, KeyUsage.DigitalSignature); + return ClientCertificateType.dss_sign; + } + + /* + * ECDSA-capable public key; the certificate MUST allow the key to be used for signing + * with the hash algorithm that will be employed in the certificate verify message; the + * public key MUST use a curve and point format supported by the server. + */ + if (publicKey is ECPublicKeyParameters) + { + ValidateKeyUsage(x509Cert, KeyUsage.DigitalSignature); + // TODO Check the curve and point format + return ClientCertificateType.ecdsa_sign; + } + + // TODO Add support for ClientCertificateType.*_fixed_* + + throw new TlsFatalAlert(AlertDescription.unsupported_certificate); + } + catch (Exception e) + { + throw new TlsFatalAlert(AlertDescription.unsupported_certificate, e); + } + } + + internal static void TrackHashAlgorithms(TlsHandshakeHash handshakeHash, IList supportedSignatureAlgorithms) + { + if (supportedSignatureAlgorithms != null) + { + foreach (SignatureAndHashAlgorithm signatureAndHashAlgorithm in supportedSignatureAlgorithms) + { + byte hashAlgorithm = signatureAndHashAlgorithm.Hash; + + // TODO Support values in the "Reserved for Private Use" range + if (!HashAlgorithm.IsPrivate(hashAlgorithm)) + { + handshakeHash.TrackHashAlgorithm(hashAlgorithm); + } + } + } + } + + public static bool HasSigningCapability(byte clientCertificateType) + { + switch (clientCertificateType) + { + case ClientCertificateType.dss_sign: + case ClientCertificateType.ecdsa_sign: + case ClientCertificateType.rsa_sign: + return true; + default: + return false; + } + } + + public static TlsSigner CreateTlsSigner(byte clientCertificateType) + { + switch (clientCertificateType) + { + case ClientCertificateType.dss_sign: + return new TlsDssSigner(); + case ClientCertificateType.ecdsa_sign: + return new TlsECDsaSigner(); + case ClientCertificateType.rsa_sign: + return new TlsRsaSigner(); + default: + throw new ArgumentException("not a type with signing capability", "clientCertificateType"); + } + } + + internal static readonly byte[] SSL_CLIENT = {0x43, 0x4C, 0x4E, 0x54}; + internal static readonly byte[] SSL_SERVER = {0x53, 0x52, 0x56, 0x52}; + + // SSL3 magic mix constants ("A", "BB", "CCC", ...) + internal static readonly byte[][] SSL3_CONST = GenSsl3Const(); + + private static byte[][] GenSsl3Const() + { + int n = 10; + byte[][] arr = new byte[n][]; + for (int i = 0; i < n; i++) + { + byte[] b = new byte[i + 1]; + Arrays.Fill(b, (byte)('A' + i)); + arr[i] = b; + } + return arr; + } + + private static IList VectorOfOne(object obj) + { + IList v = Platform.CreateArrayList(1); + v.Add(obj); + return v; + } + + public static int GetCipherType(int ciphersuite) + { + switch (GetEncryptionAlgorithm(ciphersuite)) + { + case EncryptionAlgorithm.AES_128_CCM: + case EncryptionAlgorithm.AES_128_CCM_8: + case EncryptionAlgorithm.AES_128_GCM: + case EncryptionAlgorithm.AES_128_OCB_TAGLEN96: + case EncryptionAlgorithm.AES_256_CCM: + case EncryptionAlgorithm.AES_256_CCM_8: + case EncryptionAlgorithm.AES_256_GCM: + case EncryptionAlgorithm.AES_256_OCB_TAGLEN96: + case EncryptionAlgorithm.CAMELLIA_128_GCM: + case EncryptionAlgorithm.CAMELLIA_256_GCM: + case EncryptionAlgorithm.CHACHA20_POLY1305: + return CipherType.aead; + + case EncryptionAlgorithm.RC2_CBC_40: + case EncryptionAlgorithm.IDEA_CBC: + case EncryptionAlgorithm.DES40_CBC: + case EncryptionAlgorithm.DES_CBC: + case EncryptionAlgorithm.cls_3DES_EDE_CBC: + case EncryptionAlgorithm.AES_128_CBC: + case EncryptionAlgorithm.AES_256_CBC: + case EncryptionAlgorithm.CAMELLIA_128_CBC: + case EncryptionAlgorithm.CAMELLIA_256_CBC: + case EncryptionAlgorithm.SEED_CBC: + return CipherType.block; + + case EncryptionAlgorithm.NULL: + case EncryptionAlgorithm.RC4_40: + case EncryptionAlgorithm.RC4_128: + return CipherType.stream; + + default: + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + public static int GetEncryptionAlgorithm(int ciphersuite) + { + switch (ciphersuite) + { + case CipherSuite.TLS_DH_anon_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_PSK_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA: + return EncryptionAlgorithm.cls_3DES_EDE_CBC; + + case CipherSuite.TLS_DH_anon_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DH_anon_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDH_anon_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_RSA_PSK_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_RSA_PSK_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_WITH_AES_128_CBC_SHA: + return EncryptionAlgorithm.AES_128_CBC; + + case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CCM: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM: + case CipherSuite.TLS_PSK_WITH_AES_128_CCM: + case CipherSuite.TLS_RSA_WITH_AES_128_CCM: + return EncryptionAlgorithm.AES_128_CCM; + + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM_8: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8: + case CipherSuite.TLS_PSK_DHE_WITH_AES_128_CCM_8: + case CipherSuite.TLS_PSK_WITH_AES_128_CCM_8: + case CipherSuite.TLS_RSA_WITH_AES_128_CCM_8: + return EncryptionAlgorithm.AES_128_CCM_8; + + case CipherSuite.TLS_DH_anon_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DH_DSS_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DH_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DHE_PSK_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_PSK_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_RSA_PSK_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256: + return EncryptionAlgorithm.AES_128_GCM; + + case CipherSuite.DRAFT_TLS_DHE_PSK_WITH_AES_128_OCB: + case CipherSuite.DRAFT_TLS_DHE_RSA_WITH_AES_128_OCB: + case CipherSuite.DRAFT_TLS_ECDHE_ECDSA_WITH_AES_128_OCB: + case CipherSuite.DRAFT_TLS_ECDHE_PSK_WITH_AES_128_OCB: + case CipherSuite.DRAFT_TLS_ECDHE_RSA_WITH_AES_128_OCB: + case CipherSuite.DRAFT_TLS_PSK_WITH_AES_128_OCB: + return EncryptionAlgorithm.AES_128_OCB_TAGLEN96; + + case CipherSuite.TLS_DH_anon_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_DH_anon_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_ECDH_anon_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_PSK_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_PSK_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_WITH_AES_256_CBC_SHA: + return EncryptionAlgorithm.AES_256_CBC; + + case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CCM: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM: + case CipherSuite.TLS_PSK_WITH_AES_256_CCM: + case CipherSuite.TLS_RSA_WITH_AES_256_CCM: + return EncryptionAlgorithm.AES_256_CCM; + + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM_8: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8: + case CipherSuite.TLS_PSK_DHE_WITH_AES_256_CCM_8: + case CipherSuite.TLS_PSK_WITH_AES_256_CCM_8: + case CipherSuite.TLS_RSA_WITH_AES_256_CCM_8: + return EncryptionAlgorithm.AES_256_CCM_8; + + case CipherSuite.TLS_DH_anon_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_DH_DSS_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_DH_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_DHE_DSS_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_DHE_PSK_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_PSK_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_RSA_PSK_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384: + return EncryptionAlgorithm.AES_256_GCM; + + case CipherSuite.DRAFT_TLS_DHE_PSK_WITH_AES_256_OCB: + case CipherSuite.DRAFT_TLS_DHE_RSA_WITH_AES_256_OCB: + case CipherSuite.DRAFT_TLS_ECDHE_ECDSA_WITH_AES_256_OCB: + case CipherSuite.DRAFT_TLS_ECDHE_PSK_WITH_AES_256_OCB: + case CipherSuite.DRAFT_TLS_ECDHE_RSA_WITH_AES_256_OCB: + case CipherSuite.DRAFT_TLS_PSK_WITH_AES_256_OCB: + return EncryptionAlgorithm.AES_256_OCB_TAGLEN96; + + case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA: + case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA: + case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA: + case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA: + case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_CBC_SHA: + case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256: + return EncryptionAlgorithm.CAMELLIA_128_CBC; + + case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256: + return EncryptionAlgorithm.CAMELLIA_128_GCM; + + case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA: + case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256: + case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA: + case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256: + case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA: + case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA: + case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256: + case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384: + case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256: + case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384: + case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384: + case CipherSuite.TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384: + case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_CBC_SHA: + case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256: + case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384: + return EncryptionAlgorithm.CAMELLIA_256_CBC; + + case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384: + return EncryptionAlgorithm.CAMELLIA_256_GCM; + + case CipherSuite.DRAFT_TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256: + case CipherSuite.DRAFT_TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256: + case CipherSuite.DRAFT_TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256: + case CipherSuite.DRAFT_TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256: + case CipherSuite.DRAFT_TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256: + case CipherSuite.DRAFT_TLS_PSK_WITH_CHACHA20_POLY1305_SHA256: + case CipherSuite.DRAFT_TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256: + return EncryptionAlgorithm.CHACHA20_POLY1305; + + case CipherSuite.TLS_RSA_WITH_NULL_MD5: + return EncryptionAlgorithm.NULL; + + case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA: + case CipherSuite.TLS_ECDH_anon_WITH_NULL_SHA: + case CipherSuite.TLS_ECDH_ECDSA_WITH_NULL_SHA: + case CipherSuite.TLS_ECDH_RSA_WITH_NULL_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_NULL_SHA: + case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_NULL_SHA: + case CipherSuite.TLS_PSK_WITH_NULL_SHA: + case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA: + case CipherSuite.TLS_RSA_WITH_NULL_SHA: + return EncryptionAlgorithm.NULL; + + case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA256: + case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA256: + case CipherSuite.TLS_PSK_WITH_NULL_SHA256: + case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA256: + case CipherSuite.TLS_RSA_WITH_NULL_SHA256: + return EncryptionAlgorithm.NULL; + + case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA384: + case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA384: + case CipherSuite.TLS_PSK_WITH_NULL_SHA384: + case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA384: + return EncryptionAlgorithm.NULL; + + case CipherSuite.TLS_DH_anon_WITH_RC4_128_MD5: + case CipherSuite.TLS_RSA_WITH_RC4_128_MD5: + return EncryptionAlgorithm.RC4_128; + + case CipherSuite.TLS_DHE_PSK_WITH_RC4_128_SHA: + case CipherSuite.TLS_ECDH_anon_WITH_RC4_128_SHA: + case CipherSuite.TLS_ECDH_ECDSA_WITH_RC4_128_SHA: + case CipherSuite.TLS_ECDH_RSA_WITH_RC4_128_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA: + case CipherSuite.TLS_ECDHE_PSK_WITH_RC4_128_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_RC4_128_SHA: + case CipherSuite.TLS_PSK_WITH_RC4_128_SHA: + case CipherSuite.TLS_RSA_WITH_RC4_128_SHA: + case CipherSuite.TLS_RSA_PSK_WITH_RC4_128_SHA: + return EncryptionAlgorithm.RC4_128; + + case CipherSuite.TLS_DH_anon_WITH_SEED_CBC_SHA: + case CipherSuite.TLS_DH_DSS_WITH_SEED_CBC_SHA: + case CipherSuite.TLS_DH_RSA_WITH_SEED_CBC_SHA: + case CipherSuite.TLS_DHE_DSS_WITH_SEED_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_SEED_CBC_SHA: + case CipherSuite.TLS_RSA_WITH_SEED_CBC_SHA: + return EncryptionAlgorithm.SEED_CBC; + + default: + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + public static int GetKeyExchangeAlgorithm(int ciphersuite) + { + switch (ciphersuite) + { + case CipherSuite.TLS_DH_anon_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_DH_anon_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DH_anon_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DH_anon_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DH_anon_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_DH_anon_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DH_anon_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA: + case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA: + case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256: + case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_DH_anon_WITH_RC4_128_MD5: + case CipherSuite.TLS_DH_anon_WITH_SEED_CBC_SHA: + return KeyExchangeAlgorithm.DH_anon; + + case CipherSuite.TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DH_DSS_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DH_DSS_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA: + case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA: + case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256: + case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_DH_DSS_WITH_SEED_CBC_SHA: + return KeyExchangeAlgorithm.DH_DSS; + + case CipherSuite.TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DH_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DH_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA: + case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA: + case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256: + case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_DH_RSA_WITH_SEED_CBC_SHA: + return KeyExchangeAlgorithm.DH_RSA; + + case CipherSuite.TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA: + case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA: + case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_DHE_DSS_WITH_SEED_CBC_SHA: + return KeyExchangeAlgorithm.DHE_DSS; + + case CipherSuite.TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CCM: + case CipherSuite.TLS_DHE_PSK_WITH_AES_128_GCM_SHA256: + case CipherSuite.DRAFT_TLS_DHE_PSK_WITH_AES_128_OCB: + case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CCM: + case CipherSuite.TLS_DHE_PSK_WITH_AES_256_GCM_SHA384: + case CipherSuite.DRAFT_TLS_DHE_PSK_WITH_AES_256_OCB: + case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384: + case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.DRAFT_TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256: + case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA: + case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA256: + case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA384: + case CipherSuite.TLS_DHE_PSK_WITH_RC4_128_SHA: + case CipherSuite.TLS_PSK_DHE_WITH_AES_128_CCM_8: + case CipherSuite.TLS_PSK_DHE_WITH_AES_256_CCM_8: + return KeyExchangeAlgorithm.DHE_PSK; + + case CipherSuite.TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM_8: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.DRAFT_TLS_DHE_RSA_WITH_AES_128_OCB: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM_8: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.DRAFT_TLS_DHE_RSA_WITH_AES_256_OCB: + case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.DRAFT_TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_SEED_CBC_SHA: + return KeyExchangeAlgorithm.DHE_RSA; + + case CipherSuite.TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDH_anon_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDH_anon_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDH_anon_WITH_NULL_SHA: + case CipherSuite.TLS_ECDH_anon_WITH_RC4_128_SHA: + return KeyExchangeAlgorithm.ECDH_anon; + + case CipherSuite.TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.DRAFT_TLS_ECDHE_ECDSA_WITH_AES_128_OCB: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.DRAFT_TLS_ECDHE_ECDSA_WITH_AES_256_OCB: + case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384: + case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_ECDH_ECDSA_WITH_NULL_SHA: + case CipherSuite.TLS_ECDH_ECDSA_WITH_RC4_128_SHA: + return KeyExchangeAlgorithm.ECDH_ECDSA; + + case CipherSuite.TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384: + case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_ECDH_RSA_WITH_NULL_SHA: + case CipherSuite.TLS_ECDH_RSA_WITH_RC4_128_SHA: + return KeyExchangeAlgorithm.ECDH_RSA; + + case CipherSuite.TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.DRAFT_TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_NULL_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA: + return KeyExchangeAlgorithm.ECDHE_ECDSA; + + case CipherSuite.TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256: + case CipherSuite.DRAFT_TLS_ECDHE_PSK_WITH_AES_128_OCB: + case CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384: + case CipherSuite.DRAFT_TLS_ECDHE_PSK_WITH_AES_256_OCB: + case CipherSuite.TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384: + case CipherSuite.DRAFT_TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256: + case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA: + case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA256: + case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA384: + case CipherSuite.TLS_ECDHE_PSK_WITH_RC4_128_SHA: + return KeyExchangeAlgorithm.ECDHE_PSK; + + case CipherSuite.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.DRAFT_TLS_ECDHE_RSA_WITH_AES_128_OCB: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.DRAFT_TLS_ECDHE_RSA_WITH_AES_256_OCB: + case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.DRAFT_TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256: + case CipherSuite.TLS_ECDHE_RSA_WITH_NULL_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_RC4_128_SHA: + return KeyExchangeAlgorithm.ECDHE_RSA; + + case CipherSuite.TLS_PSK_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_PSK_WITH_AES_128_CCM: + case CipherSuite.TLS_PSK_WITH_AES_128_CCM_8: + case CipherSuite.TLS_PSK_WITH_AES_128_GCM_SHA256: + case CipherSuite.DRAFT_TLS_PSK_WITH_AES_128_OCB: + case CipherSuite.TLS_PSK_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_PSK_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_PSK_WITH_AES_256_CCM: + case CipherSuite.TLS_PSK_WITH_AES_256_CCM_8: + case CipherSuite.TLS_PSK_WITH_AES_256_GCM_SHA384: + case CipherSuite.DRAFT_TLS_PSK_WITH_AES_256_OCB: + case CipherSuite.TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384: + case CipherSuite.TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.DRAFT_TLS_PSK_WITH_CHACHA20_POLY1305_SHA256: + case CipherSuite.TLS_PSK_WITH_NULL_SHA: + case CipherSuite.TLS_PSK_WITH_NULL_SHA256: + case CipherSuite.TLS_PSK_WITH_NULL_SHA384: + case CipherSuite.TLS_PSK_WITH_RC4_128_SHA: + return KeyExchangeAlgorithm.PSK; + + case CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_RSA_WITH_AES_128_CCM: + case CipherSuite.TLS_RSA_WITH_AES_128_CCM_8: + case CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_RSA_WITH_AES_256_CCM: + case CipherSuite.TLS_RSA_WITH_AES_256_CCM_8: + case CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_CBC_SHA: + case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_CBC_SHA: + case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256: + case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.DRAFT_TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256: + case CipherSuite.TLS_RSA_WITH_NULL_MD5: + case CipherSuite.TLS_RSA_WITH_NULL_SHA: + case CipherSuite.TLS_RSA_WITH_NULL_SHA256: + case CipherSuite.TLS_RSA_WITH_RC4_128_MD5: + case CipherSuite.TLS_RSA_WITH_RC4_128_SHA: + case CipherSuite.TLS_RSA_WITH_SEED_CBC_SHA: + return KeyExchangeAlgorithm.RSA; + + case CipherSuite.TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_RSA_PSK_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_RSA_PSK_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_RSA_PSK_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_RSA_PSK_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384: + case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA: + case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA256: + case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA384: + case CipherSuite.TLS_RSA_PSK_WITH_RC4_128_SHA: + return KeyExchangeAlgorithm.RSA_PSK; + + case CipherSuite.TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_WITH_AES_256_CBC_SHA: + return KeyExchangeAlgorithm.SRP; + + case CipherSuite.TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA: + return KeyExchangeAlgorithm.SRP_DSS; + + case CipherSuite.TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA: + return KeyExchangeAlgorithm.SRP_RSA; + + default: + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + public static int GetMacAlgorithm(int ciphersuite) + { + switch (ciphersuite) + { + case CipherSuite.TLS_DH_anon_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DH_anon_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_DH_DSS_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DH_DSS_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_DH_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DH_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_DHE_DSS_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CCM: + case CipherSuite.TLS_DHE_PSK_WITH_AES_128_GCM_SHA256: + case CipherSuite.DRAFT_TLS_DHE_PSK_WITH_AES_128_OCB: + case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CCM: + case CipherSuite.TLS_DHE_PSK_WITH_AES_256_GCM_SHA384: + case CipherSuite.DRAFT_TLS_DHE_PSK_WITH_AES_256_OCB: + case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.DRAFT_TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM_8: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.DRAFT_TLS_DHE_RSA_WITH_AES_128_OCB: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM_8: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.DRAFT_TLS_DHE_RSA_WITH_AES_256_OCB: + case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.DRAFT_TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.DRAFT_TLS_ECDHE_ECDSA_WITH_AES_128_OCB: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.DRAFT_TLS_ECDHE_ECDSA_WITH_AES_256_OCB: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.DRAFT_TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256: + case CipherSuite.DRAFT_TLS_ECDHE_PSK_WITH_AES_128_OCB: + case CipherSuite.DRAFT_TLS_ECDHE_PSK_WITH_AES_256_OCB: + case CipherSuite.DRAFT_TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.DRAFT_TLS_ECDHE_RSA_WITH_AES_128_OCB: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.DRAFT_TLS_ECDHE_RSA_WITH_AES_256_OCB: + case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.DRAFT_TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256: + case CipherSuite.TLS_PSK_DHE_WITH_AES_128_CCM_8: + case CipherSuite.TLS_PSK_DHE_WITH_AES_256_CCM_8: + case CipherSuite.TLS_PSK_WITH_AES_128_CCM: + case CipherSuite.TLS_PSK_WITH_AES_128_CCM_8: + case CipherSuite.TLS_PSK_WITH_AES_128_GCM_SHA256: + case CipherSuite.DRAFT_TLS_PSK_WITH_AES_128_OCB: + case CipherSuite.TLS_PSK_WITH_AES_256_CCM: + case CipherSuite.TLS_PSK_WITH_AES_256_CCM_8: + case CipherSuite.TLS_PSK_WITH_AES_256_GCM_SHA384: + case CipherSuite.DRAFT_TLS_PSK_WITH_AES_256_OCB: + case CipherSuite.TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.DRAFT_TLS_PSK_WITH_CHACHA20_POLY1305_SHA256: + case CipherSuite.TLS_RSA_PSK_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_RSA_PSK_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.DRAFT_TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256: + case CipherSuite.TLS_RSA_WITH_AES_128_CCM: + case CipherSuite.TLS_RSA_WITH_AES_128_CCM_8: + case CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_RSA_WITH_AES_256_CCM: + case CipherSuite.TLS_RSA_WITH_AES_256_CCM_8: + case CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384: + return MacAlgorithm.cls_null; + + case CipherSuite.TLS_DH_anon_WITH_RC4_128_MD5: + case CipherSuite.TLS_RSA_WITH_NULL_MD5: + case CipherSuite.TLS_RSA_WITH_RC4_128_MD5: + return MacAlgorithm.hmac_md5; + + case CipherSuite.TLS_DH_anon_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_DH_anon_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DH_anon_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA: + case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA: + case CipherSuite.TLS_DH_anon_WITH_SEED_CBC_SHA: + case CipherSuite.TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA: + case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA: + case CipherSuite.TLS_DH_DSS_WITH_SEED_CBC_SHA: + case CipherSuite.TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA: + case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA: + case CipherSuite.TLS_DH_RSA_WITH_SEED_CBC_SHA: + case CipherSuite.TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA: + case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA: + case CipherSuite.TLS_DHE_DSS_WITH_SEED_CBC_SHA: + case CipherSuite.TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA: + case CipherSuite.TLS_DHE_PSK_WITH_RC4_128_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_SEED_CBC_SHA: + case CipherSuite.TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDH_anon_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDH_anon_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDH_anon_WITH_NULL_SHA: + case CipherSuite.TLS_ECDH_anon_WITH_RC4_128_SHA: + case CipherSuite.TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDH_ECDSA_WITH_NULL_SHA: + case CipherSuite.TLS_ECDH_ECDSA_WITH_RC4_128_SHA: + case CipherSuite.TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDH_RSA_WITH_NULL_SHA: + case CipherSuite.TLS_ECDH_RSA_WITH_RC4_128_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_NULL_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA: + case CipherSuite.TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA: + case CipherSuite.TLS_ECDHE_PSK_WITH_RC4_128_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_NULL_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_RC4_128_SHA: + case CipherSuite.TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_RSA_PSK_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_PSK_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_PSK_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_PSK_WITH_NULL_SHA: + case CipherSuite.TLS_PSK_WITH_RC4_128_SHA: + case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA: + case CipherSuite.TLS_RSA_PSK_WITH_RC4_128_SHA: + case CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_CBC_SHA: + case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_CBC_SHA: + case CipherSuite.TLS_RSA_WITH_NULL_SHA: + case CipherSuite.TLS_RSA_WITH_RC4_128_SHA: + case CipherSuite.TLS_RSA_WITH_SEED_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_WITH_AES_256_CBC_SHA: + return MacAlgorithm.hmac_sha1; + + case CipherSuite.TLS_DH_anon_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DH_anon_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256: + case CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256: + case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256: + case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA256: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_PSK_WITH_NULL_SHA256: + case CipherSuite.TLS_RSA_PSK_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA256: + case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256: + case CipherSuite.TLS_RSA_WITH_NULL_SHA256: + return MacAlgorithm.hmac_sha256; + + case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384: + case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA384: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA384: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384: + case CipherSuite.TLS_PSK_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384: + case CipherSuite.TLS_PSK_WITH_NULL_SHA384: + case CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384: + case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA384: + return MacAlgorithm.hmac_sha384; + + default: + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + public static ProtocolVersion GetMinimumVersion(int ciphersuite) + { + switch (ciphersuite) + { + case CipherSuite.TLS_DH_anon_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DH_anon_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DH_anon_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DH_anon_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256: + case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DH_DSS_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DH_DSS_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256: + case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DH_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DH_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256: + case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256: + case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CCM: + case CipherSuite.TLS_DHE_PSK_WITH_AES_128_GCM_SHA256: + case CipherSuite.DRAFT_TLS_DHE_PSK_WITH_AES_128_OCB: + case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CCM: + case CipherSuite.TLS_DHE_PSK_WITH_AES_256_GCM_SHA384: + case CipherSuite.DRAFT_TLS_DHE_PSK_WITH_AES_256_OCB: + case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.DRAFT_TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM_8: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.DRAFT_TLS_DHE_RSA_WITH_AES_128_OCB: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM_8: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.DRAFT_TLS_DHE_RSA_WITH_AES_256_OCB: + case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256: + case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.DRAFT_TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384: + case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384: + case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.DRAFT_TLS_ECDHE_ECDSA_WITH_AES_128_OCB: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.DRAFT_TLS_ECDHE_ECDSA_WITH_AES_256_OCB: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.DRAFT_TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256: + case CipherSuite.DRAFT_TLS_ECDHE_PSK_WITH_AES_128_OCB: + case CipherSuite.DRAFT_TLS_ECDHE_PSK_WITH_AES_256_OCB: + case CipherSuite.DRAFT_TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.DRAFT_TLS_ECDHE_RSA_WITH_AES_128_OCB: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.DRAFT_TLS_ECDHE_RSA_WITH_AES_256_OCB: + case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.DRAFT_TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256: + case CipherSuite.TLS_PSK_DHE_WITH_AES_128_CCM_8: + case CipherSuite.TLS_PSK_DHE_WITH_AES_256_CCM_8: + case CipherSuite.TLS_PSK_WITH_AES_128_CCM: + case CipherSuite.TLS_PSK_WITH_AES_128_CCM_8: + case CipherSuite.TLS_PSK_WITH_AES_128_GCM_SHA256: + case CipherSuite.DRAFT_TLS_PSK_WITH_AES_128_OCB: + case CipherSuite.TLS_PSK_WITH_AES_256_CCM: + case CipherSuite.TLS_PSK_WITH_AES_256_CCM_8: + case CipherSuite.TLS_PSK_WITH_AES_256_GCM_SHA384: + case CipherSuite.DRAFT_TLS_PSK_WITH_AES_256_OCB: + case CipherSuite.TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.DRAFT_TLS_PSK_WITH_CHACHA20_POLY1305_SHA256: + case CipherSuite.TLS_RSA_PSK_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_RSA_PSK_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.DRAFT_TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256: + case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_RSA_WITH_AES_128_CCM: + case CipherSuite.TLS_RSA_WITH_AES_128_CCM_8: + case CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256: + case CipherSuite.TLS_RSA_WITH_AES_256_CCM: + case CipherSuite.TLS_RSA_WITH_AES_256_CCM_8: + case CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256: + case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_RSA_WITH_NULL_SHA256: + return ProtocolVersion.TLSv12; + + default: + return ProtocolVersion.SSLv3; + } + } + + public static bool IsAeadCipherSuite(int ciphersuite) + { + return CipherType.aead == GetCipherType(ciphersuite); + } + + public static bool IsBlockCipherSuite(int ciphersuite) + { + return CipherType.block == GetCipherType(ciphersuite); + } + + public static bool IsStreamCipherSuite(int ciphersuite) + { + return CipherType.stream == GetCipherType(ciphersuite); + } + + public static bool IsValidCipherSuiteForSignatureAlgorithms(int cipherSuite, IList sigAlgs) + { + int keyExchangeAlgorithm; + try + { + keyExchangeAlgorithm = GetKeyExchangeAlgorithm(cipherSuite); + } + catch (IOException e) + { + return true; + } + + switch (keyExchangeAlgorithm) + { + case KeyExchangeAlgorithm.DH_anon: + case KeyExchangeAlgorithm.DH_anon_EXPORT: + case KeyExchangeAlgorithm.ECDH_anon: + return sigAlgs.Contains(SignatureAlgorithm.anonymous); + + case KeyExchangeAlgorithm.DHE_RSA: + case KeyExchangeAlgorithm.DHE_RSA_EXPORT: + case KeyExchangeAlgorithm.ECDHE_RSA: + case KeyExchangeAlgorithm.SRP_RSA: + return sigAlgs.Contains(SignatureAlgorithm.rsa); + + case KeyExchangeAlgorithm.DHE_DSS: + case KeyExchangeAlgorithm.DHE_DSS_EXPORT: + case KeyExchangeAlgorithm.SRP_DSS: + return sigAlgs.Contains(SignatureAlgorithm.dsa); + + case KeyExchangeAlgorithm.ECDHE_ECDSA: + return sigAlgs.Contains(SignatureAlgorithm.ecdsa); + + default: + return true; + } + } + + public static bool IsValidCipherSuiteForVersion(int cipherSuite, ProtocolVersion serverVersion) + { + return GetMinimumVersion(cipherSuite).IsEqualOrEarlierVersionOf(serverVersion.GetEquivalentTLSVersion()); + } + + public static IList GetUsableSignatureAlgorithms(IList sigHashAlgs) + { + if (sigHashAlgs == null) + return GetAllSignatureAlgorithms(); + + IList v = Platform.CreateArrayList(4); + v.Add(SignatureAlgorithm.anonymous); + foreach (SignatureAndHashAlgorithm sigHashAlg in sigHashAlgs) + { + //if (sigHashAlg.Hash >= MINIMUM_HASH_STRICT) + { + byte sigAlg = sigHashAlg.Signature; + if (!v.Contains(sigAlg)) + { + v.Add(sigAlg); + } + } + } + return v; + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/UrlAndHash.cs b/bc-sharp-crypto/src/crypto/tls/UrlAndHash.cs new file mode 100644 index 0000000..9ffd2cb --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/UrlAndHash.cs @@ -0,0 +1,94 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /** + * RFC 6066 5. + */ + public class UrlAndHash + { + protected readonly string mUrl; + protected readonly byte[] mSha1Hash; + + public UrlAndHash(string url, byte[] sha1Hash) + { + if (url == null || url.Length < 1 || url.Length >= (1 << 16)) + throw new ArgumentException("must have length from 1 to (2^16 - 1)", "url"); + if (sha1Hash != null && sha1Hash.Length != 20) + throw new ArgumentException("must have length == 20, if present", "sha1Hash"); + + this.mUrl = url; + this.mSha1Hash = sha1Hash; + } + + public virtual string Url + { + get { return mUrl; } + } + + public virtual byte[] Sha1Hash + { + get { return mSha1Hash; } + } + + /** + * Encode this {@link UrlAndHash} to a {@link Stream}. + * + * @param output the {@link Stream} to encode to. + * @throws IOException + */ + public virtual void Encode(Stream output) + { + byte[] urlEncoding = Strings.ToByteArray(this.mUrl); + TlsUtilities.WriteOpaque16(urlEncoding, output); + + if (this.mSha1Hash == null) + { + TlsUtilities.WriteUint8(0, output); + } + else + { + TlsUtilities.WriteUint8(1, output); + output.Write(this.mSha1Hash, 0, this.mSha1Hash.Length); + } + } + + /** + * Parse a {@link UrlAndHash} from a {@link Stream}. + * + * @param context + * the {@link TlsContext} of the current connection. + * @param input + * the {@link Stream} to parse from. + * @return a {@link UrlAndHash} object. + * @throws IOException + */ + public static UrlAndHash Parse(TlsContext context, Stream input) + { + byte[] urlEncoding = TlsUtilities.ReadOpaque16(input); + if (urlEncoding.Length < 1) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + string url = Strings.FromByteArray(urlEncoding); + + byte[] sha1Hash = null; + byte padding = TlsUtilities.ReadUint8(input); + switch (padding) + { + case 0: + if (TlsUtilities.IsTlsV12(context)) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + break; + case 1: + sha1Hash = TlsUtilities.ReadFully(20, input); + break; + default: + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + + return new UrlAndHash(url, sha1Hash); + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/UseSrtpData.cs b/bc-sharp-crypto/src/crypto/tls/UseSrtpData.cs new file mode 100644 index 0000000..fe8f8ac --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/UseSrtpData.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /** + * RFC 5764 4.1.1 + */ + public class UseSrtpData + { + protected readonly int[] mProtectionProfiles; + protected readonly byte[] mMki; + + /** + * @param protectionProfiles see {@link SrtpProtectionProfile} for valid constants. + * @param mki valid lengths from 0 to 255. + */ + public UseSrtpData(int[] protectionProfiles, byte[] mki) + { + if (protectionProfiles == null || protectionProfiles.Length < 1 + || protectionProfiles.Length >= (1 << 15)) + { + throw new ArgumentException("must have length from 1 to (2^15 - 1)", "protectionProfiles"); + } + + if (mki == null) + { + mki = TlsUtilities.EmptyBytes; + } + else if (mki.Length > 255) + { + throw new ArgumentException("cannot be longer than 255 bytes", "mki"); + } + + this.mProtectionProfiles = protectionProfiles; + this.mMki = mki; + } + + /** + * @return see {@link SrtpProtectionProfile} for valid constants. + */ + public virtual int[] ProtectionProfiles + { + get { return mProtectionProfiles; } + } + + /** + * @return valid lengths from 0 to 255. + */ + public virtual byte[] Mki + { + get { return mMki; } + } + } +} diff --git a/bc-sharp-crypto/src/crypto/tls/UserMappingType.cs b/bc-sharp-crypto/src/crypto/tls/UserMappingType.cs new file mode 100644 index 0000000..6cff517 --- /dev/null +++ b/bc-sharp-crypto/src/crypto/tls/UserMappingType.cs @@ -0,0 +1,13 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /// RFC 4681 + public abstract class UserMappingType + { + /* + * RFC 4681 + */ + public const byte upn_domain_hint = 64; + } +} diff --git a/bc-sharp-crypto/src/crypto/util/Pack.cs b/bc-sharp-crypto/src/crypto/util/Pack.cs new file mode 100644 index 0000000..1b94fee --- /dev/null +++ b/bc-sharp-crypto/src/crypto/util/Pack.cs @@ -0,0 +1,345 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Utilities +{ + internal sealed class Pack + { + private Pack() + { + } + + internal static void UInt16_To_BE(ushort n, byte[] bs) + { + bs[0] = (byte)(n >> 8); + bs[1] = (byte)(n); + } + + internal static void UInt16_To_BE(ushort n, byte[] bs, int off) + { + bs[off] = (byte)(n >> 8); + bs[off + 1] = (byte)(n); + } + + internal static ushort BE_To_UInt16(byte[] bs) + { + uint n = (uint)bs[0] << 8 + | (uint)bs[1]; + return (ushort)n; + } + + internal static ushort BE_To_UInt16(byte[] bs, int off) + { + uint n = (uint)bs[off] << 8 + | (uint)bs[off + 1]; + return (ushort)n; + } + + internal static byte[] UInt32_To_BE(uint n) + { + byte[] bs = new byte[4]; + UInt32_To_BE(n, bs, 0); + return bs; + } + + internal static void UInt32_To_BE(uint n, byte[] bs) + { + bs[0] = (byte)(n >> 24); + bs[1] = (byte)(n >> 16); + bs[2] = (byte)(n >> 8); + bs[3] = (byte)(n); + } + + internal static void UInt32_To_BE(uint n, byte[] bs, int off) + { + bs[off] = (byte)(n >> 24); + bs[off + 1] = (byte)(n >> 16); + bs[off + 2] = (byte)(n >> 8); + bs[off + 3] = (byte)(n); + } + + internal static byte[] UInt32_To_BE(uint[] ns) + { + byte[] bs = new byte[4 * ns.Length]; + UInt32_To_BE(ns, bs, 0); + return bs; + } + + internal static void UInt32_To_BE(uint[] ns, byte[] bs, int off) + { + for (int i = 0; i < ns.Length; ++i) + { + UInt32_To_BE(ns[i], bs, off); + off += 4; + } + } + + internal static uint BE_To_UInt32(byte[] bs) + { + return (uint)bs[0] << 24 + | (uint)bs[1] << 16 + | (uint)bs[2] << 8 + | (uint)bs[3]; + } + + internal static uint BE_To_UInt32(byte[] bs, int off) + { + return (uint)bs[off] << 24 + | (uint)bs[off + 1] << 16 + | (uint)bs[off + 2] << 8 + | (uint)bs[off + 3]; + } + + internal static void BE_To_UInt32(byte[] bs, int off, uint[] ns) + { + for (int i = 0; i < ns.Length; ++i) + { + ns[i] = BE_To_UInt32(bs, off); + off += 4; + } + } + + internal static byte[] UInt64_To_BE(ulong n) + { + byte[] bs = new byte[8]; + UInt64_To_BE(n, bs, 0); + return bs; + } + + internal static void UInt64_To_BE(ulong n, byte[] bs) + { + UInt32_To_BE((uint)(n >> 32), bs); + UInt32_To_BE((uint)(n), bs, 4); + } + + internal static void UInt64_To_BE(ulong n, byte[] bs, int off) + { + UInt32_To_BE((uint)(n >> 32), bs, off); + UInt32_To_BE((uint)(n), bs, off + 4); + } + + internal static byte[] UInt64_To_BE(ulong[] ns) + { + byte[] bs = new byte[8 * ns.Length]; + UInt64_To_BE(ns, bs, 0); + return bs; + } + + internal static void UInt64_To_BE(ulong[] ns, byte[] bs, int off) + { + for (int i = 0; i < ns.Length; ++i) + { + UInt64_To_BE(ns[i], bs, off); + off += 8; + } + } + + internal static ulong BE_To_UInt64(byte[] bs) + { + uint hi = BE_To_UInt32(bs); + uint lo = BE_To_UInt32(bs, 4); + return ((ulong)hi << 32) | (ulong)lo; + } + + internal static ulong BE_To_UInt64(byte[] bs, int off) + { + uint hi = BE_To_UInt32(bs, off); + uint lo = BE_To_UInt32(bs, off + 4); + return ((ulong)hi << 32) | (ulong)lo; + } + + internal static void BE_To_UInt64(byte[] bs, int off, ulong[] ns) + { + for (int i = 0; i < ns.Length; ++i) + { + ns[i] = BE_To_UInt64(bs, off); + off += 8; + } + } + + internal static void UInt16_To_LE(ushort n, byte[] bs) + { + bs[0] = (byte)(n); + bs[1] = (byte)(n >> 8); + } + + internal static void UInt16_To_LE(ushort n, byte[] bs, int off) + { + bs[off] = (byte)(n); + bs[off + 1] = (byte)(n >> 8); + } + + internal static ushort LE_To_UInt16(byte[] bs) + { + uint n = (uint)bs[0] + | (uint)bs[1] << 8; + return (ushort)n; + } + + internal static ushort LE_To_UInt16(byte[] bs, int off) + { + uint n = (uint)bs[off] + | (uint)bs[off + 1] << 8; + return (ushort)n; + } + + internal static byte[] UInt32_To_LE(uint n) + { + byte[] bs = new byte[4]; + UInt32_To_LE(n, bs, 0); + return bs; + } + + internal static void UInt32_To_LE(uint n, byte[] bs) + { + bs[0] = (byte)(n); + bs[1] = (byte)(n >> 8); + bs[2] = (byte)(n >> 16); + bs[3] = (byte)(n >> 24); + } + + internal static void UInt32_To_LE(uint n, byte[] bs, int off) + { + bs[off] = (byte)(n); + bs[off + 1] = (byte)(n >> 8); + bs[off + 2] = (byte)(n >> 16); + bs[off + 3] = (byte)(n >> 24); + } + + internal static byte[] UInt32_To_LE(uint[] ns) + { + byte[] bs = new byte[4 * ns.Length]; + UInt32_To_LE(ns, bs, 0); + return bs; + } + + internal static void UInt32_To_LE(uint[] ns, byte[] bs, int off) + { + for (int i = 0; i < ns.Length; ++i) + { + UInt32_To_LE(ns[i], bs, off); + off += 4; + } + } + + internal static uint LE_To_UInt32(byte[] bs) + { + return (uint)bs[0] + | (uint)bs[1] << 8 + | (uint)bs[2] << 16 + | (uint)bs[3] << 24; + } + + internal static uint LE_To_UInt32(byte[] bs, int off) + { + return (uint)bs[off] + | (uint)bs[off + 1] << 8 + | (uint)bs[off + 2] << 16 + | (uint)bs[off + 3] << 24; + } + + internal static void LE_To_UInt32(byte[] bs, int off, uint[] ns) + { + for (int i = 0; i < ns.Length; ++i) + { + ns[i] = LE_To_UInt32(bs, off); + off += 4; + } + } + + internal static void LE_To_UInt32(byte[] bs, int bOff, uint[] ns, int nOff, int count) + { + for (int i = 0; i < count; ++i) + { + ns[nOff + i] = LE_To_UInt32(bs, bOff); + bOff += 4; + } + } + + internal static uint[] LE_To_UInt32(byte[] bs, int off, int count) + { + uint[] ns = new uint[count]; + for (int i = 0; i < ns.Length; ++i) + { + ns[i] = LE_To_UInt32(bs, off); + off += 4; + } + return ns; + } + + internal static byte[] UInt64_To_LE(ulong n) + { + byte[] bs = new byte[8]; + UInt64_To_LE(n, bs, 0); + return bs; + } + + internal static void UInt64_To_LE(ulong n, byte[] bs) + { + UInt32_To_LE((uint)(n), bs); + UInt32_To_LE((uint)(n >> 32), bs, 4); + } + + internal static void UInt64_To_LE(ulong n, byte[] bs, int off) + { + UInt32_To_LE((uint)(n), bs, off); + UInt32_To_LE((uint)(n >> 32), bs, off + 4); + } + + internal static byte[] UInt64_To_LE(ulong[] ns) + { + byte[] bs = new byte[8 * ns.Length]; + UInt64_To_LE(ns, bs, 0); + return bs; + } + + internal static void UInt64_To_LE(ulong[] ns, byte[] bs, int off) + { + for (int i = 0; i < ns.Length; ++i) + { + UInt64_To_LE(ns[i], bs, off); + off += 8; + } + } + + internal static void UInt64_To_LE(ulong[] ns, int nsOff, int nsLen, byte[] bs, int bsOff) + { + for (int i = 0; i < nsLen; ++i) + { + UInt64_To_LE(ns[nsOff + i], bs, bsOff); + bsOff += 8; + } + } + + internal static ulong LE_To_UInt64(byte[] bs) + { + uint lo = LE_To_UInt32(bs); + uint hi = LE_To_UInt32(bs, 4); + return ((ulong)hi << 32) | (ulong)lo; + } + + internal static ulong LE_To_UInt64(byte[] bs, int off) + { + uint lo = LE_To_UInt32(bs, off); + uint hi = LE_To_UInt32(bs, off + 4); + return ((ulong)hi << 32) | (ulong)lo; + } + + internal static void LE_To_UInt64(byte[] bs, int off, ulong[] ns) + { + for (int i = 0; i < ns.Length; ++i) + { + ns[i] = LE_To_UInt64(bs, off); + off += 8; + } + } + + internal static void LE_To_UInt64(byte[] bs, int bsOff, ulong[] ns, int nsOff, int nsLen) + { + for (int i = 0; i < nsLen; ++i) + { + ns[nsOff + i] = LE_To_UInt64(bs, bsOff); + bsOff += 8; + } + } + } +} diff --git a/bc-sharp-crypto/src/math/BigInteger.cs b/bc-sharp-crypto/src/math/BigInteger.cs new file mode 100644 index 0000000..b35701f --- /dev/null +++ b/bc-sharp-crypto/src/math/BigInteger.cs @@ -0,0 +1,3592 @@ +using System; +using System.Collections; +using System.Diagnostics; +using System.Globalization; +using System.Text; + +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Math +{ +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT || PORTABLE) + [Serializable] +#endif + public class BigInteger + { + // The first few odd primes + /* + 3 5 7 11 13 17 19 23 29 + 31 37 41 43 47 53 59 61 67 71 + 73 79 83 89 97 101 103 107 109 113 + 127 131 137 139 149 151 157 163 167 173 + 179 181 191 193 197 199 211 223 227 229 + 233 239 241 251 257 263 269 271 277 281 + 283 293 307 311 313 317 331 337 347 349 + 353 359 367 373 379 383 389 397 401 409 + 419 421 431 433 439 443 449 457 461 463 + 467 479 487 491 499 503 509 521 523 541 + 547 557 563 569 571 577 587 593 599 601 + 607 613 617 619 631 641 643 647 653 659 + 661 673 677 683 691 701 709 719 727 733 + 739 743 751 757 761 769 773 787 797 809 + 811 821 823 827 829 839 853 857 859 863 + 877 881 883 887 907 911 919 929 937 941 + 947 953 967 971 977 983 991 997 1009 + 1013 1019 1021 1031 1033 1039 1049 1051 + 1061 1063 1069 1087 1091 1093 1097 1103 + 1109 1117 1123 1129 1151 1153 1163 1171 + 1181 1187 1193 1201 1213 1217 1223 1229 + 1231 1237 1249 1259 1277 1279 1283 1289 + */ + + // Each list has a product < 2^31 + internal static readonly int[][] primeLists = new int[][] + { + new int[]{ 3, 5, 7, 11, 13, 17, 19, 23 }, + new int[]{ 29, 31, 37, 41, 43 }, + new int[]{ 47, 53, 59, 61, 67 }, + new int[]{ 71, 73, 79, 83 }, + new int[]{ 89, 97, 101, 103 }, + + new int[]{ 107, 109, 113, 127 }, + new int[]{ 131, 137, 139, 149 }, + new int[]{ 151, 157, 163, 167 }, + new int[]{ 173, 179, 181, 191 }, + new int[]{ 193, 197, 199, 211 }, + + new int[]{ 223, 227, 229 }, + new int[]{ 233, 239, 241 }, + new int[]{ 251, 257, 263 }, + new int[]{ 269, 271, 277 }, + new int[]{ 281, 283, 293 }, + + new int[]{ 307, 311, 313 }, + new int[]{ 317, 331, 337 }, + new int[]{ 347, 349, 353 }, + new int[]{ 359, 367, 373 }, + new int[]{ 379, 383, 389 }, + + new int[]{ 397, 401, 409 }, + new int[]{ 419, 421, 431 }, + new int[]{ 433, 439, 443 }, + new int[]{ 449, 457, 461 }, + new int[]{ 463, 467, 479 }, + + new int[]{ 487, 491, 499 }, + new int[]{ 503, 509, 521 }, + new int[]{ 523, 541, 547 }, + new int[]{ 557, 563, 569 }, + new int[]{ 571, 577, 587 }, + + new int[]{ 593, 599, 601 }, + new int[]{ 607, 613, 617 }, + new int[]{ 619, 631, 641 }, + new int[]{ 643, 647, 653 }, + new int[]{ 659, 661, 673 }, + + new int[]{ 677, 683, 691 }, + new int[]{ 701, 709, 719 }, + new int[]{ 727, 733, 739 }, + new int[]{ 743, 751, 757 }, + new int[]{ 761, 769, 773 }, + + new int[]{ 787, 797, 809 }, + new int[]{ 811, 821, 823 }, + new int[]{ 827, 829, 839 }, + new int[]{ 853, 857, 859 }, + new int[]{ 863, 877, 881 }, + + new int[]{ 883, 887, 907 }, + new int[]{ 911, 919, 929 }, + new int[]{ 937, 941, 947 }, + new int[]{ 953, 967, 971 }, + new int[]{ 977, 983, 991 }, + + new int[]{ 997, 1009, 1013 }, + new int[]{ 1019, 1021, 1031 }, + new int[]{ 1033, 1039, 1049 }, + new int[]{ 1051, 1061, 1063 }, + new int[]{ 1069, 1087, 1091 }, + + new int[]{ 1093, 1097, 1103 }, + new int[]{ 1109, 1117, 1123 }, + new int[]{ 1129, 1151, 1153 }, + new int[]{ 1163, 1171, 1181 }, + new int[]{ 1187, 1193, 1201 }, + + new int[]{ 1213, 1217, 1223 }, + new int[]{ 1229, 1231, 1237 }, + new int[]{ 1249, 1259, 1277 }, + new int[]{ 1279, 1283, 1289 }, + }; + + internal static readonly int[] primeProducts; + + private const long IMASK = 0xFFFFFFFFL; + private const ulong UIMASK = 0xFFFFFFFFUL; + + private static readonly int[] ZeroMagnitude = new int[0]; + private static readonly byte[] ZeroEncoding = new byte[0]; + + private static readonly BigInteger[] SMALL_CONSTANTS = new BigInteger[17]; + public static readonly BigInteger Zero; + public static readonly BigInteger One; + public static readonly BigInteger Two; + public static readonly BigInteger Three; + public static readonly BigInteger Ten; + + //private readonly static byte[] BitCountTable = + //{ + // 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, + // 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + // 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + // 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + // 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + // 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + // 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + // 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + // 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + // 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + // 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + // 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + // 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + // 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + // 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + // 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8 + //}; + + private readonly static byte[] BitLengthTable = + { + 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 + }; + + // TODO Parse radix-2 64 bits at a time and radix-8 63 bits at a time + private const int chunk2 = 1, chunk8 = 1, chunk10 = 19, chunk16 = 16; + private static readonly BigInteger radix2, radix2E, radix8, radix8E, radix10, radix10E, radix16, radix16E; + + private static readonly SecureRandom RandomSource = new SecureRandom(); + + /* + * These are the threshold bit-lengths (of an exponent) where we increase the window size. + * They are calculated according to the expected savings in multiplications. + * Some squares will also be saved on average, but we offset these against the extra storage costs. + */ + private static readonly int[] ExpWindowThresholds = { 7, 25, 81, 241, 673, 1793, 4609, Int32.MaxValue }; + + private const int BitsPerByte = 8; + private const int BitsPerInt = 32; + private const int BytesPerInt = 4; + + static BigInteger() + { + Zero = new BigInteger(0, ZeroMagnitude, false); + Zero.nBits = 0; Zero.nBitLength = 0; + + SMALL_CONSTANTS[0] = Zero; + for (uint i = 1; i < SMALL_CONSTANTS.Length; ++i) + { + SMALL_CONSTANTS[i] = CreateUValueOf(i); + } + + One = SMALL_CONSTANTS[1]; + Two = SMALL_CONSTANTS[2]; + Three = SMALL_CONSTANTS[3]; + Ten = SMALL_CONSTANTS[10]; + + radix2 = ValueOf(2); + radix2E = radix2.Pow(chunk2); + + radix8 = ValueOf(8); + radix8E = radix8.Pow(chunk8); + + radix10 = ValueOf(10); + radix10E = radix10.Pow(chunk10); + + radix16 = ValueOf(16); + radix16E = radix16.Pow(chunk16); + + primeProducts = new int[primeLists.Length]; + + for (int i = 0; i < primeLists.Length; ++i) + { + int[] primeList = primeLists[i]; + int product = primeList[0]; + for (int j = 1; j < primeList.Length; ++j) + { + product *= primeList[j]; + } + primeProducts[i] = product; + } + } + + private int[] magnitude; // array of ints with [0] being the most significant + private int sign; // -1 means -ve; +1 means +ve; 0 means 0; + private int nBits = -1; // cache BitCount() value + private int nBitLength = -1; // cache BitLength() value + private int mQuote = 0; // -m^(-1) mod b, b = 2^32 (see Montgomery mult.), 0 when uninitialised + + private static int GetByteLength( + int nBits) + { + return (nBits + BitsPerByte - 1) / BitsPerByte; + } + + internal static BigInteger Arbitrary(int sizeInBits) + { + return new BigInteger(sizeInBits, RandomSource); + } + + private BigInteger( + int signum, + int[] mag, + bool checkMag) + { + if (checkMag) + { + int i = 0; + while (i < mag.Length && mag[i] == 0) + { + ++i; + } + + if (i == mag.Length) + { + this.sign = 0; + this.magnitude = ZeroMagnitude; + } + else + { + this.sign = signum; + + if (i == 0) + { + this.magnitude = mag; + } + else + { + // strip leading 0 words + this.magnitude = new int[mag.Length - i]; + Array.Copy(mag, i, this.magnitude, 0, this.magnitude.Length); + } + } + } + else + { + this.sign = signum; + this.magnitude = mag; + } + } + + public BigInteger( + string value) + : this(value, 10) + { + } + + public BigInteger( + string str, + int radix) + { + if (str.Length == 0) + throw new FormatException("Zero length BigInteger"); + + NumberStyles style; + int chunk; + BigInteger r; + BigInteger rE; + + switch (radix) + { + case 2: + // Is there anyway to restrict to binary digits? + style = NumberStyles.Integer; + chunk = chunk2; + r = radix2; + rE = radix2E; + break; + case 8: + // Is there anyway to restrict to octal digits? + style = NumberStyles.Integer; + chunk = chunk8; + r = radix8; + rE = radix8E; + break; + case 10: + // This style seems to handle spaces and minus sign already (our processing redundant?) + style = NumberStyles.Integer; + chunk = chunk10; + r = radix10; + rE = radix10E; + break; + case 16: + // TODO Should this be HexNumber? + style = NumberStyles.AllowHexSpecifier; + chunk = chunk16; + r = radix16; + rE = radix16E; + break; + default: + throw new FormatException("Only bases 2, 8, 10, or 16 allowed"); + } + + + int index = 0; + sign = 1; + + if (str[0] == '-') + { + if (str.Length == 1) + throw new FormatException("Zero length BigInteger"); + + sign = -1; + index = 1; + } + + // strip leading zeros from the string str + while (index < str.Length && Int32.Parse(str[index].ToString(), style) == 0) + { + index++; + } + + if (index >= str.Length) + { + // zero value - we're done + sign = 0; + magnitude = ZeroMagnitude; + return; + } + + ////// + // could we work out the max number of ints required to store + // str.Length digits in the given base, then allocate that + // storage in one hit?, then Generate the magnitude in one hit too? + ////// + + BigInteger b = Zero; + + + int next = index + chunk; + + if (next <= str.Length) + { + do + { + string s = str.Substring(index, chunk); + ulong i = ulong.Parse(s, style); + BigInteger bi = CreateUValueOf(i); + + switch (radix) + { + case 2: + // TODO Need this because we are parsing in radix 10 above + if (i >= 2) + throw new FormatException("Bad character in radix 2 string: " + s); + + // TODO Parse 64 bits at a time + b = b.ShiftLeft(1); + break; + case 8: + // TODO Need this because we are parsing in radix 10 above + if (i >= 8) + throw new FormatException("Bad character in radix 8 string: " + s); + + // TODO Parse 63 bits at a time + b = b.ShiftLeft(3); + break; + case 16: + b = b.ShiftLeft(64); + break; + default: + b = b.Multiply(rE); + break; + } + + b = b.Add(bi); + + index = next; + next += chunk; + } + while (next <= str.Length); + } + + if (index < str.Length) + { + string s = str.Substring(index); + ulong i = ulong.Parse(s, style); + BigInteger bi = CreateUValueOf(i); + + if (b.sign > 0) + { + if (radix == 2) + { + // NB: Can't reach here since we are parsing one char at a time + Debug.Assert(false); + + // TODO Parse all bits at once +// b = b.ShiftLeft(s.Length); + } + else if (radix == 8) + { + // NB: Can't reach here since we are parsing one char at a time + Debug.Assert(false); + + // TODO Parse all bits at once +// b = b.ShiftLeft(s.Length * 3); + } + else if (radix == 16) + { + b = b.ShiftLeft(s.Length << 2); + } + else + { + b = b.Multiply(r.Pow(s.Length)); + } + + b = b.Add(bi); + } + else + { + b = bi; + } + } + + // Note: This is the previous (slower) algorithm +// while (index < value.Length) +// { +// char c = value[index]; +// string s = c.ToString(); +// int i = Int32.Parse(s, style); +// +// b = b.Multiply(r).Add(ValueOf(i)); +// index++; +// } + + magnitude = b.magnitude; + } + + public BigInteger( + byte[] bytes) + : this(bytes, 0, bytes.Length) + { + } + + public BigInteger( + byte[] bytes, + int offset, + int length) + { + if (length == 0) + throw new FormatException("Zero length BigInteger"); + + // TODO Move this processing into MakeMagnitude (provide sign argument) + if ((sbyte)bytes[offset] < 0) + { + this.sign = -1; + + int end = offset + length; + + int iBval; + // strip leading sign bytes + for (iBval = offset; iBval < end && ((sbyte)bytes[iBval] == -1); iBval++) + { + } + + if (iBval >= end) + { + this.magnitude = One.magnitude; + } + else + { + int numBytes = end - iBval; + byte[] inverse = new byte[numBytes]; + + int index = 0; + while (index < numBytes) + { + inverse[index++] = (byte)~bytes[iBval++]; + } + + Debug.Assert(iBval == end); + + while (inverse[--index] == byte.MaxValue) + { + inverse[index] = byte.MinValue; + } + + inverse[index]++; + + this.magnitude = MakeMagnitude(inverse, 0, inverse.Length); + } + } + else + { + // strip leading zero bytes and return magnitude bytes + this.magnitude = MakeMagnitude(bytes, offset, length); + this.sign = this.magnitude.Length > 0 ? 1 : 0; + } + } + + private static int[] MakeMagnitude( + byte[] bytes, + int offset, + int length) + { + int end = offset + length; + + // strip leading zeros + int firstSignificant; + for (firstSignificant = offset; firstSignificant < end + && bytes[firstSignificant] == 0; firstSignificant++) + { + } + + if (firstSignificant >= end) + { + return ZeroMagnitude; + } + + int nInts = (end - firstSignificant + 3) / BytesPerInt; + int bCount = (end - firstSignificant) % BytesPerInt; + if (bCount == 0) + { + bCount = BytesPerInt; + } + + if (nInts < 1) + { + return ZeroMagnitude; + } + + int[] mag = new int[nInts]; + + int v = 0; + int magnitudeIndex = 0; + for (int i = firstSignificant; i < end; ++i) + { + v <<= 8; + v |= bytes[i] & 0xff; + bCount--; + if (bCount <= 0) + { + mag[magnitudeIndex] = v; + magnitudeIndex++; + bCount = BytesPerInt; + v = 0; + } + } + + if (magnitudeIndex < mag.Length) + { + mag[magnitudeIndex] = v; + } + + return mag; + } + + public BigInteger( + int sign, + byte[] bytes) + : this(sign, bytes, 0, bytes.Length) + { + } + + public BigInteger( + int sign, + byte[] bytes, + int offset, + int length) + { + if (sign < -1 || sign > 1) + throw new FormatException("Invalid sign value"); + + if (sign == 0) + { + this.sign = 0; + this.magnitude = ZeroMagnitude; + } + else + { + // copy bytes + this.magnitude = MakeMagnitude(bytes, offset, length); + this.sign = this.magnitude.Length < 1 ? 0 : sign; + } + } + + public BigInteger( + int sizeInBits, + Random random) + { + if (sizeInBits < 0) + throw new ArgumentException("sizeInBits must be non-negative"); + + this.nBits = -1; + this.nBitLength = -1; + + if (sizeInBits == 0) + { + this.sign = 0; + this.magnitude = ZeroMagnitude; + return; + } + + int nBytes = GetByteLength(sizeInBits); + byte[] b = new byte[nBytes]; + random.NextBytes(b); + + // strip off any excess bits in the MSB + int xBits = BitsPerByte * nBytes - sizeInBits; + b[0] &= (byte)(255U >> xBits); + + this.magnitude = MakeMagnitude(b, 0, b.Length); + this.sign = this.magnitude.Length < 1 ? 0 : 1; + } + + public BigInteger( + int bitLength, + int certainty, + Random random) + { + if (bitLength < 2) + throw new ArithmeticException("bitLength < 2"); + + this.sign = 1; + this.nBitLength = bitLength; + + if (bitLength == 2) + { + this.magnitude = random.Next(2) == 0 + ? Two.magnitude + : Three.magnitude; + return; + } + + int nBytes = GetByteLength(bitLength); + byte[] b = new byte[nBytes]; + + int xBits = BitsPerByte * nBytes - bitLength; + byte mask = (byte)(255U >> xBits); + byte lead = (byte)(1 << (7 - xBits)); + + for (;;) + { + random.NextBytes(b); + + // strip off any excess bits in the MSB + b[0] &= mask; + + // ensure the leading bit is 1 (to meet the strength requirement) + b[0] |= lead; + + // ensure the trailing bit is 1 (i.e. must be odd) + b[nBytes - 1] |= 1; + + this.magnitude = MakeMagnitude(b, 0, b.Length); + this.nBits = -1; + this.mQuote = 0; + + if (certainty < 1) + break; + + if (CheckProbablePrime(certainty, random, true)) + break; + + for (int j = 1; j < (magnitude.Length - 1); ++j) + { + this.magnitude[j] ^= random.Next(); + + if (CheckProbablePrime(certainty, random, true)) + return; + } + } + } + + public BigInteger Abs() + { + return sign >= 0 ? this : Negate(); + } + + /** + * return a = a + b - b preserved. + */ + private static int[] AddMagnitudes( + int[] a, + int[] b) + { + int tI = a.Length - 1; + int vI = b.Length - 1; + long m = 0; + + while (vI >= 0) + { + m += ((long)(uint)a[tI] + (long)(uint)b[vI--]); + a[tI--] = (int)m; + m = (long)((ulong)m >> 32); + } + + if (m != 0) + { + while (tI >= 0 && ++a[tI--] == 0) + { + } + } + + return a; + } + + public BigInteger Add( + BigInteger value) + { + if (this.sign == 0) + return value; + + if (this.sign != value.sign) + { + if (value.sign == 0) + return this; + + if (value.sign < 0) + return Subtract(value.Negate()); + + return value.Subtract(Negate()); + } + + return AddToMagnitude(value.magnitude); + } + + private BigInteger AddToMagnitude( + int[] magToAdd) + { + int[] big, small; + if (this.magnitude.Length < magToAdd.Length) + { + big = magToAdd; + small = this.magnitude; + } + else + { + big = this.magnitude; + small = magToAdd; + } + + // Conservatively avoid over-allocation when no overflow possible + uint limit = uint.MaxValue; + if (big.Length == small.Length) + limit -= (uint) small[0]; + + bool possibleOverflow = (uint) big[0] >= limit; + + int[] bigCopy; + if (possibleOverflow) + { + bigCopy = new int[big.Length + 1]; + big.CopyTo(bigCopy, 1); + } + else + { + bigCopy = (int[]) big.Clone(); + } + + bigCopy = AddMagnitudes(bigCopy, small); + + return new BigInteger(this.sign, bigCopy, possibleOverflow); + } + + public BigInteger And( + BigInteger value) + { + if (this.sign == 0 || value.sign == 0) + { + return Zero; + } + + int[] aMag = this.sign > 0 + ? this.magnitude + : Add(One).magnitude; + + int[] bMag = value.sign > 0 + ? value.magnitude + : value.Add(One).magnitude; + + bool resultNeg = sign < 0 && value.sign < 0; + int resultLength = System.Math.Max(aMag.Length, bMag.Length); + int[] resultMag = new int[resultLength]; + + int aStart = resultMag.Length - aMag.Length; + int bStart = resultMag.Length - bMag.Length; + + for (int i = 0; i < resultMag.Length; ++i) + { + int aWord = i >= aStart ? aMag[i - aStart] : 0; + int bWord = i >= bStart ? bMag[i - bStart] : 0; + + if (this.sign < 0) + { + aWord = ~aWord; + } + + if (value.sign < 0) + { + bWord = ~bWord; + } + + resultMag[i] = aWord & bWord; + + if (resultNeg) + { + resultMag[i] = ~resultMag[i]; + } + } + + BigInteger result = new BigInteger(1, resultMag, true); + + // TODO Optimise this case + if (resultNeg) + { + result = result.Not(); + } + + return result; + } + + public BigInteger AndNot( + BigInteger val) + { + return And(val.Not()); + } + + public int BitCount + { + get + { + if (nBits == -1) + { + if (sign < 0) + { + // TODO Optimise this case + nBits = Not().BitCount; + } + else + { + int sum = 0; + for (int i = 0; i < magnitude.Length; ++i) + { + sum += BitCnt(magnitude[i]); + } + nBits = sum; + } + } + + return nBits; + } + } + + public static int BitCnt(int i) + { + uint u = (uint)i; + u = u - ((u >> 1) & 0x55555555); + u = (u & 0x33333333) + ((u >> 2) & 0x33333333); + u = (u + (u >> 4)) & 0x0f0f0f0f; + u += (u >> 8); + u += (u >> 16); + u &= 0x3f; + return (int)u; + } + + private static int CalcBitLength(int sign, int indx, int[] mag) + { + for (;;) + { + if (indx >= mag.Length) + return 0; + + if (mag[indx] != 0) + break; + + ++indx; + } + + // bit length for everything after the first int + int bitLength = 32 * ((mag.Length - indx) - 1); + + // and determine bitlength of first int + int firstMag = mag[indx]; + bitLength += BitLen(firstMag); + + // Check for negative powers of two + if (sign < 0 && ((firstMag & -firstMag) == firstMag)) + { + do + { + if (++indx >= mag.Length) + { + --bitLength; + break; + } + } + while (mag[indx] == 0); + } + + return bitLength; + } + + public int BitLength + { + get + { + if (nBitLength == -1) + { + nBitLength = sign == 0 + ? 0 + : CalcBitLength(sign, 0, magnitude); + } + + return nBitLength; + } + } + + // + // BitLen(value) is the number of bits in value. + // + internal static int BitLen(int w) + { + uint v = (uint)w; + uint t = v >> 24; + if (t != 0) + return 24 + BitLengthTable[t]; + t = v >> 16; + if (t != 0) + return 16 + BitLengthTable[t]; + t = v >> 8; + if (t != 0) + return 8 + BitLengthTable[t]; + return BitLengthTable[v]; + } + + private bool QuickPow2Check() + { + return sign > 0 && nBits == 1; + } + + public int CompareTo( + object obj) + { + return CompareTo((BigInteger)obj); + } + + /** + * unsigned comparison on two arrays - note the arrays may + * start with leading zeros. + */ + private static int CompareTo( + int xIndx, + int[] x, + int yIndx, + int[] y) + { + while (xIndx != x.Length && x[xIndx] == 0) + { + xIndx++; + } + + while (yIndx != y.Length && y[yIndx] == 0) + { + yIndx++; + } + + return CompareNoLeadingZeroes(xIndx, x, yIndx, y); + } + + private static int CompareNoLeadingZeroes( + int xIndx, + int[] x, + int yIndx, + int[] y) + { + int diff = (x.Length - y.Length) - (xIndx - yIndx); + + if (diff != 0) + { + return diff < 0 ? -1 : 1; + } + + // lengths of magnitudes the same, test the magnitude values + + while (xIndx < x.Length) + { + uint v1 = (uint)x[xIndx++]; + uint v2 = (uint)y[yIndx++]; + + if (v1 != v2) + return v1 < v2 ? -1 : 1; + } + + return 0; + } + + public int CompareTo( + BigInteger value) + { + return sign < value.sign ? -1 + : sign > value.sign ? 1 + : sign == 0 ? 0 + : sign * CompareNoLeadingZeroes(0, magnitude, 0, value.magnitude); + } + + /** + * return z = x / y - done in place (z value preserved, x contains the + * remainder) + */ + private int[] Divide( + int[] x, + int[] y) + { + int xStart = 0; + while (xStart < x.Length && x[xStart] == 0) + { + ++xStart; + } + + int yStart = 0; + while (yStart < y.Length && y[yStart] == 0) + { + ++yStart; + } + + Debug.Assert(yStart < y.Length); + + int xyCmp = CompareNoLeadingZeroes(xStart, x, yStart, y); + int[] count; + + if (xyCmp > 0) + { + int yBitLength = CalcBitLength(1, yStart, y); + int xBitLength = CalcBitLength(1, xStart, x); + int shift = xBitLength - yBitLength; + + int[] iCount; + int iCountStart = 0; + + int[] c; + int cStart = 0; + int cBitLength = yBitLength; + if (shift > 0) + { +// iCount = ShiftLeft(One.magnitude, shift); + iCount = new int[(shift >> 5) + 1]; + iCount[0] = 1 << (shift % 32); + + c = ShiftLeft(y, shift); + cBitLength += shift; + } + else + { + iCount = new int[] { 1 }; + + int len = y.Length - yStart; + c = new int[len]; + Array.Copy(y, yStart, c, 0, len); + } + + count = new int[iCount.Length]; + + for (;;) + { + if (cBitLength < xBitLength + || CompareNoLeadingZeroes(xStart, x, cStart, c) >= 0) + { + Subtract(xStart, x, cStart, c); + AddMagnitudes(count, iCount); + + while (x[xStart] == 0) + { + if (++xStart == x.Length) + return count; + } + + //xBitLength = CalcBitLength(xStart, x); + xBitLength = 32 * (x.Length - xStart - 1) + BitLen(x[xStart]); + + if (xBitLength <= yBitLength) + { + if (xBitLength < yBitLength) + return count; + + xyCmp = CompareNoLeadingZeroes(xStart, x, yStart, y); + + if (xyCmp <= 0) + break; + } + } + + shift = cBitLength - xBitLength; + + // NB: The case where c[cStart] is 1-bit is harmless + if (shift == 1) + { + uint firstC = (uint) c[cStart] >> 1; + uint firstX = (uint) x[xStart]; + if (firstC > firstX) + ++shift; + } + + if (shift < 2) + { + ShiftRightOneInPlace(cStart, c); + --cBitLength; + ShiftRightOneInPlace(iCountStart, iCount); + } + else + { + ShiftRightInPlace(cStart, c, shift); + cBitLength -= shift; + ShiftRightInPlace(iCountStart, iCount, shift); + } + + //cStart = c.Length - ((cBitLength + 31) / 32); + while (c[cStart] == 0) + { + ++cStart; + } + + while (iCount[iCountStart] == 0) + { + ++iCountStart; + } + } + } + else + { + count = new int[1]; + } + + if (xyCmp == 0) + { + AddMagnitudes(count, One.magnitude); + Array.Clear(x, xStart, x.Length - xStart); + } + + return count; + } + + public BigInteger Divide( + BigInteger val) + { + if (val.sign == 0) + throw new ArithmeticException("Division by zero error"); + + if (sign == 0) + return Zero; + + if (val.QuickPow2Check()) // val is power of two + { + BigInteger result = this.Abs().ShiftRight(val.Abs().BitLength - 1); + return val.sign == this.sign ? result : result.Negate(); + } + + int[] mag = (int[]) this.magnitude.Clone(); + + return new BigInteger(this.sign * val.sign, Divide(mag, val.magnitude), true); + } + + public BigInteger[] DivideAndRemainder( + BigInteger val) + { + if (val.sign == 0) + throw new ArithmeticException("Division by zero error"); + + BigInteger[] biggies = new BigInteger[2]; + + if (sign == 0) + { + biggies[0] = Zero; + biggies[1] = Zero; + } + else if (val.QuickPow2Check()) // val is power of two + { + int e = val.Abs().BitLength - 1; + BigInteger quotient = this.Abs().ShiftRight(e); + int[] remainder = this.LastNBits(e); + + biggies[0] = val.sign == this.sign ? quotient : quotient.Negate(); + biggies[1] = new BigInteger(this.sign, remainder, true); + } + else + { + int[] remainder = (int[]) this.magnitude.Clone(); + int[] quotient = Divide(remainder, val.magnitude); + + biggies[0] = new BigInteger(this.sign * val.sign, quotient, true); + biggies[1] = new BigInteger(this.sign, remainder, true); + } + + return biggies; + } + + public override bool Equals( + object obj) + { + if (obj == this) + return true; + + BigInteger biggie = obj as BigInteger; + if (biggie == null) + return false; + + return sign == biggie.sign && IsEqualMagnitude(biggie); + } + + private bool IsEqualMagnitude(BigInteger x) + { + int[] xMag = x.magnitude; + if (magnitude.Length != x.magnitude.Length) + return false; + for (int i = 0; i < magnitude.Length; i++) + { + if (magnitude[i] != x.magnitude[i]) + return false; + } + return true; + } + + public BigInteger Gcd( + BigInteger value) + { + if (value.sign == 0) + return Abs(); + + if (sign == 0) + return value.Abs(); + + BigInteger r; + BigInteger u = this; + BigInteger v = value; + + while (v.sign != 0) + { + r = u.Mod(v); + u = v; + v = r; + } + + return u; + } + + public override int GetHashCode() + { + int hc = magnitude.Length; + if (magnitude.Length > 0) + { + hc ^= magnitude[0]; + + if (magnitude.Length > 1) + { + hc ^= magnitude[magnitude.Length - 1]; + } + } + + return sign < 0 ? ~hc : hc; + } + + // TODO Make public? + private BigInteger Inc() + { + if (this.sign == 0) + return One; + + if (this.sign < 0) + return new BigInteger(-1, doSubBigLil(this.magnitude, One.magnitude), true); + + return AddToMagnitude(One.magnitude); + } + + public int IntValue + { + get + { + if (sign == 0) + return 0; + + int n = magnitude.Length; + + int v = magnitude[n - 1]; + + return sign < 0 ? -v : v; + } + } + + /** + * return whether or not a BigInteger is probably prime with a + * probability of 1 - (1/2)**certainty. + *

From Knuth Vol 2, pg 395.

+ */ + public bool IsProbablePrime(int certainty) + { + return IsProbablePrime(certainty, false); + } + + internal bool IsProbablePrime(int certainty, bool randomlySelected) + { + if (certainty <= 0) + return true; + + BigInteger n = Abs(); + + if (!n.TestBit(0)) + return n.Equals(Two); + + if (n.Equals(One)) + return false; + + return n.CheckProbablePrime(certainty, RandomSource, randomlySelected); + } + + private bool CheckProbablePrime(int certainty, Random random, bool randomlySelected) + { + Debug.Assert(certainty > 0); + Debug.Assert(CompareTo(Two) > 0); + Debug.Assert(TestBit(0)); + + + // Try to reduce the penalty for really small numbers + int numLists = System.Math.Min(BitLength - 1, primeLists.Length); + + for (int i = 0; i < numLists; ++i) + { + int test = Remainder(primeProducts[i]); + + int[] primeList = primeLists[i]; + for (int j = 0; j < primeList.Length; ++j) + { + int prime = primeList[j]; + int qRem = test % prime; + if (qRem == 0) + { + // We may find small numbers in the list + return BitLength < 16 && IntValue == prime; + } + } + } + + + // TODO Special case for < 10^16 (RabinMiller fixed list) +// if (BitLength < 30) +// { +// RabinMiller against 2, 3, 5, 7, 11, 13, 23 is sufficient +// } + + + // TODO Is it worth trying to create a hybrid of these two? + return RabinMillerTest(certainty, random, randomlySelected); +// return SolovayStrassenTest(certainty, random); + +// bool rbTest = RabinMillerTest(certainty, random); +// bool ssTest = SolovayStrassenTest(certainty, random); +// +// Debug.Assert(rbTest == ssTest); +// +// return rbTest; + } + + public bool RabinMillerTest(int certainty, Random random) + { + return RabinMillerTest(certainty, random, false); + } + + internal bool RabinMillerTest(int certainty, Random random, bool randomlySelected) + { + int bits = BitLength; + + Debug.Assert(certainty > 0); + Debug.Assert(bits > 2); + Debug.Assert(TestBit(0)); + + int iterations = ((certainty - 1) / 2) + 1; + if (randomlySelected) + { + int itersFor100Cert = bits >= 1024 ? 4 + : bits >= 512 ? 8 + : bits >= 256 ? 16 + : 50; + + if (certainty < 100) + { + iterations = System.Math.Min(itersFor100Cert, iterations); + } + else + { + iterations -= 50; + iterations += itersFor100Cert; + } + } + + // let n = 1 + d . 2^s + BigInteger n = this; + int s = n.GetLowestSetBitMaskFirst(-1 << 1); + Debug.Assert(s >= 1); + BigInteger r = n.ShiftRight(s); + + // NOTE: Avoid conversion to/from Montgomery form and check for R/-R as result instead + + BigInteger montRadix = One.ShiftLeft(32 * n.magnitude.Length).Remainder(n); + BigInteger minusMontRadix = n.Subtract(montRadix); + + do + { + BigInteger a; + do + { + a = new BigInteger(n.BitLength, random); + } + while (a.sign == 0 || a.CompareTo(n) >= 0 + || a.IsEqualMagnitude(montRadix) || a.IsEqualMagnitude(minusMontRadix)); + + BigInteger y = ModPowMonty(a, r, n, false); + + if (!y.Equals(montRadix)) + { + int j = 0; + while (!y.Equals(minusMontRadix)) + { + if (++j == s) + return false; + + y = ModPowMonty(y, Two, n, false); + + if (y.Equals(montRadix)) + return false; + } + } + } + while (--iterations > 0); + + return true; + } + +// private bool SolovayStrassenTest( +// int certainty, +// Random random) +// { +// Debug.Assert(certainty > 0); +// Debug.Assert(CompareTo(Two) > 0); +// Debug.Assert(TestBit(0)); +// +// BigInteger n = this; +// BigInteger nMinusOne = n.Subtract(One); +// BigInteger e = nMinusOne.ShiftRight(1); +// +// do +// { +// BigInteger a; +// do +// { +// a = new BigInteger(nBitLength, random); +// } +// // NB: Spec says 0 < x < n, but 1 is trivial +// while (a.CompareTo(One) <= 0 || a.CompareTo(n) >= 0); +// +// +// // TODO Check this is redundant given the way Jacobi() works? +//// if (!a.Gcd(n).Equals(One)) +//// return false; +// +// int x = Jacobi(a, n); +// +// if (x == 0) +// return false; +// +// BigInteger check = a.ModPow(e, n); +// +// if (x == 1 && !check.Equals(One)) +// return false; +// +// if (x == -1 && !check.Equals(nMinusOne)) +// return false; +// +// --certainty; +// } +// while (certainty > 0); +// +// return true; +// } +// +// private static int Jacobi( +// BigInteger a, +// BigInteger b) +// { +// Debug.Assert(a.sign >= 0); +// Debug.Assert(b.sign > 0); +// Debug.Assert(b.TestBit(0)); +// Debug.Assert(a.CompareTo(b) < 0); +// +// int totalS = 1; +// for (;;) +// { +// if (a.sign == 0) +// return 0; +// +// if (a.Equals(One)) +// break; +// +// int e = a.GetLowestSetBit(); +// +// int bLsw = b.magnitude[b.magnitude.Length - 1]; +// if ((e & 1) != 0 && ((bLsw & 7) == 3 || (bLsw & 7) == 5)) +// totalS = -totalS; +// +// // TODO Confirm this is faster than later a1.Equals(One) test +// if (a.BitLength == e + 1) +// break; +// BigInteger a1 = a.ShiftRight(e); +//// if (a1.Equals(One)) +//// break; +// +// int a1Lsw = a1.magnitude[a1.magnitude.Length - 1]; +// if ((bLsw & 3) == 3 && (a1Lsw & 3) == 3) +// totalS = -totalS; +// +//// a = b.Mod(a1); +// a = b.Remainder(a1); +// b = a1; +// } +// return totalS; +// } + + public long LongValue + { + get + { + if (sign == 0) + return 0; + + int n = magnitude.Length; + + long v = magnitude[n - 1] & IMASK; + if (n > 1) + { + v |= (magnitude[n - 2] & IMASK) << 32; + } + + return sign < 0 ? -v : v; + } + } + + public BigInteger Max( + BigInteger value) + { + return CompareTo(value) > 0 ? this : value; + } + + public BigInteger Min( + BigInteger value) + { + return CompareTo(value) < 0 ? this : value; + } + + public BigInteger Mod( + BigInteger m) + { + if (m.sign < 1) + throw new ArithmeticException("Modulus must be positive"); + + BigInteger biggie = Remainder(m); + + return (biggie.sign >= 0 ? biggie : biggie.Add(m)); + } + + public BigInteger ModInverse( + BigInteger m) + { + if (m.sign < 1) + throw new ArithmeticException("Modulus must be positive"); + + // TODO Too slow at the moment +// // "Fast Key Exchange with Elliptic Curve Systems" R.Schoeppel +// if (m.TestBit(0)) +// { +// //The Almost Inverse Algorithm +// int k = 0; +// BigInteger B = One, C = Zero, F = this, G = m, tmp; +// +// for (;;) +// { +// // While F is even, do F=F/u, C=C*u, k=k+1. +// int zeroes = F.GetLowestSetBit(); +// if (zeroes > 0) +// { +// F = F.ShiftRight(zeroes); +// C = C.ShiftLeft(zeroes); +// k += zeroes; +// } +// +// // If F = 1, then return B,k. +// if (F.Equals(One)) +// { +// BigInteger half = m.Add(One).ShiftRight(1); +// BigInteger halfK = half.ModPow(BigInteger.ValueOf(k), m); +// return B.Multiply(halfK).Mod(m); +// } +// +// if (F.CompareTo(G) < 0) +// { +// tmp = G; G = F; F = tmp; +// tmp = B; B = C; C = tmp; +// } +// +// F = F.Add(G); +// B = B.Add(C); +// } +// } + + if (m.QuickPow2Check()) + { + return ModInversePow2(m); + } + + BigInteger d = this.Remainder(m); + BigInteger x; + BigInteger gcd = ExtEuclid(d, m, out x); + + if (!gcd.Equals(One)) + throw new ArithmeticException("Numbers not relatively prime."); + + if (x.sign < 0) + { + x = x.Add(m); + } + + return x; + } + + private BigInteger ModInversePow2(BigInteger m) + { + Debug.Assert(m.SignValue > 0); + Debug.Assert(m.BitCount == 1); + + if (!TestBit(0)) + { + throw new ArithmeticException("Numbers not relatively prime."); + } + + int pow = m.BitLength - 1; + + long inv64 = ModInverse64(LongValue); + if (pow < 64) + { + inv64 &= ((1L << pow) - 1); + } + + BigInteger x = BigInteger.ValueOf(inv64); + + if (pow > 64) + { + BigInteger d = this.Remainder(m); + int bitsCorrect = 64; + + do + { + BigInteger t = x.Multiply(d).Remainder(m); + x = x.Multiply(Two.Subtract(t)).Remainder(m); + bitsCorrect <<= 1; + } + while (bitsCorrect < pow); + } + + if (x.sign < 0) + { + x = x.Add(m); + } + + return x; + } + + private static int ModInverse32(int d) + { + // Newton's method with initial estimate "correct to 4 bits" + Debug.Assert((d & 1) != 0); + int x = d + (((d + 1) & 4) << 1); // d.x == 1 mod 2**4 + Debug.Assert(((d * x) & 15) == 1); + x *= 2 - d * x; // d.x == 1 mod 2**8 + x *= 2 - d * x; // d.x == 1 mod 2**16 + x *= 2 - d * x; // d.x == 1 mod 2**32 + Debug.Assert(d * x == 1); + return x; + } + + private static long ModInverse64(long d) + { + // Newton's method with initial estimate "correct to 4 bits" + Debug.Assert((d & 1L) != 0); + long x = d + (((d + 1L) & 4L) << 1); // d.x == 1 mod 2**4 + Debug.Assert(((d * x) & 15L) == 1L); + x *= 2 - d * x; // d.x == 1 mod 2**8 + x *= 2 - d * x; // d.x == 1 mod 2**16 + x *= 2 - d * x; // d.x == 1 mod 2**32 + x *= 2 - d * x; // d.x == 1 mod 2**64 + Debug.Assert(d * x == 1L); + return x; + } + + /** + * Calculate the numbers u1, u2, and u3 such that: + * + * u1 * a + u2 * b = u3 + * + * where u3 is the greatest common divider of a and b. + * a and b using the extended Euclid algorithm (refer p. 323 + * of The Art of Computer Programming vol 2, 2nd ed). + * This also seems to have the side effect of calculating + * some form of multiplicative inverse. + * + * @param a First number to calculate gcd for + * @param b Second number to calculate gcd for + * @param u1Out the return object for the u1 value + * @return The greatest common divisor of a and b + */ + private static BigInteger ExtEuclid(BigInteger a, BigInteger b, out BigInteger u1Out) + { + BigInteger u1 = One, v1 = Zero; + BigInteger u3 = a, v3 = b; + + if (v3.sign > 0) + { + for (;;) + { + BigInteger[] q = u3.DivideAndRemainder(v3); + u3 = v3; + v3 = q[1]; + + BigInteger oldU1 = u1; + u1 = v1; + + if (v3.sign <= 0) + break; + + v1 = oldU1.Subtract(v1.Multiply(q[0])); + } + } + + u1Out = u1; + + return u3; + } + + private static void ZeroOut( + int[] x) + { + Array.Clear(x, 0, x.Length); + } + + public BigInteger ModPow(BigInteger e, BigInteger m) + { + if (m.sign < 1) + throw new ArithmeticException("Modulus must be positive"); + + if (m.Equals(One)) + return Zero; + + if (e.sign == 0) + return One; + + if (sign == 0) + return Zero; + + bool negExp = e.sign < 0; + if (negExp) + e = e.Negate(); + + BigInteger result = this.Mod(m); + if (!e.Equals(One)) + { + if ((m.magnitude[m.magnitude.Length - 1] & 1) == 0) + { + result = ModPowBarrett(result, e, m); + } + else + { + result = ModPowMonty(result, e, m, true); + } + } + + if (negExp) + result = result.ModInverse(m); + + return result; + } + + private static BigInteger ModPowBarrett(BigInteger b, BigInteger e, BigInteger m) + { + int k = m.magnitude.Length; + BigInteger mr = One.ShiftLeft((k + 1) << 5); + BigInteger yu = One.ShiftLeft(k << 6).Divide(m); + + // Sliding window from MSW to LSW + int extraBits = 0, expLength = e.BitLength; + while (expLength > ExpWindowThresholds[extraBits]) + { + ++extraBits; + } + + int numPowers = 1 << extraBits; + BigInteger[] oddPowers = new BigInteger[numPowers]; + oddPowers[0] = b; + + BigInteger b2 = ReduceBarrett(b.Square(), m, mr, yu); + + for (int i = 1; i < numPowers; ++i) + { + oddPowers[i] = ReduceBarrett(oddPowers[i - 1].Multiply(b2), m, mr, yu); + } + + int[] windowList = GetWindowList(e.magnitude, extraBits); + Debug.Assert(windowList.Length > 0); + + int window = windowList[0]; + int mult = window & 0xFF, lastZeroes = window >> 8; + + BigInteger y; + if (mult == 1) + { + y = b2; + --lastZeroes; + } + else + { + y = oddPowers[mult >> 1]; + } + + int windowPos = 1; + while ((window = windowList[windowPos++]) != -1) + { + mult = window & 0xFF; + + int bits = lastZeroes + BitLengthTable[mult]; + for (int j = 0; j < bits; ++j) + { + y = ReduceBarrett(y.Square(), m, mr, yu); + } + + y = ReduceBarrett(y.Multiply(oddPowers[mult >> 1]), m, mr, yu); + + lastZeroes = window >> 8; + } + + for (int i = 0; i < lastZeroes; ++i) + { + y = ReduceBarrett(y.Square(), m, mr, yu); + } + + return y; + } + + private static BigInteger ReduceBarrett(BigInteger x, BigInteger m, BigInteger mr, BigInteger yu) + { + int xLen = x.BitLength, mLen = m.BitLength; + if (xLen < mLen) + return x; + + if (xLen - mLen > 1) + { + int k = m.magnitude.Length; + + BigInteger q1 = x.DivideWords(k - 1); + BigInteger q2 = q1.Multiply(yu); // TODO Only need partial multiplication here + BigInteger q3 = q2.DivideWords(k + 1); + + BigInteger r1 = x.RemainderWords(k + 1); + BigInteger r2 = q3.Multiply(m); // TODO Only need partial multiplication here + BigInteger r3 = r2.RemainderWords(k + 1); + + x = r1.Subtract(r3); + if (x.sign < 0) + { + x = x.Add(mr); + } + } + + while (x.CompareTo(m) >= 0) + { + x = x.Subtract(m); + } + + return x; + } + + private static BigInteger ModPowMonty(BigInteger b, BigInteger e, BigInteger m, bool convert) + { + int n = m.magnitude.Length; + int powR = 32 * n; + bool smallMontyModulus = m.BitLength + 2 <= powR; + uint mDash = (uint)m.GetMQuote(); + + // tmp = this * R mod m + if (convert) + { + b = b.ShiftLeft(powR).Remainder(m); + } + + int[] yAccum = new int[n + 1]; + + int[] zVal = b.magnitude; + Debug.Assert(zVal.Length <= n); + if (zVal.Length < n) + { + int[] tmp = new int[n]; + zVal.CopyTo(tmp, n - zVal.Length); + zVal = tmp; + } + + // Sliding window from MSW to LSW + + int extraBits = 0; + + // Filter the common case of small RSA exponents with few bits set + if (e.magnitude.Length > 1 || e.BitCount > 2) + { + int expLength = e.BitLength; + while (expLength > ExpWindowThresholds[extraBits]) + { + ++extraBits; + } + } + + int numPowers = 1 << extraBits; + int[][] oddPowers = new int[numPowers][]; + oddPowers[0] = zVal; + + int[] zSquared = Arrays.Clone(zVal); + SquareMonty(yAccum, zSquared, m.magnitude, mDash, smallMontyModulus); + + for (int i = 1; i < numPowers; ++i) + { + oddPowers[i] = Arrays.Clone(oddPowers[i - 1]); + MultiplyMonty(yAccum, oddPowers[i], zSquared, m.magnitude, mDash, smallMontyModulus); + } + + int[] windowList = GetWindowList(e.magnitude, extraBits); + Debug.Assert(windowList.Length > 1); + + int window = windowList[0]; + int mult = window & 0xFF, lastZeroes = window >> 8; + + int[] yVal; + if (mult == 1) + { + yVal = zSquared; + --lastZeroes; + } + else + { + yVal = Arrays.Clone(oddPowers[mult >> 1]); + } + + int windowPos = 1; + while ((window = windowList[windowPos++]) != -1) + { + mult = window & 0xFF; + + int bits = lastZeroes + BitLengthTable[mult]; + for (int j = 0; j < bits; ++j) + { + SquareMonty(yAccum, yVal, m.magnitude, mDash, smallMontyModulus); + } + + MultiplyMonty(yAccum, yVal, oddPowers[mult >> 1], m.magnitude, mDash, smallMontyModulus); + + lastZeroes = window >> 8; + } + + for (int i = 0; i < lastZeroes; ++i) + { + SquareMonty(yAccum, yVal, m.magnitude, mDash, smallMontyModulus); + } + + if (convert) + { + // Return y * R^(-1) mod m + MontgomeryReduce(yVal, m.magnitude, mDash); + } + else if (smallMontyModulus && CompareTo(0, yVal, 0, m.magnitude) >= 0) + { + Subtract(0, yVal, 0, m.magnitude); + } + + return new BigInteger(1, yVal, true); + } + + private static int[] GetWindowList(int[] mag, int extraBits) + { + int v = mag[0]; + Debug.Assert(v != 0); + + int leadingBits = BitLen(v); + + int resultSize = (((mag.Length - 1) << 5) + leadingBits) / (1 + extraBits) + 2; + int[] result = new int[resultSize]; + int resultPos = 0; + + int bitPos = 33 - leadingBits; + v <<= bitPos; + + int mult = 1, multLimit = 1 << extraBits; + int zeroes = 0; + + int i = 0; + for (; ; ) + { + for (; bitPos < 32; ++bitPos) + { + if (mult < multLimit) + { + mult = (mult << 1) | (int)((uint)v >> 31); + } + else if (v < 0) + { + result[resultPos++] = CreateWindowEntry(mult, zeroes); + mult = 1; + zeroes = 0; + } + else + { + ++zeroes; + } + + v <<= 1; + } + + if (++i == mag.Length) + { + result[resultPos++] = CreateWindowEntry(mult, zeroes); + break; + } + + v = mag[i]; + bitPos = 0; + } + + result[resultPos] = -1; + return result; + } + + private static int CreateWindowEntry(int mult, int zeroes) + { + while ((mult & 1) == 0) + { + mult >>= 1; + ++zeroes; + } + + return mult | (zeroes << 8); + } + + /** + * return w with w = x * x - w is assumed to have enough space. + */ + private static int[] Square( + int[] w, + int[] x) + { + // Note: this method allows w to be only (2 * x.Length - 1) words if result will fit +// if (w.Length != 2 * x.Length) +// throw new ArgumentException("no I don't think so..."); + + ulong c; + + int wBase = w.Length - 1; + + for (int i = x.Length - 1; i > 0; --i) + { + ulong v = (uint)x[i]; + + c = v * v + (uint)w[wBase]; + w[wBase] = (int)c; + c >>= 32; + + for (int j = i - 1; j >= 0; --j) + { + ulong prod = v * (uint)x[j]; + + c += ((uint)w[--wBase] & UIMASK) + ((uint)prod << 1); + w[wBase] = (int)c; + c = (c >> 32) + (prod >> 31); + } + + c += (uint)w[--wBase]; + w[wBase] = (int)c; + + if (--wBase >= 0) + { + w[wBase] = (int)(c >> 32); + } + else + { + Debug.Assert((c >> 32) == 0); + } + + wBase += i; + } + + c = (uint)x[0]; + + c = c * c + (uint)w[wBase]; + w[wBase] = (int)c; + + if (--wBase >= 0) + { + w[wBase] += (int)(c >> 32); + } + else + { + Debug.Assert((c >> 32) == 0); + } + + return w; + } + + /** + * return x with x = y * z - x is assumed to have enough space. + */ + private static int[] Multiply(int[] x, int[] y, int[] z) + { + int i = z.Length; + + if (i < 1) + return x; + + int xBase = x.Length - y.Length; + + do + { + long a = z[--i] & IMASK; + long val = 0; + + if (a != 0) + { + for (int j = y.Length - 1; j >= 0; j--) + { + val += a * (y[j] & IMASK) + (x[xBase + j] & IMASK); + + x[xBase + j] = (int)val; + + val = (long)((ulong)val >> 32); + } + } + + --xBase; + + if (xBase >= 0) + { + x[xBase] = (int)val; + } + else + { + Debug.Assert(val == 0); + } + } + while (i > 0); + + return x; + } + + /** + * Calculate mQuote = -m^(-1) mod b with b = 2^32 (32 = word size) + */ + private int GetMQuote() + { + if (mQuote != 0) + { + return mQuote; // already calculated + } + + Debug.Assert(this.sign > 0); + + int d = -magnitude[magnitude.Length - 1]; + + Debug.Assert((d & 1) != 0); + + return mQuote = ModInverse32(d); + } + + private static void MontgomeryReduce(int[] x, int[] m, uint mDash) // mDash = -m^(-1) mod b + { + // NOTE: Not a general purpose reduction (which would allow x up to twice the bitlength of m) + Debug.Assert(x.Length == m.Length); + + int n = m.Length; + + for (int i = n - 1; i >= 0; --i) + { + uint x0 = (uint)x[n - 1]; + ulong t = x0 * mDash; + + ulong carry = t * (uint)m[n - 1] + x0; + Debug.Assert((uint)carry == 0); + carry >>= 32; + + for (int j = n - 2; j >= 0; --j) + { + carry += t * (uint)m[j] + (uint)x[j]; + x[j + 1] = (int)carry; + carry >>= 32; + } + + x[0] = (int)carry; + Debug.Assert(carry >> 32 == 0); + } + + if (CompareTo(0, x, 0, m) >= 0) + { + Subtract(0, x, 0, m); + } + } + + /** + * Montgomery multiplication: a = x * y * R^(-1) mod m + *
+ * Based algorithm 14.36 of Handbook of Applied Cryptography. + *
+ *
  • m, x, y should have length n
  • + *
  • a should have length (n + 1)
  • + *
  • b = 2^32, R = b^n
  • + *
    + * The result is put in x + *
    + * NOTE: the indices of x, y, m, a different in HAC and in Java + */ + private static void MultiplyMonty(int[] a, int[] x, int[] y, int[] m, uint mDash, bool smallMontyModulus) + // mDash = -m^(-1) mod b + { + int n = m.Length; + + if (n == 1) + { + x[0] = (int)MultiplyMontyNIsOne((uint)x[0], (uint)y[0], (uint)m[0], mDash); + return; + } + + uint y0 = (uint)y[n - 1]; + int aMax; + + { + ulong xi = (uint)x[n - 1]; + + ulong carry = xi * y0; + ulong t = (uint)carry * mDash; + + ulong prod2 = t * (uint)m[n - 1]; + carry += (uint)prod2; + Debug.Assert((uint)carry == 0); + carry = (carry >> 32) + (prod2 >> 32); + + for (int j = n - 2; j >= 0; --j) + { + ulong prod1 = xi * (uint)y[j]; + prod2 = t * (uint)m[j]; + + carry += (prod1 & UIMASK) + (uint)prod2; + a[j + 2] = (int)carry; + carry = (carry >> 32) + (prod1 >> 32) + (prod2 >> 32); + } + + a[1] = (int)carry; + aMax = (int)(carry >> 32); + } + + for (int i = n - 2; i >= 0; --i) + { + uint a0 = (uint)a[n]; + ulong xi = (uint)x[i]; + + ulong prod1 = xi * y0; + ulong carry = (prod1 & UIMASK) + a0; + ulong t = (uint)carry * mDash; + + ulong prod2 = t * (uint)m[n - 1]; + carry += (uint)prod2; + Debug.Assert((uint)carry == 0); + carry = (carry >> 32) + (prod1 >> 32) + (prod2 >> 32); + + for (int j = n - 2; j >= 0; --j) + { + prod1 = xi * (uint)y[j]; + prod2 = t * (uint)m[j]; + + carry += (prod1 & UIMASK) + (uint)prod2 + (uint)a[j + 1]; + a[j + 2] = (int)carry; + carry = (carry >> 32) + (prod1 >> 32) + (prod2 >> 32); + } + + carry += (uint)aMax; + a[1] = (int)carry; + aMax = (int)(carry >> 32); + } + + a[0] = aMax; + + if (!smallMontyModulus && CompareTo(0, a, 0, m) >= 0) + { + Subtract(0, a, 0, m); + } + + Array.Copy(a, 1, x, 0, n); + } + + private static void SquareMonty(int[] a, int[] x, int[] m, uint mDash, bool smallMontyModulus) + // mDash = -m^(-1) mod b + { + int n = m.Length; + + if (n == 1) + { + uint xVal = (uint)x[0]; + x[0] = (int)MultiplyMontyNIsOne(xVal, xVal, (uint)m[0], mDash); + return; + } + + ulong x0 = (uint)x[n - 1]; + int aMax; + + { + ulong carry = x0 * x0; + ulong t = (uint)carry * mDash; + + ulong prod2 = t * (uint)m[n - 1]; + carry += (uint)prod2; + Debug.Assert((uint)carry == 0); + carry = (carry >> 32) + (prod2 >> 32); + + for (int j = n - 2; j >= 0; --j) + { + ulong prod1 = x0 * (uint)x[j]; + prod2 = t * (uint)m[j]; + + carry += (prod2 & UIMASK) + ((uint)prod1 << 1); + a[j + 2] = (int)carry; + carry = (carry >> 32) + (prod1 >> 31) + (prod2 >> 32); + } + + a[1] = (int)carry; + aMax = (int)(carry >> 32); + } + + for (int i = n - 2; i >= 0; --i) + { + uint a0 = (uint)a[n]; + ulong t = a0 * mDash; + + ulong carry = t * (uint)m[n - 1] + a0; + Debug.Assert((uint)carry == 0); + carry >>= 32; + + for (int j = n - 2; j > i; --j) + { + carry += t * (uint)m[j] + (uint)a[j + 1]; + a[j + 2] = (int)carry; + carry >>= 32; + } + + ulong xi = (uint)x[i]; + + { + ulong prod1 = xi * xi; + ulong prod2 = t * (uint)m[i]; + + carry += (prod1 & UIMASK) + (uint)prod2 + (uint)a[i + 1]; + a[i + 2] = (int)carry; + carry = (carry >> 32) + (prod1 >> 32) + (prod2 >> 32); + } + + for (int j = i - 1; j >= 0; --j) + { + ulong prod1 = xi * (uint)x[j]; + ulong prod2 = t * (uint)m[j]; + + carry += (prod2 & UIMASK) + ((uint)prod1 << 1) + (uint)a[j + 1]; + a[j + 2] = (int)carry; + carry = (carry >> 32) + (prod1 >> 31) + (prod2 >> 32); + } + + carry += (uint)aMax; + a[1] = (int)carry; + aMax = (int)(carry >> 32); + } + + a[0] = aMax; + + if (!smallMontyModulus && CompareTo(0, a, 0, m) >= 0) + { + Subtract(0, a, 0, m); + } + + Array.Copy(a, 1, x, 0, n); + } + + private static uint MultiplyMontyNIsOne(uint x, uint y, uint m, uint mDash) + { + ulong carry = (ulong)x * y; + uint t = (uint)carry * mDash; + ulong um = m; + ulong prod2 = um * t; + carry += (uint)prod2; + Debug.Assert((uint)carry == 0); + carry = (carry >> 32) + (prod2 >> 32); + if (carry > um) + { + carry -= um; + } + Debug.Assert(carry < um); + return (uint)carry; + } + + public BigInteger Multiply( + BigInteger val) + { + if (val == this) + return Square(); + + if ((sign & val.sign) == 0) + return Zero; + + if (val.QuickPow2Check()) // val is power of two + { + BigInteger result = this.ShiftLeft(val.Abs().BitLength - 1); + return val.sign > 0 ? result : result.Negate(); + } + + if (this.QuickPow2Check()) // this is power of two + { + BigInteger result = val.ShiftLeft(this.Abs().BitLength - 1); + return this.sign > 0 ? result : result.Negate(); + } + + int resLength = magnitude.Length + val.magnitude.Length; + int[] res = new int[resLength]; + + Multiply(res, this.magnitude, val.magnitude); + + int resSign = sign ^ val.sign ^ 1; + return new BigInteger(resSign, res, true); + } + + public BigInteger Square() + { + if (sign == 0) + return Zero; + if (this.QuickPow2Check()) + return ShiftLeft(Abs().BitLength - 1); + int resLength = magnitude.Length << 1; + if ((uint)magnitude[0] >> 16 == 0) + --resLength; + int[] res = new int[resLength]; + Square(res, magnitude); + return new BigInteger(1, res, false); + } + + public BigInteger Negate() + { + if (sign == 0) + return this; + + return new BigInteger(-sign, magnitude, false); + } + + public BigInteger NextProbablePrime() + { + if (sign < 0) + throw new ArithmeticException("Cannot be called on value < 0"); + + if (CompareTo(Two) < 0) + return Two; + + BigInteger n = Inc().SetBit(0); + + while (!n.CheckProbablePrime(100, RandomSource, false)) + { + n = n.Add(Two); + } + + return n; + } + + public BigInteger Not() + { + return Inc().Negate(); + } + + public BigInteger Pow(int exp) + { + if (exp <= 0) + { + if (exp < 0) + throw new ArithmeticException("Negative exponent"); + + return One; + } + + if (sign == 0) + { + return this; + } + + if (QuickPow2Check()) + { + long powOf2 = (long)exp * (BitLength - 1); + if (powOf2 > Int32.MaxValue) + { + throw new ArithmeticException("Result too large"); + } + return One.ShiftLeft((int)powOf2); + } + + BigInteger y = One; + BigInteger z = this; + + for (;;) + { + if ((exp & 0x1) == 1) + { + y = y.Multiply(z); + } + exp >>= 1; + if (exp == 0) break; + z = z.Multiply(z); + } + + return y; + } + + public static BigInteger ProbablePrime( + int bitLength, + Random random) + { + return new BigInteger(bitLength, 100, random); + } + + private int Remainder( + int m) + { + Debug.Assert(m > 0); + + long acc = 0; + for (int pos = 0; pos < magnitude.Length; ++pos) + { + long posVal = (uint) magnitude[pos]; + acc = (acc << 32 | posVal) % m; + } + + return (int) acc; + } + + /** + * return x = x % y - done in place (y value preserved) + */ + private static int[] Remainder( + int[] x, + int[] y) + { + int xStart = 0; + while (xStart < x.Length && x[xStart] == 0) + { + ++xStart; + } + + int yStart = 0; + while (yStart < y.Length && y[yStart] == 0) + { + ++yStart; + } + + Debug.Assert(yStart < y.Length); + + int xyCmp = CompareNoLeadingZeroes(xStart, x, yStart, y); + + if (xyCmp > 0) + { + int yBitLength = CalcBitLength(1, yStart, y); + int xBitLength = CalcBitLength(1, xStart, x); + int shift = xBitLength - yBitLength; + + int[] c; + int cStart = 0; + int cBitLength = yBitLength; + if (shift > 0) + { + c = ShiftLeft(y, shift); + cBitLength += shift; + Debug.Assert(c[0] != 0); + } + else + { + int len = y.Length - yStart; + c = new int[len]; + Array.Copy(y, yStart, c, 0, len); + } + + for (;;) + { + if (cBitLength < xBitLength + || CompareNoLeadingZeroes(xStart, x, cStart, c) >= 0) + { + Subtract(xStart, x, cStart, c); + + while (x[xStart] == 0) + { + if (++xStart == x.Length) + return x; + } + + //xBitLength = CalcBitLength(xStart, x); + xBitLength = 32 * (x.Length - xStart - 1) + BitLen(x[xStart]); + + if (xBitLength <= yBitLength) + { + if (xBitLength < yBitLength) + return x; + + xyCmp = CompareNoLeadingZeroes(xStart, x, yStart, y); + + if (xyCmp <= 0) + break; + } + } + + shift = cBitLength - xBitLength; + + // NB: The case where c[cStart] is 1-bit is harmless + if (shift == 1) + { + uint firstC = (uint) c[cStart] >> 1; + uint firstX = (uint) x[xStart]; + if (firstC > firstX) + ++shift; + } + + if (shift < 2) + { + ShiftRightOneInPlace(cStart, c); + --cBitLength; + } + else + { + ShiftRightInPlace(cStart, c, shift); + cBitLength -= shift; + } + + //cStart = c.Length - ((cBitLength + 31) / 32); + while (c[cStart] == 0) + { + ++cStart; + } + } + } + + if (xyCmp == 0) + { + Array.Clear(x, xStart, x.Length - xStart); + } + + return x; + } + + public BigInteger Remainder( + BigInteger n) + { + if (n.sign == 0) + throw new ArithmeticException("Division by zero error"); + + if (this.sign == 0) + return Zero; + + // For small values, use fast remainder method + if (n.magnitude.Length == 1) + { + int val = n.magnitude[0]; + + if (val > 0) + { + if (val == 1) + return Zero; + + // TODO Make this func work on uint, and handle val == 1? + int rem = Remainder(val); + + return rem == 0 + ? Zero + : new BigInteger(sign, new int[]{ rem }, false); + } + } + + if (CompareNoLeadingZeroes(0, magnitude, 0, n.magnitude) < 0) + return this; + + int[] result; + if (n.QuickPow2Check()) // n is power of two + { + // TODO Move before small values branch above? + result = LastNBits(n.Abs().BitLength - 1); + } + else + { + result = (int[]) this.magnitude.Clone(); + result = Remainder(result, n.magnitude); + } + + return new BigInteger(sign, result, true); + } + + private int[] LastNBits( + int n) + { + if (n < 1) + return ZeroMagnitude; + + int numWords = (n + BitsPerInt - 1) / BitsPerInt; + numWords = System.Math.Min(numWords, this.magnitude.Length); + int[] result = new int[numWords]; + + Array.Copy(this.magnitude, this.magnitude.Length - numWords, result, 0, numWords); + + int excessBits = (numWords << 5) - n; + if (excessBits > 0) + { + result[0] &= (int)(UInt32.MaxValue >> excessBits); + } + + return result; + } + + private BigInteger DivideWords(int w) + { + Debug.Assert(w >= 0); + int n = magnitude.Length; + if (w >= n) + return Zero; + int[] mag = new int[n - w]; + Array.Copy(magnitude, 0, mag, 0, n - w); + return new BigInteger(sign, mag, false); + } + + private BigInteger RemainderWords(int w) + { + Debug.Assert(w >= 0); + int n = magnitude.Length; + if (w >= n) + return this; + int[] mag = new int[w]; + Array.Copy(magnitude, n - w, mag, 0, w); + return new BigInteger(sign, mag, false); + } + + /** + * do a left shift - this returns a new array. + */ + private static int[] ShiftLeft( + int[] mag, + int n) + { + int nInts = (int)((uint)n >> 5); + int nBits = n & 0x1f; + int magLen = mag.Length; + int[] newMag; + + if (nBits == 0) + { + newMag = new int[magLen + nInts]; + mag.CopyTo(newMag, 0); + } + else + { + int i = 0; + int nBits2 = 32 - nBits; + int highBits = (int)((uint)mag[0] >> nBits2); + + if (highBits != 0) + { + newMag = new int[magLen + nInts + 1]; + newMag[i++] = highBits; + } + else + { + newMag = new int[magLen + nInts]; + } + + int m = mag[0]; + for (int j = 0; j < magLen - 1; j++) + { + int next = mag[j + 1]; + + newMag[i++] = (m << nBits) | (int)((uint)next >> nBits2); + m = next; + } + + newMag[i] = mag[magLen - 1] << nBits; + } + + return newMag; + } + + private static int ShiftLeftOneInPlace(int[] x, int carry) + { + Debug.Assert(carry == 0 || carry == 1); + int pos = x.Length; + while (--pos >= 0) + { + uint val = (uint)x[pos]; + x[pos] = (int)(val << 1) | carry; + carry = (int)(val >> 31); + } + return carry; + } + + public BigInteger ShiftLeft( + int n) + { + if (sign == 0 || magnitude.Length == 0) + return Zero; + + if (n == 0) + return this; + + if (n < 0) + return ShiftRight(-n); + + BigInteger result = new BigInteger(sign, ShiftLeft(magnitude, n), true); + + if (this.nBits != -1) + { + result.nBits = sign > 0 + ? this.nBits + : this.nBits + n; + } + + if (this.nBitLength != -1) + { + result.nBitLength = this.nBitLength + n; + } + + return result; + } + + /** + * do a right shift - this does it in place. + */ + private static void ShiftRightInPlace( + int start, + int[] mag, + int n) + { + int nInts = (int)((uint)n >> 5) + start; + int nBits = n & 0x1f; + int magEnd = mag.Length - 1; + + if (nInts != start) + { + int delta = (nInts - start); + + for (int i = magEnd; i >= nInts; i--) + { + mag[i] = mag[i - delta]; + } + for (int i = nInts - 1; i >= start; i--) + { + mag[i] = 0; + } + } + + if (nBits != 0) + { + int nBits2 = 32 - nBits; + int m = mag[magEnd]; + + for (int i = magEnd; i > nInts; --i) + { + int next = mag[i - 1]; + + mag[i] = (int)((uint)m >> nBits) | (next << nBits2); + m = next; + } + + mag[nInts] = (int)((uint)mag[nInts] >> nBits); + } + } + + /** + * do a right shift by one - this does it in place. + */ + private static void ShiftRightOneInPlace( + int start, + int[] mag) + { + int i = mag.Length; + int m = mag[i - 1]; + + while (--i > start) + { + int next = mag[i - 1]; + mag[i] = ((int)((uint)m >> 1)) | (next << 31); + m = next; + } + + mag[start] = (int)((uint)mag[start] >> 1); + } + + public BigInteger ShiftRight( + int n) + { + if (n == 0) + return this; + + if (n < 0) + return ShiftLeft(-n); + + if (n >= BitLength) + return (this.sign < 0 ? One.Negate() : Zero); + +// int[] res = (int[]) this.magnitude.Clone(); +// +// ShiftRightInPlace(0, res, n); +// +// return new BigInteger(this.sign, res, true); + + int resultLength = (BitLength - n + 31) >> 5; + int[] res = new int[resultLength]; + + int numInts = n >> 5; + int numBits = n & 31; + + if (numBits == 0) + { + Array.Copy(this.magnitude, 0, res, 0, res.Length); + } + else + { + int numBits2 = 32 - numBits; + + int magPos = this.magnitude.Length - 1 - numInts; + for (int i = resultLength - 1; i >= 0; --i) + { + res[i] = (int)((uint) this.magnitude[magPos--] >> numBits); + + if (magPos >= 0) + { + res[i] |= this.magnitude[magPos] << numBits2; + } + } + } + + Debug.Assert(res[0] != 0); + + return new BigInteger(this.sign, res, false); + } + + public int SignValue + { + get { return sign; } + } + + /** + * returns x = x - y - we assume x is >= y + */ + private static int[] Subtract( + int xStart, + int[] x, + int yStart, + int[] y) + { + Debug.Assert(yStart < y.Length); + Debug.Assert(x.Length - xStart >= y.Length - yStart); + + int iT = x.Length; + int iV = y.Length; + long m; + int borrow = 0; + + do + { + m = (x[--iT] & IMASK) - (y[--iV] & IMASK) + borrow; + x[iT] = (int) m; + +// borrow = (m < 0) ? -1 : 0; + borrow = (int)(m >> 63); + } + while (iV > yStart); + + if (borrow != 0) + { + while (--x[--iT] == -1) + { + } + } + + return x; + } + + public BigInteger Subtract( + BigInteger n) + { + if (n.sign == 0) + return this; + + if (this.sign == 0) + return n.Negate(); + + if (this.sign != n.sign) + return Add(n.Negate()); + + int compare = CompareNoLeadingZeroes(0, magnitude, 0, n.magnitude); + if (compare == 0) + return Zero; + + BigInteger bigun, lilun; + if (compare < 0) + { + bigun = n; + lilun = this; + } + else + { + bigun = this; + lilun = n; + } + + return new BigInteger(this.sign * compare, doSubBigLil(bigun.magnitude, lilun.magnitude), true); + } + + private static int[] doSubBigLil( + int[] bigMag, + int[] lilMag) + { + int[] res = (int[]) bigMag.Clone(); + + return Subtract(0, res, 0, lilMag); + } + + public byte[] ToByteArray() + { + return ToByteArray(false); + } + + public byte[] ToByteArrayUnsigned() + { + return ToByteArray(true); + } + + private byte[] ToByteArray( + bool unsigned) + { + if (sign == 0) + return unsigned ? ZeroEncoding : new byte[1]; + + int nBits = (unsigned && sign > 0) + ? BitLength + : BitLength + 1; + + int nBytes = GetByteLength(nBits); + byte[] bytes = new byte[nBytes]; + + int magIndex = magnitude.Length; + int bytesIndex = bytes.Length; + + if (sign > 0) + { + while (magIndex > 1) + { + uint mag = (uint) magnitude[--magIndex]; + bytes[--bytesIndex] = (byte) mag; + bytes[--bytesIndex] = (byte)(mag >> 8); + bytes[--bytesIndex] = (byte)(mag >> 16); + bytes[--bytesIndex] = (byte)(mag >> 24); + } + + uint lastMag = (uint) magnitude[0]; + while (lastMag > byte.MaxValue) + { + bytes[--bytesIndex] = (byte) lastMag; + lastMag >>= 8; + } + + bytes[--bytesIndex] = (byte) lastMag; + } + else // sign < 0 + { + bool carry = true; + + while (magIndex > 1) + { + uint mag = ~((uint) magnitude[--magIndex]); + + if (carry) + { + carry = (++mag == uint.MinValue); + } + + bytes[--bytesIndex] = (byte) mag; + bytes[--bytesIndex] = (byte)(mag >> 8); + bytes[--bytesIndex] = (byte)(mag >> 16); + bytes[--bytesIndex] = (byte)(mag >> 24); + } + + uint lastMag = (uint) magnitude[0]; + + if (carry) + { + // Never wraps because magnitude[0] != 0 + --lastMag; + } + + while (lastMag > byte.MaxValue) + { + bytes[--bytesIndex] = (byte) ~lastMag; + lastMag >>= 8; + } + + bytes[--bytesIndex] = (byte) ~lastMag; + + if (bytesIndex > 0) + { + bytes[--bytesIndex] = byte.MaxValue; + } + } + + return bytes; + } + + public override string ToString() + { + return ToString(10); + } + + public string ToString(int radix) + { + // TODO Make this method work for other radices (ideally 2 <= radix <= 36 as in Java) + + switch (radix) + { + case 2: + case 8: + case 10: + case 16: + break; + default: + throw new FormatException("Only bases 2, 8, 10, 16 are allowed"); + } + + // NB: Can only happen to internally managed instances + if (magnitude == null) + return "null"; + + if (sign == 0) + return "0"; + + + // NOTE: This *should* be unnecessary, since the magnitude *should* never have leading zero digits + int firstNonZero = 0; + while (firstNonZero < magnitude.Length) + { + if (magnitude[firstNonZero] != 0) + { + break; + } + ++firstNonZero; + } + + if (firstNonZero == magnitude.Length) + { + return "0"; + } + + + StringBuilder sb = new StringBuilder(); + if (sign == -1) + { + sb.Append('-'); + } + + switch (radix) + { + case 2: + { + int pos = firstNonZero; + sb.Append(Convert.ToString(magnitude[pos], 2)); + while (++pos < magnitude.Length) + { + AppendZeroExtendedString(sb, Convert.ToString(magnitude[pos], 2), 32); + } + break; + } + case 8: + { + int mask = (1 << 30) - 1; + BigInteger u = this.Abs(); + int bits = u.BitLength; + IList S = Platform.CreateArrayList(); + while (bits > 30) + { + S.Add(Convert.ToString(u.IntValue & mask, 8)); + u = u.ShiftRight(30); + bits -= 30; + } + sb.Append(Convert.ToString(u.IntValue, 8)); + for (int i = S.Count - 1; i >= 0; --i) + { + AppendZeroExtendedString(sb, (string)S[i], 10); + } + break; + } + case 16: + { + int pos = firstNonZero; + sb.Append(Convert.ToString(magnitude[pos], 16)); + while (++pos < magnitude.Length) + { + AppendZeroExtendedString(sb, Convert.ToString(magnitude[pos], 16), 8); + } + break; + } + // TODO This could work for other radices if there is an alternative to Convert.ToString method + //default: + case 10: + { + BigInteger q = this.Abs(); + if (q.BitLength < 64) + { + sb.Append(Convert.ToString(q.LongValue, radix)); + break; + } + + // Based on algorithm 1a from chapter 4.4 in Seminumerical Algorithms (Knuth) + + // Work out the largest power of 'rdx' that is a positive 64-bit integer + // TODO possibly cache power/exponent against radix? + long limit = Int64.MaxValue / radix; + long power = radix; + int exponent = 1; + while (power <= limit) + { + power *= radix; + ++exponent; + } + + BigInteger bigPower = BigInteger.ValueOf(power); + + IList S = Platform.CreateArrayList(); + while (q.CompareTo(bigPower) >= 0) + { + BigInteger[] qr = q.DivideAndRemainder(bigPower); + S.Add(Convert.ToString(qr[1].LongValue, radix)); + q = qr[0]; + } + + sb.Append(Convert.ToString(q.LongValue, radix)); + for (int i = S.Count - 1; i >= 0; --i) + { + AppendZeroExtendedString(sb, (string)S[i], exponent); + } + break; + } + } + + return sb.ToString(); + } + + private static void AppendZeroExtendedString(StringBuilder sb, string s, int minLength) + { + for (int len = s.Length; len < minLength; ++len) + { + sb.Append('0'); + } + sb.Append(s); + } + + private static BigInteger CreateUValueOf( + ulong value) + { + int msw = (int)(value >> 32); + int lsw = (int)value; + + if (msw != 0) + return new BigInteger(1, new int[] { msw, lsw }, false); + + if (lsw != 0) + { + BigInteger n = new BigInteger(1, new int[] { lsw }, false); + // Check for a power of two + if ((lsw & -lsw) == lsw) + { + n.nBits = 1; + } + return n; + } + + return Zero; + } + + private static BigInteger CreateValueOf( + long value) + { + if (value < 0) + { + if (value == long.MinValue) + return CreateValueOf(~value).Not(); + + return CreateValueOf(-value).Negate(); + } + + return CreateUValueOf((ulong)value); + } + + public static BigInteger ValueOf( + long value) + { + if (value >= 0 && value < SMALL_CONSTANTS.Length) + { + return SMALL_CONSTANTS[value]; + } + + return CreateValueOf(value); + } + + public int GetLowestSetBit() + { + if (this.sign == 0) + return -1; + + return GetLowestSetBitMaskFirst(-1); + } + + private int GetLowestSetBitMaskFirst(int firstWordMask) + { + int w = magnitude.Length, offset = 0; + + uint word = (uint)(magnitude[--w] & firstWordMask); + Debug.Assert(magnitude[0] != 0); + + while (word == 0) + { + word = (uint)magnitude[--w]; + offset += 32; + } + + while ((word & 0xFF) == 0) + { + word >>= 8; + offset += 8; + } + + while ((word & 1) == 0) + { + word >>= 1; + ++offset; + } + + return offset; + } + + public bool TestBit( + int n) + { + if (n < 0) + throw new ArithmeticException("Bit position must not be negative"); + + if (sign < 0) + return !Not().TestBit(n); + + int wordNum = n / 32; + if (wordNum >= magnitude.Length) + return false; + + int word = magnitude[magnitude.Length - 1 - wordNum]; + return ((word >> (n % 32)) & 1) > 0; + } + + public BigInteger Or( + BigInteger value) + { + if (this.sign == 0) + return value; + + if (value.sign == 0) + return this; + + int[] aMag = this.sign > 0 + ? this.magnitude + : Add(One).magnitude; + + int[] bMag = value.sign > 0 + ? value.magnitude + : value.Add(One).magnitude; + + bool resultNeg = sign < 0 || value.sign < 0; + int resultLength = System.Math.Max(aMag.Length, bMag.Length); + int[] resultMag = new int[resultLength]; + + int aStart = resultMag.Length - aMag.Length; + int bStart = resultMag.Length - bMag.Length; + + for (int i = 0; i < resultMag.Length; ++i) + { + int aWord = i >= aStart ? aMag[i - aStart] : 0; + int bWord = i >= bStart ? bMag[i - bStart] : 0; + + if (this.sign < 0) + { + aWord = ~aWord; + } + + if (value.sign < 0) + { + bWord = ~bWord; + } + + resultMag[i] = aWord | bWord; + + if (resultNeg) + { + resultMag[i] = ~resultMag[i]; + } + } + + BigInteger result = new BigInteger(1, resultMag, true); + + // TODO Optimise this case + if (resultNeg) + { + result = result.Not(); + } + + return result; + } + + public BigInteger Xor( + BigInteger value) + { + if (this.sign == 0) + return value; + + if (value.sign == 0) + return this; + + int[] aMag = this.sign > 0 + ? this.magnitude + : Add(One).magnitude; + + int[] bMag = value.sign > 0 + ? value.magnitude + : value.Add(One).magnitude; + + // TODO Can just replace with sign != value.sign? + bool resultNeg = (sign < 0 && value.sign >= 0) || (sign >= 0 && value.sign < 0); + int resultLength = System.Math.Max(aMag.Length, bMag.Length); + int[] resultMag = new int[resultLength]; + + int aStart = resultMag.Length - aMag.Length; + int bStart = resultMag.Length - bMag.Length; + + for (int i = 0; i < resultMag.Length; ++i) + { + int aWord = i >= aStart ? aMag[i - aStart] : 0; + int bWord = i >= bStart ? bMag[i - bStart] : 0; + + if (this.sign < 0) + { + aWord = ~aWord; + } + + if (value.sign < 0) + { + bWord = ~bWord; + } + + resultMag[i] = aWord ^ bWord; + + if (resultNeg) + { + resultMag[i] = ~resultMag[i]; + } + } + + BigInteger result = new BigInteger(1, resultMag, true); + + // TODO Optimise this case + if (resultNeg) + { + result = result.Not(); + } + + return result; + } + + public BigInteger SetBit( + int n) + { + if (n < 0) + throw new ArithmeticException("Bit address less than zero"); + + if (TestBit(n)) + return this; + + // TODO Handle negative values and zero + if (sign > 0 && n < (BitLength - 1)) + return FlipExistingBit(n); + + return Or(One.ShiftLeft(n)); + } + + public BigInteger ClearBit( + int n) + { + if (n < 0) + throw new ArithmeticException("Bit address less than zero"); + + if (!TestBit(n)) + return this; + + // TODO Handle negative values + if (sign > 0 && n < (BitLength - 1)) + return FlipExistingBit(n); + + return AndNot(One.ShiftLeft(n)); + } + + public BigInteger FlipBit( + int n) + { + if (n < 0) + throw new ArithmeticException("Bit address less than zero"); + + // TODO Handle negative values and zero + if (sign > 0 && n < (BitLength - 1)) + return FlipExistingBit(n); + + return Xor(One.ShiftLeft(n)); + } + + private BigInteger FlipExistingBit( + int n) + { + Debug.Assert(sign > 0); + Debug.Assert(n >= 0); + Debug.Assert(n < BitLength - 1); + + int[] mag = (int[]) this.magnitude.Clone(); + mag[mag.Length - 1 - (n >> 5)] ^= (1 << (n & 31)); // Flip bit + //mag[mag.Length - 1 - (n / 32)] ^= (1 << (n % 32)); + return new BigInteger(this.sign, mag, false); + } + } +} diff --git a/bc-sharp-crypto/src/math/Primes.cs b/bc-sharp-crypto/src/math/Primes.cs new file mode 100644 index 0000000..fb279f1 --- /dev/null +++ b/bc-sharp-crypto/src/math/Primes.cs @@ -0,0 +1,629 @@ +using System; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Math +{ + /** + * Utility methods for generating primes and testing for primality. + */ + public abstract class Primes + { + public static readonly int SmallFactorLimit = 211; + + private static readonly BigInteger One = BigInteger.One; + private static readonly BigInteger Two = BigInteger.Two; + private static readonly BigInteger Three = BigInteger.Three; + + /** + * Used to return the output from the + * {@linkplain Primes#enhancedMRProbablePrimeTest(BigInteger, SecureRandom, int) Enhanced + * Miller-Rabin Probabilistic Primality Test} + */ + public class MROutput + { + internal static MROutput ProbablyPrime() + { + return new MROutput(false, null); + } + + internal static MROutput ProvablyCompositeWithFactor(BigInteger factor) + { + return new MROutput(true, factor); + } + + internal static MROutput ProvablyCompositeNotPrimePower() + { + return new MROutput(true, null); + } + + private readonly bool mProvablyComposite; + private readonly BigInteger mFactor; + + private MROutput(bool provablyComposite, BigInteger factor) + { + this.mProvablyComposite = provablyComposite; + this.mFactor = factor; + } + + public BigInteger Factor + { + get { return mFactor; } + } + + public bool IsProvablyComposite + { + get { return mProvablyComposite; } + } + + public bool IsNotPrimePower + { + get { return mProvablyComposite && mFactor == null; } + } + } + + /** + * Used to return the output from the {@linkplain Primes#generateSTRandomPrime(Digest, int, byte[]) Shawe-Taylor Random_Prime Routine} + */ + public class STOutput + { + private readonly BigInteger mPrime; + private readonly byte[] mPrimeSeed; + private readonly int mPrimeGenCounter; + + internal STOutput(BigInteger prime, byte[] primeSeed, int primeGenCounter) + { + this.mPrime = prime; + this.mPrimeSeed = primeSeed; + this.mPrimeGenCounter = primeGenCounter; + } + + public BigInteger Prime + { + get { return mPrime; } + } + + public byte[] PrimeSeed + { + get { return mPrimeSeed; } + } + + public int PrimeGenCounter + { + get { return mPrimeGenCounter; } + } + } + + /** + * FIPS 186-4 C.6 Shawe-Taylor Random_Prime Routine + * + * Construct a provable prime number using a hash function. + * + * @param hash + * the {@link Digest} instance to use (as "Hash()"). Cannot be null. + * @param length + * the length (in bits) of the prime to be generated. Must be at least 2. + * @param inputSeed + * the seed to be used for the generation of the requested prime. Cannot be null or + * empty. + * @return an {@link STOutput} instance containing the requested prime. + */ + public static STOutput GenerateSTRandomPrime(IDigest hash, int length, byte[] inputSeed) + { + if (hash == null) + throw new ArgumentNullException("hash"); + if (length < 2) + throw new ArgumentException("must be >= 2", "length"); + if (inputSeed == null) + throw new ArgumentNullException("inputSeed"); + if (inputSeed.Length == 0) + throw new ArgumentException("cannot be empty", "inputSeed"); + + return ImplSTRandomPrime(hash, length, Arrays.Clone(inputSeed)); + } + + /** + * FIPS 186-4 C.3.2 Enhanced Miller-Rabin Probabilistic Primality Test + * + * Run several iterations of the Miller-Rabin algorithm with randomly-chosen bases. This is an + * alternative to {@link #isMRProbablePrime(BigInteger, SecureRandom, int)} that provides more + * information about a composite candidate, which may be useful when generating or validating + * RSA moduli. + * + * @param candidate + * the {@link BigInteger} instance to test for primality. + * @param random + * the source of randomness to use to choose bases. + * @param iterations + * the number of randomly-chosen bases to perform the test for. + * @return an {@link MROutput} instance that can be further queried for details. + */ + public static MROutput EnhancedMRProbablePrimeTest(BigInteger candidate, SecureRandom random, int iterations) + { + CheckCandidate(candidate, "candidate"); + + if (random == null) + throw new ArgumentNullException("random"); + if (iterations < 1) + throw new ArgumentException("must be > 0", "iterations"); + + if (candidate.BitLength == 2) + return MROutput.ProbablyPrime(); + + if (!candidate.TestBit(0)) + return MROutput.ProvablyCompositeWithFactor(Two); + + BigInteger w = candidate; + BigInteger wSubOne = candidate.Subtract(One); + BigInteger wSubTwo = candidate.Subtract(Two); + + int a = wSubOne.GetLowestSetBit(); + BigInteger m = wSubOne.ShiftRight(a); + + for (int i = 0; i < iterations; ++i) + { + BigInteger b = BigIntegers.CreateRandomInRange(Two, wSubTwo, random); + BigInteger g = b.Gcd(w); + + if (g.CompareTo(One) > 0) + return MROutput.ProvablyCompositeWithFactor(g); + + BigInteger z = b.ModPow(m, w); + + if (z.Equals(One) || z.Equals(wSubOne)) + continue; + + bool primeToBase = false; + + BigInteger x = z; + for (int j = 1; j < a; ++j) + { + z = z.ModPow(Two, w); + + if (z.Equals(wSubOne)) + { + primeToBase = true; + break; + } + + if (z.Equals(One)) + break; + + x = z; + } + + if (!primeToBase) + { + if (!z.Equals(One)) + { + x = z; + z = z.ModPow(Two, w); + + if (!z.Equals(One)) + { + x = z; + } + } + + g = x.Subtract(One).Gcd(w); + + if (g.CompareTo(One) > 0) + return MROutput.ProvablyCompositeWithFactor(g); + + return MROutput.ProvablyCompositeNotPrimePower(); + } + } + + return MROutput.ProbablyPrime(); + } + + /** + * A fast check for small divisors, up to some implementation-specific limit. + * + * @param candidate + * the {@link BigInteger} instance to test for division by small factors. + * + * @return true if the candidate is found to have any small factors, + * false otherwise. + */ + public static bool HasAnySmallFactors(BigInteger candidate) + { + CheckCandidate(candidate, "candidate"); + + return ImplHasAnySmallFactors(candidate); + } + + /** + * FIPS 186-4 C.3.1 Miller-Rabin Probabilistic Primality Test + * + * Run several iterations of the Miller-Rabin algorithm with randomly-chosen bases. + * + * @param candidate + * the {@link BigInteger} instance to test for primality. + * @param random + * the source of randomness to use to choose bases. + * @param iterations + * the number of randomly-chosen bases to perform the test for. + * @return false if any witness to compositeness is found amongst the chosen bases + * (so candidate is definitely NOT prime), or else true + * (indicating primality with some probability dependent on the number of iterations + * that were performed). + */ + public static bool IsMRProbablePrime(BigInteger candidate, SecureRandom random, int iterations) + { + CheckCandidate(candidate, "candidate"); + + if (random == null) + throw new ArgumentException("cannot be null", "random"); + if (iterations < 1) + throw new ArgumentException("must be > 0", "iterations"); + + if (candidate.BitLength == 2) + return true; + if (!candidate.TestBit(0)) + return false; + + BigInteger w = candidate; + BigInteger wSubOne = candidate.Subtract(One); + BigInteger wSubTwo = candidate.Subtract(Two); + + int a = wSubOne.GetLowestSetBit(); + BigInteger m = wSubOne.ShiftRight(a); + + for (int i = 0; i < iterations; ++i) + { + BigInteger b = BigIntegers.CreateRandomInRange(Two, wSubTwo, random); + + if (!ImplMRProbablePrimeToBase(w, wSubOne, m, a, b)) + return false; + } + + return true; + } + + /** + * FIPS 186-4 C.3.1 Miller-Rabin Probabilistic Primality Test (to a fixed base). + * + * Run a single iteration of the Miller-Rabin algorithm against the specified base. + * + * @param candidate + * the {@link BigInteger} instance to test for primality. + * @param baseValue + * the base value to use for this iteration. + * @return false if the specified base is a witness to compositeness (so + * candidate is definitely NOT prime), or else true. + */ + public static bool IsMRProbablePrimeToBase(BigInteger candidate, BigInteger baseValue) + { + CheckCandidate(candidate, "candidate"); + CheckCandidate(baseValue, "baseValue"); + + if (baseValue.CompareTo(candidate.Subtract(One)) >= 0) + throw new ArgumentException("must be < ('candidate' - 1)", "baseValue"); + + if (candidate.BitLength == 2) + return true; + + BigInteger w = candidate; + BigInteger wSubOne = candidate.Subtract(One); + + int a = wSubOne.GetLowestSetBit(); + BigInteger m = wSubOne.ShiftRight(a); + + return ImplMRProbablePrimeToBase(w, wSubOne, m, a, baseValue); + } + + private static void CheckCandidate(BigInteger n, string name) + { + if (n == null || n.SignValue < 1 || n.BitLength < 2) + throw new ArgumentException("must be non-null and >= 2", name); + } + + private static bool ImplHasAnySmallFactors(BigInteger x) + { + /* + * Bundle trial divisors into ~32-bit moduli then use fast tests on the ~32-bit remainders. + */ + int m = 2 * 3 * 5 * 7 * 11 * 13 * 17 * 19 * 23; + int r = x.Mod(BigInteger.ValueOf(m)).IntValue; + if ((r % 2) == 0 || (r % 3) == 0 || (r % 5) == 0 || (r % 7) == 0 || (r % 11) == 0 || (r % 13) == 0 + || (r % 17) == 0 || (r % 19) == 0 || (r % 23) == 0) + { + return true; + } + + m = 29 * 31 * 37 * 41 * 43; + r = x.Mod(BigInteger.ValueOf(m)).IntValue; + if ((r % 29) == 0 || (r % 31) == 0 || (r % 37) == 0 || (r % 41) == 0 || (r % 43) == 0) + { + return true; + } + + m = 47 * 53 * 59 * 61 * 67; + r = x.Mod(BigInteger.ValueOf(m)).IntValue; + if ((r % 47) == 0 || (r % 53) == 0 || (r % 59) == 0 || (r % 61) == 0 || (r % 67) == 0) + { + return true; + } + + m = 71 * 73 * 79 * 83; + r = x.Mod(BigInteger.ValueOf(m)).IntValue; + if ((r % 71) == 0 || (r % 73) == 0 || (r % 79) == 0 || (r % 83) == 0) + { + return true; + } + + m = 89 * 97 * 101 * 103; + r = x.Mod(BigInteger.ValueOf(m)).IntValue; + if ((r % 89) == 0 || (r % 97) == 0 || (r % 101) == 0 || (r % 103) == 0) + { + return true; + } + + m = 107 * 109 * 113 * 127; + r = x.Mod(BigInteger.ValueOf(m)).IntValue; + if ((r % 107) == 0 || (r % 109) == 0 || (r % 113) == 0 || (r % 127) == 0) + { + return true; + } + + m = 131 * 137 * 139 * 149; + r = x.Mod(BigInteger.ValueOf(m)).IntValue; + if ((r % 131) == 0 || (r % 137) == 0 || (r % 139) == 0 || (r % 149) == 0) + { + return true; + } + + m = 151 * 157 * 163 * 167; + r = x.Mod(BigInteger.ValueOf(m)).IntValue; + if ((r % 151) == 0 || (r % 157) == 0 || (r % 163) == 0 || (r % 167) == 0) + { + return true; + } + + m = 173 * 179 * 181 * 191; + r = x.Mod(BigInteger.ValueOf(m)).IntValue; + if ((r % 173) == 0 || (r % 179) == 0 || (r % 181) == 0 || (r % 191) == 0) + { + return true; + } + + m = 193 * 197 * 199 * 211; + r = x.Mod(BigInteger.ValueOf(m)).IntValue; + if ((r % 193) == 0 || (r % 197) == 0 || (r % 199) == 0 || (r % 211) == 0) + { + return true; + } + + /* + * NOTE: Unit tests depend on SMALL_FACTOR_LIMIT matching the + * highest small factor tested here. + */ + return false; + } + + private static bool ImplMRProbablePrimeToBase(BigInteger w, BigInteger wSubOne, BigInteger m, int a, BigInteger b) + { + BigInteger z = b.ModPow(m, w); + + if (z.Equals(One) || z.Equals(wSubOne)) + return true; + + bool result = false; + + for (int j = 1; j < a; ++j) + { + z = z.ModPow(Two, w); + + if (z.Equals(wSubOne)) + { + result = true; + break; + } + + if (z.Equals(One)) + return false; + } + + return result; + } + + private static STOutput ImplSTRandomPrime(IDigest d, int length, byte[] primeSeed) + { + int dLen = d.GetDigestSize(); + + if (length < 33) + { + int primeGenCounter = 0; + + byte[] c0 = new byte[dLen]; + byte[] c1 = new byte[dLen]; + + for (;;) + { + Hash(d, primeSeed, c0, 0); + Inc(primeSeed, 1); + + Hash(d, primeSeed, c1, 0); + Inc(primeSeed, 1); + + uint c = Extract32(c0) ^ Extract32(c1); + c &= (uint.MaxValue >> (32 - length)); + c |= (1U << (length - 1)) | 1U; + + ++primeGenCounter; + + if (IsPrime32(c)) + { + return new STOutput(BigInteger.ValueOf((long)c), primeSeed, primeGenCounter); + } + + if (primeGenCounter > (4 * length)) + { + throw new InvalidOperationException("Too many iterations in Shawe-Taylor Random_Prime Routine"); + } + } + } + + STOutput rec = ImplSTRandomPrime(d, (length + 3)/2, primeSeed); + + { + BigInteger c0 = rec.Prime; + primeSeed = rec.PrimeSeed; + int primeGenCounter = rec.PrimeGenCounter; + + int outlen = 8 * dLen; + int iterations = (length - 1)/outlen; + + int oldCounter = primeGenCounter; + + BigInteger x = HashGen(d, primeSeed, iterations + 1); + x = x.Mod(One.ShiftLeft(length - 1)).SetBit(length - 1); + + BigInteger c0x2 = c0.ShiftLeft(1); + BigInteger tx2 = x.Subtract(One).Divide(c0x2).Add(One).ShiftLeft(1); + int dt = 0; + + BigInteger c = tx2.Multiply(c0).Add(One); + + /* + * TODO Since the candidate primes are generated by constant steps ('c0x2'), + * sieving could be used here in place of the 'HasAnySmallFactors' approach. + */ + for (;;) + { + if (c.BitLength > length) + { + tx2 = One.ShiftLeft(length - 1).Subtract(One).Divide(c0x2).Add(One).ShiftLeft(1); + c = tx2.Multiply(c0).Add(One); + } + + ++primeGenCounter; + + /* + * This is an optimization of the original algorithm, using trial division to screen out + * many non-primes quickly. + * + * NOTE: 'primeSeed' is still incremented as if we performed the full check! + */ + if (!ImplHasAnySmallFactors(c)) + { + BigInteger a = HashGen(d, primeSeed, iterations + 1); + a = a.Mod(c.Subtract(Three)).Add(Two); + + tx2 = tx2.Add(BigInteger.ValueOf(dt)); + dt = 0; + + BigInteger z = a.ModPow(tx2, c); + + if (c.Gcd(z.Subtract(One)).Equals(One) && z.ModPow(c0, c).Equals(One)) + { + return new STOutput(c, primeSeed, primeGenCounter); + } + } + else + { + Inc(primeSeed, iterations + 1); + } + + if (primeGenCounter >= ((4 * length) + oldCounter)) + { + throw new InvalidOperationException("Too many iterations in Shawe-Taylor Random_Prime Routine"); + } + + dt += 2; + c = c.Add(c0x2); + } + } + } + + private static uint Extract32(byte[] bs) + { + uint result = 0; + + int count = System.Math.Min(4, bs.Length); + for (int i = 0; i < count; ++i) + { + uint b = bs[bs.Length - (i + 1)]; + result |= (b << (8 * i)); + } + + return result; + } + + private static void Hash(IDigest d, byte[] input, byte[] output, int outPos) + { + d.BlockUpdate(input, 0, input.Length); + d.DoFinal(output, outPos); + } + + private static BigInteger HashGen(IDigest d, byte[] seed, int count) + { + int dLen = d.GetDigestSize(); + int pos = count * dLen; + byte[] buf = new byte[pos]; + for (int i = 0; i < count; ++i) + { + pos -= dLen; + Hash(d, seed, buf, pos); + Inc(seed, 1); + } + return new BigInteger(1, buf); + } + + private static void Inc(byte[] seed, int c) + { + int pos = seed.Length; + while (c > 0 && --pos >= 0) + { + c += seed[pos]; + seed[pos] = (byte)c; + c >>= 8; + } + } + + private static bool IsPrime32(uint x) + { + /* + * Use wheel factorization with 2, 3, 5 to select trial divisors. + */ + + if (x <= 5) + { + return x == 2 || x == 3 || x == 5; + } + + if ((x & 1) == 0 || (x % 3) == 0 || (x % 5) == 0) + { + return false; + } + + uint[] ds = new uint[]{ 1, 7, 11, 13, 17, 19, 23, 29 }; + uint b = 0; + for (int pos = 1; ; pos = 0) + { + /* + * Trial division by wheel-selected divisors + */ + while (pos < ds.Length) + { + uint d = b + ds[pos]; + if (x % d == 0) + { + return x < 30; + } + ++pos; + } + + b += 30; + + if ((b >> 16 != 0) || (b * b >= x)) + { + return true; + } + } + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/ECAlgorithms.cs b/bc-sharp-crypto/src/math/ec/ECAlgorithms.cs new file mode 100644 index 0000000..5d60de4 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/ECAlgorithms.cs @@ -0,0 +1,479 @@ +using System; + +using Org.BouncyCastle.Math.EC.Endo; +using Org.BouncyCastle.Math.EC.Multiplier; +using Org.BouncyCastle.Math.Field; + +namespace Org.BouncyCastle.Math.EC +{ + public class ECAlgorithms + { + public static bool IsF2mCurve(ECCurve c) + { + return IsF2mField(c.Field); + } + + public static bool IsF2mField(IFiniteField field) + { + return field.Dimension > 1 && field.Characteristic.Equals(BigInteger.Two) + && field is IPolynomialExtensionField; + } + + public static bool IsFpCurve(ECCurve c) + { + return IsFpField(c.Field); + } + + public static bool IsFpField(IFiniteField field) + { + return field.Dimension == 1; + } + + public static ECPoint SumOfMultiplies(ECPoint[] ps, BigInteger[] ks) + { + if (ps == null || ks == null || ps.Length != ks.Length || ps.Length < 1) + throw new ArgumentException("point and scalar arrays should be non-null, and of equal, non-zero, length"); + + int count = ps.Length; + switch (count) + { + case 1: + return ps[0].Multiply(ks[0]); + case 2: + return SumOfTwoMultiplies(ps[0], ks[0], ps[1], ks[1]); + default: + break; + } + + ECPoint p = ps[0]; + ECCurve c = p.Curve; + + ECPoint[] imported = new ECPoint[count]; + imported[0] = p; + for (int i = 1; i < count; ++i) + { + imported[i] = ImportPoint(c, ps[i]); + } + + GlvEndomorphism glvEndomorphism = c.GetEndomorphism() as GlvEndomorphism; + if (glvEndomorphism != null) + { + return ValidatePoint(ImplSumOfMultipliesGlv(imported, ks, glvEndomorphism)); + } + + return ValidatePoint(ImplSumOfMultiplies(imported, ks)); + } + + public static ECPoint SumOfTwoMultiplies(ECPoint P, BigInteger a, ECPoint Q, BigInteger b) + { + ECCurve cp = P.Curve; + Q = ImportPoint(cp, Q); + + // Point multiplication for Koblitz curves (using WTNAF) beats Shamir's trick + { + AbstractF2mCurve f2mCurve = cp as AbstractF2mCurve; + if (f2mCurve != null && f2mCurve.IsKoblitz) + { + return ValidatePoint(P.Multiply(a).Add(Q.Multiply(b))); + } + } + + GlvEndomorphism glvEndomorphism = cp.GetEndomorphism() as GlvEndomorphism; + if (glvEndomorphism != null) + { + return ValidatePoint( + ImplSumOfMultipliesGlv(new ECPoint[] { P, Q }, new BigInteger[] { a, b }, glvEndomorphism)); + } + + return ValidatePoint(ImplShamirsTrickWNaf(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) + { + ECCurve cp = P.Curve; + Q = ImportPoint(cp, Q); + + return ValidatePoint(ImplShamirsTrickJsf(P, k, Q, l)); + } + + public static ECPoint ImportPoint(ECCurve c, ECPoint p) + { + ECCurve cp = p.Curve; + if (!c.Equals(cp)) + throw new ArgumentException("Point must be on the same curve"); + + return c.ImportPoint(p); + } + + public static void MontgomeryTrick(ECFieldElement[] zs, int off, int len) + { + MontgomeryTrick(zs, off, len, null); + } + + public static void MontgomeryTrick(ECFieldElement[] zs, int off, int len, ECFieldElement scale) + { + /* + * Uses the "Montgomery Trick" to invert many field elements, with only a single actual + * field inversion. See e.g. the paper: + * "Fast Multi-scalar Multiplication Methods on Elliptic Curves with Precomputation Strategy Using Montgomery Trick" + * by Katsuyuki Okeya, Kouichi Sakurai. + */ + + ECFieldElement[] c = new ECFieldElement[len]; + c[0] = zs[off]; + + int i = 0; + while (++i < len) + { + c[i] = c[i - 1].Multiply(zs[off + i]); + } + + --i; + + if (scale != null) + { + c[i] = c[i].Multiply(scale); + } + + ECFieldElement u = c[i].Invert(); + + while (i > 0) + { + int j = off + i--; + ECFieldElement tmp = zs[j]; + zs[j] = c[i].Multiply(u); + u = u.Multiply(tmp); + } + + zs[off] = u; + } + + /** + * Simple shift-and-add multiplication. Serves as reference implementation + * to verify (possibly faster) implementations, and for very small scalars. + * + * @param p + * The point to multiply. + * @param k + * The multiplier. + * @return The result of the point multiplication kP. + */ + public static ECPoint ReferenceMultiply(ECPoint p, BigInteger k) + { + BigInteger x = k.Abs(); + ECPoint q = p.Curve.Infinity; + int t = x.BitLength; + if (t > 0) + { + if (x.TestBit(0)) + { + q = p; + } + for (int i = 1; i < t; i++) + { + p = p.Twice(); + if (x.TestBit(i)) + { + q = q.Add(p); + } + } + } + return k.SignValue < 0 ? q.Negate() : q; + } + + public static ECPoint ValidatePoint(ECPoint p) + { + if (!p.IsValid()) + throw new ArgumentException("Invalid point", "p"); + + return p; + } + + internal static ECPoint ImplShamirsTrickJsf(ECPoint P, BigInteger k, ECPoint Q, BigInteger l) + { + ECCurve curve = P.Curve; + ECPoint infinity = curve.Infinity; + + // TODO conjugate co-Z addition (ZADDC) can return both of these + ECPoint PaddQ = P.Add(Q); + ECPoint PsubQ = P.Subtract(Q); + + ECPoint[] points = new ECPoint[] { Q, PsubQ, P, PaddQ }; + curve.NormalizeAll(points); + + ECPoint[] table = new ECPoint[] { + points[3].Negate(), points[2].Negate(), points[1].Negate(), + points[0].Negate(), infinity, points[0], + points[1], points[2], points[3] }; + + byte[] jsf = WNafUtilities.GenerateJsf(k, l); + + ECPoint R = infinity; + + int i = jsf.Length; + while (--i >= 0) + { + int jsfi = jsf[i]; + + // NOTE: The shifting ensures the sign is extended correctly + int kDigit = ((jsfi << 24) >> 28), lDigit = ((jsfi << 28) >> 28); + + int index = 4 + (kDigit * 3) + lDigit; + R = R.TwicePlus(table[index]); + } + + return R; + } + + internal static ECPoint ImplShamirsTrickWNaf(ECPoint P, BigInteger k, + ECPoint Q, BigInteger l) + { + bool negK = k.SignValue < 0, negL = l.SignValue < 0; + + k = k.Abs(); + l = l.Abs(); + + int widthP = System.Math.Max(2, System.Math.Min(16, WNafUtilities.GetWindowSize(k.BitLength))); + int widthQ = System.Math.Max(2, System.Math.Min(16, WNafUtilities.GetWindowSize(l.BitLength))); + + WNafPreCompInfo infoP = WNafUtilities.Precompute(P, widthP, true); + WNafPreCompInfo infoQ = WNafUtilities.Precompute(Q, widthQ, true); + + ECPoint[] preCompP = negK ? infoP.PreCompNeg : infoP.PreComp; + ECPoint[] preCompQ = negL ? infoQ.PreCompNeg : infoQ.PreComp; + ECPoint[] preCompNegP = negK ? infoP.PreComp : infoP.PreCompNeg; + ECPoint[] preCompNegQ = negL ? infoQ.PreComp : infoQ.PreCompNeg; + + byte[] wnafP = WNafUtilities.GenerateWindowNaf(widthP, k); + byte[] wnafQ = WNafUtilities.GenerateWindowNaf(widthQ, l); + + return ImplShamirsTrickWNaf(preCompP, preCompNegP, wnafP, preCompQ, preCompNegQ, wnafQ); + } + + internal static ECPoint ImplShamirsTrickWNaf(ECPoint P, BigInteger k, ECPointMap pointMapQ, BigInteger l) + { + bool negK = k.SignValue < 0, negL = l.SignValue < 0; + + k = k.Abs(); + l = l.Abs(); + + int width = System.Math.Max(2, System.Math.Min(16, WNafUtilities.GetWindowSize(System.Math.Max(k.BitLength, l.BitLength)))); + + ECPoint Q = WNafUtilities.MapPointWithPrecomp(P, width, true, pointMapQ); + WNafPreCompInfo infoP = WNafUtilities.GetWNafPreCompInfo(P); + WNafPreCompInfo infoQ = WNafUtilities.GetWNafPreCompInfo(Q); + + ECPoint[] preCompP = negK ? infoP.PreCompNeg : infoP.PreComp; + ECPoint[] preCompQ = negL ? infoQ.PreCompNeg : infoQ.PreComp; + ECPoint[] preCompNegP = negK ? infoP.PreComp : infoP.PreCompNeg; + ECPoint[] preCompNegQ = negL ? infoQ.PreComp : infoQ.PreCompNeg; + + byte[] wnafP = WNafUtilities.GenerateWindowNaf(width, k); + byte[] wnafQ = WNafUtilities.GenerateWindowNaf(width, l); + + return ImplShamirsTrickWNaf(preCompP, preCompNegP, wnafP, preCompQ, preCompNegQ, wnafQ); + } + + private static ECPoint ImplShamirsTrickWNaf(ECPoint[] preCompP, ECPoint[] preCompNegP, byte[] wnafP, + ECPoint[] preCompQ, ECPoint[] preCompNegQ, byte[] wnafQ) + { + int len = System.Math.Max(wnafP.Length, wnafQ.Length); + + ECCurve curve = preCompP[0].Curve; + ECPoint infinity = curve.Infinity; + + ECPoint R = infinity; + int zeroes = 0; + + for (int i = len - 1; i >= 0; --i) + { + int wiP = i < wnafP.Length ? (int)(sbyte)wnafP[i] : 0; + int wiQ = i < wnafQ.Length ? (int)(sbyte)wnafQ[i] : 0; + + if ((wiP | wiQ) == 0) + { + ++zeroes; + continue; + } + + ECPoint r = infinity; + if (wiP != 0) + { + int nP = System.Math.Abs(wiP); + ECPoint[] tableP = wiP < 0 ? preCompNegP : preCompP; + r = r.Add(tableP[nP >> 1]); + } + if (wiQ != 0) + { + int nQ = System.Math.Abs(wiQ); + ECPoint[] tableQ = wiQ < 0 ? preCompNegQ : preCompQ; + r = r.Add(tableQ[nQ >> 1]); + } + + if (zeroes > 0) + { + R = R.TimesPow2(zeroes); + zeroes = 0; + } + + R = R.TwicePlus(r); + } + + if (zeroes > 0) + { + R = R.TimesPow2(zeroes); + } + + return R; + } + + internal static ECPoint ImplSumOfMultiplies(ECPoint[] ps, BigInteger[] ks) + { + int count = ps.Length; + bool[] negs = new bool[count]; + WNafPreCompInfo[] infos = new WNafPreCompInfo[count]; + byte[][] wnafs = new byte[count][]; + + for (int i = 0; i < count; ++i) + { + BigInteger ki = ks[i]; negs[i] = ki.SignValue < 0; ki = ki.Abs(); + + int width = System.Math.Max(2, System.Math.Min(16, WNafUtilities.GetWindowSize(ki.BitLength))); + infos[i] = WNafUtilities.Precompute(ps[i], width, true); + wnafs[i] = WNafUtilities.GenerateWindowNaf(width, ki); + } + + return ImplSumOfMultiplies(negs, infos, wnafs); + } + + internal static ECPoint ImplSumOfMultipliesGlv(ECPoint[] ps, BigInteger[] ks, GlvEndomorphism glvEndomorphism) + { + BigInteger n = ps[0].Curve.Order; + + int len = ps.Length; + + BigInteger[] abs = new BigInteger[len << 1]; + for (int i = 0, j = 0; i < len; ++i) + { + BigInteger[] ab = glvEndomorphism.DecomposeScalar(ks[i].Mod(n)); + abs[j++] = ab[0]; + abs[j++] = ab[1]; + } + + ECPointMap pointMap = glvEndomorphism.PointMap; + if (glvEndomorphism.HasEfficientPointMap) + { + return ECAlgorithms.ImplSumOfMultiplies(ps, pointMap, abs); + } + + ECPoint[] pqs = new ECPoint[len << 1]; + for (int i = 0, j = 0; i < len; ++i) + { + ECPoint p = ps[i], q = pointMap.Map(p); + pqs[j++] = p; + pqs[j++] = q; + } + + return ECAlgorithms.ImplSumOfMultiplies(pqs, abs); + } + + internal static ECPoint ImplSumOfMultiplies(ECPoint[] ps, ECPointMap pointMap, BigInteger[] ks) + { + int halfCount = ps.Length, fullCount = halfCount << 1; + + bool[] negs = new bool[fullCount]; + WNafPreCompInfo[] infos = new WNafPreCompInfo[fullCount]; + byte[][] wnafs = new byte[fullCount][]; + + for (int i = 0; i < halfCount; ++i) + { + int j0 = i << 1, j1 = j0 + 1; + + BigInteger kj0 = ks[j0]; negs[j0] = kj0.SignValue < 0; kj0 = kj0.Abs(); + BigInteger kj1 = ks[j1]; negs[j1] = kj1.SignValue < 0; kj1 = kj1.Abs(); + + int width = System.Math.Max(2, System.Math.Min(16, WNafUtilities.GetWindowSize(System.Math.Max(kj0.BitLength, kj1.BitLength)))); + + ECPoint P = ps[i], Q = WNafUtilities.MapPointWithPrecomp(P, width, true, pointMap); + infos[j0] = WNafUtilities.GetWNafPreCompInfo(P); + infos[j1] = WNafUtilities.GetWNafPreCompInfo(Q); + wnafs[j0] = WNafUtilities.GenerateWindowNaf(width, kj0); + wnafs[j1] = WNafUtilities.GenerateWindowNaf(width, kj1); + } + + return ImplSumOfMultiplies(negs, infos, wnafs); + } + + private static ECPoint ImplSumOfMultiplies(bool[] negs, WNafPreCompInfo[] infos, byte[][] wnafs) + { + int len = 0, count = wnafs.Length; + for (int i = 0; i < count; ++i) + { + len = System.Math.Max(len, wnafs[i].Length); + } + + ECCurve curve = infos[0].PreComp[0].Curve; + ECPoint infinity = curve.Infinity; + + ECPoint R = infinity; + int zeroes = 0; + + for (int i = len - 1; i >= 0; --i) + { + ECPoint r = infinity; + + for (int j = 0; j < count; ++j) + { + byte[] wnaf = wnafs[j]; + int wi = i < wnaf.Length ? (int)(sbyte)wnaf[i] : 0; + if (wi != 0) + { + int n = System.Math.Abs(wi); + WNafPreCompInfo info = infos[j]; + ECPoint[] table = (wi < 0 == negs[j]) ? info.PreComp : info.PreCompNeg; + r = r.Add(table[n >> 1]); + } + } + + if (r == infinity) + { + ++zeroes; + continue; + } + + if (zeroes > 0) + { + R = R.TimesPow2(zeroes); + zeroes = 0; + } + + R = R.TwicePlus(r); + } + + if (zeroes > 0) + { + R = R.TimesPow2(zeroes); + } + + return R; + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/ECCurve.cs b/bc-sharp-crypto/src/math/ec/ECCurve.cs new file mode 100644 index 0000000..6ccd97e --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/ECCurve.cs @@ -0,0 +1,1131 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Math.EC.Abc; +using Org.BouncyCastle.Math.EC.Endo; +using Org.BouncyCastle.Math.EC.Multiplier; +using Org.BouncyCastle.Math.Field; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Math.EC +{ + /// Base class for an elliptic curve. + public abstract class ECCurve + { + public const int COORD_AFFINE = 0; + public const int COORD_HOMOGENEOUS = 1; + public const int COORD_JACOBIAN = 2; + public const int COORD_JACOBIAN_CHUDNOVSKY = 3; + public const int COORD_JACOBIAN_MODIFIED = 4; + public const int COORD_LAMBDA_AFFINE = 5; + public const int COORD_LAMBDA_PROJECTIVE = 6; + public const int COORD_SKEWED = 7; + + public static int[] GetAllCoordinateSystems() + { + return new int[]{ COORD_AFFINE, COORD_HOMOGENEOUS, COORD_JACOBIAN, COORD_JACOBIAN_CHUDNOVSKY, + COORD_JACOBIAN_MODIFIED, COORD_LAMBDA_AFFINE, COORD_LAMBDA_PROJECTIVE, COORD_SKEWED }; + } + + public class Config + { + protected ECCurve outer; + protected int coord; + protected ECEndomorphism endomorphism; + protected ECMultiplier multiplier; + + internal Config(ECCurve outer, int coord, ECEndomorphism endomorphism, ECMultiplier multiplier) + { + this.outer = outer; + this.coord = coord; + this.endomorphism = endomorphism; + this.multiplier = multiplier; + } + + public Config SetCoordinateSystem(int coord) + { + this.coord = coord; + return this; + } + + public Config SetEndomorphism(ECEndomorphism endomorphism) + { + this.endomorphism = endomorphism; + return this; + } + + public Config SetMultiplier(ECMultiplier multiplier) + { + this.multiplier = multiplier; + return this; + } + + public ECCurve Create() + { + if (!outer.SupportsCoordinateSystem(coord)) + { + throw new InvalidOperationException("unsupported coordinate system"); + } + + ECCurve c = outer.CloneCurve(); + if (c == outer) + { + throw new InvalidOperationException("implementation returned current curve"); + } + + c.m_coord = coord; + c.m_endomorphism = endomorphism; + c.m_multiplier = multiplier; + + return c; + } + } + + protected readonly IFiniteField m_field; + protected ECFieldElement m_a, m_b; + protected BigInteger m_order, m_cofactor; + + protected int m_coord = COORD_AFFINE; + protected ECEndomorphism m_endomorphism = null; + protected ECMultiplier m_multiplier = null; + + protected ECCurve(IFiniteField field) + { + this.m_field = field; + } + + public abstract int FieldSize { get; } + public abstract ECFieldElement FromBigInteger(BigInteger x); + public abstract bool IsValidFieldElement(BigInteger x); + + public virtual Config Configure() + { + return new Config(this, this.m_coord, this.m_endomorphism, this.m_multiplier); + } + + public virtual ECPoint ValidatePoint(BigInteger x, BigInteger y) + { + ECPoint p = CreatePoint(x, y); + if (!p.IsValid()) + { + throw new ArgumentException("Invalid point coordinates"); + } + return p; + } + + [Obsolete("Per-point compression property will be removed")] + public virtual ECPoint ValidatePoint(BigInteger x, BigInteger y, bool withCompression) + { + ECPoint p = CreatePoint(x, y, withCompression); + if (!p.IsValid()) + { + throw new ArgumentException("Invalid point coordinates"); + } + return p; + } + + public virtual ECPoint CreatePoint(BigInteger x, BigInteger y) + { + return CreatePoint(x, y, false); + } + + [Obsolete("Per-point compression property will be removed")] + public virtual ECPoint CreatePoint(BigInteger x, BigInteger y, bool withCompression) + { + return CreateRawPoint(FromBigInteger(x), FromBigInteger(y), withCompression); + } + + protected abstract ECCurve CloneCurve(); + + protected internal abstract ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, bool withCompression); + + protected internal abstract ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression); + + protected virtual ECMultiplier CreateDefaultMultiplier() + { + GlvEndomorphism glvEndomorphism = m_endomorphism as GlvEndomorphism; + if (glvEndomorphism != null) + { + return new GlvMultiplier(this, glvEndomorphism); + } + + return new WNafL2RMultiplier(); + } + + public virtual bool SupportsCoordinateSystem(int coord) + { + return coord == COORD_AFFINE; + } + + public virtual PreCompInfo GetPreCompInfo(ECPoint point, string name) + { + CheckPoint(point); + lock (point) + { + IDictionary table = point.m_preCompTable; + return table == null ? null : (PreCompInfo)table[name]; + } + } + + /** + * Adds PreCompInfo for a point on this curve, under a given name. Used by + * ECMultipliers to save the precomputation for this ECPoint for use + * by subsequent multiplication. + * + * @param point + * The ECPoint to store precomputations for. + * @param name + * A String used to index precomputations of different types. + * @param preCompInfo + * The values precomputed by the ECMultiplier. + */ + public virtual void SetPreCompInfo(ECPoint point, string name, PreCompInfo preCompInfo) + { + CheckPoint(point); + lock (point) + { + IDictionary table = point.m_preCompTable; + if (null == table) + { + point.m_preCompTable = table = Platform.CreateHashtable(4); + } + table[name] = preCompInfo; + } + } + + public virtual ECPoint ImportPoint(ECPoint p) + { + if (this == p.Curve) + { + return p; + } + if (p.IsInfinity) + { + return Infinity; + } + + // TODO Default behaviour could be improved if the two curves have the same coordinate system by copying any Z coordinates. + p = p.Normalize(); + + return ValidatePoint(p.XCoord.ToBigInteger(), p.YCoord.ToBigInteger(), p.IsCompressed); + } + + /** + * Normalization ensures that any projective coordinate is 1, and therefore that the x, y + * coordinates reflect those of the equivalent point in an affine coordinate system. Where more + * than one point is to be normalized, this method will generally be more efficient than + * normalizing each point separately. + * + * @param points + * An array of points that will be updated in place with their normalized versions, + * where necessary + */ + public virtual void NormalizeAll(ECPoint[] points) + { + NormalizeAll(points, 0, points.Length, null); + } + + /** + * Normalization ensures that any projective coordinate is 1, and therefore that the x, y + * coordinates reflect those of the equivalent point in an affine coordinate system. Where more + * than one point is to be normalized, this method will generally be more efficient than + * normalizing each point separately. An (optional) z-scaling factor can be applied; effectively + * each z coordinate is scaled by this value prior to normalization (but only one + * actual multiplication is needed). + * + * @param points + * An array of points that will be updated in place with their normalized versions, + * where necessary + * @param off + * The start of the range of points to normalize + * @param len + * The length of the range of points to normalize + * @param iso + * The (optional) z-scaling factor - can be null + */ + public virtual void NormalizeAll(ECPoint[] points, int off, int len, ECFieldElement iso) + { + CheckPoints(points, off, len); + + switch (this.CoordinateSystem) + { + case ECCurve.COORD_AFFINE: + case ECCurve.COORD_LAMBDA_AFFINE: + { + if (iso != null) + throw new ArgumentException("not valid for affine coordinates", "iso"); + + return; + } + } + + /* + * Figure out which of the points actually need to be normalized + */ + ECFieldElement[] zs = new ECFieldElement[len]; + int[] indices = new int[len]; + int count = 0; + for (int i = 0; i < len; ++i) + { + ECPoint p = points[off + i]; + if (null != p && (iso != null || !p.IsNormalized())) + { + zs[count] = p.GetZCoord(0); + indices[count++] = off + i; + } + } + + if (count == 0) + { + return; + } + + ECAlgorithms.MontgomeryTrick(zs, 0, count, iso); + + for (int j = 0; j < count; ++j) + { + int index = indices[j]; + points[index] = points[index].Normalize(zs[j]); + } + } + + public abstract ECPoint Infinity { get; } + + public virtual IFiniteField Field + { + get { return m_field; } + } + + public virtual ECFieldElement A + { + get { return m_a; } + } + + public virtual ECFieldElement B + { + get { return m_b; } + } + + public virtual BigInteger Order + { + get { return m_order; } + } + + public virtual BigInteger Cofactor + { + get { return m_cofactor; } + } + + public virtual int CoordinateSystem + { + get { return m_coord; } + } + + protected virtual void CheckPoint(ECPoint point) + { + if (null == point || (this != point.Curve)) + throw new ArgumentException("must be non-null and on this curve", "point"); + } + + protected virtual void CheckPoints(ECPoint[] points) + { + CheckPoints(points, 0, points.Length); + } + + protected virtual void CheckPoints(ECPoint[] points, int off, int len) + { + if (points == null) + throw new ArgumentNullException("points"); + if (off < 0 || len < 0 || (off > (points.Length - len))) + throw new ArgumentException("invalid range specified", "points"); + + for (int i = 0; i < len; ++i) + { + ECPoint point = points[off + i]; + if (null != point && this != point.Curve) + throw new ArgumentException("entries must be null or on this curve", "points"); + } + } + + public virtual bool Equals(ECCurve other) + { + if (this == other) + return true; + if (null == other) + return false; + return Field.Equals(other.Field) + && A.ToBigInteger().Equals(other.A.ToBigInteger()) + && B.ToBigInteger().Equals(other.B.ToBigInteger()); + } + + public override bool Equals(object obj) + { + return Equals(obj as ECCurve); + } + + public override int GetHashCode() + { + return Field.GetHashCode() + ^ Integers.RotateLeft(A.ToBigInteger().GetHashCode(), 8) + ^ Integers.RotateLeft(B.ToBigInteger().GetHashCode(), 16); + } + + protected abstract ECPoint DecompressPoint(int yTilde, BigInteger X1); + + public virtual ECEndomorphism GetEndomorphism() + { + return m_endomorphism; + } + + /** + * Sets the default ECMultiplier, unless already set. + */ + public virtual ECMultiplier GetMultiplier() + { + lock (this) + { + if (this.m_multiplier == null) + { + this.m_multiplier = CreateDefaultMultiplier(); + } + return this.m_multiplier; + } + } + + /** + * 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 virtual ECPoint DecodePoint(byte[] encoded) + { + ECPoint p = null; + int expectedLength = (FieldSize + 7) / 8; + + byte type = encoded[0]; + switch (type) + { + case 0x00: // infinity + { + if (encoded.Length != 1) + throw new ArgumentException("Incorrect length for infinity encoding", "encoded"); + + p = Infinity; + break; + } + + case 0x02: // compressed + case 0x03: // compressed + { + if (encoded.Length != (expectedLength + 1)) + throw new ArgumentException("Incorrect length for compressed encoding", "encoded"); + + int yTilde = type & 1; + BigInteger X = new BigInteger(1, encoded, 1, expectedLength); + + p = DecompressPoint(yTilde, X); + if (!p.SatisfiesCofactor()) + throw new ArgumentException("Invalid point"); + + break; + } + + case 0x04: // uncompressed + { + if (encoded.Length != (2 * expectedLength + 1)) + throw new ArgumentException("Incorrect length for uncompressed encoding", "encoded"); + + BigInteger X = new BigInteger(1, encoded, 1, expectedLength); + BigInteger Y = new BigInteger(1, encoded, 1 + expectedLength, expectedLength); + + p = ValidatePoint(X, Y); + break; + } + + case 0x06: // hybrid + case 0x07: // hybrid + { + if (encoded.Length != (2 * expectedLength + 1)) + throw new ArgumentException("Incorrect length for hybrid encoding", "encoded"); + + BigInteger X = new BigInteger(1, encoded, 1, expectedLength); + BigInteger Y = new BigInteger(1, encoded, 1 + expectedLength, expectedLength); + + if (Y.TestBit(0) != (type == 0x07)) + throw new ArgumentException("Inconsistent Y coordinate in hybrid encoding", "encoded"); + + p = ValidatePoint(X, Y); + break; + } + + default: + throw new FormatException("Invalid point encoding " + type); + } + + if (type != 0x00 && p.IsInfinity) + throw new ArgumentException("Invalid infinity encoding", "encoded"); + + return p; + } + } + + public abstract class AbstractFpCurve + : ECCurve + { + protected AbstractFpCurve(BigInteger q) + : base(FiniteFields.GetPrimeField(q)) + { + } + + public override bool IsValidFieldElement(BigInteger x) + { + return x != null && x.SignValue >= 0 && x.CompareTo(Field.Characteristic) < 0; + } + + protected override ECPoint DecompressPoint(int yTilde, BigInteger X1) + { + ECFieldElement x = FromBigInteger(X1); + ECFieldElement rhs = x.Square().Add(A).Multiply(x).Add(B); + ECFieldElement y = rhs.Sqrt(); + + /* + * If y is not a square, then we haven't got a point on the curve + */ + if (y == null) + throw new ArgumentException("Invalid point compression"); + + if (y.TestBitZero() != (yTilde == 1)) + { + // Use the other root + y = y.Negate(); + } + + return CreateRawPoint(x, y, true); + } + } + + /** + * Elliptic curve over Fp + */ + public class FpCurve + : AbstractFpCurve + { + private const int FP_DEFAULT_COORDS = COORD_JACOBIAN_MODIFIED; + + protected readonly BigInteger m_q, m_r; + protected readonly FpPoint m_infinity; + + public FpCurve(BigInteger q, BigInteger a, BigInteger b) + : this(q, a, b, null, null) + { + } + + public FpCurve(BigInteger q, BigInteger a, BigInteger b, BigInteger order, BigInteger cofactor) + : base(q) + { + this.m_q = q; + this.m_r = FpFieldElement.CalculateResidue(q); + this.m_infinity = new FpPoint(this, null, null); + + this.m_a = FromBigInteger(a); + this.m_b = FromBigInteger(b); + this.m_order = order; + this.m_cofactor = cofactor; + this.m_coord = FP_DEFAULT_COORDS; + } + + protected FpCurve(BigInteger q, BigInteger r, ECFieldElement a, ECFieldElement b) + : this(q, r, a, b, null, null) + { + } + + protected FpCurve(BigInteger q, BigInteger r, ECFieldElement a, ECFieldElement b, BigInteger order, BigInteger cofactor) + : base(q) + { + this.m_q = q; + this.m_r = r; + this.m_infinity = new FpPoint(this, null, null); + + this.m_a = a; + this.m_b = b; + this.m_order = order; + this.m_cofactor = cofactor; + this.m_coord = FP_DEFAULT_COORDS; + } + + protected override ECCurve CloneCurve() + { + return new FpCurve(m_q, m_r, m_a, m_b, m_order, m_cofactor); + } + + public override bool SupportsCoordinateSystem(int coord) + { + switch (coord) + { + case COORD_AFFINE: + case COORD_HOMOGENEOUS: + case COORD_JACOBIAN: + case COORD_JACOBIAN_MODIFIED: + return true; + default: + return false; + } + } + + public virtual BigInteger Q + { + get { return m_q; } + } + + public override ECPoint Infinity + { + get { return m_infinity; } + } + + public override int FieldSize + { + get { return m_q.BitLength; } + } + + public override ECFieldElement FromBigInteger(BigInteger x) + { + return new FpFieldElement(this.m_q, this.m_r, x); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, bool withCompression) + { + return new FpPoint(this, x, y, withCompression); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + { + return new FpPoint(this, x, y, zs, withCompression); + } + + public override ECPoint ImportPoint(ECPoint p) + { + if (this != p.Curve && this.CoordinateSystem == COORD_JACOBIAN && !p.IsInfinity) + { + switch (p.Curve.CoordinateSystem) + { + case COORD_JACOBIAN: + case COORD_JACOBIAN_CHUDNOVSKY: + case COORD_JACOBIAN_MODIFIED: + return new FpPoint(this, + FromBigInteger(p.RawXCoord.ToBigInteger()), + FromBigInteger(p.RawYCoord.ToBigInteger()), + new ECFieldElement[] { FromBigInteger(p.GetZCoord(0).ToBigInteger()) }, + p.IsCompressed); + default: + break; + } + } + + return base.ImportPoint(p); + } + } + + public abstract class AbstractF2mCurve + : ECCurve + { + public static BigInteger Inverse(int m, int[] ks, BigInteger x) + { + return new LongArray(x).ModInverse(m, ks).ToBigInteger(); + } + + /** + * The auxiliary values s0 and + * s1 used for partial modular reduction for + * Koblitz curves. + */ + private BigInteger[] si = null; + + private static IFiniteField BuildField(int m, int k1, int k2, int k3) + { + if (k1 == 0) + { + throw new ArgumentException("k1 must be > 0"); + } + + if (k2 == 0) + { + if (k3 != 0) + { + throw new ArgumentException("k3 must be 0 if k2 == 0"); + } + + return FiniteFields.GetBinaryExtensionField(new int[]{ 0, k1, m }); + } + + if (k2 <= k1) + { + throw new ArgumentException("k2 must be > k1"); + } + + if (k3 <= k2) + { + throw new ArgumentException("k3 must be > k2"); + } + + return FiniteFields.GetBinaryExtensionField(new int[]{ 0, k1, k2, k3, m }); + } + + protected AbstractF2mCurve(int m, int k1, int k2, int k3) + : base(BuildField(m, k1, k2, k3)) + { + } + + public override bool IsValidFieldElement(BigInteger x) + { + return x != null && x.SignValue >= 0 && x.BitLength <= FieldSize; + } + + [Obsolete("Per-point compression property will be removed")] + public override ECPoint CreatePoint(BigInteger x, BigInteger y, bool withCompression) + { + ECFieldElement X = FromBigInteger(x), Y = FromBigInteger(y); + + switch (this.CoordinateSystem) + { + case COORD_LAMBDA_AFFINE: + case COORD_LAMBDA_PROJECTIVE: + { + if (X.IsZero) + { + if (!Y.Square().Equals(B)) + throw new ArgumentException(); + } + else + { + // Y becomes Lambda (X + Y/X) here + Y = Y.Divide(X).Add(X); + } + break; + } + default: + { + break; + } + } + + return CreateRawPoint(X, Y, withCompression); + } + + protected override ECPoint DecompressPoint(int yTilde, BigInteger X1) + { + ECFieldElement xp = FromBigInteger(X1), yp = null; + if (xp.IsZero) + { + yp = B.Sqrt(); + } + else + { + ECFieldElement beta = xp.Square().Invert().Multiply(B).Add(A).Add(xp); + ECFieldElement z = SolveQuadradicEquation(beta); + + if (z != null) + { + if (z.TestBitZero() != (yTilde == 1)) + { + z = z.AddOne(); + } + + switch (this.CoordinateSystem) + { + case COORD_LAMBDA_AFFINE: + case COORD_LAMBDA_PROJECTIVE: + { + yp = z.Add(xp); + break; + } + default: + { + yp = z.Multiply(xp); + break; + } + } + } + } + + if (yp == null) + throw new ArgumentException("Invalid point compression"); + + return CreateRawPoint(xp, yp, true); + } + + /** + * 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) + { + if (beta.IsZero) + return beta; + + ECFieldElement gamma, z, zeroElement = FromBigInteger(BigInteger.Zero); + + int m = FieldSize; + do + { + ECFieldElement t = FromBigInteger(BigInteger.Arbitrary(m)); + z = zeroElement; + ECFieldElement w = beta; + for (int i = 1; i < m; i++) + { + ECFieldElement w2 = w.Square(); + z = z.Square().Add(w2.Multiply(t)); + w = w2.Add(beta); + } + if (!w.IsZero) + { + return null; + } + gamma = z.Square().Add(z); + } + while (gamma.IsZero); + + return z; + } + + /** + * @return the auxiliary values s0 and + * s1 used for partial modular reduction for + * Koblitz curves. + */ + internal virtual BigInteger[] GetSi() + { + if (si == null) + { + lock (this) + { + if (si == null) + { + si = Tnaf.GetSi(this); + } + } + } + return si; + } + + /** + * Returns true if this is a Koblitz curve (ABC curve). + * @return true if this is a Koblitz curve (ABC curve), false otherwise + */ + public virtual bool IsKoblitz + { + get + { + return m_order != null && m_cofactor != null && m_b.IsOne && (m_a.IsZero || m_a.IsOne); + } + } + } + + /** + * Elliptic curves over F2m. The Weierstrass equation is given by + * y2 + xy = x3 + ax2 + b. + */ + public class F2mCurve + : AbstractF2mCurve + { + private const int F2M_DEFAULT_COORDS = COORD_LAMBDA_PROJECTIVE; + + /** + * The exponent m of F2m. + */ + private readonly 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 readonly int k1; + + /** + * TPB: Always set to 0
    + * PPB: The integer k2 where xm + + * xk3 + xk2 + xk1 + 1 + * represents the reduction polynomial f(z).
    + */ + private readonly int k2; + + /** + * TPB: Always set to 0
    + * PPB: The integer k3 where xm + + * xk3 + xk2 + xk1 + 1 + * represents the reduction polynomial f(z).
    + */ + private readonly int k3; + + /** + * The point at infinity on this curve. + */ + protected readonly F2mPoint m_infinity; + + /** + * 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 F2mCurve( + 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 order The order of the main subgroup of the elliptic curve. + * @param cofactor The cofactor of the elliptic curve, i.e. + * #Ea(F2m) = h * n. + */ + public F2mCurve( + int m, + int k, + BigInteger a, + BigInteger b, + BigInteger order, + BigInteger cofactor) + : this(m, k, 0, 0, a, b, order, cofactor) + { + } + + /** + * 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 F2mCurve( + 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 order The order of the main subgroup of the elliptic curve. + * @param cofactor The cofactor of the elliptic curve, i.e. + * #Ea(F2m) = h * n. + */ + public F2mCurve( + int m, + int k1, + int k2, + int k3, + BigInteger a, + BigInteger b, + BigInteger order, + BigInteger cofactor) + : base(m, k1, k2, k3) + { + this.m = m; + this.k1 = k1; + this.k2 = k2; + this.k3 = k3; + this.m_order = order; + this.m_cofactor = cofactor; + this.m_infinity = new F2mPoint(this, null, null); + + if (k1 == 0) + throw new ArgumentException("k1 must be > 0"); + + if (k2 == 0) + { + if (k3 != 0) + throw new ArgumentException("k3 must be 0 if k2 == 0"); + } + else + { + if (k2 <= k1) + throw new ArgumentException("k2 must be > k1"); + + if (k3 <= k2) + throw new ArgumentException("k3 must be > k2"); + } + + this.m_a = FromBigInteger(a); + this.m_b = FromBigInteger(b); + this.m_coord = F2M_DEFAULT_COORDS; + } + + protected F2mCurve(int m, int k1, int k2, int k3, ECFieldElement a, ECFieldElement b, BigInteger order, BigInteger cofactor) + : base(m, k1, k2, k3) + { + this.m = m; + this.k1 = k1; + this.k2 = k2; + this.k3 = k3; + this.m_order = order; + this.m_cofactor = cofactor; + + this.m_infinity = new F2mPoint(this, null, null); + this.m_a = a; + this.m_b = b; + this.m_coord = F2M_DEFAULT_COORDS; + } + + protected override ECCurve CloneCurve() + { + return new F2mCurve(m, k1, k2, k3, m_a, m_b, m_order, m_cofactor); + } + + public override bool SupportsCoordinateSystem(int coord) + { + switch (coord) + { + case COORD_AFFINE: + case COORD_HOMOGENEOUS: + case COORD_LAMBDA_PROJECTIVE: + return true; + default: + return false; + } + } + + protected override ECMultiplier CreateDefaultMultiplier() + { + if (IsKoblitz) + { + return new WTauNafMultiplier(); + } + + return base.CreateDefaultMultiplier(); + } + + public override int FieldSize + { + get { return m; } + } + + public override ECFieldElement FromBigInteger(BigInteger x) + { + return new F2mFieldElement(this.m, this.k1, this.k2, this.k3, x); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, bool withCompression) + { + return new F2mPoint(this, x, y, withCompression); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + { + return new F2mPoint(this, x, y, zs, withCompression); + } + + public override ECPoint Infinity + { + get { return m_infinity; } + } + + public int M + { + get { return m; } + } + + /** + * Return true if curve uses a Trinomial basis. + * + * @return true if curve Trinomial, false otherwise. + */ + public bool IsTrinomial() + { + return k2 == 0 && k3 == 0; + } + + public int K1 + { + get { return k1; } + } + + public int K2 + { + get { return k2; } + } + + public int K3 + { + get { return k3; } + } + + [Obsolete("Use 'Order' property instead")] + public BigInteger N + { + get { return m_order; } + } + + [Obsolete("Use 'Cofactor' property instead")] + public BigInteger H + { + get { return m_cofactor; } + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/ECFieldElement.cs b/bc-sharp-crypto/src/math/ec/ECFieldElement.cs new file mode 100644 index 0000000..d0e008a --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/ECFieldElement.cs @@ -0,0 +1,928 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Math.Raw; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Math.EC +{ + public abstract class ECFieldElement + { + public abstract BigInteger ToBigInteger(); + public abstract string FieldName { get; } + public abstract int FieldSize { get; } + public abstract ECFieldElement Add(ECFieldElement b); + public abstract ECFieldElement AddOne(); + 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 virtual int BitLength + { + get { return ToBigInteger().BitLength; } + } + + public virtual bool IsOne + { + get { return BitLength == 1; } + } + + public virtual bool IsZero + { + get { return 0 == ToBigInteger().SignValue; } + } + + public virtual ECFieldElement MultiplyMinusProduct(ECFieldElement b, ECFieldElement x, ECFieldElement y) + { + return Multiply(b).Subtract(x.Multiply(y)); + } + + public virtual ECFieldElement MultiplyPlusProduct(ECFieldElement b, ECFieldElement x, ECFieldElement y) + { + return Multiply(b).Add(x.Multiply(y)); + } + + public virtual ECFieldElement SquareMinusProduct(ECFieldElement x, ECFieldElement y) + { + return Square().Subtract(x.Multiply(y)); + } + + public virtual ECFieldElement SquarePlusProduct(ECFieldElement x, ECFieldElement y) + { + return Square().Add(x.Multiply(y)); + } + + public virtual ECFieldElement SquarePow(int pow) + { + ECFieldElement r = this; + for (int i = 0; i < pow; ++i) + { + r = r.Square(); + } + return r; + } + + public virtual bool TestBitZero() + { + return ToBigInteger().TestBit(0); + } + + public override bool Equals(object obj) + { + return Equals(obj as ECFieldElement); + } + + public virtual bool Equals(ECFieldElement other) + { + if (this == other) + return true; + if (null == other) + return false; + return ToBigInteger().Equals(other.ToBigInteger()); + } + + public override int GetHashCode() + { + return ToBigInteger().GetHashCode(); + } + + public override string ToString() + { + return this.ToBigInteger().ToString(16); + } + + public virtual byte[] GetEncoded() + { + return BigIntegers.AsUnsignedByteArray((FieldSize + 7) / 8, ToBigInteger()); + } + } + + public class FpFieldElement + : ECFieldElement + { + private readonly BigInteger q, r, x; + + internal static BigInteger CalculateResidue(BigInteger p) + { + int bitLength = p.BitLength; + if (bitLength >= 96) + { + BigInteger firstWord = p.ShiftRight(bitLength - 64); + if (firstWord.LongValue == -1L) + { + return BigInteger.One.ShiftLeft(bitLength).Subtract(p); + } + if ((bitLength & 7) == 0) + { + return BigInteger.One.ShiftLeft(bitLength << 1).Divide(p).Negate(); + } + } + return null; + } + + [Obsolete("Use ECCurve.FromBigInteger to construct field elements")] + public FpFieldElement(BigInteger q, BigInteger x) + : this(q, CalculateResidue(q), x) + { + } + + internal FpFieldElement(BigInteger q, BigInteger r, BigInteger x) + { + if (x == null || x.SignValue < 0 || x.CompareTo(q) >= 0) + throw new ArgumentException("value invalid in Fp field element", "x"); + + this.q = q; + this.r = r; + this.x = x; + } + + public override BigInteger ToBigInteger() + { + return x; + } + + /** + * return the field name for this field. + * + * @return the string "Fp". + */ + public override string FieldName + { + get { return "Fp"; } + } + + public override int FieldSize + { + get { return q.BitLength; } + } + + public BigInteger Q + { + get { return q; } + } + + public override ECFieldElement Add( + ECFieldElement b) + { + return new FpFieldElement(q, r, ModAdd(x, b.ToBigInteger())); + } + + public override ECFieldElement AddOne() + { + BigInteger x2 = x.Add(BigInteger.One); + if (x2.CompareTo(q) == 0) + { + x2 = BigInteger.Zero; + } + return new FpFieldElement(q, r, x2); + } + + public override ECFieldElement Subtract( + ECFieldElement b) + { + return new FpFieldElement(q, r, ModSubtract(x, b.ToBigInteger())); + } + + public override ECFieldElement Multiply( + ECFieldElement b) + { + return new FpFieldElement(q, r, ModMult(x, b.ToBigInteger())); + } + + public override ECFieldElement MultiplyMinusProduct(ECFieldElement b, ECFieldElement x, ECFieldElement y) + { + BigInteger ax = this.x, bx = b.ToBigInteger(), xx = x.ToBigInteger(), yx = y.ToBigInteger(); + BigInteger ab = ax.Multiply(bx); + BigInteger xy = xx.Multiply(yx); + return new FpFieldElement(q, r, ModReduce(ab.Subtract(xy))); + } + + public override ECFieldElement MultiplyPlusProduct(ECFieldElement b, ECFieldElement x, ECFieldElement y) + { + BigInteger ax = this.x, bx = b.ToBigInteger(), xx = x.ToBigInteger(), yx = y.ToBigInteger(); + BigInteger ab = ax.Multiply(bx); + BigInteger xy = xx.Multiply(yx); + BigInteger sum = ab.Add(xy); + if (r != null && r.SignValue < 0 && sum.BitLength > (q.BitLength << 1)) + { + sum = sum.Subtract(q.ShiftLeft(q.BitLength)); + } + return new FpFieldElement(q, r, ModReduce(sum)); + } + + public override ECFieldElement Divide( + ECFieldElement b) + { + return new FpFieldElement(q, r, ModMult(x, ModInverse(b.ToBigInteger()))); + } + + public override ECFieldElement Negate() + { + return x.SignValue == 0 ? this : new FpFieldElement(q, r, q.Subtract(x)); + } + + public override ECFieldElement Square() + { + return new FpFieldElement(q, r, ModMult(x, x)); + } + + public override ECFieldElement SquareMinusProduct(ECFieldElement x, ECFieldElement y) + { + BigInteger ax = this.x, xx = x.ToBigInteger(), yx = y.ToBigInteger(); + BigInteger aa = ax.Multiply(ax); + BigInteger xy = xx.Multiply(yx); + return new FpFieldElement(q, r, ModReduce(aa.Subtract(xy))); + } + + public override ECFieldElement SquarePlusProduct(ECFieldElement x, ECFieldElement y) + { + BigInteger ax = this.x, xx = x.ToBigInteger(), yx = y.ToBigInteger(); + BigInteger aa = ax.Multiply(ax); + BigInteger xy = xx.Multiply(yx); + BigInteger sum = aa.Add(xy); + if (r != null && r.SignValue < 0 && sum.BitLength > (q.BitLength << 1)) + { + sum = sum.Subtract(q.ShiftLeft(q.BitLength)); + } + return new FpFieldElement(q, r, ModReduce(sum)); + } + + public override ECFieldElement Invert() + { + // TODO Modular inversion can be faster for a (Generalized) Mersenne Prime. + return new FpFieldElement(q, r, ModInverse(x)); + } + + /** + * return a sqrt root - the routine verifies that the calculation + * returns the right value - if none exists it returns null. + */ + public override ECFieldElement Sqrt() + { + if (IsZero || IsOne) + return this; + + if (!q.TestBit(0)) + throw Platform.CreateNotImplementedException("even value of q"); + + if (q.TestBit(1)) // q == 4m + 3 + { + BigInteger e = q.ShiftRight(2).Add(BigInteger.One); + return CheckSqrt(new FpFieldElement(q, r, x.ModPow(e, q))); + } + + if (q.TestBit(2)) // q == 8m + 5 + { + BigInteger t1 = x.ModPow(q.ShiftRight(3), q); + BigInteger t2 = ModMult(t1, x); + BigInteger t3 = ModMult(t2, t1); + + if (t3.Equals(BigInteger.One)) + { + return CheckSqrt(new FpFieldElement(q, r, t2)); + } + + // TODO This is constant and could be precomputed + BigInteger t4 = BigInteger.Two.ModPow(q.ShiftRight(2), q); + + BigInteger y = ModMult(t2, t4); + + return CheckSqrt(new FpFieldElement(q, r, y)); + } + + // q == 8m + 1 + + BigInteger legendreExponent = q.ShiftRight(1); + if (!(x.ModPow(legendreExponent, q).Equals(BigInteger.One))) + return null; + + BigInteger X = this.x; + BigInteger fourX = ModDouble(ModDouble(X)); ; + + BigInteger k = legendreExponent.Add(BigInteger.One), qMinusOne = q.Subtract(BigInteger.One); + + BigInteger U, V; + do + { + BigInteger P; + do + { + P = BigInteger.Arbitrary(q.BitLength); + } + while (P.CompareTo(q) >= 0 + || !ModReduce(P.Multiply(P).Subtract(fourX)).ModPow(legendreExponent, q).Equals(qMinusOne)); + + BigInteger[] result = LucasSequence(P, X, k); + U = result[0]; + V = result[1]; + + if (ModMult(V, V).Equals(fourX)) + { + return new FpFieldElement(q, r, ModHalfAbs(V)); + } + } + while (U.Equals(BigInteger.One) || U.Equals(qMinusOne)); + + return null; + } + + private ECFieldElement CheckSqrt(ECFieldElement z) + { + return z.Square().Equals(this) ? z : null; + } + + private BigInteger[] LucasSequence( + BigInteger P, + BigInteger Q, + BigInteger k) + { + // TODO Research and apply "common-multiplicand multiplication here" + + int n = k.BitLength; + int s = k.GetLowestSetBit(); + + Debug.Assert(k.TestBit(s)); + + BigInteger Uh = BigInteger.One; + BigInteger Vl = BigInteger.Two; + BigInteger Vh = P; + BigInteger Ql = BigInteger.One; + BigInteger Qh = BigInteger.One; + + for (int j = n - 1; j >= s + 1; --j) + { + Ql = ModMult(Ql, Qh); + + if (k.TestBit(j)) + { + Qh = ModMult(Ql, Q); + Uh = ModMult(Uh, Vh); + Vl = ModReduce(Vh.Multiply(Vl).Subtract(P.Multiply(Ql))); + Vh = ModReduce(Vh.Multiply(Vh).Subtract(Qh.ShiftLeft(1))); + } + else + { + Qh = Ql; + Uh = ModReduce(Uh.Multiply(Vl).Subtract(Ql)); + Vh = ModReduce(Vh.Multiply(Vl).Subtract(P.Multiply(Ql))); + Vl = ModReduce(Vl.Multiply(Vl).Subtract(Ql.ShiftLeft(1))); + } + } + + Ql = ModMult(Ql, Qh); + Qh = ModMult(Ql, Q); + Uh = ModReduce(Uh.Multiply(Vl).Subtract(Ql)); + Vl = ModReduce(Vh.Multiply(Vl).Subtract(P.Multiply(Ql))); + Ql = ModMult(Ql, Qh); + + for (int j = 1; j <= s; ++j) + { + Uh = ModMult(Uh, Vl); + Vl = ModReduce(Vl.Multiply(Vl).Subtract(Ql.ShiftLeft(1))); + Ql = ModMult(Ql, Ql); + } + + return new BigInteger[] { Uh, Vl }; + } + + protected virtual BigInteger ModAdd(BigInteger x1, BigInteger x2) + { + BigInteger x3 = x1.Add(x2); + if (x3.CompareTo(q) >= 0) + { + x3 = x3.Subtract(q); + } + return x3; + } + + protected virtual BigInteger ModDouble(BigInteger x) + { + BigInteger _2x = x.ShiftLeft(1); + if (_2x.CompareTo(q) >= 0) + { + _2x = _2x.Subtract(q); + } + return _2x; + } + + protected virtual BigInteger ModHalf(BigInteger x) + { + if (x.TestBit(0)) + { + x = q.Add(x); + } + return x.ShiftRight(1); + } + + protected virtual BigInteger ModHalfAbs(BigInteger x) + { + if (x.TestBit(0)) + { + x = q.Subtract(x); + } + return x.ShiftRight(1); + } + + protected virtual BigInteger ModInverse(BigInteger x) + { + int bits = FieldSize; + int len = (bits + 31) >> 5; + uint[] p = Nat.FromBigInteger(bits, q); + uint[] n = Nat.FromBigInteger(bits, x); + uint[] z = Nat.Create(len); + Mod.Invert(p, n, z); + return Nat.ToBigInteger(len, z); + } + + protected virtual BigInteger ModMult(BigInteger x1, BigInteger x2) + { + return ModReduce(x1.Multiply(x2)); + } + + protected virtual BigInteger ModReduce(BigInteger x) + { + if (r == null) + { + x = x.Mod(q); + } + else + { + bool negative = x.SignValue < 0; + if (negative) + { + x = x.Abs(); + } + int qLen = q.BitLength; + if (r.SignValue > 0) + { + BigInteger qMod = BigInteger.One.ShiftLeft(qLen); + bool rIsOne = r.Equals(BigInteger.One); + while (x.BitLength > (qLen + 1)) + { + BigInteger u = x.ShiftRight(qLen); + BigInteger v = x.Remainder(qMod); + if (!rIsOne) + { + u = u.Multiply(r); + } + x = u.Add(v); + } + } + else + { + int d = ((qLen - 1) & 31) + 1; + BigInteger mu = r.Negate(); + BigInteger u = mu.Multiply(x.ShiftRight(qLen - d)); + BigInteger quot = u.ShiftRight(qLen + d); + BigInteger v = quot.Multiply(q); + BigInteger bk1 = BigInteger.One.ShiftLeft(qLen + d); + v = v.Remainder(bk1); + x = x.Remainder(bk1); + x = x.Subtract(v); + if (x.SignValue < 0) + { + x = x.Add(bk1); + } + } + while (x.CompareTo(q) >= 0) + { + x = x.Subtract(q); + } + if (negative && x.SignValue != 0) + { + x = q.Subtract(x); + } + } + return x; + } + + protected virtual BigInteger ModSubtract(BigInteger x1, BigInteger x2) + { + BigInteger x3 = x1.Subtract(x2); + if (x3.SignValue < 0) + { + x3 = x3.Add(q); + } + return x3; + } + + public override bool Equals( + object obj) + { + if (obj == this) + return true; + + FpFieldElement other = obj as FpFieldElement; + + if (other == null) + return false; + + return Equals(other); + } + + public virtual bool Equals( + FpFieldElement other) + { + return q.Equals(other.q) && base.Equals(other); + } + + public override int GetHashCode() + { + return q.GetHashCode() ^ base.GetHashCode(); + } + } + + /** + * 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 class F2mFieldElement + : ECFieldElement + { + /** + * Indicates gaussian normal basis representation (GNB). Number chosen + * according to X9.62. GNB is not implemented at present. + */ + public const int Gnb = 1; + + /** + * Indicates trinomial basis representation (Tpb). Number chosen + * according to X9.62. + */ + public const int Tpb = 2; + + /** + * Indicates pentanomial basis representation (Ppb). Number chosen + * according to X9.62. + */ + public const int Ppb = 3; + + /** + * Tpb or Ppb. + */ + private int representation; + + /** + * The exponent m of F2m. + */ + private int m; + + private int[] ks; + + /** + * The LongArray holding the bits. + */ + private LongArray x; + + /** + * 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 F2mFieldElement( + int m, + int k1, + int k2, + int k3, + BigInteger x) + { + if (x == null || x.SignValue < 0 || x.BitLength > m) + throw new ArgumentException("value invalid in F2m field element", "x"); + + if ((k2 == 0) && (k3 == 0)) + { + this.representation = Tpb; + this.ks = new int[] { k1 }; + } + else + { + if (k2 >= k3) + throw new ArgumentException("k2 must be smaller than k3"); + if (k2 <= 0) + throw new ArgumentException("k2 must be larger than 0"); + + this.representation = Ppb; + this.ks = new int[] { k1, k2, k3 }; + } + + this.m = m; + this.x = new LongArray(x); + } + + /** + * 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 F2mFieldElement( + int m, + int k, + BigInteger x) + : this(m, k, 0, 0, x) + { + // Set k1 to k, and set k2 and k3 to 0 + } + + private F2mFieldElement(int m, int[] ks, LongArray x) + { + this.m = m; + this.representation = (ks.Length == 1) ? Tpb : Ppb; + this.ks = ks; + this.x = x; + } + + public override int BitLength + { + get { return x.Degree(); } + } + + public override bool IsOne + { + get { return x.IsOne(); } + } + + public override bool IsZero + { + get { return x.IsZero(); } + } + + public override bool TestBitZero() + { + return x.TestBitZero(); + } + + public override BigInteger ToBigInteger() + { + return x.ToBigInteger(); + } + + public override string FieldName + { + get { return "F2m"; } + } + + public override int FieldSize + { + get { 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 ArgumentException 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 is F2mFieldElement) || !(b is F2mFieldElement)) + { + throw new ArgumentException("Field elements are not " + + "both instances of F2mFieldElement"); + } + + F2mFieldElement aF2m = (F2mFieldElement)a; + F2mFieldElement bF2m = (F2mFieldElement)b; + + if (aF2m.representation != bF2m.representation) + { + // Should never occur + throw new ArgumentException("One of the F2m field elements has incorrect representation"); + } + + if ((aF2m.m != bF2m.m) || !Arrays.AreEqual(aF2m.ks, bF2m.ks)) + { + throw new ArgumentException("Field elements are not elements of the same field F2m"); + } + } + + public override ECFieldElement Add( + ECFieldElement b) + { + // No check performed here for performance reasons. Instead the + // elements involved are checked in ECPoint.F2m + // checkFieldElements(this, b); + LongArray iarrClone = this.x.Copy(); + F2mFieldElement bF2m = (F2mFieldElement)b; + iarrClone.AddShiftedByWords(bF2m.x, 0); + return new F2mFieldElement(m, ks, iarrClone); + } + + public override ECFieldElement AddOne() + { + return new F2mFieldElement(m, ks, x.AddOne()); + } + + public override ECFieldElement Subtract( + ECFieldElement b) + { + // Addition and subtraction are the same in F2m + return Add(b); + } + + public override ECFieldElement Multiply( + ECFieldElement b) + { + // Right-to-left comb multiplication in the LongArray + // 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); + return new F2mFieldElement(m, ks, x.ModMultiply(((F2mFieldElement)b).x, m, ks)); + } + + public override ECFieldElement MultiplyMinusProduct(ECFieldElement b, ECFieldElement x, ECFieldElement y) + { + return MultiplyPlusProduct(b, x, y); + } + + public override ECFieldElement MultiplyPlusProduct(ECFieldElement b, ECFieldElement x, ECFieldElement y) + { + LongArray ax = this.x, bx = ((F2mFieldElement)b).x, xx = ((F2mFieldElement)x).x, yx = ((F2mFieldElement)y).x; + + LongArray ab = ax.Multiply(bx, m, ks); + LongArray xy = xx.Multiply(yx, m, ks); + + if (ab == ax || ab == bx) + { + ab = (LongArray)ab.Copy(); + } + + ab.AddShiftedByWords(xy, 0); + ab.Reduce(m, ks); + + return new F2mFieldElement(m, ks, ab); + } + + public override ECFieldElement Divide( + ECFieldElement b) + { + // There may be more efficient implementations + ECFieldElement bInv = b.Invert(); + return Multiply(bInv); + } + + public override ECFieldElement Negate() + { + // -x == x holds for all x in F2m + return this; + } + + public override ECFieldElement Square() + { + return new F2mFieldElement(m, ks, x.ModSquare(m, ks)); + } + + public override ECFieldElement SquareMinusProduct(ECFieldElement x, ECFieldElement y) + { + return SquarePlusProduct(x, y); + } + + public override ECFieldElement SquarePlusProduct(ECFieldElement x, ECFieldElement y) + { + LongArray ax = this.x, xx = ((F2mFieldElement)x).x, yx = ((F2mFieldElement)y).x; + + LongArray aa = ax.Square(m, ks); + LongArray xy = xx.Multiply(yx, m, ks); + + if (aa == ax) + { + aa = (LongArray)aa.Copy(); + } + + aa.AddShiftedByWords(xy, 0); + aa.Reduce(m, ks); + + return new F2mFieldElement(m, ks, aa); + } + + public override ECFieldElement SquarePow(int pow) + { + return pow < 1 ? this : new F2mFieldElement(m, ks, x.ModSquareN(pow, m, ks)); + } + + public override ECFieldElement Invert() + { + return new F2mFieldElement(this.m, this.ks, this.x.ModInverse(m, ks)); + } + + public override ECFieldElement Sqrt() + { + return (x.IsZero() || x.IsOne()) ? this : SquarePow(m - 1); + } + + /** + * @return the representation of the field + * F2m, either of + * {@link F2mFieldElement.Tpb} (trinomial + * basis representation) or + * {@link F2mFieldElement.Ppb} (pentanomial + * basis representation). + */ + public int Representation + { + get { return this.representation; } + } + + /** + * @return the degree m of the reduction polynomial + * f(z). + */ + public int M + { + get { 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 K1 + { + get { return this.ks[0]; } + } + + /** + * @return Tpb: Always returns 0
    + * Ppb: The integer k2 where xm + + * xk3 + xk2 + xk1 + 1 + * represents the reduction polynomial f(z).
    + */ + public int K2 + { + get { return this.ks.Length >= 2 ? this.ks[1] : 0; } + } + + /** + * @return Tpb: Always set to 0
    + * Ppb: The integer k3 where xm + + * xk3 + xk2 + xk1 + 1 + * represents the reduction polynomial f(z).
    + */ + public int K3 + { + get { return this.ks.Length >= 3 ? this.ks[2] : 0; } + } + + public override bool Equals( + object obj) + { + if (obj == this) + return true; + + F2mFieldElement other = obj as F2mFieldElement; + + if (other == null) + return false; + + return Equals(other); + } + + public virtual bool Equals( + F2mFieldElement other) + { + return ((this.m == other.m) + && (this.representation == other.representation) + && Arrays.AreEqual(this.ks, other.ks) + && (this.x.Equals(other.x))); + } + + public override int GetHashCode() + { + return x.GetHashCode() ^ m ^ Arrays.GetHashCode(ks); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/ECPoint.cs b/bc-sharp-crypto/src/math/ec/ECPoint.cs new file mode 100644 index 0000000..a5ba515 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/ECPoint.cs @@ -0,0 +1,2064 @@ +using System; +using System.Collections; +using System.Diagnostics; +using System.Text; + +using Org.BouncyCastle.Math.EC.Multiplier; + +namespace Org.BouncyCastle.Math.EC +{ + /** + * base class for points on elliptic curves. + */ + public abstract class ECPoint + { + protected static ECFieldElement[] EMPTY_ZS = new ECFieldElement[0]; + + protected static ECFieldElement[] GetInitialZCoords(ECCurve curve) + { + // Cope with null curve, most commonly used by implicitlyCa + int coord = null == curve ? ECCurve.COORD_AFFINE : curve.CoordinateSystem; + + switch (coord) + { + case ECCurve.COORD_AFFINE: + case ECCurve.COORD_LAMBDA_AFFINE: + return EMPTY_ZS; + default: + break; + } + + ECFieldElement one = curve.FromBigInteger(BigInteger.One); + + switch (coord) + { + case ECCurve.COORD_HOMOGENEOUS: + case ECCurve.COORD_JACOBIAN: + case ECCurve.COORD_LAMBDA_PROJECTIVE: + return new ECFieldElement[] { one }; + case ECCurve.COORD_JACOBIAN_CHUDNOVSKY: + return new ECFieldElement[] { one, one, one }; + case ECCurve.COORD_JACOBIAN_MODIFIED: + return new ECFieldElement[] { one, curve.A }; + default: + throw new ArgumentException("unknown coordinate system"); + } + } + + protected internal readonly ECCurve m_curve; + protected internal readonly ECFieldElement m_x, m_y; + protected internal readonly ECFieldElement[] m_zs; + protected internal readonly bool m_withCompression; + + // Dictionary is (string -> PreCompInfo) + protected internal IDictionary m_preCompTable = null; + + protected ECPoint(ECCurve curve, ECFieldElement x, ECFieldElement y, bool withCompression) + : this(curve, x, y, GetInitialZCoords(curve), withCompression) + { + } + + internal ECPoint(ECCurve curve, ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + { + this.m_curve = curve; + this.m_x = x; + this.m_y = y; + this.m_zs = zs; + this.m_withCompression = withCompression; + } + + protected internal bool SatisfiesCofactor() + { + BigInteger h = Curve.Cofactor; + return h == null || h.Equals(BigInteger.One) || !ECAlgorithms.ReferenceMultiply(this, h).IsInfinity; + } + + protected abstract bool SatisfiesCurveEquation(); + + public ECPoint GetDetachedPoint() + { + return Normalize().Detach(); + } + + public virtual ECCurve Curve + { + get { return m_curve; } + } + + protected abstract ECPoint Detach(); + + protected virtual int CurveCoordinateSystem + { + get + { + // Cope with null curve, most commonly used by implicitlyCa + return null == m_curve ? ECCurve.COORD_AFFINE : m_curve.CoordinateSystem; + } + } + + /** + * Normalizes this point, and then returns the affine x-coordinate. + * + * Note: normalization can be expensive, this method is deprecated in favour + * of caller-controlled normalization. + */ + [Obsolete("Use AffineXCoord, or Normalize() and XCoord, instead")] + public virtual ECFieldElement X + { + get { return Normalize().XCoord; } + } + + /** + * Normalizes this point, and then returns the affine y-coordinate. + * + * Note: normalization can be expensive, this method is deprecated in favour + * of caller-controlled normalization. + */ + [Obsolete("Use AffineYCoord, or Normalize() and YCoord, instead")] + public virtual ECFieldElement Y + { + get { return Normalize().YCoord; } + } + + /** + * Returns the affine x-coordinate after checking that this point is normalized. + * + * @return The affine x-coordinate of this point + * @throws IllegalStateException if the point is not normalized + */ + public virtual ECFieldElement AffineXCoord + { + get + { + CheckNormalized(); + return XCoord; + } + } + + /** + * Returns the affine y-coordinate after checking that this point is normalized + * + * @return The affine y-coordinate of this point + * @throws IllegalStateException if the point is not normalized + */ + public virtual ECFieldElement AffineYCoord + { + get + { + CheckNormalized(); + return YCoord; + } + } + + /** + * Returns the x-coordinate. + * + * Caution: depending on the curve's coordinate system, this may not be the same value as in an + * affine coordinate system; use Normalize() to get a point where the coordinates have their + * affine values, or use AffineXCoord if you expect the point to already have been normalized. + * + * @return the x-coordinate of this point + */ + public virtual ECFieldElement XCoord + { + get { return m_x; } + } + + /** + * Returns the y-coordinate. + * + * Caution: depending on the curve's coordinate system, this may not be the same value as in an + * affine coordinate system; use Normalize() to get a point where the coordinates have their + * affine values, or use AffineYCoord if you expect the point to already have been normalized. + * + * @return the y-coordinate of this point + */ + public virtual ECFieldElement YCoord + { + get { return m_y; } + } + + public virtual ECFieldElement GetZCoord(int index) + { + return (index < 0 || index >= m_zs.Length) ? null : m_zs[index]; + } + + public virtual ECFieldElement[] GetZCoords() + { + int zsLen = m_zs.Length; + if (zsLen == 0) + { + return m_zs; + } + ECFieldElement[] copy = new ECFieldElement[zsLen]; + Array.Copy(m_zs, 0, copy, 0, zsLen); + return copy; + } + + protected internal ECFieldElement RawXCoord + { + get { return m_x; } + } + + protected internal ECFieldElement RawYCoord + { + get { return m_y; } + } + + protected internal ECFieldElement[] RawZCoords + { + get { return m_zs; } + } + + protected virtual void CheckNormalized() + { + if (!IsNormalized()) + throw new InvalidOperationException("point not in normal form"); + } + + public virtual bool IsNormalized() + { + int coord = this.CurveCoordinateSystem; + + return coord == ECCurve.COORD_AFFINE + || coord == ECCurve.COORD_LAMBDA_AFFINE + || IsInfinity + || RawZCoords[0].IsOne; + } + + /** + * Normalization ensures that any projective coordinate is 1, and therefore that the x, y + * coordinates reflect those of the equivalent point in an affine coordinate system. + * + * @return a new ECPoint instance representing the same point, but with normalized coordinates + */ + public virtual ECPoint Normalize() + { + if (this.IsInfinity) + { + return this; + } + + switch (this.CurveCoordinateSystem) + { + case ECCurve.COORD_AFFINE: + case ECCurve.COORD_LAMBDA_AFFINE: + { + return this; + } + default: + { + ECFieldElement Z1 = RawZCoords[0]; + if (Z1.IsOne) + { + return this; + } + + return Normalize(Z1.Invert()); + } + } + } + + internal virtual ECPoint Normalize(ECFieldElement zInv) + { + switch (this.CurveCoordinateSystem) + { + case ECCurve.COORD_HOMOGENEOUS: + case ECCurve.COORD_LAMBDA_PROJECTIVE: + { + return CreateScaledPoint(zInv, zInv); + } + case ECCurve.COORD_JACOBIAN: + case ECCurve.COORD_JACOBIAN_CHUDNOVSKY: + case ECCurve.COORD_JACOBIAN_MODIFIED: + { + ECFieldElement zInv2 = zInv.Square(), zInv3 = zInv2.Multiply(zInv); + return CreateScaledPoint(zInv2, zInv3); + } + default: + { + throw new InvalidOperationException("not a projective coordinate system"); + } + } + } + + protected virtual ECPoint CreateScaledPoint(ECFieldElement sx, ECFieldElement sy) + { + return Curve.CreateRawPoint(RawXCoord.Multiply(sx), RawYCoord.Multiply(sy), IsCompressed); + } + + public bool IsInfinity + { + get { return m_x == null && m_y == null; } + } + + public bool IsCompressed + { + get { return m_withCompression; } + } + + public bool IsValid() + { + if (IsInfinity) + return true; + + // TODO Sanity-check the field elements + + ECCurve curve = Curve; + if (curve != null) + { + if (!SatisfiesCurveEquation()) + return false; + + if (!SatisfiesCofactor()) + return false; + } + + return true; + } + + public virtual ECPoint ScaleX(ECFieldElement scale) + { + return IsInfinity + ? this + : Curve.CreateRawPoint(RawXCoord.Multiply(scale), RawYCoord, RawZCoords, IsCompressed); + } + + public virtual ECPoint ScaleY(ECFieldElement scale) + { + return IsInfinity + ? this + : Curve.CreateRawPoint(RawXCoord, RawYCoord.Multiply(scale), RawZCoords, IsCompressed); + } + + public override bool Equals(object obj) + { + return Equals(obj as ECPoint); + } + + public virtual bool Equals(ECPoint other) + { + if (this == other) + return true; + if (null == other) + return false; + + ECCurve c1 = this.Curve, c2 = other.Curve; + bool n1 = (null == c1), n2 = (null == c2); + bool i1 = IsInfinity, i2 = other.IsInfinity; + + if (i1 || i2) + { + return (i1 && i2) && (n1 || n2 || c1.Equals(c2)); + } + + ECPoint p1 = this, p2 = other; + if (n1 && n2) + { + // Points with null curve are in affine form, so already normalized + } + else if (n1) + { + p2 = p2.Normalize(); + } + else if (n2) + { + p1 = p1.Normalize(); + } + else if (!c1.Equals(c2)) + { + return false; + } + else + { + // TODO Consider just requiring already normalized, to avoid silent performance degradation + + ECPoint[] points = new ECPoint[] { this, c1.ImportPoint(p2) }; + + // TODO This is a little strong, really only requires coZNormalizeAll to get Zs equal + c1.NormalizeAll(points); + + p1 = points[0]; + p2 = points[1]; + } + + return p1.XCoord.Equals(p2.XCoord) && p1.YCoord.Equals(p2.YCoord); + } + + public override int GetHashCode() + { + ECCurve c = this.Curve; + int hc = (null == c) ? 0 : ~c.GetHashCode(); + + if (!this.IsInfinity) + { + // TODO Consider just requiring already normalized, to avoid silent performance degradation + + ECPoint p = Normalize(); + + hc ^= p.XCoord.GetHashCode() * 17; + hc ^= p.YCoord.GetHashCode() * 257; + } + + return hc; + } + + public override string ToString() + { + if (this.IsInfinity) + { + return "INF"; + } + + StringBuilder sb = new StringBuilder(); + sb.Append('('); + sb.Append(RawXCoord); + sb.Append(','); + sb.Append(RawYCoord); + for (int i = 0; i < m_zs.Length; ++i) + { + sb.Append(','); + sb.Append(m_zs[i]); + } + sb.Append(')'); + return sb.ToString(); + } + + public virtual byte[] GetEncoded() + { + return GetEncoded(m_withCompression); + } + + public abstract byte[] GetEncoded(bool compressed); + + protected internal abstract bool CompressionYTilde { get; } + + public abstract ECPoint Add(ECPoint b); + public abstract ECPoint Subtract(ECPoint b); + public abstract ECPoint Negate(); + + public virtual ECPoint TimesPow2(int e) + { + if (e < 0) + throw new ArgumentException("cannot be negative", "e"); + + ECPoint p = this; + while (--e >= 0) + { + p = p.Twice(); + } + return p; + } + + public abstract ECPoint Twice(); + public abstract ECPoint Multiply(BigInteger b); + + public virtual ECPoint TwicePlus(ECPoint b) + { + return Twice().Add(b); + } + + public virtual ECPoint ThreeTimes() + { + return TwicePlus(this); + } + } + + public abstract class ECPointBase + : ECPoint + { + protected internal ECPointBase( + ECCurve curve, + ECFieldElement x, + ECFieldElement y, + bool withCompression) + : base(curve, x, y, withCompression) + { + } + + protected internal ECPointBase(ECCurve curve, ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + : base(curve, x, y, zs, withCompression) + { + } + + /** + * return the field element encoded with point compression. (S 4.3.6) + */ + public override byte[] GetEncoded(bool compressed) + { + if (this.IsInfinity) + { + return new byte[1]; + } + + ECPoint normed = Normalize(); + + byte[] X = normed.XCoord.GetEncoded(); + + if (compressed) + { + byte[] PO = new byte[X.Length + 1]; + PO[0] = (byte)(normed.CompressionYTilde ? 0x03 : 0x02); + Array.Copy(X, 0, PO, 1, X.Length); + return PO; + } + + byte[] Y = normed.YCoord.GetEncoded(); + + { + byte[] PO = new byte[X.Length + Y.Length + 1]; + PO[0] = 0x04; + Array.Copy(X, 0, PO, 1, X.Length); + Array.Copy(Y, 0, PO, X.Length + 1, Y.Length); + return PO; + } + } + + /** + * Multiplies this ECPoint by the given number. + * @param k The multiplicator. + * @return k * this. + */ + public override ECPoint Multiply(BigInteger k) + { + return this.Curve.GetMultiplier().Multiply(this, k); + } + } + + public abstract class AbstractFpPoint + : ECPointBase + { + protected AbstractFpPoint(ECCurve curve, ECFieldElement x, ECFieldElement y, bool withCompression) + : base(curve, x, y, withCompression) + { + } + + protected AbstractFpPoint(ECCurve curve, ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + : base(curve, x, y, zs, withCompression) + { + } + + protected internal override bool CompressionYTilde + { + get { return this.AffineYCoord.TestBitZero(); } + } + + protected override bool SatisfiesCurveEquation() + { + ECFieldElement X = this.RawXCoord, Y = this.RawYCoord, A = Curve.A, B = Curve.B; + ECFieldElement lhs = Y.Square(); + + switch (CurveCoordinateSystem) + { + case ECCurve.COORD_AFFINE: + break; + case ECCurve.COORD_HOMOGENEOUS: + { + ECFieldElement Z = this.RawZCoords[0]; + if (!Z.IsOne) + { + ECFieldElement Z2 = Z.Square(), Z3 = Z.Multiply(Z2); + lhs = lhs.Multiply(Z); + A = A.Multiply(Z2); + B = B.Multiply(Z3); + } + break; + } + case ECCurve.COORD_JACOBIAN: + case ECCurve.COORD_JACOBIAN_CHUDNOVSKY: + case ECCurve.COORD_JACOBIAN_MODIFIED: + { + ECFieldElement Z = this.RawZCoords[0]; + if (!Z.IsOne) + { + ECFieldElement Z2 = Z.Square(), Z4 = Z2.Square(), Z6 = Z2.Multiply(Z4); + A = A.Multiply(Z4); + B = B.Multiply(Z6); + } + break; + } + default: + throw new InvalidOperationException("unsupported coordinate system"); + } + + ECFieldElement rhs = X.Square().Add(A).Multiply(X).Add(B); + return lhs.Equals(rhs); + } + + public override ECPoint Subtract(ECPoint b) + { + if (b.IsInfinity) + return this; + + // Add -b + return Add(b.Negate()); + } + } + + /** + * Elliptic curve points over Fp + */ + public class FpPoint + : AbstractFpPoint + { + /** + * Create a point which encodes without point compression. + * + * @param curve the curve to use + * @param x affine x co-ordinate + * @param y affine y co-ordinate + */ + public FpPoint(ECCurve curve, ECFieldElement x, ECFieldElement y) + : this(curve, x, y, false) + { + } + + /** + * Create a point that encodes with or without point compression. + * + * @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 FpPoint(ECCurve curve, ECFieldElement x, ECFieldElement y, bool withCompression) + : base(curve, x, y, withCompression) + { + if ((x == null) != (y == null)) + throw new ArgumentException("Exactly one of the field elements is null"); + } + + internal FpPoint(ECCurve curve, ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + : base(curve, x, y, zs, withCompression) + { + } + + protected override ECPoint Detach() + { + return new FpPoint(null, AffineXCoord, AffineYCoord); + } + + public override ECFieldElement GetZCoord(int index) + { + if (index == 1 && ECCurve.COORD_JACOBIAN_MODIFIED == this.CurveCoordinateSystem) + { + return GetJacobianModifiedW(); + } + + return base.GetZCoord(index); + } + + // B.3 pg 62 + public override ECPoint Add(ECPoint b) + { + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return this; + if (this == b) + return Twice(); + + ECCurve curve = this.Curve; + int coord = curve.CoordinateSystem; + + ECFieldElement X1 = this.RawXCoord, Y1 = this.RawYCoord; + ECFieldElement X2 = b.RawXCoord, Y2 = b.RawYCoord; + + switch (coord) + { + case ECCurve.COORD_AFFINE: + { + ECFieldElement dx = X2.Subtract(X1), dy = Y2.Subtract(Y1); + + if (dx.IsZero) + { + if (dy.IsZero) + { + // this == b, i.e. this must be doubled + return Twice(); + } + + // this == -b, i.e. the result is the point at infinity + return Curve.Infinity; + } + + ECFieldElement gamma = dy.Divide(dx); + ECFieldElement X3 = gamma.Square().Subtract(X1).Subtract(X2); + ECFieldElement Y3 = gamma.Multiply(X1.Subtract(X3)).Subtract(Y1); + + return new FpPoint(Curve, X3, Y3, IsCompressed); + } + + case ECCurve.COORD_HOMOGENEOUS: + { + ECFieldElement Z1 = this.RawZCoords[0]; + ECFieldElement Z2 = b.RawZCoords[0]; + + bool Z1IsOne = Z1.IsOne; + bool Z2IsOne = Z2.IsOne; + + ECFieldElement u1 = Z1IsOne ? Y2 : Y2.Multiply(Z1); + ECFieldElement u2 = Z2IsOne ? Y1 : Y1.Multiply(Z2); + ECFieldElement u = u1.Subtract(u2); + ECFieldElement v1 = Z1IsOne ? X2 : X2.Multiply(Z1); + ECFieldElement v2 = Z2IsOne ? X1 : X1.Multiply(Z2); + ECFieldElement v = v1.Subtract(v2); + + // Check if b == this or b == -this + if (v.IsZero) + { + if (u.IsZero) + { + // this == b, i.e. this must be doubled + return this.Twice(); + } + + // this == -b, i.e. the result is the point at infinity + return curve.Infinity; + } + + // TODO Optimize for when w == 1 + ECFieldElement w = Z1IsOne ? Z2 : Z2IsOne ? Z1 : Z1.Multiply(Z2); + ECFieldElement vSquared = v.Square(); + ECFieldElement vCubed = vSquared.Multiply(v); + ECFieldElement vSquaredV2 = vSquared.Multiply(v2); + ECFieldElement A = u.Square().Multiply(w).Subtract(vCubed).Subtract(Two(vSquaredV2)); + + ECFieldElement X3 = v.Multiply(A); + ECFieldElement Y3 = vSquaredV2.Subtract(A).MultiplyMinusProduct(u, u2, vCubed); + ECFieldElement Z3 = vCubed.Multiply(w); + + return new FpPoint(curve, X3, Y3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + case ECCurve.COORD_JACOBIAN: + case ECCurve.COORD_JACOBIAN_MODIFIED: + { + ECFieldElement Z1 = this.RawZCoords[0]; + ECFieldElement Z2 = b.RawZCoords[0]; + + bool Z1IsOne = Z1.IsOne; + + ECFieldElement X3, Y3, Z3, Z3Squared = null; + + if (!Z1IsOne && Z1.Equals(Z2)) + { + // TODO Make this available as public method coZAdd? + + ECFieldElement dx = X1.Subtract(X2), dy = Y1.Subtract(Y2); + if (dx.IsZero) + { + if (dy.IsZero) + { + return Twice(); + } + return curve.Infinity; + } + + ECFieldElement C = dx.Square(); + ECFieldElement W1 = X1.Multiply(C), W2 = X2.Multiply(C); + ECFieldElement A1 = W1.Subtract(W2).Multiply(Y1); + + X3 = dy.Square().Subtract(W1).Subtract(W2); + Y3 = W1.Subtract(X3).Multiply(dy).Subtract(A1); + Z3 = dx; + + if (Z1IsOne) + { + Z3Squared = C; + } + else + { + Z3 = Z3.Multiply(Z1); + } + } + else + { + ECFieldElement Z1Squared, U2, S2; + if (Z1IsOne) + { + Z1Squared = Z1; U2 = X2; S2 = Y2; + } + else + { + Z1Squared = Z1.Square(); + U2 = Z1Squared.Multiply(X2); + ECFieldElement Z1Cubed = Z1Squared.Multiply(Z1); + S2 = Z1Cubed.Multiply(Y2); + } + + bool Z2IsOne = Z2.IsOne; + ECFieldElement Z2Squared, U1, S1; + if (Z2IsOne) + { + Z2Squared = Z2; U1 = X1; S1 = Y1; + } + else + { + Z2Squared = Z2.Square(); + U1 = Z2Squared.Multiply(X1); + ECFieldElement Z2Cubed = Z2Squared.Multiply(Z2); + S1 = Z2Cubed.Multiply(Y1); + } + + ECFieldElement H = U1.Subtract(U2); + ECFieldElement R = S1.Subtract(S2); + + // Check if b == this or b == -this + if (H.IsZero) + { + if (R.IsZero) + { + // this == b, i.e. this must be doubled + return this.Twice(); + } + + // this == -b, i.e. the result is the point at infinity + return curve.Infinity; + } + + ECFieldElement HSquared = H.Square(); + ECFieldElement G = HSquared.Multiply(H); + ECFieldElement V = HSquared.Multiply(U1); + + X3 = R.Square().Add(G).Subtract(Two(V)); + Y3 = V.Subtract(X3).MultiplyMinusProduct(R, G, S1); + + Z3 = H; + if (!Z1IsOne) + { + Z3 = Z3.Multiply(Z1); + } + if (!Z2IsOne) + { + Z3 = Z3.Multiply(Z2); + } + + // Alternative calculation of Z3 using fast square + //X3 = four(X3); + //Y3 = eight(Y3); + //Z3 = doubleProductFromSquares(Z1, Z2, Z1Squared, Z2Squared).Multiply(H); + + if (Z3 == H) + { + Z3Squared = HSquared; + } + } + + ECFieldElement[] zs; + if (coord == ECCurve.COORD_JACOBIAN_MODIFIED) + { + // TODO If the result will only be used in a subsequent addition, we don't need W3 + ECFieldElement W3 = CalculateJacobianModifiedW(Z3, Z3Squared); + + zs = new ECFieldElement[] { Z3, W3 }; + } + else + { + zs = new ECFieldElement[] { Z3 }; + } + + return new FpPoint(curve, X3, Y3, zs, IsCompressed); + } + + default: + { + throw new InvalidOperationException("unsupported coordinate system"); + } + } + } + + // B.3 pg 62 + public override ECPoint Twice() + { + if (this.IsInfinity) + return this; + + ECCurve curve = this.Curve; + + ECFieldElement Y1 = this.RawYCoord; + if (Y1.IsZero) + return curve.Infinity; + + int coord = curve.CoordinateSystem; + + ECFieldElement X1 = this.RawXCoord; + + switch (coord) + { + case ECCurve.COORD_AFFINE: + { + ECFieldElement X1Squared = X1.Square(); + ECFieldElement gamma = Three(X1Squared).Add(this.Curve.A).Divide(Two(Y1)); + ECFieldElement X3 = gamma.Square().Subtract(Two(X1)); + ECFieldElement Y3 = gamma.Multiply(X1.Subtract(X3)).Subtract(Y1); + + return new FpPoint(Curve, X3, Y3, IsCompressed); + } + + case ECCurve.COORD_HOMOGENEOUS: + { + ECFieldElement Z1 = this.RawZCoords[0]; + + bool Z1IsOne = Z1.IsOne; + + // TODO Optimize for small negative a4 and -3 + ECFieldElement w = curve.A; + if (!w.IsZero && !Z1IsOne) + { + w = w.Multiply(Z1.Square()); + } + w = w.Add(Three(X1.Square())); + + ECFieldElement s = Z1IsOne ? Y1 : Y1.Multiply(Z1); + ECFieldElement t = Z1IsOne ? Y1.Square() : s.Multiply(Y1); + ECFieldElement B = X1.Multiply(t); + ECFieldElement _4B = Four(B); + ECFieldElement h = w.Square().Subtract(Two(_4B)); + + ECFieldElement _2s = Two(s); + ECFieldElement X3 = h.Multiply(_2s); + ECFieldElement _2t = Two(t); + ECFieldElement Y3 = _4B.Subtract(h).Multiply(w).Subtract(Two(_2t.Square())); + ECFieldElement _4sSquared = Z1IsOne ? Two(_2t) : _2s.Square(); + ECFieldElement Z3 = Two(_4sSquared).Multiply(s); + + return new FpPoint(curve, X3, Y3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + case ECCurve.COORD_JACOBIAN: + { + ECFieldElement Z1 = this.RawZCoords[0]; + + bool Z1IsOne = Z1.IsOne; + + ECFieldElement Y1Squared = Y1.Square(); + ECFieldElement T = Y1Squared.Square(); + + ECFieldElement a4 = curve.A; + ECFieldElement a4Neg = a4.Negate(); + + ECFieldElement M, S; + if (a4Neg.ToBigInteger().Equals(BigInteger.ValueOf(3))) + { + ECFieldElement Z1Squared = Z1IsOne ? Z1 : Z1.Square(); + M = Three(X1.Add(Z1Squared).Multiply(X1.Subtract(Z1Squared))); + S = Four(Y1Squared.Multiply(X1)); + } + else + { + ECFieldElement X1Squared = X1.Square(); + M = Three(X1Squared); + if (Z1IsOne) + { + M = M.Add(a4); + } + else if (!a4.IsZero) + { + ECFieldElement Z1Squared = Z1IsOne ? Z1 : Z1.Square(); + ECFieldElement Z1Pow4 = Z1Squared.Square(); + if (a4Neg.BitLength < a4.BitLength) + { + M = M.Subtract(Z1Pow4.Multiply(a4Neg)); + } + else + { + M = M.Add(Z1Pow4.Multiply(a4)); + } + } + //S = two(doubleProductFromSquares(X1, Y1Squared, X1Squared, T)); + S = Four(X1.Multiply(Y1Squared)); + } + + ECFieldElement X3 = M.Square().Subtract(Two(S)); + ECFieldElement Y3 = S.Subtract(X3).Multiply(M).Subtract(Eight(T)); + + ECFieldElement Z3 = Two(Y1); + if (!Z1IsOne) + { + Z3 = Z3.Multiply(Z1); + } + + // Alternative calculation of Z3 using fast square + //ECFieldElement Z3 = doubleProductFromSquares(Y1, Z1, Y1Squared, Z1Squared); + + return new FpPoint(curve, X3, Y3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + case ECCurve.COORD_JACOBIAN_MODIFIED: + { + return TwiceJacobianModified(true); + } + + default: + { + throw new InvalidOperationException("unsupported coordinate system"); + } + } + } + + public override ECPoint TwicePlus(ECPoint b) + { + if (this == b) + return ThreeTimes(); + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return Twice(); + + ECFieldElement Y1 = this.RawYCoord; + if (Y1.IsZero) + return b; + + ECCurve curve = this.Curve; + int coord = curve.CoordinateSystem; + + switch (coord) + { + case ECCurve.COORD_AFFINE: + { + ECFieldElement X1 = this.RawXCoord; + ECFieldElement X2 = b.RawXCoord, Y2 = b.RawYCoord; + + ECFieldElement dx = X2.Subtract(X1), dy = Y2.Subtract(Y1); + + if (dx.IsZero) + { + if (dy.IsZero) + { + // this == b i.e. the result is 3P + return ThreeTimes(); + } + + // this == -b, i.e. the result is P + return this; + } + + /* + * Optimized calculation of 2P + Q, as described in "Trading Inversions for + * Multiplications in Elliptic Curve Cryptography", by Ciet, Joye, Lauter, Montgomery. + */ + + ECFieldElement X = dx.Square(), Y = dy.Square(); + ECFieldElement d = X.Multiply(Two(X1).Add(X2)).Subtract(Y); + if (d.IsZero) + { + return Curve.Infinity; + } + + ECFieldElement D = d.Multiply(dx); + ECFieldElement I = D.Invert(); + ECFieldElement L1 = d.Multiply(I).Multiply(dy); + ECFieldElement L2 = Two(Y1).Multiply(X).Multiply(dx).Multiply(I).Subtract(L1); + ECFieldElement X4 = (L2.Subtract(L1)).Multiply(L1.Add(L2)).Add(X2); + ECFieldElement Y4 = (X1.Subtract(X4)).Multiply(L2).Subtract(Y1); + + return new FpPoint(Curve, X4, Y4, IsCompressed); + } + case ECCurve.COORD_JACOBIAN_MODIFIED: + { + return TwiceJacobianModified(false).Add(b); + } + default: + { + return Twice().Add(b); + } + } + } + + public override ECPoint ThreeTimes() + { + if (this.IsInfinity) + return this; + + ECFieldElement Y1 = this.RawYCoord; + if (Y1.IsZero) + return this; + + ECCurve curve = this.Curve; + int coord = curve.CoordinateSystem; + + switch (coord) + { + case ECCurve.COORD_AFFINE: + { + ECFieldElement X1 = this.RawXCoord; + + ECFieldElement _2Y1 = Two(Y1); + ECFieldElement X = _2Y1.Square(); + ECFieldElement Z = Three(X1.Square()).Add(Curve.A); + ECFieldElement Y = Z.Square(); + + ECFieldElement d = Three(X1).Multiply(X).Subtract(Y); + if (d.IsZero) + { + return Curve.Infinity; + } + + ECFieldElement D = d.Multiply(_2Y1); + ECFieldElement I = D.Invert(); + ECFieldElement L1 = d.Multiply(I).Multiply(Z); + ECFieldElement L2 = X.Square().Multiply(I).Subtract(L1); + + ECFieldElement X4 = (L2.Subtract(L1)).Multiply(L1.Add(L2)).Add(X1); + ECFieldElement Y4 = (X1.Subtract(X4)).Multiply(L2).Subtract(Y1); + return new FpPoint(Curve, X4, Y4, IsCompressed); + } + case ECCurve.COORD_JACOBIAN_MODIFIED: + { + return TwiceJacobianModified(false).Add(this); + } + default: + { + // NOTE: Be careful about recursions between TwicePlus and ThreeTimes + return Twice().Add(this); + } + } + } + + public override ECPoint TimesPow2(int e) + { + if (e < 0) + throw new ArgumentException("cannot be negative", "e"); + if (e == 0 || this.IsInfinity) + return this; + if (e == 1) + return Twice(); + + ECCurve curve = this.Curve; + + ECFieldElement Y1 = this.RawYCoord; + if (Y1.IsZero) + return curve.Infinity; + + int coord = curve.CoordinateSystem; + + ECFieldElement W1 = curve.A; + ECFieldElement X1 = this.RawXCoord; + ECFieldElement Z1 = this.RawZCoords.Length < 1 ? curve.FromBigInteger(BigInteger.One) : this.RawZCoords[0]; + + if (!Z1.IsOne) + { + switch (coord) + { + case ECCurve.COORD_HOMOGENEOUS: + ECFieldElement Z1Sq = Z1.Square(); + X1 = X1.Multiply(Z1); + Y1 = Y1.Multiply(Z1Sq); + W1 = CalculateJacobianModifiedW(Z1, Z1Sq); + break; + case ECCurve.COORD_JACOBIAN: + W1 = CalculateJacobianModifiedW(Z1, null); + break; + case ECCurve.COORD_JACOBIAN_MODIFIED: + W1 = GetJacobianModifiedW(); + break; + } + } + + for (int i = 0; i < e; ++i) + { + if (Y1.IsZero) + return curve.Infinity; + + ECFieldElement X1Squared = X1.Square(); + ECFieldElement M = Three(X1Squared); + ECFieldElement _2Y1 = Two(Y1); + ECFieldElement _2Y1Squared = _2Y1.Multiply(Y1); + ECFieldElement S = Two(X1.Multiply(_2Y1Squared)); + ECFieldElement _4T = _2Y1Squared.Square(); + ECFieldElement _8T = Two(_4T); + + if (!W1.IsZero) + { + M = M.Add(W1); + W1 = Two(_8T.Multiply(W1)); + } + + X1 = M.Square().Subtract(Two(S)); + Y1 = M.Multiply(S.Subtract(X1)).Subtract(_8T); + Z1 = Z1.IsOne ? _2Y1 : _2Y1.Multiply(Z1); + } + + switch (coord) + { + case ECCurve.COORD_AFFINE: + ECFieldElement zInv = Z1.Invert(), zInv2 = zInv.Square(), zInv3 = zInv2.Multiply(zInv); + return new FpPoint(curve, X1.Multiply(zInv2), Y1.Multiply(zInv3), IsCompressed); + case ECCurve.COORD_HOMOGENEOUS: + X1 = X1.Multiply(Z1); + Z1 = Z1.Multiply(Z1.Square()); + return new FpPoint(curve, X1, Y1, new ECFieldElement[] { Z1 }, IsCompressed); + case ECCurve.COORD_JACOBIAN: + return new FpPoint(curve, X1, Y1, new ECFieldElement[] { Z1 }, IsCompressed); + case ECCurve.COORD_JACOBIAN_MODIFIED: + return new FpPoint(curve, X1, Y1, new ECFieldElement[] { Z1, W1 }, IsCompressed); + default: + throw new InvalidOperationException("unsupported coordinate system"); + } + } + + protected virtual ECFieldElement Two(ECFieldElement x) + { + return x.Add(x); + } + + protected virtual ECFieldElement Three(ECFieldElement x) + { + return Two(x).Add(x); + } + + protected virtual ECFieldElement Four(ECFieldElement x) + { + return Two(Two(x)); + } + + protected virtual ECFieldElement Eight(ECFieldElement x) + { + return Four(Two(x)); + } + + protected virtual ECFieldElement DoubleProductFromSquares(ECFieldElement a, ECFieldElement b, + ECFieldElement aSquared, ECFieldElement bSquared) + { + /* + * NOTE: If squaring in the field is faster than multiplication, then this is a quicker + * way to calculate 2.A.B, if A^2 and B^2 are already known. + */ + return a.Add(b).Square().Subtract(aSquared).Subtract(bSquared); + } + + public override ECPoint Negate() + { + if (IsInfinity) + return this; + + ECCurve curve = Curve; + int coord = curve.CoordinateSystem; + + if (ECCurve.COORD_AFFINE != coord) + { + return new FpPoint(curve, RawXCoord, RawYCoord.Negate(), RawZCoords, IsCompressed); + } + + return new FpPoint(curve, RawXCoord, RawYCoord.Negate(), IsCompressed); + } + + protected virtual ECFieldElement CalculateJacobianModifiedW(ECFieldElement Z, ECFieldElement ZSquared) + { + ECFieldElement a4 = this.Curve.A; + if (a4.IsZero || Z.IsOne) + return a4; + + if (ZSquared == null) + { + ZSquared = Z.Square(); + } + + ECFieldElement W = ZSquared.Square(); + ECFieldElement a4Neg = a4.Negate(); + if (a4Neg.BitLength < a4.BitLength) + { + W = W.Multiply(a4Neg).Negate(); + } + else + { + W = W.Multiply(a4); + } + return W; + } + + protected virtual ECFieldElement GetJacobianModifiedW() + { + ECFieldElement[] ZZ = this.RawZCoords; + ECFieldElement W = ZZ[1]; + if (W == null) + { + // NOTE: Rarely, TwicePlus will result in the need for a lazy W1 calculation here + ZZ[1] = W = CalculateJacobianModifiedW(ZZ[0], null); + } + return W; + } + + protected virtual FpPoint TwiceJacobianModified(bool calculateW) + { + ECFieldElement X1 = this.RawXCoord, Y1 = this.RawYCoord, Z1 = this.RawZCoords[0], W1 = GetJacobianModifiedW(); + + ECFieldElement X1Squared = X1.Square(); + ECFieldElement M = Three(X1Squared).Add(W1); + ECFieldElement _2Y1 = Two(Y1); + ECFieldElement _2Y1Squared = _2Y1.Multiply(Y1); + ECFieldElement S = Two(X1.Multiply(_2Y1Squared)); + ECFieldElement X3 = M.Square().Subtract(Two(S)); + ECFieldElement _4T = _2Y1Squared.Square(); + ECFieldElement _8T = Two(_4T); + ECFieldElement Y3 = M.Multiply(S.Subtract(X3)).Subtract(_8T); + ECFieldElement W3 = calculateW ? Two(_8T.Multiply(W1)) : null; + ECFieldElement Z3 = Z1.IsOne ? _2Y1 : _2Y1.Multiply(Z1); + + return new FpPoint(this.Curve, X3, Y3, new ECFieldElement[] { Z3, W3 }, IsCompressed); + } + } + + public abstract class AbstractF2mPoint + : ECPointBase + { + protected AbstractF2mPoint(ECCurve curve, ECFieldElement x, ECFieldElement y, bool withCompression) + : base(curve, x, y, withCompression) + { + } + + protected AbstractF2mPoint(ECCurve curve, ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + : base(curve, x, y, zs, withCompression) + { + } + + protected override bool SatisfiesCurveEquation() + { + ECCurve curve = Curve; + ECFieldElement X = this.RawXCoord, Y = this.RawYCoord, A = curve.A, B = curve.B; + ECFieldElement lhs, rhs; + + int coord = curve.CoordinateSystem; + if (coord == ECCurve.COORD_LAMBDA_PROJECTIVE) + { + ECFieldElement Z = this.RawZCoords[0]; + bool ZIsOne = Z.IsOne; + + if (X.IsZero) + { + // NOTE: For x == 0, we expect the affine-y instead of the lambda-y + lhs = Y.Square(); + rhs = B; + if (!ZIsOne) + { + ECFieldElement Z2 = Z.Square(); + rhs = rhs.Multiply(Z2); + } + } + else + { + ECFieldElement L = Y, X2 = X.Square(); + if (ZIsOne) + { + lhs = L.Square().Add(L).Add(A); + rhs = X2.Square().Add(B); + } + else + { + ECFieldElement Z2 = Z.Square(), Z4 = Z2.Square(); + lhs = L.Add(Z).MultiplyPlusProduct(L, A, Z2); + // TODO If sqrt(b) is precomputed this can be simplified to a single square + rhs = X2.SquarePlusProduct(B, Z4); + } + lhs = lhs.Multiply(X2); + } + } + else + { + lhs = Y.Add(X).Multiply(Y); + + switch (coord) + { + case ECCurve.COORD_AFFINE: + break; + case ECCurve.COORD_HOMOGENEOUS: + { + ECFieldElement Z = this.RawZCoords[0]; + if (!Z.IsOne) + { + ECFieldElement Z2 = Z.Square(), Z3 = Z.Multiply(Z2); + lhs = lhs.Multiply(Z); + A = A.Multiply(Z); + B = B.Multiply(Z3); + } + break; + } + default: + throw new InvalidOperationException("unsupported coordinate system"); + } + + rhs = X.Add(A).Multiply(X.Square()).Add(B); + } + + return lhs.Equals(rhs); + } + + public override ECPoint ScaleX(ECFieldElement scale) + { + if (this.IsInfinity) + return this; + + switch (CurveCoordinateSystem) + { + case ECCurve.COORD_LAMBDA_AFFINE: + { + // Y is actually Lambda (X + Y/X) here + ECFieldElement X = RawXCoord, L = RawYCoord; + + ECFieldElement X2 = X.Multiply(scale); + ECFieldElement L2 = L.Add(X).Divide(scale).Add(X2); + + return Curve.CreateRawPoint(X, L2, RawZCoords, IsCompressed); + } + case ECCurve.COORD_LAMBDA_PROJECTIVE: + { + // Y is actually Lambda (X + Y/X) here + ECFieldElement X = RawXCoord, L = RawYCoord, Z = RawZCoords[0]; + + // We scale the Z coordinate also, to avoid an inversion + ECFieldElement X2 = X.Multiply(scale.Square()); + ECFieldElement L2 = L.Add(X).Add(X2); + ECFieldElement Z2 = Z.Multiply(scale); + + return Curve.CreateRawPoint(X, L2, new ECFieldElement[] { Z2 }, IsCompressed); + } + default: + { + return base.ScaleX(scale); + } + } + } + + public override ECPoint ScaleY(ECFieldElement scale) + { + if (this.IsInfinity) + return this; + + switch (CurveCoordinateSystem) + { + case ECCurve.COORD_LAMBDA_AFFINE: + case ECCurve.COORD_LAMBDA_PROJECTIVE: + { + ECFieldElement X = RawXCoord, L = RawYCoord; + + // Y is actually Lambda (X + Y/X) here + ECFieldElement L2 = L.Add(X).Multiply(scale).Add(X); + + return Curve.CreateRawPoint(X, L2, RawZCoords, IsCompressed); + } + default: + { + return base.ScaleY(scale); + } + } + } + + public override ECPoint Subtract(ECPoint b) + { + if (b.IsInfinity) + return this; + + // Add -b + return Add(b.Negate()); + } + + public virtual AbstractF2mPoint Tau() + { + if (this.IsInfinity) + return this; + + ECCurve curve = this.Curve; + int coord = curve.CoordinateSystem; + + ECFieldElement X1 = this.RawXCoord; + + switch (coord) + { + case ECCurve.COORD_AFFINE: + case ECCurve.COORD_LAMBDA_AFFINE: + { + ECFieldElement Y1 = this.RawYCoord; + return (AbstractF2mPoint)curve.CreateRawPoint(X1.Square(), Y1.Square(), IsCompressed); + } + case ECCurve.COORD_HOMOGENEOUS: + case ECCurve.COORD_LAMBDA_PROJECTIVE: + { + ECFieldElement Y1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + return (AbstractF2mPoint)curve.CreateRawPoint(X1.Square(), Y1.Square(), + new ECFieldElement[] { Z1.Square() }, IsCompressed); + } + default: + { + throw new InvalidOperationException("unsupported coordinate system"); + } + } + } + + public virtual AbstractF2mPoint TauPow(int pow) + { + if (this.IsInfinity) + return this; + + ECCurve curve = this.Curve; + int coord = curve.CoordinateSystem; + + ECFieldElement X1 = this.RawXCoord; + + switch (coord) + { + case ECCurve.COORD_AFFINE: + case ECCurve.COORD_LAMBDA_AFFINE: + { + ECFieldElement Y1 = this.RawYCoord; + return (AbstractF2mPoint)curve.CreateRawPoint(X1.SquarePow(pow), Y1.SquarePow(pow), IsCompressed); + } + case ECCurve.COORD_HOMOGENEOUS: + case ECCurve.COORD_LAMBDA_PROJECTIVE: + { + ECFieldElement Y1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + return (AbstractF2mPoint)curve.CreateRawPoint(X1.SquarePow(pow), Y1.SquarePow(pow), + new ECFieldElement[] { Z1.SquarePow(pow) }, IsCompressed); + } + default: + { + throw new InvalidOperationException("unsupported coordinate system"); + } + } + } + } + + /** + * Elliptic curve points over F2m + */ + public class F2mPoint + : AbstractF2mPoint + { + /** + * @param curve base curve + * @param x x point + * @param y y point + */ + public F2mPoint( + 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 F2mPoint( + ECCurve curve, + ECFieldElement x, + ECFieldElement y, + bool withCompression) + : base(curve, x, y, withCompression) + { + if ((x == null) != (y == null)) + { + throw new ArgumentException("Exactly one of the field elements is null"); + } + + if (x != null) + { + // Check if x and y are elements of the same field + F2mFieldElement.CheckFieldElements(x, y); + + // Check if x and a are elements of the same field + if (curve != null) + { + F2mFieldElement.CheckFieldElements(x, curve.A); + } + } + } + + internal F2mPoint(ECCurve curve, ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + : base(curve, x, y, zs, withCompression) + { + } + + /** + * Constructor for point at infinity + */ + [Obsolete("Use ECCurve.Infinity property")] + public F2mPoint( + ECCurve curve) + : this(curve, null, null) + { + } + + protected override ECPoint Detach() + { + return new F2mPoint(null, AffineXCoord, AffineYCoord); + } + + public override ECFieldElement YCoord + { + get + { + int coord = this.CurveCoordinateSystem; + + switch (coord) + { + case ECCurve.COORD_LAMBDA_AFFINE: + case ECCurve.COORD_LAMBDA_PROJECTIVE: + { + ECFieldElement X = RawXCoord, L = RawYCoord; + + if (this.IsInfinity || X.IsZero) + return L; + + // Y is actually Lambda (X + Y/X) here; convert to affine value on the fly + ECFieldElement Y = L.Add(X).Multiply(X); + if (ECCurve.COORD_LAMBDA_PROJECTIVE == coord) + { + ECFieldElement Z = RawZCoords[0]; + if (!Z.IsOne) + { + Y = Y.Divide(Z); + } + } + return Y; + } + default: + { + return RawYCoord; + } + } + } + } + + protected internal override bool CompressionYTilde + { + get + { + ECFieldElement X = this.RawXCoord; + if (X.IsZero) + { + return false; + } + + ECFieldElement Y = this.RawYCoord; + + switch (this.CurveCoordinateSystem) + { + case ECCurve.COORD_LAMBDA_AFFINE: + case ECCurve.COORD_LAMBDA_PROJECTIVE: + { + // Y is actually Lambda (X + Y/X) here + return Y.TestBitZero() != X.TestBitZero(); + } + default: + { + return Y.Divide(X).TestBitZero(); + } + } + } + } + + public override ECPoint Add(ECPoint b) + { + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return this; + + ECCurve curve = this.Curve; + int coord = curve.CoordinateSystem; + + ECFieldElement X1 = this.RawXCoord; + ECFieldElement X2 = b.RawXCoord; + + switch (coord) + { + case ECCurve.COORD_AFFINE: + { + ECFieldElement Y1 = this.RawYCoord; + ECFieldElement Y2 = b.RawYCoord; + + ECFieldElement dx = X1.Add(X2), dy = Y1.Add(Y2); + if (dx.IsZero) + { + if (dy.IsZero) + { + return Twice(); + } + + return curve.Infinity; + } + + ECFieldElement L = dy.Divide(dx); + + ECFieldElement X3 = L.Square().Add(L).Add(dx).Add(curve.A); + ECFieldElement Y3 = L.Multiply(X1.Add(X3)).Add(X3).Add(Y1); + + return new F2mPoint(curve, X3, Y3, IsCompressed); + } + case ECCurve.COORD_HOMOGENEOUS: + { + ECFieldElement Y1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + ECFieldElement Y2 = b.RawYCoord, Z2 = b.RawZCoords[0]; + + bool Z1IsOne = Z1.IsOne; + ECFieldElement U1 = Y2, V1 = X2; + if (!Z1IsOne) + { + U1 = U1.Multiply(Z1); + V1 = V1.Multiply(Z1); + } + + bool Z2IsOne = Z2.IsOne; + ECFieldElement U2 = Y1, V2 = X1; + if (!Z2IsOne) + { + U2 = U2.Multiply(Z2); + V2 = V2.Multiply(Z2); + } + + ECFieldElement U = U1.Add(U2); + ECFieldElement V = V1.Add(V2); + + if (V.IsZero) + { + if (U.IsZero) + { + return Twice(); + } + + return curve.Infinity; + } + + ECFieldElement VSq = V.Square(); + ECFieldElement VCu = VSq.Multiply(V); + ECFieldElement W = Z1IsOne ? Z2 : Z2IsOne ? Z1 : Z1.Multiply(Z2); + ECFieldElement uv = U.Add(V); + ECFieldElement A = uv.MultiplyPlusProduct(U, VSq, curve.A).Multiply(W).Add(VCu); + + ECFieldElement X3 = V.Multiply(A); + ECFieldElement VSqZ2 = Z2IsOne ? VSq : VSq.Multiply(Z2); + ECFieldElement Y3 = U.MultiplyPlusProduct(X1, V, Y1).MultiplyPlusProduct(VSqZ2, uv, A); + ECFieldElement Z3 = VCu.Multiply(W); + + return new F2mPoint(curve, X3, Y3, new ECFieldElement[] { Z3 }, IsCompressed); + } + case ECCurve.COORD_LAMBDA_PROJECTIVE: + { + if (X1.IsZero) + { + if (X2.IsZero) + return curve.Infinity; + + return b.Add(this); + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + ECFieldElement L2 = b.RawYCoord, Z2 = b.RawZCoords[0]; + + bool Z1IsOne = Z1.IsOne; + ECFieldElement U2 = X2, S2 = L2; + if (!Z1IsOne) + { + U2 = U2.Multiply(Z1); + S2 = S2.Multiply(Z1); + } + + bool Z2IsOne = Z2.IsOne; + ECFieldElement U1 = X1, S1 = L1; + if (!Z2IsOne) + { + U1 = U1.Multiply(Z2); + S1 = S1.Multiply(Z2); + } + + ECFieldElement A = S1.Add(S2); + ECFieldElement B = U1.Add(U2); + + if (B.IsZero) + { + if (A.IsZero) + { + return Twice(); + } + + return curve.Infinity; + } + + ECFieldElement X3, L3, Z3; + if (X2.IsZero) + { + // TODO This can probably be optimized quite a bit + ECPoint p = this.Normalize(); + X1 = p.RawXCoord; + ECFieldElement Y1 = p.YCoord; + + ECFieldElement Y2 = L2; + ECFieldElement L = Y1.Add(Y2).Divide(X1); + + X3 = L.Square().Add(L).Add(X1).Add(curve.A); + if (X3.IsZero) + { + return new F2mPoint(curve, X3, curve.B.Sqrt(), IsCompressed); + } + + ECFieldElement Y3 = L.Multiply(X1.Add(X3)).Add(X3).Add(Y1); + L3 = Y3.Divide(X3).Add(X3); + Z3 = curve.FromBigInteger(BigInteger.One); + } + else + { + B = B.Square(); + + ECFieldElement AU1 = A.Multiply(U1); + ECFieldElement AU2 = A.Multiply(U2); + + X3 = AU1.Multiply(AU2); + if (X3.IsZero) + { + return new F2mPoint(curve, X3, curve.B.Sqrt(), IsCompressed); + } + + ECFieldElement ABZ2 = A.Multiply(B); + if (!Z2IsOne) + { + ABZ2 = ABZ2.Multiply(Z2); + } + + L3 = AU2.Add(B).SquarePlusProduct(ABZ2, L1.Add(Z1)); + + Z3 = ABZ2; + if (!Z1IsOne) + { + Z3 = Z3.Multiply(Z1); + } + } + + return new F2mPoint(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + default: + { + throw new InvalidOperationException("unsupported coordinate system"); + } + } + } + + /* (non-Javadoc) + * @see Org.BouncyCastle.Math.EC.ECPoint#twice() + */ + public override ECPoint Twice() + { + if (this.IsInfinity) + return this; + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + if (X1.IsZero) + { + // A point with X == 0 is it's own additive inverse + return curve.Infinity; + } + + int coord = curve.CoordinateSystem; + + switch (coord) + { + case ECCurve.COORD_AFFINE: + { + ECFieldElement Y1 = this.RawYCoord; + + ECFieldElement L1 = Y1.Divide(X1).Add(X1); + + ECFieldElement X3 = L1.Square().Add(L1).Add(curve.A); + ECFieldElement Y3 = X1.SquarePlusProduct(X3, L1.AddOne()); + + return new F2mPoint(curve, X3, Y3, IsCompressed); + } + case ECCurve.COORD_HOMOGENEOUS: + { + ECFieldElement Y1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + + bool Z1IsOne = Z1.IsOne; + ECFieldElement X1Z1 = Z1IsOne ? X1 : X1.Multiply(Z1); + ECFieldElement Y1Z1 = Z1IsOne ? Y1 : Y1.Multiply(Z1); + + ECFieldElement X1Sq = X1.Square(); + ECFieldElement S = X1Sq.Add(Y1Z1); + ECFieldElement V = X1Z1; + ECFieldElement vSquared = V.Square(); + ECFieldElement sv = S.Add(V); + ECFieldElement h = sv.MultiplyPlusProduct(S, vSquared, curve.A); + + ECFieldElement X3 = V.Multiply(h); + ECFieldElement Y3 = X1Sq.Square().MultiplyPlusProduct(V, h, sv); + ECFieldElement Z3 = V.Multiply(vSquared); + + return new F2mPoint(curve, X3, Y3, new ECFieldElement[] { Z3 }, IsCompressed); + } + case ECCurve.COORD_LAMBDA_PROJECTIVE: + { + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + + bool Z1IsOne = Z1.IsOne; + ECFieldElement L1Z1 = Z1IsOne ? L1 : L1.Multiply(Z1); + ECFieldElement Z1Sq = Z1IsOne ? Z1 : Z1.Square(); + ECFieldElement a = curve.A; + ECFieldElement aZ1Sq = Z1IsOne ? a : a.Multiply(Z1Sq); + ECFieldElement T = L1.Square().Add(L1Z1).Add(aZ1Sq); + if (T.IsZero) + { + return new F2mPoint(curve, T, curve.B.Sqrt(), IsCompressed); + } + + ECFieldElement X3 = T.Square(); + ECFieldElement Z3 = Z1IsOne ? T : T.Multiply(Z1Sq); + + ECFieldElement b = curve.B; + ECFieldElement L3; + if (b.BitLength < (curve.FieldSize >> 1)) + { + ECFieldElement t1 = L1.Add(X1).Square(); + ECFieldElement t2; + if (b.IsOne) + { + t2 = aZ1Sq.Add(Z1Sq).Square(); + } + else + { + // TODO Can be calculated with one square if we pre-compute sqrt(b) + t2 = aZ1Sq.SquarePlusProduct(b, Z1Sq.Square()); + } + L3 = t1.Add(T).Add(Z1Sq).Multiply(t1).Add(t2).Add(X3); + if (a.IsZero) + { + L3 = L3.Add(Z3); + } + else if (!a.IsOne) + { + L3 = L3.Add(a.AddOne().Multiply(Z3)); + } + } + else + { + ECFieldElement X1Z1 = Z1IsOne ? X1 : X1.Multiply(Z1); + L3 = X1Z1.SquarePlusProduct(T, L1Z1).Add(X3).Add(Z3); + } + + return new F2mPoint(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + default: + { + throw new InvalidOperationException("unsupported coordinate system"); + } + } + } + + public override ECPoint TwicePlus(ECPoint b) + { + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return Twice(); + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + if (X1.IsZero) + { + // A point with X == 0 is it's own additive inverse + return b; + } + + int coord = curve.CoordinateSystem; + + switch (coord) + { + case ECCurve.COORD_LAMBDA_PROJECTIVE: + { + // NOTE: twicePlus() only optimized for lambda-affine argument + ECFieldElement X2 = b.RawXCoord, Z2 = b.RawZCoords[0]; + if (X2.IsZero || !Z2.IsOne) + { + return Twice().Add(b); + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + ECFieldElement L2 = b.RawYCoord; + + ECFieldElement X1Sq = X1.Square(); + ECFieldElement L1Sq = L1.Square(); + ECFieldElement Z1Sq = Z1.Square(); + ECFieldElement L1Z1 = L1.Multiply(Z1); + + ECFieldElement T = curve.A.Multiply(Z1Sq).Add(L1Sq).Add(L1Z1); + ECFieldElement L2plus1 = L2.AddOne(); + ECFieldElement A = curve.A.Add(L2plus1).Multiply(Z1Sq).Add(L1Sq).MultiplyPlusProduct(T, X1Sq, Z1Sq); + ECFieldElement X2Z1Sq = X2.Multiply(Z1Sq); + ECFieldElement B = X2Z1Sq.Add(T).Square(); + + if (B.IsZero) + { + if (A.IsZero) + { + return b.Twice(); + } + + return curve.Infinity; + } + + if (A.IsZero) + { + return new F2mPoint(curve, A, curve.B.Sqrt(), IsCompressed); + } + + ECFieldElement X3 = A.Square().Multiply(X2Z1Sq); + ECFieldElement Z3 = A.Multiply(B).Multiply(Z1Sq); + ECFieldElement L3 = A.Add(B).Square().MultiplyPlusProduct(T, L2plus1, Z3); + + return new F2mPoint(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + default: + { + return Twice().Add(b); + } + } + } + + public override ECPoint Negate() + { + if (this.IsInfinity) + return this; + + ECFieldElement X = this.RawXCoord; + if (X.IsZero) + return this; + + ECCurve curve = this.Curve; + int coord = curve.CoordinateSystem; + + switch (coord) + { + case ECCurve.COORD_AFFINE: + { + ECFieldElement Y = this.RawYCoord; + return new F2mPoint(curve, X, Y.Add(X), IsCompressed); + } + case ECCurve.COORD_HOMOGENEOUS: + { + ECFieldElement Y = this.RawYCoord, Z = this.RawZCoords[0]; + return new F2mPoint(curve, X, Y.Add(X), new ECFieldElement[] { Z }, IsCompressed); + } + case ECCurve.COORD_LAMBDA_AFFINE: + { + ECFieldElement L = this.RawYCoord; + return new F2mPoint(curve, X, L.AddOne(), IsCompressed); + } + case ECCurve.COORD_LAMBDA_PROJECTIVE: + { + // L is actually Lambda (X + Y/X) here + ECFieldElement L = this.RawYCoord, Z = this.RawZCoords[0]; + return new F2mPoint(curve, X, L.Add(Z), new ECFieldElement[] { Z }, IsCompressed); + } + default: + { + throw new InvalidOperationException("unsupported coordinate system"); + } + } + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/ECPointMap.cs b/bc-sharp-crypto/src/math/ec/ECPointMap.cs new file mode 100644 index 0000000..e78c800 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/ECPointMap.cs @@ -0,0 +1,9 @@ +using System; + +namespace Org.BouncyCastle.Math.EC +{ + public interface ECPointMap + { + ECPoint Map(ECPoint p); + } +} diff --git a/bc-sharp-crypto/src/math/ec/LongArray.cs b/bc-sharp-crypto/src/math/ec/LongArray.cs new file mode 100644 index 0000000..84462e0 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/LongArray.cs @@ -0,0 +1,2201 @@ +using System; +using System.Text; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Math.EC +{ + internal class LongArray + { + //private static long DEInterleave_MASK = 0x5555555555555555L; + + /* + * This expands 8 bit indices into 16 bit contents (high bit 14), by inserting 0s between bits. + * In a binary field, this operation is the same as squaring an 8 bit number. + */ + private static readonly ushort[] INTERLEAVE2_TABLE = new ushort[] + { + 0x0000, 0x0001, 0x0004, 0x0005, 0x0010, 0x0011, 0x0014, 0x0015, + 0x0040, 0x0041, 0x0044, 0x0045, 0x0050, 0x0051, 0x0054, 0x0055, + 0x0100, 0x0101, 0x0104, 0x0105, 0x0110, 0x0111, 0x0114, 0x0115, + 0x0140, 0x0141, 0x0144, 0x0145, 0x0150, 0x0151, 0x0154, 0x0155, + 0x0400, 0x0401, 0x0404, 0x0405, 0x0410, 0x0411, 0x0414, 0x0415, + 0x0440, 0x0441, 0x0444, 0x0445, 0x0450, 0x0451, 0x0454, 0x0455, + 0x0500, 0x0501, 0x0504, 0x0505, 0x0510, 0x0511, 0x0514, 0x0515, + 0x0540, 0x0541, 0x0544, 0x0545, 0x0550, 0x0551, 0x0554, 0x0555, + 0x1000, 0x1001, 0x1004, 0x1005, 0x1010, 0x1011, 0x1014, 0x1015, + 0x1040, 0x1041, 0x1044, 0x1045, 0x1050, 0x1051, 0x1054, 0x1055, + 0x1100, 0x1101, 0x1104, 0x1105, 0x1110, 0x1111, 0x1114, 0x1115, + 0x1140, 0x1141, 0x1144, 0x1145, 0x1150, 0x1151, 0x1154, 0x1155, + 0x1400, 0x1401, 0x1404, 0x1405, 0x1410, 0x1411, 0x1414, 0x1415, + 0x1440, 0x1441, 0x1444, 0x1445, 0x1450, 0x1451, 0x1454, 0x1455, + 0x1500, 0x1501, 0x1504, 0x1505, 0x1510, 0x1511, 0x1514, 0x1515, + 0x1540, 0x1541, 0x1544, 0x1545, 0x1550, 0x1551, 0x1554, 0x1555, + 0x4000, 0x4001, 0x4004, 0x4005, 0x4010, 0x4011, 0x4014, 0x4015, + 0x4040, 0x4041, 0x4044, 0x4045, 0x4050, 0x4051, 0x4054, 0x4055, + 0x4100, 0x4101, 0x4104, 0x4105, 0x4110, 0x4111, 0x4114, 0x4115, + 0x4140, 0x4141, 0x4144, 0x4145, 0x4150, 0x4151, 0x4154, 0x4155, + 0x4400, 0x4401, 0x4404, 0x4405, 0x4410, 0x4411, 0x4414, 0x4415, + 0x4440, 0x4441, 0x4444, 0x4445, 0x4450, 0x4451, 0x4454, 0x4455, + 0x4500, 0x4501, 0x4504, 0x4505, 0x4510, 0x4511, 0x4514, 0x4515, + 0x4540, 0x4541, 0x4544, 0x4545, 0x4550, 0x4551, 0x4554, 0x4555, + 0x5000, 0x5001, 0x5004, 0x5005, 0x5010, 0x5011, 0x5014, 0x5015, + 0x5040, 0x5041, 0x5044, 0x5045, 0x5050, 0x5051, 0x5054, 0x5055, + 0x5100, 0x5101, 0x5104, 0x5105, 0x5110, 0x5111, 0x5114, 0x5115, + 0x5140, 0x5141, 0x5144, 0x5145, 0x5150, 0x5151, 0x5154, 0x5155, + 0x5400, 0x5401, 0x5404, 0x5405, 0x5410, 0x5411, 0x5414, 0x5415, + 0x5440, 0x5441, 0x5444, 0x5445, 0x5450, 0x5451, 0x5454, 0x5455, + 0x5500, 0x5501, 0x5504, 0x5505, 0x5510, 0x5511, 0x5514, 0x5515, + 0x5540, 0x5541, 0x5544, 0x5545, 0x5550, 0x5551, 0x5554, 0x5555 + }; + + /* + * This expands 7 bit indices into 21 bit contents (high bit 18), by inserting 0s between bits. + */ + private static readonly int[] INTERLEAVE3_TABLE = new int[] + { + 0x00000, 0x00001, 0x00008, 0x00009, 0x00040, 0x00041, 0x00048, 0x00049, + 0x00200, 0x00201, 0x00208, 0x00209, 0x00240, 0x00241, 0x00248, 0x00249, + 0x01000, 0x01001, 0x01008, 0x01009, 0x01040, 0x01041, 0x01048, 0x01049, + 0x01200, 0x01201, 0x01208, 0x01209, 0x01240, 0x01241, 0x01248, 0x01249, + 0x08000, 0x08001, 0x08008, 0x08009, 0x08040, 0x08041, 0x08048, 0x08049, + 0x08200, 0x08201, 0x08208, 0x08209, 0x08240, 0x08241, 0x08248, 0x08249, + 0x09000, 0x09001, 0x09008, 0x09009, 0x09040, 0x09041, 0x09048, 0x09049, + 0x09200, 0x09201, 0x09208, 0x09209, 0x09240, 0x09241, 0x09248, 0x09249, + 0x40000, 0x40001, 0x40008, 0x40009, 0x40040, 0x40041, 0x40048, 0x40049, + 0x40200, 0x40201, 0x40208, 0x40209, 0x40240, 0x40241, 0x40248, 0x40249, + 0x41000, 0x41001, 0x41008, 0x41009, 0x41040, 0x41041, 0x41048, 0x41049, + 0x41200, 0x41201, 0x41208, 0x41209, 0x41240, 0x41241, 0x41248, 0x41249, + 0x48000, 0x48001, 0x48008, 0x48009, 0x48040, 0x48041, 0x48048, 0x48049, + 0x48200, 0x48201, 0x48208, 0x48209, 0x48240, 0x48241, 0x48248, 0x48249, + 0x49000, 0x49001, 0x49008, 0x49009, 0x49040, 0x49041, 0x49048, 0x49049, + 0x49200, 0x49201, 0x49208, 0x49209, 0x49240, 0x49241, 0x49248, 0x49249 + }; + + /* + * This expands 8 bit indices into 32 bit contents (high bit 28), by inserting 0s between bits. + */ + private static readonly int[] INTERLEAVE4_TABLE = new int[] + { + 0x00000000, 0x00000001, 0x00000010, 0x00000011, 0x00000100, 0x00000101, 0x00000110, 0x00000111, + 0x00001000, 0x00001001, 0x00001010, 0x00001011, 0x00001100, 0x00001101, 0x00001110, 0x00001111, + 0x00010000, 0x00010001, 0x00010010, 0x00010011, 0x00010100, 0x00010101, 0x00010110, 0x00010111, + 0x00011000, 0x00011001, 0x00011010, 0x00011011, 0x00011100, 0x00011101, 0x00011110, 0x00011111, + 0x00100000, 0x00100001, 0x00100010, 0x00100011, 0x00100100, 0x00100101, 0x00100110, 0x00100111, + 0x00101000, 0x00101001, 0x00101010, 0x00101011, 0x00101100, 0x00101101, 0x00101110, 0x00101111, + 0x00110000, 0x00110001, 0x00110010, 0x00110011, 0x00110100, 0x00110101, 0x00110110, 0x00110111, + 0x00111000, 0x00111001, 0x00111010, 0x00111011, 0x00111100, 0x00111101, 0x00111110, 0x00111111, + 0x01000000, 0x01000001, 0x01000010, 0x01000011, 0x01000100, 0x01000101, 0x01000110, 0x01000111, + 0x01001000, 0x01001001, 0x01001010, 0x01001011, 0x01001100, 0x01001101, 0x01001110, 0x01001111, + 0x01010000, 0x01010001, 0x01010010, 0x01010011, 0x01010100, 0x01010101, 0x01010110, 0x01010111, + 0x01011000, 0x01011001, 0x01011010, 0x01011011, 0x01011100, 0x01011101, 0x01011110, 0x01011111, + 0x01100000, 0x01100001, 0x01100010, 0x01100011, 0x01100100, 0x01100101, 0x01100110, 0x01100111, + 0x01101000, 0x01101001, 0x01101010, 0x01101011, 0x01101100, 0x01101101, 0x01101110, 0x01101111, + 0x01110000, 0x01110001, 0x01110010, 0x01110011, 0x01110100, 0x01110101, 0x01110110, 0x01110111, + 0x01111000, 0x01111001, 0x01111010, 0x01111011, 0x01111100, 0x01111101, 0x01111110, 0x01111111, + 0x10000000, 0x10000001, 0x10000010, 0x10000011, 0x10000100, 0x10000101, 0x10000110, 0x10000111, + 0x10001000, 0x10001001, 0x10001010, 0x10001011, 0x10001100, 0x10001101, 0x10001110, 0x10001111, + 0x10010000, 0x10010001, 0x10010010, 0x10010011, 0x10010100, 0x10010101, 0x10010110, 0x10010111, + 0x10011000, 0x10011001, 0x10011010, 0x10011011, 0x10011100, 0x10011101, 0x10011110, 0x10011111, + 0x10100000, 0x10100001, 0x10100010, 0x10100011, 0x10100100, 0x10100101, 0x10100110, 0x10100111, + 0x10101000, 0x10101001, 0x10101010, 0x10101011, 0x10101100, 0x10101101, 0x10101110, 0x10101111, + 0x10110000, 0x10110001, 0x10110010, 0x10110011, 0x10110100, 0x10110101, 0x10110110, 0x10110111, + 0x10111000, 0x10111001, 0x10111010, 0x10111011, 0x10111100, 0x10111101, 0x10111110, 0x10111111, + 0x11000000, 0x11000001, 0x11000010, 0x11000011, 0x11000100, 0x11000101, 0x11000110, 0x11000111, + 0x11001000, 0x11001001, 0x11001010, 0x11001011, 0x11001100, 0x11001101, 0x11001110, 0x11001111, + 0x11010000, 0x11010001, 0x11010010, 0x11010011, 0x11010100, 0x11010101, 0x11010110, 0x11010111, + 0x11011000, 0x11011001, 0x11011010, 0x11011011, 0x11011100, 0x11011101, 0x11011110, 0x11011111, + 0x11100000, 0x11100001, 0x11100010, 0x11100011, 0x11100100, 0x11100101, 0x11100110, 0x11100111, + 0x11101000, 0x11101001, 0x11101010, 0x11101011, 0x11101100, 0x11101101, 0x11101110, 0x11101111, + 0x11110000, 0x11110001, 0x11110010, 0x11110011, 0x11110100, 0x11110101, 0x11110110, 0x11110111, + 0x11111000, 0x11111001, 0x11111010, 0x11111011, 0x11111100, 0x11111101, 0x11111110, 0x11111111 + }; + + /* + * This expands 7 bit indices into 35 bit contents (high bit 30), by inserting 0s between bits. + */ + private static readonly int[] INTERLEAVE5_TABLE = new int[] { + 0x00000000, 0x00000001, 0x00000020, 0x00000021, 0x00000400, 0x00000401, 0x00000420, 0x00000421, + 0x00008000, 0x00008001, 0x00008020, 0x00008021, 0x00008400, 0x00008401, 0x00008420, 0x00008421, + 0x00100000, 0x00100001, 0x00100020, 0x00100021, 0x00100400, 0x00100401, 0x00100420, 0x00100421, + 0x00108000, 0x00108001, 0x00108020, 0x00108021, 0x00108400, 0x00108401, 0x00108420, 0x00108421, + 0x02000000, 0x02000001, 0x02000020, 0x02000021, 0x02000400, 0x02000401, 0x02000420, 0x02000421, + 0x02008000, 0x02008001, 0x02008020, 0x02008021, 0x02008400, 0x02008401, 0x02008420, 0x02008421, + 0x02100000, 0x02100001, 0x02100020, 0x02100021, 0x02100400, 0x02100401, 0x02100420, 0x02100421, + 0x02108000, 0x02108001, 0x02108020, 0x02108021, 0x02108400, 0x02108401, 0x02108420, 0x02108421, + 0x40000000, 0x40000001, 0x40000020, 0x40000021, 0x40000400, 0x40000401, 0x40000420, 0x40000421, + 0x40008000, 0x40008001, 0x40008020, 0x40008021, 0x40008400, 0x40008401, 0x40008420, 0x40008421, + 0x40100000, 0x40100001, 0x40100020, 0x40100021, 0x40100400, 0x40100401, 0x40100420, 0x40100421, + 0x40108000, 0x40108001, 0x40108020, 0x40108021, 0x40108400, 0x40108401, 0x40108420, 0x40108421, + 0x42000000, 0x42000001, 0x42000020, 0x42000021, 0x42000400, 0x42000401, 0x42000420, 0x42000421, + 0x42008000, 0x42008001, 0x42008020, 0x42008021, 0x42008400, 0x42008401, 0x42008420, 0x42008421, + 0x42100000, 0x42100001, 0x42100020, 0x42100021, 0x42100400, 0x42100401, 0x42100420, 0x42100421, + 0x42108000, 0x42108001, 0x42108020, 0x42108021, 0x42108400, 0x42108401, 0x42108420, 0x42108421 + }; + + /* + * This expands 9 bit indices into 63 bit (long) contents (high bit 56), by inserting 0s between bits. + */ + private static readonly long[] INTERLEAVE7_TABLE = new long[] + { + 0x0000000000000000L, 0x0000000000000001L, 0x0000000000000080L, 0x0000000000000081L, + 0x0000000000004000L, 0x0000000000004001L, 0x0000000000004080L, 0x0000000000004081L, + 0x0000000000200000L, 0x0000000000200001L, 0x0000000000200080L, 0x0000000000200081L, + 0x0000000000204000L, 0x0000000000204001L, 0x0000000000204080L, 0x0000000000204081L, + 0x0000000010000000L, 0x0000000010000001L, 0x0000000010000080L, 0x0000000010000081L, + 0x0000000010004000L, 0x0000000010004001L, 0x0000000010004080L, 0x0000000010004081L, + 0x0000000010200000L, 0x0000000010200001L, 0x0000000010200080L, 0x0000000010200081L, + 0x0000000010204000L, 0x0000000010204001L, 0x0000000010204080L, 0x0000000010204081L, + 0x0000000800000000L, 0x0000000800000001L, 0x0000000800000080L, 0x0000000800000081L, + 0x0000000800004000L, 0x0000000800004001L, 0x0000000800004080L, 0x0000000800004081L, + 0x0000000800200000L, 0x0000000800200001L, 0x0000000800200080L, 0x0000000800200081L, + 0x0000000800204000L, 0x0000000800204001L, 0x0000000800204080L, 0x0000000800204081L, + 0x0000000810000000L, 0x0000000810000001L, 0x0000000810000080L, 0x0000000810000081L, + 0x0000000810004000L, 0x0000000810004001L, 0x0000000810004080L, 0x0000000810004081L, + 0x0000000810200000L, 0x0000000810200001L, 0x0000000810200080L, 0x0000000810200081L, + 0x0000000810204000L, 0x0000000810204001L, 0x0000000810204080L, 0x0000000810204081L, + 0x0000040000000000L, 0x0000040000000001L, 0x0000040000000080L, 0x0000040000000081L, + 0x0000040000004000L, 0x0000040000004001L, 0x0000040000004080L, 0x0000040000004081L, + 0x0000040000200000L, 0x0000040000200001L, 0x0000040000200080L, 0x0000040000200081L, + 0x0000040000204000L, 0x0000040000204001L, 0x0000040000204080L, 0x0000040000204081L, + 0x0000040010000000L, 0x0000040010000001L, 0x0000040010000080L, 0x0000040010000081L, + 0x0000040010004000L, 0x0000040010004001L, 0x0000040010004080L, 0x0000040010004081L, + 0x0000040010200000L, 0x0000040010200001L, 0x0000040010200080L, 0x0000040010200081L, + 0x0000040010204000L, 0x0000040010204001L, 0x0000040010204080L, 0x0000040010204081L, + 0x0000040800000000L, 0x0000040800000001L, 0x0000040800000080L, 0x0000040800000081L, + 0x0000040800004000L, 0x0000040800004001L, 0x0000040800004080L, 0x0000040800004081L, + 0x0000040800200000L, 0x0000040800200001L, 0x0000040800200080L, 0x0000040800200081L, + 0x0000040800204000L, 0x0000040800204001L, 0x0000040800204080L, 0x0000040800204081L, + 0x0000040810000000L, 0x0000040810000001L, 0x0000040810000080L, 0x0000040810000081L, + 0x0000040810004000L, 0x0000040810004001L, 0x0000040810004080L, 0x0000040810004081L, + 0x0000040810200000L, 0x0000040810200001L, 0x0000040810200080L, 0x0000040810200081L, + 0x0000040810204000L, 0x0000040810204001L, 0x0000040810204080L, 0x0000040810204081L, + 0x0002000000000000L, 0x0002000000000001L, 0x0002000000000080L, 0x0002000000000081L, + 0x0002000000004000L, 0x0002000000004001L, 0x0002000000004080L, 0x0002000000004081L, + 0x0002000000200000L, 0x0002000000200001L, 0x0002000000200080L, 0x0002000000200081L, + 0x0002000000204000L, 0x0002000000204001L, 0x0002000000204080L, 0x0002000000204081L, + 0x0002000010000000L, 0x0002000010000001L, 0x0002000010000080L, 0x0002000010000081L, + 0x0002000010004000L, 0x0002000010004001L, 0x0002000010004080L, 0x0002000010004081L, + 0x0002000010200000L, 0x0002000010200001L, 0x0002000010200080L, 0x0002000010200081L, + 0x0002000010204000L, 0x0002000010204001L, 0x0002000010204080L, 0x0002000010204081L, + 0x0002000800000000L, 0x0002000800000001L, 0x0002000800000080L, 0x0002000800000081L, + 0x0002000800004000L, 0x0002000800004001L, 0x0002000800004080L, 0x0002000800004081L, + 0x0002000800200000L, 0x0002000800200001L, 0x0002000800200080L, 0x0002000800200081L, + 0x0002000800204000L, 0x0002000800204001L, 0x0002000800204080L, 0x0002000800204081L, + 0x0002000810000000L, 0x0002000810000001L, 0x0002000810000080L, 0x0002000810000081L, + 0x0002000810004000L, 0x0002000810004001L, 0x0002000810004080L, 0x0002000810004081L, + 0x0002000810200000L, 0x0002000810200001L, 0x0002000810200080L, 0x0002000810200081L, + 0x0002000810204000L, 0x0002000810204001L, 0x0002000810204080L, 0x0002000810204081L, + 0x0002040000000000L, 0x0002040000000001L, 0x0002040000000080L, 0x0002040000000081L, + 0x0002040000004000L, 0x0002040000004001L, 0x0002040000004080L, 0x0002040000004081L, + 0x0002040000200000L, 0x0002040000200001L, 0x0002040000200080L, 0x0002040000200081L, + 0x0002040000204000L, 0x0002040000204001L, 0x0002040000204080L, 0x0002040000204081L, + 0x0002040010000000L, 0x0002040010000001L, 0x0002040010000080L, 0x0002040010000081L, + 0x0002040010004000L, 0x0002040010004001L, 0x0002040010004080L, 0x0002040010004081L, + 0x0002040010200000L, 0x0002040010200001L, 0x0002040010200080L, 0x0002040010200081L, + 0x0002040010204000L, 0x0002040010204001L, 0x0002040010204080L, 0x0002040010204081L, + 0x0002040800000000L, 0x0002040800000001L, 0x0002040800000080L, 0x0002040800000081L, + 0x0002040800004000L, 0x0002040800004001L, 0x0002040800004080L, 0x0002040800004081L, + 0x0002040800200000L, 0x0002040800200001L, 0x0002040800200080L, 0x0002040800200081L, + 0x0002040800204000L, 0x0002040800204001L, 0x0002040800204080L, 0x0002040800204081L, + 0x0002040810000000L, 0x0002040810000001L, 0x0002040810000080L, 0x0002040810000081L, + 0x0002040810004000L, 0x0002040810004001L, 0x0002040810004080L, 0x0002040810004081L, + 0x0002040810200000L, 0x0002040810200001L, 0x0002040810200080L, 0x0002040810200081L, + 0x0002040810204000L, 0x0002040810204001L, 0x0002040810204080L, 0x0002040810204081L, + 0x0100000000000000L, 0x0100000000000001L, 0x0100000000000080L, 0x0100000000000081L, + 0x0100000000004000L, 0x0100000000004001L, 0x0100000000004080L, 0x0100000000004081L, + 0x0100000000200000L, 0x0100000000200001L, 0x0100000000200080L, 0x0100000000200081L, + 0x0100000000204000L, 0x0100000000204001L, 0x0100000000204080L, 0x0100000000204081L, + 0x0100000010000000L, 0x0100000010000001L, 0x0100000010000080L, 0x0100000010000081L, + 0x0100000010004000L, 0x0100000010004001L, 0x0100000010004080L, 0x0100000010004081L, + 0x0100000010200000L, 0x0100000010200001L, 0x0100000010200080L, 0x0100000010200081L, + 0x0100000010204000L, 0x0100000010204001L, 0x0100000010204080L, 0x0100000010204081L, + 0x0100000800000000L, 0x0100000800000001L, 0x0100000800000080L, 0x0100000800000081L, + 0x0100000800004000L, 0x0100000800004001L, 0x0100000800004080L, 0x0100000800004081L, + 0x0100000800200000L, 0x0100000800200001L, 0x0100000800200080L, 0x0100000800200081L, + 0x0100000800204000L, 0x0100000800204001L, 0x0100000800204080L, 0x0100000800204081L, + 0x0100000810000000L, 0x0100000810000001L, 0x0100000810000080L, 0x0100000810000081L, + 0x0100000810004000L, 0x0100000810004001L, 0x0100000810004080L, 0x0100000810004081L, + 0x0100000810200000L, 0x0100000810200001L, 0x0100000810200080L, 0x0100000810200081L, + 0x0100000810204000L, 0x0100000810204001L, 0x0100000810204080L, 0x0100000810204081L, + 0x0100040000000000L, 0x0100040000000001L, 0x0100040000000080L, 0x0100040000000081L, + 0x0100040000004000L, 0x0100040000004001L, 0x0100040000004080L, 0x0100040000004081L, + 0x0100040000200000L, 0x0100040000200001L, 0x0100040000200080L, 0x0100040000200081L, + 0x0100040000204000L, 0x0100040000204001L, 0x0100040000204080L, 0x0100040000204081L, + 0x0100040010000000L, 0x0100040010000001L, 0x0100040010000080L, 0x0100040010000081L, + 0x0100040010004000L, 0x0100040010004001L, 0x0100040010004080L, 0x0100040010004081L, + 0x0100040010200000L, 0x0100040010200001L, 0x0100040010200080L, 0x0100040010200081L, + 0x0100040010204000L, 0x0100040010204001L, 0x0100040010204080L, 0x0100040010204081L, + 0x0100040800000000L, 0x0100040800000001L, 0x0100040800000080L, 0x0100040800000081L, + 0x0100040800004000L, 0x0100040800004001L, 0x0100040800004080L, 0x0100040800004081L, + 0x0100040800200000L, 0x0100040800200001L, 0x0100040800200080L, 0x0100040800200081L, + 0x0100040800204000L, 0x0100040800204001L, 0x0100040800204080L, 0x0100040800204081L, + 0x0100040810000000L, 0x0100040810000001L, 0x0100040810000080L, 0x0100040810000081L, + 0x0100040810004000L, 0x0100040810004001L, 0x0100040810004080L, 0x0100040810004081L, + 0x0100040810200000L, 0x0100040810200001L, 0x0100040810200080L, 0x0100040810200081L, + 0x0100040810204000L, 0x0100040810204001L, 0x0100040810204080L, 0x0100040810204081L, + 0x0102000000000000L, 0x0102000000000001L, 0x0102000000000080L, 0x0102000000000081L, + 0x0102000000004000L, 0x0102000000004001L, 0x0102000000004080L, 0x0102000000004081L, + 0x0102000000200000L, 0x0102000000200001L, 0x0102000000200080L, 0x0102000000200081L, + 0x0102000000204000L, 0x0102000000204001L, 0x0102000000204080L, 0x0102000000204081L, + 0x0102000010000000L, 0x0102000010000001L, 0x0102000010000080L, 0x0102000010000081L, + 0x0102000010004000L, 0x0102000010004001L, 0x0102000010004080L, 0x0102000010004081L, + 0x0102000010200000L, 0x0102000010200001L, 0x0102000010200080L, 0x0102000010200081L, + 0x0102000010204000L, 0x0102000010204001L, 0x0102000010204080L, 0x0102000010204081L, + 0x0102000800000000L, 0x0102000800000001L, 0x0102000800000080L, 0x0102000800000081L, + 0x0102000800004000L, 0x0102000800004001L, 0x0102000800004080L, 0x0102000800004081L, + 0x0102000800200000L, 0x0102000800200001L, 0x0102000800200080L, 0x0102000800200081L, + 0x0102000800204000L, 0x0102000800204001L, 0x0102000800204080L, 0x0102000800204081L, + 0x0102000810000000L, 0x0102000810000001L, 0x0102000810000080L, 0x0102000810000081L, + 0x0102000810004000L, 0x0102000810004001L, 0x0102000810004080L, 0x0102000810004081L, + 0x0102000810200000L, 0x0102000810200001L, 0x0102000810200080L, 0x0102000810200081L, + 0x0102000810204000L, 0x0102000810204001L, 0x0102000810204080L, 0x0102000810204081L, + 0x0102040000000000L, 0x0102040000000001L, 0x0102040000000080L, 0x0102040000000081L, + 0x0102040000004000L, 0x0102040000004001L, 0x0102040000004080L, 0x0102040000004081L, + 0x0102040000200000L, 0x0102040000200001L, 0x0102040000200080L, 0x0102040000200081L, + 0x0102040000204000L, 0x0102040000204001L, 0x0102040000204080L, 0x0102040000204081L, + 0x0102040010000000L, 0x0102040010000001L, 0x0102040010000080L, 0x0102040010000081L, + 0x0102040010004000L, 0x0102040010004001L, 0x0102040010004080L, 0x0102040010004081L, + 0x0102040010200000L, 0x0102040010200001L, 0x0102040010200080L, 0x0102040010200081L, + 0x0102040010204000L, 0x0102040010204001L, 0x0102040010204080L, 0x0102040010204081L, + 0x0102040800000000L, 0x0102040800000001L, 0x0102040800000080L, 0x0102040800000081L, + 0x0102040800004000L, 0x0102040800004001L, 0x0102040800004080L, 0x0102040800004081L, + 0x0102040800200000L, 0x0102040800200001L, 0x0102040800200080L, 0x0102040800200081L, + 0x0102040800204000L, 0x0102040800204001L, 0x0102040800204080L, 0x0102040800204081L, + 0x0102040810000000L, 0x0102040810000001L, 0x0102040810000080L, 0x0102040810000081L, + 0x0102040810004000L, 0x0102040810004001L, 0x0102040810004080L, 0x0102040810004081L, + 0x0102040810200000L, 0x0102040810200001L, 0x0102040810200080L, 0x0102040810200081L, + 0x0102040810204000L, 0x0102040810204001L, 0x0102040810204080L, 0x0102040810204081L + }; + + // For toString(); must have length 64 + private const string ZEROES = "0000000000000000000000000000000000000000000000000000000000000000"; + + internal static readonly byte[] BitLengths = + { + 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 + }; + + // TODO make m fixed for the LongArray, and hence compute T once and for all + + private long[] m_ints; + + public LongArray(int intLen) + { + m_ints = new long[intLen]; + } + + public LongArray(long[] ints) + { + m_ints = ints; + } + + public LongArray(long[] ints, int off, int len) + { + if (off == 0 && len == ints.Length) + { + m_ints = ints; + } + else + { + m_ints = new long[len]; + Array.Copy(ints, off, m_ints, 0, len); + } + } + + public LongArray(BigInteger bigInt) + { + if (bigInt == null || bigInt.SignValue < 0) + { + throw new ArgumentException("invalid F2m field value", "bigInt"); + } + + if (bigInt.SignValue == 0) + { + m_ints = new long[] { 0L }; + 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 + 7) / 8; + m_ints = new long[intLen]; + + int iarrJ = intLen - 1; + int rem = barrLen % 8 + barrStart; + long temp = 0; + int barrI = barrStart; + if (barrStart < rem) + { + for (; barrI < rem; barrI++) + { + temp <<= 8; + uint barrBarrI = barr[barrI]; + temp |= barrBarrI; + } + m_ints[iarrJ--] = temp; + } + + for (; iarrJ >= 0; iarrJ--) + { + temp = 0; + for (int i = 0; i < 8; i++) + { + temp <<= 8; + uint barrBarrI = barr[barrI++]; + temp |= barrBarrI; + } + m_ints[iarrJ] = temp; + } + } + + public bool IsOne() + { + long[] a = m_ints; + if (a[0] != 1L) + { + return false; + } + for (int i = 1; i < a.Length; ++i) + { + if (a[i] != 0L) + { + return false; + } + } + return true; + } + + public bool IsZero() + { + long[] a = m_ints; + for (int i = 0; i < a.Length; ++i) + { + if (a[i] != 0L) + { + return false; + } + } + return true; + } + + public int GetUsedLength() + { + return GetUsedLengthFrom(m_ints.Length); + } + + public int GetUsedLengthFrom(int from) + { + long[] a = m_ints; + from = System.Math.Min(from, a.Length); + + if (from < 1) + { + return 0; + } + + // Check if first element will act as sentinel + if (a[0] != 0) + { + while (a[--from] == 0) + { + } + return from + 1; + } + + do + { + if (a[--from] != 0) + { + return from + 1; + } + } + while (from > 0); + + return 0; + } + + public int Degree() + { + int i = m_ints.Length; + long w; + do + { + if (i == 0) + { + return 0; + } + w = m_ints[--i]; + } + while (w == 0); + + return (i << 6) + BitLength(w); + } + + private int DegreeFrom(int limit) + { + int i = (int)(((uint)limit + 62) >> 6); + long w; + do + { + if (i == 0) + { + return 0; + } + w = m_ints[--i]; + } + while (w == 0); + + return (i << 6) + BitLength(w); + } + + // private int lowestCoefficient() + // { + // for (int i = 0; i < m_ints.Length; ++i) + // { + // long mi = m_ints[i]; + // if (mi != 0) + // { + // int j = 0; + // while ((mi & 0xFFL) == 0) + // { + // j += 8; + // mi >>>= 8; + // } + // while ((mi & 1L) == 0) + // { + // ++j; + // mi >>>= 1; + // } + // return (i << 6) + j; + // } + // } + // return -1; + // } + + private static int BitLength(long w) + { + int u = (int)((ulong)w >> 32), b; + if (u == 0) + { + u = (int)w; + b = 0; + } + else + { + b = 32; + } + + int t = (int)((uint)u >> 16), k; + if (t == 0) + { + t = (int)((uint)u >> 8); + k = (t == 0) ? BitLengths[u] : 8 + BitLengths[t]; + } + else + { + int v = (int)((uint)t >> 8); + k = (v == 0) ? 16 + BitLengths[t] : 24 + BitLengths[v]; + } + + return b + k; + } + + private long[] ResizedInts(int newLen) + { + long[] newInts = new long[newLen]; + Array.Copy(m_ints, 0, newInts, 0, System.Math.Min(m_ints.Length, newLen)); + return newInts; + } + + public BigInteger ToBigInteger() + { + int usedLen = GetUsedLength(); + if (usedLen == 0) + { + return BigInteger.Zero; + } + + long highestInt = m_ints[usedLen - 1]; + byte[] temp = new byte[8]; + int barrI = 0; + bool trailingZeroBytesDone = false; + for (int j = 7; j >= 0; j--) + { + byte thisByte = (byte)((ulong)highestInt >> (8 * j)); + if (trailingZeroBytesDone || (thisByte != 0)) + { + trailingZeroBytesDone = true; + temp[barrI++] = thisByte; + } + } + + int barrLen = 8 * (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--) + { + long mi = m_ints[iarrJ]; + for (int j = 7; j >= 0; j--) + { + barr[barrI++] = (byte)((ulong)mi >> (8 * j)); + } + } + return new BigInteger(1, barr); + } + + // private static long shiftUp(long[] x, int xOff, int count) + // { + // long prev = 0; + // for (int i = 0; i < count; ++i) + // { + // long next = x[xOff + i]; + // x[xOff + i] = (next << 1) | prev; + // prev = next >>> 63; + // } + // return prev; + // } + + private static long ShiftUp(long[] x, int xOff, int count, int shift) + { + int shiftInv = 64 - shift; + long prev = 0; + for (int i = 0; i < count; ++i) + { + long next = x[xOff + i]; + x[xOff + i] = (next << shift) | prev; + prev = (long)((ulong)next >> shiftInv); + } + return prev; + } + + private static long ShiftUp(long[] x, int xOff, long[] z, int zOff, int count, int shift) + { + int shiftInv = 64 - shift; + long prev = 0; + for (int i = 0; i < count; ++i) + { + long next = x[xOff + i]; + z[zOff + i] = (next << shift) | prev; + prev = (long)((ulong)next >> shiftInv); + } + return prev; + } + + public LongArray AddOne() + { + if (m_ints.Length == 0) + { + return new LongArray(new long[]{ 1L }); + } + + int resultLen = System.Math.Max(1, GetUsedLength()); + long[] ints = ResizedInts(resultLen); + ints[0] ^= 1L; + return new LongArray(ints); + } + + // private void addShiftedByBits(LongArray other, int bits) + // { + // int words = bits >>> 6; + // int shift = bits & 0x3F; + // + // if (shift == 0) + // { + // addShiftedByWords(other, words); + // return; + // } + // + // int otherUsedLen = other.GetUsedLength(); + // if (otherUsedLen == 0) + // { + // return; + // } + // + // int minLen = otherUsedLen + words + 1; + // if (minLen > m_ints.Length) + // { + // m_ints = resizedInts(minLen); + // } + // + // long carry = addShiftedByBits(m_ints, words, other.m_ints, 0, otherUsedLen, shift); + // m_ints[otherUsedLen + words] ^= carry; + // } + + private void AddShiftedByBitsSafe(LongArray other, int otherDegree, int bits) + { + int otherLen = (int)((uint)(otherDegree + 63) >> 6); + + int words = (int)((uint)bits >> 6); + int shift = bits & 0x3F; + + if (shift == 0) + { + Add(m_ints, words, other.m_ints, 0, otherLen); + return; + } + + long carry = AddShiftedUp(m_ints, words, other.m_ints, 0, otherLen, shift); + if (carry != 0L) + { + m_ints[otherLen + words] ^= carry; + } + } + + private static long AddShiftedUp(long[] x, int xOff, long[] y, int yOff, int count, int shift) + { + int shiftInv = 64 - shift; + long prev = 0; + for (int i = 0; i < count; ++i) + { + long next = y[yOff + i]; + x[xOff + i] ^= (next << shift) | prev; + prev = (long)((ulong)next >> shiftInv); + } + return prev; + } + + private static long AddShiftedDown(long[] x, int xOff, long[] y, int yOff, int count, int shift) + { + int shiftInv = 64 - shift; + long prev = 0; + int i = count; + while (--i >= 0) + { + long next = y[yOff + i]; + x[xOff + i] ^= (long)((ulong)next >> shift) | prev; + prev = next << shiftInv; + } + return prev; + } + + public void AddShiftedByWords(LongArray other, int words) + { + int otherUsedLen = other.GetUsedLength(); + if (otherUsedLen == 0) + { + return; + } + + int minLen = otherUsedLen + words; + if (minLen > m_ints.Length) + { + m_ints = ResizedInts(minLen); + } + + Add(m_ints, words, other.m_ints, 0, otherUsedLen); + } + + private static void Add(long[] x, int xOff, long[] y, int yOff, int count) + { + for (int i = 0; i < count; ++i) + { + x[xOff + i] ^= y[yOff + i]; + } + } + + private static void Add(long[] x, int xOff, long[] y, int yOff, long[] z, int zOff, int count) + { + for (int i = 0; i < count; ++i) + { + z[zOff + i] = x[xOff + i] ^ y[yOff + i]; + } + } + + private static void AddBoth(long[] x, int xOff, long[] y1, int y1Off, long[] y2, int y2Off, int count) + { + for (int i = 0; i < count; ++i) + { + x[xOff + i] ^= y1[y1Off + i] ^ y2[y2Off + i]; + } + } + + private static void Distribute(long[] x, int src, int dst1, int dst2, int count) + { + for (int i = 0; i < count; ++i) + { + long v = x[src + i]; + x[dst1 + i] ^= v; + x[dst2 + i] ^= v; + } + } + + public int Length + { + get { return m_ints.Length; } + } + + private static void FlipWord(long[] buf, int off, int bit, long word) + { + int n = off + (int)((uint)bit >> 6); + int shift = bit & 0x3F; + if (shift == 0) + { + buf[n] ^= word; + } + else + { + buf[n] ^= word << shift; + word = (long)((ulong)word >> (64 - shift)); + if (word != 0) + { + buf[++n] ^= word; + } + } + } + + // private static long getWord(long[] buf, int off, int len, int bit) + // { + // int n = off + (bit >>> 6); + // int shift = bit & 0x3F; + // if (shift == 0) + // { + // return buf[n]; + // } + // long result = buf[n] >>> shift; + // if (++n < len) + // { + // result |= buf[n] << (64 - shift); + // } + // return result; + // } + + public bool TestBitZero() + { + return m_ints.Length > 0 && (m_ints[0] & 1L) != 0; + } + + private static bool TestBit(long[] buf, int off, int n) + { + // theInt = n / 64 + int theInt = (int)((uint)n >> 6); + // theBit = n % 64 + int theBit = n & 0x3F; + long tester = 1L << theBit; + return (buf[off + theInt] & tester) != 0; + } + + private static void FlipBit(long[] buf, int off, int n) + { + // theInt = n / 64 + int theInt = (int)((uint)n >> 6); + // theBit = n % 64 + int theBit = n & 0x3F; + long flipper = 1L << theBit; + buf[off + theInt] ^= flipper; + } + + // private static void SetBit(long[] buf, int off, int n) + // { + // // theInt = n / 64 + // int theInt = n >>> 6; + // // theBit = n % 64 + // int theBit = n & 0x3F; + // long setter = 1L << theBit; + // buf[off + theInt] |= setter; + // } + // + // private static void ClearBit(long[] buf, int off, int n) + // { + // // theInt = n / 64 + // int theInt = n >>> 6; + // // theBit = n % 64 + // int theBit = n & 0x3F; + // long setter = 1L << theBit; + // buf[off + theInt] &= ~setter; + // } + + private static void MultiplyWord(long a, long[] b, int bLen, long[] c, int cOff) + { + if ((a & 1L) != 0L) + { + Add(c, cOff, b, 0, bLen); + } + int k = 1; + while ((a = (long)((ulong)a >> 1)) != 0L) + { + if ((a & 1L) != 0L) + { + long carry = AddShiftedUp(c, cOff, b, 0, bLen, k); + if (carry != 0L) + { + c[cOff + bLen] ^= carry; + } + } + ++k; + } + } + + public LongArray ModMultiplyLD(LongArray other, int m, int[] ks) + { + /* + * Find out the degree of each argument and handle the zero cases + */ + int aDeg = Degree(); + if (aDeg == 0) + { + return this; + } + int bDeg = other.Degree(); + if (bDeg == 0) + { + return other; + } + + /* + * Swap if necessary so that A is the smaller argument + */ + LongArray A = this, B = other; + if (aDeg > bDeg) + { + A = other; B = this; + int tmp = aDeg; aDeg = bDeg; bDeg = tmp; + } + + /* + * Establish the word lengths of the arguments and result + */ + int aLen = (int)((uint)(aDeg + 63) >> 6); + int bLen = (int)((uint)(bDeg + 63) >> 6); + int cLen = (int)((uint)(aDeg + bDeg + 62) >> 6); + + if (aLen == 1) + { + long a0 = A.m_ints[0]; + if (a0 == 1L) + { + return B; + } + + /* + * Fast path for small A, with performance dependent only on the number of set bits + */ + long[] c0 = new long[cLen]; + MultiplyWord(a0, B.m_ints, bLen, c0, 0); + + /* + * Reduce the raw answer against the reduction coefficients + */ + return ReduceResult(c0, 0, cLen, m, ks); + } + + /* + * Determine if B will get bigger during shifting + */ + int bMax = (int)((uint)(bDeg + 7 + 63) >> 6); + + /* + * Lookup table for the offset of each B in the tables + */ + int[] ti = new int[16]; + + /* + * Precompute table of all 4-bit products of B + */ + long[] T0 = new long[bMax << 4]; + int tOff = bMax; + ti[1] = tOff; + Array.Copy(B.m_ints, 0, T0, tOff, bLen); + for (int i = 2; i < 16; ++i) + { + ti[i] = (tOff += bMax); + if ((i & 1) == 0) + { + ShiftUp(T0, (int)((uint)tOff >> 1), T0, tOff, bMax, 1); + } + else + { + Add(T0, bMax, T0, tOff - bMax, T0, tOff, bMax); + } + } + + /* + * Second table with all 4-bit products of B shifted 4 bits + */ + long[] T1 = new long[T0.Length]; + ShiftUp(T0, 0, T1, 0, T0.Length, 4); + // shiftUp(T0, bMax, T1, bMax, tOff, 4); + + long[] a = A.m_ints; + long[] c = new long[cLen]; + + int MASK = 0xF; + + /* + * Lopez-Dahab algorithm + */ + + for (int k = 56; k >= 0; k -= 8) + { + for (int j = 1; j < aLen; j += 2) + { + int aVal = (int)((ulong)a[j] >> k); + int u = aVal & MASK; + int v = (int)((uint)aVal >> 4) & MASK; + AddBoth(c, j - 1, T0, ti[u], T1, ti[v], bMax); + } + ShiftUp(c, 0, cLen, 8); + } + + for (int k = 56; k >= 0; k -= 8) + { + for (int j = 0; j < aLen; j += 2) + { + int aVal = (int)((ulong)a[j] >> k); + int u = aVal & MASK; + int v = (int)((uint)aVal >> 4) & MASK; + AddBoth(c, j, T0, ti[u], T1, ti[v], bMax); + } + if (k > 0) + { + ShiftUp(c, 0, cLen, 8); + } + } + + /* + * Finally the raw answer is collected, reduce it against the reduction coefficients + */ + return ReduceResult(c, 0, cLen, m, ks); + } + + public LongArray ModMultiply(LongArray other, int m, int[] ks) + { + /* + * Find out the degree of each argument and handle the zero cases + */ + int aDeg = Degree(); + if (aDeg == 0) + { + return this; + } + int bDeg = other.Degree(); + if (bDeg == 0) + { + return other; + } + + /* + * Swap if necessary so that A is the smaller argument + */ + LongArray A = this, B = other; + if (aDeg > bDeg) + { + A = other; B = this; + int tmp = aDeg; aDeg = bDeg; bDeg = tmp; + } + + /* + * Establish the word lengths of the arguments and result + */ + int aLen = (int)((uint)(aDeg + 63) >> 6); + int bLen = (int)((uint)(bDeg + 63) >> 6); + int cLen = (int)((uint)(aDeg + bDeg + 62) >> 6); + + if (aLen == 1) + { + long a0 = A.m_ints[0]; + if (a0 == 1L) + { + return B; + } + + /* + * Fast path for small A, with performance dependent only on the number of set bits + */ + long[] c0 = new long[cLen]; + MultiplyWord(a0, B.m_ints, bLen, c0, 0); + + /* + * Reduce the raw answer against the reduction coefficients + */ + return ReduceResult(c0, 0, cLen, m, ks); + } + + /* + * Determine if B will get bigger during shifting + */ + int bMax = (int)((uint)(bDeg + 7 + 63) >> 6); + + /* + * Lookup table for the offset of each B in the tables + */ + int[] ti = new int[16]; + + /* + * Precompute table of all 4-bit products of B + */ + long[] T0 = new long[bMax << 4]; + int tOff = bMax; + ti[1] = tOff; + Array.Copy(B.m_ints, 0, T0, tOff, bLen); + for (int i = 2; i < 16; ++i) + { + ti[i] = (tOff += bMax); + if ((i & 1) == 0) + { + ShiftUp(T0, (int)((uint)tOff >> 1), T0, tOff, bMax, 1); + } + else + { + Add(T0, bMax, T0, tOff - bMax, T0, tOff, bMax); + } + } + + /* + * Second table with all 4-bit products of B shifted 4 bits + */ + long[] T1 = new long[T0.Length]; + ShiftUp(T0, 0, T1, 0, T0.Length, 4); + // ShiftUp(T0, bMax, T1, bMax, tOff, 4); + + long[] a = A.m_ints; + long[] c = new long[cLen << 3]; + + int MASK = 0xF; + + /* + * Lopez-Dahab (Modified) algorithm + */ + + for (int aPos = 0; aPos < aLen; ++aPos) + { + long aVal = a[aPos]; + int cOff = aPos; + for (;;) + { + int u = (int)aVal & MASK; + aVal = (long)((ulong)aVal >> 4); + int v = (int)aVal & MASK; + AddBoth(c, cOff, T0, ti[u], T1, ti[v], bMax); + aVal = (long)((ulong)aVal >> 4); + if (aVal == 0L) + { + break; + } + cOff += cLen; + } + } + + { + int cOff = c.Length; + while ((cOff -= cLen) != 0) + { + AddShiftedUp(c, cOff - cLen, c, cOff, cLen, 8); + } + } + + /* + * Finally the raw answer is collected, reduce it against the reduction coefficients + */ + return ReduceResult(c, 0, cLen, m, ks); + } + + public LongArray ModMultiplyAlt(LongArray other, int m, int[] ks) + { + /* + * Find out the degree of each argument and handle the zero cases + */ + int aDeg = Degree(); + if (aDeg == 0) + { + return this; + } + int bDeg = other.Degree(); + if (bDeg == 0) + { + return other; + } + + /* + * Swap if necessary so that A is the smaller argument + */ + LongArray A = this, B = other; + if (aDeg > bDeg) + { + A = other; B = this; + int tmp = aDeg; aDeg = bDeg; bDeg = tmp; + } + + /* + * Establish the word lengths of the arguments and result + */ + int aLen = (int)((uint)(aDeg + 63) >> 6); + int bLen = (int)((uint)(bDeg + 63) >> 6); + int cLen = (int)((uint)(aDeg + bDeg + 62) >> 6); + + if (aLen == 1) + { + long a0 = A.m_ints[0]; + if (a0 == 1L) + { + return B; + } + + /* + * Fast path for small A, with performance dependent only on the number of set bits + */ + long[] c0 = new long[cLen]; + MultiplyWord(a0, B.m_ints, bLen, c0, 0); + + /* + * Reduce the raw answer against the reduction coefficients + */ + return ReduceResult(c0, 0, cLen, m, ks); + } + + // NOTE: This works, but is slower than width 4 processing + // if (aLen == 2) + // { + // /* + // * Use common-multiplicand optimization to save ~1/4 of the adds + // */ + // long a1 = A.m_ints[0], a2 = A.m_ints[1]; + // long aa = a1 & a2; a1 ^= aa; a2 ^= aa; + // + // long[] b = B.m_ints; + // long[] c = new long[cLen]; + // multiplyWord(aa, b, bLen, c, 1); + // add(c, 0, c, 1, cLen - 1); + // multiplyWord(a1, b, bLen, c, 0); + // multiplyWord(a2, b, bLen, c, 1); + // + // /* + // * Reduce the raw answer against the reduction coefficients + // */ + // return ReduceResult(c, 0, cLen, m, ks); + // } + + /* + * Determine the parameters of the Interleaved window algorithm: the 'width' in bits to + * process together, the number of evaluation 'positions' implied by that width, and the + * 'top' position at which the regular window algorithm stops. + */ + int width, positions, top, banks; + + // NOTE: width 4 is the fastest over the entire range of sizes used in current crypto + // width = 1; positions = 64; top = 64; banks = 4; + // width = 2; positions = 32; top = 64; banks = 4; + // width = 3; positions = 21; top = 63; banks = 3; + width = 4; positions = 16; top = 64; banks = 8; + // width = 5; positions = 13; top = 65; banks = 7; + // width = 7; positions = 9; top = 63; banks = 9; + // width = 8; positions = 8; top = 64; banks = 8; + + /* + * Determine if B will get bigger during shifting + */ + int shifts = top < 64 ? positions : positions - 1; + int bMax = (int)((uint)(bDeg + shifts + 63) >> 6); + + int bTotal = bMax * banks, stride = width * banks; + + /* + * Create a single temporary buffer, with an offset table to find the positions of things in it + */ + int[] ci = new int[1 << width]; + int cTotal = aLen; + { + ci[0] = cTotal; + cTotal += bTotal; + ci[1] = cTotal; + for (int i = 2; i < ci.Length; ++i) + { + cTotal += cLen; + ci[i] = cTotal; + } + cTotal += cLen; + } + // NOTE: Provide a safe dump for "high zeroes" since we are adding 'bMax' and not 'bLen' + ++cTotal; + + long[] c = new long[cTotal]; + + // Prepare A in Interleaved form, according to the chosen width + Interleave(A.m_ints, 0, c, 0, aLen, width); + + // Make a working copy of B, since we will be shifting it + { + int bOff = aLen; + Array.Copy(B.m_ints, 0, c, bOff, bLen); + for (int bank = 1; bank < banks; ++bank) + { + ShiftUp(c, aLen, c, bOff += bMax, bMax, bank); + } + } + + /* + * The main loop analyzes the Interleaved windows in A, and for each non-zero window + * a single word-array XOR is performed to a carefully selected slice of 'c'. The loop is + * breadth-first, checking the lowest window in each word, then looping again for the + * next higher window position. + */ + int MASK = (1 << width) - 1; + + int k = 0; + for (;;) + { + int aPos = 0; + do + { + long aVal = (long)((ulong)c[aPos] >> k); + int bank = 0, bOff = aLen; + for (;;) + { + int index = (int)(aVal) & MASK; + if (index != 0) + { + /* + * Add to a 'c' buffer based on the bit-pattern of 'index'. Since A is in + * Interleaved form, the bits represent the current B shifted by 0, 'positions', + * 'positions' * 2, ..., 'positions' * ('width' - 1) + */ + Add(c, aPos + ci[index], c, bOff, bMax); + } + if (++bank == banks) + { + break; + } + bOff += bMax; + aVal = (long)((ulong)aVal >> width); + } + } + while (++aPos < aLen); + + if ((k += stride) >= top) + { + if (k >= 64) + { + break; + } + + /* + * Adjustment for window setups with top == 63, the final bit (if any) is processed + * as the top-bit of a window + */ + k = 64 - width; + MASK &= MASK << (top - k); + } + + /* + * After each position has been checked for all words of A, B is shifted up 1 place + */ + ShiftUp(c, aLen, bTotal, banks); + } + + int ciPos = ci.Length; + while (--ciPos > 1) + { + if ((ciPos & 1L) == 0L) + { + /* + * For even numbers, shift contents and add to the half-position + */ + AddShiftedUp(c, ci[(uint)ciPos >> 1], c, ci[ciPos], cLen, positions); + } + else + { + /* + * For odd numbers, 'distribute' contents to the result and the next-lowest position + */ + Distribute(c, ci[ciPos], ci[ciPos - 1], ci[1], cLen); + } + } + + /* + * Finally the raw answer is collected, reduce it against the reduction coefficients + */ + return ReduceResult(c, ci[1], cLen, m, ks); + } + + public LongArray ModReduce(int m, int[] ks) + { + long[] buf = Arrays.Clone(m_ints); + int rLen = ReduceInPlace(buf, 0, buf.Length, m, ks); + return new LongArray(buf, 0, rLen); + } + + public LongArray Multiply(LongArray other, int m, int[] ks) + { + /* + * Find out the degree of each argument and handle the zero cases + */ + int aDeg = Degree(); + if (aDeg == 0) + { + return this; + } + int bDeg = other.Degree(); + if (bDeg == 0) + { + return other; + } + + /* + * Swap if necessary so that A is the smaller argument + */ + LongArray A = this, B = other; + if (aDeg > bDeg) + { + A = other; B = this; + int tmp = aDeg; aDeg = bDeg; bDeg = tmp; + } + + /* + * Establish the word lengths of the arguments and result + */ + int aLen = (int)((uint)(aDeg + 63) >> 6); + int bLen = (int)((uint)(bDeg + 63) >> 6); + int cLen = (int)((uint)(aDeg + bDeg + 62) >> 6); + + if (aLen == 1) + { + long a0 = A.m_ints[0]; + if (a0 == 1L) + { + return B; + } + + /* + * Fast path for small A, with performance dependent only on the number of set bits + */ + long[] c0 = new long[cLen]; + MultiplyWord(a0, B.m_ints, bLen, c0, 0); + + /* + * Reduce the raw answer against the reduction coefficients + */ + //return ReduceResult(c0, 0, cLen, m, ks); + return new LongArray(c0, 0, cLen); + } + + /* + * Determine if B will get bigger during shifting + */ + int bMax = (int)((uint)(bDeg + 7 + 63) >> 6); + + /* + * Lookup table for the offset of each B in the tables + */ + int[] ti = new int[16]; + + /* + * Precompute table of all 4-bit products of B + */ + long[] T0 = new long[bMax << 4]; + int tOff = bMax; + ti[1] = tOff; + Array.Copy(B.m_ints, 0, T0, tOff, bLen); + for (int i = 2; i < 16; ++i) + { + ti[i] = (tOff += bMax); + if ((i & 1) == 0) + { + ShiftUp(T0, (int)((uint)tOff >> 1), T0, tOff, bMax, 1); + } + else + { + Add(T0, bMax, T0, tOff - bMax, T0, tOff, bMax); + } + } + + /* + * Second table with all 4-bit products of B shifted 4 bits + */ + long[] T1 = new long[T0.Length]; + ShiftUp(T0, 0, T1, 0, T0.Length, 4); + // ShiftUp(T0, bMax, T1, bMax, tOff, 4); + + long[] a = A.m_ints; + long[] c = new long[cLen << 3]; + + int MASK = 0xF; + + /* + * Lopez-Dahab (Modified) algorithm + */ + + for (int aPos = 0; aPos < aLen; ++aPos) + { + long aVal = a[aPos]; + int cOff = aPos; + for (; ; ) + { + int u = (int)aVal & MASK; + aVal = (long)((ulong)aVal >> 4); + int v = (int)aVal & MASK; + AddBoth(c, cOff, T0, ti[u], T1, ti[v], bMax); + aVal = (long)((ulong)aVal >> 4); + if (aVal == 0L) + { + break; + } + cOff += cLen; + } + } + + { + int cOff = c.Length; + while ((cOff -= cLen) != 0) + { + AddShiftedUp(c, cOff - cLen, c, cOff, cLen, 8); + } + } + + /* + * Finally the raw answer is collected, reduce it against the reduction coefficients + */ + //return ReduceResult(c, 0, cLen, m, ks); + return new LongArray(c, 0, cLen); + } + + public void Reduce(int m, int[] ks) + { + long[] buf = m_ints; + int rLen = ReduceInPlace(buf, 0, buf.Length, m, ks); + if (rLen < buf.Length) + { + m_ints = new long[rLen]; + Array.Copy(buf, 0, m_ints, 0, rLen); + } + } + + private static LongArray ReduceResult(long[] buf, int off, int len, int m, int[] ks) + { + int rLen = ReduceInPlace(buf, off, len, m, ks); + return new LongArray(buf, off, rLen); + } + + // private static void deInterleave(long[] x, int xOff, long[] z, int zOff, int count, int rounds) + // { + // for (int i = 0; i < count; ++i) + // { + // z[zOff + i] = deInterleave(x[zOff + i], rounds); + // } + // } + // + // private static long deInterleave(long x, int rounds) + // { + // while (--rounds >= 0) + // { + // x = deInterleave32(x & DEInterleave_MASK) | (deInterleave32((x >>> 1) & DEInterleave_MASK) << 32); + // } + // return x; + // } + // + // private static long deInterleave32(long x) + // { + // x = (x | (x >>> 1)) & 0x3333333333333333L; + // x = (x | (x >>> 2)) & 0x0F0F0F0F0F0F0F0FL; + // x = (x | (x >>> 4)) & 0x00FF00FF00FF00FFL; + // x = (x | (x >>> 8)) & 0x0000FFFF0000FFFFL; + // x = (x | (x >>> 16)) & 0x00000000FFFFFFFFL; + // return x; + // } + + private static int ReduceInPlace(long[] buf, int off, int len, int m, int[] ks) + { + int mLen = (m + 63) >> 6; + if (len < mLen) + { + return len; + } + + int numBits = System.Math.Min(len << 6, (m << 1) - 1); // TODO use actual degree? + int excessBits = (len << 6) - numBits; + while (excessBits >= 64) + { + --len; + excessBits -= 64; + } + + int kLen = ks.Length, kMax = ks[kLen - 1], kNext = kLen > 1 ? ks[kLen - 2] : 0; + int wordWiseLimit = System.Math.Max(m, kMax + 64); + int vectorableWords = (excessBits + System.Math.Min(numBits - wordWiseLimit, m - kNext)) >> 6; + if (vectorableWords > 1) + { + int vectorWiseWords = len - vectorableWords; + ReduceVectorWise(buf, off, len, vectorWiseWords, m, ks); + while (len > vectorWiseWords) + { + buf[off + --len] = 0L; + } + numBits = vectorWiseWords << 6; + } + + if (numBits > wordWiseLimit) + { + ReduceWordWise(buf, off, len, wordWiseLimit, m, ks); + numBits = wordWiseLimit; + } + + if (numBits > m) + { + ReduceBitWise(buf, off, numBits, m, ks); + } + + return mLen; + } + + private static void ReduceBitWise(long[] buf, int off, int BitLength, int m, int[] ks) + { + while (--BitLength >= m) + { + if (TestBit(buf, off, BitLength)) + { + ReduceBit(buf, off, BitLength, m, ks); + } + } + } + + private static void ReduceBit(long[] buf, int off, int bit, int m, int[] ks) + { + FlipBit(buf, off, bit); + int n = bit - m; + int j = ks.Length; + while (--j >= 0) + { + FlipBit(buf, off, ks[j] + n); + } + FlipBit(buf, off, n); + } + + private static void ReduceWordWise(long[] buf, int off, int len, int toBit, int m, int[] ks) + { + int toPos = (int)((uint)toBit >> 6); + + while (--len > toPos) + { + long word = buf[off + len]; + if (word != 0) + { + buf[off + len] = 0; + ReduceWord(buf, off, (len << 6), word, m, ks); + } + } + + { + int partial = toBit & 0x3F; + long word = (long)((ulong)buf[off + toPos] >> partial); + if (word != 0) + { + buf[off + toPos] ^= word << partial; + ReduceWord(buf, off, toBit, word, m, ks); + } + } + } + + private static void ReduceWord(long[] buf, int off, int bit, long word, int m, int[] ks) + { + int offset = bit - m; + int j = ks.Length; + while (--j >= 0) + { + FlipWord(buf, off, offset + ks[j], word); + } + FlipWord(buf, off, offset, word); + } + + private static void ReduceVectorWise(long[] buf, int off, int len, int words, int m, int[] ks) + { + /* + * NOTE: It's important we go from highest coefficient to lowest, because for the highest + * one (only) we allow the ranges to partially overlap, and therefore any changes must take + * effect for the subsequent lower coefficients. + */ + int baseBit = (words << 6) - m; + int j = ks.Length; + while (--j >= 0) + { + FlipVector(buf, off, buf, off + words, len - words, baseBit + ks[j]); + } + FlipVector(buf, off, buf, off + words, len - words, baseBit); + } + + private static void FlipVector(long[] x, int xOff, long[] y, int yOff, int yLen, int bits) + { + xOff += (int)((uint)bits >> 6); + bits &= 0x3F; + + if (bits == 0) + { + Add(x, xOff, y, yOff, yLen); + } + else + { + long carry = AddShiftedDown(x, xOff + 1, y, yOff, yLen, 64 - bits); + x[xOff] ^= carry; + } + } + + public LongArray ModSquare(int m, int[] ks) + { + int len = GetUsedLength(); + if (len == 0) + { + return this; + } + + int _2len = len << 1; + long[] r = new long[_2len]; + + int pos = 0; + while (pos < _2len) + { + long mi = m_ints[(uint)pos >> 1]; + r[pos++] = Interleave2_32to64((int)mi); + r[pos++] = Interleave2_32to64((int)((ulong)mi >> 32)); + } + + return new LongArray(r, 0, ReduceInPlace(r, 0, r.Length, m, ks)); + } + + public LongArray ModSquareN(int n, int m, int[] ks) + { + int len = GetUsedLength(); + if (len == 0) + { + return this; + } + + int mLen = (m + 63) >> 6; + long[] r = new long[mLen << 1]; + Array.Copy(m_ints, 0, r, 0, len); + + while (--n >= 0) + { + SquareInPlace(r, len, m, ks); + len = ReduceInPlace(r, 0, r.Length, m, ks); + } + + return new LongArray(r, 0, len); + } + + public LongArray Square(int m, int[] ks) + { + int len = GetUsedLength(); + if (len == 0) + { + return this; + } + + int _2len = len << 1; + long[] r = new long[_2len]; + + int pos = 0; + while (pos < _2len) + { + long mi = m_ints[(uint)pos >> 1]; + r[pos++] = Interleave2_32to64((int)mi); + r[pos++] = Interleave2_32to64((int)((ulong)mi >> 32)); + } + + return new LongArray(r, 0, r.Length); + } + + private static void SquareInPlace(long[] x, int xLen, int m, int[] ks) + { + int pos = xLen << 1; + while (--xLen >= 0) + { + long xVal = x[xLen]; + x[--pos] = Interleave2_32to64((int)((ulong)xVal >> 32)); + x[--pos] = Interleave2_32to64((int)xVal); + } + } + + private static void Interleave(long[] x, int xOff, long[] z, int zOff, int count, int width) + { + switch (width) + { + case 3: + Interleave3(x, xOff, z, zOff, count); + break; + case 5: + Interleave5(x, xOff, z, zOff, count); + break; + case 7: + Interleave7(x, xOff, z, zOff, count); + break; + default: + Interleave2_n(x, xOff, z, zOff, count, BitLengths[width] - 1); + break; + } + } + + private static void Interleave3(long[] x, int xOff, long[] z, int zOff, int count) + { + for (int i = 0; i < count; ++i) + { + z[zOff + i] = Interleave3(x[xOff + i]); + } + } + + private static long Interleave3(long x) + { + long z = x & (1L << 63); + return z + | Interleave3_21to63((int)x & 0x1FFFFF) + | Interleave3_21to63((int)((ulong)x >> 21) & 0x1FFFFF) << 1 + | Interleave3_21to63((int)((ulong)x >> 42) & 0x1FFFFF) << 2; + + // int zPos = 0, wPos = 0, xPos = 0; + // for (;;) + // { + // z |= ((x >>> xPos) & 1L) << zPos; + // if (++zPos == 63) + // { + // String sz2 = Long.toBinaryString(z); + // return z; + // } + // if ((xPos += 21) >= 63) + // { + // xPos = ++wPos; + // } + // } + } + + private static long Interleave3_21to63(int x) + { + int r00 = INTERLEAVE3_TABLE[x & 0x7F]; + int r21 = INTERLEAVE3_TABLE[((uint)x >> 7) & 0x7F]; + int r42 = INTERLEAVE3_TABLE[(uint)x >> 14]; + return (r42 & 0xFFFFFFFFL) << 42 | (r21 & 0xFFFFFFFFL) << 21 | (r00 & 0xFFFFFFFFL); + } + + private static void Interleave5(long[] x, int xOff, long[] z, int zOff, int count) + { + for (int i = 0; i < count; ++i) + { + z[zOff + i] = Interleave5(x[xOff + i]); + } + } + + private static long Interleave5(long x) + { + return Interleave3_13to65((int)x & 0x1FFF) + | Interleave3_13to65((int)((ulong)x >> 13) & 0x1FFF) << 1 + | Interleave3_13to65((int)((ulong)x >> 26) & 0x1FFF) << 2 + | Interleave3_13to65((int)((ulong)x >> 39) & 0x1FFF) << 3 + | Interleave3_13to65((int)((ulong)x >> 52) & 0x1FFF) << 4; + + // long z = 0; + // int zPos = 0, wPos = 0, xPos = 0; + // for (;;) + // { + // z |= ((x >>> xPos) & 1L) << zPos; + // if (++zPos == 64) + // { + // return z; + // } + // if ((xPos += 13) >= 64) + // { + // xPos = ++wPos; + // } + // } + } + + private static long Interleave3_13to65(int x) + { + int r00 = INTERLEAVE5_TABLE[x & 0x7F]; + int r35 = INTERLEAVE5_TABLE[(uint)x >> 7]; + return (r35 & 0xFFFFFFFFL) << 35 | (r00 & 0xFFFFFFFFL); + } + + private static void Interleave7(long[] x, int xOff, long[] z, int zOff, int count) + { + for (int i = 0; i < count; ++i) + { + z[zOff + i] = Interleave7(x[xOff + i]); + } + } + + private static long Interleave7(long x) + { + long z = x & (1L << 63); + return z + | INTERLEAVE7_TABLE[(int)x & 0x1FF] + | INTERLEAVE7_TABLE[(int)((ulong)x >> 9) & 0x1FF] << 1 + | INTERLEAVE7_TABLE[(int)((ulong)x >> 18) & 0x1FF] << 2 + | INTERLEAVE7_TABLE[(int)((ulong)x >> 27) & 0x1FF] << 3 + | INTERLEAVE7_TABLE[(int)((ulong)x >> 36) & 0x1FF] << 4 + | INTERLEAVE7_TABLE[(int)((ulong)x >> 45) & 0x1FF] << 5 + | INTERLEAVE7_TABLE[(int)((ulong)x >> 54) & 0x1FF] << 6; + + // int zPos = 0, wPos = 0, xPos = 0; + // for (;;) + // { + // z |= ((x >>> xPos) & 1L) << zPos; + // if (++zPos == 63) + // { + // return z; + // } + // if ((xPos += 9) >= 63) + // { + // xPos = ++wPos; + // } + // } + } + + private static void Interleave2_n(long[] x, int xOff, long[] z, int zOff, int count, int rounds) + { + for (int i = 0; i < count; ++i) + { + z[zOff + i] = Interleave2_n(x[xOff + i], rounds); + } + } + + private static long Interleave2_n(long x, int rounds) + { + while (rounds > 1) + { + rounds -= 2; + x = Interleave4_16to64((int)x & 0xFFFF) + | Interleave4_16to64((int)((ulong)x >> 16) & 0xFFFF) << 1 + | Interleave4_16to64((int)((ulong)x >> 32) & 0xFFFF) << 2 + | Interleave4_16to64((int)((ulong)x >> 48) & 0xFFFF) << 3; + } + if (rounds > 0) + { + x = Interleave2_32to64((int)x) | Interleave2_32to64((int)((ulong)x >> 32)) << 1; + } + return x; + } + + private static long Interleave4_16to64(int x) + { + int r00 = INTERLEAVE4_TABLE[x & 0xFF]; + int r32 = INTERLEAVE4_TABLE[(uint)x >> 8]; + return (r32 & 0xFFFFFFFFL) << 32 | (r00 & 0xFFFFFFFFL); + } + + private static long Interleave2_32to64(int x) + { + int r00 = INTERLEAVE2_TABLE[x & 0xFF] | INTERLEAVE2_TABLE[((uint)x >> 8) & 0xFF] << 16; + int r32 = INTERLEAVE2_TABLE[((uint)x >> 16) & 0xFF] | INTERLEAVE2_TABLE[(uint)x >> 24] << 16; + return (r32 & 0xFFFFFFFFL) << 32 | (r00 & 0xFFFFFFFFL); + } + + // private static LongArray ExpItohTsujii2(LongArray B, int n, int m, int[] ks) + // { + // LongArray t1 = B, t3 = new LongArray(new long[]{ 1L }); + // int scale = 1; + // + // int numTerms = n; + // while (numTerms > 1) + // { + // if ((numTerms & 1) != 0) + // { + // t3 = t3.ModMultiply(t1, m, ks); + // t1 = t1.modSquareN(scale, m, ks); + // } + // + // LongArray t2 = t1.modSquareN(scale, m, ks); + // t1 = t1.ModMultiply(t2, m, ks); + // numTerms >>>= 1; scale <<= 1; + // } + // + // return t3.ModMultiply(t1, m, ks); + // } + // + // private static LongArray ExpItohTsujii23(LongArray B, int n, int m, int[] ks) + // { + // LongArray t1 = B, t3 = new LongArray(new long[]{ 1L }); + // int scale = 1; + // + // int numTerms = n; + // while (numTerms > 1) + // { + // bool m03 = numTerms % 3 == 0; + // bool m14 = !m03 && (numTerms & 1) != 0; + // + // if (m14) + // { + // t3 = t3.ModMultiply(t1, m, ks); + // t1 = t1.modSquareN(scale, m, ks); + // } + // + // LongArray t2 = t1.modSquareN(scale, m, ks); + // t1 = t1.ModMultiply(t2, m, ks); + // + // if (m03) + // { + // t2 = t2.modSquareN(scale, m, ks); + // t1 = t1.ModMultiply(t2, m, ks); + // numTerms /= 3; scale *= 3; + // } + // else + // { + // numTerms >>>= 1; scale <<= 1; + // } + // } + // + // return t3.ModMultiply(t1, m, ks); + // } + // + // private static LongArray ExpItohTsujii235(LongArray B, int n, int m, int[] ks) + // { + // LongArray t1 = B, t4 = new LongArray(new long[]{ 1L }); + // int scale = 1; + // + // int numTerms = n; + // while (numTerms > 1) + // { + // if (numTerms % 5 == 0) + // { + //// t1 = ExpItohTsujii23(t1, 5, m, ks); + // + // LongArray t3 = t1; + // t1 = t1.modSquareN(scale, m, ks); + // + // LongArray t2 = t1.modSquareN(scale, m, ks); + // t1 = t1.ModMultiply(t2, m, ks); + // t2 = t1.modSquareN(scale << 1, m, ks); + // t1 = t1.ModMultiply(t2, m, ks); + // + // t1 = t1.ModMultiply(t3, m, ks); + // + // numTerms /= 5; scale *= 5; + // continue; + // } + // + // bool m03 = numTerms % 3 == 0; + // bool m14 = !m03 && (numTerms & 1) != 0; + // + // if (m14) + // { + // t4 = t4.ModMultiply(t1, m, ks); + // t1 = t1.modSquareN(scale, m, ks); + // } + // + // LongArray t2 = t1.modSquareN(scale, m, ks); + // t1 = t1.ModMultiply(t2, m, ks); + // + // if (m03) + // { + // t2 = t2.modSquareN(scale, m, ks); + // t1 = t1.ModMultiply(t2, m, ks); + // numTerms /= 3; scale *= 3; + // } + // else + // { + // numTerms >>>= 1; scale <<= 1; + // } + // } + // + // return t4.ModMultiply(t1, m, ks); + // } + + public LongArray ModInverse(int m, int[] ks) + { + /* + * Fermat's Little Theorem + */ + // LongArray A = this; + // LongArray B = A.modSquare(m, ks); + // LongArray R0 = B, R1 = B; + // for (int i = 2; i < m; ++i) + // { + // R1 = R1.modSquare(m, ks); + // R0 = R0.ModMultiply(R1, m, ks); + // } + // + // return R0; + + /* + * Itoh-Tsujii + */ + // LongArray B = modSquare(m, ks); + // switch (m) + // { + // case 409: + // return ExpItohTsujii23(B, m - 1, m, ks); + // case 571: + // return ExpItohTsujii235(B, m - 1, m, ks); + // case 163: + // case 233: + // case 283: + // default: + // return ExpItohTsujii2(B, m - 1, m, ks); + // } + + /* + * 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) + */ + int uzDegree = Degree(); + if (uzDegree == 0) + { + throw new InvalidOperationException(); + } + if (uzDegree == 1) + { + return this; + } + + // u(z) := a(z) + LongArray uz = (LongArray)Copy(); + + int t = (m + 63) >> 6; + + // v(z) := f(z) + LongArray vz = new LongArray(t); + ReduceBit(vz.m_ints, 0, m, m, ks); + + // g1(z) := 1, g2(z) := 0 + LongArray g1z = new LongArray(t); + g1z.m_ints[0] = 1L; + LongArray g2z = new LongArray(t); + + int[] uvDeg = new int[]{ uzDegree, m + 1 }; + LongArray[] uv = new LongArray[]{ uz, vz }; + + int[] ggDeg = new int[]{ 1, 0 }; + LongArray[] gg = new LongArray[]{ g1z, g2z }; + + int b = 1; + int duv1 = uvDeg[b]; + int dgg1 = ggDeg[b]; + int j = duv1 - uvDeg[1 - b]; + + for (;;) + { + if (j < 0) + { + j = -j; + uvDeg[b] = duv1; + ggDeg[b] = dgg1; + b = 1 - b; + duv1 = uvDeg[b]; + dgg1 = ggDeg[b]; + } + + uv[b].AddShiftedByBitsSafe(uv[1 - b], uvDeg[1 - b], j); + + int duv2 = uv[b].DegreeFrom(duv1); + if (duv2 == 0) + { + return gg[1 - b]; + } + + { + int dgg2 = ggDeg[1 - b]; + gg[b].AddShiftedByBitsSafe(gg[1 - b], dgg2, j); + dgg2 += j; + + if (dgg2 > dgg1) + { + dgg1 = dgg2; + } + else if (dgg2 == dgg1) + { + dgg1 = gg[b].DegreeFrom(dgg1); + } + } + + j += (duv2 - duv1); + duv1 = duv2; + } + } + + public override bool Equals(object obj) + { + return Equals(obj as LongArray); + } + + public virtual bool Equals(LongArray other) + { + if (this == other) + return true; + if (null == other) + return false; + 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 override int GetHashCode() + { + int usedLen = GetUsedLength(); + int hash = 1; + for (int i = 0; i < usedLen; i++) + { + long mi = m_ints[i]; + hash *= 31; + hash ^= (int)mi; + hash *= 31; + hash ^= (int)((ulong)mi >> 32); + } + return hash; + } + + public LongArray Copy() + { + return new LongArray(Arrays.Clone(m_ints)); + } + + public override string ToString() + { + int i = GetUsedLength(); + if (i == 0) + { + return "0"; + } + + StringBuilder sb = new StringBuilder(Convert.ToString(m_ints[--i], 2)); + while (--i >= 0) + { + string s = Convert.ToString(m_ints[i], 2); + + // Add leading zeroes, except for highest significant word + int len = s.Length; + if (len < 64) + { + sb.Append(ZEROES.Substring(len)); + } + + sb.Append(s); + } + return sb.ToString(); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/ScaleXPointMap.cs b/bc-sharp-crypto/src/math/ec/ScaleXPointMap.cs new file mode 100644 index 0000000..f8a363b --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/ScaleXPointMap.cs @@ -0,0 +1,20 @@ +using System; + +namespace Org.BouncyCastle.Math.EC +{ + public class ScaleXPointMap + : ECPointMap + { + protected readonly ECFieldElement scale; + + public ScaleXPointMap(ECFieldElement scale) + { + this.scale = scale; + } + + public virtual ECPoint Map(ECPoint p) + { + return p.ScaleX(scale); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/ScaleYPointMap.cs b/bc-sharp-crypto/src/math/ec/ScaleYPointMap.cs new file mode 100644 index 0000000..1c4795b --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/ScaleYPointMap.cs @@ -0,0 +1,20 @@ +using System; + +namespace Org.BouncyCastle.Math.EC +{ + public class ScaleYPointMap + : ECPointMap + { + protected readonly ECFieldElement scale; + + public ScaleYPointMap(ECFieldElement scale) + { + this.scale = scale; + } + + public virtual ECPoint Map(ECPoint p) + { + return p.ScaleY(scale); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/abc/SimpleBigDecimal.cs b/bc-sharp-crypto/src/math/ec/abc/SimpleBigDecimal.cs new file mode 100644 index 0000000..d5664db --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/abc/SimpleBigDecimal.cs @@ -0,0 +1,241 @@ +using System; +using System.Text; + +namespace Org.BouncyCastle.Math.EC.Abc +{ + /** + * 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. + */ + internal class SimpleBigDecimal + // : Number + { + // private static final long serialVersionUID = 1L; + + private readonly BigInteger bigInt; + private readonly 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 val, int scale) + { + return new SimpleBigDecimal(val.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 ArgumentException("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 ArgumentException("Only SimpleBigDecimal of same scale allowed in arithmetic operations"); + } + + public SimpleBigDecimal AdjustScale(int newScale) + { + if (newScale < 0) + throw new ArgumentException("scale may not be negative"); + + if (newScale == scale) + return 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(BigInteger.One, 1); + return Add(oneHalf.AdjustScale(scale)).Floor(); + } + + public int IntValue + { + get { return Floor().IntValue; } + } + + public long LongValue + { + get { return Floor().LongValue; } + } + +// public double doubleValue() +// { +// return new Double(ToString()).doubleValue(); +// } +// +// public float floatValue() +// { +// return new Float(ToString()).floatValue(); +// } + + public int Scale + { + get { return scale; } + } + + public override string ToString() + { + if (scale == 0) + return bigInt.ToString(); + + BigInteger floorBigInt = Floor(); + + BigInteger fract = bigInt.Subtract(floorBigInt.ShiftLeft(scale)); + if (bigInt.SignValue < 0) + { + fract = BigInteger.One.ShiftLeft(scale).Subtract(fract); + } + + if ((floorBigInt.SignValue == -1) && (!(fract.Equals(BigInteger.Zero)))) + { + floorBigInt = floorBigInt.Add(BigInteger.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[j]; + } + string rightOfPoint = new string(fractCharArr); + + StringBuilder sb = new StringBuilder(leftOfPoint); + sb.Append("."); + sb.Append(rightOfPoint); + + return sb.ToString(); + } + + public override bool Equals( + object obj) + { + if (this == obj) + return true; + + SimpleBigDecimal other = obj as SimpleBigDecimal; + + if (other == null) + return false; + + return bigInt.Equals(other.bigInt) + && scale == other.scale; + } + + public override int GetHashCode() + { + return bigInt.GetHashCode() ^ scale; + } + + } +} diff --git a/bc-sharp-crypto/src/math/ec/abc/Tnaf.cs b/bc-sharp-crypto/src/math/ec/abc/Tnaf.cs new file mode 100644 index 0000000..b6e792a --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/abc/Tnaf.cs @@ -0,0 +1,845 @@ +using System; + +namespace Org.BouncyCastle.Math.EC.Abc +{ + /** + * 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. + */ + internal class Tnaf + { + private static readonly BigInteger MinusOne = BigInteger.One.Negate(); + private static readonly BigInteger MinusTwo = BigInteger.Two.Negate(); + private static readonly BigInteger MinusThree = BigInteger.Three.Negate(); + private static readonly BigInteger Four = BigInteger.ValueOf(4); + + /** + * 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 const sbyte Width = 4; + + /** + * 24 + */ + public const sbyte Pow2Width = 16; + + /** + * The αu's for a=0 as an array + * of ZTauElements. + */ + public static readonly ZTauElement[] Alpha0 = + { + null, + new ZTauElement(BigInteger.One, BigInteger.Zero), null, + new ZTauElement(MinusThree, MinusOne), null, + new ZTauElement(MinusOne, MinusOne), null, + new ZTauElement(BigInteger.One, MinusOne), null + }; + + /** + * The αu's for a=0 as an array + * of TNAFs. + */ + public static readonly sbyte[][] Alpha0Tnaf = + { + null, new sbyte[]{1}, null, new sbyte[]{-1, 0, 1}, null, new sbyte[]{1, 0, 1}, null, new sbyte[]{-1, 0, 0, 1} + }; + + /** + * The αu's for a=1 as an array + * of ZTauElements. + */ + public static readonly ZTauElement[] Alpha1 = + { + null, + new ZTauElement(BigInteger.One, BigInteger.Zero), null, + new ZTauElement(MinusThree, BigInteger.One), null, + new ZTauElement(MinusOne, BigInteger.One), null, + new ZTauElement(BigInteger.One, BigInteger.One), null + }; + + /** + * The αu's for a=1 as an array + * of TNAFs. + */ + public static readonly sbyte[][] Alpha1Tnaf = + { + null, new sbyte[]{1}, null, new sbyte[]{-1, 0, 1}, null, new sbyte[]{1, 0, 1}, null, new sbyte[]{-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(sbyte 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 ArgumentException("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(sbyte 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 ArgumentException("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 ArgumentException if lambda0 and + * lambda1 do not have same scale. + */ + public static ZTauElement Round(SimpleBigDecimal lambda0, + SimpleBigDecimal lambda1, sbyte mu) + { + int scale = lambda0.Scale; + if (lambda1.Scale != scale) + throw new ArgumentException("lambda0 and lambda1 do not have same scale"); + + if (!((mu == 1) || (mu == -1))) + throw new ArgumentException("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); + } + + sbyte h0 = 0; + sbyte h1 = 0; + + // if eta >= 1 + if (eta.CompareTo(BigInteger.One) >= 0) + { + if (check1.CompareTo(MinusOne) < 0) + { + h1 = mu; + } + else + { + h0 = 1; + } + } + else + { + // eta < 1 + if (check2.CompareTo(BigInteger.Two) >= 0) + { + h1 = mu; + } + } + + // if eta < -1 + if (eta.CompareTo(MinusOne) < 0) + { + if (check1.CompareTo(BigInteger.One) >= 0) + { + h1 = (sbyte)-mu; + } + else + { + h0 = -1; + } + } + else + { + // eta >= -1 + if (check2.CompareTo(MinusTwo) < 0) + { + h1 = (sbyte)-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, sbyte 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(BigInteger.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 sbyte[] TauAdicNaf(sbyte mu, ZTauElement lambda) + { + if (!((mu == 1) || (mu == -1))) + throw new ArgumentException("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 + sbyte[] u = new sbyte[maxLength]; + int i = 0; + + // The actual length of the TNAF + int length = 0; + + BigInteger r0 = lambda.u; + BigInteger r1 = lambda.v; + + while(!((r0.Equals(BigInteger.Zero)) && (r1.Equals(BigInteger.Zero)))) + { + // If r0 is odd + if (r0.TestBit(0)) + { + u[i] = (sbyte) BigInteger.Two.Subtract((r0.Subtract(r1.ShiftLeft(1))).Mod(Four)).IntValue; + + // r0 = r0 - u[i] + if (u[i] == 1) + { + r0 = r0.ClearBit(0); + } + else + { + // u[i] == -1 + r0 = r0.Add(BigInteger.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 + sbyte[] tnaf = new sbyte[length]; + Array.Copy(u, 0, tnaf, 0, length); + return tnaf; + } + + /** + * Applies the operation τ() to an + * AbstractF2mPoint. + * @param p The AbstractF2mPoint to which τ() is applied. + * @return τ(p) + */ + public static AbstractF2mPoint Tau(AbstractF2mPoint p) + { + return p.Tau(); + } + + /** + * 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 ArgumentException if the given ECCurve is not a Koblitz + * curve. + */ + public static sbyte GetMu(AbstractF2mCurve curve) + { + BigInteger a = curve.A.ToBigInteger(); + + sbyte mu; + if (a.SignValue == 0) + { + mu = -1; + } + else if (a.Equals(BigInteger.One)) + { + mu = 1; + } + else + { + throw new ArgumentException("No Koblitz curve (ABC), TNAF multiplication not possible"); + } + return mu; + } + + public static sbyte GetMu(ECFieldElement curveA) + { + return (sbyte)(curveA.IsZero ? -1 : 1); + } + + public static sbyte GetMu(int curveA) + { + return (sbyte)(curveA == 0 ? -1 : 1); + } + + /** + * 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(sbyte mu, int k, bool doV) + { + if (!(mu == 1 || mu == -1)) + throw new ArgumentException("mu must be 1 or -1"); + + BigInteger u0; + BigInteger u1; + BigInteger u2; + + if (doV) + { + u0 = BigInteger.Two; + u1 = BigInteger.ValueOf(mu); + } + else + { + u0 = BigInteger.Zero; + u1 = BigInteger.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(sbyte 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 = BigInteger.Zero.SetBit(w); + BigInteger u1invert = us[1].ModInverse(twoToW); + BigInteger tw; + tw = BigInteger.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 ArgumentException if curve is not a + * Koblitz curve (Anomalous Binary Curve, ABC). + */ + public static BigInteger[] GetSi(AbstractF2mCurve curve) + { + if (!curve.IsKoblitz) + throw new ArgumentException("si is defined for Koblitz curves only"); + + int m = curve.FieldSize; + int a = curve.A.ToBigInteger().IntValue; + sbyte mu = GetMu(a); + int shifts = GetShiftsForCofactor(curve.Cofactor); + int index = m + 3 - a; + BigInteger[] ui = GetLucas(mu, index, false); + + if (mu == 1) + { + ui[0] = ui[0].Negate(); + ui[1] = ui[1].Negate(); + } + + BigInteger dividend0 = BigInteger.One.Add(ui[1]).ShiftRight(shifts); + BigInteger dividend1 = BigInteger.One.Add(ui[0]).ShiftRight(shifts).Negate(); + + return new BigInteger[] { dividend0, dividend1 }; + } + + public static BigInteger[] GetSi(int fieldSize, int curveA, BigInteger cofactor) + { + sbyte mu = GetMu(curveA); + int shifts = GetShiftsForCofactor(cofactor); + int index = fieldSize + 3 - curveA; + BigInteger[] ui = GetLucas(mu, index, false); + if (mu == 1) + { + ui[0] = ui[0].Negate(); + ui[1] = ui[1].Negate(); + } + + BigInteger dividend0 = BigInteger.One.Add(ui[1]).ShiftRight(shifts); + BigInteger dividend1 = BigInteger.One.Add(ui[0]).ShiftRight(shifts).Negate(); + + return new BigInteger[] { dividend0, dividend1 }; + } + + protected static int GetShiftsForCofactor(BigInteger h) + { + if (h != null && h.BitLength < 4) + { + int hi = h.IntValue; + if (hi == 2) + return 1; + if (hi == 4) + return 2; + } + + throw new ArgumentException("h (Cofactor) must be 2 or 4"); + } + + /** + * 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, sbyte a, + BigInteger[] s, sbyte mu, sbyte 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 org.bouncycastle.math.ec.AbstractF2mPoint AbstractF2mPoint} + * by a BigInteger using the reduced τ-adic + * NAF (RTNAF) method. + * @param p The AbstractF2mPoint to Multiply. + * @param k The BigInteger by which to Multiply p. + * @return k * p + */ + public static AbstractF2mPoint MultiplyRTnaf(AbstractF2mPoint p, BigInteger k) + { + AbstractF2mCurve curve = (AbstractF2mCurve)p.Curve; + int m = curve.FieldSize; + int a = curve.A.ToBigInteger().IntValue; + sbyte mu = GetMu(a); + BigInteger[] s = curve.GetSi(); + ZTauElement rho = PartModReduction(k, m, (sbyte)a, s, mu, (sbyte)10); + + return MultiplyTnaf(p, rho); + } + + /** + * Multiplies a {@link org.bouncycastle.math.ec.AbstractF2mPoint AbstractF2mPoint} + * by an element λ of Z[τ] + * using the τ-adic NAF (TNAF) method. + * @param p The AbstractF2mPoint to Multiply. + * @param lambda The element λ of + * Z[τ]. + * @return λ * p + */ + public static AbstractF2mPoint MultiplyTnaf(AbstractF2mPoint p, ZTauElement lambda) + { + AbstractF2mCurve curve = (AbstractF2mCurve)p.Curve; + sbyte mu = GetMu(curve.A); + sbyte[] u = TauAdicNaf(mu, lambda); + + AbstractF2mPoint q = MultiplyFromTnaf(p, u); + + return q; + } + + /** + * Multiplies a {@link org.bouncycastle.math.ec.AbstractF2mPoint AbstractF2mPoint} + * by an element λ of Z[τ] + * using the τ-adic NAF (TNAF) method, given the TNAF + * of λ. + * @param p The AbstractF2mPoint to Multiply. + * @param u The the TNAF of λ.. + * @return λ * p + */ + public static AbstractF2mPoint MultiplyFromTnaf(AbstractF2mPoint p, sbyte[] u) + { + ECCurve curve = p.Curve; + AbstractF2mPoint q = (AbstractF2mPoint)curve.Infinity; + AbstractF2mPoint pNeg = (AbstractF2mPoint)p.Negate(); + int tauCount = 0; + for (int i = u.Length - 1; i >= 0; i--) + { + ++tauCount; + sbyte ui = u[i]; + if (ui != 0) + { + q = q.TauPow(tauCount); + tauCount = 0; + + ECPoint x = ui > 0 ? p : pNeg; + q = (AbstractF2mPoint)q.Add(x); + } + } + if (tauCount > 0) + { + q = q.TauPow(tauCount); + } + 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 sbyte[] TauAdicWNaf(sbyte mu, ZTauElement lambda, + sbyte width, BigInteger pow2w, BigInteger tw, ZTauElement[] alpha) + { + if (!((mu == 1) || (mu == -1))) + throw new ArgumentException("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 + sbyte[] u = new sbyte[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(BigInteger.Zero))&&(r1.Equals(BigInteger.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); + + sbyte uLocal; + // if uUnMod >= 2^(width - 1) + if (uUnMod.CompareTo(pow2wMin1) >= 0) + { + uLocal = (sbyte) uUnMod.Subtract(pow2w).IntValue; + } + else + { + uLocal = (sbyte) uUnMod.IntValue; + } + // uLocal is now in [-2^(width-1), 2^(width-1)-1] + + u[i] = uLocal; + bool s = true; + if (uLocal < 0) + { + s = false; + uLocal = (sbyte)-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 AbstractF2mPoint[] GetPreComp(AbstractF2mPoint p, sbyte a) + { + sbyte[][] alphaTnaf = (a == 0) ? Tnaf.Alpha0Tnaf : Tnaf.Alpha1Tnaf; + + AbstractF2mPoint[] pu = new AbstractF2mPoint[(uint)(alphaTnaf.Length + 1) >> 1]; + pu[0] = p; + + uint precompLen = (uint)alphaTnaf.Length; + for (uint i = 3; i < precompLen; i += 2) + { + pu[i >> 1] = Tnaf.MultiplyFromTnaf(p, alphaTnaf[i]); + } + + p.Curve.NormalizeAll(pu); + + return pu; + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/abc/ZTauElement.cs b/bc-sharp-crypto/src/math/ec/abc/ZTauElement.cs new file mode 100644 index 0000000..4fcbf1b --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/abc/ZTauElement.cs @@ -0,0 +1,36 @@ +namespace Org.BouncyCastle.Math.EC.Abc +{ + /** + * 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. + */ + internal class ZTauElement + { + /** + * The "real" part of λ. + */ + public readonly BigInteger u; + + /** + * The "τ-adic" part of λ. + */ + public readonly 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/bc-sharp-crypto/src/math/ec/custom/djb/Curve25519.cs b/bc-sharp-crypto/src/math/ec/custom/djb/Curve25519.cs new file mode 100644 index 0000000..6ed7c06 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/djb/Curve25519.cs @@ -0,0 +1,77 @@ +using System; + +using Org.BouncyCastle.Math.Raw; +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Math.EC.Custom.Djb +{ + internal class Curve25519 + : AbstractFpCurve + { + public static readonly BigInteger q = Nat256.ToBigInteger(Curve25519Field.P); + + private const int Curve25519_DEFAULT_COORDS = COORD_JACOBIAN_MODIFIED; + + protected readonly Curve25519Point m_infinity; + + public Curve25519() + : base(q) + { + this.m_infinity = new Curve25519Point(this, null, null); + + this.m_a = FromBigInteger(new BigInteger(1, + Hex.Decode("2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA984914A144"))); + this.m_b = FromBigInteger(new BigInteger(1, + Hex.Decode("7B425ED097B425ED097B425ED097B425ED097B425ED097B4260B5E9C7710C864"))); + this.m_order = new BigInteger(1, Hex.Decode("1000000000000000000000000000000014DEF9DEA2F79CD65812631A5CF5D3ED")); + this.m_cofactor = BigInteger.ValueOf(8); + this.m_coord = Curve25519_DEFAULT_COORDS; + } + + protected override ECCurve CloneCurve() + { + return new Curve25519(); + } + + public override bool SupportsCoordinateSystem(int coord) + { + switch (coord) + { + case COORD_JACOBIAN_MODIFIED: + return true; + default: + return false; + } + } + + public virtual BigInteger Q + { + get { return q; } + } + + public override ECPoint Infinity + { + get { return m_infinity; } + } + + public override int FieldSize + { + get { return q.BitLength; } + } + + public override ECFieldElement FromBigInteger(BigInteger x) + { + return new Curve25519FieldElement(x); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, bool withCompression) + { + return new Curve25519Point(this, x, y, withCompression); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + { + return new Curve25519Point(this, x, y, zs, withCompression); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/djb/Curve25519Field.cs b/bc-sharp-crypto/src/math/ec/custom/djb/Curve25519Field.cs new file mode 100644 index 0000000..837821e --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/djb/Curve25519Field.cs @@ -0,0 +1,253 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Math.Raw; + +namespace Org.BouncyCastle.Math.EC.Custom.Djb +{ + internal class Curve25519Field + { + // 2^255 - 2^4 - 2^1 - 1 + internal static readonly uint[] P = new uint[]{ 0xFFFFFFED, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0x7FFFFFFF }; + private const uint P7 = 0x7FFFFFFF; + private static readonly uint[] PExt = new uint[]{ 0x00000169, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0xFFFFFFED, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0x3FFFFFFF }; + private const uint PInv = 0x13; + + public static void Add(uint[] x, uint[] y, uint[] z) + { + Nat256.Add(x, y, z); + if (Nat256.Gte(z, P)) + { + SubPFrom(z); + } + } + + public static void AddExt(uint[] xx, uint[] yy, uint[] zz) + { + Nat.Add(16, xx, yy, zz); + if (Nat.Gte(16, zz, PExt)) + { + SubPExtFrom(zz); + } + } + + public static void AddOne(uint[] x, uint[] z) + { + Nat.Inc(8, x, z); + if (Nat256.Gte(z, P)) + { + SubPFrom(z); + } + } + + public static uint[] FromBigInteger(BigInteger x) + { + uint[] z = Nat256.FromBigInteger(x); + while (Nat256.Gte(z, P)) + { + Nat256.SubFrom(P, z); + } + return z; + } + + public static void Half(uint[] x, uint[] z) + { + if ((x[0] & 1) == 0) + { + Nat.ShiftDownBit(8, x, 0, z); + } + else + { + Nat256.Add(x, P, z); + Nat.ShiftDownBit(8, z, 0); + } + } + + public static void Multiply(uint[] x, uint[] y, uint[] z) + { + uint[] tt = Nat256.CreateExt(); + Nat256.Mul(x, y, tt); + Reduce(tt, z); + } + + public static void MultiplyAddToExt(uint[] x, uint[] y, uint[] zz) + { + Nat256.MulAddTo(x, y, zz); + if (Nat.Gte(16, zz, PExt)) + { + SubPExtFrom(zz); + } + } + + public static void Negate(uint[] x, uint[] z) + { + if (Nat256.IsZero(x)) + { + Nat256.Zero(z); + } + else + { + Nat256.Sub(P, x, z); + } + } + + public static void Reduce(uint[] xx, uint[] z) + { + Debug.Assert(xx[15] >> 30 == 0); + + uint xx07 = xx[7]; + Nat.ShiftUpBit(8, xx, 8, xx07, z, 0); + uint c = Nat256.MulByWordAddTo(PInv, xx, z) << 1; + uint z7 = z[7]; + c += (z7 >> 31) - (xx07 >> 31); + z7 &= P7; + z7 += Nat.AddWordTo(7, c * PInv, z); + z[7] = z7; + if (z7 >= P7 && Nat256.Gte(z, P)) + { + SubPFrom(z); + } + } + + public static void Reduce27(uint x, uint[] z) + { + Debug.Assert(x >> 26 == 0); + + uint z7 = z[7]; + uint c = (x << 1 | z7 >> 31); + z7 &= P7; + z7 += Nat.AddWordTo(7, c * PInv, z); + z[7] = z7; + if (z7 >= P7 && Nat256.Gte(z, P)) + { + SubPFrom(z); + } + } + + public static void Square(uint[] x, uint[] z) + { + uint[] tt = Nat256.CreateExt(); + Nat256.Square(x, tt); + Reduce(tt, z); + } + + public static void SquareN(uint[] x, int n, uint[] z) + { + Debug.Assert(n > 0); + + uint[] tt = Nat256.CreateExt(); + Nat256.Square(x, tt); + Reduce(tt, z); + + while (--n > 0) + { + Nat256.Square(z, tt); + Reduce(tt, z); + } + } + + public static void Subtract(uint[] x, uint[] y, uint[] z) + { + int c = Nat256.Sub(x, y, z); + if (c != 0) + { + AddPTo(z); + } + } + + public static void SubtractExt(uint[] xx, uint[] yy, uint[] zz) + { + int c = Nat.Sub(16, xx, yy, zz); + if (c != 0) + { + AddPExtTo(zz); + } + } + + public static void Twice(uint[] x, uint[] z) + { + Nat.ShiftUpBit(8, x, 0, z); + if (Nat256.Gte(z, P)) + { + SubPFrom(z); + } + } + + private static uint AddPTo(uint[] z) + { + long c = (long)z[0] - PInv; + z[0] = (uint)c; + c >>= 32; + if (c != 0) + { + c = Nat.DecAt(7, z, 1); + } + c += (long)z[7] + (P7 + 1); + z[7] = (uint)c; + c >>= 32; + return (uint)c; + } + + private static uint AddPExtTo(uint[] zz) + { + long c = (long)zz[0] + PExt[0]; + zz[0] = (uint)c; + c >>= 32; + if (c != 0) + { + c = Nat.IncAt(8, zz, 1); + } + c += (long)zz[8] - PInv; + zz[8] = (uint)c; + c >>= 32; + if (c != 0) + { + c = Nat.DecAt(15, zz, 9); + } + c += (long)zz[15] + (PExt[15] + 1); + zz[15] = (uint)c; + c >>= 32; + return (uint)c; + } + + private static int SubPFrom(uint[] z) + { + long c = (long)z[0] + PInv; + z[0] = (uint)c; + c >>= 32; + if (c != 0) + { + c = Nat.IncAt(7, z, 1); + } + c += (long)z[7] - (P7 + 1); + z[7] = (uint)c; + c >>= 32; + return (int)c; + } + + private static int SubPExtFrom(uint[] zz) + { + long c = (long)zz[0] - PExt[0]; + zz[0] = (uint)c; + c >>= 32; + if (c != 0) + { + c = Nat.DecAt(8, zz, 1); + } + c += (long)zz[8] + PInv; + zz[8] = (uint)c; + c >>= 32; + if (c != 0) + { + c = Nat.IncAt(15, zz, 9); + } + c += (long)zz[15] - (PExt[15] + 1); + zz[15] = (uint)c; + c >>= 32; + return (int)c; + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/djb/Curve25519FieldElement.cs b/bc-sharp-crypto/src/math/ec/custom/djb/Curve25519FieldElement.cs new file mode 100644 index 0000000..732e9e4 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/djb/Curve25519FieldElement.cs @@ -0,0 +1,233 @@ +using System; + +using Org.BouncyCastle.Math.Raw; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Math.EC.Custom.Djb +{ + internal class Curve25519FieldElement + : ECFieldElement + { + public static readonly BigInteger Q = Curve25519.q; + + // Calculated as ECConstants.TWO.modPow(Q.shiftRight(2), Q) + private static readonly uint[] PRECOMP_POW2 = new uint[]{ 0x4a0ea0b0, 0xc4ee1b27, 0xad2fe478, 0x2f431806, + 0x3dfbd7a7, 0x2b4d0099, 0x4fc1df0b, 0x2b832480 }; + + protected internal readonly uint[] x; + + public Curve25519FieldElement(BigInteger x) + { + if (x == null || x.SignValue < 0 || x.CompareTo(Q) >= 0) + throw new ArgumentException("value invalid for Curve25519FieldElement", "x"); + + this.x = Curve25519Field.FromBigInteger(x); + } + + public Curve25519FieldElement() + { + this.x = Nat256.Create(); + } + + protected internal Curve25519FieldElement(uint[] x) + { + this.x = x; + } + + public override bool IsZero + { + get { return Nat256.IsZero(x); } + } + + public override bool IsOne + { + get { return Nat256.IsOne(x); } + } + + public override bool TestBitZero() + { + return Nat256.GetBit(x, 0) == 1; + } + + public override BigInteger ToBigInteger() + { + return Nat256.ToBigInteger(x); + } + + public override string FieldName + { + get { return "Curve25519Field"; } + } + + public override int FieldSize + { + get { return Q.BitLength; } + } + + public override ECFieldElement Add(ECFieldElement b) + { + uint[] z = Nat256.Create(); + Curve25519Field.Add(x, ((Curve25519FieldElement)b).x, z); + return new Curve25519FieldElement(z); + } + + public override ECFieldElement AddOne() + { + uint[] z = Nat256.Create(); + Curve25519Field.AddOne(x, z); + return new Curve25519FieldElement(z); + } + + public override ECFieldElement Subtract(ECFieldElement b) + { + uint[] z = Nat256.Create(); + Curve25519Field.Subtract(x, ((Curve25519FieldElement)b).x, z); + return new Curve25519FieldElement(z); + } + + public override ECFieldElement Multiply(ECFieldElement b) + { + uint[] z = Nat256.Create(); + Curve25519Field.Multiply(x, ((Curve25519FieldElement)b).x, z); + return new Curve25519FieldElement(z); + } + + public override ECFieldElement Divide(ECFieldElement b) + { + //return Multiply(b.Invert()); + uint[] z = Nat256.Create(); + Mod.Invert(Curve25519Field.P, ((Curve25519FieldElement)b).x, z); + Curve25519Field.Multiply(z, x, z); + return new Curve25519FieldElement(z); + } + + public override ECFieldElement Negate() + { + uint[] z = Nat256.Create(); + Curve25519Field.Negate(x, z); + return new Curve25519FieldElement(z); + } + + public override ECFieldElement Square() + { + uint[] z = Nat256.Create(); + Curve25519Field.Square(x, z); + return new Curve25519FieldElement(z); + } + + public override ECFieldElement Invert() + { + //return new Curve25519FieldElement(ToBigInteger().ModInverse(Q)); + uint[] z = Nat256.Create(); + Mod.Invert(Curve25519Field.P, x, z); + return new Curve25519FieldElement(z); + } + + /** + * return a sqrt root - the routine verifies that the calculation returns the right value - if + * none exists it returns null. + */ + public override ECFieldElement Sqrt() + { + /* + * Q == 8m + 5, so we use Pocklington's method for this case. + * + * First, raise this element to the exponent 2^252 - 2^1 (i.e. m + 1) + * + * Breaking up the exponent's binary representation into "repunits", we get: + * { 251 1s } { 1 0s } + * + * Therefore we need an addition chain containing 251 (the lengths of the repunits) + * We use: 1, 2, 3, 4, 7, 11, 15, 30, 60, 120, 131, [251] + */ + + uint[] x1 = this.x; + if (Nat256.IsZero(x1) || Nat256.IsOne(x1)) + return this; + + uint[] x2 = Nat256.Create(); + Curve25519Field.Square(x1, x2); + Curve25519Field.Multiply(x2, x1, x2); + uint[] x3 = x2; + Curve25519Field.Square(x2, x3); + Curve25519Field.Multiply(x3, x1, x3); + uint[] x4 = Nat256.Create(); + Curve25519Field.Square(x3, x4); + Curve25519Field.Multiply(x4, x1, x4); + uint[] x7 = Nat256.Create(); + Curve25519Field.SquareN(x4, 3, x7); + Curve25519Field.Multiply(x7, x3, x7); + uint[] x11 = x3; + Curve25519Field.SquareN(x7, 4, x11); + Curve25519Field.Multiply(x11, x4, x11); + uint[] x15 = x7; + Curve25519Field.SquareN(x11, 4, x15); + Curve25519Field.Multiply(x15, x4, x15); + uint[] x30 = x4; + Curve25519Field.SquareN(x15, 15, x30); + Curve25519Field.Multiply(x30, x15, x30); + uint[] x60 = x15; + Curve25519Field.SquareN(x30, 30, x60); + Curve25519Field.Multiply(x60, x30, x60); + uint[] x120 = x30; + Curve25519Field.SquareN(x60, 60, x120); + Curve25519Field.Multiply(x120, x60, x120); + uint[] x131 = x60; + Curve25519Field.SquareN(x120, 11, x131); + Curve25519Field.Multiply(x131, x11, x131); + uint[] x251 = x11; + Curve25519Field.SquareN(x131, 120, x251); + Curve25519Field.Multiply(x251, x120, x251); + + uint[] t1 = x251; + Curve25519Field.Square(t1, t1); + + uint[] t2 = x120; + Curve25519Field.Square(t1, t2); + + if (Nat256.Eq(x1, t2)) + { + return new Curve25519FieldElement(t1); + } + + /* + * If the first guess is incorrect, we multiply by a precomputed power of 2 to get the second guess, + * which is ((4x)^(m + 1))/2 mod Q + */ + Curve25519Field.Multiply(t1, PRECOMP_POW2, t1); + + Curve25519Field.Square(t1, t2); + + if (Nat256.Eq(x1, t2)) + { + return new Curve25519FieldElement(t1); + } + + return null; + } + + public override bool Equals(object obj) + { + return Equals(obj as Curve25519FieldElement); + } + + public override bool Equals(ECFieldElement other) + { + return Equals(other as Curve25519FieldElement); + } + + public virtual bool Equals(Curve25519FieldElement other) + { + if (this == other) + return true; + if (null == other) + return false; + return Nat256.Eq(x, other.x); + } + + public override int GetHashCode() + { + return Q.GetHashCode() ^ Arrays.GetHashCode(x, 0, 8); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/djb/Curve25519Point.cs b/bc-sharp-crypto/src/math/ec/custom/djb/Curve25519Point.cs new file mode 100644 index 0000000..eb8fc12 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/djb/Curve25519Point.cs @@ -0,0 +1,313 @@ +using System; + +using Org.BouncyCastle.Math.Raw; + +namespace Org.BouncyCastle.Math.EC.Custom.Djb +{ + internal class Curve25519Point + : AbstractFpPoint + { + /** + * 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 + * + * @deprecated Use ECCurve.CreatePoint to construct points + */ + public Curve25519Point(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 + * + * @deprecated per-point compression property will be removed, refer {@link #getEncoded(bool)} + */ + public Curve25519Point(ECCurve curve, ECFieldElement x, ECFieldElement y, bool withCompression) + : base(curve, x, y, withCompression) + { + if ((x == null) != (y == null)) + throw new ArgumentException("Exactly one of the field elements is null"); + } + + internal Curve25519Point(ECCurve curve, ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + : base(curve, x, y, zs, withCompression) + { + } + + protected override ECPoint Detach() + { + return new Curve25519Point(null, AffineXCoord, AffineYCoord); + } + + public override ECFieldElement GetZCoord(int index) + { + if (index == 1) + { + return GetJacobianModifiedW(); + } + + return base.GetZCoord(index); + } + + public override ECPoint Add(ECPoint b) + { + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return this; + if (this == b) + return Twice(); + + ECCurve curve = this.Curve; + + Curve25519FieldElement X1 = (Curve25519FieldElement)this.RawXCoord, Y1 = (Curve25519FieldElement)this.RawYCoord, + Z1 = (Curve25519FieldElement)this.RawZCoords[0]; + Curve25519FieldElement X2 = (Curve25519FieldElement)b.RawXCoord, Y2 = (Curve25519FieldElement)b.RawYCoord, + Z2 = (Curve25519FieldElement)b.RawZCoords[0]; + + uint c; + uint[] tt1 = Nat256.CreateExt(); + uint[] t2 = Nat256.Create(); + uint[] t3 = Nat256.Create(); + uint[] t4 = Nat256.Create(); + + bool Z1IsOne = Z1.IsOne; + uint[] U2, S2; + if (Z1IsOne) + { + U2 = X2.x; + S2 = Y2.x; + } + else + { + S2 = t3; + Curve25519Field.Square(Z1.x, S2); + + U2 = t2; + Curve25519Field.Multiply(S2, X2.x, U2); + + Curve25519Field.Multiply(S2, Z1.x, S2); + Curve25519Field.Multiply(S2, Y2.x, S2); + } + + bool Z2IsOne = Z2.IsOne; + uint[] U1, S1; + if (Z2IsOne) + { + U1 = X1.x; + S1 = Y1.x; + } + else + { + S1 = t4; + Curve25519Field.Square(Z2.x, S1); + + U1 = tt1; + Curve25519Field.Multiply(S1, X1.x, U1); + + Curve25519Field.Multiply(S1, Z2.x, S1); + Curve25519Field.Multiply(S1, Y1.x, S1); + } + + uint[] H = Nat256.Create(); + Curve25519Field.Subtract(U1, U2, H); + + uint[] R = t2; + Curve25519Field.Subtract(S1, S2, R); + + // Check if b == this or b == -this + if (Nat256.IsZero(H)) + { + if (Nat256.IsZero(R)) + { + // this == b, i.e. this must be doubled + return this.Twice(); + } + + // this == -b, i.e. the result is the point at infinity + return curve.Infinity; + } + + uint[] HSquared = Nat256.Create(); + Curve25519Field.Square(H, HSquared); + + uint[] G = Nat256.Create(); + Curve25519Field.Multiply(HSquared, H, G); + + uint[] V = t3; + Curve25519Field.Multiply(HSquared, U1, V); + + Curve25519Field.Negate(G, G); + Nat256.Mul(S1, G, tt1); + + c = Nat256.AddBothTo(V, V, G); + Curve25519Field.Reduce27(c, G); + + Curve25519FieldElement X3 = new Curve25519FieldElement(t4); + Curve25519Field.Square(R, X3.x); + Curve25519Field.Subtract(X3.x, G, X3.x); + + Curve25519FieldElement Y3 = new Curve25519FieldElement(G); + Curve25519Field.Subtract(V, X3.x, Y3.x); + Curve25519Field.MultiplyAddToExt(Y3.x, R, tt1); + Curve25519Field.Reduce(tt1, Y3.x); + + Curve25519FieldElement Z3 = new Curve25519FieldElement(H); + if (!Z1IsOne) + { + Curve25519Field.Multiply(Z3.x, Z1.x, Z3.x); + } + if (!Z2IsOne) + { + Curve25519Field.Multiply(Z3.x, Z2.x, Z3.x); + } + + uint[] Z3Squared = (Z1IsOne && Z2IsOne) ? HSquared : null; + + // TODO If the result will only be used in a subsequent addition, we don't need W3 + Curve25519FieldElement W3 = CalculateJacobianModifiedW((Curve25519FieldElement)Z3, Z3Squared); + + ECFieldElement[] zs = new ECFieldElement[] { Z3, W3 }; + + return new Curve25519Point(curve, X3, Y3, zs, IsCompressed); + } + + public override ECPoint Twice() + { + if (this.IsInfinity) + return this; + + ECCurve curve = this.Curve; + + ECFieldElement Y1 = this.RawYCoord; + if (Y1.IsZero) + return curve.Infinity; + + return TwiceJacobianModified(true); + } + + public override ECPoint TwicePlus(ECPoint b) + { + if (this == b) + return ThreeTimes(); + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return Twice(); + + ECFieldElement Y1 = this.RawYCoord; + if (Y1.IsZero) + return b; + + return TwiceJacobianModified(false).Add(b); + } + + public override ECPoint ThreeTimes() + { + if (this.IsInfinity || this.RawYCoord.IsZero) + return this; + + return TwiceJacobianModified(false).Add(this); + } + + public override ECPoint Negate() + { + if (IsInfinity) + return this; + + return new Curve25519Point(Curve, RawXCoord, RawYCoord.Negate(), RawZCoords, IsCompressed); + } + + protected virtual Curve25519FieldElement CalculateJacobianModifiedW(Curve25519FieldElement Z, uint[] ZSquared) + { + Curve25519FieldElement a4 = (Curve25519FieldElement)this.Curve.A; + if (Z.IsOne) + return a4; + + Curve25519FieldElement W = new Curve25519FieldElement(); + if (ZSquared == null) + { + ZSquared = W.x; + Curve25519Field.Square(Z.x, ZSquared); + } + Curve25519Field.Square(ZSquared, W.x); + Curve25519Field.Multiply(W.x, a4.x, W.x); + return W; + } + + protected virtual Curve25519FieldElement GetJacobianModifiedW() + { + ECFieldElement[] ZZ = this.RawZCoords; + Curve25519FieldElement W = (Curve25519FieldElement)ZZ[1]; + if (W == null) + { + // NOTE: Rarely, TwicePlus will result in the need for a lazy W1 calculation here + ZZ[1] = W = CalculateJacobianModifiedW((Curve25519FieldElement)ZZ[0], null); + } + return W; + } + + protected virtual Curve25519Point TwiceJacobianModified(bool calculateW) + { + Curve25519FieldElement X1 = (Curve25519FieldElement)this.RawXCoord, Y1 = (Curve25519FieldElement)this.RawYCoord, + Z1 = (Curve25519FieldElement)this.RawZCoords[0], W1 = GetJacobianModifiedW(); + + uint c; + + uint[] M = Nat256.Create(); + Curve25519Field.Square(X1.x, M); + c = Nat256.AddBothTo(M, M, M); + c += Nat256.AddTo(W1.x, M); + Curve25519Field.Reduce27(c, M); + + uint[] _2Y1 = Nat256.Create(); + Curve25519Field.Twice(Y1.x, _2Y1); + + uint[] _2Y1Squared = Nat256.Create(); + Curve25519Field.Multiply(_2Y1, Y1.x, _2Y1Squared); + + uint[] S = Nat256.Create(); + Curve25519Field.Multiply(_2Y1Squared, X1.x, S); + Curve25519Field.Twice(S, S); + + uint[] _8T = Nat256.Create(); + Curve25519Field.Square(_2Y1Squared, _8T); + Curve25519Field.Twice(_8T, _8T); + + Curve25519FieldElement X3 = new Curve25519FieldElement(_2Y1Squared); + Curve25519Field.Square(M, X3.x); + Curve25519Field.Subtract(X3.x, S, X3.x); + Curve25519Field.Subtract(X3.x, S, X3.x); + + Curve25519FieldElement Y3 = new Curve25519FieldElement(S); + Curve25519Field.Subtract(S, X3.x, Y3.x); + Curve25519Field.Multiply(Y3.x, M, Y3.x); + Curve25519Field.Subtract(Y3.x, _8T, Y3.x); + + Curve25519FieldElement Z3 = new Curve25519FieldElement(_2Y1); + if (!Nat256.IsOne(Z1.x)) + { + Curve25519Field.Multiply(Z3.x, Z1.x, Z3.x); + } + + Curve25519FieldElement W3 = null; + if (calculateW) + { + W3 = new Curve25519FieldElement(_8T); + Curve25519Field.Multiply(W3.x, W1.x, W3.x); + Curve25519Field.Twice(W3.x, W3.x); + } + + return new Curve25519Point(this.Curve, X3, Y3, new ECFieldElement[] { Z3, W3 }, IsCompressed); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/gm/SM2P256V1Curve.cs b/bc-sharp-crypto/src/math/ec/custom/gm/SM2P256V1Curve.cs new file mode 100644 index 0000000..70b1190 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/gm/SM2P256V1Curve.cs @@ -0,0 +1,77 @@ +using System; + +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Math.EC.Custom.GM +{ + internal class SM2P256V1Curve + : AbstractFpCurve + { + public static readonly BigInteger q = new BigInteger(1, + Hex.Decode("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF")); + + private const int SM2P256V1_DEFAULT_COORDS = COORD_JACOBIAN; + + protected readonly SM2P256V1Point m_infinity; + + public SM2P256V1Curve() + : base(q) + { + this.m_infinity = new SM2P256V1Point(this, null, null); + + this.m_a = FromBigInteger(new BigInteger(1, + Hex.Decode("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC"))); + this.m_b = FromBigInteger(new BigInteger(1, + Hex.Decode("28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93"))); + this.m_order = new BigInteger(1, Hex.Decode("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123")); + this.m_cofactor = BigInteger.One; + this.m_coord = SM2P256V1_DEFAULT_COORDS; + } + + protected override ECCurve CloneCurve() + { + return new SM2P256V1Curve(); + } + + public override bool SupportsCoordinateSystem(int coord) + { + switch (coord) + { + case COORD_JACOBIAN: + return true; + default: + return false; + } + } + + public virtual BigInteger Q + { + get { return q; } + } + + public override ECPoint Infinity + { + get { return m_infinity; } + } + + public override int FieldSize + { + get { return q.BitLength; } + } + + public override ECFieldElement FromBigInteger(BigInteger x) + { + return new SM2P256V1FieldElement(x); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, bool withCompression) + { + return new SM2P256V1Point(this, x, y, withCompression); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + { + return new SM2P256V1Point(this, x, y, zs, withCompression); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/gm/SM2P256V1Field.cs b/bc-sharp-crypto/src/math/ec/custom/gm/SM2P256V1Field.cs new file mode 100644 index 0000000..b1d2323 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/gm/SM2P256V1Field.cs @@ -0,0 +1,307 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Math.Raw; + +namespace Org.BouncyCastle.Math.EC.Custom.GM +{ + internal class SM2P256V1Field + { + // 2^256 - 2^224 - 2^96 + 2^64 - 1 + internal static readonly uint[] P = new uint[]{ 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFE }; + internal static readonly uint[] PExt = new uint[]{ 00000001, 0x00000000, 0xFFFFFFFE, 0x00000001, 0x00000001, + 0xFFFFFFFE, 0x00000000, 0x00000002, 0xFFFFFFFE, 0xFFFFFFFD, 0x00000003, 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFF, + 0x00000000, 0xFFFFFFFE }; + internal const uint P7 = 0xFFFFFFFE; + internal const uint PExt15 = 0xFFFFFFFE; + + public static void Add(uint[] x, uint[] y, uint[] z) + { + uint c = Nat256.Add(x, y, z); + if (c != 0 || (z[7] >= P7 && Nat256.Gte(z, P))) + { + AddPInvTo(z); + } + } + + public static void AddExt(uint[] xx, uint[] yy, uint[] zz) + { + uint c = Nat.Add(16, xx, yy, zz); + if (c != 0 || (zz[15] >= PExt15 && Nat.Gte(16, zz, PExt))) + { + Nat.SubFrom(16, PExt, zz); + } + } + + public static void AddOne(uint[] x, uint[] z) + { + uint c = Nat.Inc(8, x, z); + if (c != 0 || (z[7] >= P7 && Nat256.Gte(z, P))) + { + AddPInvTo(z); + } + } + + public static uint[] FromBigInteger(BigInteger x) + { + uint[] z = Nat256.FromBigInteger(x); + if (z[7] >= P7 && Nat256.Gte(z, P)) + { + Nat256.SubFrom(P, z); + } + return z; + } + + public static void Half(uint[] x, uint[] z) + { + if ((x[0] & 1) == 0) + { + Nat.ShiftDownBit(8, x, 0, z); + } + else + { + uint c = Nat256.Add(x, P, z); + Nat.ShiftDownBit(8, z, c); + } + } + + public static void Multiply(uint[] x, uint[] y, uint[] z) + { + uint[] tt = Nat256.CreateExt(); + Nat256.Mul(x, y, tt); + Reduce(tt, z); + } + + public static void MultiplyAddToExt(uint[] x, uint[] y, uint[] zz) + { + uint c = Nat256.MulAddTo(x, y, zz); + if (c != 0 || (zz[15] >= PExt15 && Nat.Gte(16, zz, PExt))) + { + Nat.SubFrom(16, PExt, zz); + } + } + + public static void Negate(uint[] x, uint[] z) + { + if (Nat256.IsZero(x)) + { + Nat256.Zero(z); + } + else + { + Nat256.Sub(P, x, z); + } + } + + public static void Reduce(uint[] xx, uint[] z) + { + long xx08 = xx[8], xx09 = xx[9], xx10 = xx[10], xx11 = xx[11]; + long xx12 = xx[12], xx13 = xx[13], xx14 = xx[14], xx15 = xx[15]; + + long t0 = xx08 + xx09; + long t1 = xx10 + xx11; + long t2 = xx12 + xx15; + long t3 = xx13 + xx14; + long t4 = t3 + (xx15 << 1); + + long ts = t0 + t3; + long tt = t1 + t2 + ts; + + long cc = 0; + cc += (long)xx[0] + tt + xx13 + xx14 + xx15; + z[0] = (uint)cc; + cc >>= 32; + cc += (long)xx[1] + tt - xx08 + xx14 + xx15; + z[1] = (uint)cc; + cc >>= 32; + cc += (long)xx[2] - ts; + z[2] = (uint)cc; + cc >>= 32; + cc += (long)xx[3] + tt - xx09 - xx10 + xx13; + z[3] = (uint)cc; + cc >>= 32; + cc += (long)xx[4] + tt - t1 - xx08 + xx14; + z[4] = (uint)cc; + cc >>= 32; + cc += (long)xx[5] + t4 + xx10; + z[5] = (uint)cc; + cc >>= 32; + cc += (long)xx[6] + xx11 + xx14 + xx15; + z[6] = (uint)cc; + cc >>= 32; + cc += (long)xx[7] + tt + t4 + xx12; + z[7] = (uint)cc; + cc >>= 32; + + Debug.Assert(cc >= 0); + + Reduce32((uint)cc, z); + } + + public static void Reduce32(uint x, uint[] z) + { + long cc = 0; + + if (x != 0) + { + long xx08 = x; + + cc += (long)z[0] + xx08; + z[0] = (uint)cc; + cc >>= 32; + if (cc != 0) + { + cc += (long)z[1]; + z[1] = (uint)cc; + cc >>= 32; + } + cc += (long)z[2] - xx08; + z[2] = (uint)cc; + cc >>= 32; + cc += (long)z[3] + xx08; + z[3] = (uint)cc; + cc >>= 32; + if (cc != 0) + { + cc += (long)z[4]; + z[4] = (uint)cc; + cc >>= 32; + cc += (long)z[5]; + z[5] = (uint)cc; + cc >>= 32; + cc += (long)z[6]; + z[6] = (uint)cc; + cc >>= 32; + } + cc += (long)z[7] + xx08; + z[7] = (uint)cc; + cc >>= 32; + + Debug.Assert(cc == 0 || cc == 1); + } + + if (cc != 0 || (z[7] >= P7 && Nat256.Gte(z, P))) + { + AddPInvTo(z); + } + } + + public static void Square(uint[] x, uint[] z) + { + uint[] tt = Nat256.CreateExt(); + Nat256.Square(x, tt); + Reduce(tt, z); + } + + public static void SquareN(uint[] x, int n, uint[] z) + { + Debug.Assert(n > 0); + + uint[] tt = Nat256.CreateExt(); + Nat256.Square(x, tt); + Reduce(tt, z); + + while (--n > 0) + { + Nat256.Square(z, tt); + Reduce(tt, z); + } + } + + public static void Subtract(uint[] x, uint[] y, uint[] z) + { + int c = Nat256.Sub(x, y, z); + if (c != 0) + { + SubPInvFrom(z); + } + } + + public static void SubtractExt(uint[] xx, uint[] yy, uint[] zz) + { + int c = Nat.Sub(16, xx, yy, zz); + if (c != 0) + { + Nat.AddTo(16, PExt, zz); + } + } + + public static void Twice(uint[] x, uint[] z) + { + uint c = Nat.ShiftUpBit(8, x, 0, z); + if (c != 0 || (z[7] >= P7 && Nat256.Gte(z, P))) + { + AddPInvTo(z); + } + } + + private static void AddPInvTo(uint[] z) + { + long c = (long)z[0] + 1; + z[0] = (uint)c; + c >>= 32; + if (c != 0) + { + c += (long)z[1]; + z[1] = (uint)c; + c >>= 32; + } + c += (long)z[2] - 1; + z[2] = (uint)c; + c >>= 32; + c += (long)z[3] + 1; + z[3] = (uint)c; + c >>= 32; + if (c != 0) + { + c += (long)z[4]; + z[4] = (uint)c; + c >>= 32; + c += (long)z[5]; + z[5] = (uint)c; + c >>= 32; + c += (long)z[6]; + z[6] = (uint)c; + c >>= 32; + } + c += (long)z[7] + 1; + z[7] = (uint)c; + //c >>= 32; + } + + private static void SubPInvFrom(uint[] z) + { + long c = (long)z[0] - 1; + z[0] = (uint)c; + c >>= 32; + if (c != 0) + { + c += (long)z[1]; + z[1] = (uint)c; + c >>= 32; + } + c += (long)z[2] + 1; + z[2] = (uint)c; + c >>= 32; + c += (long)z[3] - 1; + z[3] = (uint)c; + c >>= 32; + if (c != 0) + { + c += (long)z[4]; + z[4] = (uint)c; + c >>= 32; + c += (long)z[5]; + z[5] = (uint)c; + c >>= 32; + c += (long)z[6]; + z[6] = (uint)c; + c >>= 32; + } + c += (long)z[7] - 1; + z[7] = (uint)c; + //c >>= 32; + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/gm/SM2P256V1FieldElement.cs b/bc-sharp-crypto/src/math/ec/custom/gm/SM2P256V1FieldElement.cs new file mode 100644 index 0000000..4f6428f --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/gm/SM2P256V1FieldElement.cs @@ -0,0 +1,211 @@ +using System; + +using Org.BouncyCastle.Math.Raw; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Math.EC.Custom.GM +{ + internal class SM2P256V1FieldElement + : ECFieldElement + { + public static readonly BigInteger Q = SM2P256V1Curve.q; + + protected internal readonly uint[] x; + + public SM2P256V1FieldElement(BigInteger x) + { + if (x == null || x.SignValue < 0 || x.CompareTo(Q) >= 0) + throw new ArgumentException("value invalid for SM2P256V1FieldElement", "x"); + + this.x = SM2P256V1Field.FromBigInteger(x); + } + + public SM2P256V1FieldElement() + { + this.x = Nat256.Create(); + } + + protected internal SM2P256V1FieldElement(uint[] x) + { + this.x = x; + } + + public override bool IsZero + { + get { return Nat256.IsZero(x); } + } + + public override bool IsOne + { + get { return Nat256.IsOne(x); } + } + + public override bool TestBitZero() + { + return Nat256.GetBit(x, 0) == 1; + } + + public override BigInteger ToBigInteger() + { + return Nat256.ToBigInteger(x); + } + + public override string FieldName + { + get { return "SM2P256V1Field"; } + } + + public override int FieldSize + { + get { return Q.BitLength; } + } + + public override ECFieldElement Add(ECFieldElement b) + { + uint[] z = Nat256.Create(); + SM2P256V1Field.Add(x, ((SM2P256V1FieldElement)b).x, z); + return new SM2P256V1FieldElement(z); + } + + public override ECFieldElement AddOne() + { + uint[] z = Nat256.Create(); + SM2P256V1Field.AddOne(x, z); + return new SM2P256V1FieldElement(z); + } + + public override ECFieldElement Subtract(ECFieldElement b) + { + uint[] z = Nat256.Create(); + SM2P256V1Field.Subtract(x, ((SM2P256V1FieldElement)b).x, z); + return new SM2P256V1FieldElement(z); + } + + public override ECFieldElement Multiply(ECFieldElement b) + { + uint[] z = Nat256.Create(); + SM2P256V1Field.Multiply(x, ((SM2P256V1FieldElement)b).x, z); + return new SM2P256V1FieldElement(z); + } + + public override ECFieldElement Divide(ECFieldElement b) + { + //return Multiply(b.Invert()); + uint[] z = Nat256.Create(); + Mod.Invert(SM2P256V1Field.P, ((SM2P256V1FieldElement)b).x, z); + SM2P256V1Field.Multiply(z, x, z); + return new SM2P256V1FieldElement(z); + } + + public override ECFieldElement Negate() + { + uint[] z = Nat256.Create(); + SM2P256V1Field.Negate(x, z); + return new SM2P256V1FieldElement(z); + } + + public override ECFieldElement Square() + { + uint[] z = Nat256.Create(); + SM2P256V1Field.Square(x, z); + return new SM2P256V1FieldElement(z); + } + + public override ECFieldElement Invert() + { + //return new SM2P256V1FieldElement(ToBigInteger().ModInverse(Q)); + uint[] z = Nat256.Create(); + Mod.Invert(SM2P256V1Field.P, x, z); + return new SM2P256V1FieldElement(z); + } + + /** + * return a sqrt root - the routine verifies that the calculation returns the right value - if + * none exists it returns null. + */ + public override ECFieldElement Sqrt() + { + /* + * Raise this element to the exponent 2^254 - 2^222 - 2^94 + 2^62 + * + * Breaking up the exponent's binary representation into "repunits", we get: + * { 31 1s } { 1 0s } { 128 1s } { 31 0s } { 1 1s } { 62 0s} + * + * We use an addition chain for the beginning: [1], 2, 3, 6, 12, [24], 30, [31] + */ + + uint[] x1 = this.x; + if (Nat256.IsZero(x1) || Nat256.IsOne(x1)) + { + return this; + } + + uint[] x2 = Nat256.Create(); + SM2P256V1Field.Square(x1, x2); + SM2P256V1Field.Multiply(x2, x1, x2); + uint[] x4 = Nat256.Create(); + SM2P256V1Field.SquareN(x2, 2, x4); + SM2P256V1Field.Multiply(x4, x2, x4); + uint[] x6 = Nat256.Create(); + SM2P256V1Field.SquareN(x4, 2, x6); + SM2P256V1Field.Multiply(x6, x2, x6); + uint[] x12 = x2; + SM2P256V1Field.SquareN(x6, 6, x12); + SM2P256V1Field.Multiply(x12, x6, x12); + uint[] x24 = Nat256.Create(); + SM2P256V1Field.SquareN(x12, 12, x24); + SM2P256V1Field.Multiply(x24, x12, x24); + uint[] x30 = x12; + SM2P256V1Field.SquareN(x24, 6, x30); + SM2P256V1Field.Multiply(x30, x6, x30); + uint[] x31 = x6; + SM2P256V1Field.Square(x30, x31); + SM2P256V1Field.Multiply(x31, x1, x31); + + uint[] t1 = x24; + SM2P256V1Field.SquareN(x31, 31, t1); + + uint[] x62 = x30; + SM2P256V1Field.Multiply(t1, x31, x62); + + SM2P256V1Field.SquareN(t1, 32, t1); + SM2P256V1Field.Multiply(t1, x62, t1); + SM2P256V1Field.SquareN(t1, 62, t1); + SM2P256V1Field.Multiply(t1, x62, t1); + SM2P256V1Field.SquareN(t1, 4, t1); + SM2P256V1Field.Multiply(t1, x4, t1); + SM2P256V1Field.SquareN(t1, 32, t1); + SM2P256V1Field.Multiply(t1, x1, t1); + SM2P256V1Field.SquareN(t1, 62, t1); + + uint[] t2 = x4; + SM2P256V1Field.Square(t1, t2); + + return Nat256.Eq(x1, t2) ? new SM2P256V1FieldElement(t1) : null; + } + + public override bool Equals(object obj) + { + return Equals(obj as SM2P256V1FieldElement); + } + + public override bool Equals(ECFieldElement other) + { + return Equals(other as SM2P256V1FieldElement); + } + + public virtual bool Equals(SM2P256V1FieldElement other) + { + if (this == other) + return true; + if (null == other) + return false; + return Nat256.Eq(x, other.x); + } + + public override int GetHashCode() + { + return Q.GetHashCode() ^ Arrays.GetHashCode(x, 0, 8); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/gm/SM2P256V1Point.cs b/bc-sharp-crypto/src/math/ec/custom/gm/SM2P256V1Point.cs new file mode 100644 index 0000000..916c906 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/gm/SM2P256V1Point.cs @@ -0,0 +1,279 @@ +using System; + +using Org.BouncyCastle.Math.Raw; + +namespace Org.BouncyCastle.Math.EC.Custom.GM +{ + internal class SM2P256V1Point + : AbstractFpPoint + { + /** + * 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 + * + * @deprecated Use ECCurve.createPoint to construct points + */ + public SM2P256V1Point(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 + * + * @deprecated per-point compression property will be removed, refer + * {@link #getEncoded(bool)} + */ + public SM2P256V1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, bool withCompression) + : base(curve, x, y, withCompression) + { + if ((x == null) != (y == null)) + throw new ArgumentException("Exactly one of the field elements is null"); + } + + internal SM2P256V1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + : base(curve, x, y, zs, withCompression) + { + } + + protected override ECPoint Detach() + { + return new SM2P256V1Point(null, AffineXCoord, AffineYCoord); + } + + public override ECPoint Add(ECPoint b) + { + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return this; + if (this == b) + return Twice(); + + ECCurve curve = this.Curve; + + SM2P256V1FieldElement X1 = (SM2P256V1FieldElement)this.RawXCoord, Y1 = (SM2P256V1FieldElement)this.RawYCoord; + SM2P256V1FieldElement X2 = (SM2P256V1FieldElement)b.RawXCoord, Y2 = (SM2P256V1FieldElement)b.RawYCoord; + + SM2P256V1FieldElement Z1 = (SM2P256V1FieldElement)this.RawZCoords[0]; + SM2P256V1FieldElement Z2 = (SM2P256V1FieldElement)b.RawZCoords[0]; + + uint c; + uint[] tt1 = Nat256.CreateExt(); + uint[] t2 = Nat256.Create(); + uint[] t3 = Nat256.Create(); + uint[] t4 = Nat256.Create(); + + bool Z1IsOne = Z1.IsOne; + uint[] U2, S2; + if (Z1IsOne) + { + U2 = X2.x; + S2 = Y2.x; + } + else + { + S2 = t3; + SM2P256V1Field.Square(Z1.x, S2); + + U2 = t2; + SM2P256V1Field.Multiply(S2, X2.x, U2); + + SM2P256V1Field.Multiply(S2, Z1.x, S2); + SM2P256V1Field.Multiply(S2, Y2.x, S2); + } + + bool Z2IsOne = Z2.IsOne; + uint[] U1, S1; + if (Z2IsOne) + { + U1 = X1.x; + S1 = Y1.x; + } + else + { + S1 = t4; + SM2P256V1Field.Square(Z2.x, S1); + + U1 = tt1; + SM2P256V1Field.Multiply(S1, X1.x, U1); + + SM2P256V1Field.Multiply(S1, Z2.x, S1); + SM2P256V1Field.Multiply(S1, Y1.x, S1); + } + + uint[] H = Nat256.Create(); + SM2P256V1Field.Subtract(U1, U2, H); + + uint[] R = t2; + SM2P256V1Field.Subtract(S1, S2, R); + + // Check if b == this or b == -this + if (Nat256.IsZero(H)) + { + if (Nat256.IsZero(R)) + { + // this == b, i.e. this must be doubled + return this.Twice(); + } + + // this == -b, i.e. the result is the point at infinity + return curve.Infinity; + } + + uint[] HSquared = t3; + SM2P256V1Field.Square(H, HSquared); + + uint[] G = Nat256.Create(); + SM2P256V1Field.Multiply(HSquared, H, G); + + uint[] V = t3; + SM2P256V1Field.Multiply(HSquared, U1, V); + + SM2P256V1Field.Negate(G, G); + Nat256.Mul(S1, G, tt1); + + c = Nat256.AddBothTo(V, V, G); + SM2P256V1Field.Reduce32(c, G); + + SM2P256V1FieldElement X3 = new SM2P256V1FieldElement(t4); + SM2P256V1Field.Square(R, X3.x); + SM2P256V1Field.Subtract(X3.x, G, X3.x); + + SM2P256V1FieldElement Y3 = new SM2P256V1FieldElement(G); + SM2P256V1Field.Subtract(V, X3.x, Y3.x); + SM2P256V1Field.MultiplyAddToExt(Y3.x, R, tt1); + SM2P256V1Field.Reduce(tt1, Y3.x); + + SM2P256V1FieldElement Z3 = new SM2P256V1FieldElement(H); + if (!Z1IsOne) + { + SM2P256V1Field.Multiply(Z3.x, Z1.x, Z3.x); + } + if (!Z2IsOne) + { + SM2P256V1Field.Multiply(Z3.x, Z2.x, Z3.x); + } + + ECFieldElement[] zs = new ECFieldElement[]{ Z3 }; + + return new SM2P256V1Point(curve, X3, Y3, zs, IsCompressed); + } + + public override ECPoint Twice() + { + if (this.IsInfinity) + return this; + + ECCurve curve = this.Curve; + + SM2P256V1FieldElement Y1 = (SM2P256V1FieldElement)this.RawYCoord; + if (Y1.IsZero) + return curve.Infinity; + + SM2P256V1FieldElement X1 = (SM2P256V1FieldElement)this.RawXCoord, Z1 = (SM2P256V1FieldElement)this.RawZCoords[0]; + + uint c; + uint[] t1 = Nat256.Create(); + uint[] t2 = Nat256.Create(); + + uint[] Y1Squared = Nat256.Create(); + SM2P256V1Field.Square(Y1.x, Y1Squared); + + uint[] T = Nat256.Create(); + SM2P256V1Field.Square(Y1Squared, T); + + bool Z1IsOne = Z1.IsOne; + + uint[] Z1Squared = Z1.x; + if (!Z1IsOne) + { + Z1Squared = t2; + SM2P256V1Field.Square(Z1.x, Z1Squared); + } + + SM2P256V1Field.Subtract(X1.x, Z1Squared, t1); + + uint[] M = t2; + SM2P256V1Field.Add(X1.x, Z1Squared, M); + SM2P256V1Field.Multiply(M, t1, M); + c = Nat256.AddBothTo(M, M, M); + SM2P256V1Field.Reduce32(c, M); + + uint[] S = Y1Squared; + SM2P256V1Field.Multiply(Y1Squared, X1.x, S); + c = Nat.ShiftUpBits(8, S, 2, 0); + SM2P256V1Field.Reduce32(c, S); + + c = Nat.ShiftUpBits(8, T, 3, 0, t1); + SM2P256V1Field.Reduce32(c, t1); + + SM2P256V1FieldElement X3 = new SM2P256V1FieldElement(T); + SM2P256V1Field.Square(M, X3.x); + SM2P256V1Field.Subtract(X3.x, S, X3.x); + SM2P256V1Field.Subtract(X3.x, S, X3.x); + + SM2P256V1FieldElement Y3 = new SM2P256V1FieldElement(S); + SM2P256V1Field.Subtract(S, X3.x, Y3.x); + SM2P256V1Field.Multiply(Y3.x, M, Y3.x); + SM2P256V1Field.Subtract(Y3.x, t1, Y3.x); + + SM2P256V1FieldElement Z3 = new SM2P256V1FieldElement(M); + SM2P256V1Field.Twice(Y1.x, Z3.x); + if (!Z1IsOne) + { + SM2P256V1Field.Multiply(Z3.x, Z1.x, Z3.x); + } + + return new SM2P256V1Point(curve, X3, Y3, new ECFieldElement[]{ Z3 }, IsCompressed); + } + + public override ECPoint TwicePlus(ECPoint b) + { + if (this == b) + return ThreeTimes(); + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return Twice(); + + ECFieldElement Y1 = this.RawYCoord; + if (Y1.IsZero) + return b; + + return Twice().Add(b); + } + + public override ECPoint ThreeTimes() + { + if (this.IsInfinity || this.RawYCoord.IsZero) + return this; + + // NOTE: Be careful about recursions between TwicePlus and ThreeTimes + return Twice().Add(this); + } + + public override ECPoint Negate() + { + if (IsInfinity) + return this; + + return new SM2P256V1Point(Curve, RawXCoord, RawYCoord.Negate(), RawZCoords, IsCompressed); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecP128R1Curve.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecP128R1Curve.cs new file mode 100644 index 0000000..9da27b4 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecP128R1Curve.cs @@ -0,0 +1,78 @@ +using System; + +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecP128R1Curve + : AbstractFpCurve + { + public static readonly BigInteger q = new BigInteger(1, + Hex.Decode("FFFFFFFDFFFFFFFFFFFFFFFFFFFFFFFF")); + + private const int SecP128R1_DEFAULT_COORDS = COORD_JACOBIAN; + + protected readonly SecP128R1Point m_infinity; + + public SecP128R1Curve() + : base(q) + { + this.m_infinity = new SecP128R1Point(this, null, null); + + this.m_a = FromBigInteger(new BigInteger(1, + Hex.Decode("FFFFFFFDFFFFFFFFFFFFFFFFFFFFFFFC"))); + this.m_b = FromBigInteger(new BigInteger(1, + Hex.Decode("E87579C11079F43DD824993C2CEE5ED3"))); + this.m_order = new BigInteger(1, Hex.Decode("FFFFFFFE0000000075A30D1B9038A115")); + this.m_cofactor = BigInteger.One; + + this.m_coord = SecP128R1_DEFAULT_COORDS; + } + + protected override ECCurve CloneCurve() + { + return new SecP128R1Curve(); + } + + public override bool SupportsCoordinateSystem(int coord) + { + switch (coord) + { + case COORD_JACOBIAN: + return true; + default: + return false; + } + } + + public virtual BigInteger Q + { + get { return q; } + } + + public override ECPoint Infinity + { + get { return m_infinity; } + } + + public override int FieldSize + { + get { return q.BitLength; } + } + + public override ECFieldElement FromBigInteger(BigInteger x) + { + return new SecP128R1FieldElement(x); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, bool withCompression) + { + return new SecP128R1Point(this, x, y, withCompression); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + { + return new SecP128R1Point(this, x, y, zs, withCompression); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecP128R1Field.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecP128R1Field.cs new file mode 100644 index 0000000..d1ac009 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecP128R1Field.cs @@ -0,0 +1,218 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Math.Raw; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecP128R1Field + { + // 2^128 - 2^97 - 1 + internal static readonly uint[] P = new uint[] { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFD }; + internal static readonly uint[] PExt = new uint[] { 0x00000001, 0x00000000, 0x00000000, 0x00000004, 0xFFFFFFFE, + 0xFFFFFFFF, 0x00000003, 0xFFFFFFFC }; + private static readonly uint[] PExtInv = new uint[]{ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFB, + 0x00000001, 0x00000000, 0xFFFFFFFC, 0x00000003 }; + private const uint P3 = 0xFFFFFFFD; + private const uint PExt7 = 0xFFFFFFFC; + + public static void Add(uint[] x, uint[] y, uint[] z) + { + uint c = Nat128.Add(x, y, z); + if (c != 0 || (z[3] >= P3 && Nat128.Gte(z, P))) + { + AddPInvTo(z); + } + } + + public static void AddExt(uint[] xx, uint[] yy, uint[] zz) + { + uint c = Nat256.Add(xx, yy, zz); + if (c != 0 || (zz[7] >= PExt7 && Nat256.Gte(zz, PExt))) + { + Nat.AddTo(PExtInv.Length, PExtInv, zz); + } + } + + public static void AddOne(uint[] x, uint[] z) + { + uint c = Nat.Inc(4, x, z); + if (c != 0 || (z[3] >= P3 && Nat128.Gte(z, P))) + { + AddPInvTo(z); + } + } + + public static uint[] FromBigInteger(BigInteger x) + { + uint[] z = Nat128.FromBigInteger(x); + if (z[3] >= P3 && Nat128.Gte(z, P)) + { + Nat128.SubFrom(P, z); + } + return z; + } + + public static void Half(uint[] x, uint[] z) + { + if ((x[0] & 1) == 0) + { + Nat.ShiftDownBit(4, x, 0, z); + } + else + { + uint c = Nat128.Add(x, P, z); + Nat.ShiftDownBit(4, z, c); + } + } + + public static void Multiply(uint[] x, uint[] y, uint[] z) + { + uint[] tt = Nat128.CreateExt(); + Nat128.Mul(x, y, tt); + Reduce(tt, z); + } + + public static void MultiplyAddToExt(uint[] x, uint[] y, uint[] zz) + { + uint c = Nat128.MulAddTo(x, y, zz); + if (c != 0 || (zz[7] >= PExt7 && Nat256.Gte(zz, PExt))) + { + Nat.AddTo(PExtInv.Length, PExtInv, zz); + } + } + + public static void Negate(uint[] x, uint[] z) + { + if (Nat128.IsZero(x)) + { + Nat128.Zero(z); + } + else + { + Nat128.Sub(P, x, z); + } + } + + public static void Reduce(uint[] xx, uint[] z) + { + ulong x0 = xx[0], x1 = xx[1], x2 = xx[2], x3 = xx[3]; + ulong x4 = xx[4], x5 = xx[5], x6 = xx[6], x7 = xx[7]; + + x3 += x7; x6 += (x7 << 1); + x2 += x6; x5 += (x6 << 1); + x1 += x5; x4 += (x5 << 1); + x0 += x4; x3 += (x4 << 1); + + z[0] = (uint)x0; x1 += (x0 >> 32); + z[1] = (uint)x1; x2 += (x1 >> 32); + z[2] = (uint)x2; x3 += (x2 >> 32); + z[3] = (uint)x3; + + Reduce32((uint)(x3 >> 32), z); + } + + public static void Reduce32(uint x, uint[] z) + { + while (x != 0) + { + ulong c, x4 = x; + + c = (ulong)z[0] + x4; + z[0] = (uint)c; c >>= 32; + if (c != 0) + { + c += (ulong)z[1]; + z[1] = (uint)c; c >>= 32; + c += (ulong)z[2]; + z[2] = (uint)c; c >>= 32; + } + c += (ulong)z[3] + (x4 << 1); + z[3] = (uint)c; c >>= 32; + + Debug.Assert(c >= 0 && c <= 2); + + x = (uint)c; + } + } + + public static void Square(uint[] x, uint[] z) + { + uint[] tt = Nat128.CreateExt(); + Nat128.Square(x, tt); + Reduce(tt, z); + } + + public static void SquareN(uint[] x, int n, uint[] z) + { + Debug.Assert(n > 0); + + uint[] tt = Nat128.CreateExt(); + Nat128.Square(x, tt); + Reduce(tt, z); + + while (--n > 0) + { + Nat128.Square(z, tt); + Reduce(tt, z); + } + } + + public static void Subtract(uint[] x, uint[] y, uint[] z) + { + int c = Nat128.Sub(x, y, z); + if (c != 0) + { + SubPInvFrom(z); + } + } + + public static void SubtractExt(uint[] xx, uint[] yy, uint[] zz) + { + int c = Nat.Sub(10, xx, yy, zz); + if (c != 0) + { + Nat.SubFrom(PExtInv.Length, PExtInv, zz); + } + } + + public static void Twice(uint[] x, uint[] z) + { + uint c = Nat.ShiftUpBit(4, x, 0, z); + if (c != 0 || (z[3] >= P3 && Nat128.Gte(z, P))) + { + AddPInvTo(z); + } + } + + private static void AddPInvTo(uint[] z) + { + long c = (long)z[0] + 1; + z[0] = (uint)c; c >>= 32; + if (c != 0) + { + c += (long)z[1]; + z[1] = (uint)c; c >>= 32; + c += (long)z[2]; + z[2] = (uint)c; c >>= 32; + } + c += (long)z[3] + 2; + z[3] = (uint)c; + } + + private static void SubPInvFrom(uint[] z) + { + long c = (long)z[0] - 1; + z[0] = (uint)c; c >>= 32; + if (c != 0) + { + c += (long)z[1]; + z[1] = (uint)c; c >>= 32; + c += (long)z[2]; + z[2] = (uint)c; c >>= 32; + } + c += (long)z[3] - 2; + z[3] = (uint)c; + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecP128R1FieldElement.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecP128R1FieldElement.cs new file mode 100644 index 0000000..fa7951d --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecP128R1FieldElement.cs @@ -0,0 +1,198 @@ +using System; + +using Org.BouncyCastle.Math.Raw; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecP128R1FieldElement + : ECFieldElement + { + public static readonly BigInteger Q = SecP128R1Curve.q; + + protected internal readonly uint[] x; + + public SecP128R1FieldElement(BigInteger x) + { + if (x == null || x.SignValue < 0 || x.CompareTo(Q) >= 0) + throw new ArgumentException("value invalid for SecP128R1FieldElement", "x"); + + this.x = SecP128R1Field.FromBigInteger(x); + } + + public SecP128R1FieldElement() + { + this.x = Nat128.Create(); + } + + protected internal SecP128R1FieldElement(uint[] x) + { + this.x = x; + } + + public override bool IsZero + { + get { return Nat128.IsZero(x); } + } + + public override bool IsOne + { + get { return Nat128.IsOne(x); } + } + + public override bool TestBitZero() + { + return Nat128.GetBit(x, 0) == 1; + } + + public override BigInteger ToBigInteger() + { + return Nat128.ToBigInteger(x); + } + + public override string FieldName + { + get { return "SecP128R1Field"; } + } + + public override int FieldSize + { + get { return Q.BitLength; } + } + + public override ECFieldElement Add(ECFieldElement b) + { + uint[] z = Nat128.Create(); + SecP128R1Field.Add(x, ((SecP128R1FieldElement)b).x, z); + return new SecP128R1FieldElement(z); + } + + public override ECFieldElement AddOne() + { + uint[] z = Nat128.Create(); + SecP128R1Field.AddOne(x, z); + return new SecP128R1FieldElement(z); + } + + public override ECFieldElement Subtract(ECFieldElement b) + { + uint[] z = Nat128.Create(); + SecP128R1Field.Subtract(x, ((SecP128R1FieldElement)b).x, z); + return new SecP128R1FieldElement(z); + } + + public override ECFieldElement Multiply(ECFieldElement b) + { + uint[] z = Nat128.Create(); + SecP128R1Field.Multiply(x, ((SecP128R1FieldElement)b).x, z); + return new SecP128R1FieldElement(z); + } + + public override ECFieldElement Divide(ECFieldElement b) + { + // return multiply(b.invert()); + uint[] z = Nat128.Create(); + Mod.Invert(SecP128R1Field.P, ((SecP128R1FieldElement)b).x, z); + SecP128R1Field.Multiply(z, x, z); + return new SecP128R1FieldElement(z); + } + + public override ECFieldElement Negate() + { + uint[] z = Nat128.Create(); + SecP128R1Field.Negate(x, z); + return new SecP128R1FieldElement(z); + } + + public override ECFieldElement Square() + { + uint[] z = Nat128.Create(); + SecP128R1Field.Square(x, z); + return new SecP128R1FieldElement(z); + } + + public override ECFieldElement Invert() + { + // return new SecP128R1FieldElement(toBigInteger().modInverse(Q)); + uint[] z = Nat128.Create(); + Mod.Invert(SecP128R1Field.P, x, z); + return new SecP128R1FieldElement(z); + } + + // 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 override ECFieldElement Sqrt() + { + /* + * Raise this element to the exponent 2^126 - 2^95 + * + * Breaking up the exponent's binary representation into "repunits", we get: + * { 31 1s } { 95 0s } + * + * Therefore we need an addition chain containing 31 (the length of the repunit) We use: + * 1, 2, 4, 8, 10, 20, 30, [31] + */ + + uint[] x1 = this.x; + if (Nat128.IsZero(x1) || Nat128.IsOne(x1)) + return this; + + uint[] x2 = Nat128.Create(); + SecP128R1Field.Square(x1, x2); + SecP128R1Field.Multiply(x2, x1, x2); + uint[] x4 = Nat128.Create(); + SecP128R1Field.SquareN(x2, 2, x4); + SecP128R1Field.Multiply(x4, x2, x4); + uint[] x8 = Nat128.Create(); + SecP128R1Field.SquareN(x4, 4, x8); + SecP128R1Field.Multiply(x8, x4, x8); + uint[] x10 = x4; + SecP128R1Field.SquareN(x8, 2, x10); + SecP128R1Field.Multiply(x10, x2, x10); + uint[] x20 = x2; + SecP128R1Field.SquareN(x10, 10, x20); + SecP128R1Field.Multiply(x20, x10, x20); + uint[] x30 = x8; + SecP128R1Field.SquareN(x20, 10, x30); + SecP128R1Field.Multiply(x30, x10, x30); + uint[] x31 = x10; + SecP128R1Field.Square(x30, x31); + SecP128R1Field.Multiply(x31, x1, x31); + + uint[] t1 = x31; + SecP128R1Field.SquareN(t1, 95, t1); + + uint[] t2 = x30; + SecP128R1Field.Square(t1, t2); + + return Nat128.Eq(x1, t2) ? new SecP128R1FieldElement(t1) : null; + } + + public override bool Equals(object obj) + { + return Equals(obj as SecP128R1FieldElement); + } + + public override bool Equals(ECFieldElement other) + { + return Equals(other as SecP128R1FieldElement); + } + + public virtual bool Equals(SecP128R1FieldElement other) + { + if (this == other) + return true; + if (null == other) + return false; + return Nat128.Eq(x, other.x); + } + + public override int GetHashCode() + { + return Q.GetHashCode() ^ Arrays.GetHashCode(x, 0, 4); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecP128R1Point.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecP128R1Point.cs new file mode 100644 index 0000000..ae76d3c --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecP128R1Point.cs @@ -0,0 +1,279 @@ +using System; + +using Org.BouncyCastle.Math.Raw; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecP128R1Point + : AbstractFpPoint + { + /** + * 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 + * + * @deprecated Use ECCurve.createPoint to construct points + */ + public SecP128R1Point(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 + * + * @deprecated per-point compression property will be removed, refer + * {@link #getEncoded(boolean)} + */ + public SecP128R1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, bool withCompression) + : base(curve, x, y, withCompression) + { + if ((x == null) != (y == null)) + throw new ArgumentException("Exactly one of the field elements is null"); + } + + internal SecP128R1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + : base(curve, x, y, zs, withCompression) + { + } + + protected override ECPoint Detach() + { + return new SecP128R1Point(null, AffineXCoord, AffineYCoord); + } + + public override ECPoint Add(ECPoint b) + { + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return this; + if (this == b) + return Twice(); + + ECCurve curve = this.Curve; + + SecP128R1FieldElement X1 = (SecP128R1FieldElement)this.RawXCoord, Y1 = (SecP128R1FieldElement)this.RawYCoord; + SecP128R1FieldElement X2 = (SecP128R1FieldElement)b.RawXCoord, Y2 = (SecP128R1FieldElement)b.RawYCoord; + + SecP128R1FieldElement Z1 = (SecP128R1FieldElement)this.RawZCoords[0]; + SecP128R1FieldElement Z2 = (SecP128R1FieldElement)b.RawZCoords[0]; + + uint c; + uint[] tt1 = Nat128.CreateExt(); + uint[] t2 = Nat128.Create(); + uint[] t3 = Nat128.Create(); + uint[] t4 = Nat128.Create(); + + bool Z1IsOne = Z1.IsOne; + uint[] U2, S2; + if (Z1IsOne) + { + U2 = X2.x; + S2 = Y2.x; + } + else + { + S2 = t3; + SecP128R1Field.Square(Z1.x, S2); + + U2 = t2; + SecP128R1Field.Multiply(S2, X2.x, U2); + + SecP128R1Field.Multiply(S2, Z1.x, S2); + SecP128R1Field.Multiply(S2, Y2.x, S2); + } + + bool Z2IsOne = Z2.IsOne; + uint[] U1, S1; + if (Z2IsOne) + { + U1 = X1.x; + S1 = Y1.x; + } + else + { + S1 = t4; + SecP128R1Field.Square(Z2.x, S1); + + U1 = tt1; + SecP128R1Field.Multiply(S1, X1.x, U1); + + SecP128R1Field.Multiply(S1, Z2.x, S1); + SecP128R1Field.Multiply(S1, Y1.x, S1); + } + + uint[] H = Nat128.Create(); + SecP128R1Field.Subtract(U1, U2, H); + + uint[] R = t2; + SecP128R1Field.Subtract(S1, S2, R); + + // Check if b == this or b == -this + if (Nat128.IsZero(H)) + { + if (Nat128.IsZero(R)) + { + // this == b, i.e. this must be doubled + return this.Twice(); + } + + // this == -b, i.e. the result is the point at infinity + return curve.Infinity; + } + + uint[] HSquared = t3; + SecP128R1Field.Square(H, HSquared); + + uint[] G = Nat128.Create(); + SecP128R1Field.Multiply(HSquared, H, G); + + uint[] V = t3; + SecP128R1Field.Multiply(HSquared, U1, V); + + SecP128R1Field.Negate(G, G); + Nat128.Mul(S1, G, tt1); + + c = Nat128.AddBothTo(V, V, G); + SecP128R1Field.Reduce32(c, G); + + SecP128R1FieldElement X3 = new SecP128R1FieldElement(t4); + SecP128R1Field.Square(R, X3.x); + SecP128R1Field.Subtract(X3.x, G, X3.x); + + SecP128R1FieldElement Y3 = new SecP128R1FieldElement(G); + SecP128R1Field.Subtract(V, X3.x, Y3.x); + SecP128R1Field.MultiplyAddToExt(Y3.x, R, tt1); + SecP128R1Field.Reduce(tt1, Y3.x); + + SecP128R1FieldElement Z3 = new SecP128R1FieldElement(H); + if (!Z1IsOne) + { + SecP128R1Field.Multiply(Z3.x, Z1.x, Z3.x); + } + if (!Z2IsOne) + { + SecP128R1Field.Multiply(Z3.x, Z2.x, Z3.x); + } + + ECFieldElement[] zs = new ECFieldElement[]{ Z3 }; + + return new SecP128R1Point(curve, X3, Y3, zs, IsCompressed); + } + + public override ECPoint Twice() + { + if (this.IsInfinity) + return this; + + ECCurve curve = this.Curve; + + SecP128R1FieldElement Y1 = (SecP128R1FieldElement)this.RawYCoord; + if (Y1.IsZero) + return curve.Infinity; + + SecP128R1FieldElement X1 = (SecP128R1FieldElement)this.RawXCoord, Z1 = (SecP128R1FieldElement)this.RawZCoords[0]; + + uint c; + uint[] t1 = Nat128.Create(); + uint[] t2 = Nat128.Create(); + + uint[] Y1Squared = Nat128.Create(); + SecP128R1Field.Square(Y1.x, Y1Squared); + + uint[] T = Nat128.Create(); + SecP128R1Field.Square(Y1Squared, T); + + bool Z1IsOne = Z1.IsOne; + + uint[] Z1Squared = Z1.x; + if (!Z1IsOne) + { + Z1Squared = t2; + SecP128R1Field.Square(Z1.x, Z1Squared); + } + + SecP128R1Field.Subtract(X1.x, Z1Squared, t1); + + uint[] M = t2; + SecP128R1Field.Add(X1.x, Z1Squared, M); + SecP128R1Field.Multiply(M, t1, M); + c = Nat128.AddBothTo(M, M, M); + SecP128R1Field.Reduce32(c, M); + + uint[] S = Y1Squared; + SecP128R1Field.Multiply(Y1Squared, X1.x, S); + c = Nat.ShiftUpBits(4, S, 2, 0); + SecP128R1Field.Reduce32(c, S); + + c = Nat.ShiftUpBits(4, T, 3, 0, t1); + SecP128R1Field.Reduce32(c, t1); + + SecP128R1FieldElement X3 = new SecP128R1FieldElement(T); + SecP128R1Field.Square(M, X3.x); + SecP128R1Field.Subtract(X3.x, S, X3.x); + SecP128R1Field.Subtract(X3.x, S, X3.x); + + SecP128R1FieldElement Y3 = new SecP128R1FieldElement(S); + SecP128R1Field.Subtract(S, X3.x, Y3.x); + SecP128R1Field.Multiply(Y3.x, M, Y3.x); + SecP128R1Field.Subtract(Y3.x, t1, Y3.x); + + SecP128R1FieldElement Z3 = new SecP128R1FieldElement(M); + SecP128R1Field.Twice(Y1.x, Z3.x); + if (!Z1IsOne) + { + SecP128R1Field.Multiply(Z3.x, Z1.x, Z3.x); + } + + return new SecP128R1Point(curve, X3, Y3, new ECFieldElement[]{ Z3 }, IsCompressed); + } + + public override ECPoint TwicePlus(ECPoint b) + { + if (this == b) + return ThreeTimes(); + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return Twice(); + + ECFieldElement Y1 = this.RawYCoord; + if (Y1.IsZero) + return b; + + return Twice().Add(b); + } + + public override ECPoint ThreeTimes() + { + if (this.IsInfinity || this.RawYCoord.IsZero) + return this; + + // NOTE: Be careful about recursions between twicePlus and threeTimes + return Twice().Add(this); + } + + public override ECPoint Negate() + { + if (IsInfinity) + return this; + + return new SecP128R1Point(Curve, RawXCoord, RawYCoord.Negate(), RawZCoords, IsCompressed); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecP160K1Curve.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecP160K1Curve.cs new file mode 100644 index 0000000..7d45c62 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecP160K1Curve.cs @@ -0,0 +1,74 @@ +using System; + +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecP160K1Curve + : AbstractFpCurve + { + public static readonly BigInteger q = SecP160R2Curve.q; + + private const int SECP160K1_DEFAULT_COORDS = COORD_JACOBIAN; + + protected readonly SecP160K1Point m_infinity; + + public SecP160K1Curve() + : base(q) + { + this.m_infinity = new SecP160K1Point(this, null, null); + + this.m_a = FromBigInteger(BigInteger.Zero); + this.m_b = FromBigInteger(BigInteger.ValueOf(7)); + this.m_order = new BigInteger(1, Hex.Decode("0100000000000000000001B8FA16DFAB9ACA16B6B3")); + this.m_cofactor = BigInteger.One; + this.m_coord = SECP160K1_DEFAULT_COORDS; + } + + protected override ECCurve CloneCurve() + { + return new SecP160K1Curve(); + } + + public override bool SupportsCoordinateSystem(int coord) + { + switch (coord) + { + case COORD_JACOBIAN: + return true; + default: + return false; + } + } + + public virtual BigInteger Q + { + get { return q; } + } + + public override ECPoint Infinity + { + get { return m_infinity; } + } + + public override int FieldSize + { + get { return q.BitLength; } + } + + public override ECFieldElement FromBigInteger(BigInteger x) + { + return new SecP160R2FieldElement(x); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, bool withCompression) + { + return new SecP160K1Point(this, x, y, withCompression); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + { + return new SecP160K1Point(this, x, y, zs, withCompression); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecP160K1Point.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecP160K1Point.cs new file mode 100644 index 0000000..1bcbadb --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecP160K1Point.cs @@ -0,0 +1,269 @@ +using System; + +using Org.BouncyCastle.Math.Raw; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecP160K1Point + : AbstractFpPoint + { + /** + * 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 + * + * @deprecated Use ECCurve.CreatePoint to construct points + */ + public SecP160K1Point(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 + * + * @deprecated per-point compression property will be removed, refer + * {@link #getEncoded(bool)} + */ + public SecP160K1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, bool withCompression) + : base(curve, x, y, withCompression) + { + if ((x == null) != (y == null)) + throw new ArgumentException("Exactly one of the field elements is null"); + } + + internal SecP160K1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, + bool withCompression) + : base(curve, x, y, zs, withCompression) + { + } + + protected override ECPoint Detach() + { + return new SecP160K1Point(null, AffineXCoord, AffineYCoord); + } + + // B.3 pg 62 + public override ECPoint Add(ECPoint b) + { + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return this; + if (this == b) + return Twice(); + + ECCurve curve = this.Curve; + + SecP160R2FieldElement X1 = (SecP160R2FieldElement)this.RawXCoord, Y1 = (SecP160R2FieldElement)this.RawYCoord; + SecP160R2FieldElement X2 = (SecP160R2FieldElement)b.RawXCoord, Y2 = (SecP160R2FieldElement)b.RawYCoord; + + SecP160R2FieldElement Z1 = (SecP160R2FieldElement)this.RawZCoords[0]; + SecP160R2FieldElement Z2 = (SecP160R2FieldElement)b.RawZCoords[0]; + + uint c; + uint[] tt1 = Nat160.CreateExt(); + uint[] t2 = Nat160.Create(); + uint[] t3 = Nat160.Create(); + uint[] t4 = Nat160.Create(); + + bool Z1IsOne = Z1.IsOne; + uint[] U2, S2; + if (Z1IsOne) + { + U2 = X2.x; + S2 = Y2.x; + } + else + { + S2 = t3; + SecP160R2Field.Square(Z1.x, S2); + + U2 = t2; + SecP160R2Field.Multiply(S2, X2.x, U2); + + SecP160R2Field.Multiply(S2, Z1.x, S2); + SecP160R2Field.Multiply(S2, Y2.x, S2); + } + + bool Z2IsOne = Z2.IsOne; + uint[] U1, S1; + if (Z2IsOne) + { + U1 = X1.x; + S1 = Y1.x; + } + else + { + S1 = t4; + SecP160R2Field.Square(Z2.x, S1); + + U1 = tt1; + SecP160R2Field.Multiply(S1, X1.x, U1); + + SecP160R2Field.Multiply(S1, Z2.x, S1); + SecP160R2Field.Multiply(S1, Y1.x, S1); + } + + uint[] H = Nat160.Create(); + SecP160R2Field.Subtract(U1, U2, H); + + uint[] R = t2; + SecP160R2Field.Subtract(S1, S2, R); + + // Check if b == this or b == -this + if (Nat160.IsZero(H)) + { + if (Nat160.IsZero(R)) + { + // this == b, i.e. this must be doubled + return this.Twice(); + } + + // this == -b, i.e. the result is the point at infinity + return curve.Infinity; + } + + uint[] HSquared = t3; + SecP160R2Field.Square(H, HSquared); + + uint[] G = Nat160.Create(); + SecP160R2Field.Multiply(HSquared, H, G); + + uint[] V = t3; + SecP160R2Field.Multiply(HSquared, U1, V); + + SecP160R2Field.Negate(G, G); + Nat160.Mul(S1, G, tt1); + + c = Nat160.AddBothTo(V, V, G); + SecP160R2Field.Reduce32(c, G); + + SecP160R2FieldElement X3 = new SecP160R2FieldElement(t4); + SecP160R2Field.Square(R, X3.x); + SecP160R2Field.Subtract(X3.x, G, X3.x); + + SecP160R2FieldElement Y3 = new SecP160R2FieldElement(G); + SecP160R2Field.Subtract(V, X3.x, Y3.x); + SecP160R2Field.MultiplyAddToExt(Y3.x, R, tt1); + SecP160R2Field.Reduce(tt1, Y3.x); + + SecP160R2FieldElement Z3 = new SecP160R2FieldElement(H); + if (!Z1IsOne) + { + SecP160R2Field.Multiply(Z3.x, Z1.x, Z3.x); + } + if (!Z2IsOne) + { + SecP160R2Field.Multiply(Z3.x, Z2.x, Z3.x); + } + + ECFieldElement[] zs = new ECFieldElement[] { Z3 }; + + return new SecP160K1Point(curve, X3, Y3, zs, IsCompressed); + } + + // B.3 pg 62 + public override ECPoint Twice() + { + if (this.IsInfinity) + return this; + + ECCurve curve = this.Curve; + + SecP160R2FieldElement Y1 = (SecP160R2FieldElement)this.RawYCoord; + if (Y1.IsZero) + return curve.Infinity; + + SecP160R2FieldElement X1 = (SecP160R2FieldElement)this.RawXCoord, Z1 = (SecP160R2FieldElement)this.RawZCoords[0]; + + uint c; + + uint[] Y1Squared = Nat160.Create(); + SecP160R2Field.Square(Y1.x, Y1Squared); + + uint[] T = Nat160.Create(); + SecP160R2Field.Square(Y1Squared, T); + + uint[] M = Nat160.Create(); + SecP160R2Field.Square(X1.x, M); + c = Nat160.AddBothTo(M, M, M); + SecP160R2Field.Reduce32(c, M); + + uint[] S = Y1Squared; + SecP160R2Field.Multiply(Y1Squared, X1.x, S); + c = Nat.ShiftUpBits(5, S, 2, 0); + SecP160R2Field.Reduce32(c, S); + + uint[] t1 = Nat160.Create(); + c = Nat.ShiftUpBits(5, T, 3, 0, t1); + SecP160R2Field.Reduce32(c, t1); + + SecP160R2FieldElement X3 = new SecP160R2FieldElement(T); + SecP160R2Field.Square(M, X3.x); + SecP160R2Field.Subtract(X3.x, S, X3.x); + SecP160R2Field.Subtract(X3.x, S, X3.x); + + SecP160R2FieldElement Y3 = new SecP160R2FieldElement(S); + SecP160R2Field.Subtract(S, X3.x, Y3.x); + SecP160R2Field.Multiply(Y3.x, M, Y3.x); + SecP160R2Field.Subtract(Y3.x, t1, Y3.x); + + SecP160R2FieldElement Z3 = new SecP160R2FieldElement(M); + SecP160R2Field.Twice(Y1.x, Z3.x); + if (!Z1.IsOne) + { + SecP160R2Field.Multiply(Z3.x, Z1.x, Z3.x); + } + + return new SecP160K1Point(curve, X3, Y3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint TwicePlus(ECPoint b) + { + if (this == b) + return ThreeTimes(); + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return Twice(); + + ECFieldElement Y1 = this.RawYCoord; + if (Y1.IsZero) + return b; + + return Twice().Add(b); + } + + public override ECPoint ThreeTimes() + { + if (this.IsInfinity || this.RawYCoord.IsZero) + return this; + + // NOTE: Be careful about recursions between TwicePlus and threeTimes + return Twice().Add(this); + } + + public override ECPoint Negate() + { + if (IsInfinity) + return this; + + return new SecP160K1Point(Curve, this.RawXCoord, this.RawYCoord.Negate(), this.RawZCoords, IsCompressed); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecP160R1Curve.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecP160R1Curve.cs new file mode 100644 index 0000000..87389af --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecP160R1Curve.cs @@ -0,0 +1,78 @@ +using System; + +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecP160R1Curve + : AbstractFpCurve + { + public static readonly BigInteger q = new BigInteger(1, + Hex.Decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7FFFFFFF")); + + private const int SecP160R1_DEFAULT_COORDS = COORD_JACOBIAN; + + protected readonly SecP160R1Point m_infinity; + + public SecP160R1Curve() + : base(q) + { + this.m_infinity = new SecP160R1Point(this, null, null); + + this.m_a = FromBigInteger(new BigInteger(1, + Hex.Decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7FFFFFFC"))); + this.m_b = FromBigInteger(new BigInteger(1, + Hex.Decode("1C97BEFC54BD7A8B65ACF89F81D4D4ADC565FA45"))); + this.m_order = new BigInteger(1, Hex.Decode("0100000000000000000001F4C8F927AED3CA752257")); + this.m_cofactor = BigInteger.One; + + this.m_coord = SecP160R1_DEFAULT_COORDS; + } + + protected override ECCurve CloneCurve() + { + return new SecP160R1Curve(); + } + + public override bool SupportsCoordinateSystem(int coord) + { + switch (coord) + { + case COORD_JACOBIAN: + return true; + default: + return false; + } + } + + public virtual BigInteger Q + { + get { return q; } + } + + public override ECPoint Infinity + { + get { return m_infinity; } + } + + public override int FieldSize + { + get { return q.BitLength; } + } + + public override ECFieldElement FromBigInteger(BigInteger x) + { + return new SecP160R1FieldElement(x); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, bool withCompression) + { + return new SecP160R1Point(this, x, y, withCompression); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + { + return new SecP160R1Point(this, x, y, zs, withCompression); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecP160R1Field.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecP160R1Field.cs new file mode 100644 index 0000000..6a5a2ef --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecP160R1Field.cs @@ -0,0 +1,186 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Math.Raw; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecP160R1Field + { + // 2^160 - 2^31 - 1 + internal static readonly uint[] P = new uint[] { 0x7FFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF}; + internal static readonly uint[] PExt = new uint[] { 0x00000001, 0x40000001, 0x00000000, 0x00000000, 0x00000000, + 0xFFFFFFFE, 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }; + private static readonly uint[] PExtInv = new uint[]{ 0xFFFFFFFF, 0xBFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0x00000001, 0x00000001 }; + private const uint P4 = 0xFFFFFFFF; + private const uint PExt9 = 0xFFFFFFFF; + private const uint PInv = 0x80000001; + + public static void Add(uint[] x, uint[] y, uint[] z) + { + uint c = Nat160.Add(x, y, z); + if (c != 0 || (z[4] == P4 && Nat160.Gte(z, P))) + { + Nat.AddWordTo(5, PInv, z); + } + } + + public static void AddExt(uint[] xx, uint[] yy, uint[] zz) + { + uint c = Nat.Add(10, xx, yy, zz); + if (c != 0 || (zz[9] == PExt9 && Nat.Gte(10, zz, PExt))) + { + if (Nat.AddTo(PExtInv.Length, PExtInv, zz) != 0) + { + Nat.IncAt(10, zz, PExtInv.Length); + } + } + } + + public static void AddOne(uint[] x, uint[] z) + { + uint c = Nat.Inc(5, x, z); + if (c != 0 || (z[4] == P4 && Nat160.Gte(z, P))) + { + Nat.AddWordTo(5, PInv, z); + } + } + + public static uint[] FromBigInteger(BigInteger x) + { + uint[] z = Nat160.FromBigInteger(x); + if (z[4] == P4 && Nat160.Gte(z, P)) + { + Nat160.SubFrom(P, z); + } + return z; + } + + public static void Half(uint[] x, uint[] z) + { + if ((x[0] & 1) == 0) + { + Nat.ShiftDownBit(5, x, 0, z); + } + else + { + uint c = Nat160.Add(x, P, z); + Nat.ShiftDownBit(5, z, c); + } + } + + public static void Multiply(uint[] x, uint[] y, uint[] z) + { + uint[] tt = Nat160.CreateExt(); + Nat160.Mul(x, y, tt); + Reduce(tt, z); + } + + public static void MultiplyAddToExt(uint[] x, uint[] y, uint[] zz) + { + uint c = Nat160.MulAddTo(x, y, zz); + if (c != 0 || (zz[9] == PExt9 && Nat.Gte(10, zz, PExt))) + { + if (Nat.AddTo(PExtInv.Length, PExtInv, zz) != 0) + { + Nat.IncAt(10, zz, PExtInv.Length); + } + } + } + + public static void Negate(uint[] x, uint[] z) + { + if (Nat160.IsZero(x)) + { + Nat160.Zero(z); + } + else + { + Nat160.Sub(P, x, z); + } + } + + public static void Reduce(uint[] xx, uint[] z) + { + ulong x5 = xx[5], x6 = xx[6], x7 = xx[7], x8 = xx[8], x9 = xx[9]; + + ulong c = 0; + c += (ulong)xx[0] + x5 + (x5 << 31); + z[0] = (uint)c; c >>= 32; + c += (ulong)xx[1] + x6 + (x6 << 31); + z[1] = (uint)c; c >>= 32; + c += (ulong)xx[2] + x7 + (x7 << 31); + z[2] = (uint)c; c >>= 32; + c += (ulong)xx[3] + x8 + (x8 << 31); + z[3] = (uint)c; c >>= 32; + c += (ulong)xx[4] + x9 + (x9 << 31); + z[4] = (uint)c; c >>= 32; + + Debug.Assert(c >> 32 == 0); + + Reduce32((uint)c, z); + } + + public static void Reduce32(uint x, uint[] z) + { + if ((x != 0 && Nat160.MulWordsAdd(PInv, x, z, 0) != 0) + || (z[4] == P4 && Nat160.Gte(z, P))) + { + Nat.AddWordTo(5, PInv, z); + } + } + + public static void Square(uint[] x, uint[] z) + { + uint[] tt = Nat160.CreateExt(); + Nat160.Square(x, tt); + Reduce(tt, z); + } + + public static void SquareN(uint[] x, int n, uint[] z) + { + Debug.Assert(n > 0); + + uint[] tt = Nat160.CreateExt(); + Nat160.Square(x, tt); + Reduce(tt, z); + + while (--n > 0) + { + Nat160.Square(z, tt); + Reduce(tt, z); + } + } + + public static void Subtract(uint[] x, uint[] y, uint[] z) + { + int c = Nat160.Sub(x, y, z); + if (c != 0) + { + Nat.SubWordFrom(5, PInv, z); + } + } + + public static void SubtractExt(uint[] xx, uint[] yy, uint[] zz) + { + int c = Nat.Sub(10, xx, yy, zz); + if (c != 0) + { + if (Nat.SubFrom(PExtInv.Length, PExtInv, zz) != 0) + { + Nat.DecAt(10, zz, PExtInv.Length); + } + } + } + + public static void Twice(uint[] x, uint[] z) + { + uint c = Nat.ShiftUpBit(5, x, 0, z); + if (c != 0 || (z[4] == P4 && Nat160.Gte(z, P))) + { + Nat.AddWordTo(5, PInv, z); + } + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecP160R1FieldElement.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecP160R1FieldElement.cs new file mode 100644 index 0000000..d1fc756 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecP160R1FieldElement.cs @@ -0,0 +1,203 @@ +using System; + +using Org.BouncyCastle.Math.Raw; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecP160R1FieldElement + : ECFieldElement + { + public static readonly BigInteger Q = SecP160R1Curve.q; + + protected internal readonly uint[] x; + + public SecP160R1FieldElement(BigInteger x) + { + if (x == null || x.SignValue < 0 || x.CompareTo(Q) >= 0) + throw new ArgumentException("value invalid for SecP160R1FieldElement", "x"); + + this.x = SecP160R1Field.FromBigInteger(x); + } + + public SecP160R1FieldElement() + { + this.x = Nat160.Create(); + } + + protected internal SecP160R1FieldElement(uint[] x) + { + this.x = x; + } + + public override bool IsZero + { + get { return Nat160.IsZero(x); } + } + + public override bool IsOne + { + get { return Nat160.IsOne(x); } + } + + public override bool TestBitZero() + { + return Nat160.GetBit(x, 0) == 1; + } + + public override BigInteger ToBigInteger() + { + return Nat160.ToBigInteger(x); + } + + public override string FieldName + { + get { return "SecP160R1Field"; } + } + + public override int FieldSize + { + get { return Q.BitLength; } + } + + public override ECFieldElement Add(ECFieldElement b) + { + uint[] z = Nat160.Create(); + SecP160R1Field.Add(x, ((SecP160R1FieldElement)b).x, z); + return new SecP160R1FieldElement(z); + } + + public override ECFieldElement AddOne() + { + uint[] z = Nat160.Create(); + SecP160R1Field.AddOne(x, z); + return new SecP160R1FieldElement(z); + } + + public override ECFieldElement Subtract(ECFieldElement b) + { + uint[] z = Nat160.Create(); + SecP160R1Field.Subtract(x, ((SecP160R1FieldElement)b).x, z); + return new SecP160R1FieldElement(z); + } + + public override ECFieldElement Multiply(ECFieldElement b) + { + uint[] z = Nat160.Create(); + SecP160R1Field.Multiply(x, ((SecP160R1FieldElement)b).x, z); + return new SecP160R1FieldElement(z); + } + + public override ECFieldElement Divide(ECFieldElement b) + { + // return multiply(b.invert()); + uint[] z = Nat160.Create(); + Mod.Invert(SecP160R1Field.P, ((SecP160R1FieldElement)b).x, z); + SecP160R1Field.Multiply(z, x, z); + return new SecP160R1FieldElement(z); + } + + public override ECFieldElement Negate() + { + uint[] z = Nat160.Create(); + SecP160R1Field.Negate(x, z); + return new SecP160R1FieldElement(z); + } + + public override ECFieldElement Square() + { + uint[] z = Nat160.Create(); + SecP160R1Field.Square(x, z); + return new SecP160R1FieldElement(z); + } + + public override ECFieldElement Invert() + { + // return new SecP160R1FieldElement(ToBigInteger().modInverse(Q)); + uint[] z = Nat160.Create(); + Mod.Invert(SecP160R1Field.P, x, z); + return new SecP160R1FieldElement(z); + } + + // 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 override ECFieldElement Sqrt() + { + /* + * Raise this element to the exponent 2^158 - 2^29 + * + * Breaking up the exponent's binary representation into "repunits", we get: + * { 129 1s } { 29 0s } + * + * Therefore we need an addition chain containing 129 (the length of the repunit) We use: + * 1, 2, 4, 8, 16, 32, 64, 128, [129] + */ + + uint[] x1 = this.x; + if (Nat160.IsZero(x1) || Nat160.IsOne(x1)) + { + return this; + } + + uint[] x2 = Nat160.Create(); + SecP160R1Field.Square(x1, x2); + SecP160R1Field.Multiply(x2, x1, x2); + uint[] x4 = Nat160.Create(); + SecP160R1Field.SquareN(x2, 2, x4); + SecP160R1Field.Multiply(x4, x2, x4); + uint[] x8 = x2; + SecP160R1Field.SquareN(x4, 4, x8); + SecP160R1Field.Multiply(x8, x4, x8); + uint[] x16 = x4; + SecP160R1Field.SquareN(x8, 8, x16); + SecP160R1Field.Multiply(x16, x8, x16); + uint[] x32 = x8; + SecP160R1Field.SquareN(x16, 16, x32); + SecP160R1Field.Multiply(x32, x16, x32); + uint[] x64 = x16; + SecP160R1Field.SquareN(x32, 32, x64); + SecP160R1Field.Multiply(x64, x32, x64); + uint[] x128 = x32; + SecP160R1Field.SquareN(x64, 64, x128); + SecP160R1Field.Multiply(x128, x64, x128); + uint[] x129 = x64; + SecP160R1Field.Square(x128, x129); + SecP160R1Field.Multiply(x129, x1, x129); + + uint[] t1 = x129; + SecP160R1Field.SquareN(t1, 29, t1); + + uint[] t2 = x128; + SecP160R1Field.Square(t1, t2); + + return Nat160.Eq(x1, t2) ? new SecP160R1FieldElement(t1) : null; + } + + public override bool Equals(object obj) + { + return Equals(obj as SecP160R1FieldElement); + } + + public override bool Equals(ECFieldElement other) + { + return Equals(other as SecP160R1FieldElement); + } + + public virtual bool Equals(SecP160R1FieldElement other) + { + if (this == other) + return true; + if (null == other) + return false; + return Nat160.Eq(x, other.x); + } + + public override int GetHashCode() + { + return Q.GetHashCode() ^ Arrays.GetHashCode(x, 0, 5); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecP160R1Point.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecP160R1Point.cs new file mode 100644 index 0000000..f9f065d --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecP160R1Point.cs @@ -0,0 +1,279 @@ +using System; + +using Org.BouncyCastle.Math.Raw; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecP160R1Point + : AbstractFpPoint + { + /** + * 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 + * + * @deprecated Use ECCurve.CreatePoint to construct points + */ + public SecP160R1Point(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 + * + * @deprecated per-point compression property will be removed, refer + * {@link #getEncoded(bool)} + */ + public SecP160R1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, bool withCompression) + : base(curve, x, y, withCompression) + { + if ((x == null) != (y == null)) + throw new ArgumentException("Exactly one of the field elements is null"); + } + + internal SecP160R1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + : base(curve, x, y, zs, withCompression) + { + } + + protected override ECPoint Detach() + { + return new SecP160R1Point(null, AffineXCoord, AffineYCoord); + } + + public override ECPoint Add(ECPoint b) + { + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return this; + if (this == b) + return Twice(); + + ECCurve curve = this.Curve; + + SecP160R1FieldElement X1 = (SecP160R1FieldElement)this.RawXCoord, Y1 = (SecP160R1FieldElement)this.RawYCoord; + SecP160R1FieldElement X2 = (SecP160R1FieldElement)b.RawXCoord, Y2 = (SecP160R1FieldElement)b.RawYCoord; + + SecP160R1FieldElement Z1 = (SecP160R1FieldElement)this.RawZCoords[0]; + SecP160R1FieldElement Z2 = (SecP160R1FieldElement)b.RawZCoords[0]; + + uint c; + uint[] tt1 = Nat160.CreateExt(); + uint[] t2 = Nat160.Create(); + uint[] t3 = Nat160.Create(); + uint[] t4 = Nat160.Create(); + + bool Z1IsOne = Z1.IsOne; + uint[] U2, S2; + if (Z1IsOne) + { + U2 = X2.x; + S2 = Y2.x; + } + else + { + S2 = t3; + SecP160R1Field.Square(Z1.x, S2); + + U2 = t2; + SecP160R1Field.Multiply(S2, X2.x, U2); + + SecP160R1Field.Multiply(S2, Z1.x, S2); + SecP160R1Field.Multiply(S2, Y2.x, S2); + } + + bool Z2IsOne = Z2.IsOne; + uint[] U1, S1; + if (Z2IsOne) + { + U1 = X1.x; + S1 = Y1.x; + } + else + { + S1 = t4; + SecP160R1Field.Square(Z2.x, S1); + + U1 = tt1; + SecP160R1Field.Multiply(S1, X1.x, U1); + + SecP160R1Field.Multiply(S1, Z2.x, S1); + SecP160R1Field.Multiply(S1, Y1.x, S1); + } + + uint[] H = Nat160.Create(); + SecP160R1Field.Subtract(U1, U2, H); + + uint[] R = t2; + SecP160R1Field.Subtract(S1, S2, R); + + // Check if b == this or b == -this + if (Nat160.IsZero(H)) + { + if (Nat160.IsZero(R)) + { + // this == b, i.e. this must be doubled + return this.Twice(); + } + + // this == -b, i.e. the result is the point at infinity + return curve.Infinity; + } + + uint[] HSquared = t3; + SecP160R1Field.Square(H, HSquared); + + uint[] G = Nat160.Create(); + SecP160R1Field.Multiply(HSquared, H, G); + + uint[] V = t3; + SecP160R1Field.Multiply(HSquared, U1, V); + + SecP160R1Field.Negate(G, G); + Nat160.Mul(S1, G, tt1); + + c = Nat160.AddBothTo(V, V, G); + SecP160R1Field.Reduce32(c, G); + + SecP160R1FieldElement X3 = new SecP160R1FieldElement(t4); + SecP160R1Field.Square(R, X3.x); + SecP160R1Field.Subtract(X3.x, G, X3.x); + + SecP160R1FieldElement Y3 = new SecP160R1FieldElement(G); + SecP160R1Field.Subtract(V, X3.x, Y3.x); + SecP160R1Field.MultiplyAddToExt(Y3.x, R, tt1); + SecP160R1Field.Reduce(tt1, Y3.x); + + SecP160R1FieldElement Z3 = new SecP160R1FieldElement(H); + if (!Z1IsOne) + { + SecP160R1Field.Multiply(Z3.x, Z1.x, Z3.x); + } + if (!Z2IsOne) + { + SecP160R1Field.Multiply(Z3.x, Z2.x, Z3.x); + } + + ECFieldElement[] zs = new ECFieldElement[]{ Z3 }; + + return new SecP160R1Point(curve, X3, Y3, zs, IsCompressed); + } + + public override ECPoint Twice() + { + if (this.IsInfinity) + return this; + + ECCurve curve = this.Curve; + + SecP160R1FieldElement Y1 = (SecP160R1FieldElement)this.RawYCoord; + if (Y1.IsZero) + return curve.Infinity; + + SecP160R1FieldElement X1 = (SecP160R1FieldElement)this.RawXCoord, Z1 = (SecP160R1FieldElement)this.RawZCoords[0]; + + uint c; + uint[] t1 = Nat160.Create(); + uint[] t2 = Nat160.Create(); + + uint[] Y1Squared = Nat160.Create(); + SecP160R1Field.Square(Y1.x, Y1Squared); + + uint[] T = Nat160.Create(); + SecP160R1Field.Square(Y1Squared, T); + + bool Z1IsOne = Z1.IsOne; + + uint[] Z1Squared = Z1.x; + if (!Z1IsOne) + { + Z1Squared = t2; + SecP160R1Field.Square(Z1.x, Z1Squared); + } + + SecP160R1Field.Subtract(X1.x, Z1Squared, t1); + + uint[] M = t2; + SecP160R1Field.Add(X1.x, Z1Squared, M); + SecP160R1Field.Multiply(M, t1, M); + c = Nat160.AddBothTo(M, M, M); + SecP160R1Field.Reduce32(c, M); + + uint[] S = Y1Squared; + SecP160R1Field.Multiply(Y1Squared, X1.x, S); + c = Nat.ShiftUpBits(5, S, 2, 0); + SecP160R1Field.Reduce32(c, S); + + c = Nat.ShiftUpBits(5, T, 3, 0, t1); + SecP160R1Field.Reduce32(c, t1); + + SecP160R1FieldElement X3 = new SecP160R1FieldElement(T); + SecP160R1Field.Square(M, X3.x); + SecP160R1Field.Subtract(X3.x, S, X3.x); + SecP160R1Field.Subtract(X3.x, S, X3.x); + + SecP160R1FieldElement Y3 = new SecP160R1FieldElement(S); + SecP160R1Field.Subtract(S, X3.x, Y3.x); + SecP160R1Field.Multiply(Y3.x, M, Y3.x); + SecP160R1Field.Subtract(Y3.x, t1, Y3.x); + + SecP160R1FieldElement Z3 = new SecP160R1FieldElement(M); + SecP160R1Field.Twice(Y1.x, Z3.x); + if (!Z1IsOne) + { + SecP160R1Field.Multiply(Z3.x, Z1.x, Z3.x); + } + + return new SecP160R1Point(curve, X3, Y3, new ECFieldElement[]{ Z3 }, IsCompressed); + } + + public override ECPoint TwicePlus(ECPoint b) + { + if (this == b) + return ThreeTimes(); + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return Twice(); + + ECFieldElement Y1 = this.RawYCoord; + if (Y1.IsZero) + return b; + + return Twice().Add(b); + } + + public override ECPoint ThreeTimes() + { + if (this.IsInfinity || this.RawYCoord.IsZero) + return this; + + // NOTE: Be careful about recursions between TwicePlus and ThreeTimes + return Twice().Add(this); + } + + public override ECPoint Negate() + { + if (IsInfinity) + return this; + + return new SecP160R1Point(Curve, RawXCoord, RawYCoord.Negate(), RawZCoords, IsCompressed); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecP160R2Curve.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecP160R2Curve.cs new file mode 100644 index 0000000..1005614 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecP160R2Curve.cs @@ -0,0 +1,78 @@ +using System; + +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecP160R2Curve + : AbstractFpCurve + { + public static readonly BigInteger q = new BigInteger(1, + Hex.Decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFAC73")); + + private const int SecP160R2_DEFAULT_COORDS = COORD_JACOBIAN; + + protected readonly SecP160R2Point m_infinity; + + public SecP160R2Curve() + : base(q) + { + this.m_infinity = new SecP160R2Point(this, null, null); + + this.m_a = FromBigInteger(new BigInteger(1, + Hex.Decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFAC70"))); + this.m_b = FromBigInteger(new BigInteger(1, + Hex.Decode("B4E134D3FB59EB8BAB57274904664D5AF50388BA"))); + this.m_order = new BigInteger(1, Hex.Decode("0100000000000000000000351EE786A818F3A1A16B")); + this.m_cofactor = BigInteger.One; + + this.m_coord = SecP160R2_DEFAULT_COORDS; + } + + protected override ECCurve CloneCurve() + { + return new SecP160R2Curve(); + } + + public override bool SupportsCoordinateSystem(int coord) + { + switch (coord) + { + case COORD_JACOBIAN: + return true; + default: + return false; + } + } + + public virtual BigInteger Q + { + get { return q; } + } + + public override ECPoint Infinity + { + get { return m_infinity; } + } + + public override int FieldSize + { + get { return q.BitLength; } + } + + public override ECFieldElement FromBigInteger(BigInteger x) + { + return new SecP160R2FieldElement(x); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, bool withCompression) + { + return new SecP160R2Point(this, x, y, withCompression); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + { + return new SecP160R2Point(this, x, y, zs, withCompression); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecP160R2Field.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecP160R2Field.cs new file mode 100644 index 0000000..1bef32e --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecP160R2Field.cs @@ -0,0 +1,178 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Math.Raw; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecP160R2Field + { + // 2^160 - 2^32 - 2^14 - 2^12 - 2^9 - 2^8 - 2^7 - 2^3 - 2^2 - 1 + internal static readonly uint[] P = new uint[]{ 0xFFFFAC73, 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }; + internal static readonly uint[] PExt = new uint[]{ 0x1B44BBA9, 0x0000A71A, 0x00000001, 0x00000000, 0x00000000, + 0xFFFF58E6, 0xFFFFFFFD, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }; + private static readonly uint[] PExtInv = new uint[]{ 0xE4BB4457, 0xFFFF58E5, 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFF, + 0x0000A719, 0x00000002 }; + private const uint P4 = 0xFFFFFFFF; + private const uint PExt9 = 0xFFFFFFFF; + private const uint PInv33 = 0x538D; + + public static void Add(uint[] x, uint[] y, uint[] z) + { + uint c = Nat160.Add(x, y, z); + if (c != 0 || (z[4] == P4 && Nat160.Gte(z, P))) + { + Nat.Add33To(5, PInv33, z); + } + } + + public static void AddExt(uint[] xx, uint[] yy, uint[] zz) + { + uint c = Nat.Add(10, xx, yy, zz); + if (c != 0 || (zz[9] == PExt9 && Nat.Gte(10, zz, PExt))) + { + if (Nat.AddTo(PExtInv.Length, PExtInv, zz) != 0) + { + Nat.IncAt(10, zz, PExtInv.Length); + } + } + } + + public static void AddOne(uint[] x, uint[] z) + { + uint c = Nat.Inc(5, x, z); + if (c != 0 || (z[4] == P4 && Nat160.Gte(z, P))) + { + Nat.Add33To(5, PInv33, z); + } + } + + public static uint[] FromBigInteger(BigInteger x) + { + uint[] z = Nat160.FromBigInteger(x); + if (z[4] == P4 && Nat160.Gte(z, P)) + { + Nat160.SubFrom(P, z); + } + return z; + } + + public static void Half(uint[] x, uint[] z) + { + if ((x[0] & 1) == 0) + { + Nat.ShiftDownBit(5, x, 0, z); + } + else + { + uint c = Nat160.Add(x, P, z); + Nat.ShiftDownBit(5, z, c); + } + } + + public static void Multiply(uint[] x, uint[] y, uint[] z) + { + uint[] tt = Nat160.CreateExt(); + Nat160.Mul(x, y, tt); + Reduce(tt, z); + } + + public static void MultiplyAddToExt(uint[] x, uint[] y, uint[] zz) + { + uint c = Nat160.MulAddTo(x, y, zz); + if (c != 0 || (zz[9] == PExt9 && Nat.Gte(10, zz, PExt))) + { + if (Nat.AddTo(PExtInv.Length, PExtInv, zz) != 0) + { + Nat.IncAt(10, zz, PExtInv.Length); + } + } + } + + public static void Negate(uint[] x, uint[] z) + { + if (Nat160.IsZero(x)) + { + Nat160.Zero(z); + } + else + { + Nat160.Sub(P, x, z); + } + } + + public static void Reduce(uint[] xx, uint[] z) + { + ulong cc = Nat160.Mul33Add(PInv33, xx, 5, xx, 0, z, 0); + uint c = Nat160.Mul33DWordAdd(PInv33, cc, z, 0); + + Debug.Assert(c == 0 || c == 1); + + if (c != 0 || (z[4] == P4 && Nat160.Gte(z, P))) + { + Nat.Add33To(5, PInv33, z); + } + } + + public static void Reduce32(uint x, uint[] z) + { + if ((x != 0 && Nat160.Mul33WordAdd(PInv33, x, z, 0) != 0) + || (z[4] == P4 && Nat160.Gte(z, P))) + { + Nat.Add33To(5, PInv33, z); + } + } + + public static void Square(uint[] x, uint[] z) + { + uint[] tt = Nat160.CreateExt(); + Nat160.Square(x, tt); + Reduce(tt, z); + } + + public static void SquareN(uint[] x, int n, uint[] z) + { + Debug.Assert(n > 0); + + uint[] tt = Nat160.CreateExt(); + Nat160.Square(x, tt); + Reduce(tt, z); + + while (--n > 0) + { + Nat160.Square(z, tt); + Reduce(tt, z); + } + } + + public static void Subtract(uint[] x, uint[] y, uint[] z) + { + int c = Nat160.Sub(x, y, z); + if (c != 0) + { + Nat.Sub33From(5, PInv33, z); + } + } + + public static void SubtractExt(uint[] xx, uint[] yy, uint[] zz) + { + int c = Nat.Sub(10, xx, yy, zz); + if (c != 0) + { + if (Nat.SubFrom(PExtInv.Length, PExtInv, zz) != 0) + { + Nat.DecAt(10, zz, PExtInv.Length); + } + } + } + + public static void Twice(uint[] x, uint[] z) + { + uint c = Nat.ShiftUpBit(5, x, 0, z); + if (c != 0 || (z[4] == P4 && Nat160.Gte(z, P))) + { + Nat.Add33To(5, PInv33, z); + } + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecP160R2FieldElement.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecP160R2FieldElement.cs new file mode 100644 index 0000000..bdb5245 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecP160R2FieldElement.cs @@ -0,0 +1,218 @@ +using System; + +using Org.BouncyCastle.Math.Raw; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecP160R2FieldElement + : ECFieldElement + { + public static readonly BigInteger Q = SecP160R2Curve.q; + + protected internal readonly uint[] x; + + public SecP160R2FieldElement(BigInteger x) + { + if (x == null || x.SignValue < 0 || x.CompareTo(Q) >= 0) + throw new ArgumentException("value invalid for SecP160R2FieldElement", "x"); + + this.x = SecP160R2Field.FromBigInteger(x); + } + + public SecP160R2FieldElement() + { + this.x = Nat160.Create(); + } + + protected internal SecP160R2FieldElement(uint[] x) + { + this.x = x; + } + + public override bool IsZero + { + get { return Nat160.IsZero(x); } + } + + public override bool IsOne + { + get { return Nat160.IsOne(x); } + } + + public override bool TestBitZero() + { + return Nat160.GetBit(x, 0) == 1; + } + + public override BigInteger ToBigInteger() + { + return Nat160.ToBigInteger(x); + } + + public override string FieldName + { + get { return "SecP160R2Field"; } + } + + public override int FieldSize + { + get { return Q.BitLength; } + } + + public override ECFieldElement Add(ECFieldElement b) + { + uint[] z = Nat160.Create(); + SecP160R2Field.Add(x, ((SecP160R2FieldElement)b).x, z); + return new SecP160R2FieldElement(z); + } + + public override ECFieldElement AddOne() + { + uint[] z = Nat160.Create(); + SecP160R2Field.AddOne(x, z); + return new SecP160R2FieldElement(z); + } + + public override ECFieldElement Subtract(ECFieldElement b) + { + uint[] z = Nat160.Create(); + SecP160R2Field.Subtract(x, ((SecP160R2FieldElement)b).x, z); + return new SecP160R2FieldElement(z); + } + + public override ECFieldElement Multiply(ECFieldElement b) + { + uint[] z = Nat160.Create(); + SecP160R2Field.Multiply(x, ((SecP160R2FieldElement)b).x, z); + return new SecP160R2FieldElement(z); + } + + public override ECFieldElement Divide(ECFieldElement b) + { + // return Multiply(b.invert()); + uint[] z = Nat160.Create(); + Mod.Invert(SecP160R2Field.P, ((SecP160R2FieldElement)b).x, z); + SecP160R2Field.Multiply(z, x, z); + return new SecP160R2FieldElement(z); + } + + public override ECFieldElement Negate() + { + uint[] z = Nat160.Create(); + SecP160R2Field.Negate(x, z); + return new SecP160R2FieldElement(z); + } + + public override ECFieldElement Square() + { + uint[] z = Nat160.Create(); + SecP160R2Field.Square(x, z); + return new SecP160R2FieldElement(z); + } + + public override ECFieldElement Invert() + { + // return new SecP160R2FieldElement(ToBigInteger().modInverse(Q)); + uint[] z = Nat160.Create(); + Mod.Invert(SecP160R2Field.P, x, z); + return new SecP160R2FieldElement(z); + } + + // 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 override ECFieldElement Sqrt() + { + /* + * Raise this element to the exponent 2^158 - 2^30 - 2^12 - 2^10 - 2^7 - 2^6 - 2^5 - 2^1 - 2^0 + * + * Breaking up the exponent's binary representation into "repunits", we get: { 127 1s } { 1 + * 0s } { 17 1s } { 1 0s } { 1 1s } { 1 0s } { 2 1s } { 3 0s } { 3 1s } { 1 0s } { 1 1s } + * + * Therefore we need an Addition chain containing 1, 2, 3, 17, 127 (the lengths of the repunits) + * We use: [1], [2], [3], 4, 7, 14, [17], 31, 62, 124, [127] + */ + + uint[] x1 = this.x; + if (Nat160.IsZero(x1) || Nat160.IsOne(x1)) + { + return this; + } + + uint[] x2 = Nat160.Create(); + SecP160R2Field.Square(x1, x2); + SecP160R2Field.Multiply(x2, x1, x2); + uint[] x3 = Nat160.Create(); + SecP160R2Field.Square(x2, x3); + SecP160R2Field.Multiply(x3, x1, x3); + uint[] x4 = Nat160.Create(); + SecP160R2Field.Square(x3, x4); + SecP160R2Field.Multiply(x4, x1, x4); + uint[] x7 = Nat160.Create(); + SecP160R2Field.SquareN(x4, 3, x7); + SecP160R2Field.Multiply(x7, x3, x7); + uint[] x14 = x4; + SecP160R2Field.SquareN(x7, 7, x14); + SecP160R2Field.Multiply(x14, x7, x14); + uint[] x17 = x7; + SecP160R2Field.SquareN(x14, 3, x17); + SecP160R2Field.Multiply(x17, x3, x17); + uint[] x31 = Nat160.Create(); + SecP160R2Field.SquareN(x17, 14, x31); + SecP160R2Field.Multiply(x31, x14, x31); + uint[] x62 = x14; + SecP160R2Field.SquareN(x31, 31, x62); + SecP160R2Field.Multiply(x62, x31, x62); + uint[] x124 = x31; + SecP160R2Field.SquareN(x62, 62, x124); + SecP160R2Field.Multiply(x124, x62, x124); + uint[] x127 = x62; + SecP160R2Field.SquareN(x124, 3, x127); + SecP160R2Field.Multiply(x127, x3, x127); + + uint[] t1 = x127; + SecP160R2Field.SquareN(t1, 18, t1); + SecP160R2Field.Multiply(t1, x17, t1); + SecP160R2Field.SquareN(t1, 2, t1); + SecP160R2Field.Multiply(t1, x1, t1); + SecP160R2Field.SquareN(t1, 3, t1); + SecP160R2Field.Multiply(t1, x2, t1); + SecP160R2Field.SquareN(t1, 6, t1); + SecP160R2Field.Multiply(t1, x3, t1); + SecP160R2Field.SquareN(t1, 2, t1); + SecP160R2Field.Multiply(t1, x1, t1); + + uint[] t2 = x2; + SecP160R2Field.Square(t1, t2); + + return Nat160.Eq(x1, t2) ? new SecP160R2FieldElement(t1) : null; + } + + public override bool Equals(object obj) + { + return Equals(obj as SecP160R2FieldElement); + } + + public override bool Equals(ECFieldElement other) + { + return Equals(other as SecP160R2FieldElement); + } + + public virtual bool Equals(SecP160R2FieldElement other) + { + if (this == other) + return true; + if (null == other) + return false; + return Nat160.Eq(x, other.x); + } + + public override int GetHashCode() + { + return Q.GetHashCode() ^ Arrays.GetHashCode(x, 0, 5); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecP160R2Point.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecP160R2Point.cs new file mode 100644 index 0000000..343cf8c --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecP160R2Point.cs @@ -0,0 +1,279 @@ +using System; + +using Org.BouncyCastle.Math.Raw; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecP160R2Point + : AbstractFpPoint + { + /** + * 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 + * + * @deprecated Use ECCurve.CreatePoint to construct points + */ + public SecP160R2Point(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 + * + * @deprecated per-point compression property will be removed, refer + * {@link #getEncoded(bool)} + */ + public SecP160R2Point(ECCurve curve, ECFieldElement x, ECFieldElement y, bool withCompression) + : base(curve, x, y, withCompression) + { + if ((x == null) != (y == null)) + throw new ArgumentException("Exactly one of the field elements is null"); + } + + internal SecP160R2Point(ECCurve curve, ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + : base(curve, x, y, zs, withCompression) + { + } + + protected override ECPoint Detach() + { + return new SecP160R2Point(null, AffineXCoord, AffineYCoord); + } + + public override ECPoint Add(ECPoint b) + { + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return this; + if (this == b) + return Twice(); + + ECCurve curve = this.Curve; + + SecP160R2FieldElement X1 = (SecP160R2FieldElement)this.RawXCoord, Y1 = (SecP160R2FieldElement)this.RawYCoord; + SecP160R2FieldElement X2 = (SecP160R2FieldElement)b.RawXCoord, Y2 = (SecP160R2FieldElement)b.RawYCoord; + + SecP160R2FieldElement Z1 = (SecP160R2FieldElement)this.RawZCoords[0]; + SecP160R2FieldElement Z2 = (SecP160R2FieldElement)b.RawZCoords[0]; + + uint c; + uint[] tt1 = Nat160.CreateExt(); + uint[] t2 = Nat160.Create(); + uint[] t3 = Nat160.Create(); + uint[] t4 = Nat160.Create(); + + bool Z1IsOne = Z1.IsOne; + uint[] U2, S2; + if (Z1IsOne) + { + U2 = X2.x; + S2 = Y2.x; + } + else + { + S2 = t3; + SecP160R2Field.Square(Z1.x, S2); + + U2 = t2; + SecP160R2Field.Multiply(S2, X2.x, U2); + + SecP160R2Field.Multiply(S2, Z1.x, S2); + SecP160R2Field.Multiply(S2, Y2.x, S2); + } + + bool Z2IsOne = Z2.IsOne; + uint[] U1, S1; + if (Z2IsOne) + { + U1 = X1.x; + S1 = Y1.x; + } + else + { + S1 = t4; + SecP160R2Field.Square(Z2.x, S1); + + U1 = tt1; + SecP160R2Field.Multiply(S1, X1.x, U1); + + SecP160R2Field.Multiply(S1, Z2.x, S1); + SecP160R2Field.Multiply(S1, Y1.x, S1); + } + + uint[] H = Nat160.Create(); + SecP160R2Field.Subtract(U1, U2, H); + + uint[] R = t2; + SecP160R2Field.Subtract(S1, S2, R); + + // Check if b == this or b == -this + if (Nat160.IsZero(H)) + { + if (Nat160.IsZero(R)) + { + // this == b, i.e. this must be doubled + return this.Twice(); + } + + // this == -b, i.e. the result is the point at infinity + return curve.Infinity; + } + + uint[] HSquared = t3; + SecP160R2Field.Square(H, HSquared); + + uint[] G = Nat160.Create(); + SecP160R2Field.Multiply(HSquared, H, G); + + uint[] V = t3; + SecP160R2Field.Multiply(HSquared, U1, V); + + SecP160R2Field.Negate(G, G); + Nat160.Mul(S1, G, tt1); + + c = Nat160.AddBothTo(V, V, G); + SecP160R2Field.Reduce32(c, G); + + SecP160R2FieldElement X3 = new SecP160R2FieldElement(t4); + SecP160R2Field.Square(R, X3.x); + SecP160R2Field.Subtract(X3.x, G, X3.x); + + SecP160R2FieldElement Y3 = new SecP160R2FieldElement(G); + SecP160R2Field.Subtract(V, X3.x, Y3.x); + SecP160R2Field.MultiplyAddToExt(Y3.x, R, tt1); + SecP160R2Field.Reduce(tt1, Y3.x); + + SecP160R2FieldElement Z3 = new SecP160R2FieldElement(H); + if (!Z1IsOne) + { + SecP160R2Field.Multiply(Z3.x, Z1.x, Z3.x); + } + if (!Z2IsOne) + { + SecP160R2Field.Multiply(Z3.x, Z2.x, Z3.x); + } + + ECFieldElement[] zs = new ECFieldElement[]{ Z3 }; + + return new SecP160R2Point(curve, X3, Y3, zs, IsCompressed); + } + + public override ECPoint Twice() + { + if (this.IsInfinity) + return this; + + ECCurve curve = this.Curve; + + SecP160R2FieldElement Y1 = (SecP160R2FieldElement)this.RawYCoord; + if (Y1.IsZero) + return curve.Infinity; + + SecP160R2FieldElement X1 = (SecP160R2FieldElement)this.RawXCoord, Z1 = (SecP160R2FieldElement)this.RawZCoords[0]; + + uint c; + uint[] t1 = Nat160.Create(); + uint[] t2 = Nat160.Create(); + + uint[] Y1Squared = Nat160.Create(); + SecP160R2Field.Square(Y1.x, Y1Squared); + + uint[] T = Nat160.Create(); + SecP160R2Field.Square(Y1Squared, T); + + bool Z1IsOne = Z1.IsOne; + + uint[] Z1Squared = Z1.x; + if (!Z1IsOne) + { + Z1Squared = t2; + SecP160R2Field.Square(Z1.x, Z1Squared); + } + + SecP160R2Field.Subtract(X1.x, Z1Squared, t1); + + uint[] M = t2; + SecP160R2Field.Add(X1.x, Z1Squared, M); + SecP160R2Field.Multiply(M, t1, M); + c = Nat160.AddBothTo(M, M, M); + SecP160R2Field.Reduce32(c, M); + + uint[] S = Y1Squared; + SecP160R2Field.Multiply(Y1Squared, X1.x, S); + c = Nat.ShiftUpBits(5, S, 2, 0); + SecP160R2Field.Reduce32(c, S); + + c = Nat.ShiftUpBits(5, T, 3, 0, t1); + SecP160R2Field.Reduce32(c, t1); + + SecP160R2FieldElement X3 = new SecP160R2FieldElement(T); + SecP160R2Field.Square(M, X3.x); + SecP160R2Field.Subtract(X3.x, S, X3.x); + SecP160R2Field.Subtract(X3.x, S, X3.x); + + SecP160R2FieldElement Y3 = new SecP160R2FieldElement(S); + SecP160R2Field.Subtract(S, X3.x, Y3.x); + SecP160R2Field.Multiply(Y3.x, M, Y3.x); + SecP160R2Field.Subtract(Y3.x, t1, Y3.x); + + SecP160R2FieldElement Z3 = new SecP160R2FieldElement(M); + SecP160R2Field.Twice(Y1.x, Z3.x); + if (!Z1IsOne) + { + SecP160R2Field.Multiply(Z3.x, Z1.x, Z3.x); + } + + return new SecP160R2Point(curve, X3, Y3, new ECFieldElement[]{ Z3 }, IsCompressed); + } + + public override ECPoint TwicePlus(ECPoint b) + { + if (this == b) + return ThreeTimes(); + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return Twice(); + + ECFieldElement Y1 = this.RawYCoord; + if (Y1.IsZero) + return b; + + return Twice().Add(b); + } + + public override ECPoint ThreeTimes() + { + if (this.IsInfinity || this.RawYCoord.IsZero) + return this; + + // NOTE: Be careful about recursions between TwicePlus and ThreeTimes + return Twice().Add(this); + } + + public override ECPoint Negate() + { + if (IsInfinity) + return this; + + return new SecP160R2Point(Curve, this.RawXCoord, this.RawYCoord.Negate(), this.RawZCoords, IsCompressed); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecP192K1Curve.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecP192K1Curve.cs new file mode 100644 index 0000000..81f7719 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecP192K1Curve.cs @@ -0,0 +1,75 @@ +using System; + +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecP192K1Curve + : AbstractFpCurve + { + public static readonly BigInteger q = new BigInteger(1, + Hex.Decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFEE37")); + + private const int SECP192K1_DEFAULT_COORDS = COORD_JACOBIAN; + + protected readonly SecP192K1Point m_infinity; + + public SecP192K1Curve() + : base(q) + { + this.m_infinity = new SecP192K1Point(this, null, null); + + this.m_a = FromBigInteger(BigInteger.Zero); + this.m_b = FromBigInteger(BigInteger.ValueOf(3)); + this.m_order = new BigInteger(1, Hex.Decode("FFFFFFFFFFFFFFFFFFFFFFFE26F2FC170F69466A74DEFD8D")); + this.m_cofactor = BigInteger.One; + this.m_coord = SECP192K1_DEFAULT_COORDS; + } + + protected override ECCurve CloneCurve() + { + return new SecP192K1Curve(); + } + + public override bool SupportsCoordinateSystem(int coord) + { + switch (coord) + { + case COORD_JACOBIAN: + return true; + default: + return false; + } + } + + public virtual BigInteger Q + { + get { return q; } + } + + public override ECPoint Infinity + { + get { return m_infinity; } + } + + public override int FieldSize + { + get { return q.BitLength; } + } + + public override ECFieldElement FromBigInteger(BigInteger x) + { + return new SecP192K1FieldElement(x); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, bool withCompression) + { + return new SecP192K1Point(this, x, y, withCompression); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + { + return new SecP192K1Point(this, x, y, zs, withCompression); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecP192K1Field.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecP192K1Field.cs new file mode 100644 index 0000000..a003603 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecP192K1Field.cs @@ -0,0 +1,178 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Math.Raw; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecP192K1Field + { + // 2^192 - 2^32 - 2^12 - 2^8 - 2^7 - 2^6 - 2^3 - 1 + internal static readonly uint[] P = new uint[]{ 0xFFFFEE37, 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }; + internal static readonly uint[] PExt = new uint[]{ 0x013C4FD1, 0x00002392, 0x00000001, 0x00000000, 0x00000000, + 0x00000000, 0xFFFFDC6E, 0xFFFFFFFD, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }; + private static readonly uint[] PExtInv = new uint[]{ 0xFEC3B02F, 0xFFFFDC6D, 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0x00002391, 0x00000002 }; + private const uint P5 = 0xFFFFFFFF; + private const uint PExt11 = 0xFFFFFFFF; + private const uint PInv33 = 0x11C9; + + public static void Add(uint[] x, uint[] y, uint[] z) + { + uint c = Nat192.Add(x, y, z); + if (c != 0 || (z[5] == P5 && Nat192.Gte(z, P))) + { + Nat.Add33To(6, PInv33, z); + } + } + + public static void AddExt(uint[] xx, uint[] yy, uint[] zz) + { + uint c = Nat.Add(12, xx, yy, zz); + if (c != 0 || (zz[11] == PExt11 && Nat.Gte(12, zz, PExt))) + { + if (Nat.AddTo(PExtInv.Length, PExtInv, zz) != 0) + { + Nat.IncAt(12, zz, PExtInv.Length); + } + } + } + + public static void AddOne(uint[] x, uint[] z) + { + uint c = Nat.Inc(6, x, z); + if (c != 0 || (z[5] == P5 && Nat192.Gte(z, P))) + { + Nat.Add33To(6, PInv33, z); + } + } + + public static uint[] FromBigInteger(BigInteger x) + { + uint[] z = Nat192.FromBigInteger(x); + if (z[5] == P5 && Nat192.Gte(z, P)) + { + Nat192.SubFrom(P, z); + } + return z; + } + + public static void Half(uint[] x, uint[] z) + { + if ((x[0] & 1) == 0) + { + Nat.ShiftDownBit(6, x, 0, z); + } + else + { + uint c = Nat192.Add(x, P, z); + Nat.ShiftDownBit(6, z, c); + } + } + + public static void Multiply(uint[] x, uint[] y, uint[] z) + { + uint[] tt = Nat192.CreateExt(); + Nat192.Mul(x, y, tt); + Reduce(tt, z); + } + + public static void MultiplyAddToExt(uint[] x, uint[] y, uint[] zz) + { + uint c = Nat192.MulAddTo(x, y, zz); + if (c != 0 || (zz[11] == PExt11 && Nat.Gte(12, zz, PExt))) + { + if (Nat.AddTo(PExtInv.Length, PExtInv, zz) != 0) + { + Nat.IncAt(12, zz, PExtInv.Length); + } + } + } + + public static void Negate(uint[] x, uint[] z) + { + if (Nat192.IsZero(x)) + { + Nat192.Zero(z); + } + else + { + Nat192.Sub(P, x, z); + } + } + + public static void Reduce(uint[] xx, uint[] z) + { + ulong cc = Nat192.Mul33Add(PInv33, xx, 6, xx, 0, z, 0); + uint c = Nat192.Mul33DWordAdd(PInv33, cc, z, 0); + + Debug.Assert(c == 0 || c == 1); + + if (c != 0 || (z[5] == P5 && Nat192.Gte(z, P))) + { + Nat.Add33To(6, PInv33, z); + } + } + + public static void Reduce32(uint x, uint[] z) + { + if ((x != 0 && Nat192.Mul33WordAdd(PInv33, x, z, 0) != 0) + || (z[5] == P5 && Nat192.Gte(z, P))) + { + Nat.Add33To(6, PInv33, z); + } + } + + public static void Square(uint[] x, uint[] z) + { + uint[] tt = Nat192.CreateExt(); + Nat192.Square(x, tt); + Reduce(tt, z); + } + + public static void SquareN(uint[] x, int n, uint[] z) + { + Debug.Assert(n > 0); + + uint[] tt = Nat192.CreateExt(); + Nat192.Square(x, tt); + Reduce(tt, z); + + while (--n > 0) + { + Nat192.Square(z, tt); + Reduce(tt, z); + } + } + + public static void Subtract(uint[] x, uint[] y, uint[] z) + { + int c = Nat192.Sub(x, y, z); + if (c != 0) + { + Nat.Sub33From(6, PInv33, z); + } + } + + public static void SubtractExt(uint[] xx, uint[] yy, uint[] zz) + { + int c = Nat.Sub(12, xx, yy, zz); + if (c != 0) + { + if (Nat.SubFrom(PExtInv.Length, PExtInv, zz) != 0) + { + Nat.DecAt(12, zz, PExtInv.Length); + } + } + } + + public static void Twice(uint[] x, uint[] z) + { + uint c = Nat.ShiftUpBit(6, x, 0, z); + if (c != 0 || (z[5] == P5 && Nat192.Gte(z, P))) + { + Nat.Add33To(6, PInv33, z); + } + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecP192K1FieldElement.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecP192K1FieldElement.cs new file mode 100644 index 0000000..dce3770 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecP192K1FieldElement.cs @@ -0,0 +1,213 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Math.Raw; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecP192K1FieldElement + : ECFieldElement + { + public static readonly BigInteger Q = SecP192K1Curve.q; + + protected internal readonly uint[] x; + + public SecP192K1FieldElement(BigInteger x) + { + if (x == null || x.SignValue < 0 || x.CompareTo(Q) >= 0) + throw new ArgumentException("value invalid for SecP192K1FieldElement", "x"); + + this.x = SecP192K1Field.FromBigInteger(x); + } + + public SecP192K1FieldElement() + { + this.x = Nat192.Create(); + } + + protected internal SecP192K1FieldElement(uint[] x) + { + this.x = x; + } + + public override bool IsZero + { + get { return Nat192.IsZero(x); } + } + + public override bool IsOne + { + get { return Nat192.IsOne(x); } + } + + public override bool TestBitZero() + { + return Nat192.GetBit(x, 0) == 1; + } + + public override BigInteger ToBigInteger() + { + return Nat192.ToBigInteger(x); + } + + public override string FieldName + { + get { return "SecP192K1Field"; } + } + + public override int FieldSize + { + get { return Q.BitLength; } + } + + public override ECFieldElement Add(ECFieldElement b) + { + uint[] z = Nat192.Create(); + SecP192K1Field.Add(x, ((SecP192K1FieldElement)b).x, z); + return new SecP192K1FieldElement(z); + } + + public override ECFieldElement AddOne() + { + uint[] z = Nat192.Create(); + SecP192K1Field.AddOne(x, z); + return new SecP192K1FieldElement(z); + } + + public override ECFieldElement Subtract(ECFieldElement b) + { + uint[] z = Nat192.Create(); + SecP192K1Field.Subtract(x, ((SecP192K1FieldElement)b).x, z); + return new SecP192K1FieldElement(z); + } + + public override ECFieldElement Multiply(ECFieldElement b) + { + uint[] z = Nat192.Create(); + SecP192K1Field.Multiply(x, ((SecP192K1FieldElement)b).x, z); + return new SecP192K1FieldElement(z); + } + + public override ECFieldElement Divide(ECFieldElement b) + { + //return Multiply(b.Invert()); + uint[] z = Nat192.Create(); + Mod.Invert(SecP192K1Field.P, ((SecP192K1FieldElement)b).x, z); + SecP192K1Field.Multiply(z, x, z); + return new SecP192K1FieldElement(z); + } + + public override ECFieldElement Negate() + { + uint[] z = Nat192.Create(); + SecP192K1Field.Negate(x, z); + return new SecP192K1FieldElement(z); + } + + public override ECFieldElement Square() + { + uint[] z = Nat192.Create(); + SecP192K1Field.Square(x, z); + return new SecP192K1FieldElement(z); + } + + public override ECFieldElement Invert() + { + //return new SecP192K1FieldElement(ToBigInteger().ModInverse(Q)); + uint[] z = Nat192.Create(); + Mod.Invert(SecP192K1Field.P, x, z); + return new SecP192K1FieldElement(z); + } + + /** + * return a sqrt root - the routine verifies that the calculation returns the right value - if + * none exists it returns null. + */ + public override ECFieldElement Sqrt() + { + /* + * Raise this element to the exponent 2^190 - 2^30 - 2^10 - 2^6 - 2^5 - 2^4 - 2^1 + * + * Breaking up the exponent's binary representation into "repunits", we get: + * { 159 1s } { 1 0s } { 19 1s } { 1 0s } { 3 1s } { 3 0s} { 3 1s } { 1 0s } + * + * Therefore we need an addition chain containing 3, 19, 159 (the lengths of the repunits) + * We use: 1, 2, [3], 6, 8, 16, [19], 35, 70, 140, [159] + */ + + uint[] x1 = this.x; + if (Nat192.IsZero(x1) || Nat192.IsOne(x1)) + return this; + + uint[] x2 = Nat192.Create(); + SecP192K1Field.Square(x1, x2); + SecP192K1Field.Multiply(x2, x1, x2); + uint[] x3 = Nat192.Create(); + SecP192K1Field.Square(x2, x3); + SecP192K1Field.Multiply(x3, x1, x3); + uint[] x6 = Nat192.Create(); + SecP192K1Field.SquareN(x3, 3, x6); + SecP192K1Field.Multiply(x6, x3, x6); + uint[] x8 = x6; + SecP192K1Field.SquareN(x6, 2, x8); + SecP192K1Field.Multiply(x8, x2, x8); + uint[] x16 = x2; + SecP192K1Field.SquareN(x8, 8, x16); + SecP192K1Field.Multiply(x16, x8, x16); + uint[] x19 = x8; + SecP192K1Field.SquareN(x16, 3, x19); + SecP192K1Field.Multiply(x19, x3, x19); + uint[] x35 = Nat192.Create(); + SecP192K1Field.SquareN(x19, 16, x35); + SecP192K1Field.Multiply(x35, x16, x35); + uint[] x70 = x16; + SecP192K1Field.SquareN(x35, 35, x70); + SecP192K1Field.Multiply(x70, x35, x70); + uint[] x140 = x35; + SecP192K1Field.SquareN(x70, 70, x140); + SecP192K1Field.Multiply(x140, x70, x140); + uint[] x159 = x70; + SecP192K1Field.SquareN(x140, 19, x159); + SecP192K1Field.Multiply(x159, x19, x159); + + uint[] t1 = x159; + SecP192K1Field.SquareN(t1, 20, t1); + SecP192K1Field.Multiply(t1, x19, t1); + SecP192K1Field.SquareN(t1, 4, t1); + SecP192K1Field.Multiply(t1, x3, t1); + SecP192K1Field.SquareN(t1, 6, t1); + SecP192K1Field.Multiply(t1, x3, t1); + SecP192K1Field.Square(t1, t1); + + uint[] t2 = x3; + SecP192K1Field.Square(t1, t2); + + return Nat192.Eq(x1, t2) ? new SecP192K1FieldElement(t1) : null; + } + + public override bool Equals(object obj) + { + return Equals(obj as SecP192K1FieldElement); + } + + public override bool Equals(ECFieldElement other) + { + return Equals(other as SecP192K1FieldElement); + } + + public virtual bool Equals(SecP192K1FieldElement other) + { + if (this == other) + return true; + if (null == other) + return false; + return Nat192.Eq(x, other.x); + } + + public override int GetHashCode() + { + return Q.GetHashCode() ^ Arrays.GetHashCode(x, 0, 6); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecP192K1Point.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecP192K1Point.cs new file mode 100644 index 0000000..58eb091 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecP192K1Point.cs @@ -0,0 +1,267 @@ +using System; + +using Org.BouncyCastle.Math.Raw; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecP192K1Point + : AbstractFpPoint + { + /** + * 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 + * + * @deprecated Use ECCurve.createPoint to construct points + */ + public SecP192K1Point(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 + * + * @deprecated per-point compression property will be removed, refer + * {@link #getEncoded(bool)} + */ + public SecP192K1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, bool withCompression) + : base(curve, x, y, withCompression) + { + if ((x == null) != (y == null)) + throw new ArgumentException("Exactly one of the field elements is null"); + } + + internal SecP192K1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, + bool withCompression) + : base(curve, x, y, zs, withCompression) + { + } + + protected override ECPoint Detach() + { + return new SecP192K1Point(null, AffineXCoord, AffineYCoord); + } + + public override ECPoint Add(ECPoint b) + { + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return this; + if (this == b) + return Twice(); + + ECCurve curve = this.Curve; + + SecP192K1FieldElement X1 = (SecP192K1FieldElement)this.RawXCoord, Y1 = (SecP192K1FieldElement)this.RawYCoord; + SecP192K1FieldElement X2 = (SecP192K1FieldElement)b.RawXCoord, Y2 = (SecP192K1FieldElement)b.RawYCoord; + + SecP192K1FieldElement Z1 = (SecP192K1FieldElement)this.RawZCoords[0]; + SecP192K1FieldElement Z2 = (SecP192K1FieldElement)b.RawZCoords[0]; + + uint c; + uint[] tt1 = Nat192.CreateExt(); + uint[] t2 = Nat192.Create(); + uint[] t3 = Nat192.Create(); + uint[] t4 = Nat192.Create(); + + bool Z1IsOne = Z1.IsOne; + uint[] U2, S2; + if (Z1IsOne) + { + U2 = X2.x; + S2 = Y2.x; + } + else + { + S2 = t3; + SecP192K1Field.Square(Z1.x, S2); + + U2 = t2; + SecP192K1Field.Multiply(S2, X2.x, U2); + + SecP192K1Field.Multiply(S2, Z1.x, S2); + SecP192K1Field.Multiply(S2, Y2.x, S2); + } + + bool Z2IsOne = Z2.IsOne; + uint[] U1, S1; + if (Z2IsOne) + { + U1 = X1.x; + S1 = Y1.x; + } + else + { + S1 = t4; + SecP192K1Field.Square(Z2.x, S1); + + U1 = tt1; + SecP192K1Field.Multiply(S1, X1.x, U1); + + SecP192K1Field.Multiply(S1, Z2.x, S1); + SecP192K1Field.Multiply(S1, Y1.x, S1); + } + + uint[] H = Nat192.Create(); + SecP192K1Field.Subtract(U1, U2, H); + + uint[] R = t2; + SecP192K1Field.Subtract(S1, S2, R); + + // Check if b == this or b == -this + if (Nat192.IsZero(H)) + { + if (Nat192.IsZero(R)) + { + // this == b, i.e. this must be doubled + return this.Twice(); + } + + // this == -b, i.e. the result is the point at infinity + return curve.Infinity; + } + + uint[] HSquared = t3; + SecP192K1Field.Square(H, HSquared); + + uint[] G = Nat192.Create(); + SecP192K1Field.Multiply(HSquared, H, G); + + uint[] V = t3; + SecP192K1Field.Multiply(HSquared, U1, V); + + SecP192K1Field.Negate(G, G); + Nat192.Mul(S1, G, tt1); + + c = Nat192.AddBothTo(V, V, G); + SecP192K1Field.Reduce32(c, G); + + SecP192K1FieldElement X3 = new SecP192K1FieldElement(t4); + SecP192K1Field.Square(R, X3.x); + SecP192K1Field.Subtract(X3.x, G, X3.x); + + SecP192K1FieldElement Y3 = new SecP192K1FieldElement(G); + SecP192K1Field.Subtract(V, X3.x, Y3.x); + SecP192K1Field.MultiplyAddToExt(Y3.x, R, tt1); + SecP192K1Field.Reduce(tt1, Y3.x); + + SecP192K1FieldElement Z3 = new SecP192K1FieldElement(H); + if (!Z1IsOne) + { + SecP192K1Field.Multiply(Z3.x, Z1.x, Z3.x); + } + if (!Z2IsOne) + { + SecP192K1Field.Multiply(Z3.x, Z2.x, Z3.x); + } + + ECFieldElement[] zs = new ECFieldElement[] { Z3 }; + + return new SecP192K1Point(curve, X3, Y3, zs, IsCompressed); + } + + public override ECPoint Twice() + { + if (this.IsInfinity) + return this; + + ECCurve curve = this.Curve; + + SecP192K1FieldElement Y1 = (SecP192K1FieldElement)this.RawYCoord; + if (Y1.IsZero) + return curve.Infinity; + + SecP192K1FieldElement X1 = (SecP192K1FieldElement)this.RawXCoord, Z1 = (SecP192K1FieldElement)this.RawZCoords[0]; + + uint c; + + uint[] Y1Squared = Nat192.Create(); + SecP192K1Field.Square(Y1.x, Y1Squared); + + uint[] T = Nat192.Create(); + SecP192K1Field.Square(Y1Squared, T); + + uint[] M = Nat192.Create(); + SecP192K1Field.Square(X1.x, M); + c = Nat192.AddBothTo(M, M, M); + SecP192K1Field.Reduce32(c, M); + + uint[] S = Y1Squared; + SecP192K1Field.Multiply(Y1Squared, X1.x, S); + c = Nat.ShiftUpBits(6, S, 2, 0); + SecP192K1Field.Reduce32(c, S); + + uint[] t1 = Nat192.Create(); + c = Nat.ShiftUpBits(6, T, 3, 0, t1); + SecP192K1Field.Reduce32(c, t1); + + SecP192K1FieldElement X3 = new SecP192K1FieldElement(T); + SecP192K1Field.Square(M, X3.x); + SecP192K1Field.Subtract(X3.x, S, X3.x); + SecP192K1Field.Subtract(X3.x, S, X3.x); + + SecP192K1FieldElement Y3 = new SecP192K1FieldElement(S); + SecP192K1Field.Subtract(S, X3.x, Y3.x); + SecP192K1Field.Multiply(Y3.x, M, Y3.x); + SecP192K1Field.Subtract(Y3.x, t1, Y3.x); + + SecP192K1FieldElement Z3 = new SecP192K1FieldElement(M); + SecP192K1Field.Twice(Y1.x, Z3.x); + if (!Z1.IsOne) + { + SecP192K1Field.Multiply(Z3.x, Z1.x, Z3.x); + } + + return new SecP192K1Point(curve, X3, Y3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint TwicePlus(ECPoint b) + { + if (this == b) + return ThreeTimes(); + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return Twice(); + + ECFieldElement Y1 = this.RawYCoord; + if (Y1.IsZero) + return b; + + return Twice().Add(b); + } + + public override ECPoint ThreeTimes() + { + if (this.IsInfinity || this.RawYCoord.IsZero) + return this; + + // NOTE: Be careful about recursions between TwicePlus and ThreeTimes + return Twice().Add(this); + } + + public override ECPoint Negate() + { + if (IsInfinity) + return this; + + return new SecP192K1Point(Curve, RawXCoord, RawYCoord.Negate(), RawZCoords, IsCompressed); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecP192R1Curve.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecP192R1Curve.cs new file mode 100644 index 0000000..cb3a981 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecP192R1Curve.cs @@ -0,0 +1,78 @@ +using System; + +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecP192R1Curve + : AbstractFpCurve + { + public static readonly BigInteger q = new BigInteger(1, + Hex.Decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFF")); + + private const int SecP192R1_DEFAULT_COORDS = COORD_JACOBIAN; + + protected readonly SecP192R1Point m_infinity; + + public SecP192R1Curve() + : base(q) + { + this.m_infinity = new SecP192R1Point(this, null, null); + + this.m_a = FromBigInteger(new BigInteger(1, + Hex.Decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFC"))); + this.m_b = FromBigInteger(new BigInteger(1, + Hex.Decode("64210519E59C80E70FA7E9AB72243049FEB8DEECC146B9B1"))); + this.m_order = new BigInteger(1, Hex.Decode("FFFFFFFFFFFFFFFFFFFFFFFF99DEF836146BC9B1B4D22831")); + this.m_cofactor = BigInteger.One; + + this.m_coord = SecP192R1_DEFAULT_COORDS; + } + + protected override ECCurve CloneCurve() + { + return new SecP192R1Curve(); + } + + public override bool SupportsCoordinateSystem(int coord) + { + switch (coord) + { + case COORD_JACOBIAN: + return true; + default: + return false; + } + } + + public virtual BigInteger Q + { + get { return q; } + } + + public override ECPoint Infinity + { + get { return m_infinity; } + } + + public override int FieldSize + { + get { return q.BitLength; } + } + + public override ECFieldElement FromBigInteger(BigInteger x) + { + return new SecP192R1FieldElement(x); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, bool withCompression) + { + return new SecP192R1Point(this, x, y, withCompression); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + { + return new SecP192R1Point(this, x, y, zs, withCompression); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecP192R1Field.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecP192R1Field.cs new file mode 100644 index 0000000..096c2b5 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecP192R1Field.cs @@ -0,0 +1,283 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Math.Raw; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecP192R1Field + { + // 2^192 - 2^64 - 1 + internal static readonly uint[] P = new uint[]{ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }; + internal static readonly uint[] PExt = new uint[]{ 0x00000001, 0x00000000, 0x00000002, 0x00000000, 0x00000001, + 0x00000000, 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFD, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }; + private static readonly uint[] PExtInv = new uint[]{ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFD, 0xFFFFFFFF, 0xFFFFFFFE, + 0xFFFFFFFF, 0x00000001, 0x00000000, 0x00000002 }; + private const uint P5 = 0xFFFFFFFF; + private const uint PExt11 = 0xFFFFFFFF; + + public static void Add(uint[] x, uint[] y, uint[] z) + { + uint c = Nat192.Add(x, y, z); + if (c != 0 || (z[5] == P5 && Nat192.Gte(z, P))) + { + AddPInvTo(z); + } + } + + public static void AddExt(uint[] xx, uint[] yy, uint[] zz) + { + uint c = Nat.Add(12, xx, yy, zz); + if (c != 0 || (zz[11] == PExt11 && Nat.Gte(12, zz, PExt))) + { + if (Nat.AddTo(PExtInv.Length, PExtInv, zz) != 0) + { + Nat.IncAt(12, zz, PExtInv.Length); + } + } + } + + public static void AddOne(uint[] x, uint[] z) + { + uint c = Nat.Inc(6, x, z); + if (c != 0 || (z[5] == P5 && Nat192.Gte(z, P))) + { + AddPInvTo(z); + } + } + + public static uint[] FromBigInteger(BigInteger x) + { + uint[] z = Nat192.FromBigInteger(x); + if (z[5] == P5 && Nat192.Gte(z, P)) + { + Nat192.SubFrom(P, z); + } + return z; + } + + public static void Half(uint[] x, uint[] z) + { + if ((x[0] & 1) == 0) + { + Nat.ShiftDownBit(6, x, 0, z); + } + else + { + uint c = Nat192.Add(x, P, z); + Nat.ShiftDownBit(6, z, c); + } + } + + public static void Multiply(uint[] x, uint[] y, uint[] z) + { + uint[] tt = Nat192.CreateExt(); + Nat192.Mul(x, y, tt); + Reduce(tt, z); + } + + public static void MultiplyAddToExt(uint[] x, uint[] y, uint[] zz) + { + uint c = Nat192.MulAddTo(x, y, zz); + if (c != 0 || (zz[11] == PExt11 && Nat.Gte(12, zz, PExt))) + { + if (Nat.AddTo(PExtInv.Length, PExtInv, zz) != 0) + { + Nat.IncAt(12, zz, PExtInv.Length); + } + } + } + + public static void Negate(uint[] x, uint[] z) + { + if (Nat192.IsZero(x)) + { + Nat192.Zero(z); + } + else + { + Nat192.Sub(P, x, z); + } + } + + public static void Reduce(uint[] xx, uint[] z) + { + ulong xx06 = xx[6], xx07 = xx[7], xx08 = xx[8]; + ulong xx09 = xx[9], xx10 = xx[10], xx11 = xx[11]; + + ulong t0 = xx06 + xx10; + ulong t1 = xx07 + xx11; + + ulong cc = 0; + cc += (ulong)xx[0] + t0; + uint z0 = (uint)cc; + cc >>= 32; + cc += (ulong)xx[1] + t1; + z[1] = (uint)cc; + cc >>= 32; + + t0 += xx08; + t1 += xx09; + + cc += (ulong)xx[2] + t0; + ulong z2 = (uint)cc; + cc >>= 32; + cc += (ulong)xx[3] + t1; + z[3] = (uint)cc; + cc >>= 32; + + t0 -= xx06; + t1 -= xx07; + + cc += (ulong)xx[4] + t0; + z[4] = (uint)cc; + cc >>= 32; + cc += (ulong)xx[5] + t1; + z[5] = (uint)cc; + cc >>= 32; + + z2 += cc; + + cc += z0; + z[0] = (uint)cc; + cc >>= 32; + if (cc != 0) + { + cc += z[1]; + z[1] = (uint)cc; + z2 += cc >> 32; + } + z[2] = (uint)z2; + cc = z2 >> 32; + + Debug.Assert(cc == 0 || cc == 1); + + if ((cc != 0 && Nat.IncAt(6, z, 3) != 0) + || (z[5] == P5 && Nat192.Gte(z, P))) + { + AddPInvTo(z); + } + } + + public static void Reduce32(uint x, uint[] z) + { + ulong cc = 0; + + if (x != 0) + { + cc += (ulong)z[0] + x; + z[0] = (uint)cc; + cc >>= 32; + if (cc != 0) + { + cc += (ulong)z[1]; + z[1] = (uint)cc; + cc >>= 32; + } + cc += (ulong)z[2] + x; + z[2] = (uint)cc; + cc >>= 32; + + Debug.Assert(cc == 0 || cc == 1); + } + + if ((cc != 0 && Nat.IncAt(6, z, 3) != 0) + || (z[5] == P5 && Nat192.Gte(z, P))) + { + AddPInvTo(z); + } + } + + public static void Square(uint[] x, uint[] z) + { + uint[] tt = Nat192.CreateExt(); + Nat192.Square(x, tt); + Reduce(tt, z); + } + + public static void SquareN(uint[] x, int n, uint[] z) + { + Debug.Assert(n > 0); + + uint[] tt = Nat192.CreateExt(); + Nat192.Square(x, tt); + Reduce(tt, z); + + while (--n > 0) + { + Nat192.Square(z, tt); + Reduce(tt, z); + } + } + + public static void Subtract(uint[] x, uint[] y, uint[] z) + { + int c = Nat192.Sub(x, y, z); + if (c != 0) + { + SubPInvFrom(z); + } + } + + public static void SubtractExt(uint[] xx, uint[] yy, uint[] zz) + { + int c = Nat.Sub(12, xx, yy, zz); + if (c != 0) + { + if (Nat.SubFrom(PExtInv.Length, PExtInv, zz) != 0) + { + Nat.DecAt(12, zz, PExtInv.Length); + } + } + } + + public static void Twice(uint[] x, uint[] z) + { + uint c = Nat.ShiftUpBit(6, x, 0, z); + if (c != 0 || (z[5] == P5 && Nat192.Gte(z, P))) + { + AddPInvTo(z); + } + } + + private static void AddPInvTo(uint[] z) + { + long c = (long)z[0] + 1; + z[0] = (uint)c; + c >>= 32; + if (c != 0) + { + c += (long)z[1]; + z[1] = (uint)c; + c >>= 32; + } + c += (long)z[2] + 1; + z[2] = (uint)c; + c >>= 32; + if (c != 0) + { + Nat.IncAt(6, z, 3); + } + } + + private static void SubPInvFrom(uint[] z) + { + long c = (long)z[0] - 1; + z[0] = (uint)c; + c >>= 32; + if (c != 0) + { + c += (long)z[1]; + z[1] = (uint)c; + c >>= 32; + } + c += (long)z[2] - 1; + z[2] = (uint)c; + c >>= 32; + if (c != 0) + { + Nat.DecAt(6, z, 3); + } + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecP192R1FieldElement.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecP192R1FieldElement.cs new file mode 100644 index 0000000..45bcb00 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecP192R1FieldElement.cs @@ -0,0 +1,188 @@ +using System; + +using Org.BouncyCastle.Math.Raw; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecP192R1FieldElement + : ECFieldElement + { + public static readonly BigInteger Q = SecP192R1Curve.q; + + protected internal readonly uint[] x; + + public SecP192R1FieldElement(BigInteger x) + { + if (x == null || x.SignValue < 0 || x.CompareTo(Q) >= 0) + throw new ArgumentException("value invalid for SecP192R1FieldElement", "x"); + + this.x = SecP192R1Field.FromBigInteger(x); + } + + public SecP192R1FieldElement() + { + this.x = Nat192.Create(); + } + + protected internal SecP192R1FieldElement(uint[] x) + { + this.x = x; + } + + public override bool IsZero + { + get { return Nat192.IsZero(x); } + } + + public override bool IsOne + { + get { return Nat192.IsOne(x); } + } + + public override bool TestBitZero() + { + return Nat192.GetBit(x, 0) == 1; + } + + public override BigInteger ToBigInteger() + { + return Nat192.ToBigInteger(x); + } + + public override string FieldName + { + get { return "SecP192R1Field"; } + } + + public override int FieldSize + { + get { return Q.BitLength; } + } + + public override ECFieldElement Add(ECFieldElement b) + { + uint[] z = Nat192.Create(); + SecP192R1Field.Add(x, ((SecP192R1FieldElement)b).x, z); + return new SecP192R1FieldElement(z); + } + + public override ECFieldElement AddOne() + { + uint[] z = Nat192.Create(); + SecP192R1Field.AddOne(x, z); + return new SecP192R1FieldElement(z); + } + + public override ECFieldElement Subtract(ECFieldElement b) + { + uint[] z = Nat192.Create(); + SecP192R1Field.Subtract(x, ((SecP192R1FieldElement)b).x, z); + return new SecP192R1FieldElement(z); + } + + public override ECFieldElement Multiply(ECFieldElement b) + { + uint[] z = Nat192.Create(); + SecP192R1Field.Multiply(x, ((SecP192R1FieldElement)b).x, z); + return new SecP192R1FieldElement(z); + } + + public override ECFieldElement Divide(ECFieldElement b) + { + //return Multiply(b.Invert()); + uint[] z = Nat192.Create(); + Mod.Invert(SecP192R1Field.P, ((SecP192R1FieldElement)b).x, z); + SecP192R1Field.Multiply(z, x, z); + return new SecP192R1FieldElement(z); + } + + public override ECFieldElement Negate() + { + uint[] z = Nat192.Create(); + SecP192R1Field.Negate(x, z); + return new SecP192R1FieldElement(z); + } + + public override ECFieldElement Square() + { + uint[] z = Nat192.Create(); + SecP192R1Field.Square(x, z); + return new SecP192R1FieldElement(z); + } + + public override ECFieldElement Invert() + { + //return new SecP192R1FieldElement(ToBigInteger().ModInverse(Q)); + uint[] z = Nat192.Create(); + Mod.Invert(SecP192R1Field.P, x, z); + return new SecP192R1FieldElement(z); + } + + /** + * return a sqrt root - the routine verifies that the calculation returns the right value - if + * none exists it returns null. + */ + public override ECFieldElement Sqrt() + { + // Raise this element to the exponent 2^190 - 2^62 + + uint[] x1 = this.x; + if (Nat192.IsZero(x1) || Nat192.IsOne(x1)) + return this; + + uint[] t1 = Nat192.Create(); + uint[] t2 = Nat192.Create(); + + SecP192R1Field.Square(x1, t1); + SecP192R1Field.Multiply(t1, x1, t1); + + SecP192R1Field.SquareN(t1, 2, t2); + SecP192R1Field.Multiply(t2, t1, t2); + + SecP192R1Field.SquareN(t2, 4, t1); + SecP192R1Field.Multiply(t1, t2, t1); + + SecP192R1Field.SquareN(t1, 8, t2); + SecP192R1Field.Multiply(t2, t1, t2); + + SecP192R1Field.SquareN(t2, 16, t1); + SecP192R1Field.Multiply(t1, t2, t1); + + SecP192R1Field.SquareN(t1, 32, t2); + SecP192R1Field.Multiply(t2, t1, t2); + + SecP192R1Field.SquareN(t2, 64, t1); + SecP192R1Field.Multiply(t1, t2, t1); + + SecP192R1Field.SquareN(t1, 62, t1); + SecP192R1Field.Square(t1, t2); + + return Nat192.Eq(x1, t2) ? new SecP192R1FieldElement(t1) : null; + } + + public override bool Equals(object obj) + { + return Equals(obj as SecP192R1FieldElement); + } + + public override bool Equals(ECFieldElement other) + { + return Equals(other as SecP192R1FieldElement); + } + + public virtual bool Equals(SecP192R1FieldElement other) + { + if (this == other) + return true; + if (null == other) + return false; + return Nat192.Eq(x, other.x); + } + + public override int GetHashCode() + { + return Q.GetHashCode() ^ Arrays.GetHashCode(x, 0, 6); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecP192R1Point.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecP192R1Point.cs new file mode 100644 index 0000000..3b53e34 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecP192R1Point.cs @@ -0,0 +1,279 @@ +using System; + +using Org.BouncyCastle.Math.Raw; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecP192R1Point + : AbstractFpPoint + { + /** + * 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 + * + * @deprecated Use ECCurve.createPoint to construct points + */ + public SecP192R1Point(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 + * + * @deprecated per-point compression property will be removed, refer + * {@link #getEncoded(bool)} + */ + public SecP192R1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, bool withCompression) + : base(curve, x, y, withCompression) + { + if ((x == null) != (y == null)) + throw new ArgumentException("Exactly one of the field elements is null"); + } + + internal SecP192R1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + : base(curve, x, y, zs, withCompression) + { + } + + protected override ECPoint Detach() + { + return new SecP192R1Point(null, AffineXCoord, AffineYCoord); + } + + public override ECPoint Add(ECPoint b) + { + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return this; + if (this == b) + return Twice(); + + ECCurve curve = this.Curve; + + SecP192R1FieldElement X1 = (SecP192R1FieldElement)this.RawXCoord, Y1 = (SecP192R1FieldElement)this.RawYCoord; + SecP192R1FieldElement X2 = (SecP192R1FieldElement)b.RawXCoord, Y2 = (SecP192R1FieldElement)b.RawYCoord; + + SecP192R1FieldElement Z1 = (SecP192R1FieldElement)this.RawZCoords[0]; + SecP192R1FieldElement Z2 = (SecP192R1FieldElement)b.RawZCoords[0]; + + uint c; + uint[] tt1 = Nat192.CreateExt(); + uint[] t2 = Nat192.Create(); + uint[] t3 = Nat192.Create(); + uint[] t4 = Nat192.Create(); + + bool Z1IsOne = Z1.IsOne; + uint[] U2, S2; + if (Z1IsOne) + { + U2 = X2.x; + S2 = Y2.x; + } + else + { + S2 = t3; + SecP192R1Field.Square(Z1.x, S2); + + U2 = t2; + SecP192R1Field.Multiply(S2, X2.x, U2); + + SecP192R1Field.Multiply(S2, Z1.x, S2); + SecP192R1Field.Multiply(S2, Y2.x, S2); + } + + bool Z2IsOne = Z2.IsOne; + uint[] U1, S1; + if (Z2IsOne) + { + U1 = X1.x; + S1 = Y1.x; + } + else + { + S1 = t4; + SecP192R1Field.Square(Z2.x, S1); + + U1 = tt1; + SecP192R1Field.Multiply(S1, X1.x, U1); + + SecP192R1Field.Multiply(S1, Z2.x, S1); + SecP192R1Field.Multiply(S1, Y1.x, S1); + } + + uint[] H = Nat192.Create(); + SecP192R1Field.Subtract(U1, U2, H); + + uint[] R = t2; + SecP192R1Field.Subtract(S1, S2, R); + + // Check if b == this or b == -this + if (Nat192.IsZero(H)) + { + if (Nat192.IsZero(R)) + { + // this == b, i.e. this must be doubled + return this.Twice(); + } + + // this == -b, i.e. the result is the point at infinity + return curve.Infinity; + } + + uint[] HSquared = t3; + SecP192R1Field.Square(H, HSquared); + + uint[] G = Nat192.Create(); + SecP192R1Field.Multiply(HSquared, H, G); + + uint[] V = t3; + SecP192R1Field.Multiply(HSquared, U1, V); + + SecP192R1Field.Negate(G, G); + Nat192.Mul(S1, G, tt1); + + c = Nat192.AddBothTo(V, V, G); + SecP192R1Field.Reduce32(c, G); + + SecP192R1FieldElement X3 = new SecP192R1FieldElement(t4); + SecP192R1Field.Square(R, X3.x); + SecP192R1Field.Subtract(X3.x, G, X3.x); + + SecP192R1FieldElement Y3 = new SecP192R1FieldElement(G); + SecP192R1Field.Subtract(V, X3.x, Y3.x); + SecP192R1Field.MultiplyAddToExt(Y3.x, R, tt1); + SecP192R1Field.Reduce(tt1, Y3.x); + + SecP192R1FieldElement Z3 = new SecP192R1FieldElement(H); + if (!Z1IsOne) + { + SecP192R1Field.Multiply(Z3.x, Z1.x, Z3.x); + } + if (!Z2IsOne) + { + SecP192R1Field.Multiply(Z3.x, Z2.x, Z3.x); + } + + ECFieldElement[] zs = new ECFieldElement[] { Z3 }; + + return new SecP192R1Point(curve, X3, Y3, zs, IsCompressed); + } + + public override ECPoint Twice() + { + if (this.IsInfinity) + return this; + + ECCurve curve = this.Curve; + + SecP192R1FieldElement Y1 = (SecP192R1FieldElement)this.RawYCoord; + if (Y1.IsZero) + return curve.Infinity; + + SecP192R1FieldElement X1 = (SecP192R1FieldElement)this.RawXCoord, Z1 = (SecP192R1FieldElement)this.RawZCoords[0]; + + uint c; + uint[] t1 = Nat192.Create(); + uint[] t2 = Nat192.Create(); + + uint[] Y1Squared = Nat192.Create(); + SecP192R1Field.Square(Y1.x, Y1Squared); + + uint[] T = Nat192.Create(); + SecP192R1Field.Square(Y1Squared, T); + + bool Z1IsOne = Z1.IsOne; + + uint[] Z1Squared = Z1.x; + if (!Z1IsOne) + { + Z1Squared = t2; + SecP192R1Field.Square(Z1.x, Z1Squared); + } + + SecP192R1Field.Subtract(X1.x, Z1Squared, t1); + + uint[] M = t2; + SecP192R1Field.Add(X1.x, Z1Squared, M); + SecP192R1Field.Multiply(M, t1, M); + c = Nat192.AddBothTo(M, M, M); + SecP192R1Field.Reduce32(c, M); + + uint[] S = Y1Squared; + SecP192R1Field.Multiply(Y1Squared, X1.x, S); + c = Nat.ShiftUpBits(6, S, 2, 0); + SecP192R1Field.Reduce32(c, S); + + c = Nat.ShiftUpBits(6, T, 3, 0, t1); + SecP192R1Field.Reduce32(c, t1); + + SecP192R1FieldElement X3 = new SecP192R1FieldElement(T); + SecP192R1Field.Square(M, X3.x); + SecP192R1Field.Subtract(X3.x, S, X3.x); + SecP192R1Field.Subtract(X3.x, S, X3.x); + + SecP192R1FieldElement Y3 = new SecP192R1FieldElement(S); + SecP192R1Field.Subtract(S, X3.x, Y3.x); + SecP192R1Field.Multiply(Y3.x, M, Y3.x); + SecP192R1Field.Subtract(Y3.x, t1, Y3.x); + + SecP192R1FieldElement Z3 = new SecP192R1FieldElement(M); + SecP192R1Field.Twice(Y1.x, Z3.x); + if (!Z1IsOne) + { + SecP192R1Field.Multiply(Z3.x, Z1.x, Z3.x); + } + + return new SecP192R1Point(curve, X3, Y3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint TwicePlus(ECPoint b) + { + if (this == b) + return ThreeTimes(); + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return Twice(); + + ECFieldElement Y1 = this.RawYCoord; + if (Y1.IsZero) + return b; + + return Twice().Add(b); + } + + public override ECPoint ThreeTimes() + { + if (this.IsInfinity || this.RawYCoord.IsZero) + return this; + + // NOTE: Be careful about recursions between TwicePlus and ThreeTimes + return Twice().Add(this); + } + + public override ECPoint Negate() + { + if (IsInfinity) + return this; + + return new SecP192R1Point(Curve, RawXCoord, RawYCoord.Negate(), RawZCoords, IsCompressed); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecP224K1Curve.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecP224K1Curve.cs new file mode 100644 index 0000000..d4be7d8 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecP224K1Curve.cs @@ -0,0 +1,75 @@ +using System; + +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecP224K1Curve + : AbstractFpCurve + { + public static readonly BigInteger q = new BigInteger(1, + Hex.Decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFE56D")); + + private const int SECP224K1_DEFAULT_COORDS = COORD_JACOBIAN; + + protected readonly SecP224K1Point m_infinity; + + public SecP224K1Curve() + : base(q) + { + this.m_infinity = new SecP224K1Point(this, null, null); + + this.m_a = FromBigInteger(BigInteger.Zero); + this.m_b = FromBigInteger(BigInteger.ValueOf(5)); + this.m_order = new BigInteger(1, Hex.Decode("010000000000000000000000000001DCE8D2EC6184CAF0A971769FB1F7")); + this.m_cofactor = BigInteger.One; + this.m_coord = SECP224K1_DEFAULT_COORDS; + } + + protected override ECCurve CloneCurve() + { + return new SecP224K1Curve(); + } + + public override bool SupportsCoordinateSystem(int coord) + { + switch (coord) + { + case COORD_JACOBIAN: + return true; + default: + return false; + } + } + + public virtual BigInteger Q + { + get { return q; } + } + + public override ECPoint Infinity + { + get { return m_infinity; } + } + + public override int FieldSize + { + get { return q.BitLength; } + } + + public override ECFieldElement FromBigInteger(BigInteger x) + { + return new SecP224K1FieldElement(x); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, bool withCompression) + { + return new SecP224K1Point(this, x, y, withCompression); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + { + return new SecP224K1Point(this, x, y, zs, withCompression); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecP224K1Field.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecP224K1Field.cs new file mode 100644 index 0000000..98cf777 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecP224K1Field.cs @@ -0,0 +1,179 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Math.Raw; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecP224K1Field + { + // 2^224 - 2^32 - 2^12 - 2^11 - 2^9 - 2^7 - 2^4 - 2 - 1 + internal static readonly uint[] P = new uint[]{ 0xFFFFE56D, 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF }; + internal static readonly uint[] PExt = new uint[]{ 0x02C23069, 0x00003526, 0x00000001, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xFFFFCADA, 0xFFFFFFFD, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }; + private static readonly uint[] PExtInv = new uint[]{ 0xFD3DCF97, 0xFFFFCAD9, 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0x00003525, 0x00000002 }; + private const uint P6 = 0xFFFFFFFF; + private const uint PExt13 = 0xFFFFFFFF; + private const uint PInv33 = 0x1A93; + + public static void Add(uint[] x, uint[] y, uint[] z) + { + uint c = Nat224.Add(x, y, z); + if (c != 0 || (z[6] == P6 && Nat224.Gte(z, P))) + { + Nat.Add33To(7, PInv33, z); + } + } + + public static void AddExt(uint[] xx, uint[] yy, uint[] zz) + { + uint c = Nat.Add(14, xx, yy, zz); + if (c != 0 || (zz[13] == PExt13 && Nat.Gte(14, zz, PExt))) + { + if (Nat.AddTo(PExtInv.Length, PExtInv, zz) != 0) + { + Nat.IncAt(14, zz, PExtInv.Length); + } + } + } + + public static void AddOne(uint[] x, uint[] z) + { + uint c = Nat.Inc(7, x, z); + if (c != 0 || (z[6] == P6 && Nat224.Gte(z, P))) + { + Nat.Add33To(7, PInv33, z); + } + } + + public static uint[] FromBigInteger(BigInteger x) + { + uint[] z = Nat224.FromBigInteger(x); + if (z[6] == P6 && Nat224.Gte(z, P)) + { + Nat224.SubFrom(P, z); + } + return z; + } + + public static void Half(uint[] x, uint[] z) + { + if ((x[0] & 1) == 0) + { + Nat.ShiftDownBit(7, x, 0, z); + } + else + { + uint c = Nat224.Add(x, P, z); + Nat.ShiftDownBit(7, z, c); + } + } + + public static void Multiply(uint[] x, uint[] y, uint[] z) + { + uint[] tt = Nat224.CreateExt(); + Nat224.Mul(x, y, tt); + Reduce(tt, z); + } + + public static void MultiplyAddToExt(uint[] x, uint[] y, uint[] zz) + { + uint c = Nat224.MulAddTo(x, y, zz); + if (c != 0 || (zz[13] == PExt13 && Nat.Gte(14, zz, PExt))) + { + if (Nat.AddTo(PExtInv.Length, PExtInv, zz) != 0) + { + Nat.IncAt(14, zz, PExtInv.Length); + } + } + } + + public static void Negate(uint[] x, uint[] z) + { + if (Nat224.IsZero(x)) + { + Nat224.Zero(z); + } + else + { + Nat224.Sub(P, x, z); + } + } + + public static void Reduce(uint[] xx, uint[] z) + { + ulong cc = Nat224.Mul33Add(PInv33, xx, 7, xx, 0, z, 0); + uint c = Nat224.Mul33DWordAdd(PInv33, cc, z, 0); + + Debug.Assert(c == 0 || c == 1); + + if (c != 0 || (z[6] == P6 && Nat224.Gte(z, P))) + { + Nat.Add33To(7, PInv33, z); + } + } + + public static void Reduce32(uint x, uint[] z) + { + if ((x != 0 && Nat224.Mul33WordAdd(PInv33, x, z, 0) != 0) + || (z[6] == P6 && Nat224.Gte(z, P))) + { + Nat.Add33To(7, PInv33, z); + } + } + + public static void Square(uint[] x, uint[] z) + { + uint[] tt = Nat224.CreateExt(); + Nat224.Square(x, tt); + Reduce(tt, z); + } + + public static void SquareN(uint[] x, int n, uint[] z) + { + Debug.Assert(n > 0); + + uint[] tt = Nat224.CreateExt(); + Nat224.Square(x, tt); + Reduce(tt, z); + + while (--n > 0) + { + Nat224.Square(z, tt); + Reduce(tt, z); + } + } + + public static void Subtract(uint[] x, uint[] y, uint[] z) + { + int c = Nat224.Sub(x, y, z); + if (c != 0) + { + Nat.Sub33From(7, PInv33, z); + } + } + + public static void SubtractExt(uint[] xx, uint[] yy, uint[] zz) + { + int c = Nat.Sub(14, xx, yy, zz); + if (c != 0) + { + if (Nat.SubFrom(PExtInv.Length, PExtInv, zz) != 0) + { + Nat.DecAt(14, zz, PExtInv.Length); + } + } + } + + public static void Twice(uint[] x, uint[] z) + { + uint c = Nat.ShiftUpBit(7, x, 0, z); + if (c != 0 || (z[6] == P6 && Nat224.Gte(z, P))) + { + Nat.Add33To(7, PInv33, z); + } + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecP224K1FieldElement.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecP224K1FieldElement.cs new file mode 100644 index 0000000..fec0743 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecP224K1FieldElement.cs @@ -0,0 +1,242 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Math.Raw; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecP224K1FieldElement + : ECFieldElement + { + public static readonly BigInteger Q = SecP224K1Curve.q; + + // Calculated as BigInteger.Two.ModPow(Q.ShiftRight(2), Q) + private static readonly uint[] PRECOMP_POW2 = new uint[]{ 0x33bfd202, 0xdcfad133, 0x2287624a, 0xc3811ba8, + 0xa85558fc, 0x1eaef5d7, 0x8edf154c }; + + protected internal readonly uint[] x; + + public SecP224K1FieldElement(BigInteger x) + { + if (x == null || x.SignValue < 0 || x.CompareTo(Q) >= 0) + throw new ArgumentException("value invalid for SecP224K1FieldElement", "x"); + + this.x = SecP224K1Field.FromBigInteger(x); + } + + public SecP224K1FieldElement() + { + this.x = Nat224.Create(); + } + + protected internal SecP224K1FieldElement(uint[] x) + { + this.x = x; + } + + public override bool IsZero + { + get { return Nat224.IsZero(x); } + } + + public override bool IsOne + { + get { return Nat224.IsOne(x); } + } + + public override bool TestBitZero() + { + return Nat224.GetBit(x, 0) == 1; + } + + public override BigInteger ToBigInteger() + { + return Nat224.ToBigInteger(x); + } + + public override string FieldName + { + get { return "SecP224K1Field"; } + } + + public override int FieldSize + { + get { return Q.BitLength; } + } + + public override ECFieldElement Add(ECFieldElement b) + { + uint[] z = Nat224.Create(); + SecP224K1Field.Add(x, ((SecP224K1FieldElement)b).x, z); + return new SecP224K1FieldElement(z); + } + + public override ECFieldElement AddOne() + { + uint[] z = Nat224.Create(); + SecP224K1Field.AddOne(x, z); + return new SecP224K1FieldElement(z); + } + + public override ECFieldElement Subtract(ECFieldElement b) + { + uint[] z = Nat224.Create(); + SecP224K1Field.Subtract(x, ((SecP224K1FieldElement)b).x, z); + return new SecP224K1FieldElement(z); + } + + public override ECFieldElement Multiply(ECFieldElement b) + { + uint[] z = Nat224.Create(); + SecP224K1Field.Multiply(x, ((SecP224K1FieldElement)b).x, z); + return new SecP224K1FieldElement(z); + } + + public override ECFieldElement Divide(ECFieldElement b) + { + //return Multiply(b.Invert()); + uint[] z = Nat224.Create(); + Mod.Invert(SecP224K1Field.P, ((SecP224K1FieldElement)b).x, z); + SecP224K1Field.Multiply(z, x, z); + return new SecP224K1FieldElement(z); + } + + public override ECFieldElement Negate() + { + uint[] z = Nat224.Create(); + SecP224K1Field.Negate(x, z); + return new SecP224K1FieldElement(z); + } + + public override ECFieldElement Square() + { + uint[] z = Nat224.Create(); + SecP224K1Field.Square(x, z); + return new SecP224K1FieldElement(z); + } + + public override ECFieldElement Invert() + { + //return new SecP224K1FieldElement(ToBigInteger().ModInverse(Q)); + uint[] z = Nat224.Create(); + Mod.Invert(SecP224K1Field.P, x, z); + return new SecP224K1FieldElement(z); + } + + /** + * return a sqrt root - the routine verifies that the calculation returns the right value - if + * none exists it returns null. + */ + public override ECFieldElement Sqrt() + { + /* + * Q == 8m + 5, so we use Pocklington's method for this case. + * + * First, raise this element to the exponent 2^221 - 2^29 - 2^9 - 2^8 - 2^6 - 2^4 - 2^1 (i.e. m + 1) + * + * Breaking up the exponent's binary representation into "repunits", we get: + * { 191 1s } { 1 0s } { 19 1s } { 2 0s } { 1 1s } { 1 0s} { 1 1s } { 1 0s} { 3 1s } { 1 0s} + * + * Therefore we need an addition chain containing 1, 3, 19, 191 (the lengths of the repunits) + * We use: [1], 2, [3], 4, 8, 11, [19], 23, 42, 84, 107, [191] + */ + + uint[] x1 = this.x; + if (Nat224.IsZero(x1) || Nat224.IsOne(x1)) + return this; + + uint[] x2 = Nat224.Create(); + SecP224K1Field.Square(x1, x2); + SecP224K1Field.Multiply(x2, x1, x2); + uint[] x3 = x2; + SecP224K1Field.Square(x2, x3); + SecP224K1Field.Multiply(x3, x1, x3); + uint[] x4 = Nat224.Create(); + SecP224K1Field.Square(x3, x4); + SecP224K1Field.Multiply(x4, x1, x4); + uint[] x8 = Nat224.Create(); + SecP224K1Field.SquareN(x4, 4, x8); + SecP224K1Field.Multiply(x8, x4, x8); + uint[] x11 = Nat224.Create(); + SecP224K1Field.SquareN(x8, 3, x11); + SecP224K1Field.Multiply(x11, x3, x11); + uint[] x19 = x11; + SecP224K1Field.SquareN(x11, 8, x19); + SecP224K1Field.Multiply(x19, x8, x19); + uint[] x23 = x8; + SecP224K1Field.SquareN(x19, 4, x23); + SecP224K1Field.Multiply(x23, x4, x23); + uint[] x42 = x4; + SecP224K1Field.SquareN(x23, 19, x42); + SecP224K1Field.Multiply(x42, x19, x42); + uint[] x84 = Nat224.Create(); + SecP224K1Field.SquareN(x42, 42, x84); + SecP224K1Field.Multiply(x84, x42, x84); + uint[] x107 = x42; + SecP224K1Field.SquareN(x84, 23, x107); + SecP224K1Field.Multiply(x107, x23, x107); + uint[] x191 = x23; + SecP224K1Field.SquareN(x107, 84, x191); + SecP224K1Field.Multiply(x191, x84, x191); + + uint[] t1 = x191; + SecP224K1Field.SquareN(t1, 20, t1); + SecP224K1Field.Multiply(t1, x19, t1); + SecP224K1Field.SquareN(t1, 3, t1); + SecP224K1Field.Multiply(t1, x1, t1); + SecP224K1Field.SquareN(t1, 2, t1); + SecP224K1Field.Multiply(t1, x1, t1); + SecP224K1Field.SquareN(t1, 4, t1); + SecP224K1Field.Multiply(t1, x3, t1); + SecP224K1Field.Square(t1, t1); + + uint[] t2 = x84; + SecP224K1Field.Square(t1, t2); + + if (Nat224.Eq(x1, t2)) + { + return new SecP224K1FieldElement(t1); + } + + /* + * If the first guess is incorrect, we multiply by a precomputed power of 2 to get the second guess, + * which is ((4x)^(m + 1))/2 mod Q + */ + SecP224K1Field.Multiply(t1, PRECOMP_POW2, t1); + + SecP224K1Field.Square(t1, t2); + + if (Nat224.Eq(x1, t2)) + { + return new SecP224K1FieldElement(t1); + } + + return null; + } + + public override bool Equals(object obj) + { + return Equals(obj as SecP224K1FieldElement); + } + + public override bool Equals(ECFieldElement other) + { + return Equals(other as SecP224K1FieldElement); + } + + public virtual bool Equals(SecP224K1FieldElement other) + { + if (this == other) + return true; + if (null == other) + return false; + return Nat224.Eq(x, other.x); + } + + public override int GetHashCode() + { + return Q.GetHashCode() ^ Arrays.GetHashCode(x, 0, 7); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecP224K1Point.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecP224K1Point.cs new file mode 100644 index 0000000..98cb292 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecP224K1Point.cs @@ -0,0 +1,267 @@ +using System; + +using Org.BouncyCastle.Math.Raw; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecP224K1Point + : AbstractFpPoint + { + /** + * 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 + * + * @deprecated Use ECCurve.createPoint to construct points + */ + public SecP224K1Point(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 + * + * @deprecated per-point compression property will be removed, refer + * {@link #getEncoded(bool)} + */ + public SecP224K1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, bool withCompression) + : base(curve, x, y, withCompression) + { + if ((x == null) != (y == null)) + throw new ArgumentException("Exactly one of the field elements is null"); + } + + internal SecP224K1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, + bool withCompression) + : base(curve, x, y, zs, withCompression) + { + } + + protected override ECPoint Detach() + { + return new SecP224K1Point(null, AffineXCoord, AffineYCoord); + } + + public override ECPoint Add(ECPoint b) + { + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return this; + if (this == b) + return Twice(); + + ECCurve curve = this.Curve; + + SecP224K1FieldElement X1 = (SecP224K1FieldElement)this.RawXCoord, Y1 = (SecP224K1FieldElement)this.RawYCoord; + SecP224K1FieldElement X2 = (SecP224K1FieldElement)b.RawXCoord, Y2 = (SecP224K1FieldElement)b.RawYCoord; + + SecP224K1FieldElement Z1 = (SecP224K1FieldElement)this.RawZCoords[0]; + SecP224K1FieldElement Z2 = (SecP224K1FieldElement)b.RawZCoords[0]; + + uint c; + uint[] tt1 = Nat224.CreateExt(); + uint[] t2 = Nat224.Create(); + uint[] t3 = Nat224.Create(); + uint[] t4 = Nat224.Create(); + + bool Z1IsOne = Z1.IsOne; + uint[] U2, S2; + if (Z1IsOne) + { + U2 = X2.x; + S2 = Y2.x; + } + else + { + S2 = t3; + SecP224K1Field.Square(Z1.x, S2); + + U2 = t2; + SecP224K1Field.Multiply(S2, X2.x, U2); + + SecP224K1Field.Multiply(S2, Z1.x, S2); + SecP224K1Field.Multiply(S2, Y2.x, S2); + } + + bool Z2IsOne = Z2.IsOne; + uint[] U1, S1; + if (Z2IsOne) + { + U1 = X1.x; + S1 = Y1.x; + } + else + { + S1 = t4; + SecP224K1Field.Square(Z2.x, S1); + + U1 = tt1; + SecP224K1Field.Multiply(S1, X1.x, U1); + + SecP224K1Field.Multiply(S1, Z2.x, S1); + SecP224K1Field.Multiply(S1, Y1.x, S1); + } + + uint[] H = Nat224.Create(); + SecP224K1Field.Subtract(U1, U2, H); + + uint[] R = t2; + SecP224K1Field.Subtract(S1, S2, R); + + // Check if b == this or b == -this + if (Nat224.IsZero(H)) + { + if (Nat224.IsZero(R)) + { + // this == b, i.e. this must be doubled + return this.Twice(); + } + + // this == -b, i.e. the result is the point at infinity + return curve.Infinity; + } + + uint[] HSquared = t3; + SecP224K1Field.Square(H, HSquared); + + uint[] G = Nat224.Create(); + SecP224K1Field.Multiply(HSquared, H, G); + + uint[] V = t3; + SecP224K1Field.Multiply(HSquared, U1, V); + + SecP224K1Field.Negate(G, G); + Nat224.Mul(S1, G, tt1); + + c = Nat224.AddBothTo(V, V, G); + SecP224K1Field.Reduce32(c, G); + + SecP224K1FieldElement X3 = new SecP224K1FieldElement(t4); + SecP224K1Field.Square(R, X3.x); + SecP224K1Field.Subtract(X3.x, G, X3.x); + + SecP224K1FieldElement Y3 = new SecP224K1FieldElement(G); + SecP224K1Field.Subtract(V, X3.x, Y3.x); + SecP224K1Field.MultiplyAddToExt(Y3.x, R, tt1); + SecP224K1Field.Reduce(tt1, Y3.x); + + SecP224K1FieldElement Z3 = new SecP224K1FieldElement(H); + if (!Z1IsOne) + { + SecP224K1Field.Multiply(Z3.x, Z1.x, Z3.x); + } + if (!Z2IsOne) + { + SecP224K1Field.Multiply(Z3.x, Z2.x, Z3.x); + } + + ECFieldElement[] zs = new ECFieldElement[] { Z3 }; + + return new SecP224K1Point(curve, X3, Y3, zs, IsCompressed); + } + + public override ECPoint Twice() + { + if (this.IsInfinity) + return this; + + ECCurve curve = this.Curve; + + SecP224K1FieldElement Y1 = (SecP224K1FieldElement)this.RawYCoord; + if (Y1.IsZero) + return curve.Infinity; + + SecP224K1FieldElement X1 = (SecP224K1FieldElement)this.RawXCoord, Z1 = (SecP224K1FieldElement)this.RawZCoords[0]; + + uint c; + + uint[] Y1Squared = Nat224.Create(); + SecP224K1Field.Square(Y1.x, Y1Squared); + + uint[] T = Nat224.Create(); + SecP224K1Field.Square(Y1Squared, T); + + uint[] M = Nat224.Create(); + SecP224K1Field.Square(X1.x, M); + c = Nat224.AddBothTo(M, M, M); + SecP224K1Field.Reduce32(c, M); + + uint[] S = Y1Squared; + SecP224K1Field.Multiply(Y1Squared, X1.x, S); + c = Nat.ShiftUpBits(7, S, 2, 0); + SecP224K1Field.Reduce32(c, S); + + uint[] t1 = Nat224.Create(); + c = Nat.ShiftUpBits(7, T, 3, 0, t1); + SecP224K1Field.Reduce32(c, t1); + + SecP224K1FieldElement X3 = new SecP224K1FieldElement(T); + SecP224K1Field.Square(M, X3.x); + SecP224K1Field.Subtract(X3.x, S, X3.x); + SecP224K1Field.Subtract(X3.x, S, X3.x); + + SecP224K1FieldElement Y3 = new SecP224K1FieldElement(S); + SecP224K1Field.Subtract(S, X3.x, Y3.x); + SecP224K1Field.Multiply(Y3.x, M, Y3.x); + SecP224K1Field.Subtract(Y3.x, t1, Y3.x); + + SecP224K1FieldElement Z3 = new SecP224K1FieldElement(M); + SecP224K1Field.Twice(Y1.x, Z3.x); + if (!Z1.IsOne) + { + SecP224K1Field.Multiply(Z3.x, Z1.x, Z3.x); + } + + return new SecP224K1Point(curve, X3, Y3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint TwicePlus(ECPoint b) + { + if (this == b) + return ThreeTimes(); + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return Twice(); + + ECFieldElement Y1 = this.RawYCoord; + if (Y1.IsZero) + return b; + + return Twice().Add(b); + } + + public override ECPoint ThreeTimes() + { + if (this.IsInfinity || this.RawYCoord.IsZero) + return this; + + // NOTE: Be careful about recursions between TwicePlus and ThreeTimes + return Twice().Add(this); + } + + public override ECPoint Negate() + { + if (IsInfinity) + return this; + + return new SecP224K1Point(Curve, RawXCoord, RawYCoord.Negate(), RawZCoords, IsCompressed); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecP224R1Curve.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecP224R1Curve.cs new file mode 100644 index 0000000..cda8781 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecP224R1Curve.cs @@ -0,0 +1,78 @@ +using System; + +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecP224R1Curve + : AbstractFpCurve + { + public static readonly BigInteger q = new BigInteger(1, + Hex.Decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000000000000000001")); + + private const int SecP224R1_DEFAULT_COORDS = COORD_JACOBIAN; + + protected readonly SecP224R1Point m_infinity; + + public SecP224R1Curve() + : base(q) + { + this.m_infinity = new SecP224R1Point(this, null, null); + + this.m_a = FromBigInteger(new BigInteger(1, + Hex.Decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFE"))); + this.m_b = FromBigInteger(new BigInteger(1, + Hex.Decode("B4050A850C04B3ABF54132565044B0B7D7BFD8BA270B39432355FFB4"))); + this.m_order = new BigInteger(1, Hex.Decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFF16A2E0B8F03E13DD29455C5C2A3D")); + this.m_cofactor = BigInteger.One; + + this.m_coord = SecP224R1_DEFAULT_COORDS; + } + + protected override ECCurve CloneCurve() + { + return new SecP224R1Curve(); + } + + public override bool SupportsCoordinateSystem(int coord) + { + switch (coord) + { + case COORD_JACOBIAN: + return true; + default: + return false; + } + } + + public virtual BigInteger Q + { + get { return q; } + } + + public override ECPoint Infinity + { + get { return m_infinity; } + } + + public override int FieldSize + { + get { return q.BitLength; } + } + + public override ECFieldElement FromBigInteger(BigInteger x) + { + return new SecP224R1FieldElement(x); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, bool withCompression) + { + return new SecP224R1Point(this, x, y, withCompression); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + { + return new SecP224R1Point(this, x, y, zs, withCompression); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecP224R1Field.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecP224R1Field.cs new file mode 100644 index 0000000..4f5c3bb --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecP224R1Field.cs @@ -0,0 +1,297 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Math.Raw; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecP224R1Field + { + // 2^224 - 2^96 + 1 + internal static readonly uint[] P = new uint[] { 0x00000001, 0x00000000, 0x00000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }; + internal static readonly uint[] PExt = new uint[]{ 0x00000001, 0x00000000, 0x00000000, 0xFFFFFFFE, 0xFFFFFFFF, + 0xFFFFFFFF, 0x00000000, 0x00000002, 0x00000000, 0x00000000, 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }; + private static readonly uint[] PExtInv = new uint[]{ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000001, 0x00000000, + 0x00000000, 0xFFFFFFFF, 0xFFFFFFFD, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000001 }; + private const uint P6 = 0xFFFFFFFF; + private const uint PExt13 = 0xFFFFFFFF; + + public static void Add(uint[] x, uint[] y, uint[] z) + { + uint c = Nat224.Add(x, y, z); + if (c != 0 || (z[6] == P6 && Nat224.Gte(z, P))) + { + AddPInvTo(z); + } + } + + public static void AddExt(uint[] xx, uint[] yy, uint[] zz) + { + uint c = Nat.Add(14, xx, yy, zz); + if (c != 0 || (zz[13] == PExt13 && Nat.Gte(14, zz, PExt))) + { + if (Nat.AddTo(PExtInv.Length, PExtInv, zz) != 0) + { + Nat.IncAt(14, zz, PExtInv.Length); + } + } + } + + public static void AddOne(uint[] x, uint[] z) + { + uint c = Nat.Inc(7, x, z); + if (c != 0 || (z[6] == P6 && Nat224.Gte(z, P))) + { + AddPInvTo(z); + } + } + + public static uint[] FromBigInteger(BigInteger x) + { + uint[] z = Nat224.FromBigInteger(x); + if (z[6] == P6 && Nat224.Gte(z, P)) + { + Nat224.SubFrom(P, z); + } + return z; + } + + public static void Half(uint[] x, uint[] z) + { + if ((x[0] & 1) == 0) + { + Nat.ShiftDownBit(7, x, 0, z); + } + else + { + uint c = Nat224.Add(x, P, z); + Nat.ShiftDownBit(7, z, c); + } + } + + public static void Multiply(uint[] x, uint[] y, uint[] z) + { + uint[] tt = Nat224.CreateExt(); + Nat224.Mul(x, y, tt); + Reduce(tt, z); + } + + public static void MultiplyAddToExt(uint[] x, uint[] y, uint[] zz) + { + uint c = Nat224.MulAddTo(x, y, zz); + if (c != 0 || (zz[13] == PExt13 && Nat.Gte(14, zz, PExt))) + { + if (Nat.AddTo(PExtInv.Length, PExtInv, zz) != 0) + { + Nat.IncAt(14, zz, PExtInv.Length); + } + } + } + + public static void Negate(uint[] x, uint[] z) + { + if (Nat224.IsZero(x)) + { + Nat224.Zero(z); + } + else + { + Nat224.Sub(P, x, z); + } + } + + public static void Reduce(uint[] xx, uint[] z) + { + long xx10 = xx[10], xx11 = xx[11], xx12 = xx[12], xx13 = xx[13]; + + const long n = 1; + + long t0 = (long)xx[7] + xx11 - n; + long t1 = (long)xx[8] + xx12; + long t2 = (long)xx[9] + xx13; + + long cc = 0; + cc += (long)xx[0] - t0; + long z0 = (uint)cc; + cc >>= 32; + cc += (long)xx[1] - t1; + z[1] = (uint)cc; + cc >>= 32; + cc += (long)xx[2] - t2; + z[2] = (uint)cc; + cc >>= 32; + cc += (long)xx[3] + t0 - xx10; + long z3 = (uint)cc; + cc >>= 32; + cc += (long)xx[4] + t1 - xx11; + z[4] = (uint)cc; + cc >>= 32; + cc += (long)xx[5] + t2 - xx12; + z[5] = (uint)cc; + cc >>= 32; + cc += (long)xx[6] + xx10 - xx13; + z[6] = (uint)cc; + cc >>= 32; + cc += n; + + Debug.Assert(cc >= 0); + + z3 += cc; + + z0 -= cc; + z[0] = (uint)z0; + cc = z0 >> 32; + if (cc != 0) + { + cc += (long)z[1]; + z[1] = (uint)cc; + cc >>= 32; + cc += (long)z[2]; + z[2] = (uint)cc; + z3 += cc >> 32; + } + z[3] = (uint)z3; + cc = z3 >> 32; + + Debug.Assert(cc == 0 || cc == 1); + + if ((cc != 0 && Nat.IncAt(7, z, 4) != 0) + || (z[6] == P6 && Nat224.Gte(z, P))) + { + AddPInvTo(z); + } + } + + public static void Reduce32(uint x, uint[] z) + { + long cc = 0; + + if (x != 0) + { + long xx07 = x; + + cc += (long)z[0] - xx07; + z[0] = (uint)cc; + cc >>= 32; + if (cc != 0) + { + cc += (long)z[1]; + z[1] = (uint)cc; + cc >>= 32; + cc += (long)z[2]; + z[2] = (uint)cc; + cc >>= 32; + } + cc += (long)z[3] + xx07; + z[3] = (uint)cc; + cc >>= 32; + + Debug.Assert(cc == 0 || cc == 1); + } + + if ((cc != 0 && Nat.IncAt(7, z, 4) != 0) + || (z[6] == P6 && Nat224.Gte(z, P))) + { + AddPInvTo(z); + } + } + + public static void Square(uint[] x, uint[] z) + { + uint[] tt = Nat224.CreateExt(); + Nat224.Square(x, tt); + Reduce(tt, z); + } + + public static void SquareN(uint[] x, int n, uint[] z) + { + Debug.Assert(n > 0); + + uint[] tt = Nat224.CreateExt(); + Nat224.Square(x, tt); + Reduce(tt, z); + + while (--n > 0) + { + Nat224.Square(z, tt); + Reduce(tt, z); + } + } + + public static void Subtract(uint[] x, uint[] y, uint[] z) + { + int c = Nat224.Sub(x, y, z); + if (c != 0) + { + SubPInvFrom(z); + } + } + + public static void SubtractExt(uint[] xx, uint[] yy, uint[] zz) + { + int c = Nat.Sub(14, xx, yy, zz); + if (c != 0) + { + if (Nat.SubFrom(PExtInv.Length, PExtInv, zz) != 0) + { + Nat.DecAt(14, zz, PExtInv.Length); + } + } + } + + public static void Twice(uint[] x, uint[] z) + { + uint c = Nat.ShiftUpBit(7, x, 0, z); + if (c != 0 || (z[6] == P6 && Nat224.Gte(z, P))) + { + AddPInvTo(z); + } + } + + private static void AddPInvTo(uint[] z) + { + long c = (long)z[0] - 1; + z[0] = (uint)c; + c >>= 32; + if (c != 0) + { + c += (long)z[1]; + z[1] = (uint)c; + c >>= 32; + c += (long)z[2]; + z[2] = (uint)c; + c >>= 32; + } + c += (long)z[3] + 1; + z[3] = (uint)c; + c >>= 32; + if (c != 0) + { + Nat.IncAt(7, z, 4); + } + } + + private static void SubPInvFrom(uint[] z) + { + long c = (long)z[0] + 1; + z[0] = (uint)c; + c >>= 32; + if (c != 0) + { + c += (long)z[1]; + z[1] = (uint)c; + c >>= 32; + c += (long)z[2]; + z[2] = (uint)c; + c >>= 32; + } + c += (long)z[3] - 1; + z[3] = (uint)c; + c >>= 32; + if (c != 0) + { + Nat.DecAt(7, z, 4); + } + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecP224R1FieldElement.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecP224R1FieldElement.cs new file mode 100644 index 0000000..2b9a065 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecP224R1FieldElement.cs @@ -0,0 +1,269 @@ +using System; + +using Org.BouncyCastle.Math.Raw; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecP224R1FieldElement + : ECFieldElement + { + public static readonly BigInteger Q = SecP224R1Curve.q; + + protected internal readonly uint[] x; + + public SecP224R1FieldElement(BigInteger x) + { + if (x == null || x.SignValue < 0 || x.CompareTo(Q) >= 0) + throw new ArgumentException("value invalid for SecP224R1FieldElement", "x"); + + this.x = SecP224R1Field.FromBigInteger(x); + } + + public SecP224R1FieldElement() + { + this.x = Nat224.Create(); + } + + protected internal SecP224R1FieldElement(uint[] x) + { + this.x = x; + } + + public override bool IsZero + { + get { return Nat224.IsZero(x); } + } + + public override bool IsOne + { + get { return Nat224.IsOne(x); } + } + + public override bool TestBitZero() + { + return Nat224.GetBit(x, 0) == 1; + } + + public override BigInteger ToBigInteger() + { + return Nat224.ToBigInteger(x); + } + + public override string FieldName + { + get { return "SecP224R1Field"; } + } + + public override int FieldSize + { + get { return Q.BitLength; } + } + + public override ECFieldElement Add(ECFieldElement b) + { + uint[] z = Nat224.Create(); + SecP224R1Field.Add(x, ((SecP224R1FieldElement)b).x, z); + return new SecP224R1FieldElement(z); + } + + public override ECFieldElement AddOne() + { + uint[] z = Nat224.Create(); + SecP224R1Field.AddOne(x, z); + return new SecP224R1FieldElement(z); + } + + public override ECFieldElement Subtract(ECFieldElement b) + { + uint[] z = Nat224.Create(); + SecP224R1Field.Subtract(x, ((SecP224R1FieldElement)b).x, z); + return new SecP224R1FieldElement(z); + } + + public override ECFieldElement Multiply(ECFieldElement b) + { + uint[] z = Nat224.Create(); + SecP224R1Field.Multiply(x, ((SecP224R1FieldElement)b).x, z); + return new SecP224R1FieldElement(z); + } + + public override ECFieldElement Divide(ECFieldElement b) + { + //return Multiply(b.Invert()); + uint[] z = Nat224.Create(); + Mod.Invert(SecP224R1Field.P, ((SecP224R1FieldElement)b).x, z); + SecP224R1Field.Multiply(z, x, z); + return new SecP224R1FieldElement(z); + } + + public override ECFieldElement Negate() + { + uint[] z = Nat224.Create(); + SecP224R1Field.Negate(x, z); + return new SecP224R1FieldElement(z); + } + + public override ECFieldElement Square() + { + uint[] z = Nat224.Create(); + SecP224R1Field.Square(x, z); + return new SecP224R1FieldElement(z); + } + + public override ECFieldElement Invert() + { + //return new SecP224R1FieldElement(ToBigInteger().ModInverse(Q)); + uint[] z = Nat224.Create(); + Mod.Invert(SecP224R1Field.P, x, z); + return new SecP224R1FieldElement(z); + } + + /** + * return a sqrt root - the routine verifies that the calculation returns the right value - if + * none exists it returns null. + */ + public override ECFieldElement Sqrt() + { + uint[] c = this.x; + if (Nat224.IsZero(c) || Nat224.IsOne(c)) + return this; + + uint[] nc = Nat224.Create(); + SecP224R1Field.Negate(c, nc); + + uint[] r = Mod.Random(SecP224R1Field.P); + uint[] t = Nat224.Create(); + + if (!IsSquare(c)) + return null; + + while (!TrySqrt(nc, r, t)) + { + SecP224R1Field.AddOne(r, r); + } + + SecP224R1Field.Square(t, r); + + return Nat224.Eq(c, r) ? new SecP224R1FieldElement(t) : null; + } + + public override bool Equals(object obj) + { + return Equals(obj as SecP224R1FieldElement); + } + + public override bool Equals(ECFieldElement other) + { + return Equals(other as SecP224R1FieldElement); + } + + public virtual bool Equals(SecP224R1FieldElement other) + { + if (this == other) + return true; + if (null == other) + return false; + return Nat224.Eq(x, other.x); + } + + public override int GetHashCode() + { + return Q.GetHashCode() ^ Arrays.GetHashCode(x, 0, 7); + } + + private static bool IsSquare(uint[] x) + { + uint[] t1 = Nat224.Create(); + uint[] t2 = Nat224.Create(); + Nat224.Copy(x, t1); + + for (int i = 0; i < 7; ++i) + { + Nat224.Copy(t1, t2); + SecP224R1Field.SquareN(t1, 1 << i, t1); + SecP224R1Field.Multiply(t1, t2, t1); + } + + SecP224R1Field.SquareN(t1, 95, t1); + return Nat224.IsOne(t1); + } + + private static void RM(uint[] nc, uint[] d0, uint[] e0, uint[] d1, uint[] e1, uint[] f1, uint[] t) + { + SecP224R1Field.Multiply(e1, e0, t); + SecP224R1Field.Multiply(t, nc, t); + SecP224R1Field.Multiply(d1, d0, f1); + SecP224R1Field.Add(f1, t, f1); + SecP224R1Field.Multiply(d1, e0, t); + Nat224.Copy(f1, d1); + SecP224R1Field.Multiply(e1, d0, e1); + SecP224R1Field.Add(e1, t, e1); + SecP224R1Field.Square(e1, f1); + SecP224R1Field.Multiply(f1, nc, f1); + } + + private static void RP(uint[] nc, uint[] d1, uint[] e1, uint[] f1, uint[] t) + { + Nat224.Copy(nc, f1); + + uint[] d0 = Nat224.Create(); + uint[] e0 = Nat224.Create(); + + for (int i = 0; i < 7; ++i) + { + Nat224.Copy(d1, d0); + Nat224.Copy(e1, e0); + + int j = 1 << i; + while (--j >= 0) + { + RS(d1, e1, f1, t); + } + + RM(nc, d0, e0, d1, e1, f1, t); + } + } + + private static void RS(uint[] d, uint[] e, uint[] f, uint[] t) + { + SecP224R1Field.Multiply(e, d, e); + SecP224R1Field.Twice(e, e); + SecP224R1Field.Square(d, t); + SecP224R1Field.Add(f, t, d); + SecP224R1Field.Multiply(f, t, f); + uint c = Nat.ShiftUpBits(7, f, 2, 0); + SecP224R1Field.Reduce32(c, f); + } + + private static bool TrySqrt(uint[] nc, uint[] r, uint[] t) + { + uint[] d1 = Nat224.Create(); + Nat224.Copy(r, d1); + uint[] e1 = Nat224.Create(); + e1[0] = 1; + uint[] f1 = Nat224.Create(); + RP(nc, d1, e1, f1, t); + + uint[] d0 = Nat224.Create(); + uint[] e0 = Nat224.Create(); + + for (int k = 1; k < 96; ++k) + { + Nat224.Copy(d1, d0); + Nat224.Copy(e1, e0); + + RS(d1, e1, f1, t); + + if (Nat224.IsZero(d1)) + { + Mod.Invert(SecP224R1Field.P, e0, t); + SecP224R1Field.Multiply(t, d0, t); + return true; + } + } + + return false; + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecP224R1Point.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecP224R1Point.cs new file mode 100644 index 0000000..73c4f19 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecP224R1Point.cs @@ -0,0 +1,279 @@ +using System; + +using Org.BouncyCastle.Math.Raw; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecP224R1Point + : AbstractFpPoint + { + /** + * 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 + * + * @deprecated Use ECCurve.createPoint to construct points + */ + public SecP224R1Point(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 + * + * @deprecated per-point compression property will be removed, refer + * {@link #getEncoded(bool)} + */ + public SecP224R1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, bool withCompression) + : base(curve, x, y, withCompression) + { + if ((x == null) != (y == null)) + throw new ArgumentException("Exactly one of the field elements is null"); + } + + internal SecP224R1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + : base(curve, x, y, zs, withCompression) + { + } + + protected override ECPoint Detach() + { + return new SecP224R1Point(null, AffineXCoord, AffineYCoord); + } + + public override ECPoint Add(ECPoint b) + { + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return this; + if (this == b) + return Twice(); + + ECCurve curve = this.Curve; + + SecP224R1FieldElement X1 = (SecP224R1FieldElement)this.RawXCoord, Y1 = (SecP224R1FieldElement)this.RawYCoord; + SecP224R1FieldElement X2 = (SecP224R1FieldElement)b.RawXCoord, Y2 = (SecP224R1FieldElement)b.RawYCoord; + + SecP224R1FieldElement Z1 = (SecP224R1FieldElement)this.RawZCoords[0]; + SecP224R1FieldElement Z2 = (SecP224R1FieldElement)b.RawZCoords[0]; + + uint c; + uint[] tt1 = Nat224.CreateExt(); + uint[] t2 = Nat224.Create(); + uint[] t3 = Nat224.Create(); + uint[] t4 = Nat224.Create(); + + bool Z1IsOne = Z1.IsOne; + uint[] U2, S2; + if (Z1IsOne) + { + U2 = X2.x; + S2 = Y2.x; + } + else + { + S2 = t3; + SecP224R1Field.Square(Z1.x, S2); + + U2 = t2; + SecP224R1Field.Multiply(S2, X2.x, U2); + + SecP224R1Field.Multiply(S2, Z1.x, S2); + SecP224R1Field.Multiply(S2, Y2.x, S2); + } + + bool Z2IsOne = Z2.IsOne; + uint[] U1, S1; + if (Z2IsOne) + { + U1 = X1.x; + S1 = Y1.x; + } + else + { + S1 = t4; + SecP224R1Field.Square(Z2.x, S1); + + U1 = tt1; + SecP224R1Field.Multiply(S1, X1.x, U1); + + SecP224R1Field.Multiply(S1, Z2.x, S1); + SecP224R1Field.Multiply(S1, Y1.x, S1); + } + + uint[] H = Nat224.Create(); + SecP224R1Field.Subtract(U1, U2, H); + + uint[] R = t2; + SecP224R1Field.Subtract(S1, S2, R); + + // Check if b == this or b == -this + if (Nat224.IsZero(H)) + { + if (Nat224.IsZero(R)) + { + // this == b, i.e. this must be doubled + return this.Twice(); + } + + // this == -b, i.e. the result is the point at infinity + return curve.Infinity; + } + + uint[] HSquared = t3; + SecP224R1Field.Square(H, HSquared); + + uint[] G = Nat224.Create(); + SecP224R1Field.Multiply(HSquared, H, G); + + uint[] V = t3; + SecP224R1Field.Multiply(HSquared, U1, V); + + SecP224R1Field.Negate(G, G); + Nat224.Mul(S1, G, tt1); + + c = Nat224.AddBothTo(V, V, G); + SecP224R1Field.Reduce32(c, G); + + SecP224R1FieldElement X3 = new SecP224R1FieldElement(t4); + SecP224R1Field.Square(R, X3.x); + SecP224R1Field.Subtract(X3.x, G, X3.x); + + SecP224R1FieldElement Y3 = new SecP224R1FieldElement(G); + SecP224R1Field.Subtract(V, X3.x, Y3.x); + SecP224R1Field.MultiplyAddToExt(Y3.x, R, tt1); + SecP224R1Field.Reduce(tt1, Y3.x); + + SecP224R1FieldElement Z3 = new SecP224R1FieldElement(H); + if (!Z1IsOne) + { + SecP224R1Field.Multiply(Z3.x, Z1.x, Z3.x); + } + if (!Z2IsOne) + { + SecP224R1Field.Multiply(Z3.x, Z2.x, Z3.x); + } + + ECFieldElement[] zs = new ECFieldElement[] { Z3 }; + + return new SecP224R1Point(curve, X3, Y3, zs, IsCompressed); + } + + public override ECPoint Twice() + { + if (this.IsInfinity) + return this; + + ECCurve curve = this.Curve; + + SecP224R1FieldElement Y1 = (SecP224R1FieldElement)this.RawYCoord; + if (Y1.IsZero) + return curve.Infinity; + + SecP224R1FieldElement X1 = (SecP224R1FieldElement)this.RawXCoord, Z1 = (SecP224R1FieldElement)this.RawZCoords[0]; + + uint c; + uint[] t1 = Nat224.Create(); + uint[] t2 = Nat224.Create(); + + uint[] Y1Squared = Nat224.Create(); + SecP224R1Field.Square(Y1.x, Y1Squared); + + uint[] T = Nat224.Create(); + SecP224R1Field.Square(Y1Squared, T); + + bool Z1IsOne = Z1.IsOne; + + uint[] Z1Squared = Z1.x; + if (!Z1IsOne) + { + Z1Squared = t2; + SecP224R1Field.Square(Z1.x, Z1Squared); + } + + SecP224R1Field.Subtract(X1.x, Z1Squared, t1); + + uint[] M = t2; + SecP224R1Field.Add(X1.x, Z1Squared, M); + SecP224R1Field.Multiply(M, t1, M); + c = Nat224.AddBothTo(M, M, M); + SecP224R1Field.Reduce32(c, M); + + uint[] S = Y1Squared; + SecP224R1Field.Multiply(Y1Squared, X1.x, S); + c = Nat.ShiftUpBits(7, S, 2, 0); + SecP224R1Field.Reduce32(c, S); + + c = Nat.ShiftUpBits(7, T, 3, 0, t1); + SecP224R1Field.Reduce32(c, t1); + + SecP224R1FieldElement X3 = new SecP224R1FieldElement(T); + SecP224R1Field.Square(M, X3.x); + SecP224R1Field.Subtract(X3.x, S, X3.x); + SecP224R1Field.Subtract(X3.x, S, X3.x); + + SecP224R1FieldElement Y3 = new SecP224R1FieldElement(S); + SecP224R1Field.Subtract(S, X3.x, Y3.x); + SecP224R1Field.Multiply(Y3.x, M, Y3.x); + SecP224R1Field.Subtract(Y3.x, t1, Y3.x); + + SecP224R1FieldElement Z3 = new SecP224R1FieldElement(M); + SecP224R1Field.Twice(Y1.x, Z3.x); + if (!Z1IsOne) + { + SecP224R1Field.Multiply(Z3.x, Z1.x, Z3.x); + } + + return new SecP224R1Point(curve, X3, Y3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint TwicePlus(ECPoint b) + { + if (this == b) + return ThreeTimes(); + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return Twice(); + + ECFieldElement Y1 = this.RawYCoord; + if (Y1.IsZero) + return b; + + return Twice().Add(b); + } + + public override ECPoint ThreeTimes() + { + if (this.IsInfinity || this.RawYCoord.IsZero) + return this; + + // NOTE: Be careful about recursions between TwicePlus and ThreeTimes + return Twice().Add(this); + } + + public override ECPoint Negate() + { + if (IsInfinity) + return this; + + return new SecP224R1Point(Curve, RawXCoord, RawYCoord.Negate(), RawZCoords, IsCompressed); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecP256K1Curve.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecP256K1Curve.cs new file mode 100644 index 0000000..59e2cef --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecP256K1Curve.cs @@ -0,0 +1,75 @@ +using System; + +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecP256K1Curve + : AbstractFpCurve + { + public static readonly BigInteger q = new BigInteger(1, + Hex.Decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F")); + + private const int SECP256K1_DEFAULT_COORDS = COORD_JACOBIAN; + + protected readonly SecP256K1Point m_infinity; + + public SecP256K1Curve() + : base(q) + { + this.m_infinity = new SecP256K1Point(this, null, null); + + this.m_a = FromBigInteger(BigInteger.Zero); + this.m_b = FromBigInteger(BigInteger.ValueOf(7)); + this.m_order = new BigInteger(1, Hex.Decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141")); + this.m_cofactor = BigInteger.One; + this.m_coord = SECP256K1_DEFAULT_COORDS; + } + + protected override ECCurve CloneCurve() + { + return new SecP256K1Curve(); + } + + public override bool SupportsCoordinateSystem(int coord) + { + switch (coord) + { + case COORD_JACOBIAN: + return true; + default: + return false; + } + } + + public virtual BigInteger Q + { + get { return q; } + } + + public override ECPoint Infinity + { + get { return m_infinity; } + } + + public override int FieldSize + { + get { return q.BitLength; } + } + + public override ECFieldElement FromBigInteger(BigInteger x) + { + return new SecP256K1FieldElement(x); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, bool withCompression) + { + return new SecP256K1Point(this, x, y, withCompression); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + { + return new SecP256K1Point(this, x, y, zs, withCompression); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecP256K1Field.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecP256K1Field.cs new file mode 100644 index 0000000..b0646e9 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecP256K1Field.cs @@ -0,0 +1,180 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Math.Raw; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecP256K1Field + { + // 2^256 - 2^32 - 2^9 - 2^8 - 2^7 - 2^6 - 2^4 - 1 + internal static readonly uint[] P = new uint[]{ 0xFFFFFC2F, 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF }; + internal static readonly uint[] PExt = new uint[]{ 0x000E90A1, 0x000007A2, 0x00000001, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0xFFFFF85E, 0xFFFFFFFD, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF }; + private static readonly uint[] PExtInv = new uint[]{ 0xFFF16F5F, 0xFFFFF85D, 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x000007A1, 0x00000002 }; + private const uint P7 = 0xFFFFFFFF; + private const uint PExt15 = 0xFFFFFFFF; + private const uint PInv33 = 0x3D1; + + public static void Add(uint[] x, uint[] y, uint[] z) + { + uint c = Nat256.Add(x, y, z); + if (c != 0 || (z[7] == P7 && Nat256.Gte(z, P))) + { + Nat.Add33To(8, PInv33, z); + } + } + + public static void AddExt(uint[] xx, uint[] yy, uint[] zz) + { + uint c = Nat.Add(16, xx, yy, zz); + if (c != 0 || (zz[15] == PExt15 && Nat.Gte(16, zz, PExt))) + { + if (Nat.AddTo(PExtInv.Length, PExtInv, zz) != 0) + { + Nat.IncAt(16, zz, PExtInv.Length); + } + } + } + + public static void AddOne(uint[] x, uint[] z) + { + uint c = Nat.Inc(8, x, z); + if (c != 0 || (z[7] == P7 && Nat256.Gte(z, P))) + { + Nat.Add33To(8, PInv33, z); + } + } + + public static uint[] FromBigInteger(BigInteger x) + { + uint[] z = Nat256.FromBigInteger(x); + if (z[7] == P7 && Nat256.Gte(z, P)) + { + Nat256.SubFrom(P, z); + } + return z; + } + + public static void Half(uint[] x, uint[] z) + { + if ((x[0] & 1) == 0) + { + Nat.ShiftDownBit(8, x, 0, z); + } + else + { + uint c = Nat256.Add(x, P, z); + Nat.ShiftDownBit(8, z, c); + } + } + + public static void Multiply(uint[] x, uint[] y, uint[] z) + { + uint[] tt = Nat256.CreateExt(); + Nat256.Mul(x, y, tt); + Reduce(tt, z); + } + + public static void MultiplyAddToExt(uint[] x, uint[] y, uint[] zz) + { + uint c = Nat256.MulAddTo(x, y, zz); + if (c != 0 || (zz[15] == PExt15 && Nat.Gte(16, zz, PExt))) + { + if (Nat.AddTo(PExtInv.Length, PExtInv, zz) != 0) + { + Nat.IncAt(16, zz, PExtInv.Length); + } + } + } + + public static void Negate(uint[] x, uint[] z) + { + if (Nat256.IsZero(x)) + { + Nat256.Zero(z); + } + else + { + Nat256.Sub(P, x, z); + } + } + + public static void Reduce(uint[] xx, uint[] z) + { + ulong cc = Nat256.Mul33Add(PInv33, xx, 8, xx, 0, z, 0); + uint c = Nat256.Mul33DWordAdd(PInv33, cc, z, 0); + + Debug.Assert(c == 0 || c == 1); + + if (c != 0 || (z[7] == P7 && Nat256.Gte(z, P))) + { + Nat.Add33To(8, PInv33, z); + } + } + + public static void Reduce32(uint x, uint[] z) + { + if ((x != 0 && Nat256.Mul33WordAdd(PInv33, x, z, 0) != 0) + || (z[7] == P7 && Nat256.Gte(z, P))) + { + Nat.Add33To(8, PInv33, z); + } + } + + public static void Square(uint[] x, uint[] z) + { + uint[] tt = Nat256.CreateExt(); + Nat256.Square(x, tt); + Reduce(tt, z); + } + + public static void SquareN(uint[] x, int n, uint[] z) + { + Debug.Assert(n > 0); + + uint[] tt = Nat256.CreateExt(); + Nat256.Square(x, tt); + Reduce(tt, z); + + while (--n > 0) + { + Nat256.Square(z, tt); + Reduce(tt, z); + } + } + + public static void Subtract(uint[] x, uint[] y, uint[] z) + { + int c = Nat256.Sub(x, y, z); + if (c != 0) + { + Nat.Sub33From(8, PInv33, z); + } + } + + public static void SubtractExt(uint[] xx, uint[] yy, uint[] zz) + { + int c = Nat.Sub(16, xx, yy, zz); + if (c != 0) + { + if (Nat.SubFrom(PExtInv.Length, PExtInv, zz) != 0) + { + Nat.DecAt(16, zz, PExtInv.Length); + } + } + } + + public static void Twice(uint[] x, uint[] z) + { + uint c = Nat.ShiftUpBit(8, x, 0, z); + if (c != 0 || (z[7] == P7 && Nat256.Gte(z, P))) + { + Nat.Add33To(8, PInv33, z); + } + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecP256K1FieldElement.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecP256K1FieldElement.cs new file mode 100644 index 0000000..473113d --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecP256K1FieldElement.cs @@ -0,0 +1,214 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Math.Raw; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecP256K1FieldElement + : ECFieldElement + { + public static readonly BigInteger Q = SecP256K1Curve.q; + + protected internal readonly uint[] x; + + public SecP256K1FieldElement(BigInteger x) + { + if (x == null || x.SignValue < 0 || x.CompareTo(Q) >= 0) + throw new ArgumentException("value invalid for SecP256K1FieldElement", "x"); + + this.x = SecP256K1Field.FromBigInteger(x); + } + + public SecP256K1FieldElement() + { + this.x = Nat256.Create(); + } + + protected internal SecP256K1FieldElement(uint[] x) + { + this.x = x; + } + + public override bool IsZero + { + get { return Nat256.IsZero(x); } + } + + public override bool IsOne + { + get { return Nat256.IsOne(x); } + } + + public override bool TestBitZero() + { + return Nat256.GetBit(x, 0) == 1; + } + + public override BigInteger ToBigInteger() + { + return Nat256.ToBigInteger(x); + } + + public override string FieldName + { + get { return "SecP256K1Field"; } + } + + public override int FieldSize + { + get { return Q.BitLength; } + } + + public override ECFieldElement Add(ECFieldElement b) + { + uint[] z = Nat256.Create(); + SecP256K1Field.Add(x, ((SecP256K1FieldElement)b).x, z); + return new SecP256K1FieldElement(z); + } + + public override ECFieldElement AddOne() + { + uint[] z = Nat256.Create(); + SecP256K1Field.AddOne(x, z); + return new SecP256K1FieldElement(z); + } + + public override ECFieldElement Subtract(ECFieldElement b) + { + uint[] z = Nat256.Create(); + SecP256K1Field.Subtract(x, ((SecP256K1FieldElement)b).x, z); + return new SecP256K1FieldElement(z); + } + + public override ECFieldElement Multiply(ECFieldElement b) + { + uint[] z = Nat256.Create(); + SecP256K1Field.Multiply(x, ((SecP256K1FieldElement)b).x, z); + return new SecP256K1FieldElement(z); + } + + public override ECFieldElement Divide(ECFieldElement b) + { + //return Multiply(b.Invert()); + uint[] z = Nat256.Create(); + Mod.Invert(SecP256K1Field.P, ((SecP256K1FieldElement)b).x, z); + SecP256K1Field.Multiply(z, x, z); + return new SecP256K1FieldElement(z); + } + + public override ECFieldElement Negate() + { + uint[] z = Nat256.Create(); + SecP256K1Field.Negate(x, z); + return new SecP256K1FieldElement(z); + } + + public override ECFieldElement Square() + { + uint[] z = Nat256.Create(); + SecP256K1Field.Square(x, z); + return new SecP256K1FieldElement(z); + } + + public override ECFieldElement Invert() + { + //return new SecP256K1FieldElement(ToBigInteger().ModInverse(Q)); + uint[] z = Nat256.Create(); + Mod.Invert(SecP256K1Field.P, x, z); + return new SecP256K1FieldElement(z); + } + + /** + * return a sqrt root - the routine verifies that the calculation returns the right value - if + * none exists it returns null. + */ + public override ECFieldElement Sqrt() + { + /* + * Raise this element to the exponent 2^254 - 2^30 - 2^7 - 2^6 - 2^5 - 2^4 - 2^2 + * + * Breaking up the exponent's binary representation into "repunits", we get: + * { 223 1s } { 1 0s } { 22 1s } { 4 0s } { 2 1s } { 2 0s} + * + * Therefore we need an addition chain containing 2, 22, 223 (the lengths of the repunits) + * We use: 1, [2], 3, 6, 9, 11, [22], 44, 88, 176, 220, [223] + */ + + uint[] x1 = this.x; + if (Nat256.IsZero(x1) || Nat256.IsOne(x1)) + return this; + + uint[] x2 = Nat256.Create(); + SecP256K1Field.Square(x1, x2); + SecP256K1Field.Multiply(x2, x1, x2); + uint[] x3 = Nat256.Create(); + SecP256K1Field.Square(x2, x3); + SecP256K1Field.Multiply(x3, x1, x3); + uint[] x6 = Nat256.Create(); + SecP256K1Field.SquareN(x3, 3, x6); + SecP256K1Field.Multiply(x6, x3, x6); + uint[] x9 = x6; + SecP256K1Field.SquareN(x6, 3, x9); + SecP256K1Field.Multiply(x9, x3, x9); + uint[] x11 = x9; + SecP256K1Field.SquareN(x9, 2, x11); + SecP256K1Field.Multiply(x11, x2, x11); + uint[] x22 = Nat256.Create(); + SecP256K1Field.SquareN(x11, 11, x22); + SecP256K1Field.Multiply(x22, x11, x22); + uint[] x44 = x11; + SecP256K1Field.SquareN(x22, 22, x44); + SecP256K1Field.Multiply(x44, x22, x44); + uint[] x88 = Nat256.Create(); + SecP256K1Field.SquareN(x44, 44, x88); + SecP256K1Field.Multiply(x88, x44, x88); + uint[] x176 = Nat256.Create(); + SecP256K1Field.SquareN(x88, 88, x176); + SecP256K1Field.Multiply(x176, x88, x176); + uint[] x220 = x88; + SecP256K1Field.SquareN(x176, 44, x220); + SecP256K1Field.Multiply(x220, x44, x220); + uint[] x223 = x44; + SecP256K1Field.SquareN(x220, 3, x223); + SecP256K1Field.Multiply(x223, x3, x223); + + uint[] t1 = x223; + SecP256K1Field.SquareN(t1, 23, t1); + SecP256K1Field.Multiply(t1, x22, t1); + SecP256K1Field.SquareN(t1, 6, t1); + SecP256K1Field.Multiply(t1, x2, t1); + SecP256K1Field.SquareN(t1, 2, t1); + + uint[] t2 = x2; + SecP256K1Field.Square(t1, t2); + + return Nat256.Eq(x1, t2) ? new SecP256K1FieldElement(t1) : null; + } + + public override bool Equals(object obj) + { + return Equals(obj as SecP256K1FieldElement); + } + + public override bool Equals(ECFieldElement other) + { + return Equals(other as SecP256K1FieldElement); + } + + public virtual bool Equals(SecP256K1FieldElement other) + { + if (this == other) + return true; + if (null == other) + return false; + return Nat256.Eq(x, other.x); + } + + public override int GetHashCode() + { + return Q.GetHashCode() ^ Arrays.GetHashCode(x, 0, 8); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecP256K1Point.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecP256K1Point.cs new file mode 100644 index 0000000..072a0b9 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecP256K1Point.cs @@ -0,0 +1,267 @@ +using System; + +using Org.BouncyCastle.Math.Raw; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecP256K1Point + : AbstractFpPoint + { + /** + * 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 + * + * @deprecated Use ECCurve.createPoint to construct points + */ + public SecP256K1Point(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 + * + * @deprecated per-point compression property will be removed, refer + * {@link #getEncoded(bool)} + */ + public SecP256K1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, bool withCompression) + : base(curve, x, y, withCompression) + { + if ((x == null) != (y == null)) + throw new ArgumentException("Exactly one of the field elements is null"); + } + + internal SecP256K1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, + bool withCompression) + : base(curve, x, y, zs, withCompression) + { + } + + protected override ECPoint Detach() + { + return new SecP256K1Point(null, AffineXCoord, AffineYCoord); + } + + public override ECPoint Add(ECPoint b) + { + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return this; + if (this == b) + return Twice(); + + ECCurve curve = this.Curve; + + SecP256K1FieldElement X1 = (SecP256K1FieldElement)this.RawXCoord, Y1 = (SecP256K1FieldElement)this.RawYCoord; + SecP256K1FieldElement X2 = (SecP256K1FieldElement)b.RawXCoord, Y2 = (SecP256K1FieldElement)b.RawYCoord; + + SecP256K1FieldElement Z1 = (SecP256K1FieldElement)this.RawZCoords[0]; + SecP256K1FieldElement Z2 = (SecP256K1FieldElement)b.RawZCoords[0]; + + uint c; + uint[] tt1 = Nat256.CreateExt(); + uint[] t2 = Nat256.Create(); + uint[] t3 = Nat256.Create(); + uint[] t4 = Nat256.Create(); + + bool Z1IsOne = Z1.IsOne; + uint[] U2, S2; + if (Z1IsOne) + { + U2 = X2.x; + S2 = Y2.x; + } + else + { + S2 = t3; + SecP256K1Field.Square(Z1.x, S2); + + U2 = t2; + SecP256K1Field.Multiply(S2, X2.x, U2); + + SecP256K1Field.Multiply(S2, Z1.x, S2); + SecP256K1Field.Multiply(S2, Y2.x, S2); + } + + bool Z2IsOne = Z2.IsOne; + uint[] U1, S1; + if (Z2IsOne) + { + U1 = X1.x; + S1 = Y1.x; + } + else + { + S1 = t4; + SecP256K1Field.Square(Z2.x, S1); + + U1 = tt1; + SecP256K1Field.Multiply(S1, X1.x, U1); + + SecP256K1Field.Multiply(S1, Z2.x, S1); + SecP256K1Field.Multiply(S1, Y1.x, S1); + } + + uint[] H = Nat256.Create(); + SecP256K1Field.Subtract(U1, U2, H); + + uint[] R = t2; + SecP256K1Field.Subtract(S1, S2, R); + + // Check if b == this or b == -this + if (Nat256.IsZero(H)) + { + if (Nat256.IsZero(R)) + { + // this == b, i.e. this must be doubled + return this.Twice(); + } + + // this == -b, i.e. the result is the point at infinity + return curve.Infinity; + } + + uint[] HSquared = t3; + SecP256K1Field.Square(H, HSquared); + + uint[] G = Nat256.Create(); + SecP256K1Field.Multiply(HSquared, H, G); + + uint[] V = t3; + SecP256K1Field.Multiply(HSquared, U1, V); + + SecP256K1Field.Negate(G, G); + Nat256.Mul(S1, G, tt1); + + c = Nat256.AddBothTo(V, V, G); + SecP256K1Field.Reduce32(c, G); + + SecP256K1FieldElement X3 = new SecP256K1FieldElement(t4); + SecP256K1Field.Square(R, X3.x); + SecP256K1Field.Subtract(X3.x, G, X3.x); + + SecP256K1FieldElement Y3 = new SecP256K1FieldElement(G); + SecP256K1Field.Subtract(V, X3.x, Y3.x); + SecP256K1Field.MultiplyAddToExt(Y3.x, R, tt1); + SecP256K1Field.Reduce(tt1, Y3.x); + + SecP256K1FieldElement Z3 = new SecP256K1FieldElement(H); + if (!Z1IsOne) + { + SecP256K1Field.Multiply(Z3.x, Z1.x, Z3.x); + } + if (!Z2IsOne) + { + SecP256K1Field.Multiply(Z3.x, Z2.x, Z3.x); + } + + ECFieldElement[] zs = new ECFieldElement[] { Z3 }; + + return new SecP256K1Point(curve, X3, Y3, zs, IsCompressed); + } + + public override ECPoint Twice() + { + if (this.IsInfinity) + return this; + + ECCurve curve = this.Curve; + + SecP256K1FieldElement Y1 = (SecP256K1FieldElement)this.RawYCoord; + if (Y1.IsZero) + return curve.Infinity; + + SecP256K1FieldElement X1 = (SecP256K1FieldElement)this.RawXCoord, Z1 = (SecP256K1FieldElement)this.RawZCoords[0]; + + uint c; + + uint[] Y1Squared = Nat256.Create(); + SecP256K1Field.Square(Y1.x, Y1Squared); + + uint[] T = Nat256.Create(); + SecP256K1Field.Square(Y1Squared, T); + + uint[] M = Nat256.Create(); + SecP256K1Field.Square(X1.x, M); + c = Nat256.AddBothTo(M, M, M); + SecP256K1Field.Reduce32(c, M); + + uint[] S = Y1Squared; + SecP256K1Field.Multiply(Y1Squared, X1.x, S); + c = Nat.ShiftUpBits(8, S, 2, 0); + SecP256K1Field.Reduce32(c, S); + + uint[] t1 = Nat256.Create(); + c = Nat.ShiftUpBits(8, T, 3, 0, t1); + SecP256K1Field.Reduce32(c, t1); + + SecP256K1FieldElement X3 = new SecP256K1FieldElement(T); + SecP256K1Field.Square(M, X3.x); + SecP256K1Field.Subtract(X3.x, S, X3.x); + SecP256K1Field.Subtract(X3.x, S, X3.x); + + SecP256K1FieldElement Y3 = new SecP256K1FieldElement(S); + SecP256K1Field.Subtract(S, X3.x, Y3.x); + SecP256K1Field.Multiply(Y3.x, M, Y3.x); + SecP256K1Field.Subtract(Y3.x, t1, Y3.x); + + SecP256K1FieldElement Z3 = new SecP256K1FieldElement(M); + SecP256K1Field.Twice(Y1.x, Z3.x); + if (!Z1.IsOne) + { + SecP256K1Field.Multiply(Z3.x, Z1.x, Z3.x); + } + + return new SecP256K1Point(curve, X3, Y3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint TwicePlus(ECPoint b) + { + if (this == b) + return ThreeTimes(); + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return Twice(); + + ECFieldElement Y1 = this.RawYCoord; + if (Y1.IsZero) + return b; + + return Twice().Add(b); + } + + public override ECPoint ThreeTimes() + { + if (this.IsInfinity || this.RawYCoord.IsZero) + return this; + + // NOTE: Be careful about recursions between TwicePlus and ThreeTimes + return Twice().Add(this); + } + + public override ECPoint Negate() + { + if (IsInfinity) + return this; + + return new SecP256K1Point(Curve, RawXCoord, RawYCoord.Negate(), RawZCoords, IsCompressed); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecP256R1Curve.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecP256R1Curve.cs new file mode 100644 index 0000000..6b3448f --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecP256R1Curve.cs @@ -0,0 +1,77 @@ +using System; + +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecP256R1Curve + : AbstractFpCurve + { + public static readonly BigInteger q = new BigInteger(1, + Hex.Decode("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF")); + + private const int SecP256R1_DEFAULT_COORDS = COORD_JACOBIAN; + + protected readonly SecP256R1Point m_infinity; + + public SecP256R1Curve() + : base(q) + { + this.m_infinity = new SecP256R1Point(this, null, null); + + this.m_a = FromBigInteger(new BigInteger(1, + Hex.Decode("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC"))); + this.m_b = FromBigInteger(new BigInteger(1, + Hex.Decode("5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B"))); + this.m_order = new BigInteger(1, Hex.Decode("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551")); + this.m_cofactor = BigInteger.One; + this.m_coord = SecP256R1_DEFAULT_COORDS; + } + + protected override ECCurve CloneCurve() + { + return new SecP256R1Curve(); + } + + public override bool SupportsCoordinateSystem(int coord) + { + switch (coord) + { + case COORD_JACOBIAN: + return true; + default: + return false; + } + } + + public virtual BigInteger Q + { + get { return q; } + } + + public override ECPoint Infinity + { + get { return m_infinity; } + } + + public override int FieldSize + { + get { return q.BitLength; } + } + + public override ECFieldElement FromBigInteger(BigInteger x) + { + return new SecP256R1FieldElement(x); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, bool withCompression) + { + return new SecP256R1Point(this, x, y, withCompression); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + { + return new SecP256R1Point(this, x, y, zs, withCompression); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecP256R1Field.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecP256R1Field.cs new file mode 100644 index 0000000..5b3de6d --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecP256R1Field.cs @@ -0,0 +1,312 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Math.Raw; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecP256R1Field + { + // 2^256 - 2^224 + 2^192 + 2^96 - 1 + internal static readonly uint[] P = new uint[]{ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0x00000000, 0x00000000, + 0x00000001, 0xFFFFFFFF }; + internal static readonly uint[] PExt = new uint[]{ 0x00000001, 0x00000000, 0x00000000, 0xFFFFFFFE, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFE, 0x00000001, 0xFFFFFFFE, 0x00000001, 0xFFFFFFFE, 0x00000001, 0x00000001, 0xFFFFFFFE, + 0x00000002, 0xFFFFFFFE }; + internal const uint P7 = 0xFFFFFFFF; + internal const uint PExt15 = 0xFFFFFFFE; + + public static void Add(uint[] x, uint[] y, uint[] z) + { + uint c = Nat256.Add(x, y, z); + if (c != 0 || (z[7] == P7 && Nat256.Gte(z, P))) + { + AddPInvTo(z); + } + } + + public static void AddExt(uint[] xx, uint[] yy, uint[] zz) + { + uint c = Nat.Add(16, xx, yy, zz); + if (c != 0 || (zz[15] >= PExt15 && Nat.Gte(16, zz, PExt))) + { + Nat.SubFrom(16, PExt, zz); + } + } + + public static void AddOne(uint[] x, uint[] z) + { + uint c = Nat.Inc(8, x, z); + if (c != 0 || (z[7] == P7 && Nat256.Gte(z, P))) + { + AddPInvTo(z); + } + } + + public static uint[] FromBigInteger(BigInteger x) + { + uint[] z = Nat256.FromBigInteger(x); + if (z[7] == P7 && Nat256.Gte(z, P)) + { + Nat256.SubFrom(P, z); + } + return z; + } + + public static void Half(uint[] x, uint[] z) + { + if ((x[0] & 1) == 0) + { + Nat.ShiftDownBit(8, x, 0, z); + } + else + { + uint c = Nat256.Add(x, P, z); + Nat.ShiftDownBit(8, z, c); + } + } + + public static void Multiply(uint[] x, uint[] y, uint[] z) + { + uint[] tt = Nat256.CreateExt(); + Nat256.Mul(x, y, tt); + Reduce(tt, z); + } + + public static void MultiplyAddToExt(uint[] x, uint[] y, uint[] zz) + { + uint c = Nat256.MulAddTo(x, y, zz); + if (c != 0 || (zz[15] >= PExt15 && Nat.Gte(16, zz, PExt))) + { + Nat.SubFrom(16, PExt, zz); + } + } + + public static void Negate(uint[] x, uint[] z) + { + if (Nat256.IsZero(x)) + { + Nat256.Zero(z); + } + else + { + Nat256.Sub(P, x, z); + } + } + + public static void Reduce(uint[] xx, uint[] z) + { + long xx08 = xx[8], xx09 = xx[9], xx10 = xx[10], xx11 = xx[11]; + long xx12 = xx[12], xx13 = xx[13], xx14 = xx[14], xx15 = xx[15]; + + const long n = 6; + + xx08 -= n; + + long t0 = xx08 + xx09; + long t1 = xx09 + xx10; + long t2 = xx10 + xx11 - xx15; + long t3 = xx11 + xx12; + long t4 = xx12 + xx13; + long t5 = xx13 + xx14; + long t6 = xx14 + xx15; + long t7 = t5 - t0; + + long cc = 0; + cc += (long)xx[0] - t3 - t7; + z[0] = (uint)cc; + cc >>= 32; + cc += (long)xx[1] + t1 - t4 - t6; + z[1] = (uint)cc; + cc >>= 32; + cc += (long)xx[2] + t2 - t5; + z[2] = (uint)cc; + cc >>= 32; + cc += (long)xx[3] + (t3 << 1) + t7 - t6; + z[3] = (uint)cc; + cc >>= 32; + cc += (long)xx[4] + (t4 << 1) + xx14 - t1; + z[4] = (uint)cc; + cc >>= 32; + cc += (long)xx[5] + (t5 << 1) - t2; + z[5] = (uint)cc; + cc >>= 32; + cc += (long)xx[6] + (t6 << 1) + t7; + z[6] = (uint)cc; + cc >>= 32; + cc += (long)xx[7] + (xx15 << 1) + xx08 - t2 - t4; + z[7] = (uint)cc; + cc >>= 32; + cc += n; + + Debug.Assert(cc >= 0); + + Reduce32((uint)cc, z); + } + + public static void Reduce32(uint x, uint[] z) + { + long cc = 0; + + if (x != 0) + { + long xx08 = x; + + cc += (long)z[0] + xx08; + z[0] = (uint)cc; + cc >>= 32; + if (cc != 0) + { + cc += (long)z[1]; + z[1] = (uint)cc; + cc >>= 32; + cc += (long)z[2]; + z[2] = (uint)cc; + cc >>= 32; + } + cc += (long)z[3] - xx08; + z[3] = (uint)cc; + cc >>= 32; + if (cc != 0) + { + cc += (long)z[4]; + z[4] = (uint)cc; + cc >>= 32; + cc += (long)z[5]; + z[5] = (uint)cc; + cc >>= 32; + } + cc += (long)z[6] - xx08; + z[6] = (uint)cc; + cc >>= 32; + cc += (long)z[7] + xx08; + z[7] = (uint)cc; + cc >>= 32; + + Debug.Assert(cc == 0 || cc == 1); + } + + if (cc != 0 || (z[7] == P7 && Nat256.Gte(z, P))) + { + AddPInvTo(z); + } + } + + public static void Square(uint[] x, uint[] z) + { + uint[] tt = Nat256.CreateExt(); + Nat256.Square(x, tt); + Reduce(tt, z); + } + + public static void SquareN(uint[] x, int n, uint[] z) + { + Debug.Assert(n > 0); + + uint[] tt = Nat256.CreateExt(); + Nat256.Square(x, tt); + Reduce(tt, z); + + while (--n > 0) + { + Nat256.Square(z, tt); + Reduce(tt, z); + } + } + + public static void Subtract(uint[] x, uint[] y, uint[] z) + { + int c = Nat256.Sub(x, y, z); + if (c != 0) + { + SubPInvFrom(z); + } + } + + public static void SubtractExt(uint[] xx, uint[] yy, uint[] zz) + { + int c = Nat.Sub(16, xx, yy, zz); + if (c != 0) + { + Nat.AddTo(16, PExt, zz); + } + } + + public static void Twice(uint[] x, uint[] z) + { + uint c = Nat.ShiftUpBit(8, x, 0, z); + if (c != 0 || (z[7] == P7 && Nat256.Gte(z, P))) + { + AddPInvTo(z); + } + } + + private static void AddPInvTo(uint[] z) + { + long c = (long)z[0] + 1; + z[0] = (uint)c; + c >>= 32; + if (c != 0) + { + c += (long)z[1]; + z[1] = (uint)c; + c >>= 32; + c += (long)z[2]; + z[2] = (uint)c; + c >>= 32; + } + c += (long)z[3] - 1; + z[3] = (uint)c; + c >>= 32; + if (c != 0) + { + c += (long)z[4]; + z[4] = (uint)c; + c >>= 32; + c += (long)z[5]; + z[5] = (uint)c; + c >>= 32; + } + c += (long)z[6] - 1; + z[6] = (uint)c; + c >>= 32; + c += (long)z[7] + 1; + z[7] = (uint)c; + //c >>= 32; + } + + private static void SubPInvFrom(uint[] z) + { + long c = (long)z[0] - 1; + z[0] = (uint)c; + c >>= 32; + if (c != 0) + { + c += (long)z[1]; + z[1] = (uint)c; + c >>= 32; + c += (long)z[2]; + z[2] = (uint)c; + c >>= 32; + } + c += (long)z[3] + 1; + z[3] = (uint)c; + c >>= 32; + if (c != 0) + { + c += (long)z[4]; + z[4] = (uint)c; + c >>= 32; + c += (long)z[5]; + z[5] = (uint)c; + c >>= 32; + } + c += (long)z[6] + 1; + z[6] = (uint)c; + c >>= 32; + c += (long)z[7] - 1; + z[7] = (uint)c; + //c >>= 32; + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecP256R1FieldElement.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecP256R1FieldElement.cs new file mode 100644 index 0000000..d7838ae --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecP256R1FieldElement.cs @@ -0,0 +1,188 @@ +using System; + +using Org.BouncyCastle.Math.Raw; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecP256R1FieldElement + : ECFieldElement + { + public static readonly BigInteger Q = SecP256R1Curve.q; + + protected internal readonly uint[] x; + + public SecP256R1FieldElement(BigInteger x) + { + if (x == null || x.SignValue < 0 || x.CompareTo(Q) >= 0) + throw new ArgumentException("value invalid for SecP256R1FieldElement", "x"); + + this.x = SecP256R1Field.FromBigInteger(x); + } + + public SecP256R1FieldElement() + { + this.x = Nat256.Create(); + } + + protected internal SecP256R1FieldElement(uint[] x) + { + this.x = x; + } + + public override bool IsZero + { + get { return Nat256.IsZero(x); } + } + + public override bool IsOne + { + get { return Nat256.IsOne(x); } + } + + public override bool TestBitZero() + { + return Nat256.GetBit(x, 0) == 1; + } + + public override BigInteger ToBigInteger() + { + return Nat256.ToBigInteger(x); + } + + public override string FieldName + { + get { return "SecP256R1Field"; } + } + + public override int FieldSize + { + get { return Q.BitLength; } + } + + public override ECFieldElement Add(ECFieldElement b) + { + uint[] z = Nat256.Create(); + SecP256R1Field.Add(x, ((SecP256R1FieldElement)b).x, z); + return new SecP256R1FieldElement(z); + } + + public override ECFieldElement AddOne() + { + uint[] z = Nat256.Create(); + SecP256R1Field.AddOne(x, z); + return new SecP256R1FieldElement(z); + } + + public override ECFieldElement Subtract(ECFieldElement b) + { + uint[] z = Nat256.Create(); + SecP256R1Field.Subtract(x, ((SecP256R1FieldElement)b).x, z); + return new SecP256R1FieldElement(z); + } + + public override ECFieldElement Multiply(ECFieldElement b) + { + uint[] z = Nat256.Create(); + SecP256R1Field.Multiply(x, ((SecP256R1FieldElement)b).x, z); + return new SecP256R1FieldElement(z); + } + + public override ECFieldElement Divide(ECFieldElement b) + { + //return Multiply(b.Invert()); + uint[] z = Nat256.Create(); + Mod.Invert(SecP256R1Field.P, ((SecP256R1FieldElement)b).x, z); + SecP256R1Field.Multiply(z, x, z); + return new SecP256R1FieldElement(z); + } + + public override ECFieldElement Negate() + { + uint[] z = Nat256.Create(); + SecP256R1Field.Negate(x, z); + return new SecP256R1FieldElement(z); + } + + public override ECFieldElement Square() + { + uint[] z = Nat256.Create(); + SecP256R1Field.Square(x, z); + return new SecP256R1FieldElement(z); + } + + public override ECFieldElement Invert() + { + //return new SecP256R1FieldElement(ToBigInteger().ModInverse(Q)); + uint[] z = Nat256.Create(); + Mod.Invert(SecP256R1Field.P, x, z); + return new SecP256R1FieldElement(z); + } + + /** + * return a sqrt root - the routine verifies that the calculation returns the right value - if + * none exists it returns null. + */ + public override ECFieldElement Sqrt() + { + // Raise this element to the exponent 2^254 - 2^222 + 2^190 + 2^94 + + uint[] x1 = this.x; + if (Nat256.IsZero(x1) || Nat256.IsOne(x1)) + return this; + + uint[] t1 = Nat256.Create(); + uint[] t2 = Nat256.Create(); + + SecP256R1Field.Square(x1, t1); + SecP256R1Field.Multiply(t1, x1, t1); + + SecP256R1Field.SquareN(t1, 2, t2); + SecP256R1Field.Multiply(t2, t1, t2); + + SecP256R1Field.SquareN(t2, 4, t1); + SecP256R1Field.Multiply(t1, t2, t1); + + SecP256R1Field.SquareN(t1, 8, t2); + SecP256R1Field.Multiply(t2, t1, t2); + + SecP256R1Field.SquareN(t2, 16, t1); + SecP256R1Field.Multiply(t1, t2, t1); + + SecP256R1Field.SquareN(t1, 32, t1); + SecP256R1Field.Multiply(t1, x1, t1); + + SecP256R1Field.SquareN(t1, 96, t1); + SecP256R1Field.Multiply(t1, x1, t1); + + SecP256R1Field.SquareN(t1, 94, t1); + SecP256R1Field.Multiply(t1, t1, t2); + + return Nat256.Eq(x1, t2) ? new SecP256R1FieldElement(t1) : null; + } + + public override bool Equals(object obj) + { + return Equals(obj as SecP256R1FieldElement); + } + + public override bool Equals(ECFieldElement other) + { + return Equals(other as SecP256R1FieldElement); + } + + public virtual bool Equals(SecP256R1FieldElement other) + { + if (this == other) + return true; + if (null == other) + return false; + return Nat256.Eq(x, other.x); + } + + public override int GetHashCode() + { + return Q.GetHashCode() ^ Arrays.GetHashCode(x, 0, 8); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecP256R1Point.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecP256R1Point.cs new file mode 100644 index 0000000..8332082 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecP256R1Point.cs @@ -0,0 +1,279 @@ +using System; + +using Org.BouncyCastle.Math.Raw; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecP256R1Point + : AbstractFpPoint + { + /** + * 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 + * + * @deprecated Use ECCurve.createPoint to construct points + */ + public SecP256R1Point(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 + * + * @deprecated per-point compression property will be removed, refer + * {@link #getEncoded(bool)} + */ + public SecP256R1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, bool withCompression) + : base(curve, x, y, withCompression) + { + if ((x == null) != (y == null)) + throw new ArgumentException("Exactly one of the field elements is null"); + } + + internal SecP256R1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + : base(curve, x, y, zs, withCompression) + { + } + + protected override ECPoint Detach() + { + return new SecP256R1Point(null, AffineXCoord, AffineYCoord); + } + + public override ECPoint Add(ECPoint b) + { + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return this; + if (this == b) + return Twice(); + + ECCurve curve = this.Curve; + + SecP256R1FieldElement X1 = (SecP256R1FieldElement)this.RawXCoord, Y1 = (SecP256R1FieldElement)this.RawYCoord; + SecP256R1FieldElement X2 = (SecP256R1FieldElement)b.RawXCoord, Y2 = (SecP256R1FieldElement)b.RawYCoord; + + SecP256R1FieldElement Z1 = (SecP256R1FieldElement)this.RawZCoords[0]; + SecP256R1FieldElement Z2 = (SecP256R1FieldElement)b.RawZCoords[0]; + + uint c; + uint[] tt1 = Nat256.CreateExt(); + uint[] t2 = Nat256.Create(); + uint[] t3 = Nat256.Create(); + uint[] t4 = Nat256.Create(); + + bool Z1IsOne = Z1.IsOne; + uint[] U2, S2; + if (Z1IsOne) + { + U2 = X2.x; + S2 = Y2.x; + } + else + { + S2 = t3; + SecP256R1Field.Square(Z1.x, S2); + + U2 = t2; + SecP256R1Field.Multiply(S2, X2.x, U2); + + SecP256R1Field.Multiply(S2, Z1.x, S2); + SecP256R1Field.Multiply(S2, Y2.x, S2); + } + + bool Z2IsOne = Z2.IsOne; + uint[] U1, S1; + if (Z2IsOne) + { + U1 = X1.x; + S1 = Y1.x; + } + else + { + S1 = t4; + SecP256R1Field.Square(Z2.x, S1); + + U1 = tt1; + SecP256R1Field.Multiply(S1, X1.x, U1); + + SecP256R1Field.Multiply(S1, Z2.x, S1); + SecP256R1Field.Multiply(S1, Y1.x, S1); + } + + uint[] H = Nat256.Create(); + SecP256R1Field.Subtract(U1, U2, H); + + uint[] R = t2; + SecP256R1Field.Subtract(S1, S2, R); + + // Check if b == this or b == -this + if (Nat256.IsZero(H)) + { + if (Nat256.IsZero(R)) + { + // this == b, i.e. this must be doubled + return this.Twice(); + } + + // this == -b, i.e. the result is the point at infinity + return curve.Infinity; + } + + uint[] HSquared = t3; + SecP256R1Field.Square(H, HSquared); + + uint[] G = Nat256.Create(); + SecP256R1Field.Multiply(HSquared, H, G); + + uint[] V = t3; + SecP256R1Field.Multiply(HSquared, U1, V); + + SecP256R1Field.Negate(G, G); + Nat256.Mul(S1, G, tt1); + + c = Nat256.AddBothTo(V, V, G); + SecP256R1Field.Reduce32(c, G); + + SecP256R1FieldElement X3 = new SecP256R1FieldElement(t4); + SecP256R1Field.Square(R, X3.x); + SecP256R1Field.Subtract(X3.x, G, X3.x); + + SecP256R1FieldElement Y3 = new SecP256R1FieldElement(G); + SecP256R1Field.Subtract(V, X3.x, Y3.x); + SecP256R1Field.MultiplyAddToExt(Y3.x, R, tt1); + SecP256R1Field.Reduce(tt1, Y3.x); + + SecP256R1FieldElement Z3 = new SecP256R1FieldElement(H); + if (!Z1IsOne) + { + SecP256R1Field.Multiply(Z3.x, Z1.x, Z3.x); + } + if (!Z2IsOne) + { + SecP256R1Field.Multiply(Z3.x, Z2.x, Z3.x); + } + + ECFieldElement[] zs = new ECFieldElement[]{ Z3 }; + + return new SecP256R1Point(curve, X3, Y3, zs, IsCompressed); + } + + public override ECPoint Twice() + { + if (this.IsInfinity) + return this; + + ECCurve curve = this.Curve; + + SecP256R1FieldElement Y1 = (SecP256R1FieldElement)this.RawYCoord; + if (Y1.IsZero) + return curve.Infinity; + + SecP256R1FieldElement X1 = (SecP256R1FieldElement)this.RawXCoord, Z1 = (SecP256R1FieldElement)this.RawZCoords[0]; + + uint c; + uint[] t1 = Nat256.Create(); + uint[] t2 = Nat256.Create(); + + uint[] Y1Squared = Nat256.Create(); + SecP256R1Field.Square(Y1.x, Y1Squared); + + uint[] T = Nat256.Create(); + SecP256R1Field.Square(Y1Squared, T); + + bool Z1IsOne = Z1.IsOne; + + uint[] Z1Squared = Z1.x; + if (!Z1IsOne) + { + Z1Squared = t2; + SecP256R1Field.Square(Z1.x, Z1Squared); + } + + SecP256R1Field.Subtract(X1.x, Z1Squared, t1); + + uint[] M = t2; + SecP256R1Field.Add(X1.x, Z1Squared, M); + SecP256R1Field.Multiply(M, t1, M); + c = Nat256.AddBothTo(M, M, M); + SecP256R1Field.Reduce32(c, M); + + uint[] S = Y1Squared; + SecP256R1Field.Multiply(Y1Squared, X1.x, S); + c = Nat.ShiftUpBits(8, S, 2, 0); + SecP256R1Field.Reduce32(c, S); + + c = Nat.ShiftUpBits(8, T, 3, 0, t1); + SecP256R1Field.Reduce32(c, t1); + + SecP256R1FieldElement X3 = new SecP256R1FieldElement(T); + SecP256R1Field.Square(M, X3.x); + SecP256R1Field.Subtract(X3.x, S, X3.x); + SecP256R1Field.Subtract(X3.x, S, X3.x); + + SecP256R1FieldElement Y3 = new SecP256R1FieldElement(S); + SecP256R1Field.Subtract(S, X3.x, Y3.x); + SecP256R1Field.Multiply(Y3.x, M, Y3.x); + SecP256R1Field.Subtract(Y3.x, t1, Y3.x); + + SecP256R1FieldElement Z3 = new SecP256R1FieldElement(M); + SecP256R1Field.Twice(Y1.x, Z3.x); + if (!Z1IsOne) + { + SecP256R1Field.Multiply(Z3.x, Z1.x, Z3.x); + } + + return new SecP256R1Point(curve, X3, Y3, new ECFieldElement[]{ Z3 }, IsCompressed); + } + + public override ECPoint TwicePlus(ECPoint b) + { + if (this == b) + return ThreeTimes(); + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return Twice(); + + ECFieldElement Y1 = this.RawYCoord; + if (Y1.IsZero) + return b; + + return Twice().Add(b); + } + + public override ECPoint ThreeTimes() + { + if (this.IsInfinity || this.RawYCoord.IsZero) + return this; + + // NOTE: Be careful about recursions between TwicePlus and ThreeTimes + return Twice().Add(this); + } + + public override ECPoint Negate() + { + if (IsInfinity) + return this; + + return new SecP256R1Point(Curve, RawXCoord, RawYCoord.Negate(), RawZCoords, IsCompressed); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecP384R1Curve.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecP384R1Curve.cs new file mode 100644 index 0000000..7fd5827 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecP384R1Curve.cs @@ -0,0 +1,77 @@ +using System; + +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecP384R1Curve + : AbstractFpCurve + { + public static readonly BigInteger q = new BigInteger(1, + Hex.Decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFF")); + + private const int SecP384R1_DEFAULT_COORDS = COORD_JACOBIAN; + + protected readonly SecP384R1Point m_infinity; + + public SecP384R1Curve() + : base(q) + { + this.m_infinity = new SecP384R1Point(this, null, null); + + this.m_a = FromBigInteger(new BigInteger(1, + Hex.Decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFC"))); + this.m_b = FromBigInteger(new BigInteger(1, + Hex.Decode("B3312FA7E23EE7E4988E056BE3F82D19181D9C6EFE8141120314088F5013875AC656398D8A2ED19D2A85C8EDD3EC2AEF"))); + this.m_order = new BigInteger(1, Hex.Decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973")); + this.m_cofactor = BigInteger.One; + this.m_coord = SecP384R1_DEFAULT_COORDS; + } + + protected override ECCurve CloneCurve() + { + return new SecP384R1Curve(); + } + + public override bool SupportsCoordinateSystem(int coord) + { + switch (coord) + { + case COORD_JACOBIAN: + return true; + default: + return false; + } + } + + public virtual BigInteger Q + { + get { return q; } + } + + public override ECPoint Infinity + { + get { return m_infinity; } + } + + public override int FieldSize + { + get { return q.BitLength; } + } + + public override ECFieldElement FromBigInteger(BigInteger x) + { + return new SecP384R1FieldElement(x); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, bool withCompression) + { + return new SecP384R1Point(this, x, y, withCompression); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + { + return new SecP384R1Point(this, x, y, zs, withCompression); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecP384R1Field.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecP384R1Field.cs new file mode 100644 index 0000000..0780df3 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecP384R1Field.cs @@ -0,0 +1,295 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Math.Raw; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecP384R1Field + { + // 2^384 - 2^128 - 2^96 + 2^32 - 1 + internal static readonly uint[] P = new uint[]{ 0xFFFFFFFF, 0x00000000, 0x00000000, 0xFFFFFFFF, 0xFFFFFFFE, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }; + internal static readonly uint[] PExt = new uint[]{ 0x00000001, 0xFFFFFFFE, 0x00000000, 0x00000002, 0x00000000, 0xFFFFFFFE, + 0x00000000, 0x00000002, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0xFFFFFFFE, 0x00000001, 0x00000000, + 0xFFFFFFFE, 0xFFFFFFFD, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }; + private static readonly uint[] PExtInv = new uint[]{ 0xFFFFFFFF, 0x00000001, 0xFFFFFFFF, 0xFFFFFFFD, 0xFFFFFFFF, 0x00000001, + 0xFFFFFFFF, 0xFFFFFFFD, 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000001, 0xFFFFFFFE, 0xFFFFFFFF, + 0x00000001, 0x00000002 }; + private const uint P11 = 0xFFFFFFFF; + private const uint PExt23 = 0xFFFFFFFF; + + public static void Add(uint[] x, uint[] y, uint[] z) + { + uint c = Nat.Add(12, x, y, z); + if (c != 0 || (z[11] == P11 && Nat.Gte(12, z, P))) + { + AddPInvTo(z); + } + } + + public static void AddExt(uint[] xx, uint[] yy, uint[] zz) + { + uint c = Nat.Add(24, xx, yy, zz); + if (c != 0 || (zz[23] == PExt23 && Nat.Gte(24, zz, PExt))) + { + if (Nat.AddTo(PExtInv.Length, PExtInv, zz) != 0) + { + Nat.IncAt(24, zz, PExtInv.Length); + } + } + } + + public static void AddOne(uint[] x, uint[] z) + { + uint c = Nat.Inc(12, x, z); + if (c != 0 || (z[11] == P11 && Nat.Gte(12, z, P))) + { + AddPInvTo(z); + } + } + + public static uint[] FromBigInteger(BigInteger x) + { + uint[] z = Nat.FromBigInteger(384, x); + if (z[11] == P11 && Nat.Gte(12, z, P)) + { + Nat.SubFrom(12, P, z); + } + return z; + } + + public static void Half(uint[] x, uint[] z) + { + if ((x[0] & 1) == 0) + { + Nat.ShiftDownBit(12, x, 0, z); + } + else + { + uint c = Nat.Add(12, x, P, z); + Nat.ShiftDownBit(12, z, c); + } + } + + public static void Multiply(uint[] x, uint[] y, uint[] z) + { + uint[] tt = Nat.Create(24); + Nat384.Mul(x, y, tt); + Reduce(tt, z); + } + + public static void Negate(uint[] x, uint[] z) + { + if (Nat.IsZero(12, x)) + { + Nat.Zero(12, z); + } + else + { + Nat.Sub(12, P, x, z); + } + } + + public static void Reduce(uint[] xx, uint[] z) + { + long xx16 = xx[16], xx17 = xx[17], xx18 = xx[18], xx19 = xx[19]; + long xx20 = xx[20], xx21 = xx[21], xx22 = xx[22], xx23 = xx[23]; + + const long n = 1; + + long t0 = (long)xx[12] + xx20 - n; + long t1 = (long)xx[13] + xx22; + long t2 = (long)xx[14] + xx22 + xx23; + long t3 = (long)xx[15] + xx23; + long t4 = xx17 + xx21; + long t5 = xx21 - xx23; + long t6 = xx22 - xx23; + long t7 = t0 + t5; + + long cc = 0; + cc += (long)xx[0] + t7; + z[0] = (uint)cc; + cc >>= 32; + cc += (long)xx[1] + xx23 - t0 + t1; + z[1] = (uint)cc; + cc >>= 32; + cc += (long)xx[2] - xx21 - t1 + t2; + z[2] = (uint)cc; + cc >>= 32; + cc += (long)xx[3] - t2 + t3 + t7; + z[3] = (uint)cc; + cc >>= 32; + cc += (long)xx[4] + xx16 + xx21 + t1 - t3 + t7; + z[4] = (uint)cc; + cc >>= 32; + cc += (long)xx[5] - xx16 + t1 + t2 + t4; + z[5] = (uint)cc; + cc >>= 32; + cc += (long)xx[6] + xx18 - xx17 + t2 + t3; + z[6] = (uint)cc; + cc >>= 32; + cc += (long)xx[7] + xx16 + xx19 - xx18 + t3; + z[7] = (uint)cc; + cc >>= 32; + cc += (long)xx[8] + xx16 + xx17 + xx20 - xx19; + z[8] = (uint)cc; + cc >>= 32; + cc += (long)xx[9] + xx18 - xx20 + t4; + z[9] = (uint)cc; + cc >>= 32; + cc += (long)xx[10] + xx18 + xx19 - t5 + t6; + z[10] = (uint)cc; + cc >>= 32; + cc += (long)xx[11] + xx19 + xx20 - t6; + z[11] = (uint)cc; + cc >>= 32; + cc += n; + + Debug.Assert(cc >= 0); + + Reduce32((uint)cc, z); + } + + public static void Reduce32(uint x, uint[] z) + { + long cc = 0; + + if (x != 0) + { + long xx12 = x; + + cc += (long)z[0] + xx12; + z[0] = (uint)cc; + cc >>= 32; + cc += (long)z[1] - xx12; + z[1] = (uint)cc; + cc >>= 32; + if (cc != 0) + { + cc += (long)z[2]; + z[2] = (uint)cc; + cc >>= 32; + } + cc += (long)z[3] + xx12; + z[3] = (uint)cc; + cc >>= 32; + cc += (long)z[4] + xx12; + z[4] = (uint)cc; + cc >>= 32; + + Debug.Assert(cc == 0 || cc == 1); + } + + if ((cc != 0 && Nat.IncAt(12, z, 5) != 0) + || (z[11] == P11 && Nat.Gte(12, z, P))) + { + AddPInvTo(z); + } + } + + public static void Square(uint[] x, uint[] z) + { + uint[] tt = Nat.Create(24); + Nat384.Square(x, tt); + Reduce(tt, z); + } + + public static void SquareN(uint[] x, int n, uint[] z) + { + Debug.Assert(n > 0); + + uint[] tt = Nat.Create(24); + Nat384.Square(x, tt); + Reduce(tt, z); + + while (--n > 0) + { + Nat384.Square(z, tt); + Reduce(tt, z); + } + } + + public static void Subtract(uint[] x, uint[] y, uint[] z) + { + int c = Nat.Sub(12, x, y, z); + if (c != 0) + { + SubPInvFrom(z); + } + } + + public static void SubtractExt(uint[] xx, uint[] yy, uint[] zz) + { + int c = Nat.Sub(24, xx, yy, zz); + if (c != 0) + { + if (Nat.SubFrom(PExtInv.Length, PExtInv, zz) != 0) + { + Nat.DecAt(24, zz, PExtInv.Length); + } + } + } + + public static void Twice(uint[] x, uint[] z) + { + uint c = Nat.ShiftUpBit(12, x, 0, z); + if (c != 0 || (z[11] == P11 && Nat.Gte(12, z, P))) + { + AddPInvTo(z); + } + } + + private static void AddPInvTo(uint[] z) + { + long c = (long)z[0] + 1; + z[0] = (uint)c; + c >>= 32; + c += (long)z[1] - 1; + z[1] = (uint)c; + c >>= 32; + if (c != 0) + { + c += (long)z[2]; + z[2] = (uint)c; + c >>= 32; + } + c += (long)z[3] + 1; + z[3] = (uint)c; + c >>= 32; + c += (long)z[4] + 1; + z[4] = (uint)c; + c >>= 32; + if (c != 0) + { + Nat.IncAt(12, z, 5); + } + } + + private static void SubPInvFrom(uint[] z) + { + long c = (long)z[0] - 1; + z[0] = (uint)c; + c >>= 32; + c += (long)z[1] + 1; + z[1] = (uint)c; + c >>= 32; + if (c != 0) + { + c += (long)z[2]; + z[2] = (uint)c; + c >>= 32; + } + c += (long)z[3] - 1; + z[3] = (uint)c; + c >>= 32; + c += (long)z[4] - 1; + z[4] = (uint)c; + c >>= 32; + if (c != 0) + { + Nat.DecAt(12, z, 5); + } + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecP384R1FieldElement.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecP384R1FieldElement.cs new file mode 100644 index 0000000..18d48a5 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecP384R1FieldElement.cs @@ -0,0 +1,210 @@ +using System; + +using Org.BouncyCastle.Math.Raw; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecP384R1FieldElement + : ECFieldElement + { + public static readonly BigInteger Q = SecP384R1Curve.q; + + protected internal readonly uint[] x; + + public SecP384R1FieldElement(BigInteger x) + { + if (x == null || x.SignValue < 0 || x.CompareTo(Q) >= 0) + throw new ArgumentException("value invalid for SecP384R1FieldElement", "x"); + + this.x = SecP384R1Field.FromBigInteger(x); + } + + public SecP384R1FieldElement() + { + this.x = Nat.Create(12); + } + + protected internal SecP384R1FieldElement(uint[] x) + { + this.x = x; + } + + public override bool IsZero + { + get { return Nat.IsZero(12, x); } + } + + public override bool IsOne + { + get { return Nat.IsOne(12, x); } + } + + public override bool TestBitZero() + { + return Nat.GetBit(x, 0) == 1; + } + + public override BigInteger ToBigInteger() + { + return Nat.ToBigInteger(12, x); + } + + public override string FieldName + { + get { return "SecP384R1Field"; } + } + + public override int FieldSize + { + get { return Q.BitLength; } + } + + public override ECFieldElement Add(ECFieldElement b) + { + uint[] z = Nat.Create(12); + SecP384R1Field.Add(x, ((SecP384R1FieldElement)b).x, z); + return new SecP384R1FieldElement(z); + } + + public override ECFieldElement AddOne() + { + uint[] z = Nat.Create(12); + SecP384R1Field.AddOne(x, z); + return new SecP384R1FieldElement(z); + } + + public override ECFieldElement Subtract(ECFieldElement b) + { + uint[] z = Nat.Create(12); + SecP384R1Field.Subtract(x, ((SecP384R1FieldElement)b).x, z); + return new SecP384R1FieldElement(z); + } + + public override ECFieldElement Multiply(ECFieldElement b) + { + uint[] z = Nat.Create(12); + SecP384R1Field.Multiply(x, ((SecP384R1FieldElement)b).x, z); + return new SecP384R1FieldElement(z); + } + + public override ECFieldElement Divide(ECFieldElement b) + { + //return Multiply(b.Invert()); + uint[] z = Nat.Create(12); + Mod.Invert(SecP384R1Field.P, ((SecP384R1FieldElement)b).x, z); + SecP384R1Field.Multiply(z, x, z); + return new SecP384R1FieldElement(z); + } + + public override ECFieldElement Negate() + { + uint[] z = Nat.Create(12); + SecP384R1Field.Negate(x, z); + return new SecP384R1FieldElement(z); + } + + public override ECFieldElement Square() + { + uint[] z = Nat.Create(12); + SecP384R1Field.Square(x, z); + return new SecP384R1FieldElement(z); + } + + public override ECFieldElement Invert() + { + //return new SecP384R1FieldElement(ToBigInteger().ModInverse(Q)); + uint[] z = Nat.Create(12); + Mod.Invert(SecP384R1Field.P, x, z); + return new SecP384R1FieldElement(z); + } + + /** + * return a sqrt root - the routine verifies that the calculation returns the right value - if + * none exists it returns null. + */ + public override ECFieldElement Sqrt() + { + // Raise this element to the exponent 2^382 - 2^126 - 2^94 + 2^30 + + uint[] x1 = this.x; + if (Nat.IsZero(12, x1) || Nat.IsOne(12, x1)) + return this; + + uint[] t1 = Nat.Create(12); + uint[] t2 = Nat.Create(12); + uint[] t3 = Nat.Create(12); + uint[] t4 = Nat.Create(12); + + SecP384R1Field.Square(x1, t1); + SecP384R1Field.Multiply(t1, x1, t1); + + SecP384R1Field.SquareN(t1, 2, t2); + SecP384R1Field.Multiply(t2, t1, t2); + + SecP384R1Field.Square(t2, t2); + SecP384R1Field.Multiply(t2, x1, t2); + + SecP384R1Field.SquareN(t2, 5, t3); + SecP384R1Field.Multiply(t3, t2, t3); + + SecP384R1Field.SquareN(t3, 5, t4); + SecP384R1Field.Multiply(t4, t2, t4); + + SecP384R1Field.SquareN(t4, 15, t2); + SecP384R1Field.Multiply(t2, t4, t2); + + SecP384R1Field.SquareN(t2, 2, t3); + SecP384R1Field.Multiply(t1, t3, t1); + + SecP384R1Field.SquareN(t3, 28, t3); + SecP384R1Field.Multiply(t2, t3, t2); + + SecP384R1Field.SquareN(t2, 60, t3); + SecP384R1Field.Multiply(t3, t2, t3); + + uint[] r = t2; + + SecP384R1Field.SquareN(t3, 120, r); + SecP384R1Field.Multiply(r, t3, r); + + SecP384R1Field.SquareN(r, 15, r); + SecP384R1Field.Multiply(r, t4, r); + + SecP384R1Field.SquareN(r, 33, r); + SecP384R1Field.Multiply(r, t1, r); + + SecP384R1Field.SquareN(r, 64, r); + SecP384R1Field.Multiply(r, x1, r); + + SecP384R1Field.SquareN(r, 30, t1); + SecP384R1Field.Square(t1, t2); + + return Nat.Eq(12, x1, t2) ? new SecP384R1FieldElement(t1) : null; + } + + public override bool Equals(object obj) + { + return Equals(obj as SecP384R1FieldElement); + } + + public override bool Equals(ECFieldElement other) + { + return Equals(other as SecP384R1FieldElement); + } + + public virtual bool Equals(SecP384R1FieldElement other) + { + if (this == other) + return true; + if (null == other) + return false; + return Nat.Eq(12, x, other.x); + } + + public override int GetHashCode() + { + return Q.GetHashCode() ^ Arrays.GetHashCode(x, 0, 12); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecP384R1Point.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecP384R1Point.cs new file mode 100644 index 0000000..83159ce --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecP384R1Point.cs @@ -0,0 +1,280 @@ +using System; + +using Org.BouncyCastle.Math.Raw; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecP384R1Point + : AbstractFpPoint + { + /** + * 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 + * + * @deprecated Use ECCurve.createPoint to construct points + */ + public SecP384R1Point(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 + * + * @deprecated per-point compression property will be removed, refer + * {@link #getEncoded(bool)} + */ + public SecP384R1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, bool withCompression) + : base(curve, x, y, withCompression) + { + if ((x == null) != (y == null)) + throw new ArgumentException("Exactly one of the field elements is null"); + } + + internal SecP384R1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + : base(curve, x, y, zs, withCompression) + { + } + + protected override ECPoint Detach() + { + return new SecP384R1Point(null, AffineXCoord, AffineYCoord); + } + + public override ECPoint Add(ECPoint b) + { + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return this; + if (this == b) + return Twice(); + + ECCurve curve = this.Curve; + + SecP384R1FieldElement X1 = (SecP384R1FieldElement)this.RawXCoord, Y1 = (SecP384R1FieldElement)this.RawYCoord; + SecP384R1FieldElement X2 = (SecP384R1FieldElement)b.RawXCoord, Y2 = (SecP384R1FieldElement)b.RawYCoord; + + SecP384R1FieldElement Z1 = (SecP384R1FieldElement)this.RawZCoords[0]; + SecP384R1FieldElement Z2 = (SecP384R1FieldElement)b.RawZCoords[0]; + + uint c; + uint[] tt1 = Nat.Create(24); + uint[] tt2 = Nat.Create(24); + uint[] t3 = Nat.Create(12); + uint[] t4 = Nat.Create(12); + + bool Z1IsOne = Z1.IsOne; + uint[] U2, S2; + if (Z1IsOne) + { + U2 = X2.x; + S2 = Y2.x; + } + else + { + S2 = t3; + SecP384R1Field.Square(Z1.x, S2); + + U2 = tt2; + SecP384R1Field.Multiply(S2, X2.x, U2); + + SecP384R1Field.Multiply(S2, Z1.x, S2); + SecP384R1Field.Multiply(S2, Y2.x, S2); + } + + bool Z2IsOne = Z2.IsOne; + uint[] U1, S1; + if (Z2IsOne) + { + U1 = X1.x; + S1 = Y1.x; + } + else + { + S1 = t4; + SecP384R1Field.Square(Z2.x, S1); + + U1 = tt1; + SecP384R1Field.Multiply(S1, X1.x, U1); + + SecP384R1Field.Multiply(S1, Z2.x, S1); + SecP384R1Field.Multiply(S1, Y1.x, S1); + } + + uint[] H = Nat.Create(12); + SecP384R1Field.Subtract(U1, U2, H); + + uint[] R = Nat.Create(12); + SecP384R1Field.Subtract(S1, S2, R); + + // Check if b == this or b == -this + if (Nat.IsZero(12, H)) + { + if (Nat.IsZero(12, R)) + { + // this == b, i.e. this must be doubled + return this.Twice(); + } + + // this == -b, i.e. the result is the point at infinity + return curve.Infinity; + } + + uint[] HSquared = t3; + SecP384R1Field.Square(H, HSquared); + + uint[] G = Nat.Create(12); + SecP384R1Field.Multiply(HSquared, H, G); + + uint[] V = t3; + SecP384R1Field.Multiply(HSquared, U1, V); + + SecP384R1Field.Negate(G, G); + Nat384.Mul(S1, G, tt1); + + c = Nat.AddBothTo(12, V, V, G); + SecP384R1Field.Reduce32(c, G); + + SecP384R1FieldElement X3 = new SecP384R1FieldElement(t4); + SecP384R1Field.Square(R, X3.x); + SecP384R1Field.Subtract(X3.x, G, X3.x); + + SecP384R1FieldElement Y3 = new SecP384R1FieldElement(G); + SecP384R1Field.Subtract(V, X3.x, Y3.x); + Nat384.Mul(Y3.x, R, tt2); + SecP384R1Field.AddExt(tt1, tt2, tt1); + SecP384R1Field.Reduce(tt1, Y3.x); + + SecP384R1FieldElement Z3 = new SecP384R1FieldElement(H); + if (!Z1IsOne) + { + SecP384R1Field.Multiply(Z3.x, Z1.x, Z3.x); + } + if (!Z2IsOne) + { + SecP384R1Field.Multiply(Z3.x, Z2.x, Z3.x); + } + + ECFieldElement[] zs = new ECFieldElement[] { Z3 }; + + return new SecP384R1Point(curve, X3, Y3, zs, IsCompressed); + } + + public override ECPoint Twice() + { + if (this.IsInfinity) + return this; + + ECCurve curve = this.Curve; + + SecP384R1FieldElement Y1 = (SecP384R1FieldElement)this.RawYCoord; + if (Y1.IsZero) + return curve.Infinity; + + SecP384R1FieldElement X1 = (SecP384R1FieldElement)this.RawXCoord, Z1 = (SecP384R1FieldElement)this.RawZCoords[0]; + + uint c; + uint[] t1 = Nat.Create(12); + uint[] t2 = Nat.Create(12); + + uint[] Y1Squared = Nat.Create(12); + SecP384R1Field.Square(Y1.x, Y1Squared); + + uint[] T = Nat.Create(12); + SecP384R1Field.Square(Y1Squared, T); + + bool Z1IsOne = Z1.IsOne; + + uint[] Z1Squared = Z1.x; + if (!Z1IsOne) + { + Z1Squared = t2; + SecP384R1Field.Square(Z1.x, Z1Squared); + } + + SecP384R1Field.Subtract(X1.x, Z1Squared, t1); + + uint[] M = t2; + SecP384R1Field.Add(X1.x, Z1Squared, M); + SecP384R1Field.Multiply(M, t1, M); + c = Nat.AddBothTo(12, M, M, M); + SecP384R1Field.Reduce32(c, M); + + uint[] S = Y1Squared; + SecP384R1Field.Multiply(Y1Squared, X1.x, S); + c = Nat.ShiftUpBits(12, S, 2, 0); + SecP384R1Field.Reduce32(c, S); + + c = Nat.ShiftUpBits(12, T, 3, 0, t1); + SecP384R1Field.Reduce32(c, t1); + + SecP384R1FieldElement X3 = new SecP384R1FieldElement(T); + SecP384R1Field.Square(M, X3.x); + SecP384R1Field.Subtract(X3.x, S, X3.x); + SecP384R1Field.Subtract(X3.x, S, X3.x); + + SecP384R1FieldElement Y3 = new SecP384R1FieldElement(S); + SecP384R1Field.Subtract(S, X3.x, Y3.x); + SecP384R1Field.Multiply(Y3.x, M, Y3.x); + SecP384R1Field.Subtract(Y3.x, t1, Y3.x); + + SecP384R1FieldElement Z3 = new SecP384R1FieldElement(M); + SecP384R1Field.Twice(Y1.x, Z3.x); + if (!Z1IsOne) + { + SecP384R1Field.Multiply(Z3.x, Z1.x, Z3.x); + } + + return new SecP384R1Point(curve, X3, Y3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint TwicePlus(ECPoint b) + { + if (this == b) + return ThreeTimes(); + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return Twice(); + + ECFieldElement Y1 = this.RawYCoord; + if (Y1.IsZero) + return b; + + return Twice().Add(b); + } + + public override ECPoint ThreeTimes() + { + if (this.IsInfinity || this.RawYCoord.IsZero) + return this; + + // NOTE: Be careful about recursions between TwicePlus and ThreeTimes + return Twice().Add(this); + } + + public override ECPoint Negate() + { + if (IsInfinity) + return this; + + return new SecP384R1Point(Curve, RawXCoord, RawYCoord.Negate(), RawZCoords, IsCompressed); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecP521R1Curve.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecP521R1Curve.cs new file mode 100644 index 0000000..e5083c7 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecP521R1Curve.cs @@ -0,0 +1,77 @@ +using System; + +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecP521R1Curve + : AbstractFpCurve + { + public static readonly BigInteger q = new BigInteger(1, + Hex.Decode("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")); + + private const int SecP521R1_DEFAULT_COORDS = COORD_JACOBIAN; + + protected readonly SecP521R1Point m_infinity; + + public SecP521R1Curve() + : base(q) + { + this.m_infinity = new SecP521R1Point(this, null, null); + + this.m_a = FromBigInteger(new BigInteger(1, + Hex.Decode("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC"))); + this.m_b = FromBigInteger(new BigInteger(1, + Hex.Decode("0051953EB9618E1C9A1F929A21A0B68540EEA2DA725B99B315F3B8B489918EF109E156193951EC7E937B1652C0BD3BB1BF073573DF883D2C34F1EF451FD46B503F00"))); + this.m_order = new BigInteger(1, Hex.Decode("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409")); + this.m_cofactor = BigInteger.One; + this.m_coord = SecP521R1_DEFAULT_COORDS; + } + + protected override ECCurve CloneCurve() + { + return new SecP521R1Curve(); + } + + public override bool SupportsCoordinateSystem(int coord) + { + switch (coord) + { + case COORD_JACOBIAN: + return true; + default: + return false; + } + } + + public virtual BigInteger Q + { + get { return q; } + } + + public override ECPoint Infinity + { + get { return m_infinity; } + } + + public override int FieldSize + { + get { return q.BitLength; } + } + + public override ECFieldElement FromBigInteger(BigInteger x) + { + return new SecP521R1FieldElement(x); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, bool withCompression) + { + return new SecP521R1Point(this, x, y, withCompression); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + { + return new SecP521R1Point(this, x, y, zs, withCompression); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecP521R1Field.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecP521R1Field.cs new file mode 100644 index 0000000..b7f8eb1 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecP521R1Field.cs @@ -0,0 +1,155 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Math.Raw; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecP521R1Field + { + // 2^521 - 1 + internal static readonly uint[] P = new uint[]{ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x1FF }; + private const int P16 = 0x1FF; + + public static void Add(uint[] x, uint[] y, uint[] z) + { + uint c = Nat.Add(16, x, y, z) + x[16] + y[16]; + if (c > P16 || (c == P16 && Nat.Eq(16, z, P))) + { + c += Nat.Inc(16, z); + c &= P16; + } + z[16] = c; + } + + public static void AddOne(uint[] x, uint[] z) + { + uint c = Nat.Inc(16, x, z) + x[16]; + if (c > P16 || (c == P16 && Nat.Eq(16, z, P))) + { + c += Nat.Inc(16, z); + c &= P16; + } + z[16] = c; + } + + public static uint[] FromBigInteger(BigInteger x) + { + uint[] z = Nat.FromBigInteger(521, x); + if (Nat.Eq(17, z, P)) + { + Nat.Zero(17, z); + } + return z; + } + + public static void Half(uint[] x, uint[] z) + { + uint x16 = x[16]; + uint c = Nat.ShiftDownBit(16, x, x16, z); + z[16] = (x16 >> 1) | (c >> 23); + } + + public static void Multiply(uint[] x, uint[] y, uint[] z) + { + uint[] tt = Nat.Create(33); + ImplMultiply(x, y, tt); + Reduce(tt, z); + } + + public static void Negate(uint[] x, uint[] z) + { + if (Nat.IsZero(17, x)) + { + Nat.Zero(17, z); + } + else + { + Nat.Sub(17, P, x, z); + } + } + + public static void Reduce(uint[] xx, uint[] z) + { + Debug.Assert(xx[32] >> 18 == 0); + uint xx32 = xx[32]; + uint c = Nat.ShiftDownBits(16, xx, 16, 9, xx32, z, 0) >> 23; + c += xx32 >> 9; + c += Nat.AddTo(16, xx, z); + if (c > P16 || (c == P16 && Nat.Eq(16, z, P))) + { + c += Nat.Inc(16, z); + c &= P16; + } + z[16] = c; + } + + public static void Reduce23(uint[] z) + { + uint z16 = z[16]; + uint c = Nat.AddWordTo(16, z16 >> 9, z) + (z16 & P16); + if (c > P16 || (c == P16 && Nat.Eq(16, z, P))) + { + c += Nat.Inc(16, z); + c &= P16; + } + z[16] = c; + } + + public static void Square(uint[] x, uint[] z) + { + uint[] tt = Nat.Create(33); + ImplSquare(x, tt); + Reduce(tt, z); + } + + public static void SquareN(uint[] x, int n, uint[] z) + { + Debug.Assert(n > 0); + uint[] tt = Nat.Create(33); + ImplSquare(x, tt); + Reduce(tt, z); + + while (--n > 0) + { + ImplSquare(z, tt); + Reduce(tt, z); + } + } + + public static void Subtract(uint[] x, uint[] y, uint[] z) + { + int c = Nat.Sub(16, x, y, z) + (int)(x[16] - y[16]); + if (c < 0) + { + c += Nat.Dec(16, z); + c &= P16; + } + z[16] = (uint)c; + } + + public static void Twice(uint[] x, uint[] z) + { + uint x16 = x[16]; + uint c = Nat.ShiftUpBit(16, x, x16 << 23, z) | (x16 << 1); + z[16] = c & P16; + } + + protected static void ImplMultiply(uint[] x, uint[] y, uint[] zz) + { + Nat512.Mul(x, y, zz); + + uint x16 = x[16], y16 = y[16]; + zz[32] = Nat.Mul31BothAdd(16, x16, y, y16, x, zz, 16) + (x16 * y16); + } + + protected static void ImplSquare(uint[] x, uint[] zz) + { + Nat512.Square(x, zz); + + uint x16 = x[16]; + zz[32] = Nat.MulWordAddTo(16, x16 << 1, x, 0, zz, 16) + (x16 * x16); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecP521R1FieldElement.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecP521R1FieldElement.cs new file mode 100644 index 0000000..6f02a7e --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecP521R1FieldElement.cs @@ -0,0 +1,167 @@ +using System; + +using Org.BouncyCastle.Math.Raw; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecP521R1FieldElement + : ECFieldElement + { + public static readonly BigInteger Q = SecP521R1Curve.q; + + protected internal readonly uint[] x; + + public SecP521R1FieldElement(BigInteger x) + { + if (x == null || x.SignValue < 0 || x.CompareTo(Q) >= 0) + throw new ArgumentException("value invalid for SecP521R1FieldElement", "x"); + + this.x = SecP521R1Field.FromBigInteger(x); + } + + public SecP521R1FieldElement() + { + this.x = Nat.Create(17); + } + + protected internal SecP521R1FieldElement(uint[] x) + { + this.x = x; + } + + public override bool IsZero + { + get { return Nat.IsZero(17, x); } + } + + public override bool IsOne + { + get { return Nat.IsOne(17, x); } + } + + public override bool TestBitZero() + { + return Nat.GetBit(x, 0) == 1; + } + + public override BigInteger ToBigInteger() + { + return Nat.ToBigInteger(17, x); + } + + public override string FieldName + { + get { return "SecP521R1Field"; } + } + + public override int FieldSize + { + get { return Q.BitLength; } + } + + public override ECFieldElement Add(ECFieldElement b) + { + uint[] z = Nat.Create(17); + SecP521R1Field.Add(x, ((SecP521R1FieldElement)b).x, z); + return new SecP521R1FieldElement(z); + } + + public override ECFieldElement AddOne() + { + uint[] z = Nat.Create(17); + SecP521R1Field.AddOne(x, z); + return new SecP521R1FieldElement(z); + } + + public override ECFieldElement Subtract(ECFieldElement b) + { + uint[] z = Nat.Create(17); + SecP521R1Field.Subtract(x, ((SecP521R1FieldElement)b).x, z); + return new SecP521R1FieldElement(z); + } + + public override ECFieldElement Multiply(ECFieldElement b) + { + uint[] z = Nat.Create(17); + SecP521R1Field.Multiply(x, ((SecP521R1FieldElement)b).x, z); + return new SecP521R1FieldElement(z); + } + + public override ECFieldElement Divide(ECFieldElement b) + { + //return Multiply(b.Invert()); + uint[] z = Nat.Create(17); + Mod.Invert(SecP521R1Field.P, ((SecP521R1FieldElement)b).x, z); + SecP521R1Field.Multiply(z, x, z); + return new SecP521R1FieldElement(z); + } + + public override ECFieldElement Negate() + { + uint[] z = Nat.Create(17); + SecP521R1Field.Negate(x, z); + return new SecP521R1FieldElement(z); + } + + public override ECFieldElement Square() + { + uint[] z = Nat.Create(17); + SecP521R1Field.Square(x, z); + return new SecP521R1FieldElement(z); + } + + public override ECFieldElement Invert() + { + //return new SecP521R1FieldElement(ToBigInteger().ModInverse(Q)); + uint[] z = Nat.Create(17); + Mod.Invert(SecP521R1Field.P, x, z); + return new SecP521R1FieldElement(z); + } + + /** + * return a sqrt root - the routine verifies that the calculation returns the right value - if + * none exists it returns null. + */ + public override ECFieldElement Sqrt() + { + // Raise this element to the exponent 2^519 + + uint[] x1 = this.x; + if (Nat.IsZero(17, x1) || Nat.IsOne(17, x1)) + return this; + + uint[] t1 = Nat.Create(17); + uint[] t2 = Nat.Create(17); + + SecP521R1Field.SquareN(x1, 519, t1); + SecP521R1Field.Square(t1, t2); + + return Nat.Eq(17, x1, t2) ? new SecP521R1FieldElement(t1) : null; + } + + public override bool Equals(object obj) + { + return Equals(obj as SecP521R1FieldElement); + } + + public override bool Equals(ECFieldElement other) + { + return Equals(other as SecP521R1FieldElement); + } + + public virtual bool Equals(SecP521R1FieldElement other) + { + if (this == other) + return true; + if (null == other) + return false; + return Nat.Eq(17, x, other.x); + } + + public override int GetHashCode() + { + return Q.GetHashCode() ^ Arrays.GetHashCode(x, 0, 17); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecP521R1Point.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecP521R1Point.cs new file mode 100644 index 0000000..7ad97f7 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecP521R1Point.cs @@ -0,0 +1,275 @@ +using System; + +using Org.BouncyCastle.Math.Raw; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecP521R1Point + : AbstractFpPoint + { + /** + * 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 + * + * @deprecated Use ECCurve.createPoint to construct points + */ + public SecP521R1Point(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 + * + * @deprecated per-point compression property will be removed, refer + * {@link #getEncoded(bool)} + */ + public SecP521R1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, bool withCompression) + : base(curve, x, y, withCompression) + { + if ((x == null) != (y == null)) + throw new ArgumentException("Exactly one of the field elements is null"); + } + + internal SecP521R1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + : base(curve, x, y, zs, withCompression) + { + } + + protected override ECPoint Detach() + { + return new SecP521R1Point(null, AffineXCoord, AffineYCoord); + } + + public override ECPoint Add(ECPoint b) + { + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return this; + if (this == b) + return Twice(); + + ECCurve curve = this.Curve; + + SecP521R1FieldElement X1 = (SecP521R1FieldElement)this.RawXCoord, Y1 = (SecP521R1FieldElement)this.RawYCoord; + SecP521R1FieldElement X2 = (SecP521R1FieldElement)b.RawXCoord, Y2 = (SecP521R1FieldElement)b.RawYCoord; + + SecP521R1FieldElement Z1 = (SecP521R1FieldElement)this.RawZCoords[0]; + SecP521R1FieldElement Z2 = (SecP521R1FieldElement)b.RawZCoords[0]; + + uint[] t1 = Nat.Create(17); + uint[] t2 = Nat.Create(17); + uint[] t3 = Nat.Create(17); + uint[] t4 = Nat.Create(17); + + bool Z1IsOne = Z1.IsOne; + uint[] U2, S2; + if (Z1IsOne) + { + U2 = X2.x; + S2 = Y2.x; + } + else + { + S2 = t3; + SecP521R1Field.Square(Z1.x, S2); + + U2 = t2; + SecP521R1Field.Multiply(S2, X2.x, U2); + + SecP521R1Field.Multiply(S2, Z1.x, S2); + SecP521R1Field.Multiply(S2, Y2.x, S2); + } + + bool Z2IsOne = Z2.IsOne; + uint[] U1, S1; + if (Z2IsOne) + { + U1 = X1.x; + S1 = Y1.x; + } + else + { + S1 = t4; + SecP521R1Field.Square(Z2.x, S1); + + U1 = t1; + SecP521R1Field.Multiply(S1, X1.x, U1); + + SecP521R1Field.Multiply(S1, Z2.x, S1); + SecP521R1Field.Multiply(S1, Y1.x, S1); + } + + uint[] H = Nat.Create(17); + SecP521R1Field.Subtract(U1, U2, H); + + uint[] R = t2; + SecP521R1Field.Subtract(S1, S2, R); + + // Check if b == this or b == -this + if (Nat.IsZero(17, H)) + { + if (Nat.IsZero(17, R)) + { + // this == b, i.e. this must be doubled + return this.Twice(); + } + + // this == -b, i.e. the result is the point at infinity + return curve.Infinity; + } + + uint[] HSquared = t3; + SecP521R1Field.Square(H, HSquared); + + uint[] G = Nat.Create(17); + SecP521R1Field.Multiply(HSquared, H, G); + + uint[] V = t3; + SecP521R1Field.Multiply(HSquared, U1, V); + + SecP521R1Field.Multiply(S1, G, t1); + + SecP521R1FieldElement X3 = new SecP521R1FieldElement(t4); + SecP521R1Field.Square(R, X3.x); + SecP521R1Field.Add(X3.x, G, X3.x); + SecP521R1Field.Subtract(X3.x, V, X3.x); + SecP521R1Field.Subtract(X3.x, V, X3.x); + + SecP521R1FieldElement Y3 = new SecP521R1FieldElement(G); + SecP521R1Field.Subtract(V, X3.x, Y3.x); + SecP521R1Field.Multiply(Y3.x, R, t2); + SecP521R1Field.Subtract(t2, t1, Y3.x); + + SecP521R1FieldElement Z3 = new SecP521R1FieldElement(H); + if (!Z1IsOne) + { + SecP521R1Field.Multiply(Z3.x, Z1.x, Z3.x); + } + if (!Z2IsOne) + { + SecP521R1Field.Multiply(Z3.x, Z2.x, Z3.x); + } + + ECFieldElement[] zs = new ECFieldElement[] { Z3 }; + + return new SecP521R1Point(curve, X3, Y3, zs, IsCompressed); + } + + public override ECPoint Twice() + { + if (this.IsInfinity) + return this; + + ECCurve curve = this.Curve; + + SecP521R1FieldElement Y1 = (SecP521R1FieldElement)this.RawYCoord; + if (Y1.IsZero) + return curve.Infinity; + + SecP521R1FieldElement X1 = (SecP521R1FieldElement)this.RawXCoord, Z1 = (SecP521R1FieldElement)this.RawZCoords[0]; + + uint[] t1 = Nat.Create(17); + uint[] t2 = Nat.Create(17); + + uint[] Y1Squared = Nat.Create(17); + SecP521R1Field.Square(Y1.x, Y1Squared); + + uint[] T = Nat.Create(17); + SecP521R1Field.Square(Y1Squared, T); + + bool Z1IsOne = Z1.IsOne; + + uint[] Z1Squared = Z1.x; + if (!Z1IsOne) + { + Z1Squared = t2; + SecP521R1Field.Square(Z1.x, Z1Squared); + } + + SecP521R1Field.Subtract(X1.x, Z1Squared, t1); + + uint[] M = t2; + SecP521R1Field.Add(X1.x, Z1Squared, M); + SecP521R1Field.Multiply(M, t1, M); + Nat.AddBothTo(17, M, M, M); + SecP521R1Field.Reduce23(M); + + uint[] S = Y1Squared; + SecP521R1Field.Multiply(Y1Squared, X1.x, S); + Nat.ShiftUpBits(17, S, 2, 0); + SecP521R1Field.Reduce23(S); + + Nat.ShiftUpBits(17, T, 3, 0, t1); + SecP521R1Field.Reduce23(t1); + + SecP521R1FieldElement X3 = new SecP521R1FieldElement(T); + SecP521R1Field.Square(M, X3.x); + SecP521R1Field.Subtract(X3.x, S, X3.x); + SecP521R1Field.Subtract(X3.x, S, X3.x); + + SecP521R1FieldElement Y3 = new SecP521R1FieldElement(S); + SecP521R1Field.Subtract(S, X3.x, Y3.x); + SecP521R1Field.Multiply(Y3.x, M, Y3.x); + SecP521R1Field.Subtract(Y3.x, t1, Y3.x); + + SecP521R1FieldElement Z3 = new SecP521R1FieldElement(M); + SecP521R1Field.Twice(Y1.x, Z3.x); + if (!Z1IsOne) + { + SecP521R1Field.Multiply(Z3.x, Z1.x, Z3.x); + } + + return new SecP521R1Point(curve, X3, Y3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint TwicePlus(ECPoint b) + { + if (this == b) + return ThreeTimes(); + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return Twice(); + + ECFieldElement Y1 = this.RawYCoord; + if (Y1.IsZero) + return b; + + return Twice().Add(b); + } + + public override ECPoint ThreeTimes() + { + if (this.IsInfinity || this.RawYCoord.IsZero) + return this; + + // NOTE: Be careful about recursions between TwicePlus and ThreeTimes + return Twice().Add(this); + } + + public override ECPoint Negate() + { + if (IsInfinity) + return this; + + return new SecP521R1Point(Curve, RawXCoord, RawYCoord.Negate(), RawZCoords, IsCompressed); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT113Field.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT113Field.cs new file mode 100644 index 0000000..49773b6 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT113Field.cs @@ -0,0 +1,225 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Math.Raw; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT113Field + { + private const ulong M49 = ulong.MaxValue >> 15; + private const ulong M57 = ulong.MaxValue >> 7; + + public static void Add(ulong[] x, ulong[] y, ulong[] z) + { + z[0] = x[0] ^ y[0]; + z[1] = x[1] ^ y[1]; + } + + public static void AddExt(ulong[] xx, ulong[] yy, ulong[] zz) + { + zz[0] = xx[0] ^ yy[0]; + zz[1] = xx[1] ^ yy[1]; + zz[2] = xx[2] ^ yy[2]; + zz[3] = xx[3] ^ yy[3]; + } + + public static void AddOne(ulong[] x, ulong[] z) + { + z[0] = x[0] ^ 1UL; + z[1] = x[1]; + } + + public static ulong[] FromBigInteger(BigInteger x) + { + ulong[] z = Nat128.FromBigInteger64(x); + Reduce15(z, 0); + return z; + } + + public static void Invert(ulong[] x, ulong[] z) + { + if (Nat128.IsZero64(x)) + throw new InvalidOperationException(); + + // Itoh-Tsujii inversion + + ulong[] t0 = Nat128.Create64(); + ulong[] t1 = Nat128.Create64(); + + Square(x, t0); + Multiply(t0, x, t0); + Square(t0, t0); + Multiply(t0, x, t0); + SquareN(t0, 3, t1); + Multiply(t1, t0, t1); + Square(t1, t1); + Multiply(t1, x, t1); + SquareN(t1, 7, t0); + Multiply(t0, t1, t0); + SquareN(t0, 14, t1); + Multiply(t1, t0, t1); + SquareN(t1, 28, t0); + Multiply(t0, t1, t0); + SquareN(t0, 56, t1); + Multiply(t1, t0, t1); + Square(t1, z); + } + + public static void Multiply(ulong[] x, ulong[] y, ulong[] z) + { + ulong[] tt = Nat128.CreateExt64(); + ImplMultiply(x, y, tt); + Reduce(tt, z); + } + + public static void MultiplyAddToExt(ulong[] x, ulong[] y, ulong[] zz) + { + ulong[] tt = Nat128.CreateExt64(); + ImplMultiply(x, y, tt); + AddExt(zz, tt, zz); + } + + public static void Reduce(ulong[] xx, ulong[] z) + { + ulong x0 = xx[0], x1 = xx[1], x2 = xx[2], x3 = xx[3]; + + x1 ^= (x3 << 15) ^ (x3 << 24); + x2 ^= (x3 >> 49) ^ (x3 >> 40); + + x0 ^= (x2 << 15) ^ (x2 << 24); + x1 ^= (x2 >> 49) ^ (x2 >> 40); + + ulong t = x1 >> 49; + z[0] = x0 ^ t ^ (t << 9); + z[1] = x1 & M49; + } + + public static void Reduce15(ulong[] z, int zOff) + { + ulong z1 = z[zOff + 1], t = z1 >> 49; + z[zOff ] ^= t ^ (t << 9); + z[zOff + 1] = z1 & M49; + } + + public static void Sqrt(ulong[] x, ulong[] z) + { + ulong u0 = Interleave.Unshuffle(x[0]), u1 = Interleave.Unshuffle(x[1]); + ulong e0 = (u0 & 0x00000000FFFFFFFFUL) | (u1 << 32); + ulong c0 = (u0 >> 32) | (u1 & 0xFFFFFFFF00000000UL); + + z[0] = e0 ^ (c0 << 57) ^ (c0 << 5); + z[1] = (c0 >> 7) ^ (c0 >> 59); + } + + public static void Square(ulong[] x, ulong[] z) + { + ulong[] tt = Nat128.CreateExt64(); + ImplSquare(x, tt); + Reduce(tt, z); + } + + public static void SquareAddToExt(ulong[] x, ulong[] zz) + { + ulong[] tt = Nat128.CreateExt64(); + ImplSquare(x, tt); + AddExt(zz, tt, zz); + } + + public static void SquareN(ulong[] x, int n, ulong[] z) + { + Debug.Assert(n > 0); + + ulong[] tt = Nat128.CreateExt64(); + ImplSquare(x, tt); + Reduce(tt, z); + + while (--n > 0) + { + ImplSquare(z, tt); + Reduce(tt, z); + } + } + + public static uint Trace(ulong[] x) + { + // Non-zero-trace bits: 0 + return (uint)(x[0]) & 1U; + } + + protected static void ImplMultiply(ulong[] x, ulong[] y, ulong[] zz) + { + /* + * "Three-way recursion" as described in "Batch binary Edwards", Daniel J. Bernstein. + */ + + ulong f0 = x[0], f1 = x[1]; + f1 = ((f0 >> 57) ^ (f1 << 7)) & M57; + f0 &= M57; + + ulong g0 = y[0], g1 = y[1]; + g1 = ((g0 >> 57) ^ (g1 << 7)) & M57; + g0 &= M57; + + ulong[] H = new ulong[6]; + + ImplMulw(f0, g0, H, 0); // H(0) 57/56 bits + ImplMulw(f1, g1, H, 2); // H(INF) 57/54 bits + ImplMulw(f0 ^ f1, g0 ^ g1, H, 4); // H(1) 57/56 bits + + ulong r = H[1] ^ H[2]; + ulong z0 = H[0], + z3 = H[3], + z1 = H[4] ^ z0 ^ r, + z2 = H[5] ^ z3 ^ r; + + zz[0] = z0 ^ (z1 << 57); + zz[1] = (z1 >> 7) ^ (z2 << 50); + zz[2] = (z2 >> 14) ^ (z3 << 43); + zz[3] = (z3 >> 21); + } + + protected static void ImplMulw(ulong x, ulong y, ulong[] z, int zOff) + { + Debug.Assert(x >> 57 == 0); + Debug.Assert(y >> 57 == 0); + + ulong[] u = new ulong[8]; + //u[0] = 0; + u[1] = y; + u[2] = u[1] << 1; + u[3] = u[2] ^ y; + u[4] = u[2] << 1; + u[5] = u[4] ^ y; + u[6] = u[3] << 1; + u[7] = u[6] ^ y; + + uint j = (uint)x; + ulong g, h = 0, l = u[j & 7]; + int k = 48; + do + { + j = (uint)(x >> k); + g = u[j & 7] + ^ u[(j >> 3) & 7] << 3 + ^ u[(j >> 6) & 7] << 6; + l ^= (g << k); + h ^= (g >> -k); + } + while ((k -= 9) > 0); + + h ^= ((x & 0x0100804020100800UL) & (ulong)(((long)y << 7) >> 63)) >> 8; + + Debug.Assert(h >> 49 == 0); + + z[zOff ] = l & M57; + z[zOff + 1] = (l >> 57) ^ (h << 7); + } + + protected static void ImplSquare(ulong[] x, ulong[] zz) + { + Interleave.Expand64To128(x[0], zz, 0); + Interleave.Expand64To128(x[1], zz, 2); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT113FieldElement.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT113FieldElement.cs new file mode 100644 index 0000000..9ba25d9 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT113FieldElement.cs @@ -0,0 +1,216 @@ +using System; + +using Org.BouncyCastle.Math.Raw; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT113FieldElement + : ECFieldElement + { + protected internal readonly ulong[] x; + + public SecT113FieldElement(BigInteger x) + { + if (x == null || x.SignValue < 0 || x.BitLength > 113) + throw new ArgumentException("value invalid for SecT113FieldElement", "x"); + + this.x = SecT113Field.FromBigInteger(x); + } + + public SecT113FieldElement() + { + this.x = Nat128.Create64(); + } + + protected internal SecT113FieldElement(ulong[] x) + { + this.x = x; + } + + public override bool IsOne + { + get { return Nat128.IsOne64(x); } + } + + public override bool IsZero + { + get { return Nat128.IsZero64(x); } + } + + public override bool TestBitZero() + { + return (x[0] & 1L) != 0L; + } + + public override BigInteger ToBigInteger() + { + return Nat128.ToBigInteger64(x); + } + + public override string FieldName + { + get { return "SecT113Field"; } + } + + public override int FieldSize + { + get { return 113; } + } + + public override ECFieldElement Add(ECFieldElement b) + { + ulong[] z = Nat128.Create64(); + SecT113Field.Add(x, ((SecT113FieldElement)b).x, z); + return new SecT113FieldElement(z); + } + + public override ECFieldElement AddOne() + { + ulong[] z = Nat128.Create64(); + SecT113Field.AddOne(x, z); + return new SecT113FieldElement(z); + } + + public override ECFieldElement Subtract(ECFieldElement b) + { + // Addition and Subtraction are the same in F2m + return Add(b); + } + + public override ECFieldElement Multiply(ECFieldElement b) + { + ulong[] z = Nat128.Create64(); + SecT113Field.Multiply(x, ((SecT113FieldElement)b).x, z); + return new SecT113FieldElement(z); + } + + public override ECFieldElement MultiplyMinusProduct(ECFieldElement b, ECFieldElement x, ECFieldElement y) + { + return MultiplyPlusProduct(b, x, y); + } + + public override ECFieldElement MultiplyPlusProduct(ECFieldElement b, ECFieldElement x, ECFieldElement y) + { + ulong[] ax = this.x, bx = ((SecT113FieldElement)b).x; + ulong[] xx = ((SecT113FieldElement)x).x, yx = ((SecT113FieldElement)y).x; + + ulong[] tt = Nat128.CreateExt64(); + SecT113Field.MultiplyAddToExt(ax, bx, tt); + SecT113Field.MultiplyAddToExt(xx, yx, tt); + + ulong[] z = Nat128.Create64(); + SecT113Field.Reduce(tt, z); + return new SecT113FieldElement(z); + } + + public override ECFieldElement Divide(ECFieldElement b) + { + return Multiply(b.Invert()); + } + + public override ECFieldElement Negate() + { + return this; + } + + public override ECFieldElement Square() + { + ulong[] z = Nat128.Create64(); + SecT113Field.Square(x, z); + return new SecT113FieldElement(z); + } + + public override ECFieldElement SquareMinusProduct(ECFieldElement x, ECFieldElement y) + { + return SquarePlusProduct(x, y); + } + + public override ECFieldElement SquarePlusProduct(ECFieldElement x, ECFieldElement y) + { + ulong[] ax = this.x; + ulong[] xx = ((SecT113FieldElement)x).x, yx = ((SecT113FieldElement)y).x; + + ulong[] tt = Nat128.CreateExt64(); + SecT113Field.SquareAddToExt(ax, tt); + SecT113Field.MultiplyAddToExt(xx, yx, tt); + + ulong[] z = Nat128.Create64(); + SecT113Field.Reduce(tt, z); + return new SecT113FieldElement(z); + } + + public override ECFieldElement SquarePow(int pow) + { + if (pow < 1) + return this; + + ulong[] z = Nat128.Create64(); + SecT113Field.SquareN(x, pow, z); + return new SecT113FieldElement(z); + } + + public override ECFieldElement Invert() + { + ulong[] z = Nat128.Create64(); + SecT113Field.Invert(x, z); + return new SecT113FieldElement(z); + } + + public override ECFieldElement Sqrt() + { + ulong[] z = Nat128.Create64(); + SecT113Field.Sqrt(x, z); + return new SecT113FieldElement(z); + } + + public virtual int Representation + { + get { return F2mFieldElement.Tpb; } + } + + public virtual int M + { + get { return 113; } + } + + public virtual int K1 + { + get { return 9; } + } + + public virtual int K2 + { + get { return 0; } + } + + public virtual int K3 + { + get { return 0; } + } + + public override bool Equals(object obj) + { + return Equals(obj as SecT113FieldElement); + } + + public override bool Equals(ECFieldElement other) + { + return Equals(other as SecT113FieldElement); + } + + public virtual bool Equals(SecT113FieldElement other) + { + if (this == other) + return true; + if (null == other) + return false; + return Nat128.Eq64(x, other.x); + } + + public override int GetHashCode() + { + return 113009 ^ Arrays.GetHashCode(x, 0, 2); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT113R1Curve.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT113R1Curve.cs new file mode 100644 index 0000000..2705c94 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT113R1Curve.cs @@ -0,0 +1,98 @@ +using System; + +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT113R1Curve + : AbstractF2mCurve + { + private const int SecT113R1_DEFAULT_COORDS = COORD_LAMBDA_PROJECTIVE; + + protected readonly SecT113R1Point m_infinity; + + public SecT113R1Curve() + : base(113, 9, 0, 0) + { + this.m_infinity = new SecT113R1Point(this, null, null); + + this.m_a = FromBigInteger(new BigInteger(1, Hex.Decode("003088250CA6E7C7FE649CE85820F7"))); + this.m_b = FromBigInteger(new BigInteger(1, Hex.Decode("00E8BEE4D3E2260744188BE0E9C723"))); + this.m_order = new BigInteger(1, Hex.Decode("0100000000000000D9CCEC8A39E56F")); + this.m_cofactor = BigInteger.Two; + + this.m_coord = SecT113R1_DEFAULT_COORDS; + } + + protected override ECCurve CloneCurve() + { + return new SecT113R1Curve(); + } + + public override bool SupportsCoordinateSystem(int coord) + { + switch (coord) + { + case COORD_LAMBDA_PROJECTIVE: + return true; + default: + return false; + } + } + + public override ECPoint Infinity + { + get { return m_infinity; } + } + + public override int FieldSize + { + get { return 113; } + } + + public override ECFieldElement FromBigInteger(BigInteger x) + { + return new SecT113FieldElement(x); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, bool withCompression) + { + return new SecT113R1Point(this, x, y, withCompression); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + { + return new SecT113R1Point(this, x, y, zs, withCompression); + } + + public override bool IsKoblitz + { + get { return false; } + } + + public virtual int M + { + get { return 113; } + } + + public virtual bool IsTrinomial + { + get { return true; } + } + + public virtual int K1 + { + get { return 9; } + } + + public virtual int K2 + { + get { return 0; } + } + + public virtual int K3 + { + get { return 0; } + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT113R1Point.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT113R1Point.cs new file mode 100644 index 0000000..6ecc8b0 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT113R1Point.cs @@ -0,0 +1,281 @@ +using System; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT113R1Point + : AbstractF2mPoint + { + /** + * @deprecated Use ECCurve.createPoint to construct points + */ + public SecT113R1Point(ECCurve curve, ECFieldElement x, ECFieldElement y) + : this(curve, x, y, false) + { + } + + /** + * @deprecated per-point compression property will be removed, refer {@link #getEncoded(bool)} + */ + public SecT113R1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, bool withCompression) + : base(curve, x, y, withCompression) + { + if ((x == null) != (y == null)) + throw new ArgumentException("Exactly one of the field elements is null"); + } + + internal SecT113R1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + : base(curve, x, y, zs, withCompression) + { + } + + protected override ECPoint Detach() + { + return new SecT113R1Point(null, AffineXCoord, AffineYCoord); + } + + public override ECFieldElement YCoord + { + get + { + ECFieldElement X = RawXCoord, L = RawYCoord; + + if (this.IsInfinity || X.IsZero) + return L; + + // Y is actually Lambda (X + Y/X) here; convert to affine value on the fly + ECFieldElement Y = L.Add(X).Multiply(X); + + ECFieldElement Z = RawZCoords[0]; + if (!Z.IsOne) + { + Y = Y.Divide(Z); + } + + return Y; + } + } + + protected internal override bool CompressionYTilde + { + get + { + ECFieldElement X = this.RawXCoord; + if (X.IsZero) + return false; + + ECFieldElement Y = this.RawYCoord; + + // Y is actually Lambda (X + Y/X) here + return Y.TestBitZero() != X.TestBitZero(); + } + } + + public override ECPoint Add(ECPoint b) + { + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return this; + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + ECFieldElement X2 = b.RawXCoord; + + if (X1.IsZero) + { + if (X2.IsZero) + return curve.Infinity; + + return b.Add(this); + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + ECFieldElement L2 = b.RawYCoord, Z2 = b.RawZCoords[0]; + + bool Z1IsOne = Z1.IsOne; + ECFieldElement U2 = X2, S2 = L2; + if (!Z1IsOne) + { + U2 = U2.Multiply(Z1); + S2 = S2.Multiply(Z1); + } + + bool Z2IsOne = Z2.IsOne; + ECFieldElement U1 = X1, S1 = L1; + if (!Z2IsOne) + { + U1 = U1.Multiply(Z2); + S1 = S1.Multiply(Z2); + } + + ECFieldElement A = S1.Add(S2); + ECFieldElement B = U1.Add(U2); + + if (B.IsZero) + { + if (A.IsZero) + return Twice(); + + return curve.Infinity; + } + + ECFieldElement X3, L3, Z3; + if (X2.IsZero) + { + // TODO This can probably be optimized quite a bit + ECPoint p = this.Normalize(); + X1 = p.XCoord; + ECFieldElement Y1 = p.YCoord; + + ECFieldElement Y2 = L2; + ECFieldElement L = Y1.Add(Y2).Divide(X1); + + X3 = L.Square().Add(L).Add(X1).Add(curve.A); + if (X3.IsZero) + { + return new SecT113R1Point(curve, X3, curve.B.Sqrt(), IsCompressed); + } + + ECFieldElement Y3 = L.Multiply(X1.Add(X3)).Add(X3).Add(Y1); + L3 = Y3.Divide(X3).Add(X3); + Z3 = curve.FromBigInteger(BigInteger.One); + } + else + { + B = B.Square(); + + ECFieldElement AU1 = A.Multiply(U1); + ECFieldElement AU2 = A.Multiply(U2); + + X3 = AU1.Multiply(AU2); + if (X3.IsZero) + { + return new SecT113R1Point(curve, X3, curve.B.Sqrt(), IsCompressed); + } + + ECFieldElement ABZ2 = A.Multiply(B); + if (!Z2IsOne) + { + ABZ2 = ABZ2.Multiply(Z2); + } + + L3 = AU2.Add(B).SquarePlusProduct(ABZ2, L1.Add(Z1)); + + Z3 = ABZ2; + if (!Z1IsOne) + { + Z3 = Z3.Multiply(Z1); + } + } + + return new SecT113R1Point(curve, X3, L3, new ECFieldElement[]{ Z3 }, IsCompressed); + } + + public override ECPoint Twice() + { + if (this.IsInfinity) + return this; + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + if (X1.IsZero) + { + // A point with X == 0 is it's own Additive inverse + return curve.Infinity; + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + + bool Z1IsOne = Z1.IsOne; + ECFieldElement L1Z1 = Z1IsOne ? L1 : L1.Multiply(Z1); + ECFieldElement Z1Sq = Z1IsOne ? Z1 : Z1.Square(); + ECFieldElement a = curve.A; + ECFieldElement aZ1Sq = Z1IsOne ? a : a.Multiply(Z1Sq); + ECFieldElement T = L1.Square().Add(L1Z1).Add(aZ1Sq); + if (T.IsZero) + { + return new SecT113R1Point(curve, T, curve.B.Sqrt(), IsCompressed); + } + + ECFieldElement X3 = T.Square(); + ECFieldElement Z3 = Z1IsOne ? T : T.Multiply(Z1Sq); + + ECFieldElement X1Z1 = Z1IsOne ? X1 : X1.Multiply(Z1); + ECFieldElement L3 = X1Z1.SquarePlusProduct(T, L1Z1).Add(X3).Add(Z3); + + return new SecT113R1Point(curve, X3, L3, new ECFieldElement[]{ Z3 }, IsCompressed); + } + + public override ECPoint TwicePlus(ECPoint b) + { + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return Twice(); + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + if (X1.IsZero) + { + // A point with X == 0 is it's own Additive inverse + return b; + } + + ECFieldElement X2 = b.RawXCoord, Z2 = b.RawZCoords[0]; + if (X2.IsZero || !Z2.IsOne) + { + return Twice().Add(b); + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + ECFieldElement L2 = b.RawYCoord; + + ECFieldElement X1Sq = X1.Square(); + ECFieldElement L1Sq = L1.Square(); + ECFieldElement Z1Sq = Z1.Square(); + ECFieldElement L1Z1 = L1.Multiply(Z1); + + ECFieldElement T = curve.A.Multiply(Z1Sq).Add(L1Sq).Add(L1Z1); + ECFieldElement L2plus1 = L2.AddOne(); + ECFieldElement A = curve.A.Add(L2plus1).Multiply(Z1Sq).Add(L1Sq).MultiplyPlusProduct(T, X1Sq, Z1Sq); + ECFieldElement X2Z1Sq = X2.Multiply(Z1Sq); + ECFieldElement B = X2Z1Sq.Add(T).Square(); + + if (B.IsZero) + { + if (A.IsZero) + return b.Twice(); + + return curve.Infinity; + } + + if (A.IsZero) + { + return new SecT113R1Point(curve, A, curve.B.Sqrt(), IsCompressed); + } + + ECFieldElement X3 = A.Square().Multiply(X2Z1Sq); + ECFieldElement Z3 = A.Multiply(B).Multiply(Z1Sq); + ECFieldElement L3 = A.Add(B).Square().MultiplyPlusProduct(T, L2plus1, Z3); + + return new SecT113R1Point(curve, X3, L3, new ECFieldElement[]{ Z3 }, IsCompressed); + } + + public override ECPoint Negate() + { + if (IsInfinity) + return this; + + ECFieldElement X = this.RawXCoord; + if (X.IsZero) + return this; + + // L is actually Lambda (X + Y/X) here + ECFieldElement L = this.RawYCoord, Z = this.RawZCoords[0]; + return new SecT113R1Point(Curve, X, L.Add(Z), new ECFieldElement[]{ Z }, IsCompressed); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT113R2Curve.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT113R2Curve.cs new file mode 100644 index 0000000..abfd26d --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT113R2Curve.cs @@ -0,0 +1,98 @@ +using System; + +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT113R2Curve + : AbstractF2mCurve + { + private const int SecT113R2_DEFAULT_COORDS = COORD_LAMBDA_PROJECTIVE; + + protected readonly SecT113R2Point m_infinity; + + public SecT113R2Curve() + : base(113, 9, 0, 0) + { + this.m_infinity = new SecT113R2Point(this, null, null); + + this.m_a = FromBigInteger(new BigInteger(1, Hex.Decode("00689918DBEC7E5A0DD6DFC0AA55C7"))); + this.m_b = FromBigInteger(new BigInteger(1, Hex.Decode("0095E9A9EC9B297BD4BF36E059184F"))); + this.m_order = new BigInteger(1, Hex.Decode("010000000000000108789B2496AF93")); + this.m_cofactor = BigInteger.Two; + + this.m_coord = SecT113R2_DEFAULT_COORDS; + } + + protected override ECCurve CloneCurve() + { + return new SecT113R2Curve(); + } + + public override bool SupportsCoordinateSystem(int coord) + { + switch (coord) + { + case COORD_LAMBDA_PROJECTIVE: + return true; + default: + return false; + } + } + + public override ECPoint Infinity + { + get { return m_infinity; } + } + + public override int FieldSize + { + get { return 113; } + } + + public override ECFieldElement FromBigInteger(BigInteger x) + { + return new SecT113FieldElement(x); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, bool withCompression) + { + return new SecT113R2Point(this, x, y, withCompression); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + { + return new SecT113R2Point(this, x, y, zs, withCompression); + } + + public override bool IsKoblitz + { + get { return false; } + } + + public virtual int M + { + get { return 113; } + } + + public virtual bool IsTrinomial + { + get { return true; } + } + + public virtual int K1 + { + get { return 9; } + } + + public virtual int K2 + { + get { return 0; } + } + + public virtual int K3 + { + get { return 0; } + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT113R2Point.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT113R2Point.cs new file mode 100644 index 0000000..1453d78 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT113R2Point.cs @@ -0,0 +1,291 @@ +using System; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT113R2Point + : AbstractF2mPoint + { + /** + * @deprecated Use ECCurve.createPoint to construct points + */ + public SecT113R2Point(ECCurve curve, ECFieldElement x, ECFieldElement y) + : this(curve, x, y, false) + { + } + + /** + * @deprecated per-point compression property will be removed, refer {@link #getEncoded(bool)} + */ + public SecT113R2Point(ECCurve curve, ECFieldElement x, ECFieldElement y, bool withCompression) + : base(curve, x, y, withCompression) + { + if ((x == null) != (y == null)) + throw new ArgumentException("Exactly one of the field elements is null"); + } + + internal SecT113R2Point(ECCurve curve, ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + : base(curve, x, y, zs, withCompression) + { + } + + protected override ECPoint Detach() + { + return new SecT113R2Point(null, AffineXCoord, AffineYCoord); + } + + public override ECFieldElement YCoord + { + get + { + ECFieldElement X = RawXCoord, L = RawYCoord; + + if (this.IsInfinity || X.IsZero) + return L; + + // Y is actually Lambda (X + Y/X) here; convert to affine value on the fly + ECFieldElement Y = L.Add(X).Multiply(X); + + ECFieldElement Z = RawZCoords[0]; + if (!Z.IsOne) + { + Y = Y.Divide(Z); + } + + return Y; + } + } + + protected internal override bool CompressionYTilde + { + get + { + ECFieldElement X = this.RawXCoord; + if (X.IsZero) + return false; + + ECFieldElement Y = this.RawYCoord; + + // Y is actually Lambda (X + Y/X) here + return Y.TestBitZero() != X.TestBitZero(); + } + } + + public override ECPoint Add(ECPoint b) + { + if (this.IsInfinity) + { + return b; + } + if (b.IsInfinity) + { + return this; + } + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + ECFieldElement X2 = b.RawXCoord; + + if (X1.IsZero) + { + if (X2.IsZero) + return curve.Infinity; + + return b.Add(this); + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + ECFieldElement L2 = b.RawYCoord, Z2 = b.RawZCoords[0]; + + bool Z1IsOne = Z1.IsOne; + ECFieldElement U2 = X2, S2 = L2; + if (!Z1IsOne) + { + U2 = U2.Multiply(Z1); + S2 = S2.Multiply(Z1); + } + + bool Z2IsOne = Z2.IsOne; + ECFieldElement U1 = X1, S1 = L1; + if (!Z2IsOne) + { + U1 = U1.Multiply(Z2); + S1 = S1.Multiply(Z2); + } + + ECFieldElement A = S1.Add(S2); + ECFieldElement B = U1.Add(U2); + + if (B.IsZero) + { + if (A.IsZero) + return Twice(); + + return curve.Infinity; + } + + ECFieldElement X3, L3, Z3; + if (X2.IsZero) + { + // TODO This can probably be optimized quite a bit + ECPoint p = this.Normalize(); + X1 = p.XCoord; + ECFieldElement Y1 = p.YCoord; + + ECFieldElement Y2 = L2; + ECFieldElement L = Y1.Add(Y2).Divide(X1); + + X3 = L.Square().Add(L).Add(X1).Add(curve.A); + if (X3.IsZero) + { + return new SecT113R2Point(curve, X3, curve.B.Sqrt(), IsCompressed); + } + + ECFieldElement Y3 = L.Multiply(X1.Add(X3)).Add(X3).Add(Y1); + L3 = Y3.Divide(X3).Add(X3); + Z3 = curve.FromBigInteger(BigInteger.One); + } + else + { + B = B.Square(); + + ECFieldElement AU1 = A.Multiply(U1); + ECFieldElement AU2 = A.Multiply(U2); + + X3 = AU1.Multiply(AU2); + if (X3.IsZero) + { + return new SecT113R2Point(curve, X3, curve.B.Sqrt(), IsCompressed); + } + + ECFieldElement ABZ2 = A.Multiply(B); + if (!Z2IsOne) + { + ABZ2 = ABZ2.Multiply(Z2); + } + + L3 = AU2.Add(B).SquarePlusProduct(ABZ2, L1.Add(Z1)); + + Z3 = ABZ2; + if (!Z1IsOne) + { + Z3 = Z3.Multiply(Z1); + } + } + + return new SecT113R2Point(curve, X3, L3, new ECFieldElement[]{ Z3 }, IsCompressed); + } + + public override ECPoint Twice() + { + if (this.IsInfinity) + { + return this; + } + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + if (X1.IsZero) + { + // A point with X == 0 is it's own Additive inverse + return curve.Infinity; + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + + bool Z1IsOne = Z1.IsOne; + ECFieldElement L1Z1 = Z1IsOne ? L1 : L1.Multiply(Z1); + ECFieldElement Z1Sq = Z1IsOne ? Z1 : Z1.Square(); + ECFieldElement a = curve.A; + ECFieldElement aZ1Sq = Z1IsOne ? a : a.Multiply(Z1Sq); + ECFieldElement T = L1.Square().Add(L1Z1).Add(aZ1Sq); + if (T.IsZero) + { + return new SecT113R2Point(curve, T, curve.B.Sqrt(), IsCompressed); + } + + ECFieldElement X3 = T.Square(); + ECFieldElement Z3 = Z1IsOne ? T : T.Multiply(Z1Sq); + + ECFieldElement X1Z1 = Z1IsOne ? X1 : X1.Multiply(Z1); + ECFieldElement L3 = X1Z1.SquarePlusProduct(T, L1Z1).Add(X3).Add(Z3); + + return new SecT113R2Point(curve, X3, L3, new ECFieldElement[]{ Z3 }, IsCompressed); + } + + public override ECPoint TwicePlus(ECPoint b) + { + if (this.IsInfinity) + { + return b; + } + if (b.IsInfinity) + { + return Twice(); + } + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + if (X1.IsZero) + { + // A point with X == 0 is it's own Additive inverse + return b; + } + + ECFieldElement X2 = b.RawXCoord, Z2 = b.RawZCoords[0]; + if (X2.IsZero || !Z2.IsOne) + { + return Twice().Add(b); + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + ECFieldElement L2 = b.RawYCoord; + + ECFieldElement X1Sq = X1.Square(); + ECFieldElement L1Sq = L1.Square(); + ECFieldElement Z1Sq = Z1.Square(); + ECFieldElement L1Z1 = L1.Multiply(Z1); + + ECFieldElement T = curve.A.Multiply(Z1Sq).Add(L1Sq).Add(L1Z1); + ECFieldElement L2plus1 = L2.AddOne(); + ECFieldElement A = curve.A.Add(L2plus1).Multiply(Z1Sq).Add(L1Sq).MultiplyPlusProduct(T, X1Sq, Z1Sq); + ECFieldElement X2Z1Sq = X2.Multiply(Z1Sq); + ECFieldElement B = X2Z1Sq.Add(T).Square(); + + if (B.IsZero) + { + if (A.IsZero) + return b.Twice(); + + return curve.Infinity; + } + + if (A.IsZero) + { + return new SecT113R2Point(curve, A, curve.B.Sqrt(), IsCompressed); + } + + ECFieldElement X3 = A.Square().Multiply(X2Z1Sq); + ECFieldElement Z3 = A.Multiply(B).Multiply(Z1Sq); + ECFieldElement L3 = A.Add(B).Square().MultiplyPlusProduct(T, L2plus1, Z3); + + return new SecT113R2Point(curve, X3, L3, new ECFieldElement[]{ Z3 }, IsCompressed); + } + + public override ECPoint Negate() + { + if (IsInfinity) + return this; + + ECFieldElement X = this.RawXCoord; + if (X.IsZero) + return this; + + // L is actually Lambda (X + Y/X) here + ECFieldElement L = this.RawYCoord, Z = this.RawZCoords[0]; + return new SecT113R2Point(Curve, X, L.Add(Z), new ECFieldElement[]{ Z }, IsCompressed); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT131Field.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT131Field.cs new file mode 100644 index 0000000..1b6697a --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT131Field.cs @@ -0,0 +1,330 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Math.Raw; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT131Field + { + private const ulong M03 = ulong.MaxValue >> 61; + private const ulong M44 = ulong.MaxValue >> 20; + + private static readonly ulong[] ROOT_Z = new ulong[]{ 0x26BC4D789AF13523UL, 0x26BC4D789AF135E2UL, 0x6UL }; + + public static void Add(ulong[] x, ulong[] y, ulong[] z) + { + z[0] = x[0] ^ y[0]; + z[1] = x[1] ^ y[1]; + z[2] = x[2] ^ y[2]; + } + + public static void AddExt(ulong[] xx, ulong[] yy, ulong[] zz) + { + zz[0] = xx[0] ^ yy[0]; + zz[1] = xx[1] ^ yy[1]; + zz[2] = xx[2] ^ yy[2]; + zz[3] = xx[3] ^ yy[3]; + zz[4] = xx[4] ^ yy[4]; + } + + public static void AddOne(ulong[] x, ulong[] z) + { + z[0] = x[0] ^ 1UL; + z[1] = x[1]; + z[2] = x[2]; + } + + public static ulong[] FromBigInteger(BigInteger x) + { + ulong[] z = Nat192.FromBigInteger64(x); + Reduce61(z, 0); + return z; + } + + public static void Invert(ulong[] x, ulong[] z) + { + if (Nat192.IsZero64(x)) + throw new InvalidOperationException(); + + // Itoh-Tsujii inversion + + ulong[] t0 = Nat192.Create64(); + ulong[] t1 = Nat192.Create64(); + + Square(x, t0); + Multiply(t0, x, t0); + SquareN(t0, 2, t1); + Multiply(t1, t0, t1); + SquareN(t1, 4, t0); + Multiply(t0, t1, t0); + SquareN(t0, 8, t1); + Multiply(t1, t0, t1); + SquareN(t1, 16, t0); + Multiply(t0, t1, t0); + SquareN(t0, 32, t1); + Multiply(t1, t0, t1); + Square(t1, t1); + Multiply(t1, x, t1); + SquareN(t1, 65, t0); + Multiply(t0, t1, t0); + Square(t0, z); + } + + public static void Multiply(ulong[] x, ulong[] y, ulong[] z) + { + ulong[] tt = Nat192.CreateExt64(); + ImplMultiply(x, y, tt); + Reduce(tt, z); + } + + public static void MultiplyAddToExt(ulong[] x, ulong[] y, ulong[] zz) + { + ulong[] tt = Nat192.CreateExt64(); + ImplMultiply(x, y, tt); + AddExt(zz, tt, zz); + } + + public static void Reduce(ulong[] xx, ulong[] z) + { + ulong x0 = xx[0], x1 = xx[1], x2 = xx[2], x3 = xx[3], x4 = xx[4]; + + x1 ^= (x4 << 61) ^ (x4 << 63); + x2 ^= (x4 >> 3) ^ (x4 >> 1) ^ x4 ^ (x4 << 5); + x3 ^= (x4 >> 59); + + x0 ^= (x3 << 61) ^ (x3 << 63); + x1 ^= (x3 >> 3) ^ (x3 >> 1) ^ x3 ^ (x3 << 5); + x2 ^= (x3 >> 59); + + ulong t = x2 >> 3; + z[0] = x0 ^ t ^ (t << 2) ^ (t << 3) ^ (t << 8); + z[1] = x1 ^ (t >> 56); + z[2] = x2 & M03; + } + + public static void Reduce61(ulong[] z, int zOff) + { + ulong z2 = z[zOff + 2], t = z2 >> 3; + z[zOff ] ^= t ^ (t << 2) ^ (t << 3) ^ (t << 8); + z[zOff + 1] ^= (t >> 56); + z[zOff + 2] = z2 & M03; + } + + public static void Sqrt(ulong[] x, ulong[] z) + { + ulong[] odd = Nat192.Create64(); + + ulong u0, u1; + u0 = Interleave.Unshuffle(x[0]); u1 = Interleave.Unshuffle(x[1]); + ulong e0 = (u0 & 0x00000000FFFFFFFFUL) | (u1 << 32); + odd[0] = (u0 >> 32) | (u1 & 0xFFFFFFFF00000000UL); + + u0 = Interleave.Unshuffle(x[2]); + ulong e1 = (u0 & 0x00000000FFFFFFFFUL); + odd[1] = (u0 >> 32); + + Multiply(odd, ROOT_Z, z); + + z[0] ^= e0; + z[1] ^= e1; + } + + public static void Square(ulong[] x, ulong[] z) + { + ulong[] tt = Nat.Create64(5); + ImplSquare(x, tt); + Reduce(tt, z); + } + + public static void SquareAddToExt(ulong[] x, ulong[] zz) + { + ulong[] tt = Nat.Create64(5); + ImplSquare(x, tt); + AddExt(zz, tt, zz); + } + + public static void SquareN(ulong[] x, int n, ulong[] z) + { + Debug.Assert(n > 0); + + ulong[] tt = Nat.Create64(5); + ImplSquare(x, tt); + Reduce(tt, z); + + while (--n > 0) + { + ImplSquare(z, tt); + Reduce(tt, z); + } + } + + public static uint Trace(ulong[] x) + { + // Non-zero-trace bits: 0, 123, 129 + return (uint)(x[0] ^ (x[1] >> 59) ^ (x[2] >> 1)) & 1U; + } + + protected static void ImplCompactExt(ulong[] zz) + { + ulong z0 = zz[0], z1 = zz[1], z2 = zz[2], z3 = zz[3], z4 = zz[4], z5 = zz[5]; + zz[0] = z0 ^ (z1 << 44); + zz[1] = (z1 >> 20) ^ (z2 << 24); + zz[2] = (z2 >> 40) ^ (z3 << 4) + ^ (z4 << 48); + zz[3] = (z3 >> 60) ^ (z5 << 28) + ^ (z4 >> 16); + zz[4] = (z5 >> 36); + zz[5] = 0; + } + + protected static void ImplMultiply(ulong[] x, ulong[] y, ulong[] zz) + { + /* + * "Five-way recursion" as described in "Batch binary Edwards", Daniel J. Bernstein. + */ + + ulong f0 = x[0], f1 = x[1], f2 = x[2]; + f2 = ((f1 >> 24) ^ (f2 << 40)) & M44; + f1 = ((f0 >> 44) ^ (f1 << 20)) & M44; + f0 &= M44; + + ulong g0 = y[0], g1 = y[1], g2 = y[2]; + g2 = ((g1 >> 24) ^ (g2 << 40)) & M44; + g1 = ((g0 >> 44) ^ (g1 << 20)) & M44; + g0 &= M44; + + ulong[] H = new ulong[10]; + + ImplMulw(f0, g0, H, 0); // H(0) 44/43 bits + ImplMulw(f2, g2, H, 2); // H(INF) 44/41 bits + + ulong t0 = f0 ^ f1 ^ f2; + ulong t1 = g0 ^ g1 ^ g2; + + ImplMulw(t0, t1, H, 4); // H(1) 44/43 bits + + ulong t2 = (f1 << 1) ^ (f2 << 2); + ulong t3 = (g1 << 1) ^ (g2 << 2); + + ImplMulw(f0 ^ t2, g0 ^ t3, H, 6); // H(t) 44/45 bits + ImplMulw(t0 ^ t2, t1 ^ t3, H, 8); // H(t + 1) 44/45 bits + + ulong t4 = H[6] ^ H[8]; + ulong t5 = H[7] ^ H[9]; + + Debug.Assert(t5 >> 44 == 0); + + // Calculate V + ulong v0 = (t4 << 1) ^ H[6]; + ulong v1 = t4 ^ (t5 << 1) ^ H[7]; + ulong v2 = t5; + + // Calculate U + ulong u0 = H[0]; + ulong u1 = H[1] ^ H[0] ^ H[4]; + ulong u2 = H[1] ^ H[5]; + + // Calculate W + ulong w0 = u0 ^ v0 ^ (H[2] << 4) ^ (H[2] << 1); + ulong w1 = u1 ^ v1 ^ (H[3] << 4) ^ (H[3] << 1); + ulong w2 = u2 ^ v2; + + // Propagate carries + w1 ^= (w0 >> 44); w0 &= M44; + w2 ^= (w1 >> 44); w1 &= M44; + + Debug.Assert((w0 & 1UL) == 0); + + // Divide W by t + + w0 = (w0 >> 1) ^ ((w1 & 1UL) << 43); + w1 = (w1 >> 1) ^ ((w2 & 1UL) << 43); + w2 = (w2 >> 1); + + // Divide W by (t + 1) + + w0 ^= (w0 << 1); + w0 ^= (w0 << 2); + w0 ^= (w0 << 4); + w0 ^= (w0 << 8); + w0 ^= (w0 << 16); + w0 ^= (w0 << 32); + + w0 &= M44; w1 ^= (w0 >> 43); + + w1 ^= (w1 << 1); + w1 ^= (w1 << 2); + w1 ^= (w1 << 4); + w1 ^= (w1 << 8); + w1 ^= (w1 << 16); + w1 ^= (w1 << 32); + + w1 &= M44; w2 ^= (w1 >> 43); + + w2 ^= (w2 << 1); + w2 ^= (w2 << 2); + w2 ^= (w2 << 4); + w2 ^= (w2 << 8); + w2 ^= (w2 << 16); + w2 ^= (w2 << 32); + + Debug.Assert(w2 >> 42 == 0); + + zz[0] = u0; + zz[1] = u1 ^ w0 ^ H[2]; + zz[2] = u2 ^ w1 ^ w0 ^ H[3]; + zz[3] = w2 ^ w1; + zz[4] = w2 ^ H[2]; + zz[5] = H[3]; + + ImplCompactExt(zz); + } + + protected static void ImplMulw(ulong x, ulong y, ulong[] z, int zOff) + { + Debug.Assert(x >> 45 == 0); + Debug.Assert(y >> 45 == 0); + + ulong[] u = new ulong[8]; + //u[0] = 0; + u[1] = y; + u[2] = u[1] << 1; + u[3] = u[2] ^ y; + u[4] = u[2] << 1; + u[5] = u[4] ^ y; + u[6] = u[3] << 1; + u[7] = u[6] ^ y; + + uint j = (uint)x; + ulong g, h = 0, l = u[j & 7] + ^ u[(j >> 3) & 7] << 3 + ^ u[(j >> 6) & 7] << 6; + int k = 33; + do + { + j = (uint)(x >> k); + g = u[j & 7] + ^ u[(j >> 3) & 7] << 3 + ^ u[(j >> 6) & 7] << 6 + ^ u[(j >> 9) & 7] << 9; + l ^= (g << k); + h ^= (g >> -k); + } + while ((k -= 12) > 0); + + Debug.Assert(h >> 25 == 0); + + z[zOff ] = l & M44; + z[zOff + 1] = (l >> 44) ^ (h << 20); + } + + protected static void ImplSquare(ulong[] x, ulong[] zz) + { + Interleave.Expand64To128(x[0], zz, 0); + Interleave.Expand64To128(x[1], zz, 2); + + zz[4] = Interleave.Expand8to16((uint)x[2]); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT131FieldElement.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT131FieldElement.cs new file mode 100644 index 0000000..e0ecc10 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT131FieldElement.cs @@ -0,0 +1,216 @@ +using System; + +using Org.BouncyCastle.Math.Raw; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT131FieldElement + : ECFieldElement + { + protected readonly ulong[] x; + + public SecT131FieldElement(BigInteger x) + { + if (x == null || x.SignValue < 0 || x.BitLength > 131) + throw new ArgumentException("value invalid for SecT131FieldElement", "x"); + + this.x = SecT131Field.FromBigInteger(x); + } + + public SecT131FieldElement() + { + this.x = Nat192.Create64(); + } + + protected internal SecT131FieldElement(ulong[] x) + { + this.x = x; + } + + public override bool IsOne + { + get { return Nat192.IsOne64(x); } + } + + public override bool IsZero + { + get { return Nat192.IsZero64(x); } + } + + public override bool TestBitZero() + { + return (x[0] & 1UL) != 0UL; + } + + public override BigInteger ToBigInteger() + { + return Nat192.ToBigInteger64(x); + } + + public override string FieldName + { + get { return "SecT131Field"; } + } + + public override int FieldSize + { + get { return 131; } + } + + public override ECFieldElement Add(ECFieldElement b) + { + ulong[] z = Nat192.Create64(); + SecT131Field.Add(x, ((SecT131FieldElement)b).x, z); + return new SecT131FieldElement(z); + } + + public override ECFieldElement AddOne() + { + ulong[] z = Nat192.Create64(); + SecT131Field.AddOne(x, z); + return new SecT131FieldElement(z); + } + + public override ECFieldElement Subtract(ECFieldElement b) + { + // Addition and Subtraction are the same in F2m + return Add(b); + } + + public override ECFieldElement Multiply(ECFieldElement b) + { + ulong[] z = Nat192.Create64(); + SecT131Field.Multiply(x, ((SecT131FieldElement)b).x, z); + return new SecT131FieldElement(z); + } + + public override ECFieldElement MultiplyMinusProduct(ECFieldElement b, ECFieldElement x, ECFieldElement y) + { + return MultiplyPlusProduct(b, x, y); + } + + public override ECFieldElement MultiplyPlusProduct(ECFieldElement b, ECFieldElement x, ECFieldElement y) + { + ulong[] ax = this.x, bx = ((SecT131FieldElement)b).x; + ulong[] xx = ((SecT131FieldElement)x).x, yx = ((SecT131FieldElement)y).x; + + ulong[] tt = Nat.Create64(5); + SecT131Field.MultiplyAddToExt(ax, bx, tt); + SecT131Field.MultiplyAddToExt(xx, yx, tt); + + ulong[] z = Nat192.Create64(); + SecT131Field.Reduce(tt, z); + return new SecT131FieldElement(z); + } + + public override ECFieldElement Divide(ECFieldElement b) + { + return Multiply(b.Invert()); + } + + public override ECFieldElement Negate() + { + return this; + } + + public override ECFieldElement Square() + { + ulong[] z = Nat192.Create64(); + SecT131Field.Square(x, z); + return new SecT131FieldElement(z); + } + + public override ECFieldElement SquareMinusProduct(ECFieldElement x, ECFieldElement y) + { + return SquarePlusProduct(x, y); + } + + public override ECFieldElement SquarePlusProduct(ECFieldElement x, ECFieldElement y) + { + ulong[] ax = this.x; + ulong[] xx = ((SecT131FieldElement)x).x, yx = ((SecT131FieldElement)y).x; + + ulong[] tt = Nat.Create64(5); + SecT131Field.SquareAddToExt(ax, tt); + SecT131Field.MultiplyAddToExt(xx, yx, tt); + + ulong[] z = Nat192.Create64(); + SecT131Field.Reduce(tt, z); + return new SecT131FieldElement(z); + } + + public override ECFieldElement SquarePow(int pow) + { + if (pow < 1) + return this; + + ulong[] z = Nat192.Create64(); + SecT131Field.SquareN(x, pow, z); + return new SecT131FieldElement(z); + } + + public override ECFieldElement Invert() + { + ulong[] z = Nat192.Create64(); + SecT131Field.Invert(x, z); + return new SecT131FieldElement(z); + } + + public override ECFieldElement Sqrt() + { + ulong[] z = Nat192.Create64(); + SecT131Field.Sqrt(x, z); + return new SecT131FieldElement(z); + } + + public virtual int Representation + { + get { return F2mFieldElement.Ppb; } + } + + public virtual int M + { + get { return 131; } + } + + public virtual int K1 + { + get { return 2; } + } + + public virtual int K2 + { + get { return 3; } + } + + public virtual int K3 + { + get { return 8; } + } + + public override bool Equals(object obj) + { + return Equals(obj as SecT131FieldElement); + } + + public override bool Equals(ECFieldElement other) + { + return Equals(other as SecT131FieldElement); + } + + public virtual bool Equals(SecT131FieldElement other) + { + if (this == other) + return true; + if (null == other) + return false; + return Nat192.Eq64(x, other.x); + } + + public override int GetHashCode() + { + return 131832 ^ Arrays.GetHashCode(x, 0, 3); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT131R1Curve.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT131R1Curve.cs new file mode 100644 index 0000000..b73964c --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT131R1Curve.cs @@ -0,0 +1,98 @@ +using System; + +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT131R1Curve + : AbstractF2mCurve + { + private const int SecT131R1_DEFAULT_COORDS = COORD_LAMBDA_PROJECTIVE; + + protected readonly SecT131R1Point m_infinity; + + public SecT131R1Curve() + : base(131, 2, 3, 8) + { + this.m_infinity = new SecT131R1Point(this, null, null); + + this.m_a = FromBigInteger(new BigInteger(1, Hex.Decode("07A11B09A76B562144418FF3FF8C2570B8"))); + this.m_b = FromBigInteger(new BigInteger(1, Hex.Decode("0217C05610884B63B9C6C7291678F9D341"))); + this.m_order = new BigInteger(1, Hex.Decode("0400000000000000023123953A9464B54D")); + this.m_cofactor = BigInteger.Two; + + this.m_coord = SecT131R1_DEFAULT_COORDS; + } + + protected override ECCurve CloneCurve() + { + return new SecT131R1Curve(); + } + + public override bool SupportsCoordinateSystem(int coord) + { + switch (coord) + { + case COORD_LAMBDA_PROJECTIVE: + return true; + default: + return false; + } + } + + public override ECPoint Infinity + { + get { return m_infinity; } + } + + public override int FieldSize + { + get { return 131; } + } + + public override ECFieldElement FromBigInteger(BigInteger x) + { + return new SecT131FieldElement(x); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, bool withCompression) + { + return new SecT131R1Point(this, x, y, withCompression); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + { + return new SecT131R1Point(this, x, y, zs, withCompression); + } + + public override bool IsKoblitz + { + get { return false; } + } + + public virtual int M + { + get { return 131; } + } + + public virtual bool IsTrinomial + { + get { return false; } + } + + public virtual int K1 + { + get { return 2; } + } + + public virtual int K2 + { + get { return 3; } + } + + public virtual int K3 + { + get { return 8; } + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT131R1Point.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT131R1Point.cs new file mode 100644 index 0000000..7afdad8 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT131R1Point.cs @@ -0,0 +1,287 @@ +using System; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT131R1Point + : AbstractF2mPoint + { + /** + * @deprecated Use ECCurve.createPoint to construct points + */ + public SecT131R1Point(ECCurve curve, ECFieldElement x, ECFieldElement y) + : this(curve, x, y, false) + { + } + + /** + * @deprecated per-point compression property will be removed, refer {@link #getEncoded(bool)} + */ + public SecT131R1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, bool withCompression) + : base(curve, x, y, withCompression) + { + if ((x == null) != (y == null)) + throw new ArgumentException("Exactly one of the field elements is null"); + } + + internal SecT131R1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + : base(curve, x, y, zs, withCompression) + { + } + + protected override ECPoint Detach() + { + return new SecT131R1Point(null, AffineXCoord, AffineYCoord); + } + + public override ECFieldElement YCoord + { + get + { + ECFieldElement X = RawXCoord, L = RawYCoord; + + if (this.IsInfinity || X.IsZero) + return L; + + // Y is actually Lambda (X + Y/X) here; convert to affine value on the fly + ECFieldElement Y = L.Add(X).Multiply(X); + + ECFieldElement Z = RawZCoords[0]; + if (!Z.IsOne) + { + Y = Y.Divide(Z); + } + + return Y; + } + } + + protected internal override bool CompressionYTilde + { + get + { + ECFieldElement X = this.RawXCoord; + if (X.IsZero) + return false; + + ECFieldElement Y = this.RawYCoord; + + // Y is actually Lambda (X + Y/X) here + return Y.TestBitZero() != X.TestBitZero(); + } + } + + public override ECPoint Add(ECPoint b) + { + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return this; + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + ECFieldElement X2 = b.RawXCoord; + + if (X1.IsZero) + { + if (X2.IsZero) + return curve.Infinity; + + return b.Add(this); + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + ECFieldElement L2 = b.RawYCoord, Z2 = b.RawZCoords[0]; + + bool Z1IsOne = Z1.IsOne; + ECFieldElement U2 = X2, S2 = L2; + if (!Z1IsOne) + { + U2 = U2.Multiply(Z1); + S2 = S2.Multiply(Z1); + } + + bool Z2IsOne = Z2.IsOne; + ECFieldElement U1 = X1, S1 = L1; + if (!Z2IsOne) + { + U1 = U1.Multiply(Z2); + S1 = S1.Multiply(Z2); + } + + ECFieldElement A = S1.Add(S2); + ECFieldElement B = U1.Add(U2); + + if (B.IsZero) + { + if (A.IsZero) + return Twice(); + + return curve.Infinity; + } + + ECFieldElement X3, L3, Z3; + if (X2.IsZero) + { + // TODO This can probably be optimized quite a bit + ECPoint p = this.Normalize(); + X1 = p.XCoord; + ECFieldElement Y1 = p.YCoord; + + ECFieldElement Y2 = L2; + ECFieldElement L = Y1.Add(Y2).Divide(X1); + + X3 = L.Square().Add(L).Add(X1).Add(curve.A); + if (X3.IsZero) + { + return new SecT131R1Point(curve, X3, curve.B.Sqrt(), IsCompressed); + } + + ECFieldElement Y3 = L.Multiply(X1.Add(X3)).Add(X3).Add(Y1); + L3 = Y3.Divide(X3).Add(X3); + Z3 = curve.FromBigInteger(BigInteger.One); + } + else + { + B = B.Square(); + + ECFieldElement AU1 = A.Multiply(U1); + ECFieldElement AU2 = A.Multiply(U2); + + X3 = AU1.Multiply(AU2); + if (X3.IsZero) + { + return new SecT131R1Point(curve, X3, curve.B.Sqrt(), IsCompressed); + } + + ECFieldElement ABZ2 = A.Multiply(B); + if (!Z2IsOne) + { + ABZ2 = ABZ2.Multiply(Z2); + } + + L3 = AU2.Add(B).SquarePlusProduct(ABZ2, L1.Add(Z1)); + + Z3 = ABZ2; + if (!Z1IsOne) + { + Z3 = Z3.Multiply(Z1); + } + } + + return new SecT131R1Point(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint Twice() + { + if (this.IsInfinity) + { + return this; + } + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + if (X1.IsZero) + { + // A point with X == 0 is it's own Additive inverse + return curve.Infinity; + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + + bool Z1IsOne = Z1.IsOne; + ECFieldElement L1Z1 = Z1IsOne ? L1 : L1.Multiply(Z1); + ECFieldElement Z1Sq = Z1IsOne ? Z1 : Z1.Square(); + ECFieldElement a = curve.A; + ECFieldElement aZ1Sq = Z1IsOne ? a : a.Multiply(Z1Sq); + ECFieldElement T = L1.Square().Add(L1Z1).Add(aZ1Sq); + if (T.IsZero) + { + return new SecT131R1Point(curve, T, curve.B.Sqrt(), IsCompressed); + } + + ECFieldElement X3 = T.Square(); + ECFieldElement Z3 = Z1IsOne ? T : T.Multiply(Z1Sq); + + ECFieldElement X1Z1 = Z1IsOne ? X1 : X1.Multiply(Z1); + ECFieldElement L3 = X1Z1.SquarePlusProduct(T, L1Z1).Add(X3).Add(Z3); + + return new SecT131R1Point(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint TwicePlus(ECPoint b) + { + if (this.IsInfinity) + { + return b; + } + if (b.IsInfinity) + { + return Twice(); + } + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + if (X1.IsZero) + { + // A point with X == 0 is it's own Additive inverse + return b; + } + + ECFieldElement X2 = b.RawXCoord, Z2 = b.RawZCoords[0]; + if (X2.IsZero || !Z2.IsOne) + { + return Twice().Add(b); + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + ECFieldElement L2 = b.RawYCoord; + + ECFieldElement X1Sq = X1.Square(); + ECFieldElement L1Sq = L1.Square(); + ECFieldElement Z1Sq = Z1.Square(); + ECFieldElement L1Z1 = L1.Multiply(Z1); + + ECFieldElement T = curve.A.Multiply(Z1Sq).Add(L1Sq).Add(L1Z1); + ECFieldElement L2plus1 = L2.AddOne(); + ECFieldElement A = curve.A.Add(L2plus1).Multiply(Z1Sq).Add(L1Sq).MultiplyPlusProduct(T, X1Sq, Z1Sq); + ECFieldElement X2Z1Sq = X2.Multiply(Z1Sq); + ECFieldElement B = X2Z1Sq.Add(T).Square(); + + if (B.IsZero) + { + if (A.IsZero) + return b.Twice(); + + return curve.Infinity; + } + + if (A.IsZero) + { + return new SecT131R1Point(curve, A, curve.B.Sqrt(), IsCompressed); + } + + ECFieldElement X3 = A.Square().Multiply(X2Z1Sq); + ECFieldElement Z3 = A.Multiply(B).Multiply(Z1Sq); + ECFieldElement L3 = A.Add(B).Square().MultiplyPlusProduct(T, L2plus1, Z3); + + return new SecT131R1Point(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint Negate() + { + if (IsInfinity) + return this; + + ECFieldElement X = this.RawXCoord; + if (X.IsZero) + return this; + + // L is actually Lambda (X + Y/X) here + ECFieldElement L = this.RawYCoord, Z = this.RawZCoords[0]; + return new SecT131R1Point(Curve, X, L.Add(Z), new ECFieldElement[] { Z }, IsCompressed); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT131R2Curve.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT131R2Curve.cs new file mode 100644 index 0000000..724921c --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT131R2Curve.cs @@ -0,0 +1,98 @@ +using System; + +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT131R2Curve + : AbstractF2mCurve + { + private const int SecT131R2_DEFAULT_COORDS = COORD_LAMBDA_PROJECTIVE; + + protected readonly SecT131R2Point m_infinity; + + public SecT131R2Curve() + : base(131, 2, 3, 8) + { + this.m_infinity = new SecT131R2Point(this, null, null); + + this.m_a = FromBigInteger(new BigInteger(1, Hex.Decode("03E5A88919D7CAFCBF415F07C2176573B2"))); + this.m_b = FromBigInteger(new BigInteger(1, Hex.Decode("04B8266A46C55657AC734CE38F018F2192"))); + this.m_order = new BigInteger(1, Hex.Decode("0400000000000000016954A233049BA98F")); + this.m_cofactor = BigInteger.Two; + + this.m_coord = SecT131R2_DEFAULT_COORDS; + } + + protected override ECCurve CloneCurve() + { + return new SecT131R2Curve(); + } + + public override bool SupportsCoordinateSystem(int coord) + { + switch (coord) + { + case COORD_LAMBDA_PROJECTIVE: + return true; + default: + return false; + } + } + + public override int FieldSize + { + get { return 131; } + } + + public override ECFieldElement FromBigInteger(BigInteger x) + { + return new SecT131FieldElement(x); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, bool withCompression) + { + return new SecT131R2Point(this, x, y, withCompression); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + { + return new SecT131R2Point(this, x, y, zs, withCompression); + } + + public override ECPoint Infinity + { + get { return m_infinity; } + } + + public override bool IsKoblitz + { + get { return false; } + } + + public virtual int M + { + get { return 131; } + } + + public virtual bool IsTrinomial + { + get { return false; } + } + + public virtual int K1 + { + get { return 2; } + } + + public virtual int K2 + { + get { return 3; } + } + + public virtual int K3 + { + get { return 8; } + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT131R2Point.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT131R2Point.cs new file mode 100644 index 0000000..be61561 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT131R2Point.cs @@ -0,0 +1,283 @@ +using System; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT131R2Point + : AbstractF2mPoint + { + /** + * @deprecated Use ECCurve.createPoint to construct points + */ + public SecT131R2Point(ECCurve curve, ECFieldElement x, ECFieldElement y) + : this(curve, x, y, false) + { + } + + /** + * @deprecated per-point compression property will be removed, refer {@link #getEncoded(bool)} + */ + public SecT131R2Point(ECCurve curve, ECFieldElement x, ECFieldElement y, bool withCompression) + : base(curve, x, y, withCompression) + { + if ((x == null) != (y == null)) + throw new ArgumentException("Exactly one of the field elements is null"); + } + + internal SecT131R2Point(ECCurve curve, ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + : base(curve, x, y, zs, withCompression) + { + } + + protected override ECPoint Detach() + { + return new SecT131R2Point(null, AffineXCoord, AffineYCoord); + } + + public override ECFieldElement YCoord + { + get + { + ECFieldElement X = RawXCoord, L = RawYCoord; + + if (this.IsInfinity || X.IsZero) + return L; + + // Y is actually Lambda (X + Y/X) here; convert to affine value on the fly + ECFieldElement Y = L.Add(X).Multiply(X); + + ECFieldElement Z = RawZCoords[0]; + if (!Z.IsOne) + { + Y = Y.Divide(Z); + } + + return Y; + } + } + + protected internal override bool CompressionYTilde + { + get + { + ECFieldElement X = this.RawXCoord; + if (X.IsZero) + return false; + + ECFieldElement Y = this.RawYCoord; + + // Y is actually Lambda (X + Y/X) here + return Y.TestBitZero() != X.TestBitZero(); + } + } + + public override ECPoint Add(ECPoint b) + { + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return this; + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + ECFieldElement X2 = b.RawXCoord; + + if (X1.IsZero) + { + if (X2.IsZero) + return curve.Infinity; + + return b.Add(this); + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + ECFieldElement L2 = b.RawYCoord, Z2 = b.RawZCoords[0]; + + bool Z1IsOne = Z1.IsOne; + ECFieldElement U2 = X2, S2 = L2; + if (!Z1IsOne) + { + U2 = U2.Multiply(Z1); + S2 = S2.Multiply(Z1); + } + + bool Z2IsOne = Z2.IsOne; + ECFieldElement U1 = X1, S1 = L1; + if (!Z2IsOne) + { + U1 = U1.Multiply(Z2); + S1 = S1.Multiply(Z2); + } + + ECFieldElement A = S1.Add(S2); + ECFieldElement B = U1.Add(U2); + + if (B.IsZero) + { + if (A.IsZero) + return Twice(); + + return curve.Infinity; + } + + ECFieldElement X3, L3, Z3; + if (X2.IsZero) + { + // TODO This can probably be optimized quite a bit + ECPoint p = this.Normalize(); + X1 = p.XCoord; + ECFieldElement Y1 = p.YCoord; + + ECFieldElement Y2 = L2; + ECFieldElement L = Y1.Add(Y2).Divide(X1); + + X3 = L.Square().Add(L).Add(X1).Add(curve.A); + if (X3.IsZero) + { + return new SecT131R2Point(curve, X3, curve.B.Sqrt(), IsCompressed); + } + + ECFieldElement Y3 = L.Multiply(X1.Add(X3)).Add(X3).Add(Y1); + L3 = Y3.Divide(X3).Add(X3); + Z3 = curve.FromBigInteger(BigInteger.One); + } + else + { + B = B.Square(); + + ECFieldElement AU1 = A.Multiply(U1); + ECFieldElement AU2 = A.Multiply(U2); + + X3 = AU1.Multiply(AU2); + if (X3.IsZero) + { + return new SecT131R2Point(curve, X3, curve.B.Sqrt(), IsCompressed); + } + + ECFieldElement ABZ2 = A.Multiply(B); + if (!Z2IsOne) + { + ABZ2 = ABZ2.Multiply(Z2); + } + + L3 = AU2.Add(B).SquarePlusProduct(ABZ2, L1.Add(Z1)); + + Z3 = ABZ2; + if (!Z1IsOne) + { + Z3 = Z3.Multiply(Z1); + } + } + + return new SecT131R2Point(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint Twice() + { + if (this.IsInfinity) + { + return this; + } + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + if (X1.IsZero) + { + // A point with X == 0 is it's own Additive inverse + return curve.Infinity; + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + + bool Z1IsOne = Z1.IsOne; + ECFieldElement L1Z1 = Z1IsOne ? L1 : L1.Multiply(Z1); + ECFieldElement Z1Sq = Z1IsOne ? Z1 : Z1.Square(); + ECFieldElement a = curve.A; + ECFieldElement aZ1Sq = Z1IsOne ? a : a.Multiply(Z1Sq); + ECFieldElement T = L1.Square().Add(L1Z1).Add(aZ1Sq); + if (T.IsZero) + { + return new SecT131R2Point(curve, T, curve.B.Sqrt(), IsCompressed); + } + + ECFieldElement X3 = T.Square(); + ECFieldElement Z3 = Z1IsOne ? T : T.Multiply(Z1Sq); + + ECFieldElement X1Z1 = Z1IsOne ? X1 : X1.Multiply(Z1); + ECFieldElement L3 = X1Z1.SquarePlusProduct(T, L1Z1).Add(X3).Add(Z3); + + return new SecT131R2Point(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint TwicePlus(ECPoint b) + { + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return Twice(); + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + if (X1.IsZero) + { + // A point with X == 0 is it's own Additive inverse + return b; + } + + ECFieldElement X2 = b.RawXCoord, Z2 = b.RawZCoords[0]; + if (X2.IsZero || !Z2.IsOne) + { + return Twice().Add(b); + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + ECFieldElement L2 = b.RawYCoord; + + ECFieldElement X1Sq = X1.Square(); + ECFieldElement L1Sq = L1.Square(); + ECFieldElement Z1Sq = Z1.Square(); + ECFieldElement L1Z1 = L1.Multiply(Z1); + + ECFieldElement T = curve.A.Multiply(Z1Sq).Add(L1Sq).Add(L1Z1); + ECFieldElement L2plus1 = L2.AddOne(); + ECFieldElement A = curve.A.Add(L2plus1).Multiply(Z1Sq).Add(L1Sq).MultiplyPlusProduct(T, X1Sq, Z1Sq); + ECFieldElement X2Z1Sq = X2.Multiply(Z1Sq); + ECFieldElement B = X2Z1Sq.Add(T).Square(); + + if (B.IsZero) + { + if (A.IsZero) + return b.Twice(); + + return curve.Infinity; + } + + if (A.IsZero) + { + return new SecT131R2Point(curve, A, curve.B.Sqrt(), IsCompressed); + } + + ECFieldElement X3 = A.Square().Multiply(X2Z1Sq); + ECFieldElement Z3 = A.Multiply(B).Multiply(Z1Sq); + ECFieldElement L3 = A.Add(B).Square().MultiplyPlusProduct(T, L2plus1, Z3); + + return new SecT131R2Point(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint Negate() + { + if (IsInfinity) + return this; + + ECFieldElement X = this.RawXCoord; + if (X.IsZero) + return this; + + // L is actually Lambda (X + Y/X) here + ECFieldElement L = this.RawYCoord, Z = this.RawZCoords[0]; + return new SecT131R2Point(Curve, X, L.Add(Z), new ECFieldElement[] { Z }, IsCompressed); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT163Field.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT163Field.cs new file mode 100644 index 0000000..b1e9aa7 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT163Field.cs @@ -0,0 +1,340 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Math.Raw; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT163Field + { + private const ulong M35 = ulong.MaxValue >> 29; + private const ulong M55 = ulong.MaxValue >> 9; + + private static readonly ulong[] ROOT_Z = new ulong[]{ 0xB6DB6DB6DB6DB6B0UL, 0x492492492492DB6DUL, 0x492492492UL }; + + public static void Add(ulong[] x, ulong[] y, ulong[] z) + { + z[0] = x[0] ^ y[0]; + z[1] = x[1] ^ y[1]; + z[2] = x[2] ^ y[2]; + } + + public static void AddExt(ulong[] xx, ulong[] yy, ulong[] zz) + { + zz[0] = xx[0] ^ yy[0]; + zz[1] = xx[1] ^ yy[1]; + zz[2] = xx[2] ^ yy[2]; + zz[3] = xx[3] ^ yy[3]; + zz[4] = xx[4] ^ yy[4]; + zz[5] = xx[5] ^ yy[5]; + } + + public static void AddOne(ulong[] x, ulong[] z) + { + z[0] = x[0] ^ 1UL; + z[1] = x[1]; + z[2] = x[2]; + } + + public static ulong[] FromBigInteger(BigInteger x) + { + ulong[] z = Nat192.FromBigInteger64(x); + Reduce29(z, 0); + return z; + } + + public static void Invert(ulong[] x, ulong[] z) + { + if (Nat192.IsZero64(x)) + throw new InvalidOperationException(); + + // Itoh-Tsujii inversion with bases { 2, 3 } + + ulong[] t0 = Nat192.Create64(); + ulong[] t1 = Nat192.Create64(); + + Square(x, t0); + + // 3 | 162 + SquareN(t0, 1, t1); + Multiply(t0, t1, t0); + SquareN(t1, 1, t1); + Multiply(t0, t1, t0); + + // 3 | 54 + SquareN(t0, 3, t1); + Multiply(t0, t1, t0); + SquareN(t1, 3, t1); + Multiply(t0, t1, t0); + + // 3 | 18 + SquareN(t0, 9, t1); + Multiply(t0, t1, t0); + SquareN(t1, 9, t1); + Multiply(t0, t1, t0); + + // 3 | 6 + SquareN(t0, 27, t1); + Multiply(t0, t1, t0); + SquareN(t1, 27, t1); + Multiply(t0, t1, t0); + + // 2 | 2 + SquareN(t0, 81, t1); + Multiply(t0, t1, z); + } + + public static void Multiply(ulong[] x, ulong[] y, ulong[] z) + { + ulong[] tt = Nat192.CreateExt64(); + ImplMultiply(x, y, tt); + Reduce(tt, z); + } + + public static void MultiplyAddToExt(ulong[] x, ulong[] y, ulong[] zz) + { + ulong[] tt = Nat192.CreateExt64(); + ImplMultiply(x, y, tt); + AddExt(zz, tt, zz); + } + + public static void Reduce(ulong[] xx, ulong[] z) + { + ulong x0 = xx[0], x1 = xx[1], x2 = xx[2], x3 = xx[3], x4 = xx[4], x5 = xx[5]; + + x2 ^= (x5 << 29) ^ (x5 << 32) ^ (x5 << 35) ^ (x5 << 36); + x3 ^= (x5 >> 35) ^ (x5 >> 32) ^ (x5 >> 29) ^ (x5 >> 28); + + x1 ^= (x4 << 29) ^ (x4 << 32) ^ (x4 << 35) ^ (x4 << 36); + x2 ^= (x4 >> 35) ^ (x4 >> 32) ^ (x4 >> 29) ^ (x4 >> 28); + + x0 ^= (x3 << 29) ^ (x3 << 32) ^ (x3 << 35) ^ (x3 << 36); + x1 ^= (x3 >> 35) ^ (x3 >> 32) ^ (x3 >> 29) ^ (x3 >> 28); + + ulong t = x2 >> 35; + z[0] = x0 ^ t ^ (t << 3) ^ (t << 6) ^ (t << 7); + z[1] = x1; + z[2] = x2 & M35; + } + + public static void Reduce29(ulong[] z, int zOff) + { + ulong z2 = z[zOff + 2], t = z2 >> 35; + z[zOff ] ^= t ^ (t << 3) ^ (t << 6) ^ (t << 7); + z[zOff + 2] = z2 & M35; + } + + public static void Sqrt(ulong[] x, ulong[] z) + { + ulong[] odd = Nat192.Create64(); + + ulong u0, u1; + u0 = Interleave.Unshuffle(x[0]); u1 = Interleave.Unshuffle(x[1]); + ulong e0 = (u0 & 0x00000000FFFFFFFFUL) | (u1 << 32); + odd[0] = (u0 >> 32) | (u1 & 0xFFFFFFFF00000000UL); + + u0 = Interleave.Unshuffle(x[2]); + ulong e1 = (u0 & 0x00000000FFFFFFFFUL); + odd[1] = (u0 >> 32); + + Multiply(odd, ROOT_Z, z); + + z[0] ^= e0; + z[1] ^= e1; + } + + public static void Square(ulong[] x, ulong[] z) + { + ulong[] tt = Nat192.CreateExt64(); + ImplSquare(x, tt); + Reduce(tt, z); + } + + public static void SquareAddToExt(ulong[] x, ulong[] zz) + { + ulong[] tt = Nat192.CreateExt64(); + ImplSquare(x, tt); + AddExt(zz, tt, zz); + } + + public static void SquareN(ulong[] x, int n, ulong[] z) + { + Debug.Assert(n > 0); + + ulong[] tt = Nat192.CreateExt64(); + ImplSquare(x, tt); + Reduce(tt, z); + + while (--n > 0) + { + ImplSquare(z, tt); + Reduce(tt, z); + } + } + + public static uint Trace(ulong[] x) + { + // Non-zero-trace bits: 0, 157 + return (uint)(x[0] ^ (x[2] >> 29)) & 1U; + } + + protected static void ImplCompactExt(ulong[] zz) + { + ulong z0 = zz[0], z1 = zz[1], z2 = zz[2], z3 = zz[3], z4 = zz[4], z5 = zz[5]; + zz[0] = z0 ^ (z1 << 55); + zz[1] = (z1 >> 9) ^ (z2 << 46); + zz[2] = (z2 >> 18) ^ (z3 << 37); + zz[3] = (z3 >> 27) ^ (z4 << 28); + zz[4] = (z4 >> 36) ^ (z5 << 19); + zz[5] = (z5 >> 45); + } + + protected static void ImplMultiply(ulong[] x, ulong[] y, ulong[] zz) + { + /* + * "Five-way recursion" as described in "Batch binary Edwards", Daniel J. Bernstein. + */ + + ulong f0 = x[0], f1 = x[1], f2 = x[2]; + f2 = ((f1 >> 46) ^ (f2 << 18)); + f1 = ((f0 >> 55) ^ (f1 << 9)) & M55; + f0 &= M55; + + ulong g0 = y[0], g1 = y[1], g2 = y[2]; + g2 = ((g1 >> 46) ^ (g2 << 18)); + g1 = ((g0 >> 55) ^ (g1 << 9)) & M55; + g0 &= M55; + + ulong[] H = new ulong[10]; + + ImplMulw(f0, g0, H, 0); // H(0) 55/54 bits + ImplMulw(f2, g2, H, 2); // H(INF) 55/50 bits + + ulong t0 = f0 ^ f1 ^ f2; + ulong t1 = g0 ^ g1 ^ g2; + + ImplMulw(t0, t1, H, 4); // H(1) 55/54 bits + + ulong t2 = (f1 << 1) ^ (f2 << 2); + ulong t3 = (g1 << 1) ^ (g2 << 2); + + ImplMulw(f0 ^ t2, g0 ^ t3, H, 6); // H(t) 55/56 bits + ImplMulw(t0 ^ t2, t1 ^ t3, H, 8); // H(t + 1) 55/56 bits + + ulong t4 = H[6] ^ H[8]; + ulong t5 = H[7] ^ H[9]; + + Debug.Assert(t5 >> 55 == 0); + + // Calculate V + ulong v0 = (t4 << 1) ^ H[6]; + ulong v1 = t4 ^ (t5 << 1) ^ H[7]; + ulong v2 = t5; + + // Calculate U + ulong u0 = H[0]; + ulong u1 = H[1] ^ H[0] ^ H[4]; + ulong u2 = H[1] ^ H[5]; + + // Calculate W + ulong w0 = u0 ^ v0 ^ (H[2] << 4) ^ (H[2] << 1); + ulong w1 = u1 ^ v1 ^ (H[3] << 4) ^ (H[3] << 1); + ulong w2 = u2 ^ v2; + + // Propagate carries + w1 ^= (w0 >> 55); w0 &= M55; + w2 ^= (w1 >> 55); w1 &= M55; + + Debug.Assert((w0 & 1UL) == 0UL); + + // Divide W by t + + w0 = (w0 >> 1) ^ ((w1 & 1UL) << 54); + w1 = (w1 >> 1) ^ ((w2 & 1UL) << 54); + w2 = (w2 >> 1); + + // Divide W by (t + 1) + + w0 ^= (w0 << 1); + w0 ^= (w0 << 2); + w0 ^= (w0 << 4); + w0 ^= (w0 << 8); + w0 ^= (w0 << 16); + w0 ^= (w0 << 32); + + w0 &= M55; w1 ^= (w0 >> 54); + + w1 ^= (w1 << 1); + w1 ^= (w1 << 2); + w1 ^= (w1 << 4); + w1 ^= (w1 << 8); + w1 ^= (w1 << 16); + w1 ^= (w1 << 32); + + w1 &= M55; w2 ^= (w1 >> 54); + + w2 ^= (w2 << 1); + w2 ^= (w2 << 2); + w2 ^= (w2 << 4); + w2 ^= (w2 << 8); + w2 ^= (w2 << 16); + w2 ^= (w2 << 32); + + Debug.Assert(w2 >> 52 == 0); + + zz[0] = u0; + zz[1] = u1 ^ w0 ^ H[2]; + zz[2] = u2 ^ w1 ^ w0 ^ H[3]; + zz[3] = w2 ^ w1; + zz[4] = w2 ^ H[2]; + zz[5] = H[3]; + + ImplCompactExt(zz); + } + + protected static void ImplMulw(ulong x, ulong y, ulong[] z, int zOff) + { + Debug.Assert(x >> 56 == 0); + Debug.Assert(y >> 56 == 0); + + ulong[] u = new ulong[8]; + //u[0] = 0; + u[1] = y; + u[2] = u[1] << 1; + u[3] = u[2] ^ y; + u[4] = u[2] << 1; + u[5] = u[4] ^ y; + u[6] = u[3] << 1; + u[7] = u[6] ^ y; + + uint j = (uint)x; + ulong g, h = 0, l = u[j & 3]; + int k = 47; + do + { + j = (uint)(x >> k); + g = u[j & 7] + ^ u[(j >> 3) & 7] << 3 + ^ u[(j >> 6) & 7] << 6; + l ^= (g << k); + h ^= (g >> -k); + } + while ((k -= 9) > 0); + + Debug.Assert(h >> 47 == 0); + + z[zOff ] = l & M55; + z[zOff + 1] = (l >> 55) ^ (h << 9); + } + + protected static void ImplSquare(ulong[] x, ulong[] zz) + { + Interleave.Expand64To128(x[0], zz, 0); + Interleave.Expand64To128(x[1], zz, 2); + + ulong x2 = x[2]; + zz[4] = Interleave.Expand32to64((uint)x2); + zz[5] = Interleave.Expand8to16((uint)(x2 >> 32)); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT163FieldElement.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT163FieldElement.cs new file mode 100644 index 0000000..8953fb5 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT163FieldElement.cs @@ -0,0 +1,216 @@ +using System; + +using Org.BouncyCastle.Math.Raw; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT163FieldElement + : ECFieldElement + { + protected readonly ulong[] x; + + public SecT163FieldElement(BigInteger x) + { + if (x == null || x.SignValue < 0 || x.BitLength > 163) + throw new ArgumentException("value invalid for SecT163FieldElement", "x"); + + this.x = SecT163Field.FromBigInteger(x); + } + + public SecT163FieldElement() + { + this.x = Nat192.Create64(); + } + + protected internal SecT163FieldElement(ulong[] x) + { + this.x = x; + } + + public override bool IsOne + { + get { return Nat192.IsOne64(x); } + } + + public override bool IsZero + { + get { return Nat192.IsZero64(x); } + } + + public override bool TestBitZero() + { + return (x[0] & 1L) != 0L; + } + + public override BigInteger ToBigInteger() + { + return Nat192.ToBigInteger64(x); + } + + public override string FieldName + { + get { return "SecT163Field"; } + } + + public override int FieldSize + { + get { return 163; } + } + + public override ECFieldElement Add(ECFieldElement b) + { + ulong[] z = Nat192.Create64(); + SecT163Field.Add(x, ((SecT163FieldElement)b).x, z); + return new SecT163FieldElement(z); + } + + public override ECFieldElement AddOne() + { + ulong[] z = Nat192.Create64(); + SecT163Field.AddOne(x, z); + return new SecT163FieldElement(z); + } + + public override ECFieldElement Subtract(ECFieldElement b) + { + // Addition and subtraction are the same in F2m + return Add(b); + } + + public override ECFieldElement Multiply(ECFieldElement b) + { + ulong[] z = Nat192.Create64(); + SecT163Field.Multiply(x, ((SecT163FieldElement)b).x, z); + return new SecT163FieldElement(z); + } + + public override ECFieldElement MultiplyMinusProduct(ECFieldElement b, ECFieldElement x, ECFieldElement y) + { + return MultiplyPlusProduct(b, x, y); + } + + public override ECFieldElement MultiplyPlusProduct(ECFieldElement b, ECFieldElement x, ECFieldElement y) + { + ulong[] ax = this.x, bx = ((SecT163FieldElement)b).x; + ulong[] xx = ((SecT163FieldElement)x).x, yx = ((SecT163FieldElement)y).x; + + ulong[] tt = Nat192.CreateExt64(); + SecT163Field.MultiplyAddToExt(ax, bx, tt); + SecT163Field.MultiplyAddToExt(xx, yx, tt); + + ulong[] z = Nat192.Create64(); + SecT163Field.Reduce(tt, z); + return new SecT163FieldElement(z); + } + + public override ECFieldElement Divide(ECFieldElement b) + { + return Multiply(b.Invert()); + } + + public override ECFieldElement Negate() + { + return this; + } + + public override ECFieldElement Square() + { + ulong[] z = Nat192.Create64(); + SecT163Field.Square(x, z); + return new SecT163FieldElement(z); + } + + public override ECFieldElement SquareMinusProduct(ECFieldElement x, ECFieldElement y) + { + return SquarePlusProduct(x, y); + } + + public override ECFieldElement SquarePlusProduct(ECFieldElement x, ECFieldElement y) + { + ulong[] ax = this.x; + ulong[] xx = ((SecT163FieldElement)x).x, yx = ((SecT163FieldElement)y).x; + + ulong[] tt = Nat192.CreateExt64(); + SecT163Field.SquareAddToExt(ax, tt); + SecT163Field.MultiplyAddToExt(xx, yx, tt); + + ulong[] z = Nat192.Create64(); + SecT163Field.Reduce(tt, z); + return new SecT163FieldElement(z); + } + + public override ECFieldElement SquarePow(int pow) + { + if (pow < 1) + return this; + + ulong[] z = Nat192.Create64(); + SecT163Field.SquareN(x, pow, z); + return new SecT163FieldElement(z); + } + + public override ECFieldElement Invert() + { + ulong[] z = Nat192.Create64(); + SecT163Field.Invert(x, z); + return new SecT163FieldElement(z); + } + + public override ECFieldElement Sqrt() + { + ulong[] z = Nat192.Create64(); + SecT163Field.Sqrt(x, z); + return new SecT163FieldElement(z); + } + + public virtual int Representation + { + get { return F2mFieldElement.Ppb; } + } + + public virtual int M + { + get { return 163; } + } + + public virtual int K1 + { + get { return 3; } + } + + public virtual int K2 + { + get { return 6; } + } + + public virtual int K3 + { + get { return 7; } + } + + public override bool Equals(object obj) + { + return Equals(obj as SecT163FieldElement); + } + + public override bool Equals(ECFieldElement other) + { + return Equals(other as SecT163FieldElement); + } + + public virtual bool Equals(SecT163FieldElement other) + { + if (this == other) + return true; + if (null == other) + return false; + return Nat192.Eq64(x, other.x); + } + + public override int GetHashCode() + { + return 163763 ^ Arrays.GetHashCode(x, 0, 3); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT163K1Curve.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT163K1Curve.cs new file mode 100644 index 0000000..68ff646 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT163K1Curve.cs @@ -0,0 +1,104 @@ +using System; + +using Org.BouncyCastle.Math.EC.Multiplier; +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT163K1Curve + : AbstractF2mCurve + { + private const int SecT163K1_DEFAULT_COORDS = COORD_LAMBDA_PROJECTIVE; + + protected readonly SecT163K1Point m_infinity; + + public SecT163K1Curve() + : base(163, 3, 6, 7) + { + this.m_infinity = new SecT163K1Point(this, null, null); + + this.m_a = FromBigInteger(BigInteger.One); + this.m_b = this.m_a; + this.m_order = new BigInteger(1, Hex.Decode("04000000000000000000020108A2E0CC0D99F8A5EF")); + this.m_cofactor = BigInteger.Two; + + this.m_coord = SecT163K1_DEFAULT_COORDS; + } + + protected override ECCurve CloneCurve() + { + return new SecT163K1Curve(); + } + + public override bool SupportsCoordinateSystem(int coord) + { + switch (coord) + { + case COORD_LAMBDA_PROJECTIVE: + return true; + default: + return false; + } + } + + protected override ECMultiplier CreateDefaultMultiplier() + { + return new WTauNafMultiplier(); + } + + public override ECPoint Infinity + { + get { return m_infinity; } + } + + public override int FieldSize + { + get { return 163; } + } + + public override ECFieldElement FromBigInteger(BigInteger x) + { + return new SecT163FieldElement(x); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, bool withCompression) + { + return new SecT163K1Point(this, x, y, withCompression); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + { + return new SecT163K1Point(this, x, y, zs, withCompression); + } + + public override bool IsKoblitz + { + get { return true; } + } + + public virtual int M + { + get { return 163; } + } + + public virtual bool IsTrinomial + { + get { return false; } + } + + public virtual int K1 + { + get { return 3; } + } + + public virtual int K2 + { + get { return 6; } + } + + public virtual int K3 + { + get { return 7; } + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT163K1Point.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT163K1Point.cs new file mode 100644 index 0000000..8693fe1 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT163K1Point.cs @@ -0,0 +1,281 @@ +using System; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT163K1Point + : AbstractF2mPoint + { + /** + * @deprecated Use ECCurve.createPoint to construct points + */ + public SecT163K1Point(ECCurve curve, ECFieldElement x, ECFieldElement y) + : this(curve, x, y, false) + { + } + + /** + * @deprecated per-point compression property will be removed, refer {@link #getEncoded(bool)} + */ + public SecT163K1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, bool withCompression) + : base(curve, x, y, withCompression) + { + if ((x == null) != (y == null)) + throw new ArgumentException("Exactly one of the field elements is null"); + } + + internal SecT163K1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + : base(curve, x, y, zs, withCompression) + { + } + + protected override ECPoint Detach() + { + return new SecT163K1Point(null, this.AffineXCoord, this.AffineYCoord); + } + + public override ECFieldElement YCoord + { + get + { + ECFieldElement X = RawXCoord, L = RawYCoord; + + if (this.IsInfinity || X.IsZero) + return L; + + // Y is actually Lambda (X + Y/X) here; convert to affine value on the fly + ECFieldElement Y = L.Add(X).Multiply(X); + + ECFieldElement Z = RawZCoords[0]; + if (!Z.IsOne) + { + Y = Y.Divide(Z); + } + + return Y; + } + } + + protected internal override bool CompressionYTilde + { + get + { + ECFieldElement X = this.RawXCoord; + if (X.IsZero) + return false; + + ECFieldElement Y = this.RawYCoord; + + // Y is actually Lambda (X + Y/X) here + return Y.TestBitZero() != X.TestBitZero(); + } + } + + public override ECPoint Add(ECPoint b) + { + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return this; + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + ECFieldElement X2 = b.RawXCoord; + + if (X1.IsZero) + { + if (X2.IsZero) + return curve.Infinity; + + return b.Add(this); + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + ECFieldElement L2 = b.RawYCoord, Z2 = b.RawZCoords[0]; + + bool Z1IsOne = Z1.IsOne; + ECFieldElement U2 = X2, S2 = L2; + if (!Z1IsOne) + { + U2 = U2.Multiply(Z1); + S2 = S2.Multiply(Z1); + } + + bool Z2IsOne = Z2.IsOne; + ECFieldElement U1 = X1, S1 = L1; + if (!Z2IsOne) + { + U1 = U1.Multiply(Z2); + S1 = S1.Multiply(Z2); + } + + ECFieldElement A = S1.Add(S2); + ECFieldElement B = U1.Add(U2); + + if (B.IsZero) + { + if (A.IsZero) + return Twice(); + + return curve.Infinity; + } + + ECFieldElement X3, L3, Z3; + if (X2.IsZero) + { + // TODO This can probably be optimized quite a bit + ECPoint p = this.Normalize(); + X1 = p.XCoord; + ECFieldElement Y1 = p.YCoord; + + ECFieldElement Y2 = L2; + ECFieldElement L = Y1.Add(Y2).Divide(X1); + + X3 = L.Square().Add(L).Add(X1).AddOne(); + if (X3.IsZero) + { + return new SecT163K1Point(curve, X3, curve.B, IsCompressed); + } + + ECFieldElement Y3 = L.Multiply(X1.Add(X3)).Add(X3).Add(Y1); + L3 = Y3.Divide(X3).Add(X3); + Z3 = curve.FromBigInteger(BigInteger.One); + } + else + { + B = B.Square(); + + ECFieldElement AU1 = A.Multiply(U1); + ECFieldElement AU2 = A.Multiply(U2); + + X3 = AU1.Multiply(AU2); + if (X3.IsZero) + { + return new SecT163K1Point(curve, X3, curve.B, IsCompressed); + } + + ECFieldElement ABZ2 = A.Multiply(B); + if (!Z2IsOne) + { + ABZ2 = ABZ2.Multiply(Z2); + } + + L3 = AU2.Add(B).SquarePlusProduct(ABZ2, L1.Add(Z1)); + + Z3 = ABZ2; + if (!Z1IsOne) + { + Z3 = Z3.Multiply(Z1); + } + } + + return new SecT163K1Point(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint Twice() + { + if (this.IsInfinity) + { + return this; + } + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + if (X1.IsZero) + { + // A point with X == 0 is it's own Additive inverse + return curve.Infinity; + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + + bool Z1IsOne = Z1.IsOne; + ECFieldElement L1Z1 = Z1IsOne ? L1 : L1.Multiply(Z1); + ECFieldElement Z1Sq = Z1IsOne ? Z1 : Z1.Square(); + ECFieldElement T = L1.Square().Add(L1Z1).Add(Z1Sq); + if (T.IsZero) + { + return new SecT163K1Point(curve, T, curve.B, IsCompressed); + } + + ECFieldElement X3 = T.Square(); + ECFieldElement Z3 = Z1IsOne ? T : T.Multiply(Z1Sq); + + ECFieldElement t1 = L1.Add(X1).Square(); + ECFieldElement L3 = t1.Add(T).Add(Z1Sq).Multiply(t1).Add(X3); + + return new SecT163K1Point(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint TwicePlus(ECPoint b) + { + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return Twice(); + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + if (X1.IsZero) + { + // A point with X == 0 is it's own Additive inverse + return b; + } + + // NOTE: TwicePlus() only optimized for lambda-affine argument + ECFieldElement X2 = b.RawXCoord, Z2 = b.RawZCoords[0]; + if (X2.IsZero || !Z2.IsOne) + { + return Twice().Add(b); + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + ECFieldElement L2 = b.RawYCoord; + + ECFieldElement X1Sq = X1.Square(); + ECFieldElement L1Sq = L1.Square(); + ECFieldElement Z1Sq = Z1.Square(); + ECFieldElement L1Z1 = L1.Multiply(Z1); + + ECFieldElement T = Z1Sq.Add(L1Sq).Add(L1Z1); + ECFieldElement A = L2.Multiply(Z1Sq).Add(L1Sq).MultiplyPlusProduct(T, X1Sq, Z1Sq); + ECFieldElement X2Z1Sq = X2.Multiply(Z1Sq); + ECFieldElement B = X2Z1Sq.Add(T).Square(); + + if (B.IsZero) + { + if (A.IsZero) + return b.Twice(); + + return curve.Infinity; + } + + if (A.IsZero) + { + return new SecT163K1Point(curve, A, curve.B, IsCompressed); + } + + ECFieldElement X3 = A.Square().Multiply(X2Z1Sq); + ECFieldElement Z3 = A.Multiply(B).Multiply(Z1Sq); + ECFieldElement L3 = A.Add(B).Square().MultiplyPlusProduct(T, L2.AddOne(), Z3); + + return new SecT163K1Point(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint Negate() + { + if (this.IsInfinity) + return this; + + ECFieldElement X = this.RawXCoord; + if (X.IsZero) + return this; + + // L is actually Lambda (X + Y/X) here + ECFieldElement L = this.RawYCoord, Z = this.RawZCoords[0]; + return new SecT163K1Point(Curve, X, L.Add(Z), new ECFieldElement[] { Z }, IsCompressed); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT163R1Curve.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT163R1Curve.cs new file mode 100644 index 0000000..8ae58cc --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT163R1Curve.cs @@ -0,0 +1,98 @@ +using System; + +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT163R1Curve + : AbstractF2mCurve + { + private const int SecT163R1_DEFAULT_COORDS = COORD_LAMBDA_PROJECTIVE; + + protected readonly SecT163R1Point m_infinity; + + public SecT163R1Curve() + : base(163, 3, 6, 7) + { + this.m_infinity = new SecT163R1Point(this, null, null); + + this.m_a = FromBigInteger(new BigInteger(1, Hex.Decode("07B6882CAAEFA84F9554FF8428BD88E246D2782AE2"))); + this.m_b = FromBigInteger(new BigInteger(1, Hex.Decode("0713612DCDDCB40AAB946BDA29CA91F73AF958AFD9"))); + this.m_order = new BigInteger(1, Hex.Decode("03FFFFFFFFFFFFFFFFFFFF48AAB689C29CA710279B")); + this.m_cofactor = BigInteger.Two; + + this.m_coord = SecT163R1_DEFAULT_COORDS; + } + + protected override ECCurve CloneCurve() + { + return new SecT163R1Curve(); + } + + public override bool SupportsCoordinateSystem(int coord) + { + switch (coord) + { + case COORD_LAMBDA_PROJECTIVE: + return true; + default: + return false; + } + } + + public override ECPoint Infinity + { + get { return m_infinity; } + } + + public override int FieldSize + { + get { return 163; } + } + + public override ECFieldElement FromBigInteger(BigInteger x) + { + return new SecT163FieldElement(x); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, bool withCompression) + { + return new SecT163R1Point(this, x, y, withCompression); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + { + return new SecT163R1Point(this, x, y, zs, withCompression); + } + + public override bool IsKoblitz + { + get { return false; } + } + + public virtual int M + { + get { return 163; } + } + + public virtual bool IsTrinomial + { + get { return false; } + } + + public virtual int K1 + { + get { return 3; } + } + + public virtual int K2 + { + get { return 6; } + } + + public virtual int K3 + { + get { return 7; } + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT163R1Point.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT163R1Point.cs new file mode 100644 index 0000000..811a09f --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT163R1Point.cs @@ -0,0 +1,283 @@ +using System; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT163R1Point + : AbstractF2mPoint + { + /** + * @deprecated Use ECCurve.createPoint to construct points + */ + public SecT163R1Point(ECCurve curve, ECFieldElement x, ECFieldElement y) + : this(curve, x, y, false) + { + } + + /** + * @deprecated per-point compression property will be removed, refer {@link #getEncoded(bool)} + */ + public SecT163R1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, bool withCompression) + : base(curve, x, y, withCompression) + { + if ((x == null) != (y == null)) + throw new ArgumentException("Exactly one of the field elements is null"); + } + + internal SecT163R1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + : base(curve, x, y, zs, withCompression) + { + } + + protected override ECPoint Detach() + { + return new SecT163R1Point(null, AffineXCoord, AffineYCoord); + } + + public override ECFieldElement YCoord + { + get + { + ECFieldElement X = RawXCoord, L = RawYCoord; + + if (this.IsInfinity || X.IsZero) + return L; + + // Y is actually Lambda (X + Y/X) here; convert to affine value on the fly + ECFieldElement Y = L.Add(X).Multiply(X); + + ECFieldElement Z = RawZCoords[0]; + if (!Z.IsOne) + { + Y = Y.Divide(Z); + } + + return Y; + } + } + + protected internal override bool CompressionYTilde + { + get + { + ECFieldElement X = this.RawXCoord; + if (X.IsZero) + return false; + + ECFieldElement Y = this.RawYCoord; + + // Y is actually Lambda (X + Y/X) here + return Y.TestBitZero() != X.TestBitZero(); + } + } + + public override ECPoint Add(ECPoint b) + { + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return this; + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + ECFieldElement X2 = b.RawXCoord; + + if (X1.IsZero) + { + if (X2.IsZero) + return curve.Infinity; + + return b.Add(this); + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + ECFieldElement L2 = b.RawYCoord, Z2 = b.RawZCoords[0]; + + bool Z1IsOne = Z1.IsOne; + ECFieldElement U2 = X2, S2 = L2; + if (!Z1IsOne) + { + U2 = U2.Multiply(Z1); + S2 = S2.Multiply(Z1); + } + + bool Z2IsOne = Z2.IsOne; + ECFieldElement U1 = X1, S1 = L1; + if (!Z2IsOne) + { + U1 = U1.Multiply(Z2); + S1 = S1.Multiply(Z2); + } + + ECFieldElement A = S1.Add(S2); + ECFieldElement B = U1.Add(U2); + + if (B.IsZero) + { + if (A.IsZero) + return Twice(); + + return curve.Infinity; + } + + ECFieldElement X3, L3, Z3; + if (X2.IsZero) + { + // TODO This can probably be optimized quite a bit + ECPoint p = this.Normalize(); + X1 = p.XCoord; + ECFieldElement Y1 = p.YCoord; + + ECFieldElement Y2 = L2; + ECFieldElement L = Y1.Add(Y2).Divide(X1); + + X3 = L.Square().Add(L).Add(X1).Add(curve.A); + if (X3.IsZero) + { + return new SecT163R1Point(curve, X3, curve.B.Sqrt(), IsCompressed); + } + + ECFieldElement Y3 = L.Multiply(X1.Add(X3)).Add(X3).Add(Y1); + L3 = Y3.Divide(X3).Add(X3); + Z3 = curve.FromBigInteger(BigInteger.One); + } + else + { + B = B.Square(); + + ECFieldElement AU1 = A.Multiply(U1); + ECFieldElement AU2 = A.Multiply(U2); + + X3 = AU1.Multiply(AU2); + if (X3.IsZero) + { + return new SecT163R1Point(curve, X3, curve.B.Sqrt(), IsCompressed); + } + + ECFieldElement ABZ2 = A.Multiply(B); + if (!Z2IsOne) + { + ABZ2 = ABZ2.Multiply(Z2); + } + + L3 = AU2.Add(B).SquarePlusProduct(ABZ2, L1.Add(Z1)); + + Z3 = ABZ2; + if (!Z1IsOne) + { + Z3 = Z3.Multiply(Z1); + } + } + + return new SecT163R1Point(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint Twice() + { + if (this.IsInfinity) + { + return this; + } + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + if (X1.IsZero) + { + // A point with X == 0 is it's own Additive inverse + return curve.Infinity; + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + + bool Z1IsOne = Z1.IsOne; + ECFieldElement L1Z1 = Z1IsOne ? L1 : L1.Multiply(Z1); + ECFieldElement Z1Sq = Z1IsOne ? Z1 : Z1.Square(); + ECFieldElement a = curve.A; + ECFieldElement aZ1Sq = Z1IsOne ? a : a.Multiply(Z1Sq); + ECFieldElement T = L1.Square().Add(L1Z1).Add(aZ1Sq); + if (T.IsZero) + { + return new SecT163R1Point(curve, T, curve.B.Sqrt(), IsCompressed); + } + + ECFieldElement X3 = T.Square(); + ECFieldElement Z3 = Z1IsOne ? T : T.Multiply(Z1Sq); + + ECFieldElement X1Z1 = Z1IsOne ? X1 : X1.Multiply(Z1); + ECFieldElement L3 = X1Z1.SquarePlusProduct(T, L1Z1).Add(X3).Add(Z3); + + return new SecT163R1Point(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint TwicePlus(ECPoint b) + { + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return Twice(); + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + if (X1.IsZero) + { + // A point with X == 0 is it's own Additive inverse + return b; + } + + ECFieldElement X2 = b.RawXCoord, Z2 = b.RawZCoords[0]; + if (X2.IsZero || !Z2.IsOne) + { + return Twice().Add(b); + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + ECFieldElement L2 = b.RawYCoord; + + ECFieldElement X1Sq = X1.Square(); + ECFieldElement L1Sq = L1.Square(); + ECFieldElement Z1Sq = Z1.Square(); + ECFieldElement L1Z1 = L1.Multiply(Z1); + + ECFieldElement T = curve.A.Multiply(Z1Sq).Add(L1Sq).Add(L1Z1); + ECFieldElement L2plus1 = L2.AddOne(); + ECFieldElement A = curve.A.Add(L2plus1).Multiply(Z1Sq).Add(L1Sq).MultiplyPlusProduct(T, X1Sq, Z1Sq); + ECFieldElement X2Z1Sq = X2.Multiply(Z1Sq); + ECFieldElement B = X2Z1Sq.Add(T).Square(); + + if (B.IsZero) + { + if (A.IsZero) + return b.Twice(); + + return curve.Infinity; + } + + if (A.IsZero) + { + return new SecT163R1Point(curve, A, curve.B.Sqrt(), IsCompressed); + } + + ECFieldElement X3 = A.Square().Multiply(X2Z1Sq); + ECFieldElement Z3 = A.Multiply(B).Multiply(Z1Sq); + ECFieldElement L3 = A.Add(B).Square().MultiplyPlusProduct(T, L2plus1, Z3); + + return new SecT163R1Point(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint Negate() + { + if (this.IsInfinity) + return this; + + ECFieldElement X = this.RawXCoord; + if (X.IsZero) + return this; + + // L is actually Lambda (X + Y/X) here + ECFieldElement L = this.RawYCoord, Z = this.RawZCoords[0]; + return new SecT163R1Point(Curve, X, L.Add(Z), new ECFieldElement[] { Z }, IsCompressed); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT163R2Curve.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT163R2Curve.cs new file mode 100644 index 0000000..5a4fa5a --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT163R2Curve.cs @@ -0,0 +1,98 @@ +using System; + +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT163R2Curve + : AbstractF2mCurve + { + private const int SecT163R2_DEFAULT_COORDS = COORD_LAMBDA_PROJECTIVE; + + protected readonly SecT163R2Point m_infinity; + + public SecT163R2Curve() + : base(163, 3, 6, 7) + { + this.m_infinity = new SecT163R2Point(this, null, null); + + this.m_a = FromBigInteger(BigInteger.One); + this.m_b = FromBigInteger(new BigInteger(1, Hex.Decode("020A601907B8C953CA1481EB10512F78744A3205FD"))); + this.m_order = new BigInteger(1, Hex.Decode("040000000000000000000292FE77E70C12A4234C33")); + this.m_cofactor = BigInteger.Two; + + this.m_coord = SecT163R2_DEFAULT_COORDS; + } + + protected override ECCurve CloneCurve() + { + return new SecT163R2Curve(); + } + + public override bool SupportsCoordinateSystem(int coord) + { + switch (coord) + { + case COORD_LAMBDA_PROJECTIVE: + return true; + default: + return false; + } + } + + public override ECPoint Infinity + { + get { return m_infinity; } + } + + public override int FieldSize + { + get { return 163; } + } + + public override ECFieldElement FromBigInteger(BigInteger x) + { + return new SecT163FieldElement(x); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, bool withCompression) + { + return new SecT163R2Point(this, x, y, withCompression); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + { + return new SecT163R2Point(this, x, y, zs, withCompression); + } + + public override bool IsKoblitz + { + get { return false; } + } + + public virtual int M + { + get { return 163; } + } + + public virtual bool IsTrinomial + { + get { return false; } + } + + public virtual int K1 + { + get { return 3; } + } + + public virtual int K2 + { + get { return 6; } + } + + public virtual int K3 + { + get { return 7; } + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT163R2Point.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT163R2Point.cs new file mode 100644 index 0000000..69e2497 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT163R2Point.cs @@ -0,0 +1,286 @@ +using System; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT163R2Point + : AbstractF2mPoint + { + /** + * @deprecated Use ECCurve.createPoint to construct points + */ + public SecT163R2Point(ECCurve curve, ECFieldElement x, ECFieldElement y) + : this(curve, x, y, false) + { + } + + /** + * @deprecated per-point compression property will be removed, refer {@link #getEncoded(bool)} + */ + public SecT163R2Point(ECCurve curve, ECFieldElement x, ECFieldElement y, bool withCompression) + : base(curve, x, y, withCompression) + { + if ((x == null) != (y == null)) + throw new ArgumentException("Exactly one of the field elements is null"); + } + + internal SecT163R2Point(ECCurve curve, ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + : base(curve, x, y, zs, withCompression) + { + } + + protected override ECPoint Detach() + { + return new SecT163R2Point(null, AffineXCoord, AffineYCoord); + } + + public override ECFieldElement YCoord + { + get + { + ECFieldElement X = RawXCoord, L = RawYCoord; + + if (this.IsInfinity || X.IsZero) + return L; + + // Y is actually Lambda (X + Y/X) here; convert to affine value on the fly + ECFieldElement Y = L.Add(X).Multiply(X); + + ECFieldElement Z = RawZCoords[0]; + if (!Z.IsOne) + { + Y = Y.Divide(Z); + } + + return Y; + } + } + + protected internal override bool CompressionYTilde + { + get + { + ECFieldElement X = this.RawXCoord; + if (X.IsZero) + return false; + + ECFieldElement Y = this.RawYCoord; + + // Y is actually Lambda (X + Y/X) here + return Y.TestBitZero() != X.TestBitZero(); + } + } + + public override ECPoint Add(ECPoint b) + { + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return this; + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + ECFieldElement X2 = b.RawXCoord; + + if (X1.IsZero) + { + if (X2.IsZero) + return curve.Infinity; + + return b.Add(this); + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + ECFieldElement L2 = b.RawYCoord, Z2 = b.RawZCoords[0]; + + bool Z1IsOne = Z1.IsOne; + ECFieldElement U2 = X2, S2 = L2; + if (!Z1IsOne) + { + U2 = U2.Multiply(Z1); + S2 = S2.Multiply(Z1); + } + + bool Z2IsOne = Z2.IsOne; + ECFieldElement U1 = X1, S1 = L1; + if (!Z2IsOne) + { + U1 = U1.Multiply(Z2); + S1 = S1.Multiply(Z2); + } + + ECFieldElement A = S1.Add(S2); + ECFieldElement B = U1.Add(U2); + + if (B.IsZero) + { + if (A.IsZero) + { + return Twice(); + } + + return curve.Infinity; + } + + ECFieldElement X3, L3, Z3; + if (X2.IsZero) + { + // TODO This can probably be optimized quite a bit + ECPoint p = this.Normalize(); + X1 = p.XCoord; + ECFieldElement Y1 = p.YCoord; + + ECFieldElement Y2 = L2; + ECFieldElement L = Y1.Add(Y2).Divide(X1); + + X3 = L.Square().Add(L).Add(X1).AddOne(); + if (X3.IsZero) + { + return new SecT163R2Point(curve, X3, curve.B.Sqrt(), IsCompressed); + } + + ECFieldElement Y3 = L.Multiply(X1.Add(X3)).Add(X3).Add(Y1); + L3 = Y3.Divide(X3).Add(X3); + Z3 = curve.FromBigInteger(BigInteger.One); + } + else + { + B = B.Square(); + + ECFieldElement AU1 = A.Multiply(U1); + ECFieldElement AU2 = A.Multiply(U2); + + X3 = AU1.Multiply(AU2); + if (X3.IsZero) + { + return new SecT163R2Point(curve, X3, curve.B.Sqrt(), IsCompressed); + } + + ECFieldElement ABZ2 = A.Multiply(B); + if (!Z2IsOne) + { + ABZ2 = ABZ2.Multiply(Z2); + } + + L3 = AU2.Add(B).SquarePlusProduct(ABZ2, L1.Add(Z1)); + + Z3 = ABZ2; + if (!Z1IsOne) + { + Z3 = Z3.Multiply(Z1); + } + } + + return new SecT163R2Point(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint Twice() + { + if (this.IsInfinity) + { + return this; + } + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + if (X1.IsZero) + { + // A point with X == 0 is it's own Additive inverse + return curve.Infinity; + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + + bool Z1IsOne = Z1.IsOne; + ECFieldElement L1Z1 = Z1IsOne ? L1 : L1.Multiply(Z1); + ECFieldElement Z1Sq = Z1IsOne ? Z1 : Z1.Square(); + ECFieldElement T = L1.Square().Add(L1Z1).Add(Z1Sq); + if (T.IsZero) + { + return new SecT163R2Point(curve, T, curve.B.Sqrt(), IsCompressed); + } + + ECFieldElement X3 = T.Square(); + ECFieldElement Z3 = Z1IsOne ? T : T.Multiply(Z1Sq); + + ECFieldElement X1Z1 = Z1IsOne ? X1 : X1.Multiply(Z1); + ECFieldElement L3 = X1Z1.SquarePlusProduct(T, L1Z1).Add(X3).Add(Z3); + + return new SecT163R2Point(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint TwicePlus(ECPoint b) + { + if (this.IsInfinity) + { + return b; + } + if (b.IsInfinity) + { + return Twice(); + } + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + if (X1.IsZero) + { + // A point with X == 0 is it's own Additive inverse + return b; + } + + ECFieldElement X2 = b.RawXCoord, Z2 = b.RawZCoords[0]; + if (X2.IsZero || !Z2.IsOne) + { + return Twice().Add(b); + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + ECFieldElement L2 = b.RawYCoord; + + ECFieldElement X1Sq = X1.Square(); + ECFieldElement L1Sq = L1.Square(); + ECFieldElement Z1Sq = Z1.Square(); + ECFieldElement L1Z1 = L1.Multiply(Z1); + + ECFieldElement T = Z1Sq.Add(L1Sq).Add(L1Z1); + ECFieldElement A = L2.Multiply(Z1Sq).Add(L1Sq).MultiplyPlusProduct(T, X1Sq, Z1Sq); + ECFieldElement X2Z1Sq = X2.Multiply(Z1Sq); + ECFieldElement B = X2Z1Sq.Add(T).Square(); + + if (B.IsZero) + { + if (A.IsZero) + return b.Twice(); + + return curve.Infinity; + } + + if (A.IsZero) + { + return new SecT163R2Point(curve, A, curve.B.Sqrt(), IsCompressed); + } + + ECFieldElement X3 = A.Square().Multiply(X2Z1Sq); + ECFieldElement Z3 = A.Multiply(B).Multiply(Z1Sq); + ECFieldElement L3 = A.Add(B).Square().MultiplyPlusProduct(T, L2.AddOne(), Z3); + + return new SecT163R2Point(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint Negate() + { + if (this.IsInfinity) + return this; + + ECFieldElement X = this.RawXCoord; + if (X.IsZero) + return this; + + // L is actually Lambda (X + Y/X) here + ECFieldElement L = this.RawYCoord, Z = this.RawZCoords[0]; + return new SecT163R2Point(Curve, X, L.Add(Z), new ECFieldElement[] { Z }, IsCompressed); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT193Field.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT193Field.cs new file mode 100644 index 0000000..41acb4f --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT193Field.cs @@ -0,0 +1,305 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Math.Raw; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT193Field + { + private const ulong M01 = 1UL; + private const ulong M49 = ulong.MaxValue >> 15; + + public static void Add(ulong[] x, ulong[] y, ulong[] z) + { + z[0] = x[0] ^ y[0]; + z[1] = x[1] ^ y[1]; + z[2] = x[2] ^ y[2]; + z[3] = x[3] ^ y[3]; + } + + public static void AddExt(ulong[] xx, ulong[] yy, ulong[] zz) + { + zz[0] = xx[0] ^ yy[0]; + zz[1] = xx[1] ^ yy[1]; + zz[2] = xx[2] ^ yy[2]; + zz[3] = xx[3] ^ yy[3]; + zz[4] = xx[4] ^ yy[4]; + zz[5] = xx[5] ^ yy[5]; + zz[6] = xx[6] ^ yy[6]; + } + + public static void AddOne(ulong[] x, ulong[] z) + { + z[0] = x[0] ^ 1UL; + z[1] = x[1]; + z[2] = x[2]; + z[3] = x[3]; + } + + public static ulong[] FromBigInteger(BigInteger x) + { + ulong[] z = Nat256.FromBigInteger64(x); + Reduce63(z, 0); + return z; + } + + public static void Invert(ulong[] x, ulong[] z) + { + if (Nat256.IsZero64(x)) + throw new InvalidOperationException(); + + // Itoh-Tsujii inversion with bases { 2, 3 } + + ulong[] t0 = Nat256.Create64(); + ulong[] t1 = Nat256.Create64(); + + Square(x, t0); + + // 3 | 192 + SquareN(t0, 1, t1); + Multiply(t0, t1, t0); + SquareN(t1, 1, t1); + Multiply(t0, t1, t0); + + // 2 | 64 + SquareN(t0, 3, t1); + Multiply(t0, t1, t0); + + // 2 | 32 + SquareN(t0, 6, t1); + Multiply(t0, t1, t0); + + // 2 | 16 + SquareN(t0, 12, t1); + Multiply(t0, t1, t0); + + // 2 | 8 + SquareN(t0, 24, t1); + Multiply(t0, t1, t0); + + // 2 | 4 + SquareN(t0, 48, t1); + Multiply(t0, t1, t0); + + // 2 | 2 + SquareN(t0, 96, t1); + Multiply(t0, t1, z); + } + + public static void Multiply(ulong[] x, ulong[] y, ulong[] z) + { + ulong[] tt = Nat256.CreateExt64(); + ImplMultiply(x, y, tt); + Reduce(tt, z); + } + + public static void MultiplyAddToExt(ulong[] x, ulong[] y, ulong[] zz) + { + ulong[] tt = Nat256.CreateExt64(); + ImplMultiply(x, y, tt); + AddExt(zz, tt, zz); + } + + public static void Reduce(ulong[] xx, ulong[] z) + { + ulong x0 = xx[0], x1 = xx[1], x2 = xx[2], x3 = xx[3], x4 = xx[4], x5 = xx[5], x6 = xx[6]; + + x2 ^= (x6 << 63); + x3 ^= (x6 >> 1) ^ (x6 << 14); + x4 ^= (x6 >> 50); + + x1 ^= (x5 << 63); + x2 ^= (x5 >> 1) ^ (x5 << 14); + x3 ^= (x5 >> 50); + + x0 ^= (x4 << 63); + x1 ^= (x4 >> 1) ^ (x4 << 14); + x2 ^= (x4 >> 50); + + ulong t = x3 >> 1; + z[0] = x0 ^ t ^ (t << 15); + z[1] = x1 ^ (t >> 49); + z[2] = x2; + z[3] = x3 & M01; + } + + public static void Reduce63(ulong[] z, int zOff) + { + ulong z3 = z[zOff + 3], t = z3 >> 1; + z[zOff ] ^= t ^ (t << 15); + z[zOff + 1] ^= (t >> 49); + z[zOff + 3] = z3 & M01; + } + + public static void Sqrt(ulong[] x, ulong[] z) + { + ulong u0, u1; + u0 = Interleave.Unshuffle(x[0]); u1 = Interleave.Unshuffle(x[1]); + ulong e0 = (u0 & 0x00000000FFFFFFFFUL) | (u1 << 32); + ulong c0 = (u0 >> 32) | (u1 & 0xFFFFFFFF00000000UL); + + u0 = Interleave.Unshuffle(x[2]); + ulong e1 = (u0 & 0x00000000FFFFFFFFUL) ^ (x[3] << 32); + ulong c1 = (u0 >> 32); + + z[0] = e0 ^ (c0 << 8); + z[1] = e1 ^ (c1 << 8) ^ (c0 >> 56) ^ (c0 << 33); + z[2] = (c1 >> 56) ^ (c1 << 33) ^ (c0 >> 31); + z[3] = (c1 >> 31); + } + + public static void Square(ulong[] x, ulong[] z) + { + ulong[] tt = Nat256.CreateExt64(); + ImplSquare(x, tt); + Reduce(tt, z); + } + + public static void SquareAddToExt(ulong[] x, ulong[] zz) + { + ulong[] tt = Nat256.CreateExt64(); + ImplSquare(x, tt); + AddExt(zz, tt, zz); + } + + public static void SquareN(ulong[] x, int n, ulong[] z) + { + Debug.Assert(n > 0); + + ulong[] tt = Nat256.CreateExt64(); + ImplSquare(x, tt); + Reduce(tt, z); + + while (--n > 0) + { + ImplSquare(z, tt); + Reduce(tt, z); + } + } + + public static uint Trace(ulong[] x) + { + // Non-zero-trace bits: 0 + return (uint)(x[0]) & 1U; + } + + protected static void ImplCompactExt(ulong[] zz) + { + ulong z0 = zz[0], z1 = zz[1], z2 = zz[2], z3 = zz[3], z4 = zz[4], z5 = zz[5], z6 = zz[6], z7 = zz[7]; + zz[0] = z0 ^ (z1 << 49); + zz[1] = (z1 >> 15) ^ (z2 << 34); + zz[2] = (z2 >> 30) ^ (z3 << 19); + zz[3] = (z3 >> 45) ^ (z4 << 4) + ^ (z5 << 53); + zz[4] = (z4 >> 60) ^ (z6 << 38) + ^ (z5 >> 11); + zz[5] = (z6 >> 26) ^ (z7 << 23); + zz[6] = (z7 >> 41); + zz[7] = 0; + } + + protected static void ImplExpand(ulong[] x, ulong[] z) + { + ulong x0 = x[0], x1 = x[1], x2 = x[2], x3 = x[3]; + z[0] = x0 & M49; + z[1] = ((x0 >> 49) ^ (x1 << 15)) & M49; + z[2] = ((x1 >> 34) ^ (x2 << 30)) & M49; + z[3] = ((x2 >> 19) ^ (x3 << 45)); + } + + protected static void ImplMultiply(ulong[] x, ulong[] y, ulong[] zz) + { + /* + * "Two-level seven-way recursion" as described in "Batch binary Edwards", Daniel J. Bernstein. + */ + + ulong[] f = new ulong[4], g = new ulong[4]; + ImplExpand(x, f); + ImplExpand(y, g); + + ImplMulwAcc(f[0], g[0], zz, 0); + ImplMulwAcc(f[1], g[1], zz, 1); + ImplMulwAcc(f[2], g[2], zz, 2); + ImplMulwAcc(f[3], g[3], zz, 3); + + // U *= (1 - t^n) + for (int i = 5; i > 0; --i) + { + zz[i] ^= zz[i - 1]; + } + + ImplMulwAcc(f[0] ^ f[1], g[0] ^ g[1], zz, 1); + ImplMulwAcc(f[2] ^ f[3], g[2] ^ g[3], zz, 3); + + // V *= (1 - t^2n) + for (int i = 7; i > 1; --i) + { + zz[i] ^= zz[i - 2]; + } + + // Double-length recursion + { + ulong c0 = f[0] ^ f[2], c1 = f[1] ^ f[3]; + ulong d0 = g[0] ^ g[2], d1 = g[1] ^ g[3]; + ImplMulwAcc(c0 ^ c1, d0 ^ d1, zz, 3); + ulong[] t = new ulong[3]; + ImplMulwAcc(c0, d0, t, 0); + ImplMulwAcc(c1, d1, t, 1); + ulong t0 = t[0], t1 = t[1], t2 = t[2]; + zz[2] ^= t0; + zz[3] ^= t0 ^ t1; + zz[4] ^= t2 ^ t1; + zz[5] ^= t2; + } + + ImplCompactExt(zz); + } + + protected static void ImplMulwAcc(ulong x, ulong y, ulong[] z, int zOff) + { + Debug.Assert(x >> 49 == 0); + Debug.Assert(y >> 49 == 0); + + ulong[] u = new ulong[8]; + //u[0] = 0; + u[1] = y; + u[2] = u[1] << 1; + u[3] = u[2] ^ y; + u[4] = u[2] << 1; + u[5] = u[4] ^ y; + u[6] = u[3] << 1; + u[7] = u[6] ^ y; + + uint j = (uint)x; + ulong g, h = 0, l = u[j & 7] + ^ (u[(j >> 3) & 7] << 3); + int k = 36; + do + { + j = (uint)(x >> k); + g = u[j & 7] + ^ u[(j >> 3) & 7] << 3 + ^ u[(j >> 6) & 7] << 6 + ^ u[(j >> 9) & 7] << 9 + ^ u[(j >> 12) & 7] << 12; + l ^= (g << k); + h ^= (g >> -k); + } + while ((k -= 15) > 0); + + Debug.Assert(h >> 33 == 0); + + z[zOff ] ^= l & M49; + z[zOff + 1] ^= (l >> 49) ^ (h << 15); + } + + protected static void ImplSquare(ulong[] x, ulong[] zz) + { + Interleave.Expand64To128(x[0], zz, 0); + Interleave.Expand64To128(x[1], zz, 2); + Interleave.Expand64To128(x[2], zz, 4); + zz[6] = (x[3] & M01); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT193FieldElement.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT193FieldElement.cs new file mode 100644 index 0000000..a1150b3 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT193FieldElement.cs @@ -0,0 +1,216 @@ +using System; + +using Org.BouncyCastle.Math.Raw; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT193FieldElement + : ECFieldElement + { + protected readonly ulong[] x; + + public SecT193FieldElement(BigInteger x) + { + if (x == null || x.SignValue < 0 || x.BitLength > 193) + throw new ArgumentException("value invalid for SecT193FieldElement", "x"); + + this.x = SecT193Field.FromBigInteger(x); + } + + public SecT193FieldElement() + { + this.x = Nat256.Create64(); + } + + protected internal SecT193FieldElement(ulong[] x) + { + this.x = x; + } + + public override bool IsOne + { + get { return Nat256.IsOne64(x); } + } + + public override bool IsZero + { + get { return Nat256.IsZero64(x); } + } + + public override bool TestBitZero() + { + return (x[0] & 1UL) != 0UL; + } + + public override BigInteger ToBigInteger() + { + return Nat256.ToBigInteger64(x); + } + + public override string FieldName + { + get { return "SecT193Field"; } + } + + public override int FieldSize + { + get { return 193; } + } + + public override ECFieldElement Add(ECFieldElement b) + { + ulong[] z = Nat256.Create64(); + SecT193Field.Add(x, ((SecT193FieldElement)b).x, z); + return new SecT193FieldElement(z); + } + + public override ECFieldElement AddOne() + { + ulong[] z = Nat256.Create64(); + SecT193Field.AddOne(x, z); + return new SecT193FieldElement(z); + } + + public override ECFieldElement Subtract(ECFieldElement b) + { + // Addition and Subtraction are the same in F2m + return Add(b); + } + + public override ECFieldElement Multiply(ECFieldElement b) + { + ulong[] z = Nat256.Create64(); + SecT193Field.Multiply(x, ((SecT193FieldElement)b).x, z); + return new SecT193FieldElement(z); + } + + public override ECFieldElement MultiplyMinusProduct(ECFieldElement b, ECFieldElement x, ECFieldElement y) + { + return MultiplyPlusProduct(b, x, y); + } + + public override ECFieldElement MultiplyPlusProduct(ECFieldElement b, ECFieldElement x, ECFieldElement y) + { + ulong[] ax = this.x, bx = ((SecT193FieldElement)b).x; + ulong[] xx = ((SecT193FieldElement)x).x, yx = ((SecT193FieldElement)y).x; + + ulong[] tt = Nat256.CreateExt64(); + SecT193Field.MultiplyAddToExt(ax, bx, tt); + SecT193Field.MultiplyAddToExt(xx, yx, tt); + + ulong[] z = Nat256.Create64(); + SecT193Field.Reduce(tt, z); + return new SecT193FieldElement(z); + } + + public override ECFieldElement Divide(ECFieldElement b) + { + return Multiply(b.Invert()); + } + + public override ECFieldElement Negate() + { + return this; + } + + public override ECFieldElement Square() + { + ulong[] z = Nat256.Create64(); + SecT193Field.Square(x, z); + return new SecT193FieldElement(z); + } + + public override ECFieldElement SquareMinusProduct(ECFieldElement x, ECFieldElement y) + { + return SquarePlusProduct(x, y); + } + + public override ECFieldElement SquarePlusProduct(ECFieldElement x, ECFieldElement y) + { + ulong[] ax = this.x; + ulong[] xx = ((SecT193FieldElement)x).x, yx = ((SecT193FieldElement)y).x; + + ulong[] tt = Nat256.CreateExt64(); + SecT193Field.SquareAddToExt(ax, tt); + SecT193Field.MultiplyAddToExt(xx, yx, tt); + + ulong[] z = Nat256.Create64(); + SecT193Field.Reduce(tt, z); + return new SecT193FieldElement(z); + } + + public override ECFieldElement SquarePow(int pow) + { + if (pow < 1) + return this; + + ulong[] z = Nat256.Create64(); + SecT193Field.SquareN(x, pow, z); + return new SecT193FieldElement(z); + } + + public override ECFieldElement Invert() + { + ulong[] z = Nat256.Create64(); + SecT193Field.Invert(x, z); + return new SecT193FieldElement(z); + } + + public override ECFieldElement Sqrt() + { + ulong[] z = Nat256.Create64(); + SecT193Field.Sqrt(x, z); + return new SecT193FieldElement(z); + } + + public virtual int Representation + { + get { return F2mFieldElement.Tpb; } + } + + public virtual int M + { + get { return 193; } + } + + public virtual int K1 + { + get { return 15; } + } + + public virtual int K2 + { + get { return 0; } + } + + public virtual int K3 + { + get { return 0; } + } + + public override bool Equals(object obj) + { + return Equals(obj as SecT193FieldElement); + } + + public override bool Equals(ECFieldElement other) + { + return Equals(other as SecT193FieldElement); + } + + public virtual bool Equals(SecT193FieldElement other) + { + if (this == other) + return true; + if (null == other) + return false; + return Nat256.Eq64(x, other.x); + } + + public override int GetHashCode() + { + return 1930015 ^ Arrays.GetHashCode(x, 0, 4); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT193R1Curve.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT193R1Curve.cs new file mode 100644 index 0000000..a2cb5a8 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT193R1Curve.cs @@ -0,0 +1,98 @@ +using System; + +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT193R1Curve + : AbstractF2mCurve + { + private const int SecT193R1_DEFAULT_COORDS = COORD_LAMBDA_PROJECTIVE; + + protected readonly SecT193R1Point m_infinity; + + public SecT193R1Curve() + : base(193, 15, 0, 0) + { + this.m_infinity = new SecT193R1Point(this, null, null); + + this.m_a = FromBigInteger(new BigInteger(1, Hex.Decode("0017858FEB7A98975169E171F77B4087DE098AC8A911DF7B01"))); + this.m_b = FromBigInteger(new BigInteger(1, Hex.Decode("00FDFB49BFE6C3A89FACADAA7A1E5BBC7CC1C2E5D831478814"))); + this.m_order = new BigInteger(1, Hex.Decode("01000000000000000000000000C7F34A778F443ACC920EBA49")); + this.m_cofactor = BigInteger.Two; + + this.m_coord = SecT193R1_DEFAULT_COORDS; + } + + protected override ECCurve CloneCurve() + { + return new SecT193R1Curve(); + } + + public override bool SupportsCoordinateSystem(int coord) + { + switch (coord) + { + case COORD_LAMBDA_PROJECTIVE: + return true; + default: + return false; + } + } + + public override ECPoint Infinity + { + get { return m_infinity; } + } + + public override int FieldSize + { + get { return 193; } + } + + public override ECFieldElement FromBigInteger(BigInteger x) + { + return new SecT193FieldElement(x); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, bool withCompression) + { + return new SecT193R1Point(this, x, y, withCompression); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + { + return new SecT193R1Point(this, x, y, zs, withCompression); + } + + public override bool IsKoblitz + { + get { return false; } + } + + public virtual int M + { + get { return 193; } + } + + public virtual bool IsTrinomial + { + get { return true; } + } + + public virtual int K1 + { + get { return 15; } + } + + public virtual int K2 + { + get { return 0; } + } + + public virtual int K3 + { + get { return 0; } + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT193R1Point.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT193R1Point.cs new file mode 100644 index 0000000..062fce9 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT193R1Point.cs @@ -0,0 +1,283 @@ +using System; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT193R1Point + : AbstractF2mPoint + { + /** + * @deprecated Use ECCurve.createPoint to construct points + */ + public SecT193R1Point(ECCurve curve, ECFieldElement x, ECFieldElement y) + : this(curve, x, y, false) + { + } + + /** + * @deprecated per-point compression property will be removed, refer {@link #getEncoded(bool)} + */ + public SecT193R1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, bool withCompression) + : base(curve, x, y, withCompression) + { + if ((x == null) != (y == null)) + throw new ArgumentException("Exactly one of the field elements is null"); + } + + internal SecT193R1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + : base(curve, x, y, zs, withCompression) + { + } + + protected override ECPoint Detach() + { + return new SecT193R1Point(null, AffineXCoord, AffineYCoord); + } + + public override ECFieldElement YCoord + { + get + { + ECFieldElement X = RawXCoord, L = RawYCoord; + + if (this.IsInfinity || X.IsZero) + return L; + + // Y is actually Lambda (X + Y/X) here; convert to affine value on the fly + ECFieldElement Y = L.Add(X).Multiply(X); + + ECFieldElement Z = RawZCoords[0]; + if (!Z.IsOne) + { + Y = Y.Divide(Z); + } + + return Y; + } + } + + protected internal override bool CompressionYTilde + { + get + { + ECFieldElement X = this.RawXCoord; + if (X.IsZero) + return false; + + ECFieldElement Y = this.RawYCoord; + + // Y is actually Lambda (X + Y/X) here + return Y.TestBitZero() != X.TestBitZero(); + } + } + + public override ECPoint Add(ECPoint b) + { + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return this; + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + ECFieldElement X2 = b.RawXCoord; + + if (X1.IsZero) + { + if (X2.IsZero) + return curve.Infinity; + + return b.Add(this); + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + ECFieldElement L2 = b.RawYCoord, Z2 = b.RawZCoords[0]; + + bool Z1IsOne = Z1.IsOne; + ECFieldElement U2 = X2, S2 = L2; + if (!Z1IsOne) + { + U2 = U2.Multiply(Z1); + S2 = S2.Multiply(Z1); + } + + bool Z2IsOne = Z2.IsOne; + ECFieldElement U1 = X1, S1 = L1; + if (!Z2IsOne) + { + U1 = U1.Multiply(Z2); + S1 = S1.Multiply(Z2); + } + + ECFieldElement A = S1.Add(S2); + ECFieldElement B = U1.Add(U2); + + if (B.IsZero) + { + if (A.IsZero) + return Twice(); + + return curve.Infinity; + } + + ECFieldElement X3, L3, Z3; + if (X2.IsZero) + { + // TODO This can probably be optimized quite a bit + ECPoint p = this.Normalize(); + X1 = p.XCoord; + ECFieldElement Y1 = p.YCoord; + + ECFieldElement Y2 = L2; + ECFieldElement L = Y1.Add(Y2).Divide(X1); + + X3 = L.Square().Add(L).Add(X1).Add(curve.A); + if (X3.IsZero) + { + return new SecT193R1Point(curve, X3, curve.B.Sqrt(), IsCompressed); + } + + ECFieldElement Y3 = L.Multiply(X1.Add(X3)).Add(X3).Add(Y1); + L3 = Y3.Divide(X3).Add(X3); + Z3 = curve.FromBigInteger(BigInteger.One); + } + else + { + B = B.Square(); + + ECFieldElement AU1 = A.Multiply(U1); + ECFieldElement AU2 = A.Multiply(U2); + + X3 = AU1.Multiply(AU2); + if (X3.IsZero) + { + return new SecT193R1Point(curve, X3, curve.B.Sqrt(), IsCompressed); + } + + ECFieldElement ABZ2 = A.Multiply(B); + if (!Z2IsOne) + { + ABZ2 = ABZ2.Multiply(Z2); + } + + L3 = AU2.Add(B).SquarePlusProduct(ABZ2, L1.Add(Z1)); + + Z3 = ABZ2; + if (!Z1IsOne) + { + Z3 = Z3.Multiply(Z1); + } + } + + return new SecT193R1Point(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint Twice() + { + if (this.IsInfinity) + { + return this; + } + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + if (X1.IsZero) + { + // A point with X == 0 is it's own Additive inverse + return curve.Infinity; + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + + bool Z1IsOne = Z1.IsOne; + ECFieldElement L1Z1 = Z1IsOne ? L1 : L1.Multiply(Z1); + ECFieldElement Z1Sq = Z1IsOne ? Z1 : Z1.Square(); + ECFieldElement a = curve.A; + ECFieldElement aZ1Sq = Z1IsOne ? a : a.Multiply(Z1Sq); + ECFieldElement T = L1.Square().Add(L1Z1).Add(aZ1Sq); + if (T.IsZero) + { + return new SecT193R1Point(curve, T, curve.B.Sqrt(), IsCompressed); + } + + ECFieldElement X3 = T.Square(); + ECFieldElement Z3 = Z1IsOne ? T : T.Multiply(Z1Sq); + + ECFieldElement X1Z1 = Z1IsOne ? X1 : X1.Multiply(Z1); + ECFieldElement L3 = X1Z1.SquarePlusProduct(T, L1Z1).Add(X3).Add(Z3); + + return new SecT193R1Point(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint TwicePlus(ECPoint b) + { + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return Twice(); + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + if (X1.IsZero) + { + // A point with X == 0 is it's own Additive inverse + return b; + } + + ECFieldElement X2 = b.RawXCoord, Z2 = b.RawZCoords[0]; + if (X2.IsZero || !Z2.IsOne) + { + return Twice().Add(b); + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + ECFieldElement L2 = b.RawYCoord; + + ECFieldElement X1Sq = X1.Square(); + ECFieldElement L1Sq = L1.Square(); + ECFieldElement Z1Sq = Z1.Square(); + ECFieldElement L1Z1 = L1.Multiply(Z1); + + ECFieldElement T = curve.A.Multiply(Z1Sq).Add(L1Sq).Add(L1Z1); + ECFieldElement L2plus1 = L2.AddOne(); + ECFieldElement A = curve.A.Add(L2plus1).Multiply(Z1Sq).Add(L1Sq).MultiplyPlusProduct(T, X1Sq, Z1Sq); + ECFieldElement X2Z1Sq = X2.Multiply(Z1Sq); + ECFieldElement B = X2Z1Sq.Add(T).Square(); + + if (B.IsZero) + { + if (A.IsZero) + return b.Twice(); + + return curve.Infinity; + } + + if (A.IsZero) + { + return new SecT193R1Point(curve, A, curve.B.Sqrt(), IsCompressed); + } + + ECFieldElement X3 = A.Square().Multiply(X2Z1Sq); + ECFieldElement Z3 = A.Multiply(B).Multiply(Z1Sq); + ECFieldElement L3 = A.Add(B).Square().MultiplyPlusProduct(T, L2plus1, Z3); + + return new SecT193R1Point(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint Negate() + { + if (this.IsInfinity) + return this; + + ECFieldElement X = this.RawXCoord; + if (X.IsZero) + return this; + + // L is actually Lambda (X + Y/X) here + ECFieldElement L = this.RawYCoord, Z = this.RawZCoords[0]; + return new SecT193R1Point(Curve, X, L.Add(Z), new ECFieldElement[] { Z }, IsCompressed); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT193R2Curve.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT193R2Curve.cs new file mode 100644 index 0000000..1c84a3e --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT193R2Curve.cs @@ -0,0 +1,98 @@ +using System; + +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT193R2Curve + : AbstractF2mCurve + { + private const int SecT193R2_DEFAULT_COORDS = COORD_LAMBDA_PROJECTIVE; + + protected readonly SecT193R2Point m_infinity; + + public SecT193R2Curve() + : base(193, 15, 0, 0) + { + this.m_infinity = new SecT193R2Point(this, null, null); + + this.m_a = FromBigInteger(new BigInteger(1, Hex.Decode("0163F35A5137C2CE3EA6ED8667190B0BC43ECD69977702709B"))); + this.m_b = FromBigInteger(new BigInteger(1, Hex.Decode("00C9BB9E8927D4D64C377E2AB2856A5B16E3EFB7F61D4316AE"))); + this.m_order = new BigInteger(1, Hex.Decode("010000000000000000000000015AAB561B005413CCD4EE99D5")); + this.m_cofactor = BigInteger.Two; + + this.m_coord = SecT193R2_DEFAULT_COORDS; + } + + protected override ECCurve CloneCurve() + { + return new SecT193R2Curve(); + } + + public override bool SupportsCoordinateSystem(int coord) + { + switch (coord) + { + case COORD_LAMBDA_PROJECTIVE: + return true; + default: + return false; + } + } + + public override ECPoint Infinity + { + get { return m_infinity; } + } + + public override int FieldSize + { + get { return 193; } + } + + public override ECFieldElement FromBigInteger(BigInteger x) + { + return new SecT193FieldElement(x); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, bool withCompression) + { + return new SecT193R2Point(this, x, y, withCompression); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + { + return new SecT193R2Point(this, x, y, zs, withCompression); + } + + public override bool IsKoblitz + { + get { return false; } + } + + public virtual int M + { + get { return 193; } + } + + public virtual bool IsTrinomial + { + get { return true; } + } + + public virtual int K1 + { + get { return 15; } + } + + public virtual int K2 + { + get { return 0; } + } + + public virtual int K3 + { + get { return 0; } + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT193R2Point.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT193R2Point.cs new file mode 100644 index 0000000..18d89e3 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT193R2Point.cs @@ -0,0 +1,283 @@ +using System; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT193R2Point + : AbstractF2mPoint + { + /** + * @deprecated Use ECCurve.createPoint to construct points + */ + public SecT193R2Point(ECCurve curve, ECFieldElement x, ECFieldElement y) + : this(curve, x, y, false) + { + } + + /** + * @deprecated per-point compression property will be removed, refer {@link #getEncoded(bool)} + */ + public SecT193R2Point(ECCurve curve, ECFieldElement x, ECFieldElement y, bool withCompression) + : base(curve, x, y, withCompression) + { + if ((x == null) != (y == null)) + throw new ArgumentException("Exactly one of the field elements is null"); + } + + internal SecT193R2Point(ECCurve curve, ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + : base(curve, x, y, zs, withCompression) + { + } + + protected override ECPoint Detach() + { + return new SecT193R2Point(null, AffineXCoord, AffineYCoord); + } + + public override ECFieldElement YCoord + { + get + { + ECFieldElement X = RawXCoord, L = RawYCoord; + + if (this.IsInfinity || X.IsZero) + return L; + + // Y is actually Lambda (X + Y/X) here; convert to affine value on the fly + ECFieldElement Y = L.Add(X).Multiply(X); + + ECFieldElement Z = RawZCoords[0]; + if (!Z.IsOne) + { + Y = Y.Divide(Z); + } + + return Y; + } + } + + protected internal override bool CompressionYTilde + { + get + { + ECFieldElement X = this.RawXCoord; + if (X.IsZero) + return false; + + ECFieldElement Y = this.RawYCoord; + + // Y is actually Lambda (X + Y/X) here + return Y.TestBitZero() != X.TestBitZero(); + } + } + + public override ECPoint Add(ECPoint b) + { + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return this; + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + ECFieldElement X2 = b.RawXCoord; + + if (X1.IsZero) + { + if (X2.IsZero) + return curve.Infinity; + + return b.Add(this); + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + ECFieldElement L2 = b.RawYCoord, Z2 = b.RawZCoords[0]; + + bool Z1IsOne = Z1.IsOne; + ECFieldElement U2 = X2, S2 = L2; + if (!Z1IsOne) + { + U2 = U2.Multiply(Z1); + S2 = S2.Multiply(Z1); + } + + bool Z2IsOne = Z2.IsOne; + ECFieldElement U1 = X1, S1 = L1; + if (!Z2IsOne) + { + U1 = U1.Multiply(Z2); + S1 = S1.Multiply(Z2); + } + + ECFieldElement A = S1.Add(S2); + ECFieldElement B = U1.Add(U2); + + if (B.IsZero) + { + if (A.IsZero) + return Twice(); + + return curve.Infinity; + } + + ECFieldElement X3, L3, Z3; + if (X2.IsZero) + { + // TODO This can probably be optimized quite a bit + ECPoint p = this.Normalize(); + X1 = p.XCoord; + ECFieldElement Y1 = p.YCoord; + + ECFieldElement Y2 = L2; + ECFieldElement L = Y1.Add(Y2).Divide(X1); + + X3 = L.Square().Add(L).Add(X1).Add(curve.A); + if (X3.IsZero) + { + return new SecT193R2Point(curve, X3, curve.B.Sqrt(), IsCompressed); + } + + ECFieldElement Y3 = L.Multiply(X1.Add(X3)).Add(X3).Add(Y1); + L3 = Y3.Divide(X3).Add(X3); + Z3 = curve.FromBigInteger(BigInteger.One); + } + else + { + B = B.Square(); + + ECFieldElement AU1 = A.Multiply(U1); + ECFieldElement AU2 = A.Multiply(U2); + + X3 = AU1.Multiply(AU2); + if (X3.IsZero) + { + return new SecT193R2Point(curve, X3, curve.B.Sqrt(), IsCompressed); + } + + ECFieldElement ABZ2 = A.Multiply(B); + if (!Z2IsOne) + { + ABZ2 = ABZ2.Multiply(Z2); + } + + L3 = AU2.Add(B).SquarePlusProduct(ABZ2, L1.Add(Z1)); + + Z3 = ABZ2; + if (!Z1IsOne) + { + Z3 = Z3.Multiply(Z1); + } + } + + return new SecT193R2Point(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint Twice() + { + if (this.IsInfinity) + { + return this; + } + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + if (X1.IsZero) + { + // A point with X == 0 is it's own Additive inverse + return curve.Infinity; + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + + bool Z1IsOne = Z1.IsOne; + ECFieldElement L1Z1 = Z1IsOne ? L1 : L1.Multiply(Z1); + ECFieldElement Z1Sq = Z1IsOne ? Z1 : Z1.Square(); + ECFieldElement a = curve.A; + ECFieldElement aZ1Sq = Z1IsOne ? a : a.Multiply(Z1Sq); + ECFieldElement T = L1.Square().Add(L1Z1).Add(aZ1Sq); + if (T.IsZero) + { + return new SecT193R2Point(curve, T, curve.B.Sqrt(), IsCompressed); + } + + ECFieldElement X3 = T.Square(); + ECFieldElement Z3 = Z1IsOne ? T : T.Multiply(Z1Sq); + + ECFieldElement X1Z1 = Z1IsOne ? X1 : X1.Multiply(Z1); + ECFieldElement L3 = X1Z1.SquarePlusProduct(T, L1Z1).Add(X3).Add(Z3); + + return new SecT193R2Point(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint TwicePlus(ECPoint b) + { + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return Twice(); + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + if (X1.IsZero) + { + // A point with X == 0 is it's own Additive inverse + return b; + } + + ECFieldElement X2 = b.RawXCoord, Z2 = b.RawZCoords[0]; + if (X2.IsZero || !Z2.IsOne) + { + return Twice().Add(b); + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + ECFieldElement L2 = b.RawYCoord; + + ECFieldElement X1Sq = X1.Square(); + ECFieldElement L1Sq = L1.Square(); + ECFieldElement Z1Sq = Z1.Square(); + ECFieldElement L1Z1 = L1.Multiply(Z1); + + ECFieldElement T = curve.A.Multiply(Z1Sq).Add(L1Sq).Add(L1Z1); + ECFieldElement L2plus1 = L2.AddOne(); + ECFieldElement A = curve.A.Add(L2plus1).Multiply(Z1Sq).Add(L1Sq).MultiplyPlusProduct(T, X1Sq, Z1Sq); + ECFieldElement X2Z1Sq = X2.Multiply(Z1Sq); + ECFieldElement B = X2Z1Sq.Add(T).Square(); + + if (B.IsZero) + { + if (A.IsZero) + return b.Twice(); + + return curve.Infinity; + } + + if (A.IsZero) + { + return new SecT193R2Point(curve, A, curve.B.Sqrt(), IsCompressed); + } + + ECFieldElement X3 = A.Square().Multiply(X2Z1Sq); + ECFieldElement Z3 = A.Multiply(B).Multiply(Z1Sq); + ECFieldElement L3 = A.Add(B).Square().MultiplyPlusProduct(T, L2plus1, Z3); + + return new SecT193R2Point(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint Negate() + { + if (this.IsInfinity) + return this; + + ECFieldElement X = this.RawXCoord; + if (X.IsZero) + return this; + + // L is actually Lambda (X + Y/X) here + ECFieldElement L = this.RawYCoord, Z = this.RawZCoords[0]; + return new SecT193R2Point(Curve, X, L.Add(Z), new ECFieldElement[] { Z }, IsCompressed); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT233Field.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT233Field.cs new file mode 100644 index 0000000..870dade --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT233Field.cs @@ -0,0 +1,317 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Math.Raw; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT233Field + { + private const ulong M41 = ulong.MaxValue >> 23; + private const ulong M59 = ulong.MaxValue >> 5; + + public static void Add(ulong[] x, ulong[] y, ulong[] z) + { + z[0] = x[0] ^ y[0]; + z[1] = x[1] ^ y[1]; + z[2] = x[2] ^ y[2]; + z[3] = x[3] ^ y[3]; + } + + public static void AddExt(ulong[] xx, ulong[] yy, ulong[] zz) + { + zz[0] = xx[0] ^ yy[0]; + zz[1] = xx[1] ^ yy[1]; + zz[2] = xx[2] ^ yy[2]; + zz[3] = xx[3] ^ yy[3]; + zz[4] = xx[4] ^ yy[4]; + zz[5] = xx[5] ^ yy[5]; + zz[6] = xx[6] ^ yy[6]; + zz[7] = xx[7] ^ yy[7]; + } + + public static void AddOne(ulong[] x, ulong[] z) + { + z[0] = x[0] ^ 1UL; + z[1] = x[1]; + z[2] = x[2]; + z[3] = x[3]; + } + + public static ulong[] FromBigInteger(BigInteger x) + { + ulong[] z = Nat256.FromBigInteger64(x); + Reduce23(z, 0); + return z; + } + + public static void Invert(ulong[] x, ulong[] z) + { + if (Nat256.IsZero64(x)) + throw new InvalidOperationException(); + + // Itoh-Tsujii inversion + + ulong[] t0 = Nat256.Create64(); + ulong[] t1 = Nat256.Create64(); + + Square(x, t0); + Multiply(t0, x, t0); + Square(t0, t0); + Multiply(t0, x, t0); + SquareN(t0, 3, t1); + Multiply(t1, t0, t1); + Square(t1, t1); + Multiply(t1, x, t1); + SquareN(t1, 7, t0); + Multiply(t0, t1, t0); + SquareN(t0, 14, t1); + Multiply(t1, t0, t1); + Square(t1, t1); + Multiply(t1, x, t1); + SquareN(t1, 29, t0); + Multiply(t0, t1, t0); + SquareN(t0, 58, t1); + Multiply(t1, t0, t1); + SquareN(t1, 116, t0); + Multiply(t0, t1, t0); + Square(t0, z); + } + + public static void Multiply(ulong[] x, ulong[] y, ulong[] z) + { + ulong[] tt = Nat256.CreateExt64(); + ImplMultiply(x, y, tt); + Reduce(tt, z); + } + + public static void MultiplyAddToExt(ulong[] x, ulong[] y, ulong[] zz) + { + ulong[] tt = Nat256.CreateExt64(); + ImplMultiply(x, y, tt); + AddExt(zz, tt, zz); + } + + public static void Reduce(ulong[] xx, ulong[] z) + { + ulong x0 = xx[0], x1 = xx[1], x2 = xx[2], x3 = xx[3]; + ulong x4 = xx[4], x5 = xx[5], x6 = xx[6], x7 = xx[7]; + + x3 ^= (x7 << 23); + x4 ^= (x7 >> 41) ^ (x7 << 33); + x5 ^= (x7 >> 31); + + x2 ^= (x6 << 23); + x3 ^= (x6 >> 41) ^ (x6 << 33); + x4 ^= (x6 >> 31); + + x1 ^= (x5 << 23); + x2 ^= (x5 >> 41) ^ (x5 << 33); + x3 ^= (x5 >> 31); + + x0 ^= (x4 << 23); + x1 ^= (x4 >> 41) ^ (x4 << 33); + x2 ^= (x4 >> 31); + + ulong t = x3 >> 41; + z[0] = x0 ^ t; + z[1] = x1 ^ (t << 10); + z[2] = x2; + z[3] = x3 & M41; + } + + public static void Reduce23(ulong[] z, int zOff) + { + ulong z3 = z[zOff + 3], t = z3 >> 41; + z[zOff ] ^= t; + z[zOff + 1] ^= (t << 10); + z[zOff + 3] = z3 & M41; + } + + public static void Sqrt(ulong[] x, ulong[] z) + { + ulong u0, u1; + u0 = Interleave.Unshuffle(x[0]); u1 = Interleave.Unshuffle(x[1]); + ulong e0 = (u0 & 0x00000000FFFFFFFFUL) | (u1 << 32); + ulong c0 = (u0 >> 32) | (u1 & 0xFFFFFFFF00000000UL); + + u0 = Interleave.Unshuffle(x[2]); u1 = Interleave.Unshuffle(x[3]); + ulong e1 = (u0 & 0x00000000FFFFFFFFUL) | (u1 << 32); + ulong c1 = (u0 >> 32) | (u1 & 0xFFFFFFFF00000000UL); + + ulong c2; + c2 = (c1 >> 27); + c1 ^= (c0 >> 27) | (c1 << 37); + c0 ^= (c0 << 37); + + ulong[] tt = Nat256.CreateExt64(); + + int[] shifts = { 32, 117, 191 }; + for (int i = 0; i < shifts.Length; ++i) + { + int w = shifts[i] >> 6, s = shifts[i] & 63; + Debug.Assert(s != 0); + tt[w ] ^= (c0 << s); + tt[w + 1] ^= (c1 << s) | (c0 >> -s); + tt[w + 2] ^= (c2 << s) | (c1 >> -s); + tt[w + 3] ^= (c2 >> -s); + } + + Reduce(tt, z); + + z[0] ^= e0; + z[1] ^= e1; + } + + public static void Square(ulong[] x, ulong[] z) + { + ulong[] tt = Nat256.CreateExt64(); + ImplSquare(x, tt); + Reduce(tt, z); + } + + public static void SquareAddToExt(ulong[] x, ulong[] zz) + { + ulong[] tt = Nat256.CreateExt64(); + ImplSquare(x, tt); + AddExt(zz, tt, zz); + } + + public static void SquareN(ulong[] x, int n, ulong[] z) + { + Debug.Assert(n > 0); + + ulong[] tt = Nat256.CreateExt64(); + ImplSquare(x, tt); + Reduce(tt, z); + + while (--n > 0) + { + ImplSquare(z, tt); + Reduce(tt, z); + } + } + + public static uint Trace(ulong[] x) + { + // Non-zero-trace bits: 0, 159 + return (uint)(x[0] ^ (x[2] >> 31)) & 1U; + } + + protected static void ImplCompactExt(ulong[] zz) + { + ulong z0 = zz[0], z1 = zz[1], z2 = zz[2], z3 = zz[3], z4 = zz[4], z5 = zz[5], z6 = zz[6], z7 = zz[7]; + zz[0] = z0 ^ (z1 << 59); + zz[1] = (z1 >> 5) ^ (z2 << 54); + zz[2] = (z2 >> 10) ^ (z3 << 49); + zz[3] = (z3 >> 15) ^ (z4 << 44); + zz[4] = (z4 >> 20) ^ (z5 << 39); + zz[5] = (z5 >> 25) ^ (z6 << 34); + zz[6] = (z6 >> 30) ^ (z7 << 29); + zz[7] = (z7 >> 35); + } + + protected static void ImplExpand(ulong[] x, ulong[] z) + { + ulong x0 = x[0], x1 = x[1], x2 = x[2], x3 = x[3]; + z[0] = x0 & M59; + z[1] = ((x0 >> 59) ^ (x1 << 5)) & M59; + z[2] = ((x1 >> 54) ^ (x2 << 10)) & M59; + z[3] = ((x2 >> 49) ^ (x3 << 15)); + } + + protected static void ImplMultiply(ulong[] x, ulong[] y, ulong[] zz) + { + /* + * "Two-level seven-way recursion" as described in "Batch binary Edwards", Daniel J. Bernstein. + */ + + ulong[] f = new ulong[4], g = new ulong[4]; + ImplExpand(x, f); + ImplExpand(y, g); + + ImplMulwAcc(f[0], g[0], zz, 0); + ImplMulwAcc(f[1], g[1], zz, 1); + ImplMulwAcc(f[2], g[2], zz, 2); + ImplMulwAcc(f[3], g[3], zz, 3); + + // U *= (1 - t^n) + for (int i = 5; i > 0; --i) + { + zz[i] ^= zz[i - 1]; + } + + ImplMulwAcc(f[0] ^ f[1], g[0] ^ g[1], zz, 1); + ImplMulwAcc(f[2] ^ f[3], g[2] ^ g[3], zz, 3); + + // V *= (1 - t^2n) + for (int i = 7; i > 1; --i) + { + zz[i] ^= zz[i - 2]; + } + + // Double-length recursion + { + ulong c0 = f[0] ^ f[2], c1 = f[1] ^ f[3]; + ulong d0 = g[0] ^ g[2], d1 = g[1] ^ g[3]; + ImplMulwAcc(c0 ^ c1, d0 ^ d1, zz, 3); + ulong[] t = new ulong[3]; + ImplMulwAcc(c0, d0, t, 0); + ImplMulwAcc(c1, d1, t, 1); + ulong t0 = t[0], t1 = t[1], t2 = t[2]; + zz[2] ^= t0; + zz[3] ^= t0 ^ t1; + zz[4] ^= t2 ^ t1; + zz[5] ^= t2; + } + + ImplCompactExt(zz); + } + + protected static void ImplMulwAcc(ulong x, ulong y, ulong[] z, int zOff) + { + Debug.Assert(x >> 59 == 0); + Debug.Assert(y >> 59 == 0); + + ulong[] u = new ulong[8]; + //u[0] = 0; + u[1] = y; + u[2] = u[1] << 1; + u[3] = u[2] ^ y; + u[4] = u[2] << 1; + u[5] = u[4] ^ y; + u[6] = u[3] << 1; + u[7] = u[6] ^ y; + + uint j = (uint)x; + ulong g, h = 0, l = u[j & 7] + ^ (u[(j >> 3) & 7] << 3); + int k = 54; + do + { + j = (uint)(x >> k); + g = u[j & 7] + ^ u[(j >> 3) & 7] << 3; + l ^= (g << k); + h ^= (g >> -k); + } + while ((k -= 6) > 0); + + Debug.Assert(h >> 53 == 0); + + z[zOff ] ^= l & M59; + z[zOff + 1] ^= (l >> 59) ^ (h << 5); + } + + protected static void ImplSquare(ulong[] x, ulong[] zz) + { + Interleave.Expand64To128(x[0], zz, 0); + Interleave.Expand64To128(x[1], zz, 2); + Interleave.Expand64To128(x[2], zz, 4); + + ulong x3 = x[3]; + zz[6] = Interleave.Expand32to64((uint)x3); + zz[7] = Interleave.Expand16to32((uint)(x3 >> 32)); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT233FieldElement.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT233FieldElement.cs new file mode 100644 index 0000000..91b8e2f --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT233FieldElement.cs @@ -0,0 +1,216 @@ +using System; + +using Org.BouncyCastle.Math.Raw; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT233FieldElement + : ECFieldElement + { + protected readonly ulong[] x; + + public SecT233FieldElement(BigInteger x) + { + if (x == null || x.SignValue < 0 || x.BitLength > 233) + throw new ArgumentException("value invalid for SecT233FieldElement", "x"); + + this.x = SecT233Field.FromBigInteger(x); + } + + public SecT233FieldElement() + { + this.x = Nat256.Create64(); + } + + protected internal SecT233FieldElement(ulong[] x) + { + this.x = x; + } + + public override bool IsOne + { + get { return Nat256.IsOne64(x); } + } + + public override bool IsZero + { + get { return Nat256.IsZero64(x); } + } + + public override bool TestBitZero() + { + return (x[0] & 1UL) != 0UL; + } + + public override BigInteger ToBigInteger() + { + return Nat256.ToBigInteger64(x); + } + + public override string FieldName + { + get { return "SecT233Field"; } + } + + public override int FieldSize + { + get { return 233; } + } + + public override ECFieldElement Add(ECFieldElement b) + { + ulong[] z = Nat256.Create64(); + SecT233Field.Add(x, ((SecT233FieldElement)b).x, z); + return new SecT233FieldElement(z); + } + + public override ECFieldElement AddOne() + { + ulong[] z = Nat256.Create64(); + SecT233Field.AddOne(x, z); + return new SecT233FieldElement(z); + } + + public override ECFieldElement Subtract(ECFieldElement b) + { + // Addition and Subtraction are the same in F2m + return Add(b); + } + + public override ECFieldElement Multiply(ECFieldElement b) + { + ulong[] z = Nat256.Create64(); + SecT233Field.Multiply(x, ((SecT233FieldElement)b).x, z); + return new SecT233FieldElement(z); + } + + public override ECFieldElement MultiplyMinusProduct(ECFieldElement b, ECFieldElement x, ECFieldElement y) + { + return MultiplyPlusProduct(b, x, y); + } + + public override ECFieldElement MultiplyPlusProduct(ECFieldElement b, ECFieldElement x, ECFieldElement y) + { + ulong[] ax = this.x, bx = ((SecT233FieldElement)b).x; + ulong[] xx = ((SecT233FieldElement)x).x, yx = ((SecT233FieldElement)y).x; + + ulong[] tt = Nat256.CreateExt64(); + SecT233Field.MultiplyAddToExt(ax, bx, tt); + SecT233Field.MultiplyAddToExt(xx, yx, tt); + + ulong[] z = Nat256.Create64(); + SecT233Field.Reduce(tt, z); + return new SecT233FieldElement(z); + } + + public override ECFieldElement Divide(ECFieldElement b) + { + return Multiply(b.Invert()); + } + + public override ECFieldElement Negate() + { + return this; + } + + public override ECFieldElement Square() + { + ulong[] z = Nat256.Create64(); + SecT233Field.Square(x, z); + return new SecT233FieldElement(z); + } + + public override ECFieldElement SquareMinusProduct(ECFieldElement x, ECFieldElement y) + { + return SquarePlusProduct(x, y); + } + + public override ECFieldElement SquarePlusProduct(ECFieldElement x, ECFieldElement y) + { + ulong[] ax = this.x; + ulong[] xx = ((SecT233FieldElement)x).x, yx = ((SecT233FieldElement)y).x; + + ulong[] tt = Nat256.CreateExt64(); + SecT233Field.SquareAddToExt(ax, tt); + SecT233Field.MultiplyAddToExt(xx, yx, tt); + + ulong[] z = Nat256.Create64(); + SecT233Field.Reduce(tt, z); + return new SecT233FieldElement(z); + } + + public override ECFieldElement SquarePow(int pow) + { + if (pow < 1) + return this; + + ulong[] z = Nat256.Create64(); + SecT233Field.SquareN(x, pow, z); + return new SecT233FieldElement(z); + } + + public override ECFieldElement Invert() + { + ulong[] z = Nat256.Create64(); + SecT233Field.Invert(x, z); + return new SecT233FieldElement(z); + } + + public override ECFieldElement Sqrt() + { + ulong[] z = Nat256.Create64(); + SecT233Field.Sqrt(x, z); + return new SecT233FieldElement(z); + } + + public virtual int Representation + { + get { return F2mFieldElement.Tpb; } + } + + public virtual int M + { + get { return 233; } + } + + public virtual int K1 + { + get { return 74; } + } + + public virtual int K2 + { + get { return 0; } + } + + public virtual int K3 + { + get { return 0; } + } + + public override bool Equals(object obj) + { + return Equals(obj as SecT233FieldElement); + } + + public override bool Equals(ECFieldElement other) + { + return Equals(other as SecT233FieldElement); + } + + public virtual bool Equals(SecT233FieldElement other) + { + if (this == other) + return true; + if (null == other) + return false; + return Nat256.Eq64(x, other.x); + } + + public override int GetHashCode() + { + return 2330074 ^ Arrays.GetHashCode(x, 0, 4); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT233K1Curve.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT233K1Curve.cs new file mode 100644 index 0000000..7293591 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT233K1Curve.cs @@ -0,0 +1,104 @@ +using System; + +using Org.BouncyCastle.Math.EC.Multiplier; +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT233K1Curve + : AbstractF2mCurve + { + private const int SecT233K1_DEFAULT_COORDS = COORD_LAMBDA_PROJECTIVE; + + protected readonly SecT233K1Point m_infinity; + + public SecT233K1Curve() + : base(233, 74, 0, 0) + { + this.m_infinity = new SecT233K1Point(this, null, null); + + this.m_a = FromBigInteger(BigInteger.Zero); + this.m_b = FromBigInteger(BigInteger.One); + this.m_order = new BigInteger(1, Hex.Decode("8000000000000000000000000000069D5BB915BCD46EFB1AD5F173ABDF")); + this.m_cofactor = BigInteger.ValueOf(4); + + this.m_coord = SecT233K1_DEFAULT_COORDS; + } + + protected override ECCurve CloneCurve() + { + return new SecT233K1Curve(); + } + + public override bool SupportsCoordinateSystem(int coord) + { + switch (coord) + { + case COORD_LAMBDA_PROJECTIVE: + return true; + default: + return false; + } + } + + protected override ECMultiplier CreateDefaultMultiplier() + { + return new WTauNafMultiplier(); + } + + public override int FieldSize + { + get { return 233; } + } + + public override ECFieldElement FromBigInteger(BigInteger x) + { + return new SecT233FieldElement(x); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, bool withCompression) + { + return new SecT233K1Point(this, x, y, withCompression); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + { + return new SecT233K1Point(this, x, y, zs, withCompression); + } + + public override ECPoint Infinity + { + get { return m_infinity; } + } + + public override bool IsKoblitz + { + get { return true; } + } + + public virtual int M + { + get { return 233; } + } + + public virtual bool IsTrinomial + { + get { return true; } + } + + public virtual int K1 + { + get { return 74; } + } + + public virtual int K2 + { + get { return 0; } + } + + public virtual int K3 + { + get { return 0; } + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT233K1Point.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT233K1Point.cs new file mode 100644 index 0000000..9a357ff --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT233K1Point.cs @@ -0,0 +1,295 @@ +using System; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT233K1Point + : AbstractF2mPoint + { + /** + * @deprecated Use ECCurve.createPoint to construct points + */ + public SecT233K1Point(ECCurve curve, ECFieldElement x, ECFieldElement y) + : this(curve, x, y, false) + { + } + + /** + * @deprecated per-point compression property will be removed, refer {@link #getEncoded(bool)} + */ + public SecT233K1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, bool withCompression) + : base(curve, x, y, withCompression) + { + if ((x == null) != (y == null)) + throw new ArgumentException("Exactly one of the field elements is null"); + } + + internal SecT233K1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + : base(curve, x, y, zs, withCompression) + { + } + + protected override ECPoint Detach() + { + return new SecT233K1Point(null, this.AffineXCoord, this.AffineYCoord); // earlier JDK + } + + public override ECFieldElement YCoord + { + get + { + ECFieldElement X = RawXCoord, L = RawYCoord; + + if (this.IsInfinity || X.IsZero) + return L; + + // Y is actually Lambda (X + Y/X) here; convert to affine value on the fly + ECFieldElement Y = L.Add(X).Multiply(X); + + ECFieldElement Z = RawZCoords[0]; + if (!Z.IsOne) + { + Y = Y.Divide(Z); + } + + return Y; + } + } + + protected internal override bool CompressionYTilde + { + get + { + ECFieldElement X = this.RawXCoord; + if (X.IsZero) + return false; + + ECFieldElement Y = this.RawYCoord; + + // Y is actually Lambda (X + Y/X) here + return Y.TestBitZero() != X.TestBitZero(); + } + } + + public override ECPoint Add(ECPoint b) + { + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return this; + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + ECFieldElement X2 = b.RawXCoord; + + if (X1.IsZero) + { + if (X2.IsZero) + { + return curve.Infinity; + } + + return b.Add(this); + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + ECFieldElement L2 = b.RawYCoord, Z2 = b.RawZCoords[0]; + + bool Z1IsOne = Z1.IsOne; + ECFieldElement U2 = X2, S2 = L2; + if (!Z1IsOne) + { + U2 = U2.Multiply(Z1); + S2 = S2.Multiply(Z1); + } + + bool Z2IsOne = Z2.IsOne; + ECFieldElement U1 = X1, S1 = L1; + if (!Z2IsOne) + { + U1 = U1.Multiply(Z2); + S1 = S1.Multiply(Z2); + } + + ECFieldElement A = S1.Add(S2); + ECFieldElement B = U1.Add(U2); + + if (B.IsZero) + { + if (A.IsZero) + return Twice(); + + return curve.Infinity; + } + + ECFieldElement X3, L3, Z3; + if (X2.IsZero) + { + // TODO This can probably be optimized quite a bit + ECPoint p = this.Normalize(); + X1 = p.XCoord; + ECFieldElement Y1 = p.YCoord; + + ECFieldElement Y2 = L2; + ECFieldElement L = Y1.Add(Y2).Divide(X1); + + X3 = L.Square().Add(L).Add(X1); + if (X3.IsZero) + { + return new SecT233K1Point(curve, X3, curve.B, IsCompressed); + } + + ECFieldElement Y3 = L.Multiply(X1.Add(X3)).Add(X3).Add(Y1); + L3 = Y3.Divide(X3).Add(X3); + Z3 = curve.FromBigInteger(BigInteger.One); + } + else + { + B = B.Square(); + + ECFieldElement AU1 = A.Multiply(U1); + ECFieldElement AU2 = A.Multiply(U2); + + X3 = AU1.Multiply(AU2); + if (X3.IsZero) + { + return new SecT233K1Point(curve, X3, curve.B, IsCompressed); + } + + ECFieldElement ABZ2 = A.Multiply(B); + if (!Z2IsOne) + { + ABZ2 = ABZ2.Multiply(Z2); + } + + L3 = AU2.Add(B).SquarePlusProduct(ABZ2, L1.Add(Z1)); + + Z3 = ABZ2; + if (!Z1IsOne) + { + Z3 = Z3.Multiply(Z1); + } + } + + return new SecT233K1Point(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint Twice() + { + if (this.IsInfinity) + { + return this; + } + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + if (X1.IsZero) + { + // A point with X == 0 is it's own Additive inverse + return curve.Infinity; + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + + bool Z1IsOne = Z1.IsOne; + ECFieldElement Z1Sq = Z1IsOne ? Z1 : Z1.Square(); + ECFieldElement T; + if (Z1IsOne) + { + T = L1.Square().Add(L1); + } + else + { + T = L1.Add(Z1).Multiply(L1); + } + + if (T.IsZero) + { + return new SecT233K1Point(curve, T, curve.B, IsCompressed); + } + + ECFieldElement X3 = T.Square(); + ECFieldElement Z3 = Z1IsOne ? T : T.Multiply(Z1Sq); + + ECFieldElement t1 = L1.Add(X1).Square(); + ECFieldElement t2 = Z1IsOne ? Z1 : Z1Sq.Square(); + ECFieldElement L3 = t1.Add(T).Add(Z1Sq).Multiply(t1).Add(t2).Add(X3).Add(Z3); + + return new SecT233K1Point(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint TwicePlus(ECPoint b) + { + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return Twice(); + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + if (X1.IsZero) + { + // A point with X == 0 is it's own Additive inverse + return b; + } + + // NOTE: TwicePlus() only optimized for lambda-affine argument + ECFieldElement X2 = b.RawXCoord, Z2 = b.RawZCoords[0]; + if (X2.IsZero || !Z2.IsOne) + { + return Twice().Add(b); + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + ECFieldElement L2 = b.RawYCoord; + + ECFieldElement X1Sq = X1.Square(); + ECFieldElement L1Sq = L1.Square(); + ECFieldElement Z1Sq = Z1.Square(); + ECFieldElement L1Z1 = L1.Multiply(Z1); + + ECFieldElement T = L1Sq.Add(L1Z1); + ECFieldElement L2plus1 = L2.AddOne(); + ECFieldElement A = L2plus1.Multiply(Z1Sq).Add(L1Sq).MultiplyPlusProduct(T, X1Sq, Z1Sq); + ECFieldElement X2Z1Sq = X2.Multiply(Z1Sq); + ECFieldElement B = X2Z1Sq.Add(T).Square(); + + if (B.IsZero) + { + if (A.IsZero) + { + return b.Twice(); + } + + return curve.Infinity; + } + + if (A.IsZero) + { + return new SecT233K1Point(curve, A, curve.B, IsCompressed); + } + + ECFieldElement X3 = A.Square().Multiply(X2Z1Sq); + ECFieldElement Z3 = A.Multiply(B).Multiply(Z1Sq); + ECFieldElement L3 = A.Add(B).Square().MultiplyPlusProduct(T, L2plus1, Z3); + + return new SecT233K1Point(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint Negate() + { + if (this.IsInfinity) + return this; + + ECFieldElement X = this.RawXCoord; + if (X.IsZero) + return this; + + // L is actually Lambda (X + Y/X) here + ECFieldElement L = this.RawYCoord, Z = this.RawZCoords[0]; + return new SecT233K1Point(Curve, X, L.Add(Z), new ECFieldElement[] { Z }, IsCompressed); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT233R1Curve.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT233R1Curve.cs new file mode 100644 index 0000000..db6e6e1 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT233R1Curve.cs @@ -0,0 +1,98 @@ +using System; + +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT233R1Curve + : AbstractF2mCurve + { + private const int SecT233R1_DEFAULT_COORDS = COORD_LAMBDA_PROJECTIVE; + + protected readonly SecT233R1Point m_infinity; + + public SecT233R1Curve() + : base(233, 74, 0, 0) + { + this.m_infinity = new SecT233R1Point(this, null, null); + + this.m_a = FromBigInteger(BigInteger.One); + this.m_b = FromBigInteger(new BigInteger(1, Hex.Decode("0066647EDE6C332C7F8C0923BB58213B333B20E9CE4281FE115F7D8F90AD"))); + this.m_order = new BigInteger(1, Hex.Decode("01000000000000000000000000000013E974E72F8A6922031D2603CFE0D7")); + this.m_cofactor = BigInteger.Two; + + this.m_coord = SecT233R1_DEFAULT_COORDS; + } + + protected override ECCurve CloneCurve() + { + return new SecT233R1Curve(); + } + + public override bool SupportsCoordinateSystem(int coord) + { + switch (coord) + { + case COORD_LAMBDA_PROJECTIVE: + return true; + default: + return false; + } + } + + public override ECPoint Infinity + { + get { return m_infinity; } + } + + public override int FieldSize + { + get { return 233; } + } + + public override ECFieldElement FromBigInteger(BigInteger x) + { + return new SecT233FieldElement(x); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, bool withCompression) + { + return new SecT233R1Point(this, x, y, withCompression); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + { + return new SecT233R1Point(this, x, y, zs, withCompression); + } + + public override bool IsKoblitz + { + get { return false; } + } + + public virtual int M + { + get { return 233; } + } + + public virtual bool IsTrinomial + { + get { return true; } + } + + public virtual int K1 + { + get { return 74; } + } + + public virtual int K2 + { + get { return 0; } + } + + public virtual int K3 + { + get { return 0; } + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT233R1Point.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT233R1Point.cs new file mode 100644 index 0000000..6347051 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT233R1Point.cs @@ -0,0 +1,278 @@ +using System; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT233R1Point + : AbstractF2mPoint + { + /** + * @deprecated Use ECCurve.createPoint to construct points + */ + public SecT233R1Point(ECCurve curve, ECFieldElement x, ECFieldElement y) + : this(curve, x, y, false) + { + } + + /** + * @deprecated per-point compression property will be removed, refer {@link #getEncoded(bool)} + */ + public SecT233R1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, bool withCompression) + : base(curve, x, y, withCompression) + { + if ((x == null) != (y == null)) + throw new ArgumentException("Exactly one of the field elements is null"); + } + + internal SecT233R1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + : base(curve, x, y, zs, withCompression) + { + } + + protected override ECPoint Detach() + { + return new SecT233R1Point(null, AffineXCoord, AffineYCoord); + } + + public override ECFieldElement YCoord + { + get + { + ECFieldElement X = RawXCoord, L = RawYCoord; + + if (this.IsInfinity || X.IsZero) + return L; + + // Y is actually Lambda (X + Y/X) here; convert to affine value on the fly + ECFieldElement Y = L.Add(X).Multiply(X); + + ECFieldElement Z = RawZCoords[0]; + if (!Z.IsOne) + { + Y = Y.Divide(Z); + } + + return Y; + } + } + + protected internal override bool CompressionYTilde + { + get + { + ECFieldElement X = this.RawXCoord; + if (X.IsZero) + return false; + + ECFieldElement Y = this.RawYCoord; + + // Y is actually Lambda (X + Y/X) here + return Y.TestBitZero() != X.TestBitZero(); + } + } + + public override ECPoint Add(ECPoint b) + { + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return this; + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + ECFieldElement X2 = b.RawXCoord; + + if (X1.IsZero) + { + if (X2.IsZero) + return curve.Infinity; + + return b.Add(this); + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + ECFieldElement L2 = b.RawYCoord, Z2 = b.RawZCoords[0]; + + bool Z1IsOne = Z1.IsOne; + ECFieldElement U2 = X2, S2 = L2; + if (!Z1IsOne) + { + U2 = U2.Multiply(Z1); + S2 = S2.Multiply(Z1); + } + + bool Z2IsOne = Z2.IsOne; + ECFieldElement U1 = X1, S1 = L1; + if (!Z2IsOne) + { + U1 = U1.Multiply(Z2); + S1 = S1.Multiply(Z2); + } + + ECFieldElement A = S1.Add(S2); + ECFieldElement B = U1.Add(U2); + + if (B.IsZero) + { + if (A.IsZero) + return Twice(); + + return curve.Infinity; + } + + ECFieldElement X3, L3, Z3; + if (X2.IsZero) + { + // TODO This can probably be optimized quite a bit + ECPoint p = this.Normalize(); + X1 = p.XCoord; + ECFieldElement Y1 = p.YCoord; + + ECFieldElement Y2 = L2; + ECFieldElement L = Y1.Add(Y2).Divide(X1); + + X3 = L.Square().Add(L).Add(X1).AddOne(); + if (X3.IsZero) + { + return new SecT233R1Point(curve, X3, curve.B.Sqrt(), IsCompressed); + } + + ECFieldElement Y3 = L.Multiply(X1.Add(X3)).Add(X3).Add(Y1); + L3 = Y3.Divide(X3).Add(X3); + Z3 = curve.FromBigInteger(BigInteger.One); + } + else + { + B = B.Square(); + + ECFieldElement AU1 = A.Multiply(U1); + ECFieldElement AU2 = A.Multiply(U2); + + X3 = AU1.Multiply(AU2); + if (X3.IsZero) + { + return new SecT233R1Point(curve, X3, curve.B.Sqrt(), IsCompressed); + } + + ECFieldElement ABZ2 = A.Multiply(B); + if (!Z2IsOne) + { + ABZ2 = ABZ2.Multiply(Z2); + } + + L3 = AU2.Add(B).SquarePlusProduct(ABZ2, L1.Add(Z1)); + + Z3 = ABZ2; + if (!Z1IsOne) + { + Z3 = Z3.Multiply(Z1); + } + } + + return new SecT233R1Point(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint Twice() + { + if (this.IsInfinity) + return this; + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + if (X1.IsZero) + { + // A point with X == 0 is it's own Additive inverse + return curve.Infinity; + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + + bool Z1IsOne = Z1.IsOne; + ECFieldElement L1Z1 = Z1IsOne ? L1 : L1.Multiply(Z1); + ECFieldElement Z1Sq = Z1IsOne ? Z1 : Z1.Square(); + ECFieldElement T = L1.Square().Add(L1Z1).Add(Z1Sq); + if (T.IsZero) + { + return new SecT233R1Point(curve, T, curve.B.Sqrt(), IsCompressed); + } + + ECFieldElement X3 = T.Square(); + ECFieldElement Z3 = Z1IsOne ? T : T.Multiply(Z1Sq); + + ECFieldElement X1Z1 = Z1IsOne ? X1 : X1.Multiply(Z1); + ECFieldElement L3 = X1Z1.SquarePlusProduct(T, L1Z1).Add(X3).Add(Z3); + + return new SecT233R1Point(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint TwicePlus(ECPoint b) + { + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return Twice(); + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + if (X1.IsZero) + { + // A point with X == 0 is it's own Additive inverse + return b; + } + + ECFieldElement X2 = b.RawXCoord, Z2 = b.RawZCoords[0]; + if (X2.IsZero || !Z2.IsOne) + { + return Twice().Add(b); + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + ECFieldElement L2 = b.RawYCoord; + + ECFieldElement X1Sq = X1.Square(); + ECFieldElement L1Sq = L1.Square(); + ECFieldElement Z1Sq = Z1.Square(); + ECFieldElement L1Z1 = L1.Multiply(Z1); + + ECFieldElement T = Z1Sq.Add(L1Sq).Add(L1Z1); + ECFieldElement A = L2.Multiply(Z1Sq).Add(L1Sq).MultiplyPlusProduct(T, X1Sq, Z1Sq); + ECFieldElement X2Z1Sq = X2.Multiply(Z1Sq); + ECFieldElement B = X2Z1Sq.Add(T).Square(); + + if (B.IsZero) + { + if (A.IsZero) + return b.Twice(); + + return curve.Infinity; + } + + if (A.IsZero) + { + return new SecT233R1Point(curve, A, curve.B.Sqrt(), IsCompressed); + } + + ECFieldElement X3 = A.Square().Multiply(X2Z1Sq); + ECFieldElement Z3 = A.Multiply(B).Multiply(Z1Sq); + ECFieldElement L3 = A.Add(B).Square().MultiplyPlusProduct(T, L2.AddOne(), Z3); + + return new SecT233R1Point(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint Negate() + { + if (this.IsInfinity) + return this; + + ECFieldElement X = this.RawXCoord; + if (X.IsZero) + return this; + + // L is actually Lambda (X + Y/X) here + ECFieldElement L = this.RawYCoord, Z = this.RawZCoords[0]; + return new SecT233R1Point(Curve, X, L.Add(Z), new ECFieldElement[] { Z }, IsCompressed); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT239Field.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT239Field.cs new file mode 100644 index 0000000..2e6ed2a --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT239Field.cs @@ -0,0 +1,328 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Math.Raw; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT239Field + { + private const ulong M47 = ulong.MaxValue >> 17; + private const ulong M60 = ulong.MaxValue >> 4; + + public static void Add(ulong[] x, ulong[] y, ulong[] z) + { + z[0] = x[0] ^ y[0]; + z[1] = x[1] ^ y[1]; + z[2] = x[2] ^ y[2]; + z[3] = x[3] ^ y[3]; + } + + public static void AddExt(ulong[] xx, ulong[] yy, ulong[] zz) + { + zz[0] = xx[0] ^ yy[0]; + zz[1] = xx[1] ^ yy[1]; + zz[2] = xx[2] ^ yy[2]; + zz[3] = xx[3] ^ yy[3]; + zz[4] = xx[4] ^ yy[4]; + zz[5] = xx[5] ^ yy[5]; + zz[6] = xx[6] ^ yy[6]; + zz[7] = xx[7] ^ yy[7]; + } + + public static void AddOne(ulong[] x, ulong[] z) + { + z[0] = x[0] ^ 1UL; + z[1] = x[1]; + z[2] = x[2]; + z[3] = x[3]; + } + + public static ulong[] FromBigInteger(BigInteger x) + { + ulong[] z = Nat256.FromBigInteger64(x); + Reduce17(z, 0); + return z; + } + + public static void Invert(ulong[] x, ulong[] z) + { + if (Nat256.IsZero64(x)) + throw new InvalidOperationException(); + + // Itoh-Tsujii inversion + + ulong[] t0 = Nat256.Create64(); + ulong[] t1 = Nat256.Create64(); + + Square(x, t0); + Multiply(t0, x, t0); + Square(t0, t0); + Multiply(t0, x, t0); + SquareN(t0, 3, t1); + Multiply(t1, t0, t1); + Square(t1, t1); + Multiply(t1, x, t1); + SquareN(t1, 7, t0); + Multiply(t0, t1, t0); + SquareN(t0, 14, t1); + Multiply(t1, t0, t1); + Square(t1, t1); + Multiply(t1, x, t1); + SquareN(t1, 29, t0); + Multiply(t0, t1, t0); + Square(t0, t0); + Multiply(t0, x, t0); + SquareN(t0, 59, t1); + Multiply(t1, t0, t1); + Square(t1, t1); + Multiply(t1, x, t1); + SquareN(t1, 119, t0); + Multiply(t0, t1, t0); + Square(t0, z); + } + + public static void Multiply(ulong[] x, ulong[] y, ulong[] z) + { + ulong[] tt = Nat256.CreateExt64(); + ImplMultiply(x, y, tt); + Reduce(tt, z); + } + + public static void MultiplyAddToExt(ulong[] x, ulong[] y, ulong[] zz) + { + ulong[] tt = Nat256.CreateExt64(); + ImplMultiply(x, y, tt); + AddExt(zz, tt, zz); + } + + public static void Reduce(ulong[] xx, ulong[] z) + { + ulong x0 = xx[0], x1 = xx[1], x2 = xx[2], x3 = xx[3]; + ulong x4 = xx[4], x5 = xx[5], x6 = xx[6], x7 = xx[7]; + + x3 ^= (x7 << 17); + x4 ^= (x7 >> 47); + x5 ^= (x7 << 47); + x6 ^= (x7 >> 17); + + x2 ^= (x6 << 17); + x3 ^= (x6 >> 47); + x4 ^= (x6 << 47); + x5 ^= (x6 >> 17); + + x1 ^= (x5 << 17); + x2 ^= (x5 >> 47); + x3 ^= (x5 << 47); + x4 ^= (x5 >> 17); + + x0 ^= (x4 << 17); + x1 ^= (x4 >> 47); + x2 ^= (x4 << 47); + x3 ^= (x4 >> 17); + + ulong t = x3 >> 47; + z[0] = x0 ^ t; + z[1] = x1; + z[2] = x2 ^ (t << 30); + z[3] = x3 & M47; + } + + public static void Reduce17(ulong[] z, int zOff) + { + ulong z3 = z[zOff + 3], t = z3 >> 47; + z[zOff ] ^= t; + z[zOff + 2] ^= (t << 30); + z[zOff + 3] = z3 & M47; + } + + public static void Sqrt(ulong[] x, ulong[] z) + { + ulong u0, u1; + u0 = Interleave.Unshuffle(x[0]); u1 = Interleave.Unshuffle(x[1]); + ulong e0 = (u0 & 0x00000000FFFFFFFFUL) | (u1 << 32); + ulong c0 = (u0 >> 32) | (u1 & 0xFFFFFFFF00000000UL); + + u0 = Interleave.Unshuffle(x[2]); u1 = Interleave.Unshuffle(x[3]); + ulong e1 = (u0 & 0x00000000FFFFFFFFUL) | (u1 << 32); + ulong c1 = (u0 >> 32) | (u1 & 0xFFFFFFFF00000000UL); + + ulong c2, c3; + c3 = (c1 >> 49); + c2 = (c0 >> 49) | (c1 << 15); + c1 ^= (c0 << 15); + + ulong[] tt = Nat256.CreateExt64(); + + int[] shifts = { 39, 120 }; + for (int i = 0; i < shifts.Length; ++i) + { + int w = shifts[i] >> 6, s = shifts[i] & 63; + Debug.Assert(s != 0); + tt[w ] ^= (c0 << s); + tt[w + 1] ^= (c1 << s) | (c0 >> -s); + tt[w + 2] ^= (c2 << s) | (c1 >> -s); + tt[w + 3] ^= (c3 << s) | (c2 >> -s); + tt[w + 4] ^= (c3 >> -s); + } + + Reduce(tt, z); + + z[0] ^= e0; + z[1] ^= e1; + } + + public static void Square(ulong[] x, ulong[] z) + { + ulong[] tt = Nat256.CreateExt64(); + ImplSquare(x, tt); + Reduce(tt, z); + } + + public static void SquareAddToExt(ulong[] x, ulong[] zz) + { + ulong[] tt = Nat256.CreateExt64(); + ImplSquare(x, tt); + AddExt(zz, tt, zz); + } + + public static void SquareN(ulong[] x, int n, ulong[] z) + { + Debug.Assert(n > 0); + + ulong[] tt = Nat256.CreateExt64(); + ImplSquare(x, tt); + Reduce(tt, z); + + while (--n > 0) + { + ImplSquare(z, tt); + Reduce(tt, z); + } + } + + public static uint Trace(ulong[] x) + { + // Non-zero-trace bits: 0, 81, 162 + return (uint)(x[0] ^ (x[1] >> 17) ^ (x[2] >> 34)) & 1U; + } + + protected static void ImplCompactExt(ulong[] zz) + { + ulong z0 = zz[0], z1 = zz[1], z2 = zz[2], z3 = zz[3], z4 = zz[4], z5 = zz[5], z6 = zz[6], z7 = zz[7]; + zz[0] = z0 ^ (z1 << 60); + zz[1] = (z1 >> 4) ^ (z2 << 56); + zz[2] = (z2 >> 8) ^ (z3 << 52); + zz[3] = (z3 >> 12) ^ (z4 << 48); + zz[4] = (z4 >> 16) ^ (z5 << 44); + zz[5] = (z5 >> 20) ^ (z6 << 40); + zz[6] = (z6 >> 24) ^ (z7 << 36); + zz[7] = (z7 >> 28); + } + + protected static void ImplExpand(ulong[] x, ulong[] z) + { + ulong x0 = x[0], x1 = x[1], x2 = x[2], x3 = x[3]; + z[0] = x0 & M60; + z[1] = ((x0 >> 60) ^ (x1 << 4)) & M60; + z[2] = ((x1 >> 56) ^ (x2 << 8)) & M60; + z[3] = ((x2 >> 52) ^ (x3 << 12)); + } + + protected static void ImplMultiply(ulong[] x, ulong[] y, ulong[] zz) + { + /* + * "Two-level seven-way recursion" as described in "Batch binary Edwards", Daniel J. Bernstein. + */ + + ulong[] f = new ulong[4], g = new ulong[4]; + ImplExpand(x, f); + ImplExpand(y, g); + + ImplMulwAcc(f[0], g[0], zz, 0); + ImplMulwAcc(f[1], g[1], zz, 1); + ImplMulwAcc(f[2], g[2], zz, 2); + ImplMulwAcc(f[3], g[3], zz, 3); + + // U *= (1 - t^n) + for (int i = 5; i > 0; --i) + { + zz[i] ^= zz[i - 1]; + } + + ImplMulwAcc(f[0] ^ f[1], g[0] ^ g[1], zz, 1); + ImplMulwAcc(f[2] ^ f[3], g[2] ^ g[3], zz, 3); + + // V *= (1 - t^2n) + for (int i = 7; i > 1; --i) + { + zz[i] ^= zz[i - 2]; + } + + // Double-length recursion + { + ulong c0 = f[0] ^ f[2], c1 = f[1] ^ f[3]; + ulong d0 = g[0] ^ g[2], d1 = g[1] ^ g[3]; + ImplMulwAcc(c0 ^ c1, d0 ^ d1, zz, 3); + ulong[] t = new ulong[3]; + ImplMulwAcc(c0, d0, t, 0); + ImplMulwAcc(c1, d1, t, 1); + ulong t0 = t[0], t1 = t[1], t2 = t[2]; + zz[2] ^= t0; + zz[3] ^= t0 ^ t1; + zz[4] ^= t2 ^ t1; + zz[5] ^= t2; + } + + ImplCompactExt(zz); + } + + protected static void ImplMulwAcc(ulong x, ulong y, ulong[] z, int zOff) + { + Debug.Assert(x >> 60 == 0); + Debug.Assert(y >> 60 == 0); + + ulong[] u = new ulong[8]; + //u[0] = 0; + u[1] = y; + u[2] = u[1] << 1; + u[3] = u[2] ^ y; + u[4] = u[2] << 1; + u[5] = u[4] ^ y; + u[6] = u[3] << 1; + u[7] = u[6] ^ y; + + uint j = (uint)x; + ulong g, h = 0, l = u[j & 7] + ^ (u[(j >> 3) & 7] << 3); + int k = 54; + do + { + j = (uint)(x >> k); + g = u[j & 7] + ^ u[(j >> 3) & 7] << 3; + l ^= (g << k); + h ^= (g >> -k); + } + while ((k -= 6) > 0); + + h ^= ((x & 0x0820820820820820L) & (ulong)(((long)y << 4) >> 63)) >> 5; + + Debug.Assert(h >> 55 == 0); + + z[zOff ] ^= l & M60; + z[zOff + 1] ^= (l >> 60) ^ (h << 4); + } + + protected static void ImplSquare(ulong[] x, ulong[] zz) + { + Interleave.Expand64To128(x[0], zz, 0); + Interleave.Expand64To128(x[1], zz, 2); + Interleave.Expand64To128(x[2], zz, 4); + + ulong x3 = x[3]; + zz[6] = Interleave.Expand32to64((uint)x3); + zz[7] = Interleave.Expand16to32((uint)(x3 >> 32)); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT239FieldElement.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT239FieldElement.cs new file mode 100644 index 0000000..a32ffc5 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT239FieldElement.cs @@ -0,0 +1,216 @@ +using System; + +using Org.BouncyCastle.Math.Raw; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT239FieldElement + : ECFieldElement + { + protected ulong[] x; + + public SecT239FieldElement(BigInteger x) + { + if (x == null || x.SignValue < 0 || x.BitLength > 239) + throw new ArgumentException("value invalid for SecT239FieldElement", "x"); + + this.x = SecT239Field.FromBigInteger(x); + } + + public SecT239FieldElement() + { + this.x = Nat256.Create64(); + } + + protected internal SecT239FieldElement(ulong[] x) + { + this.x = x; + } + + public override bool IsOne + { + get { return Nat256.IsOne64(x); } + } + + public override bool IsZero + { + get { return Nat256.IsZero64(x); } + } + + public override bool TestBitZero() + { + return (x[0] & 1L) != 0L; + } + + public override BigInteger ToBigInteger() + { + return Nat256.ToBigInteger64(x); + } + + public override string FieldName + { + get { return "SecT239Field"; } + } + + public override int FieldSize + { + get { return 239; } + } + + public override ECFieldElement Add(ECFieldElement b) + { + ulong[] z = Nat256.Create64(); + SecT239Field.Add(x, ((SecT239FieldElement)b).x, z); + return new SecT239FieldElement(z); + } + + public override ECFieldElement AddOne() + { + ulong[] z = Nat256.Create64(); + SecT239Field.AddOne(x, z); + return new SecT239FieldElement(z); + } + + public override ECFieldElement Subtract(ECFieldElement b) + { + // Addition and Subtraction are the same in F2m + return Add(b); + } + + public override ECFieldElement Multiply(ECFieldElement b) + { + ulong[] z = Nat256.Create64(); + SecT239Field.Multiply(x, ((SecT239FieldElement)b).x, z); + return new SecT239FieldElement(z); + } + + public override ECFieldElement MultiplyMinusProduct(ECFieldElement b, ECFieldElement x, ECFieldElement y) + { + return MultiplyPlusProduct(b, x, y); + } + + public override ECFieldElement MultiplyPlusProduct(ECFieldElement b, ECFieldElement x, ECFieldElement y) + { + ulong[] ax = this.x, bx = ((SecT239FieldElement)b).x; + ulong[] xx = ((SecT239FieldElement)x).x, yx = ((SecT239FieldElement)y).x; + + ulong[] tt = Nat256.CreateExt64(); + SecT239Field.MultiplyAddToExt(ax, bx, tt); + SecT239Field.MultiplyAddToExt(xx, yx, tt); + + ulong[] z = Nat256.Create64(); + SecT239Field.Reduce(tt, z); + return new SecT239FieldElement(z); + } + + public override ECFieldElement Divide(ECFieldElement b) + { + return Multiply(b.Invert()); + } + + public override ECFieldElement Negate() + { + return this; + } + + public override ECFieldElement Square() + { + ulong[] z = Nat256.Create64(); + SecT239Field.Square(x, z); + return new SecT239FieldElement(z); + } + + public override ECFieldElement SquareMinusProduct(ECFieldElement x, ECFieldElement y) + { + return SquarePlusProduct(x, y); + } + + public override ECFieldElement SquarePlusProduct(ECFieldElement x, ECFieldElement y) + { + ulong[] ax = this.x; + ulong[] xx = ((SecT239FieldElement)x).x, yx = ((SecT239FieldElement)y).x; + + ulong[] tt = Nat256.CreateExt64(); + SecT239Field.SquareAddToExt(ax, tt); + SecT239Field.MultiplyAddToExt(xx, yx, tt); + + ulong[] z = Nat256.Create64(); + SecT239Field.Reduce(tt, z); + return new SecT239FieldElement(z); + } + + public override ECFieldElement SquarePow(int pow) + { + if (pow < 1) + return this; + + ulong[] z = Nat256.Create64(); + SecT239Field.SquareN(x, pow, z); + return new SecT239FieldElement(z); + } + + public override ECFieldElement Invert() + { + ulong[] z = Nat256.Create64(); + SecT239Field.Invert(x, z); + return new SecT239FieldElement(z); + } + + public override ECFieldElement Sqrt() + { + ulong[] z = Nat256.Create64(); + SecT239Field.Sqrt(x, z); + return new SecT239FieldElement(z); + } + + public virtual int Representation + { + get { return F2mFieldElement.Tpb; } + } + + public virtual int M + { + get { return 239; } + } + + public virtual int K1 + { + get { return 158; } + } + + public virtual int K2 + { + get { return 0; } + } + + public virtual int K3 + { + get { return 0; } + } + + public override bool Equals(object obj) + { + return Equals(obj as SecT239FieldElement); + } + + public override bool Equals(ECFieldElement other) + { + return Equals(other as SecT239FieldElement); + } + + public virtual bool Equals(SecT239FieldElement other) + { + if (this == other) + return true; + if (null == other) + return false; + return Nat256.Eq64(x, other.x); + } + + public override int GetHashCode() + { + return 23900158 ^ Arrays.GetHashCode(x, 0, 4); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT239K1Curve.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT239K1Curve.cs new file mode 100644 index 0000000..a499d48 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT239K1Curve.cs @@ -0,0 +1,104 @@ +using System; + +using Org.BouncyCastle.Math.EC.Multiplier; +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT239K1Curve + : AbstractF2mCurve + { + private const int SecT239K1_DEFAULT_COORDS = COORD_LAMBDA_PROJECTIVE; + + protected readonly SecT239K1Point m_infinity; + + public SecT239K1Curve() + : base(239, 158, 0, 0) + { + this.m_infinity = new SecT239K1Point(this, null, null); + + this.m_a = FromBigInteger(BigInteger.Zero); + this.m_b = FromBigInteger(BigInteger.One); + this.m_order = new BigInteger(1, Hex.Decode("2000000000000000000000000000005A79FEC67CB6E91F1C1DA800E478A5")); + this.m_cofactor = BigInteger.ValueOf(4); + + this.m_coord = SecT239K1_DEFAULT_COORDS; + } + + protected override ECCurve CloneCurve() + { + return new SecT239K1Curve(); + } + + public override bool SupportsCoordinateSystem(int coord) + { + switch (coord) + { + case COORD_LAMBDA_PROJECTIVE: + return true; + default: + return false; + } + } + + protected override ECMultiplier CreateDefaultMultiplier() + { + return new WTauNafMultiplier(); + } + + public override ECPoint Infinity + { + get { return m_infinity; } + } + + public override int FieldSize + { + get { return 239; } + } + + public override ECFieldElement FromBigInteger(BigInteger x) + { + return new SecT239FieldElement(x); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, bool withCompression) + { + return new SecT239K1Point(this, x, y, withCompression); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + { + return new SecT239K1Point(this, x, y, zs, withCompression); + } + + public override bool IsKoblitz + { + get { return true; } + } + + public virtual int M + { + get { return 239; } + } + + public virtual bool IsTrinomial + { + get { return true; } + } + + public virtual int K1 + { + get { return 158; } + } + + public virtual int K2 + { + get { return 0; } + } + + public virtual int K3 + { + get { return 0; } + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT239K1Point.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT239K1Point.cs new file mode 100644 index 0000000..fbd5117 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT239K1Point.cs @@ -0,0 +1,290 @@ +using System; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT239K1Point + : AbstractF2mPoint + { + /** + * @deprecated Use ECCurve.createPoint to construct points + */ + public SecT239K1Point(ECCurve curve, ECFieldElement x, ECFieldElement y) + : this(curve, x, y, false) + { + } + + /** + * @deprecated per-point compression property will be removed, refer {@link #getEncoded(bool)} + */ + public SecT239K1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, bool withCompression) + : base(curve, x, y, withCompression) + { + if ((x == null) != (y == null)) + throw new ArgumentException("Exactly one of the field elements is null"); + } + + internal SecT239K1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + : base(curve, x, y, zs, withCompression) + { + } + + protected override ECPoint Detach() + { + return new SecT239K1Point(null, this.AffineXCoord, this.AffineYCoord); // earlier JDK + } + + public override ECFieldElement YCoord + { + get + { + ECFieldElement X = RawXCoord, L = RawYCoord; + + if (this.IsInfinity || X.IsZero) + return L; + + // Y is actually Lambda (X + Y/X) here; convert to affine value on the fly + ECFieldElement Y = L.Add(X).Multiply(X); + + ECFieldElement Z = RawZCoords[0]; + if (!Z.IsOne) + { + Y = Y.Divide(Z); + } + + return Y; + } + } + + protected internal override bool CompressionYTilde + { + get + { + ECFieldElement X = this.RawXCoord; + if (X.IsZero) + return false; + + ECFieldElement Y = this.RawYCoord; + + // Y is actually Lambda (X + Y/X) here + return Y.TestBitZero() != X.TestBitZero(); + } + } + + public override ECPoint Add(ECPoint b) + { + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return this; + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + ECFieldElement X2 = b.RawXCoord; + + if (X1.IsZero) + { + if (X2.IsZero) + return curve.Infinity; + + return b.Add(this); + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + ECFieldElement L2 = b.RawYCoord, Z2 = b.RawZCoords[0]; + + bool Z1IsOne = Z1.IsOne; + ECFieldElement U2 = X2, S2 = L2; + if (!Z1IsOne) + { + U2 = U2.Multiply(Z1); + S2 = S2.Multiply(Z1); + } + + bool Z2IsOne = Z2.IsOne; + ECFieldElement U1 = X1, S1 = L1; + if (!Z2IsOne) + { + U1 = U1.Multiply(Z2); + S1 = S1.Multiply(Z2); + } + + ECFieldElement A = S1.Add(S2); + ECFieldElement B = U1.Add(U2); + + if (B.IsZero) + { + if (A.IsZero) + return Twice(); + + return curve.Infinity; + } + + ECFieldElement X3, L3, Z3; + if (X2.IsZero) + { + // TODO This can probably be optimized quite a bit + ECPoint p = this.Normalize(); + X1 = p.XCoord; + ECFieldElement Y1 = p.YCoord; + + ECFieldElement Y2 = L2; + ECFieldElement L = Y1.Add(Y2).Divide(X1); + + X3 = L.Square().Add(L).Add(X1); + if (X3.IsZero) + { + return new SecT239K1Point(curve, X3, curve.B, IsCompressed); + } + + ECFieldElement Y3 = L.Multiply(X1.Add(X3)).Add(X3).Add(Y1); + L3 = Y3.Divide(X3).Add(X3); + Z3 = curve.FromBigInteger(BigInteger.One); + } + else + { + B = B.Square(); + + ECFieldElement AU1 = A.Multiply(U1); + ECFieldElement AU2 = A.Multiply(U2); + + X3 = AU1.Multiply(AU2); + if (X3.IsZero) + { + return new SecT239K1Point(curve, X3, curve.B, IsCompressed); + } + + ECFieldElement ABZ2 = A.Multiply(B); + if (!Z2IsOne) + { + ABZ2 = ABZ2.Multiply(Z2); + } + + L3 = AU2.Add(B).SquarePlusProduct(ABZ2, L1.Add(Z1)); + + Z3 = ABZ2; + if (!Z1IsOne) + { + Z3 = Z3.Multiply(Z1); + } + } + + return new SecT239K1Point(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint Twice() + { + if (this.IsInfinity) + return this; + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + if (X1.IsZero) + { + // A point with X == 0 is it's own Additive inverse + return curve.Infinity; + } + + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + + bool Z1IsOne = Z1.IsOne; + ECFieldElement Z1Sq = Z1IsOne ? Z1 : Z1.Square(); + ECFieldElement T; + if (Z1IsOne) + { + T = L1.Square().Add(L1); + } + else + { + T = L1.Add(Z1).Multiply(L1); + } + + if (T.IsZero) + { + return new SecT239K1Point(curve, T, curve.B, IsCompressed); + } + + ECFieldElement X3 = T.Square(); + ECFieldElement Z3 = Z1IsOne ? T : T.Multiply(Z1Sq); + + ECFieldElement t1 = L1.Add(X1).Square(); + ECFieldElement t2 = Z1IsOne ? Z1 : Z1Sq.Square(); + ECFieldElement L3 = t1.Add(T).Add(Z1Sq).Multiply(t1).Add(t2).Add(X3).Add(Z3); + + return new SecT239K1Point(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint TwicePlus(ECPoint b) + { + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return Twice(); + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + if (X1.IsZero) + { + // A point with X == 0 is it's own Additive inverse + return b; + } + + // NOTE: TwicePlus() only optimized for lambda-affine argument + ECFieldElement X2 = b.RawXCoord, Z2 = b.RawZCoords[0]; + if (X2.IsZero || !Z2.IsOne) + { + return Twice().Add(b); + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + ECFieldElement L2 = b.RawYCoord; + + ECFieldElement X1Sq = X1.Square(); + ECFieldElement L1Sq = L1.Square(); + ECFieldElement Z1Sq = Z1.Square(); + ECFieldElement L1Z1 = L1.Multiply(Z1); + + ECFieldElement T = L1Sq.Add(L1Z1); + ECFieldElement L2plus1 = L2.AddOne(); + ECFieldElement A = L2plus1.Multiply(Z1Sq).Add(L1Sq).MultiplyPlusProduct(T, X1Sq, Z1Sq); + ECFieldElement X2Z1Sq = X2.Multiply(Z1Sq); + ECFieldElement B = X2Z1Sq.Add(T).Square(); + + if (B.IsZero) + { + if (A.IsZero) + return b.Twice(); + + return curve.Infinity; + } + + if (A.IsZero) + { + return new SecT239K1Point(curve, A, curve.B, IsCompressed); + } + + ECFieldElement X3 = A.Square().Multiply(X2Z1Sq); + ECFieldElement Z3 = A.Multiply(B).Multiply(Z1Sq); + ECFieldElement L3 = A.Add(B).Square().MultiplyPlusProduct(T, L2plus1, Z3); + + return new SecT239K1Point(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint Negate() + { + if (this.IsInfinity) + return this; + + ECFieldElement X = this.RawXCoord; + if (X.IsZero) + return this; + + // L is actually Lambda (X + Y/X) here + ECFieldElement L = this.RawYCoord, Z = this.RawZCoords[0]; + return new SecT239K1Point(Curve, X, L.Add(Z), new ECFieldElement[] { Z }, IsCompressed); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT283Field.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT283Field.cs new file mode 100644 index 0000000..22b7eaa --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT283Field.cs @@ -0,0 +1,402 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Math.Raw; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT283Field + { + private const ulong M27 = ulong.MaxValue >> 37; + private const ulong M57 = ulong.MaxValue >> 7; + + private static readonly ulong[] ROOT_Z = new ulong[]{ 0x0C30C30C30C30808UL, 0x30C30C30C30C30C3UL, 0x820820820820830CUL, 0x0820820820820820UL, 0x2082082UL }; + + public static void Add(ulong[] x, ulong[] y, ulong[] z) + { + z[0] = x[0] ^ y[0]; + z[1] = x[1] ^ y[1]; + z[2] = x[2] ^ y[2]; + z[3] = x[3] ^ y[3]; + z[4] = x[4] ^ y[4]; + } + + public static void AddExt(ulong[] xx, ulong[] yy, ulong[] zz) + { + zz[0] = xx[0] ^ yy[0]; + zz[1] = xx[1] ^ yy[1]; + zz[2] = xx[2] ^ yy[2]; + zz[3] = xx[3] ^ yy[3]; + zz[4] = xx[4] ^ yy[4]; + zz[5] = xx[5] ^ yy[5]; + zz[6] = xx[6] ^ yy[6]; + zz[7] = xx[7] ^ yy[7]; + zz[8] = xx[8] ^ yy[8]; + } + + public static void AddOne(ulong[] x, ulong[] z) + { + z[0] = x[0] ^ 1UL; + z[1] = x[1]; + z[2] = x[2]; + z[3] = x[3]; + z[4] = x[4]; + } + + public static ulong[] FromBigInteger(BigInteger x) + { + ulong[] z = Nat320.FromBigInteger64(x); + Reduce37(z, 0); + return z; + } + + public static void Invert(ulong[] x, ulong[] z) + { + if (Nat320.IsZero64(x)) + throw new InvalidOperationException(); + + // Itoh-Tsujii inversion + + ulong[] t0 = Nat320.Create64(); + ulong[] t1 = Nat320.Create64(); + + Square(x, t0); + Multiply(t0, x, t0); + SquareN(t0, 2, t1); + Multiply(t1, t0, t1); + SquareN(t1, 4, t0); + Multiply(t0, t1, t0); + SquareN(t0, 8, t1); + Multiply(t1, t0, t1); + Square(t1, t1); + Multiply(t1, x, t1); + SquareN(t1, 17, t0); + Multiply(t0, t1, t0); + Square(t0, t0); + Multiply(t0, x, t0); + SquareN(t0, 35, t1); + Multiply(t1, t0, t1); + SquareN(t1, 70, t0); + Multiply(t0, t1, t0); + Square(t0, t0); + Multiply(t0, x, t0); + SquareN(t0, 141, t1); + Multiply(t1, t0, t1); + Square(t1, z); + } + + public static void Multiply(ulong[] x, ulong[] y, ulong[] z) + { + ulong[] tt = Nat320.CreateExt64(); + ImplMultiply(x, y, tt); + Reduce(tt, z); + } + + public static void MultiplyAddToExt(ulong[] x, ulong[] y, ulong[] zz) + { + ulong[] tt = Nat320.CreateExt64(); + ImplMultiply(x, y, tt); + AddExt(zz, tt, zz); + } + + public static void Reduce(ulong[] xx, ulong[] z) + { + ulong x0 = xx[0], x1 = xx[1], x2 = xx[2], x3 = xx[3], x4 = xx[4]; + ulong x5 = xx[5], x6 = xx[6], x7 = xx[7], x8 = xx[8]; + + x3 ^= (x8 << 37) ^ (x8 << 42) ^ (x8 << 44) ^ (x8 << 49); + x4 ^= (x8 >> 27) ^ (x8 >> 22) ^ (x8 >> 20) ^ (x8 >> 15); + + x2 ^= (x7 << 37) ^ (x7 << 42) ^ (x7 << 44) ^ (x7 << 49); + x3 ^= (x7 >> 27) ^ (x7 >> 22) ^ (x7 >> 20) ^ (x7 >> 15); + + x1 ^= (x6 << 37) ^ (x6 << 42) ^ (x6 << 44) ^ (x6 << 49); + x2 ^= (x6 >> 27) ^ (x6 >> 22) ^ (x6 >> 20) ^ (x6 >> 15); + + x0 ^= (x5 << 37) ^ (x5 << 42) ^ (x5 << 44) ^ (x5 << 49); + x1 ^= (x5 >> 27) ^ (x5 >> 22) ^ (x5 >> 20) ^ (x5 >> 15); + + ulong t = x4 >> 27; + z[0] = x0 ^ t ^ (t << 5) ^ (t << 7) ^ (t << 12); + z[1] = x1; + z[2] = x2; + z[3] = x3; + z[4] = x4 & M27; + } + + public static void Reduce37(ulong[] z, int zOff) + { + ulong z4 = z[zOff + 4], t = z4 >> 27; + z[zOff ] ^= t ^ (t << 5) ^ (t << 7) ^ (t << 12); + z[zOff + 4] = z4 & M27; + } + + public static void Sqrt(ulong[] x, ulong[] z) + { + ulong[] odd = Nat320.Create64(); + + ulong u0, u1; + u0 = Interleave.Unshuffle(x[0]); u1 = Interleave.Unshuffle(x[1]); + ulong e0 = (u0 & 0x00000000FFFFFFFFUL) | (u1 << 32); + odd[0] = (u0 >> 32) | (u1 & 0xFFFFFFFF00000000UL); + + u0 = Interleave.Unshuffle(x[2]); u1 = Interleave.Unshuffle(x[3]); + ulong e1 = (u0 & 0x00000000FFFFFFFFUL) | (u1 << 32); + odd[1] = (u0 >> 32) | (u1 & 0xFFFFFFFF00000000UL); + + u0 = Interleave.Unshuffle(x[4]); + ulong e2 = (u0 & 0x00000000FFFFFFFFUL); + odd[2] = (u0 >> 32); + + Multiply(odd, ROOT_Z, z); + + z[0] ^= e0; + z[1] ^= e1; + z[2] ^= e2; + } + + public static void Square(ulong[] x, ulong[] z) + { + ulong[] tt = Nat.Create64(9); + ImplSquare(x, tt); + Reduce(tt, z); + } + + public static void SquareAddToExt(ulong[] x, ulong[] zz) + { + ulong[] tt = Nat.Create64(9); + ImplSquare(x, tt); + AddExt(zz, tt, zz); + } + + public static void SquareN(ulong[] x, int n, ulong[] z) + { + Debug.Assert(n > 0); + + ulong[] tt = Nat.Create64(9); + ImplSquare(x, tt); + Reduce(tt, z); + + while (--n > 0) + { + ImplSquare(z, tt); + Reduce(tt, z); + } + } + + public static uint Trace(ulong[] x) + { + // Non-zero-trace bits: 0, 271 + return (uint)(x[0] ^ (x[4] >> 15)) & 1U; + } + + protected static void ImplCompactExt(ulong[] zz) + { + ulong z0 = zz[0], z1 = zz[1], z2 = zz[2], z3 = zz[3], z4 = zz[4]; + ulong z5 = zz[5], z6 = zz[6], z7 = zz[7], z8 = zz[8], z9 = zz[9]; + zz[0] = z0 ^ (z1 << 57); + zz[1] = (z1 >> 7) ^ (z2 << 50); + zz[2] = (z2 >> 14) ^ (z3 << 43); + zz[3] = (z3 >> 21) ^ (z4 << 36); + zz[4] = (z4 >> 28) ^ (z5 << 29); + zz[5] = (z5 >> 35) ^ (z6 << 22); + zz[6] = (z6 >> 42) ^ (z7 << 15); + zz[7] = (z7 >> 49) ^ (z8 << 8); + zz[8] = (z8 >> 56) ^ (z9 << 1); + zz[9] = (z9 >> 63); // Zero! + } + + protected static void ImplExpand(ulong[] x, ulong[] z) + { + ulong x0 = x[0], x1 = x[1], x2 = x[2], x3 = x[3], x4 = x[4]; + z[0] = x0 & M57; + z[1] = ((x0 >> 57) ^ (x1 << 7)) & M57; + z[2] = ((x1 >> 50) ^ (x2 << 14)) & M57; + z[3] = ((x2 >> 43) ^ (x3 << 21)) & M57; + z[4] = ((x3 >> 36) ^ (x4 << 28)); + } + + //protected static void AddMs(ulong[] zz, int zOff, ulong[] p, params int[] ms) + //{ + // ulong t0 = 0, t1 = 0; + // foreach (int m in ms) + // { + // int i = (m - 1) << 1; + // t0 ^= p[i ]; + // t1 ^= p[i + 1]; + // } + // zz[zOff ] ^= t0; + // zz[zOff + 1] ^= t1; + //} + + protected static void ImplMultiply(ulong[] x, ulong[] y, ulong[] zz) + { + /* + * Formula (17) from "Some New Results on Binary Polynomial Multiplication", + * Murat Cenk and M. Anwar Hasan. + * + * The formula as given contained an error in the term t25, as noted below + */ + ulong[] a = new ulong[5], b = new ulong[5]; + ImplExpand(x, a); + ImplExpand(y, b); + + ulong[] p = new ulong[26]; + + ImplMulw(a[0], b[0], p, 0); // m1 + ImplMulw(a[1], b[1], p, 2); // m2 + ImplMulw(a[2], b[2], p, 4); // m3 + ImplMulw(a[3], b[3], p, 6); // m4 + ImplMulw(a[4], b[4], p, 8); // m5 + + ulong u0 = a[0] ^ a[1], v0 = b[0] ^ b[1]; + ulong u1 = a[0] ^ a[2], v1 = b[0] ^ b[2]; + ulong u2 = a[2] ^ a[4], v2 = b[2] ^ b[4]; + ulong u3 = a[3] ^ a[4], v3 = b[3] ^ b[4]; + + ImplMulw(u1 ^ a[3], v1 ^ b[3], p, 18); // m10 + ImplMulw(u2 ^ a[1], v2 ^ b[1], p, 20); // m11 + + ulong A4 = u0 ^ u3 , B4 = v0 ^ v3; + ulong A5 = A4 ^ a[2], B5 = B4 ^ b[2]; + + ImplMulw(A4, B4, p, 22); // m12 + ImplMulw(A5, B5, p, 24); // m13 + + ImplMulw(u0, v0, p, 10); // m6 + ImplMulw(u1, v1, p, 12); // m7 + ImplMulw(u2, v2, p, 14); // m8 + ImplMulw(u3, v3, p, 16); // m9 + + + // Original method, corresponding to formula (16) + //AddMs(zz, 0, p, 1); + //AddMs(zz, 1, p, 1, 2, 6); + //AddMs(zz, 2, p, 1, 2, 3, 7); + //AddMs(zz, 3, p, 1, 3, 4, 5, 8, 10, 12, 13); + //AddMs(zz, 4, p, 1, 2, 4, 5, 6, 9, 10, 11, 13); + //AddMs(zz, 5, p, 1, 2, 3, 5, 7, 11, 12, 13); + //AddMs(zz, 6, p, 3, 4, 5, 8); + //AddMs(zz, 7, p, 4, 5, 9); + //AddMs(zz, 8, p, 5); + + // Improved method factors out common single-word terms + // NOTE: p1,...,p26 in the paper maps to p[0],...,p[25] here + + zz[0] = p[ 0]; + zz[9] = p[ 9]; + + ulong t1 = p[ 0] ^ p[ 1]; + ulong t2 = t1 ^ p[ 2]; + ulong t3 = t2 ^ p[10]; + + zz[1] = t3; + + ulong t4 = p[ 3] ^ p[ 4]; + ulong t5 = p[11] ^ p[12]; + ulong t6 = t4 ^ t5; + ulong t7 = t2 ^ t6; + + zz[2] = t7; + + ulong t8 = t1 ^ t4; + ulong t9 = p[ 5] ^ p[ 6]; + ulong t10 = t8 ^ t9; + ulong t11 = t10 ^ p[ 8]; + ulong t12 = p[13] ^ p[14]; + ulong t13 = t11 ^ t12; + ulong t14 = p[18] ^ p[22]; + ulong t15 = t14 ^ p[24]; + ulong t16 = t13 ^ t15; + + zz[3] = t16; + + ulong t17 = p[ 7] ^ p[ 8]; + ulong t18 = t17 ^ p[ 9]; + ulong t19 = t18 ^ p[17]; + + zz[8] = t19; + + ulong t20 = t18 ^ t9; + ulong t21 = p[15] ^ p[16]; + ulong t22 = t20 ^ t21; + + zz[7] = t22; + + ulong t23 = t22 ^ t3; + ulong t24 = p[19] ^ p[20]; + // ulong t25 = p[23] ^ p[24]; + ulong t25 = p[25] ^ p[24]; // Fixes an error in the paper: p[23] -> p{25] + ulong t26 = p[18] ^ p[23]; + ulong t27 = t24 ^ t25; + ulong t28 = t27 ^ t26; + ulong t29 = t28 ^ t23; + + zz[4] = t29; + + ulong t30 = t7 ^ t19; + ulong t31 = t27 ^ t30; + ulong t32 = p[21] ^ p[22]; + ulong t33 = t31 ^ t32; + + zz[5] = t33; + + ulong t34 = t11 ^ p[0]; + ulong t35 = t34 ^ p[9]; + ulong t36 = t35 ^ t12; + ulong t37 = t36 ^ p[21]; + ulong t38 = t37 ^ p[23]; + ulong t39 = t38 ^ p[25]; + + zz[6] = t39; + + ImplCompactExt(zz); + } + + protected static void ImplMulw(ulong x, ulong y, ulong[] z, int zOff) + { + Debug.Assert(x >> 57 == 0); + Debug.Assert(y >> 57 == 0); + + ulong[] u = new ulong[8]; + //u[0] = 0; + u[1] = y; + u[2] = u[1] << 1; + u[3] = u[2] ^ y; + u[4] = u[2] << 1; + u[5] = u[4] ^ y; + u[6] = u[3] << 1; + u[7] = u[6] ^ y; + + uint j = (uint)x; + ulong g, h = 0, l = u[j & 7]; + int k = 48; + do + { + j = (uint)(x >> k); + g = u[j & 7] + ^ u[(j >> 3) & 7] << 3 + ^ u[(j >> 6) & 7] << 6; + l ^= (g << k); + h ^= (g >> -k); + } + while ((k -= 9) > 0); + + h ^= ((x & 0x0100804020100800L) & (ulong)(((long)y << 7) >> 63)) >> 8; + + Debug.Assert(h >> 49 == 0); + + z[zOff ] = l & M57; + z[zOff + 1] = (l >> 57) ^ (h << 7); + } + + protected static void ImplSquare(ulong[] x, ulong[] zz) + { + for (int i = 0; i < 4; ++i) + { + Interleave.Expand64To128(x[i], zz, i << 1); + } + zz[8] = Interleave.Expand32to64((uint)x[4]); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT283FieldElement.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT283FieldElement.cs new file mode 100644 index 0000000..adfd4e0 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT283FieldElement.cs @@ -0,0 +1,216 @@ +using System; + +using Org.BouncyCastle.Math.Raw; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT283FieldElement + : ECFieldElement + { + protected readonly ulong[] x; + + public SecT283FieldElement(BigInteger x) + { + if (x == null || x.SignValue < 0 || x.BitLength > 283) + throw new ArgumentException("value invalid for SecT283FieldElement", "x"); + + this.x = SecT283Field.FromBigInteger(x); + } + + public SecT283FieldElement() + { + this.x = Nat320.Create64(); + } + + protected internal SecT283FieldElement(ulong[] x) + { + this.x = x; + } + + public override bool IsOne + { + get { return Nat320.IsOne64(x); } + } + + public override bool IsZero + { + get { return Nat320.IsZero64(x); } + } + + public override bool TestBitZero() + { + return (x[0] & 1UL) != 0UL; + } + + public override BigInteger ToBigInteger() + { + return Nat320.ToBigInteger64(x); + } + + public override string FieldName + { + get { return "SecT283Field"; } + } + + public override int FieldSize + { + get { return 283; } + } + + public override ECFieldElement Add(ECFieldElement b) + { + ulong[] z = Nat320.Create64(); + SecT283Field.Add(x, ((SecT283FieldElement)b).x, z); + return new SecT283FieldElement(z); + } + + public override ECFieldElement AddOne() + { + ulong[] z = Nat320.Create64(); + SecT283Field.AddOne(x, z); + return new SecT283FieldElement(z); + } + + public override ECFieldElement Subtract(ECFieldElement b) + { + // Addition and subtraction are the same in F2m + return Add(b); + } + + public override ECFieldElement Multiply(ECFieldElement b) + { + ulong[] z = Nat320.Create64(); + SecT283Field.Multiply(x, ((SecT283FieldElement)b).x, z); + return new SecT283FieldElement(z); + } + + public override ECFieldElement MultiplyMinusProduct(ECFieldElement b, ECFieldElement x, ECFieldElement y) + { + return MultiplyPlusProduct(b, x, y); + } + + public override ECFieldElement MultiplyPlusProduct(ECFieldElement b, ECFieldElement x, ECFieldElement y) + { + ulong[] ax = this.x, bx = ((SecT283FieldElement)b).x; + ulong[] xx = ((SecT283FieldElement)x).x, yx = ((SecT283FieldElement)y).x; + + ulong[] tt = Nat.Create64(9); + SecT283Field.MultiplyAddToExt(ax, bx, tt); + SecT283Field.MultiplyAddToExt(xx, yx, tt); + + ulong[] z = Nat320.Create64(); + SecT283Field.Reduce(tt, z); + return new SecT283FieldElement(z); + } + + public override ECFieldElement Divide(ECFieldElement b) + { + return Multiply(b.Invert()); + } + + public override ECFieldElement Negate() + { + return this; + } + + public override ECFieldElement Square() + { + ulong[] z = Nat320.Create64(); + SecT283Field.Square(x, z); + return new SecT283FieldElement(z); + } + + public override ECFieldElement SquareMinusProduct(ECFieldElement x, ECFieldElement y) + { + return SquarePlusProduct(x, y); + } + + public override ECFieldElement SquarePlusProduct(ECFieldElement x, ECFieldElement y) + { + ulong[] ax = this.x; + ulong[] xx = ((SecT283FieldElement)x).x, yx = ((SecT283FieldElement)y).x; + + ulong[] tt = Nat.Create64(9); + SecT283Field.SquareAddToExt(ax, tt); + SecT283Field.MultiplyAddToExt(xx, yx, tt); + + ulong[] z = Nat320.Create64(); + SecT283Field.Reduce(tt, z); + return new SecT283FieldElement(z); + } + + public override ECFieldElement SquarePow(int pow) + { + if (pow < 1) + return this; + + ulong[] z = Nat320.Create64(); + SecT283Field.SquareN(x, pow, z); + return new SecT283FieldElement(z); + } + + public override ECFieldElement Invert() + { + ulong[] z = Nat320.Create64(); + SecT283Field.Invert(x, z); + return new SecT283FieldElement(z); + } + + public override ECFieldElement Sqrt() + { + ulong[] z = Nat320.Create64(); + SecT283Field.Sqrt(x, z); + return new SecT283FieldElement(z); + } + + public virtual int Representation + { + get { return F2mFieldElement.Ppb; } + } + + public virtual int M + { + get { return 283; } + } + + public virtual int K1 + { + get { return 5; } + } + + public virtual int K2 + { + get { return 7; } + } + + public virtual int K3 + { + get { return 12; } + } + + public override bool Equals(object obj) + { + return Equals(obj as SecT283FieldElement); + } + + public override bool Equals(ECFieldElement other) + { + return Equals(other as SecT283FieldElement); + } + + public virtual bool Equals(SecT283FieldElement other) + { + if (this == other) + return true; + if (null == other) + return false; + return Nat320.Eq64(x, other.x); + } + + public override int GetHashCode() + { + return 2831275 ^ Arrays.GetHashCode(x, 0, 5); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT283K1Curve.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT283K1Curve.cs new file mode 100644 index 0000000..4053287 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT283K1Curve.cs @@ -0,0 +1,104 @@ +using System; + +using Org.BouncyCastle.Math.EC.Multiplier; +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT283K1Curve + : AbstractF2mCurve + { + private const int SecT283K1_DEFAULT_COORDS = COORD_LAMBDA_PROJECTIVE; + + protected readonly SecT283K1Point m_infinity; + + public SecT283K1Curve() + : base(283, 5, 7, 12) + { + this.m_infinity = new SecT283K1Point(this, null, null); + + this.m_a = FromBigInteger(BigInteger.Zero); + this.m_b = FromBigInteger(BigInteger.One); + this.m_order = new BigInteger(1, Hex.Decode("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE9AE2ED07577265DFF7F94451E061E163C61")); + this.m_cofactor = BigInteger.ValueOf(4); + + this.m_coord = SecT283K1_DEFAULT_COORDS; + } + + protected override ECCurve CloneCurve() + { + return new SecT283K1Curve(); + } + + public override bool SupportsCoordinateSystem(int coord) + { + switch (coord) + { + case COORD_LAMBDA_PROJECTIVE: + return true; + default: + return false; + } + } + + protected override ECMultiplier CreateDefaultMultiplier() + { + return new WTauNafMultiplier(); + } + + public override ECPoint Infinity + { + get { return m_infinity; } + } + + public override int FieldSize + { + get { return 283; } + } + + public override ECFieldElement FromBigInteger(BigInteger x) + { + return new SecT283FieldElement(x); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, bool withCompression) + { + return new SecT283K1Point(this, x, y, withCompression); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + { + return new SecT283K1Point(this, x, y, zs, withCompression); + } + + public override bool IsKoblitz + { + get { return true; } + } + + public virtual int M + { + get { return 283; } + } + + public virtual bool IsTrinomial + { + get { return false; } + } + + public virtual int K1 + { + get { return 5; } + } + + public virtual int K2 + { + get { return 7; } + } + + public virtual int K3 + { + get { return 12; } + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT283K1Point.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT283K1Point.cs new file mode 100644 index 0000000..9856894 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT283K1Point.cs @@ -0,0 +1,289 @@ +using System; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT283K1Point + : AbstractF2mPoint + { + /** + * @deprecated Use ECCurve.createPoint to construct points + */ + public SecT283K1Point(ECCurve curve, ECFieldElement x, ECFieldElement y) + : this(curve, x, y, false) + { + } + + /** + * @deprecated per-point compression property will be removed, refer {@link #getEncoded(bool)} + */ + public SecT283K1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, bool withCompression) + : base(curve, x, y, withCompression) + { + if ((x == null) != (y == null)) + throw new ArgumentException("Exactly one of the field elements is null"); + } + + internal SecT283K1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + : base(curve, x, y, zs, withCompression) + { + } + + protected override ECPoint Detach() + { + return new SecT283K1Point(null, this.AffineXCoord, this.AffineYCoord); // earlier JDK + } + + public override ECFieldElement YCoord + { + get + { + ECFieldElement X = RawXCoord, L = RawYCoord; + + if (this.IsInfinity || X.IsZero) + return L; + + // Y is actually Lambda (X + Y/X) here; convert to affine value on the fly + ECFieldElement Y = L.Add(X).Multiply(X); + + ECFieldElement Z = RawZCoords[0]; + if (!Z.IsOne) + { + Y = Y.Divide(Z); + } + + return Y; + } + } + + protected internal override bool CompressionYTilde + { + get + { + ECFieldElement X = this.RawXCoord; + if (X.IsZero) + return false; + + ECFieldElement Y = this.RawYCoord; + + // Y is actually Lambda (X + Y/X) here + return Y.TestBitZero() != X.TestBitZero(); + } + } + + public override ECPoint Add(ECPoint b) + { + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return this; + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + ECFieldElement X2 = b.RawXCoord; + + if (X1.IsZero) + { + if (X2.IsZero) + return curve.Infinity; + + return b.Add(this); + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + ECFieldElement L2 = b.RawYCoord, Z2 = b.RawZCoords[0]; + + bool Z1IsOne = Z1.IsOne; + ECFieldElement U2 = X2, S2 = L2; + if (!Z1IsOne) + { + U2 = U2.Multiply(Z1); + S2 = S2.Multiply(Z1); + } + + bool Z2IsOne = Z2.IsOne; + ECFieldElement U1 = X1, S1 = L1; + if (!Z2IsOne) + { + U1 = U1.Multiply(Z2); + S1 = S1.Multiply(Z2); + } + + ECFieldElement A = S1.Add(S2); + ECFieldElement B = U1.Add(U2); + + if (B.IsZero) + { + if (A.IsZero) + return Twice(); + + return curve.Infinity; + } + + ECFieldElement X3, L3, Z3; + if (X2.IsZero) + { + // TODO This can probably be optimized quite a bit + ECPoint p = this.Normalize(); + X1 = p.XCoord; + ECFieldElement Y1 = p.YCoord; + + ECFieldElement Y2 = L2; + ECFieldElement L = Y1.Add(Y2).Divide(X1); + + X3 = L.Square().Add(L).Add(X1); + if (X3.IsZero) + { + return new SecT283K1Point(curve, X3, curve.B, IsCompressed); + } + + ECFieldElement Y3 = L.Multiply(X1.Add(X3)).Add(X3).Add(Y1); + L3 = Y3.Divide(X3).Add(X3); + Z3 = curve.FromBigInteger(BigInteger.One); + } + else + { + B = B.Square(); + + ECFieldElement AU1 = A.Multiply(U1); + ECFieldElement AU2 = A.Multiply(U2); + + X3 = AU1.Multiply(AU2); + if (X3.IsZero) + { + return new SecT283K1Point(curve, X3, curve.B, IsCompressed); + } + + ECFieldElement ABZ2 = A.Multiply(B); + if (!Z2IsOne) + { + ABZ2 = ABZ2.Multiply(Z2); + } + + L3 = AU2.Add(B).SquarePlusProduct(ABZ2, L1.Add(Z1)); + + Z3 = ABZ2; + if (!Z1IsOne) + { + Z3 = Z3.Multiply(Z1); + } + } + + return new SecT283K1Point(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint Twice() + { + if (this.IsInfinity) + return this; + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + if (X1.IsZero) + { + // A point with X == 0 is it's own Additive inverse + return curve.Infinity; + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + + bool Z1IsOne = Z1.IsOne; + ECFieldElement Z1Sq = Z1IsOne ? Z1 : Z1.Square(); + ECFieldElement T; + if (Z1IsOne) + { + T = L1.Square().Add(L1); + } + else + { + T = L1.Add(Z1).Multiply(L1); + } + + if (T.IsZero) + { + return new SecT283K1Point(curve, T, curve.B, IsCompressed); + } + + ECFieldElement X3 = T.Square(); + ECFieldElement Z3 = Z1IsOne ? T : T.Multiply(Z1Sq); + + ECFieldElement t1 = L1.Add(X1).Square(); + ECFieldElement t2 = Z1IsOne ? Z1 : Z1Sq.Square(); + ECFieldElement L3 = t1.Add(T).Add(Z1Sq).Multiply(t1).Add(t2).Add(X3).Add(Z3); + + return new SecT283K1Point(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint TwicePlus(ECPoint b) + { + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return Twice(); + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + if (X1.IsZero) + { + // A point with X == 0 is it's own Additive inverse + return b; + } + + // NOTE: TwicePlus() only optimized for lambda-affine argument + ECFieldElement X2 = b.RawXCoord, Z2 = b.RawZCoords[0]; + if (X2.IsZero || !Z2.IsOne) + { + return Twice().Add(b); + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + ECFieldElement L2 = b.RawYCoord; + + ECFieldElement X1Sq = X1.Square(); + ECFieldElement L1Sq = L1.Square(); + ECFieldElement Z1Sq = Z1.Square(); + ECFieldElement L1Z1 = L1.Multiply(Z1); + + ECFieldElement T = L1Sq.Add(L1Z1); + ECFieldElement L2plus1 = L2.AddOne(); + ECFieldElement A = L2plus1.Multiply(Z1Sq).Add(L1Sq).MultiplyPlusProduct(T, X1Sq, Z1Sq); + ECFieldElement X2Z1Sq = X2.Multiply(Z1Sq); + ECFieldElement B = X2Z1Sq.Add(T).Square(); + + if (B.IsZero) + { + if (A.IsZero) + return b.Twice(); + + return curve.Infinity; + } + + if (A.IsZero) + { + return new SecT283K1Point(curve, A, curve.B, IsCompressed); + } + + ECFieldElement X3 = A.Square().Multiply(X2Z1Sq); + ECFieldElement Z3 = A.Multiply(B).Multiply(Z1Sq); + ECFieldElement L3 = A.Add(B).Square().MultiplyPlusProduct(T, L2plus1, Z3); + + return new SecT283K1Point(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint Negate() + { + if (this.IsInfinity) + return this; + + ECFieldElement X = this.RawXCoord; + if (X.IsZero) + return this; + + // L is actually Lambda (X + Y/X) here + ECFieldElement L = this.RawYCoord, Z = this.RawZCoords[0]; + return new SecT283K1Point(Curve, X, L.Add(Z), new ECFieldElement[] { Z }, IsCompressed); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT283R1Curve.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT283R1Curve.cs new file mode 100644 index 0000000..e659675 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT283R1Curve.cs @@ -0,0 +1,98 @@ +using System; + +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT283R1Curve + : AbstractF2mCurve + { + private const int SecT283R1_DEFAULT_COORDS = COORD_LAMBDA_PROJECTIVE; + + protected readonly SecT283R1Point m_infinity; + + public SecT283R1Curve() + : base(283, 5, 7, 12) + { + this.m_infinity = new SecT283R1Point(this, null, null); + + this.m_a = FromBigInteger(BigInteger.One); + this.m_b = FromBigInteger(new BigInteger(1, Hex.Decode("027B680AC8B8596DA5A4AF8A19A0303FCA97FD7645309FA2A581485AF6263E313B79A2F5"))); + this.m_order = new BigInteger(1, Hex.Decode("03FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEF90399660FC938A90165B042A7CEFADB307")); + this.m_cofactor = BigInteger.Two; + + this.m_coord = SecT283R1_DEFAULT_COORDS; + } + + protected override ECCurve CloneCurve() + { + return new SecT283R1Curve(); + } + + public override bool SupportsCoordinateSystem(int coord) + { + switch (coord) + { + case COORD_LAMBDA_PROJECTIVE: + return true; + default: + return false; + } + } + + public override ECPoint Infinity + { + get { return m_infinity; } + } + + public override int FieldSize + { + get { return 283; } + } + + public override ECFieldElement FromBigInteger(BigInteger x) + { + return new SecT283FieldElement(x); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, bool withCompression) + { + return new SecT283R1Point(this, x, y, withCompression); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + { + return new SecT283R1Point(this, x, y, zs, withCompression); + } + + public override bool IsKoblitz + { + get { return false; } + } + + public virtual int M + { + get { return 283; } + } + + public virtual bool IsTrinomial + { + get { return false; } + } + + public virtual int K1 + { + get { return 5; } + } + + public virtual int K2 + { + get { return 7; } + } + + public virtual int K3 + { + get { return 12; } + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT283R1Point.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT283R1Point.cs new file mode 100644 index 0000000..4c1a780 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT283R1Point.cs @@ -0,0 +1,278 @@ +using System; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT283R1Point + : AbstractF2mPoint + { + /** + * @deprecated Use ECCurve.createPoint to construct points + */ + public SecT283R1Point(ECCurve curve, ECFieldElement x, ECFieldElement y) + : this(curve, x, y, false) + { + } + + /** + * @deprecated per-point compression property will be removed, refer {@link #getEncoded(bool)} + */ + public SecT283R1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, bool withCompression) + : base(curve, x, y, withCompression) + { + if ((x == null) != (y == null)) + throw new ArgumentException("Exactly one of the field elements is null"); + } + + internal SecT283R1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + : base(curve, x, y, zs, withCompression) + { + } + + protected override ECPoint Detach() + { + return new SecT283R1Point(null, AffineXCoord, AffineYCoord); + } + + public override ECFieldElement YCoord + { + get + { + ECFieldElement X = RawXCoord, L = RawYCoord; + + if (this.IsInfinity || X.IsZero) + return L; + + // Y is actually Lambda (X + Y/X) here; convert to affine value on the fly + ECFieldElement Y = L.Add(X).Multiply(X); + + ECFieldElement Z = RawZCoords[0]; + if (!Z.IsOne) + { + Y = Y.Divide(Z); + } + + return Y; + } + } + + protected internal override bool CompressionYTilde + { + get + { + ECFieldElement X = this.RawXCoord; + if (X.IsZero) + return false; + + ECFieldElement Y = this.RawYCoord; + + // Y is actually Lambda (X + Y/X) here + return Y.TestBitZero() != X.TestBitZero(); + } + } + + public override ECPoint Add(ECPoint b) + { + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return this; + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + ECFieldElement X2 = b.RawXCoord; + + if (X1.IsZero) + { + if (X2.IsZero) + return curve.Infinity; + + return b.Add(this); + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + ECFieldElement L2 = b.RawYCoord, Z2 = b.RawZCoords[0]; + + bool Z1IsOne = Z1.IsOne; + ECFieldElement U2 = X2, S2 = L2; + if (!Z1IsOne) + { + U2 = U2.Multiply(Z1); + S2 = S2.Multiply(Z1); + } + + bool Z2IsOne = Z2.IsOne; + ECFieldElement U1 = X1, S1 = L1; + if (!Z2IsOne) + { + U1 = U1.Multiply(Z2); + S1 = S1.Multiply(Z2); + } + + ECFieldElement A = S1.Add(S2); + ECFieldElement B = U1.Add(U2); + + if (B.IsZero) + { + if (A.IsZero) + return Twice(); + + return curve.Infinity; + } + + ECFieldElement X3, L3, Z3; + if (X2.IsZero) + { + // TODO This can probably be optimized quite a bit + ECPoint p = this.Normalize(); + X1 = p.XCoord; + ECFieldElement Y1 = p.YCoord; + + ECFieldElement Y2 = L2; + ECFieldElement L = Y1.Add(Y2).Divide(X1); + + X3 = L.Square().Add(L).Add(X1).AddOne(); + if (X3.IsZero) + { + return new SecT283R1Point(curve, X3, curve.B.Sqrt(), IsCompressed); + } + + ECFieldElement Y3 = L.Multiply(X1.Add(X3)).Add(X3).Add(Y1); + L3 = Y3.Divide(X3).Add(X3); + Z3 = curve.FromBigInteger(BigInteger.One); + } + else + { + B = B.Square(); + + ECFieldElement AU1 = A.Multiply(U1); + ECFieldElement AU2 = A.Multiply(U2); + + X3 = AU1.Multiply(AU2); + if (X3.IsZero) + { + return new SecT283R1Point(curve, X3, curve.B.Sqrt(), IsCompressed); + } + + ECFieldElement ABZ2 = A.Multiply(B); + if (!Z2IsOne) + { + ABZ2 = ABZ2.Multiply(Z2); + } + + L3 = AU2.Add(B).SquarePlusProduct(ABZ2, L1.Add(Z1)); + + Z3 = ABZ2; + if (!Z1IsOne) + { + Z3 = Z3.Multiply(Z1); + } + } + + return new SecT283R1Point(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint Twice() + { + if (this.IsInfinity) + return this; + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + if (X1.IsZero) + { + // A point with X == 0 is it's own Additive inverse + return curve.Infinity; + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + + bool Z1IsOne = Z1.IsOne; + ECFieldElement L1Z1 = Z1IsOne ? L1 : L1.Multiply(Z1); + ECFieldElement Z1Sq = Z1IsOne ? Z1 : Z1.Square(); + ECFieldElement T = L1.Square().Add(L1Z1).Add(Z1Sq); + if (T.IsZero) + { + return new SecT283R1Point(curve, T, curve.B.Sqrt(), IsCompressed); + } + + ECFieldElement X3 = T.Square(); + ECFieldElement Z3 = Z1IsOne ? T : T.Multiply(Z1Sq); + + ECFieldElement X1Z1 = Z1IsOne ? X1 : X1.Multiply(Z1); + ECFieldElement L3 = X1Z1.SquarePlusProduct(T, L1Z1).Add(X3).Add(Z3); + + return new SecT283R1Point(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint TwicePlus(ECPoint b) + { + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return Twice(); + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + if (X1.IsZero) + { + // A point with X == 0 is it's own Additive inverse + return b; + } + + ECFieldElement X2 = b.RawXCoord, Z2 = b.RawZCoords[0]; + if (X2.IsZero || !Z2.IsOne) + { + return Twice().Add(b); + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + ECFieldElement L2 = b.RawYCoord; + + ECFieldElement X1Sq = X1.Square(); + ECFieldElement L1Sq = L1.Square(); + ECFieldElement Z1Sq = Z1.Square(); + ECFieldElement L1Z1 = L1.Multiply(Z1); + + ECFieldElement T = Z1Sq.Add(L1Sq).Add(L1Z1); + ECFieldElement A = L2.Multiply(Z1Sq).Add(L1Sq).MultiplyPlusProduct(T, X1Sq, Z1Sq); + ECFieldElement X2Z1Sq = X2.Multiply(Z1Sq); + ECFieldElement B = X2Z1Sq.Add(T).Square(); + + if (B.IsZero) + { + if (A.IsZero) + return b.Twice(); + + return curve.Infinity; + } + + if (A.IsZero) + { + return new SecT283R1Point(curve, A, curve.B.Sqrt(), IsCompressed); + } + + ECFieldElement X3 = A.Square().Multiply(X2Z1Sq); + ECFieldElement Z3 = A.Multiply(B).Multiply(Z1Sq); + ECFieldElement L3 = A.Add(B).Square().MultiplyPlusProduct(T, L2.AddOne(), Z3); + + return new SecT283R1Point(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint Negate() + { + if (this.IsInfinity) + return this; + + ECFieldElement X = this.RawXCoord; + if (X.IsZero) + return this; + + // L is actually Lambda (X + Y/X) here + ECFieldElement L = this.RawYCoord, Z = this.RawZCoords[0]; + return new SecT283R1Point(Curve, X, L.Add(Z), new ECFieldElement[] { Z }, IsCompressed); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT409Field.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT409Field.cs new file mode 100644 index 0000000..861b77a --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT409Field.cs @@ -0,0 +1,331 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Math.Raw; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT409Field + { + private const ulong M25 = ulong.MaxValue >> 39; + private const ulong M59 = ulong.MaxValue >> 5; + + public static void Add(ulong[] x, ulong[] y, ulong[] z) + { + z[0] = x[0] ^ y[0]; + z[1] = x[1] ^ y[1]; + z[2] = x[2] ^ y[2]; + z[3] = x[3] ^ y[3]; + z[4] = x[4] ^ y[4]; + z[5] = x[5] ^ y[5]; + z[6] = x[6] ^ y[6]; + } + + public static void AddExt(ulong[] xx, ulong[] yy, ulong[] zz) + { + for (int i = 0; i < 13; ++i) + { + zz[i] = xx[i] ^ yy[i]; + } + } + + public static void AddOne(ulong[] x, ulong[] z) + { + z[0] = x[0] ^ 1UL; + z[1] = x[1]; + z[2] = x[2]; + z[3] = x[3]; + z[4] = x[4]; + z[5] = x[5]; + z[6] = x[6]; + } + + public static ulong[] FromBigInteger(BigInteger x) + { + ulong[] z = Nat448.FromBigInteger64(x); + Reduce39(z, 0); + return z; + } + + public static void Invert(ulong[] x, ulong[] z) + { + if (Nat448.IsZero64(x)) + throw new InvalidOperationException(); + + // Itoh-Tsujii inversion with bases { 2, 3 } + + ulong[] t0 = Nat448.Create64(); + ulong[] t1 = Nat448.Create64(); + ulong[] t2 = Nat448.Create64(); + + Square(x, t0); + + // 3 | 408 + SquareN(t0, 1, t1); + Multiply(t0, t1, t0); + SquareN(t1, 1, t1); + Multiply(t0, t1, t0); + + // 2 | 136 + SquareN(t0, 3, t1); + Multiply(t0, t1, t0); + + // 2 | 68 + SquareN(t0, 6, t1); + Multiply(t0, t1, t0); + + // 2 | 34 + SquareN(t0, 12, t1); + Multiply(t0, t1, t2); + + // ! {2,3} | 17 + SquareN(t2, 24, t0); + SquareN(t0, 24, t1); + Multiply(t0, t1, t0); + + // 2 | 8 + SquareN(t0, 48, t1); + Multiply(t0, t1, t0); + + // 2 | 4 + SquareN(t0, 96, t1); + Multiply(t0, t1, t0); + + // 2 | 2 + SquareN(t0, 192, t1); + Multiply(t0, t1, t0); + + Multiply(t0, t2, z); + } + + public static void Multiply(ulong[] x, ulong[] y, ulong[] z) + { + ulong[] tt = Nat448.CreateExt64(); + ImplMultiply(x, y, tt); + Reduce(tt, z); + } + + public static void MultiplyAddToExt(ulong[] x, ulong[] y, ulong[] zz) + { + ulong[] tt = Nat448.CreateExt64(); + ImplMultiply(x, y, tt); + AddExt(zz, tt, zz); + } + + public static void Reduce(ulong[] xx, ulong[] z) + { + ulong x00 = xx[0], x01 = xx[1], x02 = xx[2], x03 = xx[3]; + ulong x04 = xx[4], x05 = xx[5], x06 = xx[6], x07 = xx[7]; + + ulong u = xx[12]; + x05 ^= (u << 39); + x06 ^= (u >> 25) ^ (u << 62); + x07 ^= (u >> 2); + + u = xx[11]; + x04 ^= (u << 39); + x05 ^= (u >> 25) ^ (u << 62); + x06 ^= (u >> 2); + + u = xx[10]; + x03 ^= (u << 39); + x04 ^= (u >> 25) ^ (u << 62); + x05 ^= (u >> 2); + + u = xx[9]; + x02 ^= (u << 39); + x03 ^= (u >> 25) ^ (u << 62); + x04 ^= (u >> 2); + + u = xx[8]; + x01 ^= (u << 39); + x02 ^= (u >> 25) ^ (u << 62); + x03 ^= (u >> 2); + + u = x07; + x00 ^= (u << 39); + x01 ^= (u >> 25) ^ (u << 62); + x02 ^= (u >> 2); + + ulong t = x06 >> 25; + z[0] = x00 ^ t; + z[1] = x01 ^ (t << 23); + z[2] = x02; + z[3] = x03; + z[4] = x04; + z[5] = x05; + z[6] = x06 & M25; + } + + public static void Reduce39(ulong[] z, int zOff) + { + ulong z6 = z[zOff + 6], t = z6 >> 25; + z[zOff ] ^= t; + z[zOff + 1] ^= (t << 23); + z[zOff + 6] = z6 & M25; + } + + public static void Sqrt(ulong[] x, ulong[] z) + { + ulong u0, u1; + u0 = Interleave.Unshuffle(x[0]); u1 = Interleave.Unshuffle(x[1]); + ulong e0 = (u0 & 0x00000000FFFFFFFFUL) | (u1 << 32); + ulong c0 = (u0 >> 32) | (u1 & 0xFFFFFFFF00000000UL); + + u0 = Interleave.Unshuffle(x[2]); u1 = Interleave.Unshuffle(x[3]); + ulong e1 = (u0 & 0x00000000FFFFFFFFUL) | (u1 << 32); + ulong c1 = (u0 >> 32) | (u1 & 0xFFFFFFFF00000000UL); + + u0 = Interleave.Unshuffle(x[4]); u1 = Interleave.Unshuffle(x[5]); + ulong e2 = (u0 & 0x00000000FFFFFFFFUL) | (u1 << 32); + ulong c2 = (u0 >> 32) | (u1 & 0xFFFFFFFF00000000UL); + + u0 = Interleave.Unshuffle(x[6]); + ulong e3 = (u0 & 0x00000000FFFFFFFFUL); + ulong c3 = (u0 >> 32); + + z[0] = e0 ^ (c0 << 44); + z[1] = e1 ^ (c1 << 44) ^ (c0 >> 20); + z[2] = e2 ^ (c2 << 44) ^ (c1 >> 20); + z[3] = e3 ^ (c3 << 44) ^ (c2 >> 20) ^ (c0 << 13); + z[4] = (c3 >> 20) ^ (c1 << 13) ^ (c0 >> 51); + z[5] = (c2 << 13) ^ (c1 >> 51); + z[6] = (c3 << 13) ^ (c2 >> 51); + + Debug.Assert((c3 >> 51) == 0); + } + + public static void Square(ulong[] x, ulong[] z) + { + ulong[] tt = Nat.Create64(13); + ImplSquare(x, tt); + Reduce(tt, z); + } + + public static void SquareAddToExt(ulong[] x, ulong[] zz) + { + ulong[] tt = Nat.Create64(13); + ImplSquare(x, tt); + AddExt(zz, tt, zz); + } + + public static void SquareN(ulong[] x, int n, ulong[] z) + { + Debug.Assert(n > 0); + + ulong[] tt = Nat.Create64(13); + ImplSquare(x, tt); + Reduce(tt, z); + + while (--n > 0) + { + ImplSquare(z, tt); + Reduce(tt, z); + } + } + + public static uint Trace(ulong[] x) + { + // Non-zero-trace bits: 0 + return (uint)(x[0]) & 1U; + } + + protected static void ImplCompactExt(ulong[] zz) + { + ulong z00 = zz[ 0], z01 = zz[ 1], z02 = zz[ 2], z03 = zz[ 3], z04 = zz[ 4], z05 = zz[ 5], z06 = zz[ 6]; + ulong z07 = zz[ 7], z08 = zz[ 8], z09 = zz[ 9], z10 = zz[10], z11 = zz[11], z12 = zz[12], z13 = zz[13]; + zz[ 0] = z00 ^ (z01 << 59); + zz[ 1] = (z01 >> 5) ^ (z02 << 54); + zz[ 2] = (z02 >> 10) ^ (z03 << 49); + zz[ 3] = (z03 >> 15) ^ (z04 << 44); + zz[ 4] = (z04 >> 20) ^ (z05 << 39); + zz[ 5] = (z05 >> 25) ^ (z06 << 34); + zz[ 6] = (z06 >> 30) ^ (z07 << 29); + zz[ 7] = (z07 >> 35) ^ (z08 << 24); + zz[ 8] = (z08 >> 40) ^ (z09 << 19); + zz[ 9] = (z09 >> 45) ^ (z10 << 14); + zz[10] = (z10 >> 50) ^ (z11 << 9); + zz[11] = (z11 >> 55) ^ (z12 << 4) + ^ (z13 << 63); + zz[12] = (z12 >> 60) + ^ (z13 >> 1); + zz[13] = 0; + } + + protected static void ImplExpand(ulong[] x, ulong[] z) + { + ulong x0 = x[0], x1 = x[1], x2 = x[2], x3 = x[3], x4 = x[4], x5 = x[5], x6 = x[6]; + z[0] = x0 & M59; + z[1] = ((x0 >> 59) ^ (x1 << 5)) & M59; + z[2] = ((x1 >> 54) ^ (x2 << 10)) & M59; + z[3] = ((x2 >> 49) ^ (x3 << 15)) & M59; + z[4] = ((x3 >> 44) ^ (x4 << 20)) & M59; + z[5] = ((x4 >> 39) ^ (x5 << 25)) & M59; + z[6] = ((x5 >> 34) ^ (x6 << 30)); + } + + protected static void ImplMultiply(ulong[] x, ulong[] y, ulong[] zz) + { + ulong[] a = new ulong[7], b = new ulong[7]; + ImplExpand(x, a); + ImplExpand(y, b); + + for (int i = 0; i < 7; ++i) + { + ImplMulwAcc(a, b[i], zz, i); + } + + ImplCompactExt(zz); + } + + protected static void ImplMulwAcc(ulong[] xs, ulong y, ulong[] z, int zOff) + { + Debug.Assert(y >> 59 == 0); + + ulong[] u = new ulong[8]; + //u[0] = 0; + u[1] = y; + u[2] = u[1] << 1; + u[3] = u[2] ^ y; + u[4] = u[2] << 1; + u[5] = u[4] ^ y; + u[6] = u[3] << 1; + u[7] = u[6] ^ y; + + for (int i = 0; i < 7; ++i) + { + ulong x = xs[i]; + + Debug.Assert(x >> 59 == 0); + + uint j = (uint)x; + ulong g, h = 0, l = u[j & 7] + ^ (u[(j >> 3) & 7] << 3); + int k = 54; + do + { + j = (uint)(x >> k); + g = u[j & 7] + ^ u[(j >> 3) & 7] << 3; + l ^= (g << k); + h ^= (g >> -k); + } + while ((k -= 6) > 0); + + Debug.Assert(h >> 53 == 0); + + z[zOff + i ] ^= l & M59; + z[zOff + i + 1] ^= (l >> 59) ^ (h << 5); + } + } + + protected static void ImplSquare(ulong[] x, ulong[] zz) + { + for (int i = 0; i < 6; ++i) + { + Interleave.Expand64To128(x[i], zz, i << 1); + } + zz[12] = Interleave.Expand32to64((uint)x[6]); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT409FieldElement.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT409FieldElement.cs new file mode 100644 index 0000000..f954f46 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT409FieldElement.cs @@ -0,0 +1,216 @@ +using System; + +using Org.BouncyCastle.Math.Raw; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT409FieldElement + : ECFieldElement + { + protected ulong[] x; + + public SecT409FieldElement(BigInteger x) + { + if (x == null || x.SignValue < 0 || x.BitLength > 409) + throw new ArgumentException("value invalid for SecT409FieldElement", "x"); + + this.x = SecT409Field.FromBigInteger(x); + } + + public SecT409FieldElement() + { + this.x = Nat448.Create64(); + } + + protected internal SecT409FieldElement(ulong[] x) + { + this.x = x; + } + + public override bool IsOne + { + get { return Nat448.IsOne64(x); } + } + + public override bool IsZero + { + get { return Nat448.IsZero64(x); } + } + + public override bool TestBitZero() + { + return (x[0] & 1UL) != 0UL; + } + + public override BigInteger ToBigInteger() + { + return Nat448.ToBigInteger64(x); + } + + public override string FieldName + { + get { return "SecT409Field"; } + } + + public override int FieldSize + { + get { return 409; } + } + + public override ECFieldElement Add(ECFieldElement b) + { + ulong[] z = Nat448.Create64(); + SecT409Field.Add(x, ((SecT409FieldElement)b).x, z); + return new SecT409FieldElement(z); + } + + public override ECFieldElement AddOne() + { + ulong[] z = Nat448.Create64(); + SecT409Field.AddOne(x, z); + return new SecT409FieldElement(z); + } + + public override ECFieldElement Subtract(ECFieldElement b) + { + // Addition and subtraction are the same in F2m + return Add(b); + } + + public override ECFieldElement Multiply(ECFieldElement b) + { + ulong[] z = Nat448.Create64(); + SecT409Field.Multiply(x, ((SecT409FieldElement)b).x, z); + return new SecT409FieldElement(z); + } + + public override ECFieldElement MultiplyMinusProduct(ECFieldElement b, ECFieldElement x, ECFieldElement y) + { + return MultiplyPlusProduct(b, x, y); + } + + public override ECFieldElement MultiplyPlusProduct(ECFieldElement b, ECFieldElement x, ECFieldElement y) + { + ulong[] ax = this.x, bx = ((SecT409FieldElement)b).x; + ulong[] xx = ((SecT409FieldElement)x).x, yx = ((SecT409FieldElement)y).x; + + ulong[] tt = Nat.Create64(13); + SecT409Field.MultiplyAddToExt(ax, bx, tt); + SecT409Field.MultiplyAddToExt(xx, yx, tt); + + ulong[] z = Nat448.Create64(); + SecT409Field.Reduce(tt, z); + return new SecT409FieldElement(z); + } + + public override ECFieldElement Divide(ECFieldElement b) + { + return Multiply(b.Invert()); + } + + public override ECFieldElement Negate() + { + return this; + } + + public override ECFieldElement Square() + { + ulong[] z = Nat448.Create64(); + SecT409Field.Square(x, z); + return new SecT409FieldElement(z); + } + + public override ECFieldElement SquareMinusProduct(ECFieldElement x, ECFieldElement y) + { + return SquarePlusProduct(x, y); + } + + public override ECFieldElement SquarePlusProduct(ECFieldElement x, ECFieldElement y) + { + ulong[] ax = this.x; + ulong[] xx = ((SecT409FieldElement)x).x, yx = ((SecT409FieldElement)y).x; + + ulong[] tt = Nat.Create64(13); + SecT409Field.SquareAddToExt(ax, tt); + SecT409Field.MultiplyAddToExt(xx, yx, tt); + + ulong[] z = Nat448.Create64(); + SecT409Field.Reduce(tt, z); + return new SecT409FieldElement(z); + } + + public override ECFieldElement SquarePow(int pow) + { + if (pow < 1) + return this; + + ulong[] z = Nat448.Create64(); + SecT409Field.SquareN(x, pow, z); + return new SecT409FieldElement(z); + } + + public override ECFieldElement Invert() + { + ulong[] z = Nat448.Create64(); + SecT409Field.Invert(x, z); + return new SecT409FieldElement(z); + } + + public override ECFieldElement Sqrt() + { + ulong[] z = Nat448.Create64(); + SecT409Field.Sqrt(x, z); + return new SecT409FieldElement(z); + } + + public virtual int Representation + { + get { return F2mFieldElement.Tpb; } + } + + public virtual int M + { + get { return 409; } + } + + public virtual int K1 + { + get { return 87; } + } + + public virtual int K2 + { + get { return 0; } + } + + public virtual int K3 + { + get { return 0; } + } + + public override bool Equals(object obj) + { + return Equals(obj as SecT409FieldElement); + } + + public override bool Equals(ECFieldElement other) + { + return Equals(other as SecT409FieldElement); + } + + public virtual bool Equals(SecT409FieldElement other) + { + if (this == other) + return true; + if (null == other) + return false; + return Nat448.Eq64(x, other.x); + } + + public override int GetHashCode() + { + return 4090087 ^ Arrays.GetHashCode(x, 0, 7); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT409K1Curve.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT409K1Curve.cs new file mode 100644 index 0000000..4f57355 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT409K1Curve.cs @@ -0,0 +1,104 @@ +using System; + +using Org.BouncyCastle.Math.EC.Multiplier; +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT409K1Curve + : AbstractF2mCurve + { + private const int SecT409K1_DEFAULT_COORDS = COORD_LAMBDA_PROJECTIVE; + + protected readonly SecT409K1Point m_infinity; + + public SecT409K1Curve() + : base(409, 87, 0, 0) + { + this.m_infinity = new SecT409K1Point(this, null, null); + + this.m_a = FromBigInteger(BigInteger.Zero); + this.m_b = FromBigInteger(BigInteger.One); + this.m_order = new BigInteger(1, Hex.Decode("7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE5F83B2D4EA20400EC4557D5ED3E3E7CA5B4B5C83B8E01E5FCF")); + this.m_cofactor = BigInteger.ValueOf(4); + + this.m_coord = SecT409K1_DEFAULT_COORDS; + } + + protected override ECCurve CloneCurve() + { + return new SecT409K1Curve(); + } + + public override bool SupportsCoordinateSystem(int coord) + { + switch (coord) + { + case COORD_LAMBDA_PROJECTIVE: + return true; + default: + return false; + } + } + + protected override ECMultiplier CreateDefaultMultiplier() + { + return new WTauNafMultiplier(); + } + + public override ECPoint Infinity + { + get { return m_infinity; } + } + + public override int FieldSize + { + get { return 409; } + } + + public override ECFieldElement FromBigInteger(BigInteger x) + { + return new SecT409FieldElement(x); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, bool withCompression) + { + return new SecT409K1Point(this, x, y, withCompression); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + { + return new SecT409K1Point(this, x, y, zs, withCompression); + } + + public override bool IsKoblitz + { + get { return true; } + } + + public virtual int M + { + get { return 409; } + } + + public virtual bool IsTrinomial + { + get { return true; } + } + + public virtual int K1 + { + get { return 87; } + } + + public virtual int K2 + { + get { return 0; } + } + + public virtual int K3 + { + get { return 0; } + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT409K1Point.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT409K1Point.cs new file mode 100644 index 0000000..e67ca9a --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT409K1Point.cs @@ -0,0 +1,289 @@ +using System; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT409K1Point + : AbstractF2mPoint + { + /** + * @deprecated Use ECCurve.createPoint to construct points + */ + public SecT409K1Point(ECCurve curve, ECFieldElement x, ECFieldElement y) + : this(curve, x, y, false) + { + } + + /** + * @deprecated per-point compression property will be removed, refer {@link #getEncoded(bool)} + */ + public SecT409K1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, bool withCompression) + : base(curve, x, y, withCompression) + { + if ((x == null) != (y == null)) + throw new ArgumentException("Exactly one of the field elements is null"); + } + + internal SecT409K1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + : base(curve, x, y, zs, withCompression) + { + } + + protected override ECPoint Detach() + { + return new SecT409K1Point(null, this.AffineXCoord, this.AffineYCoord); // earlier JDK + } + + public override ECFieldElement YCoord + { + get + { + ECFieldElement X = RawXCoord, L = RawYCoord; + + if (this.IsInfinity || X.IsZero) + return L; + + // Y is actually Lambda (X + Y/X) here; convert to affine value on the fly + ECFieldElement Y = L.Add(X).Multiply(X); + + ECFieldElement Z = RawZCoords[0]; + if (!Z.IsOne) + { + Y = Y.Divide(Z); + } + + return Y; + } + } + + protected internal override bool CompressionYTilde + { + get + { + ECFieldElement X = this.RawXCoord; + if (X.IsZero) + return false; + + ECFieldElement Y = this.RawYCoord; + + // Y is actually Lambda (X + Y/X) here + return Y.TestBitZero() != X.TestBitZero(); + } + } + + public override ECPoint Add(ECPoint b) + { + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return this; + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + ECFieldElement X2 = b.RawXCoord; + + if (X1.IsZero) + { + if (X2.IsZero) + return curve.Infinity; + + return b.Add(this); + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + ECFieldElement L2 = b.RawYCoord, Z2 = b.RawZCoords[0]; + + bool Z1IsOne = Z1.IsOne; + ECFieldElement U2 = X2, S2 = L2; + if (!Z1IsOne) + { + U2 = U2.Multiply(Z1); + S2 = S2.Multiply(Z1); + } + + bool Z2IsOne = Z2.IsOne; + ECFieldElement U1 = X1, S1 = L1; + if (!Z2IsOne) + { + U1 = U1.Multiply(Z2); + S1 = S1.Multiply(Z2); + } + + ECFieldElement A = S1.Add(S2); + ECFieldElement B = U1.Add(U2); + + if (B.IsZero) + { + if (A.IsZero) + return Twice(); + + return curve.Infinity; + } + + ECFieldElement X3, L3, Z3; + if (X2.IsZero) + { + // TODO This can probably be optimized quite a bit + ECPoint p = this.Normalize(); + X1 = p.XCoord; + ECFieldElement Y1 = p.YCoord; + + ECFieldElement Y2 = L2; + ECFieldElement L = Y1.Add(Y2).Divide(X1); + + X3 = L.Square().Add(L).Add(X1); + if (X3.IsZero) + { + return new SecT409K1Point(curve, X3, curve.B, IsCompressed); + } + + ECFieldElement Y3 = L.Multiply(X1.Add(X3)).Add(X3).Add(Y1); + L3 = Y3.Divide(X3).Add(X3); + Z3 = curve.FromBigInteger(BigInteger.One); + } + else + { + B = B.Square(); + + ECFieldElement AU1 = A.Multiply(U1); + ECFieldElement AU2 = A.Multiply(U2); + + X3 = AU1.Multiply(AU2); + if (X3.IsZero) + { + return new SecT409K1Point(curve, X3, curve.B, IsCompressed); + } + + ECFieldElement ABZ2 = A.Multiply(B); + if (!Z2IsOne) + { + ABZ2 = ABZ2.Multiply(Z2); + } + + L3 = AU2.Add(B).SquarePlusProduct(ABZ2, L1.Add(Z1)); + + Z3 = ABZ2; + if (!Z1IsOne) + { + Z3 = Z3.Multiply(Z1); + } + } + + return new SecT409K1Point(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint Twice() + { + if (this.IsInfinity) + return this; + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + if (X1.IsZero) + { + // A point with X == 0 is it's own Additive inverse + return curve.Infinity; + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + + bool Z1IsOne = Z1.IsOne; + ECFieldElement Z1Sq = Z1IsOne ? Z1 : Z1.Square(); + ECFieldElement T; + if (Z1IsOne) + { + T = L1.Square().Add(L1); + } + else + { + T = L1.Add(Z1).Multiply(L1); + } + + if (T.IsZero) + { + return new SecT409K1Point(curve, T, curve.B, IsCompressed); + } + + ECFieldElement X3 = T.Square(); + ECFieldElement Z3 = Z1IsOne ? T : T.Multiply(Z1Sq); + + ECFieldElement t1 = L1.Add(X1).Square(); + ECFieldElement t2 = Z1IsOne ? Z1 : Z1Sq.Square(); + ECFieldElement L3 = t1.Add(T).Add(Z1Sq).Multiply(t1).Add(t2).Add(X3).Add(Z3); + + return new SecT409K1Point(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint TwicePlus(ECPoint b) + { + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return Twice(); + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + if (X1.IsZero) + { + // A point with X == 0 is it's own Additive inverse + return b; + } + + // NOTE: TwicePlus() only optimized for lambda-affine argument + ECFieldElement X2 = b.RawXCoord, Z2 = b.RawZCoords[0]; + if (X2.IsZero || !Z2.IsOne) + { + return Twice().Add(b); + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + ECFieldElement L2 = b.RawYCoord; + + ECFieldElement X1Sq = X1.Square(); + ECFieldElement L1Sq = L1.Square(); + ECFieldElement Z1Sq = Z1.Square(); + ECFieldElement L1Z1 = L1.Multiply(Z1); + + ECFieldElement T = L1Sq.Add(L1Z1); + ECFieldElement L2plus1 = L2.AddOne(); + ECFieldElement A = L2plus1.Multiply(Z1Sq).Add(L1Sq).MultiplyPlusProduct(T, X1Sq, Z1Sq); + ECFieldElement X2Z1Sq = X2.Multiply(Z1Sq); + ECFieldElement B = X2Z1Sq.Add(T).Square(); + + if (B.IsZero) + { + if (A.IsZero) + return b.Twice(); + + return curve.Infinity; + } + + if (A.IsZero) + { + return new SecT409K1Point(curve, A, curve.B, IsCompressed); + } + + ECFieldElement X3 = A.Square().Multiply(X2Z1Sq); + ECFieldElement Z3 = A.Multiply(B).Multiply(Z1Sq); + ECFieldElement L3 = A.Add(B).Square().MultiplyPlusProduct(T, L2plus1, Z3); + + return new SecT409K1Point(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint Negate() + { + if (this.IsInfinity) + return this; + + ECFieldElement X = this.RawXCoord; + if (X.IsZero) + return this; + + // L is actually Lambda (X + Y/X) here + ECFieldElement L = this.RawYCoord, Z = this.RawZCoords[0]; + return new SecT409K1Point(Curve, X, L.Add(Z), new ECFieldElement[] { Z }, IsCompressed); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT409R1Curve.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT409R1Curve.cs new file mode 100644 index 0000000..9212fb5 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT409R1Curve.cs @@ -0,0 +1,98 @@ +using System; + +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT409R1Curve + : AbstractF2mCurve + { + private const int SecT409R1_DEFAULT_COORDS = COORD_LAMBDA_PROJECTIVE; + + protected readonly SecT409R1Point m_infinity; + + public SecT409R1Curve() + : base(409, 87, 0, 0) + { + this.m_infinity = new SecT409R1Point(this, null, null); + + this.m_a = FromBigInteger(BigInteger.One); + this.m_b = FromBigInteger(new BigInteger(1, Hex.Decode("0021A5C2C8EE9FEB5C4B9A753B7B476B7FD6422EF1F3DD674761FA99D6AC27C8A9A197B272822F6CD57A55AA4F50AE317B13545F"))); + this.m_order = new BigInteger(1, Hex.Decode("010000000000000000000000000000000000000000000000000001E2AAD6A612F33307BE5FA47C3C9E052F838164CD37D9A21173")); + this.m_cofactor = BigInteger.Two; + + this.m_coord = SecT409R1_DEFAULT_COORDS; + } + + protected override ECCurve CloneCurve() + { + return new SecT409R1Curve(); + } + + public override bool SupportsCoordinateSystem(int coord) + { + switch (coord) + { + case COORD_LAMBDA_PROJECTIVE: + return true; + default: + return false; + } + } + + public override ECPoint Infinity + { + get { return m_infinity; } + } + + public override int FieldSize + { + get { return 409; } + } + + public override ECFieldElement FromBigInteger(BigInteger x) + { + return new SecT409FieldElement(x); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, bool withCompression) + { + return new SecT409R1Point(this, x, y, withCompression); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + { + return new SecT409R1Point(this, x, y, zs, withCompression); + } + + public override bool IsKoblitz + { + get { return false; } + } + + public virtual int M + { + get { return 409; } + } + + public virtual bool IsTrinomial + { + get { return true; } + } + + public virtual int K1 + { + get { return 87; } + } + + public virtual int K2 + { + get { return 0; } + } + + public virtual int K3 + { + get { return 0; } + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT409R1Point.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT409R1Point.cs new file mode 100644 index 0000000..92f6143 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT409R1Point.cs @@ -0,0 +1,278 @@ +using System; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT409R1Point + : AbstractF2mPoint + { + /** + * @deprecated Use ECCurve.createPoint to construct points + */ + public SecT409R1Point(ECCurve curve, ECFieldElement x, ECFieldElement y) + : this(curve, x, y, false) + { + } + + /** + * @deprecated per-point compression property will be removed, refer {@link #getEncoded(bool)} + */ + public SecT409R1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, bool withCompression) + : base(curve, x, y, withCompression) + { + if ((x == null) != (y == null)) + throw new ArgumentException("Exactly one of the field elements is null"); + } + + internal SecT409R1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + : base(curve, x, y, zs, withCompression) + { + } + + protected override ECPoint Detach() + { + return new SecT409R1Point(null, AffineXCoord, AffineYCoord); + } + + public override ECFieldElement YCoord + { + get + { + ECFieldElement X = RawXCoord, L = RawYCoord; + + if (this.IsInfinity || X.IsZero) + return L; + + // Y is actually Lambda (X + Y/X) here; convert to affine value on the fly + ECFieldElement Y = L.Add(X).Multiply(X); + + ECFieldElement Z = RawZCoords[0]; + if (!Z.IsOne) + { + Y = Y.Divide(Z); + } + + return Y; + } + } + + protected internal override bool CompressionYTilde + { + get + { + ECFieldElement X = this.RawXCoord; + if (X.IsZero) + return false; + + ECFieldElement Y = this.RawYCoord; + + // Y is actually Lambda (X + Y/X) here + return Y.TestBitZero() != X.TestBitZero(); + } + } + + public override ECPoint Add(ECPoint b) + { + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return this; + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + ECFieldElement X2 = b.RawXCoord; + + if (X1.IsZero) + { + if (X2.IsZero) + return curve.Infinity; + + return b.Add(this); + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + ECFieldElement L2 = b.RawYCoord, Z2 = b.RawZCoords[0]; + + bool Z1IsOne = Z1.IsOne; + ECFieldElement U2 = X2, S2 = L2; + if (!Z1IsOne) + { + U2 = U2.Multiply(Z1); + S2 = S2.Multiply(Z1); + } + + bool Z2IsOne = Z2.IsOne; + ECFieldElement U1 = X1, S1 = L1; + if (!Z2IsOne) + { + U1 = U1.Multiply(Z2); + S1 = S1.Multiply(Z2); + } + + ECFieldElement A = S1.Add(S2); + ECFieldElement B = U1.Add(U2); + + if (B.IsZero) + { + if (A.IsZero) + return Twice(); + + return curve.Infinity; + } + + ECFieldElement X3, L3, Z3; + if (X2.IsZero) + { + // TODO This can probably be optimized quite a bit + ECPoint p = this.Normalize(); + X1 = p.XCoord; + ECFieldElement Y1 = p.YCoord; + + ECFieldElement Y2 = L2; + ECFieldElement L = Y1.Add(Y2).Divide(X1); + + X3 = L.Square().Add(L).Add(X1).AddOne(); + if (X3.IsZero) + { + return new SecT409R1Point(curve, X3, curve.B.Sqrt(), IsCompressed); + } + + ECFieldElement Y3 = L.Multiply(X1.Add(X3)).Add(X3).Add(Y1); + L3 = Y3.Divide(X3).Add(X3); + Z3 = curve.FromBigInteger(BigInteger.One); + } + else + { + B = B.Square(); + + ECFieldElement AU1 = A.Multiply(U1); + ECFieldElement AU2 = A.Multiply(U2); + + X3 = AU1.Multiply(AU2); + if (X3.IsZero) + { + return new SecT409R1Point(curve, X3, curve.B.Sqrt(), IsCompressed); + } + + ECFieldElement ABZ2 = A.Multiply(B); + if (!Z2IsOne) + { + ABZ2 = ABZ2.Multiply(Z2); + } + + L3 = AU2.Add(B).SquarePlusProduct(ABZ2, L1.Add(Z1)); + + Z3 = ABZ2; + if (!Z1IsOne) + { + Z3 = Z3.Multiply(Z1); + } + } + + return new SecT409R1Point(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint Twice() + { + if (this.IsInfinity) + return this; + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + if (X1.IsZero) + { + // A point with X == 0 is it's own Additive inverse + return curve.Infinity; + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + + bool Z1IsOne = Z1.IsOne; + ECFieldElement L1Z1 = Z1IsOne ? L1 : L1.Multiply(Z1); + ECFieldElement Z1Sq = Z1IsOne ? Z1 : Z1.Square(); + ECFieldElement T = L1.Square().Add(L1Z1).Add(Z1Sq); + if (T.IsZero) + { + return new SecT409R1Point(curve, T, curve.B.Sqrt(), IsCompressed); + } + + ECFieldElement X3 = T.Square(); + ECFieldElement Z3 = Z1IsOne ? T : T.Multiply(Z1Sq); + + ECFieldElement X1Z1 = Z1IsOne ? X1 : X1.Multiply(Z1); + ECFieldElement L3 = X1Z1.SquarePlusProduct(T, L1Z1).Add(X3).Add(Z3); + + return new SecT409R1Point(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint TwicePlus(ECPoint b) + { + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return Twice(); + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + if (X1.IsZero) + { + // A point with X == 0 is it's own Additive inverse + return b; + } + + ECFieldElement X2 = b.RawXCoord, Z2 = b.RawZCoords[0]; + if (X2.IsZero || !Z2.IsOne) + { + return Twice().Add(b); + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + ECFieldElement L2 = b.RawYCoord; + + ECFieldElement X1Sq = X1.Square(); + ECFieldElement L1Sq = L1.Square(); + ECFieldElement Z1Sq = Z1.Square(); + ECFieldElement L1Z1 = L1.Multiply(Z1); + + ECFieldElement T = Z1Sq.Add(L1Sq).Add(L1Z1); + ECFieldElement A = L2.Multiply(Z1Sq).Add(L1Sq).MultiplyPlusProduct(T, X1Sq, Z1Sq); + ECFieldElement X2Z1Sq = X2.Multiply(Z1Sq); + ECFieldElement B = X2Z1Sq.Add(T).Square(); + + if (B.IsZero) + { + if (A.IsZero) + return b.Twice(); + + return curve.Infinity; + } + + if (A.IsZero) + { + return new SecT409R1Point(curve, A, curve.B.Sqrt(), IsCompressed); + } + + ECFieldElement X3 = A.Square().Multiply(X2Z1Sq); + ECFieldElement Z3 = A.Multiply(B).Multiply(Z1Sq); + ECFieldElement L3 = A.Add(B).Square().MultiplyPlusProduct(T, L2.AddOne(), Z3); + + return new SecT409R1Point(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint Negate() + { + if (this.IsInfinity) + return this; + + ECFieldElement X = this.RawXCoord; + if (X.IsZero) + return this; + + // L is actually Lambda (X + Y/X) here + ECFieldElement L = this.RawYCoord, Z = this.RawZCoords[0]; + return new SecT409R1Point(Curve, X, L.Add(Z), new ECFieldElement[] { Z }, IsCompressed); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT571Field.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT571Field.cs new file mode 100644 index 0000000..98f4f7f --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT571Field.cs @@ -0,0 +1,333 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Math.Raw; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT571Field + { + private const ulong M59 = ulong.MaxValue >> 5; + + private const ulong RM = 0xEF7BDEF7BDEF7BDEUL; + + private static readonly ulong[] ROOT_Z = new ulong[]{ 0x2BE1195F08CAFB99UL, 0x95F08CAF84657C23UL, 0xCAF84657C232BE11UL, 0x657C232BE1195F08UL, + 0xF84657C2308CAF84UL, 0x7C232BE1195F08CAUL, 0xBE1195F08CAF8465UL, 0x5F08CAF84657C232UL, 0x784657C232BE119UL }; + + public static void Add(ulong[] x, ulong[] y, ulong[] z) + { + for (int i = 0; i < 9; ++i) + { + z[i] = x[i] ^ y[i]; + } + } + + private static void Add(ulong[] x, int xOff, ulong[] y, int yOff, ulong[] z, int zOff) + { + for (int i = 0; i < 9; ++i) + { + z[zOff + i] = x[xOff + i] ^ y[yOff + i]; + } + } + + private static void AddBothTo(ulong[] x, int xOff, ulong[] y, int yOff, ulong[] z, int zOff) + { + for (int i = 0; i < 9; ++i) + { + z[zOff + i] ^= x[xOff + i] ^ y[yOff + i]; + } + } + + public static void AddExt(ulong[] xx, ulong[] yy, ulong[] zz) + { + for (int i = 0; i < 18; ++i) + { + zz[i] = xx[i] ^ yy[i]; + } + } + + public static void AddOne(ulong[] x, ulong[] z) + { + z[0] = x[0] ^ 1UL; + for (int i = 1; i < 9; ++i) + { + z[i] = x[i]; + } + } + + public static ulong[] FromBigInteger(BigInteger x) + { + ulong[] z = Nat576.FromBigInteger64(x); + Reduce5(z, 0); + return z; + } + + public static void Invert(ulong[] x, ulong[] z) + { + if (Nat576.IsZero64(x)) + throw new InvalidOperationException(); + + // Itoh-Tsujii inversion with bases { 2, 3, 5 } + + ulong[] t0 = Nat576.Create64(); + ulong[] t1 = Nat576.Create64(); + ulong[] t2 = Nat576.Create64(); + + Square(x, t2); + + // 5 | 570 + Square(t2, t0); + Square(t0, t1); + Multiply(t0, t1, t0); + SquareN(t0, 2, t1); + Multiply(t0, t1, t0); + Multiply(t0, t2, t0); + + // 3 | 114 + SquareN(t0, 5, t1); + Multiply(t0, t1, t0); + SquareN(t1, 5, t1); + Multiply(t0, t1, t0); + + // 2 | 38 + SquareN(t0, 15, t1); + Multiply(t0, t1, t2); + + // ! {2,3,5} | 19 + SquareN(t2, 30, t0); + SquareN(t0, 30, t1); + Multiply(t0, t1, t0); + + // 3 | 9 + SquareN(t0, 60, t1); + Multiply(t0, t1, t0); + SquareN(t1, 60, t1); + Multiply(t0, t1, t0); + + // 3 | 3 + SquareN(t0, 180, t1); + Multiply(t0, t1, t0); + SquareN(t1, 180, t1); + Multiply(t0, t1, t0); + + Multiply(t0, t2, z); + } + + public static void Multiply(ulong[] x, ulong[] y, ulong[] z) + { + ulong[] tt = Nat576.CreateExt64(); + ImplMultiply(x, y, tt); + Reduce(tt, z); + } + + public static void MultiplyAddToExt(ulong[] x, ulong[] y, ulong[] zz) + { + ulong[] tt = Nat576.CreateExt64(); + ImplMultiply(x, y, tt); + AddExt(zz, tt, zz); + } + + public static void Reduce(ulong[] xx, ulong[] z) + { + ulong xx09 = xx[9]; + ulong u = xx[17], v = xx09; + + xx09 = v ^ (u >> 59) ^ (u >> 57) ^ (u >> 54) ^ (u >> 49); + v = xx[8] ^ (u << 5) ^ (u << 7) ^ (u << 10) ^ (u << 15); + + for (int i = 16; i >= 10; --i) + { + u = xx[i]; + z[i - 8] = v ^ (u >> 59) ^ (u >> 57) ^ (u >> 54) ^ (u >> 49); + v = xx[i - 9] ^ (u << 5) ^ (u << 7) ^ (u << 10) ^ (u << 15); + } + + u = xx09; + z[1] = v ^ (u >> 59) ^ (u >> 57) ^ (u >> 54) ^ (u >> 49); + v = xx[0] ^ (u << 5) ^ (u << 7) ^ (u << 10) ^ (u << 15); + + ulong x08 = z[8]; + ulong t = x08 >> 59; + z[0] = v ^ t ^ (t << 2) ^ (t << 5) ^ (t << 10); + z[8] = x08 & M59; + } + + public static void Reduce5(ulong[] z, int zOff) + { + ulong z8 = z[zOff + 8], t = z8 >> 59; + z[zOff ] ^= t ^ (t << 2) ^ (t << 5) ^ (t << 10); + z[zOff + 8] = z8 & M59; + } + + public static void Sqrt(ulong[] x, ulong[] z) + { + ulong[] evn = Nat576.Create64(), odd = Nat576.Create64(); + + int pos = 0; + for (int i = 0; i < 4; ++i) + { + ulong u0 = Interleave.Unshuffle(x[pos++]); + ulong u1 = Interleave.Unshuffle(x[pos++]); + evn[i] = (u0 & 0x00000000FFFFFFFFUL) | (u1 << 32); + odd[i] = (u0 >> 32) | (u1 & 0xFFFFFFFF00000000UL); + } + { + ulong u0 = Interleave.Unshuffle(x[pos]); + evn[4] = (u0 & 0x00000000FFFFFFFFUL); + odd[4] = (u0 >> 32); + } + + Multiply(odd, ROOT_Z, z); + Add(z, evn, z); + } + + public static void Square(ulong[] x, ulong[] z) + { + ulong[] tt = Nat576.CreateExt64(); + ImplSquare(x, tt); + Reduce(tt, z); + } + + public static void SquareAddToExt(ulong[] x, ulong[] zz) + { + ulong[] tt = Nat576.CreateExt64(); + ImplSquare(x, tt); + AddExt(zz, tt, zz); + } + + public static void SquareN(ulong[] x, int n, ulong[] z) + { + Debug.Assert(n > 0); + + ulong[] tt = Nat576.CreateExt64(); + ImplSquare(x, tt); + Reduce(tt, z); + + while (--n > 0) + { + ImplSquare(z, tt); + Reduce(tt, z); + } + } + + public static uint Trace(ulong[] x) + { + // Non-zero-trace bits: 0, 561, 569 + return (uint)(x[0] ^ (x[8] >> 49) ^ (x[8] >> 57)) & 1U; + } + + protected static void ImplMultiply(ulong[] x, ulong[] y, ulong[] zz) + { + //for (int i = 0; i < 9; ++i) + //{ + // ImplMulwAcc(x, y[i], zz, i); + //} + + /* + * Precompute table of all 4-bit products of y + */ + ulong[] T0 = new ulong[9 << 4]; + Array.Copy(y, 0, T0, 9, 9); + // Reduce5(T0, 9); + int tOff = 0; + for (int i = 7; i > 0; --i) + { + tOff += 18; + Nat.ShiftUpBit64(9, T0, tOff >> 1, 0UL, T0, tOff); + Reduce5(T0, tOff); + Add(T0, 9, T0, tOff, T0, tOff + 9); + } + + /* + * Second table with all 4-bit products of B shifted 4 bits + */ + ulong[] T1 = new ulong[T0.Length]; + Nat.ShiftUpBits64(T0.Length, T0, 0, 4, 0L, T1, 0); + + uint MASK = 0xF; + + /* + * Lopez-Dahab algorithm + */ + + for (int k = 56; k >= 0; k -= 8) + { + for (int j = 1; j < 9; j += 2) + { + uint aVal = (uint)(x[j] >> k); + uint u = aVal & MASK; + uint v = (aVal >> 4) & MASK; + AddBothTo(T0, (int)(9 * u), T1, (int)(9 * v), zz, j - 1); + } + Nat.ShiftUpBits64(16, zz, 0, 8, 0L); + } + + for (int k = 56; k >= 0; k -= 8) + { + for (int j = 0; j < 9; j += 2) + { + uint aVal = (uint)(x[j] >> k); + uint u = aVal & MASK; + uint v = (aVal >> 4) & MASK; + AddBothTo(T0, (int)(9 * u), T1, (int)(9 * v), zz, j); + } + if (k > 0) + { + Nat.ShiftUpBits64(18, zz, 0, 8, 0L); + } + } + } + + protected static void ImplMulwAcc(ulong[] xs, ulong y, ulong[] z, int zOff) + { + ulong[] u = new ulong[32]; + //u[0] = 0; + u[1] = y; + for (int i = 2; i < 32; i += 2) + { + u[i ] = u[i >> 1] << 1; + u[i + 1] = u[i ] ^ y; + } + + ulong l = 0; + for (int i = 0; i < 9; ++i) + { + ulong x = xs[i]; + + uint j = (uint)x; + + l ^= u[j & 31]; + + ulong g, h = 0; + int k = 60; + do + { + j = (uint)(x >> k); + g = u[j & 31]; + l ^= (g << k); + h ^= (g >> -k); + } + while ((k -= 5) > 0); + + for (int p = 0; p < 4; ++p) + { + x = (x & RM) >> 1; + h ^= x & (ulong)(((long)y << p) >> 63); + } + + z[zOff + i] ^= l; + + l = h; + } + z[zOff + 9] ^= l; + } + + protected static void ImplSquare(ulong[] x, ulong[] zz) + { + for (int i = 0; i < 9; ++i) + { + Interleave.Expand64To128(x[i], zz, i << 1); + } + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT571FieldElement.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT571FieldElement.cs new file mode 100644 index 0000000..c43b8dc --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT571FieldElement.cs @@ -0,0 +1,216 @@ +using System; + +using Org.BouncyCastle.Math.Raw; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT571FieldElement + : ECFieldElement + { + protected readonly ulong[] x; + + public SecT571FieldElement(BigInteger x) + { + if (x == null || x.SignValue < 0 || x.BitLength > 571) + throw new ArgumentException("value invalid for SecT571FieldElement", "x"); + + this.x = SecT571Field.FromBigInteger(x); + } + + public SecT571FieldElement() + { + this.x = Nat576.Create64(); + } + + protected internal SecT571FieldElement(ulong[] x) + { + this.x = x; + } + + public override bool IsOne + { + get { return Nat576.IsOne64(x); } + } + + public override bool IsZero + { + get { return Nat576.IsZero64(x); } + } + + public override bool TestBitZero() + { + return (x[0] & 1UL) != 0UL; + } + + public override BigInteger ToBigInteger() + { + return Nat576.ToBigInteger64(x); + } + + public override String FieldName + { + get { return "SecT571Field"; } + } + + public override int FieldSize + { + get { return 571; } + } + + public override ECFieldElement Add(ECFieldElement b) + { + ulong[] z = Nat576.Create64(); + SecT571Field.Add(x, ((SecT571FieldElement)b).x, z); + return new SecT571FieldElement(z); + } + + public override ECFieldElement AddOne() + { + ulong[] z = Nat576.Create64(); + SecT571Field.AddOne(x, z); + return new SecT571FieldElement(z); + } + + public override ECFieldElement Subtract(ECFieldElement b) + { + // Addition and subtraction are the same in F2m + return Add(b); + } + + public override ECFieldElement Multiply(ECFieldElement b) + { + ulong[] z = Nat576.Create64(); + SecT571Field.Multiply(x, ((SecT571FieldElement)b).x, z); + return new SecT571FieldElement(z); + } + + public override ECFieldElement MultiplyMinusProduct(ECFieldElement b, ECFieldElement x, ECFieldElement y) + { + return MultiplyPlusProduct(b, x, y); + } + + public override ECFieldElement MultiplyPlusProduct(ECFieldElement b, ECFieldElement x, ECFieldElement y) + { + ulong[] ax = this.x, bx = ((SecT571FieldElement)b).x; + ulong[] xx = ((SecT571FieldElement)x).x, yx = ((SecT571FieldElement)y).x; + + ulong[] tt = Nat576.CreateExt64(); + SecT571Field.MultiplyAddToExt(ax, bx, tt); + SecT571Field.MultiplyAddToExt(xx, yx, tt); + + ulong[] z = Nat576.Create64(); + SecT571Field.Reduce(tt, z); + return new SecT571FieldElement(z); + } + + public override ECFieldElement Divide(ECFieldElement b) + { + return Multiply(b.Invert()); + } + + public override ECFieldElement Negate() + { + return this; + } + + public override ECFieldElement Square() + { + ulong[] z = Nat576.Create64(); + SecT571Field.Square(x, z); + return new SecT571FieldElement(z); + } + + public override ECFieldElement SquareMinusProduct(ECFieldElement x, ECFieldElement y) + { + return SquarePlusProduct(x, y); + } + + public override ECFieldElement SquarePlusProduct(ECFieldElement x, ECFieldElement y) + { + ulong[] ax = this.x; + ulong[] xx = ((SecT571FieldElement)x).x, yx = ((SecT571FieldElement)y).x; + + ulong[] tt = Nat576.CreateExt64(); + SecT571Field.SquareAddToExt(ax, tt); + SecT571Field.MultiplyAddToExt(xx, yx, tt); + + ulong[] z = Nat576.Create64(); + SecT571Field.Reduce(tt, z); + return new SecT571FieldElement(z); + } + + public override ECFieldElement SquarePow(int pow) + { + if (pow < 1) + return this; + + ulong[] z = Nat576.Create64(); + SecT571Field.SquareN(x, pow, z); + return new SecT571FieldElement(z); + } + + public override ECFieldElement Invert() + { + ulong[] z = Nat576.Create64(); + SecT571Field.Invert(x, z); + return new SecT571FieldElement(z); + } + + public override ECFieldElement Sqrt() + { + ulong[] z = Nat576.Create64(); + SecT571Field.Sqrt(x, z); + return new SecT571FieldElement(z); + } + + public virtual int Representation + { + get { return F2mFieldElement.Ppb; } + } + + public virtual int M + { + get { return 571; } + } + + public virtual int K1 + { + get { return 2; } + } + + public virtual int K2 + { + get { return 5; } + } + + public virtual int K3 + { + get { return 10; } + } + + public override bool Equals(object obj) + { + return Equals(obj as SecT571FieldElement); + } + + public override bool Equals(ECFieldElement other) + { + return Equals(other as SecT571FieldElement); + } + + public virtual bool Equals(SecT571FieldElement other) + { + if (this == other) + return true; + if (null == other) + return false; + return Nat576.Eq64(x, other.x); + } + + public override int GetHashCode() + { + return 5711052 ^ Arrays.GetHashCode(x, 0, 9); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT571K1Curve.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT571K1Curve.cs new file mode 100644 index 0000000..f5806f0 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT571K1Curve.cs @@ -0,0 +1,104 @@ +using System; + +using Org.BouncyCastle.Math.EC.Multiplier; +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT571K1Curve + : AbstractF2mCurve + { + private const int SecT571K1_DEFAULT_COORDS = COORD_LAMBDA_PROJECTIVE; + + protected readonly SecT571K1Point m_infinity; + + public SecT571K1Curve() + : base(571, 2, 5, 10) + { + this.m_infinity = new SecT571K1Point(this, null, null); + + this.m_a = FromBigInteger(BigInteger.Zero); + this.m_b = FromBigInteger(BigInteger.One); + this.m_order = new BigInteger(1, Hex.Decode("020000000000000000000000000000000000000000000000000000000000000000000000131850E1F19A63E4B391A8DB917F4138B630D84BE5D639381E91DEB45CFE778F637C1001")); + this.m_cofactor = BigInteger.ValueOf(4); + + this.m_coord = SecT571K1_DEFAULT_COORDS; + } + + protected override ECCurve CloneCurve() + { + return new SecT571K1Curve(); + } + + public override bool SupportsCoordinateSystem(int coord) + { + switch (coord) + { + case COORD_LAMBDA_PROJECTIVE: + return true; + default: + return false; + } + } + + protected override ECMultiplier CreateDefaultMultiplier() + { + return new WTauNafMultiplier(); + } + + public override ECPoint Infinity + { + get { return m_infinity; } + } + + public override int FieldSize + { + get { return 571; } + } + + public override ECFieldElement FromBigInteger(BigInteger x) + { + return new SecT571FieldElement(x); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, bool withCompression) + { + return new SecT571K1Point(this, x, y, withCompression); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + { + return new SecT571K1Point(this, x, y, zs, withCompression); + } + + public override bool IsKoblitz + { + get { return true; } + } + + public virtual int M + { + get { return 571; } + } + + public virtual bool IsTrinomial + { + get { return false; } + } + + public virtual int K1 + { + get { return 2; } + } + + public virtual int K2 + { + get { return 5; } + } + + public virtual int K3 + { + get { return 10; } + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT571K1Point.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT571K1Point.cs new file mode 100644 index 0000000..deaaf0c --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT571K1Point.cs @@ -0,0 +1,289 @@ +using System; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT571K1Point + : AbstractF2mPoint + { + /** + * @deprecated Use ECCurve.createPoint to construct points + */ + public SecT571K1Point(ECCurve curve, ECFieldElement x, ECFieldElement y) + : this(curve, x, y, false) + { + } + + /** + * @deprecated per-point compression property will be removed, refer {@link #getEncoded(bool)} + */ + public SecT571K1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, bool withCompression) + : base(curve, x, y, withCompression) + { + if ((x == null) != (y == null)) + throw new ArgumentException("Exactly one of the field elements is null"); + } + + internal SecT571K1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + : base(curve, x, y, zs, withCompression) + { + } + + protected override ECPoint Detach() + { + return new SecT571K1Point(null, this.AffineXCoord, this.AffineYCoord); // earlier JDK + } + + public override ECFieldElement YCoord + { + get + { + ECFieldElement X = RawXCoord, L = RawYCoord; + + if (this.IsInfinity || X.IsZero) + return L; + + // Y is actually Lambda (X + Y/X) here; convert to affine value on the fly + ECFieldElement Y = L.Add(X).Multiply(X); + + ECFieldElement Z = RawZCoords[0]; + if (!Z.IsOne) + { + Y = Y.Divide(Z); + } + + return Y; + } + } + + protected internal override bool CompressionYTilde + { + get + { + ECFieldElement X = this.RawXCoord; + if (X.IsZero) + return false; + + ECFieldElement Y = this.RawYCoord; + + // Y is actually Lambda (X + Y/X) here + return Y.TestBitZero() != X.TestBitZero(); + } + } + + public override ECPoint Add(ECPoint b) + { + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return this; + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + ECFieldElement X2 = b.RawXCoord; + + if (X1.IsZero) + { + if (X2.IsZero) + return curve.Infinity; + + return b.Add(this); + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + ECFieldElement L2 = b.RawYCoord, Z2 = b.RawZCoords[0]; + + bool Z1IsOne = Z1.IsOne; + ECFieldElement U2 = X2, S2 = L2; + if (!Z1IsOne) + { + U2 = U2.Multiply(Z1); + S2 = S2.Multiply(Z1); + } + + bool Z2IsOne = Z2.IsOne; + ECFieldElement U1 = X1, S1 = L1; + if (!Z2IsOne) + { + U1 = U1.Multiply(Z2); + S1 = S1.Multiply(Z2); + } + + ECFieldElement A = S1.Add(S2); + ECFieldElement B = U1.Add(U2); + + if (B.IsZero) + { + if (A.IsZero) + return Twice(); + + return curve.Infinity; + } + + ECFieldElement X3, L3, Z3; + if (X2.IsZero) + { + // TODO This can probably be optimized quite a bit + ECPoint p = this.Normalize(); + X1 = p.XCoord; + ECFieldElement Y1 = p.YCoord; + + ECFieldElement Y2 = L2; + ECFieldElement L = Y1.Add(Y2).Divide(X1); + + X3 = L.Square().Add(L).Add(X1); + if (X3.IsZero) + { + return new SecT571K1Point(curve, X3, curve.B, IsCompressed); + } + + ECFieldElement Y3 = L.Multiply(X1.Add(X3)).Add(X3).Add(Y1); + L3 = Y3.Divide(X3).Add(X3); + Z3 = curve.FromBigInteger(BigInteger.One); + } + else + { + B = B.Square(); + + ECFieldElement AU1 = A.Multiply(U1); + ECFieldElement AU2 = A.Multiply(U2); + + X3 = AU1.Multiply(AU2); + if (X3.IsZero) + { + return new SecT571K1Point(curve, X3, curve.B, IsCompressed); + } + + ECFieldElement ABZ2 = A.Multiply(B); + if (!Z2IsOne) + { + ABZ2 = ABZ2.Multiply(Z2); + } + + L3 = AU2.Add(B).SquarePlusProduct(ABZ2, L1.Add(Z1)); + + Z3 = ABZ2; + if (!Z1IsOne) + { + Z3 = Z3.Multiply(Z1); + } + } + + return new SecT571K1Point(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint Twice() + { + if (this.IsInfinity) + return this; + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + if (X1.IsZero) + { + // A point with X == 0 is it's own Additive inverse + return curve.Infinity; + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + + bool Z1IsOne = Z1.IsOne; + ECFieldElement Z1Sq = Z1IsOne ? Z1 : Z1.Square(); + ECFieldElement T; + if (Z1IsOne) + { + T = L1.Square().Add(L1); + } + else + { + T = L1.Add(Z1).Multiply(L1); + } + + if (T.IsZero) + { + return new SecT571K1Point(curve, T, curve.B, IsCompressed); + } + + ECFieldElement X3 = T.Square(); + ECFieldElement Z3 = Z1IsOne ? T : T.Multiply(Z1Sq); + + ECFieldElement t1 = L1.Add(X1).Square(); + ECFieldElement t2 = Z1IsOne ? Z1 : Z1Sq.Square(); + ECFieldElement L3 = t1.Add(T).Add(Z1Sq).Multiply(t1).Add(t2).Add(X3).Add(Z3); + + return new SecT571K1Point(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint TwicePlus(ECPoint b) + { + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return Twice(); + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + if (X1.IsZero) + { + // A point with X == 0 is it's own Additive inverse + return b; + } + + // NOTE: TwicePlus() only optimized for lambda-affine argument + ECFieldElement X2 = b.RawXCoord, Z2 = b.RawZCoords[0]; + if (X2.IsZero || !Z2.IsOne) + { + return Twice().Add(b); + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + ECFieldElement L2 = b.RawYCoord; + + ECFieldElement X1Sq = X1.Square(); + ECFieldElement L1Sq = L1.Square(); + ECFieldElement Z1Sq = Z1.Square(); + ECFieldElement L1Z1 = L1.Multiply(Z1); + + ECFieldElement T = L1Sq.Add(L1Z1); + ECFieldElement L2plus1 = L2.AddOne(); + ECFieldElement A = L2plus1.Multiply(Z1Sq).Add(L1Sq).MultiplyPlusProduct(T, X1Sq, Z1Sq); + ECFieldElement X2Z1Sq = X2.Multiply(Z1Sq); + ECFieldElement B = X2Z1Sq.Add(T).Square(); + + if (B.IsZero) + { + if (A.IsZero) + return b.Twice(); + + return curve.Infinity; + } + + if (A.IsZero) + { + return new SecT571K1Point(curve, A, curve.B, IsCompressed); + } + + ECFieldElement X3 = A.Square().Multiply(X2Z1Sq); + ECFieldElement Z3 = A.Multiply(B).Multiply(Z1Sq); + ECFieldElement L3 = A.Add(B).Square().MultiplyPlusProduct(T, L2plus1, Z3); + + return new SecT571K1Point(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint Negate() + { + if (this.IsInfinity) + return this; + + ECFieldElement X = this.RawXCoord; + if (X.IsZero) + return this; + + // L is actually Lambda (X + Y/X) here + ECFieldElement L = this.RawYCoord, Z = this.RawZCoords[0]; + return new SecT571K1Point(Curve, X, L.Add(Z), new ECFieldElement[] { Z }, IsCompressed); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT571R1Curve.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT571R1Curve.cs new file mode 100644 index 0000000..082afa5 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT571R1Curve.cs @@ -0,0 +1,102 @@ +using System; + +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT571R1Curve + : AbstractF2mCurve + { + private const int SecT571R1_DEFAULT_COORDS = COORD_LAMBDA_PROJECTIVE; + + protected readonly SecT571R1Point m_infinity; + + internal static readonly SecT571FieldElement SecT571R1_B = new SecT571FieldElement( + new BigInteger(1, Hex.Decode("02F40E7E2221F295DE297117B7F3D62F5C6A97FFCB8CEFF1CD6BA8CE4A9A18AD84FFABBD8EFA59332BE7AD6756A66E294AFD185A78FF12AA520E4DE739BACA0C7FFEFF7F2955727A"))); + internal static readonly SecT571FieldElement SecT571R1_B_SQRT = (SecT571FieldElement)SecT571R1_B.Sqrt(); + + public SecT571R1Curve() + : base(571, 2, 5, 10) + { + this.m_infinity = new SecT571R1Point(this, null, null); + + this.m_a = FromBigInteger(BigInteger.One); + this.m_b = SecT571R1_B; + this.m_order = new BigInteger(1, Hex.Decode("03FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE661CE18FF55987308059B186823851EC7DD9CA1161DE93D5174D66E8382E9BB2FE84E47")); + this.m_cofactor = BigInteger.Two; + + this.m_coord = SecT571R1_DEFAULT_COORDS; + } + + protected override ECCurve CloneCurve() + { + return new SecT571R1Curve(); + } + + public override bool SupportsCoordinateSystem(int coord) + { + switch (coord) + { + case COORD_LAMBDA_PROJECTIVE: + return true; + default: + return false; + } + } + + public override ECPoint Infinity + { + get { return m_infinity; } + } + + public override int FieldSize + { + get { return 571; } + } + + public override ECFieldElement FromBigInteger(BigInteger x) + { + return new SecT571FieldElement(x); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, bool withCompression) + { + return new SecT571R1Point(this, x, y, withCompression); + } + + protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + { + return new SecT571R1Point(this, x, y, zs, withCompression); + } + + public override bool IsKoblitz + { + get { return false; } + } + + public virtual int M + { + get { return 571; } + } + + public virtual bool IsTrinomial + { + get { return false; } + } + + public virtual int K1 + { + get { return 2; } + } + + public virtual int K2 + { + get { return 5; } + } + + public virtual int K3 + { + get { return 10; } + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/custom/sec/SecT571R1Point.cs b/bc-sharp-crypto/src/math/ec/custom/sec/SecT571R1Point.cs new file mode 100644 index 0000000..0d1fc98 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/custom/sec/SecT571R1Point.cs @@ -0,0 +1,278 @@ +using System; + +namespace Org.BouncyCastle.Math.EC.Custom.Sec +{ + internal class SecT571R1Point + : AbstractF2mPoint + { + /** + * @deprecated Use ECCurve.createPoint to construct points + */ + public SecT571R1Point(ECCurve curve, ECFieldElement x, ECFieldElement y) + : this(curve, x, y, false) + { + } + + /** + * @deprecated per-point compression property will be removed, refer {@link #getEncoded(bool)} + */ + public SecT571R1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, bool withCompression) + : base(curve, x, y, withCompression) + { + if ((x == null) != (y == null)) + throw new ArgumentException("Exactly one of the field elements is null"); + } + + internal SecT571R1Point(ECCurve curve, ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression) + : base(curve, x, y, zs, withCompression) + { + } + + protected override ECPoint Detach() + { + return new SecT571R1Point(null, AffineXCoord, AffineYCoord); + } + + public override ECFieldElement YCoord + { + get + { + ECFieldElement X = RawXCoord, L = RawYCoord; + + if (this.IsInfinity || X.IsZero) + return L; + + // Y is actually Lambda (X + Y/X) here; convert to affine value on the fly + ECFieldElement Y = L.Add(X).Multiply(X); + + ECFieldElement Z = RawZCoords[0]; + if (!Z.IsOne) + { + Y = Y.Divide(Z); + } + + return Y; + } + } + + protected internal override bool CompressionYTilde + { + get + { + ECFieldElement X = this.RawXCoord; + if (X.IsZero) + return false; + + ECFieldElement Y = this.RawYCoord; + + // Y is actually Lambda (X + Y/X) here + return Y.TestBitZero() != X.TestBitZero(); + } + } + + public override ECPoint Add(ECPoint b) + { + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return this; + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + ECFieldElement X2 = b.RawXCoord; + + if (X1.IsZero) + { + if (X2.IsZero) + return curve.Infinity; + + return b.Add(this); + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + ECFieldElement L2 = b.RawYCoord, Z2 = b.RawZCoords[0]; + + bool Z1IsOne = Z1.IsOne; + ECFieldElement U2 = X2, S2 = L2; + if (!Z1IsOne) + { + U2 = U2.Multiply(Z1); + S2 = S2.Multiply(Z1); + } + + bool Z2IsOne = Z2.IsOne; + ECFieldElement U1 = X1, S1 = L1; + if (!Z2IsOne) + { + U1 = U1.Multiply(Z2); + S1 = S1.Multiply(Z2); + } + + ECFieldElement A = S1.Add(S2); + ECFieldElement B = U1.Add(U2); + + if (B.IsZero) + { + if (A.IsZero) + return Twice(); + + return curve.Infinity; + } + + ECFieldElement X3, L3, Z3; + if (X2.IsZero) + { + // TODO This can probably be optimized quite a bit + ECPoint p = this.Normalize(); + X1 = p.XCoord; + ECFieldElement Y1 = p.YCoord; + + ECFieldElement Y2 = L2; + ECFieldElement L = Y1.Add(Y2).Divide(X1); + + X3 = L.Square().Add(L).Add(X1).AddOne(); + if (X3.IsZero) + { + return new SecT571R1Point(curve, X3, SecT571R1Curve.SecT571R1_B_SQRT, IsCompressed); + } + + ECFieldElement Y3 = L.Multiply(X1.Add(X3)).Add(X3).Add(Y1); + L3 = Y3.Divide(X3).Add(X3); + Z3 = curve.FromBigInteger(BigInteger.One); + } + else + { + B = B.Square(); + + ECFieldElement AU1 = A.Multiply(U1); + ECFieldElement AU2 = A.Multiply(U2); + + X3 = AU1.Multiply(AU2); + if (X3.IsZero) + { + return new SecT571R1Point(curve, X3, SecT571R1Curve.SecT571R1_B_SQRT, IsCompressed); + } + + ECFieldElement ABZ2 = A.Multiply(B); + if (!Z2IsOne) + { + ABZ2 = ABZ2.Multiply(Z2); + } + + L3 = AU2.Add(B).SquarePlusProduct(ABZ2, L1.Add(Z1)); + + Z3 = ABZ2; + if (!Z1IsOne) + { + Z3 = Z3.Multiply(Z1); + } + } + + return new SecT571R1Point(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint Twice() + { + if (this.IsInfinity) + return this; + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + if (X1.IsZero) + { + // A point with X == 0 is it's own Additive inverse + return curve.Infinity; + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + + bool Z1IsOne = Z1.IsOne; + ECFieldElement L1Z1 = Z1IsOne ? L1 : L1.Multiply(Z1); + ECFieldElement Z1Sq = Z1IsOne ? Z1 : Z1.Square(); + ECFieldElement T = L1.Square().Add(L1Z1).Add(Z1Sq); + if (T.IsZero) + { + return new SecT571R1Point(curve, T, SecT571R1Curve.SecT571R1_B_SQRT, IsCompressed); + } + + ECFieldElement X3 = T.Square(); + ECFieldElement Z3 = Z1IsOne ? T : T.Multiply(Z1Sq); + + ECFieldElement X1Z1 = Z1IsOne ? X1 : X1.Multiply(Z1); + ECFieldElement L3 = X1Z1.SquarePlusProduct(T, L1Z1).Add(X3).Add(Z3); + + return new SecT571R1Point(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint TwicePlus(ECPoint b) + { + if (this.IsInfinity) + return b; + if (b.IsInfinity) + return Twice(); + + ECCurve curve = this.Curve; + + ECFieldElement X1 = this.RawXCoord; + if (X1.IsZero) + { + // A point with X == 0 is it's own Additive inverse + return b; + } + + ECFieldElement X2 = b.RawXCoord, Z2 = b.RawZCoords[0]; + if (X2.IsZero || !Z2.IsOne) + { + return Twice().Add(b); + } + + ECFieldElement L1 = this.RawYCoord, Z1 = this.RawZCoords[0]; + ECFieldElement L2 = b.RawYCoord; + + ECFieldElement X1Sq = X1.Square(); + ECFieldElement L1Sq = L1.Square(); + ECFieldElement Z1Sq = Z1.Square(); + ECFieldElement L1Z1 = L1.Multiply(Z1); + + ECFieldElement T = Z1Sq.Add(L1Sq).Add(L1Z1); + ECFieldElement A = L2.Multiply(Z1Sq).Add(L1Sq).MultiplyPlusProduct(T, X1Sq, Z1Sq); + ECFieldElement X2Z1Sq = X2.Multiply(Z1Sq); + ECFieldElement B = X2Z1Sq.Add(T).Square(); + + if (B.IsZero) + { + if (A.IsZero) + return b.Twice(); + + return curve.Infinity; + } + + if (A.IsZero) + { + return new SecT571R1Point(curve, A, SecT571R1Curve.SecT571R1_B_SQRT, IsCompressed); + } + + ECFieldElement X3 = A.Square().Multiply(X2Z1Sq); + ECFieldElement Z3 = A.Multiply(B).Multiply(Z1Sq); + ECFieldElement L3 = A.Add(B).Square().MultiplyPlusProduct(T, L2.AddOne(), Z3); + + return new SecT571R1Point(curve, X3, L3, new ECFieldElement[] { Z3 }, IsCompressed); + } + + public override ECPoint Negate() + { + if (this.IsInfinity) + return this; + + ECFieldElement X = this.RawXCoord; + if (X.IsZero) + return this; + + // L is actually Lambda (X + Y/X) here + ECFieldElement L = this.RawYCoord, Z = this.RawZCoords[0]; + return new SecT571R1Point(Curve, X, L.Add(Z), new ECFieldElement[] { Z }, IsCompressed); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/endo/ECEndomorphism.cs b/bc-sharp-crypto/src/math/ec/endo/ECEndomorphism.cs new file mode 100644 index 0000000..dfb3213 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/endo/ECEndomorphism.cs @@ -0,0 +1,11 @@ +using System; + +namespace Org.BouncyCastle.Math.EC.Endo +{ + public interface ECEndomorphism + { + ECPointMap PointMap { get; } + + bool HasEfficientPointMap { get; } + } +} diff --git a/bc-sharp-crypto/src/math/ec/endo/GlvEndomorphism.cs b/bc-sharp-crypto/src/math/ec/endo/GlvEndomorphism.cs new file mode 100644 index 0000000..f65bdd6 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/endo/GlvEndomorphism.cs @@ -0,0 +1,10 @@ +using System; + +namespace Org.BouncyCastle.Math.EC.Endo +{ + public interface GlvEndomorphism + : ECEndomorphism + { + BigInteger[] DecomposeScalar(BigInteger k); + } +} diff --git a/bc-sharp-crypto/src/math/ec/endo/GlvTypeBEndomorphism.cs b/bc-sharp-crypto/src/math/ec/endo/GlvTypeBEndomorphism.cs new file mode 100644 index 0000000..d234d88 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/endo/GlvTypeBEndomorphism.cs @@ -0,0 +1,55 @@ +using System; + +namespace Org.BouncyCastle.Math.EC.Endo +{ + public class GlvTypeBEndomorphism + : GlvEndomorphism + { + protected readonly ECCurve m_curve; + protected readonly GlvTypeBParameters m_parameters; + protected readonly ECPointMap m_pointMap; + + public GlvTypeBEndomorphism(ECCurve curve, GlvTypeBParameters parameters) + { + this.m_curve = curve; + this.m_parameters = parameters; + this.m_pointMap = new ScaleXPointMap(curve.FromBigInteger(parameters.Beta)); + } + + public virtual BigInteger[] DecomposeScalar(BigInteger k) + { + int bits = m_parameters.Bits; + BigInteger b1 = CalculateB(k, m_parameters.G1, bits); + BigInteger b2 = CalculateB(k, m_parameters.G2, bits); + + BigInteger[] v1 = m_parameters.V1, v2 = m_parameters.V2; + BigInteger a = k.Subtract((b1.Multiply(v1[0])).Add(b2.Multiply(v2[0]))); + BigInteger b = (b1.Multiply(v1[1])).Add(b2.Multiply(v2[1])).Negate(); + + return new BigInteger[]{ a, b }; + } + + public virtual ECPointMap PointMap + { + get { return m_pointMap; } + } + + public virtual bool HasEfficientPointMap + { + get { return true; } + } + + protected virtual BigInteger CalculateB(BigInteger k, BigInteger g, int t) + { + bool negative = (g.SignValue < 0); + BigInteger b = k.Multiply(g.Abs()); + bool extra = b.TestBit(t - 1); + b = b.ShiftRight(t); + if (extra) + { + b = b.Add(BigInteger.One); + } + return negative ? b.Negate() : b; + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/endo/GlvTypeBParameters.cs b/bc-sharp-crypto/src/math/ec/endo/GlvTypeBParameters.cs new file mode 100644 index 0000000..f93dfaf --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/endo/GlvTypeBParameters.cs @@ -0,0 +1,60 @@ +using System; + +namespace Org.BouncyCastle.Math.EC.Endo +{ + public class GlvTypeBParameters + { + protected readonly BigInteger m_beta; + protected readonly BigInteger m_lambda; + protected readonly BigInteger[] m_v1, m_v2; + protected readonly BigInteger m_g1, m_g2; + protected readonly int m_bits; + + public GlvTypeBParameters(BigInteger beta, BigInteger lambda, BigInteger[] v1, BigInteger[] v2, + BigInteger g1, BigInteger g2, int bits) + { + this.m_beta = beta; + this.m_lambda = lambda; + this.m_v1 = v1; + this.m_v2 = v2; + this.m_g1 = g1; + this.m_g2 = g2; + this.m_bits = bits; + } + + public virtual BigInteger Beta + { + get { return m_beta; } + } + + public virtual BigInteger Lambda + { + get { return m_lambda; } + } + + public virtual BigInteger[] V1 + { + get { return m_v1; } + } + + public virtual BigInteger[] V2 + { + get { return m_v2; } + } + + public virtual BigInteger G1 + { + get { return m_g1; } + } + + public virtual BigInteger G2 + { + get { return m_g2; } + } + + public virtual int Bits + { + get { return m_bits; } + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/multiplier/AbstractECMultiplier.cs b/bc-sharp-crypto/src/math/ec/multiplier/AbstractECMultiplier.cs new file mode 100644 index 0000000..5178813 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/multiplier/AbstractECMultiplier.cs @@ -0,0 +1,24 @@ +namespace Org.BouncyCastle.Math.EC.Multiplier +{ + public abstract class AbstractECMultiplier + : ECMultiplier + { + public virtual ECPoint Multiply(ECPoint p, BigInteger k) + { + int sign = k.SignValue; + if (sign == 0 || p.IsInfinity) + return p.Curve.Infinity; + + ECPoint positive = MultiplyPositive(p, k.Abs()); + ECPoint result = sign > 0 ? positive : positive.Negate(); + + /* + * Although the various multipliers ought not to produce invalid output under normal + * circumstances, a final check here is advised to guard against fault attacks. + */ + return ECAlgorithms.ValidatePoint(result); + } + + protected abstract ECPoint MultiplyPositive(ECPoint p, BigInteger k); + } +} diff --git a/bc-sharp-crypto/src/math/ec/multiplier/DoubleAddMultiplier.cs b/bc-sharp-crypto/src/math/ec/multiplier/DoubleAddMultiplier.cs new file mode 100644 index 0000000..18a72c0 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/multiplier/DoubleAddMultiplier.cs @@ -0,0 +1,24 @@ +namespace Org.BouncyCastle.Math.EC.Multiplier +{ + public class DoubleAddMultiplier + : AbstractECMultiplier + { + /** + * Joye's double-add algorithm. + */ + protected override ECPoint MultiplyPositive(ECPoint p, BigInteger k) + { + ECPoint[] R = new ECPoint[]{ p.Curve.Infinity, p }; + + int n = k.BitLength; + for (int i = 0; i < n; ++i) + { + int b = k.TestBit(i) ? 1 : 0; + int bp = 1 - b; + R[bp] = R[bp].TwicePlus(R[b]); + } + + return R[0]; + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/multiplier/ECMultiplier.cs b/bc-sharp-crypto/src/math/ec/multiplier/ECMultiplier.cs new file mode 100644 index 0000000..8d6136b --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/multiplier/ECMultiplier.cs @@ -0,0 +1,18 @@ +namespace Org.BouncyCastle.Math.EC.Multiplier +{ + /** + * Interface for classes encapsulating a point multiplication algorithm + * for ECPoints. + */ + public 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 is multiplied. + * @return p multiplied by k. + */ + ECPoint Multiply(ECPoint p, BigInteger k); + } +} diff --git a/bc-sharp-crypto/src/math/ec/multiplier/FixedPointCombMultiplier.cs b/bc-sharp-crypto/src/math/ec/multiplier/FixedPointCombMultiplier.cs new file mode 100644 index 0000000..a8ef5a7 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/multiplier/FixedPointCombMultiplier.cs @@ -0,0 +1,59 @@ +using System; + +namespace Org.BouncyCastle.Math.EC.Multiplier +{ + public class FixedPointCombMultiplier + : AbstractECMultiplier + { + protected override ECPoint MultiplyPositive(ECPoint p, BigInteger k) + { + ECCurve c = p.Curve; + int size = FixedPointUtilities.GetCombSize(c); + + if (k.BitLength > size) + { + /* + * TODO The comb works best when the scalars are less than the (possibly unknown) order. + * Still, if we want to handle larger scalars, we could allow customization of the comb + * size, or alternatively we could deal with the 'extra' bits either by running the comb + * multiple times as necessary, or by using an alternative multiplier as prelude. + */ + throw new InvalidOperationException("fixed-point comb doesn't support scalars larger than the curve order"); + } + + int minWidth = GetWidthForCombSize(size); + + FixedPointPreCompInfo info = FixedPointUtilities.Precompute(p, minWidth); + ECPoint[] lookupTable = info.PreComp; + int width = info.Width; + + int d = (size + width - 1) / width; + + ECPoint R = c.Infinity; + + int top = d * width - 1; + for (int i = 0; i < d; ++i) + { + int index = 0; + + for (int j = top - i; j >= 0; j -= d) + { + index <<= 1; + if (k.TestBit(j)) + { + index |= 1; + } + } + + R = R.TwicePlus(lookupTable[index]); + } + + return R; + } + + protected virtual int GetWidthForCombSize(int combSize) + { + return combSize > 257 ? 6 : 5; + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/multiplier/FixedPointPreCompInfo.cs b/bc-sharp-crypto/src/math/ec/multiplier/FixedPointPreCompInfo.cs new file mode 100644 index 0000000..56a6326 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/multiplier/FixedPointPreCompInfo.cs @@ -0,0 +1,34 @@ +namespace Org.BouncyCastle.Math.EC.Multiplier +{ + /** + * Class holding precomputation data for fixed-point multiplications. + */ + public class FixedPointPreCompInfo + : PreCompInfo + { + /** + * Array holding the precomputed ECPoints used for a fixed + * point multiplication. + */ + protected ECPoint[] m_preComp = null; + + /** + * The width used for the precomputation. If a larger width precomputation + * is already available this may be larger than was requested, so calling + * code should refer to the actual width. + */ + protected int m_width = -1; + + public virtual ECPoint[] PreComp + { + get { return m_preComp; } + set { this.m_preComp = value; } + } + + public virtual int Width + { + get { return m_width; } + set { this.m_width = value; } + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/multiplier/FixedPointUtilities.cs b/bc-sharp-crypto/src/math/ec/multiplier/FixedPointUtilities.cs new file mode 100644 index 0000000..d927d01 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/multiplier/FixedPointUtilities.cs @@ -0,0 +1,72 @@ +using System; + +namespace Org.BouncyCastle.Math.EC.Multiplier +{ + public class FixedPointUtilities + { + public static readonly string PRECOMP_NAME = "bc_fixed_point"; + + public static int GetCombSize(ECCurve c) + { + BigInteger order = c.Order; + return order == null ? c.FieldSize + 1 : order.BitLength; + } + + public static FixedPointPreCompInfo GetFixedPointPreCompInfo(PreCompInfo preCompInfo) + { + if ((preCompInfo != null) && (preCompInfo is FixedPointPreCompInfo)) + { + return (FixedPointPreCompInfo)preCompInfo; + } + + return new FixedPointPreCompInfo(); + } + + public static FixedPointPreCompInfo Precompute(ECPoint p, int minWidth) + { + ECCurve c = p.Curve; + + int n = 1 << minWidth; + FixedPointPreCompInfo info = GetFixedPointPreCompInfo(c.GetPreCompInfo(p, PRECOMP_NAME)); + ECPoint[] lookupTable = info.PreComp; + + if (lookupTable == null || lookupTable.Length < n) + { + int bits = GetCombSize(c); + int d = (bits + minWidth - 1) / minWidth; + + ECPoint[] pow2Table = new ECPoint[minWidth]; + pow2Table[0] = p; + for (int i = 1; i < minWidth; ++i) + { + pow2Table[i] = pow2Table[i - 1].TimesPow2(d); + } + + c.NormalizeAll(pow2Table); + + lookupTable = new ECPoint[n]; + lookupTable[0] = c.Infinity; + + for (int bit = minWidth - 1; bit >= 0; --bit) + { + ECPoint pow2 = pow2Table[bit]; + + int step = 1 << bit; + for (int i = step; i < n; i += (step << 1)) + { + lookupTable[i] = lookupTable[i - step].Add(pow2); + } + } + + c.NormalizeAll(lookupTable); + + info.PreComp = lookupTable; + info.Width = minWidth; + + c.SetPreCompInfo(p, PRECOMP_NAME, info); + } + + return info; + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/multiplier/GlvMultiplier.cs b/bc-sharp-crypto/src/math/ec/multiplier/GlvMultiplier.cs new file mode 100644 index 0000000..f190494 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/multiplier/GlvMultiplier.cs @@ -0,0 +1,40 @@ +using System; + +using Org.BouncyCastle.Math.EC.Endo; + +namespace Org.BouncyCastle.Math.EC.Multiplier +{ + public class GlvMultiplier + : AbstractECMultiplier + { + protected readonly ECCurve curve; + protected readonly GlvEndomorphism glvEndomorphism; + + public GlvMultiplier(ECCurve curve, GlvEndomorphism glvEndomorphism) + { + if (curve == null || curve.Order == null) + throw new ArgumentException("Need curve with known group order", "curve"); + + this.curve = curve; + this.glvEndomorphism = glvEndomorphism; + } + + protected override ECPoint MultiplyPositive(ECPoint p, BigInteger k) + { + if (!curve.Equals(p.Curve)) + throw new InvalidOperationException(); + + BigInteger n = p.Curve.Order; + BigInteger[] ab = glvEndomorphism.DecomposeScalar(k.Mod(n)); + BigInteger a = ab[0], b = ab[1]; + + ECPointMap pointMap = glvEndomorphism.PointMap; + if (glvEndomorphism.HasEfficientPointMap) + { + return ECAlgorithms.ImplShamirsTrickWNaf(p, a, pointMap, b); + } + + return ECAlgorithms.ImplShamirsTrickWNaf(p, a, pointMap.Map(p), b); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/multiplier/MixedNafR2LMultiplier.cs b/bc-sharp-crypto/src/math/ec/multiplier/MixedNafR2LMultiplier.cs new file mode 100644 index 0000000..a4c2018 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/multiplier/MixedNafR2LMultiplier.cs @@ -0,0 +1,75 @@ +using System; + +namespace Org.BouncyCastle.Math.EC.Multiplier +{ + /** + * Class implementing the NAF (Non-Adjacent Form) multiplication algorithm (right-to-left) using + * mixed coordinates. + */ + public class MixedNafR2LMultiplier + : AbstractECMultiplier + { + protected readonly int additionCoord, doublingCoord; + + /** + * By default, addition will be done in Jacobian coordinates, and doubling will be done in + * Modified Jacobian coordinates (independent of the original coordinate system of each point). + */ + public MixedNafR2LMultiplier() + : this(ECCurve.COORD_JACOBIAN, ECCurve.COORD_JACOBIAN_MODIFIED) + { + } + + public MixedNafR2LMultiplier(int additionCoord, int doublingCoord) + { + this.additionCoord = additionCoord; + this.doublingCoord = doublingCoord; + } + + protected override ECPoint MultiplyPositive(ECPoint p, BigInteger k) + { + ECCurve curveOrig = p.Curve; + + ECCurve curveAdd = ConfigureCurve(curveOrig, additionCoord); + ECCurve curveDouble = ConfigureCurve(curveOrig, doublingCoord); + + int[] naf = WNafUtilities.GenerateCompactNaf(k); + + ECPoint Ra = curveAdd.Infinity; + ECPoint Td = curveDouble.ImportPoint(p); + + int zeroes = 0; + for (int i = 0; i < naf.Length; ++i) + { + int ni = naf[i]; + int digit = ni >> 16; + zeroes += ni & 0xFFFF; + + Td = Td.TimesPow2(zeroes); + + ECPoint Tj = curveAdd.ImportPoint(Td); + if (digit < 0) + { + Tj = Tj.Negate(); + } + + Ra = Ra.Add(Tj); + + zeroes = 1; + } + + return curveOrig.ImportPoint(Ra); + } + + protected virtual ECCurve ConfigureCurve(ECCurve c, int coord) + { + if (c.CoordinateSystem == coord) + return c; + + if (!c.SupportsCoordinateSystem(coord)) + throw new ArgumentException("Coordinate system " + coord + " not supported by this curve", "coord"); + + return c.Configure().SetCoordinateSystem(coord).Create(); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/multiplier/MontgomeryLadderMultiplier.cs b/bc-sharp-crypto/src/math/ec/multiplier/MontgomeryLadderMultiplier.cs new file mode 100644 index 0000000..e2470a3 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/multiplier/MontgomeryLadderMultiplier.cs @@ -0,0 +1,25 @@ +namespace Org.BouncyCastle.Math.EC.Multiplier +{ + public class MontgomeryLadderMultiplier + : AbstractECMultiplier + { + /** + * Montgomery ladder. + */ + protected override ECPoint MultiplyPositive(ECPoint p, BigInteger k) + { + ECPoint[] R = new ECPoint[]{ p.Curve.Infinity, p }; + + int n = k.BitLength; + int i = n; + while (--i >= 0) + { + int b = k.TestBit(i) ? 1 : 0; + int bp = 1 - b; + R[bp] = R[bp].Add(R[b]); + R[b] = R[b].Twice(); + } + return R[0]; + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/multiplier/NafL2RMultiplier.cs b/bc-sharp-crypto/src/math/ec/multiplier/NafL2RMultiplier.cs new file mode 100644 index 0000000..ac80cf9 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/multiplier/NafL2RMultiplier.cs @@ -0,0 +1,30 @@ +namespace Org.BouncyCastle.Math.EC.Multiplier +{ + /** + * Class implementing the NAF (Non-Adjacent Form) multiplication algorithm (left-to-right). + */ + public class NafL2RMultiplier + : AbstractECMultiplier + { + protected override ECPoint MultiplyPositive(ECPoint p, BigInteger k) + { + int[] naf = WNafUtilities.GenerateCompactNaf(k); + + ECPoint addP = p.Normalize(), subP = addP.Negate(); + + ECPoint R = p.Curve.Infinity; + + int i = naf.Length; + while (--i >= 0) + { + int ni = naf[i]; + int digit = ni >> 16, zeroes = ni & 0xFFFF; + + R = R.TwicePlus(digit < 0 ? subP : addP); + R = R.TimesPow2(zeroes); + } + + return R; + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/multiplier/NafR2LMultiplier.cs b/bc-sharp-crypto/src/math/ec/multiplier/NafR2LMultiplier.cs new file mode 100644 index 0000000..1fa69fa --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/multiplier/NafR2LMultiplier.cs @@ -0,0 +1,31 @@ +namespace Org.BouncyCastle.Math.EC.Multiplier +{ + /** + * Class implementing the NAF (Non-Adjacent Form) multiplication algorithm (right-to-left). + */ + public class NafR2LMultiplier + : AbstractECMultiplier + { + protected override ECPoint MultiplyPositive(ECPoint p, BigInteger k) + { + int[] naf = WNafUtilities.GenerateCompactNaf(k); + + ECPoint R0 = p.Curve.Infinity, R1 = p; + + int zeroes = 0; + for (int i = 0; i < naf.Length; ++i) + { + int ni = naf[i]; + int digit = ni >> 16; + zeroes += ni & 0xFFFF; + + R1 = R1.TimesPow2(zeroes); + R0 = R0.Add(digit < 0 ? R1.Negate() : R1); + + zeroes = 1; + } + + return R0; + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/multiplier/PreCompInfo.cs b/bc-sharp-crypto/src/math/ec/multiplier/PreCompInfo.cs new file mode 100644 index 0000000..5c32892 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/multiplier/PreCompInfo.cs @@ -0,0 +1,11 @@ +namespace Org.BouncyCastle.Math.EC.Multiplier +{ + /** + * Interface for classes storing precomputation data for multiplication + * algorithms. Used as a Memento (see GOF patterns) for + * WNafMultiplier. + */ + public interface PreCompInfo + { + } +} diff --git a/bc-sharp-crypto/src/math/ec/multiplier/ReferenceMultiplier.cs b/bc-sharp-crypto/src/math/ec/multiplier/ReferenceMultiplier.cs new file mode 100644 index 0000000..4848ada --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/multiplier/ReferenceMultiplier.cs @@ -0,0 +1,11 @@ +namespace Org.BouncyCastle.Math.EC.Multiplier +{ + public class ReferenceMultiplier + : AbstractECMultiplier + { + protected override ECPoint MultiplyPositive(ECPoint p, BigInteger k) + { + return ECAlgorithms.ReferenceMultiply(p, k); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/multiplier/WNafL2RMultiplier.cs b/bc-sharp-crypto/src/math/ec/multiplier/WNafL2RMultiplier.cs new file mode 100644 index 0000000..f671f6a --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/multiplier/WNafL2RMultiplier.cs @@ -0,0 +1,98 @@ +using System; + +namespace Org.BouncyCastle.Math.EC.Multiplier +{ + /** + * Class implementing the WNAF (Window Non-Adjacent Form) multiplication + * algorithm. + */ + public class WNafL2RMultiplier + : AbstractECMultiplier + { + /** + * 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. + */ + protected override ECPoint MultiplyPositive(ECPoint p, BigInteger k) + { + // Clamp the window width in the range [2, 16] + int width = System.Math.Max(2, System.Math.Min(16, GetWindowSize(k.BitLength))); + + WNafPreCompInfo wnafPreCompInfo = WNafUtilities.Precompute(p, width, true); + ECPoint[] preComp = wnafPreCompInfo.PreComp; + ECPoint[] preCompNeg = wnafPreCompInfo.PreCompNeg; + + int[] wnaf = WNafUtilities.GenerateCompactWindowNaf(width, k); + + ECPoint R = p.Curve.Infinity; + + int i = wnaf.Length; + + /* + * NOTE: We try to optimize the first window using the precomputed points to substitute an + * addition for 2 or more doublings. + */ + if (i > 1) + { + int wi = wnaf[--i]; + int digit = wi >> 16, zeroes = wi & 0xFFFF; + + int n = System.Math.Abs(digit); + ECPoint[] table = digit < 0 ? preCompNeg : preComp; + + // Optimization can only be used for values in the lower half of the table + if ((n << 2) < (1 << width)) + { + int highest = LongArray.BitLengths[n]; + + // TODO Get addition/doubling cost ratio from curve and compare to 'scale' to see if worth substituting? + int scale = width - highest; + int lowBits = n ^ (1 << (highest - 1)); + + int i1 = ((1 << (width - 1)) - 1); + int i2 = (lowBits << scale) + 1; + R = table[i1 >> 1].Add(table[i2 >> 1]); + + zeroes -= scale; + + //Console.WriteLine("Optimized: 2^" + scale + " * " + n + " = " + i1 + " + " + i2); + } + else + { + R = table[n >> 1]; + } + + R = R.TimesPow2(zeroes); + } + + while (i > 0) + { + int wi = wnaf[--i]; + int digit = wi >> 16, zeroes = wi & 0xFFFF; + + int n = System.Math.Abs(digit); + ECPoint[] table = digit < 0 ? preCompNeg : preComp; + ECPoint r = table[n >> 1]; + + R = R.TwicePlus(r); + R = R.TimesPow2(zeroes); + } + + return R; + } + + /** + * Determine window width to use for a scalar multiplication of the given size. + * + * @param bits the bit-length of the scalar to multiply by + * @return the window size to use + */ + protected virtual int GetWindowSize(int bits) + { + return WNafUtilities.GetWindowSize(bits); + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/multiplier/WNafPreCompInfo.cs b/bc-sharp-crypto/src/math/ec/multiplier/WNafPreCompInfo.cs new file mode 100644 index 0000000..7e0a731 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/multiplier/WNafPreCompInfo.cs @@ -0,0 +1,46 @@ +namespace Org.BouncyCastle.Math.EC.Multiplier +{ + /** + * Class holding precomputation data for the WNAF (Window Non-Adjacent Form) + * algorithm. + */ + public class WNafPreCompInfo + : PreCompInfo + { + /** + * Array holding the precomputed ECPoints used for a Window + * NAF multiplication. + */ + protected ECPoint[] m_preComp = null; + + /** + * Array holding the negations of the precomputed ECPoints used + * for a Window NAF multiplication. + */ + protected ECPoint[] m_preCompNeg = null; + + /** + * Holds an ECPoint representing Twice(this). Used for the + * Window NAF multiplication to create or extend the precomputed values. + */ + protected ECPoint m_twice = null; + + public virtual ECPoint[] PreComp + { + get { return m_preComp; } + set { this.m_preComp = value; } + } + + public virtual ECPoint[] PreCompNeg + { + get { return m_preCompNeg; } + set { this.m_preCompNeg = value; } + } + + public virtual ECPoint Twice + { + get { return m_twice; } + set { this.m_twice = value; } + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/multiplier/WNafUtilities.cs b/bc-sharp-crypto/src/math/ec/multiplier/WNafUtilities.cs new file mode 100644 index 0000000..7d565df --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/multiplier/WNafUtilities.cs @@ -0,0 +1,524 @@ +using System; + +namespace Org.BouncyCastle.Math.EC.Multiplier +{ + public abstract class WNafUtilities + { + public static readonly string PRECOMP_NAME = "bc_wnaf"; + + private static readonly int[] DEFAULT_WINDOW_SIZE_CUTOFFS = new int[]{ 13, 41, 121, 337, 897, 2305 }; + + private static readonly byte[] EMPTY_BYTES = new byte[0]; + private static readonly int[] EMPTY_INTS = new int[0]; + private static readonly ECPoint[] EMPTY_POINTS = new ECPoint[0]; + + public static int[] GenerateCompactNaf(BigInteger k) + { + if ((k.BitLength >> 16) != 0) + throw new ArgumentException("must have bitlength < 2^16", "k"); + if (k.SignValue == 0) + return EMPTY_INTS; + + BigInteger _3k = k.ShiftLeft(1).Add(k); + + int bits = _3k.BitLength; + int[] naf = new int[bits >> 1]; + + BigInteger diff = _3k.Xor(k); + + int highBit = bits - 1, length = 0, zeroes = 0; + for (int i = 1; i < highBit; ++i) + { + if (!diff.TestBit(i)) + { + ++zeroes; + continue; + } + + int digit = k.TestBit(i) ? -1 : 1; + naf[length++] = (digit << 16) | zeroes; + zeroes = 1; + ++i; + } + + naf[length++] = (1 << 16) | zeroes; + + if (naf.Length > length) + { + naf = Trim(naf, length); + } + + return naf; + } + + public static int[] GenerateCompactWindowNaf(int width, BigInteger k) + { + if (width == 2) + { + return GenerateCompactNaf(k); + } + + if (width < 2 || width > 16) + throw new ArgumentException("must be in the range [2, 16]", "width"); + if ((k.BitLength >> 16) != 0) + throw new ArgumentException("must have bitlength < 2^16", "k"); + if (k.SignValue == 0) + return EMPTY_INTS; + + int[] wnaf = new int[k.BitLength / width + 1]; + + // 2^width and a mask and sign bit set accordingly + int pow2 = 1 << width; + int mask = pow2 - 1; + int sign = pow2 >> 1; + + bool carry = false; + int length = 0, pos = 0; + + while (pos <= k.BitLength) + { + if (k.TestBit(pos) == carry) + { + ++pos; + continue; + } + + k = k.ShiftRight(pos); + + int digit = k.IntValue & mask; + if (carry) + { + ++digit; + } + + carry = (digit & sign) != 0; + if (carry) + { + digit -= pow2; + } + + int zeroes = length > 0 ? pos - 1 : pos; + wnaf[length++] = (digit << 16) | zeroes; + pos = width; + } + + // Reduce the WNAF array to its actual length + if (wnaf.Length > length) + { + wnaf = Trim(wnaf, length); + } + + return wnaf; + } + + public static byte[] GenerateJsf(BigInteger g, BigInteger h) + { + int digits = System.Math.Max(g.BitLength, h.BitLength) + 1; + byte[] jsf = new byte[digits]; + + BigInteger k0 = g, k1 = h; + int j = 0, d0 = 0, d1 = 0; + + int offset = 0; + while ((d0 | d1) != 0 || k0.BitLength > offset || k1.BitLength > offset) + { + int n0 = ((int)((uint)k0.IntValue >> offset) + d0) & 7; + int n1 = ((int)((uint)k1.IntValue >> offset) + d1) & 7; + + int u0 = n0 & 1; + if (u0 != 0) + { + u0 -= (n0 & 2); + if ((n0 + u0) == 4 && (n1 & 3) == 2) + { + u0 = -u0; + } + } + + int u1 = n1 & 1; + if (u1 != 0) + { + u1 -= (n1 & 2); + if ((n1 + u1) == 4 && (n0 & 3) == 2) + { + u1 = -u1; + } + } + + if ((d0 << 1) == 1 + u0) + { + d0 ^= 1; + } + if ((d1 << 1) == 1 + u1) + { + d1 ^= 1; + } + + if (++offset == 30) + { + offset = 0; + k0 = k0.ShiftRight(30); + k1 = k1.ShiftRight(30); + } + + jsf[j++] = (byte)((u0 << 4) | (u1 & 0xF)); + } + + // Reduce the JSF array to its actual length + if (jsf.Length > j) + { + jsf = Trim(jsf, j); + } + + return jsf; + } + + public static byte[] GenerateNaf(BigInteger k) + { + if (k.SignValue == 0) + return EMPTY_BYTES; + + BigInteger _3k = k.ShiftLeft(1).Add(k); + + int digits = _3k.BitLength - 1; + byte[] naf = new byte[digits]; + + BigInteger diff = _3k.Xor(k); + + for (int i = 1; i < digits; ++i) + { + if (diff.TestBit(i)) + { + naf[i - 1] = (byte)(k.TestBit(i) ? -1 : 1); + ++i; + } + } + + naf[digits - 1] = 1; + + return naf; + } + + /** + * 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 = &sum;i=0l-1 ki2i + * , where the ki denote the elements of the + * returned byte[]. + */ + public static byte[] GenerateWindowNaf(int width, BigInteger k) + { + if (width == 2) + { + return GenerateNaf(k); + } + + if (width < 2 || width > 8) + throw new ArgumentException("must be in the range [2, 8]", "width"); + if (k.SignValue == 0) + return EMPTY_BYTES; + + byte[] wnaf = new byte[k.BitLength + 1]; + + // 2^width and a mask and sign bit set accordingly + int pow2 = 1 << width; + int mask = pow2 - 1; + int sign = pow2 >> 1; + + bool carry = false; + int length = 0, pos = 0; + + while (pos <= k.BitLength) + { + if (k.TestBit(pos) == carry) + { + ++pos; + continue; + } + + k = k.ShiftRight(pos); + + int digit = k.IntValue & mask; + if (carry) + { + ++digit; + } + + carry = (digit & sign) != 0; + if (carry) + { + digit -= pow2; + } + + length += (length > 0) ? pos - 1 : pos; + wnaf[length++] = (byte)digit; + pos = width; + } + + // Reduce the WNAF array to its actual length + if (wnaf.Length > length) + { + wnaf = Trim(wnaf, length); + } + + return wnaf; + } + + public static int GetNafWeight(BigInteger k) + { + if (k.SignValue == 0) + return 0; + + BigInteger _3k = k.ShiftLeft(1).Add(k); + BigInteger diff = _3k.Xor(k); + + return diff.BitCount; + } + + public static WNafPreCompInfo GetWNafPreCompInfo(ECPoint p) + { + return GetWNafPreCompInfo(p.Curve.GetPreCompInfo(p, PRECOMP_NAME)); + } + + public static WNafPreCompInfo GetWNafPreCompInfo(PreCompInfo preCompInfo) + { + if ((preCompInfo != null) && (preCompInfo is WNafPreCompInfo)) + { + return (WNafPreCompInfo)preCompInfo; + } + + return new WNafPreCompInfo(); + } + + /** + * Determine window width to use for a scalar multiplication of the given size. + * + * @param bits the bit-length of the scalar to multiply by + * @return the window size to use + */ + public static int GetWindowSize(int bits) + { + return GetWindowSize(bits, DEFAULT_WINDOW_SIZE_CUTOFFS); + } + + /** + * Determine window width to use for a scalar multiplication of the given size. + * + * @param bits the bit-length of the scalar to multiply by + * @param windowSizeCutoffs a monotonically increasing list of bit sizes at which to increment the window width + * @return the window size to use + */ + public static int GetWindowSize(int bits, int[] windowSizeCutoffs) + { + int w = 0; + for (; w < windowSizeCutoffs.Length; ++w) + { + if (bits < windowSizeCutoffs[w]) + { + break; + } + } + return w + 2; + } + + public static ECPoint MapPointWithPrecomp(ECPoint p, int width, bool includeNegated, + ECPointMap pointMap) + { + ECCurve c = p.Curve; + WNafPreCompInfo wnafPreCompP = Precompute(p, width, includeNegated); + + ECPoint q = pointMap.Map(p); + WNafPreCompInfo wnafPreCompQ = GetWNafPreCompInfo(c.GetPreCompInfo(q, PRECOMP_NAME)); + + ECPoint twiceP = wnafPreCompP.Twice; + if (twiceP != null) + { + ECPoint twiceQ = pointMap.Map(twiceP); + wnafPreCompQ.Twice = twiceQ; + } + + ECPoint[] preCompP = wnafPreCompP.PreComp; + ECPoint[] preCompQ = new ECPoint[preCompP.Length]; + for (int i = 0; i < preCompP.Length; ++i) + { + preCompQ[i] = pointMap.Map(preCompP[i]); + } + wnafPreCompQ.PreComp = preCompQ; + + if (includeNegated) + { + ECPoint[] preCompNegQ = new ECPoint[preCompQ.Length]; + for (int i = 0; i < preCompNegQ.Length; ++i) + { + preCompNegQ[i] = preCompQ[i].Negate(); + } + wnafPreCompQ.PreCompNeg = preCompNegQ; + } + + c.SetPreCompInfo(q, PRECOMP_NAME, wnafPreCompQ); + + return q; + } + + public static WNafPreCompInfo Precompute(ECPoint p, int width, bool includeNegated) + { + ECCurve c = p.Curve; + WNafPreCompInfo wnafPreCompInfo = GetWNafPreCompInfo(c.GetPreCompInfo(p, PRECOMP_NAME)); + + int iniPreCompLen = 0, reqPreCompLen = 1 << System.Math.Max(0, width - 2); + + ECPoint[] preComp = wnafPreCompInfo.PreComp; + if (preComp == null) + { + preComp = EMPTY_POINTS; + } + else + { + iniPreCompLen = preComp.Length; + } + + if (iniPreCompLen < reqPreCompLen) + { + preComp = ResizeTable(preComp, reqPreCompLen); + + if (reqPreCompLen == 1) + { + preComp[0] = p.Normalize(); + } + else + { + int curPreCompLen = iniPreCompLen; + if (curPreCompLen == 0) + { + preComp[0] = p; + curPreCompLen = 1; + } + + ECFieldElement iso = null; + + if (reqPreCompLen == 2) + { + preComp[1] = p.ThreeTimes(); + } + else + { + ECPoint twiceP = wnafPreCompInfo.Twice, last = preComp[curPreCompLen - 1]; + if (twiceP == null) + { + twiceP = preComp[0].Twice(); + wnafPreCompInfo.Twice = twiceP; + + /* + * For Fp curves with Jacobian projective coordinates, use a (quasi-)isomorphism + * where 'twiceP' is "affine", so that the subsequent additions are cheaper. This + * also requires scaling the initial point's X, Y coordinates, and reversing the + * isomorphism as part of the subsequent normalization. + * + * NOTE: The correctness of this optimization depends on: + * 1) additions do not use the curve's A, B coefficients. + * 2) no special cases (i.e. Q +/- Q) when calculating 1P, 3P, 5P, ... + */ + if (!twiceP.IsInfinity && ECAlgorithms.IsFpCurve(c) && c.FieldSize >= 64) + { + switch (c.CoordinateSystem) + { + case ECCurve.COORD_JACOBIAN: + case ECCurve.COORD_JACOBIAN_CHUDNOVSKY: + case ECCurve.COORD_JACOBIAN_MODIFIED: + { + iso = twiceP.GetZCoord(0); + twiceP = c.CreatePoint(twiceP.XCoord.ToBigInteger(), + twiceP.YCoord.ToBigInteger()); + + ECFieldElement iso2 = iso.Square(), iso3 = iso2.Multiply(iso); + last = last.ScaleX(iso2).ScaleY(iso3); + + if (iniPreCompLen == 0) + { + preComp[0] = last; + } + break; + } + } + } + } + + while (curPreCompLen < reqPreCompLen) + { + /* + * Compute the new ECPoints for the precomputation array. The values 1, 3, + * 5, ..., 2^(width-1)-1 times p are computed + */ + preComp[curPreCompLen++] = last = last.Add(twiceP); + } + } + + /* + * Having oft-used operands in affine form makes operations faster. + */ + c.NormalizeAll(preComp, iniPreCompLen, reqPreCompLen - iniPreCompLen, iso); + } + } + + wnafPreCompInfo.PreComp = preComp; + + if (includeNegated) + { + ECPoint[] preCompNeg = wnafPreCompInfo.PreCompNeg; + + int pos; + if (preCompNeg == null) + { + pos = 0; + preCompNeg = new ECPoint[reqPreCompLen]; + } + else + { + pos = preCompNeg.Length; + if (pos < reqPreCompLen) + { + preCompNeg = ResizeTable(preCompNeg, reqPreCompLen); + } + } + + while (pos < reqPreCompLen) + { + preCompNeg[pos] = preComp[pos].Negate(); + ++pos; + } + + wnafPreCompInfo.PreCompNeg = preCompNeg; + } + + c.SetPreCompInfo(p, PRECOMP_NAME, wnafPreCompInfo); + + return wnafPreCompInfo; + } + + private static byte[] Trim(byte[] a, int length) + { + byte[] result = new byte[length]; + Array.Copy(a, 0, result, 0, result.Length); + return result; + } + + private static int[] Trim(int[] a, int length) + { + int[] result = new int[length]; + Array.Copy(a, 0, result, 0, result.Length); + return result; + } + + private static ECPoint[] ResizeTable(ECPoint[] a, int length) + { + ECPoint[] result = new ECPoint[length]; + Array.Copy(a, 0, result, 0, a.Length); + return result; + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/multiplier/WTauNafMultiplier.cs b/bc-sharp-crypto/src/math/ec/multiplier/WTauNafMultiplier.cs new file mode 100644 index 0000000..1e7ddae --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/multiplier/WTauNafMultiplier.cs @@ -0,0 +1,125 @@ +using System; + +using Org.BouncyCastle.Math.EC.Abc; + +namespace Org.BouncyCastle.Math.EC.Multiplier +{ + /** + * Class implementing the WTNAF (Window + * τ-adic Non-Adjacent Form) algorithm. + */ + public class WTauNafMultiplier + : AbstractECMultiplier + { + // TODO Create WTauNafUtilities class and move various functionality into it + internal static readonly string PRECOMP_NAME = "bc_wtnaf"; + + /** + * Multiplies a {@link org.bouncycastle.math.ec.AbstractF2mPoint AbstractF2mPoint} + * by k using the reduced τ-adic NAF (RTNAF) + * method. + * @param p The AbstractF2mPoint to multiply. + * @param k The integer by which to multiply k. + * @return p multiplied by k. + */ + protected override ECPoint MultiplyPositive(ECPoint point, BigInteger k) + { + if (!(point is AbstractF2mPoint)) + throw new ArgumentException("Only AbstractF2mPoint can be used in WTauNafMultiplier"); + + AbstractF2mPoint p = (AbstractF2mPoint)point; + AbstractF2mCurve curve = (AbstractF2mCurve)p.Curve; + int m = curve.FieldSize; + sbyte a = (sbyte)curve.A.ToBigInteger().IntValue; + sbyte mu = Tnaf.GetMu(a); + BigInteger[] s = curve.GetSi(); + + ZTauElement rho = Tnaf.PartModReduction(k, m, a, s, mu, (sbyte)10); + + return MultiplyWTnaf(p, rho, curve.GetPreCompInfo(p, PRECOMP_NAME), a, mu); + } + + /** + * Multiplies a {@link org.bouncycastle.math.ec.AbstractF2mPoint AbstractF2mPoint} + * by an element λ of Z[τ] using + * the τ-adic NAF (TNAF) method. + * @param p The AbstractF2mPoint to multiply. + * @param lambda The element λ of + * Z[τ] of which to compute the + * [τ]-adic NAF. + * @return p multiplied by λ. + */ + private AbstractF2mPoint MultiplyWTnaf(AbstractF2mPoint p, ZTauElement lambda, + PreCompInfo preCompInfo, sbyte a, sbyte mu) + { + ZTauElement[] alpha = (a == 0) ? Tnaf.Alpha0 : Tnaf.Alpha1; + + BigInteger tw = Tnaf.GetTw(mu, Tnaf.Width); + + sbyte[]u = Tnaf.TauAdicWNaf(mu, lambda, Tnaf.Width, + BigInteger.ValueOf(Tnaf.Pow2Width), tw, alpha); + + return MultiplyFromWTnaf(p, u, preCompInfo); + } + + /** + * Multiplies a {@link org.bouncycastle.math.ec.AbstractF2mPoint AbstractF2mPoint} + * by an element λ of Z[τ] + * using the window τ-adic NAF (TNAF) method, given the + * WTNAF of λ. + * @param p The AbstractF2mPoint to multiply. + * @param u The the WTNAF of λ.. + * @return λ * p + */ + private static AbstractF2mPoint MultiplyFromWTnaf(AbstractF2mPoint p, sbyte[] u, PreCompInfo preCompInfo) + { + AbstractF2mCurve curve = (AbstractF2mCurve)p.Curve; + sbyte a = (sbyte)curve.A.ToBigInteger().IntValue; + + AbstractF2mPoint[] pu; + if ((preCompInfo == null) || !(preCompInfo is WTauNafPreCompInfo)) + { + pu = Tnaf.GetPreComp(p, a); + + WTauNafPreCompInfo pre = new WTauNafPreCompInfo(); + pre.PreComp = pu; + curve.SetPreCompInfo(p, PRECOMP_NAME, pre); + } + else + { + pu = ((WTauNafPreCompInfo)preCompInfo).PreComp; + } + + // TODO Include negations in precomp (optionally) and use from here + AbstractF2mPoint[] puNeg = new AbstractF2mPoint[pu.Length]; + for (int i = 0; i < pu.Length; ++i) + { + puNeg[i] = (AbstractF2mPoint)pu[i].Negate(); + } + + + // q = infinity + AbstractF2mPoint q = (AbstractF2mPoint) p.Curve.Infinity; + + int tauCount = 0; + for (int i = u.Length - 1; i >= 0; i--) + { + ++tauCount; + int ui = u[i]; + if (ui != 0) + { + q = q.TauPow(tauCount); + tauCount = 0; + + ECPoint x = ui > 0 ? pu[ui >> 1] : puNeg[(-ui) >> 1]; + q = (AbstractF2mPoint)q.Add(x); + } + } + if (tauCount > 0) + { + q = q.TauPow(tauCount); + } + return q; + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/multiplier/WTauNafPreCompInfo.cs b/bc-sharp-crypto/src/math/ec/multiplier/WTauNafPreCompInfo.cs new file mode 100644 index 0000000..72659b3 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/multiplier/WTauNafPreCompInfo.cs @@ -0,0 +1,24 @@ +namespace Org.BouncyCastle.Math.EC.Multiplier +{ + /** + * Class holding precomputation data for the WTNAF (Window + * τ-adic Non-Adjacent Form) algorithm. + */ + public class WTauNafPreCompInfo + : PreCompInfo + { + /** + * Array holding the precomputed AbstractF2mPoints used for the + * WTNAF multiplication in + * {@link org.bouncycastle.math.ec.multiplier.WTauNafMultiplier.multiply() + * WTauNafMultiplier.multiply()}. + */ + protected AbstractF2mPoint[] m_preComp; + + public virtual AbstractF2mPoint[] PreComp + { + get { return m_preComp; } + set { this.m_preComp = value; } + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/multiplier/ZSignedDigitL2RMultiplier.cs b/bc-sharp-crypto/src/math/ec/multiplier/ZSignedDigitL2RMultiplier.cs new file mode 100644 index 0000000..554ac61 --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/multiplier/ZSignedDigitL2RMultiplier.cs @@ -0,0 +1,29 @@ +namespace Org.BouncyCastle.Math.EC.Multiplier +{ + public class ZSignedDigitL2RMultiplier + : AbstractECMultiplier + { + /** + * 'Zeroless' Signed Digit Left-to-Right. + */ + protected override ECPoint MultiplyPositive(ECPoint p, BigInteger k) + { + ECPoint addP = p.Normalize(), subP = addP.Negate(); + + ECPoint R0 = addP; + + int n = k.BitLength; + int s = k.GetLowestSetBit(); + + int i = n; + while (--i > s) + { + R0 = R0.TwicePlus(k.TestBit(i) ? addP : subP); + } + + R0 = R0.TimesPow2(s); + + return R0; + } + } +} diff --git a/bc-sharp-crypto/src/math/ec/multiplier/ZSignedDigitR2LMultiplier.cs b/bc-sharp-crypto/src/math/ec/multiplier/ZSignedDigitR2LMultiplier.cs new file mode 100644 index 0000000..91c06cb --- /dev/null +++ b/bc-sharp-crypto/src/math/ec/multiplier/ZSignedDigitR2LMultiplier.cs @@ -0,0 +1,30 @@ +namespace Org.BouncyCastle.Math.EC.Multiplier +{ + public class ZSignedDigitR2LMultiplier + : AbstractECMultiplier + { + /** + * 'Zeroless' Signed Digit Right-to-Left. + */ + protected override ECPoint MultiplyPositive(ECPoint p, BigInteger k) + { + ECPoint R0 = p.Curve.Infinity, R1 = p; + + int n = k.BitLength; + int s = k.GetLowestSetBit(); + + R1 = R1.TimesPow2(s); + + int i = s; + while (++i < n) + { + R0 = R0.Add(k.TestBit(i) ? R1 : R1.Negate()); + R1 = R1.Twice(); + } + + R0 = R0.Add(R1); + + return R0; + } + } +} diff --git a/bc-sharp-crypto/src/math/field/FiniteFields.cs b/bc-sharp-crypto/src/math/field/FiniteFields.cs new file mode 100644 index 0000000..7b84569 --- /dev/null +++ b/bc-sharp-crypto/src/math/field/FiniteFields.cs @@ -0,0 +1,54 @@ +using System; + +namespace Org.BouncyCastle.Math.Field +{ + public abstract class FiniteFields + { + internal static readonly IFiniteField GF_2 = new PrimeField(BigInteger.ValueOf(2)); + internal static readonly IFiniteField GF_3 = new PrimeField(BigInteger.ValueOf(3)); + + public static IPolynomialExtensionField GetBinaryExtensionField(int[] exponents) + { + if (exponents[0] != 0) + { + throw new ArgumentException("Irreducible polynomials in GF(2) must have constant term", "exponents"); + } + for (int i = 1; i < exponents.Length; ++i) + { + if (exponents[i] <= exponents[i - 1]) + { + throw new ArgumentException("Polynomial exponents must be montonically increasing", "exponents"); + } + } + + return new GenericPolynomialExtensionField(GF_2, new GF2Polynomial(exponents)); + } + + // public static IPolynomialExtensionField GetTernaryExtensionField(Term[] terms) + // { + // return new GenericPolynomialExtensionField(GF_3, new GF3Polynomial(terms)); + // } + + public static IFiniteField GetPrimeField(BigInteger characteristic) + { + int bitLength = characteristic.BitLength; + if (characteristic.SignValue <= 0 || bitLength < 2) + { + throw new ArgumentException("Must be >= 2", "characteristic"); + } + + if (bitLength < 3) + { + switch (characteristic.IntValue) + { + case 2: + return GF_2; + case 3: + return GF_3; + } + } + + return new PrimeField(characteristic); + } + } +} diff --git a/bc-sharp-crypto/src/math/field/GF2Polynomial.cs b/bc-sharp-crypto/src/math/field/GF2Polynomial.cs new file mode 100644 index 0000000..c062d50 --- /dev/null +++ b/bc-sharp-crypto/src/math/field/GF2Polynomial.cs @@ -0,0 +1,46 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Math.Field +{ + internal class GF2Polynomial + : IPolynomial + { + protected readonly int[] exponents; + + internal GF2Polynomial(int[] exponents) + { + this.exponents = Arrays.Clone(exponents); + } + + public virtual int Degree + { + get { return exponents[exponents.Length - 1]; } + } + + public virtual int[] GetExponentsPresent() + { + return Arrays.Clone(exponents); + } + + public override bool Equals(object obj) + { + if (this == obj) + { + return true; + } + GF2Polynomial other = obj as GF2Polynomial; + if (null == other) + { + return false; + } + return Arrays.AreEqual(exponents, other.exponents); + } + + public override int GetHashCode() + { + return Arrays.GetHashCode(exponents); + } + } +} diff --git a/bc-sharp-crypto/src/math/field/GenericPolynomialExtensionField.cs b/bc-sharp-crypto/src/math/field/GenericPolynomialExtensionField.cs new file mode 100644 index 0000000..13ef571 --- /dev/null +++ b/bc-sharp-crypto/src/math/field/GenericPolynomialExtensionField.cs @@ -0,0 +1,63 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Math.Field +{ + internal class GenericPolynomialExtensionField + : IPolynomialExtensionField + { + protected readonly IFiniteField subfield; + protected readonly IPolynomial minimalPolynomial; + + internal GenericPolynomialExtensionField(IFiniteField subfield, IPolynomial polynomial) + { + this.subfield = subfield; + this.minimalPolynomial = polynomial; + } + + public virtual BigInteger Characteristic + { + get { return subfield.Characteristic; } + } + + public virtual int Dimension + { + get { return subfield.Dimension * minimalPolynomial.Degree; } + } + + public virtual IFiniteField Subfield + { + get { return subfield; } + } + + public virtual int Degree + { + get { return minimalPolynomial.Degree; } + } + + public virtual IPolynomial MinimalPolynomial + { + get { return minimalPolynomial; } + } + + public override bool Equals(object obj) + { + if (this == obj) + { + return true; + } + GenericPolynomialExtensionField other = obj as GenericPolynomialExtensionField; + if (null == other) + { + return false; + } + return subfield.Equals(other.subfield) && minimalPolynomial.Equals(other.minimalPolynomial); + } + + public override int GetHashCode() + { + return subfield.GetHashCode() ^ Integers.RotateLeft(minimalPolynomial.GetHashCode(), 16); + } + } +} diff --git a/bc-sharp-crypto/src/math/field/IExtensionField.cs b/bc-sharp-crypto/src/math/field/IExtensionField.cs new file mode 100644 index 0000000..17f45c1 --- /dev/null +++ b/bc-sharp-crypto/src/math/field/IExtensionField.cs @@ -0,0 +1,12 @@ +using System; + +namespace Org.BouncyCastle.Math.Field +{ + public interface IExtensionField + : IFiniteField + { + IFiniteField Subfield { get; } + + int Degree { get; } + } +} diff --git a/bc-sharp-crypto/src/math/field/IFiniteField.cs b/bc-sharp-crypto/src/math/field/IFiniteField.cs new file mode 100644 index 0000000..b618be7 --- /dev/null +++ b/bc-sharp-crypto/src/math/field/IFiniteField.cs @@ -0,0 +1,11 @@ +using System; + +namespace Org.BouncyCastle.Math.Field +{ + public interface IFiniteField + { + BigInteger Characteristic { get; } + + int Dimension { get; } + } +} diff --git a/bc-sharp-crypto/src/math/field/IPolynomial.cs b/bc-sharp-crypto/src/math/field/IPolynomial.cs new file mode 100644 index 0000000..ad6dfb6 --- /dev/null +++ b/bc-sharp-crypto/src/math/field/IPolynomial.cs @@ -0,0 +1,15 @@ +using System; + +namespace Org.BouncyCastle.Math.Field +{ + public interface IPolynomial + { + int Degree { get; } + + //BigInteger[] GetCoefficients(); + + int[] GetExponentsPresent(); + + //Term[] GetNonZeroTerms(); + } +} diff --git a/bc-sharp-crypto/src/math/field/IPolynomialExtensionField.cs b/bc-sharp-crypto/src/math/field/IPolynomialExtensionField.cs new file mode 100644 index 0000000..3818c18 --- /dev/null +++ b/bc-sharp-crypto/src/math/field/IPolynomialExtensionField.cs @@ -0,0 +1,10 @@ +using System; + +namespace Org.BouncyCastle.Math.Field +{ + public interface IPolynomialExtensionField + : IExtensionField + { + IPolynomial MinimalPolynomial { get; } + } +} diff --git a/bc-sharp-crypto/src/math/field/PrimeField.cs b/bc-sharp-crypto/src/math/field/PrimeField.cs new file mode 100644 index 0000000..f6ba629 --- /dev/null +++ b/bc-sharp-crypto/src/math/field/PrimeField.cs @@ -0,0 +1,44 @@ +using System; + +namespace Org.BouncyCastle.Math.Field +{ + internal class PrimeField + : IFiniteField + { + protected readonly BigInteger characteristic; + + internal PrimeField(BigInteger characteristic) + { + this.characteristic = characteristic; + } + + public virtual BigInteger Characteristic + { + get { return characteristic; } + } + + public virtual int Dimension + { + get { return 1; } + } + + public override bool Equals(object obj) + { + if (this == obj) + { + return true; + } + PrimeField other = obj as PrimeField; + if (null == other) + { + return false; + } + return characteristic.Equals(other.characteristic); + } + + public override int GetHashCode() + { + return characteristic.GetHashCode(); + } + } +} diff --git a/bc-sharp-crypto/src/math/raw/Interleave.cs b/bc-sharp-crypto/src/math/raw/Interleave.cs new file mode 100644 index 0000000..d218406 --- /dev/null +++ b/bc-sharp-crypto/src/math/raw/Interleave.cs @@ -0,0 +1,107 @@ +using System; + +namespace Org.BouncyCastle.Math.Raw +{ + internal abstract class Interleave + { + private const ulong M32 = 0x55555555UL; + private const ulong M64 = 0x5555555555555555UL; + + /* + * This expands 8 bit indices into 16 bit contents (high bit 14), by inserting 0s between bits. + * In a binary field, this operation is the same as squaring an 8 bit number. + */ + //private static readonly ushort[] INTERLEAVE2_TABLE = new ushort[] + //{ + // 0x0000, 0x0001, 0x0004, 0x0005, 0x0010, 0x0011, 0x0014, 0x0015, + // 0x0040, 0x0041, 0x0044, 0x0045, 0x0050, 0x0051, 0x0054, 0x0055, + // 0x0100, 0x0101, 0x0104, 0x0105, 0x0110, 0x0111, 0x0114, 0x0115, + // 0x0140, 0x0141, 0x0144, 0x0145, 0x0150, 0x0151, 0x0154, 0x0155, + // 0x0400, 0x0401, 0x0404, 0x0405, 0x0410, 0x0411, 0x0414, 0x0415, + // 0x0440, 0x0441, 0x0444, 0x0445, 0x0450, 0x0451, 0x0454, 0x0455, + // 0x0500, 0x0501, 0x0504, 0x0505, 0x0510, 0x0511, 0x0514, 0x0515, + // 0x0540, 0x0541, 0x0544, 0x0545, 0x0550, 0x0551, 0x0554, 0x0555, + // 0x1000, 0x1001, 0x1004, 0x1005, 0x1010, 0x1011, 0x1014, 0x1015, + // 0x1040, 0x1041, 0x1044, 0x1045, 0x1050, 0x1051, 0x1054, 0x1055, + // 0x1100, 0x1101, 0x1104, 0x1105, 0x1110, 0x1111, 0x1114, 0x1115, + // 0x1140, 0x1141, 0x1144, 0x1145, 0x1150, 0x1151, 0x1154, 0x1155, + // 0x1400, 0x1401, 0x1404, 0x1405, 0x1410, 0x1411, 0x1414, 0x1415, + // 0x1440, 0x1441, 0x1444, 0x1445, 0x1450, 0x1451, 0x1454, 0x1455, + // 0x1500, 0x1501, 0x1504, 0x1505, 0x1510, 0x1511, 0x1514, 0x1515, + // 0x1540, 0x1541, 0x1544, 0x1545, 0x1550, 0x1551, 0x1554, 0x1555, + // 0x4000, 0x4001, 0x4004, 0x4005, 0x4010, 0x4011, 0x4014, 0x4015, + // 0x4040, 0x4041, 0x4044, 0x4045, 0x4050, 0x4051, 0x4054, 0x4055, + // 0x4100, 0x4101, 0x4104, 0x4105, 0x4110, 0x4111, 0x4114, 0x4115, + // 0x4140, 0x4141, 0x4144, 0x4145, 0x4150, 0x4151, 0x4154, 0x4155, + // 0x4400, 0x4401, 0x4404, 0x4405, 0x4410, 0x4411, 0x4414, 0x4415, + // 0x4440, 0x4441, 0x4444, 0x4445, 0x4450, 0x4451, 0x4454, 0x4455, + // 0x4500, 0x4501, 0x4504, 0x4505, 0x4510, 0x4511, 0x4514, 0x4515, + // 0x4540, 0x4541, 0x4544, 0x4545, 0x4550, 0x4551, 0x4554, 0x4555, + // 0x5000, 0x5001, 0x5004, 0x5005, 0x5010, 0x5011, 0x5014, 0x5015, + // 0x5040, 0x5041, 0x5044, 0x5045, 0x5050, 0x5051, 0x5054, 0x5055, + // 0x5100, 0x5101, 0x5104, 0x5105, 0x5110, 0x5111, 0x5114, 0x5115, + // 0x5140, 0x5141, 0x5144, 0x5145, 0x5150, 0x5151, 0x5154, 0x5155, + // 0x5400, 0x5401, 0x5404, 0x5405, 0x5410, 0x5411, 0x5414, 0x5415, + // 0x5440, 0x5441, 0x5444, 0x5445, 0x5450, 0x5451, 0x5454, 0x5455, + // 0x5500, 0x5501, 0x5504, 0x5505, 0x5510, 0x5511, 0x5514, 0x5515, + // 0x5540, 0x5541, 0x5544, 0x5545, 0x5550, 0x5551, 0x5554, 0x5555 + //}; + + internal static uint Expand8to16(uint x) + { + x &= 0xFFU; + x = (x | (x << 4)) & 0x0F0FU; + x = (x | (x << 2)) & 0x3333U; + x = (x | (x << 1)) & 0x5555U; + return x; + } + + internal static uint Expand16to32(uint x) + { + x &= 0xFFFFU; + x = (x | (x << 8)) & 0x00FF00FFU; + x = (x | (x << 4)) & 0x0F0F0F0FU; + x = (x | (x << 2)) & 0x33333333U; + x = (x | (x << 1)) & 0x55555555U; + return x; + } + + internal static ulong Expand32to64(uint x) + { + // "shuffle" low half to even bits and high half to odd bits + uint t; + t = (x ^ (x >> 8)) & 0x0000FF00U; x ^= (t ^ (t << 8)); + t = (x ^ (x >> 4)) & 0x00F000F0U; x ^= (t ^ (t << 4)); + t = (x ^ (x >> 2)) & 0x0C0C0C0CU; x ^= (t ^ (t << 2)); + t = (x ^ (x >> 1)) & 0x22222222U; x ^= (t ^ (t << 1)); + + return ((x >> 1) & M32) << 32 | (x & M32); + } + + internal static void Expand64To128(ulong x, ulong[] z, int zOff) + { + // "shuffle" low half to even bits and high half to odd bits + ulong t; + t = (x ^ (x >> 16)) & 0x00000000FFFF0000UL; x ^= (t ^ (t << 16)); + t = (x ^ (x >> 8)) & 0x0000FF000000FF00UL; x ^= (t ^ (t << 8)); + t = (x ^ (x >> 4)) & 0x00F000F000F000F0UL; x ^= (t ^ (t << 4)); + t = (x ^ (x >> 2)) & 0x0C0C0C0C0C0C0C0CUL; x ^= (t ^ (t << 2)); + t = (x ^ (x >> 1)) & 0x2222222222222222UL; x ^= (t ^ (t << 1)); + + z[zOff ] = (x ) & M64; + z[zOff + 1] = (x >> 1) & M64; + } + + internal static ulong Unshuffle(ulong x) + { + // "unshuffle" even bits to low half and odd bits to high half + ulong t; + t = (x ^ (x >> 1)) & 0x2222222222222222UL; x ^= (t ^ (t << 1)); + t = (x ^ (x >> 2)) & 0x0C0C0C0C0C0C0C0CUL; x ^= (t ^ (t << 2)); + t = (x ^ (x >> 4)) & 0x00F000F000F000F0UL; x ^= (t ^ (t << 4)); + t = (x ^ (x >> 8)) & 0x0000FF000000FF00UL; x ^= (t ^ (t << 8)); + t = (x ^ (x >> 16)) & 0x00000000FFFF0000UL; x ^= (t ^ (t << 16)); + return x; + } + } +} diff --git a/bc-sharp-crypto/src/math/raw/Mod.cs b/bc-sharp-crypto/src/math/raw/Mod.cs new file mode 100644 index 0000000..8d9e8fd --- /dev/null +++ b/bc-sharp-crypto/src/math/raw/Mod.cs @@ -0,0 +1,186 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Crypto.Utilities; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Math.Raw +{ + internal abstract class Mod + { + private static readonly SecureRandom RandomSource = new SecureRandom(); + + public static void Invert(uint[] p, uint[] x, uint[] z) + { + int len = p.Length; + if (Nat.IsZero(len, x)) + throw new ArgumentException("cannot be 0", "x"); + if (Nat.IsOne(len, x)) + { + Array.Copy(x, 0, z, 0, len); + return; + } + + uint[] u = Nat.Copy(len, x); + uint[] a = Nat.Create(len); + a[0] = 1; + int ac = 0; + + if ((u[0] & 1) == 0) + { + InversionStep(p, u, len, a, ref ac); + } + if (Nat.IsOne(len, u)) + { + InversionResult(p, ac, a, z); + return; + } + + uint[] v = Nat.Copy(len, p); + uint[] b = Nat.Create(len); + int bc = 0; + + int uvLen = len; + + for (;;) + { + while (u[uvLen - 1] == 0 && v[uvLen - 1] == 0) + { + --uvLen; + } + + if (Nat.Gte(len, u, v)) + { + Nat.SubFrom(len, v, u); + Debug.Assert((u[0] & 1) == 0); + ac += Nat.SubFrom(len, b, a) - bc; + InversionStep(p, u, uvLen, a, ref ac); + if (Nat.IsOne(len, u)) + { + InversionResult(p, ac, a, z); + return; + } + } + else + { + Nat.SubFrom(len, u, v); + Debug.Assert((v[0] & 1) == 0); + bc += Nat.SubFrom(len, a, b) - ac; + InversionStep(p, v, uvLen, b, ref bc); + if (Nat.IsOne(len, v)) + { + InversionResult(p, bc, b, z); + return; + } + } + } + } + + public static uint[] Random(uint[] p) + { + int len = p.Length; + uint[] s = Nat.Create(len); + + uint m = p[len - 1]; + m |= m >> 1; + m |= m >> 2; + m |= m >> 4; + m |= m >> 8; + m |= m >> 16; + + do + { + byte[] bytes = new byte[len << 2]; + RandomSource.NextBytes(bytes); + Pack.BE_To_UInt32(bytes, 0, s); + s[len - 1] &= m; + } + while (Nat.Gte(len, s, p)); + + return s; + } + + public static void Add(uint[] p, uint[] x, uint[] y, uint[] z) + { + int len = p.Length; + uint c = Nat.Add(len, x, y, z); + if (c != 0) + { + Nat.SubFrom(len, p, z); + } + } + + public static void Subtract(uint[] p, uint[] x, uint[] y, uint[] z) + { + int len = p.Length; + int c = Nat.Sub(len, x, y, z); + if (c != 0) + { + Nat.AddTo(len, p, z); + } + } + + private static void InversionResult(uint[] p, int ac, uint[] a, uint[] z) + { + if (ac < 0) + { + Nat.Add(p.Length, a, p, z); + } + else + { + Array.Copy(a, 0, z, 0, p.Length); + } + } + + private static void InversionStep(uint[] p, uint[] u, int uLen, uint[] x, ref int xc) + { + int len = p.Length; + int count = 0; + while (u[0] == 0) + { + Nat.ShiftDownWord(uLen, u, 0); + count += 32; + } + + { + int zeroes = GetTrailingZeroes(u[0]); + if (zeroes > 0) + { + Nat.ShiftDownBits(uLen, u, zeroes, 0); + count += zeroes; + } + } + + for (int i = 0; i < count; ++i) + { + if ((x[0] & 1) != 0) + { + if (xc < 0) + { + xc += (int)Nat.AddTo(len, p, x); + } + else + { + xc += Nat.SubFrom(len, p, x); + } + } + + Debug.Assert(xc == 0 || xc == -1); + Nat.ShiftDownBit(len, x, (uint)xc); + } + } + + private static int GetTrailingZeroes(uint x) + { + Debug.Assert(x != 0); + int count = 0; + while ((x & 1) == 0) + { + x >>= 1; + ++count; + } + return count; + } + } +} diff --git a/bc-sharp-crypto/src/math/raw/Nat.cs b/bc-sharp-crypto/src/math/raw/Nat.cs new file mode 100644 index 0000000..1f9ab00 --- /dev/null +++ b/bc-sharp-crypto/src/math/raw/Nat.cs @@ -0,0 +1,1053 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Crypto.Utilities; + +namespace Org.BouncyCastle.Math.Raw +{ + internal abstract class Nat + { + private const ulong M = 0xFFFFFFFFUL; + + public static uint Add(int len, uint[] x, uint[] y, uint[] z) + { + ulong c = 0; + for (int i = 0; i < len; ++i) + { + c += (ulong)x[i] + y[i]; + z[i] = (uint)c; + c >>= 32; + } + return (uint)c; + } + + public static uint Add33At(int len, uint x, uint[] z, int zPos) + { + Debug.Assert(zPos <= (len - 2)); + ulong c = (ulong)z[zPos + 0] + x; + z[zPos + 0] = (uint)c; + c >>= 32; + c += (ulong)z[zPos + 1] + 1; + z[zPos + 1] = (uint)c; + c >>= 32; + return c == 0 ? 0 : IncAt(len, z, zPos + 2); + } + + public static uint Add33At(int len, uint x, uint[] z, int zOff, int zPos) + { + Debug.Assert(zPos <= (len - 2)); + ulong c = (ulong)z[zOff + zPos] + x; + z[zOff + zPos] = (uint)c; + c >>= 32; + c += (ulong)z[zOff + zPos + 1] + 1; + z[zOff + zPos + 1] = (uint)c; + c >>= 32; + return c == 0 ? 0 : IncAt(len, z, zOff, zPos + 2); + } + + public static uint Add33To(int len, uint x, uint[] z) + { + ulong c = (ulong)z[0] + x; + z[0] = (uint)c; + c >>= 32; + c += (ulong)z[1] + 1; + z[1] = (uint)c; + c >>= 32; + return c == 0 ? 0 : IncAt(len, z, 2); + } + + public static uint Add33To(int len, uint x, uint[] z, int zOff) + { + ulong c = (ulong)z[zOff + 0] + x; + z[zOff + 0] = (uint)c; + c >>= 32; + c += (ulong)z[zOff + 1] + 1; + z[zOff + 1] = (uint)c; + c >>= 32; + return c == 0 ? 0 : IncAt(len, z, zOff, 2); + } + + public static uint AddBothTo(int len, uint[] x, uint[] y, uint[] z) + { + ulong c = 0; + for (int i = 0; i < len; ++i) + { + c += (ulong)x[i] + y[i] + z[i]; + z[i] = (uint)c; + c >>= 32; + } + return (uint)c; + } + + public static uint AddBothTo(int len, uint[] x, int xOff, uint[] y, int yOff, uint[] z, int zOff) + { + ulong c = 0; + for (int i = 0; i < len; ++i) + { + c += (ulong)x[xOff + i] + y[yOff + i] + z[zOff + i]; + z[zOff + i] = (uint)c; + c >>= 32; + } + return (uint)c; + } + + public static uint AddDWordAt(int len, ulong x, uint[] z, int zPos) + { + Debug.Assert(zPos <= (len - 2)); + ulong c = (ulong)z[zPos + 0] + (x & M); + z[zPos + 0] = (uint)c; + c >>= 32; + c += (ulong)z[zPos + 1] + (x >> 32); + z[zPos + 1] = (uint)c; + c >>= 32; + return c == 0 ? 0 : IncAt(len, z, zPos + 2); + } + + public static uint AddDWordAt(int len, ulong x, uint[] z, int zOff, int zPos) + { + Debug.Assert(zPos <= (len - 2)); + ulong c = (ulong)z[zOff + zPos] + (x & M); + z[zOff + zPos] = (uint)c; + c >>= 32; + c += (ulong)z[zOff + zPos + 1] + (x >> 32); + z[zOff + zPos + 1] = (uint)c; + c >>= 32; + return c == 0 ? 0 : IncAt(len, z, zOff, zPos + 2); + } + + public static uint AddDWordTo(int len, ulong x, uint[] z) + { + ulong c = (ulong)z[0] + (x & M); + z[0] = (uint)c; + c >>= 32; + c += (ulong)z[1] + (x >> 32); + z[1] = (uint)c; + c >>= 32; + return c == 0 ? 0 : IncAt(len, z, 2); + } + + public static uint AddDWordTo(int len, ulong x, uint[] z, int zOff) + { + ulong c = (ulong)z[zOff + 0] + (x & M); + z[zOff + 0] = (uint)c; + c >>= 32; + c += (ulong)z[zOff + 1] + (x >> 32); + z[zOff + 1] = (uint)c; + c >>= 32; + return c == 0 ? 0 : IncAt(len, z, zOff, 2); + } + + public static uint AddTo(int len, uint[] x, uint[] z) + { + ulong c = 0; + for (int i = 0; i < len; ++i) + { + c += (ulong)x[i] + z[i]; + z[i] = (uint)c; + c >>= 32; + } + return (uint)c; + } + + public static uint AddTo(int len, uint[] x, int xOff, uint[] z, int zOff) + { + ulong c = 0; + for (int i = 0; i < len; ++i) + { + c += (ulong)x[xOff + i] + z[zOff + i]; + z[zOff + i] = (uint)c; + c >>= 32; + } + return (uint)c; + } + + public static uint AddWordAt(int len, uint x, uint[] z, int zPos) + { + Debug.Assert(zPos <= (len - 1)); + ulong c = (ulong)x + z[zPos]; + z[zPos] = (uint)c; + c >>= 32; + return c == 0 ? 0 : IncAt(len, z, zPos + 1); + } + + public static uint AddWordAt(int len, uint x, uint[] z, int zOff, int zPos) + { + Debug.Assert(zPos <= (len - 1)); + ulong c = (ulong)x + z[zOff + zPos]; + z[zOff + zPos] = (uint)c; + c >>= 32; + return c == 0 ? 0 : IncAt(len, z, zOff, zPos + 1); + } + + public static uint AddWordTo(int len, uint x, uint[] z) + { + ulong c = (ulong)x + z[0]; + z[0] = (uint)c; + c >>= 32; + return c == 0 ? 0 : IncAt(len, z, 1); + } + + public static uint AddWordTo(int len, uint x, uint[] z, int zOff) + { + ulong c = (ulong)x + z[zOff]; + z[zOff] = (uint)c; + c >>= 32; + return c == 0 ? 0 : IncAt(len, z, zOff, 1); + } + + public static void Copy(int len, uint[] x, uint[] z) + { + Array.Copy(x, 0, z, 0, len); + } + + public static uint[] Copy(int len, uint[] x) + { + uint[] z = new uint[len]; + Array.Copy(x, 0, z, 0, len); + return z; + } + + public static uint[] Create(int len) + { + return new uint[len]; + } + + public static ulong[] Create64(int len) + { + return new ulong[len]; + } + + public static int Dec(int len, uint[] z) + { + for (int i = 0; i < len; ++i) + { + if (--z[i] != uint.MaxValue) + { + return 0; + } + } + return -1; + } + + public static int Dec(int len, uint[] x, uint[] z) + { + int i = 0; + while (i < len) + { + uint c = x[i] - 1; + z[i] = c; + ++i; + if (c != uint.MaxValue) + { + while (i < len) + { + z[i] = x[i]; + ++i; + } + return 0; + } + } + return -1; + } + + public static int DecAt(int len, uint[] z, int zPos) + { + Debug.Assert(zPos <= len); + for (int i = zPos; i < len; ++i) + { + if (--z[i] != uint.MaxValue) + { + return 0; + } + } + return -1; + } + + public static int DecAt(int len, uint[] z, int zOff, int zPos) + { + Debug.Assert(zPos <= len); + for (int i = zPos; i < len; ++i) + { + if (--z[zOff + i] != uint.MaxValue) + { + return 0; + } + } + return -1; + } + + public static bool Eq(int len, uint[] x, uint[] y) + { + for (int i = len - 1; i >= 0; --i) + { + if (x[i] != y[i]) + { + return false; + } + } + return true; + } + + public static uint[] FromBigInteger(int bits, BigInteger x) + { + if (x.SignValue < 0 || x.BitLength > bits) + throw new ArgumentException(); + + int len = (bits + 31) >> 5; + uint[] z = Create(len); + int i = 0; + while (x.SignValue != 0) + { + z[i++] = (uint)x.IntValue; + x = x.ShiftRight(32); + } + return z; + } + + public static uint GetBit(uint[] x, int bit) + { + if (bit == 0) + { + return x[0] & 1; + } + int w = bit >> 5; + if (w < 0 || w >= x.Length) + { + return 0; + } + int b = bit & 31; + return (x[w] >> b) & 1; + } + + public static bool Gte(int len, uint[] x, uint[] y) + { + for (int i = len - 1; i >= 0; --i) + { + uint x_i = x[i], y_i = y[i]; + if (x_i < y_i) + return false; + if (x_i > y_i) + return true; + } + return true; + } + + public static uint Inc(int len, uint[] z) + { + for (int i = 0; i < len; ++i) + { + if (++z[i] != uint.MinValue) + { + return 0; + } + } + return 1; + } + + public static uint Inc(int len, uint[] x, uint[] z) + { + int i = 0; + while (i < len) + { + uint c = x[i] + 1; + z[i] = c; + ++i; + if (c != 0) + { + while (i < len) + { + z[i] = x[i]; + ++i; + } + return 0; + } + } + return 1; + } + + public static uint IncAt(int len, uint[] z, int zPos) + { + Debug.Assert(zPos <= len); + for (int i = zPos; i < len; ++i) + { + if (++z[i] != uint.MinValue) + { + return 0; + } + } + return 1; + } + + public static uint IncAt(int len, uint[] z, int zOff, int zPos) + { + Debug.Assert(zPos <= len); + for (int i = zPos; i < len; ++i) + { + if (++z[zOff + i] != uint.MinValue) + { + return 0; + } + } + return 1; + } + + public static bool IsOne(int len, uint[] x) + { + if (x[0] != 1) + { + return false; + } + for (int i = 1; i < len; ++i) + { + if (x[i] != 0) + { + return false; + } + } + return true; + } + + public static bool IsZero(int len, uint[] x) + { + if (x[0] != 0) + { + return false; + } + for (int i = 1; i < len; ++i) + { + if (x[i] != 0) + { + return false; + } + } + return true; + } + + public static void Mul(int len, uint[] x, uint[] y, uint[] zz) + { + zz[len] = (uint)MulWord(len, x[0], y, zz); + + for (int i = 1; i < len; ++i) + { + zz[i + len] = (uint)MulWordAddTo(len, x[i], y, 0, zz, i); + } + } + + public static void Mul(int len, uint[] x, int xOff, uint[] y, int yOff, uint[] zz, int zzOff) + { + zz[zzOff + len] = (uint)MulWord(len, x[xOff], y, yOff, zz, zzOff); + + for (int i = 1; i < len; ++i) + { + zz[zzOff + i + len] = (uint)MulWordAddTo(len, x[xOff + i], y, yOff, zz, zzOff + i); + } + } + + public static uint Mul31BothAdd(int len, uint a, uint[] x, uint b, uint[] y, uint[] z, int zOff) + { + ulong c = 0, aVal = (ulong)a, bVal = (ulong)b; + int i = 0; + do + { + c += aVal * x[i] + bVal * y[i] + z[zOff + i]; + z[zOff + i] = (uint)c; + c >>= 32; + } + while (++i < len); + return (uint)c; + } + + public static uint MulWord(int len, uint x, uint[] y, uint[] z) + { + ulong c = 0, xVal = (ulong)x; + int i = 0; + do + { + c += xVal * y[i]; + z[i] = (uint)c; + c >>= 32; + } + while (++i < len); + return (uint)c; + } + + public static uint MulWord(int len, uint x, uint[] y, int yOff, uint[] z, int zOff) + { + ulong c = 0, xVal = (ulong)x; + int i = 0; + do + { + c += xVal * y[yOff + i]; + z[zOff + i] = (uint)c; + c >>= 32; + } + while (++i < len); + return (uint)c; + } + + public static uint MulWordAddTo(int len, uint x, uint[] y, int yOff, uint[] z, int zOff) + { + ulong c = 0, xVal = (ulong)x; + int i = 0; + do + { + c += xVal * y[yOff + i] + z[zOff + i]; + z[zOff + i] = (uint)c; + c >>= 32; + } + while (++i < len); + return (uint)c; + } + + public static uint MulWordDwordAddAt(int len, uint x, ulong y, uint[] z, int zPos) + { + Debug.Assert(zPos <= (len - 3)); + ulong c = 0, xVal = (ulong)x; + c += xVal * (uint)y + z[zPos + 0]; + z[zPos + 0] = (uint)c; + c >>= 32; + c += xVal * (y >> 32) + z[zPos + 1]; + z[zPos + 1] = (uint)c; + c >>= 32; + c += (ulong)z[zPos + 2]; + z[zPos + 2] = (uint)c; + c >>= 32; + return c == 0 ? 0 : IncAt(len, z, zPos + 3); + } + + public static uint ShiftDownBit(int len, uint[] z, uint c) + { + int i = len; + while (--i >= 0) + { + uint next = z[i]; + z[i] = (next >> 1) | (c << 31); + c = next; + } + return c << 31; + } + + public static uint ShiftDownBit(int len, uint[] z, int zOff, uint c) + { + int i = len; + while (--i >= 0) + { + uint next = z[zOff + i]; + z[zOff + i] = (next >> 1) | (c << 31); + c = next; + } + return c << 31; + } + + public static uint ShiftDownBit(int len, uint[] x, uint c, uint[] z) + { + int i = len; + while (--i >= 0) + { + uint next = x[i]; + z[i] = (next >> 1) | (c << 31); + c = next; + } + return c << 31; + } + + public static uint ShiftDownBit(int len, uint[] x, int xOff, uint c, uint[] z, int zOff) + { + int i = len; + while (--i >= 0) + { + uint next = x[xOff + i]; + z[zOff + i] = (next >> 1) | (c << 31); + c = next; + } + return c << 31; + } + + public static uint ShiftDownBits(int len, uint[] z, int bits, uint c) + { + Debug.Assert(bits > 0 && bits < 32); + int i = len; + while (--i >= 0) + { + uint next = z[i]; + z[i] = (next >> bits) | (c << -bits); + c = next; + } + return c << -bits; + } + + public static uint ShiftDownBits(int len, uint[] z, int zOff, int bits, uint c) + { + Debug.Assert(bits > 0 && bits < 32); + int i = len; + while (--i >= 0) + { + uint next = z[zOff + i]; + z[zOff + i] = (next >> bits) | (c << -bits); + c = next; + } + return c << -bits; + } + + public static uint ShiftDownBits(int len, uint[] x, int bits, uint c, uint[] z) + { + Debug.Assert(bits > 0 && bits < 32); + int i = len; + while (--i >= 0) + { + uint next = x[i]; + z[i] = (next >> bits) | (c << -bits); + c = next; + } + return c << -bits; + } + + public static uint ShiftDownBits(int len, uint[] x, int xOff, int bits, uint c, uint[] z, int zOff) + { + Debug.Assert(bits > 0 && bits < 32); + int i = len; + while (--i >= 0) + { + uint next = x[xOff + i]; + z[zOff + i] = (next >> bits) | (c << -bits); + c = next; + } + return c << -bits; + } + + public static uint ShiftDownWord(int len, uint[] z, uint c) + { + int i = len; + while (--i >= 0) + { + uint next = z[i]; + z[i] = c; + c = next; + } + return c; + } + + public static uint ShiftUpBit(int len, uint[] z, uint c) + { + for (int i = 0; i < len; ++i) + { + uint next = z[i]; + z[i] = (next << 1) | (c >> 31); + c = next; + } + return c >> 31; + } + + public static uint ShiftUpBit(int len, uint[] z, int zOff, uint c) + { + for (int i = 0; i < len; ++i) + { + uint next = z[zOff + i]; + z[zOff + i] = (next << 1) | (c >> 31); + c = next; + } + return c >> 31; + } + + public static uint ShiftUpBit(int len, uint[] x, uint c, uint[] z) + { + for (int i = 0; i < len; ++i) + { + uint next = x[i]; + z[i] = (next << 1) | (c >> 31); + c = next; + } + return c >> 31; + } + + public static uint ShiftUpBit(int len, uint[] x, int xOff, uint c, uint[] z, int zOff) + { + for (int i = 0; i < len; ++i) + { + uint next = x[xOff + i]; + z[zOff + i] = (next << 1) | (c >> 31); + c = next; + } + return c >> 31; + } + + public static ulong ShiftUpBit64(int len, ulong[] x, int xOff, ulong c, ulong[] z, int zOff) + { + for (int i = 0; i < len; ++i) + { + ulong next = x[xOff + i]; + z[zOff + i] = (next << 1) | (c >> 63); + c = next; + } + return c >> 63; + } + + public static uint ShiftUpBits(int len, uint[] z, int bits, uint c) + { + Debug.Assert(bits > 0 && bits < 32); + for (int i = 0; i < len; ++i) + { + uint next = z[i]; + z[i] = (next << bits) | (c >> -bits); + c = next; + } + return c >> -bits; + } + + public static uint ShiftUpBits(int len, uint[] z, int zOff, int bits, uint c) + { + Debug.Assert(bits > 0 && bits < 32); + for (int i = 0; i < len; ++i) + { + uint next = z[zOff + i]; + z[zOff + i] = (next << bits) | (c >> -bits); + c = next; + } + return c >> -bits; + } + + public static ulong ShiftUpBits64(int len, ulong[] z, int zOff, int bits, ulong c) + { + Debug.Assert(bits > 0 && bits < 64); + for (int i = 0; i < len; ++i) + { + ulong next = z[zOff + i]; + z[zOff + i] = (next << bits) | (c >> -bits); + c = next; + } + return c >> -bits; + } + + public static uint ShiftUpBits(int len, uint[] x, int bits, uint c, uint[] z) + { + Debug.Assert(bits > 0 && bits < 32); + for (int i = 0; i < len; ++i) + { + uint next = x[i]; + z[i] = (next << bits) | (c >> -bits); + c = next; + } + return c >> -bits; + } + + public static uint ShiftUpBits(int len, uint[] x, int xOff, int bits, uint c, uint[] z, int zOff) + { + Debug.Assert(bits > 0 && bits < 32); + for (int i = 0; i < len; ++i) + { + uint next = x[xOff + i]; + z[zOff + i] = (next << bits) | (c >> -bits); + c = next; + } + return c >> -bits; + } + + public static ulong ShiftUpBits64(int len, ulong[] x, int xOff, int bits, ulong c, ulong[] z, int zOff) + { + Debug.Assert(bits > 0 && bits < 64); + for (int i = 0; i < len; ++i) + { + ulong next = x[xOff + i]; + z[zOff + i] = (next << bits) | (c >> -bits); + c = next; + } + return c >> -bits; + } + + public static void Square(int len, uint[] x, uint[] zz) + { + int extLen = len << 1; + uint c = 0; + int j = len, k = extLen; + do + { + ulong xVal = (ulong)x[--j]; + ulong p = xVal * xVal; + zz[--k] = (c << 31) | (uint)(p >> 33); + zz[--k] = (uint)(p >> 1); + c = (uint)p; + } + while (j > 0); + + for (int i = 1; i < len; ++i) + { + c = SquareWordAdd(x, i, zz); + AddWordAt(extLen, c, zz, i << 1); + } + + ShiftUpBit(extLen, zz, x[0] << 31); + } + + public static void Square(int len, uint[] x, int xOff, uint[] zz, int zzOff) + { + int extLen = len << 1; + uint c = 0; + int j = len, k = extLen; + do + { + ulong xVal = (ulong)x[xOff + --j]; + ulong p = xVal * xVal; + zz[zzOff + --k] = (c << 31) | (uint)(p >> 33); + zz[zzOff + --k] = (uint)(p >> 1); + c = (uint)p; + } + while (j > 0); + + for (int i = 1; i < len; ++i) + { + c = SquareWordAdd(x, xOff, i, zz, zzOff); + AddWordAt(extLen, c, zz, zzOff, i << 1); + } + + ShiftUpBit(extLen, zz, zzOff, x[xOff] << 31); + } + + public static uint SquareWordAdd(uint[] x, int xPos, uint[] z) + { + ulong c = 0, xVal = (ulong)x[xPos]; + int i = 0; + do + { + c += xVal * x[i] + z[xPos + i]; + z[xPos + i] = (uint)c; + c >>= 32; + } + while (++i < xPos); + return (uint)c; + } + + public static uint SquareWordAdd(uint[] x, int xOff, int xPos, uint[] z, int zOff) + { + ulong c = 0, xVal = (ulong)x[xOff + xPos]; + int i = 0; + do + { + c += xVal * (x[xOff + i] & M) + (z[xPos + zOff] & M); + z[xPos + zOff] = (uint)c; + c >>= 32; + ++zOff; + } + while (++i < xPos); + return (uint)c; + } + + public static int Sub(int len, uint[] x, uint[] y, uint[] z) + { + long c = 0; + for (int i = 0; i < len; ++i) + { + c += (long)x[i] - y[i]; + z[i] = (uint)c; + c >>= 32; + } + return (int)c; + } + + public static int Sub(int len, uint[] x, int xOff, uint[] y, int yOff, uint[] z, int zOff) + { + long c = 0; + for (int i = 0; i < len; ++i) + { + c += (long)x[xOff + i] - y[yOff + i]; + z[zOff + i] = (uint)c; + c >>= 32; + } + return (int)c; + } + public static int Sub33At(int len, uint x, uint[] z, int zPos) + { + Debug.Assert(zPos <= (len - 2)); + long c = (long)z[zPos + 0] - x; + z[zPos + 0] = (uint)c; + c >>= 32; + c += (long)z[zPos + 1] - 1; + z[zPos + 1] = (uint)c; + c >>= 32; + return c == 0 ? 0 : DecAt(len, z, zPos + 2); + } + + public static int Sub33At(int len, uint x, uint[] z, int zOff, int zPos) + { + Debug.Assert(zPos <= (len - 2)); + long c = (long)z[zOff + zPos] - x; + z[zOff + zPos] = (uint)c; + c >>= 32; + c += (long)z[zOff + zPos + 1] - 1; + z[zOff + zPos + 1] = (uint)c; + c >>= 32; + return c == 0 ? 0 : DecAt(len, z, zOff, zPos + 2); + } + + public static int Sub33From(int len, uint x, uint[] z) + { + long c = (long)z[0] - x; + z[0] = (uint)c; + c >>= 32; + c += (long)z[1] - 1; + z[1] = (uint)c; + c >>= 32; + return c == 0 ? 0 : DecAt(len, z, 2); + } + + public static int Sub33From(int len, uint x, uint[] z, int zOff) + { + long c = (long)z[zOff + 0] - x; + z[zOff + 0] = (uint)c; + c >>= 32; + c += (long)z[zOff + 1] - 1; + z[zOff + 1] = (uint)c; + c >>= 32; + return c == 0 ? 0 : DecAt(len, z, zOff, 2); + } + + public static int SubBothFrom(int len, uint[] x, uint[] y, uint[] z) + { + long c = 0; + for (int i = 0; i < len; ++i) + { + c += (long)z[i] - x[i] - y[i]; + z[i] = (uint)c; + c >>= 32; + } + return (int)c; + } + + public static int SubBothFrom(int len, uint[] x, int xOff, uint[] y, int yOff, uint[] z, int zOff) + { + long c = 0; + for (int i = 0; i < len; ++i) + { + c += (long)z[zOff + i] - x[xOff + i] - y[yOff + i]; + z[zOff + i] = (uint)c; + c >>= 32; + } + return (int)c; + } + + public static int SubDWordAt(int len, ulong x, uint[] z, int zPos) + { + Debug.Assert(zPos <= (len - 2)); + long c = (long)z[zPos + 0] - (long)(x & M); + z[zPos + 0] = (uint)c; + c >>= 32; + c += (long)z[zPos + 1] - (long)(x >> 32); + z[zPos + 1] = (uint)c; + c >>= 32; + return c == 0 ? 0 : DecAt(len, z, zPos + 2); + } + + public static int SubDWordAt(int len, ulong x, uint[] z, int zOff, int zPos) + { + Debug.Assert(zPos <= (len - 2)); + long c = (long)z[zOff + zPos] - (long)(x & M); + z[zOff + zPos] = (uint)c; + c >>= 32; + c += (long)z[zOff + zPos + 1] - (long)(x >> 32); + z[zOff + zPos + 1] = (uint)c; + c >>= 32; + return c == 0 ? 0 : DecAt(len, z, zOff, zPos + 2); + } + + public static int SubDWordFrom(int len, ulong x, uint[] z) + { + long c = (long)z[0] - (long)(x & M); + z[0] = (uint)c; + c >>= 32; + c += (long)z[1] - (long)(x >> 32); + z[1] = (uint)c; + c >>= 32; + return c == 0 ? 0 : DecAt(len, z, 2); + } + + public static int SubDWordFrom(int len, ulong x, uint[] z, int zOff) + { + long c = (long)z[zOff + 0] - (long)(x & M); + z[zOff + 0] = (uint)c; + c >>= 32; + c += (long)z[zOff + 1] - (long)(x >> 32); + z[zOff + 1] = (uint)c; + c >>= 32; + return c == 0 ? 0 : DecAt(len, z, zOff, 2); + } + + public static int SubFrom(int len, uint[] x, uint[] z) + { + long c = 0; + for (int i = 0; i < len; ++i) + { + c += (long)z[i] - x[i]; + z[i] = (uint)c; + c >>= 32; + } + return (int)c; + } + + public static int SubFrom(int len, uint[] x, int xOff, uint[] z, int zOff) + { + long c = 0; + for (int i = 0; i < len; ++i) + { + c += (long)z[zOff + i] - x[xOff + i]; + z[zOff + i] = (uint)c; + c >>= 32; + } + return (int)c; + } + + public static int SubWordAt(int len, uint x, uint[] z, int zPos) + { + Debug.Assert(zPos <= (len - 1)); + long c = (long)z[zPos] - x; + z[zPos] = (uint)c; + c >>= 32; + return c == 0 ? 0 : DecAt(len, z, zPos + 1); + } + + public static int SubWordAt(int len, uint x, uint[] z, int zOff, int zPos) + { + Debug.Assert(zPos <= (len - 1)); + long c = (long)z[zOff + zPos] - x; + z[zOff + zPos] = (uint)c; + c >>= 32; + return c == 0 ? 0 : DecAt(len, z, zOff, zPos + 1); + } + + public static int SubWordFrom(int len, uint x, uint[] z) + { + long c = (long)z[0] - x; + z[0] = (uint)c; + c >>= 32; + return c == 0 ? 0 : DecAt(len, z, 1); + } + + public static int SubWordFrom(int len, uint x, uint[] z, int zOff) + { + long c = (long)z[zOff + 0] - x; + z[zOff + 0] = (uint)c; + c >>= 32; + return c == 0 ? 0 : DecAt(len, z, zOff, 1); + } + + public static BigInteger ToBigInteger(int len, uint[] x) + { + byte[] bs = new byte[len << 2]; + for (int i = 0; i < len; ++i) + { + uint x_i = x[i]; + if (x_i != 0) + { + Pack.UInt32_To_BE(x_i, bs, (len - 1 - i) << 2); + } + } + return new BigInteger(1, bs); + } + + public static void Zero(int len, uint[] z) + { + for (int i = 0; i < len; ++i) + { + z[i] = 0; + } + } + } +} diff --git a/bc-sharp-crypto/src/math/raw/Nat128.cs b/bc-sharp-crypto/src/math/raw/Nat128.cs new file mode 100644 index 0000000..1d3b64d --- /dev/null +++ b/bc-sharp-crypto/src/math/raw/Nat128.cs @@ -0,0 +1,856 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Crypto.Utilities; + +namespace Org.BouncyCastle.Math.Raw +{ + internal abstract class Nat128 + { + private const ulong M = 0xFFFFFFFFUL; + + public static uint Add(uint[] x, uint[] y, uint[] z) + { + ulong c = 0; + c += (ulong)x[0] + y[0]; + z[0] = (uint)c; + c >>= 32; + c += (ulong)x[1] + y[1]; + z[1] = (uint)c; + c >>= 32; + c += (ulong)x[2] + y[2]; + z[2] = (uint)c; + c >>= 32; + c += (ulong)x[3] + y[3]; + z[3] = (uint)c; + c >>= 32; + return (uint)c; + } + + public static uint AddBothTo(uint[] x, uint[] y, uint[] z) + { + ulong c = 0; + c += (ulong)x[0] + y[0] + z[0]; + z[0] = (uint)c; + c >>= 32; + c += (ulong)x[1] + y[1] + z[1]; + z[1] = (uint)c; + c >>= 32; + c += (ulong)x[2] + y[2] + z[2]; + z[2] = (uint)c; + c >>= 32; + c += (ulong)x[3] + y[3] + z[3]; + z[3] = (uint)c; + c >>= 32; + return (uint)c; + } + + public static uint AddTo(uint[] x, uint[] z) + { + ulong c = 0; + c += (ulong)x[0] + z[0]; + z[0] = (uint)c; + c >>= 32; + c += (ulong)x[1] + z[1]; + z[1] = (uint)c; + c >>= 32; + c += (ulong)x[2] + z[2]; + z[2] = (uint)c; + c >>= 32; + c += (ulong)x[3] + z[3]; + z[3] = (uint)c; + c >>= 32; + return (uint)c; + } + + public static uint AddTo(uint[] x, int xOff, uint[] z, int zOff, uint cIn) + { + ulong c = cIn; + c += (ulong)x[xOff + 0] + z[zOff + 0]; + z[zOff + 0] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 1] + z[zOff + 1]; + z[zOff + 1] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 2] + z[zOff + 2]; + z[zOff + 2] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 3] + z[zOff + 3]; + z[zOff + 3] = (uint)c; + c >>= 32; + return (uint)c; + } + + public static uint AddToEachOther(uint[] u, int uOff, uint[] v, int vOff) + { + ulong c = 0; + c += (ulong)u[uOff + 0] + v[vOff + 0]; + u[uOff + 0] = (uint)c; + v[vOff + 0] = (uint)c; + c >>= 32; + c += (ulong)u[uOff + 1] + v[vOff + 1]; + u[uOff + 1] = (uint)c; + v[vOff + 1] = (uint)c; + c >>= 32; + c += (ulong)u[uOff + 2] + v[vOff + 2]; + u[uOff + 2] = (uint)c; + v[vOff + 2] = (uint)c; + c >>= 32; + c += (ulong)u[uOff + 3] + v[vOff + 3]; + u[uOff + 3] = (uint)c; + v[vOff + 3] = (uint)c; + c >>= 32; + return (uint)c; + } + + public static void Copy(uint[] x, uint[] z) + { + z[0] = x[0]; + z[1] = x[1]; + z[2] = x[2]; + z[3] = x[3]; + } + + public static void Copy64(ulong[] x, ulong[] z) + { + z[0] = x[0]; + z[1] = x[1]; + } + + public static uint[] Create() + { + return new uint[4]; + } + + public static ulong[] Create64() + { + return new ulong[2]; + } + + public static uint[] CreateExt() + { + return new uint[8]; + } + + public static ulong[] CreateExt64() + { + return new ulong[4]; + } + + public static bool Diff(uint[] x, int xOff, uint[] y, int yOff, uint[] z, int zOff) + { + bool pos = Gte(x, xOff, y, yOff); + if (pos) + { + Sub(x, xOff, y, yOff, z, zOff); + } + else + { + Sub(y, yOff, x, xOff, z, zOff); + } + return pos; + } + + public static bool Eq(uint[] x, uint[] y) + { + for (int i = 3; i >= 0; --i) + { + if (x[i] != y[i]) + return false; + } + return true; + } + + public static bool Eq64(ulong[] x, ulong[] y) + { + for (int i = 1; i >= 0; --i) + { + if (x[i] != y[i]) + return false; + } + return true; + } + + public static uint[] FromBigInteger(BigInteger x) + { + if (x.SignValue < 0 || x.BitLength > 128) + throw new ArgumentException(); + + uint[] z = Create(); + int i = 0; + while (x.SignValue != 0) + { + z[i++] = (uint)x.IntValue; + x = x.ShiftRight(32); + } + return z; + } + + public static ulong[] FromBigInteger64(BigInteger x) + { + if (x.SignValue < 0 || x.BitLength > 128) + throw new ArgumentException(); + + ulong[] z = Create64(); + int i = 0; + while (x.SignValue != 0) + { + z[i++] = (ulong)x.LongValue; + x = x.ShiftRight(64); + } + return z; + } + + public static uint GetBit(uint[] x, int bit) + { + if (bit == 0) + { + return x[0] & 1; + } + if ((bit & 127) != bit) + { + return 0; + } + int w = bit >> 5; + int b = bit & 31; + return (x[w] >> b) & 1; + } + + public static bool Gte(uint[] x, uint[] y) + { + for (int i = 3; i >= 0; --i) + { + uint x_i = x[i], y_i = y[i]; + if (x_i < y_i) + return false; + if (x_i > y_i) + return true; + } + return true; + } + + public static bool Gte(uint[] x, int xOff, uint[] y, int yOff) + { + for (int i = 3; i >= 0; --i) + { + uint x_i = x[xOff + i], y_i = y[yOff + i]; + if (x_i < y_i) + return false; + if (x_i > y_i) + return true; + } + return true; + } + + public static bool IsOne(uint[] x) + { + if (x[0] != 1) + { + return false; + } + for (int i = 1; i < 4; ++i) + { + if (x[i] != 0) + { + return false; + } + } + return true; + } + + public static bool IsOne64(ulong[] x) + { + if (x[0] != 1UL) + { + return false; + } + for (int i = 1; i < 2; ++i) + { + if (x[i] != 0UL) + { + return false; + } + } + return true; + } + + public static bool IsZero(uint[] x) + { + for (int i = 0; i < 4; ++i) + { + if (x[i] != 0) + { + return false; + } + } + return true; + } + + public static bool IsZero64(ulong[] x) + { + for (int i = 0; i < 2; ++i) + { + if (x[i] != 0UL) + { + return false; + } + } + return true; + } + + public static void Mul(uint[] x, uint[] y, uint[] zz) + { + ulong y_0 = y[0]; + ulong y_1 = y[1]; + ulong y_2 = y[2]; + ulong y_3 = y[3]; + + { + ulong c = 0, x_0 = x[0]; + c += x_0 * y_0; + zz[0] = (uint)c; + c >>= 32; + c += x_0 * y_1; + zz[1] = (uint)c; + c >>= 32; + c += x_0 * y_2; + zz[2] = (uint)c; + c >>= 32; + c += x_0 * y_3; + zz[3] = (uint)c; + c >>= 32; + zz[4] = (uint)c; + } + + for (int i = 1; i < 4; ++i) + { + ulong c = 0, x_i = x[i]; + c += x_i * y_0 + zz[i + 0]; + zz[i + 0] = (uint)c; + c >>= 32; + c += x_i * y_1 + zz[i + 1]; + zz[i + 1] = (uint)c; + c >>= 32; + c += x_i * y_2 + zz[i + 2]; + zz[i + 2] = (uint)c; + c >>= 32; + c += x_i * y_3 + zz[i + 3]; + zz[i + 3] = (uint)c; + c >>= 32; + zz[i + 4] = (uint)c; + } + } + + public static void Mul(uint[] x, int xOff, uint[] y, int yOff, uint[] zz, int zzOff) + { + ulong y_0 = y[yOff + 0]; + ulong y_1 = y[yOff + 1]; + ulong y_2 = y[yOff + 2]; + ulong y_3 = y[yOff + 3]; + + { + ulong c = 0, x_0 = x[xOff + 0]; + c += x_0 * y_0; + zz[zzOff + 0] = (uint)c; + c >>= 32; + c += x_0 * y_1; + zz[zzOff + 1] = (uint)c; + c >>= 32; + c += x_0 * y_2; + zz[zzOff + 2] = (uint)c; + c >>= 32; + c += x_0 * y_3; + zz[zzOff + 3] = (uint)c; + c >>= 32; + zz[zzOff + 4] = (uint)c; + } + + for (int i = 1; i < 4; ++i) + { + ++zzOff; + ulong c = 0, x_i = x[xOff + i]; + c += x_i * y_0 + zz[zzOff + 0]; + zz[zzOff + 0] = (uint)c; + c >>= 32; + c += x_i * y_1 + zz[zzOff + 1]; + zz[zzOff + 1] = (uint)c; + c >>= 32; + c += x_i * y_2 + zz[zzOff + 2]; + zz[zzOff + 2] = (uint)c; + c >>= 32; + c += x_i * y_3 + zz[zzOff + 3]; + zz[zzOff + 3] = (uint)c; + c >>= 32; + zz[zzOff + 4] = (uint)c; + } + } + + public static uint MulAddTo(uint[] x, uint[] y, uint[] zz) + { + ulong y_0 = y[0]; + ulong y_1 = y[1]; + ulong y_2 = y[2]; + ulong y_3 = y[3]; + + ulong zc = 0; + for (int i = 0; i < 4; ++i) + { + ulong c = 0, x_i = x[i]; + c += x_i * y_0 + zz[i + 0]; + zz[i + 0] = (uint)c; + c >>= 32; + c += x_i * y_1 + zz[i + 1]; + zz[i + 1] = (uint)c; + c >>= 32; + c += x_i * y_2 + zz[i + 2]; + zz[i + 2] = (uint)c; + c >>= 32; + c += x_i * y_3 + zz[i + 3]; + zz[i + 3] = (uint)c; + c >>= 32; + c += zc + zz[i + 4]; + zz[i + 4] = (uint)c; + zc = c >> 32; + } + return (uint)zc; + } + + public static uint MulAddTo(uint[] x, int xOff, uint[] y, int yOff, uint[] zz, int zzOff) + { + ulong y_0 = y[yOff + 0]; + ulong y_1 = y[yOff + 1]; + ulong y_2 = y[yOff + 2]; + ulong y_3 = y[yOff + 3]; + + ulong zc = 0; + for (int i = 0; i < 4; ++i) + { + ulong c = 0, x_i = x[xOff + i]; + c += x_i * y_0 + zz[zzOff + 0]; + zz[zzOff + 0] = (uint)c; + c >>= 32; + c += x_i * y_1 + zz[zzOff + 1]; + zz[zzOff + 1] = (uint)c; + c >>= 32; + c += x_i * y_2 + zz[zzOff + 2]; + zz[zzOff + 2] = (uint)c; + c >>= 32; + c += x_i * y_3 + zz[zzOff + 3]; + zz[zzOff + 3] = (uint)c; + c >>= 32; + c += zc + zz[zzOff + 4]; + zz[zzOff + 4] = (uint)c; + zc = c >> 32; + ++zzOff; + } + return (uint)zc; + } + + public static ulong Mul33Add(uint w, uint[] x, int xOff, uint[] y, int yOff, uint[] z, int zOff) + { + Debug.Assert(w >> 31 == 0); + + ulong c = 0, wVal = w; + ulong x0 = x[xOff + 0]; + c += wVal * x0 + y[yOff + 0]; + z[zOff + 0] = (uint)c; + c >>= 32; + ulong x1 = x[xOff + 1]; + c += wVal * x1 + x0 + y[yOff + 1]; + z[zOff + 1] = (uint)c; + c >>= 32; + ulong x2 = x[xOff + 2]; + c += wVal * x2 + x1 + y[yOff + 2]; + z[zOff + 2] = (uint)c; + c >>= 32; + ulong x3 = x[xOff + 3]; + c += wVal * x3 + x2 + y[yOff + 3]; + z[zOff + 3] = (uint)c; + c >>= 32; + c += x3; + return c; + } + + public static uint MulWordAddExt(uint x, uint[] yy, int yyOff, uint[] zz, int zzOff) + { + Debug.Assert(yyOff <= 4); + Debug.Assert(zzOff <= 4); + + ulong c = 0, xVal = x; + c += xVal * yy[yyOff + 0] + zz[zzOff + 0]; + zz[zzOff + 0] = (uint)c; + c >>= 32; + c += xVal * yy[yyOff + 1] + zz[zzOff + 1]; + zz[zzOff + 1] = (uint)c; + c >>= 32; + c += xVal * yy[yyOff + 2] + zz[zzOff + 2]; + zz[zzOff + 2] = (uint)c; + c >>= 32; + c += xVal * yy[yyOff + 3] + zz[zzOff + 3]; + zz[zzOff + 3] = (uint)c; + c >>= 32; + return (uint)c; + } + + public static uint Mul33DWordAdd(uint x, ulong y, uint[] z, int zOff) + { + Debug.Assert(x >> 31 == 0); + Debug.Assert(zOff <= 0); + ulong c = 0, xVal = x; + ulong y00 = y & M; + c += xVal * y00 + z[zOff + 0]; + z[zOff + 0] = (uint)c; + c >>= 32; + ulong y01 = y >> 32; + c += xVal * y01 + y00 + z[zOff + 1]; + z[zOff + 1] = (uint)c; + c >>= 32; + c += y01 + z[zOff + 2]; + z[zOff + 2] = (uint)c; + c >>= 32; + c += z[zOff + 3]; + z[zOff + 3] = (uint)c; + c >>= 32; + return (uint)c; + } + + public static uint Mul33WordAdd(uint x, uint y, uint[] z, int zOff) + { + Debug.Assert(x >> 31 == 0); + Debug.Assert(zOff <= 1); + ulong c = 0, yVal = y; + c += yVal * x + z[zOff + 0]; + z[zOff + 0] = (uint)c; + c >>= 32; + c += yVal + z[zOff + 1]; + z[zOff + 1] = (uint)c; + c >>= 32; + c += z[zOff + 2]; + z[zOff + 2] = (uint)c; + c >>= 32; + return c == 0 ? 0 : Nat.IncAt(4, z, zOff, 3); + } + + public static uint MulWordDwordAdd(uint x, ulong y, uint[] z, int zOff) + { + Debug.Assert(zOff <= 1); + ulong c = 0, xVal = x; + c += xVal * y + z[zOff + 0]; + z[zOff + 0] = (uint)c; + c >>= 32; + c += xVal * (y >> 32) + z[zOff + 1]; + z[zOff + 1] = (uint)c; + c >>= 32; + c += z[zOff + 2]; + z[zOff + 2] = (uint)c; + c >>= 32; + return c == 0 ? 0 : Nat.IncAt(4, z, zOff, 3); + } + + public static uint MulWordsAdd(uint x, uint y, uint[] z, int zOff) + { + Debug.Assert(zOff <= 2); + + ulong c = 0, xVal = x, yVal = y; + c += yVal * xVal + z[zOff + 0]; + z[zOff + 0] = (uint)c; + c >>= 32; + c += z[zOff + 1]; + z[zOff + 1] = (uint)c; + c >>= 32; + return c == 0 ? 0 : Nat.IncAt(4, z, zOff, 2); + } + + public static uint MulWord(uint x, uint[] y, uint[] z, int zOff) + { + ulong c = 0, xVal = x; + int i = 0; + do + { + c += xVal * y[i]; + z[zOff + i] = (uint)c; + c >>= 32; + } + while (++i < 4); + return (uint)c; + } + + public static void Square(uint[] x, uint[] zz) + { + ulong x_0 = x[0]; + ulong zz_1; + + uint c = 0, w; + { + int i = 3, j = 8; + do + { + ulong xVal = x[i--]; + ulong p = xVal * xVal; + zz[--j] = (c << 31) | (uint)(p >> 33); + zz[--j] = (uint)(p >> 1); + c = (uint)p; + } + while (i > 0); + + { + ulong p = x_0 * x_0; + zz_1 = (ulong)(c << 31) | (p >> 33); + zz[0] = (uint)p; + c = (uint)(p >> 32) & 1; + } + } + + ulong x_1 = x[1]; + ulong zz_2 = zz[2]; + + { + zz_1 += x_1 * x_0; + w = (uint)zz_1; + zz[1] = (w << 1) | c; + c = w >> 31; + zz_2 += zz_1 >> 32; + } + + ulong x_2 = x[2]; + ulong zz_3 = zz[3]; + ulong zz_4 = zz[4]; + { + zz_2 += x_2 * x_0; + w = (uint)zz_2; + zz[2] = (w << 1) | c; + c = w >> 31; + zz_3 += (zz_2 >> 32) + x_2 * x_1; + zz_4 += zz_3 >> 32; + zz_3 &= M; + } + + ulong x_3 = x[3]; + ulong zz_5 = zz[5] + (zz_4 >> 32); zz_4 &= M; + ulong zz_6 = zz[6] + (zz_5 >> 32); zz_5 &= M; + { + zz_3 += x_3 * x_0; + w = (uint)zz_3; + zz[3] = (w << 1) | c; + c = w >> 31; + zz_4 += (zz_3 >> 32) + x_3 * x_1; + zz_5 += (zz_4 >> 32) + x_3 * x_2; + zz_6 += zz_5 >> 32; + } + + w = (uint)zz_4; + zz[4] = (w << 1) | c; + c = w >> 31; + w = (uint)zz_5; + zz[5] = (w << 1) | c; + c = w >> 31; + w = (uint)zz_6; + zz[6] = (w << 1) | c; + c = w >> 31; + w = zz[7] + (uint)(zz_6 >> 32); + zz[7] = (w << 1) | c; + } + + public static void Square(uint[] x, int xOff, uint[] zz, int zzOff) + { + ulong x_0 = x[xOff + 0]; + ulong zz_1; + + uint c = 0, w; + { + int i = 3, j = 8; + do + { + ulong xVal = x[xOff + i--]; + ulong p = xVal * xVal; + zz[zzOff + --j] = (c << 31) | (uint)(p >> 33); + zz[zzOff + --j] = (uint)(p >> 1); + c = (uint)p; + } + while (i > 0); + + { + ulong p = x_0 * x_0; + zz_1 = (ulong)(c << 31) | (p >> 33); + zz[zzOff + 0] = (uint)p; + c = (uint)(p >> 32) & 1; + } + } + + ulong x_1 = x[xOff + 1]; + ulong zz_2 = zz[zzOff + 2]; + + { + zz_1 += x_1 * x_0; + w = (uint)zz_1; + zz[zzOff + 1] = (w << 1) | c; + c = w >> 31; + zz_2 += zz_1 >> 32; + } + + ulong x_2 = x[xOff + 2]; + ulong zz_3 = zz[zzOff + 3]; + ulong zz_4 = zz[zzOff + 4]; + { + zz_2 += x_2 * x_0; + w = (uint)zz_2; + zz[zzOff + 2] = (w << 1) | c; + c = w >> 31; + zz_3 += (zz_2 >> 32) + x_2 * x_1; + zz_4 += zz_3 >> 32; + zz_3 &= M; + } + + ulong x_3 = x[xOff + 3]; + ulong zz_5 = zz[zzOff + 5] + (zz_4 >> 32); zz_4 &= M; + ulong zz_6 = zz[zzOff + 6] + (zz_5 >> 32); zz_5 &= M; + { + zz_3 += x_3 * x_0; + w = (uint)zz_3; + zz[zzOff + 3] = (w << 1) | c; + c = w >> 31; + zz_4 += (zz_3 >> 32) + x_3 * x_1; + zz_5 += (zz_4 >> 32) + x_3 * x_2; + zz_6 += zz_5 >> 32; + } + + w = (uint)zz_4; + zz[zzOff + 4] = (w << 1) | c; + c = w >> 31; + w = (uint)zz_5; + zz[zzOff + 5] = (w << 1) | c; + c = w >> 31; + w = (uint)zz_6; + zz[zzOff + 6] = (w << 1) | c; + c = w >> 31; + w = zz[zzOff + 7] + (uint)(zz_6 >> 32); + zz[zzOff + 7] = (w << 1) | c; + } + + public static int Sub(uint[] x, uint[] y, uint[] z) + { + long c = 0; + c += (long)x[0] - y[0]; + z[0] = (uint)c; + c >>= 32; + c += (long)x[1] - y[1]; + z[1] = (uint)c; + c >>= 32; + c += (long)x[2] - y[2]; + z[2] = (uint)c; + c >>= 32; + c += (long)x[3] - y[3]; + z[3] = (uint)c; + c >>= 32; + return (int)c; + } + + public static int Sub(uint[] x, int xOff, uint[] y, int yOff, uint[] z, int zOff) + { + long c = 0; + c += (long)x[xOff + 0] - y[yOff + 0]; + z[zOff + 0] = (uint)c; + c >>= 32; + c += (long)x[xOff + 1] - y[yOff + 1]; + z[zOff + 1] = (uint)c; + c >>= 32; + c += (long)x[xOff + 2] - y[yOff + 2]; + z[zOff + 2] = (uint)c; + c >>= 32; + c += (long)x[xOff + 3] - y[yOff + 3]; + z[zOff + 3] = (uint)c; + c >>= 32; + return (int)c; + } + + public static int SubBothFrom(uint[] x, uint[] y, uint[] z) + { + long c = 0; + c += (long)z[0] - x[0] - y[0]; + z[0] = (uint)c; + c >>= 32; + c += (long)z[1] - x[1] - y[1]; + z[1] = (uint)c; + c >>= 32; + c += (long)z[2] - x[2] - y[2]; + z[2] = (uint)c; + c >>= 32; + c += (long)z[3] - x[3] - y[3]; + z[3] = (uint)c; + c >>= 32; + return (int)c; + } + + public static int SubFrom(uint[] x, uint[] z) + { + long c = 0; + c += (long)z[0] - x[0]; + z[0] = (uint)c; + c >>= 32; + c += (long)z[1] - x[1]; + z[1] = (uint)c; + c >>= 32; + c += (long)z[2] - x[2]; + z[2] = (uint)c; + c >>= 32; + c += (long)z[3] - x[3]; + z[3] = (uint)c; + c >>= 32; + return (int)c; + } + + public static int SubFrom(uint[] x, int xOff, uint[] z, int zOff) + { + long c = 0; + c += (long)z[zOff + 0] - x[xOff + 0]; + z[zOff + 0] = (uint)c; + c >>= 32; + c += (long)z[zOff + 1] - x[xOff + 1]; + z[zOff + 1] = (uint)c; + c >>= 32; + c += (long)z[zOff + 2] - x[xOff + 2]; + z[zOff + 2] = (uint)c; + c >>= 32; + c += (long)z[zOff + 3] - x[xOff + 3]; + z[zOff + 3] = (uint)c; + c >>= 32; + return (int)c; + } + + public static BigInteger ToBigInteger(uint[] x) + { + byte[] bs = new byte[16]; + for (int i = 0; i < 4; ++i) + { + uint x_i = x[i]; + if (x_i != 0) + { + Pack.UInt32_To_BE(x_i, bs, (3 - i) << 2); + } + } + return new BigInteger(1, bs); + } + + public static BigInteger ToBigInteger64(ulong[] x) + { + byte[] bs = new byte[16]; + for (int i = 0; i < 2; ++i) + { + ulong x_i = x[i]; + if (x_i != 0UL) + { + Pack.UInt64_To_BE(x_i, bs, (1 - i) << 3); + } + } + return new BigInteger(1, bs); + } + + public static void Zero(uint[] z) + { + z[0] = 0; + z[1] = 0; + z[2] = 0; + z[3] = 0; + } + } +} diff --git a/bc-sharp-crypto/src/math/raw/Nat160.cs b/bc-sharp-crypto/src/math/raw/Nat160.cs new file mode 100644 index 0000000..1fd00e5 --- /dev/null +++ b/bc-sharp-crypto/src/math/raw/Nat160.cs @@ -0,0 +1,874 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Crypto.Utilities; + +namespace Org.BouncyCastle.Math.Raw +{ + internal abstract class Nat160 + { + private const ulong M = 0xFFFFFFFFUL; + + public static uint Add(uint[] x, uint[] y, uint[] z) + { + ulong c = 0; + c += (ulong)x[0] + y[0]; + z[0] = (uint)c; + c >>= 32; + c += (ulong)x[1] + y[1]; + z[1] = (uint)c; + c >>= 32; + c += (ulong)x[2] + y[2]; + z[2] = (uint)c; + c >>= 32; + c += (ulong)x[3] + y[3]; + z[3] = (uint)c; + c >>= 32; + c += (ulong)x[4] + y[4]; + z[4] = (uint)c; + c >>= 32; + return (uint)c; + } + + public static uint AddBothTo(uint[] x, uint[] y, uint[] z) + { + ulong c = 0; + c += (ulong)x[0] + y[0] + z[0]; + z[0] = (uint)c; + c >>= 32; + c += (ulong)x[1] + y[1] + z[1]; + z[1] = (uint)c; + c >>= 32; + c += (ulong)x[2] + y[2] + z[2]; + z[2] = (uint)c; + c >>= 32; + c += (ulong)x[3] + y[3] + z[3]; + z[3] = (uint)c; + c >>= 32; + c += (ulong)x[4] + y[4] + z[4]; + z[4] = (uint)c; + c >>= 32; + return (uint)c; + } + + public static uint AddTo(uint[] x, uint[] z) + { + ulong c = 0; + c += (ulong)x[0] + z[0]; + z[0] = (uint)c; + c >>= 32; + c += (ulong)x[1] + z[1]; + z[1] = (uint)c; + c >>= 32; + c += (ulong)x[2] + z[2]; + z[2] = (uint)c; + c >>= 32; + c += (ulong)x[3] + z[3]; + z[3] = (uint)c; + c >>= 32; + c += (ulong)x[4] + z[4]; + z[4] = (uint)c; + c >>= 32; + return (uint)c; + } + + public static uint AddTo(uint[] x, int xOff, uint[] z, int zOff, uint cIn) + { + ulong c = cIn; + c += (ulong)x[xOff + 0] + z[zOff + 0]; + z[zOff + 0] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 1] + z[zOff + 1]; + z[zOff + 1] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 2] + z[zOff + 2]; + z[zOff + 2] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 3] + z[zOff + 3]; + z[zOff + 3] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 4] + z[zOff + 4]; + z[zOff + 4] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 5] + z[zOff + 5]; + return (uint)c; + } + + public static uint AddToEachOther(uint[] u, int uOff, uint[] v, int vOff) + { + ulong c = 0; + c += (ulong)u[uOff + 0] + v[vOff + 0]; + u[uOff + 0] = (uint)c; + v[vOff + 0] = (uint)c; + c >>= 32; + c += (ulong)u[uOff + 1] + v[vOff + 1]; + u[uOff + 1] = (uint)c; + v[vOff + 1] = (uint)c; + c >>= 32; + c += (ulong)u[uOff + 2] + v[vOff + 2]; + u[uOff + 2] = (uint)c; + v[vOff + 2] = (uint)c; + c >>= 32; + c += (ulong)u[uOff + 3] + v[vOff + 3]; + u[uOff + 3] = (uint)c; + v[vOff + 3] = (uint)c; + c >>= 32; + c += (ulong)u[uOff + 4] + v[vOff + 4]; + u[uOff + 4] = (uint)c; + v[vOff + 4] = (uint)c; + c >>= 32; + return (uint)c; + } + + public static void Copy(uint[] x, uint[] z) + { + z[0] = x[0]; + z[1] = x[1]; + z[2] = x[2]; + z[3] = x[3]; + z[4] = x[4]; + } + + public static uint[] Create() + { + return new uint[5]; + } + + public static uint[] CreateExt() + { + return new uint[10]; + } + + public static bool Diff(uint[] x, int xOff, uint[] y, int yOff, uint[] z, int zOff) + { + bool pos = Gte(x, xOff, y, yOff); + if (pos) + { + Sub(x, xOff, y, yOff, z, zOff); + } + else + { + Sub(y, yOff, x, xOff, z, zOff); + } + return pos; + } + + public static bool Eq(uint[] x, uint[] y) + { + for (int i = 4; i >= 0; --i) + { + if (x[i] != y[i]) + return false; + } + return true; + } + + public static uint[] FromBigInteger(BigInteger x) + { + if (x.SignValue < 0 || x.BitLength > 160) + throw new ArgumentException(); + + uint[] z = Create(); + int i = 0; + while (x.SignValue != 0) + { + z[i++] = (uint)x.IntValue; + x = x.ShiftRight(32); + } + return z; + } + + public static uint GetBit(uint[] x, int bit) + { + if (bit == 0) + { + return x[0] & 1; + } + int w = bit >> 5; + if (w < 0 || w >= 5) + { + return 0; + } + int b = bit & 31; + return (x[w] >> b) & 1; + } + + public static bool Gte(uint[] x, uint[] y) + { + for (int i = 4; i >= 0; --i) + { + uint x_i = x[i], y_i = y[i]; + if (x_i < y_i) + return false; + if (x_i > y_i) + return true; + } + return true; + } + + public static bool Gte(uint[] x, int xOff, uint[] y, int yOff) + { + for (int i = 4; i >= 0; --i) + { + uint x_i = x[xOff + i], y_i = y[yOff + i]; + if (x_i < y_i) + return false; + if (x_i > y_i) + return true; + } + return true; + } + + public static bool IsOne(uint[] x) + { + if (x[0] != 1) + { + return false; + } + for (int i = 1; i < 5; ++i) + { + if (x[i] != 0) + { + return false; + } + } + return true; + } + + public static bool IsZero(uint[] x) + { + for (int i = 0; i < 5; ++i) + { + if (x[i] != 0) + { + return false; + } + } + return true; + } + + public static void Mul(uint[] x, uint[] y, uint[] zz) + { + ulong y_0 = y[0]; + ulong y_1 = y[1]; + ulong y_2 = y[2]; + ulong y_3 = y[3]; + ulong y_4 = y[4]; + + { + ulong c = 0, x_0 = x[0]; + c += x_0 * y_0; + zz[0] = (uint)c; + c >>= 32; + c += x_0 * y_1; + zz[1] = (uint)c; + c >>= 32; + c += x_0 * y_2; + zz[2] = (uint)c; + c >>= 32; + c += x_0 * y_3; + zz[3] = (uint)c; + c >>= 32; + c += x_0 * y_4; + zz[4] = (uint)c; + c >>= 32; + zz[5] = (uint)c; + } + + for (int i = 1; i < 5; ++i) + { + ulong c = 0, x_i = x[i]; + c += x_i * y_0 + zz[i + 0]; + zz[i + 0] = (uint)c; + c >>= 32; + c += x_i * y_1 + zz[i + 1]; + zz[i + 1] = (uint)c; + c >>= 32; + c += x_i * y_2 + zz[i + 2]; + zz[i + 2] = (uint)c; + c >>= 32; + c += x_i * y_3 + zz[i + 3]; + zz[i + 3] = (uint)c; + c >>= 32; + c += x_i * y_4 + zz[i + 4]; + zz[i + 4] = (uint)c; + c >>= 32; + zz[i + 5] = (uint)c; + } + } + + public static void Mul(uint[] x, int xOff, uint[] y, int yOff, uint[] zz, int zzOff) + { + ulong y_0 = y[yOff + 0]; + ulong y_1 = y[yOff + 1]; + ulong y_2 = y[yOff + 2]; + ulong y_3 = y[yOff + 3]; + ulong y_4 = y[yOff + 4]; + + { + ulong c = 0, x_0 = x[xOff + 0]; + c += x_0 * y_0; + zz[zzOff + 0] = (uint)c; + c >>= 32; + c += x_0 * y_1; + zz[zzOff + 1] = (uint)c; + c >>= 32; + c += x_0 * y_2; + zz[zzOff + 2] = (uint)c; + c >>= 32; + c += x_0 * y_3; + zz[zzOff + 3] = (uint)c; + c >>= 32; + c += x_0 * y_4; + zz[zzOff + 4] = (uint)c; + c >>= 32; + zz[zzOff + 5] = (uint)c; + } + + for (int i = 1; i < 5; ++i) + { + ++zzOff; + ulong c = 0, x_i = x[xOff + i]; + c += x_i * y_0 + zz[zzOff + 0]; + zz[zzOff + 0] = (uint)c; + c >>= 32; + c += x_i * y_1 + zz[zzOff + 1]; + zz[zzOff + 1] = (uint)c; + c >>= 32; + c += x_i * y_2 + zz[zzOff + 2]; + zz[zzOff + 2] = (uint)c; + c >>= 32; + c += x_i * y_3 + zz[zzOff + 3]; + zz[zzOff + 3] = (uint)c; + c >>= 32; + c += x_i * y_4 + zz[zzOff + 4]; + zz[zzOff + 4] = (uint)c; + c >>= 32; + zz[zzOff + 5] = (uint)c; + } + } + + public static uint MulAddTo(uint[] x, uint[] y, uint[] zz) + { + ulong y_0 = y[0]; + ulong y_1 = y[1]; + ulong y_2 = y[2]; + ulong y_3 = y[3]; + ulong y_4 = y[4]; + + ulong zc = 0; + for (int i = 0; i < 5; ++i) + { + ulong c = 0, x_i = x[i]; + c += x_i * y_0 + zz[i + 0]; + zz[i + 0] = (uint)c; + c >>= 32; + c += x_i * y_1 + zz[i + 1]; + zz[i + 1] = (uint)c; + c >>= 32; + c += x_i * y_2 + zz[i + 2]; + zz[i + 2] = (uint)c; + c >>= 32; + c += x_i * y_3 + zz[i + 3]; + zz[i + 3] = (uint)c; + c >>= 32; + c += x_i * y_4 + zz[i + 4]; + zz[i + 4] = (uint)c; + c >>= 32; + c += zc + zz[i + 5]; + zz[i + 5] = (uint)c; + zc = c >> 32; + } + return (uint)zc; + } + + public static uint MulAddTo(uint[] x, int xOff, uint[] y, int yOff, uint[] zz, int zzOff) + { + ulong y_0 = y[yOff + 0]; + ulong y_1 = y[yOff + 1]; + ulong y_2 = y[yOff + 2]; + ulong y_3 = y[yOff + 3]; + ulong y_4 = y[yOff + 4]; + + ulong zc = 0; + for (int i = 0; i < 5; ++i) + { + ulong c = 0, x_i = x[xOff + i]; + c += x_i * y_0 + zz[zzOff + 0]; + zz[zzOff + 0] = (uint)c; + c >>= 32; + c += x_i * y_1 + zz[zzOff + 1]; + zz[zzOff + 1] = (uint)c; + c >>= 32; + c += x_i * y_2 + zz[zzOff + 2]; + zz[zzOff + 2] = (uint)c; + c >>= 32; + c += x_i * y_3 + zz[zzOff + 3]; + zz[zzOff + 3] = (uint)c; + c >>= 32; + c += x_i * y_4 + zz[zzOff + 4]; + zz[zzOff + 4] = (uint)c; + c >>= 32; + c += zc + zz[zzOff + 5]; + zz[zzOff + 5] = (uint)c; + zc = c >> 32; + ++zzOff; + } + return (uint)zc; + } + + public static ulong Mul33Add(uint w, uint[] x, int xOff, uint[] y, int yOff, uint[] z, int zOff) + { + Debug.Assert(w >> 31 == 0); + + ulong c = 0, wVal = w; + ulong x0 = x[xOff + 0]; + c += wVal * x0 + y[yOff + 0]; + z[zOff + 0] = (uint)c; + c >>= 32; + ulong x1 = x[xOff + 1]; + c += wVal * x1 + x0 + y[yOff + 1]; + z[zOff + 1] = (uint)c; + c >>= 32; + ulong x2 = x[xOff + 2]; + c += wVal * x2 + x1 + y[yOff + 2]; + z[zOff + 2] = (uint)c; + c >>= 32; + ulong x3 = x[xOff + 3]; + c += wVal * x3 + x2 + y[yOff + 3]; + z[zOff + 3] = (uint)c; + c >>= 32; + ulong x4 = x[xOff + 4]; + c += wVal * x4 + x3 + y[yOff + 4]; + z[zOff + 4] = (uint)c; + c >>= 32; + c += x4; + return c; + } + + public static uint MulWordAddExt(uint x, uint[] yy, int yyOff, uint[] zz, int zzOff) + { + Debug.Assert(yyOff <= 5); + Debug.Assert(zzOff <= 5); + + ulong c = 0, xVal = x; + c += xVal * yy[yyOff + 0] + zz[zzOff + 0]; + zz[zzOff + 0] = (uint)c; + c >>= 32; + c += xVal * yy[yyOff + 1] + zz[zzOff + 1]; + zz[zzOff + 1] = (uint)c; + c >>= 32; + c += xVal * yy[yyOff + 2] + zz[zzOff + 2]; + zz[zzOff + 2] = (uint)c; + c >>= 32; + c += xVal * yy[yyOff + 3] + zz[zzOff + 3]; + zz[zzOff + 3] = (uint)c; + c >>= 32; + c += xVal * yy[yyOff + 4] + zz[zzOff + 4]; + zz[zzOff + 4] = (uint)c; + c >>= 32; + return (uint)c; + } + + public static uint Mul33DWordAdd(uint x, ulong y, uint[] z, int zOff) + { + Debug.Assert(x >> 31 == 0); + Debug.Assert(zOff <= 1); + ulong c = 0, xVal = x; + ulong y00 = y & M; + c += xVal * y00 + z[zOff + 0]; + z[zOff + 0] = (uint)c; + c >>= 32; + ulong y01 = y >> 32; + c += xVal * y01 + y00 + z[zOff + 1]; + z[zOff + 1] = (uint)c; + c >>= 32; + c += y01 + z[zOff + 2]; + z[zOff + 2] = (uint)c; + c >>= 32; + c += z[zOff + 3]; + z[zOff + 3] = (uint)c; + c >>= 32; + return c == 0 ? 0 : Nat.IncAt(5, z, zOff, 4); + } + + public static uint Mul33WordAdd(uint x, uint y, uint[] z, int zOff) + { + Debug.Assert(x >> 31 == 0); + Debug.Assert(zOff <= 2); + ulong c = 0, yVal = y; + c += yVal * x + z[zOff + 0]; + z[zOff + 0] = (uint)c; + c >>= 32; + c += yVal + z[zOff + 1]; + z[zOff + 1] = (uint)c; + c >>= 32; + c += z[zOff + 2]; + z[zOff + 2] = (uint)c; + c >>= 32; + return c == 0 ? 0 : Nat.IncAt(5, z, zOff, 3); + } + + public static uint MulWordDwordAdd(uint x, ulong y, uint[] z, int zOff) + { + Debug.Assert(zOff <= 2); + ulong c = 0, xVal = x; + c += xVal * y + z[zOff + 0]; + z[zOff + 0] = (uint)c; + c >>= 32; + c += xVal * (y >> 32) + z[zOff + 1]; + z[zOff + 1] = (uint)c; + c >>= 32; + c += z[zOff + 2]; + z[zOff + 2] = (uint)c; + c >>= 32; + return c == 0 ? 0 : Nat.IncAt(5, z, zOff, 3); + } + + public static uint MulWordsAdd(uint x, uint y, uint[] z, int zOff) + { + Debug.Assert(zOff <= 3); + + ulong c = 0, xVal = x, yVal = y; + c += yVal * xVal + z[zOff + 0]; + z[zOff + 0] = (uint)c; + c >>= 32; + c += z[zOff + 1]; + z[zOff + 1] = (uint)c; + c >>= 32; + return c == 0 ? 0 : Nat.IncAt(5, z, zOff, 2); + } + + public static uint MulWord(uint x, uint[] y, uint[] z, int zOff) + { + ulong c = 0, xVal = x; + int i = 0; + do + { + c += xVal * y[i]; + z[zOff + i] = (uint)c; + c >>= 32; + } + while (++i < 5); + return (uint)c; + } + + public static void Square(uint[] x, uint[] zz) + { + ulong x_0 = x[0]; + ulong zz_1; + + uint c = 0, w; + { + int i = 4, j = 10; + do + { + ulong xVal = x[i--]; + ulong p = xVal * xVal; + zz[--j] = (c << 31) | (uint)(p >> 33); + zz[--j] = (uint)(p >> 1); + c = (uint)p; + } + while (i > 0); + + { + ulong p = x_0 * x_0; + zz_1 = (ulong)(c << 31) | (p >> 33); + zz[0] = (uint)p; + c = (uint)(p >> 32) & 1; + } + } + + ulong x_1 = x[1]; + ulong zz_2 = zz[2]; + + { + zz_1 += x_1 * x_0; + w = (uint)zz_1; + zz[1] = (w << 1) | c; + c = w >> 31; + zz_2 += zz_1 >> 32; + } + + ulong x_2 = x[2]; + ulong zz_3 = zz[3]; + ulong zz_4 = zz[4]; + { + zz_2 += x_2 * x_0; + w = (uint)zz_2; + zz[2] = (w << 1) | c; + c = w >> 31; + zz_3 += (zz_2 >> 32) + x_2 * x_1; + zz_4 += zz_3 >> 32; + zz_3 &= M; + } + + ulong x_3 = x[3]; + ulong zz_5 = zz[5] + (zz_4 >> 32); zz_4 &= M; + ulong zz_6 = zz[6] + (zz_5 >> 32); zz_5 &= M; + { + zz_3 += x_3 * x_0; + w = (uint)zz_3; + zz[3] = (w << 1) | c; + c = w >> 31; + zz_4 += (zz_3 >> 32) + x_3 * x_1; + zz_5 += (zz_4 >> 32) + x_3 * x_2; + zz_4 &= M; + zz_6 += zz_5 >> 32; + zz_5 &= M; + } + + ulong x_4 = x[4]; + ulong zz_7 = zz[7] + (zz_6 >> 32); zz_6 &= M; + ulong zz_8 = zz[8] + (zz_7 >> 32); zz_7 &= M; + { + zz_4 += x_4 * x_0; + w = (uint)zz_4; + zz[4] = (w << 1) | c; + c = w >> 31; + zz_5 += (zz_4 >> 32) + x_4 * x_1; + zz_6 += (zz_5 >> 32) + x_4 * x_2; + zz_7 += (zz_6 >> 32) + x_4 * x_3; + zz_8 += zz_7 >> 32; + } + + w = (uint)zz_5; + zz[5] = (w << 1) | c; + c = w >> 31; + w = (uint)zz_6; + zz[6] = (w << 1) | c; + c = w >> 31; + w = (uint)zz_7; + zz[7] = (w << 1) | c; + c = w >> 31; + w = (uint)zz_8; + zz[8] = (w << 1) | c; + c = w >> 31; + w = zz[9] + (uint)(zz_8 >> 32); + zz[9] = (w << 1) | c; + } + + public static void Square(uint[] x, int xOff, uint[] zz, int zzOff) + { + ulong x_0 = x[xOff + 0]; + ulong zz_1; + + uint c = 0, w; + { + int i = 4, j = 10; + do + { + ulong xVal = x[xOff + i--]; + ulong p = xVal * xVal; + zz[zzOff + --j] = (c << 31) | (uint)(p >> 33); + zz[zzOff + --j] = (uint)(p >> 1); + c = (uint)p; + } + while (i > 0); + + { + ulong p = x_0 * x_0; + zz_1 = (ulong)(c << 31) | (p >> 33); + zz[zzOff + 0] = (uint)p; + c = (uint)(p >> 32) & 1; + } + } + + ulong x_1 = x[xOff + 1]; + ulong zz_2 = zz[zzOff + 2]; + + { + zz_1 += x_1 * x_0; + w = (uint)zz_1; + zz[zzOff + 1] = (w << 1) | c; + c = w >> 31; + zz_2 += zz_1 >> 32; + } + + ulong x_2 = x[xOff + 2]; + ulong zz_3 = zz[zzOff + 3]; + ulong zz_4 = zz[zzOff + 4]; + { + zz_2 += x_2 * x_0; + w = (uint)zz_2; + zz[zzOff + 2] = (w << 1) | c; + c = w >> 31; + zz_3 += (zz_2 >> 32) + x_2 * x_1; + zz_4 += zz_3 >> 32; + zz_3 &= M; + } + + ulong x_3 = x[xOff + 3]; + ulong zz_5 = zz[zzOff + 5] + (zz_4 >> 32); zz_4 &= M; + ulong zz_6 = zz[zzOff + 6] + (zz_5 >> 32); zz_5 &= M; + { + zz_3 += x_3 * x_0; + w = (uint)zz_3; + zz[zzOff + 3] = (w << 1) | c; + c = w >> 31; + zz_4 += (zz_3 >> 32) + x_3 * x_1; + zz_5 += (zz_4 >> 32) + x_3 * x_2; + zz_4 &= M; + zz_6 += zz_5 >> 32; + zz_5 &= M; + } + + ulong x_4 = x[xOff + 4]; + ulong zz_7 = zz[zzOff + 7] + (zz_6 >> 32); zz_6 &= M; + ulong zz_8 = zz[zzOff + 8] + (zz_7 >> 32); zz_7 &= M; + { + zz_4 += x_4 * x_0; + w = (uint)zz_4; + zz[zzOff + 4] = (w << 1) | c; + c = w >> 31; + zz_5 += (zz_4 >> 32) + x_4 * x_1; + zz_6 += (zz_5 >> 32) + x_4 * x_2; + zz_7 += (zz_6 >> 32) + x_4 * x_3; + zz_8 += zz_7 >> 32; + } + + w = (uint)zz_5; + zz[zzOff + 5] = (w << 1) | c; + c = w >> 31; + w = (uint)zz_6; + zz[zzOff + 6] = (w << 1) | c; + c = w >> 31; + w = (uint)zz_7; + zz[zzOff + 7] = (w << 1) | c; + c = w >> 31; + w = (uint)zz_8; + zz[zzOff + 8] = (w << 1) | c; + c = w >> 31; + w = zz[zzOff + 9] + (uint)(zz_8 >> 32); + zz[zzOff + 9] = (w << 1) | c; + } + + public static int Sub(uint[] x, uint[] y, uint[] z) + { + long c = 0; + c += (long)x[0] - y[0]; + z[0] = (uint)c; + c >>= 32; + c += (long)x[1] - y[1]; + z[1] = (uint)c; + c >>= 32; + c += (long)x[2] - y[2]; + z[2] = (uint)c; + c >>= 32; + c += (long)x[3] - y[3]; + z[3] = (uint)c; + c >>= 32; + c += (long)x[4] - y[4]; + z[4] = (uint)c; + c >>= 32; + return (int)c; + } + + public static int Sub(uint[] x, int xOff, uint[] y, int yOff, uint[] z, int zOff) + { + long c = 0; + c += (long)x[xOff + 0] - y[yOff + 0]; + z[zOff + 0] = (uint)c; + c >>= 32; + c += (long)x[xOff + 1] - y[yOff + 1]; + z[zOff + 1] = (uint)c; + c >>= 32; + c += (long)x[xOff + 2] - y[yOff + 2]; + z[zOff + 2] = (uint)c; + c >>= 32; + c += (long)x[xOff + 3] - y[yOff + 3]; + z[zOff + 3] = (uint)c; + c >>= 32; + c += (long)x[xOff + 4] - y[yOff + 4]; + z[zOff + 4] = (uint)c; + c >>= 32; + return (int)c; + } + + public static int SubBothFrom(uint[] x, uint[] y, uint[] z) + { + long c = 0; + c += (long)z[0] - x[0] - y[0]; + z[0] = (uint)c; + c >>= 32; + c += (long)z[1] - x[1] - y[1]; + z[1] = (uint)c; + c >>= 32; + c += (long)z[2] - x[2] - y[2]; + z[2] = (uint)c; + c >>= 32; + c += (long)z[3] - x[3] - y[3]; + z[3] = (uint)c; + c >>= 32; + c += (long)z[4] - x[4] - y[4]; + z[4] = (uint)c; + c >>= 32; + return (int)c; + } + + public static int SubFrom(uint[] x, uint[] z) + { + long c = 0; + c += (long)z[0] - x[0]; + z[0] = (uint)c; + c >>= 32; + c += (long)z[1] - x[1]; + z[1] = (uint)c; + c >>= 32; + c += (long)z[2] - x[2]; + z[2] = (uint)c; + c >>= 32; + c += (long)z[3] - x[3]; + z[3] = (uint)c; + c >>= 32; + c += (long)z[4] - x[4]; + z[4] = (uint)c; + c >>= 32; + return (int)c; + } + + public static int SubFrom(uint[] x, int xOff, uint[] z, int zOff) + { + long c = 0; + c += (long)z[zOff + 0] - x[xOff + 0]; + z[zOff + 0] = (uint)c; + c >>= 32; + c += (long)z[zOff + 1] - x[xOff + 1]; + z[zOff + 1] = (uint)c; + c >>= 32; + c += (long)z[zOff + 2] - x[xOff + 2]; + z[zOff + 2] = (uint)c; + c >>= 32; + c += (long)z[zOff + 3] - x[xOff + 3]; + z[zOff + 3] = (uint)c; + c >>= 32; + c += (long)z[zOff + 4] - x[xOff + 4]; + z[zOff + 4] = (uint)c; + c >>= 32; + return (int)c; + } + + public static BigInteger ToBigInteger(uint[] x) + { + byte[] bs = new byte[20]; + for (int i = 0; i < 5; ++i) + { + uint x_i = x[i]; + if (x_i != 0) + { + Pack.UInt32_To_BE(x_i, bs, (4 - i) << 2); + } + } + return new BigInteger(1, bs); + } + + public static void Zero(uint[] z) + { + z[0] = 0; + z[1] = 0; + z[2] = 0; + z[3] = 0; + z[4] = 0; + } + } +} diff --git a/bc-sharp-crypto/src/math/raw/Nat192.cs b/bc-sharp-crypto/src/math/raw/Nat192.cs new file mode 100644 index 0000000..3099baf --- /dev/null +++ b/bc-sharp-crypto/src/math/raw/Nat192.cs @@ -0,0 +1,1048 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Crypto.Utilities; + +namespace Org.BouncyCastle.Math.Raw +{ + internal abstract class Nat192 + { + private const ulong M = 0xFFFFFFFFUL; + + public static uint Add(uint[] x, uint[] y, uint[] z) + { + ulong c = 0; + c += (ulong)x[0] + y[0]; + z[0] = (uint)c; + c >>= 32; + c += (ulong)x[1] + y[1]; + z[1] = (uint)c; + c >>= 32; + c += (ulong)x[2] + y[2]; + z[2] = (uint)c; + c >>= 32; + c += (ulong)x[3] + y[3]; + z[3] = (uint)c; + c >>= 32; + c += (ulong)x[4] + y[4]; + z[4] = (uint)c; + c >>= 32; + c += (ulong)x[5] + y[5]; + z[5] = (uint)c; + c >>= 32; + return (uint)c; + } + + public static uint AddBothTo(uint[] x, uint[] y, uint[] z) + { + ulong c = 0; + c += (ulong)x[0] + y[0] + z[0]; + z[0] = (uint)c; + c >>= 32; + c += (ulong)x[1] + y[1] + z[1]; + z[1] = (uint)c; + c >>= 32; + c += (ulong)x[2] + y[2] + z[2]; + z[2] = (uint)c; + c >>= 32; + c += (ulong)x[3] + y[3] + z[3]; + z[3] = (uint)c; + c >>= 32; + c += (ulong)x[4] + y[4] + z[4]; + z[4] = (uint)c; + c >>= 32; + c += (ulong)x[5] + y[5] + z[5]; + z[5] = (uint)c; + c >>= 32; + return (uint)c; + } + + public static uint AddTo(uint[] x, uint[] z) + { + ulong c = 0; + c += (ulong)x[0] + z[0]; + z[0] = (uint)c; + c >>= 32; + c += (ulong)x[1] + z[1]; + z[1] = (uint)c; + c >>= 32; + c += (ulong)x[2] + z[2]; + z[2] = (uint)c; + c >>= 32; + c += (ulong)x[3] + z[3]; + z[3] = (uint)c; + c >>= 32; + c += (ulong)x[4] + z[4]; + z[4] = (uint)c; + c >>= 32; + c += (ulong)x[5] + z[5]; + z[5] = (uint)c; + c >>= 32; + return (uint)c; + } + + public static uint AddTo(uint[] x, int xOff, uint[] z, int zOff, uint cIn) + { + ulong c = cIn; + c += (ulong)x[xOff + 0] + z[zOff + 0]; + z[zOff + 0] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 1] + z[zOff + 1]; + z[zOff + 1] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 2] + z[zOff + 2]; + z[zOff + 2] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 3] + z[zOff + 3]; + z[zOff + 3] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 4] + z[zOff + 4]; + z[zOff + 4] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 5] + z[zOff + 5]; + z[zOff + 5] = (uint)c; + c >>= 32; + return (uint)c; + } + + public static uint AddToEachOther(uint[] u, int uOff, uint[] v, int vOff) + { + ulong c = 0; + c += (ulong)u[uOff + 0] + v[vOff + 0]; + u[uOff + 0] = (uint)c; + v[vOff + 0] = (uint)c; + c >>= 32; + c += (ulong)u[uOff + 1] + v[vOff + 1]; + u[uOff + 1] = (uint)c; + v[vOff + 1] = (uint)c; + c >>= 32; + c += (ulong)u[uOff + 2] + v[vOff + 2]; + u[uOff + 2] = (uint)c; + v[vOff + 2] = (uint)c; + c >>= 32; + c += (ulong)u[uOff + 3] + v[vOff + 3]; + u[uOff + 3] = (uint)c; + v[vOff + 3] = (uint)c; + c >>= 32; + c += (ulong)u[uOff + 4] + v[vOff + 4]; + u[uOff + 4] = (uint)c; + v[vOff + 4] = (uint)c; + c >>= 32; + c += (ulong)u[uOff + 5] + v[vOff + 5]; + u[uOff + 5] = (uint)c; + v[vOff + 5] = (uint)c; + c >>= 32; + return (uint)c; + } + + public static void Copy(uint[] x, uint[] z) + { + z[0] = x[0]; + z[1] = x[1]; + z[2] = x[2]; + z[3] = x[3]; + z[4] = x[4]; + z[5] = x[5]; + } + + public static void Copy64(ulong[] x, ulong[] z) + { + z[0] = x[0]; + z[1] = x[1]; + z[2] = x[2]; + } + + public static uint[] Create() + { + return new uint[6]; + } + + public static ulong[] Create64() + { + return new ulong[3]; + } + + public static uint[] CreateExt() + { + return new uint[12]; + } + + public static ulong[] CreateExt64() + { + return new ulong[6]; + } + + public static bool Diff(uint[] x, int xOff, uint[] y, int yOff, uint[] z, int zOff) + { + bool pos = Gte(x, xOff, y, yOff); + if (pos) + { + Sub(x, xOff, y, yOff, z, zOff); + } + else + { + Sub(y, yOff, x, xOff, z, zOff); + } + return pos; + } + + public static bool Eq(uint[] x, uint[] y) + { + for (int i = 5; i >= 0; --i) + { + if (x[i] != y[i]) + return false; + } + return true; + } + + public static bool Eq64(ulong[] x, ulong[] y) + { + for (int i = 2; i >= 0; --i) + { + if (x[i] != y[i]) + { + return false; + } + } + return true; + } + + public static uint[] FromBigInteger(BigInteger x) + { + if (x.SignValue < 0 || x.BitLength > 192) + throw new ArgumentException(); + + uint[] z = Create(); + int i = 0; + while (x.SignValue != 0) + { + z[i++] = (uint)x.IntValue; + x = x.ShiftRight(32); + } + return z; + } + + public static ulong[] FromBigInteger64(BigInteger x) + { + if (x.SignValue < 0 || x.BitLength > 192) + throw new ArgumentException(); + + ulong[] z = Create64(); + int i = 0; + while (x.SignValue != 0) + { + z[i++] = (ulong)x.LongValue; + x = x.ShiftRight(64); + } + return z; + } + + public static uint GetBit(uint[] x, int bit) + { + if (bit == 0) + { + return x[0] & 1; + } + int w = bit >> 5; + if (w < 0 || w >= 6) + { + return 0; + } + int b = bit & 31; + return (x[w] >> b) & 1; + } + + public static bool Gte(uint[] x, uint[] y) + { + for (int i = 5; i >= 0; --i) + { + uint x_i = x[i], y_i = y[i]; + if (x_i < y_i) + return false; + if (x_i > y_i) + return true; + } + return true; + } + + public static bool Gte(uint[] x, int xOff, uint[] y, int yOff) + { + for (int i = 5; i >= 0; --i) + { + uint x_i = x[xOff + i], y_i = y[yOff + i]; + if (x_i < y_i) + return false; + if (x_i > y_i) + return true; + } + return true; + } + + public static bool IsOne(uint[] x) + { + if (x[0] != 1) + { + return false; + } + for (int i = 1; i < 6; ++i) + { + if (x[i] != 0) + { + return false; + } + } + return true; + } + + public static bool IsOne64(ulong[] x) + { + if (x[0] != 1UL) + { + return false; + } + for (int i = 1; i < 3; ++i) + { + if (x[i] != 0UL) + { + return false; + } + } + return true; + } + + public static bool IsZero(uint[] x) + { + for (int i = 0; i < 6; ++i) + { + if (x[i] != 0) + { + return false; + } + } + return true; + } + + public static bool IsZero64(ulong[] x) + { + for (int i = 0; i < 3; ++i) + { + if (x[i] != 0UL) + { + return false; + } + } + return true; + } + + public static void Mul(uint[] x, uint[] y, uint[] zz) + { + ulong y_0 = y[0]; + ulong y_1 = y[1]; + ulong y_2 = y[2]; + ulong y_3 = y[3]; + ulong y_4 = y[4]; + ulong y_5 = y[5]; + + { + ulong c = 0, x_0 = x[0]; + c += x_0 * y_0; + zz[0] = (uint)c; + c >>= 32; + c += x_0 * y_1; + zz[1] = (uint)c; + c >>= 32; + c += x_0 * y_2; + zz[2] = (uint)c; + c >>= 32; + c += x_0 * y_3; + zz[3] = (uint)c; + c >>= 32; + c += x_0 * y_4; + zz[4] = (uint)c; + c >>= 32; + c += x_0 * y_5; + zz[5] = (uint)c; + c >>= 32; + zz[6] = (uint)c; + } + + for (int i = 1; i < 6; ++i) + { + ulong c = 0, x_i = x[i]; + c += x_i * y_0 + zz[i + 0]; + zz[i + 0] = (uint)c; + c >>= 32; + c += x_i * y_1 + zz[i + 1]; + zz[i + 1] = (uint)c; + c >>= 32; + c += x_i * y_2 + zz[i + 2]; + zz[i + 2] = (uint)c; + c >>= 32; + c += x_i * y_3 + zz[i + 3]; + zz[i + 3] = (uint)c; + c >>= 32; + c += x_i * y_4 + zz[i + 4]; + zz[i + 4] = (uint)c; + c >>= 32; + c += x_i * y_5 + zz[i + 5]; + zz[i + 5] = (uint)c; + c >>= 32; + zz[i + 6] = (uint)c; + } + } + + public static void Mul(uint[] x, int xOff, uint[] y, int yOff, uint[] zz, int zzOff) + { + ulong y_0 = y[yOff + 0]; + ulong y_1 = y[yOff + 1]; + ulong y_2 = y[yOff + 2]; + ulong y_3 = y[yOff + 3]; + ulong y_4 = y[yOff + 4]; + ulong y_5 = y[yOff + 5]; + + { + ulong c = 0, x_0 = x[xOff + 0]; + c += x_0 * y_0; + zz[zzOff + 0] = (uint)c; + c >>= 32; + c += x_0 * y_1; + zz[zzOff + 1] = (uint)c; + c >>= 32; + c += x_0 * y_2; + zz[zzOff + 2] = (uint)c; + c >>= 32; + c += x_0 * y_3; + zz[zzOff + 3] = (uint)c; + c >>= 32; + c += x_0 * y_4; + zz[zzOff + 4] = (uint)c; + c >>= 32; + c += x_0 * y_5; + zz[zzOff + 5] = (uint)c; + c >>= 32; + zz[zzOff + 6] = (uint)c; + } + + for (int i = 1; i < 6; ++i) + { + ++zzOff; + ulong c = 0, x_i = x[xOff + i]; + c += x_i * y_0 + zz[zzOff + 0]; + zz[zzOff + 0] = (uint)c; + c >>= 32; + c += x_i * y_1 + zz[zzOff + 1]; + zz[zzOff + 1] = (uint)c; + c >>= 32; + c += x_i * y_2 + zz[zzOff + 2]; + zz[zzOff + 2] = (uint)c; + c >>= 32; + c += x_i * y_3 + zz[zzOff + 3]; + zz[zzOff + 3] = (uint)c; + c >>= 32; + c += x_i * y_4 + zz[zzOff + 4]; + zz[zzOff + 4] = (uint)c; + c >>= 32; + c += x_i * y_5 + zz[zzOff + 5]; + zz[zzOff + 5] = (uint)c; + c >>= 32; + zz[zzOff + 6] = (uint)c; + } + } + + public static uint MulAddTo(uint[] x, uint[] y, uint[] zz) + { + ulong y_0 = y[0]; + ulong y_1 = y[1]; + ulong y_2 = y[2]; + ulong y_3 = y[3]; + ulong y_4 = y[4]; + ulong y_5 = y[5]; + + ulong zc = 0; + for (int i = 0; i < 6; ++i) + { + ulong c = 0, x_i = x[i]; + c += x_i * y_0 + zz[i + 0]; + zz[i + 0] = (uint)c; + c >>= 32; + c += x_i * y_1 + zz[i + 1]; + zz[i + 1] = (uint)c; + c >>= 32; + c += x_i * y_2 + zz[i + 2]; + zz[i + 2] = (uint)c; + c >>= 32; + c += x_i * y_3 + zz[i + 3]; + zz[i + 3] = (uint)c; + c >>= 32; + c += x_i * y_4 + zz[i + 4]; + zz[i + 4] = (uint)c; + c >>= 32; + c += x_i * y_5 + zz[i + 5]; + zz[i + 5] = (uint)c; + c >>= 32; + c += zc + zz[i + 6]; + zz[i + 6] = (uint)c; + zc = c >> 32; + } + return (uint)zc; + } + + public static uint MulAddTo(uint[] x, int xOff, uint[] y, int yOff, uint[] zz, int zzOff) + { + ulong y_0 = y[yOff + 0]; + ulong y_1 = y[yOff + 1]; + ulong y_2 = y[yOff + 2]; + ulong y_3 = y[yOff + 3]; + ulong y_4 = y[yOff + 4]; + ulong y_5 = y[yOff + 5]; + + ulong zc = 0; + for (int i = 0; i < 6; ++i) + { + ulong c = 0, x_i = x[xOff + i]; + c += x_i * y_0 + zz[zzOff + 0]; + zz[zzOff + 0] = (uint)c; + c >>= 32; + c += x_i * y_1 + zz[zzOff + 1]; + zz[zzOff + 1] = (uint)c; + c >>= 32; + c += x_i * y_2 + zz[zzOff + 2]; + zz[zzOff + 2] = (uint)c; + c >>= 32; + c += x_i * y_3 + zz[zzOff + 3]; + zz[zzOff + 3] = (uint)c; + c >>= 32; + c += x_i * y_4 + zz[zzOff + 4]; + zz[zzOff + 4] = (uint)c; + c >>= 32; + c += x_i * y_5 + zz[zzOff + 5]; + zz[zzOff + 5] = (uint)c; + c >>= 32; + c += zc + zz[zzOff + 6]; + zz[zzOff + 6] = (uint)c; + zc = c >> 32; + ++zzOff; + } + return (uint)zc; + } + + public static ulong Mul33Add(uint w, uint[] x, int xOff, uint[] y, int yOff, uint[] z, int zOff) + { + Debug.Assert(w >> 31 == 0); + + ulong c = 0, wVal = w; + ulong x0 = x[xOff + 0]; + c += wVal * x0 + y[yOff + 0]; + z[zOff + 0] = (uint)c; + c >>= 32; + ulong x1 = x[xOff + 1]; + c += wVal * x1 + x0 + y[yOff + 1]; + z[zOff + 1] = (uint)c; + c >>= 32; + ulong x2 = x[xOff + 2]; + c += wVal * x2 + x1 + y[yOff + 2]; + z[zOff + 2] = (uint)c; + c >>= 32; + ulong x3 = x[xOff + 3]; + c += wVal * x3 + x2 + y[yOff + 3]; + z[zOff + 3] = (uint)c; + c >>= 32; + ulong x4 = x[xOff + 4]; + c += wVal * x4 + x3 + y[yOff + 4]; + z[zOff + 4] = (uint)c; + c >>= 32; + ulong x5 = x[xOff + 5]; + c += wVal * x5 + x4 + y[yOff + 5]; + z[zOff + 5] = (uint)c; + c >>= 32; + c += x5; + return c; + } + + public static uint MulWordAddExt(uint x, uint[] yy, int yyOff, uint[] zz, int zzOff) + { + Debug.Assert(yyOff <= 6); + Debug.Assert(zzOff <= 6); + ulong c = 0, xVal = x; + c += xVal * yy[yyOff + 0] + zz[zzOff + 0]; + zz[zzOff + 0] = (uint)c; + c >>= 32; + c += xVal * yy[yyOff + 1] + zz[zzOff + 1]; + zz[zzOff + 1] = (uint)c; + c >>= 32; + c += xVal * yy[yyOff + 2] + zz[zzOff + 2]; + zz[zzOff + 2] = (uint)c; + c >>= 32; + c += xVal * yy[yyOff + 3] + zz[zzOff + 3]; + zz[zzOff + 3] = (uint)c; + c >>= 32; + c += xVal * yy[yyOff + 4] + zz[zzOff + 4]; + zz[zzOff + 4] = (uint)c; + c >>= 32; + c += xVal * yy[yyOff + 5] + zz[zzOff + 5]; + zz[zzOff + 5] = (uint)c; + c >>= 32; + return (uint)c; + } + + public static uint Mul33DWordAdd(uint x, ulong y, uint[] z, int zOff) + { + Debug.Assert(x >> 31 == 0); + Debug.Assert(zOff <= 2); + ulong c = 0, xVal = x; + ulong y00 = y & M; + c += xVal * y00 + z[zOff + 0]; + z[zOff + 0] = (uint)c; + c >>= 32; + ulong y01 = y >> 32; + c += xVal * y01 + y00 + z[zOff + 1]; + z[zOff + 1] = (uint)c; + c >>= 32; + c += y01 + z[zOff + 2]; + z[zOff + 2] = (uint)c; + c >>= 32; + c += z[zOff + 3]; + z[zOff + 3] = (uint)c; + c >>= 32; + return c == 0 ? 0 : Nat.IncAt(6, z, zOff, 4); + } + + public static uint Mul33WordAdd(uint x, uint y, uint[] z, int zOff) + { + Debug.Assert(x >> 31 == 0); + Debug.Assert(zOff <=3); + ulong c = 0, yVal = y; + c += yVal * x + z[zOff + 0]; + z[zOff + 0] = (uint)c; + c >>= 32; + c += yVal + z[zOff + 1]; + z[zOff + 1] = (uint)c; + c >>= 32; + c += z[zOff + 2]; + z[zOff + 2] = (uint)c; + c >>= 32; + return c == 0 ? 0 : Nat.IncAt(6, z, zOff, 3); + } + + public static uint MulWordDwordAdd(uint x, ulong y, uint[] z, int zOff) + { + Debug.Assert(zOff <= 3); + ulong c = 0, xVal = x; + c += xVal * y + z[zOff + 0]; + z[zOff + 0] = (uint)c; + c >>= 32; + c += xVal * (y >> 32) + z[zOff + 1]; + z[zOff + 1] = (uint)c; + c >>= 32; + c += z[zOff + 2]; + z[zOff + 2] = (uint)c; + c >>= 32; + return c == 0 ? 0 : Nat.IncAt(6, z, zOff, 3); + } + + public static uint MulWord(uint x, uint[] y, uint[] z, int zOff) + { + ulong c = 0, xVal = x; + int i = 0; + do + { + c += xVal * y[i]; + z[zOff + i] = (uint)c; + c >>= 32; + } + while (++i < 6); + return (uint)c; + } + + public static void Square(uint[] x, uint[] zz) + { + ulong x_0 = x[0]; + ulong zz_1; + + uint c = 0, w; + { + int i = 5, j = 12; + do + { + ulong xVal = x[i--]; + ulong p = xVal * xVal; + zz[--j] = (c << 31) | (uint)(p >> 33); + zz[--j] = (uint)(p >> 1); + c = (uint)p; + } + while (i > 0); + + { + ulong p = x_0 * x_0; + zz_1 = (ulong)(c << 31) | (p >> 33); + zz[0] = (uint)p; + c = (uint)(p >> 32) & 1; + } + } + + ulong x_1 = x[1]; + ulong zz_2 = zz[2]; + + { + zz_1 += x_1 * x_0; + w = (uint)zz_1; + zz[1] = (w << 1) | c; + c = w >> 31; + zz_2 += zz_1 >> 32; + } + + ulong x_2 = x[2]; + ulong zz_3 = zz[3]; + ulong zz_4 = zz[4]; + { + zz_2 += x_2 * x_0; + w = (uint)zz_2; + zz[2] = (w << 1) | c; + c = w >> 31; + zz_3 += (zz_2 >> 32) + x_2 * x_1; + zz_4 += zz_3 >> 32; + zz_3 &= M; + } + + ulong x_3 = x[3]; + ulong zz_5 = zz[5] + (zz_4 >> 32); zz_4 &= M; + ulong zz_6 = zz[6] + (zz_5 >> 32); zz_5 &= M; + { + zz_3 += x_3 * x_0; + w = (uint)zz_3; + zz[3] = (w << 1) | c; + c = w >> 31; + zz_4 += (zz_3 >> 32) + x_3 * x_1; + zz_5 += (zz_4 >> 32) + x_3 * x_2; + zz_4 &= M; + zz_6 += zz_5 >> 32; + zz_5 &= M; + } + + ulong x_4 = x[4]; + ulong zz_7 = zz[7] + (zz_6 >> 32); zz_6 &= M; + ulong zz_8 = zz[8] + (zz_7 >> 32); zz_7 &= M; + { + zz_4 += x_4 * x_0; + w = (uint)zz_4; + zz[4] = (w << 1) | c; + c = w >> 31; + zz_5 += (zz_4 >> 32) + x_4 * x_1; + zz_6 += (zz_5 >> 32) + x_4 * x_2; + zz_5 &= M; + zz_7 += (zz_6 >> 32) + x_4 * x_3; + zz_6 &= M; + zz_8 += zz_7 >> 32; + zz_7 &= M; + } + + ulong x_5 = x[5]; + ulong zz_9 = zz[9] + (zz_8 >> 32); zz_8 &= M; + ulong zz_10 = zz[10] + (zz_9 >> 32); zz_9 &= M; + { + zz_5 += x_5 * x_0; + w = (uint)zz_5; + zz[5] = (w << 1) | c; + c = w >> 31; + zz_6 += (zz_5 >> 32) + x_5 * x_1; + zz_7 += (zz_6 >> 32) + x_5 * x_2; + zz_8 += (zz_7 >> 32) + x_5 * x_3; + zz_9 += (zz_8 >> 32) + x_5 * x_4; + zz_10 += zz_9 >> 32; + } + + w = (uint)zz_6; + zz[6] = (w << 1) | c; + c = w >> 31; + w = (uint)zz_7; + zz[7] = (w << 1) | c; + c = w >> 31; + w = (uint)zz_8; + zz[8] = (w << 1) | c; + c = w >> 31; + w = (uint)zz_9; + zz[9] = (w << 1) | c; + c = w >> 31; + w = (uint)zz_10; + zz[10] = (w << 1) | c; + c = w >> 31; + w = zz[11] + (uint)(zz_10 >> 32); + zz[11] = (w << 1) | c; + } + + public static void Square(uint[] x, int xOff, uint[] zz, int zzOff) + { + ulong x_0 = x[xOff + 0]; + ulong zz_1; + + uint c = 0, w; + { + int i = 5, j = 12; + do + { + ulong xVal = x[xOff + i--]; + ulong p = xVal * xVal; + zz[zzOff + --j] = (c << 31) | (uint)(p >> 33); + zz[zzOff + --j] = (uint)(p >> 1); + c = (uint)p; + } + while (i > 0); + + { + ulong p = x_0 * x_0; + zz_1 = (ulong)(c << 31) | (p >> 33); + zz[zzOff + 0] = (uint)p; + c = (uint)(p >> 32) & 1; + } + } + + ulong x_1 = x[xOff + 1]; + ulong zz_2 = zz[zzOff + 2]; + + { + zz_1 += x_1 * x_0; + w = (uint)zz_1; + zz[zzOff + 1] = (w << 1) | c; + c = w >> 31; + zz_2 += zz_1 >> 32; + } + + ulong x_2 = x[xOff + 2]; + ulong zz_3 = zz[zzOff + 3]; + ulong zz_4 = zz[zzOff + 4]; + { + zz_2 += x_2 * x_0; + w = (uint)zz_2; + zz[zzOff + 2] = (w << 1) | c; + c = w >> 31; + zz_3 += (zz_2 >> 32) + x_2 * x_1; + zz_4 += zz_3 >> 32; + zz_3 &= M; + } + + ulong x_3 = x[xOff + 3]; + ulong zz_5 = zz[zzOff + 5] + (zz_4 >> 32); zz_4 &= M; + ulong zz_6 = zz[zzOff + 6] + (zz_5 >> 32); zz_5 &= M; + { + zz_3 += x_3 * x_0; + w = (uint)zz_3; + zz[zzOff + 3] = (w << 1) | c; + c = w >> 31; + zz_4 += (zz_3 >> 32) + x_3 * x_1; + zz_5 += (zz_4 >> 32) + x_3 * x_2; + zz_4 &= M; + zz_6 += zz_5 >> 32; + zz_5 &= M; + } + + ulong x_4 = x[xOff + 4]; + ulong zz_7 = zz[zzOff + 7] + (zz_6 >> 32); zz_6 &= M; + ulong zz_8 = zz[zzOff + 8] + (zz_7 >> 32); zz_7 &= M; + { + zz_4 += x_4 * x_0; + w = (uint)zz_4; + zz[zzOff + 4] = (w << 1) | c; + c = w >> 31; + zz_5 += (zz_4 >> 32) + x_4 * x_1; + zz_6 += (zz_5 >> 32) + x_4 * x_2; + zz_5 &= M; + zz_7 += (zz_6 >> 32) + x_4 * x_3; + zz_6 &= M; + zz_8 += zz_7 >> 32; + zz_7 &= M; + } + + ulong x_5 = x[xOff + 5]; + ulong zz_9 = zz[zzOff + 9] + (zz_8 >> 32); zz_8 &= M; + ulong zz_10 = zz[zzOff + 10] + (zz_9 >> 32); zz_9 &= M; + { + zz_5 += x_5 * x_0; + w = (uint)zz_5; + zz[zzOff + 5] = (w << 1) | c; + c = w >> 31; + zz_6 += (zz_5 >> 32) + x_5 * x_1; + zz_7 += (zz_6 >> 32) + x_5 * x_2; + zz_8 += (zz_7 >> 32) + x_5 * x_3; + zz_9 += (zz_8 >> 32) + x_5 * x_4; + zz_10 += zz_9 >> 32; + } + + w = (uint)zz_6; + zz[zzOff + 6] = (w << 1) | c; + c = w >> 31; + w = (uint)zz_7; + zz[zzOff + 7] = (w << 1) | c; + c = w >> 31; + w = (uint)zz_8; + zz[zzOff + 8] = (w << 1) | c; + c = w >> 31; + w = (uint)zz_9; + zz[zzOff + 9] = (w << 1) | c; + c = w >> 31; + w = (uint)zz_10; + zz[zzOff + 10] = (w << 1) | c; + c = w >> 31; + w = zz[zzOff + 11] + (uint)(zz_10 >> 32); + zz[zzOff + 11] = (w << 1) | c; + } + + public static int Sub(uint[] x, uint[] y, uint[] z) + { + long c = 0; + c += (long)x[0] - y[0]; + z[0] = (uint)c; + c >>= 32; + c += (long)x[1] - y[1]; + z[1] = (uint)c; + c >>= 32; + c += (long)x[2] - y[2]; + z[2] = (uint)c; + c >>= 32; + c += (long)x[3] - y[3]; + z[3] = (uint)c; + c >>= 32; + c += (long)x[4] - y[4]; + z[4] = (uint)c; + c >>= 32; + c += (long)x[5] - y[5]; + z[5] = (uint)c; + c >>= 32; + return (int)c; + } + + public static int Sub(uint[] x, int xOff, uint[] y, int yOff, uint[] z, int zOff) + { + long c = 0; + c += (long)x[xOff + 0] - y[yOff + 0]; + z[zOff + 0] = (uint)c; + c >>= 32; + c += (long)x[xOff + 1] - y[yOff + 1]; + z[zOff + 1] = (uint)c; + c >>= 32; + c += (long)x[xOff + 2] - y[yOff + 2]; + z[zOff + 2] = (uint)c; + c >>= 32; + c += (long)x[xOff + 3] - y[yOff + 3]; + z[zOff + 3] = (uint)c; + c >>= 32; + c += (long)x[xOff + 4] - y[yOff + 4]; + z[zOff + 4] = (uint)c; + c >>= 32; + c += (long)x[xOff + 5] - y[yOff + 5]; + z[zOff + 5] = (uint)c; + c >>= 32; + return (int)c; + } + + public static int SubBothFrom(uint[] x, uint[] y, uint[] z) + { + long c = 0; + c += (long)z[0] - x[0] - y[0]; + z[0] = (uint)c; + c >>= 32; + c += (long)z[1] - x[1] - y[1]; + z[1] = (uint)c; + c >>= 32; + c += (long)z[2] - x[2] - y[2]; + z[2] = (uint)c; + c >>= 32; + c += (long)z[3] - x[3] - y[3]; + z[3] = (uint)c; + c >>= 32; + c += (long)z[4] - x[4] - y[4]; + z[4] = (uint)c; + c >>= 32; + c += (long)z[5] - x[5] - y[5]; + z[5] = (uint)c; + c >>= 32; + return (int)c; + } + + public static int SubFrom(uint[] x, uint[] z) + { + long c = 0; + c += (long)z[0] - x[0]; + z[0] = (uint)c; + c >>= 32; + c += (long)z[1] - x[1]; + z[1] = (uint)c; + c >>= 32; + c += (long)z[2] - x[2]; + z[2] = (uint)c; + c >>= 32; + c += (long)z[3] - x[3]; + z[3] = (uint)c; + c >>= 32; + c += (long)z[4] - x[4]; + z[4] = (uint)c; + c >>= 32; + c += (long)z[5] - x[5]; + z[5] = (uint)c; + c >>= 32; + return (int)c; + } + + public static int SubFrom(uint[] x, int xOff, uint[] z, int zOff) + { + long c = 0; + c += (long)z[zOff + 0] - x[xOff + 0]; + z[zOff + 0] = (uint)c; + c >>= 32; + c += (long)z[zOff + 1] - x[xOff + 1]; + z[zOff + 1] = (uint)c; + c >>= 32; + c += (long)z[zOff + 2] - x[xOff + 2]; + z[zOff + 2] = (uint)c; + c >>= 32; + c += (long)z[zOff + 3] - x[xOff + 3]; + z[zOff + 3] = (uint)c; + c >>= 32; + c += (long)z[zOff + 4] - x[xOff + 4]; + z[zOff + 4] = (uint)c; + c >>= 32; + c += (long)z[zOff + 5] - x[xOff + 5]; + z[zOff + 5] = (uint)c; + c >>= 32; + return (int)c; + } + + public static BigInteger ToBigInteger(uint[] x) + { + byte[] bs = new byte[24]; + for (int i = 0; i < 6; ++i) + { + uint x_i = x[i]; + if (x_i != 0) + { + Pack.UInt32_To_BE(x_i, bs, (5 - i) << 2); + } + } + return new BigInteger(1, bs); + } + + public static BigInteger ToBigInteger64(ulong[] x) + { + byte[] bs = new byte[24]; + for (int i = 0; i < 3; ++i) + { + ulong x_i = x[i]; + if (x_i != 0L) + { + Pack.UInt64_To_BE(x_i, bs, (2 - i) << 3); + } + } + return new BigInteger(1, bs); + } + + public static void Zero(uint[] z) + { + z[0] = 0; + z[1] = 0; + z[2] = 0; + z[3] = 0; + z[4] = 0; + z[5] = 0; + } + } +} diff --git a/bc-sharp-crypto/src/math/raw/Nat224.cs b/bc-sharp-crypto/src/math/raw/Nat224.cs new file mode 100644 index 0000000..978caf2 --- /dev/null +++ b/bc-sharp-crypto/src/math/raw/Nat224.cs @@ -0,0 +1,1176 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Crypto.Utilities; + +namespace Org.BouncyCastle.Math.Raw +{ + internal abstract class Nat224 + { + private const ulong M = 0xFFFFFFFFUL; + + public static uint Add(uint[] x, uint[] y, uint[] z) + { + ulong c = 0; + c += (ulong)x[0] + y[0]; + z[0] = (uint)c; + c >>= 32; + c += (ulong)x[1] + y[1]; + z[1] = (uint)c; + c >>= 32; + c += (ulong)x[2] + y[2]; + z[2] = (uint)c; + c >>= 32; + c += (ulong)x[3] + y[3]; + z[3] = (uint)c; + c >>= 32; + c += (ulong)x[4] + y[4]; + z[4] = (uint)c; + c >>= 32; + c += (ulong)x[5] + y[5]; + z[5] = (uint)c; + c >>= 32; + c += (ulong)x[6] + y[6]; + z[6] = (uint)c; + c >>= 32; + return (uint)c; + } + + public static uint Add(uint[] x, int xOff, uint[] y, int yOff, uint[] z, int zOff) + { + ulong c = 0; + c += (ulong)x[xOff + 0] + y[yOff + 0]; + z[zOff + 0] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 1] + y[yOff + 1]; + z[zOff + 1] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 2] + y[yOff + 2]; + z[zOff + 2] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 3] + y[yOff + 3]; + z[zOff + 3] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 4] + y[yOff + 4]; + z[zOff + 4] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 5] + y[yOff + 5]; + z[zOff + 5] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 6] + y[yOff + 6]; + z[zOff + 6] = (uint)c; + c >>= 32; + return (uint)c; + } + + public static uint AddBothTo(uint[] x, uint[] y, uint[] z) + { + ulong c = 0; + c += (ulong)x[0] + y[0] + z[0]; + z[0] = (uint)c; + c >>= 32; + c += (ulong)x[1] + y[1] + z[1]; + z[1] = (uint)c; + c >>= 32; + c += (ulong)x[2] + y[2] + z[2]; + z[2] = (uint)c; + c >>= 32; + c += (ulong)x[3] + y[3] + z[3]; + z[3] = (uint)c; + c >>= 32; + c += (ulong)x[4] + y[4] + z[4]; + z[4] = (uint)c; + c >>= 32; + c += (ulong)x[5] + y[5] + z[5]; + z[5] = (uint)c; + c >>= 32; + c += (ulong)x[6] + y[6] + z[6]; + z[6] = (uint)c; + c >>= 32; + return (uint)c; + } + + public static uint AddBothTo(uint[] x, int xOff, uint[] y, int yOff, uint[] z, int zOff) + { + ulong c = 0; + c += (ulong)x[xOff + 0] + y[yOff + 0] + z[zOff + 0]; + z[zOff + 0] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 1] + y[yOff + 1] + z[zOff + 1]; + z[zOff + 1] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 2] + y[yOff + 2] + z[zOff + 2]; + z[zOff + 2] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 3] + y[yOff + 3] + z[zOff + 3]; + z[zOff + 3] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 4] + y[yOff + 4] + z[zOff + 4]; + z[zOff + 4] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 5] + y[yOff + 5] + z[zOff + 5]; + z[zOff + 5] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 6] + y[yOff + 6] + z[zOff + 6]; + z[zOff + 6] = (uint)c; + c >>= 32; + return (uint)c; + } + + public static uint AddTo(uint[] x, uint[] z) + { + ulong c = 0; + c += (ulong)x[0] + z[0]; + z[0] = (uint)c; + c >>= 32; + c += (ulong)x[1] + z[1]; + z[1] = (uint)c; + c >>= 32; + c += (ulong)x[2] + z[2]; + z[2] = (uint)c; + c >>= 32; + c += (ulong)x[3] + z[3]; + z[3] = (uint)c; + c >>= 32; + c += (ulong)x[4] + z[4]; + z[4] = (uint)c; + c >>= 32; + c += (ulong)x[5] + z[5]; + z[5] = (uint)c; + c >>= 32; + c += (ulong)x[6] + z[6]; + z[6] = (uint)c; + c >>= 32; + return (uint)c; + } + + public static uint AddTo(uint[] x, int xOff, uint[] z, int zOff, uint cIn) + { + ulong c = cIn; + c += (ulong)x[xOff + 0] + z[zOff + 0]; + z[zOff + 0] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 1] + z[zOff + 1]; + z[zOff + 1] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 2] + z[zOff + 2]; + z[zOff + 2] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 3] + z[zOff + 3]; + z[zOff + 3] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 4] + z[zOff + 4]; + z[zOff + 4] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 5] + z[zOff + 5]; + z[zOff + 5] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 6] + z[zOff + 6]; + z[zOff + 6] = (uint)c; + c >>= 32; + return (uint)c; + } + + public static uint AddToEachOther(uint[] u, int uOff, uint[] v, int vOff) + { + ulong c = 0; + c += (ulong)u[uOff + 0] + v[vOff + 0]; + u[uOff + 0] = (uint)c; + v[vOff + 0] = (uint)c; + c >>= 32; + c += (ulong)u[uOff + 1] + v[vOff + 1]; + u[uOff + 1] = (uint)c; + v[vOff + 1] = (uint)c; + c >>= 32; + c += (ulong)u[uOff + 2] + v[vOff + 2]; + u[uOff + 2] = (uint)c; + v[vOff + 2] = (uint)c; + c >>= 32; + c += (ulong)u[uOff + 3] + v[vOff + 3]; + u[uOff + 3] = (uint)c; + v[vOff + 3] = (uint)c; + c >>= 32; + c += (ulong)u[uOff + 4] + v[vOff + 4]; + u[uOff + 4] = (uint)c; + v[vOff + 4] = (uint)c; + c >>= 32; + c += (ulong)u[uOff + 5] + v[vOff + 5]; + u[uOff + 5] = (uint)c; + v[vOff + 5] = (uint)c; + c >>= 32; + c += (ulong)u[uOff + 6] + v[vOff + 6]; + u[uOff + 6] = (uint)c; + v[vOff + 6] = (uint)c; + c >>= 32; + return (uint)c; + } + + public static void Copy(uint[] x, uint[] z) + { + z[0] = x[0]; + z[1] = x[1]; + z[2] = x[2]; + z[3] = x[3]; + z[4] = x[4]; + z[5] = x[5]; + z[6] = x[6]; + } + + public static uint[] Create() + { + return new uint[7]; + } + + public static uint[] CreateExt() + { + return new uint[14]; + } + + public static bool Diff(uint[] x, int xOff, uint[] y, int yOff, uint[] z, int zOff) + { + bool pos = Gte(x, xOff, y, yOff); + if (pos) + { + Sub(x, xOff, y, yOff, z, zOff); + } + else + { + Sub(y, yOff, x, xOff, z, zOff); + } + return pos; + } + + public static bool Eq(uint[] x, uint[] y) + { + for (int i = 6; i >= 0; --i) + { + if (x[i] != y[i]) + return false; + } + return true; + } + + public static uint[] FromBigInteger(BigInteger x) + { + if (x.SignValue < 0 || x.BitLength > 224) + throw new ArgumentException(); + + uint[] z = Create(); + int i = 0; + while (x.SignValue != 0) + { + z[i++] = (uint)x.IntValue; + x = x.ShiftRight(32); + } + return z; + } + + public static uint GetBit(uint[] x, int bit) + { + if (bit == 0) + { + return x[0] & 1; + } + int w = bit >> 5; + if (w < 0 || w >= 7) + { + return 0; + } + int b = bit & 31; + return (x[w] >> b) & 1; + } + + public static bool Gte(uint[] x, uint[] y) + { + for (int i = 6; i >= 0; --i) + { + uint x_i = x[i], y_i = y[i]; + if (x_i < y_i) + return false; + if (x_i > y_i) + return true; + } + return true; + } + + public static bool Gte(uint[] x, int xOff, uint[] y, int yOff) + { + for (int i = 6; i >= 0; --i) + { + uint x_i = x[xOff + i], y_i = y[yOff + i]; + if (x_i < y_i) + return false; + if (x_i > y_i) + return true; + } + return true; + } + + public static bool IsOne(uint[] x) + { + if (x[0] != 1) + { + return false; + } + for (int i = 1; i < 7; ++i) + { + if (x[i] != 0) + { + return false; + } + } + return true; + } + + public static bool IsZero(uint[] x) + { + for (int i = 0; i < 7; ++i) + { + if (x[i] != 0) + { + return false; + } + } + return true; + } + + public static void Mul(uint[] x, uint[] y, uint[] zz) + { + ulong y_0 = y[0]; + ulong y_1 = y[1]; + ulong y_2 = y[2]; + ulong y_3 = y[3]; + ulong y_4 = y[4]; + ulong y_5 = y[5]; + ulong y_6 = y[6]; + + { + ulong c = 0, x_0 = x[0]; + c += x_0 * y_0; + zz[0] = (uint)c; + c >>= 32; + c += x_0 * y_1; + zz[1] = (uint)c; + c >>= 32; + c += x_0 * y_2; + zz[2] = (uint)c; + c >>= 32; + c += x_0 * y_3; + zz[3] = (uint)c; + c >>= 32; + c += x_0 * y_4; + zz[4] = (uint)c; + c >>= 32; + c += x_0 * y_5; + zz[5] = (uint)c; + c >>= 32; + c += x_0 * y_6; + zz[6] = (uint)c; + c >>= 32; + zz[7] = (uint)c; + } + + for (int i = 1; i < 7; ++i) + { + ulong c = 0, x_i = x[i]; + c += x_i * y_0 + zz[i + 0]; + zz[i + 0] = (uint)c; + c >>= 32; + c += x_i * y_1 + zz[i + 1]; + zz[i + 1] = (uint)c; + c >>= 32; + c += x_i * y_2 + zz[i + 2]; + zz[i + 2] = (uint)c; + c >>= 32; + c += x_i * y_3 + zz[i + 3]; + zz[i + 3] = (uint)c; + c >>= 32; + c += x_i * y_4 + zz[i + 4]; + zz[i + 4] = (uint)c; + c >>= 32; + c += x_i * y_5 + zz[i + 5]; + zz[i + 5] = (uint)c; + c >>= 32; + c += x_i * y_6 + zz[i + 6]; + zz[i + 6] = (uint)c; + c >>= 32; + zz[i + 7] = (uint)c; + } + } + + public static void Mul(uint[] x, int xOff, uint[] y, int yOff, uint[] zz, int zzOff) + { + ulong y_0 = y[yOff + 0]; + ulong y_1 = y[yOff + 1]; + ulong y_2 = y[yOff + 2]; + ulong y_3 = y[yOff + 3]; + ulong y_4 = y[yOff + 4]; + ulong y_5 = y[yOff + 5]; + ulong y_6 = y[yOff + 6]; + + { + ulong c = 0, x_0 = x[xOff + 0]; + c += x_0 * y_0; + zz[zzOff + 0] = (uint)c; + c >>= 32; + c += x_0 * y_1; + zz[zzOff + 1] = (uint)c; + c >>= 32; + c += x_0 * y_2; + zz[zzOff + 2] = (uint)c; + c >>= 32; + c += x_0 * y_3; + zz[zzOff + 3] = (uint)c; + c >>= 32; + c += x_0 * y_4; + zz[zzOff + 4] = (uint)c; + c >>= 32; + c += x_0 * y_5; + zz[zzOff + 5] = (uint)c; + c >>= 32; + c += x_0 * y_6; + zz[zzOff + 6] = (uint)c; + c >>= 32; + zz[zzOff + 7] = (uint)c; + } + + for (int i = 1; i < 7; ++i) + { + ++zzOff; + ulong c = 0, x_i = x[xOff + i]; + c += x_i * y_0 + zz[zzOff + 0]; + zz[zzOff + 0] = (uint)c; + c >>= 32; + c += x_i * y_1 + zz[zzOff + 1]; + zz[zzOff + 1] = (uint)c; + c >>= 32; + c += x_i * y_2 + zz[zzOff + 2]; + zz[zzOff + 2] = (uint)c; + c >>= 32; + c += x_i * y_3 + zz[zzOff + 3]; + zz[zzOff + 3] = (uint)c; + c >>= 32; + c += x_i * y_4 + zz[zzOff + 4]; + zz[zzOff + 4] = (uint)c; + c >>= 32; + c += x_i * y_5 + zz[zzOff + 5]; + zz[zzOff + 5] = (uint)c; + c >>= 32; + c += x_i * y_6 + zz[zzOff + 6]; + zz[zzOff + 6] = (uint)c; + c >>= 32; + zz[zzOff + 7] = (uint)c; + } + } + + public static uint MulAddTo(uint[] x, uint[] y, uint[] zz) + { + ulong y_0 = y[0]; + ulong y_1 = y[1]; + ulong y_2 = y[2]; + ulong y_3 = y[3]; + ulong y_4 = y[4]; + ulong y_5 = y[5]; + ulong y_6 = y[6]; + + ulong zc = 0; + for (int i = 0; i < 7; ++i) + { + ulong c = 0, x_i = x[i]; + c += x_i * y_0 + zz[i + 0]; + zz[i + 0] = (uint)c; + c >>= 32; + c += x_i * y_1 + zz[i + 1]; + zz[i + 1] = (uint)c; + c >>= 32; + c += x_i * y_2 + zz[i + 2]; + zz[i + 2] = (uint)c; + c >>= 32; + c += x_i * y_3 + zz[i + 3]; + zz[i + 3] = (uint)c; + c >>= 32; + c += x_i * y_4 + zz[i + 4]; + zz[i + 4] = (uint)c; + c >>= 32; + c += x_i * y_5 + zz[i + 5]; + zz[i + 5] = (uint)c; + c >>= 32; + c += x_i * y_6 + zz[i + 6]; + zz[i + 6] = (uint)c; + c >>= 32; + c += zc + zz[i + 7]; + zz[i + 7] = (uint)c; + zc = c >> 32; + } + return (uint)zc; + } + + public static uint MulAddTo(uint[] x, int xOff, uint[] y, int yOff, uint[] zz, int zzOff) + { + ulong y_0 = y[yOff + 0]; + ulong y_1 = y[yOff + 1]; + ulong y_2 = y[yOff + 2]; + ulong y_3 = y[yOff + 3]; + ulong y_4 = y[yOff + 4]; + ulong y_5 = y[yOff + 5]; + ulong y_6 = y[yOff + 6]; + + ulong zc = 0; + for (int i = 0; i < 7; ++i) + { + ulong c = 0, x_i = x[xOff + i]; + c += x_i * y_0 + zz[zzOff + 0]; + zz[zzOff + 0] = (uint)c; + c >>= 32; + c += x_i * y_1 + zz[zzOff + 1]; + zz[zzOff + 1] = (uint)c; + c >>= 32; + c += x_i * y_2 + zz[zzOff + 2]; + zz[zzOff + 2] = (uint)c; + c >>= 32; + c += x_i * y_3 + zz[zzOff + 3]; + zz[zzOff + 3] = (uint)c; + c >>= 32; + c += x_i * y_4 + zz[zzOff + 4]; + zz[zzOff + 4] = (uint)c; + c >>= 32; + c += x_i * y_5 + zz[zzOff + 5]; + zz[zzOff + 5] = (uint)c; + c >>= 32; + c += x_i * y_6 + zz[zzOff + 6]; + zz[zzOff + 6] = (uint)c; + c >>= 32; + c += zc + zz[zzOff + 7]; + zz[zzOff + 7] = (uint)c; + zc = c >> 32; + ++zzOff; + } + return (uint)zc; + } + + public static ulong Mul33Add(uint w, uint[] x, int xOff, uint[] y, int yOff, uint[] z, int zOff) + { + Debug.Assert(w >> 31 == 0); + + ulong c = 0, wVal = w; + ulong x0 = x[xOff + 0]; + c += wVal * x0 + y[yOff + 0]; + z[zOff + 0] = (uint)c; + c >>= 32; + ulong x1 = x[xOff + 1]; + c += wVal * x1 + x0 + y[yOff + 1]; + z[zOff + 1] = (uint)c; + c >>= 32; + ulong x2 = x[xOff + 2]; + c += wVal * x2 + x1 + y[yOff + 2]; + z[zOff + 2] = (uint)c; + c >>= 32; + ulong x3 = x[xOff + 3]; + c += wVal * x3 + x2 + y[yOff + 3]; + z[zOff + 3] = (uint)c; + c >>= 32; + ulong x4 = x[xOff + 4]; + c += wVal * x4 + x3 + y[yOff + 4]; + z[zOff + 4] = (uint)c; + c >>= 32; + ulong x5 = x[xOff + 5]; + c += wVal * x5 + x4 + y[yOff + 5]; + z[zOff + 5] = (uint)c; + c >>= 32; + ulong x6 = x[xOff + 6]; + c += wVal * x6 + x5 + y[yOff + 6]; + z[zOff + 6] = (uint)c; + c >>= 32; + c += x6; + return c; + } + + public static uint MulByWord(uint x, uint[] z) + { + ulong c = 0, xVal = x; + c += xVal * (ulong)z[0]; + z[0] = (uint)c; + c >>= 32; + c += xVal * (ulong)z[1]; + z[1] = (uint)c; + c >>= 32; + c += xVal * (ulong)z[2]; + z[2] = (uint)c; + c >>= 32; + c += xVal * (ulong)z[3]; + z[3] = (uint)c; + c >>= 32; + c += xVal * (ulong)z[4]; + z[4] = (uint)c; + c >>= 32; + c += xVal * (ulong)z[5]; + z[5] = (uint)c; + c >>= 32; + c += xVal * (ulong)z[6]; + z[6] = (uint)c; + c >>= 32; + return (uint)c; + } + + public static uint MulByWordAddTo(uint x, uint[] y, uint[] z) + { + ulong c = 0, xVal = x; + c += xVal * (ulong)z[0] + y[0]; + z[0] = (uint)c; + c >>= 32; + c += xVal * (ulong)z[1] + y[1]; + z[1] = (uint)c; + c >>= 32; + c += xVal * (ulong)z[2] + y[2]; + z[2] = (uint)c; + c >>= 32; + c += xVal * (ulong)z[3] + y[3]; + z[3] = (uint)c; + c >>= 32; + c += xVal * (ulong)z[4] + y[4]; + z[4] = (uint)c; + c >>= 32; + c += xVal * (ulong)z[5] + y[5]; + z[5] = (uint)c; + c >>= 32; + c += xVal * (ulong)z[6] + y[6]; + z[6] = (uint)c; + c >>= 32; + return (uint)c; + } + + public static uint MulWordAddTo(uint x, uint[] y, int yOff, uint[] z, int zOff) + { + ulong c = 0, xVal = x; + c += xVal * y[yOff + 0] + z[zOff + 0]; + z[zOff + 0] = (uint)c; + c >>= 32; + c += xVal * y[yOff + 1] + z[zOff + 1]; + z[zOff + 1] = (uint)c; + c >>= 32; + c += xVal * y[yOff + 2] + z[zOff + 2]; + z[zOff + 2] = (uint)c; + c >>= 32; + c += xVal * y[yOff + 3] + z[zOff + 3]; + z[zOff + 3] = (uint)c; + c >>= 32; + c += xVal * y[yOff + 4] + z[zOff + 4]; + z[zOff + 4] = (uint)c; + c >>= 32; + c += xVal * y[yOff + 5] + z[zOff + 5]; + z[zOff + 5] = (uint)c; + c >>= 32; + c += xVal * y[yOff + 6] + z[zOff + 6]; + z[zOff + 6] = (uint)c; + c >>= 32; + return (uint)c; + } + + public static uint Mul33DWordAdd(uint x, ulong y, uint[] z, int zOff) + { + Debug.Assert(x >> 31 == 0); + Debug.Assert(zOff <= 3); + ulong c = 0, xVal = x; + ulong y00 = y & M; + c += xVal * y00 + z[zOff + 0]; + z[zOff + 0] = (uint)c; + c >>= 32; + ulong y01 = y >> 32; + c += xVal * y01 + y00 + z[zOff + 1]; + z[zOff + 1] = (uint)c; + c >>= 32; + c += y01 + z[zOff + 2]; + z[zOff + 2] = (uint)c; + c >>= 32; + c += z[zOff + 3]; + z[zOff + 3] = (uint)c; + c >>= 32; + return c == 0 ? 0 : Nat.IncAt(7, z, zOff, 4); + } + + public static uint Mul33WordAdd(uint x, uint y, uint[] z, int zOff) + { + Debug.Assert(x >> 31 == 0); + Debug.Assert(zOff <= 4); + ulong c = 0, yVal = y; + c += yVal * x + z[zOff + 0]; + z[zOff + 0] = (uint)c; + c >>= 32; + c += yVal + z[zOff + 1]; + z[zOff + 1] = (uint)c; + c >>= 32; + c += z[zOff + 2]; + z[zOff + 2] = (uint)c; + c >>= 32; + return c == 0 ? 0 : Nat.IncAt(7, z, zOff, 3); + } + + public static uint MulWordDwordAdd(uint x, ulong y, uint[] z, int zOff) + { + Debug.Assert(zOff <= 4); + ulong c = 0, xVal = x; + c += xVal * y + z[zOff + 0]; + z[zOff + 0] = (uint)c; + c >>= 32; + c += xVal * (y >> 32) + z[zOff + 1]; + z[zOff + 1] = (uint)c; + c >>= 32; + c += z[zOff + 2]; + z[zOff + 2] = (uint)c; + c >>= 32; + return c == 0 ? 0 : Nat.IncAt(7, z, zOff, 3); + } + + public static uint MulWord(uint x, uint[] y, uint[] z, int zOff) + { + ulong c = 0, xVal = x; + int i = 0; + do + { + c += xVal * y[i]; + z[zOff + i] = (uint)c; + c >>= 32; + } + while (++i < 7); + return (uint)c; + } + + public static void Square(uint[] x, uint[] zz) + { + ulong x_0 = x[0]; + ulong zz_1; + + uint c = 0, w; + { + int i = 6, j = 14; + do + { + ulong xVal = x[i--]; + ulong p = xVal * xVal; + zz[--j] = (c << 31) | (uint)(p >> 33); + zz[--j] = (uint)(p >> 1); + c = (uint)p; + } + while (i > 0); + + { + ulong p = x_0 * x_0; + zz_1 = (ulong)(c << 31) | (p >> 33); + zz[0] = (uint)p; + c = (uint)(p >> 32) & 1; + } + } + + ulong x_1 = x[1]; + ulong zz_2 = zz[2]; + + { + zz_1 += x_1 * x_0; + w = (uint)zz_1; + zz[1] = (w << 1) | c; + c = w >> 31; + zz_2 += zz_1 >> 32; + } + + ulong x_2 = x[2]; + ulong zz_3 = zz[3]; + ulong zz_4 = zz[4]; + { + zz_2 += x_2 * x_0; + w = (uint)zz_2; + zz[2] = (w << 1) | c; + c = w >> 31; + zz_3 += (zz_2 >> 32) + x_2 * x_1; + zz_4 += zz_3 >> 32; + zz_3 &= M; + } + + ulong x_3 = x[3]; + ulong zz_5 = zz[5] + (zz_4 >> 32); zz_4 &= M; + ulong zz_6 = zz[6] + (zz_5 >> 32); zz_5 &= M; + { + zz_3 += x_3 * x_0; + w = (uint)zz_3; + zz[3] = (w << 1) | c; + c = w >> 31; + zz_4 += (zz_3 >> 32) + x_3 * x_1; + zz_5 += (zz_4 >> 32) + x_3 * x_2; + zz_4 &= M; + zz_6 += zz_5 >> 32; + zz_5 &= M; + } + + ulong x_4 = x[4]; + ulong zz_7 = zz[7] + (zz_6 >> 32); zz_6 &= M; + ulong zz_8 = zz[8] + (zz_7 >> 32); zz_7 &= M; + { + zz_4 += x_4 * x_0; + w = (uint)zz_4; + zz[4] = (w << 1) | c; + c = w >> 31; + zz_5 += (zz_4 >> 32) + x_4 * x_1; + zz_6 += (zz_5 >> 32) + x_4 * x_2; + zz_5 &= M; + zz_7 += (zz_6 >> 32) + x_4 * x_3; + zz_6 &= M; + zz_8 += zz_7 >> 32; + zz_7 &= M; + } + + ulong x_5 = x[5]; + ulong zz_9 = zz[9] + (zz_8 >> 32); zz_8 &= M; + ulong zz_10 = zz[10] + (zz_9 >> 32); zz_9 &= M; + { + zz_5 += x_5 * x_0; + w = (uint)zz_5; + zz[5] = (w << 1) | c; + c = w >> 31; + zz_6 += (zz_5 >> 32) + x_5 * x_1; + zz_7 += (zz_6 >> 32) + x_5 * x_2; + zz_6 &= M; + zz_8 += (zz_7 >> 32) + x_5 * x_3; + zz_7 &= M; + zz_9 += (zz_8 >> 32) + x_5 * x_4; + zz_8 &= M; + zz_10 += zz_9 >> 32; + zz_9 &= M; + } + + ulong x_6 = x[6]; + ulong zz_11 = zz[11] + (zz_10 >> 32); zz_10 &= M; + ulong zz_12 = zz[12] + (zz_11 >> 32); zz_11 &= M; + { + zz_6 += x_6 * x_0; + w = (uint)zz_6; + zz[6] = (w << 1) | c; + c = w >> 31; + zz_7 += (zz_6 >> 32) + x_6 * x_1; + zz_8 += (zz_7 >> 32) + x_6 * x_2; + zz_9 += (zz_8 >> 32) + x_6 * x_3; + zz_10 += (zz_9 >> 32) + x_6 * x_4; + zz_11 += (zz_10 >> 32) + x_6 * x_5; + zz_12 += zz_11 >> 32; + } + + w = (uint)zz_7; + zz[7] = (w << 1) | c; + c = w >> 31; + w = (uint)zz_8; + zz[8] = (w << 1) | c; + c = w >> 31; + w = (uint)zz_9; + zz[9] = (w << 1) | c; + c = w >> 31; + w = (uint)zz_10; + zz[10] = (w << 1) | c; + c = w >> 31; + w = (uint)zz_11; + zz[11] = (w << 1) | c; + c = w >> 31; + w = (uint)zz_12; + zz[12] = (w << 1) | c; + c = w >> 31; + w = zz[13] + (uint)(zz_12 >> 32); + zz[13] = (w << 1) | c; + } + + public static void Square(uint[] x, int xOff, uint[] zz, int zzOff) + { + ulong x_0 = x[xOff + 0]; + ulong zz_1; + + uint c = 0, w; + { + int i = 6, j = 14; + do + { + ulong xVal = x[xOff + i--]; + ulong p = xVal * xVal; + zz[zzOff + --j] = (c << 31) | (uint)(p >> 33); + zz[zzOff + --j] = (uint)(p >> 1); + c = (uint)p; + } + while (i > 0); + + { + ulong p = x_0 * x_0; + zz_1 = (ulong)(c << 31) | (p >> 33); + zz[zzOff + 0] = (uint)p; + c = (uint)(p >> 32) & 1; + } + } + + ulong x_1 = x[xOff + 1]; + ulong zz_2 = zz[zzOff + 2]; + + { + zz_1 += x_1 * x_0; + w = (uint)zz_1; + zz[zzOff + 1] = (w << 1) | c; + c = w >> 31; + zz_2 += zz_1 >> 32; + } + + ulong x_2 = x[xOff + 2]; + ulong zz_3 = zz[zzOff + 3]; + ulong zz_4 = zz[zzOff + 4]; + { + zz_2 += x_2 * x_0; + w = (uint)zz_2; + zz[zzOff + 2] = (w << 1) | c; + c = w >> 31; + zz_3 += (zz_2 >> 32) + x_2 * x_1; + zz_4 += zz_3 >> 32; + zz_3 &= M; + } + + ulong x_3 = x[xOff + 3]; + ulong zz_5 = zz[zzOff + 5] + (zz_4 >> 32); zz_4 &= M; + ulong zz_6 = zz[zzOff + 6] + (zz_5 >> 32); zz_5 &= M; + { + zz_3 += x_3 * x_0; + w = (uint)zz_3; + zz[zzOff + 3] = (w << 1) | c; + c = w >> 31; + zz_4 += (zz_3 >> 32) + x_3 * x_1; + zz_5 += (zz_4 >> 32) + x_3 * x_2; + zz_4 &= M; + zz_6 += zz_5 >> 32; + zz_5 &= M; + } + + ulong x_4 = x[xOff + 4]; + ulong zz_7 = zz[zzOff + 7] + (zz_6 >> 32); zz_6 &= M; + ulong zz_8 = zz[zzOff + 8] + (zz_7 >> 32); zz_7 &= M; + { + zz_4 += x_4 * x_0; + w = (uint)zz_4; + zz[zzOff + 4] = (w << 1) | c; + c = w >> 31; + zz_5 += (zz_4 >> 32) + x_4 * x_1; + zz_6 += (zz_5 >> 32) + x_4 * x_2; + zz_5 &= M; + zz_7 += (zz_6 >> 32) + x_4 * x_3; + zz_6 &= M; + zz_8 += zz_7 >> 32; + zz_7 &= M; + } + + ulong x_5 = x[xOff + 5]; + ulong zz_9 = zz[zzOff + 9] + (zz_8 >> 32); zz_8 &= M; + ulong zz_10 = zz[zzOff + 10] + (zz_9 >> 32); zz_9 &= M; + { + zz_5 += x_5 * x_0; + w = (uint)zz_5; + zz[zzOff + 5] = (w << 1) | c; + c = w >> 31; + zz_6 += (zz_5 >> 32) + x_5 * x_1; + zz_7 += (zz_6 >> 32) + x_5 * x_2; + zz_6 &= M; + zz_8 += (zz_7 >> 32) + x_5 * x_3; + zz_7 &= M; + zz_9 += (zz_8 >> 32) + x_5 * x_4; + zz_8 &= M; + zz_10 += zz_9 >> 32; + zz_9 &= M; + } + + ulong x_6 = x[xOff + 6]; + ulong zz_11 = zz[zzOff + 11] + (zz_10 >> 32); zz_10 &= M; + ulong zz_12 = zz[zzOff + 12] + (zz_11 >> 32); zz_11 &= M; + { + zz_6 += x_6 * x_0; + w = (uint)zz_6; + zz[zzOff + 6] = (w << 1) | c; + c = w >> 31; + zz_7 += (zz_6 >> 32) + x_6 * x_1; + zz_8 += (zz_7 >> 32) + x_6 * x_2; + zz_9 += (zz_8 >> 32) + x_6 * x_3; + zz_10 += (zz_9 >> 32) + x_6 * x_4; + zz_11 += (zz_10 >> 32) + x_6 * x_5; + zz_12 += zz_11 >> 32; + } + + w = (uint)zz_7; + zz[zzOff + 7] = (w << 1) | c; + c = w >> 31; + w = (uint)zz_8; + zz[zzOff + 8] = (w << 1) | c; + c = w >> 31; + w = (uint)zz_9; + zz[zzOff + 9] = (w << 1) | c; + c = w >> 31; + w = (uint)zz_10; + zz[zzOff + 10] = (w << 1) | c; + c = w >> 31; + w = (uint)zz_11; + zz[zzOff + 11] = (w << 1) | c; + c = w >> 31; + w = (uint)zz_12; + zz[zzOff + 12] = (w << 1) | c; + c = w >> 31; + w = zz[zzOff + 13] + (uint)(zz_12 >> 32); + zz[zzOff + 13] = (w << 1) | c; + } + + public static int Sub(uint[] x, uint[] y, uint[] z) + { + long c = 0; + c += (long)x[0] - y[0]; + z[0] = (uint)c; + c >>= 32; + c += (long)x[1] - y[1]; + z[1] = (uint)c; + c >>= 32; + c += (long)x[2] - y[2]; + z[2] = (uint)c; + c >>= 32; + c += (long)x[3] - y[3]; + z[3] = (uint)c; + c >>= 32; + c += (long)x[4] - y[4]; + z[4] = (uint)c; + c >>= 32; + c += (long)x[5] - y[5]; + z[5] = (uint)c; + c >>= 32; + c += (long)x[6] - y[6]; + z[6] = (uint)c; + c >>= 32; + return (int)c; + } + + public static int Sub(uint[] x, int xOff, uint[] y, int yOff, uint[] z, int zOff) + { + long c = 0; + c += (long)x[xOff + 0] - y[yOff + 0]; + z[zOff + 0] = (uint)c; + c >>= 32; + c += (long)x[xOff + 1] - y[yOff + 1]; + z[zOff + 1] = (uint)c; + c >>= 32; + c += (long)x[xOff + 2] - y[yOff + 2]; + z[zOff + 2] = (uint)c; + c >>= 32; + c += (long)x[xOff + 3] - y[yOff + 3]; + z[zOff + 3] = (uint)c; + c >>= 32; + c += (long)x[xOff + 4] - y[yOff + 4]; + z[zOff + 4] = (uint)c; + c >>= 32; + c += (long)x[xOff + 5] - y[yOff + 5]; + z[zOff + 5] = (uint)c; + c >>= 32; + c += (long)x[xOff + 6] - y[yOff + 6]; + z[zOff + 6] = (uint)c; + c >>= 32; + return (int)c; + } + + public static int SubBothFrom(uint[] x, uint[] y, uint[] z) + { + long c = 0; + c += (long)z[0] - x[0] - y[0]; + z[0] = (uint)c; + c >>= 32; + c += (long)z[1] - x[1] - y[1]; + z[1] = (uint)c; + c >>= 32; + c += (long)z[2] - x[2] - y[2]; + z[2] = (uint)c; + c >>= 32; + c += (long)z[3] - x[3] - y[3]; + z[3] = (uint)c; + c >>= 32; + c += (long)z[4] - x[4] - y[4]; + z[4] = (uint)c; + c >>= 32; + c += (long)z[5] - x[5] - y[5]; + z[5] = (uint)c; + c >>= 32; + c += (long)z[6] - x[6] - y[6]; + z[6] = (uint)c; + c >>= 32; + return (int)c; + } + + public static int SubFrom(uint[] x, uint[] z) + { + long c = 0; + c += (long)z[0] - x[0]; + z[0] = (uint)c; + c >>= 32; + c += (long)z[1] - x[1]; + z[1] = (uint)c; + c >>= 32; + c += (long)z[2] - x[2]; + z[2] = (uint)c; + c >>= 32; + c += (long)z[3] - x[3]; + z[3] = (uint)c; + c >>= 32; + c += (long)z[4] - x[4]; + z[4] = (uint)c; + c >>= 32; + c += (long)z[5] - x[5]; + z[5] = (uint)c; + c >>= 32; + c += (long)z[6] - x[6]; + z[6] = (uint)c; + c >>= 32; + return (int)c; + } + + public static int SubFrom(uint[] x, int xOff, uint[] z, int zOff) + { + long c = 0; + c += (long)z[zOff + 0] - x[xOff + 0]; + z[zOff + 0] = (uint)c; + c >>= 32; + c += (long)z[zOff + 1] - x[xOff + 1]; + z[zOff + 1] = (uint)c; + c >>= 32; + c += (long)z[zOff + 2] - x[xOff + 2]; + z[zOff + 2] = (uint)c; + c >>= 32; + c += (long)z[zOff + 3] - x[xOff + 3]; + z[zOff + 3] = (uint)c; + c >>= 32; + c += (long)z[zOff + 4] - x[xOff + 4]; + z[zOff + 4] = (uint)c; + c >>= 32; + c += (long)z[zOff + 5] - x[xOff + 5]; + z[zOff + 5] = (uint)c; + c >>= 32; + c += (long)z[zOff + 6] - x[xOff + 6]; + z[zOff + 6] = (uint)c; + c >>= 32; + return (int)c; + } + + public static BigInteger ToBigInteger(uint[] x) + { + byte[] bs = new byte[28]; + for (int i = 0; i < 7; ++i) + { + uint x_i = x[i]; + if (x_i != 0) + { + Pack.UInt32_To_BE(x_i, bs, (6 - i) << 2); + } + } + return new BigInteger(1, bs); + } + + public static void Zero(uint[] z) + { + z[0] = 0; + z[1] = 0; + z[2] = 0; + z[3] = 0; + z[4] = 0; + z[5] = 0; + z[6] = 0; + } + } +} diff --git a/bc-sharp-crypto/src/math/raw/Nat256.cs b/bc-sharp-crypto/src/math/raw/Nat256.cs new file mode 100644 index 0000000..09c751a --- /dev/null +++ b/bc-sharp-crypto/src/math/raw/Nat256.cs @@ -0,0 +1,1387 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Crypto.Utilities; + +namespace Org.BouncyCastle.Math.Raw +{ + internal abstract class Nat256 + { + private const ulong M = 0xFFFFFFFFUL; + + public static uint Add(uint[] x, uint[] y, uint[] z) + { + ulong c = 0; + c += (ulong)x[0] + y[0]; + z[0] = (uint)c; + c >>= 32; + c += (ulong)x[1] + y[1]; + z[1] = (uint)c; + c >>= 32; + c += (ulong)x[2] + y[2]; + z[2] = (uint)c; + c >>= 32; + c += (ulong)x[3] + y[3]; + z[3] = (uint)c; + c >>= 32; + c += (ulong)x[4] + y[4]; + z[4] = (uint)c; + c >>= 32; + c += (ulong)x[5] + y[5]; + z[5] = (uint)c; + c >>= 32; + c += (ulong)x[6] + y[6]; + z[6] = (uint)c; + c >>= 32; + c += (ulong)x[7] + y[7]; + z[7] = (uint)c; + c >>= 32; + return (uint)c; + } + + public static uint Add(uint[] x, int xOff, uint[] y, int yOff, uint[] z, int zOff) + { + ulong c = 0; + c += (ulong)x[xOff + 0] + y[yOff + 0]; + z[zOff + 0] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 1] + y[yOff + 1]; + z[zOff + 1] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 2] + y[yOff + 2]; + z[zOff + 2] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 3] + y[yOff + 3]; + z[zOff + 3] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 4] + y[yOff + 4]; + z[zOff + 4] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 5] + y[yOff + 5]; + z[zOff + 5] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 6] + y[yOff + 6]; + z[zOff + 6] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 7] + y[yOff + 7]; + z[zOff + 7] = (uint)c; + c >>= 32; + return (uint)c; + } + + public static uint AddBothTo(uint[] x, uint[] y, uint[] z) + { + ulong c = 0; + c += (ulong)x[0] + y[0] + z[0]; + z[0] = (uint)c; + c >>= 32; + c += (ulong)x[1] + y[1] + z[1]; + z[1] = (uint)c; + c >>= 32; + c += (ulong)x[2] + y[2] + z[2]; + z[2] = (uint)c; + c >>= 32; + c += (ulong)x[3] + y[3] + z[3]; + z[3] = (uint)c; + c >>= 32; + c += (ulong)x[4] + y[4] + z[4]; + z[4] = (uint)c; + c >>= 32; + c += (ulong)x[5] + y[5] + z[5]; + z[5] = (uint)c; + c >>= 32; + c += (ulong)x[6] + y[6] + z[6]; + z[6] = (uint)c; + c >>= 32; + c += (ulong)x[7] + y[7] + z[7]; + z[7] = (uint)c; + c >>= 32; + return (uint)c; + } + + public static uint AddBothTo(uint[] x, int xOff, uint[] y, int yOff, uint[] z, int zOff) + { + ulong c = 0; + c += (ulong)x[xOff + 0] + y[yOff + 0] + z[zOff + 0]; + z[zOff + 0] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 1] + y[yOff + 1] + z[zOff + 1]; + z[zOff + 1] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 2] + y[yOff + 2] + z[zOff + 2]; + z[zOff + 2] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 3] + y[yOff + 3] + z[zOff + 3]; + z[zOff + 3] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 4] + y[yOff + 4] + z[zOff + 4]; + z[zOff + 4] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 5] + y[yOff + 5] + z[zOff + 5]; + z[zOff + 5] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 6] + y[yOff + 6] + z[zOff + 6]; + z[zOff + 6] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 7] + y[yOff + 7] + z[zOff + 7]; + z[zOff + 7] = (uint)c; + c >>= 32; + return (uint)c; + } + + public static uint AddTo(uint[] x, uint[] z) + { + ulong c = 0; + c += (ulong)x[0] + z[0]; + z[0] = (uint)c; + c >>= 32; + c += (ulong)x[1] + z[1]; + z[1] = (uint)c; + c >>= 32; + c += (ulong)x[2] + z[2]; + z[2] = (uint)c; + c >>= 32; + c += (ulong)x[3] + z[3]; + z[3] = (uint)c; + c >>= 32; + c += (ulong)x[4] + z[4]; + z[4] = (uint)c; + c >>= 32; + c += (ulong)x[5] + z[5]; + z[5] = (uint)c; + c >>= 32; + c += (ulong)x[6] + z[6]; + z[6] = (uint)c; + c >>= 32; + c += (ulong)x[7] + z[7]; + z[7] = (uint)c; + c >>= 32; + return (uint)c; + } + + public static uint AddTo(uint[] x, int xOff, uint[] z, int zOff, uint cIn) + { + ulong c = cIn; + c += (ulong)x[xOff + 0] + z[zOff + 0]; + z[zOff + 0] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 1] + z[zOff + 1]; + z[zOff + 1] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 2] + z[zOff + 2]; + z[zOff + 2] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 3] + z[zOff + 3]; + z[zOff + 3] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 4] + z[zOff + 4]; + z[zOff + 4] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 5] + z[zOff + 5]; + z[zOff + 5] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 6] + z[zOff + 6]; + z[zOff + 6] = (uint)c; + c >>= 32; + c += (ulong)x[xOff + 7] + z[zOff + 7]; + z[zOff + 7] = (uint)c; + c >>= 32; + return (uint)c; + } + + public static uint AddToEachOther(uint[] u, int uOff, uint[] v, int vOff) + { + ulong c = 0; + c += (ulong)u[uOff + 0] + v[vOff + 0]; + u[uOff + 0] = (uint)c; + v[vOff + 0] = (uint)c; + c >>= 32; + c += (ulong)u[uOff + 1] + v[vOff + 1]; + u[uOff + 1] = (uint)c; + v[vOff + 1] = (uint)c; + c >>= 32; + c += (ulong)u[uOff + 2] + v[vOff + 2]; + u[uOff + 2] = (uint)c; + v[vOff + 2] = (uint)c; + c >>= 32; + c += (ulong)u[uOff + 3] + v[vOff + 3]; + u[uOff + 3] = (uint)c; + v[vOff + 3] = (uint)c; + c >>= 32; + c += (ulong)u[uOff + 4] + v[vOff + 4]; + u[uOff + 4] = (uint)c; + v[vOff + 4] = (uint)c; + c >>= 32; + c += (ulong)u[uOff + 5] + v[vOff + 5]; + u[uOff + 5] = (uint)c; + v[vOff + 5] = (uint)c; + c >>= 32; + c += (ulong)u[uOff + 6] + v[vOff + 6]; + u[uOff + 6] = (uint)c; + v[vOff + 6] = (uint)c; + c >>= 32; + c += (ulong)u[uOff + 7] + v[vOff + 7]; + u[uOff + 7] = (uint)c; + v[vOff + 7] = (uint)c; + c >>= 32; + return (uint)c; + } + + public static void Copy(uint[] x, uint[] z) + { + z[0] = x[0]; + z[1] = x[1]; + z[2] = x[2]; + z[3] = x[3]; + z[4] = x[4]; + z[5] = x[5]; + z[6] = x[6]; + z[7] = x[7]; + } + + public static void Copy64(ulong[] x, ulong[] z) + { + z[0] = x[0]; + z[1] = x[1]; + z[2] = x[2]; + z[3] = x[3]; + } + + public static uint[] Create() + { + return new uint[8]; + } + + public static ulong[] Create64() + { + return new ulong[4]; + } + + public static uint[] CreateExt() + { + return new uint[16]; + } + + public static ulong[] CreateExt64() + { + return new ulong[8]; + } + + public static bool Diff(uint[] x, int xOff, uint[] y, int yOff, uint[] z, int zOff) + { + bool pos = Gte(x, xOff, y, yOff); + if (pos) + { + Sub(x, xOff, y, yOff, z, zOff); + } + else + { + Sub(y, yOff, x, xOff, z, zOff); + } + return pos; + } + + public static bool Eq(uint[] x, uint[] y) + { + for (int i = 7; i >= 0; --i) + { + if (x[i] != y[i]) + return false; + } + return true; + } + + public static bool Eq64(ulong[] x, ulong[] y) + { + for (int i = 3; i >= 0; --i) + { + if (x[i] != y[i]) + { + return false; + } + } + return true; + } + + public static uint[] FromBigInteger(BigInteger x) + { + if (x.SignValue < 0 || x.BitLength > 256) + throw new ArgumentException(); + + uint[] z = Create(); + int i = 0; + while (x.SignValue != 0) + { + z[i++] = (uint)x.IntValue; + x = x.ShiftRight(32); + } + return z; + } + + public static ulong[] FromBigInteger64(BigInteger x) + { + if (x.SignValue < 0 || x.BitLength > 256) + throw new ArgumentException(); + + ulong[] z = Create64(); + int i = 0; + while (x.SignValue != 0) + { + z[i++] = (ulong)x.LongValue; + x = x.ShiftRight(64); + } + return z; + } + + public static uint GetBit(uint[] x, int bit) + { + if (bit == 0) + { + return x[0] & 1; + } + if ((bit & 255) != bit) + { + return 0; + } + int w = bit >> 5; + int b = bit & 31; + return (x[w] >> b) & 1; + } + + public static bool Gte(uint[] x, uint[] y) + { + for (int i = 7; i >= 0; --i) + { + uint x_i = x[i], y_i = y[i]; + if (x_i < y_i) + return false; + if (x_i > y_i) + return true; + } + return true; + } + + public static bool Gte(uint[] x, int xOff, uint[] y, int yOff) + { + for (int i = 7; i >= 0; --i) + { + uint x_i = x[xOff + i], y_i = y[yOff + i]; + if (x_i < y_i) + return false; + if (x_i > y_i) + return true; + } + return true; + } + + public static bool IsOne(uint[] x) + { + if (x[0] != 1) + { + return false; + } + for (int i = 1; i < 8; ++i) + { + if (x[i] != 0) + { + return false; + } + } + return true; + } + + public static bool IsOne64(ulong[] x) + { + if (x[0] != 1UL) + { + return false; + } + for (int i = 1; i < 4; ++i) + { + if (x[i] != 0UL) + { + return false; + } + } + return true; + } + + public static bool IsZero(uint[] x) + { + for (int i = 0; i < 8; ++i) + { + if (x[i] != 0) + { + return false; + } + } + return true; + } + + public static bool IsZero64(ulong[] x) + { + for (int i = 0; i < 4; ++i) + { + if (x[i] != 0UL) + { + return false; + } + } + return true; + } + + public static void Mul(uint[] x, uint[] y, uint[] zz) + { + ulong y_0 = y[0]; + ulong y_1 = y[1]; + ulong y_2 = y[2]; + ulong y_3 = y[3]; + ulong y_4 = y[4]; + ulong y_5 = y[5]; + ulong y_6 = y[6]; + ulong y_7 = y[7]; + + { + ulong c = 0, x_0 = x[0]; + c += x_0 * y_0; + zz[0] = (uint)c; + c >>= 32; + c += x_0 * y_1; + zz[1] = (uint)c; + c >>= 32; + c += x_0 * y_2; + zz[2] = (uint)c; + c >>= 32; + c += x_0 * y_3; + zz[3] = (uint)c; + c >>= 32; + c += x_0 * y_4; + zz[4] = (uint)c; + c >>= 32; + c += x_0 * y_5; + zz[5] = (uint)c; + c >>= 32; + c += x_0 * y_6; + zz[6] = (uint)c; + c >>= 32; + c += x_0 * y_7; + zz[7] = (uint)c; + c >>= 32; + zz[8] = (uint)c; + } + + for (int i = 1; i < 8; ++i) + { + ulong c = 0, x_i = x[i]; + c += x_i * y_0 + zz[i + 0]; + zz[i + 0] = (uint)c; + c >>= 32; + c += x_i * y_1 + zz[i + 1]; + zz[i + 1] = (uint)c; + c >>= 32; + c += x_i * y_2 + zz[i + 2]; + zz[i + 2] = (uint)c; + c >>= 32; + c += x_i * y_3 + zz[i + 3]; + zz[i + 3] = (uint)c; + c >>= 32; + c += x_i * y_4 + zz[i + 4]; + zz[i + 4] = (uint)c; + c >>= 32; + c += x_i * y_5 + zz[i + 5]; + zz[i + 5] = (uint)c; + c >>= 32; + c += x_i * y_6 + zz[i + 6]; + zz[i + 6] = (uint)c; + c >>= 32; + c += x_i * y_7 + zz[i + 7]; + zz[i + 7] = (uint)c; + c >>= 32; + zz[i + 8] = (uint)c; + } + } + + public static void Mul(uint[] x, int xOff, uint[] y, int yOff, uint[] zz, int zzOff) + { + ulong y_0 = y[yOff + 0]; + ulong y_1 = y[yOff + 1]; + ulong y_2 = y[yOff + 2]; + ulong y_3 = y[yOff + 3]; + ulong y_4 = y[yOff + 4]; + ulong y_5 = y[yOff + 5]; + ulong y_6 = y[yOff + 6]; + ulong y_7 = y[yOff + 7]; + + { + ulong c = 0, x_0 = x[xOff + 0]; + c += x_0 * y_0; + zz[zzOff + 0] = (uint)c; + c >>= 32; + c += x_0 * y_1; + zz[zzOff + 1] = (uint)c; + c >>= 32; + c += x_0 * y_2; + zz[zzOff + 2] = (uint)c; + c >>= 32; + c += x_0 * y_3; + zz[zzOff + 3] = (uint)c; + c >>= 32; + c += x_0 * y_4; + zz[zzOff + 4] = (uint)c; + c >>= 32; + c += x_0 * y_5; + zz[zzOff + 5] = (uint)c; + c >>= 32; + c += x_0 * y_6; + zz[zzOff + 6] = (uint)c; + c >>= 32; + c += x_0 * y_7; + zz[zzOff + 7] = (uint)c; + c >>= 32; + zz[zzOff + 8] = (uint)c; + } + + for (int i = 1; i < 8; ++i) + { + ++zzOff; + ulong c = 0, x_i = x[xOff + i]; + c += x_i * y_0 + zz[zzOff + 0]; + zz[zzOff + 0] = (uint)c; + c >>= 32; + c += x_i * y_1 + zz[zzOff + 1]; + zz[zzOff + 1] = (uint)c; + c >>= 32; + c += x_i * y_2 + zz[zzOff + 2]; + zz[zzOff + 2] = (uint)c; + c >>= 32; + c += x_i * y_3 + zz[zzOff + 3]; + zz[zzOff + 3] = (uint)c; + c >>= 32; + c += x_i * y_4 + zz[zzOff + 4]; + zz[zzOff + 4] = (uint)c; + c >>= 32; + c += x_i * y_5 + zz[zzOff + 5]; + zz[zzOff + 5] = (uint)c; + c >>= 32; + c += x_i * y_6 + zz[zzOff + 6]; + zz[zzOff + 6] = (uint)c; + c >>= 32; + c += x_i * y_7 + zz[zzOff + 7]; + zz[zzOff + 7] = (uint)c; + c >>= 32; + zz[zzOff + 8] = (uint)c; + } + } + + public static uint MulAddTo(uint[] x, uint[] y, uint[] zz) + { + ulong y_0 = y[0]; + ulong y_1 = y[1]; + ulong y_2 = y[2]; + ulong y_3 = y[3]; + ulong y_4 = y[4]; + ulong y_5 = y[5]; + ulong y_6 = y[6]; + ulong y_7 = y[7]; + + ulong zc = 0; + for (int i = 0; i < 8; ++i) + { + ulong c = 0, x_i = x[i]; + c += x_i * y_0 + zz[i + 0]; + zz[i + 0] = (uint)c; + c >>= 32; + c += x_i * y_1 + zz[i + 1]; + zz[i + 1] = (uint)c; + c >>= 32; + c += x_i * y_2 + zz[i + 2]; + zz[i + 2] = (uint)c; + c >>= 32; + c += x_i * y_3 + zz[i + 3]; + zz[i + 3] = (uint)c; + c >>= 32; + c += x_i * y_4 + zz[i + 4]; + zz[i + 4] = (uint)c; + c >>= 32; + c += x_i * y_5 + zz[i + 5]; + zz[i + 5] = (uint)c; + c >>= 32; + c += x_i * y_6 + zz[i + 6]; + zz[i + 6] = (uint)c; + c >>= 32; + c += x_i * y_7 + zz[i + 7]; + zz[i + 7] = (uint)c; + c >>= 32; + c += zc + zz[i + 8]; + zz[i + 8] = (uint)c; + zc = c >> 32; + } + return (uint)zc; + } + + public static uint MulAddTo(uint[] x, int xOff, uint[] y, int yOff, uint[] zz, int zzOff) + { + ulong y_0 = y[yOff + 0]; + ulong y_1 = y[yOff + 1]; + ulong y_2 = y[yOff + 2]; + ulong y_3 = y[yOff + 3]; + ulong y_4 = y[yOff + 4]; + ulong y_5 = y[yOff + 5]; + ulong y_6 = y[yOff + 6]; + ulong y_7 = y[yOff + 7]; + + ulong zc = 0; + for (int i = 0; i < 8; ++i) + { + ulong c = 0, x_i = x[xOff + i]; + c += x_i * y_0 + zz[zzOff + 0]; + zz[zzOff + 0] = (uint)c; + c >>= 32; + c += x_i * y_1 + zz[zzOff + 1]; + zz[zzOff + 1] = (uint)c; + c >>= 32; + c += x_i * y_2 + zz[zzOff + 2]; + zz[zzOff + 2] = (uint)c; + c >>= 32; + c += x_i * y_3 + zz[zzOff + 3]; + zz[zzOff + 3] = (uint)c; + c >>= 32; + c += x_i * y_4 + zz[zzOff + 4]; + zz[zzOff + 4] = (uint)c; + c >>= 32; + c += x_i * y_5 + zz[zzOff + 5]; + zz[zzOff + 5] = (uint)c; + c >>= 32; + c += x_i * y_6 + zz[zzOff + 6]; + zz[zzOff + 6] = (uint)c; + c >>= 32; + c += x_i * y_7 + zz[zzOff + 7]; + zz[zzOff + 7] = (uint)c; + c >>= 32; + c += zc + zz[zzOff + 8]; + zz[zzOff + 8] = (uint)c; + zc = c >> 32; + ++zzOff; + } + return (uint)zc; + } + + public static ulong Mul33Add(uint w, uint[] x, int xOff, uint[] y, int yOff, uint[] z, int zOff) + { + Debug.Assert(w >> 31 == 0); + + ulong c = 0, wVal = w; + ulong x0 = x[xOff + 0]; + c += wVal * x0 + y[yOff + 0]; + z[zOff + 0] = (uint)c; + c >>= 32; + ulong x1 = x[xOff + 1]; + c += wVal * x1 + x0 + y[yOff + 1]; + z[zOff + 1] = (uint)c; + c >>= 32; + ulong x2 = x[xOff + 2]; + c += wVal * x2 + x1 + y[yOff + 2]; + z[zOff + 2] = (uint)c; + c >>= 32; + ulong x3 = x[xOff + 3]; + c += wVal * x3 + x2 + y[yOff + 3]; + z[zOff + 3] = (uint)c; + c >>= 32; + ulong x4 = x[xOff + 4]; + c += wVal * x4 + x3 + y[yOff + 4]; + z[zOff + 4] = (uint)c; + c >>= 32; + ulong x5 = x[xOff + 5]; + c += wVal * x5 + x4 + y[yOff + 5]; + z[zOff + 5] = (uint)c; + c >>= 32; + ulong x6 = x[xOff + 6]; + c += wVal * x6 + x5 + y[yOff + 6]; + z[zOff + 6] = (uint)c; + c >>= 32; + ulong x7 = x[xOff + 7]; + c += wVal * x7 + x6 + y[yOff + 7]; + z[zOff + 7] = (uint)c; + c >>= 32; + c += x7; + return c; + } + + public static uint MulByWord(uint x, uint[] z) + { + ulong c = 0, xVal = x; + c += xVal * (ulong)z[0]; + z[0] = (uint)c; + c >>= 32; + c += xVal * (ulong)z[1]; + z[1] = (uint)c; + c >>= 32; + c += xVal * (ulong)z[2]; + z[2] = (uint)c; + c >>= 32; + c += xVal * (ulong)z[3]; + z[3] = (uint)c; + c >>= 32; + c += xVal * (ulong)z[4]; + z[4] = (uint)c; + c >>= 32; + c += xVal * (ulong)z[5]; + z[5] = (uint)c; + c >>= 32; + c += xVal * (ulong)z[6]; + z[6] = (uint)c; + c >>= 32; + c += xVal * (ulong)z[7]; + z[7] = (uint)c; + c >>= 32; + return (uint)c; + } + + public static uint MulByWordAddTo(uint x, uint[] y, uint[] z) + { + ulong c = 0, xVal = x; + c += xVal * (ulong)z[0] + y[0]; + z[0] = (uint)c; + c >>= 32; + c += xVal * (ulong)z[1] + y[1]; + z[1] = (uint)c; + c >>= 32; + c += xVal * (ulong)z[2] + y[2]; + z[2] = (uint)c; + c >>= 32; + c += xVal * (ulong)z[3] + y[3]; + z[3] = (uint)c; + c >>= 32; + c += xVal * (ulong)z[4] + y[4]; + z[4] = (uint)c; + c >>= 32; + c += xVal * (ulong)z[5] + y[5]; + z[5] = (uint)c; + c >>= 32; + c += xVal * (ulong)z[6] + y[6]; + z[6] = (uint)c; + c >>= 32; + c += xVal * (ulong)z[7] + y[7]; + z[7] = (uint)c; + c >>= 32; + return (uint)c; + } + + public static uint MulWordAddTo(uint x, uint[] y, int yOff, uint[] z, int zOff) + { + ulong c = 0, xVal = x; + c += xVal * y[yOff + 0] + z[zOff + 0]; + z[zOff + 0] = (uint)c; + c >>= 32; + c += xVal * y[yOff + 1] + z[zOff + 1]; + z[zOff + 1] = (uint)c; + c >>= 32; + c += xVal * y[yOff + 2] + z[zOff + 2]; + z[zOff + 2] = (uint)c; + c >>= 32; + c += xVal * y[yOff + 3] + z[zOff + 3]; + z[zOff + 3] = (uint)c; + c >>= 32; + c += xVal * y[yOff + 4] + z[zOff + 4]; + z[zOff + 4] = (uint)c; + c >>= 32; + c += xVal * y[yOff + 5] + z[zOff + 5]; + z[zOff + 5] = (uint)c; + c >>= 32; + c += xVal * y[yOff + 6] + z[zOff + 6]; + z[zOff + 6] = (uint)c; + c >>= 32; + c += xVal * y[yOff + 7] + z[zOff + 7]; + z[zOff + 7] = (uint)c; + c >>= 32; + return (uint)c; + } + + public static uint Mul33DWordAdd(uint x, ulong y, uint[] z, int zOff) + { + Debug.Assert(x >> 31 == 0); + Debug.Assert(zOff <= 4); + ulong c = 0, xVal = x; + ulong y00 = y & M; + c += xVal * y00 + z[zOff + 0]; + z[zOff + 0] = (uint)c; + c >>= 32; + ulong y01 = y >> 32; + c += xVal * y01 + y00 + z[zOff + 1]; + z[zOff + 1] = (uint)c; + c >>= 32; + c += y01 + z[zOff + 2]; + z[zOff + 2] = (uint)c; + c >>= 32; + c += z[zOff + 3]; + z[zOff + 3] = (uint)c; + c >>= 32; + return c == 0 ? 0 : Nat.IncAt(8, z, zOff, 4); + } + + public static uint Mul33WordAdd(uint x, uint y, uint[] z, int zOff) + { + Debug.Assert(x >> 31 == 0); + Debug.Assert(zOff <= 5); + ulong c = 0, yVal = y; + c += yVal * x + z[zOff + 0]; + z[zOff + 0] = (uint)c; + c >>= 32; + c += yVal + z[zOff + 1]; + z[zOff + 1] = (uint)c; + c >>= 32; + c += z[zOff + 2]; + z[zOff + 2] = (uint)c; + c >>= 32; + return c == 0 ? 0 : Nat.IncAt(8, z, zOff, 3); + } + + public static uint MulWordDwordAdd(uint x, ulong y, uint[] z, int zOff) + { + Debug.Assert(zOff <= 5); + ulong c = 0, xVal = x; + c += xVal * y + z[zOff + 0]; + z[zOff + 0] = (uint)c; + c >>= 32; + c += xVal * (y >> 32) + z[zOff + 1]; + z[zOff + 1] = (uint)c; + c >>= 32; + c += z[zOff + 2]; + z[zOff + 2] = (uint)c; + c >>= 32; + return c == 0 ? 0 : Nat.IncAt(8, z, zOff, 3); + } + + public static uint MulWord(uint x, uint[] y, uint[] z, int zOff) + { + ulong c = 0, xVal = x; + int i = 0; + do + { + c += xVal * y[i]; + z[zOff + i] = (uint)c; + c >>= 32; + } + while (++i < 8); + return (uint)c; + } + + public static void Square(uint[] x, uint[] zz) + { + ulong x_0 = x[0]; + ulong zz_1; + + uint c = 0, w; + { + int i = 7, j = 16; + do + { + ulong xVal = x[i--]; + ulong p = xVal * xVal; + zz[--j] = (c << 31) | (uint)(p >> 33); + zz[--j] = (uint)(p >> 1); + c = (uint)p; + } + while (i > 0); + + { + ulong p = x_0 * x_0; + zz_1 = (ulong)(c << 31) | (p >> 33); + zz[0] = (uint)p; + c = (uint)(p >> 32) & 1; + } + } + + ulong x_1 = x[1]; + ulong zz_2 = zz[2]; + + { + zz_1 += x_1 * x_0; + w = (uint)zz_1; + zz[1] = (w << 1) | c; + c = w >> 31; + zz_2 += zz_1 >> 32; + } + + ulong x_2 = x[2]; + ulong zz_3 = zz[3]; + ulong zz_4 = zz[4]; + { + zz_2 += x_2 * x_0; + w = (uint)zz_2; + zz[2] = (w << 1) | c; + c = w >> 31; + zz_3 += (zz_2 >> 32) + x_2 * x_1; + zz_4 += zz_3 >> 32; + zz_3 &= M; + } + + ulong x_3 = x[3]; + ulong zz_5 = zz[5] + (zz_4 >> 32); zz_4 &= M; + ulong zz_6 = zz[6] + (zz_5 >> 32); zz_5 &= M; + { + zz_3 += x_3 * x_0; + w = (uint)zz_3; + zz[3] = (w << 1) | c; + c = w >> 31; + zz_4 += (zz_3 >> 32) + x_3 * x_1; + zz_5 += (zz_4 >> 32) + x_3 * x_2; + zz_4 &= M; + zz_6 += zz_5 >> 32; + zz_5 &= M; + } + + ulong x_4 = x[4]; + ulong zz_7 = zz[7] + (zz_6 >> 32); zz_6 &= M; + ulong zz_8 = zz[8] + (zz_7 >> 32); zz_7 &= M; + { + zz_4 += x_4 * x_0; + w = (uint)zz_4; + zz[4] = (w << 1) | c; + c = w >> 31; + zz_5 += (zz_4 >> 32) + x_4 * x_1; + zz_6 += (zz_5 >> 32) + x_4 * x_2; + zz_5 &= M; + zz_7 += (zz_6 >> 32) + x_4 * x_3; + zz_6 &= M; + zz_8 += zz_7 >> 32; + zz_7 &= M; + } + + ulong x_5 = x[5]; + ulong zz_9 = zz[9] + (zz_8 >> 32); zz_8 &= M; + ulong zz_10 = zz[10] + (zz_9 >> 32); zz_9 &= M; + { + zz_5 += x_5 * x_0; + w = (uint)zz_5; + zz[5] = (w << 1) | c; + c = w >> 31; + zz_6 += (zz_5 >> 32) + x_5 * x_1; + zz_7 += (zz_6 >> 32) + x_5 * x_2; + zz_6 &= M; + zz_8 += (zz_7 >> 32) + x_5 * x_3; + zz_7 &= M; + zz_9 += (zz_8 >> 32) + x_5 * x_4; + zz_8 &= M; + zz_10 += zz_9 >> 32; + zz_9 &= M; + } + + ulong x_6 = x[6]; + ulong zz_11 = zz[11] + (zz_10 >> 32); zz_10 &= M; + ulong zz_12 = zz[12] + (zz_11 >> 32); zz_11 &= M; + { + zz_6 += x_6 * x_0; + w = (uint)zz_6; + zz[6] = (w << 1) | c; + c = w >> 31; + zz_7 += (zz_6 >> 32) + x_6 * x_1; + zz_8 += (zz_7 >> 32) + x_6 * x_2; + zz_7 &= M; + zz_9 += (zz_8 >> 32) + x_6 * x_3; + zz_8 &= M; + zz_10 += (zz_9 >> 32) + x_6 * x_4; + zz_9 &= M; + zz_11 += (zz_10 >> 32) + x_6 * x_5; + zz_10 &= M; + zz_12 += zz_11 >> 32; + zz_11 &= M; + } + + ulong x_7 = x[7]; + ulong zz_13 = zz[13] + (zz_12 >> 32); zz_12 &= M; + ulong zz_14 = zz[14] + (zz_13 >> 32); zz_13 &= M; + { + zz_7 += x_7 * x_0; + w = (uint)zz_7; + zz[7] = (w << 1) | c; + c = w >> 31; + zz_8 += (zz_7 >> 32) + x_7 * x_1; + zz_9 += (zz_8 >> 32) + x_7 * x_2; + zz_10 += (zz_9 >> 32) + x_7 * x_3; + zz_11 += (zz_10 >> 32) + x_7 * x_4; + zz_12 += (zz_11 >> 32) + x_7 * x_5; + zz_13 += (zz_12 >> 32) + x_7 * x_6; + zz_14 += zz_13 >> 32; + } + + w = (uint)zz_8; + zz[8] = (w << 1) | c; + c = w >> 31; + w = (uint)zz_9; + zz[9] = (w << 1) | c; + c = w >> 31; + w = (uint)zz_10; + zz[10] = (w << 1) | c; + c = w >> 31; + w = (uint)zz_11; + zz[11] = (w << 1) | c; + c = w >> 31; + w = (uint)zz_12; + zz[12] = (w << 1) | c; + c = w >> 31; + w = (uint)zz_13; + zz[13] = (w << 1) | c; + c = w >> 31; + w = (uint)zz_14; + zz[14] = (w << 1) | c; + c = w >> 31; + w = zz[15] + (uint)(zz_14 >> 32); + zz[15] = (w << 1) | c; + } + + public static void Square(uint[] x, int xOff, uint[] zz, int zzOff) + { + ulong x_0 = x[xOff + 0]; + ulong zz_1; + + uint c = 0, w; + { + int i = 7, j = 16; + do + { + ulong xVal = x[xOff + i--]; + ulong p = xVal * xVal; + zz[zzOff + --j] = (c << 31) | (uint)(p >> 33); + zz[zzOff + --j] = (uint)(p >> 1); + c = (uint)p; + } + while (i > 0); + + { + ulong p = x_0 * x_0; + zz_1 = (ulong)(c << 31) | (p >> 33); + zz[zzOff + 0] = (uint)p; + c = (uint)(p >> 32) & 1; + } + } + + ulong x_1 = x[xOff + 1]; + ulong zz_2 = zz[zzOff + 2]; + + { + zz_1 += x_1 * x_0; + w = (uint)zz_1; + zz[zzOff + 1] = (w << 1) | c; + c = w >> 31; + zz_2 += zz_1 >> 32; + } + + ulong x_2 = x[xOff + 2]; + ulong zz_3 = zz[zzOff + 3]; + ulong zz_4 = zz[zzOff + 4]; + { + zz_2 += x_2 * x_0; + w = (uint)zz_2; + zz[zzOff + 2] = (w << 1) | c; + c = w >> 31; + zz_3 += (zz_2 >> 32) + x_2 * x_1; + zz_4 += zz_3 >> 32; + zz_3 &= M; + } + + ulong x_3 = x[xOff + 3]; + ulong zz_5 = zz[zzOff + 5] + (zz_4 >> 32); zz_4 &= M; + ulong zz_6 = zz[zzOff + 6] + (zz_5 >> 32); zz_5 &= M; + { + zz_3 += x_3 * x_0; + w = (uint)zz_3; + zz[zzOff + 3] = (w << 1) | c; + c = w >> 31; + zz_4 += (zz_3 >> 32) + x_3 * x_1; + zz_5 += (zz_4 >> 32) + x_3 * x_2; + zz_4 &= M; + zz_6 += zz_5 >> 32; + zz_5 &= M; + } + + ulong x_4 = x[xOff + 4]; + ulong zz_7 = zz[zzOff + 7] + (zz_6 >> 32); zz_6 &= M; + ulong zz_8 = zz[zzOff + 8] + (zz_7 >> 32); zz_7 &= M; + { + zz_4 += x_4 * x_0; + w = (uint)zz_4; + zz[zzOff + 4] = (w << 1) | c; + c = w >> 31; + zz_5 += (zz_4 >> 32) + x_4 * x_1; + zz_6 += (zz_5 >> 32) + x_4 * x_2; + zz_5 &= M; + zz_7 += (zz_6 >> 32) + x_4 * x_3; + zz_6 &= M; + zz_8 += zz_7 >> 32; + zz_7 &= M; + } + + ulong x_5 = x[xOff + 5]; + ulong zz_9 = zz[zzOff + 9] + (zz_8 >> 32); zz_8 &= M; + ulong zz_10 = zz[zzOff + 10] + (zz_9 >> 32); zz_9 &= M; + { + zz_5 += x_5 * x_0; + w = (uint)zz_5; + zz[zzOff + 5] = (w << 1) | c; + c = w >> 31; + zz_6 += (zz_5 >> 32) + x_5 * x_1; + zz_7 += (zz_6 >> 32) + x_5 * x_2; + zz_6 &= M; + zz_8 += (zz_7 >> 32) + x_5 * x_3; + zz_7 &= M; + zz_9 += (zz_8 >> 32) + x_5 * x_4; + zz_8 &= M; + zz_10 += zz_9 >> 32; + zz_9 &= M; + } + + ulong x_6 = x[xOff + 6]; + ulong zz_11 = zz[zzOff + 11] + (zz_10 >> 32); zz_10 &= M; + ulong zz_12 = zz[zzOff + 12] + (zz_11 >> 32); zz_11 &= M; + { + zz_6 += x_6 * x_0; + w = (uint)zz_6; + zz[zzOff + 6] = (w << 1) | c; + c = w >> 31; + zz_7 += (zz_6 >> 32) + x_6 * x_1; + zz_8 += (zz_7 >> 32) + x_6 * x_2; + zz_7 &= M; + zz_9 += (zz_8 >> 32) + x_6 * x_3; + zz_8 &= M; + zz_10 += (zz_9 >> 32) + x_6 * x_4; + zz_9 &= M; + zz_11 += (zz_10 >> 32) + x_6 * x_5; + zz_10 &= M; + zz_12 += zz_11 >> 32; + zz_11 &= M; + } + + ulong x_7 = x[xOff + 7]; + ulong zz_13 = zz[zzOff + 13] + (zz_12 >> 32); zz_12 &= M; + ulong zz_14 = zz[zzOff + 14] + (zz_13 >> 32); zz_13 &= M; + { + zz_7 += x_7 * x_0; + w = (uint)zz_7; + zz[zzOff + 7] = (w << 1) | c; + c = w >> 31; + zz_8 += (zz_7 >> 32) + x_7 * x_1; + zz_9 += (zz_8 >> 32) + x_7 * x_2; + zz_10 += (zz_9 >> 32) + x_7 * x_3; + zz_11 += (zz_10 >> 32) + x_7 * x_4; + zz_12 += (zz_11 >> 32) + x_7 * x_5; + zz_13 += (zz_12 >> 32) + x_7 * x_6; + zz_14 += zz_13 >> 32; + } + + w = (uint)zz_8; + zz[zzOff + 8] = (w << 1) | c; + c = w >> 31; + w = (uint)zz_9; + zz[zzOff + 9] = (w << 1) | c; + c = w >> 31; + w = (uint)zz_10; + zz[zzOff + 10] = (w << 1) | c; + c = w >> 31; + w = (uint)zz_11; + zz[zzOff + 11] = (w << 1) | c; + c = w >> 31; + w = (uint)zz_12; + zz[zzOff + 12] = (w << 1) | c; + c = w >> 31; + w = (uint)zz_13; + zz[zzOff + 13] = (w << 1) | c; + c = w >> 31; + w = (uint)zz_14; + zz[zzOff + 14] = (w << 1) | c; + c = w >> 31; + w = zz[zzOff + 15] + (uint)(zz_14 >> 32); + zz[zzOff + 15] = (w << 1) | c; + } + + public static int Sub(uint[] x, uint[] y, uint[] z) + { + long c = 0; + c += (long)x[0] - y[0]; + z[0] = (uint)c; + c >>= 32; + c += (long)x[1] - y[1]; + z[1] = (uint)c; + c >>= 32; + c += (long)x[2] - y[2]; + z[2] = (uint)c; + c >>= 32; + c += (long)x[3] - y[3]; + z[3] = (uint)c; + c >>= 32; + c += (long)x[4] - y[4]; + z[4] = (uint)c; + c >>= 32; + c += (long)x[5] - y[5]; + z[5] = (uint)c; + c >>= 32; + c += (long)x[6] - y[6]; + z[6] = (uint)c; + c >>= 32; + c += (long)x[7] - y[7]; + z[7] = (uint)c; + c >>= 32; + return (int)c; + } + + public static int Sub(uint[] x, int xOff, uint[] y, int yOff, uint[] z, int zOff) + { + long c = 0; + c += (long)x[xOff + 0] - y[yOff + 0]; + z[zOff + 0] = (uint)c; + c >>= 32; + c += (long)x[xOff + 1] - y[yOff + 1]; + z[zOff + 1] = (uint)c; + c >>= 32; + c += (long)x[xOff + 2] - y[yOff + 2]; + z[zOff + 2] = (uint)c; + c >>= 32; + c += (long)x[xOff + 3] - y[yOff + 3]; + z[zOff + 3] = (uint)c; + c >>= 32; + c += (long)x[xOff + 4] - y[yOff + 4]; + z[zOff + 4] = (uint)c; + c >>= 32; + c += (long)x[xOff + 5] - y[yOff + 5]; + z[zOff + 5] = (uint)c; + c >>= 32; + c += (long)x[xOff + 6] - y[yOff + 6]; + z[zOff + 6] = (uint)c; + c >>= 32; + c += (long)x[xOff + 7] - y[yOff + 7]; + z[zOff + 7] = (uint)c; + c >>= 32; + return (int)c; + } + + public static int SubBothFrom(uint[] x, uint[] y, uint[] z) + { + long c = 0; + c += (long)z[0] - x[0] - y[0]; + z[0] = (uint)c; + c >>= 32; + c += (long)z[1] - x[1] - y[1]; + z[1] = (uint)c; + c >>= 32; + c += (long)z[2] - x[2] - y[2]; + z[2] = (uint)c; + c >>= 32; + c += (long)z[3] - x[3] - y[3]; + z[3] = (uint)c; + c >>= 32; + c += (long)z[4] - x[4] - y[4]; + z[4] = (uint)c; + c >>= 32; + c += (long)z[5] - x[5] - y[5]; + z[5] = (uint)c; + c >>= 32; + c += (long)z[6] - x[6] - y[6]; + z[6] = (uint)c; + c >>= 32; + c += (long)z[7] - x[7] - y[7]; + z[7] = (uint)c; + c >>= 32; + return (int)c; + } + + public static int SubFrom(uint[] x, uint[] z) + { + long c = 0; + c += (long)z[0] - x[0]; + z[0] = (uint)c; + c >>= 32; + c += (long)z[1] - x[1]; + z[1] = (uint)c; + c >>= 32; + c += (long)z[2] - x[2]; + z[2] = (uint)c; + c >>= 32; + c += (long)z[3] - x[3]; + z[3] = (uint)c; + c >>= 32; + c += (long)z[4] - x[4]; + z[4] = (uint)c; + c >>= 32; + c += (long)z[5] - x[5]; + z[5] = (uint)c; + c >>= 32; + c += (long)z[6] - x[6]; + z[6] = (uint)c; + c >>= 32; + c += (long)z[7] - x[7]; + z[7] = (uint)c; + c >>= 32; + return (int)c; + } + + public static int SubFrom(uint[] x, int xOff, uint[] z, int zOff) + { + long c = 0; + c += (long)z[zOff + 0] - x[xOff + 0]; + z[zOff + 0] = (uint)c; + c >>= 32; + c += (long)z[zOff + 1] - x[xOff + 1]; + z[zOff + 1] = (uint)c; + c >>= 32; + c += (long)z[zOff + 2] - x[xOff + 2]; + z[zOff + 2] = (uint)c; + c >>= 32; + c += (long)z[zOff + 3] - x[xOff + 3]; + z[zOff + 3] = (uint)c; + c >>= 32; + c += (long)z[zOff + 4] - x[xOff + 4]; + z[zOff + 4] = (uint)c; + c >>= 32; + c += (long)z[zOff + 5] - x[xOff + 5]; + z[zOff + 5] = (uint)c; + c >>= 32; + c += (long)z[zOff + 6] - x[xOff + 6]; + z[zOff + 6] = (uint)c; + c >>= 32; + c += (long)z[zOff + 7] - x[xOff + 7]; + z[zOff + 7] = (uint)c; + c >>= 32; + return (int)c; + } + + public static BigInteger ToBigInteger(uint[] x) + { + byte[] bs = new byte[32]; + for (int i = 0; i < 8; ++i) + { + uint x_i = x[i]; + if (x_i != 0) + { + Pack.UInt32_To_BE(x_i, bs, (7 - i) << 2); + } + } + return new BigInteger(1, bs); + } + + public static BigInteger ToBigInteger64(ulong[] x) + { + byte[] bs = new byte[32]; + for (int i = 0; i < 4; ++i) + { + ulong x_i = x[i]; + if (x_i != 0L) + { + Pack.UInt64_To_BE(x_i, bs, (3 - i) << 3); + } + } + return new BigInteger(1, bs); + } + + public static void Zero(uint[] z) + { + z[0] = 0; + z[1] = 0; + z[2] = 0; + z[3] = 0; + z[4] = 0; + z[5] = 0; + z[6] = 0; + z[7] = 0; + } + } +} diff --git a/bc-sharp-crypto/src/math/raw/Nat320.cs b/bc-sharp-crypto/src/math/raw/Nat320.cs new file mode 100644 index 0000000..c7daa71 --- /dev/null +++ b/bc-sharp-crypto/src/math/raw/Nat320.cs @@ -0,0 +1,98 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Crypto.Utilities; + +namespace Org.BouncyCastle.Math.Raw +{ + internal abstract class Nat320 + { + public static void Copy64(ulong[] x, ulong[] z) + { + z[0] = x[0]; + z[1] = x[1]; + z[2] = x[2]; + z[3] = x[3]; + z[4] = x[4]; + } + + public static ulong[] Create64() + { + return new ulong[5]; + } + + public static ulong[] CreateExt64() + { + return new ulong[10]; + } + + public static bool Eq64(ulong[] x, ulong[] y) + { + for (int i = 4; i >= 0; --i) + { + if (x[i] != y[i]) + { + return false; + } + } + return true; + } + + public static ulong[] FromBigInteger64(BigInteger x) + { + if (x.SignValue < 0 || x.BitLength > 320) + throw new ArgumentException(); + + ulong[] z = Create64(); + int i = 0; + while (x.SignValue != 0) + { + z[i++] = (ulong)x.LongValue; + x = x.ShiftRight(64); + } + return z; + } + + public static bool IsOne64(ulong[] x) + { + if (x[0] != 1UL) + { + return false; + } + for (int i = 1; i < 5; ++i) + { + if (x[i] != 0UL) + { + return false; + } + } + return true; + } + + public static bool IsZero64(ulong[] x) + { + for (int i = 0; i < 5; ++i) + { + if (x[i] != 0UL) + { + return false; + } + } + return true; + } + + public static BigInteger ToBigInteger64(ulong[] x) + { + byte[] bs = new byte[40]; + for (int i = 0; i < 5; ++i) + { + ulong x_i = x[i]; + if (x_i != 0L) + { + Pack.UInt64_To_BE(x_i, bs, (4 - i) << 3); + } + } + return new BigInteger(1, bs); + } + } +} diff --git a/bc-sharp-crypto/src/math/raw/Nat384.cs b/bc-sharp-crypto/src/math/raw/Nat384.cs new file mode 100644 index 0000000..ed1c47e --- /dev/null +++ b/bc-sharp-crypto/src/math/raw/Nat384.cs @@ -0,0 +1,46 @@ +using System; +using System.Diagnostics; + +namespace Org.BouncyCastle.Math.Raw +{ + internal abstract class Nat384 + { + public static void Mul(uint[] x, uint[] y, uint[] zz) + { + Nat192.Mul(x, y, zz); + Nat192.Mul(x, 6, y, 6, zz, 12); + + uint c18 = Nat192.AddToEachOther(zz, 6, zz, 12); + uint c12 = c18 + Nat192.AddTo(zz, 0, zz, 6, 0); + c18 += Nat192.AddTo(zz, 18, zz, 12, c12); + + uint[] dx = Nat192.Create(), dy = Nat192.Create(); + bool neg = Nat192.Diff(x, 6, x, 0, dx, 0) != Nat192.Diff(y, 6, y, 0, dy, 0); + + uint[] tt = Nat192.CreateExt(); + Nat192.Mul(dx, dy, tt); + + c18 += neg ? Nat.AddTo(12, tt, 0, zz, 6) : (uint)Nat.SubFrom(12, tt, 0, zz, 6); + Nat.AddWordAt(24, c18, zz, 18); + } + + public static void Square(uint[] x, uint[] zz) + { + Nat192.Square(x, zz); + Nat192.Square(x, 6, zz, 12); + + uint c18 = Nat192.AddToEachOther(zz, 6, zz, 12); + uint c12 = c18 + Nat192.AddTo(zz, 0, zz, 6, 0); + c18 += Nat192.AddTo(zz, 18, zz, 12, c12); + + uint[] dx = Nat192.Create(); + Nat192.Diff(x, 6, x, 0, dx, 0); + + uint[] m = Nat192.CreateExt(); + Nat192.Square(dx, m); + + c18 += (uint)Nat.SubFrom(12, m, 0, zz, 6); + Nat.AddWordAt(24, c18, zz, 18); + } + } +} diff --git a/bc-sharp-crypto/src/math/raw/Nat448.cs b/bc-sharp-crypto/src/math/raw/Nat448.cs new file mode 100644 index 0000000..52a253f --- /dev/null +++ b/bc-sharp-crypto/src/math/raw/Nat448.cs @@ -0,0 +1,100 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Crypto.Utilities; + +namespace Org.BouncyCastle.Math.Raw +{ + internal abstract class Nat448 + { + public static void Copy64(ulong[] x, ulong[] z) + { + z[0] = x[0]; + z[1] = x[1]; + z[2] = x[2]; + z[3] = x[3]; + z[4] = x[4]; + z[5] = x[5]; + z[6] = x[6]; + } + + public static ulong[] Create64() + { + return new ulong[7]; + } + + public static ulong[] CreateExt64() + { + return new ulong[14]; + } + + public static bool Eq64(ulong[] x, ulong[] y) + { + for (int i = 6; i >= 0; --i) + { + if (x[i] != y[i]) + { + return false; + } + } + return true; + } + + public static ulong[] FromBigInteger64(BigInteger x) + { + if (x.SignValue < 0 || x.BitLength > 448) + throw new ArgumentException(); + + ulong[] z = Create64(); + int i = 0; + while (x.SignValue != 0) + { + z[i++] = (ulong)x.LongValue; + x = x.ShiftRight(64); + } + return z; + } + + public static bool IsOne64(ulong[] x) + { + if (x[0] != 1UL) + { + return false; + } + for (int i = 1; i < 7; ++i) + { + if (x[i] != 0UL) + { + return false; + } + } + return true; + } + + public static bool IsZero64(ulong[] x) + { + for (int i = 0; i < 7; ++i) + { + if (x[i] != 0UL) + { + return false; + } + } + return true; + } + + public static BigInteger ToBigInteger64(ulong[] x) + { + byte[] bs = new byte[56]; + for (int i = 0; i < 7; ++i) + { + ulong x_i = x[i]; + if (x_i != 0L) + { + Pack.UInt64_To_BE(x_i, bs, (6 - i) << 3); + } + } + return new BigInteger(1, bs); + } + } +} diff --git a/bc-sharp-crypto/src/math/raw/Nat512.cs b/bc-sharp-crypto/src/math/raw/Nat512.cs new file mode 100644 index 0000000..a9ef2b3 --- /dev/null +++ b/bc-sharp-crypto/src/math/raw/Nat512.cs @@ -0,0 +1,46 @@ +using System; +using System.Diagnostics; + +namespace Org.BouncyCastle.Math.Raw +{ + internal abstract class Nat512 + { + public static void Mul(uint[] x, uint[] y, uint[] zz) + { + Nat256.Mul(x, y, zz); + Nat256.Mul(x, 8, y, 8, zz, 16); + + uint c24 = Nat256.AddToEachOther(zz, 8, zz, 16); + uint c16 = c24 + Nat256.AddTo(zz, 0, zz, 8, 0); + c24 += Nat256.AddTo(zz, 24, zz, 16, c16); + + uint[] dx = Nat256.Create(), dy = Nat256.Create(); + bool neg = Nat256.Diff(x, 8, x, 0, dx, 0) != Nat256.Diff(y, 8, y, 0, dy, 0); + + uint[] tt = Nat256.CreateExt(); + Nat256.Mul(dx, dy, tt); + + c24 += neg ? Nat.AddTo(16, tt, 0, zz, 8) : (uint)Nat.SubFrom(16, tt, 0, zz, 8); + Nat.AddWordAt(32, c24, zz, 24); + } + + public static void Square(uint[] x, uint[] zz) + { + Nat256.Square(x, zz); + Nat256.Square(x, 8, zz, 16); + + uint c24 = Nat256.AddToEachOther(zz, 8, zz, 16); + uint c16 = c24 + Nat256.AddTo(zz, 0, zz, 8, 0); + c24 += Nat256.AddTo(zz, 24, zz, 16, c16); + + uint[] dx = Nat256.Create(); + Nat256.Diff(x, 8, x, 0, dx, 0); + + uint[] m = Nat256.CreateExt(); + Nat256.Square(dx, m); + + c24 += (uint)Nat.SubFrom(16, m, 0, zz, 8); + Nat.AddWordAt(32, c24, zz, 24); + } + } +} diff --git a/bc-sharp-crypto/src/math/raw/Nat576.cs b/bc-sharp-crypto/src/math/raw/Nat576.cs new file mode 100644 index 0000000..813fb86 --- /dev/null +++ b/bc-sharp-crypto/src/math/raw/Nat576.cs @@ -0,0 +1,102 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Crypto.Utilities; + +namespace Org.BouncyCastle.Math.Raw +{ + internal abstract class Nat576 + { + public static void Copy64(ulong[] x, ulong[] z) + { + z[0] = x[0]; + z[1] = x[1]; + z[2] = x[2]; + z[3] = x[3]; + z[4] = x[4]; + z[5] = x[5]; + z[6] = x[6]; + z[7] = x[7]; + z[8] = x[8]; + } + + public static ulong[] Create64() + { + return new ulong[9]; + } + + public static ulong[] CreateExt64() + { + return new ulong[18]; + } + + public static bool Eq64(ulong[] x, ulong[] y) + { + for (int i = 8; i >= 0; --i) + { + if (x[i] != y[i]) + { + return false; + } + } + return true; + } + + public static ulong[] FromBigInteger64(BigInteger x) + { + if (x.SignValue < 0 || x.BitLength > 576) + throw new ArgumentException(); + + ulong[] z = Create64(); + int i = 0; + while (x.SignValue != 0) + { + z[i++] = (ulong)x.LongValue; + x = x.ShiftRight(64); + } + return z; + } + + public static bool IsOne64(ulong[] x) + { + if (x[0] != 1UL) + { + return false; + } + for (int i = 1; i < 9; ++i) + { + if (x[i] != 0UL) + { + return false; + } + } + return true; + } + + public static bool IsZero64(ulong[] x) + { + for (int i = 0; i < 9; ++i) + { + if (x[i] != 0UL) + { + return false; + } + } + return true; + } + + public static BigInteger ToBigInteger64(ulong[] x) + { + byte[] bs = new byte[72]; + for (int i = 0; i < 9; ++i) + { + ulong x_i = x[i]; + if (x_i != 0L) + { + Pack.UInt64_To_BE(x_i, bs, (8 - i) << 3); + } + } + return new BigInteger(1, bs); + } + } +} diff --git a/bc-sharp-crypto/src/ocsp/BasicOCSPResp.cs b/bc-sharp-crypto/src/ocsp/BasicOCSPResp.cs new file mode 100644 index 0000000..63ab892 --- /dev/null +++ b/bc-sharp-crypto/src/ocsp/BasicOCSPResp.cs @@ -0,0 +1,220 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Ocsp; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Security.Certificates; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.X509; +using Org.BouncyCastle.X509.Store; + +namespace Org.BouncyCastle.Ocsp +{ + /// + /// + /// BasicOcspResponse ::= SEQUENCE { + /// tbsResponseData ResponseData, + /// signatureAlgorithm AlgorithmIdentifier, + /// signature BIT STRING, + /// certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL + /// } + /// + /// + public class BasicOcspResp + : X509ExtensionBase + { + private readonly BasicOcspResponse resp; + private readonly ResponseData data; +// private readonly X509Certificate[] chain; + + public BasicOcspResp( + BasicOcspResponse resp) + { + this.resp = resp; + this.data = resp.TbsResponseData; + } + + /// The DER encoding of the tbsResponseData field. + /// In the event of an encoding error. + public byte[] GetTbsResponseData() + { + try + { + return data.GetDerEncoded(); + } + catch (IOException e) + { + throw new OcspException("problem encoding tbsResponseData", e); + } + } + + public int Version + { + get { return data.Version.Value.IntValue + 1; } + } + + public RespID ResponderId + { + get { return new RespID(data.ResponderID); } + } + + public DateTime ProducedAt + { + get { return data.ProducedAt.ToDateTime(); } + } + + public SingleResp[] Responses + { + get + { + Asn1Sequence s = data.Responses; + SingleResp[] rs = new SingleResp[s.Count]; + + for (int i = 0; i != rs.Length; i++) + { + rs[i] = new SingleResp(SingleResponse.GetInstance(s[i])); + } + + return rs; + } + } + + public X509Extensions ResponseExtensions + { + get { return data.ResponseExtensions; } + } + + protected override X509Extensions GetX509Extensions() + { + return ResponseExtensions; + } + + public string SignatureAlgName + { + get { return OcspUtilities.GetAlgorithmName(resp.SignatureAlgorithm.Algorithm); } + } + + public string SignatureAlgOid + { + get { return resp.SignatureAlgorithm.Algorithm.Id; } + } + + [Obsolete("RespData class is no longer required as all functionality is available on this class")] + public RespData GetResponseData() + { + return new RespData(data); + } + + public byte[] GetSignature() + { + return resp.GetSignatureOctets(); + } + + private IList GetCertList() + { + // load the certificates and revocation lists if we have any + + IList certs = Platform.CreateArrayList(); + Asn1Sequence s = resp.Certs; + + if (s != null) + { + foreach (Asn1Encodable ae in s) + { + try + { + certs.Add(new X509CertificateParser().ReadCertificate(ae.GetEncoded())); + } + catch (IOException ex) + { + throw new OcspException("can't re-encode certificate!", ex); + } + catch (CertificateException ex) + { + throw new OcspException("can't re-encode certificate!", ex); + } + } + } + + return certs; + } + + public X509Certificate[] GetCerts() + { + IList certs = GetCertList(); + X509Certificate[] result = new X509Certificate[certs.Count]; + for (int i = 0; i < certs.Count; ++i) + { + result[i] = (X509Certificate)certs[i]; + } + return result; + } + + /// The certificates, if any, associated with the response. + /// In the event of an encoding error. + public IX509Store GetCertificates( + string type) + { + try + { + return X509StoreFactory.Create( + "Certificate/" + type, + new X509CollectionStoreParameters(this.GetCertList())); + } + catch (Exception e) + { + throw new OcspException("can't setup the CertStore", e); + } + } + + /// + /// Verify the signature against the tbsResponseData object we contain. + /// + public bool Verify( + AsymmetricKeyParameter publicKey) + { + try + { + ISigner signature = SignerUtilities.GetSigner(this.SignatureAlgName); + signature.Init(false, publicKey); + byte[] bs = data.GetDerEncoded(); + signature.BlockUpdate(bs, 0, bs.Length); + + return signature.VerifySignature(this.GetSignature()); + } + catch (Exception e) + { + throw new OcspException("exception processing sig: " + e, e); + } + } + + /// The ASN.1 encoded representation of this object. + public byte[] GetEncoded() + { + return resp.GetEncoded(); + } + + public override bool Equals( + object obj) + { + if (obj == this) + return true; + + BasicOcspResp other = obj as BasicOcspResp; + + if (other == null) + return false; + + return resp.Equals(other.resp); + } + + public override int GetHashCode() + { + return resp.GetHashCode(); + } + } +} diff --git a/bc-sharp-crypto/src/ocsp/BasicOCSPRespGenerator.cs b/bc-sharp-crypto/src/ocsp/BasicOCSPRespGenerator.cs new file mode 100644 index 0000000..0dd4e0a --- /dev/null +++ b/bc-sharp-crypto/src/ocsp/BasicOCSPRespGenerator.cs @@ -0,0 +1,313 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Ocsp; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Security.Certificates; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.X509; +using Org.BouncyCastle.Crypto.Operators; + +namespace Org.BouncyCastle.Ocsp +{ + /** + * Generator for basic OCSP response objects. + */ + public class BasicOcspRespGenerator + { + private readonly IList list = Platform.CreateArrayList(); + + private X509Extensions responseExtensions; + private RespID responderID; + + private class ResponseObject + { + internal CertificateID certId; + internal CertStatus certStatus; + internal DerGeneralizedTime thisUpdate; + internal DerGeneralizedTime nextUpdate; + internal X509Extensions extensions; + + public ResponseObject( + CertificateID certId, + CertificateStatus certStatus, + DateTime thisUpdate, + X509Extensions extensions) + : this(certId, certStatus, new DerGeneralizedTime(thisUpdate), null, extensions) + { + } + + public ResponseObject( + CertificateID certId, + CertificateStatus certStatus, + DateTime thisUpdate, + DateTime nextUpdate, + X509Extensions extensions) + : this(certId, certStatus, new DerGeneralizedTime(thisUpdate), new DerGeneralizedTime(nextUpdate), extensions) + { + } + + private ResponseObject( + CertificateID certId, + CertificateStatus certStatus, + DerGeneralizedTime thisUpdate, + DerGeneralizedTime nextUpdate, + X509Extensions extensions) + { + this.certId = certId; + + if (certStatus == null) + { + this.certStatus = new CertStatus(); + } + else if (certStatus is UnknownStatus) + { + this.certStatus = new CertStatus(2, DerNull.Instance); + } + else + { + RevokedStatus rs = (RevokedStatus) certStatus; + CrlReason revocationReason = rs.HasRevocationReason + ? new CrlReason(rs.RevocationReason) + : null; + + this.certStatus = new CertStatus( + new RevokedInfo(new DerGeneralizedTime(rs.RevocationTime), revocationReason)); + } + + this.thisUpdate = thisUpdate; + this.nextUpdate = nextUpdate; + + this.extensions = extensions; + } + + public SingleResponse ToResponse() + { + return new SingleResponse(certId.ToAsn1Object(), certStatus, thisUpdate, nextUpdate, extensions); + } + } + + /** + * basic constructor + */ + public BasicOcspRespGenerator( + RespID responderID) + { + this.responderID = responderID; + } + + /** + * construct with the responderID to be the SHA-1 keyHash of the passed in public key. + */ + public BasicOcspRespGenerator( + AsymmetricKeyParameter publicKey) + { + this.responderID = new RespID(publicKey); + } + + /** + * Add a response for a particular Certificate ID. + * + * @param certID certificate ID details + * @param certStatus status of the certificate - null if okay + */ + public void AddResponse( + CertificateID certID, + CertificateStatus certStatus) + { + list.Add(new ResponseObject(certID, certStatus, DateTime.UtcNow, null)); + } + + /** + * Add a response for a particular Certificate ID. + * + * @param certID certificate ID details + * @param certStatus status of the certificate - null if okay + * @param singleExtensions optional extensions + */ + public void AddResponse( + CertificateID certID, + CertificateStatus certStatus, + X509Extensions singleExtensions) + { + list.Add(new ResponseObject(certID, certStatus, DateTime.UtcNow, singleExtensions)); + } + + /** + * Add a response for a particular Certificate ID. + * + * @param certID certificate ID details + * @param nextUpdate date when next update should be requested + * @param certStatus status of the certificate - null if okay + * @param singleExtensions optional extensions + */ + public void AddResponse( + CertificateID certID, + CertificateStatus certStatus, + DateTime nextUpdate, + X509Extensions singleExtensions) + { + list.Add(new ResponseObject(certID, certStatus, DateTime.UtcNow, nextUpdate, singleExtensions)); + } + + /** + * Add a response for a particular Certificate ID. + * + * @param certID certificate ID details + * @param thisUpdate date this response was valid on + * @param nextUpdate date when next update should be requested + * @param certStatus status of the certificate - null if okay + * @param singleExtensions optional extensions + */ + public void AddResponse( + CertificateID certID, + CertificateStatus certStatus, + DateTime thisUpdate, + DateTime nextUpdate, + X509Extensions singleExtensions) + { + list.Add(new ResponseObject(certID, certStatus, thisUpdate, nextUpdate, singleExtensions)); + } + + /** + * Set the extensions for the response. + * + * @param responseExtensions the extension object to carry. + */ + public void SetResponseExtensions( + X509Extensions responseExtensions) + { + this.responseExtensions = responseExtensions; + } + + private BasicOcspResp GenerateResponse( + ISignatureFactory signatureCalculator, + X509Certificate[] chain, + DateTime producedAt) + { + AlgorithmIdentifier signingAlgID = (AlgorithmIdentifier)signatureCalculator.AlgorithmDetails; + DerObjectIdentifier signingAlgorithm = signingAlgID.Algorithm; + + Asn1EncodableVector responses = new Asn1EncodableVector(); + + foreach (ResponseObject respObj in list) + { + try + { + responses.Add(respObj.ToResponse()); + } + catch (Exception e) + { + throw new OcspException("exception creating Request", e); + } + } + + ResponseData tbsResp = new ResponseData(responderID.ToAsn1Object(), new DerGeneralizedTime(producedAt), new DerSequence(responses), responseExtensions); + DerBitString bitSig = null; + + try + { + IStreamCalculator streamCalculator = signatureCalculator.CreateCalculator(); + + byte[] encoded = tbsResp.GetDerEncoded(); + + streamCalculator.Stream.Write(encoded, 0, encoded.Length); + + Platform.Dispose(streamCalculator.Stream); + + bitSig = new DerBitString(((IBlockResult)streamCalculator.GetResult()).Collect()); + } + catch (Exception e) + { + throw new OcspException("exception processing TBSRequest: " + e, e); + } + + AlgorithmIdentifier sigAlgId = OcspUtilities.GetSigAlgID(signingAlgorithm); + + DerSequence chainSeq = null; + if (chain != null && chain.Length > 0) + { + Asn1EncodableVector v = new Asn1EncodableVector(); + try + { + for (int i = 0; i != chain.Length; i++) + { + v.Add( + X509CertificateStructure.GetInstance( + Asn1Object.FromByteArray(chain[i].GetEncoded()))); + } + } + catch (IOException e) + { + throw new OcspException("error processing certs", e); + } + catch (CertificateEncodingException e) + { + throw new OcspException("error encoding certs", e); + } + + chainSeq = new DerSequence(v); + } + + return new BasicOcspResp(new BasicOcspResponse(tbsResp, sigAlgId, bitSig, chainSeq)); + } + + public BasicOcspResp Generate( + string signingAlgorithm, + AsymmetricKeyParameter privateKey, + X509Certificate[] chain, + DateTime thisUpdate) + { + return Generate(signingAlgorithm, privateKey, chain, thisUpdate, null); + } + + public BasicOcspResp Generate( + string signingAlgorithm, + AsymmetricKeyParameter privateKey, + X509Certificate[] chain, + DateTime producedAt, + SecureRandom random) + { + if (signingAlgorithm == null) + { + throw new ArgumentException("no signing algorithm specified"); + } + + return GenerateResponse(new Asn1SignatureFactory(signingAlgorithm, privateKey, random), chain, producedAt); + } + + /// + /// Generate the signed response using the passed in signature calculator. + /// + /// Implementation of signing calculator factory. + /// The certificate chain associated with the response signer. + /// "produced at" date. + /// + public BasicOcspResp Generate( + ISignatureFactory signatureCalculatorFactory, + X509Certificate[] chain, + DateTime producedAt) + { + if (signatureCalculatorFactory == null) + { + throw new ArgumentException("no signature calculator specified"); + } + + return GenerateResponse(signatureCalculatorFactory, chain, producedAt); + } + + /** + * Return an IEnumerable of the signature names supported by the generator. + * + * @return an IEnumerable containing recognised names. + */ + public IEnumerable SignatureAlgNames + { + get { return OcspUtilities.AlgNames; } + } + } +} diff --git a/bc-sharp-crypto/src/ocsp/CertificateID.cs b/bc-sharp-crypto/src/ocsp/CertificateID.cs new file mode 100644 index 0000000..ec902d5 --- /dev/null +++ b/bc-sharp-crypto/src/ocsp/CertificateID.cs @@ -0,0 +1,141 @@ +using System; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Ocsp; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.X509; + +namespace Org.BouncyCastle.Ocsp +{ + public class CertificateID + { + public const string HashSha1 = "1.3.14.3.2.26"; + + private readonly CertID id; + + public CertificateID( + CertID id) + { + if (id == null) + throw new ArgumentNullException("id"); + + this.id = id; + } + + /** + * create from an issuer certificate and the serial number of the + * certificate it signed. + * @exception OcspException if any problems occur creating the id fields. + */ + public CertificateID( + string hashAlgorithm, + X509Certificate issuerCert, + BigInteger serialNumber) + { + AlgorithmIdentifier hashAlg = new AlgorithmIdentifier( + new DerObjectIdentifier(hashAlgorithm), DerNull.Instance); + + this.id = CreateCertID(hashAlg, issuerCert, new DerInteger(serialNumber)); + } + + public string HashAlgOid + { + get { return id.HashAlgorithm.Algorithm.Id; } + } + + public byte[] GetIssuerNameHash() + { + return id.IssuerNameHash.GetOctets(); + } + + public byte[] GetIssuerKeyHash() + { + return id.IssuerKeyHash.GetOctets(); + } + + /** + * return the serial number for the certificate associated + * with this request. + */ + public BigInteger SerialNumber + { + get { return id.SerialNumber.Value; } + } + + public bool MatchesIssuer( + X509Certificate issuerCert) + { + return CreateCertID(id.HashAlgorithm, issuerCert, id.SerialNumber).Equals(id); + } + + public CertID ToAsn1Object() + { + return id; + } + + public override bool Equals( + object obj) + { + if (obj == this) + return true; + + CertificateID other = obj as CertificateID; + + if (other == null) + return false; + + return id.ToAsn1Object().Equals(other.id.ToAsn1Object()); + } + + public override int GetHashCode() + { + return id.ToAsn1Object().GetHashCode(); + } + + + /** + * Create a new CertificateID for a new serial number derived from a previous one + * calculated for the same CA certificate. + * + * @param original the previously calculated CertificateID for the CA. + * @param newSerialNumber the serial number for the new certificate of interest. + * + * @return a new CertificateID for newSerialNumber + */ + public static CertificateID DeriveCertificateID(CertificateID original, BigInteger newSerialNumber) + { + return new CertificateID(new CertID(original.id.HashAlgorithm, original.id.IssuerNameHash, + original.id.IssuerKeyHash, new DerInteger(newSerialNumber))); + } + + private static CertID CreateCertID( + AlgorithmIdentifier hashAlg, + X509Certificate issuerCert, + DerInteger serialNumber) + { + try + { + String hashAlgorithm = hashAlg.Algorithm.Id; + + X509Name issuerName = PrincipalUtilities.GetSubjectX509Principal(issuerCert); + byte[] issuerNameHash = DigestUtilities.CalculateDigest( + hashAlgorithm, issuerName.GetEncoded()); + + AsymmetricKeyParameter issuerKey = issuerCert.GetPublicKey(); + SubjectPublicKeyInfo info = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(issuerKey); + byte[] issuerKeyHash = DigestUtilities.CalculateDigest( + hashAlgorithm, info.PublicKeyData.GetBytes()); + + return new CertID(hashAlg, new DerOctetString(issuerNameHash), + new DerOctetString(issuerKeyHash), serialNumber); + } + catch (Exception e) + { + throw new OcspException("problem creating ID: " + e, e); + } + } + } +} diff --git a/bc-sharp-crypto/src/ocsp/CertificateStatus.cs b/bc-sharp-crypto/src/ocsp/CertificateStatus.cs new file mode 100644 index 0000000..edfcc25 --- /dev/null +++ b/bc-sharp-crypto/src/ocsp/CertificateStatus.cs @@ -0,0 +1,9 @@ +using System; + +namespace Org.BouncyCastle.Ocsp +{ + public abstract class CertificateStatus + { + public static readonly CertificateStatus Good = null; + } +} diff --git a/bc-sharp-crypto/src/ocsp/OCSPException.cs b/bc-sharp-crypto/src/ocsp/OCSPException.cs new file mode 100644 index 0000000..d7b14dd --- /dev/null +++ b/bc-sharp-crypto/src/ocsp/OCSPException.cs @@ -0,0 +1,28 @@ +using System; + +namespace Org.BouncyCastle.Ocsp +{ +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT || PORTABLE) + [Serializable] +#endif + public class OcspException + : Exception + { + public OcspException() + { + } + + public OcspException( + string message) + : base(message) + { + } + + public OcspException( + string message, + Exception e) + : base(message, e) + { + } + } +} diff --git a/bc-sharp-crypto/src/ocsp/OCSPReq.cs b/bc-sharp-crypto/src/ocsp/OCSPReq.cs new file mode 100644 index 0000000..0cd95c6 --- /dev/null +++ b/bc-sharp-crypto/src/ocsp/OCSPReq.cs @@ -0,0 +1,268 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Ocsp; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Security.Certificates; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.X509; +using Org.BouncyCastle.X509.Store; + +namespace Org.BouncyCastle.Ocsp +{ + /** + *
    +	 * OcspRequest     ::=     SEQUENCE {
    +	 *       tbsRequest                  TBSRequest,
    +	 *       optionalSignature   [0]     EXPLICIT Signature OPTIONAL }
    +	 *
    +	 *   TBSRequest      ::=     SEQUENCE {
    +	 *       version             [0]     EXPLICIT Version DEFAULT v1,
    +	 *       requestorName       [1]     EXPLICIT GeneralName OPTIONAL,
    +	 *       requestList                 SEQUENCE OF Request,
    +	 *       requestExtensions   [2]     EXPLICIT Extensions OPTIONAL }
    +	 *
    +	 *   Signature       ::=     SEQUENCE {
    +	 *       signatureAlgorithm      AlgorithmIdentifier,
    +	 *       signature               BIT STRING,
    +	 *       certs               [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL}
    +	 *
    +	 *   Version         ::=             INTEGER  {  v1(0) }
    +	 *
    +	 *   Request         ::=     SEQUENCE {
    +	 *       reqCert                     CertID,
    +	 *       singleRequestExtensions     [0] EXPLICIT Extensions OPTIONAL }
    +	 *
    +	 *   CertID          ::=     SEQUENCE {
    +	 *       hashAlgorithm       AlgorithmIdentifier,
    +	 *       issuerNameHash      OCTET STRING, -- Hash of Issuer's DN
    +	 *       issuerKeyHash       OCTET STRING, -- Hash of Issuers public key
    +	 *       serialNumber        CertificateSerialNumber }
    +	 * 
    + */ + public class OcspReq + : X509ExtensionBase + { + private OcspRequest req; + + public OcspReq( + OcspRequest req) + { + this.req = req; + } + + public OcspReq( + byte[] req) + : this(new Asn1InputStream(req)) + { + } + + public OcspReq( + Stream inStr) + : this(new Asn1InputStream(inStr)) + { + } + + private OcspReq( + Asn1InputStream aIn) + { + try + { + this.req = OcspRequest.GetInstance(aIn.ReadObject()); + } + catch (ArgumentException e) + { + throw new IOException("malformed request: " + e.Message); + } + catch (InvalidCastException e) + { + throw new IOException("malformed request: " + e.Message); + } + } + + /** + * Return the DER encoding of the tbsRequest field. + * @return DER encoding of tbsRequest + * @throws OcspException in the event of an encoding error. + */ + public byte[] GetTbsRequest() + { + try + { + return req.TbsRequest.GetEncoded(); + } + catch (IOException e) + { + throw new OcspException("problem encoding tbsRequest", e); + } + } + + public int Version + { + get { return req.TbsRequest.Version.Value.IntValue + 1; } + } + + public GeneralName RequestorName + { + get { return GeneralName.GetInstance(req.TbsRequest.RequestorName); } + } + + public Req[] GetRequestList() + { + Asn1Sequence seq = req.TbsRequest.RequestList; + Req[] requests = new Req[seq.Count]; + + for (int i = 0; i != requests.Length; i++) + { + requests[i] = new Req(Request.GetInstance(seq[i])); + } + + return requests; + } + + public X509Extensions RequestExtensions + { + get { return X509Extensions.GetInstance(req.TbsRequest.RequestExtensions); } + } + + protected override X509Extensions GetX509Extensions() + { + return RequestExtensions; + } + + /** + * return the object identifier representing the signature algorithm + */ + public string SignatureAlgOid + { + get + { + if (!this.IsSigned) + return null; + + return req.OptionalSignature.SignatureAlgorithm.Algorithm.Id; + } + } + + public byte[] GetSignature() + { + if (!this.IsSigned) + return null; + + return req.OptionalSignature.GetSignatureOctets(); + } + + private IList GetCertList() + { + // load the certificates if we have any + + IList certs = Platform.CreateArrayList(); + Asn1Sequence s = req.OptionalSignature.Certs; + + if (s != null) + { + foreach (Asn1Encodable ae in s) + { + try + { + certs.Add(new X509CertificateParser().ReadCertificate(ae.GetEncoded())); + } + catch (Exception e) + { + throw new OcspException("can't re-encode certificate!", e); + } + } + } + + return certs; + } + + public X509Certificate[] GetCerts() + { + if (!this.IsSigned) + return null; + + IList certs = this.GetCertList(); + X509Certificate[] result = new X509Certificate[certs.Count]; + for (int i = 0; i < certs.Count; ++i) + { + result[i] = (X509Certificate)certs[i]; + } + return result; + } + + /** + * If the request is signed return a possibly empty CertStore containing the certificates in the + * request. If the request is not signed the method returns null. + * + * @return null if not signed, a CertStore otherwise + * @throws OcspException + */ + public IX509Store GetCertificates( + string type) + { + if (!this.IsSigned) + return null; + + try + { + return X509StoreFactory.Create( + "Certificate/" + type, + new X509CollectionStoreParameters(this.GetCertList())); + } + catch (Exception e) + { + throw new OcspException("can't setup the CertStore", e); + } + } + + /** + * Return whether or not this request is signed. + * + * @return true if signed false otherwise. + */ + public bool IsSigned + { + get { return req.OptionalSignature != null; } + } + + /** + * Verify the signature against the TBSRequest object we contain. + */ + public bool Verify( + AsymmetricKeyParameter publicKey) + { + if (!this.IsSigned) + throw new OcspException("attempt to Verify signature on unsigned object"); + + try + { + ISigner signature = SignerUtilities.GetSigner(this.SignatureAlgOid); + + signature.Init(false, publicKey); + + byte[] encoded = req.TbsRequest.GetEncoded(); + + signature.BlockUpdate(encoded, 0, encoded.Length); + + return signature.VerifySignature(this.GetSignature()); + } + catch (Exception e) + { + throw new OcspException("exception processing sig: " + e, e); + } + } + + /** + * return the ASN.1 encoded representation of this object. + */ + public byte[] GetEncoded() + { + return req.GetEncoded(); + } + } +} diff --git a/bc-sharp-crypto/src/ocsp/OCSPReqGenerator.cs b/bc-sharp-crypto/src/ocsp/OCSPReqGenerator.cs new file mode 100644 index 0000000..8032a45 --- /dev/null +++ b/bc-sharp-crypto/src/ocsp/OCSPReqGenerator.cs @@ -0,0 +1,243 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Ocsp; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Security.Certificates; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.X509; + +namespace Org.BouncyCastle.Ocsp +{ + public class OcspReqGenerator + { + private IList list = Platform.CreateArrayList(); + private GeneralName requestorName = null; + private X509Extensions requestExtensions = null; + + private class RequestObject + { + internal CertificateID certId; + internal X509Extensions extensions; + + public RequestObject( + CertificateID certId, + X509Extensions extensions) + { + this.certId = certId; + this.extensions = extensions; + } + + public Request ToRequest() + { + return new Request(certId.ToAsn1Object(), extensions); + } + } + + /** + * Add a request for the given CertificateID. + * + * @param certId certificate ID of interest + */ + public void AddRequest( + CertificateID certId) + { + list.Add(new RequestObject(certId, null)); + } + + /** + * Add a request with extensions + * + * @param certId certificate ID of interest + * @param singleRequestExtensions the extensions to attach to the request + */ + public void AddRequest( + CertificateID certId, + X509Extensions singleRequestExtensions) + { + list.Add(new RequestObject(certId, singleRequestExtensions)); + } + + /** + * Set the requestor name to the passed in X509Principal + * + * @param requestorName a X509Principal representing the requestor name. + */ + public void SetRequestorName( + X509Name requestorName) + { + try + { + this.requestorName = new GeneralName(GeneralName.DirectoryName, requestorName); + } + catch (Exception e) + { + throw new ArgumentException("cannot encode principal", e); + } + } + + public void SetRequestorName( + GeneralName requestorName) + { + this.requestorName = requestorName; + } + + public void SetRequestExtensions( + X509Extensions requestExtensions) + { + this.requestExtensions = requestExtensions; + } + + private OcspReq GenerateRequest( + DerObjectIdentifier signingAlgorithm, + AsymmetricKeyParameter privateKey, + X509Certificate[] chain, + SecureRandom random) + { + Asn1EncodableVector requests = new Asn1EncodableVector(); + + foreach (RequestObject reqObj in list) + { + try + { + requests.Add(reqObj.ToRequest()); + } + catch (Exception e) + { + throw new OcspException("exception creating Request", e); + } + } + + TbsRequest tbsReq = new TbsRequest(requestorName, new DerSequence(requests), requestExtensions); + + ISigner sig = null; + Signature signature = null; + + if (signingAlgorithm != null) + { + if (requestorName == null) + { + throw new OcspException("requestorName must be specified if request is signed."); + } + + try + { + sig = SignerUtilities.GetSigner(signingAlgorithm.Id); + if (random != null) + { + sig.Init(true, new ParametersWithRandom(privateKey, random)); + } + else + { + sig.Init(true, privateKey); + } + } + catch (Exception e) + { + throw new OcspException("exception creating signature: " + e, e); + } + + DerBitString bitSig = null; + + try + { + byte[] encoded = tbsReq.GetEncoded(); + sig.BlockUpdate(encoded, 0, encoded.Length); + + bitSig = new DerBitString(sig.GenerateSignature()); + } + catch (Exception e) + { + throw new OcspException("exception processing TBSRequest: " + e, e); + } + + AlgorithmIdentifier sigAlgId = new AlgorithmIdentifier(signingAlgorithm, DerNull.Instance); + + if (chain != null && chain.Length > 0) + { + Asn1EncodableVector v = new Asn1EncodableVector(); + try + { + for (int i = 0; i != chain.Length; i++) + { + v.Add( + X509CertificateStructure.GetInstance( + Asn1Object.FromByteArray(chain[i].GetEncoded()))); + } + } + catch (IOException e) + { + throw new OcspException("error processing certs", e); + } + catch (CertificateEncodingException e) + { + throw new OcspException("error encoding certs", e); + } + + signature = new Signature(sigAlgId, bitSig, new DerSequence(v)); + } + else + { + signature = new Signature(sigAlgId, bitSig); + } + } + + return new OcspReq(new OcspRequest(tbsReq, signature)); + } + + /** + * Generate an unsigned request + * + * @return the OcspReq + * @throws OcspException + */ + public OcspReq Generate() + { + return GenerateRequest(null, null, null, null); + } + + public OcspReq Generate( + string signingAlgorithm, + AsymmetricKeyParameter privateKey, + X509Certificate[] chain) + { + return Generate(signingAlgorithm, privateKey, chain, null); + } + + public OcspReq Generate( + string signingAlgorithm, + AsymmetricKeyParameter privateKey, + X509Certificate[] chain, + SecureRandom random) + { + if (signingAlgorithm == null) + throw new ArgumentException("no signing algorithm specified"); + + try + { + DerObjectIdentifier oid = OcspUtilities.GetAlgorithmOid(signingAlgorithm); + + return GenerateRequest(oid, privateKey, chain, random); + } + catch (ArgumentException) + { + throw new ArgumentException("unknown signing algorithm specified: " + signingAlgorithm); + } + } + + /** + * Return an IEnumerable of the signature names supported by the generator. + * + * @return an IEnumerable containing recognised names. + */ + public IEnumerable SignatureAlgNames + { + get { return OcspUtilities.AlgNames; } + } + } +} diff --git a/bc-sharp-crypto/src/ocsp/OCSPResp.cs b/bc-sharp-crypto/src/ocsp/OCSPResp.cs new file mode 100644 index 0000000..dc99c6a --- /dev/null +++ b/bc-sharp-crypto/src/ocsp/OCSPResp.cs @@ -0,0 +1,100 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Ocsp; + +namespace Org.BouncyCastle.Ocsp +{ + public class OcspResp + { + private OcspResponse resp; + + public OcspResp( + OcspResponse resp) + { + this.resp = resp; + } + + public OcspResp( + byte[] resp) + : this(new Asn1InputStream(resp)) + { + } + + public OcspResp( + Stream inStr) + : this(new Asn1InputStream(inStr)) + { + } + + private OcspResp( + Asn1InputStream aIn) + { + try + { + this.resp = OcspResponse.GetInstance(aIn.ReadObject()); + } + catch (Exception e) + { + throw new IOException("malformed response: " + e.Message, e); + } + } + + public int Status + { + get { return this.resp.ResponseStatus.Value.IntValue; } + } + + public object GetResponseObject() + { + ResponseBytes rb = this.resp.ResponseBytes; + + if (rb == null) + return null; + + if (rb.ResponseType.Equals(OcspObjectIdentifiers.PkixOcspBasic)) + { + try + { + return new BasicOcspResp( + BasicOcspResponse.GetInstance( + Asn1Object.FromByteArray(rb.Response.GetOctets()))); + } + catch (Exception e) + { + throw new OcspException("problem decoding object: " + e, e); + } + } + + return rb.Response; + } + + /** + * return the ASN.1 encoded representation of this object. + */ + public byte[] GetEncoded() + { + return resp.GetEncoded(); + } + + public override bool Equals( + object obj) + { + if (obj == this) + return true; + + OcspResp other = obj as OcspResp; + + if (other == null) + return false; + + return resp.Equals(other.resp); + } + + public override int GetHashCode() + { + return resp.GetHashCode(); + } + } +} diff --git a/bc-sharp-crypto/src/ocsp/OCSPRespGenerator.cs b/bc-sharp-crypto/src/ocsp/OCSPRespGenerator.cs new file mode 100644 index 0000000..e0eb9ae --- /dev/null +++ b/bc-sharp-crypto/src/ocsp/OCSPRespGenerator.cs @@ -0,0 +1,54 @@ +using System; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Ocsp; + +namespace Org.BouncyCastle.Ocsp +{ + /** + * base generator for an OCSP response - at the moment this only supports the + * generation of responses containing BasicOCSP responses. + */ + public class OCSPRespGenerator + { + public const int Successful = 0; // Response has valid confirmations + public const int MalformedRequest = 1; // Illegal confirmation request + public const int InternalError = 2; // Internal error in issuer + public const int TryLater = 3; // Try again later + // (4) is not used + public const int SigRequired = 5; // Must sign the request + public const int Unauthorized = 6; // Request unauthorized + + public OcspResp Generate( + int status, + object response) + { + if (response == null) + { + return new OcspResp(new OcspResponse(new OcspResponseStatus(status),null)); + } + if (response is BasicOcspResp) + { + BasicOcspResp r = (BasicOcspResp)response; + Asn1OctetString octs; + + try + { + octs = new DerOctetString(r.GetEncoded()); + } + catch (Exception e) + { + throw new OcspException("can't encode object.", e); + } + + ResponseBytes rb = new ResponseBytes( + OcspObjectIdentifiers.PkixOcspBasic, octs); + + return new OcspResp(new OcspResponse( + new OcspResponseStatus(status), rb)); + } + + throw new OcspException("unknown response object"); + } + } +} diff --git a/bc-sharp-crypto/src/ocsp/OCSPRespStatus.cs b/bc-sharp-crypto/src/ocsp/OCSPRespStatus.cs new file mode 100644 index 0000000..9c00c70 --- /dev/null +++ b/bc-sharp-crypto/src/ocsp/OCSPRespStatus.cs @@ -0,0 +1,22 @@ +using System; + +namespace Org.BouncyCastle.Ocsp +{ + [Obsolete("Use version with correct spelling 'OcspRespStatus'")] + public abstract class OcscpRespStatus : OcspRespStatus + { + } + + public abstract class OcspRespStatus + { + /** + * note 4 is not used. + */ + public const int Successful = 0; // --Response has valid confirmations + public const int MalformedRequest = 1; // --Illegal confirmation request + public const int InternalError = 2; // --Internal error in issuer + public const int TryLater = 3; // --Try again later + public const int SigRequired = 5; // --Must sign the request + public const int Unauthorized = 6; // --Request unauthorized + } +} diff --git a/bc-sharp-crypto/src/ocsp/OCSPUtil.cs b/bc-sharp-crypto/src/ocsp/OCSPUtil.cs new file mode 100644 index 0000000..cbc1e95 --- /dev/null +++ b/bc-sharp-crypto/src/ocsp/OCSPUtil.cs @@ -0,0 +1,132 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.CryptoPro; +using Org.BouncyCastle.Asn1.Nist; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.TeleTrust; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; + +namespace Org.BouncyCastle.Ocsp +{ + class OcspUtilities + { + private static readonly IDictionary algorithms = Platform.CreateHashtable(); + private static readonly IDictionary oids = Platform.CreateHashtable(); + private static readonly ISet noParams = new HashSet(); + + static OcspUtilities() + { + algorithms.Add("MD2WITHRSAENCRYPTION", PkcsObjectIdentifiers.MD2WithRsaEncryption); + algorithms.Add("MD2WITHRSA", PkcsObjectIdentifiers.MD2WithRsaEncryption); + algorithms.Add("MD5WITHRSAENCRYPTION", PkcsObjectIdentifiers.MD5WithRsaEncryption); + algorithms.Add("MD5WITHRSA", PkcsObjectIdentifiers.MD5WithRsaEncryption); + algorithms.Add("SHA1WITHRSAENCRYPTION", PkcsObjectIdentifiers.Sha1WithRsaEncryption); + algorithms.Add("SHA1WITHRSA", PkcsObjectIdentifiers.Sha1WithRsaEncryption); + algorithms.Add("SHA224WITHRSAENCRYPTION", PkcsObjectIdentifiers.Sha224WithRsaEncryption); + algorithms.Add("SHA224WITHRSA", PkcsObjectIdentifiers.Sha224WithRsaEncryption); + algorithms.Add("SHA256WITHRSAENCRYPTION", PkcsObjectIdentifiers.Sha256WithRsaEncryption); + algorithms.Add("SHA256WITHRSA", PkcsObjectIdentifiers.Sha256WithRsaEncryption); + algorithms.Add("SHA384WITHRSAENCRYPTION", PkcsObjectIdentifiers.Sha384WithRsaEncryption); + algorithms.Add("SHA384WITHRSA", PkcsObjectIdentifiers.Sha384WithRsaEncryption); + algorithms.Add("SHA512WITHRSAENCRYPTION", PkcsObjectIdentifiers.Sha512WithRsaEncryption); + algorithms.Add("SHA512WITHRSA", PkcsObjectIdentifiers.Sha512WithRsaEncryption); + algorithms.Add("RIPEMD160WITHRSAENCRYPTION", TeleTrusTObjectIdentifiers.RsaSignatureWithRipeMD160); + algorithms.Add("RIPEMD160WITHRSA", TeleTrusTObjectIdentifiers.RsaSignatureWithRipeMD160); + algorithms.Add("RIPEMD128WITHRSAENCRYPTION", TeleTrusTObjectIdentifiers.RsaSignatureWithRipeMD128); + algorithms.Add("RIPEMD128WITHRSA", TeleTrusTObjectIdentifiers.RsaSignatureWithRipeMD128); + algorithms.Add("RIPEMD256WITHRSAENCRYPTION", TeleTrusTObjectIdentifiers.RsaSignatureWithRipeMD256); + algorithms.Add("RIPEMD256WITHRSA", TeleTrusTObjectIdentifiers.RsaSignatureWithRipeMD256); + algorithms.Add("SHA1WITHDSA", X9ObjectIdentifiers.IdDsaWithSha1); + algorithms.Add("DSAWITHSHA1", X9ObjectIdentifiers.IdDsaWithSha1); + algorithms.Add("SHA224WITHDSA", NistObjectIdentifiers.DsaWithSha224); + algorithms.Add("SHA256WITHDSA", NistObjectIdentifiers.DsaWithSha256); + algorithms.Add("SHA1WITHECDSA", X9ObjectIdentifiers.ECDsaWithSha1); + algorithms.Add("ECDSAWITHSHA1", X9ObjectIdentifiers.ECDsaWithSha1); + algorithms.Add("SHA224WITHECDSA", X9ObjectIdentifiers.ECDsaWithSha224); + algorithms.Add("SHA256WITHECDSA", X9ObjectIdentifiers.ECDsaWithSha256); + algorithms.Add("SHA384WITHECDSA", X9ObjectIdentifiers.ECDsaWithSha384); + algorithms.Add("SHA512WITHECDSA", X9ObjectIdentifiers.ECDsaWithSha512); + algorithms.Add("GOST3411WITHGOST3410", CryptoProObjectIdentifiers.GostR3411x94WithGostR3410x94); + algorithms.Add("GOST3411WITHGOST3410-94", CryptoProObjectIdentifiers.GostR3411x94WithGostR3410x94); + + oids.Add(PkcsObjectIdentifiers.MD2WithRsaEncryption, "MD2WITHRSA"); + oids.Add(PkcsObjectIdentifiers.MD5WithRsaEncryption, "MD5WITHRSA"); + oids.Add(PkcsObjectIdentifiers.Sha1WithRsaEncryption, "SHA1WITHRSA"); + oids.Add(PkcsObjectIdentifiers.Sha224WithRsaEncryption, "SHA224WITHRSA"); + oids.Add(PkcsObjectIdentifiers.Sha256WithRsaEncryption, "SHA256WITHRSA"); + oids.Add(PkcsObjectIdentifiers.Sha384WithRsaEncryption, "SHA384WITHRSA"); + oids.Add(PkcsObjectIdentifiers.Sha512WithRsaEncryption, "SHA512WITHRSA"); + oids.Add(TeleTrusTObjectIdentifiers.RsaSignatureWithRipeMD160, "RIPEMD160WITHRSA"); + oids.Add(TeleTrusTObjectIdentifiers.RsaSignatureWithRipeMD128, "RIPEMD128WITHRSA"); + oids.Add(TeleTrusTObjectIdentifiers.RsaSignatureWithRipeMD256, "RIPEMD256WITHRSA"); + oids.Add(X9ObjectIdentifiers.IdDsaWithSha1, "SHA1WITHDSA"); + oids.Add(NistObjectIdentifiers.DsaWithSha224, "SHA224WITHDSA"); + oids.Add(NistObjectIdentifiers.DsaWithSha256, "SHA256WITHDSA"); + oids.Add(X9ObjectIdentifiers.ECDsaWithSha1, "SHA1WITHECDSA"); + oids.Add(X9ObjectIdentifiers.ECDsaWithSha224, "SHA224WITHECDSA"); + oids.Add(X9ObjectIdentifiers.ECDsaWithSha256, "SHA256WITHECDSA"); + oids.Add(X9ObjectIdentifiers.ECDsaWithSha384, "SHA384WITHECDSA"); + oids.Add(X9ObjectIdentifiers.ECDsaWithSha512, "SHA512WITHECDSA"); + oids.Add(CryptoProObjectIdentifiers.GostR3411x94WithGostR3410x94, "GOST3411WITHGOST3410"); + + // + // According to RFC 3279, the ASN.1 encoding SHALL (id-dsa-with-sha1) or MUST (ecdsa-with-SHA*) omit the parameters field. + // The parameters field SHALL be NULL for RSA based signature algorithms. + // + noParams.Add(X9ObjectIdentifiers.ECDsaWithSha1); + noParams.Add(X9ObjectIdentifiers.ECDsaWithSha224); + noParams.Add(X9ObjectIdentifiers.ECDsaWithSha256); + noParams.Add(X9ObjectIdentifiers.ECDsaWithSha384); + noParams.Add(X9ObjectIdentifiers.ECDsaWithSha512); + noParams.Add(X9ObjectIdentifiers.IdDsaWithSha1); + noParams.Add(NistObjectIdentifiers.DsaWithSha224); + noParams.Add(NistObjectIdentifiers.DsaWithSha256); + } + + internal static DerObjectIdentifier GetAlgorithmOid( + string algorithmName) + { + algorithmName = Platform.ToUpperInvariant(algorithmName); + + if (algorithms.Contains(algorithmName)) + { + return (DerObjectIdentifier)algorithms[algorithmName]; + } + + return new DerObjectIdentifier(algorithmName); + } + + + internal static string GetAlgorithmName( + DerObjectIdentifier oid) + { + if (oids.Contains(oid)) + { + return (string)oids[oid]; + } + + return oid.Id; + } + + internal static AlgorithmIdentifier GetSigAlgID( + DerObjectIdentifier sigOid) + { + if (noParams.Contains(sigOid)) + { + return new AlgorithmIdentifier(sigOid); + } + + return new AlgorithmIdentifier(sigOid, DerNull.Instance); + } + + internal static IEnumerable AlgNames + { + get { return new EnumerableProxy(algorithms.Keys); } + } + } +} diff --git a/bc-sharp-crypto/src/ocsp/Req.cs b/bc-sharp-crypto/src/ocsp/Req.cs new file mode 100644 index 0000000..68fd9f1 --- /dev/null +++ b/bc-sharp-crypto/src/ocsp/Req.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Ocsp; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.X509; + +namespace Org.BouncyCastle.Ocsp +{ + public class Req + : X509ExtensionBase + { + private Request req; + + public Req( + Request req) + { + this.req = req; + } + + public CertificateID GetCertID() + { + return new CertificateID(req.ReqCert); + } + + public X509Extensions SingleRequestExtensions + { + get { return req.SingleRequestExtensions; } + } + + protected override X509Extensions GetX509Extensions() + { + return SingleRequestExtensions; + } + } +} diff --git a/bc-sharp-crypto/src/ocsp/RespData.cs b/bc-sharp-crypto/src/ocsp/RespData.cs new file mode 100644 index 0000000..105726c --- /dev/null +++ b/bc-sharp-crypto/src/ocsp/RespData.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Ocsp; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.X509; + +namespace Org.BouncyCastle.Ocsp +{ + public class RespData + : X509ExtensionBase + { + internal readonly ResponseData data; + + public RespData( + ResponseData data) + { + this.data = data; + } + + public int Version + { + get { return data.Version.Value.IntValue + 1; } + } + + public RespID GetResponderId() + { + return new RespID(data.ResponderID); + } + + public DateTime ProducedAt + { + get { return data.ProducedAt.ToDateTime(); } + } + + public SingleResp[] GetResponses() + { + Asn1Sequence s = data.Responses; + SingleResp[] rs = new SingleResp[s.Count]; + + for (int i = 0; i != rs.Length; i++) + { + rs[i] = new SingleResp(SingleResponse.GetInstance(s[i])); + } + + return rs; + } + + public X509Extensions ResponseExtensions + { + get { return data.ResponseExtensions; } + } + + protected override X509Extensions GetX509Extensions() + { + return ResponseExtensions; + } + } +} diff --git a/bc-sharp-crypto/src/ocsp/RespID.cs b/bc-sharp-crypto/src/ocsp/RespID.cs new file mode 100644 index 0000000..3238b26 --- /dev/null +++ b/bc-sharp-crypto/src/ocsp/RespID.cs @@ -0,0 +1,72 @@ +using System; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Ocsp; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.X509; + +namespace Org.BouncyCastle.Ocsp +{ + /** + * Carrier for a ResponderID. + */ + public class RespID + { + internal readonly ResponderID id; + + public RespID( + ResponderID id) + { + this.id = id; + } + + public RespID( + X509Name name) + { + this.id = new ResponderID(name); + } + + public RespID( + AsymmetricKeyParameter publicKey) + { + try + { + SubjectPublicKeyInfo info = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(publicKey); + + byte[] keyHash = DigestUtilities.CalculateDigest("SHA1", info.PublicKeyData.GetBytes()); + + this.id = new ResponderID(new DerOctetString(keyHash)); + } + catch (Exception e) + { + throw new OcspException("problem creating ID: " + e, e); + } + } + + public ResponderID ToAsn1Object() + { + return id; + } + + public override bool Equals( + object obj) + { + if (obj == this) + return true; + + RespID other = obj as RespID; + + if (other == null) + return false; + + return id.Equals(other.id); + } + + public override int GetHashCode() + { + return id.GetHashCode(); + } + } +} diff --git a/bc-sharp-crypto/src/ocsp/RevokedStatus.cs b/bc-sharp-crypto/src/ocsp/RevokedStatus.cs new file mode 100644 index 0000000..6e5ad1b --- /dev/null +++ b/bc-sharp-crypto/src/ocsp/RevokedStatus.cs @@ -0,0 +1,58 @@ +using System; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Ocsp; +using Org.BouncyCastle.Asn1.X509; + +namespace Org.BouncyCastle.Ocsp +{ + /** + * wrapper for the RevokedInfo object + */ + public class RevokedStatus + : CertificateStatus + { + internal readonly RevokedInfo info; + + public RevokedStatus( + RevokedInfo info) + { + this.info = info; + } + + public RevokedStatus( + DateTime revocationDate, + int reason) + { + this.info = new RevokedInfo(new DerGeneralizedTime(revocationDate), new CrlReason(reason)); + } + + public DateTime RevocationTime + { + get { return info.RevocationTime.ToDateTime(); } + } + + public bool HasRevocationReason + { + get { return (info.RevocationReason != null); } + } + + /** + * return the revocation reason. Note: this field is optional, test for it + * with hasRevocationReason() first. + * @exception InvalidOperationException if a reason is asked for and none is avaliable + */ + public int RevocationReason + { + get + { + if (info.RevocationReason == null) + { + throw new InvalidOperationException("attempt to get a reason where none is available"); + } + + return info.RevocationReason.Value.IntValue; + } + } + } +} diff --git a/bc-sharp-crypto/src/ocsp/SingleResp.cs b/bc-sharp-crypto/src/ocsp/SingleResp.cs new file mode 100644 index 0000000..b8979c5 --- /dev/null +++ b/bc-sharp-crypto/src/ocsp/SingleResp.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Ocsp; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities.Date; +using Org.BouncyCastle.X509; + +namespace Org.BouncyCastle.Ocsp +{ + public class SingleResp + : X509ExtensionBase + { + internal readonly SingleResponse resp; + + public SingleResp( + SingleResponse resp) + { + this.resp = resp; + } + + public CertificateID GetCertID() + { + return new CertificateID(resp.CertId); + } + + /** + * Return the status object for the response - null indicates good. + * + * @return the status object for the response, null if it is good. + */ + public object GetCertStatus() + { + CertStatus s = resp.CertStatus; + + if (s.TagNo == 0) + { + return null; // good + } + + if (s.TagNo == 1) + { + return new RevokedStatus(RevokedInfo.GetInstance(s.Status)); + } + + return new UnknownStatus(); + } + + public DateTime ThisUpdate + { + get { return resp.ThisUpdate.ToDateTime(); } + } + + /** + * return the NextUpdate value - note: this is an optional field so may + * be returned as null. + * + * @return nextUpdate, or null if not present. + */ + public DateTimeObject NextUpdate + { + get + { + return resp.NextUpdate == null + ? null + : new DateTimeObject(resp.NextUpdate.ToDateTime()); + } + } + + public X509Extensions SingleExtensions + { + get { return resp.SingleExtensions; } + } + + protected override X509Extensions GetX509Extensions() + { + return SingleExtensions; + } + } +} diff --git a/bc-sharp-crypto/src/ocsp/UnknownStatus.cs b/bc-sharp-crypto/src/ocsp/UnknownStatus.cs new file mode 100644 index 0000000..c0f7a3a --- /dev/null +++ b/bc-sharp-crypto/src/ocsp/UnknownStatus.cs @@ -0,0 +1,15 @@ +using System; + +namespace Org.BouncyCastle.Ocsp +{ + /** + * wrapper for the UnknownInfo object + */ + public class UnknownStatus + : CertificateStatus + { + public UnknownStatus() + { + } + } +} diff --git a/bc-sharp-crypto/src/openpgp/IStreamGenerator.cs b/bc-sharp-crypto/src/openpgp/IStreamGenerator.cs new file mode 100644 index 0000000..379213a --- /dev/null +++ b/bc-sharp-crypto/src/openpgp/IStreamGenerator.cs @@ -0,0 +1,7 @@ +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + public interface IStreamGenerator + { + void Close(); + } +} diff --git a/bc-sharp-crypto/src/openpgp/PGPKeyRing.cs b/bc-sharp-crypto/src/openpgp/PGPKeyRing.cs new file mode 100644 index 0000000..6426f3f --- /dev/null +++ b/bc-sharp-crypto/src/openpgp/PGPKeyRing.cs @@ -0,0 +1,79 @@ +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + public abstract class PgpKeyRing + : PgpObject + { + internal PgpKeyRing() + { + } + + internal static TrustPacket ReadOptionalTrustPacket( + BcpgInputStream bcpgInput) + { + return (bcpgInput.NextPacketTag() == PacketTag.Trust) + ? (TrustPacket) bcpgInput.ReadPacket() + : null; + } + + internal static IList ReadSignaturesAndTrust( + BcpgInputStream bcpgInput) + { + try + { + IList sigList = Platform.CreateArrayList(); + + while (bcpgInput.NextPacketTag() == PacketTag.Signature) + { + SignaturePacket signaturePacket = (SignaturePacket) bcpgInput.ReadPacket(); + TrustPacket trustPacket = ReadOptionalTrustPacket(bcpgInput); + + sigList.Add(new PgpSignature(signaturePacket, trustPacket)); + } + + return sigList; + } + catch (PgpException e) + { + throw new IOException("can't create signature object: " + e.Message, e); + } + } + + internal static void ReadUserIDs( + BcpgInputStream bcpgInput, + out IList ids, + out IList idTrusts, + out IList idSigs) + { + ids = Platform.CreateArrayList(); + idTrusts = Platform.CreateArrayList(); + idSigs = Platform.CreateArrayList(); + + while (bcpgInput.NextPacketTag() == PacketTag.UserId + || bcpgInput.NextPacketTag() == PacketTag.UserAttribute) + { + Packet obj = bcpgInput.ReadPacket(); + if (obj is UserIdPacket) + { + UserIdPacket id = (UserIdPacket)obj; + ids.Add(id.GetId()); + } + else + { + UserAttributePacket user = (UserAttributePacket) obj; + ids.Add(new PgpUserAttributeSubpacketVector(user.GetSubpackets())); + } + + idTrusts.Add( + ReadOptionalTrustPacket(bcpgInput)); + + idSigs.Add( + ReadSignaturesAndTrust(bcpgInput)); + } + } + } +} diff --git a/bc-sharp-crypto/src/openpgp/PGPObject.cs b/bc-sharp-crypto/src/openpgp/PGPObject.cs new file mode 100644 index 0000000..d38276c --- /dev/null +++ b/bc-sharp-crypto/src/openpgp/PGPObject.cs @@ -0,0 +1,9 @@ +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + public abstract class PgpObject + { + internal PgpObject() + { + } + } +} diff --git a/bc-sharp-crypto/src/openpgp/PGPUserAttributeSubpacketVectorGenerator.cs b/bc-sharp-crypto/src/openpgp/PGPUserAttributeSubpacketVectorGenerator.cs new file mode 100644 index 0000000..9d56c8b --- /dev/null +++ b/bc-sharp-crypto/src/openpgp/PGPUserAttributeSubpacketVectorGenerator.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Bcpg.Attr; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + public class PgpUserAttributeSubpacketVectorGenerator + { + private IList list = Platform.CreateArrayList(); + + public virtual void SetImageAttribute( + ImageAttrib.Format imageType, + byte[] imageData) + { + if (imageData == null) + throw new ArgumentException("attempt to set null image", "imageData"); + + list.Add(new ImageAttrib(imageType, imageData)); + } + + public virtual PgpUserAttributeSubpacketVector Generate() + { + UserAttributeSubpacket[] a = new UserAttributeSubpacket[list.Count]; + for (int i = 0; i < list.Count; ++i) + { + a[i] = (UserAttributeSubpacket)list[i]; + } + return new PgpUserAttributeSubpacketVector(a); + } + } +} diff --git a/bc-sharp-crypto/src/openpgp/PgpCompressedData.cs b/bc-sharp-crypto/src/openpgp/PgpCompressedData.cs new file mode 100644 index 0000000..e64a17c --- /dev/null +++ b/bc-sharp-crypto/src/openpgp/PgpCompressedData.cs @@ -0,0 +1,50 @@ +using System.IO; + +using Org.BouncyCastle.Apache.Bzip2; +using Org.BouncyCastle.Utilities.Zlib; + +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + /// Compressed data objects + public class PgpCompressedData + : PgpObject + { + private readonly CompressedDataPacket data; + + public PgpCompressedData( + BcpgInputStream bcpgInput) + { + data = (CompressedDataPacket) bcpgInput.ReadPacket(); + } + + /// The algorithm used for compression + public CompressionAlgorithmTag Algorithm + { + get { return data.Algorithm; } + } + + /// Get the raw input stream contained in the object. + public Stream GetInputStream() + { + return data.GetInputStream(); + } + + /// Return an uncompressed input stream which allows reading of the compressed data. + public Stream GetDataStream() + { + switch (Algorithm) + { + case CompressionAlgorithmTag.Uncompressed: + return GetInputStream(); + case CompressionAlgorithmTag.Zip: + return new ZInputStream(GetInputStream(), true); + case CompressionAlgorithmTag.ZLib: + return new ZInputStream(GetInputStream()); + case CompressionAlgorithmTag.BZip2: + return new CBZip2InputStream(GetInputStream()); + default: + throw new PgpException("can't recognise compression algorithm: " + Algorithm); + } + } + } +} diff --git a/bc-sharp-crypto/src/openpgp/PgpCompressedDataGenerator.cs b/bc-sharp-crypto/src/openpgp/PgpCompressedDataGenerator.cs new file mode 100644 index 0000000..51b6452 --- /dev/null +++ b/bc-sharp-crypto/src/openpgp/PgpCompressedDataGenerator.cs @@ -0,0 +1,221 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Apache.Bzip2; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Zlib; + +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + /// Class for producing compressed data packets. + public class PgpCompressedDataGenerator + : IStreamGenerator + { + private readonly CompressionAlgorithmTag algorithm; + private readonly int compression; + + private Stream dOut; + private BcpgOutputStream pkOut; + + public PgpCompressedDataGenerator( + CompressionAlgorithmTag algorithm) + : this(algorithm, JZlib.Z_DEFAULT_COMPRESSION) + { + } + + public PgpCompressedDataGenerator( + CompressionAlgorithmTag algorithm, + int compression) + { + switch (algorithm) + { + case CompressionAlgorithmTag.Uncompressed: + case CompressionAlgorithmTag.Zip: + case CompressionAlgorithmTag.ZLib: + case CompressionAlgorithmTag.BZip2: + break; + default: + throw new ArgumentException("unknown compression algorithm", "algorithm"); + } + + if (compression != JZlib.Z_DEFAULT_COMPRESSION) + { + if ((compression < JZlib.Z_NO_COMPRESSION) || (compression > JZlib.Z_BEST_COMPRESSION)) + { + throw new ArgumentException("unknown compression level: " + compression); + } + } + + this.algorithm = algorithm; + this.compression = compression; + } + + /// + ///

    + /// Return an output stream which will save the data being written to + /// the compressed object. + ///

    + ///

    + /// The stream created can be closed off by either calling Close() + /// on the stream or Close() on the generator. Closing the returned + /// stream does not close off the Stream parameter outStr. + ///

    + ///
    + /// Stream to be used for output. + /// A Stream for output of the compressed data. + /// + /// + /// + public Stream Open( + Stream outStr) + { + if (dOut != null) + throw new InvalidOperationException("generator already in open state"); + if (outStr == null) + throw new ArgumentNullException("outStr"); + + this.pkOut = new BcpgOutputStream(outStr, PacketTag.CompressedData); + + doOpen(); + + return new WrappedGeneratorStream(this, dOut); + } + + /// + ///

    + /// Return an output stream which will compress the data as it is written to it. + /// The stream will be written out in chunks according to the size of the passed in buffer. + ///

    + ///

    + /// The stream created can be closed off by either calling Close() + /// on the stream or Close() on the generator. Closing the returned + /// stream does not close off the Stream parameter outStr. + ///

    + ///

    + /// Note: if the buffer is not a power of 2 in length only the largest power of 2 + /// bytes worth of the buffer will be used. + ///

    + ///

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

    + ///
    + /// Stream to be used for output. + /// The buffer to use. + /// A Stream for output of the compressed data. + /// + /// + /// + /// + public Stream Open( + Stream outStr, + byte[] buffer) + { + if (dOut != null) + throw new InvalidOperationException("generator already in open state"); + if (outStr == null) + throw new ArgumentNullException("outStr"); + if (buffer == null) + throw new ArgumentNullException("buffer"); + + this.pkOut = new BcpgOutputStream(outStr, PacketTag.CompressedData, buffer); + + doOpen(); + + return new WrappedGeneratorStream(this, dOut); + } + + private void doOpen() + { + pkOut.WriteByte((byte) algorithm); + + switch (algorithm) + { + case CompressionAlgorithmTag.Uncompressed: + dOut = pkOut; + break; + case CompressionAlgorithmTag.Zip: + dOut = new SafeZOutputStream(pkOut, compression, true); + break; + case CompressionAlgorithmTag.ZLib: + dOut = new SafeZOutputStream(pkOut, compression, false); + break; + case CompressionAlgorithmTag.BZip2: + dOut = new SafeCBZip2OutputStream(pkOut); + break; + default: + // Constructor should guard against this possibility + throw new InvalidOperationException(); + } + } + + /// Close the compressed object.summary> + public void Close() + { + if (dOut != null) + { + if (dOut != pkOut) + { + Platform.Dispose(dOut); + } + dOut = null; + + pkOut.Finish(); + pkOut.Flush(); + pkOut = null; + } + } + + private class SafeCBZip2OutputStream : CBZip2OutputStream + { + public SafeCBZip2OutputStream(Stream output) + : base(output) + { + } + +#if PORTABLE + protected override void Dispose(bool disposing) + { + if (disposing) + { + Finish(); + return; + } + base.Dispose(disposing); + } +#else + public override void Close() + { + Finish(); + } +#endif + } + + private class SafeZOutputStream : ZOutputStream + { + public SafeZOutputStream(Stream output, int level, bool nowrap) + : base(output, level, nowrap) + { + } + +#if PORTABLE + protected override void Dispose(bool disposing) + { + if (disposing) + { + Finish(); + End(); + return; + } + base.Dispose(disposing); + } +#else + public override void Close() + { + Finish(); + End(); + } +#endif + } + } +} diff --git a/bc-sharp-crypto/src/openpgp/PgpDataValidationException.cs b/bc-sharp-crypto/src/openpgp/PgpDataValidationException.cs new file mode 100644 index 0000000..d06833c --- /dev/null +++ b/bc-sharp-crypto/src/openpgp/PgpDataValidationException.cs @@ -0,0 +1,18 @@ +using System; + +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + /// + /// Thrown if the IV at the start of a data stream indicates the wrong key is being used. + /// +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT || PORTABLE) + [Serializable] +#endif + public class PgpDataValidationException + : PgpException + { + public PgpDataValidationException() : base() {} + public PgpDataValidationException(string message) : base(message) {} + public PgpDataValidationException(string message, Exception exception) : base(message, exception) {} + } +} diff --git a/bc-sharp-crypto/src/openpgp/PgpEncryptedData.cs b/bc-sharp-crypto/src/openpgp/PgpEncryptedData.cs new file mode 100644 index 0000000..558e0b8 --- /dev/null +++ b/bc-sharp-crypto/src/openpgp/PgpEncryptedData.cs @@ -0,0 +1,151 @@ +using System; +using System.Diagnostics; +using System.IO; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.IO; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + public abstract class PgpEncryptedData + { + internal class TruncatedStream + : BaseInputStream + { + private const int LookAheadSize = 22; + private const int LookAheadBufSize = 512; + private const int LookAheadBufLimit = LookAheadBufSize - LookAheadSize; + + private readonly Stream inStr; + private readonly byte[] lookAhead = new byte[LookAheadBufSize]; + private int bufStart, bufEnd; + + internal TruncatedStream( + Stream inStr) + { + int numRead = Streams.ReadFully(inStr, lookAhead, 0, lookAhead.Length); + + if (numRead < LookAheadSize) + throw new EndOfStreamException(); + + this.inStr = inStr; + this.bufStart = 0; + this.bufEnd = numRead - LookAheadSize; + } + + private int FillBuffer() + { + if (bufEnd < LookAheadBufLimit) + return 0; + + Debug.Assert(bufStart == LookAheadBufLimit); + Debug.Assert(bufEnd == LookAheadBufLimit); + + Array.Copy(lookAhead, LookAheadBufLimit, lookAhead, 0, LookAheadSize); + bufEnd = Streams.ReadFully(inStr, lookAhead, LookAheadSize, LookAheadBufLimit); + bufStart = 0; + return bufEnd; + } + + public override int ReadByte() + { + if (bufStart < bufEnd) + return lookAhead[bufStart++]; + + if (FillBuffer() < 1) + return -1; + + return lookAhead[bufStart++]; + } + + public override int Read(byte[] buf, int off, int len) + { + int avail = bufEnd - bufStart; + + int pos = off; + while (len > avail) + { + Array.Copy(lookAhead, bufStart, buf, pos, avail); + + bufStart += avail; + pos += avail; + len -= avail; + + if ((avail = FillBuffer()) < 1) + return pos - off; + } + + Array.Copy(lookAhead, bufStart, buf, pos, len); + bufStart += len; + + return pos + len - off; + } + + internal byte[] GetLookAhead() + { + byte[] temp = new byte[LookAheadSize]; + Array.Copy(lookAhead, bufStart, temp, 0, LookAheadSize); + return temp; + } + } + + internal InputStreamPacket encData; + internal Stream encStream; + internal TruncatedStream truncStream; + + internal PgpEncryptedData( + InputStreamPacket encData) + { + this.encData = encData; + } + + /// Return the raw input stream for the data stream. + public virtual Stream GetInputStream() + { + return encData.GetInputStream(); + } + + /// Return true if the message is integrity protected. + /// True, if there is a modification detection code namespace associated + /// with this stream. + public bool IsIntegrityProtected() + { + return encData is SymmetricEncIntegrityPacket; + } + + /// Note: This can only be called after the message has been read. + /// True, if the message verifies, false otherwise + public bool Verify() + { + if (!IsIntegrityProtected()) + throw new PgpException("data not integrity protected."); + + DigestStream dIn = (DigestStream) encStream; + + // + // make sure we are at the end. + // + while (encStream.ReadByte() >= 0) + { + // do nothing + } + + // + // process the MDC packet + // + byte[] lookAhead = truncStream.GetLookAhead(); + + IDigest hash = dIn.ReadDigest(); + hash.BlockUpdate(lookAhead, 0, 2); + byte[] digest = DigestUtilities.DoFinal(hash); + + byte[] streamDigest = new byte[digest.Length]; + Array.Copy(lookAhead, 2, streamDigest, 0, streamDigest.Length); + + return Arrays.ConstantTimeAreEqual(digest, streamDigest); + } + } +} diff --git a/bc-sharp-crypto/src/openpgp/PgpEncryptedDataGenerator.cs b/bc-sharp-crypto/src/openpgp/PgpEncryptedDataGenerator.cs new file mode 100644 index 0000000..014281b --- /dev/null +++ b/bc-sharp-crypto/src/openpgp/PgpEncryptedDataGenerator.cs @@ -0,0 +1,598 @@ +using System; +using System.Collections; +using System.Diagnostics; +using System.IO; + +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.IO; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Math.EC; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + /// Generator for encrypted objects. + public class PgpEncryptedDataGenerator + : IStreamGenerator + { + private BcpgOutputStream pOut; + private CipherStream cOut; + private IBufferedCipher c; + private bool withIntegrityPacket; + private bool oldFormat; + private DigestStream digestOut; + + private abstract class EncMethod + : ContainedPacket + { + protected byte[] sessionInfo; + protected SymmetricKeyAlgorithmTag encAlgorithm; + protected KeyParameter key; + + public abstract void AddSessionInfo(byte[] si, SecureRandom random); + } + + private class PbeMethod + : EncMethod + { + private S2k s2k; + + internal PbeMethod( + SymmetricKeyAlgorithmTag encAlgorithm, + S2k s2k, + KeyParameter key) + { + this.encAlgorithm = encAlgorithm; + this.s2k = s2k; + this.key = key; + } + + public KeyParameter GetKey() + { + return key; + } + + public override void AddSessionInfo( + byte[] si, + SecureRandom random) + { + string cName = PgpUtilities.GetSymmetricCipherName(encAlgorithm); + IBufferedCipher c = CipherUtilities.GetCipher(cName + "/CFB/NoPadding"); + + byte[] iv = new byte[c.GetBlockSize()]; + c.Init(true, new ParametersWithRandom(new ParametersWithIV(key, iv), random)); + + this.sessionInfo = c.DoFinal(si, 0, si.Length - 2); + } + + public override void Encode(BcpgOutputStream pOut) + { + SymmetricKeyEncSessionPacket pk = new SymmetricKeyEncSessionPacket( + encAlgorithm, s2k, sessionInfo); + + pOut.WritePacket(pk); + } + } + + private class PubMethod + : EncMethod + { + internal PgpPublicKey pubKey; + internal byte[][] data; + + internal PubMethod(PgpPublicKey pubKey) + { + this.pubKey = pubKey; + } + + public override void AddSessionInfo( + byte[] sessionInfo, + SecureRandom random) + { + byte[] encryptedSessionInfo = EncryptSessionInfo(sessionInfo, random); + + this.data = ProcessSessionInfo(encryptedSessionInfo); + } + + private byte[] EncryptSessionInfo(byte[] sessionInfo, SecureRandom random) + { + if (pubKey.Algorithm != PublicKeyAlgorithmTag.ECDH) + { + IBufferedCipher c; + switch (pubKey.Algorithm) + { + case PublicKeyAlgorithmTag.RsaEncrypt: + case PublicKeyAlgorithmTag.RsaGeneral: + c = CipherUtilities.GetCipher("RSA//PKCS1Padding"); + break; + case PublicKeyAlgorithmTag.ElGamalEncrypt: + case PublicKeyAlgorithmTag.ElGamalGeneral: + c = CipherUtilities.GetCipher("ElGamal/ECB/PKCS1Padding"); + break; + case PublicKeyAlgorithmTag.Dsa: + throw new PgpException("Can't use DSA for encryption."); + case PublicKeyAlgorithmTag.ECDsa: + throw new PgpException("Can't use ECDSA for encryption."); + default: + throw new PgpException("unknown asymmetric algorithm: " + pubKey.Algorithm); + } + + AsymmetricKeyParameter akp = pubKey.GetKey(); + c.Init(true, new ParametersWithRandom(akp, random)); + return c.DoFinal(sessionInfo); + } + + ECDHPublicBcpgKey ecKey = (ECDHPublicBcpgKey)pubKey.PublicKeyPacket.Key; + + // Generate the ephemeral key pair + IAsymmetricCipherKeyPairGenerator gen = GeneratorUtilities.GetKeyPairGenerator("ECDH"); + gen.Init(new ECKeyGenerationParameters(ecKey.CurveOid, random)); + + AsymmetricCipherKeyPair ephKp = gen.GenerateKeyPair(); + ECPrivateKeyParameters ephPriv = (ECPrivateKeyParameters)ephKp.Private; + ECPublicKeyParameters ephPub = (ECPublicKeyParameters)ephKp.Public; + + ECPublicKeyParameters pub = (ECPublicKeyParameters)pubKey.GetKey(); + ECPoint S = pub.Q.Multiply(ephPriv.D).Normalize(); + + KeyParameter key = new KeyParameter(Rfc6637Utilities.CreateKey(pubKey.PublicKeyPacket, S)); + + IWrapper w = PgpUtilities.CreateWrapper(ecKey.SymmetricKeyAlgorithm); + w.Init(true, new ParametersWithRandom(key, random)); + + byte[] paddedSessionData = PgpPad.PadSessionData(sessionInfo); + + byte[] C = w.Wrap(paddedSessionData, 0, paddedSessionData.Length); + byte[] VB = new MPInteger(new BigInteger(1, ephPub.Q.GetEncoded(false))).GetEncoded(); + + byte[] rv = new byte[VB.Length + 1 + C.Length]; + + Array.Copy(VB, 0, rv, 0, VB.Length); + rv[VB.Length] = (byte)C.Length; + Array.Copy(C, 0, rv, VB.Length + 1, C.Length); + + return rv; + } + + private byte[][] ProcessSessionInfo(byte[] encryptedSessionInfo) + { + byte[][] data; + + switch (pubKey.Algorithm) + { + case PublicKeyAlgorithmTag.RsaEncrypt: + case PublicKeyAlgorithmTag.RsaGeneral: + data = new byte[][] { ConvertToEncodedMpi(encryptedSessionInfo) }; + break; + case PublicKeyAlgorithmTag.ElGamalEncrypt: + case PublicKeyAlgorithmTag.ElGamalGeneral: + int halfLength = encryptedSessionInfo.Length / 2; + byte[] b1 = new byte[halfLength]; + byte[] b2 = new byte[halfLength]; + + Array.Copy(encryptedSessionInfo, 0, b1, 0, halfLength); + Array.Copy(encryptedSessionInfo, halfLength, b2, 0, halfLength); + + data = new byte[][] { + ConvertToEncodedMpi(b1), + ConvertToEncodedMpi(b2), + }; + break; + case PublicKeyAlgorithmTag.ECDH: + data = new byte[][]{ encryptedSessionInfo }; + break; + default: + throw new PgpException("unknown asymmetric algorithm: " + pubKey.Algorithm); + } + + return data; + } + + private byte[] ConvertToEncodedMpi(byte[] encryptedSessionInfo) + { + try + { + return new MPInteger(new BigInteger(1, encryptedSessionInfo)).GetEncoded(); + } + catch (IOException e) + { + throw new PgpException("Invalid MPI encoding: " + e.Message, e); + } + } + + public override void Encode(BcpgOutputStream pOut) + { + PublicKeyEncSessionPacket pk = new PublicKeyEncSessionPacket(pubKey.KeyId, pubKey.Algorithm, data); + + pOut.WritePacket(pk); + } + } + + private readonly IList methods = Platform.CreateArrayList(); + private readonly SymmetricKeyAlgorithmTag defAlgorithm; + private readonly SecureRandom rand; + + public PgpEncryptedDataGenerator( + SymmetricKeyAlgorithmTag encAlgorithm) + { + this.defAlgorithm = encAlgorithm; + this.rand = new SecureRandom(); + } + + public PgpEncryptedDataGenerator( + SymmetricKeyAlgorithmTag encAlgorithm, + bool withIntegrityPacket) + { + this.defAlgorithm = encAlgorithm; + this.withIntegrityPacket = withIntegrityPacket; + this.rand = new SecureRandom(); + } + + /// Existing SecureRandom constructor. + /// The symmetric algorithm to use. + /// Source of randomness. + public PgpEncryptedDataGenerator( + SymmetricKeyAlgorithmTag encAlgorithm, + SecureRandom rand) + { + this.defAlgorithm = encAlgorithm; + this.rand = rand; + } + + /// Creates a cipher stream which will have an integrity packet associated with it. + public PgpEncryptedDataGenerator( + SymmetricKeyAlgorithmTag encAlgorithm, + bool withIntegrityPacket, + SecureRandom rand) + { + this.defAlgorithm = encAlgorithm; + this.rand = rand; + this.withIntegrityPacket = withIntegrityPacket; + } + + /// Base constructor. + /// The symmetric algorithm to use. + /// Source of randomness. + /// PGP 2.6.x compatibility required. + public PgpEncryptedDataGenerator( + SymmetricKeyAlgorithmTag encAlgorithm, + SecureRandom rand, + bool oldFormat) + { + this.defAlgorithm = encAlgorithm; + this.rand = rand; + this.oldFormat = oldFormat; + } + + /// + /// Add a PBE encryption method to the encrypted object using the default algorithm (S2K_SHA1). + /// + /// + /// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is + /// the historical behaviour of the library (1.7 and earlier). + /// + [Obsolete("Use version that takes an explicit s2kDigest parameter")] + public void AddMethod(char[] passPhrase) + { + AddMethod(passPhrase, HashAlgorithmTag.Sha1); + } + + /// Add a PBE encryption method to the encrypted object. + /// + /// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is + /// the historical behaviour of the library (1.7 and earlier). + /// + public void AddMethod(char[] passPhrase, HashAlgorithmTag s2kDigest) + { + DoAddMethod(PgpUtilities.EncodePassPhrase(passPhrase, false), true, s2kDigest); + } + + /// Add a PBE encryption method to the encrypted object. + /// + /// The passphrase is encoded to bytes using UTF8 (Encoding.UTF8.GetBytes). + /// + public void AddMethodUtf8(char[] passPhrase, HashAlgorithmTag s2kDigest) + { + DoAddMethod(PgpUtilities.EncodePassPhrase(passPhrase, true), true, s2kDigest); + } + + /// Add a PBE encryption method to the encrypted object. + /// + /// Allows the caller to handle the encoding of the passphrase to bytes. + /// + public void AddMethodRaw(byte[] rawPassPhrase, HashAlgorithmTag s2kDigest) + { + DoAddMethod(rawPassPhrase, false, s2kDigest); + } + + internal void DoAddMethod(byte[] rawPassPhrase, bool clearPassPhrase, HashAlgorithmTag s2kDigest) + { + S2k s2k = PgpUtilities.GenerateS2k(s2kDigest, 0x60, rand); + + methods.Add(new PbeMethod(defAlgorithm, s2k, PgpUtilities.DoMakeKeyFromPassPhrase(defAlgorithm, s2k, rawPassPhrase, clearPassPhrase))); + } + + /// Add a public key encrypted session key to the encrypted object. + public void AddMethod( + PgpPublicKey key) + { + if (!key.IsEncryptionKey) + { + throw new ArgumentException("passed in key not an encryption key!"); + } + + methods.Add(new PubMethod(key)); + } + + private void AddCheckSum( + byte[] sessionInfo) + { + Debug.Assert(sessionInfo != null); + Debug.Assert(sessionInfo.Length >= 3); + + int check = 0; + + for (int i = 1; i < sessionInfo.Length - 2; i++) + { + check += sessionInfo[i]; + } + + sessionInfo[sessionInfo.Length - 2] = (byte)(check >> 8); + sessionInfo[sessionInfo.Length - 1] = (byte)(check); + } + + private byte[] CreateSessionInfo( + SymmetricKeyAlgorithmTag algorithm, + KeyParameter key) + { + byte[] keyBytes = key.GetKey(); + byte[] sessionInfo = new byte[keyBytes.Length + 3]; + sessionInfo[0] = (byte) algorithm; + keyBytes.CopyTo(sessionInfo, 1); + AddCheckSum(sessionInfo); + return sessionInfo; + } + + /// + ///

    + /// If buffer is non null stream assumed to be partial, otherwise the length will be used + /// to output a fixed length packet. + ///

    + ///

    + /// The stream created can be closed off by either calling Close() + /// on the stream or Close() on the generator. Closing the returned + /// stream does not close off the Stream parameter outStr. + ///

    + ///
    + private Stream Open( + Stream outStr, + long length, + byte[] buffer) + { + if (cOut != null) + throw new InvalidOperationException("generator already in open state"); + if (methods.Count == 0) + throw new InvalidOperationException("No encryption methods specified"); + if (outStr == null) + throw new ArgumentNullException("outStr"); + + pOut = new BcpgOutputStream(outStr); + + KeyParameter key; + + if (methods.Count == 1) + { + if (methods[0] is PbeMethod) + { + PbeMethod m = (PbeMethod)methods[0]; + + key = m.GetKey(); + } + else + { + key = PgpUtilities.MakeRandomKey(defAlgorithm, rand); + + byte[] sessionInfo = CreateSessionInfo(defAlgorithm, key); + PubMethod m = (PubMethod)methods[0]; + + try + { + m.AddSessionInfo(sessionInfo, rand); + } + catch (Exception e) + { + throw new PgpException("exception encrypting session key", e); + } + } + + pOut.WritePacket((ContainedPacket)methods[0]); + } + else // multiple methods + { + key = PgpUtilities.MakeRandomKey(defAlgorithm, rand); + byte[] sessionInfo = CreateSessionInfo(defAlgorithm, key); + + for (int i = 0; i != methods.Count; i++) + { + EncMethod m = (EncMethod)methods[i]; + + try + { + m.AddSessionInfo(sessionInfo, rand); + } + catch (Exception e) + { + throw new PgpException("exception encrypting session key", e); + } + + pOut.WritePacket(m); + } + } + + string cName = PgpUtilities.GetSymmetricCipherName(defAlgorithm); + if (cName == null) + { + throw new PgpException("null cipher specified"); + } + + try + { + if (withIntegrityPacket) + { + cName += "/CFB/NoPadding"; + } + else + { + cName += "/OpenPGPCFB/NoPadding"; + } + + c = CipherUtilities.GetCipher(cName); + + // TODO Confirm the IV should be all zero bytes (not inLineIv - see below) + byte[] iv = new byte[c.GetBlockSize()]; + c.Init(true, new ParametersWithRandom(new ParametersWithIV(key, iv), rand)); + + if (buffer == null) + { + // + // we have to Add block size + 2 for the Generated IV and + 1 + 22 if integrity protected + // + if (withIntegrityPacket) + { + pOut = new BcpgOutputStream(outStr, PacketTag.SymmetricEncryptedIntegrityProtected, length + c.GetBlockSize() + 2 + 1 + 22); + pOut.WriteByte(1); // version number + } + else + { + pOut = new BcpgOutputStream(outStr, PacketTag.SymmetricKeyEncrypted, length + c.GetBlockSize() + 2, oldFormat); + } + } + else + { + if (withIntegrityPacket) + { + pOut = new BcpgOutputStream(outStr, PacketTag.SymmetricEncryptedIntegrityProtected, buffer); + pOut.WriteByte(1); // version number + } + else + { + pOut = new BcpgOutputStream(outStr, PacketTag.SymmetricKeyEncrypted, buffer); + } + } + + int blockSize = c.GetBlockSize(); + byte[] inLineIv = new byte[blockSize + 2]; + rand.NextBytes(inLineIv, 0, blockSize); + Array.Copy(inLineIv, inLineIv.Length - 4, inLineIv, inLineIv.Length - 2, 2); + + Stream myOut = cOut = new CipherStream(pOut, null, c); + + if (withIntegrityPacket) + { + string digestName = PgpUtilities.GetDigestName(HashAlgorithmTag.Sha1); + IDigest digest = DigestUtilities.GetDigest(digestName); + myOut = digestOut = new DigestStream(myOut, null, digest); + } + + myOut.Write(inLineIv, 0, inLineIv.Length); + + return new WrappedGeneratorStream(this, myOut); + } + catch (Exception e) + { + throw new PgpException("Exception creating cipher", e); + } + } + + /// + ///

    + /// Return an output stream which will encrypt the data as it is written to it. + ///

    + ///

    + /// The stream created can be closed off by either calling Close() + /// on the stream or Close() on the generator. Closing the returned + /// stream does not close off the Stream parameter outStr. + ///

    + ///
    + public Stream Open( + Stream outStr, + long length) + { + return Open(outStr, length, null); + } + + /// + ///

    + /// Return an output stream which will encrypt the data as it is written to it. + /// The stream will be written out in chunks according to the size of the passed in buffer. + ///

    + ///

    + /// The stream created can be closed off by either calling Close() + /// on the stream or Close() on the generator. Closing the returned + /// stream does not close off the Stream parameter outStr. + ///

    + ///

    + /// Note: if the buffer is not a power of 2 in length only the largest power of 2 + /// bytes worth of the buffer will be used. + ///

    + ///
    + public Stream Open( + Stream outStr, + byte[] buffer) + { + return Open(outStr, 0, buffer); + } + + /// + ///

    + /// Close off the encrypted object - this is equivalent to calling Close() on the stream + /// returned by the Open() method. + ///

    + ///

    + /// Note: This does not close the underlying output stream, only the stream on top of + /// it created by the Open() method. + ///

    + ///
    + public void Close() + { + if (cOut != null) + { + // TODO Should this all be under the try/catch block? + if (digestOut != null) + { + // + // hand code a mod detection packet + // + BcpgOutputStream bOut = new BcpgOutputStream( + digestOut, PacketTag.ModificationDetectionCode, 20); + + bOut.Flush(); + digestOut.Flush(); + + // TODO + byte[] dig = DigestUtilities.DoFinal(digestOut.WriteDigest()); + cOut.Write(dig, 0, dig.Length); + } + + cOut.Flush(); + + try + { + pOut.Write(c.DoFinal()); + pOut.Finish(); + } + catch (Exception e) + { + throw new IOException(e.Message, e); + } + + cOut = null; + pOut = null; + } + } + } +} diff --git a/bc-sharp-crypto/src/openpgp/PgpEncryptedDataList.cs b/bc-sharp-crypto/src/openpgp/PgpEncryptedDataList.cs new file mode 100644 index 0000000..8dded7c --- /dev/null +++ b/bc-sharp-crypto/src/openpgp/PgpEncryptedDataList.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; + +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + /// A holder for a list of PGP encryption method packets. + public class PgpEncryptedDataList + : PgpObject + { + private IList list = Platform.CreateArrayList(); + private InputStreamPacket data; + + public PgpEncryptedDataList( + BcpgInputStream bcpgInput) + { + while (bcpgInput.NextPacketTag() == PacketTag.PublicKeyEncryptedSession + || bcpgInput.NextPacketTag() == PacketTag.SymmetricKeyEncryptedSessionKey) + { + list.Add(bcpgInput.ReadPacket()); + } + + data = (InputStreamPacket)bcpgInput.ReadPacket(); + + for (int i = 0; i != list.Count; i++) + { + if (list[i] is SymmetricKeyEncSessionPacket) + { + list[i] = new PgpPbeEncryptedData((SymmetricKeyEncSessionPacket) list[i], data); + } + else + { + list[i] = new PgpPublicKeyEncryptedData((PublicKeyEncSessionPacket) list[i], data); + } + } + } + + public PgpEncryptedData this[int index] + { + get { return (PgpEncryptedData) list[index]; } + } + + [Obsolete("Use 'object[index]' syntax instead")] + public object Get(int index) + { + return this[index]; + } + + [Obsolete("Use 'Count' property instead")] + public int Size + { + get { return list.Count; } + } + + public int Count + { + get { return list.Count; } + } + + public bool IsEmpty + { + get { return list.Count == 0; } + } + + public IEnumerable GetEncryptedDataObjects() + { + return new EnumerableProxy(list); + } + } +} diff --git a/bc-sharp-crypto/src/openpgp/PgpException.cs b/bc-sharp-crypto/src/openpgp/PgpException.cs new file mode 100644 index 0000000..230dab8 --- /dev/null +++ b/bc-sharp-crypto/src/openpgp/PgpException.cs @@ -0,0 +1,22 @@ +using System; + +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + /// Generic exception class for PGP encoding/decoding problems. +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT || PORTABLE) + [Serializable] +#endif + public class PgpException + : Exception + { + public PgpException() : base() {} + public PgpException(string message) : base(message) {} + public PgpException(string message, Exception exception) : base(message, exception) {} + + [Obsolete("Use InnerException property")] + public Exception UnderlyingException + { + get { return InnerException; } + } + } +} diff --git a/bc-sharp-crypto/src/openpgp/PgpExperimental.cs b/bc-sharp-crypto/src/openpgp/PgpExperimental.cs new file mode 100644 index 0000000..8518335 --- /dev/null +++ b/bc-sharp-crypto/src/openpgp/PgpExperimental.cs @@ -0,0 +1,16 @@ +using System; + +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + public class PgpExperimental + : PgpObject + { + private readonly ExperimentalPacket p; + + public PgpExperimental( + BcpgInputStream bcpgIn) + { + p = (ExperimentalPacket) bcpgIn.ReadPacket(); + } + } +} diff --git a/bc-sharp-crypto/src/openpgp/PgpKeyFlags.cs b/bc-sharp-crypto/src/openpgp/PgpKeyFlags.cs new file mode 100644 index 0000000..ea18006 --- /dev/null +++ b/bc-sharp-crypto/src/openpgp/PgpKeyFlags.cs @@ -0,0 +1,13 @@ +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + /// Key flag values for the KeyFlags subpacket. + public abstract class PgpKeyFlags + { + public const int CanCertify = 0x01; // This key may be used to certify other keys. + public const int CanSign = 0x02; // This key may be used to sign data. + public const int CanEncryptCommunications = 0x04; // This key may be used to encrypt communications. + public const int CanEncryptStorage = 0x08; // This key may be used to encrypt storage. + public const int MaybeSplit = 0x10; // The private component of this key may have been split by a secret-sharing mechanism. + public const int MaybeShared = 0x80; // The private component of this key may be in the possession of more than one person. + } +} diff --git a/bc-sharp-crypto/src/openpgp/PgpKeyPair.cs b/bc-sharp-crypto/src/openpgp/PgpKeyPair.cs new file mode 100644 index 0000000..9cf78fa --- /dev/null +++ b/bc-sharp-crypto/src/openpgp/PgpKeyPair.cs @@ -0,0 +1,67 @@ +using System; + +using Org.BouncyCastle.Crypto; + +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + /// + /// General class to handle JCA key pairs and convert them into OpenPGP ones. + ///

    + /// A word for the unwary, the KeyId for an OpenPGP public key is calculated from + /// a hash that includes the time of creation, if you pass a different date to the + /// constructor below with the same public private key pair the KeyIs will not be the + /// same as for previous generations of the key, so ideally you only want to do + /// this once. + ///

    + ///
    + public class PgpKeyPair + { + private readonly PgpPublicKey pub; + private readonly PgpPrivateKey priv; + + public PgpKeyPair( + PublicKeyAlgorithmTag algorithm, + AsymmetricCipherKeyPair keyPair, + DateTime time) + : this(algorithm, keyPair.Public, keyPair.Private, time) + { + } + + public PgpKeyPair( + PublicKeyAlgorithmTag algorithm, + AsymmetricKeyParameter pubKey, + AsymmetricKeyParameter privKey, + DateTime time) + { + this.pub = new PgpPublicKey(algorithm, pubKey, time); + this.priv = new PgpPrivateKey(pub.KeyId, pub.PublicKeyPacket, privKey); + } + + /// Create a key pair from a PgpPrivateKey and a PgpPublicKey. + /// The public key. + /// The private key. + public PgpKeyPair( + PgpPublicKey pub, + PgpPrivateKey priv) + { + this.pub = pub; + this.priv = priv; + } + + /// The keyId associated with this key pair. + public long KeyId + { + get { return pub.KeyId; } + } + + public PgpPublicKey PublicKey + { + get { return pub; } + } + + public PgpPrivateKey PrivateKey + { + get { return priv; } + } + } +} diff --git a/bc-sharp-crypto/src/openpgp/PgpKeyRingGenerator.cs b/bc-sharp-crypto/src/openpgp/PgpKeyRingGenerator.cs new file mode 100644 index 0000000..4f6a4b1 --- /dev/null +++ b/bc-sharp-crypto/src/openpgp/PgpKeyRingGenerator.cs @@ -0,0 +1,402 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + /// + /// Generator for a PGP master and subkey ring. + /// This class will generate both the secret and public key rings + /// + public class PgpKeyRingGenerator + { + private IList keys = Platform.CreateArrayList(); + private string id; + private SymmetricKeyAlgorithmTag encAlgorithm; + private HashAlgorithmTag hashAlgorithm; + private int certificationLevel; + private byte[] rawPassPhrase; + private bool useSha1; + private PgpKeyPair masterKey; + private PgpSignatureSubpacketVector hashedPacketVector; + private PgpSignatureSubpacketVector unhashedPacketVector; + private SecureRandom rand; + + /// + /// Create a new key ring generator using old style checksumming. It is recommended to use + /// SHA1 checksumming where possible. + /// + /// + /// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is + /// the historical behaviour of the library (1.7 and earlier). + /// + /// The certification level for keys on this ring. + /// The master key pair. + /// The id to be associated with the ring. + /// The algorithm to be used to protect secret keys. + /// The passPhrase to be used to protect secret keys. + /// Packets to be included in the certification hash. + /// Packets to be attached unhashed to the certification. + /// input secured random. + [Obsolete("Use version taking an explicit 'useSha1' parameter instead")] + public PgpKeyRingGenerator( + int certificationLevel, + PgpKeyPair masterKey, + string id, + SymmetricKeyAlgorithmTag encAlgorithm, + char[] passPhrase, + PgpSignatureSubpacketVector hashedPackets, + PgpSignatureSubpacketVector unhashedPackets, + SecureRandom rand) + : this(certificationLevel, masterKey, id, encAlgorithm, passPhrase, false, hashedPackets, unhashedPackets, rand) + { + } + + /// + /// Create a new key ring generator. + /// + /// + /// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is + /// the historical behaviour of the library (1.7 and earlier). + /// + /// The certification level for keys on this ring. + /// The master key pair. + /// The id to be associated with the ring. + /// The algorithm to be used to protect secret keys. + /// The passPhrase to be used to protect secret keys. + /// Checksum the secret keys with SHA1 rather than the older 16 bit checksum. + /// Packets to be included in the certification hash. + /// Packets to be attached unhashed to the certification. + /// input secured random. + public PgpKeyRingGenerator( + int certificationLevel, + PgpKeyPair masterKey, + string id, + SymmetricKeyAlgorithmTag encAlgorithm, + char[] passPhrase, + bool useSha1, + PgpSignatureSubpacketVector hashedPackets, + PgpSignatureSubpacketVector unhashedPackets, + SecureRandom rand) + : this(certificationLevel, masterKey, id, encAlgorithm, false, passPhrase, useSha1, hashedPackets, unhashedPackets, rand) + { + } + + /// + /// Create a new key ring generator. + /// + /// The certification level for keys on this ring. + /// The master key pair. + /// The id to be associated with the ring. + /// The algorithm to be used to protect secret keys. + /// + /// If true, conversion of the passphrase to bytes uses Encoding.UTF8.GetBytes(), otherwise the conversion + /// is performed using Convert.ToByte(), which is the historical behaviour of the library (1.7 and earlier). + /// + /// The passPhrase to be used to protect secret keys. + /// Checksum the secret keys with SHA1 rather than the older 16 bit checksum. + /// Packets to be included in the certification hash. + /// Packets to be attached unhashed to the certification. + /// input secured random. + public PgpKeyRingGenerator( + int certificationLevel, + PgpKeyPair masterKey, + string id, + SymmetricKeyAlgorithmTag encAlgorithm, + bool utf8PassPhrase, + char[] passPhrase, + bool useSha1, + PgpSignatureSubpacketVector hashedPackets, + PgpSignatureSubpacketVector unhashedPackets, + SecureRandom rand) + : this(certificationLevel, masterKey, id, encAlgorithm, + PgpUtilities.EncodePassPhrase(passPhrase, utf8PassPhrase), + useSha1, hashedPackets, unhashedPackets, rand) + { + } + + /// + /// Create a new key ring generator. + /// + /// The certification level for keys on this ring. + /// The master key pair. + /// The id to be associated with the ring. + /// The algorithm to be used to protect secret keys. + /// The passPhrase to be used to protect secret keys. + /// Checksum the secret keys with SHA1 rather than the older 16 bit checksum. + /// Packets to be included in the certification hash. + /// Packets to be attached unhashed to the certification. + /// input secured random. + public PgpKeyRingGenerator( + int certificationLevel, + PgpKeyPair masterKey, + string id, + SymmetricKeyAlgorithmTag encAlgorithm, + byte[] rawPassPhrase, + bool useSha1, + PgpSignatureSubpacketVector hashedPackets, + PgpSignatureSubpacketVector unhashedPackets, + SecureRandom rand) + { + this.certificationLevel = certificationLevel; + this.masterKey = masterKey; + this.id = id; + this.encAlgorithm = encAlgorithm; + this.rawPassPhrase = rawPassPhrase; + this.useSha1 = useSha1; + this.hashedPacketVector = hashedPackets; + this.unhashedPacketVector = unhashedPackets; + this.rand = rand; + + keys.Add(new PgpSecretKey(certificationLevel, masterKey, id, encAlgorithm, rawPassPhrase, false, useSha1, hashedPackets, unhashedPackets, rand)); + } + + /// + /// Create a new key ring generator. + /// + /// + /// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is + /// the historical behaviour of the library (1.7 and earlier). + /// + /// The certification level for keys on this ring. + /// The master key pair. + /// The id to be associated with the ring. + /// The algorithm to be used to protect secret keys. + /// The hash algorithm. + /// The passPhrase to be used to protect secret keys. + /// Checksum the secret keys with SHA1 rather than the older 16 bit checksum. + /// Packets to be included in the certification hash. + /// Packets to be attached unhashed to the certification. + /// input secured random. + public PgpKeyRingGenerator( + int certificationLevel, + PgpKeyPair masterKey, + string id, + SymmetricKeyAlgorithmTag encAlgorithm, + HashAlgorithmTag hashAlgorithm, + char[] passPhrase, + bool useSha1, + PgpSignatureSubpacketVector hashedPackets, + PgpSignatureSubpacketVector unhashedPackets, + SecureRandom rand) + : this(certificationLevel, masterKey, id, encAlgorithm, hashAlgorithm, false, passPhrase, useSha1, hashedPackets, unhashedPackets, rand) + { + } + + /// + /// Create a new key ring generator. + /// + /// The certification level for keys on this ring. + /// The master key pair. + /// The id to be associated with the ring. + /// The algorithm to be used to protect secret keys. + /// The hash algorithm. + /// + /// If true, conversion of the passphrase to bytes uses Encoding.UTF8.GetBytes(), otherwise the conversion + /// is performed using Convert.ToByte(), which is the historical behaviour of the library (1.7 and earlier). + /// + /// The passPhrase to be used to protect secret keys. + /// Checksum the secret keys with SHA1 rather than the older 16 bit checksum. + /// Packets to be included in the certification hash. + /// Packets to be attached unhashed to the certification. + /// input secured random. + public PgpKeyRingGenerator( + int certificationLevel, + PgpKeyPair masterKey, + string id, + SymmetricKeyAlgorithmTag encAlgorithm, + HashAlgorithmTag hashAlgorithm, + bool utf8PassPhrase, + char[] passPhrase, + bool useSha1, + PgpSignatureSubpacketVector hashedPackets, + PgpSignatureSubpacketVector unhashedPackets, + SecureRandom rand) + : this(certificationLevel, masterKey, id, encAlgorithm, hashAlgorithm, + PgpUtilities.EncodePassPhrase(passPhrase, utf8PassPhrase), + useSha1, hashedPackets, unhashedPackets, rand) + { + } + + /// + /// Create a new key ring generator. + /// + /// + /// Allows the caller to handle the encoding of the passphrase to bytes. + /// + /// The certification level for keys on this ring. + /// The master key pair. + /// The id to be associated with the ring. + /// The algorithm to be used to protect secret keys. + /// The hash algorithm. + /// The passPhrase to be used to protect secret keys. + /// Checksum the secret keys with SHA1 rather than the older 16 bit checksum. + /// Packets to be included in the certification hash. + /// Packets to be attached unhashed to the certification. + /// input secured random. + public PgpKeyRingGenerator( + int certificationLevel, + PgpKeyPair masterKey, + string id, + SymmetricKeyAlgorithmTag encAlgorithm, + HashAlgorithmTag hashAlgorithm, + byte[] rawPassPhrase, + bool useSha1, + PgpSignatureSubpacketVector hashedPackets, + PgpSignatureSubpacketVector unhashedPackets, + SecureRandom rand) + { + this.certificationLevel = certificationLevel; + this.masterKey = masterKey; + this.id = id; + this.encAlgorithm = encAlgorithm; + this.rawPassPhrase = rawPassPhrase; + this.useSha1 = useSha1; + this.hashedPacketVector = hashedPackets; + this.unhashedPacketVector = unhashedPackets; + this.rand = rand; + this.hashAlgorithm = hashAlgorithm; + + keys.Add(new PgpSecretKey(certificationLevel, masterKey, id, encAlgorithm, hashAlgorithm, rawPassPhrase, false, useSha1, hashedPackets, unhashedPackets, rand)); + } + + /// Add a subkey to the key ring to be generated with default certification. + public void AddSubKey( + PgpKeyPair keyPair) + { + AddSubKey(keyPair, this.hashedPacketVector, this.unhashedPacketVector); + } + + + /// + /// Add a subkey to the key ring to be generated with default certification. + /// + /// The key pair. + /// The hash algorithm. + public void AddSubKey(PgpKeyPair keyPair, HashAlgorithmTag hashAlgorithm) + { + this.AddSubKey(keyPair, this.hashedPacketVector, this.unhashedPacketVector, hashAlgorithm); + } + + /// + /// Add a subkey with specific hashed and unhashed packets associated with it and + /// default certification. + /// + /// Public/private key pair. + /// Hashed packet values to be included in certification. + /// Unhashed packets values to be included in certification. + /// + public void AddSubKey( + PgpKeyPair keyPair, + PgpSignatureSubpacketVector hashedPackets, + PgpSignatureSubpacketVector unhashedPackets) + { + try + { + PgpSignatureGenerator sGen = new PgpSignatureGenerator( + masterKey.PublicKey.Algorithm, HashAlgorithmTag.Sha1); + + // + // Generate the certification + // + sGen.InitSign(PgpSignature.SubkeyBinding, masterKey.PrivateKey); + + sGen.SetHashedSubpackets(hashedPackets); + sGen.SetUnhashedSubpackets(unhashedPackets); + + IList subSigs = Platform.CreateArrayList(); + + subSigs.Add(sGen.GenerateCertification(masterKey.PublicKey, keyPair.PublicKey)); + + keys.Add(new PgpSecretKey(keyPair.PrivateKey, new PgpPublicKey(keyPair.PublicKey, null, subSigs), encAlgorithm, + rawPassPhrase, false, useSha1, rand, false)); + } + catch (PgpException e) + { + throw e; + } + catch (Exception e) + { + throw new PgpException("exception adding subkey: ", e); + } + } + + /// + /// Add a subkey with specific hashed and unhashed packets associated with it and + /// default certification. + /// + /// Public/private key pair. + /// Hashed packet values to be included in certification. + /// Unhashed packets values to be included in certification. + /// The hash algorithm. + /// exception adding subkey: + /// + public void AddSubKey( + PgpKeyPair keyPair, + PgpSignatureSubpacketVector hashedPackets, + PgpSignatureSubpacketVector unhashedPackets, + HashAlgorithmTag hashAlgorithm) + { + try + { + PgpSignatureGenerator sGen = new PgpSignatureGenerator(masterKey.PublicKey.Algorithm, hashAlgorithm); + + // + // Generate the certification + // + sGen.InitSign(PgpSignature.SubkeyBinding, masterKey.PrivateKey); + + sGen.SetHashedSubpackets(hashedPackets); + sGen.SetUnhashedSubpackets(unhashedPackets); + + IList subSigs = Platform.CreateArrayList(); + subSigs.Add(sGen.GenerateCertification(masterKey.PublicKey, keyPair.PublicKey)); + + keys.Add(new PgpSecretKey(keyPair.PrivateKey, new PgpPublicKey(keyPair.PublicKey, null, subSigs), encAlgorithm, + rawPassPhrase, false, useSha1, rand, false)); + } + catch (PgpException) + { + throw; + } + catch (Exception e) + { + throw new PgpException("exception adding subkey: ", e); + } + } + + + /// Return the secret key ring. + public PgpSecretKeyRing GenerateSecretKeyRing() + { + return new PgpSecretKeyRing(keys); + } + + /// Return the public key ring that corresponds to the secret key ring. + public PgpPublicKeyRing GeneratePublicKeyRing() + { + IList pubKeys = Platform.CreateArrayList(); + + IEnumerator enumerator = keys.GetEnumerator(); + enumerator.MoveNext(); + + PgpSecretKey pgpSecretKey = (PgpSecretKey) enumerator.Current; + pubKeys.Add(pgpSecretKey.PublicKey); + + while (enumerator.MoveNext()) + { + pgpSecretKey = (PgpSecretKey) enumerator.Current; + + PgpPublicKey k = new PgpPublicKey(pgpSecretKey.PublicKey); + k.publicPk = new PublicSubkeyPacket( + k.Algorithm, k.CreationTime, k.publicPk.Key); + + pubKeys.Add(k); + } + + return new PgpPublicKeyRing(pubKeys); + } + } +} diff --git a/bc-sharp-crypto/src/openpgp/PgpKeyValidationException.cs b/bc-sharp-crypto/src/openpgp/PgpKeyValidationException.cs new file mode 100644 index 0000000..383ae57 --- /dev/null +++ b/bc-sharp-crypto/src/openpgp/PgpKeyValidationException.cs @@ -0,0 +1,18 @@ +using System; + +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + /// + /// Thrown if the key checksum is invalid. + /// +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT || PORTABLE) + [Serializable] +#endif + public class PgpKeyValidationException + : PgpException + { + public PgpKeyValidationException() : base() {} + public PgpKeyValidationException(string message) : base(message) {} + public PgpKeyValidationException(string message, Exception exception) : base(message, exception) {} + } +} diff --git a/bc-sharp-crypto/src/openpgp/PgpLiteralData.cs b/bc-sharp-crypto/src/openpgp/PgpLiteralData.cs new file mode 100644 index 0000000..79bbc39 --- /dev/null +++ b/bc-sharp-crypto/src/openpgp/PgpLiteralData.cs @@ -0,0 +1,63 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Utilities.Date; + +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + /// Class for processing literal data objects. + public class PgpLiteralData + : PgpObject + { + public const char Binary = 'b'; + public const char Text = 't'; + public const char Utf8 = 'u'; + + /// The special name indicating a "for your eyes only" packet. + public const string Console = "_CONSOLE"; + + private LiteralDataPacket data; + + public PgpLiteralData( + BcpgInputStream bcpgInput) + { + data = (LiteralDataPacket) bcpgInput.ReadPacket(); + } + + /// The format of the data stream - Binary or Text + public int Format + { + get { return data.Format; } + } + + /// The file name that's associated with the data stream. + public string FileName + { + get { return data.FileName; } + } + + /// Return the file name as an unintrepreted byte array. + public byte[] GetRawFileName() + { + return data.GetRawFileName(); + } + + /// The modification time for the file. + public DateTime ModificationTime + { + get { return DateTimeUtilities.UnixMsToDateTime(data.ModificationTime); } + } + + /// The raw input stream for the data stream. + public Stream GetInputStream() + { + return data.GetInputStream(); + } + + /// The input stream representing the data stream. + public Stream GetDataStream() + { + return GetInputStream(); + } + } +} diff --git a/bc-sharp-crypto/src/openpgp/PgpLiteralDataGenerator.cs b/bc-sharp-crypto/src/openpgp/PgpLiteralDataGenerator.cs new file mode 100644 index 0000000..7672659 --- /dev/null +++ b/bc-sharp-crypto/src/openpgp/PgpLiteralDataGenerator.cs @@ -0,0 +1,182 @@ +using System; +using System.IO; +using System.Text; + +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Date; + +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + /// Class for producing literal data packets. + public class PgpLiteralDataGenerator + : IStreamGenerator + { + public const char Binary = PgpLiteralData.Binary; + public const char Text = PgpLiteralData.Text; + public const char Utf8 = PgpLiteralData.Utf8; + + /// The special name indicating a "for your eyes only" packet. + public const string Console = PgpLiteralData.Console; + + private BcpgOutputStream pkOut; + private bool oldFormat; + + public PgpLiteralDataGenerator() + { + } + + /// + /// Generates literal data objects in the old format. + /// This is important if you need compatibility with PGP 2.6.x. + /// + /// If true, uses old format. + public PgpLiteralDataGenerator( + bool oldFormat) + { + this.oldFormat = oldFormat; + } + + private void WriteHeader( + BcpgOutputStream outStr, + char format, + byte[] encName, + long modificationTime) + { + outStr.Write( + (byte) format, + (byte) encName.Length); + + outStr.Write(encName); + + long modDate = modificationTime / 1000L; + + outStr.Write( + (byte)(modDate >> 24), + (byte)(modDate >> 16), + (byte)(modDate >> 8), + (byte)modDate); + } + + /// + ///

    + /// Open a literal data packet, returning a stream to store the data inside the packet. + ///

    + ///

    + /// The stream created can be closed off by either calling Close() + /// on the stream or Close() on the generator. Closing the returned + /// stream does not close off the Stream parameter outStr. + ///

    + ///
    + /// The stream we want the packet in. + /// The format we are using. + /// The name of the 'file'. + /// The length of the data we will write. + /// The time of last modification we want stored. + public Stream Open( + Stream outStr, + char format, + string name, + long length, + DateTime modificationTime) + { + if (pkOut != null) + throw new InvalidOperationException("generator already in open state"); + if (outStr == null) + throw new ArgumentNullException("outStr"); + + // Do this first, since it might throw an exception + long unixMs = DateTimeUtilities.DateTimeToUnixMs(modificationTime); + + byte[] encName = Strings.ToUtf8ByteArray(name); + + pkOut = new BcpgOutputStream(outStr, PacketTag.LiteralData, + length + 2 + encName.Length + 4, oldFormat); + + WriteHeader(pkOut, format, encName, unixMs); + + return new WrappedGeneratorStream(this, pkOut); + } + + /// + ///

    + /// Open a literal data packet, returning a stream to store the data inside the packet, + /// as an indefinite length stream. The stream is written out as a series of partial + /// packets with a chunk size determined by the size of the passed in buffer. + ///

    + ///

    + /// The stream created can be closed off by either calling Close() + /// on the stream or Close() on the generator. Closing the returned + /// stream does not close off the Stream parameter outStr. + ///

    + ///

    + /// Note: if the buffer is not a power of 2 in length only the largest power of 2 + /// bytes worth of the buffer will be used.

    + ///
    + /// The stream we want the packet in. + /// The format we are using. + /// The name of the 'file'. + /// The time of last modification we want stored. + /// The buffer to use for collecting data to put into chunks. + public Stream Open( + Stream outStr, + char format, + string name, + DateTime modificationTime, + byte[] buffer) + { + if (pkOut != null) + throw new InvalidOperationException("generator already in open state"); + if (outStr == null) + throw new ArgumentNullException("outStr"); + + // Do this first, since it might throw an exception + long unixMs = DateTimeUtilities.DateTimeToUnixMs(modificationTime); + + byte[] encName = Strings.ToUtf8ByteArray(name); + + pkOut = new BcpgOutputStream(outStr, PacketTag.LiteralData, buffer); + + WriteHeader(pkOut, format, encName, unixMs); + + return new WrappedGeneratorStream(this, pkOut); + } + +#if !PORTABLE || DOTNET + /// + ///

    + /// Open a literal data packet for the passed in FileInfo object, returning + /// an output stream for saving the file contents. + ///

    + ///

    + /// The stream created can be closed off by either calling Close() + /// on the stream or Close() on the generator. Closing the returned + /// stream does not close off the Stream parameter outStr. + ///

    + ///
    + /// The stream we want the packet in. + /// The format we are using. + /// The FileInfo object containg the packet details. + public Stream Open( + Stream outStr, + char format, + FileInfo file) + { + return Open(outStr, format, file.Name, file.Length, file.LastWriteTime); + } +#endif + + /// + /// Close the literal data packet - this is equivalent to calling Close() + /// on the stream returned by the Open() method. + /// + public void Close() + { + if (pkOut != null) + { + pkOut.Finish(); + pkOut.Flush(); + pkOut = null; + } + } + } +} diff --git a/bc-sharp-crypto/src/openpgp/PgpMarker.cs b/bc-sharp-crypto/src/openpgp/PgpMarker.cs new file mode 100644 index 0000000..733e4e9 --- /dev/null +++ b/bc-sharp-crypto/src/openpgp/PgpMarker.cs @@ -0,0 +1,18 @@ +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + /// + /// A PGP marker packet - in general these should be ignored other than where + /// the idea is to preserve the original input stream. + /// + public class PgpMarker + : PgpObject + { + private readonly MarkerPacket p; + + public PgpMarker( + BcpgInputStream bcpgIn) + { + p = (MarkerPacket) bcpgIn.ReadPacket(); + } + } +} diff --git a/bc-sharp-crypto/src/openpgp/PgpObjectFactory.cs b/bc-sharp-crypto/src/openpgp/PgpObjectFactory.cs new file mode 100644 index 0000000..c5c6fcb --- /dev/null +++ b/bc-sharp-crypto/src/openpgp/PgpObjectFactory.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + /// + /// General class for reading a PGP object stream. + ///

    + /// Note: if this class finds a PgpPublicKey or a PgpSecretKey it + /// will create a PgpPublicKeyRing, or a PgpSecretKeyRing for each + /// key found. If all you are trying to do is read a key ring file use + /// either PgpPublicKeyRingBundle or PgpSecretKeyRingBundle.

    + ///
    + public class PgpObjectFactory + { + private readonly BcpgInputStream bcpgIn; + + public PgpObjectFactory( + Stream inputStream) + { + this.bcpgIn = BcpgInputStream.Wrap(inputStream); + } + + public PgpObjectFactory( + byte[] bytes) + : this(new MemoryStream(bytes, false)) + { + } + + /// Return the next object in the stream, or null if the end is reached. + /// On a parse error + public PgpObject NextPgpObject() + { + PacketTag tag = bcpgIn.NextPacketTag(); + + if ((int) tag == -1) return null; + + switch (tag) + { + case PacketTag.Signature: + { + IList l = Platform.CreateArrayList(); + + while (bcpgIn.NextPacketTag() == PacketTag.Signature) + { + try + { + l.Add(new PgpSignature(bcpgIn)); + } + catch (PgpException e) + { + throw new IOException("can't create signature object: " + e); + } + } + + PgpSignature[] sigs = new PgpSignature[l.Count]; + for (int i = 0; i < l.Count; ++i) + { + sigs[i] = (PgpSignature)l[i]; + } + return new PgpSignatureList(sigs); + } + case PacketTag.SecretKey: + try + { + return new PgpSecretKeyRing(bcpgIn); + } + catch (PgpException e) + { + throw new IOException("can't create secret key object: " + e); + } + case PacketTag.PublicKey: + return new PgpPublicKeyRing(bcpgIn); + // TODO Make PgpPublicKey a PgpObject or return a PgpPublicKeyRing +// case PacketTag.PublicSubkey: +// return PgpPublicKeyRing.ReadSubkey(bcpgIn); + case PacketTag.CompressedData: + return new PgpCompressedData(bcpgIn); + case PacketTag.LiteralData: + return new PgpLiteralData(bcpgIn); + case PacketTag.PublicKeyEncryptedSession: + case PacketTag.SymmetricKeyEncryptedSessionKey: + return new PgpEncryptedDataList(bcpgIn); + case PacketTag.OnePassSignature: + { + IList l = Platform.CreateArrayList(); + + while (bcpgIn.NextPacketTag() == PacketTag.OnePassSignature) + { + try + { + l.Add(new PgpOnePassSignature(bcpgIn)); + } + catch (PgpException e) + { + throw new IOException("can't create one pass signature object: " + e); + } + } + + PgpOnePassSignature[] sigs = new PgpOnePassSignature[l.Count]; + for (int i = 0; i < l.Count; ++i) + { + sigs[i] = (PgpOnePassSignature)l[i]; + } + return new PgpOnePassSignatureList(sigs); + } + case PacketTag.Marker: + return new PgpMarker(bcpgIn); + case PacketTag.Experimental1: + case PacketTag.Experimental2: + case PacketTag.Experimental3: + case PacketTag.Experimental4: + return new PgpExperimental(bcpgIn); + } + + throw new IOException("unknown object in stream " + bcpgIn.NextPacketTag()); + } + + [Obsolete("Use NextPgpObject() instead")] + public object NextObject() + { + return NextPgpObject(); + } + + /// + /// Return all available objects in a list. + /// + /// An IList containing all objects from this factory, in order. + public IList AllPgpObjects() + { + IList result = Platform.CreateArrayList(); + PgpObject pgpObject; + while ((pgpObject = NextPgpObject()) != null) + { + result.Add(pgpObject); + } + return result; + } + } +} diff --git a/bc-sharp-crypto/src/openpgp/PgpOnePassSignature.cs b/bc-sharp-crypto/src/openpgp/PgpOnePassSignature.cs new file mode 100644 index 0000000..68fc599 --- /dev/null +++ b/bc-sharp-crypto/src/openpgp/PgpOnePassSignature.cs @@ -0,0 +1,179 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + /// A one pass signature object. + public class PgpOnePassSignature + { + private OnePassSignaturePacket sigPack; + private int signatureType; + private ISigner sig; + private byte lastb; + + internal PgpOnePassSignature( + BcpgInputStream bcpgInput) + : this((OnePassSignaturePacket) bcpgInput.ReadPacket()) + { + } + + internal PgpOnePassSignature( + OnePassSignaturePacket sigPack) + { + this.sigPack = sigPack; + this.signatureType = sigPack.SignatureType; + } + + /// Initialise the signature object for verification. + public void InitVerify( + PgpPublicKey pubKey) + { + lastb = 0; + + try + { + sig = SignerUtilities.GetSigner( + PgpUtilities.GetSignatureName(sigPack.KeyAlgorithm, sigPack.HashAlgorithm)); + } + catch (Exception e) + { + throw new PgpException("can't set up signature object.", e); + } + + try + { + sig.Init(false, pubKey.GetKey()); + } + catch (InvalidKeyException e) + { + throw new PgpException("invalid key.", e); + } + } + + public void Update( + byte b) + { + if (signatureType == PgpSignature.CanonicalTextDocument) + { + doCanonicalUpdateByte(b); + } + else + { + sig.Update(b); + } + } + + private void doCanonicalUpdateByte( + byte b) + { + if (b == '\r') + { + doUpdateCRLF(); + } + else if (b == '\n') + { + if (lastb != '\r') + { + doUpdateCRLF(); + } + } + else + { + sig.Update(b); + } + + lastb = b; + } + + private void doUpdateCRLF() + { + sig.Update((byte)'\r'); + sig.Update((byte)'\n'); + } + + public void Update( + byte[] bytes) + { + if (signatureType == PgpSignature.CanonicalTextDocument) + { + for (int i = 0; i != bytes.Length; i++) + { + doCanonicalUpdateByte(bytes[i]); + } + } + else + { + sig.BlockUpdate(bytes, 0, bytes.Length); + } + } + + public void Update( + byte[] bytes, + int off, + int length) + { + if (signatureType == PgpSignature.CanonicalTextDocument) + { + int finish = off + length; + + for (int i = off; i != finish; i++) + { + doCanonicalUpdateByte(bytes[i]); + } + } + else + { + sig.BlockUpdate(bytes, off, length); + } + } + + /// Verify the calculated signature against the passed in PgpSignature. + public bool Verify( + PgpSignature pgpSig) + { + byte[] trailer = pgpSig.GetSignatureTrailer(); + + sig.BlockUpdate(trailer, 0, trailer.Length); + + return sig.VerifySignature(pgpSig.GetSignature()); + } + + public long KeyId + { + get { return sigPack.KeyId; } + } + + public int SignatureType + { + get { return sigPack.SignatureType; } + } + + public HashAlgorithmTag HashAlgorithm + { + get { return sigPack.HashAlgorithm; } + } + + public PublicKeyAlgorithmTag KeyAlgorithm + { + get { return sigPack.KeyAlgorithm; } + } + + public byte[] GetEncoded() + { + MemoryStream bOut = new MemoryStream(); + + Encode(bOut); + + return bOut.ToArray(); + } + + public void Encode( + Stream outStr) + { + BcpgOutputStream.Wrap(outStr).WritePacket(sigPack); + } + } +} diff --git a/bc-sharp-crypto/src/openpgp/PgpOnePassSignatureList.cs b/bc-sharp-crypto/src/openpgp/PgpOnePassSignatureList.cs new file mode 100644 index 0000000..37c4288 --- /dev/null +++ b/bc-sharp-crypto/src/openpgp/PgpOnePassSignatureList.cs @@ -0,0 +1,51 @@ +using System; + +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + /// Holder for a list of PgpOnePassSignature objects. + public class PgpOnePassSignatureList + : PgpObject + { + private readonly PgpOnePassSignature[] sigs; + + public PgpOnePassSignatureList( + PgpOnePassSignature[] sigs) + { + this.sigs = (PgpOnePassSignature[]) sigs.Clone(); + } + + public PgpOnePassSignatureList( + PgpOnePassSignature sig) + { + this.sigs = new PgpOnePassSignature[]{ sig }; + } + + public PgpOnePassSignature this[int index] + { + get { return sigs[index]; } + } + + [Obsolete("Use 'object[index]' syntax instead")] + public PgpOnePassSignature Get( + int index) + { + return this[index]; + } + + [Obsolete("Use 'Count' property instead")] + public int Size + { + get { return sigs.Length; } + } + + public int Count + { + get { return sigs.Length; } + } + + public bool IsEmpty + { + get { return (sigs.Length == 0); } + } + } +} diff --git a/bc-sharp-crypto/src/openpgp/PgpPad.cs b/bc-sharp-crypto/src/openpgp/PgpPad.cs new file mode 100644 index 0000000..48f7f2f --- /dev/null +++ b/bc-sharp-crypto/src/openpgp/PgpPad.cs @@ -0,0 +1,45 @@ +using System; + +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + /// Padding functions. + public sealed class PgpPad + { + private PgpPad() + { + } + + public static byte[] PadSessionData(byte[] sessionInfo) + { + byte[] result = new byte[40]; + + Array.Copy(sessionInfo, 0, result, 0, sessionInfo.Length); + + byte padValue = (byte)(result.Length - sessionInfo.Length); + + for (int i = sessionInfo.Length; i != result.Length; i++) + { + result[i] = padValue; + } + + return result; + } + + public static byte[] UnpadSessionData(byte[] encoded) + { + byte padValue = encoded[encoded.Length - 1]; + + for (int i = encoded.Length - padValue; i != encoded.Length; i++) + { + if (encoded[i] != padValue) + throw new PgpException("bad padding found in session data"); + } + + byte[] taggedKey = new byte[encoded.Length - padValue]; + + Array.Copy(encoded, 0, taggedKey, 0, taggedKey.Length); + + return taggedKey; + } + } +} diff --git a/bc-sharp-crypto/src/openpgp/PgpPbeEncryptedData.cs b/bc-sharp-crypto/src/openpgp/PgpPbeEncryptedData.cs new file mode 100644 index 0000000..f43f2f5 --- /dev/null +++ b/bc-sharp-crypto/src/openpgp/PgpPbeEncryptedData.cs @@ -0,0 +1,160 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.IO; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + /// A password based encryption object. + public class PgpPbeEncryptedData + : PgpEncryptedData + { + private readonly SymmetricKeyEncSessionPacket keyData; + + internal PgpPbeEncryptedData( + SymmetricKeyEncSessionPacket keyData, + InputStreamPacket encData) + : base(encData) + { + this.keyData = keyData; + } + + /// Return the raw input stream for the data stream. + public override Stream GetInputStream() + { + return encData.GetInputStream(); + } + + /// Return the decrypted input stream, using the passed in passphrase. + /// + /// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is + /// the historical behaviour of the library (1.7 and earlier). + /// + public Stream GetDataStream(char[] passPhrase) + { + return DoGetDataStream(PgpUtilities.EncodePassPhrase(passPhrase, false), true); + } + + /// Return the decrypted input stream, using the passed in passphrase. + /// + /// The passphrase is encoded to bytes using UTF8 (Encoding.UTF8.GetBytes). + /// + public Stream GetDataStreamUtf8(char[] passPhrase) + { + return DoGetDataStream(PgpUtilities.EncodePassPhrase(passPhrase, true), true); + } + + /// Return the decrypted input stream, using the passed in passphrase. + /// + /// Allows the caller to handle the encoding of the passphrase to bytes. + /// + public Stream GetDataStreamRaw(byte[] rawPassPhrase) + { + return DoGetDataStream(rawPassPhrase, false); + } + + internal Stream DoGetDataStream(byte[] rawPassPhrase, bool clearPassPhrase) + { + try + { + SymmetricKeyAlgorithmTag keyAlgorithm = keyData.EncAlgorithm; + + KeyParameter key = PgpUtilities.DoMakeKeyFromPassPhrase( + keyAlgorithm, keyData.S2k, rawPassPhrase, clearPassPhrase); + + byte[] secKeyData = keyData.GetSecKeyData(); + if (secKeyData != null && secKeyData.Length > 0) + { + IBufferedCipher keyCipher = CipherUtilities.GetCipher( + PgpUtilities.GetSymmetricCipherName(keyAlgorithm) + "/CFB/NoPadding"); + + keyCipher.Init(false, + new ParametersWithIV(key, new byte[keyCipher.GetBlockSize()])); + + byte[] keyBytes = keyCipher.DoFinal(secKeyData); + + keyAlgorithm = (SymmetricKeyAlgorithmTag) keyBytes[0]; + + key = ParameterUtilities.CreateKeyParameter( + PgpUtilities.GetSymmetricCipherName(keyAlgorithm), + keyBytes, 1, keyBytes.Length - 1); + } + + + IBufferedCipher c = CreateStreamCipher(keyAlgorithm); + + byte[] iv = new byte[c.GetBlockSize()]; + + c.Init(false, new ParametersWithIV(key, iv)); + + encStream = BcpgInputStream.Wrap(new CipherStream(encData.GetInputStream(), c, null)); + + if (encData is SymmetricEncIntegrityPacket) + { + truncStream = new TruncatedStream(encStream); + + string digestName = PgpUtilities.GetDigestName(HashAlgorithmTag.Sha1); + IDigest digest = DigestUtilities.GetDigest(digestName); + + encStream = new DigestStream(truncStream, digest, null); + } + + if (Streams.ReadFully(encStream, iv, 0, iv.Length) < iv.Length) + throw new EndOfStreamException("unexpected end of stream."); + + int v1 = encStream.ReadByte(); + int v2 = encStream.ReadByte(); + + if (v1 < 0 || v2 < 0) + throw new EndOfStreamException("unexpected end of stream."); + + + // Note: the oracle attack on the "quick check" bytes is not deemed + // a security risk for PBE (see PgpPublicKeyEncryptedData) + + bool repeatCheckPassed = + iv[iv.Length - 2] == (byte)v1 + && iv[iv.Length - 1] == (byte)v2; + + // Note: some versions of PGP appear to produce 0 for the extra + // bytes rather than repeating the two previous bytes + bool zeroesCheckPassed = + v1 == 0 + && v2 == 0; + + if (!repeatCheckPassed && !zeroesCheckPassed) + { + throw new PgpDataValidationException("quick check failed."); + } + + + return encStream; + } + catch (PgpException e) + { + throw e; + } + catch (Exception e) + { + throw new PgpException("Exception creating cipher", e); + } + } + + private IBufferedCipher CreateStreamCipher( + SymmetricKeyAlgorithmTag keyAlgorithm) + { + string mode = (encData is SymmetricEncIntegrityPacket) + ? "CFB" + : "OpenPGPCFB"; + + string cName = PgpUtilities.GetSymmetricCipherName(keyAlgorithm) + + "/" + mode + "/NoPadding"; + + return CipherUtilities.GetCipher(cName); + } + } +} diff --git a/bc-sharp-crypto/src/openpgp/PgpPrivateKey.cs b/bc-sharp-crypto/src/openpgp/PgpPrivateKey.cs new file mode 100644 index 0000000..61487a5 --- /dev/null +++ b/bc-sharp-crypto/src/openpgp/PgpPrivateKey.cs @@ -0,0 +1,51 @@ +using System; + +using Org.BouncyCastle.Crypto; + +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + /// General class to contain a private key for use with other OpenPGP objects. + public class PgpPrivateKey + { + private readonly long keyID; + private readonly PublicKeyPacket publicKeyPacket; + private readonly AsymmetricKeyParameter privateKey; + + /// + /// Create a PgpPrivateKey from a keyID, the associated public data packet, and a regular private key. + /// + /// ID of the corresponding public key. + /// the public key data packet to be associated with this private key. + /// the private key data packet to be associated with this private key. + public PgpPrivateKey( + long keyID, + PublicKeyPacket publicKeyPacket, + AsymmetricKeyParameter privateKey) + { + if (!privateKey.IsPrivate) + throw new ArgumentException("Expected a private key", "privateKey"); + + this.keyID = keyID; + this.publicKeyPacket = publicKeyPacket; + this.privateKey = privateKey; + } + + /// The keyId associated with the contained private key. + public long KeyId + { + get { return keyID; } + } + + /// The public key packet associated with this private key, if available. + public PublicKeyPacket PublicKeyPacket + { + get { return publicKeyPacket; } + } + + /// The contained private key. + public AsymmetricKeyParameter Key + { + get { return privateKey; } + } + } +} diff --git a/bc-sharp-crypto/src/openpgp/PgpPublicKey.cs b/bc-sharp-crypto/src/openpgp/PgpPublicKey.cs new file mode 100644 index 0000000..fc125e8 --- /dev/null +++ b/bc-sharp-crypto/src/openpgp/PgpPublicKey.cs @@ -0,0 +1,980 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1.Sec; +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.IO; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Math.EC; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; + +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + /// General class to handle a PGP public key object. + public class PgpPublicKey + { + public static byte[] CalculateFingerprint(PublicKeyPacket publicPk) + { + IBcpgKey key = publicPk.Key; + IDigest digest; + + if (publicPk.Version <= 3) + { + RsaPublicBcpgKey rK = (RsaPublicBcpgKey)key; + + try + { + digest = DigestUtilities.GetDigest("MD5"); + UpdateDigest(digest, rK.Modulus); + UpdateDigest(digest, rK.PublicExponent); + } + catch (Exception e) + { + throw new PgpException("can't encode key components: " + e.Message, e); + } + } + else + { + try + { + byte[] kBytes = publicPk.GetEncodedContents(); + + digest = DigestUtilities.GetDigest("SHA1"); + + digest.Update(0x99); + digest.Update((byte)(kBytes.Length >> 8)); + digest.Update((byte)kBytes.Length); + digest.BlockUpdate(kBytes, 0, kBytes.Length); + } + catch (Exception e) + { + throw new PgpException("can't encode key components: " + e.Message, e); + } + } + + return DigestUtilities.DoFinal(digest); + } + + private static void UpdateDigest(IDigest d, BigInteger b) + { + byte[] bytes = b.ToByteArrayUnsigned(); + d.BlockUpdate(bytes, 0, bytes.Length); + } + + private static readonly int[] MasterKeyCertificationTypes = new int[] + { + PgpSignature.PositiveCertification, + PgpSignature.CasualCertification, + PgpSignature.NoCertification, + PgpSignature.DefaultCertification + }; + + private long keyId; + private byte[] fingerprint; + private int keyStrength; + + internal PublicKeyPacket publicPk; + internal TrustPacket trustPk; + internal IList keySigs = Platform.CreateArrayList(); + internal IList ids = Platform.CreateArrayList(); + internal IList idTrusts = Platform.CreateArrayList(); + internal IList idSigs = Platform.CreateArrayList(); + internal IList subSigs; + + private void Init() + { + IBcpgKey key = publicPk.Key; + + this.fingerprint = CalculateFingerprint(publicPk); + + if (publicPk.Version <= 3) + { + RsaPublicBcpgKey rK = (RsaPublicBcpgKey) key; + + this.keyId = rK.Modulus.LongValue; + this.keyStrength = rK.Modulus.BitLength; + } + else + { + this.keyId = (long)(((ulong)fingerprint[fingerprint.Length - 8] << 56) + | ((ulong)fingerprint[fingerprint.Length - 7] << 48) + | ((ulong)fingerprint[fingerprint.Length - 6] << 40) + | ((ulong)fingerprint[fingerprint.Length - 5] << 32) + | ((ulong)fingerprint[fingerprint.Length - 4] << 24) + | ((ulong)fingerprint[fingerprint.Length - 3] << 16) + | ((ulong)fingerprint[fingerprint.Length - 2] << 8) + | (ulong)fingerprint[fingerprint.Length - 1]); + + if (key is RsaPublicBcpgKey) + { + this.keyStrength = ((RsaPublicBcpgKey)key).Modulus.BitLength; + } + else if (key is DsaPublicBcpgKey) + { + this.keyStrength = ((DsaPublicBcpgKey)key).P.BitLength; + } + else if (key is ElGamalPublicBcpgKey) + { + this.keyStrength = ((ElGamalPublicBcpgKey)key).P.BitLength; + } + else if (key is ECPublicBcpgKey) + { + this.keyStrength = ECKeyPairGenerator.FindECCurveByOid(((ECPublicBcpgKey)key).CurveOid).Curve.FieldSize; + } + } + } + + /// + /// Create a PgpPublicKey from the passed in lightweight one. + /// + /// + /// Note: the time passed in affects the value of the key's keyId, so you probably only want + /// to do this once for a lightweight key, or make sure you keep track of the time you used. + /// + /// Asymmetric algorithm type representing the public key. + /// Actual public key to associate. + /// Date of creation. + /// If pubKey is not public. + /// On key creation problem. + public PgpPublicKey( + PublicKeyAlgorithmTag algorithm, + AsymmetricKeyParameter pubKey, + DateTime time) + { + if (pubKey.IsPrivate) + throw new ArgumentException("Expected a public key", "pubKey"); + + IBcpgKey bcpgKey; + if (pubKey is RsaKeyParameters) + { + RsaKeyParameters rK = (RsaKeyParameters) pubKey; + + bcpgKey = new RsaPublicBcpgKey(rK.Modulus, rK.Exponent); + } + else if (pubKey is DsaPublicKeyParameters) + { + DsaPublicKeyParameters dK = (DsaPublicKeyParameters) pubKey; + DsaParameters dP = dK.Parameters; + + bcpgKey = new DsaPublicBcpgKey(dP.P, dP.Q, dP.G, dK.Y); + } + else if (pubKey is ECPublicKeyParameters) + { + ECPublicKeyParameters ecK = (ECPublicKeyParameters)pubKey; + + if (algorithm == PublicKeyAlgorithmTag.ECDH) + { + bcpgKey = new ECDHPublicBcpgKey(ecK.PublicKeyParamSet, ecK.Q, HashAlgorithmTag.Sha256, SymmetricKeyAlgorithmTag.Aes128); + } + else if (algorithm == PublicKeyAlgorithmTag.ECDsa) + { + bcpgKey = new ECDsaPublicBcpgKey(ecK.PublicKeyParamSet, ecK.Q); + } + else + { + throw new PgpException("unknown EC algorithm"); + } + } + else if (pubKey is ElGamalPublicKeyParameters) + { + ElGamalPublicKeyParameters eK = (ElGamalPublicKeyParameters) pubKey; + ElGamalParameters eS = eK.Parameters; + + bcpgKey = new ElGamalPublicBcpgKey(eS.P, eS.G, eK.Y); + } + else + { + throw new PgpException("unknown key class"); + } + + this.publicPk = new PublicKeyPacket(algorithm, time, bcpgKey); + this.ids = Platform.CreateArrayList(); + this.idSigs = Platform.CreateArrayList(); + + try + { + Init(); + } + catch (IOException e) + { + throw new PgpException("exception calculating keyId", e); + } + } + + public PgpPublicKey(PublicKeyPacket publicPk) + : this(publicPk, Platform.CreateArrayList(), Platform.CreateArrayList()) + { + } + + /// Constructor for a sub-key. + internal PgpPublicKey( + PublicKeyPacket publicPk, + TrustPacket trustPk, + IList sigs) + { + this.publicPk = publicPk; + this.trustPk = trustPk; + this.subSigs = sigs; + + Init(); + } + + internal PgpPublicKey( + PgpPublicKey key, + TrustPacket trust, + IList subSigs) + { + this.publicPk = key.publicPk; + this.trustPk = trust; + this.subSigs = subSigs; + + this.fingerprint = key.fingerprint; + this.keyId = key.keyId; + this.keyStrength = key.keyStrength; + } + + /// Copy constructor. + /// The public key to copy. + internal PgpPublicKey( + PgpPublicKey pubKey) + { + this.publicPk = pubKey.publicPk; + + this.keySigs = Platform.CreateArrayList(pubKey.keySigs); + this.ids = Platform.CreateArrayList(pubKey.ids); + this.idTrusts = Platform.CreateArrayList(pubKey.idTrusts); + this.idSigs = Platform.CreateArrayList(pubKey.idSigs.Count); + for (int i = 0; i != pubKey.idSigs.Count; i++) + { + this.idSigs.Add(Platform.CreateArrayList((IList)pubKey.idSigs[i])); + } + + if (pubKey.subSigs != null) + { + this.subSigs = Platform.CreateArrayList(pubKey.subSigs.Count); + for (int i = 0; i != pubKey.subSigs.Count; i++) + { + this.subSigs.Add(pubKey.subSigs[i]); + } + } + + this.fingerprint = pubKey.fingerprint; + this.keyId = pubKey.keyId; + this.keyStrength = pubKey.keyStrength; + } + + internal PgpPublicKey( + PublicKeyPacket publicPk, + TrustPacket trustPk, + IList keySigs, + IList ids, + IList idTrusts, + IList idSigs) + { + this.publicPk = publicPk; + this.trustPk = trustPk; + this.keySigs = keySigs; + this.ids = ids; + this.idTrusts = idTrusts; + this.idSigs = idSigs; + + Init(); + } + + internal PgpPublicKey( + PublicKeyPacket publicPk, + IList ids, + IList idSigs) + { + this.publicPk = publicPk; + this.ids = ids; + this.idSigs = idSigs; + Init(); + } + + /// The version of this key. + public int Version + { + get { return publicPk.Version; } + } + + /// The creation time of this key. + public DateTime CreationTime + { + get { return publicPk.GetTime(); } + } + + /// The number of valid days from creation time - zero means no expiry. + /// WARNING: This method will return 1 for keys with version > 3 that expire in less than 1 day + [Obsolete("Use 'GetValidSeconds' instead")] + public int ValidDays + { + get + { + if (publicPk.Version <= 3) + { + return publicPk.ValidDays; + } + + long expSecs = GetValidSeconds(); + if (expSecs <= 0) + return 0; + + int days = (int)(expSecs / (24 * 60 * 60)); + return System.Math.Max(1, days); + } + } + + /// Return the trust data associated with the public key, if present. + /// A byte array with trust data, null otherwise. + public byte[] GetTrustData() + { + if (trustPk == null) + { + return null; + } + + return Arrays.Clone(trustPk.GetLevelAndTrustAmount()); + } + + /// The number of valid seconds from creation time - zero means no expiry. + public long GetValidSeconds() + { + if (publicPk.Version <= 3) + { + return (long)publicPk.ValidDays * (24 * 60 * 60); + } + + if (IsMasterKey) + { + for (int i = 0; i != MasterKeyCertificationTypes.Length; i++) + { + long seconds = GetExpirationTimeFromSig(true, MasterKeyCertificationTypes[i]); + if (seconds >= 0) + { + return seconds; + } + } + } + else + { + long seconds = GetExpirationTimeFromSig(false, PgpSignature.SubkeyBinding); + if (seconds >= 0) + { + return seconds; + } + } + + return 0; + } + + private long GetExpirationTimeFromSig(bool selfSigned, int signatureType) + { + long expiryTime = -1; + long lastDate = -1; + + foreach (PgpSignature sig in GetSignaturesOfType(signatureType)) + { + if (selfSigned && sig.KeyId != this.KeyId) + continue; + + PgpSignatureSubpacketVector hashed = sig.GetHashedSubPackets(); + if (hashed == null) + continue; + + long current = hashed.GetKeyExpirationTime(); + + if (sig.KeyId == this.KeyId) + { + if (sig.CreationTime.Ticks > lastDate) + { + lastDate = sig.CreationTime.Ticks; + expiryTime = current; + } + } + else if (current == 0 || current > expiryTime) + { + expiryTime = current; + } + } + + return expiryTime; + } + + /// The keyId associated with the public key. + public long KeyId + { + get { return keyId; } + } + + /// The fingerprint of the key + public byte[] GetFingerprint() + { + return (byte[]) fingerprint.Clone(); + } + + /// + /// Check if this key has an algorithm type that makes it suitable to use for encryption. + /// + /// + /// Note: with version 4 keys KeyFlags subpackets should also be considered when present for + /// determining the preferred use of the key. + /// + /// + /// true if this key algorithm is suitable for encryption. + /// + public bool IsEncryptionKey + { + get + { + switch (publicPk.Algorithm) + { + case PublicKeyAlgorithmTag.ECDH: + case PublicKeyAlgorithmTag.ElGamalEncrypt: + case PublicKeyAlgorithmTag.ElGamalGeneral: + case PublicKeyAlgorithmTag.RsaEncrypt: + case PublicKeyAlgorithmTag.RsaGeneral: + return true; + default: + return false; + } + } + } + + /// True, if this is a master key. + public bool IsMasterKey + { + get { return subSigs == null; } + } + + /// The algorithm code associated with the public key. + public PublicKeyAlgorithmTag Algorithm + { + get { return publicPk.Algorithm; } + } + + /// The strength of the key in bits. + public int BitStrength + { + get { return keyStrength; } + } + + /// The public key contained in the object. + /// A lightweight public key. + /// If the key algorithm is not recognised. + public AsymmetricKeyParameter GetKey() + { + try + { + switch (publicPk.Algorithm) + { + case PublicKeyAlgorithmTag.RsaEncrypt: + case PublicKeyAlgorithmTag.RsaGeneral: + case PublicKeyAlgorithmTag.RsaSign: + RsaPublicBcpgKey rsaK = (RsaPublicBcpgKey)publicPk.Key; + return new RsaKeyParameters(false, rsaK.Modulus, rsaK.PublicExponent); + case PublicKeyAlgorithmTag.Dsa: + DsaPublicBcpgKey dsaK = (DsaPublicBcpgKey)publicPk.Key; + return new DsaPublicKeyParameters(dsaK.Y, new DsaParameters(dsaK.P, dsaK.Q, dsaK.G)); + case PublicKeyAlgorithmTag.ECDsa: + return GetECKey("ECDSA"); + case PublicKeyAlgorithmTag.ECDH: + return GetECKey("ECDH"); + case PublicKeyAlgorithmTag.ElGamalEncrypt: + case PublicKeyAlgorithmTag.ElGamalGeneral: + ElGamalPublicBcpgKey elK = (ElGamalPublicBcpgKey)publicPk.Key; + return new ElGamalPublicKeyParameters(elK.Y, new ElGamalParameters(elK.P, elK.G)); + default: + throw new PgpException("unknown public key algorithm encountered"); + } + } + catch (PgpException e) + { + throw e; + } + catch (Exception e) + { + throw new PgpException("exception constructing public key", e); + } + } + + private ECPublicKeyParameters GetECKey(string algorithm) + { + ECPublicBcpgKey ecK = (ECPublicBcpgKey)publicPk.Key; + X9ECParameters x9 = ECKeyPairGenerator.FindECCurveByOid(ecK.CurveOid); + ECPoint q = x9.Curve.DecodePoint(BigIntegers.AsUnsignedByteArray(ecK.EncodedPoint)); + return new ECPublicKeyParameters(algorithm, q, ecK.CurveOid); + } + + /// Allows enumeration of any user IDs associated with the key. + /// An IEnumerable of string objects. + public IEnumerable GetUserIds() + { + IList temp = Platform.CreateArrayList(); + + foreach (object o in ids) + { + if (o is string) + { + temp.Add(o); + } + } + + return new EnumerableProxy(temp); + } + + /// Allows enumeration of any user attribute vectors associated with the key. + /// An IEnumerable of PgpUserAttributeSubpacketVector objects. + public IEnumerable GetUserAttributes() + { + IList temp = Platform.CreateArrayList(); + + foreach (object o in ids) + { + if (o is PgpUserAttributeSubpacketVector) + { + temp.Add(o); + } + } + + return new EnumerableProxy(temp); + } + + /// Allows enumeration of any signatures associated with the passed in id. + /// The ID to be matched. + /// An IEnumerable of PgpSignature objects. + public IEnumerable GetSignaturesForId( + string id) + { + if (id == null) + throw new ArgumentNullException("id"); + + for (int i = 0; i != ids.Count; i++) + { + if (id.Equals(ids[i])) + { + return new EnumerableProxy((IList)idSigs[i]); + } + } + + return null; + } + + /// Allows enumeration of signatures associated with the passed in user attributes. + /// The vector of user attributes to be matched. + /// An IEnumerable of PgpSignature objects. + public IEnumerable GetSignaturesForUserAttribute( + PgpUserAttributeSubpacketVector userAttributes) + { + for (int i = 0; i != ids.Count; i++) + { + if (userAttributes.Equals(ids[i])) + { + return new EnumerableProxy((IList) idSigs[i]); + } + } + + return null; + } + + /// Allows enumeration of signatures of the passed in type that are on this key. + /// The type of the signature to be returned. + /// An IEnumerable of PgpSignature objects. + public IEnumerable GetSignaturesOfType( + int signatureType) + { + IList temp = Platform.CreateArrayList(); + + foreach (PgpSignature sig in GetSignatures()) + { + if (sig.SignatureType == signatureType) + { + temp.Add(sig); + } + } + + return new EnumerableProxy(temp); + } + + /// Allows enumeration of all signatures/certifications associated with this key. + /// An IEnumerable with all signatures/certifications. + public IEnumerable GetSignatures() + { + IList sigs = subSigs; + if (sigs == null) + { + sigs = Platform.CreateArrayList(keySigs); + + foreach (ICollection extraSigs in idSigs) + { + CollectionUtilities.AddRange(sigs, extraSigs); + } + } + + return new EnumerableProxy(sigs); + } + + /** + * Return all signatures/certifications directly associated with this key (ie, not to a user id). + * + * @return an iterator (possibly empty) with all signatures/certifications. + */ + public IEnumerable GetKeySignatures() + { + IList sigs = subSigs; + if (sigs == null) + { + sigs = Platform.CreateArrayList(keySigs); + } + return new EnumerableProxy(sigs); + } + + public PublicKeyPacket PublicKeyPacket + { + get { return publicPk; } + } + + public byte[] GetEncoded() + { + MemoryStream bOut = new MemoryStream(); + Encode(bOut); + return bOut.ToArray(); + } + + public void Encode( + Stream outStr) + { + BcpgOutputStream bcpgOut = BcpgOutputStream.Wrap(outStr); + + bcpgOut.WritePacket(publicPk); + if (trustPk != null) + { + bcpgOut.WritePacket(trustPk); + } + + if (subSigs == null) // not a sub-key + { + foreach (PgpSignature keySig in keySigs) + { + keySig.Encode(bcpgOut); + } + + for (int i = 0; i != ids.Count; i++) + { + if (ids[i] is string) + { + string id = (string) ids[i]; + + bcpgOut.WritePacket(new UserIdPacket(id)); + } + else + { + PgpUserAttributeSubpacketVector v = (PgpUserAttributeSubpacketVector)ids[i]; + bcpgOut.WritePacket(new UserAttributePacket(v.ToSubpacketArray())); + } + + if (idTrusts[i] != null) + { + bcpgOut.WritePacket((ContainedPacket)idTrusts[i]); + } + + foreach (PgpSignature sig in (IList) idSigs[i]) + { + sig.Encode(bcpgOut); + } + } + } + else + { + foreach (PgpSignature subSig in subSigs) + { + subSig.Encode(bcpgOut); + } + } + } + + /// Check whether this (sub)key has a revocation signature on it. + /// True, if this (sub)key has been revoked. + public bool IsRevoked() + { + int ns = 0; + bool revoked = false; + if (IsMasterKey) // Master key + { + while (!revoked && (ns < keySigs.Count)) + { + if (((PgpSignature)keySigs[ns++]).SignatureType == PgpSignature.KeyRevocation) + { + revoked = true; + } + } + } + else // Sub-key + { + while (!revoked && (ns < subSigs.Count)) + { + if (((PgpSignature)subSigs[ns++]).SignatureType == PgpSignature.SubkeyRevocation) + { + revoked = true; + } + } + } + return revoked; + } + + /// Add a certification for an id to the given public key. + /// The key the certification is to be added to. + /// The ID the certification is associated with. + /// The new certification. + /// The re-certified key. + public static PgpPublicKey AddCertification( + PgpPublicKey key, + string id, + PgpSignature certification) + { + return AddCert(key, id, certification); + } + + /// Add a certification for the given UserAttributeSubpackets to the given public key. + /// The key the certification is to be added to. + /// The attributes the certification is associated with. + /// The new certification. + /// The re-certified key. + public static PgpPublicKey AddCertification( + PgpPublicKey key, + PgpUserAttributeSubpacketVector userAttributes, + PgpSignature certification) + { + return AddCert(key, userAttributes, certification); + } + + private static PgpPublicKey AddCert( + PgpPublicKey key, + object id, + PgpSignature certification) + { + PgpPublicKey returnKey = new PgpPublicKey(key); + IList sigList = null; + + for (int i = 0; i != returnKey.ids.Count; i++) + { + if (id.Equals(returnKey.ids[i])) + { + sigList = (IList) returnKey.idSigs[i]; + } + } + + if (sigList != null) + { + sigList.Add(certification); + } + else + { + sigList = Platform.CreateArrayList(); + sigList.Add(certification); + returnKey.ids.Add(id); + returnKey.idTrusts.Add(null); + returnKey.idSigs.Add(sigList); + } + + return returnKey; + } + + /// + /// Remove any certifications associated with a user attribute subpacket on a key. + /// + /// The key the certifications are to be removed from. + /// The attributes to be removed. + /// + /// The re-certified key, or null if the user attribute subpacket was not found on the key. + /// + public static PgpPublicKey RemoveCertification( + PgpPublicKey key, + PgpUserAttributeSubpacketVector userAttributes) + { + return RemoveCert(key, userAttributes); + } + + /// Remove any certifications associated with a given ID on a key. + /// The key the certifications are to be removed from. + /// The ID that is to be removed. + /// The re-certified key, or null if the ID was not found on the key. + public static PgpPublicKey RemoveCertification( + PgpPublicKey key, + string id) + { + return RemoveCert(key, id); + } + + private static PgpPublicKey RemoveCert( + PgpPublicKey key, + object id) + { + PgpPublicKey returnKey = new PgpPublicKey(key); + bool found = false; + + for (int i = 0; i < returnKey.ids.Count; i++) + { + if (id.Equals(returnKey.ids[i])) + { + found = true; + returnKey.ids.RemoveAt(i); + returnKey.idTrusts.RemoveAt(i); + returnKey.idSigs.RemoveAt(i); + } + } + + return found ? returnKey : null; + } + + /// Remove a certification associated with a given ID on a key. + /// The key the certifications are to be removed from. + /// The ID that the certfication is to be removed from. + /// The certfication to be removed. + /// The re-certified key, or null if the certification was not found. + public static PgpPublicKey RemoveCertification( + PgpPublicKey key, + string id, + PgpSignature certification) + { + return RemoveCert(key, id, certification); + } + + /// Remove a certification associated with a given user attributes on a key. + /// The key the certifications are to be removed from. + /// The user attributes that the certfication is to be removed from. + /// The certification to be removed. + /// The re-certified key, or null if the certification was not found. + public static PgpPublicKey RemoveCertification( + PgpPublicKey key, + PgpUserAttributeSubpacketVector userAttributes, + PgpSignature certification) + { + return RemoveCert(key, userAttributes, certification); + } + + private static PgpPublicKey RemoveCert( + PgpPublicKey key, + object id, + PgpSignature certification) + { + PgpPublicKey returnKey = new PgpPublicKey(key); + bool found = false; + + for (int i = 0; i < returnKey.ids.Count; i++) + { + if (id.Equals(returnKey.ids[i])) + { + IList certs = (IList) returnKey.idSigs[i]; + found = certs.Contains(certification); + + if (found) + { + certs.Remove(certification); + } + } + } + + return found ? returnKey : null; + } + + /// Add a revocation or some other key certification to a key. + /// The key the revocation is to be added to. + /// The key signature to be added. + /// The new changed public key object. + public static PgpPublicKey AddCertification( + PgpPublicKey key, + PgpSignature certification) + { + if (key.IsMasterKey) + { + if (certification.SignatureType == PgpSignature.SubkeyRevocation) + { + throw new ArgumentException("signature type incorrect for master key revocation."); + } + } + else + { + if (certification.SignatureType == PgpSignature.KeyRevocation) + { + throw new ArgumentException("signature type incorrect for sub-key revocation."); + } + } + + PgpPublicKey returnKey = new PgpPublicKey(key); + + if (returnKey.subSigs != null) + { + returnKey.subSigs.Add(certification); + } + else + { + returnKey.keySigs.Add(certification); + } + + return returnKey; + } + + /// Remove a certification from the key. + /// The key the certifications are to be removed from. + /// The certfication to be removed. + /// The modified key, null if the certification was not found. + public static PgpPublicKey RemoveCertification( + PgpPublicKey key, + PgpSignature certification) + { + PgpPublicKey returnKey = new PgpPublicKey(key); + IList sigs = returnKey.subSigs != null + ? returnKey.subSigs + : returnKey.keySigs; + +// bool found = sigs.Remove(certification); + int pos = sigs.IndexOf(certification); + bool found = pos >= 0; + + if (found) + { + sigs.RemoveAt(pos); + } + else + { + foreach (String id in key.GetUserIds()) + { + foreach (object sig in key.GetSignaturesForId(id)) + { + // TODO Is this the right type of equality test? + if (certification == sig) + { + found = true; + returnKey = PgpPublicKey.RemoveCertification(returnKey, id, certification); + } + } + } + + if (!found) + { + foreach (PgpUserAttributeSubpacketVector id in key.GetUserAttributes()) + { + foreach (object sig in key.GetSignaturesForUserAttribute(id)) + { + // TODO Is this the right type of equality test? + if (certification == sig) + { + found = true; + returnKey = PgpPublicKey.RemoveCertification(returnKey, id, certification); + } + } + } + } + } + + return returnKey; + } + } +} diff --git a/bc-sharp-crypto/src/openpgp/PgpPublicKeyEncryptedData.cs b/bc-sharp-crypto/src/openpgp/PgpPublicKeyEncryptedData.cs new file mode 100644 index 0000000..c2a3511 --- /dev/null +++ b/bc-sharp-crypto/src/openpgp/PgpPublicKeyEncryptedData.cs @@ -0,0 +1,272 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.IO; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Math.EC; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + /// A public key encrypted data object. + public class PgpPublicKeyEncryptedData + : PgpEncryptedData + { + private PublicKeyEncSessionPacket keyData; + + internal PgpPublicKeyEncryptedData( + PublicKeyEncSessionPacket keyData, + InputStreamPacket encData) + : base(encData) + { + this.keyData = keyData; + } + + private static IBufferedCipher GetKeyCipher( + PublicKeyAlgorithmTag algorithm) + { + try + { + switch (algorithm) + { + case PublicKeyAlgorithmTag.RsaEncrypt: + case PublicKeyAlgorithmTag.RsaGeneral: + return CipherUtilities.GetCipher("RSA//PKCS1Padding"); + case PublicKeyAlgorithmTag.ElGamalEncrypt: + case PublicKeyAlgorithmTag.ElGamalGeneral: + return CipherUtilities.GetCipher("ElGamal/ECB/PKCS1Padding"); + default: + throw new PgpException("unknown asymmetric algorithm: " + algorithm); + } + } + catch (PgpException e) + { + throw e; + } + catch (Exception e) + { + throw new PgpException("Exception creating cipher", e); + } + } + + private bool ConfirmCheckSum( + byte[] sessionInfo) + { + int check = 0; + + for (int i = 1; i != sessionInfo.Length - 2; i++) + { + check += sessionInfo[i] & 0xff; + } + + return (sessionInfo[sessionInfo.Length - 2] == (byte)(check >> 8)) + && (sessionInfo[sessionInfo.Length - 1] == (byte)(check)); + } + + /// The key ID for the key used to encrypt the data. + public long KeyId + { + get { return keyData.KeyId; } + } + + /// + /// Return the algorithm code for the symmetric algorithm used to encrypt the data. + /// + public SymmetricKeyAlgorithmTag GetSymmetricAlgorithm( + PgpPrivateKey privKey) + { + byte[] sessionData = RecoverSessionData(privKey); + + return (SymmetricKeyAlgorithmTag)sessionData[0]; + } + + /// Return the decrypted data stream for the packet. + public Stream GetDataStream( + PgpPrivateKey privKey) + { + byte[] sessionData = RecoverSessionData(privKey); + + if (!ConfirmCheckSum(sessionData)) + throw new PgpKeyValidationException("key checksum failed"); + + SymmetricKeyAlgorithmTag symmAlg = (SymmetricKeyAlgorithmTag)sessionData[0]; + if (symmAlg == SymmetricKeyAlgorithmTag.Null) + return encData.GetInputStream(); + + IBufferedCipher cipher; + string cipherName = PgpUtilities.GetSymmetricCipherName(symmAlg); + string cName = cipherName; + + try + { + if (encData is SymmetricEncIntegrityPacket) + { + cName += "/CFB/NoPadding"; + } + else + { + cName += "/OpenPGPCFB/NoPadding"; + } + + cipher = CipherUtilities.GetCipher(cName); + } + catch (PgpException e) + { + throw e; + } + catch (Exception e) + { + throw new PgpException("exception creating cipher", e); + } + + try + { + KeyParameter key = ParameterUtilities.CreateKeyParameter( + cipherName, sessionData, 1, sessionData.Length - 3); + + byte[] iv = new byte[cipher.GetBlockSize()]; + + cipher.Init(false, new ParametersWithIV(key, iv)); + + encStream = BcpgInputStream.Wrap(new CipherStream(encData.GetInputStream(), cipher, null)); + + if (encData is SymmetricEncIntegrityPacket) + { + truncStream = new TruncatedStream(encStream); + + string digestName = PgpUtilities.GetDigestName(HashAlgorithmTag.Sha1); + IDigest digest = DigestUtilities.GetDigest(digestName); + + encStream = new DigestStream(truncStream, digest, null); + } + + if (Streams.ReadFully(encStream, iv, 0, iv.Length) < iv.Length) + throw new EndOfStreamException("unexpected end of stream."); + + int v1 = encStream.ReadByte(); + int v2 = encStream.ReadByte(); + + if (v1 < 0 || v2 < 0) + throw new EndOfStreamException("unexpected end of stream."); + + // Note: the oracle attack on the "quick check" bytes is deemed + // a security risk for typical public key encryption usages, + // therefore we do not perform the check. + +// bool repeatCheckPassed = +// iv[iv.Length - 2] == (byte)v1 +// && iv[iv.Length - 1] == (byte)v2; +// +// // Note: some versions of PGP appear to produce 0 for the extra +// // bytes rather than repeating the two previous bytes +// bool zeroesCheckPassed = +// v1 == 0 +// && v2 == 0; +// +// if (!repeatCheckPassed && !zeroesCheckPassed) +// { +// throw new PgpDataValidationException("quick check failed."); +// } + + return encStream; + } + catch (PgpException e) + { + throw e; + } + catch (Exception e) + { + throw new PgpException("Exception starting decryption", e); + } + } + + private byte[] RecoverSessionData(PgpPrivateKey privKey) + { + byte[][] secKeyData = keyData.GetEncSessionKey(); + + if (keyData.Algorithm == PublicKeyAlgorithmTag.ECDH) + { + ECDHPublicBcpgKey ecKey = (ECDHPublicBcpgKey)privKey.PublicKeyPacket.Key; + X9ECParameters x9Params = ECKeyPairGenerator.FindECCurveByOid(ecKey.CurveOid); + + byte[] enc = secKeyData[0]; + + int pLen = ((((enc[0] & 0xff) << 8) + (enc[1] & 0xff)) + 7) / 8; + byte[] pEnc = new byte[pLen]; + + Array.Copy(enc, 2, pEnc, 0, pLen); + + byte[] keyEnc = new byte[enc[pLen + 2]]; + + Array.Copy(enc, 2 + pLen + 1, keyEnc, 0, keyEnc.Length); + + ECPoint publicPoint = x9Params.Curve.DecodePoint(pEnc); + + ECPrivateKeyParameters privKeyParams = (ECPrivateKeyParameters)privKey.Key; + ECPoint S = publicPoint.Multiply(privKeyParams.D).Normalize(); + + KeyParameter key = new KeyParameter(Rfc6637Utilities.CreateKey(privKey.PublicKeyPacket, S)); + + IWrapper w = PgpUtilities.CreateWrapper(ecKey.SymmetricKeyAlgorithm); + w.Init(false, key); + + return PgpPad.UnpadSessionData(w.Unwrap(keyEnc, 0, keyEnc.Length)); + } + + IBufferedCipher cipher = GetKeyCipher(keyData.Algorithm); + + try + { + cipher.Init(false, privKey.Key); + } + catch (InvalidKeyException e) + { + throw new PgpException("error setting asymmetric cipher", e); + } + + if (keyData.Algorithm == PublicKeyAlgorithmTag.RsaEncrypt + || keyData.Algorithm == PublicKeyAlgorithmTag.RsaGeneral) + { + byte[] bi = secKeyData[0]; + + cipher.ProcessBytes(bi, 2, bi.Length - 2); + } + else + { + ElGamalPrivateKeyParameters k = (ElGamalPrivateKeyParameters)privKey.Key; + int size = (k.Parameters.P.BitLength + 7) / 8; + + ProcessEncodedMpi(cipher, size, secKeyData[0]); + ProcessEncodedMpi(cipher, size, secKeyData[1]); + } + + try + { + return cipher.DoFinal(); + } + catch (Exception e) + { + throw new PgpException("exception decrypting secret key", e); + } + } + + private static void ProcessEncodedMpi(IBufferedCipher cipher, int size, byte[] mpiEnc) + { + if (mpiEnc.Length - 2 > size) // leading Zero? Shouldn't happen but... + { + cipher.ProcessBytes(mpiEnc, 3, mpiEnc.Length - 3); + } + else + { + byte[] tmp = new byte[size]; + Array.Copy(mpiEnc, 2, tmp, tmp.Length - (mpiEnc.Length - 2), mpiEnc.Length - 2); + cipher.ProcessBytes(tmp, 0, tmp.Length); + } + } + } +} diff --git a/bc-sharp-crypto/src/openpgp/PgpPublicKeyRing.cs b/bc-sharp-crypto/src/openpgp/PgpPublicKeyRing.cs new file mode 100644 index 0000000..92464d6 --- /dev/null +++ b/bc-sharp-crypto/src/openpgp/PgpPublicKeyRing.cs @@ -0,0 +1,200 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; + +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + /// + /// Class to hold a single master public key and its subkeys. + ///

    + /// Often PGP keyring files consist of multiple master keys, if you are trying to process + /// or construct one of these you should use the PgpPublicKeyRingBundle class. + ///

    + ///
    + public class PgpPublicKeyRing + : PgpKeyRing + { + private readonly IList keys; + + public PgpPublicKeyRing( + byte[] encoding) + : this(new MemoryStream(encoding, false)) + { + } + + internal PgpPublicKeyRing( + IList pubKeys) + { + this.keys = pubKeys; + } + + public PgpPublicKeyRing( + Stream inputStream) + { + this.keys = Platform.CreateArrayList(); + + BcpgInputStream bcpgInput = BcpgInputStream.Wrap(inputStream); + + PacketTag initialTag = bcpgInput.NextPacketTag(); + if (initialTag != PacketTag.PublicKey && initialTag != PacketTag.PublicSubkey) + { + throw new IOException("public key ring doesn't start with public key tag: " + + "tag 0x" + ((int)initialTag).ToString("X")); + } + + PublicKeyPacket pubPk = (PublicKeyPacket) bcpgInput.ReadPacket(); + TrustPacket trustPk = ReadOptionalTrustPacket(bcpgInput); + + // direct signatures and revocations + IList keySigs = ReadSignaturesAndTrust(bcpgInput); + + IList ids, idTrusts, idSigs; + ReadUserIDs(bcpgInput, out ids, out idTrusts, out idSigs); + + keys.Add(new PgpPublicKey(pubPk, trustPk, keySigs, ids, idTrusts, idSigs)); + + + // Read subkeys + while (bcpgInput.NextPacketTag() == PacketTag.PublicSubkey) + { + keys.Add(ReadSubkey(bcpgInput)); + } + } + + /// Return the first public key in the ring. + public virtual PgpPublicKey GetPublicKey() + { + return (PgpPublicKey) keys[0]; + } + + /// Return the public key referred to by the passed in key ID if it is present. + public virtual PgpPublicKey GetPublicKey( + long keyId) + { + foreach (PgpPublicKey k in keys) + { + if (keyId == k.KeyId) + { + return k; + } + } + + return null; + } + + /// Allows enumeration of all the public keys. + /// An IEnumerable of PgpPublicKey objects. + public virtual IEnumerable GetPublicKeys() + { + return new EnumerableProxy(keys); + } + + public virtual byte[] GetEncoded() + { + MemoryStream bOut = new MemoryStream(); + + Encode(bOut); + + return bOut.ToArray(); + } + + public virtual void Encode( + Stream outStr) + { + if (outStr == null) + throw new ArgumentNullException("outStr"); + + foreach (PgpPublicKey k in keys) + { + k.Encode(outStr); + } + } + + /// + /// Returns a new key ring with the public key passed in either added or + /// replacing an existing one. + /// + /// The public key ring to be modified. + /// The public key to be inserted. + /// A new PgpPublicKeyRing + public static PgpPublicKeyRing InsertPublicKey( + PgpPublicKeyRing pubRing, + PgpPublicKey pubKey) + { + IList keys = Platform.CreateArrayList(pubRing.keys); + bool found = false; + bool masterFound = false; + + for (int i = 0; i != keys.Count; i++) + { + PgpPublicKey key = (PgpPublicKey) keys[i]; + + if (key.KeyId == pubKey.KeyId) + { + found = true; + keys[i] = pubKey; + } + if (key.IsMasterKey) + { + masterFound = true; + } + } + + if (!found) + { + if (pubKey.IsMasterKey) + { + if (masterFound) + throw new ArgumentException("cannot add a master key to a ring that already has one"); + + keys.Insert(0, pubKey); + } + else + { + keys.Add(pubKey); + } + } + + return new PgpPublicKeyRing(keys); + } + + /// Returns a new key ring with the public key passed in removed from the key ring. + /// The public key ring to be modified. + /// The public key to be removed. + /// A new PgpPublicKeyRing, or null if pubKey is not found. + public static PgpPublicKeyRing RemovePublicKey( + PgpPublicKeyRing pubRing, + PgpPublicKey pubKey) + { + IList keys = Platform.CreateArrayList(pubRing.keys); + bool found = false; + + for (int i = 0; i < keys.Count; i++) + { + PgpPublicKey key = (PgpPublicKey) keys[i]; + + if (key.KeyId == pubKey.KeyId) + { + found = true; + keys.RemoveAt(i); + } + } + + return found ? new PgpPublicKeyRing(keys) : null; + } + + internal static PgpPublicKey ReadSubkey(BcpgInputStream bcpgInput) + { + PublicKeyPacket pk = (PublicKeyPacket) bcpgInput.ReadPacket(); + TrustPacket kTrust = ReadOptionalTrustPacket(bcpgInput); + + // PGP 8 actually leaves out the signature. + IList sigList = ReadSignaturesAndTrust(bcpgInput); + + return new PgpPublicKey(pk, kTrust, sigList); + } + } +} diff --git a/bc-sharp-crypto/src/openpgp/PgpPublicKeyRingBundle.cs b/bc-sharp-crypto/src/openpgp/PgpPublicKeyRingBundle.cs new file mode 100644 index 0000000..91113e9 --- /dev/null +++ b/bc-sharp-crypto/src/openpgp/PgpPublicKeyRingBundle.cs @@ -0,0 +1,279 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; + +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + /// + /// Often a PGP key ring file is made up of a succession of master/sub-key key rings. + /// If you want to read an entire public key file in one hit this is the class for you. + /// + public class PgpPublicKeyRingBundle + { + private readonly IDictionary pubRings; + private readonly IList order; + + private PgpPublicKeyRingBundle( + IDictionary pubRings, + IList order) + { + this.pubRings = pubRings; + this.order = order; + } + + public PgpPublicKeyRingBundle( + byte[] encoding) + : this(new MemoryStream(encoding, false)) + { + } + + /// Build a PgpPublicKeyRingBundle from the passed in input stream. + /// Input stream containing data. + /// If a problem parsing the stream occurs. + /// If an object is encountered which isn't a PgpPublicKeyRing. + public PgpPublicKeyRingBundle( + Stream inputStream) + : this(new PgpObjectFactory(inputStream).AllPgpObjects()) + { + } + + public PgpPublicKeyRingBundle( + IEnumerable e) + { + this.pubRings = Platform.CreateHashtable(); + this.order = Platform.CreateArrayList(); + + foreach (object obj in e) + { + PgpPublicKeyRing pgpPub = obj as PgpPublicKeyRing; + + if (pgpPub == null) + { + throw new PgpException(Platform.GetTypeName(obj) + " found where PgpPublicKeyRing expected"); + } + + long key = pgpPub.GetPublicKey().KeyId; + pubRings.Add(key, pgpPub); + order.Add(key); + } + } + + [Obsolete("Use 'Count' property instead")] + public int Size + { + get { return order.Count; } + } + + /// Return the number of key rings in this collection. + public int Count + { + get { return order.Count; } + } + + /// Allow enumeration of the public key rings making up this collection. + public IEnumerable GetKeyRings() + { + return new EnumerableProxy(pubRings.Values); + } + + /// Allow enumeration of the key rings associated with the passed in userId. + /// The user ID to be matched. + /// An IEnumerable of key rings which matched (possibly none). + public IEnumerable GetKeyRings( + string userId) + { + return GetKeyRings(userId, false, false); + } + + /// Allow enumeration of the key rings associated with the passed in userId. + /// The user ID to be matched. + /// If true, userId need only be a substring of an actual ID string to match. + /// An IEnumerable of key rings which matched (possibly none). + public IEnumerable GetKeyRings( + string userId, + bool matchPartial) + { + return GetKeyRings(userId, matchPartial, false); + } + + /// Allow enumeration of the key rings associated with the passed in userId. + /// The user ID to be matched. + /// If true, userId need only be a substring of an actual ID string to match. + /// If true, case is ignored in user ID comparisons. + /// An IEnumerable of key rings which matched (possibly none). + public IEnumerable GetKeyRings( + string userId, + bool matchPartial, + bool ignoreCase) + { + IList rings = Platform.CreateArrayList(); + + if (ignoreCase) + { + userId = Platform.ToUpperInvariant(userId); + } + + foreach (PgpPublicKeyRing pubRing in GetKeyRings()) + { + foreach (string nextUserID in pubRing.GetPublicKey().GetUserIds()) + { + string next = nextUserID; + if (ignoreCase) + { + next = Platform.ToUpperInvariant(next); + } + + if (matchPartial) + { + if (Platform.IndexOf(next, userId) > -1) + { + rings.Add(pubRing); + } + } + else + { + if (next.Equals(userId)) + { + rings.Add(pubRing); + } + } + } + } + + return new EnumerableProxy(rings); + } + + /// Return the PGP public key associated with the given key id. + /// The ID of the public key to return. + public PgpPublicKey GetPublicKey( + long keyId) + { + foreach (PgpPublicKeyRing pubRing in GetKeyRings()) + { + PgpPublicKey pub = pubRing.GetPublicKey(keyId); + + if (pub != null) + { + return pub; + } + } + + return null; + } + + /// Return the public key ring which contains the key referred to by keyId + /// key ID to match against + public PgpPublicKeyRing GetPublicKeyRing( + long keyId) + { + if (pubRings.Contains(keyId)) + { + return (PgpPublicKeyRing)pubRings[keyId]; + } + + foreach (PgpPublicKeyRing pubRing in GetKeyRings()) + { + PgpPublicKey pub = pubRing.GetPublicKey(keyId); + + if (pub != null) + { + return pubRing; + } + } + + return null; + } + + /// + /// Return true if a key matching the passed in key ID is present, false otherwise. + /// + /// key ID to look for. + public bool Contains( + long keyID) + { + return GetPublicKey(keyID) != null; + } + + public byte[] GetEncoded() + { + MemoryStream bOut = new MemoryStream(); + + Encode(bOut); + + return bOut.ToArray(); + } + + public void Encode( + Stream outStr) + { + BcpgOutputStream bcpgOut = BcpgOutputStream.Wrap(outStr); + + foreach (long key in order) + { + PgpPublicKeyRing sec = (PgpPublicKeyRing) pubRings[key]; + + sec.Encode(bcpgOut); + } + } + + /// + /// Return a new bundle containing the contents of the passed in bundle and + /// the passed in public key ring. + /// + /// The PgpPublicKeyRingBundle the key ring is to be added to. + /// The key ring to be added. + /// A new PgpPublicKeyRingBundle merging the current one with the passed in key ring. + /// If the keyId for the passed in key ring is already present. + public static PgpPublicKeyRingBundle AddPublicKeyRing( + PgpPublicKeyRingBundle bundle, + PgpPublicKeyRing publicKeyRing) + { + long key = publicKeyRing.GetPublicKey().KeyId; + + if (bundle.pubRings.Contains(key)) + { + throw new ArgumentException("Bundle already contains a key with a keyId for the passed in ring."); + } + + IDictionary newPubRings = Platform.CreateHashtable(bundle.pubRings); + IList newOrder = Platform.CreateArrayList(bundle.order); + + newPubRings[key] = publicKeyRing; + + newOrder.Add(key); + + return new PgpPublicKeyRingBundle(newPubRings, newOrder); + } + + /// + /// Return a new bundle containing the contents of the passed in bundle with + /// the passed in public key ring removed. + /// + /// The PgpPublicKeyRingBundle the key ring is to be removed from. + /// The key ring to be removed. + /// A new PgpPublicKeyRingBundle not containing the passed in key ring. + /// If the keyId for the passed in key ring is not present. + public static PgpPublicKeyRingBundle RemovePublicKeyRing( + PgpPublicKeyRingBundle bundle, + PgpPublicKeyRing publicKeyRing) + { + long key = publicKeyRing.GetPublicKey().KeyId; + + if (!bundle.pubRings.Contains(key)) + { + throw new ArgumentException("Bundle does not contain a key with a keyId for the passed in ring."); + } + + IDictionary newPubRings = Platform.CreateHashtable(bundle.pubRings); + IList newOrder = Platform.CreateArrayList(bundle.order); + + newPubRings.Remove(key); + newOrder.Remove(key); + + return new PgpPublicKeyRingBundle(newPubRings, newOrder); + } + } +} diff --git a/bc-sharp-crypto/src/openpgp/PgpSecretKey.cs b/bc-sharp-crypto/src/openpgp/PgpSecretKey.cs new file mode 100644 index 0000000..b398607 --- /dev/null +++ b/bc-sharp-crypto/src/openpgp/PgpSecretKey.cs @@ -0,0 +1,1295 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + /// General class to handle a PGP secret key object. + public class PgpSecretKey + { + private readonly SecretKeyPacket secret; + private readonly PgpPublicKey pub; + + internal PgpSecretKey( + SecretKeyPacket secret, + PgpPublicKey pub) + { + this.secret = secret; + this.pub = pub; + } + + internal PgpSecretKey( + PgpPrivateKey privKey, + PgpPublicKey pubKey, + SymmetricKeyAlgorithmTag encAlgorithm, + byte[] rawPassPhrase, + bool clearPassPhrase, + bool useSha1, + SecureRandom rand, + bool isMasterKey) + { + BcpgObject secKey; + + this.pub = pubKey; + + switch (pubKey.Algorithm) + { + case PublicKeyAlgorithmTag.RsaEncrypt: + case PublicKeyAlgorithmTag.RsaSign: + case PublicKeyAlgorithmTag.RsaGeneral: + RsaPrivateCrtKeyParameters rsK = (RsaPrivateCrtKeyParameters) privKey.Key; + secKey = new RsaSecretBcpgKey(rsK.Exponent, rsK.P, rsK.Q); + break; + case PublicKeyAlgorithmTag.Dsa: + DsaPrivateKeyParameters dsK = (DsaPrivateKeyParameters) privKey.Key; + secKey = new DsaSecretBcpgKey(dsK.X); + break; + case PublicKeyAlgorithmTag.ECDH: + case PublicKeyAlgorithmTag.ECDsa: + ECPrivateKeyParameters ecK = (ECPrivateKeyParameters)privKey.Key; + secKey = new ECSecretBcpgKey(ecK.D); + break; + case PublicKeyAlgorithmTag.ElGamalEncrypt: + case PublicKeyAlgorithmTag.ElGamalGeneral: + ElGamalPrivateKeyParameters esK = (ElGamalPrivateKeyParameters) privKey.Key; + secKey = new ElGamalSecretBcpgKey(esK.X); + break; + default: + throw new PgpException("unknown key class"); + } + + try + { + MemoryStream bOut = new MemoryStream(); + BcpgOutputStream pOut = new BcpgOutputStream(bOut); + + pOut.WriteObject(secKey); + + byte[] keyData = bOut.ToArray(); + byte[] checksumData = Checksum(useSha1, keyData, keyData.Length); + + keyData = Arrays.Concatenate(keyData, checksumData); + + if (encAlgorithm == SymmetricKeyAlgorithmTag.Null) + { + if (isMasterKey) + { + this.secret = new SecretKeyPacket(pub.publicPk, encAlgorithm, null, null, keyData); + } + else + { + this.secret = new SecretSubkeyPacket(pub.publicPk, encAlgorithm, null, null, keyData); + } + } + else + { + S2k s2k; + byte[] iv; + + byte[] encData; + if (pub.Version >= 4) + { + encData = EncryptKeyDataV4(keyData, encAlgorithm, HashAlgorithmTag.Sha1, rawPassPhrase, clearPassPhrase, rand, out s2k, out iv); + } + else + { + encData = EncryptKeyDataV3(keyData, encAlgorithm, rawPassPhrase, clearPassPhrase, rand, out s2k, out iv); + } + + int s2kUsage = useSha1 + ? SecretKeyPacket.UsageSha1 + : SecretKeyPacket.UsageChecksum; + + if (isMasterKey) + { + this.secret = new SecretKeyPacket(pub.publicPk, encAlgorithm, s2kUsage, s2k, iv, encData); + } + else + { + this.secret = new SecretSubkeyPacket(pub.publicPk, encAlgorithm, s2kUsage, s2k, iv, encData); + } + } + } + catch (PgpException e) + { + throw e; + } + catch (Exception e) + { + throw new PgpException("Exception encrypting key", e); + } + } + + /// + /// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is + /// the historical behaviour of the library (1.7 and earlier). + /// + [Obsolete("Use the constructor taking an explicit 'useSha1' parameter instead")] + public PgpSecretKey( + int certificationLevel, + PgpKeyPair keyPair, + string id, + SymmetricKeyAlgorithmTag encAlgorithm, + char[] passPhrase, + PgpSignatureSubpacketVector hashedPackets, + PgpSignatureSubpacketVector unhashedPackets, + SecureRandom rand) + : this(certificationLevel, keyPair, id, encAlgorithm, passPhrase, false, hashedPackets, unhashedPackets, rand) + { + } + + /// + /// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is + /// the historical behaviour of the library (1.7 and earlier). + /// + public PgpSecretKey( + int certificationLevel, + PgpKeyPair keyPair, + string id, + SymmetricKeyAlgorithmTag encAlgorithm, + char[] passPhrase, + bool useSha1, + PgpSignatureSubpacketVector hashedPackets, + PgpSignatureSubpacketVector unhashedPackets, + SecureRandom rand) + : this(certificationLevel, keyPair, id, encAlgorithm, false, passPhrase, useSha1, hashedPackets, unhashedPackets, rand) + { + } + + /// + /// If utf8PassPhrase is true, conversion of the passphrase to bytes uses Encoding.UTF8.GetBytes(), otherwise the conversion + /// is performed using Convert.ToByte(), which is the historical behaviour of the library (1.7 and earlier). + /// + public PgpSecretKey( + int certificationLevel, + PgpKeyPair keyPair, + string id, + SymmetricKeyAlgorithmTag encAlgorithm, + bool utf8PassPhrase, + char[] passPhrase, + bool useSha1, + PgpSignatureSubpacketVector hashedPackets, + PgpSignatureSubpacketVector unhashedPackets, + SecureRandom rand) + : this(certificationLevel, keyPair, id, encAlgorithm, + PgpUtilities.EncodePassPhrase(passPhrase, utf8PassPhrase), true, + useSha1, hashedPackets, unhashedPackets, rand) + { + } + + /// + /// Allows the caller to handle the encoding of the passphrase to bytes. + /// + public PgpSecretKey( + int certificationLevel, + PgpKeyPair keyPair, + string id, + SymmetricKeyAlgorithmTag encAlgorithm, + byte[] rawPassPhrase, + bool useSha1, + PgpSignatureSubpacketVector hashedPackets, + PgpSignatureSubpacketVector unhashedPackets, + SecureRandom rand) + : this(certificationLevel, keyPair, id, encAlgorithm, rawPassPhrase, false, useSha1, hashedPackets, unhashedPackets, rand) + { + } + + internal PgpSecretKey( + int certificationLevel, + PgpKeyPair keyPair, + string id, + SymmetricKeyAlgorithmTag encAlgorithm, + byte[] rawPassPhrase, + bool clearPassPhrase, + bool useSha1, + PgpSignatureSubpacketVector hashedPackets, + PgpSignatureSubpacketVector unhashedPackets, + SecureRandom rand) + : this(keyPair.PrivateKey, CertifiedPublicKey(certificationLevel, keyPair, id, hashedPackets, unhashedPackets), + encAlgorithm, rawPassPhrase, clearPassPhrase, useSha1, rand, true) + { + } + + /// + /// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is + /// the historical behaviour of the library (1.7 and earlier). + /// + public PgpSecretKey( + int certificationLevel, + PgpKeyPair keyPair, + string id, + SymmetricKeyAlgorithmTag encAlgorithm, + HashAlgorithmTag hashAlgorithm, + char[] passPhrase, + bool useSha1, + PgpSignatureSubpacketVector hashedPackets, + PgpSignatureSubpacketVector unhashedPackets, + SecureRandom rand) + : this(certificationLevel, keyPair, id, encAlgorithm, hashAlgorithm, false, passPhrase, useSha1, hashedPackets, unhashedPackets, rand) + { + } + + /// + /// If utf8PassPhrase is true, conversion of the passphrase to bytes uses Encoding.UTF8.GetBytes(), otherwise the conversion + /// is performed using Convert.ToByte(), which is the historical behaviour of the library (1.7 and earlier). + /// + public PgpSecretKey( + int certificationLevel, + PgpKeyPair keyPair, + string id, + SymmetricKeyAlgorithmTag encAlgorithm, + HashAlgorithmTag hashAlgorithm, + bool utf8PassPhrase, + char[] passPhrase, + bool useSha1, + PgpSignatureSubpacketVector hashedPackets, + PgpSignatureSubpacketVector unhashedPackets, + SecureRandom rand) + : this(certificationLevel, keyPair, id, encAlgorithm, hashAlgorithm, + PgpUtilities.EncodePassPhrase(passPhrase, utf8PassPhrase), true, + useSha1, hashedPackets, unhashedPackets, rand) + { + } + + /// + /// Allows the caller to handle the encoding of the passphrase to bytes. + /// + public PgpSecretKey( + int certificationLevel, + PgpKeyPair keyPair, + string id, + SymmetricKeyAlgorithmTag encAlgorithm, + HashAlgorithmTag hashAlgorithm, + byte[] rawPassPhrase, + bool useSha1, + PgpSignatureSubpacketVector hashedPackets, + PgpSignatureSubpacketVector unhashedPackets, + SecureRandom rand) + : this(certificationLevel, keyPair, id, encAlgorithm, hashAlgorithm, rawPassPhrase, false, useSha1, hashedPackets, unhashedPackets, rand) + { + } + + internal PgpSecretKey( + int certificationLevel, + PgpKeyPair keyPair, + string id, + SymmetricKeyAlgorithmTag encAlgorithm, + HashAlgorithmTag hashAlgorithm, + byte[] rawPassPhrase, + bool clearPassPhrase, + bool useSha1, + PgpSignatureSubpacketVector hashedPackets, + PgpSignatureSubpacketVector unhashedPackets, + SecureRandom rand) + : this(keyPair.PrivateKey, CertifiedPublicKey(certificationLevel, keyPair, id, hashedPackets, unhashedPackets, hashAlgorithm), + encAlgorithm, rawPassPhrase, clearPassPhrase, useSha1, rand, true) + { + } + + private static PgpPublicKey CertifiedPublicKey( + int certificationLevel, + PgpKeyPair keyPair, + string id, + PgpSignatureSubpacketVector hashedPackets, + PgpSignatureSubpacketVector unhashedPackets) + { + PgpSignatureGenerator sGen; + try + { + sGen = new PgpSignatureGenerator(keyPair.PublicKey.Algorithm, HashAlgorithmTag.Sha1); + } + catch (Exception e) + { + throw new PgpException("Creating signature generator: " + e.Message, e); + } + + // + // Generate the certification + // + sGen.InitSign(certificationLevel, keyPair.PrivateKey); + + sGen.SetHashedSubpackets(hashedPackets); + sGen.SetUnhashedSubpackets(unhashedPackets); + + try + { + PgpSignature certification = sGen.GenerateCertification(id, keyPair.PublicKey); + return PgpPublicKey.AddCertification(keyPair.PublicKey, id, certification); + } + catch (Exception e) + { + throw new PgpException("Exception doing certification: " + e.Message, e); + } + } + + + private static PgpPublicKey CertifiedPublicKey( + int certificationLevel, + PgpKeyPair keyPair, + string id, + PgpSignatureSubpacketVector hashedPackets, + PgpSignatureSubpacketVector unhashedPackets, + HashAlgorithmTag hashAlgorithm) + { + PgpSignatureGenerator sGen; + try + { + sGen = new PgpSignatureGenerator(keyPair.PublicKey.Algorithm, hashAlgorithm); + } + catch (Exception e) + { + throw new PgpException("Creating signature generator: " + e.Message, e); + } + + // + // Generate the certification + // + sGen.InitSign(certificationLevel, keyPair.PrivateKey); + + sGen.SetHashedSubpackets(hashedPackets); + sGen.SetUnhashedSubpackets(unhashedPackets); + + try + { + PgpSignature certification = sGen.GenerateCertification(id, keyPair.PublicKey); + return PgpPublicKey.AddCertification(keyPair.PublicKey, id, certification); + } + catch (Exception e) + { + throw new PgpException("Exception doing certification: " + e.Message, e); + } + } + + public PgpSecretKey( + int certificationLevel, + PublicKeyAlgorithmTag algorithm, + AsymmetricKeyParameter pubKey, + AsymmetricKeyParameter privKey, + DateTime time, + string id, + SymmetricKeyAlgorithmTag encAlgorithm, + char[] passPhrase, + PgpSignatureSubpacketVector hashedPackets, + PgpSignatureSubpacketVector unhashedPackets, + SecureRandom rand) + : this(certificationLevel, + new PgpKeyPair(algorithm, pubKey, privKey, time), + id, encAlgorithm, passPhrase, false, hashedPackets, unhashedPackets, rand) + { + } + + public PgpSecretKey( + int certificationLevel, + PublicKeyAlgorithmTag algorithm, + AsymmetricKeyParameter pubKey, + AsymmetricKeyParameter privKey, + DateTime time, + string id, + SymmetricKeyAlgorithmTag encAlgorithm, + char[] passPhrase, + bool useSha1, + PgpSignatureSubpacketVector hashedPackets, + PgpSignatureSubpacketVector unhashedPackets, + SecureRandom rand) + : this(certificationLevel, new PgpKeyPair(algorithm, pubKey, privKey, time), id, encAlgorithm, passPhrase, useSha1, hashedPackets, unhashedPackets, rand) + { + } + + /// + /// Check if this key has an algorithm type that makes it suitable to use for signing. + /// + /// + /// Note: with version 4 keys KeyFlags subpackets should also be considered when present for + /// determining the preferred use of the key. + /// + /// + /// true if this key algorithm is suitable for use with signing. + /// + public bool IsSigningKey + { + get + { + switch (pub.Algorithm) + { + case PublicKeyAlgorithmTag.RsaGeneral: + case PublicKeyAlgorithmTag.RsaSign: + case PublicKeyAlgorithmTag.Dsa: + case PublicKeyAlgorithmTag.ECDsa: + case PublicKeyAlgorithmTag.ElGamalGeneral: + return true; + default: + return false; + } + } + } + + /// True, if this is a master key. + public bool IsMasterKey + { + get { return pub.IsMasterKey; } + } + + /// Detect if the Secret Key's Private Key is empty or not + public bool IsPrivateKeyEmpty + { + get + { + byte[] secKeyData = secret.GetSecretKeyData(); + + return secKeyData == null || secKeyData.Length < 1; + } + } + + /// The algorithm the key is encrypted with. + public SymmetricKeyAlgorithmTag KeyEncryptionAlgorithm + { + get { return secret.EncAlgorithm; } + } + + /// The key ID of the public key associated with this key. + public long KeyId + { + get { return pub.KeyId; } + } + + /// Return the S2K usage associated with this key. + public int S2kUsage + { + get { return secret.S2kUsage; } + } + + /// Return the S2K used to process this key. + public S2k S2k + { + get { return secret.S2k; } + } + + /// The public key associated with this key. + public PgpPublicKey PublicKey + { + get { return pub; } + } + + /// Allows enumeration of any user IDs associated with the key. + /// An IEnumerable of string objects. + public IEnumerable UserIds + { + get { return pub.GetUserIds(); } + } + + /// Allows enumeration of any user attribute vectors associated with the key. + /// An IEnumerable of string objects. + public IEnumerable UserAttributes + { + get { return pub.GetUserAttributes(); } + } + + private byte[] ExtractKeyData(byte[] rawPassPhrase, bool clearPassPhrase) + { + SymmetricKeyAlgorithmTag encAlgorithm = secret.EncAlgorithm; + byte[] encData = secret.GetSecretKeyData(); + + if (encAlgorithm == SymmetricKeyAlgorithmTag.Null) + // TODO Check checksum here? + return encData; + + // TODO Factor this block out as 'decryptData' + try + { + KeyParameter key = PgpUtilities.DoMakeKeyFromPassPhrase(secret.EncAlgorithm, secret.S2k, rawPassPhrase, clearPassPhrase); + byte[] iv = secret.GetIV(); + byte[] data; + + if (secret.PublicKeyPacket.Version >= 4) + { + data = RecoverKeyData(encAlgorithm, "/CFB/NoPadding", key, iv, encData, 0, encData.Length); + + bool useSha1 = secret.S2kUsage == SecretKeyPacket.UsageSha1; + byte[] check = Checksum(useSha1, data, (useSha1) ? data.Length - 20 : data.Length - 2); + + for (int i = 0; i != check.Length; i++) + { + if (check[i] != data[data.Length - check.Length + i]) + { + throw new PgpException("Checksum mismatch at " + i + " of " + check.Length); + } + } + } + else // version 2 or 3, RSA only. + { + data = new byte[encData.Length]; + + iv = Arrays.Clone(iv); + + // + // read in the four numbers + // + int pos = 0; + + for (int i = 0; i != 4; i++) + { + int encLen = (((encData[pos] << 8) | (encData[pos + 1] & 0xff)) + 7) / 8; + + data[pos] = encData[pos]; + data[pos + 1] = encData[pos + 1]; + pos += 2; + + byte[] tmp = RecoverKeyData(encAlgorithm, "/CFB/NoPadding", key, iv, encData, pos, encLen); + Array.Copy(tmp, 0, data, pos, encLen); + pos += encLen; + + if (i != 3) + { + Array.Copy(encData, pos - iv.Length, iv, 0, iv.Length); + } + } + + // + // verify and copy checksum + // + + data[pos] = encData[pos]; + data[pos + 1] = encData[pos + 1]; + + int cs = ((encData[pos] << 8) & 0xff00) | (encData[pos + 1] & 0xff); + int calcCs = 0; + for (int j = 0; j < pos; j++) + { + calcCs += data[j] & 0xff; + } + + calcCs &= 0xffff; + if (calcCs != cs) + { + throw new PgpException("Checksum mismatch: passphrase wrong, expected " + + cs.ToString("X") + + " found " + calcCs.ToString("X")); + } + } + + return data; + } + catch (PgpException e) + { + throw e; + } + catch (Exception e) + { + throw new PgpException("Exception decrypting key", e); + } + } + + private static byte[] RecoverKeyData(SymmetricKeyAlgorithmTag encAlgorithm, string modeAndPadding, + KeyParameter key, byte[] iv, byte[] keyData, int keyOff, int keyLen) + { + IBufferedCipher c; + try + { + string cName = PgpUtilities.GetSymmetricCipherName(encAlgorithm); + c = CipherUtilities.GetCipher(cName + modeAndPadding); + } + catch (Exception e) + { + throw new PgpException("Exception creating cipher", e); + } + + c.Init(false, new ParametersWithIV(key, iv)); + + return c.DoFinal(keyData, keyOff, keyLen); + } + + /// Extract a PgpPrivateKey from this secret key's encrypted contents. + /// + /// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is + /// the historical behaviour of the library (1.7 and earlier). + /// + public PgpPrivateKey ExtractPrivateKey(char[] passPhrase) + { + return DoExtractPrivateKey(PgpUtilities.EncodePassPhrase(passPhrase, false), true); + } + + /// Extract a PgpPrivateKey from this secret key's encrypted contents. + /// + /// The passphrase is encoded to bytes using UTF8 (Encoding.UTF8.GetBytes). + /// + public PgpPrivateKey ExtractPrivateKeyUtf8(char[] passPhrase) + { + return DoExtractPrivateKey(PgpUtilities.EncodePassPhrase(passPhrase, true), true); + } + + /// Extract a PgpPrivateKey from this secret key's encrypted contents. + /// + /// Allows the caller to handle the encoding of the passphrase to bytes. + /// + public PgpPrivateKey ExtractPrivateKeyRaw(byte[] rawPassPhrase) + { + return DoExtractPrivateKey(rawPassPhrase, false); + } + + internal PgpPrivateKey DoExtractPrivateKey(byte[] rawPassPhrase, bool clearPassPhrase) + { + if (IsPrivateKeyEmpty) + return null; + + PublicKeyPacket pubPk = secret.PublicKeyPacket; + try + { + byte[] data = ExtractKeyData(rawPassPhrase, clearPassPhrase); + BcpgInputStream bcpgIn = BcpgInputStream.Wrap(new MemoryStream(data, false)); + AsymmetricKeyParameter privateKey; + switch (pubPk.Algorithm) + { + case PublicKeyAlgorithmTag.RsaEncrypt: + case PublicKeyAlgorithmTag.RsaGeneral: + case PublicKeyAlgorithmTag.RsaSign: + RsaPublicBcpgKey rsaPub = (RsaPublicBcpgKey)pubPk.Key; + RsaSecretBcpgKey rsaPriv = new RsaSecretBcpgKey(bcpgIn); + RsaPrivateCrtKeyParameters rsaPrivSpec = new RsaPrivateCrtKeyParameters( + rsaPriv.Modulus, + rsaPub.PublicExponent, + rsaPriv.PrivateExponent, + rsaPriv.PrimeP, + rsaPriv.PrimeQ, + rsaPriv.PrimeExponentP, + rsaPriv.PrimeExponentQ, + rsaPriv.CrtCoefficient); + privateKey = rsaPrivSpec; + break; + case PublicKeyAlgorithmTag.Dsa: + DsaPublicBcpgKey dsaPub = (DsaPublicBcpgKey)pubPk.Key; + DsaSecretBcpgKey dsaPriv = new DsaSecretBcpgKey(bcpgIn); + DsaParameters dsaParams = new DsaParameters(dsaPub.P, dsaPub.Q, dsaPub.G); + privateKey = new DsaPrivateKeyParameters(dsaPriv.X, dsaParams); + break; + case PublicKeyAlgorithmTag.ECDH: + privateKey = GetECKey("ECDH", bcpgIn); + break; + case PublicKeyAlgorithmTag.ECDsa: + privateKey = GetECKey("ECDSA", bcpgIn); + break; + case PublicKeyAlgorithmTag.ElGamalEncrypt: + case PublicKeyAlgorithmTag.ElGamalGeneral: + ElGamalPublicBcpgKey elPub = (ElGamalPublicBcpgKey)pubPk.Key; + ElGamalSecretBcpgKey elPriv = new ElGamalSecretBcpgKey(bcpgIn); + ElGamalParameters elParams = new ElGamalParameters(elPub.P, elPub.G); + privateKey = new ElGamalPrivateKeyParameters(elPriv.X, elParams); + break; + default: + throw new PgpException("unknown public key algorithm encountered"); + } + + return new PgpPrivateKey(KeyId, pubPk, privateKey); + } + catch (PgpException e) + { + throw e; + } + catch (Exception e) + { + throw new PgpException("Exception constructing key", e); + } + } + + private ECPrivateKeyParameters GetECKey(string algorithm, BcpgInputStream bcpgIn) + { + ECPublicBcpgKey ecdsaPub = (ECPublicBcpgKey)secret.PublicKeyPacket.Key; + ECSecretBcpgKey ecdsaPriv = new ECSecretBcpgKey(bcpgIn); + return new ECPrivateKeyParameters(algorithm, ecdsaPriv.X, ecdsaPub.CurveOid); + } + + private static byte[] Checksum( + bool useSha1, + byte[] bytes, + int length) + { + if (useSha1) + { + try + { + IDigest dig = DigestUtilities.GetDigest("SHA1"); + dig.BlockUpdate(bytes, 0, length); + return DigestUtilities.DoFinal(dig); + } + //catch (NoSuchAlgorithmException e) + catch (Exception e) + { + throw new PgpException("Can't find SHA-1", e); + } + } + else + { + int Checksum = 0; + for (int i = 0; i != length; i++) + { + Checksum += bytes[i]; + } + + return new byte[] { (byte)(Checksum >> 8), (byte)Checksum }; + } + } + + public byte[] GetEncoded() + { + MemoryStream bOut = new MemoryStream(); + Encode(bOut); + return bOut.ToArray(); + } + + public void Encode( + Stream outStr) + { + BcpgOutputStream bcpgOut = BcpgOutputStream.Wrap(outStr); + + bcpgOut.WritePacket(secret); + if (pub.trustPk != null) + { + bcpgOut.WritePacket(pub.trustPk); + } + + if (pub.subSigs == null) // is not a sub key + { + foreach (PgpSignature keySig in pub.keySigs) + { + keySig.Encode(bcpgOut); + } + + for (int i = 0; i != pub.ids.Count; i++) + { + object pubID = pub.ids[i]; + if (pubID is string) + { + string id = (string) pubID; + bcpgOut.WritePacket(new UserIdPacket(id)); + } + else + { + PgpUserAttributeSubpacketVector v = (PgpUserAttributeSubpacketVector) pubID; + bcpgOut.WritePacket(new UserAttributePacket(v.ToSubpacketArray())); + } + + if (pub.idTrusts[i] != null) + { + bcpgOut.WritePacket((ContainedPacket)pub.idTrusts[i]); + } + + foreach (PgpSignature sig in (IList) pub.idSigs[i]) + { + sig.Encode(bcpgOut); + } + } + } + else + { + foreach (PgpSignature subSig in pub.subSigs) + { + subSig.Encode(bcpgOut); + } + } + + // TODO Check that this is right/necessary + //bcpgOut.Finish(); + } + + /// + /// Return a copy of the passed in secret key, encrypted using a new password + /// and the passed in algorithm. + /// + /// + /// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is + /// the historical behaviour of the library (1.7 and earlier). + /// + /// The PgpSecretKey to be copied. + /// The current password for the key. + /// The new password for the key. + /// The algorithm to be used for the encryption. + /// Source of randomness. + public static PgpSecretKey CopyWithNewPassword( + PgpSecretKey key, + char[] oldPassPhrase, + char[] newPassPhrase, + SymmetricKeyAlgorithmTag newEncAlgorithm, + SecureRandom rand) + { + return DoCopyWithNewPassword(key, PgpUtilities.EncodePassPhrase(oldPassPhrase, false), + PgpUtilities.EncodePassPhrase(newPassPhrase, false), true, newEncAlgorithm, rand); + } + + /// + /// Return a copy of the passed in secret key, encrypted using a new password + /// and the passed in algorithm. + /// + /// + /// The passphrase is encoded to bytes using UTF8 (Encoding.UTF8.GetBytes). + /// + /// The PgpSecretKey to be copied. + /// The current password for the key. + /// The new password for the key. + /// The algorithm to be used for the encryption. + /// Source of randomness. + public static PgpSecretKey CopyWithNewPasswordUtf8( + PgpSecretKey key, + char[] oldPassPhrase, + char[] newPassPhrase, + SymmetricKeyAlgorithmTag newEncAlgorithm, + SecureRandom rand) + { + return DoCopyWithNewPassword(key, PgpUtilities.EncodePassPhrase(oldPassPhrase, true), + PgpUtilities.EncodePassPhrase(newPassPhrase, true), true, newEncAlgorithm, rand); + } + + /// + /// Return a copy of the passed in secret key, encrypted using a new password + /// and the passed in algorithm. + /// + /// + /// Allows the caller to handle the encoding of the passphrase to bytes. + /// + /// The PgpSecretKey to be copied. + /// The current password for the key. + /// The new password for the key. + /// The algorithm to be used for the encryption. + /// Source of randomness. + public static PgpSecretKey CopyWithNewPasswordRaw( + PgpSecretKey key, + byte[] rawOldPassPhrase, + byte[] rawNewPassPhrase, + SymmetricKeyAlgorithmTag newEncAlgorithm, + SecureRandom rand) + { + return DoCopyWithNewPassword(key, rawOldPassPhrase, rawNewPassPhrase, false, newEncAlgorithm, rand); + } + + internal static PgpSecretKey DoCopyWithNewPassword( + PgpSecretKey key, + byte[] rawOldPassPhrase, + byte[] rawNewPassPhrase, + bool clearPassPhrase, + SymmetricKeyAlgorithmTag newEncAlgorithm, + SecureRandom rand) + { + if (key.IsPrivateKeyEmpty) + throw new PgpException("no private key in this SecretKey - public key present only."); + + byte[] rawKeyData = key.ExtractKeyData(rawOldPassPhrase, clearPassPhrase); + int s2kUsage = key.secret.S2kUsage; + byte[] iv = null; + S2k s2k = null; + byte[] keyData; + PublicKeyPacket pubKeyPacket = key.secret.PublicKeyPacket; + + if (newEncAlgorithm == SymmetricKeyAlgorithmTag.Null) + { + s2kUsage = SecretKeyPacket.UsageNone; + if (key.secret.S2kUsage == SecretKeyPacket.UsageSha1) // SHA-1 hash, need to rewrite Checksum + { + keyData = new byte[rawKeyData.Length - 18]; + + Array.Copy(rawKeyData, 0, keyData, 0, keyData.Length - 2); + + byte[] check = Checksum(false, keyData, keyData.Length - 2); + + keyData[keyData.Length - 2] = check[0]; + keyData[keyData.Length - 1] = check[1]; + } + else + { + keyData = rawKeyData; + } + } + else + { + if (s2kUsage == SecretKeyPacket.UsageNone) + { + s2kUsage = SecretKeyPacket.UsageChecksum; + } + + try + { + if (pubKeyPacket.Version >= 4) + { + keyData = EncryptKeyDataV4(rawKeyData, newEncAlgorithm, HashAlgorithmTag.Sha1, rawNewPassPhrase, clearPassPhrase, rand, out s2k, out iv); + } + else + { + keyData = EncryptKeyDataV3(rawKeyData, newEncAlgorithm, rawNewPassPhrase, clearPassPhrase, rand, out s2k, out iv); + } + } + catch (PgpException e) + { + throw e; + } + catch (Exception e) + { + throw new PgpException("Exception encrypting key", e); + } + } + + SecretKeyPacket secret; + if (key.secret is SecretSubkeyPacket) + { + secret = new SecretSubkeyPacket(pubKeyPacket, newEncAlgorithm, s2kUsage, s2k, iv, keyData); + } + else + { + secret = new SecretKeyPacket(pubKeyPacket, newEncAlgorithm, s2kUsage, s2k, iv, keyData); + } + + return new PgpSecretKey(secret, key.pub); + } + + /// Replace the passed the public key on the passed in secret key. + /// Secret key to change. + /// New public key. + /// A new secret key. + /// If KeyId's do not match. + public static PgpSecretKey ReplacePublicKey( + PgpSecretKey secretKey, + PgpPublicKey publicKey) + { + if (publicKey.KeyId != secretKey.KeyId) + throw new ArgumentException("KeyId's do not match"); + + return new PgpSecretKey(secretKey.secret, publicKey); + } + + private static byte[] EncryptKeyDataV3( + byte[] rawKeyData, + SymmetricKeyAlgorithmTag encAlgorithm, + byte[] rawPassPhrase, + bool clearPassPhrase, + SecureRandom random, + out S2k s2k, + out byte[] iv) + { + // Version 2 or 3 - RSA Keys only + + s2k = null; + iv = null; + + KeyParameter encKey = PgpUtilities.DoMakeKeyFromPassPhrase(encAlgorithm, s2k, rawPassPhrase, clearPassPhrase); + + byte[] keyData = new byte[rawKeyData.Length]; + + // + // process 4 numbers + // + int pos = 0; + for (int i = 0; i != 4; i++) + { + int encLen = (((rawKeyData[pos] << 8) | (rawKeyData[pos + 1] & 0xff)) + 7) / 8; + + keyData[pos] = rawKeyData[pos]; + keyData[pos + 1] = rawKeyData[pos + 1]; + + byte[] tmp; + if (i == 0) + { + tmp = EncryptData(encAlgorithm, encKey, rawKeyData, pos + 2, encLen, random, ref iv); + } + else + { + byte[] tmpIv = Arrays.CopyOfRange(keyData, pos - iv.Length, pos); + + tmp = EncryptData(encAlgorithm, encKey, rawKeyData, pos + 2, encLen, random, ref tmpIv); + } + + Array.Copy(tmp, 0, keyData, pos + 2, tmp.Length); + pos += 2 + encLen; + } + + // + // copy in checksum. + // + keyData[pos] = rawKeyData[pos]; + keyData[pos + 1] = rawKeyData[pos + 1]; + + return keyData; + } + + private static byte[] EncryptKeyDataV4( + byte[] rawKeyData, + SymmetricKeyAlgorithmTag encAlgorithm, + HashAlgorithmTag hashAlgorithm, + byte[] rawPassPhrase, + bool clearPassPhrase, + SecureRandom random, + out S2k s2k, + out byte[] iv) + { + s2k = PgpUtilities.GenerateS2k(hashAlgorithm, 0x60, random); + + KeyParameter key = PgpUtilities.DoMakeKeyFromPassPhrase(encAlgorithm, s2k, rawPassPhrase, clearPassPhrase); + + iv = null; + return EncryptData(encAlgorithm, key, rawKeyData, 0, rawKeyData.Length, random, ref iv); + } + + private static byte[] EncryptData( + SymmetricKeyAlgorithmTag encAlgorithm, + KeyParameter key, + byte[] data, + int dataOff, + int dataLen, + SecureRandom random, + ref byte[] iv) + { + IBufferedCipher c; + try + { + string cName = PgpUtilities.GetSymmetricCipherName(encAlgorithm); + c = CipherUtilities.GetCipher(cName + "/CFB/NoPadding"); + } + catch (Exception e) + { + throw new PgpException("Exception creating cipher", e); + } + + if (iv == null) + { + iv = PgpUtilities.GenerateIV(c.GetBlockSize(), random); + } + + c.Init(true, new ParametersWithRandom(new ParametersWithIV(key, iv), random)); + + return c.DoFinal(data, dataOff, dataLen); + } + + /// + /// Parse a secret key from one of the GPG S expression keys associating it with the passed in public key. + /// + /// + /// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is + /// the historical behaviour of the library (1.7 and earlier). + /// + public static PgpSecretKey ParseSecretKeyFromSExpr(Stream inputStream, char[] passPhrase, PgpPublicKey pubKey) + { + return DoParseSecretKeyFromSExpr(inputStream, PgpUtilities.EncodePassPhrase(passPhrase, false), true, pubKey); + } + + /// + /// Parse a secret key from one of the GPG S expression keys associating it with the passed in public key. + /// + /// + /// The passphrase is encoded to bytes using UTF8 (Encoding.UTF8.GetBytes). + /// + public static PgpSecretKey ParseSecretKeyFromSExprUtf8(Stream inputStream, char[] passPhrase, PgpPublicKey pubKey) + { + return DoParseSecretKeyFromSExpr(inputStream, PgpUtilities.EncodePassPhrase(passPhrase, true), true, pubKey); + } + + /// + /// Parse a secret key from one of the GPG S expression keys associating it with the passed in public key. + /// + /// + /// Allows the caller to handle the encoding of the passphrase to bytes. + /// + public static PgpSecretKey ParseSecretKeyFromSExprRaw(Stream inputStream, byte[] rawPassPhrase, PgpPublicKey pubKey) + { + return DoParseSecretKeyFromSExpr(inputStream, rawPassPhrase, false, pubKey); + } + + internal static PgpSecretKey DoParseSecretKeyFromSExpr(Stream inputStream, byte[] rawPassPhrase, bool clearPassPhrase, PgpPublicKey pubKey) + { + SXprUtilities.SkipOpenParenthesis(inputStream); + + string type = SXprUtilities.ReadString(inputStream, inputStream.ReadByte()); + if (type.Equals("protected-private-key")) + { + SXprUtilities.SkipOpenParenthesis(inputStream); + + string curveName; + + string keyType = SXprUtilities.ReadString(inputStream, inputStream.ReadByte()); + if (keyType.Equals("ecc")) + { + SXprUtilities.SkipOpenParenthesis(inputStream); + + string curveID = SXprUtilities.ReadString(inputStream, inputStream.ReadByte()); + curveName = SXprUtilities.ReadString(inputStream, inputStream.ReadByte()); + + SXprUtilities.SkipCloseParenthesis(inputStream); + } + else + { + throw new PgpException("no curve details found"); + } + + byte[] qVal; + + SXprUtilities.SkipOpenParenthesis(inputStream); + + type = SXprUtilities.ReadString(inputStream, inputStream.ReadByte()); + if (type.Equals("q")) + { + qVal = SXprUtilities.ReadBytes(inputStream, inputStream.ReadByte()); + } + else + { + throw new PgpException("no q value found"); + } + + SXprUtilities.SkipCloseParenthesis(inputStream); + + byte[] dValue = GetDValue(inputStream, rawPassPhrase, clearPassPhrase, curveName); + // TODO: check SHA-1 hash. + + return new PgpSecretKey(new SecretKeyPacket(pubKey.PublicKeyPacket, SymmetricKeyAlgorithmTag.Null, null, null, + new ECSecretBcpgKey(new BigInteger(1, dValue)).GetEncoded()), pubKey); + } + + throw new PgpException("unknown key type found"); + } + + /// + /// Parse a secret key from one of the GPG S expression keys. + /// + /// + /// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is + /// the historical behaviour of the library (1.7 and earlier). + /// + public static PgpSecretKey ParseSecretKeyFromSExpr(Stream inputStream, char[] passPhrase) + { + return DoParseSecretKeyFromSExpr(inputStream, PgpUtilities.EncodePassPhrase(passPhrase, false), true); + } + + /// + /// Parse a secret key from one of the GPG S expression keys. + /// + /// + /// The passphrase is encoded to bytes using UTF8 (Encoding.UTF8.GetBytes). + /// + public static PgpSecretKey ParseSecretKeyFromSExprUtf8(Stream inputStream, char[] passPhrase) + { + return DoParseSecretKeyFromSExpr(inputStream, PgpUtilities.EncodePassPhrase(passPhrase, true), true); + } + + /// + /// Parse a secret key from one of the GPG S expression keys. + /// + /// + /// Allows the caller to handle the encoding of the passphrase to bytes. + /// + public static PgpSecretKey ParseSecretKeyFromSExprRaw(Stream inputStream, byte[] rawPassPhrase) + { + return DoParseSecretKeyFromSExpr(inputStream, rawPassPhrase, false); + } + + /// + /// Parse a secret key from one of the GPG S expression keys. + /// + internal static PgpSecretKey DoParseSecretKeyFromSExpr(Stream inputStream, byte[] rawPassPhrase, bool clearPassPhrase) + { + SXprUtilities.SkipOpenParenthesis(inputStream); + + string type = SXprUtilities.ReadString(inputStream, inputStream.ReadByte()); + if (type.Equals("protected-private-key")) + { + SXprUtilities.SkipOpenParenthesis(inputStream); + + string curveName; + + string keyType = SXprUtilities.ReadString(inputStream, inputStream.ReadByte()); + if (keyType.Equals("ecc")) + { + SXprUtilities.SkipOpenParenthesis(inputStream); + + string curveID = SXprUtilities.ReadString(inputStream, inputStream.ReadByte()); + curveName = SXprUtilities.ReadString(inputStream, inputStream.ReadByte()); + + if (Platform.StartsWith(curveName, "NIST ")) + { + curveName = curveName.Substring("NIST ".Length); + } + + SXprUtilities.SkipCloseParenthesis(inputStream); + } + else + { + throw new PgpException("no curve details found"); + } + + byte[] qVal; + + SXprUtilities.SkipOpenParenthesis(inputStream); + + type = SXprUtilities.ReadString(inputStream, inputStream.ReadByte()); + if (type.Equals("q")) + { + qVal = SXprUtilities.ReadBytes(inputStream, inputStream.ReadByte()); + } + else + { + throw new PgpException("no q value found"); + } + + PublicKeyPacket pubPacket = new PublicKeyPacket(PublicKeyAlgorithmTag.ECDsa, DateTime.UtcNow, + new ECDsaPublicBcpgKey(ECNamedCurveTable.GetOid(curveName), new BigInteger(1, qVal))); + + SXprUtilities.SkipCloseParenthesis(inputStream); + + byte[] dValue = GetDValue(inputStream, rawPassPhrase, clearPassPhrase, curveName); + // TODO: check SHA-1 hash. + + return new PgpSecretKey(new SecretKeyPacket(pubPacket, SymmetricKeyAlgorithmTag.Null, null, null, + new ECSecretBcpgKey(new BigInteger(1, dValue)).GetEncoded()), new PgpPublicKey(pubPacket)); + } + + throw new PgpException("unknown key type found"); + } + + private static byte[] GetDValue(Stream inputStream, byte[] rawPassPhrase, bool clearPassPhrase, string curveName) + { + string type; + SXprUtilities.SkipOpenParenthesis(inputStream); + + string protection; + S2k s2k; + byte[] iv; + byte[] secKeyData; + + type = SXprUtilities.ReadString(inputStream, inputStream.ReadByte()); + if (type.Equals("protected")) + { + protection = SXprUtilities.ReadString(inputStream, inputStream.ReadByte()); + + SXprUtilities.SkipOpenParenthesis(inputStream); + + s2k = SXprUtilities.ParseS2k(inputStream); + + iv = SXprUtilities.ReadBytes(inputStream, inputStream.ReadByte()); + + SXprUtilities.SkipCloseParenthesis(inputStream); + + secKeyData = SXprUtilities.ReadBytes(inputStream, inputStream.ReadByte()); + } + else + { + throw new PgpException("protected block not found"); + } + + // TODO: recognise other algorithms + KeyParameter key = PgpUtilities.DoMakeKeyFromPassPhrase(SymmetricKeyAlgorithmTag.Aes128, s2k, rawPassPhrase, clearPassPhrase); + + byte[] data = RecoverKeyData(SymmetricKeyAlgorithmTag.Aes128, "/CBC/NoPadding", key, iv, secKeyData, 0, secKeyData.Length); + + // + // parse the secret key S-expr + // + Stream keyIn = new MemoryStream(data, false); + + SXprUtilities.SkipOpenParenthesis(keyIn); + SXprUtilities.SkipOpenParenthesis(keyIn); + SXprUtilities.SkipOpenParenthesis(keyIn); + String name = SXprUtilities.ReadString(keyIn, keyIn.ReadByte()); + return SXprUtilities.ReadBytes(keyIn, keyIn.ReadByte()); + } + } +} diff --git a/bc-sharp-crypto/src/openpgp/PgpSecretKeyRing.cs b/bc-sharp-crypto/src/openpgp/PgpSecretKeyRing.cs new file mode 100644 index 0000000..70cd721 --- /dev/null +++ b/bc-sharp-crypto/src/openpgp/PgpSecretKeyRing.cs @@ -0,0 +1,308 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; + +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + /// + /// Class to hold a single master secret key and its subkeys. + ///

    + /// Often PGP keyring files consist of multiple master keys, if you are trying to process + /// or construct one of these you should use the PgpSecretKeyRingBundle class. + ///

    + ///
    + public class PgpSecretKeyRing + : PgpKeyRing + { + private readonly IList keys; + private readonly IList extraPubKeys; + + internal PgpSecretKeyRing( + IList keys) + : this(keys, Platform.CreateArrayList()) + { + } + + private PgpSecretKeyRing( + IList keys, + IList extraPubKeys) + { + this.keys = keys; + this.extraPubKeys = extraPubKeys; + } + + public PgpSecretKeyRing( + byte[] encoding) + : this(new MemoryStream(encoding)) + { + } + + public PgpSecretKeyRing( + Stream inputStream) + { + this.keys = Platform.CreateArrayList(); + this.extraPubKeys = Platform.CreateArrayList(); + + BcpgInputStream bcpgInput = BcpgInputStream.Wrap(inputStream); + + PacketTag initialTag = bcpgInput.NextPacketTag(); + if (initialTag != PacketTag.SecretKey && initialTag != PacketTag.SecretSubkey) + { + throw new IOException("secret key ring doesn't start with secret key tag: " + + "tag 0x" + ((int)initialTag).ToString("X")); + } + + SecretKeyPacket secret = (SecretKeyPacket) bcpgInput.ReadPacket(); + + // + // ignore GPG comment packets if found. + // + while (bcpgInput.NextPacketTag() == PacketTag.Experimental2) + { + bcpgInput.ReadPacket(); + } + + TrustPacket trust = ReadOptionalTrustPacket(bcpgInput); + + // revocation and direct signatures + IList keySigs = ReadSignaturesAndTrust(bcpgInput); + + IList ids, idTrusts, idSigs; + ReadUserIDs(bcpgInput, out ids, out idTrusts, out idSigs); + + keys.Add(new PgpSecretKey(secret, new PgpPublicKey(secret.PublicKeyPacket, trust, keySigs, ids, idTrusts, idSigs))); + + + // Read subkeys + while (bcpgInput.NextPacketTag() == PacketTag.SecretSubkey + || bcpgInput.NextPacketTag() == PacketTag.PublicSubkey) + { + if (bcpgInput.NextPacketTag() == PacketTag.SecretSubkey) + { + SecretSubkeyPacket sub = (SecretSubkeyPacket) bcpgInput.ReadPacket(); + + // + // ignore GPG comment packets if found. + // + while (bcpgInput.NextPacketTag() == PacketTag.Experimental2) + { + bcpgInput.ReadPacket(); + } + + TrustPacket subTrust = ReadOptionalTrustPacket(bcpgInput); + IList sigList = ReadSignaturesAndTrust(bcpgInput); + + keys.Add(new PgpSecretKey(sub, new PgpPublicKey(sub.PublicKeyPacket, subTrust, sigList))); + } + else + { + PublicSubkeyPacket sub = (PublicSubkeyPacket) bcpgInput.ReadPacket(); + + TrustPacket subTrust = ReadOptionalTrustPacket(bcpgInput); + IList sigList = ReadSignaturesAndTrust(bcpgInput); + + extraPubKeys.Add(new PgpPublicKey(sub, subTrust, sigList)); + } + } + } + + /// Return the public key for the master key. + public PgpPublicKey GetPublicKey() + { + return ((PgpSecretKey) keys[0]).PublicKey; + } + + /// Return the master private key. + public PgpSecretKey GetSecretKey() + { + return (PgpSecretKey) keys[0]; + } + + /// Allows enumeration of the secret keys. + /// An IEnumerable of PgpSecretKey objects. + public IEnumerable GetSecretKeys() + { + return new EnumerableProxy(keys); + } + + public PgpSecretKey GetSecretKey( + long keyId) + { + foreach (PgpSecretKey k in keys) + { + if (keyId == k.KeyId) + { + return k; + } + } + + return null; + } + + /// + /// Return an iterator of the public keys in the secret key ring that + /// have no matching private key. At the moment only personal certificate data + /// appears in this fashion. + /// + /// An IEnumerable of unattached, or extra, public keys. + public IEnumerable GetExtraPublicKeys() + { + return new EnumerableProxy(extraPubKeys); + } + + public byte[] GetEncoded() + { + MemoryStream bOut = new MemoryStream(); + + Encode(bOut); + + return bOut.ToArray(); + } + + public void Encode( + Stream outStr) + { + if (outStr == null) + throw new ArgumentNullException("outStr"); + + foreach (PgpSecretKey key in keys) + { + key.Encode(outStr); + } + foreach (PgpPublicKey extraPubKey in extraPubKeys) + { + extraPubKey.Encode(outStr); + } + } + + /// + /// Replace the public key set on the secret ring with the corresponding key off the public ring. + /// + /// Secret ring to be changed. + /// Public ring containing the new public key set. + public static PgpSecretKeyRing ReplacePublicKeys( + PgpSecretKeyRing secretRing, + PgpPublicKeyRing publicRing) + { + IList newList = Platform.CreateArrayList(secretRing.keys.Count); + + foreach (PgpSecretKey sk in secretRing.keys) + { + PgpPublicKey pk = publicRing.GetPublicKey(sk.KeyId); + + newList.Add(PgpSecretKey.ReplacePublicKey(sk, pk)); + } + + return new PgpSecretKeyRing(newList); + } + + /// + /// Return a copy of the passed in secret key ring, with the master key and sub keys encrypted + /// using a new password and the passed in algorithm. + /// + /// The PgpSecretKeyRing to be copied. + /// The current password for key. + /// The new password for the key. + /// The algorithm to be used for the encryption. + /// Source of randomness. + public static PgpSecretKeyRing CopyWithNewPassword( + PgpSecretKeyRing ring, + char[] oldPassPhrase, + char[] newPassPhrase, + SymmetricKeyAlgorithmTag newEncAlgorithm, + SecureRandom rand) + { + IList newKeys = Platform.CreateArrayList(ring.keys.Count); + foreach (PgpSecretKey secretKey in ring.GetSecretKeys()) + { + if (secretKey.IsPrivateKeyEmpty) + { + newKeys.Add(secretKey); + } + else + { + newKeys.Add(PgpSecretKey.CopyWithNewPassword(secretKey, oldPassPhrase, newPassPhrase, newEncAlgorithm, rand)); + } + } + + return new PgpSecretKeyRing(newKeys, ring.extraPubKeys); + } + + /// + /// Returns a new key ring with the secret key passed in either added or + /// replacing an existing one with the same key ID. + /// + /// The secret key ring to be modified. + /// The secret key to be inserted. + /// A new PgpSecretKeyRing + public static PgpSecretKeyRing InsertSecretKey( + PgpSecretKeyRing secRing, + PgpSecretKey secKey) + { + IList keys = Platform.CreateArrayList(secRing.keys); + bool found = false; + bool masterFound = false; + + for (int i = 0; i != keys.Count; i++) + { + PgpSecretKey key = (PgpSecretKey) keys[i]; + + if (key.KeyId == secKey.KeyId) + { + found = true; + keys[i] = secKey; + } + if (key.IsMasterKey) + { + masterFound = true; + } + } + + if (!found) + { + if (secKey.IsMasterKey) + { + if (masterFound) + throw new ArgumentException("cannot add a master key to a ring that already has one"); + + keys.Insert(0, secKey); + } + else + { + keys.Add(secKey); + } + } + + return new PgpSecretKeyRing(keys, secRing.extraPubKeys); + } + + /// Returns a new key ring with the secret key passed in removed from the key ring. + /// The secret key ring to be modified. + /// The secret key to be removed. + /// A new PgpSecretKeyRing, or null if secKey is not found. + public static PgpSecretKeyRing RemoveSecretKey( + PgpSecretKeyRing secRing, + PgpSecretKey secKey) + { + IList keys = Platform.CreateArrayList(secRing.keys); + bool found = false; + + for (int i = 0; i < keys.Count; i++) + { + PgpSecretKey key = (PgpSecretKey)keys[i]; + + if (key.KeyId == secKey.KeyId) + { + found = true; + keys.RemoveAt(i); + } + } + + return found ? new PgpSecretKeyRing(keys, secRing.extraPubKeys) : null; + } + } +} diff --git a/bc-sharp-crypto/src/openpgp/PgpSecretKeyRingBundle.cs b/bc-sharp-crypto/src/openpgp/PgpSecretKeyRingBundle.cs new file mode 100644 index 0000000..c9f4d39 --- /dev/null +++ b/bc-sharp-crypto/src/openpgp/PgpSecretKeyRingBundle.cs @@ -0,0 +1,280 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; + +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + /// + /// Often a PGP key ring file is made up of a succession of master/sub-key key rings. + /// If you want to read an entire secret key file in one hit this is the class for you. + /// + public class PgpSecretKeyRingBundle + { + private readonly IDictionary secretRings; + private readonly IList order; + + private PgpSecretKeyRingBundle( + IDictionary secretRings, + IList order) + { + this.secretRings = secretRings; + this.order = order; + } + + public PgpSecretKeyRingBundle( + byte[] encoding) + : this(new MemoryStream(encoding, false)) + { + } + + /// Build a PgpSecretKeyRingBundle from the passed in input stream. + /// Input stream containing data. + /// If a problem parsing the stream occurs. + /// If an object is encountered which isn't a PgpSecretKeyRing. + public PgpSecretKeyRingBundle( + Stream inputStream) + : this(new PgpObjectFactory(inputStream).AllPgpObjects()) + { + } + + public PgpSecretKeyRingBundle( + IEnumerable e) + { + this.secretRings = Platform.CreateHashtable(); + this.order = Platform.CreateArrayList(); + + foreach (object obj in e) + { + PgpSecretKeyRing pgpSecret = obj as PgpSecretKeyRing; + + if (pgpSecret == null) + { + throw new PgpException(Platform.GetTypeName(obj) + " found where PgpSecretKeyRing expected"); + } + + long key = pgpSecret.GetPublicKey().KeyId; + secretRings.Add(key, pgpSecret); + order.Add(key); + } + } + + [Obsolete("Use 'Count' property instead")] + public int Size + { + get { return order.Count; } + } + + /// Return the number of rings in this collection. + public int Count + { + get { return order.Count; } + } + + /// Allow enumeration of the secret key rings making up this collection. + public IEnumerable GetKeyRings() + { + return new EnumerableProxy(secretRings.Values); + } + + /// Allow enumeration of the key rings associated with the passed in userId. + /// The user ID to be matched. + /// An IEnumerable of key rings which matched (possibly none). + public IEnumerable GetKeyRings( + string userId) + { + return GetKeyRings(userId, false, false); + } + + /// Allow enumeration of the key rings associated with the passed in userId. + /// The user ID to be matched. + /// If true, userId need only be a substring of an actual ID string to match. + /// An IEnumerable of key rings which matched (possibly none). + public IEnumerable GetKeyRings( + string userId, + bool matchPartial) + { + return GetKeyRings(userId, matchPartial, false); + } + + /// Allow enumeration of the key rings associated with the passed in userId. + /// The user ID to be matched. + /// If true, userId need only be a substring of an actual ID string to match. + /// If true, case is ignored in user ID comparisons. + /// An IEnumerable of key rings which matched (possibly none). + public IEnumerable GetKeyRings( + string userId, + bool matchPartial, + bool ignoreCase) + { + IList rings = Platform.CreateArrayList(); + + if (ignoreCase) + { + userId = Platform.ToUpperInvariant(userId); + } + + foreach (PgpSecretKeyRing secRing in GetKeyRings()) + { + foreach (string nextUserID in secRing.GetSecretKey().UserIds) + { + string next = nextUserID; + if (ignoreCase) + { + next = Platform.ToUpperInvariant(next); + } + + if (matchPartial) + { + if (Platform.IndexOf(next, userId) > -1) + { + rings.Add(secRing); + } + } + else + { + if (next.Equals(userId)) + { + rings.Add(secRing); + } + } + } + } + + return new EnumerableProxy(rings); + } + + /// Return the PGP secret key associated with the given key id. + /// The ID of the secret key to return. + public PgpSecretKey GetSecretKey( + long keyId) + { + foreach (PgpSecretKeyRing secRing in GetKeyRings()) + { + PgpSecretKey sec = secRing.GetSecretKey(keyId); + + if (sec != null) + { + return sec; + } + } + + return null; + } + + /// Return the secret key ring which contains the key referred to by keyId + /// The ID of the secret key + public PgpSecretKeyRing GetSecretKeyRing( + long keyId) + { + long id = keyId; + + if (secretRings.Contains(id)) + { + return (PgpSecretKeyRing) secretRings[id]; + } + + foreach (PgpSecretKeyRing secretRing in GetKeyRings()) + { + PgpSecretKey secret = secretRing.GetSecretKey(keyId); + + if (secret != null) + { + return secretRing; + } + } + + return null; + } + + /// + /// Return true if a key matching the passed in key ID is present, false otherwise. + /// + /// key ID to look for. + public bool Contains( + long keyID) + { + return GetSecretKey(keyID) != null; + } + + public byte[] GetEncoded() + { + MemoryStream bOut = new MemoryStream(); + + Encode(bOut); + + return bOut.ToArray(); + } + + public void Encode( + Stream outStr) + { + BcpgOutputStream bcpgOut = BcpgOutputStream.Wrap(outStr); + + foreach (long key in order) + { + PgpSecretKeyRing pub = (PgpSecretKeyRing) secretRings[key]; + + pub.Encode(bcpgOut); + } + } + + /// + /// Return a new bundle containing the contents of the passed in bundle and + /// the passed in secret key ring. + /// + /// The PgpSecretKeyRingBundle the key ring is to be added to. + /// The key ring to be added. + /// A new PgpSecretKeyRingBundle merging the current one with the passed in key ring. + /// If the keyId for the passed in key ring is already present. + public static PgpSecretKeyRingBundle AddSecretKeyRing( + PgpSecretKeyRingBundle bundle, + PgpSecretKeyRing secretKeyRing) + { + long key = secretKeyRing.GetPublicKey().KeyId; + + if (bundle.secretRings.Contains(key)) + { + throw new ArgumentException("Collection already contains a key with a keyId for the passed in ring."); + } + + IDictionary newSecretRings = Platform.CreateHashtable(bundle.secretRings); + IList newOrder = Platform.CreateArrayList(bundle.order); + + newSecretRings[key] = secretKeyRing; + newOrder.Add(key); + + return new PgpSecretKeyRingBundle(newSecretRings, newOrder); + } + + /// + /// Return a new bundle containing the contents of the passed in bundle with + /// the passed in secret key ring removed. + /// + /// The PgpSecretKeyRingBundle the key ring is to be removed from. + /// The key ring to be removed. + /// A new PgpSecretKeyRingBundle not containing the passed in key ring. + /// If the keyId for the passed in key ring is not present. + public static PgpSecretKeyRingBundle RemoveSecretKeyRing( + PgpSecretKeyRingBundle bundle, + PgpSecretKeyRing secretKeyRing) + { + long key = secretKeyRing.GetPublicKey().KeyId; + + if (!bundle.secretRings.Contains(key)) + { + throw new ArgumentException("Collection does not contain a key with a keyId for the passed in ring."); + } + + IDictionary newSecretRings = Platform.CreateHashtable(bundle.secretRings); + IList newOrder = Platform.CreateArrayList(bundle.order); + + newSecretRings.Remove(key); + newOrder.Remove(key); + + return new PgpSecretKeyRingBundle(newSecretRings, newOrder); + } + } +} diff --git a/bc-sharp-crypto/src/openpgp/PgpSignature.cs b/bc-sharp-crypto/src/openpgp/PgpSignature.cs new file mode 100644 index 0000000..c8c541b --- /dev/null +++ b/bc-sharp-crypto/src/openpgp/PgpSignature.cs @@ -0,0 +1,447 @@ +using System; +using System.IO; +using Org.BouncyCastle.Asn1; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Date; + +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + /// A PGP signature object. + public class PgpSignature + { + public const int BinaryDocument = 0x00; + public const int CanonicalTextDocument = 0x01; + public const int StandAlone = 0x02; + + public const int DefaultCertification = 0x10; + public const int NoCertification = 0x11; + public const int CasualCertification = 0x12; + public const int PositiveCertification = 0x13; + + public const int SubkeyBinding = 0x18; + public const int PrimaryKeyBinding = 0x19; + public const int DirectKey = 0x1f; + public const int KeyRevocation = 0x20; + public const int SubkeyRevocation = 0x28; + public const int CertificationRevocation = 0x30; + public const int Timestamp = 0x40; + + private readonly SignaturePacket sigPck; + private readonly int signatureType; + private readonly TrustPacket trustPck; + + private ISigner sig; + private byte lastb; // Initial value anything but '\r' + + internal PgpSignature( + BcpgInputStream bcpgInput) + : this((SignaturePacket)bcpgInput.ReadPacket()) + { + } + + internal PgpSignature( + SignaturePacket sigPacket) + : this(sigPacket, null) + { + } + + internal PgpSignature( + SignaturePacket sigPacket, + TrustPacket trustPacket) + { + if (sigPacket == null) + throw new ArgumentNullException("sigPacket"); + + this.sigPck = sigPacket; + this.signatureType = sigPck.SignatureType; + this.trustPck = trustPacket; + } + + private void GetSig() + { + this.sig = SignerUtilities.GetSigner( + PgpUtilities.GetSignatureName(sigPck.KeyAlgorithm, sigPck.HashAlgorithm)); + } + + /// The OpenPGP version number for this signature. + public int Version + { + get { return sigPck.Version; } + } + + /// The key algorithm associated with this signature. + public PublicKeyAlgorithmTag KeyAlgorithm + { + get { return sigPck.KeyAlgorithm; } + } + + /// The hash algorithm associated with this signature. + public HashAlgorithmTag HashAlgorithm + { + get { return sigPck.HashAlgorithm; } + } + + /// Return true if this signature represents a certification. + public bool IsCertification() + { + return IsCertification(SignatureType); + } + + public void InitVerify( + PgpPublicKey pubKey) + { + lastb = 0; + if (sig == null) + { + GetSig(); + } + try + { + sig.Init(false, pubKey.GetKey()); + } + catch (InvalidKeyException e) + { + throw new PgpException("invalid key.", e); + } + } + + public void Update( + byte b) + { + if (signatureType == CanonicalTextDocument) + { + doCanonicalUpdateByte(b); + } + else + { + sig.Update(b); + } + } + + private void doCanonicalUpdateByte( + byte b) + { + if (b == '\r') + { + doUpdateCRLF(); + } + else if (b == '\n') + { + if (lastb != '\r') + { + doUpdateCRLF(); + } + } + else + { + sig.Update(b); + } + + lastb = b; + } + + private void doUpdateCRLF() + { + sig.Update((byte)'\r'); + sig.Update((byte)'\n'); + } + + public void Update( + params byte[] bytes) + { + Update(bytes, 0, bytes.Length); + } + + public void Update( + byte[] bytes, + int off, + int length) + { + if (signatureType == CanonicalTextDocument) + { + int finish = off + length; + + for (int i = off; i != finish; i++) + { + doCanonicalUpdateByte(bytes[i]); + } + } + else + { + sig.BlockUpdate(bytes, off, length); + } + } + + public bool Verify() + { + byte[] trailer = GetSignatureTrailer(); + sig.BlockUpdate(trailer, 0, trailer.Length); + + return sig.VerifySignature(GetSignature()); + } + + private void UpdateWithIdData( + int header, + byte[] idBytes) + { + this.Update( + (byte) header, + (byte)(idBytes.Length >> 24), + (byte)(idBytes.Length >> 16), + (byte)(idBytes.Length >> 8), + (byte)(idBytes.Length)); + this.Update(idBytes); + } + + private void UpdateWithPublicKey( + PgpPublicKey key) + { + byte[] keyBytes = GetEncodedPublicKey(key); + + this.Update( + (byte) 0x99, + (byte)(keyBytes.Length >> 8), + (byte)(keyBytes.Length)); + this.Update(keyBytes); + } + + /// + /// Verify the signature as certifying the passed in public key as associated + /// with the passed in user attributes. + /// + /// User attributes the key was stored under. + /// The key to be verified. + /// True, if the signature matches, false otherwise. + public bool VerifyCertification( + PgpUserAttributeSubpacketVector userAttributes, + PgpPublicKey key) + { + UpdateWithPublicKey(key); + + // + // hash in the userAttributes + // + try + { + MemoryStream bOut = new MemoryStream(); + foreach (UserAttributeSubpacket packet in userAttributes.ToSubpacketArray()) + { + packet.Encode(bOut); + } + UpdateWithIdData(0xd1, bOut.ToArray()); + } + catch (IOException e) + { + throw new PgpException("cannot encode subpacket array", e); + } + + this.Update(sigPck.GetSignatureTrailer()); + + return sig.VerifySignature(this.GetSignature()); + } + + /// + /// Verify the signature as certifying the passed in public key as associated + /// with the passed in ID. + /// + /// ID the key was stored under. + /// The key to be verified. + /// True, if the signature matches, false otherwise. + public bool VerifyCertification( + string id, + PgpPublicKey key) + { + UpdateWithPublicKey(key); + + // + // hash in the id + // + UpdateWithIdData(0xb4, Strings.ToUtf8ByteArray(id)); + + Update(sigPck.GetSignatureTrailer()); + + return sig.VerifySignature(GetSignature()); + } + + /// Verify a certification for the passed in key against the passed in master key. + /// The key we are verifying against. + /// The key we are verifying. + /// True, if the certification is valid, false otherwise. + public bool VerifyCertification( + PgpPublicKey masterKey, + PgpPublicKey pubKey) + { + UpdateWithPublicKey(masterKey); + UpdateWithPublicKey(pubKey); + + Update(sigPck.GetSignatureTrailer()); + + return sig.VerifySignature(GetSignature()); + } + + /// Verify a key certification, such as revocation, for the passed in key. + /// The key we are checking. + /// True, if the certification is valid, false otherwise. + public bool VerifyCertification( + PgpPublicKey pubKey) + { + if (SignatureType != KeyRevocation + && SignatureType != SubkeyRevocation) + { + throw new InvalidOperationException("signature is not a key signature"); + } + + UpdateWithPublicKey(pubKey); + + Update(sigPck.GetSignatureTrailer()); + + return sig.VerifySignature(GetSignature()); + } + + public int SignatureType + { + get { return sigPck.SignatureType; } + } + + /// The ID of the key that created the signature. + public long KeyId + { + get { return sigPck.KeyId; } + } + + [Obsolete("Use 'CreationTime' property instead")] + public DateTime GetCreationTime() + { + return CreationTime; + } + + /// The creation time of this signature. + public DateTime CreationTime + { + get { return DateTimeUtilities.UnixMsToDateTime(sigPck.CreationTime); } + } + + public byte[] GetSignatureTrailer() + { + return sigPck.GetSignatureTrailer(); + } + + /// + /// Return true if the signature has either hashed or unhashed subpackets. + /// + public bool HasSubpackets + { + get + { + return sigPck.GetHashedSubPackets() != null + || sigPck.GetUnhashedSubPackets() != null; + } + } + + public PgpSignatureSubpacketVector GetHashedSubPackets() + { + return createSubpacketVector(sigPck.GetHashedSubPackets()); + } + + public PgpSignatureSubpacketVector GetUnhashedSubPackets() + { + return createSubpacketVector(sigPck.GetUnhashedSubPackets()); + } + + private PgpSignatureSubpacketVector createSubpacketVector(SignatureSubpacket[] pcks) + { + return pcks == null ? null : new PgpSignatureSubpacketVector(pcks); + } + + public byte[] GetSignature() + { + MPInteger[] sigValues = sigPck.GetSignature(); + byte[] signature; + + if (sigValues != null) + { + if (sigValues.Length == 1) // an RSA signature + { + signature = sigValues[0].Value.ToByteArrayUnsigned(); + } + else + { + try + { + signature = new DerSequence( + new DerInteger(sigValues[0].Value), + new DerInteger(sigValues[1].Value)).GetEncoded(); + } + catch (IOException e) + { + throw new PgpException("exception encoding DSA sig.", e); + } + } + } + else + { + signature = sigPck.GetSignatureBytes(); + } + + return signature; + } + + // TODO Handle the encoding stuff by subclassing BcpgObject? + public byte[] GetEncoded() + { + MemoryStream bOut = new MemoryStream(); + + Encode(bOut); + + return bOut.ToArray(); + } + + public void Encode( + Stream outStream) + { + BcpgOutputStream bcpgOut = BcpgOutputStream.Wrap(outStream); + + bcpgOut.WritePacket(sigPck); + + if (trustPck != null) + { + bcpgOut.WritePacket(trustPck); + } + } + + private byte[] GetEncodedPublicKey( + PgpPublicKey pubKey) + { + try + { + return pubKey.publicPk.GetEncodedContents(); + } + catch (IOException e) + { + throw new PgpException("exception preparing key.", e); + } + } + + /// + /// Return true if the passed in signature type represents a certification, false if the signature type is not. + /// + /// + /// true if signatureType is a certification, false otherwise. + public static bool IsCertification(int signatureType) + { + switch (signatureType) + { + case DefaultCertification: + case NoCertification: + case CasualCertification: + case PositiveCertification: + return true; + default: + return false; + } + } + } +} diff --git a/bc-sharp-crypto/src/openpgp/PgpSignatureGenerator.cs b/bc-sharp-crypto/src/openpgp/PgpSignatureGenerator.cs new file mode 100644 index 0000000..c530968 --- /dev/null +++ b/bc-sharp-crypto/src/openpgp/PgpSignatureGenerator.cs @@ -0,0 +1,393 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Bcpg.Sig; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + /// Generator for PGP signatures. + // TODO Should be able to implement ISigner? + public class PgpSignatureGenerator + { + private static readonly SignatureSubpacket[] EmptySignatureSubpackets = new SignatureSubpacket[0]; + + private PublicKeyAlgorithmTag keyAlgorithm; + private HashAlgorithmTag hashAlgorithm; + private PgpPrivateKey privKey; + private ISigner sig; + private IDigest dig; + private int signatureType; + private byte lastb; + + private SignatureSubpacket[] unhashed = EmptySignatureSubpackets; + private SignatureSubpacket[] hashed = EmptySignatureSubpackets; + + /// Create a generator for the passed in keyAlgorithm and hashAlgorithm codes. + public PgpSignatureGenerator( + PublicKeyAlgorithmTag keyAlgorithm, + HashAlgorithmTag hashAlgorithm) + { + this.keyAlgorithm = keyAlgorithm; + this.hashAlgorithm = hashAlgorithm; + + dig = DigestUtilities.GetDigest(PgpUtilities.GetDigestName(hashAlgorithm)); + sig = SignerUtilities.GetSigner(PgpUtilities.GetSignatureName(keyAlgorithm, hashAlgorithm)); + } + + /// Initialise the generator for signing. + public void InitSign( + int sigType, + PgpPrivateKey key) + { + InitSign(sigType, key, null); + } + + /// Initialise the generator for signing. + public void InitSign( + int sigType, + PgpPrivateKey key, + SecureRandom random) + { + this.privKey = key; + this.signatureType = sigType; + + try + { + ICipherParameters cp = key.Key; + if (random != null) + { + cp = new ParametersWithRandom(key.Key, random); + } + + sig.Init(true, cp); + } + catch (InvalidKeyException e) + { + throw new PgpException("invalid key.", e); + } + + dig.Reset(); + lastb = 0; + } + + public void Update( + byte b) + { + if (signatureType == PgpSignature.CanonicalTextDocument) + { + doCanonicalUpdateByte(b); + } + else + { + doUpdateByte(b); + } + } + + private void doCanonicalUpdateByte( + byte b) + { + if (b == '\r') + { + doUpdateCRLF(); + } + else if (b == '\n') + { + if (lastb != '\r') + { + doUpdateCRLF(); + } + } + else + { + doUpdateByte(b); + } + + lastb = b; + } + + private void doUpdateCRLF() + { + doUpdateByte((byte)'\r'); + doUpdateByte((byte)'\n'); + } + + private void doUpdateByte( + byte b) + { + sig.Update(b); + dig.Update(b); + } + + public void Update( + params byte[] b) + { + Update(b, 0, b.Length); + } + + public void Update( + byte[] b, + int off, + int len) + { + if (signatureType == PgpSignature.CanonicalTextDocument) + { + int finish = off + len; + + for (int i = off; i != finish; i++) + { + doCanonicalUpdateByte(b[i]); + } + } + else + { + sig.BlockUpdate(b, off, len); + dig.BlockUpdate(b, off, len); + } + } + + public void SetHashedSubpackets( + PgpSignatureSubpacketVector hashedPackets) + { + hashed = hashedPackets == null + ? EmptySignatureSubpackets + : hashedPackets.ToSubpacketArray(); + } + + public void SetUnhashedSubpackets( + PgpSignatureSubpacketVector unhashedPackets) + { + unhashed = unhashedPackets == null + ? EmptySignatureSubpackets + : unhashedPackets.ToSubpacketArray(); + } + + /// Return the one pass header associated with the current signature. + public PgpOnePassSignature GenerateOnePassVersion( + bool isNested) + { + return new PgpOnePassSignature( + new OnePassSignaturePacket( + signatureType, hashAlgorithm, keyAlgorithm, privKey.KeyId, isNested)); + } + + /// Return a signature object containing the current signature state. + public PgpSignature Generate() + { + SignatureSubpacket[] hPkts = hashed, unhPkts = unhashed; + + if (!packetPresent(hashed, SignatureSubpacketTag.CreationTime)) + { + hPkts = insertSubpacket(hPkts, new SignatureCreationTime(false, DateTime.UtcNow)); + } + + if (!packetPresent(hashed, SignatureSubpacketTag.IssuerKeyId) + && !packetPresent(unhashed, SignatureSubpacketTag.IssuerKeyId)) + { + unhPkts = insertSubpacket(unhPkts, new IssuerKeyId(false, privKey.KeyId)); + } + + int version = 4; + byte[] hData; + + try + { + MemoryStream hOut = new MemoryStream(); + + for (int i = 0; i != hPkts.Length; i++) + { + hPkts[i].Encode(hOut); + } + + byte[] data = hOut.ToArray(); + + MemoryStream sOut = new MemoryStream(data.Length + 6); + sOut.WriteByte((byte)version); + sOut.WriteByte((byte)signatureType); + sOut.WriteByte((byte)keyAlgorithm); + sOut.WriteByte((byte)hashAlgorithm); + sOut.WriteByte((byte)(data.Length >> 8)); + sOut.WriteByte((byte)data.Length); + sOut.Write(data, 0, data.Length); + + hData = sOut.ToArray(); + } + catch (IOException e) + { + throw new PgpException("exception encoding hashed data.", e); + } + + sig.BlockUpdate(hData, 0, hData.Length); + dig.BlockUpdate(hData, 0, hData.Length); + + hData = new byte[] + { + (byte) version, + 0xff, + (byte)(hData.Length >> 24), + (byte)(hData.Length >> 16), + (byte)(hData.Length >> 8), + (byte) hData.Length + }; + + sig.BlockUpdate(hData, 0, hData.Length); + dig.BlockUpdate(hData, 0, hData.Length); + + byte[] sigBytes = sig.GenerateSignature(); + byte[] digest = DigestUtilities.DoFinal(dig); + byte[] fingerPrint = new byte[] { digest[0], digest[1] }; + + // an RSA signature + bool isRsa = keyAlgorithm == PublicKeyAlgorithmTag.RsaSign + || keyAlgorithm == PublicKeyAlgorithmTag.RsaGeneral; + + MPInteger[] sigValues = isRsa + ? PgpUtilities.RsaSigToMpi(sigBytes) + : PgpUtilities.DsaSigToMpi(sigBytes); + + return new PgpSignature( + new SignaturePacket(signatureType, privKey.KeyId, keyAlgorithm, + hashAlgorithm, hPkts, unhPkts, fingerPrint, sigValues)); + } + + /// Generate a certification for the passed in ID and key. + /// The ID we are certifying against the public key. + /// The key we are certifying against the ID. + /// The certification. + public PgpSignature GenerateCertification( + string id, + PgpPublicKey pubKey) + { + UpdateWithPublicKey(pubKey); + + // + // hash in the id + // + UpdateWithIdData(0xb4, Strings.ToUtf8ByteArray(id)); + + return Generate(); + } + + /// Generate a certification for the passed in userAttributes. + /// The ID we are certifying against the public key. + /// The key we are certifying against the ID. + /// The certification. + public PgpSignature GenerateCertification( + PgpUserAttributeSubpacketVector userAttributes, + PgpPublicKey pubKey) + { + UpdateWithPublicKey(pubKey); + + // + // hash in the attributes + // + try + { + MemoryStream bOut = new MemoryStream(); + foreach (UserAttributeSubpacket packet in userAttributes.ToSubpacketArray()) + { + packet.Encode(bOut); + } + UpdateWithIdData(0xd1, bOut.ToArray()); + } + catch (IOException e) + { + throw new PgpException("cannot encode subpacket array", e); + } + + return this.Generate(); + } + + /// Generate a certification for the passed in key against the passed in master key. + /// The key we are certifying against. + /// The key we are certifying. + /// The certification. + public PgpSignature GenerateCertification( + PgpPublicKey masterKey, + PgpPublicKey pubKey) + { + UpdateWithPublicKey(masterKey); + UpdateWithPublicKey(pubKey); + + return Generate(); + } + + /// Generate a certification, such as a revocation, for the passed in key. + /// The key we are certifying. + /// The certification. + public PgpSignature GenerateCertification( + PgpPublicKey pubKey) + { + UpdateWithPublicKey(pubKey); + + return Generate(); + } + + private byte[] GetEncodedPublicKey( + PgpPublicKey pubKey) + { + try + { + return pubKey.publicPk.GetEncodedContents(); + } + catch (IOException e) + { + throw new PgpException("exception preparing key.", e); + } + } + + private bool packetPresent( + SignatureSubpacket[] packets, + SignatureSubpacketTag type) + { + for (int i = 0; i != packets.Length; i++) + { + if (packets[i].SubpacketType == type) + { + return true; + } + } + + return false; + } + + private SignatureSubpacket[] insertSubpacket( + SignatureSubpacket[] packets, + SignatureSubpacket subpacket) + { + SignatureSubpacket[] tmp = new SignatureSubpacket[packets.Length + 1]; + tmp[0] = subpacket; + packets.CopyTo(tmp, 1); + return tmp; + } + + private void UpdateWithIdData( + int header, + byte[] idBytes) + { + this.Update( + (byte) header, + (byte)(idBytes.Length >> 24), + (byte)(idBytes.Length >> 16), + (byte)(idBytes.Length >> 8), + (byte)(idBytes.Length)); + this.Update(idBytes); + } + + private void UpdateWithPublicKey( + PgpPublicKey key) + { + byte[] keyBytes = GetEncodedPublicKey(key); + + this.Update( + (byte) 0x99, + (byte)(keyBytes.Length >> 8), + (byte)(keyBytes.Length)); + this.Update(keyBytes); + } + } +} diff --git a/bc-sharp-crypto/src/openpgp/PgpSignatureList.cs b/bc-sharp-crypto/src/openpgp/PgpSignatureList.cs new file mode 100644 index 0000000..61976fc --- /dev/null +++ b/bc-sharp-crypto/src/openpgp/PgpSignatureList.cs @@ -0,0 +1,51 @@ +using System; + +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + /// A list of PGP signatures - normally in the signature block after literal data. + public class PgpSignatureList + : PgpObject + { + private PgpSignature[] sigs; + + public PgpSignatureList( + PgpSignature[] sigs) + { + this.sigs = (PgpSignature[]) sigs.Clone(); + } + + public PgpSignatureList( + PgpSignature sig) + { + this.sigs = new PgpSignature[]{ sig }; + } + + public PgpSignature this[int index] + { + get { return sigs[index]; } + } + + [Obsolete("Use 'object[index]' syntax instead")] + public PgpSignature Get( + int index) + { + return this[index]; + } + + [Obsolete("Use 'Count' property instead")] + public int Size + { + get { return sigs.Length; } + } + + public int Count + { + get { return sigs.Length; } + } + + public bool IsEmpty + { + get { return (sigs.Length == 0); } + } + } +} diff --git a/bc-sharp-crypto/src/openpgp/PgpSignatureSubpacketGenerator.cs b/bc-sharp-crypto/src/openpgp/PgpSignatureSubpacketGenerator.cs new file mode 100644 index 0000000..d2177d0 --- /dev/null +++ b/bc-sharp-crypto/src/openpgp/PgpSignatureSubpacketGenerator.cs @@ -0,0 +1,210 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Bcpg.Sig; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + /// Generator for signature subpackets. + public class PgpSignatureSubpacketGenerator + { + private IList list = Platform.CreateArrayList(); + + public void SetRevocable( + bool isCritical, + bool isRevocable) + { + list.Add(new Revocable(isCritical, isRevocable)); + } + + public void SetExportable( + bool isCritical, + bool isExportable) + { + list.Add(new Exportable(isCritical, isExportable)); + } + + public void SetFeature( + bool isCritical, + byte feature) + { + list.Add(new Features(isCritical, feature)); + } + + /// + /// Add a TrustSignature packet to the signature. The values for depth and trust are largely + /// installation dependent but there are some guidelines in RFC 4880 - 5.2.3.13. + /// + /// true if the packet is critical. + /// depth level. + /// trust amount. + public void SetTrust( + bool isCritical, + int depth, + int trustAmount) + { + list.Add(new TrustSignature(isCritical, depth, trustAmount)); + } + + /// + /// Set the number of seconds a key is valid for after the time of its creation. + /// A value of zero means the key never expires. + /// + /// True, if should be treated as critical, false otherwise. + /// The number of seconds the key is valid, or zero if no expiry. + public void SetKeyExpirationTime( + bool isCritical, + long seconds) + { + list.Add(new KeyExpirationTime(isCritical, seconds)); + } + + /// + /// Set the number of seconds a signature is valid for after the time of its creation. + /// A value of zero means the signature never expires. + /// + /// True, if should be treated as critical, false otherwise. + /// The number of seconds the signature is valid, or zero if no expiry. + public void SetSignatureExpirationTime( + bool isCritical, + long seconds) + { + list.Add(new SignatureExpirationTime(isCritical, seconds)); + } + + /// + /// Set the creation time for the signature. + ///

    + /// Note: this overrides the generation of a creation time when the signature + /// is generated.

    + ///
    + public void SetSignatureCreationTime( + bool isCritical, + DateTime date) + { + list.Add(new SignatureCreationTime(isCritical, date)); + } + + public void SetPreferredHashAlgorithms( + bool isCritical, + int[] algorithms) + { + list.Add(new PreferredAlgorithms(SignatureSubpacketTag.PreferredHashAlgorithms, isCritical, algorithms)); + } + + public void SetPreferredSymmetricAlgorithms( + bool isCritical, + int[] algorithms) + { + list.Add(new PreferredAlgorithms(SignatureSubpacketTag.PreferredSymmetricAlgorithms, isCritical, algorithms)); + } + + public void SetPreferredCompressionAlgorithms( + bool isCritical, + int[] algorithms) + { + list.Add(new PreferredAlgorithms(SignatureSubpacketTag.PreferredCompressionAlgorithms, isCritical, algorithms)); + } + + public void SetKeyFlags( + bool isCritical, + int flags) + { + list.Add(new KeyFlags(isCritical, flags)); + } + + public void SetSignerUserId( + bool isCritical, + string userId) + { + if (userId == null) + throw new ArgumentNullException("userId"); + + list.Add(new SignerUserId(isCritical, userId)); + } + + public void SetSignerUserId( + bool isCritical, + byte[] rawUserId) + { + if (rawUserId == null) + throw new ArgumentNullException("rawUserId"); + + list.Add(new SignerUserId(isCritical, false, rawUserId)); + } + + public void SetEmbeddedSignature( + bool isCritical, + PgpSignature pgpSignature) + { + byte[] sig = pgpSignature.GetEncoded(); + byte[] data; + + // TODO Should be >= ? + if (sig.Length - 1 > 256) + { + data = new byte[sig.Length - 3]; + } + else + { + data = new byte[sig.Length - 2]; + } + + Array.Copy(sig, sig.Length - data.Length, data, 0, data.Length); + + list.Add(new EmbeddedSignature(isCritical, false, data)); + } + + public void SetPrimaryUserId( + bool isCritical, + bool isPrimaryUserId) + { + list.Add(new PrimaryUserId(isCritical, isPrimaryUserId)); + } + + public void SetNotationData( + bool isCritical, + bool isHumanReadable, + string notationName, + string notationValue) + { + list.Add(new NotationData(isCritical, isHumanReadable, notationName, notationValue)); + } + + /// + /// Sets revocation reason sub packet + /// + public void SetRevocationReason(bool isCritical, RevocationReasonTag reason, + string description) + { + list.Add(new RevocationReason(isCritical, reason, description)); + } + + /// + /// Sets revocation key sub packet + /// + public void SetRevocationKey(bool isCritical, PublicKeyAlgorithmTag keyAlgorithm, byte[] fingerprint) + { + list.Add(new RevocationKey(isCritical, RevocationKeyTag.ClassDefault, keyAlgorithm, fingerprint)); + } + + /// + /// Sets issuer key sub packet + /// + public void SetIssuerKeyID(bool isCritical, long keyID) + { + list.Add(new IssuerKeyId(isCritical, keyID)); + } + + public PgpSignatureSubpacketVector Generate() + { + SignatureSubpacket[] a = new SignatureSubpacket[list.Count]; + for (int i = 0; i < list.Count; ++i) + { + a[i] = (SignatureSubpacket)list[i]; + } + return new PgpSignatureSubpacketVector(a); + } + } +} diff --git a/bc-sharp-crypto/src/openpgp/PgpSignatureSubpacketVector.cs b/bc-sharp-crypto/src/openpgp/PgpSignatureSubpacketVector.cs new file mode 100644 index 0000000..156243f --- /dev/null +++ b/bc-sharp-crypto/src/openpgp/PgpSignatureSubpacketVector.cs @@ -0,0 +1,239 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Bcpg.Sig; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + /// Container for a list of signature subpackets. + public class PgpSignatureSubpacketVector + { + private readonly SignatureSubpacket[] packets; + + internal PgpSignatureSubpacketVector( + SignatureSubpacket[] packets) + { + this.packets = packets; + } + + public SignatureSubpacket GetSubpacket( + SignatureSubpacketTag type) + { + for (int i = 0; i != packets.Length; i++) + { + if (packets[i].SubpacketType == type) + { + return packets[i]; + } + } + + return null; + } + + /** + * Return true if a particular subpacket type exists. + * + * @param type type to look for. + * @return true if present, false otherwise. + */ + public bool HasSubpacket( + SignatureSubpacketTag type) + { + return GetSubpacket(type) != null; + } + + /** + * Return all signature subpackets of the passed in type. + * @param type subpacket type code + * @return an array of zero or more matching subpackets. + */ + public SignatureSubpacket[] GetSubpackets( + SignatureSubpacketTag type) + { + int count = 0; + for (int i = 0; i < packets.Length; ++i) + { + if (packets[i].SubpacketType == type) + { + ++count; + } + } + + SignatureSubpacket[] result = new SignatureSubpacket[count]; + + int pos = 0; + for (int i = 0; i < packets.Length; ++i) + { + if (packets[i].SubpacketType == type) + { + result[pos++] = packets[i]; + } + } + + return result; + } + + public NotationData[] GetNotationDataOccurences() + { + SignatureSubpacket[] notations = GetSubpackets(SignatureSubpacketTag.NotationData); + NotationData[] vals = new NotationData[notations.Length]; + + for (int i = 0; i < notations.Length; i++) + { + vals[i] = (NotationData) notations[i]; + } + + return vals; + } + + public long GetIssuerKeyId() + { + SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.IssuerKeyId); + + return p == null ? 0 : ((IssuerKeyId) p).KeyId; + } + + public bool HasSignatureCreationTime() + { + return GetSubpacket(SignatureSubpacketTag.CreationTime) != null; + } + + public DateTime GetSignatureCreationTime() + { + SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.CreationTime); + + if (p == null) + { + throw new PgpException("SignatureCreationTime not available"); + } + + return ((SignatureCreationTime)p).GetTime(); + } + + /// + /// Return the number of seconds a signature is valid for after its creation date. + /// A value of zero means the signature never expires. + /// + /// Seconds a signature is valid for. + public long GetSignatureExpirationTime() + { + SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.ExpireTime); + + return p == null ? 0 : ((SignatureExpirationTime) p).Time; + } + + /// + /// Return the number of seconds a key is valid for after its creation date. + /// A value of zero means the key never expires. + /// + /// Seconds a signature is valid for. + public long GetKeyExpirationTime() + { + SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.KeyExpireTime); + + return p == null ? 0 : ((KeyExpirationTime) p).Time; + } + + public int[] GetPreferredHashAlgorithms() + { + SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.PreferredHashAlgorithms); + + return p == null ? null : ((PreferredAlgorithms) p).GetPreferences(); + } + + public int[] GetPreferredSymmetricAlgorithms() + { + SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.PreferredSymmetricAlgorithms); + + return p == null ? null : ((PreferredAlgorithms) p).GetPreferences(); + } + + public int[] GetPreferredCompressionAlgorithms() + { + SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.PreferredCompressionAlgorithms); + + return p == null ? null : ((PreferredAlgorithms) p).GetPreferences(); + } + + public int GetKeyFlags() + { + SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.KeyFlags); + + return p == null ? 0 : ((KeyFlags) p).Flags; + } + + public string GetSignerUserId() + { + SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.SignerUserId); + + return p == null ? null : ((SignerUserId) p).GetId(); + } + + public bool IsPrimaryUserId() + { + PrimaryUserId primaryId = (PrimaryUserId) + this.GetSubpacket(SignatureSubpacketTag.PrimaryUserId); + + if (primaryId != null) + { + return primaryId.IsPrimaryUserId(); + } + + return false; + } + + public SignatureSubpacketTag[] GetCriticalTags() + { + int count = 0; + for (int i = 0; i != packets.Length; i++) + { + if (packets[i].IsCritical()) + { + count++; + } + } + + SignatureSubpacketTag[] list = new SignatureSubpacketTag[count]; + + count = 0; + + for (int i = 0; i != packets.Length; i++) + { + if (packets[i].IsCritical()) + { + list[count++] = packets[i].SubpacketType; + } + } + + return list; + } + + public Features GetFeatures() + { + SignatureSubpacket p = this.GetSubpacket(SignatureSubpacketTag.Features); + + if (p == null) + return null; + + return new Features(p.IsCritical(), p.IsLongLength(), p.GetData()); + } + + [Obsolete("Use 'Count' property instead")] + public int Size + { + get { return packets.Length; } + } + + /// Return the number of packets this vector contains. + public int Count + { + get { return packets.Length; } + } + + internal SignatureSubpacket[] ToSubpacketArray() + { + return packets; + } + } +} diff --git a/bc-sharp-crypto/src/openpgp/PgpUserAttributeSubpacketVector.cs b/bc-sharp-crypto/src/openpgp/PgpUserAttributeSubpacketVector.cs new file mode 100644 index 0000000..4cdbeda --- /dev/null +++ b/bc-sharp-crypto/src/openpgp/PgpUserAttributeSubpacketVector.cs @@ -0,0 +1,81 @@ +using Org.BouncyCastle.Bcpg.Attr; + +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + /// Container for a list of user attribute subpackets. + public class PgpUserAttributeSubpacketVector + { + private readonly UserAttributeSubpacket[] packets; + + internal PgpUserAttributeSubpacketVector( + UserAttributeSubpacket[] packets) + { + this.packets = packets; + } + + public UserAttributeSubpacket GetSubpacket( + UserAttributeSubpacketTag type) + { + for (int i = 0; i != packets.Length; i++) + { + if (packets[i].SubpacketType == type) + { + return packets[i]; + } + } + + return null; + } + + public ImageAttrib GetImageAttribute() + { + UserAttributeSubpacket p = GetSubpacket(UserAttributeSubpacketTag.ImageAttribute); + + return p == null ? null : (ImageAttrib) p; + } + + internal UserAttributeSubpacket[] ToSubpacketArray() + { + return packets; + } + + public override bool Equals( + object obj) + { + if (obj == this) + return true; + + PgpUserAttributeSubpacketVector other = obj as PgpUserAttributeSubpacketVector; + + if (other == null) + return false; + + if (other.packets.Length != packets.Length) + { + return false; + } + + for (int i = 0; i != packets.Length; i++) + { + if (!other.packets[i].Equals(packets[i])) + { + return false; + } + } + + return true; + } + + public override int GetHashCode() + { + int code = 0; + + foreach (object o in packets) + { + code ^= o.GetHashCode(); + } + + return code; + } + } +} diff --git a/bc-sharp-crypto/src/openpgp/PgpUtilities.cs b/bc-sharp-crypto/src/openpgp/PgpUtilities.cs new file mode 100644 index 0000000..7d96dee --- /dev/null +++ b/bc-sharp-crypto/src/openpgp/PgpUtilities.cs @@ -0,0 +1,518 @@ +using System; +using System.IO; +using System.Text; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Encoders; +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + /// Basic utility class. + public sealed class PgpUtilities + { + private PgpUtilities() + { + } + + public static MPInteger[] DsaSigToMpi( + byte[] encoding) + { + DerInteger i1, i2; + + try + { + Asn1Sequence s = (Asn1Sequence) Asn1Object.FromByteArray(encoding); + + i1 = (DerInteger) s[0]; + i2 = (DerInteger) s[1]; + } + catch (IOException e) + { + throw new PgpException("exception encoding signature", e); + } + + return new MPInteger[]{ new MPInteger(i1.Value), new MPInteger(i2.Value) }; + } + + public static MPInteger[] RsaSigToMpi( + byte[] encoding) + { + return new MPInteger[]{ new MPInteger(new BigInteger(1, encoding)) }; + } + + public static string GetDigestName( + HashAlgorithmTag hashAlgorithm) + { + switch (hashAlgorithm) + { + case HashAlgorithmTag.Sha1: + return "SHA1"; + case HashAlgorithmTag.MD2: + return "MD2"; + case HashAlgorithmTag.MD5: + return "MD5"; + case HashAlgorithmTag.RipeMD160: + return "RIPEMD160"; + case HashAlgorithmTag.Sha224: + return "SHA224"; + case HashAlgorithmTag.Sha256: + return "SHA256"; + case HashAlgorithmTag.Sha384: + return "SHA384"; + case HashAlgorithmTag.Sha512: + return "SHA512"; + default: + throw new PgpException("unknown hash algorithm tag in GetDigestName: " + hashAlgorithm); + } + } + + public static string GetSignatureName( + PublicKeyAlgorithmTag keyAlgorithm, + HashAlgorithmTag hashAlgorithm) + { + string encAlg; + switch (keyAlgorithm) + { + case PublicKeyAlgorithmTag.RsaGeneral: + case PublicKeyAlgorithmTag.RsaSign: + encAlg = "RSA"; + break; + case PublicKeyAlgorithmTag.Dsa: + encAlg = "DSA"; + break; + case PublicKeyAlgorithmTag.ECDH: + encAlg = "ECDH"; + break; + case PublicKeyAlgorithmTag.ECDsa: + encAlg = "ECDSA"; + break; + case PublicKeyAlgorithmTag.ElGamalEncrypt: // in some malformed cases. + case PublicKeyAlgorithmTag.ElGamalGeneral: + encAlg = "ElGamal"; + break; + default: + throw new PgpException("unknown algorithm tag in signature:" + keyAlgorithm); + } + + return GetDigestName(hashAlgorithm) + "with" + encAlg; + } + + public static string GetSymmetricCipherName( + SymmetricKeyAlgorithmTag algorithm) + { + switch (algorithm) + { + case SymmetricKeyAlgorithmTag.Null: + return null; + case SymmetricKeyAlgorithmTag.TripleDes: + return "DESEDE"; + case SymmetricKeyAlgorithmTag.Idea: + return "IDEA"; + case SymmetricKeyAlgorithmTag.Cast5: + return "CAST5"; + case SymmetricKeyAlgorithmTag.Blowfish: + return "Blowfish"; + case SymmetricKeyAlgorithmTag.Safer: + return "SAFER"; + case SymmetricKeyAlgorithmTag.Des: + return "DES"; + case SymmetricKeyAlgorithmTag.Aes128: + return "AES"; + case SymmetricKeyAlgorithmTag.Aes192: + return "AES"; + case SymmetricKeyAlgorithmTag.Aes256: + return "AES"; + case SymmetricKeyAlgorithmTag.Twofish: + return "Twofish"; + case SymmetricKeyAlgorithmTag.Camellia128: + return "Camellia"; + case SymmetricKeyAlgorithmTag.Camellia192: + return "Camellia"; + case SymmetricKeyAlgorithmTag.Camellia256: + return "Camellia"; + default: + throw new PgpException("unknown symmetric algorithm: " + algorithm); + } + } + + public static int GetKeySize(SymmetricKeyAlgorithmTag algorithm) + { + int keySize; + switch (algorithm) + { + case SymmetricKeyAlgorithmTag.Des: + keySize = 64; + break; + case SymmetricKeyAlgorithmTag.Idea: + case SymmetricKeyAlgorithmTag.Cast5: + case SymmetricKeyAlgorithmTag.Blowfish: + case SymmetricKeyAlgorithmTag.Safer: + case SymmetricKeyAlgorithmTag.Aes128: + case SymmetricKeyAlgorithmTag.Camellia128: + keySize = 128; + break; + case SymmetricKeyAlgorithmTag.TripleDes: + case SymmetricKeyAlgorithmTag.Aes192: + case SymmetricKeyAlgorithmTag.Camellia192: + keySize = 192; + break; + case SymmetricKeyAlgorithmTag.Aes256: + case SymmetricKeyAlgorithmTag.Twofish: + case SymmetricKeyAlgorithmTag.Camellia256: + keySize = 256; + break; + default: + throw new PgpException("unknown symmetric algorithm: " + algorithm); + } + + return keySize; + } + + public static KeyParameter MakeKey( + SymmetricKeyAlgorithmTag algorithm, + byte[] keyBytes) + { + string algName = GetSymmetricCipherName(algorithm); + + return ParameterUtilities.CreateKeyParameter(algName, keyBytes); + } + + public static KeyParameter MakeRandomKey( + SymmetricKeyAlgorithmTag algorithm, + SecureRandom random) + { + int keySize = GetKeySize(algorithm); + byte[] keyBytes = new byte[(keySize + 7) / 8]; + random.NextBytes(keyBytes); + return MakeKey(algorithm, keyBytes); + } + + internal static byte[] EncodePassPhrase(char[] passPhrase, bool utf8) + { + return passPhrase == null + ? null + : utf8 + ? Encoding.UTF8.GetBytes(passPhrase) + : Strings.ToByteArray(passPhrase); + } + + /// + /// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is + /// the historical behaviour of the library (1.7 and earlier). + /// + public static KeyParameter MakeKeyFromPassPhrase(SymmetricKeyAlgorithmTag algorithm, S2k s2k, char[] passPhrase) + { + return DoMakeKeyFromPassPhrase(algorithm, s2k, EncodePassPhrase(passPhrase, false), true); + } + + /// + /// The passphrase is encoded to bytes using UTF8 (Encoding.UTF8.GetBytes). + /// + public static KeyParameter MakeKeyFromPassPhraseUtf8(SymmetricKeyAlgorithmTag algorithm, S2k s2k, char[] passPhrase) + { + return DoMakeKeyFromPassPhrase(algorithm, s2k, EncodePassPhrase(passPhrase, true), true); + } + + /// + /// Allows the caller to handle the encoding of the passphrase to bytes. + /// + public static KeyParameter MakeKeyFromPassPhraseRaw(SymmetricKeyAlgorithmTag algorithm, S2k s2k, byte[] rawPassPhrase) + { + return DoMakeKeyFromPassPhrase(algorithm, s2k, rawPassPhrase, false); + } + + internal static KeyParameter DoMakeKeyFromPassPhrase(SymmetricKeyAlgorithmTag algorithm, S2k s2k, byte[] rawPassPhrase, bool clearPassPhrase) + { + int keySize = GetKeySize(algorithm); + byte[] pBytes = rawPassPhrase; + byte[] keyBytes = new byte[(keySize + 7) / 8]; + + int generatedBytes = 0; + int loopCount = 0; + + while (generatedBytes < keyBytes.Length) + { + IDigest digest; + if (s2k != null) + { + string digestName = GetDigestName(s2k.HashAlgorithm); + + try + { + digest = DigestUtilities.GetDigest(digestName); + } + catch (Exception e) + { + throw new PgpException("can't find S2k digest", e); + } + + for (int i = 0; i != loopCount; i++) + { + digest.Update(0); + } + + byte[] iv = s2k.GetIV(); + + switch (s2k.Type) + { + case S2k.Simple: + digest.BlockUpdate(pBytes, 0, pBytes.Length); + break; + case S2k.Salted: + digest.BlockUpdate(iv, 0, iv.Length); + digest.BlockUpdate(pBytes, 0, pBytes.Length); + break; + case S2k.SaltedAndIterated: + long count = s2k.IterationCount; + digest.BlockUpdate(iv, 0, iv.Length); + digest.BlockUpdate(pBytes, 0, pBytes.Length); + + count -= iv.Length + pBytes.Length; + + while (count > 0) + { + if (count < iv.Length) + { + digest.BlockUpdate(iv, 0, (int)count); + break; + } + else + { + digest.BlockUpdate(iv, 0, iv.Length); + count -= iv.Length; + } + + if (count < pBytes.Length) + { + digest.BlockUpdate(pBytes, 0, (int)count); + count = 0; + } + else + { + digest.BlockUpdate(pBytes, 0, pBytes.Length); + count -= pBytes.Length; + } + } + break; + default: + throw new PgpException("unknown S2k type: " + s2k.Type); + } + } + else + { + try + { + digest = DigestUtilities.GetDigest("MD5"); + + for (int i = 0; i != loopCount; i++) + { + digest.Update(0); + } + + digest.BlockUpdate(pBytes, 0, pBytes.Length); + } + catch (Exception e) + { + throw new PgpException("can't find MD5 digest", e); + } + } + + byte[] dig = DigestUtilities.DoFinal(digest); + + if (dig.Length > (keyBytes.Length - generatedBytes)) + { + Array.Copy(dig, 0, keyBytes, generatedBytes, keyBytes.Length - generatedBytes); + } + else + { + Array.Copy(dig, 0, keyBytes, generatedBytes, dig.Length); + } + + generatedBytes += dig.Length; + + loopCount++; + } + + if (clearPassPhrase && rawPassPhrase != null) + { + Array.Clear(rawPassPhrase, 0, rawPassPhrase.Length); + } + + return MakeKey(algorithm, keyBytes); + } + +#if !PORTABLE || DOTNET + /// Write out the passed in file as a literal data packet. + public static void WriteFileToLiteralData( + Stream output, + char fileType, + FileInfo file) + { + PgpLiteralDataGenerator lData = new PgpLiteralDataGenerator(); + Stream pOut = lData.Open(output, fileType, file.Name, file.Length, file.LastWriteTime); + PipeFileContents(file, pOut, 4096); + } + + /// Write out the passed in file as a literal data packet in partial packet format. + public static void WriteFileToLiteralData( + Stream output, + char fileType, + FileInfo file, + byte[] buffer) + { + PgpLiteralDataGenerator lData = new PgpLiteralDataGenerator(); + Stream pOut = lData.Open(output, fileType, file.Name, file.LastWriteTime, buffer); + PipeFileContents(file, pOut, buffer.Length); + } + + private static void PipeFileContents(FileInfo file, Stream pOut, int bufSize) + { + FileStream inputStream = file.OpenRead(); + byte[] buf = new byte[bufSize]; + + int len; + while ((len = inputStream.Read(buf, 0, buf.Length)) > 0) + { + pOut.Write(buf, 0, len); + } + + Platform.Dispose(pOut); + Platform.Dispose(inputStream); + } +#endif + + private const int ReadAhead = 60; + + private static bool IsPossiblyBase64( + int ch) + { + return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') + || (ch >= '0' && ch <= '9') || (ch == '+') || (ch == '/') + || (ch == '\r') || (ch == '\n'); + } + + /// + /// Return either an ArmoredInputStream or a BcpgInputStream based on whether + /// the initial characters of the stream are binary PGP encodings or not. + /// + public static Stream GetDecoderStream( + Stream inputStream) + { + // TODO Remove this restriction? + if (!inputStream.CanSeek) + throw new ArgumentException("inputStream must be seek-able", "inputStream"); + + long markedPos = inputStream.Position; + + int ch = inputStream.ReadByte(); + if ((ch & 0x80) != 0) + { + inputStream.Position = markedPos; + + return inputStream; + } + + if (!IsPossiblyBase64(ch)) + { + inputStream.Position = markedPos; + + return new ArmoredInputStream(inputStream); + } + + byte[] buf = new byte[ReadAhead]; + int count = 1; + int index = 1; + + buf[0] = (byte)ch; + while (count != ReadAhead && (ch = inputStream.ReadByte()) >= 0) + { + if (!IsPossiblyBase64(ch)) + { + inputStream.Position = markedPos; + + return new ArmoredInputStream(inputStream); + } + + if (ch != '\n' && ch != '\r') + { + buf[index++] = (byte)ch; + } + + count++; + } + + inputStream.Position = markedPos; + + // + // nothing but new lines, little else, assume regular armoring + // + if (count < 4) + { + return new ArmoredInputStream(inputStream); + } + + // + // test our non-blank data + // + byte[] firstBlock = new byte[8]; + + Array.Copy(buf, 0, firstBlock, 0, firstBlock.Length); + + try + { + byte[] decoded = Base64.Decode(firstBlock); + + // + // it's a base64 PGP block. + // + bool hasHeaders = (decoded[0] & 0x80) == 0; + + return new ArmoredInputStream(inputStream, hasHeaders); + } + catch (IOException e) + { + throw e; + } + catch (Exception e) + { + throw new IOException(e.Message); + } + } + + internal static IWrapper CreateWrapper(SymmetricKeyAlgorithmTag encAlgorithm) + { + switch (encAlgorithm) + { + case SymmetricKeyAlgorithmTag.Aes128: + case SymmetricKeyAlgorithmTag.Aes192: + case SymmetricKeyAlgorithmTag.Aes256: + return WrapperUtilities.GetWrapper("AESWRAP"); + case SymmetricKeyAlgorithmTag.Camellia128: + case SymmetricKeyAlgorithmTag.Camellia192: + case SymmetricKeyAlgorithmTag.Camellia256: + return WrapperUtilities.GetWrapper("CAMELLIAWRAP"); + default: + throw new PgpException("unknown wrap algorithm: " + encAlgorithm); + } + } + + internal static byte[] GenerateIV(int length, SecureRandom random) + { + byte[] iv = new byte[length]; + random.NextBytes(iv); + return iv; + } + + internal static S2k GenerateS2k(HashAlgorithmTag hashAlgorithm, int s2kCount, SecureRandom random) + { + byte[] iv = GenerateIV(8, random); + return new S2k(hashAlgorithm, iv, s2kCount); + } + } +} diff --git a/bc-sharp-crypto/src/openpgp/PgpV3SignatureGenerator.cs b/bc-sharp-crypto/src/openpgp/PgpV3SignatureGenerator.cs new file mode 100644 index 0000000..fc8b42d --- /dev/null +++ b/bc-sharp-crypto/src/openpgp/PgpV3SignatureGenerator.cs @@ -0,0 +1,199 @@ +using System; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities.Date; + +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + /// Generator for old style PGP V3 Signatures. + // TODO Should be able to implement ISigner? + public class PgpV3SignatureGenerator + { + private PublicKeyAlgorithmTag keyAlgorithm; + private HashAlgorithmTag hashAlgorithm; + private PgpPrivateKey privKey; + private ISigner sig; + private IDigest dig; + private int signatureType; + private byte lastb; + + /// Create a generator for the passed in keyAlgorithm and hashAlgorithm codes. + public PgpV3SignatureGenerator( + PublicKeyAlgorithmTag keyAlgorithm, + HashAlgorithmTag hashAlgorithm) + { + this.keyAlgorithm = keyAlgorithm; + this.hashAlgorithm = hashAlgorithm; + + dig = DigestUtilities.GetDigest(PgpUtilities.GetDigestName(hashAlgorithm)); + sig = SignerUtilities.GetSigner(PgpUtilities.GetSignatureName(keyAlgorithm, hashAlgorithm)); + } + + /// Initialise the generator for signing. + public void InitSign( + int sigType, + PgpPrivateKey key) + { + InitSign(sigType, key, null); + } + + /// Initialise the generator for signing. + public void InitSign( + int sigType, + PgpPrivateKey key, + SecureRandom random) + { + this.privKey = key; + this.signatureType = sigType; + + try + { + ICipherParameters cp = key.Key; + if (random != null) + { + cp = new ParametersWithRandom(key.Key, random); + } + + sig.Init(true, cp); + } + catch (InvalidKeyException e) + { + throw new PgpException("invalid key.", e); + } + + dig.Reset(); + lastb = 0; + } + + public void Update( + byte b) + { + if (signatureType == PgpSignature.CanonicalTextDocument) + { + doCanonicalUpdateByte(b); + } + else + { + doUpdateByte(b); + } + } + + private void doCanonicalUpdateByte( + byte b) + { + if (b == '\r') + { + doUpdateCRLF(); + } + else if (b == '\n') + { + if (lastb != '\r') + { + doUpdateCRLF(); + } + } + else + { + doUpdateByte(b); + } + + lastb = b; + } + + private void doUpdateCRLF() + { + doUpdateByte((byte)'\r'); + doUpdateByte((byte)'\n'); + } + + private void doUpdateByte( + byte b) + { + sig.Update(b); + dig.Update(b); + } + + public void Update( + byte[] b) + { + if (signatureType == PgpSignature.CanonicalTextDocument) + { + for (int i = 0; i != b.Length; i++) + { + doCanonicalUpdateByte(b[i]); + } + } + else + { + sig.BlockUpdate(b, 0, b.Length); + dig.BlockUpdate(b, 0, b.Length); + } + } + + public void Update( + byte[] b, + int off, + int len) + { + if (signatureType == PgpSignature.CanonicalTextDocument) + { + int finish = off + len; + + for (int i = off; i != finish; i++) + { + doCanonicalUpdateByte(b[i]); + } + } + else + { + sig.BlockUpdate(b, off, len); + dig.BlockUpdate(b, off, len); + } + } + + /// Return the one pass header associated with the current signature. + public PgpOnePassSignature GenerateOnePassVersion( + bool isNested) + { + return new PgpOnePassSignature( + new OnePassSignaturePacket(signatureType, hashAlgorithm, keyAlgorithm, privKey.KeyId, isNested)); + } + + /// Return a V3 signature object containing the current signature state. + public PgpSignature Generate() + { + long creationTime = DateTimeUtilities.CurrentUnixMs() / 1000L; + + byte[] hData = new byte[] + { + (byte) signatureType, + (byte)(creationTime >> 24), + (byte)(creationTime >> 16), + (byte)(creationTime >> 8), + (byte) creationTime + }; + + sig.BlockUpdate(hData, 0, hData.Length); + dig.BlockUpdate(hData, 0, hData.Length); + + byte[] sigBytes = sig.GenerateSignature(); + byte[] digest = DigestUtilities.DoFinal(dig); + byte[] fingerPrint = new byte[]{ digest[0], digest[1] }; + + // an RSA signature + bool isRsa = keyAlgorithm == PublicKeyAlgorithmTag.RsaSign + || keyAlgorithm == PublicKeyAlgorithmTag.RsaGeneral; + + MPInteger[] sigValues = isRsa + ? PgpUtilities.RsaSigToMpi(sigBytes) + : PgpUtilities.DsaSigToMpi(sigBytes); + + return new PgpSignature( + new SignaturePacket(3, signatureType, privKey.KeyId, keyAlgorithm, + hashAlgorithm, creationTime * 1000L, fingerPrint, sigValues)); + } + } +} diff --git a/bc-sharp-crypto/src/openpgp/Rfc6637Utilities.cs b/bc-sharp-crypto/src/openpgp/Rfc6637Utilities.cs new file mode 100644 index 0000000..5d992ec --- /dev/null +++ b/bc-sharp-crypto/src/openpgp/Rfc6637Utilities.cs @@ -0,0 +1,138 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Nist; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Math.EC; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + public sealed class Rfc6637Utilities + { + private Rfc6637Utilities() + { + } + + // "Anonymous Sender ", which is the octet sequence + private static readonly byte[] ANONYMOUS_SENDER = Hex.Decode("416E6F6E796D6F75732053656E64657220202020"); + + public static string GetAgreementAlgorithm(PublicKeyPacket pubKeyData) + { + ECDHPublicBcpgKey ecKey = (ECDHPublicBcpgKey)pubKeyData.Key; + + switch (ecKey.HashAlgorithm) + { + case HashAlgorithmTag.Sha256: + return "ECCDHwithSHA256CKDF"; + case HashAlgorithmTag.Sha384: + return "ECCDHwithSHA384CKDF"; + case HashAlgorithmTag.Sha512: + return "ECCDHwithSHA512CKDF"; + default: + throw new ArgumentException("Unknown hash algorithm specified: " + ecKey.HashAlgorithm); + } + } + + public static DerObjectIdentifier GetKeyEncryptionOID(SymmetricKeyAlgorithmTag algID) + { + switch (algID) + { + case SymmetricKeyAlgorithmTag.Aes128: + return NistObjectIdentifiers.IdAes128Wrap; + case SymmetricKeyAlgorithmTag.Aes192: + return NistObjectIdentifiers.IdAes192Wrap; + case SymmetricKeyAlgorithmTag.Aes256: + return NistObjectIdentifiers.IdAes256Wrap; + default: + throw new PgpException("unknown symmetric algorithm ID: " + algID); + } + } + + public static int GetKeyLength(SymmetricKeyAlgorithmTag algID) + { + switch (algID) + { + case SymmetricKeyAlgorithmTag.Aes128: + return 16; + case SymmetricKeyAlgorithmTag.Aes192: + return 24; + case SymmetricKeyAlgorithmTag.Aes256: + return 32; + default: + throw new PgpException("unknown symmetric algorithm ID: " + algID); + } + } + + public static byte[] CreateKey(PublicKeyPacket pubKeyData, ECPoint s) + { + byte[] userKeyingMaterial = CreateUserKeyingMaterial(pubKeyData); + + ECDHPublicBcpgKey ecKey = (ECDHPublicBcpgKey)pubKeyData.Key; + + return Kdf(ecKey.HashAlgorithm, s, GetKeyLength(ecKey.SymmetricKeyAlgorithm), userKeyingMaterial); + } + + // RFC 6637 - Section 8 + // curve_OID_len = (byte)len(curve_OID); + // Param = curve_OID_len || curve_OID || public_key_alg_ID || 03 + // || 01 || KDF_hash_ID || KEK_alg_ID for AESKeyWrap || "Anonymous + // Sender " || recipient_fingerprint; + // Z_len = the key size for the KEK_alg_ID used with AESKeyWrap + // Compute Z = KDF( S, Z_len, Param ); + public static byte[] CreateUserKeyingMaterial(PublicKeyPacket pubKeyData) + { + MemoryStream pOut = new MemoryStream(); + ECDHPublicBcpgKey ecKey = (ECDHPublicBcpgKey)pubKeyData.Key; + byte[] encOid = ecKey.CurveOid.GetEncoded(); + + pOut.Write(encOid, 1, encOid.Length - 1); + pOut.WriteByte((byte)pubKeyData.Algorithm); + pOut.WriteByte(0x03); + pOut.WriteByte(0x01); + pOut.WriteByte((byte)ecKey.HashAlgorithm); + pOut.WriteByte((byte)ecKey.SymmetricKeyAlgorithm); + pOut.Write(ANONYMOUS_SENDER, 0, ANONYMOUS_SENDER.Length); + + byte[] fingerprint = PgpPublicKey.CalculateFingerprint(pubKeyData); + pOut.Write(fingerprint, 0, fingerprint.Length); + + return pOut.ToArray(); + } + + // RFC 6637 - Section 7 + // Implements KDF( X, oBits, Param ); + // Input: point X = (x,y) + // oBits - the desired size of output + // hBits - the size of output of hash function Hash + // Param - octets representing the parameters + // Assumes that oBits <= hBits + // Convert the point X to the octet string, see section 6: + // ZB' = 04 || x || y + // and extract the x portion from ZB' + // ZB = x; + // MB = Hash ( 00 || 00 || 00 || 01 || ZB || Param ); + // return oBits leftmost bits of MB. + private static byte[] Kdf(HashAlgorithmTag digestAlg, ECPoint s, int keyLen, byte[] parameters) + { + byte[] ZB = s.XCoord.GetEncoded(); + + string digestName = PgpUtilities.GetDigestName(digestAlg); + IDigest digest = DigestUtilities.GetDigest(digestName); + + digest.Update(0x00); + digest.Update(0x00); + digest.Update(0x00); + digest.Update(0x01); + digest.BlockUpdate(ZB, 0, ZB.Length); + digest.BlockUpdate(parameters, 0, parameters.Length); + + byte[] hash = DigestUtilities.DoFinal(digest); + + return Arrays.CopyOfRange(hash, 0, keyLen); + } + } +} diff --git a/bc-sharp-crypto/src/openpgp/SXprUtilities.cs b/bc-sharp-crypto/src/openpgp/SXprUtilities.cs new file mode 100644 index 0000000..68ff373 --- /dev/null +++ b/bc-sharp-crypto/src/openpgp/SXprUtilities.cs @@ -0,0 +1,102 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + /** + * Utility functions for looking a S-expression keys. This class will move when it finds a better home! + *

    + * Format documented here: + * http://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob;f=agent/keyformat.txt;h=42c4b1f06faf1bbe71ffadc2fee0fad6bec91a97;hb=refs/heads/master + *

    + */ + public sealed class SXprUtilities + { + private SXprUtilities() + { + } + + private static int ReadLength(Stream input, int ch) + { + int len = ch - '0'; + + while ((ch = input.ReadByte()) >= 0 && ch != ':') + { + len = len * 10 + ch - '0'; + } + + return len; + } + + internal static string ReadString(Stream input, int ch) + { + int len = ReadLength(input, ch); + + char[] chars = new char[len]; + + for (int i = 0; i != chars.Length; i++) + { + chars[i] = (char)input.ReadByte(); + } + + return new string(chars); + } + + internal static byte[] ReadBytes(Stream input, int ch) + { + int len = ReadLength(input, ch); + + byte[] data = new byte[len]; + + Streams.ReadFully(input, data); + + return data; + } + + internal static S2k ParseS2k(Stream input) + { + SkipOpenParenthesis(input); + + string alg = ReadString(input, input.ReadByte()); + byte[] iv = ReadBytes(input, input.ReadByte()); + long iterationCount = Int64.Parse(ReadString(input, input.ReadByte())); + + SkipCloseParenthesis(input); + + // we have to return the actual iteration count provided. + return new MyS2k(HashAlgorithmTag.Sha1, iv, iterationCount); + } + + internal static void SkipOpenParenthesis(Stream input) + { + int ch = input.ReadByte(); + if (ch != '(') + throw new IOException("unknown character encountered"); + } + + internal static void SkipCloseParenthesis(Stream input) + { + int ch = input.ReadByte(); + if (ch != ')') + throw new IOException("unknown character encountered"); + } + + private class MyS2k : S2k + { + private readonly long mIterationCount64; + + internal MyS2k(HashAlgorithmTag algorithm, byte[] iv, long iterationCount64) + : base(algorithm, iv, (int)iterationCount64) + { + this.mIterationCount64 = iterationCount64; + } + + public override long IterationCount + { + get { return mIterationCount64; } + } + } + } +} diff --git a/bc-sharp-crypto/src/openpgp/WrappedGeneratorStream.cs b/bc-sharp-crypto/src/openpgp/WrappedGeneratorStream.cs new file mode 100644 index 0000000..5f4a4b0 --- /dev/null +++ b/bc-sharp-crypto/src/openpgp/WrappedGeneratorStream.cs @@ -0,0 +1,37 @@ +using System.IO; + +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + public class WrappedGeneratorStream + : FilterStream + { + private readonly IStreamGenerator gen; + + public WrappedGeneratorStream( + IStreamGenerator gen, + Stream str) + : base(str) + { + this.gen = gen; + } + +#if PORTABLE + protected override void Dispose(bool disposing) + { + if (disposing) + { + gen.Close(); + return; + } + base.Dispose(disposing); + } +#else + public override void Close() + { + gen.Close(); + } +#endif + } +} diff --git a/bc-sharp-crypto/src/openssl/EncryptionException.cs b/bc-sharp-crypto/src/openssl/EncryptionException.cs new file mode 100644 index 0000000..043e902 --- /dev/null +++ b/bc-sharp-crypto/src/openssl/EncryptionException.cs @@ -0,0 +1,25 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Security +{ +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT || PORTABLE) + [Serializable] +#endif + public class EncryptionException + : IOException + { + public EncryptionException( + string message) + : base(message) + { + } + + public EncryptionException( + string message, + Exception exception) + : base(message, exception) + { + } + } +} diff --git a/bc-sharp-crypto/src/openssl/IPasswordFinder.cs b/bc-sharp-crypto/src/openssl/IPasswordFinder.cs new file mode 100644 index 0000000..4fcef1b --- /dev/null +++ b/bc-sharp-crypto/src/openssl/IPasswordFinder.cs @@ -0,0 +1,9 @@ +using System; + +namespace Org.BouncyCastle.OpenSsl +{ + public interface IPasswordFinder + { + char[] GetPassword(); + } +} diff --git a/bc-sharp-crypto/src/openssl/MiscPemGenerator.cs b/bc-sharp-crypto/src/openssl/MiscPemGenerator.cs new file mode 100644 index 0000000..22ae1ea --- /dev/null +++ b/bc-sharp-crypto/src/openssl/MiscPemGenerator.cs @@ -0,0 +1,275 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.CryptoPro; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Pkcs; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Security.Certificates; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Encoders; +using Org.BouncyCastle.Utilities.IO.Pem; +using Org.BouncyCastle.X509; + +namespace Org.BouncyCastle.OpenSsl +{ + /** + * PEM generator for the original set of PEM objects used in Open SSL. + */ + public class MiscPemGenerator + : PemObjectGenerator + { + private object obj; + private string algorithm; + private char[] password; + private SecureRandom random; + + public MiscPemGenerator(object obj) + { + this.obj = obj; + } + + public MiscPemGenerator( + object obj, + string algorithm, + char[] password, + SecureRandom random) + { + this.obj = obj; + this.algorithm = algorithm; + this.password = password; + this.random = random; + } + + private static PemObject CreatePemObject(object obj) + { + if (obj == null) + throw new ArgumentNullException("obj"); + + if (obj is AsymmetricCipherKeyPair) + { + return CreatePemObject(((AsymmetricCipherKeyPair)obj).Private); + } + + string type; + byte[] encoding; + + if (obj is PemObject) + return (PemObject)obj; + + if (obj is PemObjectGenerator) + return ((PemObjectGenerator)obj).Generate(); + + if (obj is X509Certificate) + { + // TODO Should we prefer "X509 CERTIFICATE" here? + type = "CERTIFICATE"; + try + { + encoding = ((X509Certificate)obj).GetEncoded(); + } + catch (CertificateEncodingException e) + { + throw new IOException("Cannot Encode object: " + e.ToString()); + } + } + else if (obj is X509Crl) + { + type = "X509 CRL"; + try + { + encoding = ((X509Crl)obj).GetEncoded(); + } + catch (CrlException e) + { + throw new IOException("Cannot Encode object: " + e.ToString()); + } + } + else if (obj is AsymmetricKeyParameter) + { + AsymmetricKeyParameter akp = (AsymmetricKeyParameter) obj; + if (akp.IsPrivate) + { + string keyType; + encoding = EncodePrivateKey(akp, out keyType); + + type = keyType + " PRIVATE KEY"; + } + else + { + type = "PUBLIC KEY"; + + encoding = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(akp).GetDerEncoded(); + } + } + else if (obj is IX509AttributeCertificate) + { + type = "ATTRIBUTE CERTIFICATE"; + encoding = ((X509V2AttributeCertificate)obj).GetEncoded(); + } + else if (obj is Pkcs10CertificationRequest) + { + type = "CERTIFICATE REQUEST"; + encoding = ((Pkcs10CertificationRequest)obj).GetEncoded(); + } + else if (obj is Asn1.Cms.ContentInfo) + { + type = "PKCS7"; + encoding = ((Asn1.Cms.ContentInfo)obj).GetEncoded(); + } + else + { + throw new PemGenerationException("Object type not supported: " + Platform.GetTypeName(obj)); + } + + return new PemObject(type, encoding); + } + +// private string GetHexEncoded(byte[] bytes) +// { +// bytes = Hex.Encode(bytes); +// +// char[] chars = new char[bytes.Length]; +// +// for (int i = 0; i != bytes.Length; i++) +// { +// chars[i] = (char)bytes[i]; +// } +// +// return new string(chars); +// } + + private static PemObject CreatePemObject( + object obj, + string algorithm, + char[] password, + SecureRandom random) + { + if (obj == null) + throw new ArgumentNullException("obj"); + if (algorithm == null) + throw new ArgumentNullException("algorithm"); + if (password == null) + throw new ArgumentNullException("password"); + if (random == null) + throw new ArgumentNullException("random"); + + if (obj is AsymmetricCipherKeyPair) + { + return CreatePemObject(((AsymmetricCipherKeyPair)obj).Private, algorithm, password, random); + } + + string type = null; + byte[] keyData = null; + + if (obj is AsymmetricKeyParameter) + { + AsymmetricKeyParameter akp = (AsymmetricKeyParameter) obj; + if (akp.IsPrivate) + { + string keyType; + keyData = EncodePrivateKey(akp, out keyType); + + type = keyType + " PRIVATE KEY"; + } + } + + if (type == null || keyData == null) + { + // TODO Support other types? + throw new PemGenerationException("Object type not supported: " + Platform.GetTypeName(obj)); + } + + + string dekAlgName = Platform.ToUpperInvariant(algorithm); + + // Note: For backward compatibility + if (dekAlgName == "DESEDE") + { + dekAlgName = "DES-EDE3-CBC"; + } + + int ivLength = Platform.StartsWith(dekAlgName, "AES-") ? 16 : 8; + + byte[] iv = new byte[ivLength]; + random.NextBytes(iv); + + byte[] encData = PemUtilities.Crypt(true, keyData, password, dekAlgName, iv); + + IList headers = Platform.CreateArrayList(2); + + headers.Add(new PemHeader("Proc-Type", "4,ENCRYPTED")); + headers.Add(new PemHeader("DEK-Info", dekAlgName + "," + Hex.ToHexString(iv))); + + return new PemObject(type, headers, encData); + } + + private static byte[] EncodePrivateKey( + AsymmetricKeyParameter akp, + out string keyType) + { + PrivateKeyInfo info = PrivateKeyInfoFactory.CreatePrivateKeyInfo(akp); + AlgorithmIdentifier algID = info.PrivateKeyAlgorithm; + DerObjectIdentifier oid = algID.Algorithm; + + if (oid.Equals(X9ObjectIdentifiers.IdDsa)) + { + keyType = "DSA"; + + DsaParameter p = DsaParameter.GetInstance(algID.Parameters); + + BigInteger x = ((DsaPrivateKeyParameters) akp).X; + BigInteger y = p.G.ModPow(x, p.P); + + // TODO Create an ASN1 object somewhere for this? + return new DerSequence( + new DerInteger(0), + new DerInteger(p.P), + new DerInteger(p.Q), + new DerInteger(p.G), + new DerInteger(y), + new DerInteger(x)).GetEncoded(); + } + + if (oid.Equals(PkcsObjectIdentifiers.RsaEncryption)) + { + keyType = "RSA"; + } + else if (oid.Equals(CryptoProObjectIdentifiers.GostR3410x2001) + || oid.Equals(X9ObjectIdentifiers.IdECPublicKey)) + { + keyType = "EC"; + } + else + { + throw new ArgumentException("Cannot handle private key of type: " + Platform.GetTypeName(akp), "akp"); + } + + return info.ParsePrivateKey().GetEncoded(); + } + + public PemObject Generate() + { + try + { + if (algorithm != null) + { + return CreatePemObject(obj, algorithm, password, random); + } + + return CreatePemObject(obj); + } + catch (IOException e) + { + throw new PemGenerationException("encoding exception", e); + } + } + } +} diff --git a/bc-sharp-crypto/src/openssl/PEMException.cs b/bc-sharp-crypto/src/openssl/PEMException.cs new file mode 100644 index 0000000..6b3e510 --- /dev/null +++ b/bc-sharp-crypto/src/openssl/PEMException.cs @@ -0,0 +1,25 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.OpenSsl +{ +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT || PORTABLE) + [Serializable] +#endif + public class PemException + : IOException + { + public PemException( + string message) + : base(message) + { + } + + public PemException( + string message, + Exception exception) + : base(message, exception) + { + } + } +} diff --git a/bc-sharp-crypto/src/openssl/PEMReader.cs b/bc-sharp-crypto/src/openssl/PEMReader.cs new file mode 100644 index 0000000..9a5f99b --- /dev/null +++ b/bc-sharp-crypto/src/openssl/PEMReader.cs @@ -0,0 +1,401 @@ +using System; +using System.Collections; +using System.Diagnostics; +using System.IO; +using System.Text; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Nist; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.Sec; +using Org.BouncyCastle.Asn1.TeleTrust; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.EC; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Pkcs; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Encoders; +using Org.BouncyCastle.Utilities.IO.Pem; +using Org.BouncyCastle.X509; + +namespace Org.BouncyCastle.OpenSsl +{ + /** + * Class for reading OpenSSL PEM encoded streams containing + * X509 certificates, PKCS8 encoded keys and PKCS7 objects. + *

    + * In the case of PKCS7 objects the reader will return a CMS ContentInfo object. Keys and + * Certificates will be returned using the appropriate java.security type.

    + */ + public class PemReader + : Org.BouncyCastle.Utilities.IO.Pem.PemReader + { +// private static readonly IDictionary parsers = new Hashtable(); + + static PemReader() + { +// parsers.Add("CERTIFICATE REQUEST", new PKCS10CertificationRequestParser()); +// parsers.Add("NEW CERTIFICATE REQUEST", new PKCS10CertificationRequestParser()); +// parsers.Add("CERTIFICATE", new X509CertificateParser(provider)); +// parsers.Add("X509 CERTIFICATE", new X509CertificateParser(provider)); +// parsers.Add("X509 CRL", new X509CRLParser(provider)); +// parsers.Add("PKCS7", new PKCS7Parser()); +// parsers.Add("ATTRIBUTE CERTIFICATE", new X509AttributeCertificateParser()); +// parsers.Add("EC PARAMETERS", new ECNamedCurveSpecParser()); +// parsers.Add("PUBLIC KEY", new PublicKeyParser(provider)); +// parsers.Add("RSA PUBLIC KEY", new RSAPublicKeyParser(provider)); +// parsers.Add("RSA PRIVATE KEY", new RSAKeyPairParser(provider)); +// parsers.Add("DSA PRIVATE KEY", new DSAKeyPairParser(provider)); +// parsers.Add("EC PRIVATE KEY", new ECDSAKeyPairParser(provider)); +// parsers.Add("ENCRYPTED PRIVATE KEY", new EncryptedPrivateKeyParser(provider)); +// parsers.Add("PRIVATE KEY", new PrivateKeyParser(provider)); + } + + private readonly IPasswordFinder pFinder; + + /** + * Create a new PemReader + * + * @param reader the Reader + */ + public PemReader( + TextReader reader) + : this(reader, null) + { + } + + /** + * Create a new PemReader with a password finder + * + * @param reader the Reader + * @param pFinder the password finder + */ + public PemReader( + TextReader reader, + IPasswordFinder pFinder) + : base(reader) + { + this.pFinder = pFinder; + } + + public object ReadObject() + { + PemObject obj = ReadPemObject(); + + if (obj == null) + return null; + + // TODO Follow Java build and map to parser objects? +// if (parsers.Contains(obj.Type)) +// return ((PemObjectParser)parsers[obj.Type]).ParseObject(obj); + + if (Platform.EndsWith(obj.Type, "PRIVATE KEY")) + return ReadPrivateKey(obj); + + switch (obj.Type) + { + case "PUBLIC KEY": + return ReadPublicKey(obj); + case "RSA PUBLIC KEY": + return ReadRsaPublicKey(obj); + case "CERTIFICATE REQUEST": + case "NEW CERTIFICATE REQUEST": + return ReadCertificateRequest(obj); + case "CERTIFICATE": + case "X509 CERTIFICATE": + return ReadCertificate(obj); + case "PKCS7": + case "CMS": + return ReadPkcs7(obj); + case "X509 CRL": + return ReadCrl(obj); + case "ATTRIBUTE CERTIFICATE": + return ReadAttributeCertificate(obj); + // TODO Add back in when tests done, and return type issue resolved + //case "EC PARAMETERS": + // return ReadECParameters(obj); + default: + throw new IOException("unrecognised object: " + obj.Type); + } + } + + private AsymmetricKeyParameter ReadRsaPublicKey(PemObject pemObject) + { + RsaPublicKeyStructure rsaPubStructure = RsaPublicKeyStructure.GetInstance( + Asn1Object.FromByteArray(pemObject.Content)); + + return new RsaKeyParameters( + false, // not private + rsaPubStructure.Modulus, + rsaPubStructure.PublicExponent); + } + + private AsymmetricKeyParameter ReadPublicKey(PemObject pemObject) + { + return PublicKeyFactory.CreateKey(pemObject.Content); + } + + /** + * Reads in a X509Certificate. + * + * @return the X509Certificate + * @throws IOException if an I/O error occured + */ + private X509Certificate ReadCertificate(PemObject pemObject) + { + try + { + return new X509CertificateParser().ReadCertificate(pemObject.Content); + } + catch (Exception e) + { + throw new PemException("problem parsing cert: " + e.ToString()); + } + } + + /** + * Reads in a X509CRL. + * + * @return the X509Certificate + * @throws IOException if an I/O error occured + */ + private X509Crl ReadCrl(PemObject pemObject) + { + try + { + return new X509CrlParser().ReadCrl(pemObject.Content); + } + catch (Exception e) + { + throw new PemException("problem parsing cert: " + e.ToString()); + } + } + + /** + * Reads in a PKCS10 certification request. + * + * @return the certificate request. + * @throws IOException if an I/O error occured + */ + private Pkcs10CertificationRequest ReadCertificateRequest(PemObject pemObject) + { + try + { + return new Pkcs10CertificationRequest(pemObject.Content); + } + catch (Exception e) + { + throw new PemException("problem parsing cert: " + e.ToString()); + } + } + + /** + * Reads in a X509 Attribute Certificate. + * + * @return the X509 Attribute Certificate + * @throws IOException if an I/O error occured + */ + private IX509AttributeCertificate ReadAttributeCertificate(PemObject pemObject) + { + return new X509V2AttributeCertificate(pemObject.Content); + } + + /** + * Reads in a PKCS7 object. This returns a ContentInfo object suitable for use with the CMS + * API. + * + * @return the X509Certificate + * @throws IOException if an I/O error occured + */ + // TODO Consider returning Asn1.Pkcs.ContentInfo + private Asn1.Cms.ContentInfo ReadPkcs7(PemObject pemObject) + { + try + { + return Asn1.Cms.ContentInfo.GetInstance( + Asn1Object.FromByteArray(pemObject.Content)); + } + catch (Exception e) + { + throw new PemException("problem parsing PKCS7 object: " + e.ToString()); + } + } + + /** + * Read a Key Pair + */ + private object ReadPrivateKey(PemObject pemObject) + { + // + // extract the key + // + Debug.Assert(Platform.EndsWith(pemObject.Type, "PRIVATE KEY")); + + string type = pemObject.Type.Substring(0, pemObject.Type.Length - "PRIVATE KEY".Length).Trim(); + byte[] keyBytes = pemObject.Content; + + IDictionary fields = Platform.CreateHashtable(); + foreach (PemHeader header in pemObject.Headers) + { + fields[header.Name] = header.Value; + } + + string procType = (string) fields["Proc-Type"]; + + if (procType == "4,ENCRYPTED") + { + if (pFinder == null) + throw new PasswordException("No password finder specified, but a password is required"); + + char[] password = pFinder.GetPassword(); + + if (password == null) + throw new PasswordException("Password is null, but a password is required"); + + string dekInfo = (string) fields["DEK-Info"]; + string[] tknz = dekInfo.Split(','); + + string dekAlgName = tknz[0].Trim(); + byte[] iv = Hex.Decode(tknz[1].Trim()); + + keyBytes = PemUtilities.Crypt(false, keyBytes, password, dekAlgName, iv); + } + + try + { + AsymmetricKeyParameter pubSpec, privSpec; + Asn1Sequence seq = Asn1Sequence.GetInstance(keyBytes); + + switch (type) + { + case "RSA": + { + if (seq.Count != 9) + throw new PemException("malformed sequence in RSA private key"); + + RsaPrivateKeyStructure rsa = RsaPrivateKeyStructure.GetInstance(seq); + + pubSpec = new RsaKeyParameters(false, rsa.Modulus, rsa.PublicExponent); + privSpec = new RsaPrivateCrtKeyParameters( + rsa.Modulus, rsa.PublicExponent, rsa.PrivateExponent, + rsa.Prime1, rsa.Prime2, rsa.Exponent1, rsa.Exponent2, + rsa.Coefficient); + + break; + } + + case "DSA": + { + if (seq.Count != 6) + throw new PemException("malformed sequence in DSA private key"); + + // TODO Create an ASN1 object somewhere for this? + //DerInteger v = (DerInteger)seq[0]; + DerInteger p = (DerInteger)seq[1]; + DerInteger q = (DerInteger)seq[2]; + DerInteger g = (DerInteger)seq[3]; + DerInteger y = (DerInteger)seq[4]; + DerInteger x = (DerInteger)seq[5]; + + DsaParameters parameters = new DsaParameters(p.Value, q.Value, g.Value); + + privSpec = new DsaPrivateKeyParameters(x.Value, parameters); + pubSpec = new DsaPublicKeyParameters(y.Value, parameters); + + break; + } + + case "EC": + { + ECPrivateKeyStructure pKey = ECPrivateKeyStructure.GetInstance(seq); + AlgorithmIdentifier algId = new AlgorithmIdentifier( + X9ObjectIdentifiers.IdECPublicKey, pKey.GetParameters()); + + PrivateKeyInfo privInfo = new PrivateKeyInfo(algId, pKey.ToAsn1Object()); + + // TODO Are the keys returned here ECDSA, as Java version forces? + privSpec = PrivateKeyFactory.CreateKey(privInfo); + + DerBitString pubKey = pKey.GetPublicKey(); + if (pubKey != null) + { + SubjectPublicKeyInfo pubInfo = new SubjectPublicKeyInfo(algId, pubKey.GetBytes()); + + // TODO Are the keys returned here ECDSA, as Java version forces? + pubSpec = PublicKeyFactory.CreateKey(pubInfo); + } + else + { + pubSpec = ECKeyPairGenerator.GetCorrespondingPublicKey( + (ECPrivateKeyParameters)privSpec); + } + + break; + } + + case "ENCRYPTED": + { + char[] password = pFinder.GetPassword(); + + if (password == null) + throw new PasswordException("Password is null, but a password is required"); + + return PrivateKeyFactory.DecryptKey(password, EncryptedPrivateKeyInfo.GetInstance(seq)); + } + + case "": + { + return PrivateKeyFactory.CreateKey(PrivateKeyInfo.GetInstance(seq)); + } + + default: + throw new ArgumentException("Unknown key type: " + type, "type"); + } + + return new AsymmetricCipherKeyPair(pubSpec, privSpec); + } + catch (IOException e) + { + throw e; + } + catch (Exception e) + { + throw new PemException( + "problem creating " + type + " private key: " + e.ToString()); + } + } + + // TODO Add an equivalent class for ECNamedCurveParameterSpec? + //private ECNamedCurveParameterSpec ReadECParameters( +// private X9ECParameters ReadECParameters(PemObject pemObject) +// { +// DerObjectIdentifier oid = (DerObjectIdentifier)Asn1Object.FromByteArray(pemObject.Content); +// +// //return ECNamedCurveTable.getParameterSpec(oid.Id); +// return GetCurveParameters(oid.Id); +// } + + //private static ECDomainParameters GetCurveParameters( + private static X9ECParameters GetCurveParameters( + string name) + { + // TODO ECGost3410NamedCurves support (returns ECDomainParameters though) + + X9ECParameters ecP = CustomNamedCurves.GetByName(name); + if (ecP == null) + { + ecP = ECNamedCurveTable.GetByName(name); + } + + if (ecP == null) + throw new Exception("unknown curve name: " + name); + + //return new ECDomainParameters(ecP.Curve, ecP.G, ecP.N, ecP.H, ecP.GetSeed()); + return ecP; + } + } +} diff --git a/bc-sharp-crypto/src/openssl/PEMUtilities.cs b/bc-sharp-crypto/src/openssl/PEMUtilities.cs new file mode 100644 index 0000000..b58e5e7 --- /dev/null +++ b/bc-sharp-crypto/src/openssl/PEMUtilities.cs @@ -0,0 +1,158 @@ +using System; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.OpenSsl +{ + internal sealed class PemUtilities + { + private enum PemBaseAlg { AES_128, AES_192, AES_256, BF, DES, DES_EDE, DES_EDE3, RC2, RC2_40, RC2_64 }; + private enum PemMode { CBC, CFB, ECB, OFB }; + + static PemUtilities() + { + // Signal to obfuscation tools not to change enum constants + ((PemBaseAlg)Enums.GetArbitraryValue(typeof(PemBaseAlg))).ToString(); + ((PemMode)Enums.GetArbitraryValue(typeof(PemMode))).ToString(); + } + + private static void ParseDekAlgName( + string dekAlgName, + out PemBaseAlg baseAlg, + out PemMode mode) + { + try + { + mode = PemMode.ECB; + + if (dekAlgName == "DES-EDE" || dekAlgName == "DES-EDE3") + { + baseAlg = (PemBaseAlg)Enums.GetEnumValue(typeof(PemBaseAlg), dekAlgName); + return; + } + + int pos = dekAlgName.LastIndexOf('-'); + if (pos >= 0) + { + baseAlg = (PemBaseAlg)Enums.GetEnumValue(typeof(PemBaseAlg), dekAlgName.Substring(0, pos)); + mode = (PemMode)Enums.GetEnumValue(typeof(PemMode), dekAlgName.Substring(pos + 1)); + return; + } + } + catch (ArgumentException) + { + } + + throw new EncryptionException("Unknown DEK algorithm: " + dekAlgName); + } + + internal static byte[] Crypt( + bool encrypt, + byte[] bytes, + char[] password, + string dekAlgName, + byte[] iv) + { + PemBaseAlg baseAlg; + PemMode mode; + ParseDekAlgName(dekAlgName, out baseAlg, out mode); + + string padding; + switch (mode) + { + case PemMode.CBC: + case PemMode.ECB: + padding = "PKCS5Padding"; + break; + case PemMode.CFB: + case PemMode.OFB: + padding = "NoPadding"; + break; + default: + throw new EncryptionException("Unknown DEK algorithm: " + dekAlgName); + } + + string algorithm; + + byte[] salt = iv; + switch (baseAlg) + { + case PemBaseAlg.AES_128: + case PemBaseAlg.AES_192: + case PemBaseAlg.AES_256: + algorithm = "AES"; + if (salt.Length > 8) + { + salt = new byte[8]; + Array.Copy(iv, 0, salt, 0, salt.Length); + } + break; + case PemBaseAlg.BF: + algorithm = "BLOWFISH"; + break; + case PemBaseAlg.DES: + algorithm = "DES"; + break; + case PemBaseAlg.DES_EDE: + case PemBaseAlg.DES_EDE3: + algorithm = "DESede"; + break; + case PemBaseAlg.RC2: + case PemBaseAlg.RC2_40: + case PemBaseAlg.RC2_64: + algorithm = "RC2"; + break; + default: + throw new EncryptionException("Unknown DEK algorithm: " + dekAlgName); + } + + string cipherName = algorithm + "/" + mode + "/" + padding; + IBufferedCipher cipher = CipherUtilities.GetCipher(cipherName); + + ICipherParameters cParams = GetCipherParameters(password, baseAlg, salt); + + if (mode != PemMode.ECB) + { + cParams = new ParametersWithIV(cParams, iv); + } + + cipher.Init(encrypt, cParams); + + return cipher.DoFinal(bytes); + } + + private static ICipherParameters GetCipherParameters( + char[] password, + PemBaseAlg baseAlg, + byte[] salt) + { + string algorithm; + int keyBits; + switch (baseAlg) + { + case PemBaseAlg.AES_128: keyBits = 128; algorithm = "AES128"; break; + case PemBaseAlg.AES_192: keyBits = 192; algorithm = "AES192"; break; + case PemBaseAlg.AES_256: keyBits = 256; algorithm = "AES256"; break; + case PemBaseAlg.BF: keyBits = 128; algorithm = "BLOWFISH"; break; + case PemBaseAlg.DES: keyBits = 64; algorithm = "DES"; break; + case PemBaseAlg.DES_EDE: keyBits = 128; algorithm = "DESEDE"; break; + case PemBaseAlg.DES_EDE3: keyBits = 192; algorithm = "DESEDE3"; break; + case PemBaseAlg.RC2: keyBits = 128; algorithm = "RC2"; break; + case PemBaseAlg.RC2_40: keyBits = 40; algorithm = "RC2"; break; + case PemBaseAlg.RC2_64: keyBits = 64; algorithm = "RC2"; break; + default: + return null; + } + + OpenSslPbeParametersGenerator pGen = new OpenSslPbeParametersGenerator(); + + pGen.Init(PbeParametersGenerator.Pkcs5PasswordToBytes(password), salt); + + return pGen.GenerateDerivedParameters(algorithm, keyBits); + } + } +} diff --git a/bc-sharp-crypto/src/openssl/PEMWriter.cs b/bc-sharp-crypto/src/openssl/PEMWriter.cs new file mode 100644 index 0000000..aefb018 --- /dev/null +++ b/bc-sharp-crypto/src/openssl/PEMWriter.cs @@ -0,0 +1,61 @@ +using System; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Text; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.CryptoPro; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Pkcs; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Security.Certificates; +using Org.BouncyCastle.Utilities.Encoders; +using Org.BouncyCastle.Utilities.IO.Pem; +using Org.BouncyCastle.X509; + +namespace Org.BouncyCastle.OpenSsl +{ + /// General purpose writer for OpenSSL PEM objects. + public class PemWriter + : Org.BouncyCastle.Utilities.IO.Pem.PemWriter + { + /// The TextWriter object to write the output to. + public PemWriter( + TextWriter writer) + : base(writer) + { + } + + public void WriteObject( + object obj) + { + try + { + base.WriteObject(new MiscPemGenerator(obj)); + } + catch (PemGenerationException e) + { + if (e.InnerException is IOException) + throw (IOException)e.InnerException; + + throw e; + } + } + + public void WriteObject( + object obj, + string algorithm, + char[] password, + SecureRandom random) + { + base.WriteObject(new MiscPemGenerator(obj, algorithm, password, random)); + } + } +} diff --git a/bc-sharp-crypto/src/openssl/PasswordException.cs b/bc-sharp-crypto/src/openssl/PasswordException.cs new file mode 100644 index 0000000..38e679b --- /dev/null +++ b/bc-sharp-crypto/src/openssl/PasswordException.cs @@ -0,0 +1,25 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Security +{ +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT || PORTABLE) + [Serializable] +#endif + public class PasswordException + : IOException + { + public PasswordException( + string message) + : base(message) + { + } + + public PasswordException( + string message, + Exception exception) + : base(message, exception) + { + } + } +} diff --git a/bc-sharp-crypto/src/openssl/Pkcs8Generator.cs b/bc-sharp-crypto/src/openssl/Pkcs8Generator.cs new file mode 100644 index 0000000..d03ea08 --- /dev/null +++ b/bc-sharp-crypto/src/openssl/Pkcs8Generator.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Nist; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Pkcs; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities.IO.Pem; + +namespace Org.BouncyCastle.OpenSsl +{ + public class Pkcs8Generator + : PemObjectGenerator + { + // FIXME See PbeUtilities static constructor +// public static readonly string Aes128Cbc = NistObjectIdentifiers.IdAes128Cbc.Id; +// public static readonly string Aes192Cbc = NistObjectIdentifiers.IdAes192Cbc.Id; +// public static readonly string Aes256Cbc = NistObjectIdentifiers.IdAes256Cbc.Id; +// +// public static readonly string Des3Cbc = PkcsObjectIdentifiers.DesEde3Cbc.Id; + + public static readonly string PbeSha1_RC4_128 = PkcsObjectIdentifiers.PbeWithShaAnd128BitRC4.Id; + public static readonly string PbeSha1_RC4_40 = PkcsObjectIdentifiers.PbeWithShaAnd40BitRC4.Id; + public static readonly string PbeSha1_3DES = PkcsObjectIdentifiers.PbeWithShaAnd3KeyTripleDesCbc.Id; + public static readonly string PbeSha1_2DES = PkcsObjectIdentifiers.PbeWithShaAnd2KeyTripleDesCbc.Id; + public static readonly string PbeSha1_RC2_128 = PkcsObjectIdentifiers.PbeWithShaAnd128BitRC2Cbc.Id; + public static readonly string PbeSha1_RC2_40 = PkcsObjectIdentifiers.PbewithShaAnd40BitRC2Cbc.Id; + + private char[] password; + private string algorithm; + private int iterationCount; + private AsymmetricKeyParameter privKey; + private SecureRandom random; + + /** + * Constructor for an unencrypted private key PEM object. + * + * @param key private key to be encoded. + */ + public Pkcs8Generator(AsymmetricKeyParameter privKey) + { + this.privKey = privKey; + } + + /** + * Constructor for an encrypted private key PEM object. + * + * @param key private key to be encoded + * @param algorithm encryption algorithm to use + * @param provider provider to use + * @throws NoSuchAlgorithmException if algorithm/mode cannot be found + */ + public Pkcs8Generator(AsymmetricKeyParameter privKey, string algorithm) + { + // TODO Check privKey.IsPrivate + this.privKey = privKey; + this.algorithm = algorithm; + this.iterationCount = 2048; + } + + public SecureRandom SecureRandom + { + set { this.random = value; } + } + + public char[] Password + { + set { this.password = value; } + } + + public int IterationCount + { + set { this.iterationCount = value; } + } + + public PemObject Generate() + { + if (algorithm == null) + { + PrivateKeyInfo pki = PrivateKeyInfoFactory.CreatePrivateKeyInfo(privKey); + + return new PemObject("PRIVATE KEY", pki.GetEncoded()); + } + + // TODO Theoretically, the amount of salt needed depends on the algorithm + byte[] salt = new byte[20]; + if (random == null) + { + random = new SecureRandom(); + } + random.NextBytes(salt); + + try + { + EncryptedPrivateKeyInfo epki = EncryptedPrivateKeyInfoFactory.CreateEncryptedPrivateKeyInfo( + algorithm, password, salt, iterationCount, privKey); + + return new PemObject("ENCRYPTED PRIVATE KEY", epki.GetEncoded()); + } + catch (Exception e) + { + throw new PemGenerationException("Couldn't encrypt private key", e); + } + } + } +} diff --git a/bc-sharp-crypto/src/pkcs/AsymmetricKeyEntry.cs b/bc-sharp-crypto/src/pkcs/AsymmetricKeyEntry.cs new file mode 100644 index 0000000..6da3ade --- /dev/null +++ b/bc-sharp-crypto/src/pkcs/AsymmetricKeyEntry.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Pkcs +{ + public class AsymmetricKeyEntry + : Pkcs12Entry + { + private readonly AsymmetricKeyParameter key; + + public AsymmetricKeyEntry( + AsymmetricKeyParameter key) + : base(Platform.CreateHashtable()) + { + this.key = key; + } + +#if !(SILVERLIGHT || PORTABLE) + [Obsolete] + public AsymmetricKeyEntry( + AsymmetricKeyParameter key, + Hashtable attributes) + : base(attributes) + { + this.key = key; + } +#endif + + public AsymmetricKeyEntry( + AsymmetricKeyParameter key, + IDictionary attributes) + : base(attributes) + { + this.key = key; + } + + public AsymmetricKeyParameter Key + { + get { return this.key; } + } + + public override bool Equals(object obj) + { + AsymmetricKeyEntry other = obj as AsymmetricKeyEntry; + + if (other == null) + return false; + + return key.Equals(other.key); + } + + public override int GetHashCode() + { + return ~key.GetHashCode(); + } + } +} diff --git a/bc-sharp-crypto/src/pkcs/EncryptedPrivateKeyInfoFactory.cs b/bc-sharp-crypto/src/pkcs/EncryptedPrivateKeyInfoFactory.cs new file mode 100644 index 0000000..b6b7bac --- /dev/null +++ b/bc-sharp-crypto/src/pkcs/EncryptedPrivateKeyInfoFactory.cs @@ -0,0 +1,64 @@ +using System; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Pkcs +{ + public sealed class EncryptedPrivateKeyInfoFactory + { + private EncryptedPrivateKeyInfoFactory() + { + } + + public static EncryptedPrivateKeyInfo CreateEncryptedPrivateKeyInfo( + DerObjectIdentifier algorithm, + char[] passPhrase, + byte[] salt, + int iterationCount, + AsymmetricKeyParameter key) + { + return CreateEncryptedPrivateKeyInfo( + algorithm.Id, passPhrase, salt, iterationCount, + PrivateKeyInfoFactory.CreatePrivateKeyInfo(key)); + } + + public static EncryptedPrivateKeyInfo CreateEncryptedPrivateKeyInfo( + string algorithm, + char[] passPhrase, + byte[] salt, + int iterationCount, + AsymmetricKeyParameter key) + { + return CreateEncryptedPrivateKeyInfo( + algorithm, passPhrase, salt, iterationCount, + PrivateKeyInfoFactory.CreatePrivateKeyInfo(key)); + } + + public static EncryptedPrivateKeyInfo CreateEncryptedPrivateKeyInfo( + string algorithm, + char[] passPhrase, + byte[] salt, + int iterationCount, + PrivateKeyInfo keyInfo) + { + IBufferedCipher cipher = PbeUtilities.CreateEngine(algorithm) as IBufferedCipher; + if (cipher == null) + throw new Exception("Unknown encryption algorithm: " + algorithm); + + Asn1Encodable pbeParameters = PbeUtilities.GenerateAlgorithmParameters( + algorithm, salt, iterationCount); + ICipherParameters cipherParameters = PbeUtilities.GenerateCipherParameters( + algorithm, passPhrase, pbeParameters); + cipher.Init(true, cipherParameters); + byte[] encoding = cipher.DoFinal(keyInfo.GetEncoded()); + + DerObjectIdentifier oid = PbeUtilities.GetObjectIdentifier(algorithm); + AlgorithmIdentifier algID = new AlgorithmIdentifier(oid, pbeParameters); + return new EncryptedPrivateKeyInfo(algID, encoding); + } + } +} diff --git a/bc-sharp-crypto/src/pkcs/PKCS12StoreBuilder.cs b/bc-sharp-crypto/src/pkcs/PKCS12StoreBuilder.cs new file mode 100644 index 0000000..c8fa0f6 --- /dev/null +++ b/bc-sharp-crypto/src/pkcs/PKCS12StoreBuilder.cs @@ -0,0 +1,41 @@ +using System; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Pkcs; + +namespace Org.BouncyCastle.Pkcs +{ + public class Pkcs12StoreBuilder + { + private DerObjectIdentifier keyAlgorithm = PkcsObjectIdentifiers.PbeWithShaAnd3KeyTripleDesCbc; + private DerObjectIdentifier certAlgorithm = PkcsObjectIdentifiers.PbewithShaAnd40BitRC2Cbc; + private bool useDerEncoding = false; + + public Pkcs12StoreBuilder() + { + } + + public Pkcs12Store Build() + { + return new Pkcs12Store(keyAlgorithm, certAlgorithm, useDerEncoding); + } + + public Pkcs12StoreBuilder SetCertAlgorithm(DerObjectIdentifier certAlgorithm) + { + this.certAlgorithm = certAlgorithm; + return this; + } + + public Pkcs12StoreBuilder SetKeyAlgorithm(DerObjectIdentifier keyAlgorithm) + { + this.keyAlgorithm = keyAlgorithm; + return this; + } + + public Pkcs12StoreBuilder SetUseDerEncoding(bool useDerEncoding) + { + this.useDerEncoding = useDerEncoding; + return this; + } + } +} diff --git a/bc-sharp-crypto/src/pkcs/Pkcs10CertificationRequest.cs b/bc-sharp-crypto/src/pkcs/Pkcs10CertificationRequest.cs new file mode 100644 index 0000000..c2504e6 --- /dev/null +++ b/bc-sharp-crypto/src/pkcs/Pkcs10CertificationRequest.cs @@ -0,0 +1,464 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.CryptoPro; +using Org.BouncyCastle.Asn1.Nist; +using Org.BouncyCastle.Asn1.Oiw; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.TeleTrust; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; +using Org.BouncyCastle.X509; +using Org.BouncyCastle.Crypto.Operators; + +namespace Org.BouncyCastle.Pkcs +{ + /// + /// A class for verifying and creating Pkcs10 Certification requests. + /// + /// + /// CertificationRequest ::= Sequence { + /// certificationRequestInfo CertificationRequestInfo, + /// signatureAlgorithm AlgorithmIdentifier{{ SignatureAlgorithms }}, + /// signature BIT STRING + /// } + /// + /// CertificationRequestInfo ::= Sequence { + /// version Integer { v1(0) } (v1,...), + /// subject Name, + /// subjectPKInfo SubjectPublicKeyInfo{{ PKInfoAlgorithms }}, + /// attributes [0] Attributes{{ CRIAttributes }} + /// } + /// + /// Attributes { ATTRIBUTE:IOSet } ::= Set OF Attr{{ IOSet }} + /// + /// Attr { ATTRIBUTE:IOSet } ::= Sequence { + /// type ATTRIBUTE.&id({IOSet}), + /// values Set SIZE(1..MAX) OF ATTRIBUTE.&Type({IOSet}{\@type}) + /// } + /// + /// see + public class Pkcs10CertificationRequest + : CertificationRequest + { + protected static readonly IDictionary algorithms = Platform.CreateHashtable(); + protected static readonly IDictionary exParams = Platform.CreateHashtable(); + protected static readonly IDictionary keyAlgorithms = Platform.CreateHashtable(); + protected static readonly IDictionary oids = Platform.CreateHashtable(); + protected static readonly ISet noParams = new HashSet(); + + static Pkcs10CertificationRequest() + { + algorithms.Add("MD2WITHRSAENCRYPTION", new DerObjectIdentifier("1.2.840.113549.1.1.2")); + algorithms.Add("MD2WITHRSA", new DerObjectIdentifier("1.2.840.113549.1.1.2")); + algorithms.Add("MD5WITHRSAENCRYPTION", new DerObjectIdentifier("1.2.840.113549.1.1.4")); + algorithms.Add("MD5WITHRSA", new DerObjectIdentifier("1.2.840.113549.1.1.4")); + algorithms.Add("RSAWITHMD5", new DerObjectIdentifier("1.2.840.113549.1.1.4")); + algorithms.Add("SHA1WITHRSAENCRYPTION", new DerObjectIdentifier("1.2.840.113549.1.1.5")); + algorithms.Add("SHA1WITHRSA", new DerObjectIdentifier("1.2.840.113549.1.1.5")); + algorithms.Add("SHA224WITHRSAENCRYPTION", PkcsObjectIdentifiers.Sha224WithRsaEncryption); + algorithms.Add("SHA224WITHRSA", PkcsObjectIdentifiers.Sha224WithRsaEncryption); + algorithms.Add("SHA256WITHRSAENCRYPTION", PkcsObjectIdentifiers.Sha256WithRsaEncryption); + algorithms.Add("SHA256WITHRSA", PkcsObjectIdentifiers.Sha256WithRsaEncryption); + algorithms.Add("SHA384WITHRSAENCRYPTION", PkcsObjectIdentifiers.Sha384WithRsaEncryption); + algorithms.Add("SHA384WITHRSA", PkcsObjectIdentifiers.Sha384WithRsaEncryption); + algorithms.Add("SHA512WITHRSAENCRYPTION", PkcsObjectIdentifiers.Sha512WithRsaEncryption); + algorithms.Add("SHA512WITHRSA", PkcsObjectIdentifiers.Sha512WithRsaEncryption); + algorithms.Add("SHA1WITHRSAANDMGF1", PkcsObjectIdentifiers.IdRsassaPss); + algorithms.Add("SHA224WITHRSAANDMGF1", PkcsObjectIdentifiers.IdRsassaPss); + algorithms.Add("SHA256WITHRSAANDMGF1", PkcsObjectIdentifiers.IdRsassaPss); + algorithms.Add("SHA384WITHRSAANDMGF1", PkcsObjectIdentifiers.IdRsassaPss); + algorithms.Add("SHA512WITHRSAANDMGF1", PkcsObjectIdentifiers.IdRsassaPss); + algorithms.Add("RSAWITHSHA1", new DerObjectIdentifier("1.2.840.113549.1.1.5")); + algorithms.Add("RIPEMD128WITHRSAENCRYPTION", TeleTrusTObjectIdentifiers.RsaSignatureWithRipeMD128); + algorithms.Add("RIPEMD128WITHRSA", TeleTrusTObjectIdentifiers.RsaSignatureWithRipeMD128); + algorithms.Add("RIPEMD160WITHRSAENCRYPTION", TeleTrusTObjectIdentifiers.RsaSignatureWithRipeMD160); + algorithms.Add("RIPEMD160WITHRSA", TeleTrusTObjectIdentifiers.RsaSignatureWithRipeMD160); + algorithms.Add("RIPEMD256WITHRSAENCRYPTION", TeleTrusTObjectIdentifiers.RsaSignatureWithRipeMD256); + algorithms.Add("RIPEMD256WITHRSA", TeleTrusTObjectIdentifiers.RsaSignatureWithRipeMD256); + algorithms.Add("SHA1WITHDSA", new DerObjectIdentifier("1.2.840.10040.4.3")); + algorithms.Add("DSAWITHSHA1", new DerObjectIdentifier("1.2.840.10040.4.3")); + algorithms.Add("SHA224WITHDSA", NistObjectIdentifiers.DsaWithSha224); + algorithms.Add("SHA256WITHDSA", NistObjectIdentifiers.DsaWithSha256); + algorithms.Add("SHA384WITHDSA", NistObjectIdentifiers.DsaWithSha384); + algorithms.Add("SHA512WITHDSA", NistObjectIdentifiers.DsaWithSha512); + algorithms.Add("SHA1WITHECDSA", X9ObjectIdentifiers.ECDsaWithSha1); + algorithms.Add("SHA224WITHECDSA", X9ObjectIdentifiers.ECDsaWithSha224); + algorithms.Add("SHA256WITHECDSA", X9ObjectIdentifiers.ECDsaWithSha256); + algorithms.Add("SHA384WITHECDSA", X9ObjectIdentifiers.ECDsaWithSha384); + algorithms.Add("SHA512WITHECDSA", X9ObjectIdentifiers.ECDsaWithSha512); + algorithms.Add("ECDSAWITHSHA1", X9ObjectIdentifiers.ECDsaWithSha1); + algorithms.Add("GOST3411WITHGOST3410", CryptoProObjectIdentifiers.GostR3411x94WithGostR3410x94); + algorithms.Add("GOST3410WITHGOST3411", CryptoProObjectIdentifiers.GostR3411x94WithGostR3410x94); + algorithms.Add("GOST3411WITHECGOST3410", CryptoProObjectIdentifiers.GostR3411x94WithGostR3410x2001); + algorithms.Add("GOST3411WITHECGOST3410-2001", CryptoProObjectIdentifiers.GostR3411x94WithGostR3410x2001); + algorithms.Add("GOST3411WITHGOST3410-2001", CryptoProObjectIdentifiers.GostR3411x94WithGostR3410x2001); + + // + // reverse mappings + // + oids.Add(new DerObjectIdentifier("1.2.840.113549.1.1.5"), "SHA1WITHRSA"); + oids.Add(PkcsObjectIdentifiers.Sha224WithRsaEncryption, "SHA224WITHRSA"); + oids.Add(PkcsObjectIdentifiers.Sha256WithRsaEncryption, "SHA256WITHRSA"); + oids.Add(PkcsObjectIdentifiers.Sha384WithRsaEncryption, "SHA384WITHRSA"); + oids.Add(PkcsObjectIdentifiers.Sha512WithRsaEncryption, "SHA512WITHRSA"); + oids.Add(CryptoProObjectIdentifiers.GostR3411x94WithGostR3410x94, "GOST3411WITHGOST3410"); + oids.Add(CryptoProObjectIdentifiers.GostR3411x94WithGostR3410x2001, "GOST3411WITHECGOST3410"); + + oids.Add(new DerObjectIdentifier("1.2.840.113549.1.1.4"), "MD5WITHRSA"); + oids.Add(new DerObjectIdentifier("1.2.840.113549.1.1.2"), "MD2WITHRSA"); + oids.Add(new DerObjectIdentifier("1.2.840.10040.4.3"), "SHA1WITHDSA"); + oids.Add(X9ObjectIdentifiers.ECDsaWithSha1, "SHA1WITHECDSA"); + oids.Add(X9ObjectIdentifiers.ECDsaWithSha224, "SHA224WITHECDSA"); + oids.Add(X9ObjectIdentifiers.ECDsaWithSha256, "SHA256WITHECDSA"); + oids.Add(X9ObjectIdentifiers.ECDsaWithSha384, "SHA384WITHECDSA"); + oids.Add(X9ObjectIdentifiers.ECDsaWithSha512, "SHA512WITHECDSA"); + oids.Add(OiwObjectIdentifiers.Sha1WithRsa, "SHA1WITHRSA"); + oids.Add(OiwObjectIdentifiers.DsaWithSha1, "SHA1WITHDSA"); + oids.Add(NistObjectIdentifiers.DsaWithSha224, "SHA224WITHDSA"); + oids.Add(NistObjectIdentifiers.DsaWithSha256, "SHA256WITHDSA"); + + // + // key types + // + keyAlgorithms.Add(PkcsObjectIdentifiers.RsaEncryption, "RSA"); + keyAlgorithms.Add(X9ObjectIdentifiers.IdDsa, "DSA"); + + // + // According to RFC 3279, the ASN.1 encoding SHALL (id-dsa-with-sha1) or MUST (ecdsa-with-SHA*) omit the parameters field. + // The parameters field SHALL be NULL for RSA based signature algorithms. + // + noParams.Add(X9ObjectIdentifiers.ECDsaWithSha1); + noParams.Add(X9ObjectIdentifiers.ECDsaWithSha224); + noParams.Add(X9ObjectIdentifiers.ECDsaWithSha256); + noParams.Add(X9ObjectIdentifiers.ECDsaWithSha384); + noParams.Add(X9ObjectIdentifiers.ECDsaWithSha512); + noParams.Add(X9ObjectIdentifiers.IdDsaWithSha1); + noParams.Add(NistObjectIdentifiers.DsaWithSha224); + noParams.Add(NistObjectIdentifiers.DsaWithSha256); + + // + // RFC 4491 + // + noParams.Add(CryptoProObjectIdentifiers.GostR3411x94WithGostR3410x94); + noParams.Add(CryptoProObjectIdentifiers.GostR3411x94WithGostR3410x2001); + + // + // explicit params + // + AlgorithmIdentifier sha1AlgId = new AlgorithmIdentifier(OiwObjectIdentifiers.IdSha1, DerNull.Instance); + exParams.Add("SHA1WITHRSAANDMGF1", CreatePssParams(sha1AlgId, 20)); + + AlgorithmIdentifier sha224AlgId = new AlgorithmIdentifier(NistObjectIdentifiers.IdSha224, DerNull.Instance); + exParams.Add("SHA224WITHRSAANDMGF1", CreatePssParams(sha224AlgId, 28)); + + AlgorithmIdentifier sha256AlgId = new AlgorithmIdentifier(NistObjectIdentifiers.IdSha256, DerNull.Instance); + exParams.Add("SHA256WITHRSAANDMGF1", CreatePssParams(sha256AlgId, 32)); + + AlgorithmIdentifier sha384AlgId = new AlgorithmIdentifier(NistObjectIdentifiers.IdSha384, DerNull.Instance); + exParams.Add("SHA384WITHRSAANDMGF1", CreatePssParams(sha384AlgId, 48)); + + AlgorithmIdentifier sha512AlgId = new AlgorithmIdentifier(NistObjectIdentifiers.IdSha512, DerNull.Instance); + exParams.Add("SHA512WITHRSAANDMGF1", CreatePssParams(sha512AlgId, 64)); + } + + private static RsassaPssParameters CreatePssParams( + AlgorithmIdentifier hashAlgId, + int saltSize) + { + return new RsassaPssParameters( + hashAlgId, + new AlgorithmIdentifier(PkcsObjectIdentifiers.IdMgf1, hashAlgId), + new DerInteger(saltSize), + new DerInteger(1)); + } + + protected Pkcs10CertificationRequest() + { + } + + public Pkcs10CertificationRequest( + byte[] encoded) + : base((Asn1Sequence) Asn1Object.FromByteArray(encoded)) + { + } + + public Pkcs10CertificationRequest( + Asn1Sequence seq) + : base(seq) + { + } + + public Pkcs10CertificationRequest( + Stream input) + : base((Asn1Sequence) Asn1Object.FromStream(input)) + { + } + + /// + /// Instantiate a Pkcs10CertificationRequest object with the necessary credentials. + /// + ///Name of Sig Alg. + /// X509Name of subject eg OU="My unit." O="My Organisatioin" C="au" + /// Public Key to be included in cert reqest. + /// ASN1Set of Attributes. + /// Matching Private key for nominated (above) public key to be used to sign the request. + [Obsolete("Use constructor with an ISignatureFactory")] + public Pkcs10CertificationRequest( + string signatureAlgorithm, + X509Name subject, + AsymmetricKeyParameter publicKey, + Asn1Set attributes, + AsymmetricKeyParameter signingKey) + { + if (signatureAlgorithm == null) + throw new ArgumentNullException("signatureAlgorithm"); + if (subject == null) + throw new ArgumentNullException("subject"); + if (publicKey == null) + throw new ArgumentNullException("publicKey"); + if (publicKey.IsPrivate) + throw new ArgumentException("expected public key", "publicKey"); + if (!signingKey.IsPrivate) + throw new ArgumentException("key for signing must be private", "signingKey"); + + init(new Asn1SignatureFactory(signatureAlgorithm, signingKey), subject, publicKey, attributes, signingKey); + } + + /// + /// Instantiate a Pkcs10CertificationRequest object with the necessary credentials. + /// + ///The factory for signature calculators to sign the PKCS#10 request with. + /// X509Name of subject eg OU="My unit." O="My Organisatioin" C="au" + /// Public Key to be included in cert reqest. + /// ASN1Set of Attributes. + /// Matching Private key for nominated (above) public key to be used to sign the request. + public Pkcs10CertificationRequest( + ISignatureFactory signatureCalculatorFactory, + X509Name subject, + AsymmetricKeyParameter publicKey, + Asn1Set attributes, + AsymmetricKeyParameter signingKey) + { + if (signatureCalculatorFactory == null) + throw new ArgumentNullException("signatureCalculator"); + if (subject == null) + throw new ArgumentNullException("subject"); + if (publicKey == null) + throw new ArgumentNullException("publicKey"); + if (publicKey.IsPrivate) + throw new ArgumentException("expected public key", "publicKey"); + if (!signingKey.IsPrivate) + throw new ArgumentException("key for signing must be private", "signingKey"); + + init(signatureCalculatorFactory, subject, publicKey, attributes, signingKey); + } + + private void init( + ISignatureFactory signatureCalculator, + X509Name subject, + AsymmetricKeyParameter publicKey, + Asn1Set attributes, + AsymmetricKeyParameter signingKey) + { + this.sigAlgId = (AlgorithmIdentifier)signatureCalculator.AlgorithmDetails; + + SubjectPublicKeyInfo pubInfo = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(publicKey); + + this.reqInfo = new CertificationRequestInfo(subject, pubInfo, attributes); + + IStreamCalculator streamCalculator = signatureCalculator.CreateCalculator(); + + byte[] reqInfoData = reqInfo.GetDerEncoded(); + + streamCalculator.Stream.Write(reqInfoData, 0, reqInfoData.Length); + + Platform.Dispose(streamCalculator.Stream); + + // Generate Signature. + sigBits = new DerBitString(((IBlockResult)streamCalculator.GetResult()).Collect()); + } + + // internal Pkcs10CertificationRequest( + // Asn1InputStream seqStream) + // { + // Asn1Sequence seq = (Asn1Sequence) seqStream.ReadObject(); + // try + // { + // this.reqInfo = CertificationRequestInfo.GetInstance(seq[0]); + // this.sigAlgId = AlgorithmIdentifier.GetInstance(seq[1]); + // this.sigBits = (DerBitString) seq[2]; + // } + // catch (Exception ex) + // { + // throw new ArgumentException("Create From Asn1Sequence: " + ex.Message); + // } + // } + + /// + /// Get the public key. + /// + /// The public key. + public AsymmetricKeyParameter GetPublicKey() + { + return PublicKeyFactory.CreateKey(reqInfo.SubjectPublicKeyInfo); + } + + /// + /// Verify Pkcs10 Cert Request is valid. + /// + /// true = valid. + public bool Verify() + { + return Verify(this.GetPublicKey()); + } + + public bool Verify( + AsymmetricKeyParameter publicKey) + { + return Verify(new Asn1VerifierFactoryProvider(publicKey)); + } + + public bool Verify( + IVerifierFactoryProvider verifierProvider) + { + return Verify(verifierProvider.CreateVerifierFactory(sigAlgId)); + } + + public bool Verify( + IVerifierFactory verifier) + { + try + { + byte[] b = reqInfo.GetDerEncoded(); + + IStreamCalculator streamCalculator = verifier.CreateCalculator(); + + streamCalculator.Stream.Write(b, 0, b.Length); + + Platform.Dispose(streamCalculator.Stream); + + return ((IVerifier)streamCalculator.GetResult()).IsVerified(sigBits.GetOctets()); + } + catch (Exception e) + { + throw new SignatureException("exception encoding TBS cert request", e); + } + } + + // /// + // /// Get the Der Encoded Pkcs10 Certification Request. + // /// + // /// A byte array. + // public byte[] GetEncoded() + // { + // return new CertificationRequest(reqInfo, sigAlgId, sigBits).GetDerEncoded(); + // } + + // TODO Figure out how to set parameters on an ISigner + private void SetSignatureParameters( + ISigner signature, + Asn1Encodable asn1Params) + { + if (asn1Params != null && !(asn1Params is Asn1Null)) + { +// AlgorithmParameters sigParams = AlgorithmParameters.GetInstance(signature.getAlgorithm()); +// +// try +// { +// sigParams.init(asn1Params.ToAsn1Object().GetDerEncoded()); +// } +// catch (IOException e) +// { +// throw new SignatureException("IOException decoding parameters: " + e.Message); +// } + + if (Platform.EndsWith(signature.AlgorithmName, "MGF1")) + { + throw Platform.CreateNotImplementedException("signature algorithm with MGF1"); + +// try +// { +// signature.setParameter(sigParams.getParameterSpec(PSSParameterSpec.class)); +// } +// catch (GeneralSecurityException e) +// { +// throw new SignatureException("Exception extracting parameters: " + e.getMessage()); +// } + } + } + } + + internal static string GetSignatureName( + AlgorithmIdentifier sigAlgId) + { + Asn1Encodable asn1Params = sigAlgId.Parameters; + + if (asn1Params != null && !(asn1Params is Asn1Null)) + { + if (sigAlgId.Algorithm.Equals(PkcsObjectIdentifiers.IdRsassaPss)) + { + RsassaPssParameters rsaParams = RsassaPssParameters.GetInstance(asn1Params); + return GetDigestAlgName(rsaParams.HashAlgorithm.Algorithm) + "withRSAandMGF1"; + } + } + + return sigAlgId.Algorithm.Id; + } + + private static string GetDigestAlgName( + DerObjectIdentifier digestAlgOID) + { + if (PkcsObjectIdentifiers.MD5.Equals(digestAlgOID)) + { + return "MD5"; + } + else if (OiwObjectIdentifiers.IdSha1.Equals(digestAlgOID)) + { + return "SHA1"; + } + else if (NistObjectIdentifiers.IdSha224.Equals(digestAlgOID)) + { + return "SHA224"; + } + else if (NistObjectIdentifiers.IdSha256.Equals(digestAlgOID)) + { + return "SHA256"; + } + else if (NistObjectIdentifiers.IdSha384.Equals(digestAlgOID)) + { + return "SHA384"; + } + else if (NistObjectIdentifiers.IdSha512.Equals(digestAlgOID)) + { + return "SHA512"; + } + else if (TeleTrusTObjectIdentifiers.RipeMD128.Equals(digestAlgOID)) + { + return "RIPEMD128"; + } + else if (TeleTrusTObjectIdentifiers.RipeMD160.Equals(digestAlgOID)) + { + return "RIPEMD160"; + } + else if (TeleTrusTObjectIdentifiers.RipeMD256.Equals(digestAlgOID)) + { + return "RIPEMD256"; + } + else if (CryptoProObjectIdentifiers.GostR3411.Equals(digestAlgOID)) + { + return "GOST3411"; + } + else + { + return digestAlgOID.Id; + } + } + } +} diff --git a/bc-sharp-crypto/src/pkcs/Pkcs10CertificationRequestDelaySigned.cs b/bc-sharp-crypto/src/pkcs/Pkcs10CertificationRequestDelaySigned.cs new file mode 100644 index 0000000..ecbb4ab --- /dev/null +++ b/bc-sharp-crypto/src/pkcs/Pkcs10CertificationRequestDelaySigned.cs @@ -0,0 +1,150 @@ +using System; +using System.Collections; +using System.Globalization; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.CryptoPro; +using Org.BouncyCastle.Asn1.Nist; +using Org.BouncyCastle.Asn1.Oiw; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.TeleTrust; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; +using Org.BouncyCastle.X509; + +namespace Org.BouncyCastle.Pkcs +{ + /// + /// A class for creating and verifying Pkcs10 Certification requests (this is an extension on ). + /// The requests are made using delay signing. This is useful for situations where + /// the private key is in another environment and not directly accessible (e.g. HSM) + /// So the first step creates the request, then the signing is done outside this + /// object and the signature is then used to complete the request. + /// + /// + /// CertificationRequest ::= Sequence { + /// certificationRequestInfo CertificationRequestInfo, + /// signatureAlgorithm AlgorithmIdentifier{{ SignatureAlgorithms }}, + /// signature BIT STRING + /// } + /// + /// CertificationRequestInfo ::= Sequence { + /// version Integer { v1(0) } (v1,...), + /// subject Name, + /// subjectPKInfo SubjectPublicKeyInfo{{ PKInfoAlgorithms }}, + /// attributes [0] Attributes{{ CRIAttributes }} + /// } + /// + /// Attributes { ATTRIBUTE:IOSet } ::= Set OF Attr{{ IOSet }} + /// + /// Attr { ATTRIBUTE:IOSet } ::= Sequence { + /// type ATTRIBUTE.&id({IOSet}), + /// values Set SIZE(1..MAX) OF ATTRIBUTE.&Type({IOSet}{\@type}) + /// } + /// + /// see + public class Pkcs10CertificationRequestDelaySigned : Pkcs10CertificationRequest + { + protected Pkcs10CertificationRequestDelaySigned() + : base() + { + } + public Pkcs10CertificationRequestDelaySigned( + byte[] encoded) + : base(encoded) + { + } + public Pkcs10CertificationRequestDelaySigned( + Asn1Sequence seq) + : base(seq) + { + } + public Pkcs10CertificationRequestDelaySigned( + Stream input) + : base(input) + { + } + public Pkcs10CertificationRequestDelaySigned( + string signatureAlgorithm, + X509Name subject, + AsymmetricKeyParameter publicKey, + Asn1Set attributes, + AsymmetricKeyParameter signingKey) + : base(signatureAlgorithm, subject, publicKey, attributes, signingKey) + { + } + /// + /// Instantiate a Pkcs10CertificationRequest object with the necessary credentials. + /// + /// Name of Sig Alg. + /// X509Name of subject eg OU="My unit." O="My Organisatioin" C="au" + /// Public Key to be included in cert reqest. + /// ASN1Set of Attributes. + /// + /// After the object is constructed use the and finally the + /// SignRequest methods to finalize the request. + /// + public Pkcs10CertificationRequestDelaySigned( + string signatureAlgorithm, + X509Name subject, + AsymmetricKeyParameter publicKey, + Asn1Set attributes) + { + if (signatureAlgorithm == null) + throw new ArgumentNullException("signatureAlgorithm"); + if (subject == null) + throw new ArgumentNullException("subject"); + if (publicKey == null) + throw new ArgumentNullException("publicKey"); + if (publicKey.IsPrivate) + throw new ArgumentException("expected public key", "publicKey"); +// DerObjectIdentifier sigOid = SignerUtilities.GetObjectIdentifier(signatureAlgorithm); + string algorithmName = Platform.ToUpperInvariant(signatureAlgorithm); + DerObjectIdentifier sigOid = (DerObjectIdentifier) algorithms[algorithmName]; + if (sigOid == null) + { + try + { + sigOid = new DerObjectIdentifier(algorithmName); + } + catch (Exception e) + { + throw new ArgumentException("Unknown signature type requested", e); + } + } + if (noParams.Contains(sigOid)) + { + this.sigAlgId = new AlgorithmIdentifier(sigOid); + } + else if (exParams.Contains(algorithmName)) + { + this.sigAlgId = new AlgorithmIdentifier(sigOid, (Asn1Encodable) exParams[algorithmName]); + } + else + { + this.sigAlgId = new AlgorithmIdentifier(sigOid, DerNull.Instance); + } + SubjectPublicKeyInfo pubInfo = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(publicKey); + this.reqInfo = new CertificationRequestInfo(subject, pubInfo, attributes); + } + public byte[] GetDataToSign() + { + return reqInfo.GetDerEncoded(); + } + public void SignRequest(byte[] signedData) + { + //build the signature from the signed data + sigBits = new DerBitString(signedData); + } + public void SignRequest(DerBitString signedData) + { + //build the signature from the signed data + sigBits = signedData; + } + } +} diff --git a/bc-sharp-crypto/src/pkcs/Pkcs12Entry.cs b/bc-sharp-crypto/src/pkcs/Pkcs12Entry.cs new file mode 100644 index 0000000..5dcc94e --- /dev/null +++ b/bc-sharp-crypto/src/pkcs/Pkcs12Entry.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Utilities.Collections; + +namespace Org.BouncyCastle.Pkcs +{ + public abstract class Pkcs12Entry + { + private readonly IDictionary attributes; + + protected internal Pkcs12Entry( + IDictionary attributes) + { + this.attributes = attributes; + + foreach (DictionaryEntry entry in attributes) + { + if (!(entry.Key is string)) + throw new ArgumentException("Attribute keys must be of type: " + typeof(string).FullName, "attributes"); + if (!(entry.Value is Asn1Encodable)) + throw new ArgumentException("Attribute values must be of type: " + typeof(Asn1Encodable).FullName, "attributes"); + } + } + + [Obsolete("Use 'object[index]' syntax instead")] + public Asn1Encodable GetBagAttribute( + DerObjectIdentifier oid) + { + return (Asn1Encodable)this.attributes[oid.Id]; + } + + [Obsolete("Use 'object[index]' syntax instead")] + public Asn1Encodable GetBagAttribute( + string oid) + { + return (Asn1Encodable)this.attributes[oid]; + } + + [Obsolete("Use 'BagAttributeKeys' property")] + public IEnumerator GetBagAttributeKeys() + { + return this.attributes.Keys.GetEnumerator(); + } + + public Asn1Encodable this[ + DerObjectIdentifier oid] + { + get { return (Asn1Encodable) this.attributes[oid.Id]; } + } + + public Asn1Encodable this[ + string oid] + { + get { return (Asn1Encodable) this.attributes[oid]; } + } + + public IEnumerable BagAttributeKeys + { + get { return new EnumerableProxy(this.attributes.Keys); } + } + } +} diff --git a/bc-sharp-crypto/src/pkcs/Pkcs12Store.cs b/bc-sharp-crypto/src/pkcs/Pkcs12Store.cs new file mode 100644 index 0000000..e657887 --- /dev/null +++ b/bc-sharp-crypto/src/pkcs/Pkcs12Store.cs @@ -0,0 +1,1100 @@ +using System; +using System.Collections; +using System.IO; +using System.Text; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Oiw; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Asn1.Utilities; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; +using Org.BouncyCastle.Utilities.Encoders; +using Org.BouncyCastle.X509; + +namespace Org.BouncyCastle.Pkcs +{ + public class Pkcs12Store + { + private readonly IgnoresCaseHashtable keys = new IgnoresCaseHashtable(); + private readonly IDictionary localIds = Platform.CreateHashtable(); + private readonly IgnoresCaseHashtable certs = new IgnoresCaseHashtable(); + private readonly IDictionary chainCerts = Platform.CreateHashtable(); + private readonly IDictionary keyCerts = Platform.CreateHashtable(); + private readonly DerObjectIdentifier keyAlgorithm; + private readonly DerObjectIdentifier certAlgorithm; + private readonly bool useDerEncoding; + + private AsymmetricKeyEntry unmarkedKeyEntry = null; + + private const int MinIterations = 1024; + private const int SaltSize = 20; + + private static SubjectKeyIdentifier CreateSubjectKeyID( + AsymmetricKeyParameter pubKey) + { + return new SubjectKeyIdentifier( + SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(pubKey)); + } + + internal class CertId + { + private readonly byte[] id; + + internal CertId( + AsymmetricKeyParameter pubKey) + { + this.id = CreateSubjectKeyID(pubKey).GetKeyIdentifier(); + } + + internal CertId( + byte[] id) + { + this.id = id; + } + + internal byte[] Id + { + get { return id; } + } + + public override int GetHashCode() + { + return Arrays.GetHashCode(id); + } + + public override bool Equals( + object obj) + { + if (obj == this) + return true; + + CertId other = obj as CertId; + + if (other == null) + return false; + + return Arrays.AreEqual(id, other.id); + } + } + + internal Pkcs12Store( + DerObjectIdentifier keyAlgorithm, + DerObjectIdentifier certAlgorithm, + bool useDerEncoding) + { + this.keyAlgorithm = keyAlgorithm; + this.certAlgorithm = certAlgorithm; + this.useDerEncoding = useDerEncoding; + } + + // TODO Consider making obsolete +// [Obsolete("Use 'Pkcs12StoreBuilder' instead")] + public Pkcs12Store() + : this(PkcsObjectIdentifiers.PbeWithShaAnd3KeyTripleDesCbc, + PkcsObjectIdentifiers.PbewithShaAnd40BitRC2Cbc, false) + { + } + + // TODO Consider making obsolete +// [Obsolete("Use 'Pkcs12StoreBuilder' and 'Load' method instead")] + public Pkcs12Store( + Stream input, + char[] password) + : this() + { + Load(input, password); + } + + protected virtual void LoadKeyBag(PrivateKeyInfo privKeyInfo, Asn1Set bagAttributes) + { + AsymmetricKeyParameter privKey = PrivateKeyFactory.CreateKey(privKeyInfo); + + IDictionary attributes = Platform.CreateHashtable(); + AsymmetricKeyEntry keyEntry = new AsymmetricKeyEntry(privKey, attributes); + + string alias = null; + Asn1OctetString localId = null; + + if (bagAttributes != null) + { + foreach (Asn1Sequence sq in bagAttributes) + { + DerObjectIdentifier aOid = DerObjectIdentifier.GetInstance(sq[0]); + Asn1Set attrSet = Asn1Set.GetInstance(sq[1]); + Asn1Encodable attr = null; + + if (attrSet.Count > 0) + { + // TODO We should be adding all attributes in the set + attr = attrSet[0]; + + // TODO We might want to "merge" attribute sets with + // the same OID - currently, differing values give an error + if (attributes.Contains(aOid.Id)) + { + // OK, but the value has to be the same + if (!attributes[aOid.Id].Equals(attr)) + throw new IOException("attempt to add existing attribute with different value"); + } + else + { + attributes.Add(aOid.Id, attr); + } + + if (aOid.Equals(PkcsObjectIdentifiers.Pkcs9AtFriendlyName)) + { + alias = ((DerBmpString)attr).GetString(); + // TODO Do these in a separate loop, just collect aliases here + keys[alias] = keyEntry; + } + else if (aOid.Equals(PkcsObjectIdentifiers.Pkcs9AtLocalKeyID)) + { + localId = (Asn1OctetString)attr; + } + } + } + } + + if (localId != null) + { + string name = Hex.ToHexString(localId.GetOctets()); + + if (alias == null) + { + keys[name] = keyEntry; + } + else + { + // TODO There may have been more than one alias + localIds[alias] = name; + } + } + else + { + unmarkedKeyEntry = keyEntry; + } + } + + protected virtual void LoadPkcs8ShroudedKeyBag(EncryptedPrivateKeyInfo encPrivKeyInfo, Asn1Set bagAttributes, + char[] password, bool wrongPkcs12Zero) + { + if (password != null) + { + PrivateKeyInfo privInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo( + password, wrongPkcs12Zero, encPrivKeyInfo); + + LoadKeyBag(privInfo, bagAttributes); + } + } + + public void Load( + Stream input, + char[] password) + { + if (input == null) + throw new ArgumentNullException("input"); + + Asn1Sequence obj = (Asn1Sequence) Asn1Object.FromStream(input); + Pfx bag = new Pfx(obj); + ContentInfo info = bag.AuthSafe; + bool wrongPkcs12Zero = false; + + if (password != null && bag.MacData != null) // check the mac code + { + MacData mData = bag.MacData; + DigestInfo dInfo = mData.Mac; + AlgorithmIdentifier algId = dInfo.AlgorithmID; + byte[] salt = mData.GetSalt(); + int itCount = mData.IterationCount.IntValue; + + byte[] data = ((Asn1OctetString) info.Content).GetOctets(); + + byte[] mac = CalculatePbeMac(algId.Algorithm, salt, itCount, password, false, data); + byte[] dig = dInfo.GetDigest(); + + if (!Arrays.ConstantTimeAreEqual(mac, dig)) + { + if (password.Length > 0) + throw new IOException("PKCS12 key store MAC invalid - wrong password or corrupted file."); + + // Try with incorrect zero length password + mac = CalculatePbeMac(algId.Algorithm, salt, itCount, password, true, data); + + if (!Arrays.ConstantTimeAreEqual(mac, dig)) + throw new IOException("PKCS12 key store MAC invalid - wrong password or corrupted file."); + + wrongPkcs12Zero = true; + } + } + + keys.Clear(); + localIds.Clear(); + unmarkedKeyEntry = null; + + IList certBags = Platform.CreateArrayList(); + + if (info.ContentType.Equals(PkcsObjectIdentifiers.Data)) + { + byte[] octs = ((Asn1OctetString)info.Content).GetOctets(); + AuthenticatedSafe authSafe = new AuthenticatedSafe( + (Asn1Sequence) Asn1OctetString.FromByteArray(octs)); + ContentInfo[] cis = authSafe.GetContentInfo(); + + foreach (ContentInfo ci in cis) + { + DerObjectIdentifier oid = ci.ContentType; + + byte[] octets = null; + if (oid.Equals(PkcsObjectIdentifiers.Data)) + { + octets = ((Asn1OctetString)ci.Content).GetOctets(); + } + else if (oid.Equals(PkcsObjectIdentifiers.EncryptedData)) + { + if (password != null) + { + EncryptedData d = EncryptedData.GetInstance(ci.Content); + octets = CryptPbeData(false, d.EncryptionAlgorithm, + password, wrongPkcs12Zero, d.Content.GetOctets()); + } + } + else + { + // TODO Other data types + } + + if (octets != null) + { + Asn1Sequence seq = (Asn1Sequence)Asn1Object.FromByteArray(octets); + + foreach (Asn1Sequence subSeq in seq) + { + SafeBag b = new SafeBag(subSeq); + + if (b.BagID.Equals(PkcsObjectIdentifiers.CertBag)) + { + certBags.Add(b); + } + else if (b.BagID.Equals(PkcsObjectIdentifiers.Pkcs8ShroudedKeyBag)) + { + LoadPkcs8ShroudedKeyBag(EncryptedPrivateKeyInfo.GetInstance(b.BagValue), + b.BagAttributes, password, wrongPkcs12Zero); + } + else if (b.BagID.Equals(PkcsObjectIdentifiers.KeyBag)) + { + LoadKeyBag(PrivateKeyInfo.GetInstance(b.BagValue), b.BagAttributes); + } + else + { + // TODO Other bag types + } + } + } + } + } + + certs.Clear(); + chainCerts.Clear(); + keyCerts.Clear(); + + foreach (SafeBag b in certBags) + { + CertBag certBag = new CertBag((Asn1Sequence)b.BagValue); + byte[] octets = ((Asn1OctetString)certBag.CertValue).GetOctets(); + X509Certificate cert = new X509CertificateParser().ReadCertificate(octets); + + // + // set the attributes + // + IDictionary attributes = Platform.CreateHashtable(); + Asn1OctetString localId = null; + string alias = null; + + if (b.BagAttributes != null) + { + foreach (Asn1Sequence sq in b.BagAttributes) + { + DerObjectIdentifier aOid = DerObjectIdentifier.GetInstance(sq[0]); + Asn1Set attrSet = Asn1Set.GetInstance(sq[1]); + + if (attrSet.Count > 0) + { + // TODO We should be adding all attributes in the set + Asn1Encodable attr = attrSet[0]; + + // TODO We might want to "merge" attribute sets with + // the same OID - currently, differing values give an error + if (attributes.Contains(aOid.Id)) + { + // OK, but the value has to be the same + if (!attributes[aOid.Id].Equals(attr)) + { + throw new IOException("attempt to add existing attribute with different value"); + } + } + else + { + attributes.Add(aOid.Id, attr); + } + + if (aOid.Equals(PkcsObjectIdentifiers.Pkcs9AtFriendlyName)) + { + alias = ((DerBmpString)attr).GetString(); + } + else if (aOid.Equals(PkcsObjectIdentifiers.Pkcs9AtLocalKeyID)) + { + localId = (Asn1OctetString)attr; + } + } + } + } + + CertId certId = new CertId(cert.GetPublicKey()); + X509CertificateEntry certEntry = new X509CertificateEntry(cert, attributes); + + chainCerts[certId] = certEntry; + + if (unmarkedKeyEntry != null) + { + if (keyCerts.Count == 0) + { + string name = Hex.ToHexString(certId.Id); + + keyCerts[name] = certEntry; + keys[name] = unmarkedKeyEntry; + } + } + else + { + if (localId != null) + { + string name = Hex.ToHexString(localId.GetOctets()); + + keyCerts[name] = certEntry; + } + + if (alias != null) + { + // TODO There may have been more than one alias + certs[alias] = certEntry; + } + } + } + } + + public AsymmetricKeyEntry GetKey( + string alias) + { + if (alias == null) + throw new ArgumentNullException("alias"); + + return (AsymmetricKeyEntry)keys[alias]; + } + + public bool IsCertificateEntry( + string alias) + { + if (alias == null) + throw new ArgumentNullException("alias"); + + return (certs[alias] != null && keys[alias] == null); + } + + public bool IsKeyEntry( + string alias) + { + if (alias == null) + throw new ArgumentNullException("alias"); + + return (keys[alias] != null); + } + + private IDictionary GetAliasesTable() + { + IDictionary tab = Platform.CreateHashtable(); + + foreach (string key in certs.Keys) + { + tab[key] = "cert"; + } + + foreach (string a in keys.Keys) + { + if (tab[a] == null) + { + tab[a] = "key"; + } + } + + return tab; + } + + public IEnumerable Aliases + { + get { return new EnumerableProxy(GetAliasesTable().Keys); } + } + + public bool ContainsAlias( + string alias) + { + return certs[alias] != null || keys[alias] != null; + } + + /** + * simply return the cert entry for the private key + */ + public X509CertificateEntry GetCertificate( + string alias) + { + if (alias == null) + throw new ArgumentNullException("alias"); + + X509CertificateEntry c = (X509CertificateEntry) certs[alias]; + + // + // look up the key table - and try the local key id + // + if (c == null) + { + string id = (string)localIds[alias]; + if (id != null) + { + c = (X509CertificateEntry)keyCerts[id]; + } + else + { + c = (X509CertificateEntry)keyCerts[alias]; + } + } + + return c; + } + + public string GetCertificateAlias( + X509Certificate cert) + { + if (cert == null) + throw new ArgumentNullException("cert"); + + foreach (DictionaryEntry entry in certs) + { + X509CertificateEntry entryValue = (X509CertificateEntry) entry.Value; + if (entryValue.Certificate.Equals(cert)) + { + return (string) entry.Key; + } + } + + foreach (DictionaryEntry entry in keyCerts) + { + X509CertificateEntry entryValue = (X509CertificateEntry) entry.Value; + if (entryValue.Certificate.Equals(cert)) + { + return (string) entry.Key; + } + } + + return null; + } + + public X509CertificateEntry[] GetCertificateChain( + string alias) + { + if (alias == null) + throw new ArgumentNullException("alias"); + + if (!IsKeyEntry(alias)) + { + return null; + } + + X509CertificateEntry c = GetCertificate(alias); + + if (c != null) + { + IList cs = Platform.CreateArrayList(); + + while (c != null) + { + X509Certificate x509c = c.Certificate; + X509CertificateEntry nextC = null; + + Asn1OctetString ext = x509c.GetExtensionValue(X509Extensions.AuthorityKeyIdentifier); + if (ext != null) + { + AuthorityKeyIdentifier id = AuthorityKeyIdentifier.GetInstance( + Asn1Object.FromByteArray(ext.GetOctets())); + + if (id.GetKeyIdentifier() != null) + { + nextC = (X509CertificateEntry) chainCerts[new CertId(id.GetKeyIdentifier())]; + } + } + + if (nextC == null) + { + // + // no authority key id, try the Issuer DN + // + X509Name i = x509c.IssuerDN; + X509Name s = x509c.SubjectDN; + + if (!i.Equivalent(s)) + { + foreach (CertId certId in chainCerts.Keys) + { + X509CertificateEntry x509CertEntry = (X509CertificateEntry) chainCerts[certId]; + + X509Certificate crt = x509CertEntry.Certificate; + + X509Name sub = crt.SubjectDN; + if (sub.Equivalent(i)) + { + try + { + x509c.Verify(crt.GetPublicKey()); + + nextC = x509CertEntry; + break; + } + catch (InvalidKeyException) + { + // TODO What if it doesn't verify? + } + } + } + } + } + + cs.Add(c); + if (nextC != c) // self signed - end of the chain + { + c = nextC; + } + else + { + c = null; + } + } + + X509CertificateEntry[] result = new X509CertificateEntry[cs.Count]; + for (int i = 0; i < cs.Count; ++i) + { + result[i] = (X509CertificateEntry)cs[i]; + } + return result; + } + + return null; + } + + public void SetCertificateEntry( + string alias, + X509CertificateEntry certEntry) + { + if (alias == null) + throw new ArgumentNullException("alias"); + if (certEntry == null) + throw new ArgumentNullException("certEntry"); + if (keys[alias] != null) + throw new ArgumentException("There is a key entry with the name " + alias + "."); + + certs[alias] = certEntry; + chainCerts[new CertId(certEntry.Certificate.GetPublicKey())] = certEntry; + } + + public void SetKeyEntry( + string alias, + AsymmetricKeyEntry keyEntry, + X509CertificateEntry[] chain) + { + if (alias == null) + throw new ArgumentNullException("alias"); + if (keyEntry == null) + throw new ArgumentNullException("keyEntry"); + if (keyEntry.Key.IsPrivate && (chain == null)) + throw new ArgumentException("No certificate chain for private key"); + + if (keys[alias] != null) + { + DeleteEntry(alias); + } + + keys[alias] = keyEntry; + certs[alias] = chain[0]; + + for (int i = 0; i != chain.Length; i++) + { + chainCerts[new CertId(chain[i].Certificate.GetPublicKey())] = chain[i]; + } + } + + public void DeleteEntry( + string alias) + { + if (alias == null) + throw new ArgumentNullException("alias"); + + AsymmetricKeyEntry k = (AsymmetricKeyEntry)keys[alias]; + if (k != null) + { + keys.Remove(alias); + } + + X509CertificateEntry c = (X509CertificateEntry)certs[alias]; + + if (c != null) + { + certs.Remove(alias); + chainCerts.Remove(new CertId(c.Certificate.GetPublicKey())); + } + + if (k != null) + { + string id = (string)localIds[alias]; + if (id != null) + { + localIds.Remove(alias); + c = (X509CertificateEntry)keyCerts[id]; + } + if (c != null) + { + keyCerts.Remove(id); + chainCerts.Remove(new CertId(c.Certificate.GetPublicKey())); + } + } + + if (c == null && k == null) + { + throw new ArgumentException("no such entry as " + alias); + } + } + + public bool IsEntryOfType( + string alias, + Type entryType) + { + if (entryType == typeof(X509CertificateEntry)) + return IsCertificateEntry(alias); + + if (entryType == typeof(AsymmetricKeyEntry)) + return IsKeyEntry(alias) && GetCertificate(alias) != null; + + return false; + } + + [Obsolete("Use 'Count' property instead")] + public int Size() + { + return Count; + } + + public int Count + { + // TODO Seems a little inefficient + get { return GetAliasesTable().Count; } + } + + public void Save( + Stream stream, + char[] password, + SecureRandom random) + { + if (stream == null) + throw new ArgumentNullException("stream"); + if (random == null) + throw new ArgumentNullException("random"); + + // + // handle the keys + // + Asn1EncodableVector keyBags = new Asn1EncodableVector(); + foreach (string name in keys.Keys) + { + byte[] kSalt = new byte[SaltSize]; + random.NextBytes(kSalt); + + AsymmetricKeyEntry privKey = (AsymmetricKeyEntry)keys[name]; + + DerObjectIdentifier bagOid; + Asn1Encodable bagData; + + if (password == null) + { + bagOid = PkcsObjectIdentifiers.KeyBag; + bagData = PrivateKeyInfoFactory.CreatePrivateKeyInfo(privKey.Key); + } + else + { + bagOid = PkcsObjectIdentifiers.Pkcs8ShroudedKeyBag; + bagData = EncryptedPrivateKeyInfoFactory.CreateEncryptedPrivateKeyInfo( + keyAlgorithm, password, kSalt, MinIterations, privKey.Key); + } + + Asn1EncodableVector kName = new Asn1EncodableVector(); + + foreach (string oid in privKey.BagAttributeKeys) + { + Asn1Encodable entry = privKey[oid]; + + // NB: Ignore any existing FriendlyName + if (oid.Equals(PkcsObjectIdentifiers.Pkcs9AtFriendlyName.Id)) + continue; + + kName.Add( + new DerSequence( + new DerObjectIdentifier(oid), + new DerSet(entry))); + } + + // + // make sure we are using the local alias on store + // + // NB: We always set the FriendlyName based on 'name' + //if (privKey[PkcsObjectIdentifiers.Pkcs9AtFriendlyName] == null) + { + kName.Add( + new DerSequence( + PkcsObjectIdentifiers.Pkcs9AtFriendlyName, + new DerSet(new DerBmpString(name)))); + } + + // + // make sure we have a local key-id + // + if (privKey[PkcsObjectIdentifiers.Pkcs9AtLocalKeyID] == null) + { + X509CertificateEntry ct = GetCertificate(name); + AsymmetricKeyParameter pubKey = ct.Certificate.GetPublicKey(); + SubjectKeyIdentifier subjectKeyID = CreateSubjectKeyID(pubKey); + + kName.Add( + new DerSequence( + PkcsObjectIdentifiers.Pkcs9AtLocalKeyID, + new DerSet(subjectKeyID))); + } + + keyBags.Add(new SafeBag(bagOid, bagData.ToAsn1Object(), new DerSet(kName))); + } + + byte[] keyBagsEncoding = new DerSequence(keyBags).GetDerEncoded(); + ContentInfo keysInfo = new ContentInfo(PkcsObjectIdentifiers.Data, new BerOctetString(keyBagsEncoding)); + + // + // certificate processing + // + byte[] cSalt = new byte[SaltSize]; + + random.NextBytes(cSalt); + + Asn1EncodableVector certBags = new Asn1EncodableVector(); + Pkcs12PbeParams cParams = new Pkcs12PbeParams(cSalt, MinIterations); + AlgorithmIdentifier cAlgId = new AlgorithmIdentifier(certAlgorithm, cParams.ToAsn1Object()); + ISet doneCerts = new HashSet(); + + foreach (string name in keys.Keys) + { + X509CertificateEntry certEntry = GetCertificate(name); + CertBag cBag = new CertBag( + PkcsObjectIdentifiers.X509Certificate, + new DerOctetString(certEntry.Certificate.GetEncoded())); + + Asn1EncodableVector fName = new Asn1EncodableVector(); + + foreach (string oid in certEntry.BagAttributeKeys) + { + Asn1Encodable entry = certEntry[oid]; + + // NB: Ignore any existing FriendlyName + if (oid.Equals(PkcsObjectIdentifiers.Pkcs9AtFriendlyName.Id)) + continue; + + fName.Add( + new DerSequence( + new DerObjectIdentifier(oid), + new DerSet(entry))); + } + + // + // make sure we are using the local alias on store + // + // NB: We always set the FriendlyName based on 'name' + //if (certEntry[PkcsObjectIdentifiers.Pkcs9AtFriendlyName] == null) + { + fName.Add( + new DerSequence( + PkcsObjectIdentifiers.Pkcs9AtFriendlyName, + new DerSet(new DerBmpString(name)))); + } + + // + // make sure we have a local key-id + // + if (certEntry[PkcsObjectIdentifiers.Pkcs9AtLocalKeyID] == null) + { + AsymmetricKeyParameter pubKey = certEntry.Certificate.GetPublicKey(); + SubjectKeyIdentifier subjectKeyID = CreateSubjectKeyID(pubKey); + + fName.Add( + new DerSequence( + PkcsObjectIdentifiers.Pkcs9AtLocalKeyID, + new DerSet(subjectKeyID))); + } + + certBags.Add(new SafeBag(PkcsObjectIdentifiers.CertBag, cBag.ToAsn1Object(), new DerSet(fName))); + + doneCerts.Add(certEntry.Certificate); + } + + foreach (string certId in certs.Keys) + { + X509CertificateEntry cert = (X509CertificateEntry)certs[certId]; + + if (keys[certId] != null) + continue; + + CertBag cBag = new CertBag( + PkcsObjectIdentifiers.X509Certificate, + new DerOctetString(cert.Certificate.GetEncoded())); + + Asn1EncodableVector fName = new Asn1EncodableVector(); + + foreach (string oid in cert.BagAttributeKeys) + { + // a certificate not immediately linked to a key doesn't require + // a localKeyID and will confuse some PKCS12 implementations. + // + // If we find one, we'll prune it out. + if (oid.Equals(PkcsObjectIdentifiers.Pkcs9AtLocalKeyID.Id)) + continue; + + Asn1Encodable entry = cert[oid]; + + // NB: Ignore any existing FriendlyName + if (oid.Equals(PkcsObjectIdentifiers.Pkcs9AtFriendlyName.Id)) + continue; + + fName.Add( + new DerSequence( + new DerObjectIdentifier(oid), + new DerSet(entry))); + } + + // + // make sure we are using the local alias on store + // + // NB: We always set the FriendlyName based on 'certId' + //if (cert[PkcsObjectIdentifiers.Pkcs9AtFriendlyName] == null) + { + fName.Add( + new DerSequence( + PkcsObjectIdentifiers.Pkcs9AtFriendlyName, + new DerSet(new DerBmpString(certId)))); + } + + certBags.Add(new SafeBag(PkcsObjectIdentifiers.CertBag, cBag.ToAsn1Object(), new DerSet(fName))); + + doneCerts.Add(cert.Certificate); + } + + foreach (CertId certId in chainCerts.Keys) + { + X509CertificateEntry cert = (X509CertificateEntry)chainCerts[certId]; + + if (doneCerts.Contains(cert.Certificate)) + continue; + + CertBag cBag = new CertBag( + PkcsObjectIdentifiers.X509Certificate, + new DerOctetString(cert.Certificate.GetEncoded())); + + Asn1EncodableVector fName = new Asn1EncodableVector(); + + foreach (string oid in cert.BagAttributeKeys) + { + // a certificate not immediately linked to a key doesn't require + // a localKeyID and will confuse some PKCS12 implementations. + // + // If we find one, we'll prune it out. + if (oid.Equals(PkcsObjectIdentifiers.Pkcs9AtLocalKeyID.Id)) + continue; + + fName.Add( + new DerSequence( + new DerObjectIdentifier(oid), + new DerSet(cert[oid]))); + } + + certBags.Add(new SafeBag(PkcsObjectIdentifiers.CertBag, cBag.ToAsn1Object(), new DerSet(fName))); + } + + byte[] certBagsEncoding = new DerSequence(certBags).GetDerEncoded(); + + ContentInfo certsInfo; + if (password == null) + { + certsInfo = new ContentInfo(PkcsObjectIdentifiers.Data, new BerOctetString(certBagsEncoding)); + } + else + { + byte[] certBytes = CryptPbeData(true, cAlgId, password, false, certBagsEncoding); + EncryptedData cInfo = new EncryptedData(PkcsObjectIdentifiers.Data, cAlgId, new BerOctetString(certBytes)); + certsInfo = new ContentInfo(PkcsObjectIdentifiers.EncryptedData, cInfo.ToAsn1Object()); + } + + ContentInfo[] info = new ContentInfo[]{ keysInfo, certsInfo }; + + byte[] data = new AuthenticatedSafe(info).GetEncoded( + useDerEncoding ? Asn1Encodable.Der : Asn1Encodable.Ber); + + ContentInfo mainInfo = new ContentInfo(PkcsObjectIdentifiers.Data, new BerOctetString(data)); + + // + // create the mac + // + MacData macData = null; + if (password != null) + { + byte[] mSalt = new byte[20]; + random.NextBytes(mSalt); + + byte[] mac = CalculatePbeMac(OiwObjectIdentifiers.IdSha1, + mSalt, MinIterations, password, false, data); + + AlgorithmIdentifier algId = new AlgorithmIdentifier( + OiwObjectIdentifiers.IdSha1, DerNull.Instance); + DigestInfo dInfo = new DigestInfo(algId, mac); + + macData = new MacData(dInfo, mSalt, MinIterations); + } + + // + // output the Pfx + // + Pfx pfx = new Pfx(mainInfo, macData); + + DerOutputStream derOut; + if (useDerEncoding) + { + derOut = new DerOutputStream(stream); + } + else + { + derOut = new BerOutputStream(stream); + } + + derOut.WriteObject(pfx); + } + + internal static byte[] CalculatePbeMac( + DerObjectIdentifier oid, + byte[] salt, + int itCount, + char[] password, + bool wrongPkcs12Zero, + byte[] data) + { + Asn1Encodable asn1Params = PbeUtilities.GenerateAlgorithmParameters( + oid, salt, itCount); + ICipherParameters cipherParams = PbeUtilities.GenerateCipherParameters( + oid, password, wrongPkcs12Zero, asn1Params); + + IMac mac = (IMac) PbeUtilities.CreateEngine(oid); + mac.Init(cipherParams); + return MacUtilities.DoFinal(mac, data); + } + + private static byte[] CryptPbeData( + bool forEncryption, + AlgorithmIdentifier algId, + char[] password, + bool wrongPkcs12Zero, + byte[] data) + { + IBufferedCipher cipher = PbeUtilities.CreateEngine(algId.Algorithm) as IBufferedCipher; + + if (cipher == null) + throw new Exception("Unknown encryption algorithm: " + algId.Algorithm); + + Pkcs12PbeParams pbeParameters = Pkcs12PbeParams.GetInstance(algId.Parameters); + ICipherParameters cipherParams = PbeUtilities.GenerateCipherParameters( + algId.Algorithm, password, wrongPkcs12Zero, pbeParameters); + cipher.Init(forEncryption, cipherParams); + return cipher.DoFinal(data); + } + + private class IgnoresCaseHashtable + : IEnumerable + { + private readonly IDictionary orig = Platform.CreateHashtable(); + private readonly IDictionary keys = Platform.CreateHashtable(); + + public void Clear() + { + orig.Clear(); + keys.Clear(); + } + + public IEnumerator GetEnumerator() + { + return orig.GetEnumerator(); + } + + public ICollection Keys + { + get { return orig.Keys; } + } + + public object Remove( + string alias) + { + string upper = Platform.ToUpperInvariant(alias); + string k = (string)keys[upper]; + + if (k == null) + return null; + + keys.Remove(upper); + + object o = orig[k]; + orig.Remove(k); + return o; + } + + public object this[ + string alias] + { + get + { + string upper = Platform.ToUpperInvariant(alias); + string k = (string)keys[upper]; + + if (k == null) + return null; + + return orig[k]; + } + set + { + string upper = Platform.ToUpperInvariant(alias); + string k = (string)keys[upper]; + if (k != null) + { + orig.Remove(k); + } + keys[upper] = alias; + orig[alias] = value; + } + } + + public ICollection Values + { + get { return orig.Values; } + } + } + } +} diff --git a/bc-sharp-crypto/src/pkcs/Pkcs12Utilities.cs b/bc-sharp-crypto/src/pkcs/Pkcs12Utilities.cs new file mode 100644 index 0000000..923eca5 --- /dev/null +++ b/bc-sharp-crypto/src/pkcs/Pkcs12Utilities.cs @@ -0,0 +1,77 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.X509; + +namespace Org.BouncyCastle.Pkcs +{ + /** + * Utility class for reencoding PKCS#12 files to definite length. + */ + public class Pkcs12Utilities + { + /** + * Just re-encode the outer layer of the PKCS#12 file to definite length encoding. + * + * @param berPKCS12File - original PKCS#12 file + * @return a byte array representing the DER encoding of the PFX structure + * @throws IOException + */ + public static byte[] ConvertToDefiniteLength( + byte[] berPkcs12File) + { + Pfx pfx = new Pfx(Asn1Sequence.GetInstance(Asn1Object.FromByteArray(berPkcs12File))); + + return pfx.GetEncoded(Asn1Encodable.Der); + } + + /** + * Re-encode the PKCS#12 structure to definite length encoding at the inner layer + * as well, recomputing the MAC accordingly. + * + * @param berPKCS12File - original PKCS12 file. + * @param provider - provider to use for MAC calculation. + * @return a byte array representing the DER encoding of the PFX structure. + * @throws IOException on parsing, encoding errors. + */ + public static byte[] ConvertToDefiniteLength( + byte[] berPkcs12File, + char[] passwd) + { + Pfx pfx = new Pfx(Asn1Sequence.GetInstance(Asn1Object.FromByteArray(berPkcs12File))); + + ContentInfo info = pfx.AuthSafe; + + Asn1OctetString content = Asn1OctetString.GetInstance(info.Content); + Asn1Object obj = Asn1Object.FromByteArray(content.GetOctets()); + + info = new ContentInfo(info.ContentType, new DerOctetString(obj.GetEncoded(Asn1Encodable.Der))); + + MacData mData = pfx.MacData; + + try + { + int itCount = mData.IterationCount.IntValue; + byte[] data = Asn1OctetString.GetInstance(info.Content).GetOctets(); + byte[] res = Pkcs12Store.CalculatePbeMac( + mData.Mac.AlgorithmID.Algorithm, mData.GetSalt(), itCount, passwd, false, data); + + AlgorithmIdentifier algId = new AlgorithmIdentifier( + mData.Mac.AlgorithmID.Algorithm, DerNull.Instance); + DigestInfo dInfo = new DigestInfo(algId, res); + + mData = new MacData(dInfo, mData.GetSalt(), itCount); + } + catch (Exception e) + { + throw new IOException("error constructing MAC: " + e.ToString()); + } + + pfx = new Pfx(info, mData); + + return pfx.GetEncoded(Asn1Encodable.Der); + } + } +} \ No newline at end of file diff --git a/bc-sharp-crypto/src/pkcs/PrivateKeyInfoFactory.cs b/bc-sharp-crypto/src/pkcs/PrivateKeyInfoFactory.cs new file mode 100644 index 0000000..a349a11 --- /dev/null +++ b/bc-sharp-crypto/src/pkcs/PrivateKeyInfoFactory.cs @@ -0,0 +1,205 @@ +using System; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.CryptoPro; +using Org.BouncyCastle.Asn1.Oiw; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.Sec; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Pkcs +{ + public sealed class PrivateKeyInfoFactory + { + private PrivateKeyInfoFactory() + { + } + + public static PrivateKeyInfo CreatePrivateKeyInfo( + AsymmetricKeyParameter key) + { + if (key == null) + throw new ArgumentNullException("key"); + if (!key.IsPrivate) + throw new ArgumentException("Public key passed - private key expected", "key"); + + if (key is ElGamalPrivateKeyParameters) + { + ElGamalPrivateKeyParameters _key = (ElGamalPrivateKeyParameters)key; + return new PrivateKeyInfo( + new AlgorithmIdentifier( + OiwObjectIdentifiers.ElGamalAlgorithm, + new ElGamalParameter( + _key.Parameters.P, + _key.Parameters.G).ToAsn1Object()), + new DerInteger(_key.X)); + } + + if (key is DsaPrivateKeyParameters) + { + DsaPrivateKeyParameters _key = (DsaPrivateKeyParameters)key; + return new PrivateKeyInfo( + new AlgorithmIdentifier( + X9ObjectIdentifiers.IdDsa, + new DsaParameter( + _key.Parameters.P, + _key.Parameters.Q, + _key.Parameters.G).ToAsn1Object()), + new DerInteger(_key.X)); + } + + if (key is DHPrivateKeyParameters) + { + DHPrivateKeyParameters _key = (DHPrivateKeyParameters)key; + + DHParameter p = new DHParameter( + _key.Parameters.P, _key.Parameters.G, _key.Parameters.L); + + return new PrivateKeyInfo( + new AlgorithmIdentifier(_key.AlgorithmOid, p.ToAsn1Object()), + new DerInteger(_key.X)); + } + + if (key is RsaKeyParameters) + { + AlgorithmIdentifier algID = new AlgorithmIdentifier( + PkcsObjectIdentifiers.RsaEncryption, DerNull.Instance); + + RsaPrivateKeyStructure keyStruct; + if (key is RsaPrivateCrtKeyParameters) + { + RsaPrivateCrtKeyParameters _key = (RsaPrivateCrtKeyParameters)key; + + keyStruct = new RsaPrivateKeyStructure( + _key.Modulus, + _key.PublicExponent, + _key.Exponent, + _key.P, + _key.Q, + _key.DP, + _key.DQ, + _key.QInv); + } + else + { + RsaKeyParameters _key = (RsaKeyParameters) key; + + keyStruct = new RsaPrivateKeyStructure( + _key.Modulus, + BigInteger.Zero, + _key.Exponent, + BigInteger.Zero, + BigInteger.Zero, + BigInteger.Zero, + BigInteger.Zero, + BigInteger.Zero); + } + + return new PrivateKeyInfo(algID, keyStruct.ToAsn1Object()); + } + + if (key is ECPrivateKeyParameters) + { + ECPrivateKeyParameters priv = (ECPrivateKeyParameters)key; + ECDomainParameters dp = priv.Parameters; + int orderBitLength = dp.N.BitLength; + + AlgorithmIdentifier algID; + ECPrivateKeyStructure ec; + + if (priv.AlgorithmName == "ECGOST3410") + { + if (priv.PublicKeyParamSet == null) + throw Platform.CreateNotImplementedException("Not a CryptoPro parameter set"); + + Gost3410PublicKeyAlgParameters gostParams = new Gost3410PublicKeyAlgParameters( + priv.PublicKeyParamSet, CryptoProObjectIdentifiers.GostR3411x94CryptoProParamSet); + + algID = new AlgorithmIdentifier(CryptoProObjectIdentifiers.GostR3410x2001, gostParams); + + // TODO Do we need to pass any parameters here? + ec = new ECPrivateKeyStructure(orderBitLength, priv.D); + } + else + { + X962Parameters x962; + if (priv.PublicKeyParamSet == null) + { + X9ECParameters ecP = new X9ECParameters(dp.Curve, dp.G, dp.N, dp.H, dp.GetSeed()); + x962 = new X962Parameters(ecP); + } + else + { + x962 = new X962Parameters(priv.PublicKeyParamSet); + } + + // TODO Possible to pass the publicKey bitstring here? + ec = new ECPrivateKeyStructure(orderBitLength, priv.D, x962); + + algID = new AlgorithmIdentifier(X9ObjectIdentifiers.IdECPublicKey, x962); + } + + return new PrivateKeyInfo(algID, ec); + } + + if (key is Gost3410PrivateKeyParameters) + { + Gost3410PrivateKeyParameters _key = (Gost3410PrivateKeyParameters)key; + + if (_key.PublicKeyParamSet == null) + throw Platform.CreateNotImplementedException("Not a CryptoPro parameter set"); + + byte[] keyEnc = _key.X.ToByteArrayUnsigned(); + byte[] keyBytes = new byte[keyEnc.Length]; + + for (int i = 0; i != keyBytes.Length; i++) + { + keyBytes[i] = keyEnc[keyEnc.Length - 1 - i]; // must be little endian + } + + Gost3410PublicKeyAlgParameters algParams = new Gost3410PublicKeyAlgParameters( + _key.PublicKeyParamSet, CryptoProObjectIdentifiers.GostR3411x94CryptoProParamSet, null); + + AlgorithmIdentifier algID = new AlgorithmIdentifier( + CryptoProObjectIdentifiers.GostR3410x94, + algParams.ToAsn1Object()); + + return new PrivateKeyInfo(algID, new DerOctetString(keyBytes)); + } + + throw new ArgumentException("Class provided is not convertible: " + Platform.GetTypeName(key)); + } + + public static PrivateKeyInfo CreatePrivateKeyInfo( + char[] passPhrase, + EncryptedPrivateKeyInfo encInfo) + { + return CreatePrivateKeyInfo(passPhrase, false, encInfo); + } + + public static PrivateKeyInfo CreatePrivateKeyInfo( + char[] passPhrase, + bool wrongPkcs12Zero, + EncryptedPrivateKeyInfo encInfo) + { + AlgorithmIdentifier algID = encInfo.EncryptionAlgorithm; + + IBufferedCipher cipher = PbeUtilities.CreateEngine(algID) as IBufferedCipher; + if (cipher == null) + throw new Exception("Unknown encryption algorithm: " + algID.Algorithm); + + ICipherParameters cipherParameters = PbeUtilities.GenerateCipherParameters( + algID, passPhrase, wrongPkcs12Zero); + cipher.Init(false, cipherParameters); + byte[] keyBytes = cipher.DoFinal(encInfo.GetEncryptedData()); + + return PrivateKeyInfo.GetInstance(keyBytes); + } + } +} diff --git a/bc-sharp-crypto/src/pkcs/X509CertificateEntry.cs b/bc-sharp-crypto/src/pkcs/X509CertificateEntry.cs new file mode 100644 index 0000000..2f81dd8 --- /dev/null +++ b/bc-sharp-crypto/src/pkcs/X509CertificateEntry.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.X509; + +namespace Org.BouncyCastle.Pkcs +{ + public class X509CertificateEntry + : Pkcs12Entry + { + private readonly X509Certificate cert; + + public X509CertificateEntry( + X509Certificate cert) + : base(Platform.CreateHashtable()) + { + this.cert = cert; + } + +#if !(SILVERLIGHT || PORTABLE) + [Obsolete] + public X509CertificateEntry( + X509Certificate cert, + Hashtable attributes) + : base(attributes) + { + this.cert = cert; + } +#endif + + public X509CertificateEntry( + X509Certificate cert, + IDictionary attributes) + : base(attributes) + { + this.cert = cert; + } + + public X509Certificate Certificate + { + get { return this.cert; } + } + + public override bool Equals(object obj) + { + X509CertificateEntry other = obj as X509CertificateEntry; + + if (other == null) + return false; + + return cert.Equals(other.cert); + } + + public override int GetHashCode() + { + return ~cert.GetHashCode(); + } + } +} diff --git a/bc-sharp-crypto/src/pkix/CertStatus.cs b/bc-sharp-crypto/src/pkix/CertStatus.cs new file mode 100644 index 0000000..4f40b7b --- /dev/null +++ b/bc-sharp-crypto/src/pkix/CertStatus.cs @@ -0,0 +1,35 @@ +using System; + +using Org.BouncyCastle.Utilities.Date; + +namespace Org.BouncyCastle.Pkix +{ + public class CertStatus + { + public const int Unrevoked = 11; + + public const int Undetermined = 12; + + private int status = Unrevoked; + + DateTimeObject revocationDate = null; + + /// + /// Returns the revocationDate. + /// + public DateTimeObject RevocationDate + { + get { return revocationDate; } + set { this.revocationDate = value; } + } + + /// + /// Returns the certStatus. + /// + public int Status + { + get { return status; } + set { this.status = value; } + } + } +} diff --git a/bc-sharp-crypto/src/pkix/PkixAttrCertChecker.cs b/bc-sharp-crypto/src/pkix/PkixAttrCertChecker.cs new file mode 100644 index 0000000..a6eab84 --- /dev/null +++ b/bc-sharp-crypto/src/pkix/PkixAttrCertChecker.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Utilities.Collections; +using Org.BouncyCastle.X509; + +namespace Org.BouncyCastle.Pkix +{ + public abstract class PkixAttrCertChecker + { + /** + * Returns an immutable Set of X.509 attribute certificate + * extensions that this PkixAttrCertChecker supports or + * null if no extensions are supported. + *

    + * Each element of the set is a String representing the + * Object Identifier (OID) of the X.509 extension that is supported. + *

    + *

    + * All X.509 attribute certificate extensions that a + * PkixAttrCertChecker might possibly be able to process + * should be included in the set. + *

    + * + * @return an immutable Set of X.509 extension OIDs (in + * String format) supported by this + * PkixAttrCertChecker, or null if no + * extensions are supported + */ + public abstract ISet GetSupportedExtensions(); + + /** + * Performs checks on the specified attribute certificate. Every handled + * extension is rmeoved from the unresolvedCritExts + * collection. + * + * @param attrCert The attribute certificate to be checked. + * @param certPath The certificate path which belongs to the attribute + * certificate issuer public key certificate. + * @param holderCertPath The certificate path which belongs to the holder + * certificate. + * @param unresolvedCritExts a Collection of OID strings + * representing the current set of unresolved critical extensions + * @throws CertPathValidatorException if the specified attribute certificate + * does not pass the check. + */ + public abstract void Check(IX509AttributeCertificate attrCert, PkixCertPath certPath, + PkixCertPath holderCertPath, ICollection unresolvedCritExts); + + /** + * Returns a clone of this object. + * + * @return a copy of this PkixAttrCertChecker + */ + public abstract PkixAttrCertChecker Clone(); + } +} diff --git a/bc-sharp-crypto/src/pkix/PkixAttrCertPathBuilder.cs b/bc-sharp-crypto/src/pkix/PkixAttrCertPathBuilder.cs new file mode 100644 index 0000000..646cc5d --- /dev/null +++ b/bc-sharp-crypto/src/pkix/PkixAttrCertPathBuilder.cs @@ -0,0 +1,215 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Security.Certificates; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; +using Org.BouncyCastle.X509; +using Org.BouncyCastle.X509.Store; + +namespace Org.BouncyCastle.Pkix +{ + public class PkixAttrCertPathBuilder + { + /** + * Build and validate a CertPath using the given parameter. + * + * @param params PKIXBuilderParameters object containing all information to + * build the CertPath + */ + public virtual PkixCertPathBuilderResult Build( + PkixBuilderParameters pkixParams) + { + // search target certificates + + IX509Selector certSelect = pkixParams.GetTargetConstraints(); + if (!(certSelect is X509AttrCertStoreSelector)) + { + throw new PkixCertPathBuilderException( + "TargetConstraints must be an instance of " + + typeof(X509AttrCertStoreSelector).FullName + + " for " + + typeof(PkixAttrCertPathBuilder).FullName + " class."); + } + + ICollection targets; + try + { + targets = PkixCertPathValidatorUtilities.FindCertificates( + (X509AttrCertStoreSelector)certSelect, pkixParams.GetStores()); + } + catch (Exception e) + { + throw new PkixCertPathBuilderException("Error finding target attribute certificate.", e); + } + + if (targets.Count == 0) + { + throw new PkixCertPathBuilderException( + "No attribute certificate found matching targetContraints."); + } + + PkixCertPathBuilderResult result = null; + + // check all potential target certificates + foreach (IX509AttributeCertificate cert in targets) + { + X509CertStoreSelector selector = new X509CertStoreSelector(); + X509Name[] principals = cert.Issuer.GetPrincipals(); + ISet issuers = new HashSet(); + for (int i = 0; i < principals.Length; i++) + { + try + { + selector.Subject = principals[i]; + + issuers.AddAll(PkixCertPathValidatorUtilities.FindCertificates(selector, pkixParams.GetStores())); + } + catch (Exception e) + { + throw new PkixCertPathBuilderException( + "Public key certificate for attribute certificate cannot be searched.", + e); + } + } + + if (issuers.IsEmpty) + throw new PkixCertPathBuilderException("Public key certificate for attribute certificate cannot be found."); + + IList certPathList = Platform.CreateArrayList(); + + foreach (X509Certificate issuer in issuers) + { + result = Build(cert, issuer, pkixParams, certPathList); + + if (result != null) + break; + } + + if (result != null) + break; + } + + if (result == null && certPathException != null) + { + throw new PkixCertPathBuilderException( + "Possible certificate chain could not be validated.", + certPathException); + } + + if (result == null && certPathException == null) + { + throw new PkixCertPathBuilderException( + "Unable to find certificate chain."); + } + + return result; + } + + private Exception certPathException; + + private PkixCertPathBuilderResult Build( + IX509AttributeCertificate attrCert, + X509Certificate tbvCert, + PkixBuilderParameters pkixParams, + IList tbvPath) + { + // If tbvCert is readily present in tbvPath, it indicates having run + // into a cycle in the + // PKI graph. + if (tbvPath.Contains(tbvCert)) + return null; + + // step out, the certificate is not allowed to appear in a certification + // chain + if (pkixParams.GetExcludedCerts().Contains(tbvCert)) + return null; + + // test if certificate path exceeds maximum length + if (pkixParams.MaxPathLength != -1) + { + if (tbvPath.Count - 1 > pkixParams.MaxPathLength) + return null; + } + + tbvPath.Add(tbvCert); + + PkixCertPathBuilderResult builderResult = null; + +// X509CertificateParser certParser = new X509CertificateParser(); + PkixAttrCertPathValidator validator = new PkixAttrCertPathValidator(); + + try + { + // check whether the issuer of is a TrustAnchor + if (PkixCertPathValidatorUtilities.FindTrustAnchor(tbvCert, pkixParams.GetTrustAnchors()) != null) + { + PkixCertPath certPath = new PkixCertPath(tbvPath); + PkixCertPathValidatorResult result; + + try + { + result = validator.Validate(certPath, pkixParams); + } + catch (Exception e) + { + throw new Exception("Certification path could not be validated.", e); + } + + return new PkixCertPathBuilderResult(certPath, result.TrustAnchor, + result.PolicyTree, result.SubjectPublicKey); + } + else + { + // add additional X.509 stores from locations in certificate + try + { + PkixCertPathValidatorUtilities.AddAdditionalStoresFromAltNames(tbvCert, pkixParams); + } + catch (CertificateParsingException e) + { + throw new Exception("No additional X.509 stores can be added from certificate locations.", e); + } + + // try to get the issuer certificate from one of the stores + ISet issuers = new HashSet(); + try + { + issuers.AddAll(PkixCertPathValidatorUtilities.FindIssuerCerts(tbvCert, pkixParams)); + } + catch (Exception e) + { + throw new Exception("Cannot find issuer certificate for certificate in certification path.", e); + } + + if (issuers.IsEmpty) + throw new Exception("No issuer certificate for certificate in certification path found."); + + foreach (X509Certificate issuer in issuers) + { + // if untrusted self signed certificate continue + if (PkixCertPathValidatorUtilities.IsSelfIssued(issuer)) + continue; + + builderResult = Build(attrCert, issuer, pkixParams, tbvPath); + + if (builderResult != null) + break; + } + } + } + catch (Exception e) + { + certPathException = new Exception("No valid certification path could be build.", e); + } + + if (builderResult == null) + { + tbvPath.Remove(tbvCert); + } + + return builderResult; + } + } +} diff --git a/bc-sharp-crypto/src/pkix/PkixAttrCertPathValidator.cs b/bc-sharp-crypto/src/pkix/PkixAttrCertPathValidator.cs new file mode 100644 index 0000000..5f53bcd --- /dev/null +++ b/bc-sharp-crypto/src/pkix/PkixAttrCertPathValidator.cs @@ -0,0 +1,76 @@ +using System; + +using Org.BouncyCastle.X509; +using Org.BouncyCastle.X509.Store; + +namespace Org.BouncyCastle.Pkix +{ + /** + * CertPathValidatorSpi implementation for X.509 Attribute Certificates la RFC 3281. + * + * @see org.bouncycastle.x509.ExtendedPkixParameters + */ + public class PkixAttrCertPathValidator + // extends CertPathValidatorSpi + { + /** + * Validates an attribute certificate with the given certificate path. + * + *

    + * params must be an instance of + * ExtendedPkixParameters. + *

    + * The target constraints in the params must be an + * X509AttrCertStoreSelector with at least the attribute + * certificate criterion set. Obey that also target informations may be + * necessary to correctly validate this attribute certificate. + *

    + * The attribute certificate issuer must be added to the trusted attribute + * issuers with {@link ExtendedPkixParameters#setTrustedACIssuers(Set)}. + *

    + * @param certPath The certificate path which belongs to the attribute + * certificate issuer public key certificate. + * @param params The PKIX parameters. + * @return A PKIXCertPathValidatorResult of the result of + * validating the certPath. + * @throws InvalidAlgorithmParameterException if params is + * inappropriate for this validator. + * @throws CertPathValidatorException if the verification fails. + */ + public virtual PkixCertPathValidatorResult Validate( + PkixCertPath certPath, + PkixParameters pkixParams) + { + IX509Selector certSelect = pkixParams.GetTargetConstraints(); + if (!(certSelect is X509AttrCertStoreSelector)) + { + throw new ArgumentException( + "TargetConstraints must be an instance of " + typeof(X509AttrCertStoreSelector).FullName, + "pkixParams"); + } + IX509AttributeCertificate attrCert = ((X509AttrCertStoreSelector) certSelect).AttributeCert; + + PkixCertPath holderCertPath = Rfc3281CertPathUtilities.ProcessAttrCert1(attrCert, pkixParams); + PkixCertPathValidatorResult result = Rfc3281CertPathUtilities.ProcessAttrCert2(certPath, pkixParams); + X509Certificate issuerCert = (X509Certificate)certPath.Certificates[0]; + Rfc3281CertPathUtilities.ProcessAttrCert3(issuerCert, pkixParams); + Rfc3281CertPathUtilities.ProcessAttrCert4(issuerCert, pkixParams); + Rfc3281CertPathUtilities.ProcessAttrCert5(attrCert, pkixParams); + // 6 already done in X509AttrCertStoreSelector + Rfc3281CertPathUtilities.ProcessAttrCert7(attrCert, certPath, holderCertPath, pkixParams); + Rfc3281CertPathUtilities.AdditionalChecks(attrCert, pkixParams); + DateTime date; + try + { + date = PkixCertPathValidatorUtilities.GetValidCertDateFromValidityModel(pkixParams, null, -1); + } + catch (Exception e) + { + throw new PkixCertPathValidatorException( + "Could not get validity date from attribute certificate.", e); + } + Rfc3281CertPathUtilities.CheckCrls(attrCert, pkixParams, issuerCert, date, certPath.Certificates); + return result; + } + } +} diff --git a/bc-sharp-crypto/src/pkix/PkixBuilderParameters.cs b/bc-sharp-crypto/src/pkix/PkixBuilderParameters.cs new file mode 100644 index 0000000..32fc043 --- /dev/null +++ b/bc-sharp-crypto/src/pkix/PkixBuilderParameters.cs @@ -0,0 +1,140 @@ +using System; +using System.Text; + +using Org.BouncyCastle.Security; +using Org.BouncyCastle.X509.Store; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; + +namespace Org.BouncyCastle.Pkix +{ + /// + /// Summary description for PkixBuilderParameters. + /// + public class PkixBuilderParameters + : PkixParameters + { + private int maxPathLength = 5; + + private ISet excludedCerts = new HashSet(); + + /** + * Returns an instance of PkixBuilderParameters. + *

    + * This method can be used to get a copy from other + * PKIXBuilderParameters, PKIXParameters, + * and ExtendedPKIXParameters instances. + *

    + * + * @param pkixParams The PKIX parameters to create a copy of. + * @return An PkixBuilderParameters instance. + */ + public static PkixBuilderParameters GetInstance( + PkixParameters pkixParams) + { + PkixBuilderParameters parameters = new PkixBuilderParameters( + pkixParams.GetTrustAnchors(), + new X509CertStoreSelector(pkixParams.GetTargetCertConstraints())); + parameters.SetParams(pkixParams); + return parameters; + } + + public PkixBuilderParameters( + ISet trustAnchors, + IX509Selector targetConstraints) + : base(trustAnchors) + { + SetTargetCertConstraints(targetConstraints); + } + + public virtual int MaxPathLength + { + get { return maxPathLength; } + set + { + if (value < -1) + { + throw new InvalidParameterException( + "The maximum path length parameter can not be less than -1."); + } + this.maxPathLength = value; + } + } + + /// + /// Excluded certificates are not used for building a certification path. + /// + /// the excluded certificates. + public virtual ISet GetExcludedCerts() + { + return new HashSet(excludedCerts); + } + + /// + /// Sets the excluded certificates which are not used for building a + /// certification path. If the ISet is null an + /// empty set is assumed. + /// + /// + /// The given set is cloned to protect it against subsequent modifications. + /// + /// The excluded certificates to set. + public virtual void SetExcludedCerts( + ISet excludedCerts) + { + if (excludedCerts == null) + { + excludedCerts = new HashSet(); + } + else + { + this.excludedCerts = new HashSet(excludedCerts); + } + } + + /** + * Can alse handle ExtendedPKIXBuilderParameters and + * PKIXBuilderParameters. + * + * @param params Parameters to set. + * @see org.bouncycastle.x509.ExtendedPKIXParameters#setParams(java.security.cert.PKIXParameters) + */ + protected override void SetParams( + PkixParameters parameters) + { + base.SetParams(parameters); + if (parameters is PkixBuilderParameters) + { + PkixBuilderParameters _params = (PkixBuilderParameters) parameters; + maxPathLength = _params.maxPathLength; + excludedCerts = new HashSet(_params.excludedCerts); + } + } + + /** + * Makes a copy of this PKIXParameters object. Changes to the + * copy will not affect the original and vice versa. + * + * @return a copy of this PKIXParameters object + */ + public override object Clone() + { + PkixBuilderParameters parameters = new PkixBuilderParameters( + GetTrustAnchors(), GetTargetCertConstraints()); + parameters.SetParams(this); + return parameters; + } + + public override string ToString() + { + string nl = Platform.NewLine; + StringBuilder s = new StringBuilder(); + s.Append("PkixBuilderParameters [" + nl); + s.Append(base.ToString()); + s.Append(" Maximum Path Length: "); + s.Append(MaxPathLength); + s.Append(nl + "]" + nl); + return s.ToString(); + } + } +} diff --git a/bc-sharp-crypto/src/pkix/PkixCertPath.cs b/bc-sharp-crypto/src/pkix/PkixCertPath.cs new file mode 100644 index 0000000..3c428f6 --- /dev/null +++ b/bc-sharp-crypto/src/pkix/PkixCertPath.cs @@ -0,0 +1,460 @@ +using System; +using System.Collections; +using System.IO; +using System.Text; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Cms; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Cms; +using Org.BouncyCastle.X509; +using Org.BouncyCastle.OpenSsl; +using Org.BouncyCastle.Security.Certificates; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; + +namespace Org.BouncyCastle.Pkix +{ + /** + * An immutable sequence of certificates (a certification path).
    + *
    + * This is an abstract class that defines the methods common to all CertPaths. + * Subclasses can handle different kinds of certificates (X.509, PGP, etc.).
    + *
    + * All CertPath objects have a type, a list of Certificates, and one or more + * supported encodings. Because the CertPath class is immutable, a CertPath + * cannot change in any externally visible way after being constructed. This + * stipulation applies to all public fields and methods of this class and any + * added or overridden by subclasses.
    + *
    + * The type is a string that identifies the type of Certificates in the + * certification path. For each certificate cert in a certification path + * certPath, cert.getType().equals(certPath.getType()) must be true.
    + *
    + * The list of Certificates is an ordered List of zero or more Certificates. + * This List and all of the Certificates contained in it must be immutable.
    + *
    + * Each CertPath object must support one or more encodings so that the object + * can be translated into a byte array for storage or transmission to other + * parties. Preferably, these encodings should be well-documented standards + * (such as PKCS#7). One of the encodings supported by a CertPath is considered + * the default encoding. This encoding is used if no encoding is explicitly + * requested (for the {@link #getEncoded()} method, for instance).
    + *
    + * All CertPath objects are also Serializable. CertPath objects are resolved + * into an alternate {@link CertPathRep} object during serialization. This + * allows a CertPath object to be serialized into an equivalent representation + * regardless of its underlying implementation.
    + *
    + * CertPath objects can be created with a CertificateFactory or they can be + * returned by other classes, such as a CertPathBuilder.
    + *
    + * By convention, X.509 CertPaths (consisting of X509Certificates), are ordered + * starting with the target certificate and ending with a certificate issued by + * the trust anchor. That is, the issuer of one certificate is the subject of + * the following one. The certificate representing the + * {@link TrustAnchor TrustAnchor} should not be included in the certification + * path. Unvalidated X.509 CertPaths may not follow these conventions. PKIX + * CertPathValidators will detect any departure from these conventions that + * cause the certification path to be invalid and throw a + * CertPathValidatorException.
    + *
    + * Concurrent Access
    + *
    + * All CertPath objects must be thread-safe. That is, multiple threads may + * concurrently invoke the methods defined in this class on a single CertPath + * object (or more than one) with no ill effects. This is also true for the List + * returned by CertPath.getCertificates.
    + *
    + * Requiring CertPath objects to be immutable and thread-safe allows them to be + * passed around to various pieces of code without worrying about coordinating + * access. Providing this thread-safety is generally not difficult, since the + * CertPath and List objects in question are immutable. + * + * @see CertificateFactory + * @see CertPathBuilder + */ + /// + /// CertPath implementation for X.509 certificates. + /// + public class PkixCertPath +// : CertPath + { + internal static readonly IList certPathEncodings; + + static PkixCertPath() + { + IList encodings = Platform.CreateArrayList(); + encodings.Add("PkiPath"); + encodings.Add("PEM"); + encodings.Add("PKCS7"); + certPathEncodings = CollectionUtilities.ReadOnly(encodings); + } + + private readonly IList certificates; + + /** + * @param certs + */ + private static IList SortCerts( + IList certs) + { + if (certs.Count < 2) + return certs; + + X509Name issuer = ((X509Certificate)certs[0]).IssuerDN; + bool okay = true; + + for (int i = 1; i != certs.Count; i++) + { + X509Certificate cert = (X509Certificate)certs[i]; + + if (issuer.Equivalent(cert.SubjectDN, true)) + { + issuer = ((X509Certificate)certs[i]).IssuerDN; + } + else + { + okay = false; + break; + } + } + + if (okay) + return certs; + + // find end-entity cert + IList retList = Platform.CreateArrayList(certs.Count); + IList orig = Platform.CreateArrayList(certs); + + for (int i = 0; i < certs.Count; i++) + { + X509Certificate cert = (X509Certificate)certs[i]; + bool found = false; + + X509Name subject = cert.SubjectDN; + foreach (X509Certificate c in certs) + { + if (c.IssuerDN.Equivalent(subject, true)) + { + found = true; + break; + } + } + + if (!found) + { + retList.Add(cert); + certs.RemoveAt(i); + } + } + + // can only have one end entity cert - something's wrong, give up. + if (retList.Count > 1) + return orig; + + for (int i = 0; i != retList.Count; i++) + { + issuer = ((X509Certificate)retList[i]).IssuerDN; + + for (int j = 0; j < certs.Count; j++) + { + X509Certificate c = (X509Certificate)certs[j]; + if (issuer.Equivalent(c.SubjectDN, true)) + { + retList.Add(c); + certs.RemoveAt(j); + break; + } + } + } + + // make sure all certificates are accounted for. + if (certs.Count > 0) + return orig; + + return retList; + } + + /** + * Creates a CertPath of the specified type. + * This constructor is protected because most users should use + * a CertificateFactory to create CertPaths. + * @param type the standard name of the type of Certificatesin this path + **/ + public PkixCertPath( + ICollection certificates) +// : base("X.509") + { + this.certificates = SortCerts(Platform.CreateArrayList(certificates)); + } + + public PkixCertPath( + Stream inStream) + : this(inStream, "PkiPath") + { + } + + /** + * Creates a CertPath of the specified type. + * This constructor is protected because most users should use + * a CertificateFactory to create CertPaths. + * + * @param type the standard name of the type of Certificatesin this path + **/ + public PkixCertPath( + Stream inStream, + string encoding) +// : base("X.509") + { + string upper = Platform.ToUpperInvariant(encoding); + + IList certs; + try + { + if (upper.Equals(Platform.ToUpperInvariant("PkiPath"))) + { + Asn1InputStream derInStream = new Asn1InputStream(inStream); + Asn1Object derObject = derInStream.ReadObject(); + if (!(derObject is Asn1Sequence)) + { + throw new CertificateException( + "input stream does not contain a ASN1 SEQUENCE while reading PkiPath encoded data to load CertPath"); + } + + certs = Platform.CreateArrayList(); + + foreach (Asn1Encodable ae in (Asn1Sequence)derObject) + { + byte[] derBytes = ae.GetEncoded(Asn1Encodable.Der); + Stream certInStream = new MemoryStream(derBytes, false); + + // TODO Is inserting at the front important (list will be sorted later anyway)? + certs.Insert(0, new X509CertificateParser().ReadCertificate(certInStream)); + } + } + else if (upper.Equals("PKCS7") || upper.Equals("PEM")) + { + certs = Platform.CreateArrayList(new X509CertificateParser().ReadCertificates(inStream)); + } + else + { + throw new CertificateException("unsupported encoding: " + encoding); + } + } + catch (IOException ex) + { + throw new CertificateException( + "IOException throw while decoding CertPath:\n" + + ex.ToString()); + } + + this.certificates = SortCerts(certs); + } + + /** + * Returns an iteration of the encodings supported by this + * certification path, with the default encoding + * first. Attempts to modify the returned Iterator via its + * remove method result in an UnsupportedOperationException. + * + * @return an Iterator over the names of the supported encodings (as Strings) + **/ + public virtual IEnumerable Encodings + { + get { return new EnumerableProxy(certPathEncodings); } + } + + /** + * Compares this certification path for equality with the specified object. + * Two CertPaths are equal if and only if their types are equal and their + * certificate Lists (and by implication the Certificates in those Lists) + * are equal. A CertPath is never equal to an object that is not a CertPath.
    + *
    + * This algorithm is implemented by this method. If it is overridden, the + * behavior specified here must be maintained. + * + * @param other + * the object to test for equality with this certification path + * + * @return true if the specified object is equal to this certification path, + * false otherwise + * + * @see Object#hashCode() Object.hashCode() + */ + public override bool Equals( + object obj) + { + if (this == obj) + return true; + + PkixCertPath other = obj as PkixCertPath; + if (other == null) + return false; + +// if (!this.Type.Equals(other.Type)) +// return false; + + //return this.Certificates.Equals(other.Certificates); + + // TODO Extract this to a utility class + IList thisCerts = this.Certificates; + IList otherCerts = other.Certificates; + + if (thisCerts.Count != otherCerts.Count) + return false; + + IEnumerator e1 = thisCerts.GetEnumerator(); + IEnumerator e2 = thisCerts.GetEnumerator(); + + while (e1.MoveNext()) + { + e2.MoveNext(); + + if (!Platform.Equals(e1.Current, e2.Current)) + return false; + } + + return true; + } + + public override int GetHashCode() + { + // FIXME? + return this.Certificates.GetHashCode(); + } + + /** + * Returns the encoded form of this certification path, using + * the default encoding. + * + * @return the encoded bytes + * @exception CertificateEncodingException if an encoding error occurs + **/ + public virtual byte[] GetEncoded() + { + foreach (object enc in Encodings) + { + if (enc is string) + { + return GetEncoded((string)enc); + } + } + return null; + } + + /** + * Returns the encoded form of this certification path, using + * the specified encoding. + * + * @param encoding the name of the encoding to use + * @return the encoded bytes + * @exception CertificateEncodingException if an encoding error + * occurs or the encoding requested is not supported + * + */ + public virtual byte[] GetEncoded( + string encoding) + { + if (Platform.EqualsIgnoreCase(encoding, "PkiPath")) + { + Asn1EncodableVector v = new Asn1EncodableVector(); + + for (int i = certificates.Count - 1; i >= 0; i--) + { + v.Add(ToAsn1Object((X509Certificate) certificates[i])); + } + + return ToDerEncoded(new DerSequence(v)); + } + else if (Platform.EqualsIgnoreCase(encoding, "PKCS7")) + { + Asn1.Pkcs.ContentInfo encInfo = new Asn1.Pkcs.ContentInfo( + PkcsObjectIdentifiers.Data, null); + + Asn1EncodableVector v = new Asn1EncodableVector(); + for (int i = 0; i != certificates.Count; i++) + { + v.Add(ToAsn1Object((X509Certificate)certificates[i])); + } + + Asn1.Pkcs.SignedData sd = new Asn1.Pkcs.SignedData( + new DerInteger(1), + new DerSet(), + encInfo, + new DerSet(v), + null, + new DerSet()); + + return ToDerEncoded(new Asn1.Pkcs.ContentInfo(PkcsObjectIdentifiers.SignedData, sd)); + } + else if (Platform.EqualsIgnoreCase(encoding, "PEM")) + { + MemoryStream bOut = new MemoryStream(); + PemWriter pWrt = new PemWriter(new StreamWriter(bOut)); + + try + { + for (int i = 0; i != certificates.Count; i++) + { + pWrt.WriteObject(certificates[i]); + } + + Platform.Dispose(pWrt.Writer); + } + catch (Exception) + { + throw new CertificateEncodingException("can't encode certificate for PEM encoded path"); + } + + return bOut.ToArray(); + } + else + { + throw new CertificateEncodingException("unsupported encoding: " + encoding); + } + } + + /// + /// Returns the list of certificates in this certification + /// path. + /// + public virtual IList Certificates + { + get { return CollectionUtilities.ReadOnly(certificates); } + } + + /** + * Return a DERObject containing the encoded certificate. + * + * @param cert the X509Certificate object to be encoded + * + * @return the DERObject + **/ + private Asn1Object ToAsn1Object( + X509Certificate cert) + { + try + { + return Asn1Object.FromByteArray(cert.GetEncoded()); + } + catch (Exception e) + { + throw new CertificateEncodingException("Exception while encoding certificate", e); + } + } + + private byte[] ToDerEncoded(Asn1Encodable obj) + { + try + { + return obj.GetEncoded(Asn1Encodable.Der); + } + catch (IOException e) + { + throw new CertificateEncodingException("Exception thrown", e); + } + } + } +} diff --git a/bc-sharp-crypto/src/pkix/PkixCertPathBuilder.cs b/bc-sharp-crypto/src/pkix/PkixCertPathBuilder.cs new file mode 100644 index 0000000..fa38a5e --- /dev/null +++ b/bc-sharp-crypto/src/pkix/PkixCertPathBuilder.cs @@ -0,0 +1,205 @@ +using System; +using System.Collections; +using System.Text; + +using Org.BouncyCastle.Asn1.IsisMtt; +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Asn1.X500; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security.Certificates; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; +using Org.BouncyCastle.X509; +using Org.BouncyCastle.X509.Store; + +namespace Org.BouncyCastle.Pkix +{ + /** + * Implements the PKIX CertPathBuilding algorithm for BouncyCastle. + * + * @see CertPathBuilderSpi + */ + public class PkixCertPathBuilder + // : CertPathBuilderSpi + { + /** + * Build and validate a CertPath using the given parameter. + * + * @param params PKIXBuilderParameters object containing all information to + * build the CertPath + */ + public virtual PkixCertPathBuilderResult Build( + PkixBuilderParameters pkixParams) + { + // search target certificates + + IX509Selector certSelect = pkixParams.GetTargetCertConstraints(); + if (!(certSelect is X509CertStoreSelector)) + { + throw new PkixCertPathBuilderException( + "TargetConstraints must be an instance of " + + typeof(X509CertStoreSelector).FullName + " for " + + Platform.GetTypeName(this) + " class."); + } + + ISet targets = new HashSet(); + try + { + targets.AddAll(PkixCertPathValidatorUtilities.FindCertificates((X509CertStoreSelector)certSelect, pkixParams.GetStores())); + // TODO Should this include an entry for pkixParams.GetAdditionalStores() too? + } + catch (Exception e) + { + throw new PkixCertPathBuilderException( + "Error finding target certificate.", e); + } + + if (targets.IsEmpty) + throw new PkixCertPathBuilderException("No certificate found matching targetContraints."); + + PkixCertPathBuilderResult result = null; + IList certPathList = Platform.CreateArrayList(); + + // check all potential target certificates + foreach (X509Certificate cert in targets) + { + result = Build(cert, pkixParams, certPathList); + + if (result != null) + break; + } + + if (result == null && certPathException != null) + { + throw new PkixCertPathBuilderException(certPathException.Message, certPathException.InnerException); + } + + if (result == null && certPathException == null) + { + throw new PkixCertPathBuilderException("Unable to find certificate chain."); + } + + return result; + } + + private Exception certPathException; + + protected virtual PkixCertPathBuilderResult Build( + X509Certificate tbvCert, + PkixBuilderParameters pkixParams, + IList tbvPath) + { + // If tbvCert is readily present in tbvPath, it indicates having run + // into a cycle in the PKI graph. + if (tbvPath.Contains(tbvCert)) + return null; + + // step out, the certificate is not allowed to appear in a certification + // chain. + if (pkixParams.GetExcludedCerts().Contains(tbvCert)) + return null; + + // test if certificate path exceeds maximum length + if (pkixParams.MaxPathLength != -1) + { + if (tbvPath.Count - 1 > pkixParams.MaxPathLength) + return null; + } + + tbvPath.Add(tbvCert); + +// X509CertificateParser certParser = new X509CertificateParser(); + PkixCertPathBuilderResult builderResult = null; + PkixCertPathValidator validator = new PkixCertPathValidator(); + + try + { + // check whether the issuer of is a TrustAnchor + if (PkixCertPathValidatorUtilities.FindTrustAnchor(tbvCert, pkixParams.GetTrustAnchors()) != null) + { + // exception message from possibly later tried certification + // chains + PkixCertPath certPath = null; + try + { + certPath = new PkixCertPath(tbvPath); + } + catch (Exception e) + { + throw new Exception( + "Certification path could not be constructed from certificate list.", + e); + } + + PkixCertPathValidatorResult result = null; + try + { + result = (PkixCertPathValidatorResult)validator.Validate( + certPath, pkixParams); + } + catch (Exception e) + { + throw new Exception( + "Certification path could not be validated.", e); + } + + return new PkixCertPathBuilderResult(certPath, result.TrustAnchor, + result.PolicyTree, result.SubjectPublicKey); + } + else + { + // add additional X.509 stores from locations in certificate + try + { + PkixCertPathValidatorUtilities.AddAdditionalStoresFromAltNames( + tbvCert, pkixParams); + } + catch (CertificateParsingException e) + { + throw new Exception( + "No additiontal X.509 stores can be added from certificate locations.", + e); + } + + // try to get the issuer certificate from one of the stores + HashSet issuers = new HashSet(); + try + { + issuers.AddAll(PkixCertPathValidatorUtilities.FindIssuerCerts(tbvCert, pkixParams)); + } + catch (Exception e) + { + throw new Exception( + "Cannot find issuer certificate for certificate in certification path.", + e); + } + + if (issuers.IsEmpty) + throw new Exception("No issuer certificate for certificate in certification path found."); + + foreach (X509Certificate issuer in issuers) + { + builderResult = Build(issuer, pkixParams, tbvPath); + + if (builderResult != null) + break; + } + } + } + catch (Exception e) + { + certPathException = e; + } + + if (builderResult == null) + { + tbvPath.Remove(tbvCert); + } + + return builderResult; + } + } +} diff --git a/bc-sharp-crypto/src/pkix/PkixCertPathBuilderException.cs b/bc-sharp-crypto/src/pkix/PkixCertPathBuilderException.cs new file mode 100644 index 0000000..0f10179 --- /dev/null +++ b/bc-sharp-crypto/src/pkix/PkixCertPathBuilderException.cs @@ -0,0 +1,22 @@ +using System; + +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Pkix +{ + /// + /// Summary description for PkixCertPathBuilderException. + /// +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT || PORTABLE) + [Serializable] +#endif + public class PkixCertPathBuilderException : GeneralSecurityException + { + public PkixCertPathBuilderException() : base() { } + + public PkixCertPathBuilderException(string message) : base(message) { } + + public PkixCertPathBuilderException(string message, Exception exception) : base(message, exception) { } + + } +} diff --git a/bc-sharp-crypto/src/pkix/PkixCertPathBuilderResult.cs b/bc-sharp-crypto/src/pkix/PkixCertPathBuilderResult.cs new file mode 100644 index 0000000..f800303 --- /dev/null +++ b/bc-sharp-crypto/src/pkix/PkixCertPathBuilderResult.cs @@ -0,0 +1,45 @@ +using System; +using System.Text; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Pkix; + +namespace Org.BouncyCastle.Pkix +{ + /// + /// Summary description for PkixCertPathBuilderResult. + /// + public class PkixCertPathBuilderResult + : PkixCertPathValidatorResult//, ICertPathBuilderResult + { + private PkixCertPath certPath; + + public PkixCertPathBuilderResult( + PkixCertPath certPath, + TrustAnchor trustAnchor, + PkixPolicyNode policyTree, + AsymmetricKeyParameter subjectPublicKey) + : base(trustAnchor, policyTree, subjectPublicKey) + { + if (certPath == null) + throw new ArgumentNullException("certPath"); + + this.certPath = certPath; + } + + public PkixCertPath CertPath + { + get { return certPath; } + } + + public override string ToString() + { + StringBuilder s = new StringBuilder(); + s.Append("SimplePKIXCertPathBuilderResult: [\n"); + s.Append(" Certification Path: ").Append(CertPath).Append('\n'); + s.Append(" Trust Anchor: ").Append(this.TrustAnchor.TrustedCert.IssuerDN.ToString()).Append('\n'); + s.Append(" Subject Public Key: ").Append(this.SubjectPublicKey).Append("\n]"); + return s.ToString(); + } + } +} diff --git a/bc-sharp-crypto/src/pkix/PkixCertPathChecker.cs b/bc-sharp-crypto/src/pkix/PkixCertPathChecker.cs new file mode 100644 index 0000000..da7e82b --- /dev/null +++ b/bc-sharp-crypto/src/pkix/PkixCertPathChecker.cs @@ -0,0 +1,99 @@ +using Org.BouncyCastle.Utilities.Collections; +using Org.BouncyCastle.X509; + +namespace Org.BouncyCastle.Pkix +{ + public abstract class PkixCertPathChecker + { + protected PkixCertPathChecker() + { + } + + /** + * Initializes the internal state of this PKIXCertPathChecker. + *

    + * The forward flag specifies the order that certificates + * will be passed to the {@link #check check} method (forward or reverse). A + * PKIXCertPathChecker must support reverse checking + * and may support forward checking. + *

    + * + * @param forward + * the order that certificates are presented to the + * check method. If true, + * certificates are presented from target to most-trusted CA + * (forward); if false, from most-trusted CA to + * target (reverse). + * @exception CertPathValidatorException + * if this PKIXCertPathChecker is unable to + * check certificates in the specified order; it should never + * be thrown if the forward flag is false since reverse + * checking must be supported + */ + public abstract void Init(bool forward); + //throws CertPathValidatorException; + + /** + * Indicates if forward checking is supported. Forward checking refers to + * the ability of the PKIXCertPathChecker to perform its + * checks when certificates are presented to the check method + * in the forward direction (from target to most-trusted CA). + * + * @return true if forward checking is supported, + * false otherwise + */ + public abstract bool IsForwardCheckingSupported(); + + /** + * Returns an immutable Set of X.509 certificate extensions + * that this PKIXCertPathChecker supports (i.e. recognizes, + * is able to process), or null if no extensions are + * supported. + *

    + * Each element of the set is a String representing the + * Object Identifier (OID) of the X.509 extension that is supported. The OID + * is represented by a set of nonnegative integers separated by periods. + *

    + * All X.509 certificate extensions that a PKIXCertPathChecker + * might possibly be able to process should be included in the set. + *

    + * + * @return an immutable Set of X.509 extension OIDs (in + * String format) supported by this + * PKIXCertPathChecker, or null if no + * extensions are supported + */ + public abstract ISet GetSupportedExtensions(); + + /** + * Performs the check(s) on the specified certificate using its internal + * state and removes any critical extensions that it processes from the + * specified collection of OID strings that represent the unresolved + * critical extensions. The certificates are presented in the order + * specified by the init method. + * + * @param cert + * the Certificate to be checked + * @param unresolvedCritExts + * a Collection of OID strings representing the + * current set of unresolved critical extensions + * @exception CertPathValidatorException + * if the specified certificate does not pass the check + */ + public abstract void Check(X509Certificate cert, ISet unresolvedCritExts); + //throws CertPathValidatorException; + + /** + * Returns a clone of this object. Calls the Object.clone() + * method. All subclasses which maintain state must support and override + * this method, if necessary. + * + * @return a copy of this PKIXCertPathChecker + */ + public virtual object Clone() + { + // TODO Check this + return base.MemberwiseClone(); + } + } +} diff --git a/bc-sharp-crypto/src/pkix/PkixCertPathValidator.cs b/bc-sharp-crypto/src/pkix/PkixCertPathValidator.cs new file mode 100644 index 0000000..fcfa638 --- /dev/null +++ b/bc-sharp-crypto/src/pkix/PkixCertPathValidator.cs @@ -0,0 +1,420 @@ +using System; +using System.Collections; +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; +using Org.BouncyCastle.X509; +using Org.BouncyCastle.X509.Store; + +namespace Org.BouncyCastle.Pkix +{ + /** + * The Service Provider Interface (SPI) + * for the {@link CertPathValidator CertPathValidator} class. All + * CertPathValidator implementations must include a class (the + * SPI class) that extends this class (CertPathValidatorSpi) + * and implements all of its methods. In general, instances of this class + * should only be accessed through the CertPathValidator class. + * For details, see the Java Cryptography Architecture.
    + *
    + * Concurrent Access
    + *
    + * Instances of this class need not be protected against concurrent + * access from multiple threads. Threads that need to access a single + * CertPathValidatorSpi instance concurrently should synchronize + * amongst themselves and provide the necessary locking before calling the + * wrapping CertPathValidator object.
    + *
    + * However, implementations of CertPathValidatorSpi may still + * encounter concurrency issues, since multiple threads each + * manipulating a different CertPathValidatorSpi instance need not + * synchronize. + */ + /// + /// CertPathValidatorSpi implementation for X.509 Certificate validation a la RFC + /// 3280. + /// + public class PkixCertPathValidator + { + public virtual PkixCertPathValidatorResult Validate( + PkixCertPath certPath, + PkixParameters paramsPkix) + { + if (paramsPkix.GetTrustAnchors() == null) + { + throw new ArgumentException( + "trustAnchors is null, this is not allowed for certification path validation.", + "parameters"); + } + + // + // 6.1.1 - inputs + // + + // + // (a) + // + IList certs = certPath.Certificates; + int n = certs.Count; + + if (certs.Count == 0) + throw new PkixCertPathValidatorException("Certification path is empty.", null, certPath, 0); + + // + // (b) + // + // DateTime validDate = PkixCertPathValidatorUtilities.GetValidDate(paramsPkix); + + // + // (c) + // + ISet userInitialPolicySet = paramsPkix.GetInitialPolicies(); + + // + // (d) + // + TrustAnchor trust; + try + { + trust = PkixCertPathValidatorUtilities.FindTrustAnchor( + (X509Certificate)certs[certs.Count - 1], + paramsPkix.GetTrustAnchors()); + } + catch (Exception e) + { + throw new PkixCertPathValidatorException(e.Message, e, certPath, certs.Count - 1); + } + + if (trust == null) + throw new PkixCertPathValidatorException("Trust anchor for certification path not found.", null, certPath, -1); + + // + // (e), (f), (g) are part of the paramsPkix object. + // + IEnumerator certIter; + int index = 0; + int i; + // Certificate for each interation of the validation loop + // Signature information for each iteration of the validation loop + // + // 6.1.2 - setup + // + + // + // (a) + // + IList[] policyNodes = new IList[n + 1]; + for (int j = 0; j < policyNodes.Length; j++) + { + policyNodes[j] = Platform.CreateArrayList(); + } + + ISet policySet = new HashSet(); + + policySet.Add(Rfc3280CertPathUtilities.ANY_POLICY); + + PkixPolicyNode validPolicyTree = new PkixPolicyNode(Platform.CreateArrayList(), 0, policySet, null, new HashSet(), + Rfc3280CertPathUtilities.ANY_POLICY, false); + + policyNodes[0].Add(validPolicyTree); + + // + // (b) and (c) + // + PkixNameConstraintValidator nameConstraintValidator = new PkixNameConstraintValidator(); + + // (d) + // + int explicitPolicy; + ISet acceptablePolicies = new HashSet(); + + if (paramsPkix.IsExplicitPolicyRequired) + { + explicitPolicy = 0; + } + else + { + explicitPolicy = n + 1; + } + + // + // (e) + // + int inhibitAnyPolicy; + + if (paramsPkix.IsAnyPolicyInhibited) + { + inhibitAnyPolicy = 0; + } + else + { + inhibitAnyPolicy = n + 1; + } + + // + // (f) + // + int policyMapping; + + if (paramsPkix.IsPolicyMappingInhibited) + { + policyMapping = 0; + } + else + { + policyMapping = n + 1; + } + + // + // (g), (h), (i), (j) + // + AsymmetricKeyParameter workingPublicKey; + X509Name workingIssuerName; + + X509Certificate sign = trust.TrustedCert; + try + { + if (sign != null) + { + workingIssuerName = sign.SubjectDN; + workingPublicKey = sign.GetPublicKey(); + } + else + { + workingIssuerName = new X509Name(trust.CAName); + workingPublicKey = trust.CAPublicKey; + } + } + catch (ArgumentException ex) + { + throw new PkixCertPathValidatorException("Subject of trust anchor could not be (re)encoded.", ex, certPath, + -1); + } + + AlgorithmIdentifier workingAlgId = null; + try + { + workingAlgId = PkixCertPathValidatorUtilities.GetAlgorithmIdentifier(workingPublicKey); + } + catch (PkixCertPathValidatorException e) + { + throw new PkixCertPathValidatorException( + "Algorithm identifier of public key of trust anchor could not be read.", e, certPath, -1); + } + +// DerObjectIdentifier workingPublicKeyAlgorithm = workingAlgId.Algorithm; +// Asn1Encodable workingPublicKeyParameters = workingAlgId.Parameters; + + // + // (k) + // + int maxPathLength = n; + + // + // 6.1.3 + // + + X509CertStoreSelector certConstraints = paramsPkix.GetTargetCertConstraints(); + if (certConstraints != null && !certConstraints.Match((X509Certificate)certs[0])) + { + throw new PkixCertPathValidatorException( + "Target certificate in certification path does not match targetConstraints.", null, certPath, 0); + } + + // + // initialize CertPathChecker's + // + IList pathCheckers = paramsPkix.GetCertPathCheckers(); + certIter = pathCheckers.GetEnumerator(); + + while (certIter.MoveNext()) + { + ((PkixCertPathChecker)certIter.Current).Init(false); + } + + X509Certificate cert = null; + + for (index = certs.Count - 1; index >= 0; index--) + { + // try + // { + // + // i as defined in the algorithm description + // + i = n - index; + + // + // set certificate to be checked in this round + // sign and workingPublicKey and workingIssuerName are set + // at the end of the for loop and initialized the + // first time from the TrustAnchor + // + cert = (X509Certificate)certs[index]; + + // + // 6.1.3 + // + + Rfc3280CertPathUtilities.ProcessCertA(certPath, paramsPkix, index, workingPublicKey, + workingIssuerName, sign); + + Rfc3280CertPathUtilities.ProcessCertBC(certPath, index, nameConstraintValidator); + + validPolicyTree = Rfc3280CertPathUtilities.ProcessCertD(certPath, index, + acceptablePolicies, validPolicyTree, policyNodes, inhibitAnyPolicy); + + validPolicyTree = Rfc3280CertPathUtilities.ProcessCertE(certPath, index, validPolicyTree); + + Rfc3280CertPathUtilities.ProcessCertF(certPath, index, validPolicyTree, explicitPolicy); + + // + // 6.1.4 + // + + if (i != n) + { + if (cert != null && cert.Version == 1) + { + throw new PkixCertPathValidatorException( + "Version 1 certificates can't be used as CA ones.", null, certPath, index); + } + + Rfc3280CertPathUtilities.PrepareNextCertA(certPath, index); + + validPolicyTree = Rfc3280CertPathUtilities.PrepareCertB(certPath, index, policyNodes, + validPolicyTree, policyMapping); + + Rfc3280CertPathUtilities.PrepareNextCertG(certPath, index, nameConstraintValidator); + + // (h) + explicitPolicy = Rfc3280CertPathUtilities.PrepareNextCertH1(certPath, index, explicitPolicy); + policyMapping = Rfc3280CertPathUtilities.PrepareNextCertH2(certPath, index, policyMapping); + inhibitAnyPolicy = Rfc3280CertPathUtilities.PrepareNextCertH3(certPath, index, inhibitAnyPolicy); + + // + // (i) + // + explicitPolicy = Rfc3280CertPathUtilities.PrepareNextCertI1(certPath, index, explicitPolicy); + policyMapping = Rfc3280CertPathUtilities.PrepareNextCertI2(certPath, index, policyMapping); + + // (j) + inhibitAnyPolicy = Rfc3280CertPathUtilities.PrepareNextCertJ(certPath, index, inhibitAnyPolicy); + + // (k) + Rfc3280CertPathUtilities.PrepareNextCertK(certPath, index); + + // (l) + maxPathLength = Rfc3280CertPathUtilities.PrepareNextCertL(certPath, index, maxPathLength); + + // (m) + maxPathLength = Rfc3280CertPathUtilities.PrepareNextCertM(certPath, index, maxPathLength); + + // (n) + Rfc3280CertPathUtilities.PrepareNextCertN(certPath, index); + + ISet criticalExtensions1 = cert.GetCriticalExtensionOids(); + + if (criticalExtensions1 != null) + { + criticalExtensions1 = new HashSet(criticalExtensions1); + + // these extensions are handled by the algorithm + criticalExtensions1.Remove(X509Extensions.KeyUsage.Id); + criticalExtensions1.Remove(X509Extensions.CertificatePolicies.Id); + criticalExtensions1.Remove(X509Extensions.PolicyMappings.Id); + criticalExtensions1.Remove(X509Extensions.InhibitAnyPolicy.Id); + criticalExtensions1.Remove(X509Extensions.IssuingDistributionPoint.Id); + criticalExtensions1.Remove(X509Extensions.DeltaCrlIndicator.Id); + criticalExtensions1.Remove(X509Extensions.PolicyConstraints.Id); + criticalExtensions1.Remove(X509Extensions.BasicConstraints.Id); + criticalExtensions1.Remove(X509Extensions.SubjectAlternativeName.Id); + criticalExtensions1.Remove(X509Extensions.NameConstraints.Id); + } + else + { + criticalExtensions1 = new HashSet(); + } + + // (o) + Rfc3280CertPathUtilities.PrepareNextCertO(certPath, index, criticalExtensions1, pathCheckers); + + // set signing certificate for next round + sign = cert; + + // (c) + workingIssuerName = sign.SubjectDN; + + // (d) + try + { + workingPublicKey = PkixCertPathValidatorUtilities.GetNextWorkingKey(certPath.Certificates, index); + } + catch (PkixCertPathValidatorException e) + { + throw new PkixCertPathValidatorException("Next working key could not be retrieved.", e, certPath, index); + } + + workingAlgId = PkixCertPathValidatorUtilities.GetAlgorithmIdentifier(workingPublicKey); + // (f) +// workingPublicKeyAlgorithm = workingAlgId.Algorithm; + // (e) +// workingPublicKeyParameters = workingAlgId.Parameters; + } + } + + // + // 6.1.5 Wrap-up procedure + // + + explicitPolicy = Rfc3280CertPathUtilities.WrapupCertA(explicitPolicy, cert); + + explicitPolicy = Rfc3280CertPathUtilities.WrapupCertB(certPath, index + 1, explicitPolicy); + + // + // (c) (d) and (e) are already done + // + + // + // (f) + // + ISet criticalExtensions = cert.GetCriticalExtensionOids(); + + if (criticalExtensions != null) + { + criticalExtensions = new HashSet(criticalExtensions); + + // Requires .Id + // these extensions are handled by the algorithm + criticalExtensions.Remove(X509Extensions.KeyUsage.Id); + criticalExtensions.Remove(X509Extensions.CertificatePolicies.Id); + criticalExtensions.Remove(X509Extensions.PolicyMappings.Id); + criticalExtensions.Remove(X509Extensions.InhibitAnyPolicy.Id); + criticalExtensions.Remove(X509Extensions.IssuingDistributionPoint.Id); + criticalExtensions.Remove(X509Extensions.DeltaCrlIndicator.Id); + criticalExtensions.Remove(X509Extensions.PolicyConstraints.Id); + criticalExtensions.Remove(X509Extensions.BasicConstraints.Id); + criticalExtensions.Remove(X509Extensions.SubjectAlternativeName.Id); + criticalExtensions.Remove(X509Extensions.NameConstraints.Id); + criticalExtensions.Remove(X509Extensions.CrlDistributionPoints.Id); + } + else + { + criticalExtensions = new HashSet(); + } + + Rfc3280CertPathUtilities.WrapupCertF(certPath, index + 1, pathCheckers, criticalExtensions); + + PkixPolicyNode intersection = Rfc3280CertPathUtilities.WrapupCertG(certPath, paramsPkix, userInitialPolicySet, + index + 1, policyNodes, validPolicyTree, acceptablePolicies); + + if ((explicitPolicy > 0) || (intersection != null)) + { + return new PkixCertPathValidatorResult(trust, intersection, cert.GetPublicKey()); + } + + throw new PkixCertPathValidatorException("Path processing failed on policy.", null, certPath, index); + } + } +} diff --git a/bc-sharp-crypto/src/pkix/PkixCertPathValidatorException.cs b/bc-sharp-crypto/src/pkix/PkixCertPathValidatorException.cs new file mode 100644 index 0000000..a477f7d --- /dev/null +++ b/bc-sharp-crypto/src/pkix/PkixCertPathValidatorException.cs @@ -0,0 +1,221 @@ +using System; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Pkix +{ + /** + * An exception indicating one of a variety of problems encountered when + * validating a certification path.
    + *
    + * A CertPathValidatorException provides support for wrapping + * exceptions. The {@link #getCause getCause} method returns the throwable, + * if any, that caused this exception to be thrown.
    + *
    + * A CertPathValidatorException may also include the + * certification path that was being validated when the exception was thrown + * and the index of the certificate in the certification path that caused the + * exception to be thrown. Use the {@link #getCertPath getCertPath} and + * {@link #getIndex getIndex} methods to retrieve this information.
    + *
    + * Concurrent Access
    + *
    + * Unless otherwise specified, the methods defined in this class are not + * thread-safe. Multiple threads that need to access a single + * object concurrently should synchronize amongst themselves and + * provide the necessary locking. Multiple threads each manipulating + * separate objects need not synchronize. + * + * @see CertPathValidator + **/ +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT || PORTABLE) + [Serializable] +#endif + public class PkixCertPathValidatorException + : GeneralSecurityException + { + private Exception cause; + private PkixCertPath certPath; + private int index = -1; + + public PkixCertPathValidatorException() : base() { } + + /// + /// Creates a PkixCertPathValidatorException with the given detail + /// message. A detail message is a String that describes this + /// particular exception. + /// + /// the detail message + public PkixCertPathValidatorException(string message) : base(message) { } + + /// + /// Creates a PkixCertPathValidatorException with the specified + /// detail message and cause. + /// + /// the detail message + /// the cause (which is saved for later retrieval by the + /// {@link #getCause getCause()} method). (A null + /// value is permitted, and indicates that the cause is + /// nonexistent or unknown.) + public PkixCertPathValidatorException(string message, Exception cause) : base(message) + { + this.cause = cause; + } + + /// + /// Creates a PkixCertPathValidatorException with the specified + /// detail message, cause, certification path, and index. + /// + /// the detail message (or null if none) + /// the cause (or null if none) + /// the certification path that was in the process of being + /// validated when the error was encountered + /// the index of the certificate in the certification path that * + public PkixCertPathValidatorException( + string message, + Exception cause, + PkixCertPath certPath, + int index) + : base(message) + { + if (certPath == null && index != -1) + { + throw new ArgumentNullException( + "certPath = null and index != -1"); + } + if (index < -1 + || (certPath != null && index >= certPath.Certificates.Count)) + { + throw new IndexOutOfRangeException( + " index < -1 or out of bound of certPath.getCertificates()"); + } + + this.cause = cause; + this.certPath = certPath; + this.index = index; + } + + // + // Prints a stack trace to a PrintWriter, including the + // backtrace of the cause, if any. + // + // @param pw + // the PrintWriter to use for output + // + // public void printStackTrace(PrintWriter pw) + // { + // super.printStackTrace(pw); + // if (getCause() != null) + // { + // getCause().printStackTrace(pw); + // } + // } + //} + + + // /** + // * Creates a CertPathValidatorException that wraps the + // * specified throwable. This allows any exception to be converted into a + // * CertPathValidatorException, while retaining information + // * about the wrapped exception, which may be useful for debugging. The + // * detail message is set to (cause==null ? null : cause.toString() + // * ) + // * (which typically contains the class and detail message of cause). + // * + // * @param cause + // * the cause (which is saved for later retrieval by the + // * {@link #getCause getCause()} method). (A null + // * value is permitted, and indicates that the cause is + // * nonexistent or unknown.) + // */ + // public PkixCertPathValidatorException(Throwable cause) + // { + // this.cause = cause; + // } + // + + /// + /// Returns the detail message for this CertPathValidatorException. + /// + /// the detail message, or null if neither the message nor cause were specified + public override string Message + { + get + { + string message = base.Message; + + if (message != null) + { + return message; + } + + if (cause != null) + { + return cause.Message; + } + + return null; + } + } + + /** + * Returns the certification path that was being validated when the + * exception was thrown. + * + * @return the CertPath that was being validated when the + * exception was thrown (or null if not specified) + */ + public PkixCertPath CertPath + { + get { return certPath; } + } + + /** + * Returns the index of the certificate in the certification path that + * caused the exception to be thrown. Note that the list of certificates in + * a CertPath is zero based. If no index has been set, -1 is + * returned. + * + * @return the index that has been set, or -1 if none has been set + */ + public int Index + { + get { return index; } + } + +// /** +// * Returns the cause of this CertPathValidatorException or +// * null if the cause is nonexistent or unknown. +// * +// * @return the cause of this throwable or null if the cause +// * is nonexistent or unknown. +// */ +// public Throwable getCause() +// { +// return cause; +// } +// +// /** +// * Returns a string describing this exception, including a description of +// * the internal (wrapped) cause if there is one. +// * +// * @return a string representation of this +// * CertPathValidatorException +// */ +// public String toString() +// { +// StringBuffer sb = new StringBuffer(); +// String s = getMessage(); +// if (s != null) +// { +// sb.append(s); +// } +// if (getIndex() >= 0) +// { +// sb.append("index in certpath: ").append(getIndex()).append('\n'); +// sb.append(getCertPath()); +// } +// return sb.toString(); +// } + + } +} diff --git a/bc-sharp-crypto/src/pkix/PkixCertPathValidatorResult.cs b/bc-sharp-crypto/src/pkix/PkixCertPathValidatorResult.cs new file mode 100644 index 0000000..c7d81c7 --- /dev/null +++ b/bc-sharp-crypto/src/pkix/PkixCertPathValidatorResult.cs @@ -0,0 +1,69 @@ +using System; +using System.Text; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Pkix +{ + /// + /// Summary description for PkixCertPathValidatorResult. + /// + public class PkixCertPathValidatorResult + //: ICertPathValidatorResult + { + private TrustAnchor trustAnchor; + private PkixPolicyNode policyTree; + private AsymmetricKeyParameter subjectPublicKey; + + public PkixPolicyNode PolicyTree + { + get { return this.policyTree; } + } + + public TrustAnchor TrustAnchor + { + get { return this.trustAnchor; } + } + + public AsymmetricKeyParameter SubjectPublicKey + { + get { return this.subjectPublicKey; } + } + + public PkixCertPathValidatorResult( + TrustAnchor trustAnchor, + PkixPolicyNode policyTree, + AsymmetricKeyParameter subjectPublicKey) + { + if (subjectPublicKey == null) + { + throw new NullReferenceException("subjectPublicKey must be non-null"); + } + if (trustAnchor == null) + { + throw new NullReferenceException("trustAnchor must be non-null"); + } + + this.trustAnchor = trustAnchor; + this.policyTree = policyTree; + this.subjectPublicKey = subjectPublicKey; + } + + public object Clone() + { + return new PkixCertPathValidatorResult(this.TrustAnchor, this.PolicyTree, this.SubjectPublicKey); + } + + public override String ToString() + { + StringBuilder sB = new StringBuilder(); + sB.Append("PKIXCertPathValidatorResult: [ \n"); + sB.Append(" Trust Anchor: ").Append(this.TrustAnchor).Append('\n'); + sB.Append(" Policy Tree: ").Append(this.PolicyTree).Append('\n'); + sB.Append(" Subject Public Key: ").Append(this.SubjectPublicKey).Append("\n]"); + return sB.ToString(); + } + + } +} diff --git a/bc-sharp-crypto/src/pkix/PkixCertPathValidatorUtilities.cs b/bc-sharp-crypto/src/pkix/PkixCertPathValidatorUtilities.cs new file mode 100644 index 0000000..a2704a7 --- /dev/null +++ b/bc-sharp-crypto/src/pkix/PkixCertPathValidatorUtilities.cs @@ -0,0 +1,1194 @@ +using System; +using System.Collections; +using System.IO; +using System.Text; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.IsisMtt; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; +using Org.BouncyCastle.Utilities.Date; +using Org.BouncyCastle.X509; +using Org.BouncyCastle.X509.Extension; +using Org.BouncyCastle.X509.Store; + +namespace Org.BouncyCastle.Pkix +{ + /// + /// Summary description for PkixCertPathValidatorUtilities. + /// + public class PkixCertPathValidatorUtilities + { + private static readonly PkixCrlUtilities CrlUtilities = new PkixCrlUtilities(); + + internal static readonly string ANY_POLICY = "2.5.29.32.0"; + + internal static readonly string CRL_NUMBER = X509Extensions.CrlNumber.Id; + + /// + /// key usage bits + /// + internal static readonly int KEY_CERT_SIGN = 5; + internal static readonly int CRL_SIGN = 6; + + internal static readonly string[] crlReasons = new string[] + { + "unspecified", + "keyCompromise", + "cACompromise", + "affiliationChanged", + "superseded", + "cessationOfOperation", + "certificateHold", + "unknown", + "removeFromCRL", + "privilegeWithdrawn", + "aACompromise" + }; + + /// + /// Search the given Set of TrustAnchor's for one that is the + /// issuer of the given X509 certificate. + /// + /// the X509 certificate + /// a Set of TrustAnchor's + /// the TrustAnchor object if found or + /// null if not. + /// + /// @exception + internal static TrustAnchor FindTrustAnchor( + X509Certificate cert, + ISet trustAnchors) + { + IEnumerator iter = trustAnchors.GetEnumerator(); + TrustAnchor trust = null; + AsymmetricKeyParameter trustPublicKey = null; + Exception invalidKeyEx = null; + + X509CertStoreSelector certSelectX509 = new X509CertStoreSelector(); + + try + { + certSelectX509.Subject = GetIssuerPrincipal(cert); + } + catch (IOException ex) + { + throw new Exception("Cannot set subject search criteria for trust anchor.", ex); + } + + while (iter.MoveNext() && trust == null) + { + trust = (TrustAnchor) iter.Current; + if (trust.TrustedCert != null) + { + if (certSelectX509.Match(trust.TrustedCert)) + { + trustPublicKey = trust.TrustedCert.GetPublicKey(); + } + else + { + trust = null; + } + } + else if (trust.CAName != null && trust.CAPublicKey != null) + { + try + { + X509Name certIssuer = GetIssuerPrincipal(cert); + X509Name caName = new X509Name(trust.CAName); + + if (certIssuer.Equivalent(caName, true)) + { + trustPublicKey = trust.CAPublicKey; + } + else + { + trust = null; + } + } + catch (InvalidParameterException) + { + trust = null; + } + } + else + { + trust = null; + } + + if (trustPublicKey != null) + { + try + { + cert.Verify(trustPublicKey); + } + catch (Exception ex) + { + invalidKeyEx = ex; + trust = null; + } + } + } + + if (trust == null && invalidKeyEx != null) + { + throw new Exception("TrustAnchor found but certificate validation failed.", invalidKeyEx); + } + + return trust; + } + + internal static void AddAdditionalStoresFromAltNames( + X509Certificate cert, + PkixParameters pkixParams) + { + // if in the IssuerAltName extension an URI + // is given, add an additinal X.509 store + if (cert.GetIssuerAlternativeNames() != null) + { + IEnumerator it = cert.GetIssuerAlternativeNames().GetEnumerator(); + while (it.MoveNext()) + { + // look for URI + IList list = (IList)it.Current; + //if (list[0].Equals(new Integer(GeneralName.UniformResourceIdentifier))) + if (list[0].Equals(GeneralName.UniformResourceIdentifier)) + { + // found + string temp = (string)list[1]; + PkixCertPathValidatorUtilities.AddAdditionalStoreFromLocation(temp, pkixParams); + } + } + } + } + + internal static DateTime GetValidDate(PkixParameters paramsPKIX) + { + DateTimeObject validDate = paramsPKIX.Date; + + if (validDate == null) + return DateTime.UtcNow; + + return validDate.Value; + } + + /// + /// Returns the issuer of an attribute certificate or certificate. + /// + /// The attribute certificate or certificate. + /// The issuer as X500Principal. + internal static X509Name GetIssuerPrincipal( + object cert) + { + if (cert is X509Certificate) + { + return ((X509Certificate)cert).IssuerDN; + } + else + { + return ((IX509AttributeCertificate)cert).Issuer.GetPrincipals()[0]; + } + } + + internal static bool IsSelfIssued( + X509Certificate cert) + { + return cert.SubjectDN.Equivalent(cert.IssuerDN, true); + } + + internal static AlgorithmIdentifier GetAlgorithmIdentifier( + AsymmetricKeyParameter key) + { + try + { + SubjectPublicKeyInfo info = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(key); + + return info.AlgorithmID; + } + catch (Exception e) + { + throw new PkixCertPathValidatorException("Subject public key cannot be decoded.", e); + } + } + + internal static bool IsAnyPolicy( + ISet policySet) + { + return policySet == null || policySet.Contains(ANY_POLICY) || policySet.Count == 0; + } + + internal static void AddAdditionalStoreFromLocation( + string location, + PkixParameters pkixParams) + { + if (pkixParams.IsAdditionalLocationsEnabled) + { + try + { + if (Platform.StartsWith(location, "ldap://")) + { + // ldap://directory.d-trust.net/CN=D-TRUST + // Qualified CA 2003 1:PN,O=D-Trust GmbH,C=DE + // skip "ldap://" + location = location.Substring(7); + // after first / baseDN starts + string url;//, baseDN; + int slashPos = location.IndexOf('/'); + if (slashPos != -1) + { + url = "ldap://" + location.Substring(0, slashPos); +// baseDN = location.Substring(slashPos); + } + else + { + url = "ldap://" + location; +// baseDN = nsull; + } + + throw Platform.CreateNotImplementedException("LDAP cert/CRL stores"); + + // use all purpose parameters + //X509LDAPCertStoreParameters ldapParams = new X509LDAPCertStoreParameters.Builder( + // url, baseDN).build(); + //pkixParams.AddAdditionalStore(X509Store.getInstance( + // "CERTIFICATE/LDAP", ldapParams)); + //pkixParams.AddAdditionalStore(X509Store.getInstance( + // "CRL/LDAP", ldapParams)); + //pkixParams.AddAdditionalStore(X509Store.getInstance( + // "ATTRIBUTECERTIFICATE/LDAP", ldapParams)); + //pkixParams.AddAdditionalStore(X509Store.getInstance( + // "CERTIFICATEPAIR/LDAP", ldapParams)); + } + } + catch (Exception) + { + // cannot happen + throw new Exception("Exception adding X.509 stores."); + } + } + } + + private static BigInteger GetSerialNumber( + object cert) + { + if (cert is X509Certificate) + { + return ((X509Certificate)cert).SerialNumber; + } + else + { + return ((X509V2AttributeCertificate)cert).SerialNumber; + } + } + + // + // policy checking + // + + internal static ISet GetQualifierSet(Asn1Sequence qualifiers) + { + ISet pq = new HashSet(); + + if (qualifiers == null) + { + return pq; + } + + foreach (Asn1Encodable ae in qualifiers) + { + try + { +// pq.Add(PolicyQualifierInfo.GetInstance(Asn1Object.FromByteArray(ae.GetEncoded()))); + pq.Add(PolicyQualifierInfo.GetInstance(ae.ToAsn1Object())); + } + catch (IOException ex) + { + throw new PkixCertPathValidatorException("Policy qualifier info cannot be decoded.", ex); + } + } + + return pq; + } + + internal static PkixPolicyNode RemovePolicyNode( + PkixPolicyNode validPolicyTree, + IList[] policyNodes, + PkixPolicyNode _node) + { + PkixPolicyNode _parent = (PkixPolicyNode)_node.Parent; + + if (validPolicyTree == null) + { + return null; + } + + if (_parent == null) + { + for (int j = 0; j < policyNodes.Length; j++) + { + policyNodes[j] = Platform.CreateArrayList(); + } + + return null; + } + else + { + _parent.RemoveChild(_node); + RemovePolicyNodeRecurse(policyNodes, _node); + + return validPolicyTree; + } + } + + private static void RemovePolicyNodeRecurse(IList[] policyNodes, PkixPolicyNode _node) + { + policyNodes[_node.Depth].Remove(_node); + + if (_node.HasChildren) + { + foreach (PkixPolicyNode _child in _node.Children) + { + RemovePolicyNodeRecurse(policyNodes, _child); + } + } + } + + internal static void PrepareNextCertB1( + int i, + IList[] policyNodes, + string id_p, + IDictionary m_idp, + X509Certificate cert) + { + bool idp_found = false; + IEnumerator nodes_i = policyNodes[i].GetEnumerator(); + while (nodes_i.MoveNext()) + { + PkixPolicyNode node = (PkixPolicyNode)nodes_i.Current; + if (node.ValidPolicy.Equals(id_p)) + { + idp_found = true; + node.ExpectedPolicies = (ISet)m_idp[id_p]; + break; + } + } + + if (!idp_found) + { + nodes_i = policyNodes[i].GetEnumerator(); + while (nodes_i.MoveNext()) + { + PkixPolicyNode node = (PkixPolicyNode)nodes_i.Current; + if (ANY_POLICY.Equals(node.ValidPolicy)) + { + ISet pq = null; + Asn1Sequence policies = null; + try + { + policies = DerSequence.GetInstance(GetExtensionValue(cert, X509Extensions.CertificatePolicies)); + } + catch (Exception e) + { + throw new Exception("Certificate policies cannot be decoded.", e); + } + + IEnumerator enm = policies.GetEnumerator(); + while (enm.MoveNext()) + { + PolicyInformation pinfo = null; + + try + { + pinfo = PolicyInformation.GetInstance(enm.Current); + } + catch (Exception ex) + { + throw new Exception("Policy information cannot be decoded.", ex); + } + + if (ANY_POLICY.Equals(pinfo.PolicyIdentifier.Id)) + { + try + { + pq = GetQualifierSet(pinfo.PolicyQualifiers); + } + catch (PkixCertPathValidatorException ex) + { + throw new PkixCertPathValidatorException( + "Policy qualifier info set could not be built.", ex); + } + break; + } + } + bool ci = false; + ISet critExtOids = cert.GetCriticalExtensionOids(); + if (critExtOids != null) + { + ci = critExtOids.Contains(X509Extensions.CertificatePolicies.Id); + } + + PkixPolicyNode p_node = (PkixPolicyNode)node.Parent; + if (ANY_POLICY.Equals(p_node.ValidPolicy)) + { + PkixPolicyNode c_node = new PkixPolicyNode( + Platform.CreateArrayList(), i, + (ISet)m_idp[id_p], + p_node, pq, id_p, ci); + p_node.AddChild(c_node); + policyNodes[i].Add(c_node); + } + break; + } + } + } + } + + internal static PkixPolicyNode PrepareNextCertB2( + int i, + IList[] policyNodes, + string id_p, + PkixPolicyNode validPolicyTree) + { + int pos = 0; + + // Copy to avoid RemoveAt calls interfering with enumeration + foreach (PkixPolicyNode node in Platform.CreateArrayList(policyNodes[i])) + { + if (node.ValidPolicy.Equals(id_p)) + { + PkixPolicyNode p_node = (PkixPolicyNode)node.Parent; + p_node.RemoveChild(node); + + // Removal of element at current iterator position not supported in C# + //nodes_i.remove(); + policyNodes[i].RemoveAt(pos); + + for (int k = (i - 1); k >= 0; k--) + { + IList nodes = policyNodes[k]; + for (int l = 0; l < nodes.Count; l++) + { + PkixPolicyNode node2 = (PkixPolicyNode)nodes[l]; + if (!node2.HasChildren) + { + validPolicyTree = RemovePolicyNode(validPolicyTree, policyNodes, node2); + if (validPolicyTree == null) + break; + } + } + } + } + else + { + ++pos; + } + } + return validPolicyTree; + } + + internal static void GetCertStatus( + DateTime validDate, + X509Crl crl, + Object cert, + CertStatus certStatus) + { + X509Crl bcCRL = null; + + try + { + bcCRL = new X509Crl(CertificateList.GetInstance((Asn1Sequence)Asn1Sequence.FromByteArray(crl.GetEncoded()))); + } + catch (Exception exception) + { + throw new Exception("Bouncy Castle X509Crl could not be created.", exception); + } + + X509CrlEntry crl_entry = (X509CrlEntry)bcCRL.GetRevokedCertificate(GetSerialNumber(cert)); + + if (crl_entry == null) + return; + + X509Name issuer = GetIssuerPrincipal(cert); + + if (issuer.Equivalent(crl_entry.GetCertificateIssuer(), true) + || issuer.Equivalent(crl.IssuerDN, true)) + { + DerEnumerated reasonCode = null; + if (crl_entry.HasExtensions) + { + try + { + reasonCode = DerEnumerated.GetInstance( + GetExtensionValue(crl_entry, X509Extensions.ReasonCode)); + } + catch (Exception e) + { + throw new Exception( + "Reason code CRL entry extension could not be decoded.", + e); + } + } + + // for reason keyCompromise, caCompromise, aACompromise or + // unspecified + if (!(validDate.Ticks < crl_entry.RevocationDate.Ticks) + || reasonCode == null + || reasonCode.Value.TestBit(0) + || reasonCode.Value.TestBit(1) + || reasonCode.Value.TestBit(2) + || reasonCode.Value.TestBit(8)) + { + if (reasonCode != null) // (i) or (j) (1) + { + certStatus.Status = reasonCode.Value.SignValue; + } + else // (i) or (j) (2) + { + certStatus.Status = CrlReason.Unspecified; + } + certStatus.RevocationDate = new DateTimeObject(crl_entry.RevocationDate); + } + } + } + + /** + * Return the next working key inheriting DSA parameters if necessary. + *

    + * This methods inherits DSA parameters from the indexed certificate or + * previous certificates in the certificate chain to the returned + * PublicKey. The list is searched upwards, meaning the end + * certificate is at position 0 and previous certificates are following. + *

    + *

    + * If the indexed certificate does not contain a DSA key this method simply + * returns the public key. If the DSA key already contains DSA parameters + * the key is also only returned. + *

    + * + * @param certs The certification path. + * @param index The index of the certificate which contains the public key + * which should be extended with DSA parameters. + * @return The public key of the certificate in list position + * index extended with DSA parameters if applicable. + * @throws Exception if DSA parameters cannot be inherited. + */ + internal static AsymmetricKeyParameter GetNextWorkingKey( + IList certs, + int index) + { + //Only X509Certificate + X509Certificate cert = (X509Certificate)certs[index]; + + AsymmetricKeyParameter pubKey = cert.GetPublicKey(); + + if (!(pubKey is DsaPublicKeyParameters)) + return pubKey; + + DsaPublicKeyParameters dsaPubKey = (DsaPublicKeyParameters)pubKey; + + if (dsaPubKey.Parameters != null) + return dsaPubKey; + + for (int i = index + 1; i < certs.Count; i++) + { + X509Certificate parentCert = (X509Certificate)certs[i]; + pubKey = parentCert.GetPublicKey(); + + if (!(pubKey is DsaPublicKeyParameters)) + { + throw new PkixCertPathValidatorException( + "DSA parameters cannot be inherited from previous certificate."); + } + + DsaPublicKeyParameters prevDSAPubKey = (DsaPublicKeyParameters)pubKey; + + if (prevDSAPubKey.Parameters == null) + continue; + + DsaParameters dsaParams = prevDSAPubKey.Parameters; + + try + { + return new DsaPublicKeyParameters(dsaPubKey.Y, dsaParams); + } + catch (Exception exception) + { + throw new Exception(exception.Message); + } + } + + throw new PkixCertPathValidatorException("DSA parameters cannot be inherited from previous certificate."); + } + + internal static DateTime GetValidCertDateFromValidityModel( + PkixParameters paramsPkix, + PkixCertPath certPath, + int index) + { + if (paramsPkix.ValidityModel != PkixParameters.ChainValidityModel) + { + return GetValidDate(paramsPkix); + } + + // if end cert use given signing/encryption/... time + if (index <= 0) + { + return PkixCertPathValidatorUtilities.GetValidDate(paramsPkix); + // else use time when previous cert was created + } + + if (index - 1 == 0) + { + DerGeneralizedTime dateOfCertgen = null; + try + { + X509Certificate cert = (X509Certificate)certPath.Certificates[index - 1]; + Asn1OctetString extVal = cert.GetExtensionValue( + IsisMttObjectIdentifiers.IdIsisMttATDateOfCertGen); + dateOfCertgen = DerGeneralizedTime.GetInstance(extVal); + } + catch (ArgumentException) + { + throw new Exception( + "Date of cert gen extension could not be read."); + } + if (dateOfCertgen != null) + { + try + { + return dateOfCertgen.ToDateTime(); + } + catch (ArgumentException e) + { + throw new Exception( + "Date from date of cert gen extension could not be parsed.", + e); + } + } + } + + return ((X509Certificate)certPath.Certificates[index - 1]).NotBefore; + } + + /// + /// Return a Collection of all certificates or attribute certificates found + /// in the X509Store's that are matching the certSelect criteriums. + /// + /// a {@link Selector} object that will be used to select + /// the certificates + /// a List containing only X509Store objects. These + /// are used to search for certificates. + /// a Collection of all found or + /// objects. + /// May be empty but never null. + /// + internal static ICollection FindCertificates( + X509CertStoreSelector certSelect, + IList certStores) + { + ISet certs = new HashSet(); + + foreach (IX509Store certStore in certStores) + { + try + { +// certs.AddAll(certStore.GetMatches(certSelect)); + foreach (X509Certificate c in certStore.GetMatches(certSelect)) + { + certs.Add(c); + } + } + catch (Exception e) + { + throw new Exception("Problem while picking certificates from X.509 store.", e); + } + } + + return certs; + } + + /** + * Add the CRL issuers from the cRLIssuer field of the distribution point or + * from the certificate if not given to the issuer criterion of the + * selector. + *

    + * The issuerPrincipals are a collection with a single + * X500Principal for X509Certificates. For + * {@link X509AttributeCertificate}s the issuer may contain more than one + * X500Principal. + *

    + * + * @param dp The distribution point. + * @param issuerPrincipals The issuers of the certificate or attribute + * certificate which contains the distribution point. + * @param selector The CRL selector. + * @param pkixParams The PKIX parameters containing the cert stores. + * @throws Exception if an exception occurs while processing. + * @throws ClassCastException if issuerPrincipals does not + * contain only X500Principals. + */ + internal static void GetCrlIssuersFromDistributionPoint( + DistributionPoint dp, + ICollection issuerPrincipals, + X509CrlStoreSelector selector, + PkixParameters pkixParams) + { + IList issuers = Platform.CreateArrayList(); + // indirect CRL + if (dp.CrlIssuer != null) + { + GeneralName[] genNames = dp.CrlIssuer.GetNames(); + // look for a DN + for (int j = 0; j < genNames.Length; j++) + { + if (genNames[j].TagNo == GeneralName.DirectoryName) + { + try + { + issuers.Add(X509Name.GetInstance(genNames[j].Name.ToAsn1Object())); + } + catch (IOException e) + { + throw new Exception( + "CRL issuer information from distribution point cannot be decoded.", + e); + } + } + } + } + else + { + /* + * certificate issuer is CRL issuer, distributionPoint field MUST be + * present. + */ + if (dp.DistributionPointName == null) + { + throw new Exception( + "CRL issuer is omitted from distribution point but no distributionPoint field present."); + } + + // add and check issuer principals + for (IEnumerator it = issuerPrincipals.GetEnumerator(); it.MoveNext(); ) + { + issuers.Add((X509Name)it.Current); + } + } + // TODO: is not found although this should correctly add the rel name. selector of Sun is buggy here or PKI test case is invalid + // distributionPoint + // if (dp.getDistributionPoint() != null) + // { + // // look for nameRelativeToCRLIssuer + // if (dp.getDistributionPoint().getType() == DistributionPointName.NAME_RELATIVE_TO_CRL_ISSUER) + // { + // // append fragment to issuer, only one + // // issuer can be there, if this is given + // if (issuers.size() != 1) + // { + // throw new AnnotatedException( + // "nameRelativeToCRLIssuer field is given but more than one CRL issuer is given."); + // } + // DEREncodable relName = dp.getDistributionPoint().getName(); + // Iterator it = issuers.iterator(); + // List issuersTemp = new ArrayList(issuers.size()); + // while (it.hasNext()) + // { + // Enumeration e = null; + // try + // { + // e = ASN1Sequence.getInstance( + // new ASN1InputStream(((X500Principal) it.next()) + // .getEncoded()).readObject()).getObjects(); + // } + // catch (IOException ex) + // { + // throw new AnnotatedException( + // "Cannot decode CRL issuer information.", ex); + // } + // ASN1EncodableVector v = new ASN1EncodableVector(); + // while (e.hasMoreElements()) + // { + // v.add((DEREncodable) e.nextElement()); + // } + // v.add(relName); + // issuersTemp.add(new X500Principal(new DERSequence(v) + // .getDEREncoded())); + // } + // issuers.clear(); + // issuers.addAll(issuersTemp); + // } + // } + + selector.Issuers = issuers; + } + + /** + * Fetches complete CRLs according to RFC 3280. + * + * @param dp The distribution point for which the complete CRL + * @param cert The X509Certificate or + * {@link org.bouncycastle.x509.X509AttributeCertificate} for + * which the CRL should be searched. + * @param currentDate The date for which the delta CRLs must be valid. + * @param paramsPKIX The extended PKIX parameters. + * @return A Set of X509CRLs with complete + * CRLs. + * @throws Exception if an exception occurs while picking the CRLs + * or no CRLs are found. + */ + internal static ISet GetCompleteCrls( + DistributionPoint dp, + object cert, + DateTime currentDate, + PkixParameters paramsPKIX) + { + X509CrlStoreSelector crlselect = new X509CrlStoreSelector(); + try + { + ISet issuers = new HashSet(); + if (cert is X509V2AttributeCertificate) + { + issuers.Add(((X509V2AttributeCertificate)cert) + .Issuer.GetPrincipals()[0]); + } + else + { + issuers.Add(GetIssuerPrincipal(cert)); + } + PkixCertPathValidatorUtilities.GetCrlIssuersFromDistributionPoint(dp, issuers, crlselect, paramsPKIX); + } + catch (Exception e) + { + throw new Exception("Could not get issuer information from distribution point.", e); + } + + if (cert is X509Certificate) + { + crlselect.CertificateChecking = (X509Certificate)cert; + } + else if (cert is X509V2AttributeCertificate) + { + crlselect.AttrCertChecking = (IX509AttributeCertificate)cert; + } + + crlselect.CompleteCrlEnabled = true; + ISet crls = CrlUtilities.FindCrls(crlselect, paramsPKIX, currentDate); + + if (crls.IsEmpty) + { + if (cert is IX509AttributeCertificate) + { + IX509AttributeCertificate aCert = (IX509AttributeCertificate)cert; + + throw new Exception("No CRLs found for issuer \"" + aCert.Issuer.GetPrincipals()[0] + "\""); + } + else + { + X509Certificate xCert = (X509Certificate)cert; + + throw new Exception("No CRLs found for issuer \"" + xCert.IssuerDN + "\""); + } + } + + return crls; + } + + /** + * Fetches delta CRLs according to RFC 3280 section 5.2.4. + * + * @param currentDate The date for which the delta CRLs must be valid. + * @param paramsPKIX The extended PKIX parameters. + * @param completeCRL The complete CRL the delta CRL is for. + * @return A Set of X509CRLs with delta CRLs. + * @throws Exception if an exception occurs while picking the delta + * CRLs. + */ + internal static ISet GetDeltaCrls( + DateTime currentDate, + PkixParameters paramsPKIX, + X509Crl completeCRL) + { + X509CrlStoreSelector deltaSelect = new X509CrlStoreSelector(); + + // 5.2.4 (a) + try + { + IList deltaSelectIssuer = Platform.CreateArrayList(); + deltaSelectIssuer.Add(completeCRL.IssuerDN); + deltaSelect.Issuers = deltaSelectIssuer; + } + catch (IOException e) + { + throw new Exception("Cannot extract issuer from CRL.", e); + } + + BigInteger completeCRLNumber = null; + try + { + Asn1Object asn1Object = GetExtensionValue(completeCRL, X509Extensions.CrlNumber); + if (asn1Object != null) + { + completeCRLNumber = CrlNumber.GetInstance(asn1Object).PositiveValue; + } + } + catch (Exception e) + { + throw new Exception( + "CRL number extension could not be extracted from CRL.", e); + } + + // 5.2.4 (b) + byte[] idp = null; + + try + { + Asn1Object obj = GetExtensionValue(completeCRL, X509Extensions.IssuingDistributionPoint); + if (obj != null) + { + idp = obj.GetDerEncoded(); + } + } + catch (Exception e) + { + throw new Exception( + "Issuing distribution point extension value could not be read.", + e); + } + + // 5.2.4 (d) + + deltaSelect.MinCrlNumber = (completeCRLNumber == null) + ? null + : completeCRLNumber.Add(BigInteger.One); + + deltaSelect.IssuingDistributionPoint = idp; + deltaSelect.IssuingDistributionPointEnabled = true; + + // 5.2.4 (c) + deltaSelect.MaxBaseCrlNumber = completeCRLNumber; + + // find delta CRLs + ISet temp = CrlUtilities.FindCrls(deltaSelect, paramsPKIX, currentDate); + + ISet result = new HashSet(); + + foreach (X509Crl crl in temp) + { + if (isDeltaCrl(crl)) + { + result.Add(crl); + } + } + + return result; + } + + private static bool isDeltaCrl( + X509Crl crl) + { + ISet critical = crl.GetCriticalExtensionOids(); + + return critical.Contains(X509Extensions.DeltaCrlIndicator.Id); + } + + internal static ICollection FindCertificates( + X509AttrCertStoreSelector certSelect, + IList certStores) + { + ISet certs = new HashSet(); + + foreach (IX509Store certStore in certStores) + { + try + { +// certs.AddAll(certStore.GetMatches(certSelect)); + foreach (X509V2AttributeCertificate ac in certStore.GetMatches(certSelect)) + { + certs.Add(ac); + } + } + catch (Exception e) + { + throw new Exception( + "Problem while picking certificates from X.509 store.", e); + } + } + + return certs; + } + + internal static void AddAdditionalStoresFromCrlDistributionPoint( + CrlDistPoint crldp, + PkixParameters pkixParams) + { + if (crldp != null) + { + DistributionPoint[] dps = null; + try + { + dps = crldp.GetDistributionPoints(); + } + catch (Exception e) + { + throw new Exception( + "Distribution points could not be read.", e); + } + for (int i = 0; i < dps.Length; i++) + { + DistributionPointName dpn = dps[i].DistributionPointName; + // look for URIs in fullName + if (dpn != null) + { + if (dpn.PointType == DistributionPointName.FullName) + { + GeneralName[] genNames = GeneralNames.GetInstance( + dpn.Name).GetNames(); + // look for an URI + for (int j = 0; j < genNames.Length; j++) + { + if (genNames[j].TagNo == GeneralName.UniformResourceIdentifier) + { + string location = DerIA5String.GetInstance( + genNames[j].Name).GetString(); + PkixCertPathValidatorUtilities.AddAdditionalStoreFromLocation( + location, pkixParams); + } + } + } + } + } + } + } + + internal static bool ProcessCertD1i( + int index, + IList[] policyNodes, + DerObjectIdentifier pOid, + ISet pq) + { + IList policyNodeVec = policyNodes[index - 1]; + + for (int j = 0; j < policyNodeVec.Count; j++) + { + PkixPolicyNode node = (PkixPolicyNode)policyNodeVec[j]; + ISet expectedPolicies = node.ExpectedPolicies; + + if (expectedPolicies.Contains(pOid.Id)) + { + ISet childExpectedPolicies = new HashSet(); + childExpectedPolicies.Add(pOid.Id); + + PkixPolicyNode child = new PkixPolicyNode(Platform.CreateArrayList(), + index, + childExpectedPolicies, + node, + pq, + pOid.Id, + false); + node.AddChild(child); + policyNodes[index].Add(child); + + return true; + } + } + + return false; + } + + internal static void ProcessCertD1ii( + int index, + IList[] policyNodes, + DerObjectIdentifier _poid, + ISet _pq) + { + IList policyNodeVec = policyNodes[index - 1]; + + for (int j = 0; j < policyNodeVec.Count; j++) + { + PkixPolicyNode _node = (PkixPolicyNode)policyNodeVec[j]; + + if (ANY_POLICY.Equals(_node.ValidPolicy)) + { + ISet _childExpectedPolicies = new HashSet(); + _childExpectedPolicies.Add(_poid.Id); + + PkixPolicyNode _child = new PkixPolicyNode(Platform.CreateArrayList(), + index, + _childExpectedPolicies, + _node, + _pq, + _poid.Id, + false); + _node.AddChild(_child); + policyNodes[index].Add(_child); + return; + } + } + } + + /** + * Find the issuer certificates of a given certificate. + * + * @param cert + * The certificate for which an issuer should be found. + * @param pkixParams + * @return A Collection object containing the issuer + * X509Certificates. Never null. + * + * @exception Exception + * if an error occurs. + */ + internal static ICollection FindIssuerCerts( + X509Certificate cert, + PkixBuilderParameters pkixParams) + { + X509CertStoreSelector certSelect = new X509CertStoreSelector(); + ISet certs = new HashSet(); + try + { + certSelect.Subject = cert.IssuerDN; + } + catch (IOException ex) + { + throw new Exception( + "Subject criteria for certificate selector to find issuer certificate could not be set.", ex); + } + + try + { + certs.AddAll(PkixCertPathValidatorUtilities.FindCertificates(certSelect, pkixParams.GetStores())); + certs.AddAll(PkixCertPathValidatorUtilities.FindCertificates(certSelect, pkixParams.GetAdditionalStores())); + } + catch (Exception e) + { + throw new Exception("Issuer certificate cannot be searched.", e); + } + + return certs; + } + + /// + /// Extract the value of the given extension, if it exists. + /// + /// The extension object. + /// The object identifier to obtain. + /// Asn1Object + /// if the extension cannot be read. + internal static Asn1Object GetExtensionValue( + IX509Extension ext, + DerObjectIdentifier oid) + { + Asn1OctetString bytes = ext.GetExtensionValue(oid); + + if (bytes == null) + return null; + + return X509ExtensionUtilities.FromExtensionValue(bytes); + } + } +} diff --git a/bc-sharp-crypto/src/pkix/PkixCrlUtilities.cs b/bc-sharp-crypto/src/pkix/PkixCrlUtilities.cs new file mode 100644 index 0000000..c386b8a --- /dev/null +++ b/bc-sharp-crypto/src/pkix/PkixCrlUtilities.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Utilities.Collections; +using Org.BouncyCastle.X509; +using Org.BouncyCastle.X509.Store; + +namespace Org.BouncyCastle.Pkix +{ + public class PkixCrlUtilities + { + public virtual ISet FindCrls(X509CrlStoreSelector crlselect, PkixParameters paramsPkix, DateTime currentDate) + { + ISet initialSet = new HashSet(); + + // get complete CRL(s) + try + { + initialSet.AddAll(FindCrls(crlselect, paramsPkix.GetAdditionalStores())); + initialSet.AddAll(FindCrls(crlselect, paramsPkix.GetStores())); + } + catch (Exception e) + { + throw new Exception("Exception obtaining complete CRLs.", e); + } + + ISet finalSet = new HashSet(); + DateTime validityDate = currentDate; + + if (paramsPkix.Date != null) + { + validityDate = paramsPkix.Date.Value; + } + + // based on RFC 5280 6.3.3 + foreach (X509Crl crl in initialSet) + { + if (crl.NextUpdate.Value.CompareTo(validityDate) > 0) + { + X509Certificate cert = crlselect.CertificateChecking; + + if (cert != null) + { + if (crl.ThisUpdate.CompareTo(cert.NotAfter) < 0) + { + finalSet.Add(crl); + } + } + else + { + finalSet.Add(crl); + } + } + } + + return finalSet; + } + + public virtual ISet FindCrls(X509CrlStoreSelector crlselect, PkixParameters paramsPkix) + { + ISet completeSet = new HashSet(); + + // get complete CRL(s) + try + { + completeSet.AddAll(FindCrls(crlselect, paramsPkix.GetStores())); + } + catch (Exception e) + { + throw new Exception("Exception obtaining complete CRLs.", e); + } + + return completeSet; + } + + /// + /// crl checking + /// Return a Collection of all CRLs found in the X509Store's that are + /// matching the crlSelect criteriums. + /// + /// a {@link X509CRLStoreSelector} object that will be used + /// to select the CRLs + /// a List containing only {@link org.bouncycastle.x509.X509Store + /// X509Store} objects. These are used to search for CRLs + /// a Collection of all found {@link X509CRL X509CRL} objects. May be + /// empty but never null. + /// + private ICollection FindCrls(X509CrlStoreSelector crlSelect, IList crlStores) + { + ISet crls = new HashSet(); + + Exception lastException = null; + bool foundValidStore = false; + + foreach (IX509Store store in crlStores) + { + try + { + crls.AddAll(store.GetMatches(crlSelect)); + foundValidStore = true; + } + catch (X509StoreException e) + { + lastException = new Exception("Exception searching in X.509 CRL store.", e); + } + } + + if (!foundValidStore && lastException != null) + throw lastException; + + return crls; + } + } +} diff --git a/bc-sharp-crypto/src/pkix/PkixNameConstraintValidator.cs b/bc-sharp-crypto/src/pkix/PkixNameConstraintValidator.cs new file mode 100644 index 0000000..f4ae739 --- /dev/null +++ b/bc-sharp-crypto/src/pkix/PkixNameConstraintValidator.cs @@ -0,0 +1,1939 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; + +namespace Org.BouncyCastle.Pkix +{ + public class PkixNameConstraintValidator + { + private ISet excludedSubtreesDN = new HashSet(); + + private ISet excludedSubtreesDNS = new HashSet(); + + private ISet excludedSubtreesEmail = new HashSet(); + + private ISet excludedSubtreesURI = new HashSet(); + + private ISet excludedSubtreesIP = new HashSet(); + + private ISet permittedSubtreesDN; + + private ISet permittedSubtreesDNS; + + private ISet permittedSubtreesEmail; + + private ISet permittedSubtreesURI; + + private ISet permittedSubtreesIP; + + public PkixNameConstraintValidator() + { + } + + private static bool WithinDNSubtree( + Asn1Sequence dns, + Asn1Sequence subtree) + { + if (subtree.Count < 1) + { + return false; + } + + if (subtree.Count > dns.Count) + { + return false; + } + + for (int j = subtree.Count - 1; j >= 0; j--) + { + if (!(subtree[j].Equals(dns[j]))) + { + return false; + } + } + + return true; + } + + public void CheckPermittedDN(Asn1Sequence dns) + //throws PkixNameConstraintValidatorException + { + CheckPermittedDN(permittedSubtreesDN, dns); + } + + public void CheckExcludedDN(Asn1Sequence dns) + //throws PkixNameConstraintValidatorException + { + CheckExcludedDN(excludedSubtreesDN, dns); + } + + private void CheckPermittedDN(ISet permitted, Asn1Sequence dns) + //throws PkixNameConstraintValidatorException + { + if (permitted == null) + { + return; + } + + if ((permitted.Count == 0) && dns.Count == 0) + { + return; + } + + IEnumerator it = permitted.GetEnumerator(); + + while (it.MoveNext()) + { + Asn1Sequence subtree = (Asn1Sequence)it.Current; + + if (WithinDNSubtree(dns, subtree)) + { + return; + } + } + + throw new PkixNameConstraintValidatorException( + "Subject distinguished name is not from a permitted subtree"); + } + + private void CheckExcludedDN(ISet excluded, Asn1Sequence dns) + //throws PkixNameConstraintValidatorException + { + if (excluded.IsEmpty) + { + return; + } + + IEnumerator it = excluded.GetEnumerator(); + + while (it.MoveNext()) + { + Asn1Sequence subtree = (Asn1Sequence)it.Current; + + if (WithinDNSubtree(dns, subtree)) + { + throw new PkixNameConstraintValidatorException( + "Subject distinguished name is from an excluded subtree"); + } + } + } + + private ISet IntersectDN(ISet permitted, ISet dns) + { + ISet intersect = new HashSet(); + for (IEnumerator it = dns.GetEnumerator(); it.MoveNext(); ) + { + Asn1Sequence dn = Asn1Sequence.GetInstance(((GeneralSubtree)it + .Current).Base.Name.ToAsn1Object()); + if (permitted == null) + { + if (dn != null) + { + intersect.Add(dn); + } + } + else + { + IEnumerator _iter = permitted.GetEnumerator(); + while (_iter.MoveNext()) + { + Asn1Sequence subtree = (Asn1Sequence)_iter.Current; + + if (WithinDNSubtree(dn, subtree)) + { + intersect.Add(dn); + } + else if (WithinDNSubtree(subtree, dn)) + { + intersect.Add(subtree); + } + } + } + } + return intersect; + } + + private ISet UnionDN(ISet excluded, Asn1Sequence dn) + { + if (excluded.IsEmpty) + { + if (dn == null) + { + return excluded; + } + excluded.Add(dn); + + return excluded; + } + else + { + ISet intersect = new HashSet(); + + IEnumerator it = excluded.GetEnumerator(); + while (it.MoveNext()) + { + Asn1Sequence subtree = (Asn1Sequence)it.Current; + + if (WithinDNSubtree(dn, subtree)) + { + intersect.Add(subtree); + } + else if (WithinDNSubtree(subtree, dn)) + { + intersect.Add(dn); + } + else + { + intersect.Add(subtree); + intersect.Add(dn); + } + } + + return intersect; + } + } + + private ISet IntersectEmail(ISet permitted, ISet emails) + { + ISet intersect = new HashSet(); + for (IEnumerator it = emails.GetEnumerator(); it.MoveNext(); ) + { + String email = ExtractNameAsString(((GeneralSubtree)it.Current) + .Base); + + if (permitted == null) + { + if (email != null) + { + intersect.Add(email); + } + } + else + { + IEnumerator it2 = permitted.GetEnumerator(); + while (it2.MoveNext()) + { + String _permitted = (String)it2.Current; + + intersectEmail(email, _permitted, intersect); + } + } + } + return intersect; + } + + private ISet UnionEmail(ISet excluded, String email) + { + if (excluded.IsEmpty) + { + if (email == null) + { + return excluded; + } + excluded.Add(email); + return excluded; + } + else + { + ISet union = new HashSet(); + + IEnumerator it = excluded.GetEnumerator(); + while (it.MoveNext()) + { + String _excluded = (String)it.Current; + + unionEmail(_excluded, email, union); + } + + return union; + } + } + + /** + * Returns the intersection of the permitted IP ranges in + * permitted with ip. + * + * @param permitted A Set of permitted IP addresses with + * their subnet mask as byte arrays. + * @param ips The IP address with its subnet mask. + * @return The Set of permitted IP ranges intersected with + * ip. + */ + private ISet IntersectIP(ISet permitted, ISet ips) + { + ISet intersect = new HashSet(); + for (IEnumerator it = ips.GetEnumerator(); it.MoveNext(); ) + { + byte[] ip = Asn1OctetString.GetInstance( + ((GeneralSubtree)it.Current).Base.Name).GetOctets(); + if (permitted == null) + { + if (ip != null) + { + intersect.Add(ip); + } + } + else + { + IEnumerator it2 = permitted.GetEnumerator(); + while (it2.MoveNext()) + { + byte[] _permitted = (byte[])it2.Current; + intersect.AddAll(IntersectIPRange(_permitted, ip)); + } + } + } + return intersect; + } + + /** + * Returns the union of the excluded IP ranges in excluded + * with ip. + * + * @param excluded A Set of excluded IP addresses with their + * subnet mask as byte arrays. + * @param ip The IP address with its subnet mask. + * @return The Set of excluded IP ranges unified with + * ip as byte arrays. + */ + private ISet UnionIP(ISet excluded, byte[] ip) + { + if (excluded.IsEmpty) + { + if (ip == null) + { + return excluded; + } + excluded.Add(ip); + + return excluded; + } + else + { + ISet union = new HashSet(); + + IEnumerator it = excluded.GetEnumerator(); + while (it.MoveNext()) + { + byte[] _excluded = (byte[])it.Current; + union.AddAll(UnionIPRange(_excluded, ip)); + } + + return union; + } + } + + /** + * Calculates the union if two IP ranges. + * + * @param ipWithSubmask1 The first IP address with its subnet mask. + * @param ipWithSubmask2 The second IP address with its subnet mask. + * @return A Set with the union of both addresses. + */ + private ISet UnionIPRange(byte[] ipWithSubmask1, byte[] ipWithSubmask2) + { + ISet set = new HashSet(); + + // difficult, adding always all IPs is not wrong + if (Org.BouncyCastle.Utilities.Arrays.AreEqual(ipWithSubmask1, ipWithSubmask2)) + { + set.Add(ipWithSubmask1); + } + else + { + set.Add(ipWithSubmask1); + set.Add(ipWithSubmask2); + } + return set; + } + + /** + * Calculates the interesction if two IP ranges. + * + * @param ipWithSubmask1 The first IP address with its subnet mask. + * @param ipWithSubmask2 The second IP address with its subnet mask. + * @return A Set with the single IP address with its subnet + * mask as a byte array or an empty Set. + */ + private ISet IntersectIPRange(byte[] ipWithSubmask1, byte[] ipWithSubmask2) + { + if (ipWithSubmask1.Length != ipWithSubmask2.Length) + { + //Collections.EMPTY_SET; + return new HashSet(); + } + + byte[][] temp = ExtractIPsAndSubnetMasks(ipWithSubmask1, ipWithSubmask2); + byte[] ip1 = temp[0]; + byte[] subnetmask1 = temp[1]; + byte[] ip2 = temp[2]; + byte[] subnetmask2 = temp[3]; + + byte[][] minMax = MinMaxIPs(ip1, subnetmask1, ip2, subnetmask2); + byte[] min; + byte[] max; + max = Min(minMax[1], minMax[3]); + min = Max(minMax[0], minMax[2]); + + // minimum IP address must be bigger than max + if (CompareTo(min, max) == 1) + { + //return Collections.EMPTY_SET; + return new HashSet(); + } + // OR keeps all significant bits + byte[] ip = Or(minMax[0], minMax[2]); + byte[] subnetmask = Or(subnetmask1, subnetmask2); + + //return new HashSet( ICollectionsingleton(IpWithSubnetMask(ip, subnetmask)); + ISet hs = new HashSet(); + hs.Add(IpWithSubnetMask(ip, subnetmask)); + + return hs; + } + + /** + * Concatenates the IP address with its subnet mask. + * + * @param ip The IP address. + * @param subnetMask Its subnet mask. + * @return The concatenated IP address with its subnet mask. + */ + private byte[] IpWithSubnetMask(byte[] ip, byte[] subnetMask) + { + int ipLength = ip.Length; + byte[] temp = new byte[ipLength * 2]; + Array.Copy(ip, 0, temp, 0, ipLength); + Array.Copy(subnetMask, 0, temp, ipLength, ipLength); + return temp; + } + + /** + * Splits the IP addresses and their subnet mask. + * + * @param ipWithSubmask1 The first IP address with the subnet mask. + * @param ipWithSubmask2 The second IP address with the subnet mask. + * @return An array with two elements. Each element contains the IP address + * and the subnet mask in this order. + */ + private byte[][] ExtractIPsAndSubnetMasks( + byte[] ipWithSubmask1, + byte[] ipWithSubmask2) + { + int ipLength = ipWithSubmask1.Length / 2; + byte[] ip1 = new byte[ipLength]; + byte[] subnetmask1 = new byte[ipLength]; + Array.Copy(ipWithSubmask1, 0, ip1, 0, ipLength); + Array.Copy(ipWithSubmask1, ipLength, subnetmask1, 0, ipLength); + + byte[] ip2 = new byte[ipLength]; + byte[] subnetmask2 = new byte[ipLength]; + Array.Copy(ipWithSubmask2, 0, ip2, 0, ipLength); + Array.Copy(ipWithSubmask2, ipLength, subnetmask2, 0, ipLength); + return new byte[][] + {ip1, subnetmask1, ip2, subnetmask2}; + } + + /** + * Based on the two IP addresses and their subnet masks the IP range is + * computed for each IP address - subnet mask pair and returned as the + * minimum IP address and the maximum address of the range. + * + * @param ip1 The first IP address. + * @param subnetmask1 The subnet mask of the first IP address. + * @param ip2 The second IP address. + * @param subnetmask2 The subnet mask of the second IP address. + * @return A array with two elements. The first/second element contains the + * min and max IP address of the first/second IP address and its + * subnet mask. + */ + private byte[][] MinMaxIPs( + byte[] ip1, + byte[] subnetmask1, + byte[] ip2, + byte[] subnetmask2) + { + int ipLength = ip1.Length; + byte[] min1 = new byte[ipLength]; + byte[] max1 = new byte[ipLength]; + + byte[] min2 = new byte[ipLength]; + byte[] max2 = new byte[ipLength]; + + for (int i = 0; i < ipLength; i++) + { + min1[i] = (byte)(ip1[i] & subnetmask1[i]); + max1[i] = (byte)(ip1[i] & subnetmask1[i] | ~subnetmask1[i]); + + min2[i] = (byte)(ip2[i] & subnetmask2[i]); + max2[i] = (byte)(ip2[i] & subnetmask2[i] | ~subnetmask2[i]); + } + + return new byte[][] { min1, max1, min2, max2 }; + } + + private void CheckPermittedEmail(ISet permitted, String email) + //throws PkixNameConstraintValidatorException + { + if (permitted == null) + { + return; + } + + IEnumerator it = permitted.GetEnumerator(); + + while (it.MoveNext()) + { + String str = ((String)it.Current); + + if (EmailIsConstrained(email, str)) + { + return; + } + } + + if (email.Length == 0 && permitted.Count == 0) + { + return; + } + + throw new PkixNameConstraintValidatorException( + "Subject email address is not from a permitted subtree."); + } + + private void CheckExcludedEmail(ISet excluded, String email) + //throws PkixNameConstraintValidatorException + { + if (excluded.IsEmpty) + { + return; + } + + IEnumerator it = excluded.GetEnumerator(); + + while (it.MoveNext()) + { + String str = (String)it.Current; + + if (EmailIsConstrained(email, str)) + { + throw new PkixNameConstraintValidatorException( + "Email address is from an excluded subtree."); + } + } + } + + /** + * Checks if the IP ip is included in the permitted ISet + * permitted. + * + * @param permitted A Set of permitted IP addresses with + * their subnet mask as byte arrays. + * @param ip The IP address. + * @throws PkixNameConstraintValidatorException + * if the IP is not permitted. + */ + private void CheckPermittedIP(ISet permitted, byte[] ip) + //throws PkixNameConstraintValidatorException + { + if (permitted == null) + { + return; + } + + IEnumerator it = permitted.GetEnumerator(); + + while (it.MoveNext()) + { + byte[] ipWithSubnet = (byte[])it.Current; + + if (IsIPConstrained(ip, ipWithSubnet)) + { + return; + } + } + if (ip.Length == 0 && permitted.Count == 0) + { + return; + } + throw new PkixNameConstraintValidatorException( + "IP is not from a permitted subtree."); + } + + /** + * Checks if the IP ip is included in the excluded ISet + * excluded. + * + * @param excluded A Set of excluded IP addresses with their + * subnet mask as byte arrays. + * @param ip The IP address. + * @throws PkixNameConstraintValidatorException + * if the IP is excluded. + */ + private void checkExcludedIP(ISet excluded, byte[] ip) + //throws PkixNameConstraintValidatorException + { + if (excluded.IsEmpty) + { + return; + } + + IEnumerator it = excluded.GetEnumerator(); + + while (it.MoveNext()) + { + byte[] ipWithSubnet = (byte[])it.Current; + + if (IsIPConstrained(ip, ipWithSubnet)) + { + throw new PkixNameConstraintValidatorException( + "IP is from an excluded subtree."); + } + } + } + + /** + * Checks if the IP address ip is constrained by + * constraint. + * + * @param ip The IP address. + * @param constraint The constraint. This is an IP address concatenated with + * its subnetmask. + * @return true if constrained, false + * otherwise. + */ + private bool IsIPConstrained(byte[] ip, byte[] constraint) + { + int ipLength = ip.Length; + + if (ipLength != (constraint.Length / 2)) + { + return false; + } + + byte[] subnetMask = new byte[ipLength]; + Array.Copy(constraint, ipLength, subnetMask, 0, ipLength); + + byte[] permittedSubnetAddress = new byte[ipLength]; + + byte[] ipSubnetAddress = new byte[ipLength]; + + // the resulting IP address by applying the subnet mask + for (int i = 0; i < ipLength; i++) + { + permittedSubnetAddress[i] = (byte)(constraint[i] & subnetMask[i]); + ipSubnetAddress[i] = (byte)(ip[i] & subnetMask[i]); + } + + return Org.BouncyCastle.Utilities.Arrays.AreEqual(permittedSubnetAddress, ipSubnetAddress); + } + + private bool EmailIsConstrained(String email, String constraint) + { + String sub = email.Substring(email.IndexOf('@') + 1); + // a particular mailbox + if (constraint.IndexOf('@') != -1) + { + if (Platform.ToUpperInvariant(email).Equals(Platform.ToUpperInvariant(constraint))) + { + return true; + } + } + // on particular host + else if (!(constraint[0].Equals('.'))) + { + if (Platform.ToUpperInvariant(sub).Equals(Platform.ToUpperInvariant(constraint))) + { + return true; + } + } + // address in sub domain + else if (WithinDomain(sub, constraint)) + { + return true; + } + return false; + } + + private bool WithinDomain(String testDomain, String domain) + { + String tempDomain = domain; + if (Platform.StartsWith(tempDomain, ".")) + { + tempDomain = tempDomain.Substring(1); + } + String[] domainParts = tempDomain.Split('.'); // Strings.split(tempDomain, '.'); + String[] testDomainParts = testDomain.Split('.'); // Strings.split(testDomain, '.'); + + // must have at least one subdomain + if (testDomainParts.Length <= domainParts.Length) + { + return false; + } + + int d = testDomainParts.Length - domainParts.Length; + for (int i = -1; i < domainParts.Length; i++) + { + if (i == -1) + { + if (testDomainParts[i + d].Equals("")) + { + return false; + } + } + else if (!Platform.EqualsIgnoreCase(testDomainParts[i + d], domainParts[i])) + { + return false; + } + } + return true; + } + + private void CheckPermittedDNS(ISet permitted, String dns) + //throws PkixNameConstraintValidatorException + { + if (permitted == null) + { + return; + } + + IEnumerator it = permitted.GetEnumerator(); + + while (it.MoveNext()) + { + String str = ((String)it.Current); + + // is sub domain + if (WithinDomain(dns, str) + || Platform.ToUpperInvariant(dns).Equals(Platform.ToUpperInvariant(str))) + { + return; + } + } + if (dns.Length == 0 && permitted.Count == 0) + { + return; + } + throw new PkixNameConstraintValidatorException( + "DNS is not from a permitted subtree."); + } + + private void checkExcludedDNS(ISet excluded, String dns) + // throws PkixNameConstraintValidatorException + { + if (excluded.IsEmpty) + { + return; + } + + IEnumerator it = excluded.GetEnumerator(); + + while (it.MoveNext()) + { + String str = ((String)it.Current); + + // is sub domain or the same + if (WithinDomain(dns, str) || Platform.EqualsIgnoreCase(dns, str)) + { + throw new PkixNameConstraintValidatorException( + "DNS is from an excluded subtree."); + } + } + } + + /** + * The common part of email1 and email2 is + * added to the union union. If email1 and + * email2 have nothing in common they are added both. + * + * @param email1 Email address constraint 1. + * @param email2 Email address constraint 2. + * @param union The union. + */ + private void unionEmail(String email1, String email2, ISet union) + { + // email1 is a particular address + if (email1.IndexOf('@') != -1) + { + String _sub = email1.Substring(email1.IndexOf('@') + 1); + // both are a particular mailbox + if (email2.IndexOf('@') != -1) + { + if (Platform.EqualsIgnoreCase(email1, email2)) + { + union.Add(email1); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + // email2 specifies a domain + else if (Platform.StartsWith(email2, ".")) + { + if (WithinDomain(_sub, email2)) + { + union.Add(email2); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + // email2 specifies a particular host + else + { + if (Platform.EqualsIgnoreCase(_sub, email2)) + { + union.Add(email2); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + } + // email1 specifies a domain + else if (Platform.StartsWith(email1, ".")) + { + if (email2.IndexOf('@') != -1) + { + String _sub = email2.Substring(email1.IndexOf('@') + 1); + if (WithinDomain(_sub, email1)) + { + union.Add(email1); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + // email2 specifies a domain + else if (Platform.StartsWith(email2, ".")) + { + if (WithinDomain(email1, email2) || Platform.EqualsIgnoreCase(email1, email2)) + { + union.Add(email2); + } + else if (WithinDomain(email2, email1)) + { + union.Add(email1); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + else + { + if (WithinDomain(email2, email1)) + { + union.Add(email1); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + } + // email specifies a host + else + { + if (email2.IndexOf('@') != -1) + { + String _sub = email2.Substring(email1.IndexOf('@') + 1); + if (Platform.EqualsIgnoreCase(_sub, email1)) + { + union.Add(email1); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + // email2 specifies a domain + else if (Platform.StartsWith(email2, ".")) + { + if (WithinDomain(email1, email2)) + { + union.Add(email2); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + // email2 specifies a particular host + else + { + if (Platform.EqualsIgnoreCase(email1, email2)) + { + union.Add(email1); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + } + } + + private void unionURI(String email1, String email2, ISet union) + { + // email1 is a particular address + if (email1.IndexOf('@') != -1) + { + String _sub = email1.Substring(email1.IndexOf('@') + 1); + // both are a particular mailbox + if (email2.IndexOf('@') != -1) + { + if (Platform.EqualsIgnoreCase(email1, email2)) + { + union.Add(email1); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + // email2 specifies a domain + else if (Platform.StartsWith(email2, ".")) + { + if (WithinDomain(_sub, email2)) + { + union.Add(email2); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + // email2 specifies a particular host + else + { + if (Platform.EqualsIgnoreCase(_sub, email2)) + { + union.Add(email2); + } + else + { + union.Add(email1); + union.Add(email2); + + } + } + } + // email1 specifies a domain + else if (Platform.StartsWith(email1, ".")) + { + if (email2.IndexOf('@') != -1) + { + String _sub = email2.Substring(email1.IndexOf('@') + 1); + if (WithinDomain(_sub, email1)) + { + union.Add(email1); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + // email2 specifies a domain + else if (Platform.StartsWith(email2, ".")) + { + if (WithinDomain(email1, email2) || Platform.EqualsIgnoreCase(email1, email2)) + { + union.Add(email2); + } + else if (WithinDomain(email2, email1)) + { + union.Add(email1); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + else + { + if (WithinDomain(email2, email1)) + { + union.Add(email1); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + } + // email specifies a host + else + { + if (email2.IndexOf('@') != -1) + { + String _sub = email2.Substring(email1.IndexOf('@') + 1); + if (Platform.EqualsIgnoreCase(_sub, email1)) + { + union.Add(email1); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + // email2 specifies a domain + else if (Platform.StartsWith(email2, ".")) + { + if (WithinDomain(email1, email2)) + { + union.Add(email2); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + // email2 specifies a particular host + else + { + if (Platform.EqualsIgnoreCase(email1, email2)) + { + union.Add(email1); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + } + } + + private ISet intersectDNS(ISet permitted, ISet dnss) + { + ISet intersect = new HashSet(); + for (IEnumerator it = dnss.GetEnumerator(); it.MoveNext(); ) + { + String dns = ExtractNameAsString(((GeneralSubtree)it.Current) + .Base); + if (permitted == null) + { + if (dns != null) + { + intersect.Add(dns); + } + } + else + { + IEnumerator _iter = permitted.GetEnumerator(); + while (_iter.MoveNext()) + { + String _permitted = (String)_iter.Current; + + if (WithinDomain(_permitted, dns)) + { + intersect.Add(_permitted); + } + else if (WithinDomain(dns, _permitted)) + { + intersect.Add(dns); + } + } + } + } + + return intersect; + } + + protected ISet unionDNS(ISet excluded, String dns) + { + if (excluded.IsEmpty) + { + if (dns == null) + { + return excluded; + } + excluded.Add(dns); + + return excluded; + } + else + { + ISet union = new HashSet(); + + IEnumerator _iter = excluded.GetEnumerator(); + while (_iter.MoveNext()) + { + String _permitted = (String)_iter.Current; + + if (WithinDomain(_permitted, dns)) + { + union.Add(dns); + } + else if (WithinDomain(dns, _permitted)) + { + union.Add(_permitted); + } + else + { + union.Add(_permitted); + union.Add(dns); + } + } + + return union; + } + } + + /** + * The most restricting part from email1 and + * email2 is added to the intersection intersect. + * + * @param email1 Email address constraint 1. + * @param email2 Email address constraint 2. + * @param intersect The intersection. + */ + private void intersectEmail(String email1, String email2, ISet intersect) + { + // email1 is a particular address + if (email1.IndexOf('@') != -1) + { + String _sub = email1.Substring(email1.IndexOf('@') + 1); + // both are a particular mailbox + if (email2.IndexOf('@') != -1) + { + if (Platform.EqualsIgnoreCase(email1, email2)) + { + intersect.Add(email1); + } + } + // email2 specifies a domain + else if (Platform.StartsWith(email2, ".")) + { + if (WithinDomain(_sub, email2)) + { + intersect.Add(email1); + } + } + // email2 specifies a particular host + else + { + if (Platform.EqualsIgnoreCase(_sub, email2)) + { + intersect.Add(email1); + } + } + } + // email specifies a domain + else if (Platform.StartsWith(email1, ".")) + { + if (email2.IndexOf('@') != -1) + { + String _sub = email2.Substring(email1.IndexOf('@') + 1); + if (WithinDomain(_sub, email1)) + { + intersect.Add(email2); + } + } + // email2 specifies a domain + else if (Platform.StartsWith(email2, ".")) + { + if (WithinDomain(email1, email2) || Platform.EqualsIgnoreCase(email1, email2)) + { + intersect.Add(email1); + } + else if (WithinDomain(email2, email1)) + { + intersect.Add(email2); + } + } + else + { + if (WithinDomain(email2, email1)) + { + intersect.Add(email2); + } + } + } + // email1 specifies a host + else + { + if (email2.IndexOf('@') != -1) + { + String _sub = email2.Substring(email2.IndexOf('@') + 1); + if (Platform.EqualsIgnoreCase(_sub, email1)) + { + intersect.Add(email2); + } + } + // email2 specifies a domain + else if (Platform.StartsWith(email2, ".")) + { + if (WithinDomain(email1, email2)) + { + intersect.Add(email1); + } + } + // email2 specifies a particular host + else + { + if (Platform.EqualsIgnoreCase(email1, email2)) + { + intersect.Add(email1); + } + } + } + } + + private void checkExcludedURI(ISet excluded, String uri) + // throws PkixNameConstraintValidatorException + { + if (excluded.IsEmpty) + { + return; + } + + IEnumerator it = excluded.GetEnumerator(); + + while (it.MoveNext()) + { + String str = ((String)it.Current); + + if (IsUriConstrained(uri, str)) + { + throw new PkixNameConstraintValidatorException( + "URI is from an excluded subtree."); + } + } + } + + private ISet intersectURI(ISet permitted, ISet uris) + { + ISet intersect = new HashSet(); + for (IEnumerator it = uris.GetEnumerator(); it.MoveNext(); ) + { + String uri = ExtractNameAsString(((GeneralSubtree)it.Current) + .Base); + if (permitted == null) + { + if (uri != null) + { + intersect.Add(uri); + } + } + else + { + IEnumerator _iter = permitted.GetEnumerator(); + while (_iter.MoveNext()) + { + String _permitted = (String)_iter.Current; + intersectURI(_permitted, uri, intersect); + } + } + } + return intersect; + } + + private ISet unionURI(ISet excluded, String uri) + { + if (excluded.IsEmpty) + { + if (uri == null) + { + return excluded; + } + excluded.Add(uri); + + return excluded; + } + else + { + ISet union = new HashSet(); + + IEnumerator _iter = excluded.GetEnumerator(); + while (_iter.MoveNext()) + { + String _excluded = (String)_iter.Current; + + unionURI(_excluded, uri, union); + } + + return union; + } + } + + private void intersectURI(String email1, String email2, ISet intersect) + { + // email1 is a particular address + if (email1.IndexOf('@') != -1) + { + String _sub = email1.Substring(email1.IndexOf('@') + 1); + // both are a particular mailbox + if (email2.IndexOf('@') != -1) + { + if (Platform.EqualsIgnoreCase(email1, email2)) + { + intersect.Add(email1); + } + } + // email2 specifies a domain + else if (Platform.StartsWith(email2, ".")) + { + if (WithinDomain(_sub, email2)) + { + intersect.Add(email1); + } + } + // email2 specifies a particular host + else + { + if (Platform.EqualsIgnoreCase(_sub, email2)) + { + intersect.Add(email1); + } + } + } + // email specifies a domain + else if (Platform.StartsWith(email1, ".")) + { + if (email2.IndexOf('@') != -1) + { + String _sub = email2.Substring(email1.IndexOf('@') + 1); + if (WithinDomain(_sub, email1)) + { + intersect.Add(email2); + } + } + // email2 specifies a domain + else if (Platform.StartsWith(email2, ".")) + { + if (WithinDomain(email1, email2) || Platform.EqualsIgnoreCase(email1, email2)) + { + intersect.Add(email1); + } + else if (WithinDomain(email2, email1)) + { + intersect.Add(email2); + } + } + else + { + if (WithinDomain(email2, email1)) + { + intersect.Add(email2); + } + } + } + // email1 specifies a host + else + { + if (email2.IndexOf('@') != -1) + { + String _sub = email2.Substring(email2.IndexOf('@') + 1); + if (Platform.EqualsIgnoreCase(_sub, email1)) + { + intersect.Add(email2); + } + } + // email2 specifies a domain + else if (Platform.StartsWith(email2, ".")) + { + if (WithinDomain(email1, email2)) + { + intersect.Add(email1); + } + } + // email2 specifies a particular host + else + { + if (Platform.EqualsIgnoreCase(email1, email2)) + { + intersect.Add(email1); + } + } + } + } + + private void CheckPermittedURI(ISet permitted, String uri) + // throws PkixNameConstraintValidatorException + { + if (permitted == null) + { + return; + } + + IEnumerator it = permitted.GetEnumerator(); + + while (it.MoveNext()) + { + String str = ((String)it.Current); + + if (IsUriConstrained(uri, str)) + { + return; + } + } + if (uri.Length == 0 && permitted.Count == 0) + { + return; + } + throw new PkixNameConstraintValidatorException( + "URI is not from a permitted subtree."); + } + + private bool IsUriConstrained(String uri, String constraint) + { + String host = ExtractHostFromURL(uri); + // a host + if (!Platform.StartsWith(constraint, ".")) + { + if (Platform.EqualsIgnoreCase(host, constraint)) + { + return true; + } + } + + // in sub domain or domain + else if (WithinDomain(host, constraint)) + { + return true; + } + + return false; + } + + private static String ExtractHostFromURL(String url) + { + // see RFC 1738 + // remove ':' after protocol, e.g. http: + String sub = url.Substring(url.IndexOf(':') + 1); + // extract host from Common Internet Scheme Syntax, e.g. http:// + int idxOfSlashes = Platform.IndexOf(sub, "//"); + if (idxOfSlashes != -1) + { + sub = sub.Substring(idxOfSlashes + 2); + } + // first remove port, e.g. http://test.com:21 + if (sub.LastIndexOf(':') != -1) + { + sub = sub.Substring(0, sub.LastIndexOf(':')); + } + // remove user and password, e.g. http://john:password@test.com + sub = sub.Substring(sub.IndexOf(':') + 1); + sub = sub.Substring(sub.IndexOf('@') + 1); + // remove local parts, e.g. http://test.com/bla + if (sub.IndexOf('/') != -1) + { + sub = sub.Substring(0, sub.IndexOf('/')); + } + return sub; + } + + /** + * Checks if the given GeneralName is in the permitted ISet. + * + * @param name The GeneralName + * @throws PkixNameConstraintValidatorException + * If the name + */ + public void checkPermitted(GeneralName name) + // throws PkixNameConstraintValidatorException + { + switch (name.TagNo) + { + case 1: + CheckPermittedEmail(permittedSubtreesEmail, + ExtractNameAsString(name)); + break; + case 2: + CheckPermittedDNS(permittedSubtreesDNS, DerIA5String.GetInstance( + name.Name).GetString()); + break; + case 4: + CheckPermittedDN(Asn1Sequence.GetInstance(name.Name.ToAsn1Object())); + break; + case 6: + CheckPermittedURI(permittedSubtreesURI, DerIA5String.GetInstance( + name.Name).GetString()); + break; + case 7: + byte[] ip = Asn1OctetString.GetInstance(name.Name).GetOctets(); + + CheckPermittedIP(permittedSubtreesIP, ip); + break; + } + } + + /** + * Check if the given GeneralName is contained in the excluded ISet. + * + * @param name The GeneralName. + * @throws PkixNameConstraintValidatorException + * If the name is + * excluded. + */ + public void checkExcluded(GeneralName name) + // throws PkixNameConstraintValidatorException + { + switch (name.TagNo) + { + case 1: + CheckExcludedEmail(excludedSubtreesEmail, ExtractNameAsString(name)); + break; + case 2: + checkExcludedDNS(excludedSubtreesDNS, DerIA5String.GetInstance( + name.Name).GetString()); + break; + case 4: + CheckExcludedDN(Asn1Sequence.GetInstance(name.Name.ToAsn1Object())); + break; + case 6: + checkExcludedURI(excludedSubtreesURI, DerIA5String.GetInstance( + name.Name).GetString()); + break; + case 7: + byte[] ip = Asn1OctetString.GetInstance(name.Name).GetOctets(); + + checkExcludedIP(excludedSubtreesIP, ip); + break; + } + } + + /** + * Updates the permitted ISet of these name constraints with the intersection + * with the given subtree. + * + * @param permitted The permitted subtrees + */ + + public void IntersectPermittedSubtree(Asn1Sequence permitted) + { + IDictionary subtreesMap = Platform.CreateHashtable(); + + // group in ISets in a map ordered by tag no. + for (IEnumerator e = permitted.GetEnumerator(); e.MoveNext(); ) + { + GeneralSubtree subtree = GeneralSubtree.GetInstance(e.Current); + + int tagNo = subtree.Base.TagNo; + if (subtreesMap[tagNo] == null) + { + subtreesMap[tagNo] = new HashSet(); + } + + ((ISet)subtreesMap[tagNo]).Add(subtree); + } + + for (IEnumerator it = subtreesMap.GetEnumerator(); it.MoveNext(); ) + { + DictionaryEntry entry = (DictionaryEntry)it.Current; + + // go through all subtree groups + switch ((int)entry.Key ) + { + case 1: + permittedSubtreesEmail = IntersectEmail(permittedSubtreesEmail, + (ISet)entry.Value); + break; + case 2: + permittedSubtreesDNS = intersectDNS(permittedSubtreesDNS, + (ISet)entry.Value); + break; + case 4: + permittedSubtreesDN = IntersectDN(permittedSubtreesDN, + (ISet)entry.Value); + break; + case 6: + permittedSubtreesURI = intersectURI(permittedSubtreesURI, + (ISet)entry.Value); + break; + case 7: + permittedSubtreesIP = IntersectIP(permittedSubtreesIP, + (ISet)entry.Value); + break; + } + } + } + + private String ExtractNameAsString(GeneralName name) + { + return DerIA5String.GetInstance(name.Name).GetString(); + } + + public void IntersectEmptyPermittedSubtree(int nameType) + { + switch (nameType) + { + case 1: + permittedSubtreesEmail = new HashSet(); + break; + case 2: + permittedSubtreesDNS = new HashSet(); + break; + case 4: + permittedSubtreesDN = new HashSet(); + break; + case 6: + permittedSubtreesURI = new HashSet(); + break; + case 7: + permittedSubtreesIP = new HashSet(); + break; + } + } + + /** + * Adds a subtree to the excluded ISet of these name constraints. + * + * @param subtree A subtree with an excluded GeneralName. + */ + public void AddExcludedSubtree(GeneralSubtree subtree) + { + GeneralName subTreeBase = subtree.Base; + + switch (subTreeBase.TagNo) + { + case 1: + excludedSubtreesEmail = UnionEmail(excludedSubtreesEmail, + ExtractNameAsString(subTreeBase)); + break; + case 2: + excludedSubtreesDNS = unionDNS(excludedSubtreesDNS, + ExtractNameAsString(subTreeBase)); + break; + case 4: + excludedSubtreesDN = UnionDN(excludedSubtreesDN, + (Asn1Sequence)subTreeBase.Name.ToAsn1Object()); + break; + case 6: + excludedSubtreesURI = unionURI(excludedSubtreesURI, + ExtractNameAsString(subTreeBase)); + break; + case 7: + excludedSubtreesIP = UnionIP(excludedSubtreesIP, Asn1OctetString + .GetInstance(subTreeBase.Name).GetOctets()); + break; + } + } + + /** + * Returns the maximum IP address. + * + * @param ip1 The first IP address. + * @param ip2 The second IP address. + * @return The maximum IP address. + */ + private static byte[] Max(byte[] ip1, byte[] ip2) + { + for (int i = 0; i < ip1.Length; i++) + { + if ((ip1[i] & 0xFFFF) > (ip2[i] & 0xFFFF)) + { + return ip1; + } + } + return ip2; + } + + /** + * Returns the minimum IP address. + * + * @param ip1 The first IP address. + * @param ip2 The second IP address. + * @return The minimum IP address. + */ + private static byte[] Min(byte[] ip1, byte[] ip2) + { + for (int i = 0; i < ip1.Length; i++) + { + if ((ip1[i] & 0xFFFF) < (ip2[i] & 0xFFFF)) + { + return ip1; + } + } + return ip2; + } + + /** + * Compares IP address ip1 with ip2. If ip1 + * is equal to ip2 0 is returned. If ip1 is bigger 1 is returned, -1 + * otherwise. + * + * @param ip1 The first IP address. + * @param ip2 The second IP address. + * @return 0 if ip1 is equal to ip2, 1 if ip1 is bigger, -1 otherwise. + */ + private static int CompareTo(byte[] ip1, byte[] ip2) + { + if (Org.BouncyCastle.Utilities.Arrays.AreEqual(ip1, ip2)) + { + return 0; + } + if (Org.BouncyCastle.Utilities.Arrays.AreEqual(Max(ip1, ip2), ip1)) + { + return 1; + } + return -1; + } + + /** + * Returns the logical OR of the IP addresses ip1 and + * ip2. + * + * @param ip1 The first IP address. + * @param ip2 The second IP address. + * @return The OR of ip1 and ip2. + */ + private static byte[] Or(byte[] ip1, byte[] ip2) + { + byte[] temp = new byte[ip1.Length]; + for (int i = 0; i < ip1.Length; i++) + { + temp[i] = (byte)(ip1[i] | ip2[i]); + } + return temp; + } + + [Obsolete("Use GetHashCode instead")] + public int HashCode() + { + return GetHashCode(); + } + + public override int GetHashCode() + { + return HashCollection(excludedSubtreesDN) + + HashCollection(excludedSubtreesDNS) + + HashCollection(excludedSubtreesEmail) + + HashCollection(excludedSubtreesIP) + + HashCollection(excludedSubtreesURI) + + HashCollection(permittedSubtreesDN) + + HashCollection(permittedSubtreesDNS) + + HashCollection(permittedSubtreesEmail) + + HashCollection(permittedSubtreesIP) + + HashCollection(permittedSubtreesURI); + } + + private int HashCollection(ICollection coll) + { + if (coll == null) + { + return 0; + } + int hash = 0; + IEnumerator it1 = coll.GetEnumerator(); + while (it1.MoveNext()) + { + Object o = it1.Current; + if (o is byte[]) + { + hash += Org.BouncyCastle.Utilities.Arrays.GetHashCode((byte[])o); + } + else + { + hash += o.GetHashCode(); + } + } + return hash; + } + + public override bool Equals(Object o) + { + if (!(o is PkixNameConstraintValidator)) + return false; + + PkixNameConstraintValidator constraintValidator = (PkixNameConstraintValidator)o; + + return CollectionsAreEqual(constraintValidator.excludedSubtreesDN, excludedSubtreesDN) + && CollectionsAreEqual(constraintValidator.excludedSubtreesDNS, excludedSubtreesDNS) + && CollectionsAreEqual(constraintValidator.excludedSubtreesEmail, excludedSubtreesEmail) + && CollectionsAreEqual(constraintValidator.excludedSubtreesIP, excludedSubtreesIP) + && CollectionsAreEqual(constraintValidator.excludedSubtreesURI, excludedSubtreesURI) + && CollectionsAreEqual(constraintValidator.permittedSubtreesDN, permittedSubtreesDN) + && CollectionsAreEqual(constraintValidator.permittedSubtreesDNS, permittedSubtreesDNS) + && CollectionsAreEqual(constraintValidator.permittedSubtreesEmail, permittedSubtreesEmail) + && CollectionsAreEqual(constraintValidator.permittedSubtreesIP, permittedSubtreesIP) + && CollectionsAreEqual(constraintValidator.permittedSubtreesURI, permittedSubtreesURI); + } + + private bool CollectionsAreEqual(ICollection coll1, ICollection coll2) + { + if (coll1 == coll2) + { + return true; + } + if (coll1 == null || coll2 == null) + { + return false; + } + if (coll1.Count != coll2.Count) + { + return false; + } + IEnumerator it1 = coll1.GetEnumerator(); + + while (it1.MoveNext()) + { + Object a = it1.Current; + IEnumerator it2 = coll2.GetEnumerator(); + bool found = false; + while (it2.MoveNext()) + { + Object b = it2.Current; + if (SpecialEquals(a, b)) + { + found = true; + break; + } + } + if (!found) + { + return false; + } + } + return true; + } + + private bool SpecialEquals(Object o1, Object o2) + { + if (o1 == o2) + { + return true; + } + if (o1 == null || o2 == null) + { + return false; + } + if ((o1 is byte[]) && (o2 is byte[])) + { + return Org.BouncyCastle.Utilities.Arrays.AreEqual((byte[])o1, (byte[])o2); + } + else + { + return o1.Equals(o2); + } + } + + /** + * Stringifies an IPv4 or v6 address with subnet mask. + * + * @param ip The IP with subnet mask. + * @return The stringified IP address. + */ + private String StringifyIP(byte[] ip) + { + String temp = ""; + for (int i = 0; i < ip.Length / 2; i++) + { + //temp += Integer.toString(ip[i] & 0x00FF) + "."; + temp += (ip[i] & 0x00FF) + "."; + } + temp = temp.Substring(0, temp.Length - 1); + temp += "/"; + for (int i = ip.Length / 2; i < ip.Length; i++) + { + //temp += Integer.toString(ip[i] & 0x00FF) + "."; + temp += (ip[i] & 0x00FF) + "."; + } + temp = temp.Substring(0, temp.Length - 1); + return temp; + } + + private String StringifyIPCollection(ISet ips) + { + String temp = ""; + temp += "["; + for (IEnumerator it = ips.GetEnumerator(); it.MoveNext(); ) + { + temp += StringifyIP((byte[])it.Current) + ","; + } + if (temp.Length > 1) + { + temp = temp.Substring(0, temp.Length - 1); + } + temp += "]"; + + return temp; + } + + public override String ToString() + { + String temp = ""; + + temp += "permitted:\n"; + if (permittedSubtreesDN != null) + { + temp += "DN:\n"; + temp += permittedSubtreesDN.ToString() + "\n"; + } + if (permittedSubtreesDNS != null) + { + temp += "DNS:\n"; + temp += permittedSubtreesDNS.ToString() + "\n"; + } + if (permittedSubtreesEmail != null) + { + temp += "Email:\n"; + temp += permittedSubtreesEmail.ToString() + "\n"; + } + if (permittedSubtreesURI != null) + { + temp += "URI:\n"; + temp += permittedSubtreesURI.ToString() + "\n"; + } + if (permittedSubtreesIP != null) + { + temp += "IP:\n"; + temp += StringifyIPCollection(permittedSubtreesIP) + "\n"; + } + temp += "excluded:\n"; + if (!(excludedSubtreesDN.IsEmpty)) + { + temp += "DN:\n"; + temp += excludedSubtreesDN.ToString() + "\n"; + } + if (!excludedSubtreesDNS.IsEmpty) + { + temp += "DNS:\n"; + temp += excludedSubtreesDNS.ToString() + "\n"; + } + if (!excludedSubtreesEmail.IsEmpty) + { + temp += "Email:\n"; + temp += excludedSubtreesEmail.ToString() + "\n"; + } + if (!excludedSubtreesURI.IsEmpty) + { + temp += "URI:\n"; + temp += excludedSubtreesURI.ToString() + "\n"; + } + if (!excludedSubtreesIP.IsEmpty) + { + temp += "IP:\n"; + temp += StringifyIPCollection(excludedSubtreesIP) + "\n"; + } + return temp; + } + + } +} diff --git a/bc-sharp-crypto/src/pkix/PkixNameConstraintValidatorException.cs b/bc-sharp-crypto/src/pkix/PkixNameConstraintValidatorException.cs new file mode 100644 index 0000000..b187525 --- /dev/null +++ b/bc-sharp-crypto/src/pkix/PkixNameConstraintValidatorException.cs @@ -0,0 +1,16 @@ +using System; + +namespace Org.BouncyCastle.Pkix +{ +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT || PORTABLE) + [Serializable] +#endif + public class PkixNameConstraintValidatorException + : Exception + { + public PkixNameConstraintValidatorException(String msg) + : base(msg) + { + } + } +} diff --git a/bc-sharp-crypto/src/pkix/PkixParameters.cs b/bc-sharp-crypto/src/pkix/PkixParameters.cs new file mode 100644 index 0000000..01ed9d4 --- /dev/null +++ b/bc-sharp-crypto/src/pkix/PkixParameters.cs @@ -0,0 +1,893 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; +using Org.BouncyCastle.Utilities.Date; +using Org.BouncyCastle.X509.Store; + +namespace Org.BouncyCastle.Pkix +{ + /// + /// Summary description for PkixParameters. + /// + public class PkixParameters +// : ICertPathParameters + { + /** + * This is the default PKIX validity model. Actually there are two variants + * of this: The PKIX model and the modified PKIX model. The PKIX model + * verifies that all involved certificates must have been valid at the + * current time. The modified PKIX model verifies that all involved + * certificates were valid at the signing time. Both are indirectly choosen + * with the {@link PKIXParameters#setDate(java.util.Date)} method, so this + * methods sets the Date when all certificates must have been + * valid. + */ + public const int PkixValidityModel = 0; + + /** + * This model uses the following validity model. Each certificate must have + * been valid at the moment where is was used. That means the end + * certificate must have been valid at the time the signature was done. The + * CA certificate which signed the end certificate must have been valid, + * when the end certificate was signed. The CA (or Root CA) certificate must + * have been valid, when the CA certificate was signed and so on. So the + * {@link PKIXParameters#setDate(java.util.Date)} method sets the time, when + * the end certificate must have been valid.

    It is used e.g. + * in the German signature law. + */ + public const int ChainValidityModel = 1; + + private ISet trustAnchors; + private DateTimeObject date; + private IList certPathCheckers; + private bool revocationEnabled = true; + private ISet initialPolicies; + //private bool checkOnlyEECertificateCrl = false; + private bool explicitPolicyRequired = false; + private bool anyPolicyInhibited = false; + private bool policyMappingInhibited = false; + private bool policyQualifiersRejected = true; + private IX509Selector certSelector; + private IList stores; + private IX509Selector selector; + private bool additionalLocationsEnabled; + private IList additionalStores; + private ISet trustedACIssuers; + private ISet necessaryACAttributes; + private ISet prohibitedACAttributes; + private ISet attrCertCheckers; + private int validityModel = PkixValidityModel; + private bool useDeltas = false; + + /** + * Creates an instance of PKIXParameters with the specified Set of + * most-trusted CAs. Each element of the set is a TrustAnchor.
    + *
    + * Note that the Set is copied to protect against subsequent modifications. + * + * @param trustAnchors + * a Set of TrustAnchors + * + * @exception InvalidAlgorithmParameterException + * if the specified Set is empty + * (trustAnchors.isEmpty() == true) + * @exception NullPointerException + * if the specified Set is null + * @exception ClassCastException + * if any of the elements in the Set are not of type + * java.security.cert.TrustAnchor + */ + public PkixParameters( + ISet trustAnchors) + { + SetTrustAnchors(trustAnchors); + + this.initialPolicies = new HashSet(); + this.certPathCheckers = Platform.CreateArrayList(); + this.stores = Platform.CreateArrayList(); + this.additionalStores = Platform.CreateArrayList(); + this.trustedACIssuers = new HashSet(); + this.necessaryACAttributes = new HashSet(); + this.prohibitedACAttributes = new HashSet(); + this.attrCertCheckers = new HashSet(); + } + +// // TODO implement for other keystores (see Java build)? +// /** +// * Creates an instance of PKIXParameters that +// * populates the set of most-trusted CAs from the trusted +// * certificate entries contained in the specified KeyStore. +// * Only keystore entries that contain trusted X509Certificates +// * are considered; all other certificate types are ignored. +// * +// * @param keystore a KeyStore from which the set of +// * most-trusted CAs will be populated +// * @throws KeyStoreException if the keystore has not been initialized +// * @throws InvalidAlgorithmParameterException if the keystore does +// * not contain at least one trusted certificate entry +// * @throws NullPointerException if the keystore is null +// */ +// public PkixParameters( +// Pkcs12Store keystore) +//// throws KeyStoreException, InvalidAlgorithmParameterException +// { +// if (keystore == null) +// throw new ArgumentNullException("keystore"); +// ISet trustAnchors = new HashSet(); +// foreach (string alias in keystore.Aliases) +// { +// if (keystore.IsCertificateEntry(alias)) +// { +// X509CertificateEntry x509Entry = keystore.GetCertificate(alias); +// trustAnchors.Add(new TrustAnchor(x509Entry.Certificate, null)); +// } +// } +// SetTrustAnchors(trustAnchors); +// +// this.initialPolicies = new HashSet(); +// this.certPathCheckers = new ArrayList(); +// this.stores = new ArrayList(); +// this.additionalStores = new ArrayList(); +// this.trustedACIssuers = new HashSet(); +// this.necessaryACAttributes = new HashSet(); +// this.prohibitedACAttributes = new HashSet(); +// this.attrCertCheckers = new HashSet(); +// } + + public virtual bool IsRevocationEnabled + { + get { return revocationEnabled; } + set { revocationEnabled = value; } + } + + public virtual bool IsExplicitPolicyRequired + { + get { return explicitPolicyRequired; } + set { this.explicitPolicyRequired = value; } + } + + public virtual bool IsAnyPolicyInhibited + { + get { return anyPolicyInhibited; } + set { this.anyPolicyInhibited = value; } + } + + public virtual bool IsPolicyMappingInhibited + { + get { return policyMappingInhibited; } + set { this.policyMappingInhibited = value; } + } + + public virtual bool IsPolicyQualifiersRejected + { + get { return policyQualifiersRejected; } + set { this.policyQualifiersRejected = value; } + } + + //public bool IsCheckOnlyEECertificateCrl + //{ + // get { return this.checkOnlyEECertificateCrl; } + // set { this.checkOnlyEECertificateCrl = value; } + //} + + public virtual DateTimeObject Date + { + get { return this.date; } + set { this.date = value; } + } + + // Returns a Set of the most-trusted CAs. + public virtual ISet GetTrustAnchors() + { + return new HashSet(this.trustAnchors); + } + + // Sets the set of most-trusted CAs. + // Set is copied to protect against subsequent modifications. + public virtual void SetTrustAnchors( + ISet tas) + { + if (tas == null) + throw new ArgumentNullException("value"); + if (tas.IsEmpty) + throw new ArgumentException("non-empty set required", "value"); + + // Explicit copy to enforce type-safety + this.trustAnchors = new HashSet(); + foreach (TrustAnchor ta in tas) + { + if (ta != null) + { + trustAnchors.Add(ta); + } + } + } + + /** + * Returns the required constraints on the target certificate. The + * constraints are returned as an instance of CertSelector. If + * null, no constraints are defined.
    + *
    + * Note that the CertSelector returned is cloned to protect against + * subsequent modifications. + * + * @return a CertSelector specifying the constraints on the target + * certificate (or null) + * + * @see #setTargetCertConstraints(CertSelector) + */ + public virtual X509CertStoreSelector GetTargetCertConstraints() + { + if (certSelector == null) + { + return null; + } + + return (X509CertStoreSelector)certSelector.Clone(); + } + + /** + * Sets the required constraints on the target certificate. The constraints + * are specified as an instance of CertSelector. If null, no constraints are + * defined.
    + *
    + * Note that the CertSelector specified is cloned to protect against + * subsequent modifications. + * + * @param selector + * a CertSelector specifying the constraints on the target + * certificate (or null) + * + * @see #getTargetCertConstraints() + */ + public virtual void SetTargetCertConstraints( + IX509Selector selector) + { + if (selector == null) + { + certSelector = null; + } + else + { + certSelector = (IX509Selector)selector.Clone(); + } + } + + /** + * Returns an immutable Set of initial policy identifiers (OID strings), + * indicating that any one of these policies would be acceptable to the + * certificate user for the purposes of certification path processing. The + * default return value is an empty Set, which is + * interpreted as meaning that any policy would be acceptable. + * + * @return an immutable Set of initial policy OIDs in String + * format, or an empty Set (implying any policy is + * acceptable). Never returns null. + * + * @see #setInitialPolicies(java.util.Set) + */ + public virtual ISet GetInitialPolicies() + { + ISet returnSet = initialPolicies; + + // TODO Can it really be null? + if (initialPolicies == null) + { + returnSet = new HashSet(); + } + + return new HashSet(returnSet); + } + + /** + * Sets the Set of initial policy identifiers (OID strings), + * indicating that any one of these policies would be acceptable to the + * certificate user for the purposes of certification path processing. By + * default, any policy is acceptable (i.e. all policies), so a user that + * wants to allow any policy as acceptable does not need to call this + * method, or can call it with an empty Set (or + * null).
    + *
    + * Note that the Set is copied to protect against subsequent modifications.
    + *
    + * + * @param initialPolicies + * a Set of initial policy OIDs in String format (or + * null) + * + * @exception ClassCastException + * if any of the elements in the set are not of type String + * + * @see #getInitialPolicies() + */ + public virtual void SetInitialPolicies( + ISet initialPolicies) + { + this.initialPolicies = new HashSet(); + if (initialPolicies != null) + { + foreach (string obj in initialPolicies) + { + if (obj != null) + { + this.initialPolicies.Add(obj); + } + } + } + } + + /** + * Sets a List of additional certification path checkers. If + * the specified List contains an object that is not a PKIXCertPathChecker, + * it is ignored.
    + *
    + * Each PKIXCertPathChecker specified implements additional + * checks on a certificate. Typically, these are checks to process and + * verify private extensions contained in certificates. Each + * PKIXCertPathChecker should be instantiated with any + * initialization parameters needed to execute the check.
    + *
    + * This method allows sophisticated applications to extend a PKIX + * CertPathValidator or CertPathBuilder. Each + * of the specified PKIXCertPathCheckers will be called, in turn, by a PKIX + * CertPathValidator or CertPathBuilder for + * each certificate processed or validated.
    + *
    + * Regardless of whether these additional PKIXCertPathCheckers are set, a + * PKIX CertPathValidator or CertPathBuilder + * must perform all of the required PKIX checks on each certificate. The one + * exception to this rule is if the RevocationEnabled flag is set to false + * (see the {@link #setRevocationEnabled(boolean) setRevocationEnabled} + * method).
    + *
    + * Note that the List supplied here is copied and each PKIXCertPathChecker + * in the list is cloned to protect against subsequent modifications. + * + * @param checkers + * a List of PKIXCertPathCheckers. May be null, in which case no + * additional checkers will be used. + * @exception ClassCastException + * if any of the elements in the list are not of type + * java.security.cert.PKIXCertPathChecker + * @see #getCertPathCheckers() + */ + public virtual void SetCertPathCheckers(IList checkers) + { + certPathCheckers = Platform.CreateArrayList(); + if (checkers != null) + { + foreach (PkixCertPathChecker obj in checkers) + { + certPathCheckers.Add(obj.Clone()); + } + } + } + + /** + * Returns the List of certification path checkers. Each PKIXCertPathChecker + * in the returned IList is cloned to protect against subsequent modifications. + * + * @return an immutable List of PKIXCertPathCheckers (may be empty, but not + * null) + * + * @see #setCertPathCheckers(java.util.List) + */ + public virtual IList GetCertPathCheckers() + { + IList checkers = Platform.CreateArrayList(); + foreach (PkixCertPathChecker obj in certPathCheckers) + { + checkers.Add(obj.Clone()); + } + return checkers; + } + + /** + * Adds a PKIXCertPathChecker to the list of certification + * path checkers. See the {@link #setCertPathCheckers setCertPathCheckers} + * method for more details. + *

    + * Note that the PKIXCertPathChecker is cloned to protect + * against subsequent modifications.

    + * + * @param checker a PKIXCertPathChecker to add to the list of + * checks. If null, the checker is ignored (not added to list). + */ + public virtual void AddCertPathChecker( + PkixCertPathChecker checker) + { + if (checker != null) + { + certPathCheckers.Add(checker.Clone()); + } + } + + public virtual object Clone() + { + // FIXME Check this whole method against the Java implementation! + + PkixParameters parameters = new PkixParameters(GetTrustAnchors()); + parameters.SetParams(this); + return parameters; + + +// PkixParameters obj = new PkixParameters(new HashSet()); +//// (PkixParameters) this.MemberwiseClone(); +// obj.x509Stores = new ArrayList(x509Stores); +// obj.certPathCheckers = new ArrayList(certPathCheckers); +// +// //Iterator iter = certPathCheckers.iterator(); +// //obj.certPathCheckers = new ArrayList(); +// //while (iter.hasNext()) +// //{ +// // obj.certPathCheckers.add(((PKIXCertPathChecker)iter.next()) +// // .clone()); +// //} +// //if (initialPolicies != null) +// //{ +// // obj.initialPolicies = new HashSet(initialPolicies); +// //} +//// if (trustAnchors != null) +//// { +//// obj.trustAnchors = new HashSet(trustAnchors); +//// } +//// if (certSelector != null) +//// { +//// obj.certSelector = (X509CertStoreSelector) certSelector.Clone(); +//// } +// return obj; + } + + /** + * Method to support Clone() under J2ME. + * super.Clone() does not exist and fields are not copied. + * + * @param params Parameters to set. If this are + * ExtendedPkixParameters they are copied to. + */ + protected virtual void SetParams( + PkixParameters parameters) + { + Date = parameters.Date; + SetCertPathCheckers(parameters.GetCertPathCheckers()); + IsAnyPolicyInhibited = parameters.IsAnyPolicyInhibited; + IsExplicitPolicyRequired = parameters.IsExplicitPolicyRequired; + IsPolicyMappingInhibited = parameters.IsPolicyMappingInhibited; + IsRevocationEnabled = parameters.IsRevocationEnabled; + SetInitialPolicies(parameters.GetInitialPolicies()); + IsPolicyQualifiersRejected = parameters.IsPolicyQualifiersRejected; + SetTargetCertConstraints(parameters.GetTargetCertConstraints()); + SetTrustAnchors(parameters.GetTrustAnchors()); + + validityModel = parameters.validityModel; + useDeltas = parameters.useDeltas; + additionalLocationsEnabled = parameters.additionalLocationsEnabled; + selector = parameters.selector == null ? null + : (IX509Selector) parameters.selector.Clone(); + stores = Platform.CreateArrayList(parameters.stores); + additionalStores = Platform.CreateArrayList(parameters.additionalStores); + trustedACIssuers = new HashSet(parameters.trustedACIssuers); + prohibitedACAttributes = new HashSet(parameters.prohibitedACAttributes); + necessaryACAttributes = new HashSet(parameters.necessaryACAttributes); + attrCertCheckers = new HashSet(parameters.attrCertCheckers); + } + + /** + * Whether delta CRLs should be used for checking the revocation status. + * Defaults to false. + */ + public virtual bool IsUseDeltasEnabled + { + get { return useDeltas; } + set { useDeltas = value; } + } + + /** + * The validity model. + * @see #CHAIN_VALIDITY_MODEL + * @see #PKIX_VALIDITY_MODEL + */ + public virtual int ValidityModel + { + get { return validityModel; } + set { validityModel = value; } + } + + /** + * Sets the Bouncy Castle Stores for finding CRLs, certificates, attribute + * certificates or cross certificates. + *

    + * The IList is cloned. + *

    + * + * @param stores A list of stores to use. + * @see #getStores + * @throws ClassCastException if an element of stores is not + * a {@link Store}. + */ + public virtual void SetStores( + IList stores) + { + if (stores == null) + { + this.stores = Platform.CreateArrayList(); + } + else + { + foreach (object obj in stores) + { + if (!(obj is IX509Store)) + { + throw new InvalidCastException( + "All elements of list must be of type " + typeof(IX509Store).FullName); + } + } + this.stores = Platform.CreateArrayList(stores); + } + } + + /** + * Adds a Bouncy Castle {@link Store} to find CRLs, certificates, attribute + * certificates or cross certificates. + *

    + * This method should be used to add local stores, like collection based + * X.509 stores, if available. Local stores should be considered first, + * before trying to use additional (remote) locations, because they do not + * need possible additional network traffic. + *

    + * If store is null it is ignored. + *

    + * + * @param store The store to add. + * @see #getStores + */ + public virtual void AddStore( + IX509Store store) + { + if (store != null) + { + stores.Add(store); + } + } + + /** + * Adds an additional Bouncy Castle {@link Store} to find CRLs, certificates, + * attribute certificates or cross certificates. + *

    + * You should not use this method. This method is used for adding additional + * X.509 stores, which are used to add (remote) locations, e.g. LDAP, found + * during X.509 object processing, e.g. in certificates or CRLs. This method + * is used in PKIX certification path processing. + *

    + * If store is null it is ignored. + *

    + * + * @param store The store to add. + * @see #getStores() + */ + public virtual void AddAdditionalStore( + IX509Store store) + { + if (store != null) + { + additionalStores.Add(store); + } + } + + /** + * Returns an IList of additional Bouncy Castle + * Stores used for finding CRLs, certificates, attribute + * certificates or cross certificates. + * + * @return an immutable IList of additional Bouncy Castle + * Stores. Never null. + * + * @see #addAddionalStore(Store) + */ + public virtual IList GetAdditionalStores() + { + return Platform.CreateArrayList(additionalStores); + } + + /** + * Returns an IList of Bouncy Castle + * Stores used for finding CRLs, certificates, attribute + * certificates or cross certificates. + * + * @return an immutable IList of Bouncy Castle + * Stores. Never null. + * + * @see #setStores(IList) + */ + public virtual IList GetStores() + { + return Platform.CreateArrayList(stores); + } + + /** + * Returns if additional {@link X509Store}s for locations like LDAP found + * in certificates or CRLs should be used. + * + * @return Returns true if additional stores are used. + */ + public virtual bool IsAdditionalLocationsEnabled + { + get { return additionalLocationsEnabled; } + } + + /** + * Sets if additional {@link X509Store}s for locations like LDAP found in + * certificates or CRLs should be used. + * + * @param enabled true if additional stores are used. + */ + public virtual void SetAdditionalLocationsEnabled( + bool enabled) + { + additionalLocationsEnabled = enabled; + } + + /** + * Returns the required constraints on the target certificate or attribute + * certificate. The constraints are returned as an instance of + * IX509Selector. If null, no constraints are + * defined. + * + *

    + * The target certificate in a PKIX path may be a certificate or an + * attribute certificate. + *

    + * Note that the IX509Selector returned is cloned to protect + * against subsequent modifications. + *

    + * @return a IX509Selector specifying the constraints on the + * target certificate or attribute certificate (or null) + * @see #setTargetConstraints + * @see X509CertStoreSelector + * @see X509AttributeCertStoreSelector + */ + public virtual IX509Selector GetTargetConstraints() + { + if (selector != null) + { + return (IX509Selector) selector.Clone(); + } + else + { + return null; + } + } + + /** + * Sets the required constraints on the target certificate or attribute + * certificate. The constraints are specified as an instance of + * IX509Selector. If null, no constraints are + * defined. + *

    + * The target certificate in a PKIX path may be a certificate or an + * attribute certificate. + *

    + * Note that the IX509Selector specified is cloned to protect + * against subsequent modifications. + *

    + * + * @param selector a IX509Selector specifying the constraints on + * the target certificate or attribute certificate (or + * null) + * @see #getTargetConstraints + * @see X509CertStoreSelector + * @see X509AttributeCertStoreSelector + */ + public virtual void SetTargetConstraints(IX509Selector selector) + { + if (selector != null) + { + this.selector = (IX509Selector) selector.Clone(); + } + else + { + this.selector = null; + } + } + + /** + * Returns the trusted attribute certificate issuers. If attribute + * certificates is verified the trusted AC issuers must be set. + *

    + * The returned ISet consists of TrustAnchors. + *

    + * The returned ISet is immutable. Never null + *

    + * + * @return Returns an immutable set of the trusted AC issuers. + */ + public virtual ISet GetTrustedACIssuers() + { + return new HashSet(trustedACIssuers); + } + + /** + * Sets the trusted attribute certificate issuers. If attribute certificates + * is verified the trusted AC issuers must be set. + *

    + * The trustedACIssuers must be a ISet of + * TrustAnchor + *

    + * The given set is cloned. + *

    + * + * @param trustedACIssuers The trusted AC issuers to set. Is never + * null. + * @throws ClassCastException if an element of stores is not + * a TrustAnchor. + */ + public virtual void SetTrustedACIssuers( + ISet trustedACIssuers) + { + if (trustedACIssuers == null) + { + this.trustedACIssuers = new HashSet(); + } + else + { + foreach (object obj in trustedACIssuers) + { + if (!(obj is TrustAnchor)) + { + throw new InvalidCastException("All elements of set must be " + + "of type " + typeof(TrustAnchor).FullName + "."); + } + } + this.trustedACIssuers = new HashSet(trustedACIssuers); + } + } + + /** + * Returns the necessary attributes which must be contained in an attribute + * certificate. + *

    + * The returned ISet is immutable and contains + * Strings with the OIDs. + *

    + * + * @return Returns the necessary AC attributes. + */ + public virtual ISet GetNecessaryACAttributes() + { + return new HashSet(necessaryACAttributes); + } + + /** + * Sets the necessary which must be contained in an attribute certificate. + *

    + * The ISet must contain Strings with the + * OIDs. + *

    + * The set is cloned. + *

    + * + * @param necessaryACAttributes The necessary AC attributes to set. + * @throws ClassCastException if an element of + * necessaryACAttributes is not a + * String. + */ + public virtual void SetNecessaryACAttributes( + ISet necessaryACAttributes) + { + if (necessaryACAttributes == null) + { + this.necessaryACAttributes = new HashSet(); + } + else + { + foreach (object obj in necessaryACAttributes) + { + if (!(obj is string)) + { + throw new InvalidCastException("All elements of set must be " + + "of type string."); + } + } + this.necessaryACAttributes = new HashSet(necessaryACAttributes); + } + } + + /** + * Returns the attribute certificates which are not allowed. + *

    + * The returned ISet is immutable and contains + * Strings with the OIDs. + *

    + * + * @return Returns the prohibited AC attributes. Is never null. + */ + public virtual ISet GetProhibitedACAttributes() + { + return new HashSet(prohibitedACAttributes); + } + + /** + * Sets the attribute certificates which are not allowed. + *

    + * The ISet must contain Strings with the + * OIDs. + *

    + * The set is cloned. + *

    + * + * @param prohibitedACAttributes The prohibited AC attributes to set. + * @throws ClassCastException if an element of + * prohibitedACAttributes is not a + * String. + */ + public virtual void SetProhibitedACAttributes( + ISet prohibitedACAttributes) + { + if (prohibitedACAttributes == null) + { + this.prohibitedACAttributes = new HashSet(); + } + else + { + foreach (object obj in prohibitedACAttributes) + { + if (!(obj is String)) + { + throw new InvalidCastException("All elements of set must be " + + "of type string."); + } + } + this.prohibitedACAttributes = new HashSet(prohibitedACAttributes); + } + } + + /** + * Returns the attribute certificate checker. The returned set contains + * {@link PKIXAttrCertChecker}s and is immutable. + * + * @return Returns the attribute certificate checker. Is never + * null. + */ + public virtual ISet GetAttrCertCheckers() + { + return new HashSet(attrCertCheckers); + } + + /** + * Sets the attribute certificate checkers. + *

    + * All elements in the ISet must a {@link PKIXAttrCertChecker}. + *

    + *

    + * The given set is cloned. + *

    + * + * @param attrCertCheckers The attribute certificate checkers to set. Is + * never null. + * @throws ClassCastException if an element of attrCertCheckers + * is not a PKIXAttrCertChecker. + */ + public virtual void SetAttrCertCheckers( + ISet attrCertCheckers) + { + if (attrCertCheckers == null) + { + this.attrCertCheckers = new HashSet(); + } + else + { + foreach (object obj in attrCertCheckers) + { + if (!(obj is PkixAttrCertChecker)) + { + throw new InvalidCastException("All elements of set must be " + + "of type " + typeof(PkixAttrCertChecker).FullName + "."); + } + } + this.attrCertCheckers = new HashSet(attrCertCheckers); + } + } + } +} diff --git a/bc-sharp-crypto/src/pkix/PkixPolicyNode.cs b/bc-sharp-crypto/src/pkix/PkixPolicyNode.cs new file mode 100644 index 0000000..fc5b82f --- /dev/null +++ b/bc-sharp-crypto/src/pkix/PkixPolicyNode.cs @@ -0,0 +1,158 @@ +using System; +using System.Collections; +using System.Text; + +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; + +namespace Org.BouncyCastle.Pkix +{ + /// + /// Summary description for PkixPolicyNode. + /// + public class PkixPolicyNode +// : IPolicyNode + { + protected IList mChildren; + protected int mDepth; + protected ISet mExpectedPolicies; + protected PkixPolicyNode mParent; + protected ISet mPolicyQualifiers; + protected string mValidPolicy; + protected bool mCritical; + + public virtual int Depth + { + get { return this.mDepth; } + } + + public virtual IEnumerable Children + { + get { return new EnumerableProxy(mChildren); } + } + + public virtual bool IsCritical + { + get { return this.mCritical; } + set { this.mCritical = value; } + } + + public virtual ISet PolicyQualifiers + { + get { return new HashSet(this.mPolicyQualifiers); } + } + + public virtual string ValidPolicy + { + get { return this.mValidPolicy; } + } + + public virtual bool HasChildren + { + get { return mChildren.Count != 0; } + } + + public virtual ISet ExpectedPolicies + { + get { return new HashSet(this.mExpectedPolicies); } + set { this.mExpectedPolicies = new HashSet(value); } + } + + public virtual PkixPolicyNode Parent + { + get { return this.mParent; } + set { this.mParent = value; } + } + + /// Constructors + public PkixPolicyNode( + IList children, + int depth, + ISet expectedPolicies, + PkixPolicyNode parent, + ISet policyQualifiers, + string validPolicy, + bool critical) + { + if (children == null) + { + this.mChildren = Platform.CreateArrayList(); + } + else + { + this.mChildren = Platform.CreateArrayList(children); + } + + this.mDepth = depth; + this.mExpectedPolicies = expectedPolicies; + this.mParent = parent; + this.mPolicyQualifiers = policyQualifiers; + this.mValidPolicy = validPolicy; + this.mCritical = critical; + } + + public virtual void AddChild( + PkixPolicyNode child) + { + child.Parent = this; + mChildren.Add(child); + } + + public virtual void RemoveChild( + PkixPolicyNode child) + { + mChildren.Remove(child); + } + + public override string ToString() + { + return ToString(""); + } + + public virtual string ToString( + string indent) + { + StringBuilder buf = new StringBuilder(); + buf.Append(indent); + buf.Append(mValidPolicy); + buf.Append(" {"); + buf.Append(Platform.NewLine); + + foreach (PkixPolicyNode child in mChildren) + { + buf.Append(child.ToString(indent + " ")); + } + + buf.Append(indent); + buf.Append("}"); + buf.Append(Platform.NewLine); + return buf.ToString(); + } + + public virtual object Clone() + { + return Copy(); + } + + public virtual PkixPolicyNode Copy() + { + PkixPolicyNode node = new PkixPolicyNode( + Platform.CreateArrayList(), + mDepth, + new HashSet(mExpectedPolicies), + null, + new HashSet(mPolicyQualifiers), + mValidPolicy, + mCritical); + + foreach (PkixPolicyNode child in mChildren) + { + PkixPolicyNode copy = child.Copy(); + copy.Parent = node; + node.AddChild(copy); + } + + return node; + } + } +} diff --git a/bc-sharp-crypto/src/pkix/ReasonsMask.cs b/bc-sharp-crypto/src/pkix/ReasonsMask.cs new file mode 100644 index 0000000..e389bfe --- /dev/null +++ b/bc-sharp-crypto/src/pkix/ReasonsMask.cs @@ -0,0 +1,96 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; + +namespace Org.BouncyCastle.Pkix +{ + /// + /// This class helps to handle CRL revocation reasons mask. Each CRL handles a + /// certain set of revocation reasons. + /// + internal class ReasonsMask + { + private int _reasons; + + /// + /// Constructs are reason mask with the reasons. + /// + /// The reasons. + internal ReasonsMask( + int reasons) + { + _reasons = reasons; + } + + /// + /// A reason mask with no reason. + /// + internal ReasonsMask() + : this(0) + { + } + + /// + /// A mask with all revocation reasons. + /// + internal static readonly ReasonsMask AllReasons = new ReasonsMask( + ReasonFlags.AACompromise | ReasonFlags.AffiliationChanged | ReasonFlags.CACompromise + | ReasonFlags.CertificateHold | ReasonFlags.CessationOfOperation + | ReasonFlags.KeyCompromise | ReasonFlags.PrivilegeWithdrawn | ReasonFlags.Unused + | ReasonFlags.Superseded); + + /** + * Adds all reasons from the reasons mask to this mask. + * + * @param mask The reasons mask to add. + */ + internal void AddReasons( + ReasonsMask mask) + { + _reasons = _reasons | mask.Reasons.IntValue; + } + + /// + /// Returns true if this reasons mask contains all possible + /// reasons. + /// + /// true if this reasons mask contains all possible reasons. + /// + internal bool IsAllReasons + { + get { return _reasons == AllReasons._reasons; } + } + + /// + /// Intersects this mask with the given reasons mask. + /// + /// mask The mask to intersect with. + /// The intersection of this and teh given mask. + internal ReasonsMask Intersect( + ReasonsMask mask) + { + ReasonsMask _mask = new ReasonsMask(); + _mask.AddReasons(new ReasonsMask(_reasons & mask.Reasons.IntValue)); + return _mask; + } + + /// + /// Returns true if the passed reasons mask has new reasons. + /// + /// The reasons mask which should be tested for new reasons. + /// true if the passed reasons mask has new reasons. + internal bool HasNewReasons( + ReasonsMask mask) + { + return ((_reasons | mask.Reasons.IntValue ^ _reasons) != 0); + } + + /// + /// Returns the reasons in this mask. + /// + public ReasonFlags Reasons + { + get { return new ReasonFlags(_reasons); } + } + } +} diff --git a/bc-sharp-crypto/src/pkix/Rfc3280CertPathUtilities.cs b/bc-sharp-crypto/src/pkix/Rfc3280CertPathUtilities.cs new file mode 100644 index 0000000..c6f3fbf --- /dev/null +++ b/bc-sharp-crypto/src/pkix/Rfc3280CertPathUtilities.cs @@ -0,0 +1,2448 @@ +using System; +using System.Collections; +using System.Globalization; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Security.Certificates; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; +using Org.BouncyCastle.Utilities.Date; +using Org.BouncyCastle.X509; +using Org.BouncyCastle.X509.Store; + +namespace Org.BouncyCastle.Pkix +{ + public class Rfc3280CertPathUtilities + { + private static readonly PkixCrlUtilities CrlUtilities = new PkixCrlUtilities(); + + internal static readonly string ANY_POLICY = "2.5.29.32.0"; + + // key usage bits + internal static readonly int KEY_CERT_SIGN = 5; + internal static readonly int CRL_SIGN = 6; + + /** + * If the complete CRL includes an issuing distribution point (IDP) CRL + * extension check the following: + *

    + * (i) If the distribution point name is present in the IDP CRL extension + * and the distribution field is present in the DP, then verify that one of + * the names in the IDP matches one of the names in the DP. If the + * distribution point name is present in the IDP CRL extension and the + * distribution field is omitted from the DP, then verify that one of the + * names in the IDP matches one of the names in the cRLIssuer field of the + * DP. + *

    + *

    + * (ii) If the onlyContainsUserCerts boolean is asserted in the IDP CRL + * extension, verify that the certificate does not include the basic + * constraints extension with the cA boolean asserted. + *

    + *

    + * (iii) If the onlyContainsCACerts boolean is asserted in the IDP CRL + * extension, verify that the certificate includes the basic constraints + * extension with the cA boolean asserted. + *

    + *

    + * (iv) Verify that the onlyContainsAttributeCerts boolean is not asserted. + *

    + * + * @param dp The distribution point. + * @param cert The certificate. + * @param crl The CRL. + * @throws AnnotatedException if one of the conditions is not met or an error occurs. + */ + internal static void ProcessCrlB2( + DistributionPoint dp, + object cert, + X509Crl crl) + { + IssuingDistributionPoint idp = null; + try + { + idp = IssuingDistributionPoint.GetInstance(PkixCertPathValidatorUtilities.GetExtensionValue(crl, X509Extensions.IssuingDistributionPoint)); + } + catch (Exception e) + { + throw new Exception("0 Issuing distribution point extension could not be decoded.", e); + } + // (b) (2) (i) + // distribution point name is present + if (idp != null) + { + if (idp.DistributionPoint != null) + { + // make list of names + DistributionPointName dpName = IssuingDistributionPoint.GetInstance(idp).DistributionPoint; + IList names = Platform.CreateArrayList(); + + if (dpName.PointType == DistributionPointName.FullName) + { + GeneralName[] genNames = GeneralNames.GetInstance(dpName.Name).GetNames(); + for (int j = 0; j < genNames.Length; j++) + { + names.Add(genNames[j]); + } + } + if (dpName.PointType == DistributionPointName.NameRelativeToCrlIssuer) + { + Asn1EncodableVector vec = new Asn1EncodableVector(); + try + { + IEnumerator e = Asn1Sequence.GetInstance( + Asn1Sequence.FromByteArray(crl.IssuerDN.GetEncoded())).GetEnumerator(); + while (e.MoveNext()) + { + vec.Add((Asn1Encodable)e.Current); + } + } + catch (IOException e) + { + throw new Exception("Could not read CRL issuer.", e); + } + vec.Add(dpName.Name); + names.Add(new GeneralName(X509Name.GetInstance(new DerSequence(vec)))); + } + bool matches = false; + // verify that one of the names in the IDP matches one + // of the names in the DP. + if (dp.DistributionPointName != null) + { + dpName = dp.DistributionPointName; + GeneralName[] genNames = null; + if (dpName.PointType == DistributionPointName.FullName) + { + genNames = GeneralNames.GetInstance(dpName.Name).GetNames(); + } + if (dpName.PointType == DistributionPointName.NameRelativeToCrlIssuer) + { + if (dp.CrlIssuer != null) + { + genNames = dp.CrlIssuer.GetNames(); + } + else + { + genNames = new GeneralName[1]; + try + { + genNames[0] = new GeneralName( + PkixCertPathValidatorUtilities.GetIssuerPrincipal(cert)); + } + catch (IOException e) + { + throw new Exception("Could not read certificate issuer.", e); + } + } + for (int j = 0; j < genNames.Length; j++) + { + IEnumerator e = Asn1Sequence.GetInstance(genNames[j].Name.ToAsn1Object()).GetEnumerator(); + Asn1EncodableVector vec = new Asn1EncodableVector(); + while (e.MoveNext()) + { + vec.Add((Asn1Encodable)e.Current); + } + vec.Add(dpName.Name); + genNames[j] = new GeneralName(X509Name.GetInstance(new DerSequence(vec))); + } + } + if (genNames != null) + { + for (int j = 0; j < genNames.Length; j++) + { + if (names.Contains(genNames[j])) + { + matches = true; + break; + } + } + } + if (!matches) + { + throw new Exception( + "No match for certificate CRL issuing distribution point name to cRLIssuer CRL distribution point."); + } + } + // verify that one of the names in + // the IDP matches one of the names in the cRLIssuer field of + // the DP + else + { + if (dp.CrlIssuer == null) + { + throw new Exception("Either the cRLIssuer or the distributionPoint field must " + + "be contained in DistributionPoint."); + } + GeneralName[] genNames = dp.CrlIssuer.GetNames(); + for (int j = 0; j < genNames.Length; j++) + { + if (names.Contains(genNames[j])) + { + matches = true; + break; + } + } + if (!matches) + { + throw new Exception( + "No match for certificate CRL issuing distribution point name to cRLIssuer CRL distribution point."); + } + } + } + BasicConstraints bc = null; + try + { + bc = BasicConstraints.GetInstance(PkixCertPathValidatorUtilities.GetExtensionValue( + (IX509Extension)cert, X509Extensions.BasicConstraints)); + } + catch (Exception e) + { + throw new Exception("Basic constraints extension could not be decoded.", e); + } + + //if (cert is X509Certificate) + { + // (b) (2) (ii) + if (idp.OnlyContainsUserCerts && ((bc != null) && bc.IsCA())) + { + throw new Exception("CA Cert CRL only contains user certificates."); + } + + // (b) (2) (iii) + if (idp.OnlyContainsCACerts && (bc == null || !bc.IsCA())) + { + throw new Exception("End CRL only contains CA certificates."); + } + } + + // (b) (2) (iv) + if (idp.OnlyContainsAttributeCerts) + { + throw new Exception("onlyContainsAttributeCerts boolean is asserted."); + } + } + } + + internal static void ProcessCertBC( + PkixCertPath certPath, + int index, + PkixNameConstraintValidator nameConstraintValidator) + //throws CertPathValidatorException + { + IList certs = certPath.Certificates; + X509Certificate cert = (X509Certificate)certs[index]; + int n = certs.Count; + // i as defined in the algorithm description + int i = n - index; + // + // (b), (c) permitted and excluded subtree checking. + // + if (!(PkixCertPathValidatorUtilities.IsSelfIssued(cert) && (i < n))) + { + X509Name principal = cert.SubjectDN; + Asn1InputStream aIn = new Asn1InputStream(principal.GetEncoded()); + Asn1Sequence dns; + + try + { + dns = DerSequence.GetInstance(aIn.ReadObject()); + } + catch (Exception e) + { + throw new PkixCertPathValidatorException( + "Exception extracting subject name when checking subtrees.", e, certPath, index); + } + + try + { + nameConstraintValidator.CheckPermittedDN(dns); + nameConstraintValidator.CheckExcludedDN(dns); + } + catch (PkixNameConstraintValidatorException e) + { + throw new PkixCertPathValidatorException( + "Subtree check for certificate subject failed.", e, certPath, index); + } + + GeneralNames altName = null; + try + { + altName = GeneralNames.GetInstance( + PkixCertPathValidatorUtilities.GetExtensionValue(cert, X509Extensions.SubjectAlternativeName)); + } + catch (Exception e) + { + throw new PkixCertPathValidatorException( + "Subject alternative name extension could not be decoded.", e, certPath, index); + } + + IList emails = X509Name.GetInstance(dns).GetValueList(X509Name.EmailAddress); + foreach (string email in emails) + { + GeneralName emailAsGeneralName = new GeneralName(GeneralName.Rfc822Name, email); + try + { + nameConstraintValidator.checkPermitted(emailAsGeneralName); + nameConstraintValidator.checkExcluded(emailAsGeneralName); + } + catch (PkixNameConstraintValidatorException ex) + { + throw new PkixCertPathValidatorException( + "Subtree check for certificate subject alternative email failed.", ex, certPath, index); + } + } + if (altName != null) + { + GeneralName[] genNames = null; + try + { + genNames = altName.GetNames(); + } + catch (Exception e) + { + throw new PkixCertPathValidatorException( + "Subject alternative name contents could not be decoded.", e, certPath, index); + } + foreach (GeneralName genName in genNames) + { + try + { + nameConstraintValidator.checkPermitted(genName); + nameConstraintValidator.checkExcluded(genName); + } + catch (PkixNameConstraintValidatorException e) + { + throw new PkixCertPathValidatorException( + "Subtree check for certificate subject alternative name failed.", e, certPath, index); + } + } + } + } + } + + internal static void PrepareNextCertA( + PkixCertPath certPath, + int index) + //throws CertPathValidatorException + { + IList certs = certPath.Certificates; + X509Certificate cert = (X509Certificate)certs[index]; + // + // + // (a) check the policy mappings + // + Asn1Sequence pm = null; + try + { + pm = Asn1Sequence.GetInstance( + PkixCertPathValidatorUtilities.GetExtensionValue(cert, X509Extensions.PolicyMappings)); + } + catch (Exception ex) + { + throw new PkixCertPathValidatorException( + "Policy mappings extension could not be decoded.", ex, certPath, index); + } + if (pm != null) + { + Asn1Sequence mappings = pm; + + for (int j = 0; j < mappings.Count; j++) + { + DerObjectIdentifier issuerDomainPolicy = null; + DerObjectIdentifier subjectDomainPolicy = null; + try + { + Asn1Sequence mapping = DerSequence.GetInstance(mappings[j]); + + issuerDomainPolicy = DerObjectIdentifier.GetInstance(mapping[0]); + subjectDomainPolicy = DerObjectIdentifier.GetInstance(mapping[1]); + } + catch (Exception e) + { + throw new PkixCertPathValidatorException( + "Policy mappings extension contents could not be decoded.", e, certPath, index); + } + + if (Rfc3280CertPathUtilities.ANY_POLICY.Equals(issuerDomainPolicy.Id)) + throw new PkixCertPathValidatorException( + "IssuerDomainPolicy is anyPolicy", null, certPath, index); + + if (Rfc3280CertPathUtilities.ANY_POLICY.Equals(subjectDomainPolicy.Id)) + throw new PkixCertPathValidatorException( + "SubjectDomainPolicy is anyPolicy,", null, certPath, index); + } + } + } + + internal static PkixPolicyNode ProcessCertD( + PkixCertPath certPath, + int index, + ISet acceptablePolicies, + PkixPolicyNode validPolicyTree, + IList[] policyNodes, + int inhibitAnyPolicy) + //throws CertPathValidatorException + { + IList certs = certPath.Certificates; + X509Certificate cert = (X509Certificate)certs[index]; + int n = certs.Count; + // i as defined in the algorithm description + int i = n - index; + // + // (d) policy Information checking against initial policy and + // policy mapping + // + Asn1Sequence certPolicies = null; + try + { + certPolicies = DerSequence.GetInstance( + PkixCertPathValidatorUtilities.GetExtensionValue(cert, X509Extensions.CertificatePolicies)); + } + catch (Exception e) + { + throw new PkixCertPathValidatorException( + "Could not read certificate policies extension from certificate.", e, certPath, index); + } + if (certPolicies != null && validPolicyTree != null) + { + // + // (d) (1) + // + ISet pols = new HashSet(); + + foreach (Asn1Encodable ae in certPolicies) + { + PolicyInformation pInfo = PolicyInformation.GetInstance(ae.ToAsn1Object()); + DerObjectIdentifier pOid = pInfo.PolicyIdentifier; + + pols.Add(pOid.Id); + + if (!Rfc3280CertPathUtilities.ANY_POLICY.Equals(pOid.Id)) + { + ISet pq = null; + try + { + pq = PkixCertPathValidatorUtilities.GetQualifierSet(pInfo.PolicyQualifiers); + } + catch (PkixCertPathValidatorException ex) + { + throw new PkixCertPathValidatorException( + "Policy qualifier info set could not be build.", ex, certPath, index); + } + + bool match = PkixCertPathValidatorUtilities.ProcessCertD1i(i, policyNodes, pOid, pq); + + if (!match) + { + PkixCertPathValidatorUtilities.ProcessCertD1ii(i, policyNodes, pOid, pq); + } + } + } + + if (acceptablePolicies.IsEmpty || acceptablePolicies.Contains(Rfc3280CertPathUtilities.ANY_POLICY)) + { + acceptablePolicies.Clear(); + acceptablePolicies.AddAll(pols); + } + else + { + ISet t1 = new HashSet(); + + foreach (object o in acceptablePolicies) + { + if (pols.Contains(o)) + { + t1.Add(o); + } + } + acceptablePolicies.Clear(); + acceptablePolicies.AddAll(t1); + } + + // + // (d) (2) + // + if ((inhibitAnyPolicy > 0) || ((i < n) && PkixCertPathValidatorUtilities.IsSelfIssued(cert))) + { + foreach (Asn1Encodable ae in certPolicies) + { + PolicyInformation pInfo = PolicyInformation.GetInstance(ae.ToAsn1Object()); + if (Rfc3280CertPathUtilities.ANY_POLICY.Equals(pInfo.PolicyIdentifier.Id)) + { + ISet _apq = PkixCertPathValidatorUtilities.GetQualifierSet(pInfo.PolicyQualifiers); + IList _nodes = policyNodes[i - 1]; + + for (int k = 0; k < _nodes.Count; k++) + { + PkixPolicyNode _node = (PkixPolicyNode)_nodes[k]; + + IEnumerator _policySetIter = _node.ExpectedPolicies.GetEnumerator(); + while (_policySetIter.MoveNext()) + { + object _tmp = _policySetIter.Current; + + string _policy; + if (_tmp is string) + { + _policy = (string)_tmp; + } + else if (_tmp is DerObjectIdentifier) + { + _policy = ((DerObjectIdentifier)_tmp).Id; + } + else + { + continue; + } + + bool _found = false; + + foreach (PkixPolicyNode _child in _node.Children) + { + if (_policy.Equals(_child.ValidPolicy)) + { + _found = true; + } + } + + if (!_found) + { + ISet _newChildExpectedPolicies = new HashSet(); + _newChildExpectedPolicies.Add(_policy); + + PkixPolicyNode _newChild = new PkixPolicyNode(Platform.CreateArrayList(), i, + _newChildExpectedPolicies, _node, _apq, _policy, false); + _node.AddChild(_newChild); + policyNodes[i].Add(_newChild); + } + } + } + break; + } + } + } + + PkixPolicyNode _validPolicyTree = validPolicyTree; + // + // (d) (3) + // + for (int j = (i - 1); j >= 0; j--) + { + IList nodes = policyNodes[j]; + + for (int k = 0; k < nodes.Count; k++) + { + PkixPolicyNode node = (PkixPolicyNode)nodes[k]; + if (!node.HasChildren) + { + _validPolicyTree = PkixCertPathValidatorUtilities.RemovePolicyNode(_validPolicyTree, policyNodes, + node); + if (_validPolicyTree == null) + { + break; + } + } + } + } + + // + // d (4) + // + ISet criticalExtensionOids = cert.GetCriticalExtensionOids(); + + if (criticalExtensionOids != null) + { + bool critical = criticalExtensionOids.Contains(X509Extensions.CertificatePolicies.Id); + + IList nodes = policyNodes[i]; + for (int j = 0; j < nodes.Count; j++) + { + PkixPolicyNode node = (PkixPolicyNode)nodes[j]; + node.IsCritical = critical; + } + } + return _validPolicyTree; + } + return null; + } + + /** + * If the DP includes cRLIssuer, then verify that the issuer field in the + * complete CRL matches cRLIssuer in the DP and that the complete CRL + * contains an + * g distribution point extension with the indirectCRL + * boolean asserted. Otherwise, verify that the CRL issuer matches the + * certificate issuer. + * + * @param dp The distribution point. + * @param cert The certificate ot attribute certificate. + * @param crl The CRL for cert. + * @throws AnnotatedException if one of the above conditions does not apply or an error + * occurs. + */ + internal static void ProcessCrlB1( + DistributionPoint dp, + object cert, + X509Crl crl) + { + Asn1Object idp = PkixCertPathValidatorUtilities.GetExtensionValue( + crl, X509Extensions.IssuingDistributionPoint); + + bool isIndirect = false; + if (idp != null) + { + if (IssuingDistributionPoint.GetInstance(idp).IsIndirectCrl) + { + isIndirect = true; + } + } + byte[] issuerBytes = crl.IssuerDN.GetEncoded(); + + bool matchIssuer = false; + if (dp.CrlIssuer != null) + { + GeneralName[] genNames = dp.CrlIssuer.GetNames(); + for (int j = 0; j < genNames.Length; j++) + { + if (genNames[j].TagNo == GeneralName.DirectoryName) + { + try + { + if (Org.BouncyCastle.Utilities.Arrays.AreEqual(genNames[j].Name.ToAsn1Object().GetEncoded(), issuerBytes)) + { + matchIssuer = true; + } + } + catch (IOException e) + { + throw new Exception( + "CRL issuer information from distribution point cannot be decoded.", e); + } + } + } + if (matchIssuer && !isIndirect) + { + throw new Exception("Distribution point contains cRLIssuer field but CRL is not indirect."); + } + if (!matchIssuer) + { + throw new Exception("CRL issuer of CRL does not match CRL issuer of distribution point."); + } + } + else + { + if (crl.IssuerDN.Equivalent(PkixCertPathValidatorUtilities.GetIssuerPrincipal(cert), true)) + { + matchIssuer = true; + } + } + if (!matchIssuer) + { + throw new Exception("Cannot find matching CRL issuer for certificate."); + } + } + + internal static ReasonsMask ProcessCrlD( + X509Crl crl, + DistributionPoint dp) + //throws AnnotatedException + { + IssuingDistributionPoint idp = null; + try + { + idp = IssuingDistributionPoint.GetInstance(PkixCertPathValidatorUtilities.GetExtensionValue(crl, X509Extensions.IssuingDistributionPoint)); + } + catch (Exception e) + { + throw new Exception("issuing distribution point extension could not be decoded.", e); + } + + // (d) (1) + if (idp != null && idp.OnlySomeReasons != null && dp.Reasons != null) + { + return new ReasonsMask(dp.Reasons.IntValue).Intersect(new ReasonsMask(idp.OnlySomeReasons + .IntValue)); + } + // (d) (4) + if ((idp == null || idp.OnlySomeReasons == null) && dp.Reasons == null) + { + return ReasonsMask.AllReasons; + } + + // (d) (2) and (d)(3) + + ReasonsMask dpReasons = null; + + if (dp.Reasons == null) + { + dpReasons = ReasonsMask.AllReasons; + } + else + { + dpReasons = new ReasonsMask(dp.Reasons.IntValue); + } + + ReasonsMask idpReasons = null; + + if (idp == null) + { + idpReasons = ReasonsMask.AllReasons; + } + else + { + idpReasons = new ReasonsMask(idp.OnlySomeReasons.IntValue); + } + + return dpReasons.Intersect(idpReasons); + } + + /** + * Obtain and validate the certification path for the complete CRL issuer. + * If a key usage extension is present in the CRL issuer's certificate, + * verify that the cRLSign bit is set. + * + * @param crl CRL which contains revocation information for the certificate + * cert. + * @param cert The attribute certificate or certificate to check if it is + * revoked. + * @param defaultCRLSignCert The issuer certificate of the certificate cert. + * @param defaultCRLSignKey The public key of the issuer certificate + * defaultCRLSignCert. + * @param paramsPKIX paramsPKIX PKIX parameters. + * @param certPathCerts The certificates on the certification path. + * @return A Set with all keys of possible CRL issuer + * certificates. + * @throws AnnotatedException if the CRL is not valid or the status cannot be checked or + * some error occurs. + */ + internal static ISet ProcessCrlF( + X509Crl crl, + object cert, + X509Certificate defaultCRLSignCert, + AsymmetricKeyParameter defaultCRLSignKey, + PkixParameters paramsPKIX, + IList certPathCerts) + { + // (f) + + // get issuer from CRL + X509CertStoreSelector selector = new X509CertStoreSelector(); + try + { + selector.Subject = crl.IssuerDN; + } + catch (IOException e) + { + throw new Exception( + "Subject criteria for certificate selector to find issuer certificate for CRL could not be set.", e); + } + + // get CRL signing certs + IList coll = Platform.CreateArrayList(); + + try + { + CollectionUtilities.AddRange(coll, PkixCertPathValidatorUtilities.FindCertificates(selector, paramsPKIX.GetStores())); + CollectionUtilities.AddRange(coll, PkixCertPathValidatorUtilities.FindCertificates(selector, paramsPKIX.GetAdditionalStores())); + } + catch (Exception e) + { + throw new Exception("Issuer certificate for CRL cannot be searched.", e); + } + + coll.Add(defaultCRLSignCert); + + IEnumerator cert_it = coll.GetEnumerator(); + + IList validCerts = Platform.CreateArrayList(); + IList validKeys = Platform.CreateArrayList(); + + while (cert_it.MoveNext()) + { + X509Certificate signingCert = (X509Certificate)cert_it.Current; + + /* + * CA of the certificate, for which this CRL is checked, has also + * signed CRL, so skip the path validation, because is already done + */ + if (signingCert.Equals(defaultCRLSignCert)) + { + validCerts.Add(signingCert); + validKeys.Add(defaultCRLSignKey); + continue; + } + try + { +// CertPathBuilder builder = CertPathBuilder.GetInstance("PKIX"); + PkixCertPathBuilder builder = new PkixCertPathBuilder(); + selector = new X509CertStoreSelector(); + selector.Certificate = signingCert; + + PkixParameters temp = (PkixParameters)paramsPKIX.Clone(); + temp.SetTargetCertConstraints(selector); + + PkixBuilderParameters parameters = (PkixBuilderParameters) + PkixBuilderParameters.GetInstance(temp); + + /* + * if signingCert is placed not higher on the cert path a + * dependency loop results. CRL for cert is checked, but + * signingCert is needed for checking the CRL which is dependent + * on checking cert because it is higher in the cert path and so + * signing signingCert transitively. so, revocation is disabled, + * forgery attacks of the CRL are detected in this outer loop + * for all other it must be enabled to prevent forgery attacks + */ + if (certPathCerts.Contains(signingCert)) + { + parameters.IsRevocationEnabled = false; + } + else + { + parameters.IsRevocationEnabled = true; + } + IList certs = builder.Build(parameters).CertPath.Certificates; + validCerts.Add(signingCert); + validKeys.Add(PkixCertPathValidatorUtilities.GetNextWorkingKey(certs, 0)); + } + catch (PkixCertPathBuilderException e) + { + throw new Exception("Internal error.", e); + } + catch (PkixCertPathValidatorException e) + { + throw new Exception("Public key of issuer certificate of CRL could not be retrieved.", e); + } + //catch (Exception e) + //{ + // throw new Exception(e.Message); + //} + } + + ISet checkKeys = new HashSet(); + + Exception lastException = null; + for (int i = 0; i < validCerts.Count; i++) + { + X509Certificate signCert = (X509Certificate)validCerts[i]; + bool[] keyusage = signCert.GetKeyUsage(); + + if (keyusage != null && (keyusage.Length < 7 || !keyusage[CRL_SIGN])) + { + lastException = new Exception( + "Issuer certificate key usage extension does not permit CRL signing."); + } + else + { + checkKeys.Add(validKeys[i]); + } + } + + if ((checkKeys.Count == 0) && lastException == null) + { + throw new Exception("Cannot find a valid issuer certificate."); + } + if ((checkKeys.Count == 0) && lastException != null) + { + throw lastException; + } + + return checkKeys; + } + + internal static AsymmetricKeyParameter ProcessCrlG( + X509Crl crl, + ISet keys) + { + Exception lastException = null; + foreach (AsymmetricKeyParameter key in keys) + { + try + { + crl.Verify(key); + return key; + } + catch (Exception e) + { + lastException = e; + } + } + throw new Exception("Cannot verify CRL.", lastException); + } + + internal static X509Crl ProcessCrlH( + ISet deltaCrls, + AsymmetricKeyParameter key) + { + Exception lastException = null; + foreach (X509Crl crl in deltaCrls) + { + try + { + crl.Verify(key); + return crl; + } + catch (Exception e) + { + lastException = e; + } + } + if (lastException != null) + { + throw new Exception("Cannot verify delta CRL.", lastException); + } + return null; + } + + /** + * Checks a distribution point for revocation information for the + * certificate cert. + * + * @param dp The distribution point to consider. + * @param paramsPKIX PKIX parameters. + * @param cert Certificate to check if it is revoked. + * @param validDate The date when the certificate revocation status should be + * checked. + * @param defaultCRLSignCert The issuer certificate of the certificate cert. + * @param defaultCRLSignKey The public key of the issuer certificate + * defaultCRLSignCert. + * @param certStatus The current certificate revocation status. + * @param reasonMask The reasons mask which is already checked. + * @param certPathCerts The certificates of the certification path. + * @throws AnnotatedException if the certificate is revoked or the status cannot be checked + * or some error occurs. + */ + private static void CheckCrl( + DistributionPoint dp, + PkixParameters paramsPKIX, + X509Certificate cert, + DateTime validDate, + X509Certificate defaultCRLSignCert, + AsymmetricKeyParameter defaultCRLSignKey, + CertStatus certStatus, + ReasonsMask reasonMask, + IList certPathCerts) + //throws AnnotatedException + { + DateTime currentDate = DateTime.UtcNow; + + if (validDate.Ticks > currentDate.Ticks) + { + throw new Exception("Validation time is in future."); + } + + // (a) + /* + * We always get timely valid CRLs, so there is no step (a) (1). + * "locally cached" CRLs are assumed to be in getStore(), additional + * CRLs must be enabled in the ExtendedPKIXParameters and are in + * getAdditionalStore() + */ + + ISet crls = PkixCertPathValidatorUtilities.GetCompleteCrls(dp, cert, currentDate, paramsPKIX); + bool validCrlFound = false; + Exception lastException = null; + + IEnumerator crl_iter = crls.GetEnumerator(); + + while (crl_iter.MoveNext() && certStatus.Status == CertStatus.Unrevoked && !reasonMask.IsAllReasons) + { + try + { + X509Crl crl = (X509Crl)crl_iter.Current; + + // (d) + ReasonsMask interimReasonsMask = Rfc3280CertPathUtilities.ProcessCrlD(crl, dp); + + // (e) + /* + * The reasons mask is updated at the end, so only valid CRLs + * can update it. If this CRL does not contain new reasons it + * must be ignored. + */ + if (!interimReasonsMask.HasNewReasons(reasonMask)) + { + continue; + } + + // (f) + ISet keys = Rfc3280CertPathUtilities.ProcessCrlF(crl, cert, defaultCRLSignCert, defaultCRLSignKey, + paramsPKIX, certPathCerts); + // (g) + AsymmetricKeyParameter key = Rfc3280CertPathUtilities.ProcessCrlG(crl, keys); + + X509Crl deltaCRL = null; + + if (paramsPKIX.IsUseDeltasEnabled) + { + // get delta CRLs + ISet deltaCRLs = PkixCertPathValidatorUtilities.GetDeltaCrls(currentDate, paramsPKIX, crl); + // we only want one valid delta CRL + // (h) + deltaCRL = Rfc3280CertPathUtilities.ProcessCrlH(deltaCRLs, key); + } + + /* + * CRL must be be valid at the current time, not the validation + * time. If a certificate is revoked with reason keyCompromise, + * cACompromise, it can be used for forgery, also for the past. + * This reason may not be contained in older CRLs. + */ + + /* + * in the chain model signatures stay valid also after the + * certificate has been expired, so they do not have to be in + * the CRL validity time + */ + + if (paramsPKIX.ValidityModel != PkixParameters.ChainValidityModel) + { + /* + * if a certificate has expired, but was revoked, it is not + * more in the CRL, so it would be regarded as valid if the + * first check is not done + */ + if (cert.NotAfter.Ticks < crl.ThisUpdate.Ticks) + { + throw new Exception("No valid CRL for current time found."); + } + } + + Rfc3280CertPathUtilities.ProcessCrlB1(dp, cert, crl); + + // (b) (2) + Rfc3280CertPathUtilities.ProcessCrlB2(dp, cert, crl); + + // (c) + Rfc3280CertPathUtilities.ProcessCrlC(deltaCRL, crl, paramsPKIX); + + // (i) + Rfc3280CertPathUtilities.ProcessCrlI(validDate, deltaCRL, cert, certStatus, paramsPKIX); + + // (j) + Rfc3280CertPathUtilities.ProcessCrlJ(validDate, crl, cert, certStatus); + + // (k) + if (certStatus.Status == CrlReason.RemoveFromCrl) + { + certStatus.Status = CertStatus.Unrevoked; + } + + // update reasons mask + reasonMask.AddReasons(interimReasonsMask); + + ISet criticalExtensions = crl.GetCriticalExtensionOids(); + + if (criticalExtensions != null) + { + criticalExtensions = new HashSet(criticalExtensions); + criticalExtensions.Remove(X509Extensions.IssuingDistributionPoint.Id); + criticalExtensions.Remove(X509Extensions.DeltaCrlIndicator.Id); + + if (!criticalExtensions.IsEmpty) + throw new Exception("CRL contains unsupported critical extensions."); + } + + if (deltaCRL != null) + { + criticalExtensions = deltaCRL.GetCriticalExtensionOids(); + if (criticalExtensions != null) + { + criticalExtensions = new HashSet(criticalExtensions); + criticalExtensions.Remove(X509Extensions.IssuingDistributionPoint.Id); + criticalExtensions.Remove(X509Extensions.DeltaCrlIndicator.Id); + + if (!criticalExtensions.IsEmpty) + throw new Exception("Delta CRL contains unsupported critical extension."); + } + } + + validCrlFound = true; + } + catch (Exception e) + { + lastException = e; + } + } + if (!validCrlFound) + { + throw lastException; + } + } + + /** + * Checks a certificate if it is revoked. + * + * @param paramsPKIX PKIX parameters. + * @param cert Certificate to check if it is revoked. + * @param validDate The date when the certificate revocation status should be + * checked. + * @param sign The issuer certificate of the certificate cert. + * @param workingPublicKey The public key of the issuer certificate sign. + * @param certPathCerts The certificates of the certification path. + * @throws AnnotatedException if the certificate is revoked or the status cannot be checked + * or some error occurs. + */ + protected static void CheckCrls( + PkixParameters paramsPKIX, + X509Certificate cert, + DateTime validDate, + X509Certificate sign, + AsymmetricKeyParameter workingPublicKey, + IList certPathCerts) + { + Exception lastException = null; + CrlDistPoint crldp = null; + + try + { + crldp = CrlDistPoint.GetInstance(PkixCertPathValidatorUtilities.GetExtensionValue(cert, X509Extensions.CrlDistributionPoints)); + } + catch (Exception e) + { + throw new Exception("CRL distribution point extension could not be read.", e); + } + + try + { + PkixCertPathValidatorUtilities.AddAdditionalStoresFromCrlDistributionPoint(crldp, paramsPKIX); + } + catch (Exception e) + { + throw new Exception( + "No additional CRL locations could be decoded from CRL distribution point extension.", e); + } + CertStatus certStatus = new CertStatus(); + ReasonsMask reasonsMask = new ReasonsMask(); + + bool validCrlFound = false; + + // for each distribution point + if (crldp != null) + { + DistributionPoint[] dps = null; + try + { + dps = crldp.GetDistributionPoints(); + } + catch (Exception e) + { + throw new Exception("Distribution points could not be read.", e); + } + if (dps != null) + { + for (int i = 0; i < dps.Length && certStatus.Status == CertStatus.Unrevoked && !reasonsMask.IsAllReasons; i++) + { + PkixParameters paramsPKIXClone = (PkixParameters)paramsPKIX.Clone(); + try + { + CheckCrl(dps[i], paramsPKIXClone, cert, validDate, sign, workingPublicKey, certStatus, reasonsMask, certPathCerts); + validCrlFound = true; + } + catch (Exception e) + { + lastException = e; + } + } + } + } + + /* + * If the revocation status has not been determined, repeat the process + * above with any available CRLs not specified in a distribution point + * but issued by the certificate issuer. + */ + + if (certStatus.Status == CertStatus.Unrevoked && !reasonsMask.IsAllReasons) + { + try + { + /* + * assume a DP with both the reasons and the cRLIssuer fields + * omitted and a distribution point name of the certificate + * issuer. + */ + Asn1Object issuer = null; + try + { + issuer = new Asn1InputStream(cert.IssuerDN.GetEncoded()).ReadObject(); + } + catch (Exception e) + { + throw new Exception("Issuer from certificate for CRL could not be reencoded.", e); + } + DistributionPoint dp = new DistributionPoint(new DistributionPointName(0, new GeneralNames( + new GeneralName(GeneralName.DirectoryName, issuer))), null, null); + PkixParameters paramsPKIXClone = (PkixParameters)paramsPKIX.Clone(); + + CheckCrl(dp, paramsPKIXClone, cert, validDate, sign, workingPublicKey, certStatus, reasonsMask, + certPathCerts); + + validCrlFound = true; + } + catch (Exception e) + { + lastException = e; + } + } + + if (!validCrlFound) + { + throw lastException; + } + if (certStatus.Status != CertStatus.Unrevoked) + { + // This format is enforced by the NistCertPath tests + string formattedDate = certStatus.RevocationDate.Value.ToString( + "ddd MMM dd HH:mm:ss K yyyy"); + string message = "Certificate revocation after " + formattedDate; + message += ", reason: " + CrlReasons[certStatus.Status]; + throw new Exception(message); + } + + if (!reasonsMask.IsAllReasons && certStatus.Status == CertStatus.Unrevoked) + { + certStatus.Status = CertStatus.Undetermined; + } + + if (certStatus.Status == CertStatus.Undetermined) + { + throw new Exception("Certificate status could not be determined."); + } + } + + internal static PkixPolicyNode PrepareCertB( + PkixCertPath certPath, + int index, + IList[] policyNodes, + PkixPolicyNode validPolicyTree, + int policyMapping) + //throws CertPathValidatorException + { + IList certs = certPath.Certificates; + X509Certificate cert = (X509Certificate)certs[index]; + int n = certs.Count; + // i as defined in the algorithm description + int i = n - index; + // (b) + // + Asn1Sequence pm = null; + try + { + pm = (Asn1Sequence)Asn1Sequence.GetInstance(PkixCertPathValidatorUtilities.GetExtensionValue(cert, X509Extensions.PolicyMappings)); + } + catch (Exception ex) + { + throw new PkixCertPathValidatorException( + "Policy mappings extension could not be decoded.", ex, certPath, index); + } + PkixPolicyNode _validPolicyTree = validPolicyTree; + if (pm != null) + { + Asn1Sequence mappings = (Asn1Sequence)pm; + IDictionary m_idp = Platform.CreateHashtable(); + ISet s_idp = new HashSet(); + + for (int j = 0; j < mappings.Count; j++) + { + Asn1Sequence mapping = (Asn1Sequence) mappings[j]; + string id_p = ((DerObjectIdentifier) mapping[0]).Id; + string sd_p = ((DerObjectIdentifier) mapping[1]).Id; + ISet tmp; + + if (!m_idp.Contains(id_p)) + { + tmp = new HashSet(); + tmp.Add(sd_p); + m_idp[id_p] = tmp; + s_idp.Add(id_p); + } + else + { + tmp = (ISet)m_idp[id_p]; + tmp.Add(sd_p); + } + } + + IEnumerator it_idp = s_idp.GetEnumerator(); + while (it_idp.MoveNext()) + { + string id_p = (string)it_idp.Current; + + // + // (1) + // + if (policyMapping > 0) + { + bool idp_found = false; + IEnumerator nodes_i = policyNodes[i].GetEnumerator(); + + while (nodes_i.MoveNext()) + { + PkixPolicyNode node = (PkixPolicyNode)nodes_i.Current; + if (node.ValidPolicy.Equals(id_p)) + { + idp_found = true; + node.ExpectedPolicies = (ISet)m_idp[id_p]; + break; + } + } + + if (!idp_found) + { + nodes_i = policyNodes[i].GetEnumerator(); + while (nodes_i.MoveNext()) + { + PkixPolicyNode node = (PkixPolicyNode)nodes_i.Current; + if (Rfc3280CertPathUtilities.ANY_POLICY.Equals(node.ValidPolicy)) + { + ISet pq = null; + Asn1Sequence policies = null; + try + { + policies = (Asn1Sequence)PkixCertPathValidatorUtilities.GetExtensionValue(cert, + X509Extensions.CertificatePolicies); + } + catch (Exception e) + { + throw new PkixCertPathValidatorException( + "Certificate policies extension could not be decoded.", e, certPath, index); + } + + foreach (Asn1Encodable ae in policies) + { + PolicyInformation pinfo = null; + try + { + pinfo = PolicyInformation.GetInstance(ae.ToAsn1Object()); + } + catch (Exception ex) + { + throw new PkixCertPathValidatorException( + "Policy information could not be decoded.", ex, certPath, index); + } + if (Rfc3280CertPathUtilities.ANY_POLICY.Equals(pinfo.PolicyIdentifier.Id)) + { + try + { + pq = PkixCertPathValidatorUtilities + .GetQualifierSet(pinfo.PolicyQualifiers); + } + catch (PkixCertPathValidatorException ex) + { + throw new PkixCertPathValidatorException( + "Policy qualifier info set could not be decoded.", ex, certPath, + index); + } + break; + } + } + bool ci = false; + ISet critExtOids = cert.GetCriticalExtensionOids(); + if (critExtOids != null) + { + ci = critExtOids.Contains(X509Extensions.CertificatePolicies.Id); + } + + PkixPolicyNode p_node = (PkixPolicyNode)node.Parent; + if (Rfc3280CertPathUtilities.ANY_POLICY.Equals(p_node.ValidPolicy)) + { + PkixPolicyNode c_node = new PkixPolicyNode(Platform.CreateArrayList(), i, + (ISet)m_idp[id_p], p_node, pq, id_p, ci); + p_node.AddChild(c_node); + policyNodes[i].Add(c_node); + } + break; + } + } + } + + // + // (2) + // + } + else if (policyMapping <= 0) + { + foreach (PkixPolicyNode node in Platform.CreateArrayList(policyNodes[i])) + { + if (node.ValidPolicy.Equals(id_p)) + { + node.Parent.RemoveChild(node); + + for (int k = i - 1; k >= 0; k--) + { + foreach (PkixPolicyNode node2 in Platform.CreateArrayList(policyNodes[k])) + { + if (!node2.HasChildren) + { + _validPolicyTree = PkixCertPathValidatorUtilities.RemovePolicyNode( + _validPolicyTree, policyNodes, node2); + + if (_validPolicyTree == null) + break; + } + } + } + } + } + } + } + } + return _validPolicyTree; + } + + internal static ISet[] ProcessCrlA1ii( + DateTime currentDate, + PkixParameters paramsPKIX, + X509Certificate cert, + X509Crl crl) + { + ISet deltaSet = new HashSet(); + X509CrlStoreSelector crlselect = new X509CrlStoreSelector(); + crlselect.CertificateChecking = cert; + + try + { + IList issuer = Platform.CreateArrayList(); + issuer.Add(crl.IssuerDN); + crlselect.Issuers = issuer; + } + catch (IOException e) + { + throw new Exception("Cannot extract issuer from CRL." + e, e); + } + + crlselect.CompleteCrlEnabled = true; + ISet completeSet = CrlUtilities.FindCrls(crlselect, paramsPKIX, currentDate); + + if (paramsPKIX.IsUseDeltasEnabled) + { + // get delta CRL(s) + try + { + deltaSet.AddAll(PkixCertPathValidatorUtilities.GetDeltaCrls(currentDate, paramsPKIX, crl)); + } + catch (Exception e) + { + throw new Exception("Exception obtaining delta CRLs.", e); + } + } + + return new ISet[]{ completeSet, deltaSet }; + } + + internal static ISet ProcessCrlA1i( + DateTime currentDate, + PkixParameters paramsPKIX, + X509Certificate cert, + X509Crl crl) + { + ISet deltaSet = new HashSet(); + if (paramsPKIX.IsUseDeltasEnabled) + { + CrlDistPoint freshestCRL = null; + try + { + freshestCRL = CrlDistPoint.GetInstance( + PkixCertPathValidatorUtilities.GetExtensionValue(cert, X509Extensions.FreshestCrl)); + } + catch (Exception e) + { + throw new Exception("Freshest CRL extension could not be decoded from certificate.", e); + } + + if (freshestCRL == null) + { + try + { + freshestCRL = CrlDistPoint.GetInstance(PkixCertPathValidatorUtilities.GetExtensionValue(crl, X509Extensions.FreshestCrl)); + } + catch (Exception e) + { + throw new Exception("Freshest CRL extension could not be decoded from CRL.", e); + } + } + if (freshestCRL != null) + { + try + { + PkixCertPathValidatorUtilities.AddAdditionalStoresFromCrlDistributionPoint(freshestCRL, paramsPKIX); + } + catch (Exception e) + { + throw new Exception( + "No new delta CRL locations could be added from Freshest CRL extension.", e); + } + // get delta CRL(s) + try + { + deltaSet.AddAll(PkixCertPathValidatorUtilities.GetDeltaCrls(currentDate, paramsPKIX, crl)); + } + catch (Exception e) + { + throw new Exception("Exception obtaining delta CRLs.", e); + } + } + } + return deltaSet; + } + + internal static void ProcessCertF( + PkixCertPath certPath, + int index, + PkixPolicyNode validPolicyTree, + int explicitPolicy) + { + // + // (f) + // + if (explicitPolicy <= 0 && validPolicyTree == null) + { + throw new PkixCertPathValidatorException( + "No valid policy tree found when one expected.", null, certPath, index); + } + } + + internal static void ProcessCertA( + PkixCertPath certPath, + PkixParameters paramsPKIX, + int index, + AsymmetricKeyParameter workingPublicKey, + X509Name workingIssuerName, + X509Certificate sign) + { + IList certs = certPath.Certificates; + X509Certificate cert = (X509Certificate)certs[index]; + // + // (a) verify + // + try + { + // (a) (1) + // + cert.Verify(workingPublicKey); + } + catch (GeneralSecurityException e) + { + throw new PkixCertPathValidatorException("Could not validate certificate signature.", e, certPath, index); + } + + try + { + // (a) (2) + // + cert.CheckValidity(PkixCertPathValidatorUtilities + .GetValidCertDateFromValidityModel(paramsPKIX, certPath, index)); + } + catch (CertificateExpiredException e) + { + throw new PkixCertPathValidatorException("Could not validate certificate: " + e.Message, e, certPath, index); + } + catch (CertificateNotYetValidException e) + { + throw new PkixCertPathValidatorException("Could not validate certificate: " + e.Message, e, certPath, index); + } + catch (Exception e) + { + throw new PkixCertPathValidatorException("Could not validate time of certificate.", e, certPath, index); + } + + // + // (a) (3) + // + if (paramsPKIX.IsRevocationEnabled) + { + try + { + CheckCrls(paramsPKIX, cert, PkixCertPathValidatorUtilities.GetValidCertDateFromValidityModel(paramsPKIX, + certPath, index), sign, workingPublicKey, certs); + } + catch (Exception e) + { + Exception cause = e.InnerException; + if (cause == null) + { + cause = e; + } + throw new PkixCertPathValidatorException(e.Message, cause, certPath, index); + } + } + + // + // (a) (4) name chaining + // + X509Name issuer = PkixCertPathValidatorUtilities.GetIssuerPrincipal(cert); + if (!issuer.Equivalent(workingIssuerName, true)) + { + throw new PkixCertPathValidatorException("IssuerName(" + issuer + + ") does not match SubjectName(" + workingIssuerName + ") of signing certificate.", null, + certPath, index); + } + } + + internal static int PrepareNextCertI1( + PkixCertPath certPath, + int index, + int explicitPolicy) + { + IList certs = certPath.Certificates; + X509Certificate cert = (X509Certificate)certs[index]; + // + // (i) + // + Asn1Sequence pc = null; + try + { + pc = DerSequence.GetInstance( + PkixCertPathValidatorUtilities.GetExtensionValue(cert, X509Extensions.PolicyConstraints)); + } + catch (Exception e) + { + throw new PkixCertPathValidatorException( + "Policy constraints extension cannot be decoded.", e, certPath, index); + } + + int tmpInt; + + if (pc != null) + { + IEnumerator policyConstraints = pc.GetEnumerator(); + + while (policyConstraints.MoveNext()) + { + try + { + Asn1TaggedObject constraint = Asn1TaggedObject.GetInstance(policyConstraints.Current); + if (constraint.TagNo == 0) + { + tmpInt = DerInteger.GetInstance(constraint, false).Value.IntValue; + if (tmpInt < explicitPolicy) + { + return tmpInt; + } + break; + } + } + catch (ArgumentException e) + { + throw new PkixCertPathValidatorException( + "Policy constraints extension contents cannot be decoded.", e, certPath, index); + } + } + } + return explicitPolicy; + } + + internal static int PrepareNextCertI2( + PkixCertPath certPath, + int index, + int policyMapping) + //throws CertPathValidatorException + { + IList certs = certPath.Certificates; + X509Certificate cert = (X509Certificate)certs[index]; + + // + // (i) + // + Asn1Sequence pc = null; + try + { + pc = DerSequence.GetInstance( + PkixCertPathValidatorUtilities.GetExtensionValue(cert, X509Extensions.PolicyConstraints)); + } + catch (Exception e) + { + throw new PkixCertPathValidatorException( + "Policy constraints extension cannot be decoded.", e, certPath, index); + } + + int tmpInt; + + if (pc != null) + { + IEnumerator policyConstraints = pc.GetEnumerator(); + + while (policyConstraints.MoveNext()) + { + try + { + Asn1TaggedObject constraint = Asn1TaggedObject.GetInstance(policyConstraints.Current); + if (constraint.TagNo == 1) + { + tmpInt = DerInteger.GetInstance(constraint, false).Value.IntValue; + if (tmpInt < policyMapping) + { + return tmpInt; + } + break; + } + } + catch (ArgumentException e) + { + throw new PkixCertPathValidatorException( + "Policy constraints extension contents cannot be decoded.", e, certPath, index); + } + } + } + return policyMapping; + } + + internal static void PrepareNextCertG( + PkixCertPath certPath, + int index, + PkixNameConstraintValidator nameConstraintValidator) + //throws CertPathValidatorException + { + IList certs = certPath.Certificates; + X509Certificate cert = (X509Certificate)certs[index]; + + // + // (g) handle the name constraints extension + // + NameConstraints nc = null; + try + { + Asn1Sequence ncSeq = DerSequence.GetInstance( + PkixCertPathValidatorUtilities.GetExtensionValue(cert, X509Extensions.NameConstraints)); + if (ncSeq != null) + { + nc = new NameConstraints(ncSeq); + } + } + catch (Exception e) + { + throw new PkixCertPathValidatorException( + "Name constraints extension could not be decoded.", e, certPath, index); + } + if (nc != null) + { + // + // (g) (1) permitted subtrees + // + Asn1Sequence permitted = nc.PermittedSubtrees; + if (permitted != null) + { + try + { + nameConstraintValidator.IntersectPermittedSubtree(permitted); + } + catch (Exception ex) + { + throw new PkixCertPathValidatorException( + "Permitted subtrees cannot be build from name constraints extension.", ex, certPath, index); + } + } + + // + // (g) (2) excluded subtrees + // + Asn1Sequence excluded = nc.ExcludedSubtrees; + if (excluded != null) + { + IEnumerator e = excluded.GetEnumerator(); + try + { + while (e.MoveNext()) + { + GeneralSubtree subtree = GeneralSubtree.GetInstance(e.Current); + nameConstraintValidator.AddExcludedSubtree(subtree); + } + } + catch (Exception ex) + { + throw new PkixCertPathValidatorException( + "Excluded subtrees cannot be build from name constraints extension.", ex, certPath, index); + } + } + } + } + + internal static int PrepareNextCertJ( + PkixCertPath certPath, + int index, + int inhibitAnyPolicy) + //throws CertPathValidatorException + { + IList certs = certPath.Certificates; + X509Certificate cert = (X509Certificate)certs[index]; + + // + // (j) + // + DerInteger iap = null; + try + { + iap = DerInteger.GetInstance( + PkixCertPathValidatorUtilities.GetExtensionValue(cert, X509Extensions.InhibitAnyPolicy)); + } + catch (Exception e) + { + throw new PkixCertPathValidatorException( + "Inhibit any-policy extension cannot be decoded.", e, certPath, index); + } + + if (iap != null) + { + int _inhibitAnyPolicy = iap.Value.IntValue; + + if (_inhibitAnyPolicy < inhibitAnyPolicy) + return _inhibitAnyPolicy; + } + return inhibitAnyPolicy; + } + + internal static void PrepareNextCertK( + PkixCertPath certPath, + int index) + //throws CertPathValidatorException + { + IList certs = certPath.Certificates; + X509Certificate cert = (X509Certificate)certs[index]; + // + // (k) + // + BasicConstraints bc = null; + try + { + bc = BasicConstraints.GetInstance( + PkixCertPathValidatorUtilities.GetExtensionValue(cert, X509Extensions.BasicConstraints)); + } + catch (Exception e) + { + throw new PkixCertPathValidatorException("Basic constraints extension cannot be decoded.", e, certPath, + index); + } + if (bc != null) + { + if (!(bc.IsCA())) + throw new PkixCertPathValidatorException("Not a CA certificate"); + } + else + { + throw new PkixCertPathValidatorException("Intermediate certificate lacks BasicConstraints"); + } + } + + internal static int PrepareNextCertL( + PkixCertPath certPath, + int index, + int maxPathLength) + //throws CertPathValidatorException + { + IList certs = certPath.Certificates; + X509Certificate cert = (X509Certificate)certs[index]; + // + // (l) + // + if (!PkixCertPathValidatorUtilities.IsSelfIssued(cert)) + { + if (maxPathLength <= 0) + { + throw new PkixCertPathValidatorException("Max path length not greater than zero", null, certPath, index); + } + + return maxPathLength - 1; + } + return maxPathLength; + } + + internal static int PrepareNextCertM( + PkixCertPath certPath, + int index, + int maxPathLength) + //throws CertPathValidatorException + { + IList certs = certPath.Certificates; + X509Certificate cert = (X509Certificate)certs[index]; + + // + // (m) + // + BasicConstraints bc = null; + try + { + bc = BasicConstraints.GetInstance( + PkixCertPathValidatorUtilities.GetExtensionValue(cert, X509Extensions.BasicConstraints)); + } + catch (Exception e) + { + throw new PkixCertPathValidatorException("Basic constraints extension cannot be decoded.", e, certPath, + index); + } + if (bc != null) + { + BigInteger _pathLengthConstraint = bc.PathLenConstraint; + + if (_pathLengthConstraint != null) + { + int _plc = _pathLengthConstraint.IntValue; + + if (_plc < maxPathLength) + { + return _plc; + } + } + } + return maxPathLength; + } + + internal static void PrepareNextCertN( + PkixCertPath certPath, + int index) + //throws CertPathValidatorException + { + IList certs = certPath.Certificates; + X509Certificate cert = (X509Certificate)certs[index]; + + // + // (n) + // + bool[] _usage = cert.GetKeyUsage(); + + if ((_usage != null) && !_usage[Rfc3280CertPathUtilities.KEY_CERT_SIGN]) + { + throw new PkixCertPathValidatorException( + "Issuer certificate keyusage extension is critical and does not permit key signing.", null, + certPath, index); + } + } + + internal static void PrepareNextCertO( + PkixCertPath certPath, + int index, + ISet criticalExtensions, + IList pathCheckers) + //throws CertPathValidatorException + { + IList certs = certPath.Certificates; + X509Certificate cert = (X509Certificate)certs[index]; + + // + // (o) + // + IEnumerator tmpIter = pathCheckers.GetEnumerator(); + while (tmpIter.MoveNext()) + { + try + { + ((PkixCertPathChecker)tmpIter.Current).Check(cert, criticalExtensions); + } + catch (PkixCertPathValidatorException e) + { + throw new PkixCertPathValidatorException(e.Message, e.InnerException, certPath, index); + } + } + if (!criticalExtensions.IsEmpty) + { + throw new PkixCertPathValidatorException("Certificate has unsupported critical extension.", null, certPath, + index); + } + } + + internal static int PrepareNextCertH1( + PkixCertPath certPath, + int index, + int explicitPolicy) + { + IList certs = certPath.Certificates; + X509Certificate cert = (X509Certificate)certs[index]; + + // + // (h) + // + if (!PkixCertPathValidatorUtilities.IsSelfIssued(cert)) + { + // + // (1) + // + if (explicitPolicy != 0) + return explicitPolicy - 1; + } + return explicitPolicy; + } + + internal static int PrepareNextCertH2( + PkixCertPath certPath, + int index, + int policyMapping) + { + IList certs = certPath.Certificates; + X509Certificate cert = (X509Certificate)certs[index]; + + // + // (h) + // + if (!PkixCertPathValidatorUtilities.IsSelfIssued(cert)) + { + // + // (2) + // + if (policyMapping != 0) + return policyMapping - 1; + } + return policyMapping; + } + + + internal static int PrepareNextCertH3( + PkixCertPath certPath, + int index, + int inhibitAnyPolicy) + { + IList certs = certPath.Certificates; + X509Certificate cert = (X509Certificate)certs[index]; + + // + // (h) + // + if (!PkixCertPathValidatorUtilities.IsSelfIssued(cert)) + { + // + // (3) + // + if (inhibitAnyPolicy != 0) + return inhibitAnyPolicy - 1; + } + return inhibitAnyPolicy; + } + + internal static int WrapupCertA( + int explicitPolicy, + X509Certificate cert) + { + // + // (a) + // + if (!PkixCertPathValidatorUtilities.IsSelfIssued(cert) && (explicitPolicy != 0)) + { + explicitPolicy--; + } + return explicitPolicy; + } + + internal static int WrapupCertB( + PkixCertPath certPath, + int index, + int explicitPolicy) + //throws CertPathValidatorException + { + IList certs = certPath.Certificates; + X509Certificate cert = (X509Certificate)certs[index]; + + // + // (b) + // + int tmpInt; + Asn1Sequence pc = null; + try + { + pc = DerSequence.GetInstance( + PkixCertPathValidatorUtilities.GetExtensionValue(cert, X509Extensions.PolicyConstraints)); + } + catch (Exception e) + { + throw new PkixCertPathValidatorException("Policy constraints could not be decoded.", e, certPath, index); + } + + if (pc != null) + { + IEnumerator policyConstraints = pc.GetEnumerator(); + + while (policyConstraints.MoveNext()) + { + Asn1TaggedObject constraint = (Asn1TaggedObject)policyConstraints.Current; + switch (constraint.TagNo) + { + case 0: + try + { + tmpInt = DerInteger.GetInstance(constraint, false).Value.IntValue; + } + catch (Exception e) + { + throw new PkixCertPathValidatorException( + "Policy constraints requireExplicitPolicy field could not be decoded.", e, certPath, + index); + } + if (tmpInt == 0) + { + return 0; + } + break; + } + } + } + return explicitPolicy; + } + + internal static void WrapupCertF( + PkixCertPath certPath, + int index, + IList pathCheckers, + ISet criticalExtensions) + //throws CertPathValidatorException + { + IList certs = certPath.Certificates; + X509Certificate cert = (X509Certificate)certs[index]; + IEnumerator tmpIter = pathCheckers.GetEnumerator(); + + while (tmpIter.MoveNext()) + { + try + { + ((PkixCertPathChecker)tmpIter.Current).Check(cert, criticalExtensions); + } + catch (PkixCertPathValidatorException e) + { + throw new PkixCertPathValidatorException("Additional certificate path checker failed.", e, certPath, + index); + } + } + + if (!criticalExtensions.IsEmpty) + { + throw new PkixCertPathValidatorException("Certificate has unsupported critical extension", + null, certPath, index); + } + } + + internal static PkixPolicyNode WrapupCertG( + PkixCertPath certPath, + PkixParameters paramsPKIX, + ISet userInitialPolicySet, + int index, + IList[] policyNodes, + PkixPolicyNode validPolicyTree, + ISet acceptablePolicies) + { + int n = certPath.Certificates.Count; + + // + // (g) + // + PkixPolicyNode intersection; + + // + // (g) (i) + // + if (validPolicyTree == null) + { + if (paramsPKIX.IsExplicitPolicyRequired) + { + throw new PkixCertPathValidatorException( + "Explicit policy requested but none available.", null, certPath, index); + } + intersection = null; + } + else if (PkixCertPathValidatorUtilities.IsAnyPolicy(userInitialPolicySet)) // (g) + // (ii) + { + if (paramsPKIX.IsExplicitPolicyRequired) + { + if (acceptablePolicies.IsEmpty) + { + throw new PkixCertPathValidatorException( + "Explicit policy requested but none available.", null, certPath, index); + } + else + { + ISet _validPolicyNodeSet = new HashSet(); + + for (int j = 0; j < policyNodes.Length; j++) + { + IList _nodeDepth = policyNodes[j]; + + for (int k = 0; k < _nodeDepth.Count; k++) + { + PkixPolicyNode _node = (PkixPolicyNode)_nodeDepth[k]; + + if (Rfc3280CertPathUtilities.ANY_POLICY.Equals(_node.ValidPolicy)) + { + foreach (object o in _node.Children) + { + _validPolicyNodeSet.Add(o); + } + } + } + } + + foreach (PkixPolicyNode _node in _validPolicyNodeSet) + { + string _validPolicy = _node.ValidPolicy; + + if (!acceptablePolicies.Contains(_validPolicy)) + { + // TODO? + // validPolicyTree = + // removePolicyNode(validPolicyTree, policyNodes, + // _node); + } + } + if (validPolicyTree != null) + { + for (int j = (n - 1); j >= 0; j--) + { + IList nodes = policyNodes[j]; + + for (int k = 0; k < nodes.Count; k++) + { + PkixPolicyNode node = (PkixPolicyNode)nodes[k]; + if (!node.HasChildren) + { + validPolicyTree = PkixCertPathValidatorUtilities.RemovePolicyNode(validPolicyTree, + policyNodes, node); + } + } + } + } + } + } + + intersection = validPolicyTree; + } + else + { + // + // (g) (iii) + // + // This implementation is not exactly same as the one described in + // RFC3280. + // However, as far as the validation result is concerned, both + // produce + // adequate result. The only difference is whether AnyPolicy is + // remain + // in the policy tree or not. + // + // (g) (iii) 1 + // + ISet _validPolicyNodeSet = new HashSet(); + + for (int j = 0; j < policyNodes.Length; j++) + { + IList _nodeDepth = policyNodes[j]; + + for (int k = 0; k < _nodeDepth.Count; k++) + { + PkixPolicyNode _node = (PkixPolicyNode)_nodeDepth[k]; + + if (Rfc3280CertPathUtilities.ANY_POLICY.Equals(_node.ValidPolicy)) + { + foreach (PkixPolicyNode _c_node in _node.Children) + { + if (!Rfc3280CertPathUtilities.ANY_POLICY.Equals(_c_node.ValidPolicy)) + { + _validPolicyNodeSet.Add(_c_node); + } + } + } + } + } + + // + // (g) (iii) 2 + // + IEnumerator _vpnsIter = _validPolicyNodeSet.GetEnumerator(); + while (_vpnsIter.MoveNext()) + { + PkixPolicyNode _node = (PkixPolicyNode)_vpnsIter.Current; + string _validPolicy = _node.ValidPolicy; + + if (!userInitialPolicySet.Contains(_validPolicy)) + { + validPolicyTree = PkixCertPathValidatorUtilities.RemovePolicyNode(validPolicyTree, policyNodes, _node); + } + } + + // + // (g) (iii) 4 + // + if (validPolicyTree != null) + { + for (int j = (n - 1); j >= 0; j--) + { + IList nodes = policyNodes[j]; + + for (int k = 0; k < nodes.Count; k++) + { + PkixPolicyNode node = (PkixPolicyNode)nodes[k]; + if (!node.HasChildren) + { + validPolicyTree = PkixCertPathValidatorUtilities.RemovePolicyNode(validPolicyTree, policyNodes, + node); + } + } + } + } + + intersection = validPolicyTree; + } + return intersection; + } + + /** + * If use-deltas is set, verify the issuer and scope of the delta CRL. + * + * @param deltaCRL The delta CRL. + * @param completeCRL The complete CRL. + * @param pkixParams The PKIX paramaters. + * @throws AnnotatedException if an exception occurs. + */ + internal static void ProcessCrlC( + X509Crl deltaCRL, + X509Crl completeCRL, + PkixParameters pkixParams) + { + if (deltaCRL == null) + return; + + IssuingDistributionPoint completeidp = null; + try + { + completeidp = IssuingDistributionPoint.GetInstance( + PkixCertPathValidatorUtilities.GetExtensionValue(completeCRL, X509Extensions.IssuingDistributionPoint)); + } + catch (Exception e) + { + throw new Exception("000 Issuing distribution point extension could not be decoded.", e); + } + + if (pkixParams.IsUseDeltasEnabled) + { + // (c) (1) + if (!deltaCRL.IssuerDN.Equivalent(completeCRL.IssuerDN, true)) + throw new Exception("Complete CRL issuer does not match delta CRL issuer."); + + // (c) (2) + IssuingDistributionPoint deltaidp = null; + try + { + deltaidp = IssuingDistributionPoint.GetInstance( + PkixCertPathValidatorUtilities.GetExtensionValue(deltaCRL, X509Extensions.IssuingDistributionPoint)); + } + catch (Exception e) + { + throw new Exception( + "Issuing distribution point extension from delta CRL could not be decoded.", e); + } + + if (!Platform.Equals(completeidp, deltaidp)) + { + throw new Exception( + "Issuing distribution point extension from delta CRL and complete CRL does not match."); + } + + // (c) (3) + Asn1Object completeKeyIdentifier = null; + try + { + completeKeyIdentifier = PkixCertPathValidatorUtilities.GetExtensionValue( + completeCRL, X509Extensions.AuthorityKeyIdentifier); + } + catch (Exception e) + { + throw new Exception( + "Authority key identifier extension could not be extracted from complete CRL.", e); + } + + Asn1Object deltaKeyIdentifier = null; + try + { + deltaKeyIdentifier = PkixCertPathValidatorUtilities.GetExtensionValue( + deltaCRL, X509Extensions.AuthorityKeyIdentifier); + } + catch (Exception e) + { + throw new Exception( + "Authority key identifier extension could not be extracted from delta CRL.", e); + } + + if (completeKeyIdentifier == null) + throw new Exception("CRL authority key identifier is null."); + + if (deltaKeyIdentifier == null) + throw new Exception("Delta CRL authority key identifier is null."); + + if (!completeKeyIdentifier.Equals(deltaKeyIdentifier)) + { + throw new Exception( + "Delta CRL authority key identifier does not match complete CRL authority key identifier."); + } + } + } + + internal static void ProcessCrlI( + DateTime validDate, + X509Crl deltacrl, + object cert, + CertStatus certStatus, + PkixParameters pkixParams) + { + if (pkixParams.IsUseDeltasEnabled && deltacrl != null) + { + PkixCertPathValidatorUtilities.GetCertStatus(validDate, deltacrl, cert, certStatus); + } + } + + internal static void ProcessCrlJ( + DateTime validDate, + X509Crl completecrl, + object cert, + CertStatus certStatus) + { + if (certStatus.Status == CertStatus.Unrevoked) + { + PkixCertPathValidatorUtilities.GetCertStatus(validDate, completecrl, cert, certStatus); + } + } + + internal static PkixPolicyNode ProcessCertE( + PkixCertPath certPath, + int index, + PkixPolicyNode validPolicyTree) + { + IList certs = certPath.Certificates; + X509Certificate cert = (X509Certificate)certs[index]; + + // + // (e) + // + Asn1Sequence certPolicies = null; + try + { + certPolicies = DerSequence.GetInstance( + PkixCertPathValidatorUtilities.GetExtensionValue(cert, X509Extensions.CertificatePolicies)); + } + catch (Exception e) + { + throw new PkixCertPathValidatorException("Could not read certificate policies extension from certificate.", + e, certPath, index); + } + if (certPolicies == null) + { + validPolicyTree = null; + } + return validPolicyTree; + } + + internal static readonly string[] CrlReasons = new string[] + { + "unspecified", + "keyCompromise", + "cACompromise", + "affiliationChanged", + "superseded", + "cessationOfOperation", + "certificateHold", + "unknown", + "removeFromCRL", + "privilegeWithdrawn", + "aACompromise" + }; + } +} diff --git a/bc-sharp-crypto/src/pkix/Rfc3281CertPathUtilities.cs b/bc-sharp-crypto/src/pkix/Rfc3281CertPathUtilities.cs new file mode 100644 index 0000000..101ef5e --- /dev/null +++ b/bc-sharp-crypto/src/pkix/Rfc3281CertPathUtilities.cs @@ -0,0 +1,608 @@ +using System; +using System.Collections; +using System.Globalization; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Security.Certificates; +using Org.BouncyCastle.Utilities.Collections; +using Org.BouncyCastle.X509; +using Org.BouncyCastle.X509.Store; + +namespace Org.BouncyCastle.Pkix +{ + internal class Rfc3281CertPathUtilities + { + internal static void ProcessAttrCert7( + IX509AttributeCertificate attrCert, + PkixCertPath certPath, + PkixCertPath holderCertPath, + PkixParameters pkixParams) + { + // TODO: + // AA Controls + // Attribute encryption + // Proxy + ISet critExtOids = attrCert.GetCriticalExtensionOids(); + + // 7.1 + // process extensions + + // target information checked in step 6 / X509AttributeCertStoreSelector + if (critExtOids.Contains(X509Extensions.TargetInformation.Id)) + { + try + { + TargetInformation.GetInstance(PkixCertPathValidatorUtilities + .GetExtensionValue(attrCert, X509Extensions.TargetInformation)); + } + catch (Exception e) + { + throw new PkixCertPathValidatorException( + "Target information extension could not be read.", e); + } + } + critExtOids.Remove(X509Extensions.TargetInformation.Id); + foreach (PkixAttrCertChecker checker in pkixParams.GetAttrCertCheckers()) + { + checker.Check(attrCert, certPath, holderCertPath, critExtOids); + } + if (!critExtOids.IsEmpty) + { + throw new PkixCertPathValidatorException( + "Attribute certificate contains unsupported critical extensions: " + + critExtOids); + } + } + + /** + * Checks if an attribute certificate is revoked. + * + * @param attrCert Attribute certificate to check if it is revoked. + * @param paramsPKIX PKIX parameters. + * @param issuerCert The issuer certificate of the attribute certificate + * attrCert. + * @param validDate The date when the certificate revocation status should + * be checked. + * @param certPathCerts The certificates of the certification path to be + * checked. + * + * @throws CertPathValidatorException if the certificate is revoked or the + * status cannot be checked or some error occurs. + */ + internal static void CheckCrls( + IX509AttributeCertificate attrCert, + PkixParameters paramsPKIX, + X509Certificate issuerCert, + DateTime validDate, + IList certPathCerts) + { + if (paramsPKIX.IsRevocationEnabled) + { + // check if revocation is available + if (attrCert.GetExtensionValue(X509Extensions.NoRevAvail) == null) + { + CrlDistPoint crldp = null; + try + { + crldp = CrlDistPoint.GetInstance( + PkixCertPathValidatorUtilities.GetExtensionValue( + attrCert, X509Extensions.CrlDistributionPoints)); + } + catch (Exception e) + { + throw new PkixCertPathValidatorException( + "CRL distribution point extension could not be read.", e); + } + try + { + PkixCertPathValidatorUtilities + .AddAdditionalStoresFromCrlDistributionPoint(crldp, paramsPKIX); + } + catch (Exception e) + { + throw new PkixCertPathValidatorException( + "No additional CRL locations could be decoded from CRL distribution point extension.", e); + } + CertStatus certStatus = new CertStatus(); + ReasonsMask reasonsMask = new ReasonsMask(); + + Exception lastException = null; + bool validCrlFound = false; + // for each distribution point + if (crldp != null) + { + DistributionPoint[] dps = null; + try + { + dps = crldp.GetDistributionPoints(); + } + catch (Exception e) + { + throw new PkixCertPathValidatorException( + "Distribution points could not be read.", e); + } + try + { + for (int i = 0; i < dps.Length + && certStatus.Status == CertStatus.Unrevoked + && !reasonsMask.IsAllReasons; i++) + { + PkixParameters paramsPKIXClone = (PkixParameters) paramsPKIX + .Clone(); + CheckCrl(dps[i], attrCert, paramsPKIXClone, + validDate, issuerCert, certStatus, reasonsMask, + certPathCerts); + validCrlFound = true; + } + } + catch (Exception e) + { + lastException = new Exception( + "No valid CRL for distribution point found.", e); + } + } + + /* + * If the revocation status has not been determined, repeat the + * process above with any available CRLs not specified in a + * distribution point but issued by the certificate issuer. + */ + + if (certStatus.Status == CertStatus.Unrevoked + && !reasonsMask.IsAllReasons) + { + try + { + /* + * assume a DP with both the reasons and the cRLIssuer + * fields omitted and a distribution point name of the + * certificate issuer. + */ + Asn1Object issuer = null; + try + { + issuer = new Asn1InputStream( + attrCert.Issuer.GetPrincipals()[0].GetEncoded()).ReadObject(); + } + catch (Exception e) + { + throw new Exception( + "Issuer from certificate for CRL could not be reencoded.", + e); + } + DistributionPoint dp = new DistributionPoint( + new DistributionPointName(0, new GeneralNames( + new GeneralName(GeneralName.DirectoryName, issuer))), null, null); + PkixParameters paramsPKIXClone = (PkixParameters) paramsPKIX.Clone(); + CheckCrl(dp, attrCert, paramsPKIXClone, validDate, + issuerCert, certStatus, reasonsMask, certPathCerts); + validCrlFound = true; + } + catch (Exception e) + { + lastException = new Exception( + "No valid CRL for distribution point found.", e); + } + } + + if (!validCrlFound) + { + throw new PkixCertPathValidatorException( + "No valid CRL found.", lastException); + } + if (certStatus.Status != CertStatus.Unrevoked) + { + // This format is enforced by the NistCertPath tests + string formattedDate = certStatus.RevocationDate.Value.ToString( + "ddd MMM dd HH:mm:ss K yyyy"); + string message = "Attribute certificate revocation after " + + formattedDate; + message += ", reason: " + + Rfc3280CertPathUtilities.CrlReasons[certStatus.Status]; + throw new PkixCertPathValidatorException(message); + } + if (!reasonsMask.IsAllReasons + && certStatus.Status == CertStatus.Unrevoked) + { + certStatus.Status = CertStatus.Undetermined; + } + if (certStatus.Status == CertStatus.Undetermined) + { + throw new PkixCertPathValidatorException( + "Attribute certificate status could not be determined."); + } + + } + else + { + if (attrCert.GetExtensionValue(X509Extensions.CrlDistributionPoints) != null + || attrCert.GetExtensionValue(X509Extensions.AuthorityInfoAccess) != null) + { + throw new PkixCertPathValidatorException( + "No rev avail extension is set, but also an AC revocation pointer."); + } + } + } + } + + internal static void AdditionalChecks( + IX509AttributeCertificate attrCert, + PkixParameters pkixParams) + { + // 1 + foreach (string oid in pkixParams.GetProhibitedACAttributes()) + { + if (attrCert.GetAttributes(oid) != null) + { + throw new PkixCertPathValidatorException( + "Attribute certificate contains prohibited attribute: " + + oid + "."); + } + } + foreach (string oid in pkixParams.GetNecessaryACAttributes()) + { + if (attrCert.GetAttributes(oid) == null) + { + throw new PkixCertPathValidatorException( + "Attribute certificate does not contain necessary attribute: " + + oid + "."); + } + } + } + + internal static void ProcessAttrCert5( + IX509AttributeCertificate attrCert, + PkixParameters pkixParams) + { + try + { + attrCert.CheckValidity(PkixCertPathValidatorUtilities.GetValidDate(pkixParams)); + } + catch (CertificateExpiredException e) + { + throw new PkixCertPathValidatorException( + "Attribute certificate is not valid.", e); + } + catch (CertificateNotYetValidException e) + { + throw new PkixCertPathValidatorException( + "Attribute certificate is not valid.", e); + } + } + + internal static void ProcessAttrCert4( + X509Certificate acIssuerCert, + PkixParameters pkixParams) + { + ISet set = pkixParams.GetTrustedACIssuers(); + bool trusted = false; + foreach (TrustAnchor anchor in set) + { + IDictionary symbols = X509Name.RFC2253Symbols; + if (acIssuerCert.SubjectDN.ToString(false, symbols).Equals(anchor.CAName) + || acIssuerCert.Equals(anchor.TrustedCert)) + { + trusted = true; + } + } + if (!trusted) + { + throw new PkixCertPathValidatorException( + "Attribute certificate issuer is not directly trusted."); + } + } + + internal static void ProcessAttrCert3( + X509Certificate acIssuerCert, + PkixParameters pkixParams) + { + if (acIssuerCert.GetKeyUsage() != null + && (!acIssuerCert.GetKeyUsage()[0] && !acIssuerCert.GetKeyUsage()[1])) + { + throw new PkixCertPathValidatorException( + "Attribute certificate issuer public key cannot be used to validate digital signatures."); + } + if (acIssuerCert.GetBasicConstraints() != -1) + { + throw new PkixCertPathValidatorException( + "Attribute certificate issuer is also a public key certificate issuer."); + } + } + + internal static PkixCertPathValidatorResult ProcessAttrCert2( + PkixCertPath certPath, + PkixParameters pkixParams) + { + PkixCertPathValidator validator = new PkixCertPathValidator(); + + try + { + return validator.Validate(certPath, pkixParams); + } + catch (PkixCertPathValidatorException e) + { + throw new PkixCertPathValidatorException( + "Certification path for issuer certificate of attribute certificate could not be validated.", + e); + } + } + + /** + * Searches for a holder public key certificate and verifies its + * certification path. + * + * @param attrCert the attribute certificate. + * @param pkixParams The PKIX parameters. + * @return The certificate path of the holder certificate. + * @throws Exception if + *
      + *
    • no public key certificate can be found although holder + * information is given by an entity name or a base certificate + * ID
    • + *
    • support classes cannot be created
    • + *
    • no certification path for the public key certificate can + * be built
    • + *
    + */ + internal static PkixCertPath ProcessAttrCert1( + IX509AttributeCertificate attrCert, + PkixParameters pkixParams) + { + PkixCertPathBuilderResult result = null; + // find holder PKCs + ISet holderPKCs = new HashSet(); + if (attrCert.Holder.GetIssuer() != null) + { + X509CertStoreSelector selector = new X509CertStoreSelector(); + selector.SerialNumber = attrCert.Holder.SerialNumber; + X509Name[] principals = attrCert.Holder.GetIssuer(); + for (int i = 0; i < principals.Length; i++) + { + try + { +// if (principals[i] is X500Principal) + { + selector.Issuer = principals[i]; + } + holderPKCs.AddAll(PkixCertPathValidatorUtilities + .FindCertificates(selector, pkixParams.GetStores())); + } + catch (Exception e) + { + throw new PkixCertPathValidatorException( + "Public key certificate for attribute certificate cannot be searched.", + e); + } + } + if (holderPKCs.IsEmpty) + { + throw new PkixCertPathValidatorException( + "Public key certificate specified in base certificate ID for attribute certificate cannot be found."); + } + } + if (attrCert.Holder.GetEntityNames() != null) + { + X509CertStoreSelector selector = new X509CertStoreSelector(); + X509Name[] principals = attrCert.Holder.GetEntityNames(); + for (int i = 0; i < principals.Length; i++) + { + try + { +// if (principals[i] is X500Principal) + { + selector.Issuer = principals[i]; + } + holderPKCs.AddAll(PkixCertPathValidatorUtilities + .FindCertificates(selector, pkixParams.GetStores())); + } + catch (Exception e) + { + throw new PkixCertPathValidatorException( + "Public key certificate for attribute certificate cannot be searched.", + e); + } + } + if (holderPKCs.IsEmpty) + { + throw new PkixCertPathValidatorException( + "Public key certificate specified in entity name for attribute certificate cannot be found."); + } + } + + // verify cert paths for PKCs + PkixBuilderParameters parameters = (PkixBuilderParameters) + PkixBuilderParameters.GetInstance(pkixParams); + + PkixCertPathValidatorException lastException = null; + foreach (X509Certificate cert in holderPKCs) + { + X509CertStoreSelector selector = new X509CertStoreSelector(); + selector.Certificate = cert; + parameters.SetTargetConstraints(selector); + + PkixCertPathBuilder builder = new PkixCertPathBuilder(); + + try + { + result = builder.Build(PkixBuilderParameters.GetInstance(parameters)); + } + catch (PkixCertPathBuilderException e) + { + lastException = new PkixCertPathValidatorException( + "Certification path for public key certificate of attribute certificate could not be build.", + e); + } + } + if (lastException != null) + { + throw lastException; + } + return result.CertPath; + } + + /** + * + * Checks a distribution point for revocation information for the + * certificate attrCert. + * + * @param dp The distribution point to consider. + * @param attrCert The attribute certificate which should be checked. + * @param paramsPKIX PKIX parameters. + * @param validDate The date when the certificate revocation status should + * be checked. + * @param issuerCert Certificate to check if it is revoked. + * @param reasonMask The reasons mask which is already checked. + * @param certPathCerts The certificates of the certification path to be + * checked. + * @throws Exception if the certificate is revoked or the status + * cannot be checked or some error occurs. + */ + private static void CheckCrl( + DistributionPoint dp, + IX509AttributeCertificate attrCert, + PkixParameters paramsPKIX, + DateTime validDate, + X509Certificate issuerCert, + CertStatus certStatus, + ReasonsMask reasonMask, + IList certPathCerts) + { + /* + * 4.3.6 No Revocation Available + * + * The noRevAvail extension, defined in [X.509-2000], allows an AC + * issuer to indicate that no revocation information will be made + * available for this AC. + */ + if (attrCert.GetExtensionValue(X509Extensions.NoRevAvail) != null) + { + return; + } + + DateTime currentDate = DateTime.UtcNow; + if (validDate.CompareTo(currentDate) > 0) + { + throw new Exception("Validation time is in future."); + } + + // (a) + /* + * We always get timely valid CRLs, so there is no step (a) (1). + * "locally cached" CRLs are assumed to be in getStore(), additional + * CRLs must be enabled in the ExtendedPkixParameters and are in + * getAdditionalStore() + */ + ISet crls = PkixCertPathValidatorUtilities.GetCompleteCrls(dp, attrCert, + currentDate, paramsPKIX); + bool validCrlFound = false; + Exception lastException = null; + + IEnumerator crl_iter = crls.GetEnumerator(); + + while (crl_iter.MoveNext() + && certStatus.Status == CertStatus.Unrevoked + && !reasonMask.IsAllReasons) + { + try + { + X509Crl crl = (X509Crl) crl_iter.Current; + + // (d) + ReasonsMask interimReasonsMask = Rfc3280CertPathUtilities.ProcessCrlD(crl, dp); + + // (e) + /* + * The reasons mask is updated at the end, so only valid CRLs + * can update it. If this CRL does not contain new reasons it + * must be ignored. + */ + if (!interimReasonsMask.HasNewReasons(reasonMask)) + { + continue; + } + + // (f) + ISet keys = Rfc3280CertPathUtilities.ProcessCrlF(crl, attrCert, + null, null, paramsPKIX, certPathCerts); + // (g) + AsymmetricKeyParameter pubKey = Rfc3280CertPathUtilities.ProcessCrlG(crl, keys); + + X509Crl deltaCRL = null; + + if (paramsPKIX.IsUseDeltasEnabled) + { + // get delta CRLs + ISet deltaCRLs = PkixCertPathValidatorUtilities.GetDeltaCrls( + currentDate, paramsPKIX, crl); + // we only want one valid delta CRL + // (h) + deltaCRL = Rfc3280CertPathUtilities.ProcessCrlH(deltaCRLs, pubKey); + } + + /* + * CRL must be be valid at the current time, not the validation + * time. If a certificate is revoked with reason keyCompromise, + * cACompromise, it can be used for forgery, also for the past. + * This reason may not be contained in older CRLs. + */ + + /* + * in the chain model signatures stay valid also after the + * certificate has been expired, so they do not have to be in + * the CRL vality time + */ + if (paramsPKIX.ValidityModel != PkixParameters.ChainValidityModel) + { + /* + * if a certificate has expired, but was revoked, it is not + * more in the CRL, so it would be regarded as valid if the + * first check is not done + */ + if (attrCert.NotAfter.CompareTo(crl.ThisUpdate) < 0) + { + throw new Exception( + "No valid CRL for current time found."); + } + } + + Rfc3280CertPathUtilities.ProcessCrlB1(dp, attrCert, crl); + + // (b) (2) + Rfc3280CertPathUtilities.ProcessCrlB2(dp, attrCert, crl); + + // (c) + Rfc3280CertPathUtilities.ProcessCrlC(deltaCRL, crl, paramsPKIX); + + // (i) + Rfc3280CertPathUtilities.ProcessCrlI(validDate, deltaCRL, + attrCert, certStatus, paramsPKIX); + + // (j) + Rfc3280CertPathUtilities.ProcessCrlJ(validDate, crl, attrCert, + certStatus); + + // (k) + if (certStatus.Status == CrlReason.RemoveFromCrl) + { + certStatus.Status = CertStatus.Unrevoked; + } + + // update reasons mask + reasonMask.AddReasons(interimReasonsMask); + validCrlFound = true; + } + catch (Exception e) + { + lastException = e; + } + } + if (!validCrlFound) + { + throw lastException; + } + } + } +} diff --git a/bc-sharp-crypto/src/pkix/TrustAnchor.cs b/bc-sharp-crypto/src/pkix/TrustAnchor.cs new file mode 100644 index 0000000..22078ba --- /dev/null +++ b/bc-sharp-crypto/src/pkix/TrustAnchor.cs @@ -0,0 +1,259 @@ +using System; +using System.IO; +using System.Text; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.X509; + +namespace Org.BouncyCastle.Pkix +{ + /// + /// A trust anchor or most-trusted Certification Authority (CA). + /// + /// This class represents a "most-trusted CA", which is used as a trust anchor + /// for validating X.509 certification paths. A most-trusted CA includes the + /// public key of the CA, the CA's name, and any constraints upon the set of + /// paths which may be validated using this key. These parameters can be + /// specified in the form of a trusted X509Certificate or as individual + /// parameters. + /// + public class TrustAnchor + { + private readonly AsymmetricKeyParameter pubKey; + private readonly string caName; + private readonly X509Name caPrincipal; + private readonly X509Certificate trustedCert; + private byte[] ncBytes; + private NameConstraints nc; + + /// + /// Creates an instance of TrustAnchor with the specified X509Certificate and + /// optional name constraints, which are intended to be used as additional + /// constraints when validating an X.509 certification path. + /// The name constraints are specified as a byte array. This byte array + /// should contain the DER encoded form of the name constraints, as they + /// would appear in the NameConstraints structure defined in RFC 2459 and + /// X.509. The ASN.1 definition of this structure appears below. + /// + ///
    +	    ///	NameConstraints ::= SEQUENCE {
    +	    ///		permittedSubtrees       [0]     GeneralSubtrees OPTIONAL,
    +	    ///		excludedSubtrees        [1]     GeneralSubtrees OPTIONAL }
    +	    ///	   
    +        /// GeneralSubtrees ::= SEQUENCE SIZE (1..MAX) OF GeneralSubtree
    +        /// 
    +        ///		GeneralSubtree ::= SEQUENCE {
    +        ///		base                    GeneralName,
    +        ///		minimum         [0]     BaseDistance DEFAULT 0,
    +        ///		maximum         [1]     BaseDistance OPTIONAL }
    +        ///		
    +        ///		BaseDistance ::= INTEGER (0..MAX)
    +		///
    +		///		GeneralName ::= CHOICE {
    +		///		otherName                       [0]     OtherName,
    +		///		rfc822Name                      [1]     IA5String,
    +		///		dNSName                         [2]     IA5String,
    +		///		x400Address                     [3]     ORAddress,
    +		///		directoryName                   [4]     Name,
    +		///		ediPartyName                    [5]     EDIPartyName,
    +		///		uniformResourceIdentifier       [6]     IA5String,
    +		///		iPAddress                       [7]     OCTET STRING,
    +		///		registeredID                    [8]     OBJECT IDENTIFIER}
    +		///	
    + /// + /// Note that the name constraints byte array supplied is cloned to protect + /// against subsequent modifications. + ///
    + /// a trusted X509Certificate + /// a byte array containing the ASN.1 DER encoding of a + /// NameConstraints extension to be used for checking name + /// constraints. Only the value of the extension is included, not + /// the OID or criticality flag. Specify null to omit the + /// parameter. + /// if the specified X509Certificate is null + public TrustAnchor( + X509Certificate trustedCert, + byte[] nameConstraints) + { + if (trustedCert == null) + throw new ArgumentNullException("trustedCert"); + + this.trustedCert = trustedCert; + this.pubKey = null; + this.caName = null; + this.caPrincipal = null; + setNameConstraints(nameConstraints); + } + + /// + /// Creates an instance of TrustAnchor where the + /// most-trusted CA is specified as an X500Principal and public key. + /// + /// + ///

    + /// Name constraints are an optional parameter, and are intended to be used + /// as additional constraints when validating an X.509 certification path. + ///

    + /// The name constraints are specified as a byte array. This byte array + /// contains the DER encoded form of the name constraints, as they + /// would appear in the NameConstraints structure defined in RFC 2459 + /// and X.509. The ASN.1 notation for this structure is supplied in the + /// documentation for the other constructors. + ///

    + /// Note that the name constraints byte array supplied here is cloned to + /// protect against subsequent modifications. + ///

    + ///
    + /// the name of the most-trusted CA as X509Name + /// the public key of the most-trusted CA + /// + /// a byte array containing the ASN.1 DER encoding of a NameConstraints extension to + /// be used for checking name constraints. Only the value of the extension is included, + /// not the OID or criticality flag. Specify null to omit the parameter. + /// + /// + /// if caPrincipal or pubKey is null + /// + public TrustAnchor( + X509Name caPrincipal, + AsymmetricKeyParameter pubKey, + byte[] nameConstraints) + { + if (caPrincipal == null) + throw new ArgumentNullException("caPrincipal"); + if (pubKey == null) + throw new ArgumentNullException("pubKey"); + + this.trustedCert = null; + this.caPrincipal = caPrincipal; + this.caName = caPrincipal.ToString(); + this.pubKey = pubKey; + setNameConstraints(nameConstraints); + } + + /// + /// Creates an instance of TrustAnchor where the most-trusted + /// CA is specified as a distinguished name and public key. Name constraints + /// are an optional parameter, and are intended to be used as additional + /// constraints when validating an X.509 certification path. + ///
    + /// The name constraints are specified as a byte array. This byte array + /// contains the DER encoded form of the name constraints, as they would + /// appear in the NameConstraints structure defined in RFC 2459 and X.509. + ///
    + /// the X.500 distinguished name of the most-trusted CA in RFC + /// 2253 string format + /// the public key of the most-trusted CA + /// a byte array containing the ASN.1 DER encoding of a + /// NameConstraints extension to be used for checking name + /// constraints. Only the value of the extension is included, not + /// the OID or criticality flag. Specify null to omit the + /// parameter. + /// throws NullPointerException, IllegalArgumentException + public TrustAnchor( + string caName, + AsymmetricKeyParameter pubKey, + byte[] nameConstraints) + { + if (caName == null) + throw new ArgumentNullException("caName"); + if (pubKey == null) + throw new ArgumentNullException("pubKey"); + if (caName.Length == 0) + throw new ArgumentException("caName can not be an empty string"); + + this.caPrincipal = new X509Name(caName); + this.pubKey = pubKey; + this.caName = caName; + this.trustedCert = null; + setNameConstraints(nameConstraints); + } + + /// + /// Returns the most-trusted CA certificate. + /// + public X509Certificate TrustedCert + { + get { return this.trustedCert; } + } + + /// + /// Returns the name of the most-trusted CA as an X509Name. + /// + public X509Name CA + { + get { return this.caPrincipal; } + } + + /// + /// Returns the name of the most-trusted CA in RFC 2253 string format. + /// + public string CAName + { + get { return this.caName; } + } + + /// + /// Returns the public key of the most-trusted CA. + /// + public AsymmetricKeyParameter CAPublicKey + { + get { return this.pubKey; } + } + + /// + /// Decode the name constraints and clone them if not null. + /// + private void setNameConstraints( + byte[] bytes) + { + if (bytes == null) + { + ncBytes = null; + nc = null; + } + else + { + ncBytes = (byte[]) bytes.Clone(); + // validate DER encoding + //nc = new NameConstraintsExtension(Boolean.FALSE, bytes); + nc = NameConstraints.GetInstance(Asn1Object.FromByteArray(bytes)); + } + } + + public byte[] GetNameConstraints + { + get { return Arrays.Clone(ncBytes); } + } + + /// + /// Returns a formatted string describing the TrustAnchor. + /// + /// a formatted string describing the TrustAnchor + public override string ToString() + { + // TODO Some of the sub-objects might not implement ToString() properly + string nl = Platform.NewLine; + StringBuilder sb = new StringBuilder(); + sb.Append("["); + sb.Append(nl); + if (this.pubKey != null) + { + sb.Append(" Trusted CA Public Key: ").Append(this.pubKey).Append(nl); + sb.Append(" Trusted CA Issuer Name: ").Append(this.caName).Append(nl); + } + else + { + sb.Append(" Trusted CA cert: ").Append(this.TrustedCert).Append(nl); + } + if (nc != null) + { + sb.Append(" Name Constraints: ").Append(nc).Append(nl); + } + return sb.ToString(); + } + } +} \ No newline at end of file diff --git a/bc-sharp-crypto/src/security/AgreementUtilities.cs b/bc-sharp-crypto/src/security/AgreementUtilities.cs new file mode 100644 index 0000000..12d427c --- /dev/null +++ b/bc-sharp-crypto/src/security/AgreementUtilities.cs @@ -0,0 +1,105 @@ +using System.Collections; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Agreement; +using Org.BouncyCastle.Crypto.Agreement.Kdf; +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Security +{ + /// + /// Utility class for creating IBasicAgreement objects from their names/Oids + /// + public sealed class AgreementUtilities + { + private AgreementUtilities() + { + } + + private static readonly IDictionary algorithms = Platform.CreateHashtable(); + //private static readonly IDictionary oids = Platform.CreateHashtable(); + + static AgreementUtilities() + { + algorithms[X9ObjectIdentifiers.DHSinglePassCofactorDHSha1KdfScheme.Id] = "ECCDHWITHSHA1KDF"; + algorithms[X9ObjectIdentifiers.DHSinglePassStdDHSha1KdfScheme.Id] = "ECDHWITHSHA1KDF"; + algorithms[X9ObjectIdentifiers.MqvSinglePassSha1KdfScheme.Id] = "ECMQVWITHSHA1KDF"; + } + + public static IBasicAgreement GetBasicAgreement( + DerObjectIdentifier oid) + { + return GetBasicAgreement(oid.Id); + } + + public static IBasicAgreement GetBasicAgreement( + string algorithm) + { + string upper = Platform.ToUpperInvariant(algorithm); + string mechanism = (string) algorithms[upper]; + + if (mechanism == null) + { + mechanism = upper; + } + + if (mechanism == "DH" || mechanism == "DIFFIEHELLMAN") + return new DHBasicAgreement(); + + if (mechanism == "ECDH") + return new ECDHBasicAgreement(); + + if (mechanism == "ECDHC" || mechanism == "ECCDH") + return new ECDHCBasicAgreement(); + + if (mechanism == "ECMQV") + return new ECMqvBasicAgreement(); + + throw new SecurityUtilityException("Basic Agreement " + algorithm + " not recognised."); + } + + public static IBasicAgreement GetBasicAgreementWithKdf( + DerObjectIdentifier oid, + string wrapAlgorithm) + { + return GetBasicAgreementWithKdf(oid.Id, wrapAlgorithm); + } + + public static IBasicAgreement GetBasicAgreementWithKdf( + string agreeAlgorithm, + string wrapAlgorithm) + { + string upper = Platform.ToUpperInvariant(agreeAlgorithm); + string mechanism = (string) algorithms[upper]; + + if (mechanism == null) + { + mechanism = upper; + } + + // 'DHWITHSHA1KDF' retained for backward compatibility + if (mechanism == "DHWITHSHA1KDF" || mechanism == "ECDHWITHSHA1KDF") + return new ECDHWithKdfBasicAgreement( + wrapAlgorithm, + new ECDHKekGenerator( + new Sha1Digest())); + + if (mechanism == "ECMQVWITHSHA1KDF") + return new ECMqvWithKdfBasicAgreement( + wrapAlgorithm, + new ECDHKekGenerator( + new Sha1Digest())); + + throw new SecurityUtilityException("Basic Agreement (with KDF) " + agreeAlgorithm + " not recognised."); + } + + public static string GetAlgorithmName( + DerObjectIdentifier oid) + { + return (string) algorithms[oid.Id]; + } + } +} diff --git a/bc-sharp-crypto/src/security/CipherUtilities.cs b/bc-sharp-crypto/src/security/CipherUtilities.cs new file mode 100644 index 0000000..de05bc9 --- /dev/null +++ b/bc-sharp-crypto/src/security/CipherUtilities.cs @@ -0,0 +1,755 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.CryptoPro; +using Org.BouncyCastle.Asn1.Kisa; +using Org.BouncyCastle.Asn1.Nist; +using Org.BouncyCastle.Asn1.Ntt; +using Org.BouncyCastle.Asn1.Oiw; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Agreement; +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Encodings; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Macs; +using Org.BouncyCastle.Crypto.Modes; +using Org.BouncyCastle.Crypto.Paddings; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Security +{ + /// + /// Cipher Utility class contains methods that can not be specifically grouped into other classes. + /// + public sealed class CipherUtilities + { + private enum CipherAlgorithm { + AES, + ARC4, + BLOWFISH, + CAMELLIA, + CAST5, + CAST6, + DES, + DESEDE, + ELGAMAL, + GOST28147, + HC128, + HC256, + IDEA, + NOEKEON, + PBEWITHSHAAND128BITRC4, + PBEWITHSHAAND40BITRC4, + RC2, + RC5, + RC5_64, + RC6, + RIJNDAEL, + RSA, + SALSA20, + SEED, + SERPENT, + SKIPJACK, + TEA, + THREEFISH_256, + THREEFISH_512, + THREEFISH_1024, + TNEPRES, + TWOFISH, + VMPC, + VMPC_KSA3, + XTEA, + }; + + private enum CipherMode { ECB, NONE, CBC, CCM, CFB, CTR, CTS, EAX, GCM, GOFB, OCB, OFB, OPENPGPCFB, SIC }; + private enum CipherPadding + { + NOPADDING, + RAW, + ISO10126PADDING, + ISO10126D2PADDING, + ISO10126_2PADDING, + ISO7816_4PADDING, + ISO9797_1PADDING, + ISO9796_1, + ISO9796_1PADDING, + OAEP, + OAEPPADDING, + OAEPWITHMD5ANDMGF1PADDING, + OAEPWITHSHA1ANDMGF1PADDING, + OAEPWITHSHA_1ANDMGF1PADDING, + OAEPWITHSHA224ANDMGF1PADDING, + OAEPWITHSHA_224ANDMGF1PADDING, + OAEPWITHSHA256ANDMGF1PADDING, + OAEPWITHSHA_256ANDMGF1PADDING, + OAEPWITHSHA384ANDMGF1PADDING, + OAEPWITHSHA_384ANDMGF1PADDING, + OAEPWITHSHA512ANDMGF1PADDING, + OAEPWITHSHA_512ANDMGF1PADDING, + PKCS1, + PKCS1PADDING, + PKCS5, + PKCS5PADDING, + PKCS7, + PKCS7PADDING, + TBCPADDING, + WITHCTS, + X923PADDING, + ZEROBYTEPADDING, + }; + + private static readonly IDictionary algorithms = Platform.CreateHashtable(); + private static readonly IDictionary oids = Platform.CreateHashtable(); + + static CipherUtilities() + { + // Signal to obfuscation tools not to change enum constants + ((CipherAlgorithm)Enums.GetArbitraryValue(typeof(CipherAlgorithm))).ToString(); + ((CipherMode)Enums.GetArbitraryValue(typeof(CipherMode))).ToString(); + ((CipherPadding)Enums.GetArbitraryValue(typeof(CipherPadding))).ToString(); + + // TODO Flesh out the list of aliases + + algorithms[NistObjectIdentifiers.IdAes128Ecb.Id] = "AES/ECB/PKCS7PADDING"; + algorithms[NistObjectIdentifiers.IdAes192Ecb.Id] = "AES/ECB/PKCS7PADDING"; + algorithms[NistObjectIdentifiers.IdAes256Ecb.Id] = "AES/ECB/PKCS7PADDING"; + algorithms["AES//PKCS7"] = "AES/ECB/PKCS7PADDING"; + algorithms["AES//PKCS7PADDING"] = "AES/ECB/PKCS7PADDING"; + algorithms["AES//PKCS5"] = "AES/ECB/PKCS7PADDING"; + algorithms["AES//PKCS5PADDING"] = "AES/ECB/PKCS7PADDING"; + + algorithms[NistObjectIdentifiers.IdAes128Cbc.Id] = "AES/CBC/PKCS7PADDING"; + algorithms[NistObjectIdentifiers.IdAes192Cbc.Id] = "AES/CBC/PKCS7PADDING"; + algorithms[NistObjectIdentifiers.IdAes256Cbc.Id] = "AES/CBC/PKCS7PADDING"; + + algorithms[NistObjectIdentifiers.IdAes128Ofb.Id] = "AES/OFB/NOPADDING"; + algorithms[NistObjectIdentifiers.IdAes192Ofb.Id] = "AES/OFB/NOPADDING"; + algorithms[NistObjectIdentifiers.IdAes256Ofb.Id] = "AES/OFB/NOPADDING"; + + algorithms[NistObjectIdentifiers.IdAes128Cfb.Id] = "AES/CFB/NOPADDING"; + algorithms[NistObjectIdentifiers.IdAes192Cfb.Id] = "AES/CFB/NOPADDING"; + algorithms[NistObjectIdentifiers.IdAes256Cfb.Id] = "AES/CFB/NOPADDING"; + + algorithms["RSA/ECB/PKCS1"] = "RSA//PKCS1PADDING"; + algorithms["RSA/ECB/PKCS1PADDING"] = "RSA//PKCS1PADDING"; + algorithms[PkcsObjectIdentifiers.RsaEncryption.Id] = "RSA//PKCS1PADDING"; + algorithms[PkcsObjectIdentifiers.IdRsaesOaep.Id] = "RSA//OAEPPADDING"; + + algorithms[OiwObjectIdentifiers.DesCbc.Id] = "DES/CBC"; + algorithms[OiwObjectIdentifiers.DesCfb.Id] = "DES/CFB"; + algorithms[OiwObjectIdentifiers.DesEcb.Id] = "DES/ECB"; + algorithms[OiwObjectIdentifiers.DesOfb.Id] = "DES/OFB"; + algorithms[OiwObjectIdentifiers.DesEde.Id] = "DESEDE"; + algorithms["TDEA"] = "DESEDE"; + algorithms[PkcsObjectIdentifiers.DesEde3Cbc.Id] = "DESEDE/CBC"; + algorithms[PkcsObjectIdentifiers.RC2Cbc.Id] = "RC2/CBC"; + algorithms["1.3.6.1.4.1.188.7.1.1.2"] = "IDEA/CBC"; + algorithms["1.2.840.113533.7.66.10"] = "CAST5/CBC"; + + algorithms["RC4"] = "ARC4"; + algorithms["ARCFOUR"] = "ARC4"; + algorithms["1.2.840.113549.3.4"] = "ARC4"; + + + + algorithms["PBEWITHSHA1AND128BITRC4"] = "PBEWITHSHAAND128BITRC4"; + algorithms[PkcsObjectIdentifiers.PbeWithShaAnd128BitRC4.Id] = "PBEWITHSHAAND128BITRC4"; + algorithms["PBEWITHSHA1AND40BITRC4"] = "PBEWITHSHAAND40BITRC4"; + algorithms[PkcsObjectIdentifiers.PbeWithShaAnd40BitRC4.Id] = "PBEWITHSHAAND40BITRC4"; + + algorithms["PBEWITHSHA1ANDDES"] = "PBEWITHSHA1ANDDES-CBC"; + algorithms[PkcsObjectIdentifiers.PbeWithSha1AndDesCbc.Id] = "PBEWITHSHA1ANDDES-CBC"; + algorithms["PBEWITHSHA1ANDRC2"] = "PBEWITHSHA1ANDRC2-CBC"; + algorithms[PkcsObjectIdentifiers.PbeWithSha1AndRC2Cbc.Id] = "PBEWITHSHA1ANDRC2-CBC"; + + algorithms["PBEWITHSHA1AND3-KEYTRIPLEDES-CBC"] = "PBEWITHSHAAND3-KEYTRIPLEDES-CBC"; + algorithms["PBEWITHSHAAND3KEYTRIPLEDES"] = "PBEWITHSHAAND3-KEYTRIPLEDES-CBC"; + algorithms[PkcsObjectIdentifiers.PbeWithShaAnd3KeyTripleDesCbc.Id] = "PBEWITHSHAAND3-KEYTRIPLEDES-CBC"; + algorithms["PBEWITHSHA1ANDDESEDE"] = "PBEWITHSHAAND3-KEYTRIPLEDES-CBC"; + + algorithms["PBEWITHSHA1AND2-KEYTRIPLEDES-CBC"] = "PBEWITHSHAAND2-KEYTRIPLEDES-CBC"; + algorithms[PkcsObjectIdentifiers.PbeWithShaAnd2KeyTripleDesCbc.Id] = "PBEWITHSHAAND2-KEYTRIPLEDES-CBC"; + + algorithms["PBEWITHSHA1AND128BITRC2-CBC"] = "PBEWITHSHAAND128BITRC2-CBC"; + algorithms[PkcsObjectIdentifiers.PbeWithShaAnd128BitRC2Cbc.Id] = "PBEWITHSHAAND128BITRC2-CBC"; + + algorithms["PBEWITHSHA1AND40BITRC2-CBC"] = "PBEWITHSHAAND40BITRC2-CBC"; + algorithms[PkcsObjectIdentifiers.PbewithShaAnd40BitRC2Cbc.Id] = "PBEWITHSHAAND40BITRC2-CBC"; + + algorithms["PBEWITHSHA1AND128BITAES-CBC-BC"] = "PBEWITHSHAAND128BITAES-CBC-BC"; + algorithms["PBEWITHSHA-1AND128BITAES-CBC-BC"] = "PBEWITHSHAAND128BITAES-CBC-BC"; + + algorithms["PBEWITHSHA1AND192BITAES-CBC-BC"] = "PBEWITHSHAAND192BITAES-CBC-BC"; + algorithms["PBEWITHSHA-1AND192BITAES-CBC-BC"] = "PBEWITHSHAAND192BITAES-CBC-BC"; + + algorithms["PBEWITHSHA1AND256BITAES-CBC-BC"] = "PBEWITHSHAAND256BITAES-CBC-BC"; + algorithms["PBEWITHSHA-1AND256BITAES-CBC-BC"] = "PBEWITHSHAAND256BITAES-CBC-BC"; + + algorithms["PBEWITHSHA-256AND128BITAES-CBC-BC"] = "PBEWITHSHA256AND128BITAES-CBC-BC"; + algorithms["PBEWITHSHA-256AND192BITAES-CBC-BC"] = "PBEWITHSHA256AND192BITAES-CBC-BC"; + algorithms["PBEWITHSHA-256AND256BITAES-CBC-BC"] = "PBEWITHSHA256AND256BITAES-CBC-BC"; + + + algorithms["GOST"] = "GOST28147"; + algorithms["GOST-28147"] = "GOST28147"; + algorithms[CryptoProObjectIdentifiers.GostR28147Cbc.Id] = "GOST28147/CBC/PKCS7PADDING"; + + algorithms["RC5-32"] = "RC5"; + + algorithms[NttObjectIdentifiers.IdCamellia128Cbc.Id] = "CAMELLIA/CBC/PKCS7PADDING"; + algorithms[NttObjectIdentifiers.IdCamellia192Cbc.Id] = "CAMELLIA/CBC/PKCS7PADDING"; + algorithms[NttObjectIdentifiers.IdCamellia256Cbc.Id] = "CAMELLIA/CBC/PKCS7PADDING"; + + algorithms[KisaObjectIdentifiers.IdSeedCbc.Id] = "SEED/CBC/PKCS7PADDING"; + + algorithms["1.3.6.1.4.1.3029.1.2"] = "BLOWFISH/CBC"; + } + + private CipherUtilities() + { + } + + /// + /// Returns a ObjectIdentifier for a give encoding. + /// + /// A string representation of the encoding. + /// A DerObjectIdentifier, null if the Oid is not available. + // TODO Don't really want to support this + public static DerObjectIdentifier GetObjectIdentifier( + string mechanism) + { + if (mechanism == null) + throw new ArgumentNullException("mechanism"); + + mechanism = Platform.ToUpperInvariant(mechanism); + string aliased = (string) algorithms[mechanism]; + + if (aliased != null) + mechanism = aliased; + + return (DerObjectIdentifier) oids[mechanism]; + } + + public static ICollection Algorithms + { + get { return oids.Keys; } + } + + public static IBufferedCipher GetCipher( + DerObjectIdentifier oid) + { + return GetCipher(oid.Id); + } + + public static IBufferedCipher GetCipher( + string algorithm) + { + if (algorithm == null) + throw new ArgumentNullException("algorithm"); + + algorithm = Platform.ToUpperInvariant(algorithm); + + { + string aliased = (string) algorithms[algorithm]; + + if (aliased != null) + algorithm = aliased; + } + + IBasicAgreement iesAgreement = null; + if (algorithm == "IES") + { + iesAgreement = new DHBasicAgreement(); + } + else if (algorithm == "ECIES") + { + iesAgreement = new ECDHBasicAgreement(); + } + + if (iesAgreement != null) + { + return new BufferedIesCipher( + new IesEngine( + iesAgreement, + new Kdf2BytesGenerator( + new Sha1Digest()), + new HMac( + new Sha1Digest()))); + } + + + + if (Platform.StartsWith(algorithm, "PBE")) + { + if (Platform.EndsWith(algorithm, "-CBC")) + { + if (algorithm == "PBEWITHSHA1ANDDES-CBC") + { + return new PaddedBufferedBlockCipher( + new CbcBlockCipher(new DesEngine())); + } + else if (algorithm == "PBEWITHSHA1ANDRC2-CBC") + { + return new PaddedBufferedBlockCipher( + new CbcBlockCipher(new RC2Engine())); + } + else if (Strings.IsOneOf(algorithm, + "PBEWITHSHAAND2-KEYTRIPLEDES-CBC", "PBEWITHSHAAND3-KEYTRIPLEDES-CBC")) + { + return new PaddedBufferedBlockCipher( + new CbcBlockCipher(new DesEdeEngine())); + } + else if (Strings.IsOneOf(algorithm, + "PBEWITHSHAAND128BITRC2-CBC", "PBEWITHSHAAND40BITRC2-CBC")) + { + return new PaddedBufferedBlockCipher( + new CbcBlockCipher(new RC2Engine())); + } + } + else if (Platform.EndsWith(algorithm, "-BC") || Platform.EndsWith(algorithm, "-OPENSSL")) + { + if (Strings.IsOneOf(algorithm, + "PBEWITHSHAAND128BITAES-CBC-BC", + "PBEWITHSHAAND192BITAES-CBC-BC", + "PBEWITHSHAAND256BITAES-CBC-BC", + "PBEWITHSHA256AND128BITAES-CBC-BC", + "PBEWITHSHA256AND192BITAES-CBC-BC", + "PBEWITHSHA256AND256BITAES-CBC-BC", + "PBEWITHMD5AND128BITAES-CBC-OPENSSL", + "PBEWITHMD5AND192BITAES-CBC-OPENSSL", + "PBEWITHMD5AND256BITAES-CBC-OPENSSL")) + { + return new PaddedBufferedBlockCipher( + new CbcBlockCipher(new AesEngine())); + } + } + } + + + + string[] parts = algorithm.Split('/'); + + IBlockCipher blockCipher = null; + IAsymmetricBlockCipher asymBlockCipher = null; + IStreamCipher streamCipher = null; + + string algorithmName = parts[0]; + + { + string aliased = (string)algorithms[algorithmName]; + + if (aliased != null) + algorithmName = aliased; + } + + CipherAlgorithm cipherAlgorithm; + try + { + cipherAlgorithm = (CipherAlgorithm)Enums.GetEnumValue(typeof(CipherAlgorithm), algorithmName); + } + catch (ArgumentException) + { + throw new SecurityUtilityException("Cipher " + algorithm + " not recognised."); + } + + switch (cipherAlgorithm) + { + case CipherAlgorithm.AES: + blockCipher = new AesEngine(); + break; + case CipherAlgorithm.ARC4: + streamCipher = new RC4Engine(); + break; + case CipherAlgorithm.BLOWFISH: + blockCipher = new BlowfishEngine(); + break; + case CipherAlgorithm.CAMELLIA: + blockCipher = new CamelliaEngine(); + break; + case CipherAlgorithm.CAST5: + blockCipher = new Cast5Engine(); + break; + case CipherAlgorithm.CAST6: + blockCipher = new Cast6Engine(); + break; + case CipherAlgorithm.DES: + blockCipher = new DesEngine(); + break; + case CipherAlgorithm.DESEDE: + blockCipher = new DesEdeEngine(); + break; + case CipherAlgorithm.ELGAMAL: + asymBlockCipher = new ElGamalEngine(); + break; + case CipherAlgorithm.GOST28147: + blockCipher = new Gost28147Engine(); + break; + case CipherAlgorithm.HC128: + streamCipher = new HC128Engine(); + break; + case CipherAlgorithm.HC256: + streamCipher = new HC256Engine(); + break; + case CipherAlgorithm.IDEA: + blockCipher = new IdeaEngine(); + break; + case CipherAlgorithm.NOEKEON: + blockCipher = new NoekeonEngine(); + break; + case CipherAlgorithm.PBEWITHSHAAND128BITRC4: + case CipherAlgorithm.PBEWITHSHAAND40BITRC4: + streamCipher = new RC4Engine(); + break; + case CipherAlgorithm.RC2: + blockCipher = new RC2Engine(); + break; + case CipherAlgorithm.RC5: + blockCipher = new RC532Engine(); + break; + case CipherAlgorithm.RC5_64: + blockCipher = new RC564Engine(); + break; + case CipherAlgorithm.RC6: + blockCipher = new RC6Engine(); + break; + case CipherAlgorithm.RIJNDAEL: + blockCipher = new RijndaelEngine(); + break; + case CipherAlgorithm.RSA: + asymBlockCipher = new RsaBlindedEngine(); + break; + case CipherAlgorithm.SALSA20: + streamCipher = new Salsa20Engine(); + break; + case CipherAlgorithm.SEED: + blockCipher = new SeedEngine(); + break; + case CipherAlgorithm.SERPENT: + blockCipher = new SerpentEngine(); + break; + case CipherAlgorithm.SKIPJACK: + blockCipher = new SkipjackEngine(); + break; + case CipherAlgorithm.TEA: + blockCipher = new TeaEngine(); + break; + case CipherAlgorithm.THREEFISH_256: + blockCipher = new ThreefishEngine(ThreefishEngine.BLOCKSIZE_256); + break; + case CipherAlgorithm.THREEFISH_512: + blockCipher = new ThreefishEngine(ThreefishEngine.BLOCKSIZE_512); + break; + case CipherAlgorithm.THREEFISH_1024: + blockCipher = new ThreefishEngine(ThreefishEngine.BLOCKSIZE_1024); + break; + case CipherAlgorithm.TNEPRES: + blockCipher = new TnepresEngine(); + break; + case CipherAlgorithm.TWOFISH: + blockCipher = new TwofishEngine(); + break; + case CipherAlgorithm.VMPC: + streamCipher = new VmpcEngine(); + break; + case CipherAlgorithm.VMPC_KSA3: + streamCipher = new VmpcKsa3Engine(); + break; + case CipherAlgorithm.XTEA: + blockCipher = new XteaEngine(); + break; + default: + throw new SecurityUtilityException("Cipher " + algorithm + " not recognised."); + } + + if (streamCipher != null) + { + if (parts.Length > 1) + throw new ArgumentException("Modes and paddings not used for stream ciphers"); + + return new BufferedStreamCipher(streamCipher); + } + + + bool cts = false; + bool padded = true; + IBlockCipherPadding padding = null; + IAeadBlockCipher aeadBlockCipher = null; + + if (parts.Length > 2) + { + if (streamCipher != null) + throw new ArgumentException("Paddings not used for stream ciphers"); + + string paddingName = parts[2]; + + CipherPadding cipherPadding; + if (paddingName == "") + { + cipherPadding = CipherPadding.RAW; + } + else if (paddingName == "X9.23PADDING") + { + cipherPadding = CipherPadding.X923PADDING; + } + else + { + try + { + cipherPadding = (CipherPadding)Enums.GetEnumValue(typeof(CipherPadding), paddingName); + } + catch (ArgumentException) + { + throw new SecurityUtilityException("Cipher " + algorithm + " not recognised."); + } + } + + switch (cipherPadding) + { + case CipherPadding.NOPADDING: + padded = false; + break; + case CipherPadding.RAW: + break; + case CipherPadding.ISO10126PADDING: + case CipherPadding.ISO10126D2PADDING: + case CipherPadding.ISO10126_2PADDING: + padding = new ISO10126d2Padding(); + break; + case CipherPadding.ISO7816_4PADDING: + case CipherPadding.ISO9797_1PADDING: + padding = new ISO7816d4Padding(); + break; + case CipherPadding.ISO9796_1: + case CipherPadding.ISO9796_1PADDING: + asymBlockCipher = new ISO9796d1Encoding(asymBlockCipher); + break; + case CipherPadding.OAEP: + case CipherPadding.OAEPPADDING: + asymBlockCipher = new OaepEncoding(asymBlockCipher); + break; + case CipherPadding.OAEPWITHMD5ANDMGF1PADDING: + asymBlockCipher = new OaepEncoding(asymBlockCipher, new MD5Digest()); + break; + case CipherPadding.OAEPWITHSHA1ANDMGF1PADDING: + case CipherPadding.OAEPWITHSHA_1ANDMGF1PADDING: + asymBlockCipher = new OaepEncoding(asymBlockCipher, new Sha1Digest()); + break; + case CipherPadding.OAEPWITHSHA224ANDMGF1PADDING: + case CipherPadding.OAEPWITHSHA_224ANDMGF1PADDING: + asymBlockCipher = new OaepEncoding(asymBlockCipher, new Sha224Digest()); + break; + case CipherPadding.OAEPWITHSHA256ANDMGF1PADDING: + case CipherPadding.OAEPWITHSHA_256ANDMGF1PADDING: + asymBlockCipher = new OaepEncoding(asymBlockCipher, new Sha256Digest()); + break; + case CipherPadding.OAEPWITHSHA384ANDMGF1PADDING: + case CipherPadding.OAEPWITHSHA_384ANDMGF1PADDING: + asymBlockCipher = new OaepEncoding(asymBlockCipher, new Sha384Digest()); + break; + case CipherPadding.OAEPWITHSHA512ANDMGF1PADDING: + case CipherPadding.OAEPWITHSHA_512ANDMGF1PADDING: + asymBlockCipher = new OaepEncoding(asymBlockCipher, new Sha512Digest()); + break; + case CipherPadding.PKCS1: + case CipherPadding.PKCS1PADDING: + asymBlockCipher = new Pkcs1Encoding(asymBlockCipher); + break; + case CipherPadding.PKCS5: + case CipherPadding.PKCS5PADDING: + case CipherPadding.PKCS7: + case CipherPadding.PKCS7PADDING: + padding = new Pkcs7Padding(); + break; + case CipherPadding.TBCPADDING: + padding = new TbcPadding(); + break; + case CipherPadding.WITHCTS: + cts = true; + break; + case CipherPadding.X923PADDING: + padding = new X923Padding(); + break; + case CipherPadding.ZEROBYTEPADDING: + padding = new ZeroBytePadding(); + break; + default: + throw new SecurityUtilityException("Cipher " + algorithm + " not recognised."); + } + } + + string mode = ""; + if (parts.Length > 1) + { + mode = parts[1]; + + int di = GetDigitIndex(mode); + string modeName = di >= 0 ? mode.Substring(0, di) : mode; + + try + { + CipherMode cipherMode = modeName == "" + ? CipherMode.NONE + : (CipherMode)Enums.GetEnumValue(typeof(CipherMode), modeName); + + switch (cipherMode) + { + case CipherMode.ECB: + case CipherMode.NONE: + break; + case CipherMode.CBC: + blockCipher = new CbcBlockCipher(blockCipher); + break; + case CipherMode.CCM: + aeadBlockCipher = new CcmBlockCipher(blockCipher); + break; + case CipherMode.CFB: + { + int bits = (di < 0) + ? 8 * blockCipher.GetBlockSize() + : int.Parse(mode.Substring(di)); + + blockCipher = new CfbBlockCipher(blockCipher, bits); + break; + } + case CipherMode.CTR: + blockCipher = new SicBlockCipher(blockCipher); + break; + case CipherMode.CTS: + cts = true; + blockCipher = new CbcBlockCipher(blockCipher); + break; + case CipherMode.EAX: + aeadBlockCipher = new EaxBlockCipher(blockCipher); + break; + case CipherMode.GCM: + aeadBlockCipher = new GcmBlockCipher(blockCipher); + break; + case CipherMode.GOFB: + blockCipher = new GOfbBlockCipher(blockCipher); + break; + case CipherMode.OCB: + aeadBlockCipher = new OcbBlockCipher(blockCipher, CreateBlockCipher(cipherAlgorithm)); + break; + case CipherMode.OFB: + { + int bits = (di < 0) + ? 8 * blockCipher.GetBlockSize() + : int.Parse(mode.Substring(di)); + + blockCipher = new OfbBlockCipher(blockCipher, bits); + break; + } + case CipherMode.OPENPGPCFB: + blockCipher = new OpenPgpCfbBlockCipher(blockCipher); + break; + case CipherMode.SIC: + if (blockCipher.GetBlockSize() < 16) + { + throw new ArgumentException("Warning: SIC-Mode can become a twotime-pad if the blocksize of the cipher is too small. Use a cipher with a block size of at least 128 bits (e.g. AES)"); + } + blockCipher = new SicBlockCipher(blockCipher); + break; + default: + throw new SecurityUtilityException("Cipher " + algorithm + " not recognised."); + } + } + catch (ArgumentException) + { + throw new SecurityUtilityException("Cipher " + algorithm + " not recognised."); + } + } + + if (aeadBlockCipher != null) + { + if (cts) + throw new SecurityUtilityException("CTS mode not valid for AEAD ciphers."); + if (padded && parts.Length > 2 && parts[2] != "") + throw new SecurityUtilityException("Bad padding specified for AEAD cipher."); + + return new BufferedAeadBlockCipher(aeadBlockCipher); + } + + if (blockCipher != null) + { + if (cts) + { + return new CtsBlockCipher(blockCipher); + } + + if (padding != null) + { + return new PaddedBufferedBlockCipher(blockCipher, padding); + } + + if (!padded || blockCipher.IsPartialBlockOkay) + { + return new BufferedBlockCipher(blockCipher); + } + + return new PaddedBufferedBlockCipher(blockCipher); + } + + if (asymBlockCipher != null) + { + return new BufferedAsymmetricBlockCipher(asymBlockCipher); + } + + throw new SecurityUtilityException("Cipher " + algorithm + " not recognised."); + } + + public static string GetAlgorithmName( + DerObjectIdentifier oid) + { + return (string) algorithms[oid.Id]; + } + + private static int GetDigitIndex( + string s) + { + for (int i = 0; i < s.Length; ++i) + { + if (char.IsDigit(s[i])) + return i; + } + + return -1; + } + + private static IBlockCipher CreateBlockCipher(CipherAlgorithm cipherAlgorithm) + { + switch (cipherAlgorithm) + { + case CipherAlgorithm.AES: return new AesEngine(); + case CipherAlgorithm.BLOWFISH: return new BlowfishEngine(); + case CipherAlgorithm.CAMELLIA: return new CamelliaEngine(); + case CipherAlgorithm.CAST5: return new Cast5Engine(); + case CipherAlgorithm.CAST6: return new Cast6Engine(); + case CipherAlgorithm.DES: return new DesEngine(); + case CipherAlgorithm.DESEDE: return new DesEdeEngine(); + case CipherAlgorithm.GOST28147: return new Gost28147Engine(); + case CipherAlgorithm.IDEA: return new IdeaEngine(); + case CipherAlgorithm.NOEKEON: return new NoekeonEngine(); + case CipherAlgorithm.RC2: return new RC2Engine(); + case CipherAlgorithm.RC5: return new RC532Engine(); + case CipherAlgorithm.RC5_64: return new RC564Engine(); + case CipherAlgorithm.RC6: return new RC6Engine(); + case CipherAlgorithm.RIJNDAEL: return new RijndaelEngine(); + case CipherAlgorithm.SEED: return new SeedEngine(); + case CipherAlgorithm.SERPENT: return new SerpentEngine(); + case CipherAlgorithm.SKIPJACK: return new SkipjackEngine(); + case CipherAlgorithm.TEA: return new TeaEngine(); + case CipherAlgorithm.THREEFISH_256: return new ThreefishEngine(ThreefishEngine.BLOCKSIZE_256); + case CipherAlgorithm.THREEFISH_512: return new ThreefishEngine(ThreefishEngine.BLOCKSIZE_512); + case CipherAlgorithm.THREEFISH_1024: return new ThreefishEngine(ThreefishEngine.BLOCKSIZE_1024); + case CipherAlgorithm.TNEPRES: return new TnepresEngine(); + case CipherAlgorithm.TWOFISH: return new TwofishEngine(); + case CipherAlgorithm.XTEA: return new XteaEngine(); + default: + throw new SecurityUtilityException("Cipher " + cipherAlgorithm + " not recognised or not a block cipher"); + } + } + } +} diff --git a/bc-sharp-crypto/src/security/DigestUtilities.cs b/bc-sharp-crypto/src/security/DigestUtilities.cs new file mode 100644 index 0000000..7ddf6c8 --- /dev/null +++ b/bc-sharp-crypto/src/security/DigestUtilities.cs @@ -0,0 +1,222 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.CryptoPro; +using Org.BouncyCastle.Asn1.Nist; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.Oiw; +using Org.BouncyCastle.Asn1.TeleTrust; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Security +{ + /// + /// Utility class for creating IDigest objects from their names/Oids + /// + public sealed class DigestUtilities + { + private enum DigestAlgorithm { + GOST3411, + KECCAK_224, KECCAK_256, KECCAK_288, KECCAK_384, KECCAK_512, + MD2, MD4, MD5, + RIPEMD128, RIPEMD160, RIPEMD256, RIPEMD320, + SHA_1, SHA_224, SHA_256, SHA_384, SHA_512, + SHA_512_224, SHA_512_256, + SHA3_224, SHA3_256, SHA3_384, SHA3_512, + SHAKE128, SHAKE256, + TIGER, + WHIRLPOOL, + }; + + private DigestUtilities() + { + } + + private static readonly IDictionary algorithms = Platform.CreateHashtable(); + private static readonly IDictionary oids = Platform.CreateHashtable(); + + static DigestUtilities() + { + // Signal to obfuscation tools not to change enum constants + ((DigestAlgorithm)Enums.GetArbitraryValue(typeof(DigestAlgorithm))).ToString(); + + algorithms[PkcsObjectIdentifiers.MD2.Id] = "MD2"; + algorithms[PkcsObjectIdentifiers.MD4.Id] = "MD4"; + algorithms[PkcsObjectIdentifiers.MD5.Id] = "MD5"; + + algorithms["SHA1"] = "SHA-1"; + algorithms[OiwObjectIdentifiers.IdSha1.Id] = "SHA-1"; + algorithms["SHA224"] = "SHA-224"; + algorithms[NistObjectIdentifiers.IdSha224.Id] = "SHA-224"; + algorithms["SHA256"] = "SHA-256"; + algorithms[NistObjectIdentifiers.IdSha256.Id] = "SHA-256"; + algorithms["SHA384"] = "SHA-384"; + algorithms[NistObjectIdentifiers.IdSha384.Id] = "SHA-384"; + algorithms["SHA512"] = "SHA-512"; + algorithms[NistObjectIdentifiers.IdSha512.Id] = "SHA-512"; + algorithms["SHA512/224"] = "SHA-512/224"; + algorithms[NistObjectIdentifiers.IdSha512_224.Id] = "SHA-512/224"; + algorithms["SHA512/256"] = "SHA-512/256"; + algorithms[NistObjectIdentifiers.IdSha512_256.Id] = "SHA-512/256"; + + algorithms["RIPEMD-128"] = "RIPEMD128"; + algorithms[TeleTrusTObjectIdentifiers.RipeMD128.Id] = "RIPEMD128"; + algorithms["RIPEMD-160"] = "RIPEMD160"; + algorithms[TeleTrusTObjectIdentifiers.RipeMD160.Id] = "RIPEMD160"; + algorithms["RIPEMD-256"] = "RIPEMD256"; + algorithms[TeleTrusTObjectIdentifiers.RipeMD256.Id] = "RIPEMD256"; + algorithms["RIPEMD-320"] = "RIPEMD320"; +// algorithms[TeleTrusTObjectIdentifiers.RipeMD320.Id] = "RIPEMD320"; + + algorithms[CryptoProObjectIdentifiers.GostR3411.Id] = "GOST3411"; + + algorithms[NistObjectIdentifiers.IdSha3_224.Id] = "SHA3-224"; + algorithms[NistObjectIdentifiers.IdSha3_256.Id] = "SHA3-256"; + algorithms[NistObjectIdentifiers.IdSha3_384.Id] = "SHA3-384"; + algorithms[NistObjectIdentifiers.IdSha3_512.Id] = "SHA3-512"; + algorithms[NistObjectIdentifiers.IdShake128.Id] = "SHAKE128"; + algorithms[NistObjectIdentifiers.IdShake256.Id] = "SHAKE256"; + + oids["MD2"] = PkcsObjectIdentifiers.MD2; + oids["MD4"] = PkcsObjectIdentifiers.MD4; + oids["MD5"] = PkcsObjectIdentifiers.MD5; + oids["SHA-1"] = OiwObjectIdentifiers.IdSha1; + oids["SHA-224"] = NistObjectIdentifiers.IdSha224; + oids["SHA-256"] = NistObjectIdentifiers.IdSha256; + oids["SHA-384"] = NistObjectIdentifiers.IdSha384; + oids["SHA-512"] = NistObjectIdentifiers.IdSha512; + oids["SHA-512/224"] = NistObjectIdentifiers.IdSha512_224; + oids["SHA-512/256"] = NistObjectIdentifiers.IdSha512_256; + oids["SHA3-224"] = NistObjectIdentifiers.IdSha3_224; + oids["SHA3-256"] = NistObjectIdentifiers.IdSha3_256; + oids["SHA3-384"] = NistObjectIdentifiers.IdSha3_384; + oids["SHA3-512"] = NistObjectIdentifiers.IdSha3_512; + oids["SHAKE128"] = NistObjectIdentifiers.IdShake128; + oids["SHAKE256"] = NistObjectIdentifiers.IdShake256; + oids["RIPEMD128"] = TeleTrusTObjectIdentifiers.RipeMD128; + oids["RIPEMD160"] = TeleTrusTObjectIdentifiers.RipeMD160; + oids["RIPEMD256"] = TeleTrusTObjectIdentifiers.RipeMD256; + oids["GOST3411"] = CryptoProObjectIdentifiers.GostR3411; + } + + /// + /// Returns a ObjectIdentifier for a given digest mechanism. + /// + /// A string representation of the digest meanism. + /// A DerObjectIdentifier, null if the Oid is not available. + + public static DerObjectIdentifier GetObjectIdentifier( + string mechanism) + { + if (mechanism == null) + throw new System.ArgumentNullException("mechanism"); + + mechanism = Platform.ToUpperInvariant(mechanism); + string aliased = (string) algorithms[mechanism]; + + if (aliased != null) + mechanism = aliased; + + return (DerObjectIdentifier) oids[mechanism]; + } + + public static ICollection Algorithms + { + get { return oids.Keys; } + } + + public static IDigest GetDigest( + DerObjectIdentifier id) + { + return GetDigest(id.Id); + } + + public static IDigest GetDigest( + string algorithm) + { + string upper = Platform.ToUpperInvariant(algorithm); + string mechanism = (string) algorithms[upper]; + + if (mechanism == null) + { + mechanism = upper; + } + + try + { + DigestAlgorithm digestAlgorithm = (DigestAlgorithm)Enums.GetEnumValue( + typeof(DigestAlgorithm), mechanism); + + switch (digestAlgorithm) + { + case DigestAlgorithm.GOST3411: return new Gost3411Digest(); + case DigestAlgorithm.KECCAK_224: return new KeccakDigest(224); + case DigestAlgorithm.KECCAK_256: return new KeccakDigest(256); + case DigestAlgorithm.KECCAK_288: return new KeccakDigest(288); + case DigestAlgorithm.KECCAK_384: return new KeccakDigest(384); + case DigestAlgorithm.KECCAK_512: return new KeccakDigest(512); + case DigestAlgorithm.MD2: return new MD2Digest(); + case DigestAlgorithm.MD4: return new MD4Digest(); + case DigestAlgorithm.MD5: return new MD5Digest(); + case DigestAlgorithm.RIPEMD128: return new RipeMD128Digest(); + case DigestAlgorithm.RIPEMD160: return new RipeMD160Digest(); + case DigestAlgorithm.RIPEMD256: return new RipeMD256Digest(); + case DigestAlgorithm.RIPEMD320: return new RipeMD320Digest(); + case DigestAlgorithm.SHA_1: return new Sha1Digest(); + case DigestAlgorithm.SHA_224: return new Sha224Digest(); + case DigestAlgorithm.SHA_256: return new Sha256Digest(); + case DigestAlgorithm.SHA_384: return new Sha384Digest(); + case DigestAlgorithm.SHA_512: return new Sha512Digest(); + case DigestAlgorithm.SHA_512_224: return new Sha512tDigest(224); + case DigestAlgorithm.SHA_512_256: return new Sha512tDigest(256); + case DigestAlgorithm.SHA3_224: return new Sha3Digest(224); + case DigestAlgorithm.SHA3_256: return new Sha3Digest(256); + case DigestAlgorithm.SHA3_384: return new Sha3Digest(384); + case DigestAlgorithm.SHA3_512: return new Sha3Digest(512); + case DigestAlgorithm.SHAKE128: return new ShakeDigest(128); + case DigestAlgorithm.SHAKE256: return new ShakeDigest(256); + case DigestAlgorithm.TIGER: return new TigerDigest(); + case DigestAlgorithm.WHIRLPOOL: return new WhirlpoolDigest(); + } + } + catch (ArgumentException) + { + } + + throw new SecurityUtilityException("Digest " + mechanism + " not recognised."); + } + + public static string GetAlgorithmName( + DerObjectIdentifier oid) + { + return (string) algorithms[oid.Id]; + } + + public static byte[] CalculateDigest(string algorithm, byte[] input) + { + IDigest digest = GetDigest(algorithm); + digest.BlockUpdate(input, 0, input.Length); + return DoFinal(digest); + } + + public static byte[] DoFinal( + IDigest digest) + { + byte[] b = new byte[digest.GetDigestSize()]; + digest.DoFinal(b, 0); + return b; + } + + public static byte[] DoFinal( + IDigest digest, + byte[] input) + { + digest.BlockUpdate(input, 0, input.Length); + return DoFinal(digest); + } + } +} diff --git a/bc-sharp-crypto/src/security/DotNetUtilities.cs b/bc-sharp-crypto/src/security/DotNetUtilities.cs new file mode 100644 index 0000000..69322b5 --- /dev/null +++ b/bc-sharp-crypto/src/security/DotNetUtilities.cs @@ -0,0 +1,245 @@ +#if !(NETCF_1_0 || SILVERLIGHT || PORTABLE) + +using System; +using System.Security.Cryptography; +using SystemX509 = System.Security.Cryptography.X509Certificates; + +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.X509; + +namespace Org.BouncyCastle.Security +{ + /// + /// A class containing methods to interface the BouncyCastle world to the .NET Crypto world. + /// + public sealed class DotNetUtilities + { + private DotNetUtilities() + { + } + + /// + /// Create an System.Security.Cryptography.X509Certificate from an X509Certificate Structure. + /// + /// + /// A System.Security.Cryptography.X509Certificate. + public static SystemX509.X509Certificate ToX509Certificate( + X509CertificateStructure x509Struct) + { + return new SystemX509.X509Certificate(x509Struct.GetDerEncoded()); + } + + public static SystemX509.X509Certificate ToX509Certificate( + X509Certificate x509Cert) + { + return new SystemX509.X509Certificate(x509Cert.GetEncoded()); + } + + public static X509Certificate FromX509Certificate( + SystemX509.X509Certificate x509Cert) + { + return new X509CertificateParser().ReadCertificate(x509Cert.GetRawCertData()); + } + + public static AsymmetricCipherKeyPair GetDsaKeyPair( + DSA dsa) + { + return GetDsaKeyPair(dsa.ExportParameters(true)); + } + + public static AsymmetricCipherKeyPair GetDsaKeyPair( + DSAParameters dp) + { + DsaValidationParameters validationParameters = (dp.Seed != null) + ? new DsaValidationParameters(dp.Seed, dp.Counter) + : null; + + DsaParameters parameters = new DsaParameters( + new BigInteger(1, dp.P), + new BigInteger(1, dp.Q), + new BigInteger(1, dp.G), + validationParameters); + + DsaPublicKeyParameters pubKey = new DsaPublicKeyParameters( + new BigInteger(1, dp.Y), + parameters); + + DsaPrivateKeyParameters privKey = new DsaPrivateKeyParameters( + new BigInteger(1, dp.X), + parameters); + + return new AsymmetricCipherKeyPair(pubKey, privKey); + } + + public static DsaPublicKeyParameters GetDsaPublicKey( + DSA dsa) + { + return GetDsaPublicKey(dsa.ExportParameters(false)); + } + + public static DsaPublicKeyParameters GetDsaPublicKey( + DSAParameters dp) + { + DsaValidationParameters validationParameters = (dp.Seed != null) + ? new DsaValidationParameters(dp.Seed, dp.Counter) + : null; + + DsaParameters parameters = new DsaParameters( + new BigInteger(1, dp.P), + new BigInteger(1, dp.Q), + new BigInteger(1, dp.G), + validationParameters); + + return new DsaPublicKeyParameters( + new BigInteger(1, dp.Y), + parameters); + } + + public static AsymmetricCipherKeyPair GetRsaKeyPair( + RSA rsa) + { + return GetRsaKeyPair(rsa.ExportParameters(true)); + } + + public static AsymmetricCipherKeyPair GetRsaKeyPair( + RSAParameters rp) + { + BigInteger modulus = new BigInteger(1, rp.Modulus); + BigInteger pubExp = new BigInteger(1, rp.Exponent); + + RsaKeyParameters pubKey = new RsaKeyParameters( + false, + modulus, + pubExp); + + RsaPrivateCrtKeyParameters privKey = new RsaPrivateCrtKeyParameters( + modulus, + pubExp, + new BigInteger(1, rp.D), + new BigInteger(1, rp.P), + new BigInteger(1, rp.Q), + new BigInteger(1, rp.DP), + new BigInteger(1, rp.DQ), + new BigInteger(1, rp.InverseQ)); + + return new AsymmetricCipherKeyPair(pubKey, privKey); + } + + public static RsaKeyParameters GetRsaPublicKey( + RSA rsa) + { + return GetRsaPublicKey(rsa.ExportParameters(false)); + } + + public static RsaKeyParameters GetRsaPublicKey( + RSAParameters rp) + { + return new RsaKeyParameters( + false, + new BigInteger(1, rp.Modulus), + new BigInteger(1, rp.Exponent)); + } + + public static AsymmetricCipherKeyPair GetKeyPair(AsymmetricAlgorithm privateKey) + { + if (privateKey is DSA) + { + return GetDsaKeyPair((DSA)privateKey); + } + + if (privateKey is RSA) + { + return GetRsaKeyPair((RSA)privateKey); + } + + throw new ArgumentException("Unsupported algorithm specified", "privateKey"); + } + + public static RSA ToRSA(RsaKeyParameters rsaKey) + { + // TODO This appears to not work for private keys (when no CRT info) + return CreateRSAProvider(ToRSAParameters(rsaKey)); + } + + public static RSA ToRSA(RsaPrivateCrtKeyParameters privKey) + { + return CreateRSAProvider(ToRSAParameters(privKey)); + } + + public static RSA ToRSA(RsaPrivateKeyStructure privKey) + { + return CreateRSAProvider(ToRSAParameters(privKey)); + } + + public static RSAParameters ToRSAParameters(RsaKeyParameters rsaKey) + { + RSAParameters rp = new RSAParameters(); + rp.Modulus = rsaKey.Modulus.ToByteArrayUnsigned(); + if (rsaKey.IsPrivate) + rp.D = ConvertRSAParametersField(rsaKey.Exponent, rp.Modulus.Length); + else + rp.Exponent = rsaKey.Exponent.ToByteArrayUnsigned(); + return rp; + } + + public static RSAParameters ToRSAParameters(RsaPrivateCrtKeyParameters privKey) + { + RSAParameters rp = new RSAParameters(); + rp.Modulus = privKey.Modulus.ToByteArrayUnsigned(); + rp.Exponent = privKey.PublicExponent.ToByteArrayUnsigned(); + rp.P = privKey.P.ToByteArrayUnsigned(); + rp.Q = privKey.Q.ToByteArrayUnsigned(); + rp.D = ConvertRSAParametersField(privKey.Exponent, rp.Modulus.Length); + rp.DP = ConvertRSAParametersField(privKey.DP, rp.P.Length); + rp.DQ = ConvertRSAParametersField(privKey.DQ, rp.Q.Length); + rp.InverseQ = ConvertRSAParametersField(privKey.QInv, rp.Q.Length); + return rp; + } + + public static RSAParameters ToRSAParameters(RsaPrivateKeyStructure privKey) + { + RSAParameters rp = new RSAParameters(); + rp.Modulus = privKey.Modulus.ToByteArrayUnsigned(); + rp.Exponent = privKey.PublicExponent.ToByteArrayUnsigned(); + rp.P = privKey.Prime1.ToByteArrayUnsigned(); + rp.Q = privKey.Prime2.ToByteArrayUnsigned(); + rp.D = ConvertRSAParametersField(privKey.PrivateExponent, rp.Modulus.Length); + rp.DP = ConvertRSAParametersField(privKey.Exponent1, rp.P.Length); + rp.DQ = ConvertRSAParametersField(privKey.Exponent2, rp.Q.Length); + rp.InverseQ = ConvertRSAParametersField(privKey.Coefficient, rp.Q.Length); + return rp; + } + + // TODO Move functionality to more general class + private static byte[] ConvertRSAParametersField(BigInteger n, int size) + { + byte[] bs = n.ToByteArrayUnsigned(); + + if (bs.Length == size) + return bs; + + if (bs.Length > size) + throw new ArgumentException("Specified size too small", "size"); + + byte[] padded = new byte[size]; + Array.Copy(bs, 0, padded, size - bs.Length, bs.Length); + return padded; + } + + private static RSA CreateRSAProvider(RSAParameters rp) + { + CspParameters csp = new CspParameters(); + csp.KeyContainerName = string.Format("BouncyCastle-{0}", Guid.NewGuid()); + RSACryptoServiceProvider rsaCsp = new RSACryptoServiceProvider(csp); + rsaCsp.ImportParameters(rp); + return rsaCsp; + } + } +} + +#endif diff --git a/bc-sharp-crypto/src/security/GeneralSecurityException.cs b/bc-sharp-crypto/src/security/GeneralSecurityException.cs new file mode 100644 index 0000000..d4ab38c --- /dev/null +++ b/bc-sharp-crypto/src/security/GeneralSecurityException.cs @@ -0,0 +1,29 @@ +using System; + +namespace Org.BouncyCastle.Security +{ +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT || PORTABLE) + [Serializable] +#endif + public class GeneralSecurityException + : Exception + { + public GeneralSecurityException() + : base() + { + } + + public GeneralSecurityException( + string message) + : base(message) + { + } + + public GeneralSecurityException( + string message, + Exception exception) + : base(message, exception) + { + } + } +} diff --git a/bc-sharp-crypto/src/security/GeneratorUtilities.cs b/bc-sharp-crypto/src/security/GeneratorUtilities.cs new file mode 100644 index 0000000..3beebd0 --- /dev/null +++ b/bc-sharp-crypto/src/security/GeneratorUtilities.cs @@ -0,0 +1,352 @@ +using System.Collections; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.CryptoPro; +using Org.BouncyCastle.Asn1.Iana; +using Org.BouncyCastle.Asn1.Kisa; +using Org.BouncyCastle.Asn1.Nist; +using Org.BouncyCastle.Asn1.Ntt; +using Org.BouncyCastle.Asn1.Oiw; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; + +namespace Org.BouncyCastle.Security +{ + public sealed class GeneratorUtilities + { + private GeneratorUtilities() + { + } + + private static readonly IDictionary kgAlgorithms = Platform.CreateHashtable(); + private static readonly IDictionary kpgAlgorithms = Platform.CreateHashtable(); + private static readonly IDictionary defaultKeySizes = Platform.CreateHashtable(); + + static GeneratorUtilities() + { + // + // key generators. + // + AddKgAlgorithm("AES", + "AESWRAP"); + AddKgAlgorithm("AES128", + "2.16.840.1.101.3.4.2", + NistObjectIdentifiers.IdAes128Cbc, + NistObjectIdentifiers.IdAes128Cfb, + NistObjectIdentifiers.IdAes128Ecb, + NistObjectIdentifiers.IdAes128Ofb, + NistObjectIdentifiers.IdAes128Wrap); + AddKgAlgorithm("AES192", + "2.16.840.1.101.3.4.22", + NistObjectIdentifiers.IdAes192Cbc, + NistObjectIdentifiers.IdAes192Cfb, + NistObjectIdentifiers.IdAes192Ecb, + NistObjectIdentifiers.IdAes192Ofb, + NistObjectIdentifiers.IdAes192Wrap); + AddKgAlgorithm("AES256", + "2.16.840.1.101.3.4.42", + NistObjectIdentifiers.IdAes256Cbc, + NistObjectIdentifiers.IdAes256Cfb, + NistObjectIdentifiers.IdAes256Ecb, + NistObjectIdentifiers.IdAes256Ofb, + NistObjectIdentifiers.IdAes256Wrap); + AddKgAlgorithm("BLOWFISH", + "1.3.6.1.4.1.3029.1.2"); + AddKgAlgorithm("CAMELLIA", + "CAMELLIAWRAP"); + AddKgAlgorithm("CAMELLIA128", + NttObjectIdentifiers.IdCamellia128Cbc, + NttObjectIdentifiers.IdCamellia128Wrap); + AddKgAlgorithm("CAMELLIA192", + NttObjectIdentifiers.IdCamellia192Cbc, + NttObjectIdentifiers.IdCamellia192Wrap); + AddKgAlgorithm("CAMELLIA256", + NttObjectIdentifiers.IdCamellia256Cbc, + NttObjectIdentifiers.IdCamellia256Wrap); + AddKgAlgorithm("CAST5", + "1.2.840.113533.7.66.10"); + AddKgAlgorithm("CAST6"); + AddKgAlgorithm("DES", + OiwObjectIdentifiers.DesCbc, + OiwObjectIdentifiers.DesCfb, + OiwObjectIdentifiers.DesEcb, + OiwObjectIdentifiers.DesOfb); + AddKgAlgorithm("DESEDE", + "DESEDEWRAP", + "TDEA", + OiwObjectIdentifiers.DesEde); + AddKgAlgorithm("DESEDE3", + PkcsObjectIdentifiers.DesEde3Cbc, + PkcsObjectIdentifiers.IdAlgCms3DesWrap); + AddKgAlgorithm("GOST28147", + "GOST", + "GOST-28147", + CryptoProObjectIdentifiers.GostR28147Cbc); + AddKgAlgorithm("HC128"); + AddKgAlgorithm("HC256"); + AddKgAlgorithm("IDEA", + "1.3.6.1.4.1.188.7.1.1.2"); + AddKgAlgorithm("NOEKEON"); + AddKgAlgorithm("RC2", + PkcsObjectIdentifiers.RC2Cbc, + PkcsObjectIdentifiers.IdAlgCmsRC2Wrap); + AddKgAlgorithm("RC4", + "ARC4", + "1.2.840.113549.3.4"); + AddKgAlgorithm("RC5", + "RC5-32"); + AddKgAlgorithm("RC5-64"); + AddKgAlgorithm("RC6"); + AddKgAlgorithm("RIJNDAEL"); + AddKgAlgorithm("SALSA20"); + AddKgAlgorithm("SEED", + KisaObjectIdentifiers.IdNpkiAppCmsSeedWrap, + KisaObjectIdentifiers.IdSeedCbc); + AddKgAlgorithm("SERPENT"); + AddKgAlgorithm("SKIPJACK"); + AddKgAlgorithm("TEA"); + AddKgAlgorithm("THREEFISH-256"); + AddKgAlgorithm("THREEFISH-512"); + AddKgAlgorithm("THREEFISH-1024"); + AddKgAlgorithm("TNEPRES"); + AddKgAlgorithm("TWOFISH"); + AddKgAlgorithm("VMPC"); + AddKgAlgorithm("VMPC-KSA3"); + AddKgAlgorithm("XTEA"); + + // + // HMac key generators + // + AddHMacKeyGenerator("MD2"); + AddHMacKeyGenerator("MD4"); + AddHMacKeyGenerator("MD5", + IanaObjectIdentifiers.HmacMD5); + AddHMacKeyGenerator("SHA1", + PkcsObjectIdentifiers.IdHmacWithSha1, + IanaObjectIdentifiers.HmacSha1); + AddHMacKeyGenerator("SHA224", + PkcsObjectIdentifiers.IdHmacWithSha224); + AddHMacKeyGenerator("SHA256", + PkcsObjectIdentifiers.IdHmacWithSha256); + AddHMacKeyGenerator("SHA384", + PkcsObjectIdentifiers.IdHmacWithSha384); + AddHMacKeyGenerator("SHA512", + PkcsObjectIdentifiers.IdHmacWithSha512); + AddHMacKeyGenerator("SHA512/224"); + AddHMacKeyGenerator("SHA512/256"); + AddHMacKeyGenerator("SHA3-224"); + AddHMacKeyGenerator("SHA3-256"); + AddHMacKeyGenerator("SHA3-384"); + AddHMacKeyGenerator("SHA3-512"); + AddHMacKeyGenerator("RIPEMD128"); + AddHMacKeyGenerator("RIPEMD160", + IanaObjectIdentifiers.HmacRipeMD160); + AddHMacKeyGenerator("TIGER", + IanaObjectIdentifiers.HmacTiger); + + + + // + // key pair generators. + // + AddKpgAlgorithm("DH", + "DIFFIEHELLMAN"); + AddKpgAlgorithm("DSA"); + AddKpgAlgorithm("EC", + // TODO Should this be an alias for ECDH? + X9ObjectIdentifiers.DHSinglePassStdDHSha1KdfScheme); + AddKpgAlgorithm("ECDH", + "ECIES"); + AddKpgAlgorithm("ECDHC"); + AddKpgAlgorithm("ECMQV", + X9ObjectIdentifiers.MqvSinglePassSha1KdfScheme); + AddKpgAlgorithm("ECDSA"); + AddKpgAlgorithm("ECGOST3410", + "ECGOST-3410", + "GOST-3410-2001"); + AddKpgAlgorithm("ELGAMAL"); + AddKpgAlgorithm("GOST3410", + "GOST-3410", + "GOST-3410-94"); + AddKpgAlgorithm("RSA", + "1.2.840.113549.1.1.1"); + + AddDefaultKeySizeEntries(64, "DES"); + AddDefaultKeySizeEntries(80, "SKIPJACK"); + AddDefaultKeySizeEntries(128, "AES128", "BLOWFISH", "CAMELLIA128", "CAST5", "DESEDE", + "HC128", "HMACMD2", "HMACMD4", "HMACMD5", "HMACRIPEMD128", "IDEA", "NOEKEON", + "RC2", "RC4", "RC5", "SALSA20", "SEED", "TEA", "XTEA", "VMPC", "VMPC-KSA3"); + AddDefaultKeySizeEntries(160, "HMACRIPEMD160", "HMACSHA1"); + AddDefaultKeySizeEntries(192, "AES", "AES192", "CAMELLIA192", "DESEDE3", "HMACTIGER", + "RIJNDAEL", "SERPENT", "TNEPRES"); + AddDefaultKeySizeEntries(224, "HMACSHA224", "HMACSHA512/224"); + AddDefaultKeySizeEntries(256, "AES256", "CAMELLIA", "CAMELLIA256", "CAST6", "GOST28147", + "HC256", "HMACSHA256", "HMACSHA512/256", "RC5-64", "RC6", "THREEFISH-256", "TWOFISH"); + AddDefaultKeySizeEntries(384, "HMACSHA384"); + AddDefaultKeySizeEntries(512, "HMACSHA512", "THREEFISH-512"); + AddDefaultKeySizeEntries(1024, "THREEFISH-1024"); + } + + private static void AddDefaultKeySizeEntries(int size, params string[] algorithms) + { + foreach (string algorithm in algorithms) + { + defaultKeySizes.Add(algorithm, size); + } + } + + private static void AddKgAlgorithm( + string canonicalName, + params object[] aliases) + { + kgAlgorithms[canonicalName] = canonicalName; + + foreach (object alias in aliases) + { + kgAlgorithms[alias.ToString()] = canonicalName; + } + } + + private static void AddKpgAlgorithm( + string canonicalName, + params object[] aliases) + { + kpgAlgorithms[canonicalName] = canonicalName; + + foreach (object alias in aliases) + { + kpgAlgorithms[alias.ToString()] = canonicalName; + } + } + + private static void AddHMacKeyGenerator( + string algorithm, + params object[] aliases) + { + string mainName = "HMAC" + algorithm; + + kgAlgorithms[mainName] = mainName; + kgAlgorithms["HMAC-" + algorithm] = mainName; + kgAlgorithms["HMAC/" + algorithm] = mainName; + + foreach (object alias in aliases) + { + kgAlgorithms[alias.ToString()] = mainName; + } + } + + // TODO Consider making this public + internal static string GetCanonicalKeyGeneratorAlgorithm( + string algorithm) + { + return (string) kgAlgorithms[Platform.ToUpperInvariant(algorithm)]; + } + + // TODO Consider making this public + internal static string GetCanonicalKeyPairGeneratorAlgorithm( + string algorithm) + { + return (string)kpgAlgorithms[Platform.ToUpperInvariant(algorithm)]; + } + + public static CipherKeyGenerator GetKeyGenerator( + DerObjectIdentifier oid) + { + return GetKeyGenerator(oid.Id); + } + + public static CipherKeyGenerator GetKeyGenerator( + string algorithm) + { + string canonicalName = GetCanonicalKeyGeneratorAlgorithm(algorithm); + + if (canonicalName == null) + throw new SecurityUtilityException("KeyGenerator " + algorithm + " not recognised."); + + int defaultKeySize = FindDefaultKeySize(canonicalName); + if (defaultKeySize == -1) + throw new SecurityUtilityException("KeyGenerator " + algorithm + + " (" + canonicalName + ") not supported."); + + if (canonicalName == "DES") + return new DesKeyGenerator(defaultKeySize); + + if (canonicalName == "DESEDE" || canonicalName == "DESEDE3") + return new DesEdeKeyGenerator(defaultKeySize); + + return new CipherKeyGenerator(defaultKeySize); + } + + public static IAsymmetricCipherKeyPairGenerator GetKeyPairGenerator( + DerObjectIdentifier oid) + { + return GetKeyPairGenerator(oid.Id); + } + + public static IAsymmetricCipherKeyPairGenerator GetKeyPairGenerator( + string algorithm) + { + string canonicalName = GetCanonicalKeyPairGeneratorAlgorithm(algorithm); + + if (canonicalName == null) + throw new SecurityUtilityException("KeyPairGenerator " + algorithm + " not recognised."); + + if (canonicalName == "DH") + return new DHKeyPairGenerator(); + + if (canonicalName == "DSA") + return new DsaKeyPairGenerator(); + + // "EC", "ECDH", "ECDHC", "ECDSA", "ECGOST3410", "ECMQV" + if (Platform.StartsWith(canonicalName, "EC")) + return new ECKeyPairGenerator(canonicalName); + + if (canonicalName == "ELGAMAL") + return new ElGamalKeyPairGenerator(); + + if (canonicalName == "GOST3410") + return new Gost3410KeyPairGenerator(); + + if (canonicalName == "RSA") + return new RsaKeyPairGenerator(); + + throw new SecurityUtilityException("KeyPairGenerator " + algorithm + + " (" + canonicalName + ") not supported."); + } + + internal static int GetDefaultKeySize( + DerObjectIdentifier oid) + { + return GetDefaultKeySize(oid.Id); + } + + internal static int GetDefaultKeySize( + string algorithm) + { + string canonicalName = GetCanonicalKeyGeneratorAlgorithm(algorithm); + + if (canonicalName == null) + throw new SecurityUtilityException("KeyGenerator " + algorithm + " not recognised."); + + int defaultKeySize = FindDefaultKeySize(canonicalName); + if (defaultKeySize == -1) + throw new SecurityUtilityException("KeyGenerator " + algorithm + + " (" + canonicalName + ") not supported."); + + return defaultKeySize; + } + + private static int FindDefaultKeySize( + string canonicalName) + { + if (!defaultKeySizes.Contains(canonicalName)) + return -1; + + return (int)defaultKeySizes[canonicalName]; + } + } +} diff --git a/bc-sharp-crypto/src/security/InvalidKeyException.cs b/bc-sharp-crypto/src/security/InvalidKeyException.cs new file mode 100644 index 0000000..ebad9e3 --- /dev/null +++ b/bc-sharp-crypto/src/security/InvalidKeyException.cs @@ -0,0 +1,14 @@ +using System; + +namespace Org.BouncyCastle.Security +{ +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT || PORTABLE) + [Serializable] +#endif + public class InvalidKeyException : KeyException + { + public InvalidKeyException() : base() { } + public InvalidKeyException(string message) : base(message) { } + public InvalidKeyException(string message, Exception exception) : base(message, exception) { } + } +} diff --git a/bc-sharp-crypto/src/security/InvalidParameterException.cs b/bc-sharp-crypto/src/security/InvalidParameterException.cs new file mode 100644 index 0000000..48172f4 --- /dev/null +++ b/bc-sharp-crypto/src/security/InvalidParameterException.cs @@ -0,0 +1,14 @@ +using System; + +namespace Org.BouncyCastle.Security +{ +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT || PORTABLE) + [Serializable] +#endif + public class InvalidParameterException : KeyException + { + public InvalidParameterException() : base() { } + public InvalidParameterException(string message) : base(message) { } + public InvalidParameterException(string message, Exception exception) : base(message, exception) { } + } +} diff --git a/bc-sharp-crypto/src/security/KeyException.cs b/bc-sharp-crypto/src/security/KeyException.cs new file mode 100644 index 0000000..e19fa89 --- /dev/null +++ b/bc-sharp-crypto/src/security/KeyException.cs @@ -0,0 +1,14 @@ +using System; + +namespace Org.BouncyCastle.Security +{ +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT || PORTABLE) + [Serializable] +#endif + public class KeyException : GeneralSecurityException + { + public KeyException() : base() { } + public KeyException(string message) : base(message) { } + public KeyException(string message, Exception exception) : base(message, exception) { } + } +} diff --git a/bc-sharp-crypto/src/security/MacUtilities.cs b/bc-sharp-crypto/src/security/MacUtilities.cs new file mode 100644 index 0000000..278f3be --- /dev/null +++ b/bc-sharp-crypto/src/security/MacUtilities.cs @@ -0,0 +1,256 @@ +using System; +using System.Collections; +using System.Globalization; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Iana; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Macs; +using Org.BouncyCastle.Crypto.Paddings; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Security +{ + /// + /// Utility class for creating HMac object from their names/Oids + /// + public sealed class MacUtilities + { + private MacUtilities() + { + } + + private static readonly IDictionary algorithms = Platform.CreateHashtable(); + //private static readonly IDictionary oids = Platform.CreateHashtable(); + + static MacUtilities() + { + algorithms[IanaObjectIdentifiers.HmacMD5.Id] = "HMAC-MD5"; + algorithms[IanaObjectIdentifiers.HmacRipeMD160.Id] = "HMAC-RIPEMD160"; + algorithms[IanaObjectIdentifiers.HmacSha1.Id] = "HMAC-SHA1"; + algorithms[IanaObjectIdentifiers.HmacTiger.Id] = "HMAC-TIGER"; + + algorithms[PkcsObjectIdentifiers.IdHmacWithSha1.Id] = "HMAC-SHA1"; + algorithms[PkcsObjectIdentifiers.IdHmacWithSha224.Id] = "HMAC-SHA224"; + algorithms[PkcsObjectIdentifiers.IdHmacWithSha256.Id] = "HMAC-SHA256"; + algorithms[PkcsObjectIdentifiers.IdHmacWithSha384.Id] = "HMAC-SHA384"; + algorithms[PkcsObjectIdentifiers.IdHmacWithSha512.Id] = "HMAC-SHA512"; + + // TODO AESMAC? + + algorithms["DES"] = "DESMAC"; + algorithms["DES/CFB8"] = "DESMAC/CFB8"; + algorithms["DES64"] = "DESMAC64"; + algorithms["DESEDE"] = "DESEDEMAC"; + algorithms[PkcsObjectIdentifiers.DesEde3Cbc.Id] = "DESEDEMAC"; + algorithms["DESEDE/CFB8"] = "DESEDEMAC/CFB8"; + algorithms["DESISO9797MAC"] = "DESWITHISO9797"; + algorithms["DESEDE64"] = "DESEDEMAC64"; + + algorithms["DESEDE64WITHISO7816-4PADDING"] = "DESEDEMAC64WITHISO7816-4PADDING"; + algorithms["DESEDEISO9797ALG1MACWITHISO7816-4PADDING"] = "DESEDEMAC64WITHISO7816-4PADDING"; + algorithms["DESEDEISO9797ALG1WITHISO7816-4PADDING"] = "DESEDEMAC64WITHISO7816-4PADDING"; + + algorithms["ISO9797ALG3"] = "ISO9797ALG3MAC"; + algorithms["ISO9797ALG3MACWITHISO7816-4PADDING"] = "ISO9797ALG3WITHISO7816-4PADDING"; + + algorithms["SKIPJACK"] = "SKIPJACKMAC"; + algorithms["SKIPJACK/CFB8"] = "SKIPJACKMAC/CFB8"; + algorithms["IDEA"] = "IDEAMAC"; + algorithms["IDEA/CFB8"] = "IDEAMAC/CFB8"; + algorithms["RC2"] = "RC2MAC"; + algorithms["RC2/CFB8"] = "RC2MAC/CFB8"; + algorithms["RC5"] = "RC5MAC"; + algorithms["RC5/CFB8"] = "RC5MAC/CFB8"; + algorithms["GOST28147"] = "GOST28147MAC"; + algorithms["VMPC"] = "VMPCMAC"; + algorithms["VMPC-MAC"] = "VMPCMAC"; + algorithms["SIPHASH"] = "SIPHASH-2-4"; + + algorithms["PBEWITHHMACSHA"] = "PBEWITHHMACSHA1"; + algorithms["1.3.14.3.2.26"] = "PBEWITHHMACSHA1"; + } + +// /// +// /// Returns a ObjectIdentifier for a given digest mechanism. +// /// +// /// A string representation of the digest meanism. +// /// A DerObjectIdentifier, null if the Oid is not available. +// public static DerObjectIdentifier GetObjectIdentifier( +// string mechanism) +// { +// mechanism = (string) algorithms[Platform.ToUpperInvariant(mechanism)]; +// +// if (mechanism != null) +// { +// return (DerObjectIdentifier)oids[mechanism]; +// } +// +// return null; +// } + +// public static ICollection Algorithms +// { +// get { return oids.Keys; } +// } + + public static IMac GetMac( + DerObjectIdentifier id) + { + return GetMac(id.Id); + } + + public static IMac GetMac( + string algorithm) + { + string upper = Platform.ToUpperInvariant(algorithm); + + string mechanism = (string) algorithms[upper]; + + if (mechanism == null) + { + mechanism = upper; + } + + if (Platform.StartsWith(mechanism, "PBEWITH")) + { + mechanism = mechanism.Substring("PBEWITH".Length); + } + + if (Platform.StartsWith(mechanism, "HMAC")) + { + string digestName; + if (Platform.StartsWith(mechanism, "HMAC-") || Platform.StartsWith(mechanism, "HMAC/")) + { + digestName = mechanism.Substring(5); + } + else + { + digestName = mechanism.Substring(4); + } + + return new HMac(DigestUtilities.GetDigest(digestName)); + } + + if (mechanism == "AESCMAC") + { + return new CMac(new AesEngine()); + } + if (mechanism == "DESMAC") + { + return new CbcBlockCipherMac(new DesEngine()); + } + if (mechanism == "DESMAC/CFB8") + { + return new CfbBlockCipherMac(new DesEngine()); + } + if (mechanism == "DESMAC64") + { + return new CbcBlockCipherMac(new DesEngine(), 64); + } + if (mechanism == "DESEDECMAC") + { + return new CMac(new DesEdeEngine()); + } + if (mechanism == "DESEDEMAC") + { + return new CbcBlockCipherMac(new DesEdeEngine()); + } + if (mechanism == "DESEDEMAC/CFB8") + { + return new CfbBlockCipherMac(new DesEdeEngine()); + } + if (mechanism == "DESEDEMAC64") + { + return new CbcBlockCipherMac(new DesEdeEngine(), 64); + } + if (mechanism == "DESEDEMAC64WITHISO7816-4PADDING") + { + return new CbcBlockCipherMac(new DesEdeEngine(), 64, new ISO7816d4Padding()); + } + if (mechanism == "DESWITHISO9797" + || mechanism == "ISO9797ALG3MAC") + { + return new ISO9797Alg3Mac(new DesEngine()); + } + if (mechanism == "ISO9797ALG3WITHISO7816-4PADDING") + { + return new ISO9797Alg3Mac(new DesEngine(), new ISO7816d4Padding()); + } + if (mechanism == "SKIPJACKMAC") + { + return new CbcBlockCipherMac(new SkipjackEngine()); + } + if (mechanism == "SKIPJACKMAC/CFB8") + { + return new CfbBlockCipherMac(new SkipjackEngine()); + } + if (mechanism == "IDEAMAC") + { + return new CbcBlockCipherMac(new IdeaEngine()); + } + if (mechanism == "IDEAMAC/CFB8") + { + return new CfbBlockCipherMac(new IdeaEngine()); + } + if (mechanism == "RC2MAC") + { + return new CbcBlockCipherMac(new RC2Engine()); + } + if (mechanism == "RC2MAC/CFB8") + { + return new CfbBlockCipherMac(new RC2Engine()); + } + if (mechanism == "RC5MAC") + { + return new CbcBlockCipherMac(new RC532Engine()); + } + if (mechanism == "RC5MAC/CFB8") + { + return new CfbBlockCipherMac(new RC532Engine()); + } + if (mechanism == "GOST28147MAC") + { + return new Gost28147Mac(); + } + if (mechanism == "VMPCMAC") + { + return new VmpcMac(); + } + if (mechanism == "SIPHASH-2-4") + { + return new SipHash(); + } + throw new SecurityUtilityException("Mac " + mechanism + " not recognised."); + } + + public static string GetAlgorithmName( + DerObjectIdentifier oid) + { + return (string) algorithms[oid.Id]; + } + + public static byte[] CalculateMac(string algorithm, ICipherParameters cp, byte[] input) + { + IMac mac = GetMac(algorithm); + mac.Init(cp); + mac.BlockUpdate(input, 0, input.Length); + return DoFinal(mac); + } + + public static byte[] DoFinal(IMac mac) + { + byte[] b = new byte[mac.GetMacSize()]; + mac.DoFinal(b, 0); + return b; + } + + public static byte[] DoFinal(IMac mac, byte[] input) + { + mac.BlockUpdate(input, 0, input.Length); + return DoFinal(mac); + } + } +} diff --git a/bc-sharp-crypto/src/security/NoSuchAlgorithmException.cs b/bc-sharp-crypto/src/security/NoSuchAlgorithmException.cs new file mode 100644 index 0000000..c56ec65 --- /dev/null +++ b/bc-sharp-crypto/src/security/NoSuchAlgorithmException.cs @@ -0,0 +1,15 @@ +using System; + +namespace Org.BouncyCastle.Security +{ + [Obsolete("Never thrown")] +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT || PORTABLE) + [Serializable] +#endif + public class NoSuchAlgorithmException : GeneralSecurityException + { + public NoSuchAlgorithmException() : base() {} + public NoSuchAlgorithmException(string message) : base(message) {} + public NoSuchAlgorithmException(string message, Exception exception) : base(message, exception) {} + } +} diff --git a/bc-sharp-crypto/src/security/ParameterUtilities.cs b/bc-sharp-crypto/src/security/ParameterUtilities.cs new file mode 100644 index 0000000..c121558 --- /dev/null +++ b/bc-sharp-crypto/src/security/ParameterUtilities.cs @@ -0,0 +1,325 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.CryptoPro; +using Org.BouncyCastle.Asn1.Kisa; +using Org.BouncyCastle.Asn1.Misc; +using Org.BouncyCastle.Asn1.Nist; +using Org.BouncyCastle.Asn1.Ntt; +using Org.BouncyCastle.Asn1.Oiw; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Security +{ + public sealed class ParameterUtilities + { + private ParameterUtilities() + { + } + + private static readonly IDictionary algorithms = Platform.CreateHashtable(); + private static readonly IDictionary basicIVSizes = Platform.CreateHashtable(); + + static ParameterUtilities() + { + AddAlgorithm("AES", + "AESWRAP"); + AddAlgorithm("AES128", + "2.16.840.1.101.3.4.2", + NistObjectIdentifiers.IdAes128Cbc, + NistObjectIdentifiers.IdAes128Cfb, + NistObjectIdentifiers.IdAes128Ecb, + NistObjectIdentifiers.IdAes128Ofb, + NistObjectIdentifiers.IdAes128Wrap); + AddAlgorithm("AES192", + "2.16.840.1.101.3.4.22", + NistObjectIdentifiers.IdAes192Cbc, + NistObjectIdentifiers.IdAes192Cfb, + NistObjectIdentifiers.IdAes192Ecb, + NistObjectIdentifiers.IdAes192Ofb, + NistObjectIdentifiers.IdAes192Wrap); + AddAlgorithm("AES256", + "2.16.840.1.101.3.4.42", + NistObjectIdentifiers.IdAes256Cbc, + NistObjectIdentifiers.IdAes256Cfb, + NistObjectIdentifiers.IdAes256Ecb, + NistObjectIdentifiers.IdAes256Ofb, + NistObjectIdentifiers.IdAes256Wrap); + AddAlgorithm("BLOWFISH", + "1.3.6.1.4.1.3029.1.2"); + AddAlgorithm("CAMELLIA", + "CAMELLIAWRAP"); + AddAlgorithm("CAMELLIA128", + NttObjectIdentifiers.IdCamellia128Cbc, + NttObjectIdentifiers.IdCamellia128Wrap); + AddAlgorithm("CAMELLIA192", + NttObjectIdentifiers.IdCamellia192Cbc, + NttObjectIdentifiers.IdCamellia192Wrap); + AddAlgorithm("CAMELLIA256", + NttObjectIdentifiers.IdCamellia256Cbc, + NttObjectIdentifiers.IdCamellia256Wrap); + AddAlgorithm("CAST5", + "1.2.840.113533.7.66.10"); + AddAlgorithm("CAST6"); + AddAlgorithm("DES", + OiwObjectIdentifiers.DesCbc, + OiwObjectIdentifiers.DesCfb, + OiwObjectIdentifiers.DesEcb, + OiwObjectIdentifiers.DesOfb); + AddAlgorithm("DESEDE", + "DESEDEWRAP", + "TDEA", + OiwObjectIdentifiers.DesEde, + PkcsObjectIdentifiers.IdAlgCms3DesWrap); + AddAlgorithm("DESEDE3", + PkcsObjectIdentifiers.DesEde3Cbc); + AddAlgorithm("GOST28147", + "GOST", + "GOST-28147", + CryptoProObjectIdentifiers.GostR28147Cbc); + AddAlgorithm("HC128"); + AddAlgorithm("HC256"); + AddAlgorithm("IDEA", + "1.3.6.1.4.1.188.7.1.1.2"); + AddAlgorithm("NOEKEON"); + AddAlgorithm("RC2", + PkcsObjectIdentifiers.RC2Cbc, + PkcsObjectIdentifiers.IdAlgCmsRC2Wrap); + AddAlgorithm("RC4", + "ARC4", + "1.2.840.113549.3.4"); + AddAlgorithm("RC5", + "RC5-32"); + AddAlgorithm("RC5-64"); + AddAlgorithm("RC6"); + AddAlgorithm("RIJNDAEL"); + AddAlgorithm("SALSA20"); + AddAlgorithm("SEED", + KisaObjectIdentifiers.IdNpkiAppCmsSeedWrap, + KisaObjectIdentifiers.IdSeedCbc); + AddAlgorithm("SERPENT"); + AddAlgorithm("SKIPJACK"); + AddAlgorithm("TEA"); + AddAlgorithm("THREEFISH-256"); + AddAlgorithm("THREEFISH-512"); + AddAlgorithm("THREEFISH-1024"); + AddAlgorithm("TNEPRES"); + AddAlgorithm("TWOFISH"); + AddAlgorithm("VMPC"); + AddAlgorithm("VMPC-KSA3"); + AddAlgorithm("XTEA"); + + AddBasicIVSizeEntries(8, "BLOWFISH", "DES", "DESEDE", "DESEDE3"); + AddBasicIVSizeEntries(16, "AES", "AES128", "AES192", "AES256", + "CAMELLIA", "CAMELLIA128", "CAMELLIA192", "CAMELLIA256", "NOEKEON", "SEED"); + + // TODO These algorithms support an IV + // but JCE doesn't seem to provide an AlgorithmParametersGenerator for them + // "RIJNDAEL", "SKIPJACK", "TWOFISH" + } + + private static void AddAlgorithm( + string canonicalName, + params object[] aliases) + { + algorithms[canonicalName] = canonicalName; + + foreach (object alias in aliases) + { + algorithms[alias.ToString()] = canonicalName; + } + } + + private static void AddBasicIVSizeEntries(int size, params string[] algorithms) + { + foreach (string algorithm in algorithms) + { + basicIVSizes.Add(algorithm, size); + } + } + + public static string GetCanonicalAlgorithmName( + string algorithm) + { + return (string) algorithms[Platform.ToUpperInvariant(algorithm)]; + } + + public static KeyParameter CreateKeyParameter( + DerObjectIdentifier algOid, + byte[] keyBytes) + { + return CreateKeyParameter(algOid.Id, keyBytes, 0, keyBytes.Length); + } + + public static KeyParameter CreateKeyParameter( + string algorithm, + byte[] keyBytes) + { + return CreateKeyParameter(algorithm, keyBytes, 0, keyBytes.Length); + } + + public static KeyParameter CreateKeyParameter( + DerObjectIdentifier algOid, + byte[] keyBytes, + int offset, + int length) + { + return CreateKeyParameter(algOid.Id, keyBytes, offset, length); + } + + public static KeyParameter CreateKeyParameter( + string algorithm, + byte[] keyBytes, + int offset, + int length) + { + if (algorithm == null) + throw new ArgumentNullException("algorithm"); + + string canonical = GetCanonicalAlgorithmName(algorithm); + + if (canonical == null) + throw new SecurityUtilityException("Algorithm " + algorithm + " not recognised."); + + if (canonical == "DES") + return new DesParameters(keyBytes, offset, length); + + if (canonical == "DESEDE" || canonical =="DESEDE3") + return new DesEdeParameters(keyBytes, offset, length); + + if (canonical == "RC2") + return new RC2Parameters(keyBytes, offset, length); + + return new KeyParameter(keyBytes, offset, length); + } + + public static ICipherParameters GetCipherParameters( + DerObjectIdentifier algOid, + ICipherParameters key, + Asn1Object asn1Params) + { + return GetCipherParameters(algOid.Id, key, asn1Params); + } + + public static ICipherParameters GetCipherParameters( + string algorithm, + ICipherParameters key, + Asn1Object asn1Params) + { + if (algorithm == null) + throw new ArgumentNullException("algorithm"); + + string canonical = GetCanonicalAlgorithmName(algorithm); + + if (canonical == null) + throw new SecurityUtilityException("Algorithm " + algorithm + " not recognised."); + + byte[] iv = null; + + try + { + // TODO These algorithms support an IV + // but JCE doesn't seem to provide an AlgorithmParametersGenerator for them + // "RIJNDAEL", "SKIPJACK", "TWOFISH" + + int basicIVKeySize = FindBasicIVSize(canonical); + if (basicIVKeySize != -1 + || canonical == "RIJNDAEL" || canonical == "SKIPJACK" || canonical == "TWOFISH") + { + iv = ((Asn1OctetString) asn1Params).GetOctets(); + } + else if (canonical == "CAST5") + { + iv = Cast5CbcParameters.GetInstance(asn1Params).GetIV(); + } + else if (canonical == "IDEA") + { + iv = IdeaCbcPar.GetInstance(asn1Params).GetIV(); + } + else if (canonical == "RC2") + { + iv = RC2CbcParameter.GetInstance(asn1Params).GetIV(); + } + } + catch (Exception e) + { + throw new ArgumentException("Could not process ASN.1 parameters", e); + } + + if (iv != null) + { + return new ParametersWithIV(key, iv); + } + + throw new SecurityUtilityException("Algorithm " + algorithm + " not recognised."); + } + + public static Asn1Encodable GenerateParameters( + DerObjectIdentifier algID, + SecureRandom random) + { + return GenerateParameters(algID.Id, random); + } + + public static Asn1Encodable GenerateParameters( + string algorithm, + SecureRandom random) + { + if (algorithm == null) + throw new ArgumentNullException("algorithm"); + + string canonical = GetCanonicalAlgorithmName(algorithm); + + if (canonical == null) + throw new SecurityUtilityException("Algorithm " + algorithm + " not recognised."); + + // TODO These algorithms support an IV + // but JCE doesn't seem to provide an AlgorithmParametersGenerator for them + // "RIJNDAEL", "SKIPJACK", "TWOFISH" + + int basicIVKeySize = FindBasicIVSize(canonical); + if (basicIVKeySize != -1) + return CreateIVOctetString(random, basicIVKeySize); + + if (canonical == "CAST5") + return new Cast5CbcParameters(CreateIV(random, 8), 128); + + if (canonical == "IDEA") + return new IdeaCbcPar(CreateIV(random, 8)); + + if (canonical == "RC2") + return new RC2CbcParameter(CreateIV(random, 8)); + + throw new SecurityUtilityException("Algorithm " + algorithm + " not recognised."); + } + + private static Asn1OctetString CreateIVOctetString( + SecureRandom random, + int ivLength) + { + return new DerOctetString(CreateIV(random, ivLength)); + } + + private static byte[] CreateIV( + SecureRandom random, + int ivLength) + { + byte[] iv = new byte[ivLength]; + random.NextBytes(iv); + return iv; + } + + private static int FindBasicIVSize( + string canonicalName) + { + if (!basicIVSizes.Contains(canonicalName)) + return -1; + + return (int)basicIVSizes[canonicalName]; + } + } +} diff --git a/bc-sharp-crypto/src/security/PbeUtilities.cs b/bc-sharp-crypto/src/security/PbeUtilities.cs new file mode 100644 index 0000000..33f31e5 --- /dev/null +++ b/bc-sharp-crypto/src/security/PbeUtilities.cs @@ -0,0 +1,663 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.BC; +using Org.BouncyCastle.Asn1.Nist; +using Org.BouncyCastle.Asn1.Oiw; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.TeleTrust; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Macs; +using Org.BouncyCastle.Crypto.Modes; +using Org.BouncyCastle.Crypto.Paddings; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Security +{ + /// + /// + /// + public sealed class PbeUtilities + { + private PbeUtilities() + { + } + + const string Pkcs5S1 = "Pkcs5S1"; + const string Pkcs5S2 = "Pkcs5S2"; + const string Pkcs12 = "Pkcs12"; + const string OpenSsl = "OpenSsl"; + + private static readonly IDictionary algorithms = Platform.CreateHashtable(); + private static readonly IDictionary algorithmType = Platform.CreateHashtable(); + private static readonly IDictionary oids = Platform.CreateHashtable(); + + static PbeUtilities() + { + algorithms["PKCS5SCHEME1"] = "Pkcs5scheme1"; + algorithms["PKCS5SCHEME2"] = "Pkcs5scheme2"; + algorithms[PkcsObjectIdentifiers.IdPbeS2.Id] = "Pkcs5scheme2"; +// algorithms[PkcsObjectIdentifiers.IdPbkdf2.Id] = "Pkcs5scheme2"; + + // FIXME Add support for these? (see Pkcs8Generator) +// algorithms[PkcsObjectIdentifiers.DesEde3Cbc.Id] = "Pkcs5scheme2"; +// algorithms[NistObjectIdentifiers.IdAes128Cbc.Id] = "Pkcs5scheme2"; +// algorithms[NistObjectIdentifiers.IdAes192Cbc.Id] = "Pkcs5scheme2"; +// algorithms[NistObjectIdentifiers.IdAes256Cbc.Id] = "Pkcs5scheme2"; + + algorithms["PBEWITHMD2ANDDES-CBC"] = "PBEwithMD2andDES-CBC"; + algorithms[PkcsObjectIdentifiers.PbeWithMD2AndDesCbc.Id] = "PBEwithMD2andDES-CBC"; + algorithms["PBEWITHMD2ANDRC2-CBC"] = "PBEwithMD2andRC2-CBC"; + algorithms[PkcsObjectIdentifiers.PbeWithMD2AndRC2Cbc.Id] = "PBEwithMD2andRC2-CBC"; + algorithms["PBEWITHMD5ANDDES-CBC"] = "PBEwithMD5andDES-CBC"; + algorithms[PkcsObjectIdentifiers.PbeWithMD5AndDesCbc.Id] = "PBEwithMD5andDES-CBC"; + algorithms["PBEWITHMD5ANDRC2-CBC"] = "PBEwithMD5andRC2-CBC"; + algorithms[PkcsObjectIdentifiers.PbeWithMD5AndRC2Cbc.Id] = "PBEwithMD5andRC2-CBC"; + algorithms["PBEWITHSHA1ANDDES"] = "PBEwithSHA-1andDES-CBC"; + algorithms["PBEWITHSHA-1ANDDES"] = "PBEwithSHA-1andDES-CBC"; + algorithms["PBEWITHSHA1ANDDES-CBC"] = "PBEwithSHA-1andDES-CBC"; + algorithms["PBEWITHSHA-1ANDDES-CBC"] = "PBEwithSHA-1andDES-CBC"; + algorithms[PkcsObjectIdentifiers.PbeWithSha1AndDesCbc.Id] = "PBEwithSHA-1andDES-CBC"; + algorithms["PBEWITHSHA1ANDRC2"] = "PBEwithSHA-1andRC2-CBC"; + algorithms["PBEWITHSHA-1ANDRC2"] = "PBEwithSHA-1andRC2-CBC"; + algorithms["PBEWITHSHA1ANDRC2-CBC"] = "PBEwithSHA-1andRC2-CBC"; + algorithms["PBEWITHSHA-1ANDRC2-CBC"] = "PBEwithSHA-1andRC2-CBC"; + algorithms[PkcsObjectIdentifiers.PbeWithSha1AndRC2Cbc.Id] = "PBEwithSHA-1andRC2-CBC"; + algorithms["PKCS12"] = "Pkcs12"; + algorithms[BCObjectIdentifiers.bc_pbe_sha1_pkcs12_aes128_cbc.Id] = "PBEwithSHA-1and128bitAES-CBC-BC"; + algorithms[BCObjectIdentifiers.bc_pbe_sha1_pkcs12_aes192_cbc.Id] = "PBEwithSHA-1and192bitAES-CBC-BC"; + algorithms[BCObjectIdentifiers.bc_pbe_sha1_pkcs12_aes256_cbc.Id] = "PBEwithSHA-1and256bitAES-CBC-BC"; + algorithms[BCObjectIdentifiers.bc_pbe_sha256_pkcs12_aes128_cbc.Id] = "PBEwithSHA-256and128bitAES-CBC-BC"; + algorithms[BCObjectIdentifiers.bc_pbe_sha256_pkcs12_aes192_cbc.Id] = "PBEwithSHA-256and192bitAES-CBC-BC"; + algorithms[BCObjectIdentifiers.bc_pbe_sha256_pkcs12_aes256_cbc.Id] = "PBEwithSHA-256and256bitAES-CBC-BC"; + algorithms["PBEWITHSHAAND128BITRC4"] = "PBEwithSHA-1and128bitRC4"; + algorithms["PBEWITHSHA1AND128BITRC4"] = "PBEwithSHA-1and128bitRC4"; + algorithms["PBEWITHSHA-1AND128BITRC4"] = "PBEwithSHA-1and128bitRC4"; + algorithms[PkcsObjectIdentifiers.PbeWithShaAnd128BitRC4.Id] = "PBEwithSHA-1and128bitRC4"; + algorithms["PBEWITHSHAAND40BITRC4"] = "PBEwithSHA-1and40bitRC4"; + algorithms["PBEWITHSHA1AND40BITRC4"] = "PBEwithSHA-1and40bitRC4"; + algorithms["PBEWITHSHA-1AND40BITRC4"] = "PBEwithSHA-1and40bitRC4"; + algorithms[PkcsObjectIdentifiers.PbeWithShaAnd40BitRC4.Id] = "PBEwithSHA-1and40bitRC4"; + algorithms["PBEWITHSHAAND3-KEYDESEDE-CBC"] = "PBEwithSHA-1and3-keyDESEDE-CBC"; + algorithms["PBEWITHSHAAND3-KEYTRIPLEDES-CBC"] = "PBEwithSHA-1and3-keyDESEDE-CBC"; + algorithms["PBEWITHSHA1AND3-KEYDESEDE-CBC"] = "PBEwithSHA-1and3-keyDESEDE-CBC"; + algorithms["PBEWITHSHA1AND3-KEYTRIPLEDES-CBC"] = "PBEwithSHA-1and3-keyDESEDE-CBC"; + algorithms["PBEWITHSHA-1AND3-KEYDESEDE-CBC"] = "PBEwithSHA-1and3-keyDESEDE-CBC"; + algorithms["PBEWITHSHA-1AND3-KEYTRIPLEDES-CBC"] = "PBEwithSHA-1and3-keyDESEDE-CBC"; + algorithms[PkcsObjectIdentifiers.PbeWithShaAnd3KeyTripleDesCbc.Id] = "PBEwithSHA-1and3-keyDESEDE-CBC"; + algorithms["PBEWITHSHAAND2-KEYDESEDE-CBC"] = "PBEwithSHA-1and2-keyDESEDE-CBC"; + algorithms["PBEWITHSHAAND2-KEYTRIPLEDES-CBC"] = "PBEwithSHA-1and2-keyDESEDE-CBC"; + algorithms["PBEWITHSHA1AND2-KEYDESEDE-CBC"] = "PBEwithSHA-1and2-keyDESEDE-CBC"; + algorithms["PBEWITHSHA1AND2-KEYTRIPLEDES-CBC"] = "PBEwithSHA-1and2-keyDESEDE-CBC"; + algorithms["PBEWITHSHA-1AND2-KEYDESEDE-CBC"] = "PBEwithSHA-1and2-keyDESEDE-CBC"; + algorithms["PBEWITHSHA-1AND2-KEYTRIPLEDES-CBC"] = "PBEwithSHA-1and2-keyDESEDE-CBC"; + algorithms[PkcsObjectIdentifiers.PbeWithShaAnd2KeyTripleDesCbc.Id] = "PBEwithSHA-1and2-keyDESEDE-CBC"; + algorithms["PBEWITHSHAAND128BITRC2-CBC"] = "PBEwithSHA-1and128bitRC2-CBC"; + algorithms["PBEWITHSHA1AND128BITRC2-CBC"] = "PBEwithSHA-1and128bitRC2-CBC"; + algorithms["PBEWITHSHA-1AND128BITRC2-CBC"] = "PBEwithSHA-1and128bitRC2-CBC"; + algorithms[PkcsObjectIdentifiers.PbeWithShaAnd128BitRC2Cbc.Id] = "PBEwithSHA-1and128bitRC2-CBC"; + algorithms["PBEWITHSHAAND40BITRC2-CBC"] = "PBEwithSHA-1and40bitRC2-CBC"; + algorithms["PBEWITHSHA1AND40BITRC2-CBC"] = "PBEwithSHA-1and40bitRC2-CBC"; + algorithms["PBEWITHSHA-1AND40BITRC2-CBC"] = "PBEwithSHA-1and40bitRC2-CBC"; + algorithms[PkcsObjectIdentifiers.PbewithShaAnd40BitRC2Cbc.Id] = "PBEwithSHA-1and40bitRC2-CBC"; + algorithms["PBEWITHSHAAND128BITAES-CBC-BC"] = "PBEwithSHA-1and128bitAES-CBC-BC"; + algorithms["PBEWITHSHA1AND128BITAES-CBC-BC"] = "PBEwithSHA-1and128bitAES-CBC-BC"; + algorithms["PBEWITHSHA-1AND128BITAES-CBC-BC"] = "PBEwithSHA-1and128bitAES-CBC-BC"; + algorithms["PBEWITHSHAAND192BITAES-CBC-BC"] = "PBEwithSHA-1and192bitAES-CBC-BC"; + algorithms["PBEWITHSHA1AND192BITAES-CBC-BC"] = "PBEwithSHA-1and192bitAES-CBC-BC"; + algorithms["PBEWITHSHA-1AND192BITAES-CBC-BC"] = "PBEwithSHA-1and192bitAES-CBC-BC"; + algorithms["PBEWITHSHAAND256BITAES-CBC-BC"] = "PBEwithSHA-1and256bitAES-CBC-BC"; + algorithms["PBEWITHSHA1AND256BITAES-CBC-BC"] = "PBEwithSHA-1and256bitAES-CBC-BC"; + algorithms["PBEWITHSHA-1AND256BITAES-CBC-BC"] = "PBEwithSHA-1and256bitAES-CBC-BC"; + algorithms["PBEWITHSHA256AND128BITAES-CBC-BC"] = "PBEwithSHA-256and128bitAES-CBC-BC"; + algorithms["PBEWITHSHA-256AND128BITAES-CBC-BC"] = "PBEwithSHA-256and128bitAES-CBC-BC"; + algorithms["PBEWITHSHA256AND192BITAES-CBC-BC"] = "PBEwithSHA-256and192bitAES-CBC-BC"; + algorithms["PBEWITHSHA-256AND192BITAES-CBC-BC"] = "PBEwithSHA-256and192bitAES-CBC-BC"; + algorithms["PBEWITHSHA256AND256BITAES-CBC-BC"] = "PBEwithSHA-256and256bitAES-CBC-BC"; + algorithms["PBEWITHSHA-256AND256BITAES-CBC-BC"] = "PBEwithSHA-256and256bitAES-CBC-BC"; + algorithms["PBEWITHSHAANDIDEA"] = "PBEwithSHA-1andIDEA-CBC"; + algorithms["PBEWITHSHAANDIDEA-CBC"] = "PBEwithSHA-1andIDEA-CBC"; + algorithms["PBEWITHSHAANDTWOFISH"] = "PBEwithSHA-1andTWOFISH-CBC"; + algorithms["PBEWITHSHAANDTWOFISH-CBC"] = "PBEwithSHA-1andTWOFISH-CBC"; + algorithms["PBEWITHHMACSHA1"] = "PBEwithHmacSHA-1"; + algorithms["PBEWITHHMACSHA-1"] = "PBEwithHmacSHA-1"; + algorithms[OiwObjectIdentifiers.IdSha1.Id] = "PBEwithHmacSHA-1"; + algorithms["PBEWITHHMACSHA224"] = "PBEwithHmacSHA-224"; + algorithms["PBEWITHHMACSHA-224"] = "PBEwithHmacSHA-224"; + algorithms[NistObjectIdentifiers.IdSha224.Id] = "PBEwithHmacSHA-224"; + algorithms["PBEWITHHMACSHA256"] = "PBEwithHmacSHA-256"; + algorithms["PBEWITHHMACSHA-256"] = "PBEwithHmacSHA-256"; + algorithms[NistObjectIdentifiers.IdSha256.Id] = "PBEwithHmacSHA-256"; + algorithms["PBEWITHHMACRIPEMD128"] = "PBEwithHmacRipeMD128"; + algorithms[TeleTrusTObjectIdentifiers.RipeMD128.Id] = "PBEwithHmacRipeMD128"; + algorithms["PBEWITHHMACRIPEMD160"] = "PBEwithHmacRipeMD160"; + algorithms[TeleTrusTObjectIdentifiers.RipeMD160.Id] = "PBEwithHmacRipeMD160"; + algorithms["PBEWITHHMACRIPEMD256"] = "PBEwithHmacRipeMD256"; + algorithms[TeleTrusTObjectIdentifiers.RipeMD256.Id] = "PBEwithHmacRipeMD256"; + algorithms["PBEWITHHMACTIGER"] = "PBEwithHmacTiger"; + + algorithms["PBEWITHMD5AND128BITAES-CBC-OPENSSL"] = "PBEwithMD5and128bitAES-CBC-OpenSSL"; + algorithms["PBEWITHMD5AND192BITAES-CBC-OPENSSL"] = "PBEwithMD5and192bitAES-CBC-OpenSSL"; + algorithms["PBEWITHMD5AND256BITAES-CBC-OPENSSL"] = "PBEwithMD5and256bitAES-CBC-OpenSSL"; + + algorithmType["Pkcs5scheme1"] = Pkcs5S1; + algorithmType["Pkcs5scheme2"] = Pkcs5S2; + algorithmType["PBEwithMD2andDES-CBC"] = Pkcs5S1; + algorithmType["PBEwithMD2andRC2-CBC"] = Pkcs5S1; + algorithmType["PBEwithMD5andDES-CBC"] = Pkcs5S1; + algorithmType["PBEwithMD5andRC2-CBC"] = Pkcs5S1; + algorithmType["PBEwithSHA-1andDES-CBC"] = Pkcs5S1; + algorithmType["PBEwithSHA-1andRC2-CBC"] = Pkcs5S1; + algorithmType["Pkcs12"] = Pkcs12; + algorithmType["PBEwithSHA-1and128bitRC4"] = Pkcs12; + algorithmType["PBEwithSHA-1and40bitRC4"] = Pkcs12; + algorithmType["PBEwithSHA-1and3-keyDESEDE-CBC"] = Pkcs12; + algorithmType["PBEwithSHA-1and2-keyDESEDE-CBC"] = Pkcs12; + algorithmType["PBEwithSHA-1and128bitRC2-CBC"] = Pkcs12; + algorithmType["PBEwithSHA-1and40bitRC2-CBC"] = Pkcs12; + algorithmType["PBEwithSHA-1and128bitAES-CBC-BC"] = Pkcs12; + algorithmType["PBEwithSHA-1and192bitAES-CBC-BC"] = Pkcs12; + algorithmType["PBEwithSHA-1and256bitAES-CBC-BC"] = Pkcs12; + algorithmType["PBEwithSHA-256and128bitAES-CBC-BC"] = Pkcs12; + algorithmType["PBEwithSHA-256and192bitAES-CBC-BC"] = Pkcs12; + algorithmType["PBEwithSHA-256and256bitAES-CBC-BC"] = Pkcs12; + algorithmType["PBEwithSHA-1andIDEA-CBC"] = Pkcs12; + algorithmType["PBEwithSHA-1andTWOFISH-CBC"] = Pkcs12; + algorithmType["PBEwithHmacSHA-1"] = Pkcs12; + algorithmType["PBEwithHmacSHA-224"] = Pkcs12; + algorithmType["PBEwithHmacSHA-256"] = Pkcs12; + algorithmType["PBEwithHmacRipeMD128"] = Pkcs12; + algorithmType["PBEwithHmacRipeMD160"] = Pkcs12; + algorithmType["PBEwithHmacRipeMD256"] = Pkcs12; + algorithmType["PBEwithHmacTiger"] = Pkcs12; + + algorithmType["PBEwithMD5and128bitAES-CBC-OpenSSL"] = OpenSsl; + algorithmType["PBEwithMD5and192bitAES-CBC-OpenSSL"] = OpenSsl; + algorithmType["PBEwithMD5and256bitAES-CBC-OpenSSL"] = OpenSsl; + + oids["PBEwithMD2andDES-CBC"] = PkcsObjectIdentifiers.PbeWithMD2AndDesCbc; + oids["PBEwithMD2andRC2-CBC"] = PkcsObjectIdentifiers.PbeWithMD2AndRC2Cbc; + oids["PBEwithMD5andDES-CBC"] = PkcsObjectIdentifiers.PbeWithMD5AndDesCbc; + oids["PBEwithMD5andRC2-CBC"] = PkcsObjectIdentifiers.PbeWithMD5AndRC2Cbc; + oids["PBEwithSHA-1andDES-CBC"] = PkcsObjectIdentifiers.PbeWithSha1AndDesCbc; + oids["PBEwithSHA-1andRC2-CBC"] = PkcsObjectIdentifiers.PbeWithSha1AndRC2Cbc; + oids["PBEwithSHA-1and128bitRC4"] = PkcsObjectIdentifiers.PbeWithShaAnd128BitRC4; + oids["PBEwithSHA-1and40bitRC4"] = PkcsObjectIdentifiers.PbeWithShaAnd40BitRC4; + oids["PBEwithSHA-1and3-keyDESEDE-CBC"] = PkcsObjectIdentifiers.PbeWithShaAnd3KeyTripleDesCbc; + oids["PBEwithSHA-1and2-keyDESEDE-CBC"] = PkcsObjectIdentifiers.PbeWithShaAnd2KeyTripleDesCbc; + oids["PBEwithSHA-1and128bitRC2-CBC"] = PkcsObjectIdentifiers.PbeWithShaAnd128BitRC2Cbc; + oids["PBEwithSHA-1and40bitRC2-CBC"] = PkcsObjectIdentifiers.PbewithShaAnd40BitRC2Cbc; + oids["PBEwithHmacSHA-1"] = OiwObjectIdentifiers.IdSha1; + oids["PBEwithHmacSHA-224"] = NistObjectIdentifiers.IdSha224; + oids["PBEwithHmacSHA-256"] = NistObjectIdentifiers.IdSha256; + oids["PBEwithHmacRipeMD128"] = TeleTrusTObjectIdentifiers.RipeMD128; + oids["PBEwithHmacRipeMD160"] = TeleTrusTObjectIdentifiers.RipeMD160; + oids["PBEwithHmacRipeMD256"] = TeleTrusTObjectIdentifiers.RipeMD256; + oids["Pkcs5scheme2"] = PkcsObjectIdentifiers.IdPbeS2; + } + + static PbeParametersGenerator MakePbeGenerator( + string type, + IDigest digest, + byte[] key, + byte[] salt, + int iterationCount) + { + PbeParametersGenerator generator; + + if (type.Equals(Pkcs5S1)) + { + generator = new Pkcs5S1ParametersGenerator(digest); + } + else if (type.Equals(Pkcs5S2)) + { + generator = new Pkcs5S2ParametersGenerator(); + } + else if (type.Equals(Pkcs12)) + { + generator = new Pkcs12ParametersGenerator(digest); + } + else if (type.Equals(OpenSsl)) + { + generator = new OpenSslPbeParametersGenerator(); + } + else + { + throw new ArgumentException("Unknown PBE type: " + type, "type"); + } + + generator.Init(key, salt, iterationCount); + return generator; + } + + /// + /// Returns a ObjectIdentifier for a give encoding. + /// + /// A string representation of the encoding. + /// A DerObjectIdentifier, null if the Oid is not available. + public static DerObjectIdentifier GetObjectIdentifier( + string mechanism) + { + mechanism = (string) algorithms[Platform.ToUpperInvariant(mechanism)]; + if (mechanism != null) + { + return (DerObjectIdentifier)oids[mechanism]; + } + return null; + } + + public static ICollection Algorithms + { + get { return oids.Keys; } + } + + public static bool IsPkcs12( + string algorithm) + { + string mechanism = (string)algorithms[Platform.ToUpperInvariant(algorithm)]; + + return mechanism != null && Pkcs12.Equals(algorithmType[mechanism]); + } + + public static bool IsPkcs5Scheme1( + string algorithm) + { + string mechanism = (string)algorithms[Platform.ToUpperInvariant(algorithm)]; + + return mechanism != null && Pkcs5S1.Equals(algorithmType[mechanism]); + } + + public static bool IsPkcs5Scheme2( + string algorithm) + { + string mechanism = (string)algorithms[Platform.ToUpperInvariant(algorithm)]; + + return mechanism != null && Pkcs5S2.Equals(algorithmType[mechanism]); + } + + public static bool IsOpenSsl( + string algorithm) + { + string mechanism = (string)algorithms[Platform.ToUpperInvariant(algorithm)]; + + return mechanism != null && OpenSsl.Equals(algorithmType[mechanism]); + } + + public static bool IsPbeAlgorithm( + string algorithm) + { + string mechanism = (string)algorithms[Platform.ToUpperInvariant(algorithm)]; + + return mechanism != null && algorithmType[mechanism] != null; + } + + public static Asn1Encodable GenerateAlgorithmParameters( + DerObjectIdentifier algorithmOid, + byte[] salt, + int iterationCount) + { + return GenerateAlgorithmParameters(algorithmOid.Id, salt, iterationCount); + } + + public static Asn1Encodable GenerateAlgorithmParameters( + string algorithm, + byte[] salt, + int iterationCount) + { + if (IsPkcs12(algorithm)) + { + return new Pkcs12PbeParams(salt, iterationCount); + } + else if (IsPkcs5Scheme2(algorithm)) + { + return new Pbkdf2Params(salt, iterationCount); + } + else + { + return new PbeParameter(salt, iterationCount); + } + } + + public static ICipherParameters GenerateCipherParameters( + DerObjectIdentifier algorithmOid, + char[] password, + Asn1Encodable pbeParameters) + { + return GenerateCipherParameters(algorithmOid.Id, password, false, pbeParameters); + } + + public static ICipherParameters GenerateCipherParameters( + DerObjectIdentifier algorithmOid, + char[] password, + bool wrongPkcs12Zero, + Asn1Encodable pbeParameters) + { + return GenerateCipherParameters(algorithmOid.Id, password, wrongPkcs12Zero, pbeParameters); + } + + public static ICipherParameters GenerateCipherParameters( + AlgorithmIdentifier algID, + char[] password) + { + return GenerateCipherParameters(algID.Algorithm.Id, password, false, algID.Parameters); + } + + public static ICipherParameters GenerateCipherParameters( + AlgorithmIdentifier algID, + char[] password, + bool wrongPkcs12Zero) + { + return GenerateCipherParameters(algID.Algorithm.Id, password, wrongPkcs12Zero, algID.Parameters); + } + + public static ICipherParameters GenerateCipherParameters( + string algorithm, + char[] password, + Asn1Encodable pbeParameters) + { + return GenerateCipherParameters(algorithm, password, false, pbeParameters); + } + + public static ICipherParameters GenerateCipherParameters( + string algorithm, + char[] password, + bool wrongPkcs12Zero, + Asn1Encodable pbeParameters) + { + string mechanism = (string)algorithms[Platform.ToUpperInvariant(algorithm)]; + + byte[] keyBytes = null; + byte[] salt = null; + int iterationCount = 0; + + if (IsPkcs12(mechanism)) + { + Pkcs12PbeParams pbeParams = Pkcs12PbeParams.GetInstance(pbeParameters); + salt = pbeParams.GetIV(); + iterationCount = pbeParams.Iterations.IntValue; + keyBytes = PbeParametersGenerator.Pkcs12PasswordToBytes(password, wrongPkcs12Zero); + } + else if (IsPkcs5Scheme2(mechanism)) + { + // See below + } + else + { + PbeParameter pbeParams = PbeParameter.GetInstance(pbeParameters); + salt = pbeParams.GetSalt(); + iterationCount = pbeParams.IterationCount.IntValue; + keyBytes = PbeParametersGenerator.Pkcs5PasswordToBytes(password); + } + + ICipherParameters parameters = null; + + if (IsPkcs5Scheme2(mechanism)) + { + PbeS2Parameters s2p = PbeS2Parameters.GetInstance(pbeParameters.ToAsn1Object()); + AlgorithmIdentifier encScheme = s2p.EncryptionScheme; + DerObjectIdentifier encOid = encScheme.Algorithm; + Asn1Object encParams = encScheme.Parameters.ToAsn1Object(); + + // TODO What about s2p.KeyDerivationFunc.Algorithm? + Pbkdf2Params pbeParams = Pbkdf2Params.GetInstance(s2p.KeyDerivationFunc.Parameters.ToAsn1Object()); + + byte[] iv; + if (encOid.Equals(PkcsObjectIdentifiers.RC2Cbc)) // PKCS5.B.2.3 + { + RC2CbcParameter rc2Params = RC2CbcParameter.GetInstance(encParams); + iv = rc2Params.GetIV(); + } + else + { + iv = Asn1OctetString.GetInstance(encParams).GetOctets(); + } + + salt = pbeParams.GetSalt(); + iterationCount = pbeParams.IterationCount.IntValue; + keyBytes = PbeParametersGenerator.Pkcs5PasswordToBytes(password); + + int keyLength = pbeParams.KeyLength != null + ? pbeParams.KeyLength.IntValue * 8 + : GeneratorUtilities.GetDefaultKeySize(encOid); + + PbeParametersGenerator gen = MakePbeGenerator( + (string)algorithmType[mechanism], null, keyBytes, salt, iterationCount); + + parameters = gen.GenerateDerivedParameters(encOid.Id, keyLength); + + if (iv != null) + { + // FIXME? OpenSSL weirdness with IV of zeros (for ECB keys?) + if (Arrays.AreEqual(iv, new byte[iv.Length])) + { + //Console.Error.Write("***** IV all 0 (length " + iv.Length + ") *****"); + } + else + { + parameters = new ParametersWithIV(parameters, iv); + } + } + } + else if (Platform.StartsWith(mechanism, "PBEwithSHA-1")) + { + PbeParametersGenerator generator = MakePbeGenerator( + (string) algorithmType[mechanism], new Sha1Digest(), keyBytes, salt, iterationCount); + + if (mechanism.Equals("PBEwithSHA-1and128bitAES-CBC-BC")) + { + parameters = generator.GenerateDerivedParameters("AES", 128, 128); + } + else if (mechanism.Equals("PBEwithSHA-1and192bitAES-CBC-BC")) + { + parameters = generator.GenerateDerivedParameters("AES", 192, 128); + } + else if (mechanism.Equals("PBEwithSHA-1and256bitAES-CBC-BC")) + { + parameters = generator.GenerateDerivedParameters("AES", 256, 128); + } + else if (mechanism.Equals("PBEwithSHA-1and128bitRC4")) + { + parameters = generator.GenerateDerivedParameters("RC4", 128); + } + else if (mechanism.Equals("PBEwithSHA-1and40bitRC4")) + { + parameters = generator.GenerateDerivedParameters("RC4", 40); + } + else if (mechanism.Equals("PBEwithSHA-1and3-keyDESEDE-CBC")) + { + parameters = generator.GenerateDerivedParameters("DESEDE", 192, 64); + } + else if (mechanism.Equals("PBEwithSHA-1and2-keyDESEDE-CBC")) + { + parameters = generator.GenerateDerivedParameters("DESEDE", 128, 64); + } + else if (mechanism.Equals("PBEwithSHA-1and128bitRC2-CBC")) + { + parameters = generator.GenerateDerivedParameters("RC2", 128, 64); + } + else if (mechanism.Equals("PBEwithSHA-1and40bitRC2-CBC")) + { + parameters = generator.GenerateDerivedParameters("RC2", 40, 64); + } + else if (mechanism.Equals("PBEwithSHA-1andDES-CBC")) + { + parameters = generator.GenerateDerivedParameters("DES", 64, 64); + } + else if (mechanism.Equals("PBEwithSHA-1andRC2-CBC")) + { + parameters = generator.GenerateDerivedParameters("RC2", 64, 64); + } + } + else if (Platform.StartsWith(mechanism, "PBEwithSHA-256")) + { + PbeParametersGenerator generator = MakePbeGenerator( + (string) algorithmType[mechanism], new Sha256Digest(), keyBytes, salt, iterationCount); + + if (mechanism.Equals("PBEwithSHA-256and128bitAES-CBC-BC")) + { + parameters = generator.GenerateDerivedParameters("AES", 128, 128); + } + else if (mechanism.Equals("PBEwithSHA-256and192bitAES-CBC-BC")) + { + parameters = generator.GenerateDerivedParameters("AES", 192, 128); + } + else if (mechanism.Equals("PBEwithSHA-256and256bitAES-CBC-BC")) + { + parameters = generator.GenerateDerivedParameters("AES", 256, 128); + } + } + else if (Platform.StartsWith(mechanism, "PBEwithMD5")) + { + PbeParametersGenerator generator = MakePbeGenerator( + (string)algorithmType[mechanism], new MD5Digest(), keyBytes, salt, iterationCount); + + if (mechanism.Equals("PBEwithMD5andDES-CBC")) + { + parameters = generator.GenerateDerivedParameters("DES", 64, 64); + } + else if (mechanism.Equals("PBEwithMD5andRC2-CBC")) + { + parameters = generator.GenerateDerivedParameters("RC2", 64, 64); + } + else if (mechanism.Equals("PBEwithMD5and128bitAES-CBC-OpenSSL")) + { + parameters = generator.GenerateDerivedParameters("AES", 128, 128); + } + else if (mechanism.Equals("PBEwithMD5and192bitAES-CBC-OpenSSL")) + { + parameters = generator.GenerateDerivedParameters("AES", 192, 128); + } + else if (mechanism.Equals("PBEwithMD5and256bitAES-CBC-OpenSSL")) + { + parameters = generator.GenerateDerivedParameters("AES", 256, 128); + } + } + else if (Platform.StartsWith(mechanism, "PBEwithMD2")) + { + PbeParametersGenerator generator = MakePbeGenerator( + (string)algorithmType[mechanism], new MD2Digest(), keyBytes, salt, iterationCount); + if (mechanism.Equals("PBEwithMD2andDES-CBC")) + { + parameters = generator.GenerateDerivedParameters("DES", 64, 64); + } + else if (mechanism.Equals("PBEwithMD2andRC2-CBC")) + { + parameters = generator.GenerateDerivedParameters("RC2", 64, 64); + } + } + else if (Platform.StartsWith(mechanism, "PBEwithHmac")) + { + string digestName = mechanism.Substring("PBEwithHmac".Length); + IDigest digest = DigestUtilities.GetDigest(digestName); + + PbeParametersGenerator generator = MakePbeGenerator( + (string) algorithmType[mechanism], digest, keyBytes, salt, iterationCount); + + int bitLen = digest.GetDigestSize() * 8; + parameters = generator.GenerateDerivedMacParameters(bitLen); + } + + Array.Clear(keyBytes, 0, keyBytes.Length); + + return FixDesParity(mechanism, parameters); + } + + public static object CreateEngine( + DerObjectIdentifier algorithmOid) + { + return CreateEngine(algorithmOid.Id); + } + + public static object CreateEngine( + AlgorithmIdentifier algID) + { + string algorithm = algID.Algorithm.Id; + + if (IsPkcs5Scheme2(algorithm)) + { + PbeS2Parameters s2p = PbeS2Parameters.GetInstance(algID.Parameters.ToAsn1Object()); + AlgorithmIdentifier encScheme = s2p.EncryptionScheme; + return CipherUtilities.GetCipher(encScheme.Algorithm); + } + + return CreateEngine(algorithm); + } + + public static object CreateEngine( + string algorithm) + { + string mechanism = (string)algorithms[Platform.ToUpperInvariant(algorithm)]; + + if (Platform.StartsWith(mechanism, "PBEwithHmac")) + { + string digestName = mechanism.Substring("PBEwithHmac".Length); + + return MacUtilities.GetMac("HMAC/" + digestName); + } + + if (Platform.StartsWith(mechanism, "PBEwithMD2") + || Platform.StartsWith(mechanism, "PBEwithMD5") + || Platform.StartsWith(mechanism, "PBEwithSHA-1") + || Platform.StartsWith(mechanism, "PBEwithSHA-256")) + { + if (Platform.EndsWith(mechanism, "AES-CBC-BC") || Platform.EndsWith(mechanism, "AES-CBC-OPENSSL")) + { + return CipherUtilities.GetCipher("AES/CBC"); + } + + if (Platform.EndsWith(mechanism, "DES-CBC")) + { + return CipherUtilities.GetCipher("DES/CBC"); + } + + if (Platform.EndsWith(mechanism, "DESEDE-CBC")) + { + return CipherUtilities.GetCipher("DESEDE/CBC"); + } + + if (Platform.EndsWith(mechanism, "RC2-CBC")) + { + return CipherUtilities.GetCipher("RC2/CBC"); + } + + if (Platform.EndsWith(mechanism, "RC4")) + { + return CipherUtilities.GetCipher("RC4"); + } + } + + return null; + } + + public static string GetEncodingName( + DerObjectIdentifier oid) + { + return (string) algorithms[oid.Id]; + } + + private static ICipherParameters FixDesParity(string mechanism, ICipherParameters parameters) + { + if (!Platform.EndsWith(mechanism, "DES-CBC") && !Platform.EndsWith(mechanism, "DESEDE-CBC")) + { + return parameters; + } + + if (parameters is ParametersWithIV) + { + ParametersWithIV ivParams = (ParametersWithIV)parameters; + return new ParametersWithIV(FixDesParity(mechanism, ivParams.Parameters), ivParams.GetIV()); + } + + KeyParameter kParam = (KeyParameter)parameters; + byte[] keyBytes = kParam.GetKey(); + DesParameters.SetOddParity(keyBytes); + return new KeyParameter(keyBytes); + } + } +} diff --git a/bc-sharp-crypto/src/security/PrivateKeyFactory.cs b/bc-sharp-crypto/src/security/PrivateKeyFactory.cs new file mode 100644 index 0000000..8c2ecfd --- /dev/null +++ b/bc-sharp-crypto/src/security/PrivateKeyFactory.cs @@ -0,0 +1,222 @@ +using System; +using System.Collections; +using System.IO; +using System.Text; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.CryptoPro; +using Org.BouncyCastle.Asn1.Oiw; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.Sec; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Pkcs; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Security +{ + public sealed class PrivateKeyFactory + { + private PrivateKeyFactory() + { + } + + public static AsymmetricKeyParameter CreateKey( + byte[] privateKeyInfoData) + { + return CreateKey( + PrivateKeyInfo.GetInstance( + Asn1Object.FromByteArray(privateKeyInfoData))); + } + + public static AsymmetricKeyParameter CreateKey( + Stream inStr) + { + return CreateKey( + PrivateKeyInfo.GetInstance( + Asn1Object.FromStream(inStr))); + } + + public static AsymmetricKeyParameter CreateKey( + PrivateKeyInfo keyInfo) + { + AlgorithmIdentifier algID = keyInfo.PrivateKeyAlgorithm; + DerObjectIdentifier algOid = algID.Algorithm; + + // TODO See RSAUtil.isRsaOid in Java build + if (algOid.Equals(PkcsObjectIdentifiers.RsaEncryption) + || algOid.Equals(X509ObjectIdentifiers.IdEARsa) + || algOid.Equals(PkcsObjectIdentifiers.IdRsassaPss) + || algOid.Equals(PkcsObjectIdentifiers.IdRsaesOaep)) + { + RsaPrivateKeyStructure keyStructure = RsaPrivateKeyStructure.GetInstance(keyInfo.ParsePrivateKey()); + + return new RsaPrivateCrtKeyParameters( + keyStructure.Modulus, + keyStructure.PublicExponent, + keyStructure.PrivateExponent, + keyStructure.Prime1, + keyStructure.Prime2, + keyStructure.Exponent1, + keyStructure.Exponent2, + keyStructure.Coefficient); + } + // TODO? +// else if (algOid.Equals(X9ObjectIdentifiers.DHPublicNumber)) + else if (algOid.Equals(PkcsObjectIdentifiers.DhKeyAgreement)) + { + DHParameter para = new DHParameter( + Asn1Sequence.GetInstance(algID.Parameters.ToAsn1Object())); + DerInteger derX = (DerInteger)keyInfo.ParsePrivateKey(); + + BigInteger lVal = para.L; + int l = lVal == null ? 0 : lVal.IntValue; + DHParameters dhParams = new DHParameters(para.P, para.G, null, l); + + return new DHPrivateKeyParameters(derX.Value, dhParams, algOid); + } + else if (algOid.Equals(OiwObjectIdentifiers.ElGamalAlgorithm)) + { + ElGamalParameter para = new ElGamalParameter( + Asn1Sequence.GetInstance(algID.Parameters.ToAsn1Object())); + DerInteger derX = (DerInteger)keyInfo.ParsePrivateKey(); + + return new ElGamalPrivateKeyParameters( + derX.Value, + new ElGamalParameters(para.P, para.G)); + } + else if (algOid.Equals(X9ObjectIdentifiers.IdDsa)) + { + DerInteger derX = (DerInteger)keyInfo.ParsePrivateKey(); + Asn1Encodable ae = algID.Parameters; + + DsaParameters parameters = null; + if (ae != null) + { + DsaParameter para = DsaParameter.GetInstance(ae.ToAsn1Object()); + parameters = new DsaParameters(para.P, para.Q, para.G); + } + + return new DsaPrivateKeyParameters(derX.Value, parameters); + } + else if (algOid.Equals(X9ObjectIdentifiers.IdECPublicKey)) + { + X962Parameters para = new X962Parameters(algID.Parameters.ToAsn1Object()); + + X9ECParameters x9; + if (para.IsNamedCurve) + { + x9 = ECKeyPairGenerator.FindECCurveByOid((DerObjectIdentifier)para.Parameters); + } + else + { + x9 = new X9ECParameters((Asn1Sequence)para.Parameters); + } + + ECPrivateKeyStructure ec = ECPrivateKeyStructure.GetInstance(keyInfo.ParsePrivateKey()); + BigInteger d = ec.GetKey(); + + if (para.IsNamedCurve) + { + return new ECPrivateKeyParameters("EC", d, (DerObjectIdentifier)para.Parameters); + } + + ECDomainParameters dParams = new ECDomainParameters(x9.Curve, x9.G, x9.N, x9.H, x9.GetSeed()); + return new ECPrivateKeyParameters(d, dParams); + } + else if (algOid.Equals(CryptoProObjectIdentifiers.GostR3410x2001)) + { + Gost3410PublicKeyAlgParameters gostParams = new Gost3410PublicKeyAlgParameters( + Asn1Sequence.GetInstance(algID.Parameters.ToAsn1Object())); + + ECDomainParameters ecP = ECGost3410NamedCurves.GetByOid(gostParams.PublicKeyParamSet); + + if (ecP == null) + throw new ArgumentException("Unrecognized curve OID for GostR3410x2001 private key"); + + Asn1Object privKey = keyInfo.ParsePrivateKey(); + ECPrivateKeyStructure ec; + + if (privKey is DerInteger) + { + // TODO Do we need to pass any parameters here? + ec = new ECPrivateKeyStructure(ecP.N.BitLength, ((DerInteger)privKey).Value); + } + else + { + ec = ECPrivateKeyStructure.GetInstance(privKey); + } + + return new ECPrivateKeyParameters("ECGOST3410", ec.GetKey(), gostParams.PublicKeyParamSet); + } + else if (algOid.Equals(CryptoProObjectIdentifiers.GostR3410x94)) + { + Gost3410PublicKeyAlgParameters gostParams = new Gost3410PublicKeyAlgParameters( + Asn1Sequence.GetInstance(algID.Parameters.ToAsn1Object())); + + DerOctetString derX = (DerOctetString)keyInfo.ParsePrivateKey(); + BigInteger x = new BigInteger(1, Arrays.Reverse(derX.GetOctets())); + + return new Gost3410PrivateKeyParameters(x, gostParams.PublicKeyParamSet); + } + else + { + throw new SecurityUtilityException("algorithm identifier in key not recognised"); + } + } + + public static AsymmetricKeyParameter DecryptKey( + char[] passPhrase, + EncryptedPrivateKeyInfo encInfo) + { + return CreateKey(PrivateKeyInfoFactory.CreatePrivateKeyInfo(passPhrase, encInfo)); + } + + public static AsymmetricKeyParameter DecryptKey( + char[] passPhrase, + byte[] encryptedPrivateKeyInfoData) + { + return DecryptKey(passPhrase, Asn1Object.FromByteArray(encryptedPrivateKeyInfoData)); + } + + public static AsymmetricKeyParameter DecryptKey( + char[] passPhrase, + Stream encryptedPrivateKeyInfoStream) + { + return DecryptKey(passPhrase, Asn1Object.FromStream(encryptedPrivateKeyInfoStream)); + } + + private static AsymmetricKeyParameter DecryptKey( + char[] passPhrase, + Asn1Object asn1Object) + { + return DecryptKey(passPhrase, EncryptedPrivateKeyInfo.GetInstance(asn1Object)); + } + + public static byte[] EncryptKey( + DerObjectIdentifier algorithm, + char[] passPhrase, + byte[] salt, + int iterationCount, + AsymmetricKeyParameter key) + { + return EncryptedPrivateKeyInfoFactory.CreateEncryptedPrivateKeyInfo( + algorithm, passPhrase, salt, iterationCount, key).GetEncoded(); + } + + public static byte[] EncryptKey( + string algorithm, + char[] passPhrase, + byte[] salt, + int iterationCount, + AsymmetricKeyParameter key) + { + return EncryptedPrivateKeyInfoFactory.CreateEncryptedPrivateKeyInfo( + algorithm, passPhrase, salt, iterationCount, key).GetEncoded(); + } + } +} diff --git a/bc-sharp-crypto/src/security/PublicKeyFactory.cs b/bc-sharp-crypto/src/security/PublicKeyFactory.cs new file mode 100644 index 0000000..f1b28b7 --- /dev/null +++ b/bc-sharp-crypto/src/security/PublicKeyFactory.cs @@ -0,0 +1,253 @@ +using System; +using System.Collections; +using System.IO; +using System.Text; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.CryptoPro; +using Org.BouncyCastle.Asn1.Oiw; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.Sec; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Math.EC; + +namespace Org.BouncyCastle.Security +{ + public sealed class PublicKeyFactory + { + private PublicKeyFactory() + { + } + + public static AsymmetricKeyParameter CreateKey( + byte[] keyInfoData) + { + return CreateKey( + SubjectPublicKeyInfo.GetInstance( + Asn1Object.FromByteArray(keyInfoData))); + } + + public static AsymmetricKeyParameter CreateKey( + Stream inStr) + { + return CreateKey( + SubjectPublicKeyInfo.GetInstance( + Asn1Object.FromStream(inStr))); + } + + public static AsymmetricKeyParameter CreateKey( + SubjectPublicKeyInfo keyInfo) + { + AlgorithmIdentifier algID = keyInfo.AlgorithmID; + DerObjectIdentifier algOid = algID.Algorithm; + + // TODO See RSAUtil.isRsaOid in Java build + if (algOid.Equals(PkcsObjectIdentifiers.RsaEncryption) + || algOid.Equals(X509ObjectIdentifiers.IdEARsa) + || algOid.Equals(PkcsObjectIdentifiers.IdRsassaPss) + || algOid.Equals(PkcsObjectIdentifiers.IdRsaesOaep)) + { + RsaPublicKeyStructure pubKey = RsaPublicKeyStructure.GetInstance( + keyInfo.GetPublicKey()); + + return new RsaKeyParameters(false, pubKey.Modulus, pubKey.PublicExponent); + } + else if (algOid.Equals(X9ObjectIdentifiers.DHPublicNumber)) + { + Asn1Sequence seq = Asn1Sequence.GetInstance(algID.Parameters.ToAsn1Object()); + + DHPublicKey dhPublicKey = DHPublicKey.GetInstance(keyInfo.GetPublicKey()); + + BigInteger y = dhPublicKey.Y.Value; + + if (IsPkcsDHParam(seq)) + return ReadPkcsDHParam(algOid, y, seq); + + DHDomainParameters dhParams = DHDomainParameters.GetInstance(seq); + + BigInteger p = dhParams.P.Value; + BigInteger g = dhParams.G.Value; + BigInteger q = dhParams.Q.Value; + + BigInteger j = null; + if (dhParams.J != null) + { + j = dhParams.J.Value; + } + + DHValidationParameters validation = null; + DHValidationParms dhValidationParms = dhParams.ValidationParms; + if (dhValidationParms != null) + { + byte[] seed = dhValidationParms.Seed.GetBytes(); + BigInteger pgenCounter = dhValidationParms.PgenCounter.Value; + + // TODO Check pgenCounter size? + + validation = new DHValidationParameters(seed, pgenCounter.IntValue); + } + + return new DHPublicKeyParameters(y, new DHParameters(p, g, q, j, validation)); + } + else if (algOid.Equals(PkcsObjectIdentifiers.DhKeyAgreement)) + { + Asn1Sequence seq = Asn1Sequence.GetInstance(algID.Parameters.ToAsn1Object()); + + DerInteger derY = (DerInteger) keyInfo.GetPublicKey(); + + return ReadPkcsDHParam(algOid, derY.Value, seq); + } + else if (algOid.Equals(OiwObjectIdentifiers.ElGamalAlgorithm)) + { + ElGamalParameter para = new ElGamalParameter( + Asn1Sequence.GetInstance(algID.Parameters.ToAsn1Object())); + DerInteger derY = (DerInteger) keyInfo.GetPublicKey(); + + return new ElGamalPublicKeyParameters( + derY.Value, + new ElGamalParameters(para.P, para.G)); + } + else if (algOid.Equals(X9ObjectIdentifiers.IdDsa) + || algOid.Equals(OiwObjectIdentifiers.DsaWithSha1)) + { + DerInteger derY = (DerInteger) keyInfo.GetPublicKey(); + Asn1Encodable ae = algID.Parameters; + + DsaParameters parameters = null; + if (ae != null) + { + DsaParameter para = DsaParameter.GetInstance(ae.ToAsn1Object()); + parameters = new DsaParameters(para.P, para.Q, para.G); + } + + return new DsaPublicKeyParameters(derY.Value, parameters); + } + else if (algOid.Equals(X9ObjectIdentifiers.IdECPublicKey)) + { + X962Parameters para = new X962Parameters(algID.Parameters.ToAsn1Object()); + + X9ECParameters x9; + if (para.IsNamedCurve) + { + x9 = ECKeyPairGenerator.FindECCurveByOid((DerObjectIdentifier)para.Parameters); + } + else + { + x9 = new X9ECParameters((Asn1Sequence)para.Parameters); + } + + Asn1OctetString key = new DerOctetString(keyInfo.PublicKeyData.GetBytes()); + X9ECPoint derQ = new X9ECPoint(x9.Curve, key); + ECPoint q = derQ.Point; + + if (para.IsNamedCurve) + { + return new ECPublicKeyParameters("EC", q, (DerObjectIdentifier)para.Parameters); + } + + ECDomainParameters dParams = new ECDomainParameters(x9.Curve, x9.G, x9.N, x9.H, x9.GetSeed()); + return new ECPublicKeyParameters(q, dParams); + } + else if (algOid.Equals(CryptoProObjectIdentifiers.GostR3410x2001)) + { + Gost3410PublicKeyAlgParameters gostParams = new Gost3410PublicKeyAlgParameters( + (Asn1Sequence) algID.Parameters); + + Asn1OctetString key; + try + { + key = (Asn1OctetString) keyInfo.GetPublicKey(); + } + catch (IOException) + { + throw new ArgumentException("invalid info structure in GOST3410 public key"); + } + + byte[] keyEnc = key.GetOctets(); + byte[] x = new byte[32]; + byte[] y = new byte[32]; + + for (int i = 0; i != y.Length; i++) + { + x[i] = keyEnc[32 - 1 - i]; + } + + for (int i = 0; i != x.Length; i++) + { + y[i] = keyEnc[64 - 1 - i]; + } + + ECDomainParameters ecP = ECGost3410NamedCurves.GetByOid(gostParams.PublicKeyParamSet); + + if (ecP == null) + return null; + + ECPoint q = ecP.Curve.CreatePoint(new BigInteger(1, x), new BigInteger(1, y)); + + return new ECPublicKeyParameters("ECGOST3410", q, gostParams.PublicKeyParamSet); + } + else if (algOid.Equals(CryptoProObjectIdentifiers.GostR3410x94)) + { + Gost3410PublicKeyAlgParameters algParams = new Gost3410PublicKeyAlgParameters( + (Asn1Sequence) algID.Parameters); + + DerOctetString derY; + try + { + derY = (DerOctetString) keyInfo.GetPublicKey(); + } + catch (IOException) + { + throw new ArgumentException("invalid info structure in GOST3410 public key"); + } + + byte[] keyEnc = derY.GetOctets(); + byte[] keyBytes = new byte[keyEnc.Length]; + + for (int i = 0; i != keyEnc.Length; i++) + { + keyBytes[i] = keyEnc[keyEnc.Length - 1 - i]; // was little endian + } + + BigInteger y = new BigInteger(1, keyBytes); + + return new Gost3410PublicKeyParameters(y, algParams.PublicKeyParamSet); + } + else + { + throw new SecurityUtilityException("algorithm identifier in key not recognised: " + algOid); + } + } + + private static bool IsPkcsDHParam(Asn1Sequence seq) + { + if (seq.Count == 2) + return true; + + if (seq.Count > 3) + return false; + + DerInteger l = DerInteger.GetInstance(seq[2]); + DerInteger p = DerInteger.GetInstance(seq[0]); + + return l.Value.CompareTo(BigInteger.ValueOf(p.Value.BitLength)) <= 0; + } + + private static DHPublicKeyParameters ReadPkcsDHParam(DerObjectIdentifier algOid, + BigInteger y, Asn1Sequence seq) + { + DHParameter para = new DHParameter(seq); + + BigInteger lVal = para.L; + int l = lVal == null ? 0 : lVal.IntValue; + DHParameters dhParams = new DHParameters(para.P, para.G, null, l); + + return new DHPublicKeyParameters(y, dhParams, algOid); + } + } +} diff --git a/bc-sharp-crypto/src/security/SecureRandom.cs b/bc-sharp-crypto/src/security/SecureRandom.cs new file mode 100644 index 0000000..bd639a3 --- /dev/null +++ b/bc-sharp-crypto/src/security/SecureRandom.cs @@ -0,0 +1,262 @@ +using System; +using System.Threading; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Prng; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Security +{ + public class SecureRandom + : Random + { + private static long counter = Times.NanoTime(); + +#if NETCF_1_0 || PORTABLE + private static object counterLock = new object(); + private static long NextCounterValue() + { + lock (counterLock) + { + return ++counter; + } + } + + private static readonly SecureRandom[] master = { null }; + private static SecureRandom Master + { + get + { + lock (master) + { + if (master[0] == null) + { + SecureRandom sr = master[0] = GetInstance("SHA256PRNG", false); + + // Even though Ticks has at most 8 or 14 bits of entropy, there's no harm in adding it. + sr.SetSeed(DateTime.Now.Ticks); + + // 32 will be enough when ThreadedSeedGenerator is fixed. Until then, ThreadedSeedGenerator returns low + // entropy, and this is not sufficient to be secure. http://www.bouncycastle.org/csharpdevmailarchive/msg00814.html + sr.SetSeed(new ThreadedSeedGenerator().GenerateSeed(32, true)); + } + + return master[0]; + } + } + } +#else + private static long NextCounterValue() + { + return Interlocked.Increment(ref counter); + } + + private static readonly SecureRandom master = new SecureRandom(new CryptoApiRandomGenerator()); + private static SecureRandom Master + { + get { return master; } + } +#endif + + private static DigestRandomGenerator CreatePrng(string digestName, bool autoSeed) + { + IDigest digest = DigestUtilities.GetDigest(digestName); + if (digest == null) + return null; + DigestRandomGenerator prng = new DigestRandomGenerator(digest); + if (autoSeed) + { + prng.AddSeedMaterial(NextCounterValue()); + prng.AddSeedMaterial(GetNextBytes(Master, digest.GetDigestSize())); + } + return prng; + } + + public static byte[] GetNextBytes(SecureRandom secureRandom, int length) + { + byte[] result = new byte[length]; + secureRandom.NextBytes(result); + return result; + } + + /// + /// Create and auto-seed an instance based on the given algorithm. + /// + /// Equivalent to GetInstance(algorithm, true) + /// e.g. "SHA256PRNG" + public static SecureRandom GetInstance(string algorithm) + { + return GetInstance(algorithm, true); + } + + /// + /// Create an instance based on the given algorithm, with optional auto-seeding + /// + /// e.g. "SHA256PRNG" + /// If true, the instance will be auto-seeded. + public static SecureRandom GetInstance(string algorithm, bool autoSeed) + { + string upper = Platform.ToUpperInvariant(algorithm); + if (Platform.EndsWith(upper, "PRNG")) + { + string digestName = upper.Substring(0, upper.Length - "PRNG".Length); + DigestRandomGenerator prng = CreatePrng(digestName, autoSeed); + if (prng != null) + { + return new SecureRandom(prng); + } + } + + throw new ArgumentException("Unrecognised PRNG algorithm: " + algorithm, "algorithm"); + } + + [Obsolete("Call GenerateSeed() on a SecureRandom instance instead")] + public static byte[] GetSeed(int length) + { + return GetNextBytes(Master, length); + } + + protected readonly IRandomGenerator generator; + + public SecureRandom() + : this(CreatePrng("SHA256", true)) + { + } + + /// + /// To replicate existing predictable output, replace with GetInstance("SHA1PRNG", false), followed by SetSeed(seed) + /// + [Obsolete("Use GetInstance/SetSeed instead")] + public SecureRandom(byte[] seed) + : this(CreatePrng("SHA1", false)) + { + SetSeed(seed); + } + + /// Use the specified instance of IRandomGenerator as random source. + /// + /// This constructor performs no seeding of either the IRandomGenerator or the + /// constructed SecureRandom. It is the responsibility of the client to provide + /// proper seed material as necessary/appropriate for the given IRandomGenerator + /// implementation. + /// + /// The source to generate all random bytes from. + public SecureRandom(IRandomGenerator generator) + : base(0) + { + this.generator = generator; + } + + public virtual byte[] GenerateSeed(int length) + { + return GetNextBytes(Master, length); + } + + public virtual void SetSeed(byte[] seed) + { + generator.AddSeedMaterial(seed); + } + + public virtual void SetSeed(long seed) + { + generator.AddSeedMaterial(seed); + } + + public override int Next() + { + return NextInt() & int.MaxValue; + } + + public override int Next(int maxValue) + { + + if (maxValue < 2) + { + if (maxValue < 0) + throw new ArgumentOutOfRangeException("maxValue", "cannot be negative"); + + return 0; + } + + int bits; + + // Test whether maxValue is a power of 2 + if ((maxValue & (maxValue - 1)) == 0) + { + bits = NextInt() & int.MaxValue; + return (int)(((long)bits * maxValue) >> 31); + } + + int result; + do + { + bits = NextInt() & int.MaxValue; + result = bits % maxValue; + } + while (bits - result + (maxValue - 1) < 0); // Ignore results near overflow + + return result; + } + + public override int Next(int minValue, int maxValue) + { + if (maxValue <= minValue) + { + if (maxValue == minValue) + return minValue; + + throw new ArgumentException("maxValue cannot be less than minValue"); + } + + int diff = maxValue - minValue; + if (diff > 0) + return minValue + Next(diff); + + for (;;) + { + int i = NextInt(); + + if (i >= minValue && i < maxValue) + return i; + } + } + + public override void NextBytes(byte[] buf) + { + generator.NextBytes(buf); + } + + public virtual void NextBytes(byte[] buf, int off, int len) + { + generator.NextBytes(buf, off, len); + } + + private static readonly double DoubleScale = System.Math.Pow(2.0, 64.0); + + public override double NextDouble() + { + return Convert.ToDouble((ulong) NextLong()) / DoubleScale; + } + + public virtual int NextInt() + { + byte[] bytes = new byte[4]; + NextBytes(bytes); + + uint result = bytes[0]; + result <<= 8; + result |= bytes[1]; + result <<= 8; + result |= bytes[2]; + result <<= 8; + result |= bytes[3]; + return (int)result; + } + + public virtual long NextLong() + { + return ((long)(uint) NextInt() << 32) | (long)(uint) NextInt(); + } + } +} diff --git a/bc-sharp-crypto/src/security/SecurityUtilityException.cs b/bc-sharp-crypto/src/security/SecurityUtilityException.cs new file mode 100644 index 0000000..8a19530 --- /dev/null +++ b/bc-sharp-crypto/src/security/SecurityUtilityException.cs @@ -0,0 +1,36 @@ +using System; + +namespace Org.BouncyCastle.Security +{ +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT || PORTABLE) + [Serializable] +#endif + public class SecurityUtilityException + : Exception + { + /** + * base constructor. + */ + public SecurityUtilityException() + { + } + + /** + * create a SecurityUtilityException with the given message. + * + * @param message the message to be carried with the exception. + */ + public SecurityUtilityException( + string message) + : base(message) + { + } + + public SecurityUtilityException( + string message, + Exception exception) + : base(message, exception) + { + } + } +} diff --git a/bc-sharp-crypto/src/security/SignatureException.cs b/bc-sharp-crypto/src/security/SignatureException.cs new file mode 100644 index 0000000..3ad617d --- /dev/null +++ b/bc-sharp-crypto/src/security/SignatureException.cs @@ -0,0 +1,14 @@ +using System; + +namespace Org.BouncyCastle.Security +{ +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT || PORTABLE) + [Serializable] +#endif + public class SignatureException : GeneralSecurityException + { + public SignatureException() : base() { } + public SignatureException(string message) : base(message) { } + public SignatureException(string message, Exception exception) : base(message, exception) { } + } +} diff --git a/bc-sharp-crypto/src/security/SignerUtilities.cs b/bc-sharp-crypto/src/security/SignerUtilities.cs new file mode 100644 index 0000000..9a4915b --- /dev/null +++ b/bc-sharp-crypto/src/security/SignerUtilities.cs @@ -0,0 +1,566 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.CryptoPro; +using Org.BouncyCastle.Asn1.Nist; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.TeleTrust; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Signers; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Security +{ + /// + /// Signer Utility class contains methods that can not be specifically grouped into other classes. + /// + public sealed class SignerUtilities + { + private SignerUtilities() + { + } + + internal static readonly IDictionary algorithms = Platform.CreateHashtable(); + internal static readonly IDictionary oids = Platform.CreateHashtable(); + + static SignerUtilities() + { + algorithms["MD2WITHRSA"] = "MD2withRSA"; + algorithms["MD2WITHRSAENCRYPTION"] = "MD2withRSA"; + algorithms[PkcsObjectIdentifiers.MD2WithRsaEncryption.Id] = "MD2withRSA"; + + algorithms["MD4WITHRSA"] = "MD4withRSA"; + algorithms["MD4WITHRSAENCRYPTION"] = "MD4withRSA"; + algorithms[PkcsObjectIdentifiers.MD4WithRsaEncryption.Id] = "MD4withRSA"; + + algorithms["MD5WITHRSA"] = "MD5withRSA"; + algorithms["MD5WITHRSAENCRYPTION"] = "MD5withRSA"; + algorithms[PkcsObjectIdentifiers.MD5WithRsaEncryption.Id] = "MD5withRSA"; + + algorithms["SHA1WITHRSA"] = "SHA-1withRSA"; + algorithms["SHA1WITHRSAENCRYPTION"] = "SHA-1withRSA"; + algorithms[PkcsObjectIdentifiers.Sha1WithRsaEncryption.Id] = "SHA-1withRSA"; + algorithms["SHA-1WITHRSA"] = "SHA-1withRSA"; + + algorithms["SHA224WITHRSA"] = "SHA-224withRSA"; + algorithms["SHA224WITHRSAENCRYPTION"] = "SHA-224withRSA"; + algorithms[PkcsObjectIdentifiers.Sha224WithRsaEncryption.Id] = "SHA-224withRSA"; + algorithms["SHA-224WITHRSA"] = "SHA-224withRSA"; + + algorithms["SHA256WITHRSA"] = "SHA-256withRSA"; + algorithms["SHA256WITHRSAENCRYPTION"] = "SHA-256withRSA"; + algorithms[PkcsObjectIdentifiers.Sha256WithRsaEncryption.Id] = "SHA-256withRSA"; + algorithms["SHA-256WITHRSA"] = "SHA-256withRSA"; + + algorithms["SHA384WITHRSA"] = "SHA-384withRSA"; + algorithms["SHA384WITHRSAENCRYPTION"] = "SHA-384withRSA"; + algorithms[PkcsObjectIdentifiers.Sha384WithRsaEncryption.Id] = "SHA-384withRSA"; + algorithms["SHA-384WITHRSA"] = "SHA-384withRSA"; + + algorithms["SHA512WITHRSA"] = "SHA-512withRSA"; + algorithms["SHA512WITHRSAENCRYPTION"] = "SHA-512withRSA"; + algorithms[PkcsObjectIdentifiers.Sha512WithRsaEncryption.Id] = "SHA-512withRSA"; + algorithms["SHA-512WITHRSA"] = "SHA-512withRSA"; + + algorithms["PSSWITHRSA"] = "PSSwithRSA"; + algorithms["RSASSA-PSS"] = "PSSwithRSA"; + algorithms[PkcsObjectIdentifiers.IdRsassaPss.Id] = "PSSwithRSA"; + algorithms["RSAPSS"] = "PSSwithRSA"; + + algorithms["SHA1WITHRSAANDMGF1"] = "SHA-1withRSAandMGF1"; + algorithms["SHA-1WITHRSAANDMGF1"] = "SHA-1withRSAandMGF1"; + algorithms["SHA1WITHRSA/PSS"] = "SHA-1withRSAandMGF1"; + algorithms["SHA-1WITHRSA/PSS"] = "SHA-1withRSAandMGF1"; + + algorithms["SHA224WITHRSAANDMGF1"] = "SHA-224withRSAandMGF1"; + algorithms["SHA-224WITHRSAANDMGF1"] = "SHA-224withRSAandMGF1"; + algorithms["SHA224WITHRSA/PSS"] = "SHA-224withRSAandMGF1"; + algorithms["SHA-224WITHRSA/PSS"] = "SHA-224withRSAandMGF1"; + + algorithms["SHA256WITHRSAANDMGF1"] = "SHA-256withRSAandMGF1"; + algorithms["SHA-256WITHRSAANDMGF1"] = "SHA-256withRSAandMGF1"; + algorithms["SHA256WITHRSA/PSS"] = "SHA-256withRSAandMGF1"; + algorithms["SHA-256WITHRSA/PSS"] = "SHA-256withRSAandMGF1"; + + algorithms["SHA384WITHRSAANDMGF1"] = "SHA-384withRSAandMGF1"; + algorithms["SHA-384WITHRSAANDMGF1"] = "SHA-384withRSAandMGF1"; + algorithms["SHA384WITHRSA/PSS"] = "SHA-384withRSAandMGF1"; + algorithms["SHA-384WITHRSA/PSS"] = "SHA-384withRSAandMGF1"; + + algorithms["SHA512WITHRSAANDMGF1"] = "SHA-512withRSAandMGF1"; + algorithms["SHA-512WITHRSAANDMGF1"] = "SHA-512withRSAandMGF1"; + algorithms["SHA512WITHRSA/PSS"] = "SHA-512withRSAandMGF1"; + algorithms["SHA-512WITHRSA/PSS"] = "SHA-512withRSAandMGF1"; + + algorithms["RIPEMD128WITHRSA"] = "RIPEMD128withRSA"; + algorithms["RIPEMD128WITHRSAENCRYPTION"] = "RIPEMD128withRSA"; + algorithms[TeleTrusTObjectIdentifiers.RsaSignatureWithRipeMD128.Id] = "RIPEMD128withRSA"; + + algorithms["RIPEMD160WITHRSA"] = "RIPEMD160withRSA"; + algorithms["RIPEMD160WITHRSAENCRYPTION"] = "RIPEMD160withRSA"; + algorithms[TeleTrusTObjectIdentifiers.RsaSignatureWithRipeMD160.Id] = "RIPEMD160withRSA"; + + algorithms["RIPEMD256WITHRSA"] = "RIPEMD256withRSA"; + algorithms["RIPEMD256WITHRSAENCRYPTION"] = "RIPEMD256withRSA"; + algorithms[TeleTrusTObjectIdentifiers.RsaSignatureWithRipeMD256.Id] = "RIPEMD256withRSA"; + + algorithms["NONEWITHRSA"] = "RSA"; + algorithms["RSAWITHNONE"] = "RSA"; + algorithms["RAWRSA"] = "RSA"; + + algorithms["RAWRSAPSS"] = "RAWRSASSA-PSS"; + algorithms["NONEWITHRSAPSS"] = "RAWRSASSA-PSS"; + algorithms["NONEWITHRSASSA-PSS"] = "RAWRSASSA-PSS"; + + algorithms["NONEWITHDSA"] = "NONEwithDSA"; + algorithms["DSAWITHNONE"] = "NONEwithDSA"; + algorithms["RAWDSA"] = "NONEwithDSA"; + + algorithms["DSA"] = "SHA-1withDSA"; + algorithms["DSAWITHSHA1"] = "SHA-1withDSA"; + algorithms["DSAWITHSHA-1"] = "SHA-1withDSA"; + algorithms["SHA/DSA"] = "SHA-1withDSA"; + algorithms["SHA1/DSA"] = "SHA-1withDSA"; + algorithms["SHA-1/DSA"] = "SHA-1withDSA"; + algorithms["SHA1WITHDSA"] = "SHA-1withDSA"; + algorithms["SHA-1WITHDSA"] = "SHA-1withDSA"; + algorithms[X9ObjectIdentifiers.IdDsaWithSha1.Id] = "SHA-1withDSA"; + + algorithms["DSAWITHSHA224"] = "SHA-224withDSA"; + algorithms["DSAWITHSHA-224"] = "SHA-224withDSA"; + algorithms["SHA224/DSA"] = "SHA-224withDSA"; + algorithms["SHA-224/DSA"] = "SHA-224withDSA"; + algorithms["SHA224WITHDSA"] = "SHA-224withDSA"; + algorithms["SHA-224WITHDSA"] = "SHA-224withDSA"; + algorithms[NistObjectIdentifiers.DsaWithSha224.Id] = "SHA-224withDSA"; + + algorithms["DSAWITHSHA256"] = "SHA-256withDSA"; + algorithms["DSAWITHSHA-256"] = "SHA-256withDSA"; + algorithms["SHA256/DSA"] = "SHA-256withDSA"; + algorithms["SHA-256/DSA"] = "SHA-256withDSA"; + algorithms["SHA256WITHDSA"] = "SHA-256withDSA"; + algorithms["SHA-256WITHDSA"] = "SHA-256withDSA"; + algorithms[NistObjectIdentifiers.DsaWithSha256.Id] = "SHA-256withDSA"; + + algorithms["DSAWITHSHA384"] = "SHA-384withDSA"; + algorithms["DSAWITHSHA-384"] = "SHA-384withDSA"; + algorithms["SHA384/DSA"] = "SHA-384withDSA"; + algorithms["SHA-384/DSA"] = "SHA-384withDSA"; + algorithms["SHA384WITHDSA"] = "SHA-384withDSA"; + algorithms["SHA-384WITHDSA"] = "SHA-384withDSA"; + algorithms[NistObjectIdentifiers.DsaWithSha384.Id] = "SHA-384withDSA"; + + algorithms["DSAWITHSHA512"] = "SHA-512withDSA"; + algorithms["DSAWITHSHA-512"] = "SHA-512withDSA"; + algorithms["SHA512/DSA"] = "SHA-512withDSA"; + algorithms["SHA-512/DSA"] = "SHA-512withDSA"; + algorithms["SHA512WITHDSA"] = "SHA-512withDSA"; + algorithms["SHA-512WITHDSA"] = "SHA-512withDSA"; + algorithms[NistObjectIdentifiers.DsaWithSha512.Id] = "SHA-512withDSA"; + + algorithms["NONEWITHECDSA"] = "NONEwithECDSA"; + algorithms["ECDSAWITHNONE"] = "NONEwithECDSA"; + + algorithms["ECDSA"] = "SHA-1withECDSA"; + algorithms["SHA1/ECDSA"] = "SHA-1withECDSA"; + algorithms["SHA-1/ECDSA"] = "SHA-1withECDSA"; + algorithms["ECDSAWITHSHA1"] = "SHA-1withECDSA"; + algorithms["ECDSAWITHSHA-1"] = "SHA-1withECDSA"; + algorithms["SHA1WITHECDSA"] = "SHA-1withECDSA"; + algorithms["SHA-1WITHECDSA"] = "SHA-1withECDSA"; + algorithms[X9ObjectIdentifiers.ECDsaWithSha1.Id] = "SHA-1withECDSA"; + algorithms[TeleTrusTObjectIdentifiers.ECSignWithSha1.Id] = "SHA-1withECDSA"; + + algorithms["SHA224/ECDSA"] = "SHA-224withECDSA"; + algorithms["SHA-224/ECDSA"] = "SHA-224withECDSA"; + algorithms["ECDSAWITHSHA224"] = "SHA-224withECDSA"; + algorithms["ECDSAWITHSHA-224"] = "SHA-224withECDSA"; + algorithms["SHA224WITHECDSA"] = "SHA-224withECDSA"; + algorithms["SHA-224WITHECDSA"] = "SHA-224withECDSA"; + algorithms[X9ObjectIdentifiers.ECDsaWithSha224.Id] = "SHA-224withECDSA"; + + algorithms["SHA256/ECDSA"] = "SHA-256withECDSA"; + algorithms["SHA-256/ECDSA"] = "SHA-256withECDSA"; + algorithms["ECDSAWITHSHA256"] = "SHA-256withECDSA"; + algorithms["ECDSAWITHSHA-256"] = "SHA-256withECDSA"; + algorithms["SHA256WITHECDSA"] = "SHA-256withECDSA"; + algorithms["SHA-256WITHECDSA"] = "SHA-256withECDSA"; + algorithms[X9ObjectIdentifiers.ECDsaWithSha256.Id] = "SHA-256withECDSA"; + + algorithms["SHA384/ECDSA"] = "SHA-384withECDSA"; + algorithms["SHA-384/ECDSA"] = "SHA-384withECDSA"; + algorithms["ECDSAWITHSHA384"] = "SHA-384withECDSA"; + algorithms["ECDSAWITHSHA-384"] = "SHA-384withECDSA"; + algorithms["SHA384WITHECDSA"] = "SHA-384withECDSA"; + algorithms["SHA-384WITHECDSA"] = "SHA-384withECDSA"; + algorithms[X9ObjectIdentifiers.ECDsaWithSha384.Id] = "SHA-384withECDSA"; + + algorithms["SHA512/ECDSA"] = "SHA-512withECDSA"; + algorithms["SHA-512/ECDSA"] = "SHA-512withECDSA"; + algorithms["ECDSAWITHSHA512"] = "SHA-512withECDSA"; + algorithms["ECDSAWITHSHA-512"] = "SHA-512withECDSA"; + algorithms["SHA512WITHECDSA"] = "SHA-512withECDSA"; + algorithms["SHA-512WITHECDSA"] = "SHA-512withECDSA"; + algorithms[X9ObjectIdentifiers.ECDsaWithSha512.Id] = "SHA-512withECDSA"; + + algorithms["RIPEMD160/ECDSA"] = "RIPEMD160withECDSA"; + algorithms["ECDSAWITHRIPEMD160"] = "RIPEMD160withECDSA"; + algorithms["RIPEMD160WITHECDSA"] = "RIPEMD160withECDSA"; + algorithms[TeleTrusTObjectIdentifiers.ECSignWithRipeMD160.Id] = "RIPEMD160withECDSA"; + + algorithms["GOST-3410"] = "GOST3410"; + algorithms["GOST-3410-94"] = "GOST3410"; + algorithms["GOST3411WITHGOST3410"] = "GOST3410"; + algorithms[CryptoProObjectIdentifiers.GostR3411x94WithGostR3410x94.Id] = "GOST3410"; + + algorithms["ECGOST-3410"] = "ECGOST3410"; + algorithms["ECGOST-3410-2001"] = "ECGOST3410"; + algorithms["GOST3411WITHECGOST3410"] = "ECGOST3410"; + algorithms[CryptoProObjectIdentifiers.GostR3411x94WithGostR3410x2001.Id] = "ECGOST3410"; + + + + oids["MD2withRSA"] = PkcsObjectIdentifiers.MD2WithRsaEncryption; + oids["MD4withRSA"] = PkcsObjectIdentifiers.MD4WithRsaEncryption; + oids["MD5withRSA"] = PkcsObjectIdentifiers.MD5WithRsaEncryption; + + oids["SHA-1withRSA"] = PkcsObjectIdentifiers.Sha1WithRsaEncryption; + oids["SHA-224withRSA"] = PkcsObjectIdentifiers.Sha224WithRsaEncryption; + oids["SHA-256withRSA"] = PkcsObjectIdentifiers.Sha256WithRsaEncryption; + oids["SHA-384withRSA"] = PkcsObjectIdentifiers.Sha384WithRsaEncryption; + oids["SHA-512withRSA"] = PkcsObjectIdentifiers.Sha512WithRsaEncryption; + + oids["PSSwithRSA"] = PkcsObjectIdentifiers.IdRsassaPss; + oids["SHA-1withRSAandMGF1"] = PkcsObjectIdentifiers.IdRsassaPss; + oids["SHA-224withRSAandMGF1"] = PkcsObjectIdentifiers.IdRsassaPss; + oids["SHA-256withRSAandMGF1"] = PkcsObjectIdentifiers.IdRsassaPss; + oids["SHA-384withRSAandMGF1"] = PkcsObjectIdentifiers.IdRsassaPss; + oids["SHA-512withRSAandMGF1"] = PkcsObjectIdentifiers.IdRsassaPss; + + oids["RIPEMD128withRSA"] = TeleTrusTObjectIdentifiers.RsaSignatureWithRipeMD128; + oids["RIPEMD160withRSA"] = TeleTrusTObjectIdentifiers.RsaSignatureWithRipeMD160; + oids["RIPEMD256withRSA"] = TeleTrusTObjectIdentifiers.RsaSignatureWithRipeMD256; + + oids["SHA-1withDSA"] = X9ObjectIdentifiers.IdDsaWithSha1; + + oids["SHA-1withECDSA"] = X9ObjectIdentifiers.ECDsaWithSha1; + oids["SHA-224withECDSA"] = X9ObjectIdentifiers.ECDsaWithSha224; + oids["SHA-256withECDSA"] = X9ObjectIdentifiers.ECDsaWithSha256; + oids["SHA-384withECDSA"] = X9ObjectIdentifiers.ECDsaWithSha384; + oids["SHA-512withECDSA"] = X9ObjectIdentifiers.ECDsaWithSha512; + + oids["GOST3410"] = CryptoProObjectIdentifiers.GostR3411x94WithGostR3410x94; + oids["ECGOST3410"] = CryptoProObjectIdentifiers.GostR3411x94WithGostR3410x2001; + } + + /// + /// Returns an ObjectIdentifier for a given encoding. + /// + /// A string representation of the encoding. + /// A DerObjectIdentifier, null if the OID is not available. + // TODO Don't really want to support this + public static DerObjectIdentifier GetObjectIdentifier( + string mechanism) + { + if (mechanism == null) + throw new ArgumentNullException("mechanism"); + + mechanism = Platform.ToUpperInvariant(mechanism); + string aliased = (string) algorithms[mechanism]; + + if (aliased != null) + mechanism = aliased; + + return (DerObjectIdentifier) oids[mechanism]; + } + + public static ICollection Algorithms + { + get { return oids.Keys; } + } + + public static Asn1Encodable GetDefaultX509Parameters( + DerObjectIdentifier id) + { + return GetDefaultX509Parameters(id.Id); + } + + public static Asn1Encodable GetDefaultX509Parameters( + string algorithm) + { + if (algorithm == null) + throw new ArgumentNullException("algorithm"); + + algorithm = Platform.ToUpperInvariant(algorithm); + + string mechanism = (string) algorithms[algorithm]; + + if (mechanism == null) + mechanism = algorithm; + + if (mechanism == "PSSwithRSA") + { + // TODO The Sha1Digest here is a default. In JCE version, the actual digest + // to be used can be overridden by subsequent parameter settings. + return GetPssX509Parameters("SHA-1"); + } + + if (Platform.EndsWith(mechanism, "withRSAandMGF1")) + { + string digestName = mechanism.Substring(0, mechanism.Length - "withRSAandMGF1".Length); + return GetPssX509Parameters(digestName); + } + + return DerNull.Instance; + } + + private static Asn1Encodable GetPssX509Parameters( + string digestName) + { + AlgorithmIdentifier hashAlgorithm = new AlgorithmIdentifier( + DigestUtilities.GetObjectIdentifier(digestName), DerNull.Instance); + + // TODO Is it possible for the MGF hash alg to be different from the PSS one? + AlgorithmIdentifier maskGenAlgorithm = new AlgorithmIdentifier( + PkcsObjectIdentifiers.IdMgf1, hashAlgorithm); + + int saltLen = DigestUtilities.GetDigest(digestName).GetDigestSize(); + return new RsassaPssParameters(hashAlgorithm, maskGenAlgorithm, + new DerInteger(saltLen), new DerInteger(1)); + } + + public static ISigner GetSigner( + DerObjectIdentifier id) + { + return GetSigner(id.Id); + } + + public static ISigner GetSigner( + string algorithm) + { + if (algorithm == null) + throw new ArgumentNullException("algorithm"); + + algorithm = Platform.ToUpperInvariant(algorithm); + + string mechanism = (string) algorithms[algorithm]; + + if (mechanism == null) + mechanism = algorithm; + + if (mechanism.Equals("RSA")) + { + return (new RsaDigestSigner(new NullDigest(), (AlgorithmIdentifier)null)); + } + if (mechanism.Equals("MD2withRSA")) + { + return (new RsaDigestSigner(new MD2Digest())); + } + if (mechanism.Equals("MD4withRSA")) + { + return (new RsaDigestSigner(new MD4Digest())); + } + if (mechanism.Equals("MD5withRSA")) + { + return (new RsaDigestSigner(new MD5Digest())); + } + if (mechanism.Equals("SHA-1withRSA")) + { + return (new RsaDigestSigner(new Sha1Digest())); + } + if (mechanism.Equals("SHA-224withRSA")) + { + return (new RsaDigestSigner(new Sha224Digest())); + } + if (mechanism.Equals("SHA-256withRSA")) + { + return (new RsaDigestSigner(new Sha256Digest())); + } + if (mechanism.Equals("SHA-384withRSA")) + { + return (new RsaDigestSigner(new Sha384Digest())); + } + if (mechanism.Equals("SHA-512withRSA")) + { + return (new RsaDigestSigner(new Sha512Digest())); + } + if (mechanism.Equals("RIPEMD128withRSA")) + { + return (new RsaDigestSigner(new RipeMD128Digest())); + } + if (mechanism.Equals("RIPEMD160withRSA")) + { + return (new RsaDigestSigner(new RipeMD160Digest())); + } + if (mechanism.Equals("RIPEMD256withRSA")) + { + return (new RsaDigestSigner(new RipeMD256Digest())); + } + + if (mechanism.Equals("RAWRSASSA-PSS")) + { + // TODO Add support for other parameter settings + return PssSigner.CreateRawSigner(new RsaBlindedEngine(), new Sha1Digest()); + } + if (mechanism.Equals("PSSwithRSA")) + { + // TODO The Sha1Digest here is a default. In JCE version, the actual digest + // to be used can be overridden by subsequent parameter settings. + return (new PssSigner(new RsaBlindedEngine(), new Sha1Digest())); + } + if (mechanism.Equals("SHA-1withRSAandMGF1")) + { + return (new PssSigner(new RsaBlindedEngine(), new Sha1Digest())); + } + if (mechanism.Equals("SHA-224withRSAandMGF1")) + { + return (new PssSigner(new RsaBlindedEngine(), new Sha224Digest())); + } + if (mechanism.Equals("SHA-256withRSAandMGF1")) + { + return (new PssSigner(new RsaBlindedEngine(), new Sha256Digest())); + } + if (mechanism.Equals("SHA-384withRSAandMGF1")) + { + return (new PssSigner(new RsaBlindedEngine(), new Sha384Digest())); + } + if (mechanism.Equals("SHA-512withRSAandMGF1")) + { + return (new PssSigner(new RsaBlindedEngine(), new Sha512Digest())); + } + + if (mechanism.Equals("NONEwithDSA")) + { + return (new DsaDigestSigner(new DsaSigner(), new NullDigest())); + } + if (mechanism.Equals("SHA-1withDSA")) + { + return (new DsaDigestSigner(new DsaSigner(), new Sha1Digest())); + } + if (mechanism.Equals("SHA-224withDSA")) + { + return (new DsaDigestSigner(new DsaSigner(), new Sha224Digest())); + } + if (mechanism.Equals("SHA-256withDSA")) + { + return (new DsaDigestSigner(new DsaSigner(), new Sha256Digest())); + } + if (mechanism.Equals("SHA-384withDSA")) + { + return (new DsaDigestSigner(new DsaSigner(), new Sha384Digest())); + } + if (mechanism.Equals("SHA-512withDSA")) + { + return (new DsaDigestSigner(new DsaSigner(), new Sha512Digest())); + } + + if (mechanism.Equals("NONEwithECDSA")) + { + return (new DsaDigestSigner(new ECDsaSigner(), new NullDigest())); + } + if (mechanism.Equals("SHA-1withECDSA")) + { + return (new DsaDigestSigner(new ECDsaSigner(), new Sha1Digest())); + } + if (mechanism.Equals("SHA-224withECDSA")) + { + return (new DsaDigestSigner(new ECDsaSigner(), new Sha224Digest())); + } + if (mechanism.Equals("SHA-256withECDSA")) + { + return (new DsaDigestSigner(new ECDsaSigner(), new Sha256Digest())); + } + if (mechanism.Equals("SHA-384withECDSA")) + { + return (new DsaDigestSigner(new ECDsaSigner(), new Sha384Digest())); + } + if (mechanism.Equals("SHA-512withECDSA")) + { + return (new DsaDigestSigner(new ECDsaSigner(), new Sha512Digest())); + } + + if (mechanism.Equals("RIPEMD160withECDSA")) + { + return (new DsaDigestSigner(new ECDsaSigner(), new RipeMD160Digest())); + } + + if (mechanism.Equals("SHA1WITHECNR")) + { + return (new DsaDigestSigner(new ECNRSigner(), new Sha1Digest())); + } + if (mechanism.Equals("SHA224WITHECNR")) + { + return (new DsaDigestSigner(new ECNRSigner(), new Sha224Digest())); + } + if (mechanism.Equals("SHA256WITHECNR")) + { + return (new DsaDigestSigner(new ECNRSigner(), new Sha256Digest())); + } + if (mechanism.Equals("SHA384WITHECNR")) + { + return (new DsaDigestSigner(new ECNRSigner(), new Sha384Digest())); + } + if (mechanism.Equals("SHA512WITHECNR")) + { + return (new DsaDigestSigner(new ECNRSigner(), new Sha512Digest())); + } + + if (mechanism.Equals("GOST3410")) + { + return new Gost3410DigestSigner(new Gost3410Signer(), new Gost3411Digest()); + } + if (mechanism.Equals("ECGOST3410")) + { + return new Gost3410DigestSigner(new ECGost3410Signer(), new Gost3411Digest()); + } + + if (mechanism.Equals("SHA1WITHRSA/ISO9796-2")) + { + return new Iso9796d2Signer(new RsaBlindedEngine(), new Sha1Digest(), true); + } + if (mechanism.Equals("MD5WITHRSA/ISO9796-2")) + { + return new Iso9796d2Signer(new RsaBlindedEngine(), new MD5Digest(), true); + } + if (mechanism.Equals("RIPEMD160WITHRSA/ISO9796-2")) + { + return new Iso9796d2Signer(new RsaBlindedEngine(), new RipeMD160Digest(), true); + } + + if (Platform.EndsWith(mechanism, "/X9.31")) + { + string x931 = mechanism.Substring(0, mechanism.Length - "/X9.31".Length); + int withPos = Platform.IndexOf(x931, "WITH"); + if (withPos > 0) + { + int endPos = withPos + "WITH".Length; + + string digestName = x931.Substring(0, withPos); + IDigest digest = DigestUtilities.GetDigest(digestName); + + string cipherName = x931.Substring(endPos, x931.Length - endPos); + if (cipherName.Equals("RSA")) + { + IAsymmetricBlockCipher cipher = new RsaBlindedEngine(); + return new X931Signer(cipher, digest); + } + } + } + + throw new SecurityUtilityException("Signer " + algorithm + " not recognised."); + } + + public static string GetEncodingName( + DerObjectIdentifier oid) + { + return (string) algorithms[oid.Id]; + } + } +} diff --git a/bc-sharp-crypto/src/security/WrapperUtilities.cs b/bc-sharp-crypto/src/security/WrapperUtilities.cs new file mode 100644 index 0000000..c576320 --- /dev/null +++ b/bc-sharp-crypto/src/security/WrapperUtilities.cs @@ -0,0 +1,153 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Kisa; +using Org.BouncyCastle.Asn1.Nist; +using Org.BouncyCastle.Asn1.Ntt; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Security +{ + /// + /// Utility class for creating IWrapper objects from their names/Oids + /// + public sealed class WrapperUtilities + { + private enum WrapAlgorithm { AESWRAP, CAMELLIAWRAP, DESEDEWRAP, RC2WRAP, SEEDWRAP, + DESEDERFC3211WRAP, AESRFC3211WRAP, CAMELLIARFC3211WRAP }; + + private WrapperUtilities() + { + } + + private static readonly IDictionary algorithms = Platform.CreateHashtable(); + //private static readonly IDictionary oids = Platform.CreateHashtable(); + + static WrapperUtilities() + { + // Signal to obfuscation tools not to change enum constants + ((WrapAlgorithm)Enums.GetArbitraryValue(typeof(WrapAlgorithm))).ToString(); + + algorithms[NistObjectIdentifiers.IdAes128Wrap.Id] = "AESWRAP"; + algorithms[NistObjectIdentifiers.IdAes192Wrap.Id] = "AESWRAP"; + algorithms[NistObjectIdentifiers.IdAes256Wrap.Id] = "AESWRAP"; + + algorithms[NttObjectIdentifiers.IdCamellia128Wrap.Id] = "CAMELLIAWRAP"; + algorithms[NttObjectIdentifiers.IdCamellia192Wrap.Id] = "CAMELLIAWRAP"; + algorithms[NttObjectIdentifiers.IdCamellia256Wrap.Id] = "CAMELLIAWRAP"; + + algorithms[PkcsObjectIdentifiers.IdAlgCms3DesWrap.Id] = "DESEDEWRAP"; + algorithms["TDEAWRAP"] = "DESEDEWRAP"; + + algorithms[PkcsObjectIdentifiers.IdAlgCmsRC2Wrap.Id] = "RC2WRAP"; + + algorithms[KisaObjectIdentifiers.IdNpkiAppCmsSeedWrap.Id] = "SEEDWRAP"; + } + + public static IWrapper GetWrapper( + DerObjectIdentifier oid) + { + return GetWrapper(oid.Id); + } + + public static IWrapper GetWrapper( + string algorithm) + { + string upper = Platform.ToUpperInvariant(algorithm); + string mechanism = (string)algorithms[upper]; + + if (mechanism == null) + { + mechanism = upper; + } + + try + { + WrapAlgorithm wrapAlgorithm = (WrapAlgorithm)Enums.GetEnumValue( + typeof(WrapAlgorithm), mechanism); + + switch (wrapAlgorithm) + { + case WrapAlgorithm.AESWRAP: return new AesWrapEngine(); + case WrapAlgorithm.CAMELLIAWRAP: return new CamelliaWrapEngine(); + case WrapAlgorithm.DESEDEWRAP: return new DesEdeWrapEngine(); + case WrapAlgorithm.RC2WRAP: return new RC2WrapEngine(); + case WrapAlgorithm.SEEDWRAP: return new SeedWrapEngine(); + case WrapAlgorithm.DESEDERFC3211WRAP: return new Rfc3211WrapEngine(new DesEdeEngine()); + case WrapAlgorithm.AESRFC3211WRAP: return new Rfc3211WrapEngine(new AesEngine()); + case WrapAlgorithm.CAMELLIARFC3211WRAP: return new Rfc3211WrapEngine(new CamelliaEngine()); + } + } + catch (ArgumentException) + { + } + + // Create an IBufferedCipher and use it as IWrapper (via BufferedCipherWrapper) + IBufferedCipher blockCipher = CipherUtilities.GetCipher(algorithm); + + if (blockCipher != null) + return new BufferedCipherWrapper(blockCipher); + + throw new SecurityUtilityException("Wrapper " + algorithm + " not recognised."); + } + + public static string GetAlgorithmName( + DerObjectIdentifier oid) + { + return (string) algorithms[oid.Id]; + } + + private class BufferedCipherWrapper + : IWrapper + { + private readonly IBufferedCipher cipher; + private bool forWrapping; + + public BufferedCipherWrapper( + IBufferedCipher cipher) + { + this.cipher = cipher; + } + + public string AlgorithmName + { + get { return cipher.AlgorithmName; } + } + + public void Init( + bool forWrapping, + ICipherParameters parameters) + { + this.forWrapping = forWrapping; + + cipher.Init(forWrapping, parameters); + } + + public byte[] Wrap( + byte[] input, + int inOff, + int length) + { + if (!forWrapping) + throw new InvalidOperationException("Not initialised for wrapping"); + + return cipher.DoFinal(input, inOff, length); + } + + public byte[] Unwrap( + byte[] input, + int inOff, + int length) + { + if (forWrapping) + throw new InvalidOperationException("Not initialised for unwrapping"); + + return cipher.DoFinal(input, inOff, length); + } + } + } +} diff --git a/bc-sharp-crypto/src/security/cert/CertificateEncodingException.cs b/bc-sharp-crypto/src/security/cert/CertificateEncodingException.cs new file mode 100644 index 0000000..ab9024f --- /dev/null +++ b/bc-sharp-crypto/src/security/cert/CertificateEncodingException.cs @@ -0,0 +1,14 @@ +using System; + +namespace Org.BouncyCastle.Security.Certificates +{ +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT || PORTABLE) + [Serializable] +#endif + public class CertificateEncodingException : CertificateException + { + public CertificateEncodingException() : base() { } + public CertificateEncodingException(string msg) : base(msg) { } + public CertificateEncodingException(string msg, Exception e) : base(msg, e) { } + } +} diff --git a/bc-sharp-crypto/src/security/cert/CertificateException.cs b/bc-sharp-crypto/src/security/cert/CertificateException.cs new file mode 100644 index 0000000..4bbaccf --- /dev/null +++ b/bc-sharp-crypto/src/security/cert/CertificateException.cs @@ -0,0 +1,14 @@ +using System; + +namespace Org.BouncyCastle.Security.Certificates +{ +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT || PORTABLE) + [Serializable] +#endif + public class CertificateException : GeneralSecurityException + { + public CertificateException() : base() { } + public CertificateException(string message) : base(message) { } + public CertificateException(string message, Exception exception) : base(message, exception) { } + } +} diff --git a/bc-sharp-crypto/src/security/cert/CertificateExpiredException.cs b/bc-sharp-crypto/src/security/cert/CertificateExpiredException.cs new file mode 100644 index 0000000..864fb85 --- /dev/null +++ b/bc-sharp-crypto/src/security/cert/CertificateExpiredException.cs @@ -0,0 +1,14 @@ +using System; + +namespace Org.BouncyCastle.Security.Certificates +{ +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT || PORTABLE) + [Serializable] +#endif + public class CertificateExpiredException : CertificateException + { + public CertificateExpiredException() : base() { } + public CertificateExpiredException(string message) : base(message) { } + public CertificateExpiredException(string message, Exception exception) : base(message, exception) { } + } +} diff --git a/bc-sharp-crypto/src/security/cert/CertificateNotYetValidException.cs b/bc-sharp-crypto/src/security/cert/CertificateNotYetValidException.cs new file mode 100644 index 0000000..02112be --- /dev/null +++ b/bc-sharp-crypto/src/security/cert/CertificateNotYetValidException.cs @@ -0,0 +1,14 @@ +using System; + +namespace Org.BouncyCastle.Security.Certificates +{ +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT || PORTABLE) + [Serializable] +#endif + public class CertificateNotYetValidException : CertificateException + { + public CertificateNotYetValidException() : base() { } + public CertificateNotYetValidException(string message) : base(message) { } + public CertificateNotYetValidException(string message, Exception exception) : base(message, exception) { } + } +} diff --git a/bc-sharp-crypto/src/security/cert/CertificateParsingException.cs b/bc-sharp-crypto/src/security/cert/CertificateParsingException.cs new file mode 100644 index 0000000..ae909ca --- /dev/null +++ b/bc-sharp-crypto/src/security/cert/CertificateParsingException.cs @@ -0,0 +1,14 @@ +using System; + +namespace Org.BouncyCastle.Security.Certificates +{ +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT || PORTABLE) + [Serializable] +#endif + public class CertificateParsingException : CertificateException + { + public CertificateParsingException() : base() { } + public CertificateParsingException(string message) : base(message) { } + public CertificateParsingException(string message, Exception exception) : base(message, exception) { } + } +} diff --git a/bc-sharp-crypto/src/security/cert/CrlException.cs b/bc-sharp-crypto/src/security/cert/CrlException.cs new file mode 100644 index 0000000..fe9807e --- /dev/null +++ b/bc-sharp-crypto/src/security/cert/CrlException.cs @@ -0,0 +1,14 @@ +using System; + +namespace Org.BouncyCastle.Security.Certificates +{ +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT || PORTABLE) + [Serializable] +#endif + public class CrlException : GeneralSecurityException + { + public CrlException() : base() { } + public CrlException(string msg) : base(msg) {} + public CrlException(string msg, Exception e) : base(msg, e) {} + } +} diff --git a/bc-sharp-crypto/src/tsp/GenTimeAccuracy.cs b/bc-sharp-crypto/src/tsp/GenTimeAccuracy.cs new file mode 100644 index 0000000..8a2f299 --- /dev/null +++ b/bc-sharp-crypto/src/tsp/GenTimeAccuracy.cs @@ -0,0 +1,33 @@ +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Tsp; + +namespace Org.BouncyCastle.Tsp +{ + public class GenTimeAccuracy + { + private Accuracy accuracy; + + public GenTimeAccuracy( + Accuracy accuracy) + { + this.accuracy = accuracy; + } + + public int Seconds { get { return GetTimeComponent(accuracy.Seconds); } } + + public int Millis { get { return GetTimeComponent(accuracy.Millis); } } + + public int Micros { get { return GetTimeComponent(accuracy.Micros); } } + + private int GetTimeComponent( + DerInteger time) + { + return time == null ? 0 : time.Value.IntValue; + } + + public override string ToString() + { + return Seconds + "." + Millis.ToString("000") + Micros.ToString("000"); + } + } +} diff --git a/bc-sharp-crypto/src/tsp/TSPAlgorithms.cs b/bc-sharp-crypto/src/tsp/TSPAlgorithms.cs new file mode 100644 index 0000000..e3dfc79 --- /dev/null +++ b/bc-sharp-crypto/src/tsp/TSPAlgorithms.cs @@ -0,0 +1,48 @@ +using System.Collections; + +using Org.BouncyCastle.Asn1.CryptoPro; +using Org.BouncyCastle.Asn1.Nist; +using Org.BouncyCastle.Asn1.Oiw; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.TeleTrust; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Tsp +{ + /** + * Recognised hash algorithms for the time stamp protocol. + */ + public abstract class TspAlgorithms + { + public static readonly string MD5 = PkcsObjectIdentifiers.MD5.Id; + + public static readonly string Sha1 = OiwObjectIdentifiers.IdSha1.Id; + + public static readonly string Sha224 = NistObjectIdentifiers.IdSha224.Id; + public static readonly string Sha256 = NistObjectIdentifiers.IdSha256.Id; + public static readonly string Sha384 = NistObjectIdentifiers.IdSha384.Id; + public static readonly string Sha512 = NistObjectIdentifiers.IdSha512.Id; + + public static readonly string RipeMD128 = TeleTrusTObjectIdentifiers.RipeMD128.Id; + public static readonly string RipeMD160 = TeleTrusTObjectIdentifiers.RipeMD160.Id; + public static readonly string RipeMD256 = TeleTrusTObjectIdentifiers.RipeMD256.Id; + + public static readonly string Gost3411 = CryptoProObjectIdentifiers.GostR3411.Id; + + public static readonly IList Allowed; + + static TspAlgorithms() + { + string[] algs = new string[] + { + Gost3411, MD5, Sha1, Sha224, Sha256, Sha384, Sha512, RipeMD128, RipeMD160, RipeMD256 + }; + + Allowed = Platform.CreateArrayList(); + foreach (string alg in algs) + { + Allowed.Add(alg); + } + } + } +} diff --git a/bc-sharp-crypto/src/tsp/TSPException.cs b/bc-sharp-crypto/src/tsp/TSPException.cs new file mode 100644 index 0000000..0f29b12 --- /dev/null +++ b/bc-sharp-crypto/src/tsp/TSPException.cs @@ -0,0 +1,28 @@ +using System; + +namespace Org.BouncyCastle.Tsp +{ +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT || PORTABLE) + [Serializable] +#endif + public class TspException + : Exception + { + public TspException() + { + } + + public TspException( + string message) + : base(message) + { + } + + public TspException( + string message, + Exception e) + : base(message, e) + { + } + } +} diff --git a/bc-sharp-crypto/src/tsp/TSPUtil.cs b/bc-sharp-crypto/src/tsp/TSPUtil.cs new file mode 100644 index 0000000..1026914 --- /dev/null +++ b/bc-sharp-crypto/src/tsp/TSPUtil.cs @@ -0,0 +1,202 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.CryptoPro; +using Org.BouncyCastle.Asn1.Nist; +using Org.BouncyCastle.Asn1.Oiw; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.TeleTrust; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Cms; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; +using Org.BouncyCastle.X509; + +namespace Org.BouncyCastle.Tsp +{ + public class TspUtil + { + private static ISet EmptySet = CollectionUtilities.ReadOnly(new HashSet()); + private static IList EmptyList = CollectionUtilities.ReadOnly(Platform.CreateArrayList()); + + private static readonly IDictionary digestLengths = Platform.CreateHashtable(); + private static readonly IDictionary digestNames = Platform.CreateHashtable(); + + static TspUtil() + { + digestLengths.Add(PkcsObjectIdentifiers.MD5.Id, 16); + digestLengths.Add(OiwObjectIdentifiers.IdSha1.Id, 20); + digestLengths.Add(NistObjectIdentifiers.IdSha224.Id, 28); + digestLengths.Add(NistObjectIdentifiers.IdSha256.Id, 32); + digestLengths.Add(NistObjectIdentifiers.IdSha384.Id, 48); + digestLengths.Add(NistObjectIdentifiers.IdSha512.Id, 64); + digestLengths.Add(TeleTrusTObjectIdentifiers.RipeMD128.Id, 16); + digestLengths.Add(TeleTrusTObjectIdentifiers.RipeMD160.Id, 20); + digestLengths.Add(TeleTrusTObjectIdentifiers.RipeMD256.Id, 32); + digestLengths.Add(CryptoProObjectIdentifiers.GostR3411.Id, 32); + + digestNames.Add(PkcsObjectIdentifiers.MD5.Id, "MD5"); + digestNames.Add(OiwObjectIdentifiers.IdSha1.Id, "SHA1"); + digestNames.Add(NistObjectIdentifiers.IdSha224.Id, "SHA224"); + digestNames.Add(NistObjectIdentifiers.IdSha256.Id, "SHA256"); + digestNames.Add(NistObjectIdentifiers.IdSha384.Id, "SHA384"); + digestNames.Add(NistObjectIdentifiers.IdSha512.Id, "SHA512"); + digestNames.Add(PkcsObjectIdentifiers.Sha1WithRsaEncryption.Id, "SHA1"); + digestNames.Add(PkcsObjectIdentifiers.Sha224WithRsaEncryption.Id, "SHA224"); + digestNames.Add(PkcsObjectIdentifiers.Sha256WithRsaEncryption.Id, "SHA256"); + digestNames.Add(PkcsObjectIdentifiers.Sha384WithRsaEncryption.Id, "SHA384"); + digestNames.Add(PkcsObjectIdentifiers.Sha512WithRsaEncryption.Id, "SHA512"); + digestNames.Add(TeleTrusTObjectIdentifiers.RipeMD128.Id, "RIPEMD128"); + digestNames.Add(TeleTrusTObjectIdentifiers.RipeMD160.Id, "RIPEMD160"); + digestNames.Add(TeleTrusTObjectIdentifiers.RipeMD256.Id, "RIPEMD256"); + digestNames.Add(CryptoProObjectIdentifiers.GostR3411.Id, "GOST3411"); + } + + + /** + * Fetches the signature time-stamp attributes from a SignerInformation object. + * Checks that the MessageImprint for each time-stamp matches the signature field. + * (see RFC 3161 Appendix A). + * + * @param signerInfo a SignerInformation to search for time-stamps + * @return a collection of TimeStampToken objects + * @throws TSPValidationException + */ + public static ICollection GetSignatureTimestamps( + SignerInformation signerInfo) + { + IList timestamps = Platform.CreateArrayList(); + + Asn1.Cms.AttributeTable unsignedAttrs = signerInfo.UnsignedAttributes; + if (unsignedAttrs != null) + { + foreach (Asn1.Cms.Attribute tsAttr in unsignedAttrs.GetAll( + PkcsObjectIdentifiers.IdAASignatureTimeStampToken)) + { + foreach (Asn1Encodable asn1 in tsAttr.AttrValues) + { + try + { + Asn1.Cms.ContentInfo contentInfo = Asn1.Cms.ContentInfo.GetInstance( + asn1.ToAsn1Object()); + TimeStampToken timeStampToken = new TimeStampToken(contentInfo); + TimeStampTokenInfo tstInfo = timeStampToken.TimeStampInfo; + + byte[] expectedDigest = DigestUtilities.CalculateDigest( + GetDigestAlgName(tstInfo.MessageImprintAlgOid), + signerInfo.GetSignature()); + + if (!Arrays.ConstantTimeAreEqual(expectedDigest, tstInfo.GetMessageImprintDigest())) + throw new TspValidationException("Incorrect digest in message imprint"); + + timestamps.Add(timeStampToken); + } + catch (SecurityUtilityException) + { + throw new TspValidationException("Unknown hash algorithm specified in timestamp"); + } + catch (Exception) + { + throw new TspValidationException("Timestamp could not be parsed"); + } + } + } + } + + return timestamps; + } + + /** + * Validate the passed in certificate as being of the correct type to be used + * for time stamping. To be valid it must have an ExtendedKeyUsage extension + * which has a key purpose identifier of id-kp-timeStamping. + * + * @param cert the certificate of interest. + * @throws TspValidationException if the certicate fails on one of the check points. + */ + public static void ValidateCertificate( + X509Certificate cert) + { + if (cert.Version != 3) + throw new ArgumentException("Certificate must have an ExtendedKeyUsage extension."); + + Asn1OctetString ext = cert.GetExtensionValue(X509Extensions.ExtendedKeyUsage); + if (ext == null) + throw new TspValidationException("Certificate must have an ExtendedKeyUsage extension."); + + if (!cert.GetCriticalExtensionOids().Contains(X509Extensions.ExtendedKeyUsage.Id)) + throw new TspValidationException("Certificate must have an ExtendedKeyUsage extension marked as critical."); + + try + { + ExtendedKeyUsage extKey = ExtendedKeyUsage.GetInstance( + Asn1Object.FromByteArray(ext.GetOctets())); + + if (!extKey.HasKeyPurposeId(KeyPurposeID.IdKPTimeStamping) || extKey.Count != 1) + throw new TspValidationException("ExtendedKeyUsage not solely time stamping."); + } + catch (IOException) + { + throw new TspValidationException("cannot process ExtendedKeyUsage extension"); + } + } + + /// + /// Return the digest algorithm using one of the standard JCA string + /// representations rather than the algorithm identifier (if possible). + /// + internal static string GetDigestAlgName( + string digestAlgOID) + { + string digestName = (string) digestNames[digestAlgOID]; + + return digestName != null ? digestName : digestAlgOID; + } + + internal static int GetDigestLength( + string digestAlgOID) + { + if (!digestLengths.Contains(digestAlgOID)) + throw new TspException("digest algorithm cannot be found."); + + return (int)digestLengths[digestAlgOID]; + } + + internal static IDigest CreateDigestInstance( + String digestAlgOID) + { + string digestName = GetDigestAlgName(digestAlgOID); + + return DigestUtilities.GetDigest(digestName); + } + + internal static ISet GetCriticalExtensionOids(X509Extensions extensions) + { + if (extensions == null) + return EmptySet; + + return CollectionUtilities.ReadOnly(new HashSet(extensions.GetCriticalExtensionOids())); + } + + internal static ISet GetNonCriticalExtensionOids(X509Extensions extensions) + { + if (extensions == null) + return EmptySet; + + // TODO: should probably produce a set that imposes correct ordering + return CollectionUtilities.ReadOnly(new HashSet(extensions.GetNonCriticalExtensionOids())); + } + + internal static IList GetExtensionOids(X509Extensions extensions) + { + if (extensions == null) + return EmptyList; + + return CollectionUtilities.ReadOnly(Platform.CreateArrayList(extensions.GetExtensionOids())); + } + } +} diff --git a/bc-sharp-crypto/src/tsp/TSPValidationException.cs b/bc-sharp-crypto/src/tsp/TSPValidationException.cs new file mode 100644 index 0000000..80f6420 --- /dev/null +++ b/bc-sharp-crypto/src/tsp/TSPValidationException.cs @@ -0,0 +1,44 @@ +using System; + +namespace Org.BouncyCastle.Tsp +{ + /** + * Exception thrown if a TSP request or response fails to validate. + *

    + * If a failure code is associated with the exception it can be retrieved using + * the getFailureCode() method.

    + */ +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT || PORTABLE) + [Serializable] +#endif + public class TspValidationException + : TspException + { + private int failureCode; + + public TspValidationException( + string message) + : base(message) + { + this.failureCode = -1; + } + + public TspValidationException( + string message, + int failureCode) + : base(message) + { + this.failureCode = failureCode; + } + + /** + * Return the failure code associated with this exception - if one is set. + * + * @return the failure code if set, -1 otherwise. + */ + public int FailureCode + { + get { return failureCode; } + } + } +} diff --git a/bc-sharp-crypto/src/tsp/TimeStampRequest.cs b/bc-sharp-crypto/src/tsp/TimeStampRequest.cs new file mode 100644 index 0000000..0b41ade --- /dev/null +++ b/bc-sharp-crypto/src/tsp/TimeStampRequest.cs @@ -0,0 +1,186 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Cmp; +using Org.BouncyCastle.Asn1.Tsp; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.X509; + +namespace Org.BouncyCastle.Tsp +{ + /** + * Base class for an RFC 3161 Time Stamp Request. + */ + public class TimeStampRequest + : X509ExtensionBase + { + private TimeStampReq req; + private X509Extensions extensions; + + public TimeStampRequest( + TimeStampReq req) + { + this.req = req; + this.extensions = req.Extensions; + } + + /** + * Create a TimeStampRequest from the past in byte array. + * + * @param req byte array containing the request. + * @throws IOException if the request is malformed. + */ + public TimeStampRequest( + byte[] req) + : this(new Asn1InputStream(req)) + { + } + + /** + * Create a TimeStampRequest from the past in input stream. + * + * @param in input stream containing the request. + * @throws IOException if the request is malformed. + */ + public TimeStampRequest( + Stream input) + : this(new Asn1InputStream(input)) + { + } + + private TimeStampRequest( + Asn1InputStream str) + { + try + { + this.req = TimeStampReq.GetInstance(str.ReadObject()); + } + catch (InvalidCastException e) + { + throw new IOException("malformed request: " + e); + } + catch (ArgumentException e) + { + throw new IOException("malformed request: " + e); + } + } + + public int Version + { + get { return req.Version.Value.IntValue; } + } + + public string MessageImprintAlgOid + { + get { return req.MessageImprint.HashAlgorithm.Algorithm.Id; } + } + + public byte[] GetMessageImprintDigest() + { + return req.MessageImprint.GetHashedMessage(); + } + + public string ReqPolicy + { + get + { + return req.ReqPolicy == null + ? null + : req.ReqPolicy.Id; + } + } + + public BigInteger Nonce + { + get + { + return req.Nonce == null + ? null + : req.Nonce.Value; + } + } + + public bool CertReq + { + get + { + return req.CertReq == null + ? false + : req.CertReq.IsTrue; + } + } + + /** + * Validate the timestamp request, checking the digest to see if it is of an + * accepted type and whether it is of the correct length for the algorithm specified. + * + * @param algorithms a set of string OIDS giving accepted algorithms. + * @param policies if non-null a set of policies we are willing to sign under. + * @param extensions if non-null a set of extensions we are willing to accept. + * @throws TspException if the request is invalid, or processing fails. + */ + public void Validate( + IList algorithms, + IList policies, + IList extensions) + { + if (!algorithms.Contains(this.MessageImprintAlgOid)) + throw new TspValidationException("request contains unknown algorithm", PkiFailureInfo.BadAlg); + + if (policies != null && this.ReqPolicy != null && !policies.Contains(this.ReqPolicy)) + throw new TspValidationException("request contains unknown policy", PkiFailureInfo.UnacceptedPolicy); + + if (this.Extensions != null && extensions != null) + { + foreach (DerObjectIdentifier oid in this.Extensions.ExtensionOids) + { + if (!extensions.Contains(oid.Id)) + throw new TspValidationException("request contains unknown extension", PkiFailureInfo.UnacceptedExtension); + } + } + + int digestLength = TspUtil.GetDigestLength(this.MessageImprintAlgOid); + + if (digestLength != this.GetMessageImprintDigest().Length) + throw new TspValidationException("imprint digest the wrong length", PkiFailureInfo.BadDataFormat); + } + + /** + * return the ASN.1 encoded representation of this object. + */ + public byte[] GetEncoded() + { + return req.GetEncoded(); + } + + internal X509Extensions Extensions + { + get { return req.Extensions; } + } + + public virtual bool HasExtensions + { + get { return extensions != null; } + } + + public virtual X509Extension GetExtension(DerObjectIdentifier oid) + { + return extensions == null ? null : extensions.GetExtension(oid); + } + + public virtual IList GetExtensionOids() + { + return TspUtil.GetExtensionOids(extensions); + } + + protected override X509Extensions GetX509Extensions() + { + return Extensions; + } + } +} diff --git a/bc-sharp-crypto/src/tsp/TimeStampRequestGenerator.cs b/bc-sharp-crypto/src/tsp/TimeStampRequestGenerator.cs new file mode 100644 index 0000000..2c698e4 --- /dev/null +++ b/bc-sharp-crypto/src/tsp/TimeStampRequestGenerator.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Tsp; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Math; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Tsp +{ + /** + * Generator for RFC 3161 Time Stamp Request objects. + */ + public class TimeStampRequestGenerator + { + private DerObjectIdentifier reqPolicy; + + private DerBoolean certReq; + + private IDictionary extensions = Platform.CreateHashtable(); + private IList extOrdering = Platform.CreateArrayList(); + + public void SetReqPolicy( + string reqPolicy) + { + this.reqPolicy = new DerObjectIdentifier(reqPolicy); + } + + public void SetCertReq( + bool certReq) + { + this.certReq = DerBoolean.GetInstance(certReq); + } + + /** + * add a given extension field for the standard extensions tag (tag 3) + * @throws IOException + */ + [Obsolete("Use method taking DerObjectIdentifier")] + public void AddExtension( + string oid, + bool critical, + Asn1Encodable value) + { + this.AddExtension(oid, critical, value.GetEncoded()); + } + + /** + * add a given extension field for the standard extensions tag + * The value parameter becomes the contents of the octet string associated + * with the extension. + */ + [Obsolete("Use method taking DerObjectIdentifier")] + public void AddExtension( + string oid, + bool critical, + byte[] value) + { + DerObjectIdentifier derOid = new DerObjectIdentifier(oid); + extensions[derOid] = new X509Extension(critical, new DerOctetString(value)); + extOrdering.Add(derOid); + } + + /** + * add a given extension field for the standard extensions tag (tag 3) + * @throws IOException + */ + public virtual void AddExtension( + DerObjectIdentifier oid, + bool critical, + Asn1Encodable extValue) + { + this.AddExtension(oid, critical, extValue.GetEncoded()); + } + + /** + * add a given extension field for the standard extensions tag + * The value parameter becomes the contents of the octet string associated + * with the extension. + */ + public virtual void AddExtension( + DerObjectIdentifier oid, + bool critical, + byte[] extValue) + { + extensions.Add(oid, new X509Extension(critical, new DerOctetString(extValue))); + extOrdering.Add(oid); + } + + public TimeStampRequest Generate( + string digestAlgorithm, + byte[] digest) + { + return this.Generate(digestAlgorithm, digest, null); + } + + public TimeStampRequest Generate( + string digestAlgorithmOid, + byte[] digest, + BigInteger nonce) + { + if (digestAlgorithmOid == null) + { + throw new ArgumentException("No digest algorithm specified"); + } + + DerObjectIdentifier digestAlgOid = new DerObjectIdentifier(digestAlgorithmOid); + + AlgorithmIdentifier algID = new AlgorithmIdentifier(digestAlgOid, DerNull.Instance); + MessageImprint messageImprint = new MessageImprint(algID, digest); + + X509Extensions ext = null; + + if (extOrdering.Count != 0) + { + ext = new X509Extensions(extOrdering, extensions); + } + + DerInteger derNonce = nonce == null + ? null + : new DerInteger(nonce); + + return new TimeStampRequest( + new TimeStampReq(messageImprint, reqPolicy, derNonce, certReq, ext)); + } + + public virtual TimeStampRequest Generate(DerObjectIdentifier digestAlgorithm, byte[] digest) + { + return Generate(digestAlgorithm.Id, digest); + } + + public virtual TimeStampRequest Generate(DerObjectIdentifier digestAlgorithm, byte[] digest, BigInteger nonce) + { + return Generate(digestAlgorithm.Id, digest, nonce); + } + } +} diff --git a/bc-sharp-crypto/src/tsp/TimeStampResponse.cs b/bc-sharp-crypto/src/tsp/TimeStampResponse.cs new file mode 100644 index 0000000..0695211 --- /dev/null +++ b/bc-sharp-crypto/src/tsp/TimeStampResponse.cs @@ -0,0 +1,184 @@ +using System; +using System.IO; +using System.Text; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Cmp; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.Tsp; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Tsp +{ + /** + * Base class for an RFC 3161 Time Stamp Response object. + */ + public class TimeStampResponse + { + private TimeStampResp resp; + private TimeStampToken timeStampToken; + + public TimeStampResponse( + TimeStampResp resp) + { + this.resp = resp; + + if (resp.TimeStampToken != null) + { + timeStampToken = new TimeStampToken(resp.TimeStampToken); + } + } + + /** + * Create a TimeStampResponse from a byte array containing an ASN.1 encoding. + * + * @param resp the byte array containing the encoded response. + * @throws TspException if the response is malformed. + * @throws IOException if the byte array doesn't represent an ASN.1 encoding. + */ + public TimeStampResponse( + byte[] resp) + : this(readTimeStampResp(new Asn1InputStream(resp))) + { + } + + /** + * Create a TimeStampResponse from an input stream containing an ASN.1 encoding. + * + * @param input the input stream containing the encoded response. + * @throws TspException if the response is malformed. + * @throws IOException if the stream doesn't represent an ASN.1 encoding. + */ + public TimeStampResponse( + Stream input) + : this(readTimeStampResp(new Asn1InputStream(input))) + { + } + + private static TimeStampResp readTimeStampResp( + Asn1InputStream input) + { + try + { + return TimeStampResp.GetInstance(input.ReadObject()); + } + catch (ArgumentException e) + { + throw new TspException("malformed timestamp response: " + e, e); + } + catch (InvalidCastException e) + { + throw new TspException("malformed timestamp response: " + e, e); + } + } + + public int Status + { + get { return resp.Status.Status.IntValue; } + } + + public string GetStatusString() + { + if (resp.Status.StatusString == null) + { + return null; + } + + StringBuilder statusStringBuf = new StringBuilder(); + PkiFreeText text = resp.Status.StatusString; + for (int i = 0; i != text.Count; i++) + { + statusStringBuf.Append(text[i].GetString()); + } + + return statusStringBuf.ToString(); + } + + public PkiFailureInfo GetFailInfo() + { + if (resp.Status.FailInfo == null) + { + return null; + } + + return new PkiFailureInfo(resp.Status.FailInfo); + } + + public TimeStampToken TimeStampToken + { + get { return timeStampToken; } + } + + /** + * Check this response against to see if it a well formed response for + * the passed in request. Validation will include checking the time stamp + * token if the response status is GRANTED or GRANTED_WITH_MODS. + * + * @param request the request to be checked against + * @throws TspException if the request can not match this response. + */ + public void Validate( + TimeStampRequest request) + { + TimeStampToken tok = this.TimeStampToken; + + if (tok != null) + { + TimeStampTokenInfo tstInfo = tok.TimeStampInfo; + + if (request.Nonce != null && !request.Nonce.Equals(tstInfo.Nonce)) + { + throw new TspValidationException("response contains wrong nonce value."); + } + + if (this.Status != (int) PkiStatus.Granted && this.Status != (int) PkiStatus.GrantedWithMods) + { + throw new TspValidationException("time stamp token found in failed request."); + } + + if (!Arrays.ConstantTimeAreEqual(request.GetMessageImprintDigest(), tstInfo.GetMessageImprintDigest())) + { + throw new TspValidationException("response for different message imprint digest."); + } + + if (!tstInfo.MessageImprintAlgOid.Equals(request.MessageImprintAlgOid)) + { + throw new TspValidationException("response for different message imprint algorithm."); + } + + Asn1.Cms.Attribute scV1 = tok.SignedAttributes[PkcsObjectIdentifiers.IdAASigningCertificate]; + Asn1.Cms.Attribute scV2 = tok.SignedAttributes[PkcsObjectIdentifiers.IdAASigningCertificateV2]; + + if (scV1 == null && scV2 == null) + { + throw new TspValidationException("no signing certificate attribute present."); + } + + if (scV1 != null && scV2 != null) + { + /* + * RFC 5035 5.4. If both attributes exist in a single message, + * they are independently evaluated. + */ + } + + if (request.ReqPolicy != null && !request.ReqPolicy.Equals(tstInfo.Policy)) + { + throw new TspValidationException("TSA policy wrong for request."); + } + } + else if (this.Status == (int) PkiStatus.Granted || this.Status == (int) PkiStatus.GrantedWithMods) + { + throw new TspValidationException("no time stamp token found and one expected."); + } + } + + /** + * return the ASN.1 encoded representation of this object. + */ + public byte[] GetEncoded() + { + return resp.GetEncoded(); + } + } +} diff --git a/bc-sharp-crypto/src/tsp/TimeStampResponseGenerator.cs b/bc-sharp-crypto/src/tsp/TimeStampResponseGenerator.cs new file mode 100644 index 0000000..b596f8d --- /dev/null +++ b/bc-sharp-crypto/src/tsp/TimeStampResponseGenerator.cs @@ -0,0 +1,209 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Cmp; +using Org.BouncyCastle.Asn1.Cms; +using Org.BouncyCastle.Asn1.Tsp; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities.Date; + +namespace Org.BouncyCastle.Tsp +{ + /** + * Generator for RFC 3161 Time Stamp Responses. + */ + public class TimeStampResponseGenerator + { + private PkiStatus status; + + private Asn1EncodableVector statusStrings; + + private int failInfo; + private TimeStampTokenGenerator tokenGenerator; + private IList acceptedAlgorithms; + private IList acceptedPolicies; + private IList acceptedExtensions; + + public TimeStampResponseGenerator( + TimeStampTokenGenerator tokenGenerator, + IList acceptedAlgorithms) + : this(tokenGenerator, acceptedAlgorithms, null, null) + { + } + + public TimeStampResponseGenerator( + TimeStampTokenGenerator tokenGenerator, + IList acceptedAlgorithms, + IList acceptedPolicy) + : this(tokenGenerator, acceptedAlgorithms, acceptedPolicy, null) + { + } + + public TimeStampResponseGenerator( + TimeStampTokenGenerator tokenGenerator, + IList acceptedAlgorithms, + IList acceptedPolicies, + IList acceptedExtensions) + { + this.tokenGenerator = tokenGenerator; + this.acceptedAlgorithms = acceptedAlgorithms; + this.acceptedPolicies = acceptedPolicies; + this.acceptedExtensions = acceptedExtensions; + + statusStrings = new Asn1EncodableVector(); + } + + private void AddStatusString(string statusString) + { + statusStrings.Add(new DerUtf8String(statusString)); + } + + private void SetFailInfoField(int field) + { + failInfo |= field; + } + + private PkiStatusInfo GetPkiStatusInfo() + { + Asn1EncodableVector v = new Asn1EncodableVector( + new DerInteger((int)status)); + + if (statusStrings.Count > 0) + { + v.Add(new PkiFreeText(new DerSequence(statusStrings))); + } + + if (failInfo != 0) + { + v.Add(new FailInfo(failInfo)); + } + + return new PkiStatusInfo(new DerSequence(v)); + } + + public TimeStampResponse Generate( + TimeStampRequest request, + BigInteger serialNumber, + DateTime genTime) + { + return Generate(request, serialNumber, new DateTimeObject(genTime)); + } + + /** + * Return an appropriate TimeStampResponse. + *

    + * If genTime is null a timeNotAvailable error response will be returned. + * + * @param request the request this response is for. + * @param serialNumber serial number for the response token. + * @param genTime generation time for the response token. + * @param provider provider to use for signature calculation. + * @return + * @throws NoSuchAlgorithmException + * @throws NoSuchProviderException + * @throws TSPException + *

    + */ + public TimeStampResponse Generate( + TimeStampRequest request, + BigInteger serialNumber, + DateTimeObject genTime) + { + TimeStampResp resp; + + try + { + if (genTime == null) + throw new TspValidationException("The time source is not available.", + PkiFailureInfo.TimeNotAvailable); + + request.Validate(acceptedAlgorithms, acceptedPolicies, acceptedExtensions); + + this.status = PkiStatus.Granted; + this.AddStatusString("Operation Okay"); + + PkiStatusInfo pkiStatusInfo = GetPkiStatusInfo(); + + ContentInfo tstTokenContentInfo; + try + { + TimeStampToken token = tokenGenerator.Generate(request, serialNumber, genTime.Value); + byte[] encoded = token.ToCmsSignedData().GetEncoded(); + + tstTokenContentInfo = ContentInfo.GetInstance(Asn1Object.FromByteArray(encoded)); + } + catch (IOException e) + { + throw new TspException("Timestamp token received cannot be converted to ContentInfo", e); + } + + resp = new TimeStampResp(pkiStatusInfo, tstTokenContentInfo); + } + catch (TspValidationException e) + { + status = PkiStatus.Rejection; + + this.SetFailInfoField(e.FailureCode); + this.AddStatusString(e.Message); + + PkiStatusInfo pkiStatusInfo = GetPkiStatusInfo(); + + resp = new TimeStampResp(pkiStatusInfo, null); + } + + try + { + return new TimeStampResponse(resp); + } + catch (IOException e) + { + throw new TspException("created badly formatted response!", e); + } + } + + class FailInfo + : DerBitString + { + internal FailInfo(int failInfoValue) + : base(failInfoValue) + { + } + } + + /** + * Generate a TimeStampResponse with chosen status and FailInfoField. + * + * @param status the PKIStatus to set. + * @param failInfoField the FailInfoField to set. + * @param statusString an optional string describing the failure. + * @return a TimeStampResponse with a failInfoField and optional statusString + * @throws TSPException in case the response could not be created + */ + public TimeStampResponse GenerateFailResponse(PkiStatus status, int failInfoField, string statusString) + { + this.status = status; + + this.SetFailInfoField(failInfoField); + + if (statusString != null) + { + this.AddStatusString(statusString); + } + + PkiStatusInfo pkiStatusInfo = GetPkiStatusInfo(); + + TimeStampResp resp = new TimeStampResp(pkiStatusInfo, null); + + try + { + return new TimeStampResponse(resp); + } + catch (IOException e) + { + throw new TspException("created badly formatted response!", e); + } + } + } +} diff --git a/bc-sharp-crypto/src/tsp/TimeStampToken.cs b/bc-sharp-crypto/src/tsp/TimeStampToken.cs new file mode 100644 index 0000000..105208a --- /dev/null +++ b/bc-sharp-crypto/src/tsp/TimeStampToken.cs @@ -0,0 +1,305 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Ess; +using Org.BouncyCastle.Asn1.Nist; +using Org.BouncyCastle.Asn1.Oiw; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.Tsp; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Cms; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Security.Certificates; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.X509; +using Org.BouncyCastle.X509.Store; + +namespace Org.BouncyCastle.Tsp +{ + public class TimeStampToken + { + private readonly CmsSignedData tsToken; + private readonly SignerInformation tsaSignerInfo; +// private readonly DateTime genTime; + private readonly TimeStampTokenInfo tstInfo; + private readonly CertID certID; + + public TimeStampToken( + Asn1.Cms.ContentInfo contentInfo) + : this(new CmsSignedData(contentInfo)) + { + } + + public TimeStampToken( + CmsSignedData signedData) + { + this.tsToken = signedData; + + if (!this.tsToken.SignedContentType.Equals(PkcsObjectIdentifiers.IdCTTstInfo)) + { + throw new TspValidationException("ContentInfo object not for a time stamp."); + } + + ICollection signers = tsToken.GetSignerInfos().GetSigners(); + + if (signers.Count != 1) + { + throw new ArgumentException("Time-stamp token signed by " + + signers.Count + + " signers, but it must contain just the TSA signature."); + } + + + IEnumerator signerEnum = signers.GetEnumerator(); + + signerEnum.MoveNext(); + tsaSignerInfo = (SignerInformation) signerEnum.Current; + + try + { + CmsProcessable content = tsToken.SignedContent; + MemoryStream bOut = new MemoryStream(); + + content.Write(bOut); + + this.tstInfo = new TimeStampTokenInfo( + TstInfo.GetInstance( + Asn1Object.FromByteArray(bOut.ToArray()))); + + Asn1.Cms.Attribute attr = tsaSignerInfo.SignedAttributes[ + PkcsObjectIdentifiers.IdAASigningCertificate]; + +// if (attr == null) +// { +// throw new TspValidationException( +// "no signing certificate attribute found, time stamp invalid."); +// } +// +// SigningCertificate signCert = SigningCertificate.GetInstance( +// attr.AttrValues[0]); +// +// this.certID = EssCertID.GetInstance(signCert.GetCerts()[0]); + + if (attr != null) + { + SigningCertificate signCert = SigningCertificate.GetInstance(attr.AttrValues[0]); + + this.certID = new CertID(EssCertID.GetInstance(signCert.GetCerts()[0])); + } + else + { + attr = tsaSignerInfo.SignedAttributes[PkcsObjectIdentifiers.IdAASigningCertificateV2]; + + if (attr == null) + throw new TspValidationException("no signing certificate attribute found, time stamp invalid."); + + SigningCertificateV2 signCertV2 = SigningCertificateV2.GetInstance(attr.AttrValues[0]); + + this.certID = new CertID(EssCertIDv2.GetInstance(signCertV2.GetCerts()[0])); + } + } + catch (CmsException e) + { + throw new TspException(e.Message, e.InnerException); + } + } + + public TimeStampTokenInfo TimeStampInfo + { + get { return tstInfo; } + } + + public SignerID SignerID + { + get { return tsaSignerInfo.SignerID; } + } + + public Asn1.Cms.AttributeTable SignedAttributes + { + get { return tsaSignerInfo.SignedAttributes; } + } + + public Asn1.Cms.AttributeTable UnsignedAttributes + { + get { return tsaSignerInfo.UnsignedAttributes; } + } + + public IX509Store GetCertificates( + string type) + { + return tsToken.GetCertificates(type); + } + + public IX509Store GetCrls( + string type) + { + return tsToken.GetCrls(type); + } + + public IX509Store GetAttributeCertificates( + string type) + { + return tsToken.GetAttributeCertificates(type); + } + + /** + * Validate the time stamp token. + *

    + * To be valid the token must be signed by the passed in certificate and + * the certificate must be the one referred to by the SigningCertificate + * attribute included in the hashed attributes of the token. The + * certificate must also have the ExtendedKeyUsageExtension with only + * KeyPurposeID.IdKPTimeStamping and have been valid at the time the + * timestamp was created. + *

    + *

    + * A successful call to validate means all the above are true. + *

    + */ + public void Validate( + X509Certificate cert) + { + try + { + byte[] hash = DigestUtilities.CalculateDigest( + certID.GetHashAlgorithmName(), cert.GetEncoded()); + + if (!Arrays.ConstantTimeAreEqual(certID.GetCertHash(), hash)) + { + throw new TspValidationException("certificate hash does not match certID hash."); + } + + if (certID.IssuerSerial != null) + { + if (!certID.IssuerSerial.Serial.Value.Equals(cert.SerialNumber)) + { + throw new TspValidationException("certificate serial number does not match certID for signature."); + } + + GeneralName[] names = certID.IssuerSerial.Issuer.GetNames(); + X509Name principal = PrincipalUtilities.GetIssuerX509Principal(cert); + bool found = false; + + for (int i = 0; i != names.Length; i++) + { + if (names[i].TagNo == 4 + && X509Name.GetInstance(names[i].Name).Equivalent(principal)) + { + found = true; + break; + } + } + + if (!found) + { + throw new TspValidationException("certificate name does not match certID for signature. "); + } + } + + TspUtil.ValidateCertificate(cert); + + cert.CheckValidity(tstInfo.GenTime); + + if (!tsaSignerInfo.Verify(cert)) + { + throw new TspValidationException("signature not created by certificate."); + } + } + catch (CmsException e) + { + if (e.InnerException != null) + { + throw new TspException(e.Message, e.InnerException); + } + + throw new TspException("CMS exception: " + e, e); + } + catch (CertificateEncodingException e) + { + throw new TspException("problem processing certificate: " + e, e); + } + catch (SecurityUtilityException e) + { + throw new TspException("cannot find algorithm: " + e.Message, e); + } + } + + /** + * Return the underlying CmsSignedData object. + * + * @return the underlying CMS structure. + */ + public CmsSignedData ToCmsSignedData() + { + return tsToken; + } + + /** + * Return a ASN.1 encoded byte stream representing the encoded object. + * + * @throws IOException if encoding fails. + */ + public byte[] GetEncoded() + { + return tsToken.GetEncoded(); + } + + + // perhaps this should be done using an interface on the ASN.1 classes... + private class CertID + { + private EssCertID certID; + private EssCertIDv2 certIDv2; + + internal CertID(EssCertID certID) + { + this.certID = certID; + this.certIDv2 = null; + } + + internal CertID(EssCertIDv2 certID) + { + this.certIDv2 = certID; + this.certID = null; + } + + public string GetHashAlgorithmName() + { + if (certID != null) + return "SHA-1"; + + if (NistObjectIdentifiers.IdSha256.Equals(certIDv2.HashAlgorithm.Algorithm)) + return "SHA-256"; + + return certIDv2.HashAlgorithm.Algorithm.Id; + } + + public AlgorithmIdentifier GetHashAlgorithm() + { + return (certID != null) + ? new AlgorithmIdentifier(OiwObjectIdentifiers.IdSha1) + : certIDv2.HashAlgorithm; + } + + public byte[] GetCertHash() + { + return certID != null + ? certID.GetCertHash() + : certIDv2.GetCertHash(); + } + + public IssuerSerial IssuerSerial + { + get + { + return certID != null + ? certID.IssuerSerial + : certIDv2.IssuerSerial; + } + } + } + } +} diff --git a/bc-sharp-crypto/src/tsp/TimeStampTokenGenerator.cs b/bc-sharp-crypto/src/tsp/TimeStampTokenGenerator.cs new file mode 100644 index 0000000..07eddd4 --- /dev/null +++ b/bc-sharp-crypto/src/tsp/TimeStampTokenGenerator.cs @@ -0,0 +1,245 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Ess; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.Tsp; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Cms; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Security.Certificates; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.X509; +using Org.BouncyCastle.X509.Store; + +namespace Org.BouncyCastle.Tsp +{ + public class TimeStampTokenGenerator + { + private int accuracySeconds = -1; + private int accuracyMillis = -1; + private int accuracyMicros = -1; + private bool ordering = false; + private GeneralName tsa = null; + private string tsaPolicyOID; + + private AsymmetricKeyParameter key; + private X509Certificate cert; + private string digestOID; + private Asn1.Cms.AttributeTable signedAttr; + private Asn1.Cms.AttributeTable unsignedAttr; + private IX509Store x509Certs; + private IX509Store x509Crls; + + /** + * basic creation - only the default attributes will be included here. + */ + public TimeStampTokenGenerator( + AsymmetricKeyParameter key, + X509Certificate cert, + string digestOID, + string tsaPolicyOID) + : this(key, cert, digestOID, tsaPolicyOID, null, null) + { + } + + /** + * create with a signer with extra signed/unsigned attributes. + */ + public TimeStampTokenGenerator( + AsymmetricKeyParameter key, + X509Certificate cert, + string digestOID, + string tsaPolicyOID, + Asn1.Cms.AttributeTable signedAttr, + Asn1.Cms.AttributeTable unsignedAttr) + { + this.key = key; + this.cert = cert; + this.digestOID = digestOID; + this.tsaPolicyOID = tsaPolicyOID; + this.unsignedAttr = unsignedAttr; + + TspUtil.ValidateCertificate(cert); + + // + // Add the ESSCertID attribute + // + IDictionary signedAttrs; + if (signedAttr != null) + { + signedAttrs = signedAttr.ToDictionary(); + } + else + { + signedAttrs = Platform.CreateHashtable(); + } + + try + { + byte[] hash = DigestUtilities.CalculateDigest("SHA-1", cert.GetEncoded()); + + EssCertID essCertid = new EssCertID(hash); + + Asn1.Cms.Attribute attr = new Asn1.Cms.Attribute( + PkcsObjectIdentifiers.IdAASigningCertificate, + new DerSet(new SigningCertificate(essCertid))); + + signedAttrs[attr.AttrType] = attr; + } + catch (CertificateEncodingException e) + { + throw new TspException("Exception processing certificate.", e); + } + catch (SecurityUtilityException e) + { + throw new TspException("Can't find a SHA-1 implementation.", e); + } + + this.signedAttr = new Asn1.Cms.AttributeTable(signedAttrs); + } + + public void SetCertificates( + IX509Store certificates) + { + this.x509Certs = certificates; + } + + public void SetCrls( + IX509Store crls) + { + this.x509Crls = crls; + } + + public void SetAccuracySeconds( + int accuracySeconds) + { + this.accuracySeconds = accuracySeconds; + } + + public void SetAccuracyMillis( + int accuracyMillis) + { + this.accuracyMillis = accuracyMillis; + } + + public void SetAccuracyMicros( + int accuracyMicros) + { + this.accuracyMicros = accuracyMicros; + } + + public void SetOrdering( + bool ordering) + { + this.ordering = ordering; + } + + public void SetTsa( + GeneralName tsa) + { + this.tsa = tsa; + } + + //------------------------------------------------------------------------------ + + public TimeStampToken Generate( + TimeStampRequest request, + BigInteger serialNumber, + DateTime genTime) + { + DerObjectIdentifier digestAlgOID = new DerObjectIdentifier(request.MessageImprintAlgOid); + + AlgorithmIdentifier algID = new AlgorithmIdentifier(digestAlgOID, DerNull.Instance); + MessageImprint messageImprint = new MessageImprint(algID, request.GetMessageImprintDigest()); + + Accuracy accuracy = null; + if (accuracySeconds > 0 || accuracyMillis > 0 || accuracyMicros > 0) + { + DerInteger seconds = null; + if (accuracySeconds > 0) + { + seconds = new DerInteger(accuracySeconds); + } + + DerInteger millis = null; + if (accuracyMillis > 0) + { + millis = new DerInteger(accuracyMillis); + } + + DerInteger micros = null; + if (accuracyMicros > 0) + { + micros = new DerInteger(accuracyMicros); + } + + accuracy = new Accuracy(seconds, millis, micros); + } + + DerBoolean derOrdering = null; + if (ordering) + { + derOrdering = DerBoolean.GetInstance(ordering); + } + + DerInteger nonce = null; + if (request.Nonce != null) + { + nonce = new DerInteger(request.Nonce); + } + + DerObjectIdentifier tsaPolicy = new DerObjectIdentifier(tsaPolicyOID); + if (request.ReqPolicy != null) + { + tsaPolicy = new DerObjectIdentifier(request.ReqPolicy); + } + + TstInfo tstInfo = new TstInfo(tsaPolicy, messageImprint, + new DerInteger(serialNumber), new DerGeneralizedTime(genTime), accuracy, + derOrdering, nonce, tsa, request.Extensions); + + try + { + CmsSignedDataGenerator signedDataGenerator = new CmsSignedDataGenerator(); + + byte[] derEncodedTstInfo = tstInfo.GetDerEncoded(); + + if (request.CertReq) + { + signedDataGenerator.AddCertificates(x509Certs); + } + + signedDataGenerator.AddCrls(x509Crls); + signedDataGenerator.AddSigner(key, cert, digestOID, signedAttr, unsignedAttr); + + CmsSignedData signedData = signedDataGenerator.Generate( + PkcsObjectIdentifiers.IdCTTstInfo.Id, + new CmsProcessableByteArray(derEncodedTstInfo), + true); + + return new TimeStampToken(signedData); + } + catch (CmsException cmsEx) + { + throw new TspException("Error generating time-stamp token", cmsEx); + } + catch (IOException e) + { + throw new TspException("Exception encoding info", e); + } + catch (X509StoreException e) + { + throw new TspException("Exception handling CertStore", e); + } +// catch (InvalidAlgorithmParameterException e) +// { +// throw new TspException("Exception handling CertStore CRLs", e); +// } + } + } +} diff --git a/bc-sharp-crypto/src/tsp/TimeStampTokenInfo.cs b/bc-sharp-crypto/src/tsp/TimeStampTokenInfo.cs new file mode 100644 index 0000000..cdef826 --- /dev/null +++ b/bc-sharp-crypto/src/tsp/TimeStampTokenInfo.cs @@ -0,0 +1,107 @@ +using System; + +using Org.BouncyCastle.Asn1.Tsp; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Tsp +{ + public class TimeStampTokenInfo + { + private TstInfo tstInfo; + private DateTime genTime; + + public TimeStampTokenInfo( + TstInfo tstInfo) + { + this.tstInfo = tstInfo; + + try + { + this.genTime = tstInfo.GenTime.ToDateTime(); + } + catch (Exception e) + { + throw new TspException("unable to parse genTime field: " + e.Message); + } + } + + public bool IsOrdered + { + get { return tstInfo.Ordering.IsTrue; } + } + + public Accuracy Accuracy + { + get { return tstInfo.Accuracy; } + } + + public DateTime GenTime + { + get { return genTime; } + } + + public GenTimeAccuracy GenTimeAccuracy + { + get + { + return this.Accuracy == null + ? null + : new GenTimeAccuracy(this.Accuracy); + } + } + + public string Policy + { + get { return tstInfo.Policy.Id; } + } + + public BigInteger SerialNumber + { + get { return tstInfo.SerialNumber.Value; } + } + + public GeneralName Tsa + { + get { return tstInfo.Tsa; } + } + + /** + * @return the nonce value, null if there isn't one. + */ + public BigInteger Nonce + { + get + { + return tstInfo.Nonce == null + ? null + : tstInfo.Nonce.Value; + } + } + + public AlgorithmIdentifier HashAlgorithm + { + get { return tstInfo.MessageImprint.HashAlgorithm; } + } + + public string MessageImprintAlgOid + { + get { return tstInfo.MessageImprint.HashAlgorithm.Algorithm.Id; } + } + + public byte[] GetMessageImprintDigest() + { + return tstInfo.MessageImprint.GetHashedMessage(); + } + + public byte[] GetEncoded() + { + return tstInfo.GetEncoded(); + } + + public TstInfo TstInfo + { + get { return tstInfo; } + } + } +} diff --git a/bc-sharp-crypto/src/util/Arrays.cs b/bc-sharp-crypto/src/util/Arrays.cs new file mode 100644 index 0000000..df9b4e7 --- /dev/null +++ b/bc-sharp-crypto/src/util/Arrays.cs @@ -0,0 +1,704 @@ +using System; +using System.Text; + +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Utilities +{ + /// General array utilities. + public abstract class Arrays + { + public static bool AreEqual( + bool[] a, + bool[] b) + { + if (a == b) + return true; + + if (a == null || b == null) + return false; + + return HaveSameContents(a, b); + } + + public static bool AreEqual( + char[] a, + char[] b) + { + if (a == b) + return true; + + if (a == null || b == null) + return false; + + return HaveSameContents(a, b); + } + + /// + /// Are two arrays equal. + /// + /// Left side. + /// Right side. + /// True if equal. + public static bool AreEqual( + byte[] a, + byte[] b) + { + if (a == b) + return true; + + if (a == null || b == null) + return false; + + return HaveSameContents(a, b); + } + + [Obsolete("Use 'AreEqual' method instead")] + public static bool AreSame( + byte[] a, + byte[] b) + { + return AreEqual(a, b); + } + + /// + /// A constant time equals comparison - does not terminate early if + /// test will fail. + /// + /// first array + /// second array + /// true if arrays equal, false otherwise. + public static bool ConstantTimeAreEqual( + byte[] a, + byte[] b) + { + int i = a.Length; + if (i != b.Length) + return false; + int cmp = 0; + while (i != 0) + { + --i; + cmp |= (a[i] ^ b[i]); + } + return cmp == 0; + } + + public static bool AreEqual( + int[] a, + int[] b) + { + if (a == b) + return true; + + if (a == null || b == null) + return false; + + return HaveSameContents(a, b); + } + + [CLSCompliantAttribute(false)] + public static bool AreEqual(uint[] a, uint[] b) + { + if (a == b) + return true; + + if (a == null || b == null) + return false; + + return HaveSameContents(a, b); + } + + private static bool HaveSameContents( + bool[] a, + bool[] b) + { + int i = a.Length; + if (i != b.Length) + return false; + while (i != 0) + { + --i; + if (a[i] != b[i]) + return false; + } + return true; + } + + private static bool HaveSameContents( + char[] a, + char[] b) + { + int i = a.Length; + if (i != b.Length) + return false; + while (i != 0) + { + --i; + if (a[i] != b[i]) + return false; + } + return true; + } + + private static bool HaveSameContents( + byte[] a, + byte[] b) + { + int i = a.Length; + if (i != b.Length) + return false; + while (i != 0) + { + --i; + if (a[i] != b[i]) + return false; + } + return true; + } + + private static bool HaveSameContents( + int[] a, + int[] b) + { + int i = a.Length; + if (i != b.Length) + return false; + while (i != 0) + { + --i; + if (a[i] != b[i]) + return false; + } + return true; + } + + private static bool HaveSameContents(uint[] a, uint[] b) + { + int i = a.Length; + if (i != b.Length) + return false; + while (i != 0) + { + --i; + if (a[i] != b[i]) + return false; + } + return true; + } + + public static string ToString( + object[] a) + { + StringBuilder sb = new StringBuilder('['); + if (a.Length > 0) + { + sb.Append(a[0]); + for (int index = 1; index < a.Length; ++index) + { + sb.Append(", ").Append(a[index]); + } + } + sb.Append(']'); + return sb.ToString(); + } + + public static int GetHashCode(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 int GetHashCode(byte[] data, int off, int len) + { + if (data == null) + { + return 0; + } + + int i = len; + int hc = i + 1; + + while (--i >= 0) + { + hc *= 257; + hc ^= data[off + i]; + } + + return hc; + } + + public static int GetHashCode(int[] data) + { + if (data == null) + return 0; + + int i = data.Length; + int hc = i + 1; + + while (--i >= 0) + { + hc *= 257; + hc ^= data[i]; + } + + return hc; + } + + public static int GetHashCode(int[] data, int off, int len) + { + if (data == null) + return 0; + + int i = len; + int hc = i + 1; + + while (--i >= 0) + { + hc *= 257; + hc ^= data[off + i]; + } + + return hc; + } + + [CLSCompliantAttribute(false)] + public static int GetHashCode(uint[] data) + { + if (data == null) + return 0; + + int i = data.Length; + int hc = i + 1; + + while (--i >= 0) + { + hc *= 257; + hc ^= (int)data[i]; + } + + return hc; + } + + [CLSCompliantAttribute(false)] + public static int GetHashCode(uint[] data, int off, int len) + { + if (data == null) + return 0; + + int i = len; + int hc = i + 1; + + while (--i >= 0) + { + hc *= 257; + hc ^= (int)data[off + i]; + } + + return hc; + } + + [CLSCompliantAttribute(false)] + public static int GetHashCode(ulong[] data) + { + if (data == null) + return 0; + + int i = data.Length; + int hc = i + 1; + + while (--i >= 0) + { + ulong di = data[i]; + hc *= 257; + hc ^= (int)di; + hc *= 257; + hc ^= (int)(di >> 32); + } + + return hc; + } + + [CLSCompliantAttribute(false)] + public static int GetHashCode(ulong[] data, int off, int len) + { + if (data == null) + return 0; + + int i = len; + int hc = i + 1; + + while (--i >= 0) + { + ulong di = data[off + i]; + hc *= 257; + hc ^= (int)di; + hc *= 257; + hc ^= (int)(di >> 32); + } + + return hc; + } + + public static byte[] Clone( + byte[] data) + { + return data == null ? null : (byte[])data.Clone(); + } + + public static byte[] Clone( + byte[] data, + byte[] existing) + { + if (data == null) + { + return null; + } + if ((existing == null) || (existing.Length != data.Length)) + { + return Clone(data); + } + Array.Copy(data, 0, existing, 0, existing.Length); + return existing; + } + + public static int[] Clone( + int[] data) + { + return data == null ? null : (int[])data.Clone(); + } + + internal static uint[] Clone(uint[] data) + { + return data == null ? null : (uint[])data.Clone(); + } + + public static long[] Clone(long[] data) + { + return data == null ? null : (long[])data.Clone(); + } + + [CLSCompliantAttribute(false)] + public static ulong[] Clone( + ulong[] data) + { + return data == null ? null : (ulong[]) data.Clone(); + } + + [CLSCompliantAttribute(false)] + public static ulong[] Clone( + ulong[] data, + ulong[] existing) + { + if (data == null) + { + return null; + } + if ((existing == null) || (existing.Length != data.Length)) + { + return Clone(data); + } + Array.Copy(data, 0, existing, 0, existing.Length); + return existing; + } + + public static bool Contains(byte[] a, byte n) + { + for (int i = 0; i < a.Length; ++i) + { + if (a[i] == n) + return true; + } + return false; + } + + public static bool Contains(short[] a, short n) + { + for (int i = 0; i < a.Length; ++i) + { + if (a[i] == n) + return true; + } + return false; + } + + public static bool Contains(int[] a, int n) + { + for (int i = 0; i < a.Length; ++i) + { + if (a[i] == n) + return true; + } + return false; + } + + public static void Fill( + byte[] buf, + byte b) + { + int i = buf.Length; + while (i > 0) + { + buf[--i] = b; + } + } + + public static byte[] CopyOf(byte[] data, int newLength) + { + byte[] tmp = new byte[newLength]; + Array.Copy(data, 0, tmp, 0, System.Math.Min(newLength, data.Length)); + return tmp; + } + + public static char[] CopyOf(char[] data, int newLength) + { + char[] tmp = new char[newLength]; + Array.Copy(data, 0, tmp, 0, System.Math.Min(newLength, data.Length)); + return tmp; + } + + public static int[] CopyOf(int[] data, int newLength) + { + int[] tmp = new int[newLength]; + Array.Copy(data, 0, tmp, 0, System.Math.Min(newLength, data.Length)); + return tmp; + } + + public static long[] CopyOf(long[] data, int newLength) + { + long[] tmp = new long[newLength]; + Array.Copy(data, 0, tmp, 0, System.Math.Min(newLength, data.Length)); + return tmp; + } + + public static BigInteger[] CopyOf(BigInteger[] data, int newLength) + { + BigInteger[] tmp = new BigInteger[newLength]; + Array.Copy(data, 0, tmp, 0, System.Math.Min(newLength, data.Length)); + return tmp; + } + + /** + * Make a copy of a range of bytes from the passed in data array. The range can + * extend beyond the end of the input array, in which case the return array will + * be padded with zeroes. + * + * @param data the array from which the data is to be copied. + * @param from the start index at which the copying should take place. + * @param to the final index of the range (exclusive). + * + * @return a new byte array containing the range given. + */ + public static byte[] CopyOfRange(byte[] data, int from, int to) + { + int newLength = GetLength(from, to); + byte[] tmp = new byte[newLength]; + Array.Copy(data, from, tmp, 0, System.Math.Min(newLength, data.Length - from)); + return tmp; + } + + public static int[] CopyOfRange(int[] data, int from, int to) + { + int newLength = GetLength(from, to); + int[] tmp = new int[newLength]; + Array.Copy(data, from, tmp, 0, System.Math.Min(newLength, data.Length - from)); + return tmp; + } + + public static long[] CopyOfRange(long[] data, int from, int to) + { + int newLength = GetLength(from, to); + long[] tmp = new long[newLength]; + Array.Copy(data, from, tmp, 0, System.Math.Min(newLength, data.Length - from)); + return tmp; + } + + public static BigInteger[] CopyOfRange(BigInteger[] data, int from, int to) + { + int newLength = GetLength(from, to); + BigInteger[] tmp = new BigInteger[newLength]; + Array.Copy(data, from, tmp, 0, System.Math.Min(newLength, data.Length - from)); + return tmp; + } + + private static int GetLength(int from, int to) + { + int newLength = to - from; + if (newLength < 0) + throw new ArgumentException(from + " > " + to); + return newLength; + } + + public static byte[] Append(byte[] a, byte b) + { + if (a == null) + return new byte[] { b }; + + int length = a.Length; + byte[] result = new byte[length + 1]; + Array.Copy(a, 0, result, 0, length); + result[length] = b; + return result; + } + + public static short[] Append(short[] a, short b) + { + if (a == null) + return new short[] { b }; + + int length = a.Length; + short[] result = new short[length + 1]; + Array.Copy(a, 0, result, 0, length); + result[length] = b; + return result; + } + + public static int[] Append(int[] a, int b) + { + if (a == null) + return new int[] { b }; + + int length = a.Length; + int[] result = new int[length + 1]; + Array.Copy(a, 0, result, 0, length); + result[length] = b; + return result; + } + + public static byte[] Concatenate(byte[] a, byte[] b) + { + if (a == null) + return Clone(b); + if (b == null) + return Clone(a); + + byte[] rv = new byte[a.Length + b.Length]; + Array.Copy(a, 0, rv, 0, a.Length); + Array.Copy(b, 0, rv, a.Length, b.Length); + return rv; + } + + public static byte[] ConcatenateAll(params byte[][] vs) + { + byte[][] nonNull = new byte[vs.Length][]; + int count = 0; + int totalLength = 0; + + for (int i = 0; i < vs.Length; ++i) + { + byte[] v = vs[i]; + if (v != null) + { + nonNull[count++] = v; + totalLength += v.Length; + } + } + + byte[] result = new byte[totalLength]; + int pos = 0; + + for (int j = 0; j < count; ++j) + { + byte[] v = nonNull[j]; + Array.Copy(v, 0, result, pos, v.Length); + pos += v.Length; + } + + return result; + } + + public static int[] Concatenate(int[] a, int[] b) + { + if (a == null) + return Clone(b); + if (b == null) + return Clone(a); + + int[] rv = new int[a.Length + b.Length]; + Array.Copy(a, 0, rv, 0, a.Length); + Array.Copy(b, 0, rv, a.Length, b.Length); + return rv; + } + + public static byte[] Prepend(byte[] a, byte b) + { + if (a == null) + return new byte[] { b }; + + int length = a.Length; + byte[] result = new byte[length + 1]; + Array.Copy(a, 0, result, 1, length); + result[0] = b; + return result; + } + + public static short[] Prepend(short[] a, short b) + { + if (a == null) + return new short[] { b }; + + int length = a.Length; + short[] result = new short[length + 1]; + Array.Copy(a, 0, result, 1, length); + result[0] = b; + return result; + } + + public static int[] Prepend(int[] a, int b) + { + if (a == null) + return new int[] { b }; + + int length = a.Length; + int[] result = new int[length + 1]; + Array.Copy(a, 0, result, 1, length); + result[0] = b; + return result; + } + + public static byte[] Reverse(byte[] a) + { + if (a == null) + return null; + + int p1 = 0, p2 = a.Length; + byte[] result = new byte[p2]; + + while (--p2 >= 0) + { + result[p2] = a[p1++]; + } + + return result; + } + + public static int[] Reverse(int[] a) + { + if (a == null) + return null; + + int p1 = 0, p2 = a.Length; + int[] result = new int[p2]; + + while (--p2 >= 0) + { + result[p2] = a[p1++]; + } + + return result; + } + } +} diff --git a/bc-sharp-crypto/src/util/BigIntegers.cs b/bc-sharp-crypto/src/util/BigIntegers.cs new file mode 100644 index 0000000..f2d0425 --- /dev/null +++ b/bc-sharp-crypto/src/util/BigIntegers.cs @@ -0,0 +1,90 @@ +using System; + +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Utilities +{ + /** + * BigInteger utilities. + */ + public abstract class BigIntegers + { + private const int MaxIterations = 1000; + + /** + * 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 n) + { + return n.ToByteArrayUnsigned(); + } + + /** + * Return the passed in value as an unsigned byte array of specified length, zero-extended as necessary. + * + * @param length desired length of result array. + * @param n value to be converted. + * @return a byte array of specified length, with leading zeroes as necessary given the size of n. + */ + public static byte[] AsUnsignedByteArray(int length, BigInteger n) + { + byte[] bytes = n.ToByteArrayUnsigned(); + + if (bytes.Length > length) + throw new ArgumentException("standard length exceeded", "n"); + + if (bytes.Length == length) + return bytes; + + byte[] tmp = new byte[length]; + Array.Copy(bytes, 0, tmp, tmp.Length - bytes.Length, bytes.Length); + return tmp; + } + + /** + * Return a random BigInteger not less than 'min' and not greater than 'max' + * + * @param min the least value that may be generated + * @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, + // TODO Should have been just Random class + SecureRandom random) + { + int cmp = min.CompareTo(max); + if (cmp >= 0) + { + if (cmp > 0) + throw new ArgumentException("'min' may not be greater than 'max'"); + + return min; + } + + if (min.BitLength > max.BitLength / 2) + { + return CreateRandomInRange(BigInteger.Zero, max.Subtract(min), random).Add(min); + } + + for (int i = 0; i < MaxIterations; ++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/bc-sharp-crypto/src/util/Enums.cs b/bc-sharp-crypto/src/util/Enums.cs new file mode 100644 index 0000000..9e908c4 --- /dev/null +++ b/bc-sharp-crypto/src/util/Enums.cs @@ -0,0 +1,78 @@ +using System; +using System.Text; + +#if NETCF_1_0 || NETCF_2_0 || SILVERLIGHT || PORTABLE +using System.Collections; +using System.Reflection; +#endif + +using Org.BouncyCastle.Utilities.Date; + +namespace Org.BouncyCastle.Utilities +{ + internal abstract class Enums + { + internal static Enum GetEnumValue(System.Type enumType, string s) + { + if (!IsEnumType(enumType)) + throw new ArgumentException("Not an enumeration type", "enumType"); + + // We only want to parse single named constants + if (s.Length > 0 && char.IsLetter(s[0]) && s.IndexOf(',') < 0) + { + s = s.Replace('-', '_'); + s = s.Replace('/', '_'); + +#if NETCF_1_0 + FieldInfo field = enumType.GetField(s, BindingFlags.Static | BindingFlags.Public); + if (field != null) + { + return (Enum)field.GetValue(null); + } +#else + return (Enum)Enum.Parse(enumType, s, false); +#endif + } + + throw new ArgumentException(); + } + + internal static Array GetEnumValues(System.Type enumType) + { + if (!IsEnumType(enumType)) + throw new ArgumentException("Not an enumeration type", "enumType"); + +#if NETCF_1_0 || NETCF_2_0 || SILVERLIGHT + IList result = Platform.CreateArrayList(); + FieldInfo[] fields = enumType.GetFields(BindingFlags.Static | BindingFlags.Public); + foreach (FieldInfo field in fields) + { + // Note: Argument to GetValue() ignored since the fields are static, + // but Silverlight for Windows Phone throws exception if we pass null + result.Add(field.GetValue(enumType)); + } + object[] arr = new object[result.Count]; + result.CopyTo(arr, 0); + return arr; +#else + return Enum.GetValues(enumType); +#endif + } + + internal static Enum GetArbitraryValue(System.Type enumType) + { + Array values = GetEnumValues(enumType); + int pos = (int)(DateTimeUtilities.CurrentUnixMs() & int.MaxValue) % values.Length; + return (Enum)values.GetValue(pos); + } + + internal static bool IsEnumType(System.Type t) + { +#if NEW_REFLECTION + return t.GetTypeInfo().IsEnum; +#else + return t.IsEnum; +#endif + } + } +} diff --git a/bc-sharp-crypto/src/util/IMemoable.cs b/bc-sharp-crypto/src/util/IMemoable.cs new file mode 100644 index 0000000..cc8a2e5 --- /dev/null +++ b/bc-sharp-crypto/src/util/IMemoable.cs @@ -0,0 +1,29 @@ +using System; + +namespace Org.BouncyCastle.Utilities +{ + public interface IMemoable + { + /// + /// Produce a copy of this object with its configuration and in its current state. + /// + /// + /// The returned object may be used simply to store the state, or may be used as a similar object + /// starting from the copied state. + /// + IMemoable Copy(); + + /// + /// Restore a copied object state into this object. + /// + /// + /// Implementations of this method should try to avoid or minimise memory allocation to perform the reset. + /// + /// an object originally {@link #copy() copied} from an object of the same type as this instance. + /// if the provided object is not of the correct type. + /// if the other parameter is in some other way invalid. + void Reset(IMemoable other); + } + +} + diff --git a/bc-sharp-crypto/src/util/Integers.cs b/bc-sharp-crypto/src/util/Integers.cs new file mode 100644 index 0000000..ccbf872 --- /dev/null +++ b/bc-sharp-crypto/src/util/Integers.cs @@ -0,0 +1,17 @@ +using System; + +namespace Org.BouncyCastle.Utilities +{ + public abstract class Integers + { + public static int RotateLeft(int i, int distance) + { + return (i << distance) ^ (int)((uint)i >> -distance); + } + + public static int RotateRight(int i, int distance) + { + return (int)((uint)i >> distance) ^ (i << -distance); + } + } +} diff --git a/bc-sharp-crypto/src/util/MemoableResetException.cs b/bc-sharp-crypto/src/util/MemoableResetException.cs new file mode 100644 index 0000000..99554f6 --- /dev/null +++ b/bc-sharp-crypto/src/util/MemoableResetException.cs @@ -0,0 +1,27 @@ +using System; + +namespace Org.BouncyCastle.Utilities +{ + /** + * Exception to be thrown on a failure to reset an object implementing Memoable. + *

    + * The exception extends InvalidCastException to enable users to have a single handling case, + * only introducing specific handling of this one if required. + *

    + */ + public class MemoableResetException + : InvalidCastException + { + /** + * Basic Constructor. + * + * @param msg message to be associated with this exception. + */ + public MemoableResetException(string msg) + : base(msg) + { + } + } + +} + diff --git a/bc-sharp-crypto/src/util/Platform.cs b/bc-sharp-crypto/src/util/Platform.cs new file mode 100644 index 0000000..8648485 --- /dev/null +++ b/bc-sharp-crypto/src/util/Platform.cs @@ -0,0 +1,229 @@ +using System; +using System.Globalization; +using System.IO; +using System.Text; + +#if SILVERLIGHT || PORTABLE +using System.Collections.Generic; +#else +using System.Collections; +#endif + +namespace Org.BouncyCastle.Utilities +{ + internal abstract class Platform + { + private static readonly CompareInfo InvariantCompareInfo = CultureInfo.InvariantCulture.CompareInfo; + +#if NETCF_1_0 || NETCF_2_0 + private static string GetNewLine() + { + MemoryStream buf = new MemoryStream(); + StreamWriter w = new StreamWriter(buf, Encoding.UTF8); + w.WriteLine(); + Dispose(w); + byte[] bs = buf.ToArray(); + return Encoding.UTF8.GetString(bs, 0, bs.Length); + } +#else + private static string GetNewLine() + { + return Environment.NewLine; + } +#endif + + internal static bool EqualsIgnoreCase(string a, string b) + { +#if PORTABLE + return String.Equals(a, b, StringComparison.OrdinalIgnoreCase); +#else + return ToUpperInvariant(a) == ToUpperInvariant(b); +#endif + } + +#if NETCF_1_0 || NETCF_2_0 || SILVERLIGHT || PORTABLE + internal static string GetEnvironmentVariable( + string variable) + { + return null; + } +#else + internal static string GetEnvironmentVariable( + string variable) + { + try + { + return Environment.GetEnvironmentVariable(variable); + } + catch (System.Security.SecurityException) + { + // We don't have the required permission to read this environment variable, + // which is fine, just act as if it's not set + return null; + } + } +#endif + +#if NETCF_1_0 + internal static Exception CreateNotImplementedException( + string message) + { + return new Exception("Not implemented: " + message); + } + + internal static bool Equals( + object a, + object b) + { + return a == b || (a != null && b != null && a.Equals(b)); + } +#else + internal static Exception CreateNotImplementedException( + string message) + { + return new NotImplementedException(message); + } +#endif + +#if SILVERLIGHT || PORTABLE + internal static System.Collections.IList CreateArrayList() + { + return new List(); + } + internal static System.Collections.IList CreateArrayList(int capacity) + { + return new List(capacity); + } + internal static System.Collections.IList CreateArrayList(System.Collections.ICollection collection) + { + System.Collections.IList result = new List(collection.Count); + foreach (object o in collection) + { + result.Add(o); + } + return result; + } + internal static System.Collections.IList CreateArrayList(System.Collections.IEnumerable collection) + { + System.Collections.IList result = new List(); + foreach (object o in collection) + { + result.Add(o); + } + return result; + } + internal static System.Collections.IDictionary CreateHashtable() + { + return new Dictionary(); + } + internal static System.Collections.IDictionary CreateHashtable(int capacity) + { + return new Dictionary(capacity); + } + internal static System.Collections.IDictionary CreateHashtable(System.Collections.IDictionary dictionary) + { + System.Collections.IDictionary result = new Dictionary(dictionary.Count); + foreach (System.Collections.DictionaryEntry entry in dictionary) + { + result.Add(entry.Key, entry.Value); + } + return result; + } +#else + internal static System.Collections.IList CreateArrayList() + { + return new ArrayList(); + } + internal static System.Collections.IList CreateArrayList(int capacity) + { + return new ArrayList(capacity); + } + internal static System.Collections.IList CreateArrayList(System.Collections.ICollection collection) + { + return new ArrayList(collection); + } + internal static System.Collections.IList CreateArrayList(System.Collections.IEnumerable collection) + { + ArrayList result = new ArrayList(); + foreach (object o in collection) + { + result.Add(o); + } + return result; + } + internal static System.Collections.IDictionary CreateHashtable() + { + return new Hashtable(); + } + internal static System.Collections.IDictionary CreateHashtable(int capacity) + { + return new Hashtable(capacity); + } + internal static System.Collections.IDictionary CreateHashtable(System.Collections.IDictionary dictionary) + { + return new Hashtable(dictionary); + } +#endif + + internal static string ToLowerInvariant(string s) + { +#if PORTABLE + return s.ToLowerInvariant(); +#else + return s.ToLower(CultureInfo.InvariantCulture); +#endif + } + + internal static string ToUpperInvariant(string s) + { +#if PORTABLE + return s.ToUpperInvariant(); +#else + return s.ToUpper(CultureInfo.InvariantCulture); +#endif + } + + internal static readonly string NewLine = GetNewLine(); + +#if PORTABLE + internal static void Dispose(IDisposable d) + { + d.Dispose(); + } +#else + internal static void Dispose(Stream s) + { + s.Close(); + } + internal static void Dispose(TextWriter t) + { + t.Close(); + } +#endif + + internal static int IndexOf(string source, string value) + { + return InvariantCompareInfo.IndexOf(source, value, CompareOptions.Ordinal); + } + + internal static int LastIndexOf(string source, string value) + { + return InvariantCompareInfo.LastIndexOf(source, value, CompareOptions.Ordinal); + } + + internal static bool StartsWith(string source, string prefix) + { + return InvariantCompareInfo.IsPrefix(source, prefix, CompareOptions.Ordinal); + } + + internal static bool EndsWith(string source, string suffix) + { + return InvariantCompareInfo.IsSuffix(source, suffix, CompareOptions.Ordinal); + } + + internal static string GetTypeName(object obj) + { + return obj.GetType().FullName; + } + } +} diff --git a/bc-sharp-crypto/src/util/Strings.cs b/bc-sharp-crypto/src/util/Strings.cs new file mode 100644 index 0000000..3937a08 --- /dev/null +++ b/bc-sharp-crypto/src/util/Strings.cs @@ -0,0 +1,103 @@ +using System; +using System.Text; + +namespace Org.BouncyCastle.Utilities +{ + /// General string utilities. + public abstract class Strings + { + internal static bool IsOneOf(string s, params string[] candidates) + { + foreach (string candidate in candidates) + { + if (s == candidate) + return true; + } + return false; + } + + public static string FromByteArray( + byte[] bs) + { + char[] cs = new char[bs.Length]; + for (int i = 0; i < cs.Length; ++i) + { + cs[i] = Convert.ToChar(bs[i]); + } + return new string(cs); + } + + public static byte[] ToByteArray( + char[] cs) + { + byte[] bs = new byte[cs.Length]; + for (int i = 0; i < bs.Length; ++i) + { + bs[i] = Convert.ToByte(cs[i]); + } + return bs; + } + + public static byte[] ToByteArray( + string s) + { + byte[] bs = new byte[s.Length]; + for (int i = 0; i < bs.Length; ++i) + { + bs[i] = Convert.ToByte(s[i]); + } + return bs; + } + + public static string FromAsciiByteArray( + byte[] bytes) + { +#if SILVERLIGHT || PORTABLE + // TODO Check for non-ASCII bytes in input? + return Encoding.UTF8.GetString(bytes, 0, bytes.Length); +#else + return Encoding.ASCII.GetString(bytes, 0, bytes.Length); +#endif + } + + public static byte[] ToAsciiByteArray( + char[] cs) + { +#if SILVERLIGHT || PORTABLE + // TODO Check for non-ASCII characters in input? + return Encoding.UTF8.GetBytes(cs); +#else + return Encoding.ASCII.GetBytes(cs); +#endif + } + + public static byte[] ToAsciiByteArray( + string s) + { +#if SILVERLIGHT || PORTABLE + // TODO Check for non-ASCII characters in input? + return Encoding.UTF8.GetBytes(s); +#else + return Encoding.ASCII.GetBytes(s); +#endif + } + + public static string FromUtf8ByteArray( + byte[] bytes) + { + return Encoding.UTF8.GetString(bytes, 0, bytes.Length); + } + + public static byte[] ToUtf8ByteArray( + char[] cs) + { + return Encoding.UTF8.GetBytes(cs); + } + + public static byte[] ToUtf8ByteArray( + string s) + { + return Encoding.UTF8.GetBytes(s); + } + } +} diff --git a/bc-sharp-crypto/src/util/Times.cs b/bc-sharp-crypto/src/util/Times.cs new file mode 100644 index 0000000..99a78d2 --- /dev/null +++ b/bc-sharp-crypto/src/util/Times.cs @@ -0,0 +1,14 @@ +using System; + +namespace Org.BouncyCastle.Utilities +{ + public sealed class Times + { + private static long NanosecondsPerTick = 100L; + + public static long NanoTime() + { + return DateTime.UtcNow.Ticks * NanosecondsPerTick; + } + } +} diff --git a/bc-sharp-crypto/src/util/TypeExtensions.cs b/bc-sharp-crypto/src/util/TypeExtensions.cs new file mode 100644 index 0000000..e2aeae4 --- /dev/null +++ b/bc-sharp-crypto/src/util/TypeExtensions.cs @@ -0,0 +1,17 @@ +#if NEW_REFLECTION + +using System; +using System.Reflection; + +namespace Org.BouncyCastle +{ + internal static class TypeExtensions + { + public static bool IsInstanceOfType(this Type type, object instance) + { + return instance != null && type.GetTypeInfo().IsAssignableFrom(instance.GetType().GetTypeInfo()); + } + } +} + +#endif diff --git a/bc-sharp-crypto/src/util/collections/CollectionUtilities.cs b/bc-sharp-crypto/src/util/collections/CollectionUtilities.cs new file mode 100644 index 0000000..18fcb67 --- /dev/null +++ b/bc-sharp-crypto/src/util/collections/CollectionUtilities.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections; +using System.Text; + +namespace Org.BouncyCastle.Utilities.Collections +{ + public abstract class CollectionUtilities + { + public static void AddRange(IList to, IEnumerable range) + { + foreach (object o in range) + { + to.Add(o); + } + } + + public static bool CheckElementsAreOfType(IEnumerable e, Type t) + { + foreach (object o in e) + { + if (!t.IsInstanceOfType(o)) + return false; + } + return true; + } + + public static IDictionary ReadOnly(IDictionary d) + { + return new UnmodifiableDictionaryProxy(d); + } + + public static IList ReadOnly(IList l) + { + return new UnmodifiableListProxy(l); + } + + public static ISet ReadOnly(ISet s) + { + return new UnmodifiableSetProxy(s); + } + + public static string ToString(IEnumerable c) + { + StringBuilder sb = new StringBuilder("["); + + IEnumerator e = c.GetEnumerator(); + + if (e.MoveNext()) + { + sb.Append(e.Current.ToString()); + + while (e.MoveNext()) + { + sb.Append(", "); + sb.Append(e.Current.ToString()); + } + } + + sb.Append(']'); + + return sb.ToString(); + } + } +} diff --git a/bc-sharp-crypto/src/util/collections/EmptyEnumerable.cs b/bc-sharp-crypto/src/util/collections/EmptyEnumerable.cs new file mode 100644 index 0000000..a61a078 --- /dev/null +++ b/bc-sharp-crypto/src/util/collections/EmptyEnumerable.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections; + +namespace Org.BouncyCastle.Utilities.Collections +{ + public sealed class EmptyEnumerable + : IEnumerable + { + public static readonly IEnumerable Instance = new EmptyEnumerable(); + + private EmptyEnumerable() + { + } + + public IEnumerator GetEnumerator() + { + return EmptyEnumerator.Instance; + } + } + + public sealed class EmptyEnumerator + : IEnumerator + { + public static readonly IEnumerator Instance = new EmptyEnumerator(); + + private EmptyEnumerator() + { + } + + public bool MoveNext() + { + return false; + } + + public void Reset() + { + } + + public object Current + { + get { throw new InvalidOperationException("No elements"); } + } + } +} diff --git a/bc-sharp-crypto/src/util/collections/EnumerableProxy.cs b/bc-sharp-crypto/src/util/collections/EnumerableProxy.cs new file mode 100644 index 0000000..9eec4af --- /dev/null +++ b/bc-sharp-crypto/src/util/collections/EnumerableProxy.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections; + +namespace Org.BouncyCastle.Utilities.Collections +{ + public sealed class EnumerableProxy + : IEnumerable + { + private readonly IEnumerable inner; + + public EnumerableProxy( + IEnumerable inner) + { + if (inner == null) + throw new ArgumentNullException("inner"); + + this.inner = inner; + } + + public IEnumerator GetEnumerator() + { + return inner.GetEnumerator(); + } + } +} diff --git a/bc-sharp-crypto/src/util/collections/HashSet.cs b/bc-sharp-crypto/src/util/collections/HashSet.cs new file mode 100644 index 0000000..1facb58 --- /dev/null +++ b/bc-sharp-crypto/src/util/collections/HashSet.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections; + +namespace Org.BouncyCastle.Utilities.Collections +{ + public class HashSet + : ISet + { + private readonly IDictionary impl = Platform.CreateHashtable(); + + public HashSet() + { + } + + public HashSet(IEnumerable s) + { + foreach (object o in s) + { + Add(o); + } + } + + public virtual void Add(object o) + { + impl[o] = null; + } + + public virtual void AddAll(IEnumerable e) + { + foreach (object o in e) + { + Add(o); + } + } + + public virtual void Clear() + { + impl.Clear(); + } + + public virtual bool Contains(object o) + { + return impl.Contains(o); + } + + public virtual void CopyTo(Array array, int index) + { + impl.Keys.CopyTo(array, index); + } + + public virtual int Count + { + get { return impl.Count; } + } + + public virtual IEnumerator GetEnumerator() + { + return impl.Keys.GetEnumerator(); + } + + public virtual bool IsEmpty + { + get { return impl.Count == 0; } + } + + public virtual bool IsFixedSize + { + get { return impl.IsFixedSize; } + } + + public virtual bool IsReadOnly + { + get { return impl.IsReadOnly; } + } + + public virtual bool IsSynchronized + { + get { return impl.IsSynchronized; } + } + + public virtual void Remove(object o) + { + impl.Remove(o); + } + + public virtual void RemoveAll(IEnumerable e) + { + foreach (object o in e) + { + Remove(o); + } + } + + public virtual object SyncRoot + { + get { return impl.SyncRoot; } + } + } +} diff --git a/bc-sharp-crypto/src/util/collections/ISet.cs b/bc-sharp-crypto/src/util/collections/ISet.cs new file mode 100644 index 0000000..1f8edba --- /dev/null +++ b/bc-sharp-crypto/src/util/collections/ISet.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections; + +namespace Org.BouncyCastle.Utilities.Collections +{ + public interface ISet + : ICollection + { + void Add(object o); + void AddAll(IEnumerable e); + void Clear(); + bool Contains(object o); + bool IsEmpty { get; } + bool IsFixedSize { get; } + bool IsReadOnly { get; } + void Remove(object o); + void RemoveAll(IEnumerable e); + } +} diff --git a/bc-sharp-crypto/src/util/collections/LinkedDictionary.cs b/bc-sharp-crypto/src/util/collections/LinkedDictionary.cs new file mode 100644 index 0000000..933d38d --- /dev/null +++ b/bc-sharp-crypto/src/util/collections/LinkedDictionary.cs @@ -0,0 +1,178 @@ +using System; +using System.Collections; + +namespace Org.BouncyCastle.Utilities.Collections +{ + public class LinkedDictionary + : IDictionary + { + internal readonly IDictionary hash = Platform.CreateHashtable(); + internal readonly IList keys = Platform.CreateArrayList(); + + public LinkedDictionary() + { + } + + public virtual void Add(object k, object v) + { + hash.Add(k, v); + keys.Add(k); + } + + public virtual void Clear() + { + hash.Clear(); + keys.Clear(); + } + + public virtual bool Contains(object k) + { + return hash.Contains(k); + } + + public virtual void CopyTo(Array array, int index) + { + foreach (object k in keys) + { + array.SetValue(hash[k], index++); + } + } + + public virtual int Count + { + get { return hash.Count; } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public virtual IDictionaryEnumerator GetEnumerator() + { + return new LinkedDictionaryEnumerator(this); + } + + public virtual void Remove(object k) + { + hash.Remove(k); + keys.Remove(k); + } + + public virtual bool IsFixedSize + { + get { return false; } + } + + public virtual bool IsReadOnly + { + get { return false; } + } + + public virtual bool IsSynchronized + { + get { return false; } + } + + public virtual object SyncRoot + { + get { return false; } + } + + public virtual ICollection Keys + { + get { return Platform.CreateArrayList(keys); } + } + + public virtual ICollection Values + { + // NB: Order has to be the same as for Keys property + get + { + IList values = Platform.CreateArrayList(keys.Count); + foreach (object k in keys) + { + values.Add(hash[k]); + } + return values; + } + } + + public virtual object this[object k] + { + get + { + return hash[k]; + } + set + { + if (!hash.Contains(k)) + keys.Add(k); + hash[k] = value; + } + } + } + + internal class LinkedDictionaryEnumerator : IDictionaryEnumerator + { + private readonly LinkedDictionary parent; + private int pos = -1; + + internal LinkedDictionaryEnumerator(LinkedDictionary parent) + { + this.parent = parent; + } + + public virtual object Current + { + get { return Entry; } + } + + public virtual DictionaryEntry Entry + { + get + { + object k = CurrentKey; + return new DictionaryEntry(k, parent.hash[k]); + } + } + + public virtual object Key + { + get + { + return CurrentKey; + } + } + + public virtual bool MoveNext() + { + if (pos >= parent.keys.Count) + return false; + return ++pos < parent.keys.Count; + } + + public virtual void Reset() + { + this.pos = -1; + } + + public virtual object Value + { + get + { + return parent.hash[CurrentKey]; + } + } + + private object CurrentKey + { + get + { + if (pos < 0 || pos >= parent.keys.Count) + throw new InvalidOperationException(); + return parent.keys[pos]; + } + } + } +} diff --git a/bc-sharp-crypto/src/util/collections/UnmodifiableDictionary.cs b/bc-sharp-crypto/src/util/collections/UnmodifiableDictionary.cs new file mode 100644 index 0000000..0bdf70a --- /dev/null +++ b/bc-sharp-crypto/src/util/collections/UnmodifiableDictionary.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections; + +namespace Org.BouncyCastle.Utilities.Collections +{ + public abstract class UnmodifiableDictionary + : IDictionary + { + protected UnmodifiableDictionary() + { + } + + public virtual void Add(object k, object v) + { + throw new NotSupportedException(); + } + + public virtual void Clear() + { + throw new NotSupportedException(); + } + + public abstract bool Contains(object k); + + public abstract void CopyTo(Array array, int index); + + public abstract int Count { get; } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public abstract IDictionaryEnumerator GetEnumerator(); + + public virtual void Remove(object k) + { + throw new NotSupportedException(); + } + + public abstract bool IsFixedSize { get; } + + public virtual bool IsReadOnly + { + get { return true; } + } + + public abstract bool IsSynchronized { get; } + + public abstract object SyncRoot { get; } + + public abstract ICollection Keys { get; } + + public abstract ICollection Values { get; } + + public virtual object this[object k] + { + get { return GetValue(k); } + set { throw new NotSupportedException(); } + } + + protected abstract object GetValue(object k); + } +} diff --git a/bc-sharp-crypto/src/util/collections/UnmodifiableDictionaryProxy.cs b/bc-sharp-crypto/src/util/collections/UnmodifiableDictionaryProxy.cs new file mode 100644 index 0000000..0fca909 --- /dev/null +++ b/bc-sharp-crypto/src/util/collections/UnmodifiableDictionaryProxy.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections; + +namespace Org.BouncyCastle.Utilities.Collections +{ + public class UnmodifiableDictionaryProxy + : UnmodifiableDictionary + { + private readonly IDictionary d; + + public UnmodifiableDictionaryProxy(IDictionary d) + { + this.d = d; + } + + public override bool Contains(object k) + { + return d.Contains(k); + } + + public override void CopyTo(Array array, int index) + { + d.CopyTo(array, index); + } + + public override int Count + { + get { return d.Count; } + } + + public override IDictionaryEnumerator GetEnumerator() + { + return d.GetEnumerator(); + } + + public override bool IsFixedSize + { + get { return d.IsFixedSize; } + } + + public override bool IsSynchronized + { + get { return d.IsSynchronized; } + } + + public override object SyncRoot + { + get { return d.SyncRoot; } + } + + public override ICollection Keys + { + get { return d.Keys; } + } + + public override ICollection Values + { + get { return d.Values; } + } + + protected override object GetValue(object k) + { + return d[k]; + } + } +} diff --git a/bc-sharp-crypto/src/util/collections/UnmodifiableList.cs b/bc-sharp-crypto/src/util/collections/UnmodifiableList.cs new file mode 100644 index 0000000..28e49ea --- /dev/null +++ b/bc-sharp-crypto/src/util/collections/UnmodifiableList.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections; + +namespace Org.BouncyCastle.Utilities.Collections +{ + public abstract class UnmodifiableList + : IList + { + protected UnmodifiableList() + { + } + + public virtual int Add(object o) + { + throw new NotSupportedException(); + } + + public virtual void Clear() + { + throw new NotSupportedException(); + } + + public abstract bool Contains(object o); + + public abstract void CopyTo(Array array, int index); + + public abstract int Count { get; } + + public abstract IEnumerator GetEnumerator(); + + public abstract int IndexOf(object o); + + public virtual void Insert(int i, object o) + { + throw new NotSupportedException(); + } + + public abstract bool IsFixedSize { get; } + + public virtual bool IsReadOnly + { + get { return true; } + } + + public abstract bool IsSynchronized { get; } + + public virtual void Remove(object o) + { + throw new NotSupportedException(); + } + + public virtual void RemoveAt(int i) + { + throw new NotSupportedException(); + } + + public abstract object SyncRoot { get; } + + public virtual object this[int i] + { + get { return GetValue(i); } + set { throw new NotSupportedException(); } + } + + protected abstract object GetValue(int i); + } +} diff --git a/bc-sharp-crypto/src/util/collections/UnmodifiableListProxy.cs b/bc-sharp-crypto/src/util/collections/UnmodifiableListProxy.cs new file mode 100644 index 0000000..9d00737 --- /dev/null +++ b/bc-sharp-crypto/src/util/collections/UnmodifiableListProxy.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections; + +namespace Org.BouncyCastle.Utilities.Collections +{ + public class UnmodifiableListProxy + : UnmodifiableList + { + private readonly IList l; + + public UnmodifiableListProxy(IList l) + { + this.l = l; + } + + public override bool Contains(object o) + { + return l.Contains(o); + } + + public override void CopyTo(Array array, int index) + { + l.CopyTo(array, index); + } + + public override int Count + { + get { return l.Count; } + } + + public override IEnumerator GetEnumerator() + { + return l.GetEnumerator(); + } + + public override int IndexOf(object o) + { + return l.IndexOf(o); + } + + public override bool IsFixedSize + { + get { return l.IsFixedSize; } + } + + public override bool IsSynchronized + { + get { return l.IsSynchronized; } + } + + public override object SyncRoot + { + get { return l.SyncRoot; } + } + + protected override object GetValue(int i) + { + return l[i]; + } + } +} diff --git a/bc-sharp-crypto/src/util/collections/UnmodifiableSet.cs b/bc-sharp-crypto/src/util/collections/UnmodifiableSet.cs new file mode 100644 index 0000000..8792815 --- /dev/null +++ b/bc-sharp-crypto/src/util/collections/UnmodifiableSet.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections; + +namespace Org.BouncyCastle.Utilities.Collections +{ + public abstract class UnmodifiableSet + : ISet + { + protected UnmodifiableSet() + { + } + + public virtual void Add(object o) + { + throw new NotSupportedException(); + } + + public virtual void AddAll(IEnumerable e) + { + throw new NotSupportedException(); + } + + public virtual void Clear() + { + throw new NotSupportedException(); + } + + public abstract bool Contains(object o); + + public abstract void CopyTo(Array array, int index); + + public abstract int Count { get; } + + public abstract IEnumerator GetEnumerator(); + + public abstract bool IsEmpty { get; } + + public abstract bool IsFixedSize { get; } + + public virtual bool IsReadOnly + { + get { return true; } + } + + public abstract bool IsSynchronized { get; } + + public abstract object SyncRoot { get; } + + public virtual void Remove(object o) + { + throw new NotSupportedException(); + } + + public virtual void RemoveAll(IEnumerable e) + { + throw new NotSupportedException(); + } + } +} diff --git a/bc-sharp-crypto/src/util/collections/UnmodifiableSetProxy.cs b/bc-sharp-crypto/src/util/collections/UnmodifiableSetProxy.cs new file mode 100644 index 0000000..e119e29 --- /dev/null +++ b/bc-sharp-crypto/src/util/collections/UnmodifiableSetProxy.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections; + +namespace Org.BouncyCastle.Utilities.Collections +{ + public class UnmodifiableSetProxy + : UnmodifiableSet + { + private readonly ISet s; + + public UnmodifiableSetProxy (ISet s) + { + this.s = s; + } + + public override bool Contains(object o) + { + return s.Contains(o); + } + + public override void CopyTo(Array array, int index) + { + s.CopyTo(array, index); + } + + public override int Count + { + get { return s.Count; } + } + + public override IEnumerator GetEnumerator() + { + return s.GetEnumerator(); + } + + public override bool IsEmpty + { + get { return s.IsEmpty; } + } + + public override bool IsFixedSize + { + get { return s.IsFixedSize; } + } + + public override bool IsSynchronized + { + get { return s.IsSynchronized; } + } + + public override object SyncRoot + { + get { return s.SyncRoot; } + } + } +} diff --git a/bc-sharp-crypto/src/util/date/DateTimeObject.cs b/bc-sharp-crypto/src/util/date/DateTimeObject.cs new file mode 100644 index 0000000..793376b --- /dev/null +++ b/bc-sharp-crypto/src/util/date/DateTimeObject.cs @@ -0,0 +1,25 @@ +using System; + +namespace Org.BouncyCastle.Utilities.Date +{ + public sealed class DateTimeObject + { + private readonly DateTime dt; + + public DateTimeObject( + DateTime dt) + { + this.dt = dt; + } + + public DateTime Value + { + get { return dt; } + } + + public override string ToString() + { + return dt.ToString(); + } + } +} diff --git a/bc-sharp-crypto/src/util/date/DateTimeUtilities.cs b/bc-sharp-crypto/src/util/date/DateTimeUtilities.cs new file mode 100644 index 0000000..311ad5d --- /dev/null +++ b/bc-sharp-crypto/src/util/date/DateTimeUtilities.cs @@ -0,0 +1,47 @@ +using System; + +namespace Org.BouncyCastle.Utilities.Date +{ + public class DateTimeUtilities + { + public static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1); + + private DateTimeUtilities() + { + } + + /// + /// Return the number of milliseconds since the Unix epoch (1 Jan., 1970 UTC) for a given DateTime value. + /// + /// A UTC DateTime value not before epoch. + /// Number of whole milliseconds after epoch. + /// 'dateTime' is before epoch. + public static long DateTimeToUnixMs( + DateTime dateTime) + { + if (dateTime.CompareTo(UnixEpoch) < 0) + throw new ArgumentException("DateTime value may not be before the epoch", "dateTime"); + + return (dateTime.Ticks - UnixEpoch.Ticks) / TimeSpan.TicksPerMillisecond; + } + + /// + /// Create a DateTime value from the number of milliseconds since the Unix epoch (1 Jan., 1970 UTC). + /// + /// Number of milliseconds since the epoch. + /// A UTC DateTime value + public static DateTime UnixMsToDateTime( + long unixMs) + { + return new DateTime(unixMs * TimeSpan.TicksPerMillisecond + UnixEpoch.Ticks); + } + + /// + /// Return the current number of milliseconds since the Unix epoch (1 Jan., 1970 UTC). + /// + public static long CurrentUnixMs() + { + return DateTimeToUnixMs(DateTime.UtcNow); + } + } +} diff --git a/bc-sharp-crypto/src/util/encoders/Base64.cs b/bc-sharp-crypto/src/util/encoders/Base64.cs new file mode 100644 index 0000000..ccecd8d --- /dev/null +++ b/bc-sharp-crypto/src/util/encoders/Base64.cs @@ -0,0 +1,120 @@ +using System; +using System.IO; +using System.Text; + +namespace Org.BouncyCastle.Utilities.Encoders +{ + public sealed class Base64 + { + private Base64() + { + } + + public static string ToBase64String( + byte[] data) + { + return Convert.ToBase64String(data, 0, data.Length); + } + + public static string ToBase64String( + byte[] data, + int off, + int length) + { + return Convert.ToBase64String(data, off, length); + } + + /** + * encode the input data producing a base 64 encoded byte array. + * + * @return a byte array containing the base 64 encoded data. + */ + public static byte[] Encode( + byte[] data) + { + return Encode(data, 0, data.Length); + } + + /** + * encode the input data producing a base 64 encoded byte array. + * + * @return a byte array containing the base 64 encoded data. + */ + public static byte[] Encode( + byte[] data, + int off, + int length) + { + string s = Convert.ToBase64String(data, off, length); + return Strings.ToAsciiByteArray(s); + } + + /** + * 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, + Stream outStream) + { + byte[] encoded = Encode(data); + outStream.Write(encoded, 0, encoded.Length); + return encoded.Length; + } + + /** + * 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, + Stream outStream) + { + byte[] encoded = Encode(data, off, length); + outStream.Write(encoded, 0, encoded.Length); + return encoded.Length; + } + + /** + * 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) + { + string s = Strings.FromAsciiByteArray(data); + return Convert.FromBase64String(s); + } + + /** + * 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) + { + return Convert.FromBase64String(data); + } + + /** + * 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, + Stream outStream) + { + byte[] decoded = Decode(data); + outStream.Write(decoded, 0, decoded.Length); + return decoded.Length; + } + } +} diff --git a/bc-sharp-crypto/src/util/encoders/Base64Encoder.cs b/bc-sharp-crypto/src/util/encoders/Base64Encoder.cs new file mode 100644 index 0000000..7b53df2 --- /dev/null +++ b/bc-sharp-crypto/src/util/encoders/Base64Encoder.cs @@ -0,0 +1,324 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Utilities.Encoders +{ + public class Base64Encoder + : IEncoder + { + protected readonly 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 readonly byte[] decodingTable = new byte[128]; + + protected void InitialiseDecodingTable() + { + Arrays.Fill(decodingTable, (byte)0xff); + + 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, + Stream outStream) + { + 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; + + outStream.WriteByte(encodingTable[(int) ((uint) a1 >> 2) & 0x3f]); + outStream.WriteByte(encodingTable[((a1 << 4) | (int) ((uint) a2 >> 4)) & 0x3f]); + outStream.WriteByte(encodingTable[((a2 << 2) | (int) ((uint) a3 >> 6)) & 0x3f]); + outStream.WriteByte(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; + + outStream.WriteByte(encodingTable[b1]); + outStream.WriteByte(encodingTable[b2]); + outStream.WriteByte(padding); + outStream.WriteByte(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; + + outStream.WriteByte(encodingTable[b1]); + outStream.WriteByte(encodingTable[b2]); + outStream.WriteByte(encodingTable[b3]); + outStream.WriteByte(padding); + break; + } + + return (dataLength / 3) * 4 + ((modulus == 0) ? 0 : 4); + } + + private bool 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, + Stream outStream) + { + 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++]]; + + if ((b1 | b2 | b3 | b4) >= 0x80) + throw new IOException("invalid characters encountered in base64 data"); + + outStream.WriteByte((byte)((b1 << 2) | (b2 >> 4))); + outStream.WriteByte((byte)((b2 << 4) | (b3 >> 2))); + outStream.WriteByte((byte)((b3 << 6) | b4)); + + outLen += 3; + + i = nextI(data, i, finish); + } + + outLen += decodeLastBlock(outStream, (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 DecodeString( + string data, + Stream outStream) + { + // Platform Implementation +// byte[] bytes = Convert.FromBase64String(data); +// outStream.Write(bytes, 0, bytes.Length); +// return bytes.Length; + + byte b1, b2, b3, b4; + int length = 0; + + int end = data.Length; + + while (end > 0) + { + if (!ignore(data[end - 1])) + { + break; + } + + end--; + } + + int i = 0; + 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++]]; + + if ((b1 | b2 | b3 | b4) >= 0x80) + throw new IOException("invalid characters encountered in base64 data"); + + outStream.WriteByte((byte)((b1 << 2) | (b2 >> 4))); + outStream.WriteByte((byte)((b2 << 4) | (b3 >> 2))); + outStream.WriteByte((byte)((b3 << 6) | b4)); + + length += 3; + + i = nextI(data, i, finish); + } + + length += decodeLastBlock(outStream, data[end - 4], data[end - 3], data[end - 2], data[end - 1]); + + return length; + } + + private int decodeLastBlock( + Stream outStream, + char c1, + char c2, + char c3, + char c4) + { + if (c3 == padding) + { + byte b1 = decodingTable[c1]; + byte b2 = decodingTable[c2]; + + if ((b1 | b2) >= 0x80) + throw new IOException("invalid characters encountered at end of base64 data"); + + outStream.WriteByte((byte)((b1 << 2) | (b2 >> 4))); + + return 1; + } + + if (c4 == padding) + { + byte b1 = decodingTable[c1]; + byte b2 = decodingTable[c2]; + byte b3 = decodingTable[c3]; + + if ((b1 | b2 | b3) >= 0x80) + throw new IOException("invalid characters encountered at end of base64 data"); + + outStream.WriteByte((byte)((b1 << 2) | (b2 >> 4))); + outStream.WriteByte((byte)((b2 << 4) | (b3 >> 2))); + + return 2; + } + + { + byte b1 = decodingTable[c1]; + byte b2 = decodingTable[c2]; + byte b3 = decodingTable[c3]; + byte b4 = decodingTable[c4]; + + if ((b1 | b2 | b3 | b4) >= 0x80) + throw new IOException("invalid characters encountered at end of base64 data"); + + outStream.WriteByte((byte)((b1 << 2) | (b2 >> 4))); + outStream.WriteByte((byte)((b2 << 4) | (b3 >> 2))); + outStream.WriteByte((byte)((b3 << 6) | b4)); + + return 3; + } + } + + private int nextI(string data, int i, int finish) + { + while ((i < finish) && ignore(data[i])) + { + i++; + } + return i; + } + } +} diff --git a/bc-sharp-crypto/src/util/encoders/BufferedDecoder.cs b/bc-sharp-crypto/src/util/encoders/BufferedDecoder.cs new file mode 100644 index 0000000..633cf1e --- /dev/null +++ b/bc-sharp-crypto/src/util/encoders/BufferedDecoder.cs @@ -0,0 +1,117 @@ +using System; + +namespace Org.BouncyCastle.Utilities.Encoders +{ + /// + /// A buffering class to allow translation from one format to another to + /// be done in discrete chunks. + /// + public class BufferedDecoder + { + internal byte[] buffer; + internal int bufOff; + + internal ITranslator translator; + + /// + /// Create a buffered Decoder. + /// + /// The translater to use. + /// The size of the buffer. + public BufferedDecoder( + ITranslator translator, + int bufferSize) + { + this.translator = translator; + + if ((bufferSize % translator.GetEncodedBlockSize()) != 0) + { + throw new ArgumentException("buffer size not multiple of input block size"); + } + + buffer = new byte[bufferSize]; +// bufOff = 0; + } + + /// + /// Process one byte of data. + /// + /// Data in. + /// Byte array for the output. + /// The offset in the output byte array to start writing from. + /// The amount of output bytes. + public int ProcessByte( + byte input, + byte[] output, + int outOff) + { + int resultLen = 0; + + buffer[bufOff++] = input; + + if (bufOff == buffer.Length) + { + resultLen = translator.Decode(buffer, 0, buffer.Length, output, outOff); + bufOff = 0; + } + + return resultLen; + } + + + /// + /// Process data from a byte array. + /// + /// The input data. + /// Start position within input data array. + /// Amount of data to process from input data array. + /// Array to store output. + /// Position in output array to start writing from. + /// The amount of output bytes. + public int ProcessBytes( + byte[] input, + int inOff, + int len, + byte[] outBytes, + int outOff) + { + if (len < 0) + { + throw new ArgumentException("Can't have a negative input length!"); + } + + int resultLen = 0; + int gapLen = buffer.Length - bufOff; + + if (len > gapLen) + { + Array.Copy(input, inOff, buffer, bufOff, gapLen); + + resultLen += translator.Decode(buffer, 0, buffer.Length, outBytes, outOff); + + bufOff = 0; + + len -= gapLen; + inOff += gapLen; + outOff += resultLen; + + int chunkSize = len - (len % buffer.Length); + + resultLen += translator.Decode(input, inOff, chunkSize, outBytes, outOff); + + len -= chunkSize; + inOff += chunkSize; + } + + if (len != 0) + { + Array.Copy(input, inOff, buffer, bufOff, len); + + bufOff += len; + } + + return resultLen; + } + } + +} diff --git a/bc-sharp-crypto/src/util/encoders/BufferedEncoder.cs b/bc-sharp-crypto/src/util/encoders/BufferedEncoder.cs new file mode 100644 index 0000000..5c3b1ab --- /dev/null +++ b/bc-sharp-crypto/src/util/encoders/BufferedEncoder.cs @@ -0,0 +1,117 @@ +using System; + +namespace Org.BouncyCastle.Utilities.Encoders +{ + /// + /// A class that allows encoding of data using a specific encoder to be processed in chunks. + /// + public class BufferedEncoder + { + internal byte[] Buffer; + internal int bufOff; + + internal ITranslator translator; + + + /// + /// Create. + /// + /// The translator to use. + /// Size of the chunks. + public BufferedEncoder( + ITranslator translator, + int bufferSize) + { + this.translator = translator; + + if ((bufferSize % translator.GetEncodedBlockSize()) != 0) + { + throw new ArgumentException("buffer size not multiple of input block size"); + } + + Buffer = new byte[bufferSize]; +// bufOff = 0; + } + + + /// + /// Process one byte of data. + /// + /// The byte. + /// An array to store output in. + /// Offset within output array to start writing from. + /// + public int ProcessByte( + byte input, + byte[] outBytes, + int outOff) + { + int resultLen = 0; + + Buffer[bufOff++] = input; + + if (bufOff == Buffer.Length) + { + resultLen = translator.Encode(Buffer, 0, Buffer.Length, outBytes, outOff); + bufOff = 0; + } + + return resultLen; + } + + /// + /// Process data from a byte array. + /// + /// Input data Byte array containing data to be processed. + /// Start position within input data array. + /// Amount of input data to be processed. + /// Output data array. + /// Offset within output data array to start writing to. + /// The amount of data written. + public int ProcessBytes( + byte[] input, + int inOff, + int len, + byte[] outBytes, + int outOff) + { + if (len < 0) + { + throw new ArgumentException("Can't have a negative input length!"); + } + + int resultLen = 0; + int gapLen = Buffer.Length - bufOff; + + if (len > gapLen) + { + Array.Copy(input, inOff, Buffer, bufOff, gapLen); + + resultLen += translator.Encode(Buffer, 0, Buffer.Length, outBytes, outOff); + + bufOff = 0; + + len -= gapLen; + inOff += gapLen; + outOff += resultLen; + + int chunkSize = len - (len % Buffer.Length); + + resultLen += translator.Encode(input, inOff, chunkSize, outBytes, outOff); + + len -= chunkSize; + inOff += chunkSize; + } + + if (len != 0) + { + Array.Copy(input, inOff, Buffer, bufOff, len); + + bufOff += len; + } + + return resultLen; + } + } + +} diff --git a/bc-sharp-crypto/src/util/encoders/Hex.cs b/bc-sharp-crypto/src/util/encoders/Hex.cs new file mode 100644 index 0000000..3540a9d --- /dev/null +++ b/bc-sharp-crypto/src/util/encoders/Hex.cs @@ -0,0 +1,130 @@ +using System; +using System.IO; +using System.Text; + +namespace Org.BouncyCastle.Utilities.Encoders +{ + /// + /// Class to decode and encode Hex. + /// + public sealed class Hex + { + private static readonly IEncoder encoder = new HexEncoder(); + + private Hex() + { + } + + public static string ToHexString( + byte[] data) + { + return ToHexString(data, 0, data.Length); + } + + public static string ToHexString( + byte[] data, + int off, + int length) + { + byte[] hex = Encode(data, off, length); + return Strings.FromAsciiByteArray(hex); + } + + /** + * 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) + { + MemoryStream bOut = new MemoryStream(length * 2); + + encoder.Encode(data, off, length, bOut); + + return bOut.ToArray(); + } + + /** + * Hex encode the byte data writing it to the given output stream. + * + * @return the number of bytes produced. + */ + public static int Encode( + byte[] data, + Stream outStream) + { + return encoder.Encode(data, 0, data.Length, outStream); + } + + /** + * 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, + Stream outStream) + { + return encoder.Encode(data, off, length, outStream); + } + + /** + * 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) + { + MemoryStream bOut = new MemoryStream((data.Length + 1) / 2); + + encoder.Decode(data, 0, data.Length, bOut); + + return bOut.ToArray(); + } + + /** + * decode the Hex encoded string data - whitespace will be ignored. + * + * @return a byte array representing the decoded data. + */ + public static byte[] Decode( + string data) + { + MemoryStream bOut = new MemoryStream((data.Length + 1) / 2); + + encoder.DecodeString(data, bOut); + + return bOut.ToArray(); + } + + /** + * 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, + Stream outStream) + { + return encoder.DecodeString(data, outStream); + } + } +} diff --git a/bc-sharp-crypto/src/util/encoders/HexEncoder.cs b/bc-sharp-crypto/src/util/encoders/HexEncoder.cs new file mode 100644 index 0000000..af526e0 --- /dev/null +++ b/bc-sharp-crypto/src/util/encoders/HexEncoder.cs @@ -0,0 +1,176 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Utilities.Encoders +{ + public class HexEncoder + : IEncoder + { + protected readonly 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 readonly byte[] decodingTable = new byte[128]; + + protected void InitialiseDecodingTable() + { + Arrays.Fill(decodingTable, (byte)0xff); + + 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, + Stream outStream) + { + for (int i = off; i < (off + length); i++) + { + int v = data[i]; + + outStream.WriteByte(encodingTable[v >> 4]); + outStream.WriteByte(encodingTable[v & 0xf]); + } + + return length * 2; + } + + private static bool 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, + Stream outStream) + { + 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++]]; + + if ((b1 | b2) >= 0x80) + throw new IOException("invalid characters encountered in Hex data"); + + outStream.WriteByte((byte)((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 DecodeString( + string data, + Stream outStream) + { + byte b1, b2; + int length = 0; + + int end = data.Length; + + while (end > 0) + { + if (!Ignore(data[end - 1])) + { + break; + } + + end--; + } + + int i = 0; + while (i < end) + { + while (i < end && Ignore(data[i])) + { + i++; + } + + b1 = decodingTable[data[i++]]; + + while (i < end && Ignore(data[i])) + { + i++; + } + + b2 = decodingTable[data[i++]]; + + if ((b1 | b2) >= 0x80) + throw new IOException("invalid characters encountered in Hex data"); + + outStream.WriteByte((byte)((b1 << 4) | b2)); + + length++; + } + + return length; + } + } +} diff --git a/bc-sharp-crypto/src/util/encoders/HexTranslator.cs b/bc-sharp-crypto/src/util/encoders/HexTranslator.cs new file mode 100644 index 0000000..9775b69 --- /dev/null +++ b/bc-sharp-crypto/src/util/encoders/HexTranslator.cs @@ -0,0 +1,108 @@ +using System; + +namespace Org.BouncyCastle.Utilities.Encoders +{ + /// + /// A hex translator. + /// + public class HexTranslator : ITranslator + { + private static readonly 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' + }; + + /// + /// Return encoded block size. + /// + /// 2 + public int GetEncodedBlockSize() + { + return 2; + } + + /// + /// Encode some data. + /// + /// Input data array. + /// Start position within input data array. + /// The amount of data to process. + /// The output data array. + /// The offset within the output data array to start writing from. + /// Amount of data encoded. + public int Encode( + byte[] input, + int inOff, + int length, + byte[] outBytes, + int outOff) + { + for (int i = 0, j = 0; i < length; i++, j += 2) + { + outBytes[outOff + j] = hexTable[(input[inOff] >> 4) & 0x0f]; + outBytes[outOff + j + 1] = hexTable[input[inOff] & 0x0f]; + + inOff++; + } + + return length * 2; + } + + /// + /// Returns the decoded block size. + /// + /// 1 + public int GetDecodedBlockSize() + { + return 1; + } + + /// + /// Decode data from a byte array. + /// + /// The input data array. + /// Start position within input data array. + /// The amounty of data to process. + /// The output data array. + /// The position within the output data array to start writing from. + /// The amount of data written. + public int Decode( + byte[] input, + int inOff, + int length, + byte[] outBytes, + int outOff) + { + int halfLength = length / 2; + byte left, right; + for (int i = 0; i < halfLength; i++) + { + left = input[inOff + i * 2]; + right = input[inOff + i * 2 + 1]; + + if (left < (byte)'a') + { + outBytes[outOff] = (byte)((left - '0') << 4); + } + else + { + outBytes[outOff] = (byte)((left - 'a' + 10) << 4); + } + if (right < (byte)'a') + { + outBytes[outOff] += (byte)(right - '0'); + } + else + { + outBytes[outOff] += (byte)(right - 'a' + 10); + } + + outOff++; + } + + return halfLength; + } + } + +} diff --git a/bc-sharp-crypto/src/util/encoders/IEncoder.cs b/bc-sharp-crypto/src/util/encoders/IEncoder.cs new file mode 100644 index 0000000..5887d5d --- /dev/null +++ b/bc-sharp-crypto/src/util/encoders/IEncoder.cs @@ -0,0 +1,18 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Utilities.Encoders +{ + /** + * Encode and decode byte arrays (typically from binary to 7-bit ASCII + * encodings). + */ + public interface IEncoder + { + int Encode(byte[] data, int off, int length, Stream outStream); + + int Decode(byte[] data, int off, int length, Stream outStream); + + int DecodeString(string data, Stream outStream); + } +} diff --git a/bc-sharp-crypto/src/util/encoders/Translator.cs b/bc-sharp-crypto/src/util/encoders/Translator.cs new file mode 100644 index 0000000..10bd24b --- /dev/null +++ b/bc-sharp-crypto/src/util/encoders/Translator.cs @@ -0,0 +1,19 @@ +using System; + +namespace Org.BouncyCastle.Utilities.Encoders +{ + /// + /// Translator interface. + /// + public interface ITranslator + { + int GetEncodedBlockSize(); + + int Encode(byte[] input, int inOff, int length, byte[] outBytes, int outOff); + + int GetDecodedBlockSize(); + + int Decode(byte[] input, int inOff, int length, byte[] outBytes, int outOff); + } + +} diff --git a/bc-sharp-crypto/src/util/encoders/UrlBase64.cs b/bc-sharp-crypto/src/util/encoders/UrlBase64.cs new file mode 100644 index 0000000..94195ef --- /dev/null +++ b/bc-sharp-crypto/src/util/encoders/UrlBase64.cs @@ -0,0 +1,127 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Utilities.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 UrlBase64 + { + private static readonly IEncoder 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) + { + MemoryStream bOut = new MemoryStream(); + + try + { + encoder.Encode(data, 0, data.Length, bOut); + } + catch (IOException e) + { + throw new Exception("exception encoding URL safe base64 string: " + e.Message, e); + } + + return bOut.ToArray(); + } + + /** + * Encode the byte data writing it to the given output stream. + * + * @return the number of bytes produced. + */ + public static int Encode( + byte[] data, + Stream outStr) + { + return encoder.Encode(data, 0, data.Length, outStr); + } + + /** + * 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) + { + MemoryStream bOut = new MemoryStream(); + + try + { + encoder.Decode(data, 0, data.Length, bOut); + } + catch (IOException e) + { + throw new Exception("exception decoding URL safe base64 string: " + e.Message, e); + } + + return bOut.ToArray(); + } + + /** + * 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, + Stream outStr) + { + return encoder.Decode(data, 0, data.Length, outStr); + } + + /** + * 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) + { + MemoryStream bOut = new MemoryStream(); + + try + { + encoder.DecodeString(data, bOut); + } + catch (IOException e) + { + throw new Exception("exception decoding URL safe base64 string: " + e.Message, e); + } + + return bOut.ToArray(); + } + + /** + * 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, + Stream outStr) + { + return encoder.DecodeString(data, outStr); + } + } +} diff --git a/bc-sharp-crypto/src/util/encoders/UrlBase64Encoder.cs b/bc-sharp-crypto/src/util/encoders/UrlBase64Encoder.cs new file mode 100644 index 0000000..5611a83 --- /dev/null +++ b/bc-sharp-crypto/src/util/encoders/UrlBase64Encoder.cs @@ -0,0 +1,31 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Utilities.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 + : 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(); + } + } +} \ No newline at end of file diff --git a/bc-sharp-crypto/src/util/io/BaseInputStream.cs b/bc-sharp-crypto/src/util/io/BaseInputStream.cs new file mode 100644 index 0000000..a5613d8 --- /dev/null +++ b/bc-sharp-crypto/src/util/io/BaseInputStream.cs @@ -0,0 +1,64 @@ +using System; +using System.Diagnostics; +using System.IO; + +namespace Org.BouncyCastle.Utilities.IO +{ + public abstract class BaseInputStream : Stream + { + private bool closed; + + public sealed override bool CanRead { get { return !closed; } } + public sealed override bool CanSeek { get { return false; } } + public sealed override bool CanWrite { get { return false; } } + +#if PORTABLE + protected override void Dispose(bool disposing) + { + if (disposing) + { + closed = true; + } + base.Dispose(disposing); + } +#else + public override void Close() + { + closed = true; + base.Close(); + } +#endif + + public sealed override void Flush() {} + public sealed override long Length { get { throw new NotSupportedException(); } } + public sealed override long Position + { + get { throw new NotSupportedException(); } + set { throw new NotSupportedException(); } + } + + public override int Read(byte[] buffer, int offset, int count) + { + int pos = offset; + try + { + int end = offset + count; + while (pos < end) + { + int b = ReadByte(); + if (b == -1) break; + buffer[pos++] = (byte) b; + } + } + catch (IOException) + { + if (pos == offset) throw; + } + return pos - offset; + } + + public sealed override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } + public sealed override void SetLength(long value) { throw new NotSupportedException(); } + public sealed override void Write(byte[] buffer, int offset, int count) { throw new NotSupportedException(); } + } +} diff --git a/bc-sharp-crypto/src/util/io/BaseOutputStream.cs b/bc-sharp-crypto/src/util/io/BaseOutputStream.cs new file mode 100644 index 0000000..0dbe821 --- /dev/null +++ b/bc-sharp-crypto/src/util/io/BaseOutputStream.cs @@ -0,0 +1,69 @@ +using System; +using System.Diagnostics; +using System.IO; + +namespace Org.BouncyCastle.Utilities.IO +{ + public abstract class BaseOutputStream : Stream + { + private bool closed; + + public sealed override bool CanRead { get { return false; } } + public sealed override bool CanSeek { get { return false; } } + public sealed override bool CanWrite { get { return !closed; } } + +#if PORTABLE + protected override void Dispose(bool disposing) + { + if (disposing) + { + closed = true; + } + base.Dispose(disposing); + } +#else + public override void Close() + { + closed = true; + base.Close(); + } +#endif + + public override void Flush() { } + public sealed override long Length { get { throw new NotSupportedException(); } } + public sealed override long Position + { + get { throw new NotSupportedException(); } + set { throw new NotSupportedException(); } + } + public sealed override int Read(byte[] buffer, int offset, int count) { throw new NotSupportedException(); } + public sealed override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } + public sealed override void SetLength(long value) { throw new NotSupportedException(); } + + public override void Write(byte[] buffer, int offset, int count) + { + Debug.Assert(buffer != null); + Debug.Assert(0 <= offset && offset <= buffer.Length); + Debug.Assert(count >= 0); + + int end = offset + count; + + Debug.Assert(0 <= end && end <= buffer.Length); + + for (int i = offset; i < end; ++i) + { + this.WriteByte(buffer[i]); + } + } + + public virtual void Write(params byte[] buffer) + { + Write(buffer, 0, buffer.Length); + } + + public override void WriteByte(byte b) + { + Write(new byte[]{ b }, 0, 1); + } + } +} diff --git a/bc-sharp-crypto/src/util/io/FilterStream.cs b/bc-sharp-crypto/src/util/io/FilterStream.cs new file mode 100644 index 0000000..a92dee3 --- /dev/null +++ b/bc-sharp-crypto/src/util/io/FilterStream.cs @@ -0,0 +1,78 @@ +using System.IO; + +namespace Org.BouncyCastle.Utilities.IO +{ + public class FilterStream : Stream + { + public FilterStream(Stream s) + { + this.s = s; + } + public override bool CanRead + { + get { return s.CanRead; } + } + public override bool CanSeek + { + get { return s.CanSeek; } + } + public override bool CanWrite + { + get { return s.CanWrite; } + } + public override long Length + { + get { return s.Length; } + } + public override long Position + { + get { return s.Position; } + set { s.Position = value; } + } +#if PORTABLE + protected override void Dispose(bool disposing) + { + if (disposing) + { + Platform.Dispose(s); + } + base.Dispose(disposing); + } +#else + public override void Close() + { + Platform.Dispose(s); + base.Close(); + } +#endif + public override void Flush() + { + s.Flush(); + } + public override long Seek(long offset, SeekOrigin origin) + { + return s.Seek(offset, origin); + } + public override void SetLength(long value) + { + s.SetLength(value); + } + public override int Read(byte[] buffer, int offset, int count) + { + return s.Read(buffer, offset, count); + } + public override int ReadByte() + { + return s.ReadByte(); + } + public override void Write(byte[] buffer, int offset, int count) + { + s.Write(buffer, offset, count); + } + public override void WriteByte(byte value) + { + s.WriteByte(value); + } + protected readonly Stream s; + } +} diff --git a/bc-sharp-crypto/src/util/io/NullOutputStream.cs b/bc-sharp-crypto/src/util/io/NullOutputStream.cs new file mode 100644 index 0000000..13877fa --- /dev/null +++ b/bc-sharp-crypto/src/util/io/NullOutputStream.cs @@ -0,0 +1,18 @@ +using System; + +namespace Org.BouncyCastle.Utilities.IO +{ + internal class NullOutputStream + : BaseOutputStream + { + public override void WriteByte(byte b) + { + // do nothing + } + + public override void Write(byte[] buffer, int offset, int count) + { + // do nothing + } + } +} diff --git a/bc-sharp-crypto/src/util/io/PushbackStream.cs b/bc-sharp-crypto/src/util/io/PushbackStream.cs new file mode 100644 index 0000000..9546942 --- /dev/null +++ b/bc-sharp-crypto/src/util/io/PushbackStream.cs @@ -0,0 +1,52 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Asn1.Utilities; + +namespace Org.BouncyCastle.Utilities.IO +{ + public class PushbackStream + : FilterStream + { + private int buf = -1; + + public PushbackStream( + Stream s) + : base(s) + { + } + + public override int ReadByte() + { + if (buf != -1) + { + int tmp = buf; + buf = -1; + return tmp; + } + + return base.ReadByte(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + if (buf != -1 && count > 0) + { + // TODO Can this case be made more efficient? + buffer[offset] = (byte) buf; + buf = -1; + return 1; + } + + return base.Read(buffer, offset, count); + } + + public virtual void Unread(int b) + { + if (buf != -1) + throw new InvalidOperationException("Can only push back one byte"); + + buf = b & 0xFF; + } + } +} diff --git a/bc-sharp-crypto/src/util/io/StreamOverflowException.cs b/bc-sharp-crypto/src/util/io/StreamOverflowException.cs new file mode 100644 index 0000000..36d21e2 --- /dev/null +++ b/bc-sharp-crypto/src/util/io/StreamOverflowException.cs @@ -0,0 +1,30 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Utilities.IO +{ +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT || PORTABLE) + [Serializable] +#endif + public class StreamOverflowException + : IOException + { + public StreamOverflowException() + : base() + { + } + + public StreamOverflowException( + string message) + : base(message) + { + } + + public StreamOverflowException( + string message, + Exception exception) + : base(message, exception) + { + } + } +} diff --git a/bc-sharp-crypto/src/util/io/Streams.cs b/bc-sharp-crypto/src/util/io/Streams.cs new file mode 100644 index 0000000..cc7fa92 --- /dev/null +++ b/bc-sharp-crypto/src/util/io/Streams.cs @@ -0,0 +1,100 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Utilities.IO +{ + public sealed class Streams + { + private const int BufferSize = 512; + + private Streams() + { + } + + public static void Drain(Stream inStr) + { + byte[] bs = new byte[BufferSize]; + while (inStr.Read(bs, 0, bs.Length) > 0) + { + } + } + + public static byte[] ReadAll(Stream inStr) + { + MemoryStream buf = new MemoryStream(); + PipeAll(inStr, buf); + return buf.ToArray(); + } + + public static byte[] ReadAllLimited(Stream inStr, int limit) + { + MemoryStream buf = new MemoryStream(); + PipeAllLimited(inStr, limit, buf); + return buf.ToArray(); + } + + public static int ReadFully(Stream inStr, byte[] buf) + { + return ReadFully(inStr, buf, 0, buf.Length); + } + + public static int ReadFully(Stream inStr, byte[] buf, int off, int len) + { + int totalRead = 0; + while (totalRead < len) + { + int numRead = inStr.Read(buf, off + totalRead, len - totalRead); + if (numRead < 1) + break; + totalRead += numRead; + } + return totalRead; + } + + public static void PipeAll(Stream inStr, Stream outStr) + { + byte[] bs = new byte[BufferSize]; + int numRead; + while ((numRead = inStr.Read(bs, 0, bs.Length)) > 0) + { + outStr.Write(bs, 0, numRead); + } + } + + /// + /// Pipe all bytes from inStr to outStr, throwing StreamFlowException if greater + /// than limit bytes in inStr. + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + /// The number of bytes actually transferred, if not greater than limit + /// + public static long PipeAllLimited(Stream inStr, long limit, Stream outStr) + { + byte[] bs = new byte[BufferSize]; + long total = 0; + int numRead; + while ((numRead = inStr.Read(bs, 0, bs.Length)) > 0) + { + if ((limit - total) < numRead) + throw new StreamOverflowException("Data Overflow"); + total += numRead; + outStr.Write(bs, 0, numRead); + } + return total; + } + + /// + public static void WriteBufTo(MemoryStream buf, Stream output) + { + buf.WriteTo(output); + } + } +} diff --git a/bc-sharp-crypto/src/util/io/TeeInputStream.cs b/bc-sharp-crypto/src/util/io/TeeInputStream.cs new file mode 100644 index 0000000..6996f3f --- /dev/null +++ b/bc-sharp-crypto/src/util/io/TeeInputStream.cs @@ -0,0 +1,64 @@ +using System; +using System.Diagnostics; +using System.IO; + +namespace Org.BouncyCastle.Utilities.IO +{ + public class TeeInputStream + : BaseInputStream + { + private readonly Stream input, tee; + + public TeeInputStream(Stream input, Stream tee) + { + Debug.Assert(input.CanRead); + Debug.Assert(tee.CanWrite); + + this.input = input; + this.tee = tee; + } + +#if PORTABLE + protected override void Dispose(bool disposing) + { + if (disposing) + { + Platform.Dispose(input); + Platform.Dispose(tee); + } + base.Dispose(disposing); + } +#else + public override void Close() + { + Platform.Dispose(input); + Platform.Dispose(tee); + base.Close(); + } +#endif + + public override int Read(byte[] buf, int off, int len) + { + int i = input.Read(buf, off, len); + + if (i > 0) + { + tee.Write(buf, off, i); + } + + return i; + } + + public override int ReadByte() + { + int i = input.ReadByte(); + + if (i >= 0) + { + tee.WriteByte((byte)i); + } + + return i; + } + } +} diff --git a/bc-sharp-crypto/src/util/io/TeeOutputStream.cs b/bc-sharp-crypto/src/util/io/TeeOutputStream.cs new file mode 100644 index 0000000..a6c7fd5 --- /dev/null +++ b/bc-sharp-crypto/src/util/io/TeeOutputStream.cs @@ -0,0 +1,52 @@ +using System; +using System.Diagnostics; +using System.IO; + +namespace Org.BouncyCastle.Utilities.IO +{ + public class TeeOutputStream + : BaseOutputStream + { + private readonly Stream output, tee; + + public TeeOutputStream(Stream output, Stream tee) + { + Debug.Assert(output.CanWrite); + Debug.Assert(tee.CanWrite); + + this.output = output; + this.tee = tee; + } + +#if PORTABLE + protected override void Dispose(bool disposing) + { + if (disposing) + { + Platform.Dispose(output); + Platform.Dispose(tee); + } + base.Dispose(disposing); + } +#else + public override void Close() + { + Platform.Dispose(output); + Platform.Dispose(tee); + base.Close(); + } +#endif + + public override void Write(byte[] buffer, int offset, int count) + { + output.Write(buffer, offset, count); + tee.Write(buffer, offset, count); + } + + public override void WriteByte(byte b) + { + output.WriteByte(b); + tee.WriteByte(b); + } + } +} diff --git a/bc-sharp-crypto/src/util/io/pem/PemGenerationException.cs b/bc-sharp-crypto/src/util/io/pem/PemGenerationException.cs new file mode 100644 index 0000000..6b39585 --- /dev/null +++ b/bc-sharp-crypto/src/util/io/pem/PemGenerationException.cs @@ -0,0 +1,29 @@ +using System; + +namespace Org.BouncyCastle.Utilities.IO.Pem +{ +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT || PORTABLE) + [Serializable] +#endif + public class PemGenerationException + : Exception + { + public PemGenerationException() + : base() + { + } + + public PemGenerationException( + string message) + : base(message) + { + } + + public PemGenerationException( + string message, + Exception exception) + : base(message, exception) + { + } + } +} diff --git a/bc-sharp-crypto/src/util/io/pem/PemHeader.cs b/bc-sharp-crypto/src/util/io/pem/PemHeader.cs new file mode 100644 index 0000000..72da8a4 --- /dev/null +++ b/bc-sharp-crypto/src/util/io/pem/PemHeader.cs @@ -0,0 +1,55 @@ +using System; + +namespace Org.BouncyCastle.Utilities.IO.Pem +{ + public class PemHeader + { + private string name; + private string val; + + public PemHeader(string name, string val) + { + this.name = name; + this.val = val; + } + + public virtual string Name + { + get { return name; } + } + + public virtual string Value + { + get { return val; } + } + + public override int GetHashCode() + { + return GetHashCode(this.name) + 31 * GetHashCode(this.val); + } + + public override bool Equals(object obj) + { + if (obj == this) + return true; + + if (!(obj is PemHeader)) + return false; + + PemHeader other = (PemHeader)obj; + + return Platform.Equals(this.name, other.name) + && Platform.Equals(this.val, other.val); + } + + private int GetHashCode(string s) + { + if (s == null) + { + return 1; + } + + return s.GetHashCode(); + } + } +} diff --git a/bc-sharp-crypto/src/util/io/pem/PemObject.cs b/bc-sharp-crypto/src/util/io/pem/PemObject.cs new file mode 100644 index 0000000..41212f9 --- /dev/null +++ b/bc-sharp-crypto/src/util/io/pem/PemObject.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Utilities.Collections; + +namespace Org.BouncyCastle.Utilities.IO.Pem +{ + public class PemObject + : PemObjectGenerator + { + private string type; + private IList headers; + private byte[] content; + + public PemObject(string type, byte[] content) + : this(type, Platform.CreateArrayList(), content) + { + } + + public PemObject(String type, IList headers, byte[] content) + { + this.type = type; + this.headers = Platform.CreateArrayList(headers); + this.content = content; + } + + public string Type + { + get { return type; } + } + + public IList Headers + { + get { return headers; } + } + + public byte[] Content + { + get { return content; } + } + + public PemObject Generate() + { + return this; + } + } +} diff --git a/bc-sharp-crypto/src/util/io/pem/PemObjectGenerator.cs b/bc-sharp-crypto/src/util/io/pem/PemObjectGenerator.cs new file mode 100644 index 0000000..6f9bfc1 --- /dev/null +++ b/bc-sharp-crypto/src/util/io/pem/PemObjectGenerator.cs @@ -0,0 +1,13 @@ +using System; + +namespace Org.BouncyCastle.Utilities.IO.Pem +{ + public interface PemObjectGenerator + { + /// + /// A + /// + /// + PemObject Generate(); + } +} diff --git a/bc-sharp-crypto/src/util/io/pem/PemObjectParser.cs b/bc-sharp-crypto/src/util/io/pem/PemObjectParser.cs new file mode 100644 index 0000000..91d26dc --- /dev/null +++ b/bc-sharp-crypto/src/util/io/pem/PemObjectParser.cs @@ -0,0 +1,17 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Utilities.IO.Pem +{ + public interface PemObjectParser + { + /// + /// A + /// + /// + /// A + /// + /// + object ParseObject(PemObject obj); + } +} diff --git a/bc-sharp-crypto/src/util/io/pem/PemReader.cs b/bc-sharp-crypto/src/util/io/pem/PemReader.cs new file mode 100644 index 0000000..bf712b6 --- /dev/null +++ b/bc-sharp-crypto/src/util/io/pem/PemReader.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections; +using System.IO; +using System.Text; + +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Utilities.IO.Pem +{ + public class PemReader + { + private const string BeginString = "-----BEGIN "; + private const string EndString = "-----END "; + + private readonly TextReader reader; + + public PemReader(TextReader reader) + { + if (reader == null) + throw new ArgumentNullException("reader"); + + this.reader = reader; + } + + public TextReader Reader + { + get { return reader; } + } + + /// + /// A + /// + /// + public PemObject ReadPemObject() + { + string line = reader.ReadLine(); + + if (line != null && Platform.StartsWith(line, BeginString)) + { + line = line.Substring(BeginString.Length); + int index = line.IndexOf('-'); + string type = line.Substring(0, index); + + if (index > 0) + return LoadObject(type); + } + + return null; + } + + private PemObject LoadObject(string type) + { + string endMarker = EndString + type; + IList headers = Platform.CreateArrayList(); + StringBuilder buf = new StringBuilder(); + + string line; + while ((line = reader.ReadLine()) != null + && Platform.IndexOf(line, endMarker) == -1) + { + int colonPos = line.IndexOf(':'); + + if (colonPos == -1) + { + buf.Append(line.Trim()); + } + else + { + // Process field + string fieldName = line.Substring(0, colonPos).Trim(); + + if (Platform.StartsWith(fieldName, "X-")) + { + fieldName = fieldName.Substring(2); + } + + string fieldValue = line.Substring(colonPos + 1).Trim(); + + headers.Add(new PemHeader(fieldName, fieldValue)); + } + } + + if (line == null) + { + throw new IOException(endMarker + " not found"); + } + + if (buf.Length % 4 != 0) + { + throw new IOException("base64 data appears to be truncated"); + } + + return new PemObject(type, headers, Base64.Decode(buf.ToString())); + } + } +} diff --git a/bc-sharp-crypto/src/util/io/pem/PemWriter.cs b/bc-sharp-crypto/src/util/io/pem/PemWriter.cs new file mode 100644 index 0000000..e85b315 --- /dev/null +++ b/bc-sharp-crypto/src/util/io/pem/PemWriter.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Utilities.IO.Pem +{ + /** + * A generic PEM writer, based on RFC 1421 + */ + public class PemWriter + { + private const int LineLength = 64; + + private readonly TextWriter writer; + private readonly int nlLength; + private char[] buf = new char[LineLength]; + + /** + * Base constructor. + * + * @param out output stream to use. + */ + public PemWriter(TextWriter writer) + { + if (writer == null) + throw new ArgumentNullException("writer"); + + this.writer = writer; + this.nlLength = Platform.NewLine.Length; + } + + public TextWriter Writer + { + get { return writer; } + } + + /** + * Return the number of bytes or characters required to contain the + * passed in object if it is PEM encoded. + * + * @param obj pem object to be output + * @return an estimate of the number of bytes + */ + public int GetOutputSize(PemObject obj) + { + // BEGIN and END boundaries. + int size = (2 * (obj.Type.Length + 10 + nlLength)) + 6 + 4; + + if (obj.Headers.Count > 0) + { + foreach (PemHeader header in obj.Headers) + { + size += header.Name.Length + ": ".Length + header.Value.Length + nlLength; + } + + size += nlLength; + } + + // base64 encoding + int dataLen = ((obj.Content.Length + 2) / 3) * 4; + + size += dataLen + (((dataLen + LineLength - 1) / LineLength) * nlLength); + + return size; + } + + public void WriteObject(PemObjectGenerator objGen) + { + PemObject obj = objGen.Generate(); + + WritePreEncapsulationBoundary(obj.Type); + + if (obj.Headers.Count > 0) + { + foreach (PemHeader header in obj.Headers) + { + writer.Write(header.Name); + writer.Write(": "); + writer.WriteLine(header.Value); + } + + writer.WriteLine(); + } + + WriteEncoded(obj.Content); + WritePostEncapsulationBoundary(obj.Type); + } + + private void WriteEncoded(byte[] bytes) + { + bytes = Base64.Encode(bytes); + + for (int i = 0; i < bytes.Length; i += buf.Length) + { + int index = 0; + while (index != buf.Length) + { + if ((i + index) >= bytes.Length) + break; + + buf[index] = (char)bytes[i + index]; + index++; + } + writer.WriteLine(buf, 0, index); + } + } + + private void WritePreEncapsulationBoundary(string type) + { + writer.WriteLine("-----BEGIN " + type + "-----"); + } + + private void WritePostEncapsulationBoundary(string type) + { + writer.WriteLine("-----END " + type + "-----"); + } + } +} diff --git a/bc-sharp-crypto/src/util/net/IPAddress.cs b/bc-sharp-crypto/src/util/net/IPAddress.cs new file mode 100644 index 0000000..38c1245 --- /dev/null +++ b/bc-sharp-crypto/src/util/net/IPAddress.cs @@ -0,0 +1,197 @@ +using System; +using System.Globalization; + +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Utilities.Net +{ + 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 bool 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 bool 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 bool IsValidIPv4( + string address) + { + try + { + return unsafeIsValidIPv4(address); + } + catch (FormatException) {} + catch (OverflowException) {} + return false; + } + + private static bool unsafeIsValidIPv4( + string address) + { + if (address.Length == 0) + return false; + + 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; + + string octetStr = temp.Substring(start, pos - start); + int octet = Int32.Parse(octetStr); + + if (octet < 0 || octet > 255) + return false; + + start = pos + 1; + octets++; + } + + return octets == 4; + } + + public static bool 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 bool 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 bool IsMaskValue( + string component, + int size) + { + int val = Int32.Parse(component); + try + { + return val >= 0 && val <= size; + } + catch (FormatException) {} + catch (OverflowException) {} + 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 bool IsValidIPv6( + string address) + { + try + { + return unsafeIsValidIPv6(address); + } + catch (FormatException) {} + catch (OverflowException) {} + return false; + } + + private static bool unsafeIsValidIPv6( + string address) + { + if (address.Length == 0) + { + return false; + } + + int octets = 0; + + string temp = address + ":"; + bool 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 - start); + + if (pos == (temp.Length - 1) && value.IndexOf('.') > 0) + { + if (!IsValidIPv4(value)) + { + return false; + } + + octets++; // add an extra one as address covers 2 words. + } + else + { + string octetStr = temp.Substring(start, pos - start); + int octet = Int32.Parse(octetStr, NumberStyles.AllowHexSpecifier); + + 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/bc-sharp-crypto/src/util/zlib/Adler32.cs b/bc-sharp-crypto/src/util/zlib/Adler32.cs new file mode 100644 index 0000000..c38258f --- /dev/null +++ b/bc-sharp-crypto/src/util/zlib/Adler32.cs @@ -0,0 +1,88 @@ +using System; +/* + * $Id: Adler32.cs,v 1.1 2006-07-31 13:59:25 bouncy Exp $ + * +Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +namespace Org.BouncyCastle.Utilities.Zlib { + + internal sealed class Adler32{ + + // largest prime smaller than 65536 + private const int BASE=65521; + // NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 + private const int NMAX=5552; + + internal long adler32(long adler, byte[] buf, int index, int len){ + if(buf == null){ return 1L; } + + long s1=adler&0xffff; + long s2=(adler>>16)&0xffff; + int k; + + while(len > 0) { + k=len=16){ + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + k-=16; + } + if(k!=0){ + do{ + s1+=buf[index++]&0xff; s2+=s1; + } + while(--k!=0); + } + s1%=BASE; + s2%=BASE; + } + return (s2<<16)|s1; + } + + } +} \ No newline at end of file diff --git a/bc-sharp-crypto/src/util/zlib/Deflate.cs b/bc-sharp-crypto/src/util/zlib/Deflate.cs new file mode 100644 index 0000000..ca04309 --- /dev/null +++ b/bc-sharp-crypto/src/util/zlib/Deflate.cs @@ -0,0 +1,1640 @@ +using System; +/* + * $Id: Deflate.cs,v 1.2 2008-05-10 09:35:40 bouncy Exp $ + * +Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +namespace Org.BouncyCastle.Utilities.Zlib { + + public sealed class Deflate{ + + private const int MAX_MEM_LEVEL=9; + + private const int Z_DEFAULT_COMPRESSION=-1; + + private const int MAX_WBITS=15; // 32K LZ77 window + private const int DEF_MEM_LEVEL=8; + + internal class Config{ + internal int good_length; // reduce lazy search above this match length + internal int max_lazy; // do not perform lazy search above this match length + internal int nice_length; // quit search above this match length + internal int max_chain; + internal int func; + internal Config(int good_length, int max_lazy, + int nice_length, int max_chain, int func){ + this.good_length=good_length; + this.max_lazy=max_lazy; + this.nice_length=nice_length; + this.max_chain=max_chain; + this.func=func; + } + } + + private const int STORED=0; + private const int FAST=1; + private const int SLOW=2; + private static readonly Config[] config_table; + + static Deflate(){ + config_table=new Config[10]; + // good lazy nice chain + config_table[0]=new Config(0, 0, 0, 0, STORED); + config_table[1]=new Config(4, 4, 8, 4, FAST); + config_table[2]=new Config(4, 5, 16, 8, FAST); + config_table[3]=new Config(4, 6, 32, 32, FAST); + + config_table[4]=new Config(4, 4, 16, 16, SLOW); + config_table[5]=new Config(8, 16, 32, 32, SLOW); + config_table[6]=new Config(8, 16, 128, 128, SLOW); + config_table[7]=new Config(8, 32, 128, 256, SLOW); + config_table[8]=new Config(32, 128, 258, 1024, SLOW); + config_table[9]=new Config(32, 258, 258, 4096, SLOW); + } + + private static readonly String[] z_errmsg = { + "need dictionary", // Z_NEED_DICT 2 + "stream end", // Z_STREAM_END 1 + "", // Z_OK 0 + "file error", // Z_ERRNO (-1) + "stream error", // Z_STREAM_ERROR (-2) + "data error", // Z_DATA_ERROR (-3) + "insufficient memory", // Z_MEM_ERROR (-4) + "buffer error", // Z_BUF_ERROR (-5) + "incompatible version",// Z_VERSION_ERROR (-6) + "" + }; + + // block not completed, need more input or more output + private const int NeedMore=0; + + // block flush performed + private const int BlockDone=1; + + // finish started, need only more output at next deflate + private const int FinishStarted=2; + + // finish done, accept no more input or output + private const int FinishDone=3; + + // preset dictionary flag in zlib header + private const int PRESET_DICT=0x20; + + private const int Z_FILTERED=1; + private const int Z_HUFFMAN_ONLY=2; + private const int Z_DEFAULT_STRATEGY=0; + + private const int Z_NO_FLUSH=0; + private const int Z_PARTIAL_FLUSH=1; + private const int Z_SYNC_FLUSH=2; + private const int Z_FULL_FLUSH=3; + private const int Z_FINISH=4; + + private const int Z_OK=0; + private const int Z_STREAM_END=1; + private const int Z_NEED_DICT=2; + private const int Z_ERRNO=-1; + private const int Z_STREAM_ERROR=-2; + private const int Z_DATA_ERROR=-3; + private const int Z_MEM_ERROR=-4; + private const int Z_BUF_ERROR=-5; + private const int Z_VERSION_ERROR=-6; + + private const int INIT_STATE=42; + private const int BUSY_STATE=113; + private const int FINISH_STATE=666; + + // The deflate compression method + private const int Z_DEFLATED=8; + + private const int STORED_BLOCK=0; + private const int STATIC_TREES=1; + private const int DYN_TREES=2; + + // The three kinds of block type + private const int Z_BINARY=0; + private const int Z_ASCII=1; + private const int Z_UNKNOWN=2; + + private const int Buf_size=8*2; + + // repeat previous bit length 3-6 times (2 bits of repeat count) + private const int REP_3_6=16; + + // repeat a zero length 3-10 times (3 bits of repeat count) + private const int REPZ_3_10=17; + + // repeat a zero length 11-138 times (7 bits of repeat count) + private const int REPZ_11_138=18; + + private const int MIN_MATCH=3; + private const int MAX_MATCH=258; + private const int MIN_LOOKAHEAD=(MAX_MATCH+MIN_MATCH+1); + + private const int MAX_BITS=15; + private const int D_CODES=30; + private const int BL_CODES=19; + private const int LENGTH_CODES=29; + private const int LITERALS=256; + private const int L_CODES=(LITERALS+1+LENGTH_CODES); + private const int HEAP_SIZE=(2*L_CODES+1); + + private const int END_BLOCK=256; + + internal ZStream strm; // pointer back to this zlib stream + internal int status; // as the name implies + internal byte[] pending_buf; // output still pending + internal int pending_buf_size; // size of pending_buf + internal int pending_out; // next pending byte to output to the stream + internal int pending; // nb of bytes in the pending buffer + internal int noheader; // suppress zlib header and adler32 + internal byte data_type; // UNKNOWN, BINARY or ASCII + internal byte method; // STORED (for zip only) or DEFLATED + internal int last_flush; // value of flush param for previous deflate call + + internal int w_size; // LZ77 window size (32K by default) + internal int w_bits; // log2(w_size) (8..16) + internal int w_mask; // w_size - 1 + + internal byte[] window; + // Sliding window. Input bytes are read into the second half of the window, + // and move to the first half later to keep a dictionary of at least wSize + // bytes. With this organization, matches are limited to a distance of + // wSize-MAX_MATCH bytes, but this ensures that IO is always + // performed with a length multiple of the block size. Also, it limits + // the window size to 64K, which is quite useful on MSDOS. + // To do: use the user input buffer as sliding window. + + internal int window_size; + // Actual size of window: 2*wSize, except when the user input buffer + // is directly used as sliding window. + + internal short[] prev; + // Link to older string with same hash index. To limit the size of this + // array to 64K, this link is maintained only for the last 32K strings. + // An index in this array is thus a window index modulo 32K. + + internal short[] head; // Heads of the hash chains or NIL. + + internal int ins_h; // hash index of string to be inserted + internal int hash_size; // number of elements in hash table + internal int hash_bits; // log2(hash_size) + internal int hash_mask; // hash_size-1 + + // Number of bits by which ins_h must be shifted at each input + // step. It must be such that after MIN_MATCH steps, the oldest + // byte no longer takes part in the hash key, that is: + // hash_shift * MIN_MATCH >= hash_bits + internal int hash_shift; + + // Window position at the beginning of the current output block. Gets + // negative when the window is moved backwards. + + internal int block_start; + + internal int match_length; // length of best match + internal int prev_match; // previous match + internal int match_available; // set if previous match exists + internal int strstart; // start of string to insert + internal int match_start; // start of matching string + internal int lookahead; // number of valid bytes ahead in window + + // Length of the best match at previous step. Matches not greater than this + // are discarded. This is used in the lazy match evaluation. + internal int prev_length; + + // To speed up deflation, hash chains are never searched beyond this + // length. A higher limit improves compression ratio but degrades the speed. + internal int max_chain_length; + + // Attempt to find a better match only when the current match is strictly + // smaller than this value. This mechanism is used only for compression + // levels >= 4. + internal int max_lazy_match; + + // Insert new strings in the hash table only if the match length is not + // greater than this length. This saves time but degrades compression. + // max_insert_length is used only for compression levels <= 3. + + internal int level; // compression level (1..9) + internal int strategy; // favor or force Huffman coding + + // Use a faster search when the previous match is longer than this + internal int good_match; + + // Stop searching when current match exceeds this + internal int nice_match; + + internal short[] dyn_ltree; // literal and length tree + internal short[] dyn_dtree; // distance tree + internal short[] bl_tree; // Huffman tree for bit lengths + + internal Tree l_desc=new Tree(); // desc for literal tree + internal Tree d_desc=new Tree(); // desc for distance tree + internal Tree bl_desc=new Tree(); // desc for bit length tree + + // number of codes at each bit length for an optimal tree + internal short[] bl_count=new short[MAX_BITS+1]; + + // heap used to build the Huffman trees + internal int[] heap=new int[2*L_CODES+1]; + + internal int heap_len; // number of elements in the heap + internal int heap_max; // element of largest frequency + // The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used. + // The same heap array is used to build all trees. + + // Depth of each subtree used as tie breaker for trees of equal frequency + internal byte[] depth=new byte[2*L_CODES+1]; + + internal int l_buf; // index for literals or lengths */ + + // Size of match buffer for literals/lengths. There are 4 reasons for + // limiting lit_bufsize to 64K: + // - frequencies can be kept in 16 bit counters + // - if compression is not successful for the first block, all input + // data is still in the window so we can still emit a stored block even + // when input comes from standard input. (This can also be done for + // all blocks if lit_bufsize is not greater than 32K.) + // - if compression is not successful for a file smaller than 64K, we can + // even emit a stored file instead of a stored block (saving 5 bytes). + // This is applicable only for zip (not gzip or zlib). + // - creating new Huffman trees less frequently may not provide fast + // adaptation to changes in the input data statistics. (Take for + // example a binary file with poorly compressible code followed by + // a highly compressible string table.) Smaller buffer sizes give + // fast adaptation but have of course the overhead of transmitting + // trees more frequently. + // - I can't count above 4 + internal int lit_bufsize; + + internal int last_lit; // running index in l_buf + + // Buffer for distances. To simplify the code, d_buf and l_buf have + // the same number of elements. To use different lengths, an extra flag + // array would be necessary. + + internal int d_buf; // index of pendig_buf + + internal int opt_len; // bit length of current block with optimal trees + internal int static_len; // bit length of current block with static trees + internal int matches; // number of string matches in current block + internal int last_eob_len; // bit length of EOB code for last block + + // Output buffer. bits are inserted starting at the bottom (least + // significant bits). + internal uint bi_buf; + + // Number of valid bits in bi_buf. All bits above the last valid bit + // are always zero. + internal int bi_valid; + + internal Deflate(){ + dyn_ltree=new short[HEAP_SIZE*2]; + dyn_dtree=new short[(2*D_CODES+1)*2]; // distance tree + bl_tree=new short[(2*BL_CODES+1)*2]; // Huffman tree for bit lengths + } + + internal void lm_init() { + window_size=2*w_size; + + head[hash_size-1]=0; + for(int i=0; i= 3; max_blindex--) { + if (bl_tree[Tree.bl_order[max_blindex]*2+1] != 0) break; + } + // Update opt_len to include the bit length tree and counts + opt_len += 3*(max_blindex+1) + 5+5+4; + + return max_blindex; + } + + + // Send the header for a block using dynamic Huffman trees: the counts, the + // lengths of the bit length codes, the literal tree and the distance tree. + // IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4. + internal void send_all_trees(int lcodes, int dcodes, int blcodes){ + int rank; // index in bl_order + + send_bits(lcodes-257, 5); // not +255 as stated in appnote.txt + send_bits(dcodes-1, 5); + send_bits(blcodes-4, 4); // not -3 as stated in appnote.txt + for (rank = 0; rank < blcodes; rank++) { + send_bits(bl_tree[Tree.bl_order[rank]*2+1], 3); + } + send_tree(dyn_ltree, lcodes-1); // literal tree + send_tree(dyn_dtree, dcodes-1); // distance tree + } + + // Send a literal or distance tree in compressed form, using the codes in + // bl_tree. + internal void send_tree (short[] tree,// the tree to be sent + int max_code // and its largest code of non zero frequency + ){ + int n; // iterates over all tree elements + int prevlen = -1; // last emitted length + int curlen; // length of current code + int nextlen = tree[0*2+1]; // length of next code + int count = 0; // repeat count of the current code + int max_count = 7; // max repeat count + int min_count = 4; // min repeat count + + if (nextlen == 0){ max_count = 138; min_count = 3; } + + for (n = 0; n <= max_code; n++) { + curlen = nextlen; nextlen = tree[(n+1)*2+1]; + if(++count < max_count && curlen == nextlen) { + continue; + } + else if(count < min_count) { + do { send_code(curlen, bl_tree); } while (--count != 0); + } + else if(curlen != 0){ + if(curlen != prevlen){ + send_code(curlen, bl_tree); count--; + } + send_code(REP_3_6, bl_tree); + send_bits(count-3, 2); + } + else if(count <= 10){ + send_code(REPZ_3_10, bl_tree); + send_bits(count-3, 3); + } + else{ + send_code(REPZ_11_138, bl_tree); + send_bits(count-11, 7); + } + count = 0; prevlen = curlen; + if(nextlen == 0){ + max_count = 138; min_count = 3; + } + else if(curlen == nextlen){ + max_count = 6; min_count = 3; + } + else{ + max_count = 7; min_count = 4; + } + } + } + + // Output a byte on the stream. + // IN assertion: there is enough room in pending_buf. + internal void put_byte(byte[] p, int start, int len){ + System.Array.Copy(p, start, pending_buf, pending, len); + pending+=len; + } + + internal void put_byte(byte c){ + pending_buf[pending++]=c; + } + internal void put_short(int w) { + pending_buf[pending++]=(byte)(w/*&0xff*/); + pending_buf[pending++]=(byte)(w>>8); + } + internal void putShortMSB(int b){ + pending_buf[pending++]=(byte)(b>>8); + pending_buf[pending++]=(byte)(b/*&0xff*/); + } + + internal void send_code(int c, short[] tree){ + int c2=c*2; + send_bits((tree[c2]&0xffff), (tree[c2+1]&0xffff)); + } + + internal void send_bits(int val, int length){ + if (bi_valid > Buf_size - length) { + bi_buf |= (uint)(val << bi_valid); + pending_buf[pending++]=(byte)(bi_buf/*&0xff*/); + pending_buf[pending++]=(byte)(bi_buf>>8); + bi_buf = ((uint)val) >> (Buf_size - bi_valid); + bi_valid += length - Buf_size; + } else { + bi_buf |= (uint)(val << bi_valid); + bi_valid += length; + } +// int len = length; +// if (bi_valid > (int)Buf_size - len) { +// int val = value; +// // bi_buf |= (val << bi_valid); +// bi_buf = (short)((ushort)bi_buf | (ushort)((val << bi_valid)&0xffff)); +// put_short(bi_buf); +// bi_buf = (short)(((uint)val) >> (Buf_size - bi_valid)); +// bi_valid += len - Buf_size; +// } else { +// // bi_buf |= (value) << bi_valid; +// bi_buf = (short)((ushort)bi_buf | (ushort)(((value) << bi_valid)&0xffff)); +// bi_valid += len; +// } + } + + // Send one empty static block to give enough lookahead for inflate. + // This takes 10 bits, of which 7 may remain in the bit buffer. + // The current inflate code requires 9 bits of lookahead. If the + // last two codes for the previous block (real code plus EOB) were coded + // on 5 bits or less, inflate may have only 5+3 bits of lookahead to decode + // the last real code. In this case we send two empty static blocks instead + // of one. (There are no problems if the previous block is stored or fixed.) + // To simplify the code, we assume the worst case of last real code encoded + // on one bit only. + internal void _tr_align(){ + send_bits(STATIC_TREES<<1, 3); + send_code(END_BLOCK, StaticTree.static_ltree); + + bi_flush(); + + // Of the 10 bits for the empty block, we have already sent + // (10 - bi_valid) bits. The lookahead for the last real code (before + // the EOB of the previous block) was thus at least one plus the length + // of the EOB plus what we have just sent of the empty static block. + if (1 + last_eob_len + 10 - bi_valid < 9) { + send_bits(STATIC_TREES<<1, 3); + send_code(END_BLOCK, StaticTree.static_ltree); + bi_flush(); + } + last_eob_len = 7; + } + + + // Save the match info and tally the frequency counts. Return true if + // the current block must be flushed. + internal bool _tr_tally (int dist, // distance of matched string + int lc // match length-MIN_MATCH or unmatched char (if dist==0) + ){ + + pending_buf[d_buf+last_lit*2] = (byte)(dist>>8); + pending_buf[d_buf+last_lit*2+1] = (byte)dist; + + pending_buf[l_buf+last_lit] = (byte)lc; last_lit++; + + if (dist == 0) { + // lc is the unmatched char + dyn_ltree[lc*2]++; + } + else { + matches++; + // Here, lc is the match length - MIN_MATCH + dist--; // dist = match distance - 1 + dyn_ltree[(Tree._length_code[lc]+LITERALS+1)*2]++; + dyn_dtree[Tree.d_code(dist)*2]++; + } + + if ((last_lit & 0x1fff) == 0 && level > 2) { + // Compute an upper bound for the compressed length + int out_length = last_lit*8; + int in_length = strstart - block_start; + int dcode; + for (dcode = 0; dcode < D_CODES; dcode++) { + out_length += (int)((int)dyn_dtree[dcode*2] * + (5L+Tree.extra_dbits[dcode])); + } + out_length >>= 3; + if ((matches < (last_lit/2)) && out_length < in_length/2) return true; + } + + return (last_lit == lit_bufsize-1); + // We avoid equality with lit_bufsize because of wraparound at 64K + // on 16 bit machines and because stored blocks are restricted to + // 64K-1 bytes. + } + + // Send the block data compressed using the given Huffman trees + internal void compress_block(short[] ltree, short[] dtree){ + int dist; // distance of matched string + int lc; // match length or unmatched char (if dist == 0) + int lx = 0; // running index in l_buf + int code; // the code to send + int extra; // number of extra bits to send + + if (last_lit != 0){ + do{ + dist=((pending_buf[d_buf+lx*2]<<8)&0xff00)| + (pending_buf[d_buf+lx*2+1]&0xff); + lc=(pending_buf[l_buf+lx])&0xff; lx++; + + if(dist == 0){ + send_code(lc, ltree); // send a literal byte + } + else{ + // Here, lc is the match length - MIN_MATCH + code = Tree._length_code[lc]; + + send_code(code+LITERALS+1, ltree); // send the length code + extra = Tree.extra_lbits[code]; + if(extra != 0){ + lc -= Tree.base_length[code]; + send_bits(lc, extra); // send the extra length bits + } + dist--; // dist is now the match distance - 1 + code = Tree.d_code(dist); + + send_code(code, dtree); // send the distance code + extra = Tree.extra_dbits[code]; + if (extra != 0) { + dist -= Tree.base_dist[code]; + send_bits(dist, extra); // send the extra distance bits + } + } // literal or match pair ? + + // Check that the overlay between pending_buf and d_buf+l_buf is ok: + } + while (lx < last_lit); + } + + send_code(END_BLOCK, ltree); + last_eob_len = ltree[END_BLOCK*2+1]; + } + + // Set the data type to ASCII or BINARY, using a crude approximation: + // binary if more than 20% of the bytes are <= 6 or >= 128, ascii otherwise. + // IN assertion: the fields freq of dyn_ltree are set and the total of all + // frequencies does not exceed 64K (to fit in an int on 16 bit machines). + internal void set_data_type(){ + int n = 0; + int ascii_freq = 0; + int bin_freq = 0; + while(n<7){ bin_freq += dyn_ltree[n*2]; n++;} + while(n<128){ ascii_freq += dyn_ltree[n*2]; n++;} + while(n (ascii_freq >> 2) ? Z_BINARY : Z_ASCII); + } + + // Flush the bit buffer, keeping at most 7 bits in it. + internal void bi_flush(){ + if (bi_valid == 16) { + pending_buf[pending++]=(byte)(bi_buf/*&0xff*/); + pending_buf[pending++]=(byte)(bi_buf>>8); + bi_buf=0; + bi_valid=0; + } + else if (bi_valid >= 8) { + pending_buf[pending++]=(byte)(bi_buf); + bi_buf>>=8; + bi_buf &= 0x00ff; + bi_valid-=8; + } + } + + // Flush the bit buffer and align the output on a byte boundary + internal void bi_windup(){ + if (bi_valid > 8) { + pending_buf[pending++]=(byte)(bi_buf); + pending_buf[pending++]=(byte)(bi_buf>>8); + } else if (bi_valid > 0) { + pending_buf[pending++]=(byte)(bi_buf); + } + bi_buf = 0; + bi_valid = 0; + } + + // Copy a stored block, storing first the length and its + // one's complement if requested. + internal void copy_block(int buf, // the input data + int len, // its length + bool header // true if block header must be written + ){ + //int index=0; + bi_windup(); // align on byte boundary + last_eob_len = 8; // enough lookahead for inflate + + if (header) { + put_short((short)len); + put_short((short)~len); + } + + // while(len--!=0) { + // put_byte(window[buf+index]); + // index++; + // } + put_byte(window, buf, len); + } + + internal void flush_block_only(bool eof){ + _tr_flush_block(block_start>=0 ? block_start : -1, + strstart-block_start, + eof); + block_start=strstart; + strm.flush_pending(); + } + + // Copy without compression as much as possible from the input stream, return + // the current block state. + // This function does not insert new strings in the dictionary since + // uncompressible data is probably not useful. This function is used + // only for the level=0 compression option. + // NOTE: this function should be optimized to avoid extra copying from + // window to pending_buf. + internal int deflate_stored(int flush){ + // Stored blocks are limited to 0xffff bytes, pending_buf is limited + // to pending_buf_size, and each stored block has a 5 byte header: + + int max_block_size = 0xffff; + int max_start; + + if(max_block_size > pending_buf_size - 5) { + max_block_size = pending_buf_size - 5; + } + + // Copy as much as possible from input to output: + while(true){ + // Fill the window as much as possible: + if(lookahead<=1){ + fill_window(); + if(lookahead==0 && flush==Z_NO_FLUSH) return NeedMore; + if(lookahead==0) break; // flush the current block + } + + strstart+=lookahead; + lookahead=0; + + // Emit a stored block if pending_buf will be full: + max_start=block_start+max_block_size; + if(strstart==0|| strstart>=max_start) { + // strstart == 0 is possible when wraparound on 16-bit machine + lookahead = (int)(strstart-max_start); + strstart = (int)max_start; + + flush_block_only(false); + if(strm.avail_out==0) return NeedMore; + + } + + // Flush if we may have to slide, otherwise block_start may become + // negative and the data will be gone: + if(strstart-block_start >= w_size-MIN_LOOKAHEAD) { + flush_block_only(false); + if(strm.avail_out==0) return NeedMore; + } + } + + flush_block_only(flush == Z_FINISH); + if(strm.avail_out==0) + return (flush == Z_FINISH) ? FinishStarted : NeedMore; + + return flush == Z_FINISH ? FinishDone : BlockDone; + } + + // Send a stored block + internal void _tr_stored_block(int buf, // input block + int stored_len, // length of input block + bool eof // true if this is the last block for a file + ){ + send_bits((STORED_BLOCK<<1)+(eof?1:0), 3); // send block type + copy_block(buf, stored_len, true); // with header + } + + // Determine the best encoding for the current block: dynamic trees, static + // trees or store, and output the encoded block to the zip file. + internal void _tr_flush_block(int buf, // input block, or NULL if too old + int stored_len, // length of input block + bool eof // true if this is the last block for a file + ) { + int opt_lenb, static_lenb;// opt_len and static_len in bytes + int max_blindex = 0; // index of last bit length code of non zero freq + + // Build the Huffman trees unless a stored block is forced + if(level > 0) { + // Check if the file is ascii or binary + if(data_type == Z_UNKNOWN) set_data_type(); + + // Construct the literal and distance trees + l_desc.build_tree(this); + + d_desc.build_tree(this); + + // At this point, opt_len and static_len are the total bit lengths of + // the compressed block data, excluding the tree representations. + + // Build the bit length tree for the above two trees, and get the index + // in bl_order of the last bit length code to send. + max_blindex=build_bl_tree(); + + // Determine the best encoding. Compute first the block length in bytes + opt_lenb=(opt_len+3+7)>>3; + static_lenb=(static_len+3+7)>>3; + + if(static_lenb<=opt_lenb) opt_lenb=static_lenb; + } + else { + opt_lenb=static_lenb=stored_len+5; // force a stored block + } + + if(stored_len+4<=opt_lenb && buf != -1){ + // 4: two words for the lengths + // The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE. + // Otherwise we can't have processed more than WSIZE input bytes since + // the last block flush, because compression would have been + // successful. If LIT_BUFSIZE <= WSIZE, it is never too late to + // transform a block into a stored block. + _tr_stored_block(buf, stored_len, eof); + } + else if(static_lenb == opt_lenb){ + send_bits((STATIC_TREES<<1)+(eof?1:0), 3); + compress_block(StaticTree.static_ltree, StaticTree.static_dtree); + } + else{ + send_bits((DYN_TREES<<1)+(eof?1:0), 3); + send_all_trees(l_desc.max_code+1, d_desc.max_code+1, max_blindex+1); + compress_block(dyn_ltree, dyn_dtree); + } + + // The above check is made mod 2^32, for files larger than 512 MB + // and uLong implemented on 32 bits. + + init_block(); + + if(eof){ + bi_windup(); + } + } + + // Fill the window when the lookahead becomes insufficient. + // Updates strstart and lookahead. + // + // IN assertion: lookahead < MIN_LOOKAHEAD + // OUT assertions: strstart <= window_size-MIN_LOOKAHEAD + // At least one byte has been read, or avail_in == 0; reads are + // performed for at least two bytes (required for the zip translate_eol + // option -- not supported here). + internal void fill_window(){ + int n, m; + int p; + int more; // Amount of free space at the end of the window. + + do{ + more = (window_size-lookahead-strstart); + + // Deal with !@#$% 64K limit: + if(more==0 && strstart==0 && lookahead==0){ + more = w_size; + } + else if(more==-1) { + // Very unlikely, but possible on 16 bit machine if strstart == 0 + // and lookahead == 1 (input done one byte at time) + more--; + + // If the window is almost full and there is insufficient lookahead, + // move the upper half to the lower one to make room in the upper half. + } + else if(strstart >= w_size+ w_size-MIN_LOOKAHEAD) { + System.Array.Copy(window, w_size, window, 0, w_size); + match_start-=w_size; + strstart-=w_size; // we now have strstart >= MAX_DIST + block_start-=w_size; + + // Slide the hash table (could be avoided with 32 bit values + // at the expense of memory usage). We slide even when level == 0 + // to keep the hash table consistent if we switch back to level > 0 + // later. (Using level 0 permanently is not an optimal usage of + // zlib, so we don't care about this pathological case.) + + n = hash_size; + p=n; + do { + m = (head[--p]&0xffff); + head[p]=(short)(m>=w_size ? (m-w_size) : 0); + } + while (--n != 0); + + n = w_size; + p = n; + do { + m = (prev[--p]&0xffff); + prev[p] = (short)(m >= w_size ? (m-w_size) : 0); + // If n is not on any hash chain, prev[n] is garbage but + // its value will never be used. + } + while (--n!=0); + more += w_size; + } + + if (strm.avail_in == 0) return; + + // If there was no sliding: + // strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 && + // more == window_size - lookahead - strstart + // => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1) + // => more >= window_size - 2*WSIZE + 2 + // In the BIG_MEM or MMAP case (not yet supported), + // window_size == input_size + MIN_LOOKAHEAD && + // strstart + s->lookahead <= input_size => more >= MIN_LOOKAHEAD. + // Otherwise, window_size == 2*WSIZE so more >= 2. + // If there was sliding, more >= WSIZE. So in all cases, more >= 2. + + n = strm.read_buf(window, strstart + lookahead, more); + lookahead += n; + + // Initialize the hash value now that we have some input: + if(lookahead >= MIN_MATCH) { + ins_h = window[strstart]&0xff; + ins_h=(((ins_h)<= MIN_MATCH){ + ins_h=(((ins_h)<=MIN_MATCH){ + // check_match(strstart, match_start, match_length); + + bflush=_tr_tally(strstart-match_start, match_length-MIN_MATCH); + + lookahead -= match_length; + + // Insert new strings in the hash table only if the match length + // is not too large. This saves time but degrades compression. + if(match_length <= max_lazy_match && + lookahead >= MIN_MATCH) { + match_length--; // string at strstart already in hash table + do{ + strstart++; + + ins_h=((ins_h<= MIN_MATCH) { + ins_h=(((ins_h)< 4096))) { + + // If prev_match is also MIN_MATCH, match_start is garbage + // but we will ignore the current match anyway. + match_length = MIN_MATCH-1; + } + } + + // If there was a match at the previous step and the current + // match is not better, output the previous match: + if(prev_length >= MIN_MATCH && match_length <= prev_length) { + int max_insert = strstart + lookahead - MIN_MATCH; + // Do not insert strings in hash table beyond this. + + // check_match(strstart-1, prev_match, prev_length); + + bflush=_tr_tally(strstart-1-prev_match, prev_length - MIN_MATCH); + + // Insert in hash table all strings up to the end of the match. + // strstart-1 and strstart are already inserted. If there is not + // enough lookahead, the last two strings are not inserted in + // the hash table. + lookahead -= prev_length-1; + prev_length -= 2; + do{ + if(++strstart <= max_insert) { + ins_h=(((ins_h)<(w_size-MIN_LOOKAHEAD) ? + strstart-(w_size-MIN_LOOKAHEAD) : 0; + int nice_match=this.nice_match; + + // Stop when cur_match becomes <= limit. To simplify the code, + // we prevent matches with the string of window index 0. + + int wmask = w_mask; + + int strend = strstart + MAX_MATCH; + byte scan_end1 = window[scan+best_len-1]; + byte scan_end = window[scan+best_len]; + + // The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16. + // It is easy to get rid of this optimization if necessary. + + // Do not waste too much time if we already have a good match: + if (prev_length >= good_match) { + chain_length >>= 2; + } + + // Do not look for matches beyond the end of the input. This is necessary + // to make deflate deterministic. + if (nice_match > lookahead) nice_match = lookahead; + + do { + match = cur_match; + + // Skip to next match if the match length cannot increase + // or if the match length is less than 2: + if (window[match+best_len] != scan_end || + window[match+best_len-1] != scan_end1 || + window[match] != window[scan] || + window[++match] != window[scan+1]) continue; + + // The check at best_len-1 can be removed because it will be made + // again later. (This heuristic is not always a win.) + // It is not necessary to compare scan[2] and match[2] since they + // are always equal when the other bytes match, given that + // the hash keys are equal and that HASH_BITS >= 8. + scan += 2; match++; + + // We check for insufficient lookahead only every 8th comparison; + // the 256th check will be made at strstart+258. + do { + } while (window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + scan < strend); + + len = MAX_MATCH - (int)(strend - scan); + scan = strend - MAX_MATCH; + + if(len>best_len) { + match_start = cur_match; + best_len = len; + if (len >= nice_match) break; + scan_end1 = window[scan+best_len-1]; + scan_end = window[scan+best_len]; + } + + } while ((cur_match = (prev[cur_match & wmask]&0xffff)) > limit + && --chain_length != 0); + + if (best_len <= lookahead) return best_len; + return lookahead; + } + + internal int deflateInit(ZStream strm, int level, int bits){ + return deflateInit2(strm, level, Z_DEFLATED, bits, DEF_MEM_LEVEL, + Z_DEFAULT_STRATEGY); + } + internal int deflateInit(ZStream strm, int level){ + return deflateInit(strm, level, MAX_WBITS); + } + internal int deflateInit2(ZStream strm, int level, int method, int windowBits, + int memLevel, int strategy){ + int noheader = 0; + // byte[] my_version=ZLIB_VERSION; + + // + // if (version == null || version[0] != my_version[0] + // || stream_size != sizeof(z_stream)) { + // return Z_VERSION_ERROR; + // } + + strm.msg = null; + + if (level == Z_DEFAULT_COMPRESSION) level = 6; + + if (windowBits < 0) { // undocumented feature: suppress zlib header + noheader = 1; + windowBits = -windowBits; + } + + if (memLevel < 1 || memLevel > MAX_MEM_LEVEL || + method != Z_DEFLATED || + windowBits < 9 || windowBits > 15 || level < 0 || level > 9 || + strategy < 0 || strategy > Z_HUFFMAN_ONLY) { + return Z_STREAM_ERROR; + } + + strm.dstate = (Deflate)this; + + this.noheader = noheader; + w_bits = windowBits; + w_size = 1 << w_bits; + w_mask = w_size - 1; + + hash_bits = memLevel + 7; + hash_size = 1 << hash_bits; + hash_mask = hash_size - 1; + hash_shift = ((hash_bits+MIN_MATCH-1)/MIN_MATCH); + + window = new byte[w_size*2]; + prev = new short[w_size]; + head = new short[hash_size]; + + lit_bufsize = 1 << (memLevel + 6); // 16K elements by default + + // We overlay pending_buf and d_buf+l_buf. This works since the average + // output size for (length,distance) codes is <= 24 bits. + pending_buf = new byte[lit_bufsize*4]; + pending_buf_size = lit_bufsize*4; + + d_buf = lit_bufsize/2; + l_buf = (1+2)*lit_bufsize; + + this.level = level; + + //System.out.println("level="+level); + + this.strategy = strategy; + this.method = (byte)method; + + return deflateReset(strm); + } + + internal int deflateReset(ZStream strm){ + strm.total_in = strm.total_out = 0; + strm.msg = null; // + strm.data_type = Z_UNKNOWN; + + pending = 0; + pending_out = 0; + + if(noheader < 0) { + noheader = 0; // was set to -1 by deflate(..., Z_FINISH); + } + status = (noheader!=0) ? BUSY_STATE : INIT_STATE; + strm.adler=strm._adler.adler32(0, null, 0, 0); + + last_flush = Z_NO_FLUSH; + + tr_init(); + lm_init(); + return Z_OK; + } + + internal int deflateEnd(){ + if(status!=INIT_STATE && status!=BUSY_STATE && status!=FINISH_STATE){ + return Z_STREAM_ERROR; + } + // Deallocate in reverse order of allocations: + pending_buf=null; + head=null; + prev=null; + window=null; + // free + // dstate=null; + return status == BUSY_STATE ? Z_DATA_ERROR : Z_OK; + } + + internal int deflateParams(ZStream strm, int _level, int _strategy){ + int err=Z_OK; + + if(_level == Z_DEFAULT_COMPRESSION){ + _level = 6; + } + if(_level < 0 || _level > 9 || + _strategy < 0 || _strategy > Z_HUFFMAN_ONLY) { + return Z_STREAM_ERROR; + } + + if(config_table[level].func!=config_table[_level].func && + strm.total_in != 0) { + // Flush the last buffer: + err = strm.deflate(Z_PARTIAL_FLUSH); + } + + if(level != _level) { + level = _level; + max_lazy_match = config_table[level].max_lazy; + good_match = config_table[level].good_length; + nice_match = config_table[level].nice_length; + max_chain_length = config_table[level].max_chain; + } + strategy = _strategy; + return err; + } + + internal int deflateSetDictionary (ZStream strm, byte[] dictionary, int dictLength){ + int length = dictLength; + int index=0; + + if(dictionary == null || status != INIT_STATE) + return Z_STREAM_ERROR; + + strm.adler=strm._adler.adler32(strm.adler, dictionary, 0, dictLength); + + if(length < MIN_MATCH) return Z_OK; + if(length > w_size-MIN_LOOKAHEAD){ + length = w_size-MIN_LOOKAHEAD; + index=dictLength-length; // use the tail of the dictionary + } + System.Array.Copy(dictionary, index, window, 0, length); + strstart = length; + block_start = length; + + // Insert all strings in the hash table (except for the last two bytes). + // s->lookahead stays null, so s->ins_h will be recomputed at the next + // call of fill_window. + + ins_h = window[0]&0xff; + ins_h=(((ins_h)<Z_FINISH || flush<0){ + return Z_STREAM_ERROR; + } + + if(strm.next_out == null || + (strm.next_in == null && strm.avail_in != 0) || + (status == FINISH_STATE && flush != Z_FINISH)) { + strm.msg=z_errmsg[Z_NEED_DICT-(Z_STREAM_ERROR)]; + return Z_STREAM_ERROR; + } + if(strm.avail_out == 0){ + strm.msg=z_errmsg[Z_NEED_DICT-(Z_BUF_ERROR)]; + return Z_BUF_ERROR; + } + + this.strm = strm; // just in case + old_flush = last_flush; + last_flush = flush; + + // Write the zlib header + if(status == INIT_STATE) { + int header = (Z_DEFLATED+((w_bits-8)<<4))<<8; + int level_flags=((level-1)&0xff)>>1; + + if(level_flags>3) level_flags=3; + header |= (level_flags<<6); + if(strstart!=0) header |= PRESET_DICT; + header+=31-(header % 31); + + status=BUSY_STATE; + putShortMSB(header); + + + // Save the adler32 of the preset dictionary: + if(strstart!=0){ + putShortMSB((int)(strm.adler>>16)); + putShortMSB((int)(strm.adler&0xffff)); + } + strm.adler=strm._adler.adler32(0, null, 0, 0); + } + + // Flush as much pending output as possible + if(pending != 0) { + strm.flush_pending(); + if(strm.avail_out == 0) { + //System.out.println(" avail_out==0"); + // Since avail_out is 0, deflate will be called again with + // more output space, but possibly with both pending and + // avail_in equal to zero. There won't be anything to do, + // but this is not an error situation so make sure we + // return OK instead of BUF_ERROR at next call of deflate: + last_flush = -1; + return Z_OK; + } + + // Make sure there is something to do and avoid duplicate consecutive + // flushes. For repeated and useless calls with Z_FINISH, we keep + // returning Z_STREAM_END instead of Z_BUFF_ERROR. + } + else if(strm.avail_in==0 && flush <= old_flush && + flush != Z_FINISH) { + strm.msg=z_errmsg[Z_NEED_DICT-(Z_BUF_ERROR)]; + return Z_BUF_ERROR; + } + + // User must not provide more input after the first FINISH: + if(status == FINISH_STATE && strm.avail_in != 0) { + strm.msg=z_errmsg[Z_NEED_DICT-(Z_BUF_ERROR)]; + return Z_BUF_ERROR; + } + + // Start a new block or continue the current one. + if(strm.avail_in!=0 || lookahead!=0 || + (flush != Z_NO_FLUSH && status != FINISH_STATE)) { + int bstate=-1; + switch(config_table[level].func){ + case STORED: + bstate = deflate_stored(flush); + break; + case FAST: + bstate = deflate_fast(flush); + break; + case SLOW: + bstate = deflate_slow(flush); + break; + default: + break; + } + + if (bstate==FinishStarted || bstate==FinishDone) { + status = FINISH_STATE; + } + if (bstate==NeedMore || bstate==FinishStarted) { + if(strm.avail_out == 0) { + last_flush = -1; // avoid BUF_ERROR next call, see above + } + return Z_OK; + // If flush != Z_NO_FLUSH && avail_out == 0, the next call + // of deflate should use the same flush parameter to make sure + // that the flush is complete. So we don't have to output an + // empty block here, this will be done at next call. This also + // ensures that for a very small output buffer, we emit at most + // one empty block. + } + + if (bstate==BlockDone) { + if(flush == Z_PARTIAL_FLUSH) { + _tr_align(); + } + else { // FULL_FLUSH or SYNC_FLUSH + _tr_stored_block(0, 0, false); + // For a full flush, this empty block will be recognized + // as a special marker by inflate_sync(). + if(flush == Z_FULL_FLUSH) { + //state.head[s.hash_size-1]=0; + for(int i=0; i>16)); + putShortMSB((int)(strm.adler&0xffff)); + strm.flush_pending(); + + // If avail_out is zero, the application will call deflate again + // to flush the rest. + noheader = -1; // write the trailer only once! + return pending != 0 ? Z_OK : Z_STREAM_END; + } + } +} \ No newline at end of file diff --git a/bc-sharp-crypto/src/util/zlib/InfBlocks.cs b/bc-sharp-crypto/src/util/zlib/InfBlocks.cs new file mode 100644 index 0000000..479d9b5 --- /dev/null +++ b/bc-sharp-crypto/src/util/zlib/InfBlocks.cs @@ -0,0 +1,618 @@ +using System; +/* + * $Id: InfBlocks.cs,v 1.2 2008-05-10 09:35:40 bouncy Exp $ + * +Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +namespace Org.BouncyCastle.Utilities.Zlib { + + internal sealed class InfBlocks{ + private const int MANY=1440; + + // And'ing with mask[n] masks the lower n bits + private static readonly int[] inflate_mask = { + 0x00000000, 0x00000001, 0x00000003, 0x00000007, 0x0000000f, + 0x0000001f, 0x0000003f, 0x0000007f, 0x000000ff, 0x000001ff, + 0x000003ff, 0x000007ff, 0x00000fff, 0x00001fff, 0x00003fff, + 0x00007fff, 0x0000ffff + }; + + // Table for deflate from PKZIP's appnote.txt. + static readonly int[] border = { // Order of the bit length code lengths + 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 + }; + + private const int Z_OK=0; + private const int Z_STREAM_END=1; + private const int Z_NEED_DICT=2; + private const int Z_ERRNO=-1; + private const int Z_STREAM_ERROR=-2; + private const int Z_DATA_ERROR=-3; + private const int Z_MEM_ERROR=-4; + private const int Z_BUF_ERROR=-5; + private const int Z_VERSION_ERROR=-6; + + private const int TYPE=0; // get type bits (3, including end bit) + private const int LENS=1; // get lengths for stored + private const int STORED=2;// processing stored block + private const int TABLE=3; // get table lengths + private const int BTREE=4; // get bit lengths tree for a dynamic block + private const int DTREE=5; // get length, distance trees for a dynamic block + private const int CODES=6; // processing fixed or dynamic block + private const int DRY=7; // output remaining window bytes + private const int DONE=8; // finished last block, done + private const int BAD=9; // ot a data error--stuck here + + internal int mode; // current inflate_block mode + + internal int left; // if STORED, bytes left to copy + + internal int table; // table lengths (14 bits) + internal int index; // index into blens (or border) + internal int[] blens; // bit lengths of codes + internal int[] bb=new int[1]; // bit length tree depth + internal int[] tb=new int[1]; // bit length decoding tree + + internal InfCodes codes=new InfCodes(); // if CODES, current state + + int last; // true if this block is the last block + + // mode independent information + internal int bitk; // bits in bit buffer + internal int bitb; // bit buffer + internal int[] hufts; // single malloc for tree space + internal byte[] window; // sliding window + internal int end; // one byte after sliding window + internal int read; // window read pointer + internal int write; // window write pointer + internal Object checkfn; // check function + internal long check; // check on output + + internal InfTree inftree=new InfTree(); + + internal InfBlocks(ZStream z, Object checkfn, int w){ + hufts=new int[MANY*3]; + window=new byte[w]; + end=w; + this.checkfn = checkfn; + mode = TYPE; + reset(z, null); + } + + internal void reset(ZStream z, long[] c){ + if(c!=null) c[0]=check; + if(mode==BTREE || mode==DTREE){ + } + if(mode==CODES){ + codes.free(z); + } + mode=TYPE; + bitk=0; + bitb=0; + read=write=0; + + if(checkfn != null) + z.adler=check=z._adler.adler32(0L, null, 0, 0); + } + + internal int proc(ZStream z, int r){ + int t; // temporary storage + int b; // bit buffer + int k; // bits in bit buffer + int p; // input data pointer + int n; // bytes available there + int q; // output window write pointer + int m; { // bytes to end of window or read pointer + + // copy input/output information to locals (UPDATE macro restores) + p=z.next_in_index;n=z.avail_in;b=bitb;k=bitk;} { + q=write;m=(int)(q> 1){ + case 0: { // stored + b>>=(3);k-=(3);} + t = k & 7; { // go to byte boundary + + b>>=(t);k-=(t);} + mode = LENS; // get length of stored block + break; + case 1: { // fixed + int[] bl=new int[1]; + int[] bd=new int[1]; + int[][] tl=new int[1][]; + int[][] td=new int[1][]; + + InfTree.inflate_trees_fixed(bl, bd, tl, td, z); + codes.init(bl[0], bd[0], tl[0], 0, td[0], 0, z); + } { + + b>>=(3);k-=(3);} + + mode = CODES; + break; + case 2: { // dynamic + + b>>=(3);k-=(3);} + + mode = TABLE; + break; + case 3: { // illegal + + b>>=(3);k-=(3);} + mode = BAD; + z.msg = "invalid block type"; + r = Z_DATA_ERROR; + + bitb=b; bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + } + break; + case LENS: + + while(k<(32)){ + if(n!=0){ + r=Z_OK; + } + else{ + bitb=b; bitk=k; + z.avail_in=n; + z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + }; + n--; + b|=(z.next_in[p++]&0xff)<> 16) & 0xffff) != (b & 0xffff)){ + mode = BAD; + z.msg = "invalid stored block lengths"; + r = Z_DATA_ERROR; + + bitb=b; bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + } + left = (b & 0xffff); + b = k = 0; // dump bits + mode = left!=0 ? STORED : (last!=0 ? DRY : TYPE); + break; + case STORED: + if (n == 0){ + bitb=b; bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + } + + if(m==0){ + if(q==end&&read!=0){ + q=0; m=(int)(qn) t = n; + if(t>m) t = m; + System.Array.Copy(z.next_in, p, window, q, t); + p += t; n -= t; + q += t; m -= t; + if ((left -= t) != 0) + break; + mode = last!=0 ? DRY : TYPE; + break; + case TABLE: + + while(k<(14)){ + if(n!=0){ + r=Z_OK; + } + else{ + bitb=b; bitk=k; + z.avail_in=n; + z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + }; + n--; + b|=(z.next_in[p++]&0xff)< 29 || ((t >> 5) & 0x1f) > 29) { + mode = BAD; + z.msg = "too many length or distance symbols"; + r = Z_DATA_ERROR; + + bitb=b; bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + } + t = 258 + (t & 0x1f) + ((t >> 5) & 0x1f); + if(blens==null || blens.Length>=(14);k-=(14);} + + index = 0; + mode = BTREE; + goto case BTREE; + case BTREE: + while (index < 4 + (table >> 10)){ + while(k<(3)){ + if(n!=0){ + r=Z_OK; + } + else{ + bitb=b; bitk=k; + z.avail_in=n; + z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + }; + n--; + b|=(z.next_in[p++]&0xff)<>=(3);k-=(3);} + } + + while(index < 19){ + blens[border[index++]] = 0; + } + + bb[0] = 7; + t = inftree.inflate_trees_bits(blens, bb, tb, hufts, z); + if (t != Z_OK){ + r = t; + if (r == Z_DATA_ERROR){ + blens=null; + mode = BAD; + } + + bitb=b; bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + } + + index = 0; + mode = DTREE; + goto case DTREE; + case DTREE: + while (true){ + t = table; + if(!(index < 258 + (t & 0x1f) + ((t >> 5) & 0x1f))){ + break; + } + + int i, j, c; + + t = bb[0]; + + while(k<(t)){ + if(n!=0){ + r=Z_OK; + } + else{ + bitb=b; bitk=k; + z.avail_in=n; + z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + }; + n--; + b|=(z.next_in[p++]&0xff)<>=(t);k-=(t); + blens[index++] = c; + } + else { // c == 16..18 + i = c == 18 ? 7 : c - 14; + j = c == 18 ? 11 : 3; + + while(k<(t+i)){ + if(n!=0){ + r=Z_OK; + } + else{ + bitb=b; bitk=k; + z.avail_in=n; + z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + }; + n--; + b|=(z.next_in[p++]&0xff)<>=(t);k-=(t); + + j += (b & inflate_mask[i]); + + b>>=(i);k-=(i); + + i = index; + t = table; + if (i + j > 258 + (t & 0x1f) + ((t >> 5) & 0x1f) || + (c == 16 && i < 1)){ + blens=null; + mode = BAD; + z.msg = "invalid bit length repeat"; + r = Z_DATA_ERROR; + + bitb=b; bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + } + + c = c == 16 ? blens[i-1] : 0; + do{ + blens[i++] = c; + } + while (--j!=0); + index = i; + } + } + + tb[0]=-1; { + int[] bl=new int[1]; + int[] bd=new int[1]; + int[] tl=new int[1]; + int[] td=new int[1]; + bl[0] = 9; // must be <= 9 for lookahead assumptions + bd[0] = 6; // must be <= 9 for lookahead assumptions + + t = table; + t = inftree.inflate_trees_dynamic(257 + (t & 0x1f), + 1 + ((t >> 5) & 0x1f), + blens, bl, bd, tl, td, hufts, z); + + if (t != Z_OK){ + if (t == Z_DATA_ERROR){ + blens=null; + mode = BAD; + } + r = t; + + bitb=b; bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + } + codes.init(bl[0], bd[0], hufts, tl[0], hufts, td[0], z); + } + mode = CODES; + goto case CODES; + case CODES: + bitb=b; bitk=k; + z.avail_in=n; z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + + if ((r = codes.proc(this, z, r)) != Z_STREAM_END){ + return inflate_flush(z, r); + } + r = Z_OK; + codes.free(z); + + p=z.next_in_index; n=z.avail_in;b=bitb;k=bitk; + q=write;m=(int)(q z.avail_out) n = z.avail_out; + if (n!=0 && r == Z_BUF_ERROR) r = Z_OK; + + // update counters + z.avail_out -= n; + z.total_out += n; + + // update check information + if(checkfn != null) + z.adler=check=z._adler.adler32(check, window, q, n); + + // copy as far as end of window + System.Array.Copy(window, q, z.next_out, p, n); + p += n; + q += n; + + // see if more to copy at beginning of window + if (q == end){ + // wrap pointers + q = 0; + if (write == end) + write = 0; + + // compute bytes to copy + n = write - q; + if (n > z.avail_out) n = z.avail_out; + if (n!=0 && r == Z_BUF_ERROR) r = Z_OK; + + // update counters + z.avail_out -= n; + z.total_out += n; + + // update check information + if(checkfn != null) + z.adler=check=z._adler.adler32(check, window, q, n); + + // copy + System.Array.Copy(window, q, z.next_out, p, n); + p += n; + q += n; + } + + // update pointers + z.next_out_index = p; + read = q; + + // done + return r; + } + } +} \ No newline at end of file diff --git a/bc-sharp-crypto/src/util/zlib/InfCodes.cs b/bc-sharp-crypto/src/util/zlib/InfCodes.cs new file mode 100644 index 0000000..6fcafe4 --- /dev/null +++ b/bc-sharp-crypto/src/util/zlib/InfCodes.cs @@ -0,0 +1,611 @@ +using System; +/* + * $Id: InfCodes.cs,v 1.2 2008-05-10 09:35:40 bouncy Exp $ + * +Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +namespace Org.BouncyCastle.Utilities.Zlib { + + internal sealed class InfCodes{ + + private static readonly int[] inflate_mask = { + 0x00000000, 0x00000001, 0x00000003, 0x00000007, 0x0000000f, + 0x0000001f, 0x0000003f, 0x0000007f, 0x000000ff, 0x000001ff, + 0x000003ff, 0x000007ff, 0x00000fff, 0x00001fff, 0x00003fff, + 0x00007fff, 0x0000ffff + }; + + private const int Z_OK=0; + private const int Z_STREAM_END=1; + private const int Z_NEED_DICT=2; + private const int Z_ERRNO=-1; + private const int Z_STREAM_ERROR=-2; + private const int Z_DATA_ERROR=-3; + private const int Z_MEM_ERROR=-4; + private const int Z_BUF_ERROR=-5; + private const int Z_VERSION_ERROR=-6; + + // waiting for "i:"=input, + // "o:"=output, + // "x:"=nothing + private const int START=0; // x: set up for LEN + private const int LEN=1; // i: get length/literal/eob next + private const int LENEXT=2; // i: getting length extra (have base) + private const int DIST=3; // i: get distance next + private const int DISTEXT=4;// i: getting distance extra + private const int COPY=5; // o: copying bytes in window, waiting for space + private const int LIT=6; // o: got literal, waiting for output space + private const int WASH=7; // o: got eob, possibly still output waiting + private const int END=8; // x: got eob and all data flushed + private const int BADCODE=9;// x: got error + + int mode; // current inflate_codes mode + + // mode dependent information + int len; + + int[] tree; // pointer into tree + int tree_index=0; + int need; // bits needed + + int lit; + + // if EXT or COPY, where and how much + int get; // bits to get for extra + int dist; // distance back to copy from + + byte lbits; // ltree bits decoded per branch + byte dbits; // dtree bits decoder per branch + int[] ltree; // literal/length/eob tree + int ltree_index; // literal/length/eob tree + int[] dtree; // distance tree + int dtree_index; // distance tree + + internal InfCodes(){ + } + internal void init(int bl, int bd, + int[] tl, int tl_index, + int[] td, int td_index, ZStream z){ + mode=START; + lbits=(byte)bl; + dbits=(byte)bd; + ltree=tl; + ltree_index=tl_index; + dtree = td; + dtree_index=td_index; + tree=null; + } + + internal int proc(InfBlocks s, ZStream z, int r){ + int j; // temporary storage + int tindex; // temporary pointer + int e; // extra bits or operation + int b=0; // bit buffer + int k=0; // bits in bit buffer + int p=0; // input data pointer + int n; // bytes available there + int q; // output window write pointer + int m; // bytes to end of window or read pointer + int f; // pointer to copy strings from + + // copy input/output information to locals (UPDATE macro restores) + p=z.next_in_index;n=z.avail_in;b=s.bitb;k=s.bitk; + q=s.write;m=q= 258 && n >= 10){ + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + r = inflate_fast(lbits, dbits, + ltree, ltree_index, + dtree, dtree_index, + s, z); + + p=z.next_in_index;n=z.avail_in;b=s.bitb;k=s.bitk; + q=s.write;m=q>=(tree[tindex+1]); + k-=(tree[tindex+1]); + + e=tree[tindex]; + + if(e == 0){ // literal + lit = tree[tindex+2]; + mode = LIT; + break; + } + if((e & 16)!=0 ){ // length + get = e & 15; + len = tree[tindex+2]; + mode = LENEXT; + break; + } + if ((e & 64) == 0){ // next table + need = e; + tree_index = tindex/3+tree[tindex+2]; + break; + } + if ((e & 32)!=0){ // end of block + mode = WASH; + break; + } + mode = BADCODE; // invalid code + z.msg = "invalid literal/length code"; + r = Z_DATA_ERROR; + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + return s.inflate_flush(z,r); + + case LENEXT: // i: getting length extra (have base) + j = get; + + while(k<(j)){ + if(n!=0)r=Z_OK; + else{ + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + return s.inflate_flush(z,r); + } + n--; b|=(z.next_in[p++]&0xff)<>=j; + k-=j; + + need = dbits; + tree = dtree; + tree_index=dtree_index; + mode = DIST; + goto case DIST; + case DIST: // i: get distance next + j = need; + + while(k<(j)){ + if(n!=0)r=Z_OK; + else{ + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + return s.inflate_flush(z,r); + } + n--; b|=(z.next_in[p++]&0xff)<>=tree[tindex+1]; + k-=tree[tindex+1]; + + e = (tree[tindex]); + if((e & 16)!=0){ // distance + get = e & 15; + dist = tree[tindex+2]; + mode = DISTEXT; + break; + } + if ((e & 64) == 0){ // next table + need = e; + tree_index = tindex/3 + tree[tindex+2]; + break; + } + mode = BADCODE; // invalid code + z.msg = "invalid distance code"; + r = Z_DATA_ERROR; + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + return s.inflate_flush(z,r); + + case DISTEXT: // i: getting distance extra + j = get; + + while(k<(j)){ + if(n!=0)r=Z_OK; + else{ + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + return s.inflate_flush(z,r); + } + n--; b|=(z.next_in[p++]&0xff)<>=j; + k-=j; + + mode = COPY; + goto case COPY; + case COPY: // o: copying bytes in window, waiting for space + f = q - dist; + while(f < 0){ // modulo window size-"while" instead + f += s.end; // of "if" handles invalid distances + } + while (len!=0){ + + if(m==0){ + if(q==s.end&&s.read!=0){q=0;m=q 7){ // return unused byte, if any + k -= 8; + n++; + p--; // can always return one + } + + s.write=q; r=s.inflate_flush(z,r); + q=s.write;m=q= 258 && n >= 10 + // get literal/length code + while(k<(20)){ // max bits for literal/length code + n--; + b|=(z.next_in[p++]&0xff)<>=(tp[tp_index_t_3+1]); k-=(tp[tp_index_t_3+1]); + + s.window[q++] = (byte)tp[tp_index_t_3+2]; + m--; + continue; + } + do { + + b>>=(tp[tp_index_t_3+1]); k-=(tp[tp_index_t_3+1]); + + if((e&16)!=0){ + e &= 15; + c = tp[tp_index_t_3+2] + ((int)b & inflate_mask[e]); + + b>>=e; k-=e; + + // decode distance base of block to copy + while(k<(15)){ // max bits for distance code + n--; + b|=(z.next_in[p++]&0xff)<>=(tp[tp_index_t_3+1]); k-=(tp[tp_index_t_3+1]); + + if((e&16)!=0){ + // get extra bits to add to distance base + e &= 15; + while(k<(e)){ // get extra bits (up to 13) + n--; + b|=(z.next_in[p++]&0xff)<>=(e); k-=(e); + + // do the copy + m -= c; + if (q >= d){ // offset before dest + // just copy + r=q-d; + if(q-r>0 && 2>(q-r)){ + s.window[q++]=s.window[r++]; // minimum count is three, + s.window[q++]=s.window[r++]; // so unroll loop a little + c-=2; + } + else{ + System.Array.Copy(s.window, r, s.window, q, 2); + q+=2; r+=2; c-=2; + } + } + else{ // else offset after destination + r=q-d; + do{ + r+=s.end; // force pointer in window + }while(r<0); // covers invalid distances + e=s.end-r; + if(c>e){ // if source crosses, + c-=e; // wrapped copy + if(q-r>0 && e>(q-r)){ + do{s.window[q++] = s.window[r++];} + while(--e!=0); + } + else{ + System.Array.Copy(s.window, r, s.window, q, e); + q+=e; r+=e; e=0; + } + r = 0; // copy rest from start of window + } + + } + + // copy all or what's left + if(q-r>0 && c>(q-r)){ + do{s.window[q++] = s.window[r++];} + while(--c!=0); + } + else{ + System.Array.Copy(s.window, r, s.window, q, c); + q+=c; r+=c; c=0; + } + break; + } + else if((e&64)==0){ + t+=tp[tp_index_t_3+2]; + t+=(b&inflate_mask[e]); + tp_index_t_3=(tp_index+t)*3; + e=tp[tp_index_t_3]; + } + else{ + z.msg = "invalid distance code"; + + c=z.avail_in-n;c=(k>>3)>3:c;n+=c;p-=c;k-=c<<3; + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + + return Z_DATA_ERROR; + } + } + while(true); + break; + } + + if((e&64)==0){ + t+=tp[tp_index_t_3+2]; + t+=(b&inflate_mask[e]); + tp_index_t_3=(tp_index+t)*3; + if((e=tp[tp_index_t_3])==0){ + + b>>=(tp[tp_index_t_3+1]); k-=(tp[tp_index_t_3+1]); + + s.window[q++]=(byte)tp[tp_index_t_3+2]; + m--; + break; + } + } + else if((e&32)!=0){ + + c=z.avail_in-n;c=(k>>3)>3:c;n+=c;p-=c;k-=c<<3; + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + + return Z_STREAM_END; + } + else{ + z.msg="invalid literal/length code"; + + c=z.avail_in-n;c=(k>>3)>3:c;n+=c;p-=c;k-=c<<3; + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + + return Z_DATA_ERROR; + } + } + while(true); + } + while(m>=258 && n>= 10); + + // not enough input or output--restore pointers and return + c=z.avail_in-n;c=(k>>3)>3:c;n+=c;p-=c;k-=c<<3; + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + + return Z_OK; + } + } +} \ No newline at end of file diff --git a/bc-sharp-crypto/src/util/zlib/InfTree.cs b/bc-sharp-crypto/src/util/zlib/InfTree.cs new file mode 100644 index 0000000..6ed7d19 --- /dev/null +++ b/bc-sharp-crypto/src/util/zlib/InfTree.cs @@ -0,0 +1,523 @@ +using System; +/* + * $Id: InfTree.cs,v 1.2 2008-05-10 09:35:40 bouncy Exp $ + * +Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +namespace Org.BouncyCastle.Utilities.Zlib { + + internal sealed class InfTree{ + + private const int MANY=1440; + + private const int Z_OK=0; + private const int Z_STREAM_END=1; + private const int Z_NEED_DICT=2; + private const int Z_ERRNO=-1; + private const int Z_STREAM_ERROR=-2; + private const int Z_DATA_ERROR=-3; + private const int Z_MEM_ERROR=-4; + private const int Z_BUF_ERROR=-5; + private const int Z_VERSION_ERROR=-6; + + private const int fixed_bl = 9; + private const int fixed_bd = 5; + + static readonly int[] fixed_tl = { + 96,7,256, 0,8,80, 0,8,16, 84,8,115, + 82,7,31, 0,8,112, 0,8,48, 0,9,192, + 80,7,10, 0,8,96, 0,8,32, 0,9,160, + 0,8,0, 0,8,128, 0,8,64, 0,9,224, + 80,7,6, 0,8,88, 0,8,24, 0,9,144, + 83,7,59, 0,8,120, 0,8,56, 0,9,208, + 81,7,17, 0,8,104, 0,8,40, 0,9,176, + 0,8,8, 0,8,136, 0,8,72, 0,9,240, + 80,7,4, 0,8,84, 0,8,20, 85,8,227, + 83,7,43, 0,8,116, 0,8,52, 0,9,200, + 81,7,13, 0,8,100, 0,8,36, 0,9,168, + 0,8,4, 0,8,132, 0,8,68, 0,9,232, + 80,7,8, 0,8,92, 0,8,28, 0,9,152, + 84,7,83, 0,8,124, 0,8,60, 0,9,216, + 82,7,23, 0,8,108, 0,8,44, 0,9,184, + 0,8,12, 0,8,140, 0,8,76, 0,9,248, + 80,7,3, 0,8,82, 0,8,18, 85,8,163, + 83,7,35, 0,8,114, 0,8,50, 0,9,196, + 81,7,11, 0,8,98, 0,8,34, 0,9,164, + 0,8,2, 0,8,130, 0,8,66, 0,9,228, + 80,7,7, 0,8,90, 0,8,26, 0,9,148, + 84,7,67, 0,8,122, 0,8,58, 0,9,212, + 82,7,19, 0,8,106, 0,8,42, 0,9,180, + 0,8,10, 0,8,138, 0,8,74, 0,9,244, + 80,7,5, 0,8,86, 0,8,22, 192,8,0, + 83,7,51, 0,8,118, 0,8,54, 0,9,204, + 81,7,15, 0,8,102, 0,8,38, 0,9,172, + 0,8,6, 0,8,134, 0,8,70, 0,9,236, + 80,7,9, 0,8,94, 0,8,30, 0,9,156, + 84,7,99, 0,8,126, 0,8,62, 0,9,220, + 82,7,27, 0,8,110, 0,8,46, 0,9,188, + 0,8,14, 0,8,142, 0,8,78, 0,9,252, + 96,7,256, 0,8,81, 0,8,17, 85,8,131, + 82,7,31, 0,8,113, 0,8,49, 0,9,194, + 80,7,10, 0,8,97, 0,8,33, 0,9,162, + 0,8,1, 0,8,129, 0,8,65, 0,9,226, + 80,7,6, 0,8,89, 0,8,25, 0,9,146, + 83,7,59, 0,8,121, 0,8,57, 0,9,210, + 81,7,17, 0,8,105, 0,8,41, 0,9,178, + 0,8,9, 0,8,137, 0,8,73, 0,9,242, + 80,7,4, 0,8,85, 0,8,21, 80,8,258, + 83,7,43, 0,8,117, 0,8,53, 0,9,202, + 81,7,13, 0,8,101, 0,8,37, 0,9,170, + 0,8,5, 0,8,133, 0,8,69, 0,9,234, + 80,7,8, 0,8,93, 0,8,29, 0,9,154, + 84,7,83, 0,8,125, 0,8,61, 0,9,218, + 82,7,23, 0,8,109, 0,8,45, 0,9,186, + 0,8,13, 0,8,141, 0,8,77, 0,9,250, + 80,7,3, 0,8,83, 0,8,19, 85,8,195, + 83,7,35, 0,8,115, 0,8,51, 0,9,198, + 81,7,11, 0,8,99, 0,8,35, 0,9,166, + 0,8,3, 0,8,131, 0,8,67, 0,9,230, + 80,7,7, 0,8,91, 0,8,27, 0,9,150, + 84,7,67, 0,8,123, 0,8,59, 0,9,214, + 82,7,19, 0,8,107, 0,8,43, 0,9,182, + 0,8,11, 0,8,139, 0,8,75, 0,9,246, + 80,7,5, 0,8,87, 0,8,23, 192,8,0, + 83,7,51, 0,8,119, 0,8,55, 0,9,206, + 81,7,15, 0,8,103, 0,8,39, 0,9,174, + 0,8,7, 0,8,135, 0,8,71, 0,9,238, + 80,7,9, 0,8,95, 0,8,31, 0,9,158, + 84,7,99, 0,8,127, 0,8,63, 0,9,222, + 82,7,27, 0,8,111, 0,8,47, 0,9,190, + 0,8,15, 0,8,143, 0,8,79, 0,9,254, + 96,7,256, 0,8,80, 0,8,16, 84,8,115, + 82,7,31, 0,8,112, 0,8,48, 0,9,193, + + 80,7,10, 0,8,96, 0,8,32, 0,9,161, + 0,8,0, 0,8,128, 0,8,64, 0,9,225, + 80,7,6, 0,8,88, 0,8,24, 0,9,145, + 83,7,59, 0,8,120, 0,8,56, 0,9,209, + 81,7,17, 0,8,104, 0,8,40, 0,9,177, + 0,8,8, 0,8,136, 0,8,72, 0,9,241, + 80,7,4, 0,8,84, 0,8,20, 85,8,227, + 83,7,43, 0,8,116, 0,8,52, 0,9,201, + 81,7,13, 0,8,100, 0,8,36, 0,9,169, + 0,8,4, 0,8,132, 0,8,68, 0,9,233, + 80,7,8, 0,8,92, 0,8,28, 0,9,153, + 84,7,83, 0,8,124, 0,8,60, 0,9,217, + 82,7,23, 0,8,108, 0,8,44, 0,9,185, + 0,8,12, 0,8,140, 0,8,76, 0,9,249, + 80,7,3, 0,8,82, 0,8,18, 85,8,163, + 83,7,35, 0,8,114, 0,8,50, 0,9,197, + 81,7,11, 0,8,98, 0,8,34, 0,9,165, + 0,8,2, 0,8,130, 0,8,66, 0,9,229, + 80,7,7, 0,8,90, 0,8,26, 0,9,149, + 84,7,67, 0,8,122, 0,8,58, 0,9,213, + 82,7,19, 0,8,106, 0,8,42, 0,9,181, + 0,8,10, 0,8,138, 0,8,74, 0,9,245, + 80,7,5, 0,8,86, 0,8,22, 192,8,0, + 83,7,51, 0,8,118, 0,8,54, 0,9,205, + 81,7,15, 0,8,102, 0,8,38, 0,9,173, + 0,8,6, 0,8,134, 0,8,70, 0,9,237, + 80,7,9, 0,8,94, 0,8,30, 0,9,157, + 84,7,99, 0,8,126, 0,8,62, 0,9,221, + 82,7,27, 0,8,110, 0,8,46, 0,9,189, + 0,8,14, 0,8,142, 0,8,78, 0,9,253, + 96,7,256, 0,8,81, 0,8,17, 85,8,131, + 82,7,31, 0,8,113, 0,8,49, 0,9,195, + 80,7,10, 0,8,97, 0,8,33, 0,9,163, + 0,8,1, 0,8,129, 0,8,65, 0,9,227, + 80,7,6, 0,8,89, 0,8,25, 0,9,147, + 83,7,59, 0,8,121, 0,8,57, 0,9,211, + 81,7,17, 0,8,105, 0,8,41, 0,9,179, + 0,8,9, 0,8,137, 0,8,73, 0,9,243, + 80,7,4, 0,8,85, 0,8,21, 80,8,258, + 83,7,43, 0,8,117, 0,8,53, 0,9,203, + 81,7,13, 0,8,101, 0,8,37, 0,9,171, + 0,8,5, 0,8,133, 0,8,69, 0,9,235, + 80,7,8, 0,8,93, 0,8,29, 0,9,155, + 84,7,83, 0,8,125, 0,8,61, 0,9,219, + 82,7,23, 0,8,109, 0,8,45, 0,9,187, + 0,8,13, 0,8,141, 0,8,77, 0,9,251, + 80,7,3, 0,8,83, 0,8,19, 85,8,195, + 83,7,35, 0,8,115, 0,8,51, 0,9,199, + 81,7,11, 0,8,99, 0,8,35, 0,9,167, + 0,8,3, 0,8,131, 0,8,67, 0,9,231, + 80,7,7, 0,8,91, 0,8,27, 0,9,151, + 84,7,67, 0,8,123, 0,8,59, 0,9,215, + 82,7,19, 0,8,107, 0,8,43, 0,9,183, + 0,8,11, 0,8,139, 0,8,75, 0,9,247, + 80,7,5, 0,8,87, 0,8,23, 192,8,0, + 83,7,51, 0,8,119, 0,8,55, 0,9,207, + 81,7,15, 0,8,103, 0,8,39, 0,9,175, + 0,8,7, 0,8,135, 0,8,71, 0,9,239, + 80,7,9, 0,8,95, 0,8,31, 0,9,159, + 84,7,99, 0,8,127, 0,8,63, 0,9,223, + 82,7,27, 0,8,111, 0,8,47, 0,9,191, + 0,8,15, 0,8,143, 0,8,79, 0,9,255 + }; + static readonly int[] fixed_td = { + 80,5,1, 87,5,257, 83,5,17, 91,5,4097, + 81,5,5, 89,5,1025, 85,5,65, 93,5,16385, + 80,5,3, 88,5,513, 84,5,33, 92,5,8193, + 82,5,9, 90,5,2049, 86,5,129, 192,5,24577, + 80,5,2, 87,5,385, 83,5,25, 91,5,6145, + 81,5,7, 89,5,1537, 85,5,97, 93,5,24577, + 80,5,4, 88,5,769, 84,5,49, 92,5,12289, + 82,5,13, 90,5,3073, 86,5,193, 192,5,24577 + }; + + // Tables for deflate from PKZIP's appnote.txt. + static readonly int[] cplens = { // Copy lengths for literal codes 257..285 + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 + }; + + // see note #13 above about 258 + static readonly int[] cplext = { // Extra bits for literal codes 257..285 + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, + 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 112, 112 // 112==invalid + }; + + static readonly int[] cpdist = { // Copy offsets for distance codes 0..29 + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, + 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, + 8193, 12289, 16385, 24577 + }; + + static readonly int[] cpdext = { // Extra bits for distance codes + 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, + 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, + 12, 12, 13, 13}; + + // If BMAX needs to be larger than 16, then h and x[] should be uLong. + const int BMAX=15; // maximum bit length of any code + + int[] hn = null; // hufts used in space + int[] v = null; // work area for huft_build + int[] c = null; // bit length count table + int[] r = null; // table entry for structure assignment + int[] u = null; // table stack + int[] x = null; // bit offsets, then code stack + + private int huft_build(int[] b, // code lengths in bits (all assumed <= BMAX) + int bindex, + int n, // number of codes (assumed <= 288) + int s, // number of simple-valued codes (0..s-1) + int[] d, // list of base values for non-simple codes + int[] e, // list of extra bits for non-simple codes + int[] t, // result: starting table + int[] m, // maximum lookup bits, returns actual + int[] hp,// space for trees + int[] hn,// hufts used in space + int[] v // working area: values in order of bit length + ){ + // Given a list of code lengths and a maximum table size, make a set of + // tables to decode that set of codes. Return Z_OK on success, Z_BUF_ERROR + // if the given code set is incomplete (the tables are still built in this + // case), Z_DATA_ERROR if the input is invalid (an over-subscribed set of + // lengths), or Z_MEM_ERROR if not enough memory. + + int a; // counter for codes of length k + int f; // i repeats in table every f entries + int g; // maximum code length + int h; // table level + int i; // counter, current code + int j; // counter + int k; // number of bits in current code + int l; // bits per table (returned in m) + int mask; // (1 << w) - 1, to avoid cc -O bug on HP + int p; // pointer into c[], b[], or v[] + int q; // points to current table + int w; // bits before this table == (l * h) + int xp; // pointer into x + int y; // number of dummy codes added + int z; // number of entries in current table + + // Generate counts for each bit length + + p = 0; i = n; + do { + c[b[bindex+p]]++; p++; i--; // assume all entries <= BMAX + }while(i!=0); + + if(c[0] == n){ // null input--all zero length codes + t[0] = -1; + m[0] = 0; + return Z_OK; + } + + // Find minimum and maximum length, bound *m by those + l = m[0]; + for (j = 1; j <= BMAX; j++) + if(c[j]!=0) break; + k = j; // minimum code length + if(l < j){ + l = j; + } + for (i = BMAX; i!=0; i--){ + if(c[i]!=0) break; + } + g = i; // maximum code length + if(l > i){ + l = i; + } + m[0] = l; + + // Adjust last length count to fill out codes, if needed + for (y = 1 << j; j < i; j++, y <<= 1){ + if ((y -= c[j]) < 0){ + return Z_DATA_ERROR; + } + } + if ((y -= c[i]) < 0){ + return Z_DATA_ERROR; + } + c[i] += y; + + // Generate starting offsets into the value table for each length + x[1] = j = 0; + p = 1; xp = 2; + while (--i!=0) { // note that i == g from above + x[xp] = (j += c[p]); + xp++; + p++; + } + + // Make a table of values in order of bit lengths + i = 0; p = 0; + do { + if ((j = b[bindex+p]) != 0){ + v[x[j]++] = i; + } + p++; + } + while (++i < n); + n = x[g]; // set n to length of v + + // Generate the Huffman codes and for each, make the table entries + x[0] = i = 0; // first Huffman code is zero + p = 0; // grab values in bit order + h = -1; // no tables yet--level -1 + w = -l; // bits decoded == (l * h) + u[0] = 0; // just to keep compilers happy + q = 0; // ditto + z = 0; // ditto + + // go through the bit lengths (k already is bits in shortest code) + for (; k <= g; k++){ + a = c[k]; + while (a--!=0){ + // here i is the Huffman code of length k bits for value *p + // make tables up to required level + while (k > w + l){ + h++; + w += l; // previous table always l bits + // compute minimum size table less than or equal to l bits + z = g - w; + z = (z > l) ? l : z; // table size upper limit + if((f=1<<(j=k-w))>a+1){ // try a k-w bit table + // too few codes for k-w bit table + f -= a + 1; // deduct codes from patterns left + xp = k; + if(j < z){ + while (++j < z){ // try smaller tables up to z bits + if((f <<= 1) <= c[++xp]) + break; // enough codes to use up j bits + f -= c[xp]; // else deduct codes from patterns + } + } + } + z = 1 << j; // table entries for j-bit table + + // allocate new table + if (hn[0] + z > MANY){ // (note: doesn't matter for fixed) + return Z_DATA_ERROR; // overflow of MANY + } + u[h] = q = /*hp+*/ hn[0]; // DEBUG + hn[0] += z; + + // connect to last table, if there is one + if(h!=0){ + x[h]=i; // save pattern for backing up + r[0]=(byte)j; // bits in this table + r[1]=(byte)l; // bits to dump before this table + j=i>>(w - l); + r[2] = (int)(q - u[h-1] - j); // offset to this table + System.Array.Copy(r, 0, hp, (u[h-1]+j)*3, 3); // connect to last table + } + else{ + t[0] = q; // first table is returned result + } + } + + // set up table entry in r + r[1] = (byte)(k - w); + if (p >= n){ + r[0] = 128 + 64; // out of values--invalid code + } + else if (v[p] < s){ + r[0] = (byte)(v[p] < 256 ? 0 : 32 + 64); // 256 is end-of-block + r[2] = v[p++]; // simple code is just the value + } + else{ + r[0]=(byte)(e[v[p]-s]+16+64); // non-simple--look up in lists + r[2]=d[v[p++] - s]; + } + + // fill code-like entries with r + f=1<<(k-w); + for (j=i>>w;j>= 1){ + i ^= j; + } + i ^= j; + + // backup over finished tables + mask = (1 << w) - 1; // needed on HP, cc -O bug + while ((i & mask) != x[h]){ + h--; // don't need to update q + w -= l; + mask = (1 << w) - 1; + } + } + } + // Return Z_BUF_ERROR if we were given an incomplete table + return y != 0 && g != 1 ? Z_BUF_ERROR : Z_OK; + } + + internal int inflate_trees_bits(int[] c, // 19 code lengths + int[] bb, // bits tree desired/actual depth + int[] tb, // bits tree result + int[] hp, // space for trees + ZStream z // for messages + ){ + int result; + initWorkArea(19); + hn[0]=0; + result = huft_build(c, 0, 19, 19, null, null, tb, bb, hp, hn, v); + + if(result == Z_DATA_ERROR){ + z.msg = "oversubscribed dynamic bit lengths tree"; + } + else if(result == Z_BUF_ERROR || bb[0] == 0){ + z.msg = "incomplete dynamic bit lengths tree"; + result = Z_DATA_ERROR; + } + return result; + } + + internal int inflate_trees_dynamic(int nl, // number of literal/length codes + int nd, // number of distance codes + int[] c, // that many (total) code lengths + int[] bl, // literal desired/actual bit depth + int[] bd, // distance desired/actual bit depth + int[] tl, // literal/length tree result + int[] td, // distance tree result + int[] hp, // space for trees + ZStream z // for messages + ){ + int result; + + // build literal/length tree + initWorkArea(288); + hn[0]=0; + result = huft_build(c, 0, nl, 257, cplens, cplext, tl, bl, hp, hn, v); + if (result != Z_OK || bl[0] == 0){ + if(result == Z_DATA_ERROR){ + z.msg = "oversubscribed literal/length tree"; + } + else if (result != Z_MEM_ERROR){ + z.msg = "incomplete literal/length tree"; + result = Z_DATA_ERROR; + } + return result; + } + + // build distance tree + initWorkArea(288); + result = huft_build(c, nl, nd, 0, cpdist, cpdext, td, bd, hp, hn, v); + + if (result != Z_OK || (bd[0] == 0 && nl > 257)){ + if (result == Z_DATA_ERROR){ + z.msg = "oversubscribed distance tree"; + } + else if (result == Z_BUF_ERROR) { + z.msg = "incomplete distance tree"; + result = Z_DATA_ERROR; + } + else if (result != Z_MEM_ERROR){ + z.msg = "empty distance tree with lengths"; + result = Z_DATA_ERROR; + } + return result; + } + + return Z_OK; + } + + internal static int inflate_trees_fixed(int[] bl, //literal desired/actual bit depth + int[] bd, //distance desired/actual bit depth + int[][] tl,//literal/length tree result + int[][] td,//distance tree result + ZStream z //for memory allocation + ){ + bl[0]=fixed_bl; + bd[0]=fixed_bd; + tl[0]=fixed_tl; + td[0]=fixed_td; + return Z_OK; + } + + private void initWorkArea(int vsize){ + if(hn==null){ + hn=new int[1]; + v=new int[vsize]; + c=new int[BMAX+1]; + r=new int[3]; + u=new int[BMAX]; + x=new int[BMAX+1]; + } + if(v.Lengthstate); + return Z_OK; + } + + internal int inflateInit(ZStream z, int w){ + z.msg = null; + blocks = null; + + // handle undocumented nowrap option (no zlib header or check) + nowrap = 0; + if(w < 0){ + w = - w; + nowrap = 1; + } + + // set window size + if(w<8 ||w>15){ + inflateEnd(z); + return Z_STREAM_ERROR; + } + wbits=w; + + z.istate.blocks=new InfBlocks(z, + z.istate.nowrap!=0 ? null : this, + 1<>4)+8>z.istate.wbits){ + z.istate.mode = BAD; + z.msg="invalid window size"; + z.istate.marker = 5; // can't try inflateSync + break; + } + z.istate.mode=FLAG; + goto case FLAG; + case FLAG: + + if(z.avail_in==0)return r;r=f; + + z.avail_in--; z.total_in++; + b = (z.next_in[z.next_in_index++])&0xff; + + if((((z.istate.method << 8)+b) % 31)!=0){ + z.istate.mode = BAD; + z.msg = "incorrect header check"; + z.istate.marker = 5; // can't try inflateSync + break; + } + + if((b&PRESET_DICT)==0){ + z.istate.mode = BLOCKS; + break; + } + z.istate.mode = DICT4; + goto case DICT4; + case DICT4: + + if(z.avail_in==0)return r;r=f; + + z.avail_in--; z.total_in++; + z.istate.need=((z.next_in[z.next_in_index++]&0xff)<<24)&0xff000000L; + z.istate.mode=DICT3; + goto case DICT3; + case DICT3: + + if(z.avail_in==0)return r;r=f; + + z.avail_in--; z.total_in++; + z.istate.need+=((z.next_in[z.next_in_index++]&0xff)<<16)&0xff0000L; + z.istate.mode=DICT2; + goto case DICT2; + case DICT2: + + if(z.avail_in==0)return r;r=f; + + z.avail_in--; z.total_in++; + z.istate.need+=((z.next_in[z.next_in_index++]&0xff)<<8)&0xff00L; + z.istate.mode=DICT1; + goto case DICT1; + case DICT1: + + if(z.avail_in==0)return r;r=f; + + z.avail_in--; z.total_in++; + z.istate.need += (z.next_in[z.next_in_index++]&0xffL); + z.adler = z.istate.need; + z.istate.mode = DICT0; + return Z_NEED_DICT; + case DICT0: + z.istate.mode = BAD; + z.msg = "need dictionary"; + z.istate.marker = 0; // can try inflateSync + return Z_STREAM_ERROR; + case BLOCKS: + + r = z.istate.blocks.proc(z, r); + if(r == Z_DATA_ERROR){ + z.istate.mode = BAD; + z.istate.marker = 0; // can try inflateSync + break; + } + if(r == Z_OK){ + r = f; + } + if(r != Z_STREAM_END){ + return r; + } + r = f; + z.istate.blocks.reset(z, z.istate.was); + if(z.istate.nowrap!=0){ + z.istate.mode=DONE; + break; + } + z.istate.mode=CHECK4; + goto case CHECK4; + case CHECK4: + + if(z.avail_in==0)return r;r=f; + + z.avail_in--; z.total_in++; + z.istate.need=((z.next_in[z.next_in_index++]&0xff)<<24)&0xff000000L; + z.istate.mode=CHECK3; + goto case CHECK3; + case CHECK3: + + if(z.avail_in==0)return r;r=f; + + z.avail_in--; z.total_in++; + z.istate.need+=((z.next_in[z.next_in_index++]&0xff)<<16)&0xff0000L; + z.istate.mode = CHECK2; + goto case CHECK2; + case CHECK2: + + if(z.avail_in==0)return r;r=f; + + z.avail_in--; z.total_in++; + z.istate.need+=((z.next_in[z.next_in_index++]&0xff)<<8)&0xff00L; + z.istate.mode = CHECK1; + goto case CHECK1; + case CHECK1: + + if(z.avail_in==0)return r;r=f; + + z.avail_in--; z.total_in++; + z.istate.need+=(z.next_in[z.next_in_index++]&0xffL); + + if(((int)(z.istate.was[0])) != ((int)(z.istate.need))){ + z.istate.mode = BAD; + z.msg = "incorrect data check"; + z.istate.marker = 5; // can't try inflateSync + break; + } + + z.istate.mode = DONE; + goto case DONE; + case DONE: + return Z_STREAM_END; + case BAD: + return Z_DATA_ERROR; + default: + return Z_STREAM_ERROR; + } + } + } + + + internal int inflateSetDictionary(ZStream z, byte[] dictionary, int dictLength){ + int index=0; + int length = dictLength; + if(z==null || z.istate == null|| z.istate.mode != DICT0) + return Z_STREAM_ERROR; + + if(z._adler.adler32(1L, dictionary, 0, dictLength)!=z.adler){ + return Z_DATA_ERROR; + } + + z.adler = z._adler.adler32(0, null, 0, 0); + + if(length >= (1<>7)]); + } + + internal short[] dyn_tree; // the dynamic tree + internal int max_code; // largest code with non zero frequency + internal StaticTree stat_desc; // the corresponding static tree + + // Compute the optimal bit lengths for a tree and update the total bit length + // for the current block. + // IN assertion: the fields freq and dad are set, heap[heap_max] and + // above are the tree nodes sorted by increasing frequency. + // OUT assertions: the field len is set to the optimal bit length, the + // array bl_count contains the frequencies for each bit length. + // The length opt_len is updated; static_len is also updated if stree is + // not null. + internal void gen_bitlen(Deflate s){ + short[] tree = dyn_tree; + short[] stree = stat_desc.static_tree; + int[] extra = stat_desc.extra_bits; + int based = stat_desc.extra_base; + int max_length = stat_desc.max_length; + int h; // heap index + int n, m; // iterate over the tree elements + int bits; // bit length + int xbits; // extra bits + short f; // frequency + int overflow = 0; // number of elements with bit length too large + + for (bits = 0; bits <= MAX_BITS; bits++) s.bl_count[bits] = 0; + + // In a first pass, compute the optimal bit lengths (which may + // overflow in the case of the bit length tree). + tree[s.heap[s.heap_max]*2+1] = 0; // root of the heap + + for(h=s.heap_max+1; h max_length){ bits = max_length; overflow++; } + tree[n*2+1] = (short)bits; + // We overwrite tree[n*2+1] which is no longer needed + + if (n > max_code) continue; // not a leaf node + + s.bl_count[bits]++; + xbits = 0; + if (n >= based) xbits = extra[n-based]; + f = tree[n*2]; + s.opt_len += f * (bits + xbits); + if (stree!=null) s.static_len += f * (stree[n*2+1] + xbits); + } + if (overflow == 0) return; + + // This happens for example on obj2 and pic of the Calgary corpus + // Find the first bit length which could increase: + do { + bits = max_length-1; + while(s.bl_count[bits]==0) bits--; + s.bl_count[bits]--; // move one leaf down the tree + s.bl_count[bits+1]+=2; // move one overflow item as its brother + s.bl_count[max_length]--; + // The brother of the overflow item also moves one step up, + // but this does not affect bl_count[max_length] + overflow -= 2; + } + while (overflow > 0); + + for (bits = max_length; bits != 0; bits--) { + n = s.bl_count[bits]; + while (n != 0) { + m = s.heap[--h]; + if (m > max_code) continue; + if (tree[m*2+1] != bits) { + s.opt_len += (int)(((long)bits - (long)tree[m*2+1])*(long)tree[m*2]); + tree[m*2+1] = (short)bits; + } + n--; + } + } + } + + // Construct one Huffman tree and assigns the code bit strings and lengths. + // Update the total bit length for the current block. + // IN assertion: the field freq is set for all tree elements. + // OUT assertions: the fields len and code are set to the optimal bit length + // and corresponding code. The length opt_len is updated; static_len is + // also updated if stree is not null. The field max_code is set. + internal void build_tree(Deflate s){ + short[] tree=dyn_tree; + short[] stree=stat_desc.static_tree; + int elems=stat_desc.elems; + int n, m; // iterate over heap elements + int max_code=-1; // largest code with non zero frequency + int node; // new node being created + + // Construct the initial heap, with least frequent element in + // heap[1]. The sons of heap[n] are heap[2*n] and heap[2*n+1]. + // heap[0] is not used. + s.heap_len = 0; + s.heap_max = HEAP_SIZE; + + for(n=0; n=1; n--) + s.pqdownheap(tree, n); + + // Construct the Huffman tree by repeatedly combining the least two + // frequent nodes. + + node=elems; // next internal node of the tree + do{ + // n = node of least frequency + n=s.heap[1]; + s.heap[1]=s.heap[s.heap_len--]; + s.pqdownheap(tree, 1); + m=s.heap[1]; // m = node of next least frequency + + s.heap[--s.heap_max] = n; // keep the nodes sorted by frequency + s.heap[--s.heap_max] = m; + + // Create a new node father of n and m + tree[node*2] = (short)(tree[n*2] + tree[m*2]); + s.depth[node] = (byte)(System.Math.Max(s.depth[n],s.depth[m])+1); + tree[n*2+1] = tree[m*2+1] = (short)node; + + // and insert the new node in the heap + s.heap[1] = node++; + s.pqdownheap(tree, 1); + } + while(s.heap_len>=2); + + s.heap[--s.heap_max] = s.heap[1]; + + // At this point, the fields freq and dad are set. We can now + // generate the bit lengths. + + gen_bitlen(s); + + // The field len is now set, we can generate the bit codes + gen_codes(tree, max_code, s.bl_count); + } + + // Generate the codes for a given tree and bit counts (which need not be + // optimal). + // IN assertion: the array bl_count contains the bit length statistics for + // the given tree and the field len is set for all tree elements. + // OUT assertion: the field code is set for all tree elements of non + // zero code length. + internal static void gen_codes(short[] tree, // the tree to decorate + int max_code, // largest code with non zero frequency + short[] bl_count // number of codes at each bit length + ){ + short[] next_code=new short[MAX_BITS+1]; // next code value for each bit length + short code = 0; // running code value + int bits; // bit index + int n; // code index + + // The distribution counts are first used to generate the code values + // without bit reversal. + for (bits = 1; bits <= MAX_BITS; bits++) { + next_code[bits] = code = (short)((code + bl_count[bits-1]) << 1); + } + + // Check that the bit counts in bl_count are consistent. The last code + // must be all ones. + //Assert (code + bl_count[MAX_BITS]-1 == (1<>=1; + res<<=1; + } + while(--len>0); + return res>>1; + } + } +} \ No newline at end of file diff --git a/bc-sharp-crypto/src/util/zlib/ZDeflaterOutputStream.cs b/bc-sharp-crypto/src/util/zlib/ZDeflaterOutputStream.cs new file mode 100644 index 0000000..d0f0bcb --- /dev/null +++ b/bc-sharp-crypto/src/util/zlib/ZDeflaterOutputStream.cs @@ -0,0 +1,171 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Utilities.Zlib { + /// + /// Summary description for DeflaterOutputStream. + /// + [Obsolete("Use 'ZOutputStream' instead")] + public class ZDeflaterOutputStream : Stream { + protected ZStream z=new ZStream(); + protected int flushLevel=JZlib.Z_NO_FLUSH; + private const int BUFSIZE = 4192; + protected byte[] buf=new byte[BUFSIZE]; + private byte[] buf1=new byte[1]; + + protected Stream outp; + + public ZDeflaterOutputStream(Stream outp) : this(outp, 6, false) { + } + + public ZDeflaterOutputStream(Stream outp, int level) : this(outp, level, false) { + } + + public ZDeflaterOutputStream(Stream outp, int level, bool nowrap) { + this.outp=outp; + z.deflateInit(level, nowrap); + } + + + public override bool CanRead { + get { + // TODO: Add DeflaterOutputStream.CanRead getter implementation + return false; + } + } + + public override bool CanSeek { + get { + // TODO: Add DeflaterOutputStream.CanSeek getter implementation + return false; + } + } + + public override bool CanWrite { + get { + // TODO: Add DeflaterOutputStream.CanWrite getter implementation + return true; + } + } + + public override long Length { + get { + // TODO: Add DeflaterOutputStream.Length getter implementation + return 0; + } + } + + public override long Position { + get { + // TODO: Add DeflaterOutputStream.Position getter implementation + return 0; + } + set { + // TODO: Add DeflaterOutputStream.Position setter implementation + } + } + + public override void Write(byte[] b, int off, int len) { + if(len==0) + return; + int err; + z.next_in=b; + z.next_in_index=off; + z.avail_in=len; + do{ + z.next_out=buf; + z.next_out_index=0; + z.avail_out=BUFSIZE; + err=z.deflate(flushLevel); + if(err!=JZlib.Z_OK) + throw new IOException("deflating: "+z.msg); + if (z.avail_out < BUFSIZE) + { + outp.Write(buf, 0, BUFSIZE-z.avail_out); + } + } + while(z.avail_in>0 || z.avail_out==0); + } + + public override long Seek(long offset, SeekOrigin origin) { + // TODO: Add DeflaterOutputStream.Seek implementation + return 0; + } + + public override void SetLength(long value) { + // TODO: Add DeflaterOutputStream.SetLength implementation + + } + + public override int Read(byte[] buffer, int offset, int count) { + // TODO: Add DeflaterOutputStream.Read implementation + return 0; + } + + public override void Flush() { + outp.Flush(); + } + + public override void WriteByte(byte b) { + buf1[0]=(byte)b; + Write(buf1, 0, 1); + } + + public void Finish() { + int err; + do{ + z.next_out=buf; + z.next_out_index=0; + z.avail_out=BUFSIZE; + err=z.deflate(JZlib.Z_FINISH); + if(err!=JZlib.Z_STREAM_END && err != JZlib.Z_OK) + throw new IOException("deflating: "+z.msg); + if(BUFSIZE-z.avail_out>0){ + outp.Write(buf, 0, BUFSIZE-z.avail_out); + } + } + while(z.avail_in>0 || z.avail_out==0); + Flush(); + } + + public void End() { + if(z==null) + return; + z.deflateEnd(); + z.free(); + z=null; + } + +#if PORTABLE + protected override void Dispose(bool disposing) + { + if (disposing) + { + try{ + try{Finish();} + catch (IOException) {} + } + finally{ + End(); + Platform.Dispose(outp); + outp=null; + } + } + base.Dispose(disposing); + } +#else + public override void Close() { + try{ + try{Finish();} + catch (IOException) {} + } + finally{ + End(); + Platform.Dispose(outp); + outp=null; + } + base.Close(); + } +#endif + } +} diff --git a/bc-sharp-crypto/src/util/zlib/ZInflaterInputStream.cs b/bc-sharp-crypto/src/util/zlib/ZInflaterInputStream.cs new file mode 100644 index 0000000..ef742bb --- /dev/null +++ b/bc-sharp-crypto/src/util/zlib/ZInflaterInputStream.cs @@ -0,0 +1,140 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Utilities.Zlib { + /// + /// Summary description for DeflaterOutputStream. + /// + [Obsolete("Use 'ZInputStream' instead")] + public class ZInflaterInputStream : Stream { + protected ZStream z=new ZStream(); + protected int flushLevel=JZlib.Z_NO_FLUSH; + private const int BUFSIZE = 4192; + protected byte[] buf=new byte[BUFSIZE]; + private byte[] buf1=new byte[1]; + + protected Stream inp=null; + private bool nomoreinput=false; + + public ZInflaterInputStream(Stream inp) : this(inp, false) { + } + + public ZInflaterInputStream(Stream inp, bool nowrap) { + this.inp=inp; + z.inflateInit(nowrap); + z.next_in=buf; + z.next_in_index=0; + z.avail_in=0; + } + + public override bool CanRead { + get { + // TODO: Add DeflaterOutputStream.CanRead getter implementation + return true; + } + } + + public override bool CanSeek { + get { + // TODO: Add DeflaterOutputStream.CanSeek getter implementation + return false; + } + } + + public override bool CanWrite { + get { + // TODO: Add DeflaterOutputStream.CanWrite getter implementation + return false; + } + } + + public override long Length { + get { + // TODO: Add DeflaterOutputStream.Length getter implementation + return 0; + } + } + + public override long Position { + get { + // TODO: Add DeflaterOutputStream.Position getter implementation + return 0; + } + set { + // TODO: Add DeflaterOutputStream.Position setter implementation + } + } + + public override void Write(byte[] b, int off, int len) { + } + + public override long Seek(long offset, SeekOrigin origin) { + // TODO: Add DeflaterOutputStream.Seek implementation + return 0; + } + + public override void SetLength(long value) { + // TODO: Add DeflaterOutputStream.SetLength implementation + + } + + public override int Read(byte[] b, int off, int len) { + if(len==0) + return(0); + int err; + z.next_out=b; + z.next_out_index=off; + z.avail_out=len; + do { + if((z.avail_in==0)&&(!nomoreinput)) { // if buffer is empty and more input is avaiable, refill it + z.next_in_index=0; + z.avail_in=inp.Read(buf, 0, BUFSIZE);//(BUFSIZE 0) + { + output.Write(buf, 0, count); + } + } + while (z.avail_in > 0 || z.avail_out == 0); + + Flush(); + } + + public override void Flush() + { + output.Flush(); + } + + public virtual int FlushMode + { + get { return flushLevel; } + set { this.flushLevel = value; } + } + + public sealed override long Length { get { throw new NotSupportedException(); } } + public sealed override long Position + { + get { throw new NotSupportedException(); } + set { throw new NotSupportedException(); } + } + public sealed override int Read(byte[] buffer, int offset, int count) { throw new NotSupportedException(); } + public sealed override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } + public sealed override void SetLength(long value) { throw new NotSupportedException(); } + + public virtual long TotalIn + { + get { return z.total_in; } + } + + public virtual long TotalOut + { + get { return z.total_out; } + } + + public override void Write(byte[] b, int off, int len) + { + if (len == 0) + return; + + z.next_in = b; + z.next_in_index = off; + z.avail_in = len; + + do + { + z.next_out = buf; + z.next_out_index = 0; + z.avail_out = buf.Length; + + int err = compress + ? z.deflate(flushLevel) + : z.inflate(flushLevel); + + if (err != JZlib.Z_OK) + // TODO +// throw new ZStreamException((compress ? "de" : "in") + "flating: " + z.msg); + throw new IOException((compress ? "de" : "in") + "flating: " + z.msg); + + output.Write(buf, 0, buf.Length - z.avail_out); + } + while (z.avail_in > 0 || z.avail_out == 0); + } + + public override void WriteByte(byte b) + { + buf1[0] = b; + Write(buf1, 0, 1); + } + } +} diff --git a/bc-sharp-crypto/src/util/zlib/ZStream.cs b/bc-sharp-crypto/src/util/zlib/ZStream.cs new file mode 100644 index 0000000..7ff9614 --- /dev/null +++ b/bc-sharp-crypto/src/util/zlib/ZStream.cs @@ -0,0 +1,214 @@ +using System; +/* + * $Id: ZStream.cs,v 1.1 2006-07-31 13:59:26 bouncy Exp $ + * +Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +namespace Org.BouncyCastle.Utilities.Zlib { + + public sealed class ZStream{ + + private const int MAX_WBITS=15; // 32K LZ77 window + private const int DEF_WBITS=MAX_WBITS; + + private const int Z_NO_FLUSH=0; + private const int Z_PARTIAL_FLUSH=1; + private const int Z_SYNC_FLUSH=2; + private const int Z_FULL_FLUSH=3; + private const int Z_FINISH=4; + + private const int MAX_MEM_LEVEL=9; + + private const int Z_OK=0; + private const int Z_STREAM_END=1; + private const int Z_NEED_DICT=2; + private const int Z_ERRNO=-1; + private const int Z_STREAM_ERROR=-2; + private const int Z_DATA_ERROR=-3; + private const int Z_MEM_ERROR=-4; + private const int Z_BUF_ERROR=-5; + private const int Z_VERSION_ERROR=-6; + + public byte[] next_in; // next input byte + public int next_in_index; + public int avail_in; // number of bytes available at next_in + public long total_in; // total nb of input bytes read so far + + public byte[] next_out; // next output byte should be put there + public int next_out_index; + public int avail_out; // remaining free space at next_out + public long total_out; // total nb of bytes output so far + + public String msg; + + internal Deflate dstate; + internal Inflate istate; + + internal int data_type; // best guess about the data type: ascii or binary + + public long adler; + internal Adler32 _adler=new Adler32(); + + public int inflateInit(){ + return inflateInit(DEF_WBITS); + } + public int inflateInit(bool nowrap){ + return inflateInit(DEF_WBITS, nowrap); + } + public int inflateInit(int w){ + return inflateInit(w, false); + } + + public int inflateInit(int w, bool nowrap){ + istate=new Inflate(); + return istate.inflateInit(this, nowrap?-w:w); + } + + public int inflate(int f){ + if(istate==null) return Z_STREAM_ERROR; + return istate.inflate(this, f); + } + public int inflateEnd(){ + if(istate==null) return Z_STREAM_ERROR; + int ret=istate.inflateEnd(this); + istate = null; + return ret; + } + public int inflateSync(){ + if(istate == null) + return Z_STREAM_ERROR; + return istate.inflateSync(this); + } + public int inflateSetDictionary(byte[] dictionary, int dictLength){ + if(istate == null) + return Z_STREAM_ERROR; + return istate.inflateSetDictionary(this, dictionary, dictLength); + } + + public int deflateInit(int level){ + return deflateInit(level, MAX_WBITS); + } + public int deflateInit(int level, bool nowrap){ + return deflateInit(level, MAX_WBITS, nowrap); + } + public int deflateInit(int level, int bits){ + return deflateInit(level, bits, false); + } + public int deflateInit(int level, int bits, bool nowrap){ + dstate=new Deflate(); + return dstate.deflateInit(this, level, nowrap?-bits:bits); + } + public int deflate(int flush){ + if(dstate==null){ + return Z_STREAM_ERROR; + } + return dstate.deflate(this, flush); + } + public int deflateEnd(){ + if(dstate==null) return Z_STREAM_ERROR; + int ret=dstate.deflateEnd(); + dstate=null; + return ret; + } + public int deflateParams(int level, int strategy){ + if(dstate==null) return Z_STREAM_ERROR; + return dstate.deflateParams(this, level, strategy); + } + public int deflateSetDictionary (byte[] dictionary, int dictLength){ + if(dstate == null) + return Z_STREAM_ERROR; + return dstate.deflateSetDictionary(this, dictionary, dictLength); + } + + // Flush as much pending output as possible. All deflate() output goes + // through this function so some applications may wish to modify it + // to avoid allocating a large strm->next_out buffer and copying into it. + // (See also read_buf()). + internal void flush_pending(){ + int len=dstate.pending; + + if(len>avail_out) len=avail_out; + if(len==0) return; + + if(dstate.pending_buf.Length<=dstate.pending_out || + next_out.Length<=next_out_index || + dstate.pending_buf.Length<(dstate.pending_out+len) || + next_out.Length<(next_out_index+len)){ + // System.out.println(dstate.pending_buf.length+", "+dstate.pending_out+ + // ", "+next_out.length+", "+next_out_index+", "+len); + // System.out.println("avail_out="+avail_out); + } + + System.Array.Copy(dstate.pending_buf, dstate.pending_out, + next_out, next_out_index, len); + + next_out_index+=len; + dstate.pending_out+=len; + total_out+=len; + avail_out-=len; + dstate.pending-=len; + if(dstate.pending==0){ + dstate.pending_out=0; + } + } + + // Read a new buffer from the current input stream, update the adler32 + // and total number of bytes read. All deflate() input goes through + // this function so some applications may wish to modify it to avoid + // allocating a large strm->next_in buffer and copying from it. + // (See also flush_pending()). + internal int read_buf(byte[] buf, int start, int size) { + int len=avail_in; + + if(len>size) len=size; + if(len==0) return 0; + + avail_in-=len; + + if(dstate.noheader==0) { + adler=_adler.adler32(adler, next_in, next_in_index, len); + } + System.Array.Copy(next_in, next_in_index, buf, start, len); + next_in_index += len; + total_in += len; + return len; + } + + public void free(){ + next_in=null; + next_out=null; + msg=null; + _adler=null; + } + } +} \ No newline at end of file diff --git a/bc-sharp-crypto/src/x509/AttributeCertificateHolder.cs b/bc-sharp-crypto/src/x509/AttributeCertificateHolder.cs new file mode 100644 index 0000000..04460cd --- /dev/null +++ b/bc-sharp-crypto/src/x509/AttributeCertificateHolder.cs @@ -0,0 +1,442 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Security.Certificates; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.X509.Store; + +namespace Org.BouncyCastle.X509 +{ + /// + /// The Holder object. + ///
    + 	/// Holder ::= SEQUENCE {
    + 	///		baseCertificateID   [0] IssuerSerial OPTIONAL,
    + 	///			-- the issuer and serial number of
    + 	///			-- the holder's Public Key Certificate
    + 	///		entityName          [1] GeneralNames OPTIONAL,
    + 	///			-- the name of the claimant or role
    + 	///		objectDigestInfo    [2] ObjectDigestInfo OPTIONAL
    + 	///			-- used to directly authenticate the holder,
    + 	///			-- for example, an executable
    + 	/// }
    +	/// 
    + ///
    + public class AttributeCertificateHolder + //: CertSelector, Selector + : IX509Selector + { + internal readonly Holder holder; + + internal AttributeCertificateHolder( + Asn1Sequence seq) + { + holder = Holder.GetInstance(seq); + } + + public AttributeCertificateHolder( + X509Name issuerName, + BigInteger serialNumber) + { + holder = new Holder( + new IssuerSerial( + GenerateGeneralNames(issuerName), + new DerInteger(serialNumber))); + } + + public AttributeCertificateHolder( + X509Certificate cert) + { + X509Name name; + try + { + name = PrincipalUtilities.GetIssuerX509Principal(cert); + } + catch (Exception e) + { + throw new CertificateParsingException(e.Message); + } + + holder = new Holder(new IssuerSerial(GenerateGeneralNames(name), new DerInteger(cert.SerialNumber))); + } + + public AttributeCertificateHolder( + X509Name principal) + { + holder = new Holder(GenerateGeneralNames(principal)); + } + + /** + * Constructs a holder for v2 attribute certificates with a hash value for + * some type of object. + *

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

      + *
    • 0 - publicKey - A hash of the public key of the holder must be + * passed.
    • + *
    • 1 - publicKeyCert - A hash of the public key certificate of the + * holder must be passed.
    • + *
    • 2 - otherObjectDigest - A hash of some other object type must be + * passed. otherObjectTypeID must not be empty.
    • + *
    + *

    + *

    This cannot be used if a v1 attribute certificate is used.

    + * + * @param digestedObjectType The digest object type. + * @param digestAlgorithm The algorithm identifier for the hash. + * @param otherObjectTypeID The object type ID if + * digestedObjectType is + * otherObjectDigest. + * @param objectDigest The hash value. + */ + public AttributeCertificateHolder( + int digestedObjectType, + string digestAlgorithm, + string otherObjectTypeID, + byte[] objectDigest) + { + // TODO Allow 'objectDigest' to be null? + + holder = new Holder(new ObjectDigestInfo(digestedObjectType, otherObjectTypeID, + new AlgorithmIdentifier(new DerObjectIdentifier(digestAlgorithm)), Arrays.Clone(objectDigest))); + } + + /** + * Returns the digest object type if an object digest info is used. + *

    + *

      + *
    • 0 - publicKey - A hash of the public key of the holder must be + * passed.
    • + *
    • 1 - publicKeyCert - A hash of the public key certificate of the + * holder must be passed.
    • + *
    • 2 - otherObjectDigest - A hash of some other object type must be + * passed. otherObjectTypeID must not be empty.
    • + *
    + *

    + * + * @return The digest object type or -1 if no object digest info is set. + */ + public int DigestedObjectType + { + get + { + ObjectDigestInfo odi = holder.ObjectDigestInfo; + + return odi == null + ? -1 + : odi.DigestedObjectType.Value.IntValue; + } + } + + /** + * Returns the other object type ID if an object digest info is used. + * + * @return The other object type ID or null if no object + * digest info is set. + */ + public string DigestAlgorithm + { + get + { + ObjectDigestInfo odi = holder.ObjectDigestInfo; + + return odi == null + ? null + : odi.DigestAlgorithm.Algorithm.Id; + } + } + + /** + * Returns the hash if an object digest info is used. + * + * @return The hash or null if no object digest info is set. + */ + public byte[] GetObjectDigest() + { + ObjectDigestInfo odi = holder.ObjectDigestInfo; + + return odi == null + ? null + : odi.ObjectDigest.GetBytes(); + } + + /** + * Returns the digest algorithm ID if an object digest info is used. + * + * @return The digest algorithm ID or null if no object + * digest info is set. + */ + public string OtherObjectTypeID + { + get + { + ObjectDigestInfo odi = holder.ObjectDigestInfo; + + return odi == null + ? null + : odi.OtherObjectTypeID.Id; + } + } + + private GeneralNames GenerateGeneralNames( + X509Name principal) + { +// return GeneralNames.GetInstance(new DerSequence(new GeneralName(principal))); + return new GeneralNames(new GeneralName(principal)); + } + + private bool MatchesDN( + X509Name subject, + GeneralNames targets) + { + GeneralName[] names = targets.GetNames(); + + for (int i = 0; i != names.Length; i++) + { + GeneralName gn = names[i]; + + if (gn.TagNo == GeneralName.DirectoryName) + { + try + { + if (X509Name.GetInstance(gn.Name).Equivalent(subject)) + { + return true; + } + } + catch (Exception) + { + } + } + } + + return false; + } + + private object[] GetNames( + GeneralName[] names) + { + int count = 0; + for (int i = 0; i != names.Length; i++) + { + if (names[i].TagNo == GeneralName.DirectoryName) + { + ++count; + } + } + + object[] result = new object[count]; + + int pos = 0; + for (int i = 0; i != names.Length; i++) + { + if (names[i].TagNo == GeneralName.DirectoryName) + { + result[pos++] = X509Name.GetInstance(names[i].Name); + } + } + + return result; + } + + private X509Name[] GetPrincipals( + GeneralNames names) + { + object[] p = this.GetNames(names.GetNames()); + + int count = 0; + + for (int i = 0; i != p.Length; i++) + { + if (p[i] is X509Name) + { + ++count; + } + } + + X509Name[] result = new X509Name[count]; + + int pos = 0; + for (int i = 0; i != p.Length; i++) + { + if (p[i] is X509Name) + { + result[pos++] = (X509Name)p[i]; + } + } + + return result; + } + + /** + * Return any principal objects inside the attribute certificate holder entity names field. + * + * @return an array of IPrincipal objects (usually X509Name), null if no entity names field is set. + */ + public X509Name[] GetEntityNames() + { + if (holder.EntityName != null) + { + return GetPrincipals(holder.EntityName); + } + + return null; + } + + /** + * Return the principals associated with the issuer attached to this holder + * + * @return an array of principals, null if no BaseCertificateID is set. + */ + public X509Name[] GetIssuer() + { + if (holder.BaseCertificateID != null) + { + return GetPrincipals(holder.BaseCertificateID.Issuer); + } + + return null; + } + + /** + * Return the serial number associated with the issuer attached to this holder. + * + * @return the certificate serial number, null if no BaseCertificateID is set. + */ + public BigInteger SerialNumber + { + get + { + if (holder.BaseCertificateID != null) + { + return holder.BaseCertificateID.Serial.Value; + } + + return null; + } + } + + public object Clone() + { + return new AttributeCertificateHolder((Asn1Sequence)holder.ToAsn1Object()); + } + + public bool Match( +// Certificate cert) + X509Certificate x509Cert) + { +// if (!(cert is X509Certificate)) +// { +// return false; +// } +// +// X509Certificate x509Cert = (X509Certificate)cert; + + try + { + if (holder.BaseCertificateID != null) + { + return holder.BaseCertificateID.Serial.Value.Equals(x509Cert.SerialNumber) + && MatchesDN(PrincipalUtilities.GetIssuerX509Principal(x509Cert), holder.BaseCertificateID.Issuer); + } + + if (holder.EntityName != null) + { + if (MatchesDN(PrincipalUtilities.GetSubjectX509Principal(x509Cert), holder.EntityName)) + { + return true; + } + } + + if (holder.ObjectDigestInfo != null) + { + IDigest md = null; + try + { + md = DigestUtilities.GetDigest(DigestAlgorithm); + } + catch (Exception) + { + return false; + } + + switch (DigestedObjectType) + { + case ObjectDigestInfo.PublicKey: + { + // TODO: DSA Dss-parms + + //byte[] b = x509Cert.GetPublicKey().getEncoded(); + // TODO Is this the right way to encode? + byte[] b = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo( + x509Cert.GetPublicKey()).GetEncoded(); + md.BlockUpdate(b, 0, b.Length); + break; + } + + case ObjectDigestInfo.PublicKeyCert: + { + byte[] b = x509Cert.GetEncoded(); + md.BlockUpdate(b, 0, b.Length); + break; + } + + // TODO Default handler? + } + + // TODO Shouldn't this be the other way around? + if (!Arrays.AreEqual(DigestUtilities.DoFinal(md), GetObjectDigest())) + { + return false; + } + } + } + catch (CertificateEncodingException) + { + return false; + } + + return false; + } + + public override bool Equals( + object obj) + { + if (obj == this) + { + return true; + } + + if (!(obj is AttributeCertificateHolder)) + { + return false; + } + + AttributeCertificateHolder other = (AttributeCertificateHolder)obj; + + return this.holder.Equals(other.holder); + } + + public override int GetHashCode() + { + return this.holder.GetHashCode(); + } + + public bool Match( + object obj) + { + if (!(obj is X509Certificate)) + { + return false; + } + +// return Match((Certificate)obj); + return Match((X509Certificate)obj); + } + } +} diff --git a/bc-sharp-crypto/src/x509/AttributeCertificateIssuer.cs b/bc-sharp-crypto/src/x509/AttributeCertificateIssuer.cs new file mode 100644 index 0000000..7df1416 --- /dev/null +++ b/bc-sharp-crypto/src/x509/AttributeCertificateIssuer.cs @@ -0,0 +1,199 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.X509.Store; + +namespace Org.BouncyCastle.X509 +{ + /** + * Carrying class for an attribute certificate issuer. + */ + public class AttributeCertificateIssuer + //: CertSelector, Selector + : IX509Selector + { + internal readonly Asn1Encodable form; + + /** + * Set the issuer directly with the ASN.1 structure. + * + * @param issuer The issuer + */ + public AttributeCertificateIssuer( + AttCertIssuer issuer) + { + form = issuer.Issuer; + } + + public AttributeCertificateIssuer( + X509Name principal) + { +// form = new V2Form(GeneralNames.GetInstance(new DerSequence(new GeneralName(principal)))); + form = new V2Form(new GeneralNames(new GeneralName(principal))); + } + + private object[] GetNames() + { + GeneralNames name; + if (form is V2Form) + { + name = ((V2Form)form).IssuerName; + } + else + { + name = (GeneralNames)form; + } + + GeneralName[] names = name.GetNames(); + + int count = 0; + for (int i = 0; i != names.Length; i++) + { + if (names[i].TagNo == GeneralName.DirectoryName) + { + ++count; + } + } + + object[] result = new object[count]; + + int pos = 0; + for (int i = 0; i != names.Length; i++) + { + if (names[i].TagNo == GeneralName.DirectoryName) + { + result[pos++] = X509Name.GetInstance(names[i].Name); + } + } + + return result; + } + + /// Return any principal objects inside the attribute certificate issuer object. + /// An array of IPrincipal objects (usually X509Principal). + public X509Name[] GetPrincipals() + { + object[] p = this.GetNames(); + + int count = 0; + for (int i = 0; i != p.Length; i++) + { + if (p[i] is X509Name) + { + ++count; + } + } + + X509Name[] result = new X509Name[count]; + + int pos = 0; + for (int i = 0; i != p.Length; i++) + { + if (p[i] is X509Name) + { + result[pos++] = (X509Name)p[i]; + } + } + + return result; + } + + private bool MatchesDN( + X509Name subject, + GeneralNames targets) + { + GeneralName[] names = targets.GetNames(); + + for (int i = 0; i != names.Length; i++) + { + GeneralName gn = names[i]; + + if (gn.TagNo == GeneralName.DirectoryName) + { + try + { + if (X509Name.GetInstance(gn.Name).Equivalent(subject)) + { + return true; + } + } + catch (Exception) + { + } + } + } + + return false; + } + + public object Clone() + { + return new AttributeCertificateIssuer(AttCertIssuer.GetInstance(form)); + } + + public bool Match( +// Certificate cert) + X509Certificate x509Cert) + { +// if (!(cert is X509Certificate)) +// { +// return false; +// } +// +// X509Certificate x509Cert = (X509Certificate)cert; + + if (form is V2Form) + { + V2Form issuer = (V2Form) form; + if (issuer.BaseCertificateID != null) + { + return issuer.BaseCertificateID.Serial.Value.Equals(x509Cert.SerialNumber) + && MatchesDN(x509Cert.IssuerDN, issuer.BaseCertificateID.Issuer); + } + + return MatchesDN(x509Cert.SubjectDN, issuer.IssuerName); + } + + return MatchesDN(x509Cert.SubjectDN, (GeneralNames) form); + } + + public override bool Equals( + object obj) + { + if (obj == this) + { + return true; + } + + if (!(obj is AttributeCertificateIssuer)) + { + return false; + } + + AttributeCertificateIssuer other = (AttributeCertificateIssuer)obj; + + return this.form.Equals(other.form); + } + + public override int GetHashCode() + { + return this.form.GetHashCode(); + } + + public bool Match( + object obj) + { + if (!(obj is X509Certificate)) + { + return false; + } + + //return Match((Certificate)obj); + return Match((X509Certificate)obj); + } + } +} diff --git a/bc-sharp-crypto/src/x509/IX509AttributeCertificate.cs b/bc-sharp-crypto/src/x509/IX509AttributeCertificate.cs new file mode 100644 index 0000000..9a3004e --- /dev/null +++ b/bc-sharp-crypto/src/x509/IX509AttributeCertificate.cs @@ -0,0 +1,57 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.X509 +{ + /// Interface for an X.509 Attribute Certificate. + public interface IX509AttributeCertificate + : IX509Extension + { + /// The version number for the certificate. + int Version { get; } + + /// The serial number for the certificate. + BigInteger SerialNumber { get; } + + /// The UTC DateTime before which the certificate is not valid. + DateTime NotBefore { get; } + + /// The UTC DateTime after which the certificate is not valid. + DateTime NotAfter { get; } + + /// The holder of the certificate. + AttributeCertificateHolder Holder { get; } + + /// The issuer details for the certificate. + AttributeCertificateIssuer Issuer { get; } + + /// Return the attributes contained in the attribute block in the certificate. + /// An array of attributes. + X509Attribute[] GetAttributes(); + + /// Return the attributes with the same type as the passed in oid. + /// The object identifier we wish to match. + /// An array of matched attributes, null if there is no match. + X509Attribute[] GetAttributes(string oid); + + bool[] GetIssuerUniqueID(); + + bool IsValidNow { get; } + bool IsValid(DateTime date); + + void CheckValidity(); + void CheckValidity(DateTime date); + + byte[] GetSignature(); + + void Verify(AsymmetricKeyParameter publicKey); + + /// Return an ASN.1 encoded byte array representing the attribute certificate. + /// An ASN.1 encoded byte array. + /// If the certificate cannot be encoded. + byte[] GetEncoded(); + } +} diff --git a/bc-sharp-crypto/src/x509/IX509Extension.cs b/bc-sharp-crypto/src/x509/IX509Extension.cs new file mode 100644 index 0000000..e861e87 --- /dev/null +++ b/bc-sharp-crypto/src/x509/IX509Extension.cs @@ -0,0 +1,27 @@ +using System; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Utilities.Collections; + +namespace Org.BouncyCastle.X509 +{ + public interface IX509Extension + { + /// + /// Get all critical extension values, by oid + /// + /// IDictionary with string (OID) keys and Asn1OctetString values + ISet GetCriticalExtensionOids(); + + /// + /// Get all non-critical extension values, by oid + /// + /// IDictionary with string (OID) keys and Asn1OctetString values + ISet GetNonCriticalExtensionOids(); + + [Obsolete("Use version taking a DerObjectIdentifier instead")] + Asn1OctetString GetExtensionValue(string oid); + + Asn1OctetString GetExtensionValue(DerObjectIdentifier oid); + } +} diff --git a/bc-sharp-crypto/src/x509/PEMParser.cs b/bc-sharp-crypto/src/x509/PEMParser.cs new file mode 100644 index 0000000..28f28ee --- /dev/null +++ b/bc-sharp-crypto/src/x509/PEMParser.cs @@ -0,0 +1,95 @@ +using System; +using System.IO; +using System.Text; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.X509 +{ + class PemParser + { + private readonly string _header1; + private readonly string _header2; + private readonly string _footer1; + private readonly string _footer2; + + internal PemParser( + string type) + { + _header1 = "-----BEGIN " + type + "-----"; + _header2 = "-----BEGIN X509 " + type + "-----"; + _footer1 = "-----END " + type + "-----"; + _footer2 = "-----END X509 " + type + "-----"; + } + + private string ReadLine( + Stream inStream) + { + int c; + StringBuilder l = new StringBuilder(); + + do + { + while (((c = inStream.ReadByte()) != '\r') && c != '\n' && (c >= 0)) + { + if (c == '\r') + { + continue; + } + + l.Append((char)c); + } + } + while (c >= 0 && l.Length == 0); + + if (c < 0) + { + return null; + } + + return l.ToString(); + } + + internal Asn1Sequence ReadPemObject( + Stream inStream) + { + string line; + StringBuilder pemBuf = new StringBuilder(); + + while ((line = ReadLine(inStream)) != null) + { + if (Platform.StartsWith(line, _header1) || Platform.StartsWith(line, _header2)) + { + break; + } + } + + while ((line = ReadLine(inStream)) != null) + { + if (Platform.StartsWith(line, _footer1) || Platform.StartsWith(line, _footer2)) + { + break; + } + + pemBuf.Append(line); + } + + if (pemBuf.Length != 0) + { + Asn1Object o = Asn1Object.FromByteArray(Base64.Decode(pemBuf.ToString())); + + if (!(o is Asn1Sequence)) + { + throw new IOException("malformed PEM data encountered"); + } + + return (Asn1Sequence) o; + } + + return null; + } + } +} + diff --git a/bc-sharp-crypto/src/x509/PrincipalUtil.cs b/bc-sharp-crypto/src/x509/PrincipalUtil.cs new file mode 100644 index 0000000..0edc4a3 --- /dev/null +++ b/bc-sharp-crypto/src/x509/PrincipalUtil.cs @@ -0,0 +1,70 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Security.Certificates; + +namespace Org.BouncyCastle.X509 +{ + /// + /// A utility class that will extract X509Principal objects from X.509 certificates. + ///

    + /// Use this in preference to trying to recreate a principal from a string, not all + /// DNs are what they should be, so it's best to leave them encoded where they + /// can be.

    + ///
    + public class PrincipalUtilities + { + /// Return the issuer of the given cert as an X509Principal. + public static X509Name GetIssuerX509Principal( + X509Certificate cert) + { + try + { + TbsCertificateStructure tbsCert = TbsCertificateStructure.GetInstance( + Asn1Object.FromByteArray(cert.GetTbsCertificate())); + + return tbsCert.Issuer; + } + catch (Exception e) + { + throw new CertificateEncodingException("Could not extract issuer", e); + } + } + + /// Return the subject of the given cert as an X509Principal. + public static X509Name GetSubjectX509Principal( + X509Certificate cert) + { + try + { + TbsCertificateStructure tbsCert = TbsCertificateStructure.GetInstance( + Asn1Object.FromByteArray(cert.GetTbsCertificate())); + + return tbsCert.Subject; + } + catch (Exception e) + { + throw new CertificateEncodingException("Could not extract subject", e); + } + } + + /// Return the issuer of the given CRL as an X509Principal. + public static X509Name GetIssuerX509Principal( + X509Crl crl) + { + try + { + TbsCertificateList tbsCertList = TbsCertificateList.GetInstance( + Asn1Object.FromByteArray(crl.GetTbsCertList())); + + return tbsCertList.Issuer; + } + catch (Exception e) + { + throw new CrlException("Could not extract issuer", e); + } + } + } +} diff --git a/bc-sharp-crypto/src/x509/SubjectPublicKeyInfoFactory.cs b/bc-sharp-crypto/src/x509/SubjectPublicKeyInfoFactory.cs new file mode 100644 index 0000000..7614321 --- /dev/null +++ b/bc-sharp-crypto/src/x509/SubjectPublicKeyInfoFactory.cs @@ -0,0 +1,184 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.CryptoPro; +using Org.BouncyCastle.Asn1.Oiw; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Math.EC; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.X509 +{ + /// + /// A factory to produce Public Key Info Objects. + /// + public sealed class SubjectPublicKeyInfoFactory + { + private SubjectPublicKeyInfoFactory() + { + } + + /// + /// Create a Subject Public Key Info object for a given public key. + /// + /// One of ElGammalPublicKeyParameters, DSAPublicKeyParameter, DHPublicKeyParameters, RsaKeyParameters or ECPublicKeyParameters + /// A subject public key info object. + /// Throw exception if object provided is not one of the above. + public static SubjectPublicKeyInfo CreateSubjectPublicKeyInfo( + AsymmetricKeyParameter key) + { + if (key == null) + throw new ArgumentNullException("key"); + if (key.IsPrivate) + throw new ArgumentException("Private key passed - public key expected.", "key"); + + if (key is ElGamalPublicKeyParameters) + { + ElGamalPublicKeyParameters _key = (ElGamalPublicKeyParameters)key; + ElGamalParameters kp = _key.Parameters; + + SubjectPublicKeyInfo info = new SubjectPublicKeyInfo( + new AlgorithmIdentifier( + OiwObjectIdentifiers.ElGamalAlgorithm, + new ElGamalParameter(kp.P, kp.G).ToAsn1Object()), + new DerInteger(_key.Y)); + + return info; + } + + if (key is DsaPublicKeyParameters) + { + DsaPublicKeyParameters _key = (DsaPublicKeyParameters) key; + DsaParameters kp = _key.Parameters; + Asn1Encodable ae = kp == null + ? null + : new DsaParameter(kp.P, kp.Q, kp.G).ToAsn1Object(); + + return new SubjectPublicKeyInfo( + new AlgorithmIdentifier(X9ObjectIdentifiers.IdDsa, ae), + new DerInteger(_key.Y)); + } + + if (key is DHPublicKeyParameters) + { + DHPublicKeyParameters _key = (DHPublicKeyParameters) key; + DHParameters kp = _key.Parameters; + + SubjectPublicKeyInfo info = new SubjectPublicKeyInfo( + new AlgorithmIdentifier( + _key.AlgorithmOid, + new DHParameter(kp.P, kp.G, kp.L).ToAsn1Object()), + new DerInteger(_key.Y)); + + return info; + } // End of DH + + if (key is RsaKeyParameters) + { + RsaKeyParameters _key = (RsaKeyParameters) key; + + SubjectPublicKeyInfo info = new SubjectPublicKeyInfo( + new AlgorithmIdentifier(PkcsObjectIdentifiers.RsaEncryption, DerNull.Instance), + new RsaPublicKeyStructure(_key.Modulus, _key.Exponent).ToAsn1Object()); + + return info; + } // End of RSA. + + if (key is ECPublicKeyParameters) + { + ECPublicKeyParameters _key = (ECPublicKeyParameters) key; + + if (_key.AlgorithmName == "ECGOST3410") + { + if (_key.PublicKeyParamSet == null) + throw Platform.CreateNotImplementedException("Not a CryptoPro parameter set"); + + ECPoint q = _key.Q.Normalize(); + BigInteger bX = q.AffineXCoord.ToBigInteger(); + BigInteger bY = q.AffineYCoord.ToBigInteger(); + + byte[] encKey = new byte[64]; + ExtractBytes(encKey, 0, bX); + ExtractBytes(encKey, 32, bY); + + Gost3410PublicKeyAlgParameters gostParams = new Gost3410PublicKeyAlgParameters( + _key.PublicKeyParamSet, CryptoProObjectIdentifiers.GostR3411x94CryptoProParamSet); + + AlgorithmIdentifier algID = new AlgorithmIdentifier( + CryptoProObjectIdentifiers.GostR3410x2001, + gostParams.ToAsn1Object()); + + return new SubjectPublicKeyInfo(algID, new DerOctetString(encKey)); + } + else + { + X962Parameters x962; + if (_key.PublicKeyParamSet == null) + { + ECDomainParameters kp = _key.Parameters; + X9ECParameters ecP = new X9ECParameters(kp.Curve, kp.G, kp.N, kp.H, kp.GetSeed()); + + x962 = new X962Parameters(ecP); + } + else + { + x962 = new X962Parameters(_key.PublicKeyParamSet); + } + + Asn1OctetString p = (Asn1OctetString)(new X9ECPoint(_key.Q).ToAsn1Object()); + + AlgorithmIdentifier algID = new AlgorithmIdentifier( + X9ObjectIdentifiers.IdECPublicKey, x962.ToAsn1Object()); + + return new SubjectPublicKeyInfo(algID, p.GetOctets()); + } + } // End of EC + + if (key is Gost3410PublicKeyParameters) + { + Gost3410PublicKeyParameters _key = (Gost3410PublicKeyParameters) key; + + if (_key.PublicKeyParamSet == null) + throw Platform.CreateNotImplementedException("Not a CryptoPro parameter set"); + + byte[] keyEnc = _key.Y.ToByteArrayUnsigned(); + byte[] keyBytes = new byte[keyEnc.Length]; + + for (int i = 0; i != keyBytes.Length; i++) + { + keyBytes[i] = keyEnc[keyEnc.Length - 1 - i]; // must be little endian + } + + Gost3410PublicKeyAlgParameters algParams = new Gost3410PublicKeyAlgParameters( + _key.PublicKeyParamSet, CryptoProObjectIdentifiers.GostR3411x94CryptoProParamSet); + + AlgorithmIdentifier algID = new AlgorithmIdentifier( + CryptoProObjectIdentifiers.GostR3410x94, + algParams.ToAsn1Object()); + + return new SubjectPublicKeyInfo(algID, new DerOctetString(keyBytes)); + } + + throw new ArgumentException("Class provided no convertible: " + Platform.GetTypeName(key)); + } + + private static void ExtractBytes( + byte[] encKey, + int offset, + BigInteger bI) + { + byte[] val = bI.ToByteArray(); + int n = (bI.BitLength + 7) / 8; + + for (int i = 0; i < n; ++i) + { + encKey[offset + i] = val[val.Length - 1 - i]; + } + } + } +} diff --git a/bc-sharp-crypto/src/x509/X509AttrCertParser.cs b/bc-sharp-crypto/src/x509/X509AttrCertParser.cs new file mode 100644 index 0000000..a5c0736 --- /dev/null +++ b/bc-sharp-crypto/src/x509/X509AttrCertParser.cs @@ -0,0 +1,173 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Security.Certificates; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.X509 +{ + public class X509AttrCertParser + { + private static readonly PemParser PemAttrCertParser = new PemParser("ATTRIBUTE CERTIFICATE"); + + private Asn1Set sData; + private int sDataObjectCount; + private Stream currentStream; + + private IX509AttributeCertificate ReadDerCertificate( + Asn1InputStream dIn) + { + Asn1Sequence seq = (Asn1Sequence)dIn.ReadObject(); + + if (seq.Count > 1 && seq[0] is DerObjectIdentifier) + { + if (seq[0].Equals(PkcsObjectIdentifiers.SignedData)) + { + sData = SignedData.GetInstance( + Asn1Sequence.GetInstance((Asn1TaggedObject) seq[1], true)).Certificates; + + return GetCertificate(); + } + } + +// return new X509V2AttributeCertificate(seq.getEncoded()); + return new X509V2AttributeCertificate(AttributeCertificate.GetInstance(seq)); + } + + private IX509AttributeCertificate GetCertificate() + { + if (sData != null) + { + while (sDataObjectCount < sData.Count) + { + object obj = sData[sDataObjectCount++]; + + if (obj is Asn1TaggedObject && ((Asn1TaggedObject)obj).TagNo == 2) + { + //return new X509V2AttributeCertificate( + // Asn1Sequence.GetInstance((Asn1TaggedObject)obj, false).GetEncoded()); + return new X509V2AttributeCertificate( + AttributeCertificate.GetInstance( + Asn1Sequence.GetInstance((Asn1TaggedObject)obj, false))); + } + } + } + + return null; + } + + private IX509AttributeCertificate ReadPemCertificate( + Stream inStream) + { + Asn1Sequence seq = PemAttrCertParser.ReadPemObject(inStream); + + return seq == null + ? null + //: new X509V2AttributeCertificate(seq.getEncoded()); + : new X509V2AttributeCertificate(AttributeCertificate.GetInstance(seq)); + } + + /// + /// Create loading data from byte array. + /// + /// + public IX509AttributeCertificate ReadAttrCert( + byte[] input) + { + return ReadAttrCert(new MemoryStream(input, false)); + } + + /// + /// Create loading data from byte array. + /// + /// + public ICollection ReadAttrCerts( + byte[] input) + { + return ReadAttrCerts(new MemoryStream(input, false)); + } + + /** + * Generates a certificate object and initializes it with the data + * read from the input stream inStream. + */ + public IX509AttributeCertificate ReadAttrCert( + Stream inStream) + { + if (inStream == null) + throw new ArgumentNullException("inStream"); + if (!inStream.CanRead) + throw new ArgumentException("inStream must be read-able", "inStream"); + + if (currentStream == null) + { + currentStream = inStream; + sData = null; + sDataObjectCount = 0; + } + else if (currentStream != inStream) // reset if input stream has changed + { + currentStream = inStream; + sData = null; + sDataObjectCount = 0; + } + + try + { + if (sData != null) + { + if (sDataObjectCount != sData.Count) + { + return GetCertificate(); + } + + sData = null; + sDataObjectCount = 0; + return null; + } + + PushbackStream pis = new PushbackStream(inStream); + int tag = pis.ReadByte(); + + if (tag < 0) + return null; + + pis.Unread(tag); + + if (tag != 0x30) // assume ascii PEM encoded. + { + return ReadPemCertificate(pis); + } + + return ReadDerCertificate(new Asn1InputStream(pis)); + } + catch (Exception e) + { + throw new CertificateException(e.ToString()); + } + } + + /** + * Returns a (possibly empty) collection view of the certificates + * read from the given input stream inStream. + */ + public ICollection ReadAttrCerts( + Stream inStream) + { + IX509AttributeCertificate attrCert; + IList attrCerts = Platform.CreateArrayList(); + + while ((attrCert = ReadAttrCert(inStream)) != null) + { + attrCerts.Add(attrCert); + } + + return attrCerts; + } + } +} \ No newline at end of file diff --git a/bc-sharp-crypto/src/x509/X509Attribute.cs b/bc-sharp-crypto/src/x509/X509Attribute.cs new file mode 100644 index 0000000..248d66c --- /dev/null +++ b/bc-sharp-crypto/src/x509/X509Attribute.cs @@ -0,0 +1,76 @@ +using System; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; + +namespace Org.BouncyCastle.X509 +{ + /** + * Class for carrying the values in an X.509 Attribute. + */ + public class X509Attribute + : Asn1Encodable + { + private readonly AttributeX509 attr; + + /** + * @param at an object representing an attribute. + */ + internal X509Attribute( + Asn1Encodable at) + { + this.attr = AttributeX509.GetInstance(at); + } + + /** + * Create an X.509 Attribute with the type given by the passed in oid and + * the value represented by an ASN.1 Set containing value. + * + * @param oid type of the attribute + * @param value value object to go into the atribute's value set. + */ + public X509Attribute( + string oid, + Asn1Encodable value) + { + this.attr = new AttributeX509(new DerObjectIdentifier(oid), new DerSet(value)); + } + + /** + * Create an X.59 Attribute with the type given by the passed in oid and the + * value represented by an ASN.1 Set containing the objects in value. + * + * @param oid type of the attribute + * @param value vector of values to go in the attribute's value set. + */ + public X509Attribute( + string oid, + Asn1EncodableVector value) + { + this.attr = new AttributeX509(new DerObjectIdentifier(oid), new DerSet(value)); + } + + public string Oid + { + get { return attr.AttrType.Id; } + } + + public Asn1Encodable[] GetValues() + { + Asn1Set s = attr.AttrValues; + Asn1Encodable[] values = new Asn1Encodable[s.Count]; + + for (int i = 0; i != s.Count; i++) + { + values[i] = (Asn1Encodable)s[i]; + } + + return values; + } + + public override Asn1Object ToAsn1Object() + { + return attr.ToAsn1Object(); + } + } +} diff --git a/bc-sharp-crypto/src/x509/X509CertPairParser.cs b/bc-sharp-crypto/src/x509/X509CertPairParser.cs new file mode 100644 index 0000000..8261259 --- /dev/null +++ b/bc-sharp-crypto/src/x509/X509CertPairParser.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Security.Certificates; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.X509 +{ + public class X509CertPairParser + { + private Stream currentStream; + + private X509CertificatePair ReadDerCrossCertificatePair( + Stream inStream) + { + Asn1InputStream dIn = new Asn1InputStream(inStream);//, ProviderUtil.getReadLimit(in)); + Asn1Sequence seq = (Asn1Sequence)dIn.ReadObject(); + CertificatePair pair = CertificatePair.GetInstance(seq); + return new X509CertificatePair(pair); + } + + /// + /// Create loading data from byte array. + /// + /// + public X509CertificatePair ReadCertPair( + byte[] input) + { + return ReadCertPair(new MemoryStream(input, false)); + } + + /// + /// Create loading data from byte array. + /// + /// + public ICollection ReadCertPairs( + byte[] input) + { + return ReadCertPairs(new MemoryStream(input, false)); + } + + public X509CertificatePair ReadCertPair( + Stream inStream) + { + if (inStream == null) + throw new ArgumentNullException("inStream"); + if (!inStream.CanRead) + throw new ArgumentException("inStream must be read-able", "inStream"); + + if (currentStream == null) + { + currentStream = inStream; + } + else if (currentStream != inStream) // reset if input stream has changed + { + currentStream = inStream; + } + + try + { + PushbackStream pis = new PushbackStream(inStream); + int tag = pis.ReadByte(); + + if (tag < 0) + return null; + + pis.Unread(tag); + + return ReadDerCrossCertificatePair(pis); + } + catch (Exception e) + { + throw new CertificateException(e.ToString()); + } + } + + public ICollection ReadCertPairs( + Stream inStream) + { + X509CertificatePair certPair; + IList certPairs = Platform.CreateArrayList(); + + while ((certPair = ReadCertPair(inStream)) != null) + { + certPairs.Add(certPair); + } + + return certPairs; + } + } +} diff --git a/bc-sharp-crypto/src/x509/X509Certificate.cs b/bc-sharp-crypto/src/x509/X509Certificate.cs new file mode 100644 index 0000000..6d7bd7a --- /dev/null +++ b/bc-sharp-crypto/src/x509/X509Certificate.cs @@ -0,0 +1,604 @@ +using System; +using System.Collections; +using System.IO; +using System.Text; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Misc; +using Org.BouncyCastle.Asn1.Utilities; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Security.Certificates; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Encoders; +using Org.BouncyCastle.X509.Extension; +using Org.BouncyCastle.Crypto.Operators; + +namespace Org.BouncyCastle.X509 +{ + /// + /// An Object representing an X509 Certificate. + /// Has static methods for loading Certificates encoded in many forms that return X509Certificate Objects. + /// + public class X509Certificate + : X509ExtensionBase +// , PKCS12BagAttributeCarrier + { + private readonly X509CertificateStructure c; +// private Hashtable pkcs12Attributes = new Hashtable(); +// private ArrayList pkcs12Ordering = new ArrayList(); + private readonly BasicConstraints basicConstraints; + private readonly bool[] keyUsage; + + private bool hashValueSet; + private int hashValue; + + protected X509Certificate() + { + } + + public X509Certificate( + X509CertificateStructure c) + { + this.c = c; + + try + { + Asn1OctetString str = this.GetExtensionValue(new DerObjectIdentifier("2.5.29.19")); + + if (str != null) + { + basicConstraints = BasicConstraints.GetInstance( + X509ExtensionUtilities.FromExtensionValue(str)); + } + } + catch (Exception e) + { + throw new CertificateParsingException("cannot construct BasicConstraints: " + e); + } + + try + { + Asn1OctetString str = this.GetExtensionValue(new DerObjectIdentifier("2.5.29.15")); + + if (str != null) + { + DerBitString bits = DerBitString.GetInstance( + X509ExtensionUtilities.FromExtensionValue(str)); + + byte[] bytes = bits.GetBytes(); + int length = (bytes.Length * 8) - bits.PadBits; + + keyUsage = new bool[(length < 9) ? 9 : length]; + + for (int i = 0; i != length; i++) + { +// keyUsage[i] = (bytes[i / 8] & (0x80 >>> (i % 8))) != 0; + keyUsage[i] = (bytes[i / 8] & (0x80 >> (i % 8))) != 0; + } + } + else + { + keyUsage = null; + } + } + catch (Exception e) + { + throw new CertificateParsingException("cannot construct KeyUsage: " + e); + } + } + +// internal X509Certificate( +// Asn1Sequence seq) +// { +// this.c = X509CertificateStructure.GetInstance(seq); +// } + +// /// +// /// Load certificate from byte array. +// /// +// /// Byte array containing encoded X509Certificate. +// public X509Certificate( +// byte[] encoded) +// : this((Asn1Sequence) new Asn1InputStream(encoded).ReadObject()) +// { +// } +// +// /// +// /// Load certificate from Stream. +// /// Must be positioned at start of certificate. +// /// +// /// +// public X509Certificate( +// Stream input) +// : this((Asn1Sequence) new Asn1InputStream(input).ReadObject()) +// { +// } + + public virtual X509CertificateStructure CertificateStructure + { + get { return c; } + } + + /// + /// Return true if the current time is within the start and end times nominated on the certificate. + /// + /// true id certificate is valid for the current time. + public virtual bool IsValidNow + { + get { return IsValid(DateTime.UtcNow); } + } + + /// + /// Return true if the nominated time is within the start and end times nominated on the certificate. + /// + /// The time to test validity against. + /// True if certificate is valid for nominated time. + public virtual bool IsValid( + DateTime time) + { + return time.CompareTo(NotBefore) >= 0 && time.CompareTo(NotAfter) <= 0; + } + + /// + /// Checks if the current date is within certificate's validity period. + /// + public virtual void CheckValidity() + { + this.CheckValidity(DateTime.UtcNow); + } + + /// + /// Checks if the given date is within certificate's validity period. + /// + /// if the certificate is expired by given date + /// if the certificate is not yet valid on given date + public virtual void CheckValidity( + DateTime time) + { + if (time.CompareTo(NotAfter) > 0) + throw new CertificateExpiredException("certificate expired on " + c.EndDate.GetTime()); + if (time.CompareTo(NotBefore) < 0) + throw new CertificateNotYetValidException("certificate not valid until " + c.StartDate.GetTime()); + } + + /// + /// Return the certificate's version. + /// + /// An integer whose value Equals the version of the cerficate. + public virtual int Version + { + get { return c.Version; } + } + + /// + /// Return a BigInteger containing the serial number. + /// + /// The Serial number. + public virtual BigInteger SerialNumber + { + get { return c.SerialNumber.Value; } + } + + /// + /// Get the Issuer Distinguished Name. (Who signed the certificate.) + /// + /// And X509Object containing name and value pairs. +// public IPrincipal IssuerDN + public virtual X509Name IssuerDN + { + get { return c.Issuer; } + } + + /// + /// Get the subject of this certificate. + /// + /// An X509Name object containing name and value pairs. +// public IPrincipal SubjectDN + public virtual X509Name SubjectDN + { + get { return c.Subject; } + } + + /// + /// The time that this certificate is valid from. + /// + /// A DateTime object representing that time in the local time zone. + public virtual DateTime NotBefore + { + get { return c.StartDate.ToDateTime(); } + } + + /// + /// The time that this certificate is valid up to. + /// + /// A DateTime object representing that time in the local time zone. + public virtual DateTime NotAfter + { + get { return c.EndDate.ToDateTime(); } + } + + /// + /// Return the Der encoded TbsCertificate data. + /// This is the certificate component less the signature. + /// To Get the whole certificate call the GetEncoded() member. + /// + /// A byte array containing the Der encoded Certificate component. + public virtual byte[] GetTbsCertificate() + { + return c.TbsCertificate.GetDerEncoded(); + } + + /// + /// The signature. + /// + /// A byte array containg the signature of the certificate. + public virtual byte[] GetSignature() + { + return c.GetSignatureOctets(); + } + + /// + /// A meaningful version of the Signature Algorithm. (EG SHA1WITHRSA) + /// + /// A sting representing the signature algorithm. + public virtual string SigAlgName + { + get { return SignerUtilities.GetEncodingName(c.SignatureAlgorithm.Algorithm); } + } + + /// + /// Get the Signature Algorithms Object ID. + /// + /// A string containg a '.' separated object id. + public virtual string SigAlgOid + { + get { return c.SignatureAlgorithm.Algorithm.Id; } + } + + /// + /// Get the signature algorithms parameters. (EG DSA Parameters) + /// + /// A byte array containing the Der encoded version of the parameters or null if there are none. + public virtual byte[] GetSigAlgParams() + { + if (c.SignatureAlgorithm.Parameters != null) + { + return c.SignatureAlgorithm.Parameters.GetDerEncoded(); + } + + return null; + } + + /// + /// Get the issuers UID. + /// + /// A DerBitString. + public virtual DerBitString IssuerUniqueID + { + get { return c.TbsCertificate.IssuerUniqueID; } + } + + /// + /// Get the subjects UID. + /// + /// A DerBitString. + public virtual DerBitString SubjectUniqueID + { + get { return c.TbsCertificate.SubjectUniqueID; } + } + + /// + /// Get a key usage guidlines. + /// + public virtual bool[] GetKeyUsage() + { + return keyUsage == null ? null : (bool[]) keyUsage.Clone(); + } + + // TODO Replace with something that returns a list of DerObjectIdentifier + public virtual IList GetExtendedKeyUsage() + { + Asn1OctetString str = this.GetExtensionValue(new DerObjectIdentifier("2.5.29.37")); + + if (str == null) + return null; + + try + { + Asn1Sequence seq = Asn1Sequence.GetInstance( + X509ExtensionUtilities.FromExtensionValue(str)); + + IList list = Platform.CreateArrayList(); + + foreach (DerObjectIdentifier oid in seq) + { + list.Add(oid.Id); + } + + return list; + } + catch (Exception e) + { + throw new CertificateParsingException("error processing extended key usage extension", e); + } + } + + public virtual int GetBasicConstraints() + { + if (basicConstraints != null && basicConstraints.IsCA()) + { + if (basicConstraints.PathLenConstraint == null) + { + return int.MaxValue; + } + + return basicConstraints.PathLenConstraint.IntValue; + } + + return -1; + } + + public virtual ICollection GetSubjectAlternativeNames() + { + return GetAlternativeNames("2.5.29.17"); + } + + public virtual ICollection GetIssuerAlternativeNames() + { + return GetAlternativeNames("2.5.29.18"); + } + + protected virtual ICollection GetAlternativeNames( + string oid) + { + Asn1OctetString altNames = GetExtensionValue(new DerObjectIdentifier(oid)); + + if (altNames == null) + return null; + + Asn1Object asn1Object = X509ExtensionUtilities.FromExtensionValue(altNames); + + GeneralNames gns = GeneralNames.GetInstance(asn1Object); + + IList result = Platform.CreateArrayList(); + foreach (GeneralName gn in gns.GetNames()) + { + IList entry = Platform.CreateArrayList(); + entry.Add(gn.TagNo); + entry.Add(gn.Name.ToString()); + result.Add(entry); + } + return result; + } + + protected override X509Extensions GetX509Extensions() + { + return c.Version >= 3 + ? c.TbsCertificate.Extensions + : null; + } + + /// + /// Get the public key of the subject of the certificate. + /// + /// The public key parameters. + public virtual AsymmetricKeyParameter GetPublicKey() + { + return PublicKeyFactory.CreateKey(c.SubjectPublicKeyInfo); + } + + /// + /// Return a Der encoded version of this certificate. + /// + /// A byte array. + public virtual byte[] GetEncoded() + { + return c.GetDerEncoded(); + } + + public override bool Equals( + object obj) + { + if (obj == this) + return true; + + X509Certificate other = obj as X509Certificate; + + if (other == null) + return false; + + return c.Equals(other.c); + + // NB: May prefer this implementation of Equals if more than one certificate implementation in play +// return Arrays.AreEqual(this.GetEncoded(), other.GetEncoded()); + } + + public override int GetHashCode() + { + lock (this) + { + if (!hashValueSet) + { + hashValue = c.GetHashCode(); + hashValueSet = true; + } + } + + return hashValue; + } + +// public void setBagAttribute( +// DERObjectIdentifier oid, +// DEREncodable attribute) +// { +// pkcs12Attributes.put(oid, attribute); +// pkcs12Ordering.addElement(oid); +// } +// +// public DEREncodable getBagAttribute( +// DERObjectIdentifier oid) +// { +// return (DEREncodable)pkcs12Attributes.get(oid); +// } +// +// public Enumeration getBagAttributeKeys() +// { +// return pkcs12Ordering.elements(); +// } + + public override string ToString() + { + StringBuilder buf = new StringBuilder(); + string nl = Platform.NewLine; + + buf.Append(" [0] Version: ").Append(this.Version).Append(nl); + buf.Append(" SerialNumber: ").Append(this.SerialNumber).Append(nl); + buf.Append(" IssuerDN: ").Append(this.IssuerDN).Append(nl); + buf.Append(" Start Date: ").Append(this.NotBefore).Append(nl); + buf.Append(" Final Date: ").Append(this.NotAfter).Append(nl); + buf.Append(" SubjectDN: ").Append(this.SubjectDN).Append(nl); + buf.Append(" Public Key: ").Append(this.GetPublicKey()).Append(nl); + buf.Append(" Signature Algorithm: ").Append(this.SigAlgName).Append(nl); + + byte[] sig = this.GetSignature(); + buf.Append(" Signature: ").Append(Hex.ToHexString(sig, 0, 20)).Append(nl); + + for (int i = 20; i < sig.Length; i += 20) + { + int len = System.Math.Min(20, sig.Length - i); + buf.Append(" ").Append(Hex.ToHexString(sig, i, len)).Append(nl); + } + + X509Extensions extensions = c.TbsCertificate.Extensions; + + if (extensions != null) + { + IEnumerator e = extensions.ExtensionOids.GetEnumerator(); + + if (e.MoveNext()) + { + buf.Append(" Extensions: \n"); + } + + do + { + DerObjectIdentifier oid = (DerObjectIdentifier)e.Current; + X509Extension ext = extensions.GetExtension(oid); + + if (ext.Value != null) + { + byte[] octs = ext.Value.GetOctets(); + Asn1Object obj = Asn1Object.FromByteArray(octs); + buf.Append(" critical(").Append(ext.IsCritical).Append(") "); + try + { + if (oid.Equals(X509Extensions.BasicConstraints)) + { + buf.Append(BasicConstraints.GetInstance(obj)); + } + else if (oid.Equals(X509Extensions.KeyUsage)) + { + buf.Append(KeyUsage.GetInstance(obj)); + } + else if (oid.Equals(MiscObjectIdentifiers.NetscapeCertType)) + { + buf.Append(new NetscapeCertType((DerBitString) obj)); + } + else if (oid.Equals(MiscObjectIdentifiers.NetscapeRevocationUrl)) + { + buf.Append(new NetscapeRevocationUrl((DerIA5String) obj)); + } + else if (oid.Equals(MiscObjectIdentifiers.VerisignCzagExtension)) + { + buf.Append(new VerisignCzagExtension((DerIA5String) obj)); + } + else + { + buf.Append(oid.Id); + buf.Append(" value = ").Append(Asn1Dump.DumpAsString(obj)); + //buf.Append(" value = ").Append("*****").Append(nl); + } + } + catch (Exception) + { + buf.Append(oid.Id); + //buf.Append(" value = ").Append(new string(Hex.encode(ext.getValue().getOctets()))).Append(nl); + buf.Append(" value = ").Append("*****"); + } + } + + buf.Append(nl); + } + while (e.MoveNext()); + } + + return buf.ToString(); + } + + /// + /// Verify the certificate's signature using the nominated public key. + /// + /// An appropriate public key parameter object, RsaPublicKeyParameters, DsaPublicKeyParameters or ECDsaPublicKeyParameters + /// True if the signature is valid. + /// If key submitted is not of the above nominated types. + public virtual void Verify( + AsymmetricKeyParameter key) + { + CheckSignature(new Asn1VerifierFactory(c.SignatureAlgorithm, key)); + } + + /// + /// Verify the certificate's signature using a verifier created using the passed in verifier provider. + /// + /// An appropriate provider for verifying the certificate's signature. + /// True if the signature is valid. + /// If verifier provider is not appropriate or the certificate algorithm is invalid. + public virtual void Verify( + IVerifierFactoryProvider verifierProvider) + { + CheckSignature(verifierProvider.CreateVerifierFactory (c.SignatureAlgorithm)); + } + + protected virtual void CheckSignature( + IVerifierFactory verifier) + { + if (!IsAlgIDEqual(c.SignatureAlgorithm, c.TbsCertificate.Signature)) + throw new CertificateException("signature algorithm in TBS cert not same as outer cert"); + + Asn1Encodable parameters = c.SignatureAlgorithm.Parameters; + + IStreamCalculator streamCalculator = verifier.CreateCalculator(); + + byte[] b = this.GetTbsCertificate(); + + streamCalculator.Stream.Write(b, 0, b.Length); + + Platform.Dispose(streamCalculator.Stream); + + if (!((IVerifier)streamCalculator.GetResult()).IsVerified(this.GetSignature())) + { + throw new InvalidKeyException("Public key presented not for certificate signature"); + } + } + + private static bool IsAlgIDEqual(AlgorithmIdentifier id1, AlgorithmIdentifier id2) + { + if (!id1.Algorithm.Equals(id2.Algorithm)) + return false; + + Asn1Encodable p1 = id1.Parameters; + Asn1Encodable p2 = id2.Parameters; + + if ((p1 == null) == (p2 == null)) + return Platform.Equals(p1, p2); + + // Exactly one of p1, p2 is null at this point + return p1 == null + ? p2.ToAsn1Object() is Asn1Null + : p1.ToAsn1Object() is Asn1Null; + } + } +} diff --git a/bc-sharp-crypto/src/x509/X509CertificatePair.cs b/bc-sharp-crypto/src/x509/X509CertificatePair.cs new file mode 100644 index 0000000..fbeba4d --- /dev/null +++ b/bc-sharp-crypto/src/x509/X509CertificatePair.cs @@ -0,0 +1,123 @@ +using System; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Security.Certificates; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.X509 +{ + /// + /// This class contains a cross certificate pair. Cross certificates pairs may + /// contain two cross signed certificates from two CAs. A certificate from the + /// other CA to this CA is contained in the forward certificate, the certificate + /// from this CA to the other CA is contained in the reverse certificate. + /// + public class X509CertificatePair + { + private readonly X509Certificate forward; + private readonly X509Certificate reverse; + + /// Constructor + /// Certificate from the other CA to this CA. + /// Certificate from this CA to the other CA. + public X509CertificatePair( + X509Certificate forward, + X509Certificate reverse) + { + this.forward = forward; + this.reverse = reverse; + } + + /// Constructor from a ASN.1 CertificatePair structure. + /// The CertificatePair ASN.1 object. + public X509CertificatePair( + CertificatePair pair) + { + if (pair.Forward != null) + { + this.forward = new X509Certificate(pair.Forward); + } + if (pair.Reverse != null) + { + this.reverse = new X509Certificate(pair.Reverse); + } + } + + public byte[] GetEncoded() + { + try + { + X509CertificateStructure f = null, r = null; + + if (forward != null) + { + f = X509CertificateStructure.GetInstance( + Asn1Object.FromByteArray(forward.GetEncoded())); + + if (f == null) + throw new CertificateEncodingException("unable to get encoding for forward"); + } + + if (reverse != null) + { + r = X509CertificateStructure.GetInstance( + Asn1Object.FromByteArray(reverse.GetEncoded())); + + if (r == null) + throw new CertificateEncodingException("unable to get encoding for reverse"); + } + + return new CertificatePair(f, r).GetDerEncoded(); + } + catch (Exception e) + { + // TODO +// throw new ExtCertificateEncodingException(e.toString(), e); + throw new CertificateEncodingException(e.Message, e); + } + } + + /// Returns the certificate from the other CA to this CA. + public X509Certificate Forward + { + get { return forward; } + } + + /// Returns the certificate from this CA to the other CA. + public X509Certificate Reverse + { + get { return reverse; } + } + + public override bool Equals( + object obj) + { + if (obj == this) + return true; + + X509CertificatePair other = obj as X509CertificatePair; + + if (other == null) + return false; + + return Platform.Equals(this.forward, other.forward) + && Platform.Equals(this.reverse, other.reverse); + } + + public override int GetHashCode() + { + int hash = -1; + if (forward != null) + { + hash ^= forward.GetHashCode(); + } + if (reverse != null) + { + hash *= 17; + hash ^= reverse.GetHashCode(); + } + return hash; + } + } +} diff --git a/bc-sharp-crypto/src/x509/X509CertificateParser.cs b/bc-sharp-crypto/src/x509/X509CertificateParser.cs new file mode 100644 index 0000000..8f0e740 --- /dev/null +++ b/bc-sharp-crypto/src/x509/X509CertificateParser.cs @@ -0,0 +1,183 @@ +using System; +using System.Collections; +using System.IO; +using System.Text; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Security.Certificates; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Encoders; +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.X509 +{ + /** + * class for dealing with X509 certificates. + *

    + * At the moment this will deal with "-----BEGIN CERTIFICATE-----" to "-----END CERTIFICATE-----" + * base 64 encoded certs, as well as the BER binaries of certificates and some classes of PKCS#7 + * objects.

    + */ + public class X509CertificateParser + { + private static readonly PemParser PemCertParser = new PemParser("CERTIFICATE"); + + private Asn1Set sData; + private int sDataObjectCount; + private Stream currentStream; + + private X509Certificate ReadDerCertificate( + Asn1InputStream dIn) + { + Asn1Sequence seq = (Asn1Sequence)dIn.ReadObject(); + + if (seq.Count > 1 && seq[0] is DerObjectIdentifier) + { + if (seq[0].Equals(PkcsObjectIdentifiers.SignedData)) + { + sData = SignedData.GetInstance( + Asn1Sequence.GetInstance((Asn1TaggedObject) seq[1], true)).Certificates; + + return GetCertificate(); + } + } + + return CreateX509Certificate(X509CertificateStructure.GetInstance(seq)); + } + + private X509Certificate GetCertificate() + { + if (sData != null) + { + while (sDataObjectCount < sData.Count) + { + object obj = sData[sDataObjectCount++]; + + if (obj is Asn1Sequence) + { + return CreateX509Certificate( + X509CertificateStructure.GetInstance(obj)); + } + } + } + + return null; + } + + private X509Certificate ReadPemCertificate( + Stream inStream) + { + Asn1Sequence seq = PemCertParser.ReadPemObject(inStream); + + return seq == null + ? null + : CreateX509Certificate(X509CertificateStructure.GetInstance(seq)); + } + + protected virtual X509Certificate CreateX509Certificate( + X509CertificateStructure c) + { + return new X509Certificate(c); + } + + /// + /// Create loading data from byte array. + /// + /// + public X509Certificate ReadCertificate( + byte[] input) + { + return ReadCertificate(new MemoryStream(input, false)); + } + + /// + /// Create loading data from byte array. + /// + /// + public ICollection ReadCertificates( + byte[] input) + { + return ReadCertificates(new MemoryStream(input, false)); + } + + /** + * Generates a certificate object and initializes it with the data + * read from the input stream inStream. + */ + public X509Certificate ReadCertificate( + Stream inStream) + { + if (inStream == null) + throw new ArgumentNullException("inStream"); + if (!inStream.CanRead) + throw new ArgumentException("inStream must be read-able", "inStream"); + + if (currentStream == null) + { + currentStream = inStream; + sData = null; + sDataObjectCount = 0; + } + else if (currentStream != inStream) // reset if input stream has changed + { + currentStream = inStream; + sData = null; + sDataObjectCount = 0; + } + + try + { + if (sData != null) + { + if (sDataObjectCount != sData.Count) + { + return GetCertificate(); + } + + sData = null; + sDataObjectCount = 0; + return null; + } + + PushbackStream pis = new PushbackStream(inStream); + int tag = pis.ReadByte(); + + if (tag < 0) + return null; + + pis.Unread(tag); + + if (tag != 0x30) // assume ascii PEM encoded. + { + return ReadPemCertificate(pis); + } + + return ReadDerCertificate(new Asn1InputStream(pis)); + } + catch (Exception e) + { + throw new CertificateException("Failed to read certificate", e); + } + } + + /** + * Returns a (possibly empty) collection view of the certificates + * read from the given input stream inStream. + */ + public ICollection ReadCertificates( + Stream inStream) + { + X509Certificate cert; + IList certs = Platform.CreateArrayList(); + + while ((cert = ReadCertificate(inStream)) != null) + { + certs.Add(cert); + } + + return certs; + } + } +} diff --git a/bc-sharp-crypto/src/x509/X509Crl.cs b/bc-sharp-crypto/src/x509/X509Crl.cs new file mode 100644 index 0000000..ecfb141 --- /dev/null +++ b/bc-sharp-crypto/src/x509/X509Crl.cs @@ -0,0 +1,426 @@ +using System; +using System.Collections; +using System.Text; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Utilities; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Security.Certificates; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; +using Org.BouncyCastle.Utilities.Date; +using Org.BouncyCastle.Utilities.Encoders; +using Org.BouncyCastle.X509.Extension; +using Org.BouncyCastle.Crypto.Operators; + +namespace Org.BouncyCastle.X509 +{ + /** + * The following extensions are listed in RFC 2459 as relevant to CRLs + * + * Authority Key Identifier + * Issuer Alternative Name + * CRL Number + * Delta CRL Indicator (critical) + * Issuing Distribution Point (critical) + */ + public class X509Crl + : X509ExtensionBase + // TODO Add interface Crl? + { + private readonly CertificateList c; + private readonly string sigAlgName; + private readonly byte[] sigAlgParams; + private readonly bool isIndirect; + + public X509Crl( + CertificateList c) + { + this.c = c; + + try + { + this.sigAlgName = X509SignatureUtilities.GetSignatureName(c.SignatureAlgorithm); + + if (c.SignatureAlgorithm.Parameters != null) + { + this.sigAlgParams = ((Asn1Encodable)c.SignatureAlgorithm.Parameters).GetDerEncoded(); + } + else + { + this.sigAlgParams = null; + } + + this.isIndirect = IsIndirectCrl; + } + catch (Exception e) + { + throw new CrlException("CRL contents invalid: " + e); + } + } + + protected override X509Extensions GetX509Extensions() + { + return c.Version >= 2 + ? c.TbsCertList.Extensions + : null; + } + + public virtual byte[] GetEncoded() + { + try + { + return c.GetDerEncoded(); + } + catch (Exception e) + { + throw new CrlException(e.ToString()); + } + } + + public virtual void Verify( + AsymmetricKeyParameter publicKey) + { + Verify(new Asn1VerifierFactoryProvider(publicKey)); + } + + /// + /// Verify the CRL's signature using a verifier created using the passed in verifier provider. + /// + /// An appropriate provider for verifying the CRL's signature. + /// True if the signature is valid. + /// If verifier provider is not appropriate or the CRL algorithm is invalid. + public virtual void Verify( + IVerifierFactoryProvider verifierProvider) + { + CheckSignature(verifierProvider.CreateVerifierFactory(c.SignatureAlgorithm)); + } + + protected virtual void CheckSignature( + IVerifierFactory verifier) + { + if (!c.SignatureAlgorithm.Equals(c.TbsCertList.Signature)) + { + throw new CrlException("Signature algorithm on CertificateList does not match TbsCertList."); + } + + Asn1Encodable parameters = c.SignatureAlgorithm.Parameters; + + IStreamCalculator streamCalculator = verifier.CreateCalculator(); + + byte[] b = this.GetTbsCertList(); + + streamCalculator.Stream.Write(b, 0, b.Length); + + Platform.Dispose(streamCalculator.Stream); + + if (!((IVerifier)streamCalculator.GetResult()).IsVerified(this.GetSignature())) + { + throw new InvalidKeyException("CRL does not verify with supplied public key."); + } + } + + public virtual int Version + { + get { return c.Version; } + } + + public virtual X509Name IssuerDN + { + get { return c.Issuer; } + } + + public virtual DateTime ThisUpdate + { + get { return c.ThisUpdate.ToDateTime(); } + } + + public virtual DateTimeObject NextUpdate + { + get + { + return c.NextUpdate == null + ? null + : new DateTimeObject(c.NextUpdate.ToDateTime()); + } + } + + private ISet LoadCrlEntries() + { + ISet entrySet = new HashSet(); + IEnumerable certs = c.GetRevokedCertificateEnumeration(); + + X509Name previousCertificateIssuer = IssuerDN; + foreach (CrlEntry entry in certs) + { + X509CrlEntry crlEntry = new X509CrlEntry(entry, isIndirect, previousCertificateIssuer); + entrySet.Add(crlEntry); + previousCertificateIssuer = crlEntry.GetCertificateIssuer(); + } + + return entrySet; + } + + public virtual X509CrlEntry GetRevokedCertificate( + BigInteger serialNumber) + { + IEnumerable certs = c.GetRevokedCertificateEnumeration(); + + X509Name previousCertificateIssuer = IssuerDN; + foreach (CrlEntry entry in certs) + { + X509CrlEntry crlEntry = new X509CrlEntry(entry, isIndirect, previousCertificateIssuer); + + if (serialNumber.Equals(entry.UserCertificate.Value)) + { + return crlEntry; + } + + previousCertificateIssuer = crlEntry.GetCertificateIssuer(); + } + + return null; + } + + public virtual ISet GetRevokedCertificates() + { + ISet entrySet = LoadCrlEntries(); + + if (entrySet.Count > 0) + { + return entrySet; // TODO? Collections.unmodifiableSet(entrySet); + } + + return null; + } + + public virtual byte[] GetTbsCertList() + { + try + { + return c.TbsCertList.GetDerEncoded(); + } + catch (Exception e) + { + throw new CrlException(e.ToString()); + } + } + + public virtual byte[] GetSignature() + { + return c.GetSignatureOctets(); + } + + public virtual string SigAlgName + { + get { return sigAlgName; } + } + + public virtual string SigAlgOid + { + get { return c.SignatureAlgorithm.Algorithm.Id; } + } + + public virtual byte[] GetSigAlgParams() + { + return Arrays.Clone(sigAlgParams); + } + + public override bool Equals( + object obj) + { + if (obj == this) + return true; + + X509Crl other = obj as X509Crl; + + if (other == null) + return false; + + return c.Equals(other.c); + + // NB: May prefer this implementation of Equals if more than one certificate implementation in play + //return Arrays.AreEqual(this.GetEncoded(), other.GetEncoded()); + } + + public override int GetHashCode() + { + return c.GetHashCode(); + } + + /** + * Returns a string representation of this CRL. + * + * @return a string representation of this CRL. + */ + public override string ToString() + { + StringBuilder buf = new StringBuilder(); + string nl = Platform.NewLine; + + buf.Append(" Version: ").Append(this.Version).Append(nl); + buf.Append(" IssuerDN: ").Append(this.IssuerDN).Append(nl); + buf.Append(" This update: ").Append(this.ThisUpdate).Append(nl); + buf.Append(" Next update: ").Append(this.NextUpdate).Append(nl); + buf.Append(" Signature Algorithm: ").Append(this.SigAlgName).Append(nl); + + byte[] sig = this.GetSignature(); + + buf.Append(" Signature: "); + buf.Append(Hex.ToHexString(sig, 0, 20)).Append(nl); + + for (int i = 20; i < sig.Length; i += 20) + { + int count = System.Math.Min(20, sig.Length - i); + buf.Append(" "); + buf.Append(Hex.ToHexString(sig, i, count)).Append(nl); + } + + X509Extensions extensions = c.TbsCertList.Extensions; + + if (extensions != null) + { + IEnumerator e = extensions.ExtensionOids.GetEnumerator(); + + if (e.MoveNext()) + { + buf.Append(" Extensions: ").Append(nl); + } + + do + { + DerObjectIdentifier oid = (DerObjectIdentifier) e.Current; + X509Extension ext = extensions.GetExtension(oid); + + if (ext.Value != null) + { + Asn1Object asn1Value = X509ExtensionUtilities.FromExtensionValue(ext.Value); + + buf.Append(" critical(").Append(ext.IsCritical).Append(") "); + try + { + if (oid.Equals(X509Extensions.CrlNumber)) + { + buf.Append(new CrlNumber(DerInteger.GetInstance(asn1Value).PositiveValue)).Append(nl); + } + else if (oid.Equals(X509Extensions.DeltaCrlIndicator)) + { + buf.Append( + "Base CRL: " + + new CrlNumber(DerInteger.GetInstance( + asn1Value).PositiveValue)) + .Append(nl); + } + else if (oid.Equals(X509Extensions.IssuingDistributionPoint)) + { + buf.Append(IssuingDistributionPoint.GetInstance((Asn1Sequence) asn1Value)).Append(nl); + } + else if (oid.Equals(X509Extensions.CrlDistributionPoints)) + { + buf.Append(CrlDistPoint.GetInstance((Asn1Sequence) asn1Value)).Append(nl); + } + else if (oid.Equals(X509Extensions.FreshestCrl)) + { + buf.Append(CrlDistPoint.GetInstance((Asn1Sequence) asn1Value)).Append(nl); + } + else + { + buf.Append(oid.Id); + buf.Append(" value = ").Append( + Asn1Dump.DumpAsString(asn1Value)) + .Append(nl); + } + } + catch (Exception) + { + buf.Append(oid.Id); + buf.Append(" value = ").Append("*****").Append(nl); + } + } + else + { + buf.Append(nl); + } + } + while (e.MoveNext()); + } + + ISet certSet = GetRevokedCertificates(); + if (certSet != null) + { + foreach (X509CrlEntry entry in certSet) + { + buf.Append(entry); + buf.Append(nl); + } + } + + return buf.ToString(); + } + + /** + * Checks whether the given certificate is on this CRL. + * + * @param cert the certificate to check for. + * @return true if the given certificate is on this CRL, + * false otherwise. + */ +// public bool IsRevoked( +// Certificate cert) +// { +// if (!cert.getType().Equals("X.509")) +// { +// throw new RuntimeException("X.509 CRL used with non X.509 Cert"); +// } + public virtual bool IsRevoked( + X509Certificate cert) + { + CrlEntry[] certs = c.GetRevokedCertificates(); + + if (certs != null) + { +// BigInteger serial = ((X509Certificate)cert).SerialNumber; + BigInteger serial = cert.SerialNumber; + + for (int i = 0; i < certs.Length; i++) + { + if (certs[i].UserCertificate.Value.Equals(serial)) + { + return true; + } + } + } + + return false; + } + + protected virtual bool IsIndirectCrl + { + get + { + Asn1OctetString idp = GetExtensionValue(X509Extensions.IssuingDistributionPoint); + bool isIndirect = false; + + try + { + if (idp != null) + { + isIndirect = IssuingDistributionPoint.GetInstance( + X509ExtensionUtilities.FromExtensionValue(idp)).IsIndirectCrl; + } + } + catch (Exception e) + { + // TODO +// throw new ExtCrlException("Exception reading IssuingDistributionPoint", e); + throw new CrlException("Exception reading IssuingDistributionPoint" + e); + } + + return isIndirect; + } + } + } +} diff --git a/bc-sharp-crypto/src/x509/X509CrlEntry.cs b/bc-sharp-crypto/src/x509/X509CrlEntry.cs new file mode 100644 index 0000000..caca294 --- /dev/null +++ b/bc-sharp-crypto/src/x509/X509CrlEntry.cs @@ -0,0 +1,201 @@ +using System; +using System.Collections; +using System.IO; +using System.Text; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Utilities; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security.Certificates; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.X509.Extension; + +namespace Org.BouncyCastle.X509 +{ + /** + * The following extensions are listed in RFC 2459 as relevant to CRL Entries + * + * ReasonCode Hode Instruction Code Invalidity Date Certificate Issuer + * (critical) + */ + public class X509CrlEntry + : X509ExtensionBase + { + private CrlEntry c; + private bool isIndirect; + private X509Name previousCertificateIssuer; + private X509Name certificateIssuer; + + public X509CrlEntry( + CrlEntry c) + { + this.c = c; + this.certificateIssuer = loadCertificateIssuer(); + } + + /** + * Constructor for CRLEntries of indirect CRLs. If isIndirect + * is false {@link #getCertificateIssuer()} will always + * return null, previousCertificateIssuer is + * ignored. If this isIndirect is specified and this CrlEntry + * has no certificate issuer CRL entry extension + * previousCertificateIssuer is returned by + * {@link #getCertificateIssuer()}. + * + * @param c + * TbsCertificateList.CrlEntry object. + * @param isIndirect + * true if the corresponding CRL is a indirect + * CRL. + * @param previousCertificateIssuer + * Certificate issuer of the previous CrlEntry. + */ + public X509CrlEntry( + CrlEntry c, + bool isIndirect, + X509Name previousCertificateIssuer) + { + this.c = c; + this.isIndirect = isIndirect; + this.previousCertificateIssuer = previousCertificateIssuer; + this.certificateIssuer = loadCertificateIssuer(); + } + + private X509Name loadCertificateIssuer() + { + if (!isIndirect) + { + return null; + } + + Asn1OctetString ext = GetExtensionValue(X509Extensions.CertificateIssuer); + if (ext == null) + { + return previousCertificateIssuer; + } + + try + { + GeneralName[] names = GeneralNames.GetInstance( + X509ExtensionUtilities.FromExtensionValue(ext)).GetNames(); + + for (int i = 0; i < names.Length; i++) + { + if (names[i].TagNo == GeneralName.DirectoryName) + { + return X509Name.GetInstance(names[i].Name); + } + } + } + catch (Exception) + { + } + + return null; + } + + public X509Name GetCertificateIssuer() + { + return certificateIssuer; + } + + protected override X509Extensions GetX509Extensions() + { + return c.Extensions; + } + + public byte[] GetEncoded() + { + try + { + return c.GetDerEncoded(); + } + catch (Exception e) + { + throw new CrlException(e.ToString()); + } + } + + public BigInteger SerialNumber + { + get { return c.UserCertificate.Value; } + } + + public DateTime RevocationDate + { + get { return c.RevocationDate.ToDateTime(); } + } + + public bool HasExtensions + { + get { return c.Extensions != null; } + } + + public override string ToString() + { + StringBuilder buf = new StringBuilder(); + string nl = Platform.NewLine; + + buf.Append(" userCertificate: ").Append(this.SerialNumber).Append(nl); + buf.Append(" revocationDate: ").Append(this.RevocationDate).Append(nl); + buf.Append(" certificateIssuer: ").Append(this.GetCertificateIssuer()).Append(nl); + + X509Extensions extensions = c.Extensions; + + if (extensions != null) + { + IEnumerator e = extensions.ExtensionOids.GetEnumerator(); + if (e.MoveNext()) + { + buf.Append(" crlEntryExtensions:").Append(nl); + + do + { + DerObjectIdentifier oid = (DerObjectIdentifier)e.Current; + X509Extension ext = extensions.GetExtension(oid); + + if (ext.Value != null) + { + Asn1Object obj = Asn1Object.FromByteArray(ext.Value.GetOctets()); + + buf.Append(" critical(") + .Append(ext.IsCritical) + .Append(") "); + try + { + if (oid.Equals(X509Extensions.ReasonCode)) + { + buf.Append(new CrlReason(DerEnumerated.GetInstance(obj))); + } + else if (oid.Equals(X509Extensions.CertificateIssuer)) + { + buf.Append("Certificate issuer: ").Append( + GeneralNames.GetInstance((Asn1Sequence)obj)); + } + else + { + buf.Append(oid.Id); + buf.Append(" value = ").Append(Asn1Dump.DumpAsString(obj)); + } + buf.Append(nl); + } + catch (Exception) + { + buf.Append(oid.Id); + buf.Append(" value = ").Append("*****").Append(nl); + } + } + else + { + buf.Append(nl); + } + } + while (e.MoveNext()); + } + } + + return buf.ToString(); + } + } +} diff --git a/bc-sharp-crypto/src/x509/X509CrlParser.cs b/bc-sharp-crypto/src/x509/X509CrlParser.cs new file mode 100644 index 0000000..d830bb9 --- /dev/null +++ b/bc-sharp-crypto/src/x509/X509CrlParser.cs @@ -0,0 +1,195 @@ +using System; +using System.Collections; +using System.IO; +using System.Text; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Security.Certificates; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Encoders; +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.X509 +{ + public class X509CrlParser + { + private static readonly PemParser PemCrlParser = new PemParser("CRL"); + + private readonly bool lazyAsn1; + + private Asn1Set sCrlData; + private int sCrlDataObjectCount; + private Stream currentCrlStream; + + public X509CrlParser() + : this(false) + { + } + + public X509CrlParser( + bool lazyAsn1) + { + this.lazyAsn1 = lazyAsn1; + } + + private X509Crl ReadPemCrl( + Stream inStream) + { + Asn1Sequence seq = PemCrlParser.ReadPemObject(inStream); + + return seq == null + ? null + : CreateX509Crl(CertificateList.GetInstance(seq)); + } + + private X509Crl ReadDerCrl( + Asn1InputStream dIn) + { + Asn1Sequence seq = (Asn1Sequence)dIn.ReadObject(); + + if (seq.Count > 1 && seq[0] is DerObjectIdentifier) + { + if (seq[0].Equals(PkcsObjectIdentifiers.SignedData)) + { + sCrlData = SignedData.GetInstance( + Asn1Sequence.GetInstance((Asn1TaggedObject) seq[1], true)).Crls; + + return GetCrl(); + } + } + + return CreateX509Crl(CertificateList.GetInstance(seq)); + } + + private X509Crl GetCrl() + { + if (sCrlData == null || sCrlDataObjectCount >= sCrlData.Count) + { + return null; + } + + return CreateX509Crl( + CertificateList.GetInstance( + sCrlData[sCrlDataObjectCount++])); + } + + protected virtual X509Crl CreateX509Crl( + CertificateList c) + { + return new X509Crl(c); + } + + /// + /// Create loading data from byte array. + /// + /// + public X509Crl ReadCrl( + byte[] input) + { + return ReadCrl(new MemoryStream(input, false)); + } + + /// + /// Create loading data from byte array. + /// + /// + public ICollection ReadCrls( + byte[] input) + { + return ReadCrls(new MemoryStream(input, false)); + } + + /** + * Generates a certificate revocation list (CRL) object and initializes + * it with the data read from the input stream inStream. + */ + public X509Crl ReadCrl( + Stream inStream) + { + if (inStream == null) + throw new ArgumentNullException("inStream"); + if (!inStream.CanRead) + throw new ArgumentException("inStream must be read-able", "inStream"); + + if (currentCrlStream == null) + { + currentCrlStream = inStream; + sCrlData = null; + sCrlDataObjectCount = 0; + } + else if (currentCrlStream != inStream) // reset if input stream has changed + { + currentCrlStream = inStream; + sCrlData = null; + sCrlDataObjectCount = 0; + } + + try + { + if (sCrlData != null) + { + if (sCrlDataObjectCount != sCrlData.Count) + { + return GetCrl(); + } + + sCrlData = null; + sCrlDataObjectCount = 0; + return null; + } + + PushbackStream pis = new PushbackStream(inStream); + int tag = pis.ReadByte(); + + if (tag < 0) + return null; + + pis.Unread(tag); + + if (tag != 0x30) // assume ascii PEM encoded. + { + return ReadPemCrl(pis); + } + + Asn1InputStream asn1 = lazyAsn1 + ? new LazyAsn1InputStream(pis) + : new Asn1InputStream(pis); + + return ReadDerCrl(asn1); + } + catch (CrlException e) + { + throw e; + } + catch (Exception e) + { + throw new CrlException(e.ToString()); + } + } + + /** + * Returns a (possibly empty) collection view of the CRLs read from + * the given input stream inStream. + * + * The inStream may contain a sequence of DER-encoded CRLs, or + * a PKCS#7 CRL set. This is a PKCS#7 SignedData object, with the + * only significant field being crls. In particular the signature + * and the contents are ignored. + */ + public ICollection ReadCrls( + Stream inStream) + { + X509Crl crl; + IList crls = Platform.CreateArrayList(); + + while ((crl = ReadCrl(inStream)) != null) + { + crls.Add(crl); + } + + return crls; + } + } +} diff --git a/bc-sharp-crypto/src/x509/X509ExtensionBase.cs b/bc-sharp-crypto/src/x509/X509ExtensionBase.cs new file mode 100644 index 0000000..aaf6695 --- /dev/null +++ b/bc-sharp-crypto/src/x509/X509ExtensionBase.cs @@ -0,0 +1,82 @@ +using System; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities.Collections; + +namespace Org.BouncyCastle.X509 +{ + public abstract class X509ExtensionBase + : IX509Extension + { + protected abstract X509Extensions GetX509Extensions(); + + protected virtual ISet GetExtensionOids( + bool critical) + { + X509Extensions extensions = GetX509Extensions(); + if (extensions != null) + { + HashSet set = new HashSet(); + foreach (DerObjectIdentifier oid in extensions.ExtensionOids) + { + X509Extension ext = extensions.GetExtension(oid); + if (ext.IsCritical == critical) + { + set.Add(oid.Id); + } + } + + return set; + } + + return null; + } + + /// + /// Get non critical extensions. + /// + /// A set of non critical extension oids. + public virtual ISet GetNonCriticalExtensionOids() + { + return GetExtensionOids(false); + } + + /// + /// Get any critical extensions. + /// + /// A sorted list of critical entension. + public virtual ISet GetCriticalExtensionOids() + { + return GetExtensionOids(true); + } + + /// + /// Get the value of a given extension. + /// + /// The object ID of the extension. + /// An Asn1OctetString object if that extension is found or null if not. + [Obsolete("Use version taking a DerObjectIdentifier instead")] + public Asn1OctetString GetExtensionValue( + string oid) + { + return GetExtensionValue(new DerObjectIdentifier(oid)); + } + + public virtual Asn1OctetString GetExtensionValue( + DerObjectIdentifier oid) + { + X509Extensions exts = GetX509Extensions(); + if (exts != null) + { + X509Extension ext = exts.GetExtension(oid); + if (ext != null) + { + return ext.Value; + } + } + + return null; + } + } +} diff --git a/bc-sharp-crypto/src/x509/X509KeyUsage.cs b/bc-sharp-crypto/src/x509/X509KeyUsage.cs new file mode 100644 index 0000000..e0a7b49 --- /dev/null +++ b/bc-sharp-crypto/src/x509/X509KeyUsage.cs @@ -0,0 +1,59 @@ +using System; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; + +namespace Org.BouncyCastle.X509 +{ + /** + * A holding class for constructing an X509 Key Usage extension. + * + *
    +	 *    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 X509KeyUsage + : Asn1Encodable + { + public const int DigitalSignature = 1 << 7; + public const int NonRepudiation = 1 << 6; + public const int KeyEncipherment = 1 << 5; + public const int DataEncipherment = 1 << 4; + public const int KeyAgreement = 1 << 3; + public const int KeyCertSign = 1 << 2; + public const int CrlSign = 1 << 1; + public const int EncipherOnly = 1 << 0; + public const int DecipherOnly = 1 << 15; + + private readonly int usage; + + /** + * Basic constructor. + * + * @param usage - the bitwise OR of the Key Usage flags giving the + * allowed uses for the key. + * e.g. (X509KeyUsage.keyEncipherment | X509KeyUsage.dataEncipherment) + */ + public X509KeyUsage( + int usage) + { + this.usage = usage; + } + + public override Asn1Object ToAsn1Object() + { + return new KeyUsage(usage); + } + } +} diff --git a/bc-sharp-crypto/src/x509/X509SignatureUtil.cs b/bc-sharp-crypto/src/x509/X509SignatureUtil.cs new file mode 100644 index 0000000..83863ae --- /dev/null +++ b/bc-sharp-crypto/src/x509/X509SignatureUtil.cs @@ -0,0 +1,128 @@ +using System; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.CryptoPro; +using Org.BouncyCastle.Asn1.Nist; +using Org.BouncyCastle.Asn1.Oiw; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.TeleTrust; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Crypto; + +namespace Org.BouncyCastle.X509 +{ + internal class X509SignatureUtilities + { + private static readonly Asn1Null derNull = DerNull.Instance; + + internal static void SetSignatureParameters( + ISigner signature, + Asn1Encodable parameters) + { + if (parameters != null && !derNull.Equals(parameters)) + { + // TODO Put back in +// AlgorithmParameters sigParams = AlgorithmParameters.GetInstance(signature.getAlgorithm()); +// +// try +// { +// sigParams.Init(parameters.ToAsn1Object().GetDerEncoded()); +// } +// catch (IOException e) +// { +// throw new SignatureException("IOException decoding parameters: " + e.Message); +// } +// +// if (Platform.EndsWith(signature.getAlgorithm(), "MGF1")) +// { +// try +// { +// signature.setParameter(sigParams.getParameterSpec(PSSParameterSpec.class)); +// } +// catch (GeneralSecurityException e) +// { +// throw new SignatureException("Exception extracting parameters: " + e.Message); +// } +// } + } + } + + internal static string GetSignatureName( + AlgorithmIdentifier sigAlgId) + { + Asn1Encodable parameters = sigAlgId.Parameters; + + if (parameters != null && !derNull.Equals(parameters)) + { + if (sigAlgId.Algorithm.Equals(PkcsObjectIdentifiers.IdRsassaPss)) + { + RsassaPssParameters rsaParams = RsassaPssParameters.GetInstance(parameters); + + return GetDigestAlgName(rsaParams.HashAlgorithm.Algorithm) + "withRSAandMGF1"; + } + if (sigAlgId.Algorithm.Equals(X9ObjectIdentifiers.ECDsaWithSha2)) + { + Asn1Sequence ecDsaParams = Asn1Sequence.GetInstance(parameters); + + return GetDigestAlgName((DerObjectIdentifier)ecDsaParams[0]) + "withECDSA"; + } + } + + return sigAlgId.Algorithm.Id; + } + + /** + * Return the digest algorithm using one of the standard JCA string + * representations rather than the algorithm identifier (if possible). + */ + private static string GetDigestAlgName( + DerObjectIdentifier digestAlgOID) + { + if (PkcsObjectIdentifiers.MD5.Equals(digestAlgOID)) + { + return "MD5"; + } + else if (OiwObjectIdentifiers.IdSha1.Equals(digestAlgOID)) + { + return "SHA1"; + } + else if (NistObjectIdentifiers.IdSha224.Equals(digestAlgOID)) + { + return "SHA224"; + } + else if (NistObjectIdentifiers.IdSha256.Equals(digestAlgOID)) + { + return "SHA256"; + } + else if (NistObjectIdentifiers.IdSha384.Equals(digestAlgOID)) + { + return "SHA384"; + } + else if (NistObjectIdentifiers.IdSha512.Equals(digestAlgOID)) + { + return "SHA512"; + } + else if (TeleTrusTObjectIdentifiers.RipeMD128.Equals(digestAlgOID)) + { + return "RIPEMD128"; + } + else if (TeleTrusTObjectIdentifiers.RipeMD160.Equals(digestAlgOID)) + { + return "RIPEMD160"; + } + else if (TeleTrusTObjectIdentifiers.RipeMD256.Equals(digestAlgOID)) + { + return "RIPEMD256"; + } + else if (CryptoProObjectIdentifiers.GostR3411.Equals(digestAlgOID)) + { + return "GOST3411"; + } + else + { + return digestAlgOID.Id; + } + } + } +} diff --git a/bc-sharp-crypto/src/x509/X509Utilities.cs b/bc-sharp-crypto/src/x509/X509Utilities.cs new file mode 100644 index 0000000..52a122c --- /dev/null +++ b/bc-sharp-crypto/src/x509/X509Utilities.cs @@ -0,0 +1,187 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.CryptoPro; +using Org.BouncyCastle.Asn1.Nist; +using Org.BouncyCastle.Asn1.Oiw; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.TeleTrust; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; + +namespace Org.BouncyCastle.X509 +{ + internal class X509Utilities + { + private static readonly IDictionary algorithms = Platform.CreateHashtable(); + private static readonly IDictionary exParams = Platform.CreateHashtable(); + private static readonly ISet noParams = new HashSet(); + + static X509Utilities() + { + algorithms.Add("MD2WITHRSAENCRYPTION", PkcsObjectIdentifiers.MD2WithRsaEncryption); + algorithms.Add("MD2WITHRSA", PkcsObjectIdentifiers.MD2WithRsaEncryption); + algorithms.Add("MD5WITHRSAENCRYPTION", PkcsObjectIdentifiers.MD5WithRsaEncryption); + algorithms.Add("MD5WITHRSA", PkcsObjectIdentifiers.MD5WithRsaEncryption); + algorithms.Add("SHA1WITHRSAENCRYPTION", PkcsObjectIdentifiers.Sha1WithRsaEncryption); + algorithms.Add("SHA1WITHRSA", PkcsObjectIdentifiers.Sha1WithRsaEncryption); + algorithms.Add("SHA224WITHRSAENCRYPTION", PkcsObjectIdentifiers.Sha224WithRsaEncryption); + algorithms.Add("SHA224WITHRSA", PkcsObjectIdentifiers.Sha224WithRsaEncryption); + algorithms.Add("SHA256WITHRSAENCRYPTION", PkcsObjectIdentifiers.Sha256WithRsaEncryption); + algorithms.Add("SHA256WITHRSA", PkcsObjectIdentifiers.Sha256WithRsaEncryption); + algorithms.Add("SHA384WITHRSAENCRYPTION", PkcsObjectIdentifiers.Sha384WithRsaEncryption); + algorithms.Add("SHA384WITHRSA", PkcsObjectIdentifiers.Sha384WithRsaEncryption); + algorithms.Add("SHA512WITHRSAENCRYPTION", PkcsObjectIdentifiers.Sha512WithRsaEncryption); + algorithms.Add("SHA512WITHRSA", PkcsObjectIdentifiers.Sha512WithRsaEncryption); + algorithms.Add("SHA1WITHRSAANDMGF1", PkcsObjectIdentifiers.IdRsassaPss); + algorithms.Add("SHA224WITHRSAANDMGF1", PkcsObjectIdentifiers.IdRsassaPss); + algorithms.Add("SHA256WITHRSAANDMGF1", PkcsObjectIdentifiers.IdRsassaPss); + algorithms.Add("SHA384WITHRSAANDMGF1", PkcsObjectIdentifiers.IdRsassaPss); + algorithms.Add("SHA512WITHRSAANDMGF1", PkcsObjectIdentifiers.IdRsassaPss); + algorithms.Add("RIPEMD160WITHRSAENCRYPTION", TeleTrusTObjectIdentifiers.RsaSignatureWithRipeMD160); + algorithms.Add("RIPEMD160WITHRSA", TeleTrusTObjectIdentifiers.RsaSignatureWithRipeMD160); + algorithms.Add("RIPEMD128WITHRSAENCRYPTION", TeleTrusTObjectIdentifiers.RsaSignatureWithRipeMD128); + algorithms.Add("RIPEMD128WITHRSA", TeleTrusTObjectIdentifiers.RsaSignatureWithRipeMD128); + algorithms.Add("RIPEMD256WITHRSAENCRYPTION", TeleTrusTObjectIdentifiers.RsaSignatureWithRipeMD256); + algorithms.Add("RIPEMD256WITHRSA", TeleTrusTObjectIdentifiers.RsaSignatureWithRipeMD256); + algorithms.Add("SHA1WITHDSA", X9ObjectIdentifiers.IdDsaWithSha1); + algorithms.Add("DSAWITHSHA1", X9ObjectIdentifiers.IdDsaWithSha1); + algorithms.Add("SHA224WITHDSA", NistObjectIdentifiers.DsaWithSha224); + algorithms.Add("SHA256WITHDSA", NistObjectIdentifiers.DsaWithSha256); + algorithms.Add("SHA384WITHDSA", NistObjectIdentifiers.DsaWithSha384); + algorithms.Add("SHA512WITHDSA", NistObjectIdentifiers.DsaWithSha512); + algorithms.Add("SHA1WITHECDSA", X9ObjectIdentifiers.ECDsaWithSha1); + algorithms.Add("ECDSAWITHSHA1", X9ObjectIdentifiers.ECDsaWithSha1); + algorithms.Add("SHA224WITHECDSA", X9ObjectIdentifiers.ECDsaWithSha224); + algorithms.Add("SHA256WITHECDSA", X9ObjectIdentifiers.ECDsaWithSha256); + algorithms.Add("SHA384WITHECDSA", X9ObjectIdentifiers.ECDsaWithSha384); + algorithms.Add("SHA512WITHECDSA", X9ObjectIdentifiers.ECDsaWithSha512); + algorithms.Add("GOST3411WITHGOST3410", CryptoProObjectIdentifiers.GostR3411x94WithGostR3410x94); + algorithms.Add("GOST3411WITHGOST3410-94", CryptoProObjectIdentifiers.GostR3411x94WithGostR3410x94); + algorithms.Add("GOST3411WITHECGOST3410", CryptoProObjectIdentifiers.GostR3411x94WithGostR3410x2001); + algorithms.Add("GOST3411WITHECGOST3410-2001", CryptoProObjectIdentifiers.GostR3411x94WithGostR3410x2001); + algorithms.Add("GOST3411WITHGOST3410-2001", CryptoProObjectIdentifiers.GostR3411x94WithGostR3410x2001); + + // + // According to RFC 3279, the ASN.1 encoding SHALL (id-dsa-with-sha1) or MUST (ecdsa-with-SHA*) omit the parameters field. + // The parameters field SHALL be NULL for RSA based signature algorithms. + // + noParams.Add(X9ObjectIdentifiers.ECDsaWithSha1); + noParams.Add(X9ObjectIdentifiers.ECDsaWithSha224); + noParams.Add(X9ObjectIdentifiers.ECDsaWithSha256); + noParams.Add(X9ObjectIdentifiers.ECDsaWithSha384); + noParams.Add(X9ObjectIdentifiers.ECDsaWithSha512); + noParams.Add(X9ObjectIdentifiers.IdDsaWithSha1); + noParams.Add(NistObjectIdentifiers.DsaWithSha224); + noParams.Add(NistObjectIdentifiers.DsaWithSha256); + noParams.Add(NistObjectIdentifiers.DsaWithSha384); + noParams.Add(NistObjectIdentifiers.DsaWithSha512); + + // + // RFC 4491 + // + noParams.Add(CryptoProObjectIdentifiers.GostR3411x94WithGostR3410x94); + noParams.Add(CryptoProObjectIdentifiers.GostR3411x94WithGostR3410x2001); + + // + // explicit params + // + AlgorithmIdentifier sha1AlgId = new AlgorithmIdentifier(OiwObjectIdentifiers.IdSha1, DerNull.Instance); + exParams.Add("SHA1WITHRSAANDMGF1", CreatePssParams(sha1AlgId, 20)); + + AlgorithmIdentifier sha224AlgId = new AlgorithmIdentifier(NistObjectIdentifiers.IdSha224, DerNull.Instance); + exParams.Add("SHA224WITHRSAANDMGF1", CreatePssParams(sha224AlgId, 28)); + + AlgorithmIdentifier sha256AlgId = new AlgorithmIdentifier(NistObjectIdentifiers.IdSha256, DerNull.Instance); + exParams.Add("SHA256WITHRSAANDMGF1", CreatePssParams(sha256AlgId, 32)); + + AlgorithmIdentifier sha384AlgId = new AlgorithmIdentifier(NistObjectIdentifiers.IdSha384, DerNull.Instance); + exParams.Add("SHA384WITHRSAANDMGF1", CreatePssParams(sha384AlgId, 48)); + + AlgorithmIdentifier sha512AlgId = new AlgorithmIdentifier(NistObjectIdentifiers.IdSha512, DerNull.Instance); + exParams.Add("SHA512WITHRSAANDMGF1", CreatePssParams(sha512AlgId, 64)); + } + + private static RsassaPssParameters CreatePssParams( + AlgorithmIdentifier hashAlgId, + int saltSize) + { + return new RsassaPssParameters( + hashAlgId, + new AlgorithmIdentifier(PkcsObjectIdentifiers.IdMgf1, hashAlgId), + new DerInteger(saltSize), + new DerInteger(1)); + } + + internal static DerObjectIdentifier GetAlgorithmOid( + string algorithmName) + { + algorithmName = Platform.ToUpperInvariant(algorithmName); + + if (algorithms.Contains(algorithmName)) + { + return (DerObjectIdentifier) algorithms[algorithmName]; + } + + return new DerObjectIdentifier(algorithmName); + } + + internal static AlgorithmIdentifier GetSigAlgID( + DerObjectIdentifier sigOid, + string algorithmName) + { + if (noParams.Contains(sigOid)) + { + return new AlgorithmIdentifier(sigOid); + } + + algorithmName = Platform.ToUpperInvariant(algorithmName); + + if (exParams.Contains(algorithmName)) + { + return new AlgorithmIdentifier(sigOid, (Asn1Encodable) exParams[algorithmName]); + } + + return new AlgorithmIdentifier(sigOid, DerNull.Instance); + } + + internal static IEnumerable GetAlgNames() + { + return new EnumerableProxy(algorithms.Keys); + } + + internal static byte[] GetSignatureForObject( + DerObjectIdentifier sigOid, // TODO Redundant now? + string sigName, + AsymmetricKeyParameter privateKey, + SecureRandom random, + Asn1Encodable ae) + { + if (sigOid == null) + throw new ArgumentNullException("sigOid"); + + ISigner sig = SignerUtilities.GetSigner(sigName); + + if (random != null) + { + sig.Init(true, new ParametersWithRandom(privateKey, random)); + } + else + { + sig.Init(true, privateKey); + } + + byte[] encoded = ae.GetDerEncoded(); + sig.BlockUpdate(encoded, 0, encoded.Length); + + return sig.GenerateSignature(); + } + } +} diff --git a/bc-sharp-crypto/src/x509/X509V1CertificateGenerator.cs b/bc-sharp-crypto/src/x509/X509V1CertificateGenerator.cs new file mode 100644 index 0000000..9adebcb --- /dev/null +++ b/bc-sharp-crypto/src/x509/X509V1CertificateGenerator.cs @@ -0,0 +1,210 @@ +using System; +using System.IO; +using System.Collections; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Operators; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Security.Certificates; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.X509 +{ + /// + /// Class to Generate X509V1 Certificates. + /// + public class X509V1CertificateGenerator + { + private V1TbsCertificateGenerator tbsGen; + private DerObjectIdentifier sigOID; + private AlgorithmIdentifier sigAlgId; + private string signatureAlgorithm; + + /// + /// Default Constructor. + /// + public X509V1CertificateGenerator() + { + tbsGen = new V1TbsCertificateGenerator(); + } + + /// + /// Reset the generator. + /// + public void Reset() + { + tbsGen = new V1TbsCertificateGenerator(); + } + + /// + /// Set the certificate's serial number. + /// + /// Make serial numbers long, if you have no serial number policy make sure the number is at least 16 bytes of secure random data. + /// You will be surprised how ugly a serial number collision can get. + /// The serial number. + public void SetSerialNumber( + BigInteger serialNumber) + { + if (serialNumber.SignValue <= 0) + { + throw new ArgumentException("serial number must be a positive integer", "serialNumber"); + } + + tbsGen.SetSerialNumber(new DerInteger(serialNumber)); + } + + /// + /// Set the issuer distinguished name. + /// The issuer is the entity whose private key is used to sign the certificate. + /// + /// The issuers DN. + public void SetIssuerDN( + X509Name issuer) + { + tbsGen.SetIssuer(issuer); + } + + /// + /// Set the date that this certificate is to be valid from. + /// + /// + public void SetNotBefore( + DateTime date) + { + tbsGen.SetStartDate(new Time(date)); + } + + /// + /// Set the date after which this certificate will no longer be valid. + /// + /// + public void SetNotAfter( + DateTime date) + { + tbsGen.SetEndDate(new Time(date)); + } + + /// + /// Set the subject distinguished name. + /// The subject describes the entity associated with the public key. + /// + /// + public void SetSubjectDN( + X509Name subject) + { + tbsGen.SetSubject(subject); + } + + /// + /// Set the public key that this certificate identifies. + /// + /// + public void SetPublicKey( + AsymmetricKeyParameter publicKey) + { + try + { + tbsGen.SetSubjectPublicKeyInfo( + SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(publicKey)); + } + catch (Exception e) + { + throw new ArgumentException("unable to process key - " + e.ToString()); + } + } + + /// + /// Set the signature algorithm that will be used to sign this certificate. + /// This can be either a name or an OID, names are treated as case insensitive. + /// + /// string representation of the algorithm name + [Obsolete("Not needed if Generate used with an ISignatureFactory")] + public void SetSignatureAlgorithm( + string signatureAlgorithm) + { + this.signatureAlgorithm = signatureAlgorithm; + + try + { + sigOID = X509Utilities.GetAlgorithmOid(signatureAlgorithm); + } + catch (Exception) + { + throw new ArgumentException("Unknown signature type requested", "signatureAlgorithm"); + } + + sigAlgId = X509Utilities.GetSigAlgID(sigOID, signatureAlgorithm); + + tbsGen.SetSignature(sigAlgId); + } + + /// + /// Generate a new X509Certificate. + /// + /// The private key of the issuer used to sign this certificate. + /// An X509Certificate. + [Obsolete("Use Generate with an ISignatureFactory")] + public X509Certificate Generate( + AsymmetricKeyParameter privateKey) + { + return Generate(privateKey, null); + } + + /// + /// Generate a new X509Certificate specifying a SecureRandom instance that you would like to use. + /// + /// The private key of the issuer used to sign this certificate. + /// The Secure Random you want to use. + /// An X509Certificate. + [Obsolete("Use Generate with an ISignatureFactory")] + public X509Certificate Generate( + AsymmetricKeyParameter privateKey, + SecureRandom random) + { + return Generate(new Asn1SignatureFactory(signatureAlgorithm, privateKey, random)); + } + + /// + /// Generate a new X509Certificate using the passed in SignatureCalculator. + /// + /// A signature calculator factory with the necessary algorithm details. + /// An X509Certificate. + public X509Certificate Generate(ISignatureFactory signatureCalculatorFactory) + { + tbsGen.SetSignature ((AlgorithmIdentifier)signatureCalculatorFactory.AlgorithmDetails); + + TbsCertificateStructure tbsCert = tbsGen.GenerateTbsCertificate(); + + IStreamCalculator streamCalculator = signatureCalculatorFactory.CreateCalculator(); + + byte[] encoded = tbsCert.GetDerEncoded(); + + streamCalculator.Stream.Write(encoded, 0, encoded.Length); + + Platform.Dispose(streamCalculator.Stream); + + return GenerateJcaObject(tbsCert, (AlgorithmIdentifier)signatureCalculatorFactory.AlgorithmDetails, ((IBlockResult)streamCalculator.GetResult()).Collect()); + } + + private X509Certificate GenerateJcaObject( + TbsCertificateStructure tbsCert, + AlgorithmIdentifier sigAlg, + byte[] signature) + { + return new X509Certificate( + new X509CertificateStructure(tbsCert, sigAlg, new DerBitString(signature))); + } + + /// + /// Allows enumeration of the signature names supported by the generator. + /// + public IEnumerable SignatureAlgNames + { + get { return X509Utilities.GetAlgNames(); } + } + } +} diff --git a/bc-sharp-crypto/src/x509/X509V2AttributeCertificate.cs b/bc-sharp-crypto/src/x509/X509V2AttributeCertificate.cs new file mode 100644 index 0000000..c41b312 --- /dev/null +++ b/bc-sharp-crypto/src/x509/X509V2AttributeCertificate.cs @@ -0,0 +1,280 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Operators; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Security.Certificates; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.X509 +{ + /// An implementation of a version 2 X.509 Attribute Certificate. + public class X509V2AttributeCertificate + : X509ExtensionBase, IX509AttributeCertificate + { + private readonly AttributeCertificate cert; + private readonly DateTime notBefore; + private readonly DateTime notAfter; + + private static AttributeCertificate GetObject(Stream input) + { + try + { + return AttributeCertificate.GetInstance(Asn1Object.FromStream(input)); + } + catch (IOException e) + { + throw e; + } + catch (Exception e) + { + throw new IOException("exception decoding certificate structure", e); + } + } + + public X509V2AttributeCertificate( + Stream encIn) + : this(GetObject(encIn)) + { + } + + public X509V2AttributeCertificate( + byte[] encoded) + : this(new MemoryStream(encoded, false)) + { + } + + internal X509V2AttributeCertificate( + AttributeCertificate cert) + { + this.cert = cert; + + try + { + this.notAfter = cert.ACInfo.AttrCertValidityPeriod.NotAfterTime.ToDateTime(); + this.notBefore = cert.ACInfo.AttrCertValidityPeriod.NotBeforeTime.ToDateTime(); + } + catch (Exception e) + { + throw new IOException("invalid data structure in certificate!", e); + } + } + + public virtual int Version + { + get { return cert.ACInfo.Version.Value.IntValue + 1; } + } + + public virtual BigInteger SerialNumber + { + get { return cert.ACInfo.SerialNumber.Value; } + } + + public virtual AttributeCertificateHolder Holder + { + get + { + return new AttributeCertificateHolder((Asn1Sequence)cert.ACInfo.Holder.ToAsn1Object()); + } + } + + public virtual AttributeCertificateIssuer Issuer + { + get + { + return new AttributeCertificateIssuer(cert.ACInfo.Issuer); + } + } + + public virtual DateTime NotBefore + { + get { return notBefore; } + } + + public virtual DateTime NotAfter + { + get { return notAfter; } + } + + public virtual bool[] GetIssuerUniqueID() + { + DerBitString id = cert.ACInfo.IssuerUniqueID; + + if (id != null) + { + byte[] bytes = id.GetBytes(); + bool[] boolId = new bool[bytes.Length * 8 - id.PadBits]; + + for (int i = 0; i != boolId.Length; i++) + { + //boolId[i] = (bytes[i / 8] & (0x80 >>> (i % 8))) != 0; + boolId[i] = (bytes[i / 8] & (0x80 >> (i % 8))) != 0; + } + + return boolId; + } + + return null; + } + + public virtual bool IsValidNow + { + get { return IsValid(DateTime.UtcNow); } + } + + public virtual bool IsValid( + DateTime date) + { + return date.CompareTo(NotBefore) >= 0 && date.CompareTo(NotAfter) <= 0; + } + + public virtual void CheckValidity() + { + this.CheckValidity(DateTime.UtcNow); + } + + public virtual void CheckValidity( + DateTime date) + { + if (date.CompareTo(NotAfter) > 0) + throw new CertificateExpiredException("certificate expired on " + NotAfter); + if (date.CompareTo(NotBefore) < 0) + throw new CertificateNotYetValidException("certificate not valid until " + NotBefore); + } + + public virtual AlgorithmIdentifier SignatureAlgorithm + { + get { return cert.SignatureAlgorithm; } + } + + public virtual byte[] GetSignature() + { + return cert.GetSignatureOctets(); + } + + public virtual void Verify( + AsymmetricKeyParameter key) + { + CheckSignature(new Asn1VerifierFactory(cert.SignatureAlgorithm, key)); + } + + /// + /// Verify the certificate's signature using a verifier created using the passed in verifier provider. + /// + /// An appropriate provider for verifying the certificate's signature. + /// True if the signature is valid. + /// If verifier provider is not appropriate or the certificate algorithm is invalid. + public virtual void Verify( + IVerifierFactoryProvider verifierProvider) + { + CheckSignature(verifierProvider.CreateVerifierFactory(cert.SignatureAlgorithm)); + } + + protected virtual void CheckSignature( + IVerifierFactory verifier) + { + if (!cert.SignatureAlgorithm.Equals(cert.ACInfo.Signature)) + { + throw new CertificateException("Signature algorithm in certificate info not same as outer certificate"); + } + + IStreamCalculator streamCalculator = verifier.CreateCalculator(); + + try + { + byte[] b = this.cert.ACInfo.GetEncoded(); + + streamCalculator.Stream.Write(b, 0, b.Length); + + Platform.Dispose(streamCalculator.Stream); + } + catch (IOException e) + { + throw new SignatureException("Exception encoding certificate info object", e); + } + + if (!((IVerifier)streamCalculator.GetResult()).IsVerified(this.GetSignature())) + { + throw new InvalidKeyException("Public key presented not for certificate signature"); + } + } + + public virtual byte[] GetEncoded() + { + return cert.GetEncoded(); + } + + protected override X509Extensions GetX509Extensions() + { + return cert.ACInfo.Extensions; + } + + public virtual X509Attribute[] GetAttributes() + { + Asn1Sequence seq = cert.ACInfo.Attributes; + X509Attribute[] attrs = new X509Attribute[seq.Count]; + + for (int i = 0; i != seq.Count; i++) + { + attrs[i] = new X509Attribute((Asn1Encodable)seq[i]); + } + + return attrs; + } + + public virtual X509Attribute[] GetAttributes( + string oid) + { + Asn1Sequence seq = cert.ACInfo.Attributes; + IList list = Platform.CreateArrayList(); + + for (int i = 0; i != seq.Count; i++) + { + X509Attribute attr = new X509Attribute((Asn1Encodable)seq[i]); + if (attr.Oid.Equals(oid)) + { + list.Add(attr); + } + } + + if (list.Count < 1) + { + return null; + } + + X509Attribute[] result = new X509Attribute[list.Count]; + for (int i = 0; i < list.Count; ++i) + { + result[i] = (X509Attribute)list[i]; + } + return result; + } + + public override bool Equals( + object obj) + { + if (obj == this) + return true; + + X509V2AttributeCertificate other = obj as X509V2AttributeCertificate; + + if (other == null) + return false; + + return cert.Equals(other.cert); + + // NB: May prefer this implementation of Equals if more than one certificate implementation in play + //return Arrays.AreEqual(this.GetEncoded(), other.GetEncoded()); + } + + public override int GetHashCode() + { + return cert.GetHashCode(); + } + } +} diff --git a/bc-sharp-crypto/src/x509/X509V2AttributeCertificateGenerator.cs b/bc-sharp-crypto/src/x509/X509V2AttributeCertificateGenerator.cs new file mode 100644 index 0000000..bf046cd --- /dev/null +++ b/bc-sharp-crypto/src/x509/X509V2AttributeCertificateGenerator.cs @@ -0,0 +1,203 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Operators; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Security.Certificates; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.X509 +{ + /// Class to produce an X.509 Version 2 AttributeCertificate. + public class X509V2AttributeCertificateGenerator + { + private readonly X509ExtensionsGenerator extGenerator = new X509ExtensionsGenerator(); + + private V2AttributeCertificateInfoGenerator acInfoGen; + private DerObjectIdentifier sigOID; + private AlgorithmIdentifier sigAlgId; + private string signatureAlgorithm; + + public X509V2AttributeCertificateGenerator() + { + acInfoGen = new V2AttributeCertificateInfoGenerator(); + } + + /// Reset the generator + public void Reset() + { + acInfoGen = new V2AttributeCertificateInfoGenerator(); + extGenerator.Reset(); + } + + /// Set the Holder of this Attribute Certificate. + public void SetHolder( + AttributeCertificateHolder holder) + { + acInfoGen.SetHolder(holder.holder); + } + + /// Set the issuer. + public void SetIssuer( + AttributeCertificateIssuer issuer) + { + acInfoGen.SetIssuer(AttCertIssuer.GetInstance(issuer.form)); + } + + /// Set the serial number for the certificate. + public void SetSerialNumber( + BigInteger serialNumber) + { + acInfoGen.SetSerialNumber(new DerInteger(serialNumber)); + } + + public void SetNotBefore( + DateTime date) + { + acInfoGen.SetStartDate(new DerGeneralizedTime(date)); + } + + public void SetNotAfter( + DateTime date) + { + acInfoGen.SetEndDate(new DerGeneralizedTime(date)); + } + + /// + /// Set the signature algorithm. This can be either a name or an OID, names + /// are treated as case insensitive. + /// + /// The algorithm name. + [Obsolete("Not needed if Generate used with an ISignatureFactory")] + public void SetSignatureAlgorithm( + string signatureAlgorithm) + { + this.signatureAlgorithm = signatureAlgorithm; + + try + { + sigOID = X509Utilities.GetAlgorithmOid(signatureAlgorithm); + } + catch (Exception) + { + throw new ArgumentException("Unknown signature type requested"); + } + + sigAlgId = X509Utilities.GetSigAlgID(sigOID, signatureAlgorithm); + + acInfoGen.SetSignature(sigAlgId); + } + + /// Add an attribute. + public void AddAttribute( + X509Attribute attribute) + { + acInfoGen.AddAttribute(AttributeX509.GetInstance(attribute.ToAsn1Object())); + } + + public void SetIssuerUniqueId( + bool[] iui) + { + // TODO convert bool array to bit string + //acInfoGen.SetIssuerUniqueID(iui); + throw Platform.CreateNotImplementedException("SetIssuerUniqueId()"); + } + + /// Add a given extension field for the standard extensions tag. + public void AddExtension( + string oid, + bool critical, + Asn1Encodable extensionValue) + { + extGenerator.AddExtension(new DerObjectIdentifier(oid), critical, extensionValue); + } + + /// + /// Add a given extension field for the standard extensions tag. + /// The value parameter becomes the contents of the octet string associated + /// with the extension. + /// + public void AddExtension( + string oid, + bool critical, + byte[] extensionValue) + { + extGenerator.AddExtension(new DerObjectIdentifier(oid), critical, extensionValue); + } + + /// + /// Generate an X509 certificate, based on the current issuer and subject. + /// + [Obsolete("Use Generate with an ISignatureFactory")] + public IX509AttributeCertificate Generate( + AsymmetricKeyParameter privateKey) + { + return Generate(privateKey, null); + } + + /// + /// Generate an X509 certificate, based on the current issuer and subject, + /// using the supplied source of randomness, if required. + /// + [Obsolete("Use Generate with an ISignatureFactory")] + public IX509AttributeCertificate Generate( + AsymmetricKeyParameter privateKey, + SecureRandom random) + { + return Generate(new Asn1SignatureFactory(signatureAlgorithm, privateKey, random)); + } + + /// + /// Generate a new X.509 Attribute Certificate using the passed in SignatureCalculator. + /// + /// A signature calculator factory with the necessary algorithm details. + /// An IX509AttributeCertificate. + public IX509AttributeCertificate Generate(ISignatureFactory signatureCalculatorFactory) + { + if (!extGenerator.IsEmpty) + { + acInfoGen.SetExtensions(extGenerator.Generate()); + } + + AttributeCertificateInfo acInfo = acInfoGen.GenerateAttributeCertificateInfo(); + + byte[] encoded = acInfo.GetDerEncoded(); + + IStreamCalculator streamCalculator = signatureCalculatorFactory.CreateCalculator(); + + streamCalculator.Stream.Write(encoded, 0, encoded.Length); + + Platform.Dispose(streamCalculator.Stream); + + Asn1EncodableVector v = new Asn1EncodableVector(); + + v.Add(acInfo, (AlgorithmIdentifier)signatureCalculatorFactory.AlgorithmDetails); + + try + { + v.Add(new DerBitString(((IBlockResult)streamCalculator.GetResult()).Collect())); + + return new X509V2AttributeCertificate(AttributeCertificate.GetInstance(new DerSequence(v))); + } + catch (Exception e) + { + // TODO +// throw new ExtCertificateEncodingException("constructed invalid certificate", e); + throw new CertificateEncodingException("constructed invalid certificate", e); + } + } + + /// + /// Allows enumeration of the signature names supported by the generator. + /// + public IEnumerable SignatureAlgNames + { + get { return X509Utilities.GetAlgNames(); } + } + } +} diff --git a/bc-sharp-crypto/src/x509/X509V2CRLGenerator.cs b/bc-sharp-crypto/src/x509/X509V2CRLGenerator.cs new file mode 100644 index 0000000..566d502 --- /dev/null +++ b/bc-sharp-crypto/src/x509/X509V2CRLGenerator.cs @@ -0,0 +1,278 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Operators; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Security.Certificates; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; + +namespace Org.BouncyCastle.X509 +{ + /** + * class to produce an X.509 Version 2 CRL. + */ + public class X509V2CrlGenerator + { + private readonly X509ExtensionsGenerator extGenerator = new X509ExtensionsGenerator(); + + private V2TbsCertListGenerator tbsGen; + private DerObjectIdentifier sigOID; + private AlgorithmIdentifier sigAlgId; + private string signatureAlgorithm; + + public X509V2CrlGenerator() + { + tbsGen = new V2TbsCertListGenerator(); + } + + /** + * reset the generator + */ + public void Reset() + { + tbsGen = new V2TbsCertListGenerator(); + extGenerator.Reset(); + } + + /** + * Set the issuer distinguished name - the issuer is the entity whose private key is used to sign the + * certificate. + */ + public void SetIssuerDN( + X509Name issuer) + { + tbsGen.SetIssuer(issuer); + } + + public void SetThisUpdate( + DateTime date) + { + tbsGen.SetThisUpdate(new Time(date)); + } + + public void SetNextUpdate( + DateTime date) + { + tbsGen.SetNextUpdate(new Time(date)); + } + + /** + * Reason being as indicated by CrlReason, i.e. CrlReason.KeyCompromise + * or 0 if CrlReason is not to be used + **/ + public void AddCrlEntry( + BigInteger userCertificate, + DateTime revocationDate, + int reason) + { + tbsGen.AddCrlEntry(new DerInteger(userCertificate), new Time(revocationDate), reason); + } + + /** + * Add a CRL entry with an Invalidity Date extension as well as a CrlReason extension. + * Reason being as indicated by CrlReason, i.e. CrlReason.KeyCompromise + * or 0 if CrlReason is not to be used + **/ + public void AddCrlEntry( + BigInteger userCertificate, + DateTime revocationDate, + int reason, + DateTime invalidityDate) + { + tbsGen.AddCrlEntry(new DerInteger(userCertificate), new Time(revocationDate), reason, new DerGeneralizedTime(invalidityDate)); + } + + /** + * Add a CRL entry with extensions. + **/ + public void AddCrlEntry( + BigInteger userCertificate, + DateTime revocationDate, + X509Extensions extensions) + { + tbsGen.AddCrlEntry(new DerInteger(userCertificate), new Time(revocationDate), extensions); + } + + /** + * Add the CRLEntry objects contained in a previous CRL. + * + * @param other the X509Crl to source the other entries from. + */ + public void AddCrl( + X509Crl other) + { + if (other == null) + throw new ArgumentNullException("other"); + + ISet revocations = other.GetRevokedCertificates(); + + if (revocations != null) + { + foreach (X509CrlEntry entry in revocations) + { + try + { + tbsGen.AddCrlEntry( + Asn1Sequence.GetInstance( + Asn1Object.FromByteArray(entry.GetEncoded()))); + } + catch (IOException e) + { + throw new CrlException("exception processing encoding of CRL", e); + } + } + } + } + + /// + /// Set the signature algorithm that will be used to sign this CRL. + /// + /// + [Obsolete("Not needed if Generate used with an ISignatureFactory")] + public void SetSignatureAlgorithm( + string signatureAlgorithm) + { + this.signatureAlgorithm = signatureAlgorithm; + + try + { + sigOID = X509Utilities.GetAlgorithmOid(signatureAlgorithm); + } + catch (Exception e) + { + throw new ArgumentException("Unknown signature type requested", e); + } + + sigAlgId = X509Utilities.GetSigAlgID(sigOID, signatureAlgorithm); + + tbsGen.SetSignature(sigAlgId); + } + + /** + * add a given extension field for the standard extensions tag (tag 0) + */ + public void AddExtension( + string oid, + bool critical, + Asn1Encodable extensionValue) + { + extGenerator.AddExtension(new DerObjectIdentifier(oid), critical, extensionValue); + } + + /** + * add a given extension field for the standard extensions tag (tag 0) + */ + public void AddExtension( + DerObjectIdentifier oid, + bool critical, + Asn1Encodable extensionValue) + { + extGenerator.AddExtension(oid, critical, extensionValue); + } + + /** + * add a given extension field for the standard extensions tag (tag 0) + */ + public void AddExtension( + string oid, + bool critical, + byte[] extensionValue) + { + extGenerator.AddExtension(new DerObjectIdentifier(oid), critical, new DerOctetString(extensionValue)); + } + + /** + * add a given extension field for the standard extensions tag (tag 0) + */ + public void AddExtension( + DerObjectIdentifier oid, + bool critical, + byte[] extensionValue) + { + extGenerator.AddExtension(oid, critical, new DerOctetString(extensionValue)); + } + + /// + /// Generate an X.509 CRL, based on the current issuer and subject. + /// + /// The private key of the issuer that is signing this certificate. + /// An X509Crl. + [Obsolete("Use Generate with an ISignatureFactory")] + public X509Crl Generate( + AsymmetricKeyParameter privateKey) + { + return Generate(privateKey, null); + } + + /// + /// Generate an X.509 CRL, based on the current issuer and subject using the specified secure random. + /// + /// The private key of the issuer that is signing this certificate. + /// Your Secure Random instance. + /// An X509Crl. + [Obsolete("Use Generate with an ISignatureFactory")] + public X509Crl Generate( + AsymmetricKeyParameter privateKey, + SecureRandom random) + { + return Generate(new Asn1SignatureFactory(signatureAlgorithm, privateKey, random)); + } + + /// + /// Generate a new X509Crl using the passed in SignatureCalculator. + /// + /// A signature calculator factory with the necessary algorithm details. + /// An X509Crl. + public X509Crl Generate(ISignatureFactory signatureCalculatorFactory) + { + tbsGen.SetSignature((AlgorithmIdentifier)signatureCalculatorFactory.AlgorithmDetails); + + TbsCertificateList tbsCertList = GenerateCertList(); + + IStreamCalculator streamCalculator = signatureCalculatorFactory.CreateCalculator(); + + byte[] encoded = tbsCertList.GetDerEncoded(); + + streamCalculator.Stream.Write(encoded, 0, encoded.Length); + + Platform.Dispose(streamCalculator.Stream); + + return GenerateJcaObject(tbsCertList, (AlgorithmIdentifier)signatureCalculatorFactory.AlgorithmDetails, ((IBlockResult)streamCalculator.GetResult()).Collect()); + } + + private TbsCertificateList GenerateCertList() + { + if (!extGenerator.IsEmpty) + { + tbsGen.SetExtensions(extGenerator.Generate()); + } + + return tbsGen.GenerateTbsCertList(); + } + + private X509Crl GenerateJcaObject( + TbsCertificateList tbsCrl, + AlgorithmIdentifier algId, + byte[] signature) + { + return new X509Crl( + CertificateList.GetInstance( + new DerSequence(tbsCrl, algId, new DerBitString(signature)))); + } + + /// + /// Allows enumeration of the signature names supported by the generator. + /// + public IEnumerable SignatureAlgNames + { + get { return X509Utilities.GetAlgNames(); } + } + } +} diff --git a/bc-sharp-crypto/src/x509/X509V3CertificateGenerator.cs b/bc-sharp-crypto/src/x509/X509V3CertificateGenerator.cs new file mode 100644 index 0000000..bc619c3 --- /dev/null +++ b/bc-sharp-crypto/src/x509/X509V3CertificateGenerator.cs @@ -0,0 +1,344 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Operators; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Security.Certificates; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.X509.Extension; + +namespace Org.BouncyCastle.X509 +{ + /// + /// A class to Generate Version 3 X509Certificates. + /// + public class X509V3CertificateGenerator + { + private readonly X509ExtensionsGenerator extGenerator = new X509ExtensionsGenerator(); + + private V3TbsCertificateGenerator tbsGen; + private DerObjectIdentifier sigOid; + private AlgorithmIdentifier sigAlgId; + private string signatureAlgorithm; + + public X509V3CertificateGenerator() + { + tbsGen = new V3TbsCertificateGenerator(); + } + + /// + /// Reset the Generator. + /// + public void Reset() + { + tbsGen = new V3TbsCertificateGenerator(); + extGenerator.Reset(); + } + + /// + /// Set the certificate's serial number. + /// + /// Make serial numbers long, if you have no serial number policy make sure the number is at least 16 bytes of secure random data. + /// You will be surprised how ugly a serial number collision can Get. + /// The serial number. + public void SetSerialNumber( + BigInteger serialNumber) + { + if (serialNumber.SignValue <= 0) + { + throw new ArgumentException("serial number must be a positive integer", "serialNumber"); + } + + tbsGen.SetSerialNumber(new DerInteger(serialNumber)); + } + + /// + /// Set the distinguished name of the issuer. + /// The issuer is the entity which is signing the certificate. + /// + /// The issuer's DN. + public void SetIssuerDN( + X509Name issuer) + { + tbsGen.SetIssuer(issuer); + } + + /// + /// Set the date that this certificate is to be valid from. + /// + /// + public void SetNotBefore( + DateTime date) + { + tbsGen.SetStartDate(new Time(date)); + } + + /// + /// Set the date after which this certificate will no longer be valid. + /// + /// + public void SetNotAfter( + DateTime date) + { + tbsGen.SetEndDate(new Time(date)); + } + + /// + /// Set the DN of the entity that this certificate is about. + /// + /// + public void SetSubjectDN( + X509Name subject) + { + tbsGen.SetSubject(subject); + } + + /// + /// Set the public key that this certificate identifies. + /// + /// + public void SetPublicKey( + AsymmetricKeyParameter publicKey) + { + tbsGen.SetSubjectPublicKeyInfo(SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(publicKey)); + } + + /// + /// Set the signature algorithm that will be used to sign this certificate. + /// + /// + [Obsolete("Not needed if Generate used with an ISignatureFactory")] + public void SetSignatureAlgorithm( + string signatureAlgorithm) + { + this.signatureAlgorithm = signatureAlgorithm; + + try + { + sigOid = X509Utilities.GetAlgorithmOid(signatureAlgorithm); + } + catch (Exception) + { + throw new ArgumentException("Unknown signature type requested: " + signatureAlgorithm); + } + + sigAlgId = X509Utilities.GetSigAlgID(sigOid, signatureAlgorithm); + + tbsGen.SetSignature(sigAlgId); + } + + /// + /// Set the subject unique ID - note: it is very rare that it is correct to do this. + /// + /// + public void SetSubjectUniqueID( + bool[] uniqueID) + { + tbsGen.SetSubjectUniqueID(booleanToBitString(uniqueID)); + } + + /// + /// Set the issuer unique ID - note: it is very rare that it is correct to do this. + /// + /// + public void SetIssuerUniqueID( + bool[] uniqueID) + { + tbsGen.SetIssuerUniqueID(booleanToBitString(uniqueID)); + } + + private DerBitString booleanToBitString( + bool[] id) + { + byte[] bytes = new byte[(id.Length + 7) / 8]; + + for (int i = 0; i != id.Length; i++) + { + if (id[i]) + { + bytes[i / 8] |= (byte)(1 << ((7 - (i % 8)))); + } + } + + int pad = id.Length % 8; + + if (pad == 0) + { + return new DerBitString(bytes); + } + + return new DerBitString(bytes, 8 - pad); + } + + /// + /// Add a given extension field for the standard extensions tag (tag 3). + /// + /// string containing a dotted decimal Object Identifier. + /// Is it critical. + /// The value. + public void AddExtension( + string oid, + bool critical, + Asn1Encodable extensionValue) + { + extGenerator.AddExtension(new DerObjectIdentifier(oid), critical, extensionValue); + } + + /// + /// Add an extension to this certificate. + /// + /// Its Object Identifier. + /// Is it critical. + /// The value. + public void AddExtension( + DerObjectIdentifier oid, + bool critical, + Asn1Encodable extensionValue) + { + extGenerator.AddExtension(oid, critical, extensionValue); + } + + /// + /// Add an extension using a string with a dotted decimal OID. + /// + /// string containing a dotted decimal Object Identifier. + /// Is it critical. + /// byte[] containing the value of this extension. + public void AddExtension( + string oid, + bool critical, + byte[] extensionValue) + { + extGenerator.AddExtension(new DerObjectIdentifier(oid), critical, new DerOctetString(extensionValue)); + } + + /// + /// Add an extension to this certificate. + /// + /// Its Object Identifier. + /// Is it critical. + /// byte[] containing the value of this extension. + public void AddExtension( + DerObjectIdentifier oid, + bool critical, + byte[] extensionValue) + { + extGenerator.AddExtension(oid, critical, new DerOctetString(extensionValue)); + } + + /// + /// Add a given extension field for the standard extensions tag (tag 3), + /// copying the extension value from another certificate. + /// + public void CopyAndAddExtension( + string oid, + bool critical, + X509Certificate cert) + { + CopyAndAddExtension(new DerObjectIdentifier(oid), critical, cert); + } + + /** + * add a given extension field for the standard extensions tag (tag 3) + * copying the extension value from another certificate. + * @throws CertificateParsingException if the extension cannot be extracted. + */ + public void CopyAndAddExtension( + DerObjectIdentifier oid, + bool critical, + X509Certificate cert) + { + Asn1OctetString extValue = cert.GetExtensionValue(oid); + + if (extValue == null) + { + throw new CertificateParsingException("extension " + oid + " not present"); + } + + try + { + Asn1Encodable value = X509ExtensionUtilities.FromExtensionValue(extValue); + + this.AddExtension(oid, critical, value); + } + catch (Exception e) + { + throw new CertificateParsingException(e.Message, e); + } + } + + /// + /// Generate an X509Certificate. + /// + /// The private key of the issuer that is signing this certificate. + /// An X509Certificate. + [Obsolete("Use Generate with an ISignatureFactory")] + public X509Certificate Generate( + AsymmetricKeyParameter privateKey) + { + return Generate(privateKey, null); + } + + /// + /// Generate an X509Certificate using your own SecureRandom. + /// + /// The private key of the issuer that is signing this certificate. + /// You Secure Random instance. + /// An X509Certificate. + [Obsolete("Use Generate with an ISignatureFactory")] + public X509Certificate Generate( + AsymmetricKeyParameter privateKey, + SecureRandom random) + { + return Generate(new Asn1SignatureFactory(signatureAlgorithm, privateKey, random)); + } + + /// + /// Generate a new X509Certificate using the passed in SignatureCalculator. + /// + /// A signature calculator factory with the necessary algorithm details. + /// An X509Certificate. + public X509Certificate Generate(ISignatureFactory signatureCalculatorFactory) + { + tbsGen.SetSignature ((AlgorithmIdentifier)signatureCalculatorFactory.AlgorithmDetails); + + if (!extGenerator.IsEmpty) + { + tbsGen.SetExtensions(extGenerator.Generate()); + } + + TbsCertificateStructure tbsCert = tbsGen.GenerateTbsCertificate(); + + IStreamCalculator streamCalculator = signatureCalculatorFactory.CreateCalculator(); + + byte[] encoded = tbsCert.GetDerEncoded(); + + streamCalculator.Stream.Write(encoded, 0, encoded.Length); + + Platform.Dispose(streamCalculator.Stream); + + return GenerateJcaObject(tbsCert, (AlgorithmIdentifier)signatureCalculatorFactory.AlgorithmDetails, ((IBlockResult)streamCalculator.GetResult()).Collect()); + } + + private X509Certificate GenerateJcaObject( + TbsCertificateStructure tbsCert, + AlgorithmIdentifier sigAlg, + byte[] signature) + { + return new X509Certificate( + new X509CertificateStructure(tbsCert, sigAlg, new DerBitString(signature))); + } + + /// + /// Allows enumeration of the signature names supported by the generator. + /// + public IEnumerable SignatureAlgNames + { + get { return X509Utilities.GetAlgNames(); } + } + } +} diff --git a/bc-sharp-crypto/src/x509/extension/AuthorityKeyIdentifierStructure.cs b/bc-sharp-crypto/src/x509/extension/AuthorityKeyIdentifierStructure.cs new file mode 100644 index 0000000..006dc00 --- /dev/null +++ b/bc-sharp-crypto/src/x509/extension/AuthorityKeyIdentifierStructure.cs @@ -0,0 +1,102 @@ +using System; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Security.Certificates; + +namespace Org.BouncyCastle.X509.Extension +{ + /// A high level authority key identifier. + public class AuthorityKeyIdentifierStructure + : AuthorityKeyIdentifier + { + /** + * Constructor which will take the byte[] returned from getExtensionValue() + * + * @param encodedValue a DER octet encoded string with the extension structure in it. + * @throws IOException on parsing errors. + */ + // TODO Add a functional constructor from byte[]? + public AuthorityKeyIdentifierStructure( + Asn1OctetString encodedValue) + : base((Asn1Sequence) X509ExtensionUtilities.FromExtensionValue(encodedValue)) + { + } + + private static Asn1Sequence FromCertificate( + X509Certificate certificate) + { + try + { + GeneralName genName = new GeneralName( + PrincipalUtilities.GetIssuerX509Principal(certificate)); + + if (certificate.Version == 3) + { + Asn1OctetString ext = certificate.GetExtensionValue(X509Extensions.SubjectKeyIdentifier); + + if (ext != null) + { + Asn1OctetString str = (Asn1OctetString) X509ExtensionUtilities.FromExtensionValue(ext); + + return (Asn1Sequence) new AuthorityKeyIdentifier( + str.GetOctets(), new GeneralNames(genName), certificate.SerialNumber).ToAsn1Object(); + } + } + + SubjectPublicKeyInfo info = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo( + certificate.GetPublicKey()); + + return (Asn1Sequence) new AuthorityKeyIdentifier( + info, new GeneralNames(genName), certificate.SerialNumber).ToAsn1Object(); + } + catch (Exception e) + { + throw new CertificateParsingException("Exception extracting certificate details", e); + } + } + + private static Asn1Sequence FromKey( + AsymmetricKeyParameter pubKey) + { + try + { + SubjectPublicKeyInfo info = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(pubKey); + + return (Asn1Sequence) new AuthorityKeyIdentifier(info).ToAsn1Object(); + } + catch (Exception e) + { + throw new InvalidKeyException("can't process key: " + e); + } + } + + /** + * Create an AuthorityKeyIdentifier using the passed in certificate's public + * key, issuer and serial number. + * + * @param certificate the certificate providing the information. + * @throws CertificateParsingException if there is a problem processing the certificate + */ + public AuthorityKeyIdentifierStructure( + X509Certificate certificate) + : base(FromCertificate(certificate)) + { + } + + /** + * Create an AuthorityKeyIdentifier using just the hash of the + * public key. + * + * @param pubKey the key to generate the hash from. + * @throws InvalidKeyException if there is a problem using the key. + */ + public AuthorityKeyIdentifierStructure( + AsymmetricKeyParameter pubKey) + : base(FromKey(pubKey)) + { + } + } +} diff --git a/bc-sharp-crypto/src/x509/extension/SubjectKeyIdentifierStructure.cs b/bc-sharp-crypto/src/x509/extension/SubjectKeyIdentifierStructure.cs new file mode 100644 index 0000000..4c7b79a --- /dev/null +++ b/bc-sharp-crypto/src/x509/extension/SubjectKeyIdentifierStructure.cs @@ -0,0 +1,49 @@ +using System; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Security.Certificates; + +namespace Org.BouncyCastle.X509.Extension +{ + /** + * A high level subject key identifier. + */ + public class SubjectKeyIdentifierStructure + : SubjectKeyIdentifier + { + /** + * Constructor which will take the byte[] returned from getExtensionValue() + * + * @param encodedValue a DER octet encoded string with the extension structure in it. + * @throws IOException on parsing errors. + */ + public SubjectKeyIdentifierStructure( + Asn1OctetString encodedValue) + : base((Asn1OctetString) X509ExtensionUtilities.FromExtensionValue(encodedValue)) + { + } + + private static Asn1OctetString FromPublicKey( + AsymmetricKeyParameter pubKey) + { + try + { + SubjectPublicKeyInfo info = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(pubKey); + + return (Asn1OctetString) new SubjectKeyIdentifier(info).ToAsn1Object(); + } + catch (Exception e) + { + throw new CertificateParsingException("Exception extracting certificate details: " + e.ToString()); + } + } + + public SubjectKeyIdentifierStructure( + AsymmetricKeyParameter pubKey) + : base(FromPublicKey(pubKey)) + { + } + } +} diff --git a/bc-sharp-crypto/src/x509/extension/X509ExtensionUtil.cs b/bc-sharp-crypto/src/x509/extension/X509ExtensionUtil.cs new file mode 100644 index 0000000..5f65ebf --- /dev/null +++ b/bc-sharp-crypto/src/x509/extension/X509ExtensionUtil.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Security.Certificates; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.X509.Extension +{ + public class X509ExtensionUtilities + { + public static Asn1Object FromExtensionValue( + Asn1OctetString extensionValue) + { + return Asn1Object.FromByteArray(extensionValue.GetOctets()); + } + + public static ICollection GetIssuerAlternativeNames( + X509Certificate cert) + { + Asn1OctetString extVal = cert.GetExtensionValue(X509Extensions.IssuerAlternativeName); + + return GetAlternativeName(extVal); + } + + public static ICollection GetSubjectAlternativeNames( + X509Certificate cert) + { + Asn1OctetString extVal = cert.GetExtensionValue(X509Extensions.SubjectAlternativeName); + + return GetAlternativeName(extVal); + } + + private static ICollection GetAlternativeName( + Asn1OctetString extVal) + { + IList temp = Platform.CreateArrayList(); + + if (extVal != null) + { + try + { + Asn1Sequence seq = DerSequence.GetInstance(FromExtensionValue(extVal)); + + foreach (Asn1Encodable primName in seq) + { + IList list = Platform.CreateArrayList(); + GeneralName genName = GeneralName.GetInstance(primName); + + list.Add(genName.TagNo); + + switch (genName.TagNo) + { + case GeneralName.EdiPartyName: + case GeneralName.X400Address: + case GeneralName.OtherName: + list.Add(genName.Name.ToAsn1Object()); + break; + case GeneralName.DirectoryName: + list.Add(X509Name.GetInstance(genName.Name).ToString()); + break; + case GeneralName.DnsName: + case GeneralName.Rfc822Name: + case GeneralName.UniformResourceIdentifier: + list.Add(((IAsn1String)genName.Name).GetString()); + break; + case GeneralName.RegisteredID: + list.Add(DerObjectIdentifier.GetInstance(genName.Name).Id); + break; + case GeneralName.IPAddress: + list.Add(DerOctetString.GetInstance(genName.Name).GetOctets()); + break; + default: + throw new IOException("Bad tag number: " + genName.TagNo); + } + + temp.Add(list); + } + } + catch (Exception e) + { + throw new CertificateParsingException(e.Message); + } + } + + return temp; + } + } +} diff --git a/bc-sharp-crypto/src/x509/store/IX509Selector.cs b/bc-sharp-crypto/src/x509/store/IX509Selector.cs new file mode 100644 index 0000000..75358cb --- /dev/null +++ b/bc-sharp-crypto/src/x509/store/IX509Selector.cs @@ -0,0 +1,15 @@ +using System; + +namespace Org.BouncyCastle.X509.Store +{ + public interface IX509Selector +#if !(SILVERLIGHT || PORTABLE) + : ICloneable +#endif + { +#if SILVERLIGHT || PORTABLE + object Clone(); +#endif + bool Match(object obj); + } +} diff --git a/bc-sharp-crypto/src/x509/store/IX509Store.cs b/bc-sharp-crypto/src/x509/store/IX509Store.cs new file mode 100644 index 0000000..e5c3a46 --- /dev/null +++ b/bc-sharp-crypto/src/x509/store/IX509Store.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections; + +namespace Org.BouncyCastle.X509.Store +{ + public interface IX509Store + { +// void Init(IX509StoreParameters parameters); + ICollection GetMatches(IX509Selector selector); + } +} diff --git a/bc-sharp-crypto/src/x509/store/IX509StoreParameters.cs b/bc-sharp-crypto/src/x509/store/IX509StoreParameters.cs new file mode 100644 index 0000000..aee3036 --- /dev/null +++ b/bc-sharp-crypto/src/x509/store/IX509StoreParameters.cs @@ -0,0 +1,8 @@ +using System; + +namespace Org.BouncyCastle.X509.Store +{ + public interface IX509StoreParameters + { + } +} diff --git a/bc-sharp-crypto/src/x509/store/NoSuchStoreException.cs b/bc-sharp-crypto/src/x509/store/NoSuchStoreException.cs new file mode 100644 index 0000000..28b1889 --- /dev/null +++ b/bc-sharp-crypto/src/x509/store/NoSuchStoreException.cs @@ -0,0 +1,28 @@ +using System; + +namespace Org.BouncyCastle.X509.Store +{ +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT || PORTABLE) + [Serializable] +#endif + public class NoSuchStoreException + : X509StoreException + { + public NoSuchStoreException() + { + } + + public NoSuchStoreException( + string message) + : base(message) + { + } + + public NoSuchStoreException( + string message, + Exception e) + : base(message, e) + { + } + } +} diff --git a/bc-sharp-crypto/src/x509/store/X509AttrCertStoreSelector.cs b/bc-sharp-crypto/src/x509/store/X509AttrCertStoreSelector.cs new file mode 100644 index 0000000..9f1dc20 --- /dev/null +++ b/bc-sharp-crypto/src/x509/store/X509AttrCertStoreSelector.cs @@ -0,0 +1,376 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities.Collections; +using Org.BouncyCastle.Utilities.Date; +using Org.BouncyCastle.X509.Extension; + +namespace Org.BouncyCastle.X509.Store +{ + /** + * This class is an Selector like implementation to select + * attribute certificates from a given set of criteria. + * + * @see org.bouncycastle.x509.X509AttributeCertificate + * @see org.bouncycastle.x509.X509Store + */ + public class X509AttrCertStoreSelector + : IX509Selector + { + // TODO: name constraints??? + + private IX509AttributeCertificate attributeCert; + private DateTimeObject attributeCertificateValid; + private AttributeCertificateHolder holder; + private AttributeCertificateIssuer issuer; + private BigInteger serialNumber; + private ISet targetNames = new HashSet(); + private ISet targetGroups = new HashSet(); + + public X509AttrCertStoreSelector() + { + } + + private X509AttrCertStoreSelector( + X509AttrCertStoreSelector o) + { + this.attributeCert = o.attributeCert; + this.attributeCertificateValid = o.attributeCertificateValid; + this.holder = o.holder; + this.issuer = o.issuer; + this.serialNumber = o.serialNumber; + this.targetGroups = new HashSet(o.targetGroups); + this.targetNames = new HashSet(o.targetNames); + } + + /// + /// Decides if the given attribute certificate should be selected. + /// + /// The attribute certificate to be checked. + /// true if the object matches this selector. + public bool Match( + object obj) + { + if (obj == null) + throw new ArgumentNullException("obj"); + + IX509AttributeCertificate attrCert = obj as IX509AttributeCertificate; + + if (attrCert == null) + return false; + + if (this.attributeCert != null && !this.attributeCert.Equals(attrCert)) + return false; + + if (serialNumber != null && !attrCert.SerialNumber.Equals(serialNumber)) + return false; + + if (holder != null && !attrCert.Holder.Equals(holder)) + return false; + + if (issuer != null && !attrCert.Issuer.Equals(issuer)) + return false; + + if (attributeCertificateValid != null && !attrCert.IsValid(attributeCertificateValid.Value)) + return false; + + if (targetNames.Count > 0 || targetGroups.Count > 0) + { + Asn1OctetString targetInfoExt = attrCert.GetExtensionValue( + X509Extensions.TargetInformation); + + if (targetInfoExt != null) + { + TargetInformation targetinfo; + try + { + targetinfo = TargetInformation.GetInstance( + X509ExtensionUtilities.FromExtensionValue(targetInfoExt)); + } + catch (Exception) + { + return false; + } + + Targets[] targetss = targetinfo.GetTargetsObjects(); + + if (targetNames.Count > 0) + { + bool found = false; + + for (int i = 0; i < targetss.Length && !found; i++) + { + Target[] targets = targetss[i].GetTargets(); + + for (int j = 0; j < targets.Length; j++) + { + GeneralName targetName = targets[j].TargetName; + + if (targetName != null && targetNames.Contains(targetName)) + { + found = true; + break; + } + } + } + if (!found) + { + return false; + } + } + + if (targetGroups.Count > 0) + { + bool found = false; + + for (int i = 0; i < targetss.Length && !found; i++) + { + Target[] targets = targetss[i].GetTargets(); + + for (int j = 0; j < targets.Length; j++) + { + GeneralName targetGroup = targets[j].TargetGroup; + + if (targetGroup != null && targetGroups.Contains(targetGroup)) + { + found = true; + break; + } + } + } + + if (!found) + { + return false; + } + } + } + } + + return true; + } + + public object Clone() + { + return new X509AttrCertStoreSelector(this); + } + + /// The attribute certificate which must be matched. + /// If null is given, any will do. + public IX509AttributeCertificate AttributeCert + { + get { return attributeCert; } + set { this.attributeCert = value; } + } + + [Obsolete("Use AttributeCertificateValid instead")] + public DateTimeObject AttribueCertificateValid + { + get { return attributeCertificateValid; } + set { this.attributeCertificateValid = value; } + } + + /// The criteria for validity + /// If null is given any will do. + public DateTimeObject AttributeCertificateValid + { + get { return attributeCertificateValid; } + set { this.attributeCertificateValid = value; } + } + + /// The holder. + /// If null is given any will do. + public AttributeCertificateHolder Holder + { + get { return holder; } + set { this.holder = value; } + } + + /// The issuer. + /// If null is given any will do. + public AttributeCertificateIssuer Issuer + { + get { return issuer; } + set { this.issuer = value; } + } + + /// The serial number. + /// If null is given any will do. + public BigInteger SerialNumber + { + get { return serialNumber; } + set { this.serialNumber = value; } + } + + /** + * Adds a target name criterion for the attribute certificate to the target + * information extension criteria. The X509AttributeCertificate + * must contain at least one of the specified target names. + *

    + * Each attribute certificate may contain a target information extension + * limiting the servers where this attribute certificate can be used. If + * this extension is not present, the attribute certificate is not targeted + * and may be accepted by any server. + *

    + * + * @param name The name as a GeneralName (not null) + */ + public void AddTargetName( + GeneralName name) + { + targetNames.Add(name); + } + + /** + * Adds a target name criterion for the attribute certificate to the target + * information extension criteria. The X509AttributeCertificate + * must contain at least one of the specified target names. + *

    + * Each attribute certificate may contain a target information extension + * limiting the servers where this attribute certificate can be used. If + * this extension is not present, the attribute certificate is not targeted + * and may be accepted by any server. + *

    + * + * @param name a byte array containing the name in ASN.1 DER encoded form of a GeneralName + * @throws IOException if a parsing error occurs. + */ + public void AddTargetName( + byte[] name) + { + AddTargetName(GeneralName.GetInstance(Asn1Object.FromByteArray(name))); + } + + /** + * Adds a collection with target names criteria. If null is + * given any will do. + *

    + * The collection consists of either GeneralName objects or byte[] arrays representing + * DER encoded GeneralName structures. + *

    + * + * @param names A collection of target names. + * @throws IOException if a parsing error occurs. + * @see #AddTargetName(byte[]) + * @see #AddTargetName(GeneralName) + */ + public void SetTargetNames( + IEnumerable names) + { + targetNames = ExtractGeneralNames(names); + } + + /** + * Gets the target names. The collection consists of Lists + * made up of an Integer in the first entry and a DER encoded + * byte array or a String in the second entry. + *

    The returned collection is immutable.

    + * + * @return The collection of target names + * @see #setTargetNames(Collection) + */ + public IEnumerable GetTargetNames() + { + return new EnumerableProxy(targetNames); + } + + /** + * Adds a target group criterion for the attribute certificate to the target + * information extension criteria. The X509AttributeCertificate + * must contain at least one of the specified target groups. + *

    + * Each attribute certificate may contain a target information extension + * limiting the servers where this attribute certificate can be used. If + * this extension is not present, the attribute certificate is not targeted + * and may be accepted by any server. + *

    + * + * @param group The group as GeneralName form (not null) + */ + public void AddTargetGroup( + GeneralName group) + { + targetGroups.Add(group); + } + + /** + * Adds a target group criterion for the attribute certificate to the target + * information extension criteria. The X509AttributeCertificate + * must contain at least one of the specified target groups. + *

    + * Each attribute certificate may contain a target information extension + * limiting the servers where this attribute certificate can be used. If + * this extension is not present, the attribute certificate is not targeted + * and may be accepted by any server. + *

    + * + * @param name a byte array containing the group in ASN.1 DER encoded form of a GeneralName + * @throws IOException if a parsing error occurs. + */ + public void AddTargetGroup( + byte[] name) + { + AddTargetGroup(GeneralName.GetInstance(Asn1Object.FromByteArray(name))); + } + + /** + * Adds a collection with target groups criteria. If null is + * given any will do. + *

    + * The collection consists of GeneralName objects or byte[] + * representing DER encoded GeneralNames. + *

    + * + * @param names A collection of target groups. + * @throws IOException if a parsing error occurs. + * @see #AddTargetGroup(byte[]) + * @see #AddTargetGroup(GeneralName) + */ + public void SetTargetGroups( + IEnumerable names) + { + targetGroups = ExtractGeneralNames(names); + } + + /** + * Gets the target groups. The collection consists of Lists + * made up of an Integer in the first entry and a DER encoded + * byte array or a String in the second entry. + *

    The returned collection is immutable.

    + * + * @return The collection of target groups. + * @see #setTargetGroups(Collection) + */ + public IEnumerable GetTargetGroups() + { + return new EnumerableProxy(targetGroups); + } + + private ISet ExtractGeneralNames( + IEnumerable names) + { + ISet result = new HashSet(); + + if (names != null) + { + foreach (object o in names) + { + if (o is GeneralName) + { + result.Add(o); + } + else + { + result.Add(GeneralName.GetInstance(Asn1Object.FromByteArray((byte[]) o))); + } + } + } + + return result; + } + } +} diff --git a/bc-sharp-crypto/src/x509/store/X509CertPairStoreSelector.cs b/bc-sharp-crypto/src/x509/store/X509CertPairStoreSelector.cs new file mode 100644 index 0000000..2796971 --- /dev/null +++ b/bc-sharp-crypto/src/x509/store/X509CertPairStoreSelector.cs @@ -0,0 +1,92 @@ +using System; + +namespace Org.BouncyCastle.X509.Store +{ + /// + /// This class is an IX509Selector implementation to select + /// certificate pairs, which are e.g. used for cross certificates. The set of + /// criteria is given from two X509CertStoreSelector objects, + /// each of which, if present, must match the respective component of a pair. + /// + public class X509CertPairStoreSelector + : IX509Selector + { + private static X509CertStoreSelector CloneSelector( + X509CertStoreSelector s) + { + return s == null ? null : (X509CertStoreSelector) s.Clone(); + } + + private X509CertificatePair certPair; + private X509CertStoreSelector forwardSelector; + private X509CertStoreSelector reverseSelector; + + public X509CertPairStoreSelector() + { + } + + private X509CertPairStoreSelector( + X509CertPairStoreSelector o) + { + this.certPair = o.CertPair; + this.forwardSelector = o.ForwardSelector; + this.reverseSelector = o.ReverseSelector; + } + + /// The certificate pair which is used for testing on equality. + public X509CertificatePair CertPair + { + get { return certPair; } + set { this.certPair = value; } + } + + /// The certificate selector for the forward part. + public X509CertStoreSelector ForwardSelector + { + get { return CloneSelector(forwardSelector); } + set { this.forwardSelector = CloneSelector(value); } + } + + /// The certificate selector for the reverse part. + public X509CertStoreSelector ReverseSelector + { + get { return CloneSelector(reverseSelector); } + set { this.reverseSelector = CloneSelector(value); } + } + + /// + /// Decides if the given certificate pair should be selected. If + /// obj is not a X509CertificatePair, this method + /// returns false. + /// + /// The X509CertificatePair to be tested. + /// true if the object matches this selector. + public bool Match( + object obj) + { + if (obj == null) + throw new ArgumentNullException("obj"); + + X509CertificatePair pair = obj as X509CertificatePair; + + if (pair == null) + return false; + + if (certPair != null && !certPair.Equals(pair)) + return false; + + if (forwardSelector != null && !forwardSelector.Match(pair.Forward)) + return false; + + if (reverseSelector != null && !reverseSelector.Match(pair.Reverse)) + return false; + + return true; + } + + public object Clone() + { + return new X509CertPairStoreSelector(this); + } + } +} diff --git a/bc-sharp-crypto/src/x509/store/X509CertStoreSelector.cs b/bc-sharp-crypto/src/x509/store/X509CertStoreSelector.cs new file mode 100644 index 0000000..3874edf --- /dev/null +++ b/bc-sharp-crypto/src/x509/store/X509CertStoreSelector.cs @@ -0,0 +1,337 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; +using Org.BouncyCastle.Utilities.Date; +using Org.BouncyCastle.X509.Extension; + +namespace Org.BouncyCastle.X509.Store +{ + public class X509CertStoreSelector + : IX509Selector + { + // TODO Missing criteria? + + private byte[] authorityKeyIdentifier; + private int basicConstraints = -1; + private X509Certificate certificate; + private DateTimeObject certificateValid; + private ISet extendedKeyUsage; + private X509Name issuer; + private bool[] keyUsage; + private ISet policy; + private DateTimeObject privateKeyValid; + private BigInteger serialNumber; + private X509Name subject; + private byte[] subjectKeyIdentifier; + private SubjectPublicKeyInfo subjectPublicKey; + private DerObjectIdentifier subjectPublicKeyAlgID; + + public X509CertStoreSelector() + { + } + + public X509CertStoreSelector( + X509CertStoreSelector o) + { + this.authorityKeyIdentifier = o.AuthorityKeyIdentifier; + this.basicConstraints = o.BasicConstraints; + this.certificate = o.Certificate; + this.certificateValid = o.CertificateValid; + this.extendedKeyUsage = o.ExtendedKeyUsage; + this.issuer = o.Issuer; + this.keyUsage = o.KeyUsage; + this.policy = o.Policy; + this.privateKeyValid = o.PrivateKeyValid; + this.serialNumber = o.SerialNumber; + this.subject = o.Subject; + this.subjectKeyIdentifier = o.SubjectKeyIdentifier; + this.subjectPublicKey = o.SubjectPublicKey; + this.subjectPublicKeyAlgID = o.SubjectPublicKeyAlgID; + } + + public virtual object Clone() + { + return new X509CertStoreSelector(this); + } + + public byte[] AuthorityKeyIdentifier + { + get { return Arrays.Clone(authorityKeyIdentifier); } + set { authorityKeyIdentifier = Arrays.Clone(value); } + } + + public int BasicConstraints + { + get { return basicConstraints; } + set + { + if (value < -2) + throw new ArgumentException("value can't be less than -2", "value"); + + basicConstraints = value; + } + } + + public X509Certificate Certificate + { + get { return certificate; } + set { this.certificate = value; } + } + + public DateTimeObject CertificateValid + { + get { return certificateValid; } + set { certificateValid = value; } + } + + public ISet ExtendedKeyUsage + { + get { return CopySet(extendedKeyUsage); } + set { extendedKeyUsage = CopySet(value); } + } + + public X509Name Issuer + { + get { return issuer; } + set { issuer = value; } + } + + [Obsolete("Avoid working with X509Name objects in string form")] + public string IssuerAsString + { + get { return issuer != null ? issuer.ToString() : null; } + } + + public bool[] KeyUsage + { + get { return CopyBoolArray(keyUsage); } + set { keyUsage = CopyBoolArray(value); } + } + + /// + /// An ISet of DerObjectIdentifier objects. + /// + public ISet Policy + { + get { return CopySet(policy); } + set { policy = CopySet(value); } + } + + public DateTimeObject PrivateKeyValid + { + get { return privateKeyValid; } + set { privateKeyValid = value; } + } + + public BigInteger SerialNumber + { + get { return serialNumber; } + set { serialNumber = value; } + } + + public X509Name Subject + { + get { return subject; } + set { subject = value; } + } + + public string SubjectAsString + { + get { return subject != null ? subject.ToString() : null; } + } + + public byte[] SubjectKeyIdentifier + { + get { return Arrays.Clone(subjectKeyIdentifier); } + set { subjectKeyIdentifier = Arrays.Clone(value); } + } + + public SubjectPublicKeyInfo SubjectPublicKey + { + get { return subjectPublicKey; } + set { subjectPublicKey = value; } + } + + public DerObjectIdentifier SubjectPublicKeyAlgID + { + get { return subjectPublicKeyAlgID; } + set { subjectPublicKeyAlgID = value; } + } + + public virtual bool Match( + object obj) + { + X509Certificate c = obj as X509Certificate; + + if (c == null) + return false; + + if (!MatchExtension(authorityKeyIdentifier, c, X509Extensions.AuthorityKeyIdentifier)) + return false; + + if (basicConstraints != -1) + { + int bc = c.GetBasicConstraints(); + + if (basicConstraints == -2) + { + if (bc != -1) + return false; + } + else + { + if (bc < basicConstraints) + return false; + } + } + + if (certificate != null && !certificate.Equals(c)) + return false; + + if (certificateValid != null && !c.IsValid(certificateValid.Value)) + return false; + + if (extendedKeyUsage != null) + { + IList eku = c.GetExtendedKeyUsage(); + + // Note: if no extended key usage set, all key purposes are implicitly allowed + + if (eku != null) + { + foreach (DerObjectIdentifier oid in extendedKeyUsage) + { + if (!eku.Contains(oid.Id)) + return false; + } + } + } + + if (issuer != null && !issuer.Equivalent(c.IssuerDN, true)) + return false; + + if (keyUsage != null) + { + bool[] ku = c.GetKeyUsage(); + + // Note: if no key usage set, all key purposes are implicitly allowed + + if (ku != null) + { + for (int i = 0; i < 9; ++i) + { + if (keyUsage[i] && !ku[i]) + return false; + } + } + } + + if (policy != null) + { + Asn1OctetString extVal = c.GetExtensionValue(X509Extensions.CertificatePolicies); + if (extVal == null) + return false; + + Asn1Sequence certPolicies = Asn1Sequence.GetInstance( + X509ExtensionUtilities.FromExtensionValue(extVal)); + + if (policy.Count < 1 && certPolicies.Count < 1) + return false; + + bool found = false; + foreach (PolicyInformation pi in certPolicies) + { + if (policy.Contains(pi.PolicyIdentifier)) + { + found = true; + break; + } + } + + if (!found) + return false; + } + + if (privateKeyValid != null) + { + Asn1OctetString extVal = c.GetExtensionValue(X509Extensions.PrivateKeyUsagePeriod); + if (extVal == null) + return false; + + PrivateKeyUsagePeriod pkup = PrivateKeyUsagePeriod.GetInstance( + X509ExtensionUtilities.FromExtensionValue(extVal)); + + DateTime dt = privateKeyValid.Value; + DateTime notAfter = pkup.NotAfter.ToDateTime(); + DateTime notBefore = pkup.NotBefore.ToDateTime(); + + if (dt.CompareTo(notAfter) > 0 || dt.CompareTo(notBefore) < 0) + return false; + } + + if (serialNumber != null && !serialNumber.Equals(c.SerialNumber)) + return false; + + if (subject != null && !subject.Equivalent(c.SubjectDN, true)) + return false; + + if (!MatchExtension(subjectKeyIdentifier, c, X509Extensions.SubjectKeyIdentifier)) + return false; + + if (subjectPublicKey != null && !subjectPublicKey.Equals(GetSubjectPublicKey(c))) + return false; + + if (subjectPublicKeyAlgID != null + && !subjectPublicKeyAlgID.Equals(GetSubjectPublicKey(c).AlgorithmID)) + return false; + + return true; + } + + internal static bool IssuersMatch( + X509Name a, + X509Name b) + { + return a == null ? b == null : a.Equivalent(b, true); + } + + private static bool[] CopyBoolArray( + bool[] b) + { + return b == null ? null : (bool[]) b.Clone(); + } + + private static ISet CopySet( + ISet s) + { + return s == null ? null : new HashSet(s); + } + + private static SubjectPublicKeyInfo GetSubjectPublicKey( + X509Certificate c) + { + return SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(c.GetPublicKey()); + } + + private static bool MatchExtension( + byte[] b, + X509Certificate c, + DerObjectIdentifier oid) + { + if (b == null) + return true; + + Asn1OctetString extVal = c.GetExtensionValue(oid); + + if (extVal == null) + return false; + + return Arrays.AreEqual(b, extVal.GetOctets()); + } + } +} diff --git a/bc-sharp-crypto/src/x509/store/X509CollectionStore.cs b/bc-sharp-crypto/src/x509/store/X509CollectionStore.cs new file mode 100644 index 0000000..9217314 --- /dev/null +++ b/bc-sharp-crypto/src/x509/store/X509CollectionStore.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.X509.Store +{ + /** + * A simple collection backed store. + */ + internal class X509CollectionStore + : IX509Store + { + private ICollection _local; + + /** + * Basic constructor. + * + * @param collection - initial contents for the store, this is copied. + */ + internal X509CollectionStore( + ICollection collection) + { + _local = Platform.CreateArrayList(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 ICollection GetMatches( + IX509Selector selector) + { + if (selector == null) + { + return Platform.CreateArrayList(_local); + } + + IList result = Platform.CreateArrayList(); + foreach (object obj in _local) + { + if (selector.Match(obj)) + result.Add(obj); + } + + return result; + } + } +} diff --git a/bc-sharp-crypto/src/x509/store/X509CollectionStoreParameters.cs b/bc-sharp-crypto/src/x509/store/X509CollectionStoreParameters.cs new file mode 100644 index 0000000..7fd047a --- /dev/null +++ b/bc-sharp-crypto/src/x509/store/X509CollectionStoreParameters.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections; +using System.Text; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.X509.Store +{ + /// This class contains a collection for collection based X509Stores. + public class X509CollectionStoreParameters + : IX509StoreParameters + { + private readonly IList collection; + + /// + /// Constructor. + ///

    + /// The collection is copied. + ///

    + ///
    + /// The collection containing X.509 object types. + /// If collection is null. + public X509CollectionStoreParameters( + ICollection collection) + { + if (collection == null) + throw new ArgumentNullException("collection"); + + this.collection = Platform.CreateArrayList(collection); + } + + // TODO Do we need to be able to Clone() these, and should it really be shallow? +// /** +// * Returns a shallow clone. The returned contents are not copied, so adding +// * or removing objects will effect this. +// * +// * @return a shallow clone. +// */ +// public object Clone() +// { +// return new X509CollectionStoreParameters(collection); +// } + + /// Returns a copy of the ICollection. + public ICollection GetCollection() + { + return Platform.CreateArrayList(collection); + } + + /// Returns a formatted string describing the parameters. + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.Append("X509CollectionStoreParameters: [\n"); + sb.Append(" collection: " + collection + "\n"); + sb.Append("]"); + return sb.ToString(); + } + } +} diff --git a/bc-sharp-crypto/src/x509/store/X509CrlStoreSelector.cs b/bc-sharp-crypto/src/x509/store/X509CrlStoreSelector.cs new file mode 100644 index 0000000..c4b0062 --- /dev/null +++ b/bc-sharp-crypto/src/x509/store/X509CrlStoreSelector.cs @@ -0,0 +1,283 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Date; +using Org.BouncyCastle.X509; +using Org.BouncyCastle.X509.Extension; + +namespace Org.BouncyCastle.X509.Store +{ + public class X509CrlStoreSelector + : IX509Selector + { + // TODO Missing criteria? + + private X509Certificate certificateChecking; + private DateTimeObject dateAndTime; + private ICollection issuers; + private BigInteger maxCrlNumber; + private BigInteger minCrlNumber; + + private IX509AttributeCertificate attrCertChecking; + private bool completeCrlEnabled; + private bool deltaCrlIndicatorEnabled; + private byte[] issuingDistributionPoint; + private bool issuingDistributionPointEnabled; + private BigInteger maxBaseCrlNumber; + + public X509CrlStoreSelector() + { + } + + public X509CrlStoreSelector( + X509CrlStoreSelector o) + { + this.certificateChecking = o.CertificateChecking; + this.dateAndTime = o.DateAndTime; + this.issuers = o.Issuers; + this.maxCrlNumber = o.MaxCrlNumber; + this.minCrlNumber = o.MinCrlNumber; + + this.deltaCrlIndicatorEnabled = o.DeltaCrlIndicatorEnabled; + this.completeCrlEnabled = o.CompleteCrlEnabled; + this.maxBaseCrlNumber = o.MaxBaseCrlNumber; + this.attrCertChecking = o.AttrCertChecking; + this.issuingDistributionPointEnabled = o.IssuingDistributionPointEnabled; + this.issuingDistributionPoint = o.IssuingDistributionPoint; + } + + public virtual object Clone() + { + return new X509CrlStoreSelector(this); + } + + public X509Certificate CertificateChecking + { + get { return certificateChecking; } + set { certificateChecking = value; } + } + + public DateTimeObject DateAndTime + { + get { return dateAndTime; } + set { dateAndTime = value; } + } + + /// + /// An ICollection of X509Name objects + /// + public ICollection Issuers + { + get { return Platform.CreateArrayList(issuers); } + set { issuers = Platform.CreateArrayList(value); } + } + + public BigInteger MaxCrlNumber + { + get { return maxCrlNumber; } + set { maxCrlNumber = value; } + } + + public BigInteger MinCrlNumber + { + get { return minCrlNumber; } + set { minCrlNumber = value; } + } + + /** + * The attribute certificate being checked. This is not a criterion. + * Rather, it is optional information that may help a {@link X509Store} find + * CRLs that would be relevant when checking revocation for the specified + * attribute certificate. If null is specified, then no such + * optional information is provided. + * + * @param attrCert the IX509AttributeCertificate being checked (or + * null) + * @see #getAttrCertificateChecking() + */ + public IX509AttributeCertificate AttrCertChecking + { + get { return attrCertChecking; } + set { this.attrCertChecking = value; } + } + + /** + * If true only complete CRLs are returned. Defaults to + * false. + * + * @return true if only complete CRLs are returned. + */ + public bool CompleteCrlEnabled + { + get { return completeCrlEnabled; } + set { this.completeCrlEnabled = value; } + } + + /** + * Returns if this selector must match CRLs with the delta CRL indicator + * extension set. Defaults to false. + * + * @return Returns true if only CRLs with the delta CRL + * indicator extension are selected. + */ + public bool DeltaCrlIndicatorEnabled + { + get { return deltaCrlIndicatorEnabled; } + set { this.deltaCrlIndicatorEnabled = value; } + } + + /** + * The issuing distribution point. + *

    + * The issuing distribution point extension is a CRL extension which + * identifies the scope and the distribution point of a CRL. The scope + * contains among others information about revocation reasons contained in + * the CRL. Delta CRLs and complete CRLs must have matching issuing + * distribution points.

    + *

    + * The byte array is cloned to protect against subsequent modifications.

    + *

    + * You must also enable or disable this criteria with + * {@link #setIssuingDistributionPointEnabled(bool)}.

    + * + * @param issuingDistributionPoint The issuing distribution point to set. + * This is the DER encoded OCTET STRING extension value. + * @see #getIssuingDistributionPoint() + */ + public byte[] IssuingDistributionPoint + { + get { return Arrays.Clone(issuingDistributionPoint); } + set { this.issuingDistributionPoint = Arrays.Clone(value); } + } + + /** + * Whether the issuing distribution point criteria should be applied. + * Defaults to false. + *

    + * You may also set the issuing distribution point criteria if not a missing + * issuing distribution point should be assumed.

    + * + * @return Returns if the issuing distribution point check is enabled. + */ + public bool IssuingDistributionPointEnabled + { + get { return issuingDistributionPointEnabled; } + set { this.issuingDistributionPointEnabled = value; } + } + + /** + * The maximum base CRL number. Defaults to null. + * + * @return Returns the maximum base CRL number. + * @see #setMaxBaseCRLNumber(BigInteger) + */ + public BigInteger MaxBaseCrlNumber + { + get { return maxBaseCrlNumber; } + set { this.maxBaseCrlNumber = value; } + } + + public virtual bool Match( + object obj) + { + X509Crl c = obj as X509Crl; + + if (c == null) + return false; + + if (dateAndTime != null) + { + DateTime dt = dateAndTime.Value; + DateTime tu = c.ThisUpdate; + DateTimeObject nu = c.NextUpdate; + + if (dt.CompareTo(tu) < 0 || nu == null || dt.CompareTo(nu.Value) >= 0) + return false; + } + + if (issuers != null) + { + X509Name i = c.IssuerDN; + + bool found = false; + + foreach (X509Name issuer in issuers) + { + if (issuer.Equivalent(i, true)) + { + found = true; + break; + } + } + + if (!found) + return false; + } + + if (maxCrlNumber != null || minCrlNumber != null) + { + Asn1OctetString extVal = c.GetExtensionValue(X509Extensions.CrlNumber); + if (extVal == null) + return false; + + BigInteger cn = CrlNumber.GetInstance( + X509ExtensionUtilities.FromExtensionValue(extVal)).PositiveValue; + + if (maxCrlNumber != null && cn.CompareTo(maxCrlNumber) > 0) + return false; + + if (minCrlNumber != null && cn.CompareTo(minCrlNumber) < 0) + return false; + } + + DerInteger dci = null; + try + { + Asn1OctetString bytes = c.GetExtensionValue(X509Extensions.DeltaCrlIndicator); + if (bytes != null) + { + dci = DerInteger.GetInstance(X509ExtensionUtilities.FromExtensionValue(bytes)); + } + } + catch (Exception) + { + return false; + } + + if (dci == null) + { + if (DeltaCrlIndicatorEnabled) + return false; + } + else + { + if (CompleteCrlEnabled) + return false; + + if (maxBaseCrlNumber != null && dci.PositiveValue.CompareTo(maxBaseCrlNumber) > 0) + return false; + } + + if (issuingDistributionPointEnabled) + { + Asn1OctetString idp = c.GetExtensionValue(X509Extensions.IssuingDistributionPoint); + if (issuingDistributionPoint == null) + { + if (idp != null) + return false; + } + else + { + if (!Arrays.AreEqual(idp.GetOctets(), issuingDistributionPoint)) + return false; + } + } + + return true; + } + } +} diff --git a/bc-sharp-crypto/src/x509/store/X509StoreException.cs b/bc-sharp-crypto/src/x509/store/X509StoreException.cs new file mode 100644 index 0000000..ea7e51e --- /dev/null +++ b/bc-sharp-crypto/src/x509/store/X509StoreException.cs @@ -0,0 +1,28 @@ +using System; + +namespace Org.BouncyCastle.X509.Store +{ +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT || PORTABLE) + [Serializable] +#endif + public class X509StoreException + : Exception + { + public X509StoreException() + { + } + + public X509StoreException( + string message) + : base(message) + { + } + + public X509StoreException( + string message, + Exception e) + : base(message, e) + { + } + } +} diff --git a/bc-sharp-crypto/src/x509/store/X509StoreFactory.cs b/bc-sharp-crypto/src/x509/store/X509StoreFactory.cs new file mode 100644 index 0000000..96f22be --- /dev/null +++ b/bc-sharp-crypto/src/x509/store/X509StoreFactory.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.X509.Store +{ + public sealed class X509StoreFactory + { + private X509StoreFactory() + { + } + + public static IX509Store Create( + string type, + IX509StoreParameters parameters) + { + if (type == null) + throw new ArgumentNullException("type"); + + string[] parts = Platform.ToUpperInvariant(type).Split('/'); + + if (parts.Length < 2) + throw new ArgumentException("type"); + + if (parts[1] != "COLLECTION") + throw new NoSuchStoreException("X.509 store type '" + type + "' not available."); + + X509CollectionStoreParameters p = (X509CollectionStoreParameters) parameters; + ICollection coll = p.GetCollection(); + + switch (parts[0]) + { + case "ATTRIBUTECERTIFICATE": + checkCorrectType(coll, typeof(IX509AttributeCertificate)); + break; + case "CERTIFICATE": + checkCorrectType(coll, typeof(X509Certificate)); + break; + case "CERTIFICATEPAIR": + checkCorrectType(coll, typeof(X509CertificatePair)); + break; + case "CRL": + checkCorrectType(coll, typeof(X509Crl)); + break; + default: + throw new NoSuchStoreException("X.509 store type '" + type + "' not available."); + } + + return new X509CollectionStore(coll); + } + + private static void checkCorrectType(ICollection coll, Type t) + { + foreach (object o in coll) + { + if (!t.IsInstanceOfType(o)) + throw new InvalidCastException("Can't cast object to type: " + t.FullName); + } + } + } +} diff --git a/bin/x64/Debug/ufr-signer.exe b/bin/x64/Debug/ufr-signer.exe new file mode 100644 index 0000000000000000000000000000000000000000..a60e67897b6a08ea1acbe84bfd395d6cd714e3db GIT binary patch literal 2455552 zcmeFad7NBDxi^01bkFH^CezcC?wQPFx&uk*q-T;4wh06X`yvQ}LK0*L1yl~6P7pKB z5O4zpWM9G}f-ADAsHj{8T&~+yRNP@i~wa@lC59I!7 z!Gm2#{L_M0o^^JseqP|eHaPVS_0vy1_gsHN{j@Xd!NzmzXP;X?@TgRjCZ)sqTJWlg|$8B?BEpK6^?V>BFFJUD4ul*uj%50VOQy9G(vkAc>w1> z*E!|wQA^ec2tR!qsE6?g5(5~zeUbA}cM@dBUcF+WGhzc?YPjw=hgx}-Z8-CS4Tz8U zpdV?{cGHd!4d7Fj1+Cz80E9NsC=CzrwEnG$8f^i$5hM++)9t+ajb-OY5A`|ZE&9gW zb=g9v(N_O9iV*O(+{zT>D2)e1i4QQ34|Gi09hfTe(HM4|JnC!INcYn6inMcXSg7mz z;AcJn0p~U5Bjk_r2bg)kj-SQ?hIYV@<4%@Q+Ni&yMu42Igpql6WG_3icN|%|yAxzs z4_caE3_4QI9-sqNmz>lUXLql?{Cvdnl|r-Vc$xGqPNtZxt}SHF?LKE~x+gdO+mzSM ze#$#uIo)(h>7~vv+HxfLh5sd|wHRr6FE|->_&W(+@I!_h3=b4Me+dHBtzE_Js$nv@ zg~oY3Ie)2w%jr^jRTmQ+&u;>jBMZFRKx+(Y?``bH*zSyFN}0;m zba_r`&e#ekcsB~LkuTc}9tDp8+5q$N$qK^4Gukh9J``)Ho1O9XO}XgCv0__U6pcQsc-xTvv7+}sR-OGa4TC=#4D9b<3DjH2O&MBLSr>s`fc*_ zd-(AoQ;|#_L5$+IF`Sv{bkbe;p!k+oqHX3Gb z9Y|2Vly4kFgf~h?C(L=bo5)!x6~^{P;s(O~+vlN!??MOX6A*TJF@$`OIE3^#6Tc+M zhNQQ5B?>N5pG^2wgyX_I+PI8RowvE6cg3(**JVbOXm`N83$~hlm z3Q>v1!Z74=XU;y9c{kN~_qgLtAsrfIk`MnCOCc_|=NxbT+R+mVHc$ll_X$CUZoV9q zAnyX?t@;N;fD;9M0tNXO@`v4+&v;jyz18i>jrF=-g^_g)5aQ**I?rMq<_szrq-RtS zVOWko=}s<0CEj*eGw~6jC2oh1{9J-$8N3%M(K}c~TP@wd9R6=XH6{oPE^PUZ1yID_ z0dY3;*^ba>2yNuhW-=`%^Z~&A=lEmsi3;VVatf-4oI=-ck5hKlpX^vQG}lY58cK~- zLlM7-s*jF3~ksCJ@Mn@b0x8n$m;yA*TIq5_(OrO)80NLDs;l>-P zq%;2Z#wT;WaIK~h{~y`Hsg%bwtp0)Gw+QZ0tw{|8;-k1meZ6WJ9D3c~_fh^buc7?#8Jq9;0`s%z&-C>&@nE@=Iu zLHO1#Owyo!q2pRmU0_dwinOjmWVoU7C|_;P)^s5=MrA`L7~92xCE`w!;t*hEDP|7> zDw`#LRXq8JK92sdns)dHy&`{D-8%dW@b`B7-Dv(kgeU(#W&M2_Vg5aC{+`D3&*m=; zjl#bH^EXjLz@4;K@)JD~WKb8B6KV<*XP$xl2E}Qv5d*=J+dz!3V1gtlPOV^&J|2U5 z4CuWvXpRA07J;e?X-;W5LneBzMu$!GWR1=@(W6bJITK%F;&~HaVd6y-ubX(UiT9a! zpNU8EfEg>_$cQYf{{_QU#z6-24d4_3gwhbA?umbgylFuqU4_g+C17{s56atl75=92 z_YwSYEma5455wOE{Jj@{U&r5*_{*Xc2(PmSe`n$E0{l(fj2}yy;M#!X7PDhL3N-GS zi494#TA$#F%`DPe3;F%BB;t_ZaRnQe7|Ov8NyJ*eVqz5w3q#8+DbptY1H@Z24lgTP zu@T9~1wRc_Lv|=y|B%dC8(21AWv$JP^1?!msx6ypHzzZXDuJ{?O)W-9CXP=c)|82| zo(`K5@^P#URW$|I*-`aI)F!&xPz@`&)(%SzdHDnMy5U*a@-Idp5t&+1saFe9*&7Zd z4u6{~%2Dw_g%yRP<2+M~8F0;0?0kknT+fF2!eKOF@`b~qVdtwWX=+<4X|-^;sDJKa zumXDjh5U1ocdn3GbqunocdnQXy>mUeReKT)>k<=dK1&-j^36d+)i*~c-7+LwzPTef zSJ5{|8C&%Ti)eimdFfL@wpS1vic?)Hn^^uE$MEx16F-;Gn0;m^$7_`66+07a+Qt=F z-EzF$^)eV6r4f9h-<+G7$q6YJyLKoaF67W9u_>eG0t=)Y>v| zCQ|6pu*3VHsVrArrBqq1w<)*#vb(41%WU1^>U&Ee8; zJT&L&&>S&C)7UK?ho)WY|HaU(acItWO7pidG?&uXj49Q*8h^A1eucjZ{=$E&5vXEt z9gDwsyr!I$faf`0onX(jb6!4pHGT`NR|8t~GT840S;l`UAgC zt?od%VHCi;I2VwDjQ^8>nD^2?OwGL0It9OljO(Aun3wTS6X@yqEoRfLGw}4!l%%Zx z8hQG!)yG+Q^rR}Sv+-=5gP(Fb2c`QursDZ}K0yojg4Uq!qSXBcK=YpKgBsLLN(x?Z z0b1t!2p2Qhq&N?O4dbBNeN-buNWC_25h3wHjbZa6_n-o`?AV%}`<6^6l1gk!}xwih(o zLC9G3H%b)}X`GL+|E3tABfu=}hh!_yY4$pme30%A=L~0J4+@Gq2?H`s)9fIuUc{Pw zE1rEAZnPRFNzim5|j{eBsDHgVWPQ!RO}#BR85`xAnO5|m3G;3leO_? zV72GxxD4|TYW|69Kmv-`UyGo57BaxjJ&BB+6o?^4_cEQPMGVciZ4$%cIuYX~h~YlT znr*blf6I%f$Z(c({U zpgk~HFzT>Sz>g&Z0dg3-IGLm@9HkW!{Ivwsnp8HP=O zRTNmjl0z0?eL-qXG`mecg!*^O&CdR8dwE5-GlaH7Etj11<&fIpyqoeW>BclTiJjV} zlL7mnD?R6OJke)ZXI9)R&K^9W0cJMyu7qPip#{D|L$@wi0)57Pk!I_UFlMPXs+WV+ z?xIT7Yu$={l-7Yjxx@bxQlXcl#LTQjXz$il!1XW24|Lo`sG;1A-S}?O#{T;po=~7A zr*aMy=7!zTw2FT*n_v(Aw~{jCe;c0lUxWN}PAD|@1WFdXb0_oUZqEM&HVqvwch1_p zmr)Z8q>uHp=tRj}A8Q_cTyeJlb`-p*rvm#2+Hd@W&P*XYmdP3ukMl7a>TRoYx?m@} zkjQhd;&jM=Y3JtmI>YQI5_t>&{yW$vUE^#41{5;7MAHn$JAvU}LbO@n>bh+tgka?Z zjPbPpG`Ik{pBwCoX&~5upGs|w9w}&+gSQcQy8u@x(3$B&g_!>!gh#M;=LvKU<;}%> zf61kK7-tyr_Ik)f+0eZ=t3tc2(+n`_?`GrO$-Tlv+0VWUwKXmU>i8Hov_BQa79;kR zC{{=82T^QZvmX2lV)^Ed0Xh}W;b2srb_Zk23!E6%PEy*TYf~h>By$;M|ORg1)cMLu*&*9*4 zd6t8JmuDdu%!Dm5)+iaP3l=VbxFw1QcZ=}gP7NO1Il<#qc(k6u0;Ze^*aCovuC{p0 z5C72|&@-($(sryX509(#8vlb~Go>QL-%7@RibKhm4VVNY)jtnxS&2WmY@O!G@OaWr ziL067NQ4@-;nb4~-_vck31rgK#QAQWlZu?`QAiSBS z4o^_ksKP><;^1(a^OMOW&PsNg2Ux{%o}8D&rwwFE8ce20Go=ocz!6_DW7B^Z>M*>7 zV@I4VuXS4Q29)bfcdG|I?zJvM+=r%yrAyc#VOJ3L9)(r?E2Bj8g4eo=iB~JkzlKj8 z#m2S$!go2Eeo#@FhDbg9mSz0GlE^LjZqh3H6$=dm)sGs3-pM&LeXA2;l|1nsd zqo99|#@{f))UBL9oTtHzb_mf6w0&-;wxeAfQ^}?u3Wzp+ zt~Q;3sOiN2yEHvodp`O_Z9CVVv`=LYO^v~8RzzJmnWYvVZB_zw_U0jz8_5ywJ$cSw zb|nMw&u!fV=oIHYw}lxwKZRu*f;TgW)ii>)F!(YF-pU{+69a`tK~SI=sbMxq0D`v> z_h<>emqARKVKz+pAp%&2pi~U|6+x+Zj~I7Cf>On*7@-y;jK>JeV(9W1x*~?|97CbL zLvnVBpvD^i5 za0dxW2VkU!*%nGrvO#A8Dy)8m1clY}B`DcOV<^-ma0NOnLCH2ELFs@#2}-#KOYjbo zBWh2pczK-dNC^tPhr~fjzNz9c2?|R%1Wd>7D?zCkb0?ru!kRd^UmTJKEkSTXh2}G)ThmqU=NL)m4Z4RpRt{719KN?5s=clm*c3rq5?`Fn(;-rdyuSUqw#(hkE zQw$jN?~fyfdf~RAM|wdUC5*Sg^J*2P@?&ucmDa~u!6#x!aF<0lOB+&@2V#`^xg`GA zU2Vh@x#E8^#tXj0qA(IE9wf5X6Y^e2Rx+s{xg!!HnMP4pb^udvb^ud{+ThXJF^L{w zeWQuC5)%BvYAXSCI5eHwDTzKIyc+fiBNhKsF}1;8tv0eLRsYkP%E(A1!Dns?(JLtIwL8bp0 zB~=PoQ`<8^D}hPzkb>4)v=UICLzYz%c+MEQ-2v5opA;EX8$|URUkjmPML}i4}3*|_1{mJTK0;>LJRfHMAs>kEP=5rM7 zhZIh}4f_8ON2>nk2O__+N-Cs-0fzOK^pB1s4DsTpW7LgIMK^Z0cOz%w#9T-~G#AP|t;1&zz(dKmz+zySNyY+;O<{rCr%0j( zM;GH>A|N>-60(g6kxA8XLX@J;Vo`?Eq%_t5LboGxz2umt-LV^b^G6<;c)Fzn% zEQLf!o^M(8k^wQp7)xk$0=BQwrGVW+E6C_V0;~SRI#i{MCoDQO^^rKWKE&y6zXb23 z-2RYmidsyj2%h%qI1<8swZO|dgO_&(ujmZkxiffWXYeka!Mk<_@75W-duOni?`B9@ zt*m#(=Gv?SPZqX;=eL=2EI2u*5EA^!>Nx^L(CfqPhANYyX+??(EjlS>q$eJv?6XML z8L89jTmJ(TWJ2VbloWYFf3d>M0uuAtzw_LQahO2#I{A zok%7nxhBP(VG&6{v}-Iyy&I*pnRkTO2124Ks1b`8Q5Jo>y>=!gSwu*%$*P@zT7Mgh z1Q=-r$#bQ}A`XanxG|Ps7y;X77%`M#-)JXU%1%ytQr`6z%>+aold^V6&El986ln+! z)`(+_)c=H<>o>Mn&!i;B2np`CYA2w!u#IB^oD=qsTkVv^o!H-$QYwAW-t{O3#mXW&fF{x<@ul0p3qZ*6&$VmNhu&{Pgf+)h1 zOe7@u7mE%8YJF`?6kv~CRq$7fiPV)b6B$b|k$~+pkr?&gklytPdZpmr#0o*QQqXH=%bXSzHh@Yz;uRyxt2K$b2~YBhkYLcNnt)ln5@5luDp0R|-y!PbqA7i(mpGUP(b6cqIiD!v@F{rgQdU=<}Fs|l0y~$Q60pLgfC2u>^IoK)j$mR%aZF+E#qOUj1OCcGBxT!Mg3xoTK_u7 z(EJ4bQb{yrFc!$0T1Fn(d(l{61O&Y)!9D_$1A&m>Cl*zbuhOP81Q=;v$@3?RSCo&0 z=o7Gg(I-Z2Nv)M2kHDm&m4g1uB9DNGX^hl6Oyq>e>Xc#{v4s(^<@iLs1SZ)+NYFng zR!x$xoh<^4bgAT7$~>k&I1mzSAz=G#AqLoTY=S%iCe?wIw9=xFfQT=QbmEH?)Emk_ z2V}$-M!=Un5)~7eV>1Bf%9c!U!8lje1if2P@%>plg#3KLI9rM^JFLMU~*ZrdCRjL}29f zV5I7QDXu5@`Ye)+Ba5i;faD_&$d@%2c^T2o2uR&6!9N0%{39fI&Z1QEy|Il_0wceq zWa;aR`K76bI7UERp`AD`<09f1QR`xd1bu`jM+qUpo~-OHZ6pD+tP27oPD+-w7JXbQ z#>NF>i6KM4_J<5H)VN5xrKR9x&r4w^S@aSRjT$MagK?oq_i77=kRv9SF*Af-!JrvJ z78st%PTb20z4-YVB;nxo9p*?IgbAo?I!{vzcC|` z-pnS$qF@U%RKg1^XfUQjF%J{;8LITGwD&-uZj@*E9b zEl)U+&(lCYc$)?a!HpUy26tlzIPG*Al~ z8mI@W83=viWa2EJxbQr&@*yNR8W}?cA^F<9<7VW?*{^--3ms~4L$AMKwT7d10$og@kCvO z1uLw&2$;2tL0~lFOBQ<j)vLpy^H>kK};4Ln-^DW;5sgprcu z#70PPyTu^_)O&m>bKi|dhKpk+)gsYjESlV;mVZ2-XlGs7D^Ap4nC@0eWuc?p_ z5PZv`MKE${%zOC+A%s``ugAHn{x{-CtrjXNc0w%|p1feJ@lCepO^QrF{oWLcPwplX zo}7^h37)kmldLjSlG~Pq*GBF716zuuj?LYNL|C-~o$D!CKx%r2yxK z9Ahk^QVRI4MWv=1DjDfSC5LcCB_pG?!xDpou;h|~kl-g4r3B16FC)On0VH|sVM!ek zt1-qBYX$?3ObAWYMXqk7y?_QkNl}^luYhi}neq?qf1V`F{}k z-N~bn5UN!RyrwgFzs}(OJA)7C3_h?k_@FlMXzi5=o{+la_#`CASK{$WfUY4fOsF@) zqv0v#gu8T7#3+j}W0m}fbp>Op&;}B;5T2xkkl^(eEt0i^t{^;CXp-&Pw)!Jo!Bhx4 zY=ES$VDCi2&Pe@x4)j^l2SRx8m_?BwPGVuwHWmr5MN2HkqOmAt{MjN;Q_Wap1mq30 zlSe6u$YZ2-TpMi!2JflHJttW0s!V{9Dq}1nObW17nWh@T80kb9l95hSa@a&vGE%?Enbo_52Vb*jBcQfxTh|IO z+Fq4Bzha&pRCf>%2^%Aw$l-8}$YG>ivl5ontb_#rY7s|3t=zW!5n!ZQB~NBBCXV_b zR#}WCG%Eqy*Q`>&f;P=cV5C{4j2_l`moh|CABiLN@ibVU)T~l&q*?b&kja=ay!U4C zLt2yt-lsEoug>7rZQwS)3d>EJpM(SptR@klI`N2vP9!`sJsF8iPZAz2JQ;}=p3)@P ziuu8C-O1~Nx$flaUeUUbXtUOR1O`W1gb7yrx=(=7`kTCpM30g71s-9+$yT8R%v#_{ z&d}D7EN^5MGYH7}sJV@+pti>UR-6nSl;Vu? zZGim81P*0K5Jf8O5&y@E zg=sU!VkAtMV<+&)TvQe#Ay&;!U?fafWG65ZCe-Z&M#2R3sv6=M2@|+zk1EGZnT&*3 zYEYT%jD!iQRZIdSCZW<081B31;)}-)mK?ly>{Y}V0dw-WN@(dxxCH)KK;lc_cLdY< z8ou0Dh8-@aoB8COD?^HGIUT|t%L_3wQ3c_w-GT}G2xNpP;!02s+fSVVt0~cG@g*Q?yy}A<4!VT<4Hl) z174S->JI9Lcu94`N|F#VJPDx$7|2j#MrsMZ4YG|AnindeU&T!^W5{iYHd%U()A$+l zRT@w6(SlC)8b4=n>0-C>3kKr5C@UN{(`!-bfLs^3gA@EGN`4IENv}Ebe~ApOr}0zB zY$l#!7n@F2$6uFRE>M+n)*)R+><*5*nGqS+*w=FgC4GIA?l%5eC_1i%QoP2$AhN+j zotufW^tg~tyd-%%G{Dpegl>tT0PluuX}R#B^?G2m)<E};MQZ+M zKx?yWu3VoH<{9po=Vs=42kP`sLG8`1fhZ4X=XXci>*?|QTbPFzwuR_I7x6pP0sJk* zw~H5i>hJk1Kh@eD2(3f$BhtWf@NVpD@Em1&+J707@QsWEa1YZKT#vUJ@p*C&37C=f z_eQjF8!oU*`5(itT;Aw8)pGaDVrQm%Fj^s+5U~;N3nWPA024i=}Q!jhb@@YR+gHiH*wT72%)Cl)&v zoezV~I_Y#%!YqC#gB+{iqao<0QhLkA$8q%uKl=b8@!D1fWP?16wNhFc!}cUt^VQ7d z%u}rN;C8y^ZYU)im%=NBs!leid3#D8Q#TNwL}yKrXkSz$ZaEnLT(%7HDi!2z+hw46 zfzYgSd@6bo1oF}k-bo;`O;}?BZ7DRW=ddg~Lj+pUW~1#=uC_-xa>QAB~OQ;|`e(d!hCGY>JU$zT5sXXw!wP$1YVHd8iHY zql#3q_`iaN8ykhfpc*pFh#Ry!fOt#1@Z|5%4vSSN88@LW;>+Tv$N7Yb%z}G+vQD+| zavj54x0awVwY~Ucu3v2b^n}GmDgLrG&3Om=s*1(>N3LxDE8F z*Z3*a9>*r-s~fe>z#^XUSpQ5K3c$f_K)g4O|L(;fb;uX+m%|^&EFSW+4?_Fm4|9a` z5XfequR$0_E%c2mcbRoN)nao+Y9rPZ92k195$Y^9hf^B}gpZSgCp$16{vu*r_HPA7V1h|1f|3GBT^L8}@{3w9XV4^WZL3vOX_-0P5 zpm>cLZgt}wlBH}BmB@{usylemqUceoqpxN~%|TV9D^N_UQA|EAhBumwI=DuO#UOXO zl$G>^rpuc|D2k|P++8yMK(35?=}J9nKcpQf7^g1np%Q~5 zy7(Z(p}KmS%aM4oBvLFJg!eS(8K5PgCIy!qg1&GZBRp#yXQP1zJr|B+0x^y`Fg*Dc z&Dv=k6UG1A#_=JbU3C-3@g3Q%mzo{SMCX&IuTX5!3k~9lLR`|20dAz zSG7S;0W{>t2GoT$E$$lr4YW|fX;yJZaF5)Hox%la8Sgv(Z;`MVD)<>Z7v*a`%X+<9 zepwEhme;wlJ96V+hvHp{&V3d*3gnzHem{}KXz(yKcqq@W0|Ox_1DVHvi25<>h4nWV zbHZX18_QkC8}CJoO_Nx&tBMHSe~XqBe4xR((%}Ggfqh`@r4S?}53gl#yAyoNIOu z;KwOJ>S&9iea<-B2rc?{P%T#@H~t-iC-3=x#RC%+3i=%i%C=N8Qf9r{pd~^o*|U3c;};qxA>! z#r1c=JdP^_`+^ey)RgBWW#g5&fmpS{k&e58F??Qb2sd<<)6?T~aSwFbuOWs<9go(l zmO)+6UTVD!N+{>UHZbd`C((ZPW7(T{9cXbkzFkXZuZ^9AvKeQe`H%9ZFq#Mpa1Ah) zyB;2o>EkZRSlOFbVo_3-xaHK>R2 za^Li82b}cu=dsvPGW+DCWVla?pXzFTC2Wt9j4PQ@+#yojyG?PpNqNoa)!G`l;7m+ErQH6Fi19FO1Y;}80H4v*GRkgM{7(t_6B z42(EUT+F4!wDwVmX=Up~M1`6Sdq63a=TGM;DI*qvqoq;YU|h!QeM);P05Q& zOG*N+t}88BchxOUX-Ro$X({ZbB}iEmnp@3MbNsue8jHJ&r~ilCU2GA*Q)#EtBAdI- zN`PA)&w~|@zHVUJ%D4U~YENx|ZU;*(X9e|2TEC){me1nJH_TucSD>`BYdtLKs_U{O z5Vl-Tu9(F&l`>a6h4~;Q_nKmrfcaU!vPgXkt+WexSlzkg>Bkl~o~c@xtC-9f*7XXW zz7oSgr|2y2JoU`C7}o&1+Tc>Qv4;BNVEjy-hS|YwoCCq@;g`=Ex1l#%-B>`tT!h}{ zT>LJy2TK{+J;a8Zf;qFnX&!F?SZAINIKR0$#X#2I7j+ae8(HC4UuFZJ{;L5SH1p)x zu%pn5k`q;mm3AGavJ|b%-yhj@?i{}obEo-yr`Rw%f-bCg;7fzGkWTTAY5H(_tVUSA zl@#x*bg=BAWmpLGjY^SEf#bu@YQ99?H-7u=K0z{vLx*a#t8Qq@p{}lyd z$`|#1EZ~78s(PP10|X)yrfn>ZQ&Wt>zb?G52+y46POf03WH|uitGW$DjFPY$WP;`e z$?g0KJu_nk7*Oona&~4iS>XUcXQf(rNkv>eV%2coprIn`Mj{+iO5^tZJ%EE1W^IM8 zRCv>3kK>lojn`?(3lNr1f;P@pK%acW)30-AWI1n^#BZr{`GwA%J9PD-@8sTa*vkBt zYsqQg%OJ4-;8cP44H*}s`O-gP+Z40o|B?}j+&{RKOoxf%ZDzOAZ}|sIgkVdmaRY>R z>P$i}j-lcaOt;=k{dm)M5Z;$Wpq;EX|6GxYNtWwxp|YbFF0%R4&S7W+#yrP|;M@Wn zj8#gm+?9(}VYA=KWIY`icR1OeTyA;T8U4_M13r!PKBM-!7vkhcc+AR$Yu%J2@@qn`kP9NBo(T$}7$uU61JFfUhfO z+)}2NTbV9pnry#;Xj&rPYkJB|* zWw48P$MZu;>_75NSj;sN{UY50Z~Qx0!V5~nZWsMSdsfotbh-Xpk=XbTpjB4RW&CYX ze5NSAvET?YeH{T%K2pv>yQiD;1$=KEYE4s)R=vrd6SD_OUEN2(sc zjS%KJH<_oGdBBrUz81&r_Fo57Bi51ZueQ=(&!XtZGe}_>@W6z;^WV@`26#MMZ9he3-O>y!gOU>Wbg5NT|M zZ%rFbdbWvYDF(_9^qRBAqDjiT1m)=rN+)St58*;^>?94_zMoZww5uy7e499K5@!Ku z*%b-+JB@PmI$C;%!EPIBpiVsj&9TolPt4D=pR z;kdXo=Z}j%Xx5+t*0KWHDZDm2jVp<%JdK7&GB&L8<3GX&Rl4EcWuzPu9rl*2mP>ZkPTT!Jy}I;)+rv%=3^)SNTk>O}L((V*Yc_ENv`+f97>-vgUr zE@5Qx@_hk4EDPm{10{bdshqoHd5%SMck_H@Y$4M)81_j~SlfeLnwN?F%w3Q}pfEFAz#L^e zG$fW~+|#*iGjyDCSYY%I%3Q24AcpMc{skCLb-nUq*m*IN58JR4Xgo;yCo)RiFtj{1 z(8Q=J6&{5$4i!4EdE@nWHU5*rgzkoULRSr}FxlXccKjM?Q_#R^ zd<22%m`ETA8L#nQB7yEC)6^EG{o+F7@4Ct<%-V-NoX5wG%#4p5&7tKoiL!8L@<}!@vu4MLFEvlF^PzQ{o zVvLJtRd8XPc+ITD`{KlRGLh{wjdFRXy;t;tpM^NtN^PL=R#=WUT8#$$DyKGpt&386 zUE_aHLb=-u=F|<AdyLb2l+CJ)AC$uOoLA}<157$_u`>bDA=)kxntTsK&^B`j zQ%rz%r=qyWxXn!{52thuBEJ)*h^G?KwGdwc#Csw{ ze$?JVOk0Rh?BI~-aPc2{!NO zu|`sQ(tt`&m021a0Y|tY9W*2goTM}OGlk+K7!BCH_{`BlZ>&$fH5+$>5J0+{y&3U# zgmKYzCq;hu)D)^uyXmRd<3V~{S?}xwdP1C({~MV?=@!7+LZhuWgK?46hgo-)P1}7N??y{ZE%ZYEzb9<*YqU~_=fHpJFE!B2=(t7E_F?{}ggy2D-WPQ)+9hZGP&YnAg3WNM(howAL?}6C&jT&5P(BkI-3*~y?=<&DizK@{ z38#(vq8NyLQD(yee8qUr#3dV#6LGZ!+v9Xsx|;rgp562Uekicd_Cqm> z1+*}(GDRMUABy0lAF2Z)4Jy+Gksqp^iwQr}`_TyFhq5aCd-$O~pe4tCD9n+pihihI z=N;$~XyvFJw(o~}C3)J>55@kO<%fDL3RXW9#Q~EO`y2)Zzr%;IA|5fgf%2F#Wj_qI z{69mv;HyfvqBu1vMZcGk=Q!qx{F3(rVSDoMu_&GLa4+&7|F_A*8$mgQ2uc8+!qgO_ zPIJheWFV3NI9W^*KF-$JW5}~CShr+m@F5?p>cnG658*u^6i}|=Ftyhqp~fMIBPd3X zo{p~($LqEwXbYB%+vo@KrL7-;Paa^labP*vKCpm|!EE#-HTrLmbE?HwNn9pNRUTLh zSTI?FPhmG0d=Tf71Tj&6jRj!Ai_eTvsxCz5XVlF!K*>ULOnc@y$DOHurnYqy>G7We zF@t6uC4ip*;Lqh`25W=DfHrDKli#>XU${drru^!B=WtQ`C5-433IMwcmf$<3x1ZYfW5~>NKbP` zP8S+qhE&SuUEE2^Q?~UDpkWn+aq}JZwEO=g8FT6#`RdHvoH|E1%HSt8&JheOSj8Af zx-Q+pS$z`SR~!#1=Mp?mwAw?8uoxvp1vG{f6`Kc`r4K2>hdz~_jM9@%<$R92269I<~37*BA6TggkI&mecdFjU2J(A;gFO0o#s| z3xZuLolYOb2z?MPFVvA;Qm!g-o@4B2FgdNhrAl)_siHCf-Qkw$H>s+xCMpsLq9lI+ zKX*m7Bo2H)l@A(K4wlv_XG74HL-I{$@YMMdgoacM&x`pTFqIvSCU_2L--1FNXtMJVnZtxXAa&Q7aQtnyR?p!U)dH?3H6P`%t2gBiS zYy1Te@T+obAv=rL?dV`eyv$@VyG1=1lu{b56L@G4D>OU%}WmY<4s!(dIG!d9udO zMiwOJ|D+8m2-}z{!+A2aAvwoTrdBJjBx@uoW}*pk_OWm>G!+symCJAOnf=qrh(_An z;3DR9*LfbYeFn=&&Q%YEO4e;W2G-lk#tWW{6TgGR4%5-XMd4J}poL;*cVjw=Sx|zn zlm?&yHcad*#xT&~kG@)UEK|K;ZCGTeGJvxd_^LfuB57okuMM=>ex`3My%xj}NV2Ih%$mBDh? zm3MY6-w`@)sat7$A4oXguz%=d#0nIqHs5ViKMV#X{4kpUO88-ZMArNmd1F6}`mbI? z{Qrk%sSe>;s<(g~moL|b{;P-_S?%*X%~+>9uO%(+B(cW^-N`BDl9+KlK{?@wFC07F z4qn$;3J13oteA4jidodN)rhJH&rYk(I7`~w`UGe(Iz#X5?Iq0XMcE z;_WOHzY|8_7~ay5{wU|3ya}97)``Q(Pg47D!>7Sf#{=G|b1D})pWnO7 zdj!Pt>*k+7AbD2m9P+=TkBWWS<{Q~RxF3o@eN-GOsDu6ZZnXJz($D_S*ulm`Xz3cZ zh!GU2!=C0U+rUjj38hunK|PA*LVt<-ug}5Wof>Ih}xv*{f{o!v}N zjrC1m3XD|fS<%x7=xot#_W%!GRJ2*$D%h?O^vDsTXFVs8&K2nNdDc-+SiOzq&_g&m zz6&0Dbmg~TT<{>opm7LQZn}At$yvZD-FRIOIc@L|2m4Y_k;>%OknBIOvcoU0`%kpT zW7~~sGWY`Ktkso2=o8m-BB?}#tL@rFGFNhOqpXHojF%z4?^sh0$DkY*vkg}_! zteyT znLfA?2*n%0v&Ld<4Q_rH5;TM{niiMRX0!tDe={Yx7$r`h#U6LzC^5jhI{_ah5cfnT zFrV!Nbaw*g>)J4vbpjqt;D$EL^E&~rCh(1In2&Y>&JY;1VfHpU03n>`O}E-G*LMP* zPoRtoi%Yk30)C|v@VQRFc}qH!iB}WOn|_8?npM%JPQY6Te6|hqyPbf4Ch&J{m`j&- z03Jc$at>mv_A@#G-$LL`7zZ}yjRbCK13p0D&27NP2)v~Y_$+~$w%Vb~X9E`!SZu>Q zkiecc;28w=wgKNp;G8z#`w4s=BhGHow>km066oOV2^$lK;%5WTXajQGX$!?GJ1uc( zhZUD5SaG4=iVNXZT>8t3ORrdQ=@2U}ePP9=8?3nSz=}&tHBLnvzYCL}rbY=kHaxi4 zevetfaD-Yn#w#cuhfAh5V&hQ-$9P4>;|R*u>NXy=ag5iacpOx@+6o(wiaEx^YfeHS z2O3WMtg$dLFQ)1HaNrfiyf}R>lz=NQ_@r6~U=pgzZ);~vc{<~AF4_b5aN+{Lu@suM zPnD(Xr=U17Ck=#gd;?6EafN2imD6~k9i8HSfCEP^<}>!a>pxBpzlT?OUAbSP(A+nr zg?9<&7h(>**4MXQR|ZE$`#yi+t+~tShp2()6`BSVW^e zX1gx95%Q|n}T5`1(W8FG+IcQqCJDL#3Kuc&xsZHbm276#|++*be29=!PgCH*W3n_dK zR>vlHlBJ~NWiEdwl?6^TM&0mNr4M6(;Q_P%iGIRd=r#EI zk~j~-bu30b0m#Yv*5XN7u{?-ZfxX-Zejoh@_wwgD{tQCBOkKn7w3DullCEa9nO;cO zR!Wx=X=G}Sv*%&q-jmCFGQvGf)}g4a^Y)yFzLs? zmcvFU4w|Df~MqI7DwlS=)Z3s0My1ubNzOzl>anx^jkhReq zk=TC}S$Kb#ehv>w3W!@@qdHsQ^75iSdMOf*+%lTKm&dC7iI-F{ra zJ};>{b}wh;8=PM1hmddkT5TW=Hs2~}<3U!sD1ncK%+qz+vB0^o9p^sc=u4TG&%w;Y z7rm;Eyy&%pDi8*XXT9ykmp9|Ly?z+I?L}Sx1(d(JWBKar!M4rre9=29m&3q^HieSj znG1Wae4#J_{4=79;eO##p0AcFfm$(KFyjwqWS_l?jUBFT>DtIO4^W^q1D7o2@hWW= zSBT*JZiW^TTXzReCu8`bRPKi=KFWY2Z2Wt8Lkpu%-WSzEZ8@L|3b2w>DrurUrka6L6IP(Vw&>4#!je!IroYHAJ?=^^DB) z7+XTE5sXrTjTvzRf=Zk_$*{k>DcG_jdT6smZ4jL=FG2dd3hqiLSiU@bD}6nhN_MDI zEZyy2Cpfdf*N61;EF$fbT}3WopDe-PGXp2IPc+S)WZOdfWNC~B9&|fTpp1)Ewxv66 zKn1WA#goIApx7yvVM^4THD! zN1*rQ2Ick3MHLTs##*+_9sZw?j1f{b=~>k%fnW@s70# zCV@h51DIphCbD-f1^`w@@H*?+qn_p(<5!-Gp2le?LkHB?w}GQayy=hhk77j*c;4*< zcarDbExC##bT2;`xtHnQi%&;kepXv?wX@(vr;l?1Zy-710`)I*Y`zS1(+u_^Z!a&)m5+XmgiAvhX_>?Ipf zZXB?NsHDNR<~0un9FEVk!tuFRJn+KtscG&c+ZT?{)iD}up)~!E&f9c+s)R*WzqSf@ zY%#~*ZRvn}#loe7*kY?u|J${G5qP5?U_ntkkR&q_L_54v1VZ8ds}PZeG}}R&PTD(U z#&rtgK8<^#bwzSV>q?GfZkXBoV%r_3x3ThQoF0eTrrryXjMFIQP7=+GQ_%2I*TyW- ztc{NsDdJccpubMyoVgT9*2X7v0-o5G^Q0t_E{9JQAZ7n(&?)~Q`*)!JNcL}MWTuRH z>30}yDQm5a0l-8v0N#h7QsquE?5~xwE%}#st&cOLV`+wK{IG>ASnbf01zN_t>|8^< zgR+2o8hK-eT<(#FUA425%3g(ifjZu+89m`WSo6vRQc53EosYN0*Wq>X)4qGkcaOnt zK5ZesUmsod$W_9_h>f0D$Z+D}MihJ^qUNyy)#Ht3*0sI`ZDV$$IB40W9R{s{6NA=9 zv`1%DNOI6(2YVq+EnL+Wzn_kI73d9jr`YDd(LaHk z7QQ09UAhz!{Er%32A((Yk|Aiv^@6fXGxcFMO@kj{(7yARGR=S6M|D$^U}}1bKM22{ zVg4Ze0fza5@CO;@55icSrKk9VFl%i+3a{am)SsFpMG9aj^nHBIb(~9Oe1&uM=@2Y4 zS8vRLNY2%KAs|!fnYb0$u8=iW(r2{T!#<=Ka6~&_&`8Kw#q*EA1nvrL;lhx(T%Qp4 zaKU9`hg4bpJf^H~D%;(1)rR=12>tX9Fu$H~3&oJzT`c{&aPl=`;#|4QUi zrQF|~aB$?Ze~bTXLU=fGjuwIYmgs>dGjTLg&g#u+4mqWM9!bQ)496}@B{^J%Q-BX#;DmWMXvz-|>36<0jEx!6MCH!s zEags|<6NO|tfEiP$1Lvo?*oNqe3IQn9Cs&41`+x6{M*|Qsn&^@HX}D2FJ_#+DP+O^ z?%b(+Q9?9{qD^eugLv{^TQa)it>o7|%#!lo&ah-p`R`=-K80V(@ckNoH^WeP#J`;3 zk8Ai!hCiXGH6T_f?r;JK}doL>_pLy|gbX142sOz;>E4bdRB1`o6YxYZ}X^zTkD3+=Dwg z!#@YpaTD|$a~K&UEo$pcl1H(eGG^fzT}a-=IE)z_%JS=#9`dyD{;**}J7dD@i1xh| zmpqTlW6YE{g7W6ZIODi=1aE^A!LNsIz>sJAD<1o-ac81l#-uN!F=S|DpLU{=@Q6m! zX7+hZBV!g{jDDsp#5jyuIHs+Xkr;=u*=;4%w3WfxZ8h>i8S5k;gh%vWu6&^k#`Q90 z(RPL6P&Q*6kyQ0v;(ZV2J;br@UxDJzREiTjAjzGiqd`@%1>FWut-0a^3|YAGn4q}ESIx3N#3WQsA*JHtGoiS6 zFpG%OKy)W5r(zK@pcdgX zY1RgV4GRXb{YY09MU{rtDwYFZj3@5I{De0B$J7M1!=LwzWc1DXxP zg`ul9Yr}Sp8n&pkhz-N#p^{}evXmBS!#+-Hcd_OdnJVvuJ3~K7%D81*ZXotR6kF22 zQ>mf*iz|8&UZP=gpjyU7q*DB2tlpiZ`rr)8r{{yFglLbI#sv9J!KId(d}U z#a8|bLTw~!MQMd#^WG+Yt%g2W*BR>C>y=#h-RnMA!d#bE0hBq*9m~5ZrN5b` z@ggco>?z{xPdy|y+Fwvv^2RdG1^vKL{XUj;W!W2=aIw9mIx+stg4Uq6#Y*JAQtCj} zP;#Vy$5ir_AYME*$2D(q@EZ%#MC-EyjfFUBH1#=fN#1X1q0FmTrm@B5pnsVtXvO9_ z+1cavD1o9&vFbxj95Km@0Jq{&y9{NfCg>=H8x(|$)`Uez*@W&YbyoNu)@E0`v zNroTR@TVDmM8p5U@Rv0FIflQe;V&`_b;dG3!|GH z?ug4{%&OD0k6jt#Fa{j@x^HwH=Nh)pde_=GxGqGJJ4ya?8sMVySQA&Z-tGAqoH!Nl z`4D5gao^APjQ@lBZsk09o+^lE1U1y@q6e#ytBEcwl*m3~4YW;YSwNG*n^eI~5}Kw4 zMX|XUZlkqSaQq06yd;7>cbWd_I1)Ds@5lv~%y!>_AyL%Xj;{x2VSTJeZd{yL;B2SH zN6*Afn7U9A#bO+mG`k+eaQ1=uvFdF|#EdR)8b3|CU}A9Mqmzl=vJ;TI)UOj?^!jia z*ATJ1vHp??`S)Wh6lVZAt<&^i);h9keTv7L4W3N@urve@Ivtlj6+nf&xGYq_HX+C8 zpMqgkjq5MK)6I|R^gM~;9J$B|mI^%OZi7ReR*DpQCkw@gb=(F=yslUlCn>086pa|X_c=;(;mwkqOPuBc zfaI+|#f-kn1AmsFc%chR=g2#pUA`Z_&ilTu<~P-s1qU>a3FYBwrUUv_>|AH5IG}OZ zh+(vb@0INYPG2iy7!<(@F9ge>VAZdyM_{X$!=~t3RH-e$)h{A%9`E)J!pfu28|*)# z_)T7QOR$RCx(7%y`BZ|V5O;%(gf0M1YdsjcDYc;iPU2%1cOh2vDqDCoB)EY{mjY>g z3^Cr3qcL1s9y}7I?T#4VP13Zznnz?G%b~Ew{>a$)2Fi1qM`a)DYXhy$J_h}fz*>@h zY@iLa4RW)s4^a%vDKYw`z)jwB$|7a_<(uQ4jJ|@C%J?`jX6{$31V0DiDSr+ix*yhu z&?XFH8HC@M>Gxy|a`BTh1CaefzohHroK^dadEy*{9^^~W`by>(u@9;JM;-M+;9sw_ zG)}`Hly4Q8+arg>AAbZ+71M$ft2?@ed2Cm62eV$j4_}OkoPG$-6$7&MRn!z82gL0i zxGXESg77Uj`D&a&{&G8VA1*-)hE`(dJ)^ryyaLVmT)UjBuhH`kchp^#1Vb>dmr%XpTMhzVH4xuvf>dh`OSgG0qi?EXWc(-}5!CnO@=AK_vyvrW+>9TaRV~N(ODOZ2 zly}YFyG*k9?_K7#Dev09cbV6vyz5?MnWvzSZet(SPH2@d!{*&9AV27%32gjUw&?u@ z=z$Tu(OTFgyyYQl62kAOxU#DVK^*@P?zKG8|1C9(xo|))Fk#;vh`Qb@@_<1IF_vA$ zU6D7>&zs`*$xOWq!+3px(tIi;8N!XpH7_5?pl!_xexjeG;Mt?j<-rNh((v(zsI-)~ zcAMq>C@e3d}2lT{m-|66>YB?1n~8F0KLAfTs6_ZN@w!*GH^KjrBlXb5!tF1f6f- zbdr^Kp;>Y(sS45?(PuraZPlkZSY+9LIZPk3ao(TAfu%MZ=L1Qc zOcF=i1{)c5VZ6Rz+x}H_f9JNj{01H0YvA{Y`86iYR*}mOx2*8UJnq7cw8#u5i154H zv-4j4f06gv7o9i7Z%Fg|#ArWMRI>=GtLqAxdNT=j=-lVAKUYy^4*`z$9BD_d>>2Z zWBXnpUrO%%!cGwAbl2snhA4Cc6= zs_B+GaZb)$@1tuhEIxQQ}uO_uS=WErF% zW!z?!q3lnTaa&x*F=z_}RNDd`N+LrCAEtJGw&)K0p+0zk zdF+%4oF`BzoGbA6jej94JPyC=i!~U&>dP}^6};|NDumQ%7_YbKYc?!~ui2mw>_Cox zJ@NhCZr(%9^AI*ao`cd$@IwI+Kgi%0&5xHRJJnEjAX;&sv5XflJ60Kgv+P)9Sh8~j z==e;?D~Jl-6-+nxs=&NFyO5E-DdJPASy+1PIJ|~g>c(gJGHUS^B8v}}!1#}|=1?hR zhULPMt9>x5T(Wysx!2zIg5~O7g3k5wl|xyEvA$zbAH31f1qz4Mxm5i)=P?9T5WEaO zdd@>qI4Zh%2)5?xAonU6yz-5-b@(%b-997x1C$RhH`yfyC4)9LDj*5v%Q=}F$iIJ#_?Png>HXVzI92h>^!g3p2X<6#%%95y*}Mi@ zmN==}cnDoCWz-( zJq*0BkiHYi3#3uz=J)Cc@-w{gDfA8IVUZsVc82YFla$%2g_VUgJWO~Cdk^Mj+z?;j zZfil9&<5WjgwyYKFz9(CE(bzBh}jkPaz;GFIGULCcSMH8xZW9Sfc*HcX8JR;yui^B zbmI%2ZfjAP5YlSki(|Nck4kvwHn0tl_u-q0?gXaX zlu(|Xj@S4!9m^meiYD(YWoRkDWUDb8=#u^V!} zP#uD^ojNolGtSw}D0LDp&T<_Gdwuv8 zubPQ@r+Q8~J#iMmDX30drh9TeRqH$5gbNe9BgN@W*Y5|%R%YbUbbHgq-gI_dUwQ%u z3$U}1?p8~oF9lPfj#8YH&smF)#IG&<|4IJ~xsk2)t-}sUtxvr;ur>Caz%D?R-f5Z^ zE4SOFa4d@NuSiQUMf-mIG0eMvr0|@W&zL{~LIV?t7XO|P{m=8yXKHB6$u6`^s}9c$GZo2UGGK z4Q`NUEvFeb!+=Y(^ndAg+|A!dWSTz}`Pafi#=AoR)p_$q=T$Ah;Jo2l->3~4nm1Cb z*XlNC-n@CWzFOa)m2Y?y5TMzx5y0x3(MtKTQ|6D>>YCz!5q3(H;3A=pHY8GOIpmWR zO~%%W$-Gqc)hF#`c{0yX#6QxI?SEKe4pMjdC5Gy?*)?} zodmy=U`+aL;7bX{T;B$M7r`(%(r#r6HWDAO2>E~kggdqe7Zs*p*`za5u;K`YeZ>c? zS<$K3lSKREpF1hMo+MkQaPSk2BVhWsjOQgTJupdD83Y2RpNSEqUna>ugFwLavk^jN zYHx&5|2}+-PT}Qcbj_jY=*U*N4#a$wI-VSLg&J>gedSie(K_r&EE2%ZU&09t1X7bD zLXIr&$<;>n7{vI~T$tcm3Q|+*CPt~7C{*^fGLX0gd`fGmb1SfVtLs+4uRW0=GqsAO zNqjGf?}6V?CT;|cFi+Uw6t?uW`E>QPLi6=b#+}6conBtYH7ra)b=R5@o)p9eUYX-t zUs&j6oeV!6jq?eR6T!A|WZ~uF!eFf;#JDZf=Vnk@Se4vq!zPViY%smsVDF^Fmj=TL zG^PgEU!8Z;_{Dl+<$#yjrKdY}Rd@QT@ID;6k;N;@e3yd1m~rvVu|lePHT@j(%iZ(J zUJk1((XU0P{w}n?+&zSw6H@8w+QIeJwR155CiX*CCs$0t5k=V5Jtd@4hoUXHQulH% zo0Ct*%Xuo#naBYjjuL(mo=3NL06W=P*}?Vi;lQR>rBpbyls;?iS;smP??d6Dkun7*)MI@pyh z2&;HptJh{#bA-*84?O0b5oKR9>nL|IXOU6V8EGX3=eG8|5A&MJTsP02I^UH3RFT!u zIhn)2Y`lyE2yb+Y5}V|pwBeI7D*Y>oRO`<-cQj3y#vKZ2?<(mk-I{Z;xrO_js&GyY z$?OU%_`4{VV_^ur`hS=^6F9l<;?6(yeawvJxkueIqhn-a$<6ePEK3-VG$6MtbbMg>t9v>>+u1MjXRUMeR8o_ZHvyl^$*fI}&8=@f z)xbhLCa?MNxp5Drb3<-Nbw3MxF( zcj4Fz9wf&OE#=rLhN+S_qRFcJg4h}LB5=I)3mAC%@1vPJ`g!+)CA>SntO#!85hF1B z9x=l87tR%2V{@YcL9HkD_*RPvCrFJnRR3{MEOmm;7Hm7gEzwJ&C#rKBLuoy>RUcT5 zZN<(Dcp5phEfqd& z*Z*f8DBZnx9Ik{-`|iCXwF8e>mr@?C6e}Zd^Do^K6(@7|CQs<8ES@dGYcA$M=zhv_EBtMNlarl zn<8of6Ex(R75Y58@tDfVx@&X;vQeUja!tMuluD1&Ak6^OdWJA1Wxz7&h3hICbHi&q z%VHjJnmb^~nAHfltN<6!+2(EFWW zH36YloQTUjF5vvNRlNu!IaVsIbpgiA0!BJwjrn#L~iyJFZVPnv8|A7R^gfu}lNnpkqiKH5S<#44KdPpQy_$!%GadhAIYy<7| z-tHhZn!NlZ@Z8%kyUfY1Lu`yV4_!Phu?3;*_31dh?j_{pO{_O!pPA3648-u z?y(X*&`rmM0;$^n)+;X?y^t=99UMnC&dIE$Q|Hz*h(RdrdhpLmG$RFxZyBEr(_?YV zve~71l~rTm(*?;|Z7k5#QVA<;xq&^KZPUUCNb!}{PL8k&|6ARZ&TWbtHItP7f z$e+xxTHy_o45tb~Dxf&$_HZ#2(R_x(qoyvLMc-Au2t)_xHCA=Mh;_?cZn zZ+=?wjoD<-#qw%i5r`!Di%^bOgy8a5qX&!#@~;80>dT`vQ1~upnzP<^v-aI8$e7KL z2_MZTk^muYTL0|E*|B{{Vj0C__JM=kEfgfnRHj&oFk*7+XSY-=rDrabUl03(3KU*<4`+(^k+f(>EdpeaVRil~t z3MI^S?Y+O~IcM~w%PZqt=-%>3Rk+F2omFy&LZyK?QDSLYofJ%W z?XfJ0_%c$Sh}vy}#ersP7WV{kTh=>qB)dL)M>dLZ@5;wDa}n>fQm|TXS-ex)=hP=M zz1T!<(yPntug=XpovbM%8n@+VGQHwLYt;u4AxCP|Xrf##X;k9k6f%NYgCGzk6Ha4Z zJ;%5$e(~aOzq9|#lZk2ePaW$Yg*i8soW6ncb<(_$B=5bP6U=)`UL`V_owi)cz7L|B z6H{&8NQ%%r_+{5y=~#)V&n&1j&}S{pT=VJVs|IH9z%UkAg3N+Eu-h3uL;x>RpulIl zzF@ISCoVrqMh@!A^^Lm8oN+$7j=6%r3;py^uVX#fe!7`rmiN;)_lo=fr=JE^vKa&3 z)secJM3hnEtHRWYnFY-zc&|C}X#4d32kz6(!|S)BM635VwjJjoV>@9U7w*=rH)}7B znZ@Wr>ZsDg7{?tzp;_>a?O>obUbatDSCU?ByRDKk#c{J^^qjzU4djLz%)`oAE#6*}Ia!dw% zC@qdB5ujL;YmlWStE;@~SLpJ&8P*~2Z-!;O5JR&~sze8)be0K%##|6CE((g;sf>#; zDL+AI9|!;?f>}r+#BHEWG1KQAW52xO1TXc{(I-%w8636ZTwFm}A9?dOjUXKx1*hec9$!h7)EA_~S&+TYv zK`-qeRhi|^EIe9yFxNCt*HFJyl+C`fveg@f$77Zz)zG*k_4Rq6My0APv(V(%?H%b% zoZmmXkNxTtC^|4dF2<%HqY9Hj7!4|fTOVc*#Fd@r_dcm2F7JFy2sjWbJFxS(5JIm$|)-uNBEJ3n2n2jg3h>Qcukx?X{`(g2v>=R5C{^+n&A30 zDsc>-IXkHibzyvHwGp;ZI$w=o1dm8>F4|%{_uXf6oAR6L&FiG;vj*Gjx18q5MR7 z`*?T+b0V_)_c`J$LMi zuFRBB(CFp1rmQPY4ne-6!L)WS!vjobZw%!(%vv3I50pWV@lZx^UwKfs+)FTeMOR+j z%$TRYynC{~a$PwcGDC%~2JlS&-Y&i%)*3dS1r@@|8SPPdEuG^_qC+-Y-@m2OJsUI? zx~$&doFF4hcC>rGP*=D44jvVivmiYBjtPeQx}jA=go3AY6O*mX#0$DbKC;<`3&~#J zKT@x^A#{$;`bZr8m>mVhSZU+A$!D$kQ9NC&P@C)ECAL#`wywl_OR9~ff4f&oCxGQ z){46-`TCAR-g(P7bK<_i83~{zi%5Q%^&b|%f)c%N8^|sQm-YSL$>j23Cs?EXuQZs! z)#ep)Zd=JcaV>5|C)|=^AKtYo5%;*C0RH;Ui7!O6^pd|9ALQ|K=XBDnqdk%Ow7H}v zvz`K0F`8xL>`6t(+Fvnn9g{<+{RkXk-*%_VeD-&jz-r9u&d(&HZ>QwU@tS&Vc!?VaK#35 zdHOZ1i(!Sl`r2QiOMF>4&W&#`sP~{WYr746J23MHL}nMx0Hafn&MxfcIbF({qh*?& zo$~w6at_Ug!jaizN6giu?4lN@jla$MtIE3G%1YEQF;S-w)7JL{#V|=O>#yNb|_Qq@A9|X{;o@AtdmCu&y)&CTzJ( z)WB?-+7<*-n%Gs~&h%{RXqMK5Q)8Vqp7ZipM~}9;BOre;hYz)fbJKoS!RC))_I@%| zJ$o2j8EID|!9D6sPUFo!yiSjD>Q7%eeWaGczl(m$TI1UA67e8OBW>V(C>ja)LH zHmU_;lG^bo%fm)>NFqIoQ;K}3M7D7L&@^3W1r0eUkt2;$8n$jwhj6*-=M4?$tX&_p zvUcK-4xu~u=r}9XM(L(K7(Y$h#9VKk)(dF2{$(1z>hW?~k-T4__j%6MD|DY>udmVT zv-Fx=f3f+i7}B2eDuw4=ak$#vyLb+6T<-hG-L+!*v+VbkZBirUW}eZ^!8JLwn< z%Q?)X>qz-)nFVDS--gYim-R+S#14$&AscLhPY37;x@`MJMx~^pdiP2~`P!ogEAR%p z*YH;E?$h^zVA9?_37KQF;B)C{zonM%3W_*y#yqdMb%>oTUZz3nB#1V;yU! zFMB)Y_RqTJRPL%-Y;81D=|+PNIIUG?ewBKt!X0evk#|8~w(nW|6JhWwNeR5FTH1tJ zRnm|ufA!UP1&SN@FsI$v(|smQFCccY#m>i_Wi4g-la33F-of7t11$F0*uH9MOv|&$ z7Znk>dk82HfF&+wnE4K=B4^p!P-yyBJVt?y}wr_bnW)^f?SPIyvvbL8IgK9#+&%YTuv) znbWR7&X_qMUYrJ*$FjBa6d_daaO9Po+f^A_qAwFUH&oc|s{puU<$}E_Q zXK44p%z~+c3sUj2?N=~bY8!Z#BdwcLZ7x#J%mHYzt-v3k3_$|65HXy_G@uBhinJV z5ccZ_wH8E2X`2FU+P0CUez3TB76}Rm_sov5U*aHGI9%-L?7wDc zljuu}VA{w`Nyx*7jjxb0-A%UulO}02fA!dz_u2fFvE9165FVf6n#lC8DOIl&ea0X|CG|;nfmz zf^}1XwehfW#=8+S6A!B?*P5HS@8JZo#Vk|pteJcMiMg+eE+0)?D@Y#mzl?On%9Fk( z@i^LJb6VO?qv(3NlO~V8D2t(Bt`SS0v9mXP4$QnS4O>>)@JTYwq5W4-kLtzz%fgxX z41zf`z$~tcI*F&u9IY1CcZn77?tHV=r1%fyh{N4e%uW2%e8T34$-?Wl+CR=)g2G%^ z>5?Futb?75i5X81&&;B zu;~> z)WOM*PsFBqZ~5#CamhAM;q-)Cq*6@OLh!4`l3?klljqR$_mz6)yRXuNnafw(Gv9rUJqz8}>N(O~PDu@^>bKK5LnFYh#g5hn!(6(SmTQHO@2w@BjzsQg=oPF3( zW9JuWX#`sX@{MiG8KT%3EoTma02#yrt9uJSybCO&8Setl`JiDL7%vu-tTLme3+DQ; zg+Z*V8_u?#@^B!DCE#uINv@r_2=l$`Tgwr1o4(86;mX0B=J0I5@y2;x#tyuu(X&EU4Gr zTgi)}AWoaPv&`@cu%&C@ov(=!93&!z4ykm)x4JJTxojd!K&+Nm%tbKI&B;6&mpe8M$yXH2J@x znGulDP2vVW;x4U|V2h`x-5EyF;HBQoJtp!CAH{$hO2n_X3oViHF${8v+!$sp#?cPm zhk-`-{ysQpbkW|hmdjzDKi}1!RehoT9p!FfOPpIvn)VH z+(-m@3X?`~r$RJ%$VvDM+WqBP*35ab`Hg1y{EkR7Tzd}^OE~o&F`CIk_~2-!oAw@V znytmEmR$`GE+<=NLF4zmeWXfI{y92EoZHB}gKp>B!22f6Ew}=rqzLto!{QZVa%MPlmI^b1Q0qh@W45O?tMrPJmr(=ZCk{)-y1lALb%uK~K+o<6p3# z*G?wq9Ok3)Q*NqZh#bcF$<(PR5TN$mg7Z=el5?72{H^uUiT5UV}KV~zj0}< zrFRALj+*LnJA1HJF^w~KkjuVwe5nhLIV5Y2sgpJ9g~y+FCAlKe&aZ`L3g$2fq>$bXpbRU$83xS}$Z7s{gIuPf|QmO?&nYw?Twy&k_%qv^l zt9vR#G@e6edW~nuJ6w|!aM74|{j!9Cf%|g+qnlAtc&9(UvdL(ZOPb6PMguzAr4$eP zv;->YuHOL$1bRHE#m%L7Xs&)zWz=5$>;~zQmq)8mDhV12}L3>EQ9NVE-=(AgyQhDw?=jyJm(L$Xk-gc3If z$qMGjAY;iK852LN9Xs_u&J}nNFtySDzaZdm3q6h6)|tY;bKpo^wy7`NU&TK6 z7s}=C(|&UQ{r}kj*K5b($2@j&?<#&wd3Gj!Pt!7Ji2UeGO6f-Mog--_z?z+txO66mO(E<8;n)lRE!Wb#m)C zNqmtb%O5O#yNxeTGllx{Tv~7aD)L~Q0;g|f2noO$6d_}5DulOI1ZC__Sch+MVq!y;itoyiJw+xE+z;I$to4b82oeRBYAD{9}Bmm zf8y^JJfJy*_W(NFzjn_i*UL2z zFd5o4G3dO|S@YPin#S9^zWdwcnt8mzYY0#&PWZTa(HTK#h#d2iqS+xPD16aAk=-&OC;n4e^x2O9)TIvWNTLF;v zvI?LMT>20YE`X%K#KQ|-%h6DPGlT`({hU%g+XvkE3gEQQPQZPhuJr#3xJ`oXex0D* z58L_`u-OV$VB7Wo1h(fHRa2Nk-xJDhO3=SI7?7jfUx5hnptM`-OlIFzX5yl5-CEm< zUYw?NiC$=<_^>+4RxrrrKe4Q+zWg&g$!yC*wLBIX&Usv6A%I-@CEeUPMiOY}!qdi# z@cv$a_t7Q?!bI@LsF1$0^*E9jUvdZ_q%(9o^kt0t-|fRjsJz~qAb?xet(-~4 znDsZ^5V%r(%rBWUAr!0uS*eil71YIxDQ)D&yrjN${WGkTW&hpV+`$E{5 zNu}+uZE)HCtYRZHQA4;KGj7dmqu^MyJdS(W^2IrJQ1&`NE6DI~8augS?=JAL`fb~foVr0~9(LL5ugedH%6!w1% zA5wRc5nh_hKUWfKB{5N8=oOnHZ@C~<6qPEe+Yag%f*_eH5@XYC0>@a1ZJHl)LVA!9&$FtWD z=7ayQhEt@z?yjLyy?$4{R-2ijIzA58iVeJQ?2=S%asHqvsL?3X8ta=%&&#q9zc4OM z|1}Zpl8iZ2*#;Z#V9IkH_{3UvXKxO0$2luWV@rl5+&vf_L?rz5=i{7w5z>AoQYi(U zTQJ4NyBfGQ=~1jsc?`=_Qs7v$k3-Wo(`l3Z&uGW<(H~U5VTHai*dP~94UU_Tz?r@$ z&7M1$WY-K|$bJ&pL-DzOEXRYhPt7cR2AnkNYz4ckQ7ezu23p%ltU!E4?Bg($l@HD; zK|ByZ80b-k76q?R&b>l-m8L(&;UL&XLh#`@$I{Jo zK1qeoDh_LESfs#VH2rQbpmcDUV}Mmu9q4lqyu5BOE?&#|Gz*`$P=YFWzh-nwNP>U@ ztDz4uoU#zuxyY7@1Qk$i55B|{3$Ec_Y2&6lV40e+fc4hZRdDA9qU)}sm& zKiyVht$(VlFbS3K6i-R_=rDYSefa~?h4j<^W%X~~%o5ZxiqPsp}sKjABIjg@B>=8^Ts4@;%9)yu_Ye>DVctF z^4VN{@lOQ8rvyTAD8D)gwV{0d;B>`dw)p^USCYDOWmplem7WIBUniADt<%96-9t(b zV_dm2IMau!)JfuUfZYgN+Fcm z1`yCW{Z*a$JwhVE5f_!nZW9qgwL1v~arz&;2=-+QzB(u3EV4i{6ilcMG!w%fI{6vA zmDchRZ}dplnmf|9mX36-jU!!UO7tV){WkEff1Bm`v^H+}?51=vst7P~RS3Q{U5qMv zOiYB}Qkq))o(5l?2CD~5YznELlm?46)8Kzf%NDa*KkvK4uyZTSW90wiWa%p>QT%|E zcv$~Yk9-$rrY(2;lycntj?Ct-k-wp z9fJKd+xvL4Efsco&BWUI*C9%7`L5zE(fz#DCpIM!-{W(HZ#Sngy=3^o8v38N)SCbq!zS6J(N5^1svJNZ4R%SGyU`iD4p z2C*mOM@i6HYlQ;LY&VeDkC$h51dg^>^x=6kUG82rOq_e&%IX zu*2tfvoK)9!;jOFQ61oqG8Gq?@716zFII{9uQI!B%2w#@H5}0Red3y(d&~&PTA$g= z{i>6q4jhnny*Wvxnhhqq&5byHFSDD6T74Pbg`fm*=5On^-5Y1kExiIR`m@@JeJCD_^}Y?byW ziT2Og+wJ?gm%y)*@!TS-26Q1%M=WT=bXL~D?wf#+3Eu=uT0v#g#=9>eLj9r;y&oR` z&XIXsJ$qnXEuXD-SuK~#8hyIrQ_T0LLuV4GUl zpv4i=&iV8tT_Ocy?nnR=ZmqgGL#45*O!@I7f!H!()H%D{il(XkqW}l0cV7z*wO>dS z4KY`6wN|!JZeJwb$Xmnl*X)!NTdJFXjawK3h8hPk+|@38C(D}`08?$~yLWLjlouH? za1q1OJfa1p7nb!3mO*iUp(_%g@EdB3cucnIl7#D1GzIz`2x!Y4XwI-jqA`2#9ZoFE z(3s^wr7g9gJL4fTT1#L;qagM&5F}9d@Fp}Mb?i=u2LLdXco5C9`39krseA98%>(*m zh%x@vIQm`jZ@AWuQ?WQOW{j(8i#k#J_SweX>$gXg1 z`mLWdAk`|J6+O6xr61bYtdrc-?s73?-Ki-V5`IB znZ_Qj=4k92#_7FRXN%1Zl!n*PBQw?F^jA3}#ME5V$vz>b=ikJ}FnE6<9UV0$t}=LQ zC`>o6{47na!I0n=qLFR+>_|HDt|53caZ!l5JI#=ei6Q1oX^d`ZN#S`@8eC9tqGNn2 z4K5Nqxg(R^P;b6j)Qa?xOFw9vbf70%D^Z{4e@dcml}ya}4(PG@DxI?8>0Vx)$hzPNQ^j?Q z=u0@)W*Q5$2Jp;jTHnr@>}rwiPNSi+P~i97GhzH#4L6Q|^sNN{Tx4Mtw-Ko}eler*{r@^pp)fdOg-c7gS2F{T*tdomS+6 zN(kLX0rk*r{9O)w;9k;?`}*wB_V=Uf+pF<(KH7)k`A>cQy~r7APa@3W&xU?%4GaBP z?|JLPA(3?{-GL#TSZAu!Ex8`cP|)9E4p$=ymuV=Wp}GA^XlLTXQ+xOHA%_mB&1|6* z;fhrLJ~Qp%{P zY0kIWiI}hyk~p~Fm0QDT4vYQ7r1ciJQ=QV{8B%wD9seI*#Wd@jozo_Hq4tL^^Yri; z*g0^qY&!L$zZ6Z7BjUPx!_Y+kr4C#ZxpmxCAELtMIGeN(U#}zYKP#_|J>1RX?C7%^ z)(JlBo!}g8PyZ0_%|G@4AG`3!MkkP;&M;2Zna%X4Fkr3McNnlX=$$)e^$5Kn+x{15 z**sOkP>YEGwCqr*pW5`xbTzl2b8v}u`BerR-==zmQntHEg?T~}beT4mIXe2b>+< zxSo#xzAmk=43M5_jq99qLz_^#oWIsY+CyvO7YNA42r!VMV;T5EZGOZ##-U6ytN)PTmu)>R-k0C```Oc?! zbUtko#XvN@8i0g!o5SzGl4)if;dAw#_d+|pjYd-58(PYhE^4|QM2e_U9SWn1owa@NxJc26!*AAg%#~$F*KV-*4fe)c!wKcj{ ziwh0a#8IpKCPG90ilf!-Gf|(+Xi~@$nIZKj$O6TgEEvi(zn~T+TGDb|tNkTwZ>no? zb;k~Gt`)ZM zHQLt?s1x1E-v8a|A93P{&$YJUhbl7^%9RL|5L6UpNlXpBd*p351ZzXJ>f}I0jk_EO zb*@ybu|nwKmSeRZt|DA#=!R(;e+*rcCu zfj}YXaP+|f+9-2TcArF)+kN)7#O}*PL-4JSx6HYG7svzhC1cc|leld2dugQNdik(j zdQs3l8*NGR&EHC^S7w{DFsZ(m_19(!1PNce_zbv+=xm}1)j>A` zY<}ZPIgrTP6pk!flBudpbAAs+4yvWORPLvs z%Ap~;R8UKTz2p8J^uKD9e=7bR{jUs1`KR%se({@>@vnyK_A$gHxU)NDUqf8IlIeT} zCEYxVRq%dERABj`=3h7}g0(Bx70adaT>A_%%S+HaQ!g0&b?uk3#eTLiNB(lpa59$* z4f=veZecDLt=zYRvi1`hxFj&i{#1!Ti7AybfTPN!0I%y>d4-Y1++%W0hhl!rp= zCqsSoL1a@(Yk7Qh6J}xNu|$g4|Ij1$fG#6r(gJgieLv>K@wA8lCSJ=vPYEaNI-o=> zXk_tNV1;rZY-4WcP+XqaX0$@L6JePbncBj7KBJ8|PLpk3^6#lKo1=s`TDzB)$*kGt z2w~PdM?A*tws>sF&f0b!vsr9i9P%7)EI5HJvfGAJFbr5~FYVDPC@s?^-Fb}xQgJj= zS||Iaogvp1Cw9j<=ScQ3+q|8C*Xc>$$FdYkN?eP-Fa%4YNL< z`XN@>FXzaNP{y9!k&T{0zJC+V*ddY2&sJNUAk&5>m4OmxqDr{8#2mT+6wCK}T(B6$zYo#A zVzx7Fx3xzBNqf}ao<-R|QG2p12ymOUp4(o{WpBH|Ty>z>L(!X6QZQTdvz2;bd$q74 zS+3eT21i)&K>J*n0w&bFp6*v>nq&HZO``v2>tqU(3iuD*K;DApiOFx%931J^b4ouR zc$4pOpkVXW1vS+0vpk+f>6IhJ%M4#0|9$I^Uj8c}>yhHmPx7C^pe2a1?F-cC zNx4P!z0_?b+sEMcxfa*CYP6}ZPLv*&QFyJrQXglf(#BjgLVDM=y+$nXS0-SJM}&jE zcw%xTWI>fY0>qaSCufAQPQP+5hhNQ8fInE>*D0 zh!ju_`HBOra;CYBtjXOTxk&fG2i?u`ZY#i0l0M|;Dj4(y1>bu|D56otoaPvh<}{`B zbos2w^1uU2V5oM!iJ+Xn2u$6a$=W!##~LxYHPd{&@E1)^(x86?S-j#!&NT}jYxr+ob>;p^z$zj`nk>bY@w>qzLn2ZKh8$- zXY{Ab?k_aYA?4P-l%i{oZq`4VXOX7Id--T5<@`+bA+I*kiO~GG^vP&nEwYLM)iu)^ zEEEybsAuYIUrMehv-XL0_bqSVK$I<{wtQbpw`d*BKxz7`zCP>eZMrTw$jT%le{jL;qqw+v9Sq~r9biYP0rEt;I9HSH%DhkrcN zJVsdLZW?s<#6{Nbxyx`zaSys&>X-1Cd=)&c@6gW{6qxk6vI=te)%IZjDYZ_Au|9+L zDhoiifpUV=0C38;nP}V6yxyP4C|O8ePRO;qg?od%^Q<()x)KfP0iWx8ry-Y8B@MZZ zhc!gsNkbHvG^B0~c^MTcg@Cv2n^{nz^mCHCNIiiA5~BTM^2)v?f=?nF^*)&gf>knO zfHpGcB0MP4*lt<4Qit$dbR6kj#(0M2Qvq>e0|zWX#DD{-TQOK@|K<4l>B zgrDi*DkTkfES-%IYlC*o50NKW9qDWX+ zs4*058Jrp@7n;*F9|*vJY@XeF@pqNs6VrN+0U;Nwz6;6i^Bko%!*ONYc9KgE9*8>o zL=FucH)o5b_GVt1&!@_HF%vs+qIkf^gtHQQPMr1n4?^zlGXP};ao^id+{=|>@iXAl zwA}6$eA_x1^~t=`RB4RX>lr6LM>ykF9F1n%zo2%DloOR%u0k$L(f zht`#E0_9HJQk-vJPH^;PyekF^3#r<3WWVi((b=&p#xJiHibb|^u*>2s4i8n5%V#-F zCRN!@|II|FM;E=yrhp^aZl9#RMYT8EzJjtCXsy}J>-eCF{q2Oo8H+=h+R5c)rCvKZ zj&wn&EPGQgp@AX9THNjnD0{OnRg$RWt^LD8>q_2Q`7*cXYi&{Tljc{KnkaV^MQ6uZ0IY=JC@Xk^QngLZjAt<+E%_Mo_ByJN!i^+;?=Tb?G zV?kcqTHlMduD&9g@gp0_Z1M4ecI815=4Gt$*;c;v;jFzHsCD9h7PmerD9C8dvr(#O5t<_y(U z3e5$O;R;Auw>QV=v_WO?165ks5bv?4IBdD$DoBmIS-%T!hwRU zN7Oq==0Rcy^s+CwUUL>@>0btULl0*i;SI?uzpe_%Oq|}mQcrV^_-Y_4zWFtERJ>Fj zV@>hl$B^l`5ttttd!}6{$|~QPUsCDWxDpm<4DiY&)~;L+@{wNp$r*G3=+fvl6KC!T z)?XSpzPCJn%~dNS%AJA*Fl$+BE~?m+V4aaMdZSN^$mas>`b_2)Q-0DfdiE#37zAY zC8<7e63oNC>Q*fUbLgtlRpqOU{Y{RVXRF%zL^_}kTxOa>N6UWh1N38f^{TkP9`(zmHKA_r zjr)28rzMZ?l{`y2!6=o#y2l-qi@_Eu3$0OQtV~egQ2qeBD=KmIs(9e4c(AXDau_eG zxVrN8LsRh7)$tS9vZp3 zdMUD;+Cn^9${HHoR>-BgJdm|w%6i)k82EQlb%hl>%A4-7E$U2$XvoHB{0|fWSpPEx zE2&#klgg)4K5Xg6OU=7xU|B@bH%G!Dt5nXOT(6$%@w|S{3gL!_A~I=+y9%m%PjoUEO2F*|xif4wTO(0=Q(ntcp^behiw?wu3^s?u_4Mwu6Ea+aa@{WD{qH z=hX+0O%`%ac!`MkEgxE;vd`}C*i6-z>(74JjSO!wOHlpFvJkVv;$Mxl>+LlIl}*I ze)RG%QixW4`f*13aYp(vw>)esW>)COIr-e9*ktwm%bV5>RaYUhMg6O97JcYJR#8A;jtDCks~j*rIgwfh1TAasi;QmuxXl|=|WhcDs* zQ1n0;I;9^&@(swxYw~B*1&-v&(+M*kuCx2GbvkO`5J!(Jva$)Gd|-T^*!B(&Y^1>! z$OvzifOB(kbha8Mc0RI{pZn@f*Q?~B4^b~ZJnZ?=D=PItKbbk3W8>uPfvP{TR|kp{ zS3_?`Y8_@F&?dIejqk_F3hlvuGQ?&Y zo^?dD4&lc{_tnpVR_2CJ=UFq+`J`j{QOu&q#ph5!lZ7=nzqNpTxHEkkTN_@^&-_12 zgI^mpSzS}Hbk$?l*vH)F;S)G2^on3&+c@I_~#m}64`D(HD52ZBkH_Wu-bZq zZS#Mu#L8^@G7mei_kLwb=JncX*)V|DpIJ~~pczW^lQT_L@E5wzCp76c5MA>kqOo{q zl@zOT)2FP<9+{h*#_OYvGtNsa7+O%(oF)DX(8P3O)qP~XJ(`+U@~sxfsIE2_9jhkk zY0`}i=K+j1lbk-YpanLTnkljh7XF-N5}kxg8-qgv&4C5cX>vyL-DurcKo8-rA8L(F zU%g=-%@>a)b@azyzN`+x&>zp;#YX6!kF!_MXt7WpuSt zdUO?U=b|Tpzx77SklBUf@u?8=+KX*`V3nXImD*RShD4hEO(HLGq!r66v z1P_5{4DRJTBYEaL)BXpLy+qD!5S*F$?Ahs%-&6((4W zX7$&70IH_2h^J0=T8b97oFU9?>oUC_8vN=|Z3Jc=dILDXn8j zkZ4oCXwyl1azToeR5u**`7Sm z2xLKth_}8Xx`zI$NJDbM>q&q1ercK8*U*mT@qyPY{}JT3#i#O_dXw@4FI#*nH~Zp4 zU$l4)Qp@Y_kH2X+=H2l9!d3CD&}^H@aR*TNwxGk|Tal~c zJB+Co@8TN$#qi_ zoPh6Gdy%lVY|K(vw*5Cr4-n2Q=x{hw04xGwEi~oF5T_2AHoLKzdK0y2t zaAx^aob}shzo^!`{Ap0H(=fB3;5IjP6`GGrIL&0#iZoM4k!E^%8sBSUb!DdpSyIi_ zQ|%8IZ5GstCOiEpz_VFs8h<_0-D|egGtKh>=5@F5arc&n?UA{A%V?%~fj~Sa>U<2{ zyLml?zkADArg`Ct(B=)qndt3gn%^{mj+A;aO=WrN=>XZiS=cxulbz2sY5K*Kz)C`W zXZpUSAD;(o#{yeY4&ts|QJRxgjEam7ZLLI|{|ZQj=8v&Pe03Q<3k4PXf+mxFDC;&x zwM%Flt5c$N+oKvb8~zI4bL4bcP{HjPs_3CC!PlB-Gcc|5G0M{IQo0@bdDSF8SAk?p ztTLq|`x+N7)6(uFZC{_E&L!pTCVJ|ajAzh?70HD@%ktp6&^T!@oZ6p99|ri!Jb}+G z&uRU6^wIt{n8jTf>Jk1?9irpJ8`(_rQpjZT4P8R(bvr)*!?qw`{!OuZjOKQ5Gx~ny%}yY)|(ZB5=&ibQ}eg@L1SrN z##0kkaz^7tn;PhRm?Fm=*u-vsNp$WfjI60V?WgZ|PDn|c#-HCuCvks3ul4h_&_2*a zeF8*ytmLHDo;*sp&nzfTTjcf3wtpGi^hlf5bC1`fG|l&@Nw)LV#|7yd4u_%FqO^fz z{z|yDyFeD(PtyDb9MFAcc_pR!Jc8DL-==yzCzG^Y5;H$A{{jk{f3c`$ z+9X$+D0v?l;qj=xj-XMvh5`PIsam+?%qOlgexS--5UsN zUdIo@SAiyc`vcuSvYU2n6@J;CWwQ57G4Y`$GWO&%5*b^Z{|ySd8O$rJWOBFUVNza_ zy_#!>qY=ql{a<^0G$J{?O!le`?Q^K2ealXMG+ZTpA{)I3SWfUh(MxwL=^#BKhDpi6 z)~v0ZhBcR_;EdW_3pW>g`Pwro`F9{^SU$~E5qzULC2f*Bg;{EVbXpZ}TfGq?90($Rt&KW>Q?K{Z`_$Z5s|w@_JodMxAz=s?hF|YVp$}5c3Hq z(S<_5XOe(gt~?DXpqpUP+8Jv3>oh;U21ROIu;t|$d3J4Zq2YR#+6Vb<2LO~TEHOXf ztOU{s8`0ZdP1}y0rUhoIY+ssC{Ygkq$7-WpD$5^>BpexOcBl{P=qsOoeW|bay#h+I z9psgIf!M3{D8#<#hB^wM}e!Y#v=z4&$-<0vJg!#MBo|`WMRmRiuL7|mJ(}rFK zdTme{?tBE)Z;qg;kF8v5VMtproGl1r?WYRcESymjGoAa%(44oR=1n|s>n{l18lv7! zjqTeARPSxSoR{`%9>o_Of$I~1=63jRAUZV6wujEfv!mz71s6=k()`&HWznJC+)eoD6`j&hS+Z>k;P0rzviiCW_!<2V4pt}eU2nd6O)$0bu zAVA~-5IG0JVPMW7icX3ur+Qt|wHHBB^JcX&VlFTn-3eqTQgIWjW2yhp_Y9Hd*NYlC?nv zwBG<&J&_~<2wF|Toy3C0_8IU=y?v%w=EEmaM=2AR+8^bu{Rwo52wEa9#Vir@jeTxR#z~ade`5UVh)qbur zJ%OLWY;!NUbPzu0Mig78vAe4Hw)Sa6jqhyO`{}$l??RPCLcQPAcU!B%UVUBEw4Ocd zVOclBZ8@<)w5a$GVx6*-jBOL=C2eAp%iD8sJ)%`I@H^SEAzemHu>sB@G%}Qu*Z%N;T84nY3{7v}rH=)Si zgeAjkIVwTbQ$!!v6V1%5EHS}c+3eY2s-(W1gML&;eLKsaRRvm%G&Wh|FJR+jIkRBt zSOg`uUJLp4Z7LAAZ;fdEwB;uOM`~CCeS3lm33}xk+p7fC;|Z#)m(c?2`6i>sAgLMr z)I7UGLP_;uHZp@O+c;eXR~OQ`8*6E-RCBWvAxvgL9AQnbA+w~kDyi;oRZ@M2NCux$ z&a&m_kAdE7&eTn0!e_QEAIbcM0ECV*|2p`Y`zea;@1f$=YT5Qvh}9Jmx(x%Lk;vEn z5{aMzy zW*FAVNamjohhpdT>mhFiLb$ViF=%Ly0$lfP3OO{2!u6Zf#MrHo|3aw}zYdf)8?W0q zxi(sNd^E}&fZ3Vy#|3|j%4}bv0_FBf$1EFcA}#`uY_T*?Bu$7;ZMU$#Mo^@(M_r3E z+sW5F01QmF*>nCpJ`#70WS3wF{zEJO@q!`s8oIwmX-;#0j-`2m(#Ucw`8NCN-AtN(_|r5;_J3NnmUfx{v(N6bG^yQ1 zJ&CO67~uq_fN%C9Y78NK@io#)g}_^W6?!aAu1h}e(C3TO&pY+`!u0bleZDCD{55_4 zV*2^(`uuh+De5=$`K|QxZhd|y{rpXR{-mBHemkGhMCJ9Y#=)Jws81Z}i-HbE`l7(X zb-pNg?%p!KC_wJsvVKtz+`VPPqN=`o%f?03dH0r0i>mDIEfeKMRdcs*55MIdykiHk zb-!LLy=^dBR6se*F0MDaBTSGbP_DaHh0e1(uYsOXGx{9$q@%-NNcAb6_r7Op2AxkCw%xIO+VyvX~Sj zoyyjwrKR}jj4q2wvC-MOEaurszgPjmjcLqs2wsrJEQg?z#w>?mK8;xpK}wCzW0t{| z(xUUYWicrwI?q@ZlhUE{{4~b&jZ7p5+3AuI#?dT!lc7@8bvFa0T`YR!Uvcrz1?R=6 zL+yWK^cy|UN$NfyT$@+1| zmJHk|QC=xbXHiF!jq|c^chdg9)Ocx%NDCoO6|iO4l3anKA|Q%;Z5nIuQ;&SMABRpPv`s-E=&_A`YCfE{WEM z!MXE3sKns>S;aTcfnVEeZDxD4!=@Qx#y$(hmc)#A1{BkLw!AWb2O4gMH*Zw_Z2P%X z)-3WHW9hjYq~5 z+n1>yoX1bCY-71D{2gckw{E112j13ZH#6Ekm&OHPG62)Q7dGMe%wg>Q&bI~8phLuY zD>8$a_&_woay*!=GyQvUrLAQWX4h0Z^G`_NH1R6mRK=lAyf|#V)jTZC)uJ2|~+K%b4N(Eup}q+z&aCkPKl( z^|B1QXXJOGKGkRnpdQz!SfKE4!0X}O%u+9z%{J%)Ydl0(J~3%lsW!@9iP6dE>}z05 z{ymGopbOhtbjd-5e}_MKQhV{za9Ay3QKIdCeG5E{1C0ksmRQkL@VkLbNDe!;%F8!X|_ zt^(xEVzT!4iRJMhvr%^&e;>;{tmP1y%LyK^kuQzcP#Ib+^> zu`#AsRCWIIjm<-sK&Yo_EFr5i-rCkoyta?=Fn@=eKc-%@uXX7~mJ76pa>j->jb_(6 z8XK;4Hv9x!wbt2C99oKN$=HcuSz{Ww^G52$?iEB`ahcATL=Kz1T{YxF_X?fSrbEtt ziM2oa6&{+`|26((f+oCkFYl6jheG&0<EHqH%z;QquI|9c(+>i}6yP2~QCmJ)b}3 z4kLPcG|Icm7f-4D^TkuSl`_xRVoPMwZkebvtGV+7q4=MLVi#pv1FVVi<|iT30Gm$l z;mH!B&w}E*EoZ!uw%O@cx#lN`Lv_kEKdu)teaxQOQm*;OA>^Za(ofel*YJjS1m-G6 zo$RGxxH$j6DD~zd<2kqASryHXD1Dh*MGqhnHD)P87_)pm zhWQS%>ufU&%smhI^S4ulP8U;RKsB3HHtln~4Ao@gbTFZ9jHV6lhe!@8bIWXaP1MqD zz?R-%4D0vh6S>gZnr&L6vNyd9xaq+t1zV%+jryu)iUzT_v$&AfoRlQkGSRV=^|%#y zTLopL)7p!DJTFpuJ6x+`v+LDj=R35$V9eu_=*3KPvxZMQkgV7lyDZsEQp}j#YX6kj zt;Irz4X9)UiX?4Y`FyvK;A284x<|NkUB1w{NJW_#D|F`i-mmVxyAAvMMgjSCHU2-| z+qi}>|luBj5shh|2I%k=SZ`&N2zrBQ9L9s3E4UxhyQE^^F68{Fas`#tm^b3`_l zrt$1iiw$p8&Xeprpalk0<-U|a&iuQJ=EyfFZlm{lysaG{$e4~b$Y?%uCkUX9fj&40 zAHp*)NbtY=j-J|ZtA)4Sj1%jNu|`-mCSDuB)lIgVVi?lsJM3cb&k=jWtBE}h9Y(fk zfE|fSRAv|#7z`x{Iq3=*H?Av$mig_qmudH=k3$SV8iPw^@ccgV)N75_FH%~g*A~ZD zoe}pu8adjFsErlkv3_Lhn%H$@9!o$Obx<^4G8$mvhT0Ss?t-0uUlcb&6r;RDW4%Q+Xh5rgaPUt=P7f6wdhI`Dr(vUEQnK4S{iIJ5Z~ zf;6F^xo+%+=n3#$k}cPqw2cAI#P(rJ7O+ZxOH}qnH-lwcSR)p_=~T>2y0u6VR&qQS zHlHsQ5i-^x7(R11Dph-0MrxsTTB0mczm&ze*xB(Uddzp_BE~MQ&;N-2RVx?k^Y0aN zl$-D7(SEK`HE3C!K$ogm+TUD4!SkJQQR{Jqj`g&YC}od z25NjoZPMO6+t)}M^Y79RnB9=9k}R121UhUgzwajQi6#@W0l6i*{LJeJ&ffHL>ND%q zG$N+)3auArY$&Q0>@ncldp}2MR>D#~(RKI1pu*=`lgZ-4t?pmIG*vgX6F5a#EqAe; z$Al9A?Eshv!W{S4%qpw!2e2XCwjy(5MXhwUN=2v2@G*X z2Lg&Dea&zo0=rNDVZ&yN=I_E;fw99XH(o~UbN=jYR9?<3{tBSPWrHPw`z}DtTIX@N z<&0VIGmkY_HUB(dnLiUz zp|+n7H`6fYb79voF)t4dhxoHTaKn>XA2?r7&4o}l>;BjuFC9)ith|?b-k#9%yw@Er zull-VE$i$f4rG*#FU=1dpWc$lPL1~Gue9=&VpS_Y(>w`$;f-Hm+?&2gZz|lV%&S_} zOv1hfXqki!cD~upvc#&LKPMuw@lAdYCYFA&bppyoZ}=`T9G0@>_TRwgZa7O!n8gxy zi+vU$?c2q~oP9#t4BS6}Rp{->QaG+jT~LrsjfGcRNMbPd;+}79IW!I&d5%=Og&`mte}ipWvLCvcAJ3HDzTZ=a!qY+y@b) zg-7*Svg|ZKSq|%gF=W3EeySXRw9=5hhDq5{luv9~w_m580%d~z$_%nB++6!>k|k#0 z!lh>6R*%NQ{R)u4=DD7Qi$moDs$j6+!sYM}nM`Kk{%PWR8Hze);SPn02HKyIhea1V zR6G3^?m+v~iWr7HmTGsIIL3Szdn_|(gJvi8SXHw_3=GMjjYqoTh<-D+BFtWGsz9=+ z@9cxt%IMWRWDETl{xq(N_B^8B;!or6AM)29{%yXM9=*}R@1eW!8db?9`&RRHNqR-l z?<0o!=oLKs$M)wE#+s$*F8+piSG_Fce2GavJ9)nAuXghMqQ8?)w#o@SWus-Z*rWN6 zpbB!i>$KWAcv%3IR@Z^$Rpdrj-HGJ1)>-AgQIiWSwVKP|1x@}&OV zC3`2^5YFJ$xco%g$~g+M|K;M5qhc%`+|0NmYZ|!D=BJ##Z{qq_J+*9{x=vMT3{RPmsR;<@Hw(97v@kW!9cSrHY+NO9D<1lJ^%v>fW+(bMv{bhF|p?fWU zU()S!>R*~t<0TAmP_sTa$D5~$jJW8m zMcuK%ORoK7#T*+SYcYdK%pfsORm^emaTYU_#0;UwiO4nCUHIrnP!5;dpHV(K)S+W)$48xO!BBjBf}#;Tsw}kn-_U|LQsBe+ zv{GzIQWR^ooQ;2GxsApn{qC9eZ{Q7qv>Fq(##>o;(s}wXEFtgJsvMWAd)0C+)`5l1 zHgtci?cRi4h(hKU!?k zid(xrNt>eh(dP3}_~OoFT|xT`Vs1jtUJ-K6ijWIegq*%275%gNT`b_{Zp z0C1TWLF(}C+H^d<`?jBPHf%MnH-AsXh;EHpDD-~&O7M&EFZ^zcx9$3C!*9d!OZW=> zHdf%bvA7(+jm72oZ7eRwZ)0&eejAI+@!MEjj^D=Oa{M+Hm*E$f&pI5&8^SmUSQj^% zzb~9;bDSSffLMN@`2k^NG_KaS7voXW4jZ)-;}dc5l;cs5C8;#8ak93(wYKvc&|;)V zitUp=FZ`6~Q7??v`}n`O=CnUva||;xbj80~Gx+5-$Gq0Y`M2b1@6FZpeh!m@e}rgG zhiqMfV*%M_7`gW@4`DUs2Q?te|A+nV_W&=;m7lH*g`0Bd^jMtetKS4*AJU21F`l0q z8A3%8o0)_%rXew&3SShwWsdaEM|N@csMW~&%?}Ay)^7?#)LrwD$_L32L+Auett>Nr zixNQQKx=bmL9l{Mrl_?lP+vCk4YYD2i67I)>7ZArilA34nb%a6OTsa3rHB0S@^IFB zD*VIC!x=jj{*hjICL+x7*F@4((1%?A0b6fX>tCSOODA%Sd?gJ-DN^|$M%9BDl?p<% zerO|QLVw{y3QeY|*`G|)CV#{|)`)<+1)fXvrpD!|F0(>2TBPF37lM2ng4xLRb5M0b*HPV)7@D}I(w1o z&Pt#bp%}8v#1|L1jX9Bv-w5( zLAo($ki;CPu`OO`MXVj%5og7WvePbWa-#uwJB!h>sss(JM9Uo7d2+#a{BcUqW^c>T z*?%8Nxz-HCb1A9-OA-9TJCU;)iBPV-jEme?Sf!#-CAm6Gb1EKgJ_QzAucCSgin7^x z_qmL8Mf%k7JXSHbYoc2-^N_+s9tzF)MlmCBG~Z(wN^TSi@F9qfwJw|u=e%HWb##v9 zwL$Z1LE+t083ex!Dt`#d!Vy}-PbL3Z2D7b~(iVzmy`F{4Qg23U$b+BfegRmFLYze2 zTZ#RMma@9$2SqPAZmVFJ`fUASCXLb^ORTM7;zK%{>h6HS{{%4FI+L+7X&x|MG#6sr zqm{RlLecufW{6VoCq(`ouq9rX!g@A=kWuSpDAJ7S;1j+3@Lo@f+qxK~3};Vjn_R{| za}($3@=UOdJopSiK9Z?lO!pzYzm1&=cO|kUPkbV$CB6QOL3Z*NG7mZ#iTb7QT^-}2 znW>MnC`~ntPCKI9L2rHmiXWdM|C3Z-w)G4fhw+fA!<(teE`woAP6;ym*-bFW1^s1{ zeI>70mgEEjUuDk>m+VFv6Vn2Fu&@^G-@~`7$fd|lb~-uJy}WxpZQPeia6!x25veb0 zX&e2SWz6?5I^~9@U=b}AEg9L`NLKVLzD4E(KPDoddvFbfX;m~X@Y3+U#)00Ti*Yc6 zE*Z<4xz@WFa>p$AwO&2P*1D-4y%cuh&e&8+D;y`^UI^}Qv0{E`7^mHWkt5w2kT;nl z&ULa;;mPw!Jx^`qAezQLZY2A{@nXpU>Tz8V0HKYS&WP1X9Y1v!icmd-c7+ObnT^1~V1Vge%am zq>1m&Ai~N07DPIk|pc?gq>JnO;7Hkf*>xs$@*Rg`Hzo~!FA5LL8C*7J zK=hvW+CIkpZyY?RXj1|?sIYA`HFFhi0ZIeLIhU>S2ef*pE8Sq&mG4gbWl-+B z8bMpd*8NnCLg_vpwcNfYdQ?=Xhk`}iep^LQQSDPps*%+}PL_RJo(h_%*KklE-1e$7*JSnAf$uqtcaGN$)nU>Zd~Fq z)lw!?vg{(XBvgo{_BBR5aiK*`(?wgD_%R|Mz8#YfbQgpWUE`*ul;@z{d2}G}my%cM zyky@zk3%{?VPS$p)D^?SnP4JVajL^1NYaDHT;k>~!;;+L??4kCzMK3Pt6xT0UwZe7hCvb2_K>;32i;o0X$6YNGB-_(#c$YpIuN2QVRwGd@Hk<4ZG$$`xK z8GhC^L(W}P!G4dA&dTTHQy@S$#2mhpJiU%AtYmjE^;*P$<9qtp#1)SOJ@LoS< zkeS7-;{e?pW{UZqdRBX*rVG>E!jtK0&P`V~Vi9cZS2=_1xPCVKq2RxOdJqe^1ocOqC{o^N;vAiTTmIglRYtD8M zH#w5;+H`n${(ze1;Taw?H`R=`5G~-SFOXD)`r$0&*0Ea4Ho7Ga5L&<7=wkhxGijRb zKSGiESla+*OTBj1k;TZYfHT zLgUuwY^ihtw1~pakpMW9sWRSG8}9=IyMIcT=7PdjI9cQ&$7d7qq6or>4~_8I-`e~cZ}gwUXIe4;6Y*Il_*cdN^E1W(i0I8v znoI^*%d{SrBPVDiK;nKt!c!=?=fBxT8!RRiBoaxmB;<gkMhJ&AIJ z`8WA6mXYNc5R)A{x`V?Di-Z2-jqaET);uAaS7+~n_}O@N`1e>yCAl#d?0$vD8hlHF;^0v_Z2V0OJb$(^RoP<2mZ z3ZO_(wX}q-k11KcfNI6TV$ok(V3(zgpA0^HH$$t1tfFN+vrgZ9ly8)s4fbk+(M<=o z^|SX1V57m?^4jKIlxC*yZ>Oj`krlVf*Bn#Lw&Q%KG(%$ zEZ%N%w&-Jh#>$Zd$c5k)?Oh>a`b` zmU~@AW?5Ux#HGu&!LOB_~dOh8|!JHl@6Q))@JI8w!0qv_kkt4r}d zaRcpn*0ec5VitMOif5pHFd*@l#{Ga~+%f6#5pRaGA5O{Mxp4xT45s#;u z_CoM1gK2)pw2xg#`*wDWC)by`@$6wd8^@;x^N{YrqzQg7tqDEQFo6DqYl0sSEy8ak z<8dqXAwG`wBoC8~T{8n@eIc-DCyW2mz)U6sGZqg_7J38A7bHK$`WUV9M=jH%b8mWd zwrI;p21w&`3GIFMpBbN%QsZ+(_xR8fKR(uy9=wVHQ%h1UNe$AyZjh)?Yxh8&&>+z` zp{_egC-~EbqQ^k4ozEY`z$kyCOddm6t+yZK1|G1&wXVG!IL*Xm5|dt0MgSIt%{8{bnej<(Qr^~B2`v4TgR=&bFu9p_P0 z_B1YVz*NrVL&iVSTckY zfwaw+c%J85d$LQmwdSFn89CDw)xA0Y?zGfksl~X!)8Q(2#LAzlf==U>t;YwAlXAm$ zxnZqmuxH8II`nl+%8fzjcYSE$0Q+Bs5|f_DHJ8A39|V?H4lh~znpTDC%h~Rj3Q|+a10+*75Y~X_8{1vy+r zO_xn7IL06sN@wm3@127|jcFYvnQ&hniWJf%)9Sr;#6-o4)o~g2qa-h%AcvDfNIdb{ z=+u$c%kJrG^s)%9ar@e}Mm0ziH8%mpNms^*uJv7k|a1`f)MM|6i(4wocjLf8jR1rQv_!*6olSGWeelw|B4%UnG3%Y`7wM z!7H^o|7+~(xd~T>T}UUpDyGbEiZ=4Tlr*3*U#zT4lcXRmc!*dx2?}5dQi6iBQ-T88 zt9AGmo-elj!|2OtvVs?po?_pg-Oaw89kXuK`!dt-#RkS@7T-INkJ3S>uLMe zht#+k_N{f(F}Pf7r}iz%j)pO4T$*m9HYntFe`bS0%lkBdbk@kxg$hWR6-IG~)TO4JDmqe6C>N1X?wXw)!)C(Ls)b zfu6c&MtaKeMY-Vrpe%~T%{5T83%33LrYvf$YeGuQ63e3gcdd01ga5x=YyUa9hhJ&` zNx8?+B@vhJM33!<4jIVHa2jeR_<=ZLq?^I?c zvt&yLZ=Mg&rT+Q^#g)aaQU%2#Ml6ha?wvcMx3o}7^}1A;B?ODZQKT7}vj^)v&GQq{ zDFn!SV|7=GvD~hlb@p9UPApPjkIn_R)9!2C9xa#rz4?)|jxG?_!*3zKm+{-;pQrQ0 zT0UsXtc^=pJ}(7R*5u~(6%ZC;Q=$33th6%K7wOoh^@Yiwxi7<(puR{8 zH|@KPmONu$q^c{_S6c?x7i59Kb($CF4?k;m*yY9(y_~*&8k2>cgQ<=*aeevHwR;e$ zmHXrTGj2z`&WRXDqIhBsUVGx^yj^CB>E_QEQz?U`vUxLqIE*}4 zsG~*feY2`FJBS2gu$2wo#_u@)d@9cu@oT=3KN$u_Aj9`N_}=QSFQm`A72Dk7vj~1V zvaC}U#Wv1Y5z-=dq|aBnVjB#-gQJf0O-dJZaX5{1!A(c{eo9wt!+L5GNa))wJy5jp zlPRC%-Baq%l}KOkx3qqh$;jg+g!IH*0UXH_Bcedh{rO3?&^X*{an<$40sh50x!S*2 zj~4hBYkr@9acvbYbAIjcmW7PcU;%4gK|{HaTE=74C!OO~|2&W9PJT}Lz=#;^=iNG9 zq`^=`V@iW%N@kx#ZTa1bXk4HqDQ{D1OJ||IKzWqXI81p=8|^vW1__lrlBuU|G0Q@Wsu3=Fot1Ja9%bFJgSSF@K0Rf=s04gku&jZXXciY zhkqFbT2M5Zz+WsU_t%soOVdEU1ZAUQ0$P|Fr~T$#nwX5mx_zVE$jyuF&@@+@_!h|- z-(G)-X(VL2RAVE^6mG~CZjkn%P%*P2>q1{?;(O?@o%KxQ>)l{1lejz57`>2kUA&u> z`q65Ln>-7j$&2PRe$f!eFPfA1MNk*N2p;1XL2CRW5R6|0obJUf2(1U{HD79Q2{N+b zKuY{L4#b#*vfP;Th)j5Dha~fnAsXX>3opgge~8BIOfCgsuSAdcV=xeJ5zi^T=0}}Z z4vm2XcQ;slJFWxOP@3Dy3&WM>eWYc#E4nw6H@NlVbS&dE$sqvX%0asQty}aPU9_zY zH+U9(t&7$GIvBRx<5yCG0lWQmaTgM&;dCg=#hpT&M%8VTF79CBG`J2sy0|)V8e@m9 zT-*?G8fu5)EKY_Hf^fH1-bdSRv@yteh5SgR`FbL=<9X?no!GY+FeKL~*32rLAF4#J z0W?<{&xi!ckN1M)8cd=@%kGTH(r8D}El@oiCOM9QG($l7(<|r{kJscN8J)NXFB5D? zVkfO|41}GUM*{ZOjkpd7W}Zl+UKX|A78b@Mk4YNuz^L44W({J-%Q56I4p2>;8Z*H0 z;A09~Y?BZ%{D`Kzl_;9$ZPas<>M>n`7U41!xdl)Q3FlqAhkebF<{WhHl}gjrB+5_p zKRH(AJrHN6Xv2vAP4_|(8T%%u7OIY;YLf@9p@k~niR5fg=>xYecr8x0*-}*;05vkeH8@`ek zO4pqT0cnjL#g6uhbQ_)?_u9cYs}3wm&%>+Z6fV2hB4~}#hH$mYb**tLfG?O;eQ|yI z3oVfuzR)V+3tFu&_NKp3(hOf{z3_!mL;B{n^fyYJ;Tx?xeIum=+o!F?}e7rQ#1Iv(twi<6}9d ze<++4e<*#KKI|AI%Q5{!A;9=U=|85Alx;H8Mk-}Xv^Q}7G9$bHh2pqT>R$KwihF!j zkCEM9b8p{vkMHQ=t=eEGp4bC1?&%`#@gQj#jMnbEli)X!;J+zowD9ixlHmPGa93P(NB#+*v=TQ%nJbIg+N8L~I=xur)jZKnAZ>c=s zQ?>ca9eMQT^HdrBr@K5~BDTX1uk7ZpA^nwxkJ&uH$K~*i>*1t-$x7ZCo5~G@Ujy`A z%7aSr1FRCg^tsYlU3ALPo7ieG@`_fZ{6i>TDN+I!4Mk1-%f5LAi%r1c%4iNRAI*?w`3+*|esA~soQ_~n;#5Y(}(>4cp`hqHT;{OzF z!P->Ijg=m6x9K2fKuHJ3X>^o5;?F$gGjVTxDoTf!9bpU#omQbV`DwNC_}bUDX{56J}U;K%JBuwh2mXBpz*R5{Pq`TpfJy`1h}PSMM${zaa#T95ZH zC+X#6{~{k^t@HfL8G3nwf05%x=X;w<<+aRXyX-FE!rg`tv%7>4cNS?fdmuy6jU7o4~L<&aP*ddPZ>cTH+Tnj_Ocs~YOT%dM4tLu4zNJ3 z7BqQEYTt<=ENZW8^slEaUGto5+CgJSGS7!5^Q^6fRzq?bc^ICec~)dH&yJ`U`Pn(y zADMNs6X4gRxxHs;aKhh4bM5BF@1gNpNc!UZHO<%;<W}DOw#5*12f7!8_YasCYVv=|9hCR zYG;8Ntw$%X+4VhIXWfX_F~F@g_+8=?2i@fF!lOTqzB4*hZ`2i);o04H(|HRF~Y6sl-fw&u;ZK)1Tk1K4tm~yKO|Ue#o@0 zw$0QA)Z^7Phx!5bE@6ze6HapD;tU91%xBTK$#0O(DZWm9YX1cM(TffE&v~wbyH%C` zCHyrD)rajq2p9I%d7@{PCwg8AoauN*Hhh?`Y!7OwR{xEsw(gmc{)uW2Gk-f%g_!Kq zxJYyu)65&&5sl>b+vNrat-p)9D&IAP%SEm}Q&IGVB<$(P)@#c0Ew4WVK9jsVp8-p&L^1RL58j~x*K4vyJ=LIZGp zJ6yk3a~-VVFn?y3BD1XWBK875S{}MC>Y7}2Al8e zG(606SAplXRa@(NRq0O~>$_#0Wv5!;N_g`5!w~u%E#F22S=thryX zhTW=u;dShT>eq;Ok-;4y&6lXuYOOk&_DYeA-u#rG{{?cHzaSoq`Hmm&7|Ho=By~~9 zS5jF!hmFdr3%Ujlcf_N9^0{%OS~LBNZWK8m4I1qe%;`FjNi4V*H&zI6YSqPi?P@Q% zEyf00Qez-$u`dSS#=aOF5Knvw<7|o28l=n;m&J5uBNlxR@rm{(zv?|6EYoF3n0 z!?hKz>SfgxzUr0Ll}XhreAO$eE2_(^2lxb@_>!v`*S=jf2Sa^Gn|qm5^I7VA199?? zo3WX!dJyisl*y|5$;!8O0AS<&_{}M`?j@JG-5i!fTa~+yysk(2*5%5GAg=5((r?7} zWH&#)fdDe$o$l3qqB4M1N78;!>$ywXZ02AKC$sqsp#E^ju=|OtU?-^t8{c2 z*-cA}x}9S^RG4=QUV%F4MYE%B*m%1P|I2;nk_Bh9AG=xZDixwR0@4Kf-5 zEljGgxpuY0q|m$WnAoC#%E#eZ{W8iQh{})sU3?(%cOl#8BEEgkW*ZLf!}T^RK!$0T z;sS`(U3BggYASCi2;ZjihLZAXwRzmu*Ek5bwZnT^_%vXoKr|D)ewg~OlQSN6*SA}m-2S-*n6p~mWJKyIr1=pAK#JGt3d!z^&c9S7^=+WIpCD| zqmwSGKYVJ4L*J+&E>kg;Nmls%;oH)gfZ@cI6yU96)SU5>FR8~0*Wo{Ek;Z!@m^U8z zFgYjKc)c5AyB~)A`z^DLne!%L>r1Yp3f(UHa0oT11LzOXrfN^raves(kDDDZN82!Y zDOLCbhTM0Q^B(+~Zy?tD@7pfCFPC{`=g7$1`O#f-+eZiSJnqK93`5+=_?Qhqv~aNw z7M#i);lQSna_Waqz(H-nPjA!0YSh9K$K(3&U0q@k$r4)yXgSOcqLf}+Ue-Wu=XUIv z<6QF8kpS2TA_P> zHWoDB&UWjKPDYB1F;`4=%7o~=Maet1Rf%evx4A;h*2U+rx z>R93tMO&x&L~$dqtog9DW|u2XlmvhmPfT9}+J28+8&d0$hZ`v<>2NfBAMr z7=$QCK4`w3;)ZK018Rv~YS!xb463^xiUp|cAF#FVpK{A6&-575@m)Yh8*2Fss?Rjk z)s+GoZFPtF`5do~S68^HGSaog`%>tJ72bWz1aA|L$zXK$5`Kuu6yLqf5jwa(-}12h zIabHy^&8X)u~A(TxjkH8UBBf6B87tGYapr=-34r&`3A)e0C8^>&P_2)X#FEz2zQEk#|T}ajbAFKTrg5*+jZ`}M8n_` zE+RC|!S&?3t&u4L-h3mSC4Az2BQ0ParBU6&BwA!_`Dj#8(Vg#<>Xh30%!3zXj=7NY z*X{6OQD?z`OyI&hO~Sx_;%3e@iXs+S=uUWrp9A4K5)$tlbG%os@bv9bEC(Qce;i}s z{bz%Wef9R4JJS_@Gycr`mb)^jzdrIUmkA!B?{Cw1!h>cK*JLpd${v64j@Wrke!P}% z?PO%fIhH)c({EK$UU5LsqKrj1Ue{$!-s>;bp3&0;Rw$w9l65fHm1IL86?<=BY&_C} zzZptW?U1TZ;tG@6wCc*iP=yA$BcwhfzML^avL+|(fSM_`Tw7v!1hU(hz80LF1YCmG zkr83N2{(>MC5($=k4ko@>w6g(?sSE8>WywJEadTVdnw&CH%j&QX#cXfr+3DQvB1;g zGcTuvqAQ`0b4bnItyeKT*ykAeT=NG4QLlIv`7UU5rZ9G3aWvrFeXg663^_LcLEi-J zucGy%lBd9YgR0$Q{kt}`RB^tvhTQTVvB_w{p0iR0;Lf2t|mv}t!>cyzsQOeFW0)oJfzSoFm&_3l}G`l!7_6vdVb zRG8@sYG5V&;no_L(9K%H!-!01T`Tk`r@$kkl=s`%SDiQY3uG)e*qSnKOBZ!gZ|e%b zfZ$(%3W(91FD)h#YBd}KvqW)NT#B$Gw8X>k5H$-_~hJ3F!% z0jFz*@W0PxbUn3&K-$8XmxGoYjeO*-4aU!o=*!!Kmz+Fj;Q59!37;4F9dmBLFdMoJJ!@|rt9?-?NDnbm(SL7tO2}64aFB)Hh<0j z0ZzAxxi z**hTar{7LEN)x`TCctxv9MNs!C%}1_&OnjVZi*?VTsgWse%yWM8b1;F<#5%rq8Xu` zDpYIvd~rAFnzV%f>ou{csp+bura=C`O0`jzAu=ADZ=s#>5kCw|%9|?8^h+2SLnF4_ zw|t-bc(}0B-5-_>O7b~%xbcYY{_-CI30y`Qlzk}MHV z4-lyQPQ7}VgSKj75-&`^7jM{O|O7cH6E%>BUM{5;nWyJksn#U8yg zYNfNC6}&u_mcLs&n|az>Ft{MRWEZL(e1P@VM#I+;u8x|bM>|3IZ@f?al5NKowATzH zmE!3*8<+_Wd1#R>DZ4EM=`60hic2bfo%z%X5T;AySlGX!SXTm|7R-69X1OPrw;!a}u;)DUg>wwAOMdYNgbVKyY`Xh@XO#~+ z`GqcuO5{I+ia|lABtRfYFZAmH(VD@ffbw7oFK`fBv~w@D$VE|n^EUujF@cYrHxPPs z;-C~u%3a`2CX|Yst1Hf0StdejQDknzvylK5;yn{|C=X6*pFl3*zsR9&V-egFZ#m-a z(27uK^bL)z2>Sut;C3W?UL*vX+j26*2sp`v4&~;la3zwsNbumeYp!F`P218BEMm#C zz=mPTT>g>9HZJQiQJKY(PNJ0Wuq_8{K8l#H$B9UMO@evBf4_z#%o; zlrHmt##V$CHfbin*&I|i$Fd8FDC7b!SKaJH7f?%Lq!3MTEic?NpPESLV+{ELGYz?9 z8(KM3Ufq<)FScx#BsHKb`rKV5FY`AEjZ5Sicy+2ba1K72!VU(1Ch`+h4W zBbSJLHJF$6GVIuU#3?xl=qvT4{DT)tk$Z6Mj^PL7;X7CV4PA`fs23tf=s#mmD0oNQ z6;B6>7DY}5((Exp&3z~%buxexs7I7 zIz>Yx2l*!Y7x{jK=>LR#DFoql%ICS{89NOhY@POeu;@$YR?+FbO#W{7Xp7347xPM& z2bHU${qAK!?JX{>^A%(CzXzz}3XS#tu#9fe<4({;&q-+s)+}6~4-R7V{vo{w<`VuA zSGYZ`SJ56(Vhqrgd|k@qBJ~#2nyb$dTAgxvP7*MDkxZ};hXM>n9&r7d|3cD!OYKRe z_Qa{|rTu#}_fbr?CEHRHd&smmuCBkQCtrW97JIO7t+Xn|*3(3lz;|oWiok>BFG@95 zw3@0(qYM7}l>VF(8VLh$ErIi&uCqjqtws!pki@zdxt@!r!Zx`Xy8~6g3eY z$u2bkvw*+>Vq}J#E`TojarCD#AS||CEKYzq)}F5kT&iub(Aw(Ois`5qTtxw~Hj!_x(KjZl zcNZ}r<}}CiUXh1l&ufdSL;wKjd+`Uc%g}Js&QbKH>&Kp5j>j8HgIW(YF|;CH@dp z3qxZS#72TzE!s}ZrYx=JR9hs+|G=MZi>`NdZIP*jZ_70^wq-GGk)?!|omOZ${4x35 ztw1^w8V+6^z1sRg!kKQ5aJpEZ56;#er=g>C&YvxdF7L5Z7hy=rv0j*)dN8Ex$$4u! z^>Yv9erUFG4fp&f`ni#QeuaLjlY&7FH#(sn!z%(z&4Jb-^1lL`Tmkru_PQDRyp6VT z&Bx6AS-~WqbhuKS`gttx)X_;G@e}D@v(LFRhyOTRSv^K~RvPQHh>S*jAt)OP=-Y6s zEWKgQ`PfcUvYjS^H8R^80Nf@%lbj&a4AX{-o}U_duX|V$j2svYznH>hr@EE?{Z(^{ zwg*c)x>j&ADBz9HQvc5@hkY&hz37ju8P-hX@p~zOFf<6(^E%Hj5xzSstGW3!`ME!5 z=6S|8sbH5Yr|_fS|31SAC;OivO^y9_Cd==CPmH#|?b~D}5N&@3gPF#`v~k+@XPYaM zqmdU-c!E~6bRA_O(a|s_mFey8RZIvc3GgePvYGIEUl@$MIKgoteVr8!ol?Kj*uN7F zdm=a#w!4*f#m!1X;6L@mXBa&M{+_Jy+6S??I|K{{{e6D9;9)rVk0xVpQvTk4aX*Gj zvMrtbaf|BA7pgdadpE+o=7;$6(jsYi5%9Y{@BlQ)=9@<50^f4pjnZ=zsX8L{Z<@nCGpabgxe#7JLw? zwaVp}@kKt;>=coGoK1XiALaB)7=rMFc}DvqeFx|Y`O22d#Kl2Tx?~Vozg%GFOiOdD zr8%V)Dw+vYDuzLf%Nz=jW=Qf-o7bPO&NJf-L6XF7l9G_wTJ;55RdZZnXR9<5St(`4 zcOe`E#cwxF>!sLn^BZ&qzqAwrRutCb9GZqSc3v%1h-ktn8Mf$s&9 zlog&txmqvxJgukUCWZ{>R4s9%8lS1 zjNq!7Mv#h@`z~V}g1v;FSIiYnsD)U>CnGHJ^NKiS%W~!wiO(p&Q5(a>112a*^AM=G zX&uj1$KzS$&iZJ;8_9r=xlIBg$izq7bn04mH=XOW2ovJz z#MmM#eN$5UM&EekA&5YH->}8dbguW)iS>t{P6QoHrzW#dI|zJr&>GQa8LSzW{mhvq zWP)SoYxO z^_3>Yts`%hE;)T?r6w2b3}v+OMUlu?V%Kh!JW+@V;FxbATckP0qeh)_O<+m#R0s zI4Om@2d0jUh&7043c#%HK5|rhon`>_Dm5i}^2!6lbHz2}@dgY|A|#Yk4l92bm2T=# zQ*4#PGm}`^`TYpUQMbHN!+YXXJ2U5a`+;EI%E+b_rNo|ypMOtKy(0>LC8+)&;q8|( ze8$_O!In~OJg?KQZgb_ND?aA!{K!Kah&hn ze7`pay2*#8^$U7hS29_KP5K%O{oOn|odrOPgu#8#2W;B>{X6%^_wQ&gPfm@y-Be(u z4V4>*KSi~96TLZh!uSW{zxNB~ZH(?AIxkrLf=Gv53_i}*^GWs7Nm%fK{Vr`rnW>Z1 z-E-->t?$W;6wBOay0mth`x=rh)7ll>ZO2e7DX|v|)ULuA@F@l_4B~7IT)5AP6R}Q=@71AgJsV z#>-#W*sa6kI^coYJg>u>=Wq==yun#?c(ru@hN`tpb!b}WXzZ|W?64K+8e28qQgi~h zUf-gw^thpmdf^m%vI~7IJxdgO3kMgb6npc-PwU(HPO%4xkSO+CL0+*(iUT)4o)eX% z9vO#-^e6|VShMF-rPZHeX}R|#^F%Y z2d)n09US4x6NAcGQTV!`a&r{EEjX_D&a$`^`^~KkRWX63>v+Ajd#cHHG~kcJMn{ThipVq05_Xn8o?TEAE1#8$Kx+{ z_)vWKskC=BbRvuSS_WWzfqM?$FoB_@Vn&=X)o`@UhOdiD%(Nhn^Uh5rBoVcpbVE{` z#yKlnh~QHikM{Q%dKdtYF{QZWR z*2z@b+QJ{P2gn~LUr8O#R!?K(Gaz8dw_YhNPU;Ki_UcHo>o@{eNVM>|$E@qtn8b81 z(aC_ze|!S)=i6wK8%`?ti`9(}f?=9?x4-39(sV}Q#v7wii0Yp7tpzJjUY)d-A-2va zxz<0-jq294KGfH9lcsU$R%}(6G!3+EO&jprM0Kvjo-_?6s@iW&16#*UbA~|fjc8&-~=cbV3^^c^xAN8W;$4ek9#IC=`9O~gX(b6iZMe#wl9q6f^gs_ zhHS*z7TRu4ot z>=knc-!`ZAd|HdAx;Yno*3s_L?Vtk-K4a@Jy|X)KP#C)3L~>*;7Tt(w)?S*wl%ZG_ z2eQ)*#g_0jAe1%~W4U=lF|o+`ZbPx3D}Nz7G?eQvI5V*l40_3#iS-WExDzjJCWhoX zF%#=`W@6ku+-)W%v)hvdJJ`G+>;5Rg_;yFQS@k7t9+st4XC9`IGY=~xn2gNB%GrLd z)$-YN}zjjs}p4M>Rw6eYxm*u?zmBZi0QIu=NXLnnH+142NdAHS(b2av)>bQ(LUOiKL zdtM&3cZNE$=Ex0FmwmPz)|B;E)Up%l>zmmoT+P0=tgG9%DyiF@4L^~(InJ#XqodN9 zXcvEoi&qyq;=NA+b*Cf#7?)oo>f@8Su)D6{te^;oc0WW89>3uca8MI;|1d2#e||14 z>kbEBO>0~(YZHV=aG0w@CDeZbUbc#e*St$k%9+M$VCV6B?eu=RVBYiGL3^(7Ws&u+ z%Is1b*%}zSLxV#21P0GiTte#+GWo29@b8jD_1N-72lqK>TL|xnGt_M_PJPX&s*}_N z<)OSkiSt@Qx+YNU81;I=^%)s6imLxwkjMz_{twDoa}2QA857NwAA9AB$6mb7XAQsY zfQ!iVI<{pzhto&S_A6`N{u=e*#LebcVvz4vxp7t|^TzoUKhW}Rx{!2aU7`nc%+3ty z>I*p~_8}%W)!@r#io8*g&+N*1=S-3N6d88q z?61!d$pO*JrrKRO&z>praz$R&mGhRFBJY|h^1+!R7wkVnnLp6*&Nzs-DEkBAxKcxP z&rFd=De`AsId{$!`H&)i-j#FBni(P=OJw-RnI`wbSyP^^f^OCs$W)78rO3Tqk?&XJ ztGgoaR^%;Rkq;=cFxXk~504H5A~cnmNZ|a@Wb`uKO<(CZA|CL?T_v2X|`N}Sz@LyrcqIAB?JMvXrKB2_o zlCgBYD?0M^xqQNsr6udr`L689SIfDK!kkqu>q+GljUjopc=*t0hN)*TK^f~;wtXaf z;)wliZ&>STgH@UOt!%^Cznq3#MP6W>KlwMg$TdXzpwb7s(^X&I?$`MObVF2D7LhFE zWjTDo#59`ubl}2~{a$Zd4lhWeTAM^Jl*R|bD~NO{4?>9T?B~qCF4aINCxe-R@O4W6 zmcF#~4BSuqj|;dz1QjBT(6^0gu<^*7Kh+uA_nWxpn5O@4YhPExG*8=U-#;0>m2KTm z@3y}TCDQJrnX;bx#qfr`oO|lewco*9>^_RvY-=C6%9&kyY5h#cV+*aH5LF!Ja7XJ; z3>1W`BI*sCB})AFQx%K<>P+kJ3AMk>1nxeDuZ@NmP3UP=EAib_RR0>3T1;O``s8&o znN0mV3~K#*FkuxwXX>HvAkHxGJf(iM?xBaR2l=pKcC!1uisjUPh(7Hsdb^f6!O5pM zU4z<>T*}GQa^wBE9h;c|7zDt~iO-h7uz!of!iy-UPIh+_b%>=Y{By)lpHB6TmJz`0BDM@(DCE1!IkyHE>`F14=dHF=iIY}I~L`0CmlrWZ0l%x|2l~qb?gi}9; z&hjY|c#?8}rJOnSqa;OESEL+gDThw|BuSCRl$3KUWyRF**t30UvUefnGE3ojw2hNF z%ml@KlhFjl37h>}61*=7-tU4(NGECdW1owVOccE&WBet}KWH%6-)zu732!%hrh zQsC)0&N!-RSdQ!AfT><%X7cW2i99}(F_LsDXUu-{#@pCoG^CwJfnkw$7U6Vu2DTFx zoL3Ev6fje0Uj@7wP6--E+0K!-<2Ys@G)4v=XUACRtJ^I62Y6&IBs-&vbf`%;NT*Sg zUt3oP<(D|QU*r?f%LtNXEun__E=+LID)H2Ym7J{q4q)iZHs0SAN>l11?8&Uf3+ZQX z^D5F)H{+Cp!8vv_PUK9($zfK6?_ibQElow@R?@iF@aH7BeS-HMQK%X+IN!$fjh<> z5*wMJ*mCk>N!B`qeFl$7zvKlyj#=Am1zu1!W2Tq(jKmG&?sbd4&XHBWNff zCf}g~*={93Bs_wtqp?&VUP=qr7A@ znjBlOsW)n8)JJCEu>|X}?Pt>1*1Mnx`!c&U0i|N=5GI2glJ?_+H^m>w#H*|i4pQ%O zuDb^Kx~2&QfrosFuZtYwa|!}>Ck>d+`)!lleT=Tvuj24#ZP3m==rA(K#cHk&##IZ7 z1p=k&puaTBd>9vPzJoQAHD_qSMt=0o{EOHjA>JjwTEdfC$T>?wFYL+Bk)&BaDQ z4yg9G{upoBzD4tZ*<$N;1X9r7XoCv#)kgiTJZj^Hg=-T-wY5W>zTDpiLGz;1l=~SL z_K^-3Z)$!|UEGwKjsrLgna0JNs|TcUF$RV)BIfkuaWT5JsEBGaLdKg@$argYD>|5JGeO3+b;7@!Q}`F>Pr|=V zhkw_)F4`6|bsdRrOEPe>KED~S4(vJJQ&}hEt_co*tlJ;90)U@n zq>vSM_^tM!Z zyEHo9IlsP}M9KDE_pH5 z!qA>7&Tl|5;pq$G3WO#!lM{E%Xa|(&gXY*?KR;N#EwTr?C^+{IXC4PEcJ1Wjp&xFh(m~j_MUk`|pGfZUt7yUK->xi0@x$Icmk(wv@m@hRXjr`2+{M&c8mO+RGg zF%y1{1;OXUORW=0%T8f9yo}Ur%E-}KdpI=Xw?H@eIUDU{GtN0oi|j6kXXqgKW(2og z`%B!856A65-vNd-Ik-8V)pkK+%HbE3#kZhiS3s7*4v?*vfpr0lCC1IOji_fBALhU$ zPEc~3DRhm-FU;Pqc3YHD44`oFOUwn>qzA(_-K;HaKXC_ZRr}xmtRF6cbmMC+Oz(e5 z3vJ)DHAq8sS&vKFN^=kirEs<(TS>v(g@TPZ(XzTW7rGkUdXmT+2WevO@L1lBqt$4Y zDM{qAIN84Q1WIbDatuXzC7T>4uW&bvzt5~?HcJD7!bF~0G+DwO&^O`Gs{tNoM`5$Z zu-Uks7QtoiZb7`sp6W-kTLK##gRakE8q19hxw}PgB&m$>d{Twx#__&wn2n|yF_DW1 z3hkK{%4ZqFW!L+K{UnNutU@d<6jmuE{yyG zTg&jZGLM65ztwk(stXTm1U z=xu0^b8S2EGVrMPYb(p~B21~rqtlDDx>aDU{Vg_}nM$4KScm_hwHhfo`9BrDux3CZ zKfB2)k#FMq@6@ejGgKuBkg8fXLoNBJuVUKGlTdZE{RTSM-U8zp_i)g5iY-H+cse=g zHjB=7w>$-WD;S{_ZiH4&y_JsRA4x=0>n89=`G3MKO2OXi3s7ipo5jQGgJ`4%G#M)oQt|>q7V*FYLcp}r@;$Ehp*7I z@|qzL8e2|t;In)t@L_h12u@g%bSe+GZm% z@k-Y3wwZ>b+BMdPxUoKT+E{CVY<=A826xNMr6u^aRD;vfUvRc(e&xt4BLjD>TiQqt`uxa=>}Ms+MjQZ`8h-q|7d< zVkQdVkYv(P#gZ}!tvuQI#E*Aj;|hHJ+6&{JYPGZdV4F6~uhw&kmmuiyPeXG7TR-IYOn#8Uv8+h(ob3<(ow$Q}Nz0ZN zAzM=Z>v*Q)xujV>TY_C(vZy7{EXO7I*f-Su>5O&w)t5Rk#!#xt@a6T*>1Q zIW`=Y%`n^ujK-S1PK~#_2q0xlnYX*7;PCEVJ?h`3aC6=xz7Ezk_?Go;m*wAOS$8RC zTN=QKrWKFUPhO}{je{#OB?C&QSwXX;Srbxe%P8|ZR;DwV*WD|oJmR8y!G24y?*)9) zLG>{|-<>(XDVxwUNkLR!NIAG8H6@x%wD5o|4NNI1)DbCKj@%VwS|}aWP3f9?Qbk!o z@IJYg)KsRTa$2XHX{k6`g;vk4uEMlTsg(vRgs(VTX)reAl#YwDoVM0D`@TR&*Uw?; zqlz43o*c*X3qm_7*Op;=V7yZj#?YO?etht}Zv+dL^IBV943-1`-?OoTt~Fj+Qboss zWFp%zWIqGp_0(u0M{gOn<0To>?ZYM=A7r-ct#4`q3zbr9J_cj0CH(QhF@o*K1D+1O z$yB}6TBS(LiSz9eDYablW|neh$JMS>Qyrc~xq7eVr)2yHUwG$y3i&dBqVV0R@V^jl zuLMIS)sXG$icQzE!Rp!V>k*TB9`)$mREw`5f9qBiTWneQjj8ZkRoFi=AMl*T)+?1# zO4U|uy-OL=wQ<8ey?TeQ4HQ2438o@26VKG7moq!w>GMeY{1{rKcPW-zuV2()f1@J4*=_q>Jg2Ds{+Y3wzb*eMcwJwm*A zDjkI@ho4afvjFo+Q**EtZogtZrHo8HieVBb>g=oohoi|-R9AOtLgodHXQj(Snl*lq zxZww=fXC0>Lyh9?m+tW(kM_wDFd_hJAELng0HZKpdHYZo6IX>Z<0-3bs#gFx-!}yp zFV%Sw9RG`kW5-Q?gpi|6%Cd_QQj~P3t%GQ^y1cBqth3AewT>dG^$U#|49NADINlvk zE6ce&1)nF&@w!U%$XV6pXWej%+?t^NL}VnlCQBlj5z5YqdwexEd_(P5YW&8g{;2UA zO8{29YITXja&CSb!{N7C=$ZNkET&Sj6!r|W1rQGZzTpdQ^pgcZjHk1~`K0|oX}Q+t z8KmBP{WH-H=1i?5CU<+7H^)o%FwVo)?;v-jFzk0M3=6(MDek08sV?2Cmt0RfH&h0o zJI@VSOeC8*5waIbS8>+bNC)*pwKcm#jEiX;Ky&`wkh13O1C#I1d~QgYQs;)!H+WXm zpuN#H%2ucdENS}m=-J(`2e-Y>RW_%S_!W!jM-d)2+1>@~V?4a$`A`nfaF76_z>(fs z8`#Y2s~IscrwE?R4#Zr_WSFxrvt6&ks+M-TPm?NOFtc5+ttX1Q&oz+=@$rDqAar<8 z5MI12xC}|rc10Vzj$zyar0QY)78%BM?>j+rwUsjDOOZg1QqCZ=N>Oy{1Ms_63cHt; zGHv$?n%~hn*`d1})DaycMsvDd?-Kgkc4Wpm2#+d*{q_Cne!0rUXVhb`iXa?U3scXP(~j3Po%T5vw)+T*F=%B z3^xT!TB4YVfXjw^VH&*TNTx0bt28S#VW@YAT`HO!?NW#U+~jDNk{!7j&)_h!e{jH0 z&Omx{?_1i<0UCq*3En?>L+70rHy!><6P8vvr=#q`txOL?yuXS?!*ZIB_ z|GEHg$|6c`UAd4>SjeQN^`AgFb5^ijGq$h#;AZz7Ze`S3mswVfr%xB3|xTOIN7M6&}v3F!Z+#t@uXeODNNTvb=AROE)Cw+$elslh{5o&@D*7_{dXqvc zO>Ap@2B~oBYtXj{8IO&=|L5m!emsGfu72sF%}WRz(ev=Ut-qq%(!^Ty*1LIYUO@>& ztUdOKv$tY#>XKjh?Ps06_3Ke)6}I(v%6x*$yrE~q^ES`ttIw}Gef!!ItwhDGJ+oPN zIhMR@^L1;v^O!u}eCX-dZ$(GvN-n?h(ci5-wuxQ>uEx-&Ew(MUsraAqhuCH6swlQC z?2|$Bt1ebJ(Z$}VSXqUTcUzQq;yKQUD`y*tWt_OYd9%xPS)A(v!^(Up?CEly-MF0h z8{;e|G@in{HWHV9RO1ZZg}2@Np^al0|BV{|#%Y8$Sm-!Hn=N!Gp-mP#fY3n}T0`gn z3yl#v&_cuEUg~tyn_{SnfRWKX`}2H2aFK1D1q3)o^fN5D-r;=fb39q6T7IvCeoJgk z45-~%5dYm*sLhj1z`pap@^i~(R$4ySTLGlreWN&CxrcY|;Q!~F6tB0r1FlVx4(tGc!;f1V75%jS zv#@BhZ?4^#edC6xvWNca{uhH^!`MMbsjkjdTHh+Ymx~>C4|6Qw*6|>TXSELHZ|g+< za6m>gTsA`=L|Cmed2>3fTw^78)kM*!YH#id_6{i6iep9O<0$XzM`< z6?vv9+BW2buF`Atm268gkk$r2c|F4q{u5%-rtJ)pH@*&3`WOL#OKGhjtgT;JELa2J=x(`y>%HbvBuW7NP_lg)mPpw|F`}dBxVEx7Pq*fWm;#u6EZJ|(5 zLBZWvTQ%m+b_cfvkE;&ao@Q@bK0;0+v>~{N#|gU(hL6%R7lHdgBE~D#!McWi5>ZXi z1a66~+I1sKN>2CfE=L-CJ-s%tuU6~J#Rb4S)>^9w@mS5n79FHVMLLed&O{Le(H6k ziY_uq9j&76OkU@s9F=Qo?qs#ch&|f>3_mIZT^G5a{WZN0P>p3;(7sRabDLjduo$iS zmEuJgIbL*8>nh$HLkg40tsbVfGqw(<(M<3uYEkRC`UnlCNNhBh^3J>;UM}yIRBazP z%oLb9t|qxHtnh%b_Vso9xtJ$;xOC753oGb@|LlN1Jb+fs%C`Q`Drm*Nv(;SjaIPII z_8lMI#;`gTGhDYJA`%b|541Uis&7_tlWKM4PA*mn?^fRMA^xK28e=!AKMT0YPVWs)qAT2Y9AzLbsRnod47R`-7R-Zac(Y!SA3Qp& zfOTip-4AqT(QA2d&ZEN$xGbEh=m}&S{gC0y-00&n;lWgHs|q(&;bWs0C^Oo+Ajl>a zXX>@6=Ev>3G_z!))n)*Dv3=4VP;_KlM{YJXA;i>2xuk`e{K%QD!{cPN@NxU*2m5+f z$(AWRHyM!Qg{2Z6a)zXg>F|#5&I{J{=wt_bBoaA@7|1tHZR?xF?a`-U{?5pC;cQFS? zWPY|FSnQbzZmFs2|Mi#Dr+_)KZwNoF?2Y}yui0~HxX+&R!w2j+y)!9rowAtSNFs9Fk~4||R<7M5T64AyKW z-{*EalaxfviRAX*p@aXSF-CA;qk`_kEIpUcMUR<^R8mbh;j%htTN@6hrG|FKHA_Va0g@^;1S1X%gj$;l~T zFj#g9Fm#fXI%sp+tKzcOq@_LF+nZZ+O-kCM2-u2IJ;nm8e0I-Q7CVo(Fb<8xAxo7U zs3&*}^*^k(XLjjxhjqqyQ<=tMR*Y&X8XskXguce*UaQxe*`?xzkR@vC3o`9*)8yTX zqzFCojyv|nXLhL(C=}HKU&q&Ey3!M_WtyOB#2~s_aAV2!EN3e^?|3Y`D-#J(5S$lm zz9~7M_(VnVZ|axWe3+^Ko+{-}Js7_Z>Q(2+Txw0Pzm8vr^!o4d>pZ>cyqU{9%&U_{ z3fJqOQEGe82;#3sf2THz>6Yd{you&IqRgcrHS;MS>`Fm=;ZwdArvMBQ4f{Kr6DzcS z3S%14@LFe|rVYQhbbroNjqoOJqV7!i35u1wKl~#-O!*#X7s5xQ7jQPrWT}1fG6U=F zg>byl(P%riYR5GuMn<}Uc`@!%q+E^P-`{p)_K?8p=^6H* z+SNYSHEwGm)D&1Q>v~6~r$jw?+bliMoMp^^(>!P-2NCYrazYHd*;8Nc0cku!UmOhW z$HckNVnNg0KL<^x_HDh|zO8`Yw|~)@MX$DRE8w&IvNMZbD}$LwhZS&Hrd09N^H82S zu>CfD^I&T!*UXx7!)z&G@0$>Gbrlqj%@ldmOp#~L5IJ>qT-y}M=@$OotSL9nn)3WO z4{v-GvRhv#%fUZ%(^yCSceDe}gdB0sFiA9sG9>@eLa;qY6@eiVPw z9gKIN?v(H{WxuH_`&8WPu9Wb0W&eIx_UFXeyHdh|w~_rc{-hcc-Z(>KvMEl>{<#^l zhrd?j0dMapIN3?NQ^M`!4By$6J>F@%Q^M~m`{-?*rN=vQcS`tJa)#G*W)JV0DKh^K zavsH>^my!{oY2PCY$dgV%PYTsmvYGiFV>Cr-J&v$XI5RX+Ugr~^j3WCC=b z!5?j}cBEuETu&Bn-(lp+Kw!I1Cbc`l?CLm1Hcy&1LZ2p|8QxsU=pvDR(Aw;=%4DX7 zUcagegWyrC*U?PkY9g9RbnZt=LGY)jU3Q`;t-i|mmQ8D#Bw>>EkTq| zw(4$OxhTAv(r#V3U-$)kE)ECY!@KK}>^5lGUd=~(%wCMe9itJ;FxNR5W_ieYk|+%C zMILLQ4BKKS!8B~L>kz=`^g4IokR?Uc!EM(V#W#zbgSpu^ilpfbS~8t5&dYA&XoN#L z_!tQArL$elZzI2mk3OC7x%@8Sx1HY}eiB4U@pr5Ly^H5R@)OhWWqyd~gCFpFkl!Qx zXnA1TR-TLa?a%K(en;|q48PO)ox|@Ue$A)y$408sM4S_}an&G|{>2J^QEYQlS=&Ff z;He50fs{(~7zGzt5Ye~;&`J}Ll>?YF91ze#MSi@a+m7YeA`;+Gi2$ zYragu%Lr2U3l)Buax|XPzS*8vv_ECfR{Jydym(9^iuT?1a`u=6LH^}Lcf`S^Y-@kd zax~lDx99qH4rw1^h1KnX3oLF|2vo3(nHe&qt+dMtRs{tNbC4nFa7a0P3UK5Ok71Md z9EV8m$|01j6-H{sA@f+m2cvv|5Oxw-7?{)wbF$P=$KMyY=?OcX@ZLEQT9M0x0{2`j z3mT`8(Ekrng4`SwgC&cVav+c%a~>Q?F-TVgiL*mGo)&6&Jve%d<_OgL_=z8kev1j~ zCpn3J)MCFQcnbXWyU_nap8DhMnP>W^rSTzt*g)9*piSXZT@3bhp3Qss z)BcR`XQ_FY!e4M5C3U2lKKy)9|~&;@3JpTMTI zR`#&N(fi3y3KR->?LgpHebB%T>z_{FiV*#YD5_{UL#giP5`bPPDqen`}i0 zTjLW#*MA?~Pq~^?{d)Ng^Ak!tmEV*2J)PgP_`QJNjr`uo?@Rn__@X`;?za_iQ&aJH5IXBQDC^|QL$98WUba>?aeV1y^US4s|Ayb}Y9 zd0P7$=CZck9QJ9jILf%8x`C{3NV4{}4|2%k#p<()#Zkxj<5Vm+gJGyqY#*Wl7;OBl zJ3*}jH+_swsr^rayurrlR97svZlf4M0~dqYSEpIAjm?aO%h#|2*t!RuYg^h!yX0BG zwmQh-m(E51!i?Jvujk2Gzu+wPUB#aRplO@Dl4m-8D?9mB6hxR>TF#N zA)%nUlMqBYVT*vEfC2*A9gKk_fC-X-~2!SeA3)l|-+-(WyA4v!6$S)8x+y|1RB526p% zVpj*x=+gwAyt{sj?;P75U&VIEht>p0E$i@;&P(G}`w_R=#gnGIEKWHtk@E65%G%6@r_o=AyaLG(8#(6`6viInIgL}w67vmTei zFwCVECXo_-jp&SQ>7q0CrJ^TNq6dlo%mg|EVk&wfCHf1|=YFV{I-_JN`o#%CFN|xo zB=OCTIOVWJN+(V^F_CgboHCn8*%_x?m`Hh1oU)^v!f_Fi$(85DSEVCB@^?qr-2sxd zCHkppQo$qxD?dA!WK)TF>SB@^CDy46B+E&RQx{0alGvs$knGbDZgMgrlXQe<93b1c zBTeoA*}NTTZ3oDf?MOpAKsIVe+SmcIJv-984vK2~tCXrWNAel;n zuDU?7j>KD4pv0d2yKFWb5Bsv&u%{l=W<#WYn~g7XqOf&iV~@?o_nr<3o|bg8&&4wA zYo80Nk5~d{RUhShInTNHiBt=yhSE&5KYk9y&++&<3qR-MC&CYB(^ul>wfMOaKkvg2 zmHjW^=WhJ$#m_JB!`U;7nPv<>%kabFdB@`CH2gdZKhMF>W%!wX0e-bFn}AZ3`N6C3 zJnW}xxmLmR2l>pE z3ZAJ9jPY^7*!x^fHgC_?`kFVhj5cqlOzgzR?R)jRqxC!Uwz56G zKTELp!1m+xVLWyZc{N5+wr}T=@ra&4uE0O4Uh}|f)A%8iP@nA850&}lYlq(@~NA@@yvc z)MEtCA(j$iJvPrk$ImZmSKJPb)Y?4lKC(%NhL}uG$$AW9A9uttU|*{BLaMdU9j)P% zZ7>hz$Vwiy)()mWr#nblPP%X8Bz=1a$y5GF6M?z-nA&~?ez)(32U($(+73o8v~S2l zQ%-Gvk@F12T;W0c>p(3c`NbBL75}pmKv#7^bWtnk^6Ipu{un5OEi3$~(Y94B>$cvq zo&&tX|8iNIUQT4H+iovG;TspbWyyP2qAW$01(kcLtsL9wW%i|5#la1__m+Yz;4imu zJio$uex>saArrUp)z0%Z_E}sH`x?)-UCFeals9tAoAwSsc_qE-7T7JXl-2NN$~zhO zWubT3Gcla7j)aRnVgp^ux5kAaUPS1O%MQ{s82_2Zw?<3Sa5DhLZS+I@Qg41Z*jt_+ z61t7&0kN^YTg+to^B*Jq1^wwh$G$h0^x-Zy`Yeq2MS-V)oo(EqkKrKc_#?=o<@?ucU`aqk-k2>*XWebaa{&>*FJOg=)6ISqrj1IxajaTFO z>d%n#{|Qe!uEVdO{m=Ma%%)ne!Cx;M+YvI}r?_8&f4cJA4+@b!?H@la!ILLmhP7)J zgDf_3&OkmlHm(G7dg5QV$R=!J!qS@fSzsHbL!Sn0qljk+woy9tn7}ql=VknB2+$=j zCUA`*Kz9rhpz8w((1C;m=vYAl*Yd9+KrgRml$CBdBoA^CxKB!iiyed~xCq=XCBh{R z!YmNtyy=f%^F9v=4X71OQ2$&B)5XFPpex0E+hdo28a@zz z3VuM3ix?3N8`qM)6{P%L{xw;1yE&#*V#9$F_nb+I+sqd6Wr;GsCh`7eyvLQLM~40I zoe9KWp|Ww2=+wa?z0WBUoir?hIBT#-^vAFixxF1{#SL$pa9bj)FDBmaOuXNpc>f*V zZO)xTOuXNmc)u<2&Xu+@ z+Ep^rxyW|kxxTh7*;y{Vt%$!zLfjIsFrBo*LEvQ~@t3cZ-qSA&SWU(WuW}Nwv)N6! z+DTaECS2nr(7NN6mgXDxovUv5oeOUFooj9Poy%I4I zdC;2`;~Utni|$0pIedP~zou-NkM@2riud?~1M$KSj^JPO0rp(utfRBK3H7x#yZKHD zc4Om3tb6^m7|P*cpt4e{k6L`XbTb>uZAhAk(ilQ6qR8ZCJp~*$>nU}(S$~BTYjN~y z`8yuHS^n1Z(JlH=h$w$idf5#(=P}DjfQFh{?5K)HVp%_sZ*-Wu?^Wtv4%qOZNJnzDL`FhQdR*i?rIb+IlDYEA=bFyT>YL@387%Mra5b=MnJ z3sNSW&Qy~f%MrahPVE7IBnkHXBCe5Ll+EofYt;~y7%NtL$}d-&K{O|A23hZN+h~;Q zcWnnTl65^%tbM}uO_sXu15bUZs;rN0g*6~-tSJEbrNxb;Ch3irFo|cBuyU(LpJh#w zCYGRa4Umdi+*1!n^FtO|rX@0%7ROP>q94lhe4GJ-CQ)Rqh7ziG>8|je_)c=B06;SbX9XA$j2-kb!Pb~MUQTE>k)wAnhkH#prODJS{nr5+}drVQ548`_*Nuz-=&=ZT|-^H-D`JjC; z6Pqs#B9YnH*DL5L&h{!v*c3z*-r67xn~`fzOTtM(_i&^HX2`W)Aqm|!XTkrTHqR&G z$f{lT>O}i&L-`l{f5rz{bLd8GJd?=#>%#Mt6Dhd^3kS+zTu6@=n-!5BYKp#jCnSg% z-f(l+E>r;yB)mE7dVR#<{B+~B`V5+s39EcO;GeFj4gh_x!{4GT>CQ_N4QYn9q)>6t zl%o_iH63QcJxQmA_k{)S4Iro1Ha4Ye*!xgn84wM$bzoJgVGr72(QO_3oN zJ(K`GjQ1$>v7VYoi}4;ElmK!jkB;aCI$qw1l;~8_xS$t3dI^DVO#nYd;5QP$pA)#` z6dw5IN~;R#Nb#^!0n!|iipP7%K#DGzZByS_LE-pR6dvxkD5k#LKFi8z zRTO$p1BL!7mP$oXICd3xK9ibBWbxduh4G%v`?@Yi$(6M%2}tTb8kl&p+*O`>-)lHRUK zhh$~cYZ3#YBkARuRE<6izU$vUW4IfPc~`gOe7G+kst5X}y8(l6;9l81J$q%}dGx(9A(m>8nAwlWc(27ug#UDIUy8Eo(=yKTgRG@D;p>0M zTB-#80c)ue_y??|N??GsR1*eROErP(b890=SW6Y*QLLqk@F><&MHpZ$)r79KRNwyx z)>8d|Du^|&MwiICHeoG=c~y;~<^!xAVq1$?O&gl zl(E}1Zl)Nh57!G8-I{sSs5NHHJ@X|tfh=Ri7PHV8xfU5S&|+ho8MhXzyjr7_zUWV` zO@i`ipiP1ak7|<`Y*V3>do-JhE5{}8eb@-n;xjPb)$znRq46#&X*nk#Vlm{b;`Aj; z`beBEix7tWiz#p&_FkmdB@cUstQ%-B)p$EIi%k!o!t~T{dgz)CpgWe3LHm^qWRq?_ z{n1PZT!#ztm5O{&AZA6K7im|J#D9W{14{C|S)Umb0%c~|EBHI%v`@P6p15&%uN&QT z5N;MW7B!yqoGv(C9#fKNI1vicojkhK7d@Ku8PG@qd7QHkc;EocPWQZcl7^fYKMUWut^H!sNCJP% z+>*d|4#4bmkDDja?&>&ZtB??+c8fFH%qQ6KOFKuQ9gC;&+s$FnsbOc0jP07+ow^Zl6Jh&6pQ zjwAXG{Bk>#2Xp&?9~*=z0RfVq|7j5BVPEK{=|l-g(s%*UZ^bXSTs%132mIY2Oo=Cu z{Cw(*13tfS0MLm%kfgyQ$k8qM#x0kGBS->&PQnRbHyThfMb0!wOTN@gSOOO$foB6L ziP?=SluVI822o9l$&azg)Ix z4+7HU8Qq+~{30lhUgX1eF-47hv=6HAa#U3OlkssK+gAlK+fuYKu*4WK+chU zK+ciwL@9;m9MT8mbkPT-@a+Rq^7R2J&-#EAP<=qkkv`y|7|DG=j`=GbOW$#pt)IzV!rPJ0fJz3Cq7lDe}u-D6z>WN*61x&+AHbPquZkiF?1 zf)XHm(>(+wKuQK(W=N5AEtU3OgS8Ce_G2xj1NRWDoU!kZ*!1m-u%V6sFXyFHpfClS z{jif?stKp0lslM!wL3pKPg7Qj;WpvyLN9Oj=y0HH4#5h&L&# zNVzl;HC=y?VYwdy8{s2fhqZJLWEF9#vI!E-eY#=LIZWsf&pngn zdpPsz9l$n_Ra-I-P=~h>*@==%x9&`Aese)B*M)xv4a(|&lmdP(Fl@+ey`G*IrY*>|QKTE6K#z9)a_ED!RQRp&5heuWo3vbpT5L|F8n*#Rml6Vh zgCNR9>{;URL3xxsa0@qRxb~)My75_?32kx7blBQ@(#N~s^9Bhu$W>E%TSSaWTSHpn zaG1QhxZtywL1=gy^2hwH|dlLIS&1LxTeny{$VXF0GJe2VFA^bHTN{H8@ zhp?~aL+s+NUIGi9r+3gce#(zws%rg`57?cRYc>!*swkC~h}Q@aRcQT!MA;4kYW$k$ zLt&{SaqND|q0g<%suz~UIL44pVjwdiugh{boL=aL3k!!@4}(AnfB!(bT1zb~V1EQ` zDvvUt9dTbbH;+?Ee8qX{A+=( zZ$uPw547%u*qeE)ck#qdd@R3OLlS1!n|>F%AAIlG0$xzLds?%o^+t^MClo5s^|;`1D|Hd^ zxTPS@qV=_L{k|;7vTPM(-v8T0-icHP`36CjMXDe(eWSVwgX*T3MOLx zknI$+-f+-P3Dn3WXo&ZtXx~O})Ym#_O9QnI2^udD8;PkoXx{V((f@FGi+KW3g;u{C z#qQ^cx8MY4%o7I$x@;8k3@3y(IA9+F=ZDSWq+YpNB z#+N82rxD|OT6IG8iCrT0brGD6FWAWyjYD1P6#P9<=5U!B1PK?>aq5QZ*nlf-cUItR zg8M%?mz48@-k4#A=_bA)VdLpYU5^LK8~dIkopzCIlC4q_=ck;+t8dWZqCEZk-tL^j z&QZ+n+&PNHId_iYam<|~?0u|$KfXrWzn8s%;sr$dtbMWDOYnq#kg!?F@I~^)!xl-L zS%<$V`;rT^$jA9u5a&z3j^i9BlU-x-FpV!P4Hwl_&CZs#Rm zao^?y;XA$hCkJ&$uCj>tc6xB;;WCZxbr&&C&ifu*+2CwPpKe|hhD@ot6|>#@x26uv zcCKS25~?Xv3$mSyxrZSy<*ddO<_r25mB!7ha;uhHi~=U+DbKu|?99vcC;1l7bmm(; z$C+>Ogl4|Q)0lbZ=9BdF3G}b)Jm2OcO5SsN6eziJ_unv3Dma(UYrOQzlPna!PY_CO z;EkwMjTpu%kA#mQ0e?P@KO@*{+`~=W0`4o2t4!bs{$APDoxXz(Y;tj|R8p>?JGnpe zrz6u!$;}*2bxNz$dIl1qO3mO;dog=v)V9cqKLS9=5!FlpQq`cGxX1y`sXbCY5$UaG#@nPx6}G1!$D@d zH^MQn=IT8d9Q{4^dfqt-ovZZ*JTxxl8yO+p@b?Gr_YrXMxf=;7hU2nlbb$R#-x0|vlOs=N7&1Yxj>QiMf##cOfAH_Yu zFxdGhMg@25E!Eqhane4JUsb?x>_*1}yAQ1L!fz~s`0bSy zx=(D=<;ttOfE3G#-gFTrkCOf+R9NoWiNDW?yfX1BXuW9MgIgWsGKWXOK^&e0PLE8J zVtyZt-{9pyF`XV9DQXgjcXD6G({w8LegqQ#sNBO<8m;?T6kfqruIA+mwtV*rHjMo= za?ExP92BbJ}Yyz=QA^$Ri?s+qZGZ4tXdlqD6+KupADx-vU_sR+YEEN(g(d zDjyH3UA>6Hz5<&A8%3pLEejDNa=U7)f3uL8!uecnUalWc?jzF{u|40V1yQG@=BwDA z?}+V#RmMD7Xvbn3p2{7FLCuJ|4aFP9c(;Iyj2K@$0vWTIO~xe|#Q0)IjAQMkVthiy z_=H@vsbYNG!_8Py!*RW2D?R;LnLH3G!PF2^YxUlQq*E~tVWnamx&c?B`*#Jdit!~P z#tpbD#+RTD51^6t?yXB;2*7pDOH?W!&_0I@fF)RKBOjL8JPryEie)>2UD-aYt%oos zxENZgEK%9Mq$RR_NlRq=l2%u?9|(DNV7sheej_2x&{L5INhm&eMnW|6dRZ7e4!-{HURTE3C<>9Jus$LIQ$u-ui7|RmZSYzoCuCUiw zV<9p1?P}#PqOS-K1Nt%HVS;{`K}RUdaJfZC;L53QhfQa;LD)^sHiw6YPa@!MS~Tu( zrQ8Qz3zuu~n{b8vZM{8@Ha2P}Wt}Y`u%>t{7GNsHPYx;Su&~?<1>~qj|OWk>SyyfN{0=(Nf8yO(lU0$`Z&P~hUE>Mh*aCw4@LD^0DXPp<9Z#-)-`e;iRKnK# z@vHhs3d(xC21qlWk~hUX(`~Ajm=mZ=V*QlMT167KGf|B+T4YX?$g-bLwLc`nOm2UY zbsuf$zoLGA#d27}8`on6DKivx_af4kGoQ{1r0K*(yZBQ8$ALfS;u`AAI5DQNPB}bi zmykozmr5_i-G|?W)Nc&|hUf3$Igs^hh(=qfJnoElV9MkFT%<)i?3%uo=fSeAH)YESq_e;RV+u^ z7q&;I!TZUCuE9d}!$ek>to;80zH!|=~JsWXC)c80aiYKqb_vk&# z(9aj5b&b1(0IwVX>^wX64osrI2%kr^FJ|pD+N}xilu6L|=-}`)GWIG%$dBTK}I1 z06Uk&gp=sUk#G_?)BtciL3l3-CxD$OCY(r#&L!a_a2*LBnIH@uxDVKA$ApvUSCDWL zcmsjA;+M;acMJfkiAS7ylITAifF3=Uh5L1a@aWwGfJY4it{((^6KP(76t}SNCT0?N z?;zk`2LZo8n)`jTm*!LO-UocaAm9yyfWIZp&n9U8hL}mb0hZMK& z*AX)b{KO#O{eysalVez~8YItX~d zARwawMVBQ$-^^A?0$7ov~e+uHz# z;=K=e`XJy1gMcq&xso)m7=YP%16ST6o08}^5dAj%ay#bULBKx_0v5kB;PcnBTuGWs z24Hq}$K^_*KY{4$@yjjO_Cdfm32nED@pT<128*pjLVfoe~9RFzS~=_I^O$$ zM-2j=F$j1U%ax>g;Q-9eo8odM(O*IIoAJwSuiFO!8SW}V?2aDH9}qJMe0UHr*BpRZ z{N4cIuUM@l&4UJDcCzuXNuYOr6U(ziN^~L#or_;?gtPYnWMqoAMW{R1#N zrMSe&BKY@##s?G4yLb?A^&sHvAmH&VSCZ!T0hpa~T&^Vg4MhJ|qFf&t1pMY8;KPG} zn^~?T&C&1o13N=;xsvFI6a7CD)g z{;K_80Pv^*z|L=D#!b>-fH;XdB7kYa_OThAqU81i!N;(AlD(fEOCHb zF6ffN0aDf1<$wdEny#w61Ek)qYPbWWirojKI@|}OlG_KQLfQwUM%f3X;-`9`lRGsz zRqPxfRWsGp93T}2)gK%ng`P@B2S|yd0>}Y!xa;V5fE;T&^c)}=UHRStl9!aV93c5X z*+PI~U!uM3zwMomFo+Dii;}DLecfw7=5gF;jVUp<|EUixkz5~I^%-&eJ{$iD~Ig$%I(_Bxke`)*`nZR(gHi$mD)H+4b z$h53$x|4*i>5fJn`&qv8Ntlje|C6+LEXw$JDPv;;C`)qtDr^iCI@_=+4dl6~hD@1bgGL?N!_4aJ2P)yh$-WFjq`Zqz5MV_FTut7&V zD6bO~S;Gkm!v>)&XE-RA3W_Yr1ckesi0i|f7Nv0--_Tw-MexKXA$Z)bbn)8XRJ;QO zPb?CGXW|6K^eoUAb50$wRqC(2JbzBCkubU<6fr4 zleRZ_R|%dh<^|8(w<_h64lsDn6g*jL3ZA)MwS`&=2e;4D`aVwZWSu5><~CKq6Xr5_ zCBc(*li<;57UdH>sjtENF*ZB{UG@l`x`Qa5)YssBT<~OFA$Z&fwe+OE25*<(iJmWb z>LIK2q`n64If5suzu?hB7WAH%tncxH#~DROBb{R{o?G8Vf+uRN(Bmeo#dGWX2W(LW zs+9^Jy<#n%Ti>q=o~V6-N5>J1=hpWo!4ox1@aR2a@!a~pQ1C>n5$?UC%T*9(Q_7fFH7Pb zDR{NLwq58qJLbrZTKkhqup)@Z#fWs9wTLMQq8RZPnAd{nq8O1bvlfv(5+mL&h;IX; zIvB!TcJ=E3>rSVV=1iyXkBvS+eC#m>yn~TjEcAihNat2bAvSlNS5&btd^BFT=c?YT zkHelb5@eGNyVRPB4QI83Nh|TH#8It1H<`o7wf`)&Z(;)^`WSNV3xs0hlDPhls#tIm z?)QK6z2#`g)tIMszvFSxZM?gFhkKM7$6{)2jG|^Y^A%nSk5u^}5o(uph$y!NsO5$i z4j3g~QW*Y!a0=u44^C;tDU9UrQe#|x{U({hfc%3~81YF{7!%5&7JUT`CxN0IpoFMb z^dBJEO=0XKP5E`4B0gBEpt=`AjFLThgM@YKvu}Z5sy--AC6voO;Vp>JpDrP$`}M7avGZQfSf`;}7Sja!nNLa7lI za_zN`BQb<@`P)FA4A5LArpq$##xHr9{`;25Gy`MFA(U$tt!hHK=JZiQx#smbN;MwB z7?pkC!$`$%oeNEL$K&zLd>3NBZ~}i%lkQ4wC%nbmlL~u>ie41{5Y7zkCxX`H!ND4L zW{x*^*c+k`1NRznqlVcJ)=%F9;^N069t5=++uzoQ_ekk)#eFZ`MPCHk2Fc7WKB3^<{zRyPjvEsKWW27h;7AmhIrBR#v)sC!sFP}sqJj$)+UyluEmYWqRIFB z#FH_kIoWa?-K}H$@|cR2gXL8JLe1}dr<@#HZ(usVZy@RqoJ17s*}&qc^09JyE<7e$ zQ)n}6hUZ%|5NDUd?*Rh}>>?Lng=ukElpC;Ge?a;&Z|?*mg+^z9DZ-3$1+Hz^_dwsh zd<*Pbn0oKt(fno0$Rr#DIXLyL1K9gMMgpvmVNQ6yfkJwZ^A?@v4XaNKp3m`yKY>0> zc?WpU*v@d?tZ$mW-`-kjkH-FNil7DQQMd+fC(_+D?d~jKmlB4a<_)x^oZ^`qXuoHB zTKRq@Xg+~7z33WL$(PdsDWTtf=Nxq1m+?!-!8-qr^fh36>k!)j`#0OaLu-}{MJyJemt>nqfQMz2Jj`ha(@m~+4F}B9D*W!Bgp%CY z`aM3wy@(RwEBui}_>wNfWzjk+KEAgaW_4I_5epYlGb~cu*?6t1&`O#1Jczqo`#Sti zZD+aL@4~Px!-LNATYFL9N--}YTFzbm9DuM?4NHg``Zx9j&SDbQfVXeJmtU}o@W)EE z-ixR9ofz?;w6|6Pvz^F7mWA8Gva(yY(MBOEnUPA6p3J7Nt;!kR3f8%+ccT5gZUf=0 zX0AUl3;8C$+wXqoyG$2d<_Ib~mvP)R zE{zMoxt4UW&=2j9^R*bo=sZ&U4LbjFaQYF#>CATu_&Z$u_OsB4j_E3ell5)vX9tb7 z$jE%D^NTZ2kzi{Z+S@tNB{L%B5dG|o91^@1@g~4yal7^7T)YpQBURQ2olA&Af8dyo zFZE&lF)-sY`R1AUQVa7=QD#1k=j{`vZ{=G_VwO%+2`2fwii0}r!{&f1$Xxs?1CVmQDTv2g+Z;8ZuJ^w{6IK@1$s%1MYk%;)rf zPD_5!d~21g7hMRx3e}f2G+JO79Bg#eIo>nZ>zEoJ*?AS_muOFdQktQYJM{6V?W3EYZZw0V$^*HDS3NW7QE>5M|u|rEog@YnW@|VPL79? zN_aL2hp?Zs2!y?yH@esxqfRZ)hk0Yj4|8_PTZ+`vjNZ#!S}#R@dPqZ*-`An~vGNOC ztN)IScJ<%=0XKc#C2=3=ka>HRV5=vtVVb2M6o2N2QtJg~F zNJd{FqET+o?<%)P{N2j!a8kLo%Iqg%WtL`eoI&X-vsuhO4F0XWw%S{CmYshN@=mjiBZ+~U<&sjka@8v(?~;kfm&rVxgq ze8Sx2ou16U7~RQP>Ud;%Ias%G8XiPRE@rz*a+lSq&z7>eY7T0RXv*1C1A8K>DVHIh z5T%@|G7c$IR}Rx!hb%wjS`WtRY@P(L@pmFs#<|K6 zbCn_duDVkxWG1s2tI$FXUHwW_$5m)UD75pa(1x~UGvT~_Dzx)>E)@e3N^Hg|wAd(7 zg?7{^w4+*#O4TT|Rei_VuzVOwYq!6Eb}oB3$4FrN^YZ3p^X;qf)II?`U#UUJ)G+U! z!tyq~g(p}Uv%q`OMyum~(p&RqtK-g_bKvSZM&IqsdGZ&nzKiF3t-hO)xJPHmI32;i z#(HEWW0)~%IP5hIy6{Nj9GkWfX={-N3q97BRsrp*!MMAuaS4!%sjQrPB+=5|3Qi|w zAGAwsRhaF%cGRmjy$a`Nj?)3U?@P^!lA|;=B0~*-$f8COrsDAcgaCNc-|+;%yNbv8 z$lg^v%)9Cx)WtZC?bos1@k+G35c2lU$B;e)C3p0WpdG->aJkikM8BBNwS}EijXuDV zw5|cE)*qnEh$^6SDrNLG`==i91l%giO?L%(`A><8p8Ol!3BQ$`Z>)l#%it`Fc;6{_g6? zuqE7q_D1x#T+y8{RjkI6I8g3#>H8-cl zfT8xQQxLoRGblnCv-C0~i~h=$?J#XpA7DEe=_ied8RAAzs$`Td^j(K=F}e@(i-tYTxdA!Oqk0ZO2c#3fdipq+bocKA(y3Z~Z5&LxU-tp&!mX}44kn$w`+#(^R#zPdlkV7k zKspJl|Bi!67v(-6-FDR@*TJOEth&QGKzdNB`;P;p17;ua00K*NW^*u)Cr~^B9pGjH z#U0N9UPYkz!a2a32o!fS2l({_ko+oKC`Wco?gHe2n24jCN4P*b(?{%l7f3Jti2dvW=`tU&OI;uxxg&O|3#1oy#NKm( zbYYIzdoGX;tr2_A1+sgh1JHghklhm@4^kvyD)_hWnI`IGr8aDyL`(~Z2VRf#_qS2u``?pr247Wx6l;G z7rJo!N|-u|&7o9Rqce+1&by=d4(kql$MT8)S^HD`mX<2lFmXDPkqN({?aJ6<2yMNV zGfmgjA~;a;?S7Epybd@ziz^LhKj`_GBl@y?nr0_$yBM^LZa{GB@7yaB+{f4}6P(A` zD-)dmrM)tt{V027S2%4Gt7>PkZJutmFW83Oh{oL^P|*oaMSIPH7c2%;OWLsta_%)q zShApSiU!c$ZHah$N3c4f4J#*00<)^^>TRpmboW!(kL$>HsqHL)^rO{%&L+HfALXuo z-G`qA^0DY^gLNMwwPM`|eE9?_7SvnMWMf>}1;{Qyy8Q^4zWuMPk|4x=UcazIe; z!FT(R1J_wz$L73tuaqzFU{dwgqu%wk*Fb|@xU@Qc%}z+crFUJl04n6+Xm8v}yU@Va zbzx%`c3>Aae}Tb!-Te651r~N^7q-yCs@-oFS=i6Ic#AD8>f+%}C@tR?x_I@x1^&PR z7Umnw-#fsf1%A*0mMrio-F)h03qe0OwiY z2b>~REbw^_=7|l;s;1v$AYJnehfHe!e&H;{D;CCJ1d<%TGlluY-e9*yMXo1gi zFc(?iCmhVh7I?gaIc|Z6JHWaHmL1@P1&%tv{VcHI0Qa}RGaTR&3tZ^{53s;bI^|qy zfd@I5%PerCgL$9@UhM!I7I>EP`9T)=RR{B63%uC@HZ2fAwqjnIw7`@@bIJlQb%4t) z5OJvD&sSLBlN{hm3w)*nJj4R`cYv!b@Ls1#hg#sz9pK|E@D>Mnm<1l|(0sfFe%--5 z+yW1EFsCi>CI@r11>Wxf*I3{Y&gVy1;2jR;krw!T2lFTkJiq}SZGlg8fX7(i_hXtn zpN@H@D)MIMI(d99gy_z*@mLS9`4%SAozKMc1owL+hQiXjPP7zvZbVA^N7(q<*}{{R zzYF8^ivjhj9iiKP9R&H#^?-EvtGeU4-N)zgk=axw?Z#T6S@i@E1lt-91<6%sFPu^;~cRtHf7~8#bgMFUAd*?a!xn9`4^IW`)X$Y5{7chapYOVWN zuxI0O+@x(`8h@Fzt$3^#OyY%1F&dW6e0Kc)3h2-eZNBr*tp;0!TE{^ z<-ExN81VO>x>?sAjK`w-@%T(_cHBh8;M`7S!Si%~xWk@nzu8W*fYV+HOOW?0pruBo zoA8HSl&bD{KK~f=V5ShPz8Z)gg0ttmEIq++>oH2YBA3qYg6@IP9_9ShLj=LT&FC$N z^jF<+8EFVp?6?$@)50wGhCI^?Hv^RUH* zdZj3=RO*n4%FX>JycDWZA1(?Hl{(~}anIa<8@S_!rd~9lvUZp&*+;|fng2u7+rV+Q!qgO}C$y#u!pwH|4jsCA1R{czLgjeMq3Bf` zN1$22+D}c71IQx{4sbQ(44^!~AhKgjI&*BJse}jL?J$!I6)XbDREafOU~1$wQI!SU z0z+6?8Cf#S4qlVe3pU@VgNJhAbad@Gk2QAKH83)`{O6~yy8w}eirLlht3f`>j_J%y z3G9v9qG0NQd`|g|o5eLR)uE{GmeQNvO^UhvEJvz_XUidC zYZKAgbD^P&@sajz9%1^r^Y4BWq7;T%^bj1!rtU!&bF`fy$oN?m>G)HdWx4qEbrbLg z^U%~7I8)ze+N(KFa$JO^6A=WlT${Q3X{j&=_caKlH=)T?etAuV#p4~MF(_rk)|6jG z^gkJwu`QgLoJ;ZSr)LU&kxi3jy*T#;D{`sqb!I$rCU`I#Xv=W)mpzfFmBKXquNcf} z{>*~2acXXM!TNHZV})n9(%ynHHVRQ~?jv?Tc?DZg1Z*W;tz;08DO)Kwp9&@!-^fl# zg=KAk4d+xcc`48=sWJkZkppxi!<>ZCA;ql(3(BB`r6C%IAT|+-%h5mej(9@DxSz~p zz{t0hOIQSk1#URW#odiP%6gF7Y43$7DYT)eC_zbP;0m`IG|V!8(7ol&vc7p<}|#iNct&_PFSvGZNuDTU|1mK*>Lm9#rMxs^wrzGSxz6o<$J88C)D3H&j@GvpjroI7Bi9ANA z_=Ql9r0?8$QZ6%tiO&)Q*QS&JeEBWjsc_Ia#?yCDq{DQo*gVn#a5xz1;as??vFeRq z;#U!&%(W$2`ifNPg2qjf>cLTn`rI2)hV!)y)C#ZNg4QuiTlRd@__)3WI;DILx$2FU z0_c>YmU3DNa@p#x*-}v5l@rCHEHV!F7XYg^V+13V@LFrBXqwHrFopdYkC)Q&Bx+Q) zaU)6vKj`7$ZWbZ#)TrHY2eia0NZ+cR}!&s%*fg*I=@2g6JH^e z?}j}&&IYKpKmgr!{vOD84jjx295`m#1D$ZqWf(6t^nf;NHYb72i*yD59@`znvxYUv zm-Ic>^^Gs-jhlgF424l&w;>qYtPe9tU9fp(VlMt|!1MTKLh8f(Q$Gd}vvnghuLsSoq}eJtnZ`CI_bY^R_>k2@E<7?yHtQi8LuE=Mz z=WWQwVqo%dkl*%8#BelOaF&N|3eiiN1>EZ+naz<^>(_ZO2A2;=z0>e4kL~50LbUyt zqQ!+{CUK5J6k=`5#ye3E&Rs-BGxAToSLJ&fK)Ae4gKTAuS$X60PWPW>3EX-Q3aX*fkK&t=jk z6}FEbi&}XL2;1`c;s^5_wgqV6gV|JiR)TT*VCWJ>y$>Z|wA4Hq^O+B?H>{M9b(q;z z&d4;6H>?46RbXF8>zM+I#}eKSH(#RaDqqptQy;^b7qefGk@&M(XX!j0{Hvz5EA?&V zfu@5O^>lF6$aU6b9oiA{5T!KD1)k1$pT|UX5$lCEF3=RjZo~tMvLfdPrcqd*srzA# zD9{=~ls~Hy<+uPtwN_6cH?IaT8LyJ71-?E(&kl35l|r>r zs$w3e_`>=)CDGL3=2lp^$xar~4XU-UP@h;&C1wv~TjN2}D&rO`9MPTYHJe^dB=6E{ zZBM00kvq$^uS(-`IvbX&SP83PBP<%@QF(d?CI()mm0wi}{BQ{VeJr}p@0>>9J-1pG zL?-nYNL`i>i!wVc!=lKx9Tr3~{V`@iE?*g$-bBjUl)T7c>Yn-pFain$Kvft}AqsP< z_A}_fDz2iiJmWf7sVoMS-WH{v%YXuz=`SDyEj-!~Vjc;MAg~N;gm7qAZF6M-GQtXY zLAVeiipyVONk3L_5T`&@l(qhh=n)HWv%AqQbo9eu9BU|B2#+g<13! z5G+WnAVY>WqRX=YEGQPzQ$L}Fq-4_Yh^b1_aQc7hC*E}D5iIdBFtz)zoZ@#p@jv0^ zyK7TzZ=Vbr%<2t5SmcgC_9Iq4Wq;bEU0|DxRWnU;c~Xpi-c_?L9Kt&%nnAhwJiqlG z)Jde+;gBnD#f#tKZ;4M`n{EA!$r&AQI?9&ASSV^$6JxvFe4I+vuKofQiPBa-S(ggT zX#4;O>2z!5OUC;zl)3e2WgZigv7xB*P7VC2X5FBsf4%AVgO1h&ir_3Bof<%gl4`*o zrFBWc1Mf$yRe%G4Qq%$Ux-r1EBdY%+I)zl@^Z_2FBnv;)cE(0nRrd>Hkh3Z`OH=Ibq> zb&0mAXmwVB&|-3sq8-5+JYaY%=5QE9jqafs`CTfhsX!BF9E>J{y+;?J-1`DfEsD5C zzA5jS$T#ZgOY}uS+a4bCPtdZHyE!iww2zR`NEQ83bI8l)TAx8}3b`T<_vNIDR4tg% zgEn|H`CNSUbVM?fp!h*aF ztlD6H=B4p2JlAH;rbFYuu?{L=(!B(>xo4msv%4BUU@nrL-3>EPx_Jyv8m6(ykqa~H z$syz_s*8&t!bq(?yrP5~N%0hBRL{&cr?D=86vmXpdgQ5PinZ=TGPwf`lNdmlYNuWz zs?aC#xmj?L@8)*fH(hNo(duVu?`BL>&lNtA>`u}?v$ zcBrD>iiP+KWnslI{(|I+bbVDbRUdB78E?*6P|5lA%_t#?%0*5tD@}!!C|2b4RkKCh zzcNzDvbtcroa_pob0yyi3PX2XMmLZ+wn@mua4gN2Tx2 zo74CrfF+fJam!;6g3y!W0%fgi6b6R^t>u9NZNfQ-&hitK=W;1e>*pXEj~ZVV$bU_?%RE}pYQtiUHSr&Y#eR>ATz zEE-vS8tS}V_^yV#6?{{}RBdL#hT2A~kArpfxf|lD2V($W4W0Z8XctFTuar@nKpRn< zFkjWSZlGZTYRmHPv~|+u^-;dd^7W z$HT-8!=|Eseu-Qt1ZOn4+}YJBc07PzJkA% zYz=`14ulyh9Oh4Lb~nsu97Pu6MqC&OA&)F5mN6x#xi+2pno&3#Un3Q?5Kn(8$717a z7l)PD@Hb$d&W~@Rp^%F_hR>Tv`y3Wv5cH%f=2+xisBPK}lOUoeG|$6YGXAhc}ljYLdaSQ#9h5IOMp+g=383H!}i23 z4}`@P&wFI(H>7-KAxYfvrib;l4qGNHsSa$u0rr`l$y}cFh-fpM!$)q8@&&PXz0V zX{p-SOjgVRCHSTy&vHhLA*g>=w3M`nWn>8MR?`Ly&$J$B~a}{K41}%o~A- z*WC{GH;Ijth9-aV4L6+0H=owxBS5N*By=Ezi`6?}Q=*N%Xa)lY4Te$~771`E?IjpW zd9ZL9Ly7gmSw~p#a!B8#MZs-(IKJ7u05e!jqO9Yogz>bXT3LuSBFa0|TQM(OXo?Ca zgm3|h3KIsd1!93h7+q*1vlOYsI_}lT7_IK&MMXw}1!CeV$_CrQjoF!+KXdjNT$+RtxrnLn2NPWe%t($Yz+J26 zEALb*bU2uPH-?qh+(@x%91Eb%fc-Xt#WtS<{=+p~C@{NrazCzzsC1(R(ju~QjjyA` zdq5WC#5{$NOT@q%Xu$r3AujJ2IrRz9;W+(g_$VkeAC^#bun2@3Hm>$T9_Ky6{4N+U zGv4%R=o_Go?#Y8&U`Vqv3|m~@2f1Fy8b4LrG)!B60TU%&IQhXY{j-!mzVV!{Eg|h8 z27~uvX;aRUMOeLRv!&_V*&=>!*S2y#zb=)<=42t24YuqFuq#=}(QwCwA-sTgZG*#G zkm51`k`D`nUE83ehnb6N8?m9;{fKRkJr6Dn1(Q0X+1tbjp1QS{g!!;b)u3`}hq9by zF}CdC@Zl4*FQdJ}vVnCO%ZI|KRTwkdnpJ2ozq)H1c3#}d^pVGC#_j8=?5lTn_lYH| znKK9Em8zf}!;r(fkjbT=Q`H`K0|YCx|FEmYni9gN+|~3o~2xkfAn%C1BA;jrk#@VC-5l^)XqW zv19B;GOLI)M3|Y_ez2cn85XymEDOlxYo3M{(}{$8ZnSCqQtd$WB2?=SPv!Hi)Ld(4 zE4%!bdBU#jpn9E$25}T{T$YLfv$QNOiFwDSaUb$|iQz@GgDn>xL14BxeGSsR6dre# za_M#8O`p%(*5$T|sre%CxS5gCY?d^Zj93CK2h)L7GeG1H3D^>LN{-1ejRsUqj#1@H zj8TU#KbJz70E6W;l;s@Ts%Z5!nr6)Da$;m(l9frG|%9y6INKIxgerXq#>D*P>L}Mr1!B=RzcN<(XajF;Hzk=28G0M z0)rX6IUFXN4WowkL(~w{fVKm~y+Ou=x~9zGQ_bxe%1njm{fxsCFd2ztmxnT}5l>CJc5VtTrZeGObgd#YgoXAHcq=SHePpP$&nhQE`vP~ZgX#%FV9j+f zV9gCHtm%kw3?_L?w&ak7J1Ymnh%g{D1v0^C^DI#Fp+|uXw3)O%)j+UDf>mj{6tyi& za?~YR<@^}dEXh$*YH*X`D{7xNgmcM+bJ3Evii4RD+UA;@vE3z1m=-2XCzz0-y+t;q z2Qne${?pJmFBf^d&EE#Y$VC|}9=4To7{28oj}`(b9-5peMMxBaobgk@@q*ke_Ilx( zqjEX8$eN_YM)see{|eIQnL_Y)Io0557bb!G$rZ8&Qk#LS-67Vx_JmIX!qu^;E{$1rHa zlyBC3b|r&m5LL5iER_mx+=Z$vb1QLf?Oe2-(Q4Ds9rUcA@9ji!UMcNtmVI>ku=|G3 zHU*`TtqRAMWF^bZ6KsK?Z0cim7n~;0Gn@$~bC9TxHYRX0Iuvs)=*vvLdURtOnY zRHKJ_Nr%}j%3A5>v65E-?Rp5bbQ&Q=SwMZ&o>KsH)9@VeHKop$Y8JDa0t3v~FhmUd z$T;GDiPpcq*>Ar^nvok)w1+nN;l<)#ldaDl8hjIfcN(zz_54 zu~ES4EYBqtR^{r?x|0*K(LwCUdDuIF0K^z%7I&H&UnXMkO>@)VzJnn704 zD!`Q}Pe}-sJ0v@|c{mallm}W01?VL(o4S zeO7`0cAl;ooC$tPKq+b_EDTH!Q$O>j zKSL)A@vNX5$Xrxis%so6&>~=5ZVZu+M$tB}7T&aSPxe{{`%x6dl3W&b(n?;|m2z>t zvBmK;7WMZO<=x~7)arr3!Dzp}iM;_E5%)P|#Wi0Cx^?LZvwKt^HZz)E6R}f`$TM=? zaD$B3G!{u`nrVo`6sKx>ry+Vj6ujny8EjpDhJtn0*ee)XiXN{GZKLfd@IOf#VNHoS zGOvdP(#`Yz`UFmg)hEs_m~D92>19XP%tl&jJCK9ftW`0Cg&X0EEF1Ay4 z(dJZaVrPY82p+h-ALOz$P0_G~znjn5BW9$qXh>p_g_V~vA$QhV%7x|#MN%;pU~A1kXAi#L3@Z`z1PF47QuZ(06z_w# zsZ2A}3qApQyGT#_ZW4~eV*LgxNORoEcqvW47Kog-z^mC*Y5HaQs+)wxlz(31XJGhJ zGg3NA#TSXKnh{hO_&9}stQ8r2I0_2njM_mVf9Vj!ANxp}u6-P+YLw8BQZ*#!^v zRK`|W*43>=Mdet1C*ZFWe#~^WAuwcW7Vl{ccN9?vFMtKWidCGC==Po11v4~Cp^u6% z%F<6_HotWSy)8#cj@h&Yo1STK448K6a~R^{?KQbV&XEWuXbza?wfa;?awjRdGht^& z1Chn=@xZwd2nDVYSnf~a@9Tv>?EH;E4;D#J-;5N~?`9BV6O_BQNP9(`rmHjE#gkEM z50OyyhwRxnVCJRWUpFH)l5Uk4z->4T(qw6bXr#Gn! z{lniaedEm0Rk$t6>e%kXr@S)ojuE_S<2RhT)%6QB+_YLEEzLV9yf?6|dYh%@Pa%9h z4Tgo`TXiu?F1wxmtz73`I zn&+C4lNaVMl)=)Kd`?X9wBhkoNB-_NB>>}_`#@M!_QS>i!?rduqy_MLGe$AXH8G#| zh0|AZfsi#9A51~n!{gB6_B|X(0!dcaD z2=#@PeaP0Aro?*(;9-cTM4un+r6S+G2@YcR^a=TF2yKHnU*IJikpEqm2{}(+<$K<4 z8Iz^l_{tKFx+AJ6PPz{_hk4K)j<7uEo@!!28D)QJk63Ee5OER%xD=fVI0ot(km}{u zQZ%bcbtGZ&H*PwI%Nvy_KH7o%wqQOk2~|g+{~w0u)Hk{S>xp3rejPmD{yBWhjN2t; z4rfU$8|zc8UmyX8+I@MN@rgaI^N*{EEU~z;#F@kz2SD+P9NI9h`Tl|V!Ynzi&Vh4bd>)7M9IS@*adbZIl3&W2ugoV> z)G(AQUchJ-oJh`++gcI}n~~$d0H)A1TeHx;X6rkrw{j%mb_iSDQP_qCH$)Dkt ze`{rat+Jqrkv!jr5oLb3z~s3A5Uf~bQB3Fly*PCLM`pHATX_)MIRLW27)LN?IbK{1mgFbhzkV+wk2RWxjx2 zbxR?&rHHcgx}ve#m@IsjR)alDYj(L>ZXPR|C6%<5Ud=9NZE_`J4uZ+TShl29bLO!E zYiX22UX(9fJG@Eq#-114uAnER_5+Onc5)vXTHpDNKcG3~h+e5VmO4*gImMR{%uvE3 z$j%<=t-yEti@d6CT~$tXp8P-?V6 z*W1_zsWI_y&^cB;b71FE5d>X{OPgK(1~Xkc>KbCV^ddp^p5Qhz=M%K&)Q@2Az`Rw; zjuWb$8BC)t#Um%V8q#PEL8^M0L7AEQ8IJ!$9x*091^eBFz%>6$<(z?n$(pvi;9BD1`NGp7s&5>3-@c=6z z_DCz904du!tM~A#t(XKdn8c~m^7-(hD4(<5J2@vD`Txt;$r48M_Q>3 z^c`uXHc%XNq*dDdg^$wa=MHXj?u(gv{69HHoHx~9Vb0oAdwPNA_Wpm;5$FCdbp*#C z{m|cH#~{Q=F?wrv4El0V0!$K0Ss7%$ugd&qOr3Us7HGfKl^Mp- zgIS>xW@&1cDQGBTXcFbmSxp5vrCyuG!hTkknb^=cNgT1bxJDobDP*A5S9J>^&#l5}}4Wn2FN zP&sQ4nc%Gd^!fP06P%pi2yx*V1y<1dS0HkUrIb|v(L=98Vaew91bj>B7PRglI@DZ6 zr$6;nAuMObmzyq{5QNxoK-GAj1Z$wVIJMe55t3AL?tZUd$P$TOmqX3vE=}3hNHrIb z0ImapfWJ; z&V4lu_dzfln1*RK8&8q`XzmZ2hbbl|MQCf>>{xR(b~;R#VtUost0|8$LG)~x!#Al$ z2JB<(JsIyo;M}xN-WrasAR7sTDLen243Fo4OrE(MF;CV3}_2t8a8ecU(EV?m9G6n;nim zz~&%wa#V>9#OQm?-pc6+!L2#6(kchpJVpgeZWhthNom8BK2zoFUQhfpVZy7JS9**!szp+4<`yo7siyCzoXx zub*4yzx!HPU{bW8{udPROa{8t9s#czcUD})Y^<5o=KN&!} z4q_lv7cxtQ%t1n?X~?LRmV|I=mN!v9_iIZA6IvmJ4iiF02%)2d&~cU!8&^u?lhE6) z3(&@cN_3)-c(RZuY$$pgsoB!ML-S~mL&MjmhChW4Gu3?}zp z`55GGl?RafusnbqU12Oahn`B(vmSwT8ux! z#Ubb8D}nG1^8Zj85$!o+6oRhQo#JUYP8`b7X?*DKmu)fCouwf`BOCVHTQ^K5Q@f;(F#A;Ld*$+0yzv?m@z~-hNzPt^Nqg=~c@b zh6}Es8ecA>G3QdCR&C@trQ_^Rk$(ZoBtSU4o_~#I95@p<* zJ{Ojels|uF?Ci8?B(Q(A&ftf|Z0k(?9mesDv-n;@fdQXfJ`RO1=LDWtJ(IYn#IS90;kJZO5i zd4k92IZ`%ZD^vr09nPK6Rl7RfIXzBKRJ8608b``D;!VFBdWDVCm6ki~-bwzh78YRZ zRu3ZMV0axNcdp%nlLoWb!542lw~mZ>4h$6-FW_!CnqjoiLUFI1^$;XUqC&R5gmJ>C zd96F}uuSH)G-KL$?{dab8V7~U1_K7-gqICwv3rY?&@yWN4|{I{VAoOId0)MI-`20Y z)mN|eUT^A_Y)gLD>y~ZVm_`QM*lYnq!kU(n-~~-Gc49Z5gtMR#@#M>pZ!&{3PAny( z5E+yOLN>yh6-e@BfrKRv5S9d(%wz&dW(E?H$u?$w|8wdt@AYe0HpZC6pnmsO)jf6U zoKvT&&N)>Thh9@0o}`gq)qSwrIQnC7yRcWs(PhB4eF^4!ZsQ}Y-Mz;Cy;^+VE8|7N zoNy;zx)mGEcw;PtuR~t8D}2Cpj(pEQW4O)VFV`|-B{rLxePfl`eYMKGjh_>%|5;+u zy@4pg*%xBCn17)UtTrOD^Jt=fZ1EN7IgXkC+U%HP>_c4~bT{h2B@W4|p5ScIN?OyG zHf1O53)?IvvTJM)2g*mW_Epv~w3dNj<(>51x^WG8mr=CE5M~z|Z+-4v>?n3VB5iE|-whQx9Hvyw9*u}k84{AY<>PwXDP z-A>WERa?9(+4GILeJ_blQ~%F<{ZJePqR)Nqb_20A6##?RF5xu_w_U<*6n?vepZ*)h zahGt^eKF~ zdSrIgYV<>>>em}SxR9~RUz3edQ%8P)@#n~EGzi&pgUqXysfi9|>rRrz4Xr*08020; zZmZV2lpmsqx~Dr6_09=5d}>5v?@_(h*cSKZD8L7G2d9|{Legv`y;)_fa{CP)B7?^G zY~btwQ{u8#wL-G3RB~!F4`Kf|lYs8Q6D#9b?DSxryQ9_W%7?W$=^X&5)*j2xVW>&! zy>H`vPllLVB${;bhGk$VT$^AQOg+alLlb8XM?Ms2N;wbb+hYGTVWgzpMRBj0$P;j0S;SHSQ2K za!Fyoony*bs%7VwoDUvv$nntew~G)mJDQD%k&3r=kHwn!o!+lC`@G2&Z)>8wl+DCwS_+FsCV-3l5PgDo2Oy^OlAgA22K)Pm&19GL}-Z!gNO;9;;~A6 zI={rH>e-p~ft*>Q<<|_txdb?z(thH0=%Jsr$?7D>n;ZB2F>C3d1pHnO6nMh#>$CQb zmH~o^MoIxbro-nf2RCPRY$gYxmXVT>*L1G3;OA7D+?gym@Ih(FfIhH1JT=2TbczQ> zkWFObtbI2*m%dw798y`X_6*AdN4n*+cPlO z#Sn8=nj8B1H5m+eya5QTb2w!Vhu#dZa&Ok$l{h)a2L$NNAW(2m{kA(;y~*)~rEfMM zEk1Mys=2}@Hx-X0%*L5nV7$e~8CxuHz7*%od6CT3OkEz!_v1G1c{zoFW$8cZ=~!2` zgt>EU>$ta?t{&_63EukwjmeDa&yZ*OdEqPzoEcg#Bpm*)AQ}b@V!IhOUmRNZ4?ad_<1F5iR@(Gwwy5gdq21y(+(m5Cx{Lk+*Y zJp?O?{#p+jzKRUZrGfk4K2PKYTXEh7yIO|L!LutIds|z3(4={1K^kZi+daLnF&=O* zHajzDd{R4MafVQswIC4-5Lh7rSwM*B@yFRAdxG&T;P}g;Z|vtK;V(5a2N*NbL)hBU z83J2gS_x6BK4Y^({5awmaPUF5ZYT!mG^)l-(OT(-58Qk-U>y1%8(6lO`#8k0)Yn2G*2k7ci&u$Wz`7UMtHoupXQs|C$!{%)Pu%$I&2xlWMRqdpBP`F%KYR_E2MT~j?Y(7xNMtiZo zE`yVFE)~fwG{vWDPzuQCcqpZPEr+YgzcR}(^xD|Y4}{#%RKb2h2D=JuHa!i(X7ePp z3ls#c8xBt<)x3ga10mm+W#`0H9n$y4lacsy$^rOu48S=w;QfOQ0IuM#W$;I~VOK_Z zQ!upg0bB8&p;k!u_p>^nxHe;m;>k#%{4-`-K5$AN_w(IvnGPv&s(UlMVzZaav?g%U zm@L*mru6J{lV|DQ?EA$sH@Y{|5`!T=mtcHE=>sq};viW*cO0Y#GaPni)C_XG85SQZ zVIevQ2IrI69%6@a5}tcs0LImaZ@kwfw$u_}T%ENeVE_8(<=Sq;Kn@U8t0pJOyJo8)#Nhkw#F z6|D~NufD;^G7>&{W%!z#82%$^Lmw#hv9l28kB5mP+;KFVg*F#v5>fQ8vr*>{hpFRz zSaobpAFl(6$?7;eg}jaug;Yn??w{d)0?mzYR;ciJjh>vH9+lQk+^#8hnsA^>qOCK` zh$lEPlc9lQmv$Y#?Z(rkWGsD$Z{s|s+bnR{cHgYLK~XU0Ye!(xdm$4Wjh>vGb8}C- z<}nUxd zhiv{(nmWa%{{C=qZlba2cT8v8O5mdG2?3%3!zy#Aih-778EBYh=p}=#ImtwGDp4I1 z&GDxEmGE}u9Yq!3>~@gf4RtWtF4(M|$G-gwJT(-0cTT_F2}5nSGWz3i~YSnSJ&#uxEpP*5Geum$Mpj zLRuBPxqa4Zu{S5PD$4D%SCp%Qws9o;tkvcASx^r)9YHnwtN;dkoM4%Kb^vALk>twZ z1pDl{62Wb81Bi*nvd@W=Z9rQfKqj+cLjdV)yfHtj|p0 zqbrpH-K6GZ^Xikc;qW48vk?cbHyzpLcx0R7k!_Agw7F{cgj#)*31@X;UP4#8%S8<< zyCEx_Y^uCqXDEic!}?BEh6Hb<6NHDHzzVHXp8gul*&@%0FJ1V<1MMC}Ls$XsY}g1n zE0XEmjYtVcE3!FmLe+|FK=D@m7D(XfxQoP{j%Sdt)A1}4b$Tz>Q?gFptE`jvBD%iJQ#w<})Y`xy7R*RIZS$GY4@i@M$^ z%?fpfYErGlr4&DwL;dhFV3^H*b_@e_rFMb^N@lUwMJS^T_8$rc`+@JVMC$vKaqZ-T z`Tj=gf0yZ)VDI1R!QeaZB*Sd}M4bhjfA8O@iRd6K<@E{dhOK7B4gHnO&`&5@Zs^xE z^MktEK_+S8s=9vvTa2BX3>xWE=*LUneM4*{Dm)1tIT}WK z6J$qz8nDcl0FU;GN7y_*A5Vg4A=`Wgb3Xzj>k_`kwTA$gO$XV$xnw_=3uT1;oK=Vq z9mDo>9iKg9Kfe|n%@$AB(&Y>#WBf8AI2TnKCv}AH2y48K*AlcpacjK#qsM-jtTX0t zFLq6sWR9ur8h+%M5+0JAkGqiTyA|-rzT>2+npL{tLsh8{KNfn2>g3EeqOJZwGN*BC zOv6-5qtdtrp$P&=I5~n0a3v19c8H-f;gnGpw0=IWQtOt=O`Zd(4NILFN^M%|Y$-LF z(+j(SYO6m%GgKotdTEW#QDI7{JE&c zFpyzvN&(jhaR{QzsB_*RbzH~&5>4hH5$t(byiwA@oQkmsWoI6%@mjr6@?L2bUbwFm z%ZoB(yV#+|8uJz>fi2aGnOtl>ECN=E2tCwPu`tgRtYS77+F<^-WUdr6eR_DeB7viCry%zm}1EXj6&CwJ4 zFr`NwmuQ;>I*P73f5GTFARC+yIe?#eI4hVZLl+rbb<{_r6fmM}!GAWF&w)!8Z&dg+ z6+>@$3|H+O-K=KOg}fk=1M8i0uHJO$LF#{>_*C_?RR;P9C&6l<9ohI$xea)^2v{rg zh{q!CgnK2ZC%63uJ1sE_@^UAmecW_yJYF9fu*X)TUq|P$$BRC#c?$;QS++iLa6^Tl zaDu8WJba3tM&x%o$>HyIhMsZ=4d=uoZ1r2+RzHxzu~CN^0Jh$yBiwaD23wm~ekS66 zi1s#pbbGpuuWoE{)cQ8c>8SbJx;Kn#g9&vdnmMr(1Qozksa3y?eOS#dC+ME$pt!@_ zxfM>^XO9ZyYMNrT>+u|aIr^$f$T+hjCWU-9Q~nH9K|Ksd$stAw<~-pB4$riG+VC70dw7uRn*}4FM;Eo z@&XDBhlPuRZ|BP2j*&25PH2&^P)dk*oZt{9=(RQx@9=bAhgPG%$a!f~qcS%+kiB3h zN+;)MQnf=514Gx!939FU$`SdH@z%@?4CXe-F{8Ax zY?#A>SpNKhd2AMnHfnAjG7RxKsV*q-Y@8N4MPl9_(GG6YV#yBlDf~*g-AA z_^d<(neRl1gIby?tDuC;ccR8YsU2k%si4lUh`&x~x)CN7UzIZP8{MP289#49gtwF~#{{c_sQg<=vQPc_r#Q-QAdHd829n zbY#Ko#!FMSLq;Z>-Rw@v&Bj|X8_$k38f%`Zb^9L<8;vE?&j9Xy8k0(J_W<`K=4dwq z2ynN4a28kZkTp6p2)u6)@Y5OJI;SgL{>|`jwsShzoxh8^M%iF9(UENt-hF;{=L%z! z#wSh2=JLTkSfe$+OQIhfiN_;gkIr#RHbqeQZAPbu)mEM3g5-~+Q&qkM=hqj-hFyuZ zpz@)}X4Z3SIXf${Mid`c$g36hZY`M$87t{bihWV--9ydME6q{!@>Y2(zGqXDcsvKw z=u884@cBVI_yq0$aoMkz#)=Hm6nimf+x~?Sw(WOpux)$F2;27DL$ht)2reUS)2||9 z*f#yz5%yvWLO(a#^mTw7X)nGw#Gp58^XD3YK43GZ?GL-n7?_U=n=urz(V`S{y$l^Q zTkWPcUVS%U?LxhSdbt+A0ebEg*ymVMhI7#A;QI`_{FhUajAsNZ1rGUYS<&| zhtoEPmHj4d%FKpe)~sMEfSGNufXr-lH%7Eo0JG9y0h!tAZjG=47?lPKfHft8SxF$U zv=t?IpSO3GM;Mi`?P`4_Hn2VUz;emf_@;~xz8bb=?*=<>nDYmYxV^XFiBC66DVtU^4|bFa;|TCF7eu$Qb9( zC89a!l$^27pNEPJ7zGKpw}up?7nG)1i!=7A$SD3PJ!7ChmugbNSy;GTER?-x9{#=m z?EOgHhzn^~|0Ia=qsbp3mxqZmZVBrya*DW_yl{$CNz<4vYk@r$W_7HlB|nA)Tz-s> ztftl5vP;rMrzd1|Iu|+5D8|vc!GrT?^e6<{mBgqX#Av`FgfUa1|)HP1+s z*^;wJQj4YvZ?X62=90ykz%Dv1N4xv{;*)R-CV2IIIJ(%JZ1Hl*t^C%w10)<>3c z-kH&<5@0%P4$*udyLvi7iC|E7H^U?eizPd3ywp6APKu+L-j|x071s5{iCo@t+j}l? zaarUv9Aq+wgV=Ztun#nU; zr>wq(o%+G}3Njm>AOss;VX^nVqB%f;N68+>H<~CuB0hw&?JLxqO~(fDb=z!??<47B zkV#hbJSfA&Xo6<8*&M$wl>b2}7%qivc92dU*PG+v>zzl=FX6TV=c-6W7xfuvfu(3w z_$;P3_gT!1^jTGJL!XoBcw~%s*5~APn4y32Pi1HF zxpd~AQ)&_8o%K1T7IyOrj%+!zl{%k82wn%#;^#)<_Qmx%7 z(=j=n*z|37UKPOcGFU)Pr$%r(sQ~_$!2)tRHGXs)}hAc(oGnz*fWZa~I) zZ-sL&WIpOxo^AY+XF>A2JLmW-Ycre9^W()bsRlZ`JUS(V zZ04kt_ujg4M(QA|c>zv3aDVy~p2rbX|HvTK-Okd0bRYim^q0knx_P^fU#)%!-x(e! z@Au|cE3Im9!{b-;%aXLV`4rORezk>?A7e5pzkP}L)$qHi&GNg2w%Zfx-S?La>>qhG z?H=KG+vrSsME!0H57zHC-seI)#0zEx#|Tg-iLg?B9TyveY| z4jx z-kswX|3}W3_h9>!MTNJwx^r&0Uy(Y75M|OnvS)2@4?e7YYiO_0xp^AZ!%F_eypLIP zO1+Ht79Ppa&+IMhX_*Z__ZK~~{>aRRFHcdh;g|hH)#&ZamkQiUHt^spd=&U7%ckyt zRW|W0x5fQDd0QSO{!6)c#02gucGud{&PFKWl|O!`e42@9RRbCi9(?zCJ&jpG^jUOvl*yRx+}MJ>niEk?rNP++r^! z_37JeH=pIowB~#ILTio>1ZN^#^b0exj+l5%%%ozErD7$ntC$Ap`zC!;$GaGBzodNl zmAJ$j6c4P)*;edFj5d|qFAM+v5E1kW5k}DB4d)aB5HBDu+Af^?>CK0~qj2HNe_!lz zoGdczn_(Y*rMdC^zG>11&+qe;5kCBdhvvh-5nM+4;6Fjeun+#9NBG4b-aU>x9+F?2 zwm~#v~!zr~plq!#O9glRrugA*DBVETMJ(y}9={g?ip=9|g>+F0SN_I3BO3BF%z2K;B zu8z?{p*>bfF;?kNmb4{Dbu&1s$LtZi)M;BR%S4s7;qlglK+ecFXgXs91?ZXn!K4%8gvpvi`E zAmd<;$%k^F-(Zf*h_Z(F;&BVCX%zYi6R>?W?Nv1DHO$= zcyK`x8l&4Nw7~$Awz45~9z9VXh(uBMRV2!N^5WKSYje<7I^n+a6B*qfNFXyai)%#f7MAC)g*F9XG)5r(F)=~lY? ze-5|di+r%<18o?7puh(5C0q)82tsC0xhQ0T4{H0XY5Rz{hmFqsM>OtX$AgV~C?8fz zPkA`w9>C?SaSuaPKiqK-8>@eaaSsflBjO%DjW73O<+z81_V%U8c89o!?d{ogmQaT6 zDZNmE3u*UIdnUG62yzHO8;*si#TxpXeXJD`Mem`2)G;6@0~a5wX`QpQV8=6Gr1L^_>?a&TRQ z4vMuiG**+W@8@wGdGC+Q-Ct9etp5|NQR?R&#}PEZP}WEt;Bg!SIzTh`bVv()HICz} zaU2g2$MG=f3iAlGw|OYWu)ZL59O2J;?MRIwqfn{4Wzl3`Af55R+uYAJ_(Zy+6mpVl z4{LL&LA*~8q!KiUz2!Q@eOGz>!J{EYCG%a$uOC({MN~rJS1xe=5{z9bdvHQzOSXg@ zY^(c~^R_yf54Mm;td-OWjpqJz}){ zY^D!Nw$hS5D49!3`k-VnEuAG&GMbjo5-HhDvv^tLyJR{o>4TE>G#3uaj?@Q#o%Mq+ zo7kEGUZMu!<&YwjE%N+L`@>gC+|3Z3^a`Cukn3F!A~Yx7fyABO>(_P;p46dhJ4c+? zaZdMcmScLRhGKe3cXckF?YxeQN5uMEw;|TYQ%1!4?09IgKKXea+#~t0p4XwkvEf*u z3)I9fz+IiA>e@Y`t_N{mht8Q8dG`9gc<1NHdl1)kGM<<2>x54adt4GQ9~E&)i(v*^ zI(z-|IIknS_v68z*C9Lx;+)R)ybe{l@w|>Jh>!XBxyLhTe#;F}IMf>!DHYCR*QWSR zu_Hbl!miZrP^zL~8bnIvJG!l`B4HXtN)4u36ikCisi9;=z%+=I8cLRJx4|hML&qEEDit6AXs`fHCppBus0499LnRbIH5LM>RDz(Fp%T^* z_YAgGf(U@264sIsnxPVeW)vlOU$l32I1*w-T$SR-B!66icHno4n3#W`$a~kh92AS?EcEN#55;@+`^%!de(si!dzhg3F{(A7o z8;<1JTs~L|$&|iONapJmG1R!NJxR#Rq{npqM#i7dM{i{Ox-sY1S9&mG;Gg##=TPWH zIg1nrY`QgLVdzzPfre=&3lJU2%^w`(rrX=sV8MU+YWx=(`~l;?;zSCr-6T$|d5Bmr z0(UaA2lxAyR?P(F5+^oBrx~+2v7chpc+|y-)io_t=vc|jk;h#n(R+q*{t}5DtIMi) zXoN{OZ(B8*MCJ$i@x{F?`3oIW27C#sV$OoV+EZcWDbeQD@O?zg*~UPsM>OVa@xjKN z4P7z+E)P8$2!<;n(EKc-4S{GL4p+pZWsxoN zO>njqA!D~&#K#&@p3@4Gn~s+zN$XhXTsQ3~twR)Bx?KxiCh{A?+3bdhbi0<(Ygh)t z5k}iRbS@8-+wP%rd8piW51q?G<@nx0=cZHeP$=E@berIHh(-;#uxz02@})mB4d2syIjrmoS83l%TBK8;`ozb52n=hs-SOtKyY7II4wU}a}@$juBCe0xh4-;_j( z>&fZxB^`sPg}>N-P>X-sJc{o_^84tGPgEPMF@=Gq%(rUvR|9-ApTn1S%%c00TyDj^ zJPf7W?s7ci&duW)cZ`f@lvR5~Jfr4=NmQKsRlwbN?$_+dbH6gj@X)zm-+$IX$;u9b zCH-#tQE?x-n_kf$1NW=Um*X`&#>4Jv;coihj-oAC9BY2oOC05K6x@UUP~#{@-bHc^ z#?0)qb-TBhF_5Qe3XUflgFWVz7Kb~lg>8kn- zzV)&k^m6&yCfj^ghNJHBn_D zUPzY4dmq+TRQh|X(zq+k=(8}I3(X|tEI`x$vZ~$leI5eKgnXb zA3{ITr^7KQ<$khymVQED`G|g!A@hL!A%jRu;29qqD!fZ#0McMtOYMHk4QDH;WH*zqy9Ml;QE^Fr*lc(>v$b zxk_{$ZG4;Cv`=<#HO`OKG#a$}E8X$_TvTb;5HZp8#F>bTfF$$djiqLoG<>w1&6z9F zKHzOSzI3wj#XeQX`BUnM8m3c|=z6}rPq-{S?JWIfab&;Ak$G>ocJ#;U2yP7@W6gYw zw*mLhy^Z&D?vH6-8XftWPem_2vg0qK7a#p}6pg)8id{;H zQZWr^6U|n=a@Q@P>%Z$|FOQ?7a`Zdl!_gP>r(LDus?Rkl(Jj>d->vTT*MZeoW&Kn9 zbS|ycX6{e=Te=Lr)bgXM<;^)VHBL*V`U|7Vx{ha?XwK~8rigAyoRgQkI>P@IO*?ZT_t-ZhFBTKtexXqRm^wHiIR(gL%qob%2AOHV^eRBNYD;-=l zCZzm6KH{fxOu6gdEfLG-PnBo~`Fl$FcamQ@CJ5{Qmd8UyS&ygv3v0i@vaGC*03-i! z_`6T~cghdnRibB6_opm9zJH6&Taciu;yeBujTx&=-@nBLb-cgVk*|3Z{(|N%w;jov zqpGC-7G^y`{^IC@p>}a?)>vql9??~Em8f=;;qrolguPF1iE78c-wCL3vs=JXYUP;z z-gOItwSc|ec0*$SXPm*bM zTw5T2{6A!PJFf!Ym1;!=r=GQ^o8j;H{nZHk{&EC<0#-Ywzw09UDEur_L@sFHa#%GQ zt-leu>V5x^9BM8b@+E@ZOLtUER$q2QR@HK=atAlVRHpBbVfEQy|HmQNTZdrZA65{u zkHCZIudVIN?Ra?#T@c@O%ViCIyVj?*l3H#Z`6Kn`&(Nlql0(n~H^6oG>4cox%)aUV zg_X{!xN~a#JI3g`_0=B)rl(Z8r{eCZ-d!NnuJ@1kmE5YYKbLajwO4$WViV0y{S5cI zp`D3W<#MXVsosC0_!&Rih;!p~4O$HaMO@6vM;q(!AoGs)mZ&1d4J{P*x!jJLahmi_ zk=MJ&E1PP@z5iT#=Sm{i3DxK{`27!Svthyv!>XL3L)`JdzzKELXsmyfk0CUDa0j}@ z+Ll!9XZ;BHdp$}2`BAOFsZ|$7BH3~@W9{4Bshru}P0q}`;{8D0-HCato~f5mP5Rp^ zaZ>G_%#n5F=j&DV!B+%J~?t z{{k5%zzX6{rh7lgdjyfRCipSnd|GglSHO1EQKu50I>Sgh9o4wesC)2KC9bcXTK{{# zIg>Aq(j+l&^G7LQYo%&Nk4gLNR5g51tDjo`C#!xrit8%cJ=jVUE{m(AwNo7RnARxL z@X{RbXAk31T`pzhVdY(wxN*k$S&9AwxPHdl{AwSUBw*`r0Dh}xL)CbL58>q3LW%W% zpp0rBQrDiCRCbOpuD@vxxO!hnjwX^5h zG@;`P^E12W>SuP(>;25`1r<57dxyR{vwKm1&a|s>wYRfH8*6o440+crblWRX0cqoI zU9-3}bMw-|i|D7#r3HIx%@I9)h%jZH0a;J0>G-Mj@1!OIin)UEphA_Q<7CIqS(X2C zdn^Fcrplp`JY6nCKwmh)s3%gAj-w zN%8%0r(aFd>fPVMkW|&jO5r9tOR~EzZ9k-K5S{#UNO!X$so(wQuC^=e^bN|d8%DSu94=@pn~?$1rqGcJ>x`0H%g^yoR((1r_O@*l@e^t5t@c) zV!oJvFgycm*WW^sF=iC4diQ1sLznll8|75QJWyh$_uS??;7UtN$&kU6YY^(rJUi zME@$npvF(NC!6Wy`s<{zxFw1$7GArAV)D#T!d9vt4^( zc?x|a#ZqK8J+mnWH;lpL8lOj~3Y}J5J!M;I#=5cTjP`hw+FGVCV0}Hs-U1m#fSN*; zO{eLt&Rf&poDFDnu|YziT`89%&KN2a7YtYPMvvz~kAD(9I%9O{miRv#aj9qRRHh5) zo6uH5#<(onO3401Q%>pKAWh>9Qxp?TBb_uwbM^N{zh--^1O0E0lC*V-?%hnsgPurv zL3My=)~l}~o+k9_Sf!_wh4{Db-q%d0O!uSjr>tH&^%(uS=S#OcdmO&>D|02lcQ$-G1N#sRx6= zMR$U%e(ex=Zg&y5F5{@L;cR`j_=-Ex4ZGj>fmv2*k44wnD&l0NbNoNE%BV~qKjl9^ zs-I$6eGjib*Gi&A==skkyOm@1(J}kxm_9lBg3I&8wd%cyvj1Xb<7EA()YWV2VIst2 zbUZrpu6#D?^H9pGf2gkPyXyLD&gnyar*+Q%3H5dSyS_Xn%A3#gn9IK!kj$p^Q(gaQ z!4KssR`>k|!y0BQ4XwrHSHL<^J6c2F#`}$<9s3RI?TQQ#zJ7}}%`o&-Kdw5X2CJE`r$){nD^~^a zv$`I0t=A>`y@0B=gt|02wYh9Hid+3B#_Qiq0&U>%JfC#;SK{>-kv6pj%ntFe{%M*X zuYW`F_$_+;Nm6b0i@wEZde<$}>%S-(frHkjkcf0^AGrRiaw_Y;#1Auh*^uV$zXoTs1Z4}d4)=o*CEVn0<6~_Gan?VWY zVaQ_Hr~bymkFk#x!#9$1rmpD%qtCeZ*HqV7M3%>zDt%pJ%}=q?*kVIW9QCiNb=b+_ z>ndrW^P_s&x`)#RQ~m!GTJ_ZWVZnB_ppk>-X#4;hSP)1MO{7ATZww2D$S4L&LhF6=!Jxa%xb ze~hE=0Y)O+W5koMT1QUl1D4-oFHJi8*9WQTR36vT7{2| zwNuE$WTja~gb9xc#D@kyF@rBd(s)Dsg83_NGg+%d-v&NEsy0=y#eq+P5%rsd8*-hn z?0t5{>^8CeHPg@Xfy(-u5XKZMeLYXZ?Ex z|MdQ0e)VtlNxVdeLO1wwjIkH|4`ks8f5IZ#6wlb|zEGBxx|dcro;vb9gQXkY8`{-w z+^ruyt+_8YhbsO5?O?|U^>wZW)Y*|rE!N%ewR#_D2#=Zip$xigPg6(UKM1`5+|=V* zs70F<8Pk74wXD8;BP^{crjG3Wp9AeGV2zhyX~JT5ipGxnw|GOhrd#>^C4-+ci%loI z%6{j_d&q43N4&jTdVh!59Q{$W|4XrT)l7j)=`m#m$o+#rYVDM%M7sB?O5D2*?0fg} zCqhZf#-TAj=z^up8q$fqvzP&M{q>NTSqtEUSo2*Hy@c-?CUZUxL->CLZ0J&d%VW)K zK0&CJz(Vk!`5ABcfYv&)|9@q?X{;x3Ek;5L8EHn1^?y)#M(sw|hWX&mI3U~E^mOlc z$@O+5(Km`A{6`yq%aU;?{1`IMdkfIVPwTldJ$>|h^>(klIp)Wudn*;zQG9n?3YT@@ zaVgl!Wc9cdYGuymS9`Bjy$n~=y?2J1eZC`Kk^b9IcQUHls#i!IURO2LRU4>F_LGZ& z+4TNyO*v+bu(6_Y%sxJ5XdV-iM}?DfNV-CN{d?2#W3?y4U?v~MKd5H!eQ8N)$0*oV z2kh>u(*S0=$LD6pM7%sb1=JqXyT?@C7AA$9#L*1-VT>!<5Fed@3jxO9ysvun-(^|I z85hy>eKseA_D5fy=Zf}CJ9~S~XygOgzGqT!|N08U;S2f5#sr^c#_Kl>By?>$f>}8Bc|}q5OuVjHk)b%==Y9nVL~f&{E{zAG zhmGOM(VwR}z^Z_0~T6VG5o6`c3y8S^DL$?4s*)ziD@a(Gd5@2#vg zUIn|oL-gKHJ-Jphj0aw-GmeEhe#==p-k6u` zv9Z9k@rlsvezmT-d_hzXs>ep@8kE!&PVgu|bJ!-U}w^x8BgXw?L%xyaln1yWd$7GH>CJ9pKU4mvsO0Dmo=H4H+M~4jc0FG5*iYoRVBTcp&H#9M!rJ3($1hi z-I^mgRDAHH$(Xgy$ko90vHIg=R9^N?>dWH6xN}SPmflI(a+HWksTH^Da{FA470oEn zZu&2Sq-%bsblQyV!EDBwSG$cx?s^8_mEYiNQCA$p?6O>oreVf;4ytZns(Yx2=F|u7 zV9i_)?6URQ^FVj2@#~lblIGLZABSV0GhW|T39Gb9mZym(Gojx3+fel?uf6mosJJKAE%&(HPdg?o}K4UvW2-9N=W~e^7&f13{QD}HF_W4zYF(e z=&2tt*o=*5Ko(c~kL#y;{3rCoi1d^8TMr{r!$%~In)kwm<3B~N)Q?T~3yp*#l17?g zCFd%sRnkaN|I#R^8HnF1DWA!tTBxN~N%_E_$uJuG&^+l<=i}vqkHs1rZY*d++k2mP z+ce%=jgw&>kk{3SuB5Io78~h~8Rd={;f@*EHXD~e0m+@?(u=acWaIKO=QZGlMW@x& ztp^5Ujk!ydoif=rXC`ECMo0H!w`ld7m7ZqvK5}Q{a~Ow(Ul>cO=EDl(?`XW^qpbUj zv8QZC9E)F_=So(6?hxh(5|x01r*Fiq63x-R(3eEVl~%cEK3~|ZoW_)LxniDW+8Be~Ah>Mq;dKs_$w6h~c4{)GK(4J| zq~Q%uDu$Crs~($s_)`pHEGcAE)HlT%*y!Xdn}FP4etgGgP<87Xqf7ak=MJykcb3-f zOzGVIWRz6cMj`jZJkol)pi3oc1ADv8PYuS>)cR)_#k_=Yn|w(}8t>QjHMm z<1$_vJ&&w@I(l(NACHd-eOxey)&T?Xlpo+#ZJiSIli_mAP^lbKrLlLrT{Ny(Fi^i_ z`APcqabHD^&g(x8-Pi|o%rNkEt45|qWb+x!o0e0tIvnJh-tu_ljK?Pr3Rgb$i1G@AZUEw=!l9u5Nu)%t~56(Q14G)kh^Q^)oP zUx$GeDKV@Vsh0QnuPd>OXPDrr4+J{vv3s+ud~S5zg|jC^GZzJuZtnmXflF*4P^5%C zLt!d@_}X_fey~V0O|Y9*uMbgI?OrW-DcLZzmoFHoHDF-bqM*rFe@o?@cxMtD3 z8aq@tcWaS)=Z`!xlJWBT}PYwSkT9E_M`*qZu2u z3W1Hew2fk8b%>4C5*zEVvF?7G^9x>V=-=yNV5w(|?>~*5V#eC&v*$*|rqoQF&7$8R zd}#IMlyR(msF5;NfI#cWRp_MNE)@4mJ-_S0K=K$rJ$y1_db-dT98WmF1k*ezQxR+Q zJ2TnnErBa_kL7jGIBZrekNagB33y49e>ZEDlwhg!ltUrISXb42PFvB(=CgufSVWu53!cryO`X2q_(aq)Qw9!>(aWh^bJ_y0%(h zU?wul2ilm14i)=Sg`r=4{XBI={Ta``kmS)~QPDy8=$F-J@@6Z__B4?Y0ejQg7q3(} zRAJhBNTcWwWu%a7(aMK54l?Mhyq0h3&E8JY7Uyb*K#6pF7Y7jv1{EWkH|!f-!n3>> zTJ0S}I_Lrl7-?A3UWDiu&0c9?UH1AUg)`~Qfyc56g$Sms;Jn7-eEt>NgvsYPEAcc@ zC_$$r(MO=!tYq6e9>Gw550P5cx=B*+N;&ha-f`pZMBH}LwH=#wO+NAZ>K~Z2naSHk zsajBu*Y!*3m-!W%P`6oghezlN?0Op`a)A%hbGRY|6J$+ zA;Tb49&^qHww+X(UL}6EA*d6~S`=bPuT{5}wON0o znH&QSr;7vc@oXVz27+vkoW81tuiUdIJS`xgA}s0hD{sjHLK_yvmtCi7vT!*J`cCKW=fJ{PIjUMK(F={7W_dBE>`)PJ7lw3sL*7n=J8fsYNLsV!0L z9o{C=lHap_BLp{m6Gu;`o;lamnmdoxmX_A$U(^cC>aDW>Y`hlR??kFY_(7ca<8q?g~vptFfDHU!6rmJ^TEQ{_W z*s#uIe2jMwISjNg6*OL++7Qekv*8`y3mYc1?J#;43V}J+RryF_*;;hq5!%W~UK%wIwfqm*b(3HH;gOUmWu?(@)&W`d&_-F^AKN;+mICd@UB5*xrq^8{Vz8< zWcs|kp3%Cp(yLSKrhJb0+rZ=;)O`IlSzYd6xw5dX{o_1oYK-ljVZ)YRFJyL7T7Fem zxN6*nunujWk18|Ae-5@+x^8^$B&U!60`C#g4w$!$+8Ci*M}7h@?$T875CF`u_K`fb zGV5@HGXOY*tm6=v%m8Bk`*~F~Sv4QT@z?XRS57r7kUd$0SYQ51wy4YsYtzWSc(%VK zV3`!q$qHsMuI+Kdss^akFlJ z&8rEUaI(BSFJ3#Q+UoZAvQF)om|eHOwspqqFonct|B`>^@fVK+w?)^3*4DokEwY;H z$CTDte;bJ?J7cy1t{vS+k_|LQlUUR2k;9hz)_TZs|gp2w@jGt20K zI^PXI4MZCgndjB^F)r0DipYYCT5`sSxJ-n!Fb+f|qP9fV_4iOuS=-`nKvc7kg?sXdIF&~yNUKL&$AUd7R5{w-r^g#tMrljv^p zcAWzq8O#e_u74OHEM5oFoSaP}%FLqGjG9KT357ccY7LTl1F@P;E8ipuURnoUk>MV9 z_xH>7xok2nmC*W7*7$4%FBs=eJ9%J-X?JN>f9MO+MJxDqa>cbW#llKBbPuk_WENGM zt|{phsezzV25i~-IHXsMS3w6!4|KxzJYZF#1#l_l5y5gXLGo}xCA(g7WVzW=xtXGz z#?bRj4hP0iQ(j@)Vm5|8gO=s<(y!sWpudHCY|X#y@`|ar*#2I&$FYhFJ*KUWqFU^< z`%9x$DVD_d*@X5Pd2OsU;5Rw6o^}gn<8BhY5;+@ z8YlYSfs@xKZgN|;=)~Hn^yC@p*z#uVx%zcRl6s6*XFhFFSoXU!kPseA($k{!JoQ z#o|uAgy3(?FCZvi{ms8WCR*nBm(oStkY0&)X656~{_SWJQT(o3F2(?&r}z{hyrFE> z#P zd@kQzX!Tl2zQfo@ps<4}bu&xe8)bkdng+_IES9yEpib=)v>@5cXA?&^0KaGO174*r zsWB`2C!4&wX`jvKalHPoz_!J+TUf>$?l@x`STkfXO?ppGgSy)vRc9(R;gdKS<;7)xHz*YqfG^#d@#> zr86b$A?nWt419!jauO-28u4lHRY+BNo1Bw{rEnm4X+>br&DdZY!l{5-^} zv}=T`G@3V9j0r&>{cme%6|GGdb<C$_oZ@_?K+qa)LQ8@+hEf9&X|_z=rZZ~ZTM*b_R?`oGxA(sG_^L2{PbndqMv z)ru_Bh|N>}Nu=8=7=$O761pxW5C|Dxd3SB;q5y1tC%}s}ip!V#Xe-Cm!PZ|~mM`C< zu_F9gm&trEb>tu4%SwBJ76eDY%pWHtH z-pFC`xlrI32?G@PH=dBq7hGqqA0*Yn-~ySE$x3uFqI$8@M;aU3izJLlKxuJ{pC>ZS zo)z^s5n|91AELB!v!GXMU=T*=OaCmwZS|)~$p_NL?+=?34e(a>BWBRHX3$zk zc1nJ2Jog}shK0lVnz89Yb*2R^oT5F22v7N3aVJf)ee13(8NU;T@2#*%`^0@myh47j z&>XeX`*$S5rl70&R(uKZV&83f8uB;^z=N}GuJMh+?K2Sfr6mEsGXNTp5rQ|?NUs>1 zE&AHnEFQ}Si29-5b}wtqIqQ6E)!zNe`U$>kVakhp+Q5^|AL`L}g5ae^-?4Q%v5WQ9 z8_5XcgSd#Zu)cadY1$KF9>NJfCxGO~C^@KAj|leRbKC`O!sCE7+(_=&b*oV-mnlil z6jPIrGnVLgRmbt8Q5zP&!>Ckk`V*I)dGMkl^Y^jE)ZctJagNMu`8r6mwn~HENm7XY zZ&}c)hAI;p2aiEQQ9=w>n1Ltl*@Pxkxz!=z0c3_}0m))8Fs~Jk*<6it*4KUdhp~2> z&)cTcwm)o7r_JlN&EWR&hu~w|zB1ONfF!oRrHLDj^TQpH87rOu#xijyamN6~8+q{t zM|01nb6$Ktop&l+kY2EiuWiQPcBi`)oM~DbZ10H`TN}vneY{Xf8>lVLWe-?X(rw_@ z=I(-d?^9e>z@zo4MIPt;afuq#m(vA%PZtf1O1iXu7ld(~cBb>`PA|SIUE)y`PG>kF zeR2JBBKCRdV)(v&el6XVo|ml>)AP>+GQZr({83w3hPicnPfq3w(>+ez-gJ98?a`1u z>4nyJHSX_A_dV_p;ZG!wow4^~9a>;dm!uavi5`yinC=={O)SDCR1-$T; zsjy2M>80MZ{b;c8cv-qXz0AM|?RGg}jY|8y?Vj1bh;kQs$|X*|%N?n`>Ba4S+G{^H zeJlYs>80)EsL#}?Pb-Z9bnexoDDj9gPfDMp9kP4ctBA(qkfs9>XLeB1@LNqu_KzON zWBC5b>62-{fjSUDC@oUjSEg6CpOQX>W~UdnAKz#{q1nDF>hD#)fqH!Uct3Nb{e<-K zB--asO`jUt$mZZDq>pp_uTHN@5BTFX>D5lrr=?F5eXsGiuSvhAOkeH#&gsjtD&i}v z^oi*cd9d1}{$k1<=HKeK@(0tRQC!Cit^WWY z1*K_w_hgMG62bbkLj9%itvHL&qNB6LT|>6|mGP<4FFT`0{e8x*NFSDK9F zE{HTlm1@CY!m?7=K69-LbqT@pHZD}4sE~DOKw!1Q(GrBX58r6;l`%HtTbceA&Q4K6 zy47bHI+DhkJEfiLCSPeKP1paExVeprm5B~4wXR*kv8Tqi(dC3t*hkwdd~S&y)19W; ze8N#lJC0L9pe`sh`BcEMF;c)jo#9>?d!hn^I$Pts^=>XbvjbYNy83(S`Z?XC@~s0e zgeIY>y={wukV~jz?e=Q#MENSZE1VSPF&lf*JE7jU+)#Z# z?Ry)RL0JL7e4!s!G%M)}!JoiWmdNE&23Ue!l)B`p!o9rl{4m^MwtJ9eeU9Mm zuAh|fREN5J)om%Cg{PNUpJzVBiiO$Kcd2-?`cZPi0$ocKCS%F!?~)cKV~Qusre4YF z&s$+lt61IG&JH@hf1_I^y-%}Q(3FF1+FsUS6OFkmpH24a3;9E3(iv?wm)I}ilQvCj z=?Dv_29u{i7Y=~&Y83f%0p~;b8-YBg-a`}EDWn5LR;Tz4T~{+K=kQi~FM!n(P#)r% z(VxCxlxK41FlWODAvCD z+Bk{+Eu^`;(D_IJ8b4Y6R#DEDXBj_D29))6%CcE@!A}W;_*t8~QWw0f&hsbZXFZTW zi!i5rvl3QfBlUfoNx7G6shl)xSeP+&Hr7aEn~W)PO&hq%tocQ;hVuZN{+eaWS_#|7 z!-6atQX?f(2@e{#C`zy>Z0EY9tt^NWWTU+K>fb{cVbn^VN;@P+KzDXVZiodeZi$RiTzi&&<>3 z!y=t%OfpTot9zr8r>stvF7~MlYJxs!hq=EM4&9iwq>+sELKUWvbIl%T6FjHI)?7Z3dJ3)oZvIIP=}S0tW z3n}~uN{Js9EGHn&pDLf%^D(A6Fvc|c&p2!DigNR5YSkj^Lfy8wYT#rmtRh5;%VtrE zNx(^M;p?_ZU&v); ztI>-g+!clX#F3IzCi*voF6UjUW#^M=Hhm%EyY(yAtu{OIUR5H=M1SX~Qn^ks_mgOJ zCv`v8>UIsN*=tbOD|k|Z3ka27mBsJ{viO*_QBiYh5>^iHDU7O`r?JsRbDdf?`5k2US#>%{NpOS~hl)}m1WDOTP{AkVeR+3_qbc2krZoq? zh3Ikqo%Btnq%u{5K~NSW6qSjz&OEaoo!QE=ehsRjUo+f<{jCnzY1j_`deDAE;a1$%$m&rn_)Z^O|4e!EZDM8305ItBS;Km9aXO+wRq?TqA z1Dz&2Pceyfr}Ke=hqPa0V8Nof&v?+qHQS=hNqaVb(wb@kRx+MG?jw+ofi_ruv^ugW z`B>16(#Mx`=7JFq!y*HU0<6tt!dx~iy(S2!4)Ya^hO2i9W}`hBD72L`@MP&?MefTJ ze$*>67NBDV9>yrOje~{rCj(^7jI3^!Hl(RQ$^mYv1ppz7I>K$6g|YA*^bhM0?0BoM zG%S434TCu`(H4a$e@eT%FX=xIuT9=~g_o=GY9GsqW9l+QT=9n#Q>b9DL4W4OED_8mBL3%?)9J&?vZjqeYDHH1dM`+69 z2ih(B^i4&h6~>_*vDXc^xM--V&!j4AXLUq7t@>N@>TSH(N&V+y9~Y0_OW7wBxMp)+ z?f4kf+s>PDHm9f6C)btDzZL$ewb{Qct^Z9SAAo@_es)&0 zo^z;?edN-Xliop<>7M}^G~wX+YhTic-VbIY+V(b*&!KJdtba%8pW3Fh{v8>@v@N#S zs1!e%Vx&g2?s-|^f!2l3kVAb|w9ntqrGde>@hU#geH+gMJMA+KzKtKYykXx4lq#GX zWX3oYcSW4{_#1P4Ttzsy2yGe{R#=ML``V3yROv zI{PAEgpRI7R;C3Nw$98nA<2wq%*QnZ^yxoAP_<-p|I#;rW}w81KIW-F{8c_bfcZ&ZxM7jFM@=?2gaa z%%S5Zf(adYTwk8074`0w@0OhMv~cA?#CA32+pL|4taZ#LVI`(UrT@Ys<^^9zC=>ug zamlxndoHT0zX9!XJLj83NPTBi)oRpw`*>NQ`pE6IVXWZDT6^|E#Ghe{Av4M7i!H(z zjS_AwuFs~540~eH19S23SUWg$C8SI`d}4Ub(7 zi}uM3D~?$$EVvYUOZ&p~LVYru4Uf2Xvd1|=nssF7ai7fjBkK#NTrcg3a=`;6yIYBJ za`WVrdnqq`Wm5t+e64+ykT8PRa^M`vTlRZo6ymu3?TFVNKJi2yo?B zr4PMh;_`SLaX!94Y#&GuOBp4rD_9^o!fVeBb z9k?O8+K5V zuL{Pn{iQ65Z1MXlQ6pJ>vwC3|!B{8-O<1^bh8-~S$gmI!wpVmPtBp6t_{_S7HHUSF zl!Nct_vGm^>2C9M8D~+HC9T8iVjG4WV`nGWeUyT-F@4g_z1i)Vnmp<`cV-5-ldS#~ zq{><{o3{i@k9TV>{dj*2)F_iH@^| z${AyZGdbrax;r=NSIO!#)US3rNp@KUJBtc-w$BUe()00PxZyyDNp@d^-HHL|ao(e5 zo$C(dLQ0yF(;$J{WC?KAWsMs>bEPbyo86J*BGAJV3=-BgymW8^#B2V>7J>ue7o>lgu$r+_Y-K=QTC!%9s9=t$xJb24;`9MYWMg|S5 zG>5IgfnvlsZ;-=7{m8Bpja>R)0%$LqSW0Geo)DL2P7)s(#itKY7gee9wW1_*2mv{}e_ z=tL9Dgp7ZaOrGoklL3#`L8iRpgq3|T9nfivGbw%Yv*6O+7D{=a8!DVKJ=*GBtIfPE z*a)m&8W^vWCBL&@zFNkxw!hJb&|R@dpd_n52}yEA_9MJx<(SN{gS@7W{$`1Tj)NOe zJN?@zs@7$*q7J5A*8&+zT}x5RydXLR!-DAqQv@@)BIpi@$w>E%*%PEquN#yVIbpUN zV@fl1TP2w5=AWcfHu`65{i->!EET^}`gar0-^0JvH}fascD;@j%{#>a72GTnM51I~ zpg2dCs_A(fn?r5a171m4j7} zzg;1m*OoSDSc1pKT}zvx3Ull~v#`QsrBUTP*(_vDrUv^y>T|w8wSX71SxjKqU1^&F zJk~Cbz81zj34Uep;$j!Au49wYP8YJho>;phpU8t*vyHayz1fD-wy0_KbD$Wn{}1+^ zj^T&l5WHIFZYeT(a0vaB(}jzAx;*NnBMv1jzSSZ1NqzB~8r+f7xs@aSbYp{#S!T4v z`QG2jh`q=?1~B_>BGOoh+U&Xjv2E?W}h4tK<0 zAESAIVOD_|wy?4srWTQnxG{Ftv%p9vwJY<}Y)2>-+qlR^Y;=7dAR(O=WwYTA;%zSnLxedlGZMRjoF^ zzqW95z4vB5S@V^>Xc$M=(LRG2FI}~^aQO3rD0trmyst4Fmv$cJRt|}SyeQ46atkM! zMQos0+R4#*OH+r(?TB}9lM-L#b-A_pvbWKK#@%cEx;@qICZ6~FsIhq12sE)YW65ca z65PF)m7a*|O$1k#1h?s8&<2m66<=fPS?njWp3n7Lfz7dBZDfj|WD`}q^V1Fg-Y-fH ztO2rEO!(nd9Jw+fJ)V*JWNXnPz{fkf9A*dm2A&Yr7t`jUrK@i^d>{QiAtVX=J=^g) z_yW^@o13!O^lI+bHLMKj7*=zRwJ`Uq9KoRv;RRoL^CHYVMAINrh%?8Nit&ctIZ4dv z-LJ&H?~vqt)M1|V(v?rtW)2dE{H%awI(4$mT6$Zbn2TDeVrHJ(98JJ%3kcA40|Gee zSKfnl`?Ut^o9vti3irS;jO59jGIibJJDnz-t ze&fB(nyAw%t_@{Zz1p~P9~YBoMIzl%jh<4#o6G_34gixX04FzvsEzst@_ji-U?$P? zfq8wY4tXh{UZElRcCTX?}O_yZ~^nNE}IySLu- z{CX&#?JuiE>(uc~)sZY+tv*#P_o_=?ea-PeE2g#Un>Rh5!AvyMp0Q^+4+@&?$8MT2p^Sb`znh+4W(gIIbKgSH zxxaNe?9QOb^!il!o`|rt>AF|#!$h{;`u@KWnB*CgN6A1j)6L!K^vKrBLRa#1$Da>&0RO%_?YjR>~i(~?u2#PNGCj7aM|_B-PLi;Y1pdQ zuTQGAwUZE$>v6^#V{8@m>-Y3s&CaBIt#k?B&2hTwB!%_$N&o6zebuV-6(MKrWBJoe z2VGXGPyJxHK8JMjrc%8?m}sv2k?M_g9%A&IfbTy0{$#6?1SsQ;#`X1^o?ihMV`+F7 zE)z{}frNDBcLi%pd0(>+bM6i|nfX@gd9KkrSvb5lhSc65_qRF*)0&!rpJ+C(uY#y% ze5I7Sdh|`tuP$9vKd3%E6*Ff+Rwd-Klq50&{*I;90U#!;m1r0BY91G4`mW{FnWrhj z4FW?LXF${FaUSKLCpX+(f_gDgXRU%jXz1ZhF4+`P(V~0+p|X z`Wf9Pb>>IhNnDO1ZaaNSo(+923E8!BM^)oH%1A;dg#>BUI;q$r@n>5__wep1reP3E zBmL_T8u__?mbt^%8xFlU()E4(k(rwTv^}+Y7unpM;hKn{mD!lBCZ|?kLQZ8o=8y&E z4rp<$pvEf`wRXEcmbNwGN*6J+^xtZ&zGfpn*dz);LaTGFW5c`6NqJ*+^|Qs$aFhU5 zx2YhlnVQrtgr=3GVVzFz_oTddNNf0K-pdQc{~MzulUuK?zBKiUJEb;pVb0y`FmUHH zJT|)N*lCB=k=cmr&}>MaY0vauK(HaP|6K65w!1xcI-Tj-d7WDIViBis_pp6Z@AiI~ zO7}!ucSVBiQG9dIe|ql_a-co~9l#*EshS()K1z0hr4c>b8t=z zhQHA0iKVUW$)(lyLjPMB!*_7E8l(4)lj(v4{}!w{i|OPISa25S4(nkjOW++{VbMsJ z?qTBHNO#`DRd>H(_@p~#*4i?sOU>>|cX4Io1oT_%{gSmLU06Jb;fVNAn^2#Zp11NJ zsQUc=5n`z>=wG9%FF2W=?^R?pBKLpjve7D=tf zDri!{Yf8Eib8oI27c$+rP-;+Tl>@h;xi(6%-B`M&-CRTuHkYp1k3FV) z5DPK4SFs2Ws*`m=I(CSKKFf$h?a6Lh=Ww!{U7He$)1Km%S1j76v5HTu>6o!DFEel# z4aFJ!$FEgoIRJlkGkqYVPzUS z#gZB{cDH?=Rv0NIl{${j=5Kml(9P`B^4bxJbPUlzudKW;3hiN1xQ*k=R{s=DEe$6) z=(o1?TYGm0)t0m`?0-E`^tYtjZfK*oBrVcec+DtER8sGrHJ&cyT1A^nv#lvqxCfK< z81gC`Gx9pEGHduwcSA}8AN5~@Z8?;_fYOp&OwI?sohVN1gOfC+owB;pViv>lG7{Nd zy2jtN#Q>uC?J*pu|H2o$lZKHn++%;28_wYUba1XP4Ujs0>Eri^AtjIxry5#qMGmLN3&`{i+PM5mq^NZ7qSMC)Q70UFr zygnWDbv3#WIv=vWWaD`6Z#db7$p+SqIO)9)kgfmA-kShec9dnqH+MOA@9sNyrf;uv zC);pH&x8;l>4a5e2UoV9fSDx|mV6{FGXa;-!+?r_fGEiz0W%mtRD_>RL>5J5S0HQ= z5fE`h1pNGDjgb7$^So8()H(Ouo}OezKr)cN=TyD5zg6e0r=F^crt1e^iw8p>Xq!9& zYP-%De;&MxZIcD~B>nGJ+9p4?o^2uq@v&?Zm4-Id?IZm6#|ZBl#EtK@(!HRQk4beh ztBwZW!Q=*}Ti5WU`JM-Krt2Z5ThpBek5yc{D@rPj9n6u^GzuK-?Wi;fWxygT&VuDP z;F6ZVdKUvbE+SmU)@sZ)_SFXa2D=0pweeQp)d5!q2N)5~ohoAt-_x*C^656;nL%GKr!UU5upb$Fi@!3sckakug=%nmGeHEq;ixiV3$UmdNO4`5mx2hmPXgZO(imU&^k zib^j;TsXrY?S#1>@CHP=as;zf%%7kWm}=r3^?)C=4=2{~Cz9N8HVX1Tnihb;&Udq^ zac`aGj6FUJT=X+B4DR&N?2yQRukkPp!W6?iw_E{(G>lzi>4mzgs!U7w~fsaDJNmHA)Zi@zfUfr7;Z<6VhEl-C2b3jfL@jDit#c+2zFFceCCq z6R5eBsZ#`*P2q}fv=thY?V!fAk0!Sv0feT@@IC;L;&&pgcDbVJ@DPLH8}-sJBkn!w zV*ysm$uEwL!rrwf+Lg?N%Ycq9;W8k7YMO)nN1*pQa2Qwx?3(C_@xLOOqZv2C$L3sW z`kRX1*_g)vl24S4A9idl_4VhFpS`*K03l*-s!JCOjJrfPWGhP!YiRf*qA+=E@I(1& z)~=Gl3%`Uqs)UnvVEG|vbpwK07ThT=)cLE{uai9|rA}V(#IPzbu2qG_?o+!D_O)=5 zcDVxV2xv0Utwueq+r-J&HI^;VnNI;U&(4hQ@*ZL%_0pv%*LIEt6SY<}yGz?z@Kx77 zp(*#f6~9YoIeq#Jt8wYB?(OgOw%xsbL2onmCJ=w3_8S;r|4HsZhish>>6JY0Ho(~7 zP`CKA5KBQf7;6CCKCeDhDLoKjp2jnu%ctUr{|@iQxy?`9cFQfp((d?k2)d^{+_FD( z80>@7SsAC9eS17l+U^d$gmro7Pp~>s9zK2d)h&Bm+|QS`bD!9Mb*FLJmMeA-&)Qel z@0NXgB0lVK>rFM*VPTm*zfPHUBg~gs_bkS+gkt;yq|3uDvz)xE6muCTl%hPmL&j;A zPIt{X&F$M`C>t@eJ0dOY^F~?Uo|aXQi~Fx`Td7XpXN5}FrpA}+AIFxfTDlf#jkeXH zcQ9d;3}5EmNA}q3u*odT1?6;Zn-Vt^)Va+kZYZg9J0o#JQJve)#BDxtJ1=ot@XZh1 zujx!~uZOtt+v_2|G5=gpy3b+Ut&|RowocL)ELnV%pW<;0i~sTsn+Pht+|c?dY$VioR0(x zm%Y}tF#O?^;122qG^JIeVSbSerd> zj&->DC1f#KSE%S)_;|H1_NhZvdFtMic+wm`$~;SNk1eoa(>RxTK#XV6Xg`Pma9>_9 zj%5#rGas=P9jvCC9KOdU2b`lOqghK0-1QHeZRh(OQ-J#a5H!jYOwY8QG~mJ{Oi0Dt zIMM~8auE(_dm>J0vnTeXy;!PZX1E~4NMWPz{OfQn@s!du;(emxRZN;gYcVjWgWrM7 zWkK3pPlmyY-3`0`we+*yx{RM55TRd{I{)y&i4FYV^6r@ z!E>c*d_H15%&x!fFubjg-0)lshvvgCqlJMt!n`!`;V2V^d0pbe&&oFL&(U=5%p0ys z(%~n-V#AsYVtZiXLw-Pp7xXSV7$0O4GNyeUI7gJut0YLMEkYvEpDO^lAE7*5S1Gz0PhIbf`wBL2DVRvGW7izv-54PbiN(y7cj(SMnd;oDgk;UHlOm$$6(-4VzTROxTS@2cWoeG6Kj z`eQeC;XD`J;f`hFr4rBav3oy$R1);^%1!twDvLBkuh;^loHk^|eSdE+YW<2*V#L+ra7;)Pr0U3~d*D6&ZvKTc4v! z1#MVC6IP^>99E{w*Yk4OAwNP`x+6Y~&(rEt5Zm?qQh@jLVQ^+c+O7M)@knfUUy3Hf8mmY~%X$wVnFlS{(CCuhqM=i9lb}%RTtQ z?;n_AIHs%_zZlV@r*GyP?si7GaY?Ybp}x;LyF4=&HxUR#(u;ioq2}*H%*g%w@gtk{ z&yaWQGf2jTG7g=m%xarC;u}Tv)~Uu@;fV7d?Q|7XH;-NgW0ADN`qFMRX#Hwx#&GHq zC<$O}vu(Nh&0V-uA&nar4htYOnn;jpqjziE?6Y@)sG87Z1RzW5 zYK(&x3+{34P1qRsD3s+&D+_l$R0VcaEA6HGVwCV&N;>yegO4xebnS|PBu4( z^8%jC+tMK&X>%+m-tbiB!5Rd%6!-Jsl$R^<(+NH*T|oQz{+tUkbQK^Z5G^ze+QOQp zUq>5FpIfOfozl?vq|4p*Y$R?*bX~5Rkke>9yrqeO9c^Xx&ARxxOg=ayNjusVBgbJI zaVBk?tLJp0i(bpLI>3UEiN+H4)iVEfMCL$Q5V4h`7RrH!XAWLp_h_Y!w_W7HzW-I7 zzwU;5^~ene$7xx&<0+EWnq;xM@|QL3~GQdVO_(A5i7FG}w)*@esc} zf-)%*>JKO;`yioOXyXNMLE2PT+F2aaK+6r(-zh(a$Y%Tsnr>tpu%I}=5i^LHeFNOt zw0j@Q#p%zZO2%>o4|g)^O@hmZQ4b3_T2tEF?+uU$#gWt|nCDY^UcscG)7vz-1Oq5` z+Fx?5+slRzD7zPVS?D;0cmA#EUmSmEF~2iVEoCB{H2maK8z@UEaP{ z#Y_j|CZ?7+8v)ZdSw;L@a18*;PtK5Z3UHe9-B}qdowB9h1~Z&q5WpEc_@qwl>qUF6 z!&oT`iGeVP(pb_Xbb(8ow5VR32{~{9fvti&Zp#Lk|6pksYJ;k0;I2WvC%s7;@P{W3VI;`04p6hV(Ioi=OZnv!iAGmN2X44qXROOt~qpn?*WAF>_HLR7Mhibhl)k%v< zYe<8YRwQz8n@g-o;qevL$?n+u%3_H&upegn;_g01+rTNJ6WAVu-L&9XM5-P31%RA2 zvzt%_aEq0>yTH<$-Hit4*^1C6fHb|5}a2#Vp2LGc3~1A{h_pr{)L zZC@%|p9^b<9W4VZj+21eU@ylfz~rzH>5c)8?h)LHg`i$hQ&778a4UR?vJ5b}s5gdN z)r_5{djKaK%?Me9sA3VnHMbA_V{-SNkAdwB3uMpE>4ucBS3jrIbWf(S82&DN&&{{o zf}^*gGzOP&L9H5}g%-k7->;)rOWcq2G;0^#H-*|y$x#g7v3A-OsZN|bqAS7!-=Cl}Vo+pnL?HD>jE^6C*=R?E z;$65e=!jG}>xgXaup@GwKu+(WOnU>(sbYC+m}_Vo-(gF<8jWWN3+=T9gnzn)r!`SN z^w-c6o2s439nr|$nSIt#@8xHV%cBKZRtRrs03eU?=mAV~}V`qJ_WrF1SQ zZ3T8rPMB=UjvMQnO@5vE@no=poe_#)JqB}agD;)t^yniUo-~HqxZu6;W;`|#>BA=P zSVQ5L;MdMU6#p9LL^~!Uff$bWz|R7z@PcPCxNU3Ym_D-$yT8z;!GBAa;?p_3`dGx( zZ$^k!H!JY0Sv>Rb0lQWFcj16fQFJw#Twuj6hlFE8V7fk z$n^)T!159HMr${Sh9d>ew9f291oxDnzJY}UR)BTTXf{CBU3~+RKv<0SUZsePUx^yy zY=C@dRunnNllbn;G)-d%C4wXa%n4u?Gg?vxF=!238xD{gXyE`hbG0|)v`~~}WUdbm zn*x!N{OLOOpE?z^(V=cCSO6l_W=d;7q3G#A0ZY5ihF=O^KuJdT-H{s#mxuyW%% zCTTi-g4!da=J8!zKD`MIRjsdnn%YuB!(t4n4qNedx<@!`E~-a|K8ieO+{N{H@i}Z- zx08uuWmLKc^7Sjs*F<$k{m>h$rRq}c(ChI7e#cyV8e(cs)T(pCsmtQ8V}P$m@mX*K zKw69MjL02F zdN(}r%fS+0l5W7((nP6xB_mk9E4(q5U;t!=V*G0?ZG~mgfSq7^r@Ey2qfsdj#x7X}E#z>tiN+Av&%CyqVG(xdLGURxE1wIflc#@m7z zvfacd_jI65h)Id23ka$*oDfKC2zCxurI;wbtx*FFhmYjW_v6Sr4s9(WfkgdqbLg$58=5`aZ!Q;9vKHimdV=*n|Ip5Nn^b>d|-pmB{aNtqv)@`VffXV_&{XZx6y- zw0d9s1t-Y4Jved09nO+^09*iE>iL0q26R$y;(Nl2kx@(TN%LaeeheJ2IY_+SlX-uN zje#ZFPe7H=oW=?)mSJ(hlaQbPI_hTa@8MW34-RZa&y1n> zwnm1o{FyEC{G=qcAsl-hZ(U9uDqv|K%_96TJoCfBz|n0 z3iD7o_ynH`j`@>tx41y9J9yZ8sejQ%Py8mT2cO}6FB%;9r%dS&vw}5$Jc?;VDpM~f-^;x)tX zF#JC-{Mm3#!fz=Z`T`tZ@I?K<&PU|iU0g|C=5UU0*@`Csig6!96whv z!L+Z$FK4p6{X@A$jlKj9tZJqI5`_6=8Tl+vUvovPJh~)bYp&>)=dQT| zD?;=5TPrW%Z&Y5yU*Pz4kSc-qaOn)gZM4!M+`%_3(_%)A?-$;b^msjG8(|W3wuwk$C8*C5kPAgy0 zt{}Jdm6oDpjNow_OZN<(dvMJa@ERaB^QxdgaG3E95JBmfdAQDcSHy5W`ZJ<|bqGC; z?+y^X6kSf56YLL6Sf8ajE0<=Fm#}T4_{FT_sN$NIW5oR-^P9{sDO47f(*!T=6aki3 zHRlBSl_yBT2HpAhURM?nr^X#ljM<9Zxmj_Y@lxxI92+PvxuK@edjiRPw|i2m3#RiL z1El5;SFoLfjdeVZ0sQv8$n}{k2A8eSz*iewV$Fk`qIsxe@N&%qFX|f{T=Q_^N#&cl z46RdU>!?5F;@))|wUL797%iZohKqpCPyUdVJ??Y1uFQZvPg*>sS9<0{U0t6}0HuzcALH zF2@#AIer>i#T>ymagG&pK-wh2`!V1u2DO1;iPj1G`>6l3*~*E3xwIu49yi880N6k( zV{y3eywbQX6Nh~_5f6^Dc`6EJA(?jiol_kUop;~4igfmB>W)f?qcj+EjNfa9V`fHWo4z59i)^mKP4h#kw;XJI)JVAUw+pvpOvPVvnIRg>$U%XdZR3 z2zFy1oW;irrEn6g2?8OEpTWZ7w0F=`R4d`3Peb8chuMff zPf~uV>|k&hVQ0{u+(r(gVBhxAX4e~3^U8|1HLE4^;ql$?m26!3B4%SIJv|rW7(sWF z`q{E953}3(n;>a2!gwR@%6_5o%64fyU4vHstmJlZRI8@YbcxOoohBx%a z?)2rkYdF_;VR;cA?o>POafRkAEW_9aWDINu``nXr?iQADem5$WQ(JcozWR_m3t!J5 zAzTb^J>xpZ-ToQIi#wf8UH}=~ad-BLi@VXT{f5-C-?X2W{rI~$2tRPoBiN5kH2{E! zsy$4UmL7@)DgX{OIrJu+W;G!*VbZ;>Q?WqEbPh0u4MB`_?;mwM^$(0@!*lRl(n%yPfCe0tb15~T-h zsdGHJZs~9G=`DW~En~;GT93(ejIOm(&3+>LsXL8w=s0 z6IbZE4$v}bY^|00anQD=i9jvzZf*O#V^O!(Ydfs~R)=S`KhP>XTKm`0BkL|;$3*9F ztHyNvC~68NJ0>}Y6=A?R@h?AR2nRGEjXRVQU5hYku)RBH12P?H>6 zmEWPC!8i|$1A1g!d0d{v3(-1D54ORXl=%uSt#`vRs&WTJ2nTk+Jl1ia?iv}Qfs(}q zwDh<8#L~Sw<JdF`HJ1-CP?^-<&Z2)1PymS03HL@wDUz;xJh zGkQ2mhyE1{AtGeY1vPq?QjcfP_}~}Lfe53upPd{YQ-m5wu9Wd@juIrwu2M$4%Li9q3xh_ zU_0nHv>g;x&!y*V2f0nT^k_RcAxjUoL-<~@VtU)*%P!;IcChzN@f%n(Ji$e_gWfi5 zJE$@&8E-pSR#Mx+-f27ht1@7=!yAz(?2U|hnb*o*=5)b z$-2??=*L*Un6A!Uv*j*W_W25=;@813S@xMM&2PhCa=04rgfmtln5V}ej+b(bGQ4`L z#WGtRHWAI_uvIqh{?ftYMUG*#yr-&3go;UI>U zSDLOUua$N3T1oR-*1C{eTlz!gQ^BK5a9dhp;c;@3GGI$fENk()(tK`RiD4@-tFt{n zZe5Ayht`$!#lyPNYtdZ7a#+?~zD~JT@Mh(7=ZURRTR#H!|4N64Zs`B z<}L@%AL~8x#6m)S1>0%O6MLsaPb?}lRvO-OChLjyh2~1rdt!4z7!r$KShWJa=pvrW zcq4J1OlM0v@TtVFOY6O=^Mwox+%YLlFIT4Hx1;ns$7vd>%=d~!(+pQk*O{!XqxhYy zSFAHxQ*%ytf7E4uohd8r6vNh;ECS~=Zk_2g#F?x!vE}?alh0>0{@JiS?+f{_aamw= zy(!;Kx!zRJP4A}YsJ!eW7a%^3De4w(9cof)gpDu~-I+MTb)9FSQ0ez6UDr_^j9J%V z34u*B=E{{@VL0kBieJU<6CHdje)J_b-A~s&f^bp%>b!7veI}62r16G)FzE+z@2fcu z$%-t>+aDB$OV@Rjp2q%6a(-QhWsTyINT^F=%J+x)1lN)M|3+oQb)7VCzNX!}PEymX zyBJ^N-_}^H=D@lRhjFg!P=mOxLv}CE#^@}67xks8;=+zn=faMPCuc3}yq>w@!j9bQ zTGWvn7j@*uMV+l!$&o7<$as`H=x`Wxr!a#sA6wLs7glb#sY;3P%`EOvoN4-QXqKvd1wvXWvp_D;-3Bb{6C8H3r#u)=x|2P}u#utcsB239<- z_sG>su*IYevr+kHLT`H!X7d_$2(9-hxJ)Xn_b9PEW}}E)d(K*XDoGx*k(_IXj9fBX zAH{4w!qR!#J-$n}-g7kQL+h;fP^~>?Gj_d)LMdj$H;>s^V@39Z^&aKljqT(G z55+iM60hQp2*2K=;BE}XdXK$gJh!INdXIB!+h^ac_n21LdQXA{wau^w1zPJp3fM7a zpUi`*W?f4F23;y;ZJ ztm|*Wk@(LmVZ5e`Ia&PY8wGxD4d_At9R}MN{&OR;=k4Uu;y(;~-1v`H9XO}i2>x^1Yt+&? z7XHJMWbvPR>0;#Z<@~;Fqbig+%b$+l0)1%7A;Q~R@rG)Qa7;0e#xb;|o?<9rb;BR9 zA3*>{m$Vl1m&0R}9Af_y+V%;ckc-RVF(Yw>yx%OsQi-kVY5Q*kkCAj1kCD@@!ef|E zQ1PudS((?zV=~<)kH@%fb3AxVuKFAvldC@IWFC)sK?1xmjs%bSP~uD_^?1z76IbSO z19;5M5G%lA{x1fyEYJ2IF+Q$wRcZu}kzWVR5{}$By+|| zP=Pd1nOQ(F|ISkMs@OwkenMpyVZ@ts|n6%+H9p=GWk)V;10yd9|>7>tf_-NA2Mzki^Uj2d3iL zLbYd7=fHI~b;a7Pmt9C*8>wqi44jwX4qB^9Y%32CBS$lsfGLin!qk64Oo*%It2J+2 zZOnfIn2~ymv}P=3r18KoBTDBmBN}ywDdI6BnujL`GonG>TGBZbh8f+?^Fgn>o##W2 zu=Uy$GqU&Fc|JJT!E=N;GaJQ>uHP7D6!Lt=Vn*Y6KKh3AQ_P412}X8(Xw1)sb2+4# z5ygryBL#0+a9~FEiqDNq1ki}V5q55EGff<3WqwNw63+tGd;W~6`}Q>YQlNKB&w z%*Yr>j`U-g5#zBO0xGgd-Gj?!v1`ZJK86`7*n|zwRyKPPW~2nHM9!6%kz7sIeNliJ zDV|C5`>MXxS;qj5W+mi;6n=`KS+7492%e31aaWp zfczfE5Bj9?so+s2xZy*LFp9qdq3tBt;|lii{Gf##KZs!~F{`t^FpeK|1C!^z^UvXd z`_4~Ca~1Q0K2Onz{Gbz9qqJ))x{Jj4F!5)U$`0~_{*zhKo^+N3wf!YHIDSxw4^b7$ zEc|r*1%{Sx5Fh#{ycOa@#jPcL$TTZyE#@zW4=Fjs{?CXV_)u|N!iNgt3h^PU`zZb* zA|hYMs-F5$gb!t>REH)7wE;6p6H2JoRK#0v1C)4rR> zhqj{MthE?vz}NXOs@WVvd`MC#O9mgJqbWZpJQyE}{6EcROZ+o@9fqHd{{{l%`8rDD zKQeKIe4PU*RQkP0`8ukDG58Qm2u=)Ru3WhtQ&v4j@jQo-=-}D-(O1;wuPL7;2p7eR zdEqQR6v$@MxI;de^dsf#j0?A|FkFfcDV)aQOmZF{Vp*ejB$6!V6Qmu*r{xn|NA}E7 z*^sZ3=FQi%!-tZZX5Ia6iVrcjz=t@jlCMJzB3~zA_&<&C5Mw1gq{ImisdRECJS1nr zL#cDOG24u{Gp}Uq$Q@@!X&bg&Z5y`SwGDeKjCJ!cY)C#SHe_DHhSD^|*qOqt!F&xH zvVevSoepfs{A@B|eufP_0N9ZE88&2o!iI)8gVp)z3LVQnHlz|A+Kickg)?->!nNm8 zXCgk%l{k+oE!0-ez=4p(*bh6>>U~hVuCr5LLTsqWe*@T$ zdZ{|7vSJ;R-?wysxca(bKa`$c$nb?|upOp|$9`z~ogD0khPAU%4f}aTFmF9Ed%ReS zo-f2_&llpe=L_*U;0x(B7or3BLQ)vd z7m{1oX&kX1CuAx}zEJ2;$QPmj`9k^)`9ccWbLqJ~UUHjq>5(twge*Pqg~E3mI+Fe2 z8j%COkiBox=#%d8;#kD{xHq;xT$N$9>G?vIl@$B2cVa&pX&qlk7OmUk^?oL4d%Vt| z%7QP%0n_n?SRz*m11lc+LUOedY%yu$3#t4wp|`z=FLbJ6=N>Nwmq`b{kP^%Dg%pu% z&smdKN%DLl$+>pO$R!i?QNGaqS-K2ghC}Z|`sqw>a%MSs^hhHfYh5^! z7|t`i7i{QJ4Ck5N7Z}c4GNnJv1cl+8i~3IXc;zvi2}orz93BmLDWc^TaikgU?;3~S zOfr%@elrC};5Ymk#c#|ri{B*qEW&S!@>vP~b}<`GQX?}gI@d60JFaxDa7s~6p|)eC11!o4zp53aeyHY|8V4W3b3Z}Q@v z2G5a80$+jeffH)*I5`)3HWM0mz#%k_^=D{q`%=;s92}A+(ctliE;LpPQV+z>gW_>a z)D_Ivbm-U6P2ZSw?7)HC$TJSOOR{eHLQve<+G`=cmX)Qk0|gymYgxi~>NR$>7Amt+ z@)V8=wUP5Z-f_?brj~R$wcR!7v%kxBn7#~0<>{2bM+1pM{g$ubv4`$tzVzV4K74gD zUwUvNTu`MZpB$bT?WjkYT;i1WW9Je_i{$gT9$seZzEQkP(pkJrPUG;h%sGl7UdEB@ zaW-;s+)8;=ML1bzc*^5sZg|S#WOwo7XdzCPYgY_3VU4jrt|0uY)wj@$#pf%c74zpS z!Ue4e@$igAe_Up}v`{t$6BlzNjiPfF*GYrNDkkSRX49~$(D#HGDF1n!Z&w27u?`ZP zZ(HKb0d3fH@ZbMQfIe2TjfuzFn}-_^im<+!Tk=@nF0}sIX2zSx`s5g5eR4ow%V2$U zTo3D$|2kM7{f~w9DUR`2-xE-%)UHUeKGnhcSRZ4qTzM7@{;T*H#gAfVhz|ZA{H%xd znea+pxDe|bNn>9=n8vRWtZyXTV+zCNu|8^4rbr&^vpkPPQi$J~?9b#ATt{}#sO%`# z$GrKPc359h)2zGCppX2X`n#HA6zk({o4_!Z-wZ*`lG^&)0nDci5bsk7dE(+;W{Df~ zjT#N%T$t4|I-#)*ux{|z7eed)l7+)u|^_V^f| zJwC=~kB{*=z{m8O;$wO}ZhVXtD|}23AwEV2;A2u-kB`Z%Yx*i~loOHy!=b7MMJPEG z;$un&_?Uh}d`waGTzU>4liQR_kNB7qvh;wDh3`3hOruzUkJsr)mcx4j4-d#qw7KBnNZ zz<`e_u{=Jeh-#MLtjW7J%$|WHIoA#uxpnZdXR>q|e2nKO{z1@(*4a--wf6YfSZ)-B zQhbbW9v`#DitGpb>6Cvr7@MOlXQ*XUZWP5Zl^EGir{FDXA8@1W70WHwG~z}%x3+z% z9XHCf0@DwyS0uPwJGS&9@Sq)9(q z+$a_~!P-)8)D|{!fVI6HVrv697I35H3%F6wf=!yjaT~+co`W>J9eX^u8uRMIWpFj- z*L_z*T#fQ$akW?aOmIFp2{S7>Bk|xnyccYgQC#hv-WRyqcQU0v%mg(LO3ou9tLeY6 zjv;Q;f^nl}AQhhyUYRjjTHN-cxv|`+i7Yp&D{j<8_0TLM%42JDa0Ir-pB%P^5ayZ1 z){?v~hTJISwQ^{DUK6$r?YMv&b&E=?;87;H;cJXAilZu<^+^DRyYbe8dECS~=4(mA$Wk}94Voj#pDBqjLaHG_pa@?p)x5;2W zX}8H@KFL`|QqzPRRa|{&HQ&jDrqF!YVv3NF-StBuG1o zugxd8j_hxb%7&Opnm1q54l_w=nswKQt^XRsOqg3>Ce$qQqo_f|%X!r=P?O2!e@S`lC@uMtG z<455lZ0D!ta+j4l_))FlBQRUAaJp05`L(&g0Pa-B`E`(F$N6rA054r%wY zxTw*`fG1U`=KPpB*c%M;_-3?>&O*gyh#hsO{^ZhfuXAXKb`2uP2f-Yex1uINYMT)b z#0xtUBjj4K)2_Z1jphax>gKWhH-I&1M9^TMGT0!Hsg0rAReZO zAM7tphHsYLBTzIz-5To&KZlzJzO$7LgZjf@{(NFMTC9DKLGjsRP<-|n6rTeOO0Ovf zrPnB?i2#lkdonm$`f1ruAxDc9E(}T!AqGVUph>Ad;tmhFb)B}7qveFGQxSs-9SSih z3Xr3v-w=aR$ev5jVNi0La_JF+azd6KFsSg|hOdO9rLRJOLD~Bzjesdfi{s(OIa;a= zt4)tVSyoaE%HD}VX|#73lq_q<(fT@@#yDCp(-_&d{%q%PvqY{E239;`P;#{rY%yuW zpj7^u(A!>wLA^w=6N6H4nU=ty_=UiA%GoN>;3Q2^#&ERM_&;PEEtWaKjZ%)* zsch*0H~KEb)=q~bIa;5A(Vc=qS&r6^pEBy_I9l(@cx?JECcIc?LIVTlJN}WxHT| zfxHEtQ%(FW@T@@U;FnxPt?DlFuIiUxp;^hcOXNYOR*fEDYT=GoOjn$T*je(J$$F5T zRb7da$S~XSVaUmvpUSOU@D^2+xsA&wriGR7@(xH7@(ZSVSpLFSBL>} z6#Sfxf?2*-5%!lE>hjp18|v-`Lw9>JrY3x^LhLWs)EKnFh9;KrM6`3j_bTFIZTvo6 zBY?Tvj-6rv`fZmM%cg{an1v(d(gmmMHr(TjQN-P~i_4hv=~{jHF*A>G{z@|KWnCp0 zXEcXqJ0F)bALbxBY&+=lNr^KXV`CWS9U)nOalR5C+3Xo%ABC2zoyBFwT*1A1@4-$2h4`nIdgL*X3QjmgkX33UN!5{bWAD zb!0y^Dm#jCGH*U3maVj=S$E|2zCqt_<+cFhi*^7it1aDqsa0w;VzpYS4#4xjodmv|JO8=ah^-6_fm_`37&VnW1TFPy zzTbs78Wm^t>xjkFPv3%L`ELMcQ_HIc#^P+!ZNu3pox#~`jcYIJ@{O!Z)ra9m?Q#^~ zM0J?%EZw1my3KOw=siiP}VjXZ5sNDq5>qZYHXgiK>%?FPckvOc1O6;YM7>dI_I?k;spb~W%#EPAOq*v0!-y1klRVXkt2 zwj9+>=vxv(wNS_^2FkVbddyR*rF$a}e^^F+S74Q@0xOmHGYnC78L!sLR+ehb6>zcY zZvJXvrS@+qODwlmdO6~HbJ})3^QQ9k>A`h+>L6o{YW!`6lY;|FsspslPnH1WtH``t z+S-}YDyXd;sAnve)OC88<)}win=WsIetij-b=6Q>(Dzy5;T~uLE*IGO%4zt)ZanMk zL!71^rZRf$mJ_~{h23(J?dGP2-Tsl<{6z8x*`qyn%V&?>^4Vjzd=9W%y{6c$UXL5Q zWhWMPtA`M~r30{AslMkA%B^c!CjOuklG=$s7&;VUw-g|DtKSg2Rmh%8&tbQ6n{w$9 zyLCdA9mZeIM4xZq@a`ALM7>@dsHV zR|x|v9c|_8h=nN zoAL)KR>U7v@RkJ!f6!jB0BKDl{-ARMaa`4J_b;1P7=Q5V_z>!D#hLo=lO%CK@DqE;}0?(r$JrIA^C%HwQLr zgFymLp}`+iJd@_9Z<0|mH}^L0(EZEwZ)T)S>&Ea0rAa?r{6Q8u!GTl$;1U}-z=4;$ zm`?44V*!7#=>FwRl@RsZ80H%x4L{#G9?X|{_2Dv@FZ1iZt0Cq~`LUSqsXh~51tLF_-t_=rhz1hTC{cBM{zyRn6uTU zvi!kG@du}>hwj9PF2pF19MXnEqF~7p_vKFx_eBWv%;LTYe{gHaA5>l|hsNhMp+Rv@ z74Qd_R9Xd(GQkb|WrR_De+X?qCVy~ijz7q-m6+Ap-a3vyxSh$9KX_L>kUuzs<|@X1 z&!ec$Ry%<;I)eS4t5n1vd@!>l{vb<&+P*Iw1n%%|ne^;^szRBCpN^LqTDn30;63n$ zY8>Sc7Ppr8gQi(YYZdSZl^kNfCt?SGusAO92Mgi~u|uo-D83IOB7e}TejIj~m39qf z*r7$>oW@~?8U7$^GUX5Y-ZX|ksQ#4W4`#Ye2187{&E3$|QVcQS4@ylF{$O$S1)V%- z3gIRmTl2@IU7cWS)RN(p1JS;cIJ4U)NAh2oU~4RapW{Cm^RHT|)GNm=fXBFcVaGyc z_225i6VB~d4&4=!1=!k5HIJ?J(RbGF#&`p4?F>}4IfmGpq)?s=wnoQT9U$Qcj{2X& zrc3-Y{6U7Fj(0*}JbzGWJTMbS$RB(H3YC5{Qfy6iFa}#=3Bey^%#|z8gAu65D4ya_ z5*<7pKZV#@5H5;0<%P4@S|FQAV<8_*I+9{*Esa zocuwRPR`^I%9$L()LG}N%`>k&Urp{jUyWz)%eAMo_vMaL)W9CZ-L=lAhO-iam`ETT z;w&@gYa|Z~Xm=t`gEwe#+MS4LCbVL=1KdILwpm5sO&;M2c!cI{JVJ}vc!WE^BQ!tb z5t^TPggjT@yxKfD&3ueSH~?+Wyo^O?ejRK-^3HWUI#;p?C0xghpUgmOUp(BAEuzEL zI_f6l_ao%e<-|HE?Ys1>HT8SL9W}aa4fuU$AEE(w zm?9nxqA3V8h?o}ppvF|g(w?q?wYvG{Q)>9{=2L*w;v3mM@Yw$<{p`X2ER|Znhyc{v z8HCf`l+ESU6$C1;?!w=LG_;+~YFN-)gL&+U;qkE+Jr=}gj|K7BV?lflupqsrSdd<$ zn4fC!_}HVsuhyuid z^c!M93fXh%IV?zSQ!YJXK~Bih0~Qp%+c1*w_%u2PSdhJM(nyr@_&6?YoX4liu-f!k zkYy#sg6y4GkVajH1lEM=VILR)Q@iZCH@X zKNEV}i?E<+#ZDfdg3H7M7No@TSdb!e?Kx}mDoGv-lALRYjNCd{(1k4B2#@bifjia_bu9* z=kZC>l4*jL4(O&C@ABYJ(>1t#Egg@K(YXe1SvGJXi?x6?c(H54+R=D?%2GQFWG~|J zDFIVU=Sm)*TrGjRMFk$8;^~;*R%%ZHkB>6VjHD^b7#^P*|A&mn$1*4QP0Hgtk1ZYG zH&21s+A z7Sp-dXJQo7d8+rqH+ghV=+AjyU^E7=4Cuy69zTB zhAy`juVL8Z#%mtVIyH*ZBNFqLrD*$TElb$g9n{Yminu#O=fUwc@N6IIHgp(;2<@}Y&t%w9jSU*Miy6EMAk$7z;iCT{5qpe;W7d`%zFnQU#J6- z{T94i1XRuVdy9$FD1R>n-@<;#nJpHt#X<7}3g=6lSG)okPf6x^3(N?QyIB{a46&u( zWIa`;Z!R0FGACV)C7s3HImNV{F9Z_tm6OxpL)&n1o!#$0&X%J6d#b4}R9eB2DWLseg9=rSLb4IFrVs^TA|$96Q`k z7KY1Xk<^<^avqDcJdZ>|T}tn=2=W}oKb=o-9oa{Y%8p`@%$u)i%T`*`th>*m?%err z%Cb=rBM55d*4AI1|E3HOk5mbH{@bINC7%B#7oPtn7oPuSGC2P&C&TmKazevx z-YJQDxM$SqV(;YB0|e?}I~)KnbLA?vKi5SOC@t{#Ahsqu#VH-Ou*4l7dZvACW3 zh~ajWp3dTSG|~@K#N&2PWtE;3+>R40H!m^V?i~Tv7|DtXSc8dI)hlIsmtm>U!KxZe7z*ak!k2R8}0W(4i2wqX2O`{f4-mLiSvG z4!4urluM7eofES3fZK)dIowVoS%BNw`)$MRR2hcKc-+pilHzvuPTWo-)cUxc>|5Y= z>VdZZmL+nPFtFkgx09=tV2eo`Zm06kgx>Zd-0r8?A;jTQa9Mc3?UYy^w^KwlOK{fW zQ%Ul;o#b3QWaQSt?S2mV$l`Y7aJ@6=L+fz3sMa308_VIMP>S2}&Es~~SdsmJ!=?PY z0ofdFwM8wPa=0i~#Nkr#mbDK!T=t4(AZr?NxSU(tKG*IHIMWK_a3y$P+vYc(!=->7 zQ}(TS4wp0qC}Ez%Wg675Ml}u><8d0)wH%VeC0EO4v1`ZTFbCVh;hk=BZ<7LI-qlhw5U^aZtbLK`_3Nx;tK%Jzs}UPPeDQsh z=^b6XRN{kp0SAjdN(hJ_kqH2+Ebmr)iwy4@P6_bq3Gmw_z^^aBZ?ge@TMY2Saqk5D zfXDUg0t12Wad&P*kRZXmrcgnGX@K|uAp#rZQ(KoS5C8-yC;)I50Ki7CyOIF_Cvmev zA^KNe0ageMklqF8Uk!e{fC19G000E`)+Pc-?@{;#gl*}js7%_2zj2#J# zf`AQd4yJ!w{-ZD;_5ov$u=CDBfZW*}t)PTe?G>J?Y|S8qf?*r*1%Rl?hs~Tn0NMtk z2LRfeFo{-90^lwDs~!RcS0lt%xf{6nEJ+FK*wHBeNHgQ9f!sNkldgwL~xM6 zf84-~St2iY09*?IF*%*TaJO#-kdrn5r=Zg)fVhJlX5Ka~uhY{7WfD5`!xG9#UoJLVaj)~-F7*;rPp96=_7rWsT=~sih zA2fc3xMF^o%<%ZqVKU3(3&-^7O6-Vy>r|h^9ew3-+;|4^KMr>!SZ_#Rw($`S?`u%w zr&lw;1qgk9OE3XPb4#0_3Iv8{hhv`C`@*6npIp@{HnIAetN_}0Q|N#s2r^hbZyi;=`8}dOE%U z-Wi^wrEw4U9kVnJ1aX8s$H$^zKJL&~Qaxm_MSdHF`?*qr>B!tyuDlnfN_vdqXS2ga z2VaICXK$8Erxu5c;^*dsA;&(kk#t^^3%b#8&o2l^K5nL5W=Mp^@(3f#8^!rZl<#%Q z`GQ=c$CCezB6+*JX*8d{<{jFY)V%A@u)h9Owv!sDHSA!u2b!=`Pt+@}5^e@WSMA>atUx#|L z?x(R~eg8`7&WPuw?APUrXoJ;3d+D?ac!U_xtJCeJX^iRCEA>~IaWFsPuL=j>&hUrS zzYQ0qRwH1B(8!s~Vaj-5lye|a5)jV`=QO^-*8<~S!7heBlg{S$x84UHmDR_A1leEg zEmC6_mUqC7bEB zZe6EnH)=HY zrSAi+oapsq=h)k%0Y@aqC4__z2cSI7aX&_$X!VV(wT0zJ;{`W7Z|-gCZT3COs@4Oh zmah~rwRZxh8f+b4stF#FoW}pQ#HTY^qdLkQ2V~08(or2*KvyRND=i^YxvC_+EhcS{ zsj6cp^tKm3rcIkyVoKHo8T&cxHiAzTm8P12PnB>UK2@Zysb?*2l`9XQO3pP=Ms6MW z^i3?O(H^;H=z*B8zANZs33hrXcqAQXchYg{t2eW0ve;>bvT5&1u~YRauMai<5cvln zs49Z{j(BIoP}L1m43&aK7^)&@S%U#XwO0(y)>gt$om<f9FaZHAui|IdD|PGiiR;NH4QED*bJs>NzP>zcDzfRO*KdM`e)Kq1VQ6)VCpBZ_6JKj>`P|a2XtxWsnCs z5h?G0@?&w-PxwsG-k5~FlHRZJW$z^$BELWOE8Z74>Px#}=?^nO;ixN!FUN%}194la z@J}n+b_>27cbl`{yNDY3Fa(tul~!$zALK!uGCdr=x0X87$ATpZ}=z);#o#5?|6<2DN10znG9 zB_D!!m};%U@Vtwp}yav_Cn2uRPqQybY&@P3}JZ3B-wvwd>KaxqqcyC$B3ee+$p9#q5_Y zHd3dsY`2sU#?`3{vb$YEk?X#!yffS7MfJjz%JEL5VY;xpD5-K<>9NUHK5MAq?-bNSsqK-o?3UjvGhir1}F?!KqJG=#EqH zyRZySYZC=Wc=pRP@SD}6;b5O(SkYkL9;%Zy$!_+EW@#yr2cLEi!96D1V!G|>a2u4b zQZJ(m#Xrg_uV1u+hUuTvC}FN)X9JMd=^0>`=(Nt?c!B_*_rl%fa&LM6iIn`N++w{s;(Zf1R zg3F}1j`tsn+c5Moa2p@TINatNC|EjXr(?6~VGM3#r37xn^eb217tW<@Hh6Nl4ORhgB3yOp zoY}kZ)3d;5Gr<9_-%FNk^Xbl{ujK?NuSDZ9#g2tS)foi3n2Cndz!Mtg% zH@Blv>CZ1;m|_zPy#-kHbJz)V-xeslOxy!YujTJeD>G`pFK9g=K!0~Yl=ZB zb{-@`-oc_lC^;n91TJY27_zxkw{#{O_B&NtdI+ccQ-B?qlirj|uil^RO_^|S(xn#& zS$aS?Tp&x%7qr)##1`XTtPuxU-_)BzW;faU5CDO}5Zij&b^w;>g)qegbYD2q{PHC! zEHS_QD8drWJ^_xt?^6a#Y{tC52du)f)Wq3beI&mn$@6CH+BUzI#@PNepp9u|+?CZ{ zdYiqg24wfr+u~iVFitk-y4hkLB+`H{STn8$>;=g-TuwOM&Penuf|*{Mhew{)J8j2e z3EMSQyRMP#%_1oECMb7$@AN>)@lQ<(tq>4wqoRNO1|ib-jXM zmB<>e<{u)bYf-jwZKJ)qfWsvkuFEug4ORhBk}S(QQ!aWXaDh)Zn8?_Y6X>jBNc*lls)_D{M&vh6^L?Eo(%2e&$yT~%%I z+>T!hUAJcKAmWZGg!N{70fyjQO}C0&+ZG2Q4JXjGs4S4ZAOYuUs_tB?-qmDX2bgem zJz?>DLC@67#3zLWtU9bO^;n{-+~2Q zj`tvNP-ew*5$wtG^pVQ+k-bN&gYw`=Wq_x%{-DsgI#ZrGQkkLXYI*4(z^^5Tu zgXk~MU%goY@$hXuj5y5JU9fKFMEf@$V4I>)oQT}Cq_aI7e=L)N2Dqi zx0{pCq72wigFR7CXP(IpDl;#bDgQTfYwcVS*P%$R3c;^CLC(`JL45C1olGCWdijwy zYP^HG?$(>b$$ib?%)Y&Q+l}V%&ik9gc>mtLSU7?^))e60syE=?kUJ*Jci!KxR;F>6 z*j}uTHTSW3>ZjhKf2WjoALZPrR-*pB-*kPja-|d~NZ*yK* zYA&KAci#_1Qh(qcv#_3U?y9P@;;NH+ldp3pQvsymt5R{@A8~z<`3v@@lkEZ8XukbN1S+4vxU@$X=clGkpZB zv(?!nSh%Rp9qC1Td$qkd>7_bK?rd-CsZx>Y=og>@6l)uzkHvM z=JdW0&FXzITHt&6^7iy)-Kf5}`$)e!Jf~kPm2t4my?5`w94G6*F{;n*$ItA(y-|IE zPYe6@!T`lpdJx*T7l-HI@lpoZ+r^i4*xYqyA4e0Hxm3OLP0jMDrN-2@`y4Kvvb(al zZ+P!Zc5j6syHi@0Drzrnf}>TO=Zg+x&2;pveI*T-@h0ediR0yrJ*s}n;tJI;9(ZO) zWycnO4i=1aPOpbgzPao{hRu_Ckl~vc3)X&`QEq)HWVsgG! zW>1>@IoPKDne(sc&#`$oVhmoboqxfqY=npoa?{1>UZwmKfjf9KhZ{&q3+l6dJdRkfiFMR#d zvTP)M`$S~ZH%!{aS*@*>_?dSYPNl?o_lK39iqHy*)2^mP>(tWcu09#0MZn~`iG^s; z;M0?8hTRw|Q~HkdYRXMNf}IjMUE{pkm+>Z)arGN%L0R|p#NX$0Y#rJsHYhOLX(9<< zsjilj;$j@AlpYDck8Gg4Dq|N1Yu~~G@(B5$d{vg$idVJb1?R>aE_4Ias0;*8R?D5G z#f;AQ@3jJVmS9$`qbJ`g(vvYd_1#fft550jjIYlk{SMul67+4Mu+wQ+woleAi>0tb zte$~BU_tav3$c_U{|)SYWB;vKFMcL^v2`$FyZo*yKii)CC%!LE&%|Fz5ES_HyT68C z{woikJZw%+$Df4S;jO%R^TE3!w`-3;0$RYrNHA^z>#YTTZUO5X885ehwYyTPuE8j; zmKWAuf*+0{tk*Vvo79nm+mni-4iHln-d$-$3ul*?$af*nrBtV#ky)!^c9V7*9j{DQ z{j72@@`;SDQQggZsgCk%ROcnWmT$H_zZ0cEABxgWatv-EsLW*IkzE`280?;jeu8?iGySG{3J*0;94C=@ZYT-X|%SX zESZ0I(*u0SIBQ{`T>U1Lu2TY43j-oNFRl=$=fLRt%QwKoP4;;HGjN6e@;gFYfyHZAR{7I$j0aFkcEqKI zO#{IrNnnQl@-rBn&E*i$K}MvS!Q78iwCQVMx399Q`Uy@)Th6FK^#-gsAO^U)3JAiW zH`p;lnn+cHKfExc+G8^T&{{Z)uL%gQ#Sk;S8HNBbU}Bh<`$yiC+iR);KsdsgO!f%; z%}O!bn?;d&U3(*f>9NWF@@rGPtH1nmyttBMf9eH)fp)=2##^QOz&KYg!b2OskDS|ZQeXHL_1LKDu@4Eg*t8d1$J)dsXI=E;AFJ=NA&$~{Bd(! zDmv=7kIeLjbp&8kDztm&fKwez058B0wS2OP-j^CZ0~K~0n7Ca-zaC4dZ$B#dGDRU z##Jf!H3sf^2GB$4!5w;cV5pvNd<;J*Om8YdJ9-}N;6T*xqto>9t*n z>{H>9;AYZqr`TltAP%EkO)hgnVCtLiuI$!3LEBXEmQLY?7}cG4WhE}1((I!XvOzgP z+1UoBXl=7p#*V~B!~JFtp~czI_T^a{um5I)CSpdg7j-QOa#ZYDwl_ss0N6C9BW*@U+HAH+y|fRRx-r!OzsmL#?(-Kw zf?9n$-+ht%sNb>fh8t>Bkv_#nM(L_ZkjBq3?68NShps)!*xKo``!|xs(;^FVwSn@fhESF$XQ}$L*V3y5nGHZKDqn@YK*CBPr5Ot zdi2H^ZLx2n9`m-C8C0K>L8ZwZjj?3C{&s6jCdWy(#-t7>$r_UqIBSeLaFT5?C1c#n0)e6^MN7hzvBn*$W)r)CNq*!^S^vAG8-P-DB zArq{veuP0{{^i>09tFh1WArdnkN5F`OQ~|H&qe0Kl~a?!=Czy*IGK4No(-0hs#{`Q zl(ndbKQa;wXCp9q;xXYm-$$8euB~3GTvp?sF)5Cl;o53ej%%xrc7n8*Ux+$+ne6l2 z+A3ri?A2oB|)j_Si5Xz4PlZ8O&6(RQd&uFE&)paodgq{D1; zc#r+i1T0{}X$tyKp2O=I`;V6Ar!PC|=6FMJ{yvr0+4^vX%lv+E8G9vmW1W2mSp1tq zH^1Oae=az8vEU47aRzxriZe_+69>1w?ts!~W5JTDt%fq1F0KL6igXkKQ<;t*$94pC z(2Hj5nM=}p`*m~`Ei!9sRxg?}Pn^TsKxF{DtPb|6UjSn0q3iTodwbD>2@x>Z3q+Gn zTb&cuhVg=hZGaAocpNtI4BQ6UP1kLC$S`7okLX4N)GLJd03trLV)?;&<_MceYt(y1*qgxHtT}R_dapJYPVUt$nth~ChkjZ?2!pMLa>sAr z4k=q!bt~q0-QU{`C$6$1)MmVH@voclxL;(zK9dBP)a$e2^neW71I7Y-7)~Wm+w3Vw z7*Y57!zE=D=(QCA4~!2#z0HhI4~$O_iqD=HAD)8vkT(>N@xg>bJ|Un7#-|6yrw7KT z2gPSkj1N!4vx0^L5n+lH1h6Aj^`anP2j^bBlVk)E0usgV9DdO_c$#Ok9IeLtZGdOr ztiZZ++=QJ$^p#=wc4D{*MKlEP6;9m{Ujf5Ilhg%LYu4i~bVBaJtDD2wpin7Ef!)%R z6n211gCXGPSx3gO6e@EV)h>@GF$`Y;BV$&b`OF(0lI)t`2=B-3`(v!jav5ekmtoSl z4BLJQA}O8Wt3ZshXXx%i>#SYGI0HHZZ4Yw{=?s#>7@OfM&@uiyel&{t4DzRello`) z4D>%1pFwf_-%K1KpW(t@$Y!^H;;z#L| zckoI0DdaQ+;i7mYFT8P1Ll93UeKX#&>95aecs$DFI1MZUI1Nd0z-dUH!D&dIyY?Y= zq=v_`6e2a~Nv4P+HR$sUQUj~QlNwmY+s0`K%I;0upulXW^*9ZWLU|phVcqgt8A)o` z%L0EzF^ z*cO~$vDx)Fb+-5u=*@nuKjbxht6X;dae8)?*YI?t!GC4-?U~uFA+zDPkl(e(q8pIe z@Cb|v*YMn=qF7JI!E0b%9p8Tj$vJ*f_gqV|LdYg0x1t3~x+)&F*5H zc4o>|teC|1qdoP#v&UG5lV*=m3vRc^$if1scDp_HJ+;TE=-;>Ym}TyJV2^Q-`rmAi zsbWvMJ*HX=?XlbWroHb&zNwVqBw1rpf&WRqsS-4M?50~mVW`eyHMD}YH7;e!Rtoln zd)~rd&^So(@Ji`JklhGx;oS;2sK@Ws1IP(~#)tZdON8CwIhhd`tS62LjXFic`BAA6 zr)c<7iB!chW#upAGkid~uf~7QUoXdV!d}HShB)CfNRE7lw>v?ucYO|WAJ*9cMSKRW zMPq4pxQL}zEc9bNc6hq}o?&DLymAS68z>GC(%%K_DDRR8fiO6o3({4EsSX*Yjcq0j z10P63NPiio14_gqC=m$JWPqmRL5_epTtSbZFiu~FY`X!+K8|<8Rvxaca=Ddj;1PB~ zbBj7@5n67f*~)F|!W?;Ke#nnN9NB&9Bu5|`Zn<`Bh)g7`FYa4A@tlagFtuGn9|tEQ z-REv?9L0YnRj4>s@SKED51b6?5MB+`ukMd><@gXzb9}5$!Hf=J2|1?%It1dZ2xm_C z5Hm;EdFIGfMlL2QCfoXKZUAm*t~Xg87{pk-9ZKHui? zuoJt^nO3`)193K@+=La>Xm~!D4_HgJMe19gUP`yx<9U?%a5hsxNb()dXZo423EzPc z<@pXDWFtAggPzCn9prf|zJr9ad0KWvYGN3Sm~b6a6U!~bWVH(xF&U1l=pS+ zdxz6O^CvKX*`Qmp;~rN3j^cW3e0WBw4$BGEfdjYpi*x*};d`5&*)YO$cm+gKp2N3* z8)bP8??kJt?PG*_o`W1io`W1vo(#`{j_dIp(ijT`YuX4tfe{4zlY&b6{o}}a2EoeLW<3XI$kWBtVu8Uz7WVQdbcdkf!ebAW#q=ZlXmA=gS#i5Qsln@ zo`Y&nooy`7;Z08`&w+wPJcs?zcm6AHW4UJ+7DJxHcah(<{Rmkur+c8+uHnfG<*WF! z=D_odF4 z21Yp!H+wI%(5Q(v7H*zYFRrIn(7&;Sr1n{#=Is@~g(xAy?RJ8}c(1 z_F^2ifHU3JJC}h+Ipoe|=r7-o38?aK^|=hZA;{MYtB3m{a;G!|2QPGzgBQ9u<{&(H zAvxNG2T0)Hg^sl%k74i!FZ9EM7y5Saf)nHU3~X#10FiI({?cAVrlSQRko8( z{3#GqatY5|FuFx=$_`#YktTb+q>-mGjcg~F@LPP+_xeexig^hI_S2S0*iVm>{WLi6 z=8TP$y$ZXvIkcb5&zV+xGG60ME0jGv|Ei9JQ#=FTpbRJ+P@b7zH-f|2m$iD)yxF z5>$(~^Af(_&%u&1oFra?RN#M-m!Jegd+bV1^6;+^&)SK8zV?J-JZo^^Eg4HIV}y0K zg!b7uBP?r!4gF_dSl@BjV9ekNvcb*{@-S+H-6`??ez3uq*OO+0vE_eYZLog?qEv`u z{S|>HyUQ5+vh6I=KPLR-?>OPKbmM1{K8?{OIMx-6=m!SJx?|=Dz~nHW{_?9a!%l## zYw;2QS?|zDaWa6cF~^Wn$G>mQFU#Eb!2IGk*BQ0b%3qhX`_O+{Ar^O#Bplif4)j zgG10I{$N}!rl{7hD)6mEwI0j_et=M|mq1;TG2(f6@!%IxEjG79wMLrzWI|hEulOIq zwv=FKOFf9gBmWg$3siLfPkLrmO8BfUlyR8{PGUKF)>Dhmv8wR?AkaEwCk4Dv0IWLC z>fSUAQvN`t^fqM4@j~wcnczIDGZ`f2U!G_6BMJx>=#T0FywH2_fxOVH3wfbAmxU3R zVQ16{M`Y!~4bK^M+LJg{EC(uU$N{|r^UX7@9;lpF<9qSfaO?0)tE?P3pvz8>>y*EP zdimt~98k1HANR*ZJg^F9SmCx`JWqfo*~Lk}(ePX_n{fCQ?oZiAhGz}lHF-m}MqeC) ztV6L{Acn%TDo=4i;n~6ASN%H4oVZn{j;n~o;l$IO?SQMJmDwgvwP91Et!t&bi=i&Tj1;{ zs`NYEeY@{-W;%Cr@4W9MA)(2g32zt@6h%Q)L_onA5RyPth{DyGfXX-_x~}^K6(J%b zSy5MoeX6^ztE;ZMzSbu`*9W?|yXvlw_3f@2{@-^__3Pd{lK~S5`zOE5?e3~mRi~;> zRh>HL)Ttt2K!G$^g%Jk~h3Ks-Ux3FNn4r=DGTsVwtW{{NX4n9lVu}reuR^22x((v1 zVeZATt;C*F`^yU-FWu^xZq3?rW?*Q~8Fi_Q2ihaG+jC}8fChwE)SgodGC|$#Ic?FI zNA{eNAt&4M_fghz=*J$#O+wS(kFEjoK)ez}J4u;?6votBtw!lH8$BtjobxyJE~ zrCBHZTzQT45>}$0EBNkd4Do^?7}M>wNAPk4yk3pc>lJb0UER^WQkgrjKxW3qlNVSy zb>3DbG?xw?@EdD@8agl?I+$-%f#f6#lrmn0rXx=EWmgrBFE2&DUr4Y&*}w zgKp8wxUkNq>$J(QpaeLf+J;h)^|n;raBs8?`6JV9QLdvfAsWX$mtK-Dydg=(7}kVX?Z)oHuzks_0_Rw2WHIE{gU$ww;7*a%O%o>^_Xot2z-&Tr?QLjMouLUPuLD|$D zDknI_HdROH9*9QE@FxcET5u@V>kh;?1(j{*xr3?Cpa(&U?HXs&+UeL6`z7x^SUcG}9euu4N{9XyU_!wKqN3arN>nLx+ zcNv#XvLwOQk*#q6Z5>l}kJ!6Zh3|u{It)o`Y)#@$7)-i=Q zm^@_}C2<#9#}up=Ztg}WiLGM>_EDX%DZ9j!|C~gxnx^seT~)n8_#6+h9a>5++ZmR% zbtJX4E(bPd&_*_{V)q7H$8Pb)2T5xYQ|%2sMDv@Zj5ODv&UKQ zMsN1Isuu#`g)1-)=+x<>8G&zBS--KC76xbJ#5ZQ5mM15EcHB_Qv4c$6I}+EppBC%W zl)d8JN2Yy%57e($xzIJdnD2 zDKITx-VNS>|GM*Iuf#xKXc>O&e>03#mLJ<%Uz8vFa3*Sb{MheAEP%-0?t|W*xE>&W z?1mH$oqGFM#j%G1KSo>+8b3xNc$gpKmiu9!PW#vl52BAv68a&_k128w0zbyd=>IZ5 zCJlSg`7vq5hxxHXogWioI7s}MkiY}Uk4b@Pe(YRj#}1Uf-yMab(7~-MOX&9BK;|Kl zmpj&1fWvZh`)`tjP|^K~ym(<-05tkWKyds;FII+&zH@cdbXFZfQFKq|X=%bl3!7Qvwjyj-z~aCa|_mevlW2)tj(}MO$Pkc96R^o-X8>mVRs|X`N<;(XDG-6vP=g&%5)18Y zP^eEsmud`Ag5@_(ge~<&%Of$HC_;}b<`giS=;fG^Tf=BE(N7XOoM3c>g`4O+wY3<*%NPeQou%2Mcv&kZ zD1y_|fYW~LChqmsZesIxA6MEL0|^O)*~AGz4Z|oM3bP5LY{rDnIcIUZi96VTu$z!) zY&Y>|(yNi(gnmVK6Z#d|O~}`8VK))wowb{g(1UL`A;q-YO-Nv5H=!Q~$8O@Sx+t)J zegzsZb`zUZ1_ll;v^$_aq1x>xPFTWjVhNv6BWa_E_ZAw*r?7N$S+j?PF1s26$fHWL#A?KTs) zp@$Y`AuQ^!nUKJ=&4dJ?+>Ffx1DCRykoYBRCK$gzn+eHdahr)3pi&~15!@2pBdZ>T zcd?mZuFlRaw3*n1%6#S@O`$K=qO*=zHIr+wwYjVu$hRf1DlEX z9X1p3d&Xws^{j;0Ovqc>W zWyP1!tjwV6oMc-m&-39;@}V zXKY82wV5Ebv_1)Jboj_z!~4%7n~84m%dnY{)|0`usLjMzu3KxyV>g?L=Ku%(v&UKQ z#uKtO6L$mQg`F4&a1eD7{6VD!^C^!}#+AVhm^IqHL=Ei-f94~To9R?)WZ^Yr;9S(rPgM6gO8rNfJFosSDyY*w;u}#E`lVAI|IIj778 z*Wt$AUM;%2k0??$1oMZSk0{0KL6FIFLQ2WxAA?@`M#}FIcHqWG)CR<4iMjiEI-77p zs$G3Vn1qFE$d__U7A~`hez^mpnzM^oM^dy8(F*Zf)xQx@Nocc&AsPCwvei^?~ z21%T`^HZLr@{yAvY0lJ{yU(LFzL089+{8QqzjFOk??Hr_FQ~4I21)qh)aFO3f9g~g@LQg^gN48PIYASvZ#If*OoN%zi78J~mDrOMWIq{C zQt@!5@2KQpE)Uc7pna}J9Z}(zV{$MPNB`7I@R6Qh=t`>6Ne%}|^e!E&qW1^Dhm@3- zmb+iYX%hds^H@K}U|zUdd8~`uc&z$telX2bWsFf@l8IW(_yOf+`K|9nOS$l%_rd;w zIL5rrVth@?9(4(R>lY~;I{B?%#<9z>f5`Id2cEAYt_O{;V&^|(`Ko)s;B@7yK2AQU zjj#G!azp#!tHOx6;IxV9;cc`Cs7}o@yO^!If*C#J*{VazZ2?LAkhOeJx=XLcr7r4<={EwOb?0&fR~cu(!A7zV8f8_0|SuAuN~{x;Hj2l!%&oj zuwlr_ix;*9z=omB7}_uh2y7Sxgfyw(gq0i?MHVy(u8;2DJkaD-LqFwKkjYAOGI2=iv1+i{8l2`p;3+Xok zgo!HzK#83LfYic)FogC5CP#k=fDu~ zCTWWY+ByVsEIqM!xK=G5y4pJ&*=g@!RQTn5{!E|e+tc>ZAV z=LaaTsjBY1WUJaf^s=q>z-WHpy4^H)s5K71ZC$- zYX|H*6Ke-JcUIjW9Trz$?NBz>4)pqLOcXQ_c@{SUGWD}ufq--Kgp-b9CtdyZ+ za_Tz1yij0h6A=v?*~2Ym6QQ}90jV|-nV`jNB6u%dyPj zb!r+hV(t$+ANcIgE}~cMB2M1!!*@lbX=WhoBBr+I6K@jtG7x4F#{of{_*Xl(rR*Yl z*@v);kY{Wc5lGKQb`km&*+uAAWEUY{zlB{yly}xHLP8I|U4#_VZWkeekzIs-92~od z9$iRClPfVN8oP*-Q#J+;e3Lu`>>}*d?Ihul)Z$+ zFJUji`2E>SNFIyZOPn#(-ClxK7x4~o?qV;&T%DcIpuGfNz0i`GVy=eAQuY!GY_|j7 zXM2f`@|DHRmcML!3FZcSiMTqjmx$kCFA=|I>?J%_LhL2vEp0C$JS6NT*cu1kUP9YY zIr3D6?}NR>lJ$-4B}%Ly?Ilw66MG4*o~zYczP*I>S;Ur0;>GPHw2p2!I_a>NP}o8z zY|6HwtGz^;##OJs9z35lep`z6Q+dSp5#7SFHWQ?n)-k|HhY-fl{^O5Csjp@EwzF?V z(VKAVrRKQPzi$P+$hoyrvtNgrBLi+xn~8mY`J5FB*v)35g);ch9%tR9qFJ+v#{lPr zc?^eTnN3`R$zwTY6Eg2n?ymQ19TzvuZ(}!+vSlE`UF7@O{!PjE`y&K8?I|7`MFHnd z`Tl8fY@2*P=(&}96ihKH<-|8tPGca`zKn?m_E8ULFp zn!kTC{)bH`u;d3LtqUm_9Sw5Tv0yY96Me1U`UUkMji4cVZ#;g2Z&hvY&vb%>eJGkv zC|(bOP@i*8B-E$;l8W_q+X<2BAIhc^(t3x4>4Y?ATJHZjv}ae_iFYHStL?qCoLO|o?6E!07*8)=zm(r%XUHCc_hRx`Dc{9U#tdBaYT_qwu*OORR`a8ZzYUhSe<+D>CkcFc@;4A}3zVTQL+^;Y9 z%p-cUihX1rhw<>U0S;*MlpNysXox;2%+K?%UcOP=&cJ^8?cL5}k$DIbk;<1;zNCUr zW2AzB-)ACU68Vyd&Tx^R4+$jFc#_7GH1JT1G*AwBOyfx!PttgjrVPV2rpZYfk!#|e zX`oo(i9-AkISM-Y%W_H(g5s!Qfu||qxs|70f&$Ki1uBqq>V9;ta<2n{={Tg(zIb3} z`yG!SGv_LY=zkeGWzJRdSeTE@;M5dIK*KOJ?Ik&oH_ut`LmvN1{en<#(Ke?lOOXfW zvJdaddH7b|TJdT=oGa&7BZWCBiGy$EIAwIMG69EH1&)Q=;fD-7bFJn)2xP~%@AY== z!q&u58job`@O>M2XBLukhZ+z-#_kNLLcxUO^qi1wQKtPS?hPA-@EDx=DPs6-5dw8$ z0wo;}F9t`+yCG1{2af|>+gZepoDJ!#+%K+4`X za7;k`qq28sg(x)NYmZ@I!E<&2XM^{mSPsEL4HG~Xykg#>vt zx%q$|FW>0X54V=D?b+)#3VD#%UcBdl2X`ZRdYp#EFEYdnyvPT^-u&R6JnObH-ze=t z=6J0JD}(B`YJbod^lz)KatDG{dvOKm%5BwJet1tF_X$+%K`p3nqfta2%XAezT|wd= zybc9}!O*tqFd73PHH3@=Bd85tMuXvCbX#@I9Sp|y=10*L2*lQjXl|>H2jlrhfS1+o zSg?9;FwO$jSVsL%3!-Om<%>t4c+VGrx)DkB^J%5BQvN80QI1Ab+qz{9>y;ye6mk|%NfJeV@`r@xLD_db)Wg?K`0YW#4-HcTyvIvCUh9AN}}WV8YFsI z9mm<|s)cKrZEi z9%(mWA7JUnRdKvXKb)+ZlX;zS0j#T|b%c0eCwR%R&!SW|i#%EZDQ{U&7 z{0Y}CH%ASRsWcO9NhE0(9Y>~)=X9f!McU*1PS}(#VlZmYv@Rn}<0;tT1eX0FdXLd0 zEb)U}mcXcAqcfkRmey`yqe(F4v-*v0@toiGqu-DbCv6y>1NBhoWn`S)LhCBUe zKL=&-pFPfc508Qy_yRIQfZg9e_evnV@L~+*WhpmK#jLR$tVVmsli~{BV z-OhxjbQ{Fi^ugl&n$m4t5+Ts3+qfZ$627+Gm-egT*yZRpI_XMiqnnQGKGK>|Y!F#d zY>)@%+|2xK$IbP);j1y~Ay=Sa39`p zx2BQEZq49V2qC8HO9As)nKMGl>53|FE7>6}eJAsJ60EtR4#PWlBIj3g&Es*$8U@)p zwY8r+12VkcWTMDZWt4xHtvnK0K~C4?V^Q8+sq(Y{QSw!q!=`k<<)U;?ex-hpR2G=t zw;tAO+<#x%hrp3g()FRJ*HFA31ic35ork<$Lt5{U&}&F@KCIW^{0y;yk5i&>Oy*UW zc_#D#={1BL4w7C&I{1OpYe;cvy+(2-v>T80R*dF_-%%c`^GxU$GCWo0Oz34Jo$S`G zEjU%0alnDKGs|zi7Ud}ux*UEh#a9u>1H@O|k;0+VX5`K|wvDe!@k)su$U*0;i0r}Q ztJwJuS-$GwGokmNuR5g8gp$G!*)ySv+=IYoaT>Y zoU|Ru4C_fdk`GFq-0<)DjNoE@ewc-$9mzWkAp7zWn0J=1GlzPz3b)r#Wy2@0oF#?U zjfNwSgpmYfQhtyhz@bpzhAtC;v@0Q?D@hVS9zqpB&xR&cO;Z4;hA0V`Bx8O#(bMO7 zZWLg!JV})*i3sG1l?yN+3@lAjFiZiEO5j6#q5&6;bCSQ;vhC zGp3D^SnTkYaJWn|=0kueG%8R`!YqgIfWcRxaV%2~!+N4ZLm>GY0RX;y`NI3LDV-sVJ4`X-OA#3j?=T) zzsVd{+tX?Hfq4;x&pYe&&T-ms8G98Dnxc%clFcDmwxRy7R>Y2FGwn+bhI?&{mjUKY zLzWPREPBzoShC=%^CFE;VAz5az%WIDQ}u#;!gqV)#O*8hy})<^!Zg*wDzcR&MsgK0Uw3&4(Xi1a3l~t`y4oQTUOE z__L}2G2pbm3s0@JV|ty|9e5dk4Z0<I%Cto!0_o@;k(;3NIV&zBtB!)!1(>yG)Nv7XYxqf zG(2v!yG;YDuBCEbWqYelgYaGUJact+wrJD9S1)`!$=RFx7#>}18dB-J@Ne3I*&oZZ zX-MUhDPP#9w(^&4)4(dgrXj8lY#QQs*fhlNm4+@7#-`z2tcKV$$eZw?#->4*4%jrX zJr0;nL#pl(vzuyg_R}(L8kVfDsUvL~-pdNorXf{7v1!oixmvwgC%KqSLke#&vdSY% z(k?a)DOfLjXE!=YY#K7K@9Bh1*(r2Ax0$B#w4dvz8QEhheM-g}+d0HxS(^n?OY7f( zjTyA0?X;ikZt=^oX^;^ogDt!Oqq&<+!?!Pcq#2L3(WPt}SntNuvIjPQ4ulteALC$I zHVxY_YutC6hHuA>!ZtYoHVwo#?yE%ho$c3@O~Xeb1UhXRei22$Mq+k=fnEOO)#-xF`9<;NXBm~o@4?REE58E`{f7$yXVADXtJ``;l6t4$C z*v@$;61G2N&uU8R9TGMT(wu2|{KIE8A9$MvA%}xx(;yxEz}YlNacMbxV$;x#$La~d zWBsA>Se-TvM~$U4!@PMX&{aVh_C8R;m~Q*&=<$H@l`Qz zowijy*nAanJ!pItJO3feS3PXgaR2$LL&~OsB!0-+G$?Wp0-MEYC}Oj)|9gn~xt8WT z=r#?~R%ssUVVef8T8?iDdBAKM%wRcqHVx@P@c`H~NO5UiYIRDs-_`!0VC)Y7M?b(v zq~M3F9=9jwO+#@$y~o8%+HOJ9eO+}3p2c$%-=^*bJjY=sot&O8`Tm)y3~(zjj!%~J z@FIX5Duv0<@f^i6>?BsE@pO4cN_7@a4ZZ~GJGSkGU1m@O`NF(m zy}ZOBfYqqs_9(w21l}HEpu8r7FKEwX*?=35w#*wRw+H#1LEiW~&~zPuT|+%sR0z84 zqqIH6GuRfirwFBbZn&CHr3EMPj}T_zXiqV00NLk11tQ-Ooh}YlLq{9zDZ$Z+549Cs zNu0!=6ZmuV_H9MjD?N=a;tUA2_9hJzI*d_`XGQY$F{(Q0yuE#XT#NN%g4-> zOTDV?9@@do7aUACdnq@mARxmIaD z#j^9P8b({-sX+qlK01P+!VFy4DWLvvzOmP*FY7|>4V7)ZtOQ>Y!8VKv`1=;-Dd#Kw{~Kl_)oU`_$z>g z>an3~ZxnEtbr+O=R;@Hzb(<$yeq-e{H2j`lzqGakQ^a|27_uHuop~7H`HjMKILKzO z;OAh5v`GCn6~Soq$@h5IUoHid@#58nYCGUh;zDNA=&dxSs(y2;Rj=lon=laadyMLt zSFRVfK<8T++Ky)lm<7cP;29t&T>w*~`b;>a1?C5)sczT=f8XeHYh$$?;WSHt zQhIELJzmqXfrWYU29(*;e704q_BN+bX78S$N7UVP*MPEupijzN$ua{eb0x~`jY{lA ziE!{BCE`l7$@c_3tZF}Q@#wW0E7PT0K@WkG6{n9=4OZMV9?dA74u(m@jp}-%P73b@<9i>{;o%#n-xE;7K9SL$*RM8Qz zQ)q12v#kWpe!g*XwSegXwj0#sS3MI62OB)bLeKDUsslxTP%sMoJRH;yLxEplWuR-H z{Ks0g)ZB2EtP$d^bW3JemFHl+Lb0)_FG(f2bjyo@VQ zcYVLabMa2%Gx~nU?@!+^dF;sKk=FOG8Sk#|XVtY-&LwO!)%OeUV9zsGXXjPa_w&`$ ziP4)o5-rkI-=9k7g(DoWX<*K$%hUI#^2wC1yhyhErS<*KM4gNe16Be0{}h5^*06See}(03i*d+V&jB3&+2gGDz%bk!rFwK zYutBz|C8cIVVfKPeLwLveUO$5XZtm!@1Kki=+yUL6h&c-!C2f!0G#?xU`IVyRuk|@K{%2G%wts zJXV{&f7h!Jys!su3GDjpmerlyRj12>tiOV1M$0z%m;LirDcwHtIY2zr)hQf0d8p^d zv28q5iZ6N~c&PW*XJ4`=-N18X3wN^vq!(Jh0(&iczTGz>Y=hysSh<76bCEC}vOL!p zv76|6AI5tT(S9Gs9SF<1ws%?=V-6}C5BCs@;iPX~5Bg^BylyeO_|EHX%;+J1=k;qL z`-?C2z8)W@=EK48Nq;MwwbrGaq2bK6bpc-P2hY{|Dabi{DaJ5d*!1oqaogdukn0?2 z8+=y<2m}p3=oK$y;$P9c2cr^1Z?OoW^Dz@q?eliUQ&}~zrtTotIJf;jEVV}tbpM>M zYw((vEY+o~zCz%qUA-dR;qCD(ShZgT#s0GDoTrsfK`bi;Wte6j(XD+;)`s6(KHgKyedQAElc)NiTx ze-)@Co(^8dfn;1B@G=Yzk8(#ZSDOb<* zK`J3M{6yn*&nc-ejR)&+Usih+s!;A1t$EIYrP2N@t?ixCd^jh2hrd7 zpwG>>V~8%ino}4UrZ=rkujgtic@udl#D#}-#u+~A&Pf1M{#kw;^L%mvE{ypmn*$Cr6!ux%Oph| zdMpX?!qWh?9_&cd%B!BpwT=T0uaG=yH`TYOOVA!`%&s}%69m=f0O*tL0;MjJm&S1H z_YAEB+j9`dIC-fuQ%N)l~Nj8qzaHc6f~B zG*Pmg@bN6xYW8Yn(W0j!U_B8ao-f2?`8}!U*iBc$hM{oNl{jZ!yy;4uGcVnAWd&!> zZ@LnC_eKQ|_-5$A1D+gu@qpqNs@HsB8oFAX1Lgr;ID43;5-{-Vmr%)vTdAj^4TS1; zI;h~%0dd~k!|TO$Wb3-@TjtP}_maMfN!*|D^XK$g;02dk)$Q_!Oa^zv4D{ zW4IKSKRfn0n1`!a_Rv&_sz*@>LEGeK5H=8nc@A1dau|$*m;U!1Om>7$0)S;2&Ld_n!_@HxUH1{oTCwgklS5dZWw}V{E^s< zfa@35N?ZItg>7|V6DHR2LT!IST1q>YTxW%W8ux(5&SVIYvFaw zG-XF{7|2fp({=;`1$xTZ5in3E`tEiF5`PrQIgZcR5iq_MP6D6~kJmAbmLdF$Jui7Y zE|W*vj$p<5v>idh$4DE{d4l0%)Ry2z(s9Td7By#Q=Zk1dz(Tz64>-`gx!dq)vnA+A z=Y@aN4$QTT^w$BKDdVR0WU>bCKi&0R)18~eM!B$AqPJA#b?_adx^%$4eE|5|L(e;Z%>+yI(61^2o+mi2w1S=pCX zAwQeQJJpe?QA8No!)P!%fqP6xrm&HdyRdPg?ASykjTo8wal2?z#xMN!Zvm$AURGJ9 zyyI%hI2=r|+2Bf4H5Z90^b;m8Xv`{XtobPDqo|KkkP|6}*v#QlrioQnK{^1uva2sW zR`ZEgP=qioCjmi^A?X5OdK5aS81hO1Dbe-LneaDVAXuD zlo_VFI#@k%goRLUSs*Z6gKe1#bkJ!}#Y|&FBO_Cf!v|WVA2(+8D{@-Qs(?YO3~ogT zg?O7*&3r?uq_ZgsUxfiFyD;3SRYUkFi=;i|DrLrll8(T7Pz~zR45ktE1L;ku)@YSN z5Ac32ZL|SRq|y3;tn%T@IUq3XO}wzP!Aea&7?f$Xy*5}oF=_>anvv;r{W6Uj^b+o{ z;II^&>8uP~Bwz^!Z?6t{K|L4_;WSw5oy9nZv^n?ukjN zHxk+jB?X|QfHE7~x(M_9%|b`cz7^cY*sT|01}!%quUDU7v+6qK=4LZ3bK?mR%5BTd z^*Y-YQ-Vao_NL{!iEPHU=@U`i*V9g2J zR1u}pu*vP0pi?E})Zj7sxE5^W<)6LlHY|K;8Rd`BxC?)RK*}K-V2~4&xvY%x_AYTo zM!D|rbTXAuj_nA4298>#ltU_|RGljIrz)p>56V{NfPIp-wHsPePRTk%=~h@NIVJ01 z*4q_U!rv2qQ90#TBP2TIls}20Kr@|k%AZEDr|x<^3JAIH*v+CaS&>Iwi+YlmkbOFZ zZtGnIq+}93NtxuAkOzfZEc@^XK9NbTry%kT%$$Nqljh5Dnp7E)>~X!*R)&)*!?q?- zMq1X0Gh6~63l)>pC4cf;9Wq9(&r&i*HigO<*N-{1Wy%;og(?Z}#dcd4wL>IhT(T9k zt-<##DN7_)Sy`fI%_GeZWe%X!cWiqs?t+-0h-;=H$O@l|W?$c^BIzb{TK$p|IF_Xi3HPSXEcAC#++D5bqFNMm{=%AO85H=@c4k@ zF_a*Ig5Y@b5*D+yB>r5l-BR?@r4*e}?>3ETF9jl!bSZ43rQ}uG{De6Txa?0}CD2)U zl?2@{dDRz`m`VTS-$ky~DX-#C`Jy3v87n+vA+#tWZpx_^Cw$V>8G!$9X=4Z{8`T}% zggj+y)UJoeBd;Bp=P4^1N2=XdQo@r+4X-fv;I6eBu|JCPlu?Sj0ox(*Dnk%o4t=f&?Ykk~LoH(}GH)H%@g06q^Onhp>KMk-0|e*BuSk zFGeG-UnE;nQSIfK`u(`Qe%%|6^@|%$>&I4Gw04fc+dC3}OTH46G;+AF`ydB$?J7qiyP#2jO?K!X;>Ybu^Og1SLc}d|Ec}eYjEMFRg zgL@hzkQJPv5y%2g)d-~Sa@@io1YVzI1p*gGZ8c6TK!&iR(1=_CuaZbbT|X^e=v zBt}GCjb2E&G)4tqnh1giNV~90F>$)J(m0=zug1xcd}%t$!3>50rBj7Q31TUYqe`!6 z5XnIVSZurin~PTmVI$hwSI_3)uAct^Zw$OAsZuQIzk;%hi?TO0X83BXPhj06i5VH=#q11!TbUU*B!-9Au?NzZ`({^w|1F}~egl9~ z6FTbf=~zyqI_9kNATs-jj1QHsi7d^jj!iuxk*`sDg%L6~bwngzqj)KjuZ>NuWN_;# z$QM^((xOM^ib%f3UW-kmBnA?;7z$_6Roq6flzdG-mz1v=@d1ydQodBfNRv(kUFP%! z@-@i2AYUuUHw0w~=~8;et_0yKVJ1N4+vI{&5|9zEQG`YD8gr;Zye7}IculenOnFAU zCSiT1IEvQ{kd_DWTKZdOqG}{wlLnQ7?*sZGEs_1#D8D>4_{{ zIq2O&k-Z)CSBLCiFc@n6nzb4pFt$w)y{)SU!?#yQRQ@(%Lv@djj8<2wjPP~ z`=`kmRzROnX0aILrYAv@vdbU$)2^6Dysc|t}8b8SrFBZT3)d^F11zYHcyo)W?!-ES#}VIvwjjAP9rzn@9_$t2Ty2UZ%HNQ#a~ zyxE{=qqFC}##FrISBxI&W`M$2Nyx#Fq2=nhRie~)r2rEy6z|7v4jlx_|GD-bt=)=V z{T7|!d=!PkYuM!4uwRtGej$@}rQ5A23$%rAl^|;Uh2{~@edgj|%`29t>U=2^b|h9c z1e!DB2v&ID97M1Uaxqk%3ma&bX%S%NLK%oUusy97n2XP&dCa^5KEebW4o6|ZLYT4) zmjkvTpH5d0ZJL=z!BI$-fv;mw7zrJl3Q?2~s=_o>M|AhlyyiiF1qPs$PNW}{?rNw` zOC}UNN*Y#~ZMbUEKyDQ%Stg^0xuGnfX13K2GdJ{CD-N|M6DPq807BaD}BNc*UdyUB#+Z0A< zbaF5DS#j<_fPD000RKP46E->R2soOD$b@;f{fI3Eo8XyLy>xP6`K*_zcW9%=z=Oyt z%B?q`B{b7ia|j+`oGx946wx&m%cAX%mL+LgU+$7S;mAuyec=J8(2cudzh(bd*E}7W zLl>{-7voe~+v#+5QKoH;#yDwxTS<}*Snk{YS7|BTrkb@hQPDFE{IxRHQ6yG>xZAB4P-a@{m;1|6ExMEh+=>a4JxeVjn*dRRxk#*Re>flzbnJDL3G zFYH%r7dg;S*LJfFrY$Gx=~f@!v?Z9g$|MwzkWsC7#knT&-;+9N{QKJCDPuSF;21Of zxoe#RF6`IRPl7GyCV0Y`SgAvxHI9K^Ely1U%9f0wk!Rf^gxSh1(p$+SkgH7e&XkEk ztz@dteT79D!mT>C&);lSueFhkN#~=q_u$YryF3cUxBK-W^`Fvs-n*I8e_1xC?f%ST<5q|V$nhP^( zk#h>i4CPqA(Q%-2QoY>V@bQRmTc;&aB*f7=-7G@>9 zbjY+;kl&)!4DD5jqaD7)kqqLvvNiiJ@w}b5ClTDA4ZQ9Zyqs2nbndwFEW7%ww<-v2 zE1C0Op(V3mN6CIVu6xOBk1v9XEtliI)sGb5*!b{drI8i6G<$E1vs>R-a8W-)mRyQU zsVdp60BK8(Jj%A;%Hy-*T+sUkco1vx!B)r)B36^hFAbubUu zbq9OjM2NE6#}=|=HFNNfh4sDJ0Pi73LL(qf57gl11P)cmX2BCR8i)=HJgb2Cr@ zJmXoK`zpzOjJe;LMu9hQ0tErH{Gg~BNFg^myDX)@MdCBJk(D06fEzHp0rjLS*vCEY z@btiiJvj)Zdt7g3U?w*$Q*p`hg}lr89#;Y?Zb1U76pm?GAZf2BHzUzB{NhJ_ru9qC zI56Ls=)(qF>&OYJiftw!WGaVSG1Omh@xa{nx^9q!3o!OALV=p|64UkBxD>{v2b#%R{g0Dp|&Wzrl@i1+zMK7rO#-Vrejzp z-Ccjqmi_<=kWX1ao^>mLUqK4RDRGTFOWu`sTD z+_cs#TE1z1bPf71Uck$Kk^ZBSUFAT2Qko zxFporQi4~Zj#kuH#fqOS-dChtL5ko>pyHH^adL@M?shmy1bxUnW?PR1PDN?wiIOdI z6rApXS3`HN#ac>MwB!$n_`$7LnnONFL0ATNpuz!D-4G2J>N-8^lgO{c{CZ?*W1sa| zXkE4`F#~JS6Mb{)C<4%_OU+Au{g$!~*u3lXi> zj2z4@q{5QdMup|P^fBz(zJh2-pZ)x^AVY!6to4dKX0?!S9!l_Zk4`yQr89rdV_^fZ zCEp20@T9lhBk;s%P0dlZwHN7olJr3BZAiKk$AN70?M|Z1cQ{0KuD=|3UBX6b- z77I>4ID#U};5y4Mv~EL^QVnwM+EGrvxdL}F!l+J;#%iWmpJ+VoF)#Y)#D5u7lW&O} zKSC!E<%2mYHMe{M{93Qznw=AMkSFXVwsGz2r`K5HPB(K{LEU%_&6(8-OfDH=E~m1L zV!z-|9ZTL2mC%(G)80uRM*x{L4}1a%MMyKJvk=Ys(h6wd9$_h1%B2?9YMILgmOl=V zXW@$itMaXNDyz^ABHby0OT#BP9za%vQ3L~(>&XS>Ta<2P?IacwBvfdY(zE#qe4h6&}SH}1Rl-a=I!rcf43+r2*at25#6dONzJncMZ(cogBUwLV?new|RG zU$bvVKzXo&Yfb$^eY@=yZV{Tk-6{;=V$=LSar-)Xj|#8-)?a~=K|v{Bfxh(u_ACA= zyT)m9H@RNrYUPt0=&r5Yh05oj;d4l*h}~qF;NSD5*sQIqOAb<78TPlY8{9=$;ebuZ zuzaLLMl}_SRG8g=h1ap`@|HEcu3i@wW?ij*D{>n&AFFO%H^6}5D4=cvMu!rJK|t(g zo9!1pvf^3vQXvMtBxLv74h(v$fF65x!^MdNaDk%^l0nk2@L^}6Utlu_8xIu{I;~?d zV_{Y4n|&gdjM?XN-S}^;J=eGr1Cjz`(Kq{QM*dSA37O;HYR>Ou&MFTf?{_-#hAfYf zhWAsR(1a6O!eIpxNhufpPP4#VuCQXGWY06$1%7;6A*} zGc9)J^GMCJ6=@li#I;_O$ya5T>yvVw*6$<$tOsS9nfRBc<6UWobiA8uT^m7g-Z%&8 zSL%Au+AfrWHK)JndDmTyJyOYb{95aoY!y)%9ywf%0aj;Fhv; zg=amV?IZoFf|=6nJ6~g&2F}$nW0M(&NkFc7j-Bg{0j?FUwRW`OAe>r^g%2$J7z;mz z1^I`RTu0kK$2$dDyVLD(3h?ZgcBr*3MOjii5MHfyCB8~NW=(o0hAzmLsEP+3(bsAh zuv|wR0n6KgyjDeZMw)b;QOP8GuJ5%b8W|TGXcbGo4bGB9PMb2Nv9(ONuVrJKQUjO8 zMiVn>kh+FUU$cEq21@x_aj5wX}uNYYuEh)18NKJ#e?}M zEg4!PaO=$psxr?~S34km>nywv06N`L?8{@+dCyF>F$FObP|#bDjj^T9(5sX|bP{@2 z9m$}KUwfXIO&1}WC#Kh5ei3ZPX430W5AcyO2W^CG2jq*p%d|d&(bIVhh)k^Jm%8 z;q@dJupUuQXg|$Vas~L+=T_-zi(^@ORl40=xRB3ui>xy2nCY^i@myi1&v6UWeRgw= zWq=**tb20#Y3ai0a?T@&2atpiP$)zulb>uWG?_q1d6+=>XeTL(nHWnLE_GN6?gbZDW%jRYWM_VVM7$E>nOi`&i+CY z!`M12b{=>JuBV1|@o)ksg&qWZBCQ$5IC?Q~6J8QiBAXa{b*usx;Y+5EJJ7>eS!Q2M z%K2kb4rzmcv+s(DDMJ{h7o^A6n~x}sqGL)b-A&s*nbWPWaapidz*yO~R#-c)qP2i! z-2q6FA*N}aG~#Jmi9s;@Ga{w3Me;eLY-dcv&p@y!%rJ9OVHW1li8|@X!j()q9%h(z z7)2HUboS#qOTNIYEj36sWOYgAQ7^@hIE+Ug9=}${=F>6yAn+aU|1ax|25^Tpu zhE^6M(orW)SHvYMpMDI=9FzL%D(JEdAh*v(Ba~$HD`((g)o3e#yj{X$MRj^^Tms{H z24h~%waH;=u$fY44+;}rW2Gcn)CKyOno#0yF6!)C^79+<%wf&2=dMM2FMJ459ZPC% zBDF>NfF|bJ$unfKcFsQbFDKB4w z-(80I2Ky#v9+JHRrCfr|qlt~o=$z(das1V{qe&iqGR z+71{VOR9#kUX0i%d!EhU)j%Sxca32epsbnDSuL(hH@@mrj7nx#+tE6MbyOZ{d3(-( zLb?uKh=HyDiYZNLd6|1orUfy752A{J{XQ~h5u$(m<)w?dVyAJb?k1vqoSDmT-pQ2Wg3U>#g!?s1`CiXzqKt9U0r_ptO<=}Ha#9{>POHNW(+^l9 zBNgD-I|}LUBK<~uI{dk<29=P7T7@1lDP``Ev{uf@16IgBT4__KTV3`dUe2|`Hk|ST zP;Rb@`Ra&j6d`W7SYGRg((Ka4NK2+XtC@M}mc)xj?2 z)kpxd{SAk12dB*ZJ`p&-LeljCd!^{XIOS7lHHmbJ4)U@Z*h;G!LA4h?Z(ET$W z(kChd+bCgJUvxmR}bm7tQ3C6E7p% z3V#6a9}Axl3Xf#|F>OaUffA&oOvTrX84olV*CXKz=9%YG>GJ7xDS9Ik zCO_SeJxa!nHhe+X%~G>qhbLe~OXlUJVNYy>U4wdb&9^5!9<#RHIu5__yredjOTxbh zi$@B+g8a38YT>G=q6p($bvm9h$Jsb~0TDd~YxK$3XQ^ysK&HELb1fsM2QJXrFMJd6 zsv!*1-=E@reVW#>*Hk*yxq0C;XvAnbh=*z--_(6Lw4RX+cn;(-EAbKSei}J!;9|y? zcpKfePiI`)K0POPk!y5J6O9`8x&*Z6<@=(dgrIz#ijckSN?hoHUw9DS$gLtsw4Tf+yExEeC{QCTR4uu^TG3_cN3;L!N(oqb`x@mk`GY__e!{j?QY1@)K=5nMfl3b02`svcga?J z02SV952C)hL?VAzWaa>4!!oz{`Z4S`EpdWmJ@`bRisT*EN?3TH@bxZz3@VQP#RRNBO`o^czFW!rEv z$-8SC?m-($`R>>vZK!k7I-#R*J+dp>LsHL@cO|a7^sn5S$depv9Brbo+88*|1w7d- zS@1>L???dd#T}j9+D~X+h>eu4G>=abmjrasJUSb_yg1DZ#it+^qxln)HWZpy9F*Rb ztOu3QmF6Ry2tax_ln;WJ(V%n?_t#LizfMZZ>e^qVdBJCyG_P#{dKW?-Bwa5`^HF=) zq<1BJ5PXtikv`iDl$W4+hATtt)3*)|2Mk>plXhK@qcM9Y!k{ zVyT3XcbD_Ao?-GEu@*!37v09%qm%ZtQ-Ko>-){zGnDj+Toiqx(R4-jIyCh zKF6rH_!uRSj?qr=Mf&@6Bw1{XqTY-2cW0`&zt`B|^N}ZH@>b*QxewsD(lwjxx#@nG zubjnj(f8aSZ{Y7gi&dCo$d)-zd`m!_)hNQbOvw$;L+bSzU)YZ9eSXDt1MR;7&djk8 zS8Ug7JsDYocYxIr7?*<8I8w_78Yt#2DF)@}KJswVqHgsijJ#ebb7JeM$j0z`D#MWQ zUE5)`8m$$YhwM3Gc}32;4S5`)`P%a=uT=u}utPf%NMg^o8us;A=)8r`AdUHC#K zJ3@p{Mh-k1!KmC#HvU z@xJgygwoIkkR^rW>nih#Ap?9;ehW<*`9?8DIO_NJ6Nk9p)z{7j>Gyf!%4&iisBbJb z`{uq!H1J$0&wND7BKPFDI2z?PHLak1Hfhn;Dx8aVoR=VRD=k2c`mbopWNlU7N8ckp z*ZvD@eG~X+Lw9P|pTnNa?qB#i^XOl=TOZ%h$2a*fCJjU3sVv!R{S&reLK-xHhE)uN zwccEL5DoYhg3t3ed?uQ_HH1KLpb~HjSgmw`-x`!yXW^?%t z!0=08au&>ytc}Feov47t3-7_|q;rXA_WNd?jo6G_gdek!ZtXxjqRpR&PKDLqEc+H> z{XE3C6{;-7daej7Cge6fU5RuMH1Vd1LXZQn5%u#G*3PhSMldpnf|(ai7cevGcqXXu zBS`ooAP2Lo0me!qVyw2&6mKCIu?5*|h;3_W_Ju@TWTv*)dM9C5-AmfUJ}H5VW=Hlbo7K$w%m<_oe6WTlFx}QX^#>>Ea8A=C z3C*M%t@$^y4%s#T-$+i`I;HME?X0KXL4UN+jXnP}q?XllU2%c$k-%lKHHqQLdUt7R zy-SB@*FoAZ9>emCprT-fGqMak9*U{sEhm3tr4X*cK$?Ib*0p@XHOobSfY^jb+{BCs zrwN*{3DTns{Gq7+Uevx#B=p73dSBl+k7 z5IbdJE_3) zUm5-kh@fFW0{B9ue~NIn%BY3?7fUufFfM$PABl74{GYSFjl56Lb|9HRSlGG=Whg~9 zPzsR^&qFo9!1bU{UX9rBCUh$jMuar1FX1<_j!pEWsS5c3H7G|UT@-V(B)8{DSuAmn zmI&gBOSsK|uqUt^P&CL#vNbsvL#v%_{t-ZaGqt1mVvK?YxQp2V) zVf*Jv8)%l3v_07mAY{9lH=JiYlY`@I<2R+14ZkVxhTr7GYR_{@>EJgNmrX3Iwqw^7 zc_=Cl=g^_}OJ<3mBNh5s@48nxaX)0NZzl;E8Cg!2PhBSvmuvk^6v;8tgStSb4Z`Yo zTJMZvbnIs0-WA0iW6id(I_kQJI@;D5SpPOldteWE`l)v!ORrHoe!@EmMh4KtVPm*? zg?a0rIn6;lR#xnhaNGocQ#R6jjj@^2s1W@t{JkEb`}=!0;69AM&*JZE_#=`37=ORP zUmodkaM&8d-x2u3F=0XDc--c-ChqLbtIgZ_o3{|^ z5`ns4GY&LMHrqEtG$wCX&YZ?E!rHLh9D;d`-YWc!;%_R%&+L!UTBWIVd@Ucv*CD1j$^R@Yc*-i3%?(F&UylnPG^4vOmn>?py zKQ7O+XTKxQGiKdmneI`u>*YB$d#*f>ntiD}!`ZjUbA0wY@*J2QJC5*uvuDUNKYO)2 zEnn#QSYQHvE3k&HgIEQ-X*+x$C=jQhW?u%l+t&@)e|$9}V4y$yhYScWkG5h$lSd$Z zM3eQ6+#e2be>i)+6w)_)n>=>k<@Bqk-8eo62o)T;U|E98D?-%EEbp} zW5aR{wk0Q6J!>M}bzz+r^uYZiGp1HBeb0c>V3oLtO;#FGQ8W2P7)dozKkY~D7eUta zmup>!ddqx_BAJCK>Zc+?pZzh(d0pN8tDI8BnuFNw#%xxN%8mYe_j1Ff{vz4A8>FUPf`~k=)P^I7E0e zAfgUS@aS=;vD9eG$ascY(|QE^8t7`BkvysMM-479CQH{@yNbLV^KxdCv^3P2R(g|G zfve%UH)CQD5e~=qcHp6Vp9l{$%1QAoGkec{L}UpJHarvDu}jJE=Qze-y#;dp0$+tU z&dEMdbZ$Ddcv1k;(JvAr+8#+i&H?8~>NsI_b<Th6N4+M8kqc zPDe+_4$=fu$WXdn&>?H+-J*BalPHt2?72?@uZ6==zR))H^MXWfv8+P}@md4ozrEU~bwk}zC>R%=PKnwDbUCOC;*UF&xVFI*6D%88v; zd%n3$t}xVUwYR_e=lPV%qGEis6WmoLTGBJLUTb{`ZBTpV_`N`XOWK|Q?bKPHbh%zj z$U>{aE@XyUhtH52Hk=-Xfql~uY#+5)6)QtGUaOlZ)HQv!sdfxc7UC{B3favZls>z3 z4_Ya8cF8&c-`{i{pEwx(q$z49QOcTD4TCm7RV zo}C29uLT4V@F8BDy!&pL`Cf&p=#hQw;93_T+=AjqZE2rrt^Z>=CV{qht-lBUj~4vH zUT!(L@0Q(R#oDz2_`{0)W`KBhSd%?hZ4QiUu^g^JEh|`#^5GD|DU1wP^99aB^5L^4 zfU#5#*F>L&F_~|@6sfSGq*?br7h2%3i`T(C0avoZ!$=ceJlvp&8y8zXB6c$3c6xe{ zdl_lattpTm?kOhk3f#FLpkE{NZ5hL4$FYeB9%}s8vqs?w>$+ zxo&g^am2q6ivYr0MW>+DTmUr}Qq&C38$~Dlm0*mBn3@aSa^Z9O{Hgy z)D`PbSV2-i|2fuQ;Q1J~yAzhsTt%b1z{v=>yTRdm4W=Iwo-T|+2)K)E%j1zp*;2}P!a-6yHvT#wu$^CnrqECjY`BKeB|00vEg1}GD~<*{%y1w% zf7H(x=6*qXh8R1j$a<^y!E#NC$P|mM0}(i<9-eI-$ILf-8K=LSC0Eie%2Ssr) z{TiDj#%AbrtSO}K3xiFA);d)4iEMx;tC&0fR8RKhQjJBM;O?k`5HoLe7@w5KVJ zvn)f-_Pi7feV5%F*Gwf{LRIuNAQMG-cywud#&&Caf@j+P4Ab_E2$K>^v-uN~wrAwdU#&>D{S@kG`k7_L zZ7&5&t^4iyIGxiv5@H#vt-X0B?(v=N4U5C00G(}O7mbmeyXxA;L~zMAej3U{-z4-M zeh(cNk@r(k;n^dPhC!#0H{92xCpXT7vo{jty=<6h1e}PvqZg*|Nak;9-db&~d5oCcVXUe?_K7SznyaGrki|GAa3`#wBplqt zG)3PnaRi1D7wB?c=c9^DQy2#jbr!;{h`|JhS=+421(}Bm(KN&f?@MrxGgm>+R#U!6 zT#H`&&IDfZJS3AMnFQXg7>`*Df=y_i#(1$&E)94W4EPt|0+f9C2$alq2103XZaYqe zqdGY3l@36;CIIJ_ro(-Z9ursG&CFCu7(8n+EO7Ixc%-MQr2M(HIIys=893PbdNO;k zRfL3A985wP*+Vg+~xB@E0D5r_~(7%#ApxDjBA6d5yE@nPojF*z*jZyB1Yw z%{I`F_B>0d1z_@-ODaa@uOes(Oc?sJ(KjO$#yQ!uEToap*q@r-Vn2mnN57FQc(yR#=-2t!oa4sP z2NuC}bdMsHL6awb&Rn*Y^<=>lP^~(ODLQVyr_CxyIiRu}Kyb<)^;D!en`s(l*;y1F zZz%j9@(<8Rz{J3RIi+speExB+|H^H5olE^gr2yY(WwZeZY4U67^W>haf9FVkkBai+ zS(y0bMXfO|G>jv8;V)5L%U5d)?$;g>SZB*hqYa6=WSolLWrajN0-6~@!dAOEl9fq`(^BN^J z6VMJhq0uYJce5B>1J%cc;Dy5-veVp%_VUK9^J&V_bQ7zqL>gHaQ(W@zHi0* zhX07>A28TpduBpr(^*cQ}UA3{IPehg5yASU18YHUv8IYHY= z<_eSM!$@Pm`5OKSct!&*YXyYN(O#9+7qc7#PYWHDna-l$80)k?f-rorHO$9`mqd}+ zqkl9N9et`y?kbc66#?`u(&kiqvDw4#f+|#zb{v(TEM?cbq4nPN9{emS1o{%0KX)fMDVzCPhJCxehwNI z=a{a*!)iX-z3LAQ5S6zCnu&pG257{Zt(>&txo&tgw&&sFKxU~@Fb;MwM|a`bc*xwL za^I)Qc$jCTp?BMQ0_uOBh6@_=Ow)R!_60+n)}Lzk5j`{l&dP?pD7D-Kvq^x~n-K4X zeG+D=KTFrT0sO@I%ohS)(VZ60j=`f{yD%lEGU z!tX|3>#-8>pC(|;e3K#!z-a>Tu_&P2I$Z+(P6NiHEQu~sjK^8N+5|*d5^n<_SvFrN zUnalivsqlObt*G&Ud#{V^f>uCn}AW&Vnwnn-(2fy{LqIx_oYa&+mYrel16@)NTYN&E#=IuL=r=H zIo$4LSu>{mCre)Zb#rr{h>Jv-S-7(Wj=$}2p4j|G`JE$h{B?5!bDxYeY|rmpf#Yvy zeqpM8o+@zi>uUSxqcM;)5a%k^=>n}&25#;@Floi>fG>8(7n-`CCwa?nwjFD8pT+cR z+6&hor0_Xk(#UV7oJL<7AGJw(I?I{6WiYaDHuH2gm@;gnTNxZOGvMa->=yzC&H z=@@?z^E2^{{luT2iPtlWsq`k7J7TD|GM+>nvUw;D21ki+#}QjDQ@0bbQAnx~)%#*; z4_xzQI)vd#npYt_n}&PEEz(z*$FM$#eIK8__#43AYW!hLSZv@O%=^wQnK4$>68EYJ+nxmW9?PHPTQny)p)&sZa5w&-Q#Pi_?#>m>Gbye z=f!O03-U`_@40KE1RVQZV)g_7f&^Y^P2d&1xhF&e68Ug1e+Ql)2bdkcQ2GqgTT1Op z${`5v;4aEL5S(EGa*HtmS7LDn6VT!#xA0s%WbV)K z0*o~Dqi5pOUT0p?&NIzU7VCr;MRf;-zAB+mW0R@#1Lu`X*q^(u?lIY-ozk?s($MbR z9klymq`g#W_b2GMFh#o%kIFS3XX42&I*n1M`E$0Wxh2Mdvb*ZJ17 zgz^r@QTw%a>oWNmvRgYM!o0klFp-RX`$jy?SZc@TppB)L;KH%=TzTVIdY-&-EX~Rr z#?qX;w2dWJIihVbmTWy6V99`r`k@OeY3AZhO?hd za*anJnRDoH<}Sp*ShxxU#cEFHu6n5%qm;Zb_AJaNCU#RUX6z|DVeHG3*rf;aJPUAI zH=ryG#)aqM3nP1$kKDo>A3g5EPCVm1tl{+uBIA1){K?s%k!Ns>VavrVq{f)AXiUoj z?0IHk&oe{#IY42hyJ?7`aT@w62`!azA}c@lLEwLlzk&KjEJ@a>kFiQGhd`KXDi@h& z>Ud_R@ft;&3;q+iKDgj7C0L7(DT(RcOM3!p{TR| zyjD35v|*z;@zKi_&61dy)$wsxU} ziGGCrorLjM!a$_FQJ#_uowGPGMR^oparteO8A03<3s&ZCKz<9?piXwU9-Fp>dA_b% zcoD1q#Tcx=1H=&!d!F!)7`lP`gvf&D^FWj@WO^C5+kwYr+V>2UJ$vg)vf-2*Vh_2& znmbII&7K#4_4ojt5vP3XmHf!J4#%U?9D|G0$AOK}qV0Jic~|Qd0x#kN&J$~*dTSQZ zDxBLC$!ySel7xu1Ue3hAe{4az%N0+_mxt(BIZrCr!j84L^A^yvNSPq0C4ShvV`oq0 zEx&xl79z;G-N=?UUs9nLvy8k^Pf7n$53)nbQUpv@AQmyXqFEry6xI-W<8)J0Ky^zX zrWzW~?>kV&v$Z}+A0^d@oKOwVJ_Rk~gnj6d+2`R^i;-Y0scPX;uVd*b zYt6Z;W8pk^c{dzZMmRwBbuR~F^NcQ73>bw^gheDMi}3hnHp9G zhAJauzTOQPoHAq;xi8ZGInFx#!bk_^Se)yw=%7;v7^z-x1j;dsco;)}s}&WMFOC*ai%?0mBg`8?Z6a zK49!!nV^S9mdRjjFu??y9FCmf2nYB`M?SF0L2$s4k7#oGzCTqx^USW+feU~9UO&CG z&&>4nbXQkbS65e8R~fI3`^pbnfX>Ah^ps>TV{8ed`X<;qIDAL)-6PcYNunsq?tlT- zq`fZ}d*mu}19YPc&xV885R?C&w~C!a%uD#6LMF=ez0MPv8gX>~Pes$zTH8n*op0{Q z+q>{cW4%*@C!jW6I2+8d89&&v4)zuI9}j@Fh3m*MxH&28XZ#nFdcCiQ?4gKV=_Y_2 zJdID2koq09jbN0@l5{%@?+3cEdS3-*-{l?rvC+()&Sw&%BnfSdn%Pr%)~1oPpX@_& zY^2^CY~>r$21juPF&v(0WLHvLySe{V;{Nc6tGo8P#{vfcMS=ndvJzd zO6cB|7|te^@XdJwH2Q`GTv&0m%Ku_C{++0^naSJke;>6@mEfZE=89=-C04}T-|3JQ zBg%amTUptsRWTFDOLSEqR-=zo>iO0OjwU<7nb>`0;ac*sp_Vv4Uc}Q_W$%MPU-luD zP@igKU$&dLiR9_u4vW-0|;lPEn+}%ys0vo{i_OylV*1A(KPer z`kIhOW9e&kH(t-auJ?7KH!iA*PIxH!*FYkQYxZP+Bo>E-5}3{5XvImZmAytqRJbvy zi@IK=WcicK5A@ZuFIdi6{~5F~eY+l#{v7=&zyHU4PCKE?bUPNf-3XAPx`kaGsav=TOq0@Bl=h=lM9z>gs3E# zqIIfcwyVG^3RAUa*jN9g%0sRQ8!)eRGW)raYECu3;AwCi1txv}wbA%lfrgL2Q2HpP zPq(HwuN{{DNZB8up?zr5w;OUQinY2`&vx{-jlI~RV#^Bg z+ziH+N=x6V3J*hv(K@WCFfT@do;#uYLSbJqEDk&M%k~U)zF6hd>fKeCh6dL1B_-9t zrU(a~Ya?jzbTM&f+eTOEW(ZeF2aH33v3ZqOeBteUO)F-8v)0WvFND_ZR(d-Pb)+fr zg=+01TSuy`TUuK%cC2L!LiDDncaUOx9htp|$Luq}O}+3MMZ*WL{8 z*_>S^=x6KQm95zVF~%8gXplEF$OCUp`a5Vq`{u2iZ{D6$kdvRH4Q*|0gKzXy&NaQO7)0>@Qik@Mw0(vWL<7^ig*Ea3SK+cB@15 z*<-k6XzsC;-Ad`(?S;C1EVoJjmK2)}xXm8PkIk6Sqw%k&fkkJr=@vS{SW({3Lk35) zte+QgI4luY+dioFF8-|br}<|c!3)Y2li;P6p_m)`eyc=pWVXCOb6?1p{U;z21pJm< z#5~;OB%B;~AjTT??BQau^;&wk#&pAc9*h(JN=^?teqmSE#Bw`8Kzt)>n(!!P$t=JZ zO*N^9Q?8et+nxDqELJ9Yf}y3O2Ux1Y=xXU7McS4U?km~%sf<8Atc$rBYzLp*|48?@ z;GP5}e*0EqMF-t+~ap!z&DVFg-{>4M?}D<)P!+E9u~sl8LoW z?k?_QpBxA%B}P*2Xyv^<+h@$Si&lYb<`z-f*g9# zIza2wJ5uJn%T3~=AeS#-+Tq-BFtHs0of?0&^v@8@K)kk=9%;pe+0fYUmjt!T)Y_y7 zjkHU*&UPB1gB~H_K}X>sgdumxyNRwy|5>cf z0cvDFHDs9e@wgALrX6k9Foy*UJJ>&HX8&ct*V8S~;BE z4q}zv8Hs);-1JrZSZ64P*O)OIsu4Sgd$T@(k>llzN~}i%yU~4w+t>xETES(Jg%m5y zRE@909)HxVP)&?)5TDp}jX4Y@jz%CaL8+LoJs8X27=UlJj7z3+qgELA5s8b6!?;vI zqNAj*{MpZpw2aq?O$z}Ek7gpj;=SG$<&Dex&jC@}+s*LwT%OFLQ+=Z7uZ;Xa8wj-_ z(+kOj*O^*l@I)x9)*L*EiwkN9-N(F?hST(_`Ij)+dR3TLxmK^s}g zG7>^7D%p>%5xuE;B|Dy5&<+0^_9Ajb&0>S>QJLLC72awLtc`N)qK-p8j56cP&gIaY zTfXAb0-r;9a3h_i`!aw=+;H%x;UVp(+C)thYGSePUb_J zx8j8#t5+{VzKuKQ4IZa!I5>=l8J#*^HGf~$jPs9@p2T*H!h|&Hu3~dl(LQ}y8C=Z8 zd9d$iKGtjm!#KCzxtqM1i%=JlT_Y(K@=^}m7M$M~nUnMU}?+b5nUobC9|EcG< zs@zWT2F}VoUhT<&mv+bJxTCw*qhNEHnp+Hx zRro>uxQA!+4*}L5j+oo-)!<2BHzz8Ticip^2jLhzQTKgi=u{T&e>C-62L*3bU${;& zoLA5{MPIA#6TGkWrJdl~te|CaU*&~-)#x+G;CqF~iRuJapmATNB-?STqime3-d?M5 zfE&Ca2i2lE>`J2v4Oh^3cRrfs99dBTZscKW3}kFP9biA4_a{!zNYc(82=shwYDFnH ztx?&-nAiY!?sk-|5HyE#4KEQB?0BD*fb^c7LkXO>QX!JmfoOu>8 zss)?^P;ls7ELc{C#wY-%B2Yho?*WDVfzrnUUZL4>{}R6EEEy%}`y!k7^urA`6g84P z$cyrmXP%qREO3u`I#cqoA0g)Drk96;Gu(UV6pEoa@6kmh1Pvv!iM4;>4D@9*iQ}~G z!87ukXX<8>l*{v*XW7l*1%7#sSYmHWr{+cts6-BKN$cE zubYIkK36G|gDZ52`+6EYuXO)>-OofPqOCR_uxSe`ZVGx1>CRK!z9#TOeVVZZmM|Jv@W8TK+1r02X)wTwcAmo0n~^zs7D*DbY$2Y}Q)z$vh-o~fOQw?nzY zw=zGqHBa2PrdX@CFj%ZH_GA(zyssX4k#c**AiEijbHoNz77W+CN#QTTWPT6sD4Xj>0(=!!Xhz zp=Q)0?~D}P z+(bgT#=SDwfp0AUB!>gx*{W?_I;j>k;Ad~5-RQc0z@;sbn=H6)!QE(ju(GgfDfBTC zw0XUO*Krs{%(3B)d_jX@0DD z?NvxSeP^CJm!o|inYz5aKJJT6wbr%PXTOp_+*+r6(hx&8*{2M)^*(jC){Bq3t~gFV zt5?*;UZ?{pE@uj107vMG=H#u7>1~EhS|*tprxBFF9$cV(XzS3;NraJt4Q7e`hqVrq zFR?JXMG-(g!&HG@xINRF5t1NHbiuQ4{HAFKg&fr@z@+uXNu&)0l_quTRCp%*@(8EU zP?NpIz}|m)FNyY_ntwjaW~oLHpQ$g?R;t!79yJt@FmdMMwC>vD`$yYHMy(_F%$@${ zzx~aQB}XSo5aFNnDvxDwyjcT;d+n8J|Lc9+@6$N%PK^}x7i>}lY-|!QoYul_ha+Bw zx-kxn*`i@V`xyDBrZd!F5l2LSCww}`^`cCVprN5jhWA=vxg?Qu;XG!~)Kx@rM-5!% zh&rwOybvXyI>seo8gl<)iQ=`s5AFP(KG4KTAHds>FoIQ|z}Z1!_l)@$Ayj9dF}&ww zV{^t(4!3WHWuRZyv}u!YptDVpbS<|6cRoKYsuQ*yPbZU;>A!pDR!P{uTp3y)LQ>Jn znW%ag{c%e&ae&Uh119;Zke+Rsb{*?PE|_R2ZDT97GnqHb=ZE7Cs9tSCfY0mQw8QHa z9cZo%@~TFl`IX6jsUhf5J`nJet7=n~tK#nJT4y>(J-Dw%-{sqjZEo*Nb}?Tqh@znX zp`7ZAhlyh4fQx{7L?~9i5cLg<$7$R+o5vOIjTy@wM^RpPoO7mf(1JmY5|c_JxKfcC z?m=YLoakU5OT$Hop*o|v<1xnSj_!0W-{_9F8as7-6y6m}(_?mXUT&+oQ|Y_h+exe0 zX1E|>hLx$-#Xq6FkF)mD?~+$U!?Gc)&Rxr^2^$T8buQAOPgJIEK9XoJ5iDqEOCMes zf0iT~)&VwKzn+$Wz+&dE)s1_5GrtAr9+Pr%+!x{FNwX79{{^@t^Eapb^k1#H#t;A1 zo9oiwX}!d!&HO}Qqr+rCLRg#|GMgRFr)E1uMJ~^XTz&m|-s(r3)!lkd;$a4~c0*5P z|AHObOTh{p>t(w50_)|vof^DCm(JjoetDI>|Be&_5t8bQ^|){mH4a{)+rECW9ugUD zI$f+-3E9VawhT6IeGTU4|LlEOTe*(>@lKcePVSuS`%mTHhl8o&ALjx66O@-VmpgKa z`-0U+7b}hQn?gk1g1(yL;=RC9bEFipb~Td9!h?@S{EaPFST3SK`L2~vOTWXF zzRmm)mOnc84(pz|XXp~A`*}IemWXf3G@9+kH=nN;eRY8edIWObtbmyd32z!pF{cT-%pq3R2YIO&o9VZei{W5WAO+OZZ;&#|%>1yw z{q1kje(WBMPp1Dt^6>TC9eCT)QMACfwnQ)bR8;f%E4DMdPZl1q!6|hZyu&*z>di29 zIx^zE=#fU7yc#b&>}c8PnLcCY>>sm`F;7t@ZOcyX zZou{w!9YtzZ$4 zfN*uoUR(Aa@-~cojZ9&CvQu#8_oUFi^!vgT->Y^nEE(t?c-yQUu0{rXn&9h<%$+ZJ zj5U6@xiejbK#SNgwsV}L@6d`!3I&3EV3K3XGqiOcdVRQus2V$=KiD2W_not&DU~DI?$=)a2MZMDtVl-963Dl-u zIk2{po_|fm!-0iOLBkUY*vmO}V)s_j3hA^woJ(ttWA~zwP%sQDKmAXtXXY8L+hPNr z5r&(;wT_MI?bTr~If-rua$Y0a55xJR0v|VDjH;ZynS$ugsF<9=<*`frwJE+Vipgiu zDOaM$Q{E+_jl>w8hpEkYI`?RpL2wE%<}VvzI^&dYV=Dc%X_T{vkgu`Ur?O6Cy(cRN z$J{N8Gx@pGCQ`*{dF)Vq!rKiz}-gW zTpWbq$az=Y_yA&Rf|H8$d%_Gi^6TG#x}nY4uUeb$PaT)4j_z1>=fZh3V8k1U1(i2S zjh7DBJclLL#+o}`V!zZ9tKo`KpEBo$udF={?0+Ops##>l*pA&^rutYi6;`CX2{v*cN!94x)O-2$ z)w^o`Gun3eL9y*nhJo1|^;UoZwkH^1z^noD${SYs*#m(BOSrpyR|@1^;>iWf0*e*Z z)Ont-cX(bvt76|Lqh}lLyeYv3RV@iNsBQ*0)-awaQ$$UjvU_S6H^}~=!peqn zyvlx~c4Pgs$ePn|rLDpCXa)VaS)c00OhfC_=()}84EA8Qph;gnlAQ~3U0l?pxjJgP zh26L;?1EvcOaLtJ!_;f{P%rfT*Qzvw$2`k<%5qP#*k-Za&?f2lw9Trof|;YDDuhy$ zEBl;Q0!iQQ6$?WH#cl6a%Jyz$0Dg&eq;&me9eFp(;B#F}tGA5@gZUo;NL%R=h^40^ z@?g?BDEqZAh{b6qw>Z`Ixq)}BDraQs1F7Z4Ik?E?a$(};Z8Gc9W1h{>D6sQzHJyXd zU`N0DUfQ(_C|GzhGje}rhLgN^nMQli#Y!>9iAu=Kct%Fb_Vg=(JY8pjJMF-@-EFBz zx5dQqRd>Ry1LW%DZIQCZZ6a(}m|T=pb~I58a{GjXs3iWBp6nf+E@;E>&mTt$&m2$n|1A1%Wo zBWE+Htg(MDgtvl8axkb=l4G|ps5Al@8a6Yq{!E#sTAT)nE6swrJp;kaz*~NA^?BLz zZsVvj543)+53LUCx%Kni$!YefhtqVc_k0oNp_F}ScAaWHtiHBnVXBgYiS_c8lnp5h zqed&ZY&m#1(%#|l@g=rmr%^ie$NVZwFg+EvVtOF$RL{XoryP39a$7MyNFZ5eUzb(J zpKi*&F8Njnn+NRcF4-q0-zHQcOY>EuOIcq%XDMGbTgTBHzYh|AVItmPi|oNn9w-et zVVEt{U`3%PGqI?<9V7;k7VoL!mqiwkb+=#)UHV|E#PI1(6tKMEQ&Du;@JV{4p&$xa zZuk_8YeLZvp=j)$?o81`yQf7ureKBBsJnF8tOp4+6!1_A7~F-=lm0*OYg@ec0#`~Z z*&Z?bXa&St-xl-R#od&}0`P?Ii%)F%G_*?!%OX7g$au##d%D;6eR_rXeW+iol7-nZ zromwJT7?#yMm9kt+|}Sd-G22F91X33LjJNvkn+!B7_GeP$eddRX?mbR_PXV-u?!l% z*C@q+(CQBcnod5!eCYsNpt>2}wkH^76ATH)L=s^H6J2RKg`8V5&cfbi(%(rHw70AP zN-6`Ty3IfG?NYwF5d~*yW=v0Iu|2rAnqo6nCHrc?rG`IbOi%mxOw8@Yb2UzeHj`yL zM4gN^J}cOz_x>5zD|}o9PLIGBV9$$ij1V=(f`R3BmoVz*h>c;NqVrOwEdp?#x^z&0 z>D=Oj;c>8JVUEl0mCQGii`)#o$~VGBC;Np&83spo0d^0+m6SOf~Q`@ge?-VxC z>VDw5h1pRfdoD1v8cf$$0EE`b?iX=;FiP3qHjBPJ?eVC)y*2Jy%Uc zBB{9zqz@mlAR*IGE-sMt^}=7KKR9ojDeR=P`|Dk+(eRUJHnDl0 z?40sf0wa5XQYc8!56!eqHbjJ*8i+0NTF{gj5`01IK`I$DhE%TB_>k%3)=+L%SvH2$ zXmDni8$+sS6{c<9nNU^w3I@-{ym`uzJl$)5)uvC7)Ln!M z^QOwR{Us;n&bEo1s_#g*p~bP4Soz5N>uo_MdkYr-(MH$XDA829mYwwF-kaVd*biuo zOE2<#(K1U|72sPA()5n(KS?_?O04@*>eHH~>wC2Ar&QKbmeSZWz9gl4*QO7q+NKpL z6>wZ{a&^9b;=UIhB)zVsA0MsJxYF7ZgZtg;OmBEbZeQ7R6^*UD+p9x$lWg0-NsvPT>cCx;TX^RBoC73v1wg zU*MBXx9(2w`#I&5wYHp{hzw)WC`c)Sx_eZJA$JcMg)39e5FuCkxw zm24T!h-OX42aLt;(|FVOg}t6gdGWY7a7_LE@0X6N*!-{MV-_2O^+!#|HkWLJP-7H} zIib>(Mpvry5McYQ@l*Vw>RgfgMOAkBMMYyM)nQb$LBrJR5It)|-!CU8di)H!gC0MV z3ysd{+4oladKv0nbY@+-EqxvuP^Kik&Iv+qij(P%!*$eumG@(d$$|sw`Dt_ocr#Kj z19Trs0HFJF5$$=sSNr^-cZ=XT+0&Idq*tO}Qs0ZLzP|D+yn(Kz@CFQOi<#i@1bVyu z)Y&MZGq=$mWp22w>A`Fzj5p_paW`0Y)kBPdB5r1>_@HZlZ{1vDJDK_YxgmN1j<(Ie z0QCrHk*8KgWpHj4-j%9F?8!%&?m}5fik-M&=@Qq|x5_4heC_^yq_!fE0TFp< z(A&_@9*?DzzTpA(#%WH|Qt6#B{j*B%+r0NNksb{=Gk@hd3=SLj6UF2SntRry9H@+s^47mHCs4#=v}qtE_0XhCSqA?9@?~f%?K!@k=~4T+gNY+Ud7EF>pjd6KGu7n zA$+WNpE#Mnl8G-p37E%vcV-{1Im1?Z)6(Q|QZ_M*L`spHPW6oRgeAGNakMc6N7F`Z8>br?Qjy zO*e7)bF@~OePFK!$?hBEb!RRV_=->E5GIrfQoXI3cfqeYr9f{u{1 zFBWMc$C*TF|5>CxP-%Z-WX%7jNR!8jnZGEle(Ssr9Z=kK>v&-&qG*!dj%wjWw`b?( zbg9;4OmnVsfRiMe0>+mMCu=X=M@x_R+}Vp<_SyJ4i9BZ;eir+i#6;8rYBqVF|1r&V zrxqrHAaSj*v1fmxbL3ZG*=QZ}-^kMNs^p@A)3K3#^V4bInNWpIHm)xo?j7PHm? zz#knc1A`)7xIi@yNZ9m8wEuPzVPGJ|eO(3)ml=Xt)qVX2L$G)zI+2qI_YjQ?$9$M~ zE+(JR(z_^kpeJ@lb+KTS9PIKGT|&y^xn3*lyZ@P_Y zF+5iW!mQx&vr#_J_4RHu7JPb%*dZ>h03hKsCa@_(z=~xGO}8#lKlpt7-zd|%-79v~ z4!b!o^GxU@z-hS+nC&;mNgx$FXxj;0ZyjU6ZO7x3b&WmQ4CyU`4d+cN=q|@cBHk0d zT)ZW5%Y5uBM$(6>|2Q~DRhl^)Y?dceX&3Qjym{VC#18)>&N9Y&o^OC5I1N>2Q4f30 zU?ESS3iVIE=Fm=hw-pj|lXw&NhI2)fxVgUq7^?rfo%CEMC;G2y;dWmkc!^0Za!yxE zg0@q<1_hzG=dPhm;h+J*DV8=z8aKS6V8pKmsrKcXgfv%f(#b=k|6!A4PiHpv12s}j zBdm#Ti>3t)V3-E;lMHfSS8iHNQ`MG6b`~wg8c4&n?k1i!ts_e$ZQ zS}~at>pF3W?nt)4*oC4+74_hmUuOtpiOLa5~iwV=7~Y zIwnjvvt#YNRQeE=C;L<%yWI`mJtQo#pOtMyp(<_U!Q}-12`i%LRX(3-%!)eHch$Kg zd6A&aH~Pa=*SG`EMgRvG=z0ag{3jn6)}Sg40x24!XqgNt@MmwHbwA$zZLri!*18dg z53t=KzJWEkR&KDU9H5bNgR%x|LxAhgPc0w?q;=U3bGB>&v^L3+J-3@teu(?%Ykh5H-e6{0U{5_;EvcSd9s*ap}s5qT+0+<^t(qoyR6- z5nr!_gQukH{Sn1`i-dvVnF&IqBez;1WDS$Jp2Avlk=!mfaEovs=0TmfUz8qnWjcbt zC_UtV^7W+aDEp%PCpf+Y&vNPVEw@~{EOnMkmm8zy(l5Q9^vj0nv7Ju`eONe|5fK+P z8!KqFk|#Bai`o!S$VCux5kOp2KNnTUMWyqB+y_v~GyQV8U!J9lOFQ}ed06PXTnLgI zKYDL;{sGU+Lx>hcHj!TlBVEYN# zD~1nfHY^dnWn$~#*IGI0t&Ka`Wi+`0ZpX#35n^UfM3s4VrO zRO9IUVXEv@JML*OcP}uibKAVwd?3Hh@us?p7u;Sj@8ChD8(r!(Q_=Wwk(bjB5+U`S_IJFb;zxRzb$v#I+HYLokW zvh6W6N!jB8(mc10^={EHwc)jM&=13Cf7MMoYbC{;-{;CSXxbfr2Aj{d(Mf0Kew5?r8NmBS?*l2jaLoY9eMZh> zM=l9*YfUQR=p9X@+kF7oM$e4vt-K|!pOyl!5FKzHay`LsrEz;VTI$ECoDgMCeR4Bk zJ5e}J(j$1axyjP|H|F3*5`KmF5hJ94#^Q{L$D)3d+B0cg@}`_`(0R|JpBPEc*QM%y z6#$#s^L|z0qOldyRlsuTDt}RW;Nz;q(!a*QRHh_-uj`amQt_Z{iCeL7 zG;#Pwa8+!+ezW(}d$g91Q(fRY=5ooO#%l_h`ZQtD1AT%&;qlH~^{<4BD=8mbT%}9E z#fx+gE?#WckoXdN2wo^(Y7h0n%edzJc?0E!^^FSfxm9it^c3b+O{zMxAl~;DYeV1a z?_2ej6BBC{-naVtR#9KoEL=`&40(K;^S&^js*FYW4m?q%E=mvmAcz*F2cPe%c~N@E zFIbjK7jz*#HhJN#-X_7z?8K&51!V@jRtdp-i0}NDbe9jKJ&X6hzDrvyB!zl;{0txx1^*i7mUI za%0!UlJjZCUgcYj_PzF8-tObT<_zC*ln3CesLpC)i!xcbh!s@M_cU+s;JbI|yM;>+ z6`KoD^s;C06nXHOg@+KK5CiFj2gqamc|33gw!ZM{yxekV?AJM3U$0Ab@CGhM_yS}>8)U>`&7~Vxw9ye?NQCxbT3zh zF6@~^P^zS_2QjV?wG_?@hm_ZqRitm^%bdl$N%#06_!&%m@MaZhV_Mtp#zWlLg0zb5 z+oRJU=JKWcnC*_}%xt)t-JgOkXZYqQr-OT;jWw${`J-1A={sprm=x6al7E;>pyBRV z@IK`OZ4z>O1Fb0UQ$sxI{iNR&Y_SDSSypGe+I6;z)+JIpzRk#`5Pdwp+j6mV3^4|8 z+!rnuUJlgOzCuU)4#)6b#OAjOb(4d)>5`;{#`Y~dn_ODxW!F2tK6lt!5OFKyIxF%F zHS6VS7T0%Ckrik(n?&~pKVP*uFMTChMr!H%$N^H0hmWgbMm?e#M#OPn6?$U!5)~tW z$fXOsk8QX{cOjT~6|<1=czfztRwrD-Bh+MJqIwxD~Y z=KW)e^nY2doQnAiE9-;3ecq^rSLD6obLr8*C|WJerSGThi3OX~X?-LUbV=QG@Bv|_ z$1Y`etKXa5gZNk2JJEL@2lI=t?eq>>8EX@JFMYK8D)?&lAQ+JkmJgCQ&OR!7@G+w` zuk+Eqm7@TcPCWF6LuBzSH{Cve&Rz4nJaG}k z{u5$j>XikzIRIj3P#C#q|4tBc+4E5+YmPSjF#ikcJiKGKuCG z(>WyGBK8z5EUk-)kL=#EyEXk7sNAO#y7$SS{)MMS76ez5z})$-N?#&-=#`;6PY*JK z@>)9EMAAV`RHsq})U!=&={+w%WAe}7J*OWJqXRn%_K~?7Y@#;!G-XY&q1+mq+9IiV zYHud^ZHOqX<0YFSX2g#RhRWa*y5O(= zlXk5QKE+i5j`2d*B-)g=&@5*)!YbwW5Y!CjMm7) zV_0lweZt~FK&r9RY98<=rwzuQj~mW`2|>8TQqg%RYHV-o(IxcJyM1g3aH&X#)l<7V zMh2VSEsg0dz1}0^zOr^|j0FG2HGc`9hi41G+`DH|!y0JAt`Luz<<8BplWjgjc0)2T zXpP*#zi2frG;^zoGZfU%vO>sG}e;Y2RCn}>8Rc3VI1?wMBf%aK2Hv`TX!FrT4 zcJ&*w4N!!pV=du#4l+noRvo#dtj|Nd?b;+RMue?1(77+{0o`p*bcJ51JLYf942b7?-eBM%4kmIbyvz;=M_)wMOYpBl;2V%@KN&0j-|SVFPBmWN~kLB|GU< zzuQIMin2rBHjS^fD{PX`NzEJVBp(o^PEx`$ouuStI%)Fn=p^@fvUbI-cX}U5_JS`u zFTSBT+TvI@97o&aM6F%k>*MlTzF5S6L;4qn=~dZ=MDHu{T6p7Uc+ci0FARl)&+_cn zdU0R(ZnB4r^b-j;|E}DL$557va_~8RrYCA=u(@CnrJ*~IEFJfiH)rEabCvA1{8sIE z=T0J(pbmupVv3az?Y$8SSokI?!twlRC-3N)P^i6YSMtZ5W4x(3fQb{8Ko&FUmyKhif9$r>$)%}*ZLbt$Csei@S_F2V0tNLd}Uip#oqi#M^%BtCx0}V`2Nt#zc{@puVI8?Cjhf=f%wscVWoc4(F&TkOV-uuVpG9Q zU~9Q+tkJ05mvnp``km6E27VCXY-7c4m}J9RG>fqL{u`<YzUh<+9_xS%}Q#tk=@GppLx8|Zsq34Ov750 zU9~gYV+ih6bAwj5#j5k zGg0N$5J5U9u$A6X-vz-c`!==dK&chkIw;?4!;vKSD!9{b_cq9TggIiMarM?@t8&d% zQL8P_3?y(4Rh}=@1)MOQy|&e8cZd_}u=Amc?qsirz%Uf-04+Y>@PJX#dD7Xh1Y(hk zwjpdS9I@0LHqMheY->ueac_;TwbwHDtaZ7h5&aOB^Z}}m$t>-iWikf*pQI(Sf5mzHWy;6{!5s+wZ7rqo~_myNIx@mM*`|>T+ zrt>U!!u5svW4ziLukUJ&J7>TbhoB3+QOC=j`UARXO_Yy<*clJl#bj6&1}Npe?=V2N zQ9G$Ac442?E;g8X!qY~x259QTnUGIJ(%$L6d8f4L6nasvPUzCvFUGuXB6QiyD%@xbX)OIsZ<7Uqio zP=nXN%ht4}^Cq8d!^OPr;k?b;3`ipHP}(}WeI5L%5?u>_`k?x}Qr$U{Fz_!Qt-C>| zIe*3#z9x7ZTIr9Nk2Xs{-`d0j$xfWWiV4UB0#_m9vel?HcWE$H6OAW?Q;~b)^7{R+ zIr|#HRL7&NNMZIHO^|^fA@jbzxuUPN^3v>w)AhBKfZXEpExe&zM8zgfih(F*zuX$_ zxSWIZ(i*=*t9t)y?uJLnZ~{+jew|TRqxL1)PtnInJMvBXVzgdyFJt*x0IjL7wtVym ziiT=;X1sTJGJ?VP1Ywy3MFpqW4;+Cxawf~RK4!mPH=WfM1fcXGQfw$jq3i!kx};yB zGx;JdzOwYL+Wzh-jvC-^5G2y%xl5&?CY}+}E#1Gh-e&q)758W`-8a;-Spq zzEVmKAf6_qSZ(2z#WRchGggpgz8k$tCo7m4>W@Y(4`#-^x{)#cRA^Uy2(-4f#0F(; zaHKwG!r#Od`pXiv9E-Q3l%7@iB-->gwk#6efyx`}HyCNY7XOfN=D;qJ5kGuA=^ycQ zAvEN#L>Hr!{HUjI10tGrC)9_N+u?eG5RaJXIK&^v!(&yC zb4OA%`m~d$>0VqoM(^PdAINq+H9RYA=^tbr)%kfge`h0VEPb!%(NNBN$h)09bMGJ# zTnOfI7CLO~HrH&a&OMdXkSyn)bdH>PUQAF>)1 zOX9zU5tsde;0KwQ--L(d{^lNFs#v4MHOwKwe{Vr@_F^qB z3kqJ+^eq6z)9o;-5mi`!VwdO|UgXag$54Lz`E!+`n7t!OL9gK|BI@sf#ZD_4;ZSZ(!-MH1wV#mywVFpAP z(Z`wb+97$yUFW)U?}a5&pE#iWen6&1w@NJi;WNeah&JV?xXca<+LXKE>!VF|c_p(D zBY_r6z^pf5=M;@JhXoJEYkd!?JY41;M~;wXy{p6&2yY3oJN*fra)At=$}NK(PG?8SfkCTEWZTdbHX$na^=3~6Z%E;#xi~Q)5j{O?F zXP;HYzJGTx*gRJAc#`&L>PV8;tTG9HL7MI1>pb>fk|_F^@{(Cj1vD+%`r72|k|9eN zY4;Sl>TFOxD-~J18?sU0K;n@)G!ISc>dE|sr}Y7q+*@G8+3(4pyFc+(^YmlA0|^?> z-I-()pQar6#MM zMBAfPU3(di37UzD0}Cq1yG;4rV?x{)sAvFG12ULN4DvsJl18ry3(tW}wo7GVPtWBk z7g23e8g|4a#aJH2j*|BP5mwF3I_m<7S+Zvub}muOY#s>nl6Z)R*D`aoVj1RmMlcUo!-e3YCWWQjJ{_PRcVJ5L4=-E6(UL)$mViS)KR$H}Qt<%7rIjTf%xh zM0||1C-W>OqBYwph~?wHZtw+?K*_!bs>2|6d00^(F`JR{&V=t`Bh8sW%>zKX^`MVZ zHBZvE{M~ER+BjwP(Zt*`kLxIPK*OUra(>IvU|}4k05m`>l%vLRp728;7}zMhOlN3B z7}O*QSYr0=D8zCPY0Sqzr#AFaiQ4i(EVYC&xgi{(ZVMeLI-W8N63;TV@RsarS+86m zBt1#EtT){bj@p&KG^Ai{n)L!RGG$2~ClU#o7|?l)N!lmCJ%`1L)MJDT=kTI%odWY# za}jstlCJ!A_?f3RLx@Rld(*>8I*95WZGNtdXpLYFVI`Y6b%=(#1r4oo8glV1k0_aE z-4w4*+nT|mP2b74iQ|PoVU;uZwy|>kW;rr8(xxb@QQSYVe92gr1+bI)yMfPAJ&Mo-UqCelVW52`%+P!XU zVxpiG<#q?3;`>kd`1l^>t25n7H0<~-JZlmV>4xueFfWt7I_@iDE#1NW;0J=q-o<^v zXQJh3J-w~=w!=r#l(TEym*S%1m)vB5)Jr^7mprxB$S$W=ELk6fno62?70uLZ;dZO_ zR@J3XhF3o5-P;tAuH-day1Zx6u1|Wq3~~W6vhYesnyvSR+u^!ltdZ{FaU2Bo!41^_ zuh%1gO|@(-pR`q5_6qVz4_7fSO4;|&KTE{0;oS`$l;URgXtJ4+`QV3?6H}h9X4;+| zDQpRG#+Tg2;!}bzp?UwQY%hcXkJPG;Ccl7??`SiHR6~J;~}r7$u_T@j6+E};ortKWXB@$Pub538?}MGF2USqaD|hkSEH!7oQ8 zI+1TaV>rzA(LI2|JSCgh&2AKbgQMF$5t<>Vc-ZYJ>$6MCDpm)H4Jj#>Z~9n>qlZ%F zzc21%Q{#n2RFeH3s7G)~a13s+FCTAC`I1qjUH(Ir);$pf#4K@w%*xK7X{$L1VsUHe z=Gph5myV;m@bQ&HJc|CxTr;__?bfvbg1RBut;fOlMMJ%{1*?S;S-Z;7X5g02d$v$^ zply8teNl8*#I=NpCh4&nTw>{!Q#mPxiVxvq+Xwh6S>0bng&`ZhD-RE6P@Q{mVP zFXBTl)_C3_y#Kt>O}dE>IV+I?BS(k!{s_S93opVj#t8SpQM^|ksGh!$QIw=x$?Pr} zH!XQ{)bI^u3kKQKzt-?&CD__T9a2lTk+-*kLk|MC$a{^!vo!B^%e%UiS2S{>mAm<% zn1eOj#`rbq$aa#-v>2yN*h=xOpEq**wzR<;!-!y=q0AY_M@{L_Jf}x&DCK8I!Z5o9<#KDGq{_(jGtO} zZtGO7yM4zXyqnvxpm+23?wB194#_9(xQ~+0RPxN_K(O`EweHFK^maRL6Y`wAgX2)q z946Wn`w)-8*5P4o_|v`q0Ac3I6tU}MjhS!I68(L9nZNv9ew`ztG#|fC^`BOoc|lRg z^GMgY5B9NR|z|wM&`Mv@>836y`J}QtCw%HVx^b64=Lrpxic;I6<5;(ui|}e z<|F)Wy-jWA1G*kun|VLi9Y=+Nl(<8&HRleq?>?;O$?#HN?oh;vxthIv=GByN%i7Fi zxq4@Qi5&X-TKLPK1*3BB_yjNJKF$waqG#>1=%wJ>@dh5|)UMjh8$V6a8LcFJ>)mTJ z&(r&-^Gcat%)_peCwS8AroYp|-%Wk)odQCsV#{$VPnB)i);rprW^J9UakQV58ND+% zTAki>ZRQ}QLEyESw(gcp+@krr1BK58eiqWX=MON|*nCXvCnD`Pv!so2_DUGu=5>i* z+y3lNmW8DtE2|-2K9utDOu`wAyBsQZkz{w`D&BlLSETrrtX+HvG6s`qhWuZ&{MCb% zpJN@DQ)8C!#HGT;hPzMvw+SPAi{5PO)P4TQ zU~x51GnQ;ZX08h>4s;1GccreccwIWiYO@#>+A-EeMX$qQvQD*nbxX=G0~Fg{y6`Br zo6mtdg*lu9IaXsGG<1TdIru5$*T21IB~fLc=E3=XN*k8j6| zpX!Bv{Dt`WeDyS5{7f(O<1fVE=Not9#V_|CpK$6g8)oj(j z7Rv5a7nRi_4ll~=VPUJ3fJMo$I5a^^%3B5QVBg5sNoT6!Q(H>nwWbb`CT8 z&bO=$wo54M>w-;DR=wHg0@#*1vT+MBSU0zma761AP!ZIGSevj3Tx4k^wj0x1F+Eb5 zMf$i6w9soV9CnD-sIBTKBKN6$Twiz)FUA^G+1+HfCnftfEsS!@xI^RgeOjTJEVvT* zjK3roKGCJ!oHwjstAIJlV7ICkUM|`@pPxeh@;2KUwP{PF?9-?)+QfcbcO=MKcNYlv zmWvBZ9u3L&7$*C8*Lv`q){i(AkpkF-tb3BL5VABLl+Wcc!nh6d-D_#S!^wA|%U2D_ zR~FD~u~aoeEd{^o4fBu1os#m5Z&*Zj>{u%VP!`4nkIw4sH@G2S`gC-D;t|CG<>IiR zj`EJyr?)lgg%e}-Zeayh&pjCDPNNmz4~>J%K1Wc;BDt=Bj}bFeB$viTVxpR#Yf0}5 zZe|I?-Q0T}BQl=a^}?bLtI-1~`&$NodT;WX#}_SYCdYfH*V0pG1h*E*0v8c2J#15+ zq9wDk-3%|)|3GRzyPN&q-G1+3zxU*K;U{psf}a&MXTWGDJg2$X&V~KcBKh!4K)Px2 z;mwh%erx?bQk%K*YNV4FnM`r1Zu@q-g4n})6!i7R{{F9F$OWx=Yq`w7 zuDFrE7xMn^`Ma_5=AKJ|wV4-QjkxtBZYA!hceXsXx3@O)21#WnawWyr^!Fl9`A_*@ zLoOGkWtgVFx$vj^9s;90lKS@@Qp$h&ezFj>Rv##6X$(s*>vEQkv$P`#*&}w{&vm_;2YAy+?q8Ed$UqoNCl)}q~x15iE(0DC#h@Jn4SW4A|8xv z^@FBp_LC(Yj{8(O!RY(oog2BK$F`BmHeHrRiOd z_zQVUI4_LV`nts0>$7G=d^o9PLb*NcR!4?fp&Co>&0_?ol=A|5#pPn_jo~*OkNXgq z%Ka-z7kM)({+{4RILLkqSF5BCFrJw#jDw=l{i9=48A2za6!7yGE21@PqtU~H{!V!N zPnEH-3mIE7_r17E=HAH_uP68BQn&66cyqb?`}WO|CU+` zXRVrEX!o&_AHpqr(rhCeJ-aOPBg5NpK7#(EA1f_MA4YzbFt1}tz~-KGDjUGxdw3jc zbdIVuV#yaB$#jTXl@jT-Q22}X3pqYm38d$!#Nl`<-lOZ16~R!>kI;iRqK`41{6w%I zUAuD@g7po}Rr-Lsi!5_m(MRsuIemoMTQS%Pqb1lY=|7S-UVdm}lA;RRI6YG<+hnpU zzALblz*v9b-!u*yI043auLzz#Y7kUHuYL?2wnx4x2V>MKKAhJok%xF&9*hCQfHZ+( z!~$iKRVZj%xOy-ZW;O|7myNTj%r6sBj78f8taB-CotOTGUWTir^FR=7VvU$Jq5a^;#C$iVXYs1y8@kF67Lk8M zxo_IO$GSj*mF%{pI|)UtODA7K0c z1;N`~7%-S*KeLIz@QqE>@WGL-X){N*_<(ev-mf_sd4~o~`kLI+M(}hW9B`{&Do!kX z-(7#{0uM)Y=I0KI&R2+nJK1%5{%pIhncr>KwewH0>$>@??7DvbEp|P4{v&qXFpnP! zzV9~Yf9f|Q^9Vyq8J(Z>o3Z)SZ^q}3^P7qJv6kgoIX~k!tLBgPo7Vg(esj?Lef*|9 zf4<*L&OgC#R?k1*Z#wgD@SCamtNmtr{tJGyX8vpR24Jk6|D)fmn_ruevQH}*&V)}6 zpmW;{*GZW4A|nS|({9i4TWr$6b3k#iS3w^hxF4?4gyU-TUcSc%E%Wm-8{fZErTyRY zTT35Gor6DHf_8P{zRLJN+WuD2H(~p`&V1&35tZn9wBu)LN7!^)X8$|x58Km7A5MG5 z8_r2}$kO)2tL$0*R+U#RhEXlsORjdUj^`-f6R+_`k%fvqagD@Au_x}xl9=#7K1|OQ z&i+Dq&bK79{@nSD@9>TGjzsuD2Aq-yZVqy@sYwvgC3||DKpAfmWvSRF_*9Td#Zql- z6@4!u96m9W4xo`Gd+&b)od4n8`vugy5!rmv-g|eIuUQ0?7QMPklGT;XcHd(cZhMEs z3(pW@>|qkxwwooJ?bcOzgA(z{nZO?mi!O^(ci<_-{=148?!W8lM)u!XD z%Rc+`mNmB9zKqYE8`*t-PZ+Crq24Qp_zj*~3NF3;{`;RqLpQenUX6~SY@cKM`hq6G z2UvtJ+JDyoiu+|ng)c=Gc z$xEQilNUqhz$NM1hh|VWqf}*V_t92zfYF( z-B3Hq`3`7@;KNxIa!RLE+y~0dF!+@3I?K1Nl<$KFV8(5jz7r|QWBXz(Vid{Rb(p^vx~ysKPzN@BAuq&9_yY#&7o5?;jJRf7uGs7Lp7PxQ16 zo|M>WK#+Sgw73f&0GESUWfeZ{&@*y2Ypv(jCOO&UEYq?f5-WEtyH*>&$Q*hyZ$8>acrWgb8m|~j8XfnzRcUNUe=gNio#hAZ8bi$NHq#@JKI8_afzSTw zUFk2C!&~C96M~~oHO{#aBd>1(w29&IW+ORbTSWoF$k4KA+xxRyHpT%vDD&Q<BG~yHJ-Fb6_nxi*NqoH0@uAITLF^E<_*ry+wyp7O<7S{&pJRbLS99 zPGjgB1JV8Aa3&-7k`m5*l>MXR&Q=OL1YZOA`4y*3@zy#pm(sSy2?r2Xq`$TYhvhei=Ql^_W{OuE^OT$EW^$1F#T-~n zn*RzCb%KeD+!}DPGgFgxu8Jp}2jZf$dw5FFkykD{;>KmWUv$(jcv0Y@18tlayp=AK zgE_w(qf7c7Sf=zANoC<5#HeSKS{=x%hqZqly7d1aw7=uXS@^2@*bwBmh9o8!OX};X zgr>yN1nv92rO)r?MSKZWDS4}u=bP{25JqS_p&z>&q*E79T9@9kbxf15349K;@M^PV@? zZMK+?L;f5-1Jp)I?qfnUm@TcHnPOPGx19sD(KSAgYq<2EflT1@7nyIvR9B>4lg`i> zP*a`-rf@=8u(w^~|zIa~t6`A10eet~f-umo5)*3&)Z5|IPFK2i|5(FkZb*cM} zB^sQ-g%lVsddl9wibe{H^RQI7DGYq+;+FxQjg^ z1D^s4O{(#kYV=1zh1;=cZolr<`jVYkW@|gywQ6!6*p2vYtB~8q);4yICsw0;xv945 zoX7}f?oJ}M)a%mPVmy<@0=jN82$DeyWN)c(4C_q#N}dSX%DH`wkLuYL?)bDDm5HJt zPZ^I$jMHIXUP-Kj?6bot)28+au3@9&0pxFH*9h2Nl&sCZsJo<*eZjC2*J*uZ*mJP) zT|+stCpmVU$a;o{Bs!KRtPmc0>I3!qbSL{VC5+TMm*dpz^0Bzy$o^===I;#$)45px z$xzM{g$k8}VbO&g;>4TW7-+*NV<;^bPH)BZX5p4L_>Bs-;?l;3f1nAlXp6eSi;_3_ zOq$G4cc~=%vq8l%8PNboMZI5UF9sy|r!g8|M>uI3;KMaUHM)v?iD0f`dIcA$cSh%) zMjBCRHN%O&L7Q{-pa}(tM(01vw6^dw2G*X^ENF7+==`zg=f^=&C){?(4mB{Wc69)x zGy4+U<`s#y%v?pUDur3DlYL1&S+!-$I>xPRYG0snOAo2%lkC32L$v>PjQ(2o5zsLI zUaGETe-%b*doa-L-qn)oo>bq1#1N7ip47c-FwDX(E2RRsM(dzx3TG0q5blqR+NT;T z;r_U2t?V!a%Tvi&5zYcE+5G?o+{ODZEaAiWLfA5i_MeVN(mi-`XbwSbIuOl0oIlM0 zgI*2(R%H(k0gKdX4ll=?rQFyP#GQrj=|O$HLUxb9jLAa@SyN~RnB_gUK$DZsg$K)CrEV}&D*;Y%Y^8*9 zu11;4u(1nYD@$Q4x5Rj0U$zie7`=j9N`(BI&551Ss4731OPYcjs*_aYYt|bB58XM6 zQ@g-f*jSfbCK< zkbMmf;8SC5;k9Ti%FG5fR5)WP27%!EB=z8cFNe$O`N4Aa5S?xzCn~Qn$$liB(_4tv zxkAd58`-x>-q`R^sq1#)+C?iD15eW+d{?Id8ritf*Ife3%ekjblN|Id%Y!y64rRvP zI$jrLuJbtx-NvyC`5d((I)rw8*5&dbgJKMz1~{^_Q7SiNlTYJFqhM+NC+#lsLj|#Y zW8|BS6<{;SxEYtNsn#4-i^C*XTYPm#U zlfxuV2AF6Gei=2KmVE8ui^g2xWebwa2;tVzSSy@XDQhiPqA)7y=im-P<5oVbgkRPa z(5Q*A(`wHzHET^9-5t@HzUqjnm+Y17j(LMoa zhQ6gl?WWu{5 znhLm?x@siuPI}}LYe8UOXQ+!scNMdD9UW?DeuUf$j%+8li9Qy=me`5VQLD!gFp|vH=Z|Oo->c3a%w?44y%#_Yz3Se1E@LdK zjMvJaTf@Bl5e=@Ho&@;>r8EW!Q?82GbZqGT?7`MBji(9VvNB zzMR&I^-I)+p&k|0e5t+}X@p*3}Y!{$#qI>>Fx7M5zS@2K^_HZSYjo7}0((>BHU%w|FyH zIc$@f98ZX?@viLQ3}U5&p{YmHK?;ypngAv=n3%7b zai@(k4(Jl5uIeQHz1u@*5+%^K?rOfFH)<9WWB7`$yK!-~ZjmjCjvO3l7mHt5*mhkljZH(~IlYlX+9P#lWg##nDAKrtSGsL1|% z;p5;jyN+SKkz-zqDw16P>HJ)w;X(ZT&*~T7uB{QR{3shr#6b5ObJN4_qK6*^Jkyh0 z$aSUQga1@%i^b&}>7885c2e=H_QCQ-qonQ_sgPkelGeJhN`pOIHYY&x8espbs-kE! z9;O5Tq-WV6qvjf!ef$2^HFp!IIh>Fv!A+cCaGIEi{^2aA3E92vCViD)^0olkG{ubd zjt;>>S0Q)fs6G1x_z`Oi#>>nGR(~`5Brj?Kh!89EBi9=%^kT{uTK+7U1C6eXPP|J_ zqj7Z03W)VS{4X+%uyHZ-QD8hC3W@679e!T^9Ux}8Kb$bg9f6efmy+fB{>V0?MdGfL zg_bWPS^t>7|LIk7@!u%tre0Ow-}5&&N(J?q!>$(Kmjd{+_*HMHwUdQ`zN{Yru<#s z_wsebL|u)h#(O8^$4H#jkT^Zw+f+(~6?@`p&S-J$-2^iGv_b;&yKl?=4wl5m0}nWn z3lEyVUJa}pVK0cpCn7U&Ims`#@yl)fay!4A?3dg7g-y7Vl7at+iJ$u`yKC)9C}@!YsC(6VFUypE#25H(BeynV>Al64I=H@&peNqpH$*&mx+M?{QSAoV<8BaaZU)r>iw{L;P(_{gZkbQrQ&}##|uV1IrA{WaB6QD}RM{0vhk*q^Q zb4o>vzKFs^lEJN+ljMVkn6b{@yyOFo>A0`DW9o#mAFPb~YK}FJP&aX3P3H7uwPXfv{olJ&slBpyQ78l*F0#c)I2oIy$KwjE zP1rDknE7&{A+(5XwHnzo0W-&l#sm^{G-WgtTQ{?eIYm^lR>moL($0muBUKZ$Iq!23 z#G;4c?yJ2TG7QxrelhKIS{gq+fENi2l`l4SEX^FqjrAU!JCGZq4qiFShe8-1n$kgf z8=G`2)$H5Hdc7r|!lR75q(~7#jP;rxZM0ZV#^$WeScUy@lx>{=4&wO7i{Ti@@7jhbO3A8r@Ad?8Q<*Mf*p`abnrb zeOM~ZnW-&p3dBGZdgaXY?jnD+Wf^9w^^Y98t=cv`r+yn!{x{ENt))EE6BK8&mdwSQ z&KnMH!5Iekow?OwL~_clxZ1kEG%6UxtPO2U*pdG*>{XT!UGo9qrHEEl){N~r^p)s5 zwcvWOC63VTdYLcs_1&R-chhJ`u+~iWSZ8A*S`zs$9QRiJs28uHJRGx~?iV$H$mMz$Gbul$udknT_i3bGCjH&**Ng<|vYN zscRm0@G#~|C6fj#ZpO+oVuU$-Bx+Q0eTBQIy=(5GHn$Y@F?!qR=to84FImCq6Z#Nc zOEhCNVj$u3BLF<+a5Nm?hJ~yi_m(ymZUHFbtDOU(ZB<;+C1}T^{jPtmZyvAhJ%^d! zVkXdg);*{rdVM6vqmhuBoy*O0_yB^%G zV*tQ*JYEI(e|f9^;ImL-aXss7#RhC>;$zR7wC+~+VDJGkdo6*@ApF*-=}NSE*}f~+ zjk`^v(eIRXsbY?u&T!^(7r)%qFaO||yXmreaCd*Yhc2vSuA`G-^olw5cYnMn=Gfo+ z_2QUg@1u`xuR{ZaiE4EIS!iGveh%T_zF1}Z{5m@SYNh)QQQ{ykjXBmCJV657qL^bB zXkIY1m>EzWb4)3vm}8fi;AbuA+pDwqjG1;u$nuoCX>sW(oouH)gs5RxaF#QV8nz7P zCkH~g^LW?!hInDB$U0EtRkr9KcD{W;dWaV$@Iroc#1OzC#+&eCs>LFOY67yW?r-St z$*juD@Aso%`puHGjioZ~%)2=vjY@fUuoL9u}#HxS|GU4hLi}E5<`I8GTqMpOsGV%gHjB_>ZlE2EW+_+!BFwOdMX1B^ zkHDe9ECw!5F6^WdF81-H3O^nsEC6&Z)rm&{J8}s{6b*j3 z8f?S5uU{3Sa02JTyJe4LPWWPbl_|&$?`?(aK~@X-DWF0ngb+R9o5u-^(*91VsPr@@ z9YNx8dgAO!miKysVD&Iv_Xs=KYX!~4oZQe-y6~&WC>vP29vm$Q0ttWs@XWTh2K zRgAC=LuWJ^X8N0Z4?pFK6?x^riq+EY2yiLv%A-n@|Y z4Y#e#g^ae^+3J#zXK}TPR@spmj{`PP>jY&byVx7*5xzlpp>m>f-GH*a{!nrIJh9X!;zP;!W77{V7? zyr=et)`rbAGz_uxsXKdu0pM}Cuq*N?Paz-|$7kIVHCqxfZwRRE*F7F!4>j2WMSxH* z8_FYr^!&U7enc$_Q4&U&Yrswm?HFrpaOVvozN7QTL3~I*2T(w%C-Qh}a@mlp@=W&M z3p`i4ZYK~blVK1d^IrDzd^s^BX8d35XfGwCjaXPa3o(Bn>8T7{mjP_tC)rnx;2f_> zmIOb+h9vTw<+ob zl3T%{jCrQu&Nz@5RU^Hn*h}2kjM*55XCvOM0+RJewGeKKA|%51Pzd6q77RgxTlyt7 zFBNcC;?6bcohVQHi(&KpS!!jgPQ*|-^=M&kc?BsVa@aPc7>paD{p~j4qDS_$M=^M4 zjrvM7#4bpqw|oW4VBvr>sOT;GbNyTreI1T*xXsBH&Wl4hk{>u7E@>eI-*IhcSd8c4 zwMJc&GR9#rh1N0Yzi=Vt;jjp%xvkMGylZMQD*VvJ&FM3fbiF!wmI^)Cm9T;tq#?N| zi%BL)*D2!`GNNv@AXhinya?35Nms_N5d5^#)L6lr3MT%c^cmzHIo!B7Pn#SDlowFor>| zShf=xFKk%;Bwf%VJCSm}PLXc~V`*tV?Uf&4V_}I3Vr}u+CL0!owTgI8HF_VTYNNiJ z_I#nwvIl|xaiSZEd)R8Qh;Rp;)5nD&lZIR~yO#;*hLdI<2up9Ka?$_w8+Fn9fYPCkxX9n0T3km zcoPZZUCnSUvY4z32?CO^NW&&j@oxO1tq3w9><@+QTEsIYPY zNGBk{TL6^l8Og53YxJ%Ie;5#^==ZSbX%QI&42)DRF<0&I_WRpRrrVi`XMK@;84`|S z-#IfZtQ{VVLR4VXmaBqoH0~)CczYz`dHM}a&v0{#R=PjRuyZOHD%h9Qy(B`>T;XQ! z)r~s}*IxouPxgA5lL)mQSXBPNrmEls!hcDdsp(`c-$f_c6?yc|w=SH5m7QI5pea|B z#n5$(9z$NLN{gi=uVNdy!UKW{R5jD5T{a+n(l&rrN)brrId4x{U5D&DCxY zGrf&}A-W-H{wULN_-=!YZykg3+)V4`c+k%4iZj!vi!MO7taIlCe;2cl+hh`6;ml`v z4JZg#AYI!48|?cJruhkOzp(pLvd$iXT(KfBpN8;Y228if!rMXU{+6r*N|umK@~^;i z&l&kaE#d+ugb<|NT`;pdD*1t3_(}ZCUKQ*?-7kg>T6TaPgHAy}4K052;3pMXMaOy; zp2LctUz->E8REV;D(v-sTrILtyl9o`eR4@v$u2AZiLH?veksg2lN%v>c@$?0xk&8x zo^vMh^B+t*t5GF9UZIay>f=@VSfh`%`Z!J>$MX>_bVEaUCjhsOWoWiF&Ii`9C*+xJ zt>Sa?WO#IK5wzMkJm*p^{;4lt%AK5Kc~S>hK0iYAq zhZrjbPQWNljh$a@KE$f1hv{Q++Gr-Eyw7>LSxeS5PjDTX4K;HenIPRJ<09W*sz7$| z{S_}Bt1WeZr+1~*OCP9aI813XlkgNe(&agkqxV;3rZ(aDg zDNWOqHfgdk2E?#!Rv_~b*x^}dL6DpY!?W=YO_|BVyRd3yUhkAwHlIIv86-zZ{1tZL z?9x>_v=bY&9?tr0LDc|_3yE%%!FvBQ^a8o259b;5UMlXP#E^=^_<9GOdD@MS5? z1rdDO2<%AI`}*+Y34gJfV`R;ibCvL`2!N)@VzCogF7U(VyK-J{@WTL`*K0eSyk6nQ zDKY({^6bqJXoh_)l;K2PHhIG3fCoLm@EetQmJ6#!ibFUQ}>JJ1H&x0=%PwUKTF z$auzNuC_wPE0IO|8uedlyA;T#p(&fqNH|4mC~ydKmH7@sVx4 z8V`{Jv{~=1{L2)^Hl5tp++^BRxM?$jl5qMk>k#+90kkqZI;HfiHpSHd~f zok*7QDYgjdR!BNsLx{y#z_os?W@#qkC`GmEA&I*UiSvcY!Lk2XGM*60P+b1^|uXe3;jf z?F;%xy1SfLDS|vF0v)#}*E*HYOzR{*vaOTx80hg=K>^}eHQr-9*1$Ku_^auvbbCG6 z`flRIAx&S#e&R?pd=eE*CXLM6&v7_Z>G8!8RG*KxtPb|xs)+vguAe|AU%Q={;VA_#Y8l=#aSxuR$9#Z9zy5RKW#1a0GGpIvyH{7;& zPo9ra3keGL6}FBh%c)}b5Zpj>EFh68SelVfJ?GH}evx*D(Ue6SUpZVdN8j_EGZ1h5 zG6AVJ$bDS_E|lk{@g*Pe9d^>QQO&W zsEf>twatp4iaN@|0WV+L&bErm)mR4)9t2|S_6a>z+b)sjXFHJQ=R1(*7dw#VmphQ= zS38jAKX)L_LpzY>*R!SJUJ#X^x{bk&WT)P{l89Ts#zrC!<$p5oV~!I=GY-m91Q&6S zpZBrH$@|#MyxkG(d8bE(8k}O;z8eme;$I4{PV*C-d5_?U+cr7ZCqmr7vkB%BNoOm{6K`y<=8j8fR|=(#(iR*J9xrY4fI^| zGJF|=YrS!Jha~F>fN3`42>3XG;ALRk-MV@BlgvF3=iUyas|6C$Dow~62RD0Vv()Hu2PBDIPgii)!dZexfOir(az!H z8Ds8D=oIm1a1$Chkpq{{BtL~d)NS5Fod)_6wjBG@u=j{*63kM{^|}sRaFlZ9rrNIj z=ud_>vfSVYgq}bR7KeW`2rQY7HsD}Ks=|YuB%!?6Jy9-p2Kd*SnzWbqOCZR03=-+# zJq77A>M0StJQK@pV&|zLkBs93Kxq{e)B9OGrh#HE2gY-tY>J&M!@Ed2rZuGi{ekho zwdvK)`An+|V;CwmHb#0iO4IFs(D>qoxr+EMFk5s{r>t`-%G}?=kU2;Kh9)lp7Fs}5 zDU9_IG&MYwaB(794W103M}fR_j(}Zcc;)gZHFV=HZ}i7@Lm+8CALTwr^w_8&QOwq$ zfYy#>n3uQ@TAMpoYn4Uf{g_^SS78;PtiFTs91F-Gx!vpOIc$>;uxxFU0pEBHbaRy; z!?d}0zCj!Kn9wJDSY{SD`wNm9uL=Ivn3k@byV+g#OnK9B9bD1LyD6?+g`tlUJaAHP zv!Di#&#jfiL(-2IpxgtbTyJ{tc;5XgePK>+dTlY@C+mr=yLTpREdqBX$qoQpM6LJu z+~DzR^J}Bys!|sFpDhbXl7%64@-tobp~$y9K^s#qQ?@>M4V!K{jgS~P9y_&w1z;D} zo*wzS&FJ|pQ|H=13J+#TA~F-N(`}o1(y!OaeeJ<6Xil8J<)h@U$IuP9IX>5#hGV}* z|8z7x?s=08-u!WSn=Oy)tVEsz1y?L;Q>~N*sqXrIuIRKy4~L_1Zh(z!ZlS}~jny2q z83u4FYUyFyT-)*!(kB3u{Z?Rh)ps{AT?5qiL<=G2!HisYA$o&~*EJogsI-?-9&vs6IRg9f`e;saX^ZK2OC3z)yUTtmkk>xBzVA9m0Okt8#X6!! z3r>0g*(ogI!F`H6!g-C%mCSU_&iyiS&$QGx_y9O-=-1e(xqkJ@nE&IbA#@B9z!xpT^#;a$YGp1u zlMhXABSwDNz8qZza6W`MF9`PF5aCxYKN~!tN9Fx$ z@?#+RF+cgCLjZV5?Pqt(7%(uonHJ%fk`gk#Zi}D4o%m|Uc=j0`-$wdePb_F*bd(;} z!k9-g4)b~K#+vXM2eFGQ=?i+mTzCqWH*ExsX9{WlF zy3Pw&H{BXm^szO}w_C$fVrpPDZn~ zD|5P=j2w(pr{x0n0lLn8z~zOO&Y~0V7@g51Gw95H38E5TJnEPu0D7=Sxf!NtB{-^b z00oKcd?W;!g9twOl-lZXmt z?f-P=u#ZXC5Z&!u)b~YF-=zC@fnI8SkPR$H6d+DO^%8F4Tn-M!u179DhNpx0dQUEk z?D2AWtlS@f1P}>(6awty#|Ye$5g7!XCFAi%%4Z=I^)pg~b58F4c(JxWODW<~0G5I~ z(or74MY>j2&Mv-0@WljWd|!QE0*xC z?3E|y$Ey?h)!lx03SyMX*q#}G(3gAt1gJ+e#D=FaJA{2DJRNUK${8FWO+@HXB9jPC z9yL+6l11-bm1k*qPKZx3MHr4l4H-Zj?o%fjt~TQUc+z>9^40ng;O^lM(3q?-4Vu}AxdZ3D-PFk$db6e zfNn~0FK+`6KRgqE4a>@*Toi;gcbpCu)~wHUbB(`($q}-@hR_QSdGa%Wdj#PsqZE}v z0GF!}UAcJSygQTE>0e(4Ohba7?o3U>hFs2GFDuoRGKv2mw3O%6bfNEF1x;_l3ufWn zPb9!Gt%6N}R)YwYwfVB2r=R_-p0MIp4L<|gV`YBy60~hb&D6bP1%YuHSnjs+gYJN<=K1(?E;b($wM+x05?_@C*#btoHvms)& z5KIO)v{-Uzr-QCH9xX76KJNd;2r28QMLg%?$7wpvNMz^w(n)tW@NRuhEU)d(d#e> zZR}8(T#T(`xM@Rkc)_8_7wco(SMYMvF5;S$E8A?53V!v0##yB79T8<0l8V!cj4=@YCMj}D{xF_x%1uTp)lT&yO+Yp*bOcOfXF2huKr3i9Y3+dVchDZeDT4{eog zciy!ar!Ce8%s$5(RzMcqcf3$NY~{X8?S^BR?VE(HPhY1Joa*w z*_1Qr!Qtp2IDE+tfN4PVqeW)6AH@WJ$#@&q=?U`GPE;Q+C*hz`*8pfyE!DeViHSD3 z5GW?$G)8fH4y$iqXeRpbKhIOhd!_c>e=AffYJdb`J9>VW`@Ov|U*WveJ z{GRGC!nQw_VSg;k{%qN^J#R(YyYZXt*w=dAX*1@E82&pvIqecEvQY!4s%Co=m9_kNa{Iz7quv3tMy$hi!^ z_UCB`+mT;;KKoriW!F+Y94ysOsqIpNAEe$={kR&OT)(1rVyS*?tx&2TSu2<7FR9Hf z)t_H0;dX`ENu~NhwXRbAfErw1>{sh9)%UL948(8^x56&QfVUfdyW%$=zj+u4%lLKI zdQu~y>XyHG+_CTm^nLy)SCa4iwm%!sZ>&^B;U^{DPfNU?ZQdzAEsW&54cpo0+W?Pp z^d|g>6LBF9ESqJrjK>SN^-JcPE&a~o_Ee{5eUfFo3I@aaV3^jP?DbpH0==MdKZ z<9w?9_f>?o|2~AU_TNts_MiCu48PglZFv?K3)hypz4$Kz_V#`p6aO#%wnv-qqTc+y z&-`x0lfQSF-}QL%XZ4ls!TT1{@<+XdzaN<2Y~i+D+VN=rZqMKTZSk_tF)~$hEZmIm ztMNM@znk!TD}EdCy9K|QVcnz8uX-Tbi5AZ`Y?_k0%8@n^Go#?bKF`91Icqa#2v)p@c<5m`*Bd*yM~c%n4{ogj)Fs&@GasfTIEPMw}#=b zyN0mD1Q3OA{(9@ZjQfqh1$Q1(Kr{$Eet01q|JQ}KHYaCYXmvt^F`0|gi>?y>T^$K9+0EyQof_mlaNA`O9d5THHP zT=+!cwM(q_?b(}n%Xsv7)fQB)j`=^7K zVbQ@H-w~ZJio?0oS>rSc3qHEri)%)A0Ol*%?;JNcf(seWwnE9Nc0KS;L|~K3{E6qy z!`qPgAsy2?WSZp9)+QOo_q|Oz=3yF!4dKV{`hlL=Ojy9~tFHP!p4m>QpSyczL!o~5 zd1gzYerC*GHeCms6PAg zWH#;Sey^p@i_f#`Yo2oga`RtB*8Mk~hRqU{EAB+4H=Qo8oA?^aE1GrArZe!WZf6$X zd!yaRxarJ9=($m71EIl7K!Q!&gQ4f?PDfc!M_WZ_PDd7c;auG^r!PY&CfYKG702Gj zX;_>J~WJ?)7=5$(?5x@oK{Cv?r5P^nvXo zk4Z09I!prg8uE&9x(%1|^r*%~)5xP5wjgq&SAUo8sO~PvUUTk`%U1UqsPCqmI)x{q zJoUa>zuvUhh4Ikrxy$7zUx8+beK@<$IVk__sdku$Eilz(yWnqX(;`}+?C7q=`4M?d zy-RvCM=!RF*3cw!0#NQ~MvmtoapU|b2O8uY5CaWzE*`o!g6*+I>C3oI?ZLzn{wm8b zfbd!Z@>&}S$nNKiO3{&5B-yEnOSEQFs7kEZjq3 zhf}HX%_zzZPem($`-qj73BQ3<&hSA9AlhE|75P5Qe&ZOo>`szBP%j6*#|geoqKl%X z+;R)$z5v|^4vnSmkrNS|o@D?I1{*gbEYuIbi8632N9Fol_4?p6pkp?`)9>PHlyQ7q z1}IT#T(qMF;8cBZI^f|1u#}XqT%WIAAASqvb#L>RIF;^gyev;;>oc6eqa2^Il_%o` z@vlkc(KZSHRs&by%UxXYCJ)`y@wY6lX7pw9P52BR#vXUWGg)T^a6du&QyCl9HarU% zivw3+$p-mBgry8T9)aOu3d*mxhsjF(W$g|dJOmAIoF$2; z(c~|T9l%F;vhkv~I%g>3v2L_qts|cOyoPM#NGD=EwC8|fyzWAHF<7ry5DrB5%AECj zIo^1`#U*%>F8gg-zGTysTmxixX54=v4* zT)_)(M-B|u^pYh(Zj+^jw@I+c_+EVUfn&b|U^sXdejCs5t2ykT4DZ2T)GU(=zkoM* zKF@~V$4mGw{^1*GWRm%o3CdY8P}$!SZIKr_K>O@#wpX`I_!|JN?6XTQ6Ml$q;(q}< zx@C(^0h{*=WA6f>Y?LnBmX2Nv;GPZJQm3+tH5?uYss_yr919Y2TRE+7A2tDM{ki2( zD&{tY&TPc+0g?tjhcpTVF@!(}Nn~Jw_?hq_u(90~%QhVXx+k*UklSbvq#j%?`$>z{ zOvRtP5dqR4$jj7y_Ts)2Z=^djnwxwOa;`onV^2P?%e)TFhsOncq`S-}1WA|al*DUE zG~lQE%%yOO@Z|f?-xwK7;ci z&PmEwthiiYgFb`PAlheINDfiSxDCW4%%j7j&qQ#eKEsfIa;wXBS76m;^U>L*m+iev z*1Xvh6yi~m{jD^13;?>&!%4^z(oqu7g%?p(_Q!7E#1}4)5^76J2vO*~OX$zW4a=ZE z_wp97y*v|K3DU3y!q<>u;_8)!J_t@l;86_3?mBMMgLCu)Xdl6`oA3_i>M8e(^p|_4 z&S&c+rA37<$wzS987c2XN&q^ajRtq)BVFa*@KJ=R1QsvjhqI(rENO0iA3(>c4rb~N z|C^=2Yj}BXh^dv|N6+BCQ-w|cP*9FC5F-ZDZ(%|>1|R)AaIaJA&It#^t*?Y4M;Hl# zK97qbP#fw<`{KFY$cVvJ5Db$y061Mmjlr22#hs`+0VA z@}I!d_Gec%u%1G=*ieW~WH}V$;zxDR{uY`rhS%p~oBS&P0nQ4|TljZWtyIrofy3b* zwe8BNXAZ6p{tEmyu~oEw$uata3uAfXI&VRtryBf5*2N<<#_O6Hp}TOU0Eg(->j5us zhv_<;M~ifnU}TIXkf?$)<G# z$GOBrIX^L-z5xWmdY+K|`}q(b;%GP%o;JyG zT*Z${LVsF*DtvTAa>owwy

    B@I}^n9_Vy>8=dHxz%S!iuh6L|bShGUVH-MV6VVCi zcBGR~sVg(}D$n-tWxIe0w6B}9UUF#yB~{PZ7eKOXDsi?wF$IGrChviClxE#oRSEJ+ zs0@1a!w60VLWD)lr$P-*4yE;Cp$5NvASHYXid54v0K!W}8-))}O7zr+5E6|FrpQ<> z^njo}$$m4lUnl$1!t>`uH=M9rj5^t=7PDyo_$Reej_MfPDF#bbPx=-55@m4gFzI;;Y?R}$RYG3nXZ--mmyeP?pdUD1=ka~O#U)orr2lYe0WxWxjD-xZE9 z35TVn$hd)ZBcI9G6*nG7puF@MBi>P+c%TNWjb4KysBd5l%Z2+8RHTA%EPzU>Bd742 z0WwpM)=ma~r9Me{bVf`!OSJ3SiAJ13s)%gp>!eecAsc2s{G|S#??kJ%PM2%zbR4MJ zkGjXMkWTl?L=T1Do$jF=sHu~8*H17AQB{J`Ez$*`TB|CcohH%;*j>?SaFHzN7j>FG z99hc?$&7*oQWjM4#+X>{9*%7oUh7h{Ele42z(e$-=K)`tFD|?SrlC2DD~<1gbWo$* zCXGbZl9?pmEuLNE@62JAjU|I(}aOnAXN|8Ing+%_nB# zc!+J;*~YQQCL863*J7AJ?;+N(aoh;J)y6@DlQs_dPT4qK0(^&LuYZ&1qVI_b;zR;B zHV&zE(#FAn*v7#^)${dDDA)Q@pl#!Fz&=y?lduKw4|Wu=t&W@$6Uewtfx`s-r9I8T zn>jU=$GOikW>kotz)#QSl`}XU;fvd{zU;OfOd)unOB&y!&v0IHfs@rui=P`P=3~|3R0>ibJuSoBRvyeK_BtHmxz|WtTW} z*^czTdlvAXjPVxrFKJ7Ae59*hZKyV+v+$}{F|S?Dwq6_oZ+sM$#~pW4yr~{k23p!8 zmwJ(%ZQ13dObClE=xK>_}NX%GGZ1mq>g1#F_U)Zay{_Bfg=NrAw7rjn~OXzi2IEnN+o{dfD zzr-PYCWb>EVmc5{8G_s<>mcfFw*ybh5)ls|a>(=B`S96DkJx818x1rjp;)Le-ispZ zHd(|5RyaHq2{HOc6Y6C8Y^2B9`7@+Bj*e1~rLWGj+UlE=`sPf1b5dV=y2KeJDhW}2 zF%EUEubJ{M>sKt&)77es;X2vg25K7>!X|FFaM_`^H1=U620xNuQ`n@VTC1riNfhg~ zLQ^G`#UQES#?eriNoXhqF{L@77rK%+!j+6hvZx_wA2jAL6Uu@N8d4@$B0cNnI^V%S zp8p~%mM7=FTW>-O!Y3ym@uBY4)g42p5o&d1&X;7}Q14S*<}|J&U(0aDcKs*X^=y$7 zUh5h{YZ7Kv&y~4Nf-?69>oj}U@xj%~?-_O7j5GFVXdG*vz)uI9wv=6NHnhi8I!@_`o`soLkqZN_Z+yy6cCI z%!ydN$TJaODy-+-EpXCcXUbGO%%OP;WL6BT|6gOs@jUAs1U@)N#|yH7h@iNw#}*>m z!Iqa}4Jl0>JCQt9eX_=cO(phS%t(Y5ey*TRn=6dNYC~Zm%a5ZsxW2FpW-wyh+ozm& zn}U;J9xQC>LL`nn`ePTfvq--%fNg2BvcXf|$j;bcqyuj8|{pE_$-a5?R(1DYr># zd%n&uTt9p#pi8G|ZDA2^laHtawcf?RCaEs;2l5ycNA{wdWkwMhAtBXB^cNyFQN@2E zYFqEdr&AxEnzdr8 z=n>@+T+Wmx6c(ItMMW8D@&5#VM*RE1M_8RQ4bBLyPMBMYg__5a8Q6lbLgNp%~%amwffj$MR z4LpD-LbOtT%daHU*Un$F^+@_bT1YL$FKJ8N@24NLkq2Mt^8NNIKpr zij(q&#=Nby`l#Ew71+8cvz)OEsmw?*gfiSFUohzM5w(+ixlJ-`MW->ax!r~@*Kgg< z2fUxZm(N`5eR$|v73bYJ`#}C=($TrfrwWD`L*het@g`LQMxP-Yp~KDkl*B$1Y(!TL(ldf3Ze)Ny&2q440U z_4(jaq>$xnC}vt;4bU2Z2Uxpgg!&Q^%BZvjy4uwMiqaRiUh;-l$~oaGXay9k1-@QfTc}eoG51@VtJt^6Tl11 z=BPp@U|g%FLa2a>ro+bp%LphSLTxoJi=CYXNGInt>G0`cP)T=nSjK1eb@~a4JNs7v zT_MkeuVTeuk)i;tgkW>Q+2*TGh{QyP;l-}3jg1)qQAoeRb#Swoim93yT zhbGoS3~8x?v`z9kK3J&XQ|%VGC0xa^sgrfTR4TzxBdb3DO_gmH-(l}kCKe8tU~cEwVU4%gzbmcz?#k@Pa6Fe%`;EoF`N zG!kuYdG5&mRy>+?A4cnPsKT-{c{&2Z@j%NWkj^nxyYEEpWTQ^k0Zx%NDXU-0{Mc=B zz!Rl)BPi|F2G9{QFogB;IpDEhWafKtKKc)d9y8HPHM$mPd~Jx&1t$AKlYNn9-<8>^ zJLjA%bmH^K=f))-cs%0wl5TLtbtc+cI*7oPO+NT!ERCbFM-%B>)`(oTv6bnVpo2RI z98(ZJR8~a(JIlE7LL*k28Q7a``W|`{gElsTTVNqL56A=&}e8iK2yBOFc9%fR2v41fcpAj5Oq;#1QSl#}Uh!MHg zDbaj00gyy)_!U-4>*R?)jwzL^pORvk2));Z7V$Jl5&e`9=r>%J%G7~@24nl_5YjLs zQ^ankCD|beD?;<8zq>sguk24_EH#3IS~=zs*o`PR+t4xyelm(R?SkKi`%UnGu~%h5 z5FBS!JcXEv`9o!T=x$JaO>G4fe$QBGAD|@vF$S8AM$515vZmH0320y{HR1%gzY{#z zVd_8*-?6DfPTNGM4lr~Aeq!oiW8)l3Lk`^w9S|OW=O%b9@MfZII1CXA5d6L$Xw5I5TsQx$AS$ z;%08Zx*GLfdC{_`I3(vC@#I#G!ju60h=669z!#lsa3Ax3Q$M#efG@F^z&29FwF?j> zi5Y;RW%>!{Af?>s*R?{i?A1cD<5CH9Hp;8BQ3mA|Fp~DMa{vLTSO#BELC#8JtXC1H z1kore_CgUu`zDEsln@c3^b3vMCTVP7^$Wq-;c3t7LqFQ2{V3uQI61Fiikc8OBHRyj z2BH3_%<-WRBMGx;P~L9eR$-;7VlDDm2j{!v%mX`+nTjS+P(?EhR%4)OR=S~;lV0P4 zt8(MGS42eTgqVl1E>E$tkC8}ak*E`W`P$lUbV*{sHUl3MHbTr`1H}gJ=fLFz+n!28 zP0D)~*10*)Z5M-yDRIP@vBE;cslh8@I}aWF=dwb37&BC8WRHK&Huy|M}~ zu^LgSm7rP^jM1PqL18W2$cnBiQMKZ%iFAN5AoKyXQPk`#I_XUQ(lp8-a8zq{(QrNb z)1KqJPWeMQOSfu6Ei$86EO?Dl?LY7a{my;#8Ek+F&CeqP630k?NeFlF=i1P6ZnFf{0Xe(6>QN zBh#TSAMiUgOeEHY!I>RA%)Ob(tY3!OnUYFE2_M(G<|pj@gS4?uXsdmFmA-lP<+Li5 z=Z!00SIYPgCaQTfQE9|9`#`l^m03a`WZyh9OThL|)JIi@f%3rUC(3;sh~}5)r|{Z~ zcoB*14@hG?+8vEaf`5$df=b^)gYCjLY!_N=7mf?Ii^_`xTTCCz3k|jl%L^lHcP;M< zY!?vQ!SY}V+f77Z$FNnwLh97YwUIqJD(NUi;U3%r-e&_e8zRqfpxvFqdf2wPIQ-~>fC1jL!H2^FL=)H0cRiXg$b638}7z_ zOPn)?z5#z)(gQ-57GJ;ddD(#Ct%Fwa4&`(I({n|pmETPYA3;n)? zKBF!4Oo4kqTj)C^bVXa}TOp)|zOjEgH24UvpOBrrI(~CqV+qIasjq`1uIT@A;ljte}t#DL27sHYYY`g2ku2y{qpx_t4;Tl%7S|ovx zxTJyIjg~p37HbrvdlYCoK_V99(Ld_1MPx{Uy5YVqzeL)(s zDTI~3oT-xH(CdiKgw+`N0l^1y@JJzBkD`t%Oa1sJW!xs4F|zsc!)*hs5Er999JpAF zj1VxF2qQ^qsCvXEwxBV}S_|DKtF3>DsQ{!&Q&7el8?-GgxI?-qm08P>9Rx8f10wh( zdJv|++4|FgBf+Ha3c%Lq8CL~F_Fbq!qhHlrMk~a06c-H0VOCodSC8426Sdl!6SiWh z2ikEOU4x3jGZo3olKoLp>538wnh}6eA8GODXYc@JfxQER>v;iGu8VfA-G$d<^|1|A zs5oXrTe=FEmn)j0E5Is&!+A-hD&1o@LhmtQ7W=4y^kqh{ZJJEnG#OQ5U>zuDSLSfv zjojiOb}m>az@AalK(B3U8qM_hO~Z2#T-Ovn$cw5$HFjoo;FuhO5_=e||M@oT4X+MN zsP+W?cJw`(WVVD{?PDL~7Vnwmt)Y%3{ZLHHSWXtkH@(O9AycY8?9}OCQ=hnC=|&0a zr^SJZ<->s3Zhvj>_`=Xulqjo4#r453sy*HWVP_)eeQ#JP&<>-ObxtM(>w(W9I6Ty|V?aJUI#sOjv z1M4r?``jkOV+(_PrmVY+kF2{i?h^Vc?fM1fVeF@(RhJR8>Vj`aZ?Fm5th!I6P%$Y> zsp!rY?hLCgZMs-7P<>Q&&O@+N)A}*6fz1F6%BH~(ZHRa$ZFpA38!VP1@T0g{2w zW#A=kfxlghK;9srDTk+APpaz0O6!JKW9c|}M3uakLzkQSEHs+Q*AL?e`|mXLMt5=o zQ`pIUhpnpAq^J5qXhhU6IFYwkfOx{#z8hDwbZYGw@^Qr~F44lRTMk*eVb ze?{RiAh2*<1E+CDPb6|bdDb}qLiiFLPgw?(qf+CV9W4N|L8=uEuDBLSip0)rS@_I6 z@FUa#0~i+SF%F>HW8Rv}>)!v&*k__h4l0y$dUO;SI4loL^yQGzyyo*&9Vs#Jl#43| zCbk}#pWhyWRX46|58=uzjF#on_%!qxK8{<9>mAA^jx>-+7?1!Jf(WLR(eLuO*8<7W zgX9p?lD5aBh1@OkIxfiV(I1s@#d-b;m58YbFXu6M{?s&ppn zs-Kjs<8{?B-Z8|R#JU4dO+Ebdc)wl8wWddq8a?1)JW^{-zem6ht~E(g$U3A)6U^h> z(rpr;_8s*V!gA!CP2*kLFc*0(M3l@0b&2Y~wX1TDbrfL8KX_i?RH5}d z3C)^>407{f6iqpsRs;$R3;w~>kn@5vu|#WNdt~@|{6XJ}f}`E2e$Y#gG(2>RGD%Nb zzAn>t7OdDek(C6O8LRYR;;cx}zK#CLq#Bg7DhA_is@T4LbRy@#4&yxN1myku4d=}3)vhLJw zr1c}g&|(RZP(R%-l}g7*(HSN2-}r6v7^ zC8;eVQIW~E5e&U&VIHOcv95EcBEbFJQ1t<`JCT*XNjog(wV5u!4ADQ3|dva?FKxxa>xP z3Kgi2M)8)q=$)r1I&rR`^>?5KAkk>n`X>Xb>sx$4 z=gtZKu2Xa`=qFNWaY9Rf%oB9Cp=LQh_y^Pf#N0!eb@tv*T-Iy{cHfhN4KbkGReK3$ zSo+YpsK-?^>w$H$U@lXkExLKp*Rvh09FY#uz>1xpnxxW&MQ_Ls#+un&7Z$+H#pUQQ z?9T;^Xq->p>2$9DiKlA)Dbrx!bRLm0B27iN$(F$y<@#}HBR@#dL&%Hf5xMwEJg<>^ ztI-r}H_gVkI2$oeH<4{Ti4lOByH;HP%F22<5LrOI%hHmztld$T>`~W^QoOHv1#X-I z_mjo+)P;JIdZ`JMX3+J9VN3}mooBWr>3M`h0P{_37f)XPWxz_nOYR|wWWYz+#Y6^V2Bg>ZHok(OM)p$MBQaz77wX~a z1g`aOypwla2gYz`dM59{^8P5(H<`-|=L@aUtqwDCFZv>+(r!y)zTYu;3w8)z#Ou2= z`S3EZK170kEQDR>D&T(gOx&>_;RLh?@rOG&Xs302N$s1gHwJS)FqkhQSg@9UN`)O< zdM88IIF69R3d<;@5LmLHC8=O=+3e(bQzj>+*R*wqh%QqXDqU`L`-7ijzyY*H zq2=1tjqfXb%tdrYhKG=~VHnA2aUtai20oq=CRQ{fO#qF|FaDTO< za5K*9fbX@P@bv^=&OYXo#!}HHYaI@N>@7y49(;KHZj<#7&p`x66toyOJqmyjo)Fmy zXQW$#eUd353_L79o^h6yL-6r|aOu}NycA)OK9>WT9}h1wzRtDjTtO1<|4^_lW1ydS zatC?Gb`dm1L)&gWwKB8gz2bR_H^%=5(E^pA&dNK(zr`xF4+6PE@apKaa`9`Puhv(T z*UIqI%S_HezE&0=!&`SOYrn~=nJ8;|K#w;|yLwu-p+$R~ATvvxU>C*t;quYosr|utt)fIx?QNC# zYFbanA>a83BHl_GRf=Ogx6QsIS(=jCyc51yaMw(ad;avec~^)t(_prsFyeeRgiuXx z*w*8cG%U9n#WPdY6tPvRhVcu#;NZrS^WpBo@?+sYhMJ-m?1?cmd?wkJOE5Y_9w48n z%0jeX7dZHB$^$GcrFcP4>B@AEVXM>*R6AK^59T{XJ^43LHIvxZVmhj7Ops}A$Jgv} zpbbfYin7LGWh_f{BvW+mD6iI6=uyje2d&yffXb~R_H@mm%R|}wbTVJ?kGgCHgtB~s z5u}%&*;bhkFT4#MN0_WN7XcjI_%si@11^;gMvZjFegr80Wu3!a+08EsyRb8CfjBNN{ zK{hTb*9l3WOcvn=O=|ai2YzOT%yHuo@H6MIJKi*yTqYaFb_Ou3-D_>;(QIjvzMP*@ zzQ2d%z+T_ycz(v8&iYs)+l)Cs4qU0+;vy4{Lp`Xi3Q1+!Qeb<|fq-#6`vIy@oha59 z1CAIDyYl&!)$y&}g(GR(fC}Q)aW|`)JBn0aXzZg}_^n8<&^nLgc{}=}E)RlnmOqu$ z3I~8cw8Hs(WLs4{fCu$+>TPeA{+JS#Zj(7;eUM)}tq<-n+#S{jJ-LXdBV1MumOWN2 zd={7*l!9)EGZjt7ga=A@k@sY(8ET%@@m9Wg=XE^#;FK}jl=*CwnNaj1wd?uP=Y}s! z$lL|M5&X@EQLh%_HQ|k@za>e$mV}c(RfpOvZRfQX5kNv0Iu*FwpmLT`YR$pM`J_N+ zMDPLZ>v5ZWg+9m`spD1QH4|b7q+^*NmjG|FWX(RfO7yA8<)}z@@?iWGb0g>FA4Iv& zL%D8ceJel)C@XvNVbqYUC@pffyVUq7C8BP_fP%A%z@*VijYHpHV|p^-cYq5bWEtT8 zD1x(^-3Zxh?aoK0wFe*B)?)dxh>o|O#&4(&6M%1UITEkX)O&nv&3OslVdo(3X9D*d zNwfJ^U`2l+d>Yf8F+4hDw*)!v@Nz*Zf(wp9CX5LzSmZqS2C(?pV7!E-=5^qFI^VG- zCt*!~1utc(6gQ-i9UJdP`R`!)kO?}YPZS{mvNt&@6)U`aui_N?YQR(f3(|7+!yGu2 zI6?}MG}#IkLuD+1VIW^vHBnevFmLraurlHR3_@{WYlsXXaw+3rtC{Hw0jcc;{tN;NIUk=UKKGbKvbJJm zZBNF>;z?TEqO*@aXk_aM^1PFDtkSChZi+WEYMf4QqB&VagcB0Lz_@7Xouvp!PUO*-NIRL zg_1MRJXV`*8jUGV4I@VdTd>PT+eGyv>ERR$N4D4;)~|5ScW^YZ;L-AO!Cy7uFD*sy z^*MBWm*dHEM3uX=t;<|X$AT3XvJlKYIKp}bbtF$v|qC(U~v4qF&2LzEQ zZ+hR?a!?__&ZduD?|9kDc&0XR!q&|ml4feX@-{Fo>ndJ${&Q8@jBELIpm6+Wzdl$0p08J zvt7XGF?c4D?t%Q4D)J3|S=ml=wzD2jP@YNo3~s;jWb}|LPhMJ{y1IY_yNe8lepxW9y_i3s2moCzKF!Iz%g@C$uT%9<63K0q zWWFQs;3`>PALbNV=q9=&*I7k(^uk*)#x}Oy72_@J5s&%~wrDxG zFmJ5iiCQ4bf~!l_E;n8vgsYOw7#9xUp=Yb!7UGJOj@}`s?K@V$?W=uBtope2$Od{o z7RM-V@MkHPcFbqpLqK1)P{9Ec#$y0i{Y|&$b#WkpSk-|9er-8*gvDd|S_qHK;48Uy z&J#v2a|ZD>!EGwAMwD=x5uR42`?~NKj#diTPrI&M94r^Ov-Ws&mU34gTrZXkXtQrc zHdS)#$~_nx^bi$g1?}FQ=cl>`_ZyN5|EkjE7gwsAH^Dn>g1txaH6%>E7v7X@_r{wu z#lOn7ceXK_#0cAVj1ji&7$c0|+hR=4I#^9}ZUs-- zi4puStU)J-eTCy{a&DBB2@p@2LnlB?VhZH7mVySD^X$V%rnQU@p-W{tBGaACfOogd~b~Hd{t&^%uu%7ZX(j91@TgS~GT9t9s(`eV&)KwXP z&MlQK7o#LvTPoF25OWzKUG2C|GA#KUa&z6vUGp@ zUidv|4?>XE0nFhx`7o4gEoV@Zf30`WToAI2C;$&au-F*}fxC{0UL`n#(b#Fh^X=F! z<5e{TCMmH=eJo1b6nv#Pwe)-!n4r-$m}R2N^MRcQKZv8?$Ce+a1YYpO49X>vECRub zRKg!3^%4t}K|52JB4B{X93wIJRNSJuXU@3|Lizos&m-8M8bfii_G6?xzh{y1B<9rq zl3qs4f-|o#J2oz2o-;|AB2JRFk(lRN4^E6_v_&E#8~NT6ZZ(8(M>6xk%Ib(xvl2U1 zfAf)}yF9HstRwoOjL(BD7<<6$;Be0Kd3@#iI5iL1WaOX-bsK+5z z5#^hZAb5jPOHRNB$q?X3U42hHai$r1R}pJY*f#%0WPn8oF5)<@`h!^7f*y*2_UG6G zB~xyZQL&EiaS3W7xDfe-BHI3x0V?k*h>v4g=B`m3t_uZSV4%1Zx$8N+ToQv3TH*k- z^Fh+Gf_wSkIly;w({66iIN(|fvo5y{lyctSxGcN^(I3N3B|B)TbFV}G4{3gS`S7~y z2LVQ5+AYK|a`Ij4wLnXGiFFHc5N@2M$e}gYmLw|0NB!KI{2FuaoOP?2x}hp3VBu-t}qsB2JC@K27J7%N&U87}ou8G-95DLWV($pT6#l^x-&$UNHZhFkEI9r<-! z%EUlPdjhGxl~txZ#zp1P-|>~I6Ed+0cO9;K ze3W$dT-k-nyOMYH&T7KMS;QIfIZ%Nz=(r$ua2?8*E=)cpy|sTfS&k=v1K3-NBkYgQ z0D^W=8SSa(MzD_vbO@e_IG9YC4Jr&|1uj0;&+ylfLdRhvob}VJ;|4b0ARo93Y@Hu^ zCrU2Wdt7-D*Vn=Xbfny032=8YiDcX=n=iHzEP27(5sAa~{lQI|ipT8{-jj`kc(6Y! zpeu~9DTOC5gr@K@(8`V+(O%atKMXI)n4K3la?unuDm4Z!hftLuEaLwShXQ~;q-}3w z5h5Cr0Tw}IP*?hL>TX#j_}f$xJA9fo164tNkSLx zZM>6-e@Pr9aSs+@ZwC_5P5>nNHIocq5hZ~OR*xNdKn@mm}sJF2fQZ}|ElFM;N znu2;8x1{5o9^5V5V=f7hnu)7fas1QUs9Xs^hvSc;hT-=`-~@D=7xR&Ay@U@8zb}(# zwsp8X{nkt6nQI+^C;PDWE$oS4-y(<=e#II)$^4RJiuhCXwkBPUKSI9~YU` zB$6gv`xxv;?S1ULnSCt0J=w=r#C?qQ^Q4w?@z4mgZC1^bzZ%r%Hd!lA{z^}@9gzKx zdq=eYDHQfJg<(3K=1z1-0!w$u7rNChmy2N$Q#1*n*6I3r@WzTvegxNGl2S(Z$-VA(N_**6hf?Ec zvZeG|H`y;2VhaSNbH~m}0oE8NQ%i2?Z(jIrBp=xg`S_x_lO~Y8%tR z@Bs6r=rnSEChMN)e}v~X`FE$rduB+87k;M2%RxFC&kMeSf-#1F(((ob%POQs^RMP3 z+j<2b7$RON&ur^ec#7>KH78>pQ}0wbfpiKdluqF+Efp?zCc90RF5#kV^atNkw8?v} zvj*)>f919gkEbihI4x2q3qd4vvRHjU<=3DfRDLZV+17DTW3jd7q7lE5_9lt(1lV>jMz%%zHd1eJ@z&;M%CtjZXjm9sc8o!8**W_Ob zO7cc1cQ=pI<_;i5MVNoo~vTr+VeJ?;8G$EG%VCI(MTiGGYi zTI+paWy zj@gJ9an3Ab)T@C4`sr)<$hJ{A4<|K zFm*zoWI3Mv0W0>F;jbY4_7{kO!BnnROTP36s>HWdOmavqK7XgQBaBs>C z+4!oz+8)_q9u>*P7wv0PVJ>HGpnD{tHMC9wT5d+}_H3=^d$x579}t?S$}=1M9s_Ud zWPBw1d_m52Hkmy||9|WFO;D+DBO?gAQ#i|t!po?$(N|ckARv(lCrvmHzh`E9UecjG z|D0@3`YbRqmUw%DefViyqdjtf+a$`K{0&dVw3^cfV(ZD@NbC|(ugSkDu`77J81Rk$ zLii$gi+*h}Oz#TbjNvwj;M;-(unJ~fc51koj`D6@nUh*g$7`L2ytJnTE^Jwu;4i{6 zkjHz6tvVlbdkYc`owF`P7V2lcG-ix6PJ~$XPOJm)nk1{r7@7 zK%#X9V1a$k6b#r-FWVV-e@t*?5}s60fC6^Iz}C7M9~LY(RX0;-B9aO>HXQbw)Y z#ED2JnaXm!CjZ7Vb%sqRF-}QJm8mrE*m25r_6EJasO?1@br4U`c3psPY-1-HC)lu*CRSCxo(~h%Ubv6ByMS*or(lA17~nS~hd?fbN5IWb zAv{w49wmQ|mcPf~?qC^vtX-V$;5jX>DUWWClTAyLj` zs6*+z%d2LCzeyenQ85@N@?>VsqvdQTkMgpSk1RdKn_po&r0tEor@DQ-FQUSoc$4tF zCjZ8~`6y~Kn+_!@l{X(lvY0m~K#TvdJRuZ_HE~z2QJ1;>7PftCOLJvl0SkvGp^dFs z@5^n8WCkBM-Y&NB*aqwqaoa(WYHPa$7GBUrF|nsw> z>YQJb_H8mOlA8(J`)Ukp=gHq4s~6MHsF&E;6Jv6J7CMNe_W23T9I0xFn%-^fn$zD;R$GCPe$;wEVpqe! zq=h`=9dwDV2g)A&rSmI>U5s>pyRDqpgkJ5(zqtNGRBP8qd3t&(`7v4f2E9+ z?#Yr(+j!0kZ2ch5()Ej~B$B}_S;_*9R%r{X6ff_fN*8IBglp#K(J7oom~hv55$O6|;rX-P zB78ow+;6fZSN`rh;x`3V`Md9+-y~F5{_flBHw9h!DfNsFeLm)E&v7NLL*A;X#z+7HXdfMq)$2qyt*x| zDU-pHHrYR}MIfqh4YOF-COxB4IGS*#HQ}x^3Y=vA)~-7rh55UkbjMcS)|*+7)FbOO z@a)Ksao$X2S)K>*=$3)T_)_Ed%{OK%%ZhMGV`F8lNH+CJtg8;Cq(C|@V!@au z%05AnzjK^~Et;`0mQp(`8_3qb6O~Wj61BSl{1#*(U_N8Oq+tRsBirC|`q3Y(H{8!L z>cgPJ_rUwM1ZvDu*MCL*k~RjZ;NY|!;Jxsln89O5p*&^aLobzW+J`aGpD?^_q&ZLQ zHZbCZzeD;=m`BOsIhd~dlihgj#X81rel`V!d)2t^@y!ShnEkD&3OMC$d}LeK;gRG= zoYkQ&$V4fgG2zUW3jdAn10$G-m)6e45BpzjD@{5GX^xG8ln!Ys2&r03B2)nO*P|d{ z|8_hgJ~ucpsuONZYr;L}dB9EPh?rvQ=C+>9z$@VkUu-4rHMXdpObwd=1;wMIK+tP5 z%SS)Hfe3)LWDnMv$^^FG85zK&DSS*gvqs@L;3INPot0dW(0W@pvaD?D9ekk3cj6h< z+30ml62YQeGG#DrD%_+eED1l`tx_Xntqj-=Q1>cN16HqXV;_Cdg=POihbc1PPmY(I~9_OQ3dBC&NdpnxH7!2=Fu zy~zi05GA-!E195&uCE6dBO;1)>AnLQP5a3u6J{obIUeJms96$6i<5xfy(P4cZ5?#u0;1Xa1#3J^5K`;iG)e}E7z{kxR+7?q-% z>7Pt1;p<)WPP6;gYkiQAP}dI;R8&`!hmk2*24^OYa7J3^hwQW7S-0DH8>Aa9YVN8p zcAHG=%U`aW5~44E?K~`+(+vTRSvwQ>(Yh(~==4jJU5k-qf|}WH?ZABlx%V5!4G3{G)gz zc*?XJBU9~W!kId1x0j)w_iMY!O;23VvF$eI3*)%~*>B$^zFYz2HVM~Esr~RKpy4(N zGS!qqL-MiiL)JVxr|}w;JLFA7DsNMm7mI?n1B$$;u)`KQ|HL8rM&WG; z>BAp|gI@MApu#Tlad~E2pOB~Dx!DV(Ks3TNq^ z!ikLucb%_*em|17)_v=_YS-Au5$2usKHFu%letRQ*gFvFhqp=FvPfy$11^%#+a;6* zDibse69i-h4O(MZB@l1_4`HAkd9fefDj8X0O?7}DzDI&sSq%b@-6BDxmj?Cu;d>>B zRgoa{3hk-vr>_Q1Dvtu9YiyUqQH6!8Xc>MJe9Sx1v1y2heJ29;oh9qL5D{gu%zg3X zST5_L5I8x{C=-d!jXpHDu@4w1BP*<)N+M`4i{E$o$01_?y)@ZTE=DUURB5(Y@g6;?M;%VU~3=3N_b@TmDbtG@ZhUe%rfg_5ird zMA6HL24oSbcLT$U{SveUsoy9`BZ1dXNN)i798&cclehT{;1a`kL!NczL(zuq-H2FxK9fbeO_u0~pCLrINi499 zSlqdZ+}8anT5sGgjA$Ik!*$qrx$@*!(l;k(*to^3$+96Zci{-A(eNZypU0zcy*_V5 zj|1hl7%MxpU1o>f69dGR96mtF8V2f~b&f{eA7-0WupgSp*-~WK9yArwKv>Vj=~2;8 z6Olq&&B!x+Lgc&`@uA)q_5f*%qb=gRo^AQl<(QD@3^_cWs;HX{*Wsy-N@)a`i@~=i zn`<0V$B=oYv@oytRsv{w6W!T@aTRg4^bf&B>QK;m77oq|Pe?X7N+jrycNzS%g^6A- zQ$R<;rL-oomtn#Aag#FZGl|c>hB=BBk3w-AXY`wGg&G?V&g7DexHX-Rygh42AIHNu zyR=RCOz;vGg}(%C_^$i`m!T~{Q|9xuz*k6uG+riWDqJ$~o%(q(CSsO>56WZ~!x8Dy;D^%UcQb{{ z+kwhP+}WgEM6-l)*F@`!Rxk;(y&n}fHCr?72pNgziIa#DgT9)V3Pi%i;(TOGHKsT;hr${YKOrwp^ zccooQ31J$a$~0~ecrf$pxxO4~lxvgwze~y3wZP>;!9`>&{9{1qj8stx2=MBf5wC)| z=wnE<6<5?CSTSyoV5M`LAxNSTLOb5*Lv}Ni9bGs?gTEStLy{Eizpb? zgocDOxxYe^oE5xFC@jCqk*vK<{+g=O5kU9nQJpZB?F~L+KAjijfox^7gHy^J;TUd4 zI0vUbqY7|{0|H-?AKzhCJUe<64mogR>*N8xzAU zjGVjBDY^`DbdfOnGf*)azE}tB2gBt2Oyrl#>o$E2L{N80Uyt6tfw#%e(C; zQr^=ka2o>~Z7F+#4uQiAoNPpR-U+xt6$4-F@_Ur)jPzFBCYZVjC zv9VL&W(GdemU3&Sz`ro?cWo($hdTtmn1OrGO_#o|Q{Z_FybgC1Ch>e51Fvrj{1^k@ z-WGT-12?w?KEl9UTj@AuJ~QxX4D4!4c_;(B+XB}yu%|8X^$gslE%4n8e5{hL(YHDU zKFUDcJ)Wxj$Vi94b!~y{cUr^mpc^_<@-6j9d`lG)--3POTM$otOM4~0rBxE&(jbX% zX^X_SG(+NBXps1pn(Fs3S}eXCvp7YG(qP~4YyH7*F(eyqcx@_ONz<{rCR52 z>(X@WLHXKHGTk|;blsYcovKhAB zw=t6SF_C7mYCs|{XCy0OB6*0r;53#nvfhx3SxpR&awBFxi5ZvMSxpQNawCS9wwCG# z$rY_8hMvw4!<$=iSJc$s!Rsalu4fo?hQv^XTDvEAb3435kn@B01Fw*Da>Gl9#pv5R z#Sum=D>~!CIMqb_#fVo)Nec@owuD=-;8d`?5bleIBnW;DUTNG3YKa{JgYnBu&Juei zbBekMECL8=pQqU?;ZnTmU9FP+`-$w_#LJQe@#NT^5&s5Qs|D^;r$d0da<8j5`CP!R z%HPR-fR>#%yUzZoD|UG!z3f%*)gjRhJ+AloxS3tXO;;@(yg9a1FSwJhiU`KBd^KQf z5WZp$cf*(6!;wBP)~q^RttdSH;}_q=or-%U3y7~`<>laF^BVh z=PW6QV|kbbij$vXS=<8&GY;-jg}w_N>syE(9&{!@i~#7;)E5>aFYWo9Q*lKA6>c&a z7AC@<%Uo%g*YinG_%jBm9Vln>>DnD&CGIee{KO{066x~D`v}XdtQQn@HTh96gFUOI zdJy;ES@Ve(JdF4vDseBcgF)syd}Lc&`GE7GM-bckHa?6F*@ZH&hmzG{sFcI{a(?~` z>X5kP9pS;hVFSTzm0)zkEf$&KC{LhE=p@&(!Uu&E_1-Abg!q&0LbL0O^i-RNv_#9Dnt;BGawbe3MxeC6P(f-1i7Y%jVjou@^Bk z`~{Gf^a$ttoOZGT!McG@P`sZCjzo2Wix<6sS40Pmvc)JTbx91Kk!e2>k2t zUM+hhcD2p=C{U0pDYp&I@1p<`{>(aWK$#D*EilbxE2~jQ z4Y9kRT52o7Ld5RK&2sW`t4w#Huo71f^4f$W9eV>IOjQJdX+2bs(QjKa3wCg&BQ}{L zULu4UV-d!PY)mVlbJlqW@cQ)(yi{F^@#5s%)RQG^%WJtpt^Y^fn+M2sRQLW@x4Un5 zPtQnmHKXp4EKQGWOKvn8AeM->PMxaqDmk=jyUy>fFZfm39D|dVuW~5r)`I-vtGoqf9PuPNc2$-11#VpJ>pefL zRQ&A-uuAU5%g==d`d?5ofBm9fgC`hN{*U@R_8ESCFnyLcw+LIHksflhz4SmZZ$Y-A zu=Ee}s5NL2BTnp2i_QV$+W_@qCcT8Xr-t2nWXj7R ziH0JDFrr={n)(_|xrG&2yJ{IVF;4oQCkGTJXm)afte&?X*|KMC_SzmZvzHx0L@^)C zi;27U((qnodNnSG$p~IzF)&D|`Ia46RePU~EUvQqN6y4WTIl6?sKb-d?Tsz;aVn*FX zYdIj?8eKDc`D-cF+F;|_p>H1TKEg-5O31!!%$FkV%?z?#9R=r5S9iY8zUM$gu4@@D z%K2BUJlQ!x2G=iujw7W7z!a4k&wz=ltcB6pR(Rc`MFoW|7S=P9=*g7zMQ?A&yk3@U z%j}Xc!lD^Mv}S`-$RcR;g`p6>7*dKgqRk66+r!F^?q2+t7~Rn85P8Y&O=u$y; zby`MVBkgqxTK=Xs;SNFJ>Qe+0nBIK)~ljeCNnw)|X%gv%b-<=V;wSW>i(=e+dqK~C0omRe-_Ps>aH zCMvQu2esD+S?Atr^8k>EQL3>{O-I8`5ZvKgFQu@*loe)ZaC@3q`US>PCA@*83QGg_ zg-z2gT6uq=)G-Q3$C{P$LRtxjeb)Kr|4`%@%NS`=jHiCVUExw%qb_qajpl!h`Ek}X zDoWFI)s9`I(IuERg%v5*%FG%Zq8uQvbYi{EowO|Jdd#`$`iY5jg7Z<=r|Uf!Yq}ZB zC)3HfzrDwp0+1Y48`2GP?<=5colv!|iHNC`7js+@ur9;ZI>XgafA3f8 z0Drvm4-8M)^_yuElx@2`8w)y=zlG>*p|@6|9nk!joaV(791qj`mN#r>a6@AvzOJ40 z+a9;k(}+74Lqld#*noB2MI-_|tz6Z<;yT3pa9Cm|7Edis?cZ`>+$@Bl_g7UOcdGTIuO-4azfmbSiF^D8g!yuCJk{b*fmp7Ssxj*aw1t&b+vCh5XK z#11h7_0nx3r?|6QEns}kM6hVcO1Gpi`QnxVZbY#2ig^!jQxo-Q_OVf76^2N=(j!l( zGtLJex?dqHVwK`HQ(WAWsB&>p{5N9E}_KJFLV+0msg>Fgww;V^R55abQ^G_veyO-Oz2 zQ+T;Z=GghnoO0*~Do;#6H4`QiVw;Bq-P0g2-tO@4gu#0|;NS~EVb#22orbdMv(^Ec z^lOfKetZ15bR6ih6g`GC`YUKbUn4H&81VjA^-^3B|JRiPUs@UPWdic~SHtTWCn(bOkS8)|vo+#@oWymg{S6e^vU)t7t3cKbfKLGR}9&LrDO7$}$%@ODnonggD zFPrW`1Eomk8rWPV_@|U^{!0}3xkl;ccljK_cj`5k4oJY5qRzy{*5rMp_P-9MnGxwR zwa@1<=HU=LStwt;LB3-@y8nmYMQL=;b&+yB81S7+^gPNwyKl(V;H2O~0Naz+BiSdA zzrUt)nJdQbmSL^&HGfYKQ0E`!D?xdd)&V)worLyQqeml%zN)cR^e1`G0EcJsCz<9v z)#`#Se+w=nLxT;cVy|07FDwM#613P$-vAP9aoXFV*dOR#V(kDC%F@hkOGvDd1)r0v zR}>o(p6Rucd!u-XD+(^UsOTOV#P!^7ttk2|zA2n$p4K(b;(jmmnXkD%qb3HPl`Viw zynMiy)aUNH5?oal8-B3@IFtvIj1rfATUZ;Sk6K_~Arst5a|F6!u~4?E#$)EZKnk3$ zpVpGa6KIr$6Zf;Oa*a}F@87|lEwhsFLU`J6)e03M9lffSHYh;W6D~gTL;F!$90)H! zcOSjmLB09UHAX`R%yK}1+Io`$yG?06$otR*!Ovzp7jEDkl=-mCzfR3|Y)HpY_8(N{ zx)epu2qnW%GBJ*ZoUTpBlf{~TPK1Vy+vS<*cxVTTNwFO$B6IH#;4w|gSd(B7lwks~ zb_ih4&)FO1$yP^?M|VfA#&)lZ^TmRp95Bkvzl?HjP(C*)XcNglv3!My%f0>;+=q5^=%lCrK!OHmfKs-b1 zoM{9P`FhY@zCp&^ZWBx{vfGIA&2Td!(~DVczE4tp1hdV5@X43#Hn`&q>^5fnykBOy zfn-bx8e#jY6%hAw^kBEKLK0JXD1_1QMvaCb4(6Z?!*@@~7C4gJJ{{NpDQE`Y3_g`x z^*IM5tcXiH{5#g>i>483t{wRoKEIdjN>#wr; z51L&)?tPE+?(*{RxYgHJR;(x>FE%F^i3*2OS2a0AMJS=eO7ukN;oqDu4Kik_{n8VT z%*Pw4C*s4?Oikc=fu8EDo;aBBj~i#h4FjGEAs~0658_A>w>pou)`%{&z+3aWjBY+* z&Gr8Js9BYo{qIt2+!OM_s2-WkSKdA{c?%4p^vI$J9l)M9y0vPfaolsviCCEFV33*8 zQiDDzRu4Muv4SkO#bE*o^8@PxS2VvZLJGDQ;kM)x7rM>Hz(w~cPd1m4j40dwQrD3? zk1*6Q*XcZgw}S5bK*}BW^(sr##KCbEW`QZCx^O~M?qtF8EacZdRE!VPAs@{yeJ~i4 z=tjorH>^KGeLZ2HS(4*DOslg#(MSec&qnltV;eC`x1P#PIX!i0=sE6G{s+L`&SfcF z>SfjqCn>H|&ZfSPv=(}IipP7AA#u~GCaY&`{yl)K>8Pr8#e2xyl7qBZ04YoE7o

    KDf|kTVHAW){d2n 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�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�&yks&HX`d&Ud7&6 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 literal 0 HcmV?d00001 diff --git a/bin/x64/Debug/ufr-signer.exe.config b/bin/x64/Debug/ufr-signer.exe.config new file mode 100644 index 0000000..8324aa6 --- /dev/null +++ b/bin/x64/Debug/ufr-signer.exe.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/bin/x64/Release/ufr-signer.exe b/bin/x64/Release/ufr-signer.exe new file mode 100644 index 0000000000000000000000000000000000000000..cfe5318ccf5eba1f12fc9eafe1eb0c40c9a08f98 GIT binary patch literal 2176512 zcmeFad7NBjl|Ov@-l|($ZM$9=}+N$S4J0fp_fRy0FkOa%TGZ$k-im{aS-xHo<-}rOVg9rUXDP% zRH$X`OzM_Qd9skXEWLgF_m-V@mm&#-UI3XNM*S zgYhn}`wFNVklyahx+pX-cLHO(HNaVx&93w}>Ik@_cy#65F~ZCFfO+S=o(`~F321b{ zD+Bgkg-5B<>x=`p?18NmMpTk>Cz$9EJo>Wpvd*Cl9mY_qm?|wF^{zp}7}1-WD6RKZ zAf3ZOmE@}|iz!iUEKy&|7jPAlG*2ee(rri?` zM#+DjG3-7&24`wLMZ4^vBsJPot$v? z=y&ipA22Mk10oCly}&()zdXYHM_X|b3E!!r9d zz5|t!PheO*odM?kq#>qj)sIl5K!n$JBv_sea-!T7^HA|0n!~(ZT#(JIxOayc)-DNh zBg0sMS0T5#c|@3ivtVNU2D}@g?_@1Ma1TnQ#y-!8noJ>6T7G~u82dRilZ>>IT30q^ z!<|_eXgPb`r8eyM;aXQN7T1+STq<|n=woWT*?n53*`(>__8C7jzn$e8uvfq1fnza7 zVN+B13+#0P%;^I#$`tC;w47^34ZDTtl-cCdec=eI{9QT2UNkwpu@Fa!^*x!izFlT> zF;bP?<6(Gl;!l8s`zk(IANdU0^2$Y~T_P~$Aa6Tg>x#GfJlnjD*vyJY%_dxQYTr$I z)ooI3k-Q?>ZeVkY)htkE^ zxSs^p{VgB=BVE9W??)H$j6U_h%Lq>H*)uwW_OT)VzbX;>u9P=}*4Va+ofBY7erGRL zELm>+-)GsG&B`$2x#fB$3;I8*Fm&Ae)g!w2ptZe-nN)-FK5KjKgx$XuZFdj;g7!RP zHZc-OxfrLR1rqdr3Dv^(tLq0E&nNGbqNtRGKIa7pWBijcmVyuenN4^Kv|_j$pT~1# zR|?oAbAE8$oScWaB%wd**tak$*9VC$`8G&&0J8=Im^B!{|D9ThNtsmVvsv6RfyF^O z%vnqWW-R}-=W|a(x$8%3kL5QYCo3HQO)*xox#h*A!Y|9nH?^};ZU-p0pqT6n{lrY6 znT&`m2f{z5*^if7?9t68I#xV6EpktXU}KBTSg5737`CT$jI0eLq^|TV?~eE9wa!Er z8nMmW$D!9}&hl5nn}4XA@DCFNgMaWp^0xtR{=Ec$)A+mI|6@$Jk6=KSQs5O-7poT8g&;kveqoG9_dcKA#ISsDygLw_s{9vaB z7y7|&4R-m#UI_*j&iS;Hp&^#UHsJ3~}14a>AL-E*3?^@2K zG`gaCjK>eI{csqq)ftbi%v*bH7+on!`Qz~m)?OAyOBolg-4r6!7~M*7aRyCZ5f z+XT0TW9R#NRul9iD{9XP(&4dHK53#<6}7koX>|D}0$aRVfu}I&W2>!Xq_SJFEe}@$+GQM8KAB8NOJczi7EGLEFBr8 zgnry}sE$l+qL^_f0q(pE63JXrCXl(E7B+4$BzhWsV z?H0MFFl)wAb-iV)K2hChQr_ zAeMa8O!htbX}e4nEq|{1HEh7IB-lf2U1arF5aU}v24Hzv59-Smb5UF9%NO&{xiw(w zFFssaLm}B33fdYlJdld;yQR6UA(7o*xH-t09PHS5IsVq+?=$%0oGAE*#M$mP;?BpTkV!Qzz}vl$5gGR)zTAt6xdacZ)VLI{ z#&$g5;$Dnb*2OR>4`;|Z8dhhyrdVve6mRlJQ_D-nw)b*K@45&VmS#%s4gfpf1QFfK z0a>nxC&!7F@F)a4mMRPHO+jM6{25zWxDBLpcj94YoO#fQu6sWSJ$8aeId7TGb!dPS zFBbP+$auGr6Lgc$c;6)G5L90?vD$1N2ja4O1sMwLTnW&9Ss2cUE7A$vU@HykRcT~a z%6qAFbd;tMqKG0bu8+#IpEp5Le2$B0Y{S&a$Mzi$D^Ypwvl zK-p1b5nx?=(cM=*7b*5CqxDq7b%6KBYqkz+TYTykWsGVxKD}V=m!$RsadW-`YD

    R}hMKR1kb9e=gMR0p~Oi46oX0zWjQ%={4C)jF`& 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 z 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

    EQZvu&^lmA1{DLBh9~weM)mX#g&^< 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

    D;awh#OI`38nl3VwU+=R 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%0A? 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 ngy;M|;rrn};k)1S^{%pXu=a|Z-V3OQ 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

    *4H27YfVG*~|v(PY=J0yBm~cmH{u%Ex{# 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

    Or@F(CksmwnnTKai<>WEzrlHuR(zr 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%0AO*6e2)cTd-Q zaq(JhZd%mUbOhSLiqTBt@3Xu02@@o?e9^7H(Cf=Px4@@J-_f+{IOaWO+Gof7bS6_1 z-KVEgz%7}x`C~8LSp#Q}Y37>ImM3iPbV+WUON~5L=Njg=5Y5?96vSHReXsDvQ)Qn=eJl;4)9K6rL~9WZDYg9vYcme z!^7CNt#R08cw!l1NjH7CUiX#MclmmFd0vVSor!HOm&EvEnd~9xvMlRwcyk%j#`%Z3 z^g6w2mjyRDDUBr48m++hzEd@5q~{c6^UF8iPyF9)BFS2o_h5t5@7A2p#Z0zXVFT~e z^A<6l#W}?C`05^`$TK3Hq&7cJ@jfhNxREKlb!J%gsdGzMk*>|zM<&kV-E4%K-Dn?H z8EY&b;Y{DPQGbz<=X)BX*F4(}?<~Wa{I`#^uetZl{NuQ$+sc-=k`)b6ps#Iwrlegv z_?yG1{^fUJ`$S+8YwpDRH4_Tf+MFqptn`OhOZpC)GO|F25^P@oyJ7(787QN15^#S_Sk8Ad|x-*sg~Az9%93rteQtpe=z3L z^=%$c)z6)?_#Bq%bh%e=>-6+8t;GJzqRV!z2RBQ`?%7s$lP~mDoZ9p7T^^boesR-j z&r1L{+oLYl=sL%Iu|CWH?MYb94}Eq7E%eQ8Y!9t`>EYDo7{^PCE&`FG^F?;OvDMlm zi}Q;U)gMQZZ;5z48m+C9?n-Sp&Zt}~e7ntv#@!OugY`FNU&DhbZS^D>TrPS~N}QOF zP0{akZE;fiwij#lD4Q0ByB4J%PAwj@y`gXN8nrNKv&}24q%O_J>Z-$NmrgoUChgqO zv9O%wm$$M5%kRm#i&)u>bAPZ|0GPk;;BSw6cMzxRakSCj2nXL4{xqAsu@beNgLjXa z&BBwVVXI!AD&>vtLmkd8^0+I zRby!ewAfQJ0vs;X_L)=FzrC;iw7mZ=8u)Hq`+#-V4{I*){Ke{Zc1>>@!LZh)Dq*c* zw7VY*%hjd6rExfw^{f6!c4CF5>3z4#REs(Rsqi zE49Kj-{w9t{zsBwW`%AV+a9aPKf9@5?Njspxb;q{KDSEdu)M_=N++l1TdYmG%Wuh> zzc#@7*Q&1*rORB6UAt#_-Jz{=PuB?DPG$SXGV3*o>o!YI*YI<4)?@V7&9xh7{#IK#}$U}{Mx(X+6eY1_X>mCb_H#v+M*4h*O+R%6`?O`$UjO;dM zi4|XJBTud=P3!e-sqdyS;;xX>s3;pfNe&*S&$C2MKs5sQ6i?lszQ z+Vb%7YI-!Qb~Dq6jcF^>?0N0A)mqH;@jl;ry{ySQvTDC$+jT2&zkz9t>C@gZ<9NI0 z6n(=NXICI-1@6WxQ}bwrol(Ox`O@aiwKc``@HsONBkebdO{MKKt;FE-B}p>$>`Q7~ zx1QF9nhEynr}5&&+dpSpZnY0SRJqse@t*0Ot-gM*`2AeH(vaLKk!pB#x^@PO@)>1j z*EEyku!|)uq+RHwQ?xQy}eQrKh?1xcl)yi8vCs4XEHH4|| zn35zFUS`6Oa3}1MJpN9BLjrTG9_8fDU=QUwR`&zJ9q2nNZ(7k`-|(> zGd1+pd(J(>)$>`Yv`rl&5JLLdX5}(}hkP%YdlnPKZ6xZ)dkeXjwYL%-&3>Cg(e|ai zBoHLB2517%+~KvIp8F9;%XWxy(RfNK-6gw=O-ZY@U3cY8Z|Ugcf2J(@_A-b6+PStE zx~Rihdj`adL=X+_j2RD9bN6^z>&)&UMt@ye6OHj9PQyJyrcE5PVPkk?p3s+VWXSK= z3}>g?&gNU{{hEwQ9^&}1Tc21C4@$Y__ajfWJ+wt56Y=@yhAc}(hRd}13r%opBHald z#qtLz=uZLKlXjmNc_j8vjlP;KMb;{=z!9Ol`Z1O}P3*7MC2=%dm+W40*QonNx-O5` zw=6#Nsq=g7IZ($-bJLp2&CBqaVeRY7r`;rBIoABWmO3Yybd2RP!_=~pOH$a|1!~D#)57zk&yMM&kTHEgozyR>`N@)GtP#!A=Wv?`UH(Q% zSz9@4SNJQnv{9d4c%~A=m+H7ju)bJzKU5zNFT*-CNu^gLUrd$&!1DzM2BsM+ovUlt^e-DR$ym(p3NJh z!f6w~R?T0mftX`3RAzAuna78XYo`LD%w`(V`@ZphI}{?Nl(%b0;I`HVH=Ua-y5nf1 z;UxK1EuE~2G*o03>H#TJGD4>vG|N>y9Q;l_^kBZ%I0lza*l~V4Y(4m1%|-lsq5iqL z_l5dr$EWM^zcu{*VYuu0I&*1wr0C!?73#bW>p3>8Z+|~n?0#|Gps4Sm zWsal;NCE2F1ATX9n~sgQ*AI8j_CMS+4Ugt6c>vDv==6AZdoA`7Xe3lk!r{pDn_KpV zN2a(LTkErgmnI3ta4Q*6z;N$QNEja8>CA$K?tzdcn5mx{E#%xbp7pb%X9^cqZichV zbjr6@UN5N!ug&_J^|WWpf3(ax7wVnPvfC`EXg+uRnL3))i`lfBzhwV)EBDB<9u}`j zx3W(xEBmGDN63ml=4xl8;%}|qJ$5Y{_hw0MHWBt2?#{I`veVjQh3k66&XS|`P@g47 zPt6?lOxrUHE%c+aE%c-FEm(z%3|C5;Zod$At)x^~4X2M0Xf_RMcv`rtVwM;9h3D!@XCM|oNdR59_&XB( zmo62*B(3YK?0xkotI^}N+1V>keG)Y{Ijhyxet&f1si*qBy3W&m=V&DR&C-~Dmig|m zdA#K8{g3HqVt7{Dc+m3Xef!?_7AyM{$O!lwuXFviGjHw~816G=HBlWO42jJ=ZFx9Y zXj%8zt+H5q@81hA2OqCL@5+6){wke5_+~dk;N0I$>ecgU1~o1YkJ*rJk{rVCggc9*%x*%FT;^_7&(_@@4fKZ zc~?KA?j|a;tG2O|ea<6B#tdhE31oQmY_$!SBK))M-5v%zdBrb%Kztq}RnOY}RM$d( z5ZdV)wZA9o&(qP{1JG6|RhBt5FHQD8**DRrnVC$DQ=j39vxQC5D%z0bzt=o0O9x_7 zmwGy`y0TW+m1c90L>m?JiPq;g_58ziDd14xG#^wakZ4^D?tSbbs>m7Bp>4Vpd^YzWC4Zz;exfp=qvKy3?4(HQ(Vrqjr@8dq1~5 zmgr;n)P?#k|0Z+t;xE@{h`3s3T)+Rl8nNH8SgMQjUvBMv1kAA{7mK~gX9=l$;?(|y z%ZEEIb}#o@v9hJsp4%^Pakw+9#X-nBHLto-m~kg)W-M*iaB}Y+r&sf<=OgZ$!x#Pd z@Ti%pJdths!$;ZYQrRh(p6T<%ES^1%5KkQwf@0N zW$P@%V(zyKd*g!U`NG3IfAwp2V>Ti($eza~#6B0^OM;qvR_1=8>YuHiU@tMGFIVyS zi9gTWK@k7!QD=7koRx^#W#F~-ZMMEo<}rGTyzRzP@pTL7pk0olG`k6bIfpdZRw@u_~~Qs9vrFmW{sgZFg&wr4frU2sda>S zZsIpRYT=z)T@kU~E=KX%7DJEkR5nrVM!5%ixr?Qz&Qv{i7Bimi=jZ$EHtno^ZQ6U= zkHb#-GA}U;F>@$QJvg6#``-WT*%|IlCNvu>+7-}UCElKy>{+II z*!rn-!z5XnB@G$LbNaB6B=5P#fFvYlNI=LD*rsLs)z{v#2QZwk5^lcii9{!W<-|Ii znoXm6aOlqc%C@Xi>KbnEwA_gMu3R4PQv%1<)?civsb}l@=lPO(3}_yDmw-M#A2y1= zSn|#;lef0pt(v*M(kH-KdhE~XncXIStq9Mp^8yH>9iE==Us5`T`#vpM4EnA2+rD1y zm@3sG=EBCp+3;q9fz-0>k!Ac#MF1#eXj|g@RU$#tl#4AT!I_{6;C(RyEo(^)1b1xs zQi0Vi;2t0=0a%=HWZ|R-&a_&Ok?J$TWGgq*@nhBT&RmySp1myADkxhNUMkF^#0Gs? z%3>^=W~4Fw?&T?F1Xkv>&u+`n>c1lje>kNZ`=x@5*<_U7=XJ|QSh?LtVCYk=&wU#D z2p+PYVAaJc;Be==Gjb0{vUB!q5sxQP6rgMOj|-N=7eQ^h!DD*&$++q~W|;PIF9+f9 zM0PdJ-s8@>y7P|rK|p~SE<#&7lGPKGrG>gWQa_=?MbeV2hqYq^=*g8Zu|KoLwHq4n zt7o>740m;%R?fSekN@=u{&3dxp!lxqnSO1@vd%%cC*2@LUL!vEnhAfZXi1=s@2=v zk<#Ye5&l1Ir8F?}y^uEZy+$?jy%0O|z0f-Iy|gvcqhCmzKT>_pm07G+{mztW>&$aU zN|u=;4VyDZ?7ZF`Dfy|Sz02%{ChhEnCS~^W6lF^0w&pF*b=}39cwZ@>*Hl}1pBZ!W z#?vxfZMh!A269^0<6H^A9oa(H-<7LgPwlr>;p<@iirc}L>hDsm{l8T6t>MsSt?~?q zY-JhlwN*soY1>FPYh$8icV4WvUa2}~>pFaut?T>e zE7`H--OxnL#D~q1><_U1w5Mr}i$#UE*HS1o}-4>pWJ6&lR9^9#1Pilt; zrNsT0`IfB+8B_Im*M*j*eY@HhpWInK+-Kd66r$K%&-e5m#MF+ zSP$K-v!bErZ5fs8Uf(L#TrBxYl@JA!!ivko?Md|=dAqE0p(JrFcJusNu?=>c zu4L{!hN+e^;#@0?uBPi-eG(f_&B*MsdZ<>=pl{mW;@mGb>SJ#?(oD2#Ir+>HcWDjx z%G}2I=~H6haCGsEv)dWtjSN`Cb*$RD5sMes~&R4ea#pTx@yUzNCNi(PHBI8lVTAzLPpR#o| zv|20#d}X&5@gV+oO#t66YZnTvEO*bbOaXU`>ul5O+-+aMIWn2~jf*g9M#78LW0pqJ z?>iOqm;P$a%yR@ikt2HU_ovahc$O7i%x4srivL`XjBeb`BT6~r7*5ptqOWC9sb8(_ zKXjOD5ZJI{&At8J3L}`#rH6a_-B=4}jw~Fx(aNRL@0;~6e&)*B!jZe(v-m=dJkM{< z#^T|&bM4rrdXnpK=TxSOn|DqnsqYWHG_k??8U^f5BT?byh@V}~Vx`%*BP#js9mAFUhx~5A@KBdvmy!?kGVF)%=32U27U+jA$M#Exy1sp>uK7MW zfBeB*_a$+^#x+;-=$EP=IL`e^b7${Wt=eE~L`i|kU!KYlG z;Vq(}>+EZx0#IOONjhZo2=t^o4e>{baEjmxRaxeyzhJ^5FA%a*ogRZ6xpU7TI1 zwfY(0k50B??4|A9taR1hr>64R4HY(%|@S^Z>!^CT)`^OdmuZ!*+e!kK3ph1 zv=Y{KiSBa#U76Gl=dE1qR^R!Z>${5g_%8Kxez*Eo{B}N{yZ(2UFXwlwZ_ne-=kq*- zcK+^V`W+6|tmMdS4q4gZtv7Ps?N61PZhy+z;QK+8s z_r=EGW;Iq8=Iab6=jTBGaoe$%OLXQ%@&z+QIY(PpIcioS=Dyvs!FMZHnw-6Bl$q_? zmH9TF(notQjkNa~MZLGH#O*!fhPE6}`NwFmo$uvKi|u>#*w*svdt1M6?`5g>p7yNZ z<$Kz*CYbMO&vP~Mz1n+X-cOe?^CI^QTmQv}y(qTX)Y;R-Ow+SSB9%z$J0+SOE|mRt zZf42mQU<$L6p|~b-z`F6w&fG=5d`C%Y*}iWUSb+fymLdEq%}fYE(hi^l9W$9cNPw( zrW2z1^L?s+p%TpBtnwG?;@PVeLcs@Y`c`Js8{6u~-!(3Wd3f(q+YY)yNt3D7+RUZE zReD#l0JviB1<6PD9#P~hy{=rgMzwO;l6K{?Mu!C$q_vdN0Qm#?f) z(&d}g#-0m3tG2n?*tJ<$Tv5)|#;(oP$6n}NvG)ppE0!!2ufPz|{Az5HvZbk&+bvzK zTy`~9B|}>XU9sKA;Of!6**fjUulHBJJaOgrx1-#t)#S-dLMc1!yH43@-*pNzr}r;g zp=aNefQM1X5B=so6~!t=qCEQxp7s@x{1E_%SH6w zBE!D>YBAy_YTXQ;Y*N|3Hrh1%CM3KWY_uu-P4rdRICQElowP~%p|&~aklUt*dNse? zPh{DiuWXp&-Yb~d=nC`X8-xVR*NvbR@2@J5os)6 zdfQj|(%8Prm#+3zzO=Nj@}-}do2YkME6I|*#XT3h#>aaHj0I!Y+xB-j_ZaTl@m#feV>?CKG-D2nG!NME4iby4v_7Vr=g_H`OF>`H)hDzCR;e_XXb3xpIwI>j=I?Ca*WkJtAe@w<6%ti z)J1V;->L;?(!E=HoiE)t4WymM{v##g+4y8HaXL@01~zwd^8{XSr?G z#gYC{lZBR!@1%2U-5!OVO-sX`Ta}|N+#;A(Nb6zVCF}ZjzSQ0!DyYkQA*!v* z7-z=Ro%ym?S@a7lfv|Gu5hxu8`m)$iFc zHrX0$^PEjiHmCIm1Z1 z$W2WyO!f`1JKa!4`)f~6x3=cyx$&OR#p_%$sD3A6ZS^?iGOK>veAl=AEY|8<`I^1k zR!j3W<7*cpe|wr+^|F1NLbj1#t_Q8zF5{seeZ*tb!&2JrSK(xWw*9b0N%_K}M9X{Z zSL1%8*z025Di8+DwLBwreptXOANRM-C&zcfvk2_LSlbw0sazp^7tbHtGtxq4%eR-a z#C+LSekB=-H5c-v?mM$%DrtATb$Y&J^WGKPEeTd^*Vs#nldIVvrJE-9S!4fw)EM^J zNAXh2nD(+KFEVO>g@Jw7sj=+4PK{>Yb@o4=mSgR+pLV<0zU%CNJdanbSk*6jgxTAY z+u6m6eo-<0?(Y^O`x)+UmF;Ex?csrqqrwR`9znP~_Dtmp4eht}yteRi<8$HQ#^=(< zjn5^0`@GFDH$M0Bj`#9DPs^8tH_BJm(LU2c?>ERDkN0*Q@9p^9+wr-#v#qPvrq5j- zpL;t#_jY{l?f5)xN7g_mj#tKX<8#H`+xv0v({D9~$+_zN&S}XiTaT8bb&ttw@3-YW zqwk+d-9MMqclOWPQkO=nOna}9wD%fCd#@3+_maN7m*nkT?i*{B6x*M=9KFAsPv6^p zG&y`OIo7Q$s&jhsP7dFl_f8(~k(r$iU#2%@>N->*V?l^`ktM&Gw`grrB>*Gs>!5zo)+piYN_L|Wi-$6suEOfDl zmSfj8#bufMJGCD0LR%@aSzZ>MTM3fAj`M`$wKI%Yo!LPGYfiIIHzCIqrSA-D-&l2w zZfnWUELjC0QJ)dYfNk41&oB!i;SM8y&}epUr*3LFxmStwJ-K!9>mPmuFPFBhpNy^U zApgGlaw9_4OP2eP`SKe}IV|X~b8ykm3mq@5hK`+iVh#ge+m8mvQ7xPA?=hx(+&=KT zbxZJ-8h5tB)4=e;Y9rcQQhN!eSKLIO)>h;EW|4YeA?eRIM&Dj_eQ8zQso5XovYfA5 zAGfBD(#fm)(Mj0G%|4m^Uv1l+m+0o@{m46OryYEv=K5KA>^}(ASJlH;%WO8Rcw0v5 z?mV{H$lvsLXY|+C0#><-mwi^UVL6V~{Z-}b`q>ADR@8rQc?HDW_sxknO|Ll=hQ7QS zhI((jceaI&)H%j@Z8a3_-R=?#)&}~p!!oT77gz0h8F%i0;L_lH=^OaDYdm-3*tPx2 z&}*J8PkFOezs1WjKiEO4yMCv9*$UoRa#-=EqmlW;(R;9RJ&v%Ce_FoB_u<~<=W*12 zHIeo+qGj28^Wg<~QhsN7dV9l)@p;tG&E=s@%G-MOR(g1CH9xyh*^ey44QsNdtSjdB z_Azgo^R*+cBBqvSwN-ERe7oHkH#+7;V?H)wXmwUtp0#vFS`u5fx%Y-OR@91H)2!}Z znaK;LJhY^*Bb_B~^tNKQ-YrfyZXa|HNY^i`_#yZCD^ktg$I1BSLg|Hk^uEP%-T#Mc ztKlRvPUTw6P&=w^wei}ec!-(1am_tx0F4tCJsL(z;!F6^ZHVa3SOWio#{Tdn2B z>^yk-+D;a{H@E7JKV6wjrLF}0HXn1v(=5O}K+{LOOmEJgpN+}z&F}3b!Pl-Ye763y z)o(>FO|O_?@7L~2qUU|CHd(FeSDmcv`CMx+brW7zblOVO8R0-2t!*_I_LBO#4=@@==SYGlO$=nb<@iuKlWn_oy}J zpDbZcHxY>*5v{^LS^s=jwk+^bi}z7#&yq0<*8A2UcIDgYYHW6d(SSWba`)SgWm^wzctO(^s?&E?`3r;3}MN{UJ2Kod#X} zal+|Tb!p{^I$}G9W_83&5si2d>eV1L-Azhx;$(n%&!rkiJK}>$_zXssgGIyqNQE;9 z8UuA5M1Mh9^iR-^GKw$|r3l-9z}^tK;YTdoN2|G_o~=KmWOR}1E!4?O{3*wc)%f_I z*AR)LN-?UjBMNQAL!q?$C4>eh%EDvLKsJprqgHd>hlbNuMS%zx1QM(h?Ysen@094SWPpb4e4LU5ZQK0TAI82shV5xB*L--W z3LZM3I$`4+L~_h4!NOtLb7Pol#KhV(5aoP=$_$2R^qbmxiNG0PLP2TnqZ5t}KpuTC zPlO3o(FV;JJZ*wPD2Wns=?fh(v4OQqY9FO=y8q}jS0r550O=Xj8vaPn2$;_(@EfoO z1qoC^iz9LWL{NGF_h1vy6JZ%DD1s0`zMMinTkOettWZf2G<8 zgih2sz&Tk@$+M*BnLajtU#QRDtYc?~wVSB*%uUtW9M+!Nzl6V8ljGkZ=S|lwX%~J4 z==qv(dopU*)8aNy6W`(eug8R8e`*H!ZhGGtpMjWy>6wPn;A-({PDL8#Pdv}NSVM*cKeFK(1M>;M&{>#U_yw8#%OgnW z9WVnyHISq+Yeh*B(Rh_4f!-1=NbE@3h};2wZ!f&2S0te|Z5gW;LgPjdosfYIl4d5I z&>Nn^d&bCVqUQiUUaJSr4k0+&H7o#>5N{4 z6ZW7prDG58J;ab#$uK<~$LWh(6w^2bP`5@XQFx3nhMoi?%}1TgYY>_R&xDPDRy{%p zti8n;){vu`06_^o1!$B?8m&~y!V`>_HrdE{dvTQ%L_&lJnXz2X1WTd*M1J^<)(CEJ z5s8j{BT9UYMYYp~i(l!9BoH%Z!|^(3(soqgwG*yh)#H=#66(uYs!uc}31u2*l*!|? zLnf9D*&DrfoFj?P+JhT7QZ{i}-QWS*AtBz#gJM8RcgAb}F{yJ}pKwVm3tF%v3aGQ2wTW9I1%aH&EK=kKq{6p(NLQkwj8#$NJ2QAuR zJvFtcW#bR{^iE6A1omuGGYeqz#W1LqLUl(80Yem*p%MXM0Se_aXg5NRFb=cDr9**H zH&2VHshI(~`jSimW2fhUn-VEouUZASP6$z%!Sb1?D+-I`fdgd+INGL+lqWY+XoaK* zB1)sA7|C&c2a0iqFhhEX>gmFm)do)l<9pzwAFPri;HXdIRy)S$yiC#i3Sc6&dV0<~ zdytb3{EIuLu6}4jiD;^imBoO-dH+~}oLYzlv}jD-t&AXg=T3muwH}-}=aX`*3N@i@ zmeyBv5O##0fW9ThOr=P?ln|*JbQ2ZI4JuIdJ;UOo@rRg%0s%@}#0MTng6@D!nJ6#Z zL`6&#k*UGZ!E|{4YjImhgOV_KY}Q9aXw@^`*amhq5hMhSYQs@THWuh~rG+@5daTc0 zJPra3SG$hUEtw1(N46atdW8ookV7u?qnN2 zO}~0hNu#ReQ=}WiGQPo61|`Lm2IUOk#Pu{CaRQ5|Sq%f|BIqc14plN1br_a+uGpcg zb@d2Jaq%*+13qR|QvW1j0KP?GOyQfsc@Mk-B0W)9%A`L4)xVbYr~Q$Bjor5%NSL$( zk>LU|s*W_>O~_4JK#0QvXwH%?M5yP08v&}Lrx@3W5m}cvrX*=xO3&;e7QH5Lsg22o1QFC-nJM^SIZqtDdgrwvovK=(cM4%{2d7uPKYUu-Y%o)HU zgC4fi4;G+n;|Q8HqFFdHmgpqfgBJqFK19`2#OvA&t*OmEy5&Cxo|-`^TQ~w)hQ$(8 z#8>o6Wx-GU6E@@0(SBssv!OeqZlsB-wHF^Gbu7nH`i(T&NSh%ZGLu(}7MH7&Th)q z+O?^+vtPuNGL} zES>ntpJnwIf2^QMaB+S&)^z_?zgso_wtM>%jqjt)OC>R%$TKK*8S_^+Te;i^?1BIN zLHT)vv3rp_l3r>a=ckLZSBD2K+l{GQMz;3+yhiumMm%gD)nqtU3)MJaUZRP{PV82~ z$UHJU4{WZVpIq>^-D)?vo?2ymi_d3hbr*h*Rur*os6Tah8QLaeTd2QSe$gXim)3th z$bWXf;4*|Rwz{S*)Ahm$fjQo!8`kYPbH+%*T+~@q~T)4^WA{_$`)rzCQb5 z>_p^u>R#}N3l-UEJKiVm4t%ZT?)>QPYO(cjd3pWBq(=*3?v(Fk?D*ez59e30Rgca_ zu}sGq>%5*{7+$XYL4;H<7UErR*3XUDk>_ZB2(8t>ujeg%Blzli8}5saXX8h7I@Dbf z`NXboHTm*mYT5Po&M-Vb-orHiET>aV=czw?$C>zUcb*9Q-D=qn;QXhRLCS2uQ`zN( z8qp2;+FjxHOv$mchfB6AUlxT$UAe7TT+8|^mzLr0{)3Ko-c`1?>raR(Q|r5XmMO{h zEK|G2_AFC72KFpd8OWYxDo<}u&DbfA$=FLnE0ii7tx&48v_h%U(+Z_ZQ!A7zU9C{6 zw6*Yr1le5A=)7<5XI!aVjdP`PHPV&J)mT?5SEF62+^XX(gzVWKuPp1YRIc=L-?u7L z{MyX(v+!Db;>M>gv~$lDFWA|e-Q4{w4r|Y;xrTSJTb^H%Tp)Q_n9zK|6GHHP|4Ao5 z0d=XeKtBWed>zM=c~)HSQ&hQDW&EsC%>%!d-NSjp*Ema2>N_`h35P8{b7X$jqOaWd z=}yVWC9>p4B^LcOeuq=6X;?byKgN+ynL~Q+P(GDbw?CV&4@aJ>ab9S3KU=bvZEifB zPkUj{w4ObtRwaD5rqOJ9YW#hNWA$98zh7F@p81&PMt!|>V8)g`(bwu&{bcExUdL!< zpou)54wTY;Hsgsbo@5}C7psP+SFI^aSg8HQp83LqC?@CacdZ_)zT^jxO z67^>SRVMVcWo^9M=98qHwvoC)b!Oo7EN3z&=cS)6EO{c2HK9+}tMv~N8dnSt?AB9l zPkEYm>e1-p(X2yb9YgxgIL&A5@J|+0tsnVH{cN3oFVw$h>)&v&+I;Yt8i^Wy@b1x? zedLZ1ETiSBcm0qBx5sMA_&5LW+3MG-@Y#C*Oue2fhdfj7C(HSstanRqpRLc2*BQ=w zt_|$5q9MzF7Av2vqmS2jOM|EC+vD~3M3wt&d*7^gZDrZ}T$S3~?oFA^)}C6mg#EH5 z-p|yL&uq7npf3r%TDmg8sTF!m8eOJSx`1dF_>zmKjub*hZ z`lF#)=OmU2o9(Fe$tUWwpXW2c)2-ZR>(g`f*>mQya_RWlI(Dw!<$OfY4i!=Y%Tv=6 zbwrKfoi59J7T)Vs{`DHt>xBc^r0-cD{Y-s+tV%vsB|lStjPh7v z(CYE!x@-GNl|EA~e7647J5@)mBVVmvt?Qm_ZC$C)C+p0~s@X4YUar*Q%D*bIrpF{pHr{nL0B(a-z;yfu&dS&;V^l{wu8wOn-VX$g4t5QKX?+L(|){?j7YbpkvQ`IH} zZ?@L`s`&77E)h5eNSI->YUpz>kI?2b)uL8GkC|-pZ3BP2hDc}6)FJNlWHk=iL?K$% z=)LBug)&i7h8e7Do+ofj6Kn;)(lA|Im!6~0QUF|*7Bp6<% zOkSB+voYUDcdBINWx1%!M0+cvvX2a$I}_xYrfK?QDfVUuj2GKJ*N*7$Gj)XP5a8MR zPWvzuR^$2z!25I68U!ihGX)b>?dPf#E_<%ELuIH$UFw0v)E+LsUT#N;RIgX==ZNoD z>Im3#o;T_UY$i2B7s$f}5@hmtop%NuoUYHgpvEPK%crUhCVr#7Aq@th)+g)u6Weu_ zVI0$0CQ>Sw*dP?mAd?UYDV!sjJfnyj}R5@K|g;2p)Emy&W zUqMp@1ES~ZnD6{49tmV&a7IaB1ROthOQG7Qbg)Ez_&^J%n_M7U`OC|Um-GPisa(Vl-iEhePhjSikjv}f z59^8=hD^Cv>I`3kQNAgd^7ME{!t(1?L!v5_KT)raLOt~M+-G=;=ktiG)dtyN%^$pL z4fahVJqU)+ODfb?HUVA0W%g@L9&Ex_dA8QDf)J0q>3KL4ZXu5rg6_-pi6<&SvyPu> z{m=u-FVB98XFu6In5P?+Z{#!9c>9@Z*D<~FSfy%0@f7>9w?su6Lkp}KPBL0GwI~&W z8=S)y!`YjYSQ}4NTjjTBZ%%GW9eo1$Ur{E|WV6LH)wlr*1BqxpQ8`HRd1VbjRAX-J zAna2pWsBMgNPG}7YnT-EX{{@MD3DrMtXYg=&! zWq^|Mh${IY1~DlP`o%}WYCw#YE1j$^#XaPx3>=AYuoP;I_((9NmC^_6!yil&S5InE z7EaI(qfx(sX)%U@c>VKVZBPYMi;+|-kzz0= z8s*3vu47{+kWmfiKkP_kUE~_hlk4Wkld#M(+Y1WNBihF(Qq|<+94eW2z$;&X8|iJr zS#pvXF!??XND_@1dZxgyeM?@!{H7fe!^|E9!?WcMf-bZ3`Al^vR$hZ)xEaRa_Fd5u z1A$#zoDff#YhRRierYColgP}9Nb%Sj$j<;0@@5@(>G$VBocNt zmhdA^t~qB`YMdk(@hCu5bjbn)q6DNy_2+7sJk3C*TgqpOjzeTF>YC>zP+$|;ji5jn z{-zX#Vj{FbWnmM2%X~;c$xLkM=8+hO>KvzB=aggaNGC9%9^(-Tq<*BvyaEh)shp%t zglfDFFEgXM79}XrcdD`kl8i}0OXcLHWMhsN_-3Yl%rA7+^#A(z5;fU9YcOabxhgaw zEW~0+CtD?nTf%`PP%J_o28hs!51Ei2KL{1an(&i&X)?iV1`upPJtI+j28{|0W3$SL zeN00ABsCP+64eX@WiqSyo!K@80SDjECL@qV+tlYztn>+R7~dEKCL^F!Btg$kWiW#UW6hIiQAcLL*5Q^h7 z5_E?7B{~$L7xCEihd5EC()@#J_>LgT43rQIpCsUNR2jfo~Avc}%KR zf7HyZRGUnOL=4BHVtRGKnvt0^Mg%ak6<|va0zly#EhVo2C!B)HG6jp4Xqjnxvgfn0 z&}($c@FXNBxz(o$qlni_$V5aL7MbaolSxsMhtoTm62;^9q{M)sTF#5V)BtU4sekQe zjVt|gDu87tB{?y883)Vg5A)!R7*aypWEM0e z=iCR5*a!3>gl@v8AZ`roN?%Zw8EIY8Y-@I5S-pf+qZEE3@SxzQxDbfrps*uVRf*YBFfOsLlD4 zS0fshfwam=wtmnJL`+3F3vseQM(N275~`RbZmyxE=iCM$xeOwRqlLTUI@)7=?!#1A zNN)z;17HBST=-Rny=;IL!ukL+2cdIuTPV@7PT_@y_yE%H>_$MA;vJZVRd5_knsiB9 z*pz3ndrF2ap+Q*TYdj9BB zzz*9`2gN1f(i-P7Y-g#5;&?rxQaW=3EB_)eMpO#V>Q&ouAq)`f%6(udDIa#=xKU7C zWEIuSx=}{eU4(O%%DaLbmcD>Jkt8RQG`5Z>`fLD%CwMTy!bz#(8oESu{2Ck$4t6L! z87XXJ>HtOBW+?_p28_|8{3JTsR~M@=0V)-kIYpLhVTF-4YdHU|+gkzJ1oK*voGnzL~fLp3BV8Io*4#!In=%p$?4LB=|;MP!bol)B;! zm(hk}%%c}(U@-0JGd*O^#Vm;%tX)&I>z|6-Bt?lu6a1Vuh=eEt(PB?DIZhH_LTVC( z4PECPke~^s;1^X1!9kDi3FlRWK*yPeCB_)27WGLbdV@vusbB8msDPcRNt8tb2xJ)Z z@I;)rqCQ9fJ~Z5j2itP_@~Jx|yc;piWuIIf$Kh;(Ej(f_KS4 z&ny@bvbYU%!Jb|i2tP6aR?Uih7Xu;3;<6+Rg3ZviFQ$?X7_gS{s7)?M zA=i=N`X2|M^hejcHS9tRn1M6FR9H0>ZlNS*p>3F^NGucAWl2L#bmOL>VTcoswT#sW z+FclOIAEL(U5a!5NevhQR(v9ZaLy~Fi@EGiChEvam6pf=jYh<%`hr6e$CddF51@7< zcSeH2wIdHEq$pkm)me-%K6C;U%po=5IohKMVF4`|VHPf(m(E~9UTa*A2oeYoFtrFY z7y)o_XtK#?%0G;hY|#u;3PcH)bjjdhmso+;!3PK>G{%;6myFe7H#~8iCIFn^Jp#ZJ zHE?1cArS= z8T|BVs0_*c35ygXo@kSd0K%IDSaqPDIg+(7wz8oWEr}}XIm!%b#9)p{maqh^02X60 zAnzqZ^hIxa;Mcqvp=SC`O5d3flNenfY`o+QMarj`9Q}ofYVG6wbMa5=hW(^76v*D3 zn~k9~zLPMA%nZr-td@qZ;4P{*kjzmSidxZ7f}>Zh#k7f1JPQJQBoV2kIj|p4B$kkt z+2N18)WW}_L_XR4QJx2sFj6`c%WEQ_eE1}@;A z%t&P4)T8zuy_4&Kls~==R9aIueQ7H_S$e@K6oG?;9p<1YZo~*w2t+ujdvAb>LC6C# z+NOMC;eLP#5&<&CK-`BfCKqES42bf{pTUsP(10-*52vJA?nh5_qXpUvdq%^VRK!JCCqYtAtV({i6`Fw~ z!O%A&fQ6(EpcVN8W_>%u7TXv7;7Yt=3ZGDChQ@GAE7pnShLfc{L*=P>f#|TzgP2Q( ztd#Fe1OP$|2EiE=#h5(PYi3eZF9;+xN`4ZL=5L^e9rPnWFj1np;Q}jv1O*1ASpDe% zw3s~s2wW4X@ql`@nsUUxb$1ZB$DrNSch{%eUUoa;?SyZ~uJpU#-Q{QR{CF4pv38-` zJrX|U{)F5!-tR)_cL=;!XWenIcz=Zp{VtUIE#TZCnU=m;$8%3d?(BQBogMG=|5o+t zK7r}4j@Qon6VsOL`L`jWw=;X^dzF686A#?$k-HClK30EuhQK#VJG1wtkl)=m7h2!_ zu9Wk2EKfeTSYu3|bq~u^(>tnjN5QeO$Hmh9c<+I}#yjn!wOELpDazBC89K`1Wf1yV^7|T&6AByjo{%PkOycccs3) zUPs-LLBm(74JDH9Uan)Yk+!eYQM`P){*fNavLy&QqkDF7)`mb9y;kM43#DvN>#Wzq zv28@GeWm{3SpTe!hp@LTOvb{lwnWptQR8)e(WvmqPH=U#KJyv6kqc?tk@ymuUaj(4 z$jB>w-#6}UlH#FPt2NTleC{J53GC26Y{6$~<}$srxUEjCK##VO@dKN$U_KrUwLvzG zh)sOh`K$F!uWZF;SKGV9pvF+R=f%cG*R)L=Y-a8Y-CD*aWTg$q<0m$mv)GgMN5}M< zO<=GK_blTg@0BKl&#cKWZGh)P+O*mC@%pS~qcTSFY5j724r>`R?`FT@JzLZE^KV){ z{1L8z0J3yj!#70mKY50cI){mE79O^K0wTG5X2#>~LP*zRuN;9fydEwG?NL65G0Kf#Klmpj*0elAQJZ6F>yyupWdBN6J7 z4uWVxh~3^uj7hc1u~<YZS3P%e_(GNbip%!b*a%yLF5%DFYjZxZoFF1VK{pO!C80u;Xa_ z4c%R9rQ9oE#KuR1MRWVM;nc7Ae<#9MiFt@5L4kWc1!@^vyj7T zu;~vbcpQw-8W{x{E%OUn#}>3{)Ipx(kzP&m86O0Y!=;6*)i<=$85uCbwK{@lXi-}@ zfp(lXZcH5>ihoCAYStF-k>K)ao)G8H&wqELg+XXkHbTu*2(%mlK&b}2D4lvZVod1b z^3vZ^Vi(AU`a~6XbnYFKW}NC`7k488`TZqJI8 zQ$}9JB#fJ(8b-ol&#Z*dq!%34T@YI3W{x5erWAsb+bRz_LXTcidge~GTJdUVa5sDP zbC$;_B+GXRFV;r@(n6RSi*p5)VT zLn3+U{ak%UDrsA3?OUaOu8y+3R)a)Kgx1Shg^qLOj^;M&v(jvxaY2!F@LUl{pjOXl zSh8CyDG5~5m=>Xov=EUMyo_E@(hpA|uR0i-CbDeHp0#agAG$Sal6p4+@7(j^};29jq7!Pz=)br@S@zXA(F^6*tH#`>(z*VUCakg1~! za_OA4bKx(HLtC(b0WGFm@XtE5ai(<_RB@ZpK@|^&E3z7)Iv5XrA;L^pE4&OtU<)PG zHG+6SNd2zy1wAQ|TvFI{Wd*=Vn}G+u_v{ryv)^+}Iq z>5CrOpRb4C=uAJ>D#$_4tVv?{g#h*uiE_~bnw5ey{^Bg@gxIXTi(ELc*Nunjfo)xt z)CQlW3x1+!h|#Yjc!^|M;#GXCGdMJ}A|1I1l?f)BWHp4OxQUfJs%bW8g9m&RWU(le z@qTC2HSwnbWoU{X!w|(Ue;1VlQEW~4#;Mb(}?q9C5u%3S|j?n`i<|ok|Yh|2tsz0{N1vVBKF6%Mlz(XP> zl#>sCLt9eccqA>8HGC9)!3(@HXjPi6!lspjw6!iihILpib$P3A+Q9qtO21(hykgRb zJ2Q6D^K-K6!8g(+eh!yK*8Tj$b0o$wMqrXcS6a-NMMfB=PqxSr(XU! z>$qdNRxCeat#j>FobTTp$x-Z7Om{iH71ZJ!x(LVX568qqc_LfkA`1^t#MgNqkHc7r zSooHPcyC#|t+(@%Y*4{ia7W90TviALcv^YmgeP*v+hGTajT-|y%eMTGv{+g%^uWG+ z83VAFY=rmW5E{m|;y=DKA|57-l-Xph69#=w|74|UEmkkz2;y)M`>916*iL(1m8Q)s zGZ>w6u`#?Etx?k;NlC=k^r23ow>o-*LnY-v;tz(&wNR|=sFAVGyEr;!)C^Mwk<5|k6C6*Cy&K&v`Pa;o~x(u8cg6p@jX_giONCW3vaMrChyRm z+=Hg*k`*ybm`b@Gp&&n56J09@MLgJewPSqoGoO5>Asl8IM>`l#Zp3nI$}TL#YB1$E zbh3eH?~D>KAJ!u+oyU@7W?8m`K624Sc$}59AVwZ-VKSZu5j3U0wCFp82OqLHWqE*@ zd9KE0?5s%-v`$0uaS;PskOB&{iX#fg@9melrYvVfbPk(ZmenO9!W2nmfMEa@r4v~kQk~`}Krovfjs2k*aubX||!y$MG zx8__pnBQoJ)QK~=9)7We(F7Srh65wD(X)p>*wOg367JSkXy!*W1I=>68(Fr>s?n2A5}JCUH~sBMvyJ@m!O zG8z#wOir8lDG{6}@fg--*~G-?R_tPF-XL3`J(BT^WFR6HIrJjd)y%?D6&lq&=P&@x zE5tM^t3m+AjF4cY2_ObVB;t>WwUZFXNr8~TrP4{kftuTMK%qzgY`6JAr0A0Ocm@sP zP)&+sx}$#xi)O@Lo@4;Hm)4R`c1`dI-J~|1>w$)~8^06DAxfFD$<_y&ATOths!Xa7 zVoZQ+jmkex&iNP>X%h|oF)EF4KDt6bItd9ySF}T?h9%gd6C_Pc0B@SXREc#Yjv#O# zGSf!l7T=9WVKV4rLg%Aw=7W$##-K}y)WIVLfQ{&0Zjo22pk<6RZ2dHNamX0aq9*Af z>ddt#l~*Yfl27WDZZfGro6MmH$7!xR0n$!NWXR6evUniOcQAyQn0x~8%mye~VYbJ1T#3M z_i&$|lbzQnYHWhlM{|Vt5f(hW6EwIE<4Lv&!(uE4dkL~MjT?y@LZUd@m3&nY-}*W5 z8gKrXk7I{p6jhOK@IKnm75Iyp*xMP@kvOPEL}UJB@oXYmgnr{L>T@e}!-kpbX)6we zJN1)@M?&Y61)maI`o*VXctrstVI9nIymZyrL4lzI3?kwMWXlP)lPSOBCJ~4nw7899 z2~Hs5Xp%CXkf|=0OgKw27kx{C;<|}LJYv}T=YS5Aaz1WDw{)WsdL(YLatJnnI29N2 z37^V*Md_?j09kTt+M-XaE3SB@Cd$d2N;bg*#1no)0)K)4<#~naWUAsbPomgud?zf0 z(KsAHz&uMRiei`!I8Zk25rtzpFVM3Y9V0EGQc0|c#3)2EXc$b*xswhNm@rAE;T>2E z9%qPW_yv2Y!8XMHGa~DRE@+SDH3I;Q1RRbsy1Xx16ZWHjS1% zaGLI8Q*bg2-~${{OArD=kQGu%7OF5nC}(5sZHsSQh&*nWshMUJCy0bY7mfv1F|kHa zJR2k>H_;1@)yqUKbQri$O-~7vpbEEHULb*?N=iU2j1oG;O}qdplY_I@bv-5Z22K#=l?J0d(ug?xL*x*NGH8BDDjdd7l_nR9)zAzEYXllTD~#g-BMs3T zcg_+enoE?B+ZvP_xJ0zosX=O?4+BZirZdTSv?f8IVZA3xgocwn#Si5)`XeW>%dM4- zOCb z%0!3|$qN#(a2l`XSNsr$_!aJh1)jtvxxxT|@nwBbZSX(}D(0m{)Gup6Tg=A$C`uWI zqV|BByqRTEE!p*wacZmUrmj;SGRoE4MWL`tid;RxQMd(DC+ut8K3x47eON@qWH4|+ zf>7xUO_*+gisS6XjcM2TIJ)TM@eaZkCy#IGGwVg-JYDM(A?PdmV@u^i@L8APF)1umLKumSfhD6NxYEpT z&}p5UgA#i9=#5hsz-m7g_%K&8KEV_$8I{2pGa=rXO$JEBKnunbZU)EGsOsIV`{~{^ALI!~7#e!n?w_5_Gf#4gT~J58}DV1O>r@K+7YfTJVTx z#WO*_GKI@QQ@mbm&5A37haWRUIAZo>I*Er2W#Hh#fT#&D$j%!RO7_=8QWy~apam?j#|3Rh2ZScNMn#m*Qrt5+zSUG-M1lc!u05T3w6@vJ<=@6akl0!StU z(8{OK3$-ByLL)mSNVsKuAWAp{GeePD@iVcEuOn=84F)Bo2xWL#XwY)f9dvXVSR>b%*+7cbma_&Vu_bo6g*RSQaR|+mO(KD z&n7I{5_1w$;Y{>uh%`y*A8!<~5CF6U>z#}a3bU^P&&$q zLfOe#=zuPg=+Q4=7@f8p*GgsZJdB2d%-0A^LeKy*udf+PW=Fr6niMSy6MDyR)9F)Juj zA>sKK#Zm(3CVb=^kr9^`fP-n5R-?00TL&QB6}8L{(+*Y--y)vn+8wC{I|%* zAjS*COqdKFAcZyKWJ<0rNdzr!kxYP1xDZXyz1A2qb6l{JvpD0QY58AiKBTJ=UB)O- z1BW+Za+YDbwS(GTM!je`~zKwxH+{aEqvC-5nWuVWTgl;7^hMR>z^r3se7q3EvoX_BCp5_v;Wmixm zD!~hVLZdob6s}<&l_uU1%=*oT6amQDCx)jV+H}-Biq5m1$%0vv$n1qyX}gIKKN8Q$ z$H06e;Ui=QVM)MI5=9$vOwRPYOFEb|MfW^92?{LAnJJ&Sr5w;4YJ=#~!hWlOFB6DG z#%`vNRl{Ukx@G`~49_OJAr`X{J^ajp>dJnef6X_*ED0_)=;13J(Mm=^u^It5eiwb< z5gf%Ebsf_LxO9r#X(0KLv2zKfVlrH$CgCs>8-%N9O?lcj(4dTq_>{#a^6DAJ%mcJa z8kl8pM-3lDH~<>&3m~}f-vd0l(r}%}(X^4#H>?-~YM?(vqYMv{AaR2S)57L*7Y<7OS7S(sLa<#S*Y zlHe@Nqy=SU{eAuoG6tK}mhqTHluf{-3Pv^r=!fvW1{f#+SKO0|%;G>;X&_5p1|rxN z5aex{6(#kg9zodAVe5~w0+%TbB9R_Mr-l)XQ;oF915y6Q1!+6nvSd$r%m>YNjnx>d5fiANha~D`=A^?$MDhohN$QQsUOEltmf^?!= zP>*wQyJPsCW>cQa{ZXzKL=bN}st5;v43s^OVSv9Mk7J2F5s)0uXl7GcBI$`oAdy9bL?9Fl9!a94 zXBdmg(FE>^k|Y$I15Ylp-!s58r1xwJ0=X#on-ipJ9t9F(QW6Lv7RhMGs+&E1AM2 z0VJgGmz)Ei#>QDifH9UZK)Eif_lQruR7IHrX@Pm`FC-C0%mTn0KIK33?F??Afp`ud z$>Oi5u7uEspV&;cz|g!VJO%1(Ok;osl)ekBafZy`IRzN&HQ@&IK*mv6;YyG}PP|Lv z3JzQU)9wW_<%PwNa^g(_3J^#R=~VM*GD{1Yaf&{nP=hx}T7z<+5gGX)7$-Z+ycTFF zmDf8CVw8c9fjNVegts&%KN1tOu>cJwz(OeJ;qb`=*oQW_myT&S$W2h?oS_{?<|AP; zR#gMKVVw<$4Jro4a?z(V$O+Z(J$Pp!t$`EU(tfy?>h+?wVN#&vG${oeQ?R^UY8e*i zReZ@2@se3G10o@s$x;?IgDJ(q0!HV-xSB?&mm8qsn1`(XP$7dlPgnjSEF;Y5)XOp{ zgTnfM@ZRilPI)uVq$yxU8!YBMn2{?PIWzz_E7DRtG4osb2`%DNe#aGpOWw_fEDbrd zj0>HQ<>?pYKuk0o4rL$q)+QT^rC1mxaUNvF-rK*J#B97#WI+KuO{h?TYY6O#wwl9) zoW!RLl(&bjhzqa;MnR0NoG!HkVoAHzH89Yk2=5e`NESEH3;0otG%v&Cq$Jj36qXP} za_Zsr$)d^5Jd(@M9S1;_Q3o#Pcwuc2X9{PzhFWpp9-8PO;VT5qSjI$J+(RA?MB)%N zcM15@s_|rYqg;2U;rg1P;ah>7GQBcLGHdab==?G>YJ#IY0>S`qXROpDaLyYp#$gT~ zs70I_kr*3^ZH`if5oKJ$0X}1#3_R^i z(7EH>NP$BuWU+&>>lrgF!>pMxQP*vy?h1jbsN| zn}JDn*Mu!LiKhZNxba_@$oxls#D8%-3vfr)%3R$i&KH*vhrn78W)tqJG;7j7*2*MP z586TlVuU}!8USSYoEEq@SA{)oC@$F`4dISP1T|X1j843N(KL+ ziOkc1o%3K4O2GsYD=d=bgDiq2p@J9K;*vp6a-=Zp@P2JJ$pW%Ui2<=E&4Z^n!a7+F zlc9B{05ulXtd5mN)I?#^Ac5B=g-Tkb7o>_}GrjN~Bzd&@G6>i(p&%P{_)PF*Df(n# zPL$w4mXyN}R6;gVsRJ*<264VhPtxY1MNu=;me2-F*()6lu)PL zj9y3+B_T8sRW9uxuR+P(b$k9ID`HwQm{6uREk(2HK&%MO*W_dTl8o#|H_r0_67eK? zrf^0RWYxMjEE`ZuJD`Mh|9{Vim0#gQY~~;PvO<`PJ~@}H2}@=@P8enhQIZW{CHQ4$ zKA<($WM$Z61(=0qmPGIzSyGV6h&s^3l9*X-{l<>w0(=pY;3A~2@9;B5#h(~d&*}vu zB;}0>9$I(C_oO3wtNbivpc8PH&8kLIhW*gSrwp9Wzd^ z{rY~UWokz|CO?4_9Rdo91TP~H$fQ2q{5~;cAVgzE%6@3o2x$-L7|=VNg!D=5lMbpI|XjCW^EF!A8w}&s6er( zuB3gLJIHjooa0%j2r9BXL{{j9L0S*?-J(Dm-4(3U>prov8Ih4A6q+7k#6 zcOooXLQsN{lq4vGDSm{m69##WBfJjQ(t=b)Dis&ymqrWYA(CRQ(p`L1YbmdF!HkE( zy7ow#K&D3+9?sAO%}TLCQ*57LE{sZ-kd#&v8tJ|g=%#ggLnN8;Afdgr&a>c{B>G`B zVSYH1MG3M+UQCe5iCz$)5GO6n0)zyCcSP$xWd;eeT8@Xt7xjoagn9C_pd$%{_yt`; z5uTIc@NC$KcF4vz+1h(uP(5p`VJ#Mh2+V?d`HWeq_E=QPnWJo;M5}OimXz^rBTIHh zzRcrD%c8*;$+W?n*%epC3ae-xKGdUK)?@>=<H2F>zqIFHt=m_8g zR0gksTm~yG7?x7{q(Xp4G%`x<0%A8mm{X0ToI*Cnb=1}tMTxJJrw8A;tCo$1^aw^o zMqX+nX08<}5JMa?At~!P77d~)01-6wW2d3_gaG4-bchesI36jvCZ|DvnKW_|*|5=u z)@2N2p%cz7Un~gjTT93XoU;zcAwq;9Hh>QyawinEy{(iQd#Vl|yhRnhm@^O>1|TLC zGPj{Cors(B{J~j+6pg?IV3-3ud{#4RW@O%#Wle^lHim{)d_rx^1HoWNKCq-J-z{p? z-UV8d>uVsWNq2_D`ou8^I)(=bGLhF9OTYtHF5i$Ob|=*59%S z)*!E=tH?)In9bU2=79_m)gxoH%?$j}7+?olU{sDADx~-M4J7cn>c(_gp!33exmJ&&d zJG^){o=I8;f(cl31g^k{wctz;Cm<|oEeQbz#cl{$Pdr?Edhm(vF<^KN>H>8<5S_y; z#OgEioQwdi<<6U>%a2Ne|`6xfA4?%@3)>`fAWuiZMfyw+S=iR$A+~- z_2I}7U+?g8;Lx!HYj@o7F-N!l&qK$CjjbP5u{#b8!^ZWW`F{OR+w0FiHXOX8{`=Ul zwsHM0?l`o5;OL=S*Q@fz^-mrCkJfM9*t)*)!~cEb6B}FKJG}9u|MKXeqlXSx#Ut1M z<>6xotA``k|5a=Kd#$y9(_a5=NpRqf+lO1KloEe+Lg##t=C)Pi9_%L}& z#QTZ={@NX_`+E-?%e_b5KXmJG;6Qz<{~g-;qm8W}tgj!e5o}yLG-#uZ;S)#Rznh{q z-ak_E9XPmN@~wZ&z-!b$*!cV%2E6h9QKP#5{`K2?pNBWT|LMb}*Nv^8ZkMa-_kV2t zz#SV~|LB&XEOFcV!Humy-q`w+jjf-x9`%fRv*^~8FBYdkB`gwKxiS=8m`1K?I?SH+o^+6f?$ouD& ztT&c_|4iwc0*6v~DXYD%)9-;_89p%_YDQ=H!*{NiVyX6@Z~W+=Z2a(l{@5qihAMXG z<7*o~{PV->w|xBL8(V*IxPj(Jchs;yzJBar8RPKbPp*ILj*UO~ga)=vVXhmArlR(0YyQdZfJERB)}GxnBA>uy(kO^vL^POM!3X?b~^K?-R!k zt=++XN8bPaL#@{P-^-~F8mjKkmmlQqM|t~c-u{udjraetUOw@O9BT$S^8TOX+t144 zYX{rs>;E#P|1@WQesCDpnD{`ca&6;m!`;tfx$8dzw)g+6{x`IrT=4qi|KqmxTaIr0 z_-g`2Hu#avLA~KN}zX zUmM^5X9eAj|Lj8jUpD^fzu5TkuWtPCFUwvV-@nw%{?mVP_+!mc4(s!We|^jPf&0r4 z2MRDH{K1Xu1^i!KuYP`Y(0`ajt&xb@Qpm@jPnvu;b8$bEw)+&}Q ztds?}K6q&T&MIHue!{Chao~WrTjF&8AA4^fT<4h`_FY_D;9dYE1iYfOkOXM4Ywj+0 z&0TAEr8U>4W7wu0I>W@UO*^!QuH{&paBJD2y>!Y=lo}VnUIGwFd=lg;biz&Ogie%% zZPl7;<(6uhj?z&(%#7M$JDirDk*93SPShFBRB703f4_6y7XbCO>%Wpt1YVx^`F_rG z&Uwyrp7VY@RR|p^ho+WCX@}+Rdz-_>1hc7>L1=phoTJG*jOhj;gf)Lbp_|cy#cknJ zaBz~PF$)y!Ik-(5u`GZHvrK+1-DHwNIraD0;8?eG$ohkzvvMiBV+0|Lcd9^4Bgm1+ z0o5Q=XPA$OvJG}IL%Y!(^|4lctgk-SUmqJ#xVr_CbF9A?02<63R7*hW5blSIkf+S) z9cuQCIUCCVV?Ag9V|lSRRRilMGCDH}x4K)*zk0XRzZ(e|?Cx&Wx<^@T2kWr{e}Z7( z_=y(ka*%a(kP2y(K#3sYQza3g3R7aci`3K~6G4Ffn8mq;`7mvOC4@i--)GfKgRL-C z0)_6hAi5b8xYI>XH!~ZB5VIYm$-xTx7Z9qypK&yp@ldAT+uPS{8s;yE*lMDet5`is zLgwwwfk=!Dn(pdvX?gf38~VqaJ2D2b{1-n{A_mMJ`!Pls;wOfu|Ie*0wptH1hbVOa zUn0UHOWr&FhMU{Qdihr+_kv)bDVflV4(sW`gd@eIUJ$O^+#nIT1lBbS9D*&V#{je! z2P0IM9x#uxO_U{;+tJhNO*Zcd2+dt$Cn0h%@2Z$-#rd~CzjrQnn{y+oO!{b{Ez4WHf3icN^s>&;?)#0pE-T+b+Xc7L(Xl7Jz5w`jjXpsH`!GHsR? zG{nnPEY3VpoO!Z1{iM=8HUDr^13-;{8flSSOuto3-%=^Jw-;x|tr01oL)EQbC|1uG z%g=zJtLN&A5g7kAU-)Rf3gkAR9L zQNHI;oGcs{sSYb6bprYZ@dkG_w_A9+QA}?urgxc7l)^I(#uhAV5G}T|EHuGvBuSD@ zx5ga@8dI5}12pkO+AnHQglB@9AyE?SRU2zRBsW{5(u7{4Vvfr74)(inpZN%zm!)g1 zV)ZQ-ku}4sritNi&l}Tu8ykR>4}g}8X>< zEQ_sdKnr6Mb3|KazF0nD$FLE$g5|;vc0*xgc1D!-f3ZOen9M9dis#0)950F5Q-K=8rP1+f!jY0iJ_5>ycJ&n<8jc7e7zO|xvK zDyMEAbeXPWb@U8keYgmaHYi~IfkjYzxmdX@*N59b7j3~C7kg4hW1>Du&qnxPCk0(^ zF$d!)sjk?cNxO#BGgYcKQYe0A7KMzKf?TGnqf^8zrm&XL?{DrXR<1(sV&y1ognjiC z8Rj!(+7B7yB92b=%;Z~#)SEzpv3e2ml^1PcdXEtLSjEVhs5~{Fl_K#YD1r&AP;%)G z?vAzClFa}4CK73Tkppj1hUkE( zrvMvNwjwuZGk@82U>?&8YQ-3*&U~?1Y_O2T_uwKWD@Ia4UZ&yXybb?BkP+!g;YB)7 z5HX`&3wJTvr|2UWdSp=P4=W+*yd@YK|6DOWq>xUX8Ey8zkNlAwTIQg`LNGe)91X74 zirih|wsRot8V1AEsSyeF`qL<^Y+5X*4-UGOlKyKrBy5Ty+4S13gUyc>YoizhVQj1{ zv3g>5h5{uqEQRGYwD9WN)?Y)+&j3kaz1&W#gT+~9`thxehlLx|QLUdDjD`33psQkN zQWJ}|C~8|#*X=GBME$sUt~{&U@}t9SoSAeccKOo>DKq$7^9fX&NWBHnkQsN;Z(ppE z*0wpO(%i&|%g-Z$fJy5J(FTG3)IGku6|B_9T z(>WY$KaMvb(0{9#X`qk>huMkXqm0r_Z&IB7YrCw&09P=;bYIyDJBl_dGppwQ!7zPX7e%vl7!#?cn6?dpz!)LEDO-*Ee9AWP@-2Q6aIk12Mwhab! z_cBS?^)dQLiXy)rMS@!J#b(;St{ZL!H!wE9XuUUX6BILg6V1f!ju*R|jnQb6?!3C( z<1Gg=mNeWuFMX;xL=-83i6L|d#YjY7Dmoj#BQ3lFZhT`1#lp!SX3WV%NQ}6^M17KL z`xP~Y0c%%w>OKZ#Jd$`Pfe2j6(DJ!oYksN{0;cyJi>>I9*!<1kD^`X;lr-{T;k(;r z>djm)o5zX9C@4=qGuK(?>2mp8j7-^Fp3_sYfhgOa{vlidx@wF0wJ?F^*RZZ)W4(O< zqHf>5T{DP5t{h-0_dq#lbR;-<4Kt9nv2$qn?6snEA+#Otu$ld>*cSH1QiL@VhWzwh zltEaEXbmh7I-Qy==|9u@J{D`Hf;!sXFSOe+Y2~UYmEJPExh!-3O;gsoTdGFCpgJFp^)^Csy+@j@{-U6J?^*)qJ zcdM`O!6tl6R}E#NSsW&mr!==c^rRU~w@;uS>M11Cj846S~CGH}SlNsa9hg+?BpBPa%5lb|piXNA~awH_<)&jKwc8yXH zS6TSE`UVErA`G|sS_Ax%O@e`kaeHNC<3jELbRD1sj10Pfhb#|-`ynF~AFTXoNkKIY zeFuYdw&?;Yu?weEA8yJjArWi2JeDJb$M6&8z@?9ar4G@gF6NX0hGFk6yO2zchaLj< zr^~PJ@#eO~&g}AkCe)3&-UaeymPK+vhFo-J@Y0Mm9{_1oNT0>{<6cd$T7gvS z;Mc7Fh*iCkG}3(d9mUHUgUxMK4LjICn&4y2N8VkhtoA^2`$jeEZDL9Cf^%)An1F0&yZ6ZM+^HKFA@ zOUOr+s>@NICa&8={AW!f*Zcw8ti|jT{5CQ-3XSzV-t5Qs_rBTa5DN3NEibkaK;N$W zN_K-|1#QDa1u^iMEcM0hC>@cgP2JHZ*4##oE%Iw8qv}1b{HpGlaz$b;C1ww}Le5;r z6x&|R?u_{nksc`ZisdNK2Q1nv+Bya;UmqU5!}?j4D*C)KX8)&w~rNjYAh&rr-R*ZG5v{N!zt zmonF*(d&Ai$O$87tY@dPS<>x3>TOM(T5NuScFTUY=4=hy>3uD|U-Z6mip2ZPd!QuPvF5b~nM)gpBH$|Uhqx^2fzXM@IdM~HSDQVTm zLXbW04kX_bsdp!S9%JoYTL6=cb0iK1{*0Q8t;qE zM!|On{)AQko#yy^Fyh_KzGupJ#_!AQg5S;O_iXGPiMpO;(fxGILKsV^aR2&Q>0@p0 z=NX%At$vcd-KnJ(#w?bVW@@Ydlg&p}*D4X~VfFR%^B!N+Ma4dnggPw%f^SA%_=K%ahF#K_Wg0JYm3HztD0EWHAu>KO*R6P#x6Xk%*+FQ$QDOq(hSIpk`tBSvO zH)T<~`B++JRqd*tU6iFp;Np7>aICND`*<;X->)kD-hG!vw+^vNy04dwMt#EYzMQSG zib~ap?kl)4gbLGh#q52*s-^eFv@E)nN>$QEHyaIZvGv*S;Tg)dtDY|vv-kZzxaYFy zRvJ}F8$H{?Mx&262dfs}>sn!PrI@|%SCxJ5@s~xnj=xIUz+$68wj7&(_1=E=W5nIs zUEhOZT~_x-9O?~P$ubSs^zq>ZL+VWZKin;XUKeZLRxrYyRZ z5LMDfHyaJ&8`va78LGOyUCiG1``~WNqFYB-C2e$@qbrkfm{cu#t$KaCn7!{;m3eQP zmPNPrS|z=s*D|^6<{Pcz{@(|7Qx)CP%|_Dw-Bih#fMWysESC>$~d9;2*8v`qM9 z^JB?~GWmTjs?KP7s5y9l5mks?YB>Od)s_E-#AalQ&oC>-0A)16@GS8kHyTKSES0pV z9(czTtz`JE;v4tJj8xm>?;?&E8F^j~;ZL$&m5DS{O4L)WOAQq_7vYCJrX=`-TVIgKiQJn&nUFS{b zhHodN9oM4I>pQT$qx0;9 z(5JVX@aEv7?Dbw+=g3$O3s^Bn!KfJdb2}aGiMuRvUijf$!_^ut^wJ&a1Cu?i@kTso zU5{r?z?O%r>CQU`Q)j}{<}-Y_XnE{<>4t+{-Rs?UZ;9t~F+4@B{Y;9#sh7EBaDyec zXVlwYXK!UoP@OhyshHn`ohqS=g`@n$Iz!&u&-fTmpqfat{tF5n*PqW#z`ZgM>=v$Y z$Hk|qTE&UIT*x{S1&`P-1v*E_mqK$p$9lY3Ki4SE-Y!m@Ean@<+-Py)d@*;K8ygeN zN6b30MC)hYDo(KL!OdIDT?x!i+}&~^`GiBAABske9vkc727^O`%E<$s>6GS)_lesg zMJqhD5^n?XeyoEwAWczE6ok zyg8aZ7DV+Pug}dzvL(P)x`t~l7#3ubbL>nR&pB?BA?D!uGUJvRAj10wHoJB#W`okN z9FY%FF2g+#hDR0^v#D2uNLE!uO00s6mwVoZB7Gj)^-k$_+YU@P_fbG4ChTQ z?i!UDrh5m5OdDs;Y17tIydP=y1tI2+Bgyv(?4xwPMF!s~hUZ4ycuM%<9Jh??`gTto zbvC7Rs|8`;+^%8{ERS)cQFZN088t@6Gq?)QTq5!o1T)&1g-}jVehMR;;^K=)-YW_D z#8Il!i02x30VZ!k@)DO`2%c21JdL%!)O@(y#)DsMfy1QmezDSqyt{8&#s1B(*ePW>v3%FQs3r6PLyY~IVtV1_HoDt=I0G`+du=mXaZYiKG$ zka!jiN!H1GfX;I~hzJUdOCh3WNJY%k6GKY;ebY`aL(aKW!RJO06%!oGd<*r&BiRv3 ztwIuGGpFutYZ$UsRJ?7zLG3vxp|qgEC1z*uqs*9hOah-)@aZD&tiesj15uZ5#N=f# z-Zw0j``peTk92n>n&Zv}W2QN&(`05k+DH)dN^)7Y6~WA~^D-|U^h`o`Mo{MSR+?It z9pNR4c3pQ+1eweYJvgeg(h1|HLIy&WPv3OtJTOs8=%!ubI^?qqn<<88Yryf!4ytmm zhr{P`MBOSNa0>Vs24u+oJKCA8nfoYSuZ@|p(Th#BM#5_pb7Wyx*-6JYm&}Nl&LS_o zOwl!?3ZjMK_FD)MmIkF*DO&wP`1ldMU7+yk`y2+*nV)(x7?PBg*B!;FEcb{ zg=M{RGbzFmH-VL@3KOIJ23Dcl7hYtww4NS44LF1B08Fxc{8N;o&#_=8grBOz1C z7Bzl@jxF#6`xf`pOL~V%M}g{}?&Cf-lUlW9)84bTE_aK$!xW^LzvIlBkEMP7ZiL?s zcu%+xmgd5?2!~o*NVOL4M=XpE40X}sn+hp8a;FeU!CC#z5?fHqF*+HMytk?r@RrvOEf;A zz>6nHq?-~(Z`;whd$$l^5+Jzs5jHNUfMEKt0SKdbF z#=w9#`18I2w<*JQ?Isbw)L38lm@VL?B8=x67{MyLT&9i&iO;z(lqLkWpIw3E*4yqz z5(W3Efws>lG<)26f;OV@5_SZ&@}GP^Ns~E15>0rvSU9q+3En~CKB_r7QRVeq9)C+4W?`SN7)&(0nz;H8wcQLi&J{0J}n#4nkl!HKv*E+g|KNr8=*M z)(EEFYO%yTP_ZjW9Yy3Tb6OTj{mx;gNz%N;hPON|Q^qzhe$N2&OG8jqNOLh`dI~Lx z%ERCTJWfu8iw7I=^w2NmLM{dI7tu0ERYZX5<)0IoHkjrnfOyq3!tPxqc*|31{!xaK zPSu`>;@YwEYS5WF#=~jDQG5&F>AiB(5kx_DQ6MI=5TmU&|2dI{=#clZgG3d`F2N|Z z(m<;}pX7AR?DW%Zg|$R}(w*-C_@t*xPbTDJZ#}G&52YFg37HS}F@TR$4^Bm@oXc^2 zJiReQ zx7n<+Up{TNMNE+e@k^Z*gFdgP;ekLhc^JW9E+ZVlDRw`5lcd$lt*L1*Fe;sd8ze~; zeZe&D+=lDKNA=t|BsHY3Bu8=rr63WBEH{Qri zr}ptQUrfE2jb+9CG*;=Ps?KOkp_E6L)t=x+)DPH8drAbmXX%9U(b;sazZmV81r%kZ(D!bTI+|Z_TEhvXhNI%ol3GCDxP2a4rDn!N`W8+ zSWYIfeFp2DZ>Xm)IN;Mu3eMU{aXL0Lr_Y(VbOXr%A15o5*Ovo=I2T-ITt?O} z*5exnR2=k!H^VcZ9iI98@XS*dFHY~rtsV{+`d;VNg*8Sub%sYsi_-_?kU!p(h?gbg z)@^b_-I^U4>z55oA%wyoM3AzYZx=*?DfY z*7p}nx8NWzI|Rob=N6lj*%xkj{{Ku>>=kTpDmvnP{}CCXi_Q_sXuzF5OnofGSM?E| zetI)*jdSd-?3!DD5W#(P?;79 zek}#8*rYR=_es$9r>pm-^#l1D6K|05k|z}A;{d#?8_APpGH*-yW+L(vQ<1Ln^D?X9 zcZ1sSa2*#bAV~a6Vi0LnvQ{k9;U<0oUwQSvCD)IDKN%fO1z8xnwLZ7 zMH5%Ms>qzPu8Nhz#BNVqdX-LCvs!A#}(yyxhxfqa03F z>rCLfWRiv9k>6=L{i0)Ex~m>R^h-wH05@zi>O1(u8^cE0v&~H2LMK}}{hIx&o7Gsh zs9;@p%(fd}0XlsnXniFxy^@%^1>`dg*|%hRMKRvfd`KY9ou*-9x-%= z@@_}rJSRc^7mEnq|VkoQT47mwt7kmv){k1sxugtK=M zPY^3MJE=v!=)v1*i&_)Ii&s)E7kT+7JhY2gN!`PX&lZb^_^BFEIE!IrkSY}yaoq1< zq>JaoYY{cr0{VZWT2{Ufvh~Db=O%~kaU&)wQq|cTJ&@WU!o_*9 z|K>;rNJE{mP{IG_&xLB6Px6!>e_*&v#Uk&F5#Tmdcd__PvG_)@c+Kz=Ajgw1*l$^v*j)^7#s5SK8-; zAaPqDi10nk$BIC*A1hL$QEhewnvsy7qgbrTlf`2tV;M&LVyTb_d;EcT3=!|FDC>QR z!7B?oUPNzc0;?U7Yoz_6Z&7 zc!M9HGC$TFV%Uxd35bmkbKAcd(tt1B^K|BcZ*Mi3*H1A zrOiz~jmjwA8%a`$PP94~eRy9cJ*xES%&Ie?;GxrM@t9jzre}&1i@fmP72S=AS6`L!QyiUPo_nafPA?-v{v1 zWNNqJ_O~b#Rer>;%uf9a)a=ZUlN@~<3&^89+$v#8NY&-33jL|boDI0dvNG7>qc|A8 zI_E-zt~3Ubd!&3#vx5>~$}>#e-r{a|>F@>%P&4AAOJh+Ii#mSM?#0q}SiT^uN;X8! zwScc<^asZHQ!C^nL(8C8ej_lxQDGbw;&Oa*aQSWcdmT}9-$z7kwtR1E+C7#5*CkkQ zTRi)d(dE|+@Lv7Y{7BvcUfEOdTfQVo4Xy04(Uo2CiuTGA1bHqZ9z2N+R5`IIdI+_$ zvS0U%U)>}~+I}%n3b{R8K3!mhu!V2LJXtx1_E2=JbWU0$BDFN6R<6;=wIDk!@G+ja zZLgDdEN%**UT}pu96VNjzsP1UZQ^jrO5k9N`9u4Hic?sOU=%a*W-v&^*{LORG%IY?H zqZhtIiO5saPYb;k^JR4;b9RJS`^BsuNwHn7mFo*j%*sjiPgMW6g~f5~lMADdiW8{}>e_Z^f$KM(*}0mvas z{Ee_{_{d~;+&_8>Qu7NxE*p5h@ECVeQN8He)sv1@gcKnMrCceI7(aJfpXFO0F^*7& z(%x^N_@BiE%K+@T*9$ySEI%9H8KJLS1VV$Bny}p!;{k{A+AXhauH5OQj$3`PB#~M# z+1!3gB@AAx5*{eml{c9zfy0deRIl^#yPNcmpsl_Y`rtJeAuG$*;C!U7g=doVymh!X z;rwO9sd2ADgOV6XDx3bXV(rb4h!|3c-P-GlU9o%-HK4q{aJWR1BJrzu&791oP+V;} zrS@dXH9U>lPm2JuQgxeRx_n!d&-k_AvEt0Gtg-@kBR*bUcjlGHLNonnd1dj5a)+Ngp`J)`n3nyMayLK z!4)kOJb|*Bj9~P7ET`*B5*7qDNKi&L*@>{Mhfo#kZZxSk<`}5nvZX$ znL}Fsm4WzbYpeS)zf=)_Je2j0`!HW@erij@WwG*e7QJG^UvK$mr`;$NZfI5oxsDYt z95wn#eyTNk2&J=8#s>|=$opSh0y%E{Ah~BR%J`Xms9^{*+-74Qmd8(qT{55F%$euT zCx!kjhH96Dk%uvBUoOr)iTdbEFJCeJKkk#-kG-xpBk=O&6QV~ z!s;gczE}0^b&lw>#SIkD=L^i-T4GDK9wYjje$(3`YW(mX$M>%gX(z+Ex6?Uy`Zm-I z=YT|BeC6YA4tKg0_8~EM!?CMj3qYaVFtQF{QnPc^EQot3i$|q3`fcM$s~+yjmA470 znYT_>p7aZ!)oqlGWJtF1Wa|(9>fxV%Tn*!YCJI9Ux>)U|K4*~kj_%?ry2cn3861B#^GVT8r z+!Opu!T%%pjJdxlc!8!H!-fh$olY9Pg2#RrnXXA0f4_V^^vf1E2g#}GPzhFSH zO|Tu%|M}YIIH2Nl997ZqR&C>H2KMl0Yt=uG9QxC>pRE148vC~@@v|iUIS$1brw*fU z?4Y5+$=a{gezo>B&Z_uX&aAjE`i0uID%Vq527jt{wDy(SZ&2`SwP$OOiozH9@0l7I z`T|!^b7?^ehzJ{gj=zn7k*O&UHTl>1Pj*i=PW>zd_^0rvvHB?BpUL=dlM|?an@}0} zb{Aha&c94S5+Yz~S>|_YEo#PJrfjzJ?PjL@&PL&HKVV9wCL)cYU_&b6wxp^+ea_yS~u%#jd?wKhgE4 zyC%DSvFn+x-{|_wT}Qiqv+K9IzSi~g+-a>h20^HS6xSLL3qB$k5j-Mb(6z>6g6|V> zP))7D;YPIv2lvz(9Gz2Za3WN#!I4U}#+L*fOjK)d>|CwES!T7y4+wrl@S}ns6YLZG zxPVhXJHv}EQ3BjCTUhtm^{xiWVfZC;J+07SAuJTeP1kVdj3Qh@53l;@Sf@Q&qU{$atI3wt~3aAUZ1>(`@5%db0f|j69&@UJe zY!hr33<@3)3<-t>h2TNK4#7i$hXo%Ij0heP?5+VmEBKt?QPUn1j0(mCj|)C37#Dm@ z@NvNp)&P42KP325f*%(ACBYHFR|WsB;MWAt3VvO1UhunuzbW`zf(wFg3I4X=?+7jm zeoyds1%FTQJy!uc1)mUnQt&CkF2Sb-pAmeoV7K72g3k%QPw<4`^MWr3F4X|PFZla{ ze;{~K@DBz5NbrvZmj(ZU;6D`nM}n6G|FPgd5&VJRir{6z7q0@I6g(yPlHmIVdjww= z{D9yG1>dd#e#_9;1jhuwE%+;fe@}2+@O8mo75p{9bArDv`1b|BBbXAj1=E5V!K~mL zf{x&XU`{YESP(ofI4L+KI4xKdED4qcD}q(Qn&6CJU2s-#PVh~^3xdBPI4}5J!QT}8 zEx`rBw*-G%@OK0k1-~cwyMn(bxFq;}!QU7B1Hp@ee<=7zf`2Ti{XF2(>ww=E{C&Yc z5WFb(hk}13s6pdKm!K}_7D)F-k6=hJEGPsI3U&w{0(5nE^>p=iO?3T8*N=96q-&&6 zn?S){eWR=9Uu)|3yEA|~<&Tt!h}UY5|0*ljS7Q}BRvWMVMZ&+q|F6}KkoHXN@vm|D zt=eA+`0ngSf4%lAls#5vTliNg{uNRl{WUIMr^&C>jR|!2^8{I7UtKe?ZvsAF~S8AX9@!Fx< z!P?Kz(T{QcDMFuRaM|)N)B0}_KFZXEC|v_x+q$-Q4R$@y7&ZE(uEX_O4Qr*&BVgS% zPH5qT9FJ@9K!IAFGZ6auOkb_Ojjtr~6}DRafm(g2)_DE%aOU+d2)-zIQt*`EOM>qg z>=Arf05`f`;bFx5Yt^QzKpAS(hA8H*(O@m}KZo_yYJb$qg0(;DI??r5qXw57A0?@= zAJBEL@r>aq{u;l-U*jr&jUVQ(@$%Qd|8F+NzyF{A*NsVvv_Agmz2EU3%kO!dsMFJVA=E~Mv~dE}|5zj?md7pdprpq@tdBjkG;S)NYj*=2do zpogj2ho`G~vRt0B=Bask=A!3CiixK;dW@i_vn5V>tfptDWyHV-%S zbTv~w9n)4kVNGgy`lEC*JkG9RnyOAp&71i&F%ZtbMz!lq)d z9ciwRn(kBQoz)Ht$iExD!YR60^tq^6X}bHC-U zbHNm4SQ!@!`R$05D-WAYnNTT6J^852WS?@^K5R(}ri4prxnj>sx7xw8UDXPXvebDDSqae3ll?B$0Y?# zS8mjJ_NsK`@w4{i{7yWTb?c?x%k@Huc6P~IYe{XFTs2>wBkbsMwNOXo0<0FZ2lbfq zO&yktw_KA-Vo@EgYO9XgKE7D%M9A3t{Zk%o#hH_JDr1gZdqFDZXk3vTllUm*xsK)vtV~-gYbd4mn5R^` zj%Rz)%-P{g5gX^dk}TI#=Nd~5lILqXGo-aNG{yO(u1M*14vM!jASYAbI7^jFN9f#3 zTID=;q}H{6>76F7I>(qeq#dB#+OE1=y-~@bx+L|Z^i5qT)stft#iVF*I1OO2%-Ji( zQrvMlc6Fz-GFKkc(#Vx}u2WJ<&dX7G;Y^U0N`}4Wij&88E0^Mu_JkbvpYzfqm|Zh+ zJ(P~QPPp=_VN92&$qhA?x?YNkt&%Lo<#eu9(p%isU~UeaQ}$P_HDV&RQbC@j1ttyc zTUk+_wMTM2vV?s)J8VVn*|zJyxVi3G(sVVY8bn>-43rvDSBlBQO&U33x$Nw)ZFP_{ zP?{-GQqZs*R)?rpsx`u19kF>iekId&PHC0?^37EsXO(o47CAE=zatVi>5#T$O0=3! z$&s$&pe(A_#lx$TELO^La?<&mT1uKYpVjWtz&R-It?Bxqw5P6>BhoHukh_}q)9jhf zR_LkP98G@kzYmvz50`-tmw^wL0af^i%fSCXmH`(o7YVm=F7BEM8pyd>&dr*Oq|25*F@jj1$Sa_i($Rki8D&Tcr1|MgQC2i-G{U8$ zZRFNgLr23$%DHIk{7CapZfKae(C21KoLr&ZHkcyrjysKBHx=oE)J!%vw@8}p^3)B) z2md?y9r86j?DNBo;D;MQXU~Tl!4EfrA8rKyP2UK9XaW4&ZUJZ)rHe3~Ug^fu($Gnw z#iTWpcV2bno32yCluXyA(-L$2*S(`dMhi!mi}sSv&U72OCTOdrgEQS6=}On3X2=@p z7}sLa>8e$fE^@6Qtx8?*I!3iN(o)iHOb3e?m}c3u(p-tOsnkW{Voj|!`JT7Av=&XR zY)x$@@z(A%RjV^CH*KqQ3~NDZ8SA9h*<>m0L)|s$lGBQF)M@?cYSTW`6_XBL$EH=S z(bqm}$S~uF0$Nn>0v=yW@8rI-Zm`M`e%B07sB}9bJt| zgmX`+caAC%;wr_{z4*a9fTbAc2bFPTAMOC8{fD=LKHLGM*8cETkQ(;GTS2a}t||Yf z-3t0}2k_wz;KLojhdY1|cL4u(?f|M4$=#j11$Qm(INk5Kqi{ds-p>aKa);t046bGF zmb{PPe#1vM+~MR-&pm~E(tL!_GVY&kSzYTM*WIRfg51S>2P60S_V13${g%5bcUaz6 z$lbPky0okFF&X_8w(8#69g;gD_ldUb7~Bc^(1<%89|y5t%jSI)`^tTH?r+_T=N`;) zrw_pWm-uk>#WA}*msdc2E zMf`-0*}cD5xZ9UHj@2Eql+Qi2_mJ}OD)-P*QOb$0<=jcydfxxZUAp_wX>ImeMby91ZL>30wVd7b-FN31-hq{uBF4pPe8!+Ouj z9_6Xnd)F@SyxBtT-dCTBty@PrNIM^bk|*w!(7z$E6DmC9m9n4rNOM$2gm0D+8J~k+Cl|VgAQdqtynYpW%I?9Ff$24iIM$>Yeg;x@h8toQ0c4_g*i7OnC`t?}PA{-DwlqEKUS;%c5UdF8U0W3g7wb=4>0mx#ph>gz0T#I;ia_lWwdy2`u_gqyVCpkyHTsM z9MuPAf1%cR4V0pchD=I^>F4EpX`O*yCRpQJdf z|C1Dl=YNvoPt`v8or}A+Kk?la%ZVoZF34xQ77clPKQUKkzz7 zI3<`6j0<>+5rG%f1ziF@6abhIj5jWHNc)+epzgQ7{l9#>@i7t`9~XQ=uv73U!S@P2 zDfl#izG^k6egp_lRe_1d|K3HR8PXd6o8Zp~{<+{WGrlSK^G1J=S27!%VzUddvb)|} z=ksc3*NAjxyL*ct=l@Ug|IhOOr}_VL{Qm|1{}TUyh5ujS|5y3{>-_&sDoy>7;E#!o z*xpNkrK`kt8@OxUtEL|UtnM{*w7Yk#PJsy{$4uTKm;@}n0cdX*Tr_PbV0jI&azJnx zMEw|Cv1)Wx{8yh4JV|qwZO;)H<^MR5nMnfs2uz)`j6-hs2>Jw{?coz(XU~DlpwS)m z-X6lehHC^`^*Ii_S-|RxhUNj&+XRww z+NP$Z4)3oL+6P#99dOnLrfqqeE{ka~oxf1;mFjEOTr*|H>T8yo*M=&rndF%swj+$yqutX64y5OeZO~D(2yMRuk-rErW&XA;Oko5ex>H7o{=%lSr zy=-&|FlV=O&(wPrzB$XEu$_5Ba~7R@8_>B2IKf#@9)?QrWHcv7ItKuAI|1|C04I(L z)=WEOC~_g_N85kB-W1Y HP)CzukP1k7C#+!RRsxi~VqAt6W)%nZ8E2~yKlu{i+}>7id9I2{H!O1Q zx*0bB3&#PcuAysAy-Db_R9N39*d>@1>=!uBQ=)oW1lyb=Qk;HPuotj?+0Y$Bc6s`k zp(6r|baogrZRsXpb;zjwuAc*(eoZh5=s4i@D@Jb^dc_a}E*3|?Z_#RthYU>!D987Y zO{*EQqs5j$>=*5AG03ra$Px3kTMC^QTok+*iFD2Pwk^6%upjVEk$s-hd`a6hhqtsx z@T7pg__lPtSEasqKp^Iehiz_Yr$Mn^yec>N4WF(5nvK5yv30?xM3>i9XXhg7A@T6(S3_UFnsTHd&g=SsZZ~R5T>WIy*L=SgN zJTJ&X_OSAb;0?hw!5SbiTe(GOb=0)$ro9Ge|B`8T)9x~KQegk9bAaVpL&pqBj@1i* zvrY{@TxdMHUm~>D64XpHV{MzDA&6YtT6FUyfvBxN4OqJ6K-RArpA$Hw^)~@$B6N$; z+1*Aha`p+qxL}`vo`zotFlFe5fTo9E0Jq^6mIUVnF9|?q_=WR=R|H@_`~m|Weu0jL zU$`j%gW(sh3XTb06I>9yF1ReXB{(j4QE**wM{q?j3-}u~LkwZ~g*5?)55I6%a8Ym% z#LtWib`m{nD`#!_>`MY$IV*l=uL`JJ%nSj}Z3CQGBJJEBLMu<0_6A`2Nx*5kEZUYk zy<2cVa74hcI3t8=-`quLnJ$av3r5cYUN|gxn)9Z*MvJfK<*&EK;~Teps-BZo_!=zV zisp08e(d#Ie6oZOkxqS!Z>-e^Ik>W$aj+gvA-H|KCAg^v&I)^CyR;lI@vPrm)!dx7pQ|vle#!K&fmnqNgE%7C%yZ9wFb0UT*E($BYXy^<+D1gEx*cXQ=L z7EeZrK@7{mmBSS)R)VN22SPGO10p~)3;8*CtIj;>8uVL>%?KoYc9xxaEjuGP74(>y zM8sO#k!>(D$bYaXW9){a^~oq3UoF1cXD-2Pd$MfXYuPrzso?gA!@qzs?Tdj= zEy}LejKtJ&93g#7nbk@77-uWea+@wLHVH6yCiwifp8=kz>P%gf)l;)fyg5QKQ`(P2 z+eaV+p)19dPedHB#d8Q|QFFSVa#qCXCD?nRl6 z^|t!>))W^QXF_YNm*W)qsBJTH87{NQ0|v(VM0MNmi;sJV$&S{*INu#MfjxnAdzE7+_*P>CI54mlT@Yi=a%jh^{?9}X z4SKce(q)bHj3fQP%`c&sx^_Rf^HFFoP}MI71Bs9{m}y^*v@a8Z3}v|kViGusiQJ*9 zCTT9r{o|WS3@Vcl;;C2t0hNv zcqO@tG-DY~L@$BpaQo*n;u4H1J0EB-&t*(g0u^;Vl>NrX;2A6*p665HtWE9LA<+OT zJOWHW7pyji1+H!a>gYT zX=@GI{Z3qg{@S^^-*Y{iX5nl>2m?D=W~Py?z5!@Gy(>!ZwILFx_a+f9;yMh>ruUX} ziUZ4G<&7|wki?~{vWM2nwt~p^JBj-d3W)vmHdw6D(Y`nUVhAROMQv+E)v8~pKekYl$Z!;0(DY~3RC9!cFEJR_zo+IZ0 z$`>ny&>I&)gg9uCGMeSLgGl>gS$8r9F-!KVt*L^Gq3$aNiF z8H69Z$Oz$uW%f)T1LM*pqY)0Iq7$n4ar}zOOLxk$VT^2sql9gMK+H%(>M|RS)pHFFsP^_g2ZBMRNrt6}_iRoP;>?YT zTGLLaO+_@lm&H7yqQC!D3U!qM<@yc-0!4 zA=+ic?OzHJH?*D3i`9FsBN&U)gQ-u#;Cn{bb`4=?3}(yR#Ii9G*lui13K@z$Cfs(- z<8m&0bQkAVXS0bAz~#dVpod1dpC%AGeB~bPm-`-aFwsCCgQGjzTH6Hz+h|Ml`Otp1 zvdsOPSZgpCjT|RJUv6Q?j7c6e;c>~s^`MDPJ?70LK{8v%pnag`iJmUAE!t)Fx|4Ks z8L?2#M`Fr6TVg!@tY2-7sot%A1vyZMV{5x;aP{5*>^I2PmlcO$F#CR~3T$^>9EIZa ztRipkA4S7@$+W((xH=<4gE8>-PjYX{-sRtZhGt`NWj^f>v<{aEJIe$QOx-rZOl%4p~CST}j4ib5KKKhrlIRL8xwt#l5=UOVE0=mQhdtPeAu za|p_E9CTyxNxWDQ;M|xs%Ew_c&9EmsN6OJ1f!365{|YV)vJ6akWo*9^b$1l)uVi4j z{Z#`TewZl>JIWM}Rh{})Rp!^D%!_5p!i&T0Nb%^^-!yCLTiidX0+-9c+LHt*nOPAq z%Ya#6?lve3lZbv`{bBQ=F&hgjr$d)x-;-nCU&XOtYv^q(J38mA=d#%GL9~X4>OgsR zU~aTJ$4i?I@dii-_Sf8gT4(&F?<%3;Q!B zQqX+~m{+|3H{l7@Iwdr}C{+A1hRc zQ&7*SR74HM_zA`|kg6zgc3nyBfxPsq1wSPe!GIpRF1>9=2DEVX#EtdYNQTBRQw_I_ zvOq>Mks+(J%*-rG7^D!W_GCb^L}+`hX{quT1qZ5v2C|?bE7()NZDb8;7iq4QCaPvQ zB^fy2sE{@5c^Pv&5vPy5k^_fCD{4b3su=Y`MxaPEkn&2p6&!Wc8$&qoVv&`<7z+*X zBLP{D5lXV&9Cf5h0moA|pTsdIDn#w5U{Y0qs|3w~le(>>T9lwP{xQyI;y(nAaBF}f zWr$>|R(MKBnoBy`F8>)((kMn0Nm0b1(=lP%&46fU$gGl88OSV>=&fo4(kgX zXz$4mX}y7leMo@pS3xcdh0C{5tMNz!fMN9}tHqmQ;Rq+Es{d%UY@Y&P7s*s96TuJcl&rW8fEP_D zZZ-&MqLIkGs0yOgd51IZv?j#L$yyiw9mDE7?k0qr95P={)o`A35O@FzZR-Ejde?~% zP8e$v(aS_*Uh|lho^IxHgaB0$D+L+^u800K5G~yXKEl>@qo?cGK~=10NaehN0}o|w zCKNNb#VeHab#5jUvrqW+4a{xzx(v;nkLBYUWgPC=o1xOPQ$0Pj8wgC|iG>=Mt0XF1 zs}H$sl5fP(M48zd6f9j+$XrQ6iX4Lk*Fr|@ zEk)JP1LJY$WTT5L`I`QBu_wI+l_DTrW0*Knlytku;;;8Hqa)$6yWx;obL+!2vk|id zn_9_mtzv2-dp+uwja?6%;cjpV1d>gcvrHIV<={4I@x)<^?m*aVv%ZvAa$-9)7D|vZsSiy)@p@QtmwxpoHS>ODZ6>j9^c3$3&%QxT}f?C}& z)iz?S@iOKL)JFiW4ro3EmIGO9N32d|WOt_R%gZx)aSu^1zM(6M%f*n5XeJY0i3K-| z5Sq@kH}djU^qV+$YW+xUr^b(!8kOFuQOKuY6_ZX#LNivtNgE-@3lM4dVkiT-U_`d5 zR%agplVgY9B4lzzhBC~7h&)AT7x}}aX(1>LD}_!|D3CdX>Xp1)L+%?AR9-i@SD2lU zqN1XiS5VuALUM;}e#jVZSITp0A~=QUso$$`X9y9mBoIe#B0#62<#WK!fXr`%AhNM9 zpG|pwT-rdV!|;x~gJ_xXN0g+JVGxOlNTyph@5pQNn|w0&eVrwyF;66gvcYo8U3TYE z@M1?c5PnC{=^XAGkNbpG8=YsO*b!LZ2*abgIa}q+(a?XLli){&rFouzysM`tZpCUT za+iKw@1Z+ODx4b)cC)jwFc;Nqi8KgM*x_=cHLi+5I80QIG@^f#;WTv;b$f9;z=gyz zf~VIKY=|tug~@6MT3}AC*bsS%I65=76+P_C>THOj5|^?Pb0_9jbVD+Q%&e*ogp#)n zC96AHPsPt9q+FFv41J?dOGhpbUi08~kfV#IyHVy?lI6{i?a8~jp!6^t|)Y1<_@kV6MH$rgqS($o5;<~5rb84M92Fs$@TuRJ| z5{Kj2@HS)uetKul8;Cr70mAH(d$P{kczS~GbH}6Yp@7>0ic^d_j3z>JJ9T&<8wx}O zS@`QDm$8`Yvt+o;@UT;qw<(@q=eBYXDTD=-nS#v;Ks4y;)Y}N8I~$rk>aaOGu9qj@skektjT5A{&vONIY>T5^=Ed$QHDqfv7#6 zwUK!Sr!cp$HuX++mg+o&L0QsChTM_3-GT&TEWr)joh=>UT&>QOv89${Np_sr8+5p$ zC0My7TRB^?DL}@7mGX5kD!Q`3e6V}&Vuarc%eTsgXVk)3dxfsNRH4v&NxsgJ5F$Eo0E$77^O$|e zBp7p^R|R-W0VFwG`8Mw%a91aiBhnuNX7JcXaPH}xY-evr<7aP2<7aQ@jmREaQ37eB z6~UmLE!-8S79?{pC)Ut6(DY<4E|FgDF#7ln+Ciyd{=~J7>YxVFk0?OKUSLRNShFV% zIEp!5txy}!mD6-83UhChIb~UGtX6;EAmL6T$!3Vc*|++}d$fBfCNIyv#V?2SmRge- z6954LGcNdJ~BW2rWaA-4lyCmejq!Hyn1gf09SKeDQ zz*`|SS0^!AJ#jLo$hDXv9GnRD)#%b~ed0B;9HtYZ28nMIk0Q**d|KgzkB~~JSgqjH zU6=t3EgRQd#pZ5i(}#H}B|4wGwUKZuW%UhOs@k7B3LE;IS@GnK%=7s&_bJJw`L{!k z-xjSo)kFlgRRMKDOseA$Y|wd_OSsl+l+-gngw5#FGtkP}ovfS-Z!Oxa)U=)1eg-tR zn<-K5Cz&3nE;IR|IB&@)pw2XIhQv*CgSgyA->SiTSFFV!5D~+g+@TG+jg0-OVt_$+RdQ z0SX7_VQTrbR)$a&ox}cTTIZrfFP+1TmRE|r9~H=kPGFcK37?#pE|M-=hKq&K7LVm= z^`Z+$`4N(zY+I_(!fqN5L7j2IcGo-gd$9NEGsxBf|CWKoO( z=>5(Fnk&a@t=$k6>orDbUxc)oX*$axPWrbceObzFAEnrCkrE^sAXxQNhS`=)y~?r{ zoVF>nZBnK=UnUT+g|lWT5v9VUf^DJpgdPvi$kq(_y|^`3g0|WBB8HrUe2g1 zqN`LXdd4yf)uUzPVy(4rmn`2U*XHAqs(EfjZ%^vaPe}K99<<1=-`L@PSu!uhFVstY zJL5`XOZ!;3w2GJ*U4ps~{-O(y#U(Vo%7iBubMf#BmqH+YjM8{K*9umQ&q=H>+O{dF zwMnrKO6d?UC~5!1(8LOz0+!IzMZb{~&)`>ZHQWhjcx?Tp*CG#rl&u(hDQ z3}T=d3>s{!ZCHoS0%R<#UXeq@z_%<%2a%*cN#SL40D+hxd6W4FqYw+?{LdWcFA>bl zBQP!q$}LRiId9*z_z{BtS3ung^V`tTHN3w@+I=C2tl?ngZ9e6m>xt0M^LwON8r5>H zZ=8lY&(cvEBeBkgkj4y1)2O zr%_eX6lP)$%O^*wQ2Ck)<5$b^{%StO*8WB-c+1W_me)7fqep@H3n)Iu%tF{XTFjr% z;+WIojgSM&r+YUtf!{uS+zg`@Q_SLg>aAC>-Yg-THc`QV5Q%Ou(>Jspk#mP}HHn%t$0 zGT@Puy>q-jq&w7p7)TJa>>;Dat0?HxI%I}@3rA@o=4!YJHdmHMZ_?zwvAZpU3U}a?}bj?Qi9<8J*hmey=~jk zxyf(EQWhZ}s)*c}URfP8E0W&Mv9>anwmrBn#GZX&TcWgVG@NZX-5|I#T<%WE_CX0Pr*jZ2=R@(0A})wWk%yE zI>3gCY45u;gkq^0b#>vjBW$eNH{W<4ivZ?47V)N0IUW9UH zZOA&7<-)GuUW70!1Cw0=bL3%cT1~2}PBFnEgxY8zxb(~f@JY%tShSxabYFtEeQmP+ zR7;`q;V`j`K;C)LWf?iN9U<(Zth4Z9OkujQSIJ!V`4|RCcKL3oF+r*iAJeK1nrt2_ zU6r^#nvTZsV=Je&%>0oWS}whAq<@J)>h@Ar{sp_DYvf+ zXk6|FDSG~^C%{mTP~yzS+*l2iWjW6dx>nerh70Hl@P|IL=R9^;(RCK=g%3!pY@kq;=rarPC<}gcT}?dG;ab-N zm*f*N$%A9?#=c9bk)hsm5nARy7RrLK+g-5IKJ@*6p^Tsi9#4zLLZioylqJw;%MN3H% z)w&0Gu7_Tb9g#xp_Q@izcE=NPbb}1KC|(X(Ho!-3ni7=v+?CZqmzVu{Igpphyd2DnYZeuBx^Q_~v5y-u2I6K; zW;|0lv*f3A49_(+o{jv4mo(U*c8U7YK_r;3pH%<#%0{!<+OfPGAE>qB7PWuy=y=`p0BZ6%mVy1lG zZx%*&)QxR=78i+yD|8MLTl87^RAi;z$Qt&cfl}ew_E^t5%WPGfGm|^El(%C1LX&5t z+-C*aObyL3q$`%*2!$Xs`*~dfS>-|HQ`a6qgJdXicPTjadQ$fFh+NOebdl6x9nRESWqekA7d z={lTFb<5Cr`9 z2G0);67n_c7}H+fUxHjnVe*TL{^+e_u0m&Z+15JiqtvzwcjPnz`?L)@!c0u4|V2b z2+~Bm5+Bho4y_fpe38UMr3JhMRtpSJVmwNq<`7irrY7?(EomX@#+EjfJJ!@W zB2>1x(M`e>m_k63RCqjL)i-5qkq_}0IoUIG6aeTXYn&p-Qdu*Ol!?GPBoVwN)sQ>>_Ar@fg9iF>p)dVgT-A#9FODhX z!BBQtcx$1iA{N@sTT8T)sLSmq99Gp&X?q6gKuZIHsyhGt=;AN%NytZaC?@h;7P%aBZ*>y@M;= zltN;j4f7NHLCtFj84cHs>;hE0ZaDWpm_} zM%ql%A>7E1LmEJ<1OBiOfdMK&qw9m{+MNZl2C!7DMi$1HPs7l(mXrpT7^R|!rWiMx zwpYc{R2GZ||070EMl`G$rI==+pZ`Os9GE(w%=#zFO>ImZc^uVnOHJDIJZb$c_6g+$ z;B6v(Y0XuMPHCB81D=kWJ!+ldb2)I*O~#)!Yc##D=L2gXkLEd?cn*B6j6b?ogXAQ0 z0ZN9d;SNLvfI4&y=F7>JJcQ~D5>v8S_1Hr~^}ck_lSwvBNbNMZL`MvD^d8lEdIe`# zMagtV1Jl9Ye6F5EFk(^h!TJ#fAH9SwN|v9~aSsRp{V_p$nri8c3%| zYzoXspo{<|V1bM#HsB^_(+k2GgW@UeNu?Nn*<&Fc6mfzkpHn6WK+}=e9aaF)C$J2b z1sBRZ9#DV_G^>CL#>!Bp%(N@nE!aurir-_lqx_F2RVrnQI92*VFEv5bwrkN5k>9cn zcgRx$p4kL*Sq>^uCrq8N$%gWiVnGd+YDy8TGBr*q=mAO-o!v<=9c)4c6v=Q4Ey4C0 zLx zlz+6)WI&;ssj+Th2Q^~Ku1*bPawR6)jNc_pbV_gxFuh61liwV~4W~cFjZQA9vQ^@w z1^U2BrbIGfP069%j96y$t8i#8ML1x2rOzsO#2#w&S!@lBWx=(q-eMe9PajjNS*p)G z)n`SjI;saL)pQdD7px+w{8!S|q!Lv>R%HsZ^D0va`x*e6RLo|9^oHl;=d|Iu1kY5} zez8sx;wS<&%2#lSwS^t~Nqd?<@l>YB#-B@Au0eDg3o@2MZ#Y~6S6U-%8}c$eLKSSr zirz+E$~)8y)-gfGiIJ?ODISz`HE;_@NKy&cP4VOro{Bd4GNen2uM(@$SCPS2mLY&= z0G2~mQb2oE`7#7M4s<A{}S`byTElsMKWm1qSO8%X?7FpNFm>N^_iu&0;CXJ_5L7h`_=-9&>4%>y!!WhP` z@;=GsRpintSV!kk&d^RdDDQE4!znJ|iJ>?$ipzLFnIfQ^2jCSxqnPoK@t}f7R47tb z@&NRdtgI>?z;ucBQ7ROVN2RLkv+aAjmn8p7ZAyv6(HLBpQk$|YT8b=MthCgoih!p2 zj7L=Z;`m)U)Rn0v$LOZErn@6ND77^uI@N$rbyJPiDSIi7l;uo?4%Dy&#`;X^@Iooo z9K6S$9C0Ck<|aMWh*p$3i1}<~l4{JlZb>;?Inh^A$yKayl#+~vlk6kVWNKfLk?I3B z!AsbagRCjo7RzJJc2cy`Nf9MG(wB*Lstf0a3rj~w_0)VO6lr+LgaEy$E^swdlS^I)y>WE3e>In|R{#MFZz7#Azm*f5g{+d>Is zrWwb}$xDsZm&IrCcwE5ylmJFevLacszB;Xl?s4)2T)4RQ<8&^0aal5UXXAKzG>)|_ zo9^V$<1sKiQl-gOq|WBdOCc;gM-iUGTfsD*H7i$6B{%qsEIj3T(k<{WhgKP zosx&;6f-H9J+PPEILZs)2}%YG1y{UsF3Z@@MsMSU8d@GVY^lt!(N#50y`ddRX+=lV z{1}5|=Ax3<{fu9#`K6Xq0%4VD^Jhf^KJyfHys^I&;lK1Lr8w0rji|AaJdJfS6hS5` zX+%~iCilz&q^Ym~9LtVcDBWr5GKtq>BA2G6#w0mSg+*$~B2_3yv4bry7Dki?v{nSP z<^lSO0DW1->>3azqs=#39HFiQLwSBV6drB z-s7niT()Q!dV;Pf*a%V1HVWx&nm$Kt@>>`+mVrItLm5`ZAVn2}WcGxf*{z%6!8;FY z(JI;y%)F|Yd{p9CRhH2~kx^!l=|+QWD<5J-4I6mHZ5JJ>F{j7ba~kL{DiRPb%1Ren z2qTK+)=hI$Jm6CvH(E1}xWhaZ>kY*Xs$nlNFb}{j-R`9zX&%j%fox(xOQ#zG^h$d* zxe_l_n6W@&kU$qTcvS=Rw2)3Kwb@cfTbT)fC-0!Nc*caZct|jn=(Kny`nXUL^3`V_ z!=!mq)lQKh79_;8be=3h5FZ}X;wVGuA@{MYaq?%DGLC6VEK3h7Ov{bbr_IF=UGV98 znuLW$^T=3zEcd8V(KGs4L>!NZ=b{+TdWA_B_Tq6sAZ;5ABw${!k(e{NBNLXD#cUmt zTnwv#r3KsP;=)5`7_3|vP)bb>zvS{cNE!@jIukNq^et;a4r_rgOAIqfsmpZ0dmBsUdCjg0eXG>UavYhe`rBNxxWBI^h>?pqN51 z_NQxsm2`5Ej3I{Bm6}WoOmE7Q>v2Y>tK(-*Xly!5lTKKg){qej>ci56zGtG70e%P8 zrPbo+P#EpCET0z3A!NdnZX{RfbR$kMqgK|IbYot%sUixoC?6e|Zm!5^&J$U3(Pt@G zzsIqj00SV6oHyy#`fTG8l7QEmpo_kerGJbfJAHO^0wUAtOL-bYeauilGrE~+PG{zW zrFY>Krbp9cw6~eUrbjdK+!W-wDd=L8U696dV}eJU0kPy0qI6#wf0K37nTDn(vs&2@ zn;xJ@79dL&grAaO34)j($w~`VB*T1*iY*RzgFU3ZDB<-8qX3are^7}JOS)4Ur?t*HDyhY<57%_ ztkdKSWs4BBAsy3HMhPE5(z(f@&5CD(T)I1}ELl-vvP@#IwxYB%4sf%Qp3B80Pc|Sj zJD6U|1IsXfp&F2)ab7-Pa;4$?t{8}#%?&>Q;N8}>f6u>LlWWqiiFfs&FIPlqrV(JB^G&B?9d7tCj9 z|JBk8)X@p}0T}*z3qJ%ydGuLdlMaDtgQ#O6b^6q(smqGXAl$-Z7NwhB$2q`m@k>O! zjdhd}ctB|xDy-5B%tKgzr|YJ$$zn1G7zW<)cF zxiLS0`Oz$uhpY_}#=Hy(>SD1H=JWF9hCqTkS*8Gag+aW+AQl=VD+*h`Q1lF6-3(rc zFN>AwKWdQm07iPoy+{@fm8ZF&XI!E(qV-vL48O#pyBKdn*&2%`rRU{0V`<{_0Y?Vo zQzZ}=7u}2)#v&Gv2Sy_O9T>2aAB%}F%2do2XgRRQ8IerJ*d2JDEw|Gdk*I<(B9b>W zn`KOvQO?XtMm!5kXKJ77!LwFC2B|*MYO;6>ve{)NKaVscpQ;9n&gU~I48!t2HhM#U zGWIGG?bWCA&Wr*O%Q6+Pl^%VTl~MpZAsfwEF;xFD3Kh{BMUaG}&6fUpj@7P5(@IrX zTB!<6%XlfJ^BN?lyp*c5H0m;C!sL>_8P1^VM8@o2EPTEgSmDGIxyhOza_GL`T zC+1m})AZslRyZ*aT6k zn21SZSl-lMj0C>%N;{wTe5|7KSk5}dBupB|OXrI^sbVT6P38$za+Dix%y19DesvEf}8cd zmZzvEkJFzotIkfg9+d9ovncRDrUsN56fum6KGhrgh(FW_yTw)l(u2IH0)8pvmIuH# z+FqtpOdq(Guu!Q!=LNcmHL-y8bg_aqZt|riylW)9ThXD>R5YZXteK@eYcwl>2bJlo zfgSY66gfZG`ygq#qS$iqkeam;X2qm&D2-JNX_56+1#dznFT0LaR3*z*%bp&|`r#S3 zZqIZxYVbpij0iUU&8VUG89b}Zf=l^RwQQCktpg1;oRm6#VK!S@BO3?fbTj$#0W8nN zLj93k6dtM5X$Jf7g-$6LusqX&(Z%h!6n31NVHj{+G6}z^aGEn67|ke=mTOGu%rs`{ z)b-UE<1(?)1qR_lbHfbN3%9$OdNMXVVu7t0hcZgogFYmju4d7x)Y`M|LPw$dQ4#wQ zJKapaeudSQ4Lq3!Oz<-m1~Su_Q;CVO5YwItX67?ZWrH)87^!@pmKI4L*h?)wkWqQh zG>4SSZs=FjGz5!k9)pcBS&}T-O{TH@rl|%`i-yWm*RXB08iZ&N3?DU=BaB<7thtu@ zfT5#&Q?v>r8#=R&GgcPB$4L6XG6`9xL2?#bau(wP{92|pi{#dxb_|!pOgj6f3yw@< zz9RxQWg4-h-1KL4nx^7KFbl8AlUh!eZ{r=zk!EC%U6e1ffkJ$(M2iGF{j56mDo8#&$h zB#~md+1c-k#V+M{I-B>Vhtk9BGb?CMvV*rNKk3(XY|Dmy+QqgSaag5ptb=YWp+c95 zZ%Ek%LGl|Kf%6}d*(HvE3{CdxI7^FeaN$=;DZT<1E^Ap@?D!-afNe`Q*TOqb{IWdT zFQh0#c7yNu)!FQi9t4J*6i!ON+++{XaosF)+_Qoq#3%*<>`f(Y;L3xH7X0=pD8nad zI8vE5PB$TxZ@Fbv(l;(B6`w#c9)SY-BMeXw`o}US5~ie!nks@>%0y%Q46-7K$uX2T z(Ww>1WhLT^&xR0uOr)4er{OWWF!$`60ff@8FQtWaumC6-zb8P6ORUmnpAjVDd%uQ- zu>DyC0+U}~FF}UnG8hWRtR$?`WpoW}{1z+opR3@$H3HRDjR=GTKG>{+dwuE&5u-$7@KdAIr}0pD z8f8sy7sWBZ8TIjV4)6`AXGfo7vT%!S1>7#mrMJ8wLIW@xrNOEwUX$HvFyE2+0}zVZ z1^W7U#LueHTSgu>b7`(B=0Br8et8VOR7idNW*Chvr9M^%G$fz;i?x`~=v~YjfZybz zX=>Dsz-5fN>5bTpHcMAUNmW)=r{qx|AKfD{eyIV$y7;9A8o}C&GpuPu4)e>IU&8#o zJZTBPl=I6Y7FEq3I28dj2#6Z^IQ9_!3RV_QHl+8=$4}kkKF$CfVT{D7Z8SCGUR(i7 zSI1HmGhae|oL5EjFpBUK^&p!SvMiJauVns;T6&KI7H9}#4-Pk=_xjAYWWE#g-I;I1 zd}Hb_vYZIxO*}Xe{28VSnKK4OqawYZg=@a~fYU(ErLJCHOG=M%Y)1y9ScQme) zb_4r#0FMANGvAu|1e0Ih;g0p!$=Wj<{!MQc$%m-*l|087=ZCY<9)>tJn3HfKF- zpw2FC{8G%4;m4mY=iq$N`Y=_YvvmC(NWM!rYv5-jSGs@Fh`PiGK<+wAy z3O=?hna}9SV5MO{4{0)3u8aV7&&tRC5)F4@KBF)rhTXGTGFZv@iCvneocdBmVFoXa z)`g7*>ZcpASk|J9UsPP!%9aJjGT({$ybxBblvO9qW8th)X%4&Br#{v?w7NLvhcdr_ zg)@Ss(JX{DP+G_CpHUwZTk7+WNAw<3NE!l7XY(}{WXgOY^X;hV(uVSCs0)ZXq5nDv zh=8~$wVW8{6*@*7Vjt-v3Y7$kL)-$w#B;(0NTFq*cU(-JQ3F$5%2ByAd=KXwpOnX_H`B?Na@6)G$FYmshe!cp7 znVI#o?B{Lf=VMBUmVl_4n$Z7!2yqmUwkBij-G+yG1&ij0!)6*!5QT+G*Jwa5m2`lz7by4DlCw3GGB-&fYUbJ`zV? zQHUhaFHjUF3=9dEh`f9Wu@VrAF=B}@L?rSR`3eL5gd;>~t(Qb3j0i`-oIpu{&?`jf z=n5`)Ng~2T!@YbY;;<+}dJ0H4TA?Dt|47?eK-$x^hBjd$`&kiQL59L$kt9Hj8s(Jq z5Rk4ki7Z@3{(lfph`xZd;x$Vgyut%)#lE8Ya!drIJB{=aqvWs%O6>n?g`t3SW|WB; zkER5M_=+N@8VbXsLL^?1^~D+pNJo}#n3uneL=qP09U&2o5=Dg@3PZ!hp`tJeSkg&A z^jRWXaYTruKBm5yfdbOc)+;DT!8KPf-l)4!9D<&575RX{w)v^$HaShDgBP;LtEp zI2Ba|1HfmL=q*Ae!Q$B>h!<@xWr)44ZA92?k;(8daqzG}f5#Ash!P_a*LRZ;f*Is1 z6a|TbA*SJk$Yn;J*D=H|FoY6W&u_UP7`mXH&>r7WBE7uez zw}xIp{^Br5doarIqS`2-N^c|}U1%Zlv@)?Y^a=0^n*uRWh_AJP45SGgik(~qLxiHp z(4at{K#9=MKTI4E3LR&9Xejgw^N*nICB$4ndN-_zQB|MszsZv;jhsB1NgRP{d_~?7 z{{Lu+P(a$z@@3*BXJUN|0L;Bc<_=tU( zNFBhl!!v8IdKm3 z4Hr7fT14%TTvY$BHhTH6u^YqOKiI@=6|K`b%->`fY_U(2tyj1tNMtfz5(q;VC<-@m zbT%0t7z8kFs!+j@n1dCO7`7l(nQ1`Wjof;EUHl=HMfw7Y<~(z+X3f;j{?@#?&18N5J;LI@0W6VHmW9BHH;F$^S`UeZ73VGzxDZ zzH}}^DRA=+r$Ye-LJ=Ddv_($UZNFx^#TMUTd z{}JC+K!mgmL)maE4)pzx!PZnjdeT(?M=%XtBiJyFfwz7((?G_8#bFTlP>~PJtB*W8 zA#%zM!y`gN#bFYW?{sE|F?cIyb`A~7l{3sR(097X z2mRm{6$%rfAWK0y;zJ|6F^Zt=|4Jh_1nw5511RHZ!1hLfEmB%u)!ZY&zmuIRtXjv@aQuOT2R7!6@xnhA*37!gDo!wOa!A%yf7kiHGY zoDKPMy@wPB1<@J2p|ekTDAi+EnDGc|QRNdsL${!C+n_*nkV}|YBK8quTCM1FxjDAM zJl{n=p~1AOt$?(kmD-5H?L+(nLmG&Qyj;231%@!YYGNPmS6{Atj8{y88%P``27wLc zqO^TBv!UWby0W2dVJ)O$N*6J*aR?LC5DZaaG~Q1f22Lp^8qU;G{?|l9PN|`-*pID8 z{wvV{0WoJS^~1zbu2l{Sceg!Cm=c# z|2@5yuXPNW2Kw5HF>=VY_20%GHb^eajv-K2#kyBvr5n(Qk&X2wRwUvO--dQjQKgOV zj0mW#FP+jrE6{uY)dvd8qZnBJD^+g+=|OwS5Nw$Z%LNnnKhw*X2AKDUg^TJlHQ=RU zUFVFIdLWjr;xHGv`K%`a3;Kg_S1!IUqO1E9xS;=IiR5Ny95hK3W-JU2hmHmXdK(KT z$kysIb73sBjR>M^@V@UIEh|qm-xY{yzfXp#PGdvt1LtoqXf$R{h-6YR#lC%MUm=_p%R8 z&{#CT?};SqH_ML9Yh0Vrsk6t-F^k=9iPElk?$-~WP^`Bt{zH{+#Kn#Si=JH@Hn_rc z^Pt1s#3!}y4i8kDX!x=H+QM{O<+^6)zb3yQ^0LvHo4rf@I^0&Te*GY3+`DhFJHqlU zM$hOWZF_yzi%TK_9m9*?RJ{|-Fw_YYx>cB zmrc%ox;Ntamh3s2TZI9C7M|Mm65aD2?u%S=R0M46ju4E+xW`L90j@*^yrN<3hKAF0 z2vk7=nubQwGO(y=4K1J1O%t_pUMRIoJiTH@L}FCrXZIm9S09~q-|(^X;i9-*t!jn&eiyD-9i0DgWNV+c zeHK3-Yv!5hy3pR+cg~}IGk3=xUzDT&N4;nIcZUG?Q2XC)k{$#tPoGzJWL)@-E;~ml zJqhcWH1X;9ojv^C7^&?)bXvLh*?DW0^;h3=!)WzgI{%L&9rj?KTs(yVFk-Ma3Kx5DM zJFI7IXf!RP>DUvUR(>CHztLdn5Y^rHUz`{|+AZNwO375!S&b^hDlRSldS?AJXmjFK z<}GX6vK4pM&+)Gwc4F^}RdH%?`^qx}=JY>7tCw+?-q zh30pd95yCM`|r=+OD!kp?7rDyaIXP|{dDg5YI`?My$)Jc0M zy?$_Q%KCQ2XPslJgqytPj0&8r+qR{C+A+;1IX#{icYb6$s9me}x*AOyYp*)L>*L|P zqrEz8FV*|dX6eKTHK#qR?;n4+(DV0ZLAMRx!oC!G_zgU3_j7^y6x;5zS9Lj8b?NP| zfWyx|_!RW1x_H}XY13huovoXnYbJ~xX)u53@xgW1zxQpnyZgrA^Xq$$YQNv~Q`NF@ zHBC-EEuV9sWl+!1qpP>4JN#(&%D3}~U-2b*wL{zH%r~x_V3z#8v*Vca%Pp^+x^r>U zpZJQ#{j$C+y1I4l1EVcR61&~qbuxFT)w*ZKk*Q5wrkAaqv0%8jTSD{GUn>i$lLvg8 zW^!iFm&H?KiZ+y|MF-yOwO!m*_rTTs)dNdg&)?QG+b5)Y;PH9)x7G^40BR|yK{bqC7MSEe&*WRs0-%6PMe(z}89i!dK zq$&@eT|4x2{fxMFPR1PzPKm}Y96s%}%DQ-qseL|9Cz!pu zHKKX&ZLjz>8@1;4uhUDM)O^s2H`@*0UGchLaYX<5+s7mJ4VruX;iT-3;X}vkU5RRa zzW>SaZM;e!Drc(6HX=jUW)aa*Y|Mf%^(knL&3yhl{Q{F2?vFEow(AI(R@`zOBi|Io6} zvFpua_NqAUn|nETP~XCb?dQ}9bt_CeZCE=s=KZ?XDP_xEw$I%W_t;Zu8RnZav*)@H?lB#>0M`8KZ8p(a-3P z!T#A(HaP~by4E$U?yK*zfUK4kxgKRxMTb*^LLFRwzdd?)T8rTmwDp!luqqAp)6>=a;8;EGaG0Bs@ItxuU@G=_##}IFX{Ypm&MX2 z-}_hVM?4G}@uBUk_TA5}5Fhc@-qE_K-LDBlMxDDJC0ui__)p~J$)7vk&ohftJKtxY zZqrGIF*VaY$1dF7=FrF{y`InAkai*aea@qSqj&fCvbXfj@j**mS9`VUsj}eMoLk)< z2kQBZ@1!+<_0Nmy)7w1m*(F*OyC=Rjqghek(-Vi+H4HTUcffz7QcFO`rNFJ0b>Imy zfxIRk$tLoFUVNhdOy z*pLn6F}XuVlF?*6xl3|L05K+Kh#{FyVn{zSguEj^Nd{?4{*WsqkC>C~q&cZ2TggyT zNwi2LF(7+L7c!2VBL1Wm`GUD(GI>d=i5e*&p2UH?ARUM+`9xAlE(s&?crn?6gpg$r z;#l&7d?pLYN-~VJAz#Toa+&Cn1mc3TxQ>xQq!AfIYRCa%L7I_`B#tDKo+O>DCP&CD zA|W@(Gjftxkq1Odv`Hb+ByY(a@{9B*Gl?>ZA}feDIY(}jVsf2ClNn?c=}b10DMXb_ z#hf;R+$3J)DQQiT$apfA>>?dWV=|2_CC|xeGLiU@i)1hf!ph?;DI=knZ>Eq*qzM^8 zgrt->6L<0mw&V$ENi0bT5tD&Lhlt2GJ%vYcd+ zuEdS(BSvHg$s+5>7UD~S$y`!FnvzAtj=U$1MD%|%Bn;|Nn2B^Nh38Kf>Uc$ny6}Ns445blp4{1+k%?jUHS?TKCr_azHEm~-)tEi02IDcL=yi1p9>&~1p zGHu;@V}GesM^jbxQ13NsG_=*!`Wfizu5A|+Gx*_?C*3Z;c@sFs(D3rY{rfk(j)-Wd ztD&*J{gNdTcVlA{^R{h!^=sE|e^p4xyMvP_|D2MO^EG$exY4r*4)i*5>(-U|2M){| z*S-6Y`%jG?cpg?iYjKz z7@lzH(jEVzqETT320Ta~IkFRS$eajORui9w{QCD`}amFN=l3VEL>Qd~b^5vmVVd1GchYv^WxqH`VSf@^=!lq5z zKL7hy`}e0$!6&a@cUpA$vR-Q~EsJi=oA*9bQ}eiJ`t(f}nVDsAc6I|yTD6*(R#NhD z{I+e2e=S%rV(#F<`K7+TFON-~+Ouz)HW@F%!*hx~J+s;@T6Ar_v-85uRaJg=ojWgZ z>(OK9z~tm5jpO1j#t$Ff{rtOkH)a+T3^LNwi`w?!!IQ#i)7rOe+<5k`d-n$3d-Q06 zZ{NOSBvw`(^fWc^r8+ulYBg#UW0&&Lr&bd@!mE+e`|@iciw=Ml-xcU z8U1fOd$#IhR8-%aFJIcODlMH9(!c+ayTQTnf`kO$eK&7j&mJ@8k>mF52P=bue&kP> zp!VtW=dM>@zut3EBue}|ckZhZyLY#4s;+)heC$|UZDgc)WY?}?9wsIRmiqc1CT`t& zKK$Upv6<)2b-7es9li1X{p@Oqq|=a&9q&s=jjHlGabj7MRjZcjtX!G=C^U3f^pGJX zvko2FQRe4Yne5=8+DKVh@a^kYU43os&8NM*rq8gj_`ZAM#PYzSM^joYTjsv{)G0%& z_U(@vu3v9?=kep_Z|dp}^vudK>)fo_@#}$sq1*4=S)rDgxXmm*{cx|fYda6>&>^+O z^5wz{uUe@!hf!7jxUfWXAyI=lki%Dbi?&L0P zQgKr0Wrr(cvn79bDLX1Z9G*GgK}M4+YtI|nq+LteXKeAe`-HA*_HGM$xVr1%sh`i> z-0n1>=yjJv5pB=wHd}30rsLN*xAksIOJZqbLu^{MBrRvpCbRF{A$QW!NSdc7@%;Fa zd<+XCVW&@%(>r&Poom*RH4zacqERE#XyQaNF(-%Qq@<9PZ{NtbLx;$r3m3?RO`FK3 z_;?b3{W`gxoJ^9xekEVk)QH-)ZDgCFAu&u$B#Ha?ll_j4#PPuc^1#-X*m`&nkK4D& z?epi!`5{BdkS|}zm!u?;MlPBcK(xqf+lP08z zt1EG>ttGYD*(6&eBBIx?$?MIV$>!a=$?o;*$@-caQWFzHVtVx=y=KlNGxPFDUeBJS zXTN@=-`1^U>)Es9?6PHK+33+^w3!((`~92z_VXit?(W20DkV}YD`JIn1vD};NJi79 zq-kU%iL9(7mBWUSVP$2c?EQQ4zFRla?fG-^+{=r2wQWn<4joE{4jf1ZE?!I)A3jVD z-@8ZdRaB6QtSpjMT}`T2ts<+Gl!(%sH{^|`CebuDCdTdCllFV}lD%Rv5ho;>#~o zhja6_+q5BVmMJ@l z{FpqxdzaiDJeUkFEhVM8x_oRpW8kdi)qNS{lW$fX`VNRM5+$SxfnqI2;gxfmKsLNSB>+qW<28yH9e z)qxcB_Qq5J{|pR>0W9&gpFhdZW5>v`w{OW?dwXKPXc1Wy9!|o8gGq2~EQ#H)foyp9 zj=UQ=l8iiahMX}mAtt9zkyF^&5WILnUUcnBx;i)zht;de>Z3==(E|s_fqC=Dyzk%1 z_bXS(m6IpQ$%hZg!&$S)tnS@O_sf^b<)9!E)Vw)q?(0i@*RCaNeSC<|x^-k-aWN?# zHHwTna)cb&vW09}v4X6aKAlY8w~y>=)rz$G{F!_%EF^_DZjc+phm+w~uac|%`;-3D zrjcnQMvxKd=_LKf5Ax&JFY;^36fz|{v4P+&OY?{(LfjzyLB} z$r7@ps)|(Y*gQ!i4uGY_sNxw+z(lCq zYpB|8G`S1ZYbLte9&Fx%u78W-ogvok(fm}X*fOZ^9yBi+D%}xkJ_Jp!0-L8ntQ$iW zw}KS|z^0pE+f70yXXl)vtv}#X=k&LR>F_ zMdmPLvmt81P?hZv$7>LO6|hJTs%!<5^8jko4C31tCL!xd)kBf9hty8bG}@eJ5B z7ouzcRjq~@^n{oPqHA`*+~t6+8zJuAP$f5XMF+6>5k$@xrlBe26HL!$n8*mQeI-Qs z1x$wqy5v4ocsaUx2}JcW%wjUsAsnXhD@1f1#Q7w;avaP`B~-Q)qHzu?vk0a@43TaQ zQT9RizJdwe2G!XBaV><{cZWKBhdJyGRXzx_6bZB32xj0V)M6IQO$AK$PMDE6n3DvU z@i3^;QHV_)#5N!1uOC#q2gG*}#Nz|R!5S)X1LAQUCfOF^)&gq33!*p&<}(JSY%xq} z4NSNpOx<&s-=z?D3z#A;3?DWygYPkHq`_RZftYuK>C%SD8UvFof>@q_IuyX9C&A>s zfeK%N88U?$PKWtD3zOLvX6rG`%SEW%2$-(HFa-jL>@BFl1eoU0F!fDfmi=II%V5fT z!K~>*^o3C4eJ~TNp@L2rrcT4OEr8mnLfwzRg!O@nx5ChJ8|K^{=GhDC)DG&S4|Ar2 zA+IHdmew$zouQU?7?$>97(0X^Ed^#x9YaMP)czMtqYKPpIn;GN%*P6tfJ~TzDHswm zU<&_YsJjl6Spt*gfT8dk%>6x>17(=uyBG?F!i1z_i1UESSAxkKiDAYJLz_QL#V45W zg&5ABz+|n(FwzaCr$2^{ff#zEFk7Qwl8ay-OnusCt3x<}%7_NT9 z>`%hbk_A&U114-ZOz$z6(5@JwhG96~1oQO+L$Df5{dkz&cNlUsG5k8h><3`@w}g4g z#ZX`b6FUz>_yw5aQ!o)vVH!VUNIs8Ybp6^-cR%zl+wgf&>PPXpn+qL==2iXjZ?w7E z=gP0^Pe-jaK7IUW+Nf_s@A@4J{c~Nkv3Et9c4pKQ<&CqZJ=;5{=5kTyv!w&VIvl*( z%H`WMr5&0_9u-(y)@pRAh+4YBqP*>|#Irm1KWUtLP8yanxxbIi9>1T4vo2QclytKB z8#Z$5l|cu#_?fDkIgYITvc~B4WzEclX{Ss3JgSx^jhMQ8cIVxL z=lJfuyS}8x-PBoH=d!kX^bz+Z4iS!C`mQNooh>%MJ9mB2v*Su?S#RIYS2g&#Lg|%s zY4Zrv{I_9-tzs;CwQq7}X5#6j@HgFTReX}(ySWz)yg#|`IFI#RhI{nA>bfJ@zkHqf z*wNQd8Ejp@_1)F6Lgxi0%QSU5H}`dTq>?^hz?CDvmWAdTm)t3Psl2kw^~nMEKJ?zX zKi0QVP1MQvp;OoWack|6egA0lDZ4aUyp5=+Z80|Mymq6@ZyMJfv1*;-AAj@NyHDd> zqFZ14yLim=)MisJYzoRh>a_9QvZ?d^UH26&HSggV-!Za%oBQA2#BMw0r~XwEU%qLe zZ_?P#(dv&Uyc!pmu)AqV!li(1Z4+xwcAKMOxx;?o<%5=UwzhfDq((omK-BBs~LRRm)J6l$dY`!x_b;$9_zTrny z2TuR`F=BktCPCNjs}_tQlHToH?^MrHcE7D}I%c+aMrFvt!9+aiZ?{SNj@~+(H@LfK zgHv7dtJn+I9lWnIan)dEEH2O^b@JrfXb}0Q$E4X)cLt{^qI~~n#S-p1f`ks~h zddZAcA;T*3syxDvNcOMDX*P0PhwuYmmpl4jxb$2-u37qxwfoLLYqVNxlfl{r3*YY9 zX4mh{gV!3O2ioteZ+z$wbTBFEmF@fsM>iX3Nn&q*+$5fm=`}(cJ8xa1>4#rDoiewn zxG3_isn7QoGt}n3Z1Un=`Px-EBj1hcIc7z{`5{{;%=rAr{Me-Cx=O#4`y17*KIv3y zx<|`mR?L980j+9R%-vQVXfbU4o9(AQtX}l4m(lE~_!DzG-rv%{)zp|D+cgH+x;o$4 z82!*yYt@fAmxAI7tL_XHj%c?%I>oy9Vb!(o!vy8?-RD_4xBoReR$0{N)7h7GT?T|d zeQDRZhw`XxUtDf)S?*!7I;&raQ&06lU)>98=A}Hm8(cT#S3zjs+4m21KOVNbY0rLS z&X@}`CMNytaPF?|wYG-2KhA2OX%}cat8ZB|AA`S*`aU;J>^H2BbhGiJKH8SEKFr>w zDfLLm=(i>Kl{id^&fpYV`Gs4=VIbM!xBwS#;;cp%Xg=wo%_g7xegTt!lWk z*XIYPIy-GHPE->t_c`^s?brv;HO;9y$xc&a&t*pXsT)zFqE$`y_?DpLT$MwyRHcmg*#rJgb z?1Eu2BMinrZ?(a4(}dY0iXP?k+UY-St@xAj_)UwiYz&pcN$Wtx7a%InNe2Yy%NE$Za0 zuG?dx&5goWE1#FyF6-J%FZa>8+&^dSQgpnpibUmRO*UqC+Tf6o>(;5%CpdrFkdi%3 z=7d?T3fumE`9tBnF=@A-FB{VKu=4n)DmUAd{_OPh;?|R%`3GFq=$<*eVZrNwX_wFM zTaw-;S$}U(|BTZ&2fZ5HeAm$)4^3|Fj*ZMI*8SpXa-pgB)6L5U7hV}Vpyk?i-7J0_ zx?J8PWWJW!#D$fa-qn*{?oC+H>0@H|1v|D}>rTjc`*w{EJziP7$|^K9sHgs!)5G3( z-7|AiS^5MOx6b#%+iR|ARdVNh&S4eLL4$X1+VlH;i8TG-6t&WCPb>7>>zzNFZ9YD+ zlV!^jkuwZj>H=DP*x$WSDdR`skGJhqyWIWwJSZyKHLYr|(c>MvyN`O8GuYAUc1D1W z@yv&Z`Y-II+x5iGU;9d?Hg#{jC^Wf8+_(Psbsl>RT{-s9#nI!YbR8SJeChf@Ps~rW zS+uyj^`)9YQ^iwqBo~J~9@avs>EZk*lX_o^`mWfs<`)}z>U9eq#kbUm%L_V+n@W7<_-^RqZJ--*Ky&F6;BpT&)K&`XN{lls?T=P z`F{@>9*8)c;Ndm1ETia6;ZdQp|Kdrah{av{h*huJ51AGBZ1Tirr`=X>@H+YGb)L0n z$+6d$PZfWBC3dpc57hN_{F-eOsxzhA`+-$A)~}p+=8LCx@a(+aj<+xF%2=25jSVN?KH2zi-Nf1@N0+_zDcE|r?SP_b z5Bh#f`eeCp#R@OS4`wI&w<*4RuF;nT)4ZRpeyzTF(9_+vwLi=XI}pu{mOgJdKfFad}yLNcWUUegYq()sH>3K9vr7A8FFJ&CPPX8l9`x z)h>Pu6XumKXk+tx?^e~cyH96UU%pW!8MLKWe+RpTPSzEN?w-*;7QC&^0QEcXj&0a` z^`4`vmj4(*_Fxa2qbGc4_-GqnzBB(=T)6nP_xZ*z@2&Z0k#OJ1BR-*`_KEAF0>7+{ z>ZPxrv|X{s$*9ZX)E%xx=4mH8Sq*$T%C=>r0k7ADoT+|)=W=r0?%>Wbn{%ztXk;&V zsFs($wQO4Sl&uHackZ1NxW;Yeq5yIC{a37Fi(SX}Tk&V*i<4aoS9%{jY0~+sgE+tI z^0e*SMfXm)zWgbE8Ts4)m5;^t#;1CkC%$U8vAT&Q#_`vD_0rWRpC#l7nr)1Ty0Ngh z+vK~>t2=vcJD;8QWnz(L=O-sa zQm$uAIX|ZO%twROQ?CunS=i+AU&D}jwJWRg8lAEa@7<-t=Ve`5T6A0S`bGL)GqaE9 z7PQzS>Dm8Tc-KIO%I&XKD7F05{=K)A<&)*xW`EuC$5yHpH9VBa_8^p)gmZQs(fkcpC)H_s@?w8yQGuG@!E$gB8#3(H*-$1yU=`i zL_)c0&!snyI@qMERIN?YC|&mQ?fh`FHD^?b&9JepG-hlq{iJ;>jI3BZTWD1DZTt3h z{(VnIOqo(ae!CuUf4cXQW@nuxdi#?)s$UJe(R}2fGQU1w z)e%d^l^*_?e|pgDz~Z6fl#<4b?o;XeG}Q9$BsaefwdxwDwsbNbdn8~=P|nKUi-%lR zGkw2)mSj=4kO+b*y)9vc2pB7}C`1|vS_?6%3to&!< z;+pV%Jq~&n-pw-4(A~Z9XTRH{dhPttRp-sGPq|%Eq)!HGE$r&J$04nd==B>ZUQ|s! z>RH-af8TuCZEp5N=g`9MFKzBx-I#njHs`aO>iC8GilQwhm9{wXcIVm5g@?{hKDOt< z*TYuXBdogKRdSuNV6ezBp_|+5Ik{t}+-&6%H}(4M*+I?iO7m1yI&FMjurjr6L7@9v zPwAAineA5Byly@zt^0=~zXd-M64k$aJ-lYaNvDB3pH3ZV(O&h%_{zsQADm|1c=Oio zi-E3D&r<%XmLT z$XL8XTZ9+CI0+Iz>D5#SpKM0h7Pvid^xs$jN)`Z6(iVUcD*#Fc0#MQsfRcOwN=5-t zA_Smh7XT%30F-+mpd<`{lBWQa z_yJI|AApkH0F*2PpyUPsC8hwBbOE4534oHf0Fu0N}>QL*$6;MGyo-a0F==F&;X#MF90PS z04NCppyW3IB{~3<3H15ok}fD#V? zO6&kAnF2t`Dga6@0Z?)nfRX|LN^S#CG7Nx{<^Yt$0#LFPfRgI~l;Ryo696Ue0Vp{JK*>k|N?rp{(j9=3 zJ^+;T0HEX?03{Zi{E8(2l>7pqnaKgK!1?ruFnwJFK8ztDgQ-2rq_XeDeg~1vrnSk(x^XD+8>q ze)(~u|D4l1+x0p-dAzrJm-|N=A5^{cKyZ9sa z$s0Qr?F-p|Fr?p%-a8#8MunsoUZ1`BT8m zKeoNPTSmUh(`iMuH-BtgGq%@^C-LunI=@@Crr`97;;$wKwmXzNY{@PEn0?Hvc**D) zuhzP>c%F1?itn-So4Of2wzAr`EMxnqSKi~_-+15q{M~Qkz6FX^hECG_F{j1$XEB0h zdzbHep|LjY`H+E=e<#1nyluYz&XHQ@l9&fhv;aBIlbYev21M_=u_q2t#N#g{hKqJCMFN6X=|9-L(OXY+Tfw>zI5{G z@Y65*OPfzs-Hh&-MSS@^e&1sA3ChO@9b4IK&RC5M_aKG5z_v(Wn_uZvJ{rlgFrdTJqw_7Nl3?X9vh4sIxDA0K7Ngp? z71SAq8Se9J;Z-#>?Pbp>tK%oS+M7Qf5SKG5>dM$77y9QlQU$31nJkv~Y z_56gNTlRbXDXff+oicZ%S>1(~xp8t50efY{G-@Mb;@{Z|FQ#*B7YliAmk<0a_ z!*Z1`X@}H28h9_OWl>I+lkcm@zI`(~Zg`k_<55rZoEsfJ&RSt3(QFrQzIl{zR?*qY zzO&CHY_ND}(tGvV5#q)#+JAVe<965Sc}nHo4p*bRuZR~6x$L*EbxYOu#=p;APl{dL z`RCV(pSpbMGQ02AJ{EatN|(zfcrU8WKlCPIa%R7lA!Xs$<5In)56T^nE^3$T;-oj> z+^iGl&%Mypo7=Zh`D6Pv^FE&W?YiRv(aH0Eaa3))Klz1A;c3Rf%8qqadXnAb*mEqscZrn4+(=$Ri=Fg?2l?l}=_kQ0ocfpSD zP9CQn+QhH?sq}2d8Nv6*Z*B$FRV3#;@Yvd`tH#g`FVs)>^%`?z%9A-)o<2;P_#*6# z6}6AlCQy5e_iV7o@aR4Bx%Xf1e_)bX9$i^FdMpC9ZPb3ktN}H|%WaG+wP7$tKo1ch zeK_)68&_I~4jlbA>h;Y0T_Z-UUNh82D`-JGNZbbfm?4i`y|ee@~pe+-gKu zt1p`${v}7c7`AOWcfzIx+h6;9@oeS(J38pk#D?@Xd~ZeXY23fB@-S+@Y20QYnlyj} zsspm>E6`I1NGIT_B7ut92OQRSAg_7>$+Z$#EhiwDOfX(#0NvFANGeO5uxe|c=N&?EN6iBfuAgHEd6zT&6S}1U2 zUO>2QKo9o>BFhCxEjOUBx&a}lOss)#8wJEwI52c2z|Rc?_Uk7QYCC~q8-j6i3vgm8 zK)YoFr#2RNvRbSqqJTzQ05n!SP-sseKktD>3j@AP3?$k{;MZ;dx8@D>*)m|fs)2r+ z19>b08qOKWxwF9J^#cy?Q&!b_n>i6TsYAK>}4V8utLcjQ*oC zLFJgrur-_woCBOA96A>eFvP=Yz*)oDz&XGX)52p^N0cQ_yzg8d*I5H3goqzKXgT>|N->mZ$g@)HgE2|lPWkq;{{BDjYX zE(RJgLib_g5ukd%_&@(Ij+Fa5oIaRB$^0o*$TaIX)*Jy8Jn z3IN>u4B*}#fP0<*?o9x=Ck)^oC4hSv0Pb}HxW@+IUK@aW)Bx@!1Gt9?;NDXJ_pkxn zBLi?R9KgLr0QWWk+~Wdp?=^sXz5wpw0=O3i;NE!v_hyOaBm5~JwX8Xq5<5y2H@UD0QZnS-2!lr*;0Y|7l3;e0PdLrxaS1m zUN3-qjR5XR0l0S=z`ZK~?im5NHvr(?Ab@*20Pd9lxOWWTo-%-YS^(}{0B|n`z&%v} z_kIGn2Q(&H7=U{^0PbA`a4!qMz2^Y#=>oWy2jHGUbGX6-0QVjNxc3aeJxKufz5}>d z58&P#0QaT<++zW7j|9NIQvmn&0o)q`aIYW0Jpust$N}8j1aR*LfO{zb?x_H{cMib4 z_Ww2EaXU0QW2b+=~EkPaD9!836ZK0o>ySa4!+Sy*L2( z)BxPu25|2!fO`}G?#%(XX93_IK7f1E0PewHXF&wOJ!t^<@BrMq1mGSPfP3r!?wJ9& z*9zd?Fo1h60NlF?;NC+3_nrW_w+!H3Ab@+T0Pb}GxF-SNo-2TR2>|Ym0=Rb-z`bGs z_gnznGX!w&8-RQ30PfiWxEBQA-b(=YZ~)w^1#m9_z&#EC_XGgkqX%%W5Wu}E0QWio z-1`FH-YkH7J^=190l23L;9diOdv^iciwAJe1He5w0Qaf^+_M94PXoX`bO84b0Nf)2 zaL)?BJuv|Ht^>ID4#2%w0QXt|++zT6FB8DMPXO-y0B|oAz&%m`_Z$J-n*?z0K7e~k z0Pdv&xW@?K-faN))&Si50N|bhfP35k?#%fm0PfWRxJL!xUJIN=xIvBCpIEkPcja8Nd=8MFZU$JAj1IX`QIkZT7h z+{{RoD5Z^ah2fa)dXl`KHC`|8@83~er9X`im}WZt6i;`-3O#Gp9_^Lm1H^FM;|F=-n98tbsndxW^CaQW5KUt;uD}_5aPjI;E_W(8y_I;M#joW0vRupfM8Po zf1Upk+K2RSAygbh!liiMyMbX5b57Wph81PK%{>gghWQXkp%@B2ls6Se@OjpC@M6T1Vs9` z(WuaH5)kR%13(TLiHvw73p5*PGSW<>X-IRBCLm#vP)PMil}I&+D^eUOf@C9`X9mX) z{*XpCKyduv4{0P51ji5lkUr}v5aH1j#=0h86fj8tDXN0|dtp{zxYv8_2VZJVTI13Y;A+(qbekq(R7nyl9Y)L!9CG z!5`@~#1Zy@kY^ZqmXJ!3-a?KTX*Ci6@kSP;qmf|XaQ1>jIvoiN&d5su{Qoop>1m|5 zkRwJKghWQXkp&J8>3*ckk!}WmXbuSJMx^VIMuQ{LB}g}%9RSj5Br@WSEXW}s%|@Dv zG!JPK(hLZDc92L2q*|mZ6Q&a)SWuZ@AwnfuI9WK^gXM2=R4C2_#fknF zM}^{N4u6ZILh&;Pmpd`IekJo&Ubl~wgu ztaVkD#5nOemL9fQHtDZ}C`8io-M{QENAExvdwNSPlp zCCP}7IY!~;a?9>_#b?V`78h=J@0z>H$0c+gGOTTGh4XL?q&?RmmSE==dNL$ust{G_ zc%2EGKE9rLM_=6=l4q6m-r(%NY^1p(!dbqqX%ijk{5`M< z^@guC9hi;mB z|1jGB#_0YVqyHZo!~f9WtsnNEV=3f)VE_XkbC5MM00NVN?~$?~^}pXIM-+~Q@K3X| z1p7CggYI$23(B)F4r`%gD11h@ywsgF!1ZdRsk)&qE>Qt}(KmT8bs^i@ZOE#PlEH4Z z_5SGytGkQ?xA97vdJ|6zqTWy4s(dD{GtIR1vA+_8o392yN5(c7*yR)WO( zte-?0d7Y{BOQi1`vJNT^=XNPf+X+fV7YUwfq09}v<9{Kalrqtha=AvuPp?SgTlG%u z`M1U&b;q}M58au9a7_jsf+GdKZzk$Dm$Ql)->(l7bk)qYsna33WH-bg@wqXP^+HV+ zrOX=X^8G4v(Nj%@R@L9~DZ*s}g9qq*sFR91oAbX4X`Gmqf<{Mv@;s!bJFwvLsdex1>*Hn0HsvB)IH2WEk@a7Tv?;O~n86VH9cw!c+^w$1WBPRY zq`iq`6R!yGy3`9n6%6@n6>(MqK~f8Wyw4jJZs*6WLkd-|+n*VeRi zLScF`(4S~?WL-twM})p2vzL9gL7)ECPZiSL3L^HQ-<7_}1`;pwUU7wtXx-@2P4-PR zT%oKh*u_9M+B=WYvo1l4I$hF~a9OTaEc=vG3|A|-ZPHDs-j1^%mE%yu@R^~->7ex% z!N6G_;JfqYW9yYkC8a^dpmYVTwhkv+C@g<)o zuYAHORjhb-rza$8)sa?NqYo{x{V+H5Tc%P5Rj|^f-EI>U;s>)$wh?A{7?@Z%sOZ>) z1VnHh4WAg37z2La38sQ#W1$le;o)Et6ACnUl^#YVxzLnnZ5Fle|ii4_kQ z6Zr~>g-t{VKV|e%`{hdp>ETKD{5F{&A*p75yZ1kJmGWJv!=6?>-M!y2a8y`B|FotG z?<#Y7W52P#-59Pn=`5O-@i{!(?3|mkRX$zDE3r8N1wjqyyeUbFv zI+*`%!#*eg7XG*FpM0bq$bo&#)Mo|$VsU;bs8ALaxoQl8JA$5l6GrmR#*#=Lj>6x( zKihxGC_(<;GRRlzvocT)3>C(ps5r=e(Ahp@{bwHv)PEKW$v@kM71V>&`6nil4`u(h z1&RIVPSr@>AKyQDQ0$+&&+?HpvR~=U7fGG9>2D0AjEFcsNNQ0O`EFh%MQ9;oE;1B6& zNQ0O`EFds6h>yk$g7|0<9}VK8L3}ibkA@NsX%I6A;-f))G>DG|@zEeY^cM;ElYl=7 z_>+J?q(KlL{2?6oE<_zDnT0pf!{3HXzMKMD9l8U*pdAJXBF20?uAhjcWgK@cDOAq~$P z#8-m&N)TTO;wwRXC5W#C@s%LH62w=6_(~983F0e3d?kpl1o4$1KKPS>KMDAgfIp-` z5Fh*@9S&&_#0P&!M?)F}@xh-&2^RzY#|NSTA(ko_uk=tw!gJubxw>Gwdd;-eP->5K z&p1C){?VufeQ9b5_5`YF6j@`8Fne1lfq{>Cqt{8rTzC=IOuElrdP|FA`f{v#<4(u3 z7UI4f0(STFYy2hAK9^^T%gv_Ze){R@%B8w?2tV)QLND(OGj7_)U+7SOSTw3@-GgP% z!$T2+SHmUALMQOc^}H52q2x7vZ#>&NJSRVa?$(Hy^Inf~zBJ`(+R;AuxU1Qkr|Z&5 z^X1V;cC56q7%`%69@$PLR4w^5I4%;5SNt`&s_V-|D+%bOE|*MP)$YkK-FFvo*p7Sv zH&HAIpa|%S#zE`6cXZmAxZ$Fcf0*~P@oRu*hdn$Q?jToTilVr)kG-)hc zNe%f?Rlu54o#=>9amVta;ix&2lt8cA715-c?&&g-$?vZynU_7*arW-1%h?y&j;AGX zmEQ6_e*Xx|jX$=7be>8kjBqb{V-{oGwh-e$z`b&QVV9Wr{$<_Tdf6Y_TLp{~`9!>U z+P`?BE6tuW$bDI)r>l+p@nDAV%e5#A9o7DG*wZ5@Pd`$lM(h`C4P=Xc?YSMNdutV6 zT=#vYB!wvB&%Qf}-%_qGP%s#OeI`G2+TZ>phIL9-{{k;&+V2P^JHKG!X~h-s+t}Cc zD{}a=QXah)pi&FFv3N2`?n3tZxyHKN>Z5b|Gp`=p`Pk7!Hi=I6J&NwAq(XSIhIcuc z>&4!SjiF!TMR^MC^~ZRwtHD4?m-0E1blvHf+ z$lUO3Y{GkFcC%Ic`(QzCULqO3^OAlu-{X^;r^d4fp)a13s)|X7jm#W*J+z4KnPGk= zV#)U-np)7l|I_)7!1FfjAIoxa?{cnA(m1WP3=-%RYo`*nQ! znl;I>Hnki0<@_<9%f*NJ z(wQG-?Pc1AYFoOo_nr?}jDPyB^F2k^8{Y4I!Cs_|uO_+FwJKRq2?(FuVp1>jFoYX- zUve<9QW`1y9`xj0c-q44s|HIib>?#hcnwDde@k+iSZbw6p8xplPY z+#4#$6iy%H(fQHKFfA^=7osGLKQB?__(A!O-G}NE(yP>;S?s(U!l&^}-X-JoryPtQ2s87VzX z`o+MMxGscDP<*|w}#v1MS_ zCE^Q;bV|3;yk2)?h@*rwXSU;Uh>G=IdU17Uf(LcyD!b0NH*?>5KCAV`A6h?3wJtpo z3{VX+KY17OKupd0s?FhqDM~kg6Qy^M5Hgl4=_n&s{j_txA|R zKhHeo8)D^Rx-{{mb()3`9c|dL(o#fmdFFK9!3 zRn9EDU2eqoknTKA-goM^=b~N@3>9WjGiE&Rnl5p=PVG#vbd*L`yl(zxm5U@yGm!6i z!&F8kOn4cia8Sxvgg-{}R~Ym0w)4wTpW)7k*OAFWzEy2WIk-!;m|0ci!9UyX*-P>K z5L7Bo4LS8ADW}q8-?o>vqrcwtBZqKl+sK}fspj2W&?M=b?%eAx!EvLnYCdVFI!C`u zy!pGJ<;C2U`J_{6jrNbG@t6`9Yn57DsH_#-4_~|Ky-m34O6ZOrj`N8@m?5Q`4KwrM zSV$i`_oixhB-@H{lsof6*7+;D7{pBv;bQ~$;rEAh2k=jAsVuwYxy!rvSV9*{;slBfU9E6d|+U(%aoRSt_{JIf-UzV^0K#lW>_5NWjwsyBMwC2fDt z0BfJuzxz#x)PdrZ*i_cqjw=i0N-%vVfek5_ftg{F`1{9F0sd+?@1w6fvdfu#*f0G# zL^e#E;K%6Gp*Qv+>a7Z@UW|O6sR@4TGZW>u3&|_Ao4@bjRQQ47oR0D0tRrpZJ;_+SNCVO5Rs3tTp3#iOB#E9;mnv#@548e4Q<_T!c*ls$ z5=v5n zM9o#=#mToEro{B~5%2TSVw(-OHU3(titV0P9bf!RkXC!?nHsT2_4xkDx)&ZBU#iup z1u<1Ms_@h5bIxRAX+~z%R;|-`MLhb{e~-n7#=JY={3RqnS(oBcaDY7I{K|m zPLd{$oa;79v8|XQ&Ja^VL-u3L%_A`X4yQR+M8eeX9pD=gs$?uQY=clWH z@l6e=?(aAUnInYE6;gl;_^^OsAdCpIMS|ohAX&&Zj$y4~RvS;<4aVb#$S-t<%`NtRP{}bn|EoXjb{(stt_&~g~ zmP5I-cK%Zaa?g$d35x84b|Cd5<^SmHn2=H!BrEa;A5z+c2RW*-%27>gsk%Jecvi~N zpPMQV`m2Vfk==o&bxLNCh~M|Sa8Ju;Wttj?ZPPCaqV`UO zAKEE>?`rQY*O;NdjPbej-H#D(8G6_c_V+7skex5mg{YCfnj6PG7&!^rpuIw+jK5Vhh5#M?{v>6l8Q&FmK z@y-hK%Yz@Q4EM6dP;b*(l==02upOJ`6*P+a9$8c!eqBCzc}pOEMvi#35lhT$vU?lz zstW^YV-$&xxH0ZK^g{EwtnjLcizJu*2}04{IG(>2*flyHo+wp?<)cBxID&h_LWh%D zW~~29Dq8xt)@MJ%-ds$=(;v)eKgV<@O!Ikt;n#$kN(Nak0d1L8Pq&f)LVM5bw3Q1K zk=Qr&m+8Jn3la}iab1{CHOurHUYxnK8?<4VOk?RX9i@I3`?cC_n}M?41Y@>-k(jB@ zUDGG|jMZw-=oFN5*50yg)=d^X`b~EqpD6GidDqQ?VArSk3j)9PY97BOyZ+-@(mMXc z3HQd8b%pp1+slS*+?UnA#HG3LKVTrcdq+(5Tki{Dk>-ighLG5IJr4zHr8)2K$TnIA z?v4ps`WKTDaEWzOm3`9bz4QA9dXm86I_-x=bHR^x+==_?rHa&ar^k8S!Uqap)UMCQHN4Sj^-jTi ze4)Xj?lijfs?&q>>hsxmMd+Vs)wt~NDkcc?dPM~)y-t^GI_*F7c0oHY`4jI7wh6j4 zJ3g^fJk#U28y7!Mxwpk%yOo@Jw?k^>Ic=x$sS3}dOyv&?zZMnxCj!~$2y)jZHo9`N zzR(bGrS1>ZseLn8j%Jm+R;jF}lXSnIP;i3zi_2>=cPw33rFQ36*QHjo-CGn)J}}}0 zmo~L)%hJdwX`P#ovsnq7z%cUIp-3g_7?%o(c_9(O@;v5~iimfG{SQ+2JMw0Z?B92m zu44@xj9T*y`h6{>wP1Ybd(|$FpL@ukB(>oV+5=1SZ=t_i63-Ps{{2S6;Nn6qjnpj? zvXTJjG@Ej^4wepDW_8G;XdKpy=_U_Www(v~lTdlhu=Y~&gxw2T|EOCRr?#(D zU1GB1dv8B$EaLTxnf=nmx>o8DS7r2vm0my2sk$*aFuiiDw~onv6I-`kn=dy);aH7x zWv~Y|wL2WQwAb90sRg7J{)Pf-1wmf*IMv0CZt)d=&~p{ z%6wE>#j&1UIb0iN3)*UTNLxK%Y4pFxB_1AoV?ZdwWP@%D^DtpyM(?9KpVp5}(bnI= znK#y{`(=#+{Mk!JDOExWgJaX~IGQ|5`E&*OEJbWDx#I%MH=9jQQs)*&gOKW1abari zk2+gY1muWMlC^AUh~7UR75_>3>qp-jmL6eij$(c_cU1SO=PH{LVRN$ExY_ew$(Rizb~hpp zwD*|iPH5z!_3HUvN~BChKa((x*E16Pi;5ExW74DOStM`Si zKR=S|Gko$vog{QNUB^T+@6~E`Hcy0DAfJcz((U~fRE4!`hUUHtWZ!XJYue_stBDQ_@+2JqEJA!7W8t#iFxL^!PMd){XMaZw>cBuHiNa41$qKut=z4H=fn&S;M>2OlcFEKKWchZ40t${%YJt|8eij_nIBmB6-a#z1Gh2cQVwXX`jg+ z^Jzv3i7(~u5Um{ed>f^{%6}Z?eD6D{=sezyWk0pr95x1DrU2%Tw@M-|q~B~Pr#qy% z=Fjjl`l&!3Vb{kK58cQ|@;JB#L`ByPtWjU^U@%1KsycQT;nj{{a5~uDzf{8;_e1x9 zG+s9Fz^yxUyq_O~?|T~8PPfqQpz_^qjYr=4gj>0Lteq)?g2Q`CR-YDkow&8^FAp`| z^XU%x9scx^_A7gjTb`BEA2*8=BOiNx)DnICdMR!!kC@{gr6^Z8j8vFmD1dObXmDkd z9}RwJ2qXnUtO3^Tpn<<>b}xI4n^uE;J5p@L`(}l`Hkzk;V~+jzsuJHy2XDno4f2Jm zR(v!jmfaZfwLS5(UnBQPBkDX(J7j$SY2erS%t0nkQ?pw?R;Z5Hal`$iIG*PV#N1lA z)aBHI^&lr!&D2dXcszGa1mDgq!aiSOs?^WUf%bXwxdY8753N1$XTI-guGA##-_&T2 zX>fRY)9m!g_3>YYp^?3JcHcR^avioSkCN5!$#N0PNxR2Zu~{FI;PGDg1zM5#*nGX< z;PW?`5-%%1>mJeE#SbNmSEwGyFnPd?QyoB1zqJ|18ohMk^U#eJ|KZNo4;IAcI&aPe zY%<&yk7{nc@~#X6zqiVq`JUjNY;Mz-i+?lPYH(Y|3|2ONVC-Hxoyd z?SJRQL)|V6q*46#Wru`BdM%V7r{Dbing$ueHr-_@?`W3kuT9r*WpfJ$2)ak6 z)K}g3X-JQKmp}eXhLf*}qo;pZJ!5UQr@(Hz*9xUl=}+szfVvxVteF)86tzhl#2?-f zMr+xnSQF_*rmo|lIWeLO?m9Q-sIY{YP2aTsNZ%_FWJ52k$#;JtN`xr6G+o_efN^#1 zU2L&)%$m#hpxgJ4i1~DMq$wrdJuo<`;I2byKdiV=ln;0|7dIoOvmopxbi}p z;Mwgjm=zhjy+-jbNSuzi)(IFCT2?LZa65`!W{hyBJx%`TjeYz+F$?dRr68@2anlIMNd$+amrWxQ>k7syv$GPusZ$Jg0Ts6shyH~y$bDRU|C zW6^L}niVbO{1h7nSF?p^c5J>>v1_pIsALto0*U`Fwe4)~%YsCPKr}q_%NcjS-Z;16Jz&K(m+J%`Gxi-Y#v4L4lVtge79a>FFd)aigC>)K2i7D z8rC-xcPq9QXBk1Jj?V^qGq}NRDh?+V7e3lcEt0P8c1p$ZjwIlP^&K!dG0-Bu>K z_HCo>r}|ZV0_8a8VZTvPg9)^|8z$K#YPw%VBER>yF|Ss%HWEgEa>ZddD#x1pk}Y#V zlu%zYCOFKrr{4bQq7u=p#QS)h!ik72<;mhonU0D)TZ;xQPZr0V?dsJnEBk{|@6oP9 zVRl;kyiYM5Or!kWBp4$Xu`JLO%FjP|C(WIB`1Q3*^TvaV!#VDx+GFQRmNSd@jJ+4I zn)`k3ja?0xQj+N$rQvmJQ}v*ysv^7X%JO(?Ig(Q1*MZa}#l)o-Au_D7EPL&@j;UVo zVI-9|H{H^EDxo_5(;?zbo`8IacTd1j?d03l3w0-gt)jen#e@y>Cw{!RNyC*(qh2E4G;tq} zm*f|a9i$Wgb{|rns_WAka!R_$G$-o($s)}`-&8xv|G~QZtE_oG+*uE!bq!AYkV#M1 z&z((|Mp?8^U$4xfTYiyNbLP6p)EWCqJJ|1G_PY_W0J$88kt_oA8>IK%pfVd#bq_I6 zykNgJ8~Q*tRbTI1pS(wj4QCbF{iHkHt#hqvSz~?{av0&EM(FN7I-mEv9!1JdtM20) zrlVapvb;I-OTNhHe9P_^a;a$EqRF3mPS_Wq4e$75gxFLwC7S?Y2 zgZ{uY*fx6ioHAS9%EPD6-y8l4T+qEfqY?fZHF&ya+2(sP8Cj6A zZWQ&Q?*)Mm4SG*prymIUFtF7q(Z;@kUy=rK2`@|P3oO%1V zP!G8Ys`}-gbt|E(oVO`XF0U5Q>;=D$>N-CYc~EIQU_)j4Du6Mhg!K9Awgj7FPD2LK z52o|y9?I32yH`8yhTIsW<|6OM3*-2m$=mL?(xr9%X~|oG&qY$$d6O53L9nqgXfZ~+4watq9S9(>$1{cG47L`$6t30$0Mb!aBlY= zyfl8gEJYJk8o%>Gk_g`fw_oFXps~2+aW|`I*jUO+!y~JD3d~Oz?sQ(R?)H5*+43p8 zpKid?igU;__IJS6BbBF*Q)uIOOZzJ020w+_HGS~nN-{|`Hys&+pY}ML=$k~s%w$@G zzjJF~$uV46UMgt7>LiG{eLE>5&OjIImsj9UQJI(*!_^PgCZo0D7;`&9@6(d732uZ= z&rt90jO7FiR%c_1e=$xfr`*P!JY9=LQJFDTN!qFGi>f{DZ(E2^ocdV7i!0Nzm+~`s zV5MN1Pzlv^XLVWeG=~H0NBF03lJ`tO+v(?uJA#Jug^W(Z?#`uNmH*=J_~=n!aMlsK zX&3pYD3tdvIK*_uZ&I+lcwAM5rWFd7hH*T}*@*c33Z5CbpaQoK82n(ORS%ei6 zcG%5Bsc0caahdBI%lS5fmKpuhV`6Icbo}TMnFQZaxvGMq(*oz0vhVG?)Fuf zaGbcie`At4&2c)u$Lw=Y^#NDz!lbBl=T%4TxL@;MwB+JYh;|ZejK%Irs>a(Xhco<6 zXMp*#u>}{bIlhoZUX4x-Pavz3A3phn$Nixo{S%j7YvE{ve#-K!!C1SEhuwx2ezZU8 z^EWxgoYT=1_SFkD%DXI2ZuyWsuuu)!R4j{LvO1Gw-W6hzD z`^ahk?n}|7@ZhN$Rt|mx3yC2wJ^Cj@fmiMjcA!<=(=a{Br*qCK&mW+CPLC->kZV2f z8P_W~_inAbgW%ffHvTzPmL)N6{i=&R)>%BOirXRl3~sS{7&`kgc8ndUqWe$I=L+fI z6bX|PKejv<{nOhxl`}`h%x1ILze}(1!fC(aIlk=N$=VROAkTu6F{xvvA>V5|?%Y~> zkZ0q^DH|-FKemOJ*>P@dT-kC=|FR!&SI`THf(?(#lceHH!R)(;6A>861;?Ddp6ZPltjv zG=BN%^bfKnVrMFbi>PWIw`Fa(kr&Bz)ORRJ=+f46JM4_vqU)9VJMn97afwk4F+~cv znc*IPeKg(6vu^SIkr73hw$a02$0+^2=k}Z$mNYTzFEsCsSV~6yTwo)Nza{B%X=nwH zz%u$ZgSXg(dPl{~y5W@$16ah<6Mo~dgg?XE0P^J=8 z4Qc`*O+*agvXN~>$TpIP96Pd&{ALHz2mfZbk^M;8A)!ahTk|UKh#qI9J|hhiE4rF2 z|K)2@B92l zyFsN+mqe8N?mDRLQ|j{lj=76LMIDcc@vv6-lJn?aIgv$6t(Hd~o;u8NcW=fLC1=LB#;gUE3Q+aqXlmKpXUY1TLG^8pWjV^Ev$ zM}`&17KFSu2ubkHpZmim-NNA{-tA@`8;cjT>eb}4 zNhc5+j_nu5%f5fHp+Q^HW193zXUCuhk_AN z3geq_Dhi$CPRUe+n`u1>#OMlIJc6^{#_x5J`$PXvc$gO=?*d zIHndRA43_g+gZ+BvldF%8sRs&M2JtLQcE?VkkV1`f_d8J`On{61y>hVax}|xntqj8 z)^Xfrd-EuH@wFgdS$wLv%Y8)>!Keem?c4k2#c3)E4$q$1@g?#GuP3cCR4wSB_%7e! z6kn5*Ioy(QF5kcB=Xq$LBXYE;wSRJBx4z@cR%okmuiQZF*lBN6?AuY%^uUi>4bn3( z(R5;afxyxDcfV`+^nFW-4s9EeWSpRwyWO|U%Hq8@$~FBB2{E)kk+f?{ipQ!w;8RzM z4@NPQwQDh2R){pXX0EK8DZ{|Q!#TsUi;{I-oxy@zNsC7OyUv8jdB1e|bY~M8&M`{# zhrJ`%8e1uNr@MwkN7~gIBMa_@I-jQoclAIDW`t6_p^hG%@_wc{goS*-M13t8oCot-;unj zuPQ1HEzdoceo47Jl!+&FzdN#)gvY~-(*@Uk+?Ga{d+$AT{ zo-t{O@#X>vn#PBvIQj~2x^+}n?qB_StbLhpmOwqDxnG$jG%`5ZQaMap4aMt+X^Y=) z{8)#}RO&8|g)pqx`P*&RG}B2f3{5C+d(cn|xlYiF$!4KNGFxDoVlm)v(#;U-7Cx3( zsnfe4|BO^FE__frVK9YVy5Ep_X@inq|6qhCkh8e`gN#GVIR?JIY?Sq`0-drkGA);( zUT4Ai5Wnr|mfoBtzcI_zSG#QM)LV<&3=IXlCD}ILykoh3zE5BIjr;rUoyLX01p5x@ z@WJWh=+V3NPj4oNX4aL#ZZq+tsc5i?{U26>1g5S?WpW&f5r+mAdT<&j*({`iJ7L%qm0 zk^|dkdyzHs|EdegLk0b3{orzz_mAH{^`M^lLcPpT4-|<&(y&HipfDqAs1MfRXZinZ zqktjX;qZtUJTk*0q~o0A*@toRVg%x8$W^>O&{-vM^9O~qcv6o9aPGKV-CSYeR`n+z zlpL_rZ*(D86RqKS(LAdY(p^0oEq@MIy?Sz#>Ji;>;2oq^qz%Z)0XZdLfG|P-%nMB! zko`T`^S{qC`sbX$A@;`)ilcl6=RMH(zvmSs@Bht^nEw_M|KCEY`L~dE{aZ*I|1C(K z*#J~J44OwOM+}(wo6g{ImX#X*T~y@;|2C{2$5xW*QFde>V-MJe%k;7v1*zfMvll zENW%JqVZ$dG!!j9{P`B`aP}y^Q|MH5=tx|oGr?Lw?VwK3Fi5ER&$y5q)?6S85Hdc@ zLisZ;bcQu@{+9wFk68SS$l;mcHO=KJkYXxQ$1=v=AHEbhe+rQh8 zL}7+)$Wb^eOM(=HJjj52cmzZ8V4E4)g`)6h`>c+$x{x}d4CKSUKR$oAAsg}e6A#M% z@kPp?)o~UN*+$ABWs!JDd(YaY012diWDWH~0%^xtU5Ly7+IN0Zk=7P{jA-)C4!GSrwQ>~HDHn4* zoSdV@!W?23v-L2qWzy*$(W6PPnoD17j3BCXJE7f(0Dd}=T0-aq9Ci>meN)$BO z?*uPj`&3^v3X0-rV8NaFN&fKTfnafnx4K9Ys`_`8q2^|8k{0Fy*Fg!hi)fj8+J@>>rYhcm!i6Y z3qMMPRdAo)L*1DbX)7>sX^depvPDZRbs#SIur2dMaJnpXhc{Z#&6N8M3#Eq@Hjl6R z>?R3imX*-+KJw%Ss}05zQSRGA{`^ZRHy&@1MvysB@x+M{jWMls=FHC}r_~w-6hwc&ZtcZNciI+}o~+hNyMb5P zuP-EUZCPXT0k?3y>(18=qxI7cQL8QP6Yq;5+mjTR&6jFyzEGHxC@ql7F~3>dR$VVG z$)nVEnI6>cJyQCn)LC8Uw8J2yZHvj$ub3IgIMJyU6YHd|xvXgye1k>!O zxL z{Hwr)l;7@}s#0>ZdD>NPo)R%NQGU8K^tj`ZSEkiR>!a;&G^1rk`E@xnU!x?HmV-nx z`bsZ9aKqpdSY0$prefo3wY4LzFIHC+)%aE86jyL9_NVEzWN;fJ>jidh@y^#W_x)?X zQ)J;7q+dC)G@GJU>knlqm#4dPx)JRt{cL>L!(O3!&RpnWMF1)4XB3Cj>~^$5AAbB? zzsklB4KLT?h3Pl<(vnIJI0bjU+!I3|2|LDYu}EZ=jXYe(BfRxPhxR@8^|u#qE_J=o zvmqIrBqt!_(S0{C=A96s-@J8IrHD6%W2GnLu3Rxn5d(+~g!~^IF&Ivrt^c#1jv@Ou2>b7O4SAl(K*)23oSO|`pE1Z3 zgxH6=jYsRf-acO-VDn?(V8`5ZOX7YS{UuM^ ze>i4Q?`xz^kf6`CN~>2=VsrgWX)nsW&d(0sZnP-e#OOd5J)Smx(~7!lvQ=cq5*E+Z z*~~MZlSqV;3omZy^8Z`2`0;;UgZXFNjjYc`wf{GsNPUQqrwb9%-H4E<1rhR|LWGR( z{~6;W?=j>$BO=_t!h{c^@MBi;IrCIxVO<6qmyaz(y};TZ&nLb+4@lj2te`nq#CYf< z_-ghEmrdM4cn0t0`V-U0i3esMJkdI&(jfJsS~tWxK?T@XMY_faq5mkA~E(rCD>17kwGUBJp>04DGH?(Wv%t zvocg(A&xVC$vHhHag>@uNZWr}wd?RxL^Xy(+Xk07+iQ;9;@qh85vAH|^ALTPZ_Kxd zPmjapJda9-y!i%{1FCM$zZ73T*D&zN{o@Nd(%%dA{GHQ9wU>vFE|r-(D^}ipPI2iq zRbD;C&&_x&osX0XzZjP`7(y1=a<|fb?=5QwoY$kH_&sxr-hne@&qL>G{XV5pOHcAj zQACxDX5o<7!SB!$o>PMG^8DJ-wxdbLk7?f-i>*0b6qOZ3AJVj->tlX(+|nvLEFGhJ zb1C^*zXSR7`f5X4m+dicdDb1_k9(^}pY)cf8CnWMc1+l$rh>G$pD%o`%VETSI)E7< z@zC^sQ@E9i_^W*KPGS8xjRhx*%z&&{&<%~o?rb*-u>7bwIU)* zN-cfxzWS-6bFV~uTLs;5{dk+sNY8wECv=gxrct)%?Mi9cPBmmpE0; zD;c!vE_^aE>GeK?^G@&cBQ?hN)`H)asQl|oYoy%C1Y?#NLbKB^unX08JnxG8@?jhs z)AHpjX4;O(?FXdcY%be5;+5g3))Dtfxi$V~zaDCk0VeeGVqV zpRUvS#th#7DEL_xZSZ8iY=!xTDUs%t(h9dV{tL7i9k#*WNj~I_UpzndJoo|0rwMP8 zoILDOm(j+DWMzcTDa|5c%69u-M;1Q3=JYoZfRD^GO4_ePREMyP z1ll6$%g<9n)+H=IZIR7h?IN6I;LvX;UvhpDdZj(ioX<|f(($3zqj$JzeO}?^x*^@| zH;w12yCvSxl%cfm^bbqu} zsd*KPNjBu(9?^@@X-graHn-OQDnlJJv(w5J_?V#2?Wa}N)`OX6d5<-&_3Go57~{`d zm?cSO(Yl_0u^L)3kzMz4oO`Domms@|w@L8uqISoXiOK+@3atZl<{#Z|&GVh?MUk=L z4)P*dSV{DQy8TW(eQoW{W-s|HFyEkBGqf&x+-#;{m1O3MmSKPE6it-4J2a$vE{Mzh zmmcw5t+jg>pLEFiJ`p2K-7V7H z4I#loTFF&#O#5rfr%szWI^F~u=PWEMgVb!~;BsV9dnqIVl z9NAIXu-9rZn%Frll_5N-cKx`Pn`ORUQMHxEpayHvR(FnUvpT%5=SPQS-eDSyLyny- zo3b?fGj>wb59R2!6Oh2$;I8Ix&O9ww39;?MHG z`@2vBr{>LMAAH-2p!urt+DfsAXZBpl^Gdt1CfK_0Z5YOF%-3}Lyyp%jVMD(A$TyW( zA2~jsE9DEl%A@pPdO7A7=G8_`j2ej6cU_-<1yj3h)xOP`>YPsIXs!IaSGR*^NKJP5 zJGg-+lXg`2*0OYTjM|kLHOf>vLpG+4H}kSk1ARtKV1+|)X|#mv#k)4T<@a@@VWqZT zQF`#!lm)-=m`&HJ-^;M{QNTuTJ#}eXdOr5s^)U3imG76rulz*w&ceJ3Ze_xAjvPBR z_1X-8>#-Z%VAMg5DKsRKuwd_^w-xp4Jg);Yk&Ap^P z0^dk9+s^=_>b!sXnG`E}{ZE>yw9soqzu))q~pJNo2_?wPKi9F(sX}UaDnUCy-LN^B~*4YX)Iz>qipa+wnK>E z5Z=K`nj4>j@WUZevs>Acjf0=~jz(+5Ezv>O9X#BKZ`G=#Vw)=hqLe#R^lk($`MghF zMZI_Y`MXKRpNS5-=G`p_gn_0;nerQV_cAvN8J!83j$2Pl#uslt?IR<`oXQovzIKi4gI%|K74mbmt? z{c<;gAp~WTc7q!qt22-#G=-TmJh!v1|CD8cq>#@8+1Y_(wnBo6Kfb8;u2Ks1PYBP? z#9sG5s{HnOH4Pk1rK<^auMpu`OGcev+vW*8ee00+`P`|4&R%DRH0*T?ISHx2%KU8l zoc@`A<>@bV(;>I@ppt^_&*bU4Zz{CYxK;=+2Mr!e8MwcjGE-(#=n#k^Dcq88I%}c5 zwPZWd&mw+S>|dF25D4=M8y!J9=HMN?%uNi*FD<$Sj8T35ndqbB02{~k_Af{u?6xsl z7&d>sv2e;yB0zku7(-1=c!bkf7F5zCxq6P$S?;$Jjr5h0itbfoEL>8PsX?`eLfi~i zd;DG(?A4*om@sb0+EbHOZYGv_xgVGyG+r+7~&la)?>AIm7POJmSiz5ppJ#TfG5Gy~h zELh7|@4t(++CWA3{~b7JP>pmljUS69&CT9J>-uASX^v|BO|%DQ^sexamE6w+{mXhO z*cBwB`jm6*7 zhw-IVGhIwGn4wSbX9MG$CIL2P4d#!>5e(Ce6UHAbs>Jldv5vG8PDikhWGJ~JRa?Fa zFuLieAItM?x5iaw{Ef7pu|txvZu!c;l3G@mS$YzeOoSJxCyvv1oNHM`lvneueU|5> z;Ua$TEZ;StNB?qAB`iYv;N`KroyLj(qLB8*vW4R8y*!@|#^3VO|9S&J3qt!M{+Uyo z32#{G2`B#qVVtqZ565qU)woSOKYwd_;-w}!aJ>1l`jwl_NVI%#J1Se@b_@mcwK$E{ z#y}K8Wv}h5Nr3lrcNOAHb_xT_zr*W8pSa*)36|_a7*hr5Z6ZUXs>CSqgN1gVy3q5? zHDvKk&yYH+{#fYvp28{@b@RD;6EnI@L?C-=V}PJ)_*9nwOU_H2nZ&90Fw`PtV_yR9 zCWg8vSp3yrJk=JpkY+yp*S4HBD=wCTy}ECNralRHK0AZ;EgClS&G;3p^Fb6o3pa#O z*(Knw&sdfn-Y#b!MO?Wb=3@)9Tz0OHhKIhM44+QjXr$jr>ja)E8SXcE9F5k$3}Y(l z;O~HAsYkpdy>fkMTbVAU_G<6oPYt8GH*FlZf*vFk$jrY;r2XznwIot`G`0<4FFH`a z|A51Oc4^C*{B53|O$w(kL*9q2mggPK)M4Y9^8E9LcF&3&&et5XCiRP&M7L$axbL@S zH6|O{AO6(;PDTzsLgQV?Rn%rpDk>JER(da)^ZWY66}<+^1RlL`&9V0t%+TGljR(IU zziv!BG3Z>p%8(v4uFquqqR_;UuEuwVOqU7lfjCKXSFrF4GMI3RCr zu)-<-s;ED{4Svs)+Dd0m~$-f>?1^Y zgeTxR)EF>m$Z%+gaPa6@n6O9~$l$3*C@?VSXivc7a1l^3;E}=8l3-yFFws$wo?w8d z>%ybJVIe<3M@GSbMMXn`Lqq_N;ssCH!GeQDz(j*Vg-1e1!9aw2f`|o!i2@6r#0!sx zihu-ziinJb_5>as4gm!T0~Yg$YPD{Mz()=@_%MGyV{yN1H}VSGR{~Nc z3+%y%aPyvvO?Z4MI&kg$x_fykJ3=S4=<*ui0)V$eU~91zEfJCs?Lhitk+ zwZu%l-Npn<$YQDfr0146IOaqPXQYHOG)?g73Q|PvWt&OvZjj6vI*r!)w-b$x zpdQjN!%*CsVXs6_k0_{=W*yFpkbqS@g2$TYyXsj2n34g=(T&k6$%MgG zPdj(3Qztzc4H8Raw?Y8Vt0A1e__kfeHevkR-~FmaJFSXs8Wk~G35q6iEIwky^e3NM zDT2?jE~^|h*!L`xHwj}@^^$uuGoKvBelGKI9?HmQDnfK}3ac#v$}W-NA?>^H$e0H0 zC;DQ7zqVhV=GBFLUAYp6dobfO_aJ0egA?~acU*GlEA_2d-x7x%Jl1}GcAfsIkdcsu zZSl`0moP;}izd2{A@A3>-}@y?K6^VE$5@RFaO=#*NKdgfNp_n0#38nv>j@S34RW8u z;(Zz9jdg5k-=5v)$c|}8yz})%)}`I{p74w*j61?H8Vp%;_IVxmnaJXK?v?Q^-;<@6 z)gAGt*gY*5f!%w#Z@gNNtEayg5XsK?#<`Nh+Vk~inEu6K+SCe^D@Oi^*n1ctam3~r z>=nAN!7vf0Y&|z5D5J5>JdA4AJyD_}vD&iP`a8~~(Xy&C`S;v_C<;Pxjvox$sf!d4 zYa^umd9$!a5Yb$tHK+NBEx7f$xw$WleHK|KXGyd&f%G=v0{e3WY1)kGl#C9J%@&0S z1j=PyekYcX^UjYBD&Z=h$dL{=R;Og$nk)TRNJtGN5n`Py2*@4Gt<}2qTv^$W_e5Cs zQ;w^bi7HQLjqXzObJPqbbgUmj(t{9sIeTQ#0`L)b!yKd8tXqoA;8OS0Z)&q6a30@hKPVrvZQekK*uYnybDHZPAlBlWp|-+E-6$zL~tl?C*#3XWo0+x`rGRMi~Yb78*M~WSolr*MU~} zzCmZ_`b$Y?-1*W13F%YQ#G8$6TDMX($I=p&-#UBLx{Xw71rxMgW@_zv@YUfaMyJq9Dg`fjwE!vmL%`nV?aRg7Ylmt{1mU< zf^*mROYA47ws@9{n?|m`_0N3L5$(4XU0Y`v%48$+<8IxMTJ*2~l%oo&u-b|XpA-w+kUpZ7CTJ67re?C+fTF1Y-|2Fw=+y89`ss-@K0T-7}fj?4e|JeefAe|z$8 z?k;eFI?EHFhVld`usi{(G5?L#L-(cx0iOZWfC~V0-Ejsa0dxSSfHpuTARbTvfX3tB z0-$?d(EYGAzyhEf01Y@j1v~%@0R#YVfDiz>H}wf{3{V9u0gM1c02Tl=?q37Q1-u4? z0looV0-$?2=YSD_BmlaXU<23!Facx$F@P@sKR_B_5dhu)`U7|eum=1D5CN0`(EQ?I z00*E0fCQ)jKssq506u^P0Q$O00j_{S00}?^@Erhks5b#G0Fi(U0Jv0xxyetSR0DPa zSb#Y|AfO#E2H*!M10Dfy0Eqw-05!lCKnkb?)C2SYDF9l4CEx@Ax&r_1b4de>4CnzI z0Db`S0C4~bzy|>IngFl?ya2&~c>o!p0Z;|72WSBl0A~Phz!5+Spby9g>;bF*tpF}S zCtw9Y4)_aD2P6YL0f>NTKrP@m024q3FbB*6iUIEdH-J6>1K=4T6yOFB0jL390ek?$ zfLOp3pa`%5Fay*9XaHFNN`M?-0B{e02WSI60vrJtfLp*Sz#l*l5CfC}#sN5h1b_yh z39t_k2Lu690at()03LuHFay{II01SAZ~y~Gv1#|&)0m}e1KsLYuzyt6A zTmlpU<$x#vK42X{2oMF>0Z;%gfJVS1;1DnhkOiaz3IWUjW56{a9PpnCvlsMn|NnZy zf~BGN2OT5O3m$#Qzs19eebpK>6UUeD^J9R0^M%n2g?#o;`2a#5qT7=@W}%q)c4Bw8mXyJpBWCYaDE5i3~idKIw>6LhJUqwi^RcQQE=* zVi#(9(TCC&kv{teUv-PCvs3B7DqDjb^uw66dYs7T`|H)eQ6dan*A~7!uxN1nvbK2A zIR%RlU*79_;hn-Ngn)i35M~J5ZbmzlD?1;UkGDGgqQRr{BaQ9BFPhmM@jKnR!-~P7 zfMVvLk7*K5Zm5hu&ImuCJ!+uuA<-k&XmXW$*|lgQqzxo`d?~Je%+o*W&d>VA#L)=G zAMAfqJ+WG~M(M+B)Bm}ukGR5;;ZB+48C69@o;1K~n|d7;xC?`EuCJssB;E*DD?f-{ zdVJQlD_9=E=pp)xZLtnU#UqE*pW~0^Qq7MsY?UFHzz*Byzh{gX+lR6#q1{FBjY2jS zgpC(ko7PIFU4p~$KMbr?6L>;wr9y-T5GOuL>)PWs1=VYwZo8W}oO$jXIxRrN&1xZJO!7{S*kh+(Y1Pi?Hz8!Lm+0QNG}~ z-a6Xyq_b+y-|muYS4C4OhudDDk=;EkFiiS7Nd>m@*h_s!3^{{bmKiZsS-x4#$sL;o zrK{g$DCRsX+|Sue6E6DUt+QO@zU0-6k z1Z3srmMQ8oJM0=49D1z>aF@THX0a72!4eE(eb^+j`Orj9{kr5LjbUuQgYwaqU0&a_ z9r4pihSk~)g-XjGVk8*;=3(xy1P`=3At;8CD(0A;`u^ri#gAUE6cu!9kXMu(XSa93 zr2zq4lM%qR2Lbdk5Wu+>0i3gdv|$`;MtE}gzb7O7-+sXP04Cv|(!K-j^6-ZadK5j8oc@8|9T7t_ zN|JmeZ&HR(lFRxyg4u--mFz;8RT1C?h!oWF#Z}KD!wu9$@n=&^z)8*#;0m4W{=GVmNA6lW5pGXK!deT zk9!=fAds>Y|32ZzH^IsAR6OJ96KTZ|Ve6lNxjPVJ%GBsdkPvUWkp{z z>)nP)w6gi45@ddQHLNjP{_D*Bi(!>fvDW$CyoYAx?%ek8iXHX^&o}#oXcx~$Osca= zv~vT{FP({cRSp*As5lC}-ZqVXm3)3@S`#j&ljl&X&yxM2HX_liefhpEZDLGF>cy0< zNT|3>zM~R8YtBo9)0r}PoaFCx)SOZE@0uCCKW-M;s2;BL@jMV?Si}Z)Ub!H>1}FYW z0CYWDhXMUR@VRgUH~_mKCkV=*>*9A1&jNo8`2CuYj!r_72pA<~?lyf1oGa15#Y;#y7s_A`wR7p-h;dy z00jVj7Tka);OBxeS>R6tG=WbKa;Ct31j`M9Tseqif&C2ZFrWkEB>`ptsO{#0GN>Ph z1N<=HLv5W7@Cua2fc#4U^ch(I%MbFg05ebz9@q?!!v$sXAco!>w0$?=djWm};sB(e z{1#ZKJwk0A>cf5l%R==Cfb|1$Ca43=*)9b!JMaa7)c|=z;8y_~2)G46?Gy}NJ{bkz zfwFC21Hf`nJAvw(0t@wpp*eDG0BaCK$2_!u??8SXSg7qm&!2(XUp4@G?@-^e5%{k` z{WY*03t$_>P)Bnd_|*WY9UXuiKgdDf`#vBT0QFy?;}LpaMxYJ@@RtFbpzIOE3BYoK zSOfUbF#;VQK_LDLNC*BLuu;Ief--1ZBcPriuv;L8<{c^kA8M=Uz|R5xG00m3pz{k< z7dmf1ZSfSq2+FU4Jp}m_z$ZX7pbF&L0JWez2iQb_IbZ`23Cf{mpyLc`d(d|_4Xg~v zO9S5y0DV`NAod6OFTg(o+iV6g^nE&mTsMftfgcEf&YRGF3WFTfZlUe=0HEJ)(7eem z00k)T0WlG`*SZKX0pbR=!83KesZU|ZqkOlJ4xyS(6SHMEwmjgfy_|UkrH?XrH zhR#zVz~=@z6#&$J%%D85M<9O!tSDdstlJA%=-A8!#DY4|@d=&ZpksU%_OfMSZKXa-UIkTz)k~O3xLie(Df}0_|SGffO-)iMh6xF@D}8tGN@fxf;`l|+Cd)r zEqo2~>A0MK!-1M<%S(05e~ ztUsv3jVvs@Hpsj~gTcX`AI@0VGl4ZaJx;k?7s6Lv9Vy&gkib=LDot&wtw5mHGl0}9 zEe|hmp~>Z7@s!P0_bHiCPY+zDxh$WdO_)GE4GIf$ZXk8;MmOzRK?GCbOb^~< zQV>oeB+3PghzO63PEKAOE8fP7D9A7O6is<^g6kkK5;@XTjol$PlDQZUi3q>57rqx4 zkr4m5m;b!G8@5wM0YQGfi)L-QpKz8BOG=yvLqa?_9y2yEk~p0jnVDHu15@p^U+|n4 zQ&K!HjG^#kNaPv@0|R|#f@*nkka&>}jg8q{k9G{DY5|d?aEPHuK3|i#bfY5nf97lOz;FCZ+EGqKuVbnot zbawXqXx3tGcz*urZrn*YG&JOman6JFA(kC&bqeFeQ2bPEWMbmuLE&p%B_h52c+PS# z`iI=rhtP`$hlkxYhA|o(ju=`KCDMF0A$9MnF7d9b3#O9?hKKv4PvAT_8a2{fmDRQ; zh^Kn03ulu02{rZ10MRTgIM8TeX{n#U!^4{@Guyh#@VLMsA;Hg#kS!*~QDh}Wk))f+ z(3sZ7OSWzFGj3_CkQhykQ7miA;AkZVVkb_Hk}oGmlcpDh(B&4yuoX{sVowGI!34r# zVWG<@A<5&Sk`izB(5^3zr_4%>CC}nT6%~F0{tk{7laswRTC~|*gVP};fhtQ!mDK38oA)?5 z7&q~(hwoTM1x49KS=gs@6nzv2fdKz(MC{r{n%l)yLBPXZQ|cWLf&f2wXeu@w9NZIA zd3sATbq&A%tSXFaIBP+YAi1?&L5TzcEir?d6Qj!M;!v@m8&`<})qQ~N)krC@^ zQW?7|33+JA|CgFwo0sVyS^c?}@A;qWt<8$AOkIv5}4}uGZ!V+m4%t zapdAS2a;X}<-SZq^5@Dllug!nfHn6{|l>FIMr=?i;C&<1to@U&o2 zQISI;kV2=1NEW5l(A7dh-~vMuuwuK%F-Q5(#Dry4QI+c=gzCYm;Qze--_Jz=r-%RZ z`rl>H&oUtRObiu*)5sHe00ICJ0F56Z15f~{0B9@>8kfTY-~jLdL;x}XG+sytU;{wc zZ0I^C2hasr0Neo~fC4}dU=slQzjqkK7Vo^@p1yYORs@O-vq@-pQ%+g(=*?255aEYlOKpGouKca#loekjz zn#rHef+LGRDv`=!~}xzA>9v4xDz!<`vZnswhhw%NcwO+ z32A_Ex9cH7Iv~V4NAr*t2zH5;KBNc2O6_V0X@WFQedUF8K?WzMn;~rw-7*<%NFU_m ze*7w=5mKz2xen=sSUexq@cNW9?}iDuRVGRX@{6{5y?UN zA#P>Id60(4fruq3q$9#iX+Z~RiR^w0UV-#P2z>i%Ax#lBUAIk0S0u}fac^9bX&9*+ z{P93@_!Q;-+St6`(w@(h)1LrMzl&C9BDYeTW5mztgZX`a7O0gNH|_B0QaEHv09x)*@Qc{g2k#|_pmx@Iw#u+3*Y5rUA~!4 zeBI2bW@FYd$lcYOHgt!TPU?kC9iCfSN(e(EduC`VFsg@$?j?RHo9Jgfs_=s&7;g6I z`{(Cv63*gBJ-Iu!ZSIeD)5+hyM9?0>z;2+jBm{-+jPb8DWg?jU{PG9pyRJYk2`|&_ z{LuKQ#}29UoZdwUr!hUx-p8%Cxi9tkU9Q@<4a%n($AvWziVTSf|4s>d=oI>4+Mj+} zAs4n^e8G$E&13F)H9R@!yYam;qxy#jVt*3$(MtubSK9fC*f{sF>26yNx^%bVjTcvK z=zVZhL}L-QdG6ohPQO>lu=r(*X+E!)d?h9AYC|UY@F&#O9BF%IvsprU8te3iS(KE? zE2rM`U7NqL!TvzX=`WAM7$wb|i)CV+k%|({b0>7ufu(egppX5S*@{lfh%r7I2Q;30 z*H-uXXz@8TS4G^f-xjr5Ngc4gYC37PQ%&@q3YZ6sYQLG(rKM;zuY;bq5%QI~>#J#@BX!vIP3}th z2&-kh;y!P~aK*H7Z6{rt;;nvfPPd5I=-OJ=x^5V%8!>zc9ZDF8T7X?A^}GElP!cKg zd9^@@Am+`APfzB{vB{>1K=lmvK~?I;Zy{s>-m;$G_$Wjpr`=={?8}~Vym|FKjr{BD zvZeDzp18l;kH(2q!kIXeH@%}HiZH4f;tSRB(Y1!ql`{l%q{H$sSM=}dKRccSy~L$?I%msXv=q-q6| zWc$7DaM&r-za^i|>XY3YIvoDx+vSW&F5plQ)lufWvK6E~OQcTB-FN($Yy=-lurHHs zCbGb+rGr6kA6AS`eBg63hI1VcJt!xd?G5mqz6qlGY=NEf8HL|gcutA?&`~XYyxW4}dyvE8>L=Dz zYgRZ!G0L|gf4|@DN10l`rS^3H9sdl~MCBe8KSLggpZ8g#Yf&-#UH4aoXye+XY*N)JZ4g1UDki zKD?eN(LIzhv*5(_s$Z-phu-J|pTVQrXZx4DmKwV?sIqRU0*>oKK2qlGW?=FQp8NnT z{C9kv7w-p;|DJFvm!J?H^krV+wI~0=XJ(1Sbo0Yz)r%p~Alt6mAHnJzm2_dFl^;|g zo7z+D1Ju{mrS7=cSLZA^*O>KTitC*)H zWIxU_-t}?4IkXVM6J{XR(vPbE2I?N5tNKb<109IuC*WQEcT#p0M(HMsO9_16VYEq#tJiRf{P zeg-_4)7*7A7G^VzetqhdPe1u>%^fB>w=Mr!EH$nAnP9VHVsFBpjkTx;M$6WbqKV5| zgOaOAH8KI~NHCKh<%{!+SBMALFD55KY8q&~jaf`P`L=)f!>Eb434Zw$MBWpQ!>d1W z_(cJO6w__dC~8&W&Bp{WPPV(l0pEMkam*vRaFW7ZAr6hVmt!a<@T>f{yRYpvZ|L%b zU1lp9^Gf{@P}OPUim%9wo0ZLn8aOk!O+WnI#0b_dc;Uo~Mr-)xVZB#&-#vsPgT*K= zReQG8axiNmK#R3$wn6hn(Fb3N@gUs}=Ie=VU;F7DzoDq9lEuJTV?I3fqs7^6{Bx`N)SoWd4Vh}Jf;Z<} za9;n!J%GpuG*)f}fX2q4SJF4UHCEDQ=7g(n4Yq_Q-MG$0muHKnM4E{r!A43=ng&`` z{sC);G|euD7M#h*-*0tB4j#P@nw{R97Bs&tgt4$F>VNkx^xE6|GruR=4bHmK(f8CVBSzx_FLV{E8}2zSRo-eIGbu{ zNIJ^TFXHz0mf+~wv;A5nrJ?xaW3;=9iUXP1S)022{J$nuReK6qSsiqRg;%<6Ztu4` zJ6~irHFbOb{Y$={l2R$b$Y`%7DEQn#N=nB6>I(K^bW}5hpWoQFsOVh#*DwC5lN00{ zSJ#w{#YOir9v)qD0s<*kczAT=fdQQ~Iyx)Go}O3P#Kg?p@89PMAs}F(W@I#QA%Tgk zxVVD!fq|K18yga<`}>SzOiXqfwY6gbo}QofP*BKlLqc*W*x5adNJ%;Q$;dbaot$cy zpFjUopPPI4LPzJaP)tnSYkuCWsjhC^kA>x}&gQ1%PcAOkSD~RF@N{)AK2J^ALem+Z z93HZFQB#{r-``Vh*3=C8zIbuiXm8&>J2Ije*WUi!MnvSPo2u$cs+!ttx{=XN^~8i~ z*yQBvuSZ9$eWj(pH1F;R59Q?g!qwFmGWhuPt;51naTF9LqHk`ncVS@>;R6DK9<;QM zbH&BweLj6^WhgG*e5$Q|mS0ft@KRd3GcX|`8X-BkLMl7EjYe2l+2!H_{-mvK)uOSn z%X?|bvx~55AX>P^Zn`d6*FFJmjg7aOySrb0nwfF>R$hK2XKwzvHz}!D zBrUCwXKPDn#@9FIbbFh3Zg5a8Mo8$HlcQtPQe0dt%-o!1jfTc_R!Pa4ii1P_qJhC} z`T050+1?(*gq2m*@S8XB=arSaO328>*sZN|&OSbo#|#Y4s+^o|#^mJm!k<5%7}nPh zxcB#Kf7scf8~OS3U7m+W)(#aFjpWm(Ki_}*wrmy?6OIub9fsxNlDH-*Df_OWq1V;g zx^z@oc`*?V4gvA<66OjM6PMh~te{&!z{K9r@TRh*WzsG@Jc+2Q%jhEo1(Rq_P7Ble zy72ewYrOsR^g1>i96nk*yRXyVzq^#g$44Qhrhehe%xvVKq@)#(h)5>b-K8EHA6JRc z*MBT!X7|9fp7@5{iH_DiZ;>j#A#>Lt zUz7^^v75zTeJ$$ppu3SrQF@5zQadg4HW!Ddn<*%}2uHhs5)cq^WD&QBQmw|vu*gI zmj=s-?zL=X6n^kudP8KLnnxjVyi$ke_J#atN>?(0kJ047i&Z5k@()cf>HhaVk&j~) zJTWlDbU&#yIBt|RPp#s8jEzX*ex)1Q1Rz*eSu{ReG>I3D@7l_*sms~1aF!E1D@M(6 zQd;E~u%B3_9qP=CnmsJT&BxIB3d>cpU`AQ51%KI1$PjLzkJ6qLa>}~D+wnGw|9xC~ zi(M>>M5HaE2#y+Nu7PwiL0jFj^EG2@`!}Auo`*oQ^*6lJF4TDn2j59w)-R$I>BkFs zmDpet30l>pnLqO=(BgVJIGHduD;|zZL3ebnOCs<);MKgZHZA@YrK)VEj5|k{@#p+C z&Kc&fLzVVZ&$$yrZ)%Mu{QE{eY!N!#Ry$JZe8kXzOX^m)^#1g-S0y@wUigb)*uXo# z?aQWeCdHLiv4tEqv^hM_W4Fd^G8ifOUlD(iPRde=_wGf5O%U{cJc-#&ppm)%?g>KojG+6^|6=f;BLKPw2h9z5g}!_BuBH_Bpjd7L?x}3~%>5k3=8G{m z(SV!FluQ`Mlq9O@N1u;l8!Uk?LwW9=Jt3^guX!zNT#S<7|42qE{yus98R`oo)_fZ7Ttev;zu?!^pc5-A7~f^V zEyQFyVdvCWpD8hf)VSB+h3SPE>?gY%aNM6|Y`G@ONAHs>+yU>$nJv!$+N2*7&2&Gy z`?S1G&&^~J)8UuqkHR3@XRepQ4Hk^+;g3>#k8 znZ+TddsB$#l}6wYqMLZ~R^UE*!tsY8me89wd+o&5i7i^gM6PfGszMI%^#3Mlp4_VU$W*O=EjvAm3+bI4JZ7f-4psQJj>At0Y{fP z|8Llz$7QdhSL}?FccU=ViBTh|r>&mfZ&@!aQ-9-2;;mNURi_#a(J>cHeL*C_x#ZS< zNOSH)WLMOvWBoqu`Yw`hdE8@-CeR zRs!K^FP+1R`aJlfc@=UaR{}<13w5QR=9BJQJ&FpYrq}wZ{NoA9-&4zm!8|Z!TW3sf zZu%q(a?5_$-$jE?Y(M?@2pE_n$XuSn$-Z7JpNub|xRN4wge+*RR#y9|+`O zYm4c8&()l~+6rWBItOiy4G<*A!$;xu6JFPv{3`4ys~>NzLyZZQ;e9Dz>YmSE7qs}i z`1t+jHitRp2CIWhdR3kCL{9B>^yf2(`q>NF8onRK`)?7V82B==C10?RP><^6y2mJ$ zTvz+c@ZX%$v-FZV>iP+!h{QZLQgo9R3wIEj`kmbMDqU>ac>OHJqI|y}lVbkZ_GZYp zDfwFrJDlbM^f_<0FNF{e$t)_ie9g$1iBiubR*| z_%63ng3AzB*$WEU{}i!PKToeoh#pl6MVlyimi|Fr$LkZ(6Z*Uw!ntC`ftOz;TNlto ze%Lqb4?q275vb(wSM&|V&m8M*A*?d;&wv)RTlzR|P*JS~VCMUXzAD-JDLM zdB(4>%z7?7-zKvJ3U1B!lzqah;F6wDD>-?IF}PaPuKYbl(*H{8o8e4uk;0`16$9r~ zesFHkE1jIb)+%po1I824Is3#uw6?eHbenjf{AvT&?)kc<-(S6jWE-50EB(o>rxqtl z)di@0^6sk$*;JWvIH)M1Zf!L0PnJ-VM_zUxxN9@!byL6%>v|pBQgk20KYyR`e4kN# z!+)l!@1hrJpsDIHVoz96xm8=LHsFG*&f;vDGAzi zxZk*TK0Q9*xy)oDK1ZI1htHHEcyJ&*>ylQvTR9t3XV*j7G#(2Jq3Nra`D`w~+$1n0 z)i@LX`I?Ad`UTAkmS-r4C0yD` z!^A1*Y7Qptpmsx*!=zai!9ekE%6>H|{^{HKEBQi0|D{XK?CYZ0kabgvC8hX}M4>$q zwc*5dWVp+c4@V>D>7(Kg18{#ci4z-~x0VgnG8F&v3RzX0yvu!Z<^L>_qQF+Z|JkM4 zi`n{WE-zwl_>Ys7F9<%jYDuWQ^0Zy8U!)DtpxHf7E9cbh_{oX_^Q|65q>@*QKfYS% zLOmqTjas%ioBH;O?ja|W!Y(HuuCh0BS`q$*kKH&r{>-r7qeZ2rH6X`#+We!{6ifCpFhFR> z=u^?auPX8G8cq9;0UMLYo=A6o;WMOBkxnb_zb6?SnyYg5MRC)b6}sl;PPn)2-2QN= z%6%2S<+(Yq8S%;L5aV@&6Fc+BV!9KwnAa)QxI-=HGtr7zAI<+bR z8d09Tq76fEEf?~>AHqemu8Gn)T9@*u=)p^-*+_07>_b#y2Dyay+Ga3U>~s=Dc%#m# zpC&ab-P3h@Wt5)&wGvACjv@7eOh?-C?fdF`F7(hS)M+oewFTl#UKi2I0>-cv0lIHl zG9P)~y};>sxW&jU4bw#W0O!&osC+jY-B%1RbH9n!-|iOS@^o47k+M-Y^pJ0v3U9`e zh+lG39$92exXg*{1nYUTnKvqxfF)+@1j}tU!5SV>ZDNf!T!xGg(E)CGW@3MS+N(9%pu30xq^?BG$!R@ zXa2=1H=(mzcx&&A95%(-<+4T7kEy3DJfAIF!uP(;aY&o`bunj0|216@-efmJ{;1G; zY4BjEf3LP^5IJo=P)w^+fEK+Mj>amsHKj{9A*W68<-i~Espr~4BrrTPSVafnw!bzO zlZJc^KpV=!Mw3h{~SGms{#ddrj-qz!y|4TB8 z5p;qBi`p-T^;Nwsk-{rC+g_eHjTOdF>SUxNufc8IEJ|~QEr{M-vsky8$?PfBs@(6I zYTI%VV_D%*Vwy#vj)kkHY3c!dZq;x&9^>0GHw*ewe2>0XjRp);r?Ub$cn>Qj5xU>5 zZN~)NC<@0ByGkuatc%~g>xoy@-mW=2BUP<>X8;cwRC>vRkPiVRKjT`617vngK$ZZnE9mWm%NHMF@NZ--{@e*}i3qa|^D z2{o9GHUsbZ16vm`w%1F=&pPV!op@o*zmB*PQ%7LI&W+LqC=J?zk~r+QW1`6!}Ev3WW#gxb;i2i9G}yILCLhm6FrZ8OX8(Cm+%hda$IJIH*m-QpAC3CewqQ=p6%m%kK9wmH}4a(dS>rc*3 zq?fzV=c;Tep-RjBZ|{CPTN7UDHQGB^=|MX86OOUGzO)Pb{qR zI^Fjb_Q);jl_Nvp*Orf*FMWMv_~dQg%EccCTStyxhQmz1<^Nice7vtu5qDyKyU<2y z*oQN@@V#Q?@mr1S)(qPD!(MzKmqTe`;NT^X->+A>_wvU9g;*D94Hw!yLqADrZ*=$^ zExq=@Po;ma&+YLSgr5?_M%hnF-4$3AIsJZ!bl~cv&N2SvFJVmuKln;dXVTGpi~-cN^NQDWit()E=3lF+by3es}#$FkoFI$r!cl zCC7j7>TvzWB2?#@rs`=Mb<8gQ*;0gci)xoGli}AvPTO}sBC*Zw-{{_Wx zk*aumuRWxgGXA!M?m7s zX}z#~n9-xk?`WM^ue*EoX4&(L)grHR3 zbWW4m*l|^oA6dA<`D$@;vmy`e!zIp{GO=h!D5kYR1o1^lm-|yIRLZ||)IU3FME}K` zh!=vML!w8eL7q~#IM8ieuBc&|eERXv#M&i2Ph>krL1+#UvngA=>R*{^g zGc(;ylw12c&Uo^=41-$8`zk*(;R`;7OzasxW=u=)f{W*Ka z2B&LVDvI>jyIW^UJVq=(h}#TVJ@4^BZ5PgRxIYB?}*ey%J^jCkYn6@A+GEl1b> zHgJ)|s_#o9@SlqJ!mv=nb3H;H+W&{VFAt~k3)_7++dPk%XF}##<}svDrXoXR%9Lcz zJcWu-q=YC_Aws5%B_UHKbD4(>k(BAIh5eoVeP_6?bDclVALse~p7y=he)hZ9yY{=6 zb-(-F_nncV|8Q`L-g@lh&6CW$x72wQznN4`kP$_O5^ItSzv?avJQ4Ts8NnlCa=DRv zAK3+Mylgd%BDgOO!|Q_>K^lQv1DOrZH=sZxiW*!SyuTmPqIz>jct$qZA~Sh8m)FDh z#p}DHhV5Dt=NR{?o?HBkmcDBH=^F{_{PtZ{ciy52*?@~U;*FaG_a7_v(SQGqLqdAR zaQmvx;QH?ygssQ-SuX4KTmQTEQT68K#GD~|heEd>kxSgV($-JPTL{{ef+Cmjekh`y zWG><`k`$kD&3v1mQrj^9BR=%sQ-k-u=N`Rz-5Dsqnp*eGkB9HYFzK|!WU#+PGIh7X z`y!&}B8y7`$F_Qn(>LFIX=XC1u6dhq>(X%C=&@~|hf%LXL}jAQ=a}QVWY+GgsBKzK zR@LTFQ1w%t>7@Ov}gZXpa1=f%_o+@ zt)hukAxYrMu=V%4=CErQAI$Lz8|Quf!4eX7?KJgy%a3j6L|FMBtu#FQ^Vj|NFV34H zUYRq`N5V45j&(DqRIuQMXOK&N)m6MKdW~)5&tLU_mQ$QI6gBZ70e3!XS5lyuBGT?~ zTim)s_DTO#YQQT4^Qe0T8pe`%Uay-I^43=(=c_-fCtPF0kzy7`kuZ3_bh-S@jGAU} z5`Twrf6pz*<;>$6=_@~@?;g`1_z-(eBx{EIis7wfE__R2U))AEXOg`QRD9Bf@Skz@ zLt71w7HLZ-&*~T4e;X3MN>=uLXlvl%ac38i{m=a)x1wiz&*!n9U=j&q=*U|9lK5g< zo?Kk&l|~!Walbc0drRU>UG`s(8e z;%|P0J+2jK3I$El>Wj8JQ?c`WFKVM4A4QVVo;^RfUd3v%|Cub;@=~vMN0x`wUd($An=Z5M7P?Pv zRgv9OoY^~ByJJeisQWg7w@=#(#R&3*k3xwUzve z=M#xyT=#K`{^;8vc9V0gqNt0@|k0*?o3uXlU z(e&?Vro`>CsQN?0?%Y*9X~^4RYqY-d*)z5NFPB4)?(NfLIp2I@<4I4NNsFJ)<7`bD zx9@lQepi(E_bd_fhH!Qh4@E{Pb*s{a0-;Xbp|{V>lue~9*~8;a^JNTrjUTFQYD|m= zmDFu%e2C(6SvbKu|Fz=n$TSDuWB;+yfLCrsGhbdRYF^cvW8VCb^fJ)S=w(o`Q?+ug z#3a}G%J)<~_X^%5(JJFaNxrPzkRW1+l~pv{bq|>UsrA~B`T}?IC^D{ z1skqw3-6P46i?_n!3WX#4#ZQw>3Cm6!t?~i40%ua z;y%y19XA@7LH;0Mi>}SDjmf59fm`g^i|y+t3n!}{Oc%0=r&k`Hv<36$Mg{uv<27<| z6Z>J!XV;Rs?h)K)x=^x2cAw{2b^L=|OBv}Ka>R@$ScGi4!e6K6sNFoyXWum9FL6!X z=Sg8oc2joGIG;l3V;Xv~IkkaSqnAzNS6*oTqZy>>8_=rzMN<5Yu1))%O9QvuC#8FY zXTIvrI`kap#Kn93-tLUm{#b}8!A%|$lwfe~&$s!aYaV6kC4vtUt<$zoJ2Ef7`z|pnwKq_f&a5Hi9pUn&IiiIAn*P)AQ90L9)G&T!`Fq_U*UcE$Nv(;fb%isX%Gc>^%bjZ* z_%(Jp9A7L@3XV))BQNW&teTK@T+!^Tzg0_DFsYUNBy^Q9Z?Ut*@x_Rjs_#aKlwf6j?l5}FPifs5= z({G&-`fvJTultVgo|P+AX_rj9InlXOkx2rC+BO&7cqZncnn<73kW-&`j{V{tgeE=d zq&w92j+o8a=+xqjx9;%u59xRtyKxZ#>Fhr$B)jx3u0D`zH29!xlXK@|@7%qjnKe^6 zhu`mwCC|JJEb;uV{4HokusN{rs;y^4fIQ{H@_UJCcTS6yw->#_A+d|`y8o8WeW#M( zi=zd$hB-LA5gU+j&43(zp9ilkHiY*diP=89HW~)3F+V_u z_~0UHa39tuf`r#T!~Vc)p<%#!zXNoL<&H3{?*R$dQLvs0Bn(*Zbbt;q{}G1urC9B- z4hFo9JwS(8=Lp04Rgi`t;r8J*-Y{T29B&v9(}INh5fH=cvoZaL*L1^x?cN9I5KA3l zxczpJnEqofkb?F0M>P=h9AQ|WkJXOdpGd5BO|1GkfHD1t+kydap?(4kh*=KM;rOQk z4EGK0FWe6pum=844hF=E2k5YV5Ma#l2Cr#{0c+sza9}_zbbt=)tB){jC&j`ESXloE z!~NX?iRnKsRt7) zNZ1YoGyal?cMsZqu&~S#hW+UTi5Y*)A9T1Khoc&Z`HnEGFUD%eo=>8&+O@IjmjK4} zA3h%j+@{q5I>c;87(V_8Bxd~WuxjA%M`6I{Dj%T3<9P&N%=p9KbHIQ#?g!`)iyUFN z{W`36ax8oY3!gp0uzm-m4akFI2W$R^V+I4h78OX${1X8%ra##8|25DK1J;Wlpu_$* z1B@9T?Bl`qs0P+^V%6thwI6loV4GoB?P^%{KLEyTAN&2ffK?B!1`MU=n(53VfgqjkeL3X zv1;IW!+_7F0|{#&hQH0h^dEcvzX2YD0c)fW&|&{O0LJtmd;Y(2R0HdIvFZ!4+Oa>6 zQCRI-SoI44WBL!b1q0r~-*3Z!nDqc1_U9qMnByPr2MkyPe~%3VV)*+{7;t=sK%N5$ z#~*wChu2NOfc3%$=&-)#2*Y;l{k@IVeg>=lAHbOY<6_ld_l*X$!}kMjvjrq({J8;! z0o$)1phGNqgkk^Rfy9hI_WW;$Rgc*Ytbc~pj*EpOu&~AvhV?TbG5tUI41@cyKNcWi zK#Z9;VEuiNnDNJK2i63FgaI+;a{=qWfW(YH90wS%=IQ}D#F*m{)>mS+WAAS~R{QBA z4C^;RV)~DLyqRIu!}q-rBz#>MnDNJqGi>)gc;v7~_6Wo6cZ0-?Klc3Zh*f_atNtZc zJN9_SV6~sZs$T{e(|`DSFkpYI56~gT%#-l(OpuuIx5KIl!NMv>7}k%0#Ed`YeT3Wh zII4kI^a#WH2CR1M{k@CTZh%$42e1vugMB@kaN#!LKEZ&P5+v+1#MeP$`h(do*zO4u z2E<|q=@fPnqaK0t@ijXgkz zSmy}C`ZbW4{$tPoaJ*r_=hA}2%s-O=WBQNz+`$@ekT4*YIzWfx`2k=||FP#o`=c6I z&x2K8fYpxOpGd5BO|1HPfHD1t+kydaEf3HkW;w!ef73z2eS^mX+z%MAKIi})V#Om2 z>%W1-9B*(QfC1}W56~eNI>NC24OTlT7EZvz`bQYn{{@NZKQ2}cd`%eewWvX2#{Vh6 znDOTZ7zV8IIzWe5;t0d-w}HfrKlXfh8LOTPt3D5_9R~}CV_|iy`e}eM{Xh5&gZr>Q z7msQnW;(*KKdB%wCi@W~?#>pz2pV*$5K0}=+TcR4_ZSnvqL`qxg%!E$+7SqEPNKLeivXHkO#*O>hJ;Be>i3^Af^Ioa)cwmEv7%%^Zzx_4g=PU zAE3kerv+fl_+TFownsIvo)fG7DOUSYcMi50hSjcyRX+(ZX8YLh*9EM4My&c|fHB7p z_WbXURWFZK-w!Yx3phS-9ALmcJ0GA!EO3P3`&EwB4)4Q&^|ubtA=W*@@bPt!nEqqW zw{W~+z~|C|gf$TVpRWHug6ClRk3Iii0qrp0HhB-w;rmr|gkd}O=P?SaT??yz5nxRJ z;d5ZX+e-)N5VIa(xW5@7F~>iAA7H@xn+NC+D;;52KMWFcykXD(ZdmogSoO77?b!Q! z8>{^cR{b`>nEvBp)nNCH2DD?2e|S8@$Kdh64H5?I|MdfOh$W9O?Eia^nDNJ+|Lw5q zG24Ol&#~HZv2X+y);PkjeikIA|FBOmV1FzQ&>?0%!m$1UNX+0!}=<$cI^F)$7(-~RsRQIO#iWuH#4mIV*odTgs%$&Gya%y zhV8xwj~v#>9%0y@ParYlk3Ii8V$~nVsxQH6#~#lZtoBn_^(z2l`VU_Z2JDaZ0XoE( zc@jSU1SDqs?XYS>u&~MzhV|nhG2@SUAK~^rj%pwlJ;Jd5Emk`P7QTyx4URCZ-v`No zE`K~WU&+1_*zi0p=X&38pn;xQ!V7y1v{6(qInKZR0RCXRN{X3mX=@^mwMBo8$9TJC zRS)d#*zMQH_1JilSvvU!c)5MfrjKw2Iqsx!-uMjaIT ziTl^~tQm!^?eUoQ_Gm~u2$=~vQOAIg<@wM(E9JSZjfcgkf`uv8JV@b z3&~#N`;pzK*lWMhr@tsE@Ok7Qkpsaqi^0ZO`fi%PzpT5C7wPi-exLQSDM3;uIatNe zmczIIyIds|F1evs?JM#v5yOe1G2h!uVQde~v>gq;BgT>+?uv6br`NI)=Am2m@ff7f zOj;_1ymM%OR?um2X~AP@D9PjA6HzYhCpWc|%`<4#pu&kr)mS{s*;5_CfkVnr zslt}L#V}(|&#&yR!{-_IHNI)e*E?te|1~eM@H$RBk$+h2!W6dxHNV`8F=zS?Z@SCO zG2>OLES!HcneqZRh5FB?y_27P<_uJyCW(&C1)4mcT0oYXr%9jH zR-DEXJ+^e5+YG<7y(B%UrQMVz?8;R}TUx6dAEVrH8VMSFO(dF^HdJm2 ze|T%btFY8l#S~h_qu_RX#qK@FPM$+cD8JkzJc(}NIO7k6@4sd*N&I6%U%X7uDwK$F zG!D3t@yB;ST)dPoY1pGvMmwJ5y^hPwH~)*%ri=P6IQzjG=PG>eisps>C7bN>j$T9$ z{X{nu92?(Y5PBTqT~+Z%u!!SK%SzByA`#;pwPVs*mD8K*N}PN_EJ75&n{EFNox8Fn z-rVO*LBpF9LOsqMQpXZSEKul(4Qf?p6vE>PbzUz`wZo; zl**lz{T-iUTV9fWM~VH}kMnZP{;_b)ef=T3Xz#~Q$0Em?t~L-94`yqAG^NOhZ;6#h zb%?f=b%{4r;Pd+p{>XhwEirx5qcV8c9GPe9Y2JUq(?*fL(N;+HAT^K`>6@Jn-@Kki zd0)&H;Y<~7+2_oDK^K`Q5b~flWVk%DMDXVafh)N`EQQv^p3kWi_9Qi0#?1<44ip$q zkw$kBe_>>_M&TwA_->xe8nZIZ_Zv5z9BZ&#<&2rU5pRUwR=(1bSKU{+Jm}lNJuRR` z*F3)BEV>;qQ}U|SbHieBV%;aL^9!l=l)PNj5W8qZ45Q<9q+?4nsUTC}sz7S9sBTe% z!#!tr$Jaxn{TZTo!Q(8Ryu-^zR?IUz=Qmv^cU;h&^tj5Ln$bNMMyl8auJ~*0e(Cg= z4_DjlI+1*)#jG;CZ~QE&z;wXX^KTUQIM||sO(fGwSC?qK%J=)5+M-nYf>($!>nsf4 zz}&~^5$*n$p9NZg0*t3IoJx9e54G)}_8SbYM)2JrNKTM!AUQy?g6skdoKGM>V-`BN zcobBUCi_exUiY)NXr!&b%jpbWPtE;F7mbthr5O_P`Zd_E#^Iv4g*UbgM{l2xc0-WOCrDl33X4QHYAL%;1+7kP9EKt1jnH)Ncp1! zHpC$_D?YJP!F(>D^40Z1eNow!cMahN^Lgj@jafYU*Zv)!reqa%8f%U?B!nz2Eg|LQ<;d~l$C1XyMr3+=8kw4!LZYIg5Hm9~L|R%JSy)(r8gIzUmoE`D zH8q5rn;ThKSwXH|y^16!CnH)~S_mT}Ba)Ywhj4Oof*+s%M99g>5d#AQ@pr9aRad8o0XJ<#cySou(Wo00dCWefR zj3Cz5)`*pr71Gw$hLDhuAWTe5$g^k9kni8WBl7a{NJmEp^7->;585#2M;X}m1!2#Lc-bOk* zJCV}TQbbr-81eS@MyRQ&krO9QARHVV$m;4UQdU-mL`Fs;>FMdn`1m-Yudk1M{P+=R zZf-`Tq@olODx{#G01+1#M|yjEkz>b>K@C6T>({RcEiEl# zZf=g8Jb4nawY5dY#>Nn4W@dzniV9(2VL>D$BoGP;3gpqFM+iMVJ#zm1d4z$10r~ao z7sAEGh3xI^A)1<+$fr-AkXNr>AuTN}$m`dyk$`{zBq=Eg;o;$d+Jng4+#HghpO4ts z*dRMQJBYKhGh%3Hh!hqUB0W7lNLE%Ba`Wa*Bs)7BxpnIna{2OQR#KZ(Vs1SR5d!(Q;b9~$ zE)Ic0els&O$k5ObLPSJ_sHmtQqobopN=gb66BC12T3R9z5fMO06bI?*>O%DN^pM2F zL_|_j61jf;I^yW)h-_|dBJ1nx$m7S4k&uuO;o*UtK7AUYp`k$<8X6EgJ3B-~L~c6Jug*49R@UAu<&f8f17u-)ufjiOWfL-7Go91L zp==$K&RYtlu67<0z0sLNB!g=!ygM<9r;+%g{d4acg|DQUKarAVmGH>)VlDlg>RYJIl5!rE4}aBrWg1X^Lx>Aa@*v|J54>Un?J5vx;hjj zEJxBjXL~&2s8b^{lUo#&f^e=ZG<0|woJ&(_rVUgu&I9vx5yfy>!*qEnjBprWu5uhy3-N5MJr00xBxe)M?`M*(LAZ z=wy~;bwR4INuQG|!eu+7qq1dV566UBNQnhJ85>lIvLY!?^3;Bk?X6h#KU=Z;GZTF& zn~uauoJ{B9SZ%!e&&Lkpb4%}MQ}3!UGmd+aNrv7qQYT3H*LBm<&aI)yo>R4ucmP%2 z(L0OtlGW^dPPll$(r7;QCCS3wISr3v1KQiyxDu)51|Ft#zi>WPeR5$G{#x{SntI|OL5tN?`>Z-Pi9pUojq@!_SxyL zTQ*^`%6VO>etC3{qGBIt%~-z=Y$2^77%qD1gU^=16%r^CDgN~dFOSzo!&dcojZrWwc?4tZV&Gk&3mbsFx%EN_ke{R z_ue4a7#UguHRCnf<*|n2CG)0gxq4aY?SB+!YL@P~p&UmS!|Cb9m(}wtY-)CI1SYOg z;7)R6HV^VBILd#oR(ZB!EEaQ3;nS~5b$f{?ow;-$(OOKUw{K3ZO*897j*QRNcn*D3 zCw_UeE5KsTZ1bYZ=Y%t#IQR#0yK}{Q{kIpRrAE5?CC@8F`M%y+k@btgZv(4>>5Xcq zv%%%B}m#m%SCv)7rAQUR|e3yCm2mJ)fs*@ z{Z$Ty@-nL%Bfm}~Zf{`_oRm@{s%RKa?P0Ii$9*I9zVF1{fTq8koE8E1t7Cf4oa7T2 zCpD^h60{Na(~j!Jl?#Cj?p_GfJuy`(U$-H|c9c{PSDXDs>9~cBeap|x{Fz0rxW3u$ zEScG?H_z-%h$NyYfo|3N4!l`m3NxcyC&G!wU74Kge@#&rQm|;n1mtW^FzO7QH1Ju| zKf&TlW?I(e7#Tq(P4Q3e!g{8>IzeE%j1o~Kxvg@6Uf`fj1ZjK$yO5kNRUWU;d{r+k z{%Iaw@>3B$q~W(S&3i9!kg?S|FwtuuOmqea6D91Hwda zfG`mp%O)U9qzZ(ICV?=KKM*FW1j0lEK$z$@5GHa2!bDy`m?#AZ6Qu!RqEaAC6a|Ed zYJo6O8xSTk0K!B+fiO`t5GHyEgozx0FcB3HCW-^XMDswH2n~dZ_JA-^77!-#1HweS zK$z$>5GKk2!bF#VFi|KFCc*>4L@Gd-h!Y4Cc>-agH6To62ZV`yfiO`C5GGOw!bBWE zm}nXZ6YT6Ac4lB6}cAp+<36A&iq0K!BYK$z$h5GJAo!bBcGm}nIU6X^kAB1<4l zBnO0v%z!Y_bs$Xi00-*GUx6@DBoHR*0m4M#K$u7X2oqfd!bEI9m?#hk z6XgS8A~zsRBm{(s;PMwP>*10eQVX~=g|r4PGa&_lOD{-GAU%T1K}bj7avjnUNVDM5 z5K;z6MIg=Hz$GMH-a)DYmzIzUL5c*Ii;zY@ zngo}|kUqgBK3q=1WiDLCLW%>a1EeO9@<56Jmz{7q50|BInG2VykTO8}2bbY+c?*|} zaH$HHqHuW)DH5bEkaj^T2x$^r>O&d?sSBi7kY+&20V$R$Seim=2AA4!*$5#%eiU^mwkeWeS2I&^0kZ_3%sSsQi zLs|l<3Z#RO(n5L&=_91FkS4*UJ6wXpWj>^#kbXew3YW%^%0dbQDGj7)kSanN443|p z>OhJFm*|ipLFx;a?{Jw7DFviJkQzXW1!)3YrbEgIm*9{tL0SSSF{F@?UcrDA2U1l? zpCPS=G!ZV>A(enM2+}x6Jt3Wh%X&yfAQgmk8BzvFAtCjFGzwB}NM9k%g0vaZZb(HS z1%eb2QYJ{7A!UH{2vS%`TOmb+bO+KANLe9OgR~dYP)M5~rGm5&(kV#OAkBc(6ViD| z^C6Xk)ErVMNbMmVgY+FzHAsCSU5At!(oINDA$^847@k6a)Ckg6NV_1FgOm}{WJv$u z=>|x#Aq|G76d)yrGz^}OfTt56Eriq+(jZ7BAr*sE8q#`rY5~$#NNXWIgQqwk1%fmR z(jG{AA>D(N64D`f+5(=2fD{zcEJ%+a<%QG`QZaa10-i#ErxO4_Af)6_4P};b9V|NY zP}Hi)drNjh67^T?|H-h5uNRE9`b;kWu(2Q0jZL5Z?((A$V^^o6Tuvc(x$)+lsx~C>E z8i5+Ai!bABC8^(-IQrG#TbWOcN z%-ROMls6ea%lbAla3*YPY_RWG_vuq#oLw2yAEL^m%KlzxmYr|U`e|^>$?Y>=QjB^1 zhqRF!jXu8}uLduk``Byogk)It9@D?#sqfd=uiSo+nVNX1s;R_y8)rhg_T-rtLQ1BD z%zDW#Mb!bnZVsvJDK{(>YQF1{(eb?R%X5BwJ%V3Ci!HZ5N8A0;sNKM0Ymc@4OI!b= zuPNr-R5k(lJ{gQtJQ%EGkdNV@fiO{&;ES>$xA=X5`CmbkHq*bm`a#)RKMKB@2n=5^ z>bTNeBHisnJdCHML{$*{ej$vExVSQI>(06T$-oQeb276`yL5`n$H_E2rYtuCo|U&+ z_59c#{M3DRRXV+#(&M-CJH{`M!oP5lICG496Kn4tQ~gO9b*FR7^?8T;$+tZ1Uvxdg zn9P-UY;)|IYAcK5(Gs1SH=E<0ek-EfD74rg4LKc-&cCSZ_9Ep;g0R8!XWu91#-ypE z{tP4!s$Pi=IlVjdHdheM5yTTD(R-{d+np%Nfk~#q!TIw|JWlGCyqm}Wd^(w%oc1{- zjH|JWrES%I_?GQ{Yn`Ohn#+tn!$-V{g^V|>tX-D-6lv;v&yA}~pPasZ&BbLPFkv^3 zt23XXSG<1eL~0~$|0y}1kJb{>-(cu19HE7`y!WV*p2f%#2%qoT*UXV@%e<`$x6H*ofH4-+tlS|h7Xs-E)E)7 zb4kRoTAbQ@-RVUC+CZ42=ET=;Z~UL-X$U8gu|%Gq%cXv+Qy8o;&MNxr6givUb)whA zGo-&()T{O%_uBSx$cqT#yF~v5^HIEc+E#S^$H)| zPtR1DXDQ-eaAH1`r9`ZH8-BNZm6&j$#&c8s0ZAC0p1*5C$<93Q1+uf=PJ^!39L1Qg zf1UF!B~G#xZM`^LYwO}j7qz;rY?Sp3qiLF42a4vIfzELn5>tb zzjTZezy7$2nrgM{p7w=*q~rHwCsgiQRg1_Xv!58g*S(k(Xm~y<9yw;YtU0Y5v!=$( z>=!q?+Lz_0{~?c`pwgFLL``1k*@u#KE57VDd|nq)-{v~%?(ZX?-6~JYS~%+Uq#MuB z_q`31I(PTxt#!Jk9eWRob{dO#+lCcyR^{WgRpBGW#~w4{PP|ne-LjpSHM=)2TX@kI z?R@#%zrUtR+9x+_gU$O_ES>g6vVHx%JG@?Zk~4fZDDHUTo?Q}d!c`jDezo^7R%5xZo8+a2 z!S*8et&d!8@_!UjWHQcN69auXGFDOge(XIpcya{Vzt73h1bmm2t9&1a=dClYmmTN$ z=+(WNt{4AY-#?inmCBSPBs%-p`VIF5c7gelHETXSip@|dcb3SESAr+sF2ATqx|=V@ z_@kx&oz~XMxY@5lp-qyjh^XK44UZN7jmrh}w^#-)=24v=*o#c;6ZkcQl+p(_uc>=C za;JIwu%ECw@7?@#$>(%^68#&yjkE-pnHOt{AL?iyL}w=KtL-KE!V{b?F9I$PqT?DUP|^zFw6 z`5$`!W}Q=+NRkP?6Z2i$ebCDAT2DGT(=|oP%TxeY4%zlr@aPDjF1+So&ri;Jt%Z=Q)Tc&i^uJBIi ze9>)b`6tC*B#lY`#;WcK>t5bfGr4MUM&$3@*gLo2g@)E5GZpFXMAM-!&SuvmY30?Q z>2VW3Cfni!kn8FGKjs(27)xLP)`J{f%Q+XE>-Y`;gr1dU#92_kOtJ&xsC;<@Fu^c0}gJ6V*z$ zd#~jm_Tb011&EivjPT%Omy6cxm_hN1J?XtHwePU4usg~V#NoC0W9dhK<)+>k;llfd zGwF|7;~$njm@d|>c|h>V;Z#ET4Z)zc>&eVC=4{Ef-9q7Iq%1W=Z%l4H?xVVyJX#V$ zJ$29R-3x^wfx0DNBLBH5hi7jy{0vQ-xxw+v7DkgNS@^;}qD0yW=0Ao^(l8SJ-1?x6 zwErE{m9X26o&1^cDKvALqtq3_s#OX1D>>G53QOaYyu1PQj*opVCwHTM(NabH~;45_d0?TC08ZquY*QCs?O zDDUc=^&6FfYqE}LkynG~#C1QIpO0iSu6PiwBiP{Je5YS{Bq#Mml;XW&Dxt=}{9k10 zWwG21vIflm2t$K#r;X3F_js}om~{s)y+|}Zc@qEIam5qJ+Zbq(l#fUz& zZ>sBcc@Y2A;l9-m&LYK@>FGi{%EZLm&N`aq3~9A-Y|1ZV>?bQm6mE(CtuJBxTx&g& zrDfM~B_)Gzk3V|J=#h#I>JnR(u3eQLCk3N5mKr1Ezk>$86+>r!w3a8 zsvtQ)Qi6n6oxsHcTs?rIP;jFPk^|)b`NId_pMmem-eT4z@D{!_Oc=R21#M?P;C>6H z=zB5Sf81g+du}F3(1bKv{_E{(Y7hRdI8Rfrzi$n$&;K!_FE(s^Ty^T2O{=$tD|@o< zNH<49y0s?iH(QLAt}TI*+tE=GVQ6Z`Ng-$ZFK6#gn%d+{QY<~@i3t82w8-$aYt(OlIGd=) zC-O!uE0bvH-WMr5={~^=ZfAUF@7R8N(tZ4QX!QGM+gmY3msQ4f5ZZ_J7U+98yjS$k zll~GYIp(S+HlJ+rr`U+iA^r2ZwtlWE8f}R=v)B`1ug==D6egLA zWLPpAU03Huxv!HoC=m;@J4!MJS}lyle=JD2{X#c(5FEhKZ~^x_2yiaqKLy~9|Jwk(@&76S5C4A}1z=oXfGj^6UpR8mo0t+@0ZR+6 zMpZ@B5KZ86ZwRieGlo}20^)`SU*uDws8F;hI+Q9(4W)_FLK&irP{t_WY>GyKal}JY zqN&idXgahiS`Dp<)F%9vX}=B_0(XEgl`7DxMmiCY~0aA)XPQF`fx{ zVH72r5)Yg*prWLuq@z@&RHM|S)S@(`G@>-7Gy&a0QK6~us3-xErKO^yQl(O((xlR& zGNdx1GNv*Cokr23Y4K<&X{i8xrK44)Rio9U)uJ_|HKH}9H37SXqC?Z+(NWS-(b0mF z1FCdtbeeQpbcS?BbjEZhV24qvXjME_N>wUVT2(r5%0*38Q&mgVP}NA)Sk(j!2TBdC zhNniUMx{opMyI9o-2?)8NmXVgRmI)YWlp)#>&ydoP%8=HO&QR4* z%}~=&3s7t$Lt{e|@HU`~&_;MhltxrWv_^DBszz!?nnqeihT!Civ5^UQlTgNJV?1L@ zV=7}>V>)A1V>M$ZhifIlK3IW7q)838dqDG>!ZAqtlmhX756kC5Pz5t5S< z;1Us|@W2;-6bK195eW_+1#r3~BgH31;}YVM;}PK#B4orQ1f)193N#@FJ_(A56psv- zfEXdiK@*XXBDnYjcxXa$3KTI8corTG2|`4MOHM#cfkqL6ix&_6JGy}vV!-Pe!M}%a z8{C3Z3J7?`gMc>&*JFU&qp9LQ-N*dBIjlac#k9fa{m)`9dj~ASx;B~L?dGLBnGLZ0j@S1f_km4ZKK-z)~1K9);?o${@ z6xav~Yy<^1f&y;}7!d>oe)NI@e}hpBAUQyafm8))1O93sU2u~Oo(DZUvq8cT5YhOM zGZ=(Yk}=L+TE(Zi{2Xt+@%{CLwB!@t>h!}q*fPu%cLyfu9o0P;;*L#IH=J$#GL$){ zo3;J>POa#2K!KBg65T5Tm6T@6TO?f8oS`9?9$rk8I4-T+Is5j{1KfQ^v2vZf*S%k@ z?gpI?)IZ*}p%!-LoXd!8yK7U?Kc4S_grxOl zdVaB~@soCQZu1{M?DWW4doD2Q=z2~h|E9uXcPii{W+jYCdBN`Q|L zkr0r9OXvu}!H>o#1xGk8J}ChTkAjR4Y=<1dCBZ?H5F-RQC^B4pLOdc;3UV?MGzu4d zbP!^40(?RoQWOPnY$qT_6XD>3*CZr8x-1GGA!Oiy{lOex@EF7ncucW_M_RoQOIpBJQge`y=$_h^Ks(O-j`Rck6%|Vt#5F343DTjdT2~z zK2Fu&9cMZ|rZU+w&!s`#-jqe=_Rl=*Y97Jp`u8&b)Z@PBMnQG&__A*)%|-i9gqNxf zl5Pm+ZxwI&Ih|N3bAB86{e6H=iRfl4>80Rzk3zeB^qZ#c-5sQI-217-^DRB_vQd2H zcuSrhx7%A`K05Y{#MgC~Pvb2(7|gj8p1b(z2a^t}yJ!1+R>KEcyO~Bpr=OI^^XacAi|5d8-LyPe>?T-hGe_|<4o1IL_eVF#IGB!Gj{-e`sE91&PsuCjm zWy9Q}vyGpRt=}woSCM8MWEyaXLERv9yS9>~t4-n4iDpF_U-`lX8I2wx8p$Q*V$s#x zI{mB9hw*vYm|A{ay)^UnH^&w`{i`=tPbl154e>=HQDTvqtczE#4Ood6CESoKxWeV1 zvr9XrB0;pu=DGIac*C{lo*hlO*U6Qx&OSTMkP_TZo3Jl1sQS^{=l#Ey1`4lK7d^WI zdKuZjR?Un_GX=Y`&u*&30`c4HHfX6W^_{yn1d@r?r3yaXR4#kTav~y@N{4{E#&SkK zM!kE5hiPcOl>LhtxuR6Y-!C=2<>6Y2w^#yiG(Na@Qni}Q=!Bl_@LE%vk7Y%|%x4A^ znewL!%cwg#6konceq1MGyZ`blc@}Mv$(@r;w`4E#;~rDLcRA%zuUmY?;z@;{H&?80 z>5A)}ovHr#X{6|DN~cIU3sl$pEyOR_W}EeTS2%dhu?|UHD7NtV z6F_xk(p}TCl-{x5y}eDBmBTXjS%#LYRBV78-kbf=H#MzZlr-rjM$g%_b>C)NOt94X z3Vc2{eB5R#>Q8BcpP74&n49rSQ;I7DUoVQK{t~qfe;4!NTHE$-&09rz9h$!`%<_K@ zj;+2a)LlDryQb{6M1vk(Vf@@u@_dFZ+p#K|xy<@sMCB}(_)iz2yq7yt#cA(GaaQ-H z(L{!|=-i6^`>NltYUj;&mfPtr`SKUFuTPQQiDjpcdEgUan(p^jjeGg4;3u53*=yym zY2HW8I%=Kx?vb>A1FyGA42LNwFQdb%eJ73d1%Bob$Di4!avNo}IpQ`hHho`%Q@oWQ zJlt<&kayS+;<@AKwEo=Cx@$l;w<6-3RLtvpCO4Q%oR}BfL~xW8L$0z@Ygt(D6nRUV z2nf$#BOAmuaHIUaJN}L_;YnaVGhuBQQSsC4;NgG5#~fZzchtTYAVfK6c=|uG-VgNU zONM`z4}$J1{)b#2o<)ywIRepvGhWt}qjFc4&s9BD_^lLOw=X+m*7SNO^531p`@F}m zTQ1Jaw@UNA9U@`oID1AgM(BHV0HJbse2#Ov$>~Rrt3>e7@4BTw!!CWE3pg&qv&-K88wUD-&~`R?$1j-d4{k^Xcbz#tDX+og`k?uq9T# zb7%B#T4>^}z8rD38Oj%!d@SbG`PRoS9?rbLi+4BKfAw{PRcYNWC{4vZ**TwTI4!k7pLQ2>S7rtO(m9uKIaeud)pL2 z-P1^f_Y;LASNe&WS>CfL3!`FSFMB;m;^*-^3&di{E z)Qh$saW1C+GVt#gNE9S{wVyG5;_R-?| z$_Ve1^NUaS{<=SE>W&;_PJU|te1kG5jO0R}sHD7is2fv`DFxYze{I(FX>PWW)GSg< zx+&R#ly?R~u1OMj+?!`)C>_;ZBi@fojJmJXrAw&r;Xd{VoObEja=1YM!{WqC>4cE2 zMCu>S<>I)8NpFvH)F+!VlW`@IYdiDzGrsgVo+%)zk#k-#;htSjCiDE(UbyEU)t|~j z{vLJ1=g0bFKdb6re${OK^6lIW;$5}8zQ%$x_SCzI6Z?11PCoZ=PMzZ5bG=(oO}Mf8 zz4_a-Q3~$D-n;zodLL8vypQ@E<89F6AD2UN?P-QtinfzM#J`{rHMwa5L*n7wQ>-fX z#M)~QT{);v)l2;u<7{)>Qj098E5c^iihvx z{wx)SQi^DvZ=szHH+%Eg;34#ce-HBG(RwBym@!9&Md1wqbMGEt7H$m8lv#k0axpOA z<^g8PmcUS36&N)$07GpSV8Uz&%&d2SA#*!0;-JZaBIsq6uD*|Kjd|*H> z1`L)*fNAyyFckL&hSN*H3_ArFG!p`oZ)0HQZ3&F9&4KZG9Wa=N+>!?vFS7tsb5CF# zo(2rb!B@wWoxs?76&PL*0`p}dU=}_F44OTG*?1^0VNL@k(>=h{J2E^Pza5xB+t{db z7y^UmEMUq#2MnS~fpK{SFoAvqOttlak#q$xX*LH2+O5C<+Ygv#=K_;wb42;iNI`H9~gc+ z0psZ)U`$O2&9{NcbR94WUjioXtH9X16&RdHM#K=%0z>T`V90F`%)3W`nY<7exrh!h zduIR!<7i+4&jC!doq&P7{bg-Fc(K6-Fg;HM=HRiw_?#4cGh7PH=YxPzb`LNp_XDQw zn!ubq1sIV}0b_0+V1f<5!z~7;>?6S79U78@ulL2IfDv{vFj|)a#`3hlRGb4CrON?x z`wd_sp9_rW0q4cj2S(VT!0>zy7_~=+Mxs-IxwjNB8*c}u*`2@`TM-y=djeB+O2FA$ zf!Td5Fa`GprsP||pnMRRc5eYg_Y`36ZVrsqLxCB1D=@0x0S4|mz|dR{7*H6>|Gj~N`Y+qNJpx6jZ;`4#=d^Ipv*9WHe#ztCna=^@c z1Q@$J0Yms8V4e<**av~>y$&$=HU=i=-oT(dD=U;^2^f)Us%zs{ltoGNbCaB~2ZrNY zz#M%77`|5nBl=QcHvb5iwOay%^sTiI@>9S-z7&|6ivd&mIbgQ#3QX87f#LZSFehIH zX6l{5#6C7OmXMYjpI#0azncS-^MRgLTAR!2T>P9w0%%}NFD8jAIXBVH&j8HQ!8sT_ zKVYby2#n~RfVn+CFk?>xX7nV0=dpt1KH__ZMjOx12diCaVLFz3|KxG2j|xRs*VrFx z6&3$8Pd+_~Yr-M+Z}WwZTT*PN5ASar{T+kpuV32)KZf#2Yuu z1@iBg_8M_oL~l@s>90P$t^MHL)&9S;xOc5n>FzyReCrl4lzQ%h@IXUEzqvQ3fbN8| z=A94iE;pKQ`EA_sznX5YJbL`iwaSL?2FF%oUpRc=D6Y8KeUia$TrYf@N!;dvk)+n; z^C1&u_P&v|S6{O1g=MAuB|Ks7spG_WS8F#WWz4neT;LXW(e?fxUZ-))CfV-_I+zp@ zyjtYP6@A_OFuE-;uV0q%ml%J{EAF>>H8{8~mOtskPsDv{-a6RzI0AKmf%*r z+$^KTuz&mVsq7%)`5<+Q4_lE3(f|=eq0u@LCw>;QQF)$qz38>QOTKSP5hcf_^z!p@ z8DV*QVv28^PL|JlGc1fx^2mskkrF#nB%96lRV?4nWX?!32yJZ3u^r)XzUX_Jo`sr; z_(yWhbYqsFh9cJlD>d^maN>pR#jp7HZ-guJ+iBSfqRiG9^-FFL)Gnuw?mi8%dGZIv zS7+(tkuY}usf$Y<)nWF0>uf&$fqR;jCHch&oD5p;^@O<-+Dn*Cu)VzIb{!Ri@)~d>& z=}n;(F3H6AF}i1)zRC6OC3NJ-qz#aKdiFbvKQCa6Z>Z`{uF8wIQ$i;nlNtKm%W$#o zylfn--Mo35W-f8C+Db(J&+qV-l7=UjLW7)pk_$XO3zvW5rB-M!PJd3xaMOM6kzL@h zLM>4jpWlZ21qRg8!>arUgNIjUQr&|K_h$$>>psn_(9M0Tna$|d^N<->ctZ3=pfspN zJolBjpbEFxyS$V}=^O`H@3VE!cEUSKClY(;B%crJt)bKYs4WeVhjbZ=9!uK1?Q`bK z8I6@vOJFesr?WZ{$)={#q{KlzONRCI!y7{T97d>kWY26Rrcz#7) zwSBVGYx4Q1F+$SvM}rkpiT0*Vwt*ibJwHoSdh57as`pfzoqO`uV?D4gzi^eQzS}E} ztv`{oJn>kDm}O{+igERJ9dntudl&OFvPlV-frUi|rtreZZU&3a9N{$W$NWX%&WEe! z|MkD%)YQRuH<(*E?O-QdmS8752Zo*Sd>D3uy9fG0o)CbL6XWLqYZXLZT!532iU@U5 zLXeAznixkx@&vcCj0hhaJvqLTv@kC#9T}dYln@UKEh(Crh6I-bJog63w;*9WBN6W{ zem$Nx?6oUeHO|!Yv*dgA{a`*9t;C?3^bn!7Q|Z|}e_WHwfBZrb((l}f8su=cb3SW! z?dhI*Zm?4F#$E4JGV{tiUgll#*Ov9@!dY{T17Bab*ew59>=BL4FG23o-nP{!8-50V zwT>0J;p);6|2yOiCvD=%GhLPA*h-sx>AoI6EnYt)THD*N)d2{+I^W59kWKMpEPNG5Eqyo-uo8llHiO?l*xn$%@ z*^Q`q9B#gmSmrB>Vam6-+lue^uY0&r$g6Keglm$BpgAIQ^;X-Rtuoj%)t%mPK6IVm zw0%VCxEkAE)R$%C;v{N3$p4(;{!>Y#B3F_X_bG({8pBnEnl!=fVAd;Z**>TedF2Zo z4zc7K;f&GC2^CK~v|92aZ#G!PD-7Q7n7Gc-6!k=tN%fgalj-Hc{a9x9J(CT) zdXi%V{1gu5r$rde@;^3Gc(-fGTa)w+Z}-2cZ}c}Imat)Zhh%^8_YzXw<9*`T`ILQo zYd-JJ7MH~EIQs}m@GK71Tet~ImF%u4X+5HXEm z8g@Q5zL~H7xn)0?|DA+m@pTh}gwo>{&h6$MwU?gmf6t;?txi+=M_K*fYWohTsIIo_ znV}7JD0WdsM8QH6R8)|mgH*+qXq2j;GyxT(=nNgDDE1C$5{+H4MO3X ziKr;wK85e~{oefR|JM4~e_fkB=RWtG=RR}2bM|J=45|ITL>+A(H|^?W^F@yWH-)>k z9V_+9yRvfqXYZTu?%cOodSR!N!RL_Xnnw*c>!dA0pM@j7Zd=+|V)S;1@6B6lH>_H@ ze_r0c=Y_wop4)giud!*sxdWEd?DLMVnV?-aPdW9_SnT& zdprH%wY2YkbDaT#8As+xPYEK74>X;avUx<9N#B#|Z~CkXo%HL^_rA+^DNkA!`2CII zY01*+laECUlm@2mzO*(ZqGzq1)3^k`I&+UB<92shJV7wNch^CIn;fS|{&-ZgxpcwK zpdo_9CyJx7zln{W^`0{K<+^uWZ)gwh7mh!F`|;#c3l-aru*vDwKbRj3EXv=$d!xs1 z;yG&RF+wPCb!~w}Sw*a)p{A;YKazl7&8mp-c7v9>hN_ZSq#{sOmuM)dX=$tK2sM>d z1ws{V4Y8(3T@8mQi?uZc$|^#Ux{{WTszgIgOI=ea#`_j(N~$Uv0*ST;-rdmF5eXzp z_(PERRk9l1>CzBMbhK23n%e40;)w4Zo_&xh)))@-`P+6-$AFugH0t&YND44(9i$w1Gh?X6=jdb4 zLb}|J5JY-j-&v*;@jAE2wD_x|f6l(lX4R&gqsRVe{c%a(hF@RK9C>x)j-&?X!E>gVnz>hnRe&HQ>Tc}(xf`S zzcfCtKl1g@y_02brfWTa8$CL3Qf6+d^!V<%U%l6^Yb>3;YtqzBU5i2o?XI`Eb${dJ zaf5&UIKbM`aB1s|CDwyhikjQL#95juPw#q4{&1+7if7mIwZ_pkj~orkJ#S}7&b>bW zQB7i`?6h-{hpc-{)(z8hhlTLB>l!O zYmToC5hsVe`K$hE><+JqpFEQ@O}}id?3+9Gy;G=%Mq-D>#yv5yqep&eYYn=szpL%e ziz1)eX>rZSp|hjMTnjfUaLQ!gwDD&bty%oyk1-35Sd1FE=sUgo^$!Nh&8;n3f7$08F=oh| zntGr4`i5^Ul14h~&v`n&;P{hMMXCXzpS^qN&h7Q-Rh{0Srw2Hkue|@@yKc^|83y_q zF)y`l41T`ncfZ*O!e;KuxS93r?3u5XkHV(NOSfi}eR$KhI4Q_CNGZrY^~>dhMs|NI z?{`~R-#+5H&0Ocyd3*Z4$#jw*oH;{ae`ZSILX-W?r#CvgPYSk}dN#jxs$cP!JMZjT z5?d=yXJ=M^oE4auccgaGyZI$2Z?wAy4}9@s+Pyw27U|4Md$4xqs=V)F+~cN{NLNkz zJ;&#+==}Y?nkO4oQcih>?9$t|J8}N8p39D_-`?F+q;5XT#(h|o$msj#oHK9K2NZ4` zr~5UgPalVqz3%P&NmKGJqbTxigjwp%(ADgtf0 zRa8uVec{yIW2GyLlG|S_=<{8J`XiOtEa4Jw2aWhqCQAyI%#44SadN9~e95%(UhOBg zT}_%a@zv3v+QnCwIQ&^9+2=c>C7^HHli)))x}+D|8;p;7dhYkwD!==a8_o_MF|xMi zj_t5b*Y#6E3-Uhc%x)3{$$o(m3x^Tg2_rTGMrZ1PR2VTO7_m<S3t+^2 zV8pJ&h+TjYbA=Jx0V9Ub^Bw99Bc=l*Rt6(BA4cpAjM#b@v5zofGhxKGz=-__BlaDP zSSpNIKNzt{7_nLyv1}NzMKEGxVZ`Rai21{a?Sm293nMlfMr<66m^F-;4UE__7_o&g zV*6pl3Sq>Cz=%b_h<$+(i-HkrgAuz5BPN0o%ZCx$10!YzBUS+;wirfC3?r5aBlZ)F z*c=$K6c{l>7_kx;;V2OBk^h7_sdz zV!AM5%VES+VZ>Bm#7@JA<-&;l03&t{MyxB0*k>5AXE0(eFk;(a#GGNoR=|i&gAscT zBc=f(CV>&_3nNDKU?jM!`# zv8OO%>tMuI!H8AEh)H3@ykW$8!HE3^BQ_XDtO-V}JB*k%jMzCCv0X4?yJ5t7!icHC zh~0q^3xW|F2qX3vjMx|$v86C#gJ8sJV8q&C#NNV)-G&iMhY>S|5z~VaGl3CvfDs!8 zBUS_>W(gx^1tT^AM(iPsST`84lQ3eQFk)daVmUBkjxb_FVZ>I#i21>YU4{`Wh7r?* z5mUg3-Gvd$gAqFmBeoSrtN=!=9!AU^M$7<4>;#P12N54Tlj+gb{Os5$gjZb_GUkGK`o9j94&?m==s!7Z@>n7%@{AvGXuuk72|P z!HC_15les(Yk(0O2_tq9M(kG@u~-2WH4ftFk%B>#Jpg{*20Jh zVZ@HWh&_T4D~AzFgAqFhBQ_mIYzmCnQy8QdzR72Cy6$2h;(BfnR|+z;mD%unG7cxCx8~ zegl34J^^^6T#x~{12w=v;AdbK@C4`vtOF(ke*hzZW55F7Z$Jmg1N?vnzyjD0!~m~> zzQ8tM25=V`3!DL#0$%~V`H!_3Vyx2;V||7g>o&xKA|M#J59k4_fbqa(z#2FN%mdy6 z>OeN&1)K*40lR@n;2F>p*a&;s~KS3n{@MrL6uo_qmoC(eZdw@N_=fLN{reIU> zFW_Imv%#~$Pr*;Y-ND_#>%r^6Q@~Te*TL7pBf%rV$HB+J3&9J)AHW~L60iiE56%bs zgZ;smz?Z<5U`y};@BwfvI2POjZUGyCjlkQ%+rcxzGr{-3_rQ)|NAOwjS@1IOGH?gD z158#Q$p9%31n2?*fE5r2hyhQ)42S@F08;@wAPGz?VgaQwNZ(owWeJLaRzg=_H86&?t>RdCWiN~F5raISJWe?g_l&+GdsOfg?N~J;L zRo5;P1GM`X>g5dGTc|R?q@Qn}>Em6LWG>U!;q-n={C z^Uup#Lx)PwnLc{xJS;9gRY{(@qW6Z?<$8^mGgQ-3&a2m!%cMa;GTnfH)c%D#E(~dY zsI9Ff%otH}IIr)P0)L5*OXkRuBjb8aolv7%RpDuu6qhk@XR%0MS3lo!?~dxO*DgEt z3kt4ODyvMhEjj2UlsP%;2TbsFSTZkHJ4bX*PRjSG})zVUxyL8cQ7~B5N z)g&yaq3`W0Iu=8Xy)`{uG`#&UdWG1q)=(IunXR~7vV zi^vCB)kfOdiVJ(020SZrr!Rb|`yCew814mTB7Cm$^znL;R6oFCMD zKTSDJp4olPYL(%())zEuD;sp`E41wGt(>gm<_WPn6(1C<%vR4*xD8L7cVSH1`?_9# zUa7Tfdz02>%}TGHQ^%)>m3rO;*u@RZ5=*oOM|FLhZb6bH}nE5AR3YN4VA)<*cl0*njrpp)=3xKEDp= zd8v87+P(3rSvm!tD*N7~=Gx!?T{o^^d$O+H$>cD#YmXito?+(iAl-G~WK+uBPb&t_ zF0@z_pHg<%ang_nn>^R7Z@E_LI79z+dq=-7y{DzTs-9FJE1se+O6}#>v8?I0r|u~e zbh=E*JNqHQCQGqBr`fOl=bw(IoIl>DY*$#t_9@o>>a%A$o_<&Up+nZ5d)%n}%G|uo zj~Ba~bvU@uyXMx>CuO;z8@DPZ8P={ZiZK_CGaHvxJukMh-k`$e#9J4&DEn;n)R}i) zNdlUeSuR~xcKz(K0Fm;h&)+v*@%Y7}=lE`8&&C~)8&poUu-Y)Qs&YxZs6?WX(y~ml z@5{QJ{ey@8;I-*l-ifT60fnumX)CAf?dw$%yQu5!2`QVCPq!q07yt90vR(U>-Trb@ zO8nuIO6Az*Uh`M^S42)OaC+9#-{P&(?swXzLB}2~5G96xS#vmjQNe{<3sM#+7K(CL z9cbJ3!PD-4YtlJ))6mb6kr<8|!9J}<71$TF4OPzb&BuXQ~BVNTo9J6#?WFNjduc`|?P4+Wn(MtMKm zKk7Hls~tPXtp4z+e}-4|r&9Z#Zu3J-e{7weQn=!D@MY(|ms^$hH5BYvs((D!SvP)n ze9Eq>xB|_`$@;ZL)6D!^L+pQgUN+qNic0h6LD|kJ_u5MW=N$8&R-UZ0$4G3nzek2{ zwD?PVwpqtVy?XD+{nO|4_pQ6DU!CJP^s9N}m332F+HOv`^l0|9u4{8V$9ZY(I(D)& zC4aix+^g9m*948;eO~Y~aKb!SFYinZS!u#`!`Ze={$4syG-A>V{|?K>;;NvtSIU$e zuSK_%1YFMXP|NBOBbem)xVZG%lat-#%EdNzQ`XN;mR@(rm)x~k+!k-+U%%&v>Z}C^ ze}=>oEM9YEzFJJ1AharCG=k+f=DupBsg_(L=2J z4ha){Z?z@sN$G^;QkH%3eq5(l^IrX zvyB`}c9V7D5`e7xbp)0H9RxUl4AOxEI&eS-4(PxE9XNn4h{rLwAkcjRK{^=>+iAS% zRt}saZ@yu}S25BR;+W#Zc!{mWbfFDtkb$Y^5~kb+OrfcmezG7F@`=R2RN;mx?Hr_% zE2bEKOut0Nctbi=;w9S|FV#wz8q)F7t&ZtJ4hdC(X)_a&tP)d}0#l+8Q@1Lljuxh( z989ySn9{v5^Z7Y7}I?%B%cRlMINM-81kkX(oqRh-$lr`3{0EZnEo$8Zuw$r zF2__Ugp{d}A$grJZOSpt*JH|D4yl+5X|@9Ls0?yO0f`}jMDl>V(!g|H z3CUUy`Iif6mJivK0om&f+3Eu+mjbz@fNXYw6j6el^My>Vh1AT0Y{d)k{cw5KO`0T01)m0 zB_&8eS4an6RV>(ooOXp|t%g)hgQTc~gj9$0bcZZehg@uc+>t@riXh?3Ag^;F8B`!q z>L9a)kQ`-XkqqRCA1p&Qq>VdlfeWldI^?D@q*^wl^$N&WIb>orx zkunxrK}LH)Ql~<;s=*$dgN;yz987~0y#RUc0%@%QDV+%maUQbO2i74KHYOi3wE>b( z6Y_2xtU)F@PqOis34xrp*ym5^LmAs4L-l+EY2DES)4PepN`L&)X(}e#sFB|!0IO~=WPXNQa|g@nAFeuGe&;ipE2^Y zIA>Bn>(Ar7jpv{4M@qUNL&#aq8Tr{l4H;v4{V{it`UPNm{lTPu(x%s+#Ce-ue-fAR zIA>Bn>(3ZR=(7Hd<-D!nOzNj`&ZK_UpE2_D{)~~I#W|DuS$`hqZBjqkPc|`F$XU)A z`RRV-XK~J?esVm${f-|X~^=C}#XZ;zI z`dNR*q<$LbOzLO-d7QWD_2=~y7bL`q5JrB=$WNKnPve|P{iIE=KN$IGo8AAUKWnr5 zA8{Fvb0+n({*2MU{Ydjumh-lPGxD=IXXK}CkpRmHuBT`$j{=Ok)O8d^~dp~ ze%7Bcsh_mz^(S!|k8>vV(>P~=K!E#@Uw^Qiw-ua`pT#*NKW)?NPsSJEdeZ)kN&P%O zW#ngZ&ZK^xpT#kPnB}L8{FITOGV-%HXXIyb&ZK_UpE0SQwCVLH=O^QF&ZK@C=PW>- zJU?YQZ!0(>KZ|ole%hwjpNx-sd4ArXwdwU|`FVe~pVZIuQ^o*f6AC%YIU_&Wgvd`> z$Qk)*n_hqVvMHr)p@1=|ADfWWPngtCSjd^wPuh6>p^P86A8CHda^6;OMt&COjQq4s zuRj?d^|Strk)OpmBR}uYmg&oJswxKacY^z5ew41-Kvi^#>zAZ6iNrQa_DzCiRmxz5cYnl*I*% zN&Pg=nbgnv^Ehvl|Nmq^(%?R1|NjZgd0WAm)X(}eCiRmx`TtK?fI4ZMGxD=IXHq}w z<}?i*rVPZ1{#<&T`Jkk4?nyf5c_1P3kB6>GdaZft0q{{V&4#vo^c`Nq-*aEayz> zr{kkeo}aRuw-ua8{WQ**)X(!%CiS!aj7j}8&KdbxoHO#XIA@#$?nhQXVdST6Sz5KllobI#-x7M zpE0SQ#yOMvS$`hqZFc{QgvgKkkzao>^3yi*QzrG(IA>BnY18WuMt&COOzNj`&dAUE zGnVraBVr*Y1te$uAbA57|J{TY+`X`D0i^Zq=}+wA@){ROxm`SoY{XQ13=lyw{x9RmqzRurK5+?7zj(q*k-yuor6bYotA~9Y~$%aXV zJQj(icue+7u^*4IiO3bFj|h_(8vIza*<&1ofJX~N5`ewuL#n7_*hyCOrIr;H# z8#_MYKSE>_{N)@ad;@P#KfzFN=QdQy77r(5mB>u|bi=BkK!F$5JousQg`e(tOt#a? zEnhpu2pf&H@JnsF;gZsGtN zj=?Aid}QNqVsSM8B2*hpkK>16WsxS1>O6B-jF2D*rRNd%Z|6B56~$wnP6EzwHnN1H zMK&wkF8}I3neHdYbk=8uwN0HM-z+m0?VC!h5B&*$jdb00yU>i~=si&ZQ0@jNGfQ~5vl z$FNw{LZ0J)@Ap5S>CWevFnONsXdfq3kw9w-!5J^YGw^@wS0CC>@VxUsKgMY}b=KZ7 zDLP8JAUr-HGB(D+*xJI%SQ;J^8XJbc66at%X`;Kiov}0_F*qhHI4U+K+`)KJc!IIx zSZxh$4g27Pgz)H)s6|o?5tHCxJU>2WbV6uEcyw@rd30oGd~8DOtVHwB*yz#03DFh{ ztc|77!7-7u!V?lFf13}3Nu~cim{(YMOk!l>qJKLl3oCM7F~QL|0pCSVadAna*#9j311K%C5<9K9gKq$ykZu_&JB+@md=lK3JncUz|%M@ rI4U8W7exoL{D-sYJax-|J9m4_|9%p1fW2ks73%yG(Bwbd|Ks*wD)8_H literal 0 HcmV?d00001 diff --git a/bin/x64/Release/ufr-signer.exe.config b/bin/x64/Release/ufr-signer.exe.config new file mode 100644 index 0000000..8324aa6 --- /dev/null +++ b/bin/x64/Release/ufr-signer.exe.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/bin/x86/Release/ufr-signer.exe b/bin/x86/Release/ufr-signer.exe new file mode 100644 index 0000000000000000000000000000000000000000..6b33bfebfa6abd0aa27df5b6f1b94bdbae2874e4 GIT binary patch literal 2177024 zcmeFad7NBTnLm8{-l|($Z;5mh5Zzn(>t5ro z+_#o~uIrR`y8A&TG0p(r(+Gp_7t=m6e%rOw=tlV7Usa67ar|?^)lhVy z%p!bJEX~A@3tzPZ;V0^l7jaTnU+#bm=&ci8!#f`ck&R_%L091(``ZA8On4W*+yx$*O)g2WkIP}Ev&b;= zrUSP2m|+a8N*NdXw5I7og?X>bFs^Wx0CGop0cGB;;^IKuyn6sHW^({lJL(=NFi2A+ zA0COp>tpbdAv`weWtah^ø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bSPtqSVZ!>0)Ut-^mv_)7|ZhVYja{wu+eJe@FQ575+TodlbH# z@YfXn65+1`hHq?q0s;C+${{{d03axgAOwIqD3Pjz-H>RCC?=+WLMs3elu{4^06{SX zApj7RR}caK!NLea0OUsr+Z?(tbdv=US^d#Sp<1F##F!OK}!qU`_)Z$4i z$Kio6FT8%h55r?K?*cRDKe$+kSH6o2Oe{rnHsB-0sk7YmO}4z(Aj5anj_v@?)D~fP zH!M0YN95t)G4ZT*4B73us=x7j`~mF#^nNoZ29!7 zn_hw*8Iy=^xEB9?`YR3RJ;4^|YFBkbmwkZDDI7fL$+OxW)N@{^!gz9oHx#%B5s$L@ zKBqjFUWo>&b4-1jUcT-wE`gLPD{pqYkpc(J46Q79LiQW*+PS>E*IUbG-GkVA$vX|O z>0L$4Qp7hlLeQPo4hNRy-Uc{d+FQtY?_&I$5kG!##^Xz28kopUbReZsLw<$M#IYNy!vo>l_0&c3&p#5nf7H0oHYeTZl`ZUs+yhW#Sw^K9xv%ruYP zu41GGE8}7on;$QE{|q557sr6-b-fCY=J2>R=v#}nj3nfgLbX+0TQTvBU~;YcRZhNm z)PipL|2uiM#Ie;|kT+Tcp6hNyBa>tQ{Fms5r;aqG$CI%Fw&_jipIx|=l63jGa>08C^OfF2>qZV@3Fn1={<6=2IpTd2ER2!;q#13gM#nyTSTOi; zeh3<34ozZYJ^w{$JDVVn-vO^Z5@EFvU8U-%JaOVL&3BTH_z(x+Bpf0qVuf4{{$pNgQ5I^D~KVnA{vbewI zI=T!DYNrM%&LK_)%NlV=5U~iw5jFWK7I}}-R-z*4Gv_Di8It3Tbx&g(clcN$@k&_Y zXkOIRF^6bLm!M?uQig*c8gcm74QLWLtg{;h!1Qq&O^b(dnsv*PF|6f0#9Et={^!o< zpAk(5NyjQ7(~Y`wq)~$MdD7$j_H{k7e(9zO?VO>~|(< zX_cE%w#ueP5zaAhUSF@^q1 z5NZ({Oz`n_d7uduK0iL+&&20TT-U=C=|%`OIA+zeXkVy!yo{|QS+iu7fO2M0`Bpq# zO4G$=UT8w(6vNo_FqwVk_B*!5WNuui8-mW*HcLD^7fqec1?Glh5@4NAmSi7JlIDiz zL9?d=jm2}oLqAZ<;>=S+YwO#E|o2L_u&^0o@kJY+ZL3 zJYs&obX$e4`J8m;2^|&$o`WAoE6DeLIPQJro8ftF#`&a6IVNVcd0WbZCsLUb)oST` zq*5Z99Ll6=E=Z)I%+H~_L1fW%bZzWTDjPiGoZX8{Fo)6K5vt8 zv9y`A?1yBTvFEca#a&*qZW=>r>Ad{%mr#B!wQb!bK1kDHWTok_K!PE-b#s`Gh=LhU zcXKQq^(FaHRY>=~FrDfyp6-2qx+9@)jOmarqbsLl7Tyi?+al`b`b$+>sa+bMG)%2G zKO|!;&LyEb7-Pdr;%pz$5~N~ zo;sbu(YwXWUOv1j;mGMCHdIR93i4bebQ)ZG%S`?dZ}u>MmSt;DmZ#Xhr7Qx0zmz4k z{O2f34ZNPa{>py+p>AXK@Z9wmrTeedUzASk&*qtCJaZHE2X|URcYA3eBU}$GG5u@mJsOgvMMV>80U~WWngzqLF;3UA%Ht)_mz$53?p9rik|QN z3hK-mq$_ps$xl5LZ~K-@ZFdBOo3UTqT@*4$>}Vs2GhC_w+qhm!xw0H+j%ku z=5%&!#96`{wwQBQz3zpVxj5#?U3j`?@L3k}9|ct&r{@pmaUfPW$IIo6^eHG57S1Zd ztx@F-!KMEF0N!N|wH~}&@DdWtcG0imsP8_1{SwoN13?-rTRz%x*;3}HCeE?M1O+Pq zhB4Au=I68AzpvmO`)?1z-Nlq53Se^`C3BDFKofWDt3$bGzmCNh)2f2m2DdgJRiBR< zpO2a#3RBBtz*`m!#K>EI7{E06(dL7a89S z26YTV#BR}kCLi|Cc}f`vgZ@g^1^lh_{7BYkXUxoQ2Q`(yIZX1n-B?X|B%c(|cQrge z9nyC(J4yEr2h*!Oyv1}O!>k_HGnwcMgQ-mF$O^sXS?PC_skA&~rlD8`T=lgJB4HoT;UVQF-A*PoZxz)_xRCjMGJJX+9 zb0K2wR2q)7-`ZV8a%0Q(>3qhXF8u%4Kb0P`mHwG-T#k4L77McVC7m^!MK9Jp{&Tzf z8&=}xe8vX~y{XcX>ya{3>EC8=ML(4>UWi)bUnPGX+!aPc-jCt8`Dwd9F69$8$eR;qE-$jC^gztTSxOSfng7?8zri-D`|LLuc6 zk6AKWscMV?J;Ya}3 zAbecPtn^>U;c5%6C}mcUG!5fAjxk#BL_wI&rv;A?Y-p_RgglxGz8EZaY5-msffs84 z{uhm#d(gfCIb^;*WH!-tr8c(E0b`>X`deJlsiw*^rJFI6!-_g_cIjrF%K34VkC;+P zW)poxYH#yRBiN*cZX=aM|2fg&hdJHWu zw9v1mGA(qoM5CMJ4Z{KTS*8){xGCywQWveP2{J*ijA?@<|@d;&WvfoIlP;${0}Wnl6NWmDy%X8C5*EG_S8tpic6wU&8Hc)f%V z$2I7)T0vbmX+Nnw`BZsZp|%M-3DEbXRxZO~nv6i^dwd9YnwE1i%Ay}|vheI$h~iP> zRYgL%8uz|TznKAZpY`gS{CFD&(d&B1GTg6b|7i_vydiIP}m`)2ZFT zD?DZRBFo?^@GC*Tuwa#M;Ou6}Kzd}r&W-1*Zz0R{@PKWl%F{y|%hL;CuWP;nY_8ja zu@ZGd55GLM95bHu%1k!5{uadY5b%{SdtbY6V@q3pQZaSO^d)E3&dt`xvzZC(P)kOa z5vjOJ)lWfGZ36FG=2zhXI>{>s#G`O7g&>#3kSIX5kQ?uggs&~g=BBcXr64t1OD8QY zb=09auvzt%rXA^ehgyJIb2A4+c3y-^!d_Ep=uO&i4BZqPcHa(?%7T2&FZC>r_(|Ej zML{?K0?ViN@XB9+2d!urxo=!Fy=Z!5dRolNpQEN^OwaWl4Dh&mIJ3xkDYJ&B@S*g` z%!uG%@k_vt#gFGTERj;W^yE)Lz}9J%XPg&DMF2MmqTsB5X7pV3k6K5MvY%SV057O@ zJoKbg&Y8#&rZEH47=NsGMYfLb-%Jdc?a>4D#jzxKUOPN9B*mz$m#SZ6R5CZrdzzJt)z^Rz*dxAX}ESG zR9G(&-KKbMoZ8er=9j@uaCBP%tH^|^}3%^+B zgQ7BBr}$JTGo5awKQ*&@7FwVqh8q(cW@=m2Gk2C=srMmpga|U zoM)!R@AIIU&bj-PJ{jbuJ>xl?{Fy^)BxaSPjM+yZzu2Vd%)m0sW|N)CbR|C>2#{Mj zy8krh66t`XZqvrG;QA`IRn$76BxB0cR;ZXJW)FUv=@hV<99Bdw z7T=U&^98?{f2+M1WbA=J#v2-Uf0q)@rr~r_yQRF-^vb)%IdX&8_f|*sHtzhOb zet`})hE{Jst-TM`7*{SboZdaU zt!iL=y`?>(JnheZwjy6`#8hrJKo=;QjZF|(NTg?~T_)Pf?Cvphfxo`9QCbJVQp<}uE= zAuuax?^q)iyTbP+sqP{Y(+W^)H=qPu>|ZkZc&&&5K#0aO5umHPkaF_jPAQ~>lMkEO z%+UF(tTMiXcwegrOap&*CsInkqwTwY>NH=8?SAR`x9r5w4Rub(F*MW@#p z{G4ZqPv?QzIS?$GS)B_YGzbpv%d7D6$yTzH;CNaeNlReujbP$uqtVmB^g!kXA{o{c zR1oYp6sM4xtodm&Gdh3B_^F(8Bp-^}+olT-B`j>x25$Uh#UpS*G zt;W7wHiDrke0OtN1eO_%jO9!j=a~vSryv%|Wbih2i?QgHPJ>X(I)PLk4ixGg5B?L2Kf34L?EWHxm!%F3P5%AD!RjW1Sk9Ak6FjHAe$ zx%RBV*vH5%oXX|OGd*i)a7w7rdY$$w^C5|)l?Sne(Up-?Fr>n2s~Adbq!1Uh4a%3YQO1W z_GCVd3ALWe4S;`lfU4zoT;Us2v9FX>B7C7@j-@Xw?$?SHJU;c{U=- z!f+9;vML@U4EwqB%qG^;Q(H8NFqIlui2+q8-MRs+JNf6uR>m+0F5glA#W_*RiRTy0T|< z_^P#`X159kh9&gW&_oZvqJ2K}t}7VLCBP0JeH9EC((6!$76+@Bm>K6~pe-Pdb)-n& znF)c}duN}*>zj#TodxGdA}Aw zl|e0p&Yl4f|HcK@6Iuh_j`qp~Uon6}#*Wi<^+1>5zj%_dju)KFJmqH65eMo!sZ8(Fk!T%*eq zOZ>JmwMdz1t}|6WUR#~k2Znh8oybLH37rH$pXtNvg0MktU93wa3S)@_tQjINhz)dYps)|FY?>TJ;$ zsYN%r(po<&*Yxzl(u!2iKm_0kAUBXH#`p0P_7ZJ6GSGqyNU@o_eSV&2JDk zxu`vWK`i@?0rb}-lBWWAl~#QXW@^*W+yMG(dNKxgb9X&+Gn)>slbBAOpD+4D;nhe- z{b8LGLklLs8OPJRi}?*#iY*5i@GRywY>sfd{S0<*I4MG6F|<2~KySSPV@e}4G%XWQ zpB^Lyp9DiVkZ<6c>OLcH-CyKe_7gdE#lCn>y=P?p?5AMZRE>|6x9-XT6rtn(aFs^;8@TvL9J_J|gd$AED-D?kF zC&e>kmf1QEtW3Mr4SB$;RpcG3OT2UN)}r~Mf%1k1N~<+{(@UmX!-PP{yAX143qpM| z)Z*X{A;u2Fnuj;FI2`mD4Q-U?+%4&-mA5{Al7W@-P=pMt%Dw5u(~`$w%7eENpNA}# z=xWT!#ug`!$bWwv?}WYp8WR)mD=AQrz<7(WDMh83>Cu6)~QwjNyZ$SGF>UwU#$e zM8eYc!BWXZCEBeMlusWSIEcfH2Pj+jLY{`Y=bzbR;>y?rUbPeG$_WgU303#PHBGQ8 zan`e_Vz4DT8*GU#2UW*GD$F@6|H*Yy%eWhS)t-Dqf`#R3P;(;vpZE!2T58a5AGQI5llgHP>rSaUMDbN7 zX0B?tn$&ixl#W!7#~R!b9v_RZhMSLkc0O&-6jJchv{ipcA)1T0c3Y=)e%$kz1C@Q6 z#1sRIWSe2Ko#ss0eGRu+xA6MUG55$`qR$2G;hEJpb7Kqlvwi{wgKdVz)W|BRVx_33 zc@(R0snuMEI~FIJGBt!ALKB-BOpTzvXYm*%HL_Ry58a2Zj`;W`KK_qb^k{JoXEtfp zSg~)3dHXf8fjiUi(i}a^=*-Br>CtWIN@+M{%#0AXrF?#Oc6hZp+?~lT-nKPZwMl0x z)vbk0rGo#>Gg!>=+mxAc&S1Ndo)q7c(^|$BluH$_VVQGb8<*hr(G2BLNgbzeae2xw z+rfB$pVDJgu38yN*{fp8h!r}ftsy0@&(n<01jT0uFkVWny+3T<(w5h=D_z)=XgF+a z^5zPr0(`J*@Li1(eznDB%GnHIduj^+ohMKh&^AK3p@~#(c&cCuWAQ(^dMYL@di_0FmYnl|V&kjny4ztRL<-AAzoy$tY+C!E^8{{f<1N*~ek8i}vv|Wfrmp}GX*U^T7xH}#+f6;k<)Di| z@qV*O!V#RfFTkP|D->8fg@G8Qu-sRIL%^`qI+uJ)tu4IaS$w{A0U*0|KEqAsU9VVB zRI-o2o(ATYk-h@Yyz|D#dJM{gdR&hN+oMLsfhZZHy)YX{K<4}`5I%tpXc7@DWidTd z14tCW@(c%~|ChZl0h6n!_P=v)_r1MM&)n&m?wQHb0W#q_-95=n6eb~%fGmo#iZ~O5 zganX9x!9ecX6!M5prU}`#f6A@8h4%>?knK(T+pXHa06l772Nly;`0Cf&Z&F5X9=S6 zp8xNCZ@zE()?TMhovJ!@>eMMVgX+G=WYEe>@eln+>%;iJKLHIdX#SrBUH{)A^42VF zN`w4fh5zvx`~V-xVz*s6+V-?rDQP4)nn#brSNZ3xwg zX`Z(h5}wXOX()wtnh7(-)qis~wR8eA!`O?xC7Wi zUXB0p1HjaI16#8x?BY_$<+&&cUKg9Xl63Rs3v+uYZBZd(zFF9~N&6W; z>jYN;PV_Q67rN8C{WSh@`?Ba~@#BQs5gpZMFV+fPP30*AgFT>OFKQ1frzOy-e+*hs zI=B9*+kRva`aj(}>Joc2(SbSs%)?G{e6Am#Bt<{i0a9q(~#lskPzPt9r$US!$pz9y@ z(EYn>eq`E@k*~PU;H+>IgC1WOUsxRzUIG&%=T|?es{Cnp0_rv{#yCQ_h`4Z5{Lk)k zp=#ktANtie58ecG_EQKfrjhcJRT+2DR&n9P zIVSJ}9`gu~V%dnY#tE%%0^IE+$qdstzfk{QnYUeC+Za$8#HD$z+4T6i;$A<8H9P0$ z+_4{dVYXr;j8o_7p+ld=6%+`sqk5&%+YXubdYO&Cd`;t9HrlGh5LBbmdiZsxk5*}iT+<_B5A2T^$nQ^=A zMEOsvyRg3^HluESBxdKR0~SdKXq(WrZ1%h^U-u%c#s>J^naVT&=X=T>d$D4uxVw*x zjxbSzvf^%UdINxVcW;^)fKJ_RG-7|T{S&(jU4B=&dnF7!w$@_ph6Q@x@7g>5ZOTl# z$~_|` zM!LT=bTR!$*bZfLlua3VL^vsEoe;p% zBh#nlM~Hte|M`)D=d5TmiD4}p&fX@;3|mXcWRli0d?|~g1}CG7k@9A8))tXQgat|5 zS;K?@@_N?OA@{nBd8gK6OJz28d|Q~iKOPN`F{O46{VC{v>E?eFyDT2DV2Mj5$`7t~ zK!4b*G02^4@≶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/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

    !vv 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