From d2963ef99a6bce0572143601811b2efe427d7143 Mon Sep 17 00:00:00 2001 From: Cyrus <24922493+cyrus-dev@users.noreply.github.com> Date: Thu, 31 Aug 2023 15:01:05 -0400 Subject: [PATCH] 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. --- .../provision/AbstractRequestHandler.java | 196 +----- .../provision/CertificateRequestHandler.java | 16 +- .../provision/IdentityClaimHandler.java | 258 +------ .../provision/IdentityRequestHandler.java | 267 +------- .../provision/helper/ProvisionUtils.java | 642 ++++++++++++++++++ 5 files changed, 703 insertions(+), 676 deletions(-) create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/helper/ProvisionUtils.java diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/AbstractRequestHandler.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/AbstractRequestHandler.java index aac5a61d..18bb3f54 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/AbstractRequestHandler.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/AbstractRequestHandler.java @@ -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)); - } } diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/CertificateRequestHandler.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/CertificateRequestHandler.java index f2c3cce1..2ca0611c 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/CertificateRequestHandler.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/CertificateRequestHandler.java @@ -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 diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/IdentityClaimHandler.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/IdentityClaimHandler.java index 99f0b648..a2d6dfc8 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/IdentityClaimHandler.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/IdentityClaimHandler.java @@ -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 getPlatformCredentials(final CertificateRepository certificateRepository, + final EndorsementCredential ec) { + List 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; } } diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/IdentityRequestHandler.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/IdentityRequestHandler.java index 0bf80e36..142179e4 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/IdentityRequestHandler.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/IdentityRequestHandler.java @@ -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 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); - } - } } diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/helper/ProvisionUtils.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/helper/ProvisionUtils.java new file mode 100644 index 00000000..a2fbfc3e --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/helper/ProvisionUtils.java @@ -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)); + } +}