/*
* Copyright (C) 2014 Intel Corporation
* All rights reserved.
*/
package com.intel.mtwilson.tls.policy.provider;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.intel.dcsg.cpg.crypto.CryptographyException;
import com.intel.dcsg.cpg.io.UUID;
import com.intel.dcsg.cpg.net.Hostname;
import com.intel.dcsg.cpg.net.InternetAddress;
import com.intel.dcsg.cpg.x509.repository.CertificateRepository;
import com.intel.dcsg.cpg.x509.repository.KeystoreCertificateRepository;
import com.intel.mtwilson.My;
import com.intel.mtwilson.as.controller.exceptions.ASDataException;
import com.intel.mtwilson.as.controller.exceptions.IllegalOrphanException;
import com.intel.mtwilson.as.controller.exceptions.NonexistentEntityException;
import com.intel.mtwilson.as.data.TblHosts;
import com.intel.mtwilson.jaxrs2.provider.JacksonObjectMapperProvider;
import com.intel.mtwilson.tls.policy.TlsPolicyChoice;
import com.intel.mtwilson.tls.policy.TlsPolicyDescriptor;
import com.intel.mtwilson.tls.policy.TlsProtection;
import com.intel.mtwilson.tls.policy.factory.TlsPolicyMigrationException;
import com.intel.mtwilson.tls.policy.factory.TlsPolicyProvider;
import com.intel.mtwilson.tls.policy.jdbi.TlsPolicyDAO;
import com.intel.mtwilson.tls.policy.jdbi.TlsPolicyJdbiFactory;
import com.intel.mtwilson.tls.policy.jdbi.TlsPolicyRecord;
import com.intel.mtwilson.tls.policy.codec.impl.JsonTlsPolicyWriter;
import com.intel.mtwilson.tls.policy.factory.TlsPolicyFactoryUtil;
import java.io.IOException;
import java.nio.charset.Charset;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.HashSet;
import javax.ws.rs.core.MediaType;
import org.apache.commons.codec.binary.Base64;
/**
* Looks up a TLS Policy for the input object in the database based on
* attributes of the object such as host id. Prefers private over shared
* policies. This strategy can be subclassed for subnet and vendor specific
* searches as well as pre-registration of per-host policies (from management
* tools, done so that registration API doesn't need to reference policy)
*
* @author jbuhacoff
*/
public class StoredTlsPolicyProvider implements TlsPolicyProvider {
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(StoredTlsPolicyProvider.class);
private String hostId;
public StoredTlsPolicyProvider(HostDescriptor hostDescriptor) {
this.hostId = hostDescriptor.getHostId();
}
@Override
public TlsPolicyChoice getTlsPolicyChoice() {
try {
TblHosts hostRecord;
if( UUID.isValid(hostId)) {
hostRecord = My.jpa().mwHosts().findHostByUuid(hostId);
}
else {
hostRecord = My.jpa().mwHosts().findByName(hostId);
}
if (hostRecord == null) {
return null;
}
if (hostRecord.getTlsPolicyId() != null) {
log.debug("StoredTlsPolicy id: {}", hostRecord.getTlsPolicyId());
TlsPolicyChoice tlsPolicyChoice = new TlsPolicyChoice();
tlsPolicyChoice.setTlsPolicyId(hostRecord.getTlsPolicyId());
return tlsPolicyChoice;
}
if (hostRecord.getTlsPolicyName() != null) {
log.debug("StoredTlsPolicy name: {}", hostRecord.getTlsPolicyName());
if (hostRecord.getTlsPolicyName().equals("INSECURE") ) {
TlsPolicyChoice tlsPolicyChoice = new TlsPolicyChoice();
tlsPolicyChoice.setTlsPolicyDescriptor(new TlsPolicyDescriptor());
tlsPolicyChoice.getTlsPolicyDescriptor().setPolicyType("INSECURE");
return tlsPolicyChoice;
}
if (hostRecord.getTlsPolicyName().equals("TRUST_FIRST_CERTIFICATE")) {
if (hostRecord.getTlsKeystore() != null) {
// we treat a TRUST_FIRST_CERTIFICATE policy for which there is already a cert as a public key policy during migration, and if there is not already a cert then we convert it to the insecure policy
// automatic data migration: load the certificates from the keystore, create a TlsPolicyDescriptor with the public keys, save it as a new private mw_tls_policy record, and update the mw_hosts record to point to it
TlsPolicyChoice tlsPolicyChoice = migrateExistingTlsKeystoreToTlsPolicy(hostRecord, new PublicKeyTlsPolicyConverter());
return tlsPolicyChoice;
} else {
TlsPolicyChoice tlsPolicyChoice = new TlsPolicyChoice();
tlsPolicyChoice.setTlsPolicyDescriptor(new TlsPolicyDescriptor());
tlsPolicyChoice.getTlsPolicyDescriptor().setPolicyType("INSECURE");
return tlsPolicyChoice;
}
}
if (hostRecord.getTlsPolicyName().equals("TRUST_KNOWN_CERTIFICATE")) {
if (hostRecord.getTlsKeystore() != null) {
// automatic data migration: load the certificates from the keystore, create a TlsPolicyDescriptor with the public keys, save it as a new private mw_tls_policy record, and update the mw_hosts record to point to it
TlsPolicyChoice tlsPolicyChoice = migrateExistingTlsKeystoreToTlsPolicy(hostRecord, new PublicKeyTlsPolicyConverter());
return tlsPolicyChoice;
} else {
log.warn("Missing keystore for TRUST_KNOWN_CERTIFICATE for host {}", hostId);
hostRecord.setTlsPolicyName(null);
}
}
if( hostRecord.getTlsPolicyName().equals("TRUST_CA_VERIFY_HOSTNAME")) {
if (hostRecord.getTlsKeystore() != null) {
// automatic data migration: load the certificates from the keystore, create a TlsPolicyDescriptor with the certificates, save it as a new private mw_tls_policy record, and update the mw_hosts record to point to it
TlsPolicyChoice tlsPolicyChoice = migrateExistingTlsKeystoreToTlsPolicy(hostRecord, new CertificateTlsPolicyConverter());
return tlsPolicyChoice;
} else {
log.warn("Missing keystore for TRUST_CA_VERIFY_HOSTNAME for host {}", hostId);
hostRecord.setTlsPolicyName(null);
}
}
log.warn("Existing TLS policy cannot be automatically migrated: {}", hostRecord.getTlsPolicyName());
}
} catch (IOException | CryptographyException e) {
log.error("Cannot load host record for {}", hostId, e);
}
return null;
}
private static interface TlsKeystoreConverter {
TlsPolicyDescriptor convert(CertificateRepository repository);
}
private static class PublicKeyTlsPolicyConverter implements TlsKeystoreConverter {
@Override
public TlsPolicyDescriptor convert(CertificateRepository repository) {
TlsPolicyDescriptor tlsPolicyDescriptor = new TlsPolicyDescriptor();
tlsPolicyDescriptor.setPolicyType("public-key");
tlsPolicyDescriptor.setProtection(TlsPolicyFactoryUtil.getAllTlsProtection());
tlsPolicyDescriptor.setMeta(new HashMap<String,String>());
tlsPolicyDescriptor.getMeta().put("encoding", "base64");
tlsPolicyDescriptor.setData(new HashSet<String>());
for (X509Certificate certificate : repository.getCertificates()) {
String publicKey = Base64.encodeBase64String(certificate.getPublicKey().getEncoded());
tlsPolicyDescriptor.getData().add(publicKey);
log.debug("Added public key to policy: {}", publicKey);
}
return tlsPolicyDescriptor;
}
}
private static class CertificateTlsPolicyConverter implements TlsKeystoreConverter {
@Override
public TlsPolicyDescriptor convert(CertificateRepository repository) {
TlsPolicyDescriptor tlsPolicyDescriptor = new TlsPolicyDescriptor();
tlsPolicyDescriptor.setPolicyType("certificate");
tlsPolicyDescriptor.setProtection(TlsPolicyFactoryUtil.getAllTlsProtection());
tlsPolicyDescriptor.setMeta(new HashMap<String,String>());
tlsPolicyDescriptor.getMeta().put("encoding", "base64");
tlsPolicyDescriptor.setData(new HashSet<String>());
for (X509Certificate certificate : repository.getCertificates()) {
try {
String encoded = Base64.encodeBase64String(certificate.getEncoded());
tlsPolicyDescriptor.getData().add(encoded);
log.debug("Added certificate to policy: {}", encoded);
}
catch(CertificateEncodingException e) {
log.error("Cannot add certificate to policy: {}", e.getMessage());
}
}
return tlsPolicyDescriptor;
}
}
private TlsPolicyChoice migrateExistingTlsKeystoreToTlsPolicy(TblHosts hostRecord, TlsKeystoreConverter converter) {
try {
// load certificates from keystore
KeystoreCertificateRepository repository = new KeystoreCertificateRepository(hostRecord.getTlsKeystoreResource(), My.configuration().getTlsKeystorePassword());
TlsPolicyDescriptor tlsPolicyDescriptor = converter.convert(repository);
// save the descriptor as a new record in mw_tls_policy
try (TlsPolicyDAO dao = TlsPolicyJdbiFactory.tlsPolicyDAO()) {
JsonTlsPolicyWriter writer = new JsonTlsPolicyWriter();
TlsPolicyRecord tlsPolicyRecord = new TlsPolicyRecord();
tlsPolicyRecord.setId(new UUID());
tlsPolicyRecord.setName(hostRecord.getUuid_hex());
tlsPolicyRecord.setPrivate(true);
tlsPolicyRecord.setContentType(MediaType.APPLICATION_JSON);
tlsPolicyRecord.setContent(writer.write(tlsPolicyDescriptor));
tlsPolicyRecord.setComment("automatic migration");
dao.insertTlsPolicy(tlsPolicyRecord);
hostRecord.setTlsPolicyId(tlsPolicyRecord.getId().toString());
try {
My.jpa().mwHosts().edit(hostRecord);
} catch (IllegalOrphanException | NonexistentEntityException | ASDataException | CryptographyException e) {
log.error("Cannot store host record for {}: {}", hostId, e.getMessage());
throw new TlsPolicyMigrationException("Cannot store host record", e,hostId);
}
}
TlsPolicyChoice tlsPolicyChoice = new TlsPolicyChoice();
tlsPolicyChoice.setTlsPolicyDescriptor(tlsPolicyDescriptor);
return tlsPolicyChoice;
} catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException e) {
log.error("Cannot load keystore for TRUST_KNOWN_CERTIFICATE policy for host {}", hostId, e);
hostRecord.setTlsPolicyName(null);
}
return null;
}
public static interface HostDescriptor {
String getHostId();
InternetAddress getInternetAddress();
}
}