package com.intrbiz.bergamot.agent.manager.store.impl;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.UUID;
import org.apache.log4j.Logger;
import com.intrbiz.bergamot.agent.manager.store.BergamotKeyStore;
import com.intrbiz.bergamot.crypto.util.CertificatePair;
import com.intrbiz.bergamot.crypto.util.SerialNum;
/**
* A simple, low security file based key store, which
* stores the certificates and keys in raw, unprotected
* PEM format.
*
* If using this key store, it is suggested to use an
* encrypted file system.
*
* On disk layout
*
* base/
* /root/
* /ca.crt
* /ca.key
* /site/
* /<site_uuid>.crt
* /<site_uuid>.key
* /server/
* /<crt_uuid>/
* /<crt_uuid>.<rev>.crt
* /<crt_uuid>.<rev>.key
* /<common_name>.crt -> /server/<crt_uuid>/<crt_uuid>.<rev>.crt
* /<common_name>.key -> /server/<crt_uuid>/<crt_uuid>.<rev>.key
* /agent/
* /<crt_uuid>/
* /<crt_uuid>.<rev>.crt
* /<crt_uuid>.<rev>.key
* /<agent_uuid>.crt -> /agent/<crt_uuid>/<crt_uuid>.<rev>.crt
* /<agent_uuid>.key -> /agent/<crt_uuid>/<crt_uuid>.<rev>.key
* /template/
* /<crt_uuid>/
* /<crt_uuid>.<rev>.crt
* /<crt_uuid>.<rev>.key
* /<template_uuid>.crt -> /template/<crt_uuid>/<crt_uuid>.<rev>.crt
* /<template_uuid>.key -> /template/<crt_uuid>/<crt_uuid>.<rev>.key
*
*/
public class FileKeyStore implements BergamotKeyStore
{
private Logger logger = Logger.getLogger(FileKeyStore.class);
private final File base;
private final File root;
private final File site;
private final File server;
private final File agent;
private final File template;
public FileKeyStore(File base)
{
this.base = base.getAbsoluteFile();
this.root = new File(this.base, "root");
this.site = new File(this.base, "site");
this.server = new File(this.base, "server");
this.agent = new File(this.base, "agent");
this.template = new File(this.base, "template");
// ensure dirs exists
if (! this.base.exists()) this.base.mkdirs();
if (! this.root.exists()) this.root.mkdirs();
if (! this.site.exists()) this.site.mkdirs();
if (! this.server.exists()) this.server.mkdirs();
if (! this.agent.exists()) this.agent.mkdirs();
if (! this.template.exists()) this.template.mkdirs();
}
/**
* Check this keystore structure is ok
*/
public void check()
{
// validate the root
if (! this.hasRootCA())
{
logger.error("No root CA keypair exists");
}
// sites
File[] sites = this.site.listFiles();
if (sites != null)
{
for (File site : sites)
{
if (site.isFile() && site.getName().endsWith(".crt"))
{
try
{
UUID siteId = UUID.fromString(site.getName().replace(".crt", ""));
if (! this.hasSiteCA(siteId))
{
logger.error("No site CA keypair exists for site " + siteId);
}
}
catch (IllegalArgumentException e)
{
}
}
}
}
// servers
File[] servers = this.server.listFiles();
if (servers != null)
{
for (File server : servers)
{
if ((! Files.isSymbolicLink(server.toPath())) && (! server.isDirectory()) && server.getName().endsWith(".crt"))
{
try
{
// get the common name
String commonName = server.getName().replace(".crt", "");
// looks like we have a regular file, convert to links
logger.info("Migrating server " + commonName + " into V2 file layout");
// migrate
CertificatePair crtPair = new CertificatePair(server, null);
SerialNum crtSerial = SerialNum.fromBigInt(crtPair.getCertificate().getSerialNumber());
File crtFile = this.serverCrtFile(crtSerial);
File keyFile = this.serverKeyFile(crtSerial);
File crtLink = this.serverCrtFile(commonName);
File keyLink = this.serverKeyFile(commonName);
// move stuff
crtFile.getParentFile().mkdirs();
Files.move(server.toPath(), crtFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
Files.createSymbolicLink(crtLink.toPath(), crtFile.toPath());
if (keyLink.exists())
{
keyFile.getParentFile().mkdirs();
Files.move(keyLink.toPath(), keyFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
Files.createSymbolicLink(keyLink.toPath(), keyFile.toPath());
}
}
catch (Exception e)
{
logger.error("Failed to migrate server " + server.getName() + " to V2 file layout");
}
}
}
}
// agents
File[] agentSites = this.agent.listFiles();
if (agentSites != null)
{
for (File agentSite : agentSites)
{
if (agentSite.isDirectory())
{
try
{
UUID agentSiteId = UUID.fromString(agentSite.getName());
File[] agents = agentSite.listFiles();
if (agents != null)
{
for (File agent : agents)
{
if ((! Files.isSymbolicLink(agent.toPath())) && (! agent.isDirectory()) && agent.getName().endsWith(".crt"))
{
try
{
// get the agent id
UUID agentId = UUID.fromString(agent.getName().replace(".crt", ""));
logger.info("Migrating agent " + agentId + " into V2 file layout");
// migrate
CertificatePair crtPair = new CertificatePair(agent, null);
SerialNum crtSerial = SerialNum.fromBigInt(crtPair.getCertificate().getSerialNumber());
File crtFile = this.agentCrtFile(agentSiteId, crtSerial);
File keyFile = this.agentKeyFile(agentSiteId, crtSerial);
File crtLink = this.agentCrtFile(agentSiteId, agentId);
File keyLink = this.agentKeyFile(agentSiteId, agentId);
// move stuff
crtFile.getParentFile().mkdirs();
Files.move(server.toPath(), crtFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
Files.createSymbolicLink(crtLink.toPath(), crtFile.toPath());
if (keyLink.exists())
{
keyFile.getParentFile().mkdirs();
Files.move(keyLink.toPath(), keyFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
Files.createSymbolicLink(keyLink.toPath(), keyFile.toPath());
}
}
catch (Exception e)
{
logger.error("Failed to migrate agent " + agent.getName() + " to V2 file layout");
}
}
}
}
}
catch (IllegalArgumentException e)
{
}
}
}
}
}
@Override
public boolean hasRootCA()
{
return new File(this.root, "ca.crt").exists() || new File(this.root, "ca.key").exists();
}
@Override
public CertificatePair loadRootCA()
{
try
{
return new CertificatePair(new File(this.root, "ca.crt"), new File(this.root, "ca.key"));
}
catch (Exception e)
{
throw new RuntimeException("Failed to load Root CA", e);
}
}
@Override
public void storeRootCA(CertificatePair pair)
{
synchronized (this)
{
if (this.hasRootCA()) throw new RuntimeException("Root CA already exists, not overstoring!");
try
{
pair.saveCertificate(new File(this.root, "ca.crt"));
pair.saveKey(new File(this.root, "ca.key"));
}
catch (Exception e)
{
throw new RuntimeException("Failed to store Root CA", e);
}
}
}
@Override
public boolean hasSiteCA(UUID siteId)
{
return new File(this.site, siteId + ".crt").exists() || new File(this.site, siteId + ".key").exists();
}
@Override
public CertificatePair loadSiteCA(UUID siteId)
{
try
{
return new CertificatePair(new File(this.site, siteId + ".crt"), new File(this.site, siteId + ".key"));
}
catch (Exception e)
{
throw new RuntimeException("Failed to load Site CA for site: " + siteId, e);
}
}
@Override
public void storeSiteCA(UUID siteId, CertificatePair pair)
{
synchronized (this)
{
if (this.hasSiteCA(siteId)) throw new RuntimeException("Site CA already exists for site " + siteId + ", not overstoring!");
try
{
pair.saveCertificate(new File(this.site, siteId + ".crt"));
pair.saveKey(new File(this.site, siteId + ".key"));
}
catch (Exception e)
{
throw new RuntimeException("Failed to store Site CA for site: " + siteId, e);
}
}
}
private File agentCrtFile(UUID siteId, UUID agentId)
{
return new File(new File(this.agent, siteId.toString()), agentId + ".crt");
}
private File agentCrtFile(UUID siteId, SerialNum agentSerial)
{
return new File(new File(new File(this.agent, siteId.toString()), agentSerial.getId().toString()), agentSerial.getId() + "." + agentSerial.getRev() + ".crt");
}
private File agentKeyFile(UUID siteId, UUID agentId)
{
return new File(new File(this.agent, siteId.toString()), agentId + ".key");
}
private File agentKeyFile(UUID siteId, SerialNum agentSerial)
{
return new File(new File(new File(this.agent, siteId.toString()), agentSerial.getId().toString()), agentSerial.getId() + "." + agentSerial.getRev() + ".key");
}
@Override
public boolean hasAgent(UUID siteId, UUID agentId)
{
return this.agentCrtFile(siteId, agentId).exists();
}
@Override
public CertificatePair loadAgent(UUID siteId, UUID agentId)
{
try
{
if (this.agentKeyFile(siteId, agentId).exists())
{
return new CertificatePair(this.agentCrtFile(siteId, agentId), this.agentKeyFile(siteId, agentId));
}
else
{
return new CertificatePair(this.agentCrtFile(siteId, agentId));
}
}
catch (Exception e)
{
throw new RuntimeException("Failed to load certificate for agent: " + siteId + "::" + agentId, e);
}
}
@Override
public void storeAgent(UUID siteId, UUID agentId, CertificatePair pair)
{
synchronized (this)
{
try
{
SerialNum serial = SerialNum.fromBigInt(pair.getCertificate().getSerialNumber());
// store the certificate under the serial number
File crtFile = this.agentCrtFile(siteId, serial);
crtFile.getParentFile().mkdirs();
pair.saveCertificate(crtFile);
// link the certificate to the agent
Path crtLink = this.agentCrtFile(siteId, agentId).toPath();
try
{
Files.delete(crtLink);
}
catch (NoSuchFileException e)
{
}
Files.createSymbolicLink(crtLink, crtFile.toPath());
// key?
if (pair.getKey() != null)
{
// store the key under the serial number
File keyFile = this.agentKeyFile(siteId, serial);
keyFile.getParentFile().mkdirs();
pair.saveKey(keyFile);
// link the key to the agent
Path keyLink = this.agentKeyFile(siteId, agentId).toPath();
try
{
Files.delete(keyLink);
}
catch (NoSuchFileException e)
{
}
Files.createSymbolicLink(keyLink, keyFile.toPath());
}
}
catch (Exception e)
{
throw new RuntimeException("Failed to store certificate for agent: " + siteId + "::" + agentId, e);
}
}
}
private File serverCrtFile(String commonName)
{
return new File(this.server, commonName + ".crt");
}
private File serverCrtFile(SerialNum serverSerial)
{
return new File(new File(this.server, serverSerial.getId().toString()), serverSerial.getId() + "." + serverSerial.getRev() + ".crt");
}
private File serverKeyFile(String commonName)
{
return new File(this.server, commonName + ".key");
}
private File serverKeyFile(SerialNum serverSerial)
{
return new File(new File(this.server, serverSerial.getId().toString()), serverSerial.getId() + "." + serverSerial.getRev() + ".key");
}
@Override
public boolean hasServer(String commonName)
{
return this.serverCrtFile(commonName).exists();
}
@Override
public CertificatePair loadServer(String commonName)
{
try
{
if (this.serverKeyFile(commonName).exists())
{
return new CertificatePair(this.serverCrtFile(commonName), this.serverKeyFile(commonName));
}
else
{
return new CertificatePair(this.serverCrtFile(commonName));
}
}
catch (Exception e)
{
throw new RuntimeException("Failed to load certificate for server: " + commonName, e);
}
}
@Override
public void storeServer(String commonName, CertificatePair pair)
{
synchronized (this)
{
try
{
SerialNum serial = SerialNum.fromBigInt(pair.getCertificate().getSerialNumber());
// store and link the certificate
File crtFile = this.serverCrtFile(serial);
crtFile.getParentFile().mkdirs();
pair.saveCertificate(crtFile);
Path crtLink = this.serverCrtFile(commonName).toPath();
try
{
Files.delete(crtLink);
}
catch (NoSuchFileException e)
{
}
Files.createSymbolicLink(crtLink, crtFile.toPath());
// store and link the key
if (pair.getKey() != null)
{
File keyFile = this.serverKeyFile(serial);
keyFile.getParentFile().mkdirs();
pair.saveKey(keyFile);
Path keyLink = this.serverKeyFile(commonName).toPath();
try
{
Files.delete(keyLink);
}
catch (NoSuchFileException e)
{
}
Files.createSymbolicLink(keyLink, keyFile.toPath());
}
}
catch (Exception e)
{
throw new RuntimeException("Failed to store server certificate: " + commonName, e);
}
}
}
private File templateCrtFile(UUID siteId, UUID templateId)
{
return new File(new File(this.template, siteId.toString()), templateId + ".crt");
}
private File templateCrtFile(UUID siteId, SerialNum templateSerial)
{
return new File(new File(new File(this.template, siteId.toString()), templateSerial.getId().toString()), templateSerial.getId() + "." + templateSerial.getRev() + ".crt");
}
private File templateKeyFile(UUID siteId, UUID templateId)
{
return new File(new File(this.template, siteId.toString()), templateId + ".key");
}
private File templateKeyFile(UUID siteId, SerialNum templateSerial)
{
return new File(new File(new File(this.template, siteId.toString()), templateSerial.getId().toString()), templateSerial.getId() + "." + templateSerial.getRev() + ".key");
}
@Override
public boolean hasTemplate(UUID siteId, UUID templateId)
{
return this.templateCrtFile(siteId, templateId).exists();
}
@Override
public CertificatePair loadTemplate(UUID siteId, UUID templateId)
{
try
{
if (this.templateKeyFile(siteId, templateId).exists())
{
return new CertificatePair(this.templateCrtFile(siteId, templateId), this.templateKeyFile(siteId, templateId));
}
else
{
return new CertificatePair(this.templateCrtFile(siteId, templateId));
}
}
catch (Exception e)
{
throw new RuntimeException("Failed to load certificate for template: " + siteId + "::" + templateId, e);
}
}
@Override
public void storeTemplate(UUID siteId, UUID templateId, CertificatePair pair)
{
synchronized (this)
{
try
{
SerialNum serial = SerialNum.fromBigInt(pair.getCertificate().getSerialNumber());
// store the certificate under the serial number
File crtFile = this.templateCrtFile(siteId, serial);
crtFile.getParentFile().mkdirs();
pair.saveCertificate(crtFile);
// link the certificate to the template
Path crtLink = this.templateCrtFile(siteId, templateId).toPath();
try
{
Files.delete(crtLink);
}
catch (NoSuchFileException e)
{
}
Files.createSymbolicLink(crtLink, crtFile.toPath());
// key?
if (pair.getKey() != null)
{
// store the key under the serial number
File keyFile = this.templateKeyFile(siteId, serial);
keyFile.getParentFile().mkdirs();
pair.saveKey(keyFile);
// link the key to the template
Path keyLink = this.templateKeyFile(siteId, templateId).toPath();
try
{
Files.delete(keyLink);
}
catch (NoSuchFileException e)
{
}
Files.createSymbolicLink(keyLink, keyFile.toPath());
}
}
catch (Exception e)
{
throw new RuntimeException("Failed to store certificate for template: " + siteId + "::" + templateId, e);
}
}
}
}