/* * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ using System; using System.Globalization; using System.Linq; using System.Numerics; using System.Security.Cryptography; using System.Text; using Amazon.CognitoIdentityProvider.Model; namespace Amazon.Extensions.CognitoAuthentication.Util { /// /// Class that provides utility methods for performing the Secure Remote Password protocol /// Adapted from http://srp.stanford.edu/design.html /// internal static class AuthenticationHelper { /// /// 3072-bit /// private const string Srp_hexN = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF"; private const int Srp_g = 2; public static readonly BigInteger N = BigIntegerExtensions.FromUnsignedLittleEndianHex(Srp_hexN); public static readonly BigInteger g = new BigInteger(Srp_g); public static readonly BigInteger k; /// /// Secret key size bytes /// public const int SecretKeySizeBytes = 128; /// /// Value used by AWS Cognito /// public const int DerivedKeySizeBytes = 16; public const string DerivedKeyInfo = "Caldera Derived Key"; static AuthenticationHelper() { // generate k for the input key material to HKDF var content = CognitoAuthHelper.CombineBytes(N.ToBigEndianByteArray(), g.ToBigEndianByteArray()); var messageDigest = CognitoAuthHelper.Sha256.ComputeHash(content); k = BigIntegerExtensions.FromUnsignedBigEndian(messageDigest); } /// /// Return the Tuple of (A, a) for SRP /// /// public static Tuple CreateAaTuple() { var a = CreateBigIntegerRandom(); var A = BigInteger.ModPow(g, a, N); return Tuple.Create(A, a); } /// /// Generates the claim for authenticating a user through the SRP protocol /// /// Username of CognitoUser /// Password of CognitoUser /// PoolName of CognitoUserPool (from poolID: _) /// TupleAa from CreateAaTuple /// salt provided in ChallengeParameters from Cognito /// srpb provided in ChallengeParameters from Cognito /// secret block provided in ChallengeParameters from Cognito /// En-US Culture of Current Time /// Returns the claim for authenticating the given user public static byte[] AuthenticateUser( string username, string password, string poolName, Tuple tupleAa, string saltString, string srpbString, string secretBlockBase64, string formattedTimestamp) { var B = BigIntegerExtensions.FromUnsignedLittleEndianHex(srpbString); if (B.TrueMod(N).Equals(BigInteger.Zero)) throw new ArgumentException("B mod N cannot be zero.", nameof(srpbString)); var secretBlockBytes = Convert.FromBase64String(secretBlockBase64); var salt = BigIntegerExtensions.FromUnsignedLittleEndianHex(saltString); // Need to generate the key to hash the response based on our A and what AWS sent back var key = GetPasswordAuthenticationKey(username, password, poolName, tupleAa, B, salt); // HMAC our data with key (HKDF(S)) (the shared secret) var contentBytes = CognitoAuthHelper.CombineBytes(Encoding.UTF8.GetBytes(poolName), Encoding.UTF8.GetBytes(username), secretBlockBytes, Encoding.UTF8.GetBytes(formattedTimestamp)); using (var hashAlgorithm = new HMACSHA256(key)) { return hashAlgorithm.ComputeHash(contentBytes); } } /// /// Creates the Password Authentication Key based on the SRP protocol /// /// Username of CognitoUser /// Password of CognitoUser /// PoolName of CognitoUserPool (part of poolID after "_") /// Returned from TupleAa /// BigInteger SRPB from AWS ChallengeParameters /// BigInteger salt from AWS ChallengeParameters /// Returns the password authentication key for the SRP protocol public static byte[] GetPasswordAuthenticationKey( string userID, string userPassword, string poolName, Tuple Aa, BigInteger B, BigInteger salt) { // Authenticate the password // u = H(A, B) byte[] contentBytes = CognitoAuthHelper.CombineBytes(Aa.Item1.ToBigEndianByteArray(), B.ToBigEndianByteArray()); byte[] digest = CognitoAuthHelper.Sha256.ComputeHash(contentBytes); BigInteger u = BigIntegerExtensions.FromUnsignedBigEndian(digest); if (u.Equals(BigInteger.Zero)) { throw new ArgumentException("Hash of A and B cannot be zero."); } // x = H(salt | H(poolName | userId | ":" | password)) byte[] userIdContent = CognitoAuthHelper.CombineBytes(Encoding.UTF8.GetBytes(poolName), Encoding.UTF8.GetBytes(userID), Encoding.UTF8.GetBytes(":"), Encoding.UTF8.GetBytes(userPassword)); byte[] userIdHash = CognitoAuthHelper.Sha256.ComputeHash(userIdContent); byte[] xBytes = CognitoAuthHelper.CombineBytes(salt.ToBigEndianByteArray(), userIdHash); byte[] xDigest = CognitoAuthHelper.Sha256.ComputeHash(xBytes); BigInteger x = BigIntegerExtensions.FromUnsignedBigEndian(xDigest); // Use HKDF to get final password authentication key var first = (B - k * BigInteger.ModPow(g, x, N)).TrueMod(N); var second = BigInteger.ModPow(first, Aa.Item2 + u * x, N); HkdfSha256 hkdfSha256 = new HkdfSha256(u.ToBigEndianByteArray(), second.ToBigEndianByteArray()); return hkdfSha256.Expand(Encoding.UTF8.GetBytes(DerivedKeyInfo), DerivedKeySizeBytes); } /// /// Generates a DeviceSecretVerifierConfigType object based on a CognitoDevice's Key, Group Key, and Password /// /// The Group Key of the CognitoDevice /// A random password for the CognitoDevice (used in the future for logging in via this device) /// The username of the CognitoDevice user /// public static DeviceSecretVerifierConfigType GenerateDeviceVerifier(string deviceGroupKey, string devicePass, string username) { Random r = new Random(); byte[] userIdContent = CognitoAuthHelper.CombineBytes( Encoding.UTF8.GetBytes(deviceGroupKey), Encoding.UTF8.GetBytes(username), Encoding.UTF8.GetBytes(":"), Encoding.UTF8.GetBytes(devicePass) ); byte[] userIdHash = CognitoAuthHelper.Sha256.ComputeHash(userIdContent); byte[] saltBytes = new byte[16]; RandomNumberGenerator.Create().GetBytes(saltBytes); // setting the initial byte to 0-127 to avoid negative salt or password verifier error saltBytes[0] = (byte) r.Next(sbyte.MaxValue); byte[] xBytes = CognitoAuthHelper.CombineBytes(saltBytes, userIdHash); byte[] xDigest = CognitoAuthHelper.Sha256.ComputeHash(xBytes); BigInteger x = BigIntegerExtensions.FromUnsignedBigEndian(xDigest); var v = BigInteger.ModPow(g, x, N); byte[] vBytes = v.ToBigEndianByteArray(); return new DeviceSecretVerifierConfigType { PasswordVerifier = Convert.ToBase64String(vBytes), Salt = Convert.ToBase64String(saltBytes) }; } /// /// Generates the claim for authenticating a device through the SRP protocol /// /// Username of Cognito User /// Key of CognitoDevice /// Password of CognitoDevice /// GroupKey of CognitoDevice /// salt provided in ChallengeParameters from Cognito /// srpb provided in ChallengeParameters from Cognito /// secret block provided in ChallengeParameters from Cognito /// En-US Culture of Current Time /// TupleAa from CreateAaTuple /// Returns the claim for authenticating the given user public static byte[] AuthenticateDevice( string username, string deviceKey, string devicePassword, string deviceGroupKey, string saltString, string srpbString, string secretBlockBase64, string formattedTimestamp, Tuple tupleAa) { var B = BigIntegerExtensions.FromUnsignedLittleEndianHex(srpbString); if (B.TrueMod(N).Equals(BigInteger.Zero)) throw new ArgumentException("B mod N cannot be zero.", nameof(srpbString)); var salt = BigIntegerExtensions.FromUnsignedLittleEndianHex(saltString); var secretBlockBytes = Convert.FromBase64String(secretBlockBase64); // Need to generate the key to hash the response based on our A and what AWS sent back var key = GetDeviceAuthenticationKey(username, devicePassword, deviceGroupKey, tupleAa, B, salt); // HMAC our data with key (HKDF(S)) (the shared secret) var msg = CognitoAuthHelper.CombineBytes( Encoding.UTF8.GetBytes(deviceGroupKey), Encoding.UTF8.GetBytes(deviceKey), secretBlockBytes, Encoding.UTF8.GetBytes(formattedTimestamp) ); using (var hashAlgorithm = new HMACSHA256(key)) { return hashAlgorithm.ComputeHash(msg); } } /// /// Creates the Device Password Authentication Key based on the SRP protocol /// /// Username of Cognito User /// Password of CognitoDevice /// GroupKey of CognitoDevice /// Returned from TupleAa /// BigInteger SRPB from AWS ChallengeParameters /// BigInteger salt from AWS ChallengeParameters /// Returns the password authentication key for the SRP protocol public static byte[] GetDeviceAuthenticationKey( string username, string devicePass, string deviceGroup, Tuple Aa, BigInteger B, BigInteger salt) { // Authenticate the password // u = H(A, B) byte[] contentBytes = CognitoAuthHelper.CombineBytes(Aa.Item1.ToBigEndianByteArray(), B.ToBigEndianByteArray()); byte[] digest = CognitoAuthHelper.Sha256.ComputeHash(contentBytes); BigInteger u = BigIntegerExtensions.FromUnsignedBigEndian(digest); if (u.Equals(BigInteger.Zero)) { throw new ArgumentException("Hash of A and B cannot be zero."); } // x = H(salt | H(deviceGroupKey | deviceKey | ":" | devicePassword)) byte[] deviceContent = CognitoAuthHelper.CombineBytes(Encoding.UTF8.GetBytes(deviceGroup), Encoding.UTF8.GetBytes(username), Encoding.UTF8.GetBytes(":"), Encoding.UTF8.GetBytes(devicePass)); byte[] deviceHash = CognitoAuthHelper.Sha256.ComputeHash(deviceContent); byte[] xBytes = CognitoAuthHelper.CombineBytes(salt.ToBigEndianByteArray(), deviceHash); byte[] xDigest = CognitoAuthHelper.Sha256.ComputeHash(xBytes); BigInteger x = BigIntegerExtensions.FromUnsignedBigEndian(xDigest); var gX = BigInteger.ModPow(g, x, N); // Use HKDF to get final password authentication key var intValue2 = (B - k * gX).TrueMod(N); var s_value = BigInteger.ModPow(intValue2, Aa.Item2 + u * x, N); HkdfSha256 hkdfSha256 = new HkdfSha256(u.ToBigEndianByteArray(), s_value.ToBigEndianByteArray()); return hkdfSha256.Expand(Encoding.UTF8.GetBytes(DerivedKeyInfo), DerivedKeySizeBytes); } /// /// Create a cryptographically secure random BigInteger /// /// public static BigInteger CreateBigIntegerRandom() { var b = new byte[SecretKeySizeBytes]; using(var cryptoRandom = RandomNumberGenerator.Create()) { cryptoRandom.GetBytes(b); } return BigIntegerExtensions.FromUnsignedBigEndian(b); } } }