Skip to content

Commit

Permalink
test: add online test
Browse files Browse the repository at this point in the history
Add online key attestation verification test
  • Loading branch information
Taowyoo committed Sep 26, 2023
1 parent 0c016e3 commit d278810
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,20 @@
import java.security.PublicKey;
import java.security.Security;
import java.security.cert.CertPath;
import java.security.cert.CertPathBuilder;
import java.security.cert.CertPathValidator;
import java.security.cert.CertPathValidatorException;
import java.security.cert.CertificateFactory;
import java.security.cert.PKIXBuilderParameters;
import java.security.cert.PKIXParameters;
import java.security.cert.PKIXRevocationChecker;
import java.security.cert.TrustAnchor;
import java.security.cert.X509CertSelector;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
Expand Down Expand Up @@ -52,7 +57,7 @@ public final class Verify {
*/
public static void verify(KeyAttestationResponse keyAttestationResponse,
X509Certificate trustRootCa, boolean verifyCrl) throws Exception {
LOGGER.info(String.format("Verifying KeyAttestationResponse: %s", keyAttestationResponse.toString()));
LOGGER.info("Verifying KeyAttestationResponse ...");
// skip verifying attestationStatement.format since it's been covered by json
// decoding
String attestationStatementStr = keyAttestationResponse.getAttestationStatement().getStatement();
Expand Down Expand Up @@ -86,9 +91,11 @@ public static void verify(List<X509Certificate> authorityChain, X509Certificate
// certificate, so we need to manually check 'Fortanix DSM Key Attestation' is
// correctly singed by 'Fortanix DSM SaaS Key Attestation Authority' certificate
verify_cert_signature(attestationStatement, authorityChain.get(0));
LOGGER.info("DONE");

LOGGER.info("Checking if root certificate in `authorityChain` matches given trusted root certificate");
check_root_cert_match(authorityChain.get(authorityChain.size() - 1), trustRootCa);
LOGGER.info("DONE");
// verify each signature on authority certificate chain is correctly signed by
// it's parent
try {
Expand Down Expand Up @@ -125,30 +132,40 @@ public static void verify(List<X509Certificate> authorityChain, X509Certificate
public static void verify_cert_chain_signature(List<X509Certificate> chain, X509Certificate trust_ca,
boolean verifyCrl)
throws Exception {
LOGGER.info("Checking if root certificate in `authorityChain` matches given trusted root certificate");
LOGGER.info("Checking `authorityChain` signatures");
if (chain.isEmpty()) {
throw new KeyAttestationStatementVerifyException("Empty certificate chain");
}
// Create CertPath
CertificateFactory factory = CertificateFactory.getInstance("X.509", "BC");
CertPath certPath = factory.generateCertPath(chain);
// Because root CA does not contain CRL distribution point extension,
// CertPathValidator will throw error when CRL revocation check is enabled.
// As a result, root CA need to be removed from cert chain.
CertPath certPath = factory.generateCertPath(chain.subList(0, chain.size() - 1));

// Set up TrustAnchor using the last certificate as the root certificate
TrustAnchor trustAnchor = new TrustAnchor(trust_ca, null);
Set<TrustAnchor> trustAnchors = Collections.singleton(trustAnchor);

// Set up PKIXParameters
PKIXParameters params = new PKIXParameters(trustAnchors);
params.setRevocationEnabled(verifyCrl);
CertPathBuilder cpb = CertPathBuilder.getInstance("PKIX");
PKIXRevocationChecker rc = (PKIXRevocationChecker) cpb.getRevocationChecker();
rc.setOptions(EnumSet.of(
PKIXRevocationChecker.Option.PREFER_CRLS, // prefer CLR over OCSP
PKIXRevocationChecker.Option.NO_FALLBACK)); // don't fall back to OCSP checking

PKIXBuilderParameters pkixParams = new PKIXBuilderParameters(trustAnchors, new X509CertSelector());
pkixParams.addCertPathChecker(rc);
pkixParams.setRevocationEnabled(verifyCrl);

// Validate CertPath
CertPathValidator validator = CertPathValidator.getInstance("PKIX", "BC");
CertPathValidator validator = CertPathValidator.getInstance("PKIX");
try {
validator.validate(certPath, params);
validator.validate(certPath, pkixParams);
} catch (CertPathValidatorException e) {
// Handle validation exception
throw new KeyAttestationStatementVerifyException("Certificate chain validation failed" + e.toString());
}
LOGGER.info("DONE");
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ protected void verifyDistPoint(String errStrPrefix, X509Certificate cert, String
errStrPrefix
+ " certificate cRLDistributionPoints extension wrong Name");
}
LOGGER.info("DONE");
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,28 @@
import org.junit.Ignore;
import org.junit.Test;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fortanix.keyattestationstatementverifier.types.json.KeyAttestationResponse;

import static org.junit.Assert.*;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.UUID;

import javax.net.ssl.HttpsURLConnection;

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";

Expand Down Expand Up @@ -88,7 +98,7 @@ public void verifyStatementFromJsonWithoutCrlCheck() throws Exception {
*/
@Ignore
@Test
public void verifyStatementFullCheck() throws Exception {
public void verifyStatementFullCheckExample() throws Exception {
String jsonPath = "Path to the KeyAttestationResponse json file";
Reader reader = new FileReader(jsonPath);
ObjectMapper objectMapper = new ObjectMapper();
Expand All @@ -101,4 +111,87 @@ public void verifyStatementFullCheck() throws Exception {

Verify.verify(decodedResponse, trustedRootCert, true);
}

/**
* This test tests the full process of creating a RSA key, get RSA key's key
* attestation statement and finally verify it.
*
* @throws Exception
*/
@Test
public void verifyStatementFullCheckOnlineAMER() throws Exception {
String appApiKeyString = System.getenv(JAVA_CI_AMER_APP_API_KEY);
String authString = "Basic " + appApiKeyString;
// create a key
String generateKeyUrl = FORTANIX_AMER_SAAS_SERVER_URL + "/crypto/v1/keys";
String newRsaKeyName = UUID.randomUUID().toString();
String generateKeyRequest = String.format(
"{\"name\":\"%s\",\"description\":\"\",\"obj_type\":\"RSA\",\"key_ops\":[\"APPMANAGEABLE\",\"SIGN\",\"VERIFY\"],\"key_size\":2048,\"pub_exponent\":65537,\"expirationDate\":null,\"enabled\":true,\"rsa\":{\"encryption_policy\":[{\"padding\":{\"OAEP\":{\"mgf\":{\"mgf1\":{}}}}}],\"signature_policy\":[{\"padding\":{\"PKCS1_V15\":{}}},{\"padding\":{\"PSS\":{\"mgf\":{\"mgf1\":{}}}}}]}}",
newRsaKeyName);
System.out.println(
String.format("Creating a new RSA key named '$s' through %s ...", newRsaKeyName, generateKeyUrl));
String generateKeyResponseString = sendHttpRequest(generateKeyUrl, "POST", generateKeyRequest, authString);
ObjectMapper keyObjectMapper = new ObjectMapper();
JsonNode jsonNode = keyObjectMapper.readTree(generateKeyResponseString);
String keyId = jsonNode.get("kid").asText();
System.out.println(String.format("Created a new RSA key named '$s' with key id: %s", newRsaKeyName, keyId));

String getKeyAttestationUrl = FORTANIX_AMER_SAAS_SERVER_URL + "/crypto/v1/keys/key_attestation";
String getKeyAttestationRequest = String.format("{\"key\":{\"kid\":\"%s\"}}", keyId);
System.out.println(String.format("Getting key attestation statement through $s ...", getKeyAttestationUrl));
String keyAttestationResponseString = sendHttpRequest(getKeyAttestationUrl, "POST", getKeyAttestationRequest, authString);
System.out.println(String.format("Got key attestation statement"));

// get the the key attestation statement of the key just created
ObjectMapper attestationObjectMapper = new ObjectMapper();
KeyAttestationResponse decodedResponse = attestationObjectMapper.readValue(keyAttestationResponseString, KeyAttestationResponse.class);

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"));

Verify.verify(decodedResponse, trustedRootCert, true);
}

public static String sendHttpRequest(String url, String method, String body, String authorization)
throws IOException {
URL apiUrl = new URL(url);
HttpsURLConnection connection = (HttpsURLConnection) apiUrl.openConnection();

try {
// Set the HTTP method
connection.setRequestMethod(method);

// Set the Authorization header if provided
if (authorization != null && !authorization.isEmpty()) {
connection.setRequestProperty("Authorization", authorization);
}

// Handle request body if provided
if (body != null && !body.isEmpty()) {
connection.setDoOutput(true);
connection.getOutputStream().write(body.getBytes("UTF-8"));
}

int responseCode = connection.getResponseCode();
if (responseCode >= 200 && responseCode < 300) {
// Successful response, read and return the response body
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
return response.toString();
} else {
// Non-2XX response, throw an exception
throw new IOException("HTTP request failed with response code: " + responseCode);
}
} finally {
connection.disconnect();
}
}
}

0 comments on commit d278810

Please sign in to comment.