/*******************************************************************************
* Copyright (c) 2014-2015 Sierra Wireless and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.html.
*
* Contributors:
* Sierra Wireless - initial API and implementation
*******************************************************************************/
package org.eclipse.leshan.server.bootstrap.demo;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Map;
import org.apache.commons.lang.ArrayUtils;
import org.eclipse.leshan.server.bootstrap.BootstrapConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Check a BootstrapConfig is correct. This is a complex process, we need to check if the different objects are in
* coherence with each other.
*/
public class ConfigurationChecker {
private static final Logger LOG = LoggerFactory.getLogger(ConfigurationChecker.class);
private static final String[] KEY_ALGORITHMS = new String[] { "EC", "DiffieHellman", "RSA", "DSA" };
public static void verify(BootstrapConfig config) throws ConfigurationException {
// check security configurations
for (Map.Entry<Integer, BootstrapConfig.ServerSecurity> e : config.security.entrySet()) {
BootstrapConfig.ServerSecurity sec = e.getValue();
switch (sec.securityMode) {
case NO_SEC:
assertIf(!ArrayUtils.isEmpty(sec.secretKey), "NO-SEC mode, secret key must be empty");
assertIf(!ArrayUtils.isEmpty(sec.publicKeyOrId), "NO-SEC mode, public key or ID must be empty");
assertIf(!ArrayUtils.isEmpty(sec.serverPublicKey), "NO-SEC mode, server public key must be empty");
break;
case PSK:
assertIf(ArrayUtils.isEmpty(sec.secretKey), "pre-shared-key mode, secret key must not be empty");
assertIf(ArrayUtils.isEmpty(sec.publicKeyOrId),
"pre-shared-key mode, public key or id must not be empty");
break;
case RPK:
assertIf(ArrayUtils.isEmpty(sec.secretKey), "raw-public-key mode, secret key must not be empty");
assertIf(decodeRfc5958PrivateKey(sec.secretKey) == null,
"raw-public-key mode, secret key must be RFC5958 encoded private key");
assertIf(ArrayUtils.isEmpty(sec.publicKeyOrId),
"raw-public-key mode, public key or id must not be empty");
assertIf(decodeRfc7250PublicKey(sec.publicKeyOrId) == null,
"raw-public-key mode, public key or id must be RFC7250 encoded public key");
assertIf(ArrayUtils.isEmpty(sec.serverPublicKey),
"raw-public-key mode, server public key must not be empty");
assertIf(decodeRfc7250PublicKey(sec.serverPublicKey) == null,
"raw-public-key mode, server public key must be RFC7250 encoded public key");
break;
case X509:
assertIf(ArrayUtils.isEmpty(sec.secretKey), "x509 mode, secret key must not be empty");
assertIf(decodeRfc5958PrivateKey(sec.secretKey) == null,
"x509 mode, secret key must be RFC5958 encoded private key");
assertIf(ArrayUtils.isEmpty(sec.publicKeyOrId), "x509 mode, public key or id must not be empty");
assertIf(decodeCertificate(sec.publicKeyOrId) == null,
"x509 mode, public key or id must be DER encoded X.509 certificate");
assertIf(ArrayUtils.isEmpty(sec.serverPublicKey),
"x509 mode, server public key must not be empty");
assertIf(decodeCertificate(sec.serverPublicKey) == null,
"x509 mode, server public key must be DER encoded X.509 certificate");
break;
}
}
// does each server have a corresponding security entry?
for (Map.Entry<Integer, BootstrapConfig.ServerConfig> e : config.servers.entrySet()) {
BootstrapConfig.ServerConfig srvCfg = e.getValue();
// shortId checks
if (srvCfg.shortId == 0) {
throw new ConfigurationException("short ID must not be 0");
}
// look for security entry
BootstrapConfig.ServerSecurity security = getSecurityEntry(config, srvCfg.shortId);
if (security == null) {
throw new ConfigurationException("no security entry for server instance: " + e.getKey());
}
if (security.bootstrapServer) {
throw new ConfigurationException("the security entry for server " + e.getKey()
+ " should not be a bootstrap server");
}
}
}
private static PrivateKey decodeRfc5958PrivateKey(byte[] encodedKey) {
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encodedKey);
for (String algorithm : KEY_ALGORITHMS) {
try {
KeyFactory kf = KeyFactory.getInstance(algorithm);
return kf.generatePrivate(keySpec);
} catch (NoSuchAlgorithmException e) {
LOG.debug("Failed to instantiate key factory for algorithm " + algorithm, e);
continue;
} catch (InvalidKeySpecException e) {
LOG.debug("Failed to decode RFC5958 private key with algorithm " + algorithm, e);
continue;
}
}
return null;
}
private static PublicKey decodeRfc7250PublicKey(byte[] encodedKey) {
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encodedKey);
for (String algorithm : KEY_ALGORITHMS) {
try {
KeyFactory kf = KeyFactory.getInstance(algorithm);
return kf.generatePublic(keySpec);
} catch (NoSuchAlgorithmException e) {
LOG.debug("Failed to instantiate key factory for algorithm " + algorithm, e);
continue;
} catch (InvalidKeySpecException e) {
LOG.debug("Failed to decode RFC7250 public key with algorithm " + algorithm, e);
continue;
}
}
return null;
}
private static Certificate decodeCertificate(byte[] encodedCert) {
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
try (ByteArrayInputStream in = new ByteArrayInputStream(encodedCert)) {
return cf.generateCertificate(in);
}
} catch (CertificateException | IOException e) {
LOG.debug("Failed to decode X.509 certificate", e);
return null;
}
}
private static void assertIf(boolean condition, String message) throws ConfigurationException {
if (condition) {
throw new ConfigurationException(message);
}
}
private static BootstrapConfig.ServerSecurity getSecurityEntry(BootstrapConfig config, int shortId) {
for (Map.Entry<Integer, BootstrapConfig.ServerSecurity> es : config.security.entrySet()) {
if (es.getValue().serverId == shortId) {
return es.getValue();
}
}
return null;
}
public static class ConfigurationException extends Exception {
private static final long serialVersionUID = 1L;
public ConfigurationException(String message) {
super(message);
}
}
}