реализация аутентификации пользователя SRP с помощью python boto3 для AWS Cognito

Amazon предоставляет iOS, Android и Javascript Cognito SDKs, которые предлагают высокоуровневую операцию аутентификации пользователя.

например, см. Вариант использования 4 здесь:

https://github.com/aws/amazon-cognito-identity-js

однако, если вы используете python / boto3, все, что вы получаете, это пара примитивов:cognito.initiate_auth и cognito.respond_to_auth_challenge.

Я пытаюсь использовать эти примитивы вместе с pysrp lib аутентифицируется с USER_SRP_AUTH поток, но то, что у меня не работает.

он всегда терпит неудачу с " произошла ошибка (NotAuthorizedException) при вызове операции RespondToAuthChallenge: неправильное имя пользователя или пароль."(Пара имя пользователя / пароль работает с JS SDK.)

мое подозрение, что я неправильно строю ответ на вызов (Шаг 3) и / или передаю шестнадцатеричные строки Congito, когда он хочет base64 или наоборот.

кто-нибудь получил эту работу? Кто-нибудь видит, что я делаю? не так?

Я пытаюсь скопировать поведение authenticateUser вызов найден в JavaScript SDK:

https://github.com/aws/amazon-cognito-identity-js/blob/master/src/CognitoUser.js#L138

но я делаю что-то не так и не могу понять, что именно.

#!/usr/bin/env python
import base64
import binascii
import boto3
import datetime as dt
import hashlib
import hmac

# http://pythonhosted.org/srp/
# https://github.com/cocagne/pysrp
import srp

bytes_to_hex = lambda x: "".join("{:02x}".format(ord(c)) for c in x)

cognito = boto3.client('cognito-idp', region_name="us-east-1")

username = "foobar@foobar.com"
password = "123456"

user_pool_id = u"us-east-1_XXXXXXXXX"
client_id = u"XXXXXXXXXXXXXXXXXXXXXXXXXX"

# Step 1:
# Use SRP lib to construct a SRP_A value.

srp_user = srp.User(username, password)
_, srp_a_bytes = srp_user.start_authentication()

srp_a_hex = bytes_to_hex(srp_a_bytes)

# Step 2:
# Submit USERNAME & SRP_A to Cognito, get challenge.

response = cognito.initiate_auth(
    AuthFlow='USER_SRP_AUTH',
    AuthParameters={ 'USERNAME': username, 'SRP_A': srp_a_hex },
    ClientId=client_id,
    ClientMetadata={ 'UserPoolId': user_pool_id })

# Step 3:
# Use challenge parameters from Cognito to construct 
# challenge response.

salt_hex         = response['ChallengeParameters']['SALT']
srp_b_hex        = response['ChallengeParameters']['SRP_B']
secret_block_b64 = response['ChallengeParameters']['SECRET_BLOCK']

secret_block_bytes = base64.standard_b64decode(secret_block_b64)
secret_block_hex = bytes_to_hex(secret_block_bytes)

salt_bytes = binascii.unhexlify(salt_hex)
srp_b_bytes = binascii.unhexlify(srp_b_hex)

process_challenge_bytes = srp_user.process_challenge(salt_bytes,                          
                                                     srp_b_bytes)

timestamp = unicode(dt.datetime.utcnow().strftime("%a %b %d %H:%m:%S +0000 %Y"))

hmac_obj = hmac.new(process_challenge_bytes, digestmod=hashlib.sha256)
hmac_obj.update(user_pool_id.split('_')[1].encode('utf-8'))
hmac_obj.update(username.encode('utf-8'))
hmac_obj.update(secret_block_bytes)
hmac_obj.update(timestamp.encode('utf-8'))

challenge_responses = {
    "TIMESTAMP": timestamp.encode('utf-8'),
    "USERNAME": username.encode('utf-8'),
    "PASSWORD_CLAIM_SECRET_BLOCK": secret_block_hex,
    "PASSWORD_CLAIM_SIGNATURE": hmac_obj.hexdigest()
}

# Step 4:
# Submit challenge response to Cognito.

response = cognito.respond_to_auth_challenge(
    ClientId=client_id,
    ChallengeName='PASSWORD_VERIFIER',
    ChallengeResponses=challenge_responses)

2 ответов


в вашей реализации много ошибок. Например:

  1. pysrp по умолчанию используется алгоритм SHA1. Он должен быть установлен в SHA256.
  2. _ng_const длина должна быть 3072 бит, и она должна быть скопирована из amazon-cognito-identity-js
  3. нет hkdf на pysrp.
  4. ответ должен содержать secret_block_b64, а не secret_block_hex.
  5. неправильный формат метки времени. %H:%m:%S означает "час:месяц:второй" и +0000 следует заменить на UTC.

кто-нибудь получил эту работу?

да. Он реализован в warrant.aws_srp модуль. https://github.com/capless/warrant/blob/develop/warrant/aws_srp.py

from warrant.aws_srp import AWSSRP


USERNAME='xxx'
PASSWORD='yyy'
POOL_ID='us-east-1_zzzzz'
CLIENT_ID = '12xxxxxxxxxxxxxxxxxxxxxxx'

aws = AWSSRP(username=USERNAME, password=PASSWORD, pool_id=POOL_ID,
             client_id=CLIENT_ID)
tokens = aws.authenticate_user()
id_token = tokens['AuthenticationResult']['IdToken']
refresh_token = tokens['AuthenticationResult']['RefreshToken']
access_token = tokens['AuthenticationResult']['AccessToken']
token_type = tokens['AuthenticationResult']['TokenType']

отметим, что aws_srp модуль не был объединен в master филиалом.

authenticate_user метод поддерживает только PASSWORD_VERIFIER вызов. Если вы хотите ответить на другие вызовы, просто посмотрите на authenticate_user и boto3 документация.


к сожалению, это сложная проблема, так как вы не получаете никаких намеков от службы в отношении вычислений (в основном это говорит не авторизовано, как вы упомянули).

мы работаем над улучшением опыта разработчика, когда пользователи пытаются реализовать SRP самостоятельно на языках, где у нас нет SDK. Кроме того, мы пытаемся добавить больше SDK.

Как бы сложно это ни звучало, я бы предложил взять Javascript или Android SDK, исправить входы (SRP_A, SRP_B, TIMESTAMP)и добавить консоль.инструкции журнала в различных точках реализации, чтобы убедиться, что ваши вычисления похожи. Затем вы запустите эти вычисления в своей реализации и убедитесь, что получаете то же самое. Как вы предположили, подпись утверждения пароля должна быть передана в службу как строка, закодированная в base64, чтобы это могло быть одной из проблем.

некоторые из проблем с которыми я столкнулся при реализации это типа BigInteger библиотечные различия (как они делают байтовое заполнение и преобразуют отрицательные числа в байтовые массивы и наоборот).