Initial release

This commit is contained in:
apldev4 2018-09-06 09:47:33 -04:00
commit d7e44b8310
1262 changed files with 244470 additions and 0 deletions

124
.gitignore vendored Normal file
View File

@ -0,0 +1,124 @@
# compiled python for systems tests
*.pyc
# NetBeans specific #
private/
build/
dist/
.nb-gradle-properties
# Eclipse specific #
.classpath
.project
.settings/
.checkstyle
# IDEA specific #
*.iml
.idea
cmake-build-debug/
# Gradle specific #
.gradle
# Class Files #
*.class
classes/
# Package Files #
*.war
*.ear
*.rpm
*.deb
# Specific RPMs used for testing
!HIRS_Utils/src/test/resources/repository/vim-common-7.2.411-1.8.el6.x86_64.rpm
!HIRS_Utils/src/test/resources/repository/kernel-2.6.32-642.6.1.el6.x86_64.rpm
!HIRS_Utils/src/test/resources/testrepo/*
!Systems_Tests/resources/**
# DEB Sources #
DEB_SOURCES/
# RPM Files #
BUILD/
BUILDROOT/
SOURCES/
SPECS/
RPM/
SRPM/
PLUGIN_SOURCE/
# C++ Files #
*.o
# Vagrant Files #
.vagrant/
.vagrantfile.swp
vagrant/tmp/
# tpm_module #
tpm_module/tpm_module
main.o
main.d
# Misc Files #
*~
bin/
!package/extras/*/bin
*/test-output/
# MAC OSX Finder Files #
.DS_Store
# Log Files #
*.log
/.nb-gradle/
# rejected diff applications
*.rej
# cmake artifacts from manual build
cmake_install.cmake
HIRS_ProvisionerTPM2/*.cmake
HIRS_ProvisionerTPM2/HIRS_ProvisionerTPM2.cbp
HIRS_ProvisionerTPM2/CMakeCache.txt
Makefile
CMakeFiles/
HIRS_ProvisionerTPM2/DartConfiguration.tcl
HIRS_ProvisionerTPM2/lib/cpplint-download/
HIRS_ProvisionerTPM2/lib/cpplint/
HIRS_ProvisionerTPM2/lib/cpr-build/
HIRS_ProvisionerTPM2/lib/cpr-download/
HIRS_ProvisionerTPM2/lib/cpr-src/
HIRS_ProvisionerTPM2/lib/googletest-build/
HIRS_ProvisionerTPM2/lib/googletest-download/
HIRS_ProvisionerTPM2/lib/googletest-src/
HIRS_ProvisionerTPM2/lib/*.a
HIRS_ProvisionerTPM2/lib/*.so
HIRS_ProvisionerTPM2/install_manifest.txt
HIRS_ProvisionerTPM2/src/libTPM2_PROVISIONER_LIBRARY.a
HIRS_ProvisionerTPM2/test/CTestTestfile.cmake
# C++ Doxygen Documentation
HIRS_ProvisionerTPM2/docs/html/
HIRS_ProvisionerTPM2/docs/latex/
HIRS_ProvisionerTPM2/CMakeDoxyfile.in
# C++ Style Checker
HIRS_ProvisionerTPM2/lint
/*/out
HIRS_ProvisionerTPM2/cmake-build-debug
# autogenerated protobuf files
*.pb.cc
*.pb.h
HIRS_AttestationCA/src/main/java/hirs/attestationca/configuration/provisionerTpm2/ProvisionerTpm2.java
# these files are copied over by ProvisionerTPM2 CMake build
HIRS_ProvisionerTPM2/config/logging.properties
HIRS_ProvisionerTPM2/scripts/tpm_aca_provision

31
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,31 @@
# NOTE: if you are editing this, try using the lint tool to check your work before pushing: https://forge.outer.jhuapl.edu/ci/lint
before_script:
- echo "running CI jobs for HIRS"
stages:
- build
gradle_build:
stage: build
script: ./gradlew build
artifacts:
when: on_failure
untracked: true
expire_in: 3 days
rpm_build_centos6:
stage: build
script: ONLY_BUILD_EL6_RPMS=true ./package/package.centos.sh
artifacts:
paths:
- package/rpm/RPMS/
expire_in: 3 days
rpm_build_centos7:
stage: build
script: ONLY_BUILD_EL7_RPMS=true ./package/package.centos.sh
artifacts:
paths:
- package/rpm/RPMS/
expire_in: 3 days

6
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,6 @@
All contributions to this project will be released as follows:
1. If you are a U.S. government employee, then your changes are exempt from copyright in the U.S. and will be released under the [CC0 1.0](https://creativecommons.org/publicdomain/zero/1.0/) [Universal license](https://creativecommons.org/publicdomain/zero/1.0/legalcode) worldwide.
1. If you are a not a U.S. government employee, then your changes will be released under the [CC0 1.0](https://creativecommons.org/publicdomain/zero/1.0/) [Universal license](https://creativecommons.org/publicdomain/zero/1.0/legalcode) in the U.S. and worldwide.
By submitting a pull request, you are agreeing to comply with this waiver of copyright interest.

9
DISCLAIMER.md Normal file
View File

@ -0,0 +1,9 @@
## Disclaimer of Warranty
This Work is provided "as is." Any express or implied warranties, including but not limited to, the implied warranties of merchantability and fitness for a particular purpose are disclaimed. In no event shall the United States Government be liable for any direct, indirect, incidental, special, exemplary or consequential damages (including, but not limited to, procurement of substitute goods or services, loss of use, data or profits, or business interruption) however caused and on any theory of liability, whether in contract, strict liability, or tort (including negligence or otherwise) arising in any way out of the use of this Guidance, even if advised of the possibility of such damage.
The User of this Work agrees to hold harmless and indemnify the United States Government, its agents and employees from every claim or liability (whether in tort or in contract), including attorneys' fees, court costs, and expenses, arising in direct consequence of Recipient's use of the item, including, but not limited to, claims or liabilities made for injury to or death of personnel of User or third parties, damage to or destruction of property of User or third parties, and infringement or other violations of intellectual property or technical data rights.
Nothing in this Work is intended to constitute an endorsement, explicit or implied, by the United States Government of any particular manufacturer's product or service.
## Disclaimer of Endorsement
Reference herein to any specific commercial product, process, or service by trade name, trademark, manufacturer, or otherwise, in this Work does not constitute an endorsement, recommendation, or favoring by the United States Government and shall not be used for advertising or product endorsement purposes.

View File

@ -0,0 +1,83 @@
apply plugin: 'war'
apply plugin: 'checkstyle'
evaluationDependsOn(':HIRS_Utils')
sourceCompatibility = 1.8
dependencies {
compile project(':TPM_Utils')
compile project(':HIRS_Structs')
compile project(':HIRS_Utils')
compile libs.bouncy_castle
compile libs.commons_codec
compile libs.commons_lang
compile libs.spring_webmvc
compile libs.log4j2
compile libs.log4j2_web
compile libs.protobuf_java
providedCompile libs.servlet_api
testCompile project(':HIRS_Utils').sourceSets.test.output
testCompile project(':HIRS_Utils').sourceSets.test.resources
testCompile libs.commons_lang
testCompile libs.spring_test
testCompile libs.mockito
testCompile libs.testng
testCompile libs.hsqldb
}
task generateProtoBuf(type:Exec) {
workingDir 'config'
commandLine './genJavaProtoBuf.sh'
}
compileJava.dependsOn generateProtoBuf
ext.configDir = new File(projectDir, 'config')
ext.checkstyleConfigDir = "$configDir/checkstyle"
checkstyle {
toolVersion = '5.7'
configFile = checkstyleConfigFile
configProperties.put('basedir', checkstyleConfigDir)
ignoreFailures = false
showViolations = true
}
war {
archiveName = 'HIRS_AttestationCA.war'
}
publishing {
publications {
maven(MavenPublication) {
artifactId 'hirs-attestationca'
artifact jar
pom.withXml {
def dependenciesNode = asNode().appendNode('dependencies')
configurations.runtime.allDependencies.each {
if (it.group != null && it.name != null) {
def dependencyNode = dependenciesNode.appendNode('dependency')
dependencyNode.appendNode('groupId', it.group)
dependencyNode.appendNode('artifactId', it.name)
dependencyNode.appendNode('version', it.version)
if (it.excludeRules.size() > 0) {
def exclusionsNode = dependencyNode.appendNode('exclusions')
it.excludeRules.each { rule ->
def exclusionNode = exclusionsNode.appendNode('exclusion')
exclusionNode.appendNode('groupId', rule.group)
exclusionNode.appendNode('artifactId', rule.module)
}
}
}
}
}
}
}
}

View File

@ -0,0 +1,14 @@
<?xml version="1.0"?>
<!DOCTYPE suppressions PUBLIC
"-//Puppy Crawl//DTD Suppressions 1.1//EN"
"http://www.puppycrawl.com/dtds/suppressions_1_1.dtd">
<suppressions>
<suppress checks="MagicNumber" files=".*[/\\]src[/\\]test[/\\]+" />
<suppress checks="FinalParameters" files=".*[/\\]src[/\\]test[/\\]+" />
<suppress checks="JavadocPackage" files=".*[/\\]src[/\\]test[/\\]+" />
<suppress files="src/main/java/hirs/attestationca/configuration/provisionerTpm2/ProvisionerTpm2\.java" checks="[a-zA-Z0-9]*"/>
</suppressions>

View File

@ -0,0 +1,16 @@
#!/bin/bash
# Script to generate protobuf Java code. Called by gradle to compile the
# protobuf spec file to Java source. Generates the file
# hirs/attestationca/configuration/provisionerTpm2/ProvisionerTpm2.java.
dir=$(pwd)
# Relative paths are different when building locally versus on CI
if [[ "$dir" == *"package"* ]]; then
SRC_DIR=$dir/../../../../../../HIRS_ProvisionerTPM2/src
DEST_DIR=$dir/../src/main/java
else
SRC_DIR=../../HIRS_ProvisionerTPM2/src
DEST_DIR=../src/main/java
fi
protoc -I=$SRC_DIR --java_out=$DEST_DIR $SRC_DIR/ProvisionerTpm2.proto

View File

@ -0,0 +1,72 @@
package hirs.attestationca;
import hirs.appraiser.SupplyChainAppraiser;
import hirs.data.persist.DeviceGroup;
import hirs.data.persist.SupplyChainPolicy;
import hirs.persist.AppraiserManager;
import hirs.persist.DeviceGroupManager;
import hirs.persist.PolicyManager;
import static hirs.attestationca.AbstractAttestationCertificateAuthority.LOG;
/**
* Utility class that simply holds logic to seed the ACA's database with its
* default entries.
*/
public final class AcaDbInit {
// prevent construction
private AcaDbInit() { }
/**
* Insert the ACA's default entries into the DB. This class is invoked after successful
* install of the HIRS_AttestationCA RPM.
*
* @param appraiserManager the AppraiserManager to use to persist appraisers
* @param deviceGroupManager the DeviceGroupManager to use to persist device groups
* @param policyManager the PolicyManager to use to persist policies
*/
public static synchronized void insertDefaultEntries(
final AppraiserManager appraiserManager,
final DeviceGroupManager deviceGroupManager,
final PolicyManager policyManager
) {
LOG.info("Ensuring default ACA database entries are present.");
// Ensure the default group exists. It may have already been created by the Server RPM
DeviceGroup defaultGroup = deviceGroupManager.getDeviceGroup(DeviceGroup.DEFAULT_GROUP);
if (defaultGroup == null) {
LOG.info("Default group not found; saving...");
defaultGroup = deviceGroupManager.saveDeviceGroup(new DeviceGroup(
DeviceGroup.DEFAULT_GROUP,
"This is the default group"
));
LOG.info("Saved default group.");
}
// If the SupplyChainAppraiser exists, do not attempt to re-save the supply chain appraiser
// or SupplyChainPolicy
SupplyChainAppraiser supplyChainAppraiser = (SupplyChainAppraiser)
appraiserManager.getAppraiser(SupplyChainAppraiser.NAME);
if (supplyChainAppraiser != null) {
LOG.info("Supply chain appraiser is present; not inserting any more entries.");
LOG.info("ACA database initialization complete.");
return;
}
// Create the SupplyChainAppraiser
LOG.info("Saving supply chain appraiser...");
supplyChainAppraiser = (SupplyChainAppraiser)
appraiserManager.saveAppraiser(new SupplyChainAppraiser());
// Create the SupplyChainPolicy
LOG.info("Saving default supply chain policy...");
SupplyChainPolicy supplyChainPolicy = new SupplyChainPolicy(
SupplyChainPolicy.DEFAULT_POLICY
);
policyManager.savePolicy(supplyChainPolicy);
policyManager.setDefaultPolicy(supplyChainAppraiser, supplyChainPolicy);
policyManager.setPolicy(supplyChainAppraiser, defaultGroup, supplyChainPolicy);
LOG.info("ACA database initialization complete.");
}
}

View File

@ -0,0 +1,52 @@
package hirs.attestationca;
/**
* Defines the responsibilities of the Attestation Certificate Authority.
*/
public interface AttestationCertificateAuthority {
/**
* The default size for IV blocks.
*/
int DEFAULT_IV_SIZE = 16;
/**
* Processes a given {@link hirs.structs.elements.aca.IdentityRequestEnvelope} and
* generates a {@link hirs.structs.elements.aca.IdentityResponseEnvelope}. In most cases,
* a client will generate the request using the TPM "Collate Identity" process.
*
* @param identityRequest generated during the collate identity process with a Tpm
* @return response for the request
*/
byte[] processIdentityRequest(byte[] identityRequest);
/**
* Processes a given
* {@link hirs.attestationca.configuration.provisionerTpm2.ProvisionerTpm2.IdentityClaim} and
* generates a response containing an encrypted nonce to be returned by the client in
* a future handshake request.
*
* @param identityClaim generated during the create identity claim process on a TPM2 Provisioner
* @return response for the request
*/
byte[] processIdentityClaimTpm2(byte[] identityClaim);
/**
* Processes a given
* {@link hirs.attestationca.configuration.provisionerTpm2.ProvisionerTpm2.CertificateRequest}
* and generates a response containing the signed, public certificate for
* the client's desired attestation key, if the correct nonce is supplied.
*
* @param certificateRequest request containing nonce from earlier identity
* claim handshake
* @return response for the request
*/
byte[] processCertificateRequest(byte[] certificateRequest);
/**
* Issues the PK of the ACA public/private key pair.
*
* @return public key of the attestation certificate authority
*/
byte[] getPublicKey();
}

View File

@ -0,0 +1,122 @@
package hirs.attestationca;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import hirs.data.persist.certificate.EndorsementCredential;
import hirs.data.persist.certificate.PlatformCredential;
import hirs.persist.CertificateManager;
import hirs.persist.DBManagerException;
/**
* Utility class which includes credential management functions used by the ACA.
*/
public final class CredentialManagementHelper {
private static final Logger LOG = LogManager.getLogger(CredentialManagementHelper.class);
private CredentialManagementHelper() {
}
/**
* Parses and stores the EK in the cert manager. If the cert is already present and archived,
* it is unarchived.
* @param certificateManager the certificate manager used for storage
* @param endorsementBytes the raw EK bytes used for parsing
* @return the parsed, valid EK
* @throws IllegalArgumentException if the provided bytes are not a valid EK.
*/
public static EndorsementCredential storeEndorsementCredential(
final CertificateManager certificateManager,
final byte[] endorsementBytes) throws IllegalArgumentException {
if (null == certificateManager) {
throw new IllegalArgumentException("null certificate manager");
}
if (null == endorsementBytes) {
throw new IllegalArgumentException("null endorsement credential bytes");
}
if (endorsementBytes.length <= 1) {
throw new IllegalArgumentException(
String.format("%d-length byte array given for endorsement credential",
endorsementBytes.length)
);
}
LOG.info("Parsing Endorsement Credential of length " + endorsementBytes.length);
EndorsementCredential endorsementCredential =
EndorsementCredential.parseWithPossibleHeader(endorsementBytes);
int certificateHash = endorsementCredential.getCertificateHash();
EndorsementCredential existingCredential =
EndorsementCredential.select(certificateManager).includeArchived()
.byHashCode(certificateHash).getCertificate();
if (null == existingCredential) {
LOG.info("No Endorsement Credential found with hash: " + certificateHash);
return (EndorsementCredential) certificateManager.save(endorsementCredential);
} else if (existingCredential.isArchived()) {
// if the EK is stored in the DB and it's archived, unarchive.
LOG.info("Unarchiving credential");
existingCredential.restore();
existingCredential.resetCreateTime();
certificateManager.update(existingCredential);
}
return existingCredential;
}
/**
* Parses and stores the PC in the cert manager. If the cert is already present and archived,
* it is unarchived.
* @param certificateManager the certificate manager used for storage
* @param platformBytes the raw PC bytes used for parsing
* @return the parsed, valid PC, or null if the provided bytes are not a valid EK.
*/
public static PlatformCredential storePlatformCredential(
final CertificateManager certificateManager,
final byte[] platformBytes) {
if (null == certificateManager) {
throw new IllegalArgumentException("null certificate manager");
}
if (null == platformBytes) {
throw new IllegalArgumentException("null platform credential bytes");
}
if (platformBytes.length == 0) {
throw new IllegalArgumentException(
"zero-length byte array given for platform credential"
);
}
LOG.info("Parsing Platform Credential of length " + platformBytes.length);
try {
PlatformCredential platformCredential =
PlatformCredential.parseWithPossibleHeader(platformBytes);
if (null == platformCredential) {
return null;
}
PlatformCredential existingCredential =
PlatformCredential.select(certificateManager)
.byHashCode(platformCredential.getCertificateHash()).getCertificate();
if (null == existingCredential) {
return (PlatformCredential) certificateManager.save(platformCredential);
} else if (existingCredential.isArchived()) {
// if the PC is stored in the DB and it's archived, unarchive.
LOG.info("Unarchiving credential");
existingCredential.restore();
certificateManager.update(existingCredential);
return existingCredential;
}
return existingCredential;
} catch (DBManagerException dbe) {
LOG.error("Error retrieving or saving platform credential", dbe);
} catch (Exception e) {
LOG.error("Error parsing platform credential", e);
}
return null;
}
}

View File

@ -0,0 +1,27 @@
package hirs.attestationca;
/**
* Generic exception thrown while a {@link AttestationCertificateAuthority} is processing a newly
* submitted Identity.
*/
public class IdentityProcessingException extends RuntimeException {
/**
* Constructs a generic instance of this exception using the specified reason.
*
* @param reason for the exception
*/
public IdentityProcessingException(final String reason) {
super(reason);
}
/**
* Constructs a instance of this exception with the specified reason and backing root
* exception.
*
* @param reason for this exception
* @param rootException causing this exception
*/
public IdentityProcessingException(final String reason, final Throwable rootException) {
super(reason, rootException);
}
}

View File

@ -0,0 +1,41 @@
package hirs.attestationca;
import org.hibernate.SessionFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.orm.hibernate4.LocalSessionFactoryBean;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import hirs.persist.DBAppraiserManager;
import hirs.persist.DBDeviceGroupManager;
import hirs.persist.DBPolicyManager;
import hirs.persist.PersistenceConfiguration;
import hirs.utils.HIRSProfiles;
/**
* Simply holds a contextInitialized method which will be called when the web app starts.
*/
public class InitializationListener implements ServletContextListener {
@Override
public void contextInitialized(final ServletContextEvent event) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.getEnvironment().addActiveProfile(HIRSProfiles.SERVER);
// register the database configuration and refresh the context
context.register(PersistenceConfiguration.class);
context.refresh();
// obtain reference to hibernate session factory
SessionFactory sessionFactory = context.getBean(LocalSessionFactoryBean.class).getObject();
AcaDbInit.insertDefaultEntries(
new DBAppraiserManager(sessionFactory),
new DBDeviceGroupManager(sessionFactory),
new DBPolicyManager(sessionFactory)
);
}
@Override
public void contextDestroyed(final ServletContextEvent event) {
}
}

View File

@ -0,0 +1,174 @@
package hirs.attestationca;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERUTF8String;
import org.bouncycastle.asn1.x500.AttributeTypeAndValue;
import org.bouncycastle.asn1.x500.RDN;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.X500NameBuilder;
import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.Extensions;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.asn1.x509.GeneralNamesBuilder;
import org.bouncycastle.asn1.x509.KeyPurposeId;
import org.bouncycastle.asn1.x509.TBSCertificate;
import org.bouncycastle.asn1.x509.AttributeCertificateInfo;
import org.springframework.util.CollectionUtils;
import java.io.IOException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.Collection;
import hirs.data.persist.certificate.EndorsementCredential;
import hirs.data.persist.certificate.PlatformCredential;
/**
* Builds extensions based on Platform and Endorsement credentials to provide in an issued
* certificate.
*/
public final class IssuedCertificateAttributeHelper {
private static final String TPM_ID_LABEL_OID = "2.23.133.2.15";
/**
* Object Identifier TCPA at TPM ID Label.
*/
public static final ASN1ObjectIdentifier TCPA_AT_TPM_ID_LABEL =
new ASN1ObjectIdentifier(TPM_ID_LABEL_OID);
/**
* The extended key usage extension.
*/
public static final Extension EXTENDED_KEY_USAGE_EXTENSION;
private static final Logger LOG = LogManager.getLogger(IssuedCertificateAttributeHelper.class);
private static final ASN1ObjectIdentifier TCG_KP_AIK_CERTIFICATE_ATTRIBUTE =
new ASN1ObjectIdentifier("2.23.133.8.3");
static {
// Generates an extension that identifies a cert as an AIK cert
Extension extension = null;
try {
extension = new Extension(Extension.extendedKeyUsage, true,
new ExtendedKeyUsage(new KeyPurposeId[] {
KeyPurposeId.getInstance(TCG_KP_AIK_CERTIFICATE_ATTRIBUTE)}).getEncoded());
} catch (IOException e) {
LOG.error("Error generating extended key usage extension");
}
EXTENDED_KEY_USAGE_EXTENSION = extension;
}
private IssuedCertificateAttributeHelper() {
// do not construct publicly
}
/**
* Builds the subject alternative name based on the supplied certificates.
* @param endorsementCredential the endorsement credential
* @param platformCredentials the platform credentials
* @param hostName the host name
* @return the subject alternative name extension
* @throws IOException an IO exception occurs building the extension
* @throws IllegalArgumentException if the host name is null
*/
public static Extension buildSubjectAlternativeNameFromCerts(
final EndorsementCredential endorsementCredential,
final Collection<PlatformCredential> platformCredentials, final String hostName)
throws IOException, IllegalArgumentException {
if (StringUtils.isEmpty(hostName)) {
LOG.error("null host name");
throw new IllegalArgumentException("must provide host name");
}
// assemble AIK cert SAN, using info from EC and PC
X500NameBuilder nameBuilder = new X500NameBuilder();
populateEndorsementCredentialAttributes(endorsementCredential, nameBuilder);
if (!CollectionUtils.isEmpty(platformCredentials)) {
for (PlatformCredential platformCredential : platformCredentials) {
populatePlatformCredentialAttributes(platformCredential, nameBuilder);
}
}
// add the OID for the TCG-required TPM ID label
DERUTF8String idLabel = new DERUTF8String(hostName);
nameBuilder.addRDN(new AttributeTypeAndValue(TCPA_AT_TPM_ID_LABEL, idLabel));
// put everything into the SAN, usable by the certificate builder
GeneralNamesBuilder genNamesBuilder = new GeneralNamesBuilder();
genNamesBuilder.addName(new GeneralName(nameBuilder.build()));
DEROctetString sanContent =
new DEROctetString(genNamesBuilder.build().getEncoded());
Extension subjectAlternativeName = new Extension(Extension.subjectAlternativeName,
true, sanContent);
return subjectAlternativeName;
}
private static void populatePlatformCredentialAttributes(
final PlatformCredential platformCredential,
final X500NameBuilder nameBuilder) throws IOException {
if (null == platformCredential) {
return;
}
final RDN[] rdns;
try {
LOG.debug("Applying platform credential attributes to SAN");
AttributeCertificateInfo platformCredentialAttributeHolders =
platformCredential.getAttributeCertificate().getAcinfo();
rdns = ((X500Name) GeneralNames.fromExtensions(
platformCredentialAttributeHolders.getExtensions(),
Extension.subjectAlternativeName).getNames()[0].getName()).getRDNs();
} catch (IllegalArgumentException e) {
LOG.error("Unable to extract attributes from platform credential", e);
return;
}
populateRdnAttributesInNameBuilder(nameBuilder, rdns);
}
private static void populateEndorsementCredentialAttributes(
final EndorsementCredential endorsementCredential, final X500NameBuilder nameBuilder) {
if (null == endorsementCredential) {
return;
}
final RDN[] rdns;
try {
LOG.debug("Applying endorsement credential attributes to SAN");
X509Certificate endorsementX509 = endorsementCredential.getX509Certificate();
TBSCertificate tbsCertificate = TBSCertificate.getInstance(
endorsementX509.getTBSCertificate());
Extensions extensions = tbsCertificate.getExtensions();
GeneralNames names = GeneralNames.fromExtensions(extensions,
Extension.subjectAlternativeName);
if (names != null) {
X500Name x500 = (X500Name) names.getNames()[0].getName();
rdns = x500.getRDNs();
populateRdnAttributesInNameBuilder(nameBuilder, rdns);
} else {
LOG.error("No RDNs in endorsement credential attributes");
return;
}
} catch (CertificateEncodingException e) {
LOG.error("Certificate encoding exception", e);
return;
} catch (IOException e) {
LOG.error("Error creating x509 cert from endorsement credential", e);
return;
}
}
private static void populateRdnAttributesInNameBuilder(final X500NameBuilder nameBuilder,
final RDN[] rdns) {
for (final RDN rdn : rdns) {
nameBuilder.addRDN(rdn.getTypesAndValues()[0]);
}
}
}

View File

@ -0,0 +1,251 @@
package hirs.attestationca.configuration;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.annotation.PropertySources;
import org.springframework.context.annotation.Scope;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.env.Environment;
import org.springframework.orm.hibernate4.LocalSessionFactoryBean;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import javax.annotation.PostConstruct;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.X509Certificate;
import hirs.persist.DBDeviceGroupManager;
import hirs.persist.DBDeviceManager;
import hirs.persist.DeviceGroupManager;
import hirs.persist.DeviceManager;
import hirs.persist.HibernateConfiguration;
import hirs.structs.converters.SimpleStructConverter;
import hirs.structs.converters.StructConverter;
import hirs.utils.LogConfigurationUtil;
/**
* Provides application context configuration for the Attestation Certificate Authority
* application. The properties are processed in order and as such, the last property file read in
* will override properties that may had already been defined previously. In other words, the
* 'defaults.properties' file provides a basic standard of properties that can be overrode by the
*/
@Configuration
@PropertySources({
@PropertySource(value = "classpath:defaults.properties"),
// detects if file exists, if not, ignore errors
@PropertySource(value = "file:/etc/hirs/aca/aca.properties",
ignoreResourceNotFound = true)
})
@ComponentScan({ "hirs.attestationca", "hirs.attestationca.service", "hirs.validation",
"hirs.data.service" })
@Import(HibernateConfiguration.class)
@EnableWebMvc
public class AttestationCertificateAuthorityConfiguration extends WebMvcConfigurerAdapter {
private static final Logger LOG =
LogManager.getLogger(AttestationCertificateAuthorityConfiguration.class);
static {
try {
LogConfigurationUtil.applyConfiguration();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private static final String CLIENT_FILES_PATH = "file:/etc/hirs/aca/client-files/";
@Value("${aca.directories.certificates}")
private String certificatesLocation;
@Value("${aca.keyStore.location}")
private String keyStoreLocation;
@Value("${aca.keyStore.password:''}")
private String keyStorePassword;
@Value("${aca.keyStore.alias}")
private String keyAlias;
@Autowired
private Environment environment;
@Autowired
private LocalSessionFactoryBean sessionFactory;
/**
* @return bean to resolve injected annotation.Value
* property expressions for beans.
*/
@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
/**
* Initialization of the ACA. Detects environment and runs configuration methods as required.
* This method is intended to be invoked by the Spring application context.
*/
@PostConstruct
void initialize() {
// ensure that Bouncy Castle is registered as a security provider
Security.addProvider(new BouncyCastleProvider());
// obtain path to ACA configuration
Path certificatesPath = Paths.get(certificatesLocation);
// create base directories if they do not exist
try {
Files.createDirectories(certificatesPath);
} catch (IOException e) {
throw new BeanInitializationException(
"Encountered error while initializing ACA directories: " + e.getMessage(), e);
}
// create the ACA key store if it doesn't exist
Path keyStorePath = Paths.get(keyStoreLocation);
if (!Files.exists(keyStorePath)) {
throw new IllegalStateException(
String.format("ACA Key Store not found at %s. Consult the HIRS User "
+ "Guide for ACA installation instructions.", keyStoreLocation));
}
}
/**
* @return the {@link PrivateKey} of the ACA
*/
@Bean
public PrivateKey privateKey() {
// obtain the key store
KeyStore keyStore = keyStore();
try {
// load the key from the key store
PrivateKey acaKey = (PrivateKey) keyStore.getKey(keyAlias,
keyStorePassword.toCharArray());
// break early if the certificate is not available.
if (acaKey == null) {
throw new BeanInitializationException(String.format("Key with alias "
+ "%s was not in KeyStore %s. Ensure that the KeyStore has the "
+ "specified certificate. ", keyAlias, keyStoreLocation));
}
return acaKey;
} catch (Exception e) {
throw new BeanInitializationException("Encountered error loading ACA private key "
+ "from key store: " + e.getMessage(), e);
}
}
/**
* @return the {@link X509Certificate} of the ACA
*/
@Bean
public X509Certificate acaCertificate() {
KeyStore keyStore = keyStore();
try {
X509Certificate acaCertificate = (X509Certificate) keyStore.getCertificate(keyAlias);
// break early if the certificate is not available.
if (acaCertificate == null) {
throw new BeanInitializationException(String.format("Certificate with alias "
+ "%s was not in KeyStore %s. Ensure that the KeyStore has the "
+ "specified certificate. ", keyAlias, keyStoreLocation));
}
return acaCertificate;
} catch (KeyStoreException e) {
throw new BeanInitializationException("Encountered error loading ACA certificate "
+ "from key store: " + e.getMessage(), e);
}
}
/**
* @return the {@link java.security.KeyStore} that contains the certificates for the ACA.
*/
@Bean
public KeyStore keyStore() {
Path keyStorePath = Paths.get(keyStoreLocation);
// attempt to open the key store. if that fails, log a meaningful message before failing.
try {
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(Files.newInputStream(keyStorePath), keyStorePassword.toCharArray());
return keyStore;
} catch (Exception e) {
LOG.error(String.format(
"Encountered error while loading ACA key store. The most common issue is "
+ "that configured password does not work on the configured key"
+ " store %s.", keyStorePath));
LOG.error(String.format("Exception message: %s", e.getMessage()));
throw new BeanInitializationException(e.getMessage(), e);
}
}
/**
* Prototyped {@link StructConverter}. In other words, all instances returned by this method
* will be configured identically, but subsequent invocations will return a new instance.
*
* @return ready to use {@link StructConverter}.
*/
@Bean
@Scope("prototype")
public static StructConverter structConverter() {
return new SimpleStructConverter();
}
/**
* Creates a {@link DeviceGroupManager} ready to use.
*
* @return {@link DeviceGroupManager}
*/
@Bean
public DeviceGroupManager deviceGroupManager() {
return new DBDeviceGroupManager(sessionFactory.getObject());
}
/**
* Creates a {@link DeviceManager} ready to use.
*
* @return {@link DeviceManager}
*/
@Bean
public DeviceManager deviceManager() {
return new DBDeviceManager(sessionFactory.getObject());
}
@Override
public void addResourceHandlers(final ResourceHandlerRegistry resourceHandlerRegistry) {
resourceHandlerRegistry.addResourceHandler("/client-files/**")
.addResourceLocations(CLIENT_FILES_PATH);
}
@Override
public void configureDefaultServletHandling(final DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}

View File

@ -0,0 +1,4 @@
/**
* Includes the Spring Framework application context configuration classes.
*/
package hirs.attestationca.configuration;

View File

@ -0,0 +1,5 @@
/**
* Base package that includes common exceptions, interfaces and base implementations for and related
* to the ACA.
*/
package hirs.attestationca;

View File

@ -0,0 +1,145 @@
package hirs.attestationca.rest;
import hirs.attestationca.IdentityProcessingException;
import hirs.persist.DBManager;
import hirs.persist.TPM2ProvisionerState;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import hirs.attestationca.AbstractAttestationCertificateAuthority;
import hirs.attestationca.service.SupplyChainValidationService;
import hirs.data.service.DeviceRegister;
import hirs.persist.CertificateManager;
import hirs.persist.DeviceManager;
import hirs.structs.converters.StructConverter;
/**
* Restful implementation of the {@link hirs.attestationca.AttestationCertificateAuthority}.
* Exposes the ACA methods as REST endpoints.
*/
@RestController
@RequestMapping("/")
public class RestfulAttestationCertificateAuthority
extends AbstractAttestationCertificateAuthority {
/**
* Constructor.
* @param supplyChainValidationService the supply chain service
* @param privateKey the ACA private key
* @param acaCertificate the ACA certificate
* @param structConverter the struct converter
* @param certificateManager the certificate manager
* @param deviceRegister the device register
* @param validDays the number of days issued certs are valid
* @param deviceManager the device manager
* @param tpm2ProvisionerStateDBManager the DBManager for persisting provisioner state
*/
@SuppressWarnings({ "checkstyle:parameternumber" })
@Autowired
public RestfulAttestationCertificateAuthority(
final SupplyChainValidationService supplyChainValidationService,
final PrivateKey privateKey, final X509Certificate acaCertificate,
final StructConverter structConverter,
final CertificateManager certificateManager,
final DeviceRegister deviceRegister,
final DeviceManager deviceManager,
final DBManager<TPM2ProvisionerState> tpm2ProvisionerStateDBManager,
@Value("${aca.certificates.validity}") final int validDays) {
super(supplyChainValidationService, privateKey, acaCertificate, structConverter,
certificateManager, deviceRegister, validDays, deviceManager,
tpm2ProvisionerStateDBManager);
}
/*
* (non-javadoc)
*
* Wrap the {@link AbstractAttestationCertificateAuthority#processIdentityRequest(byte[])}
* with a Spring {@link RequestMapping}. Effectively, this method then will allow spring to
* serialize and deserialize the request and responses on method invocation and
* return, respectively.
*/
@Override
@ResponseBody
@RequestMapping(value = "/identity-request/process", method = RequestMethod.POST,
consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE,
produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public byte[] processIdentityRequest(@RequestBody final byte[] request) {
return super.processIdentityRequest(request);
}
/**
* Listener for identity requests from TPM 2.0 provisioning.
* @param request The request object from the provisioner.
* @return The response to the provisioner.
*/
@Override
@ResponseBody
@RequestMapping(value = "/identity-claim-tpm2/process",
method = RequestMethod.POST,
consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE,
produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public byte[] processIdentityClaimTpm2(@RequestBody final byte[] request) {
return super.processIdentityClaimTpm2(request);
}
/**
* Endpoint for processing certificate requests for TPM 2.0 provisioning.
*
* @param request The credential request from the client provisioner.
* @return The response to the client provisioner.
*/
@Override
@ResponseBody
@RequestMapping(value = "/request-certificate-tpm2",
method = RequestMethod.POST,
consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE,
produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public byte[] processCertificateRequest(@RequestBody final byte[] request) {
return super.processCertificateRequest(request);
}
/*
* (non-javadoc)
*
* Wrap the {@link AbstractAttestationCertificateAuthority#getPublicKey()} with a Spring
* {@link RequestMapping} such that Spring can serialize the certificate to be returned to an
* HTTP Request.
*/
@Override
@ResponseBody
@RequestMapping(value = "/public-key", method = RequestMethod.GET,
produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public byte[] getPublicKey() {
return super.getPublicKey();
}
/**
* Handle processing of exceptions for ACA REST API.
* @param e exception thrown during invocation of ACA REST API
* @return exception thrown during invocation of ACA REST API
*/
@ExceptionHandler
@ResponseBody
@ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR)
public Exception handleException(final Exception e) {
if (e instanceof IdentityProcessingException) {
LOG.error("Processing exception while provisioning", e.getMessage(), e);
} else {
LOG.error(String.format("Encountered unexpected error while processing identity "
+ "claim: %s", e.getMessage()), e);
}
return e;
}
}

View File

@ -0,0 +1,4 @@
/**
* RESTful implementations of the {@link hirs.attestationca.AttestationCertificateAuthority}.
*/
package hirs.attestationca.rest;

View File

@ -0,0 +1,28 @@
package hirs.attestationca.service;
import java.util.Set;
import hirs.data.persist.Device;
import hirs.data.persist.SupplyChainValidationSummary;
import hirs.data.persist.certificate.EndorsementCredential;
import hirs.data.persist.certificate.PlatformCredential;
/**
* Interface defining a component that will perform supply chain validations, which yields a
* {@link SupplyChainValidationSummary}.
*/
public interface SupplyChainValidationService {
/**
* The "main" method of supply chain validation. Takes the credentials from an identity
* request and validates the supply chain in accordance to the current supply chain
* policy.
*
* @param ec The endorsement credential from the identity request.
* @param pc The set of platform credentials from the identity request.
* @param device The device to be validated.
* @return True if validation is successful, false otherwise.
*/
SupplyChainValidationSummary validateSupplyChain(EndorsementCredential ec,
Set<PlatformCredential> pc,
Device device);
}

View File

@ -0,0 +1,373 @@
package hirs.attestationca.service;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Import;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.apache.logging.log4j.Level;
import hirs.appraiser.Appraiser;
import hirs.appraiser.SupplyChainAppraiser;
import hirs.data.persist.AppraisalStatus;
import hirs.data.persist.Device;
import hirs.data.persist.DeviceInfoReport;
import hirs.data.persist.SupplyChainPolicy;
import hirs.data.persist.SupplyChainValidation;
import hirs.data.persist.SupplyChainValidationSummary;
import hirs.data.persist.certificate.Certificate;
import hirs.data.persist.certificate.CertificateAuthorityCredential;
import hirs.data.persist.certificate.EndorsementCredential;
import hirs.data.persist.certificate.PlatformCredential;
import hirs.persist.AppraiserManager;
import hirs.persist.CertificateManager;
import hirs.persist.CertificateSelector;
import hirs.persist.CrudManager;
import hirs.persist.DBManagerException;
import hirs.persist.PersistenceConfiguration;
import hirs.persist.PolicyManager;
import hirs.validation.CredentialValidator;
/**
* The main executor of supply chain verification tasks. The AbstractAttestationCertificateAuthority
* will feed it the PC, EC, other relevant certificates, and serial numbers of the provisioning
* task, and it will then manipulate the data as necessary, retrieve useful certs, and arrange
* for actual validation by the SupplyChainValidator.
*/
@Service
@Import(PersistenceConfiguration.class)
public class SupplyChainValidationServiceImpl implements SupplyChainValidationService {
private PolicyManager policyManager;
private AppraiserManager appraiserManager;
private CertificateManager certificateManager;
private CredentialValidator supplyChainCredentialValidator;
private CrudManager<SupplyChainValidationSummary> supplyChainValidatorSummaryManager;
private static final Logger LOGGER =
LogManager.getLogger(SupplyChainValidationServiceImpl.class);
/**
* Constructor.
* @param policyManager the policy manager
* @param appraiserManager the appraiser manager
* @param certificateManager the cert manager
* @param supplyChainValidatorSummaryManager the summary manager
* @param supplyChainCredentialValidator the credential validator
*/
@Autowired
public SupplyChainValidationServiceImpl(final PolicyManager policyManager,
final AppraiserManager appraiserManager,
final CertificateManager certificateManager,
final CrudManager<SupplyChainValidationSummary> supplyChainValidatorSummaryManager,
final CredentialValidator supplyChainCredentialValidator) {
this.policyManager = policyManager;
this.appraiserManager = appraiserManager;
this.certificateManager = certificateManager;
this.supplyChainValidatorSummaryManager = supplyChainValidatorSummaryManager;
this.supplyChainCredentialValidator = supplyChainCredentialValidator;
}
/**
* The "main" method of supply chain validation. Takes the credentials from an identity
* request and validates the supply chain in accordance to the current supply chain
* policy.
*
* @param ec The endorsement credential from the identity request.
* @param pcs The platform credentials from the identity request.
* @param device The device to be validated.
* @return A summary of the validation