Skip to content

Commit 2bae812

Browse files
authored
Fix #877: Update documentation for activations (#879)
1 parent 3bc64d1 commit 2bae812

10 files changed

+372
-251
lines changed

docs/Activation-Code.md

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,44 @@
11
# Activation Code
22

3-
The PowerAuth protocol 3, defines a new version of activation code, where OTP<sup>1</sup> is no longer applied. The format of the code is the same (four groups, each group is composed of five Base32 characters), but the code is no longer split into `OTP` and `SHORT_ID` parts. The new code has following features:
3+
The activation code is a short, user-facing activation identifier. It is not used in any cryptographic calculations. All cryptographic security is established through the shared-secret exchange and subsequent protocol steps.
44

5-
- The whole code is now a short activation identifier, and we call it simply `ACTIVATION_CODE`. This principally means, that the code is no longer used in the cryptographic calculations.
6-
- The code is using `CRC-16/ARC` to detect a typing errors. This is useful for scenarios, where the user needs to re-type the code manually.
7-
- 96 out of possible 100 bits are used (80 random bits + 16 bits for CRC).
5+
The activation code has the following properties:
86

9-
> Notes:
10-
> 1. PowerAuth protocol V2 defines OTP as a part of activation code. It's completely unrelated to an OTP described in chapter [Advanced Activation Flows](./Advanced-Activation-Flows.md).
7+
- Format: four groups, each group composed of five Base32 characters, separated by `-`.
8+
- The whole value is treated as a single identifier called `ACTIVATION_CODE`.
9+
- The code is protected by an embedded `CRC-16/ARC` checksum to detect typing errors.
10+
- 96 out of possible 100 bits are used:
11+
- 80 bits of randomness
12+
- 16 bits of CRC
1113

1214
## Code Construction
1315

14-
1. Generate 10 random bytes
15-
2. Calculate `CRC-16/ARC` from that 10 bytes. You can check a [reference implementation](resources/snippets/CRC16.java) in Java.
16-
3. Append CRC-16 in big endian order at the end of random bytes.
17-
4. Generate BASE32 representation from that 12 bytes, without padding characters.
18-
5. Split BASE32 string into four groups, each one contains file characters. Use "-" as a separator.
16+
1. Generate 10 random bytes (80 bits of entropy).
17+
2. Calculate `CRC-16/ARC` over those 10 bytes. You can check a [reference implementation](resources/snippets/CRC16.java) in Java.
18+
3. Append the CRC value in big-endian order to the end of the random bytes, producing 12 bytes total.
19+
4. Encode the resulting 12 bytes using Base32 without padding.
20+
5. Split the Base32 string into four groups, each containing five characters, and join them using `-` as a separator:
1921

20-
## Code Validation
22+
The resulting format is following:
23+
```
24+
XXXXX-XXXXX-XXXXX-XXXXX
25+
```
2126

22-
The validation process is quite simple:
27+
## Code Validation
2328

24-
1. Test whether the length of activation code is equal to 23. If not, then the code is not valid.
25-
2. Remove dashes form the code.
26-
3. Test whether the string contains only characters allowed in Base32 encoding.
27-
4. Decode Base32 string into sequence of bytes
28-
5. The length of decoded sequence must be 12
29-
6. Calculate CRC-16/ARC from first 10 bytes
30-
7. Compare the calculated value to last two bytes (in big endian order). If values doesn't match, then the code contains some mistyped characters.
29+
Validation is intentionally simple and independent of the cryptographic layer:
30+
1. Verify that the activation code length is exactly 23 characters (including dashes). If not, the code is invalid.
31+
2. Remove all `-` characters.
32+
3. Verify that the remaining string contains only characters allowed by Base32 encoding.
33+
4. Decode the Base32 string into bytes.
34+
5. Verify that the decoded byte array length is exactly 12.
35+
6. Compute `CRC-16/ARC` over the first 10 bytes.
36+
7. Compare the computed CRC with the last two bytes (interpreted as big-endian).
37+
If the values do not match, the activation code contains mistyped or corrupted characters.
3138

32-
### Test values
39+
### Test Values
3340

34-
You can use following simple values to test your application's validation logic:
41+
You can use the following values to test validation logic.
3542

3643
- `AAAAA-AAAAA-AAAAA-AAAAA`
3744
- `LLLLL-LLLLL-LLLLL-LQJTA`

docs/Activation-Status.md

Lines changed: 126 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,139 @@
11
# Activation Status
22

3-
PowerAuth Client may need to check for an activation status, so that it can determine if it should display UI for non-activated state (registration form), blocked state (how to unblock tutorial) or active state (login screen). To facilitate this use-case, PowerAuth Standard RESTful API publishes a [/pa/v3/activation/status](./Standard-RESTful-API#activation-status) endpoint.
3+
PowerAuth Client may need to check for an activation status, so that it can determine if it should display UI for non-activated state (registration form), blocked state (how to unblock tutorial) or active state (login screen). To facilitate this use-case, PowerAuth Standard RESTful API publishes a [/pa/v4/activation/status](./Standard-RESTful-API#activation-status) endpoint.
44

5-
Checking for an activation status is simple. Client needs to prepare a HTTP request with an activation ID and random `STATUS_CHALLENGE`. Server processes the request and sends back the response with activation status blob and random `STATUS_NONCE`. Activation status blob is an encrypted binary blob that encodes the activation status. Key `KEY_TRANSPORT` and `STATUS_IV` is used to encrypt the activation blob.
5+
Checking activation status is performed over standard end-to-end encryption with a temporary activation-scoped key. The legacy STATUS_CHALLENGE / STATUS_NONCE transport is no longer used.
6+
7+
The server returns a Base64-encoded binary activation status blob that is integrity protected with KMAC and transported inside the encrypted response.
68

79
## Status Check Sequence
810

9-
The following sequence diagram shows the activation status check in more detail.
10-
11-
![Check Activation Status](./resources/images/sequence_activation_status.png)
12-
13-
## Status Blob Encryption
14-
15-
1. Both, client and server calculate `KEY_TRANSPORT_IV` as:
16-
```java
17-
SecretKey KEY_TRANSPORT_IV = KDF.derive(KEY_TRANSPORT, 3000)
18-
```
19-
1. Client choose random 16 bytes long `STATUS_CHALLENGE` and send that value to the server:
20-
```java
21-
byte[] STATUS_CHALLENGE = Generator.randomBytes(16)
22-
```
23-
1. Server choose random 16 bytes long `STATUS_NONCE` and calculates `STATUS_IV` as:
24-
```java
25-
byte[] STATUS_NONCE = Generator.randomBytes(16)
26-
byte[] STATUS_IV_DATA = ByteUtils.concat(STATUS_CHALLENGE, STATUS_NONCE)
27-
byte[] STATUS_IV = KeyConversion.getBytes(KDF_INTERNAL.derive(KEY_TRANSPORT_IV, STATUS_IV_DATA))
28-
```
29-
1. Server uses `KEY_TRANSPORT` as key and `STATUS_IV` as IV to encrypt the status blob:
30-
```java
31-
encryptedStatusBlob = AES.encrypt(statusBlob, STATUS_IV, KEY_TRANSPORT, "AES/CBC/NoPadding")
32-
```
33-
1. Server sends `encryptedStatusBlob` and `STATUS_NONCE` as response to the client.
34-
1. Client receives `encryptedStatusBlob` and `STATUS_NONCE` and calculates the same `STATUS_IV` and then decrypts the status data:
35-
```java
36-
byte[] STATUS_IV_DATA = ByteUtils.concat(STATUS_CHALLENGE, STATUS_NONCE)
37-
byte[] STATUS_IV = KeyConversion.getBytes(KDF_INTERNAL.derive(KEY_TRANSPORT_IV, STATUS_IV_DATA))
38-
byte[] statusBlob = AES.decrypt(encryptedStatusBlob, STATUS_IV, KEY_TRANSPORT, "AES/CBC/NoPadding")
39-
```
11+
The client calls the activation status endpoint with an empty request body using standard end-to-end encryption.
12+
13+
Response body (before encryption):
14+
15+
```json
16+
{
17+
"activationStatus": "BASE64",
18+
"customObject": {
19+
"_comment": "Any optional service data"
20+
}
21+
}
22+
```
23+
24+
Activation status uses activation-scoped end-to-end encryption with SHARED_INFO_1 = "/pa/activation/status".
4025

4126
## Status Blob Format
4227

43-
When obtaining the activation status, application receives the binary status blob. Structure of the 32B long status blob is the following (without newlines):
28+
The final binary status blob is:
29+
30+
```java
31+
byte[] BINARY_STATUS_BLOB = ByteUtils.concat(STATUS_DATA, STATUS_MAC);
32+
```
33+
34+
### STATUS_DATA
35+
36+
Binary layout:
37+
38+
```
39+
4B: 0xDEC0DED4
40+
1B: ${STATUS}
41+
1B: ${CURRENT_VERSION}
42+
1B: ${UPGRADE_VERSION}
43+
1B: ${STATUS_FLAGS}
44+
4B: ${RESERVED}
45+
1B: ${CTR_BYTE}
46+
1B: ${FAIL_COUNT}
47+
1B: ${MAX_FAIL_COUNT}
48+
1B: ${CTR_LOOK_AHEAD}
49+
32B: ${CTR_DATA_HASH}
50+
```
51+
52+
Note: Magic prefix changed from `0xDEC0DED1` to `0xDEC0DED4` to indicate the newest status blob format.
53+
54+
### STATUS_MAC
55+
56+
Integrity protection:
57+
58+
```java
59+
byte[] STATUS_MAC = Mac.kmac256(KEY_MAC_STATUS, STATUS_DATA, 32, "PA4MAC-STATUS");
60+
```
61+
62+
Where:
63+
64+
```java
65+
SecretKey KDK_UTILITY = KDF.derive(KEY_ACTIVATION_SECRET, "util");
66+
SecretKey KEY_MAC_STATUS = KDF.derive(KDK_UTILITY, "util/mac/status");
67+
```
68+
69+
Note: Even though the blob is delivered over end-to-end encryption, it is additionally authenticated with `STATUS_MAC`.
70+
71+
## Status Fields
72+
73+
### STATUS
74+
75+
- `0x01` – CREATED
76+
- `0x02` – PENDING_COMMIT
77+
- `0x03` – ACTIVE
78+
- `0x04` – BLOCKED
79+
- `0x05` – REMOVED
4480

81+
### CURRENT_VERSION
82+
83+
Current protocol version of the activation (currently `3` or `4`).
84+
85+
### UPGRADE_VERSION
86+
87+
Maximum protocol version supported by the server for this activation (currently `4`).
88+
89+
### STATUS_FLAGS
90+
91+
Bitmask:
92+
93+
- bit 0 – `STATUS_FLAG_ACTIVATION_CONFIRMATION` – pending client confirmation
94+
- bit 1 – `STATUS_FLAG_UPGRADE_CONFIRMATION` – pending upgrade confirmation
95+
- bit 2 – `STATUS_FLAG_UNSUPPORTED_ALGORITHM` – activation uses unsupported algorithm
96+
- bit 3 – `STATUS_FLAG_BIOMETRY_FACTOR_ON` – biometry factor enabled on server
97+
98+
Flag explanation:
99+
100+
Client should treat activation as upgradeable if `CURRENT_VERSION` differs from `UPGRADE_VERSION`.
101+
102+
The `STATUS_FLAG_UNSUPPORTED_ALGORITHM` is used to denote that the algorithm used to create the activation is no longer supported.
103+
104+
Pending activation confirmation is indicated by `STATUS_FLAG_ACTIVATION_CONFIRMATION`.
105+
106+
Pending upgrade confirmation is indicated by `STATUS_FLAG_UPGRADE_CONFIRMATION`.
107+
108+
### CTR_BYTE
109+
110+
Least significant byte of current counter:
111+
112+
```java
113+
byte CTR_BYTE = (byte)(CTR & 0xFF);
114+
```
115+
116+
### Counters
117+
118+
Counter explanation:
119+
120+
- `FAIL_COUNT` = current failed attempts
121+
- `MAX_FAIL_COUNT` = maximum allowed attempts
122+
- `CTR_LOOK_AHEAD` = tolerance on the server for counter iterations when the hashed-based counter is ahead on the client
123+
124+
### CTR_DATA_HASH
125+
126+
32-byte hash of the hash-based counter is calculated like this:
127+
128+
```java
129+
byte[] CTR_DATA_HASH = Mac.kmac256(CTR_DATA, KEY_MAC_CTR_DATA, 32, "PA4MAC-CTR");
45130
```
46-
0xDEC0DED1 1B:${STATUS} 1B:${CURRENT_VERSION} 1B:${UPGRADE_VERSION}
47-
5B:${RESERVED} 1B:${CTR_BYTE} 1B:${FAIL_COUNT} 1B:${MAX_FAIL_COUNT}
48-
1B:${CTR_LOOK_AHEAD} 16B:${CTR_DATA_HASH}
131+
132+
The key for counter data is obtained as follows:
133+
134+
```java
135+
SecretKey KDK_UTILITY = KDF.derive(KEY_ACTIVATION_SECRET, "util");
136+
SecretKey KEY_MAC_CTR_DATA = KDF.derive(KDK_UTILITY, "util/mac/ctr-data");
49137
```
50138

51-
where:
52-
53-
- The first 4 bytes (`0xDE 0xC0 0xDE 0xD1`) are basically a fixed prefix.
54-
- Note that the last byte of this constant also represents the version of the status blob format. If we decide to change the status blob significantly, then the value will be changed to `0xD2`, `0xD3`, etc.
55-
- `${STATUS}` - A status of the activation record, it can be one of following values:
56-
- `0x01 - CREATED`
57-
- `0x02 - PENDING_COMMIT`
58-
- `0x03 - ACTIVE`
59-
- `0x04 - BLOCKED`
60-
- `0x05 - REMOVED`
61-
- `${CURRENT_VERSION}` - 1 byte representing current version of crypto protocol, it can be one of following values:
62-
- `0x02` - PowerAuth protocol version `2.x`
63-
- `0x03` - PowerAuth protocol version `3.x`
64-
- `${UPGRADE_VERSION}` - 1 byte representing maximum protocol version supported by the PowerAuth Server. The set of possible values is identical to `${CURRENT_VERSION}`
65-
- `${RESERVED}` - 5 bytes reserved for the future use.
66-
- `${CTR_BYTE}` - 1 byte representing the least significant byte from current value of counter, calculated as:
67-
```java
68-
byte CTR_BYTE = (byte)(CTR & 0xFF);
69-
```
70-
- `${FAIL_COUNT}` - 1 byte representing information about the number of failed attempts at the moment.
71-
- `${MAX_FAIL_COUNT}` - 1 byte representing information about the maximum allowed number of failed attempts.
72-
- `${CTR_LOOK_AHEAD}` - 1 byte representing constant for a look ahead window, used on the server to validate the authentication code.
73-
- `${CTR_DATA_HASH}` - 16 bytes containing hash from current value of a hash-based counter:
74-
```java
75-
SecretKey KEY_TRANSPORT_CTR = KDF.derive(KEY_TRANSPORT, 4000);
76-
byte[] CTR_DATA_HASH = KeyConversion.getBytes(KDF_INTERNAL.derive(KEY_TRANSPORT_CTR, CTR_DATA));
77-
```
139+

0 commit comments

Comments
 (0)