/// <summary> /// Creates and initializes a new instance of the ClientMessage2 class using /// the specified public key and client proof. /// </summary> /// <param name="publicKey">The client's public key.</param> /// <param name="proof">The calculated client proof.</param> /// <exception cref="ArgumentNullException">Thrown if either the public key /// or the proof parameter is null.</exception> public ClientMessage2(Mpi publicKey, byte[] proof) : this() { publicKey.ThrowIfNull("publicKey"); proof.ThrowIfNull("proof"); PublicKey = publicKey; Proof = proof; }
/// <summary> /// Calculates the client's ephemeral public key. /// </summary> /// <param name="generator">The generator sent by the server.</param> /// <param name="safePrimeModulus">The safe prime modulus sent by /// the server.</param> /// <param name="privateKey">The client's private key.</param> /// <returns>The client's ephemeral public key as a /// "multi-precision integer".</returns> /// <remarks> /// A = Client Public Key /// g = Generator /// a = Client Private Key /// N = Safe Prime Modulus /// </remarks> public static Mpi ComputeClientPublicKey(Mpi generator, Mpi safePrimeModulus, Mpi privateKey) { // A = g ^ a % N BigInteger result = BigInteger.ModPow(generator.Value, privateKey.Value, safePrimeModulus.Value); return new Mpi(result); }
/// <summary> /// Determines whether the specified modulus is valid. /// </summary> /// <param name="N">The modulus to validate.</param> /// <returns>True if the specified modulus is valid, otherwise /// false.</returns> public static bool IsValidModulus(Mpi N) { foreach (string s in moduli) { BigInteger a = BigInteger.Parse(s, NumberStyles.HexNumber); if (BigInteger.Compare(a, N.Value) == 0) return true; } // Fixme: Perform proper validation? return false; }
/// <summary> /// Determines whether the specified modulus is valid. /// </summary> /// <param name="N">The modulus to validate.</param> /// <returns>True if the specified modulus is valid, otherwise /// false.</returns> public static bool IsValidModulus(Mpi N) { foreach (string s in moduli) { BigInteger a = BigInteger.Parse(s, NumberStyles.HexNumber); if (BigInteger.Compare(a, N.Value) == 0) { return(true); } } // Fixme: Perform proper validation? return(false); }
public void SerializeClientSecondMessage() { BigInteger key = BigInteger.Parse(clientPublicKey, NumberStyles.HexNumber); Mpi _publicKey = new Mpi(key); ClientMessage2 m = new ClientMessage2(_publicKey, clientProof); m.InitialVector = clientInitialVector; foreach (KeyValuePair<string, string> p in clientOptions) m.Options.Add(p.Key, p.Value); byte[] serialized = m.Serialize(); Assert.IsTrue(serialized.SequenceEqual(expectedClientMessage2)); }
/// <summary> /// Determines whether the specified generator is valid. /// </summary> /// <param name="g">The generator to validate.</param> /// <returns>True if the specified generator is valid, otherwise /// false.</returns> public static bool IsValidGenerator(Mpi g) { return BigInteger.Compare(new BigInteger(2), g.Value) == 0; }
/// <summary> /// Computes the server evidence from the given parameters. /// </summary> /// <param name="clientPublicKey">The client's ephemeral public key.</param> /// <param name="clientProof"></param> /// <param name="sharedKey">The shared context key.</param> /// <param name="authId">The authorization identity.</param> /// <param name="options">The raw options string as sent by the /// client.</param> /// <param name="sid">The session id sent by the server.</param> /// <param name="ttl">The time-to-live value for the session id sent /// by the server.</param> /// <param name="hashAlgorithm">The message digest algorithm to use for /// calculating the server proof.</param> /// <returns>The server proof as an array of bytes.</returns> public static byte[] ComputeServerProof(Mpi clientPublicKey, byte[] clientProof, Mpi sharedKey, string authId, string options, string sid, uint ttl, HashAlgorithm hashAlgorithm) { byte[] A = clientPublicKey.ToBytes(), M1 = clientProof, K = sharedKey.ToBytes(), I = Encoding.UTF8.GetBytes(authId), o = Encoding.UTF8.GetBytes(options), _sid = Encoding.UTF8.GetBytes(sid); HashAlgorithm H = hashAlgorithm; // The proof is calculated as follows: // // H( bytes(A) // | bytes(M1) // | bytes(K) // | bytes(H( bytes(I) )) // | bytes(H( bytes(o) )) // | bytes(sid) // | ttl // ) byte[] seq = new ByteBuilder() .Append(A) .Append(M1) .Append(K) .Append(H.ComputeHash(I)) .Append(H.ComputeHash(o)) .Append(_sid) .Append(ttl, true) .ToArray(); return H.ComputeHash(seq); }
/// <summary> /// Computes the client evidence from the given parameters. /// </summary> /// <param name="safePrimeModulus">The safe prime modulus sent by the /// server.</param> /// <param name="generator">The generator sent by the server.</param> /// <param name="username">The username to authenticate with.</param> /// <param name="salt">The client's password salt.</param> /// <param name="clientPublicKey">The client's ephemeral public key.</param> /// <param name="serverPublicKey">The server's ephemeral public key.</param> /// <param name="sharedKey">The shared context key.</param> /// <param name="authId">The authorization identity.</param> /// <param name="options">The raw options string as received from the /// server.</param> /// <param name="hashAlgorithm">The message digest algorithm to use for /// calculating the client proof.</param> /// <returns>The client proof as an array of bytes.</returns> public static byte[] ComputeClientProof(Mpi safePrimeModulus, Mpi generator, string username, byte[] salt, Mpi clientPublicKey, Mpi serverPublicKey, Mpi sharedKey, string authId, string options, HashAlgorithm hashAlgorithm) { byte[] N = safePrimeModulus.ToBytes(), g = generator.ToBytes(), U = Encoding.UTF8.GetBytes(username), s = salt, A = clientPublicKey.ToBytes(), B = serverPublicKey.ToBytes(), K = sharedKey.ToBytes(), I = Encoding.UTF8.GetBytes(authId), L = Encoding.UTF8.GetBytes(options); HashAlgorithm H = hashAlgorithm; // The proof is calculated as follows: // // H( bytes(H( bytes(N) )) ^ bytes( H( bytes(g) )) // | bytes(H( bytes(U) )) // | bytes(s) // | bytes(A) // | bytes(B) // | bytes(K) // | bytes(H( bytes(I) )) // | bytes(H( bytes(L) )) // ) byte[] seq = new ByteBuilder() .Append(Xor(H.ComputeHash(N), H.ComputeHash(g))) .Append(H.ComputeHash(U)) .Append(s) .Append(A) .Append(B) .Append(K) .Append(H.ComputeHash(I)) .Append(H.ComputeHash(L)) .ToArray(); return H.ComputeHash(seq); }
/// <summary> /// Calculates the shared context key K from the given parameters. /// </summary> /// <param name="salt">The user's password salt.</param> /// <param name="username">The username to authenticate with.</param> /// <param name="password">The password to authenticate with.</param> /// <param name="clientPublicKey">The client's ephemeral public key.</param> /// <param name="serverPublicKey">The server's ephemeral public key.</param> /// <param name="clientPrivateKey">The client's private key.</param> /// <param name="generator">The generator sent by the server.</param> /// <param name="safePrimeModulus">The safe prime modulus sent by the /// server.</param> /// <param name="hashAlgorithm">The negotiated hash algorithm to use /// for the calculations.</param> /// <returns>The shared context key K as a "multi-precision /// integer".</returns> /// <remarks> /// A = Client Public Key /// B = Server Public Key /// N = Safe Prime Modulus /// U = Username /// p = Password /// s = User's Password Salt /// a = Client Private Key /// g = Generator /// K = Shared Public Key /// </remarks> public static Mpi ComputeSharedKey(byte[] salt, string username, string password, Mpi clientPublicKey, Mpi serverPublicKey, Mpi clientPrivateKey, Mpi generator, Mpi safePrimeModulus, HashAlgorithm hashAlgorithm) { // u = H(A | B) byte[] u = hashAlgorithm.ComputeHash(new ByteBuilder() .Append(clientPublicKey.ToBytes()) .Append(serverPublicKey.ToBytes()) .ToArray()); // x = H(s | H(U | ":" | p)) byte[] up = hashAlgorithm.ComputeHash( Encoding.UTF8.GetBytes(username + ":" + password)), sup = new ByteBuilder().Append(salt).Append(up).ToArray(), x = hashAlgorithm.ComputeHash(sup); // S = ((B - (3 * g ^ x)) ^ (a + u * x)) % N Mpi _u = new Mpi(u), _x = new Mpi(x); ts.TraceInformation("ComputeSharedKey: _u = " + _u.Value.ToString("X")); ts.TraceInformation("ComputeSharedKey: _x = " + _x.Value.ToString("X")); // base = B - (3 * (g ^ x)) BigInteger _base = BigInteger.Subtract(serverPublicKey.Value, BigInteger.Multiply(new BigInteger(3), BigInteger.ModPow(generator.Value, _x.Value, safePrimeModulus.Value)) % safePrimeModulus.Value); if (_base.Sign < 0) _base = BigInteger.Add(_base, safePrimeModulus.Value); ts.TraceInformation("ComputeSharedKey: _base = " + _base.ToString("X")); // Alternative way to calculate base; This is not being used in actual calculations // but still here to ease debugging. BigInteger gx = BigInteger.ModPow(generator.Value, _x.Value, safePrimeModulus.Value), gx3 = BigInteger.Multiply(new BigInteger(3), gx) % safePrimeModulus.Value; ts.TraceInformation("ComputeSharedKey: gx = " + gx.ToString("X")); BigInteger @base = BigInteger.Subtract(serverPublicKey.Value, gx3) % safePrimeModulus.Value; if (@base.Sign < 0) @base = BigInteger.Add(@base, safePrimeModulus.Value); ts.TraceInformation("ComputeSharedKey: @base = " + @base.ToString("X")); // exp = a + u * x BigInteger exp = BigInteger.Add(clientPrivateKey.Value, BigInteger.Multiply(_u.Value, _x.Value)), S = BigInteger.ModPow(_base, exp, safePrimeModulus.Value); ts.TraceInformation("ComputeSharedKey: exp = " + exp.ToString("X")); ts.TraceInformation("ComputeSharedKey: S = " + S.ToString("X")); // K = H(S) return new Mpi(hashAlgorithm.ComputeHash(new Mpi(S).ToBytes())); }
/// <summary> /// Determines whether the specified generator is valid. /// </summary> /// <param name="g">The generator to validate.</param> /// <returns>True if the specified generator is valid, otherwise /// false.</returns> public static bool IsValidGenerator(Mpi g) { return(BigInteger.Compare(new BigInteger(2), g.Value) == 0); }
/// <summary> /// Computes the client evidence from the given parameters. /// </summary> /// <param name="safePrimeModulus">The safe prime modulus sent by the /// server.</param> /// <param name="generator">The generator sent by the server.</param> /// <param name="username">The username to authenticate with.</param> /// <param name="salt">The client's password salt.</param> /// <param name="clientPublicKey">The client's ephemeral public key.</param> /// <param name="serverPublicKey">The server's ephemeral public key.</param> /// <param name="sharedKey">The shared context key.</param> /// <param name="authId">The authorization identity.</param> /// <param name="options">The raw options string as received from the /// server.</param> /// <param name="hashAlgorithm">The message digest algorithm to use for /// calculating the client proof.</param> /// <returns>The client proof as an array of bytes.</returns> public static byte[] ComputeClientProof(Mpi safePrimeModulus, Mpi generator, string username, byte[] salt, Mpi clientPublicKey, Mpi serverPublicKey, Mpi sharedKey, string authId, string options, HashAlgorithm hashAlgorithm) { byte[] N = safePrimeModulus.ToBytes(), g = generator.ToBytes(), U = Encoding.UTF8.GetBytes(username), s = salt, A = clientPublicKey.ToBytes(), B = serverPublicKey.ToBytes(), K = sharedKey.ToBytes(), I = Encoding.UTF8.GetBytes(authId), L = Encoding.UTF8.GetBytes(options); HashAlgorithm H = hashAlgorithm; // The proof is calculated as follows: // // H( bytes(H( bytes(N) )) ^ bytes( H( bytes(g) )) // | bytes(H( bytes(U) )) // | bytes(s) // | bytes(A) // | bytes(B) // | bytes(K) // | bytes(H( bytes(I) )) // | bytes(H( bytes(L) )) // ) byte[] seq = new ByteBuilder() .Append(Xor(H.ComputeHash(N), H.ComputeHash(g))) .Append(H.ComputeHash(U)) .Append(s) .Append(A) .Append(B) .Append(K) .Append(H.ComputeHash(I)) .Append(H.ComputeHash(L)) .ToArray(); return(H.ComputeHash(seq)); }
/// <summary> /// Computes the client response containing the client's public key and /// evidence. /// </summary> /// <param name="challenge">The challenge containing the protocol elements /// received from the server in response to the initial client /// response.</param> /// <returns>An array of bytes containing the client's challenge /// response.</returns> /// <exception cref="SaslException">Thrown if the server specified any /// mandatory options which are not supported.</exception> private byte[] ComputeFinalResponse(byte[] challenge) { ServerMessage1 m = ServerMessage1.Deserialize(challenge); // We don't support integrity protection or confidentiality. if (!String.IsNullOrEmpty(m.Options["mandatory"])) throw new SaslException("Mandatory options are not supported."); // Set up the message digest algorithm. var mda = SelectHashAlgorithm(m.Options["mda"]); HashAlgorithm = Activator.CreateInstance(mda.Item2) as HashAlgorithm; // Compute public and private key. PublicKey = Helper.ComputeClientPublicKey(m.Generator, m.SafePrimeModulus, PrivateKey); // Compute the shared key and client evidence. SharedKey = Helper.ComputeSharedKey(m.Salt, Username, Password, PublicKey, m.PublicKey, PrivateKey, m.Generator, m.SafePrimeModulus, HashAlgorithm); ClientProof = Helper.ComputeClientProof(m.SafePrimeModulus, m.Generator, Username, m.Salt, PublicKey, m.PublicKey, SharedKey, AuthId, m.RawOptions, HashAlgorithm); ClientMessage2 response = new ClientMessage2(PublicKey, ClientProof); // Let the server know which hash algorithm we are using. response.Options["mda"] = mda.Item1; // Remember the raw options string because we'll need it again // when verifying the server signature. Options = response.BuildOptionsString(); return response.Serialize(); }
/// <summary> /// Internal constructor used for unit testing. /// </summary> /// <param name="username">The username to authenticate with.</param> /// <param name="password">The plaintext password to authenticate /// with.</param> /// <param name="privateKey">The client private key to use.</param> /// <exception cref="ArgumentNullException">Thrown if the username /// or the password parameter is null.</exception> /// <exception cref="ArgumentException">Thrown if the username /// parameter is empty.</exception> internal SaslSrp(string username, string password, byte[] privateKey) : this(username, password) { PrivateKey = new Mpi(privateKey); }