ProductKeyChain.java
/*
* ProductPublicKey
*/
package gov.usgs.earthquake.distribution;
import gov.usgs.earthquake.product.ProductId;
import gov.usgs.util.Config;
import gov.usgs.util.StreamUtils;
import gov.usgs.util.StringUtils;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.LinkedList;
import java.util.logging.Logger;
import org.yaml.snakeyaml.Yaml;
/**
* A group of keys that can be used to verify product signatures.
*/
public class ProductKeyChain {
/** Logging object. */
private static final Logger LOGGER = Logger.getLogger(ProductKeyChain.class.getName());
/** List of candidate keys. */
private List<ProductKey> keychain = new LinkedList<ProductKey>();
/** Empty constructor */
public ProductKeyChain() {
}
/**
* Constructor for a string of keys
*
* @param keys String of keys, separated by commas
* @param config Config file
* @throws Exception if error occurs
*/
public ProductKeyChain(final String keys, final Config config) throws Exception {
this(StringUtils.split(keys, ","), config);
}
/**
* Constructor for list of keys
*
* @param keys String list of keys
* @param config Config file
* @throws Exception if error occurs
*/
public ProductKeyChain(final List<String> keys, final Config config) throws Exception {
Iterator<String> iter = keys.iterator();
while (iter.hasNext()) {
String keyName = iter.next();
LOGGER.config("Loading key '" + keyName + "'");
ProductKey key = (ProductKey) config.getObject(keyName);
if (key != null) {
keychain.add(key);
}
}
}
/**
* Static constructor to create from a file
*
* @param keychainFile File to use
* @param propertyStart The property to look for the keychain from
* @throws Exception if error occurs
*/
public static ProductKeyChain fromFile(final File keychainFile, final String propertyStart) throws Exception {
String extension = Optional.ofNullable(keychainFile.getName())
.filter(f -> f.contains("."))
.map(f -> f.substring(keychainFile.getName().lastIndexOf(".") + 1))
.orElse("");
if (extension.equals("ini")) {
return fromIniFile(keychainFile, propertyStart);
} else if (extension.equals("yml") || extension.equals("yaml")) {
return fromYmlFile(keychainFile, propertyStart);
} else {
LOGGER.warning(() -> String.format("Unsupported file extension %s for keychain file", extension));
throw new IllegalArgumentException(String.format("Unsupported file extension %s for keychain file", extension));
}
}
/**
* Static constructor create from an ini file
*
* @param keychainFile File to use
* @param propertyStart The property to look for the keychain from
* @throws IOException if the file does not exist
* @throws Exception if error occurs
*/
protected static ProductKeyChain fromIniFile(final File keychainFile, final String propertyStart) throws Exception {
Config keychainConfig = new Config();
try (InputStream in = StreamUtils.getInputStream(keychainFile)) {
keychainConfig.load(in);
}
String keyNames = keychainConfig.getProperty(propertyStart);
return new ProductKeyChain(keyNames, keychainConfig);
}
/**
* Static constructor create from an yml file
*
* @param keychainFile File to use
* @param propertyStart The property to look for the keychain from
* @throws IOException if the file does not exist
* @throws Exception if error occurs
*/
@SuppressWarnings("unchecked")
protected static ProductKeyChain fromYmlFile(final File keychainFile, final String propertyStart) throws Exception {
Map<String, Object> yaml;
try (InputStream in = StreamUtils.getInputStream(keychainFile)) {
yaml = new Yaml().load(in);
}
List<String> keyNames = new ArrayList<>();
Config config = new Config();
Map<String, Object> keychain = (Map<String, Object>) yaml.get(propertyStart);
if (Objects.isNull(keychain)) {
throw new IllegalArgumentException(String.format("Keychain file does not contain %s property", propertyStart));
}
for (Map.Entry<String, Object> entry : keychain.entrySet()) {
Map<String, Object> keyObject = (Map<String, Object>) entry.getValue();
List<String> sources = keyObject.containsKey("sources") ? (List<String>) keyObject.get("sources") : List.of();
List<String> types = keyObject.containsKey("types") ? (List<String>) keyObject.get("types") : List.of();
List<String> keys = (List<String>) keyObject.get("keys");
if (Objects.isNull(keys)) {
throw new IllegalArgumentException("Keychain file does not contain keys property");
}
for (int i = 0; i < keys.size(); i++) {
String key = entry.getKey() + "_" + i;
config.setSectionProperty(key, ProductKey.KEY_PROPERTY_NAME, keys.get(i));
config.setSectionProperty(key, ProductKey.SOURCES_PROPERTY_NAME, String.join(",", sources));
config.setSectionProperty(key, ProductKey.TYPES_PROPERTY_NAME, String.join(",", types));
config.setSectionProperty(key, Config.OBJECT_TYPE_PROPERTY, ProductKey.class.getName());
keyNames.add(key);
}
}
return new ProductKeyChain(keyNames, config);
}
/**
* @return the keys
*/
public List<ProductKey> getKeychain() {
return keychain;
}
/**
* Find public keys based on configured Keys.
*
* @param id ID of product
* @return an array of candidate keys used to verify a signature.
*/
public PublicKey[] getProductKeys(final ProductId id) {
LinkedList<PublicKey> publicKeys = new LinkedList<PublicKey>();
Iterator<ProductKey> iter = keychain.iterator();
while (iter.hasNext()) {
ProductKey key = iter.next();
if (key.isForProduct(id)) {
publicKeys.add(key.getKey());
}
}
return publicKeys.toArray(new PublicKey[0]);
}
}