mirror of
https://github.com/nsacyber/HIRS.git
synced 2025-01-19 03:06:41 +00:00
Resolved an issue with createTPMInfo. Created a new static class to
contain most of the generic generation code instead of the standard provision handler classes.
This commit is contained in:
parent
6a2c5d246b
commit
d2963ef99a
@ -1,7 +1,6 @@
|
||||
package hirs.attestationca.persist.provision;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
import hirs.attestationca.configuration.provisionerTpm2.ProvisionerTpm2;
|
||||
import hirs.attestationca.persist.entity.manager.CertificateRepository;
|
||||
import hirs.attestationca.persist.entity.manager.PolicyRepository;
|
||||
@ -11,19 +10,14 @@ import hirs.attestationca.persist.entity.userdefined.PolicySettings;
|
||||
import hirs.attestationca.persist.entity.userdefined.certificate.EndorsementCredential;
|
||||
import hirs.attestationca.persist.entity.userdefined.certificate.IssuedAttestationCertificate;
|
||||
import hirs.attestationca.persist.entity.userdefined.certificate.PlatformCredential;
|
||||
import hirs.attestationca.persist.entity.userdefined.info.TPMInfo;
|
||||
import hirs.attestationca.persist.exceptions.CertificateProcessingException;
|
||||
import hirs.attestationca.persist.exceptions.IdentityProcessingException;
|
||||
import hirs.attestationca.persist.exceptions.UnexpectedServerException;
|
||||
import hirs.attestationca.persist.provision.helper.CredentialManagementHelper;
|
||||
import hirs.attestationca.persist.provision.helper.IssuedCertificateAttributeHelper;
|
||||
import hirs.utils.HexUtils;
|
||||
import hirs.utils.enums.DeviceInfoEnums;
|
||||
import hirs.attestationca.persist.provision.helper.ProvisionUtils;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
import org.bouncycastle.asn1.x500.X500Name;
|
||||
import org.bouncycastle.asn1.x509.Extension;
|
||||
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
|
||||
@ -36,18 +30,10 @@ import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.RSAPublicKeySpec;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedList;
|
||||
@ -56,31 +42,6 @@ import java.util.List;
|
||||
@Log4j2
|
||||
@NoArgsConstructor
|
||||
public class AbstractRequestHandler {
|
||||
/**
|
||||
* The default size for IV blocks.
|
||||
*/
|
||||
public final static int DEFAULT_IV_SIZE = 16;
|
||||
/**
|
||||
* Defines the well known exponent.
|
||||
* https://en.wikipedia.org/wiki/65537_(number)#Applications
|
||||
*/
|
||||
private final static BigInteger EXPONENT = new BigInteger("010001", DEFAULT_IV_SIZE);
|
||||
/**
|
||||
* Number of bytes to include in the TPM2.0 nonce.
|
||||
*/
|
||||
public static final int NONCE_LENGTH = 20;
|
||||
public static final int SEED_LENGTH = 32;
|
||||
public static final int MAX_SECRET_LENGTH = 32;
|
||||
public static final int RSA_MODULUS_LENGTH = 256;
|
||||
public static final int AES_KEY_LENGTH_BYTES = 16;
|
||||
public static final int HMAC_KEY_LENGTH_BYTES = 32;
|
||||
public static final int HMAC_SIZE_LENGTH_BYTES = 2;
|
||||
public static final int TPM2_CREDENTIAL_BLOB_SIZE = 392;
|
||||
// Constants used to parse out the ak name from the ak public data. Used in generateAkName
|
||||
public static final String AK_NAME_PREFIX = "000b";
|
||||
public static final String AK_NAME_HASH_PREFIX =
|
||||
"0001000b00050072000000100014000b0800000000000100";
|
||||
public static final String TPM_SIGNATURE_ALG = "sha";
|
||||
|
||||
@Getter
|
||||
private int validDays;
|
||||
@ -88,12 +49,6 @@ public class AbstractRequestHandler {
|
||||
private PrivateKey privateKey;
|
||||
@Setter
|
||||
@Getter
|
||||
private String tpmQuoteHash = "";
|
||||
@Setter
|
||||
@Getter
|
||||
private String tpmQuoteSignature = "";
|
||||
@Setter
|
||||
@Getter
|
||||
private PolicyRepository policyRepository;
|
||||
|
||||
public AbstractRequestHandler(final PrivateKey privateKey,
|
||||
@ -102,112 +57,6 @@ public class AbstractRequestHandler {
|
||||
this.validDays = validDays;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse public key from public data segment generated by TPM 2.0.
|
||||
* @param publicArea the public area segment to parse
|
||||
* @return the RSA public key of the supplied public data
|
||||
*/
|
||||
RSAPublicKey parsePublicKey(final byte[] publicArea) {
|
||||
int pubLen = publicArea.length;
|
||||
if (pubLen < RSA_MODULUS_LENGTH) {
|
||||
throw new IllegalArgumentException(
|
||||
"EK or AK public data segment is not long enough");
|
||||
}
|
||||
// public data ends with 256 byte modulus
|
||||
byte[] modulus = HexUtils.subarray(publicArea,
|
||||
pubLen - RSA_MODULUS_LENGTH,
|
||||
pubLen - 1);
|
||||
return (RSAPublicKey) assemblePublicKey(modulus);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a public key where the modulus is in raw form.
|
||||
*
|
||||
* @param modulus
|
||||
* in byte array form
|
||||
* @return public key using specific modulus and the well known exponent
|
||||
*/
|
||||
PublicKey assemblePublicKey(final byte[] modulus) {
|
||||
return assemblePublicKey(Hex.encodeHexString(modulus));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a public key where the modulus is Hex encoded.
|
||||
*
|
||||
* @param modulus
|
||||
* hex encoded modulus
|
||||
* @return public key using specific modulus and the well known exponent
|
||||
*/
|
||||
PublicKey assemblePublicKey(final String modulus) {
|
||||
return assemblePublicKey(new BigInteger(modulus, DEFAULT_IV_SIZE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Assembles a public key using a defined big int modulus and the well known exponent.
|
||||
*/
|
||||
private PublicKey assemblePublicKey(final BigInteger modulus) {
|
||||
// generate a key spec using mod and exp
|
||||
RSAPublicKeySpec keySpec = new RSAPublicKeySpec(modulus, EXPONENT);
|
||||
|
||||
// create the public key
|
||||
try {
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||
return keyFactory.generatePublic(keySpec);
|
||||
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
|
||||
throw new UnexpectedServerException(
|
||||
"Encountered unexpected error creating public key: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to parse a byte array into an {@link ProvisionerTpm2.IdentityClaim}.
|
||||
*
|
||||
* @param identityClaim byte array that should be converted to a Protobuf IdentityClaim
|
||||
* object
|
||||
* @throws {@link IdentityProcessingException} if byte array could not be parsed
|
||||
* @return the Protobuf generated Identity Claim object
|
||||
*/
|
||||
public ProvisionerTpm2.IdentityClaim parseIdentityClaim(final byte[] identityClaim) {
|
||||
try {
|
||||
return ProvisionerTpm2.IdentityClaim.parseFrom(identityClaim);
|
||||
} catch (InvalidProtocolBufferException ipbe) {
|
||||
throw new IdentityProcessingException(
|
||||
"Could not deserialize Protobuf Identity Claim object.", ipbe);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 UnexpectedServerException} if error occurs during encoding retrieval
|
||||
* @return the byte array representing the DER encoded certificate
|
||||
*/
|
||||
public byte[] getDerEncodedCertificate(final X509Certificate certificate) {
|
||||
try {
|
||||
return certificate.getEncoded();
|
||||
} catch (CertificateEncodingException ceEx) {
|
||||
log.error("Error converting certificate to ASN.1 DER Encoding.", ceEx);
|
||||
throw new UnexpectedServerException(
|
||||
"Encountered error while converting X509 Certificate to ASN.1 DER Encoding: "
|
||||
+ ceEx.getMessage(), ceEx);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a array of random bytes.
|
||||
*
|
||||
* @param numberOfBytes
|
||||
* to be generated
|
||||
* @return byte array filled with the specified number of bytes.
|
||||
*/
|
||||
public byte[] generateRandomBytes(final int numberOfBytes) {
|
||||
byte[] bytes = new byte[numberOfBytes];
|
||||
SecureRandom random = new SecureRandom();
|
||||
random.nextBytes(bytes);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a credential using the specified public key.
|
||||
*
|
||||
@ -365,42 +214,6 @@ public class AbstractRequestHandler {
|
||||
return credential;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method takes the provided TPM Quote and splits it between the PCR
|
||||
* quote and the signature hash.
|
||||
* @param tpmQuote contains hash values for the quote and the signature
|
||||
*/
|
||||
public boolean parseTPMQuote(final String tpmQuote) {
|
||||
boolean success = false;
|
||||
if (tpmQuote != null) {
|
||||
String[] lines = tpmQuote.split(":");
|
||||
if (lines[1].contains("signature")) {
|
||||
this.tpmQuoteHash = lines[1].replace("signature", "").trim();
|
||||
} else {
|
||||
this.tpmQuoteHash = lines[1].trim();
|
||||
}
|
||||
this.tpmQuoteSignature = lines[2].trim();
|
||||
success = true;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to create the TPMInfo used for device.
|
||||
* @return a new TPMInfo Instance
|
||||
*/
|
||||
public TPMInfo createTpmInfo(final String pcrValues) {
|
||||
return new TPMInfo(DeviceInfoEnums.NOT_SPECIFIED,
|
||||
(short) 0,
|
||||
(short) 0,
|
||||
(short) 0,
|
||||
(short) 0,
|
||||
pcrValues.getBytes(StandardCharsets.UTF_8),
|
||||
this.getTpmQuoteHash().getBytes(StandardCharsets.UTF_8),
|
||||
this.getTpmQuoteSignature().getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to create an {@link IssuedAttestationCertificate} object, set its
|
||||
* corresponding device and persist it.
|
||||
@ -437,7 +250,7 @@ public class AbstractRequestHandler {
|
||||
if (issuedAc.getEndValidity().after(currentDate)) {
|
||||
// so the issued AC is not expired
|
||||
// however are we within the threshold
|
||||
days = daysBetween(currentDate, issuedAc.getEndValidity());
|
||||
days = ProvisionUtils.daysBetween(currentDate, issuedAc.getEndValidity());
|
||||
if (days < Integer.parseInt(policySettings.getReissueThreshold())) {
|
||||
generateCertificate = true;
|
||||
} else {
|
||||
@ -477,9 +290,4 @@ public class AbstractRequestHandler {
|
||||
|
||||
return credentials;
|
||||
}
|
||||
|
||||
@SuppressWarnings("magicnumber")
|
||||
private int daysBetween(final Date date1, final Date date2) {
|
||||
return (int) ((date2.getTime() - date1.getTime()) / (1000 * 60 * 60 * 24));
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import hirs.attestationca.persist.entity.userdefined.info.TPMInfo;
|
||||
import hirs.attestationca.persist.entity.userdefined.report.DeviceInfoReport;
|
||||
import hirs.attestationca.persist.enums.AppraisalStatus;
|
||||
import hirs.attestationca.persist.exceptions.CertificateProcessingException;
|
||||
import hirs.attestationca.persist.provision.helper.ProvisionUtils;
|
||||
import hirs.attestationca.persist.service.SupplyChainValidationService;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
@ -87,13 +88,13 @@ public class CertificateRequestHandler extends AbstractRequestHandler {
|
||||
if (tpm2ProvisionerState != null) {
|
||||
// Reparse Identity Claim to gather necessary components
|
||||
byte[] identityClaim = tpm2ProvisionerState.getIdentityClaim();
|
||||
ProvisionerTpm2.IdentityClaim claim = parseIdentityClaim(identityClaim);
|
||||
ProvisionerTpm2.IdentityClaim claim = ProvisionUtils.parseIdentityClaim(identityClaim);
|
||||
|
||||
// Get endorsement public key
|
||||
RSAPublicKey ekPub = parsePublicKey(claim.getEkPublicArea().toByteArray());
|
||||
RSAPublicKey ekPub = ProvisionUtils.parsePublicKey(claim.getEkPublicArea().toByteArray());
|
||||
|
||||
// Get attestation public key
|
||||
RSAPublicKey akPub = parsePublicKey(claim.getAkPublicArea().toByteArray());
|
||||
RSAPublicKey akPub = ProvisionUtils.parsePublicKey(claim.getAkPublicArea().toByteArray());
|
||||
|
||||
// Get Endorsement Credential if it exists or was uploaded
|
||||
EndorsementCredential endorsementCredential = parseEcFromIdentityClaim(claim, ekPub, certificateRepository);
|
||||
@ -109,7 +110,6 @@ public class CertificateRequestHandler extends AbstractRequestHandler {
|
||||
// Parse through the Provisioner supplied TPM Quote and pcr values
|
||||
// these fields are optional
|
||||
if (request.getQuote() != null && !request.getQuote().isEmpty()) {
|
||||
parseTPMQuote(request.getQuote().toStringUtf8());
|
||||
TPMInfo savedInfo = device.getDeviceInfo().getTpmInfo();
|
||||
TPMInfo tpmInfo = new TPMInfo(savedInfo.getTpmMake(),
|
||||
savedInfo.getTpmVersionMajor(),
|
||||
@ -117,8 +117,10 @@ public class CertificateRequestHandler extends AbstractRequestHandler {
|
||||
savedInfo.getTpmVersionRevMajor(),
|
||||
savedInfo.getTpmVersionRevMinor(),
|
||||
savedInfo.getPcrValues(),
|
||||
getTpmQuoteHash().getBytes(StandardCharsets.UTF_8),
|
||||
getTpmQuoteSignature().getBytes(StandardCharsets.UTF_8));
|
||||
ProvisionUtils.parseTPMQuoteHash(request.getQuote().toStringUtf8())
|
||||
.getBytes(StandardCharsets.UTF_8),
|
||||
ProvisionUtils.parseTPMQuoteSignature(request.getQuote().toStringUtf8())
|
||||
.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
DeviceInfoReport dvReport = new DeviceInfoReport(
|
||||
device.getDeviceInfo().getNetworkInfo(),
|
||||
@ -136,7 +138,7 @@ public class CertificateRequestHandler extends AbstractRequestHandler {
|
||||
// Create signed, attestation certificate
|
||||
X509Certificate attestationCertificate = generateCredential(akPub,
|
||||
endorsementCredential, platformCredentials, deviceName, acaCertificate);
|
||||
byte[] derEncodedAttestationCertificate = getDerEncodedCertificate(
|
||||
byte[] derEncodedAttestationCertificate = ProvisionUtils.getDerEncodedCertificate(
|
||||
attestationCertificate);
|
||||
|
||||
// We validated the nonce and made use of the identity claim so state can be deleted
|
||||
|
@ -27,6 +27,7 @@ import hirs.attestationca.persist.entity.userdefined.rim.ReferenceDigestValue;
|
||||
import hirs.attestationca.persist.entity.userdefined.rim.SupportReferenceManifest;
|
||||
import hirs.attestationca.persist.enums.AppraisalStatus;
|
||||
import hirs.attestationca.persist.exceptions.IdentityProcessingException;
|
||||
import hirs.attestationca.persist.provision.helper.ProvisionUtils;
|
||||
import hirs.attestationca.persist.service.SupplyChainValidationService;
|
||||
import hirs.utils.HexUtils;
|
||||
import hirs.utils.SwidResource;
|
||||
@ -37,28 +38,15 @@ import lombok.extern.log4j.Log4j2;
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.OAEPParameterSpec;
|
||||
import javax.crypto.spec.PSource;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PublicKey;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.security.spec.MGF1ParameterSpec;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
@ -126,11 +114,11 @@ public class IdentityClaimHandler extends AbstractRequestHandler {
|
||||
}
|
||||
|
||||
// attempt to deserialize Protobuf IdentityClaim
|
||||
ProvisionerTpm2.IdentityClaim claim = parseIdentityClaim(identityClaim);
|
||||
ProvisionerTpm2.IdentityClaim claim = ProvisionUtils.parseIdentityClaim(identityClaim);
|
||||
|
||||
// parse the EK Public key from the IdentityClaim once for use in supply chain validation
|
||||
// and later tpm20MakeCredential function
|
||||
RSAPublicKey ekPub = parsePublicKey(claim.getEkPublicArea().toByteArray());
|
||||
RSAPublicKey ekPub = ProvisionUtils.parsePublicKey(claim.getEkPublicArea().toByteArray());
|
||||
AppraisalStatus.Status validationResult = AppraisalStatus.Status.FAIL;
|
||||
|
||||
try {
|
||||
@ -143,9 +131,9 @@ public class IdentityClaimHandler extends AbstractRequestHandler {
|
||||
|
||||
ByteString blobStr = ByteString.copyFrom(new byte[]{});
|
||||
if (validationResult == AppraisalStatus.Status.PASS) {
|
||||
RSAPublicKey akPub = parsePublicKey(claim.getAkPublicArea().toByteArray());
|
||||
byte[] nonce = generateRandomBytes(NONCE_LENGTH);
|
||||
blobStr = tpm20MakeCredential(ekPub, akPub, nonce);
|
||||
RSAPublicKey akPub = ProvisionUtils.parsePublicKey(claim.getAkPublicArea().toByteArray());
|
||||
byte[] nonce = ProvisionUtils.generateRandomBytes(NONCE_LENGTH);
|
||||
blobStr = ProvisionUtils.tpm20MakeCredential(ekPub, akPub, nonce);
|
||||
PolicyRepository scp = this.getPolicyRepository();
|
||||
PolicySettings policySettings = scp.findByName("Default");
|
||||
String pcrQuoteMask = PCR_QUOTE_MASK;
|
||||
@ -223,7 +211,6 @@ public class IdentityClaimHandler extends AbstractRequestHandler {
|
||||
|
||||
private Device processDeviceInfo(final ProvisionerTpm2.IdentityClaim claim) {
|
||||
DeviceInfoReport deviceInfoReport = null;
|
||||
String deviceName = deviceInfoReport.getNetworkInfo().getHostname();
|
||||
|
||||
try {
|
||||
deviceInfoReport = parseDeviceInfo(claim);
|
||||
@ -239,7 +226,7 @@ public class IdentityClaimHandler extends AbstractRequestHandler {
|
||||
|
||||
log.info("Processing Device Info Report");
|
||||
// store device and device info report.
|
||||
Device device = this.deviceRepository.findByName(deviceName);
|
||||
Device device = this.deviceRepository.findByName(deviceInfoReport.getNetworkInfo().getHostname());
|
||||
device.setDeviceInfo(deviceInfoReport);
|
||||
return this.deviceRepository.save(device);
|
||||
}
|
||||
@ -261,8 +248,8 @@ public class IdentityClaimHandler extends AbstractRequestHandler {
|
||||
InetAddress ip = null;
|
||||
try {
|
||||
ip = InetAddress.getByName(nwProto.getIpAddress());
|
||||
} catch (UnknownHostException e) {
|
||||
log.error("Unable to parse IP address: ", e);
|
||||
} catch (UnknownHostException uhEx) {
|
||||
log.error("Unable to parse IP address: ", uhEx);
|
||||
}
|
||||
String[] macAddressParts = nwProto.getMacAddress().split(":");
|
||||
|
||||
@ -486,10 +473,16 @@ public class IdentityClaimHandler extends AbstractRequestHandler {
|
||||
}
|
||||
|
||||
// Get TPM info, currently unimplemented
|
||||
TPMInfo tpm = createTpmInfo(pcrValues);
|
||||
TPMInfo tpmInfo = new TPMInfo(DeviceInfoEnums.NOT_SPECIFIED,
|
||||
(short) 0,
|
||||
(short) 0,
|
||||
(short) 0,
|
||||
(short) 0,
|
||||
pcrValues.getBytes(StandardCharsets.UTF_8),
|
||||
null, null);
|
||||
|
||||
// Create final report
|
||||
DeviceInfoReport dvReport = new DeviceInfoReport(nw, os, fw, hw, tpm,
|
||||
DeviceInfoReport dvReport = new DeviceInfoReport(nw, os, fw, hw, tpmInfo,
|
||||
claim.getClientVersion());
|
||||
dvReport.setPaccorOutputString(claim.getPaccorOutput());
|
||||
|
||||
@ -602,214 +595,25 @@ public class IdentityClaimHandler extends AbstractRequestHandler {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the first step of the TPM 2.0 identity claim process. Takes an ek, ak, and secret
|
||||
* and then generates a seed that is used to generate AES and HMAC keys. Parses the ak name.
|
||||
* Encrypts the seed with the public ek. Uses the AES key to encrypt the secret. Uses the HMAC
|
||||
* key to generate an HMAC to cover the encrypted secret and the ak name. The output is an
|
||||
* encrypted blob that acts as the first part of a challenge-response authentication mechanism
|
||||
* to validate an identity claim.
|
||||
*
|
||||
* Equivalent to calling tpm2_makecredential using tpm2_tools.
|
||||
*
|
||||
* @param ek endorsement key in the identity claim
|
||||
* @param ak attestation key in the identity claim
|
||||
* @param secret a nonce
|
||||
* @return the encrypted blob forming the identity claim challenge
|
||||
*/
|
||||
protected ByteString tpm20MakeCredential(final RSAPublicKey ek, final RSAPublicKey ak,
|
||||
final byte[] secret) {
|
||||
// check size of the secret
|
||||
if (secret.length > MAX_SECRET_LENGTH) {
|
||||
throw new IllegalArgumentException("Secret must be " + MAX_SECRET_LENGTH
|
||||
+ " bytes or smaller.");
|
||||
}
|
||||
|
||||
// generate a random 32 byte seed
|
||||
byte[] seed = generateRandomBytes(SEED_LENGTH);
|
||||
|
||||
try {
|
||||
// encrypt seed with pubEk
|
||||
Cipher asymCipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
|
||||
OAEPParameterSpec oaepSpec = new OAEPParameterSpec("SHA-256", "MGF1",
|
||||
MGF1ParameterSpec.SHA256, new PSource.PSpecified("IDENTITY\0".getBytes()));
|
||||
asymCipher.init(Cipher.PUBLIC_KEY, ek, oaepSpec);
|
||||
asymCipher.update(seed);
|
||||
byte[] encSeed = asymCipher.doFinal();
|
||||
private List<PlatformCredential> getPlatformCredentials(final CertificateRepository certificateRepository,
|
||||
final EndorsementCredential ec) {
|
||||
List<PlatformCredential> credentials = null;
|
||||
|
||||
// generate ak name from akMod
|
||||
byte[] akModTemp = ak.getModulus().toByteArray();
|
||||
byte[] akMod = new byte[RSA_MODULUS_LENGTH];
|
||||
int startpos = 0;
|
||||
// BigIntegers are signed, so a modulus that has a first bit of 1
|
||||
// will be padded with a zero byte that must be removed
|
||||
if (akModTemp[0] == 0x00) {
|
||||
startpos = 1;
|
||||
if (ec == null) {
|
||||
log.warn("Cannot look for platform credential(s). Endorsement credential was null.");
|
||||
} else {
|
||||
log.debug("Searching for platform credential(s) based on holder serial number: "
|
||||
+ ec.getSerialNumber());
|
||||
credentials = certificateRepository.getByHolderSerialNumber(ec.getSerialNumber());
|
||||
if (credentials == null || credentials.isEmpty()) {
|
||||
log.warn("No platform credential(s) found");
|
||||
} else {
|
||||
log.debug("Platform Credential(s) found: " + credentials.size());
|
||||
}
|
||||
System.arraycopy(akModTemp, startpos, akMod, 0, RSA_MODULUS_LENGTH);
|
||||
byte[] akName = generateAkName(akMod);
|
||||
|
||||
// generate AES and HMAC keys from seed
|
||||
byte[] aesKey = cryptKDFa(seed, "STORAGE", akName, AES_KEY_LENGTH_BYTES);
|
||||
byte[] hmacKey = cryptKDFa(seed, "INTEGRITY", null, HMAC_KEY_LENGTH_BYTES);
|
||||
|
||||
// use two bytes to add a size prefix on secret
|
||||
ByteBuffer b;
|
||||
b = ByteBuffer.allocate(2);
|
||||
b.putShort((short) (secret.length));
|
||||
byte[] secretLength = b.array();
|
||||
byte[] secretBytes = new byte[secret.length + 2];
|
||||
System.arraycopy(secretLength, 0, secretBytes, 0, 2);
|
||||
System.arraycopy(secret, 0, secretBytes, 2, secret.length);
|
||||
|
||||
// encrypt size prefix + secret with AES key
|
||||
Cipher symCipher = Cipher.getInstance("AES/CFB/NoPadding");
|
||||
byte[] defaultIv = HexUtils.hexStringToByteArray("00000000000000000000000000000000");
|
||||
IvParameterSpec ivSpec = new IvParameterSpec(defaultIv);
|
||||
symCipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(aesKey, "AES"), ivSpec);
|
||||
byte[] encSecret = symCipher.doFinal(secretBytes);
|
||||
|
||||
// generate HMAC covering encrypted secret and ak name
|
||||
Mac integrityHmac = Mac.getInstance("HmacSHA256");
|
||||
SecretKeySpec integrityKey = new SecretKeySpec(hmacKey, integrityHmac.getAlgorithm());
|
||||
integrityHmac.init(integrityKey);
|
||||
byte[] message = new byte[encSecret.length + akName.length];
|
||||
System.arraycopy(encSecret, 0, message, 0, encSecret.length);
|
||||
System.arraycopy(akName, 0, message, encSecret.length, akName.length);
|
||||
integrityHmac.update(message);
|
||||
byte[] integrity = integrityHmac.doFinal();
|
||||
b = ByteBuffer.allocate(2);
|
||||
b.putShort((short) (HMAC_SIZE_LENGTH_BYTES + HMAC_KEY_LENGTH_BYTES + encSecret.length));
|
||||
byte[] topSize = b.array();
|
||||
|
||||
// return ordered blob of assembled credentials
|
||||
byte[] bytesToReturn = assembleCredential(topSize, integrity, encSecret, encSeed);
|
||||
return ByteString.copyFrom(bytesToReturn);
|
||||
|
||||
} catch (BadPaddingException | IllegalBlockSizeException | NoSuchAlgorithmException
|
||||
| InvalidKeyException | InvalidAlgorithmParameterException
|
||||
| NoSuchPaddingException e) {
|
||||
throw new IdentityProcessingException(
|
||||
"Encountered error while making the identity claim challenge: "
|
||||
+ e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("magicnumber")
|
||||
private byte[] assembleCredential(final byte[] topSize, final byte[] integrityHmac,
|
||||
final byte[] encryptedSecret,
|
||||
final byte[] encryptedSeed) {
|
||||
/*
|
||||
* Credential structure breakdown with endianness:
|
||||
* 0-1 topSize (2), LE
|
||||
* 2-3 hashsize (2), BE always 0x0020
|
||||
* 4-35 integrity HMac (32)
|
||||
* 36-133 (98 = 32*3 +2) of zeros, copy over from encSecret starting at [36]
|
||||
* 134-135 (2) LE size, always 0x0001
|
||||
* 136-391 (256) copy over with encSeed
|
||||
* */
|
||||
byte[] credentialBlob = new byte[TPM2_CREDENTIAL_BLOB_SIZE];
|
||||
credentialBlob[0] = topSize[1];
|
||||
credentialBlob[1] = topSize[0];
|
||||
credentialBlob[2] = 0x00;
|
||||
credentialBlob[3] = 0x20;
|
||||
System.arraycopy(integrityHmac, 0, credentialBlob, 4, 32);
|
||||
for (int i = 0; i < 98; i++) {
|
||||
credentialBlob[36 + i] = 0x00;
|
||||
}
|
||||
System.arraycopy(encryptedSecret, 0, credentialBlob, 36, encryptedSecret.length);
|
||||
credentialBlob[134] = 0x00;
|
||||
credentialBlob[135] = 0x01;
|
||||
System.arraycopy(encryptedSeed, 0, credentialBlob, 136, 256);
|
||||
// return the result
|
||||
return credentialBlob;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the AK name from the AK Modulus.
|
||||
* @param akModulus modulus of an attestation key
|
||||
* @return the ak name byte array
|
||||
* @throws NoSuchAlgorithmException Underlying SHA256 method used a bad algorithm
|
||||
*/
|
||||
byte[] generateAkName(final byte[] akModulus) throws NoSuchAlgorithmException {
|
||||
byte[] namePrefix = HexUtils.hexStringToByteArray(AK_NAME_PREFIX);
|
||||
byte[] hashPrefix = HexUtils.hexStringToByteArray(AK_NAME_HASH_PREFIX);
|
||||
byte[] toHash = new byte[hashPrefix.length + akModulus.length];
|
||||
System.arraycopy(hashPrefix, 0, toHash, 0, hashPrefix.length);
|
||||
System.arraycopy(akModulus, 0, toHash, hashPrefix.length, akModulus.length);
|
||||
byte[] nameHash = sha256hash(toHash);
|
||||
byte[] toReturn = new byte[namePrefix.length + nameHash.length];
|
||||
System.arraycopy(namePrefix, 0, toReturn, 0, namePrefix.length);
|
||||
System.arraycopy(nameHash, 0, toReturn, namePrefix.length, nameHash.length);
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
/**
|
||||
* This replicates the TPM 2.0 CryptKDFa function to an extent. It will only work for generation
|
||||
* that uses SHA-256, and will only generate values of 32 B or less. Counters above zero and
|
||||
* multiple contexts are not supported in this implementation. This should work for all uses of
|
||||
* the KDF for TPM2_MakeCredential.
|
||||
*
|
||||
* @param seed random value used to generate the key
|
||||
* @param label first portion of message used to generate key
|
||||
* @param context second portion of message used to generate key
|
||||
* @param sizeInBytes size of key to generate in bytes
|
||||
* @return the derived key
|
||||
* @throws NoSuchAlgorithmException Wrong crypto algorithm selected
|
||||
* @throws InvalidKeyException Invalid key used
|
||||
*/
|
||||
@SuppressWarnings("magicnumber")
|
||||
private byte[] cryptKDFa(final byte[] seed, final String label, final byte[] context,
|
||||
final int sizeInBytes)
|
||||
throws NoSuchAlgorithmException, InvalidKeyException {
|
||||
ByteBuffer b = ByteBuffer.allocate(4);
|
||||
b.putInt(1);
|
||||
byte[] counter = b.array();
|
||||
// get the label
|
||||
String labelWithEnding = label;
|
||||
if (label.charAt(label.length() - 1) != "\0".charAt(0)) {
|
||||
labelWithEnding = label + "\0";
|
||||
}
|
||||
byte[] labelBytes = labelWithEnding.getBytes();
|
||||
b = ByteBuffer.allocate(4);
|
||||
b.putInt(sizeInBytes * 8);
|
||||
byte[] desiredSizeInBits = b.array();
|
||||
int sizeOfMessage = 8 + labelBytes.length;
|
||||
if (context != null) {
|
||||
sizeOfMessage += context.length;
|
||||
}
|
||||
byte[] message = new byte[sizeOfMessage];
|
||||
int marker = 0;
|
||||
System.arraycopy(counter, 0, message, marker, 4);
|
||||
marker += 4;
|
||||
System.arraycopy(labelBytes, 0, message, marker, labelBytes.length);
|
||||
marker += labelBytes.length;
|
||||
if (context != null) {
|
||||
System.arraycopy(context, 0, message, marker, context.length);
|
||||
marker += context.length;
|
||||
}
|
||||
System.arraycopy(desiredSizeInBits, 0, message, marker, 4);
|
||||
Mac hmac;
|
||||
byte[] toReturn = new byte[sizeInBytes];
|
||||
|
||||
hmac = Mac.getInstance("HmacSHA256");
|
||||
SecretKeySpec hmacKey = new SecretKeySpec(seed, hmac.getAlgorithm());
|
||||
hmac.init(hmacKey);
|
||||
hmac.update(message);
|
||||
byte[] hmacResult = hmac.doFinal();
|
||||
System.arraycopy(hmacResult, 0, toReturn, 0, sizeInBytes);
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the sha256 hash of the given blob.
|
||||
* @param blob byte array to take the hash of
|
||||
* @return sha256 hash of blob
|
||||
* @throws NoSuchAlgorithmException improper algorithm selected
|
||||
*/
|
||||
private byte[] sha256hash(final byte[] blob) throws NoSuchAlgorithmException {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
md.update(blob);
|
||||
return md.digest();
|
||||
return credentials;
|
||||
}
|
||||
}
|
||||
|
@ -9,9 +9,9 @@ import hirs.attestationca.persist.entity.userdefined.certificate.EndorsementCred
|
||||
import hirs.attestationca.persist.entity.userdefined.certificate.PlatformCredential;
|
||||
import hirs.attestationca.persist.entity.userdefined.report.DeviceInfoReport;
|
||||
import hirs.attestationca.persist.enums.AppraisalStatus;
|
||||
import hirs.attestationca.persist.exceptions.CertificateProcessingException;
|
||||
import hirs.attestationca.persist.exceptions.IdentityProcessingException;
|
||||
import hirs.attestationca.persist.provision.helper.CredentialManagementHelper;
|
||||
import hirs.attestationca.persist.provision.helper.ProvisionUtils;
|
||||
import hirs.attestationca.persist.service.SupplyChainValidationService;
|
||||
import hirs.structs.converters.SimpleStructBuilder;
|
||||
import hirs.structs.converters.StructConverter;
|
||||
@ -27,29 +27,13 @@ import lombok.extern.log4j.Log4j2;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.commons.lang3.SerializationUtils;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.OAEPParameterSpec;
|
||||
import javax.crypto.spec.PSource;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.security.spec.MGF1ParameterSpec;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@Log4j2
|
||||
public class IdentityRequestHandler extends AbstractRequestHandler {
|
||||
@ -109,7 +93,8 @@ public class IdentityRequestHandler extends AbstractRequestHandler {
|
||||
IdentityRequestEnvelope challenge =
|
||||
structConverter.convert(identityRequest, IdentityRequestEnvelope.class);
|
||||
|
||||
byte[] identityProof = unwrapIdentityRequest(challenge.getRequest());
|
||||
byte[] identityProof = unwrapIdentityRequest(structConverter.convert(challenge.getRequest(),
|
||||
IdentityRequest.class));
|
||||
// the decrypted symmetric blob should be in the format of an IdentityProof. Use the
|
||||
// struct converter to generate it.
|
||||
IdentityProof proof = structConverter.convert(identityProof, IdentityProof.class);
|
||||
@ -130,7 +115,7 @@ public class IdentityRequestHandler extends AbstractRequestHandler {
|
||||
BigInteger publicKeyModulus = Certificate.getPublicKeyModulus(
|
||||
endorsementCredential.getX509Certificate());
|
||||
if (publicKeyModulus != null) {
|
||||
ekPublicKey = assemblePublicKey(publicKeyModulus.toByteArray());
|
||||
ekPublicKey = ProvisionUtils.assemblePublicKey(publicKeyModulus.toByteArray());
|
||||
} else {
|
||||
throw new IdentityProcessingException("TPM 1.2 Provisioning requires EK "
|
||||
+ "Credentials to be created with RSA");
|
||||
@ -142,7 +127,7 @@ public class IdentityRequestHandler extends AbstractRequestHandler {
|
||||
log.warn("EKC was not in the identity proof from the client. Checking for uploads.");
|
||||
// Check if the EC was uploaded
|
||||
ekPublicKey =
|
||||
assemblePublicKey(new String(challenge.getEndorsementCredentialModulus()));
|
||||
ProvisionUtils.assemblePublicKey(new String(challenge.getEndorsementCredentialModulus()));
|
||||
endorsementCredential = getEndorsementCredential(ekPublicKey);
|
||||
} else {
|
||||
log.warn("Zero-length endorsement credential received in identity request.");
|
||||
@ -222,7 +207,8 @@ public class IdentityRequestHandler extends AbstractRequestHandler {
|
||||
final List<PlatformCredential> platformCredentials, final Device device) {
|
||||
// decrypt the asymmetric / symmetric blobs
|
||||
log.debug("unwrapping identity request");
|
||||
byte[] identityProof = unwrapIdentityRequest(challenge.getRequest());
|
||||
byte[] identityProof = unwrapIdentityRequest(
|
||||
structConverter.convert(challenge.getRequest(), IdentityRequest.class));
|
||||
|
||||
// the decrypted symmetric blob should be in the format of an IdentityProof. Use the
|
||||
// struct converter to generate it.
|
||||
@ -230,17 +216,19 @@ public class IdentityRequestHandler extends AbstractRequestHandler {
|
||||
|
||||
// generate a session key and convert to byte array
|
||||
log.debug("generating symmetric key for response");
|
||||
SymmetricKey sessionKey = generateSymmetricKey();
|
||||
SymmetricKey sessionKey = ProvisionUtils.generateSymmetricKey();
|
||||
|
||||
// generate the asymmetric contents for the identity response
|
||||
log.debug("generating asymmetric contents for response");
|
||||
byte[] asymmetricContents = generateAsymmetricContents(proof, sessionKey, ekPublicKey);
|
||||
byte[] asymmetricContents = ProvisionUtils.generateAsymmetricContents(
|
||||
structConverter.convert(proof.getIdentityKey()),
|
||||
structConverter.convert(sessionKey), ekPublicKey);
|
||||
|
||||
// generate the identity credential
|
||||
log.debug("generating credential from identity proof");
|
||||
|
||||
// transform the public key struct into a public key
|
||||
PublicKey publicKey = assemblePublicKey(proof.getIdentityKey().getStorePubKey().getKey());
|
||||
PublicKey publicKey = ProvisionUtils.assemblePublicKey(proof.getIdentityKey().getStorePubKey().getKey());
|
||||
X509Certificate credential = generateCredential(publicKey, endorsementCredential,
|
||||
platformCredentials, device.getDeviceInfo()
|
||||
.getNetworkInfo()
|
||||
@ -249,7 +237,7 @@ public class IdentityRequestHandler extends AbstractRequestHandler {
|
||||
|
||||
// generate the attestation using the credential and the key for this session
|
||||
log.debug("generating symmetric response");
|
||||
SymmetricAttestation attestation = generateAttestation(credential, sessionKey);
|
||||
SymmetricAttestation attestation = ProvisionUtils.generateAttestation(credential, sessionKey);
|
||||
|
||||
// construct the response with the both the asymmetric contents and the CA attestation
|
||||
IdentityResponseEnvelope identityResponse =
|
||||
@ -258,7 +246,7 @@ public class IdentityRequestHandler extends AbstractRequestHandler {
|
||||
.set("symmetricAttestation", attestation).build();
|
||||
|
||||
// save new attestation certificate
|
||||
byte[] derEncodedAttestationCertificate = getDerEncodedCertificate(credential);
|
||||
byte[] derEncodedAttestationCertificate = ProvisionUtils.getDerEncodedCertificate(credential);
|
||||
saveAttestationCertificate(this.certificateRepository, derEncodedAttestationCertificate,
|
||||
endorsementCredential, platformCredentials, device);
|
||||
|
||||
@ -269,13 +257,11 @@ public class IdentityRequestHandler extends AbstractRequestHandler {
|
||||
* Unwraps a given identityRequest. That is to say, decrypt the asymmetric portion of a data
|
||||
* structure to determine the method to decrypt the symmetric portion.
|
||||
*
|
||||
* @param identityRequest
|
||||
* @param request
|
||||
* to be decrypted
|
||||
* @return the decrypted symmetric portion of an identity request.
|
||||
*/
|
||||
private byte[] unwrapIdentityRequest(final byte[] identityRequest) {
|
||||
IdentityRequest request = structConverter.convert(identityRequest, IdentityRequest.class);
|
||||
|
||||
private byte[] unwrapIdentityRequest(final IdentityRequest request) {
|
||||
// in case the TPM did not specify the IV, it must be extracted from the symmetric blob.
|
||||
// the IV will then be the the first block of the cipher text.
|
||||
final byte[] iv;
|
||||
@ -283,7 +269,7 @@ public class IdentityRequestHandler extends AbstractRequestHandler {
|
||||
if (symmetricKeyParams != null && symmetricKeyParams.getParams() != null) {
|
||||
iv = symmetricKeyParams.getParams().getIv();
|
||||
} else {
|
||||
iv = extractInitialValue(request);
|
||||
iv = ProvisionUtils.extractInitialValue(request);
|
||||
}
|
||||
|
||||
// determine the encryption scheme from the algorithm
|
||||
@ -292,14 +278,14 @@ public class IdentityRequestHandler extends AbstractRequestHandler {
|
||||
|
||||
// decrypt the asymmetric blob
|
||||
byte[] decryptedAsymmetricBlob =
|
||||
decryptAsymmetricBlob(request.getAsymmetricBlob(), asymmetricScheme);
|
||||
ProvisionUtils.decryptAsymmetricBlob(request.getAsymmetricBlob(), asymmetricScheme, getPrivateKey());
|
||||
|
||||
// construct our symmetric key structure from the decrypted asymmetric blob
|
||||
SymmetricKey symmetricKey =
|
||||
structConverter.convert(decryptedAsymmetricBlob, SymmetricKey.class);
|
||||
|
||||
byte[] decryptedSymmetricBlob =
|
||||
decryptSymmetricBlob(request.getSymmetricBlob(), symmetricKey.getKey(), iv,
|
||||
ProvisionUtils.decryptSymmetricBlob(request.getSymmetricBlob(), symmetricKey.getKey(), iv,
|
||||
"AES/CBC/PKCS5Padding");
|
||||
|
||||
// decrypt the symmetric blob
|
||||
@ -356,219 +342,4 @@ public class IdentityRequestHandler extends AbstractRequestHandler {
|
||||
return credentials;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will attempt to decrypt the asymmetric blob that originated from an
|
||||
* {@link hirs.structs.elements.tpm.IdentityRequest} using the cipher transformation.
|
||||
*
|
||||
* @param asymmetricBlob to be decrypted
|
||||
* @param scheme to decrypt with
|
||||
* @return decrypted blob
|
||||
*/
|
||||
private byte[] decryptAsymmetricBlob(final byte[] asymmetricBlob, final EncryptionScheme scheme) {
|
||||
try {
|
||||
// create a cipher from the specified transformation
|
||||
Cipher cipher = Cipher.getInstance(scheme.toString());
|
||||
|
||||
switch (scheme) {
|
||||
case OAEP:
|
||||
OAEPParameterSpec spec =
|
||||
new OAEPParameterSpec("Sha1", "MGF1", MGF1ParameterSpec.SHA1,
|
||||
new PSource.PSpecified("".getBytes()));
|
||||
|
||||
cipher.init(Cipher.PRIVATE_KEY, privateKey, spec);
|
||||
break;
|
||||
default:
|
||||
// initialize the cipher to decrypt using the ACA private key.
|
||||
cipher.init(Cipher.DECRYPT_MODE, privateKey);
|
||||
}
|
||||
|
||||
cipher.update(asymmetricBlob);
|
||||
|
||||
return cipher.doFinal();
|
||||
} catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException
|
||||
| BadPaddingException | IllegalBlockSizeException
|
||||
| InvalidAlgorithmParameterException e) {
|
||||
throw new IdentityProcessingException(
|
||||
"Encountered error while decrypting asymmetric blob of an identity request: "
|
||||
+ e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Will attempt to decrypt the symmetric blob that originated from an
|
||||
* {@link hirs.structs.elements.tpm.IdentityRequest} using the specified symmetric key
|
||||
* and cipher transformation.
|
||||
*
|
||||
* @param symmetricBlob to be decrypted
|
||||
* @param symmetricKey to use to decrypt
|
||||
* @param iv to use with decryption cipher
|
||||
* @param transformation of the cipher
|
||||
* @return decrypted symmetric blob
|
||||
*/
|
||||
private byte[] decryptSymmetricBlob(final byte[] symmetricBlob, final byte[] symmetricKey,
|
||||
final byte[] iv, final String transformation) {
|
||||
try {
|
||||
// create a cipher from the specified transformation
|
||||
Cipher cipher = Cipher.getInstance(transformation);
|
||||
|
||||
// generate a key specification to initialize the cipher
|
||||
SecretKeySpec keySpec = new SecretKeySpec(symmetricKey, "AES");
|
||||
|
||||
// initialize the cipher to decrypt using the symmetric key
|
||||
cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(iv));
|
||||
|
||||
// decrypt the symmetric blob
|
||||
return cipher.doFinal(symmetricBlob);
|
||||
} catch (IllegalBlockSizeException | InvalidKeyException | NoSuchAlgorithmException
|
||||
| BadPaddingException | NoSuchPaddingException
|
||||
| InvalidAlgorithmParameterException exception) {
|
||||
log.error("Encountered error while decrypting symmetric blob of an identity request: "
|
||||
+ exception.getMessage(), exception);
|
||||
}
|
||||
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
private SymmetricKey generateSymmetricKey() {
|
||||
// create a session key for the CA contents
|
||||
byte[] responseSymmetricKey =
|
||||
generateRandomBytes(DEFAULT_IV_SIZE);
|
||||
|
||||
// create a symmetric key struct for the CA contents
|
||||
SymmetricKey sessionKey =
|
||||
new SimpleStructBuilder<>(SymmetricKey.class)
|
||||
.set("algorithmId", SymmetricKey.ALGORITHM_AES)
|
||||
.set("encryptionScheme", SymmetricKey.SCHEME_CBC)
|
||||
.set("key", responseSymmetricKey).build();
|
||||
return sessionKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate asymmetric contents part of the identity response.
|
||||
*
|
||||
* @param proof identity requests symmetric contents, otherwise, the identity proof
|
||||
* @param symmetricKey identity response session key
|
||||
* @param publicKey of the EK certificate contained within the identity proof
|
||||
* @return encrypted asymmetric contents
|
||||
*/
|
||||
byte[] generateAsymmetricContents(final IdentityProof proof,
|
||||
final SymmetricKey symmetricKey,
|
||||
final PublicKey publicKey) {
|
||||
try {
|
||||
// obtain the identity key from the identity proof
|
||||
byte[] identityKey = structConverter.convert(proof.getIdentityKey());
|
||||
byte[] sessionKey = structConverter.convert(symmetricKey);
|
||||
|
||||
// create a SHA1 digest of the identity key
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-1");
|
||||
md.update(identityKey);
|
||||
|
||||
// generate the digest
|
||||
byte[] identityDigest = md.digest();
|
||||
|
||||
// combine the session key with the digest of the identity key
|
||||
byte[] asymmetricContents = ArrayUtils.addAll(sessionKey, identityDigest);
|
||||
|
||||
// encrypt the asymmetric contents and return
|
||||
OAEPParameterSpec oaepSpec =
|
||||
new OAEPParameterSpec("Sha1", "MGF1", MGF1ParameterSpec.SHA1,
|
||||
new PSource.PSpecified("TCPA".getBytes()));
|
||||
|
||||
// initialize the asymmetric cipher using the default OAEP transformation
|
||||
Cipher cipher = Cipher.getInstance(EncryptionScheme.OAEP.toString());
|
||||
|
||||
// initialize the cipher using the public spec with the additional OAEP specification
|
||||
cipher.init(Cipher.PUBLIC_KEY, publicKey, oaepSpec);
|
||||
|
||||
return cipher.doFinal(asymmetricContents);
|
||||
} catch (NoSuchAlgorithmException | IllegalBlockSizeException | NoSuchPaddingException
|
||||
| InvalidKeyException | BadPaddingException
|
||||
| InvalidAlgorithmParameterException e) {
|
||||
throw new CertificateProcessingException(
|
||||
"Encountered error while generating ACA session key: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the IV from the identity request. That is, take the first block of data from the
|
||||
* symmetric blob and treat that as the IV. This modifies the original symmetric block.
|
||||
*
|
||||
* @param identityRequest to extract the IV from
|
||||
* @return the IV from the identity request
|
||||
*/
|
||||
private byte[] extractInitialValue(final IdentityRequest identityRequest) {
|
||||
|
||||
// make a reference to the symmetric blob
|
||||
byte[] symmetricBlob = identityRequest.getSymmetricBlob();
|
||||
|
||||
// create the IV
|
||||
byte[] iv = new byte[DEFAULT_IV_SIZE];
|
||||
|
||||
// initialize a new symmetric blob with the length of the original minus the IV
|
||||
byte[] updatedBlob = new byte[symmetricBlob.length - iv.length];
|
||||
|
||||
// copy the IV out of the original symmetric blob
|
||||
System.arraycopy(symmetricBlob, 0, iv, 0, iv.length);
|
||||
|
||||
// copy everything but the IV out of the original blob into the new blob
|
||||
System.arraycopy(symmetricBlob, iv.length, updatedBlob, 0, updatedBlob.length);
|
||||
|
||||
// reassign the symmetric blob to the request.
|
||||
identityRequest.setSymmetricBlob(updatedBlob);
|
||||
|
||||
return iv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the Identity Response using the identity credential and the session key.
|
||||
*
|
||||
* @param credential the identity credential
|
||||
* @param symmetricKey generated session key for this request/response chain
|
||||
* @return identity response for an identity request
|
||||
*/
|
||||
SymmetricAttestation generateAttestation(final X509Certificate credential,
|
||||
final SymmetricKey symmetricKey) {
|
||||
try {
|
||||
// initialize the symmetric cipher
|
||||
Cipher aesCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
|
||||
// generate a key specification to initialize the cipher
|
||||
SecretKeySpec keySpec = new SecretKeySpec(symmetricKey.getKey(), "AES");
|
||||
|
||||
// fill IV with random bytes
|
||||
byte[] credentialIV = generateRandomBytes(DEFAULT_IV_SIZE);
|
||||
|
||||
// create IV encryption parameter specification
|
||||
IvParameterSpec ivParameterSpec = new IvParameterSpec(credentialIV);
|
||||
|
||||
// initialize the cipher to decrypt using the symmetric key
|
||||
aesCipher.init(Cipher.ENCRYPT_MODE, keySpec, ivParameterSpec);
|
||||
|
||||
// encrypt the credential
|
||||
byte[] encryptedCredential = aesCipher.doFinal(credential.getEncoded());
|
||||
|
||||
// prepend the IV to the encrypted credential
|
||||
byte[] credentialBytes = ArrayUtils.addAll(credentialIV, encryptedCredential);
|
||||
|
||||
// create attestation for identity response that contains the credential
|
||||
SymmetricAttestation attestation =
|
||||
new SimpleStructBuilder<>(SymmetricAttestation.class)
|
||||
.set("credential", credentialBytes)
|
||||
.set("algorithm",
|
||||
new SimpleStructBuilder<>(SymmetricKeyParams.class)
|
||||
.set("algorithmId", SymmetricKeyParams.ALGORITHM_AES)
|
||||
.set("encryptionScheme",
|
||||
SymmetricKeyParams.SCHEME_CBC_PKCS5PADDING)
|
||||
.set("signatureScheme", 0).build()).build();
|
||||
|
||||
return attestation;
|
||||
|
||||
} catch (BadPaddingException | IllegalBlockSizeException | NoSuchAlgorithmException
|
||||
| InvalidKeyException | InvalidAlgorithmParameterException | NoSuchPaddingException
|
||||
| CertificateEncodingException exception) {
|
||||
throw new CertificateProcessingException(
|
||||
"Encountered error while generating Identity Response: "
|
||||
+ exception.getMessage(), exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,642 @@
|
||||
package hirs.attestationca.persist.provision.helper;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
import hirs.attestationca.configuration.provisionerTpm2.ProvisionerTpm2;
|
||||
import hirs.attestationca.persist.entity.userdefined.info.TPMInfo;
|
||||
import hirs.attestationca.persist.exceptions.CertificateProcessingException;
|
||||
import hirs.attestationca.persist.exceptions.IdentityProcessingException;
|
||||
import hirs.attestationca.persist.exceptions.UnexpectedServerException;
|
||||
import hirs.structs.converters.SimpleStructBuilder;
|
||||
import hirs.structs.elements.aca.SymmetricAttestation;
|
||||
import hirs.structs.elements.tpm.EncryptionScheme;
|
||||
import hirs.structs.elements.tpm.IdentityRequest;
|
||||
import hirs.structs.elements.tpm.SymmetricKey;
|
||||
import hirs.structs.elements.tpm.SymmetricKeyParams;
|
||||
import hirs.utils.HexUtils;
|
||||
import hirs.utils.enums.DeviceInfoEnums;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.OAEPParameterSpec;
|
||||
import javax.crypto.spec.PSource;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.MGF1ParameterSpec;
|
||||
import java.security.spec.RSAPublicKeySpec;
|
||||
import java.util.Date;
|
||||
|
||||
@Log4j2
|
||||
public final class ProvisionUtils {
|
||||
|
||||
/**
|
||||
* The default size for IV blocks.
|
||||
*/
|
||||
public final static int DEFAULT_IV_SIZE = 16;
|
||||
/**
|
||||
* Defines the well known exponent.
|
||||
* https://en.wikipedia.org/wiki/65537_(number)#Applications
|
||||
*/
|
||||
private final static BigInteger EXPONENT = new BigInteger("010001", DEFAULT_IV_SIZE);
|
||||
public static final int HMAC_SIZE_LENGTH_BYTES = 2;
|
||||
public static final int HMAC_KEY_LENGTH_BYTES = 32;
|
||||
public static final int SEED_LENGTH = 32;
|
||||
public static final int MAX_SECRET_LENGTH = 32;
|
||||
public static final int AES_KEY_LENGTH_BYTES = 16;
|
||||
private static final int TPM2_CREDENTIAL_BLOB_SIZE = 392;
|
||||
private static final int RSA_MODULUS_LENGTH = 256;
|
||||
// Constants used to parse out the ak name from the ak public data. Used in generateAkName
|
||||
private static final String AK_NAME_PREFIX = "000b";
|
||||
private static final String AK_NAME_HASH_PREFIX =
|
||||
"0001000b00050072000000100014000b0800000000000100";
|
||||
|
||||
/**
|
||||
* Helper method to parse a byte array into an {@link hirs.attestationca.configuration.provisionerTpm2.ProvisionerTpm2.IdentityClaim}.
|
||||
*
|
||||
* @param identityClaim byte array that should be converted to a Protobuf IdentityClaim
|
||||
* object
|
||||
* @throws {@link IdentityProcessingException} if byte array could not be parsed
|
||||
* @return the Protobuf generated Identity Claim object
|
||||
*/
|
||||
public static ProvisionerTpm2.IdentityClaim parseIdentityClaim(final byte[] identityClaim) {
|
||||
try {
|
||||
return ProvisionerTpm2.IdentityClaim.parseFrom(identityClaim);
|
||||
} catch (InvalidProtocolBufferException ipbe) {
|
||||
throw new IdentityProcessingException(
|
||||
"Could not deserialize Protobuf Identity Claim object.", ipbe);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 UnexpectedServerException} if error occurs during encoding retrieval
|
||||
* @return the byte array representing the DER encoded certificate
|
||||
*/
|
||||
public static byte[] getDerEncodedCertificate(final X509Certificate certificate) {
|
||||
try {
|
||||
return certificate.getEncoded();
|
||||
} catch (CertificateEncodingException ceEx) {
|
||||
log.error("Error converting certificate to ASN.1 DER Encoding.", ceEx);
|
||||
throw new UnexpectedServerException(
|
||||
"Encountered error while converting X509 Certificate to ASN.1 DER Encoding: "
|
||||
+ ceEx.getMessage(), ceEx);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse public key from public data segment generated by TPM 2.0.
|
||||
* @param publicArea the public area segment to parse
|
||||
* @return the RSA public key of the supplied public data
|
||||
*/
|
||||
public static RSAPublicKey parsePublicKey(final byte[] publicArea) {
|
||||
int pubLen = publicArea.length;
|
||||
if (pubLen < RSA_MODULUS_LENGTH) {
|
||||
throw new IllegalArgumentException(
|
||||
"EK or AK public data segment is not long enough");
|
||||
}
|
||||
// public data ends with 256 byte modulus
|
||||
byte[] modulus = HexUtils.subarray(publicArea,
|
||||
pubLen - RSA_MODULUS_LENGTH,
|
||||
pubLen - 1);
|
||||
return (RSAPublicKey) assemblePublicKey(modulus);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a public key where the modulus is in raw form.
|
||||
*
|
||||
* @param modulus
|
||||
* in byte array form
|
||||
* @return public key using specific modulus and the well known exponent
|
||||
*/
|
||||
public static PublicKey assemblePublicKey(final byte[] modulus) {
|
||||
return assemblePublicKey(Hex.encodeHexString(modulus));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a public key where the modulus is Hex encoded.
|
||||
*
|
||||
* @param modulus
|
||||
* hex encoded modulus
|
||||
* @return public key using specific modulus and the well known exponent
|
||||
*/
|
||||
public static PublicKey assemblePublicKey(final String modulus) {
|
||||
return assemblePublicKey(new BigInteger(modulus, DEFAULT_IV_SIZE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Assembles a public key using a defined big int modulus and the well known exponent.
|
||||
*/
|
||||
public static PublicKey assemblePublicKey(final BigInteger modulus) {
|
||||
// generate a key spec using mod and exp
|
||||
RSAPublicKeySpec keySpec = new RSAPublicKeySpec(modulus, EXPONENT);
|
||||
|
||||
// create the public key
|
||||
try {
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||
return keyFactory.generatePublic(keySpec);
|
||||
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
|
||||
throw new UnexpectedServerException(
|
||||
"Encountered unexpected error creating public key: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Will attempt to decrypt the asymmetric blob that originated from an
|
||||
* {@link hirs.structs.elements.tpm.IdentityRequest} using the cipher transformation.
|
||||
*
|
||||
* @param asymmetricBlob to be decrypted
|
||||
* @param scheme to decrypt with
|
||||
* @param privateKey cipher private key
|
||||
* @return decrypted blob
|
||||
*/
|
||||
public static byte[] decryptAsymmetricBlob(final byte[] asymmetricBlob,
|
||||
final EncryptionScheme scheme,
|
||||
final PrivateKey privateKey) {
|
||||
try {
|
||||
// create a cipher from the specified transformation
|
||||
Cipher cipher = Cipher.getInstance(scheme.toString());
|
||||
|
||||
switch (scheme) {
|
||||
case OAEP:
|
||||
OAEPParameterSpec spec =
|
||||
new OAEPParameterSpec("Sha1", "MGF1", MGF1ParameterSpec.SHA1,
|
||||
new PSource.PSpecified("".getBytes()));
|
||||
|
||||
cipher.init(Cipher.PRIVATE_KEY, privateKey, spec);
|
||||
break;
|
||||
default:
|
||||
// initialize the cipher to decrypt using the ACA private key.
|
||||
cipher.init(Cipher.DECRYPT_MODE, privateKey);
|
||||
}
|
||||
|
||||
cipher.update(asymmetricBlob);
|
||||
|
||||
return cipher.doFinal();
|
||||
} catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException
|
||||
| BadPaddingException | IllegalBlockSizeException
|
||||
| InvalidAlgorithmParameterException e) {
|
||||
throw new IdentityProcessingException(
|
||||
"Encountered error while decrypting asymmetric blob of an identity request: "
|
||||
+ e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Will attempt to decrypt the symmetric blob that originated from an
|
||||
* {@link hirs.structs.elements.tpm.IdentityRequest} using the specified symmetric key
|
||||
* and cipher transformation.
|
||||
*
|
||||
* @param symmetricBlob to be decrypted
|
||||
* @param symmetricKey to use to decrypt
|
||||
* @param iv to use with decryption cipher
|
||||
* @param transformation of the cipher
|
||||
* @return decrypted symmetric blob
|
||||
*/
|
||||
public static byte[] decryptSymmetricBlob(final byte[] symmetricBlob, final byte[] symmetricKey,
|
||||
final byte[] iv, final String transformation) {
|
||||
try {
|
||||
// create a cipher from the specified transformation
|
||||
Cipher cipher = Cipher.getInstance(transformation);
|
||||
|
||||
// generate a key specification to initialize the cipher
|
||||
SecretKeySpec keySpec = new SecretKeySpec(symmetricKey, "AES");
|
||||
|
||||
// initialize the cipher to decrypt using the symmetric key
|
||||
cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(iv));
|
||||
|
||||
// decrypt the symmetric blob
|
||||
return cipher.doFinal(symmetricBlob);
|
||||
} catch (IllegalBlockSizeException | InvalidKeyException | NoSuchAlgorithmException
|
||||
| BadPaddingException | NoSuchPaddingException
|
||||
| InvalidAlgorithmParameterException exception) {
|
||||
log.error("Encountered error while decrypting symmetric blob of an identity request: "
|
||||
+ exception.getMessage(), exception);
|
||||
}
|
||||
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
public static SymmetricKey generateSymmetricKey() {
|
||||
// create a session key for the CA contents
|
||||
byte[] responseSymmetricKey =
|
||||
generateRandomBytes(DEFAULT_IV_SIZE);
|
||||
|
||||
// create a symmetric key struct for the CA contents
|
||||
SymmetricKey sessionKey =
|
||||
new SimpleStructBuilder<>(SymmetricKey.class)
|
||||
.set("algorithmId", SymmetricKey.ALGORITHM_AES)
|
||||
.set("encryptionScheme", SymmetricKey.SCHEME_CBC)
|
||||
.set("key", responseSymmetricKey).build();
|
||||
return sessionKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the first step of the TPM 2.0 identity claim process. Takes an ek, ak, and secret
|
||||
* and then generates a seed that is used to generate AES and HMAC keys. Parses the ak name.
|
||||
* Encrypts the seed with the public ek. Uses the AES key to encrypt the secret. Uses the HMAC
|
||||
* key to generate an HMAC to cover the encrypted secret and the ak name. The output is an
|
||||
* encrypted blob that acts as the first part of a challenge-response authentication mechanism
|
||||
* to validate an identity claim.
|
||||
*
|
||||
* Equivalent to calling tpm2_makecredential using tpm2_tools.
|
||||
*
|
||||
* @param ek endorsement key in the identity claim
|
||||
* @param ak attestation key in the identity claim
|
||||
* @param secret a nonce
|
||||
* @return the encrypted blob forming the identity claim challenge
|
||||
*/
|
||||
public static ByteString tpm20MakeCredential(final RSAPublicKey ek, final RSAPublicKey ak,
|
||||
final byte[] secret) {
|
||||
// check size of the secret
|
||||
if (secret.length > MAX_SECRET_LENGTH) {
|
||||
throw new IllegalArgumentException("Secret must be " + MAX_SECRET_LENGTH
|
||||
+ " bytes or smaller.");
|
||||
}
|
||||
|
||||
// generate a random 32 byte seed
|
||||
byte[] seed = generateRandomBytes(SEED_LENGTH);
|
||||
|
||||
try {
|
||||
// encrypt seed with pubEk
|
||||
Cipher asymCipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
|
||||
OAEPParameterSpec oaepSpec = new OAEPParameterSpec("SHA-256", "MGF1",
|
||||
MGF1ParameterSpec.SHA256, new PSource.PSpecified("IDENTITY\0".getBytes()));
|
||||
asymCipher.init(Cipher.PUBLIC_KEY, ek, oaepSpec);
|
||||
asymCipher.update(seed);
|
||||
byte[] encSeed = asymCipher.doFinal();
|
||||
|
||||
// generate ak name from akMod
|
||||
byte[] akModTemp = ak.getModulus().toByteArray();
|
||||
byte[] akMod = new byte[RSA_MODULUS_LENGTH];
|
||||
int startpos = 0;
|
||||
// BigIntegers are signed, so a modulus that has a first bit of 1
|
||||
// will be padded with a zero byte that must be removed
|
||||
if (akModTemp[0] == 0x00) {
|
||||
startpos = 1;
|
||||
}
|
||||
System.arraycopy(akModTemp, startpos, akMod, 0, RSA_MODULUS_LENGTH);
|
||||
byte[] akName = ProvisionUtils.generateAkName(akMod);
|
||||
|
||||
// generate AES and HMAC keys from seed
|
||||
byte[] aesKey = ProvisionUtils.cryptKDFa(seed, "STORAGE", akName, AES_KEY_LENGTH_BYTES);
|
||||
byte[] hmacKey = ProvisionUtils.cryptKDFa(seed, "INTEGRITY", null, HMAC_KEY_LENGTH_BYTES);
|
||||
|
||||
// use two bytes to add a size prefix on secret
|
||||
ByteBuffer b;
|
||||
b = ByteBuffer.allocate(2);
|
||||
b.putShort((short) (secret.length));
|
||||
byte[] secretLength = b.array();
|
||||
byte[] secretBytes = new byte[secret.length + 2];
|
||||
System.arraycopy(secretLength, 0, secretBytes, 0, 2);
|
||||
System.arraycopy(secret, 0, secretBytes, 2, secret.length);
|
||||
|
||||
// encrypt size prefix + secret with AES key
|
||||
Cipher symCipher = Cipher.getInstance("AES/CFB/NoPadding");
|
||||
byte[] defaultIv = HexUtils.hexStringToByteArray("00000000000000000000000000000000");
|
||||
IvParameterSpec ivSpec = new IvParameterSpec(defaultIv);
|
||||
symCipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(aesKey, "AES"), ivSpec);
|
||||
byte[] encSecret = symCipher.doFinal(secretBytes);
|
||||
|
||||
// generate HMAC covering encrypted secret and ak name
|
||||
Mac integrityHmac = Mac.getInstance("HmacSHA256");
|
||||
SecretKeySpec integrityKey = new SecretKeySpec(hmacKey, integrityHmac.getAlgorithm());
|
||||
integrityHmac.init(integrityKey);
|
||||
byte[] message = new byte[encSecret.length + akName.length];
|
||||
System.arraycopy(encSecret, 0, message, 0, encSecret.length);
|
||||
System.arraycopy(akName, 0, message, encSecret.length, akName.length);
|
||||
integrityHmac.update(message);
|
||||
byte[] integrity = integrityHmac.doFinal();
|
||||
b = ByteBuffer.allocate(2);
|
||||
b.putShort((short) (HMAC_SIZE_LENGTH_BYTES + HMAC_KEY_LENGTH_BYTES + encSecret.length));
|
||||
byte[] topSize = b.array();
|
||||
|
||||
// return ordered blob of assembled credentials
|
||||
byte[] bytesToReturn = ProvisionUtils.assembleCredential(topSize, integrity, encSecret, encSeed);
|
||||
return ByteString.copyFrom(bytesToReturn);
|
||||
|
||||
} catch (BadPaddingException | IllegalBlockSizeException | NoSuchAlgorithmException
|
||||
| InvalidKeyException | InvalidAlgorithmParameterException
|
||||
| NoSuchPaddingException e) {
|
||||
throw new IdentityProcessingException(
|
||||
"Encountered error while making the identity claim challenge: "
|
||||
+ e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate asymmetric contents part of the identity response.
|
||||
*
|
||||
* @param identityKey identity requests symmetric contents, otherwise, the identity proof
|
||||
* @param sessionKey identity response session key
|
||||
* @param publicKey of the EK certificate contained within the identity proof
|
||||
* @return encrypted asymmetric contents
|
||||
*/
|
||||
public static byte[] generateAsymmetricContents(final byte[] identityKey,
|
||||
final byte[] sessionKey,
|
||||
final PublicKey publicKey) {
|
||||
try {
|
||||
// create a SHA1 digest of the identity key
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-1");
|
||||
md.update(identityKey);
|
||||
|
||||
// generate the digest
|
||||
byte[] identityDigest = md.digest();
|
||||
|
||||
// combine the session key with the digest of the identity key
|
||||
byte[] asymmetricContents = ArrayUtils.addAll(sessionKey, identityDigest);
|
||||
|
||||
// encrypt the asymmetric contents and return
|
||||
OAEPParameterSpec oaepSpec =
|
||||
new OAEPParameterSpec("Sha1", "MGF1", MGF1ParameterSpec.SHA1,
|
||||
new PSource.PSpecified("TCPA".getBytes()));
|
||||
|
||||
// initialize the asymmetric cipher using the default OAEP transformation
|
||||
Cipher cipher = Cipher.getInstance(EncryptionScheme.OAEP.toString());
|
||||
|
||||
// initialize the cipher using the public spec with the additional OAEP specification
|
||||
cipher.init(Cipher.PUBLIC_KEY, publicKey, oaepSpec);
|
||||
|
||||
return cipher.doFinal(asymmetricContents);
|
||||
} catch (NoSuchAlgorithmException | IllegalBlockSizeException | NoSuchPaddingException
|
||||
| InvalidKeyException | BadPaddingException
|
||||
| InvalidAlgorithmParameterException e) {
|
||||
throw new CertificateProcessingException(
|
||||
"Encountered error while generating ACA session key: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the IV from the identity request. That is, take the first block of data from the
|
||||
* symmetric blob and treat that as the IV. This modifies the original symmetric block.
|
||||
*
|
||||
* @param identityRequest to extract the IV from
|
||||
* @return the IV from the identity request
|
||||
*/
|
||||
public static byte[] extractInitialValue(final IdentityRequest identityRequest) {
|
||||
// make a reference to the symmetric blob
|
||||
byte[] symmetricBlob = identityRequest.getSymmetricBlob();
|
||||
|
||||
// create the IV
|
||||
byte[] iv = new byte[DEFAULT_IV_SIZE];
|
||||
|
||||
// initialize a new symmetric blob with the length of the original minus the IV
|
||||
byte[] updatedBlob = new byte[symmetricBlob.length - iv.length];
|
||||
|
||||
// copy the IV out of the original symmetric blob
|
||||
System.arraycopy(symmetricBlob, 0, iv, 0, iv.length);
|
||||
|
||||
// copy everything but the IV out of the original blob into the new blob
|
||||
System.arraycopy(symmetricBlob, iv.length, updatedBlob, 0, updatedBlob.length);
|
||||
|
||||
// reassign the symmetric blob to the request.
|
||||
identityRequest.setSymmetricBlob(updatedBlob);
|
||||
|
||||
return iv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the Identity Response using the identity credential and the session key.
|
||||
*
|
||||
* @param credential the identity credential
|
||||
* @param symmetricKey generated session key for this request/response chain
|
||||
* @return identity response for an identity request
|
||||
*/
|
||||
public static SymmetricAttestation generateAttestation(final X509Certificate credential,
|
||||
final SymmetricKey symmetricKey) {
|
||||
try {
|
||||
// initialize the symmetric cipher
|
||||
Cipher aesCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
|
||||
// generate a key specification to initialize the cipher
|
||||
SecretKeySpec keySpec = new SecretKeySpec(symmetricKey.getKey(), "AES");
|
||||
|
||||
// fill IV with random bytes
|
||||
byte[] credentialIV = generateRandomBytes(DEFAULT_IV_SIZE);
|
||||
|
||||
// create IV encryption parameter specification
|
||||
IvParameterSpec ivParameterSpec = new IvParameterSpec(credentialIV);
|
||||
|
||||
// initialize the cipher to decrypt using the symmetric key
|
||||
aesCipher.init(Cipher.ENCRYPT_MODE, keySpec, ivParameterSpec);
|
||||
|
||||
// encrypt the credential
|
||||
byte[] encryptedCredential = aesCipher.doFinal(credential.getEncoded());
|
||||
|
||||
// prepend the IV to the encrypted credential
|
||||
byte[] credentialBytes = ArrayUtils.addAll(credentialIV, encryptedCredential);
|
||||
|
||||
// create attestation for identity response that contains the credential
|
||||
SymmetricAttestation attestation =
|
||||
new SimpleStructBuilder<>(SymmetricAttestation.class)
|
||||
.set("credential", credentialBytes)
|
||||
.set("algorithm",
|
||||
new SimpleStructBuilder<>(SymmetricKeyParams.class)
|
||||
.set("algorithmId", SymmetricKeyParams.ALGORITHM_AES)
|
||||
.set("encryptionScheme",
|
||||
SymmetricKeyParams.SCHEME_CBC_PKCS5PADDING)
|
||||
.set("signatureScheme", 0).build()).build();
|
||||
|
||||
return attestation;
|
||||
|
||||
} catch (BadPaddingException | IllegalBlockSizeException | NoSuchAlgorithmException
|
||||
| InvalidKeyException | InvalidAlgorithmParameterException | NoSuchPaddingException
|
||||
| CertificateEncodingException exception) {
|
||||
throw new CertificateProcessingException(
|
||||
"Encountered error while generating Identity Response: "
|
||||
+ exception.getMessage(), exception);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("magicnumber")
|
||||
public static byte[] assembleCredential(final byte[] topSize, final byte[] integrityHmac,
|
||||
final byte[] encryptedSecret,
|
||||
final byte[] encryptedSeed) {
|
||||
/*
|
||||
* Credential structure breakdown with endianness:
|
||||
* 0-1 topSize (2), LE
|
||||
* 2-3 hashsize (2), BE always 0x0020
|
||||
* 4-35 integrity HMac (32)
|
||||
* 36-133 (98 = 32*3 +2) of zeros, copy over from encSecret starting at [36]
|
||||
* 134-135 (2) LE size, always 0x0001
|
||||
* 136-391 (256) copy over with encSeed
|
||||
* */
|
||||
byte[] credentialBlob = new byte[TPM2_CREDENTIAL_BLOB_SIZE];
|
||||
credentialBlob[0] = topSize[1];
|
||||
credentialBlob[1] = topSize[0];
|
||||
credentialBlob[2] = 0x00;
|
||||
credentialBlob[3] = 0x20;
|
||||
System.arraycopy(integrityHmac, 0, credentialBlob, 4, 32);
|
||||
for (int i = 0; i < 98; i++) {
|
||||
credentialBlob[36 + i] = 0x00;
|
||||
}
|
||||
System.arraycopy(encryptedSecret, 0, credentialBlob, 36, encryptedSecret.length);
|
||||
credentialBlob[134] = 0x00;
|
||||
credentialBlob[135] = 0x01;
|
||||
System.arraycopy(encryptedSeed, 0, credentialBlob, 136, 256);
|
||||
// return the result
|
||||
return credentialBlob;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the AK name from the AK Modulus.
|
||||
* @param akModulus modulus of an attestation key
|
||||
* @return the ak name byte array
|
||||
* @throws java.security.NoSuchAlgorithmException Underlying SHA256 method used a bad algorithm
|
||||
*/
|
||||
public static byte[] generateAkName(final byte[] akModulus) throws NoSuchAlgorithmException {
|
||||
byte[] namePrefix = HexUtils.hexStringToByteArray(AK_NAME_PREFIX);
|
||||
byte[] hashPrefix = HexUtils.hexStringToByteArray(AK_NAME_HASH_PREFIX);
|
||||
byte[] toHash = new byte[hashPrefix.length + akModulus.length];
|
||||
System.arraycopy(hashPrefix, 0, toHash, 0, hashPrefix.length);
|
||||
System.arraycopy(akModulus, 0, toHash, hashPrefix.length, akModulus.length);
|
||||
byte[] nameHash = sha256hash(toHash);
|
||||
byte[] toReturn = new byte[namePrefix.length + nameHash.length];
|
||||
System.arraycopy(namePrefix, 0, toReturn, 0, namePrefix.length);
|
||||
System.arraycopy(nameHash, 0, toReturn, namePrefix.length, nameHash.length);
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
/**
|
||||
* This replicates the TPM 2.0 CryptKDFa function to an extent. It will only work for generation
|
||||
* that uses SHA-256, and will only generate values of 32 B or less. Counters above zero and
|
||||
* multiple contexts are not supported in this implementation. This should work for all uses of
|
||||
* the KDF for TPM2_MakeCredential.
|
||||
*
|
||||
* @param seed random value used to generate the key
|
||||
* @param label first portion of message used to generate key
|
||||
* @param context second portion of message used to generate key
|
||||
* @param sizeInBytes size of key to generate in bytes
|
||||
* @return the derived key
|
||||
* @throws NoSuchAlgorithmException Wrong crypto algorithm selected
|
||||
* @throws java.security.InvalidKeyException Invalid key used
|
||||
*/
|
||||
@SuppressWarnings("magicnumber")
|
||||
public static byte[] cryptKDFa(final byte[] seed, final String label, final byte[] context,
|
||||
final int sizeInBytes)
|
||||
throws NoSuchAlgorithmException, InvalidKeyException {
|
||||
ByteBuffer b = ByteBuffer.allocate(4);
|
||||
b.putInt(1);
|
||||
byte[] counter = b.array();
|
||||
// get the label
|
||||
String labelWithEnding = label;
|
||||
if (label.charAt(label.length() - 1) != "\0".charAt(0)) {
|
||||
labelWithEnding = label + "\0";
|
||||
}
|
||||
byte[] labelBytes = labelWithEnding.getBytes();
|
||||
b = ByteBuffer.allocate(4);
|
||||
b.putInt(sizeInBytes * 8);
|
||||
byte[] desiredSizeInBits = b.array();
|
||||
int sizeOfMessage = 8 + labelBytes.length;
|
||||
if (context != null) {
|
||||
sizeOfMessage += context.length;
|
||||
}
|
||||
byte[] message = new byte[sizeOfMessage];
|
||||
int marker = 0;
|
||||
System.arraycopy(counter, 0, message, marker, 4);
|
||||
marker += 4;
|
||||
System.arraycopy(labelBytes, 0, message, marker, labelBytes.length);
|
||||
marker += labelBytes.length;
|
||||
if (context != null) {
|
||||
System.arraycopy(context, 0, message, marker, context.length);
|
||||
marker += context.length;
|
||||
}
|
||||
System.arraycopy(desiredSizeInBits, 0, message, marker, 4);
|
||||
Mac hmac;
|
||||
byte[] toReturn = new byte[sizeInBytes];
|
||||
|
||||
hmac = Mac.getInstance("HmacSHA256");
|
||||
SecretKeySpec hmacKey = new SecretKeySpec(seed, hmac.getAlgorithm());
|
||||
hmac.init(hmacKey);
|
||||
hmac.update(message);
|
||||
byte[] hmacResult = hmac.doFinal();
|
||||
System.arraycopy(hmacResult, 0, toReturn, 0, sizeInBytes);
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method takes the provided TPM Quote and splits it between the PCR
|
||||
* quote and the signature hash.
|
||||
* @param tpmQuote contains hash values for the quote and the signature
|
||||
*/
|
||||
public static String parseTPMQuoteHash(final String tpmQuote) {
|
||||
if (tpmQuote != null) {
|
||||
String[] lines = tpmQuote.split(":");
|
||||
if (lines[1].contains("signature")) {
|
||||
return lines[1].replace("signature", "").trim();
|
||||
} else {
|
||||
return lines[1].trim();
|
||||
}
|
||||
}
|
||||
|
||||
return "null";
|
||||
}
|
||||
|
||||
/**
|
||||
* This method takes the provided TPM Quote and splits it between the PCR
|
||||
* quote and the signature hash.
|
||||
* @param tpmQuote contains hash values for the quote and the signature
|
||||
*/
|
||||
public static String parseTPMQuoteSignature(final String tpmQuote) {
|
||||
if (tpmQuote != null) {
|
||||
String[] lines = tpmQuote.split(":");
|
||||
|
||||
return lines[2].trim();
|
||||
}
|
||||
|
||||
return "null";
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the sha256 hash of the given blob.
|
||||
* @param blob byte array to take the hash of
|
||||
* @return sha256 hash of blob
|
||||
* @throws NoSuchAlgorithmException improper algorithm selected
|
||||
*/
|
||||
public static byte[] sha256hash(final byte[] blob) throws NoSuchAlgorithmException {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
md.update(blob);
|
||||
return md.digest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a array of random bytes.
|
||||
*
|
||||
* @param numberOfBytes
|
||||
* to be generated
|
||||
* @return byte array filled with the specified number of bytes.
|
||||
*/
|
||||
public static byte[] generateRandomBytes(final int numberOfBytes) {
|
||||
byte[] bytes = new byte[numberOfBytes];
|
||||
SecureRandom random = new SecureRandom();
|
||||
random.nextBytes(bytes);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
@SuppressWarnings("magicnumber")
|
||||
public static int daysBetween(final Date date1, final Date date2) {
|
||||
return (int) ((date2.getTime() - date1.getTime()) / (1000 * 60 * 60 * 24));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user