#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR) #pragma warning disable using System; using System.Text; using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto; using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Macs; using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Parameters; using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Utilities; using BestHTTP.SecureProtocol.Org.BouncyCastle.Math; using BestHTTP.SecureProtocol.Org.BouncyCastle.Security; using BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities; namespace BestHTTP.SecureProtocol.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); } } } #pragma warning restore #endif