Использование Google Cloud Key Management Service для подписи веб-токенов JSON - PullRequest
0 голосов
/ 18 января 2019

Редактировать: я нашел ответ. Прокрутите до конца этого вопроса.

Я работаю на сервере аутентификации NodeJS и хочу подписать веб-токены JSON (JWT) с помощью подписей Google.

Я использую Службу управления ключами облака Google (KMS) и создал кольцо ключей и асимметричный ключ подписи.

Это мой код для получения подписи:

signatureObject = await client.asymmetricSign({ name, digest })

signature = signatureObject["0"].signature

Мой объект подписи Google выглядит следующим образом:

enter image description here

Мой вопрос: Как подписать JWT, используя подпись Google?

Или, другими словами, как мне объединить подпись Google с (header.payload) JWT?

JWT должен выглядеть примерно так:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ. (GoogleSignature)

Код, который я использую:

Подписание:

async function sign(message, name) {
  hashedMessage = crypto.createHash('sha256').update(message).digest('base64');
  digest = { 'sha256': hashedMessage }

  signatureObject = await client.asymmetricSign({ name, digest }).catch((err) => console.log(err))
  signature = signatureObject["0"].signature
  signJWT(signature)
}

Создание JWT:

function signJWT(signature) {
  header = {
    alg: "RS256",
    typ: "JWT"
  }

  payload = {
    sub: "1234567890",
    name: "John Doe",
    iat: 1516239022
  }

  JWT = base64url(JSON.stringify(header)) + "." +
        base64url(JSON.stringify(payload)) + "." + 
        ???signature??? ; // what goes here?
}

Проверка:

async function validateSignature(message, signature) {
  // Get public key
  publicKeyObject = await client.getPublicKey({ name }).catch((err) => console.log(err))
  publicKey = publicKeyObject["0"].pem

  //Verify signature
  var verifier = crypto.createVerify('sha256');
  verifier.update(message)
  var ver = verifier.verify(publicKey, signature, 'base64')

  // Returns either true for a valid signature, or false for not valid.
  return ver
}

Ответ:

Я могу использовать метод toString () следующим образом:

signatureString = signature.toString('base64');

И тогда я могу получить исходный поток подписи октетов, используя

var buffer = Buffer.from(theString, 'base64');

1 Ответ

0 голосов
/ 18 января 2019

Вы не опубликовали свой код в своем вопросе, поэтому я не знаю, как вы создаете JWT для подписи.

[ПРАВИТЬ 1/18/2019 после добавления кода к вопросу]

Ваш код делает подпись в обратном направлении . Вы создаете подпись и пытаетесь прикрепить ее к JWT Headers + Payload. Вместо этого вы хотите взять JWT Headers + Payload и подписать эти данные, а затем прикрепить подпись к JWT, чтобы создать Signed-JWT.

Код Psuedo, используя ваш исходный код:

body_b64 = base64url(JSON.stringify(header)) + "." + base64url(JSON.stringify(payload))

signature = sign(body_b64, name);

jwt = body_b64 + '.' + base64url(signature)

Примечание. Я не уверен, какой формат данных возвращает подпись signatureObject["0"].signature. Возможно, вам придется преобразовать это перед преобразованием в base64.

[КОНЕЦ РЕДАКТИРОВАНИЯ]

Пример данных:

Заголовок JWT:

{
    alg: RS256
    kid: 0123456789abcdef62afcbbf01234567890abcdef
    typ: JWT
}

Полезная нагрузка JWT:

{
  "azp": "123456789012-gooddogsgotoheaven.apps.googleusercontent.com",
  "aud": "123456789012-gooddogsgotoheaven.apps.googleusercontent.com",
  "sub": "123456789012345678901",
  "scope": "https://www.googleapis.com/auth/cloud-platform",
  "exp": "1547806224",
  "expires_in": "3596",
  "email": "someone@example.com.com",
  "email_verified": "true",
  "access_type": "offline"
}

Алгоритм:

SHA256withRSA

Чтобы создать подписанный JWT (JWS):

Шаг 1: Возьмите JWT Header и конвертируйте в Base-64. Давайте назовем это hdr_b64.

Шаг 2: Возьмите полезную нагрузку JWT и конвертируйте в Base-64. Давайте назовем это payload_b64.

Шаг 3: Объедините закодированный заголовок и полезную нагрузку с точкой . между: hdr_b64 + '.' + payload_b64`. Давайте назовем это body_b64.

Шаг 4: Обычно JWS подписывается с SHA256 с помощью RSA, часто называемой «RS256» с использованием закрытого ключа:

signature = sign(body_b64, RS256, private_key)

Теперь конвертируйте подпись в Base-64. Позвольте назвать это signature_b64.

Для создания окончательного JWS:

jws = body_b64 + '.' + signature_b64.

Рекомендации:

Хотите использовать KMS для создания подписанных JWT? Я бы не рекомендовал это. Стоимость ключей доступа хранится в KMS. Подписанные JWT подписываются закрытым ключом и проверяются открытым ключом. Как вы собираетесь публиковать открытый ключ? Какой уровень производительности вам нужен для доступа к закрытым и открытым ключам (как часто вы будете подписывать и проверять)?

Когда вы создаете учетную запись службы в Google Cloud Platform, для вас создается пара ключей. Эта пара ключей имеет идентификатор с открытым ключом, доступным в Интернете, а закрытый ключ присутствует в файле учетных данных Json учетной записи службы. Я бы использовал служебную учетную запись для создания подписанных JWT вместо пары ключей в KMS.

Пример кода на Python для создания и подписи:

def create_signed_jwt(pkey, pkey_id, email, scope):
    '''
    Create a Signed JWT from a service account Json credentials file
    This Signed JWT will later be exchanged for an Access Token
   '''

    import jwt

    # Google Endpoint for creating OAuth 2.0 Access Tokens from Signed-JWT
    auth_url = "https://www.googleapis.com/oauth2/v4/token"

    issued = int(time.time())
    expires = issued + expires_in   # expires_in is in seconds

    # Note: this token expires and cannot be refreshed. The token must be recreated

    # JWT Headers
    headers = {
        "kid": pkey_id, # This is the service account private key ID
        "alg": "RS256",
        "typ": "JWT"    # Google uses SHA256withRSA
    }

    # JWT Payload
    payload = {
            "iss": email,           # Issuer claim
            "sub": email,           # Issuer claim
            "aud": auth_url,        # Audience claim
            "iat": issued,          # Issued At claim
            "exp": expires,         # Expire time
            "scope": scope          # Permissions
    }

    # Encode the headers and payload and sign creating a Signed JWT (JWS)
    sig = jwt.encode(payload, pkey, algorithm="RS256", headers=headers)

    return sig
...