package com.intel.mtwilson.agent.intel; import com.intel.dcsg.cpg.crypto.RsaUtil; import com.intel.dcsg.cpg.tls.policy.TlsConnection; import com.intel.dcsg.cpg.tls.policy.TlsPolicy; import com.intel.dcsg.cpg.util.ByteArray; import com.intel.dcsg.cpg.net.IPv4Address; import com.intel.dcsg.cpg.net.InternetAddress; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.net.UnknownHostException; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.HashMap; import java.util.List; import java.util.regex.Pattern; import javax.xml.bind.JAXBException; import com.intel.mtwilson.agent.*; import org.apache.commons.codec.binary.Base64; import org.apache.commons.configuration.Configuration; import org.apache.commons.io.IOUtils; import com.intel.mountwilson.as.common.ASConfig; import com.intel.mountwilson.as.common.ASException; import com.intel.mountwilson.as.helper.CommandUtil; import com.intel.mountwilson.ta.data.ClientRequestType; import com.intel.mountwilson.ta.data.daa.response.DaaResponse; import com.intel.mtwilson.as.data.TblHosts; import com.intel.dcsg.cpg.x509.X509Util; import com.intel.mtwilson.i18n.ErrorCode; import com.intel.mtwilson.model.Measurement; import com.intel.mtwilson.model.Pcr; import com.intel.mtwilson.model.PcrEventLog; import com.intel.mtwilson.model.PcrIndex; import com.intel.mtwilson.model.PcrManifest; import com.intel.dcsg.cpg.crypto.Sha1Digest; import com.intel.dcsg.cpg.io.Platform; import static com.intel.mountwilson.as.helper.CommandUtil.singleQuoteEscapeShellArgument; import com.intel.mtwilson.My; import com.intel.mtwilson.MyFilesystem; import com.intel.mtwilson.tls.policy.factory.V1TlsPolicyFactory; import com.intel.mtwilson.trustagent.client.jaxrs.TrustAgentClient; import com.intel.mtwilson.trustagent.model.TpmQuoteResponse; import java.io.StringReader; import java.io.StringWriter; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.URL; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayList; import javax.xml.bind.PropertyException; import javax.xml.stream.FactoryConfigurationError; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamConstants; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import javax.xml.stream.XMLStreamWriter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * In order to use the TAHelper, you need to have attestation-service.properties * on your machine. * * Here are example properties that Jonathan has at * C:/Intel/CloudSecurity/attestation-service.properties: * * com.intel.mountwilson.as.home=C:/Intel/CloudSecurity/AttestationServiceData/aikverifyhome * com.intel.mountwilson.as.aikqverify.cmd=aikqverify.exe * com.intel.mountwilson.as.openssl.cmd=openssl.bat * * The corresponding files must exist. From the above example: * * C:/Intel/CloudSecurity/AttestationServiceData/aikverifyhome * C:/Intel/CloudSecurity/AttestationServiceData/aikverifyhome/data (can be * empty, TAHelper will save files there) * C:/Intel/CloudSecurity/AttestationServiceData/aikverifyhome/bin contains: * aikqverify.exe, cygwin1.dll * * @author dsmagadx */ public class TAHelper { private Logger log = LoggerFactory.getLogger(getClass()); // private String aikverifyhome; private String aikverifyhomeData; private String aikverifyhomeBin; private String aikverifyCmd; private Pattern pcrNumberPattern = Pattern.compile("[0-9]|[0-1][0-9]|2[0-3]"); // integer 0-23 with optional zero-padding (00, 01, ...) private Pattern pcrValuePattern = Pattern.compile("[0-9a-fA-F]{40}"); // 40-character hex string private String pcrNumberUntaint = "[^0-9]"; private String pcrValueUntaint = "[^0-9a-fA-F]"; private boolean quoteWithIPAddress = true; // to fix issue #1038 we use this secure default // private EntityManagerFactory entityManagerFactory; private String trustedAik = null; // host's AIK in PEM format, for use in verifying quotes (caller retrieves it from database and provides it to us) private boolean deleteTemporaryFiles = true; // normally we don't need to keep them around but during debugging it's helpful to set this to false public TAHelper(/*EntityManagerFactory entityManagerFactory*/) { // check mtwilson 2.0 configuration first String binPath = MyFilesystem.getApplicationFilesystem().getBootstrapFilesystem().getBinPath(); String varPath = MyFilesystem.getApplicationFilesystem().getBootstrapFilesystem().getVarPath() + File.separator + "aikqverify"; File bin = new File(binPath); File var = new File(varPath); if (bin.exists() && var.exists()) { aikverifyhomeBin = binPath; aikverifyhomeData = varPath; // opensslCmd = aikverifyhomeBin + File.separator + (Platform.isUnix() ? "openssl" : "openssl.bat"); //My.configuration().getConfiguration().getString("com.intel.mountwilson.as.openssl.cmd", "openssl.bat")); aikverifyCmd = aikverifyhomeBin + File.separator + (Platform.isUnix() ? "aikqverify" : "aikqverify.exe"); } else { // mtwilson 1.2 configuration Configuration config = ASConfig.getConfiguration(); String aikverifyhome = config.getString("com.intel.mountwilson.as.home", "C:/work/aikverifyhome"); aikverifyhomeData = aikverifyhome + File.separator + "data"; aikverifyhomeBin = aikverifyhome + File.separator + "bin"; // opensslCmd = aikverifyhomeBin + File.separator + config.getString("com.intel.mountwilson.as.openssl.cmd", "openssl.bat"); aikverifyCmd = aikverifyhomeBin + File.separator + config.getString("com.intel.mountwilson.as.aikqverify.cmd", "aikqverify.exe"); } quoteWithIPAddress = My.configuration().getConfiguration().getBoolean("mtwilson.tpm.quote.ipv4", true); // issue #1038 boolean foundAllRequiredFiles = true; String required[] = new String[]{aikverifyCmd, aikverifyhomeData}; for (String filename : required) { File file = new File(filename); if (!file.exists()) { log.warn(String.format("Invalid service configuration: Cannot find %s", filename)); foundAllRequiredFiles = false; } } if (!foundAllRequiredFiles) { throw new ASException(ErrorCode.AS_CONFIGURATION_ERROR, "Cannot find aikverify files"); } // we must be able to write to the data folder in order to save certificates, nones, public keys, etc. File datafolder = new File(aikverifyhomeData); if (!datafolder.canWrite()) { throw new ASException(ErrorCode.AS_CONFIGURATION_ERROR, String.format(" Cannot write to %s", aikverifyhomeData)); } // this.setEntityManagerFactory(entityManagerFactory); } public void setTrustedAik(String pem) { trustedAik = pem; } /** * The default value of deleteTemporaryFiles is true. * * @param deleteTemporaryFiles true to delete them, false to keep them after * processing */ public void setDeleteTemporaryFiles(boolean deleteTemporaryFiles) { this.deleteTemporaryFiles = deleteTemporaryFiles; } // DAA challenge // public void verifyAikWithDaa(String hostIpAddress, int port) { public void verifyAikWithDaa(TblHosts tblHosts) throws XMLStreamException { try { // TrustAgentSecureClient client = new TrustAgentSecureClient(hostIpAddress, port); // bug #497 com.intel.mtwilson.tls.policy.factory.TlsPolicyFactory tlsPolicyFactory = com.intel.mtwilson.tls.policy.factory.TlsPolicyFactory.createFactory(tblHosts);//getTlsPolicyWithKeystore(tlsPolicyName, tlsKeystore); TlsPolicy tlsPolicy = tlsPolicyFactory.getTlsPolicy(); String connectionString = tblHosts.getAddOnConnectionInfo(); if (connectionString == null || connectionString.isEmpty()) { if (tblHosts.getName() != null) { connectionString = String.format("https://%s:%d", tblHosts.getName(), tblHosts.getPort()); // without vendor scheme because we are passing directly to TrustAgentSEcureClient (instead of to HOstAgentFactory) } } URL url = new URL(connectionString); TrustAgentSecureClient client = new TrustAgentSecureClient(new TlsConnection(url, tlsPolicy)); String sessionId = generateSessionId(); // request AIK certificate and CA chain (the AIK Proof File) log.info("DAA requesting AIK proof"); String aikproof = client.getAIKCertificate(); // <identity_request></identity_request> try(FileOutputStream outAikProof = new FileOutputStream(new File(getDaaAikProofFileName(sessionId)))) { IOUtils.write(aikproof, outAikProof); } // create DAA challenge secret SecureRandom random = new SecureRandom(); byte[] secret = new byte[20]; random.nextBytes(secret); try(FileOutputStream outSecret = new FileOutputStream(new File(getDaaSecretFileName(sessionId)))) { IOUtils.write(secret, outSecret); } // encrypt DAA challenge secret using AIK public key so only TPM can read it CommandUtil.runCommand(String.format("aikchallenge %s %s %s %s", CommandUtil.doubleQuoteEscapeShellArgument(getDaaSecretFileName(sessionId)), CommandUtil.doubleQuoteEscapeShellArgument(getDaaAikProofFileName(sessionId)), CommandUtil.doubleQuoteEscapeShellArgument(getDaaChallengeFileName(sessionId)), CommandUtil.doubleQuoteEscapeShellArgument(getRSAPubkeyFileName(sessionId))), false, "Aik Challenge"); // send DAA challenge to Trust Agent and validate the response try(FileInputStream in = new FileInputStream(new File(getDaaChallengeFileName(sessionId)))) { String challenge = IOUtils.toString(in); DaaResponse response = client.sendDaaChallenge(challenge); byte[] responseContentDecoded = Base64.decodeBase64(response.getContent()); if (responseContentDecoded.length != secret.length) { throw new ASException(ErrorCode.AS_TRUST_AGENT_DAA_ERROR, "Incorrect challenge response"); } for (int i = 0; i < secret.length; i++) { if (responseContentDecoded[i] != secret[i]) { throw new ASException(ErrorCode.AS_TRUST_AGENT_DAA_ERROR, "Incorrect challenge response"); } } } } catch (KeyManagementException | JAXBException | NoSuchAlgorithmException | IOException ex) { log.error("Cannot verify AIK: " + ex.getMessage(), ex); } catch (ASException ex) { throw ex; } } // BUG #497 see the other getQuoteInformationForHost which is called from IntelHostAgent // public HashMap<String, PcrManifest> getQuoteInformationForHost(String hostIpAddress, String pcrList, String name, int port) { public PcrManifest getQuoteInformationForHost(TblHosts tblHosts) { try { // going to IntelHostAgent directly because 1) we are TAHelper so we know we need intel trust agents, 2) the HostAgent interface isn't ready yet for full generic usage, // 3) one day this entire function will be in the IntelHostAgent or that agent will call THIS function instaed of the othe way around // HostAgentFactory factory = new HostAgentFactory(); TlsPolicy tlsPolicy = V1TlsPolicyFactory.getInstance().getTlsPolicyWithKeystore(tblHosts.getTlsPolicyName(), tblHosts.getTlsKeystoreResource()); String connectionString = tblHosts.getAddOnConnectionInfo(); if (connectionString == null || connectionString.isEmpty()) { if (tblHosts.getName() != null) { // without vendor scheme because we are passing directly to TrustAgentSEcureClient (instead of to HOstAgentFactory) connectionString = String.format("https://%s:%d", tblHosts.getName(), tblHosts.getPort()); log.debug("getQuoteInformationForHost called with ip address and port {}", connectionString); } } else if (connectionString.startsWith("intel:")) { //log.debug("getQuoteInformationForHost called with intel connection string: {}", connectionString); connectionString = connectionString.substring(6); } URL url = new URL(connectionString); TrustAgentSecureClient client = new TrustAgentSecureClient(new TlsConnection(url, tlsPolicy)); // IntelHostAgent agent = new IntelHostAgent(client, new InternetAddress(tblHosts.getIPAddress().toString())); return getQuoteInformationForHost(tblHosts.getName(), client); } catch (ASException e) { throw e; } catch (UnknownHostException e) { throw new ASException(e, ErrorCode.AS_HOST_COMMUNICATION_ERROR, "Unknown host: " + (tblHosts.getName() == null ? "missing IP Address" : tblHosts.getName())); } catch (Exception e) { throw new ASException(e); } } public byte[] getIPAddress(String hostname) throws UnknownHostException { byte[] ipaddress; InternetAddress address = new InternetAddress(hostname); if (address.isIPv4()) { IPv4Address ipv4address = new IPv4Address(hostname); ipaddress = ipv4address.toByteArray(); if (ipaddress == null) { throw new UnknownHostException(hostname); // throws UnknownHostException } assert ipaddress.length == 4; } else if (address.isIPv6() || address.isHostname()) { // resolve it to find the ipv4 address InetAddress inetAddress = InetAddress.getByName(hostname); // throws UnknownHostException log.info("Resolved hostname {} to address {}", hostname, inetAddress.getHostAddress()); if (inetAddress instanceof Inet4Address) { ipaddress = inetAddress.getAddress(); assert ipaddress.length == 4; } else if (inetAddress instanceof Inet6Address) { if (((Inet6Address) inetAddress).isIPv4CompatibleAddress()) { ipaddress = ByteArray.subarray(inetAddress.getAddress(), 12, 4); // the last 4 bytes of of an ipv4-compatible ipv6 address are the ipv4 address (first 12 bytes are zero) } else { throw new IllegalArgumentException("mtwilson.tpm.quote.ipv4 is enabled and requires an IPv4-compatible address but host address is IPv6: " + hostname); } } else { throw new IllegalArgumentException("mtwilson.tpm.quote.ipv4 is enabled and requires an IPv4-compatible address but host address is unknown type: " + hostname); } } else { throw new IllegalArgumentException("mtwilson.tpm.quote.ipv4 is enabled and requires an IPv4-compatible address but host address is unknown type: " + hostname); } return ipaddress; } public PcrManifest getQuoteInformationForHost(String hostname, TrustAgentSecureClient client) throws NoSuchAlgorithmException, PropertyException, JAXBException, UnknownHostException, IOException, KeyManagementException, CertificateException, XMLStreamException { // BUG #497 START CODE SNIPPET MOVED TO INTEL HOST AGENT File q; File n; File c; File r; byte[] nonce = generateNonce(); // 20 random bytes // to fix issue #1038 we have a new option to put the host ip address in the nonce (we don't send this to the host - the hsot automatically would do the same thing) byte[] verifyNonce = nonce; // verifyNonce is what we save to verify against host's tpm quote response if (quoteWithIPAddress) { // is the hostname a dns name or an ip address? if it's a dns name we have to resolve it to an ip address // see also corresponding code in TrustAgent CreateNonceFileCmd byte[] ipaddress = getIPAddress(hostname); if (ipaddress == null) { throw new IllegalArgumentException("mtwilson.tpm.quote.ipv4 is enabled but host address cannot be resolved: " + hostname); } verifyNonce = ByteArray.concat(ByteArray.subarray(nonce, 0, 16), ipaddress); } String verifyNonceBase64 = Base64.encodeBase64String(verifyNonce); String sessionId = generateSessionId(); // FIrst let us ensure that we have an AIK cert created on the host before trying to retrieve the quote. The trust agent // would verify if a AIK is already present or not. If not it will create a new one. trustedAik = client.getAIKCertificate(); // to fix issue #1038 trust agent relay we send 20 random bytes nonce to the host (base64-encoded) but if mtwilson.tpm.quote.ipaddress is enabled then in our copy we replace the last 4 bytes with the host's ip address, and when the host generates the quote it does the same thing, and we can verify it later String nonceBase64 = Base64.encodeBase64String(nonce); ClientRequestType clientRequestType = client.getQuote(nonceBase64, "0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23"); // pcrList used to be a comma-separated list passed to this method... but now we are returning a quote with ALL the PCR's ALL THE TIME. log.debug("got response from server [" + hostname + "] " + clientRequestType); String quote = clientRequestType.getQuote(); log.debug("extracted quote from response: " + quote); q = saveQuote(quote, sessionId); log.debug("saved quote with session id: " + sessionId); // we only need to save the certificate when registring the host ... when we are just getting a quote we need to verify it using the previously saved AIK. if (trustedAik == null) { String aikCertificate = clientRequestType.getAikcert(); log.debug("extracted aik cert from response: " + aikCertificate); c = saveCertificate(aikCertificate, sessionId); log.debug("saved host-provided AIK certificate with session id: " + sessionId); } else { c = saveCertificate(trustedAik, sessionId); log.debug("saved database-provided trusted AIK certificate with session id: " + sessionId); } n = saveNonce(verifyNonceBase64, sessionId); log.debug("saved nonce with session id: " + sessionId); r = createRSAKeyFile(sessionId); log.debug("created RSA key file for session id: " + sessionId); log.debug("Event log: {}", clientRequestType.getEventLog()); // issue #879 byte[] eventLogBytes = Base64.decodeBase64(clientRequestType.getEventLog());// issue #879 log.debug("Decoded event log length: {}", eventLogBytes == null ? null : eventLogBytes.length);// issue #879 if (eventLogBytes != null) { // issue #879 String decodedEventLog = new String(eventLogBytes); log.debug("Event log retrieved from the host consists of: " + decodedEventLog); /* * Example output: * <pre> 2014-05-27 11:40:49,089 DEBUG [http-listener-2(15)] c.i.m.a.i.TAHelper [TAHelper.java:464] Event log retrieved from the host consists of: <measureLog><txt><txtStatus>1</txtStatus><osSinitDataCapabilities>00000000</osSinitDataCapabilities><sinitMleData><version>8</version><sinitHash>fee3650237e216da2159d6f4ef85d33b3b8d3bbe</sinitHash><mleHash>03721dc24915743302f9648b7e82c559d5bdfc6b</mleHash><biosAcmId>800000002010120400003400ffffffffffffffff</biosAcmId><msegValid>0</msegValid><stmHash>0000000000000000000000000000000000000000</stmHash><policyControl>00000000</policyControl><lcpPolicyHash>0000000000000000000000000000000000000000</lcpPolicyHash><processorSCRTMStatus>00000001</processorSCRTMStatus><edxSenterFlags>00000000</edxSenterFlags></sinitMleData><modules><module><pcrNumber>17</pcrNumber><name>tb_policy</name><value>c3438497fda827be3b321c5309a204f0c9e53943</value></module><module><pcrNumber>18</pcrNumber><name>xen.gz</name><value>7a7262b37b255ec484c274a6b2e6a94591e2fdce</value></module><module><pcrNumber>19</pcrNumber><name>vmlinuz</name><value>d70e9875afa574c58348f25ef1249671e396cbc6</value></module><module><pcrNumber>19</pcrNumber><name>initrd</name><value>06c2db1b1050a3347f3f69616f9735da2089effa</value></module><module><pcrNumber>22</pcrNumber><name>asset-tag</name><value>4fd9fdab06ce10c7360b87478232cc3fc1a45249</value></module></modules></txt></measureLog>]] * * </pre> * */ // Since we need to add the event log details into the pcrManifest, we will pass in that information to the below function PcrManifest pcrManifest = verifyQuoteAndGetPcr(sessionId, decodedEventLog); log.info("Got PCR map"); //log.log(Level.INFO, "PCR map = "+pcrMap); // need to untaint this first if (deleteTemporaryFiles) { q.delete(); n.delete(); c.delete(); r.delete(); } return pcrManifest; } else { PcrManifest pcrManifest = verifyQuoteAndGetPcr(sessionId, null); // verify the quote but don't add any event log info to the PcrManifest. // issue #879 log.info("Got PCR map"); //log.log(Level.INFO, "PCR map = "+pcrMap); // need to untaint this first if (deleteTemporaryFiles) { q.delete(); n.delete(); c.delete(); r.delete(); } return pcrManifest; } } // NOTE: this v2 client method is a little different from the getQuoteInformationForHost for the v1 trust agent because // it hashes the nonce and the ip address together (instead of replacing the last 4 bytes of the nonce // with the ip address like the v1 does) public PcrManifest getQuoteInformationForHost(String hostname, TrustAgentClient client) throws NoSuchAlgorithmException, PropertyException, JAXBException, UnknownHostException, IOException, KeyManagementException, CertificateException, XMLStreamException { // BUG #497 START CODE SNIPPET MOVED TO INTEL HOST AGENT File q; File n; File c; File r; byte[] nonce = generateNonce(); // 20 random bytes // to fix issue #1038 we have a new option to put the host ip address in the nonce (we don't send this to the host - the hsot automatically would do the same thing) byte[] verifyNonce = nonce; // verifyNonce is what we save to verify against host's tpm quote response if (quoteWithIPAddress) { // is the hostname a dns name or an ip address? if it's a dns name we have to resolve it to an ip address // see also corresponding code in TrustAgent CreateNonceFileCmd byte[] ipaddress = getIPAddress(hostname); if (ipaddress == null) { throw new IllegalArgumentException("mtwilson.tpm.quote.ipv4 is enabled but host address cannot be resolved: " + hostname); } verifyNonce = Sha1Digest.digestOf(nonce).extend(ipaddress).toByteArray(); } // String verifyNonceBase64 = Base64.encodeBase64String(verifyNonce); String sessionId = generateSessionId(); // FIrst let us ensure that we have an AIK cert created on the host before trying to retrieve the quote. The trust agent // would verify if a AIK is already present or not. If not it will create a new one. trustedAik = X509Util.encodePemCertificate(client.getAik()); // to fix issue #1038 trust agent relay we send 20 random bytes nonce to the host (base64-encoded) but if mtwilson.tpm.quote.ipaddress is enabled then in our copy we replace the last 4 bytes with the host's ip address, and when the host generates the quote it does the same thing, and we can verify it later TpmQuoteResponse tpmQuoteResponse = client.getTpmQuote(nonce, new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}); // pcrList used to be a comma-separated list passed to this method... but now we are returning a quote with ALL the PCR's ALL THE TIME. log.debug("got response from server [" + hostname + "] "); log.debug("extracted quote from response: {}", Base64.encodeBase64String(tpmQuoteResponse.quote)); q = saveQuote(tpmQuoteResponse.quote, sessionId); log.debug("saved quote with session id: " + sessionId); // we only need to save the certificate when registring the host ... when we are just getting a quote we need to verify it using the previously saved AIK. if (trustedAik == null) { String aikCertificate = X509Util.encodePemCertificate(tpmQuoteResponse.aik); log.debug("extracted aik cert from response: " + aikCertificate); c = saveCertificate(aikCertificate, sessionId); log.debug("saved host-provided AIK certificate with session id: " + sessionId); } else { c = saveCertificate(trustedAik, sessionId); log.debug("saved database-provided trusted AIK certificate with session id: " + sessionId); } n = saveNonce(verifyNonce, sessionId); log.debug("saved nonce with session id: " + sessionId); r = createRSAKeyFile(sessionId); log.debug("created RSA key file for session id: " + sessionId); log.debug("Event log: {}", tpmQuoteResponse.eventLog); // issue #879 byte[] eventLogBytes = Base64.decodeBase64(tpmQuoteResponse.eventLog);// issue #879 log.debug("Decoded event log length: {}", eventLogBytes == null ? null : eventLogBytes.length);// issue #879 if (eventLogBytes != null) { // issue #879 String decodedEventLog = new String(eventLogBytes); log.debug("Event log retrieved from the host consists of: " + decodedEventLog); // Since we need to add the event log details into the pcrManifest, we will pass in that information to the below function PcrManifest pcrManifest = verifyQuoteAndGetPcr(sessionId, decodedEventLog); log.info("Got PCR map"); //log.log(Level.INFO, "PCR map = "+pcrMap); // need to untaint this first if (deleteTemporaryFiles) { q.delete(); n.delete(); c.delete(); r.delete(); } return pcrManifest; } else { PcrManifest pcrManifest = verifyQuoteAndGetPcr(sessionId, null); // verify the quote but don't add any event log info to the PcrManifest. // issue #879 log.info("Got PCR map"); //log.log(Level.INFO, "PCR map = "+pcrMap); // need to untaint this first if (deleteTemporaryFiles) { q.delete(); n.delete(); c.delete(); r.delete(); } return pcrManifest; } } // hostName == internetAddress.toString() or Hostname.toString() or IPAddress.toString() // vmmName == tblHosts.getVmmMleId().getName() public String getHostAttestationReport(String hostName, PcrManifest pcrManifest, String vmmName) throws XMLStreamException { XMLOutputFactory xof = XMLOutputFactory.newInstance(); XMLStreamWriter xtw; StringWriter sw = new StringWriter(); /* // We need to check if the host supports TPM or not. Only way we can do it // using the host table contents is by looking at the AIK Certificate. Based // on this flag we generate the attestation report. boolean tpmSupport = true; String hostType = ""; if (tblHosts.getAIKCertificate() == null || tblHosts.getAIKCertificate().isEmpty()) { tpmSupport = false; } * */ boolean tpmSupport = true; // xtw = xof.createXMLStreamWriter(new FileWriter("c:\\temp\\nb_xml.xml")); xtw = xof.createXMLStreamWriter(sw); xtw.writeStartDocument(); xtw.writeStartElement("Host_Attestation_Report"); xtw.writeAttribute("Host_Name", hostName); xtw.writeAttribute("Host_VMM", vmmName); xtw.writeAttribute("TXT_Support", String.valueOf(tpmSupport)); if (tpmSupport == true) { for (int i = 0; i < 24; i++) { // ArrayList<IManifest> pcrMFList = new ArrayList<IManifest>(); // pcrMFList.addAll(pcrManifestMap.values()); // for (IManifest pcrInfo : pcrMFList) { Pcr pcr = pcrManifest.getPcr(i); // PcrManifest pInfo = (PcrManifest) pcrInfo; xtw.writeStartElement("PCRInfo"); xtw.writeAttribute("ComponentName", pcr.getIndex().toString()); // String.valueOf(pInfo.getPcrNumber())); xtw.writeAttribute("DigestValue", pcr.getValue().toString().toUpperCase()); // pInfo.getPcrValue().toUpperCase()); xtw.writeEndElement(); } } else { xtw.writeStartElement("PCRInfo"); xtw.writeAttribute("Error", "Host does not support TPM."); xtw.writeEndElement(); } // Now we need to traverse through the PcrEventLogs and write that also into the Attestation Report. for (int pcrIndex = 0; pcrIndex < 24; pcrIndex++) { if (pcrManifest.containsPcrEventLog(PcrIndex.valueOf(pcrIndex))) { List<Measurement> eventLogs = pcrManifest.getPcrEventLog(pcrIndex).getEventLog(); for (Measurement eventLog : eventLogs) { xtw.writeStartElement("EventDetails"); xtw.writeAttribute("EventName", "OpenSource.EventName"); xtw.writeAttribute("ComponentName", eventLog.getLabel()); xtw.writeAttribute("DigestValue", eventLog.getValue().toString().toUpperCase()); xtw.writeAttribute("ExtendedToPCR", String.valueOf(pcrIndex)); xtw.writeAttribute("PackageName", ""); xtw.writeAttribute("PackageVendor", ""); xtw.writeAttribute("PackageVersion", ""); // since there will be only 2 modules for PCR 19, which changes across hosts, we will consider them as host specific ones xtw.writeAttribute("UseHostSpecificDigest", "true"); xtw.writeEndElement(); } } } xtw.writeEndElement(); xtw.writeEndDocument(); xtw.flush(); xtw.close(); String attestationReport = sw.toString(); return attestationReport; } public byte[] generateNonce() { try { // Create a secure random number generator SecureRandom sr = SecureRandom.getInstance("SHA1PRNG"); // Get 1024 random bits byte[] bytes = new byte[20]; // bug #1038 nonce should be 20 random bytes; even though we send 20 random bytes to the host, both we and the host will replace the last 4 bytes with the host's primary IP address sr.nextBytes(bytes); // nonce = new BASE64Encoder().encode( bytes); // String nonce = Base64.encodeBase64String(bytes); log.debug("Nonce Generated {}", Base64.encodeBase64String(bytes)); return bytes; } catch (NoSuchAlgorithmException e) { throw new ASException(e); } } private String generateSessionId() throws NoSuchAlgorithmException { // Create a secure random number generator SecureRandom sr = SecureRandom.getInstance("SHA1PRNG"); // Get 1024 random bits byte[] seed = new byte[1]; sr.nextBytes(seed); sr = SecureRandom.getInstance("SHA1PRNG"); sr.setSeed(seed); int nextInt = sr.nextInt(); String sessionId = "" + ((nextInt < 0) ? nextInt * -1 : nextInt); log.debug("Session Id Generated [{}]", sessionId); return sessionId; } // for DAA private String getDaaAikProofFileName(String sessionId) { return "daaaikproof_" + sessionId + ".data"; } private String getDaaSecretFileName(String sessionId) { return "daasecret_" + sessionId + ".data"; } private String getDaaChallengeFileName(String sessionId) { return "daachallenge_" + sessionId + ".data"; } /* private String getDaaResponseFileName(String sessionId) { return "daaresponse_"+sessionId+".data"; } */ private String getNonceFileName(String sessionId) { return "nonce_" + sessionId + ".data"; } private String getQuoteFileName(String sessionId) { return "quote_" + sessionId + ".data"; } private File saveCertificate(String aikCertificate, String sessionId) throws IOException, CertificateException { /* // first get a consistent newline character aikCertificate = aikCertificate.replace('\r', '\n').replace("\n\n", "\n"); if( aikCertificate.indexOf("-----BEGIN CERTIFICATE-----\n") < 0 && aikCertificate.indexOf("-----BEGIN CERTIFICATE-----") >= 0 ) { log.info( "adding newlines to certificate BEGIN tag"); aikCertificate = aikCertificate.replace("-----BEGIN CERTIFICATE-----", "-----BEGIN CERTIFICATE-----\n"); } if( aikCertificate.indexOf("\n-----END CERTIFICATE-----") < 0 && aikCertificate.indexOf("-----END CERTIFICATE-----") >= 0 ) { log.info( "adding newlines to certificate END tag"); aikCertificate = aikCertificate.replace("-----END CERTIFICATE-----", "\n-----END CERTIFICATE-----"); } saveFile(getCertFileName(sessionId), aikCertificate.getBytes()); */ File file = new File(aikverifyhomeData + File.separator + getCertFileName(sessionId)); X509Certificate aikcert = X509Util.decodePemCertificate(aikCertificate); String pem = X509Util.encodePemCertificate(aikcert); try (FileOutputStream out = new FileOutputStream(file)) { IOUtils.write(pem, out); } return file; } private String getCertFileName(String sessionId) { return "aikcert_" + sessionId + ".cer"; } private File saveFile(String fileName, byte[] contents) throws IOException { log.debug(String.format("saving file %s to [%s]", fileName, aikverifyhomeData)); File file = new File(aikverifyhomeData + File.separator + fileName); try (FileOutputStream fileOutputStream = new FileOutputStream(file)) { fileOutputStream.write(contents); fileOutputStream.flush(); return file; } catch (FileNotFoundException e) { log.debug(String.format("cannot save to file %s in [%s]: %s", fileName, aikverifyhomeData, e.getMessage())); throw e; } } private File saveQuote(String quote, String sessionId) throws IOException { // byte[] quoteBytes = new BASE64Decoder().decodeBuffer(quote); byte[] quoteBytes = Base64.decodeBase64(quote); File file = saveFile(getQuoteFileName(sessionId), quoteBytes); return file; } private File saveQuote(byte[] quoteBytes, String sessionId) throws IOException { File file = saveFile(getQuoteFileName(sessionId), quoteBytes); return file; } private File saveNonce(String nonce, String sessionId) throws IOException { byte[] nonceBytes = Base64.decodeBase64(nonce); File file = saveFile(getNonceFileName(sessionId), nonceBytes); return file; } private File saveNonce(byte[] nonceBytes, String sessionId) throws IOException { File file = saveFile(getNonceFileName(sessionId), nonceBytes); return file; } private File createRSAKeyFile(String sessionId) throws IOException, CertificateException { // 20130409 replacing external openssl command with equivalent java code, see below /* String command = String.format("%s %s %s",opensslCmd,aikverifyhomeData + File.separator + getCertFileName(sessionId),aikverifyhomeData + File.separator+getRSAPubkeyFileName(sessionId)); log.info( "RSA Key Command {}", command); CommandUtil.runCommand(command, false, "CreateRsaKey" ); //log.log(Level.INFO, "Result - {0} ", result); */ try (FileInputStream in = new FileInputStream(new File(aikverifyhomeData + File.separator + getCertFileName(sessionId)))) { String x509cert = IOUtils.toString(in); X509Certificate aikcert = X509Util.decodePemCertificate(x509cert); String aikpubkey = RsaUtil.encodePemPublicKey(aikcert.getPublicKey()); File file = new File(aikverifyhomeData + File.separator + getRSAPubkeyFileName(sessionId)); try (FileOutputStream out = new FileOutputStream(file)) { IOUtils.write(aikpubkey, out); return file; } } } private String getRSAPubkeyFileName(String sessionId) { return "rsapubkey_" + sessionId + ".key"; } private PcrManifest verifyQuoteAndGetPcr(String sessionId, String eventLog) { // HashMap<String,PcrManifest> pcrMp = new HashMap<String,PcrManifest>(); PcrManifest pcrManifest = new PcrManifest(); log.debug("verifyQuoteAndGetPcr for session {}", sessionId); String command = String.format("%s -c %s %s %s", CommandUtil.doubleQuoteEscapeShellArgument(aikverifyCmd), CommandUtil.doubleQuoteEscapeShellArgument(aikverifyhomeData + File.separator + getNonceFileName(sessionId)), CommandUtil.doubleQuoteEscapeShellArgument(aikverifyhomeData + File.separator + getRSAPubkeyFileName(sessionId)), CommandUtil.doubleQuoteEscapeShellArgument(aikverifyhomeData + File.separator + getQuoteFileName(sessionId))); log.debug("Command: {}", command); List<String> result = CommandUtil.runCommand(command, true, "VerifyQuote"); // Sample output from command: // 1 3a3f780f11a4b49969fcaa80cd6e3957c33b2275 // 17 bfc3ffd7940e9281a3ebfdfa4e0412869a3f55d8 //log.log(Level.INFO, "Result - {0} ", result); // need to untaint this first //List<String> pcrs = getPcrsList(); // replaced with regular expression that checks 0-23 for (String pcrString : result) { String[] parts = pcrString.trim().split(" "); if (parts.length == 2) { String pcrNumber = parts[0].trim().replaceAll(pcrNumberUntaint, "").replaceAll("\n", ""); String pcrValue = parts[1].trim().replaceAll(pcrValueUntaint, "").replaceAll("\n", ""); boolean validPcrNumber = pcrNumberPattern.matcher(pcrNumber).matches(); boolean validPcrValue = pcrValuePattern.matcher(pcrValue).matches(); if (validPcrNumber && validPcrValue) { log.debug("Result PCR " + pcrNumber + ": " + pcrValue); // pcrMp.put(pcrNumber, new PcrManifest(Integer.parseInt(pcrNumber),pcrValue)); pcrManifest.setPcr(new Pcr(PcrIndex.valueOf(Integer.parseInt(pcrNumber)), new Sha1Digest(pcrValue))); } } else { log.warn("Result PCR invalid"); } /* if(pcrs.contains(parts[0].trim())) pcrMp.put(parts[0].trim(), new PcrManifest(Integer.parseInt(parts[0]),parts[1])); */ } // Now that we captured the PCR details, we need to capture the module information also into the PcrManifest object // Sample Format: // <modules> //<module><pcrNumber>17</pcrNumber><name>tb_policy</name><value>9704353630674bfe21b86b64a7b0f99c297cf902</value></module> //<module><pcrNumber>18</pcrNumber><name>xen.gz</name><value>dfdffe5d3bdff697c4d7447115440e34fa27c1a4</value></module> //<module><pcrNumber>19</pcrNumber><name>vmlinuz</name><value>d3f525b0dc6f7d7c9a3af165bcf6c3e3e02b2599</value></module> //<module><pcrNumber>19</pcrNumber><name>initrd</name><value>3dfa5762c78623ccfc778498ab4cb7136bb3f5ab</value></module> //</modules> if (eventLog != null) { // issue #879 try { XMLInputFactory xif = XMLInputFactory.newInstance(); //FileInputStream fis = new FileInputStream("c:\\temp\\nbtest.txt"); StringReader sr = new StringReader(eventLog); XMLStreamReader reader = xif.createXMLStreamReader(sr); int extendedToPCR = -1; String digestValue = ""; String componentName = ""; while (reader.hasNext()) { if (reader.getEventType() == XMLStreamConstants.START_ELEMENT && reader.getLocalName().equalsIgnoreCase("module")) { reader.next(); // Get the PCR Number to which the module is extended to if (reader.getLocalName().equalsIgnoreCase("pcrNumber")) { extendedToPCR = Integer.parseInt(reader.getElementText()); } reader.next(); // Get the Module name if (reader.getLocalName().equalsIgnoreCase("name")) { componentName = reader.getElementText(); } reader.next(); // Get the Module hash value if (reader.getLocalName().equalsIgnoreCase("value")) { digestValue = reader.getElementText(); } log.debug("Process module " + componentName + " getting extended to " + extendedToPCR); // Attach the PcrEvent logs to the corresponding pcr indexes. // Note: Since we will not be processing the even logs for 17 & 18, we will ignore them for now. Measurement m = convertHostTpmEventLogEntryToMeasurement(extendedToPCR, componentName, digestValue); if (pcrManifest.containsPcrEventLog(PcrIndex.valueOf(extendedToPCR))) { pcrManifest.getPcrEventLog(extendedToPCR).getEventLog().add(m); } else { ArrayList<Measurement> list = new ArrayList<Measurement>(); list.add(m); pcrManifest.setPcrEventLog(new PcrEventLog(PcrIndex.valueOf(extendedToPCR), list)); } } reader.next(); } } catch (FactoryConfigurationError | XMLStreamException | NumberFormatException ex) { // bug #2171 we need to throw an exception to prevent the host from being registered with an error manifest //log.error(ex.getMessage(), ex); throw new IllegalStateException("Invalid measurement log", ex); } } return pcrManifest; } /** * Helper method to create the Measurement Object. * * @param extendedToPcr * @param moduleName * @param moduleHash * @return */ private static Measurement convertHostTpmEventLogEntryToMeasurement(int extendedToPcr, String moduleName, String moduleHash) { HashMap<String, String> info = new HashMap<String, String>(); info.put("EventName", "OpenSource.EventName"); // For OpenSource since we do not have any events associated, we are creating a dummy one. // Removing the prefix of "OpenSource" as it is being captured in the event type info.put("ComponentName", moduleName); info.put("PackageName", ""); info.put("PackageVendor", ""); info.put("PackageVersion", ""); return new Measurement(new Sha1Digest(moduleHash), moduleName, info); } /* public EntityManagerFactory getEntityManagerFactory() { return entityManagerFactory; } public void setEntityManagerFactory(EntityManagerFactory entityManagerFactory) { this.entityManagerFactory = entityManagerFactory; }*/ /* private List<String> getPcrsList() { List<String> pcrs = new ArrayList<String>() ; for(int i = 0 ; i< 24 ; i++) pcrs.add(String.valueOf(i)); return pcrs; } */ }