You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
394 lines
15 KiB
394 lines
15 KiB
#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 |
|
{ |
|
/// <summary> |
|
/// 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. |
|
/// </summary> |
|
public abstract class JPakeUtilities |
|
{ |
|
public static readonly BigInteger Zero = BigInteger.Zero; |
|
public static readonly BigInteger One = BigInteger.One; |
|
|
|
/// <summary> |
|
/// 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]. |
|
/// </summary> |
|
public static BigInteger GenerateX1(BigInteger q, SecureRandom random) |
|
{ |
|
BigInteger min = Zero; |
|
BigInteger max = q.Subtract(One); |
|
return BigIntegers.CreateRandomInRange(min, max, random); |
|
} |
|
|
|
/// <summary> |
|
/// 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]. |
|
/// </summary> |
|
public static BigInteger GenerateX2(BigInteger q, SecureRandom random) |
|
{ |
|
BigInteger min = One; |
|
BigInteger max = q.Subtract(One); |
|
return BigIntegers.CreateRandomInRange(min, max, random); |
|
} |
|
|
|
/// <summary> |
|
/// Converts the given password to a BigInteger |
|
/// for use in arithmetic calculations. |
|
/// </summary> |
|
public static BigInteger CalculateS(char[] password) |
|
{ |
|
return new BigInteger(Encoding.UTF8.GetBytes(password)); |
|
} |
|
|
|
/// <summary> |
|
/// Calculate g^x mod p as done in round 1. |
|
/// </summary> |
|
public static BigInteger CalculateGx(BigInteger p, BigInteger g, BigInteger x) |
|
{ |
|
return g.ModPow(x, p); |
|
} |
|
|
|
/// <summary> |
|
/// Calculate ga as done in round 2. |
|
/// </summary> |
|
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); |
|
} |
|
|
|
/// <summary> |
|
/// Calculate x2 * s as done in round 2. |
|
/// </summary> |
|
public static BigInteger CalculateX2s(BigInteger q, BigInteger x2, BigInteger s) |
|
{ |
|
return x2.Multiply(s).Mod(q); |
|
} |
|
|
|
/// <summary> |
|
/// Calculate A as done in round 2. |
|
/// </summary> |
|
public static BigInteger CalculateA(BigInteger p, BigInteger q, BigInteger gA, BigInteger x2s) |
|
{ |
|
// A = ga^(x*s) |
|
return gA.ModPow(x2s, p); |
|
} |
|
|
|
/// <summary> |
|
/// 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. |
|
/// </summary> |
|
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); |
|
} |
|
|
|
/// <summary> |
|
/// Validates that g^x4 is not 1. |
|
/// throws CryptoException if g^x4 is 1 |
|
/// </summary> |
|
public static void ValidateGx4(BigInteger gx4) |
|
{ |
|
if (gx4.Equals(One)) |
|
throw new CryptoException("g^x validation failed. g^x should not be 1."); |
|
} |
|
|
|
/// <summary> |
|
/// 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 |
|
/// </summary> |
|
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."); |
|
} |
|
|
|
/// <summary> |
|
/// 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 |
|
/// </summary> |
|
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"); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// 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 |
|
/// </summary> |
|
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); |
|
} |
|
|
|
/// <summary> |
|
/// 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. |
|
/// </summary> |
|
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."); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// 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. |
|
/// </summary> |
|
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 |
|
+ "."); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Validates that the given object is not null. |
|
/// throws NullReferenceException if the object is null. |
|
/// </summary> |
|
/// <param name="obj">object in question</param> |
|
/// <param name="description">name of the object (to be used in exception message)</param> |
|
public static void ValidateNotNull(object obj, string description) |
|
{ |
|
if (obj == null) |
|
throw new ArgumentNullException(description); |
|
} |
|
|
|
/// <summary> |
|
/// Calculates the MacTag (to be used for key confirmation), as defined by |
|
/// <a href="http://csrc.nist.gov/publications/nistpubs/800-56A/SP800-56A_Revision1_Mar08-2007.pdf">NIST SP 800-56A Revision 1</a>, |
|
/// 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 |
|
/// </summary> |
|
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); |
|
} |
|
|
|
/// <summary> |
|
/// Calculates the MacKey (i.e. the key to use when calculating the MagTag for key confirmation). |
|
/// |
|
/// MacKey = H(K || "JPAKE_KC") |
|
/// </summary> |
|
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); |
|
} |
|
|
|
/// <summary> |
|
/// Validates the MacTag received from the partner participant. |
|
/// |
|
/// throws CryptoException if the participantId strings are equal. |
|
/// </summary> |
|
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
|
|
|