Причина, по которой ваше решение не сработало, заключается в том, что для работы верификатора устройства и соли они должны быть рассчитаны на основе device_group_key
и device_key
.Кроме того, расчет ответа на запрос для аутентификации устройства отличается от стандартного потока SRP пароля.Вот как это сработало для меня:
Сначала подтвердите и запомните устройство ( Amazon Cognito Identity SDK для источника JavaScript ):
from warrant import aws_srp
from warrant.aws_srp import AWSSRP
import boto3
import base64
import os
def generate_hash_device(device_group_key, device_key):
# source: https://github.com/amazon-archives/amazon-cognito-identity-js/blob/6b87f1a30a998072b4d98facb49dcaf8780d15b0/src/AuthenticationHelper.js#L137
# random device password, which will be used for DEVICE_SRP_AUTH flow
device_password = base64.standard_b64encode(os.urandom(40)).decode('utf-8')
combined_string = '%s%s:%s' % (device_group_key, device_key, device_password)
combined_string_hash = aws_srp.hash_sha256(combined_string.encode('utf-8'))
salt = aws_srp.pad_hex(aws_srp.get_random(16))
x_value = aws_srp.hex_to_long(aws_srp.hex_hash(salt + combined_string_hash))
g = aws_srp.hex_to_long(aws_srp.g_hex)
big_n = aws_srp.hex_to_long(aws_srp.n_hex)
verifier_device_not_padded = pow(g, x_value, big_n)
verifier = aws_srp.pad_hex(verifier_device_not_padded)
device_secret_verifier_config = {
"PasswordVerifier": base64.standard_b64encode(bytearray.fromhex(verifier)).decode('utf-8'),
"Salt": base64.standard_b64encode(bytearray.fromhex(salt)).decode('utf-8')
}
return device_password, device_secret_verifier_config
client = boto3.client('cognito-idp')
username='xxx'
password='xxx'
client_id='xxx'
client_secret='xxx'
pool_id='xxx'
# 1. Login with the password via standard SRP flow
aws = AWSSRP(username=username, password=password, pool_id=pool_id,
client_id=client_id, client_secret=client_secret, client=client)
auth_init = client.initiate_auth(
AuthFlow='USER_SRP_AUTH',
AuthParameters=aws.get_auth_params(),
ClientId=client_id,
)
cr = aws.process_challenge(auth_init['ChallengeParameters'])
response = client.respond_to_auth_challenge(
ClientId=client_id,
ChallengeName=auth_init['ChallengeName'],
ChallengeResponses=cr
)
# 2. Get device_key and device_group_key returned after successful login
device_key = response['AuthenticationResult']['NewDeviceMetadata']['DeviceKey']
device_group_key = response['AuthenticationResult']['NewDeviceMetadata']['DeviceGroupKey']
# 3. Generate random device password, device salt and verifier
device_password, device_secret_verifier_config = generate_hash_device(device_group_key, device_key)
response_dev = client.confirm_device(
AccessToken=response['AuthenticationResult']['AccessToken'],
DeviceKey=device_key,
DeviceSecretVerifierConfig=device_secret_verifier_config,
DeviceName='some_device_name'
)
# 4. Remember the device
response_dev_upd = client.update_device_status(
AccessToken=response['AuthenticationResult']['AccessToken'],
DeviceKey=device_key,
DeviceRememberedStatus='remembered'
)
Затем наВ чистом сеансе вы можете войти, используя учетные данные устройства ( Amazon Cognito Identity SDK для источника JavaScript ):
import re
import datetime
import base64
import hmac
import hashlib
import boto3
from warrant import aws_srp
from warrant.aws_srp import AWSSRP
class AWSSRPDEV(AWSSRP):
# source: https://github.com/amazon-archives/amazon-cognito-identity-js/blob/6b87f1a30a998072b4d98facb49dcaf8780d15b0/src/CognitoUser.js#L498
def __init__(self, username, device_group_key, device_key, device_password,
client_id, client, region=None, client_secret=None):
self.username = username
self.device_group_key = device_group_key
self.device_key = device_key
self.device_password = device_password
self.client_id = client_id
self.client_secret = client_secret
self.client = client or boto3.client('cognito-idp', region_name=region)
self.big_n = aws_srp.hex_to_long(aws_srp.n_hex)
self.g = aws_srp.hex_to_long(aws_srp.g_hex)
self.k = aws_srp.hex_to_long(aws_srp.hex_hash('00' + aws_srp.n_hex + '0' + aws_srp.g_hex))
self.small_a_value = self.generate_random_small_a()
self.large_a_value = self.calculate_a()
def get_auth_params(self):
auth_params = super(AWSSRPDEV, self).get_auth_params()
auth_params['DEVICE_KEY'] = self.device_key
return auth_params
def get_device_authentication_key(self, device_group_key, device_key, device_password, server_b_value, salt):
u_value = aws_srp.calculate_u(self.large_a_value, server_b_value)
if u_value == 0:
raise ValueError('U cannot be zero.')
username_password = '%s%s:%s' % (device_group_key, device_key, device_password)
username_password_hash = aws_srp.hash_sha256(username_password.encode('utf-8'))
x_value = aws_srp.hex_to_long(aws_srp.hex_hash(aws_srp.pad_hex(salt) + username_password_hash))
g_mod_pow_xn = pow(self.g, x_value, self.big_n)
int_value2 = server_b_value - self.k * g_mod_pow_xn
s_value = pow(int_value2, self.small_a_value + u_value * x_value, self.big_n)
hkdf = aws_srp.compute_hkdf(bytearray.fromhex(aws_srp.pad_hex(s_value)),
bytearray.fromhex(aws_srp.pad_hex(aws_srp.long_to_hex(u_value))))
return hkdf
def process_device_challenge(self, challenge_parameters):
username = challenge_parameters['USERNAME']
salt_hex = challenge_parameters['SALT']
srp_b_hex = challenge_parameters['SRP_B']
secret_block_b64 = challenge_parameters['SECRET_BLOCK']
# re strips leading zero from a day number (required by AWS Cognito)
timestamp = re.sub(r" 0(\d) ", r" \1 ",
datetime.datetime.utcnow().strftime("%a %b %d %H:%M:%S UTC %Y"))
hkdf = self.get_device_authentication_key(self.device_group_key,
self.device_key,
self.device_password,
aws_srp.hex_to_long(srp_b_hex),
salt_hex)
secret_block_bytes = base64.standard_b64decode(secret_block_b64)
msg = bytearray(self.device_group_key, 'utf-8') + bytearray(self.device_key, 'utf-8') + \
bytearray(secret_block_bytes) + bytearray(timestamp, 'utf-8')
hmac_obj = hmac.new(hkdf, msg, digestmod=hashlib.sha256)
signature_string = base64.standard_b64encode(hmac_obj.digest())
response = {'TIMESTAMP': timestamp,
'USERNAME': username,
'PASSWORD_CLAIM_SECRET_BLOCK': secret_block_b64,
'PASSWORD_CLAIM_SIGNATURE': signature_string.decode('utf-8'),
'DEVICE_KEY': self.device_key}
if self.client_secret is not None:
response.update({
"SECRET_HASH":
self.get_secret_hash(username, self.client_id, self.client_secret)})
return response
client = boto3.client('cognito-idp')
username='xxx'
client_id='xxx'
client_secret='xxx'
device_key = 'xxx'
device_group_key = 'xxx'
device_password = 'xxx'
aws_dev = AWSSRPDEV(username=username,
device_group_key=device_group_key, device_key=device_key, device_password=device_password,
client_id=client_id, client_secret=client_secret, client=client)
# Note that device auth flow doesn't start with client.initiate_auth(),
# but rather with client.respond_to_auth_challenge() straight away
response_auth = client.respond_to_auth_challenge(
ClientId=client_id,
ChallengeName='DEVICE_SRP_AUTH',
ChallengeResponses=aws_dev.get_auth_params()
)
cr = aws_dev.process_device_challenge(response_auth['ChallengeParameters'])
response_verifier = client.respond_to_auth_challenge(
ClientId=client_id,
ChallengeName='DEVICE_PASSWORD_VERIFIER',
ChallengeResponses=cr
)
Обратите внимание, что в моем случае клиент Cognito имеет client_secret
, однако кодвыше может работать, если это не так.