--- /dev/null
+/*
+ * Copyright (c) 2023 PANTHEON.tech, s.r.o. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.aaa.encrypt.impl;
+
+import com.google.common.base.Strings;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.MoreExecutors;
+import java.io.File;
+import java.io.IOException;
+import java.security.SecureRandom;
+import java.util.Base64;
+import java.util.concurrent.ExecutionException;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.mdsal.binding.api.DataBroker;
+import org.opendaylight.mdsal.common.api.CommitInfo;
+import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.yang.gen.v1.config.aaa.authn.encrypt.service.config.rev160915.AaaEncryptServiceConfig;
+import org.opendaylight.yang.gen.v1.config.aaa.authn.encrypt.service.config.rev160915.AaaEncryptServiceConfigBuilder;
+import org.opendaylight.yang.gen.v1.config.aaa.authn.encrypt.service.config.rev160915.EncryptServiceConfig;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.xml.sax.SAXException;
+
+/**
+ * Intermediate component dealing with establishing initial configuration for {@link AAAEncryptionServiceImpl}. In
+ * particular it deals with generating and persisting of encryption salt and encryption password.
+ */
+public final class AAAEncryptionServiceConfigurator implements EncryptServiceConfig {
+ private static final Logger LOG = LoggerFactory.getLogger(AAAEncryptionServiceConfigurator.class);
+
+ // Note: this is a strong binding to Blueprint, which is loading etc/opendaylight/datastore/initial/config
+ private static final String DEFAULT_CONFIG_FILE_PATH = "etc" + File.separator + "opendaylight" + File.separator
+ + "datastore" + File.separator + "initial" + File.separator + "config" + File.separator
+ + "aaa-encrypt-service-config.xml";
+ private static final SecureRandom RANDOM = new SecureRandom();
+
+ private final EncryptServiceConfig delegate;
+
+ public AAAEncryptionServiceConfigurator(final DataBroker dataBroker,
+ final AaaEncryptServiceConfig blueprintConfig) {
+ if (Strings.isNullOrEmpty(blueprintConfig.getEncryptSalt())
+ || Strings.isNullOrEmpty(blueprintConfig.getEncryptKey())) {
+ final var generatedConfig = generateConfig(blueprintConfig);
+
+ // Update initial configuration and config datastore
+ updateEncrySrvConfig(generatedConfig.requireEncryptKey(), generatedConfig.requireEncryptSalt());
+ updateDatastore(dataBroker, generatedConfig);
+
+ delegate = generatedConfig;
+ } else {
+ delegate = blueprintConfig;
+ }
+ }
+
+ private static @NonNull AaaEncryptServiceConfig generateConfig(final EncryptServiceConfig blueprintConfig) {
+ LOG.debug("Set the Encryption service password and encrypt salt");
+ final var salt = new byte[16];
+ RANDOM.nextBytes(salt);
+
+ return new AaaEncryptServiceConfigBuilder(blueprintConfig)
+ .setEncryptKey(RandomStringUtils.random(blueprintConfig.requirePasswordLength(), true, true))
+ .setEncryptSalt(Base64.getEncoder().encodeToString(salt))
+ .build();
+ }
+
+ // FIXME: Update configuration datastore, but only if not present?! this looks weird lifecycle-wise
+ private static void updateDatastore(final DataBroker dataBroker, final @NonNull AaaEncryptServiceConfig config) {
+ final var iid = InstanceIdentifier.create(AaaEncryptServiceConfig.class);
+
+ final var tx = dataBroker.newReadWriteTransaction();
+ final var existsFuture = tx.exists(LogicalDatastoreType.CONFIGURATION, iid);
+
+ final boolean exists;
+ try {
+ exists = existsFuture.get();
+ } catch (InterruptedException | ExecutionException e) {
+ tx.cancel();
+ LOG.error("Failed to read configuration", e);
+ return;
+ }
+ if (exists) {
+ tx.cancel();
+ LOG.info("Configuration already present, skipping update");
+ return;
+ }
+
+ LOG.debug("Populating configuration: {}, {}", iid, config);
+ tx.put(LogicalDatastoreType.CONFIGURATION, iid, config);
+ // Perform the transaction.submit asynchronously
+ Futures.addCallback(tx.commit(), new FutureCallback<CommitInfo>() {
+ @Override
+ public void onFailure(final Throwable throwable) {
+ LOG.error("initDatastore: configuration data populated: {}", iid);
+ }
+
+ @Override
+ public void onSuccess(final CommitInfo result) {
+ LOG.info("initDatastore: transaction succeeded");
+ }
+ }, MoreExecutors.directExecutor());
+ }
+
+ private static void updateEncrySrvConfig(final String newPwd, final String newSalt) {
+ LOG.debug("Update encryption service config file");
+ try {
+ final File configFile = new File(DEFAULT_CONFIG_FILE_PATH);
+ if (configFile.exists()) {
+ final Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(configFile);
+ final Node keyNode = doc.getElementsByTagName("encrypt-key").item(0);
+ keyNode.setTextContent(newPwd);
+ final Node salt = doc.getElementsByTagName("encrypt-salt").item(0);
+ salt.setTextContent(newSalt);
+ TransformerFactory.newInstance().newTransformer().transform(new DOMSource(doc),
+ new StreamResult(new File(DEFAULT_CONFIG_FILE_PATH)));
+ } else {
+ LOG.warn("The encryption service config file does not exist {}", DEFAULT_CONFIG_FILE_PATH);
+ }
+ } catch (ParserConfigurationException | TransformerException | SAXException | IOException e) {
+ LOG.error("Error while updating the encryption service config file", e);
+ }
+ }
+
+ @Override
+ public Class<? extends EncryptServiceConfig> implementedInterface() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getEncryptKey() {
+ return delegate.getEncryptKey();
+ }
+
+ @Override
+ public Integer getPasswordLength() {
+ return delegate.getPasswordLength();
+ }
+
+ @Override
+ public String getEncryptSalt() {
+ return delegate.getEncryptSalt();
+ }
+
+ @Override
+ public String getEncryptMethod() {
+ return delegate.getEncryptMethod();
+ }
+
+ @Override
+ public String getEncryptType() {
+ return delegate.getEncryptType();
+ }
+
+ @Override
+ public Integer getEncryptIterationCount() {
+ return delegate.getEncryptIterationCount();
+ }
+
+ @Override
+ public Integer getEncryptKeyLength() {
+ return delegate.getEncryptKeyLength();
+ }
+
+ @Override
+ public String getCipherTransforms() {
+ return delegate.getCipherTransforms();
+ }
+}
*/
package org.opendaylight.aaa.encrypt.impl;
-import java.io.File;
-import java.io.IOException;
import java.nio.charset.Charset;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.Base64;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.transform.TransformerException;
-import javax.xml.transform.TransformerFactory;
-import javax.xml.transform.dom.DOMSource;
-import javax.xml.transform.stream.StreamResult;
-import org.apache.commons.lang3.RandomStringUtils;
import org.opendaylight.aaa.encrypt.AAAEncryptionService;
-import org.opendaylight.mdsal.binding.api.DataBroker;
-import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
-import org.opendaylight.yang.gen.v1.config.aaa.authn.encrypt.service.config.rev160915.AaaEncryptServiceConfig;
-import org.opendaylight.yang.gen.v1.config.aaa.authn.encrypt.service.config.rev160915.AaaEncryptServiceConfigBuilder;
+import org.opendaylight.yang.gen.v1.config.aaa.authn.encrypt.service.config.rev160915.EncryptServiceConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.w3c.dom.Document;
-import org.w3c.dom.Node;
-import org.xml.sax.SAXException;
/**
* Provides a basic encryption service implementation with configuration knobs.
@Deprecated
public class AAAEncryptionServiceImpl implements AAAEncryptionService {
private static final Logger LOG = LoggerFactory.getLogger(AAAEncryptionServiceImpl.class);
- private static final String DEFAULT_CONFIG_FILE_PATH = "etc" + File.separator + "opendaylight" + File.separator
- + "datastore" + File.separator + "initial" + File.separator + "config" + File.separator
- + "aaa-encrypt-service-config.xml";
- private static final SecureRandom RANDOM = new SecureRandom();
private final SecretKey key;
private final Cipher encryptCipher;
private final Cipher decryptCipher;
- public AAAEncryptionServiceImpl(AaaEncryptServiceConfig encrySrvConfig, final DataBroker dataBroker) {
- if (encrySrvConfig.getEncryptSalt() == null) {
- throw new IllegalArgumentException(
- "null encryptSalt in AaaEncryptServiceConfig: " + encrySrvConfig.toString());
- }
- if (encrySrvConfig.getEncryptKey() != null && encrySrvConfig.getEncryptKey().isEmpty()) {
- LOG.debug("Set the Encryption service password and encrypt salt");
- String newPwd = RandomStringUtils.random(encrySrvConfig.getPasswordLength(), true, true);
- byte[] salt = new byte[16];
- RANDOM.nextBytes(salt);
- String encodedSalt = Base64.getEncoder().encodeToString(salt);
- encrySrvConfig = new AaaEncryptServiceConfigBuilder(encrySrvConfig).setEncryptKey(newPwd)
- .setEncryptSalt(encodedSalt).build();
- updateEncrySrvConfig(newPwd, encodedSalt);
- initializeConfigDataTree(encrySrvConfig, dataBroker);
- }
-
- final byte[] encryptionKeySalt = Base64.getDecoder().decode(encrySrvConfig.getEncryptSalt());
+ public AAAEncryptionServiceImpl(final EncryptServiceConfig encrySrvConfig) {
+ final byte[] encryptionKeySalt = Base64.getDecoder().decode(encrySrvConfig.requireEncryptSalt());
IvParameterSpec tempIvSpec = null;
SecretKey tempKey = null;
try {
final SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(encrySrvConfig.getEncryptMethod());
- final KeySpec spec = new PBEKeySpec(encrySrvConfig.getEncryptKey().toCharArray(), encryptionKeySalt,
+ final KeySpec spec = new PBEKeySpec(encrySrvConfig.requireEncryptKey().toCharArray(), encryptionKeySalt,
encrySrvConfig.getEncryptIterationCount(), encrySrvConfig.getEncryptKeyLength());
tempKey = new SecretKeySpec(keyFactory.generateSecret(spec).getEncoded(), encrySrvConfig.getEncryptType());
tempIvSpec = new IvParameterSpec(encryptionKeySalt);
}
return encryptedData;
}
-
- private static void updateEncrySrvConfig(final String newPwd, final String newSalt) {
- LOG.debug("Update encryption service config file");
- try {
- final File configFile = new File(DEFAULT_CONFIG_FILE_PATH);
- if (configFile.exists()) {
- final Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(configFile);
- final Node keyNode = doc.getElementsByTagName("encrypt-key").item(0);
- keyNode.setTextContent(newPwd);
- final Node salt = doc.getElementsByTagName("encrypt-salt").item(0);
- salt.setTextContent(newSalt);
- TransformerFactory.newInstance().newTransformer().transform(new DOMSource(doc),
- new StreamResult(new File(DEFAULT_CONFIG_FILE_PATH)));
- } else {
- LOG.warn("The encryption service config file does not exist {}", DEFAULT_CONFIG_FILE_PATH);
- }
- } catch (ParserConfigurationException | TransformerException | SAXException | IOException e) {
- LOG.error("Error while updating the encryption service config file", e);
- }
- }
-
- private static void initializeConfigDataTree(final AaaEncryptServiceConfig encrySrvConfig,
- final DataBroker dataBroker) {
- if (MdsalUtils.read(dataBroker, LogicalDatastoreType.CONFIGURATION,
- MdsalUtils.getEncryptionSrvConfigIid()) == null) {
- MdsalUtils.initalizeDatastore(LogicalDatastoreType.CONFIGURATION, dataBroker,
- MdsalUtils.getEncryptionSrvConfigIid(), encrySrvConfig);
- }
- }
}
+++ /dev/null
-/*
- * Copyright (c) 2017 Inocybe Technologies. and others. All rights reserved.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License v1.0 which accompanies this distribution,
- * and is available at http://www.eclipse.org/legal/epl-v10.html
- */
-package org.opendaylight.aaa.encrypt.impl;
-
-import com.google.common.util.concurrent.FutureCallback;
-import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.MoreExecutors;
-import java.util.Optional;
-import java.util.concurrent.ExecutionException;
-import org.opendaylight.mdsal.binding.api.DataBroker;
-import org.opendaylight.mdsal.binding.api.ReadTransaction;
-import org.opendaylight.mdsal.binding.api.WriteTransaction;
-import org.opendaylight.mdsal.common.api.CommitInfo;
-import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
-import org.opendaylight.yang.gen.v1.config.aaa.authn.encrypt.service.config.rev160915.AaaEncryptServiceConfig;
-import org.opendaylight.yangtools.yang.binding.DataObject;
-import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * MdsalUtils manages all the mdsal data operation.
- *
- * @author mserngawy
- */
-public final class MdsalUtils {
- private static final Logger LOG = LoggerFactory.getLogger(MdsalUtils.class);
-
- private MdsalUtils() {
-
- }
-
- public static InstanceIdentifier<AaaEncryptServiceConfig> getEncryptionSrvConfigIid() {
- return InstanceIdentifier.builder(AaaEncryptServiceConfig.class).build();
- }
-
- /**
- * initialize the data tree for the given InstanceIdentifier type.
- *
- * @param type data store type
- * @param dataBroker Mdsal data Broker
- * @param iid InstanceIdentifier type
- * @param object data object
- */
- public static <T extends DataObject> void initalizeDatastore(final LogicalDatastoreType type,
- final DataBroker dataBroker, final InstanceIdentifier<T> iid, final T object) {
- // Put data to MD-SAL data store
- final WriteTransaction transaction = dataBroker.newWriteOnlyTransaction();
- transaction.put(type, iid, object);
-
- // Perform the transaction.submit asynchronously
- Futures.addCallback(transaction.commit(), new FutureCallback<CommitInfo>() {
- @Override
- public void onFailure(final Throwable throwable) {
- LOG.error("initDatastore: transaction failed");
- }
-
- @Override
- public void onSuccess(final CommitInfo result) {
- LOG.debug("initDatastore: transaction succeeded");
- }
- }, MoreExecutors.directExecutor());
- LOG.info("initDatastore: data populated: {}, {}, {}", type, iid, object);
- }
-
- /**
- * Executes read as a blocking transaction.
- *
- * @param store {@link LogicalDatastoreType} to read
- * @param path {@link InstanceIdentifier} for path to read
- * @param <D> the data object type
- * @return the result as the data object requested
- */
- public static <D extends org.opendaylight.yangtools.yang.binding.DataObject> D read(
- final DataBroker dataBroker, final LogicalDatastoreType store, final InstanceIdentifier<D> path) {
- try (ReadTransaction transaction = dataBroker.newReadOnlyTransaction()) {
- Optional<D> optionalDataObject = transaction.read(store, path).get();
- if (optionalDataObject.isPresent()) {
- return optionalDataObject.get();
- }
- } catch (InterruptedException | ExecutionException e) {
- LOG.warn("Failed to read {} ", path, e);
- }
-
- return null;
- }
-}
<?xml version="1.0" encoding="UTF-8"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
- xmlns:odl="http://opendaylight.org/xmlns/blueprint/v1.0.0">
-
+ xmlns:odl="http://opendaylight.org/xmlns/blueprint/v1.0.0">
<reference id="dataBroker" interface="org.opendaylight.mdsal.binding.api.DataBroker"/>
<odl:clustered-app-config id="encryptConfig" default-config-file-name="aaa-encrypt-service-config.xml"
binding-class="org.opendaylight.yang.gen.v1.config.aaa.authn.encrypt.service.config.rev160915.AaaEncryptServiceConfig">
</odl:clustered-app-config>
- <bean id="encryptService" class="org.opendaylight.aaa.encrypt.impl.AAAEncryptionServiceImpl">
- <argument ref="encryptConfig"/>
+ <bean id="encryptServiceConfigurator" class="org.opendaylight.aaa.encrypt.impl.AAAEncryptionServiceConfigurator">
<argument ref="dataBroker"/>
+ <argument ref="encryptConfig"/>
</bean>
+ <bean id="encryptService" class="org.opendaylight.aaa.encrypt.impl.AAAEncryptionServiceImpl">
+ <argument ref="encryptServiceConfigurator"/>
+ </bean>
<service ref="encryptService" interface="org.opendaylight.aaa.encrypt.AAAEncryptionService"/>
</blueprint>
*/
package org.opendaylight.aaa.encrypt.impl;
-import static org.mockito.ArgumentMatchers.any;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import java.util.Optional;
-import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.opendaylight.mdsal.binding.api.DataBroker;
-import org.opendaylight.mdsal.binding.api.ReadTransaction;
+import org.opendaylight.mdsal.binding.api.ReadWriteTransaction;
import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
import org.opendaylight.yang.gen.v1.config.aaa.authn.encrypt.service.config.rev160915.AaaEncryptServiceConfig;
import org.opendaylight.yang.gen.v1.config.aaa.authn.encrypt.service.config.rev160915.AaaEncryptServiceConfigBuilder;
@Deprecated
@RunWith(MockitoJUnitRunner.StrictStubs.class)
public class AAAEncryptServiceImplTest {
-
- private AAAEncryptionServiceImpl impl;
@Mock
private DataBroker dataBroker;
+ @Mock
+ private ReadWriteTransaction tx;
+
+ private AAAEncryptionServiceImpl impl;
@Before
public void setup() {
- AaaEncryptServiceConfig module = new AaaEncryptServiceConfigBuilder()
+ final var module = new AaaEncryptServiceConfigBuilder()
.setCipherTransforms("AES/CBC/PKCS5Padding").setEncryptIterationCount(32768).setEncryptKey("")
.setEncryptKeyLength(128).setEncryptMethod("PBKDF2WithHmacSHA1").setEncryptSalt("")
.setEncryptType("AES").setPasswordLength(12).build();
- final ReadTransaction rtx = mock(ReadTransaction.class);
- doReturn(rtx).when(dataBroker).newReadOnlyTransaction();
- doReturn(FluentFutures.immediateFluentFuture(Optional.of(module))).when(rtx).read(
- any(LogicalDatastoreType.class), any(InstanceIdentifier.class));
+ doReturn(tx).when(dataBroker).newReadWriteTransaction();
+ doReturn(FluentFutures.immediateTrueFluentFuture()).when(tx)
+ .exists(LogicalDatastoreType.CONFIGURATION, InstanceIdentifier.create(AaaEncryptServiceConfig.class));
- impl = new AAAEncryptionServiceImpl(module, dataBroker);
+ impl = new AAAEncryptionServiceImpl(new AAAEncryptionServiceConfigurator(dataBroker, module));
}
@Test
public void testShortString() {
String before = "shortone";
String encrypt = impl.encrypt(before);
- Assert.assertNotEquals(before, encrypt);
+ assertNotEquals(before, encrypt);
String after = impl.decrypt(encrypt);
- Assert.assertEquals(before, after);
+ assertEquals(before, after);
}
@Test
public void testLongString() {
String before = "This is a very long string to encrypt for testing 1...2...3";
String encrypt = impl.encrypt(before);
- Assert.assertNotEquals(before, encrypt);
+ assertNotEquals(before, encrypt);
String after = impl.decrypt(encrypt);
- Assert.assertEquals(before, after);
+ assertEquals(before, after);
}
}