Skip to content
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

New E2EE scheme for crypto4 #685

Open
romanstrobl opened this issue Jan 9, 2025 · 0 comments
Open

New E2EE scheme for crypto4 #685

romanstrobl opened this issue Jan 9, 2025 · 0 comments
Assignees

Comments

@romanstrobl
Copy link
Member

romanstrobl commented Jan 9, 2025

Description

Implement a new e2ee scheme for crypto4.

Cover the new functionality by tests.

Acceptance criteria

It is possible to use the new e2ee encryption scheme in PowerAuth server for encryption end-to-end communication. Functionality is covered by unit tests and test vectors are published.

Technical specification

Data Entities

GetTemporaryKeyRequest

Property Type Description
applicationKey String APP_KEY constant
activationId String? Optional activation ID
challenge String, Base64 Request challenge
sharedSecretRequest SharedSecretRequest Data required for a shared secret derivation

GetTemporaryKeyResponse

Property Type Description
applicationKey String APP_KEY constant
activationId String? Optional activation ID
challenge String, Base64 Request challenge
keyId String, UUID Identifier of created key
sharedSecretResponse SharedSecretResponse Data required for a shared secret derivation
expiration long Timestamp with milliseconds precision, when the key expires on the server
serverTime long Timestamp with milliseconds precision with the current time on the server

EncryptedData

Property Type Description
temporaryKeyId String, UUID Identifier of the temporary key
nonce String, Base64 24-bytes long nonce
timestamp long Timestamp with milliseconds precision
encryptedData String, Base64 Encrypted request data

Getting temporary shared key procedure

The purpose of this procedure to establish a temporary shared secret between the client and the server. The shared secret is then used for the subsequent end-to-end encryption.

Procedure:

  1. Client prepares a SharedSecretRequest with selected encryption algorithm (V4_ALG_1 or V4_ALG_2)

  2. Client creates a GetTemporaryKeyRequest request payload, signed with JWS algorithm HS256 and formatted as JWT. The presence of "activationId" determine the scope of the future issued key. Use the following keys for the signing:

    • Application scope: signing_key = KEY_MAC_GET_APP_TEMP_KEY
    • Activation scope: signing_key = KEY_MAC_GET_ACT_TEMP_KEY
  3. Server validates signature in JWT

  4. Server prepares its SharedSecretResponse and derive the temporary shared secret (KEY_TEMPORARY_SHARED_SECRET)

    • Server store the shared secret to the database with a new unique id (temporaryKeyId) for a limited time.
  5. Server prepares GetTemporaryKeyResponse response with using the following data:

    • applicationKey, activationId and challenge are copied from the request
    • sharedSecret contains data from SharedSecretResponse structure
    • keyId is assigned key identifier
  6. Server uses the general JSW JSON Serialization syntax to encode signed response. Use the following keys, depending on selected encryption algorithm and the scope:

    • Application scope, V4_ALG_1
      • Use ES384 with KEY_MASTER_ECDSA_P384_PRIVATE
    • Application scope, V4_ALG_2
      • Use ES384 with KEY_MASTER_ECDSA_P384_PRIVATE
      • USE ML-DSA-65 with KEY_MASTER_MLDSA65_PRIVATE
    • Activation scope, V4_ALG_1
      • Use ES384 with KEY_SERVER_ECDSA_P384_PRIVATE
    • Activation scope, V4_ALG_2
      • Use ES384 with KEY_SERVER_ECDSA_P384_PRIVATE
      • Use ML-DSA-65 with KEY_SERVER_MLDSA65_PRIVATE
    • Note that "ML-DSA-65" is not standardized yet. Check the current draft for updates. I recommend to use alg="ML-DSA-65" and the signature as is.
  7. Client validates the signature(s)

  8. Client deduces its own KEY_TEMPORARY_SHARED_SECRET with using data from the response and store it in the temporary storage until the expiration.

    • It's recommended to proactively remove the secret from the memory. We should not wait for the next key use as we do in the 1.9.x - 1.10.x SDK.

Encryption

Assume we have the following constants:

  • VERSION a protocol version, encoded as bytes (e.g. ByteUtils.encode("4.0"))

Assume we have the following input parameters:

  • Established KEY_TEMPORARY_SHARED_SECRET (see Getting temporary shared key procedure)
  • Data PLAINTEXT to be encrypted
  • SHARED_INFO_1 and SHARED_INFO_2 constants (byte[]) as encryption parameters.
  • ASSOCIATED_DATA data transmitted as plaintext and included in MAC calculation.
  • NONCE - optional 24-bytes long nonce. If not provided, then this is a request encryption. If provided, then this is a response encryption.

The End-To-End encryption works in a following way:

  1. Get current timestamp.

    long TIMESTAMP = Time.getTimestamp();
    byte[] TIMESTAMP_BYTES = ByteUtils.encode(TIMESTAMP); // BigEndian encoding
  2. Generate random nonces if this is a request encryption or extract the response nonce

    byte[] IV;
    if (NONCE == null) {
       // Request encryption, generate a new nonces
       byte[] REQUEST_NONCE = Generator.getUniqueNonce(12);
       byte[] RESPONSE_NONCE = Generator.getUniqueNonce(12);
       // This is oversimplified. See "Client Server communication" section below.
       NONCE = ByteUtils.concat(REQUEST_NONCE, RESPONSE_NONCE);
       IV = REQUEST_NONCE;
    } else {
       // This is a response encryption, because NONCE is provided.
       IV = ByteUtils.subarray(NONCE, 12, 12);
    }
  3. Prepare key context and associated data for AEAD

    byte[] KC = ByteUtils.concat(VERSION, SHARED_INFO_1, NONCE);
    byte[] AD = ByteUtils.concat(ASSOCIATED_DATA, ByteUtils.concatWithSizes(TIMESTAMP_BYTES, NONCE, SHARED_INFO_2));
  4. Encrypt data:

    byte[] CIPHERTEXT = AEAD.seal(KEY_TEMPORARY_SHARED_SECRET, KC, IV, AD, PLAINTEXT);
  5. Prepare EncryptedData payload. Do not include "NONCE" in the object if this is response encryption.

Decryption

Assume we have the following constants:

  • VERSION a protocol version, encoded as bytes (e.g. ByteUtils.encode("4.0"))

Assume we have the following input parameters:

  • Established KEY_TEMPORARY_SHARED_SECRET (see Getting temporary shared key procedure)
  • CIPHERTEXT encrypted data received in EncryptedData
  • NONCE - 24-bytes long nonce received in EncryptedData or remembered by the client
  • IS_REQUEST - boolean indicating that this is a request decryption
  • TIMESTAMP a timestamp received in EncryptedData
  • ASSOCIATED_DATA data transmitted as plaintext and included in MAC calculation.
  • SHARED_INFO_1 and SHARED_INFO_2 constants (byte[]) as decryption parameters.

End-To-End decryption works in the following way:

  1. Validate NONCE:

    byte[] CIPHERTEXT_NONCE = AEAD.extractNonce(CIPHERTEXT);
    byte[] IV = ByteUtils.subarray(NONCE, IS_REQUEST ? 0 : 12, 12);
    if (!CIPHERTEXT_NONCE.equals(IV)) {
       throw new Exception();  // Nonce in ciphertext is different than expected
    }
  2. Prepare key context and associated data for AEAD

    byte[] TIMESTAMP_BYTES = ByteUtils.encode(TIMESTAMP);
    byte[] KC = ByteUtils.concat(VERSION, SHARED_INFO_1, NONCE);
    byte[] AD = ByteUtils.concat(ASSOCIATED_DATA, ByteUtils.concatWithSizes(TIMESTAMP_BYTES, NONCE, SHARED_INFO_2));
  3. Decrypt data

    byte[] PLAINTEXT = AEAD.open(KEY_TEMPORARY_SHARED_SECRET, KC, AD, CIPHERTEXT);

Client-Server communication

There must be take a special care how nonce is generated on the client. The client must always keep a history of nonces used for the requests and responses with particular temporary key id.

// Safe nonce generator
class NonceGenerator {
    HashSet<byte[]> pastNonces = new HashSet();
    
    byte[] getUniqueNonce() {
        while (true) {
            byte[] requestNonce  = ByteUtils.randomBytes(12);
            byte[] responseNonce = ByteUtils.randomBytes(12);
            if (pastNonces.contains(requestNonce) || pastNonces.contains(responseNonce)) {
               continue;
            }
            if (requestNonce.equals(responseNonce)) {
               continue;
            }
            pastNonces.add(requestNonce);
            pastNonces.add(responseNonce);
            return ByteUtils.concat(requestNonce, responseNonce);
        }
    }
}

Due to fact that server doesn't keep history of used nonces, then the client must generate also a nonce for the response encryption. We have to be sure that IV is never reused during the communication.

Request data

  • End-To-End encrypted request:
    {
         "temporaryKeyId": "UUID",
         "encryptedData": "BASE64",
         "nonce": "BASE64",
         "timestamp": 0
    }
  • Headers:
    # Application scope
    X-PowerAuth-Encryption: PowerAuth version="4.0", application_key="UNfS0VZX3JhbmRvbQ=="
    # Activation scope
    X-PowerAuth-Encryption: PowerAuth version="4.0", application_key="UNfS0VZX3JhbmRvbQ==", activation_id="c564e700-7e86-4a87-b6c8-a5a0cc89683f"

Response data

  • End-To-End encrypted response
    {
        "encryptedData": "BASE64",
        "timestamp": 0
    }

SHARED_INFO_1

Pre-shared set of constants that guarantees a key separation for different encrypted endpoints, even if the same shared secret is used for different endpoints.

SHARED_INFO_2

In SH2 calculation we just change the algorithms and base key for activation scope:

  • Application scope:

    byte[] SHARED_INFO_2 = Hash.sha3_256(APPLICATION_SECRET);
  • Activation scope:

    byte[] SHARED_INFO_2 = Mac.kmac256(KEY_E2EE_SHARED_INFO2, APPLICATION_SECRET, "PA4SH2");

ASSOCIATED_DATA

The only difference against ECIES scheme is how ASSOCIATED_DATA is constructed:

Version Scope Request Response
>= 4.0 APPLICATION ByteUtils.joinStrings(VERSION, APPLICATION_KEY, TEMPORARY_KEY_ID) ""
>= 4.0 ACTIVATION ByteUtils.joinStrings(VERSION, APPLICATION_KEY, ACTIVATION_ID, TEMPORARY_KEY_ID) ""

It's basically the same algorithm as we use in Crypto 3.3.

QA specification

Cover functionality by unit tests. Test vectors are published for encryption and decryption.

@romanstrobl romanstrobl self-assigned this Jan 9, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant