Modify NetconfKeystoreAdapter to expose private keys and certificates
[netconf.git] / netconf / sal-netconf-connector / src / main / java / org / opendaylight / netconf / sal / connect / netconf / sal / NetconfKeystoreAdapter.java
1 /*
2  * Copyright (c) 2017 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8
9 package org.opendaylight.netconf.sal.connect.netconf.sal;
10
11 import java.io.ByteArrayInputStream;
12 import java.io.IOException;
13 import java.security.GeneralSecurityException;
14 import java.security.KeyFactory;
15 import java.security.KeyStoreException;
16 import java.security.cert.Certificate;
17 import java.security.cert.CertificateException;
18 import java.security.cert.CertificateFactory;
19 import java.security.cert.X509Certificate;
20 import java.security.spec.InvalidKeySpecException;
21 import java.security.spec.PKCS8EncodedKeySpec;
22 import java.util.ArrayList;
23 import java.util.Base64;
24 import java.util.Collection;
25 import java.util.Collections;
26 import java.util.HashMap;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Optional;
30 import javax.annotation.Nonnull;
31 import org.opendaylight.controller.md.sal.binding.api.ClusteredDataTreeChangeListener;
32 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
33 import org.opendaylight.controller.md.sal.binding.api.DataObjectModification;
34 import org.opendaylight.controller.md.sal.binding.api.DataTreeIdentifier;
35 import org.opendaylight.controller.md.sal.binding.api.DataTreeModification;
36 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.Keystore;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017._private.keys.PrivateKey;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.keystore.entry.KeyCredential;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.trusted.certificates.TrustedCertificate;
41 import org.opendaylight.yangtools.yang.binding.DataObject;
42 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
45
46 public class NetconfKeystoreAdapter implements ClusteredDataTreeChangeListener<Keystore> {
47
48     private static final Logger LOG = LoggerFactory.getLogger(NetconfKeystoreAdapter.class);
49
50     private final InstanceIdentifier<Keystore> keystoreIid = InstanceIdentifier.create(Keystore.class);
51
52     private final DataBroker dataBroker;
53     private final Map<String, KeyCredential> pairs = Collections.synchronizedMap(new HashMap<>());
54     private final Map<String, PrivateKey> privateKeys = Collections.synchronizedMap(new HashMap<>());
55     private final Map<String, TrustedCertificate> trustedCertificates = Collections.synchronizedMap(new HashMap<>());
56
57     public NetconfKeystoreAdapter(final DataBroker dataBroker) {
58         this.dataBroker = dataBroker;
59
60         dataBroker.registerDataTreeChangeListener(
61                 new DataTreeIdentifier<>(LogicalDatastoreType.CONFIGURATION, keystoreIid), this);
62     }
63
64     public Optional<KeyCredential> getKeypairFromId(final String keyId) {
65         final KeyCredential keypair = pairs.get(keyId);
66         return Optional.ofNullable(keypair);
67     }
68
69     /**
70      * Using private keys and trusted certificates to create a new JDK <code>KeyStore</code> which
71      * will be used by TLS clients to create <code>SSLEngine</code>. The private keys are essential
72      * to create JDK <code>KeyStore</code> while the trusted certificates are optional.
73      *
74      * @return A JDK KeyStore object
75      * @throws GeneralSecurityException If any security exception occurred
76      * @throws IOException If there is an I/O problem with the keystore data
77      */
78     public java.security.KeyStore getJavaKeyStore() throws GeneralSecurityException, IOException {
79         final java.security.KeyStore keyStore = java.security.KeyStore.getInstance("JKS");
80
81         keyStore.load(null, null);
82
83         synchronized (privateKeys) {
84             if (privateKeys.isEmpty()) {
85                 throw new KeyStoreException("No keystore private key found");
86             }
87
88             for (Map.Entry<String, PrivateKey> entry : privateKeys.entrySet()) {
89                 final java.security.PrivateKey key = getJavaPrivateKey(entry.getValue().getData());
90
91                 final List<X509Certificate> certificateChain =
92                         getCertificateChain(entry.getValue().getCertificateChain().toArray(new String[0]));
93                 if (certificateChain.isEmpty()) {
94                     throw new CertificateException("No certificate chain associated with private key found");
95                 }
96
97                 keyStore.setKeyEntry(entry.getKey(), key, "".toCharArray(),
98                         certificateChain.stream().toArray(Certificate[]::new));
99             }
100         }
101
102         synchronized (trustedCertificates) {
103             for (Map.Entry<String, TrustedCertificate> entry : trustedCertificates.entrySet()) {
104                 final List<X509Certificate> x509Certificates =
105                         getCertificateChain(new String[] {entry.getValue().getCertificate()});
106
107                 keyStore.setCertificateEntry(entry.getKey(), x509Certificates.get(0));
108             }
109         }
110
111         return keyStore;
112     }
113
114     private java.security.PrivateKey getJavaPrivateKey(final String base64PrivateKey)
115             throws GeneralSecurityException {
116         final byte[] encodedKey = base64Decode(base64PrivateKey);
117         final PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encodedKey);
118         java.security.PrivateKey key;
119
120         try {
121             final KeyFactory keyFactory = KeyFactory.getInstance("RSA");
122             key = keyFactory.generatePrivate(keySpec);
123         } catch (InvalidKeySpecException ignore) {
124             final KeyFactory keyFactory = KeyFactory.getInstance("DSA");
125             key = keyFactory.generatePrivate(keySpec);
126         }
127
128         return key;
129     }
130
131     private List<X509Certificate> getCertificateChain(final String[] base64Certificates)
132             throws GeneralSecurityException {
133         final CertificateFactory factory = CertificateFactory.getInstance("X.509");
134         final List<X509Certificate> certificates = new ArrayList<>();
135
136         for (String cert : base64Certificates) {
137             final byte[] buffer = base64Decode(cert);
138             certificates.add((X509Certificate)factory.generateCertificate(new ByteArrayInputStream(buffer)));
139         }
140
141         return certificates;
142     }
143
144     private byte[] base64Decode(final String base64) {
145         return Base64.getMimeDecoder().decode(base64.getBytes(java.nio.charset.StandardCharsets.US_ASCII));
146     }
147
148     @Override
149     public void onDataTreeChanged(@Nonnull final Collection<DataTreeModification<Keystore>> changes) {
150         LOG.debug("Keystore updated: {}", changes);
151
152         for (final DataTreeModification<Keystore> change : changes) {
153             final DataObjectModification<Keystore> rootNode = change.getRootNode();
154
155             for (final DataObjectModification<? extends DataObject> changedChild : rootNode.getModifiedChildren()) {
156                 if (changedChild.getDataType().equals(KeyCredential.class)) {
157                     final Keystore dataAfter = rootNode.getDataAfter();
158
159                     pairs.clear();
160                     if (dataAfter != null) {
161                         dataAfter.getKeyCredential().forEach(pair -> pairs.put(pair.getKey().getKeyId(), pair));
162                     }
163                     break;
164
165                 } else if (changedChild.getDataType().equals(PrivateKey.class)) {
166                     onPrivateKeyChanged((DataObjectModification<PrivateKey>)changedChild);
167                 } else if (changedChild.getDataType().equals(TrustedCertificate.class)) {
168                     onTrustedCertificateChanged((DataObjectModification<TrustedCertificate>)changedChild);
169                 }
170
171             }
172         }
173     }
174
175     private void onPrivateKeyChanged(final DataObjectModification<PrivateKey> objectModification) {
176
177         switch (objectModification.getModificationType()) {
178             case SUBTREE_MODIFIED:
179             case WRITE:
180                 final PrivateKey privateKey = objectModification.getDataAfter();
181                 privateKeys.put(privateKey.getName(), privateKey);
182                 break;
183             case DELETE:
184                 privateKeys.remove(objectModification.getDataBefore().getName());
185                 break;
186             default:
187                 break;
188         }
189     }
190
191     private void onTrustedCertificateChanged(final DataObjectModification<TrustedCertificate> objectModification) {
192         switch (objectModification.getModificationType()) {
193             case SUBTREE_MODIFIED:
194             case WRITE:
195                 final TrustedCertificate trustedCertificate = objectModification.getDataAfter();
196                 trustedCertificates.put(trustedCertificate.getName(), trustedCertificate);
197                 break;
198             case DELETE:
199                 trustedCertificates.remove(objectModification.getDataBefore().getName());
200                 break;
201             default:
202                 break;
203         }
204     }
205 }