2 * Copyright (c) 2017 Cisco Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.netconf.sal.connect.netconf.sal;
10 import java.io.ByteArrayInputStream;
11 import java.io.IOException;
12 import java.security.GeneralSecurityException;
13 import java.security.KeyFactory;
14 import java.security.KeyStoreException;
15 import java.security.cert.Certificate;
16 import java.security.cert.CertificateException;
17 import java.security.cert.CertificateFactory;
18 import java.security.cert.X509Certificate;
19 import java.security.spec.InvalidKeySpecException;
20 import java.security.spec.PKCS8EncodedKeySpec;
21 import java.util.ArrayList;
22 import java.util.Base64;
23 import java.util.Collection;
24 import java.util.Collections;
25 import java.util.HashMap;
26 import java.util.List;
28 import java.util.Optional;
29 import javax.annotation.Nonnull;
30 import org.opendaylight.mdsal.binding.api.ClusteredDataTreeChangeListener;
31 import org.opendaylight.mdsal.binding.api.DataBroker;
32 import org.opendaylight.mdsal.binding.api.DataObjectModification;
33 import org.opendaylight.mdsal.binding.api.DataTreeIdentifier;
34 import org.opendaylight.mdsal.binding.api.DataTreeModification;
35 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.Keystore;
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017._private.keys.PrivateKey;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.keystore.entry.KeyCredential;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.trusted.certificates.TrustedCertificate;
40 import org.opendaylight.yangtools.yang.binding.DataObject;
41 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
45 public class NetconfKeystoreAdapter implements ClusteredDataTreeChangeListener<Keystore> {
47 private static final Logger LOG = LoggerFactory.getLogger(NetconfKeystoreAdapter.class);
49 private final InstanceIdentifier<Keystore> keystoreIid = InstanceIdentifier.create(Keystore.class);
51 private final DataBroker dataBroker;
52 private final Map<String, KeyCredential> pairs = Collections.synchronizedMap(new HashMap<>());
53 private final Map<String, PrivateKey> privateKeys = Collections.synchronizedMap(new HashMap<>());
54 private final Map<String, TrustedCertificate> trustedCertificates = Collections.synchronizedMap(new HashMap<>());
56 public NetconfKeystoreAdapter(final DataBroker dataBroker) {
57 this.dataBroker = dataBroker;
59 dataBroker.registerDataTreeChangeListener(DataTreeIdentifier.create(LogicalDatastoreType.CONFIGURATION,
63 public Optional<KeyCredential> getKeypairFromId(final String keyId) {
64 final KeyCredential keypair = pairs.get(keyId);
65 return Optional.ofNullable(keypair);
69 * Using private keys and trusted certificates to create a new JDK <code>KeyStore</code> which
70 * will be used by TLS clients to create <code>SSLEngine</code>. The private keys are essential
71 * to create JDK <code>KeyStore</code> while the trusted certificates are optional.
73 * @return A JDK KeyStore object
74 * @throws GeneralSecurityException If any security exception occurred
75 * @throws IOException If there is an I/O problem with the keystore data
77 public java.security.KeyStore getJavaKeyStore() throws GeneralSecurityException, IOException {
78 final java.security.KeyStore keyStore = java.security.KeyStore.getInstance("JKS");
80 keyStore.load(null, null);
82 synchronized (privateKeys) {
83 if (privateKeys.isEmpty()) {
84 throw new KeyStoreException("No keystore private key found");
87 for (Map.Entry<String, PrivateKey> entry : privateKeys.entrySet()) {
88 final java.security.PrivateKey key = getJavaPrivateKey(entry.getValue().getData());
90 final List<X509Certificate> certificateChain =
91 getCertificateChain(entry.getValue().getCertificateChain().toArray(new String[0]));
92 if (certificateChain.isEmpty()) {
93 throw new CertificateException("No certificate chain associated with private key found");
96 keyStore.setKeyEntry(entry.getKey(), key, "".toCharArray(),
97 certificateChain.stream().toArray(Certificate[]::new));
101 synchronized (trustedCertificates) {
102 for (Map.Entry<String, TrustedCertificate> entry : trustedCertificates.entrySet()) {
103 final List<X509Certificate> x509Certificates =
104 getCertificateChain(new String[] {entry.getValue().getCertificate()});
106 keyStore.setCertificateEntry(entry.getKey(), x509Certificates.get(0));
113 private static java.security.PrivateKey getJavaPrivateKey(final String base64PrivateKey)
114 throws GeneralSecurityException {
115 final byte[] encodedKey = base64Decode(base64PrivateKey);
116 final PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encodedKey);
117 java.security.PrivateKey key;
120 final KeyFactory keyFactory = KeyFactory.getInstance("RSA");
121 key = keyFactory.generatePrivate(keySpec);
122 } catch (InvalidKeySpecException ignore) {
123 final KeyFactory keyFactory = KeyFactory.getInstance("DSA");
124 key = keyFactory.generatePrivate(keySpec);
130 private static List<X509Certificate> getCertificateChain(final String[] base64Certificates)
131 throws GeneralSecurityException {
132 final CertificateFactory factory = CertificateFactory.getInstance("X.509");
133 final List<X509Certificate> certificates = new ArrayList<>();
135 for (String cert : base64Certificates) {
136 final byte[] buffer = base64Decode(cert);
137 certificates.add((X509Certificate)factory.generateCertificate(new ByteArrayInputStream(buffer)));
143 private static byte[] base64Decode(final String base64) {
144 return Base64.getMimeDecoder().decode(base64.getBytes(java.nio.charset.StandardCharsets.US_ASCII));
148 public void onDataTreeChanged(@Nonnull final Collection<DataTreeModification<Keystore>> changes) {
149 LOG.debug("Keystore updated: {}", changes);
151 for (final DataTreeModification<Keystore> change : changes) {
152 final DataObjectModification<Keystore> rootNode = change.getRootNode();
154 for (final DataObjectModification<? extends DataObject> changedChild : rootNode.getModifiedChildren()) {
155 if (changedChild.getDataType().equals(KeyCredential.class)) {
156 final Keystore dataAfter = rootNode.getDataAfter();
159 if (dataAfter != null) {
160 dataAfter.getKeyCredential().forEach(pair -> pairs.put(pair.key().getKeyId(), pair));
164 } else if (changedChild.getDataType().equals(PrivateKey.class)) {
165 onPrivateKeyChanged((DataObjectModification<PrivateKey>)changedChild);
166 } else if (changedChild.getDataType().equals(TrustedCertificate.class)) {
167 onTrustedCertificateChanged((DataObjectModification<TrustedCertificate>)changedChild);
174 private void onPrivateKeyChanged(final DataObjectModification<PrivateKey> objectModification) {
176 switch (objectModification.getModificationType()) {
177 case SUBTREE_MODIFIED:
179 final PrivateKey privateKey = objectModification.getDataAfter();
180 privateKeys.put(privateKey.getName(), privateKey);
183 privateKeys.remove(objectModification.getDataBefore().getName());
190 private void onTrustedCertificateChanged(final DataObjectModification<TrustedCertificate> objectModification) {
191 switch (objectModification.getModificationType()) {
192 case SUBTREE_MODIFIED:
194 final TrustedCertificate trustedCertificate = objectModification.getDataAfter();
195 trustedCertificates.put(trustedCertificate.getName(), trustedCertificate);
198 trustedCertificates.remove(objectModification.getDataBefore().getName());