Skip to content

Fix Auth with device and device confirmation #183

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

Open
wants to merge 3 commits into
base: main
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
11 changes: 11 additions & 0 deletions .autover/changes/b1e5d908-6619-420c-800e-3184a952bb45.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"Projects": [
{
"Name": "Amazon.Extensions.CognitoAuthentication",
"Type": "Patch",
"ChangelogMessages": [
"Fix Auth with device and device confirmation"
]
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);


Expand Down Expand Up @@ -321,9 +321,9 @@ public virtual async Task<AuthFlowResponse> RespondToCustomAuthAsync(RespondToCu
/// <param name="deviceKey">The DeviceKey for the associated CognitoDevice</param>
/// <param name="devicePass">The random password for the associated CognitoDevice</param>
/// <returns></returns>
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);
}

/// <summary>
Expand Down
260 changes: 129 additions & 131 deletions src/Amazon.Extensions.CognitoAuthentication/Util/AuthenticationHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -157,136 +157,134 @@ public static byte[] GetPasswordAuthenticationKey(
return hkdfSha256.Expand(Encoding.UTF8.GetBytes(DerivedKeyInfo), DerivedKeySizeBytes);
}

/// <summary>
/// Generates a DeviceSecretVerifierConfigType object based on a CognitoDevice's Key, Group Key, and Password
/// </summary>
/// <param name="deviceGroupKey">The Group Key of the CognitoDevice</param>
/// <param name="devicePass">A random password for the CognitoDevice (used in the future for logging in via this device)</param>
/// <param name="username">The username of the CognitoDevice user</param>
/// <summary>
/// Generates a DeviceSecretVerifierConfigType object based on a CognitoDevice's Key, Group Key, and Password
/// </summary>
/// <param name="deviceGroupKey">The Group Key of the CognitoDevice</param>
/// <param name="devicePass">A random password for the CognitoDevice (used in the future for logging in via this device)</param>
/// <param name="deviceKey">The DeviceKey for the associated CognitoDevice</param>
/// <returns></returns>
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)
};
}

/// <summary>
/// Generates the claim for authenticating a device through the SRP protocol
/// </summary>
/// <param name="username"> Username of Cognito User</param>
/// <param name="deviceKey"> Key of CognitoDevice</param>
/// <param name="devicePassword"> Password of CognitoDevice</param>
/// <param name="deviceGroupKey"> GroupKey of CognitoDevice</param>
/// <param name="saltString"> salt provided in ChallengeParameters from Cognito </param>
/// <param name="srpbString"> srpb provided in ChallengeParameters from Cognito</param>
/// <param name="secretBlockBase64">secret block provided in ChallengeParameters from Cognito</param>
/// <param name="formattedTimestamp">En-US Culture of Current Time</param>
/// <param name="tupleAa"> TupleAa from CreateAaTuple</param>
/// <returns>Returns the claim for authenticating the given user</returns>
public static byte[] AuthenticateDevice(
string username,
string deviceKey,
string devicePassword,
string deviceGroupKey,
string saltString,
string srpbString,
string secretBlockBase64,
string formattedTimestamp,
Tuple<BigInteger, BigInteger> 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);
}
}

/// <summary>
/// Creates the Device Password Authentication Key based on the SRP protocol
/// </summary>
/// <param name="username"> Username of Cognito User</param>
/// <param name="devicePass">Password of CognitoDevice</param>
/// <param name="deviceGroup">GroupKey of CognitoDevice</param>
/// <param name="Aa">Returned from TupleAa</param>
/// <param name="B">BigInteger SRPB from AWS ChallengeParameters</param>
/// <param name="salt">BigInteger salt from AWS ChallengeParameters</param>
/// <returns>Returns the password authentication key for the SRP protocol</returns>
public static byte[] GetDeviceAuthenticationKey(
string username,
string devicePass,
string deviceGroup,
Tuple<BigInteger, BigInteger> 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)
};
}

/// <summary>
/// Generates the claim for authenticating a device through the SRP protocol
/// </summary>
/// <param name="deviceKey"> Key of CognitoDevice</param>
/// <param name="devicePassword"> Password of CognitoDevice</param>
/// <param name="deviceGroupKey"> GroupKey of CognitoDevice</param>
/// <param name="saltString"> salt provided in ChallengeParameters from Cognito </param>
/// <param name="srpbString"> srpb provided in ChallengeParameters from Cognito</param>
/// <param name="secretBlockBase64">secret block provided in ChallengeParameters from Cognito</param>
/// <param name="formattedTimestamp">En-US Culture of Current Time</param>
/// <param name="tupleAa"> TupleAa from CreateAaTuple</param>
/// <returns>Returns the claim for authenticating the given user</returns>
public static byte[] AuthenticateDevice(
string deviceKey,
string devicePassword,
string deviceGroupKey,
string saltString,
string srpbString,
string secretBlockBase64,
string formattedTimestamp,
Tuple<BigInteger, BigInteger> 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);
}
}

/// <summary>
/// Creates the Device Password Authentication Key based on the SRP protocol
/// </summary>
/// <param name="deviceKey">The DeviceKey for the associated CognitoDevice</param>
/// <param name="devicePass">Password of CognitoDevice</param>
/// <param name="deviceGroup">GroupKey of CognitoDevice</param>
/// <param name="Aa">Returned from TupleAa</param>
/// <param name="B">BigInteger SRPB from AWS ChallengeParameters</param>
/// <param name="salt">BigInteger salt from AWS ChallengeParameters</param>
/// <returns>Returns the password authentication key for the SRP protocol</returns>
public static byte[] GetDeviceAuthenticationKey(
string deviceKey,
string devicePass,
string deviceGroup,
Tuple<BigInteger, BigInteger> 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);
}

/// <summary>
Expand Down