/* * Copyright (C) 2014 Intel Corporation * All rights reserved. */ package com.intel.mtwilson.trustagent.setup; import com.fasterxml.jackson.databind.ObjectMapper; import com.intel.dcsg.cpg.crypto.Sha1Digest; import com.intel.dcsg.cpg.crypto.SimpleKeystore; import com.intel.dcsg.cpg.io.FileResource; import com.intel.dcsg.cpg.io.UUID; import com.intel.dcsg.cpg.x509.X509Util; import com.intel.mtwilson.as.rest.v2.model.CaCertificateFilterCriteria; import com.intel.mtwilson.attestation.client.jaxrs.CaCertificates; import com.intel.mtwilson.setup.AbstractSetupTask; import com.intel.mtwilson.tpm.endorsement.client.jaxrs.TpmEndorsements; import com.intel.mtwilson.tpm.endorsement.model.TpmEndorsement; import com.intel.mtwilson.tpm.endorsement.model.TpmEndorsementCollection; import com.intel.mtwilson.tpm.endorsement.model.TpmEndorsementFilterCriteria; import com.intel.mtwilson.trustagent.TrustagentConfiguration; import com.intel.mtwilson.trustagent.niarl.ProvisionTPM; import com.intel.mtwilson.trustagent.niarl.Util; import com.intel.mtwilson.trustagent.tpm.tasks.ReadEndorsementCertificate; import gov.niarl.his.privacyca.TpmModule; import gov.niarl.his.privacyca.TpmModule.TpmModuleException; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.security.InvalidKeyException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.SignatureException; import java.security.UnrecoverableEntryException; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.List; import org.apache.commons.codec.binary.Base64; import org.apache.commons.io.IOUtils; /** * * @author jbuhacoff */ public class RequestEndorsementCertificate extends AbstractSetupTask { private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(RequestEndorsementCertificate.class); private TrustagentConfiguration config; private File keystoreFile; private SimpleKeystore keystore; private String tpmOwnerSecretHex; private String url; private String username; private String password; private X509Certificate ekCert; private CaCertificates caCertificatesClient; private TpmEndorsements tpmEndorsementsClient; private File endorsementAuthoritiesFile; private List<X509Certificate> endorsementAuthorities; private UUID hostHardwareId; @Override protected void configure() throws Exception { config = new TrustagentConfiguration(getConfiguration()); url = config.getMtWilsonApiUrl(); username = config.getMtWilsonApiUsername(); password = config.getMtWilsonApiPassword(); if (url == null || url.isEmpty()) { configuration("Mt Wilson URL [mtwilson.api.url] must be set"); } if (username == null || username.isEmpty()) { configuration("Mt Wilson username [mtwilson.api.username] must be set"); } if (password == null || password.isEmpty()) { configuration("Mt Wilson password [mtwilson.api.password] must be set"); } tpmOwnerSecretHex = config.getTpmOwnerSecretHex(); // we check it here because ProvisionTPM calls getOwnerSecret() which relies on this if (tpmOwnerSecretHex == null) { configuration("TPM Owner Secret is not configured: %s", TrustagentConfiguration.TPM_OWNER_SECRET); // this constant is the name of the property, literally "tpm.owner.secret" } if (!Util.isOwner(config.getTpmOwnerSecret())) { configuration("Trust Agent is not the TPM owner"); } keystoreFile = config.getTrustagentKeystoreFile(); /* if (keystoreFile.exists()) { keystore = new SimpleKeystore(new FileResource(keystoreFile), config.getTrustagentKeystorePassword()); try { X509Certificate endorsementCA = keystore.getX509Certificate("endorsement", SimpleKeystore.CA); log.debug("Endorsement CA {}", Sha1Digest.digestOf(endorsementCA.getEncoded()).toHexString()); } catch (NoSuchAlgorithmException | UnrecoverableEntryException | KeyStoreException | CertificateEncodingException e) { configuration("Endorsement CA certificate cannot be loaded"); } } else { configuration("Keystore file is missing"); } */ endorsementAuthoritiesFile = config.getEndorsementAuthoritiesFile(); if( endorsementAuthoritiesFile == null ) { configuration("Endorsement authorities file location is not set"); } try { tpmEndorsementsClient = new TpmEndorsements(config.getMtWilsonClientProperties()); } catch(Exception e) { configuration(e, "Cannot configure TPM Endorsements API client"); } try { caCertificatesClient = new CaCertificates(config.getMtWilsonClientProperties()); } catch(Exception e) { configuration(e, "Cannot configure CA Certificates API client"); } String hostHardwareIdHex = config.getHardwareUuid(); if( hostHardwareIdHex == null || hostHardwareIdHex.isEmpty() || !UUID.isValid(hostHardwareIdHex) ) { configuration("Host hardware UUID [hardware.uuid] must be set"); } else { hostHardwareId = UUID.valueOf(hostHardwareIdHex); } } @Override protected void validate() throws Exception { try { readEndorsementCertificate(); } catch(Exception e) { validation(e, "Cannot read endorsement certificate"); } /* X509Certificate endorsementCA = keystore.getX509Certificate("endorsement", SimpleKeystore.CA); try { ekCert.verify(endorsementCA.getPublicKey()); } catch (SignatureException e) { validation("Known Endorsement CA did not sign TPM EC", e); } catch (CertificateException | NoSuchAlgorithmException | InvalidKeyException | NoSuchProviderException e) { validation("Unable to verify TPM EC", e); } */ if( !endorsementAuthoritiesFile.exists()) { validation("Endorsement authorities file is missing"); } if( endorsementAuthorities == null || endorsementAuthorities.isEmpty() ) { validation("No endorsement authorities"); } else { String errorMessage = ""; log.debug("Found {} endorsement authorities in {}", endorsementAuthorities.size(), endorsementAuthoritiesFile.getAbsolutePath()); // if we find one certificate authority that can verify our current EC, then we don't need to request a new EC if(!isEkSignedByEndorsementAuthority()) { errorMessage = String.format("Unable to verify TPM EC with %d authorities from %s.", endorsementAuthorities.size(), endorsementAuthoritiesFile.getAbsolutePath()); // check if we have registered with MTW if (!isEkRegisteredWithMtWilson()) { errorMessage += "EC is also not registered with Mt.Wilson"; validation(errorMessage); } // validation("Unable to verify TPM EC with %d authorities from %s", endorsementAuthorities.size(), endorsementAuthoritiesFile.getAbsolutePath()); } } } @Override protected void execute() throws Exception { /* System.setProperty("javax.net.ssl.trustStore", config.getTrustagentKeystoreFile().getAbsolutePath()); System.setProperty("javax.net.ssl.trustStorePassword", config.getTrustagentKeystorePassword()); System.setProperty("javax.net.ssl.keyStore", config.getTrustagentKeystoreFile().getAbsolutePath()); System.setProperty("javax.net.ssl.keyStorePassword", config.getTrustagentKeystorePassword()); */ // try to read the local EC from the TPM ; will set tpmEndorsementCertificate if successful, or leave it null if unsuccessful readEndorsementCertificate(); // first check if we have an EC and if it can be validated against known manufacturer CA certs log.debug("RequestEndorsementCertificate checking if EC is issued by known manufacturer"); downloadEndorsementAuthorities(); if( isEkSignedByEndorsementAuthority() ) { log.debug("EC is already issued by endorsement authority; no need to request new EC"); return; } // second check if we have an EC and if it's already registered with Mt Wilson log.debug("RequestEndorsementCertificate checking if EC is registered with Mt Wilson"); if( isEkRegisteredWithMtWilson() ) { log.debug("EK is already registered with Mt Wilson; no need to request an EC"); return; } // now if we have an EC register it with Mt Wilson if( ekCert != null ) { log.debug("RequestEndorsementCertificate registering EC with Mt Wilson"); registerEkWithMtWilson(); } else { // otherwise if we don't have an EC try to get our EK endorsed by Mt Wilson and install the received EC in TPM NNRAM log.debug("RequestEndorsementCertificate endorsing EC with Mt Wilson"); endorseTpmWithMtWilson(); } } private void readEndorsementCertificate() throws Exception { byte[] ekCertBytes; try { ekCertBytes = TpmModule.getCredential(config.getTpmOwnerSecret(), "EC"); log.debug("EC base64: {}", Base64.encodeBase64String(ekCertBytes)); ekCert = X509Util.decodeDerCertificate(ekCertBytes); } catch (TpmModuleException e) { ekCert = null; if (e.getErrorCode() != null) { switch (e.getErrorCode()) { case 1: throw new IllegalArgumentException("Incorrect TPM owner password"); case 2: //throw new IllegalArgumentException("Endorsement certificate needs to be requested"); return; default: throw new IllegalArgumentException(String.format("Error code %d while validating EC", e.getErrorCode())); } } log.debug("Failed to get EC from TPM using NIARL_TPM_Module"); // try with tpm tools // /root/tpm-tools-1.3.8-patched/src/tpm_mgmt/tpm_getpubek // that gets the EK modulus but we still need the EC: // tpm_nvinfo -i 0x1000f000 // tpm_nvread -i 0x1000f000 -x -t -pOWNER_AUTH -s 834 -f mfr.crt.tpm // openssl x509 -in mfr.crt.tpm -inform der -text (works for Nuvoton, will not work for some others if they wrapped EC in a TCG structure... then have to use dd to remove initial bytes, and sometimes do other corrections for invalid length fields) throw new RuntimeException("Failed to get EC from TPM using NIARL_TPM_Module"); } } private void downloadEndorsementAuthorities() throws Exception { // we create or replace our endorsement.pem file with what mtwilson provides // because it's mtwilson that will be evaluating it anyway in order to // issue AIK certiicates later, and because it's handy for the admin to // see locally the list of certs for troubleshooting -- so this could be // converted to getting the array of X509Certificate objects directly // from the client without saving anything to disk. CaCertificateFilterCriteria criteria = new CaCertificateFilterCriteria(); criteria.domain = "ek"; // or "endorsement" String endorsementAuthoritiesPem = caCertificatesClient.searchCaCertificatesPem(criteria); try(OutputStream out = new FileOutputStream(endorsementAuthoritiesFile)) { IOUtils.write(endorsementAuthoritiesPem, out); } try(InputStream in = new FileInputStream(endorsementAuthoritiesFile)) { String pem = IOUtils.toString(in); endorsementAuthorities = X509Util.decodePemCertificates(pem); log.debug("Found {} endorsement authorities in {}", endorsementAuthorities.size(), endorsementAuthoritiesFile.getAbsolutePath()); } } private boolean isEkSignedByEndorsementAuthority() { for(X509Certificate ca : endorsementAuthorities) { try { log.debug("Trying to verify EC with {}", ca.getSubjectX500Principal().getName()); ekCert.verify(ca.getPublicKey()); log.debug("Verified EC with {}", ca.getSubjectX500Principal().getName()); return true; } catch (SignatureException e) { log.debug("Endorsement CA '{}' did not sign TPM EC: {}", ca.getSubjectX500Principal().getName(), e.getMessage()); } catch (CertificateException | NoSuchAlgorithmException | InvalidKeyException | NoSuchProviderException | NullPointerException e) { log.debug("Unable to verify TPM EC '{}': {}", ca.getSubjectX500Principal().getName(), e.getMessage()); } } return false; } private boolean isEkRegisteredWithMtWilson() throws Exception { TpmEndorsementFilterCriteria criteria = new TpmEndorsementFilterCriteria(); criteria.hardwareUuidEqualTo = hostHardwareId.toString(); TpmEndorsementCollection collection = tpmEndorsementsClient.searchTpmEndorsements(criteria); if( collection.getTpmEndorsements().isEmpty() ) { ObjectMapper mapper = new ObjectMapper(); log.debug("Did not find EC with search criteria {}", mapper.writeValueAsString(criteria)); return false; } log.debug("Found EC by hardware uuid"); return true; } private void registerEkWithMtWilson() throws Exception { TpmEndorsement tpmEndorsement = new TpmEndorsement(); tpmEndorsement.setId(new UUID()); tpmEndorsement.setCertificate(ekCert.getEncoded()); tpmEndorsement.setComment("registered by trust agent"); tpmEndorsement.setHardwareUuid(hostHardwareId.toString()); tpmEndorsement.setIssuer(ekCert.getIssuerDN().getName().replaceAll("\\x00", "")); // should be automatically set by server upon receiving the cert tpmEndorsement.setRevoked(false); // should default to false on server tpmEndorsementsClient.createTpmEndorsement(tpmEndorsement); } private void endorseTpmWithMtWilson() throws Exception { ProvisionTPM provisioner = new ProvisionTPM(); provisioner.configure(config.getConfiguration()); provisioner.run(); } }