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