Skip to content

Commit

Permalink
Merge pull request #562 from wultra/develop
Browse files Browse the repository at this point in the history
Merge develop to master
  • Loading branch information
banterCZ authored Dec 12, 2023
2 parents e51333b + c0a74f2 commit 6c14ff1
Show file tree
Hide file tree
Showing 17 changed files with 75 additions and 60 deletions.
57 changes: 31 additions & 26 deletions docs/MAC-Token-Based-Authentication.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,39 @@
# MAC Token Based Authentication

While standard PowerAuth signatures are suitable for requests where high degree of authenticity and integrity is required, for high volume common access requests, the strong sequentiality caused by use of a counter might be too restricting. Signed requests must be sent one by one, and a request needs to wait for the previous one to complete. This causes both data processing to be slow and the programming task related to request synchronization to be unnecessarily difficult.
While standard PowerAuth signatures are suitable for requests where a high degree of authenticity and integrity is required, for high-volume common access requests, the strong sequentiality caused by the use of a counter might be too restricting. Signed requests must be sent one by one, and a request needs to wait for the previous one to complete. This causes both data processing to be slow and the programming task related to request synchronization to be unnecessarily difficult.

This is why PowerAuth also supports a simplified MAC Token Based Authentication. As the name suggests, the authentication is achieved by computing a MAC / digest of a pre-shared token.
This is why PowerAuth also supports simplified MAC Token-Based Authentication. As the name suggests, the authentication is achieved by computing a MAC (also called a "digest") using a pre-shared token.

## Security Considerations

There are couple very important things you need to keep in mind while using MAC Token Based Authentication in your APIs:
There are a couple of very important things to keep in mind while using MAC Token-Based Authentication in your APIs:

- **Data Integrity** - Since the resulting digest does not include any request data, it does not prevent data from being modified.
- **Replay Attack** - By default, there is no replay attack protection when validating the tokens. You need to implement your own replay attack protection in case you need it, for example by storing already used values of `timestamp` and `nonce` for given `token_id` and disallowing repeated values in certain time window.
- **Single Factor** - While the token has an information about the factors used while the token was created, which is handy while distinguishing different grades of information (for example, some more sensitive info may require token that was created using 2FA), the authentication as such uses only a single factor. It does not include PIN/password or biometric information at all.
- **Single Factor** - While the token has information about the factors used while the token was created, which is handy while distinguishing different grades of information (for example, some more sensitive info may require a token that was created using 2FA), the authentication as such uses only a single factor. It does not include PIN/password or biometric information at all.

As a result, you must use MAC Token Based Authentication for read-only operations only. In other words, use the MAC Token Based Authentication to access resources, not to create or modify them. Use 1FA PowerAuth signature for active operations that create or modify resources. This way, you avoid having repeated or inconsistent data, while allowing access to information that needs to be frequently accessed.
As a result, you must use MAC Token-Based Authentication for read-only operations only. In other words, use the MAC Token-Based Authentication to access resources, not to create or modify them. We recommend using the 1FA PowerAuth signature for active operations that create or modify resources but do not require user's interaction. This way, you avoid having repeated or inconsistent data while allowing access to information that needs to be frequently accessed.

Examples:

- MAC Token Based Authentication: Accessing simple information about the account, such as account name, balance of the account and last three transactions, from Apple Watch.
- PowerAuth 1FA Signature: Creating a quick small value payment from an iPhone app.
**MAC Token-Based Authentication**

Accessing simple information about the account, such as account name, balance of the account, and last three transactions, from Apple Watch.

**PowerAuth 1FA Signature**

Creating a quick, low-value payment from an iPhone app.

## Creating a Token

In order to create a new token, the client application must call a PowerAuth Standard RESTful API endpoint `/pa/v3/token/create`.

This endpoint must be called with a standard PowerAuth signature. It can be any type of a signature - 1FA, 2FA or 3FA. The token then implicitly carries the information about the signature it was issued with. Using the PowerAuth signature assures authenticity and integrity of the request data.
This endpoint must be called with a standard PowerAuth signature. It can be any type of signature - 1FA or 2FA. The token then implicitly carries the information about the signature it was issued with. Using the PowerAuth signature assures the authenticity and integrity of the data sent during the request.

The endpoint then uses the same request and response encryption principles as described in a dedicated chapter for [End-to-End Encryption](./End-To-End-Encryption.md).

Upon receiving and successfully validating a request authenticated using PowerAuth signature, server generates a new token for given activation ID. Information about used signature type and factors are stored with the token. Then, the server takes the token ID and secret and sends them in an ECIES encrypted response to the client.
Upon receiving and successfully validating a request authenticated using a PowerAuth signature, the server generates a new token for a given activation ID. Information about the used signature type and factors are stored with the token. Then, the server takes the token ID and secret and sends them in an ECIES encrypted response to the client.

The decrypted response data payload contains following raw response format:
The decrypted response data payload contains the following raw response format:

```json
{
Expand All @@ -38,29 +42,30 @@ The decrypted response data payload contains following raw response format:
}
```

The `tokenId` value is in UUID level 4 format and it uniquely identifies the token in the system and is sent with every request that requires MAC token based authentication. The `token_secret` value is random 16B value encoded as Base64, it is stored on the device and used as a secret key for computing the MAC later.
The `tokenId` value is in UUID level 4 format, and it uniquely identifies the token in the system and is sent with every request that requires MAC token-based authentication. The `token_secret` value is a random 16B value encoded as Base64. It is stored on the device and used as a secret key for computing the MAC later.

Client stores both `token_id` and `token_secret` in a suitable local storage (iOS Keychain, Android Shared Preferences).
The client stores both `token_id` and `token_secret` in a suitable local storage (iOS Keychain, encrypted Shared Preferences).

## Using the Tokens

When using MAC Token Based Authentication, the authentication of the RESTful API calls is achieved by computing a `token_digest` digest value on a client side that can be later validated on the server side. The algorithms for calculation and verification of the digest are in principle the same.
When using MAC Token-Based Authentication, the authentication of the RESTful API calls is achieved by computing a `token_digest` digest value on the client side that can be later validated on the server side. The algorithms for calculation and verification of the digest are, in principle, the same.

The `token_digest` value is computed using a following algorithm:
The `token_digest` value is computed using the following algorithm:

```java
// '$timestamp' is a unix timestamp in milliseconds (to achieve required time
// precision) converted to string and then to byte[] using UTF-8
// '$timestamp' is a Unix timestamp in milliseconds (to achieve the required time
// precision) converted to a string and then to byte[] using UTF-8
// encoding
// '$version' is the protocol version, represented as UTF-8 bytes of the version string
long timestamp = Time.getTimestamp();
byte[] timestamp_bytes = ByteUtils.encode(String.valueOf(timestamp));

// '$nonce' value is 16B of random data
byte[] nonce = Generator.randomBytes(16);

// '$nonce' is concatenated to '$timestamp' using '&' character:
// $nonce + '&' + $timestamp
byte[] data = ByteUtils.concat(nonce, ByteUtils.encode("&"), timestamp_bytes);
// '$nonce' is concatenated to '$timestamp' and '$version' using '&' character:
// $nonce + '&' + $timestamp + '&' + $version
byte[] data = ByteUtils.concat(nonce, ByteUtils.encode("&"), timestamp_bytes, ByteUtils.encode("&"), version);

// 'token_secret' is 16B of random data
SecretKey key = KeyConversion.secretKeyFromBytes(token_secret);
Expand All @@ -69,7 +74,7 @@ SecretKey key = KeyConversion.secretKeyFromBytes(token_secret);
byte[] token_digest = Mac.hmacSha256(key, data)
```

In order to use the token authentication with the RESTful API call, you need to set following HTTP header to the request:
In order to use the token authentication with the RESTful API call, you need to set the following HTTP header to the request:

```
X-PowerAuth-Token: PowerAuth token_id="${TOKEN_ID}"
Expand All @@ -82,14 +87,14 @@ X-PowerAuth-Token: PowerAuth token_id="${TOKEN_ID}"
Transport representation of the HTTP header properties is following:

- `token_id` - Identifier of the token, as is - UUID level 4.
- `token_digest` - Digest value computed using `token_secret`, `nonce` and `timestamp`, Base64 encoded.
- `token_digest` - Digest value computed using `token_secret`, `nonce`, and `timestamp`, Base64 encoded.
- `nonce` - Random cryptographic nonce, 16B long, Base64 encoded.
- `timestamp` - Current timestamp in a Unix timestamp format (in milliseconds, to achieve required time precision), represented as string value.
- `timestamp` - Current timestamp in a Unix timestamp format (in milliseconds, to achieve required time precision), represented as a string value.
- `version` - Protocol version.

## Token Removal

You can remove a token with given ID anytime by sending a signed request to the PowerAuth Standard RESTful API endpoint `/pa/v3/token/remove`:
You can remove a token with a given ID anytime by sending a signed request to the PowerAuth Standard RESTful API endpoint `/pa/v3/token/remove`:

```json
{
Expand All @@ -99,9 +104,9 @@ You can remove a token with given ID anytime by sending a signed request to the
}
```

You can use any signature type to authenticate the token removal request. All signature types are allowed because the tokens are mostly used for a simplified access. Allowing user to restrict the access again should be simple, as long as there is at least some authentication that would prevent removing token to the malicious party (causing DoS to the legitimate user).
You can use any signature type to authenticate the token removal request. All signature types are allowed because the tokens are mostly used for simplified access. Allowing the user to restrict the access again should be simple, as long as there is at least some authentication that would prevent removing token to the malicious party (causing DoS to the legitimate user).

In case the signature validation is successful and after validating that the token is associated with the activation used for computing the signature, the token is removed on the server side. The response object only confirms the removal and the payload is typically ignored in the PowerAuth Mobile SDK:
If the signature validation is successful and after validating that the token is associated with the activation used for computing the signature, the token is removed on the server side. The response object only confirms the removal and the payload is typically ignored in the PowerAuth Mobile SDK:

```json
{
Expand Down
10 changes: 5 additions & 5 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

<groupId>io.getlime.security</groupId>
<artifactId>powerauth-crypto-parent</artifactId>
<version>1.5.0</version>
<version>1.6.0</version>
<packaging>pom</packaging>

<inceptionYear>2016</inceptionYear>
Expand Down Expand Up @@ -75,12 +75,12 @@
<maven.compiler.target>17</maven.compiler.target>
<maven-jar-plugin.version>3.3.0</maven-jar-plugin.version>
<maven-deploy-plugin.version>3.1.1</maven-deploy-plugin.version>
<maven-javadoc-plugin.version>3.5.0</maven-javadoc-plugin.version>
<maven-javadoc-plugin.version>3.6.3</maven-javadoc-plugin.version>
<maven-source-plugin.version>3.3.0</maven-source-plugin.version>
<maven-surefire-plugin.version>3.1.2</maven-surefire-plugin.version>
<guava.version>32.1.2-jre</guava.version>
<maven-surefire-plugin.version>3.2.2</maven-surefire-plugin.version>
<guava.version>32.1.3-jre</guava.version>
<slf4j.version>2.0.9</slf4j.version>
<junit.version>5.9.2</junit.version>
<junit.version>5.10.1</junit.version>
</properties>

<dependencyManagement>
Expand Down
8 changes: 4 additions & 4 deletions powerauth-java-crypto/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
<parent>
<groupId>io.getlime.security</groupId>
<artifactId>powerauth-crypto-parent</artifactId>
<version>1.5.0</version>
<version>1.6.0</version>
</parent>

<dependencies>
Expand All @@ -51,7 +51,7 @@
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.1</version>
<version>2.16.0</version>
<scope>test</scope>
</dependency>
<dependency>
Expand All @@ -63,13 +63,13 @@
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>1.76</version>
<version>1.77</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
</dependencies>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,8 @@
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import java.util.Base64;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,14 @@ public byte[] generateTokenTimestamp() {
*
* @param nonce Token nonce, 16 random bytes.
* @param timestamp Token timestamp, Unix timestamp format encoded as bytes (from string representation).
* @param version Protocol version.
* @param tokenSecret Token secret, 16 random bytes.
* @return Token digest computed using provided data bytes with given token secret.
* @throws GenericCryptoException In case digest computation fails.
* @throws CryptoProviderException In case cryptography provider is incorrectly initialized.
*/
public byte[] computeTokenDigest(byte[] nonce, byte[] timestamp, byte[] tokenSecret) throws GenericCryptoException, CryptoProviderException {
return tokenUtils.computeTokenDigest(nonce, timestamp, tokenSecret);
public byte[] computeTokenDigest(byte[] nonce, byte[] timestamp, String version, byte[] tokenSecret) throws GenericCryptoException, CryptoProviderException {
return tokenUtils.computeTokenDigest(nonce, timestamp, version, tokenSecret);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
*/
package io.getlime.security.powerauth.crypto.client.vault;

import io.getlime.security.powerauth.crypto.lib.generator.KeyGenerator;
import io.getlime.security.powerauth.crypto.lib.model.exception.CryptoProviderException;
import io.getlime.security.powerauth.crypto.lib.model.exception.GenericCryptoException;
import io.getlime.security.powerauth.crypto.lib.util.AESEncryptionUtils;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@
import io.getlime.security.powerauth.crypto.lib.encryptor.ecies.ServerEciesEncryptor;
import io.getlime.security.powerauth.crypto.lib.encryptor.exception.EncryptorException;
import io.getlime.security.powerauth.crypto.lib.encryptor.model.EncryptorId;
import io.getlime.security.powerauth.crypto.lib.encryptor.model.EncryptorParameters;
import io.getlime.security.powerauth.crypto.lib.encryptor.model.EncryptorScope;
import io.getlime.security.powerauth.crypto.lib.encryptor.model.EncryptorSecrets;
import io.getlime.security.powerauth.crypto.lib.encryptor.model.EncryptorParameters;

/**
* The {@code EncryptorFactory} class provide high level encryptors for PowerAuth End-To-End encryption implementation.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@
import io.getlime.security.powerauth.crypto.lib.generator.KeyGenerator;
import io.getlime.security.powerauth.crypto.lib.model.exception.CryptoProviderException;
import io.getlime.security.powerauth.crypto.lib.model.exception.GenericCryptoException;
import io.getlime.security.powerauth.crypto.lib.util.*;
import io.getlime.security.powerauth.crypto.lib.util.AESEncryptionUtils;
import io.getlime.security.powerauth.crypto.lib.util.EciesUtils;
import io.getlime.security.powerauth.crypto.lib.util.HMACHashUtilities;
import io.getlime.security.powerauth.crypto.lib.util.KeyConvertor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
*/
package io.getlime.security.powerauth.crypto.lib.encryptor.ecies.model;

import lombok.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Value;

/**
* The EciesCryptogram structure represents cryptogram transmitted over the network.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
*/
package io.getlime.security.powerauth.crypto.lib.encryptor.ecies.model;

import lombok.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Value;

/**
* The EciesParameters structure represents additional ECIES parameters transmitted over the network.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@

import io.getlime.security.powerauth.crypto.lib.encryptor.ecies.exception.EciesException;
import io.getlime.security.powerauth.crypto.lib.encryptor.model.EncryptorScope;
import io.getlime.security.powerauth.crypto.lib.model.exception.CryptoProviderException;
import io.getlime.security.powerauth.crypto.lib.model.exception.GenericCryptoException;

import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
Expand Down Expand Up @@ -137,7 +135,7 @@ public static byte[] deriveSharedInfo2(String protocolVersion, byte[] sharedInfo
throw new EciesException("Missing nonce parameter");
}
if (timestamp == null) {
throw new EciesException("Missing nonce parameter");
throw new EciesException("Missing timestamp parameter");
}
if (associatedData == null) {
throw new EciesException("Missing associatedData parameter");
Expand Down
Loading

0 comments on commit 6c14ff1

Please sign in to comment.