691a319ec2edaeaa866cdd2207f253d31404e174
[netconf.git] / plugins / netconf-client-mdsal / src / main / java / org / opendaylight / netconf / client / mdsal / impl / NetconfSalKeystoreRpcs.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.client.mdsal.impl;
9
10 import static java.util.Objects.requireNonNull;
11
12 import com.google.common.annotations.VisibleForTesting;
13 import com.google.common.collect.ImmutableClassToInstanceMap;
14 import com.google.common.util.concurrent.FutureCallback;
15 import com.google.common.util.concurrent.Futures;
16 import com.google.common.util.concurrent.ListenableFuture;
17 import com.google.common.util.concurrent.MoreExecutors;
18 import com.google.common.util.concurrent.SettableFuture;
19 import java.nio.charset.StandardCharsets;
20 import java.security.GeneralSecurityException;
21 import java.util.ArrayList;
22 import java.util.Base64;
23 import javax.annotation.PreDestroy;
24 import javax.inject.Inject;
25 import javax.inject.Singleton;
26 import org.opendaylight.aaa.encrypt.AAAEncryptionService;
27 import org.opendaylight.mdsal.binding.api.DataBroker;
28 import org.opendaylight.mdsal.binding.api.RpcProviderService;
29 import org.opendaylight.mdsal.binding.api.WriteTransaction;
30 import org.opendaylight.mdsal.common.api.CommitInfo;
31 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
32 import org.opendaylight.mdsal.dom.api.DefaultDOMRpcException;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.AddKeystoreEntry;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.AddKeystoreEntryInput;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.AddKeystoreEntryOutput;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.AddKeystoreEntryOutputBuilder;
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.AddPrivateKey;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.AddPrivateKeyInput;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.AddPrivateKeyOutput;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.AddPrivateKeyOutputBuilder;
41 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.AddTrustedCertificate;
42 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.AddTrustedCertificateInput;
43 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.AddTrustedCertificateOutput;
44 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.AddTrustedCertificateOutputBuilder;
45 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.Keystore;
46 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.RemoveKeystoreEntry;
47 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.RemoveKeystoreEntryInput;
48 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.RemoveKeystoreEntryOutput;
49 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.RemoveKeystoreEntryOutputBuilder;
50 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.RemovePrivateKey;
51 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.RemovePrivateKeyInput;
52 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.RemovePrivateKeyOutput;
53 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.RemovePrivateKeyOutputBuilder;
54 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.RemoveTrustedCertificate;
55 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.RemoveTrustedCertificateInput;
56 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.RemoveTrustedCertificateOutput;
57 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.RemoveTrustedCertificateOutputBuilder;
58 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017._private.keys.PrivateKey;
59 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017._private.keys.PrivateKeyKey;
60 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.keystore.entry.KeyCredential;
61 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.keystore.entry.KeyCredentialBuilder;
62 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.keystore.entry.KeyCredentialKey;
63 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.trusted.certificates.TrustedCertificate;
64 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.trusted.certificates.TrustedCertificateKey;
65 import org.opendaylight.yangtools.concepts.Registration;
66 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
67 import org.opendaylight.yangtools.yang.binding.Rpc;
68 import org.opendaylight.yangtools.yang.common.RpcResult;
69 import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
70 import org.osgi.service.component.annotations.Activate;
71 import org.osgi.service.component.annotations.Component;
72 import org.osgi.service.component.annotations.Deactivate;
73 import org.osgi.service.component.annotations.Reference;
74 import org.slf4j.Logger;
75 import org.slf4j.LoggerFactory;
76
77 @Singleton
78 @Component(service = { })
79 public final class NetconfSalKeystoreRpcs implements AutoCloseable {
80     private static final Logger LOG = LoggerFactory.getLogger(NetconfSalKeystoreRpcs.class);
81     private static final InstanceIdentifier<Keystore> KEYSTORE_IID = InstanceIdentifier.create(Keystore.class);
82
83     // FIXME: we are populating config datastore, but there may be risks with concurrent access. We really should be
84     //        using cluster singleton service here.
85     private final DataBroker dataBroker;
86     private final AAAEncryptionService encryptionService;
87     private final Registration reg;
88
89     @Inject
90     @Activate
91     public NetconfSalKeystoreRpcs(@Reference final DataBroker dataBroker,
92             @Reference final AAAEncryptionService encryptionService, @Reference final RpcProviderService rpcProvider) {
93         this.dataBroker = requireNonNull(dataBroker);
94         this.encryptionService = requireNonNull(encryptionService);
95
96         reg = rpcProvider.registerRpcImplementations(ImmutableClassToInstanceMap.<Rpc<?, ?>>builder()
97             .put(RemoveKeystoreEntry.class, this::removeKeystoreEntry)
98             .put(AddKeystoreEntry.class, this::addKeystoreEntry)
99             .put(AddTrustedCertificate.class, this::addTrustedCertificate)
100             .put(RemoveTrustedCertificate.class, this::removeTrustedCertificate)
101             .put(AddPrivateKey.class, this::addPrivateKey)
102             .put(RemovePrivateKey.class, this::removePrivateKey)
103             .build());
104         LOG.info("NETCONF keystore service started");
105     }
106
107     @PreDestroy
108     @Deactivate
109     @Override
110     public void close() {
111         reg.close();
112         LOG.info("NETCONF keystore service stopped");
113     }
114
115     private ListenableFuture<RpcResult<RemoveKeystoreEntryOutput>> removeKeystoreEntry(
116             final RemoveKeystoreEntryInput input) {
117         LOG.debug("Removing keypairs: {}", input);
118
119         final WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
120
121         for (final String id : input.getKeyId()) {
122             writeTransaction.delete(LogicalDatastoreType.CONFIGURATION,
123                 KEYSTORE_IID.child(KeyCredential.class, new KeyCredentialKey(id)));
124         }
125
126         final SettableFuture<RpcResult<RemoveKeystoreEntryOutput>> rpcResult = SettableFuture.create();
127
128         writeTransaction.commit().addCallback(new FutureCallback<CommitInfo>() {
129             @Override
130             public void onSuccess(final CommitInfo result) {
131                 LOG.debug("remove-key-pair success. Input: {}", input);
132                 rpcResult.set(RpcResultBuilder.success(new RemoveKeystoreEntryOutputBuilder().build()).build());
133             }
134
135             @Override
136             public void onFailure(final Throwable throwable) {
137                 LOG.warn("remove-key-pair failed. Input: {}", input, throwable);
138                 rpcResult.setException(throwable);
139             }
140         }, MoreExecutors.directExecutor());
141
142         return rpcResult;
143     }
144
145     private ListenableFuture<RpcResult<AddKeystoreEntryOutput>> addKeystoreEntry(final AddKeystoreEntryInput input) {
146         LOG.debug("Adding keypairs: {}", input);
147
148         final var plain = input.nonnullKeyCredential();
149         final var encrypted = new ArrayList<KeyCredential>(plain.size());
150         for (var credential : plain.values()) {
151             try {
152                 encrypted.add(new KeyCredentialBuilder(credential)
153                     .setPrivateKey(encryptString(credential.getPrivateKey()))
154                     .setPassphrase(encryptString(credential.getPassphrase()))
155                     .build());
156             } catch (GeneralSecurityException e) {
157                 return Futures.immediateFailedFuture(new DefaultDOMRpcException("Failed to decrypt " + credential, e));
158             }
159         }
160
161         final var writeTransaction = dataBroker.newWriteOnlyTransaction();
162         for (var keypair : encrypted) {
163             writeTransaction.merge(LogicalDatastoreType.CONFIGURATION,
164                 KEYSTORE_IID.child(KeyCredential.class, keypair.key()), keypair);
165         }
166
167         final var rpcResult = SettableFuture.<RpcResult<AddKeystoreEntryOutput>>create();
168         writeTransaction.commit().addCallback(new FutureCallback<CommitInfo>() {
169             @Override
170             public void onSuccess(final CommitInfo result) {
171                 LOG.debug("add-key-pair success. Input: {}", input);
172                 rpcResult.set(RpcResultBuilder.success(new AddKeystoreEntryOutputBuilder().build()).build());
173             }
174
175             @Override
176             public void onFailure(final Throwable throwable) {
177                 LOG.warn("add-key-pair failed. Input: {}", input, throwable);
178                 rpcResult.setException(throwable);
179             }
180         }, MoreExecutors.directExecutor());
181         return rpcResult;
182     }
183
184     private String encryptString(final String plain) throws GeneralSecurityException {
185         return Base64.getEncoder().encodeToString(encryptionService.encrypt(plain.getBytes(StandardCharsets.UTF_8)));
186     }
187
188     @VisibleForTesting
189     ListenableFuture<RpcResult<AddTrustedCertificateOutput>> addTrustedCertificate(
190             final AddTrustedCertificateInput input) {
191         final WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
192
193         for (TrustedCertificate certificate : input.nonnullTrustedCertificate().values()) {
194             writeTransaction.merge(LogicalDatastoreType.CONFIGURATION,
195                 KEYSTORE_IID.child(TrustedCertificate.class, certificate.key()), certificate);
196         }
197
198         final SettableFuture<RpcResult<AddTrustedCertificateOutput>> rpcResult = SettableFuture.create();
199
200         writeTransaction.commit().addCallback(new FutureCallback<CommitInfo>() {
201             @Override
202             public void onSuccess(final CommitInfo result) {
203                 LOG.debug("add-trusted-certificate success. Input: {}", input);
204                 rpcResult.set(RpcResultBuilder.success(new AddTrustedCertificateOutputBuilder().build()).build());
205             }
206
207             @Override
208             public void onFailure(final Throwable throwable) {
209                 LOG.warn("add-trusted-certificate failed. Input: {}", input, throwable);
210                 rpcResult.setException(throwable);
211             }
212         }, MoreExecutors.directExecutor());
213
214         return rpcResult;
215     }
216
217     private ListenableFuture<RpcResult<RemoveTrustedCertificateOutput>> removeTrustedCertificate(
218             final RemoveTrustedCertificateInput input) {
219         final WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
220
221         for (final String name : input.getName()) {
222             writeTransaction.delete(LogicalDatastoreType.CONFIGURATION,
223                 KEYSTORE_IID.child(TrustedCertificate.class, new TrustedCertificateKey(name)));
224         }
225
226         final SettableFuture<RpcResult<RemoveTrustedCertificateOutput>> rpcResult = SettableFuture.create();
227
228         writeTransaction.commit().addCallback(new FutureCallback<CommitInfo>() {
229             @Override
230             public void onSuccess(final CommitInfo result) {
231                 LOG.debug("remove-trusted-certificate success. Input: {}", input);
232                 rpcResult.set(RpcResultBuilder.success(new RemoveTrustedCertificateOutputBuilder().build()).build());
233             }
234
235             @Override
236             public void onFailure(final Throwable throwable) {
237                 LOG.warn("remove-trusted-certificate failed. Input: {}", input, throwable);
238                 rpcResult.setException(throwable);
239             }
240         }, MoreExecutors.directExecutor());
241
242         return rpcResult;
243     }
244
245     @VisibleForTesting
246     ListenableFuture<RpcResult<AddPrivateKeyOutput>> addPrivateKey(final AddPrivateKeyInput input) {
247         final WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
248
249         for (PrivateKey key: input.nonnullPrivateKey().values()) {
250             writeTransaction.merge(LogicalDatastoreType.CONFIGURATION,
251                 KEYSTORE_IID.child(PrivateKey.class, key.key()), key);
252         }
253
254         final SettableFuture<RpcResult<AddPrivateKeyOutput>> rpcResult = SettableFuture.create();
255
256         writeTransaction.commit().addCallback(new FutureCallback<CommitInfo>() {
257             @Override
258             public void onSuccess(final CommitInfo result) {
259                 LOG.debug("add-private-key success. Input: {}", input);
260                 rpcResult.set(RpcResultBuilder.success(new AddPrivateKeyOutputBuilder().build()).build());
261             }
262
263             @Override
264             public void onFailure(final Throwable throwable) {
265                 LOG.warn("add-private-key failed. Input: {}", input, throwable);
266                 rpcResult.setException(throwable);
267             }
268         }, MoreExecutors.directExecutor());
269
270         return rpcResult;
271     }
272
273     private ListenableFuture<RpcResult<RemovePrivateKeyOutput>> removePrivateKey(final RemovePrivateKeyInput input) {
274         final WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
275
276         for (final String name : input.getName()) {
277             writeTransaction.delete(LogicalDatastoreType.CONFIGURATION,
278                 KEYSTORE_IID.child(PrivateKey.class, new PrivateKeyKey(name)));
279         }
280
281         final SettableFuture<RpcResult<RemovePrivateKeyOutput>> rpcResult = SettableFuture.create();
282
283         writeTransaction.commit().addCallback(new FutureCallback<CommitInfo>() {
284             @Override
285             public void onSuccess(final CommitInfo result) {
286                 LOG.debug("remove-private-key success. Input: {}", input);
287                 rpcResult.set(RpcResultBuilder.success(new RemovePrivateKeyOutputBuilder().build()).build());
288             }
289
290             @Override
291             public void onFailure(final Throwable throwable) {
292                 LOG.warn("remove-private-key failed. Input: {}", input, throwable);
293                 rpcResult.setException(throwable);
294             }
295         }, MoreExecutors.directExecutor());
296
297         return rpcResult;
298     }
299 }