From ef9cef2a6f69636cba0926ad9db31b45c4af7aa7 Mon Sep 17 00:00:00 2001 From: developer4 Date: Fri, 6 Oct 2017 08:28:50 +0200 Subject: [PATCH] version 1.0.0.0 --- .gitmodules | 3 + 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 -> 2496512 bytes bin/x64/Debug/ufr-signer.exe.config | 6 + bin/x64/Release/ufr-signer.exe | Bin 0 -> 2215424 bytes bin/x64/Release/ufr-signer.exe.config | 6 + bin/x86/Release/ufr-signer.exe | Bin 0 -> 2215936 bytes bin/x86/Release/ufr-signer.exe.config | 6 + frmMain.cs | 2492 ++++++++++++ frmMain.designer.cs | 1796 +++++++++ frmMain.resx | 120 + frmPassword.cs | 25 + frmPassword.designer.cs | 102 + frmPassword.resx | 120 + lib | 1 + signature.xml | 1 + uFCoder.cs | 993 +++++ ufr-signer.csproj | 1626 ++++++++ ufr-signer.sln | 32 + 1499 files changed, 244966 insertions(+) create mode 100644 .gitmodules 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 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 create mode 160000 lib create mode 100644 signature.xml create mode 100644 uFCoder.cs create mode 100644 ufr-signer.csproj create mode 100644 ufr-signer.sln diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..b270676 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib"] + path = lib + url = https://git.d-logic.net/nfc-rfid-reader-sdk/ufr-lib.git 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..b301c09 --- /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 EcdsaTest +{ + 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..985d563 --- /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("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Properties/Resources.Designer.cs b/Properties/Resources.Designer.cs new file mode 100644 index 0000000..733888b --- /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 EcdsaTest.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..14d9d08 --- /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 EcdsaTest.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..ce7f40017be70971841615d3e2125605fbc5986d GIT binary patch literal 2496512 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 z%0D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 z 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
    BekW$%gCmu?j4&gIFS6@e_l#66Y(9k5 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�rbL-sDJ(f2?9z&(&RUL@F$U%<}LE1q< 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

    &AaJna!0)0J2EsF!2;rc-QemtHXjOJ1h- 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

    l6Y+C5?CaoI-t0gOKQ9Cjak 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 z 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

    #KgpaY`y4b&2PE-*Fog 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ðAzycJc4 zQDV_nm)uFB`3>h?{qsC6GBBwOp@Fq?Vq3O?T^tq#lJW@XjNr+?v2-X zJ}BL8Y#j(%u72W6qiII_1y|+puFTh`gJ<`i@35nyoe+HAwj8TJFu=+}(O<-NP#WzL-bdGSEfth$yT z2M+$u8f*Xcr|OUJN{b~=R_=K;7E%4}X6gMfThR?m4mZp3C8oVL?zzAAp6$n;6AeW^ z!$svweI~|CA^uk29@qic5g74#^miG-3*xheSVr-hsA&@L-%8jtiV07W^qj?hJ7Zas2p*ZN+dQg3=%dL(gptxg8inGco++p-dAYb{NvGqg1lR z5aWU2mndUgP&zc>BikAu)jSv)vhmR^fZ-wyB~&AZ&0LgZO&GG$F(lG4bn~Lr5ynuI zk71SiqI}9j$;F3Zu^GbxFNWS`3~7xRdhJkZG@!(5L762+3~v|` z3sElRp?u@VQ09(utA&`@fpRSy!_N(rK_VF5Z(}HH#c(BrQo$ajfHO)IK9nkA7&04B zQW3+dHHMvLls;A%dh1b!*3)DGG-&noa-nR3Q#hnp%fBB+1Q4XOqdu(G4wJ~ z1~#I!6GAE2fwJHR%1t*6@mVN0`7zwLqU3WxSy6~miXY|8HI$A#82WCZe9OVGxgNv+ zZIoMX7@8X}RMJt(TtgYN0YjWKhSYSFdJQN|X($J-qtqeF5(X2Kjxbz1qSUa$Fv*8f z;3i7f0+ea%QO;Rm$S=h3+k!H~38m;Ql)P3LHq$W7w`0iMfKo9NrP)T5NA)OY(otfF zp+s^(c_oP9ya^?1JIcQTlx9UJn{rV0x}a=zMJbnoaw#2UvkgiS9+Y!#D3e=JY8Ikw z$j2})i1JGq<(eo;j14IJ+))w~q5O14IopU5Edym<6UrSYl%kF(bK6mBu}~h}L>bLN zsbq%|!~-Q2@dF^-13Ww^0c}w_xbb4b7RqT`l&sfKs%D|2XhR7pfYQ?*WvKwl#SWA^ z9F(>Ul<@T^uM1Ezuu!73q0FYEE(v=tVg2BXOE0obrD5*11w(@}C;b!>v58-}B0d+>C`5d28}Ug$g+%_u_C)>!B0lM-5b;Srg+%_`{ZmNf zPxdDfJHYG)GJgWosCGJqME+!d5{dlD{v;wk)t^MfC;b!>`IG%AKh?(h=dMRc?t~0F zg=rKbJ~>f?Ln8P3W9%UErvbUwA4ud+w7J)x@KbH>^(Xus%1>`(crHjzKEo>=&SbPCfbM11ag#3%g}68RI` zbFV+QKbz`LB9TAWPa%;%cmEU;`IG%gL>O_dpTaZ>(C;b!>`IE;_BI1*N3K1Xo zBbh&eh|g^!J{J+6^izoVq@O|}f3iP`ME*pZ$e%zWf3BZGB7d%*LK=;R`;g3^z%;6z zP9frxehLwv+a~fSwkPr@`;$oIPqY!A^dnEMpF$#kuAf5e0QVzx{efvz8}Yg85ufx^ zi1^$#_xfXdB7d?!iA4THn|u8UKZo*DNaWA;Q%IxHa350FADBk9(&fj;B9T88pNoi3`Y9yxr{a@-?17(*&qc)NBI0uq@ku|0h)?<{B=RTwlSt%G zw7J)xI6e;Lr;x~>>!*;0JgN9xOrzTA6e2$9rx5YEZSM6a_K$q2_*8$g&At9)e5yaW zp2(ky&qeHjSm<;L(r}|TVs?GiU=k}-J zekAiJ5b?Qf#OETBKi5wokw4KU@+T1SNk4^%Px>h&@+bR~NaRoUCy|Ccxqb@MC`_l2 z$e-*_B9TAYpG3r``jd$Gq@O|}f3iR2r`p`lf9~<)2$VVjrd$7^5^;~B=RTP z-0KfSe9})L;*)*~iTug_Bog_P{Yj)DPp+TBGz!xxB=RTwlSt%G_9qeXss1D)KIx~B z$e-*_`KdPd`g4z;hWnAa{y@a%wh^C;ME+bqg+%^Dn|uAa{n@0SMk0|v*H0mlKiQx1 zQ*Gk?Ppn56+=t}*pTIP#olYTi&nHL$-xyM3ULs)AIba)M0{==@wtfj zq@O|}e_}iC^{4VD`?I<0iTt^K3W@x=ehP{F$^IlF4DL^Ed@iO@?Q{x>{K@_#68V$; zNhI+lbFaB7d%*LLz^n&At9W#3%g} z68Ure6e2#=pTsn(&AtBI{xsa5Wc~!EA#bujiA4TfKZQj8Tt9_G{#-wWME+!d5)q&3 zPa@(|{V6|XBm>d8@>>Z5k5c7jJA>Q2qMJ&vv2ruXpBe z5NMH~bd5h}Zgg&z$@lyB^KR~VTJz-8=_f8G`UZwZ#?xoaG%*bd4haogLL~J6FaIx} z0R|19^~9SYjreZe6A1Xnf-eTqGWj>_iH*{T|Ndnmw~zR5E@Cf`4&uR|Af~C%w1IyY zZ60I1oQr8Pi}Al5rbT(voG|Ib9c?H4Z;#i+a)mkYhpfiPef_P>@U1>~!F!r}tW=W= zw%m1dX+fA=WseC&QJ4hego#W`D8WEA8qv!K-XN?^fIkRRG$P@Rp>|Wn%*VY$$1x>h zt4OR5|2KPz;BJH8gi`;EG1*HWdzg%#bWHS!#pI!A#E!vU5~w|^{KFT=jh}*SgSgvK zn_y~{5VrbzUG=wi!c7_m5FavL2J=fZFlL{B2~I84kU&RjSq z%Oql(|Gtim>0NC6GsLkNV0sV@Q{IU4_MiJ9uTUuV8;$+OW6IOtvq;0)A&%LDia}f< zBJ$sJMzn}CPyE#Bzn`Oj9{UXLx%R|=j}036Zfe7Agk1`YPWLVxwmTB}D74Fi>Gf2gO7#2}?+v`_FjT8Jqnd zJ97#Ri%E=3O!=2%($y!9D<&u!2jG@s6&Dv384{F;Bi0Ryi&N1f_mCK$l#u8Yvn2Na z@OTVWxcv}2AuJ>*9xj!l + + + + + \ 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..624e23fb7b57f45ecb166dda4ea91ece95b5cf58 GIT binary patch literal 2215936 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

    5=PdvG9Qf;t#aivn4B!Txx_D$dgk$n?*)1yk@ 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 z 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%0D}Dz4VKb^Y!M3xWmkcKwCk(P})P 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

    nx>)x|0oRX*pCQ@xQ?; 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±=GWfP3uMFjyf^e75zgNI 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

    <=f0gD$^aT035$-Yp%kP%g&pFKSQ4 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 hT9}fb|mT$eeF*;gz;8e9%nfrQaY<9u~8+ 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 z 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

    5zuD8;)$7?9veO8n@W`I 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 G;Qs=oL4zj% literal 0 HcmV?d00001 diff --git a/bin/x86/Release/ufr-signer.exe.config b/bin/x86/Release/ufr-signer.exe.config new file mode 100644 index 0000000..8324aa6 --- /dev/null +++ b/bin/x86/Release/ufr-signer.exe.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/frmMain.cs b/frmMain.cs new file mode 100644 index 0000000..dbcce2f --- /dev/null +++ b/frmMain.cs @@ -0,0 +1,2492 @@ +using System; +using System.IO; +using System.Text; +using System.Linq; +using System.Drawing; +using System.Windows.Forms; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Math.EC; +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Asn1.Sec; +using Org.BouncyCastle.X509; +using Org.BouncyCastle.OpenSsl; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities.Encoders; +using System.ComponentModel; +using uFR; + +namespace EcdsaTest +{ + public partial class frmMain : Form + { + const UInt32 MIN_UFR_LIB_VERSION = 0x04030000; + const UInt32 MIN_UFR_FW_VERSION = 0x0309002F; + string uFR_NotOpenedMessage = "uFR reader not opened.\r\nYou can't work with DL Signer cards."; + + private bool uFR_Opened = false; + private bool uFR_Selected = false; + + ECPrivateKeyParameters mPrivateKey; + X9ECParameters mECCurve; + + public frmMain() + { + InitializeComponent(); + } + + private void uFrOpen() + { + DL_STATUS status; + bool tryDefaultDllPath = false; + UInt32 version = 0; + byte version_major; + byte version_minor; + UInt16 lib_build; + byte fw_build; + +#if WIN64 + string DllPath = @"..\..\..\lib\windows\x86_64"; // for x64 target +#else + string DllPath = @"..\..\..\lib\windows\x86"; // for x86 target +#endif + string path = Directory.GetCurrentDirectory(); + string assemblyProbeDirectory = DllPath; + Directory.SetCurrentDirectory(assemblyProbeDirectory); + try + { + version = uFCoder.GetDllVersion(); + } + catch (System.DllNotFoundException) + { + tryDefaultDllPath = true; + } + catch (System.BadImageFormatException) + { + tryDefaultDllPath = true; + } + Directory.SetCurrentDirectory(path); + if (tryDefaultDllPath) + { + try + { + version = uFCoder.GetDllVersion(); + } + catch (Exception ex) + { + throw new Exception("Can't find " + + uFCoder.DLL_NAME + + ".\r\nYou will not be able to work with DL Signer cards."); + } + } + + // Check lib version: + version_major = (byte)version; + version_minor = (byte)(version >> 8); + lib_build = (ushort)(version >> 16); + + version = ((UInt32)version_major << 24) | ((UInt32)version_minor << 16) | (UInt32)lib_build; + if (version < MIN_UFR_LIB_VERSION) + { + uFR_NotOpenedMessage = "Wrong uFCoder library version.\r\n" + + "You can't work with DL Signer cards.\r\n\r\nUse uFCoder library " + + (MIN_UFR_LIB_VERSION >> 24) + "." + ((MIN_UFR_LIB_VERSION >> 16) & 0xFF) + + "." + (MIN_UFR_LIB_VERSION & 0xFFFF) + " or higher."; + throw new Exception("Wrong uFCoder library version.\r\n" + + "You can't work with DL Signer cards.\r\n\r\nUse uFCoder library " + + (MIN_UFR_LIB_VERSION >> 24) + "." + ((MIN_UFR_LIB_VERSION >> 16) & 0xFF) + + "." + (MIN_UFR_LIB_VERSION & 0xFFFF) + " or higher."); + } + + status = uFCoder.ReaderOpen(); + if (status != DL_STATUS.UFR_OK) + { + uFR_NotOpenedMessage = "uFR reader not opened.\r\nYou can't work with DL Signer cards." + + "\r\n\r\nTry to connect uFR reader and restart application."; + throw new Exception("Can't open uFR reader.\r\nYou will not be able to work with DL Signer cards."); + } + + // Check firmware version: + status = uFCoder.GetReaderFirmwareVersion(out version_major, out version_minor); + if (status != DL_STATUS.UFR_OK) + { + uFCoder.ReaderClose(); + throw new Exception("Can't open uFR reader.\r\nYou will not be able to work with DL Signer cards."); + } + status = uFCoder.GetBuildNumber(out fw_build); + if (status != DL_STATUS.UFR_OK) + { + uFCoder.ReaderClose(); + throw new Exception("Can't open uFR reader.\r\nYou will not be able to work with DL Signer cards."); + } + + version = ((UInt32)version_major << 24) | ((UInt32)version_minor << 16) | (UInt32)fw_build; + if (version < MIN_UFR_FW_VERSION) + { + uFCoder.ReaderClose(); + uFR_NotOpenedMessage = "Wrong uFR firmware version.\r\n" + + "You can't work with DL Signer cards.\r\n\r\nPlease update firmware to " + + (MIN_UFR_FW_VERSION >> 24) + "." + ((MIN_UFR_FW_VERSION >> 16) & 0xFF) + + "." + (MIN_UFR_FW_VERSION & 0xFFFF) + " or higher."; + throw new Exception("Wrong uFR firmware version.\r\n" + + "You will not be able to work with DL Signer cards.\r\n\r\nPlease update firmware to " + + (MIN_UFR_FW_VERSION >> 24) + "." + ((MIN_UFR_FW_VERSION >> 16) & 0xFF) + + "." + (MIN_UFR_FW_VERSION & 0xFFFF) + " or higher and restart application."); + } + + uFR_Opened = true; + } + + private void frmMain_Load(object sender, EventArgs e) + { + Text = Text + " v" + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(); + + cbRSAKeyIndex.SelectedIndex = 0; + cbRSAKeyLength.SelectedIndex = 8; + + cbECName.SelectedIndex = 0; + cbECKeyLength.SelectedIndex = 0; + cbECKeyIndex.SelectedIndex = 0; + + cbDigest.SelectedIndex = 0; + cbCipher.SelectedIndex = 0; + cbSignatureKeyIndex.SelectedIndex = 0; + cbHashAlg.SelectedIndex = 0; + + try + { + uFrOpen(); + } + catch (Exception ex) + { + MessageBox.Show(ex.Message, "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning); + } + } + + private void frmMain_FormClosed(object sender, FormClosedEventArgs e) + { + if (uFR_Selected) + { + uFCoder.s_block_deselect(100); + } + if (uFR_Opened) + { + uFCoder.ReaderClose(); + } + } + + private void llbDLogicURL_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) + { + System.Diagnostics.Process.Start("http://www.d-logic.net/nfc-rfid-reader-sdk/"); + } + + private byte[] zeroPadArray(byte[] theArray, byte paddingVal, Int32 paddingSize) + { + byte[] newArray = new byte[theArray.Length + paddingSize]; + for (UInt32 i = 0; i < paddingSize; i++) + { + newArray[i] = paddingVal; + } + theArray.CopyTo(newArray, paddingSize); + return newArray; + } + + //====================================================================================================================== + // RSA Keys page: + //====================================================================================================================== + private void btnRSAImportP12_Click(object sender, EventArgs e) + { + OpenFileDialog dialog = new OpenFileDialog(); + dialog.Filter = "PKCS#12 files (*.p12;*.pfx)|*.p12;*.pfx|All files (*.*)|*.*"; + //dialog.InitialDirectory = @"C:\"; + dialog.Title = "Please select the cert file"; + + Asn1InputStream decoder = null; + + if (dialog.ShowDialog() == DialogResult.OK) + { + frmPassword dlgPasswd = new frmPassword(); + if (dlgPasswd.ShowDialog() == DialogResult.OK) + { + try + { + // Load your certificate from p12 file + X509Certificate2 cert = new X509Certificate2(dialog.FileName, dlgPasswd.password, + X509KeyStorageFlags.Exportable | X509KeyStorageFlags.PersistKeySet); + var priv_key = cert.PrivateKey as RSACryptoServiceProvider; + RSAParameters rsa_params = priv_key.ExportParameters(true); + + Int32 key_len = cbRSAKeyLength.FindStringExact(priv_key.KeySize.ToString()); + if (key_len < 0) + { + MessageBox.Show("Unsupported key length of " + priv_key.KeySize + "bytes.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + cbRSAKeyLength.SelectedIndex = key_len; + key_len = priv_key.KeySize; + + tbRSAModulus.Text = BitConverter.ToString(rsa_params.Modulus).Replace("-", ""); + tbRSAPrivP.Text = BitConverter.ToString(rsa_params.P).Replace("-", ""); + tbRSAPrivQ.Text = BitConverter.ToString(rsa_params.Q).Replace("-", ""); + tbRSAPrivPQ.Text = BitConverter.ToString(rsa_params.InverseQ).Replace("-", ""); + tbRSAPrivDP1.Text = BitConverter.ToString(rsa_params.DP).Replace("-", ""); + tbRSAPrivDQ1.Text = BitConverter.ToString(rsa_params.DQ).Replace("-", ""); + tbRSAPrivExp.Text = BitConverter.ToString(rsa_params.D).Replace("-", ""); + + tbRSAPubExp.Text = BitConverter.ToString(rsa_params.Exponent).Replace("-", ""); + } + catch (Exception ex) + { + MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + finally + { + if (decoder != null) + decoder.Close(); + } + } + } + } + + private void btnMkRSAKey_Click(object sender, EventArgs e) + { + byte[] modulus; + byte[] exponent; + byte[] barr; + Int32 key_len = Convert.ToInt32(cbRSAKeyLength.Text); + tbRSAModulus.Text = ""; + tbRSAPrivExp.Text = ""; + tbRSAPrivP.Text = ""; + tbRSAPrivQ.Text = ""; + tbRSAPrivPQ.Text = ""; + tbRSAPrivDP1.Text = ""; + tbRSAPrivDQ1.Text = ""; + tbRSAPubExp.Text = ""; + Refresh(); + + Cursor.Current = Cursors.WaitCursor; + + RsaKeyPairGenerator gen = new RsaKeyPairGenerator(); + KeyGenerationParameters genParams = new KeyGenerationParameters(new SecureRandom(), key_len); + gen.Init(genParams); + + AsymmetricCipherKeyPair KeyPair = gen.GenerateKeyPair(); + + modulus = ((RsaPrivateCrtKeyParameters)KeyPair.Private).Modulus.ToByteArray(); + exponent = ((RsaKeyParameters)KeyPair.Public).Exponent.ToByteArray(); + // Remove leading zeros: + while (modulus[0] == 0 && modulus.Length > key_len / 8) + { + modulus = modulus.Skip(1).ToArray(); + } + if (key_len / 8 > modulus.Length) + modulus = zeroPadArray(modulus, 0, key_len / 8 - modulus.Length); + + while (exponent[0] == 0 && exponent.Length > 1) + { + exponent = exponent.Skip(1).ToArray(); + } + + tbRSAModulus.Text = BitConverter.ToString(modulus).Replace("-", ""); + tbRSAPubExp.Text = BitConverter.ToString(exponent).Replace("-", ""); + + barr = ((RsaPrivateCrtKeyParameters)KeyPair.Private).Exponent.ToByteArray(); + while (barr[0] == 0 && barr.Length > key_len / 16) + { + barr = barr.Skip(1).ToArray(); + } + if (key_len / 16 > barr.Length) + barr = zeroPadArray(barr, 0, key_len / 16 - barr.Length); + tbRSAPrivExp.Text = BitConverter.ToString(barr).Replace("-", ""); + barr = ((RsaPrivateCrtKeyParameters)KeyPair.Private).P.ToByteArray(); + while (barr[0] == 0 && barr.Length > key_len / 16) + { + barr = barr.Skip(1).ToArray(); + } + if (key_len / 16 > barr.Length) + barr = zeroPadArray(barr, 0, key_len / 16 - barr.Length); + tbRSAPrivP.Text = BitConverter.ToString(barr).Replace("-", ""); + barr = ((RsaPrivateCrtKeyParameters)KeyPair.Private).Q.ToByteArray(); + while (barr[0] == 0 && barr.Length > key_len / 16) + { + barr = barr.Skip(1).ToArray(); + } + tbRSAPrivQ.Text = BitConverter.ToString(barr).Replace("-", ""); + barr = ((RsaPrivateCrtKeyParameters)KeyPair.Private).QInv.ToByteArray(); + while (barr[0] == 0 && barr.Length > key_len / 16) + { + barr = barr.Skip(1).ToArray(); + } + if (key_len / 16 > barr.Length) + barr = zeroPadArray(barr, 0, key_len / 16 - barr.Length); + tbRSAPrivPQ.Text = BitConverter.ToString(barr).Replace("-", ""); + barr = ((RsaPrivateCrtKeyParameters)KeyPair.Private).DP.ToByteArray(); + while (barr[0] == 0 && barr.Length > key_len / 16) + { + barr = barr.Skip(1).ToArray(); + } + if (key_len / 16 > barr.Length) + barr = zeroPadArray(barr, 0, key_len / 16 - barr.Length); + tbRSAPrivDP1.Text = BitConverter.ToString(barr).Replace("-", ""); + barr = ((RsaPrivateCrtKeyParameters)KeyPair.Private).DQ.ToByteArray(); + while (barr[0] == 0 && barr.Length > key_len / 16) + { + barr = barr.Skip(1).ToArray(); + } + if (key_len / 16 > barr.Length) + barr = zeroPadArray(barr, 0, key_len / 16 - barr.Length); + tbRSAPrivDQ1.Text = BitConverter.ToString(barr).Replace("-", ""); + + Cursor.Current = Cursors.Default; + } + + private void btnBackupRSAPriv_Click(object sender, EventArgs e) + { + SaveFileDialog dialog; + + try + { + dialog = new SaveFileDialog(); + dialog.Filter = "Signature binary files (*.pem)|*.pem|All files (*.*)|*.*"; + dialog.RestoreDirectory = true; + + if (dialog.ShowDialog() == DialogResult.OK) + { + var key = new RsaPrivateCrtKeyParameters( + new BigInteger(1, Hex.Decode(tbRSAModulus.Text)), + new BigInteger(1, Hex.Decode(tbRSAPubExp.Text)), + new BigInteger(1, Hex.Decode(tbRSAPrivExp.Text)), + new BigInteger(1, Hex.Decode(tbRSAPrivP.Text)), + new BigInteger(1, Hex.Decode(tbRSAPrivQ.Text)), + new BigInteger(1, Hex.Decode(tbRSAPrivDP1.Text)), + new BigInteger(1, Hex.Decode(tbRSAPrivDQ1.Text)), + new BigInteger(1, Hex.Decode(tbRSAPrivPQ.Text)) + ); + + var textWriter = new StreamWriter(dialog.FileName); + var pemWr = new PemWriter(textWriter); + pemWr.WriteObject(key); + pemWr.Writer.Flush(); + textWriter.Close(); + } + } + catch (Exception ex) + { + MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + + private void btnRestoreRSAPriv_Click(object sender, EventArgs e) + { + Int32 key_len, key_idx; + byte[] modulus, d, p, q, pq, dp, dq; + byte[] pub_exp; + OpenFileDialog dialog = new OpenFileDialog(); + dialog.Filter = "PEM files (*.pem;*.crt;*.cer)|*.pem;*.crt;*.cer|All files (*.*)|*.*"; + //dialog.InitialDirectory = @"C:\"; + dialog.Title = "Please select the PEM file"; + + if (dialog.ShowDialog() == DialogResult.OK) + { + try + { + // Load your certificate from PEM file + TextReader fileStream = System.IO.File.OpenText(dialog.FileName); + PemReader pemReader = new Org.BouncyCastle.OpenSsl.PemReader(fileStream); + object KeyParameter = pemReader.ReadObject(); + + if (KeyParameter.GetType() == typeof(Org.BouncyCastle.X509.X509Certificate)) + { + // Load your certificate: + X509Certificate2 certificate = new X509Certificate2(dialog.FileName); + KeyParameter = Org.BouncyCastle.Security.DotNetUtilities.GetRsaPublicKey(certificate.GetRSAPublicKey()); + //certificate.Dispose(); ToDo: See if needed + } + + RsaPrivateCrtKeyParameters key; + + if (KeyParameter.GetType() == typeof(Org.BouncyCastle.Crypto.Parameters.RsaPrivateCrtKeyParameters)) + { + if (!((RsaPrivateCrtKeyParameters)KeyParameter).IsPrivate) + throw new Exception("File doesn't contain RSA private key."); + key = (RsaPrivateCrtKeyParameters)KeyParameter; + } + else if (KeyParameter.GetType() == typeof(Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair)) + { + if (!((AsymmetricCipherKeyPair)KeyParameter).Private.IsPrivate) + throw new Exception("File doesn't contain RSA private key."); + key = (RsaPrivateCrtKeyParameters)((AsymmetricCipherKeyPair)KeyParameter).Private; + } + else if (KeyParameter.GetType() == typeof(Org.BouncyCastle.Crypto.Parameters.RsaKeyParameters)) + { + if (!((RsaKeyParameters)KeyParameter).IsPrivate) + throw new Exception("File doesn't contain RSA private key."); + key = (RsaPrivateCrtKeyParameters)KeyParameter; + } + else + throw new Exception("File doesn't contain RSA private key."); + //? Org.BouncyCastle.Crypto.AsymmetricKeyParameter + //? Org.BouncyCastle.Crypto.Parameters.RsaBlindingParameters + //? Org.BouncyCastle.Crypto.Parameters.RsaKeyGenerationParameters + //MessageBox.Show(KeyParameter.GetType().ToString()); + + key_len = key.Modulus.BitLength; + modulus = key.Modulus.ToByteArray(); + d = key.Exponent.ToByteArray(); + p = key.P.ToByteArray(); + q = key.Q.ToByteArray(); + pq = key.QInv.ToByteArray(); + dp = key.DP.ToByteArray(); + dq = key.DQ.ToByteArray(); + pub_exp = key.PublicExponent.ToByteArray(); + + key_idx = cbRSAKeyLength.FindStringExact(key_len.ToString()); + if (key_idx < 0) + { + MessageBox.Show("Unsupported key length of " + key_len + "bytes.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + cbRSAKeyLength.SelectedIndex = key_idx; + + // Remove leading zeros: + while (modulus[0] == 0 && modulus.Length > key_len / 8) + { + modulus = modulus.Skip(1).ToArray(); + } + while (d[0] == 0 && modulus.Length > key_len / 8) + { + d = d.Skip(1).ToArray(); + } + while (p[0] == 0 && modulus.Length > key_len / 16) + { + p = p.Skip(1).ToArray(); + } + while (q[0] == 0 && modulus.Length > key_len / 16) + { + q = q.Skip(1).ToArray(); + } + while (pq[0] == 0 && modulus.Length > key_len / 16) + { + pq = pq.Skip(1).ToArray(); + } + while (dp[0] == 0 && modulus.Length > key_len / 16) + { + dp = dp.Skip(1).ToArray(); + } + while (dq[0] == 0 && modulus.Length > key_len / 16) + { + dq = dq.Skip(1).ToArray(); + } + while (pub_exp[0] == 0 && pub_exp.Length > 0) + { + pub_exp = pub_exp.Skip(1).ToArray(); + } + tbRSAModulus.Text = BitConverter.ToString(modulus).Replace("-", ""); + tbRSAPrivP.Text = BitConverter.ToString(p).Replace("-", ""); + tbRSAPrivQ.Text = BitConverter.ToString(q).Replace("-", ""); + tbRSAPrivPQ.Text = BitConverter.ToString(pq).Replace("-", ""); + tbRSAPrivDP1.Text = BitConverter.ToString(dp).Replace("-", ""); + tbRSAPrivDQ1.Text = BitConverter.ToString(dq).Replace("-", ""); + tbRSAPrivExp.Text = BitConverter.ToString(d).Replace("-", ""); + tbRSAPubExp.Text = BitConverter.ToString(pub_exp).Replace("-", ""); + } + catch (Exception ex) + { + MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + } + + private void btnSaveRSAPub_Click(object sender, EventArgs e) + { + SaveFileDialog dialog; + + try + { + dialog = new SaveFileDialog(); + dialog.Filter = "Signature binary files (*.pem)|*.pem|All files (*.*)|*.*"; + dialog.RestoreDirectory = true; + + if (dialog.ShowDialog() == DialogResult.OK) + { + var key = new RsaKeyParameters(false, new BigInteger(1, Hex.Decode(tbRSAModulus.Text)), + new BigInteger(1, Hex.Decode(tbRSAPubExp.Text))); + + var textWriter = new StreamWriter(dialog.FileName); + var pemWr = new PemWriter(textWriter); + pemWr.WriteObject(key); + pemWr.Writer.Flush(); + textWriter.Close(); + } + } + catch (Exception ex) + { + MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + + private void btnStoreRSAPriv_Click(object sender, EventArgs e) + { + DL_STATUS status; + byte key_index = Convert.ToByte(cbRSAKeyIndex.Text); + byte key_type; + UInt16 key_size_bits = Convert.ToUInt16(cbRSAKeyLength.Text); + int key_size_bytes, component_size_bytes; + int key_offset = 0; + byte[] key; + + try + { + if (!uFR_Opened) + throw new Exception(uFR_NotOpenedMessage); + + Cursor.Current = Cursors.WaitCursor; + + if (rbCRT.Checked) + { + key_type = (byte)JCDL_KEY_TYPES.TYPE_RSA_CRT_PRIVATE; + component_size_bytes = key_size_bits / 16; + key_size_bytes = component_size_bytes * 5; + key = new byte[key_size_bytes]; + + if (tbRSAPrivP.Text.Length / 2 != component_size_bytes) + throw new Exception("Wrong size of the RSA private key parameter P"); + Array.Copy(Hex.Decode(tbRSAPrivP.Text), 0, key, key_offset, component_size_bytes); + key_offset += component_size_bytes; + + if (tbRSAPrivQ.Text.Length / 2 != component_size_bytes) + throw new Exception("Wrong size of the RSA private key parameter Q"); + Array.Copy(Hex.Decode(tbRSAPrivQ.Text), 0, key, key_offset, component_size_bytes); + key_offset += component_size_bytes; + + if (tbRSAPrivPQ.Text.Length / 2 != component_size_bytes) + throw new Exception("Wrong size of the RSA private key parameter PQ"); + Array.Copy(Hex.Decode(tbRSAPrivPQ.Text), 0, key, key_offset, component_size_bytes); + key_offset += component_size_bytes; + + if (tbRSAPrivDP1.Text.Length / 2 != component_size_bytes) + throw new Exception("Wrong size of the RSA private key parameter DP1"); + Array.Copy(Hex.Decode(tbRSAPrivDP1.Text), 0, key, key_offset, component_size_bytes); + key_offset += component_size_bytes; + + if (tbRSAPrivDQ1.Text.Length / 2 != component_size_bytes) + throw new Exception("Wrong size of the RSA private key parameter DQ1"); + Array.Copy(Hex.Decode(tbRSAPrivDQ1.Text), 0, key, key_offset, component_size_bytes); + } + else + { + key_type = (byte)JCDL_KEY_TYPES.TYPE_RSA_PRIVATE; + component_size_bytes = key_size_bits / 8; + key_size_bytes = component_size_bytes * 2; + key = new byte[key_size_bytes]; + + if (tbRSAModulus.Text.Length / 2 != component_size_bytes) + throw new Exception("Wrong size of the RSA private key parameter N"); + Array.Copy(Hex.Decode(tbRSAModulus.Text), 0, key, key_offset, component_size_bytes); + key_offset += component_size_bytes; + + if (tbRSAPrivExp.Text.Length / 2 != component_size_bytes) + throw new Exception("Wrong size of the RSA private key parameter D"); + Array.Copy(Hex.Decode(tbRSAPrivExp.Text), 0, key, key_offset, component_size_bytes); + } + + byte[] aid = Hex.Decode(uFCoder.JCDL_AID); + byte[] selection_respone = new byte[16]; + + status = uFCoder.SetISO14443_4_Mode(); + if (status != DL_STATUS.UFR_OK) + throw new Exception(string.Format("Card error code: 0x{0:X}", status)); + else + uFR_Selected = true; + + status = uFCoder.JCAppSelectByAid(aid, (byte)aid.Length, selection_respone); + if (status != DL_STATUS.UFR_OK) + throw new Exception(string.Format("Card error code: 0x{0:X}", status)); + + status = uFCoder.JCAppPutPrivateKey(key_type, key_index, key, key_size_bits, null, 0); + if (status != DL_STATUS.UFR_OK) + throw new Exception(string.Format("Card error code: 0x{0:X}", status)); + + MessageBox.Show("The key has been successfully stored.", "Sucess", MessageBoxButtons.OK, MessageBoxIcon.Information); + } + catch (Exception ex) + { + MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + finally + { + if (uFR_Selected) + { + uFCoder.s_block_deselect(100); + uFR_Selected = false; + } + Cursor.Current = Cursors.Default; + } + } + + //====================================================================================================================== + // EC Keys page: + //====================================================================================================================== + void populateECDomainParameters(string curve_name) + { + byte[] barr1; + Int32 key_bytes_len; + mECCurve = SecNamedCurves.GetByName(curve_name); + + try + { + key_bytes_len = (mECCurve.Curve.FieldSize + 7) / 8; + + if (mECCurve.Curve.GetType() == typeof(FpCurve)) + { + rbECFieldPrime.Checked = true; + barr1 = mECCurve.Curve.Field.Characteristic.ToByteArray(); + while (barr1[0] == 0 && barr1.Length > key_bytes_len) + { + barr1 = barr1.Skip(1).ToArray(); + } + if (key_bytes_len > barr1.Length) + barr1 = zeroPadArray(barr1, 0, key_bytes_len - barr1.Length); + tbECParamPrime.Text = BitConverter.ToString(barr1).Replace("-", ""); + } + else if (mECCurve.Curve.GetType() == typeof(F2mCurve)) + { + rbECFieldBinary.Checked = true; + populateECReductionPolynomial(!((F2mCurve)mECCurve.Curve).IsTrinomial(), + (ushort)mECCurve.Curve.FieldSize, + (ushort)((F2mCurve)mECCurve.Curve).K3, + (ushort)((F2mCurve)mECCurve.Curve).K2, + (ushort)((F2mCurve)mECCurve.Curve).K1); + } + + barr1 = mECCurve.Curve.A.ToBigInteger().ToByteArray(); + while (barr1[0] == 0 && barr1.Length > key_bytes_len) + { + barr1 = barr1.Skip(1).ToArray(); + } + if (key_bytes_len > barr1.Length) + barr1 = zeroPadArray(barr1, 0, key_bytes_len - barr1.Length); + tbECParamA.Text = BitConverter.ToString(barr1).Replace("-", ""); + + barr1 = mECCurve.Curve.B.ToBigInteger().ToByteArray(); + while (barr1[0] == 0 && barr1.Length > key_bytes_len) + { + barr1 = barr1.Skip(1).ToArray(); + } + if (key_bytes_len > barr1.Length) + barr1 = zeroPadArray(barr1, 0, key_bytes_len - barr1.Length); + tbECParamB.Text = BitConverter.ToString(barr1).Replace("-", ""); + + barr1 = mECCurve.G.XCoord.ToBigInteger().ToByteArray(); + if (key_bytes_len > barr1.Length) + barr1 = zeroPadArray(barr1, 0, key_bytes_len - barr1.Length); + while (barr1[0] == 0 && barr1.Length > key_bytes_len) + { + barr1 = barr1.Skip(1).ToArray(); + } + tbECParamG.Text = "04" + BitConverter.ToString(barr1).Replace("-", ""); + + barr1 = mECCurve.G.YCoord.ToBigInteger().ToByteArray(); + while (barr1[0] == 0 && barr1.Length > key_bytes_len) + { + barr1 = barr1.Skip(1).ToArray(); + } + if (key_bytes_len > barr1.Length) + barr1 = zeroPadArray(barr1, 0, key_bytes_len - barr1.Length); + tbECParamG.Text += BitConverter.ToString(barr1).Replace("-", ""); + + barr1 = mECCurve.N.ToByteArray(); + while (barr1[0] == 0 && barr1.Length > key_bytes_len) + { + barr1 = barr1.Skip(1).ToArray(); + } + if (key_bytes_len > barr1.Length) + barr1 = zeroPadArray(barr1, 0, key_bytes_len - barr1.Length); + tbECParamR.Text = BitConverter.ToString(barr1).Replace("-", ""); + + tbECParamK.Text = mECCurve.H.ToString(); // mECCurve.Curve.Cofactor.ToString(); + } + catch (Exception ex) + { + MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + + private void clearECReductionPolynomial() + { + rtbECReductionPolynomial.Clear(); + //richTextBox1.SelectionColor = Color.Black; + rtbECReductionPolynomial.SelectionFont = new Font("Times New Roman", 10, FontStyle.Italic); + rtbECReductionPolynomial.SelectedText = " f(x) = "; + } + + private void populateECReductionPolynomial(Boolean isPentanomial, UInt16 m, UInt16 e1, UInt16 e2, UInt16 e3) + { + tbECParamE1.Enabled = true; + + rtbECReductionPolynomial.Clear(); + //richTextBox1.SelectionColor = Color.Black; + rtbECReductionPolynomial.SelectionFont = new Font("Times New Roman", 10, FontStyle.Italic); + rtbECReductionPolynomial.SelectedText = " f(x) = x"; + // superscripted text... + rtbECReductionPolynomial.SelectionFont = new Font("Times New Roman", 7, FontStyle.Italic); + rtbECReductionPolynomial.SelectionCharOffset = 8; + rtbECReductionPolynomial.SelectedText = m.ToString(); + // normal text... + rtbECReductionPolynomial.SelectionFont = new Font("Times New Roman", 10, FontStyle.Italic); + rtbECReductionPolynomial.SelectionCharOffset = 0; + rtbECReductionPolynomial.SelectedText = " + x"; + + if (isPentanomial) + { + lbECParamE1.Text = "e1:"; + tbECParamE1.Text = e1.ToString(); + lbECParamE2.Enabled = true; + lbECParamE3.Enabled = true; + tbECParamE2.Enabled = true; + tbECParamE3.Enabled = true; + tbECParamE2.Text = e2.ToString(); + tbECParamE3.Text = e3.ToString(); + + // superscripted text... + rtbECReductionPolynomial.SelectionFont = new Font("Times New Roman", 7, FontStyle.Italic); + rtbECReductionPolynomial.SelectionCharOffset = 8; + rtbECReductionPolynomial.SelectedText = e1.ToString(); + // normal text... + rtbECReductionPolynomial.SelectionFont = new Font("Times New Roman", 10, FontStyle.Italic); + rtbECReductionPolynomial.SelectionCharOffset = 0; + rtbECReductionPolynomial.SelectedText = " + x"; + // superscripted text... + rtbECReductionPolynomial.SelectionFont = new Font("Times New Roman", 7, FontStyle.Italic); + rtbECReductionPolynomial.SelectionCharOffset = 8; + rtbECReductionPolynomial.SelectedText = e2.ToString(); + // normal text... + rtbECReductionPolynomial.SelectionFont = new Font("Times New Roman", 10, FontStyle.Italic); + rtbECReductionPolynomial.SelectionCharOffset = 0; + rtbECReductionPolynomial.SelectedText = " + x"; + // superscripted text... + rtbECReductionPolynomial.SelectionFont = new Font("Times New Roman", 7, FontStyle.Italic); + rtbECReductionPolynomial.SelectionCharOffset = 8; + rtbECReductionPolynomial.SelectedText = e3.ToString(); + } + else + { + lbECParamE1.Text = "e:"; + tbECParamE1.Text = e3.ToString(); + tbECParamE2.Clear(); + tbECParamE3.Clear(); + tbECParamE2.Enabled = false; + tbECParamE3.Enabled = false; + lbECParamE2.Enabled = false; + lbECParamE3.Enabled = false; + + // superscripted text... + rtbECReductionPolynomial.SelectionFont = new Font("Times New Roman", 7, FontStyle.Italic); + rtbECReductionPolynomial.SelectionCharOffset = 8; + rtbECReductionPolynomial.SelectedText = e3.ToString(); + } + // normal text... + rtbECReductionPolynomial.SelectionFont = new Font("Times New Roman", 10, FontStyle.Italic); + rtbECReductionPolynomial.SelectionCharOffset = 0; + rtbECReductionPolynomial.SelectedText = " + 1"; + } + + private void rbCRT_CheckedChanged(object sender, EventArgs e) + { + if (rbCRT.Checked) + { + lbRSAPrivP.Enabled = true; + lbRSAPrivQ.Enabled = true; + lbRSAPrivPQ.Enabled = true; + lbRSAPrivDP1.Enabled = true; + lbRSAPrivDQ1.Enabled = true; + tbRSAPrivP.Enabled = true; + tbRSAPrivQ.Enabled = true; + tbRSAPrivPQ.Enabled = true; + tbRSAPrivDP1.Enabled = true; + tbRSAPrivDQ1.Enabled = true; + + lbRSAPrivExp.Enabled = false; + tbRSAPrivExp.Enabled = false; + } + else + { + lbRSAPrivP.Enabled = false; + lbRSAPrivQ.Enabled = false; + lbRSAPrivPQ.Enabled = false; + lbRSAPrivDP1.Enabled = false; + lbRSAPrivDQ1.Enabled = false; + tbRSAPrivP.Enabled = false; + tbRSAPrivQ.Enabled = false; + tbRSAPrivPQ.Enabled = false; + tbRSAPrivDP1.Enabled = false; + tbRSAPrivDQ1.Enabled = false; + + lbRSAPrivExp.Enabled = true; + tbRSAPrivExp.Enabled = true; + } + } + + private void rbECFieldPrime_CheckedChanged(object sender, EventArgs e) + { + if (rbECFieldPrime.Checked) + { + tbECParamPrime.Enabled = true; + clearECReductionPolynomial(); + tbECParamE1.Clear(); + tbECParamE2.Clear(); + tbECParamE3.Clear(); + gbECReductionPolynomial.Enabled = false; + } + } + + private void rbECFieldBinary_CheckedChanged(object sender, EventArgs e) + { + if (rbECFieldBinary.Checked) + { + tbECParamPrime.Enabled = false; + tbECParamPrime.Clear(); + gbECReductionPolynomial.Enabled = true; + } + } + + private void btnECImportP12_Click(object sender, EventArgs e) + { + OpenFileDialog dialog = new OpenFileDialog(); + dialog.Filter = "PKCS#12 files (*.p12;*.pfx)|*.p12;*.pfx|All files (*.*)|*.*"; + //dialog.InitialDirectory = @"C:\"; + dialog.Title = "Please select the cert file"; + + if (dialog.ShowDialog() == DialogResult.OK) + { + frmPassword dlgPasswd = new frmPassword(); + if (dlgPasswd.ShowDialog() == DialogResult.OK) + { + try + { + // Load your certificate from p12 file + X509Certificate2 certificate = new X509Certificate2(dialog.FileName, dlgPasswd.password, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.PersistKeySet); + //RSACryptoServiceProvider pub_key = (RSACryptoServiceProvider)certificate.PublicKey.Key; + PublicKey pub_key = certificate.PublicKey; + + //if (!certificate.HasPrivateKey) + //{ + // throw new Exception("Certificate does not contain private key."); + //} + + if (pub_key.EncodedKeyValue.Oid.FriendlyName.Equals("ECC") + || pub_key.EncodedKeyValue.Oid.FriendlyName.Equals("ECDSA")) + { + var parser = new X509CertificateParser(); + // Load your certificate + var bouncyCertificate = parser.ReadCertificate(certificate.RawData); + //certificate.Dispose(); ToDo: See if needed + + string curve_name = SecNamedCurves.GetName( + (DerObjectIdentifier)DerObjectIdentifier.FromByteArray( + bouncyCertificate.CertificateStructure.SubjectPublicKeyInfo.AlgorithmID.Parameters.GetDerEncoded() + ) + ); + + if (cbECName.Items.Contains(curve_name)) + { + int index = cbECName.FindString(curve_name); + cbECName.SelectedIndex = index; + } + else + throw new Exception("Unknown ECC Parameters in certificate."); + + tbECPubKey.Text = BitConverter.ToString( + bouncyCertificate.CertificateStructure.SubjectPublicKeyInfo.PublicKeyData.GetBytes() + ).Replace("-", ""); + } + else + { + MessageBox.Show("File doesn't contain EC public key.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + } + catch (Exception ex) + { + MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + } + } + + private void btnMkECKey_Click(object sender, EventArgs e) + { + byte[] barr1, barr2; + Int32 key_bytes_len; + + Cursor.Current = Cursors.WaitCursor; + + ECKeyPairGenerator gen = new ECKeyPairGenerator("ECDSA"); + + string CurveName = cbECName.Text; + X9ECParameters curve = SecNamedCurves.GetByName(CurveName); + ECDomainParameters curveSpec = new ECDomainParameters(curve.Curve, curve.G, curve.N, curve.H, curve.GetSeed()); + ECKeyGenerationParameters keyGenParam = new ECKeyGenerationParameters(curveSpec, new SecureRandom()); + gen.Init(keyGenParam); + //Generation of Key Pair + AsymmetricCipherKeyPair KeyPair = gen.GenerateKeyPair(); + + key_bytes_len = (curve.Curve.FieldSize + 7) / 8; + + mPrivateKey = (ECPrivateKeyParameters)KeyPair.Private; + barr1 = mPrivateKey.D.ToByteArray(); + while (barr1[0] == 0 && barr1.Length > key_bytes_len) + { + barr1 = barr1.Skip(1).ToArray(); + } + if (key_bytes_len > barr1.Length) + barr1 = zeroPadArray(barr1, 0, key_bytes_len - barr1.Length); + tbECPrivKey.Text = BitConverter.ToString(barr1).Replace("-", ""); + + ECPublicKeyParameters publicKeyParam = (ECPublicKeyParameters)KeyPair.Public; + barr1 = publicKeyParam.Q.XCoord.ToBigInteger().ToByteArray(); + while (barr1[0] == 0 && barr1.Length > key_bytes_len) + { + barr1 = barr1.Skip(1).ToArray(); + } + if (key_bytes_len > barr1.Length) + barr1 = zeroPadArray(barr1, 0, key_bytes_len - barr1.Length); + barr2 = publicKeyParam.Q.YCoord.ToBigInteger().ToByteArray(); + while (barr2[0] == 0 && barr2.Length > key_bytes_len) + { + barr2 = barr2.Skip(1).ToArray(); + } + if (key_bytes_len > barr2.Length) + barr2 = zeroPadArray(barr2, 0, key_bytes_len - barr2.Length); + tbECPubKey.Text = "04" + BitConverter.ToString(barr1).Replace("-", "") + BitConverter.ToString(barr2).Replace("-", ""); + + Cursor.Current = Cursors.Default; + } + + private void btnBackupECPriv_Click(object sender, EventArgs e) + { + SaveFileDialog dialog; + + try + { + if (tbECPrivKey.Text.Trim().Equals("")) + throw new Exception("Private key can't have a zero length."); + + dialog = new SaveFileDialog(); + dialog.Filter = "Signature binary files (*.pem)|*.pem|All files (*.*)|*.*"; + dialog.RestoreDirectory = true; + + if (dialog.ShowDialog() == DialogResult.OK) + { + X9ECParameters curve = SecNamedCurves.GetByName(cbECName.Text); + ECDomainParameters curveSpec = new ECDomainParameters(curve.Curve, curve.G, curve.N, curve.H, curve.GetSeed()); + var key = new ECPrivateKeyParameters("ECDSA", new BigInteger(1, Hex.Decode(tbECPrivKey.Text)), curveSpec); + + var textWriter = new StreamWriter(dialog.FileName); + var pemWr = new PemWriter(textWriter); + pemWr.WriteObject(key); + pemWr.Writer.Flush(); + textWriter.Close(); + } + } + catch (Exception ex) + { + MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + + } + + private void btnRestoreECPriv_Click(object sender, EventArgs e) + { + byte[] barr; + + OpenFileDialog dialog = new OpenFileDialog(); + dialog.Filter = "PEM files (*.pem;*.crt;*.cer)|*.pem;*.crt;*.cer|All files (*.*)|*.*"; + dialog.InitialDirectory = @"C:\"; + dialog.Title = "Please select the PEM file"; + + if (dialog.ShowDialog() == DialogResult.OK) + { + try + { + //ToDo: Find beter strategy to open Org.BouncyCastle.X509.X509Certificate + + // Load your certificate from PEM file + TextReader fileStream = System.IO.File.OpenText(dialog.FileName); + PemReader pemReader = new Org.BouncyCastle.OpenSsl.PemReader(fileStream); + object PemObj = pemReader.ReadObject(); + + if (PemObj.GetType() == typeof(Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair)) + { + ECPrivateKeyParameters key = (ECPrivateKeyParameters)((AsymmetricCipherKeyPair)PemObj).Private; + if (!key.IsPrivate) + throw new Exception("File doesn't contain EC public key."); + + int idx = 0; + foreach (string s in cbECName.Items) + { + var EC = SecNamedCurves.GetByName(s); + if (EC.Curve.Equals(key.Parameters.Curve)) + break; + idx++; + } + if ((idx + 1) >= cbECName.Items.Count) + throw new Exception("Unknown ECC Parameters in pem file."); + + cbECName.SelectedIndex = idx; + + int key_bytes_len = (key.Parameters.Curve.FieldSize + 7) / 8; + barr = key.D.ToByteArray(); + while (barr[0] == 0 && barr.Length > key_bytes_len) + { + barr = barr.Skip(1).ToArray(); + } + if (key_bytes_len > barr.Length) + barr = zeroPadArray(barr, 0, key_bytes_len - barr.Length); + tbECPrivKey.Text = BitConverter.ToString(barr).Replace("-", ""); + + tbECPubKey.Text = ""; + } +/* + else if (PemObj.GetType() == typeof(Org.BouncyCastle.X509.X509Certificate)) + { + var parser = new X509CertificateParser(); + // Load your certificate + X509Certificate2 certificate = new X509Certificate2(dialog.FileName); + var bouncyCertificate = parser.ReadCertificate(certificate.RawData); + //certificate.Dispose(); ToDo: See if needed + + string curve_name = SecNamedCurves.GetName( + (DerObjectIdentifier)DerObjectIdentifier.FromByteArray( + bouncyCertificate.CertificateStructure.SubjectPublicKeyInfo.AlgorithmID.Parameters.GetDerEncoded() + ) + ); + + if (cbECName.Items.Contains(curve_name)) + { + int index = cbECName.FindString(curve_name); + cbECName.SelectedIndex = index; + } + else + throw new Exception("Unknown ECC Parameters in certificate."); + + tbECPrivKey.Text = ???; + } +*/ + else + throw new Exception("File doesn't contain EC public key."); + } + catch (Exception ex) + { + MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + } + + private void btnSaveECPub_Click(object sender, EventArgs e) + { + SaveFileDialog dialog; + + try + { + dialog = new SaveFileDialog(); + dialog.Filter = "Signature binary files (*.pem)|*.pem|All files (*.*)|*.*"; + dialog.RestoreDirectory = true; + + if (dialog.ShowDialog() == DialogResult.OK) + { + X9ECParameters curve = SecNamedCurves.GetByName(cbECName.Text); + ECDomainParameters curveSpec = new ECDomainParameters(curve.Curve, curve.G, curve.N, curve.H, curve.GetSeed()); + var key = new ECPublicKeyParameters("ECDSA", curve.Curve.DecodePoint(Hex.Decode(tbECPubKey.Text)), curveSpec); + + var textWriter = new StreamWriter(dialog.FileName); + var pemWr = new PemWriter(textWriter); + pemWr.WriteObject(key); + pemWr.Writer.Flush(); + textWriter.Close(); + } + } + catch (Exception ex) + { + MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + + private void btnStoreECPriv_Click(object sender, EventArgs e) + { + DL_STATUS status; + byte key_index = Convert.ToByte(cbECKeyIndex.Text); + byte key_type; + byte[] key_param = new byte[1]; + byte r_oversized = 0; + byte key_param_len; + UInt16 key_size_bits = Convert.ToUInt16(cbECKeyLength.Text); + UInt16 temp; + int component_size_bytes = (key_size_bits + 7) / 8; + int key_size_bytes; + int key_offset = 0; + byte[] key; + bool isCurveTrinomial; + + try + { + if (!uFR_Opened) + throw new Exception(uFR_NotOpenedMessage); + + Cursor.Current = Cursors.WaitCursor; + + key_param[0] = 0; + key_param_len = 1; + + X9ECParameters curve = SecNamedCurves.GetByName(cbECName.Text); + + if (rbECFieldPrime.Checked) + { + key_type = (byte)JCDL_KEY_TYPES.TYPE_EC_FP_PRIVATE; + key_size_bytes = component_size_bytes * 7 + 3; + + // Code is logically deliberately dislocated here: + if (tbECParamR.Text.Length / 2 != component_size_bytes) + { + if (tbECParamR.Text.Length / 2 != component_size_bytes + 1) + { + throw new Exception("Wrong size of the EC curve parameter R"); + } + else + { + key_param[0] = 1; + r_oversized = 1; + ++key_size_bytes; + } + } + key = new byte[key_size_bytes]; + + if (tbECParamPrime.Text.Length / 2 != component_size_bytes) + throw new Exception("Wrong size of the EC curve parameter p"); + Array.Copy(Hex.Decode(tbECParamPrime.Text), 0, key, key_offset, component_size_bytes); + key_offset += component_size_bytes; + + if (tbECParamA.Text.Length / 2 != component_size_bytes) + throw new Exception("Wrong size of the EC curve parameter a"); + Array.Copy(Hex.Decode(tbECParamA.Text), 0, key, key_offset, component_size_bytes); + key_offset += component_size_bytes; + + if (tbECParamB.Text.Length / 2 != component_size_bytes) + throw new Exception("Wrong size of the EC curve parameter b"); + Array.Copy(Hex.Decode(tbECParamB.Text), 0, key, key_offset, component_size_bytes); + key_offset += component_size_bytes; + + if (tbECParamG.Text.Length / 2 != component_size_bytes * 2 + 1) + throw new Exception("Wrong size of the EC curve parameter G(uc)"); + Array.Copy(Hex.Decode(tbECParamG.Text), 0, key, key_offset, component_size_bytes * 2 + 1); + key_offset += component_size_bytes * 2 + 1; + + Array.Copy(Hex.Decode(tbECParamR.Text), 0, key, key_offset, component_size_bytes + r_oversized); + key_offset += component_size_bytes + r_oversized; + + if (tbECPrivKey.Text.Length / 2 != component_size_bytes) + throw new Exception("Wrong size of the EC private key (parameter S)"); + Array.Copy(Hex.Decode(tbECPrivKey.Text), 0, key, key_offset, component_size_bytes); + key_offset += component_size_bytes; + + temp = Convert.ToUInt16(tbECParamK.Text); + key[key_offset++] = (byte)(temp >> 8); + key[key_offset] = (byte)temp; + } + else + { + key_type = (byte)JCDL_KEY_TYPES.TYPE_EC_F2M_PRIVATE; + key_size_bytes = component_size_bytes * 6 + 5; + + isCurveTrinomial = ((F2mCurve)mECCurve.Curve).IsTrinomial(); + if (!isCurveTrinomial) + { + key_param[0] = 1; + key_size_bytes += 4; + } + key = new byte[key_size_bytes]; + + if (tbECParamA.Text.Length / 2 != component_size_bytes) + throw new Exception("Wrong size of the EC curve parameter a"); + Array.Copy(Hex.Decode(tbECParamA.Text), 0, key, key_offset, component_size_bytes); + key_offset += component_size_bytes; + + if (tbECParamB.Text.Length / 2 != component_size_bytes) + throw new Exception("Wrong size of the EC curve parameter b"); + Array.Copy(Hex.Decode(tbECParamB.Text), 0, key, key_offset, component_size_bytes); + key_offset += component_size_bytes; + + if (tbECParamG.Text.Length / 2 != component_size_bytes * 2 + 1) + throw new Exception("Wrong size of the EC curve parameter G(uc)"); + Array.Copy(Hex.Decode(tbECParamG.Text), 0, key, key_offset, component_size_bytes * 2 + 1); + key_offset += component_size_bytes * 2 + 1; + + if (tbECParamR.Text.Length / 2 != component_size_bytes) + throw new Exception("Wrong size of the EC curve parameter R"); + Array.Copy(Hex.Decode(tbECParamR.Text), 0, key, key_offset, component_size_bytes); + key_offset += component_size_bytes; + + if (tbECPrivKey.Text.Length / 2 != component_size_bytes) + throw new Exception("Wrong size of the EC private key (parameter S)"); + Array.Copy(Hex.Decode(tbECPrivKey.Text), 0, key, key_offset, component_size_bytes); + key_offset += component_size_bytes; + + temp = Convert.ToUInt16(tbECParamE1.Text); + key[key_offset++] = (byte)(temp >> 8); + key[key_offset++] = (byte)temp; + + if (!isCurveTrinomial) + { + temp = Convert.ToUInt16(tbECParamE2.Text); + key[key_offset++] = (byte)(temp >> 8); + key[key_offset++] = (byte)temp; + + temp = Convert.ToUInt16(tbECParamE3.Text); + key[key_offset++] = (byte)(temp >> 8); + key[key_offset++] = (byte)temp; + } + + temp = Convert.ToUInt16(tbECParamK.Text); + key[key_offset++] = (byte)(temp >> 8); + key[key_offset] = (byte)temp; + } + + byte[] aid = Hex.Decode(uFCoder.JCDL_AID); + byte[] selection_respone = new byte[16]; + + status = uFCoder.SetISO14443_4_Mode(); + if (status != DL_STATUS.UFR_OK) + throw new Exception(string.Format("Card error code: 0x{0:X}", status)); + else + uFR_Selected = true; + + status = uFCoder.JCAppSelectByAid(aid, (byte)aid.Length, selection_respone); + if (status != DL_STATUS.UFR_OK) + throw new Exception(string.Format("Card error code: 0x{0:X}", status)); + + status = uFCoder.JCAppPutPrivateKey(key_type, key_index, key, key_size_bits, key_param, key_param_len); + if (status != DL_STATUS.UFR_OK) + throw new Exception(string.Format("Card error code: 0x{0:X}", status)); + + MessageBox.Show("The key has been successfully stored.", "Sucess", MessageBoxButtons.OK, MessageBoxIcon.Information); + } + catch (Exception ex) + { + MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + finally + { + if (uFR_Selected) + { + uFCoder.s_block_deselect(100); + uFR_Selected = false; + } + Cursor.Current = Cursors.Default; + } + } + + //====================================================================================================================== + // Signature page: + //====================================================================================================================== + private bool m_gate_cbECKeyLength = false; + private int mECNameCurrComboIndex = 0; + private void cbECName_SelectedIndexChanged(object sender, EventArgs e) + { + m_gate_cbECKeyLength = true; + + if (mECNameCurrComboIndex != cbECName.SelectedIndex) + { + mECNameCurrComboIndex = cbECName.SelectedIndex; + tbECPrivKey.Text = ""; + tbECPubKey.Text = ""; + } + + if (cbECName.SelectedIndex < 15) + { + rbECFieldPrime.Checked = true; + } + else + { + rbECFieldBinary.Checked = true; + } + switch (cbECName.SelectedIndex) + { + case 0: + case 1: + cbECKeyLength.SelectedIndex = 0; + break; + case 2: + case 3: + cbECKeyLength.SelectedIndex = 2; + break; + case 4: + case 5: + case 6: + cbECKeyLength.SelectedIndex = 4; + break; + case 7: + case 8: + cbECKeyLength.SelectedIndex = 6; + break; + case 9: + case 10: + cbECKeyLength.SelectedIndex = 8; + break; + case 11: + case 12: + cbECKeyLength.SelectedIndex = 11; + break; + case 13: + cbECKeyLength.SelectedIndex = 13; + break; + case 14: + cbECKeyLength.SelectedIndex = 15; + break; + case 15: + case 16: + cbECKeyLength.SelectedIndex = 1; + break; + case 17: + case 18: + cbECKeyLength.SelectedIndex = 3; + break; + case 19: + case 20: + case 21: + cbECKeyLength.SelectedIndex = 5; + break; + case 22: + case 23: + cbECKeyLength.SelectedIndex = 7; + break; + case 24: + case 25: + cbECKeyLength.SelectedIndex = 9; + break; + + case 26: + cbECKeyLength.SelectedIndex = 10; + break; + case 27: + case 28: + cbECKeyLength.SelectedIndex = 12; + break; + case 29: + case 30: + cbECKeyLength.SelectedIndex = 14; + break; + case 31: + case 32: + cbECKeyLength.SelectedIndex = 16; + break; + } + populateECDomainParameters(cbECName.Text); + + m_gate_cbECKeyLength = false; + } + + private void cbECKeyLength_SelectedIndexChanged(object sender, EventArgs e) + { + if (m_gate_cbECKeyLength) + return; + + switch (cbECKeyLength.SelectedIndex) + { + case 0: + if (cbECName.SelectedIndex != 1) + cbECName.SelectedIndex = 0; + break; + case 1: + if (cbECName.SelectedIndex != 16) + cbECName.SelectedIndex = 15; + break; + case 2: + if (cbECName.SelectedIndex != 3) + cbECName.SelectedIndex = 2; + break; + case 3: + if (cbECName.SelectedIndex != 18) + cbECName.SelectedIndex = 17; + break; + case 4: + if (cbECName.SelectedIndex != 5 && cbECName.SelectedIndex != 6) + cbECName.SelectedIndex = 4; + break; + case 5: + if (cbECName.SelectedIndex != 20 && cbECName.SelectedIndex != 21) + cbECName.SelectedIndex = 19; + break; + case 6: + if (cbECName.SelectedIndex != 8) + cbECName.SelectedIndex = 7; + break; + case 7: + if (cbECName.SelectedIndex != 23) + cbECName.SelectedIndex = 22; + break; + case 8: + if (cbECName.SelectedIndex != 10) + cbECName.SelectedIndex = 9; + break; + case 9: + if (cbECName.SelectedIndex != 25) + cbECName.SelectedIndex = 24; + break; + case 10: + cbECName.SelectedIndex = 26; + break; + case 11: + if (cbECName.SelectedIndex != 12) + cbECName.SelectedIndex = 11; + break; + case 12: + if (cbECName.SelectedIndex != 28) + cbECName.SelectedIndex = 27; + break; + case 13: + cbECName.SelectedIndex = 13; + break; + case 14: + if (cbECName.SelectedIndex != 30) + cbECName.SelectedIndex = 29; + break; + case 15: + cbECName.SelectedIndex = 14; + break; + case 16: + if (cbECName.SelectedIndex != 32) + cbECName.SelectedIndex = 31; + break; + } + } + + //====================================================================================================================== + // Signature page: + //====================================================================================================================== + enum RADIX + { + Hex, + Base64, + ASCII, + Exception_FromFile + }; + RADIX mMessageRadix = RADIX.Hex; + RADIX mSignatureRadix = RADIX.Hex; + RADIX mHashRadix = RADIX.Hex; + Stream mSignigFileStream = null; + long mSignigFileBytesRead = 0; + bool isECDSACipher = false; + + private void tbMessageRadixChanged(object sender, EventArgs e) + { + bool showExceptionMessage = true; + try + { + if (sender == rbMessageFromFile) + { + OpenFileDialog dialog = new OpenFileDialog(); + dialog.Filter = "All files (*.*)|*.*"; + //dialog.InitialDirectory = @"C:\"; + dialog.Title = "Please select file for signing"; + if (dialog.ShowDialog() == DialogResult.OK) + { + tbMessage.ReadOnly = true; + tbMessage.BackColor = SystemColors.Info; + tbMessage.Text = dialog.FileName; + mMessageRadix = RADIX.Exception_FromFile; + btnSaveMessageToBin.Enabled = false; + } + else + { + showExceptionMessage = false; + throw new Exception("File selection canceled"); + } + } + else + { + switch (mMessageRadix) // from radix + { + case RADIX.Hex: + if (sender == rbMessageBase64) + { + tbMessage.Text = Convert.ToBase64String(Hex.Decode(tbMessage.Text)); + mMessageRadix = RADIX.Base64; + } + else if (sender == rbMessageAscii) + { + tbMessage.Text = Encoding.ASCII.GetString(Hex.Decode(tbMessage.Text)); + mMessageRadix = RADIX.ASCII; + } + break; + case RADIX.Base64: + if (sender == rbMessageHex) + { + tbMessage.Text = BitConverter.ToString(Convert.FromBase64String(tbMessage.Text)).Replace("-", ""); + mMessageRadix = RADIX.Hex; + } + else if (sender == rbMessageAscii) + { + tbMessage.Text = Encoding.ASCII.GetString(Convert.FromBase64String(tbMessage.Text)); + mMessageRadix = RADIX.ASCII; + } + break; + case RADIX.ASCII: + if (sender == rbMessageHex) + { + tbMessage.Text = BitConverter.ToString(Encoding.ASCII.GetBytes(tbMessage.Text)).Replace("-", ""); + mMessageRadix = RADIX.Hex; + } + else if (sender == rbMessageBase64) + { + tbMessage.Text = Convert.ToBase64String(Encoding.ASCII.GetBytes(tbMessage.Text)); + mMessageRadix = RADIX.Base64; + } + break; + case RADIX.Exception_FromFile: + tbMessage.BackColor = SystemColors.Window; + tbMessage.Text = ""; + tbMessage.ReadOnly = false; + if (sender == rbMessageHex) + mMessageRadix = RADIX.Hex; + else if (sender == rbMessageBase64) + mMessageRadix = RADIX.Base64; + else if (sender == rbMessageAscii) + mMessageRadix = RADIX.ASCII; + + btnSaveMessageToBin.Enabled = true; + break; + } + } + } + catch (Exception ex) + { + switch (mMessageRadix) + { + case RADIX.Hex: + rbMessageHex.Checked = true; + break; + case RADIX.Base64: + rbMessageBase64.Checked = true; + break; + case RADIX.ASCII: + rbMessageAscii.Checked = true; + break; + case RADIX.Exception_FromFile: + rbMessageFromFile.Checked = true; + break; + } + if (showExceptionMessage) + MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + + private void tbSignatureRadixChanged(object sender, EventArgs e) + { + try + { + if (mSignatureRadix == RADIX.Hex && sender == rbSignatureBase64) + { + tbSignature.Text = Convert.ToBase64String(Hex.Decode(tbSignature.Text)); + mSignatureRadix = RADIX.Base64; + } + else if (mSignatureRadix == RADIX.Base64 && sender == rbSignatureHex) + { + tbSignature.Text = BitConverter.ToString(Convert.FromBase64String(tbSignature.Text)).Replace("-", ""); + mSignatureRadix = RADIX.Hex; + } + } + catch (Exception ex) + { + if (mSignatureRadix == RADIX.Hex) + rbSignatureHex.Checked = true; + else // case RADIX.Base64: + rbSignatureBase64.Checked = true; + + MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + + private void tbHashRadixChanged(object sender, EventArgs e) + { + try + { + if (mHashRadix == RADIX.Hex && sender == rbHashBase64) + { + tbHash.Text = Convert.ToBase64String(Hex.Decode(tbHash.Text)); + mHashRadix = RADIX.Base64; + } + else if (mHashRadix == RADIX.Base64 && sender == rbHashHex) + { + tbHash.Text = BitConverter.ToString(Convert.FromBase64String(tbHash.Text)).Replace("-", ""); + mHashRadix = RADIX.Hex; + } + } + catch (Exception ex) + { + if (mHashRadix == RADIX.Hex) + rbHashHex.Checked = true; + else // RADIX.Base64: + rbHashBase64.Checked = true; + + MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + + private byte[] Hash(string filename, HashAlgorithm hash) + { + byte[] hashBytes; + + using (var fs = new FileStream(filename, FileMode.Open, FileAccess.Read, + FileShare.Delete | FileShare.ReadWrite)) + { + try + { + hashBytes = hash.ComputeHash(fs); + } + finally + { + hash.Clear(); + } + } + return hashBytes; + } + + private byte[] Hash(byte[] plain, HashAlgorithm hash) + { + byte[] hashBytes; + { + try + { + hashBytes = hash.ComputeHash(plain); + } + finally + { + hash.Clear(); + } + } + return hashBytes; + } + + private void btnHash_Click(object sender, EventArgs e) + { + HashAlgorithm hashAlg; + byte[] plain; + + try + { + if (mSignatureRadix == RADIX.Hex) + { + plain = Hex.Decode(tbSignature.Text); + } + else // mSignatureRadix == RADIX.Base64 + { + plain = Convert.FromBase64String(tbSignature.Text); + } + if (plain.Length == 0) + throw new Exception("Invalid signature."); + + switch (cbHashAlg.Text) + { + case "MD5": + hashAlg = new MD5CryptoServiceProvider(); + break; + case "SHA-1": + hashAlg = new SHA1CryptoServiceProvider(); + break; + case "SHA-256": + hashAlg = new SHA256CryptoServiceProvider(); + break; + case "SHA-384": + hashAlg = new SHA384CryptoServiceProvider(); + break; + case "SHA-512": + hashAlg = new SHA512CryptoServiceProvider(); + break; + default: + throw new Exception("Unknown hash algorithm"); + } + + if (mHashRadix == RADIX.Hex) + { + tbHash.Text = BitConverter.ToString(Hash(plain, hashAlg)).Replace("-", ""); + } + else // mHashRadix == RADIX.Base64 + { + tbHash.Text = Convert.ToBase64String(Hash(plain, hashAlg)); + } + + btnHashStoreToBin.Enabled = true; + } + catch (Exception ex) + { + MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + + private void btnSaveMessageToBin_Click(object sender, EventArgs e) + { + byte[] byteArray; + SaveFileDialog dialog; + + try + { + switch (mMessageRadix) // from radix + { + case RADIX.Hex: + byteArray = Hex.Decode(tbMessage.Text); + break; + case RADIX.Base64: + byteArray = Convert.FromBase64String(tbMessage.Text); + break; + case RADIX.ASCII: + byteArray = Encoding.ASCII.GetBytes(tbMessage.Text); + break; + default: + throw new Exception("Unknown input data radix"); + } + if (byteArray.Length == 0) + throw new Exception("There is no valid data to save."); + + dialog = new SaveFileDialog(); + dialog.Filter = "Binary files (*.bin)|*.bin|All files (*.*)|*.*"; + //dialog.FilterIndex = 2; + dialog.RestoreDirectory = true; + + if (dialog.ShowDialog() == DialogResult.OK) + { + using (var fs = new FileStream(dialog.FileName, FileMode.Create, FileAccess.Write)) + { + fs.Write(byteArray, 0, byteArray.Length); + } + } + } + catch (Exception ex) + { + MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + + private void btnSignatureStoreToBin_Click(object sender, EventArgs e) + { + byte[] byteArray; + SaveFileDialog dialog; + + try + { + switch (mSignatureRadix) // from radix + { + case RADIX.Hex: + byteArray = Hex.Decode(tbSignature.Text); + break; + case RADIX.Base64: + byteArray = Convert.FromBase64String(tbSignature.Text); + break; + case RADIX.ASCII: + byteArray = Encoding.ASCII.GetBytes(tbSignature.Text); + break; + default: + throw new Exception("Unknown input data radix"); + } + if (byteArray.Length == 0) + throw new Exception("There is no valid data to save."); + + dialog = new SaveFileDialog(); + dialog.Filter = "Signature binary files (*.sig)|*.sig|Binary files (*.bin)|*.bin|All files (*.*)|*.*"; + dialog.RestoreDirectory = true; + + if (dialog.ShowDialog() == DialogResult.OK) + { + using (var fs = new FileStream(dialog.FileName, FileMode.Create, FileAccess.Write)) + { + fs.Write(byteArray, 0, byteArray.Length); + } + } + } + catch (Exception ex) + { + MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + + private void btnHashStoreToBin_Click(object sender, EventArgs e) + { + byte[] byteArray; + string signature_extension, file_comment; + + SaveFileDialog dialog; + + try + { + file_comment = " hash of the signature generated by uFR Signer (http://www.d-logic.net/nfc-rfid-reader-sdk/)\r\n"; + switch (cbHashAlg.Text) + { + case "MD5": + signature_extension = "MD5 files (*.md5)|*.md5|"; + file_comment = "# MD5" + file_comment; + break; + case "SHA-1": + signature_extension = "SHA1 files (*.sha1)|*.sha1|"; + file_comment = "# SHA1" + file_comment; + break; + case "SHA-256": + signature_extension = "SHA256 files (*.sha256)|*.sha256|"; + file_comment = "# SHA256" + file_comment; + break; + case "SHA-384": + signature_extension = "SHA384 files (*.sha384)|*.sha384|"; + file_comment = "# SHA384" + file_comment; + break; + case "SHA-512": + signature_extension = "SHA512 files (*.sha512)|*.sha512|"; + file_comment = "# SHA512" + file_comment; + break; + default: + throw new Exception("Unknown hash algorithm"); + } + + switch (mHashRadix) // from radix + { + case RADIX.Hex: + byteArray = Hex.Decode(tbHash.Text); + break; + case RADIX.Base64: + byteArray = Convert.FromBase64String(tbHash.Text); + break; + case RADIX.ASCII: + byteArray = Encoding.ASCII.GetBytes(tbHash.Text); + break; + default: + throw new Exception("Unknown input data radix"); + } + if (byteArray.Length == 0) + throw new Exception("There is no valid data to save."); + + dialog = new SaveFileDialog(); + dialog.Filter = signature_extension + "Binary files (*.bin)|*.bin"; + dialog.RestoreDirectory = true; + + if (dialog.ShowDialog() == DialogResult.OK) + { + if (dialog.FilterIndex == 1) + { + // hash text file format: + File.WriteAllText(dialog.FileName, file_comment + "\r\n" + + BitConverter.ToString(byteArray).Replace("-", "") + + " *\r\n"); // ToDo: + mSignatureFileName between * and \r\n + } + else using (var fs = new FileStream(dialog.FileName, FileMode.Create, FileAccess.Write)) + { + fs.Write(byteArray, 0, byteArray.Length); + } + } + } + catch (Exception ex) + { + MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + + private int mHashAlgCurrComboIndex = 0; + + private void cbHashAlg_SelectedIndexChanged(object sender, EventArgs e) + { + if (mHashAlgCurrComboIndex != cbHashAlg.SelectedIndex) + { + mHashAlgCurrComboIndex = cbHashAlg.SelectedIndex; + tbHash.Text = ""; + } + } + + private void btnSignature_Click(object sender, EventArgs e) + { + DL_STATUS status; + BackgroundWorker bgw = null; + byte key_index = 0; + byte jc_signer_cipher = 0; + byte jc_signer_digest = 0; + byte jc_signer_padding = 0; + byte[] sig; + int chunk_len = 0; + byte[] chunk; + + try + { + if (!uFR_Opened) + throw new Exception(uFR_NotOpenedMessage); + + if (tbMessage.Text.Trim().Equals("")) + throw new Exception("Signing message can't have a zero length."); + + tbSignature.Text = ""; + + key_index = Convert.ToByte(cbSignatureKeyIndex.Text); + + if (cbCipher.Text.Equals("RSA")) + { + isECDSACipher = false; + jc_signer_cipher = (byte)JCDL_SIGNER_CIPHERS.SIG_CIPHER_RSA; + jc_signer_padding = (byte)JCDL_SIGNER_PADDINGS.PAD_PKCS1; + } + else if (cbCipher.Text.Equals("ECDSA")) + { + isECDSACipher = true; + jc_signer_cipher = (byte)JCDL_SIGNER_CIPHERS.SIG_CIPHER_ECDSA; + jc_signer_padding = (byte)JCDL_SIGNER_PADDINGS.PAD_NULL; + } + + switch (cbDigest.Text) + { + //(byte)JC_SIGNER_ALG.ALG_ECDSA_NONE; + case "SHA-1": + jc_signer_digest = (byte)JCDL_SIGNER_DIGESTS.ALG_SHA; + break; + case "SHA-224": + jc_signer_digest = (byte)JCDL_SIGNER_DIGESTS.ALG_SHA_224; + break; + case "SHA-256": + jc_signer_digest = (byte)JCDL_SIGNER_DIGESTS.ALG_SHA_256; + break; + case "SHA-384": + jc_signer_digest = (byte)JCDL_SIGNER_DIGESTS.ALG_SHA_384; + break; + case "SHA-512": + jc_signer_digest = (byte)JCDL_SIGNER_DIGESTS.ALG_SHA_512; + break; + default: + throw new Exception("Unknown digest algorithm"); + } + + switch (mMessageRadix) // from radix + { + case RADIX.Hex: + chunk = Hex.Decode(tbMessage.Text); + break; + case RADIX.Base64: + chunk = Convert.FromBase64String(tbMessage.Text); + break; + case RADIX.ASCII: + chunk = Encoding.ASCII.GetBytes(tbMessage.Text); + break; + case RADIX.Exception_FromFile: + chunk = new byte[uFCoder.SIG_MAX_PLAIN_DATA_LEN]; + break; + default: + throw new Exception("Unknown input data radix"); + } + chunk_len = chunk.Length; + + pbSigning.Value = 0; + pbSigning.Maximum = 10000; + Refresh(); + + if (mMessageRadix == RADIX.Exception_FromFile) + mSignigFileStream = File.OpenRead(tbMessage.Text); + + byte[] aid = Hex.Decode(uFCoder.JCDL_AID); + byte[] selection_respone = new byte[16]; + + status = uFCoder.SetISO14443_4_Mode(); + if (status != DL_STATUS.UFR_OK) + throw new Exception(string.Format("Card error code: 0x{0:X}", status)); + else + uFR_Selected = true; + + status = uFCoder.JCAppSelectByAid(aid, (byte)aid.Length, selection_respone); + if (status != DL_STATUS.UFR_OK) + throw new Exception(string.Format("Card error code: 0x{0:X}", status)); + + if (mSignigFileStream != null) + { + chunk_len = (int)mSignigFileStream.Length > uFCoder.SIG_MAX_PLAIN_DATA_LEN + ? (int)uFCoder.SIG_MAX_PLAIN_DATA_LEN + : (int)mSignigFileStream.Length; + + if (mSignigFileStream.Read(chunk, 0, chunk_len) != chunk_len) + throw new Exception("Error while reading file " + tbMessage.Text); + mSignigFileBytesRead = chunk_len; + } + + if ((mSignigFileStream != null) && (mSignigFileStream.Length > uFCoder.SIG_MAX_PLAIN_DATA_LEN)) + { + pbSigning.Maximum = (int)(mSignigFileStream.Length / uFCoder.SIG_MAX_PLAIN_DATA_LEN); + + status = uFCoder.JCAppSignatureBegin(jc_signer_cipher, jc_signer_digest, jc_signer_padding, + key_index, chunk, (UInt16)chunk_len, + null, 0); + if (status != DL_STATUS.UFR_OK) + throw new Exception(string.Format("Card error code: 0x{0:X}", status)); + + bgw = new BackgroundWorker(); + bgw.ProgressChanged += bgw_ProgressChanged; + bgw.DoWork += bgw_DoWork; + bgw.WorkerReportsProgress = true; + bgw.RunWorkerCompleted += bgw_WorkCompleted; + bgw.RunWorkerAsync(); + + pbSigning.Value = 5; + + // Disable controls critical for execution: + tabControl.Enabled = false; + return; + } + else + { + Cursor.Current = Cursors.WaitCursor; + + status = uFCoder.JCAppGenerateSignature(jc_signer_cipher, jc_signer_digest, jc_signer_padding, + key_index, + chunk, (UInt16)chunk_len, + out sig, + null, 0); + if (status != DL_STATUS.UFR_OK) + throw new Exception(string.Format("Card error code: 0x{0:X}", status)); + } + + pbSigning.Value = pbSigning.Maximum; + // workaround for animation feature (aero theme): + pbSigning.Value = pbSigning.Maximum - 1; + pbSigning.Value = pbSigning.Maximum; + + tbSignatureRadixChanged(rbMessageHex, new EventArgs()); + rbSignatureHex.Checked = true; + + if (isECDSACipher) + { + // In case of ECDSA signature, last 2 bytes are ushort value representing the key_size in bits + int len = sig.Length; + UInt16 key_size_bits = (UInt16)(((UInt16)sig[len - 2] << 8) | sig[len - 1]); + int key_size_bytes = (key_size_bits + 7) / 8; + byte[] der_sig = new byte[len - 2]; + Array.Copy(sig, der_sig, len - 2); + + Asn1InputStream decoder = new Asn1InputStream(new MemoryStream(der_sig)); + DerSequence seq = (DerSequence)decoder.ReadObject(); + DerInteger der_r = (DerInteger)seq[0]; + DerInteger der_s = (DerInteger)seq[1]; + byte[] r = der_r.PositiveValue.ToByteArray(); + byte[] s = der_s.PositiveValue.ToByteArray(); + + // Fix leading zeros (padding or removing depending on mECKeyByteSize: + while (r[0] == 0 && r.Length > key_size_bytes) + { + r = r.Skip(1).ToArray(); + } + if (key_size_bytes > r.Length) + r = zeroPadArray(r, 0, key_size_bytes - r.Length); + + while (s[0] == 0 && s.Length > key_size_bytes) + { + s = s.Skip(1).ToArray(); + } + if (key_size_bytes > s.Length) + s = zeroPadArray(s, 0, key_size_bytes - s.Length); + + tbSignature.Text = BitConverter.ToString(r).Replace("-", "") + BitConverter.ToString(s).Replace("-", ""); + } + else + tbSignature.Text = BitConverter.ToString(sig).Replace("-", ""); + } + catch (Exception ex) + { + MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + finally + { + Cursor.Current = Cursors.Default; + + if (bgw == null) + { + if (mSignigFileStream != null) + { + mSignigFileStream.Close(); + mSignigFileStream = null; + } + + if (uFR_Selected) + { + uFCoder.s_block_deselect(100); + uFR_Selected = false; + } + } + } +#if MY_DEBUG + // Debug: + debugSignature(); + //------------------ +#endif + } + + void bgw_DoWork(object sender, DoWorkEventArgs e) + { + int bytesRead; + byte[] chunk = new byte[uFCoder.SIG_MAX_PLAIN_DATA_LEN]; + DL_STATUS status; + + try + { + while ((bytesRead = mSignigFileStream.Read(chunk, 0, (int)uFCoder.SIG_MAX_PLAIN_DATA_LEN)) > 0) + { + status = uFCoder.JCAppSignatureUpdate(chunk, (UInt16)bytesRead); + if (status != DL_STATUS.UFR_OK) + throw new Exception(string.Format("Card error code: 0x{0:X}", status)); + + // when using PerformStep() the percentProgress arg is redundant + ((BackgroundWorker)sender).ReportProgress(0); + } + } + catch (Exception ex) + { + MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + finally + { + mSignigFileStream.Close(); + mSignigFileStream = null; + } + } + + void bgw_ProgressChanged(object sender, ProgressChangedEventArgs e) + { + pbSigning.PerformStep(); + } + + void bgw_WorkCompleted(object sender, RunWorkerCompletedEventArgs e) + { + DL_STATUS status; + + try + { + pbSigning.Value = pbSigning.Maximum; + + byte[] sig; + status = uFCoder.JCAppSignatureEnd(out sig); + if (status != DL_STATUS.UFR_OK) + throw new Exception(string.Format("Card error code: 0x{0:X}", status)); + + tbSignature.Text = ""; + tbSignatureRadixChanged(rbMessageHex, new EventArgs()); + rbSignatureHex.Checked = true; + + if (isECDSACipher) + { + // In case of ECDSA signature, last 2 bytes are ushort value representing the key_size in bits + int len = sig.Length; + UInt16 key_size_bits = (UInt16)(((UInt16)sig[len - 2] << 8) | sig[len - 1]); + int key_size_bytes = (key_size_bits + 7) / 8; + byte[] der_sig = new byte[len - 2]; + Array.Copy(sig, der_sig, len - 2); + + Asn1InputStream decoder = new Asn1InputStream(new MemoryStream(sig)); + DerSequence seq = (DerSequence)decoder.ReadObject(); + DerInteger der_r = (DerInteger)seq[0]; + DerInteger der_s = (DerInteger)seq[1]; + byte[] r = der_r.PositiveValue.ToByteArray(); + byte[] s = der_s.PositiveValue.ToByteArray(); + + // Fix leading zeros (padding or removing depending on mECKeyByteSize: + while (r[0] == 0 && r.Length > key_size_bytes) + { + r = r.Skip(1).ToArray(); + } + if (key_size_bytes > r.Length) + r = zeroPadArray(r, 0, key_size_bytes - r.Length); + + while (s[0] == 0 && s.Length > key_size_bytes) + { + s = s.Skip(1).ToArray(); + } + if (key_size_bytes > s.Length) + s = zeroPadArray(s, 0, key_size_bytes - s.Length); + + tbSignature.Text = BitConverter.ToString(r).Replace("-", "") + BitConverter.ToString(s).Replace("-", ""); + } + else + tbSignature.Text = BitConverter.ToString(sig).Replace("-", ""); + } + catch (Exception ex) + { + MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + finally + { + if (uFR_Selected) + { + uFCoder.s_block_deselect(100); + uFR_Selected = false; + } + + if (mSignigFileStream != null) + { + mSignigFileStream.Close(); + mSignigFileStream = null; + } + + // Enable controls critical for execution: + tabControl.Enabled = true; + } + } + +#if MY_DEBUG + const int INPUT_FILE_BUFFER_LEN = 8192; + int mECKeyByteSize; + bool isECDSACipher = false; + ISigner mSigner; + byte[] mMessage; + + void bgw_DoWork(object sender, DoWorkEventArgs e) + { + Int64 temp = 0, percentSize; + + try + { + ((BackgroundWorker)sender).ReportProgress(0); + + using (Stream source = File.OpenRead(tbMessage.Text)) + { + percentSize = source.Length / 1000; + int bytesRead; + while ((bytesRead = source.Read(mMessage, 0, mMessage.Length)) > 0) + { + mSigner.BlockUpdate(mMessage, 0, bytesRead); + temp += bytesRead; + if (temp >= percentSize) + { + temp = 0; + // when using PerformStep() the percentProgress arg is redundant + ((BackgroundWorker)sender).ReportProgress(0); + } + } + } + } + catch (Exception ex) + { + MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + + void bgw_ProgressChanged(object sender, ProgressChangedEventArgs e) + { + pbSigning.PerformStep(); + } + + void bgw_WorkCompleted(object sender, RunWorkerCompletedEventArgs e) + { + try + { + pbSigning.Value = pbSigning.Maximum; + pbSigning.Value = pbSigning.Maximum - 1; // workaround for animation feature (aero theme) + pbSigning.Value = pbSigning.Maximum; + + byte[] result = mSigner.GenerateSignature(); + + tbSignature.Text = ""; + tbSignatureRadixChanged(rbMessageHex, new EventArgs()); + rbSignatureHex.Checked = true; + + if (isECDSACipher) + { + Asn1InputStream decoder = new Asn1InputStream(new MemoryStream(result)); + DerSequence seq = (DerSequence)decoder.ReadObject(); + DerInteger der_r = (DerInteger)seq[0]; + DerInteger der_s = (DerInteger)seq[1]; + byte[] r = der_r.PositiveValue.ToByteArray(); + byte[] s = der_s.PositiveValue.ToByteArray(); + + // Fix leading zeros (padding or removing depending on mECKeyByteSize: + while (r[0] == 0 && r.Length > mECKeyByteSize) + { + r = r.Skip(1).ToArray(); + } + if (mECKeyByteSize > r.Length) + r = zeroPadArray(r, 0, mECKeyByteSize - r.Length); + + while (s[0] == 0 && s.Length > mECKeyByteSize) + { + s = s.Skip(1).ToArray(); + } + if (mECKeyByteSize > s.Length) + s = zeroPadArray(s, 0, mECKeyByteSize - s.Length); + + tbSignature.Text = BitConverter.ToString(r).Replace("-", "") + BitConverter.ToString(s).Replace("-", ""); + } + else + tbSignature.Text = BitConverter.ToString(result).Replace("-", ""); + } + catch (Exception ex) + { + MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + finally + { + // Enable controls critical for execution: + tabControl.Enabled = true; + } + } + + private void debugSignature() + { + string signatureMechanism; + AsymmetricKeyParameter key; + + try + { + if (cbCipher.Text.Equals("RSA")) + { + isECDSACipher = false; + key = new RsaPrivateCrtKeyParameters( + new BigInteger(1, Hex.Decode(tbRSAModulus.Text)), + new BigInteger(1, Hex.Decode(tbRSAPubExp.Text)), + new BigInteger(1, Hex.Decode(tbRSAPrivExp.Text)), + new BigInteger(1, Hex.Decode(tbRSAPrivP.Text)), + new BigInteger(1, Hex.Decode(tbRSAPrivQ.Text)), + new BigInteger(1, Hex.Decode(tbRSAPrivDP1.Text)), + new BigInteger(1, Hex.Decode(tbRSAPrivDQ1.Text)), + new BigInteger(1, Hex.Decode(tbRSAPrivPQ.Text)) + ); + + switch (cbDigest.Text) + { + case "SHA-1": + signatureMechanism = "SHA-1withRSA"; + break; + case "SHA-224": + signatureMechanism = "SHA-224withRSA"; + break; + case "SHA-256": + signatureMechanism = "SHA-256withRSA"; + break; + case "SHA-384": + signatureMechanism = "SHA-384withRSA"; + if (((RsaPrivateCrtKeyParameters)key).Modulus.BitLength < 736) + throw new Exception("RSA key bit length not matched with SHA-384.\r\n(use longer RSA key)"); + break; + case "SHA-512": + if (((RsaPrivateCrtKeyParameters)key).Modulus.BitLength < 768) + throw new Exception("RSA key bit length not matched with SHA-512.\r\n(use longer RSA key)"); + signatureMechanism = "SHA-512withRSA"; + break; + default: + throw new Exception("Unknown hash algorithm"); + } + } + else if (cbCipher.Text.Equals("ECDSA")) + { + isECDSACipher = true; + switch (cbDigest.Text) + { + case "SHA-1": + signatureMechanism = "SHA-1withECDSA"; + break; + case "SHA-224": + signatureMechanism = "SHA-224withECDSA"; + break; + case "SHA-256": + signatureMechanism = "SHA-256withECDSA"; + break; + case "SHA-384": + signatureMechanism = "SHA-384withECDSA"; + break; + case "SHA-512": + signatureMechanism = "SHA-512withECDSA"; + break; + default: + throw new Exception("Unknown digest algorithm"); + } + + X9ECParameters curve = SecNamedCurves.GetByName(cbECName.Text); + ECDomainParameters curveSpec = new ECDomainParameters(curve.Curve, curve.G, curve.N, curve.H, curve.GetSeed()); + key = new ECPrivateKeyParameters("ECDSA", new BigInteger(1, Hex.Decode(tbECPrivKey.Text)), curveSpec); + mECKeyByteSize = (curve.Curve.FieldSize + 7) / 8; + } + else + throw new Exception("Unknown cipher algorithm"); + + mSigner = SignerUtilities.GetSigner(signatureMechanism); + mSigner.Init(true, key); + + switch (mMessageRadix) // from radix + { + case RADIX.Hex: + mMessage = Hex.Decode(tbMessage.Text); + break; + case RADIX.Base64: + mMessage = Convert.FromBase64String(tbMessage.Text); + break; + case RADIX.ASCII: + mMessage = Encoding.ASCII.GetBytes(tbMessage.Text); + break; + case RADIX.Exception_FromFile: + mMessage = new byte[INPUT_FILE_BUFFER_LEN]; + break; + default: + throw new Exception("Unknown input data radix"); + } + + pbSigning.Value = 0; + Refresh(); + + if (mMessageRadix == RADIX.Exception_FromFile) + { + var bgw = new BackgroundWorker(); + bgw.ProgressChanged += bgw_ProgressChanged; + bgw.DoWork += bgw_DoWork; + bgw.WorkerReportsProgress = true; + bgw.RunWorkerCompleted += bgw_WorkCompleted; + bgw.RunWorkerAsync(); + + // Disable controls critical for execution: + tabControl.Enabled = false; + return; + } + else + mSigner.BlockUpdate(mMessage, 0, mMessage.Length); + + byte[] result = mSigner.GenerateSignature(); + pbSigning.Value = pbSigning.Maximum; + pbSigning.Value = pbSigning.Maximum - 1; // workaround for animation feature (aero theme) + pbSigning.Value = pbSigning.Maximum; + + tbSignature.Text = ""; + tbSignatureRadixChanged(rbMessageHex, new EventArgs()); + rbSignatureHex.Checked = true; + + if (isECDSACipher) + { + Asn1InputStream decoder = new Asn1InputStream(new MemoryStream(result)); + DerSequence seq = (DerSequence)decoder.ReadObject(); + DerInteger der_r = (DerInteger)seq[0]; + DerInteger der_s = (DerInteger)seq[1]; + byte[] r = der_r.PositiveValue.ToByteArray(); + byte[] s = der_s.PositiveValue.ToByteArray(); + + // Fix leading zeros (padding or removing depending on mECKeyByteSize: + while (r[0] == 0 && r.Length > mECKeyByteSize) + { + r = r.Skip(1).ToArray(); + } + if (mECKeyByteSize > r.Length) + r = zeroPadArray(r, 0, mECKeyByteSize - r.Length); + + while (s[0] == 0 && s.Length > mECKeyByteSize) + { + s = s.Skip(1).ToArray(); + } + if (mECKeyByteSize > s.Length) + s = zeroPadArray(s, 0, mECKeyByteSize - s.Length); + + tbSignature.Text = BitConverter.ToString(r).Replace("-", "") + BitConverter.ToString(s).Replace("-", ""); + } + else + tbSignature.Text = BitConverter.ToString(result).Replace("-", ""); + } + catch (Exception ex) + { + MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } +#endif + } +} diff --git a/frmMain.designer.cs b/frmMain.designer.cs new file mode 100644 index 0000000..c1f6901 --- /dev/null +++ b/frmMain.designer.cs @@ -0,0 +1,1796 @@ +namespace EcdsaTest +{ + partial class frmMain + { + ///

    + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.tabControl = new System.Windows.Forms.TabControl(); + this.tabRSAKeys = new System.Windows.Forms.TabPage(); + this.gbRSAPub = new System.Windows.Forms.GroupBox(); + this.lbRSAPubExp = new System.Windows.Forms.Label(); + this.tbRSAPubExp = new System.Windows.Forms.TextBox(); + this.gbRSAModulus = new System.Windows.Forms.GroupBox(); + this.lbRSAModulus = new System.Windows.Forms.Label(); + this.tbRSAModulus = new System.Windows.Forms.TextBox(); + this.gbRSAPriv = new System.Windows.Forms.GroupBox(); + this.lbRSAPrivP = new System.Windows.Forms.Label(); + this.lbRSAPrivQ = new System.Windows.Forms.Label(); + this.lbRSAPrivPQ = new System.Windows.Forms.Label(); + this.lbRSAPrivDP1 = new System.Windows.Forms.Label(); + this.tbRSAPrivDQ1 = new System.Windows.Forms.TextBox(); + this.tbRSAPrivDP1 = new System.Windows.Forms.TextBox(); + this.tbRSAPrivPQ = new System.Windows.Forms.TextBox(); + this.tbRSAPrivQ = new System.Windows.Forms.TextBox(); + this.lbRSAPrivDQ1 = new System.Windows.Forms.Label(); + this.lbRSAPrivExp = new System.Windows.Forms.Label(); + this.tbRSAPrivExp = new System.Windows.Forms.TextBox(); + this.tbRSAPrivP = new System.Windows.Forms.TextBox(); + this.gpPrivateKeyMode = new System.Windows.Forms.GroupBox(); + this.rbCRT = new System.Windows.Forms.RadioButton(); + this.rbModExp = new System.Windows.Forms.RadioButton(); + this.gbRSACommands = new System.Windows.Forms.GroupBox(); + this.btnRestoreRSAPriv = new System.Windows.Forms.Button(); + this.btnBackupRSAPriv = new System.Windows.Forms.Button(); + this.btnSaveRSAPub = new System.Windows.Forms.Button(); + this.btnStoreRSAPriv = new System.Windows.Forms.Button(); + this.btnMkRSAKey = new System.Windows.Forms.Button(); + this.cbRSAKeyLength = new System.Windows.Forms.ComboBox(); + this.cbRSAKeyIndex = new System.Windows.Forms.ComboBox(); + this.lbRSAKeyLength = new System.Windows.Forms.Label(); + this.lbRSAKeyIndex = new System.Windows.Forms.Label(); + this.btnRSAImportP12 = new System.Windows.Forms.Button(); + this.tabECKeys = new System.Windows.Forms.TabPage(); + this.gbECPubKey = new System.Windows.Forms.GroupBox(); + this.lbECPubKey = new System.Windows.Forms.Label(); + this.tbECPubKey = new System.Windows.Forms.TextBox(); + this.gbECPrivKey = new System.Windows.Forms.GroupBox(); + this.lbECPrivKey = new System.Windows.Forms.Label(); + this.tbECPrivKey = new System.Windows.Forms.TextBox(); + this.gbECDomainParameters = new System.Windows.Forms.GroupBox(); + this.lbECParamK = new System.Windows.Forms.Label(); + this.tbECParamK = new System.Windows.Forms.TextBox(); + this.lbECParamR = new System.Windows.Forms.Label(); + this.tbECParamR = new System.Windows.Forms.TextBox(); + this.lbECParamG = new System.Windows.Forms.Label(); + this.tbECParamG = new System.Windows.Forms.TextBox(); + this.lbECParamB = new System.Windows.Forms.Label(); + this.tbECParamB = new System.Windows.Forms.TextBox(); + this.ltbECParamA = new System.Windows.Forms.Label(); + this.tbECParamA = new System.Windows.Forms.TextBox(); + this.gbECReductionPolynomial = new System.Windows.Forms.GroupBox(); + this.rtbECReductionPolynomial = new System.Windows.Forms.RichTextBox(); + this.lbECParamE3 = new System.Windows.Forms.Label(); + this.tbECParamE3 = new System.Windows.Forms.TextBox(); + this.lbECParamE2 = new System.Windows.Forms.Label(); + this.tbECParamE2 = new System.Windows.Forms.TextBox(); + this.lbECParamE1 = new System.Windows.Forms.Label(); + this.tbECParamE1 = new System.Windows.Forms.TextBox(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.rbECFieldPrime = new System.Windows.Forms.RadioButton(); + this.rbECFieldBinary = new System.Windows.Forms.RadioButton(); + this.lbECParamPrime = new System.Windows.Forms.Label(); + this.tbECParamPrime = new System.Windows.Forms.TextBox(); + this.gbECCommands = new System.Windows.Forms.GroupBox(); + this.btnRestoreECPriv = new System.Windows.Forms.Button(); + this.btnBackupECPriv = new System.Windows.Forms.Button(); + this.cbECName = new System.Windows.Forms.ComboBox(); + this.lbECName = new System.Windows.Forms.Label(); + this.btnSaveECPub = new System.Windows.Forms.Button(); + this.btnStoreECPriv = new System.Windows.Forms.Button(); + this.btnMkECKey = new System.Windows.Forms.Button(); + this.cbECKeyLength = new System.Windows.Forms.ComboBox(); + this.cbECKeyIndex = new System.Windows.Forms.ComboBox(); + this.lbECKeyLength = new System.Windows.Forms.Label(); + this.lbECKeyIndex = new System.Windows.Forms.Label(); + this.btnECImportP12 = new System.Windows.Forms.Button(); + this.tabSignature = new System.Windows.Forms.TabPage(); + this.gbSignatureHash = new System.Windows.Forms.GroupBox(); + this.btnHashStoreToBin = new System.Windows.Forms.Button(); + this.btnHash = new System.Windows.Forms.Button(); + this.cbHashAlg = new System.Windows.Forms.ComboBox(); + this.lbHashAlg = new System.Windows.Forms.Label(); + this.rbHashBase64 = new System.Windows.Forms.RadioButton(); + this.rbHashHex = new System.Windows.Forms.RadioButton(); + this.lbHash = new System.Windows.Forms.Label(); + this.tbHash = new System.Windows.Forms.TextBox(); + this.gbSignature = new System.Windows.Forms.GroupBox(); + this.pbSigning = new System.Windows.Forms.ProgressBar(); + this.btnSignature = new System.Windows.Forms.Button(); + this.btnSignatureStoreToBin = new System.Windows.Forms.Button(); + this.rbSignatureBase64 = new System.Windows.Forms.RadioButton(); + this.rbSignatureHex = new System.Windows.Forms.RadioButton(); + this.lbSignature = new System.Windows.Forms.Label(); + this.tbSignature = new System.Windows.Forms.TextBox(); + this.gbMessage = new System.Windows.Forms.GroupBox(); + this.btnSaveMessageToBin = new System.Windows.Forms.Button(); + this.rbMessageFromFile = new System.Windows.Forms.RadioButton(); + this.rbMessageBase64 = new System.Windows.Forms.RadioButton(); + this.rbMessageHex = new System.Windows.Forms.RadioButton(); + this.rbMessageAscii = new System.Windows.Forms.RadioButton(); + this.lbMessage = new System.Windows.Forms.Label(); + this.tbMessage = new System.Windows.Forms.TextBox(); + this.gbSignatureParameters = new System.Windows.Forms.GroupBox(); + this.cbCipher = new System.Windows.Forms.ComboBox(); + this.lbCipher = new System.Windows.Forms.Label(); + this.cbDigest = new System.Windows.Forms.ComboBox(); + this.lbDigest = new System.Windows.Forms.Label(); + this.cbSignatureKeyIndex = new System.Windows.Forms.ComboBox(); + this.lbSignatureKeyIndex = new System.Windows.Forms.Label(); + this.llbDLogicURL = new System.Windows.Forms.LinkLabel(); + this.tabControl.SuspendLayout(); + this.tabRSAKeys.SuspendLayout(); + this.gbRSAPub.SuspendLayout(); + this.gbRSAModulus.SuspendLayout(); + this.gbRSAPriv.SuspendLayout(); + this.gpPrivateKeyMode.SuspendLayout(); + this.gbRSACommands.SuspendLayout(); + this.tabECKeys.SuspendLayout(); + this.gbECPubKey.SuspendLayout(); + this.gbECPrivKey.SuspendLayout(); + this.gbECDomainParameters.SuspendLayout(); + this.gbECReductionPolynomial.SuspendLayout(); + this.groupBox1.SuspendLayout(); + this.gbECCommands.SuspendLayout(); + this.tabSignature.SuspendLayout(); + this.gbSignatureHash.SuspendLayout(); + this.gbSignature.SuspendLayout(); + this.gbMessage.SuspendLayout(); + this.gbSignatureParameters.SuspendLayout(); + this.SuspendLayout(); + // + // tabControl + // + this.tabControl.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.tabControl.Controls.Add(this.tabRSAKeys); + this.tabControl.Controls.Add(this.tabECKeys); + this.tabControl.Controls.Add(this.tabSignature); + this.tabControl.Location = new System.Drawing.Point(12, 12); + this.tabControl.Name = "tabControl"; + this.tabControl.SelectedIndex = 0; + this.tabControl.Size = new System.Drawing.Size(1142, 600); + this.tabControl.TabIndex = 0; + // + // tabRSAKeys + // + this.tabRSAKeys.BackColor = System.Drawing.SystemColors.Control; + this.tabRSAKeys.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; + this.tabRSAKeys.Controls.Add(this.gbRSAPub); + this.tabRSAKeys.Controls.Add(this.gbRSAModulus); + this.tabRSAKeys.Controls.Add(this.gbRSAPriv); + this.tabRSAKeys.Controls.Add(this.gbRSACommands); + this.tabRSAKeys.Location = new System.Drawing.Point(4, 22); + this.tabRSAKeys.Margin = new System.Windows.Forms.Padding(0); + this.tabRSAKeys.Name = "tabRSAKeys"; + this.tabRSAKeys.Padding = new System.Windows.Forms.Padding(3); + this.tabRSAKeys.Size = new System.Drawing.Size(1134, 574); + this.tabRSAKeys.TabIndex = 0; + this.tabRSAKeys.Text = "RSA Keys"; + // + // gbRSAPub + // + this.gbRSAPub.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.gbRSAPub.Controls.Add(this.lbRSAPubExp); + this.gbRSAPub.Controls.Add(this.tbRSAPubExp); + this.gbRSAPub.Location = new System.Drawing.Point(8, 381); + this.gbRSAPub.Margin = new System.Windows.Forms.Padding(8); + this.gbRSAPub.Name = "gbRSAPub"; + this.gbRSAPub.Size = new System.Drawing.Size(1116, 60); + this.gbRSAPub.TabIndex = 3; + this.gbRSAPub.TabStop = false; + this.gbRSAPub.Text = "Public Key"; + // + // lbRSAPubExp + // + this.lbRSAPubExp.AutoSize = true; + this.lbRSAPubExp.Location = new System.Drawing.Point(212, 25); + this.lbRSAPubExp.Margin = new System.Windows.Forms.Padding(3); + this.lbRSAPubExp.Name = "lbRSAPubExp"; + this.lbRSAPubExp.Size = new System.Drawing.Size(17, 13); + this.lbRSAPubExp.TabIndex = 0; + this.lbRSAPubExp.Text = "E:"; + // + // tbRSAPubExp + // + this.tbRSAPubExp.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.tbRSAPubExp.BackColor = System.Drawing.SystemColors.Window; + this.tbRSAPubExp.Font = new System.Drawing.Font("Courier New", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); + this.tbRSAPubExp.Location = new System.Drawing.Point(233, 21); + this.tbRSAPubExp.Margin = new System.Windows.Forms.Padding(10); + this.tbRSAPubExp.Name = "tbRSAPubExp"; + this.tbRSAPubExp.Size = new System.Drawing.Size(870, 22); + this.tbRSAPubExp.TabIndex = 1; + // + // gbRSAModulus + // + this.gbRSAModulus.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.gbRSAModulus.Controls.Add(this.lbRSAModulus); + this.gbRSAModulus.Controls.Add(this.tbRSAModulus); + this.gbRSAModulus.Location = new System.Drawing.Point(8, 82); + this.gbRSAModulus.Margin = new System.Windows.Forms.Padding(8); + this.gbRSAModulus.Name = "gbRSAModulus"; + this.gbRSAModulus.Size = new System.Drawing.Size(1116, 63); + this.gbRSAModulus.TabIndex = 1; + this.gbRSAModulus.TabStop = false; + this.gbRSAModulus.Text = "Key Modulus"; + // + // lbRSAModulus + // + this.lbRSAModulus.AutoSize = true; + this.lbRSAModulus.Location = new System.Drawing.Point(6, 30); + this.lbRSAModulus.Margin = new System.Windows.Forms.Padding(3); + this.lbRSAModulus.Name = "lbRSAModulus"; + this.lbRSAModulus.Size = new System.Drawing.Size(18, 13); + this.lbRSAModulus.TabIndex = 0; + this.lbRSAModulus.Text = "N:"; + // + // tbRSAModulus + // + this.tbRSAModulus.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.tbRSAModulus.BackColor = System.Drawing.SystemColors.Window; + this.tbRSAModulus.Font = new System.Drawing.Font("Courier New", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); + this.tbRSAModulus.Location = new System.Drawing.Point(30, 26); + this.tbRSAModulus.Margin = new System.Windows.Forms.Padding(10); + this.tbRSAModulus.Name = "tbRSAModulus"; + this.tbRSAModulus.Size = new System.Drawing.Size(1073, 22); + this.tbRSAModulus.TabIndex = 1; + // + // gbRSAPriv + // + this.gbRSAPriv.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.gbRSAPriv.Controls.Add(this.lbRSAPrivP); + this.gbRSAPriv.Controls.Add(this.lbRSAPrivQ); + this.gbRSAPriv.Controls.Add(this.lbRSAPrivPQ); + this.gbRSAPriv.Controls.Add(this.lbRSAPrivDP1); + this.gbRSAPriv.Controls.Add(this.tbRSAPrivDQ1); + this.gbRSAPriv.Controls.Add(this.tbRSAPrivDP1); + this.gbRSAPriv.Controls.Add(this.tbRSAPrivPQ); + this.gbRSAPriv.Controls.Add(this.tbRSAPrivQ); + this.gbRSAPriv.Controls.Add(this.lbRSAPrivDQ1); + this.gbRSAPriv.Controls.Add(this.lbRSAPrivExp); + this.gbRSAPriv.Controls.Add(this.tbRSAPrivExp); + this.gbRSAPriv.Controls.Add(this.tbRSAPrivP); + this.gbRSAPriv.Controls.Add(this.gpPrivateKeyMode); + this.gbRSAPriv.Location = new System.Drawing.Point(8, 153); + this.gbRSAPriv.Margin = new System.Windows.Forms.Padding(0); + this.gbRSAPriv.Name = "gbRSAPriv"; + this.gbRSAPriv.Size = new System.Drawing.Size(1116, 220); + this.gbRSAPriv.TabIndex = 2; + this.gbRSAPriv.TabStop = false; + // + // lbRSAPrivP + // + this.lbRSAPrivP.AutoSize = true; + this.lbRSAPrivP.Location = new System.Drawing.Point(213, 25); + this.lbRSAPrivP.Name = "lbRSAPrivP"; + this.lbRSAPrivP.Size = new System.Drawing.Size(17, 13); + this.lbRSAPrivP.TabIndex = 1; + this.lbRSAPrivP.Text = "P:"; + // + // lbRSAPrivQ + // + this.lbRSAPrivQ.AutoSize = true; + this.lbRSAPrivQ.Location = new System.Drawing.Point(211, 57); + this.lbRSAPrivQ.Name = "lbRSAPrivQ"; + this.lbRSAPrivQ.Size = new System.Drawing.Size(18, 13); + this.lbRSAPrivQ.TabIndex = 3; + this.lbRSAPrivQ.Text = "Q:"; + // + // lbRSAPrivPQ + // + this.lbRSAPrivPQ.AutoSize = true; + this.lbRSAPrivPQ.Location = new System.Drawing.Point(205, 89); + this.lbRSAPrivPQ.Name = "lbRSAPrivPQ"; + this.lbRSAPrivPQ.Size = new System.Drawing.Size(25, 13); + this.lbRSAPrivPQ.TabIndex = 5; + this.lbRSAPrivPQ.Text = "PQ:"; + // + // lbRSAPrivDP1 + // + this.lbRSAPrivDP1.AutoSize = true; + this.lbRSAPrivDP1.Location = new System.Drawing.Point(198, 121); + this.lbRSAPrivDP1.Name = "lbRSAPrivDP1"; + this.lbRSAPrivDP1.Size = new System.Drawing.Size(31, 13); + this.lbRSAPrivDP1.TabIndex = 7; + this.lbRSAPrivDP1.Text = "DP1:"; + // + // tbRSAPrivDQ1 + // + this.tbRSAPrivDQ1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.tbRSAPrivDQ1.BackColor = System.Drawing.SystemColors.Window; + this.tbRSAPrivDQ1.Font = new System.Drawing.Font("Courier New", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); + this.tbRSAPrivDQ1.Location = new System.Drawing.Point(233, 149); + this.tbRSAPrivDQ1.Margin = new System.Windows.Forms.Padding(0); + this.tbRSAPrivDQ1.Name = "tbRSAPrivDQ1"; + this.tbRSAPrivDQ1.Size = new System.Drawing.Size(870, 22); + this.tbRSAPrivDQ1.TabIndex = 10; + // + // tbRSAPrivDP1 + // + this.tbRSAPrivDP1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.tbRSAPrivDP1.BackColor = System.Drawing.SystemColors.Window; + this.tbRSAPrivDP1.Font = new System.Drawing.Font("Courier New", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); + this.tbRSAPrivDP1.Location = new System.Drawing.Point(233, 117); + this.tbRSAPrivDP1.Margin = new System.Windows.Forms.Padding(10); + this.tbRSAPrivDP1.Name = "tbRSAPrivDP1"; + this.tbRSAPrivDP1.Size = new System.Drawing.Size(870, 22); + this.tbRSAPrivDP1.TabIndex = 8; + // + // tbRSAPrivPQ + // + this.tbRSAPrivPQ.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.tbRSAPrivPQ.BackColor = System.Drawing.SystemColors.Window; + this.tbRSAPrivPQ.Font = new System.Drawing.Font("Courier New", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); + this.tbRSAPrivPQ.Location = new System.Drawing.Point(233, 85); + this.tbRSAPrivPQ.Margin = new System.Windows.Forms.Padding(0); + this.tbRSAPrivPQ.Name = "tbRSAPrivPQ"; + this.tbRSAPrivPQ.Size = new System.Drawing.Size(870, 22); + this.tbRSAPrivPQ.TabIndex = 6; + // + // tbRSAPrivQ + // + this.tbRSAPrivQ.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.tbRSAPrivQ.BackColor = System.Drawing.SystemColors.Window; + this.tbRSAPrivQ.Font = new System.Drawing.Font("Courier New", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); + this.tbRSAPrivQ.Location = new System.Drawing.Point(233, 53); + this.tbRSAPrivQ.Margin = new System.Windows.Forms.Padding(10); + this.tbRSAPrivQ.Name = "tbRSAPrivQ"; + this.tbRSAPrivQ.Size = new System.Drawing.Size(870, 22); + this.tbRSAPrivQ.TabIndex = 4; + // + // lbRSAPrivDQ1 + // + this.lbRSAPrivDQ1.AutoSize = true; + this.lbRSAPrivDQ1.Location = new System.Drawing.Point(198, 153); + this.lbRSAPrivDQ1.Name = "lbRSAPrivDQ1"; + this.lbRSAPrivDQ1.Size = new System.Drawing.Size(32, 13); + this.lbRSAPrivDQ1.TabIndex = 9; + this.lbRSAPrivDQ1.Text = "DQ1:"; + // + // lbRSAPrivExp + // + this.lbRSAPrivExp.AutoSize = true; + this.lbRSAPrivExp.Enabled = false; + this.lbRSAPrivExp.Location = new System.Drawing.Point(7, 187); + this.lbRSAPrivExp.Margin = new System.Windows.Forms.Padding(3); + this.lbRSAPrivExp.Name = "lbRSAPrivExp"; + this.lbRSAPrivExp.Size = new System.Drawing.Size(18, 13); + this.lbRSAPrivExp.TabIndex = 11; + this.lbRSAPrivExp.Text = "D:"; + // + // tbRSAPrivExp + // + this.tbRSAPrivExp.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.tbRSAPrivExp.BackColor = System.Drawing.SystemColors.Window; + this.tbRSAPrivExp.Enabled = false; + this.tbRSAPrivExp.Font = new System.Drawing.Font("Courier New", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); + this.tbRSAPrivExp.Location = new System.Drawing.Point(31, 183); + this.tbRSAPrivExp.Name = "tbRSAPrivExp"; + this.tbRSAPrivExp.Size = new System.Drawing.Size(1072, 22); + this.tbRSAPrivExp.TabIndex = 12; + // + // tbRSAPrivP + // + this.tbRSAPrivP.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.tbRSAPrivP.BackColor = System.Drawing.SystemColors.Window; + this.tbRSAPrivP.Font = new System.Drawing.Font("Courier New", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); + this.tbRSAPrivP.Location = new System.Drawing.Point(233, 21); + this.tbRSAPrivP.Margin = new System.Windows.Forms.Padding(0); + this.tbRSAPrivP.Name = "tbRSAPrivP"; + this.tbRSAPrivP.Size = new System.Drawing.Size(870, 22); + this.tbRSAPrivP.TabIndex = 2; + // + // gpPrivateKeyMode + // + this.gpPrivateKeyMode.Controls.Add(this.rbCRT); + this.gpPrivateKeyMode.Controls.Add(this.rbModExp); + this.gpPrivateKeyMode.Location = new System.Drawing.Point(10, 14); + this.gpPrivateKeyMode.Name = "gpPrivateKeyMode"; + this.gpPrivateKeyMode.Size = new System.Drawing.Size(180, 88); + this.gpPrivateKeyMode.TabIndex = 0; + this.gpPrivateKeyMode.TabStop = false; + this.gpPrivateKeyMode.Text = "Private key mode:"; + // + // rbCRT + // + this.rbCRT.AutoSize = true; + this.rbCRT.Checked = true; + this.rbCRT.Location = new System.Drawing.Point(12, 26); + this.rbCRT.Margin = new System.Windows.Forms.Padding(10); + this.rbCRT.Name = "rbCRT"; + this.rbCRT.Size = new System.Drawing.Size(162, 17); + this.rbCRT.TabIndex = 0; + this.rbCRT.TabStop = true; + this.rbCRT.Text = "Chinese Remainder Theorem"; + this.rbCRT.UseVisualStyleBackColor = true; + this.rbCRT.CheckedChanged += new System.EventHandler(this.rbCRT_CheckedChanged); + // + // rbModExp + // + this.rbModExp.AutoSize = true; + this.rbModExp.Location = new System.Drawing.Point(12, 53); + this.rbModExp.Margin = new System.Windows.Forms.Padding(0); + this.rbModExp.Name = "rbModExp"; + this.rbModExp.Size = new System.Drawing.Size(115, 17); + this.rbModExp.TabIndex = 1; + this.rbModExp.Text = "Modulus/Exponent"; + this.rbModExp.UseVisualStyleBackColor = true; + // + // gbRSACommands + // + this.gbRSACommands.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.gbRSACommands.Controls.Add(this.btnRestoreRSAPriv); + this.gbRSACommands.Controls.Add(this.btnBackupRSAPriv); + this.gbRSACommands.Controls.Add(this.btnSaveRSAPub); + this.gbRSACommands.Controls.Add(this.btnStoreRSAPriv); + this.gbRSACommands.Controls.Add(this.btnMkRSAKey); + this.gbRSACommands.Controls.Add(this.cbRSAKeyLength); + this.gbRSACommands.Controls.Add(this.cbRSAKeyIndex); + this.gbRSACommands.Controls.Add(this.lbRSAKeyLength); + this.gbRSACommands.Controls.Add(this.lbRSAKeyIndex); + this.gbRSACommands.Controls.Add(this.btnRSAImportP12); + this.gbRSACommands.Location = new System.Drawing.Point(8, 0); + this.gbRSACommands.Margin = new System.Windows.Forms.Padding(8); + this.gbRSACommands.Name = "gbRSACommands"; + this.gbRSACommands.Size = new System.Drawing.Size(1116, 74); + this.gbRSACommands.TabIndex = 0; + this.gbRSACommands.TabStop = false; + // + // btnRestoreRSAPriv + // + this.btnRestoreRSAPriv.Location = new System.Drawing.Point(573, 43); + this.btnRestoreRSAPriv.Margin = new System.Windows.Forms.Padding(0); + this.btnRestoreRSAPriv.Name = "btnRestoreRSAPriv"; + this.btnRestoreRSAPriv.Size = new System.Drawing.Size(160, 21); + this.btnRestoreRSAPriv.TabIndex = 9; + this.btnRestoreRSAPriv.Text = "Restore private key from pem"; + this.btnRestoreRSAPriv.UseVisualStyleBackColor = true; + this.btnRestoreRSAPriv.Click += new System.EventHandler(this.btnRestoreRSAPriv_Click); + // + // btnBackupRSAPriv + // + this.btnBackupRSAPriv.BackColor = System.Drawing.SystemColors.Control; + this.btnBackupRSAPriv.Location = new System.Drawing.Point(573, 16); + this.btnBackupRSAPriv.Margin = new System.Windows.Forms.Padding(0); + this.btnBackupRSAPriv.Name = "btnBackupRSAPriv"; + this.btnBackupRSAPriv.Size = new System.Drawing.Size(160, 21); + this.btnBackupRSAPriv.TabIndex = 8; + this.btnBackupRSAPriv.Text = "Backup private key to pem"; + this.btnBackupRSAPriv.UseVisualStyleBackColor = true; + this.btnBackupRSAPriv.Click += new System.EventHandler(this.btnBackupRSAPriv_Click); + // + // btnSaveRSAPub + // + this.btnSaveRSAPub.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.btnSaveRSAPub.Location = new System.Drawing.Point(943, 42); + this.btnSaveRSAPub.Margin = new System.Windows.Forms.Padding(10); + this.btnSaveRSAPub.Name = "btnSaveRSAPub"; + this.btnSaveRSAPub.Size = new System.Drawing.Size(160, 21); + this.btnSaveRSAPub.TabIndex = 7; + this.btnSaveRSAPub.Text = "Save RSA public key to pem"; + this.btnSaveRSAPub.UseVisualStyleBackColor = true; + this.btnSaveRSAPub.Click += new System.EventHandler(this.btnSaveRSAPub_Click); + // + // btnStoreRSAPriv + // + this.btnStoreRSAPriv.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.btnStoreRSAPriv.BackColor = System.Drawing.Color.Azure; + this.btnStoreRSAPriv.Location = new System.Drawing.Point(943, 15); + this.btnStoreRSAPriv.Margin = new System.Windows.Forms.Padding(10); + this.btnStoreRSAPriv.Name = "btnStoreRSAPriv"; + this.btnStoreRSAPriv.Size = new System.Drawing.Size(160, 21); + this.btnStoreRSAPriv.TabIndex = 6; + this.btnStoreRSAPriv.Text = "Store RSA private key to card"; + this.btnStoreRSAPriv.UseVisualStyleBackColor = false; + this.btnStoreRSAPriv.Click += new System.EventHandler(this.btnStoreRSAPriv_Click); + // + // btnMkRSAKey + // + this.btnMkRSAKey.Location = new System.Drawing.Point(403, 15); + this.btnMkRSAKey.Margin = new System.Windows.Forms.Padding(10); + this.btnMkRSAKey.Name = "btnMkRSAKey"; + this.btnMkRSAKey.Size = new System.Drawing.Size(160, 48); + this.btnMkRSAKey.TabIndex = 1; + this.btnMkRSAKey.Text = "Generate RSA key pair (off-card)"; + this.btnMkRSAKey.UseVisualStyleBackColor = true; + this.btnMkRSAKey.Click += new System.EventHandler(this.btnMkRSAKey_Click); + // + // cbRSAKeyLength + // + this.cbRSAKeyLength.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cbRSAKeyLength.FormattingEnabled = true; + this.cbRSAKeyLength.Items.AddRange(new object[] { + "512", + "736", + "768", + "896", + "1024", + "1280", + "1536", + "1984", + "2048", + "4096"}); + this.cbRSAKeyLength.Location = new System.Drawing.Point(99, 16); + this.cbRSAKeyLength.Name = "cbRSAKeyLength"; + this.cbRSAKeyLength.Size = new System.Drawing.Size(121, 21); + this.cbRSAKeyLength.TabIndex = 3; + // + // cbRSAKeyIndex + // + this.cbRSAKeyIndex.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.cbRSAKeyIndex.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cbRSAKeyIndex.FormattingEnabled = true; + this.cbRSAKeyIndex.Items.AddRange(new object[] { + "0", + "1", + "2"}); + this.cbRSAKeyIndex.Location = new System.Drawing.Point(880, 16); + this.cbRSAKeyIndex.Name = "cbRSAKeyIndex"; + this.cbRSAKeyIndex.Size = new System.Drawing.Size(50, 21); + this.cbRSAKeyIndex.TabIndex = 5; + // + // lbRSAKeyLength + // + this.lbRSAKeyLength.AutoSize = true; + this.lbRSAKeyLength.Location = new System.Drawing.Point(8, 19); + this.lbRSAKeyLength.Name = "lbRSAKeyLength"; + this.lbRSAKeyLength.Size = new System.Drawing.Size(85, 13); + this.lbRSAKeyLength.TabIndex = 2; + this.lbRSAKeyLength.Text = "Key length [bits]:"; + // + // lbRSAKeyIndex + // + this.lbRSAKeyIndex.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.lbRSAKeyIndex.AutoSize = true; + this.lbRSAKeyIndex.Location = new System.Drawing.Point(777, 19); + this.lbRSAKeyIndex.Name = "lbRSAKeyIndex"; + this.lbRSAKeyIndex.Size = new System.Drawing.Size(97, 13); + this.lbRSAKeyIndex.TabIndex = 4; + this.lbRSAKeyIndex.Text = "Key index (in card):"; + // + // btnRSAImportP12 + // + this.btnRSAImportP12.Location = new System.Drawing.Point(233, 15); + this.btnRSAImportP12.Margin = new System.Windows.Forms.Padding(10); + this.btnRSAImportP12.Name = "btnRSAImportP12"; + this.btnRSAImportP12.Size = new System.Drawing.Size(160, 48); + this.btnRSAImportP12.TabIndex = 0; + this.btnRSAImportP12.Text = "Import from PKCS#12 file (.p12 ; .pfx)"; + this.btnRSAImportP12.UseVisualStyleBackColor = true; + this.btnRSAImportP12.Click += new System.EventHandler(this.btnRSAImportP12_Click); + // + // tabECKeys + // + this.tabECKeys.BackColor = System.Drawing.SystemColors.Control; + this.tabECKeys.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; + this.tabECKeys.Controls.Add(this.gbECPubKey); + this.tabECKeys.Controls.Add(this.gbECPrivKey); + this.tabECKeys.Controls.Add(this.gbECDomainParameters); + this.tabECKeys.Controls.Add(this.gbECCommands); + this.tabECKeys.Location = new System.Drawing.Point(4, 22); + this.tabECKeys.Name = "tabECKeys"; + this.tabECKeys.Padding = new System.Windows.Forms.Padding(3); + this.tabECKeys.Size = new System.Drawing.Size(1134, 574); + this.tabECKeys.TabIndex = 1; + this.tabECKeys.Text = "EC Keys"; + // + // gbECPubKey + // + this.gbECPubKey.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.gbECPubKey.Controls.Add(this.lbECPubKey); + this.gbECPubKey.Controls.Add(this.tbECPubKey); + this.gbECPubKey.Location = new System.Drawing.Point(8, 443); + this.gbECPubKey.Margin = new System.Windows.Forms.Padding(0); + this.gbECPubKey.Name = "gbECPubKey"; + this.gbECPubKey.Size = new System.Drawing.Size(1116, 104); + this.gbECPubKey.TabIndex = 6; + this.gbECPubKey.TabStop = false; + this.gbECPubKey.Text = "EC public key:"; + // + // lbECPubKey + // + this.lbECPubKey.AutoSize = true; + this.lbECPubKey.Location = new System.Drawing.Point(8, 48); + this.lbECPubKey.Margin = new System.Windows.Forms.Padding(3); + this.lbECPubKey.Name = "lbECPubKey"; + this.lbECPubKey.Size = new System.Drawing.Size(39, 13); + this.lbECPubKey.TabIndex = 17; + this.lbECPubKey.Text = "W(uc):"; + // + // tbECPubKey + // + this.tbECPubKey.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.tbECPubKey.BackColor = System.Drawing.SystemColors.Window; + this.tbECPubKey.Font = new System.Drawing.Font("Courier New", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); + this.tbECPubKey.Location = new System.Drawing.Point(53, 22); + this.tbECPubKey.Multiline = true; + this.tbECPubKey.Name = "tbECPubKey"; + this.tbECPubKey.ScrollBars = System.Windows.Forms.ScrollBars.Horizontal; + this.tbECPubKey.Size = new System.Drawing.Size(1049, 65); + this.tbECPubKey.TabIndex = 18; + // + // gbECPrivKey + // + this.gbECPrivKey.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.gbECPrivKey.Controls.Add(this.lbECPrivKey); + this.gbECPrivKey.Controls.Add(this.tbECPrivKey); + this.gbECPrivKey.Location = new System.Drawing.Point(8, 373); + this.gbECPrivKey.Margin = new System.Windows.Forms.Padding(8); + this.gbECPrivKey.Name = "gbECPrivKey"; + this.gbECPrivKey.Size = new System.Drawing.Size(1116, 62); + this.gbECPrivKey.TabIndex = 5; + this.gbECPrivKey.TabStop = false; + this.gbECPrivKey.Text = "EC private key:"; + // + // lbECPrivKey + // + this.lbECPrivKey.AutoSize = true; + this.lbECPrivKey.Location = new System.Drawing.Point(9, 25); + this.lbECPrivKey.Margin = new System.Windows.Forms.Padding(3); + this.lbECPrivKey.Name = "lbECPrivKey"; + this.lbECPrivKey.Size = new System.Drawing.Size(17, 13); + this.lbECPrivKey.TabIndex = 0; + this.lbECPrivKey.Text = "S:"; + // + // tbECPrivKey + // + this.tbECPrivKey.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.tbECPrivKey.BackColor = System.Drawing.SystemColors.Window; + this.tbECPrivKey.Font = new System.Drawing.Font("Courier New", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); + this.tbECPrivKey.Location = new System.Drawing.Point(32, 21); + this.tbECPrivKey.Name = "tbECPrivKey"; + this.tbECPrivKey.Size = new System.Drawing.Size(1071, 22); + this.tbECPrivKey.TabIndex = 1; + // + // gbECDomainParameters + // + this.gbECDomainParameters.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.gbECDomainParameters.Controls.Add(this.lbECParamK); + this.gbECDomainParameters.Controls.Add(this.tbECParamK); + this.gbECDomainParameters.Controls.Add(this.lbECParamR); + this.gbECDomainParameters.Controls.Add(this.tbECParamR); + this.gbECDomainParameters.Controls.Add(this.lbECParamG); + this.gbECDomainParameters.Controls.Add(this.tbECParamG); + this.gbECDomainParameters.Controls.Add(this.lbECParamB); + this.gbECDomainParameters.Controls.Add(this.tbECParamB); + this.gbECDomainParameters.Controls.Add(this.ltbECParamA); + this.gbECDomainParameters.Controls.Add(this.tbECParamA); + this.gbECDomainParameters.Controls.Add(this.gbECReductionPolynomial); + this.gbECDomainParameters.Controls.Add(this.groupBox1); + this.gbECDomainParameters.Controls.Add(this.lbECParamPrime); + this.gbECDomainParameters.Controls.Add(this.tbECParamPrime); + this.gbECDomainParameters.Location = new System.Drawing.Point(8, 82); + this.gbECDomainParameters.Margin = new System.Windows.Forms.Padding(0); + this.gbECDomainParameters.Name = "gbECDomainParameters"; + this.gbECDomainParameters.Size = new System.Drawing.Size(1116, 283); + this.gbECDomainParameters.TabIndex = 4; + this.gbECDomainParameters.TabStop = false; + this.gbECDomainParameters.Text = "EC domain parameters:"; + // + // lbECParamK + // + this.lbECParamK.AutoSize = true; + this.lbECParamK.Location = new System.Drawing.Point(260, 239); + this.lbECParamK.Margin = new System.Windows.Forms.Padding(3); + this.lbECParamK.Name = "lbECParamK"; + this.lbECParamK.Size = new System.Drawing.Size(17, 13); + this.lbECParamK.TabIndex = 19; + this.lbECParamK.Text = "K:"; + // + // tbECParamK + // + this.tbECParamK.BackColor = System.Drawing.SystemColors.Window; + this.tbECParamK.Font = new System.Drawing.Font("Courier New", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); + this.tbECParamK.Location = new System.Drawing.Point(284, 235); + this.tbECParamK.Margin = new System.Windows.Forms.Padding(0); + this.tbECParamK.Name = "tbECParamK"; + this.tbECParamK.ReadOnly = true; + this.tbECParamK.Size = new System.Drawing.Size(139, 22); + this.tbECParamK.TabIndex = 20; + // + // lbECParamR + // + this.lbECParamR.AutoSize = true; + this.lbECParamR.Location = new System.Drawing.Point(260, 207); + this.lbECParamR.Margin = new System.Windows.Forms.Padding(3); + this.lbECParamR.Name = "lbECParamR"; + this.lbECParamR.Size = new System.Drawing.Size(18, 13); + this.lbECParamR.TabIndex = 17; + this.lbECParamR.Text = "R:"; + // + // tbECParamR + // + this.tbECParamR.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.tbECParamR.BackColor = System.Drawing.SystemColors.Window; + this.tbECParamR.Font = new System.Drawing.Font("Courier New", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); + this.tbECParamR.Location = new System.Drawing.Point(284, 203); + this.tbECParamR.Margin = new System.Windows.Forms.Padding(10); + this.tbECParamR.Name = "tbECParamR"; + this.tbECParamR.ReadOnly = true; + this.tbECParamR.Size = new System.Drawing.Size(819, 22); + this.tbECParamR.TabIndex = 18; + // + // lbECParamG + // + this.lbECParamG.AutoSize = true; + this.lbECParamG.Location = new System.Drawing.Point(242, 152); + this.lbECParamG.Margin = new System.Windows.Forms.Padding(3); + this.lbECParamG.Name = "lbECParamG"; + this.lbECParamG.Size = new System.Drawing.Size(36, 13); + this.lbECParamG.TabIndex = 15; + this.lbECParamG.Text = "G(uc):"; + // + // tbECParamG + // + this.tbECParamG.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.tbECParamG.BackColor = System.Drawing.SystemColors.Window; + this.tbECParamG.Font = new System.Drawing.Font("Courier New", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); + this.tbECParamG.Location = new System.Drawing.Point(284, 126); + this.tbECParamG.Margin = new System.Windows.Forms.Padding(0); + this.tbECParamG.Multiline = true; + this.tbECParamG.Name = "tbECParamG"; + this.tbECParamG.ReadOnly = true; + this.tbECParamG.ScrollBars = System.Windows.Forms.ScrollBars.Horizontal; + this.tbECParamG.Size = new System.Drawing.Size(819, 65); + this.tbECParamG.TabIndex = 16; + // + // lbECParamB + // + this.lbECParamB.AutoSize = true; + this.lbECParamB.Location = new System.Drawing.Point(212, 94); + this.lbECParamB.Margin = new System.Windows.Forms.Padding(3); + this.lbECParamB.Name = "lbECParamB"; + this.lbECParamB.Size = new System.Drawing.Size(16, 13); + this.lbECParamB.TabIndex = 13; + this.lbECParamB.Text = "b:"; + // + // tbECParamB + // + this.tbECParamB.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.tbECParamB.BackColor = System.Drawing.SystemColors.Window; + this.tbECParamB.Font = new System.Drawing.Font("Courier New", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); + this.tbECParamB.Location = new System.Drawing.Point(233, 90); + this.tbECParamB.Margin = new System.Windows.Forms.Padding(14); + this.tbECParamB.Name = "tbECParamB"; + this.tbECParamB.ReadOnly = true; + this.tbECParamB.Size = new System.Drawing.Size(870, 22); + this.tbECParamB.TabIndex = 14; + // + // ltbECParamA + // + this.ltbECParamA.AutoSize = true; + this.ltbECParamA.Location = new System.Drawing.Point(212, 64); + this.ltbECParamA.Margin = new System.Windows.Forms.Padding(3); + this.ltbECParamA.Name = "ltbECParamA"; + this.ltbECParamA.Size = new System.Drawing.Size(16, 13); + this.ltbECParamA.TabIndex = 11; + this.ltbECParamA.Text = "a:"; + // + // tbECParamA + // + this.tbECParamA.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.tbECParamA.BackColor = System.Drawing.SystemColors.Window; + this.tbECParamA.Font = new System.Drawing.Font("Courier New", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); + this.tbECParamA.Location = new System.Drawing.Point(233, 60); + this.tbECParamA.Margin = new System.Windows.Forms.Padding(10); + this.tbECParamA.Name = "tbECParamA"; + this.tbECParamA.ReadOnly = true; + this.tbECParamA.Size = new System.Drawing.Size(870, 22); + this.tbECParamA.TabIndex = 12; + // + // gbECReductionPolynomial + // + this.gbECReductionPolynomial.Controls.Add(this.rtbECReductionPolynomial); + this.gbECReductionPolynomial.Controls.Add(this.lbECParamE3); + this.gbECReductionPolynomial.Controls.Add(this.tbECParamE3); + this.gbECReductionPolynomial.Controls.Add(this.lbECParamE2); + this.gbECReductionPolynomial.Controls.Add(this.tbECParamE2); + this.gbECReductionPolynomial.Controls.Add(this.lbECParamE1); + this.gbECReductionPolynomial.Controls.Add(this.tbECParamE1); + this.gbECReductionPolynomial.Enabled = false; + this.gbECReductionPolynomial.Location = new System.Drawing.Point(11, 120); + this.gbECReductionPolynomial.Margin = new System.Windows.Forms.Padding(8); + this.gbECReductionPolynomial.Name = "gbECReductionPolynomial"; + this.gbECReductionPolynomial.Size = new System.Drawing.Size(220, 151); + this.gbECReductionPolynomial.TabIndex = 10; + this.gbECReductionPolynomial.TabStop = false; + this.gbECReductionPolynomial.Text = "Reduction polynomial:"; + // + // rtbECReductionPolynomial + // + this.rtbECReductionPolynomial.BorderStyle = System.Windows.Forms.BorderStyle.None; + this.rtbECReductionPolynomial.Font = new System.Drawing.Font("Times New Roman", 9.75F, System.Drawing.FontStyle.Italic, System.Drawing.GraphicsUnit.Point, ((byte)(238))); + this.rtbECReductionPolynomial.Location = new System.Drawing.Point(12, 22); + this.rtbECReductionPolynomial.Multiline = false; + this.rtbECReductionPolynomial.Name = "rtbECReductionPolynomial"; + this.rtbECReductionPolynomial.ReadOnly = true; + this.rtbECReductionPolynomial.ScrollBars = System.Windows.Forms.RichTextBoxScrollBars.None; + this.rtbECReductionPolynomial.Size = new System.Drawing.Size(202, 29); + this.rtbECReductionPolynomial.TabIndex = 17; + this.rtbECReductionPolynomial.Text = " f(x) = "; + // + // lbECParamE3 + // + this.lbECParamE3.AutoSize = true; + this.lbECParamE3.Location = new System.Drawing.Point(12, 119); + this.lbECParamE3.Margin = new System.Windows.Forms.Padding(3); + this.lbECParamE3.Name = "lbECParamE3"; + this.lbECParamE3.Size = new System.Drawing.Size(22, 13); + this.lbECParamE3.TabIndex = 15; + this.lbECParamE3.Text = "e3:"; + // + // tbECParamE3 + // + this.tbECParamE3.BackColor = System.Drawing.SystemColors.Window; + this.tbECParamE3.Font = new System.Drawing.Font("Courier New", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); + this.tbECParamE3.Location = new System.Drawing.Point(41, 115); + this.tbECParamE3.Margin = new System.Windows.Forms.Padding(0); + this.tbECParamE3.Name = "tbECParamE3"; + this.tbECParamE3.ReadOnly = true; + this.tbECParamE3.Size = new System.Drawing.Size(139, 22); + this.tbECParamE3.TabIndex = 16; + // + // lbECParamE2 + // + this.lbECParamE2.AutoSize = true; + this.lbECParamE2.Location = new System.Drawing.Point(12, 87); + this.lbECParamE2.Margin = new System.Windows.Forms.Padding(3); + this.lbECParamE2.Name = "lbECParamE2"; + this.lbECParamE2.Size = new System.Drawing.Size(22, 13); + this.lbECParamE2.TabIndex = 13; + this.lbECParamE2.Text = "e2:"; + // + // tbECParamE2 + // + this.tbECParamE2.BackColor = System.Drawing.SystemColors.Window; + this.tbECParamE2.Font = new System.Drawing.Font("Courier New", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); + this.tbECParamE2.Location = new System.Drawing.Point(41, 83); + this.tbECParamE2.Margin = new System.Windows.Forms.Padding(10); + this.tbECParamE2.Name = "tbECParamE2"; + this.tbECParamE2.ReadOnly = true; + this.tbECParamE2.Size = new System.Drawing.Size(139, 22); + this.tbECParamE2.TabIndex = 14; + // + // lbECParamE1 + // + this.lbECParamE1.AutoSize = true; + this.lbECParamE1.Location = new System.Drawing.Point(12, 55); + this.lbECParamE1.Margin = new System.Windows.Forms.Padding(3); + this.lbECParamE1.Name = "lbECParamE1"; + this.lbECParamE1.Size = new System.Drawing.Size(22, 13); + this.lbECParamE1.TabIndex = 11; + this.lbECParamE1.Text = "e1:"; + // + // tbECParamE1 + // + this.tbECParamE1.BackColor = System.Drawing.SystemColors.Window; + this.tbECParamE1.Font = new System.Drawing.Font("Courier New", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); + this.tbECParamE1.Location = new System.Drawing.Point(41, 51); + this.tbECParamE1.Margin = new System.Windows.Forms.Padding(10); + this.tbECParamE1.Name = "tbECParamE1"; + this.tbECParamE1.ReadOnly = true; + this.tbECParamE1.Size = new System.Drawing.Size(139, 22); + this.tbECParamE1.TabIndex = 12; + // + // groupBox1 + // + this.groupBox1.Controls.Add(this.rbECFieldPrime); + this.groupBox1.Controls.Add(this.rbECFieldBinary); + this.groupBox1.Enabled = false; + this.groupBox1.Location = new System.Drawing.Point(11, 24); + this.groupBox1.Margin = new System.Windows.Forms.Padding(0); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.Size = new System.Drawing.Size(180, 88); + this.groupBox1.TabIndex = 2; + this.groupBox1.TabStop = false; + this.groupBox1.Text = "EC over field:"; + // + // rbECFieldPrime + // + this.rbECFieldPrime.AutoSize = true; + this.rbECFieldPrime.Checked = true; + this.rbECFieldPrime.Location = new System.Drawing.Point(12, 26); + this.rbECFieldPrime.Margin = new System.Windows.Forms.Padding(10); + this.rbECFieldPrime.Name = "rbECFieldPrime"; + this.rbECFieldPrime.Size = new System.Drawing.Size(102, 17); + this.rbECFieldPrime.TabIndex = 0; + this.rbECFieldPrime.TabStop = true; + this.rbECFieldPrime.Text = "Prime field { Fp }"; + this.rbECFieldPrime.UseVisualStyleBackColor = true; + this.rbECFieldPrime.CheckedChanged += new System.EventHandler(this.rbECFieldPrime_CheckedChanged); + // + // rbECFieldBinary + // + this.rbECFieldBinary.AutoSize = true; + this.rbECFieldBinary.Location = new System.Drawing.Point(12, 53); + this.rbECFieldBinary.Margin = new System.Windows.Forms.Padding(0); + this.rbECFieldBinary.Name = "rbECFieldBinary"; + this.rbECFieldBinary.Size = new System.Drawing.Size(113, 17); + this.rbECFieldBinary.TabIndex = 1; + this.rbECFieldBinary.Text = "Binary field { F2m }"; + this.rbECFieldBinary.UseVisualStyleBackColor = true; + this.rbECFieldBinary.CheckedChanged += new System.EventHandler(this.rbECFieldBinary_CheckedChanged); + // + // lbECParamPrime + // + this.lbECParamPrime.AutoSize = true; + this.lbECParamPrime.Location = new System.Drawing.Point(212, 34); + this.lbECParamPrime.Margin = new System.Windows.Forms.Padding(3); + this.lbECParamPrime.Name = "lbECParamPrime"; + this.lbECParamPrime.Size = new System.Drawing.Size(16, 13); + this.lbECParamPrime.TabIndex = 0; + this.lbECParamPrime.Text = "p:"; + // + // tbECParamPrime + // + this.tbECParamPrime.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.tbECParamPrime.BackColor = System.Drawing.SystemColors.Window; + this.tbECParamPrime.Font = new System.Drawing.Font("Courier New", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); + this.tbECParamPrime.Location = new System.Drawing.Point(233, 30); + this.tbECParamPrime.Margin = new System.Windows.Forms.Padding(10); + this.tbECParamPrime.Name = "tbECParamPrime"; + this.tbECParamPrime.ReadOnly = true; + this.tbECParamPrime.Size = new System.Drawing.Size(870, 22); + this.tbECParamPrime.TabIndex = 1; + // + // gbECCommands + // + this.gbECCommands.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.gbECCommands.Controls.Add(this.btnRestoreECPriv); + this.gbECCommands.Controls.Add(this.btnBackupECPriv); + this.gbECCommands.Controls.Add(this.cbECName); + this.gbECCommands.Controls.Add(this.lbECName); + this.gbECCommands.Controls.Add(this.btnSaveECPub); + this.gbECCommands.Controls.Add(this.btnStoreECPriv); + this.gbECCommands.Controls.Add(this.btnMkECKey); + this.gbECCommands.Controls.Add(this.cbECKeyLength); + this.gbECCommands.Controls.Add(this.cbECKeyIndex); + this.gbECCommands.Controls.Add(this.lbECKeyLength); + this.gbECCommands.Controls.Add(this.lbECKeyIndex); + this.gbECCommands.Controls.Add(this.btnECImportP12); + this.gbECCommands.Location = new System.Drawing.Point(8, 0); + this.gbECCommands.Margin = new System.Windows.Forms.Padding(8); + this.gbECCommands.Name = "gbECCommands"; + this.gbECCommands.Size = new System.Drawing.Size(1116, 74); + this.gbECCommands.TabIndex = 1; + this.gbECCommands.TabStop = false; + // + // btnRestoreECPriv + // + this.btnRestoreECPriv.Location = new System.Drawing.Point(573, 43); + this.btnRestoreECPriv.Margin = new System.Windows.Forms.Padding(0); + this.btnRestoreECPriv.Name = "btnRestoreECPriv"; + this.btnRestoreECPriv.Size = new System.Drawing.Size(160, 21); + this.btnRestoreECPriv.TabIndex = 11; + this.btnRestoreECPriv.Text = "Restore private key from pem"; + this.btnRestoreECPriv.UseVisualStyleBackColor = true; + this.btnRestoreECPriv.Click += new System.EventHandler(this.btnRestoreECPriv_Click); + // + // btnBackupECPriv + // + this.btnBackupECPriv.BackColor = System.Drawing.SystemColors.ControlLight; + this.btnBackupECPriv.Location = new System.Drawing.Point(573, 16); + this.btnBackupECPriv.Margin = new System.Windows.Forms.Padding(0); + this.btnBackupECPriv.Name = "btnBackupECPriv"; + this.btnBackupECPriv.Size = new System.Drawing.Size(160, 21); + this.btnBackupECPriv.TabIndex = 10; + this.btnBackupECPriv.Text = "Backup private key to pem"; + this.btnBackupECPriv.UseVisualStyleBackColor = true; + this.btnBackupECPriv.Click += new System.EventHandler(this.btnBackupECPriv_Click); + // + // cbECName + // + this.cbECName.DropDownHeight = 432; + this.cbECName.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cbECName.FormattingEnabled = true; + this.cbECName.IntegralHeight = false; + this.cbECName.Items.AddRange(new object[] { + "secp112r1", + "secp112r2", + "secp128r1", + "secp128r2", + "secp160k1", + "secp160r1", + "secp160r2", + "secp192k1", + "secp192r1", + "secp224k1", + "secp224r1", + "secp256k1", + "secp256r1", + "secp384r1", + "secp521r1", + "sect113r1", + "sect113r2", + "sect131r1", + "sect131r2", + "sect163k1", + "sect163r1", + "sect163r2", + "sect193r1", + "sect193r2", + "sect233k1", + "sect233r1", + "sect239k1", + "sect283k1", + "sect283r1", + "sect409k1", + "sect409r1", + "sect571k1", + "sect571r1"}); + this.cbECName.Location = new System.Drawing.Point(99, 16); + this.cbECName.Margin = new System.Windows.Forms.Padding(2); + this.cbECName.Name = "cbECName"; + this.cbECName.Size = new System.Drawing.Size(122, 21); + this.cbECName.TabIndex = 9; + this.cbECName.SelectedIndexChanged += new System.EventHandler(this.cbECName_SelectedIndexChanged); + // + // lbECName + // + this.lbECName.AutoSize = true; + this.lbECName.Location = new System.Drawing.Point(8, 19); + this.lbECName.Name = "lbECName"; + this.lbECName.Size = new System.Drawing.Size(67, 13); + this.lbECName.TabIndex = 8; + this.lbECName.Text = "Curve name:"; + // + // btnSaveECPub + // + this.btnSaveECPub.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.btnSaveECPub.Location = new System.Drawing.Point(943, 42); + this.btnSaveECPub.Margin = new System.Windows.Forms.Padding(10); + this.btnSaveECPub.Name = "btnSaveECPub"; + this.btnSaveECPub.Size = new System.Drawing.Size(160, 21); + this.btnSaveECPub.TabIndex = 7; + this.btnSaveECPub.Text = "Save EC public key to pem"; + this.btnSaveECPub.UseVisualStyleBackColor = true; + this.btnSaveECPub.Click += new System.EventHandler(this.btnSaveECPub_Click); + // + // btnStoreECPriv + // + this.btnStoreECPriv.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.btnStoreECPriv.BackColor = System.Drawing.Color.Azure; + this.btnStoreECPriv.Location = new System.Drawing.Point(943, 15); + this.btnStoreECPriv.Margin = new System.Windows.Forms.Padding(10); + this.btnStoreECPriv.Name = "btnStoreECPriv"; + this.btnStoreECPriv.Size = new System.Drawing.Size(160, 21); + this.btnStoreECPriv.TabIndex = 6; + this.btnStoreECPriv.Text = "Store EC private key to card"; + this.btnStoreECPriv.UseVisualStyleBackColor = false; + this.btnStoreECPriv.Click += new System.EventHandler(this.btnStoreECPriv_Click); + // + // btnMkECKey + // + this.btnMkECKey.Location = new System.Drawing.Point(403, 15); + this.btnMkECKey.Margin = new System.Windows.Forms.Padding(10); + this.btnMkECKey.Name = "btnMkECKey"; + this.btnMkECKey.Size = new System.Drawing.Size(160, 48); + this.btnMkECKey.TabIndex = 1; + this.btnMkECKey.Text = "Generate EC key pair (off-card)"; + this.btnMkECKey.UseVisualStyleBackColor = true; + this.btnMkECKey.Click += new System.EventHandler(this.btnMkECKey_Click); + // + // cbECKeyLength + // + this.cbECKeyLength.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cbECKeyLength.FormattingEnabled = true; + this.cbECKeyLength.Items.AddRange(new object[] { + "112", + "113", + "128", + "131", + "160", + "163", + "192", + "193", + "224", + "233", + "239", + "256", + "283", + "384", + "409", + "521", + "571"}); + this.cbECKeyLength.Location = new System.Drawing.Point(99, 42); + this.cbECKeyLength.Name = "cbECKeyLength"; + this.cbECKeyLength.Size = new System.Drawing.Size(122, 21); + this.cbECKeyLength.TabIndex = 3; + this.cbECKeyLength.SelectedIndexChanged += new System.EventHandler(this.cbECKeyLength_SelectedIndexChanged); + // + // cbECKeyIndex + // + this.cbECKeyIndex.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.cbECKeyIndex.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cbECKeyIndex.FormattingEnabled = true; + this.cbECKeyIndex.Items.AddRange(new object[] { + "0", + "1", + "2"}); + this.cbECKeyIndex.Location = new System.Drawing.Point(880, 16); + this.cbECKeyIndex.Name = "cbECKeyIndex"; + this.cbECKeyIndex.Size = new System.Drawing.Size(50, 21); + this.cbECKeyIndex.TabIndex = 5; + // + // lbECKeyLength + // + this.lbECKeyLength.AutoSize = true; + this.lbECKeyLength.Location = new System.Drawing.Point(8, 45); + this.lbECKeyLength.Name = "lbECKeyLength"; + this.lbECKeyLength.Size = new System.Drawing.Size(85, 13); + this.lbECKeyLength.TabIndex = 2; + this.lbECKeyLength.Text = "Key length [bits]:"; + // + // lbECKeyIndex + // + this.lbECKeyIndex.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.lbECKeyIndex.AutoSize = true; + this.lbECKeyIndex.Location = new System.Drawing.Point(777, 19); + this.lbECKeyIndex.Name = "lbECKeyIndex"; + this.lbECKeyIndex.Size = new System.Drawing.Size(97, 13); + this.lbECKeyIndex.TabIndex = 4; + this.lbECKeyIndex.Text = "Key index (in card):"; + // + // btnECImportP12 + // + this.btnECImportP12.Location = new System.Drawing.Point(233, 15); + this.btnECImportP12.Margin = new System.Windows.Forms.Padding(10); + this.btnECImportP12.Name = "btnECImportP12"; + this.btnECImportP12.Size = new System.Drawing.Size(160, 48); + this.btnECImportP12.TabIndex = 0; + this.btnECImportP12.Text = "Import from PKCS#12 file (.p12 ; .pfx)"; + this.btnECImportP12.UseVisualStyleBackColor = true; + this.btnECImportP12.Click += new System.EventHandler(this.btnECImportP12_Click); + // + // tabSignature + // + this.tabSignature.BackColor = System.Drawing.SystemColors.Control; + this.tabSignature.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; + this.tabSignature.Controls.Add(this.gbSignatureHash); + this.tabSignature.Controls.Add(this.gbSignature); + this.tabSignature.Controls.Add(this.gbMessage); + this.tabSignature.Controls.Add(this.gbSignatureParameters); + this.tabSignature.Location = new System.Drawing.Point(4, 22); + this.tabSignature.Name = "tabSignature"; + this.tabSignature.Size = new System.Drawing.Size(1134, 574); + this.tabSignature.TabIndex = 3; + this.tabSignature.Text = "Signature"; + // + // gbSignatureHash + // + this.gbSignatureHash.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.gbSignatureHash.Controls.Add(this.btnHashStoreToBin); + this.gbSignatureHash.Controls.Add(this.btnHash); + this.gbSignatureHash.Controls.Add(this.cbHashAlg); + this.gbSignatureHash.Controls.Add(this.lbHashAlg); + this.gbSignatureHash.Controls.Add(this.rbHashBase64); + this.gbSignatureHash.Controls.Add(this.rbHashHex); + this.gbSignatureHash.Controls.Add(this.lbHash); + this.gbSignatureHash.Controls.Add(this.tbHash); + this.gbSignatureHash.Location = new System.Drawing.Point(8, 361); + this.gbSignatureHash.Margin = new System.Windows.Forms.Padding(0); + this.gbSignatureHash.Name = "gbSignatureHash"; + this.gbSignatureHash.Size = new System.Drawing.Size(1116, 85); + this.gbSignatureHash.TabIndex = 25; + this.gbSignatureHash.TabStop = false; + this.gbSignatureHash.Text = "Signature Hash (optional):"; + // + // btnHashStoreToBin + // + this.btnHashStoreToBin.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.btnHashStoreToBin.Enabled = false; + this.btnHashStoreToBin.Location = new System.Drawing.Point(942, 18); + this.btnHashStoreToBin.Margin = new System.Windows.Forms.Padding(0); + this.btnHashStoreToBin.Name = "btnHashStoreToBin"; + this.btnHashStoreToBin.Size = new System.Drawing.Size(160, 21); + this.btnHashStoreToBin.TabIndex = 27; + this.btnHashStoreToBin.Text = "Save hash to file"; + this.btnHashStoreToBin.UseVisualStyleBackColor = true; + this.btnHashStoreToBin.Click += new System.EventHandler(this.btnHashStoreToBin_Click); + // + // btnHash + // + this.btnHash.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.btnHash.BackColor = System.Drawing.Color.LightBlue; + this.btnHash.Location = new System.Drawing.Point(772, 19); + this.btnHash.Margin = new System.Windows.Forms.Padding(0); + this.btnHash.Name = "btnHash"; + this.btnHash.Size = new System.Drawing.Size(160, 21); + this.btnHash.TabIndex = 26; + this.btnHash.Text = "Calculate hash (optional)"; + this.btnHash.UseVisualStyleBackColor = false; + this.btnHash.Click += new System.EventHandler(this.btnHash_Click); + // + // cbHashAlg + // + this.cbHashAlg.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cbHashAlg.FormattingEnabled = true; + this.cbHashAlg.Items.AddRange(new object[] { + "MD5", + "SHA-1", + "SHA-256", + "SHA-384", + "SHA-512"}); + this.cbHashAlg.Location = new System.Drawing.Point(301, 19); + this.cbHashAlg.Name = "cbHashAlg"; + this.cbHashAlg.Size = new System.Drawing.Size(80, 21); + this.cbHashAlg.TabIndex = 24; + this.cbHashAlg.SelectedIndexChanged += new System.EventHandler(this.cbHashAlg_SelectedIndexChanged); + // + // lbHashAlg + // + this.lbHashAlg.AutoSize = true; + this.lbHashAlg.Location = new System.Drawing.Point(215, 22); + this.lbHashAlg.Name = "lbHashAlg"; + this.lbHashAlg.Size = new System.Drawing.Size(80, 13); + this.lbHashAlg.TabIndex = 23; + this.lbHashAlg.Text = "Hash algorithm:"; + // + // rbHashBase64 + // + this.rbHashBase64.AutoSize = true; + this.rbHashBase64.Location = new System.Drawing.Point(122, 24); + this.rbHashBase64.Name = "rbHashBase64"; + this.rbHashBase64.Size = new System.Drawing.Size(61, 17); + this.rbHashBase64.TabIndex = 22; + this.rbHashBase64.TabStop = true; + this.rbHashBase64.Text = "Base64"; + this.rbHashBase64.UseVisualStyleBackColor = true; + this.rbHashBase64.Click += new System.EventHandler(this.tbHashRadixChanged); + // + // rbHashHex + // + this.rbHashHex.AutoSize = true; + this.rbHashHex.Checked = true; + this.rbHashHex.Location = new System.Drawing.Point(69, 24); + this.rbHashHex.Name = "rbHashHex"; + this.rbHashHex.Size = new System.Drawing.Size(47, 17); + this.rbHashHex.TabIndex = 21; + this.rbHashHex.TabStop = true; + this.rbHashHex.Text = "HEX"; + this.rbHashHex.UseVisualStyleBackColor = true; + this.rbHashHex.Click += new System.EventHandler(this.tbHashRadixChanged); + // + // lbHash + // + this.lbHash.AutoSize = true; + this.lbHash.Location = new System.Drawing.Point(8, 51); + this.lbHash.Margin = new System.Windows.Forms.Padding(3); + this.lbHash.Name = "lbHash"; + this.lbHash.Size = new System.Drawing.Size(35, 13); + this.lbHash.TabIndex = 17; + this.lbHash.Text = "Hash:"; + // + // tbHash + // + this.tbHash.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.tbHash.BackColor = System.Drawing.SystemColors.Window; + this.tbHash.Font = new System.Drawing.Font("Courier New", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); + this.tbHash.Location = new System.Drawing.Point(69, 47); + this.tbHash.Name = "tbHash"; + this.tbHash.ReadOnly = true; + this.tbHash.ScrollBars = System.Windows.Forms.ScrollBars.Horizontal; + this.tbHash.Size = new System.Drawing.Size(1033, 22); + this.tbHash.TabIndex = 18; + // + // gbSignature + // + this.gbSignature.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.gbSignature.Controls.Add(this.pbSigning); + this.gbSignature.Controls.Add(this.btnSignature); + this.gbSignature.Controls.Add(this.btnSignatureStoreToBin); + this.gbSignature.Controls.Add(this.rbSignatureBase64); + this.gbSignature.Controls.Add(this.rbSignatureHex); + this.gbSignature.Controls.Add(this.lbSignature); + this.gbSignature.Controls.Add(this.tbSignature); + this.gbSignature.Location = new System.Drawing.Point(8, 217); + this.gbSignature.Margin = new System.Windows.Forms.Padding(8); + this.gbSignature.Name = "gbSignature"; + this.gbSignature.Size = new System.Drawing.Size(1116, 136); + this.gbSignature.TabIndex = 24; + this.gbSignature.TabStop = false; + this.gbSignature.Text = "Signature:"; + // + // pbSigning + // + this.pbSigning.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.pbSigning.Location = new System.Drawing.Point(69, 118); + this.pbSigning.Name = "pbSigning"; + this.pbSigning.Size = new System.Drawing.Size(1033, 10); + this.pbSigning.Step = 1; + this.pbSigning.Style = System.Windows.Forms.ProgressBarStyle.Continuous; + this.pbSigning.TabIndex = 32; + // + // btnSignature + // + this.btnSignature.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.btnSignature.BackColor = System.Drawing.Color.Azure; + this.btnSignature.Location = new System.Drawing.Point(772, 19); + this.btnSignature.Margin = new System.Windows.Forms.Padding(0); + this.btnSignature.Name = "btnSignature"; + this.btnSignature.Size = new System.Drawing.Size(160, 21); + this.btnSignature.TabIndex = 28; + this.btnSignature.Text = "Get signature"; + this.btnSignature.UseVisualStyleBackColor = false; + this.btnSignature.Click += new System.EventHandler(this.btnSignature_Click); + // + // btnSignatureStoreToBin + // + this.btnSignatureStoreToBin.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.btnSignatureStoreToBin.Enabled = false; + this.btnSignatureStoreToBin.Location = new System.Drawing.Point(942, 19); + this.btnSignatureStoreToBin.Margin = new System.Windows.Forms.Padding(10); + this.btnSignatureStoreToBin.Name = "btnSignatureStoreToBin"; + this.btnSignatureStoreToBin.Size = new System.Drawing.Size(160, 21); + this.btnSignatureStoreToBin.TabIndex = 27; + this.btnSignatureStoreToBin.Text = "Save signature to binary file"; + this.btnSignatureStoreToBin.UseVisualStyleBackColor = true; + this.btnSignatureStoreToBin.Click += new System.EventHandler(this.btnSignatureStoreToBin_Click); + // + // rbSignatureBase64 + // + this.rbSignatureBase64.AutoSize = true; + this.rbSignatureBase64.Location = new System.Drawing.Point(122, 24); + this.rbSignatureBase64.Name = "rbSignatureBase64"; + this.rbSignatureBase64.Size = new System.Drawing.Size(61, 17); + this.rbSignatureBase64.TabIndex = 22; + this.rbSignatureBase64.TabStop = true; + this.rbSignatureBase64.Text = "Base64"; + this.rbSignatureBase64.UseVisualStyleBackColor = true; + this.rbSignatureBase64.Click += new System.EventHandler(this.tbSignatureRadixChanged); + // + // rbSignatureHex + // + this.rbSignatureHex.AutoSize = true; + this.rbSignatureHex.Checked = true; + this.rbSignatureHex.Location = new System.Drawing.Point(69, 24); + this.rbSignatureHex.Name = "rbSignatureHex"; + this.rbSignatureHex.Size = new System.Drawing.Size(47, 17); + this.rbSignatureHex.TabIndex = 21; + this.rbSignatureHex.TabStop = true; + this.rbSignatureHex.Text = "HEX"; + this.rbSignatureHex.UseVisualStyleBackColor = true; + this.rbSignatureHex.Click += new System.EventHandler(this.tbSignatureRadixChanged); + // + // lbSignature + // + this.lbSignature.AutoSize = true; + this.lbSignature.Location = new System.Drawing.Point(8, 73); + this.lbSignature.Margin = new System.Windows.Forms.Padding(3); + this.lbSignature.Name = "lbSignature"; + this.lbSignature.Size = new System.Drawing.Size(55, 13); + this.lbSignature.TabIndex = 17; + this.lbSignature.Text = "Signature:"; + // + // tbSignature + // + this.tbSignature.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.tbSignature.BackColor = System.Drawing.SystemColors.Window; + this.tbSignature.Font = new System.Drawing.Font("Courier New", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); + this.tbSignature.Location = new System.Drawing.Point(69, 47); + this.tbSignature.Multiline = true; + this.tbSignature.Name = "tbSignature"; + this.tbSignature.ReadOnly = true; + this.tbSignature.ScrollBars = System.Windows.Forms.ScrollBars.Horizontal; + this.tbSignature.Size = new System.Drawing.Size(1033, 65); + this.tbSignature.TabIndex = 18; + // + // gbMessage + // + this.gbMessage.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.gbMessage.Controls.Add(this.btnSaveMessageToBin); + this.gbMessage.Controls.Add(this.rbMessageFromFile); + this.gbMessage.Controls.Add(this.rbMessageBase64); + this.gbMessage.Controls.Add(this.rbMessageHex); + this.gbMessage.Controls.Add(this.rbMessageAscii); + this.gbMessage.Controls.Add(this.lbMessage); + this.gbMessage.Controls.Add(this.tbMessage); + this.gbMessage.Location = new System.Drawing.Point(8, 82); + this.gbMessage.Margin = new System.Windows.Forms.Padding(0); + this.gbMessage.Name = "gbMessage"; + this.gbMessage.Size = new System.Drawing.Size(1116, 127); + this.gbMessage.TabIndex = 7; + this.gbMessage.TabStop = false; + this.gbMessage.Text = "Plain text (input message to sign):"; + // + // btnSaveMessageToBin + // + this.btnSaveMessageToBin.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.btnSaveMessageToBin.Location = new System.Drawing.Point(942, 20); + this.btnSaveMessageToBin.Margin = new System.Windows.Forms.Padding(10); + this.btnSaveMessageToBin.Name = "btnSaveMessageToBin"; + this.btnSaveMessageToBin.Size = new System.Drawing.Size(160, 21); + this.btnSaveMessageToBin.TabIndex = 30; + this.btnSaveMessageToBin.Text = "Save message to binary file"; + this.btnSaveMessageToBin.UseVisualStyleBackColor = true; + this.btnSaveMessageToBin.Click += new System.EventHandler(this.btnSaveMessageToBin_Click); + // + // rbMessageFromFile + // + this.rbMessageFromFile.AutoSize = true; + this.rbMessageFromFile.Location = new System.Drawing.Point(211, 24); + this.rbMessageFromFile.Name = "rbMessageFromFile"; + this.rbMessageFromFile.Size = new System.Drawing.Size(344, 17); + this.rbMessageFromFile.TabIndex = 23; + this.rbMessageFromFile.TabStop = true; + this.rbMessageFromFile.Text = "Set file to sign (ATTENTION: it will remove any message text below)"; + this.rbMessageFromFile.UseVisualStyleBackColor = true; + this.rbMessageFromFile.Click += new System.EventHandler(this.tbMessageRadixChanged); + // + // rbMessageBase64 + // + this.rbMessageBase64.AutoSize = true; + this.rbMessageBase64.Location = new System.Drawing.Point(86, 24); + this.rbMessageBase64.Name = "rbMessageBase64"; + this.rbMessageBase64.Size = new System.Drawing.Size(61, 17); + this.rbMessageBase64.TabIndex = 22; + this.rbMessageBase64.TabStop = true; + this.rbMessageBase64.Text = "Base64"; + this.rbMessageBase64.UseVisualStyleBackColor = true; + this.rbMessageBase64.Click += new System.EventHandler(this.tbMessageRadixChanged); + // + // rbMessageHex + // + this.rbMessageHex.AutoSize = true; + this.rbMessageHex.Checked = true; + this.rbMessageHex.Location = new System.Drawing.Point(33, 24); + this.rbMessageHex.Name = "rbMessageHex"; + this.rbMessageHex.Size = new System.Drawing.Size(47, 17); + this.rbMessageHex.TabIndex = 21; + this.rbMessageHex.TabStop = true; + this.rbMessageHex.Text = "HEX"; + this.rbMessageHex.UseVisualStyleBackColor = true; + this.rbMessageHex.Click += new System.EventHandler(this.tbMessageRadixChanged); + // + // rbMessageAscii + // + this.rbMessageAscii.AutoSize = true; + this.rbMessageAscii.Location = new System.Drawing.Point(153, 24); + this.rbMessageAscii.Name = "rbMessageAscii"; + this.rbMessageAscii.Size = new System.Drawing.Size(52, 17); + this.rbMessageAscii.TabIndex = 20; + this.rbMessageAscii.Text = "ASCII"; + this.rbMessageAscii.UseVisualStyleBackColor = true; + this.rbMessageAscii.Click += new System.EventHandler(this.tbMessageRadixChanged); + // + // lbMessage + // + this.lbMessage.AutoSize = true; + this.lbMessage.Location = new System.Drawing.Point(8, 73); + this.lbMessage.Margin = new System.Windows.Forms.Padding(3); + this.lbMessage.Name = "lbMessage"; + this.lbMessage.Size = new System.Drawing.Size(19, 13); + this.lbMessage.TabIndex = 17; + this.lbMessage.Text = "M:"; + // + // tbMessage + // + this.tbMessage.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.tbMessage.BackColor = System.Drawing.SystemColors.Window; + this.tbMessage.Font = new System.Drawing.Font("Courier New", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); + this.tbMessage.Location = new System.Drawing.Point(33, 47); + this.tbMessage.Multiline = true; + this.tbMessage.Name = "tbMessage"; + this.tbMessage.ScrollBars = System.Windows.Forms.ScrollBars.Horizontal; + this.tbMessage.Size = new System.Drawing.Size(1069, 65); + this.tbMessage.TabIndex = 18; + // + // gbSignatureParameters + // + this.gbSignatureParameters.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.gbSignatureParameters.Controls.Add(this.cbCipher); + this.gbSignatureParameters.Controls.Add(this.lbCipher); + this.gbSignatureParameters.Controls.Add(this.cbDigest); + this.gbSignatureParameters.Controls.Add(this.lbDigest); + this.gbSignatureParameters.Controls.Add(this.cbSignatureKeyIndex); + this.gbSignatureParameters.Controls.Add(this.lbSignatureKeyIndex); + this.gbSignatureParameters.Location = new System.Drawing.Point(8, 0); + this.gbSignatureParameters.Margin = new System.Windows.Forms.Padding(8); + this.gbSignatureParameters.Name = "gbSignatureParameters"; + this.gbSignatureParameters.Size = new System.Drawing.Size(1116, 74); + this.gbSignatureParameters.TabIndex = 2; + this.gbSignatureParameters.TabStop = false; + // + // cbCipher + // + this.cbCipher.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cbCipher.FormattingEnabled = true; + this.cbCipher.Items.AddRange(new object[] { + "RSA", + "ECDSA"}); + this.cbCipher.Location = new System.Drawing.Point(99, 43); + this.cbCipher.Name = "cbCipher"; + this.cbCipher.Size = new System.Drawing.Size(124, 21); + this.cbCipher.TabIndex = 9; + // + // lbCipher + // + this.lbCipher.AutoSize = true; + this.lbCipher.Location = new System.Drawing.Point(8, 46); + this.lbCipher.Name = "lbCipher"; + this.lbCipher.Size = new System.Drawing.Size(85, 13); + this.lbCipher.TabIndex = 8; + this.lbCipher.Text = "Cipher algorithm:"; + // + // cbDigest + // + this.cbDigest.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cbDigest.FormattingEnabled = true; + this.cbDigest.Items.AddRange(new object[] { + "SHA-1", + "SHA-224", + "SHA-256", + "SHA-384", + "SHA-512"}); + this.cbDigest.Location = new System.Drawing.Point(143, 16); + this.cbDigest.Name = "cbDigest"; + this.cbDigest.Size = new System.Drawing.Size(80, 21); + this.cbDigest.TabIndex = 9; + // + // lbDigest + // + this.lbDigest.AutoSize = true; + this.lbDigest.Location = new System.Drawing.Point(8, 19); + this.lbDigest.Name = "lbDigest"; + this.lbDigest.Size = new System.Drawing.Size(129, 13); + this.lbDigest.TabIndex = 8; + this.lbDigest.Text = "Message digest algorithm:"; + // + // cbSignatureKeyIndex + // + this.cbSignatureKeyIndex.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cbSignatureKeyIndex.FormattingEnabled = true; + this.cbSignatureKeyIndex.Items.AddRange(new object[] { + "0", + "1", + "2"}); + this.cbSignatureKeyIndex.Location = new System.Drawing.Point(351, 16); + this.cbSignatureKeyIndex.Name = "cbSignatureKeyIndex"; + this.cbSignatureKeyIndex.Size = new System.Drawing.Size(50, 21); + this.cbSignatureKeyIndex.TabIndex = 5; + // + // lbSignatureKeyIndex + // + this.lbSignatureKeyIndex.AutoSize = true; + this.lbSignatureKeyIndex.Location = new System.Drawing.Point(248, 19); + this.lbSignatureKeyIndex.Name = "lbSignatureKeyIndex"; + this.lbSignatureKeyIndex.Size = new System.Drawing.Size(97, 13); + this.lbSignatureKeyIndex.TabIndex = 4; + this.lbSignatureKeyIndex.Text = "Key index (in card):"; + // + // llbDLogicURL + // + this.llbDLogicURL.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.llbDLogicURL.Location = new System.Drawing.Point(690, 7); + this.llbDLogicURL.Name = "llbDLogicURL"; + this.llbDLogicURL.Size = new System.Drawing.Size(460, 24); + this.llbDLogicURL.TabIndex = 1; + this.llbDLogicURL.TabStop = true; + this.llbDLogicURL.Text = "http://www.d-logic.net/nfc-rfid-reader-sdk/"; + this.llbDLogicURL.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + this.llbDLogicURL.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.llbDLogicURL_LinkClicked); + // + // frmMain + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(1164, 623); + this.Controls.Add(this.llbDLogicURL); + this.Controls.Add(this.tabControl); + this.MinimumSize = new System.Drawing.Size(1180, 646); + this.Name = "frmMain"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; + this.Text = "uFR Signer"; + this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.frmMain_FormClosed); + this.Load += new System.EventHandler(this.frmMain_Load); + this.tabControl.ResumeLayout(false); + this.tabRSAKeys.ResumeLayout(false); + this.gbRSAPub.ResumeLayout(false); + this.gbRSAPub.PerformLayout(); + this.gbRSAModulus.ResumeLayout(false); + this.gbRSAModulus.PerformLayout(); + this.gbRSAPriv.ResumeLayout(false); + this.gbRSAPriv.PerformLayout(); + this.gpPrivateKeyMode.ResumeLayout(false); + this.gpPrivateKeyMode.PerformLayout(); + this.gbRSACommands.ResumeLayout(false); + this.gbRSACommands.PerformLayout(); + this.tabECKeys.ResumeLayout(false); + this.gbECPubKey.ResumeLayout(false); + this.gbECPubKey.PerformLayout(); + this.gbECPrivKey.ResumeLayout(false); + this.gbECPrivKey.PerformLayout(); + this.gbECDomainParameters.ResumeLayout(false); + this.gbECDomainParameters.PerformLayout(); + this.gbECReductionPolynomial.ResumeLayout(false); + this.gbECReductionPolynomial.PerformLayout(); + this.groupBox1.ResumeLayout(false); + this.groupBox1.PerformLayout(); + this.gbECCommands.ResumeLayout(false); + this.gbECCommands.PerformLayout(); + this.tabSignature.ResumeLayout(false); + this.gbSignatureHash.ResumeLayout(false); + this.gbSignatureHash.PerformLayout(); + this.gbSignature.ResumeLayout(false); + this.gbSignature.PerformLayout(); + this.gbMessage.ResumeLayout(false); + this.gbMessage.PerformLayout(); + this.gbSignatureParameters.ResumeLayout(false); + this.gbSignatureParameters.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + private System.Windows.Forms.TabControl tabControl; + private System.Windows.Forms.TabPage tabRSAKeys; + private System.Windows.Forms.TabPage tabECKeys; + private System.Windows.Forms.TabPage tabSignature; + private System.Windows.Forms.Label lbRSAKeyLength; + private System.Windows.Forms.LinkLabel llbDLogicURL; + private System.Windows.Forms.ComboBox cbRSAKeyLength; + private System.Windows.Forms.ComboBox cbRSAKeyIndex; + private System.Windows.Forms.Label lbRSAKeyIndex; + private System.Windows.Forms.GroupBox gbRSACommands; + private System.Windows.Forms.Button btnStoreRSAPriv; + private System.Windows.Forms.Button btnMkRSAKey; + private System.Windows.Forms.Button btnRSAImportP12; + private System.Windows.Forms.Button btnSaveRSAPub; + private System.Windows.Forms.GroupBox gbRSAPriv; + private System.Windows.Forms.TextBox tbRSAPrivPQ; + private System.Windows.Forms.TextBox tbRSAPrivQ; + private System.Windows.Forms.Label lbRSAPrivDQ1; + private System.Windows.Forms.Label lbRSAPrivExp; + private System.Windows.Forms.TextBox tbRSAPrivExp; + private System.Windows.Forms.TextBox tbRSAPrivP; + private System.Windows.Forms.GroupBox gpPrivateKeyMode; + private System.Windows.Forms.RadioButton rbCRT; + private System.Windows.Forms.RadioButton rbModExp; + private System.Windows.Forms.Label lbRSAPrivP; + private System.Windows.Forms.Label lbRSAPrivQ; + private System.Windows.Forms.Label lbRSAPrivPQ; + private System.Windows.Forms.Label lbRSAPrivDP1; + private System.Windows.Forms.TextBox tbRSAPrivDQ1; + private System.Windows.Forms.TextBox tbRSAPrivDP1; + private System.Windows.Forms.GroupBox gbRSAModulus; + private System.Windows.Forms.Label lbRSAModulus; + private System.Windows.Forms.TextBox tbRSAModulus; + private System.Windows.Forms.GroupBox gbRSAPub; + private System.Windows.Forms.Label lbRSAPubExp; + private System.Windows.Forms.TextBox tbRSAPubExp; + private System.Windows.Forms.GroupBox gbECCommands; + private System.Windows.Forms.Button btnSaveECPub; + private System.Windows.Forms.Button btnStoreECPriv; + private System.Windows.Forms.Button btnMkECKey; + private System.Windows.Forms.ComboBox cbECKeyLength; + private System.Windows.Forms.ComboBox cbECKeyIndex; + private System.Windows.Forms.Label lbECKeyLength; + private System.Windows.Forms.Label lbECKeyIndex; + private System.Windows.Forms.Button btnECImportP12; + private System.Windows.Forms.ComboBox cbECName; + private System.Windows.Forms.Label lbECName; + private System.Windows.Forms.GroupBox gbECDomainParameters; + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.RadioButton rbECFieldPrime; + private System.Windows.Forms.RadioButton rbECFieldBinary; + private System.Windows.Forms.Label lbECParamPrime; + private System.Windows.Forms.TextBox tbECParamPrime; + private System.Windows.Forms.GroupBox gbECReductionPolynomial; + private System.Windows.Forms.RichTextBox rtbECReductionPolynomial; + private System.Windows.Forms.Label lbECParamE3; + private System.Windows.Forms.TextBox tbECParamE3; + private System.Windows.Forms.Label lbECParamE2; + private System.Windows.Forms.TextBox tbECParamE2; + private System.Windows.Forms.Label lbECParamE1; + private System.Windows.Forms.TextBox tbECParamE1; + private System.Windows.Forms.Label lbECParamK; + private System.Windows.Forms.TextBox tbECParamK; + private System.Windows.Forms.Label lbECParamR; + private System.Windows.Forms.TextBox tbECParamR; + private System.Windows.Forms.Label lbECParamG; + private System.Windows.Forms.TextBox tbECParamG; + private System.Windows.Forms.Label lbECParamB; + private System.Windows.Forms.TextBox tbECParamB; + private System.Windows.Forms.Label ltbECParamA; + private System.Windows.Forms.TextBox tbECParamA; + private System.Windows.Forms.GroupBox gbECPubKey; + private System.Windows.Forms.Label lbECPubKey; + private System.Windows.Forms.TextBox tbECPubKey; + private System.Windows.Forms.GroupBox gbECPrivKey; + private System.Windows.Forms.Label lbECPrivKey; + private System.Windows.Forms.TextBox tbECPrivKey; + private System.Windows.Forms.GroupBox gbSignatureParameters; + private System.Windows.Forms.ComboBox cbSignatureKeyIndex; + private System.Windows.Forms.Label lbSignatureKeyIndex; + private System.Windows.Forms.ComboBox cbDigest; + private System.Windows.Forms.Label lbDigest; + private System.Windows.Forms.ComboBox cbCipher; + private System.Windows.Forms.Label lbCipher; + private System.Windows.Forms.GroupBox gbMessage; + private System.Windows.Forms.RadioButton rbMessageFromFile; + private System.Windows.Forms.RadioButton rbMessageBase64; + private System.Windows.Forms.RadioButton rbMessageHex; + private System.Windows.Forms.RadioButton rbMessageAscii; + private System.Windows.Forms.Label lbMessage; + private System.Windows.Forms.TextBox tbMessage; + private System.Windows.Forms.GroupBox gbSignature; + private System.Windows.Forms.RadioButton rbSignatureBase64; + private System.Windows.Forms.RadioButton rbSignatureHex; + private System.Windows.Forms.Label lbSignature; + private System.Windows.Forms.TextBox tbSignature; + private System.Windows.Forms.GroupBox gbSignatureHash; + private System.Windows.Forms.ComboBox cbHashAlg; + private System.Windows.Forms.Label lbHashAlg; + private System.Windows.Forms.RadioButton rbHashBase64; + private System.Windows.Forms.RadioButton rbHashHex; + private System.Windows.Forms.Label lbHash; + private System.Windows.Forms.TextBox tbHash; + private System.Windows.Forms.Button btnHash; + private System.Windows.Forms.Button btnSignature; + private System.Windows.Forms.Button btnSignatureStoreToBin; + private System.Windows.Forms.Button btnHashStoreToBin; + private System.Windows.Forms.Button btnSaveMessageToBin; + private System.Windows.Forms.Button btnRestoreRSAPriv; + private System.Windows.Forms.Button btnBackupRSAPriv; + private System.Windows.Forms.Button btnRestoreECPriv; + private System.Windows.Forms.Button btnBackupECPriv; + private System.Windows.Forms.ProgressBar pbSigning; + } +} + diff --git a/frmMain.resx b/frmMain.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/frmMain.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/frmPassword.cs b/frmPassword.cs new file mode 100644 index 0000000..2903424 --- /dev/null +++ b/frmPassword.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace EcdsaTest +{ + public partial class frmPassword : Form + { + public string password + { + get { return tbPassword.Text; } + } + + public frmPassword() + { + InitializeComponent(); + } + } +} diff --git a/frmPassword.designer.cs b/frmPassword.designer.cs new file mode 100644 index 0000000..85d42b3 --- /dev/null +++ b/frmPassword.designer.cs @@ -0,0 +1,102 @@ +namespace EcdsaTest +{ + partial class frmPassword + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.lbPassword = new System.Windows.Forms.Label(); + this.tbPassword = new System.Windows.Forms.TextBox(); + this.btnOk = new System.Windows.Forms.Button(); + this.btnCancel = new System.Windows.Forms.Button(); + this.SuspendLayout(); + // + // lbPassword + // + this.lbPassword.AutoSize = true; + this.lbPassword.Location = new System.Drawing.Point(102, 24); + this.lbPassword.Name = "lbPassword"; + this.lbPassword.Size = new System.Drawing.Size(80, 13); + this.lbPassword.TabIndex = 0; + this.lbPassword.Text = "Enter password"; + // + // tbPassword + // + this.tbPassword.Font = new System.Drawing.Font("Courier New", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); + this.tbPassword.Location = new System.Drawing.Point(12, 49); + this.tbPassword.Name = "tbPassword"; + this.tbPassword.PasswordChar = '*'; + this.tbPassword.Size = new System.Drawing.Size(260, 22); + this.tbPassword.TabIndex = 1; + // + // btnOk + // + this.btnOk.DialogResult = System.Windows.Forms.DialogResult.OK; + this.btnOk.Location = new System.Drawing.Point(12, 89); + this.btnOk.Name = "btnOk"; + this.btnOk.Size = new System.Drawing.Size(122, 23); + this.btnOk.TabIndex = 2; + this.btnOk.Text = "&OK"; + this.btnOk.UseVisualStyleBackColor = true; + // + // btnCancel + // + this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.btnCancel.Location = new System.Drawing.Point(140, 89); + this.btnCancel.Name = "btnCancel"; + this.btnCancel.Size = new System.Drawing.Size(132, 23); + this.btnCancel.TabIndex = 3; + this.btnCancel.Text = "&Cancel"; + this.btnCancel.UseVisualStyleBackColor = true; + // + // frmPassword + // + this.AcceptButton = this.btnOk; + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.btnCancel; + this.ClientSize = new System.Drawing.Size(284, 141); + this.Controls.Add(this.btnCancel); + this.Controls.Add(this.btnOk); + this.Controls.Add(this.tbPassword); + this.Controls.Add(this.lbPassword); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.Name = "frmPassword"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Password"; + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label lbPassword; + private System.Windows.Forms.TextBox tbPassword; + private System.Windows.Forms.Button btnOk; + private System.Windows.Forms.Button btnCancel; + } +} \ No newline at end of file diff --git a/frmPassword.resx b/frmPassword.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/frmPassword.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/lib b/lib new file mode 160000 index 0000000..45af6b2 --- /dev/null +++ b/lib @@ -0,0 +1 @@ +Subproject commit 45af6b29b86b08b09b43b778e1e72591c094d4a3 diff --git a/signature.xml b/signature.xml new file mode 100644 index 0000000..f672181 --- /dev/null +++ b/signature.xml @@ -0,0 +1 @@ +VY42+3nZLdyJMvpscRpXF1WDUEw=qnsSrCSlDivML6FFD3IIPTL8GcEjzWku8GbeJ+wkQB4uscVP/ti7EHlSFpsj6lvH+RdIMjeX5Csa+mg7ED0ukTrLbZqbfD9tufKPQML/O64azyvEMK+dY4VSiviBc2Ehc17xwIz92WYYqnLmqWZiLoS9O0MVuAW8T17tWC06Tls=10wD5S5ZjpbzitBP8DoZfDP5PIf8TMKlYx17cwqvaryk+z/Ynj+EPBFKYYtfwo0Fow7KoFOhTA+LG7ICbxdVUClDW5RI2J+0HzUB3k6abts2+8QLkQ+pd0BQUAeVo0juAsKYg2NAyhFne3oCSx/QQMonpK2ha1BjlMrT0F8E8+0=AQAB \ No newline at end of file diff --git a/uFCoder.cs b/uFCoder.cs new file mode 100644 index 0000000..ae69582 --- /dev/null +++ b/uFCoder.cs @@ -0,0 +1,993 @@ +using System; +using System.Text; + +namespace uFR +{ + using System.Runtime.InteropServices; + using UFR_HANDLE = System.UIntPtr; + + enum CARD_SAK + { + UNKNOWN = 0x00, + MIFARE_CLASSIC_1k = 0x08, + MF1ICS50 = 0x08, + SLE66R35 = 0x88, + MIFARE_CLASSIC_4k = 0x18, + MF1ICS70 = 0x18, + MIFARE_CLASSIC_MINI = 0x09, + MF1ICS20 = 0x09, + } + + enum DLOGIC_CARD_TYPE + { + DL_NO_CARD = 0x00, + DL_MIFARE_ULTRALIGHT = 0x01, + DL_MIFARE_ULTRALIGHT_EV1_11 = 0x02, + DL_MIFARE_ULTRALIGHT_EV1_21 = 0x03, + DL_MIFARE_ULTRALIGHT_C = 0x04, + DL_NTAG_203 = 0x05, + DL_NTAG_210 = 0x06, + DL_NTAG_212 = 0x07, + DL_NTAG_213 = 0x08, + DL_NTAG_215 = 0x09, + DL_NTAG_216 = 0x0A, + DL_MIKRON_MIK640D = 0x0B, + NFC_T2T_GENERIC = 0x0C, + + DL_MIFARE_MINI = 0x20, + DL_MIFARE_CLASSIC_1K = 0x21, + DL_MIFARE_CLASSIC_4K = 0x22, + DL_MIFARE_PLUS_S_2K = 0x23, + DL_MIFARE_PLUS_S_4K = 0x24, + DL_MIFARE_PLUS_X_2K = 0x25, + DL_MIFARE_PLUS_X_4K = 0x26, + DL_MIFARE_DESFIRE = 0x27, + DL_MIFARE_DESFIRE_EV1_2K = 0x28, + DL_MIFARE_DESFIRE_EV1_4K = 0x29, + DL_MIFARE_DESFIRE_EV1_8K = 0x2A, + DL_MIFARE_DESFIRE_EV2_2K = 0x2B, + DL_MIFARE_DESFIRE_EV2_4K = 0x2C, + DL_MIFARE_DESFIRE_EV2_8K = 0x2D, + + DL_IMEI_UID = 0x80 + } + // MIFARE CLASSIC Authentication Modes: + enum MIFARE_AUTHENTICATION + { + MIFARE_AUTHENT1A = 0x60, + MIFARE_AUTHENT1B = 0x61 + } + // DLJavaCardSignerAlgorithmTypes: + enum JCDL_SIGNER_CIPHERS + { + SIG_CIPHER_RSA = 0, + SIG_CIPHER_ECDSA + }; + enum JCDL_SIGNER_PADDINGS + { + PAD_NULL = 0, + PAD_PKCS1 + }; + enum JCDL_SIGNER_DIGESTS + { + ALG_NULL = 0, + ALG_SHA, + ALG_SHA_256, + ALG_SHA_384, + ALG_SHA_512, + ALG_SHA_224 + }; + enum JCDL_KEY_TYPES + { + TYPE_RSA_PRIVATE = 0, + TYPE_RSA_CRT_PRIVATE, + TYPE_EC_F2M_PRIVATE, + TYPE_EC_FP_PRIVATE + }; + // API Status Codes Type: + public enum DL_STATUS + { + UFR_OK = 0x00, + + UFR_COMMUNICATION_ERROR = 0x01, + UFR_CHKSUM_ERROR = 0x02, + UFR_READING_ERROR = 0x03, + UFR_WRITING_ERROR = 0x04, + UFR_BUFFER_OVERFLOW = 0x05, + UFR_MAX_ADDRESS_EXCEEDED = 0x06, + UFR_MAX_KEY_INDEX_EXCEEDED = 0x07, + UFR_NO_CARD = 0x08, + UFR_COMMAND_NOT_SUPPORTED = 0x09, + UFR_FORBIDEN_DIRECT_WRITE_IN_SECTOR_TRAILER = 0x0A, + UFR_ADDRESSED_BLOCK_IS_NOT_SECTOR_TRAILER = 0x0B, + UFR_WRONG_ADDRESS_MODE = 0x0C, + UFR_WRONG_ACCESS_BITS_VALUES = 0x0D, + UFR_AUTH_ERROR = 0x0E, + UFR_PARAMETERS_ERROR = 0x0F, + UFR_MAX_SIZE_EXCEEDED = 0x10, + UFR_UNSUPPORTED_CARD_TYPE, + + UFR_WRITE_VERIFICATION_ERROR = 0x70, + UFR_BUFFER_SIZE_EXCEEDED = 0x71, + UFR_VALUE_BLOCK_INVALID = 0x72, + UFR_VALUE_BLOCK_ADDR_INVALID = 0x73, + UFR_VALUE_BLOCK_MANIPULATION_ERROR = 0x74, + UFR_WRONG_UI_MODE = 0x75, + UFR_KEYS_LOCKED = 0x76, + UFR_KEYS_UNLOCKED = 0x77, + UFR_WRONG_PASSWORD = 0x78, + UFR_CAN_NOT_LOCK_DEVICE = 0x79, + UFR_CAN_NOT_UNLOCK_DEVICE = 0x7A, + UFR_DEVICE_EEPROM_BUSY = 0x7B, + UFR_RTC_SET_ERROR = 0x7C, + UFR_TAG_UNKNOWN = 0x7D, + + UFR_COMMUNICATION_BREAK = 0x50, + UFR_NO_MEMORY_ERROR = 0x51, + UFR_CAN_NOT_OPEN_READER = 0x52, + UFR_READER_NOT_SUPPORTED = 0x53, + UFR_READER_OPENING_ERROR = 0x54, + UFR_READER_PORT_NOT_OPENED = 0x55, + UFR_CANT_CLOSE_READER_PORT = 0x56, + + UFR_FT_STATUS_ERROR_1 = 0xA0, + UFR_FT_STATUS_ERROR_2 = 0xA1, + UFR_FT_STATUS_ERROR_3 = 0xA2, + UFR_FT_STATUS_ERROR_4 = 0xA3, + UFR_FT_STATUS_ERROR_5 = 0xA4, + UFR_FT_STATUS_ERROR_6 = 0xA5, + UFR_FT_STATUS_ERROR_7 = 0xA6, + UFR_FT_STATUS_ERROR_8 = 0xA7, + UFR_FT_STATUS_ERROR_9 = 0xA8, + + //NDEF error codes + UFR_WRONG_NDEF_CARD_FORMAT = 0x80, + UFR_NDEF_MESSAGE_NOT_FOUND = 0x81, + UFR_NDEF_UNSUPPORTED_CARD_TYPE = 0x82, + UFR_NDEF_CARD_FORMAT_ERROR = 0x83, + UFR_MAD_NOT_ENABLED = 0x84, + UFR_MAD_VERSION_NOT_SUPPORTED = 0x85, + + UFR_TIMEOUT_ERR = 0x90, + + // multiple units - return from the functions with ReaderList_ prefix in name + UFR_DEVICE_WRONG_HANDLE = 0x100, + UFR_DEVICE_INDEX_OUT_OF_BOUND, + UFR_DEVICE_ALREADY_OPENED, + UFR_DEVICE_ALREADY_CLOSED, + UFR_DEVICE_IS_NOT_CONNECTED, + + // Originality Check Error Codes: + UFR_NOT_NXP_GENUINE = 0x200, + UFR_OPEN_SSL_DYNAMIC_LIB_FAILED, + UFR_OPEN_SSL_DYNAMIC_LIB_NOT_FOUND, + + UFR_NOT_IMPLEMENTED = 0x1000, + UFR_COMMAND_FAILED, + + // APDU Error Codes: + UFR_APDU_JC_APP_NOT_SELECTED = 0x6000, + UFR_APDU_WRONG_SELECT_RESPONSE, + UFR_APDU_WRONG_KEY_TYPE, + UFR_APDU_WRONG_KEY_SIZE, + UFR_APDU_WRONG_KEY_PARAMS, + UFR_APDU_WRONG_ALGORITHM, + UFR_APDU_PLAIN_TEXT_SIZE_EXCEEDED, + UFR_APDU_UNSUPPORTED_KEY_SIZE, + UFR_APDU_UNSUPPORTED_ALGORITHMS, + UFR_APDU_SW_TAG = 0x0A0000, + + MAX_UFR_STATUS /*= 10000000, + UNKNOWN_ERROR */ = 2147483647 // 0x7FFFFFFF + }; + + public static class uFCoder + { + public const uint SIG_MAX_PLAIN_DATA_LEN = 255; + public const string JCDL_AID_RID = "A0 F0 F1 F2 F3"; + public const string JCDL_AID_PIX = "00 01 00 01"; + public const string JCDL_AID = JCDL_AID_RID + JCDL_AID_PIX; + + //-------------------------------------------------------------------------------------------------- +#if WIN64 + public const string DLL_NAME = "uFCoder-x86_64.dll"; // for x64 target +#else + public const string DLL_NAME = "uFCoder-x86.dll"; // for x86 target +#endif + //-------------------------------------------------------------------------------------------------- + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, EntryPoint = "ReaderOpen")] + public static extern DL_STATUS ReaderOpen(); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, EntryPoint = "ReaderOpenEx")] + public static extern DL_STATUS ReaderOpenEx(UInt32 reader_type, IntPtr port_name, UInt32 port_interface, IntPtr arg); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, EntryPoint = "ReaderClose")] + public static extern DL_STATUS ReaderClose(); + + //-------------------------------------------------------------------------------------------------- + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, EntryPoint = "GetReaderType")] + public static extern DL_STATUS GetReaderType(out UInt32 lpulReaderType); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, EntryPoint = "GetReaderSerialDescription")] + private static extern DL_STATUS getReaderSerialDescription(StringBuilder pSerialDescription); + public static DL_STATUS GetReaderSerialDescription(out string SerialDescription) + { + StringBuilder pSerialDescription = new StringBuilder(7); + DL_STATUS status = DL_STATUS.UFR_OK; + + status = getReaderSerialDescription(pSerialDescription); + if (status == DL_STATUS.UFR_OK) + SerialDescription = pSerialDescription.ToString(); + else + SerialDescription = ""; + return status; + } + + //--------------------------------------------------------------------- + // Card emulation: + //--------------------------------------------------------------------- + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, EntryPoint = "TagEmulationStart")] + public static extern DL_STATUS TagEmulationStart(); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, EntryPoint = "TagEmulationStop")] + public static extern DL_STATUS TagEmulationStop(); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, EntryPoint = "CombinedModeEmulationStart")] + public static extern DL_STATUS CombinedModeEmulationStart(); + //--------------------------------------------------------------------- + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, EntryPoint = "GetDlogicCardType")] + public static extern DL_STATUS GetDlogicCardType(out byte lpucCardType); + + //--------------------------------------------------------------------- + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, EntryPoint = "ReaderList_UpdateAndGetCount")] + public static extern DL_STATUS ReaderList_UpdateAndGetCount(out UInt32 NumberOfDevices); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, EntryPoint = "ReaderList_GetSerialByIndex")] + public static extern DL_STATUS ReaderList_GetSerialByIndex(Int32 DeviceIndex, out UInt32 lpulSerialNumber); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, EntryPoint = "ReaderList_GetSerialDesByIndex")] + public static extern DL_STATUS ReaderList_GetSerialDescriptionByIndex(Int32 DeviceIndex, out byte pSerialDescription); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, EntryPoint = "ReaderList_GetTypeByIndex")] + public static extern DL_STATUS ReaderList_GetTypeByIndex(Int32 DeviceIndex, out UInt32 lpulReaderType); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, EntryPoint = "ReaderList_GetFTDISerialByIndex")] + private static extern DL_STATUS GetFTDISerialByIndex(UInt32 DeviceIndex, out IntPtr DeviceSerial); + public static DL_STATUS ReaderList_GetFTDISerialByIndex(UInt32 DeviceIndex, out string DeviceSerial) + { + IntPtr ptr; + DL_STATUS status = DL_STATUS.UFR_OK; + + status = GetFTDISerialByIndex(DeviceIndex, out ptr); + DeviceSerial = Marshal.PtrToStringAnsi(ptr); + return status; + } + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, EntryPoint = "ReaderList_GetFTDIDescriptionByIndex")] + private static extern DL_STATUS GetFTDIDescriptionByIndex(UInt32 DeviceIndex, out IntPtr DeviceDescription); + public static DL_STATUS ReaderList_GetFTDIDescriptionByIndex(UInt32 DeviceIndex, out string DeviceDescription) + { + IntPtr ptr; + DL_STATUS status = DL_STATUS.UFR_OK; + + status = GetFTDIDescriptionByIndex(DeviceIndex, out ptr); + DeviceDescription = Marshal.PtrToStringAnsi(ptr); + return status; + } + + //--------------------------------------------------------------------- + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, EntryPoint = "ReaderList_OpenByIndex")] + public static extern DL_STATUS ReaderList_OpenByIndex(Int32 DeviceIndex, out UFR_HANDLE hndUFR); + + //--------------------------------------------------------------------- + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, EntryPoint = "GetDllVersion")] + public static extern UInt32 GetDllVersion(); + + //--------------------------------------------------------------------- + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, EntryPoint = "GetReaderHardwareVersion")] + public static extern DL_STATUS GetReaderHardwareVersion(out byte version_major, out byte version_minor); + + //--------------------------------------------------------------------- + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, EntryPoint = "GetReaderFirmwareVersion")] + public static extern DL_STATUS GetReaderFirmwareVersion(out byte version_major, out byte version_minor); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, EntryPoint = "GetBuildNumber")] + public static extern DL_STATUS GetBuildNumber(out byte build); + + //--------------------------------------------------------------------- + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, EntryPoint = "SectorTrailerWriteUnsafe_PK")] + public static extern DL_STATUS SectorTrailerWriteUnsafe_PK(byte addressing_mode, byte address, out byte sector_trailer, + byte auth_mode, ref byte key); + //--------------------------------------------------------------------- + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, EntryPoint = "LinearWrite_PK")] + public static extern DL_STATUS LinearWrite_PK(out byte data, ushort linear_address, ushort length, out ushort bytes_written, + byte auth_mode, ref byte key); + //--------------------------------------------------------------------- + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, EntryPoint = "EE_Lock")] + private static extern DL_STATUS Linkage_EE_Lock(StringBuilder password, UInt32 locked); + public static DL_STATUS EE_Lock(String password, UInt32 locked) + { + if (password.Length != 8) + return DL_STATUS.UFR_PARAMETERS_ERROR; + + StringBuilder ptr_password = new StringBuilder(password); + return Linkage_EE_Lock(ptr_password, locked); + } + + //-------------------------------------------------------------------------------------------------------------- + + //[DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, EntryPoint = "ReaderOpen")] + //public static extern DL_STATUS ReaderOpen(); + + //[DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, EntryPoint = "ReaderClose")] + //public static extern DL_STATUS ReaderClose(); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "ReaderReset")] + public static extern DL_STATUS ReaderReset(); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "ReaderSoftRestart")] + public static extern DL_STATUS ReaderSoftRestart(); + + //-------------------------------------------------------------------------------------------------------------- + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "UfrEnterSleepMode")] + public static extern DL_STATUS UfrEnterSleepMode(); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "UfrLeaveSleepMode")] + public static extern DL_STATUS UfrLeaveSleepMode(); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "AutoSleepSet")] + public static extern DL_STATUS AutoSleepSet(byte seconds_wait); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "AutoSleepGet")] + public static extern DL_STATUS AutoSleepGet(out byte seconds_wait); + + //[DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "GetReaderType")] + //public static extern DL_STATUS GetReaderType(out UInt32 get_reader_type); + + //[DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, EntryPoint = "GetReaderSerialDescription")] + //public static extern DL_STATUS GetReaderSerialDescription(out byte pSerialDescription); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "GetReaderSerialNumber")] + public static extern DL_STATUS GetReaderSerialNumber(out UInt32 serial_number); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "ReaderKeyWrite")] + public static extern DL_STATUS ReaderKeyWrite(out byte aucKey, byte ucKeyIndex); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "ReaderUISignal")] + public static extern DL_STATUS ReaderUISignal(int light_mode, int sound_mode); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "ReadUserData")] + public static extern DL_STATUS ReadUserData(out byte aucData); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "WriteUserData")] + public static extern DL_STATUS WriteUserData(out byte aucData); + + //[DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "GetReaderHardwareVersion")] + //public static extern DL_STATUS GetReaderHardwareVersion(out byte bVerMajor, + // out byte bVerMinor); + //[DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "GetReaderFirmwareVersion")] + //public static extern DL_STATUS GetReaderFirmwareVersion(out byte bVerMajor, + // out byte bVerMinor); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "SetDisplayData")] + public static extern DL_STATUS SetDisplayData(out byte display_data, byte data_length); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "SetSpeakerFrequency")] + public static extern DL_STATUS SetSpeakerFrequency(short fhz); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "SetDisplayIntensity")] + public static extern DL_STATUS SetDisplayIntensity(Byte intensity); // 0 to 100 [%] + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "GetDisplayIntensity")] + public static extern DL_STATUS GetDisplayIntensity(out Byte intensity); // 0 to 100 [%] + //-------------------------------------------------------------------------------------------------------------- + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "GetCardIdEx")] + public static extern DL_STATUS GetCardIdEx(out byte bCardType, + [In, Out] byte[] nfc_uid, // NFC_UID_MAX_LEN = 10 + out byte bUidSize); + + //[DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, EntryPoint = "GetDlogicCardType")] + //public static extern DL_STATUS GetDlogicCardType(out byte lpucCardType); + + //-------------------------------------------------------------------------------------------------------------- + + //[DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, EntryPoint = "ReaderList_UpdateAndGetCount")] + //public static extern DL_STATUS ReaderList_UpdateAndGetCount(Int32* NumberOfDevices); + + //[DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, EntryPoint = "ReaderList_GetSerialByIndex")] + //public static extern DL_STATUS ReaderList_GetSerialByIndex(Int32 DeviceIndex, UInt32* lpulSerialNumber); + + //[DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, EntryPoint = "ReaderList_GetSerialDesByIndex")] + //public static extern DL_STATUS ReaderList_GetSerialDescriptionByIndex(Int32 DeviceIndex, char* pSerialDescription); + + //[DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, EntryPoint = "ReaderList_GetTypeByIndex")] + //public static extern DL_STATUS ReaderList_GetTypeByIndex(Int32 DeviceIndex, UInt32* lpulReaderType); + + //[DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, EntryPoint = "ReaderList_GetFTDISerialByIndex")] + //public static extern DL_STATUS ReaderList_GetFTDISerialByIndex(Int32 DeviceIndex, char** Device_Serial); + + //[DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, EntryPoint = "ReaderList_GetFTDIDescriptionByIndex")] + //public static extern DL_STATUS ReaderList_GetFTDIDescriptionByIndex(Int32 DeviceIndex, char** Device_Description); + + //-------------------------------------------------------------------------------------------------------------- + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "LinearRead")] + public static extern DL_STATUS LinearRead(out byte aucData, + ushort linear_address, + ushort data_len, + out UInt16 bytes_written, + byte auth_mode, + byte key_index); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "LinearRead_AKM1")] + public static extern DL_STATUS LinearRead_AKM1(out byte aucData, + ushort linear_address, + ushort data_len, + out UInt16 bytes_written, + byte key_mode); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "LinearRead_AKM2")] + public static extern DL_STATUS LinearRead_AKM2(out byte aucData, + ushort linear_address, + ushort data_len, + out UInt16 bytes_written, + byte key_mode); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "LinearRead_PK")] + public static extern DL_STATUS LinearRead_PK(out byte aucData, + ushort linear_address, + ushort data_len, + out UInt16 bytes_written, + byte key_mode, + out byte pk_key); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "LinearWrite")] + public static extern DL_STATUS LinearWrite(out byte aucData, + ushort linear_address, + ushort data_len, + out UInt16 bytes_written, + byte auth_mode, + byte key_index); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "LinearWrite_AKM1")] + public static extern DL_STATUS LinearWrite_AKM1(out byte aucData, + ushort linear_address, + ushort data_len, + out UInt16 bytes_written, + byte auth_mode); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "LinearWrite_AKM2")] + public static extern DL_STATUS LinearWrite_AKM2(out byte aucData, + ushort linear_address, + ushort data_len, + out UInt16 bytes_written, + byte auth_mode); + + //[DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "LinearWrite_PK")] + //public static extern DL_STATUS LinearWrite_PK(out byte aucData, + // ushort linear_address, + // ushort data_len, + // out UInt16 bytes_written, + // byte key_mode, + // out byte pk_key); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "BlockRead")] + public static extern DL_STATUS BlockRead(out byte data, + UInt16 block_address, + byte auth_mode, + byte key_index); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "BlockRead_AKM1")] + public static extern DL_STATUS BlockRead_AKM1(out byte data, + UInt16 block_address, + byte auth_mode); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "BlockRead_AKM2")] + public static extern DL_STATUS BlockRead_AKM2(out byte data, + UInt16 block_address, + byte auth_mode); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "BlockRead_PK")] + public static extern DL_STATUS BlockRead_PK(out byte data, + UInt16 block_address, + byte auth_mode, + out byte pk_key); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "BlockWrite")] + public static extern DL_STATUS BlockWrite(out byte data, + UInt16 block_address, + byte auth_mode, + byte key_index); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "BlockWrite_AKM1")] + public static extern DL_STATUS BlockWrite_AKM1(out byte data, + UInt16 block_address, + byte auth_mode); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "BlockWrite_AKM2")] + public static extern DL_STATUS BlockWrite_AKM2(out byte data, + UInt16 block_address, + byte auth_mode); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "BlockWrite_PK")] + public static extern DL_STATUS BlockWrite_PK(out byte data, + UInt16 block_address, + byte auth_mode, + out byte pk_key); + + + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "BlockInSectorRead")] + public static extern DL_STATUS BlockInSectorRead(out byte data, + byte sector_address, + byte block_in_sector_address, + byte auth_mode, byte key_index); + + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "BlockInSectorRead_AKM1")] + public static extern DL_STATUS BlockInSectorRead_AKM1(out byte data, + byte sector_address, + byte block_in_sector_address, + byte auth_mode); + + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "BlockInSectorRead_AKM2")] + public static extern DL_STATUS BlockInSectorRead_AKM2(out byte data, + byte sector_address, + byte block_in_sector_address, + byte auth_mode); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "BlockInSectorRead_PK")] + public static extern DL_STATUS BlockInSectorRead_PK(out byte data, + byte sector_address, + byte block_in_sector_address, + byte auth_mode, + out byte pk_key); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "BlockInSectorWrite")] + public static extern DL_STATUS BlockInSectorWrite(out byte data, + byte sector_address, + byte block_in_sector_address, + byte auth_mode, + byte key_index); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "BlockInSectorWrite_AKM1")] + public static extern DL_STATUS BlockInSectorWrite_AKM1(out byte data, + byte sector_address, + byte block_in_sector_address, + byte auth_mode); + + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "BlockInSectorWrite_AKM2")] + public static extern DL_STATUS BlockInSectorWrite_AKM2(out byte data, + byte sector_address, + byte block_in_sector_address, + byte auth_mode); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "BlockInSectorWrite_PK")] + public static extern DL_STATUS BlockInSectorWrite_PK(out byte data, + byte sector_address, + byte block_in_sector_address, + byte auth_mode, + out byte pk_key); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "ValueBlockRead")] + public static extern DL_STATUS ValueBlockRead(out Int32 value, + out byte value_addr, + UInt16 block_address, + byte auth_mode, + byte key_index); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "ValueBlockRead_AKM1")] + public static extern DL_STATUS ValueBlockRead_AKM1(out Int32 value, + out byte value_addr, + UInt16 block_address, + byte auth_mode); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "ValueBlockRead_AKM2")] + public static extern DL_STATUS ValueBlockRead_AKM2(out Int32 value, + out byte value_addr, + UInt16 block_address, + byte auth_mode); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "ValueBlockRead_PK")] + public static extern DL_STATUS ValueBlockRead_PK(out Int32 value, + out byte value_addr, + UInt16 block_address, + byte auth_mode, + out byte pk_key); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "ValueBlockWrite")] + public static extern DL_STATUS ValueBlockWrite(int value, + byte value_addr, + UInt16 block_address, + byte auth_mode, + byte key_index); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "ValueBlockWrite_AKM1")] + public static extern DL_STATUS ValueBlockWrite_AKM1(int value, + byte value_addr, + UInt16 block_address, + byte auth_mode); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "ValueBlockWrite_AKM2")] + public static extern DL_STATUS ValueBlockWrite_AKM2(int value, + byte value_addr, + UInt16 block_address, + byte auth_mode); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "ValueBlockWrite_PK")] + public static extern DL_STATUS ValueBlockWrite_PK(int value, + byte value_addr, + UInt16 block_address, + byte auth_mode, + out byte pk_key); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "ValueBlockIncrement")] + public static extern DL_STATUS ValueBlockIncrement(int increment_value, + UInt16 block_address, + byte auth_mode, + byte key_index); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "ValueBlockIncrement_AKM1")] + public static extern DL_STATUS ValueBlockIncrement_AKM1(int increment_value, + UInt16 block_address, + byte auth_mode); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "ValueBlockIncrement_AKM2")] + public static extern DL_STATUS ValueBlockIncrement_AKM2(int increment_value, + UInt16 block_address, + byte auth_mode); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "ValueBlockIncrement_PK")] + public static extern DL_STATUS ValueBlockIncrement_PK(int increment_value, + UInt16 block_address, + byte auth_mode, + out byte pk_key); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "ValueBlockDecrement")] + public static extern DL_STATUS ValueBlockDecrement(int increment_value, + UInt16 block_address, + byte auth_mode, + byte key_index); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "ValueBlockDecrement_AKM1")] + public static extern DL_STATUS ValueBlockDecrement_AKM1(int increment_value, + UInt16 block_address, + byte auth_mode); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "ValueBlockDecrement_AKM2")] + public static extern DL_STATUS ValueBlockDecrement_AKM2(int increment_value, + UInt16 block_address, + byte auth_mode); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "ValueBlockDecrement_PK")] + public static extern DL_STATUS ValueBlockDecrement_PK(int increment_value, + UInt16 block_address, + byte auth_mode, + out byte pk_key); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "ValueBlockInSectorRead")] + public static extern DL_STATUS ValueBlockInSectorRead(out Int32 value, + out byte value_addr, + byte sector_address, + byte block_in_sector_address, + byte auth_mode, + byte key_index); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "ValueBlockInSectorRead_AKM1")] + public static extern DL_STATUS ValueBlockInSectorRead_AKM1(out Int32 value, + out byte value_addr, + byte sector_address, + byte block_in_sector_address, + byte auth_mode); + + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "ValueBlockInSectorRead_AKM2")] + public static extern DL_STATUS ValueBlockInSectorRead_AKM2(out Int32 value, + out byte value_addr, + byte sector_address, + byte block_in_sector_address, + byte auth_mode); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "ValueBlockInSectorRead_PK")] + public static extern DL_STATUS ValueBlockInSectorRead_PK(out Int32 value, + out byte value_addr, + byte sector_address, + byte block_in_sector_address, + byte auth_mode, + out byte pk_key); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "ValueBlockInSectorWrite")] + public static extern DL_STATUS ValueBlockInSectorWrite(Int32 value, + byte value_addr, + byte sector_address, + byte block_in_sector_address, + byte auth_mode, byte key_index); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "ValueBlockInSectorWrite_AKM1")] + public static extern DL_STATUS ValueBlockInSectorWrite_AKM1(Int32 value, + byte value_addr, + byte sector_address, + byte block_in_sector_address, + byte auth_mode); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "ValueBlockInSectorWrite_AKM2")] + public static extern DL_STATUS ValueBlockInSectorWrite_AKM2(Int32 value, + byte value_addr, + byte sector_address, + byte block_in_sector_address, + byte auth_mode); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "ValueBlockInSectorWrite_PK")] + public static extern DL_STATUS ValueBlockInSectorWrite_PK(Int32 value, + byte value_addr, + byte sector_address, + byte block_in_sector_address, + byte auth_mode, + out byte pk_key); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "ValueBlockInSectorIncrement")] + public static extern DL_STATUS ValueBlockInSectorIncrement(Int32 increment_value, + byte sector_address, + byte block_in_sector_address, + byte auth_mode, + byte key_index); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "ValueBlockInSectorIncrement_AKM1")] + public static extern DL_STATUS ValueBlockInSectorIncrement_AKM1(Int32 increment_value, + byte sector_address, + byte block_in_sector_address, + byte auth_mode); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "ValueBlockInSectorIncrement_AKM2")] + public static extern DL_STATUS ValueBlockInSectorIncrement_AKM2(Int32 increment_value, + byte sector_address, + byte block_in_sector_address, + byte auth_mode); + + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "ValueBlockInSectorIncrement_PK")] + public static extern DL_STATUS ValueBlockInSectorIncrement_PK(Int32 increment_value, + byte sector_address, + byte block_in_sector_address, + byte auth_mode, + out byte pk_key); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "ValueBlockInSectorDecrement")] + public static extern DL_STATUS ValueBlockInSectorDecrement(Int32 decrement_value, + byte sector_address, + byte block_in_sector_address, + byte auth_mode, + byte key_index); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "ValueBlockInSectorDecrement_AKM1")] + public static extern DL_STATUS ValueBlockInSectorDecrement_AKM1(Int32 decrement_value, + byte sector_address, + byte block_in_sector_address, + byte auth_mode); + + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "ValueBlockInSectorDecrement_AKM2")] + public static extern DL_STATUS ValueBlockInSectorDecrement_AKM2(Int32 decrement_value, + byte sector_address, + byte block_in_sector_address, + byte auth_mode); + + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "ValueBlockInSectorDecrement_PK")] + public static extern DL_STATUS ValueBlockInSectorDecrement_PK(Int32 decrement_value, + byte sector_address, + byte block_in_sector_address, + byte auth_mode, + out byte pk_key); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "SectorTrailerWrite")] + public static extern DL_STATUS SectorTrailerWrite(byte addressing_mode, + byte address, + out byte new_key_A, + byte block0_access_bits, + byte block1_access_bits, + byte block2_access_bits, + byte sector_trailer_access_bits, + byte sector_trailer_byte9, + out byte new_key_B, + byte auth_mode, + byte key_index); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "SectorTrailerWrite_AKM1")] + public static extern DL_STATUS SectorTrailerWrite_AKM1(byte addressing_mode, + byte address, + out byte new_key_A, + byte block0_access_bits, + byte block1_access_bits, + byte block2_access_bits, + byte sector_trailer_access_bits, + byte sector_trailer_byte9, + out byte new_key_B, + byte auth_mode); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "SectorTrailerWrite_AKM2")] + public static extern DL_STATUS SectorTrailerWrite_AKM2(byte addressing_mode, + byte address, + out byte new_key_A, + byte block0_access_bits, + byte block1_access_bits, + byte block2_access_bits, + byte sector_trailer_access_bits, + byte sector_trailer_byte9, + out byte new_key_B, + byte auth_mode); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "SectorTrailerWrite_PK")] + public static extern DL_STATUS SectorTrailerWrite_PK(byte addressing_mode, + byte address, + out byte new_key_A, + byte block0_access_bits, + byte block1_access_bits, + byte block2_access_bits, + byte sector_trailer_access_bits, + byte sector_trailer_byte9, + out byte new_key_B, + byte auth_mode, + out byte pk_key); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "LinearFormatCard")] + public static extern DL_STATUS LinearFormatCard(out byte new_key_A, + byte blocks_access_bits, + byte sector_trailers_access_bits, + byte sector_trailers_byte9, + out byte new_key_B, + out byte sectors_formatted, + byte auth_mode, + byte key_index); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "LinearFormatCard_AKM1")] + public static extern DL_STATUS LinearFormatCard_AKM1(out byte new_key_A, + byte blocks_access_bits, + byte sector_trailers_access_bits, + byte sector_trailers_byte9, + out byte new_key_B, + out byte sectors_formatted, + byte auth_mode); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "LinearFormatCard_AKM2")] + public static extern DL_STATUS LinearFormatCard_AKM2(out byte new_key_A, + byte blocks_access_bits, + byte sector_trailers_access_bits, + byte sector_trailers_byte9, + out byte new_key_B, + out byte sectors_formatted, + byte auth_mode); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "LinearFormatCard_PK")] + public static extern DL_STATUS LinearFormatCard_PK(out byte new_key_A, + byte blocks_access_bits, + byte sector_trailers_access_bits, + byte sector_trailers_byte9, + out byte new_key_B, + out byte sectors_formatted, + byte auth_mode, + out byte pk_key); + + //---------------------------------------------------------------------- + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "SetISO14443_4_Mode")] + public static extern DL_STATUS SetISO14443_4_Mode(); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "s_block_deselect")] + public static extern DL_STATUS s_block_deselect(byte timeout); + //---------------------------------------------------------------------- + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "JCAppSelectByAid")] + public static extern DL_STATUS JCAppSelectByAid([In] byte[] aid, byte aid_len, [Out] byte[] selection_response); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "JCAppPutPrivateKey")] + public static extern DL_STATUS JCAppPutPrivateKey(byte key_type, byte key_index, + [In] byte[] key, UInt16 key_bit_len, + [In] byte[] key_param, UInt16 key_parm_len); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "JCAppSignatureBegin")] + public static extern DL_STATUS JCAppSignatureBegin(byte cipher, byte digest, byte padding, byte key_index, + [In] byte[] chunk, UInt16 chunk_len, + [In] byte[] alg_param, UInt16 alg_parm_len); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "JCAppSignatureUpdate")] + public static extern DL_STATUS JCAppSignatureUpdate([In] byte[] chunk, UInt16 chunk_len); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "JCAppSignatureEnd")] + private static extern DL_STATUS JCAppSignatureEnd(out UInt16 sig_len); + public static DL_STATUS JCAppSignatureEnd(out byte[] sig) + { + DL_STATUS status; + UInt16 sig_len; + + status = JCAppSignatureEnd(out sig_len); + if (status != DL_STATUS.UFR_OK) + { + sig = null; + return status; + } + + sig = new byte[sig_len]; + return JCAppGetSignature(sig, sig_len); + } + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "JCAppGenerateSignature")] + private static extern DL_STATUS JCAppGenerateSignature(byte cipher, byte digest, byte padding, byte key_index, + [In] byte[] plain_data, UInt16 plain_data_len, + out UInt16 sig_len, + [In] byte[] alg_param, UInt16 alg_parm_len); + public static DL_STATUS JCAppGenerateSignature(byte cipher, byte digest, byte padding, byte key_index, + [In] byte[] plain_data, UInt16 plain_data_len, + out byte[] sig, + [In] byte[] alg_param, UInt16 alg_parm_len) + { + DL_STATUS status; + UInt16 sig_len; + + status = JCAppGenerateSignature(cipher, digest, padding, key_index, plain_data, plain_data_len, out sig_len, alg_param, alg_parm_len); + if (status != DL_STATUS.UFR_OK) + { + sig = null; + return status; + } + + sig = new byte[sig_len]; + return JCAppGetSignature(sig, sig_len); + } + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, EntryPoint = "JCAppGetSignature")] + private static extern DL_STATUS JCAppGetSignature([Out] byte[] sig, UInt16 sig_len); + //---------------------------------------------------------------------- + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, EntryPoint = "UfrXrcLockOn")] + public static extern DL_STATUS UfrXrcLockOn(UInt16 pulse_duration); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, EntryPoint = "UfrXrcRelayState")] + public static extern DL_STATUS UfrXrcRelayState(byte state); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, EntryPoint = "UfrXrcGetIoState")] + public static extern DL_STATUS UfrXrcGetIoState(out byte intercom, out byte dig_in, out byte relay_state); + + //---------------------------------------------------------------------- + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, EntryPoint = "ReaderOpenM")] + public static extern DL_STATUS ReaderOpenM(UFR_HANDLE hndUFR); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, EntryPoint = "ReaderCloseM")] + public static extern DL_STATUS ReaderCloseM(UFR_HANDLE hndUFR); + + //---------------------------------------------------------------------- + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, EntryPoint = "GetCardIdExM")] + public static extern DL_STATUS GetCardIdExM(UFR_HANDLE hndUFR, + out byte bCardType, + [Out] byte nfc_uid, // NFC_UID_MAX_LEN = 10 + out byte bUidSize); + + //[DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, EntryPoint = "GetReaderTypeM")] + //public static extern DL_STATUS GetReaderTypeM(UInt32* get_reader_type); + + //---------------------------------------------------------------------- + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, EntryPoint = "BlockRead_PKM")] + public static extern DL_STATUS BlockRead_PKM(UFR_HANDLE hndUFR, + out byte data, + byte block_address, + byte auth_mode, + ref byte key); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall, EntryPoint = "BlockWrite_PKM")] + public static extern DL_STATUS BlockWrite_PKM(UFR_HANDLE hndUFR, + out byte data, + byte block_address, + byte auth_mode, + ref byte key); + + //---------------------------------------------------------------------- + } +} diff --git a/ufr-signer.csproj b/ufr-signer.csproj new file mode 100644 index 0000000..0ae8fe8 --- /dev/null +++ b/ufr-signer.csproj @@ -0,0 +1,1626 @@ + + + + + Debug + AnyCPU + {0D346CC0-83C5-4117-B2E6-2A33686B3FC0} + WinExe + Properties + EcdsaTest + ufr-signer + v4.6 + 512 + true + false + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + true + + + true + bin\x64\Debug\ + TRACE;DEBUG;WIN64 + full + x64 + prompt + MinimumRecommendedRules.ruleset + true + + + + + bin\x64\Release\ + TRACE;WIN64 + true + pdbonly + x64 + prompt + MinimumRecommendedRules.ruleset + true + + + true + bin\x86\Debug\ + DEBUG;TRACE + full + x86 + prompt + MinimumRecommendedRules.ruleset + true + + + bin\x86\Release\ + TRACE + true + pdbonly + x86 + prompt + MinimumRecommendedRules.ruleset + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Form + + + frmMain.cs + + + Form + + + frmPassword.cs + + + + + + frmMain.cs + + + frmPassword.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + True + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + + False + Microsoft .NET Framework 4.6 %28x86 and x64%29 + true + + + False + .NET Framework 3.5 SP1 + false + + + + + \ No newline at end of file diff --git a/ufr-signer.sln b/ufr-signer.sln new file mode 100644 index 0000000..9ca097e --- /dev/null +++ b/ufr-signer.sln @@ -0,0 +1,32 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.23107.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ufr-signer", "ufr-signer.csproj", "{0D346CC0-83C5-4117-B2E6-2A33686B3FC0}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0D346CC0-83C5-4117-B2E6-2A33686B3FC0}.Debug|Any CPU.ActiveCfg = Debug|x86 + {0D346CC0-83C5-4117-B2E6-2A33686B3FC0}.Debug|x64.ActiveCfg = Debug|x64 + {0D346CC0-83C5-4117-B2E6-2A33686B3FC0}.Debug|x64.Build.0 = Debug|x64 + {0D346CC0-83C5-4117-B2E6-2A33686B3FC0}.Debug|x86.ActiveCfg = Debug|x64 + {0D346CC0-83C5-4117-B2E6-2A33686B3FC0}.Debug|x86.Build.0 = Debug|x64 + {0D346CC0-83C5-4117-B2E6-2A33686B3FC0}.Release|Any CPU.ActiveCfg = Release|x86 + {0D346CC0-83C5-4117-B2E6-2A33686B3FC0}.Release|x64.ActiveCfg = Release|x64 + {0D346CC0-83C5-4117-B2E6-2A33686B3FC0}.Release|x64.Build.0 = Release|x64 + {0D346CC0-83C5-4117-B2E6-2A33686B3FC0}.Release|x86.ActiveCfg = Release|x86 + {0D346CC0-83C5-4117-B2E6-2A33686B3FC0}.Release|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal -- GitLab

    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 z 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 z�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

    ?|aduX%Pp63W*+y95d4 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 z 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 S`Zr#o#{c + + + + + \ 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..c3e48b2c0b1a5e8e09d2d071b81d8730c3355548 GIT binary patch literal 2215424 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

    kRv^J$v};|=RlWI1qUtWE{(zcnLF?yzj=mV z-HFij*e61xRnW@eWQQJ2q>3RYU9U&`1id@lbT2!&FZ=K7kULl$ucdJ8Owdd`d@pIw z|4-+EGL2wNZb&VY2*$%|DrvwnK4Cc{R^cuw`Ur(LX(OeKlIUSZN!T9$Ak_&OrwdKm zk)Yigd>nH>#x#xn6)mWzx;h$L`{bfKRtBYuyq@WG&?-W*b4&vym%-(n_7d2RE0P4{ zMnb?9J{PLn*IGvBM2vJmz3h%irx5b?kjWd}WD_*Fmb^t%f*Y7vl%>w2ho$}#r}OBC z!Q0h;qRl+|6!5q8-%+FfN406N%u^7;9WB7%sR&RRVyci$!6FCb&eEZq%)uwuIR{^2 z4qUq?Ep{k4sYjZx(!lM%TY65NVV{EbN3OZ4^`_ClLxw9)%p~@ zdFC>k+6@D_(CH9fP|k!mB1XIRIZOszH;>jn%eqVACQwydCpD%^dg_%hu9?8bHQoWL z*IL}HxK4jsev9HJ8yvUagwACzN)w> z2FLa#yp60yta!|O-}1iA)sJQHRWxxie`a?GcTt($E!;ZDrHbkJulA;~F@)<3;n%>c z+U>yG2{z-o7r9j9>wt>jUo6eJ1_eG$dB}bYIfsS!YfwnU(WPAIQWkWXf4a;jU1p9h zGenn}pvwf%Wz2LLCta49j4MaK!>QAb!N=ajfTF(xjBH_R5#A{nU-EaENa@4K1^yN8 z*96=@6E~W|6@gF2m9PqS8nX<`nq*4Mz$A`_hCI+K`ADaD_x=d=a_CVMQDeF8dL@P;m@L~oSqm|UY>ci0Sq#eq=D>8L08#^GCyOBt{f=}BW)>ATh>^*GsRQ;Qv zfijrNKqy8dUCFnSSW*6al33C2Zv$)YXPLd&Mjj&a{wWqZf>5i49%I5_8uJn+jK1SB z+Q1T1?fc`zVlDcvM?Bpp?n_)D^F0t!%bC%nmJ`RaWp?Q@gLIiCx=fL@Jv*X|KP7D+ z6Bu zD`|$y;>2b6kSverk7QN?%;LISX$Cf9P^;KF{;b=)5DBN8Np;tK)BUDibO!3 zf*`Bf)krAw&$$BE_f|+bMxopN(0d|ukz(ib-5|{_rtMU?FaXkMFg{VJe3+4 z(6=K|Ze)&ftlYqyMRv6B1C6QL59lj{7p@glZYB}Z#q#00H2-3Aw}F|ip_>faP}tbH znXCW^M`Ohp*xq!3a-)bgLatk^L<~AK9k@tD`I$~E$qxo8Ltz4pakU=0@n5oFQO;&ml8x74l|y0z=(%REdzeG}5dWP?K*!{h>kz<*eKMY*Q9YpKXe#^vNChn>g_*HXE;w{R5_ zvelux+*U>Ia$6PID}GRhRp*(Drph`j`Wd{_k=Z(Y1h7cy=lr-LJxqi(>SV+uogLf= z^<{89sIiKl+dN}r22Ei-Z23K8KOVFnkJ*o3+K*q@k4O0dx#m;0xrBT*-)X zB_GO_Y$#W9pvWksv;`jdEc2o3cI;wfoO#Rl9iYM!uV9rbr{|-L=O~xihKv5aBeBu2R+;qm4 zE?4<2m4p|p=Q=7+^BetTj&ezzj3ibq7=oR zGdPY%mBhNXdSXj3?j`JGm%ATyP=Xy`o99RV<%PvzosXXNp|Qs`3=0p?sf4+O>Y-7OC19In|)Szq5eo>0MMiBk^k5_oy)+x6OIM zTU$&IGk5+*JH_~$N2OuYISsnnPU%3jt9=GxJuQ9~ph#@B{dkTaPMv>m(emNY&s((n zc)?;`wjY1AAAjHn``)zNE^!9SA``v{Y^HBoX5=+|)Y_pX*la%@57y!fBb0LZk_MAL z`v7RjXQd38;J*3kVWyXT43o<~hUsG;!vwL9VS4%@WO89yGZ853mt0v})zY9+)(op1 zaAVX1QDEZBm_-v9wP*t4Mh|43FM#Qqk@l4fw$v1%+EDFq=#evcp|d<;;BcBit2mTl z-xTM`ZRkXKjZ=D<;`M2eXQE;>lrbKnwCTa!TEuvYQcy*a;l|d%fiI&X$ouzXQ}xLYAkV}vti6UGDWwxf?2V^={|6V z4}8`K&h&xL`M_BO5CtQrD^Y?w-MYJL8NX;6sYTOBrV0sJa8KJKki|YY@2!XQ`sUzi zrS&k&=~*9hkZifKgth3|TS2s8@Djw6k*$n+OUe3D26DKsufv8rV4wl7RLN<=bY^50 zH$S4eZ0v8zjLfq@UqLT{>CKF6;Nv*K1a6|m^>36J>2yF>W@Lr~z^=~%WY@|$px!3a zD|0KZiKDbrt?Q-A>;o`2+ZK7jgY0tGDyP#^|7aBnTNO{>;p>jLdYCwWS#jak*r&a^Bd_OFaCQUd zRC394D(j~n_>=C1M6-I$Y|2>j`W_zGBtqa_WpLweGS18%Cbarans*kuh~9`ka+6^%@_yqpz_#dz88*`P7hF z%e33emYP>j&1>?0ZmXHM(YxG>7soVR)zs!ks9u}msB}@aXQWG&$@)>&50r)OG5I{p z;#QpCj~7G#n8hG6&kPjtTBJ7(7U1i76z`vpM3{G_5-wAF!$G$uB4}za{zW*e+`&{eGVSR9z z179cj$ZYHT2Eof+*f$B@=0HwcgZmu#Ho+$x$cbt2X9vDR@I42?&o<2kf}1+9NN`^VHV_OQ*huht2R0GB+kwplf9}8*fV`WNLkvtUs0X84@hSEgxGF8W5jX=q4I5T(lUV{8X6(k~VFF}xo}GeSH=mLvndoo%eR<%Ju+k4omweLb z`LeU!kxf8fx}bMM{YYPh46|-5#)=95f{l?b)q9R#zI{wL!&sT&u?aZX%<`}&4Np$S#V{BjdYh4 zHpu)PFSxV$*19UV0&z@`hif8^71qOzCyt5qaFd8*B0b!N*N;14go*TU-K1k8J=}W4 zvBG+|&52{eJ=~7OvHkLJhY`mD@Nmrq#G9ZyigYX#kM2a`ScyH{r-@@F_HdUF$4czs zZYGYE*u&jR94oPhdxmN2XL&!9&Tge#1JHHr1`k6SG#7zQR9&VCw47{ zjm)Loi_`lP*;er^2HIS1r|T{MRY(yWgz(-icd&JFb`UN=Ear3b=yMDCTyZ&J8Cuvp`}LXkNKo7=>kgZn>LBIS1Q z#^jp!=p);WPFBow^n&J|I6d324L(kTJo*>5Wur3=yf-6w;(m+_<&v}3;C94;o9Pb5 zII$g0wH&Sb9HmF1vk2>Y0KXA46I+1%@A; z>MeZZea{Aj%fw&v7a^XTLmrg|3Sl!p3tr`6=Ag~$B@REs^bTM0*hY&FG^i=htzpZXXaRVA$N<1oO zjV#-7n*@stl}>7+sh4IgS?io%P}KuWK%18K!pu^eM&Dj+vk7R&H9TaSZZipqac{5? z)u8ODH+XN4MD<^ZtFT5%RxF&Ks=NeS_#~=*W&x|Kw4m*}e%{`sP;7qnH_L#Y zqXF~+0Woy-&DNxuI|X>WwYeC)?zFpD)J36YXLjO^m>NcsH*gEr`x<3!{R`C9VfHNx zU!p8C_9m>x=(DK<*DcMz7~Yze23_`MZJ?>1`7&H%r6_ce@bag$%qZUsK!Y#Zc2}d-PXXzu6p8!@aI_ns6H?SU2`h&R4c1x*(22L$)f@bZRleUqt=aCLYTzjg5J4 z7&9|p*@7GkI`kgrf!BuQrFE~;#sLk_0X44fYzp2%F;l_kE^y)pyIZc2msAyI{nlm($Zl>AG49J<|tr4L#Fm z74eLp_%<}ja7hEtd&*v2EdAmy5#Q^OTQ;ZN8cPD(H{44I)fT*kz-x5$EN2Eik?_pa zuuHJ9DdQeL5V*M&hAh|koLuN!)>J(aThD%vEcW!x+SPu}n6}PdWpZ(l& z)_V4Hi&-W6xz(&D`?>kRq>S8ZE5pm$U5H>q=2GfPao`+?6Zy-hil{4>P=2#O(b&<1 z#_J4t)wM}|n-%p6=GQY?ZB`4b}Pju*JHEK!MF=Qj?9gs;$j}%c!C1!ILkOxS1$8@UDPAD zOU-zS_OQw#(&h~W%%HoTx7fF(VbO)MOhQk4h66P(qJsto~lhF~)s{H{; z+;IMPr0mE$uiD8|IJobi25heAQBy zZaIMcWDMDJfC{xb6@#V$Y{p@}daAR~sr%jvxoE0A4Lt`8WKj`I7y>cKEX-}Zl*mXI zuPwkw#Ikr(lz2L?!qa?WdeT^9%OmZ4I<*Dl#Xrv`y$ynU=Iw0>Mn>rE2u4Qer3546 z_x1!M?+UB1ow2lyAV9ifx8ks$AP;Myw-uc103N` z@Lk|^@j(`)KI}a1i+k)r5B>x{k9&N?c_divNe}+4*o$AJTd_BOWG_?fe8lq@sc<4s z7yByCAlRI)TmU5>+=ZH+I8L-Habs+=hz=(1qw!@@pJbuoFLt(I%xf0SK>|rl|CHcS zP-w7|MiLB{XttMJGoq|6 zl{!i)u@kKPD`bRfiJLu%;*A>3)sA9qIW)B;FO6`6$Zu6q7hGRl-BBFnx=^uq==`)N zHGPpzy&u$Mpa>*6VqxgDqdrZV?4)AUb^{gr`y~`G{=_)*f+ES7zd*!kCjqd|HXVB^Us-jPi+1ebETM8_?% zyq=@FH!rl{b<=WsD%=lTst34RO7(11qTk|2A;$A0-pWCsRSGx44%V2bwKDrD} zm*J#cE2dsXW)u6s-8fwt>Y!ek_O0hUd2cV{k&6#v`y&}^ViPz~F&d`GqbZ|lCn`q6 z)b`DYBE&{ZqRNBgA>RskIE?To*~%U4JQA(k zAe!2>j?s87}pBvfFIVP|{p6m~8xH zIJd`~fn$5ji7*>%WLu5SWzew7UohF}%F!Sk;ZL%-j&UA|uJTytF%Z|4F2A>tgWT7?4E_te0pBlDjGWEb!z??m3 z*vsf^^4wBz4p>Z1ZeB#s$vv&11lcM*Ojoki@qUKyGZ0?RY)XsEQ&@tbSR!%IvO~1? zX3&*HfAvR-31bkt$+|SS7J)%d?5~axi8+}`NBu$G3_FIEZV+j7l}_$+UM?6@=>#(b z49=w4N+UtCDpn!R>9>%fe$b!Ur=JLf06i7(UKJyy)D@CST_M>T`}jW@nbA$k?9{c$ z?E5zP=46yydUg_GGyOb{*_s{Z;mN>ycjJ-BJ6B2&eW4H`v*HU^U~Ll?pj_A{Si6%F zxfXzwu@-=ccnd&2X}JTNMI5{YE!Dj&r+{>XKgl-Wlg=a22Am3y(c1D?j4bIUW%-%4 z$P#H{o#jmH^1AU-Q&4!*E(^rRM>-NRPqE<%se-&-77inYI)dtJFeKG0=dPvH?l+BJADUIXgl1l){}iPV?201uUN_UGGn&>2(N^hUx{@TeglP8& zFRyR+1WjuvD?R~j1gX=m%^Kp2f(;;DWaX0tH%Skj3b%i{b|d|rX zgmgH#PWr)%B+F~QwCZS{HMqd3qdD8pX_Hs*D=2~JDi(RTR&Yo>U2Wwpa1ur#s88IJ zdvrr8bp`Z4E9br!Jj5cDH$=s#*O*JIohCc9H`Cmj3!X(v6jz;*9U3&)wh@-nDfjK-CcI*CXe+XskuwImT=4k6u=hv;`b?^=dE9}? z4CE@8K?Y{~=^f9037h8haR+aEY<7sZK`i)QJXh`6C|moR46N^xTtx=9bWnCVIke}v z-ozL0+Vd0hm4!5LXwvLWaPWQ!-u(!?y9?|m90V_e06Bz>OTz{qAC)LA6*`M zvqQ_6jjI#cDA;V|f)hvOlwH0Q;c5?nQ&V+YPvThVD)z6R!@8GA$r^+`p{(t^K&#ugmnod^izAbaza^d03 zQW_n2Z~@YW{yQs+9%3uTnK~Vd&Fm6AUR61X=1Y+o3gu8T*(J{8Zp(z8aw?>~X=2m+ zC0X*PVQeitrLAPbbigPH`>EyWTB&*_*ykE0Y!4>vkgVL>CYP(VH)r|_a<65zm~LyX zT#cI3-jbW*$MRc^rLFnAqs%~ba`2wuy4qTB9e|NQEFyWOB^FVuA5p7DG(H|t=53L4 zTk9whjbB4V2Qx>fWtY=!pU^fzZhmja%2AZ7HjQjzZknH~jZ9>1US!=%Qz+;zaN*VR zCbQipvs2n8u9`V@w9FpOG$eFpdu5+pPEToMSu{TykO)T889vLBWOAToLeF(!BGZ#Z zN`@>GH>h7rN>+@Ll1nWUdag@Kq$f+Hfm z!sJta!buxnCwC(UAf@k zucc1FW|HNEE0?40yC&=)L$TH2N?!K5yJuu{L;*YuGL2Rc!*Uyw6&yf@;j_2-MQK> z%vvKBoL9}9VzkO{lzVw!14rU>zL@Hu+%Q#5UHR2i%TkD!^SUj8f#`6@8ne>3fyz(1 zz*1VW=Vi#OBnRsu_Oz6mjDF?p)Kwg4>s+zN@bq|P^mM7p#V$y5zV!lVuj0HilHr7u z=z?BQTxybIsu?^Zx^K_)xm)MWKs%qox_6}s8Q0p7m+orLAZKM z1a7WQ$Ok(khZ;bZ(y_$;mqZL2cJxS)H0-(o#iDItYPZ?N{4bN5i~L^g^fc2{#WmI! zG#7iO&uc06jLhM2FTGS#Sm3<~VdBWNU`Jl< z5_CQhT95&Zi%|%AJ z^1O*2WT~%p)R5k1ky6+S&ZLtIKL^&;|ht(=`uXm%z)b4WvX-2o|y=NSO*OG9B&x8B#e>k~Ag*B2)&E zZI)-fj|RW%sQ0*bXy^@-Te`{I{(`wR*S>F{*S+w=EFRRB3pUdm< zm$G_Y0epJoC|r6JUWL#3*FivPwU=jUVcnN1_u+yV|I9UWVpi1mI*R7@@oNwm`xJ1? zoYcW<33dlJLMCgtWlrs-%~&bCBbm`9 z8@l95m;C6G4_)%5OMZ08k1qMq6@F|du(ws(iRd~jMj`D4dCU44Bg14;W04CL$=>Nb z72rq;u%4!aq3_zk*@)i-h{c;K4)CvynUSHL_?b zJf49@1%Dy#PU03jT+@5N-9_9IhpQ6z4dS+TxSff+o49Qp?ik{}N!+#$_u%O`_4h5} zwsW|1NOuo$OC9bj#C@B%?H%qR;=V)N4i5JU`F)qT9Ubm3r28ImJ2_nAXFzu^aXUL) z)BB+NK5@HP9A!9;=YmqVa{gEOJ)13@pvqUC!|MYlMgD^AK&$JfH!z$02|fy(w`=B0&f}1{$BoY8n7GGH&SNO< zaWg&mlibU5i}Ofq!}zlENNmHnl^&AZPsQW-3O)FfT>t&5^GK}!-sV`H9cT45dhjR7 z>UMhYC+YEZdhjRdAr(nH5|K-75|2dArAmoMBIi=C#N*O%`;UxQA3d@pvHaA@xr@9*KKMB@~Y*;vTGtz59%O zj5i_tfRg?!zkWz8w3+kZkBe4ge!;QGzsy*KZgMR03M*@R=vtCYNiU~c+W>cnmgEqq z&e4Qso%Xao%p`Dr!=nkSFb2_m^qB5eUPVMaJEVSJPusIfksrrU=OWw<2$$*K64wcX zn;6U*s~u)*I3u|FYmFnSl}66;Fe57?F}%|b0tL>+~ho)VfCav zBk8d+Hn8!j>9egJahKGrQ}r8tuF3Pb)QFM!NR6d&AIs$ge%;Tn6Z!Qce(heu*N^$N z1HT^N*AjmH1Yg0w?y-BQe@bxSw=MWHg3m$^w3YffgDoc02l=&xUk~A{H%O0AB{H>t zBbR#1BkzK)@Gj`ym(6s=>=qW=oUFrzzm_m^r&{#>qxgr9lkL~cOcjgdZ!-~cvm?eo z441_syRO?Stsa4X*JvHxq}KftY3mKz{%Z%g5F;6J2qUpUhesQilg5otNEjPf5Rn|0 zvya+3rGHv3kAO21O#&$w98>_IJ;@aFE;Tql!j zX_1%6tL*0`tvAM+qLMy_c6yW-&gU>2`o4>-g*TSfGBr38#RV&ExzdEZ$)d8@`U#<< zVtaomc-+Xm3J=rR&oh%mE-6S8WM_(#3u2ZrGsVn6Ls7PGw5*AK6&$o-vVLh4Zq>HKX0&OMx7%ttbUPD zzZg8|TrYLP$nWtrvMa~TZOC-cN8Izo?dEWciTfLIyF1)o#QmMP4?EnE#QlM|Jsj>d z;$9%`BM$ck;{HM0o(}gV;{Hk8UJiE;aqkkhx5GV5+-t<`<8Z$x?seim>Tqum_abr2 z9IiM6xPKG3uft6t?hWGhbGSLgy-D2u4!13FtBE_n;g%8i9B~IZ+_A*{iMT@@ZUu3F zChijscQJ9lBkov-yM?%Ch&$2YzDL|k#GT}Dj}i9@al;PxB5{8qZp7jKMciMB`?SL~ zuMga-#GT=A(}{bQxX(G(c&}*KvMU^HtXQS@N=WN7yb2e!^s_4x4 z7hJ$rT#k1=&7|@tIc1P?6pzG|K?+km5>p0|ws<6_45DJdq~43 z9=TM;mzA_`;?WZKkmgK0CdECZO%so)aSv&*#A9aMLs~8I=#P6y6%~(7;~r8^#be93 zhg4Sa*e>oNHTFf=Ojo?i3h@fR*sALK=H{p~H?hAQO67Q!XY5Tg?1L^82D@36gI&SZ zvlx5V4Wa&JQrm(L|IC7yb{D;ntscWl;DY6BZp96oRZ`(Yq4lkY1>~w=9Vl zQzSjisuR+}{RE5Xy0I88{N{{gWe#}_-v=@7j=^qje&x;5Xk~L`ca=FUOONx88h93u zOq&A;H5}+}Ts{$l4GGU986=9(T*)S8up7fswF`dGwK zR9VGBFa#+ep}Nox+Y}E0!5T^*y|!Prg!3}~41~eeRJwAtlupw7v;{WMWgKY5I`$9dqa++z3Y3**?m`Vt4S!Fp}rLAKZ%}&a`oR>v-TmE5=KsL&G0=C zQu2)m`eLf_S=1Tb3Lth0j>B(eT{AE8`FwRTN|)~iZ$-m9z%)`T8hJyKF{m<6p4(-P zqRFZUZ{w}}h;@l+OAqr`5||!l(#K9EWXs4YV-nM4M0CkV$~$@|JUVyAKq7O75}7lY zn^`lgZ*hkn66MVk+`^Y#_qv60>L1suyPzAZOOiwQN*9EVEeuoPjO+_;YJizss7(V# zZsscEnyA)swg}9K{#&D2Wb9bJDYfe|wZm|p&vDC_B^@${Wut+?4PSiJV~1NHqND{x zjUPp%$1W#gZ-$x5aB)g&aRdClT1;~2lwcuhf)u&HNCWSYU^~V+rJZLfm6I?@;}5wI zGdP!1Vws5L2dA(-`i|9OIl>w?O2qozgzp>-FXlj1Ge9Ztbn2ssdq2uS+Q9g^8guSg z6_@N(%ywN+@no%d9L_TZ$p!XMB0)uFKzWXwttY7!z$p(>@NhO=Me>c(=BU zI>_)&FcN-}n>8yVD-zx$-iv2-3#Lh+o3t$Lz(O9Y_tQ4eo$)s^VmC9&{tII#6R|`a zYxD7pE1cMXWFm{YR)UFOo|rz^<7fbEnPNcGsK{v;gEFtC za{C8uMx>H1aWZe3Af#vSq$w%MGvpY>yRKFnE|D^6S7|5rzx4mI|D+W>Y<6Y;NjM%D z_l9;R6aw8$qFyMhqA4u8$->%gEro?jMV8bu#w3g6QSQ`Jaz|g&Wo!s*N~M|dI@%r? z*6WpwDEDHVNLPbN7u{sK_FhZ6@bc#b#2`iLrKi>kB-10lCOwF%(L_Zz8P$GkiAvg& z4?bR#ZfF9e;>t@_xZ5brqiv%S3V|Mv55+4Q%Bh!PQ49px#tD_vFU3m<(8J^y?{HOG zSOCa+v&lN$WY!N`OV+8U2O$6avq?6aK+XaCaH5wzu{-H@sCJ-DSBpwwdf})BhaJpH zdZasxZ3*&yr)91j&EOCVgWVx{rE^DD!Xn}mV#t8;av(`h9Y~Uv(h&-@|2Y|B#oIfE z5=k+bF@B2uP=iT&q)+Vk4+D~;wD++#kZqWEMtGWQf5Im&gP= zm9fvbathJdvm=bQjtCV2-6S`ssAQ*#iqQ_aaI`~LksVwkA}5TT#2uMTzqh$vxr}58 z4v$L{+pCa_j}C(DCuS zKSU4>i4pdEg3BGafZ#O_TuAU|4&0pJ8xGup;Pi}TwI#t_9LNqQIK_dB30~vCB?N!$ zz-rd$f-`b9qFo8@ z;=tVqp5(yY30~*G4- zzILhvL99vTM^ zCwPtat49*YpCGE2M>$+nFOPP(s9ql9a8VsR*5RT$c$~vU_2_uw zB3`S2^%xg1>R# zDFj;^Evr)rZtcKff+sofG=jG}FeJFzfg=PbHd$7mBDmCnpC)*c15YP-s{=nnaJ2)^ zAlT7tS$&q^P7XYi;0gzRj^H;PcoxA|9C$Xt=`EJk=LzoPz;g(m>%emf-tWMb1pnp0 z^9asrwXDu3c(?<=aUEy$VIoy@RQ8+8E;uqzz;%a_TBrC4r7qh?OTKok6K``w( zuO~QXf(2I*Jlugd61>8JHxYcyfj1M(x7o0_5ZuCnUnUqh@K%DiIPj|kf8)U02o@(= zR$n7H-+{LiJko(*CwQ3y?*PdA9V>Uiz*YGIhaPs1PrDhT(6uJgi%5X&F?f;wLVlWE zUsz4J3yjj)fCrds0eI=W?oMKJ;WrqI%posJrBYv6gYD6?B+B3h*5DUcd*bFv9q}_M3A{{2nHXcXEL*cBZMeq1E{Vh_lfgNCC%KHPXp2>L&@Dsk=NAwZkIfl1pKhOHcIK|G+ zNZsI-Arp3XqU!DJ-%ngJdNs>t3?pZBLk%}sh2I2IZ;JE~ad!ikHl{l9*Ksh8Ys#Na z-*q%!9%y%#w8b=X3cW*#Wmwo*!+Au6ITC$1N1|)zNRU2xsPs?5wk??haZQr)i#ED7 zn&AGNhJ4HbKQgY9n;rFAEAb?w-*l5>@Gq@JfUAu8V&pqu9DWNw>5=d9^B(#99zVa0 zAG;q86-0;(K~6o9AKFzehkK-}6X~__#2V+}_S`4c*PZ*^j(a9xYLXR?>2UG-0q*WB z(iXh;s+eU zk`uLAO>riBu3FO5(_&>S0<5FPgpP5>s2TYPWn4!Mqc=T0YO@l>xz9F8C;vwN$nen| zmt{C2vkZReyqASJ(ubeY%Hyz_B(0hHFy5%e{0bGtwwNWOQFLvKc^e5Rr*vXX9V&4z zSYev`eLA`D2XtU(_@TJD@IG<#;r-$k!XJrS41Ww))*oNc?AUc$Z^=>Wkv5)9!J)2H zW9Ap3(Il9uxMu^EB-v$1R$!%;B;tV+9=i0XCB>QUBw8|%nUX|Helk;&Xvtn?S`sa} zg~Cx*R6M4PE|Wr+anogFbjh7A+0m8ol)u{aD6*agv$-*9fGb}^F{vGOW@ox`djw5R zSH2!WUAWIbg1WJA7(r9gmAfKnYP#}`2%46z-0dJ;saKs?V#|;=y0Xz9y2%#lI|x2j zll}|aBK;g04;^$B0^I?Ae3_solr$TRNb4mwI6e6ZGllm6Y>9=>*l16R81Y}az*n+K~B9I+j@}Wy% z(A7+`9eWV**R^8|Hp1N2wKZtR=;^gJ%3Q==M~!xjF~_Kxgb|iZ9W~l9dd8@k&=D41 z9W{>V(bJ=L>+fuL79(H$OSXH=m9~5Q($;{CS#v!;JYW=IRG7)p-RX5WliWMR0NrI3i&n8EHL_-eGSFc~#9wyy9XtEoGZ96kmf5Y#W} z)N9 zzP>e|t~KLgrnb5nPuI#e6eha5QE(G_mC;YvrVE-9=1No2e-;adDW=O5(q)S1l0RKC zrYq85eLTmMpB*nN3$bdFhm4oW#EqB9DKTCq%BvR|i%)}k$%s7~H&W50SQQXsF-*5z zrje$bY@{DwOC$Z7RsHZu#0kZCicT*44IL=PZ^g}pPm7xmeQ!OLlY_9bFX{+JHg|m(fTY5a|$^i4KvQ=n&aB z9U^aEKhRbXolca4z8Hgf~gVYlSb@hZnJuPRSN>sg;JLA&plwzISry04^P0Ibvb(Z_z5jEug z4?4NfjMZ$4cSrK&EB-8oq%~w`^KgpG+K^QEXPR zGL}CSvWV@W=Cx5mmXRBz8uMaF;1OL8&*@^Mohel|r`WKCb_4yGPZ#5*Z2A^JXon;j`RX zVaCVoF(SH*0d=MzXNT+NCZtZ(L881msGNKn^hRVJqhTyQ4Q3xic{H*4gzE>^f6|`K zc?aade8@q&g?9mt2rh9V=ikmFaU$nG&LeRm=RN0*bmkV_U$@B0b4oQw)KIGK4T)tZD;A%TR4P)JDO2n@N5E+0}k z`H(arAEL&PPxl(+GtShRemG>k9@|>lu<%f&>@%YaImpe_Xy54$&Y&Djo8PN#eq&x5 zPCO!zFIQ&rMuPQ81Yi}~Qxl@X-Ky#lL{)6xH{B8POxVHGR2G$~vJ8c^gPDGT*b32_}*g*&5y-DKc!V+Ap;Ehax ziyeHec-WhqX%o5^Y5JNQ7nB;!E;c*a-7I#1yuT}54%^5SH;u^vfbsGy8BUoAcS%UK z$pdg*37&s$6f?Fcq9)SAF6Xt;nr0s>#{-xHt-?rdyyhi+1L7Oh2hu)$ydiIWZKV#Zk_tYuCXbe&Lk5uw!SPSwbSZc-+@uSF)h;bAvYnfp9B#QWl6^gps% z$|m`*6p=tsrEa7SBAP-c7fywvZDA%li)hZD8o9}IQ$pFnpUn2jKqblZJk}7P%>g%T zC~L@(K`BoL;4Gi6o8?FG7vx^qxJ{fvr6x0s6F9>pl#!@2%O0 zuP1IUoBl+fEXDQ=dNb`*ErL zk$(OKjU{U+*ciUSRu1eX7&x#>@FoZL5q#Q#{RCUfmenkPy{C@Mh7-Hua}E&Zsvb#4 zI`x(&!`$o10D3DADTA-RiQ6rCuSVrqXlif|L0(Ux;ExIN4h;n#BgiWp6nust?`Kf( zPXu{6iGptseEddEwRRkH(HSLvW3Za+_!u^U_{r}zyn#P~P3 z=(zt|_70b=T_Mc{;Rt_%Xy5NV;`rm8KNxVhh;9>yi}nX^>TuEdgUuW++UNWshl}<( z&v&?JpYsBTi_RY`bhv2m^X3j0?S0Mb{+|H+BVK3VNToDc_hqf+c}SfIc=%)I1=US-Ac2)^GMt=!}|<1gNZw4 zc61(zJ7#ur9*H|78^EfN+v4`{c zLfqpc&f~JU$DYpPy12()^bjpc6z$&h;7?LZ_HiBwE%~VPNU&PwJQ7;6FFbfUcdqHk z=_Wh!lcXcR1*z6+WP)-!b%MMfq{H9`-DG_~aC0kNa@)oJf-wgo<-z3x7$f>S($Xab zUBQ1RJv7gxgl;k=r%6gCcG>~>h>cnIAb*UJnZteulI{O+mUTG|N1<@C19aKeD*Uip zl|J5XRbc27ZNu{pfeWn+)JS6NCdlD{$syfj4o{aH9_wTpq;ewf2WZTjI37q!(NcetpnKEmOm_VyDF7oGPy61ZTsOYTu5;ZKmC zY|j`R?QpFQcZ|c89PU_$o9=MOIb5&99q(|PI9v_5U<-gNPM|M;f`b9dvqvh)6Nw8* z5|D&H!NqZsLE@r&VV4uU4kXH8h{5>d6~#%!@h7++bb6-ZWQTju;ZAY5Upd?-9qws| zJC&?n0H{$8lY~D(YnNqzn!}YGE_AqQ4maX(6^Hv2a58Smr0`@{>NJj9=C_ zr3w#)5LMXk~?ri+H&m1*QcW}Q<1mgD)+%a)4Mb{0QCBLocb>f&P`x+>~aWn zLKAX65W!*xeu4h9cRX+Is}IJ#Vb|p@AV2;jJBthH!JnkZMJ`05v$&WZ{7Htm#Cat8 zoi93%1glG(N21@k%y}e6Czm^q#OUM-=aCqlT1ebdz1y6_SWG z@0mf}Vn4hyboO>F_?|bjpwF9NtquPKwc*>xu=02et5mI+ zU2NvXkeiUH>Lw0%4-y;ROx*O~o$XRShh+3-JKHGnel!+ud7$I}5_`T z*Bw0idfIH}tb7~iW3c=jz{r6iMeWsh#=j+!^ zpO29q$l5A(`q4n*iuZwKHEx4-;L` z6|l>x76oXCK@0V~r+N93T7P*lIS-skp@mJc6*g> zU5h@L>5uM<>YUpx?aJu8q+sBbd_%_KqS2)*4H)Yt+g*-(?h=_huf}{o6rNy77AL*g z#v~aJ^o$k;EA2KAR}k!-zN6B1MzpkvTiRHiS!85HH>xvwq23*9k&)yrlR5-0|NIHe zr9j3qdgPJ{ z2}HyMNTI+r(V8g9WnxECvA~ne-$R?&3%A(Fj&3A7y{_}yYmuEZ!GDW{q9a?PFa3tA zGatAw?K>*osma^yt?0z~5q=$6scv5wlGnG}(I;`*F2ec$xO)>gxr%Ckywm&iESa00 zbWhe9ki=$svdoN2*dYP}Pk{#rGm#|(WQW0?1d;KMd8nvJ*fOBTBxqFhv8jNF2&ilV zA_hc2#IT5fpn#~j!~gp|r*7T4cY0>x`|JDvf4_V(T~(*5PMtbcb?Vfqx>f!+7}cFq zB?K8n3-PZdv?LQ%EbE? z{zC-pN5nvvDCi0f)UdcXpV$O<%v{Zk-P>9nh6j%bQt9PrIf~lVgs?5c(jsYCZ1>y{ z8@?JN$VpRmzS2c3>2qQuXFwx!0F^}EEk-xqr|#XlK6MRb*A1~gHEV0Sl8Tb~&TtRb z0sBSH^57N$YWCxNvV{FWSp(dQD%ZgnsNg@e(Q#qP(>vXieWI%0hjbK{H9{#YIZtW# z{Sl)eENPXK404M6enPiiSfYv(pq4Xt6x{UqTY=gYmOw4}o89~>EXRoP@UmQ=jDwL? z))?fH-aIQoGJ_`pSaUvaN`j!D%isUCCB4{yWg?6|k$?C%261|MHs4n~)u-)py1RGtLi}OGiwyCKLiwsE1 z*^nCr7fD}`E3J{6Uh#sIMNNQ3O|U`DBYXjBo0_OWE2}E{;Nuu${5Qh6AwFMx3owm{ zS#fT(5iu*yw{1ksigS~Vh*@#IVO7y#6tWm#64{8d3jyk6_I_!encmCrif+iO%FEqY7C%K^#nzrr zPQt&Fcs%W*Kf(yC2h#LZ^cLo#{>HMu?aa0Oas?*<@D^1cPD$#?Yh~3#tY#GS6N2QE zm->s@(75;@Q|^zug7or?Jun4UgmP>{F|Io~9XDJOUw5*E%P-}^R2pmP+4$B*M}u6sV-Uc)AcWy#UY45eqMZpZizZr1iD~-oarhm)$ukX83p0h} zfgEPL$Xa|I*LKe6{0xcz4gk+>AGl|5{BdAECyYN1>~RYV_ep*RMjjSt?SuhAEmEq@}_;vAf%27^W;n&wY+W%jfcHu>393ZOWJXoAR2m_ld5@g0T@+G6G5YBo#|UB1&RBNnv>H&sejJVqMHuwsIkK5qy7|hQNh<%m8B{ukb2AA959~eB>2LH(5jW+li zgHPMwpBQZI2r&Pd!KF6%7Y0|_;9nWM+y?)~;G;J9cLtj}162QDaCaMgoxznh_y&Vt zwLx6Hhx*#!6b4(j2~Z^&+|veA44!F&X$CjhV1~iXHkd_lgg=fj%pury#Yi4bD0&EwZEd^k%jP$T+Mg)n$TZspW)fiF2b$~V6^GF z@29sSou1R2sy$qEZu=G=3mN|{zOWvVlboa{dXke6_arChx7LgfhFa^k0lOPXDt~H= z0n@V(O%}GZg*98)J{HzuVTV}QR0~^fVXYQ+iiLR=cBX|*v#^g^Skb~Rw6Hb{TW?|0 zE$pinR$I>=3+uM9Z7ghwg>7qLds^6=EbKrF+s?ucx3GB@c9Mn7x3DuU?9CQ-j)he$ z>{1Kc-oiFom<$W)YnNMC)uy}F!n!T&RtsBTVfR{?3_V6T!+jCc2`tpP7F$?&_i&Ge zJ!bLsTG%`69mRbX_Owm+77Kg9!rp3OuUgpKEbOlq_I3+PZWr`}B^Fk&upKOHnuYCX zVY4l4C&GlM^DS&=3tMPmyI9zc7PhN}?QLPZS=d1qwmV_`an5@0Vbd+M>6Tj93JZIO zg`H$!dswaE|Lc6vJ80sIFg^1-G@@}{{hih{^8i22p&VXLEY6Ppg?v7-(as;)k zcYy}G%8_q)-%YBl`V@kK{qB^{6xhUIR(o%Vfui7&Bi2)%;@Q?%N}PPU_Z`YB^{zUIdTYy@&6M))miR`SYnmYH6tU(MuoP& zgAlKr&i3$QE?M(wel9MR=LaJZF1c>Q2g3_k4gLJnc-!z$jCjpqg4BWtVL5CV3K40a zWnrgKu%Q(Q<*J0>iri@ophWVl&MNR|%>Ey{K4~q4_jpMfpt?CPU`})oDG>8Qz3-!m!;V`f zjI=QUkv0b3$H?77Oqh)MWdxgzvOZKRFn-$9ec>J)*KpqzTr5?D+c15K^}Fu&8^H$a_OR3k zSZ!*-TEFdBvTZwNh_oF6leXP^Oxw|ox7|`{yW0O++YNx=aoTPfqT+4G8sQJ1lMVQn|gxwXUVqx znIY2t1dMI}*tx&MO#9P~xBr3C{=2?8kh^lS+c~_CHs_nle53m>p-zQ;Br%}M#-%wS zUoAO@VzeE(qllw2P&Nt+o(?Xh!>jPv8p4GFg~y@19Dxd8aOyejS&40T(Yxu)p>l34 zZUk+?4ym@T$wOPwKJ1kEXZmF%d1^Dar79r3P{D!y?1I+XjliV{jp?^?Ce$$bf?O#H z`p9k4M_7=yCNWtVL9l5})-aI7HnlkjA!NQbau)7~TYV(5j11Fv6n!6}Z#jKFeMi$b zLf;DdK1|;+@YT8BX~484-FRE}OIvPRnV>B>CyylbU&u4JE%^q6qb2=wBU{gI9t+az zk8D|+`tM`a^j8{{UxMBsr|LB>sB3AX8Z$Giij!1VnfYK8>OCy@pP@aXOC^IuG1_f`0rzmr0v| zd=i;-0+}$OOu8Ag1^rZIlF3tFCWXmUo8N~DM`Y5&z*UnJV`Y+HrA%519^Ni8i6E6p z#6+1ihu=UDtTIVOFRK{{CYz=Ar=sQy);eu+xM>r*@isX{+GKv$zokt+Jb9a(IBA<) z2->1;V)E2)lQ4N|^HK3O`6_VLB*oY^5v)O*+#zj3khTdi*)}2AZ4*{AXcL>ow25G? z(2YZJ8zZ?OzQWPm!jF4h+!g;ZC`zTu?whv0Pd=SlZyhNZ5r_K8)nY z=5+OI33E0kBYS1EcLZPTCyNuId1~`=Fe@SpZw9WKq!>Hy1Zyzv?v!ySbs}Td5R)Bu1iRyowF}0b z&BAeq4#cp0&Q(X(;$Y@EbGq?1T`g@ot$V^YO(rhGxVwwAih+mftULC!IgT@WGruL6 zCuipx&Bvtn;{={A!zXD8UItzHAd2=+1Km};l~B2ap+Sb`BJ?eWPG_jKb}Wc5Xf`Zw7ZS5Eiyy*HsRL* zuu2gm-PAd3tr*>eI2*@8#wKKuZbF=_At;{Eenv1>2VtjS`_hfK@5iNmKQmGL{+o5_ z%73#h-;XKvQ5U;xQCrR=7VMluvvz-gW%;0HJSr>|TNA*a4_xstx~xV~9M-x~VPpznP8 zK0x0E^qooHh4g)pzKiHPi@uBL8>Vk9eIKH4ls=!nOXwS+?^60c3|}p|0DO>Ba;p(E zaw%eKUSawH-FQE^Q2N0V;22kSvmcz2(0?KO-EmG4)&5V)ZoSO)zd&*@zY44MYS6P; z6Wi9=p=bCUBnGP_PYpp6Nmq+@vS^N4XThhd~lfQ)s{7)JPWjNuR6IDak` z{#-l}e|SIsDA@DwnA<)AvXjI79d~Z~94P#s&TY@I@0iosI=8W?ap$%$d1^CW1BlLT ziG>iyl49)KCRl^H?FTZqNu6YFBPKhy5$w)wtX(j-*(_#mV|cu|jYui`=*HW0y|igR z=$)|7{~PTHl?ygzNK}Nuqpa;s~D!7|Z8-APD@iHdQiL;f75$Dy z@lO{7UtpNsd;b$CCHUl)N}8KuOyp9x(w}6`k>oI4u07}uUe5xZT{_$iABq2_5I4+H zbu;d?Sb{S!VPJ1h$QrA+9B=APH{PedLDaQFLE?mC_unvI$u}_PqDs;Eiml>*3SgXT zwn}jH8jUjvBC;`xh%p~=sT_O>Bt36fbym1YCO2;*oKG!Cx_ z7hW-90$!bHcttnPt6POv%R$0~ee&PMtMfs5lvfw9vQBrr!E%SZdf0}*E6HqlCE36u z!pJKLJG>%Xc*Tebcy*HD72P!U>?Pb_dDRTxnR^iRU629a6`hWS=sqERiaFTh7b`BoE@pS5-)14^ z`gVC1ZkKCJAF<055V2yNC9~+srZ>{XjLFs=`Vkw3HufW?BocRzYrmj+x)mp@%m(3- zSHXyhZ5=%M&8@~O@VDeT{N%)h)!ZQ3eKAuldznFeD^Xa<8@NO-s4U0fmH7b6F5{D1 zrhA!Hi(tGjX1#28&cjhg*=cO>FGlsNHAPrVd%9q#kCJcpO^(t_`)i4lc9&bgFn^Sx z*4o4DX0rT~Zl-pZ9aFgFVM|-K4sGZri9OIsc&+8f@wXlRuEgJT{P7>*kKvE-*YW4V z_e402HrfY&ZSeD7a8eBFh}jNj)tv!Is$t6^xh$J@y0avwwGnssr8Zw4A3mjC)ax|- znBkLhhL4tjK{p4zC^G0qnCr1Hmpka9gal*n9vLgds3U}g*!2&=?g(M)2kRf3B^-O) zH@R2vu`I<$5-mQ0U3_f%0H4i5e318sVYG351bD~TxLAJdE&+lf<}KoM)4;q%P$Uh@ ziCMM?lJ4##4DBtkHODDtMAMDWFF)nTuKfn>G2vW%Nwh!2?3QBB_#f;UNDWkhxdau8 zOv<9F``O-nh> zoe_eTxW?)rTZ^kNv-$LR-@gzg*Ir}sI^krT5hF#SQ;{HcF4mlLVOsVoekgsGX>`7z zPkS#5Vegfj&j{AuTX_ZS2s*A!Ew?>zuHYQR{u=Iouv}4ASg>%|&K%4hS%e8bu1O-( z2l;hCz;P;5irR7C~iGzGMo6k{Z<$ zFkA^!UT5w5sTJILUzHCh3!1Kbjy2H2#Lk@h?6_EHtFvq*^_Bu1fBS+9-PF5Nk?)6?{g@ndVmeCXK?r2;IGh|aOX>dz9PE; z(Bv?GySGzUI{WONQmq1J>BpzFZ}R;|xnjV~si(VhDyDSI_h@&{_ng;sPop{U=`RI$ zKPKhQNkn0e;(dvk?zW66sL?4Xh4(eiiGLX& z;TH?OB+$mwC@`HcGGCH9YdFG1EHWaxhD%cs!@t_A?2@vh@OdN};ZLpQZIN43zQ73n z#6RBMWFul9?|zXHf@*dY)t4B-pE#;7GlD8b7yfk{5zFxn8xbq~S{o57 z{5l&EyH)6VL_qJDl2`_vTl&Ki@J#!|4T!59VuRmeux5idGI*g4ew)E-ZSW=rH{0NM z7<|bFZ)ULY_Mo6!7@Ti|w=%eo4StuwlWg#N44!9$-)Ha|8@!Fd%{F*DgRk1)9SpWD z3Cg{b!MEAqT@3cy;13u)%LeaeaJ>!Q!{DtpcyBcJhtb&k5QGq??(zc0-H$LA={L1inIT{mub!hh2u$zZs9ha+Yy0h9znH=-C@;e6LxU7!i(v!*FBHRGrp1h~Z`6Jdilo7$y%e_n z9)Pjh4(uqxnAw3{LKs`YfjvMN>!L9BH9;>0IWXz`EXaY$2w*`DOojmqa$qtRSdhTF z_Lg2}zLdW(i5Yk42+MW9l-G6pn<0R%fL<@vG#TX5wrsa2NV56?z94A>Vxn=~wB;+L5gCr!a{-4mFQU7N8 zTTH|Q5-``~J|uyxCi^J@A|FzT^CI|l^*DQBYwebwC0#pA@Ciai?(C7`gL^&%B1A;a zs|hLQ**?uB^q80a6e2{WX6a=vdPx!~!`1_H5B`|@?FcZV4X|ttK)$KHkyV(zhDNP< z(C(N(ln~M+H~SLVrg^v|lZZCWn~4jf#4`)`B+(#2VnNwB5KZ;oO{S3a&L>kK1Ug&5l zdam6w@Ex$1@eNH!jaENq6W{#KAj$Uo1-`oipC@mWKz=1U#3>Xmz41<@Yn8Eu2 zHs{h2;u{3h3PbP27Y=Gmw=#i|ahX6AMkWXf%7j?Dup707-H3^)|4XLiD!G3&(Em&0 zccc4tp#{UsFG!3T)?4T|`{9Z6AR;*+M1%6C9;V@K*S2Qt{%D1AXOlN z{9hr!iz+{87)&?L;CaH}72s3-CG3uvwz%R;S1L>y-f$o#rL_!HkwI2f%5;{MgiGxh zVLHJzQOflS>1PoiP_8q~b3_U9&oboGjg#L!3HkQ+_61A``dAEILGI8=xX{T6LuY7^ zdft#SY)GLSC#821QuYsOt>>u(CG~`K;;ra?_lw<=K(%&$2qjo+C&9+rxdf%CwKIGE ziG5E?oeV0Ow+;ucA6xsCo%(l2HviW^Q!Z5BWzVEaq^mX| zBpaSDBE+X#%_AGV-{;+RB#%;&u5%~Y+sT8rP>zz?u6woOJYMoqcRZ3Dj7OH{Sfp7T z8;G%V87<>9o!AX^+bn!yBDD|GryOJNwj3kaaBMzkh;R&i$|QCKE{?dOx8z`sEj+oks*XMYRG7}-%-!KjxIMb7$$vveKK?nX|w zb?u7b$X7hlnaWZ^!sE(PSXxSebQ5gg9--tS3#Kb@xBg*GnN(`Zv;iYR_AqCx{yg8f z6nYDe(wTGKnC@Oix?tb4fXKRL(*n59EzdIkw#VO2_}dSEN8;~P{Nd%X#HaAL4u4C& zgr8(i-`?wgbO^}|F$8+GNQWcVKlJDK)b zN%BVq4`-5N@M)v}8iVg<@I7F+|0f1_wZT6#xQIb{wfZj%cGiX;Po5sX@P6!m=!>so zKh)Fx(8y8r#2&LzQIO63%-LWaOf7mS9bxp05z{m1Iz8i^+B0yoCV3O;0&H&bhAufm zmwcehy3=K)>9V$TSxvgEA6-_AE*rIrQvv&)|5t>PJOknX&HDasegCk&*RAgj>r1o; zd8b%k()v>3WB>}Wukq)J%DL51VH>x`CkcY7)rG?_HiyQSC*(0LmS zG`C&&X=LWqw%Rl)-S~WRG!fRupP6IxiJ%PU6P1$$){%|`k0%|`+6EQmWHRw-v;+Uu zF2h4uYU|4&_XvM#;oYZM!uaFd^_3%xKMpK!Vd4E>4Hg#OxmvKW@Xpmnq8jB4A|~i2 z1Sv4jHaw*342u(WSa2=ZuT`+?aPtn-g?#nC8CX;zmwOf&Jx}g`T2Qp2Tb{URVQ~S- z*HD|fp;AO_xb{h6W6-oh!Cof9R_-uUwY{E#Oc$?|$}o}$0-2|JKznqaV)n7(nK@$O zS=w*kpzfh%CcJ=1hns4&9-Z0;_xwj{3t+JeBnb zgRKmrT69?re3wQqv;=VxBN@&$VC+^hT6Zfsuo)|(Mb<$)2ZG~=W?m^3s=oWEd$<9| zS%fk(*nnFE|E~C}?km!n(}4%X)g5hc8iQ3U%8Lxnwy|vtVt=kw*@T(GpN=4vA5*w+ zZO0mR--0`hDu+-amXM5nQCJ7Hh_2HY&kXuvQoc6ncSg7D^F`#57Lg z_@o1=;*$;}C)r}kLqB0^_7^6n%k*@ao-WhV75bzy_or^Vewe)VZl^R z*_UySE~9L=l?k>ZsQaS}?hYo{rhIocAMA_<`=UGVtJA9PSbmJ%9c@N8*s=Tw8w`A7 z33Q2?Hin$+qsI9s$=p3i^k(LCs;Ma4G~hC^I%$kviD}^+LlWI!sA52J$PRsyq)jj_ zfRB`UgLE-0jP5Pp)fKXKznUt>w)m?q#D?#4)QM`q>(6FAv1riFZ$qcDk4vcTh4i(@ z*@I(S`jXtvQI%6H3Y-aNoXR6yNN<92h&@^EYA3%Xv($s>YwXR#} z)0ue<(A#wj!DeQ@8AU|aEzW9{xJ6fI`f*m(axA?A@wRp%syYE-?LJ{@M>k%(OD3tE zT~%Mog!?B){s5$@p(aS%bmg;zg_EC9koKimaD_p(u!Nl&5iT`igsBnxF;@U#KSpQe zBMSa}BuWnR7kxUI0{E@y0{n3?!D$bVSD`11`u=95lzx3Lx?!jySH1^YeH$4~`sqj< zCrC|MdjY-OImNPfQU?&<*BaX3R=%usrZgWaO@di@DRF;vVyy>*+LbwWGl@@v_y?^l z#Pxi7eV$I)`=zpY2(%z}caaEYF6{2CbsmB@8-zjOYX;8x1GlovNWY2|tDsEx0zbXh zZ+nHRQD3@C7fD8UIoEU-y7BJvMOJzOkz~8emn5Opf`x{(FNJ1&DGAIWluRO+uE0h1 zE(c%6$X+IQpIC%VSgzNx4mb2OdK*;e=8IIG9bNtFlI35l^mIi%7kcLnx)j+{wCL*f| zrYou07juaf^?1(o&I@PcIYAE3tL`lVL_3D-EF;2omX}KKZke&;y%&j-!~Df~$7YDA zd@XRXVwOL=J1i&O{63_Sk^W_J*M>IQkgU=bmorMj84R2kYZx>Fy5PKM%E9z|-6Whp z&h+k*FEl$u1>7%dOiGChpeco>6^b@scD997fwDq*z@W;C2ZM2BmNPP&y#>+6z|?1N z&QSyFTZs_0<9tJXhi<%IeP@!|fgVAla%$|_gvF0tn=ngcZL%$%JfS6_>!##_?8w_l zShC!g9eF!_wTm&Bl1|%PXAcY>SsMB-C|u1PW%WuH_L=y(tqV*w>Bg&hyHs=hH?w1k zf*SXipwM8XuG|}{n7;$b4I5Arq!kKAF~|m#RL)FCxXg5n7-MPPk-4neG*qG5BrvQl zE;Lo5tE&sXMKg3aYr_ja0$cJ!qH8dhK2k##SPg?<^FYmQ$QaQuKy6SyK3OQBmA;NQ zul=1!6L`$;B6uG3ry>_M#o&jjJbo=mmKt1*Z~=#O!><)8ET<9Xf0Y8(qGKPxh2788q1AHi-VYcCy@TbB$By#)x7nEbj5d45)pUoK&%MG4!_&_(7o zSLZee)kTnJc09eb^fhHfgA)AMmhBsqeHkx<Ui#!HmzeZnx$)s z*nxLQbRDC7O=5~W>r(aiCM!QfXBfJNJ>o1=-}@1mhl_RJ3|r&S9S96^sCBJyqfMVX zQCTDMGJ3aVOL{@NCEHzu2@@{t64ycw>v0zJz!!l~t-m{i!!Z5sLMP+zMh6y(-RY+N zT_u?D_YgPhFBLcEzeC)-zb9Oby-Wgao;VuqqLUbUi>ub2OzFyU9H7~)>c&S$#%A?t zs^lcYO$9N-L9T>25vt#kS_e25r|ciFE^aE|oXN6SDHono)gYwkc9zU4yQz>{4u_Bh zIyGl|+5_{EsfuJ2-dJ7vNw%OKLb}+JvCAxGa$*(QKY1} z7_g2fbuNamY|3s%$45<-794f z?hODtg1DSBPX-_OFIo2YVdCL;;&*|UOb#=zQ0$X1o-;z8VW@ltY=C0(QgWC@EyyJ+FQK>3pQOx{I1m)zq5UP5*j*^5 z4QWDRyXP3H#t=eN3J4)CyWcx8F0D8iXKQ0dIt7fWq*iTWlF*r%z~#-*Hp&+2d#QxI z2X<@d=~Qf{eC57pxKFNuqPsN7xGfrKAj&n-ZBn6Kdty4ew}J$jn{1oumYI3sZC~&L zbI*Qs(*A$Y$@uT0gDL3U;%5B)#m)K$z(t#-6S&7F@pEUcaip_sKR`Ft-cIymB8`-P zQ*_=T31gB5lMe~27dam-C*+Qa98-wQ2&~bVq``c`Y6kPko9d);rxsy_om&19PA#Km zYM~pSTK*(c%YoxeE#ws?0~tY=b*9VKpiA26l1#cn0{d((@g1~BbS@z>JC_inE0?kM zCCHV_;i&}{D{~3U4J13ub}?WsA;`rL&Lt!;$VejHj2M0dxfyjz2u0?Bh>wWO{{(;) z7FWioBQpfwX%DT5msgr6qT745|^2bh|5eCMVXmKr-RI7l5xunWF`|) z-cFdf8(d+6Kq%2aR5d~=Dw8I}qZNffU@FKJ!O1zIW2{V?5Ru8N45%xUCd^KmG#Lr^ zkGzNXsvQDKM-HX$J@g$$-=XyN({~tsXVcdYpPorwpU{6J?;?DFDc()rGWzzX@4fUL zK;Pl?9Z261^!+D&N7A>FzN6?ngTCeTeSp5B={u9Y74X$Qfw8Ka`f*XQ@XEcuk3_|vJI%)p|rg$xCYf@>c>B?#>mfZ%)5}Y*^m05CI!Q-)$s80JC zBY5b>1GUo4ES6tOXUUz7175tu*@qB6y%p;=VrWNxv0N=EH*^qmut z)Qo2BPExOvLa20lnS%Y30VM&%vZhDc& z;q1uNG8UNJGMjnaSq|JLXs9>N?bqvYdk87Vj*yihRHiI{6#%4<$>}ly4vP#Ej@=Y# zfoGGeHX26IjWeQG7_oOfM#%j;J&b=?8;6HW&p=9Z*gM^wYOhW)O5+(Q67uW=bkhEr zBt&)R*!MF)G3LA6V1#YOWiP=9)3cvis1c5$_p$u(`iE$HLK#@atYjH8&=olKrm^kI zq|&}jYulF@-1dd~L(tgvHRtB>CR0ngvJ~u83(p6U7z{g$PTC))lkq=92U@ldSNIox zzY~lPVi61L)}c~Ejm4LdyE1=lWn4X*5Giw1-Ldu?u?dlIH&vjPOHG`JinI$WrtLD0 zT}CLhB6%nFIvC?GGd-7Xm%h%XR|xIE1ZXzWN|)5oW$AQT9$l?C+m$xmsBC0j+nhw; zHYbKFL9S97j(CbiW&|+%%A%qF$VX@W5P(cH*T3@jQfw)G47YTa^^1Wsfq5L)V?1o(IcQH5aD8X z_%HxulDO0<9Uc0=n0RUUe%!q(g>?V6BN~p_ydd zG6Nl&iKs(Qn3y^=69hUm{Sh4+7IOJF86N1a%nKD*(A86)!y|fUlQeT=Tu)NqQ9YcI z=^8Rw;r=3Y-*b)1-=OOXsMPJ%%t>;O;kIF z^P=1y5`LJ`PZp~#@1mZC%eUeVvKbKC%{}deM{Q)vs#^`F&M>MOMi&=BU60k~Cd_ibF zij&6z6}J`p%R`2J$uNv=oMA@_!=4_8VI+hMVnevQGf22o)GYX_Y!^CP)awXw6Z_a51qhFW_Wk#*+MZXFR*8VpJ1{RnXJ}+ zSVS9jPCtdheDvp~8mhOQuYMl;h1yO>Z$GgHcFW>PbnFSc{orvU^TK0_ztf0hpV z`Z~C#ua8Fhx?Oji3}wt`=5p|@-lFY-PAxB5m)uURw>hy8^I>0@_#{q2(ZshBH%SQ+ zN2BkBsaPq5X8cvdQM%3{_|;^leV2y%O;qP4<-z$EM1fs@<{dm8 zG?M?!LYFz{3g<#O^*MquDu)ioX}iiNNmZaElu9Y-@xG+7(6S3XiA&9<{$NTqhd<9J z8p_`@Q7`_JyQBWwaDp!S78MGdEdT%Tgb7 z^UiZpn*GRV=8eqK#&a1YM#X)=)|9^@VDe+ z_%SCkg=}*I7CK}$TVB|zUvtQknkC@nm{LVTQc&$$uq}Q{R(ThUBzNaTXbdz?<%omp z1=|lo)k=0PRkmEghQv`jx^dZZskCAHK>aop!qImPZ^rrVfTzEqpz=XFH26R_PKH_$r;W|KD)Bf~7<> z#aA?iB+O_+wF`biyHMQjD{Bd2H+55$W|yEp7^xvAhy-;+2a!@DI*3pfWL7m&6B)~~ zEE^?}W#4zmA01L(GaRHF@7mvHvf4T03@O%ttR;C}wme z3ZxD}u0#oSMPio*Bl?K07+>IX72{ulQ~^(P8FnQgp>VyORU32G_v?lkbmPqUzA)pi zC^MXK9*#XJJMBw>;0J@`i-esnNVwF95vB`<+Qn7O72ve`gg`|;9UJs5uFt+<=%x$0 zBl^MJq&vAvK8u2WKzHaKh3#f^2D(G{O8v6S>dYg%CWl$D^izJ3d5sh9#zf6X(`&gw zfkQcj$nua=HUm0kht<0pu&{c8Vibk88+9oPolzG$(}l8KX?x^9k>A%E{?LuL>qE+m zh+ZVHT1=+v+)msd1>Pqp7l%oj4&UQ6AZ##~**4~2pb0)Qn2J37$Y6W?RTpMUl|hs{ z_Bw!n;5v60hKddwf%}v6W@aqmpG`&A}b$clCziuZDWaG+mOEZ*P*X} z68Z|rGYx+;NnfX-uM1z7khFj*x>qt>pCVj+IZw9zDn~I9nx3R-(;I6c1nM<_L7;w( zPR9Q_9NtIW)>ioiGX+A}n8@lRR@qwLMy4kunJLiv{%IQ->5AFNghne(#6~962ZC7) zX_BaRD8`R=lu*_@$$F`@z0Pz*y77*9eFt?5!zLno*>g&y41*-M<>>ykg%iS7srb{|yuquuV_C zUJFPUY|I*CEV8n}FER6zZK=xhEHRLLDNUO8B_8!=PVzy*QvY1yKj3nniJWC;LT7fN zxq={fb_vh!5L;wYf&KcMh9Pw0ZS{(@)w>fBVd+c;Y?rKj7ZCC*iR+K&B44H@-IA%i zLl(P{gBca;r^|X%m!~k5bJrKE&VGBgK~j0 z^QFd8Q>i)KJ(!j)mtwlQ7cO>QiIe2v0nk2k0F>`M1cyM&IOA_ZJa5`uZ80GnI4u{p zbOOmLAX#uNUWrKSG4-66!!dR99z9l0BR7zsO!v;2py+9)XzeNKa$|_+SL5Kp3-^kr z0iJIXVSYh*WB{Jj;GaN1cL5x#ybK;9{(8obT*qIW>o@094|<uOpXj(+{p1n% zKEIokZ#85uY8$f`mB-e_U=x4rUKHybZbm&a&Sn(w>Gun{#Dcd3-h`mHEr7=i2c?bKKr|FUQro6PLeDY9U*je2< znP2r@(|g!ml6kX`Wml{2<-|(g3S!v-t8cDdj>$(6hTl?|b#6|-Ut$kcQg=KaR~m45 zvQ~6i3%aD5E=#0K+;o{0b5<_#2>8e`99nFMLZlUOh{c9TtoBGu-Aj|htWfM5LPj~8 z)Yb6DZmw)4HoQ$mX(1+7-o=C!F3B7RQxF?svfpTEwE}q}N55|1~0Y-WERiEp*(WzfuAvSMi83%|3J$R(m)$gW$!J!dc-K z>3309WQ+8B0I(^<-8(tV1bQk)j`A*ZAySN-8>XFwMilK-bT z0S25sRO~ofNaUm*<_AV_*3r2hB1iruhgl5FX07kVEGzXw(YFJpsANmkl0SxoKBquA zK3?wIS{j@;GK2;w-NP&wv>X`HBJ~-U2-(+lHa6~H9fNt!YWM<`dM6VR9sgTqI--k) zkLdVQAh!UE==e#6MRfcWVYO>P@fdsGuE}8*ZFT&0PMA7ABcnRLP>N=yt2!S0Y{YJI zqoJ8@dWY>q4$WJ z@$VHk>;Dig_X70$vVR({o-`Q;xkhRU+HYS#0Wg|OJ_oQ4FW80P@)M?~iUB9l3Q*kleM8aoFNx1-}xx)bE;c%k;o0tFW- zP~ohlyBHwVNsxKOjjALBYJdEhp+%7+kMP)y0zJEFe#%iy&Q z^w0Z%HIQe^QP1DnJ;te1YLEOVd3xooSl?A1#ZT>229c;x$d+af?o0T7_-V{_ZpeU`RW!JW*u555bjh7Q06nt~$zoOXh5oAF2&rui%28N+9qg#NwKwj#oEm;u2yco=u_41lNxH*ix0^`;KS^N_fy%w*8qMK|}s z$X(bH$7dbZ}iZ_Pchb3f5)e>VtVA z%dueZT9pR>$B0K4+Ds?yKR_qrKS&26?k8};SeA!xf}Oo^LHlo=s6S<`^Wp*BfGGWv;0BneR&u7{b4H=?+44AI_mDArZMDOUey zNM&>=bBQ?s`nj8&{KCiB!ePkEitKp)bWFS`ZgU_{`6^UJSo!dnM0@A}&90=!e+2Pe zx0 zW(bwna4^g(r8fBj=h#jsjs~xuvU47oBrk9?rKG|oG_n-3A92e&ggC}Q6mn7mw``Fk zb5NyC6MV!?S)%K%&cvbvblqj`+Ykb>J#dTZUUZ%A)uY`Dx-Lr{%tLCf#F>v-v{(_M zcfkj$S<3%8U>JCh!|8eJJ&@mlR7wHp6AN>qtOv1%-UuAp_k%wW8XWhRiZ%oJlx=ua{` zsQI3uhHjjicT7SJ_^S6RKP~eWQEHFA6Ugjc7zCT!wvGqF_+1!62>L76Oc%oqzrV0D z`arVyDWEa)Q19u%gcN*MA%?Z!nY3vT9C8N}?^E=jW<>+YMTGoPAf_WqPPuteQrh!> zJc(HelY>*=H=Lpy=hQnV;gs5N_hYInwEysc!_J3er6%eTCNS0ya{99(bJIY-BYr21*|nye=iPz)XUGqZu>}{~v5B5R zW}IW@2iaxn3QjBhicxYakj97B66an!F!?saWV&E-WaHt0Nthg*xdmtMd{=NmJvb|5 zIg$M?+l4SNRuaLL@WmSLmOJnmBP{9Sfi1>s2)y7NyE|R0uTq3lsF!={9d47i`I0nt{R(R-Nj^mUp23}`Q+$zgq= z`zbFXHMfHHL7n^;BoJ(USWq%}2tqVI$${o3cX)7xZoIhjK^meg9GG( zz?@QN*6Bg)tNR=79Aqz-YWJHIH*59+RjlqQUdz_)0fk#{}MMtk~ zVQ<%GaZ~=QOvf>DIB4R(?rkQ(6=Fqn26MSTK==4dLfQ32B z<90KeUW$e*q@JrTrWH&igy=+~snklyu{Kn?@P)aa$AU;Fs%Df_7nK>dyLm@Ja_)*Cpno81*SMp<%RC51-@%rkFpbXmh zSJK7=X&e6t_-z{#Y})ujlo8RYP-j6Dl3FyR-e*Xq8z=SLe~wf^Y5SwnN?09Q37&vf z!E|fD2s4w*^K5O%7-+@#5>5>O{&Z6r@`I9?ZoD!VOj4Q93c)g9XBVU~Xj-9QClj$N zNh;1!JD@*B*HBDi*HBF7+{~vLBXf}%f8T(6!Z_ov1SI8x9;enThGoeaUap@)W`3bI z9e)-4Rd)#P5EipR8TqSw2OxPYsGXp`2xtaDopNz_{MfZ~uYfbMcN^qpH2OW>L=yhf zjr0G~N%#+5vU=nx$;2G%t|>^St|{o#9u-+r5Nvu>{WS&GC(p|GVf_>reTs_7e3C1h5_k<0eW@yqtvw~j%1 z|7H7x>Hh--(ZvSzW@&xaU^Y6E&AnEBJ%($I}nX@pnA__>~+_ zDSQZjAHmMLN3GRTODK}y3 z_k44~M6MBQTcB$t2an(u;j~0;k8fc6bR~C9mt&VOJcmi`kAvwUQ!w55V7gg~pWsvi z_b>NDxqq>?7V}71a8&Ku-#hVlDE{7uKO8)iVX=g_ z4<+%=%4g72{&JvQ@;L*M8Q^F_K4~Cw*Ubt*s%J5a1dm}*-ddC3u?*s^H52e@dN57vcf)yi?=DGd zrIa*8+86F;e6pO)NGM%`=>~A}j$v|)F8M^445CZU&?QUgk{5Kz1gpcSE=)$K-a%g- zt(f3?T5&I4jTF7(3urO^3wK*dwoC@3TP6b%u9^tnXD0cThrob&kPWPZ2B3woA{xLo zs;rF$W@?o*fH#|2FAdDr%4q;MZ?c9OKy^vF1oRNcY3c5AGFVZSanW&jc(3c6p%MmK zh1Ww2-3lwn>Sf4VUj1JDwy!=MzwVofkC?VI*PWh-Xe{qeqLcAYW@5f-cM8MeJ5_x9 zCsW;dJ=k8I(l+>WB4N7I>c##dh#jmK`$Q0XdcD}Eg4p-hi~VH~Ta#E)FeE;1P(F{y z!RMHQp~O#bCW|9Nuon#Wqt7+bilSkvb#4_q;USYTFM2iPUhFCwv;dTH|3M( zpV<3MPFkcOXF{k`zk>cl-8Jk(WUBNbvQ_#J8S84ZT=5FqX@ zn(Q-em6w>;nD508-Z47`5UHn21n5E{Kf*m4Vo_N{U!C<7!S&Wtltr(FRuPsaeTT(` zVuyuRUITR$J47V`l?|Z{;)95wp75yQ4Bfb#{}Zb?ftnId-|d{f1*O#Wt*Gir13?dts~yff7MF?eUH_Z)akSD6l^p+NYB&cW$K6?j$>kD}l(CyF9a7)ing2s1t>D+H-UC_64PmBI(# zQYpIeN|h(6l<->5%e)}k5z5hAH zvgSB0CViD|yyo~!Y(m`us&-ptF%yLyKuS$3deodjazKic`j0(p%rs*w$sCbN3jg%o z)H*t-7~|I?5BAsI8XV!}!7;P=S7lSQ%MD27D4QD*bd}B9$QGQ$5w_5cv*j(5utjx6 z>Q66Z zW43Xo^OB%|^6GBficE;)|pDhK-1!y4FU-P+ffM5~!zeT-Bev(&Z&r6b>hZ{(Ueh}f6i5Pe~a>}p&gYod1phHHFs z*~+^ZE8q4f6LZk-^7zJKgb0)Jq}<9&s7Ny9;L|ktv~?|W2-PIK&8TZ{A=hL&1-9(2 zr2>(s(T4C9X|`)=?QNh;yBf*sI<1pC!fSk_p11k?GeIk?)P>czF>Yq%J%;X-tU2Wz zSjqn(;_y!2j`)TQzU)Qa-T1}_K76vp@BUvlV5l;__=T#{nVjUdZsDJG0Pn<>7OrSTGGVad*euvZQTv)3UO$v`M+^=Q#GS;YUG5 zcXB!oiILO-4tT?nyHIi{yVLyfI6%Spr@`fpTXk--aym$@mXekC(``vsYIISZA-Dlu zJma=6>8czDsQwl@mM(0~b}Yk8b{u<}ejOZ1a6(@p+tJgT$#(3}1I))TZ`migINaK? zc%ZV9kdpr^LMT6PLpiV5atc~dqdHCMG^5c>YTD?r?g+SPcYoL!p(k{@_H!Ih%M_(!5G0^8Vbj8u00j>zI(Urj4POyUi z@Hr9^@(W|SmFpng(39!klA6CIVZZB}Jv))|(DAUFEwQNa-ADxI2XOeNWjgU+z_$b1 ztNN}|qv9L<8+zFTja%~bD~F;|TN1g>H{L)SWF(j?pf=1tcSW`n;KWhO1skBmJ-1(r zkU*;UN=>VAsez7;qmC)3@VlZsaFofB8ju`dw|twFNTd@}i-|lW#FY8TxzcJ6bX`%J zyTSyPPwbNoJvKE|{mSulDYr6-&A|U*jI~msCDT~#gsB~mE#}we`tq40D`}06NOOzXxDCOt`tV<^Bsq3h!6$KSYz~dY>&fR@>4!6TxE` zqb3qrpChhR1XnW&nc0AZ{iEsJ=s!hFTXoT16li8&L2}+xv4~L$18Ont@YVuhnYpeF{mMb%F?> zog(vJM8qN_y{LO0_%+*NX)%dAT9@Vf%j<=49j(1j1PJd-Rt^IVI+e8d7qiJ3(vBGM z9aGhp0ctivnwbN{3tvqRa{|caAJ`zcyiD)UXUjxUER>2(xLKv(HF-@Nyry#h14a0} zbnkC~X*ZK^EnrBowg^LxBt@V+u$JiuBz{9_&z~~|GUHBUS%xM^cC=Sc022QW0FsrHZA+n&nd!xB z31mx$;M^PZSQ+X)><|svT+UQkfiGi%y&Xxt+=7CKfmFEtg^`H`XD-um5P_a1_L0+>fgS_Tqla)eV(_~_(-1}u$ z43$+39aU^J5~!>a$Sagrc#X@81rPHtTfxh(^BP8XIRabQ+6)9BjxD2c1EXl}%ue&5 z7BqBY@a}#gM&k71)N--4VL_uewd}Pn>n~2l3zV36ys7OP^jh1O6WB z3pyzseAg_$(&bt<0LMUasyDT?_)$&frFT04xGT%iua_JNUp%{HO!sCaMHUiwZ4pag zQ8Yz0_R4ER;qI2Egd%zaGBs7ZviWSD(uKd-e7pGC;hQ188Su@74?j)$8H*pRHU;UC zyeVH={7Saul{ZY>4X`OI-JpH6I3wv9DLQk>*&H=9z`PmNJxfLGxjNloj?)e16laGG zKYQ|qzpm~$;pfhr+ks>m>ClbG!()!0KGT+0(lxqEZO8lI&x5sGuJ!Kh=1nIH|mpha;LW6q& zS5D<+H(`O|1~S_g8_n3|3&EF) z;J0)tr=g#BB`bq;z**YFa8HK(OAZST89lq)0}#q1D*t)r3@X7=*J=>~IRqJUhFREx zwW)tOH;e%h-`W^`9AWD%sfU?$QFqW5(!lYSMI_Ur{&XzPXk(Zmq6MK0mh}-^R)oj1 zqCA#Sf-=VPxOXRjw}!!U6r(c^ug4!LNY7*p;|(+YA~TBlk+v(0w-+~(`iI4B}K<5 zWT!8jn#!g7vk&UxA=CS8PS#fuo6{6nrqt#H3x%;cCE-gBGe&GqDS9+4HYY})%_Sa7 zv8;$aV{^(So`K=x51a?m!8}mRccuXpnu`sNAIpR^u49A}UhYqM>Cx^ie<5n-uGmOe z5wCrxd!I<7R$d-+iPylNLaF2Rjb-F~7gi1F>9V}?@;NWPF;_%7uW=NMk7@MquYAnt zM*bqI!D|}Dj{-+~e<4Yl!jgKvmJ_0P`y42*ti~`|auIx9zC7B5ACxhQ!Uh_l`j(0< zC2Ee%z|lMkm8QA8qS)fK%RwZN9q(M4GgVWfd-X@R}1%#Xho5ZQuN z%eN#vbtB6J+%2=URP;*4Y0y@YrWKl|*NQSd&tsWhP^M?g^rTFLk){E>YT1U7H;bqI;6 z*`GmpU_s3?(B|jV$*WUMHxIe6{sg7mh!oGR|DwOQ#^_vk5dDMfy)?~W8C{=F7 z9k2dhS*^-tOjJ2nem^A~P}x3gp;bkKod_C`06^z4p(fcespFcK$thmmHRM5Q56< zA^RYJrKJS&faok@OIM`5PpK;g-W2HLIdCd(L-`tkRxLz{V{9k{xA( zxn|tGjJw3R2dXQZ5$e@iY2lR%6)E+;MZkDAb+N`wq539RIH$qNobAh}@;Ol})XJRc z{dKOqqEWcqlJ0r5>kQ~;Y+1DB>!=Bwf2hL-CcU$O`#`vCyXv0FVMcef9|I}KY}p4z z&~BM>7HZC@xhoVwa+v7m4k*^-Fwuc4(rqNFwD>x+U(l48Ix8^+Jefi}Vjy$>m1WIc zbe$nD-O;I(4alazisde~seYlh%j^e)bYVXrCCvzia9y>Bv>y82+!R=$>Vh^i0Ie~= z+*sI2&JPOG1~Km-KRlaA{4r)vFTq?W`YY_&O=#A3sKt%h{JLDnRAWMJ>U~8TcXsbH z$>BycN@=kw5*q5f8`Lh!v_n1G!9e;kpDfDE0OZXE@^x5m7iDGwGTlIy<2#{6nOT6O zW1bwAl4u!tw?Bf=_*{Zxxo4w{1bh%=&R4gp{3+7Vr z7P@7=v{9Jpwf4RmaNC2?HLyo}0k@IhmuMRrt$9tmGH2&CK!hE^-hNW=K|HWMdNEk& zN{v_wjPo08DPHUB-8X`rrFq4<^NMrk6=%;Y&YD-8S)Aczn(@KZa09Y7TwLt%W@Jn2 zypFXe^q+vZnb~p>nCZ>R!rY}Xv%J~a89`vSHzzwY2+Z;3W@iO~xoW`A_FU$HPct@n zXvmrPZTDv3cZN3`zcal#_?_j=-B8_uv@Ei;a0jIw)wqBxoVO3Uqt{8g4i0Md%K-g- zop0Q;Zl_|ybkw=2Ivsf~_8JzE1s4|^^2L0R;aTi5H1tD@#cId3f>Lkf>IZsmBh=qU z(lHAwg*@pgi$$Tgjh9|q+z#x5{6lcN;+qVJj`yYw-nQO$eNTJal)UY*J4C%0I(mSyvG-cH z^R}Tn%dx*Bi?(L$@8s7tto2&3ze7*4MZ?Y5-;prP|0Jua_Ytq9qs3YKaymsl7+|?IIm2sJ@!K)gYZM<4SBbJE zC_Kn$IJ~y^*4;$KG*H}(D~=x>{_&US>wq7x>R6zu7JNr(^`0#n!84!_oG8w(s2O0+K7p1X}0 zlzoos`5xGU?R_pT@u_tOCFJ}`; zPGU{fR!G^Vp(oykbF$JE4|_P-u@GyXT|#ZGcDx8!ap z7G6&CX@Ar0SjjZZ>_ytLa_fefzX-8PKM_C-!<;qH(bk*w3LS->?US&OG2KX!6QuQ4FMWeqIv@nb+Ky%`F~q zab!(GFFnBNenAh+X4(?yhk;K4`Jjzp3ln96+IICC4-JH;D&hTN9C>ama_mmZK!HKd z_|$0#ki+(dP51$v*cfOOd$n+d$&5TX!R#vHPtI=ZPNglqb& zlR8pK7jv$(%VJu_sPktNFG5596UPltsHh5=cKjLj^DH9N)&l3U&~}pTZ|~PI8ba+W zN1%^xkYyOU{7-ZVOoLXoq>@h4b+q;OJf}Hf*$ELcv!>|${8Z>COBI1Mb=F1D^8!|M z6gtTy^vi-zQREeTkxRfxT1qm+Amkf>1TB*d7U4W()$_7qrgbeKtrK-j^t@=uS_=iZ zP_3HkfiRx9U~(@vcZHXC&MSHOVoqwT<@Vf%&lE5%2x_CmnUL&VNBF~wfivUV|L zwX-Z!3N%C>`XDa52|93y_Id*zjWDM7CdHz(fNY%Z5(byo_G7^{AtKGQQP`Ev8nm*k z>v_3cY?|RUEi(d?eWmARuW816DNhU46Fts{jhGM19u5KFe-kG{{5IkT6XC8}w23MA z&s~AdUE!vtMHpg908od+*hWSyzLNPGwf&Wz3xIX^4`G0p3QM-3a7Ot}rN$-aarn+% zk?V(mAT}7i6N%2mQS!U+R119LjVInXic&-p!61+G9e`l8Vu!$M?pc_m^4MNkCxe|J zj9(n8aV2&SLhOQKb`m_MwWYA-dX7!V-StFJ>OntWuSfiL{sPF)SlF_R!hPf4}*-i&RE0fU$)6OBR*>@&Sg(EH}D$Ol}~jM@-e;?-!Z9u)LI70u|} zSRLmsTJSbQYR2QvzDDBWT;nSb;DoIV+>IVmgsUH+ax80lKMVSBQiCkRh_nN84D)+_ zt|N}ae%FUY@X^+>7QwU8apaY<@ho*13Mb_7=ok0u^P>+mipJZ?L*sqPQWIwxGiGsq zk=q6H_RW>;&hEkSIN{h)4*;&#ZOT2+wiexjdvsd+o(oiG`YPx<4)j3{pp9Q6mx-W* z=u<)G(?|!as0hN!=VBT~4~(1AKVd#y&p3$6M#-5|G-b_i1gYkq?o)+Ey>Wy`#O~;V z23YV3tTwx3KB>y~bR1(9{hPMB0SrJkwAGtfFE1ucJy>EJz6 zjc0(nS^p1n?*Si0)wU1M?#yhN-A#5$NTF>gftZAj2#7R^*cA~4K?DR51Q#b1B`!q; z5fMQ#7E}-$kBTCog4h)cVg);314KbY<*^{bcis0XyD9Mayx;fz-+z9vbLN`6pE@(= zoWYN;9`?9mR`yH3O6$AL=J={kwKDc9|qMMFOGi9Q+K-8ncR=2lFG-I%MPBF0@`ffd z6Ppa;X%W9E;HJ@T_akXOt=+f2;+6U%GrjAhLRd-c zO-GvwokRz0%h!jGcJgE#X??KVf#?*1vy-P*Md=hMT||{-oAAVu|GX81mipXIQDc1Z z)b=@`tRVVCt2Tx&k%1kM#FsXGZf~Nfad8(`g=0T*7Sj?#3C&wP^}D)Qg6Tj4_Lr%8 zy)|S*6sb-WQo1@pn>2#vlpd~JESlkiNkg!Eb@DXw2m1p^-}w}I%SUv)bS%0Y3rUpE z&EW9?v=OGay*IbmoQOH)Sl8Cln*&pb*e6&nCYB-Iiqpxu5wx9glSE%)G)pHLekKvZ{fvvULwh>>6L~2a z#P?6^pmCqz5>~K9X5wOP64n~hiFWx4I+^6%h;5lraeJMCcqW4+Dr@`|qOVT5{nrabXLgMDH|`(vZ9PNF=AyhC}6dp(ti!U(5HX z+7H(dwkD!08Me3}@@Wp@`WKCpRO$H~Dy^0gZ~0%?a54D>&Pw`Q6StuCFU|7i@ot0V zf9p$KtO=jv628MyCqUmJ%*KneL>7|evryhc_y|jkPDro6yjyD|4=P?%;w_L)m3S*3 z?c_Ebiyy*Ahj2{1=BKApWB|U)!@o>cK}6;KAu6r9e@WM^Q*_bG^Z15y%SHV%i>V;$ zLis)*h4e&xi**@gfxmXo4V8oZi2fZw$Nl)^8ktR(^d}f&&m-A8T zbG$`Y<{^|Ty@`crpYz5HEG!xhz?(BBaSpDJ7S}N+=xq}y&x@eC@Gm+ulRrQnI;9Fa znu)WhOm~xKDC1{~%Aq222t0rFA%_qR5swN^2CSl9#cfUI1oFb zbR?LLjMM&*L#Z+&j@0kPam0?L*f(Y+P)9g9I%N8C{E5DG(;bqzXOc9`QKDlGYF>tw zxD<5F;d9+&HbB`Nuzat9K8^cTyud~$$|3ZPY36pS-xN$rS>$$X-zo2*9% zjybiLex9ZTu{7s{C~^+rpT`}7;!@;079qfHz$tiA8eAVRzJTB^(HIL5^dDlxK#D4` zY!SZ=`L_}O#_?O8p99CKz_V4HV#7GaoSb5APO$_|u@uoqw1C|hV8%?`CuS!}a7lhS zm0QPW7Og>O&>RiGDPQsyk1<>3xX;2|%iT{#AA=$-s!UoQ6hI1QN&GZ&cLXAO9nnE>+%iRgETa-Jh zRrRP=ji6eUt7{d})>bO-BBw+(OVxnJFcmeR2_5WYQ*r<$o=%G|@K*5E3PKwWXf&v3 z1X@|T%w#DU&4}4b7Sh2^HYZ*X(JA-uqAcfd*`tdj>ZWmgvr$2;zQm)5GYe+kRD;(7 zS0T8F4tDYwI-tQT#*qe3e6@nka8Je;D~Pfj%Mf^nW?An_h|Nlt(!ox)qyvPs!jXjV z)e1U;5WZMJl;yvXkd?aWZB1-evJD;VWLr8wNIO2-$@VxFS9&X$s)C6s2!lu_sUU`O zEQ7(IucVIGdnl`-1CZPShq98CAmCUWOSjWnTrqd(Ao|&&9SBTfClyLg<|V#Eno3Xs z3QA~Om{Dvw33Ll4V2f}1g^ZlWycsjE1DuQU($z#em7|nVg0z_;6LLaUOWSO8L5ar< zdk^^%0W>_iiK`pEdEB!-#p%+jT@4w z%VvT`j|tqD@O!EaAB5P{F27=$gKjWQ zNvk6&VbUXL+91Z27eWvH(TFW)1UggR+Sn}3H|w><*zEvNB&`~V%}CK|WQn(tXe3#0Xj>%gb^>Fdd;^3`@$2>H!-g3V z+&f{KV>~QvL^7I6X3HeTzLb6Co$AAl)Ih5%l?y9*JPy>1TSYC&P3qV19U(lsJ4|Jy zf~YXuBC62TsS(2pPo0L}2=;%B2V42do3l~#M%PzqGp8~OkGm?fY%{Mi%V*|S(yVP? zP9@C?v3CdG!9tivLaUCXmWcn44sB&jf?D&Co9Vx_3?up^FF64hlD)sR)crl`W| zqEH=!(kEJCTj`HE%CA^3kI#Wh?_$CH0hO`93lenz9P2%Oa1+WX`9mK2UD45AYJcX|L)DuQ0?R#{zi+@UAKluhac_Jd?Aq%fw;} zGv^jtnCQ0H%8&UMi#EbGhW&qAzT%i^)oEk(f+PLG^bM4|bbshAY*l|~9-ALDDSxoM z8f6M$E4qZr9Exzv&ys-o1O_2^2@Foo1cgBv(fQ-Z1;q>DtbwvrVOk#YRPkzgC?E>c z@=!t)uaw6{P`RgypmHu%1eJ5L2(o7=;Yye`1Vvma)rO$DC`=oI`l5KXW2k%z)ACUJ z6t9+ts;n?A4|Q4bN_klBO9d&nYbnYRHy*-E(Rc_CMdKlS%ddn}M=NiQEi~4?8tvKD zRokYbBoW1lmPPK1oZ5yTx)I;6EgbZJO*#=_f zhp&Nb1D#mN*AUl3c(>ZAH)gE_u=pv9=Cxtw1xgq-2G!cYX4R{CFrG#8NzmX0M6-yg zC-Fk8r3nFY1cb-HacXO-G>f5leJ~Q0WW?Glw7Qril48Vu2yx8ksoun5&LPW^jMO%_ z1tT@YQX?KL$D4tXWXZOf>}mlcNTY1Q$*xi`l3kL~1{Gd|FqLE^yE=lAWJyM{s}qbM zjnW9}Z(qW-IBK4`^&Y%X4GA9;kz~`bNr!0y^ z8luSyQ$tP`x2TybjcVpflbX4bS2I^4HFKq1&0N7m&g!`mtC=hLHFKq5&0HbttK~{b zja+F|GgF$@%#=U?a9*J9tGqy0f+X@nWlMTCh>SrtAfrTIBr>Lv z$QbU6Fv|;l5oRNxFIt7A@wK%LR=rqYu~k17Tx>OnIg71^v5@Yc@OAV5zuOD+S>aA2 zs#kO>xQLN#)G0=?J*V<28*(bI)QHO%QB#R^e`!T^pe($p7!AZYiVANkMgyt*B5ze| zjGD(pab&K*(4xgcWC(VQQY|!Q&ctYX6L)U`)oP>ikr6bmk(f>8k1&56J52N6i3NZdeJbD4m*x2Au!$5hT^CbiVjT{J0C}-Q1T$2bok?_6#_$Rpt^3Y z7i$hxH;wwL=w$B`jHALTd5A6P2*puj1s0;JOFFobQ8~q#rW+ZRQ=Dlcr#RC@PN_yl z0Cmat>|DDoGyX6eRc3{;y0tPZV#b}ytPC?As?5qXHq)Da7FZn4bU7@Gs44^SlHJH=iyky2c6i!DVE`bT3Rrj>4Ro$Jrl$3cVQ$j zP00uS;=V5ytK)+i;>Hg%l6ED2<@;Q!b1k7o`!{774ezVGfOShz3iv)PiKC221s*y#|stR%%ZT0U$kA zqGq<*xGM`SV3aMDHegA5{216kE=n87#p7WExhQSGs(KD8#YXxQp`|qzi;`Alfe&k_ zm`|ETk#7SVNV<$x45E&Kr6fvP8iCWTVFQg2r45)&8*cy`$VF)b4F{u$DY+G&BgDF?5n^2)mGBs(F4}ETBVu*YVAF^& z3}NVFMZJjCjRz@BRRd{<(o{Q;4pEva2-0CnQyoD%LZcdfK0@^bKn4S3A9kfeZ2^>t zaZWZ3K(i2#V`^3q>Z7Yx5R~B>H6&i6hQ!fIR<9zhYgUocnpLDx%_J>$)Do^5 zqL!ov?4oM$5JyxE9^#0qA!-R%4IX}qsv&BLs2bFLph$Dokky2%hO8!BHDonG)zH-> zy;jJIf?9!^Bkl)eMTv@v!Z!q1^@baQ>{xa@yZaq~{_`6G3;o7oY^M;5;Ma*|;1@g3 z;Wr$M;ulRnelucq@QVgs-#;`C{NL8!$FtDGF0S6gQY*+KSDnN1c7qx}If$hkmbV*} z!(wz4qa2pwS8Dv^z?E{C2CdW%QV!G5OXV;&cN7gFa+qdTb3=$6rVZw}Aw&*ig0pA{ zQ#m{keGfN}tn!;2L4@7I2Mb$`*{xH0WWzo<)6CH~jIc8FYBA;To$C0DN7}Y4U zJ%avJjwX4F81j-H7S|E-Y^qbFHW)`;Qln0EJmjDs?Q4d>a7#*~Y;@`9j;=s2Z)5o*3Eeld&$q-cHHWHwNhL z1V(!Y$=f&dcLLKl1`+D;1Qvk~xQp0egduItgw1d*10g_{C+K3Z86iNsA4i`juq@aN zck74|g}faUl}e8&R830V4vI=8+DfiedPJdW5?3lcqToE!l`1u&AOqEiLRTcNRMkcl zx*Da|tg0glu0#64a9S0@a83A{R)sKLQ!&w12m>~OrBxw}*aVhVg)n3j7@lsbDugkc zz|yJ^t$5|9?`c&CV_b!)8($1^l{j64bZJbx@5L}z(52NNjB^E+R)a9m644*I{kQ&)HCqVDh=I^Um)T7&yj(f8o~RMZ#TpNh(Y`%`W; z)%~fcCG!51DmCAqqGJ5h8~*=xynt=AF+D-f3$=~*c;6Sz0T!#xOlP6vfN>pNN%Tm zQz#{Lm4c5dv?~dHoP^R|*v~+w$@aNjMw#Q|(Q=G*eD2&-I`5{fP+OSeZb@PI=8@~W zRFf~&H0{zr(4@1tLsHk=`Kffl3Ybv-Hv9;cN5hCg(MH559B^L&O)5r_Krp~D3RI0z zcXH>6nlTzZTF0mTU?Ru&XLOIndHvunlZ|7bvZg6QdQX;s^FuRqrN2csaj&xr(8Sj zZb+qdjLw*#uTzX}PCL~!o6#^eAZlbNxM++cVl zHcb-8k`jaqz7q=~v}&Ofnl4vBiRrhNr-wSBYGgt`hos(m++(6?%_@1rzP z`SbpXee+4whMM*bov-cNnCRh_gqoNTQ zS<|F*I!l{4`Yui}ar8Yxag|Q#$mf@M^KROJXP?PUL?l*{6fJ?##kY9P3I_SN=xUmK z%;#psX3!?-r+6N|d4OY1&X~m_l}o7{zKAcwJV1g@l2F>ph`qz{<{XfZ@enffGAdlwVz6PxpzbB?}be@OPe2ynpe+o--D(hZ;(PE$UHK#IO zFoFSWcO|@|GjmJ``+Q*|l0fAEY@_I3jG2rh#C`>sx7!Yztq^SvCgXxSUN9W9bCDRT zVy;AS9LU{^FjbccWH@pvBSG_W+E#<*1Q!Q2X-8V(!g+}zL093IWek8Lw_>eA{hR2E zS%p}8aUZgBZUQGXxHJ};KKP|*5FE_0j6f->$q+mzFf~+^kB`)Ma@7| z{DZgrUR+Cl1ciEe!N+uBiF)w~rST;X9OjM2N(nBbpW#f*B|jbZ(I3hvQ}S=oWGbV4 zZdQB>+9dseg!r&n5Wk`ri*wR#R?u%^F>IlA5PU+VUnI@MQcCBr9M13Q(0K{sg7w3b zu^N%(3LD$k$6AP@$2D(Gf(pOnwSCwhMbHVZfkhF_!w#924_ojE4@j852$SR;pPv^D zGcZ8-z?S(F4@0EwYcrE=nImn%M^V69@lFQUC{DkJwDe{fSP~bYVSve~Hsm_IBbB~J z7ZhEL3n-kte+6AaVf!LYo}f1q!$H;@OOJC+zpQfNiobGLe_nlTg7Mp8nu~54^!?E+ zUgts{@a7z>PVV5N0?}@lurN3A5CzKQ*wJDWx%BOGFr(G_f(}^sNCzx7*~&+(d&H3z z84;DqYq)7)9G_v4(M?pwX#Morutlu7p-W=d;R@nS%r2r_Ch0|mSkQwh^Ca3={wLO5 zCT8(P0wf)&7s;Y~X`xbT>F}4hLaSBjSg}46C#~G(DF5YOG{wR)tm2XGe1+?9=W9CH z$#3WYcfQ4u+@Px|H~37s@vgWkZ6^k46UmTClA3N_DvH~21!{ND!A|a^1Jr)UN35R2 zk<`=WlzKi>>czUba>Q{izU?VUr7grQ4IniY2jG2ado0{kC%oP_aTx-U_^d4+hGD%J z1)8+y(i)MJ(|gEZE4dd3a+Xf%_#+*~KFIh2FCHo)Z^Tu0y67t`bx#tJ1fy76 zj1RE}e2GaY+xX6S$PD{S8~P`9!{=99Vi#Q!%&Y-_QF}xxTvC<>CMbxJ;M8xC#R)7% zcUsm9x__)O#*33k(CxBTo|Hxcjkpaj_hbDER*I7qIRRd?y{%=I#o>-wB0>ll1zZIz z%f}&)`E<<5Zy9rvlU?4rp^CR3@eYW1%M6Rr=Qj)yZ^L--K_gSd8|l?XrieGvuZkEO z#wMfSSCOVZ4~y}PE2-V$QP9+A)WF9L6XTG{?&SHN@dB_D;{ou(M=U{0{sf!6KC65t zTutty6Fd1c9Z<=B;iH}Wm5;vUem?q>|K?*L`5TU8FSVHyaHK!o7qVchipcT{q1j4| zM5&!{H*&3L5YAIYTJu zp}=Stgz|TBPP<~0DY!h0WLd#-e8~Xo022-lB@WgIT0u5F$ZcrCrOdn+-y=*Mi&U1K zO`Ee&xJ!FgGYjuN^X6V-8s3;KoD;h(5N#|hOvUhyl}ChfU(q$F$60=vNt7F$$1#86 zaxxA(;!#nBb$IJ)bH|5@7i^EbPc=Wkg96RZA2`rbSUOfOC{%aCC>N=^gNpGd@!81( zbUFLh7%2%7W2z2f4LiTN0lCNOi)8zXy^ctna?Q$U9zAZ$pwzD6c?#r(mT# zl;&b6c>>%h51}PUT#C#ru8;aM1#tr=+Z7QdRpmJq1W{5gQBrMu z!2uYy5wrtbPJ>)bvz%>>vO?~2Gtq&B%9C(#N>E79Z`T%|~C-ha-)qh&DBv;xjdx z>gX-{m`1x|6iNMe!03slF34jRB9&t+k|hU3WC1suaz`0H?~JmRz@vF5r!DePLq&5b zo3WiVcye<5eIno(xU4b)(3LTt2eO{C=5U;>?$^Wgc29<`yY zx+$*Lv5KaF0*C21Ot>G78n@2e3I`JtY0&WMw(Q_U?3Luf3sjx4ST4W+z=JLi9s&y z+j19LWgPjcGAjUD4(VK+h4?A-KEjRqv((>+)^iQ zgF1Xos5X6mq0e7jUEw{nB^}3w-kFR0S8Y4(EqGiL(?ucs&Y0|h{?fY8_s}S zANCZbkBBp#Lp#0{T}zwxV5$OrcZW9Bu@SK=&i! z@k<8$64{N1i*%AB4)aEHS#W&uoYubbe{n{d$p}&BBs1xND+7~Jr1%-dxsXkN*vk@7 z0X9%DTJxq^B$H32_;?OK`an!r80(DZFa>L5kxB;(Vm!Ecr7E|b&TUI>djM$32HIPPo?XOqI8RW%IagS-FGmK9v-v7C zB~(3kK^>}R9WvQUX4An=X5m1Ccs|k?p7O-u{)PyP4Agq}ghw!@84A4UYYeEdH&thJ z@T?YJoark+$V1!@3obTksi92DS-TtmXEJMksTFuxx80%2seF+ z%gJCoLy-p8C04}UG;+bq&ug%~!B6l88|V3o_92nXku)~ngM?W=@g?>{X1vbvPk z4xtunHgrsJ?@A>ngCR@PD=u0dCY?UYLfua~Ji4y!2Cf4FL#DrVDhBuU0L&=3mqW5O! zNhbq|JHh2o%qQN%4t{py2O~D7BAvma0%U#pZV+I*lEf`gRg?jw5NpV#xl^IeEZRhF zpu@%jAB+O00n75`23USh;txnIFAU)4EeJ>q!Vh)O^G4HF>hneq;6g+T3$eTu9V{9U zdc=v&6-gra@NR5fDNY54ye{7TJ&#O^RLyHN?5&#m^IFBRZbalN^=M=8AA}ut^bdz}6Qz!JGgqHL(MyOy()(pkOY{#g`40oR&(o z384k1GJk&4Sc(8`>p>VW;PP!GyKLtg0G#{1ljF7nW2JDE$h35^0D$LOaH^#FU# z#5$CMWPKu`{sALBBa?MCpvx6>p*?NERl(%gp$Y(UWk8Y={=^z^JHCm%F{COD`zFva z@eHtH-^7z7M#scP#>fhiQ=GD*Ax>e%2C||NT@w9N%8ob@RL~`12Qzpp2xdDd;ZMBr zU)w=)L_2gje2&6rlb=I479T<=u2g{O(R~ve$hyU1 zyPx*k1V`HpuP;z^5T&psuI3aAv~`Bpuh6L z=Z$V3$Cg$|VdF0RqQ9q$MF(*MAyrYCqkid5DVMiv$E}%Q7xd!22JMC|g+_%?jLKM4 zNncYc5cInLWRnI6m#Uc64xjHks#)lE@vRZ+Z|$5D#$j}hc(DY0|L#bfB4&pWGkGUQ zehqn*rbsi7&S&^t>5Qm;*V3v1E)DJcJu z-Y$1(Laq01U{(C}E>eKh8z8;SYtefyD68px7m$eau)Fm|_ls0o>+OG!QX~D{qyecn zM0#7)qPOr~En3C7cD;*Jgj#R@;%a(t*MQWUL3)d7(fd6p|B=5JFVTcr?^a+{;yma+ z1xURy(tAuTdY6N;n%*``Rh-{LN?vr&xnHNX-j9G)(L4A71xUTwq_-73q7}fZXmuV^fYh2xT1!YP&2#RjbPfjH5p=Fh zU2+2tYcj2K2(WaW*=nA4JyKQ54pz~%CMCp~TGhXdWm>*FB9+#+>;;GRM0h&v+ogL>F&AF#}pv5uPG^QE&UYtB&>5d2$jxy%15d7 zwbV7Y`EpICwax~Xu2pAXb5hoZl&Sk4*0nb!!)Z`W*V&J2`R>Y8TISGwYk$~dVk<0*a7ot;W+W!|TcpzK&ucB}{^#yM$uHytcWUx6>J?$Olf z-28Ya*Z+*Ba4S=3op+7b96@O(Qrc1FU7F4n;Lti-rInX9q%$uba{H{+6z=ASxPdblNI(>$YYqND)q7y$|#dQ=E#(TS8q%OL*Jf{hDTu*ua2C_2c zZKO`zjP;tz9hyqRG8%XmebJIepDS@8y$LM;2!lZ^H9Cc?>#pKq8~>&5fv=}@KblHc z)%`av)TaA%(%qvL-4DE>rB?IX_olw$UYeHPkV;q8UHVq-y3ZorJ^#nLe}!0CTHO8{ zbh@hUXKk?e5|Ql&nG%vRrdq9P+M6WM!Ng_k9F_< zRO@r+eWue@b+`SzcHJXL_sO;BzVZt#shZ!%Z_!uWLuu)kw`#nq?rXNyu6q>eKIMO` zJNT8>=Wa};tLnb_>)LgXA>F6eqC4kXEw!58`P=msx5187I=)keR@jw81!Q?JS%fM{s@L>D?B6_uTqZth)Ras?UbZd zv0=C4UP&6^2P}C0?8jaZq9@CMghF>bQRpSK^!OPB0d-4OPBxs8vH?@ls@t$rsc_ta zpQJWyE=@^c!($4K$iZSf>>vf!Ll z8PMfW$KPF;p>o3AsgfaAuXo0VZmd>J*J}Iexs5hSFfcGyS)|Z5#wKVg?07# z-$)kpuOhWJd7Mx#8{AcDW%{AIUuey|q|Nr_L?<2&&>RRs_@n1;Z z52z9U!}%)ylk!v)+&@(^sEEB(HA6U1Y-rwb*cDV`ambM%spnaczTMV;**=7yKt1s;)e* zZo@rf!ysjY&lsl0MPr(3&C66WXmbS697+ zR1X%a@%$`Jce7?%_i&XAxq8KV*j=K~BkQiZkiEL@`$_jjT6e&pd1E{#?OmvK&r!*c zt5>#%-S-um(p_*ZxbyNG3E#yFiK>g@HR4(e+nd zAYWbozexY6H2vpD|9NmFUvg!K-7{NAYtod_E1CQT4bz>ix`e*Evd2i-SfPxblj>MM zprkwQ0hJ87dPRNMJ-g^1sjs@&zPkD+Nc|OQz7DP8tNV~r5_0vz`>=aZp-1<;>YDuO zs#lWgNu-*}_Xw%iU2}}i<3`0Y8FKXs{jhtTLerGL4DNVldiAQhcE7srr$~224c$K| z(YV7X(Yp0Y{;>PBLXWDu>Kgy*y4R5IsY-WW6>l2Ur^?nfr7Cmv?Nq_{WZWB;l^9!W;I@YY-5mEcW zop#E{b6P1)DIe**L&3fHNq4jAMgrB9y-dok7x5JDE^E=%u60da_3@Cpe#3Y;y|Px_ zXP~;O*GSbI=~qDa2clhY*DD<%S8q5Fb_cXknp5$tO>@=F2&!v-gEZfqmX}8;H+|bG zZK;^vNe|=CPY-C--3qGf+CaJ%rs=9yZVK8-9U)h5To87DRcKn(sHwT?mIl=|zeAdD z*YTzI(6!`cX@{CV9*^m1dG+I1?MpqZ=w+(zc~D)~`=skGtxNUoGB4eZ+DE_o@5A zcQU3|yYHwJTHt?6kaTZSC$;GA1I9x4yx)$ddkg7)oOGvND6OPZEHl30)T`gA_}m$475>8bKdD2jHm%yXY2UUpA+|x!&&EF$Fq%#^ zj4e1upYj?_`%Q31Up}gwDE5pA8AE?Jji&wk8K)1=l;3xpy1T_$UK4TrQr!biveL6Tqs!XRQf>&T3ovNo(`!(;wNL|L- z_I6aMA}u8osU`Tjtj~D$W{)vzCZ#?c;+3gG?|6(e=98s!d;)WfRr5SX=eC6Xd5y>D z@fc=&8V~RC7#B}MUK<}?C9XQg+`kQ`bBr@^g*Y7Jc%Ay~POroc>y)`#(Z%jkSASfk zu!zRGzacQ6(ep87^YeI7Dy+YIg-XpVR5qW5l(3+!P8oxhlu7vHSL9LhXlFMsryS#F zd}@z4GK?h!hOs@5Qu~*BIps69{_ZiR9!ngvLQ310vz3$=lPWH&c8IuS7}KDaB9vj| z>D0wC=T5pAz@ z%mateG5;8ku?Xu~d`79G?CVokr9K3mh}9dAApCf6j#sAceN3eW!8dXB_D6+BKBK&e z;yo5+O-MOSrw-n&OODQu8>OZ25;^nOO0xdvxk|@zdzIvFx2e>6&D%lelk)PuY%fRL zXY`7w{P=9SvgNl!ieorDd>7wP_8Ft$In@uJ@x7{7YF&=0q^g70Bly zYN)X9Ft$1p_MOm2*yk9(OcI_u#xwAjI2_~BekvNV%LIpG^pFwq8RMaY;^!FeXpT^4 zl{1D;&68JijE$INPI?_93%nxEr`=;n?04PX??Qxx1(P3Dwsepjj z7>i(^Xs@1Lu1e0P`6}f>-y>>Nui*;23#CTXiZ`%;L8N;1P^qo3Szy1y7Li(m_L^Eu zpYhoVUfG_G39GB2$5d*OPF<=~Ic1_>@EOIsm5vj(pjYR5K26iTu5Gzr>wQ_bjyG>q zZNzD4$AwjwKBLOeWZ7%@jAt9DtKnxwEAKOIc}HQRK2ds0$9kj%V-a1lh5p{72LSxX zBP)O};8{xGNvt@=*aWBeck1m5aU`<)5rwpnNPLY#@a?$UW`ON%P?;+ zYD~RW(3BZ zt_wM>J;yWhF?tDswD+9D$Y)y4ah}1R8PL@VHBU(H>KVl}e`4H5$o6Pg&*dJ9^I+}i zNuKdQ=$i7hk7putZ8)Ihoa(un5kp3Gx^bpwjzIoWG<`h}FmmESg`DSE&BzWdXOQPj z&wbGKr{y(f(!kmn>iLq9Vo-@5v zHm*h4Ae+m8^kuH&i*SZ=HowkH&$)~=lt@9=OwT}}3y~)p;u`Fw{5ol%*SHeVCuA5S z@1V_A$nD7I`U#eM25b&~|y2FxUH-q@eCE{x7ndNzq<-9*n zAZ6fsh`Dy5ehJC5Jt`gpx2N%kI8k`vyLNl|?ldIZ;OZyl!j9c#J;a<4a=k=B~) z34DZ`a&YuD!k+%do8E?u%$cr`cfG9{S&vNvNKSv_BX4&`c50e!-t$y4=j@tcp}wlQinfQ7`>+Y>O? zGEHYmb258BueqNhRu2k3g{Y%KVm*XV4-M4I$f^ zYmJtZW$s|46Khf%^=g{YMN$dHY0ccrns7$^(^w@kd$l~EzAN&=1UqvnlhJ9TiC*E!N`x2 z>pD-7*_yQ(|;wm-!u;ejA71Gi?g>7yi5yNO@4rH1~9#b@}%;6T5s8hA%HfA~7vrf~r zGb@T2#~snloA_o0V)!ct8A9?yX10&=^k%JVnc0A8mT6?U z*^6o3->qxEM>!-UoW0r{%3NoD zX)rR~yVktIM|s)-<0!$k)||pL_cirM^Y$qHVeBYO?Gt^D(CR9%Cdz%785QQCan%91@c6dBSPr$lgk0@;*L;JKYtbesnhh-ZEl9ov&oA;lpP3&pavnx9 z3fTlNP%pB)jFcIlnIAFIA7z7R%8bv=EsXTkG+WFa?7O3pZRSrb`6l#V#I?ZKW?Fud zyhh8}X8Qf)=}D4iyZ37|hmkUrDB>a{pOG^p&1N7CSWXPS3wypc<4ltw{o3sP#%#ef zv$U>n%wk4bX|C;N7a&n%!{bWMZnG~VYtxWHekz$GZW8t+J-f|etZRby>nC$EBYR{F zH+z3Fr?9S(TKiAfwuXG4sF9z|>zONixyN`O&mHs3U(C6T9EKc1HhX_DZ)W6!M^iMn zFw$7k5Z7&toRlW#4n{Uggybw@FCItB*B<|;8^4(MGO`-avj{0O_M1;KQg5yyk>Abd z{ZubTqV*Frf0}QxWIu9;kOjs8^KIrD@Vb(Gzo=|Ily?lBHxhm10#i*-9Ak$r|lx?1-zQZdLYkrS+k7+HYjdV;H)^*19w zW0WY6?pAS-B#*1FkRH}qjBGAZNKdPRk)dZPq?dI+BePNRgq#ztO^oz9E`@Ai#54(sd}R)?Mlj8Wa;0m8btTjM zEsb)Yc+G7UZJ=yw?1NI8Sc%==KbbG%M6jt&*K>&A%6hzWAqp`)=T6d zkTBDHE#(kRoN2z7h-FQ*T7)RhGhPys(e7C7LKLxryOs8dR(s}}D{0D%q&1n5MH-oE zUB$ZI{MKVUfd6I2G>e|)MvZf}3#5TH&6>)(-j_56Xr?nw=QNrbjAX4%$+?b^mRVjY zd4_cZBfWPh$=6u(7}+W%H?Xd=<}>nzmORT^$Vf&SQpw0SnroJI2V2-sYRBENbr;iI zlEx*FNE&iC)4aDv**wcy%+V-E4w(Jjl8E)~k#(xjBWbXRd9MrVKQ%GqN9VHi)atxYfd!u8=!>d*FQl zmiB&X-eut%UO+NXQwS+D?yWo^$k3*U7D&DUDa*VcKAoT}~FX^m!NgtljwHItD; z+MZq3JVqYWa&}vHF!GDG=V$9*jNGT~`PF)fkuSA9zgqZ)66`sw<@{!CVq}uG=YX}7 zk!!R)2d%#ud0)#pXyLm_kh4$AIc!B48LH)2b`wU*wH%+_nUOA9j?c!ok|5_*EhlK7 z&PXFICu9$1WUKZoVwW>={2o>6BKFmcysNn~?Rkt$lDfV!qc*;w1YP;k9?Pm@KgURC zS)RW#>)7}v5@@z)nrwSBBjdD%+4eR@?$wdcwSQsc6s;@I#&?h)XOFbmvg+9dj1+5K z^=y3C2sH0#T@CC`jP%!jHL!az@|*T6ZlA}<2eNE@Wfs`@s1f8$(7Kw~GZ|T^?P+4; z8%3ZwPU~uB-^0ks+MZ@MzBvS%-I}Jk{S+e!ZBKLiMMfH-uM{m(k-dqLm0H&^Hoh|i zIsLVq5*tHzAm?jcB{ser1mu9GX=&#(aR``iys+U65%eEbJ;uGMn7+xd*#uXS~|@huvv!Q9pQ&jgRMm zrXf5fq|E4#FU%6M6eWR>UFJZ$86!`?W@tVj7c&yXc#M#P=16;r;F{nu-g}3*M%vdi5;{d8qwFP&geQ0<%@y|3 zb!e zIZ2E>h2*LBE~dG9tD>1^A7rGnMy|2_S>*dYV-?r6*fE?e+=yp-Le8~zUY4gIYHWR6 z&@3>nx8sb=#oZ{;4E0@aH_4*W=@ZgI12lz<96T&&2q|Xd>G47`A*GBQo5t0eks)}B zMUsi;SVk`RJSFEiMlQmWH$`&_BU>Ly(FkNu?-b4HOw(azb;Nkt&~o}Q&EP*$$oY&c z+nADk0VCm!DVjkXjVwGpP|+C9H2=a=BW2-8Mh@>x$r;5+mo%Eo87cieMRNrsB{IJ# z8s&^^+m@oal92&;zM*tYVdRB(QZ!dH68b)cBpHdFmO^H5zHgQhD}%0U*{^3F6*NPA zH`ucnSt9d&1dtoDsPsOBcM7B(Z;tG_EN7G~nPtXY``#>i2UHhj>{dKkD>LTV4=~rw z$9Rncc%QW2H_!fnxomhr2+?d}nrjjQG5quFkFusi*PvSzO{Kk;X}&E|$i4O(>f3_DgGOt1*`|aBpInV`@F*(0}v+okVf1r?q_Ct)E)KPK$Z9mM&{aRPZ|1U;P z(@2K@5k?-=+GGAlF-bFOTslO_sqcSG(CkyX8u_1RWcFT#6!>3Y#Au?pn)qMLrZ#Fc z<|dHMcu(YiiM@D2mc#A77XI~&G?L|Tm#@VCCd*kU%i$g%Uo*{PvW)!!`(T0Ta3+QZ+eF4cmzlB>+<;qS~`ui@TYNbc$H z!ASU5h4l89G4h*FAp`w`7@2UJDnA$d6Kqc%ZO0BP|zmbuj zbS_`!zljmwU?ur-|2#$>&^6@>|ILh)O;YyU;GfS(Yc2T}|1FFZuU9lH{0kX5&`=>y z`70Trd7r}f*ZjA$%^U7k$lLx!j7;|`WTSsEbG@(Syysua$OOCx5pq88KfuV6S&D0u z{~<OH{)ZXabBRLs`!;F6ReU zGuKVmD>?N8YwObUaC3=58U>zZt_yW}ZXS4>kz;h}Z4vl@bNNx({@~qc;4`Lq{A8uQ zXW$n`nv^Q!?7(40?ujW~eFJ6=mENs*7b*Pe7jSZ@9KQH~qB%E^$uu^e@(G%A19fu9 z=JQTe$bdjTb2Zm~T@+}_T!m&817%FJ84W@PG1v3xJq6dCz=cdRZ>vBI?;Ymn&WfZ6d`Ayj}Xe8ZnEb75x6BVmAQJ{uXNoRxQUUAwVXQxH#4$ZwrqxRXW&kj z^W{=y^PPda7`ep~$g;rVz`cyrlgL5elED2Ov0pGE5S~61Sjk9Z+2;oQPX;zHmv5Jl zvm)?hU=!0ciYcxq1D`O>Xes%nz{&uokioBCO~G~0w<_Rgq>hxlBCsxyolAMKO19yK zu`ZCuH1|Itxbi*g0{OXAR)4Ldbgc{2W18=-5y(c!IhB#Cc8apGG4NdA43@k`w(5rQ zeBdlb?w2DFLe68Zmbh;azCRxr$;bsOg!UUeuLj04;+OeeX1p30C%9zplo_uFT$X&< zCxXiWGLdaA)b0H1fvcJ83)#-k5Bwf@j=6fvF^OUP9(aL~WwNFa@(OdU)%o>%;59~i zXv81ol1 z2!w)vG1nql#;~3v7|SEBHj=9hNZmZj(_f@LSlbb-$H|l#L(*C3NG&@+th^1Skf?(%7DjUb?ve7hn0we2-RcUD&EMp7*Jym$o-)I^4%!q4|8P8 zjE=z{8EJ-S2&8lHcSh=Jq-*dnBb#tnB4~O9oqY1$uW5P(ql}bkq%2sMk#1Vo$-#z< ze5B=^7A$0B{clS08NuRw%AKbqg11G%HcXSH<@619VB{w)XH2k+kv{NASeOi+%SZ#w zbwh9GZdv zSs9$iNMDVt3NFm2lF&EHE9I;W-kZM!+MDD4izFMy+TarQ{lLK#av#gN5_j1```X}x zj6`mdH0E=`M}_uxmG+l|D;YWEPldc5T*pXA+w*$xdCcOA8qfAsG;al8VVby>ygB$5 zBYm~xPlE3;^3+)#V>13@K0t6Yasv~1$TnRFdo)4-v{?F*PR;KAN-9yeMKU8 zQW8AGH2xl5V;7gqgF#O{Dr4Py3FKf9o6|E=DQNU3e`cGPZRVa(|%Xtb^@ zr%ye~WkYgx^<_J!FwK{mtFChhx3Y{w$DbV&5J6E$kUuoYH&iC`3-xzsAM`NJ# zCnM+RI1h3TF;ZV5%bX#Ow?5U2&d=kCBiEFnj-QdKsJQ|e?t~Z_kM$t}8R0}3dH+s@ zT;gOi(p$!Pp>wH|%gAtzjCL9@;^b#ilbZ45S8o9>lRiFBT!Tmf&|7LV2ew|asG+&-0kTM{rF!HxX zW;$mw(sYZUIoUVU>BmThl!MvM&LBowq#;8Y`B}@E<&0$HbWL-;GX`@o#N7BDr%>;C zy^{bEHBKJsH3;c%+~`bZu7!ABCurt6_(msa>imw?4a_y)nZwA5czP+2Tb!Git2f5# z0=d!5p*V1>CxmGxDGjfAQo^!S_GUE#+`6K6d&adqfF^n%APXj8eS81*< zoeW0q(8$kDAtQseoIjj)j9jCU%+TqKT&9ui(7B8((MbK!MT`tX|GxEF>YEBeqZnDV zT_KG_V;W3{Jv-1_37RIMaSf=9oh7*pqcC(6BXcDeAvd#}$y#z@XaOUSYst++w=uF_ zBSoP*7^#oZ`v(Eai{j9t22>K7!e)WA4n4#)K~2*k^f)8$;4V_obP26xWVJ@hLa#Bh zOe3d;-eu%Njhqwul#!P-a&G8rM*MgpEhG;P{lv%uO*1m|CnMhD70tMi)sS-V#-YmQ z>q1dRe%Eqt4mD&XUn94MnlrL|s>e9>F`65HZ>TjRv!^NKflx0-x+N9zaHt<6&uXqm zLL(SiZ+MMIxb7?uO>9W*#++#ic`|er)2#IhWOL}%(6x+wDUrdxS3}n`^7vtol=Eun z#)gy^UtsJfB)=A#$28@dW_{=uMqH25^?K+w=Gv#Z-U{8pG_y3EY@7#g+5?po7VM1=wl#wccJ6FC$v>?N$sEdehO`8BcoHKo zX*napH#71o#<4=qi12)tyh9>G!xO?wnIKgvkAG-M?s4@=}q&^*h?H&Peo zVufE~B&O}TKKvFV_iF8x;mwRJ(p-;(e_^DdMjj9U4#Y5i)?6#Ye=`k@y@eMqhQo2n z9UA!yKxj^|d{l!pAc*LDOsr_hMv}*8W}ibVdehEf*mzBN$qcj2Sgec@KaD3`F%hd70dul2Hx-ixe(J%;g4W3O@C z40^ZzdE_nTdht51amjfk=gY{)9F2{$6!L3i57YF}G`~jUVOWKl+4Msl^Thck*8c~{c(j;zY)!pOrC=@D6-(TkB;nr3yzsf?U`z3{7d zZy0%9A}b=@GIui4@CqfTN9Om8d?jg~i}cLg%Sivry~c~L(g^m%%%2$v{p^v**_nSa z(gXGgnhP`Srj%dnqY7~|gN%d^D=s&)E+a4W^cqpjJ1H|JX67@};UIV!xhgZxNIQ*8 z&1}j@?{Qe2?<1~jGFvb*U_Dl&GjdmEdqyU~9)aAG*@=-GKfx+?rdg8Ng^_zT@?d5U zMxNBjvdrF1sqP$)k-6Y{B(qObYBR6H9ng;@lsk`QUeeT45H-p%!-S9l8Ot*-ZAy9S z;7**7p2qUb%bMPYvT?})fndiUAQYcruVS|!ma{T*EF*vZA!rIR)?`j*j4b_AAZKU1k~x@3a*E!Y2nB}W!18c%c&BYVS2vNyVq5&v|B*wKHpoG*e(jz4;!=@Qs8 zI}Q1ZxyEYW{n0~=+#-FSn-Ps>H=}%aweNMJ$1pNV`aU-!9_<1I@6$e3+M7o&V490` zG>W1_nt{YvqodI+dRa4y#@^+EE8lZUw1T-VO)5F3M5lr)YV5=ldlAFaqt`KVu`bW2 zM{i-S2C}S{8D~ZpGV;6Depd7j=Gr|&Nbc=9J9q-p6vT zcvv9&GtP@HW13Sn&H2$snC4tfGdQ}EX|B~YL!#@L<_1l3ar9-Txlr3PBD$Vw;x~(0 z@SHI+`X(b;TF%Jm+l(A|NLe^4`X0+E(KMGvH!;m|TG!a6jB+Rf+yrc&XlOd$j92#WHiLcNUdvbw5X8E#$P&y^P;Vo=44$GZjQDE zVi+Gz^B64#d$7KHezZN)i~|=Ty*-Pg*kl%R+M@0Vnw8PMh2-gk3%!h#8BawAF-_5j8?KPS95KQ-pMrYXyo1KQbyKjS)P@@h%YZ%eYv0F+20?=sHFQ>G*sT zeV&oQlBQGUj_50l+@^KyjK0Q5?q?!~MVa44-(=)5O|vWd4trWh$|*DUMmIA}$q1ES zKSe)bn}5+Z{}la{ksCDPivfYnh1#A55_wD`1+g!gW{X7b$SjQQV&q{RvBKDHM)qo? zMeG+wp14eT(JFR`k&Rk%msqelY5z$gk7b@1%VuPi_VmP99wY6vUuVWj7&!<1mnc8y z#M(BeQg=}M)i2h8X%0LrqOm4(EFqw2kM~5x)zcUo>%uhiRtaQHW`YpVe6HmrV%?dh zwa&qb67foV#%4~8^?ZerzIT@0)9ZrUlp_3G{ z9UUQ486uS-Q=u1?c~;UOLm?zX#w1gzWGIq~2+fp=M)LnYYrSi|$8fvd`~ThF?|eSD z`}KX+TF-js@qP9rFt%pqopwz2CM3s9PHamuS7YX2nYsy+6WbB98DpGf@YPS^UFGaZ zRDLltp@eCP_mmro(i!b(<(O%S_mWl`#uV0iI`IKw7UI4QTb`5XlAYt8ok`}!#1SMj zrY~#NO?WYJ3|XF1mbG%s+{CdYbL&a5yeM%T$=rcmbhf-WaSF*4^wNd%)5K{MS1a{- zP2vkI^QNS9P2xOaqU!Vd#KpwS{z5EoNnAm3{fxej)AeTJN|Kq6U36hq5px#*W#+BK zb;Oh%BWZs(aT`h}!Sve8vFDg=i64+mKQ9l9CTvUmkeKo>isknb_Y-sfEMayg9wKIu zYJHM;S$W%1DtNh3A>q@+G-769{=@$4Pb@=B=1XGdv&1Z7W~kPo#ID4wR;|N{{fL=^ z7}?Iz#1X_y#|XmAuZatY32I86;6`Ff#z|ap!MBMS9uOuzcz~D{$^?Qx5z_>_b8NXl z@VD|d$F9WOhM9uFKS^e&#$G6xSi#Ed#;Sy6iU%`@*``{T1v4wy{Mj>veZJYbELe${ zkJ0B`+RQPlFG~nkCguvHU6`wh`92MAp?NY@iMbsm$V_6eCNW1-MJq9QJu!<>dqt)m zF<-nTTA^SLF(s}RnUcZA#FYL}WJ(6{mbfib%thHw$zTg&>^&}F?jYvXe9WE1e7VQB z(}5UUvZB?AW$@j=?t*dV*kvVy-746&jFWIvBgyV>ln(YHnGfURJf=)=Kn2?aj9SPt z=}y_;P}1s)UV^pC1|KD@IhadW=4K}|IErLm8oJ|h;#+1g69_IM%WE+Mv07Ua%LiX3f1;QhTV}dbKKKgRS^bM4=5#{&;4-pPOYKw$ zZX}uH4jlXGgbKk;B=Zq^QESJTiox9!*GW$+2h1MQ+U@y#I^n9|0n!?+T2+GIl2%=m zyS0;JssxV_b1!ZnTV_z=t-<5Ogw^t`!S9K=-%Goj*d+KPG5ayAv|1+;ngo9#W}TOZ zOA?y}&k}RJr?oCI7L2cGYfB0Aepc(c#N43Wm&2D78dq*GiI_&iSY}INi(o1-pL;p> z9+(g@+tDAgo!f(@h-spBZVyI?8PSnbx-T&=m`Ti^4l~y!whUfH%+Fr_pa=Dy%CVzzo|Kbp`x zIF%TC|J>%!>4ZMPX~g8-A#ptzoL|wlrf;+a`v#YgOoNspGa$H&k2lo)uLhTF=enQOspNrOz;89{4d%0l}hXsEpW|@}Yu;3YDT6s0$ zW@mWNTxm;Zhgu#UEJ95Dube*#iH`7+S7CYVD^GcBEQ!6w9)Hytu4P|0`{MZlkq#Qg8sp^}JWZPbN$W4k4Md+6JcthY|CPwy&wd zkypB~^M$sHCxYY2&W%4y9!?8RB4$B-mx5b}=~7R$ z76jiTrqB%{^K$S5D);!k68rMtVUkJ5cWrFv)!-Mz^uo7q%&ZC?CuZUplm(@9b?_&$ zQ|kw@vo`n#F>PzHR*qR4{EM{eEOktBN1H#VgQk+L^BWEcW0DFJ^Xds<;*x^IwEGb^ zGf69u6e8vx{EL}_Nu`LXSw*x8CzT^+E%K0MiX>f4OatVzWpYe%Qf*>_x5jxgDM|H- zX?;YrQj;1H(>_;Z(v$Lt>5sVVC znnp~uplH=hnnlbm^Z@Mh^+^kf>4@KfW~Oe^a$=kWk*S}wo|sN45?7O?9mJf)yL_zG zG-)p}B~hc8i6tE*rsb2Ob$ik=V)FWkOq--1iK(bQw@dmRk7Fj7gQ#QH&g#U@Nd>R6 zwPl*;p_|w{sW36mXz4thRFs&?Xv-Y?;G|??8f%U{k`yN9rFjzj<4NU+S)C~!j!de2 zm5u$xc!_;fQj}zxqYbjZx?9V0T60`5H$jnG;Ma=J-iszH=CZ+<`m278LQV(LT&~(j7>P^hzCL;4f z(g0!#XzVX04JPJGe7V4OmL`oOrlZ#I6-kqcc{)w}c|GZQVz!|-X07!}^N7j(lVx(u zhNQ*Byn;P6E0bg1PFh3Eg7ISKgQPc!X@RkcWj;*WMNA#Fvp?xGVjeCbGG8YhBW4$7 z)ExUaN#7IG3;mB}1|%Iz`iqz@PfF~^lJFcR>d#K}y{z?pQek4g$9jijPe{%HW6YPu z#BwmXOl3PVw)QONm^#VzNGs;Gcgy6EOhfc_Y^Q#5Ba*ouvzv z?QC|;9a!(>n4!tjh#95KqscQ#%f5tZWo|c*CeI?K)^l;@8^|3^cr1AdF{K6zGd6h@ zF}J?ucrxRYUngef7Gb6)ZzZNKS^?X6GI=`~V+La!e0rSqXJ+!JfK0!?Rr6RLE`A1@oV7<(CK1}|N znA6#!wKw?;F?Q|t%r2W_dy~!8cBGyR4=s~pK1wcjwXIQgY76s8a_DMXGpk+_XB_+w zBV%$2Vw&OoUS|GEE=|lEP4KWV$(&8jBqno|XgMhriRp#>VVOWmRbtYVDV$Q9m@UUd z=F*gU#H_|{25S{dX-G`P*F~myN>gI4!o1GP^b97Xv>@iUw?!r)r4=#9KVUmOgTa&z z#OyPR3^pw8D1aPC3nafj#5mQTLN~esV zT>S~TYGravnUrxP(@;~fEM)>Q2Q>B-DNhpK*|B~dD3-p#NZA?%3+e3 zg3&O9dVe(GyOg8EyoS>XW`0Qdi5UA{B{M&#{7KBS<3#3EN_-Vt$Mz#PtW1viEu}DK zM|iaiwS~2u)Jut3qjANjCK9s^Z>Cw9v55szL&Tg?tsotWP0PubM= zl+v!q!=I@KsF>QVifwTR(c%gaQzi9YFnHGpGR#C%`;eV`3*zOa4Yr-OPVGy~Zp@9J z*ln4%sSgoz>sH5O+NZh{S1xkZ%HZ|s)W=AsS{asc6W!Di#2kE>nfZw$Q%4iCZ<^1H zB|BmGv%agXt0Pm#6O#nXUC>%`%!Jg5#59G>HcG{$)X8M0BIXh~9j%8ircNa$)4oJY z%-q!J#H6dtOQ|!78MI7f7N*W6=Be(&EJ|HM%r^9OZ0D8KRm9||o#m+;h^e|7wTa?d zmHG}b^A-rRGj%sH*L8GEBdWc-Quh(F8TFHy4^lrPCOkIIJhZ~r@DEeJBxXtn$74Q9 zJx*zV61ibz@H%Dc&m^<8ooF3NJw?pyRigEE>RHk{gP#9GlxdFnHnqStwiOIQ9=FBdy)(i#%8 zu{2&JyVd4c+qB!r=a&!S)iN-6pCs*el9@S&nWG7v(pnPp&}YJQNoz;Uq%vaXp0v)y zY*40K+P%aiV@1Ysbx-R>%sWU0GxwzpBxY(Ok-0zZ5n_g7uFf*O(jF%d_hxWhc-16r z9LXG0%R|#95VKn?4^Ml7n5&d|H0>E;Y#y@Z$J1sLvjMe*nUQHP5!3EEVMeDdA*L|q z;Vd&YZ51(%%L_9;?R8>oS+LB+w9Ul)srfuP?LA^H+bgj@k+z4JLYSGe)>CN*hS`m@ zRc5xQ{Xuqa#zw73u1~_60Ja}W7M|XZpaO52XC;4x{yo{jB#waSm+*Na_5NF zWue~0{90S$N(>Dk=Hn$IlNK6I%vSW7Y$rW5l9-O@*_a83CK5AR?UV{VLCmR_L@N?{ zj+jz&g{cslLrnJx!c+<^BxWF5C0o8aw49h?7zdfD7J7}C_u`mYl$afQgP7Sh9kUxb zax|evXcIBLutH>}R_JYF>~4l-a!l>ec49hV7RNHzg$@w&7GwJKaL5#Mqf9$JHZL zf|$zRi9h#;BE-D%n#lAFWfF6l%Jd0UBF4^6*-pPu6=Ke6Dh7tGC1#bzJ|xtDm}1xm zV69=HM#R|pDl@}F_E!lK%t`d3%sdvlotXY>g&7%YOUwrJ{46sz)PhMpj%+-#AV5qg%GyR|0F49y{Cq{cNn z^b#>2w-K!uLQ9CbRb}ReRuOXxS_;RtAheN~w>4dhLT?i@N3~uK?Ib4ih>Mo1wzQ*c}FD-U}rYbMPTy zwued*^SH*fGZbM)1<^LeNtF@d3?bu@GvF*l>-vdovEJYqhmB+S>LJBhJ#b(T36>O@Tc zf+F)ns0T524#qM+g?eV$HnzRkrPm@ggrJ^;=W4=JGW3AEYan)=++^If~OD{@H!T_;6F+E63 zuF6bGPb20JWhSSWBxWDJSYgXk(n}GuL}i{xzk--CSBlKj>6M8&rZUf_S0iSzrv3T! z+QgJne`crGt!C>+FU(haKVoMNbJBAlW6V0t6C(H8+VX08!)kWD^9bq>Gpo{LWam%} zF9S!c*6Q?JVwU4X!!oy<&FQxjv(pvkM7mqeo}MfihPU-et3-)~)eIurhh2aBmt!(Y zEUOj|X2W`B>X{o#F0WPqTJ4*GCYc+-#DmGhT;2Y;iH%CWN{n6A+CRMHTyh0z+4%-D zx0Za3WZo_!b{d!5KymHzEY~wlOTJA^NxZjX|LU1m9`nO!)~aXPc}zEz8Rju}<%-NC zk6AfJm}MTbQ|;`u3{p`By^a0LG5b8GN!vJ&`K07_l6e;E6qY$y^8ISKiETz>R=~{X zC3h1uMVTWdKOx4?u`f%0M!6BHOG>7R^KHo^WI167$9^N2FRB%SIir>`<5<9lL$`-o+lIPn=j5%U(-gRB*wagr>5sg?s7zmUu`s+F4YE5-Hh zVDTq4<9A|OV}xZpX&HawCzSA8TBy&=q-UHVnG%|c(iw%S2at-7hO$gGCz5e#bz6Tf zwGA47O~4ebZgYMxUg5KUIVO^kK+HsCGBc8i$-%6WWy)uSh^eMbrHm59bW)~DMg}pl z-$koh#^sn9=Jz=}qfB+1eRXeRnR@2hjLOxALni4{(W(t59w~k1V`e&puFtrJm`=(x z$;cw6zcTk_WK&9Ke8^fYf-h&(BqnQ@FpD#;BWAOghb@AyWZXc^-8)5QNk)BQs_hYG zX+{n)M^tNB#?8dcQLW_}w-QrUnO8HK5)-enugHiIlch2%Gg`2g%B;$0NlcP5t20^? z(?_+|WV9t_xiV`rIuO%BeSR(DZeoV3%({#&#N4PduV-{4CSGOMXY?Ruw=!>JJU~n* z)!LBJo0y+fW@AQQVjfhPO&J4NMssX)#>2#HR;?`=Lx{;$=FNq^m98qR# z#t32>DD!s4Xl69W-pLq8%x;bS-HeIEOi`_E8B>Vqq+0J~JV~{8_Ixh)9J4)RI@R83 z7^%4x?8tbAWWL47z|5|U8N`g5MFd*YhZzfqspPf68=X%wmJn0*645%C@hUMxRpwB} zYGR5jb2MWedDu<0zRB1?OjBi!Wo#j>AF--qe}2e#o0vmhE66dwX6zv59?bn%=J$+U z#GJ&~WdCk-obU(4jPZJso1H@8eZ=(lWa@(XgqUp}vncJ-@Mpvv_LyqUrQt*5;p1LE zhF{kTAEwrQ1Nj_!P<9py?{%F?q^_!@rSC6CGtrhyNs|3`T1EccW7}{1^G$eK#{Lf|=pCY};oR z(4ORqZ~=Q*CD2?#$GdAkC>kCOIg$k-VAWo``LLCipv$qBb1rh&@b6mCaMvc}ag+>w|!RO{w&Ct`AyX%y~COl7rm zOSn5Rv((P5;rodBPPH0`dlEBAWtxQh5c7iOYSVB(VxCv6+rk5hIjS+H)UFfpC+c6#?>bL95Dx!xidVIm{-;3_Tf3ibWoYQ!gGn4sxn=}FR_;T z(<8iym@}%?EBp#ETU4uGco{Ka_3+{F3S#!C%#iSEVqQ?oL&L8Tb6hPy8eUIKK$#KY zjl^tGt1Naih{`qk|)%twqdLCr*$@IrtbN0{I+ESN@e9Z6* zv@WT9zRbop=QCYzJD-UJ&S%^U?A&?h`7(V=oX-?&c0RM~0_MI8^031NIW`ja_w1ja zcGK#7Cad20O!F$|Grv?gpZS1(ohrZOQ0n6usd2Zf9V?KJ(q9K@*Fp9(rx4X8WRT?@KrO0w~ zW(g{Yv{){`yyl4jnyP(6w^}9Z;^Zaz859LAe#vL}Q_b7SPZK6>ZVb%at3aC>f_- z)|dP_^egs94pI&+?awHG=%K#G9HGz!9?BnDWv($_QD~3ijPi%pKE4~D^s2_<_EH}P02j}$a>iJM~A3D;3AKZH`{cvq`jISoBBN<#lsbX?`S zYiyT~5V@j?`m4|Trb_5~MSI^6uC97@BibPQGFS7jxrVmXR2EQQ>Z>n(HQtG@iIoZo zf_^L|=*jYeLbU|FP)AVFTtS2H5Hzd3pdP5F?BC2ag6e20+h|T+p*~-&UVWwI)?GuN zd{V5OnJ8$YYOGWAoa&}(9Q8G{qw}M|VsZ_n!(;XeC9p6|Ke! zwkU<}*lSEFj1;hfy(YF&QLRjBj{Pl?J1`Kp90|2(g}Xb1+ADeyUuH4)JbcNZ&}Z=9 z(u5|0%OrHeQ*j<@0>pA%E{!u4DD?6vapo#Qui>nK(dTJ+^`5w!wh2mx9BZt3#h7a- z^g6T;Mth3kcU*~k|4s4bGawHAWwFR@{lj=x>Y>e4BaMxaWAq*5*h&WrgNx8_m(~;Yy`4njbk1w zCpk0;h~=i@bvw?3nLy0h--TnY3J`M>QG=O#28g*K=q;EV1;pGJ&kJ_~h`Emk33n8T zxefh<+X%$mx5z)v`%^${+2D%{4qb>7dk$R%#G#vkI5dnK5FF}ij!)HmZits@xQ;gi zV%=kCQOunJV(#o;lF!K~56<0>Dog!)7mD?gBd9I2d(-sN;F$0I;K8!6V1vBj%i4!$?W9}i;GdIrMM`$L>yf=_z@;cysZ|bQ#UGGp&9Wk4JG^QWPZN3Vx zS^-5(v40dV?#ZbKOA+rwWTnGpxHF2;C1M6%6=y!i96DldY%lRzxktzspNpJu%%j9L z^H9`$^)077Vwxt&*mLPyk_X*14{pYus?CEuw=-%4uO@zaQP3X`a$e=R-zXZ|OSqF$ zL~gsHQ41w>kcT2>B92t7<*0dYfMZ4>wy62;nm99_P)=n*r;*+%;80qV9P<>R(xZ)e zj!pWna7OpW#hIm~aU1NdB=q<8d=2ZtYyYvv z2GS`0^}qJnG1GCY;7yXd>0A6-I3ZidcM$pvCHMiMx8A`mAwqAaNWLtpA?UrI9rG!L zRw)o?4iegnryFxS59uCc* zV(!*yyq=D^eb)<0gtXO-xtkUU>b+V}yoRp8oR#I)VK&03lxHPsW)*SFZ;*|embgFn z7f{S?fEvqklQRT0`a*O^D7PE)Q^{9l-SS{!}jW?#~Mnyfaa$padE0;T6xSzdr z#G8*XM#arDCf;0%*|pV(H;o%}`-nIFJfGvuv*RUnL@uWz-fZ6{$i915h^+i_!g#3M zT@pG=tvrM~9vnwbF9{v}1h)mmp>mgCoW7LowfqQg6%ZPUn#j5%RBm@Ou~%E|Eyt~O zmYd=VYH*L}_H8RDggVZ0YgBH8q6UbUL;I?3Gexdixv9Tc33%g2ylJSR%@x%@Bk3rs zXyj?(9>pkGoKo3U(QrjOF#2&Ge7?akNhDXn8^PjD0nDJ-(`ntL+}fiAnOmlLwWOQK z-K3${tCi0ti^dS#Tw>k#mAeBPcBL6_&Y)+tp)vOjueM{>g1on0MNJdj!LTUi{_ooA zJni)owJrF)*~BNszjfFzVNW+HYN2i618u9d)LwnBmBpL6e@f}k*V=StQOTh{Fbn4# z8W4^1%H!sDjE8Dr9mQpIxLBM?LvA?cEo^49#zd{BRUyY*{B2^zZwu3ZkkFo56N^_D z4_2bwGN>NB+=EAhcvBhmsuY!il*j+F6l&|J@h5g6IG=Z|6VzGJM~b#+j`zpTENk@D z9`1MT(eLs~%h6JBOk=N}Vz!M`+T3-_dao2P&SKJO?Kx(HSI4oJaTn(}=Ir=Fg+phe z_p!Z{V@4>tN$tI^_R1@_)Z=2VKZeTqmh0~Gyw7!I1j}`01j}`01j}__)KvN-$^Wi> zw3;Nj+wVC+57!iQu$-X2mkT;sT+sCe1$AB}sJrSeSKZSY5_&(@(_EH+-;V6KVCCY? z7`#HCNp;tjo81eFH;cZInmuEqpt(9$d{_=EVXE1adU6lv_dfs2I$jj@(^{OTeLGfJUaQS>WgeF2$~-L3m3dg6EAy~CSLR`PuFS*oTz?+s&u;uV zjX%%v=QRF2M`kyjg2=?Yi^X#ad3>6R{>Le zMI6l-1E#Py_lddb)4BcyOgGFFY#ff+m&maN%pQDiXV>iijqdo(vU5BHcT(+MM$C=n zaLHWEp6@{*p9gm|X5D~!6js=mM-I!%>;H`94=%8rf7G}~>x;Cvn6|f=wzrtJx0trK znCq|U{#T<$L9+xsk}bD_<^ylcD`;kev-Q59nGR%o>Vjq+)&VScNadD!YlDJjJ`l^@ ziP^f{6^b_}vm8^ETGXFF*AlYp1-ASldVU@Q_5fXnP{;TsBPi3{+nUSGp&Dj&{dy45 zQjM6#Xz{juBSy-@=N$7&5_{#Cvgq5be@HcaVKlO4oVVVuF@RGUb04{bLu2lumW(21 zFV;Qv;Azags}rXqV(NeIn47>w%(I=DV^41dmuZ$4=G+Zne}h*_nP!^D#hcuH92#$e zx3F%!S?~*XK%g6MZvC6ks40UM-{PD&e9KW2xP&eHauIVFdvkWbDcf9$)d{1=uzT4G zRmU7nfw^9h{xmnuu=_FhgN<+{**|CcK<)J+={dDM(|-XgOmw#t(cg36y10uy(P1M3!m1L{M38rj=z% zDC(6RN4sT?>5Ea9Yx}2|Ni$l4@siO`*dJn)v{=wc)%Za*-cpT%xaYwdbn$GQD(3d^o;Rm_&YAPs5f)$|0RnvoNPYG${0s!+|; zKwn~8N;M;=3K261_AD1MwzvJ3?6tZ?xQ2Pc4MnS9xh!yux{Y4m>-H7q~s}A?_E(Cgh#KvRjyXg7tn1`6gIp!q! zJZ!IHeh11n%e8-bB~JR1D%g{;y4j|TqB$xz)01 z{GmR#qYCF=)O@Pw;WEPcR(#al3yry*u!^?nK%Qb{%_sx^X0!t1phfxPwZF7uODn3S zpJ=N7!1JM~Sq5bDFVmcjL$3l9H8To1CLT9Nva~j3sjhE1V(i#h5ONrQ-!`TQp(y6} z-y=4ZDn>dUYmbh{H*^Tib0>OZMZ{G5&X^Q%+2+9?jVS@-7&}gvBIH}yo#=R@+VeVg z+juj{-eIJd`=+Z#at+IKo1PNx=3|0BJtU(+hpz;4zzLcf( zUQOK7uj4H#LN*;WfU?XtpQXYp;2=XDAA^i4$mj^f}p2~8Vzj-_KsOepVdoY_(wh47-D^bmtsOC%5NWR3} zr_ge2KF8eWaC3ljT>Oif>s5Ytm?rbyRzxnEU4nv0Mr5*J@;% z+FJ$nSsG_LLC!I?(2v`C8gsw)G$N++L@uL<*%M?d5p$xnpo7RO8*iquR=PnqVy^Zq zM~vOSvav;s-L-H$ku&44w=^Z~lU zyip&w)Ciq<0`IsHDvQ_{-GlLxk_j^G?idZR^GkGft{)Xr_4=y|RthM*(v;_AQ2i3z#LCi`u-(HebV@9Ro7W zM$h|9Qx)Yll5{J1WtnNhTB^R4-)<9omtloA4jR6_sHuh-Bd6mJc*?!c`>Tc90%U#3 zQV-a_$rP{TWTx43bDVjKxLq4CXCP!y|G4I~MV(~yXbSghnPxcp99wfT%>nH8aOhgp z5=IBSwx6waBjy&=I%#XpZ0Kg0;<)EFkI=^xa6bYlYIb2?)S{TXqnzAXeE$wc+2$6^ z!fhN^Pz|$k`QzBSEPw4>cwXqn<@rOGAoSmP(6^>|u&I-vSKyyL5&L((#Lh|YivKdU zl+1sY^54%%`PzRFTV<>kZ8~D^*usMTX>3=#nm;Gs_^)F7clp=$oYLCz58{~ET+;D* zSwR=3C~7{#EX~%Oi`Svo&Pm%l|5d#IE^YnJN!yNp5Jyq31;pHEYX9rBT|7m{&q>kl ze-T@*D{N&hV(#ZRa810J73{9w zCG&$_Ef{eQ9fp;dTjw0_za(z>Ij#0P;-c`hKB00t2MM50v{o{yA7XnypX2EX(&&DU zy;H>PL+80q}%fxpU)Ea>^X4+Fb|2j^9o5Ek_Y)coqdj#OGsnI zIaZ1jxBe2b61baDek;Lq{7WK@mgiUw5jUrWSm_#I6v&rr^UtvoCXIu+q9NsW@f^DG z9NjXcJL(({$`iLub6nDq--8L~Sh7QV(P|5#Sz91n6x?!xi%ziG8Q&ar&UIhJGE3q*~K$ualBG4Eo%DAt@k zWd3JoMt1HKH5uNzCu){^>z*inWr#;{tXRAmBhD%@Te=OtoOu{rz*I$E zxrDy+_QC_^g9y*K17;G=%j_&NV7e%p>D@jFn4A;FJO*8Vw<3ZmDeehkHXJd{b>HV@ z%qQ)xCT^zAa?ChFb7u;gG}|$giK~yBnf5kIrr8$Y^ahOYmGyKQ5=|*|hN2I%DBJA8j>ueuW}0SW;>2p1W>)lPz_k9H2 z)mzY74b9XznyOqA4gEm5da65BLkp?yRVr6rLsuwwx|hT|SUugZ=%8xUReMuZx7q`u zo33d~R_-@N@82(ScdFcP_X>BQhoF7RtyAs^O5gvzy2xhGZ5Rk@hTrKsF+tuLijW1M=` zS!+Z^E#JHcs~Q2-Sf?88HIB>GtDjV3rE1Jnjk>C_Pvcmu8ogB`x3Bn8 zSlhw_+5&27j-Sz#U#}Wv6&=@jb2PSPiVCW|QtH7dMY}b3k7!M7qW(?OcxT-#o{nrN zXusB>)*9MbL$j63P_DGr-8HIvTG11V2CHrZ)hN?g;%!!4(8^l{9jzwlta6oAZh`9V zQ*MWHr_Q@O8|`$f6Sq9RvUQ(j%Udg|W;>T@^crYSd3x%J9@ zqFiD1d9?bxTDdODO;xVGay^wx)qH+i{Yz>j-q))t=qF9b3o7@E>Sn6ke3fgha%)xY zYvq>TBbH}ti|Va4F}sI^zNhwvsBWRY68e!^E}$Mvd|g7jYR&0cMYyTTJ*V6T<=#;) zS^M3S%2m2WthCA!v_$jjGxZ=|<)SLLRk>5jolx#_E#Huq?@h{GrE#>@I9e$;Lb=D3 zTdv&4$`#UhXKTE>lpCnrJms1xH$=I@TF0N!dVf&!s<(1ul)GHHSCwn1+{$^9lNA+B zS5#C{!Jm3S{zxjrg4L~Gq+ z^+YbFXqBRYI{v09YNY5Zl`HeGSZVx_pfiffE845bC>p1_tCZ`m+*IXqlzTuqGhEgI z9k4dwS!vOa#Dmc-WoEwobD6!4?IrW)EX>h){(POHX>EmjLD7|?ME6ET?G#l|-A$^o z)I+@P=GE-`b&Og0l3gtxNMOXP81^9TDT!nJPm+#T9*}j4KleU0Oj0x&do8TH}SONh!b+V3Uo~O!+1%D(4Nl({ed;{H^i-d zUeGltHdkzil|0Y?D4SACB1qda}RZv96FBL zNYqE$eATVw!ktnzKDbvQ-lRqxv#in*;`lpMNmIwSmM7ecKE zj`?x}qkuVzH8HP~#RIG$drOV2qG)MIxU~_f#mnmpw+(s9b$pu2%~ZJ-+QY4ilNSG4 zD?#g(b6nwGe?(Aoqy=eeyEE$hO!;PTvxPx6f;^eW2))qGIU(1L>A#r}akmCiQP zRJRBAY1rqE-VS7*n>$FjTX4G0p=-B`FQrBcR{=XV9Qy66!W~ewt&D_jMSs9@CCdof zhTTTyzQFl8qtCAqZX-Np&h~4JoVmh{EF->zRd=oFUx41;RB@xt^#8tkD{!#ptg)M6RyJJMV3gvuR_w6KeSfj7-ecOcb=elAte| z3aWgqpiXIwaChMkLH>Tz7W6}Qzb@14>?C=5wfbUz-;!hN^n=(NsK{^AKkBTx)oW7H zjaCclq}*1OYp7gI<@zZ1zRKk(S9!VkH&nTP=;64&^j36_qUY7h6h%7~y{V{@TE1to z*tcxr>7y~;T#BQF#s!|0H%{mP+an2U9i+{MPCm*Wo<7F0}Y zal@c!6i5~{0DUZ5sjS>s<+4=c$0U(ks9YOG>8d+Txp~SJQSL>x@~fup9_4P*cuT3q zZVi1vxq50DzhCWnKT5gIiUz6fK;>>%v_?ZKX)pCB&Pg~o#wBsx%``0s@Ti+nu0Q7)EH&NsF{Y| z>T$TGJe%uoM89&u{0ckeMKHz(%v79AB>>^(fH(Wfb=zXZw!Y-Lj&e~&ArE1G^o3(m zXr@pP_jBymHIA95sL&jTZouR2K0L5@;T>~I(MO-ijI64nrxk5@74rkq*rw&IMrgbK~eP%!u9e_B;w6-MSpd{`&95W-h97} z&z@lAJwa>H?^>TT&E2!&@Y0AywJw+1v@=0={P%lx2w$@;4D1 zDO5ZSn4jy%nTFu<+=2z9b-#hK<+k-hK+t^UuAeVls)sNyeu~o;bHDXamf86z`V{+1czK#>ZV7XJ$uyz2SU1mo;%$j-_MMD!-8XA7$~69IORRzD z4)tbQd;()nr8A8^fnk*Vo8;u#Uj=>g`-S$BJP$I>omkOx4U<~OmhG7^TV}KXrO+5T znQ1C}C}6r{r5OVkZ|=u>Fpu)S^mvXpK&P~AhlbC7QS#Fl;ivLv7)AEAUv4&3 zm@C}PXpI~?2eT1I;oBwj4SeT1jeMzro|VysGll%^?eFXEV$6Lwy>cEOFc-GQfVr?f z2aI3u7okl*OJ3R5GmFsUULX6<<+8EAF&Db=rZ#%6g=EFvD&hXhT3$ljXs?$Fm@yc2 zZG8!t?d^F-q^!yD`nvdh<>vQ8e?MCJDQfDCd8k+DPghX9h2h^?LhqI5J4*P4p@TA> zkGfZKQhJa~85 znqZ&CzMYV5_Qv==O}25a;G6DQ<_P+1ThdwPmnDL_V`uCQq$tyDdYJo8>@2qBnGx>& z59IY_rnw`T?ctk&?y`sHkE^xO%Wr`d$HcsPnrTY^iZ2)`uk2TJI|%t-cYW5C^Y47Q z-RH$HhYV?v}j^=8yZnhR;RJg+8-A`v!*}Tg1GM{`3I2hR^=>Ja2|I@TN=AWeq-g`5dJoid1w$LO_Dq7i*M-9B|to7h&tuJ4Dtvi3+u(@k}!7P6(*XR5#pnD0f ze-U%V9)1{;rnfzuVaEKF%S$ry6t2VdzGT z&mEW&M_kOk3UeRc3!neJ?23<5-oyV{gd3c3{N1Nxkf%GN$;P2b%^U9_f1@? z_RcZ;XWwzKT+DrFz3lyuQ&b3}fvv?cx3q^MCa0uh_$I1&6){KGNGjXS7Bp#@#PPoN zj-$Q2%AdP_Ds9^SJST1VmQ2%@8sJe4?^$~O;cWs%wLJf#rkQ%``{(;y%WDDn`dfQF zKd+k4<2W31q0f%FcLR^GQ8Q*Wr!8vUz-VgEx1#1I%!X~AM$HQKs)c$rB+7CTb4pvX z@1Jp(bj1Y@Wy|$2f?(#x>TC zwFdL=h>>+|o_nw>j}Uq84<3rS_n`D`Es2`es7}& zU2~0*pI$$e_H`J}p^KG=&GF06@ro^<&-s@9oGkke?~HMom;DzxiP5T%^mX?AHR}s@ z8_+7a&wd~2w^%eOiOP!AY#5z)C6lL-WO=3I|M%G*T1`P&zJMr&wH77M$Af_ws7b# z$R{q#KG3jx*PP=ODF4LM{G8NmypyBz16yuZ!=d`ZsPNO$&gWrW!Lb!ABIvK#y!OM( z;oi5+5wpCnv`a}xqUndXi#-2ulY`NhDz~X2VkDn0RwG6%mb7idSjKYOkoOklx!Kq& zuwLPXy5|HrNCR`FHS}?;dzd>hRnU)52wLy;-kfsV%SW`mMW_vOyr1(@ zdqP?H9PXdKM4oA8evO@7=tfMFaWd1A_c9{Je)+;*!$`Sdo`^X*YuLLp{C1Yl`BrQh zv92F_F%6M(%)>g0uT%7^qOWe0R$=F-c7EWP8`|(1B5DqJq4~dK8GXOBDF2%p8GGujnm=_Tg z%wQBXr7&-|?L2BuJ|(CIa)Y@mJmk=KUR-zmJoW2=Ungxn;5_hi&Cl_MMI}_;g2UX= zn;D^XdifVI7puh~%rUK3`P+V}A7sNtB0dP!Nnf>{Bl{AQdPGMdm1X(-qusjm(|!MW|UP%IQ#8pjC}DmDr4=!y-#?KXxyjBm$SO|*9{#VHSjeRW|kaB zt<6#slQ2JJ?n0kA*V@2J#LT#j+iJ{}wu*Nzb$s(j2;YjI8-A~sJeT)g@p}_%INq3R z&pCM=Z_ha`^7mx$=3|b`%-f@X=Kke_s@U?L|xl-5vBj@V)Xq{rh_L zJxYP+L$6j~bn%hyYww#@>`-9_&tu94+}dEYFCr)lR+#HjOVAFL`&8v>s$4gfE25U| z8#rv`L}@|x4IJi{YJYVp_M|zl0;+o$CsQo9L3Q`5ZX++YOsXYTHE^>|0RHTEEUH>v#==Gfev)b$V^W3X+5BW9Si>z^_^!&CKcczlI z>jmBcvg-v#7w!e&UIAvlT%W7xm~3kub3emV);$PM8NCQk8ErxyScJ_}%-8G;G~V>V zh|?V&#GBn7!tcRp4cn)q!IphIr^!@Zpb@}C zjCY>AnKbIWE2Zl1*U4JVU%y4oi_dcpoa>&RDKmw(UauTA)iKi9{yAy}6&KX`azP)J zV-#~YU>D4mKK9qpw=;?l3t9^~=FWO+67&&h)7ElCU((5SH=%d5a=GqAZ%vZxPFFO; z3w6u@^e-!z#@It7-wf41uYMs^**s7yIQGKceL=Kfq6FEgSIvZsIV z{jb{g2WZ zHPQn|jo;hW@@itl4E4$qC&pe`M$CS%Ed9RN-_@|cXeuM3zZ&b;n)@~1d%T~BR%S~Y zzrr+{n`0$kN=%G6T?qSP!yHRvkf8nhOfvA*g3NLF>G>nq_8tD8KjDY?WWoI5|l2 zrH;3Igb>Z=0^S~D{&7{x7r!!j5i8d!_nTU__N@0fHAB1Pp87D_C8PPsaYlY0VP{a& zsDH6NI?rft!OZAc;$$b)G1c+4gdNqQ=3&$!+k-@my>-uLxVC?`T&}LlxenRbq9%s> z0`{&()I5v3Ow3JzF84z*oU=1(Sz5SV#W;8IOHE#ljhgM+`#e}vLa*}5C~78nWrV)i zE2F6C=#^2_JUxkf`TV;i+r3!}UZF%iJxsY~>pi10t#G%8^581Wnt9xBIgdw!M608b$L?eFaKxltB%TZO-X_Sd=$Z51lK*^|cyO^^-uKGZ zp|$V;y#mVU4Iq3)*MQqX)HKAIwT(Aw$~G4dq`$|n7ohd9S9P#E$S81F=EeI_vw4Or zeY;;1@%o-Oi@aFg=O3f|p36VS`MaJA>xY7I-b=yKhdJ7Z@qG)fu}5Zdj^mCx@|o+O z-QD6|z@Ec0^7HT5rNWI|BXi7WF{)X)sF~>@zir`sUvskDPZHa?R_vRtxDkMTB)dAs zJljKmZzXEZwZgsag}&e|xb+gRU*>+E+WN<1fZxCD|6XowSUKKJ_Io5>PDYKWdCD6R zqvlfd44fM)ipX4fy&}J-x!A0}VX>=4CHMF?YAtmjT{fs+xIc1NX7j%$wfsV8F;(C0f{1oCon#i)|Z>H~t>2 z-wQ|^bWDSS(pyxE4g?=b#al)r`6gdDeh*PBa4O*hCf_h~sn#gJ><_7l;UoICTV zrYC2)x|5LWj+@1$8qgEAfDzq*xgGJ^IutN{;UA-Gv4_m)7sP9k-)}_CV$?7@^KeWQ zb%;HP!#K_8*D``$*O`%=rr`x(osmsy$h}_7E#dXC*p5+!y z3X0Sb)D*d9b0cCFV>V(vh?qrs-fedb?R+R=mY{|)vNL@~c2wI*Z9!^rly-LZfQz~P zjd;HhdCw(N$V+9+tzK5R>njREXY0xJFDxhp9EfBct|cjf~#+$``*@g)y1AcQMCg z^l6X${iGe~0(1|+?xx_z{wv(S1n7*9IoUN0=o;}~w-lQq>=GeG+zTY39x(bfDCojg z5jBk_^ZkhYJ9lN_z4b3@_Fl!~hkyS$f6waoqTO{C-p9LxfgMuZoVQjSQye>EeCODX zW#`{HminCMw#I00zsJG3hIhUYH9Pmq@7DXXYu}3K;uoWEi-G4kSK@S-(Z{+E*RzNC z@;bgG(+9G^~vqpY{afBk7zr>ru-l;;o-kgs&-Pf~j)Y$zxYbBz0 zP9nzcCNsAg`NycDetBR|N0_s>;1~_vEXcp}ejKX~TQX52>5bB_d034ooqE{SELQKH zfB8{y%;0U0j@f2Y?ir%y$V@2@JFB;$0rM4B@77+xY{t2ojV)lVt}Uo$S3#q^{f&Tm z0Ep|$^WL3QoLXF@e80iGj^}XkWf;YqRz-OPi#J~(*K8TZnGI@uARz# zt#T2xA=X`?axbV{Q62pTshs2sd(ZP6>pdguJtONqBkMi)^5Q*i5PRN7jd+h=j`6&T z8u31A#QUfb@1sV%$N7xs748Uo-bamiA2s4V&aG5#lge#L=afgyG%uC-1strXY;I(l z!jH>02sLoC!QQ&cUv8xfvfRHb!HbofpMU?<%HP+6|3CI@OW_)3TM8rFQW*W8_Wti< zv+a`Ot=>t{-+N$N&p)YuHr{_#6JzcGZ)Fp6YhgBLo z2R3&Z+4TM^uecwwZRY=Oyiw}^&W+diAm;ABj{DAtDed)+nA2baZF^hXYf;o!(LhCm z6%AGNn4(dNlD(Gan3?zRSn*HK{Qq7qrZ;6{dQ&#$$_?0q=VbG%Z119Wvq%p@^N z;-uPJ3*kH3sj@fR+#3h;?@G?Ai;_mgFc z${v~Ym^@l>Xbsd@i+B|0(vs0MYErS!&pSoeVB};Jt03qhtWzxKmnwcy_8l2(?LHxo zx^|zCk<7Fl{XJLwUY0ki@h==%d;W;%kC!p`1LUM_)A`RZWLAn7_}BBOhF%{1DSK*9 zG8x&K5hHt&$*4;WL2a%VG$kgeV@pO6(*oYJ#;t34Zibofbm>)^reINV%K7N%GUnAHr?;Z2`b-ZH1EML#w zFO-X#dK>s$JZc_o!YJD`yHmc)@A-y=euO!aU3FxeBNZidtA>_S-SKrL^m4V_S3@VY zk(Td%U<=R zvX^VwX1?ZCif1p|8x$<5f6u>KX{5pbt&yG?4FID8Upn$1W zInErXenW1H_-6?LS|!*!`2oG1A26-3#ll?seuDZA79?M)VBU_s83jK#XwpIUnsaI15No@bAS21@Ka>v$^nESTYwRI><`(6un zSTr*-Lm4-U0^z^vOvLmx$%bLhPqI#w-LLFseoN)5ePb*G{paOhJS`n&4BfEvM} z4{7KE4K0ScVM8yTr;>kMOYApJSTTDiKRB&l!TTV%F|$%ufuezZ>aqA15*m(bV5h^= zz&GkEL_=!b-)m%>p`*AL%{G^h6ts7QplZr}uiU-L-J+S{TO|%-22MSs34)Q)e>Y!Ki04-Tsv<4eZ1M` zH`KMCP@dUl25JtYMyMqgp^QAQB1Sxj81W!tI$;#Izd#o;B{1(~}EQ?2%k&31&N`F*B9Yw`FuX?tGf`a_O$?X2e2 z(lz|<7Jj!Bq4xJRd|wHmdWLevF#m(S zh&iL&qZ8znxlayrea3J2e(jiwBsa5PoVgkZ-@9yrM}+)VwiT_X264Xvt)>$ExU8V6 z$~{+HzQXn`S1-#c_hZ9%EQsTJviEUB#(?MHL4D#@1p zmoUmU6W^BF^h1K6@s$LPX)0)UwxIMhL21|n;QF$;kf7161dYNRlS7+gcF3r~MnOds zoj`xep>Lz-VzdrDEh7{ep?Gr>QfYVNnYbxTT7y5x|cTGD)G{W z-!b;mhI>n1+M?!AD#wN#LOyd|Rm>E0cSw-@Dm(5=VLoZ)@EwNNvqnt9cWfnMUd6hW zzoA?i`W!EHy9h`F*TpETr0ZO|jx6B90T+qN8Y)BHH^&e+eR*)m>8JLHgq3P zj3oBXabrrat;MW64;nmI>7l5Amy?dU=On&uAuColk5Gp>ai-0ARvsD_XF5>mj@kU( zC)TTn@s5(}9;Fe?TImcf|E-*Ew{saq%=2EkMa(kvtafzCpUPdMBpnx~m%pj*PO*(V z!t<+mQxf_70JwPbgx8Pb-Ns8C^B~F9M&HT#JahoR>h0%qys_`<_M_T#4f1LrS@{Di z+#!TY;rz(4?<$j+xrH57>)zB}>c{I-e`fPeOVG<*$>d>KjTtud0y)n2(p zOqSOlL`+nBh8lQ9i~F_3h|Q+MF~u<7;If>K(Rm!%n~#|XhdPBF8No0f*|7}2)?AK# z#_iM#@*w}WzJtO%`eEM|R@k23@2+#@ge`*EbrSWW595{(?=0SdT${{k`$77go;5{d z=QghQIK$S^$0teXQng%kEY}kB`D}Y1++#$pF=_!%8MW;%+bO_-4j=wP%BT@eQPv_fe;&+3DcBss@6OHUyvlR!nuNJA^Y98Dg~lm&<4eMwMBnb| zE)djUv)Ic*yd3%>QpD(zH-&qBoM@EC3XMZE)ynD`qOs|Sgy#M(=%b*ZpGylmTwYLP ztR=0z{Hbixm-8jhosWCXHiz=uH>U`?04LOLNuqL2(3q7)UPlKZvx+H1Y9;`_Nj zzu))!yZ^ZF`#gF)TAq9DwdZ-i_tjOTkvM^2J!FQHh9$9tSNl*}+#iG`P5TVwBVt!O zeC8kOttlootX>DHf1wZB7yST!`Nautwe%teOTYb!yXV%G_5fI-o53v6o)JrH-;@<| zu0Y>zDfOEth|c5By42|?uW579Hm84#7ytune{epzkVzy#=FmpYq?Ck z2nkn=kbTkPT1jqFCYD0iYuGa0Sh!O1IV_p5U50Iy#_7+jz3%AYcnoc|&Y10%wNR;8 zJLwN7<-6e;H@P2@aXL48eqNQ+%M@fKZe%1*Yyra%EApF`9w{PHnY<$1iTMz_ZbJF8 zR30-Umi{U%QmHRRI*)lQyZ(+of~C9A=dg4*N2FZU#I>(V3vjZ+dXHmdVksHD081Ah z7xpIR)Xa`xTwrP3JL39NQE|QYUXgZ{VOLH$-Hl31FWt{G`)tY|Xn-9lNQthCVDFH1 zePgF_QX`Cr%vSFg>GkhfO3{{=n7cbhV(?Hm!?xHsYV>i3j zRK4_$qIW;W8FuY(he+$x@4*hj+Nj=fj6&oiu})rGckye!u8zmemF^1pjCrE#R;g^t|9`4sc^4}mvXJhn*FMdt6r_7Pw#OonETN(?!B zG~Ak`RUmEwKgBtYudIIJ8`HRdicyGMxyPg!@NQ#1_)SYci$2ly8yG8Cs(OR4$*zw` zOJCkcSOL@*(!*~FVeWW^jX?%p2`Xyi+ym#U99m7xZfJIhb+`5}Vb4(VQOT>m6=i<# zH2Nz}+hD%W=-NDzK6`&$*%>-f9QKw>;WA%IiN7L$hmYp>eYd?Yy@2dmG|X-%8^r z@cboiR%!n%?+;Zs)zJ2N1|RmZVK17i59gn6wxG|Zz8$-HkZy)$-+YeHs1$tDD33@N zbgy8Ox~_9l#89l7*;q*lVqe9r^5dozS~Ax#dOy3R)eB2M^zjfzUDmtlBEMgPgMO0to96zDV~RZ}tV3z;h}MIZCa3pG_~p9026ddJ zaIfmMOZZ)}gyZ6yAFx{F8A>;_!Mo7%($c@cteNxW_FWG&X# z0-Z6Y{3bcR{#J1Z!j5UU`;@iC+=s19Rp}P>Ti|YSO;f3t zN|ic@>(%O`;30|W%Ub$hA(JT#Uazv$=s4~_+uFVtuH5sFLCs-lxy#*D`|d(7=(S1D z?8TwQ%^fX0)4}$v4V{%#(+;cSe$b1Xzq5gVV>rJ+{5Xs^H?TdlMrS4T8!>}1aTdFBm3FjuPGugFG`r|8j z9OM)|2fx^m85J>ERuaElzS)-5LYwBd>ZIJ*b^3qd8pbp%1!kO%i}T=8sl>U7_O?$U z{aiQ7!30^sJjY3;b&ngfG1qTI4DI%C%H{hgo>5R~@ymxS(VCMbda0GA3b_3~+v?E{ z5Horu<5kOYonXDhGj)0qjd!94VxM&Z^!OB>S*4}yiXH#lvVY&Clt^^Pe;oQSyeNzn z5Y?f$zL*-<7gO=F(LULGec?G{-m+n=ZH%4cc@!G$S}n0m-qFN~Aa1Df$#?k12Yo~7 z+0dw-a{OrK%T{PDPA{gS_P0RK3JLQPA+Yl_F&{5-a18f2>!xh`6>aCDOmji}a?t7FVg3>a|n7 zC+doi_E@2@<>l(5he~&=-@)qlY4v-r`kkgeURLQbmDZ~?p^$`DNmEo>T^nm?T{W}^ z)U}7YrmJf!bq%n8#p!t4l}D;sQb=-blS;=_%7;;neQZE2Vd+n_GM27DZD*<6HgVlL zT3oZIi0f;Yi|YegzO&W!6^-*Gjp+f@Y_{BQJ=?>}v?tk0+@xbxI^Xu_a(9O7GtC}S zjLaia%pK?jxD8%|U5(|a?Kn*invZ&XJBO`&DO4 zk)|9*iapDs&{FJ9{!yEv*N5S5o|T?Mp8jH`t?1i1C*MLH=lUW$hH2?n7nC>;K#sFL z>P5NCWpqig{WG&ux%kXA#f15H!{uV-Yu(2@psdI3GVk1Uh3M5qTsW`BV_abALDd_C z5rX3q)@(YtAQm??;{1lRp#|WyfLq0s@ls3PbyAA)P$SOUJgAD$I1f6n!l~GswHJHNZ)3EX=gra`?WoDrOoFr zQPLz~PJ zMv8tj8ouvMJu9zB<)lCCY2tS8hI1o7S|hifF=+8DEyYTSOF<-Ga|kDobGVNC+TZ&| z#sJ??TNsUe@y+S$@cKG(%{P5j`m>AYT=QeZq1z6=k#Pa{58Mdh>j;q;Zk1{h?gwh` zZ8wr6>V@&URIE-`mspj#UZi`9b9#NV|9TmjXnxOQ=(EiuCfSz$66_dKo#Z?yVZN?`6MZ8+A@7e<-;UkO za&osq$`UUXx?IC6BQDplgXr=ezYc;=+o`Vo&*w`R#;SpGuRzj^U$uAjCDr85=6O@x zJcpiv?$}&vF5v44k#ASbG|Nae-I0?#=i2Fd2K;I_`dDV?-RvvXoJMxFqNLlKLITA}1RIj_n z`CpiWunjqJ>kupL{!JM!v7~WvUoQBIS6LVXZWJHYEmh$&qh=}>(w}?G4i5PlY zy(X-*P2V{|W+=JQ({N0`$LPXROw-#*Q#p9PxE5Ev$25nAsE>ZSYNLB7Z13#b>Q`5H zBcGR2T{uGfKa<&8vlo0ZZSaUh#<760SE{*OI6&d=LH; zlsT`m-h{+HdZLz4u3@(fWl1d6+&)L*awq=H=_rLhm!%tD7wKEfH<*38UZnTOaeA@O zwGLl8+8EH;7_W9xyYQMRZVvwoHyUkdA16s}6xG_Y z);7IZQ;{xr%-8j$Z^~jHt_8xtZ33K@(x~AZ`o$2E;%3JVwuhJXSMVGu)vVRH6fYp* zZn{SFIv$X;9WBJO`n2?A7=?JQm*M7}VGMX5t_@;<-#RMJ`P?SG9==PU9LEm=NNzlWD<#$vs|bt7zV6FNw_wQDKT=1kGMP1|Kx z)vKv`t0#%xXWEi`s*mN5iEFs0^($sy9D~2|NobSvi*$`jOH`_&(t4FzsdPxCpVZ!H zW%X6su2O!LUQww+L1|HIn@jvUpfz$#|9o9sk6~uSjJ`%->B6TX(N_zOsWefgQCq~7 zzJg#q`U-+1oCP2T)DNYaONF~d!~OSGUYj8HXeX6txn!BSq1g`o%)!h zdfBR1Po*O2<35#YsgK)LdPwb!RlOJ0HKJ^~>TOi%L8litm)e`7ThH{W5`GnNJf}Bq zrlKFGa>H*RqX)Sku2FNvcHaAnnhQ7~qSamES}k08?R!^NUbKdwZzCpvEE#x$*K z`8Fx_ApARLEYWX^aj!!6BX~p%e|?e|&8g#tcA01uh_5O(%Bt<$QoQnvnBsPMU*7xj z?RP9Zh7x1H^lb}=QRE9_2H2E0#D45xNSB*G^2iR~g4o4Aa`%?_&Awe$G4ve^{f|bll2y5@*8zc<6wMOBlrGJQ0pz))I8IBzwuBR&&OFM6*Ql?7NRVt)X&+gLpKV2(+ zKM(6O_88gnuG>U!k@|f=r3xygskBCCJbRpkU)aKKH^mvVYwWs^<+Sv|*m-AGR{N65 zpGmlNl{Hj$xw4|l`n%CN)x7QYu5knQSvkYFxNk~JUy8jJ4wr5Qb1Dzj7Kv^ZGy6ct zfF~*m>(@%8C3@EJyF2ShOJ8xfq=>$=VJi=6zk5NY6)NR?D0<7Bl$O4#r&#$6GcvYP zMdzaD;!;{4Jt?kzF~YLmcbJ2)H1JokJV~w0EGa(LEfT%hGr~&gsIjuBxGudyq-56@ zrH4-nU{Rqs_jx%l^E z%6gX~+?iM|IXOn>r`6EPIEPAT-hZy;k?o|k^qX)t!L_9P6!Fm&vq<(^O4nsgbd^=$ z5z*WIDobhUbMBWIJf&@QCVDQm@<^sgFQ1XpdS6HERxT$~O{UiIJqIL=Cnt(@+V!@n z=0&X=?OhD8-a_xgp?#PkQt+YponK1WS1MJ*DvN!bN*3ugT|xe+(hik+Yu$+G%KtHC zg_XTlPwXAmI6td#ZlZBs>|&ahUKabETnc|+y}+?rd5ZH2w~x`IkZW4{HCopWwiLZD zRr)}snJT@j(yLCw*(XknIkY>qKd7ftKka>5snlQlp>|G++ZPFVgpd=HxII6j5iD_} zlO8>yvvs@=_l%4NPhieUYo=7Q-p%DxP0j*g_c>SG#%Ry?v$0o?l?{(kU#heTJvy`J z(L1uV590?*=P-Lj4Ia5Q}$>o;}{XRmYc#SOETDjhj1EZi4(N5_=7TK)s& zOMdbDm*}M~n@3W3x=Fj7yaRq~n(TImtGhpSJ)PBER@ULlOKx%Ty&IY>;aBLfA4cnL z?68cNImmpsLTj7e9CI^n0fXJSZmKE1P0}$A_p~^bHFW3h-AdBlUhXH-g4;yed|J}> z1bXFXY`AU9;!Ms;dr@~MS?StqJTnav&LA;!U16O(G!Ab{SbLjr z_m_80-YF)fkfnR@f4Tg_H;?Zi44Wd^la8B%h&|1LeJxdA&#ZkDmMYDfIrhKa?U^~& z3hifexU`?mQd<{pV(h~>e{s2-j(m>OW}f-Za=C%G)-da%v%6HY{WB@ciR&%7Pm^lC#H@a@1Wy~BUDY{*)oY%Z+r+zP} zt5GW#)NiIr_o?1OjmraSrMOBrs+G$$E)&&CbvMgRH4kbIb^kD%OSi0mcx63 zXzdt7=`1T^GGo#UgewslkNA5WY9BaDC@FbR)6M!)&2Vk|`&9Z^r59B?t0;#@ z&F4qPi0l5x#I>X92T4?oX=lE?a$^iQdNEdz-sh{dC`&if; z%HC1E)ykUxBzn&(dl2U&9L6AJ9hDtWw$Q~iZo=Gs<~qsAgP2Kkp1y%r%lT4unaogD zp!7L6!kJ8+u5$DCD~#fN`)MPjU54`Xd8`OXN}To0$N2X8d~)56FnA}X3FesGb4__y z(y>QZ!mG4SuI(cETohI~y$xOy>5pecdbYDjKVj5my~8Kja$0(g+T6MmW3^(x%s>~T z#x6x2!q-mkMa(KI4SNayvQqC^ETx)z7l`Eo^`*>v=sdCQE=fl{?Q_1JC_dKA5+6zP zMei|P@26=!pgj+cJ$(bs677<(bOP%ImY&lw>gUfyZ?g874OD8cdbw4vo4Q`6(m>T) zrg}rwwTw!wv4h4j*pD{EQn+vXNOxw5Gmne!^ro7d3QLN9(s1Kj#a?5bDHKxa3)Op8 z`^0P1^+X5p+d+56a=HrpTK#TSzZKN)@9Otn^}9v=9#+4*)o-f$&81R*^}AdBF4GnL z44ofLQmF*y)SQ1~*Gg{e_)6k@M#Ih3Kv;(&A{Cn~K2k70U@Pl%&!ej9UD;5=sHIY# z@?xce#`%4nq3mcSu2<_Q+0KoUsb;g*>{)uolBKTo?-0NHw0+z$OJb0$aUSP(7E{fq z8t1DtRwv7em2)Z;)tIi;_%+mU=WFb9Yn+#9OrO@6KBhj(x|qgIueT#+?VHp$g|B$f zw-3DfY}#F<9uM;vka%K8uH5U1YjyJ@R?)N@5HXeD%J;d%6*D=vGK`pXjPca(UW&z4 z4|n*S9zlyrg)k62Vit0Ysw{FKKZdSPVi`%$+{h>Q!Z|E-A zDV6r9^q!l2ArHn~yq27%Yso(l=R-F3BQzaU(wrhG>9`5H$QZf_ddIAfa18C~M!&e} zaINHh5v;~I?++;JfIBD5q~;{nh)Qtf9B-f!`S{6(R_9x>+zs*L7zBDh@im`|Z&QT$TPkGh9)^D9!8t!Il= zPbCjCWG<~+m6b{tR$W;hW!F6-t}6$N^kV^$mbDbAu#Uthls%Wrt-^E^!_6sYp4Wi8QpE)V0^O_qj!VOjnkwY@_-(tZ7TayEB|GO-6~d zHcO;ws`r7eaXi)Atga{2wUW9HQ`eW(b*-BdrkX8otf--1;nc8Sw9s9v8um^!Nh$Uv z7@E1InDF*Cz1zr)UXx^r-d$xW{7OjHt9VBVyBd_r#Q6c87*iN{p$RcPi&|IRT#MXb zX|ZE?5%LyZgP{lhNM?}|7rfl!;(|9JTo}0B=kgh|J15~A=u*-usF&ycCfWhwnSQt< zN2iut+v%3YdBiGis;_z)wJ|2^JV4Vr?E);9ZTGgeF5`!F6n~0HVw}W=D0BM zx|1h+1$$NMH$wIbdZ;Rwvflsx33RIIiT1%(=shTwzSlF-1uEt0E8+f9Nu-(RMaf5E89n)ol!CuX zN`bz6r+SaMpSpT*rT1RLSu^(xJl_px($tIIY;!kcEwP&Al0J@|d@j|zF4yo*qPqS7 zSMrubjoe1uGVA ztKrFBURMtg`*1sZ>rkrg)lKKRB3*_RIQRTvO#glgN>={;DQMj6My=zov9>u0-y;7PHId&mF1Szn zu&7Fs-o#c*?^zB<4F16JY-s;giHausTh?&P^%jkVk_Io?@*mVU?BzUezHG-vZUY7^0>>fM+t9UA6 zp0`$RST3tO*&)F1aOg}hf?LY$V{i-Z>*D4ftez;Z@D?z3Y*_j=UG`7!!U#(%uT)ci zv-IP4sFaMDa^K#ys7NhTYK0jOyMBRLDNAEvi=|AoMwV)09?Q~-wIaP-QKYHkL@I+* z57wLimsn}8CxzX0UbjH!b&u-2Zja9E`i~dC6X!_`MxdW$%gve#ds<^P4E-dzhI7os z+sxVLqz&F%(K9n`DTHMnN&}NQ46HTkOaBs<;1T2`m;N{Hq|AGwKj1RIUgwTy(VM+& z!p2l`T?MZ-MC1ZfDWg;#7 zO?*6vTFiQr^-aaY<%QimOQbu$6CZhp2)nV2Nc~Xn$%=1I;?@zrp}HIr{~<#X{89BWf9>E$?s{IB~uN%^JoibA8f#7)?n9n;$}W3H3>l8pY8Z{Uo8 z#5)-8DkCf0J63di*Z2yjk}z`RJO1R#61lP@@xwZ0tBeMJ>KWf`|>Bv&nOEs;k%jc}oW$kTZpOJb+$g`)Nw1pSgmD1Bw}ad7EKS6Wf~78+jvt20+u*~0k~mL7 z3@D~;()&*1l($JQrBW@GTBvlbN;Oq#?j+oNtRX4yI+E>SuB_4nDwR`deX{6%j`5sx z%|kj^`aoTqYL1^y71vohzP+mKWA#D5K1A<8wo7mRDu>Z7y@5(2R|y-e(o?gA4OOX@ zt1tL<8r=5h7oYyq8ZjE>#_9M!S2yT)c)8}>)n97KCe%}YIdUT;{uTP0wZ7c)qU87) z)H>FC(3hCbC@CeFAN7i3{|HXHIOSEp6~9t*us?a9_!t3K)*GSad!tHyU8{|luqHOd z4G4-ss@a;0!*GDDJv#|%yd9eLSaXruw zZ=YCusP^_Z6Kh0%m?W+@wwJsbi*VWRdm5{z8iTuX8RIRYJl*lQSgEg8KFWjFNUZDN z=@OTE_ZXAcva`qy&b6;GF0cd-F1?4Y5@{XQ@t0d47+hQ#l}wWod>)~37$Z==*H)&u z{DSmy9^8W*;!C^`8+FDZ{7kXIaoEr`{XHnd#GE3PF^qKr5XmLgwRuTO1p zeYA{(R<{oJxUJv6P`+GRFQJC9-^H-Pe#gQJ*QN)MlU)DGl$G?phO*=oZAF|p_Gdkj zE=L%gw&F<=M%_Ebat)Lb*Uqo*7R#*=KaN2O)G*GWD-Z(?_hXa{*RTSeB#g!-#dW2! zhG-QWm+kGO=8Qrr+42^|kJFY9<-t-B=&>}txcJ>LMPkrb!x;Ib*jqJ9@^m3Ab7+?% zZ7i)sxv{;u$QQ1=*J+u*7?pgr3u3()OpLm2qi(gD^PuufYoEeccpk@kNju zTy8^=YusCGMhw_;Q`A$glM7&(*#`)Xt^AJkvfm|`L9y#pZ6Etk(%h>QN1VB|YQYEl zJ?o2){HS$YH*P~CWGk;fDY0LM{9_-_jld~|&C`>pIovMmBkwuo4s@C4u&r7*Qw3bvyFUlo70&!+5$5HQ@eNrAx;abPWxh;AD_WLkOp}uAFkaD(?2d$gqw-=?w zp}mqJF=&F;%`trp`FykW+XK1FdgoEExSh{Jz2Y!BpnRKIy%&(DtoMCKDYx5^e;n=| z=)+j*gEq)9C|O#-9xmvAt>tqooaFKl)~lU)PG#KBm?ay-f&%eXKTHVQG>|g@;IJhqEMK&LdV_f?t%8SUrt0 z=X|M*TH>sXlH6DZD;)cmP`>Qi6K#uG8u|^EZb8Ykw=un@gXpb8pU5e?>_!RWG4x^F zs~mu3wl@c^oPVz&hnQufR6ALFUt|2>QR7jhoMSo><-yXgntuZj8vFgDxJVyr`SwA- z!Sj+*C|~xQij=dD9%!{(OZFo!9Oo9TrIoEmnRBkCfU%F2wIzN{QQO&d21<*|=y&7` z_r(*h5;jC*8igyzpe|Y&+na`V#BKU0EVGrlC|~Zm{zPeUtn&7h(m#wc;#~U_ZGo*k ziI&3U(HZGrc2wI(d$d7rwdGI>-11f;e%x=|ujNtwKC#jjImEFV4_CJQ=o2CxLMgZ~ zP%>;~404jgSok>3acl`bgx=zQD?P1krZ7fMws#XObE_?k*2C;wwK5&ODEF~1qTD#2 zM`KLka6d;M!8z0zdTgaS{IcuYTD~)?TvmcQnmeQ7r)Db-hvteaLy1b-F*a~;9@1i!5USeot(Z`Okp_LgX(x0f4 z%x-fm(Z@9iceM492l>p>jM*YRI9R0A`XXhccVs<^GfQm|XO_NwRisx=T&HY=lM-`s1$whdHYe|>BvKr;iP@q~B4wtEv5-q%&;Biq*4V%x>aLvKGf522Wq%Kqh_;}p{kch z{Z3MLr}}NGeqYd7by2-^q@V3|M=wZHVh&yPhB4D@4$-f-uv9?V&&p!TwkrEprBzOf zn+sTG@$bsqkS4#ZMEle)A`IWica=CDsm}YFX_n7pjjYnQyT^d-Kjp%Ef#Hl5)2!lUNKUwPDsnkM+>wj)|;)U-Q{U3y4yL%+^N zzX9r-w;uFN8}xj>NyokBG%LL}Ct@aA*WNdKW};bV)Acv0r!UmyKnbPL$WR}{b-yP$+hTmGih(mR)P5OPxo>umlvMI_kmCaE$N!eSWL5jJsZBSOn1C!)WzM#+_<}L zL!;l#VApU)E3SBb520>Fclb;yz&&-pNXw z$plFGL7RV+a=z=52fI-;9w80WB!1+|-#J|Dig|YnsoB%r9gMhXgS(PPt>uBJYd=Ez z|3=@GxNf9ZseeJr)6yq?#1h`WnuPalP@n(5ksJRrsYL6UAgyQ8K$b$w|GDe-D`b9k zGFha3ZVq>8d;f3o@!!d{|8su-pUB<+PVE0@_dJ2&Grn>EW#VzR-)JONlMf! zRvq&T>smmiI#rp4HTF`HZ+;%aEyXv7JBu`8C)a!I3t&~=1^p2HrJL;JuIQH{Z&R-? zrJJneeNLyanU+i}+iZ6_*jU6L<>C7vx5F0(Pe%^V!khjeI4X1G zQIPh3BNH6KR;tsvYy+qC8YP`yF{T@QVd?2~#(?w#DeXvBa)$?S$^%l-yqiog+jIvR z2~yeVWGC;#-VWtFrULk*IL~tV9Fq3GqZa&4r|CsxXm`Am_cxuxT|HYmRn>Bvja)aX zDY;NHV#dQ)btNxgrZg3#hLTnvBTXPTD7nWGY~|vQ;`|8+*{P+ZDt7B;f?&Dibj~g2 z7}iyC7=$d}sAMN<_$wgwlq~!bzoQ7!K*h+}U|T$tuL>2<&7ic@=hk2FX-X4|a%Pl8iqpohq>N2go=j#}Oa0Gf7F2eC%tQ zl7-IB3?)mQo!LsBMtpKD##i7W_O#SgkzOHk2zE-l{+1aaPGvY&b4k&pK`O_2Ru#(wG*FcaTl+=P9A}5tpfSo5m zPAmBi`STpeA4&=$KG`4$lI_p8{{%2oscflxm-y)>?{PiQb|#C zazw6DQXKj7E(kWWo#o2TPKuISoSpJYzCnD*4u&Y_>mcGocB(1aj&!X7!6Av$Ig9++ z2GUT;c-SF=g^bgg4m$@y+9{d(voU8tx++-(JF$0+>7}GC><~d0?Cdm$ogyHEm8?F; zJuR00PNz6}QR-Ji(*HYS%EA|&mT^9h$CrK8Kr)mRo`T!vAWtb-jM+j9knxUWCExKA zQUH<_*_uq2vJH`YK(J85AB}NE%SKEO5KIN)tV3iF$PCqKR*o&d>PU9-e9UDsp)*&> zni>(Cig`+=fsBF9d?mkO$7CwVLM7ij@`jSa^Re=V&YMc+L1!MwVkJu*c`s5gPI1mG zmK!HuF-WT-cUzKS+9N?s=B1EMh8d2gwgh%oMTV4S()C;X3uJX?{9nCc7f9I#St@uOmAh+2i8#O=N%_3NuV4m#%%0CXLvR@5q5j!^TXGyEq?= zG-<*laO7|#xha#c9QiJi_jV?;T@5r1jlQhB|USGN>by_gxGxL>4+y%IQSBHQiX}r2#l!Lg>ENyE~JI;OiVn;0mdegTuzTtqB+uLJFhG{sMYj_@y>L==aQqHf&Tn{Oa_c;@71$n%E zmV|clcn0<ZGIvAO);W9!s*5j`hXwKZ4}-F1I9Xr+K~NmSh!MiSH!F5$gJnE3kbI& zUOy%#<0;Raj}V#WbmlzA`CPU)nVY!I6)sY$KwQdKw(yO+d!pbl+p>r2V zF>i(=`*0rJ1*EuFa0TgPn)A@<4RV!ty(6A)mu`G$yj(2^0B9Ep67-Q_0O z{=3SX=6p5kjNkUKIvZA!6kalY2;{XT`BVI()M^)^@%#yRfou_V(-oZ}wd z5=xA7DeommB+e<`o0f!ePC>k_uY8})dVbQuMFGx@U~X>DEQ3x+p1X+bxCy^q{Wl#V zAAwZxHrSl!R!JjKMX%-uWIDr?DIPJOL8p>8+LBS`rOuw&4pP}Gy@qtM&FMiAvj^ll zueT$4F%=?G#ao!5a|ooW>wm&tqM9Qa+9Or-PC8#-uZo!C@Kw#Ly&gfKAN%}T&zuIS z?hUde!+d~UlM5g(%a-&xq zD_&T(qz=eUUTr2x<1s&^UaNuEf{7^`_e>+`H1Nh+l4FK<@=P;JUa(}e=3xWx74~IG zE330)1BI27`xxrjT_6p;RgOsQZRl-e5|P^5&^zgf)ZRuOFN<;_M5y*Q^7`9_QciAZ zr;WX#jz~Lg?2U1x6ME5m5PD;ee#wC1L-$#jyyl3s<;LDJN2Dz`_BJtzypB?)R?yhn zy`rC^J9XwiNmUOlykMQSCmPlPh}np8heeMPI$~wjH0VVf4 zUz7jh9-y%|+L7BpdRbqUKe4_do!WY~H*W0pc4S{+&s5GuaUSf*M@mLpvOe-VNPlaG z?!(YLVKcro9RzZ-SKgAG$cuA4Gt`p0j!2(yv)7D?jt)0_LmZKwwu!ghkv#Zfo9b8- zJZQ;rmfog`S3H5FBdn%gO-t5Cs$lK#6v!>!W=pcnbhP(zAh&u|J|{a|pQnK|_nIV- z*&w%j=}e5oxrJA3oAs5n^k=L#pwq%jc4P$RNKDFG5{`N;yaA3#x>|ZmEa5(3A?&pD zDt|$iGYs_!WT%xk#u4fhmRPdXlJ$|RtKd`?EO+l%boRD5@JGS%h)D5if}AIvE?z@N2IGBg>Lt2(!yJ)b zqKo$olibovbn&trkzS&UH_!Q!UZRV)jET0dF5Xc`q#kzl&N?FPtE)HR8;)V~J=iBe z47+-JELoqs&EpX}bLi$(+eQZg8)8WgUVq?zqo;TG zfiTW7h&{dT38aW611!lfNl57h#Q8pNh9eC?VoUKQh*$VqvXfzYfaC}1<=tn=`p8@O z^{zse^tU9_Yy~L+(%T#6NOQc-Kx20wZ%l#?h4n9Q;X$&TX-J3KMPF}+C1D%v=aB|_ z+CX~Re%?{5vp%v0^Qck?tDo2J5b5MZWMK%8a9@aaZKpykVak4YXwCduSdoAI$C#RHZ%WyBv>LijrOw3`NXt2&Bjy#HUhe5D2 z+?(S_pZOTg(V9khiyY~SRRGON(!CEIIfhjLkqqxkN0vBWBfUe8EWmh5bL>%G-;zGV9p>x&CFwjP>RukU>87-n}K3@T~G}_!{q(K0#rHv#$wW zV@G86HNorPh^&AnctahL70?7P%MqD=NY_MfnbVOTd!qM|BQg?A^!6qY8i^)(XDtaw zqRHMo%qpnm%7`|_d)Jb%hn(VVa5}R3o8lGzg?$mB)!!6v5fknCv%UR}NY6jj`_YoH zK2P;7`<3iuYJHySr8rXTORUI{(y3mmBMZ=nuLqgtwQ@x2{PSLSN2G?o;0JJG|)cY?%m^#ZIqq0->__+N+X4s4Vt+H#^cAz8(WP;I&I286bze zC5}kX|DCrwfl$wX)Z3mwsOSIwqMrYUi(0`?ZoZKd$nIQ79?p+pID$N{z_nu^e z3m|xh4Ke(~o0LFkg#FWd&5@-Tso#RmdGB3E=DM-_FK?YAPvHI^+4;-cnn1|T1@E9E zGitLP1K>~lLBPBrgTXMvag-$2t|LVvZrxWv|_AXIa ze{%W59g+Hz8XptaS`l}qd8YHre$OcPt^hBemzdoQ;Ofq#l zjf+SrV?7Q!Z-DDSr7}wk&L0 zz5$`9_#apjwu{pK$E=ey6IT_eK9}~lIIT_v-rz3L<^Ug^bf4?K|flz%e;~#UR zJob?&hGqRTj?@I9RFw6jNnAH>-^6E9<@~&kbONDXw47hWk&;fQynnSL*EyZ?epyGp zMGR>Kso+;}=r?fWcaS0|m5P2dNAAad3F%bwTRSoegmfzT z9UMs-#=AY0{rep$148w%vOma?w7qQSI)9iWLqW*Sb^eo%9KcGG&f}{1V;wo;)}B@T zX_kcbu&O^SPgsI9=2r7ZClDH`tNCLS2#wU$T}=p2J!<&VtWLJ6hWR0#UDojD*(*0$ zrcFFzZUMQ$FPJwBt2Ib1zqBP;W_DHFKLn}mZ+7I4xt{3?QpYcxFZ4yVrLJEpfzZnL zM!#ADAl?qEy*%@3*a6jNaKsz#m#;pL+Y18L*eOCS__TfcV#AI?2|Bq!2Ks{%NI{T?{LBPW6y#xlp(9gp>X8gG*x#B! z%7HxUpGqKAKpyjpp^u|D&%)j48$cfSYdccC8Cny_6MnY@(gb9L|8xRr0g~Y_N+9h( zM)^AvNGFgd@evM(^}dgDe2}O7nh9he$g_U$1Tqw4tUn`xi~^b9e`-mVse3E_h&e==z4sMg0h(IA9bV-a$`1hUi5nx zlMF~Bg=3uLv`ade?kJGnlRV3<#2~{ zp1K)tf>$&Uto2( z+~4fTGZ+&dM!Ht|XC3+Rb<`HbXO(YCaN2*MGY*jV{agveUqPfm0?7li+Ao$s3W0p! z4{+ogj5Z}f*7%PmkW!Y6NFWtJ*7{F5QUmV+Pz=}klN~AL$cO$*j?hgfy8W`=pPxW# zgM8$_?MMZ*(*__p{(48sII_Y2%8`fraQ%{2~d2e0}Mca%3Ia1%>sMU&E2@AQaY4zhMF)U%M_!*KWVH(>a57 ze>Z&Xb~@quYqvkZ>V#?E?GLkLl&Mr4W6mXg?ezzt%_Qd7UVn2k5uO{Fu;0(LA& z=a8RNhRH~f1_z;W6zE1m3S7g!%xiJdlH-ADUCPR=?R41W{Wz+^eu`TEO0>PU4*BGKbjS?5VqIf_p- zdbB!|g~%VW6OHb#$z%<5hy>A|wVCwl9I+%8{RBBqVg39sydHxX=8Eo0Ak#teM2|YM zH;x-qAbF$h>ankn(D%Ltaz%8HBi(WOLFHa3TKgu}xver=E)-2dk56IkLm$2rzOIZ` zYry0djCvGS@#yhJme|$S8jupv-i=x32ZXf|QYuQn z*G6IGe#5ghDkXZ-k%CX~t&g%%(}r~lpajWI+31iviG*#ieDt5UG0R8iS)Ea)0@}_l z_$nVg>IlW>C`i?4XXG!1F7dfO+Rc&WD4k!SQzN>v$KS)cF}mH7QD&RV)f=O!J=sn_ zm9q9?6R82WwDX9tn5Y(u~HL*w#&m++Nkb-4Fy zgRt(4uCXMv^RMXbgE>Bb{Em}~WSl`q`&lx|G@XQBzJjm5(b12R&M4C#HHt{T==#Ul z*B10+JwW3L7g%o>T2-nq|q%t6- z5uYK^+3d?C-N3!^W8%wCPb$@9m$QX=$nr0ti!i(v!b6{l4brx?mmv($cpZ_B&>B4 zqu(bG%CSk&KNHAEkSWoL>9#CvK2yz{8l9a$sAfJNy*7h%j%dxC;qv*2=5t6SpJzlX zT3=zA&TuI`qP1m4v?c50zHpAmlo`>x9La~4OQFt)c5$IgdA+Da$}1#N!(WW{cA-mM zof%y+l5<}2@FlUMB^VN^^Djp~XJ5IWo4~E0^o=zZCI`gB^QDIq7>AW5tm_Vp>-iQuMAXGYUX<0}sSgb@^!Q$vP>+498l+NPl zHHf zl4IHi-%%oE8WJf}CEA*nh%YU{_moHpE{~3Pp-Ty_h|X|CN^nJVAro!gE24AznAGPN$9| zdoP*;?2CRghAf9Y)*A&)QN#A}u8((o*(C&pOM} z`|TGy+ENZGk(P2;iS%PfqD{t948wh-W124MZ9*cw&9P_;>nrSSjz!yB5~lQ+j(XB> z9Fx#Br6G}&hD1`TMEivA#g3-*M{$qW99j1FGnQD&PN}0MDoW3Zl0-q zW<-hj@`5L=FU~P)??Es&fzV7Z2wrrSCG=RZ$`OfSEclK|$3>;$qIq)@Qgk?OGXB}mc0U{*nWT|V3!-3wAYXywRl@8XshNXekR zC7iC4AlC%_EeTswaxlgb$)Dt4l_QdeDZzLDu#*y;|A(D2L5VD`QBq!I0(u=SaU3ik z{PV1>Lh#SCwhF;N&)O;mZLJ+n1qK;YIiMH6U^#c*{CtzSN-)roM{X4ImfFe7>=<_?NNRAmvpo9~ zyx#&+E9l0=e%nQsYXz$;$uO&Ne>KIDnOM6@4X+4NJJ`u2smMGo3CvFy_ue&ig=13ci=G0Q^1+i?_dC-xYE@~;hk{gP*5y z+J~p$93R=yF39^lks}cbopkOBn!mt0-RJOHxI@s>k|U9|?Qtdrole2}>8$h1gE-3u z=@J~75&C)zq(|VtNQCQZI>`M&O(y1Hte~F;=^adVWIevTn*h=`=!vf}6MYQ`CNoiA z4+WhRb;_Ws2cTLAzJTa+t1{v?q~z7!s*azd;i6!GjZd~AU(`j2ufR> zY=aA6W;){YLD0{U21W4ZCdis#vLl6|L#H`wgV~mZFuY{Zm9E^G{buq2H0rF`+O?c3H*i8MmJ|W5|OVzHU_`3<=iDP4&pmSSoY^p zN)s&?u!PHtZftH0Dq9l9XH!roL5Etwrl3UvAzzz4~7 z%|YJzwv;1XKjvQj)1bH`bYlTUW(QP~LVv*1Q1xnFgs16zY;PDgTVYtYf@ zBroCI*c$xHk%w>Q9oMbFqmJ}PnNml%H5lzkgYS6fc59HuBr>i+#HM0vFx!!4E)|~# zC!Mb*BRE&LX{(g9heXo;h3ZJ!w=0pfE6FFR*daRkBo!f%R454~&LNRFe;J&&aSmJM zR{`C$LoK%D6~~PFtDu%662o1=9gav0cLm)Xk~NSD*I;-#A}X zUMEmD_5_C=kvQ)O+%9@{A#CGT$2nsCZ z(7&F^b>l!#jET0>1HrwHNIN|c^mV?dKcsegAQ)vyM$&`x#SX7`!k*+n!27FV35G;U z@SxP6KuRzqQi6v>C!e&hBf&ZwdRT%-L?@6EJffjX3963P)$iO0o0D5g@H?@SPf9Q( zQi36o5)6ry;CI0$mt#_b-${IOO9>uzVXY6+>T@Z7AADhbaVr>uxx^2_w`|8q{u~cZ zIwJY=V{m~B&oe8g0hZCN>2ne9Fdfs2pT#fDLoOibVO2m zBIszzdLyYganYD^QqpeQ@|{R`NTeqGEIF?=AtaL0UqnZ1!YLuP+{>ZA2npRep|DPA z=zomF?|Bnn`%NPTJAZq zV`~D%=Xd9eYXa%~84R?(xF+mI%RL{YvmLDoe+ABvYFiM{8D zW!;5WmMAGiX_BkTy@?ys=;c}-qd$E7^0Ddg72cR)GBr@(qRYz(kxheXnzq;Zy-5E2>Da>stR`NJhh z_xbb0a=*!~Qrhxmv7(Mhy7I=#GSL#uAFJVXqy+QF8af>rh4aT+IwI-HAM5Cdq#}Q; zmm`vj{INlna10Nij^&SyVv>}Rhfg93#IhaPUPj1#M^@DmvdWSSQ+2Rsj>1lX*j7uz z*>b^H#kVN!9M%cw6pS@ZAhcI-d8};$q5ZlmVm&S4J+w3MRVcQZb&}*YlESfrj!bl~ ze-w^gU}CmE#k(toW0xpy>0F2xAsM2eJmN3wAa1kQ8f05B{{j}%le|R3?`A2Sfl58AG>q0=N+Lh z(0jd$uNz{oSrUG=S}gXaC7EV5;!Hay#bT=*c>*z{`#;5Fn;elZ=B|qENg(un+|{un zi*2oo$TxJ!u`&sSzL-miwXg)QQe2Pk@e#wavAYvUNs#ifEzTF|ln1F4+nYdYfLs^* ziGAsrOSM?ucfxXS1f6QJnhAu;y?X4{1VZJ0eXNruS!Txih-n62HDaq+Cuwqfu5~qI z+nJbev6i7Str<&Q!ll#py@+W8Uo~UhnIt_l5%=dU>F0E)OgmdL3iEi>OfAzJViTCy zRP=<-4Y3&s&Q~{W2Ajg>^n#DmSxf@cG8ir(T|Z%=h!(%TBULO>Jszb=de1g#l2>< zl&-P7j!-MegRr{APOY{&HqOOCdc>N2U`f(V`+3#Y^Zz32+yktdzBqpFRQH~9=DyA= zm1NSC-i_XfCc>bo^loBk=>5RZR6|Y12%%9Dic%OVh4PCgm8KD*k?BFv!<5QYQ<5Ho z-`abvbkrv)0@7XZq(6;kUgq{T~qN4K-{jz5Yjpl`Yf1n@IUH`1XCK{}>V0%uK(3gRFB*Gc)~l zi16rS`WquryFfp+m1U(!;$E$1#_a(z{nbzPGyT<1^)vm|PxT-4_uVM!<3=9tuLD{j zPK4^i`<0!c^x^*W%}92)_9@+fjPM6R8O76L;wu^Ax#f}m6e7{#KKYvRDF0w0!~cSJ zO2BKhe>f5Gt&h8ajPXw=BEAPA$XNdZB0{kT02xOu#T89#KF0gkNE;+V0ml2cct{3# zP4MqVqSm43QXcW|CnCPhBHmSe#D9dyG9cpJu1EZ*h_L!S>OW89Ezm>J=12VraAv_P ztf)^`Y)5o;TYSgRB@4#*l!u@`E)nm8JO(5?ZUnRawv1fjv*0O$Dbl9t*P^UkMUXro zlj3^g3IE=2giVQ0D8L-Bc_Pkcs(;zH!e*{=Cyd2Hu$k)rRub&P_km3FAD5&=Yxp3Z zluq~mMWh`N;XmCU+KOfU+ZS8$p1ponv}r~MV*3xahP@7d-1Yi)ChSka#G+aB^c zls?0sS}N3klE4>q;f~KnD1D|s=|^FctDJ<>u&;r<;7^yt9pxAOg=NB~SdRM-VDnPk z2)^vk*ePt>S@5!dgolV(@Us754-xl9XZxpk$Ucbtihn*5^|lA#1O~`!{w0#Qd&t+~ zGV`@qh6hLE6VWaia0s>c^_UIM0+)zc@E4Tzy1(5n*<-P{mFN4r5aH~dAJ_Ib{b%-G z+V(g7HGXl4X#1Of%R@xl-}1Ngkbj}{MgBRGl&EX}#qZa>{~IFj!|(iv z=XT!lZzrNx!L`MDL4GDubOv@Hk{l!=-p>`^lzYd242fK=YD=3&zsfqtW_>6~s8%N+%l!8cNqNktqyky)pF*S* zM&~vlANpqzc^dxP6UYkxRwCuF>h%Y*(qBQOF`UW|1@e)9!U0iMfifI)=s_T#_~%G6 zTY2~X1Z516RsQ-1u|B&mz%#HwR{Jv~DNufG3BQ^NWR1U+Y{tSIodx7`|D1C4+IcyQ z6_EA*35Spz8knGb2xOCg)nO#QcN3JgK)&*4|BhtAk4&+yV{S!!f0OMWB%^a#?`}P{v(o<`0}C7>ELzDf2LBzK{6W1aetRng5)Z~W+IT2agk5?OQnsg zB&Yn*)55DjSqvHe6nLHT50fNcx%qu~x*v9xfBGLG(vHX(f1V_{(&h#5`pdtYh_Dgl zZ-3ICSf7nRUIlXAzlKO(Ad7)q^tXpx7j^z+BK+C~kbnGzL|zB70?2>Mhlb@IRN|BT|g28sehwa zGq??L07xjXpGZE`=Ohp#&>qgOMV%idVg+)DTv!h61{)`^mB=w7mj&$e=(T7cIB$gO z%0M2GFM+5)E)Sd|a&iv5j}PREz?=){wE=c0qVy{RRYb(S6hRsWdR;`Dcc4CD@M;uj z{|}O*i{QKgNRvRF|B%##J5-{dn*|Cb$y2sN9MR7$0?7(2Y|yR>iqk4E!-u2<;)t?R z0;NPQe;ebp4RlXH8&Q^s(>^eVh_Df*cMP1Xfi}A+&b5IaHIaNoZNEM+Ly`ie@Lkwv zY4F}epgw#r16Dodf1qRHy5PpZZX#`dLee!b$B#B8ki8;TQv>3@plC(LAo!g)D7{;t zjwJcYez5Vu-);)bAo2;224Hh@ARTs`Vix#7`NbQc-2=0Tya;0<$gP1diF97=QyPO; zTA-Xrd)T`Pn>zwFTnmXhccql7S@074u}>fc-h2>k{}Cd02b+5WOC)hu znZAMQ*9Cn8^WnRYyt@!*X#E29qk@!Z$-m(G-an8`qze!S{?0V3ku8>l4mV;6{11Xs0z3=<=-2O@Ti8G$WC z?t|5@4@Axg>?6Y8fw@0$Mv`J5@2Kw&B*Rw##OQoRqw_$ZC6Q7Zod*IPh)jAM_w&O7 zHxPLq2n>ZXERcr8x9u{lVP>F|$eKnzd5!;I;2@DmGLnY^XNb%NFVTwO0Uzv6#7eX| zM#4z6->HVf1GULU{T0V+c;HGRYoVV9LEA?Jt|w9oMD+8Bz#t+g)3BdM21XF6LE|+t zaE{21dT28$pw`CvoB$%mVpJf9$a7>fIxv&S60#W`P@wjr6}4`_IAa1)B6Wd?IAa0} zk!blNu~)|i3WR z3+y413z0>fae+UGEP*dviL4tJI7j3&Afkrj0{%Lf8-G8RAor`|17RWwkHh=j(5vGE zbtJ((z(YVL1Rg^h-?`(Er$8nKo+sivfh0Td8j{$TOD6>i$wpkq{RMlwNr5r2TM~W9 z**iIqMMS(6F80rp15xO4*lEbUF*FOvl^!B4B&GzKB2gN5Oi(64eWnC%LZbEqB6=ey z(4TDXhTnym1~xf?$wYF%=2;+51m;PSuhs=J3&@j!cO-FlR!;`jNHSab8up@drA^BQ zqK31Tf@yHQ3?w&T!#!g`(k8-N9YFE|^&2ADT>$%HAo+p$aCR?jGN30!oSA`Hu&WSc zHP~zc@={>T)ku0TfH$6j6a;30nhTqs-h!_o0eLkLY>wp7MT&A7$ZLU_k`yTa!S3`Q zAa4Zbw-h#WltS3))%z0OVF|2ijb448fN!n<`5;iy7Re2;7fl7SBCw<#k|2=YKt2g% zw?{G(>T^Gk{{?0dxgJ_N7RcH_We2oL55hSCkd1*=(3_&p$Lb>~4rF&i5?F{m_EjLG zGm=Sfqs=#gtwi2^2mA25Kq_Rvi1Rb7_#*PQKo*f2?<3hBI6}l)hF&`YO|L_*r9hqq zuU&ycB6m;?_r%CeE%Ap z(}Inn;(a}LXI?|KyCuLM++3}p7D@tNIT#4}5A0eU+hm{=Gou=w!A}2$5Z=$I>oydJ};gxt(^*Kp$m3gqHi2EQ-)eU5m3q)*_ zo2jLexO=2#>a#uE`iQUeG*cIN$U{)W=9iEOKw7HH(MAGgoP}&ZkU<$Zga>Pf;Hv&jV?%*7uOtfn2NRN#bV9jcWC;5Z|a~ z_Z4y6r$>UYxjptdkTi9rB)Re} zY(aXet@=wFg}=SuN9{^Pe2rG9dLOl?hkOTKebgZyQU>H6b%KZN0n%4JB1x|Nw(~(C z{ZxB^C~LOD-*z6LPLRa?w(~%>+rUeEV~~2gByMjEQu}y_=#9bZ5DyW(F+|Pu5YZdy zYLUqdf70RI(VRve=)m83vP zn*~?BU^7NtBMGkE^?;03FB>B3BWozu?g?sh4`~cG6Vxt}6#KsF3D-M79#I#j3$J3| zb|9UAWU0lH9Gq`HPk8rTd3GF4rbiKTCV z>%n`0Oj9!+Lh>He=OG}u>TV)^p+4h)Jf-FgN1L@^BhD)G)d?ezbh;8|H`qL*ZXj~Q zZMds^R^5Um_I$##>Ta^RyDNO}3%r0lEaJ>o#%_i479cZK`w>A3loOCU3xK?=W=Z0% zDX*$8Ba!zMmV(Wz>QW-pp7$vqNwS*ApqWVKsq2Y!?+d?{3pTH*UrRDq8Jme;F?wA+ z;vpNs=8d?uU_qSLEl?{T6?LAgbOWvX2D}!iV<)=gM<8#i*&gyMkcH|Gl3=^Ueb2?} zArCnQHcKuce*$?|{R?eW{-$N2s$`3@TzxK7n-gJueqRLxVN+|#pRmL22JiW*ouv(y zE}pk}U%kyks-Ub7)G?A2C@Y4-*Izfos;}0ZjH4q+7|04WZ3>c!OA?gJfqbHl$PvVS zKV`LA{T-;)YV~)ZK2`C(Em!&1s9R+mtf6>gWsSPiLmER_pQ%TXD4(R`vxRHbRZoi2 z;TPp#^=tz+Yt?chgBBtwQqK^X)Y_+X2Ag$im#OF_ep4V7$QNoJksD^fZ|eYAuWljI zp$*J0ARE-(L_UR`%pf2e)ud?{X9V6ezF2uIB-;*`(TelEl6~RIH9b zqKuCCl#yUltR_7zY-TGjLkW)q`AQv_k8w7_KIkbRTjMfwo7(XiVS_y;-p$#jrXrE= z-H1^xRcCmJ808<-*F9chpRhxH-$Q0WSv%Elkf_~W@G0|v>{fpyBEC@m4XFB0>Ruv^ zp?5*pl%LfjlDL_$N1X$`>D>+PiOY>Wak;TK&gR!Rn|*P!@_<@Zt*is;z0Zo$=PHSy zI&VQM4ya>1q!7qK)p!nV?!FyPZh`!!CV9wbKn|&eM4pS{FD(47Zji*y-Xn4KIie0M zz{u0#{)71Sgd^(PNYpP{!M!$!d{q6AY~KFGr+g(zk%yE5Ii}8k9piB6f2gY@DNuU7 ziXYgnP{kM4MAr3#`%b&S>v&v!PN-?~rA;gkPpB0zLqz{@4Ns^Y7fBMU;Ysx-B+8pG zw)-LSNwt@Uh`Z#K>cYhshiiCB{U4E*ki|#A>$F-*WNQ}uW+IR?>YpBR9>`yDb^crR z!@LqTybWe_9H-QXx%*IaUeCdyF6qnkXl+gk@6c8lo>$$T9Jno010S$?_p$N^Ewb! z>rp63fwBS4Nfraqw7EonfxG46H_j5Z>Q7<>wdyCXL9P0e7$L3td#n+y7$e7WETVP) z2uuGVlAx@HvLaeGk@ueQDMdgGZ4Hq!B9?ZJ$U6xM(#FxoeJtY4Ryx3nFHYJo)1D?0 zAyP-1FA4lYJ$P+`$aS@49E~#}^Sy=k zJCRwiuM@v^*HTm0qK%mEwZ4MuL9K;{gn*>PwW4iYE851jqFr1oI%ugfva7}&w4(K* z^a7=QKIT{lt%}H6%EL}t>kU}Lo}lU?qpsC%A@bsJxC;X{yiOb7Ay)zEqGftW3m`XY zSso&O#qTD~zY!ygwfkluw`z4fqz{nWwd60+W^6Y&;RMn{YwsbWf!w7XlcYeYI~bn8 z0n$r5N96K#u)7D+N3%C!OApi(Iz&^{j>?&(5vV?yzdEd`e|n*ao2*u z+Et~phOxC^h?asxsaFTTQ!zxFKxCzXBwbrcWNH$Up;{%8Pjc{^824%^KVaLt!RUzZ zUEHTVM5O0U@T)6OpA79W4_ORknD!=-hoM)O19?dM&_mV&8L55YA>RNQtL^rX9YDrw zr#<8VkViCSJJv@;J`UtD?Z;h6zO3g{E&!RN?e`Gh7Pzw0n(lUOR3KBdPCp^}4fb{7 zmzZ+2UXm0j&wrGlguv#BxY3!aeM(+BtUs55%~WlRhg=C{nzq+Nt_G5;wfP0hdJ?WQ zQ-Dm@Zt#%nf#hj-ddMw6^0h&d6ez;$ZXh$X;bikHkikHn(h zI0<*#!R8IkCY!@Ue98hz(j}Rz+?13c=fwgon}~RR>>aRqOUv_+Wk42czY$sb6Fe0H zc9Pk;!oceOMR5nncY zPwS5aPAc%ZokA_sL&R5c3bidBBEEt1z7{+r>QkcJ0xcDhmumHp_yjo(e_W<#5omTh{+VFb%g|=Q2HxJiqUwMe=`Sn_61lew_UT^>oBN2Y9h@NF_};E%5t&p6-u#s|MMN^& z!mNZ-^zGW$lDKbeY}bAun`>Zo5YKFH*NzZrbOfL7+phhGMCl7PjL68r|6~mn{x!7i zT75}!m02_KUiNmajBJkfgJ&J3R~-eWG(6G4Pv&peGKq*&YjH;XqgG0UpW-gl_DkZP zER|_d_^BG9Hhi+QL%Tu}%*@N7tQ}e>4-w}HJGK5ulol;~%2i;qOMBQuS_1h=o9Q9f z0@Q{e?UR~!Ez53OkPcM^YVJ4J7SvB-BJMlX(iOjGh5O!?U!NdJzA_%pX2h%v=qr)LW@SMCp2$iA-Wh>2hk$;ByuO54 z@Ew#M&^rX=c&V-0;~Wg=JtT4GU_kFnHaz13`eY=r`4!OTN#f40fZkb^krkd_0X>fh z&#!>K8;RO&A3p7)>PN_iXPl~!(J&6rI8C2Kq#>MLl|d^s{Y4Mi14P%C5cv|?E`E_G zsPFd>@r+qWKT6~|XuFsxVf~CGxyk{Ed)lDIRafxedrtJalz1$l|KH-s8qsaL;q-B6zp7Nxs0r6JjPNw&0c z*Y}3{WtQ-Aeq|Zd6)^%XK;7TDmu0F~70%?Qut1WmX z>&uWRYhZR?3#76B1rqgU%K66nx01Ly-&p_A6Q`^6I!IppreqWSA`)dBt$I!Lkd0%( zt6md535oJ9t$I!L5oE)wUK9N>N!-$#=ue=H%$8fB^d|aD4-xr%wZ70p#0t_>R~=cp zd@G?Rcs0|blDMPXOiv}kqugBYiC(g-d%&x?p6(%ofwa)4AW{Et@a?Kr`U^zv`vjj^ zYNanAat9E2{6J}?Z}r4^0OGXL_aKRlMQgp1hEKg8I zLY>>_b0onTHvveB-lBo5p^PkcmF@J-9wOdLYOgOza&5BUe|FSYd5E~;=%g=fh^2pY z5$=LQd?u5!I{f!v@E^pF`qy6RgzqyWgxdR8OX>op*^=UDj5gvMCU1k+9eR!=xk@S2XDN_7^_or4rae3xCTx1>jjl#=!_)*x zdg|?ws7s&3tE9X1d6LXk4vtBXy?U2kE=hs%@I&yu9f)(c-nFTWqYeWivZarnCCO~1 zya30dkG@foTqSP|=GZ;@F7#5wbGhPM1o!BNBq@+&Eky3C+s#De0_D@%@HPREfqI4{ z`O;=HkaT^OB)Q6&b8wFc$WVQL3v8DlqNVrg?OP)GXs1uv0VG4O-%1d7Hr=oHl_XbL z4?VUIY#z{;NRqFdg}d7#&M^H5dGUfQ-^tN%E8=!e)$K zo+8_&UU>sv!;aUly+#uGM6{^$cs-4Xcp_Sm3Hm@Hp-ODm!}=T|-?l`XM|7nv*601J z(B@ISP?7@Wyg2(xf~$IcH<5;|;Y#6KI5pDiv=d$hivJJTj{tdG&mxiu-)+7O$RvH5 zB>75%R}z#ffK1j?+N0OyVABjpj$YUSNeD=1AXD`&9Vzmv1mzAOPwAPRk^BPtAMqPK z&*)9AMPjYQ->G?4PbG3WJQ*uy%JaH?9oigR3a32~=LLP1B)Q6{jyPVk^is0f0r~SF z*v!&1u17BuN*@Pgww`nYk_&Klo&)4HJ*6v>e_@no0C`g%BT2p@$`U>Qw%$2a*yJms zJ|bHd>+cci1nV-AEkq26gQQtE^h$+!ilj4<8=wzoL0OCSuOxwQ_=DH0K$hrblH@Ai zLe7h`ly~*0IOc%S5k&OHdwTZG7+DZe!}s;R-37^2F1tHHSp;!D&^HiSn22R9)zw?j zW)qQRdJjnolsAXtSS;6T-il=B=t)cGU5m}~}s&4)lf)-!KIul7Jb z1+rRSC<*+k<_IkPfBLjEVUw>eg`5}jYmGibk^-dxTsy1-uQmD+w2?%#t4Pnf9V7RE zwxGicRVs*-=wC~cue2PA zYW$VHSCU*M{RRB`^jCW7-6C?n(gkdUwrtT0iHNrwMBBgCxAa1r<#6{+kZ<&=-bgxt zml%t0^yaW0f$PT>+k-eaGHQ|2APQ8lAgUx(O1jtYNqy2?ffs%QRPpJ!Jk3Nq`fa3h3 zR}Dm)JHe|V*!-$z4np!E)#o?8cnFeqb>V~oY!2)H5qSr$PdfwoU9XdlHrK#v)g8zm zdIk}p>b-!R&}R`j3F`^>m2qNs8BXc#C7G=RQ(>h7uT%PPBDJADf}GY1 ziQEG!dOwgq_2hd+oY~5V!EnY8q zWfi<52ITU@lwnwBL4;R>#2h01U@sveUzNDVL$(5Gk_bNwDEp`85O{9_NUOwN9`YNI zYZ42I7!c<;kdBEJ9wNN1P3-a@#z_R5zkuA3IKe~y1(KS$j7VK5OVsd|#JwIO+I4%P z{Sd~f4{=1h?n+Gc5aHE3aV!z>9KHtkBl{)hc!&vPVB#A@#II-812Q;qF_G4gQAt3C zBrYTJYAN&tko3g89?}ZPy@_>(V|^CDSabw(e`1=4+z2EyF`LNA%|7KeAR`hN5^4C8 zPw53@bmBQ8jX^gC0U4W^G6Ew9TA|nY#0(;0er15oBZ;#-MD%J_;#MMyp+3XGW@2KM zhl~d@IkDwPj9j~}Pss-IWa2;~BfdZ~EpZx=9dP$;D%eaS&DfKGeAg$dbfqM9zX{mH>G_aixcR z4`f+lxrgimvNF*ggK>n{0U)aqdk`7lD?t%npC{&d$Z;TFBo=$f-#|7c!Vgx;oEKgR z-^1#Vm`bF^X;{U;W?Nz=5e>#mknM?cJVcbWBXNs|h#LNsc+Nva4Sz{Y8HZ)P3t1O| zvVKb(LF5Ul^U=hG9wO>|JaIpfdEj+9c%4p68jo>=muSV`iRm69T2YlapNQ~k45UVI zFA=e`5;H{)rcb~)g0uyjP_TkX2N>lWfSAFS52KABw*qm3IYh+1uQ!l-!Id784x~Y_ zlE@1%%ASIM0_v$lqZ0+4wia|@VX{g|55ZB0yd(o4#9yQBFefp zxRA(si2O8=)L^BDya=RwFil*Pg7Pu_HG99NN}r%d;^I^ zc!G#f(Styq2&$9N=BhUEj0cdZ!P-QGik=3N8*D&CsOaC4G$B&)65MB$q&1P+5C?5K z5}A94PpOhNnM8zUE?ooHDZv>;9)-x-Hh3yHxQvKUQ3J^HL1i-5FdJ;@0+|`?ON4dx zh2RV#LRUrPmx60Nq#<|}1S^OLg=_)jm0+DI7g` zdBK@Pgo=vY#cRRh9E>AKC$O0xZ22S-mB#DsV4bNh=?*sU1e2yA(P5N(16dj@CGsou z>U}^y4vxr0o2$WQ6p;S~j}WN`F)uy?@>_5qk!PWwHv%aS&L$#e%GW>+#m$t%!9ud(nQ}PT^f`>o zGv#Qom=bE8P4-xt74<)~dkvX6BP%k2!y@{bLBAlzi zP{m8=#km>@%`ZU0x$1;gzU&f_t93%nL|SJ z7h)XF%zH!a7r8`aW=5!z2xsPlp|ZEphBI?y=mHVe@X?|0Vzl909UH1mgf)C@r~whq z-m#&piPUa`8a_7Eh6v~L*ibqV&gVx%(}=K!KNea-gfo0%=o}H&@a#~JC0Ikw`AMN^ zL^$WCgjRZp$oVHi+`ym<WH}O^7mGtFA0pv-@m^>i5uO)I zLp4{R4bOrVq0U5j7JL%w{4v__ELa`NT8)Hf!Dpf1r!En*peR&EglEB*p&9=}8=eK5 zLrH5~B4)u?p}j~;dVJX}FUT_QvP@LR%TcDqDm_-*0t zKOx}^zcW0Q2oX8SC@!>zhr-kMBjJ1=6<$Sz*Mc!&-m7zlj|=l&o!5eK;qS?dbADWS z7ZF|y#)bD0;aM;)96W%f^DM{;rx4+_;PG%@B0Mj$!-Yh6EtnLpBEmCea=86LjLb77 zC!FaaVx~M9o=1dd%JgtC5nc0xsw;Rf5XVU7Cas9OoV4set66wwBgzGY`F4w zBs`m53g;Yki5S6`!|ne-!n0{!IIjW;&!)G+?T;hj*|aDeJb{E~(~@xVNhCa*-Vd)N z!n0|4xK1V7@ND`hoJWLb)9UaVB0QTu3s3k9y?8dQ4d+}y!n0|8IQ61S#BBO9+`S43 z&!#Qmlz))$Z2C5Q#zVwx`YwFrU$o)b^g}rLKP0?fY!BZ@gy+|far3JzoJ%&G&t>67 zL^ykQhTkW`xw; zoIiWRBYYT{dwyS>*Mab1vf*|e2%8D$#qIhnT#pF1>$h++5pLIS;WQ%Lu0!GZM7Ujt z!&``OyM7N>5aD(miL2q!aMK!CA8yyt@NOd9u8KIX6XE_f(Tm%4BK#r|ZdYaaRU+K3 z%J4!W+^))SF%fRp>2RG|7@6DkXSh2NZr7Rc2qN6Bzv60mHoSyvIv$5BQOMP^Va1P; z*H*wi5FmfYd7TRnls4`e{<(0DB=|0QC3u|+zbr|KHXhD$#ofsZ;kSr%nu~YnE`-+* zIS8d+0Iv(-4Me(D;cu;82!BT;_Yt@m2j|om!n={gzWI3}d__Ri*?k)GV)zD0us(k9 zx)|<_HX28+3TGnm-Sv2a5(Jy7a2CbkFLzaimk{AQ`~QTYAW>F`>+BMa>=JIfOStVW ziSw$(^>J;u?f-;7lJ&uMWx?}N|AaSS4P*WMZ~k;+8jHCySPhUALK?x0I1f>*=H%8(!h(&kwpjU#iyX!9(! zt3{+~1j)SF@GSzcX%#s`MA)nXk`k$iVjMv>18En*U6cC+d&fwnA#COM${;j$ixOpydLd=bRS zjLh(m4}lDioR9<~Zv-+XqSSTE`T@viN8U!FZC-(^%*4nC zNK_u>iIIi`~xJnA+SL#mPe~LIxE%79VuyKiaeoT;cF&pt@qaa_zhbCeWa2|Cfu74{S0L#$vpIZc^^Ds3-wtaafrM@QfSk;d>mTOQba9hpdEJeBoL-Z9FNpA0>$gU}BQJh_ek-li?o;ktBjwU&wzA|ppYk$9-WuuFP_z{1*Bd~-i}aNQ zcW6TWwng5Sq(tG|*dF-+iORXLJ@OF|&W-JnFC@uVS=qKnN+=Fz%l1eG5zdw$BPmzO zo|jL!iA?x0(vJvdOIaj`2#?N=NEs0xo!ybg8(|#Is6CNIL^z}NM#_kAM(vGM5#cf2 z8|jgZad=FBjT|JxS+_q@voYFm*6oknakU`1%52C((Te?%3?h-g;cg+20}-W}uz@e_ z!`u)6X^y|Ly70v4o4m$@;%&j73B9wK9QGHeD?B4q>u6gQ-6r~@J%#p->a`xpn?`l4w>I5TTT%ZYGi2BP>o;3W!YrW*Z^Y&bL3XtOq07H6g!ZBK+VQ;!xA zVYLZHj}zfs4Mi_Y!8n|&q3F$$m(iuNPI>rN;-h6t}a zp=h2YZbpTo%aF*7DuGkgjL7lzOePTd<}{NhiJXa%nMBsd$Q&X^ zV`M%Op9NoM5-UjE=o%tw5Z(!ew%3bdoTt@2TFR!{LnG?UGti=dgJ z6<5V^t@KB{yUr57taBFK& zh+GG|+$>3!_Y^knC}&1L^AIu0!=q;;aYuP{w8mYo*A$2|AsUv%9ruT$$&!?)tdI{! z=Skw`;lt4iw2{wai&^<_bjCf{F3yBUqI3Eq;Y@fWnm!N-&)i32vvSu4c)l0nJQ^J~ z$gLqrf|3=T=poag&W}a&J!A%u$D>6aB2FPDMT-W@IEuL2CA4L7w33Lpw}qtM5VYx# ziR<#@XiFlu!_zF{&6mm1Y$A8lY4eomN=b5+8_8x$v|N$`Wf{E1HVf*L6YY^MBIhcr zdM7Ba0C^%hO_KcB4*$t$-=V^$K=}#k^9I;F8J*=JOMpy|mP!J@WC7QDD}l_2Cf$pX z$NvNGxk!>H3H+7~#1UT4N4FBmfjiEE6h!U&u%+d&;;#qtYBXIEcoz?p_A4N-MQ3=( zb|7y?R}pz~q)!p#o#+uFQ#!(lB9LX#ykz@`z9@1xOCf)pqn$0R7t zfs{tu6EV-gNh**X(VC;trmQ)S43gfoYSf&3etKt$|mg^kZB zlcYeo{S2H=0jXiwKdg)^17f`Jwus*ULOoZugi^=9wNMwjIBgIorN`QXq@wq=b-efjjV?; zPDX2t+{`E>l1ikxQS%XDldp)YHqjd`jK>}o#N92oGV+kf-SRB3X>F8}4eyrQ7*-a> z5zh>WIoQT%DvA3hR~uuABkPHB2H4 zfC!!MYFsV}u4tl$sYXjl;OTTI{UeB+YV;uzg>(KjK)M-Yh@I3z+JpbAom$fry6wM4kfj8IT8z>A7gr78Ls;cx4*z64?rOmlAeBEsZZcBoW9%#x5eF$394c zy_Qk#AvV~IFx2T7c`VF=%YlqC%87_vZ3bkVu{TfH6euM#;l35r=V3#6+9l%MLJOphxo�(olNV=`&#D#r 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 (D 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 z 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⋘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