Skip to content

Commit

Permalink
improve tests (#9)
Browse files Browse the repository at this point in the history
- Separate tests into two groups: online (`VerifyTestLive.java`) and offline (`VerifyTest.java`).
- Disable online test by default.
- Add faketime tests.
  • Loading branch information
Taowyoo authored Nov 13, 2024
1 parent 11607d3 commit 934d904
Show file tree
Hide file tree
Showing 5 changed files with 224 additions and 167 deletions.
68 changes: 41 additions & 27 deletions .github/workflows/java-example-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,38 @@ name: Java CI with Maven for java-example

on:
push:
branches: [ "main" ]
branches: ["main"]
paths:
- 'key-attestation/java-example/**'
- '.github/workflows/**'
- "key-attestation/java-example/**"
- ".github/workflows/**"
pull_request:
branches: [ "main" ]
branches: ["main"]
paths:
- 'key-attestation/java-example/**'
- '.github/workflows/**'
- "key-attestation/java-example/**"
- ".github/workflows/**"
schedule:
# Runs at 00:00 UTC every other day
- cron: '0 0 */2 * *'
- cron: "0 0 */2 * *"

jobs:
build:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- name: Set up JDK 8
uses: actions/setup-java@v3
with:
java-version: '8'
distribution: 'temurin'
cache: maven
- name: Check and Install Maven
run: |
- uses: actions/checkout@v3
- name: Install faketime
run: |
sudo apt-get install -y faketime
- name: Set up JDK 8
uses: actions/setup-java@v3
with:
java-version: "8"
distribution: "temurin"
cache: maven

- name: Check and Install Maven
run: |
if ! command -v mvn --version &> /dev/null
then
echo "Maven could not be found"
Expand All @@ -43,13 +47,23 @@ jobs:
else
echo "Maven is already installed"
fi
- name: Build with Maven
run: mvn -B package -Dstyle.color=always --file key-attestation/java-example/pom.xml
env:
AMER_APP_API_KEY: ${{ secrets.AMER_APP_API_KEY }}

# Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive
- name: Update dependency graph
uses: advanced-security/maven-dependency-submission-action@571e99aab1055c2e71a1e2309b9691de18d6b7d6
with:
directory: key-attestation/java-example
- name: Run offline tests and build package
run: mvn -B -Dstyle.color=always package
working-directory: ./key-attestation/java-example

- name: Run faketime tests
run: ./run_faketime_tests.sh
working-directory: ./key-attestation/java-example

- name: Run online tests
run: mvn test -Dstyle.color=always -Dtest=VerifyTestLive
working-directory: ./key-attestation/java-example
env:
AMER_APP_API_KEY: ${{ secrets.AMER_APP_API_KEY }}

# Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive
- name: Update dependency graph
uses: advanced-security/maven-dependency-submission-action@571e99aab1055c2e71a1e2309b9691de18d6b7d6
with:
directory: key-attestation/java-example
20 changes: 13 additions & 7 deletions key-attestation/java-example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,25 @@ This is a example java of how to verify **Fortanix DSM Key Attestation Statement

## Building

`mvn compile`
`mvn -B package`

## Testing

`mvn test`
- Run offline tests:
- `mvn test`
- Run online tests:
- `mvn test -Dtest=VerifyTestLive`
- Because the certificates stored in repo are already expired, to test the successful code path with [`faketime`](https://manpages.ubuntu.com/manpages/trusty/man1/faketime.1.html):
- `./run_faketime_tests.sh`

## Explanation

The test code under [VerifyTest.java](src/test/java/com/fortanix/keyattestationstatementverifier/VerifyTest.java)
shows how to properly verify the **Fortanix DSM Key Attestation Statement** certificate:
shows how to properly verify the **Fortanix DSM Key Attestation Statement** certificate offline:

Online check
- `verifyStatementFullCheck`: Verify given `KeyAttestationResponse` in a JSON file, the `Fortanix Attestation and Provisioning Root CA` is downloaded from https://pki.fortanix.com in runtime. **Note**: This test is turned off since CRL and PKI server is not ready when this example code is created and certificates for testing are signed by fake CA.

Offline check
- `verifyStatementFromJsonWithoutCrlCheck`: Verify given `KeyAttestationResponse` in a JSON file, assuming the last certificate in authority chain is correct ROOT certificate and skipping CRL checks.

The test code under [VerifyTestLive.java](src/test/java/com/fortanix/keyattestationstatementverifier/VerifyTest.java)
shows how to properly verify the **Fortanix DSM Key Attestation Statement** certificate online:

- `verifyStatementFullCheckOnlineAMER`: Use https://amer.smartkey.io to create a RSA key and get attestation statement of it.Then it verifies the statement with root CA downloaded from https://pki.fortanix.com/Fortanix_Attestation_and_Provisioning_Root_CA.crt.
13 changes: 13 additions & 0 deletions key-attestation/java-example/run_faketime_tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/bash

# Check if faketime is installed
if ! command -v faketime &> /dev/null; then
echo "faketime is not installed. Please install it using 'sudo apt install faketime'."
exit 1
fi

# Tell tests that faketime is active
export FAKE_TIME_ACTIVE=1

# Run Maven tests with faketime active
faketime --exclude-monotonic '2023-09-15 10:00:00' mvn test -Dstyle.color=always -Dtest=VerifyTest
Original file line number Diff line number Diff line change
Expand Up @@ -5,39 +5,17 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fortanix.keyattestationstatementverifier.types.json.KeyAttestationResponse;

import com.fortanix.sdkms.v1.ApiClient;
import com.fortanix.sdkms.v1.ApiException;
import com.fortanix.sdkms.v1.Configuration;
import com.fortanix.sdkms.v1.Pair;
import com.fortanix.sdkms.v1.api.AuthenticationApi;
import com.fortanix.sdkms.v1.api.SecurityObjectsApi;
import com.fortanix.sdkms.v1.auth.ApiKeyAuth;
import com.fortanix.sdkms.v1.model.AuthResponse;
import com.fortanix.sdkms.v1.model.KeyObject;
import com.fortanix.sdkms.v1.model.ObjectType;

import com.fortanix.sdkms.v1.model.SobjectRequest;

import static org.junit.Assert.*;

import java.io.FileReader;
import java.io.Reader;
import java.net.URL;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import javax.ws.rs.core.GenericType;

public class VerifyTest {
private static final String FORTANIX_AMER_SAAS_SERVER_URL = "https://amer.smartkey.io";
private static final String JAVA_CI_AMER_APP_API_KEY = "AMER_APP_API_KEY";
private static final String VALID_STATEMENT_CERT_PEM = "key-attestation-statement.pem";
private static final String VALID_RESPONSE_JSON = "key-attestation-response.json";
private static final boolean DEBUG = false;

private URL getTestFileUrl(String fileName) throws Exception {
URL resUrl = getClass().getClassLoader().getResource(fileName);
Expand Down Expand Up @@ -71,12 +49,16 @@ public void verifyStatementFromPemWithoutCrlCheck() throws Exception {
// NOTE: please replace with Root CA certificate if you already downloaded it
// somewhere.
X509Certificate trusted = cert_chain.get(cert_chain.size() - 1);
// because at time this code is written, CRL server is not setup, we turn of the
// CRL check
Exception exception = assertThrows(Exception.class,
() -> Verify.verify(authorityChain, cert_chain.get(0), trusted, false));
assertTrue("certificates should already expired, exception: " + exception.toString(),
exception.toString().contains("certificate expired") || exception.toString().contains("NotAfter"));

String fakeTimeEnv = System.getenv("FAKE_TIME_ACTIVE");
if (fakeTimeEnv != null && "1".equals(fakeTimeEnv)) {
Verify.verify(authorityChain, cert_chain.get(0), trusted, false);
} else {
Exception exception = assertThrows(Exception.class,
() -> Verify.verify(authorityChain, cert_chain.get(0), trusted, false));
assertTrue("certificates should already expired, exception: " + exception.toString(),
exception.toString().contains("certificate expired") || exception.toString().contains("NotAfter"));
}
}

/**
Expand All @@ -99,112 +81,15 @@ public void verifyStatementFromJsonWithoutCrlCheck() throws Exception {
// somewhere.
List<String> authorityChain = decodedResponse.getAuthorityChain();
X509Certificate trusted = Verify.readBase64EncodedCertificate(authorityChain.get(authorityChain.size() - 1));
// because at time this code is written, CRL server is not setup, we turn of the
// CRL check
Exception exception = assertThrows(Exception.class,
() -> Verify.verify(decodedResponse, trusted, false));
assertTrue("certificates should already expired, exception: " + exception.toString(),
exception.toString().contains("certificate expired") || exception.toString().contains("NotAfter"));
}

/**
* This test tests the full process:
* 1. Creating a RSA key.
* 2. Get RSA key's key attestation statement.
* 3. Verify key attestation statement.
*
* @throws Exception
*/
@Test
public void verifyStatementFullCheckOnlineAMER() throws Exception {
// Setup a SDKMS API client
String appApiKeyString = System.getenv(JAVA_CI_AMER_APP_API_KEY);
ApiClient client = new ApiClient();

// Set the path of the server to talk to.
client.setBasePath(FORTANIX_AMER_SAAS_SERVER_URL);

// This optionally enables verbose logging in the API library.
client.setDebugging(DEBUG);

// The default ApiClient (and its configured authorization) will be
// used for constructing the specific API objects, such as
// AuthenticationApi and SecurityObjectsApi.
Configuration.setDefaultApiClient(client);

// When authenticating as an application, the API Key functions as
// the entire HTTP basic auth token.
client.setBasicAuthString(appApiKeyString);

String bearerToken = null;
// Acquire a bearer token to use for other APIs.
try {
AuthResponse response = new AuthenticationApi().authorize();
bearerToken = response.getAccessToken();
if (DEBUG) {
System.err.printf("Received Bearer token %s\n", bearerToken);
}

// Configure the client library to use the bearer token.
ApiKeyAuth bearerAuth = (ApiKeyAuth) client.getAuthentication("bearerToken");
bearerAuth.setApiKey(bearerToken);
bearerAuth.setApiKeyPrefix("Bearer");
} catch (ApiException e) {
System.err.println("Unable to authenticate: " + e.getMessage());
throw e;
}

// Create a RSA key
SecurityObjectsApi securityObjectsApi = new SecurityObjectsApi();
SobjectRequest sobjectRequest = new SobjectRequest();
String newRsaKeyName = UUID.randomUUID().toString();
sobjectRequest.setName(newRsaKeyName);
sobjectRequest.setObjType(ObjectType.RSA);
sobjectRequest.setKeySize(2048);
System.out.println(String.format("Generating a new RSA key named '%s' ...", newRsaKeyName));
KeyObject newRsaKeyObject = securityObjectsApi.generateSecurityObject(sobjectRequest);
String keyId = newRsaKeyObject.getKid();
System.out.println(String.format("Generated a new RSA key named '%s' with key id: %s", newRsaKeyName, keyId));

// Get the the key attestation statement of the key just created
String path = "/crypto/v1/keys/key_attestation"; // API path
String method = "POST";
List<Pair> queryParams = new ArrayList<>(); // query parameters
Object body = String.format("{\"key\":{\"kid\":\"%s\"}}", keyId);
Map<String, String> headerParams = new HashMap<>(); // header parameters
Map<String, Object> formParams = new HashMap<>(); // form parameters
String accept = "application/json";
String contentType = "application/json";
String[] authNames = new String[] { "bearerToken" };
GenericType<KeyAttestationResponse> returnType = new GenericType<KeyAttestationResponse>() {
};
System.out.println(String.format("Getting key attestation statement through ..."));
KeyAttestationResponse keyAttestationResponse = client.invokeAPI(path, method, queryParams, body, headerParams,
formParams, accept, contentType, authNames, returnType);
System.out.println("Got key attestation statement");

// Logout SDKMS ApiClient
if (bearerToken != null) {
// It is a good idea to terminate the session when you are done
// using it. This minimizes the window of time in which an attacker
// could steal bearer token and use it.
try {
new AuthenticationApi().terminate();
} catch (ApiException e) {
System.err.println("Error logging out: " + e.getMessage());
}
bearerToken = null;
String fakeTimeEnv = System.getenv("FAKE_TIME_ACTIVE");
if (fakeTimeEnv != null && "1".equals(fakeTimeEnv)) {
Verify.verify(decodedResponse, trusted, false);
} else {
Exception exception = assertThrows(Exception.class,
() -> Verify.verify(decodedResponse, trusted, false));
assertTrue("certificates should already expired, exception: " + exception.toString(),
exception.toString().contains("certificate expired") || exception.toString().contains("NotAfter"));
}

// Download Fortanix Root CA certificate
System.out.println("Downloading Fortanix Attestation and Provisioning Root CA certificate form: "
+ Common.FORTANIX_ATTESTATION_AND_PROVISIONING_ROOT_CA_CERT_URL);
X509Certificate trustedRootCert = Common.getFortanixRootCaCertRemote(
Common.FORTANIX_ATTESTATION_AND_PROVISIONING_ROOT_CA_CERT_URL);
System.out.println(String.format("Downloaded Fortanix Attestation and Provisioning Root CA"));

// Do verification
Verify.verify(keyAttestationResponse, trustedRootCert, true);
}

}
Loading

0 comments on commit 934d904

Please sign in to comment.