Fix NetconfKeystoreAdapter onDataTreeChanged method
[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 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;
27 import java.util.Map;
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;
44
45 public class NetconfKeystoreAdapter implements ClusteredDataTreeChangeListener<Keystore> {
46
47     private static final Logger LOG = LoggerFactory.getLogger(NetconfKeystoreAdapter.class);
48
49     private final InstanceIdentifier<Keystore> keystoreIid = InstanceIdentifier.create(Keystore.class);
50
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<>());
55
56     public NetconfKeystoreAdapter(final DataBroker dataBroker) {
57         this.dataBroker = dataBroker;
58
59         dataBroker.registerDataTreeChangeListener(DataTreeIdentifier.create(LogicalDatastoreType.CONFIGURATION,
60             keystoreIid), this);
61     }
62
63     public Optional<KeyCredential> getKeypairFromId(final String keyId) {
64         final KeyCredential keypair = pairs.get(keyId);
65         return Optional.ofNullable(keypair);
66     }
67
68     /**
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.
72      *
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
76      */
77     public java.security.KeyStore getJavaKeyStore() throws GeneralSecurityException, IOException {
78         final java.security.KeyStore keyStore = java.security.KeyStore.getInstance("JKS");
79
80         keyStore.load(null, null);
81
82         synchronized (privateKeys) {
83             if (privateKeys.isEmpty()) {
84                 throw new KeyStoreException("No keystore private key found");
85             }
86
87             for (Map.Entry<String, PrivateKey> entry : privateKeys.entrySet()) {
88                 final java.security.PrivateKey key = getJavaPrivateKey(entry.getValue().getData());
89
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");
94                 }
95
96                 keyStore.setKeyEntry(entry.getKey(), key, "".toCharArray(),
97                         certificateChain.stream().toArray(Certificate[]::new));
98             }
99         }
100
101         synchronized (trustedCertificates) {
102             for (Map.Entry<String, TrustedCertificate> entry : trustedCertificates.entrySet()) {
103                 final List<X509Certificate> x509Certificates =
104                         getCertificateChain(new String[] {entry.getValue().getCertificate()});
105
106                 keyStore.setCertificateEntry(entry.getKey(), x509Certificates.get(0));
107             }
108         }
109
110         return keyStore;
111     }
112
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;
118
119         try {
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);
125         }
126
127         return key;
128     }
129
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<>();
134
135         for (String cert : base64Certificates) {
136             final byte[] buffer = base64Decode(cert);
137             certificates.add((X509Certificate)factory.generateCertificate(new ByteArrayInputStream(buffer)));
138         }
139
140         return certificates;
141     }
142
143     private static byte[] base64Decode(final String base64) {
144         return Base64.getMimeDecoder().decode(base64.getBytes(java.nio.charset.StandardCharsets.US_ASCII));
145     }
146
147     @Override
148     public void onDataTreeChanged(@Nonnull final Collection<DataTreeModification<Keystore>> changes) {
149         LOG.debug("Keystore updated: {}", changes);
150
151         for (final DataTreeModification<Keystore> change : changes) {
152             final DataObjectModification<Keystore> rootNode = change.getRootNode();
153
154             for (final DataObjectModification<? extends DataObject> changedChild : rootNode.getModifiedChildren()) {
155                 if (changedChild.getDataType().equals(KeyCredential.class)) {
156                     final Keystore dataAfter = rootNode.getDataAfter();
157
158                     pairs.clear();
159                     if (dataAfter != null) {
160                         dataAfter.getKeyCredential().forEach(pair -> pairs.put(pair.key().getKeyId(), pair));
161                     }
162
163                 } else if (changedChild.getDataType().equals(PrivateKey.class)) {
164                     onPrivateKeyChanged((DataObjectModification<PrivateKey>)changedChild);
165                 } else if (changedChild.getDataType().equals(TrustedCertificate.class)) {
166                     onTrustedCertificateChanged((DataObjectModification<TrustedCertificate>)changedChild);
167                 }
168
169             }
170         }
171     }
172
173     private void onPrivateKeyChanged(final DataObjectModification<PrivateKey> objectModification) {
174
175         switch (objectModification.getModificationType()) {
176             case SUBTREE_MODIFIED:
177             case WRITE:
178                 final PrivateKey privateKey = objectModification.getDataAfter();
179                 privateKeys.put(privateKey.getName(), privateKey);
180                 break;
181             case DELETE:
182                 privateKeys.remove(objectModification.getDataBefore().getName());
183                 break;
184             default:
185                 break;
186         }
187     }
188
189     private void onTrustedCertificateChanged(final DataObjectModification<TrustedCertificate> objectModification) {
190         switch (objectModification.getModificationType()) {
191             case SUBTREE_MODIFIED:
192             case WRITE:
193                 final TrustedCertificate trustedCertificate = objectModification.getDataAfter();
194                 trustedCertificates.put(trustedCertificate.getName(), trustedCertificate);
195                 break;
196             case DELETE:
197                 trustedCertificates.remove(objectModification.getDataBefore().getName());
198                 break;
199             default:
200                 break;
201         }
202     }
203 }