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 all 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.CalculateM2RFC2945(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.CalculateM2RFC2945(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
123 changes: 122 additions & 1 deletion 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 All @@ -112,6 +183,56 @@ public static BigInteger CalculateM2(IDigest digest, BigInteger N, BigInteger A,
return M2;
}

/**
* Computes the server evidence message (M2) according to the standard routine:
* M2 = H( A | M1 | 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 M1 The client evidence message
* @param K The final key
* @return M2 The calculated server evidence message
*/
public static BigInteger CalculateM2RFC2945(IDigest digest, BigInteger N, BigInteger A, BigInteger M1, BigInteger K)
{
int paddedLength = (N.BitLength + 7) / 8;
int digestSize = digest.GetDigestSize();

#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
Span<byte> bytes = paddedLength <= 512
? stackalloc byte[paddedLength]
: new byte[paddedLength];
BigIntegers.AsUnsignedByteArray(A, bytes);
digest.BlockUpdate(bytes);

// no padding for M1 and K
bytes = BigIntegers.AsUnsignedByteArray(M1);
digest.BlockUpdate(bytes);
bytes = BigIntegers.AsUnsignedByteArray(K);
digest.BlockUpdate(bytes);

Span<byte> output = digestSize <= 128
? stackalloc byte[digestSize]
: new byte[digestSize];
digest.DoFinal(output);
#else
byte[] bytes = new byte[paddedLength];
BigIntegers.AsUnsignedByteArray(A, bytes, 0, bytes.Length);
digest.BlockUpdate(bytes, 0, bytes.Length);

// no padding for M1 and K
bytes = BigIntegers.AsUnsignedByteArray(M1);
digest.BlockUpdate(bytes, 0, bytes.Length);
bytes = BigIntegers.AsUnsignedByteArray(K);
digest.BlockUpdate(bytes, 0, bytes.Length);

byte[] output = new byte[digestSize];
digest.DoFinal(output, 0);
#endif

return new BigInteger(1, output);
}

/**
* Computes the final Key according to the standard routine: Key = H(S)
* @param digest The Digest used as the hashing function H
Expand Down Expand Up @@ -214,5 +335,5 @@ private static BigInteger HashPaddedPair(IDigest digest, BigInteger N, BigIntege

return new BigInteger(1, output);
}
}
}
}
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;
}
}
}

Loading