Last active
December 10, 2019 20:28
-
-
Save BBNicolas/5ab3f1f8c604ebe2e4ecf14c35487495 to your computer and use it in GitHub Desktop.
Cognito register, validation and login with remember Devices enabled
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Security.Cryptography; | |
using System.Text; | |
using Org.BouncyCastle.Crypto; | |
using Org.BouncyCastle.Crypto.Digests; | |
using Org.BouncyCastle.Math; | |
using System.Globalization; | |
namespace cognito | |
{ | |
//https://gist.github.com/guywald/7a753e032ca2f979453b7f8aa4fb6569 | |
//https://gist.github.com/KonajuGames/0253adf035d83e3b58a872fb00e4f398 | |
public class AuthenticationHelper | |
{ | |
private const string InitN = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" | |
+ "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" | |
+ "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" | |
+ "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" | |
+ "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" | |
+ "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" | |
+ "83655D23DCA3AD961C62F356208552BB9ED529077096966D" | |
+ "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" | |
+ "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" | |
+ "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" | |
+ "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" | |
+ "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" | |
+ "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" | |
+ "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" | |
+ "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" | |
+ "43DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF"; | |
public const string NewPasswordRequiredChallengeUserAttributePrefix = "userAttributes."; | |
private const string DerivedKeyInfo = "Caldera Derived Key"; | |
private const int DerivedKeySize = 16; | |
private readonly BigInteger _a; | |
public BigInteger A; | |
private readonly BigInteger _g; | |
private readonly BigInteger _k; | |
public BigInteger N; | |
private readonly string _userPoolId; | |
public string verifierDevices { get; private set; } | |
public string SaltToHashDevices { get; private set; } | |
public string randomPassword; | |
public AuthenticationHelper(string userPool) | |
{ | |
N = new BigInteger(InitN, 16); | |
_g = new BigInteger("2"); | |
_k = new BigInteger(1, Hash(N.ToByteArray(), _g.ToByteArray())); | |
var random = new Random(); | |
do | |
{ | |
_a = new BigInteger(1024, random).Mod(N); | |
A = _g.ModPow(_a, N); | |
} while (Equals(A.Mod(N), BigInteger.Zero)); | |
_userPoolId = userPool.Substring(userPool.IndexOf('_') + 1); | |
} | |
public byte[] GetPasswordAuthenticationKey(string userId, string userPassword, BigInteger b, BigInteger salt) | |
{ | |
var u = new BigInteger(1, Hash(A.ToByteArray(), b.ToByteArray())); | |
var usernamePassword = $"{_userPoolId}{userId}:{userPassword}"; | |
var userIdhash10 = HmacSha256Hash(usernamePassword); | |
var x = new BigInteger(1, Hash(salt.ToByteArray(), userIdhash10)); | |
var s = Mod(b.Subtract(_k.Multiply(_g.ModPow(x, N))).ModPow(_a.Add(u.Multiply(x)), N), N); | |
var hmac = new HMACSHA256(); | |
// HKDF From Hkdf.cs | |
var hkdf = new HKDF(hmac, s.ToByteArray(), u.ToByteArray()); | |
return hkdf.Expand(Encoding.UTF8.GetBytes(DerivedKeyInfo), DerivedKeySize); | |
} | |
public static byte[] Hash(params byte[][] blocks) | |
{ | |
IDigest hash = new Sha256Digest(); | |
var result = new byte[hash.GetDigestSize()]; | |
foreach (var block in blocks) | |
hash.BlockUpdate(block, 0, block.Length); | |
hash.DoFinal(result, 0); | |
return result; | |
} | |
public static byte[] HmacSha256Hash(params string[] values) | |
{ | |
IDigest hash = new Sha256Digest(); | |
var result = new byte[hash.GetDigestSize()]; | |
foreach (var value in values) | |
{ | |
var bytes = Encoding.ASCII.GetBytes(value); | |
hash.BlockUpdate(bytes, 0, bytes.Length); | |
} | |
hash.DoFinal(result, 0); | |
return result; | |
} | |
private static BigInteger Mod(BigInteger dividend, BigInteger divisor) | |
{ | |
// Apparently needed for negative dividends, according to AWS SDK for iOS | |
return divisor.Add(dividend.Remainder(divisor)).Remainder(divisor); | |
} | |
public void generateHashDevice(string deviceGroupKey, string username) | |
{ | |
this.randomPassword = generateRandomString(); | |
var combinedString = $"{deviceGroupKey}{username}:{this.randomPassword}"; | |
var hashedString = HmacSha256Hash(combinedString); | |
var hexRandom = BitConverter.ToString(randomBytes(16)).Replace("-", string.Empty); | |
this.SaltToHashDevices = padHex(new BigInteger(hexRandom, 16)); | |
var x = new BigInteger(1, Hash(Encoding.ASCII.GetBytes(this.SaltToHashDevices), hashedString)); | |
var verifierDevicesNotPadded = _g.ModPow(x, N); | |
this.verifierDevices = padHex(verifierDevicesNotPadded); | |
} | |
private string generateRandomString() | |
{ | |
return Convert.ToBase64String(randomBytes(40)); | |
} | |
private Byte[] randomBytes(int size) | |
{ | |
var random = new Random(); | |
Byte[] b = new byte[size]; | |
random.NextBytes(b); | |
return b; | |
} | |
private string padHex(BigInteger bigInt) | |
{ | |
var hashStr = bigInt.ToString(16); | |
if (hashStr.Length % 2 == 1) | |
{ | |
hashStr = $"0{hashStr}"; | |
} | |
else if ("89ABCDEFabcdef".Contains(hashStr[0].ToString())) | |
{ | |
hashStr = $"00{hashStr}"; | |
} | |
return hashStr; | |
} | |
public byte[] getSaltToHashDevices_ByteArray() | |
{ | |
return stringTobyte(this.SaltToHashDevices); | |
} | |
public byte[] getVerifierDevices_ByteArray() | |
{ | |
return stringTobyte(this.verifierDevices); | |
} | |
public byte[] stringTobyte(string text) | |
{ | |
byte[] b = new byte[text.Length / 2]; | |
for (int i = 0; i < text.Length / 2; i++) | |
{ | |
b[i] = byte.Parse(text[i * 2].ToString() + text[i * 2 + 1].ToString(), NumberStyles.HexNumber); | |
} | |
return b; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Amazon; | |
using Amazon.CognitoIdentityProvider; | |
using Amazon.CognitoIdentityProvider.Model; | |
using Amazon.Runtime; | |
using Amazon.Util; | |
using Org.BouncyCastle.Crypto.Parameters; | |
using Org.BouncyCastle.Math; | |
using Org.BouncyCastle.Security; | |
using System; | |
using System.Collections.Generic; | |
using System.Globalization; | |
using System.Linq; | |
using System.Net; | |
using System.Text; | |
using System.Threading.Tasks; | |
namespace cognito | |
{ | |
class Cognito | |
{ | |
public AmazonCognitoIdentityProviderClient client { get; private set; } | |
private string userPoolId = null; | |
public string DeviceKey { get; set; } = null; | |
public string DeviceGroupKey { get; set; } = null; | |
public string randomPassword { get; set; } = null; | |
public string AccessToken = null; | |
public string IdToken = null; | |
public string RefreshToken = null; | |
public Cognito(string userPoolId, RegionEndpoint region) | |
{ | |
this.client = new AmazonCognitoIdentityProviderClient("", "", region); | |
this.userPoolId = userPoolId; | |
} | |
public Cognito(string userPoolId, string DeviceKey, string DeviceGroupKey, RegionEndpoint region) | |
{ | |
this.client = new AmazonCognitoIdentityProviderClient("", "", region); | |
this.userPoolId = userPoolId; | |
this.DeviceKey = DeviceKey; | |
this.DeviceGroupKey = DeviceGroupKey; | |
} | |
public Cognito(string userPoolId, string DeviceKey, string DeviceGroupKey, string RefreshToken, RegionEndpoint region) | |
{ | |
this.client = new AmazonCognitoIdentityProviderClient("", "", region); | |
this.userPoolId = userPoolId; | |
this.DeviceKey = DeviceKey; | |
this.DeviceGroupKey = DeviceGroupKey; | |
this.RefreshToken = RefreshToken; | |
} | |
public SignUpResponse Register(string clientId, string clientSecret, string password, string email) | |
{ | |
try | |
{ | |
SignUpRequest signUpRequest = new SignUpRequest(); | |
signUpRequest.ClientId = clientId; | |
signUpRequest.SecretHash = SecretHash(email.Replace('@', '_'), clientId, clientSecret); | |
// User name can not be an email address as the email address is used as an aliase, | |
// which prevent having multiple users on one email address. | |
// Loging in with the email address is still possible as only one user per email is allowed. | |
signUpRequest.Username = email.Replace('@', '_'); | |
signUpRequest.Password = password; | |
signUpRequest.UserAttributes.Add(AttributeType("email", email)); | |
Task<SignUpResponse> signUpResponseTask = this.client.SignUpAsync(signUpRequest); | |
signUpResponseTask.Wait(); | |
SignUpResponse response = signUpResponseTask.Result; | |
return response; | |
} | |
catch (Exception error) | |
{ | |
string errorMessage = " - ERROR in Register User - " + Environment.NewLine; | |
if (error.Data.Contains("CogntioFail")) | |
{ | |
error.Data["CogntioFail"] += errorMessage; | |
} | |
else | |
{ | |
error.Data.Add("CogntioFail", errorMessage); | |
} | |
throw error; | |
} | |
} | |
public ConfirmSignUpResponse Confirm(string confirmationCode, string clientId, string email, string clientSecret) | |
{ | |
try | |
{ | |
ConfirmSignUpRequest confirmSignUpRequest = new ConfirmSignUpRequest(); | |
confirmSignUpRequest.ClientId = clientId; | |
confirmSignUpRequest.SecretHash = SecretHash(email.Replace('@', '_'), clientId, clientSecret); | |
confirmSignUpRequest.ConfirmationCode = confirmationCode; | |
confirmSignUpRequest.Username = email.Replace('@', '_'); | |
Task<ConfirmSignUpResponse> confirmSignUpResponseTask = this.client.ConfirmSignUpAsync(confirmSignUpRequest); | |
confirmSignUpResponseTask.Wait(); | |
ConfirmSignUpResponse response = confirmSignUpResponseTask.Result; | |
return response; | |
} | |
catch (Exception error) | |
{ | |
string errorMessage = " - ERROR in Confirm User - " + Environment.NewLine; | |
if (error.Data.Contains("CogntioFail")) | |
{ | |
error.Data["CogntioFail"] += errorMessage; | |
} | |
else | |
{ | |
error.Data.Add("CogntioFail", errorMessage); | |
} | |
throw error; | |
} | |
} | |
public RespondToAuthChallengeResponse Login(string clientId, string email, string clientSecret, string password) | |
{ | |
try | |
{ | |
AuthenticationHelper authenticationHelper = new AuthenticationHelper(userPoolId); | |
// To use an email adresse as a login the user name given in USERNAME and SecretHash need to be that email | |
// For both initiateAuthRequest AND respondToAuthRequest | |
#region First call | |
InitiateAuthRequest initiateAuthRequest = new InitiateAuthRequest(); | |
initiateAuthRequest.AuthFlow = AuthFlowType.USER_SRP_AUTH; | |
initiateAuthRequest.AuthParameters.Add("USERNAME", email.Replace('@', '_')); | |
initiateAuthRequest.AuthParameters.Add("SECRET_HASH", SecretHash(email.Replace('@', '_'), clientId, clientSecret)); | |
initiateAuthRequest.AuthParameters.Add("SRP_A", authenticationHelper.A.ToString(16)); | |
initiateAuthRequest.AuthParameters.Add("DEVICE_KEY", this?.DeviceKey); | |
initiateAuthRequest.ClientId = clientId; | |
// First Call (initiateAuthRequest) | |
Task<InitiateAuthResponse> initiateAuthResponseTask = client.InitiateAuthAsync(initiateAuthRequest); | |
initiateAuthResponseTask.Wait(); | |
InitiateAuthResponse response = initiateAuthResponseTask.Result; | |
var userIdForSrp = response.ChallengeParameters["USER_ID_FOR_SRP"]; | |
var userIdForSrpBytes = Encoding.UTF8.GetBytes(userIdForSrp); | |
var b = new BigInteger(response.ChallengeParameters["SRP_B"], 16); | |
if (Equals(b.Mod(authenticationHelper.N), BigInteger.Zero)) | |
throw new InvalidOperationException("SRP error, B cannot be zero"); | |
var salt = new BigInteger(response.ChallengeParameters["SALT"], 16); | |
var key = authenticationHelper.GetPasswordAuthenticationKey(userIdForSrp, password, b, salt); | |
var user_Pool_Id = userPoolId.Substring(userPoolId.IndexOf('_') + 1); | |
var userPoolIdBytes = Encoding.UTF8.GetBytes(user_Pool_Id); | |
var formatTimestamp = AWSSDKUtils.CorrectedUtcNow.ToString("ddd MMM d HH:mm:ss UTC yyyy", | |
CultureInfo.InvariantCulture); | |
var keySpec = new KeyParameter(key); | |
var mac = MacUtilities.GetMac("HMAC-SHA_256"); | |
mac.Init(keySpec); | |
mac.BlockUpdate(userPoolIdBytes, 0, user_Pool_Id.Length); | |
mac.BlockUpdate(userIdForSrpBytes, 0, userIdForSrpBytes.Length); | |
var secretBlock = Convert.FromBase64String(response.ChallengeParameters["SECRET_BLOCK"]); | |
mac.BlockUpdate(secretBlock, 0, secretBlock.Length); | |
var dateString = AWSSDKUtils.CorrectedUtcNow.ToString("ddd MMM d HH:mm:ss UTC yyyy", | |
CultureInfo.InvariantCulture); | |
var dateBytes = Encoding.UTF8.GetBytes(dateString); | |
mac.BlockUpdate(dateBytes, 0, dateBytes.Length); | |
var hmac = new byte[mac.GetMacSize()]; | |
mac.DoFinal(hmac, 0); | |
var claimSignature = Convert.ToBase64String(hmac); | |
#endregion | |
#region Second call | |
// Second call | |
RespondToAuthChallengeRequest respondToAuthRequest = new RespondToAuthChallengeRequest(); | |
respondToAuthRequest.ChallengeName = response.ChallengeName; | |
respondToAuthRequest.ClientId = clientId; | |
respondToAuthRequest.Session = response.Session; | |
respondToAuthRequest.ChallengeResponses.Add("PASSWORD_CLAIM_SECRET_BLOCK", response.ChallengeParameters["SECRET_BLOCK"]); | |
respondToAuthRequest.ChallengeResponses.Add("PASSWORD_CLAIM_SIGNATURE", claimSignature); | |
respondToAuthRequest.ChallengeResponses.Add("TIMESTAMP", formatTimestamp); | |
respondToAuthRequest.ChallengeResponses.Add("USERNAME", email.Replace('@', '_')); | |
respondToAuthRequest.ChallengeResponses.Add("DEVICE_KEY", this?.DeviceKey); | |
respondToAuthRequest.ChallengeResponses.Add("SECRET_HASH", SecretHash(email.Replace('@', '_'), clientId, clientSecret)); | |
// (respondToAuthRequest) | |
Task<RespondToAuthChallengeResponse> respondToAuthChallengeResponseTask = client.RespondToAuthChallengeAsync(respondToAuthRequest); | |
respondToAuthChallengeResponseTask.Wait(); | |
RespondToAuthChallengeResponse authChallengeResponse = respondToAuthChallengeResponseTask.Result; | |
if (authChallengeResponse.HttpStatusCode == (HttpStatusCode)200) | |
{ | |
if (authChallengeResponse.ChallengeName == "DEVICE_SRP_AUTH") | |
{ | |
RespondToAuthChallengeResponse deviceResponse = GetDeviceResponse(clientId, clientSecret, email.Replace('@', '_')); | |
if (deviceResponse.HttpStatusCode == (HttpStatusCode)200) | |
{ | |
this.DeviceKey = deviceResponse.AuthenticationResult.NewDeviceMetadata?.DeviceKey; | |
this.DeviceGroupKey = deviceResponse.AuthenticationResult.NewDeviceMetadata?.DeviceKey; | |
this.AccessToken = deviceResponse.AuthenticationResult.AccessToken; | |
this.IdToken = deviceResponse.AuthenticationResult.IdToken; | |
this.RefreshToken = deviceResponse.AuthenticationResult.RefreshToken; | |
} | |
return deviceResponse; | |
} | |
else | |
{ | |
this.DeviceKey = authChallengeResponse.AuthenticationResult.NewDeviceMetadata?.DeviceKey; | |
this.DeviceGroupKey = authChallengeResponse.AuthenticationResult.NewDeviceMetadata?.DeviceKey; | |
this.AccessToken = authChallengeResponse.AuthenticationResult.AccessToken; | |
this.IdToken = authChallengeResponse.AuthenticationResult.IdToken; | |
this.RefreshToken = authChallengeResponse.AuthenticationResult.RefreshToken; | |
} | |
} | |
return authChallengeResponse; | |
#endregion | |
} | |
catch (Exception error) | |
{ | |
string errorMessage = " - ERROR in Login User - " + Environment.NewLine; | |
if (error.Data.Contains("CogntioFail")) | |
{ | |
error.Data["CogntioFail"] += errorMessage; | |
} | |
else | |
{ | |
error.Data.Add("CogntioFail", errorMessage); | |
} | |
throw error; | |
} | |
} | |
public ConfirmDeviceResponse ConfirmDevice(string Device_Name, string DeviceGroupKey, string DeviceKey) | |
{ | |
try | |
{ | |
AuthenticationHelper authenticationHelper = new AuthenticationHelper(userPoolId); | |
authenticationHelper.generateHashDevice(DeviceGroupKey, DeviceKey); | |
this.randomPassword = authenticationHelper.randomPassword; | |
ConfirmDeviceRequest confirmDeviceRequest = new ConfirmDeviceRequest(); | |
confirmDeviceRequest.DeviceKey = DeviceKey; | |
confirmDeviceRequest.AccessToken = AccessToken; | |
confirmDeviceRequest.DeviceName = Device_Name; | |
confirmDeviceRequest.DeviceSecretVerifierConfig = new DeviceSecretVerifierConfigType(); | |
confirmDeviceRequest.DeviceSecretVerifierConfig.PasswordVerifier = Convert.ToBase64String(authenticationHelper.getVerifierDevices_ByteArray()); | |
confirmDeviceRequest.DeviceSecretVerifierConfig.Salt = Convert.ToBase64String(authenticationHelper.getSaltToHashDevices_ByteArray()); | |
Task<ConfirmDeviceResponse> confirmDeviceResponseTask = client.ConfirmDeviceAsync(confirmDeviceRequest); | |
confirmDeviceResponseTask.Wait(); | |
ConfirmDeviceResponse response = confirmDeviceResponseTask.Result; | |
if (response.HttpStatusCode == (HttpStatusCode)200) | |
{ | |
this.DeviceGroupKey = DeviceGroupKey; | |
this.DeviceKey = DeviceKey; | |
} | |
return response; | |
} | |
catch (Exception error) | |
{ | |
string errorMessage = " - ERROR in Confirm Device - " + Environment.NewLine; | |
if (error.Data.Contains("CogntioFail")) | |
{ | |
error.Data["CogntioFail"] += errorMessage; | |
} | |
else | |
{ | |
error.Data.Add("CogntioFail", errorMessage); | |
} | |
throw error; | |
} | |
} | |
public InitiateAuthResponse Refresh(string clientId, string email, string clientSecret, string refreshToken) | |
{ | |
try | |
{ | |
// Device has been disabled while it is not working on cognito side. | |
// Once it is working again we will need to provide the device key when refreshing | |
// | |
InitiateAuthRequest initiateAuthRequest = new InitiateAuthRequest(); | |
initiateAuthRequest.AuthFlow = AuthFlowType.REFRESH_TOKEN_AUTH; | |
// User Name is only needed if AuthFlow is REFRESH_TOKEN | |
//initiateAuthRequest.AuthParameters.Add("USERNAME", this.TextBox_Login_userName.Text); | |
initiateAuthRequest.AuthParameters.Add("SECRET_HASH", SecretHash(email.Replace('@', '_'), clientId, clientSecret)); | |
initiateAuthRequest.AuthParameters.Add("REFRESH_TOKEN", refreshToken); | |
initiateAuthRequest.AuthParameters.Add("DEVICE_KEY", this?.DeviceKey); | |
initiateAuthRequest.ClientId = clientId; | |
Task<InitiateAuthResponse> initiateAuthResponseTask = client.InitiateAuthAsync(initiateAuthRequest); | |
initiateAuthResponseTask.Wait(); | |
InitiateAuthResponse response = initiateAuthResponseTask.Result; | |
return response; | |
} | |
catch (Exception error) | |
{ | |
string errorMessage = " - ERROR in Refresh User - " + Environment.NewLine; | |
if (this.DeviceKey == null) | |
{ | |
errorMessage += "Warning if remember device is enabled the device need to be Confirmed using the ConfirmDevice method" + Environment.NewLine; | |
} | |
if (error.Data.Contains("CogntioFail")) | |
{ | |
error.Data["CogntioFail"] += errorMessage; | |
} | |
else | |
{ | |
error.Data.Add("CogntioFail", errorMessage); | |
} | |
throw error; | |
} | |
} | |
public GlobalSignOutResponse Logout() | |
{ | |
try | |
{ | |
// At least make cognito forget the access token. | |
GlobalSignOutRequest globalSignOutRequest = new GlobalSignOutRequest(); | |
globalSignOutRequest.AccessToken = this.AccessToken; | |
Task<GlobalSignOutResponse> globalSignOutResponseTask = this.client.GlobalSignOutAsync(globalSignOutRequest); | |
globalSignOutResponseTask.Wait(); | |
GlobalSignOutResponse response = globalSignOutResponseTask.Result; | |
if (response.HttpStatusCode == (HttpStatusCode)200) | |
{ | |
AccessToken = null; | |
IdToken = null; | |
RefreshToken = null; | |
} | |
return response; | |
} | |
catch (Exception error) | |
{ | |
string errorMessage = " - ERROR in Logout - " + Environment.NewLine; | |
if (error.Data.Contains("CogntioFail")) | |
{ | |
error.Data["CogntioFail"] += errorMessage; | |
} | |
else | |
{ | |
error.Data.Add("CogntioFail", errorMessage); | |
} | |
throw error; | |
} | |
} | |
public GlobalSignOutResponse Logout(string AccessToken) | |
{ | |
try | |
{ | |
// At least make cognito forget the access token. | |
GlobalSignOutRequest globalSignOutRequest = new GlobalSignOutRequest(); | |
globalSignOutRequest.AccessToken = AccessToken; | |
Task<GlobalSignOutResponse> globalSignOutResponseTask = this.client.GlobalSignOutAsync(globalSignOutRequest); | |
globalSignOutResponseTask.Wait(); | |
GlobalSignOutResponse response = globalSignOutResponseTask.Result; | |
if (response.HttpStatusCode == (HttpStatusCode)200) | |
{ | |
this.AccessToken = null; | |
this.IdToken = null; | |
this.RefreshToken = null; | |
} | |
return response; | |
} | |
catch (Exception error) | |
{ | |
string errorMessage = " - ERROR in Logout - " + Environment.NewLine; | |
if (error.Data.Contains("CogntioFail")) | |
{ | |
error.Data["CogntioFail"] += errorMessage; | |
} | |
else | |
{ | |
error.Data.Add("CogntioFail", errorMessage); | |
} | |
throw error; | |
} | |
} | |
private RespondToAuthChallengeResponse GetDeviceResponse(string clientId, string clientSecret, string email) | |
{ | |
try | |
{ | |
AuthenticationHelper authenticationHelper = new AuthenticationHelper(DeviceGroupKey); | |
RespondToAuthChallengeRequest respondToAuthRequest = new RespondToAuthChallengeRequest(); | |
respondToAuthRequest.ChallengeName = ChallengeNameType.DEVICE_SRP_AUTH; | |
respondToAuthRequest.ClientId = clientId; | |
respondToAuthRequest.Session = null; | |
respondToAuthRequest.ChallengeResponses.Add("USERNAME", email.Replace('@', '_')); | |
respondToAuthRequest.ChallengeResponses.Add("DEVICE_KEY", this?.DeviceKey); | |
respondToAuthRequest.ChallengeResponses.Add("SECRET_HASH", SecretHash(email.Replace('@', '_'), clientId, clientSecret)); | |
respondToAuthRequest.ChallengeResponses.Add("SRP_A", authenticationHelper.A.ToString(16)); | |
Task<RespondToAuthChallengeResponse> RespondToAuthChallengeResponseTask = client.RespondToAuthChallengeAsync(respondToAuthRequest); | |
RespondToAuthChallengeResponseTask.Wait(); | |
RespondToAuthChallengeResponse authChallengeResponse = RespondToAuthChallengeResponseTask.Result; | |
var challengeParameters = authChallengeResponse.ChallengeParameters; | |
var serverBValue = new BigInteger(challengeParameters["SRP_B"], 16); | |
if (Equals(serverBValue.Mod(authenticationHelper.N), BigInteger.Zero)) | |
throw new InvalidOperationException("SRP error, B cannot be zero"); | |
var salt = new BigInteger(challengeParameters["SALT"], 16); | |
var key = authenticationHelper.GetPasswordAuthenticationKey(DeviceKey, this.randomPassword, serverBValue, salt); | |
var DeviceGroupKeyBytes = Encoding.UTF8.GetBytes(DeviceGroupKey); | |
var DeviceKeyBytes = Encoding.UTF8.GetBytes(DeviceKey); | |
var formatTimestamp = AWSSDKUtils.CorrectedUtcNow.ToString("ddd MMM d HH:mm:ss UTC yyyy", | |
CultureInfo.InvariantCulture); | |
var keySpec = new KeyParameter(key); | |
var mac = MacUtilities.GetMac("HMAC-SHA_256"); | |
mac.Init(keySpec); | |
mac.BlockUpdate(DeviceGroupKeyBytes, 0, DeviceGroupKey.Length); | |
mac.BlockUpdate(DeviceKeyBytes, 0, DeviceKeyBytes.Length); | |
var secretBlock = Convert.FromBase64String(challengeParameters["SECRET_BLOCK"]); | |
mac.BlockUpdate(secretBlock, 0, secretBlock.Length); | |
var dateString = AWSSDKUtils.CorrectedUtcNow.ToString("ddd MMM d HH:mm:ss UTC yyyy", | |
CultureInfo.InvariantCulture); | |
var dateBytes = Encoding.UTF8.GetBytes(dateString); | |
mac.BlockUpdate(dateBytes, 0, dateBytes.Length); | |
var hmac = new byte[mac.GetMacSize()]; | |
mac.DoFinal(hmac, 0); | |
var claimSignature = Convert.ToBase64String(hmac); | |
RespondToAuthChallengeRequest respondToAuthRequest_srp_b = new RespondToAuthChallengeRequest(); | |
respondToAuthRequest_srp_b.ChallengeName = authChallengeResponse.ChallengeName; | |
respondToAuthRequest_srp_b.ClientId = clientId; | |
respondToAuthRequest_srp_b.Session = authChallengeResponse.Session; | |
respondToAuthRequest_srp_b.ChallengeResponses.Add("PASSWORD_CLAIM_SECRET_BLOCK", challengeParameters["SECRET_BLOCK"]); | |
respondToAuthRequest_srp_b.ChallengeResponses.Add("PASSWORD_CLAIM_SIGNATURE", claimSignature); | |
respondToAuthRequest_srp_b.ChallengeResponses.Add("TIMESTAMP", formatTimestamp); | |
respondToAuthRequest_srp_b.ChallengeResponses.Add("USERNAME", email.Replace('@', '_')); | |
respondToAuthRequest_srp_b.ChallengeResponses.Add("DEVICE_KEY", DeviceKey); | |
respondToAuthRequest_srp_b.ChallengeResponses.Add("SECRET_HASH", SecretHash(email.Replace('@', '_'), clientId, clientSecret)); | |
Task<RespondToAuthChallengeResponse> respondToAuthChallengeResponseTask = client.RespondToAuthChallengeAsync(respondToAuthRequest_srp_b); | |
respondToAuthChallengeResponseTask.Wait(); | |
RespondToAuthChallengeResponse response = respondToAuthChallengeResponseTask.Result; | |
return response; | |
} | |
catch (Exception error) | |
{ | |
string errorMessage = " - ERROR in Device Response Challenge - " + Environment.NewLine; | |
if (error.Data.Contains("CogntioFail")) | |
{ | |
error.Data["CogntioFail"] += errorMessage; | |
} | |
else | |
{ | |
error.Data.Add("CogntioFail", errorMessage); | |
} | |
throw error; | |
} | |
} | |
public ForgotPasswordResponse ForgotPassword(string clientId, string clientSecret, string email) | |
{ | |
try | |
{ | |
ForgotPasswordRequest forgotPasswordRequest = new ForgotPasswordRequest(); | |
forgotPasswordRequest.ClientId = clientId; | |
forgotPasswordRequest.Username = email.Replace('@', '_'); | |
forgotPasswordRequest.SecretHash = SecretHash(email.Replace('@', '_'), clientId, clientSecret); | |
Task<ForgotPasswordResponse> forgotPasswordResponseTask = client.ForgotPasswordAsync(forgotPasswordRequest); | |
forgotPasswordResponseTask.Wait(); | |
ForgotPasswordResponse response = forgotPasswordResponseTask.Result; | |
return response; | |
} | |
catch (Exception error) | |
{ | |
string errorMessage = " - ERROR in Forgot Password - " + Environment.NewLine; | |
if (error.Data.Contains("CogntioFail")) | |
{ | |
error.Data["CogntioFail"] += errorMessage; | |
} | |
else | |
{ | |
error.Data.Add("CogntioFail", errorMessage); | |
} | |
throw error; | |
} | |
} | |
public ConfirmForgotPasswordResponse ConfirmPassword(string clientId, string clientSecret, string email, string confirmationCode, string newPassword) | |
{ | |
try | |
{ | |
ConfirmForgotPasswordRequest confirmForgotPasswordRequest = new ConfirmForgotPasswordRequest(); | |
confirmForgotPasswordRequest.ClientId = clientId; | |
confirmForgotPasswordRequest.Username = email.Replace('@', '_'); | |
confirmForgotPasswordRequest.SecretHash = SecretHash(email.Replace('@', '_'), clientId, clientSecret); | |
confirmForgotPasswordRequest.ConfirmationCode = confirmationCode; | |
confirmForgotPasswordRequest.Password = newPassword; | |
Task<ConfirmForgotPasswordResponse> ConfirmForgotPasswordResponseTask = client.ConfirmForgotPasswordAsync(confirmForgotPasswordRequest); | |
ConfirmForgotPasswordResponseTask.Wait(); | |
ConfirmForgotPasswordResponse response = ConfirmForgotPasswordResponseTask.Result; | |
return response; | |
} | |
catch (Exception error) | |
{ | |
string errorMessage = " - ERROR in Confirm Forgot Password - " + Environment.NewLine; | |
if (error.Data.Contains("CogntioFail")) | |
{ | |
error.Data["CogntioFail"] += errorMessage; | |
} | |
else | |
{ | |
error.Data.Add("CogntioFail", errorMessage); | |
} | |
throw error; | |
} | |
} | |
public static string SecretHash(string username, string clientId, string clientSecret) | |
{ | |
return CryptoUtilFactory.CryptoInstance.HMACSign($"{username}{clientId}", clientSecret, | |
SigningAlgorithm.HmacSHA256); | |
} | |
private static AttributeType AttributeType(String Name, String Value) | |
{ | |
AttributeType att = new AttributeType(); | |
att.Name = Name; | |
att.Value = Value; | |
return att; | |
} | |
public static double DateTimeToUnixTimestamp(DateTime dateTime) | |
{ | |
return (TimeZoneInfo.ConvertTimeToUtc(dateTime) - new DateTime(1970, 1, 1)).TotalSeconds; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//https://gist.github.com/charlesportwoodii/7c5cf32e92ee88fec5e8f3270d0b44fc | |
using System; | |
using System.IO; | |
using System.Security.Cryptography; | |
namespace cognito | |
{ | |
/// <summary> | |
/// HMAC-based Extract-and-Expand Key Derivation Function (HKDF) | |
/// https://tools.ietf.org/html/rfc5869 | |
/// </summary> | |
public class HKDF | |
{ | |
private readonly int hashLength; | |
private readonly HMAC hmac; | |
private readonly byte[] prk; | |
/// <summary> | |
/// Initializes a new instance of the <see cref="HKDF" /> class. | |
/// </summary> | |
/// <param name="hmac">The HMAC hash function to use.</param> | |
/// <param name="ikm">input keying material.</param> | |
/// <param name="salt"> | |
/// optional salt value (a non-secret random value); if not provided, it is set to a string of | |
/// HMAC.HashSize/8 zeros. | |
/// </param> | |
public HKDF(HMAC hmac, byte[] ikm, byte[] salt = null) | |
{ | |
this.hmac = hmac; | |
hashLength = hmac.HashSize / 8; | |
// now we compute the PRK | |
hmac.Key = salt ?? new byte[hashLength]; | |
prk = hmac.ComputeHash(ikm); | |
} | |
/// <summary> | |
/// Expands the specified info. | |
/// </summary> | |
/// <param name="info">optional context and application specific information (can be a zero-length string)</param> | |
/// <param name="l">length of output keying material in octets (<= 255*HashLen)</param> | |
/// <returns>OKM (output keying material) of L octets</returns> | |
public byte[] Expand(byte[] info, int l) | |
{ | |
if (info == null) info = new byte[0]; | |
hmac.Key = prk; | |
var n = (int) Math.Ceiling(l * 1f / hashLength); | |
var t = new byte[n * hashLength]; | |
using (var ms = new MemoryStream()) | |
{ | |
var prev = new byte[0]; | |
for (var i = 1; i <= n; i++) | |
{ | |
ms.Write(prev, 0, prev.Length); | |
if (info.Length > 0) ms.Write(info, 0, info.Length); | |
ms.WriteByte((byte) (0x01 * i)); | |
prev = hmac.ComputeHash(ms.ToArray()); | |
Array.Copy(prev, 0, t, (i - 1) * hashLength, hashLength); | |
ms.SetLength(0); //reset | |
} | |
} | |
var okm = new byte[l]; | |
Array.Copy(t, okm, okm.Length); | |
return okm; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Amazon; | |
using Amazon.CognitoIdentityProvider; | |
using Amazon.CognitoIdentityProvider.Model; | |
using Amazon.Runtime; | |
using Amazon.Util; | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Net; | |
using System.Text; | |
using System.Threading.Tasks; | |
namespace cognito | |
{ | |
class Program | |
{ | |
static void Main(string[] args) | |
{ | |
string password = "*******"; | |
string email = "Test@Demo.com"; | |
string clientId = "******************0h8k"; | |
string clientSecret = "******************************mggcg"; | |
string userPoolId = "us-east-1_*******"; | |
Cognito cognito = new Cognito(userPoolId, RegionEndpoint.USEast1); | |
cognito.Register(clientId, clientSecret, password, email); | |
string validationCode = Console.ReadLine(); | |
cognito.Confirm(validationCode, clientId, email, clientSecret); | |
try | |
{ | |
var response1 = cognito.Login(clientId, email, clientSecret, password); | |
if (response1.AuthenticationResult.NewDeviceMetadata?.DeviceKey != null && cognito.DeviceKey != null) | |
{ | |
ConfirmDeviceResponse confirmDeviceResponse = cognito.ConfirmDevice(Environment.MachineName, response1.AuthenticationResult.NewDeviceMetadata.DeviceGroupKey, response1.AuthenticationResult.NewDeviceMetadata.DeviceKey); | |
} | |
cognito.Login(clientId, email, clientSecret, password); | |
} | |
catch (Exception error) | |
{ | |
Console.Write(error.ToString()); | |
} | |
Console.Read(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment