From 87be5a396b6ad3a55d117599637cb3c0d52f1a8b Mon Sep 17 00:00:00 2001 From: apldev3 Date: Thu, 11 Oct 2018 22:03:25 -0400 Subject: [PATCH] [#25] Make ACA exception handling more descriptive --- ...stractAttestationCertificateAuthority.java | 38 +++++----- .../java/hirs/attestationca/AcaRestError.java | 45 ++++++++++++ ...tionCertificateAuthorityConfiguration.java | 4 +- .../CertificateProcessingException.java | 27 +++++++ .../IdentityProcessingException.java | 6 +- .../exceptions/UnexpectedServerException.java | 27 +++++++ .../exceptions/package-info.java | 4 ++ ...nCertificateAuthorityExceptionHandler.java | 72 +++++++++++++++++++ ...estfulAttestationCertificateAuthority.java | 34 ++------- .../SupplyChainValidationServiceImpl.java | 2 +- ...ctAttestationCertificateAuthorityTest.java | 5 +- .../include/RestfulClientProvisioner.h | 1 + HIRS_ProvisionerTPM2/include/Utils.h | 23 +++++- .../src/RestfulClientProvisioner.cpp | 24 +++++-- HIRS_ProvisionerTPM2/src/Utils.cpp | 19 +++++ HIRS_ProvisionerTPM2/test/Utils_Test.cpp | 52 ++++++++++++++ 16 files changed, 322 insertions(+), 61 deletions(-) create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/AcaRestError.java create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/exceptions/CertificateProcessingException.java rename HIRS_AttestationCA/src/main/java/hirs/attestationca/{ => exceptions}/IdentityProcessingException.java (79%) create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/exceptions/UnexpectedServerException.java create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/exceptions/package-info.java create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/rest/AttestationCertificateAuthorityExceptionHandler.java diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/AbstractAttestationCertificateAuthority.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/AbstractAttestationCertificateAuthority.java index 8b0c330a..d97ef467 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/AbstractAttestationCertificateAuthority.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/AbstractAttestationCertificateAuthority.java @@ -3,6 +3,9 @@ package hirs.attestationca; import com.google.protobuf.ByteString; import com.google.protobuf.InvalidProtocolBufferException; +import hirs.attestationca.exceptions.CertificateProcessingException; +import hirs.attestationca.exceptions.IdentityProcessingException; +import hirs.attestationca.exceptions.UnexpectedServerException; import hirs.data.persist.AppraisalStatus; import hirs.data.persist.BIOSComponentInfo; import hirs.data.persist.BaseboardComponentInfo; @@ -238,7 +241,8 @@ public abstract class AbstractAttestationCertificateAuthority if (publicKeyModulus != null) { ekPublicKey = assemblePublicKey(publicKeyModulus.toByteArray()); } else { - throw new IllegalArgumentException("TPM 1.2 Provisioning requires RSA EKC"); + throw new IdentityProcessingException("TPM 1.2 Provisioning requires EK " + + "Credentials to be created with RSA"); } } catch (IOException e) { LOG.error("Could not retrieve the public key modulus from the EK cert"); @@ -277,7 +281,7 @@ public abstract class AbstractAttestationCertificateAuthority if (deviceInfoReport == null) { LOG.error("Failed to deserialize Device Info Report"); - throw new IllegalArgumentException("Device Info Report failed to deserialize " + throw new IdentityProcessingException("Device Info Report failed to deserialize " + "from Identity Request"); } @@ -382,7 +386,8 @@ public abstract class AbstractAttestationCertificateAuthority LOG.info("Got identity claim"); if (ArrayUtils.isEmpty(identityClaim)) { - throw new IllegalArgumentException("identityClaim cannot be null or empty"); + LOG.error("Identity claim empty throwing exception."); + throw new IdentityProcessingException("identityClaim cannot be null or empty"); } // attempt to deserialize Protobuf IdentityClaim @@ -466,7 +471,7 @@ public abstract class AbstractAttestationCertificateAuthority try { request = ProvisionerTpm2.CertificateRequest.parseFrom(certificateRequest); } catch (InvalidProtocolBufferException ipbe) { - throw new IdentityProcessingException( + throw new CertificateProcessingException( "Could not deserialize certificate request", ipbe); } @@ -512,7 +517,7 @@ public abstract class AbstractAttestationCertificateAuthority } else { LOG.error("Could not process credential request. Invalid nonce provided: " + request.getNonce().toString()); - throw new IdentityProcessingException("Invalid nonce given in request"); + throw new CertificateProcessingException("Invalid nonce given in request"); } } @@ -524,7 +529,7 @@ public abstract class AbstractAttestationCertificateAuthority RSAPublicKey parsePublicKey(final byte[] publicArea) { int pubLen = publicArea.length; if (pubLen < RSA_MODULUS_LENGTH) { - throw new IdentityProcessingException( + throw new IllegalArgumentException( "EK or AK public data segment is not long enough"); } // public data ends with 256 byte modulus @@ -661,7 +666,7 @@ public abstract class AbstractAttestationCertificateAuthority if (deviceInfoReport == null) { LOG.error("Failed to deserialize Device Info Report"); - throw new IllegalArgumentException("Device Info Report failed to deserialize " + throw new IdentityProcessingException("Device Info Report failed to deserialize " + "from Identity Claim"); } @@ -882,7 +887,7 @@ public abstract class AbstractAttestationCertificateAuthority KeyFactory keyFactory = KeyFactory.getInstance("RSA"); return keyFactory.generatePublic(keySpec); } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { - throw new IdentityProcessingException( + throw new UnexpectedServerException( "Encountered unexpected error creating public key: " + e.getMessage(), e); } } @@ -948,7 +953,7 @@ public abstract class AbstractAttestationCertificateAuthority } catch (NoSuchAlgorithmException | IllegalBlockSizeException | NoSuchPaddingException | InvalidKeyException | BadPaddingException | InvalidAlgorithmParameterException e) { - throw new IdentityProcessingException( + throw new CertificateProcessingException( "Encountered error while generating ACA session key: " + e.getMessage(), e); } } @@ -1004,7 +1009,7 @@ public abstract class AbstractAttestationCertificateAuthority } catch (BadPaddingException | IllegalBlockSizeException | NoSuchAlgorithmException | InvalidKeyException | InvalidAlgorithmParameterException | NoSuchPaddingException | CertificateEncodingException e) { - throw new IdentityProcessingException( + throw new CertificateProcessingException( "Encountered error while generating Identity Response: " + e.getMessage(), e); } } @@ -1066,7 +1071,7 @@ public abstract class AbstractAttestationCertificateAuthority .setProvider("BC").getCertificate(holder); return certificate; } catch (IOException | OperatorCreationException | CertificateException e) { - throw new IdentityProcessingException("Encountered error while generating " + throw new CertificateProcessingException("Encountered error while generating " + "identity credential: " + e.getMessage(), e); } } @@ -1159,7 +1164,8 @@ public abstract class AbstractAttestationCertificateAuthority | InvalidKeyException | InvalidAlgorithmParameterException | NoSuchPaddingException e) { throw new IdentityProcessingException( - "Encountered error while making credential: " + e.getMessage(), e); + "Encountered error while making the identity claim challenge: " + + e.getMessage(), e); } } @@ -1418,7 +1424,7 @@ public abstract class AbstractAttestationCertificateAuthority * Helper method to extract a DER encoded ASN.1 certificate from an X509 certificate. * * @param certificate the X509 certificate to be converted to DER encoding - * @throws {@link IdentityProcessingException} if error occurs during encoding retrieval + * @throws {@link UnexpectedServerException} if error occurs during encoding retrieval * @return the byte array representing the DER encoded certificate */ private byte[] getDerEncodedCertificate(final X509Certificate certificate) { @@ -1426,7 +1432,7 @@ public abstract class AbstractAttestationCertificateAuthority return certificate.getEncoded(); } catch (CertificateEncodingException e) { LOG.error("Error converting certificate to ASN.1 DER Encoding.", e); - throw new IdentityProcessingException( + throw new UnexpectedServerException( "Encountered error while converting X509 Certificate: " + e.getMessage(), e); } @@ -1441,7 +1447,7 @@ public abstract class AbstractAttestationCertificateAuthority * @param endorsementCredential the endorsement credential used to generate the AC * @param platformCredentials the platform credentials used to generate the AC * @param device the device to which the attestation certificate is tied - * @throws {@link IdentityProcessingException} if error occurs in persisting the Attestation + * @throws {@link CertificateProcessingException} if error occurs in persisting the Attestation * Certificate */ private void saveAttestationCertificate(final byte[] derEncodedAttestationCertificate, @@ -1456,7 +1462,7 @@ public abstract class AbstractAttestationCertificateAuthority certificateManager.save(attCert); } catch (Exception e) { LOG.error("Error saving generated Attestation Certificate to database.", e); - throw new IdentityProcessingException( + throw new CertificateProcessingException( "Encountered error while storing Attestation Certificate: " + e.getMessage(), e); } diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/AcaRestError.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/AcaRestError.java new file mode 100644 index 00000000..35ed374e --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/AcaRestError.java @@ -0,0 +1,45 @@ +package hirs.attestationca; + +/** + * A simple POJO that will provide a clean error message to clients making + * REST requests to the ACA. It is to be serialized to JSON for the return message. + */ +public class AcaRestError { + + private String error; + + /** + * Basic constructor necessary for Jackson JSON serialization to work properly. + */ + public AcaRestError() { + // Don't remove this constructor as it's required for JSON mapping + } + + /** + * Parameterized constructor for creating this class normally. + * + * @param error the error message to store in this object + */ + public AcaRestError(final String error) { + this.error = error; + } + + /** + * Simple getter to get the error message stored in this object. + * + * @return the error message + */ + public String getError() { + return error; + } + + /** + * Simple setter to get the error message stored in this object. + * + * @param error the new error message to store in this object + */ + public void setError(final String error) { + this.error = error; + } + +} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/configuration/AttestationCertificateAuthorityConfiguration.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/configuration/AttestationCertificateAuthorityConfiguration.java index 550502a4..27708ecb 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/configuration/AttestationCertificateAuthorityConfiguration.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/configuration/AttestationCertificateAuthorityConfiguration.java @@ -54,8 +54,8 @@ import hirs.utils.LogConfigurationUtil; @PropertySource(value = "file:/etc/hirs/aca/aca.properties", ignoreResourceNotFound = true) }) -@ComponentScan({ "hirs.attestationca", "hirs.attestationca.service", "hirs.validation", - "hirs.data.service" }) +@ComponentScan({ "hirs.attestationca", "hirs.attestationca.service", "hirs.attestationca.rest", + "hirs.validation", "hirs.data.service" }) @Import(HibernateConfiguration.class) @EnableWebMvc public class AttestationCertificateAuthorityConfiguration extends WebMvcConfigurerAdapter { diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/exceptions/CertificateProcessingException.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/exceptions/CertificateProcessingException.java new file mode 100644 index 00000000..28b38f36 --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/exceptions/CertificateProcessingException.java @@ -0,0 +1,27 @@ +package hirs.attestationca.exceptions; + +/** + * Generic exception thrown while a {@link hirs.attestationca.AttestationCertificateAuthority} + * is processing a newly created Attestation Certificate for a validated identity. + */ +public class CertificateProcessingException extends RuntimeException { + /** + * Constructs a generic instance of this exception using the specified reason. + * + * @param reason for the exception + */ + public CertificateProcessingException(final String reason) { + super(reason); + } + + /** + * Constructs a instance of this exception with the specified reason and backing root + * exception. + * + * @param reason for this exception + * @param rootException causing this exception + */ + public CertificateProcessingException(final String reason, final Throwable rootException) { + super(reason, rootException); + } +} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/IdentityProcessingException.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/exceptions/IdentityProcessingException.java similarity index 79% rename from HIRS_AttestationCA/src/main/java/hirs/attestationca/IdentityProcessingException.java rename to HIRS_AttestationCA/src/main/java/hirs/attestationca/exceptions/IdentityProcessingException.java index f455992a..bf2855f6 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/IdentityProcessingException.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/exceptions/IdentityProcessingException.java @@ -1,8 +1,8 @@ -package hirs.attestationca; +package hirs.attestationca.exceptions; /** - * Generic exception thrown while a {@link AttestationCertificateAuthority} is processing a newly - * submitted Identity. + * Generic exception thrown while a {@link hirs.attestationca.AttestationCertificateAuthority} + * is processing a newly submitted Identity. */ public class IdentityProcessingException extends RuntimeException { /** diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/exceptions/UnexpectedServerException.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/exceptions/UnexpectedServerException.java new file mode 100644 index 00000000..078c9904 --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/exceptions/UnexpectedServerException.java @@ -0,0 +1,27 @@ +package hirs.attestationca.exceptions; + +/** + * Generic exception thrown when a {@link hirs.attestationca.AttestationCertificateAuthority} + * encounters an unexpected condition that can't be handled. + */ +public class UnexpectedServerException extends RuntimeException { + /** + * Constructs a generic instance of this exception using the specified reason. + * + * @param reason for the exception + */ + public UnexpectedServerException(final String reason) { + super(reason); + } + + /** + * Constructs a instance of this exception with the specified reason and backing root + * exception. + * + * @param reason for this exception + * @param rootException causing this exception + */ + public UnexpectedServerException(final String reason, final Throwable rootException) { + super(reason, rootException); + } +} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/exceptions/package-info.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/exceptions/package-info.java new file mode 100644 index 00000000..b45887aa --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/exceptions/package-info.java @@ -0,0 +1,4 @@ +/** + * Custom exceptions of the {@link hirs.attestationca.AttestationCertificateAuthority}. + */ +package hirs.attestationca.exceptions; diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/rest/AttestationCertificateAuthorityExceptionHandler.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/rest/AttestationCertificateAuthorityExceptionHandler.java new file mode 100644 index 00000000..26253ffe --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/rest/AttestationCertificateAuthorityExceptionHandler.java @@ -0,0 +1,72 @@ +package hirs.attestationca.rest; + +import hirs.attestationca.AcaRestError; +import hirs.attestationca.exceptions.CertificateProcessingException; +import hirs.attestationca.exceptions.IdentityProcessingException; +import hirs.attestationca.exceptions.UnexpectedServerException; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.context.request.WebRequest; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + +/** + * Handle processing of exceptions for ACA REST API. + */ +@ControllerAdvice +public class AttestationCertificateAuthorityExceptionHandler + extends ResponseEntityExceptionHandler { + + private static final Logger LOGGER = LogManager.getLogger( + AttestationCertificateAuthorityExceptionHandler.class); + + /** + * Method to handle errors of the type {@link CertificateProcessingException}, + * {@link IdentityProcessingException}, and {@link IllegalArgumentException} + * that are thrown when performing a RESTful operation. + * + * @param ex exception that was thrown + * @param request the web request that started the RESTful operation + * @return the response entity that will form the message returned to the client + */ + @ExceptionHandler({ CertificateProcessingException.class, IdentityProcessingException.class, + IllegalArgumentException.class }) + public final ResponseEntity handleExpectedExceptions(final Exception ex, + final WebRequest request) { + LOGGER.error(String.format("The ACA has encountered an expected exception: %s", + ex.getMessage()), ex); + return handleGeneralException(ex, HttpStatus.BAD_REQUEST, request); + } + + /** + * Method to handle errors of the type {@link IllegalStateException} and + * {@link UnexpectedServerException} that are thrown when performing a RESTful operation. + * + * @param ex exception that was thrown + * @param request the web request that started the RESTful operation + * @return the response entity that will form the message returned to the client + */ + @ExceptionHandler({ IllegalStateException.class, UnexpectedServerException.class }) + public final ResponseEntity handleUnexpectedExceptions(final Exception ex, + final WebRequest request) { + LOGGER.error(String.format("The ACA has encountered an unexpected exception: %s", + ex.getMessage()), ex); + return handleGeneralException(ex, HttpStatus.INTERNAL_SERVER_ERROR, request); + } + + private ResponseEntity handleGeneralException(final Exception ex, + final HttpStatus responseStatus, + final WebRequest request) { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + + return handleExceptionInternal(ex, new AcaRestError(ex.getMessage()), + headers, responseStatus, request); + } + +} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/rest/RestfulAttestationCertificateAuthority.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/rest/RestfulAttestationCertificateAuthority.java index 10ba5ef1..f8dc39fa 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/rest/RestfulAttestationCertificateAuthority.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/rest/RestfulAttestationCertificateAuthority.java @@ -1,18 +1,14 @@ package hirs.attestationca.rest; -import hirs.attestationca.IdentityProcessingException; import hirs.persist.DBManager; import hirs.persist.TPM2ProvisionerState; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import java.security.PrivateKey; @@ -72,8 +68,7 @@ public class RestfulAttestationCertificateAuthority @Override @ResponseBody @RequestMapping(value = "/identity-request/process", method = RequestMethod.POST, - consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE, - produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) + consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE) public byte[] processIdentityRequest(@RequestBody final byte[] request) { return super.processIdentityRequest(request); } @@ -87,8 +82,7 @@ public class RestfulAttestationCertificateAuthority @ResponseBody @RequestMapping(value = "/identity-claim-tpm2/process", method = RequestMethod.POST, - consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE, - produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) + consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE) public byte[] processIdentityClaimTpm2(@RequestBody final byte[] request) { return super.processIdentityClaimTpm2(request); } @@ -103,8 +97,7 @@ public class RestfulAttestationCertificateAuthority @ResponseBody @RequestMapping(value = "/request-certificate-tpm2", method = RequestMethod.POST, - consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE, - produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) + consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE) public byte[] processCertificateRequest(@RequestBody final byte[] request) { return super.processCertificateRequest(request); } @@ -118,28 +111,9 @@ public class RestfulAttestationCertificateAuthority */ @Override @ResponseBody - @RequestMapping(value = "/public-key", method = RequestMethod.GET, - produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) + @RequestMapping(value = "/public-key", method = RequestMethod.GET) public byte[] getPublicKey() { return super.getPublicKey(); } - /** - * Handle processing of exceptions for ACA REST API. - * @param e exception thrown during invocation of ACA REST API - * @return exception thrown during invocation of ACA REST API - */ - @ExceptionHandler - @ResponseBody - @ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR) - public Exception handleException(final Exception e) { - if (e instanceof IdentityProcessingException) { - LOG.error("Processing exception while provisioning", e.getMessage(), e); - } else { - LOG.error(String.format("Encountered unexpected error while processing identity " - + "claim: %s", e.getMessage()), e); - } - return e; - } - } diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/service/SupplyChainValidationServiceImpl.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/service/SupplyChainValidationServiceImpl.java index 58c532a3..26a9fddc 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/service/SupplyChainValidationServiceImpl.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/service/SupplyChainValidationServiceImpl.java @@ -324,7 +324,7 @@ public class SupplyChainValidationServiceImpl implements SupplyChainValidationSe * * @param credential the credential whose CA chain should be retrieved * @return A keystore ontaining all relevant CA credentials to the given certificate's - * organization + * organization or null if the keystore can't be assembled */ public KeyStore getCaChain(final Certificate credential) { KeyStore caKeyStore = null; diff --git a/HIRS_AttestationCA/src/test/java/hirs/attestationca/AbstractAttestationCertificateAuthorityTest.java b/HIRS_AttestationCA/src/test/java/hirs/attestationca/AbstractAttestationCertificateAuthorityTest.java index 61b18e5c..112463ce 100644 --- a/HIRS_AttestationCA/src/test/java/hirs/attestationca/AbstractAttestationCertificateAuthorityTest.java +++ b/HIRS_AttestationCA/src/test/java/hirs/attestationca/AbstractAttestationCertificateAuthorityTest.java @@ -1,6 +1,7 @@ package hirs.attestationca; import com.google.protobuf.ByteString; +import hirs.attestationca.exceptions.IdentityProcessingException; import hirs.utils.HexUtils; import org.apache.commons.codec.binary.Hex; import org.apache.commons.lang3.ArrayUtils; @@ -158,9 +159,9 @@ public class AbstractAttestationCertificateAuthorityTest { /** * Tests {@link AbstractAttestationCertificateAuthority#processIdentityClaimTpm2(byte[])} - * where the byte array is null. Expects an illegal argument exception to be thrown. + * where the byte array is null. Expects an identity processing exception to be thrown. */ - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expectedExceptions = IdentityProcessingException.class) public void testProcessIdentityClaimTpm2NullRequest() { aca.processIdentityClaimTpm2(null); } diff --git a/HIRS_ProvisionerTPM2/include/RestfulClientProvisioner.h b/HIRS_ProvisionerTPM2/include/RestfulClientProvisioner.h index 8c39e127..f5a389f4 100644 --- a/HIRS_ProvisionerTPM2/include/RestfulClientProvisioner.h +++ b/HIRS_ProvisionerTPM2/include/RestfulClientProvisioner.h @@ -20,6 +20,7 @@ class RestfulClientProvisioner { static const char * const PROP_FILE_LOC; static const char * const PROP_ACA_FQDN; static const char * const PROP_ACA_PORT; + static const char * const ACA_ERROR_FIELDNAME; /** diff --git a/HIRS_ProvisionerTPM2/include/Utils.h b/HIRS_ProvisionerTPM2/include/Utils.h index ac2e9db2..c631ed26 100644 --- a/HIRS_ProvisionerTPM2/include/Utils.h +++ b/HIRS_ProvisionerTPM2/include/Utils.h @@ -47,6 +47,27 @@ namespace file_utils { int readSize); } // namespace file_utils +namespace json_utils { + +/** +* Utility class that provides functions to parse information from ACA +* output. +*/ +class JSONFieldParser { + public: + /** + * Parses the target field of the provided JSON object as a string. + * + * @param jsonObject the JSON-formatted object + * @param jsonFieldName the name of the field to parse from the JSON object + * @return the value of the target field in the JSON object + */ + static std::string parseJsonStringField(const std::string& jsonObject, + const std::string& jsonFieldName); +}; + +} // namespace json_utils + namespace string_utils { /** * Converts a binary string to a hex string. @@ -165,7 +186,7 @@ class Tpm2ToolsVersionChecker { }; - /** +/** * Utility class that provides functions to parse information from tpm2_tools * output. */ diff --git a/HIRS_ProvisionerTPM2/src/RestfulClientProvisioner.cpp b/HIRS_ProvisionerTPM2/src/RestfulClientProvisioner.cpp index 62124055..4969d146 100644 --- a/HIRS_ProvisionerTPM2/src/RestfulClientProvisioner.cpp +++ b/HIRS_ProvisionerTPM2/src/RestfulClientProvisioner.cpp @@ -19,6 +19,7 @@ using hirs::pb::IdentityClaimResponse; using hirs::pb::CertificateRequest; using hirs::pb::CertificateResponse; using hirs::properties::Properties; +using hirs::json_utils::JSONFieldParser; using hirs::string_utils::binaryToHex; using std::string; using std::stringstream; @@ -31,6 +32,8 @@ const char * const RestfulClientProvisioner::PROP_ACA_FQDN = "ATTESTATION_CA_FQDN"; const char * const RestfulClientProvisioner::PROP_ACA_PORT = "ATTESTATION_CA_PORT"; +const char * const RestfulClientProvisioner::ACA_ERROR_FIELDNAME + = "error"; RestfulClientProvisioner::RestfulClientProvisioner() { Properties props(PROP_FILE_LOC); @@ -66,7 +69,9 @@ string RestfulClientProvisioner::sendIdentityClaim( + "process"}, cpr::Body{identityClaimByteString}, cpr::Header{{"Content-Type", - "application/octet-stream"}}, + "application/octet-stream"}, + {"Accept", + "application/octet-stream, application/json"}}, cpr::VerifySsl{false}); // Check ACA response, should be 200 if successful @@ -91,8 +96,11 @@ string RestfulClientProvisioner::sendIdentityClaim( } else { stringstream errormsg; - errormsg << "Couldn't communicate with ACA server. " - << "Received response code: " << to_string(r.status_code); + errormsg << "Error communicating with ACA server. " + << "Received response code: " << to_string(r.status_code) + << "\n\nError message fom ACA was: " + << JSONFieldParser::parseJsonStringField(r.text, + ACA_ERROR_FIELDNAME); throw HirsRuntimeException(errormsg.str(), "RestfulClientProvisioner::sendIdentityClaim"); } @@ -110,7 +118,9 @@ string RestfulClientProvisioner::sendAttestationCertificateRequest( + "/request-certificate-tpm2"}, cpr::Body{certificateRequestByteString}, cpr::Header{{"Content-Type", - "application/octet-stream"}}, + "application/octet-stream"}, + {"Accept", + "application/octet-stream, application/json"}}, cpr::VerifySsl{false}); // Check ACA response, should be 200 if successful @@ -131,9 +141,11 @@ string RestfulClientProvisioner::sendAttestationCertificateRequest( } else { stringstream errormsg; - errormsg << "Couldn't communicate with ACA server. " + errormsg << "Error communicating with ACA server. " << "Received response code: " << to_string(r.status_code) - << "\n\nWith message body: " << r.text; + << "\n\nError message from ACA was: " + << JSONFieldParser::parseJsonStringField(r.text, + ACA_ERROR_FIELDNAME); throw HirsRuntimeException(errormsg.str(), "RestfulClientProvisioner::sendAttestationCertificateRequest"); } diff --git a/HIRS_ProvisionerTPM2/src/Utils.cpp b/HIRS_ProvisionerTPM2/src/Utils.cpp index 5c27ee3d..d2fb691d 100644 --- a/HIRS_ProvisionerTPM2/src/Utils.cpp +++ b/HIRS_ProvisionerTPM2/src/Utils.cpp @@ -34,6 +34,25 @@ using hirs::exception::HirsRuntimeException; namespace hirs { +namespace json_utils { + +string JSONFieldParser::parseJsonStringField(const std::string &jsonObject, + const std::string &jsonFieldName) { + stringstream regexPatternStream; + regexPatternStream << "(?i)\\\"" + << jsonFieldName + << "\\\"\\s*:\\s*\\\"(.*)\\\""; + + string value; + if (RE2::PartialMatch(jsonObject, regexPatternStream.str(), &value)) { + return value; + } else { + return ""; + } +} + +} // namespace json_utils + namespace file_utils { /** diff --git a/HIRS_ProvisionerTPM2/test/Utils_Test.cpp b/HIRS_ProvisionerTPM2/test/Utils_Test.cpp index 1bbcdb95..ef9b46b0 100644 --- a/HIRS_ProvisionerTPM2/test/Utils_Test.cpp +++ b/HIRS_ProvisionerTPM2/test/Utils_Test.cpp @@ -12,6 +12,7 @@ using hirs::file_utils::dirExists; using hirs::file_utils::fileExists; +using hirs::json_utils::JSONFieldParser; using hirs::string_utils::binaryToHex; using hirs::string_utils::contains; using hirs::string_utils::longToHex; @@ -58,6 +59,57 @@ class UtilsTest : public :: testing::Test { const char UtilsTest::kFileName[] = "bitsAndBytes"; +TEST_F(UtilsTest, ParseJsonFieldSuccess) { + stringstream jsonObject; + jsonObject << R"({"error":"identityClaim cannot be null or empty"})"; + + string errorMessage = JSONFieldParser::parseJsonStringField( + jsonObject.str(), "error"); + string expectedOutput = "identityClaim cannot be null or empty"; + ASSERT_EQ(expectedOutput, errorMessage); +} + +TEST_F(UtilsTest, ParseJsonFieldSuccessCaseInsensitive) { + stringstream jsonObject; + jsonObject << R"({"ERROR":"identityClaim cannot be null or empty"})"; + + string errorMessage = JSONFieldParser::parseJsonStringField( + jsonObject.str(), "error"); + string expectedOutput = "identityClaim cannot be null or empty"; + ASSERT_EQ(expectedOutput, errorMessage); +} + +TEST_F(UtilsTest, ParseJsonFieldSuccessWhiteSpaces) { + stringstream jsonObject; + jsonObject << R"({"error" : "identityClaim cannot be null or empty"})"; + + string errorMessage = JSONFieldParser::parseJsonStringField( + jsonObject.str(), "error"); + string expectedOutput = "identityClaim cannot be null or empty"; + ASSERT_EQ(expectedOutput, errorMessage); +} + +TEST_F(UtilsTest, ParseJsonFieldSuccessMultiJsonFields) { + stringstream jsonObject; + jsonObject << R"({"error" : "identityClaim cannot be null or empty",)" + << "\n" << R"("endpoint":"url.com"})"; + + string errorMessage = JSONFieldParser::parseJsonStringField( + jsonObject.str(), "error"); + string expectedOutput = "identityClaim cannot be null or empty"; + ASSERT_EQ(expectedOutput, errorMessage); +} + +TEST_F(UtilsTest, ParseJsonFieldInvalidJson) { + stringstream jsonObject; + jsonObject << R"({error:"identityClaim cannot be null or empty"})"; + + string errorMessage = JSONFieldParser::parseJsonStringField( + jsonObject.str(), "error"); + string expectedOutput = ""; + ASSERT_EQ(expectedOutput, errorMessage); +} + TEST_F(UtilsTest, DirectoryExists) { mkdir(kFileName, 0755); ASSERT_TRUE(dirExists(kFileName));