diff --git a/.autover/changes/b1e5d908-6619-420c-800e-3184a952bb45.json b/.autover/changes/b1e5d908-6619-420c-800e-3184a952bb45.json new file mode 100644 index 0000000..484c5ba --- /dev/null +++ b/.autover/changes/b1e5d908-6619-420c-800e-3184a952bb45.json @@ -0,0 +1,11 @@ +{ + "Projects": [ + { + "Name": "Amazon.Extensions.CognitoAuthentication", + "Type": "Patch", + "ChangelogMessages": [ + "Fix Auth with device and device confirmation" + ] + } + ] +} \ No newline at end of file diff --git a/src/Amazon.Extensions.CognitoAuthentication/CognitoUserAuthentication.cs b/src/Amazon.Extensions.CognitoAuthentication/CognitoUserAuthentication.cs index 1b6bf24..824980b 100644 --- a/src/Amazon.Extensions.CognitoAuthentication/CognitoUserAuthentication.cs +++ b/src/Amazon.Extensions.CognitoAuthentication/CognitoUserAuthentication.cs @@ -193,7 +193,7 @@ private RespondToAuthChallengeRequest CreateDevicePasswordVerifierAuthRequest(Re string timeStr = DateTime.UtcNow.ToString("ddd MMM d HH:mm:ss \"UTC\" yyyy", CultureInfo.InvariantCulture); - var claimBytes = AuthenticationHelper.AuthenticateDevice(username, deviceKey, devicePassword, deviceKeyGroup, salt, + var claimBytes = AuthenticationHelper.AuthenticateDevice(deviceKey, devicePassword, deviceKeyGroup, salt, challenge.ChallengeParameters[CognitoConstants.ChlgParamSrpB], secretBlock, timeStr, tupleAa); @@ -321,9 +321,9 @@ public virtual async Task RespondToCustomAuthAsync(RespondToCu /// The DeviceKey for the associated CognitoDevice /// The random password for the associated CognitoDevice /// - public DeviceSecretVerifierConfigType GenerateDeviceVerifier(string deviceGroupKey, string devicePass, string username) + public DeviceSecretVerifierConfigType GenerateDeviceVerifier(string deviceGroupKey, string devicePass, string deviceKey) { - return AuthenticationHelper.GenerateDeviceVerifier(deviceGroupKey, devicePass, username); + return AuthenticationHelper.GenerateDeviceVerifier(deviceGroupKey, devicePass, deviceKey); } /// diff --git a/src/Amazon.Extensions.CognitoAuthentication/Util/AuthenticationHelper.cs b/src/Amazon.Extensions.CognitoAuthentication/Util/AuthenticationHelper.cs index 412d074..725c82d 100644 --- a/src/Amazon.Extensions.CognitoAuthentication/Util/AuthenticationHelper.cs +++ b/src/Amazon.Extensions.CognitoAuthentication/Util/AuthenticationHelper.cs @@ -12,10 +12,10 @@ * 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.Globalization; +using System.Linq; using System.Numerics; using System.Security.Cryptography; using System.Text; @@ -157,136 +157,134 @@ public static byte[] GetPasswordAuthenticationKey( 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 + /// + /// 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 DeviceKey for the associated CognitoDevice /// - 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(":"), + public static DeviceSecretVerifierConfigType GenerateDeviceVerifier(string deviceGroupKey, string devicePass, string deviceKey) + { + Random r = new Random(); + byte[] userIdContent = CognitoAuthHelper.CombineBytes( + Encoding.UTF8.GetBytes(deviceGroupKey), + Encoding.UTF8.GetBytes(deviceKey), + 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); + ); + + 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 + /// + /// 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 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(deviceKey, 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 + /// + /// The DeviceKey for the associated CognitoDevice + /// 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 deviceKey, + 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(deviceKey), + 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); } ///