Skip to content

Commit

Permalink
Fix #640: Add documentation for temporary keys (#641)
Browse files Browse the repository at this point in the history
* Fix #640: Add documentation for temporary keys

* Add note to E2E encryption

* Fix typo
  • Loading branch information
petrdvorak committed Sep 11, 2024
1 parent c227a0e commit 6843a39
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 25 deletions.
54 changes: 29 additions & 25 deletions docs/End-To-End-Encryption.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ Assume we have the following constants and variables defined in our scheme:
- `KEY_EPH_PUB` - Public part of `EPH_KEYPAIR`.
- `SHARED_INFO_2` - Input parameter to MAC calculation.

### Temporary Encryption Keys

To provide required cryptographic features, such as forward secrecy, encryption uses [temporary encryption keys](./Temporary-Encryption-Keys.md) since protocol version `3.3`.

### Encryption Scope

Expand All @@ -37,17 +40,17 @@ PowerAuth protocol defines two basic usage scopes for ECIES encryption:

#### Application Scope

ECIES in application scope has following configuration of parameters:
ECIES in application scope has the following configuration of parameters:

- `KEY_ENC_PUB` is `KEY_SERVER_MASTER_PUBLIC`
- `KEY_ENC_PUB` is a [temporary key](./Temporary-Encryption-Keys.md) with given `TEMP_KEY_ID` identifier fetched from the server associated with a specific application version and signed with `KEY_SERVER_MASTER_PRIVATE` (to prove it was intended for the application scope).
- `SHARED_INFO_1` is a pre-shared constant and is different for each endpoint (see [Pre-shared constants](#pre-shared-constants))
- `SHARED_INFO_2_BASE` is calculated from `APPLICATION_SECRET`:
```java
byte[] SHARED_INFO_2_BASE = Hash.sha256(APPLICATION_SECRET);
```
- `ASSOCIATED_DATA` is calculated as:
```java
byte[] ASSOCIATED_DATA = ByteUtils.concatWithSizes(VERSION, APPLICATION_KEY);
byte[] ASSOCIATED_DATA = ByteUtils.concatWithSizes(VERSION, APPLICATION_KEY, TEMP_KEY_ID);
```

<!-- begin box warning -->
Expand All @@ -56,17 +59,17 @@ Note that the `APPLICATION_SECRET` constant is in Base64 form, so we need to rei

#### Activation Scope

ECIES in activation scope has following configuration of parameters:
ECIES in activation scope has the following configuration of parameters:

- `KEY_ENC_PUB` is `KEY_SERVER_PUBLIC` (e.g. key which is unique for each activation)
- `KEY_ENC_PUB` is a [temporary key](./Temporary-Encryption-Keys.md) with given `TEMP_KEY_ID` identifier fetched from the server associated with a specific application version and activation, and signed with `KEY_SERVER_PRIVATE` (the key which is unique for each activation, to prove it was intended for the activations cope).
- `SHARED_INFO_1` is a pre-shared constant and is different for each endpoint (see [Pre-shared constants](#pre-shared-constants))
- `SHARED_INFO_2_BASE` is calculated from `APPLICATION_SECRET` and `KEY_TRANSPORT`:
```java
byte[] SHARED_INFO_2_BASE = Mac.hmacSha256(KEY_TRANSPORT, APPLICATION_SECRET);
```
- `ASSOCIATED_DATA` is calculated as:
```java
byte[] ASSOCIATED_DATA = ByteUtils.concatWithSizes(VERSION, APPLICATION_KEY, ACTIVATION_ID);
byte[] ASSOCIATED_DATA = ByteUtils.concatWithSizes(VERSION, APPLICATION_KEY, ACTIVATION_ID, TEMP_KEY_ID);
```

<!-- begin box warning -->
Expand All @@ -75,7 +78,7 @@ Note that the `APPLICATION_SECRET` constant is in Base64 form, so we need to rei

### ECIES Encryption

Assume we have a public key `KEY_ENC_PUB`, data `PLAINTEXT` to be encrypted, `ASSOCIATED_DATA` to be included in mac calculation and a `SHARED_INFO_1` and `SHARED_INFO_2_BASE` constants (`byte[]`) as encryption parameters. ECIES encryption works in a following way:
Assume we have a public key `KEY_ENC_PUB`, data `PLAINTEXT` to be encrypted, `ASSOCIATED_DATA` to be included in MAC calculation and a `SHARED_INFO_1` and `SHARED_INFO_2_BASE` constants (`byte[]`) as encryption parameters. ECIES encryption works in the following way:

1. Generate an ephemeral key pair:
```java
Expand Down Expand Up @@ -111,16 +114,16 @@ Assume we have a public key `KEY_ENC_PUB`, data `PLAINTEXT` to be encrypted, `AS
1. Derive `IV` from `NONCE` and encrypt ata using AES.
```java
byte[] IV = KDF_INTERNAL.derive(KEY_IV, NONCE);
byte[] DATA_ENCRYPTED = AES.encrypt(PLAINTEXT, IV, KEY_ENC)
byte[] DATA_ENCRYPTED = AES.encrypt(PLAINTEXT, IV, KEY_ENC);
```
1. Compute the MAC of encrypted data, include `SHARED_INFO_2`.
```java
byte[] DATA = Bytes.concat(DATA_ENCRYPTED, SHARED_INFO_2);
byte[] MAC = Mac.hmacSha256(KEY_MAC, DATA)
byte[] MAC = Mac.hmacSha256(KEY_MAC, DATA);
```
1. Prepare ECIES payload.
```java
EciesPayload payload = (DATA_ENCRYPTED, MAC, KEY_EPH_PUB, NONCE, TIMESTAMP)
EciesPayload payload = (DATA_ENCRYPTED, MAC, KEY_EPH_PUB, NONCE, TIMESTAMP);
```

If this is a response encryption, then we omit `KEY_EPH_PUB` and set it to `null` in steps 3. and 9. to make the response shorter. For example, `SHARED_INFO_2` is then calculated as:
Expand All @@ -140,13 +143,13 @@ Assume we have a private key `KEY_ENC_PRIV`, encrypted data as an instance of th
```
1. Derive base secret key from the private key and ephemeral public key from the ECIES payload (in this step, we do not trim the key to 16b only, we keep all 32b).
```java
SecretKey KEY_BASE = ECDH.phase(KEY_ENC_PRIV, KEY_EPH_PUB)
SecretKey KEY_BASE = ECDH.phase(KEY_ENC_PRIV, KEY_EPH_PUB);
```
1. Derive a secret key using X9.63 KDF function (using SHA256 internally). When calling the KDF, we use `VERSION`, `SHARED_INFO_1` together with `KEY_EPH_PUB` value (as raw `byte[]`) as an `info` parameter.
```java
byte[] VERSION_BYTES = ByteUtils.encode(VERSION);
byte[] INFO = Bytes.concat(VERSION_BYTES, SHARED_INFO_1, KEY_EPH_PUB);
SecretKey KEY_SECRET = KDF_X9_63_SHA256.derive(KEY_BASE, INFO, 48)
SecretKey KEY_SECRET = KDF_X9_63_SHA256.derive(KEY_BASE, INFO, 48);
```
1. Split the 48 bytes long `KEY_SECRET` to three 16B keys. The first part is used as an encryption key `KEY_ENC`. The second part is used as MAC key `KEY_MAC`. The final part is a key for IV derivation `KEY_IV`.
```java
Expand All @@ -166,7 +169,7 @@ Assume we have a private key `KEY_ENC_PRIV`, encrypted data as an instance of th
1. Decrypt the data using AES, with `IV` value derived from `NONCE`.
```java
byte[] IV = KDF_INTERNAL.derive(KEY_IV, NONCE);
byte[] PLAINTEXT = AES.decrypt(DATA_ENCRYPTED, IV, KEY_ENC)
byte[] PLAINTEXT = AES.decrypt(DATA_ENCRYPTED, IV, KEY_ENC);
```

If this is a response decryption, then we omit `KEY_EPH_PUB` and set it to `null` in step 1.
Expand All @@ -175,9 +178,9 @@ If this is a response decryption, then we omit `KEY_EPH_PUB` and set it to `null

Practical implementation of ECIES encryption in PowerAuth accounts for a typical request-response cycle, since encrypting RESTful API requests and responses is the most common use-case.

Client implementation creates an encryptor object that allows encrypting the request and decrypting the response. When encrypting the request, encryptor object accepts a `byte[]` and a public key (for example, `MASTER_SERVER_PUBLIC_KEY`) and produces an instance of `EciesPayload` class. After it receives an encrypted response from the server, which is essentially another instance of `EciesPayload`, it is able to use the original encryption context (the shared encryption keys) to decrypt the response.
Client implementation creates an encryptor object that allows encrypting the request and decrypting the response. When encrypting the request, encryptor object accepts a `byte[]` and a [temporary public key](./Temporary-Encryption-Keys.md) . Then, it produces an instance of `EciesPayload` class. After it receives an encrypted response from the server, which is essentially another instance of `EciesPayload`, it is able to use the original encryption context (the shared encryption keys) to decrypt the response.

Server implementation creates a decryptor object that allows decrypting the original request data and encrypting the response. When server receives an encrypted request, essentially as an `EciesPayload` instance again, it uses a private key (for example, `MASTER_SERVER_PRIVATE_KEY`) to decrypt the original bytes and uses the encryption context to encrypt a response to the client.
Server implementation creates a decryptor object that allows decrypting the original request data and encrypting the response. When server receives an encrypted request, essentially as an `EciesPayload` instance again, it uses a [temporary private key](./Temporary-Encryption-Keys.md) (looked up based on the temporary key ID) to decrypt the original bytes and uses the encryption context to encrypt a response to the client.

Since the client and server use the same encryption context, the ephemeral public key needs to be only sent with the request from the client. Response may only contain encrypted data and MAC value.

Expand All @@ -202,6 +205,7 @@ The typical JSON encoded request is following:

```json
{
"temporaryKeyId": "dc497e8a-8faa-44bc-a52a-20d8393005d2",
"ephemeralPublicKey" : "A97NlW0JPLJfpG0AUvaRHRGSHh+quZu+u0c+yxsK7Xji",
"encryptedData" : "qYLONkDWFpXefTKPbaKTA/PWdRYH5pk9uvGjUqSYbeK7Q0aOohK2MknTyviyNuSp",
"mac" : "DNlZdsM1wgH8v2mAROjj3vmQu4DI4ZJnuTBzQMrHsew=",
Expand Down Expand Up @@ -246,18 +250,18 @@ The response doesn't use HTTP headers.

PowerAuth protocol defines following `SHARED_INFO_1` (also called as `sh1` or `sharedInfo1`) constants for its own internal purposes:

| RESTful endpoint | ECIES scope | `SHARED_INFO_1` value |
| ------------------------------------- | ------------ | --------------------- |
| RESTful endpoint | ECIES scope | `SHARED_INFO_1` value |
| ------------------------------------- | ------------ |---------------------------|
| `/pa/v3/activation/create` (level 1) | application | `/pa/generic/application` |
| `/pa/v3/activation/create` (level 2) | application | `/pa/activation` |
| `/pa/v3/upgrade` | activation | `/pa/upgrade` |
| `/pa/v3/vault/unlock` | activation | `/pa/vault/unlock` |
| `/pa/v3/token/create` | activation | `/pa/token/create` |
| `/pa/v3/recovery/confirm` | activation | `/pa/recovery/confirm` |
| `/pa/v3/activation/create` (level 2) | application | `/pa/activation` |
| `/pa/v3/upgrade` | activation | `/pa/upgrade` |
| `/pa/v3/vault/unlock` | activation | `/pa/vault/unlock` |
| `/pa/v3/token/create` | activation | `/pa/token/create` |
| `/pa/v3/recovery/confirm` | activation | `/pa/recovery/confirm` |

On top of that, following constants can be used for application-specific purposes:

| Purpose | ECIES scope | `SHARED_INFO_1` value |
| ---------------------------------------- | ------------ | --------------------- |
| Purpose | ECIES scope | `SHARED_INFO_1` value |
| ---------------------------------------- | ------------ |---------------------------|
| Generic encryptor for application scope | application | `/pa/generic/application` |
| Generic encryptor for activation scope | activation | `/pa/generic/activation` |
| Generic encryptor for activation scope | activation | `/pa/generic/activation` |
84 changes: 84 additions & 0 deletions docs/Standard-RESTful-API.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ The following endpoints are published in PowerAuth Standard RESTful API (protoco
- [`/pa/v3/upgrade/start`](#upgrade-start) - Start a protocol upgrade (requires encryption).
- [`/pa/v3/upgrade/commit`](#upgrade-commit) - Commits a protocol upgrade (requires authentication).
- [`/pa/v3/recovery/confirm`](#confirm-recovery) - Confirm a recovery code (requires authentication and encryption).
- [`/pa/v3/keystore/create`](#create-new-key-pair) - Create a new temporary key pair for ECIES encryption.
<!-- end -->

## Security Features
Expand Down Expand Up @@ -720,3 +721,86 @@ The JSON response after the decryption:
}
```
<!-- end -->

## Temporary Keys API

<!-- begin api POST /pa/v3/keystore/create -->
### Create New Key Pair

Create a new temporary key pair with either application or activation scope, and obtain the temporary public for subsequent ECIES encryption.

<!-- begin remove -->
| Request parameter | Value |
| ----------------- |------------------------------------------|
| Method | `POST` |
| Resource URI | `/pa/v3/keystore/create` |
<!-- end -->

#### Request

##### Body

The JSON request contains an encoded JWT payload (signed with `HS256`) in a standard request envelope:

```json
{
"requestObject": {
"jwt": "..."
}
}
```

The decoded content of the JWT payload is:

```json
{
"applicationKey" : "...",
"activationId" : "...",
"challenge" : "..."
}
```

If the `activationId` is present (and represents an existing activation), the payload represents request for **activation scoped** temporary public key. Otherwise, the payload represents request for **application scoped** temporary public key. The scope determines how the JWT is signed. In both cases, the JWT is signed with standard `HS256` algorithm, with the following secret key:

- Application scope: Secret key is application secret `APP_SECRET` (decoded to raw bytes).
- Activation scope: Secret key is derived as `KDF_INTERNAL.derive(KEY_TRANSPORT, APP_SECRET)`.

#### Response 200

The JSON response contains an encoded JWT payload (signed with `ES256`) in a standard request envelope:

```json
{
"requestObject": {
"jwt": "..."
}
}
```

The decoded content of the JWT payload is:

```json
{
"sub": "...",
"applicationKey" : "...",
"activationId" : "...",
"challenge" : "...",
"publicKey": "...",
"iat": "...",
"exp": "...",
"iat_ms": "...",
"exp_ms": "..."
}
```

- The `sub` claim represents temporary key ID.
- The `applicationKey`, `activationId` and `challenge` claims are the same as in the request, so that the client can validate the response from the server not only for correct signature, but also to ensure the response is related to the issued request.
- The `publicKey` claim represents Base64 encoded temporary public key.
- The `iat` and `exp` attributes are standard claims representing timestamp of JWT issue and expiration timestamp. To provide a millisecond precision, they are augmented with `iat_ms` and `exp_ms` claims.

The issued public key can be related to either application or activation scope, based on the presence of `activationId` (see the request description for the details). In both cases, the JWT with the public key is signed using `ES256` algorithm, and the scope determines what key is used:

- Application scope: Private key is the application-specific master server private key `KEY_SERVER_MASTER_PRIVATE`.
- Activation scope: Private key is the activation-specific server private key `KEY_SERVER_PRIVATE`.

<!-- end -->
70 changes: 70 additions & 0 deletions docs/Temporary-Encryption-Keys.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Temporary Encryption Keys

To provide better resilience of encryption via advanced features, such as forward secrecy, PowerAuth protocol supports temporary encryption keys (since protocol version 3.3). The idea is that the keys embedded in the mobile app (`KEY_SERVER_MASTER_PUBLIC`) and device specific server public key (`KEY_SERVER_PUBLIC`) are only used for signature verification, serving as trust store on the client for data signed on the server.

Temporary encryption keys are created on the server side via PowerAuth Standard RESTful API. The server keeps the temporary encryption key pair and the client receives a public key, that can be used in a standard ECIES encryption.

The client can request two scopes of temporary encryption keys:

- *Application scope* - the encryption key pair was obtained based on the trust created for the application specific key pair (master server keypair).
- *Activation scope* - the encryption key pair was obtained based on the trust created for the specific activation and it's server key pair (server keypair).

You can see more information about specific request payloads in [Standard RESTful API documentation](./Standard-RESTful-API.md#temporary-keys-api).

## Application Scope

The client sends request in the form of JWT, specifying two parameters:

- `applicationKey` - key `APP_KEY` associated with the application version
- `challenge` - random challenge, used as a request reference

The JWT is signed using `HS256` with the "application secret" (`APP_SECRET`) as the signing key.

The server then takes the request, generates a random temporary encryption key pair associated with the application key, and sends the JWT response signed with `ES256` using `KEY_SERVER_MASTER_PRIVATE`. The JWT response contains:

- `sub` - identifier of the key
- `applicationKey` - back reference to the original data
- `challenge` - back reference to the original data
- `publicKey` - temporary encryption public key
- `iss` / `iss_ms` - temporary key pair issuance timestamp
- `exp` / `exp_ms` - temporary key pair expiration timestamp

The client app should process the response by verifying the signature and checking that the application key and challenge match the expected value. Then, the client app can accept the public key with given key identifier.

## Activation Scope

The client sends request in the form of JWT, specifying three parameters:

- `applicationKey` - key `APP_KEY` associated with the application version
- `activationId` - identifier of the specific PowerAuth activation
- `challenge` - random challenge, used as a request reference

The JWT is signed using `HS256` with the key derived from "application secret" (`APP_SECRET`) and transport key (`KEY_TRANSPORT`) as the signing key:

```
JWT_SIGN_KEY = KDF_INTERNAL.derive(KEY_TRANSPORT, APP_SECRET)
```

The server then takes the request, generates a random temporary encryption key pair associated with the application key and activation ID, and sends the JWT response signed with `ES256` using `KEY_SERVER_PRIVATE`. The JWT response contains:

- `sub` - identifier of the key
- `applicationKey` - back reference to the original data
- `activationId` - back reference to the original data
- `challenge` - back reference to the original data
- `publicKey` - temporary encryption public key
- `iss` / `iss_ms` - temporary key pair issuance timestamp
- `exp` / `exp_ms` - temporary key pair expiration timestamp

The client app should process the response by verifying the signature and checking that the application key, activation ID and challenge match the expected value. Then, the client app can accept the public key with given key identifier.

## Impacted Use-Cases

Besides [End-to-End Encryption](./End-To-End-Encryption.md) itself, the introduction of temporary encryption keys impacts all use-cases that implicitly rely on data encryption, such as:

- New activations (using all supported methods)
- Obtaining and changing activation name from the mobile app.
- Secure Vault
- MAC-based Tokens
- Obtaining User Info
- Confirmation of the Recovery Codes
- Protocol upgrade
1 change: 1 addition & 0 deletions docs/_Sidebar.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
**Encryption**

- [End-To-End Encryption](./End-To-End-Encryption.md)
- [Temporary Encryption Keys](./Temporary-Encryption-Keys.md)

**Other Chapters**

Expand Down

0 comments on commit 6843a39

Please sign in to comment.