package com.intel.mtwilson.agent.vmware;
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.mtwilson.model.Sha1Digest;
import com.intel.dcsg.cpg.crypto.Sha1Digest;
import com.vmware.vim25.DynamicProperty;
import com.vmware.vim25.HostTpmAttestationReport;
import com.vmware.vim25.HostTpmBootSecurityOptionEventDetails;
import com.vmware.vim25.HostTpmCommandEventDetails;
import com.vmware.vim25.HostTpmDigestInfo;
import com.vmware.vim25.HostTpmEventDetails;
import com.vmware.vim25.HostTpmEventLogEntry;
import com.vmware.vim25.HostTpmOptionEventDetails;
import com.vmware.vim25.HostTpmSoftwareComponentEventDetails;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
//import org.owasp.esapi.codecs.Hex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class VMWare51Esxi51 {
private static Logger log = LoggerFactory.getLogger(VMWare51Esxi51.class);
public static PcrManifest createPcrManifest(HostTpmAttestationReport report) {
PcrManifest pcrManifest = new PcrManifest();
// for each PCR get index and value
HostTpmDigestInfo[] tpmPcrValues = report.getTpmPcrValues();
if( tpmPcrValues != null ) {
for (HostTpmDigestInfo hostTpmDigestInfo :tpmPcrValues ) {
log.debug("HostTpmDigestInfo PCR {}", hostTpmDigestInfo.getPcrNumber());
// this block just out of curiosity ... we should instantiate the right digest class using this info. expected to be SHA1... always...
String algorithm = hostTpmDigestInfo.getDigestMethod();
log.debug("HostTpmDigestInfo DigestMethod = {}", algorithm); // output is "SHA1"
// convert the vwmare data types to mt wilson datatypes
String digest = VMwareClient.byteArrayToHexString(hostTpmDigestInfo.getDigestValue());
log.debug("HostTpmDigestInfo Digest = {}", digest);
Pcr pcr = new Pcr(hostTpmDigestInfo.getPcrNumber(), digest);
pcrManifest.setPcr(pcr);
}
}
// for each event assign it to a PCR event log
HostTpmEventLogEntry[] tpmEvents = report.getTpmEvents();
if( tpmEvents != null ) {
for(HostTpmEventLogEntry logEntry : tpmEvents) {
int pcrIndex = logEntry.getPcrIndex();
log.debug("PCR {}", pcrIndex);
Measurement m = convertHostTpmEventLogEntryToMeasurement(logEntry);
if( pcrManifest.containsPcrEventLog(PcrIndex.valueOf(pcrIndex)) ) {
pcrManifest.getPcrEventLog(pcrIndex).getEventLog().add(m);
}
else {
ArrayList<Measurement> list = new ArrayList<Measurement>();
list.add(m);
pcrManifest.setPcrEventLog(new PcrEventLog(PcrIndex.valueOf(pcrIndex),list));
}
}
}
return pcrManifest;
}
private static Measurement convertHostTpmEventLogEntryToMeasurement(HostTpmEventLogEntry logEntry) {
String label;
String digest = VMwareClient.byteArrayToHexString(logEntry.getEventDetails().getDataHash());;
HashMap<String,String> info = new HashMap<String,String>();
HostTpmEventDetails logEventDetails = logEntry.getEventDetails();
log.debug("Event {}", logEventDetails.getClass().getName());
if (logEventDetails instanceof HostTpmSoftwareComponentEventDetails) { // ComponentName, VibName, VibVersion, VibVendor, and DataHash
HostTpmSoftwareComponentEventDetails componentEventDetails = (HostTpmSoftwareComponentEventDetails)logEventDetails;
log.debug("Event name {}", componentEventDetails.getComponentName());
info.put("EventType", "HostTpmSoftwareComponentEvent"); // new, properly capture the type of event in a separate field
info.put("EventName", "Vim25Api.HostTpmSoftwareComponentEventDetails");
info.put("ComponentName", "componentName." + componentEventDetails.getComponentName());
info.put("PackageName", componentEventDetails.getVibName());
info.put("PackageVendor", componentEventDetails.getVibVendor());
info.put("PackageVersion", componentEventDetails.getVibVersion());
if (logEntry.pcrIndex == 19) {
String fullCompName = "componentName." + componentEventDetails.getComponentName().substring(0, componentEventDetails.getComponentName().lastIndexOf("."));
fullCompName = fullCompName + "-" + componentEventDetails.getVibName()+ "-" +componentEventDetails.getVibVersion();
info.put("FullComponentName",fullCompName);
}
// label = String.format("%s: %s-%s-%s", info.get("EventType"), componentEventDetails.getVibVendor(), componentEventDetails.getVibName(), componentEventDetails.getVibVersion() );
// There are usually 3 components that are filenames like imgdb.tgz, state.tgz, and onetime.tgz, where the filename is listed as ComponentName and there is no PackageVendor, PackageName, or PackageVersion defined.
// So we need to label those using the ComponentName, and label the rest of the modules using PackageVendor-PackageName-PackageVersion.
if( (componentEventDetails.getVibVendor() == null || componentEventDetails.getVibVendor().isEmpty()) &&
(componentEventDetails.getVibName() == null || componentEventDetails.getVibName().isEmpty()) &&
(componentEventDetails.getVibVersion() == null || componentEventDetails.getVibVersion().isEmpty())) {
label = componentEventDetails.getComponentName(); // imgdb.tgz, state.tgz, onetime.tgz
}
else {
label = componentEventDetails.getComponentName(); // was doing vendor-package-version but now that attestation logic matches modules by digest instead of by name, the name doesn't matter. so we can use the short component name.
// label = String.format("%s-%s-%s", componentEventDetails.getVibVendor(), componentEventDetails.getVibName(), componentEventDetails.getVibVersion() ); // VMware-esx-xserver-5.1.0-0.0.799733, VMware-net-ixgbe-sriov-3.7.13.2iov-10vmw.510.0.0.613838, etc.
}
} else if (logEventDetails instanceof HostTpmCommandEventDetails) { // CommandLine and DataHash
HostTpmCommandEventDetails commandEventDetails = (HostTpmCommandEventDetails) logEventDetails;
log.debug("Event name {}", commandEventDetails.getCommandLine());
info.put("EventType", "HostTpmCommandEvent"); // new, properly capture the type of event in a separate field
info.put("EventName", "Vim25Api.HostTpmCommandEventDetails");
info.put("ComponentName", "commandLine."+ getCommandLine(commandEventDetails));
info.put("UUID", getUuidFromCommandLine(commandEventDetails.getCommandLine()));
log.debug("UUID is {}", info.get("UUID"));
// label = String.format("%s: %s", info.get("EventType"), getCommandLine(commandEventDetails) );
label = String.format("%s", commandEventDetails.getCommandLine()); // UI should abbreviate it with "..." if desired...
} else if (logEventDetails instanceof HostTpmOptionEventDetails) { // OptionsFilename, BootOptions, and DataHash
HostTpmOptionEventDetails optionEventDetails = (HostTpmOptionEventDetails)logEventDetails;
log.debug("Event name {}", optionEventDetails.getOptionsFileName());
info.put("EventType", "HostTpmOptionEvent"); // new, properly capture the type of event in a separate field
info.put("EventName", "Vim25Api.HostTpmOptionEventDetails");
info.put("ComponentName", "bootOptions."+ optionEventDetails.getOptionsFileName());
// label = String.format("%s: %s", info.get("EventType"), optionEventDetails.getOptionsFileName() );
label = optionEventDetails.getOptionsFileName(); // String.format("%s", optionEventDetails.getOptionsFileName())
} else if (logEventDetails instanceof HostTpmBootSecurityOptionEventDetails) { // BootSecurityOption and DataHash
HostTpmBootSecurityOptionEventDetails optionEventDetails = (HostTpmBootSecurityOptionEventDetails)logEventDetails;
log.debug("Event name {}", optionEventDetails.getBootSecurityOption());
info.put("EventType", "HostTpmBootSecurityOptionEvent"); // new, properly capture the type of event in a separate field
info.put("EventName", "Vim25Api.HostTpmBootSecurityOptionEventDetails");
info.put("ComponentName", "bootSecurityOption."+ optionEventDetails.getBootSecurityOption());
// label = String.format("%s: %s", info.get("EventType"), optionEventDetails.getBootSecurityOption() );
label = optionEventDetails.getBootSecurityOption();
} else {
log.warn("Unrecognized event in module event log "
+ logEventDetails.getClass().getName());
List<DynamicProperty> ps = Arrays.asList(logEventDetails.getDynamicProperty());
for(DynamicProperty p : ps) {
info.put(p.getName(), p.getVal().toString());
}
label = String.format("%s: %s", logEventDetails.getClass().getSimpleName(), logEventDetails.getDynamicType());
}
log.debug("Event Digest is {}", digest);
if( digest.length() == 40 ) { // sha1 is 20 bytes, so 40 hex digits
return new Measurement(new Sha1Digest(digest), label, info );
}
if( digest.replace("0", "").trim().isEmpty() ) {
log.warn("Event Digest is zero longer than 20 bytes: {} -- replacing with 20 bytes of zero", digest);
return new Measurement(Sha1Digest.ZERO, label, info );
}
/**
* The following lines may cause a problem. If you are reading this, it's probably because
* you are troubleshooting a measurement that is misbehaving. Need to find out why vmware is
* sending event logs with digests more than 20 bytes that are not zero...
* You can't extend a PCR with something more than 20 bytes. The TPM spec for PCR extend is PCRi = SHA1(PCRi||20-bytes-data).
* So the TSS automatically does a SHA1 hash on the data -- haven't read the source code so I don't know
* if it only hashes if it's longer than 20 bytes, or if it always hashes. Probably always hashes for consistency.
*/
log.error("Event Digest is non-zero longer than 20 bytes: {} -- trying to decode it", digest);
try{
return new Measurement(Sha1Digest.valueOf(Hex.decodeHex(digest.toCharArray())), label, info );
}
catch(DecoderException e) {
throw new IllegalArgumentException(digest);
}
}
private static String getCommandLine(HostTpmCommandEventDetails commandEventDetails) {
String commandLine = commandEventDetails.getCommandLine();
if (commandLine != null && commandLine.contains("no-auto-partition")){
commandLine = "";
}
return commandLine;
}
// example input: /b.b00 vmbTrustedBoot=true tboot=0x0x101a000 no-auto-partition bootUUID=772753050c0a140bdfbf92e306b9793d
private static Pattern uuidPattern = Pattern.compile(".*bootUUID=([a-fA-F0-9]+).*"); // don't need [^a-fA-F0-9]? before the last .* because the (a-fA-F0-9]+) match is greedy
private static String getUuidFromCommandLine(String commandLine) {
Matcher uuidMatcher = uuidPattern.matcher(commandLine);
if( uuidMatcher.matches() ) {
return uuidMatcher.group(1);
}
return null;
}
}