[#25] Make ACA exception handling more descriptive

This commit is contained in:
apldev3 2018-10-11 22:03:25 -04:00
parent 6847c814af
commit 87be5a396b
16 changed files with 322 additions and 61 deletions

View File

@ -3,6 +3,9 @@ package hirs.attestationca;
import com.google.protobuf.ByteString; import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException; 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.AppraisalStatus;
import hirs.data.persist.BIOSComponentInfo; import hirs.data.persist.BIOSComponentInfo;
import hirs.data.persist.BaseboardComponentInfo; import hirs.data.persist.BaseboardComponentInfo;
@ -238,7 +241,8 @@ public abstract class AbstractAttestationCertificateAuthority
if (publicKeyModulus != null) { if (publicKeyModulus != null) {
ekPublicKey = assemblePublicKey(publicKeyModulus.toByteArray()); ekPublicKey = assemblePublicKey(publicKeyModulus.toByteArray());
} else { } 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) { } catch (IOException e) {
LOG.error("Could not retrieve the public key modulus from the EK cert"); LOG.error("Could not retrieve the public key modulus from the EK cert");
@ -277,7 +281,7 @@ public abstract class AbstractAttestationCertificateAuthority
if (deviceInfoReport == null) { if (deviceInfoReport == null) {
LOG.error("Failed to deserialize Device Info Report"); 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"); + "from Identity Request");
} }
@ -382,7 +386,8 @@ public abstract class AbstractAttestationCertificateAuthority
LOG.info("Got identity claim"); LOG.info("Got identity claim");
if (ArrayUtils.isEmpty(identityClaim)) { 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 // attempt to deserialize Protobuf IdentityClaim
@ -466,7 +471,7 @@ public abstract class AbstractAttestationCertificateAuthority
try { try {
request = ProvisionerTpm2.CertificateRequest.parseFrom(certificateRequest); request = ProvisionerTpm2.CertificateRequest.parseFrom(certificateRequest);
} catch (InvalidProtocolBufferException ipbe) { } catch (InvalidProtocolBufferException ipbe) {
throw new IdentityProcessingException( throw new CertificateProcessingException(
"Could not deserialize certificate request", ipbe); "Could not deserialize certificate request", ipbe);
} }
@ -512,7 +517,7 @@ public abstract class AbstractAttestationCertificateAuthority
} else { } else {
LOG.error("Could not process credential request. Invalid nonce provided: " LOG.error("Could not process credential request. Invalid nonce provided: "
+ request.getNonce().toString()); + 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) { RSAPublicKey parsePublicKey(final byte[] publicArea) {
int pubLen = publicArea.length; int pubLen = publicArea.length;
if (pubLen < RSA_MODULUS_LENGTH) { if (pubLen < RSA_MODULUS_LENGTH) {
throw new IdentityProcessingException( throw new IllegalArgumentException(
"EK or AK public data segment is not long enough"); "EK or AK public data segment is not long enough");
} }
// public data ends with 256 byte modulus // public data ends with 256 byte modulus
@ -661,7 +666,7 @@ public abstract class AbstractAttestationCertificateAuthority
if (deviceInfoReport == null) { if (deviceInfoReport == null) {
LOG.error("Failed to deserialize Device Info Report"); 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"); + "from Identity Claim");
} }
@ -882,7 +887,7 @@ public abstract class AbstractAttestationCertificateAuthority
KeyFactory keyFactory = KeyFactory.getInstance("RSA"); KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePublic(keySpec); return keyFactory.generatePublic(keySpec);
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) { } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new IdentityProcessingException( throw new UnexpectedServerException(
"Encountered unexpected error creating public key: " + e.getMessage(), e); "Encountered unexpected error creating public key: " + e.getMessage(), e);
} }
} }
@ -948,7 +953,7 @@ public abstract class AbstractAttestationCertificateAuthority
} catch (NoSuchAlgorithmException | IllegalBlockSizeException | NoSuchPaddingException } catch (NoSuchAlgorithmException | IllegalBlockSizeException | NoSuchPaddingException
| InvalidKeyException | BadPaddingException | InvalidKeyException | BadPaddingException
| InvalidAlgorithmParameterException e) { | InvalidAlgorithmParameterException e) {
throw new IdentityProcessingException( throw new CertificateProcessingException(
"Encountered error while generating ACA session key: " + e.getMessage(), e); "Encountered error while generating ACA session key: " + e.getMessage(), e);
} }
} }
@ -1004,7 +1009,7 @@ public abstract class AbstractAttestationCertificateAuthority
} catch (BadPaddingException | IllegalBlockSizeException | NoSuchAlgorithmException } catch (BadPaddingException | IllegalBlockSizeException | NoSuchAlgorithmException
| InvalidKeyException | InvalidAlgorithmParameterException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException | NoSuchPaddingException
| CertificateEncodingException e) { | CertificateEncodingException e) {
throw new IdentityProcessingException( throw new CertificateProcessingException(
"Encountered error while generating Identity Response: " + e.getMessage(), e); "Encountered error while generating Identity Response: " + e.getMessage(), e);
} }
} }
@ -1066,7 +1071,7 @@ public abstract class AbstractAttestationCertificateAuthority
.setProvider("BC").getCertificate(holder); .setProvider("BC").getCertificate(holder);
return certificate; return certificate;
} catch (IOException | OperatorCreationException | CertificateException e) { } catch (IOException | OperatorCreationException | CertificateException e) {
throw new IdentityProcessingException("Encountered error while generating " throw new CertificateProcessingException("Encountered error while generating "
+ "identity credential: " + e.getMessage(), e); + "identity credential: " + e.getMessage(), e);
} }
} }
@ -1159,7 +1164,8 @@ public abstract class AbstractAttestationCertificateAuthority
| InvalidKeyException | InvalidAlgorithmParameterException | InvalidKeyException | InvalidAlgorithmParameterException
| NoSuchPaddingException e) { | NoSuchPaddingException e) {
throw new IdentityProcessingException( 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. * 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 * @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 * @return the byte array representing the DER encoded certificate
*/ */
private byte[] getDerEncodedCertificate(final X509Certificate certificate) { private byte[] getDerEncodedCertificate(final X509Certificate certificate) {
@ -1426,7 +1432,7 @@ public abstract class AbstractAttestationCertificateAuthority
return certificate.getEncoded(); return certificate.getEncoded();
} catch (CertificateEncodingException e) { } catch (CertificateEncodingException e) {
LOG.error("Error converting certificate to ASN.1 DER Encoding.", e); LOG.error("Error converting certificate to ASN.1 DER Encoding.", e);
throw new IdentityProcessingException( throw new UnexpectedServerException(
"Encountered error while converting X509 Certificate: " "Encountered error while converting X509 Certificate: "
+ e.getMessage(), e); + e.getMessage(), e);
} }
@ -1441,7 +1447,7 @@ public abstract class AbstractAttestationCertificateAuthority
* @param endorsementCredential the endorsement credential used to generate the AC * @param endorsementCredential the endorsement credential used to generate the AC
* @param platformCredentials the platform credentials 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 * @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 * Certificate
*/ */
private void saveAttestationCertificate(final byte[] derEncodedAttestationCertificate, private void saveAttestationCertificate(final byte[] derEncodedAttestationCertificate,
@ -1456,7 +1462,7 @@ public abstract class AbstractAttestationCertificateAuthority
certificateManager.save(attCert); certificateManager.save(attCert);
} catch (Exception e) { } catch (Exception e) {
LOG.error("Error saving generated Attestation Certificate to database.", e); LOG.error("Error saving generated Attestation Certificate to database.", e);
throw new IdentityProcessingException( throw new CertificateProcessingException(
"Encountered error while storing Attestation Certificate: " "Encountered error while storing Attestation Certificate: "
+ e.getMessage(), e); + e.getMessage(), e);
} }

View File

@ -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;
}
}

View File

@ -54,8 +54,8 @@ import hirs.utils.LogConfigurationUtil;
@PropertySource(value = "file:/etc/hirs/aca/aca.properties", @PropertySource(value = "file:/etc/hirs/aca/aca.properties",
ignoreResourceNotFound = true) ignoreResourceNotFound = true)
}) })
@ComponentScan({ "hirs.attestationca", "hirs.attestationca.service", "hirs.validation", @ComponentScan({ "hirs.attestationca", "hirs.attestationca.service", "hirs.attestationca.rest",
"hirs.data.service" }) "hirs.validation", "hirs.data.service" })
@Import(HibernateConfiguration.class) @Import(HibernateConfiguration.class)
@EnableWebMvc @EnableWebMvc
public class AttestationCertificateAuthorityConfiguration extends WebMvcConfigurerAdapter { public class AttestationCertificateAuthorityConfiguration extends WebMvcConfigurerAdapter {

View File

@ -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);
}
}

View File

@ -1,8 +1,8 @@
package hirs.attestationca; package hirs.attestationca.exceptions;
/** /**
* Generic exception thrown while a {@link AttestationCertificateAuthority} is processing a newly * Generic exception thrown while a {@link hirs.attestationca.AttestationCertificateAuthority}
* submitted Identity. * is processing a newly submitted Identity.
*/ */
public class IdentityProcessingException extends RuntimeException { public class IdentityProcessingException extends RuntimeException {
/** /**

View File

@ -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);
}
}

View File

@ -0,0 +1,4 @@
/**
* Custom exceptions of the {@link hirs.attestationca.AttestationCertificateAuthority}.
*/
package hirs.attestationca.exceptions;

View File

@ -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<Object> 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<Object> 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<Object> 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);
}
}

View File

@ -1,18 +1,14 @@
package hirs.attestationca.rest; package hirs.attestationca.rest;
import hirs.attestationca.IdentityProcessingException;
import hirs.persist.DBManager; import hirs.persist.DBManager;
import hirs.persist.TPM2ProvisionerState; import hirs.persist.TPM2ProvisionerState;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; 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.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import java.security.PrivateKey; import java.security.PrivateKey;
@ -72,8 +68,7 @@ public class RestfulAttestationCertificateAuthority
@Override @Override
@ResponseBody @ResponseBody
@RequestMapping(value = "/identity-request/process", method = RequestMethod.POST, @RequestMapping(value = "/identity-request/process", method = RequestMethod.POST,
consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE, consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE)
produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public byte[] processIdentityRequest(@RequestBody final byte[] request) { public byte[] processIdentityRequest(@RequestBody final byte[] request) {
return super.processIdentityRequest(request); return super.processIdentityRequest(request);
} }
@ -87,8 +82,7 @@ public class RestfulAttestationCertificateAuthority
@ResponseBody @ResponseBody
@RequestMapping(value = "/identity-claim-tpm2/process", @RequestMapping(value = "/identity-claim-tpm2/process",
method = RequestMethod.POST, method = RequestMethod.POST,
consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE, consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE)
produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public byte[] processIdentityClaimTpm2(@RequestBody final byte[] request) { public byte[] processIdentityClaimTpm2(@RequestBody final byte[] request) {
return super.processIdentityClaimTpm2(request); return super.processIdentityClaimTpm2(request);
} }
@ -103,8 +97,7 @@ public class RestfulAttestationCertificateAuthority
@ResponseBody @ResponseBody
@RequestMapping(value = "/request-certificate-tpm2", @RequestMapping(value = "/request-certificate-tpm2",
method = RequestMethod.POST, method = RequestMethod.POST,
consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE, consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE)
produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public byte[] processCertificateRequest(@RequestBody final byte[] request) { public byte[] processCertificateRequest(@RequestBody final byte[] request) {
return super.processCertificateRequest(request); return super.processCertificateRequest(request);
} }
@ -118,28 +111,9 @@ public class RestfulAttestationCertificateAuthority
*/ */
@Override @Override
@ResponseBody @ResponseBody
@RequestMapping(value = "/public-key", method = RequestMethod.GET, @RequestMapping(value = "/public-key", method = RequestMethod.GET)
produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public byte[] getPublicKey() { public byte[] getPublicKey() {
return super.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;
}
} }

View File

@ -324,7 +324,7 @@ public class SupplyChainValidationServiceImpl implements SupplyChainValidationSe
* *
* @param credential the credential whose CA chain should be retrieved * @param credential the credential whose CA chain should be retrieved
* @return A keystore ontaining all relevant CA credentials to the given certificate's * @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) { public KeyStore getCaChain(final Certificate credential) {
KeyStore caKeyStore = null; KeyStore caKeyStore = null;

View File

@ -1,6 +1,7 @@
package hirs.attestationca; package hirs.attestationca;
import com.google.protobuf.ByteString; import com.google.protobuf.ByteString;
import hirs.attestationca.exceptions.IdentityProcessingException;
import hirs.utils.HexUtils; import hirs.utils.HexUtils;
import org.apache.commons.codec.binary.Hex; import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ArrayUtils;
@ -158,9 +159,9 @@ public class AbstractAttestationCertificateAuthorityTest {
/** /**
* Tests {@link AbstractAttestationCertificateAuthority#processIdentityClaimTpm2(byte[])} * 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() { public void testProcessIdentityClaimTpm2NullRequest() {
aca.processIdentityClaimTpm2(null); aca.processIdentityClaimTpm2(null);
} }

View File

@ -20,6 +20,7 @@ class RestfulClientProvisioner {
static const char * const PROP_FILE_LOC; static const char * const PROP_FILE_LOC;
static const char * const PROP_ACA_FQDN; static const char * const PROP_ACA_FQDN;
static const char * const PROP_ACA_PORT; static const char * const PROP_ACA_PORT;
static const char * const ACA_ERROR_FIELDNAME;
/** /**

View File

@ -47,6 +47,27 @@ namespace file_utils {
int readSize); int readSize);
} // namespace file_utils } // 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 { namespace string_utils {
/** /**
* Converts a binary string to a hex string. * 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 * Utility class that provides functions to parse information from tpm2_tools
* output. * output.
*/ */

View File

@ -19,6 +19,7 @@ using hirs::pb::IdentityClaimResponse;
using hirs::pb::CertificateRequest; using hirs::pb::CertificateRequest;
using hirs::pb::CertificateResponse; using hirs::pb::CertificateResponse;
using hirs::properties::Properties; using hirs::properties::Properties;
using hirs::json_utils::JSONFieldParser;
using hirs::string_utils::binaryToHex; using hirs::string_utils::binaryToHex;
using std::string; using std::string;
using std::stringstream; using std::stringstream;
@ -31,6 +32,8 @@ const char * const RestfulClientProvisioner::PROP_ACA_FQDN
= "ATTESTATION_CA_FQDN"; = "ATTESTATION_CA_FQDN";
const char * const RestfulClientProvisioner::PROP_ACA_PORT const char * const RestfulClientProvisioner::PROP_ACA_PORT
= "ATTESTATION_CA_PORT"; = "ATTESTATION_CA_PORT";
const char * const RestfulClientProvisioner::ACA_ERROR_FIELDNAME
= "error";
RestfulClientProvisioner::RestfulClientProvisioner() { RestfulClientProvisioner::RestfulClientProvisioner() {
Properties props(PROP_FILE_LOC); Properties props(PROP_FILE_LOC);
@ -66,7 +69,9 @@ string RestfulClientProvisioner::sendIdentityClaim(
+ "process"}, + "process"},
cpr::Body{identityClaimByteString}, cpr::Body{identityClaimByteString},
cpr::Header{{"Content-Type", cpr::Header{{"Content-Type",
"application/octet-stream"}}, "application/octet-stream"},
{"Accept",
"application/octet-stream, application/json"}},
cpr::VerifySsl{false}); cpr::VerifySsl{false});
// Check ACA response, should be 200 if successful // Check ACA response, should be 200 if successful
@ -91,8 +96,11 @@ string RestfulClientProvisioner::sendIdentityClaim(
} else { } else {
stringstream errormsg; stringstream errormsg;
errormsg << "Couldn't communicate with ACA server. " errormsg << "Error communicating with ACA server. "
<< "Received response code: " << to_string(r.status_code); << "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(), throw HirsRuntimeException(errormsg.str(),
"RestfulClientProvisioner::sendIdentityClaim"); "RestfulClientProvisioner::sendIdentityClaim");
} }
@ -110,7 +118,9 @@ string RestfulClientProvisioner::sendAttestationCertificateRequest(
+ "/request-certificate-tpm2"}, + "/request-certificate-tpm2"},
cpr::Body{certificateRequestByteString}, cpr::Body{certificateRequestByteString},
cpr::Header{{"Content-Type", cpr::Header{{"Content-Type",
"application/octet-stream"}}, "application/octet-stream"},
{"Accept",
"application/octet-stream, application/json"}},
cpr::VerifySsl{false}); cpr::VerifySsl{false});
// Check ACA response, should be 200 if successful // Check ACA response, should be 200 if successful
@ -131,9 +141,11 @@ string RestfulClientProvisioner::sendAttestationCertificateRequest(
} else { } else {
stringstream errormsg; stringstream errormsg;
errormsg << "Couldn't communicate with ACA server. " errormsg << "Error communicating with ACA server. "
<< "Received response code: " << to_string(r.status_code) << "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(), throw HirsRuntimeException(errormsg.str(),
"RestfulClientProvisioner::sendAttestationCertificateRequest"); "RestfulClientProvisioner::sendAttestationCertificateRequest");
} }

View File

@ -34,6 +34,25 @@ using hirs::exception::HirsRuntimeException;
namespace hirs { 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 { namespace file_utils {
/** /**

View File

@ -12,6 +12,7 @@
using hirs::file_utils::dirExists; using hirs::file_utils::dirExists;
using hirs::file_utils::fileExists; using hirs::file_utils::fileExists;
using hirs::json_utils::JSONFieldParser;
using hirs::string_utils::binaryToHex; using hirs::string_utils::binaryToHex;
using hirs::string_utils::contains; using hirs::string_utils::contains;
using hirs::string_utils::longToHex; using hirs::string_utils::longToHex;
@ -58,6 +59,57 @@ class UtilsTest : public :: testing::Test {
const char UtilsTest::kFileName[] = "bitsAndBytes"; 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) { TEST_F(UtilsTest, DirectoryExists) {
mkdir(kFileName, 0755); mkdir(kFileName, 0755);
ASSERT_TRUE(dirExists(kFileName)); ASSERT_TRUE(dirExists(kFileName));