Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added SRP message verification using RFC2945 #507

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 49 additions & 8 deletions crypto/src/crypto/agreement/srp/SRP6Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ namespace Org.BouncyCastle.Crypto.Agreement.Srp
*/
public class Srp6Client
{
protected BigInteger N;
protected BigInteger g;
private bool isRFC2945 = false;

protected BigInteger N;
protected BigInteger g;

protected BigInteger privA;
protected BigInteger pubA;
Expand Down Expand Up @@ -115,10 +117,39 @@ public virtual BigInteger CalculateClientEvidenceMessage()
"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;
this.M1 = Srp6Utilities.CalculateM1(digest, N, pubA, B, S);
this.isRFC2945 = false;

return M1;
}

/**
* 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
* @param messageVerifier: message verifier pre-generated from identity and salt
* @throws CryptoException
*/
public virtual BigInteger CalculateClientEvidenceMessageRFC2945(byte[] messageVerifier)
{
// 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)");
}

if (this.Key == null)
{
this.Key = Srp6Utilities.CalculateKey(digest, N, S);
}

// compute the client evidence message 'M1'
this.M1 = Srp6Utilities.CalculateM1(digest, N, g, pubA, B, Key, messageVerifier);
this.isRFC2945 = true;
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.
Expand All @@ -133,12 +164,22 @@ public virtual bool VerifyServerEvidenceMessage(BigInteger serverM2)
"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);
// Compute the own server evidence message 'M2'
BigInteger computedM2;

if(isRFC2945)
{
computedM2 = Srp6Utilities.CalculateM2(digest, N, pubA, M1, Key);
}
else
{
computedM2 = Srp6Utilities.CalculateM2(digest, N, pubA, M1, S);
}

if (computedM2.Equals(serverM2))
{
this.M2 = serverM2;
return true;
this.M2 = serverM2;
return true;
}
return false;
}
Expand Down
79 changes: 61 additions & 18 deletions crypto/src/crypto/agreement/srp/SRP6Server.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ public class Srp6Server
protected BigInteger M2;
protected BigInteger Key;

private bool isRFC2945 = false;

public Srp6Server()
{
}
Expand Down Expand Up @@ -114,33 +116,74 @@ public virtual bool VerifyClientEvidenceMessage(BigInteger clientM1)

// 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;
}
if (computedM1.Equals(clientM1))
{
this.M1 = clientM1;
this.isRFC2945 = false;
return true;
}
return false;
}

/**
* Authenticates the received client evidence message M1 using RFC2945 and saves it only if correct.
* To be called after calculating the secret S.
* @param M1: the client side generated evidence message
* @param messageVerifier: message verifier pre-generated from identity and salt
* @return A boolean indicating if the client message M1 was the expected one.
* @throws CryptoException
*/
public virtual bool VerifyClientEvidenceMessageRFC2945(BigInteger clientM1, byte[] messageVerifier)
{
// Verify pre-requirements
if (this.A == null || this.pubB == null || this.S == null || messageVerifier == null)
{
throw new CryptoException("Impossible to compute and verify M1: " +
"some data are missing from the previous operations (A,B,S,messageVerifier)");
}

if (this.Key == null)
{
this.Key = Srp6Utilities.CalculateKey(digest, N, S);
}

// Compute the own client evidence message 'M1'
BigInteger computedM1 = Srp6Utilities.CalculateM1(digest, N, g, A, pubB, Key, messageVerifier);
if (computedM1.Equals(clientM1))
{
this.M1 = clientM1;
this.isRFC2945 = true;
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)");
}
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;
}
if (isRFC2945)
{
this.M2 = Srp6Utilities.CalculateM2(digest, N, A, M1, Key);
}
else
{
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
Expand Down
71 changes: 71 additions & 0 deletions crypto/src/crypto/agreement/srp/SRP6Utilities.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;

using System.Linq;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities;
Expand Down Expand Up @@ -60,6 +61,15 @@ public static BigInteger CalculateX(IDigest digest, BigInteger N, ReadOnlySpan<b
}
#endif

public static byte[] CalculateY(IDigest digest, byte[] salt, byte[] identity)
{
byte[] output = new byte[digest.GetDigestSize()];
digest.BlockUpdate(identity, 0, identity.Length);
digest.DoFinal(output, 0);
output = output.Concat(salt).ToArray();
return output;
}

public static BigInteger GeneratePrivateValue(IDigest digest, BigInteger N, BigInteger g, SecureRandom random)
{
int minBits = System.Math.Min(256, N.BitLength / 2);
Expand Down Expand Up @@ -96,6 +106,67 @@ public static BigInteger CalculateM1(IDigest digest, BigInteger N, BigInteger A,
return M1;
}

/**
* Computes the client evidence message (M1) according to the standard routine:
* M1 = H(H(N) XOR H(g) | H(I) | s | A | B | K)
* @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 K final key
* @param messageVerifier = H(I) | s
* @return M1 The calculated client evidence message
*/
public static BigInteger CalculateM1(IDigest digest, BigInteger N, BigInteger g, BigInteger A, BigInteger B, BigInteger K, byte[] messageVerifier)
{
byte[] bA = VALUEOF(A);
byte[] bB = VALUEOF(B);
byte[] bK = VALUEOF(K, digest.GetDigestSize());
byte[] bM1 = SHA(digest, CONCAT(XOR(SHA(digest, N), SHA(digest, g)), CONCAT(messageVerifier, CONCAT(bA, CONCAT(bB, bK)))));
BigInteger M1 = new BigInteger(1, bM1);
return M1;
}

private static byte[] VALUEOF(BigInteger value, int length = -1)
{
int paddedLength = (value.BitLength + 7) / 8;
if(length > 0)
{
paddedLength = length;
}
byte[] bytes = new byte[paddedLength];
BigIntegers.AsUnsignedByteArray(value, bytes, 0, bytes.Length);
return bytes;
}

private static byte[] SHA(IDigest digest, BigInteger value)
{
return SHA(digest, value.ToByteArrayUnsigned());
}

private static byte[] SHA(IDigest digest, byte[] bytes)
{
digest.Reset();
digest.BlockUpdate(bytes, 0, bytes.Length);
byte[] rv = new byte[digest.GetDigestSize()];
digest.DoFinal(rv, 0);
return rv;
}

private static byte[] CONCAT(byte[] a, byte[] b)
{
return a.Concat(b).ToArray();
}

private static byte[] XOR(byte[] a, byte[] b)
{
for (int i = 0; i < a.Length; i++)
{
a[i] ^= b[i];
}
return a;
}

/**
* Computes the server evidence message (M2) according to the standard routine:
* M2 = H( A | M1 | S )
Expand Down
14 changes: 13 additions & 1 deletion crypto/src/crypto/agreement/srp/SRP6VerifierGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,18 @@ public virtual BigInteger GenerateVerifier(byte[] salt, byte[] identity, byte[]

return g.ModPow(x, N);
}
}

/**
* Creates a new SRP verifier for M1
* @param salt The salt to use, generally should be large and random
* @param identity The user's identifying information (eg. username)
* @return A new verifier for use in future SRP authentication
*/
public virtual byte[] GenerateMessageVerifierRFC2945(byte[] salt, byte[] identity)
{
byte[] mv = Srp6Utilities.CalculateY(digest, salt, identity);
return mv;
}
}
}

63 changes: 57 additions & 6 deletions crypto/test/src/crypto/test/SRP6Test.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,69 @@ public override string Name

public override void PerformTest()
{
rfc5054AppendixBTestVectors();
rfc5054AppendixBTestVectors();
rfc2945MessageVerify();

testMutualVerification(Srp6StandardGroups.rfc5054_1024);
testClientCatchesBadB(Srp6StandardGroups.rfc5054_1024);
testServerCatchesBadA(Srp6StandardGroups.rfc5054_1024);

testWithRandomParams(256);
testWithRandomParams(384);
testWithRandomParams(512);
}
testWithRandomParams(256);
testWithRandomParams(384);
testWithRandomParams(512);
}

private void rfc2945MessageVerify()
jimm98y marked this conversation as resolved.
Show resolved Hide resolved
{
BigInteger N = Srp6StandardGroups.rfc5054_1024.N;
BigInteger g = Srp6StandardGroups.rfc5054_1024.G;

byte[] I = Encoding.UTF8.GetBytes("username");
byte[] P = Encoding.UTF8.GetBytes("password");
byte[] s = new byte[16];
random.NextBytes(s);

var group = new Srp6GroupParameters(N, g);

Srp6VerifierGenerator gen = new Srp6VerifierGenerator();
gen.Init(group, new Sha256Digest());
BigInteger v = gen.GenerateVerifier(s, I, P);
byte[] messageVerifier = gen.GenerateMessageVerifierRFC2945(s, I);

Srp6Client client = new Srp6Client();
client.Init(group, new Sha256Digest(), random);

Srp6Server server = new Srp6Server();
server.Init(group, v, new Sha256Digest(), random);

BigInteger A = client.GenerateClientCredentials(s, I, P);
BigInteger B = server.GenerateServerCredentials();

BigInteger clientS = client.CalculateSecret(B);
BigInteger clientM1 = client.CalculateClientEvidenceMessageRFC2945(messageVerifier);

BigInteger serverS = server.CalculateSecret(A);

if (!clientS.Equals(serverS))
{
Fail("SRP agreement failed - client/server calculated different secrets");
}

bool isClientM1Valid = server.VerifyClientEvidenceMessageRFC2945(clientM1, messageVerifier);
if(!isClientM1Valid)
{
Fail("SRP server was not able to verify M1 from the client");
}

BigInteger serverM2 = server.CalculateServerEvidenceMessage();
bool isServerM2Valid = client.VerifyServerEvidenceMessage(serverM2);
if (!isServerM2Valid)
{
Fail("SRP client was not able to verify M2 from the server");
}
}

private void rfc5054AppendixBTestVectors()
private void rfc5054AppendixBTestVectors()
{
byte[] I = Encoding.UTF8.GetBytes("alice");
byte[] P = Encoding.UTF8.GetBytes("password123");
Expand Down