diff --git a/build.gradle b/build.gradle index abb819ae4f..5cb3b263b7 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ import java.time.Duration plugins { - id "org.owasp.dependencycheck" version "7.0.4.1" + id "org.owasp.dependencycheck" version "7.1.0.1" id 'jacoco' id 'com.diffplug.gradle.spotless' version '3.25.0' id "io.github.gradle-nexus.publish-plugin" version "1.1.0" @@ -248,6 +248,9 @@ subprojects { dependencyCheck { failBuildOnCVSS = 0 suppressionFile = project.getRootProject().file("cvss-suppressions.xml") + analyzers { + assemblyEnabled = false + } } jacoco { diff --git a/cli/cli-api/src/test/java/com/quorum/tessera/cli/parsers/ServerURIOutputMixinTest.java b/cli/cli-api/src/test/java/com/quorum/tessera/cli/parsers/ServerURIOutputMixinTest.java index 9868ac5f13..b3938c4cbd 100644 --- a/cli/cli-api/src/test/java/com/quorum/tessera/cli/parsers/ServerURIOutputMixinTest.java +++ b/cli/cli-api/src/test/java/com/quorum/tessera/cli/parsers/ServerURIOutputMixinTest.java @@ -32,4 +32,12 @@ public void testConfigPathStaysNullWithoutSpecifiedPath() { serverURIOutputMixin.updateConfig(null, config); assertThat(config.getOutputServerURIPath()).isNull(); } + + @Test + public void defaultDoNothing() { + final Config config = new Config(); + assertThat(config.getOutputServerURIPath()).isNull(); + serverURIOutputMixin.updateConfig(config); + assertThat(config.getOutputServerURIPath()).isNull(); + } } diff --git a/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/KeyGenCommand.java b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/KeyGenCommand.java index d8a1569bdd..290a5c546a 100644 --- a/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/KeyGenCommand.java +++ b/cli/config-cli/src/main/java/com/quorum/tessera/config/cli/KeyGenCommand.java @@ -3,9 +3,10 @@ import com.quorum.tessera.cli.CliException; import com.quorum.tessera.cli.CliResult; import com.quorum.tessera.config.*; -import com.quorum.tessera.config.keypairs.ConfigKeyPair; +import com.quorum.tessera.config.keypairs.*; import com.quorum.tessera.config.util.ConfigFileUpdaterWriter; import com.quorum.tessera.config.util.PasswordFileUpdaterWriter; +import com.quorum.tessera.key.generation.GeneratedKeyPair; import com.quorum.tessera.key.generation.KeyGenerator; import com.quorum.tessera.key.generation.KeyGeneratorFactory; import com.quorum.tessera.key.generation.KeyVaultOptions; @@ -18,6 +19,8 @@ import java.util.concurrent.Callable; import java.util.function.Predicate; import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import picocli.CommandLine; @CommandLine.Command( @@ -34,6 +37,8 @@ subcommands = {CommandLine.HelpCommand.class}) public class KeyGenCommand implements Callable { + private static final Logger LOGGER = LoggerFactory.getLogger(KeyGenCommand.class); + private final KeyGeneratorFactory keyGeneratorFactory; private final ConfigFileUpdaterWriter configFileUpdaterWriter; @@ -83,6 +88,79 @@ public class KeyGenCommand implements Callable { this.keyDataMarshaller = Objects.requireNonNull(keyDataMarshaller); } + static void output(List generatedKeyPairs) { + StringJoiner sj = new StringJoiner("\n"); + sj.add(String.format("%d keypair(s) generated:", generatedKeyPairs.size())); + + int i = 0; + for (GeneratedKeyPair kp : generatedKeyPairs) { + i++; + if (kp.getConfigKeyPair() instanceof AzureVaultKeyPair) { + AzureVaultKeyPair akp = (AzureVaultKeyPair) kp.getConfigKeyPair(); + String type = "azure"; + String pubKey = kp.getPublicKey(); + String pubId = akp.getPublicKeyId(); + String privId = akp.getPrivateKeyId(); + String pubVersion = akp.getPublicKeyVersion(); + String privVersion = akp.getPrivateKeyVersion(); + + sj.add(String.format("\t%d: type=%s, pub=%s", i, type, pubKey)); + sj.add(String.format("\t\tpub: id=%s, version=%s", pubId, pubVersion)); + sj.add(String.format("\t\tprv: id=%s, version=%s", privId, privVersion)); + } else if (kp.getConfigKeyPair() instanceof AWSKeyPair) { + AWSKeyPair akp = (AWSKeyPair) kp.getConfigKeyPair(); + String type = "aws"; + String pubKey = kp.getPublicKey(); + String pubId = akp.getPublicKeyId(); + String privId = akp.getPrivateKeyId(); + + sj.add(String.format("\t%d: type=%s, pub=%s", i, type, pubKey)); + sj.add(String.format("\t\tpub: id=%s", pubId)); + sj.add(String.format("\t\tprv: id=%s", privId)); + } else if (kp.getConfigKeyPair() instanceof HashicorpVaultKeyPair) { + HashicorpVaultKeyPair hkp = (HashicorpVaultKeyPair) kp.getConfigKeyPair(); + String type = "hashicorp"; + String pubKey = kp.getPublicKey(); + String name = hkp.getSecretName(); + String secretEngine = hkp.getSecretEngineName(); + String version = hkp.getSecretVersion().toString(); + String pubId = hkp.getPublicKeyId(); + String privId = hkp.getPrivateKeyId(); + + sj.add(String.format("\t%d: type=%s, pub=%s", i, type, pubKey)); + sj.add( + String.format( + "\t\tpub: name=%s/%s, id=%s, version=%s", secretEngine, name, pubId, version)); + sj.add( + String.format( + "\t\tprv: name=%s/%s, id=%s, version=%s", secretEngine, name, privId, version)); + } else if (kp.getConfigKeyPair() instanceof FilesystemKeyPair) { + FilesystemKeyPair fkp = (FilesystemKeyPair) kp.getConfigKeyPair(); + String type = "file"; + String pubPath = fkp.getPublicKeyPath().toAbsolutePath().toString(); + String privPath = fkp.getPrivateKeyPath().toAbsolutePath().toString(); + String pubKey = kp.getPublicKey(); + + sj.add(String.format("\t%d: type=%s, pub=%s", i, type, pubKey)); + sj.add(String.format("\t\tpub: path=%s", pubPath)); + sj.add(String.format("\t\tprv: path=%s", privPath)); + } else { + sj.add( + String.format("\t%d: type=unknown, pub=%s", i, kp.getConfigKeyPair().getPublicKey())); + } + } + System.out.println(sj); + } + + static void prepareConfigForNewKeys(Config config) { + if (Objects.isNull(config.getKeys())) { + config.setKeys(new KeyConfiguration()); + } + if (Objects.isNull(config.getKeys().getKeyData())) { + config.getKeys().setKeyData(new ArrayList<>()); + } + } + @Override public CliResult call() throws IOException { if (Objects.nonNull(fileUpdateOptions) && Objects.isNull(fileUpdateOptions.getConfig())) { @@ -118,12 +196,10 @@ public CliResult call() throws IOException { .flatMap(c -> c.getKeyVaultConfig(keyVaultConfigOptions.getVaultType())) .orElse(null); } else { - final KeyVaultHandler keyVaultHandler = new DispatchingKeyVaultHandler(); keyVaultConfig = keyVaultHandler.handle(keyVaultConfigOptions); if (keyVaultConfig.getKeyVaultType() == KeyVaultType.HASHICORP) { - if (Objects.isNull(keyOut)) { throw new CliException( "At least one -filename must be provided when saving generated keys in a Hashicorp Vault"); @@ -145,24 +221,30 @@ public CliResult call() throws IOException { .map(List::copyOf) .orElseGet(() -> List.of("")); - final List newConfigKeyPairs = + final List generatedKeyPairs = newKeyNames.stream() .map(name -> keyGenerator.generate(name, argonOptions, keyVaultOptions)) .collect(Collectors.toList()); + output(generatedKeyPairs); + final List newPasswords = - newConfigKeyPairs.stream() + generatedKeyPairs.stream() .filter(Objects::nonNull) + .map(GeneratedKeyPair::getConfigKeyPair) .map(ConfigKeyPair::getPassword) .collect(Collectors.toList()); - final List newKeyData = - newConfigKeyPairs.stream().map(keyDataMarshaller::marshal).collect(Collectors.toList()); - if (Objects.isNull(fileUpdateOptions)) { return new CliResult(0, true, null); } + final List newKeyData = + generatedKeyPairs.stream() + .map(GeneratedKeyPair::getConfigKeyPair) + .map(keyDataMarshaller::marshal) + .collect(Collectors.toList()); + // prepare config for addition of new keys if required prepareConfigForNewKeys(fileUpdateOptions.getConfig()); @@ -184,13 +266,4 @@ public CliResult call() throws IOException { return new CliResult(0, true, fileUpdateOptions.getConfig()); } - - static void prepareConfigForNewKeys(Config config) { - if (Objects.isNull(config.getKeys())) { - config.setKeys(new KeyConfiguration()); - } - if (Objects.isNull(config.getKeys().getKeyData())) { - config.getKeys().setKeyData(new ArrayList<>()); - } - } } diff --git a/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/KeyGenCommandTest.java b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/KeyGenCommandTest.java index e1cb0e3c94..08673b96d4 100644 --- a/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/KeyGenCommandTest.java +++ b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/KeyGenCommandTest.java @@ -7,18 +7,22 @@ import com.quorum.tessera.cli.CliException; import com.quorum.tessera.cli.CliResult; import com.quorum.tessera.config.*; -import com.quorum.tessera.config.keypairs.ConfigKeyPair; +import com.quorum.tessera.config.keypairs.*; import com.quorum.tessera.config.util.ConfigFileUpdaterWriter; import com.quorum.tessera.config.util.PasswordFileUpdaterWriter; +import com.quorum.tessera.key.generation.GeneratedKeyPair; import com.quorum.tessera.key.generation.KeyGenerator; import com.quorum.tessera.key.generation.KeyGeneratorFactory; import jakarta.validation.ConstraintViolationException; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; +import java.util.StringJoiner; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.contrib.java.lang.system.SystemOutRule; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; @@ -28,22 +32,17 @@ @RunWith(MockitoJUnitRunner.class) public class KeyGenCommandTest { - private KeyGeneratorFactory keyGeneratorFactory; + @Rule public SystemOutRule systemOutOutput = new SystemOutRule().enableLog(); + @Captor protected ArgumentCaptor parameterExceptionArgumentCaptor; + private KeyGeneratorFactory keyGeneratorFactory; private ConfigFileUpdaterWriter configFileUpdaterWriter; - private PasswordFileUpdaterWriter passwordFileUpdaterWriter; - private KeyDataMarshaller keyDataMarshaller; - private KeyGenCommand keyGenCommand; - private KeyGenerator keyGenerator; - private CliExecutionExceptionHandler executionExceptionHandler; private CommandLine.IParameterExceptionHandler parameterExceptionHandler; - @Captor protected ArgumentCaptor parameterExceptionArgumentCaptor; - private CommandLine commandLine; @Before @@ -81,9 +80,10 @@ public void afterTest() { @Test public void noArgsProvided() throws Exception { - + GeneratedKeyPair gkp = mock(GeneratedKeyPair.class); ConfigKeyPair configKeyPair = mock(ConfigKeyPair.class); - when(keyGenerator.generate("", null, null)).thenReturn(configKeyPair); + when(gkp.getConfigKeyPair()).thenReturn(configKeyPair); + when(keyGenerator.generate("", null, null)).thenReturn(gkp); when(keyGeneratorFactory.create(refEq(null), any(EncryptorConfig.class))) .thenReturn(keyGenerator); @@ -103,7 +103,6 @@ public void noArgsProvided() throws Exception { assertThat(result.getConfig()).isNotPresent(); assertThat(result.getStatus()).isEqualTo(0); - verify(keyDataMarshaller).marshal(configKeyPair); verify(keyGeneratorFactory).create(refEq(null), any(EncryptorConfig.class)); verify(keyGenerator).generate("", null, null); @@ -111,11 +110,12 @@ public void noArgsProvided() throws Exception { @Test public void updateNoOutputFileDefined() { - String filename = ""; + GeneratedKeyPair gkp = mock(GeneratedKeyPair.class); ConfigKeyPair configKeyPair = mock(ConfigKeyPair.class); - when(keyGenerator.generate(filename, null, null)).thenReturn(configKeyPair); + when(gkp.getConfigKeyPair()).thenReturn(configKeyPair); + when(keyGenerator.generate(filename, null, null)).thenReturn(gkp); when(keyGeneratorFactory.create(refEq(null), any(EncryptorConfig.class))) .thenReturn(keyGenerator); @@ -149,9 +149,11 @@ public void updateFileStuffWithOutputFile() throws Exception { String filename = ""; char[] password = "I LOVE SPARROWS".toCharArray(); + GeneratedKeyPair gkp = mock(GeneratedKeyPair.class); ConfigKeyPair configKeyPair = mock(ConfigKeyPair.class); + when(gkp.getConfigKeyPair()).thenReturn(configKeyPair); when(configKeyPair.getPassword()).thenReturn(password); - when(keyGenerator.generate(filename, null, null)).thenReturn(configKeyPair); + when(keyGenerator.generate(filename, null, null)).thenReturn(gkp); when(keyGeneratorFactory.create(refEq(null), any(EncryptorConfig.class))) .thenReturn(keyGenerator); @@ -189,8 +191,10 @@ public void onlySingleOutputFileProvided() throws Exception { List optionVariations = List.of("--keyout", "-filename"); + GeneratedKeyPair gkp = mock(GeneratedKeyPair.class); ConfigKeyPair configKeyPair = mock(ConfigKeyPair.class); - when(keyGenerator.generate("myfile", null, null)).thenReturn(configKeyPair); + when(gkp.getConfigKeyPair()).thenReturn(configKeyPair); + when(keyGenerator.generate("myfile", null, null)).thenReturn(gkp); when(keyGeneratorFactory.create(refEq(null), any(EncryptorConfig.class))) .thenReturn(keyGenerator); @@ -214,7 +218,6 @@ public void onlySingleOutputFileProvided() throws Exception { assertThat(result.getStatus()).isEqualTo(0); } - verify(keyDataMarshaller, times(optionVariations.size())).marshal(configKeyPair); verify(keyGeneratorFactory, times(optionVariations.size())) .create(refEq(null), any(EncryptorConfig.class)); @@ -226,11 +229,13 @@ public void onlyMulipleOutputFilesProvided() throws Exception { List optionVariations = List.of("--keyout", "-filename"); List valueVariations = List.of("myfile", "myotherfile", "yetanother"); + GeneratedKeyPair gkp = mock(GeneratedKeyPair.class); ConfigKeyPair configKeyPair = mock(ConfigKeyPair.class); + when(gkp.getConfigKeyPair()).thenReturn(configKeyPair); valueVariations.forEach( filename -> { - when(keyGenerator.generate(filename, null, null)).thenReturn(configKeyPair); + when(keyGenerator.generate(filename, null, null)).thenReturn(gkp); }); when(keyGeneratorFactory.create(refEq(null), any(EncryptorConfig.class))) @@ -254,8 +259,6 @@ public void onlyMulipleOutputFilesProvided() throws Exception { assertThat(result.getConfig()).isNotPresent(); } - verify(keyDataMarshaller, times(optionVariations.size() * valueVariations.size())) - .marshal(configKeyPair); verify(keyGeneratorFactory, times(optionVariations.size())) .create(refEq(null), any(EncryptorConfig.class)); @@ -312,8 +315,10 @@ public void nullVaultUrlProvidedOnCommandLine() { @Test public void vaultUrlProvidedOnCommandLine() { + GeneratedKeyPair gkp = mock(GeneratedKeyPair.class); ConfigKeyPair configKeyPair = mock(ConfigKeyPair.class); - when(keyGenerator.generate("", null, null)).thenReturn(configKeyPair); + when(gkp.getConfigKeyPair()).thenReturn(configKeyPair); + when(keyGenerator.generate("", null, null)).thenReturn(gkp); when(keyGeneratorFactory.create(any(AzureKeyVaultConfig.class), any(EncryptorConfig.class))) .thenReturn(keyGenerator); @@ -326,7 +331,6 @@ public void vaultUrlProvidedOnCommandLine() { assertThat(executionExceptionHandler.getExceptions()).isEmpty(); verify(keyGenerator).generate("", null, null); verify(keyGeneratorFactory).create(any(AzureKeyVaultConfig.class), any(EncryptorConfig.class)); - verify(keyDataMarshaller).marshal(configKeyPair); } @Test @@ -334,6 +338,11 @@ public void onlyConfigWithKeysProvided() throws Exception { // given when(keyGeneratorFactory.create(eq(null), any(EncryptorConfig.class))).thenReturn(keyGenerator); + GeneratedKeyPair gkp = mock(GeneratedKeyPair.class); + ConfigKeyPair configKeyPair = mock(ConfigKeyPair.class); + when(gkp.getConfigKeyPair()).thenReturn(configKeyPair); + when(keyGenerator.generate("", null, null)).thenReturn(gkp); + CommandLine commandLine = new CommandLine(keyGenCommand); Config config = mock(Config.class); @@ -345,18 +354,9 @@ public void onlyConfigWithKeysProvided() throws Exception { commandLine.registerConverter(Config.class, configConverter); - int exceptionExitCode = 999; - List exceptions = new ArrayList<>(); - commandLine.setExecutionExceptionHandler( - (ex, cmd, parseResult) -> { - exceptions.add(ex); - return exceptionExitCode; - }); - int exitCode = commandLine.execute("--configfile=myconfig.file"); assertThat(exitCode).isZero(); - assertThat(exceptions).isEmpty(); verify(configConverter).convert("myconfig.file"); CliResult result = commandLine.getExecutionResult(); @@ -369,7 +369,7 @@ public void onlyConfigWithKeysProvided() throws Exception { verify(configFileUpdaterWriter).updateAndWriteToCLI(anyList(), eq(null), any(Config.class)); - verify(keyDataMarshaller).marshal(null); + verify(keyDataMarshaller).marshal(configKeyPair); verify(keyGenerator).generate("", null, null); } @@ -418,7 +418,14 @@ public void hashicorpNoKeyOutDefinedRaisesCliExceptionEmptyList() throws Excepti @Test public void hashicorpKeyOutDefinedRaises() throws Exception { - when(keyGeneratorFactory.create(any(), any())).thenReturn(mock(KeyGenerator.class)); + when(keyGeneratorFactory.create(any(), any())).thenReturn(keyGenerator); + + String keyout = "key.out"; + + GeneratedKeyPair gkp = mock(GeneratedKeyPair.class); + ConfigKeyPair configKeyPair = mock(ConfigKeyPair.class); + when(gkp.getConfigKeyPair()).thenReturn(configKeyPair); + when(keyGenerator.generate(keyout, null, null)).thenReturn(gkp); Config config = mock(Config.class); KeyConfiguration keyConfiguration = mock(KeyConfiguration.class); @@ -428,12 +435,11 @@ public void hashicorpKeyOutDefinedRaises() throws Exception { CommandLine commandLine = new CommandLine(keyGenCommand); commandLine.setExecutionExceptionHandler(executionExceptionHandler); commandLine.registerConverter(Config.class, value -> config); - String keyout = "key.out"; int result = commandLine.execute( "--vault.type=HASHICORP", "--vault.url=someurl", - "--configfile=".concat(keyout), + "--configfile=".concat("myconfig.json"), "--keyout=".concat(keyout)); executionExceptionHandler.getExceptions().forEach(Throwable::printStackTrace); @@ -442,6 +448,7 @@ public void hashicorpKeyOutDefinedRaises() throws Exception { assertThat(result).isZero(); verify(keyGeneratorFactory).create(any(), any()); + verify(keyGenerator).generate(keyout, null, null); verify(configFileUpdaterWriter).updateAndWriteToCLI(anyList(), any(), refEq(config)); verify(keyDataMarshaller).marshal(any()); } @@ -454,6 +461,91 @@ public void prepareConfigForNewKeys() { assertThat(config.getKeys().getKeyData()).isEmpty(); } + @Test + public void output() { + FilesystemKeyPair file = mock(FilesystemKeyPair.class); + when(file.getPublicKeyPath()).thenReturn(Paths.get("/file.pub")); + when(file.getPrivateKeyPath()).thenReturn(Paths.get("/file.key")); + + HashicorpVaultKeyPair hashi = mock(HashicorpVaultKeyPair.class); + when(hashi.getSecretEngineName()).thenReturn("kv"); + when(hashi.getSecretName()).thenReturn("mySecret"); + when(hashi.getPublicKeyId()).thenReturn("publicKey"); + when(hashi.getPrivateKeyId()).thenReturn("privateKey"); + when(hashi.getSecretVersion()).thenReturn(1); + + AzureVaultKeyPair azure = mock(AzureVaultKeyPair.class); + when(azure.getPublicKeyId()).thenReturn("myPub"); + when(azure.getPrivateKeyId()).thenReturn("myPriv"); + when(azure.getPublicKeyVersion()).thenReturn("abc123"); + when(azure.getPrivateKeyVersion()).thenReturn("def456"); + + AWSKeyPair aws = mock(AWSKeyPair.class); + when(aws.getPublicKeyId()).thenReturn("myPub"); + when(aws.getPrivateKeyId()).thenReturn("myPriv"); + + // cover cases where a new key pair gets implemented and output code is not yet updated + UnknownKeyPair unknown = mock(UnknownKeyPair.class); + when(unknown.getPublicKey()).thenReturn("unknownPub"); + + List kps = + List.of( + new GeneratedKeyPair(file, "filePub"), + new GeneratedKeyPair(hashi, "hashiPub"), + new GeneratedKeyPair(azure, "azurePub"), + new GeneratedKeyPair(aws, "awsPub"), + new GeneratedKeyPair(unknown, "unknownPub")); + KeyGenCommand.output(kps); + + String got = systemOutOutput.getLog(); + + StringJoiner sj = new StringJoiner("\n"); + sj.add("5 keypair(s) generated:"); + + sj.add("\t1: type=file, pub=filePub"); + sj.add("\t\tpub: path=/file.pub"); + sj.add("\t\tprv: path=/file.key"); + + sj.add("\t2: type=hashicorp, pub=hashiPub"); + sj.add("\t\tpub: name=kv/mySecret, id=publicKey, version=1"); + sj.add("\t\tprv: name=kv/mySecret, id=privateKey, version=1"); + + sj.add("\t3: type=azure, pub=azurePub"); + sj.add("\t\tpub: id=myPub, version=abc123"); + sj.add("\t\tprv: id=myPriv, version=def456"); + + sj.add("\t4: type=aws, pub=awsPub"); + sj.add("\t\tpub: id=myPub"); + sj.add("\t\tprv: id=myPriv"); + + sj.add("\t5: type=unknown, pub=unknownPub"); + + String expected = sj.toString(); + + assertThat(got).contains(expected); + } + + static class UnknownKeyPair implements ConfigKeyPair { + + @Override + public String getPublicKey() { + return null; + } + + @Override + public String getPrivateKey() { + return null; + } + + @Override + public void withPassword(char[] password) {} + + @Override + public char[] getPassword() { + return new char[0]; + } + } + static class CliExecutionExceptionHandler implements CommandLine.IExecutionExceptionHandler { private List exceptions = new ArrayList<>(); diff --git a/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/PicoCliDelegateTest.java b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/PicoCliDelegateTest.java index f2c378742e..fdcc258707 100644 --- a/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/PicoCliDelegateTest.java +++ b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/PicoCliDelegateTest.java @@ -10,10 +10,11 @@ import com.quorum.tessera.config.Config; import com.quorum.tessera.config.KeyDataConfig; import com.quorum.tessera.config.Peer; -import com.quorum.tessera.config.keypairs.DirectKeyPair; +import com.quorum.tessera.config.keypairs.ConfigKeyPair; import com.quorum.tessera.config.keypairs.FilesystemKeyPair; import com.quorum.tessera.config.keys.KeyEncryptor; import com.quorum.tessera.config.util.JaxbUtil; +import com.quorum.tessera.key.generation.GeneratedKeyPair; import com.quorum.tessera.key.generation.KeyGenerator; import com.quorum.tessera.key.generation.KeyGeneratorFactory; import jakarta.validation.ConstraintViolationException; @@ -269,8 +270,12 @@ public void withConstraintViolations() throws Exception { @Test public void keygen() throws Exception { - FilesystemKeyPair keypair = mock(FilesystemKeyPair.class); - when(keyGenerator.generate(anyString(), eq(null), eq(null))).thenReturn(keypair); + ConfigKeyPair ckp = mock(ConfigKeyPair.class); + when(ckp.getPublicKey()).thenReturn("mypub"); + GeneratedKeyPair gkp = mock(GeneratedKeyPair.class); + when(gkp.getConfigKeyPair()).thenReturn(ckp); + + when(keyGenerator.generate(anyString(), eq(null), eq(null))).thenReturn(gkp); CliResult result = cliDelegate.execute("-keygen", "-filename", UUID.randomUUID().toString()); @@ -284,8 +289,12 @@ public void keygen() throws Exception { @Test public void keygenThenExit() throws Exception { - FilesystemKeyPair keypair = mock(FilesystemKeyPair.class); - when(keyGenerator.generate(anyString(), eq(null), eq(null))).thenReturn(keypair); + ConfigKeyPair ckp = mock(ConfigKeyPair.class); + when(ckp.getPublicKey()).thenReturn("mypub"); + GeneratedKeyPair gkp = mock(GeneratedKeyPair.class); + when(gkp.getConfigKeyPair()).thenReturn(ckp); + + when(keyGenerator.generate(anyString(), eq(null), eq(null))).thenReturn(gkp); final CliResult result = cliDelegate.execute("-keygen", "--encryptor.type", "NACL"); @@ -315,7 +324,8 @@ public void keygenUpdateConfig() throws Exception { KeyEncryptor keyEncryptor = mock(KeyEncryptor.class); FilesystemKeyPair keypair = new FilesystemKeyPair(publicKeyPath, privateKeyPath, keyEncryptor); - when(keyGenerator.generate(anyString(), eq(null), eq(null))).thenReturn(keypair); + GeneratedKeyPair gkp = new GeneratedKeyPair(keypair, "SOMEDATA"); + when(keyGenerator.generate(anyString(), eq(null), eq(null))).thenReturn(gkp); Path unixSocketPath = Files.createTempFile(UUID.randomUUID().toString(), ".ipc"); Map params = new HashMap<>(); @@ -376,7 +386,8 @@ public void keygenUpdateConfigAndPasswordFile() throws Exception { KeyEncryptor keyEncryptor = mock(KeyEncryptor.class); FilesystemKeyPair keypair = new FilesystemKeyPair(publicKeyPath, privateKeyPath, keyEncryptor); - when(keyGenerator.generate(anyString(), eq(null), eq(null))).thenReturn(keypair); + GeneratedKeyPair gkp = new GeneratedKeyPair(keypair, "SOMEDATA"); + when(keyGenerator.generate(anyString(), eq(null), eq(null))).thenReturn(gkp); Path unixSocketPath = Files.createTempFile(UUID.randomUUID().toString(), ".ipc"); Map params = new HashMap<>(); @@ -444,7 +455,8 @@ public void keygenOutputToCLI() throws Exception { KeyEncryptor keyEncryptor = mock(KeyEncryptor.class); FilesystemKeyPair keypair = new FilesystemKeyPair(publicKeyPath, privateKeyPath, keyEncryptor); - when(keyGenerator.generate(anyString(), eq(null), eq(null))).thenReturn(keypair); + GeneratedKeyPair gkp = new GeneratedKeyPair(keypair, "SOMEDATA"); + when(keyGenerator.generate(anyString(), eq(null), eq(null))).thenReturn(gkp); Path unixSocketPath = Files.createTempFile(UUID.randomUUID().toString(), ".ipc"); Map params = new HashMap<>(); @@ -726,8 +738,12 @@ public void updatingPasswordsDoesntProcessOtherOptions() throws Exception { @Test public void suppressStartupForKeygenOption() throws Exception { - when(keyGenerator.generate(anyString(), eq(null), eq(null))) - .thenReturn(mock(DirectKeyPair.class)); + ConfigKeyPair ckp = mock(ConfigKeyPair.class); + when(ckp.getPublicKey()).thenReturn("mypub"); + GeneratedKeyPair gkp = mock(GeneratedKeyPair.class); + when(gkp.getConfigKeyPair()).thenReturn(ckp); + + when(keyGenerator.generate(anyString(), eq(null), eq(null))).thenReturn(gkp); final CliResult cliResult = cliDelegate.execute("-keygen", "--encryptor.type", "NACL"); @@ -746,7 +762,8 @@ public void suppressStartupForKeygenOptionWithFileOutputOptions() throws Excepti Files.write(publicKeyPath, Arrays.asList("SOMEDATA")); FilesystemKeyPair keypair = new FilesystemKeyPair(publicKeyPath, privateKeyPath, null); - when(keyGenerator.generate(anyString(), eq(null), eq(null))).thenReturn(keypair); + GeneratedKeyPair gkp = new GeneratedKeyPair(keypair, "SOMEDATA"); + when(keyGenerator.generate(anyString(), eq(null), eq(null))).thenReturn(gkp); final Path configFile = Paths.get(getClass().getResource("/sample-config.json").toURI()); diff --git a/cvss-suppressions.xml b/cvss-suppressions.xml index 815a3edf6f..1b2804d976 100644 --- a/cvss-suppressions.xml +++ b/cvss-suppressions.xml @@ -1,20 +1,12 @@ - - - ^pkg:maven/net\.consensys\.quorum\.tessera/partyinfo\-model@.*$ - cpe:/a:model_project:model - ^pkg:maven/net\.consensys\.quorum\.tessera/partyinfo\-model@.*$ - CVE-2020-36460 + cpe:/a:model_project:model ^pkg:maven/com\.h2database/h2@.*$ CVE-2018-14335 + + + ^pkg:maven/org\.springframework/spring\-.*$ + CVE-2016-1000027 + diff --git a/key-generation/src/main/java/com/quorum/tessera/key/generation/AWSSecretManagerKeyGenerator.java b/key-generation/src/main/java/com/quorum/tessera/key/generation/AWSSecretManagerKeyGenerator.java index 1ee0645ec1..783922b02b 100644 --- a/key-generation/src/main/java/com/quorum/tessera/key/generation/AWSSecretManagerKeyGenerator.java +++ b/key-generation/src/main/java/com/quorum/tessera/key/generation/AWSSecretManagerKeyGenerator.java @@ -6,6 +6,7 @@ import com.quorum.tessera.encryption.Key; import com.quorum.tessera.encryption.KeyPair; import com.quorum.tessera.key.vault.KeyVaultService; +import com.quorum.tessera.key.vault.SetSecretResponse; import java.nio.charset.UnsupportedCharsetException; import java.nio.file.Path; import java.nio.file.Paths; @@ -27,7 +28,7 @@ public AWSSecretManagerKeyGenerator(Encryptor encryptor, KeyVaultService keyVaul } @Override - public AWSKeyPair generate( + public GeneratedKeyPair generate( String filename, ArgonOptions encryptionOptions, KeyVaultOptions keyVaultOptions) { final KeyPair keys = this.encryptor.generateNewKeys(); @@ -53,12 +54,15 @@ public AWSKeyPair generate( saveKeyInSecretManager(publicId.toString(), keys.getPublicKey()); saveKeyInSecretManager(privateId.toString(), keys.getPrivateKey()); - return new AWSKeyPair(publicId.toString(), privateId.toString()); + AWSKeyPair keyPair = new AWSKeyPair(publicId.toString(), privateId.toString()); + + return new GeneratedKeyPair(keyPair, keys.getPublicKey().encodeToBase64()); } - private void saveKeyInSecretManager(String id, Key key) { - keyVaultService.setSecret(Map.of("secretName", id, "secret", key.encodeToBase64())); + private SetSecretResponse saveKeyInSecretManager(String id, Key key) { + SetSecretResponse resp = + keyVaultService.setSecret(Map.of("secretName", id, "secret", key.encodeToBase64())); LOGGER.debug("Key {} saved to vault with id {}", key.encodeToBase64(), id); - LOGGER.info("Key saved to vault with id {}", id); + return resp; } } diff --git a/key-generation/src/main/java/com/quorum/tessera/key/generation/AzureVaultKeyGenerator.java b/key-generation/src/main/java/com/quorum/tessera/key/generation/AzureVaultKeyGenerator.java index 3214306371..4c2d42ec24 100644 --- a/key-generation/src/main/java/com/quorum/tessera/key/generation/AzureVaultKeyGenerator.java +++ b/key-generation/src/main/java/com/quorum/tessera/key/generation/AzureVaultKeyGenerator.java @@ -6,6 +6,7 @@ import com.quorum.tessera.encryption.Key; import com.quorum.tessera.encryption.KeyPair; import com.quorum.tessera.key.vault.KeyVaultService; +import com.quorum.tessera.key.vault.SetSecretResponse; import java.nio.charset.UnsupportedCharsetException; import java.nio.file.Path; import java.nio.file.Paths; @@ -27,7 +28,7 @@ public AzureVaultKeyGenerator(final Encryptor nacl, KeyVaultService keyVaultServ } @Override - public AzureVaultKeyPair generate( + public GeneratedKeyPair generate( String filename, ArgonOptions encryptionOptions, KeyVaultOptions keyVaultOptions) { final KeyPair keys = this.nacl.generateNewKeys(); @@ -50,15 +51,23 @@ public AzureVaultKeyPair generate( publicId.append("Pub"); privateId.append("Key"); - saveKeyInVault(publicId.toString(), keys.getPublicKey()); - saveKeyInVault(privateId.toString(), keys.getPrivateKey()); + SetSecretResponse pubResp = saveKeyInVault(publicId.toString(), keys.getPublicKey()); + SetSecretResponse privResp = saveKeyInVault(privateId.toString(), keys.getPrivateKey()); - return new AzureVaultKeyPair(publicId.toString(), privateId.toString(), null, null); + AzureVaultKeyPair keyPair = + new AzureVaultKeyPair( + publicId.toString(), + privateId.toString(), + pubResp.getProperty("version"), + privResp.getProperty("version")); + + return new GeneratedKeyPair(keyPair, keys.getPublicKey().encodeToBase64()); } - private void saveKeyInVault(String id, Key key) { - keyVaultService.setSecret(Map.of("secretName", id, "secret", key.encodeToBase64())); + private SetSecretResponse saveKeyInVault(String id, Key key) { + SetSecretResponse resp = + keyVaultService.setSecret(Map.of("secretName", id, "secret", key.encodeToBase64())); LOGGER.debug("Key {} saved to vault with id {}", key.encodeToBase64(), id); - LOGGER.info("Key saved to vault with id {}", id); + return resp; } } diff --git a/key-generation/src/main/java/com/quorum/tessera/key/generation/FileKeyGenerator.java b/key-generation/src/main/java/com/quorum/tessera/key/generation/FileKeyGenerator.java index 06d8fc8fe7..c036df6dd2 100644 --- a/key-generation/src/main/java/com/quorum/tessera/key/generation/FileKeyGenerator.java +++ b/key-generation/src/main/java/com/quorum/tessera/key/generation/FileKeyGenerator.java @@ -46,7 +46,7 @@ public FileKeyGenerator( } @Override - public FilesystemKeyPair generate( + public GeneratedKeyPair generate( final String filename, final ArgonOptions encryptionOptions, final KeyVaultOptions keyVaultOptions) { @@ -108,14 +108,14 @@ public FilesystemKeyPair generate( IOCallback.execute( () -> Files.write(privateKeyPath, privateKeyJson.getBytes(UTF_8), CREATE_NEW)); - LOGGER.info("Saved public key to {}", publicKeyPath.toAbsolutePath().toString()); - LOGGER.info("Saved private key to {}", privateKeyPath.toAbsolutePath().toString()); + LOGGER.debug("Saved public key to {}", publicKeyPath.toAbsolutePath()); + LOGGER.debug("Saved private key to {}", privateKeyPath.toAbsolutePath()); final FilesystemKeyPair keyPair = new FilesystemKeyPair(publicKeyPath, privateKeyPath, keyEncryptor); keyPair.withPassword(password); - return keyPair; + return new GeneratedKeyPair(keyPair, publicKeyBase64); } } diff --git a/key-generation/src/main/java/com/quorum/tessera/key/generation/GeneratedKeyPair.java b/key-generation/src/main/java/com/quorum/tessera/key/generation/GeneratedKeyPair.java new file mode 100644 index 0000000000..e719e3345b --- /dev/null +++ b/key-generation/src/main/java/com/quorum/tessera/key/generation/GeneratedKeyPair.java @@ -0,0 +1,23 @@ +package com.quorum.tessera.key.generation; + +import com.quorum.tessera.config.keypairs.*; + +public class GeneratedKeyPair { + // key pair data that can be marshalled and used to update the configfile + private ConfigKeyPair configKeyPair; + + private String publicKey; + + public GeneratedKeyPair(ConfigKeyPair configKeyPair, String publicKey) { + this.configKeyPair = configKeyPair; + this.publicKey = publicKey; + } + + public ConfigKeyPair getConfigKeyPair() { + return configKeyPair; + } + + public String getPublicKey() { + return publicKey; + } +} diff --git a/key-generation/src/main/java/com/quorum/tessera/key/generation/HashicorpVaultKeyGenerator.java b/key-generation/src/main/java/com/quorum/tessera/key/generation/HashicorpVaultKeyGenerator.java index 63d28efab4..56ad2d46f4 100644 --- a/key-generation/src/main/java/com/quorum/tessera/key/generation/HashicorpVaultKeyGenerator.java +++ b/key-generation/src/main/java/com/quorum/tessera/key/generation/HashicorpVaultKeyGenerator.java @@ -5,9 +5,11 @@ import com.quorum.tessera.encryption.Encryptor; import com.quorum.tessera.encryption.KeyPair; import com.quorum.tessera.key.vault.KeyVaultService; +import com.quorum.tessera.key.vault.SetSecretResponse; import java.util.HashMap; import java.util.Map; import java.util.Objects; +import java.util.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -25,7 +27,7 @@ public HashicorpVaultKeyGenerator(final Encryptor encryptor, KeyVaultService key } @Override - public HashicorpVaultKeyPair generate( + public GeneratedKeyPair generate( String filename, ArgonOptions encryptionOptions, KeyVaultOptions keyVaultOptions) { Objects.requireNonNull(filename); Objects.requireNonNull( @@ -46,21 +48,29 @@ public HashicorpVaultKeyPair generate( setSecretData.put("secretName", filename); setSecretData.put("secretEngineName", keyVaultOptions.getSecretEngineName()); - keyVaultService.setSecret(setSecretData); + SetSecretResponse resp = keyVaultService.setSecret(setSecretData); + Integer version = + Optional.ofNullable(resp) + .map(r -> r.getProperty("version")) + .map(Integer::valueOf) + .orElse(0); - LOGGER.info( + LOGGER.debug( "Key saved to vault secret engine {} with name {} and id {}", keyVaultOptions.getSecretEngineName(), filename, pubId); - LOGGER.info( + LOGGER.debug( "Key saved to vault secret engine {} with name {} and id {}", keyVaultOptions.getSecretEngineName(), filename, privId); - return new HashicorpVaultKeyPair( - pubId, privId, keyVaultOptions.getSecretEngineName(), filename, null); + HashicorpVaultKeyPair keyPair = + new HashicorpVaultKeyPair( + pubId, privId, keyVaultOptions.getSecretEngineName(), filename, version); + + return new GeneratedKeyPair(keyPair, keys.getPublicKey().encodeToBase64()); } } diff --git a/key-generation/src/main/java/com/quorum/tessera/key/generation/KeyGenerator.java b/key-generation/src/main/java/com/quorum/tessera/key/generation/KeyGenerator.java index 1a9f10d207..c88fb2912c 100644 --- a/key-generation/src/main/java/com/quorum/tessera/key/generation/KeyGenerator.java +++ b/key-generation/src/main/java/com/quorum/tessera/key/generation/KeyGenerator.java @@ -1,10 +1,9 @@ package com.quorum.tessera.key.generation; import com.quorum.tessera.config.ArgonOptions; -import com.quorum.tessera.config.keypairs.ConfigKeyPair; public interface KeyGenerator { - ConfigKeyPair generate( + GeneratedKeyPair generate( String filename, ArgonOptions encryptionOptions, KeyVaultOptions keyVaultOptions); } diff --git a/key-generation/src/test/java/com/quorum/tessera/key/generation/AWSSecretManagerKeyGeneratorTest.java b/key-generation/src/test/java/com/quorum/tessera/key/generation/AWSSecretManagerKeyGeneratorTest.java index 67d15dd760..74d9b1f4df 100644 --- a/key-generation/src/test/java/com/quorum/tessera/key/generation/AWSSecretManagerKeyGeneratorTest.java +++ b/key-generation/src/test/java/com/quorum/tessera/key/generation/AWSSecretManagerKeyGeneratorTest.java @@ -46,7 +46,7 @@ public void keysSavedInVaultWithProvidedVaultIdAndCorrectSuffix() { final String pubVaultId = vaultId + "Pub"; final String privVaultId = vaultId + "Key"; - final AWSKeyPair result = awsSecretManagerKeyGenerator.generate(vaultId, null, null); + final GeneratedKeyPair result = awsSecretManagerKeyGenerator.generate(vaultId, null, null); final ArgumentCaptor captor = ArgumentCaptor.forClass(Map.class); @@ -66,10 +66,11 @@ public void keysSavedInVaultWithProvidedVaultIdAndCorrectSuffix() { verifyNoMoreInteractions(keyVaultService); - final AWSKeyPair expected = new AWSKeyPair(pubVaultId, privVaultId); + final AWSKeyPair kp = new AWSKeyPair(pubVaultId, privVaultId); + final GeneratedKeyPair expected = new GeneratedKeyPair(kp, pub.encodeToBase64()); - assertThat(result).isExactlyInstanceOf(AWSKeyPair.class); - assertThat(result).isEqualToComparingFieldByField(expected); + assertThat(result).isExactlyInstanceOf(GeneratedKeyPair.class); + assertThat(result).usingRecursiveComparison().isEqualTo(expected); } @Test diff --git a/key-generation/src/test/java/com/quorum/tessera/key/generation/AzureVaultKeyGeneratorTest.java b/key-generation/src/test/java/com/quorum/tessera/key/generation/AzureVaultKeyGeneratorTest.java index a41e91288c..13fa941454 100644 --- a/key-generation/src/test/java/com/quorum/tessera/key/generation/AzureVaultKeyGeneratorTest.java +++ b/key-generation/src/test/java/com/quorum/tessera/key/generation/AzureVaultKeyGeneratorTest.java @@ -11,6 +11,7 @@ import com.quorum.tessera.encryption.PrivateKey; import com.quorum.tessera.encryption.PublicKey; import com.quorum.tessera.key.vault.KeyVaultService; +import com.quorum.tessera.key.vault.SetSecretResponse; import java.nio.charset.UnsupportedCharsetException; import java.util.List; import java.util.Map; @@ -24,6 +25,8 @@ public class AzureVaultKeyGeneratorTest { private final String privStr = "private"; private final PublicKey pub = PublicKey.from(pubStr.getBytes()); private final PrivateKey priv = PrivateKey.from(privStr.getBytes()); + private final String pubVersion = "pubVersion"; + private final String privVersion = "privVersion"; private Encryptor encryptor; private KeyVaultService keyVaultService; @@ -35,9 +38,12 @@ public void setUp() { this.keyVaultService = mock(KeyVaultService.class); final KeyPair keyPair = new KeyPair(pub, priv); - when(encryptor.generateNewKeys()).thenReturn(keyPair); + final SetSecretResponse setRespPub = new SetSecretResponse(Map.of("version", pubVersion)); + final SetSecretResponse setRespPriv = new SetSecretResponse(Map.of("version", privVersion)); + when(keyVaultService.setSecret(anyMap())).thenReturn(setRespPub, setRespPriv); + azureVaultKeyGenerator = new AzureVaultKeyGenerator(encryptor, keyVaultService); } @@ -47,7 +53,7 @@ public void keysSavedInVaultWithProvidedVaultIdAndCorrectSuffix() { final String pubVaultId = vaultId + "Pub"; final String privVaultId = vaultId + "Key"; - final AzureVaultKeyPair result = azureVaultKeyGenerator.generate(vaultId, null, null); + final GeneratedKeyPair result = azureVaultKeyGenerator.generate(vaultId, null, null); final ArgumentCaptor captor = ArgumentCaptor.forClass(Map.class); @@ -68,9 +74,11 @@ public void keysSavedInVaultWithProvidedVaultIdAndCorrectSuffix() { verifyNoMoreInteractions(keyVaultService); - final AzureVaultKeyPair expected = new AzureVaultKeyPair(pubVaultId, privVaultId, null, null); + final AzureVaultKeyPair kp = + new AzureVaultKeyPair(pubVaultId, privVaultId, pubVersion, privVersion); + final GeneratedKeyPair expected = new GeneratedKeyPair(kp, pub.encodeToBase64()); - assertThat(result).isEqualToComparingFieldByFieldRecursively(expected); + assertThat(result).usingRecursiveComparison().isEqualTo(expected); } @Test diff --git a/key-generation/src/test/java/com/quorum/tessera/key/generation/FileKeyGeneratorTest.java b/key-generation/src/test/java/com/quorum/tessera/key/generation/FileKeyGeneratorTest.java index d3bad593d4..b7e674a20c 100644 --- a/key-generation/src/test/java/com/quorum/tessera/key/generation/FileKeyGeneratorTest.java +++ b/key-generation/src/test/java/com/quorum/tessera/key/generation/FileKeyGeneratorTest.java @@ -67,18 +67,20 @@ public void onTearDown() { @Test public void generateFromKeyDataUnlockedPrivateKey() throws IOException { - doReturn(keyPair).when(encryptor).generateNewKeys(); String filename = UUID.randomUUID().toString(); final Path tmpDir = Files.createTempDirectory("keygen").toAbsolutePath().resolve(filename); - final FilesystemKeyPair generated = generator.generate(tmpDir.toString(), null, null); + final GeneratedKeyPair generated = generator.generate(tmpDir.toString(), null, null); - assertThat(generated).isInstanceOf(FilesystemKeyPair.class); assertThat(generated.getPublicKey()).isEqualTo("cHVibGljS2V5"); - assertThat(generated.getPrivateKey()).isEqualTo("cHJpdmF0ZUtleQ=="); - assertThat(generated.getInlineKeypair().getPrivateKeyConfig().getType()).isEqualTo(UNLOCKED); + assertThat(generated.getConfigKeyPair()).isInstanceOf(FilesystemKeyPair.class); + assertThat(generated.getConfigKeyPair().getPublicKey()).isEqualTo("cHVibGljS2V5"); + assertThat(generated.getConfigKeyPair().getPrivateKey()).isEqualTo("cHJpdmF0ZUtleQ=="); + + FilesystemKeyPair fkp = (FilesystemKeyPair) generated.getConfigKeyPair(); + assertThat(fkp.getInlineKeypair().getPrivateKeyConfig().getType()).isEqualTo(UNLOCKED); verify(encryptor).generateNewKeys(); } @@ -109,10 +111,14 @@ public void generateFromKeyDataLockedPrivateKey() throws IOException { .when(keyEncryptor) .encryptPrivateKey(any(PrivateKey.class), any(), eq(null)); - final FilesystemKeyPair generated = generator.generate(keyFilesName, null, null); + final GeneratedKeyPair generated = generator.generate(keyFilesName, null, null); - final KeyDataConfig pkd = generated.getInlineKeypair().getPrivateKeyConfig(); assertThat(generated.getPublicKey()).isEqualTo("cHVibGljS2V5"); + assertThat(generated.getConfigKeyPair()).isInstanceOf(FilesystemKeyPair.class); + assertThat(generated.getConfigKeyPair().getPublicKey()).isEqualTo("cHVibGljS2V5"); + + final FilesystemKeyPair fkp = (FilesystemKeyPair) generated.getConfigKeyPair(); + final KeyDataConfig pkd = fkp.getInlineKeypair().getPrivateKeyConfig(); assertThat(pkd.getSbox()).isEqualTo("sbox"); assertThat(pkd.getSnonce()).isEqualTo("snonce"); assertThat(pkd.getAsalt()).isEqualTo("salt"); @@ -129,7 +135,7 @@ public void providingPathSavesToFile() throws IOException { doReturn(keyPair).when(encryptor).generateNewKeys(); - final FilesystemKeyPair generated = generator.generate(keyFilesName, null, null); + final GeneratedKeyPair generated = generator.generate(keyFilesName, null, null); assertThat(Files.exists(tempFolder.resolve("providingPathSavesToFile.pub"))).isTrue(); assertThat(Files.exists(tempFolder.resolve("providingPathSavesToFile.key"))).isTrue(); @@ -144,7 +150,7 @@ public void providingNoPathSavesToFileInSameDirectory() throws IOException { doReturn(keyPair).when(encryptor).generateNewKeys(); - final FilesystemKeyPair generated = generator.generate("", null, null); + final GeneratedKeyPair generated = generator.generate("", null, null); assertThat(Files.exists(Paths.get(".pub"))).isTrue(); assertThat(Files.exists(Paths.get(".key"))).isTrue(); diff --git a/key-generation/src/test/java/com/quorum/tessera/key/generation/HashicorpVaultKeyGeneratorTest.java b/key-generation/src/test/java/com/quorum/tessera/key/generation/HashicorpVaultKeyGeneratorTest.java index 4d9fa2cd01..19ff872cf0 100644 --- a/key-generation/src/test/java/com/quorum/tessera/key/generation/HashicorpVaultKeyGeneratorTest.java +++ b/key-generation/src/test/java/com/quorum/tessera/key/generation/HashicorpVaultKeyGeneratorTest.java @@ -9,6 +9,7 @@ import com.quorum.tessera.encryption.PrivateKey; import com.quorum.tessera.encryption.PublicKey; import com.quorum.tessera.key.vault.KeyVaultService; +import com.quorum.tessera.key.vault.SetSecretResponse; import java.util.HashMap; import java.util.Map; import org.junit.Before; @@ -65,15 +66,18 @@ public void generatedKeyPairIsSavedToSpecifiedPathInVaultWithIds() { String secretEngine = "secretEngine"; String filename = "secretName"; + final SetSecretResponse setResp = new SetSecretResponse(Map.of("version", "1")); + when(keyVaultService.setSecret(anyMap())).thenReturn(setResp); + KeyVaultOptions keyVaultOptions = mock(KeyVaultOptions.class); when(keyVaultOptions.getSecretEngineName()).thenReturn(secretEngine); - HashicorpVaultKeyPair result = - hashicorpVaultKeyGenerator.generate(filename, null, keyVaultOptions); + GeneratedKeyPair result = hashicorpVaultKeyGenerator.generate(filename, null, keyVaultOptions); - HashicorpVaultKeyPair expected = - new HashicorpVaultKeyPair("publicKey", "privateKey", secretEngine, filename, null); - assertThat(result).isEqualToComparingFieldByField(expected); + HashicorpVaultKeyPair kp = + new HashicorpVaultKeyPair("publicKey", "privateKey", secretEngine, filename, 1); + GeneratedKeyPair expected = new GeneratedKeyPair(kp, pub.encodeToBase64()); + assertThat(result).usingRecursiveComparison().isEqualTo(expected); final ArgumentCaptor captor = ArgumentCaptor.forClass(Map.class); verify(keyVaultService).setSecret(captor.capture()); diff --git a/key-vault/aws-key-vault/build.gradle b/key-vault/aws-key-vault/build.gradle index 02f6b630a0..87b7a93613 100644 --- a/key-vault/aws-key-vault/build.gradle +++ b/key-vault/aws-key-vault/build.gradle @@ -3,7 +3,7 @@ plugins { id "application" } -def nettyVersion = "4.1.46.Final" +//def nettyVersion = "4.1.76.Final" dependencyCheck { failBuildOnCVSS = 11 @@ -21,26 +21,21 @@ configurations.all { dependencies { implementation project(":config") - - implementation("software.amazon.awssdk:secretsmanager:2.10.25") - - implementation("software.amazon.awssdk:apache-client:2.10.25") - - - implementation("org.apache.httpcomponents:httpclient:4.5.9") - implementation project(":key-vault:key-vault-api") - // compile "io.netty:netty:"+ nettyVersion - implementation "io.netty:netty-handler:$nettyVersion" - implementation "io.netty:netty-common:$nettyVersion" - implementation "io.netty:netty-buffer:$nettyVersion" - implementation "io.netty:netty-transport:$nettyVersion" - implementation "io.netty:netty-codec:$nettyVersion" - implementation "io.netty:netty-codec-http:$nettyVersion" - implementation "io.netty:netty-codec-http2:$nettyVersion" - implementation "io.netty:netty-transport-native-unix-common:$nettyVersion" - implementation "io.netty:netty-transport-native-epoll:$nettyVersion:linux-x86_64" + implementation("software.amazon.awssdk:secretsmanager:2.17.204") + implementation("software.amazon.awssdk:apache-client:2.17.204") + + // the aws dependencies often introduce CVE vulnerabilities - keep this code block here as a guide for if netty causes problems and an aws patch isn't available + // implementation "io.netty:netty-handler:$nettyVersion" + // implementation "io.netty:netty-common:$nettyVersion" + // implementation "io.netty:netty-buffer:$nettyVersion" + // implementation "io.netty:netty-transport:$nettyVersion" + // implementation "io.netty:netty-codec:$nettyVersion" + // implementation "io.netty:netty-codec-http:$nettyVersion" + // implementation "io.netty:netty-codec-http2:$nettyVersion" + // implementation "io.netty:netty-transport-native-unix-common:$nettyVersion" + // implementation "io.netty:netty-transport-native-epoll:$nettyVersion:linux-x86_64" implementation "com.fasterxml.jackson.core:jackson-core:$jacksonVersion" implementation "com.fasterxml.jackson.core:jackson-databind:$jacksonDatabindVersion" diff --git a/key-vault/aws-key-vault/src/main/java/com/quorum/tessera/key/vault/aws/AWSKeyVaultService.java b/key-vault/aws-key-vault/src/main/java/com/quorum/tessera/key/vault/aws/AWSKeyVaultService.java index 6f6f98b7bf..1ddbec428c 100644 --- a/key-vault/aws-key-vault/src/main/java/com/quorum/tessera/key/vault/aws/AWSKeyVaultService.java +++ b/key-vault/aws-key-vault/src/main/java/com/quorum/tessera/key/vault/aws/AWSKeyVaultService.java @@ -1,16 +1,12 @@ package com.quorum.tessera.key.vault.aws; import com.quorum.tessera.key.vault.KeyVaultService; +import com.quorum.tessera.key.vault.SetSecretResponse; import com.quorum.tessera.key.vault.VaultSecretNotFoundException; import java.util.Map; import java.util.Objects; import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient; -import software.amazon.awssdk.services.secretsmanager.model.CreateSecretRequest; -import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueRequest; -import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueResponse; -import software.amazon.awssdk.services.secretsmanager.model.InvalidParameterException; -import software.amazon.awssdk.services.secretsmanager.model.InvalidRequestException; -import software.amazon.awssdk.services.secretsmanager.model.ResourceNotFoundException; +import software.amazon.awssdk.services.secretsmanager.model.*; public class AWSKeyVaultService implements KeyVaultService { @@ -26,7 +22,6 @@ public class AWSKeyVaultService implements KeyVaultService { @Override public String getSecret(Map getSecretData) { - final String secretName = getSecretData.get(SECRET_NAME_KEY); GetSecretValueRequest getSecretValueRequest = @@ -51,14 +46,15 @@ public String getSecret(Map getSecretData) { } @Override - public Object setSecret(Map setSecretData) { - + public SetSecretResponse setSecret(Map setSecretData) { final String secretName = setSecretData.get(SECRET_NAME_KEY); final String secret = setSecretData.get(SECRET_KEY); CreateSecretRequest createSecretRequest = CreateSecretRequest.builder().name(secretName).secretString(secret).build(); - return secretsManager.createSecret(createSecretRequest); + CreateSecretResponse r = secretsManager.createSecret(createSecretRequest); + + return new SetSecretResponse(Map.of("version", r.versionId())); } } diff --git a/key-vault/aws-key-vault/src/test/java/com/quorum/tessera/key/vault/aws/AWSKeyVaultServiceTest.java b/key-vault/aws-key-vault/src/test/java/com/quorum/tessera/key/vault/aws/AWSKeyVaultServiceTest.java index 044290e3bb..58f636146c 100644 --- a/key-vault/aws-key-vault/src/test/java/com/quorum/tessera/key/vault/aws/AWSKeyVaultServiceTest.java +++ b/key-vault/aws-key-vault/src/test/java/com/quorum/tessera/key/vault/aws/AWSKeyVaultServiceTest.java @@ -12,11 +12,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mockito; import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient; -import software.amazon.awssdk.services.secretsmanager.model.CreateSecretRequest; -import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueRequest; -import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueResponse; -import software.amazon.awssdk.services.secretsmanager.model.InvalidParameterException; -import software.amazon.awssdk.services.secretsmanager.model.ResourceNotFoundException; +import software.amazon.awssdk.services.secretsmanager.model.*; public class AWSKeyVaultServiceTest { @@ -109,6 +105,10 @@ public void setSecret() { String secretName = "id"; String secret = "secret"; + CreateSecretResponse resp = mock(CreateSecretResponse.class); + when(secretsManager.createSecret(any(CreateSecretRequest.class))).thenReturn(resp); + when(resp.versionId()).thenReturn("vers"); + Map setSecretData = Map.of( AWSKeyVaultService.SECRET_NAME_KEY, secretName, diff --git a/key-vault/azure-key-vault/build.gradle b/key-vault/azure-key-vault/build.gradle index eeadaa7dea..c7bbf1c727 100644 --- a/key-vault/azure-key-vault/build.gradle +++ b/key-vault/azure-key-vault/build.gradle @@ -18,21 +18,20 @@ configurations.all { } dependencies { - implementation platform('io.projectreactor:reactor-bom:2020.0.10') - - // define dependencies without versions - implementation 'io.projectreactor.netty:reactor-netty' - implementation 'io.projectreactor.netty:reactor-netty-core' - implementation 'io.projectreactor.netty:reactor-netty-http' - implementation project(":config") implementation project(":key-vault:key-vault-api") - implementation "jakarta.annotation:jakarta.annotation-api" - implementation "com.sun.activation:jakarta.activation" - implementation ("com.azure:azure-security-keyvault-secrets:4.2.3") - implementation("com.azure:azure-identity:1.3.5") - implementation("com.azure:azure-core:1.19.0") + implementation ("com.azure:azure-security-keyvault-secrets:4.4.2") { + exclude group: 'com.azure', module: 'azure-core-http-netty' + } + implementation("com.azure:azure-identity:1.5.1") { + exclude group: 'com.azure', module: 'azure-core-http-netty' + } + implementation("com.azure:azure-core:1.29.1") { + exclude group: 'com.azure', module: 'azure-core-http-netty' + } + implementation 'com.azure:azure-core-http-okhttp:1.10.1' + implementation 'com.squareup.okio:okio:3.1.0' testImplementation "org.glassfish:jakarta.json" diff --git a/key-vault/azure-key-vault/src/main/java/com/quorum/tessera/key/vault/azure/AzureKeyVaultService.java b/key-vault/azure-key-vault/src/main/java/com/quorum/tessera/key/vault/azure/AzureKeyVaultService.java index 5997dd7b67..c2509aeac2 100644 --- a/key-vault/azure-key-vault/src/main/java/com/quorum/tessera/key/vault/azure/AzureKeyVaultService.java +++ b/key-vault/azure-key-vault/src/main/java/com/quorum/tessera/key/vault/azure/AzureKeyVaultService.java @@ -4,6 +4,7 @@ import com.azure.security.keyvault.secrets.SecretClient; import com.azure.security.keyvault.secrets.models.KeyVaultSecret; import com.quorum.tessera.key.vault.KeyVaultService; +import com.quorum.tessera.key.vault.SetSecretResponse; import com.quorum.tessera.key.vault.VaultSecretNotFoundException; import java.util.Map; import java.util.Objects; @@ -48,11 +49,15 @@ public String getSecret(Map azureGetSecretData) { } @Override - public Object setSecret(Map azureSetSecretData) { + public SetSecretResponse setSecret(Map azureSetSecretData) { final String secretName = azureSetSecretData.get(SECRET_NAME_KEY); final String secret = azureSetSecretData.get(SECRET_KEY); - return secretClient.setSecret(secretName, secret); + KeyVaultSecret kvs = secretClient.setSecret(secretName, secret); + + SetSecretResponse resp = + new SetSecretResponse(Map.of("version", kvs.getProperties().getVersion())); + return resp; } } diff --git a/key-vault/azure-key-vault/src/main/java/module-info.java b/key-vault/azure-key-vault/src/main/java/module-info.java index 29effb1c79..f1d292125a 100644 --- a/key-vault/azure-key-vault/src/main/java/module-info.java +++ b/key-vault/azure-key-vault/src/main/java/module-info.java @@ -3,13 +3,9 @@ requires org.slf4j; requires tessera.config; requires tessera.keyvault.api; - requires jakarta.annotation; requires com.azure.identity; requires com.azure.security.keyvault.secrets; - requires com.azure.http.netty; - requires reactor.netty; - requires io.netty.handler; - requires io.netty.common; + requires kotlin.stdlib; provides com.quorum.tessera.key.vault.KeyVaultServiceFactory with com.quorum.tessera.key.vault.azure.AzureKeyVaultServiceFactory; diff --git a/key-vault/azure-key-vault/src/test/java/com/quorum/tessera/key/vault/azure/AzureKeyVaultServiceTest.java b/key-vault/azure-key-vault/src/test/java/com/quorum/tessera/key/vault/azure/AzureKeyVaultServiceTest.java index a9a43a8f5c..f68ccf4545 100644 --- a/key-vault/azure-key-vault/src/test/java/com/quorum/tessera/key/vault/azure/AzureKeyVaultServiceTest.java +++ b/key-vault/azure-key-vault/src/test/java/com/quorum/tessera/key/vault/azure/AzureKeyVaultServiceTest.java @@ -1,13 +1,14 @@ package com.quorum.tessera.key.vault.azure; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.catchThrowable; +import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; import com.azure.core.exception.ResourceNotFoundException; import com.azure.core.http.HttpResponse; import com.azure.security.keyvault.secrets.SecretClient; import com.azure.security.keyvault.secrets.models.KeyVaultSecret; +import com.azure.security.keyvault.secrets.models.SecretProperties; +import com.quorum.tessera.key.vault.SetSecretResponse; import com.quorum.tessera.key.vault.VaultSecretNotFoundException; import java.util.Map; import org.junit.Before; @@ -89,10 +90,14 @@ public void setSecret() { final KeyVaultSecret newSecret = mock(KeyVaultSecret.class); when(secretClient.setSecret(secretName, secret)).thenReturn(newSecret); + final SecretProperties props = mock(SecretProperties.class); + when(newSecret.getProperties()).thenReturn(props); + when(props.getVersion()).thenReturn("myvers"); - final Object result = keyVaultService.setSecret(setSecretData); + final SetSecretResponse result = keyVaultService.setSecret(setSecretData); - assertThat(result).isSameAs(newSecret); + assertThat(result.getProperties()).size().isEqualTo(1); + assertThat(result.getProperties()).contains(entry("version", "myvers")); verify(secretClient).setSecret("secret-name", "secret-value"); } } diff --git a/key-vault/hashicorp-key-vault/build.gradle b/key-vault/hashicorp-key-vault/build.gradle index ba8a397692..171e15497d 100644 --- a/key-vault/hashicorp-key-vault/build.gradle +++ b/key-vault/hashicorp-key-vault/build.gradle @@ -18,7 +18,7 @@ configurations.all { exclude group: "jakarta.json" } -def springVersion = "5.3.5" +def springVersion = "5.3.20" dependencies { implementation project(":config") implementation project(":key-vault:key-vault-api") diff --git a/key-vault/hashicorp-key-vault/src/main/java/com/quorum/tessera/key/vault/hashicorp/HashicorpKeyVaultService.java b/key-vault/hashicorp-key-vault/src/main/java/com/quorum/tessera/key/vault/hashicorp/HashicorpKeyVaultService.java index 18ccd1428b..2b2f307713 100644 --- a/key-vault/hashicorp-key-vault/src/main/java/com/quorum/tessera/key/vault/hashicorp/HashicorpKeyVaultService.java +++ b/key-vault/hashicorp-key-vault/src/main/java/com/quorum/tessera/key/vault/hashicorp/HashicorpKeyVaultService.java @@ -3,6 +3,7 @@ import static java.util.function.Predicate.not; import com.quorum.tessera.key.vault.KeyVaultService; +import com.quorum.tessera.key.vault.SetSecretResponse; import java.util.List; import java.util.Map; import java.util.Optional; @@ -66,7 +67,7 @@ public String getSecret(Map hashicorpGetSecretData) { } @Override - public Object setSecret(Map hashicorpSetSecretData) { + public SetSecretResponse setSecret(Map hashicorpSetSecretData) { String secretName = hashicorpSetSecretData.get(SECRET_NAME_KEY); String secretEngineName = hashicorpSetSecretData.get(SECRET_ENGINE_NAME_KEY); @@ -84,7 +85,9 @@ public Object setSecret(Map hashicorpSetSecretData) { vaultVersionedKeyValueTemplateFactory.createVaultVersionedKeyValueTemplate( vaultOperations, secretEngineName); try { - return keyValueOperations.put(secretName, nameValuePairs); + Versioned.Metadata metadata = keyValueOperations.put(secretName, nameValuePairs); + return new SetSecretResponse( + Map.of("version", String.valueOf(metadata.getVersion().getVersion()))); } catch (NullPointerException ex) { throw new HashicorpVaultException( "Unable to save generated secret to vault. Ensure that the secret engine being used is a v2 kv secret engine"); diff --git a/key-vault/hashicorp-key-vault/src/test/java/com/quorum/tessera/key/vault/hashicorp/HashicorpKeyVaultServiceTest.java b/key-vault/hashicorp-key-vault/src/test/java/com/quorum/tessera/key/vault/hashicorp/HashicorpKeyVaultServiceTest.java index dae25ebb67..e5005b073e 100644 --- a/key-vault/hashicorp-key-vault/src/test/java/com/quorum/tessera/key/vault/hashicorp/HashicorpKeyVaultServiceTest.java +++ b/key-vault/hashicorp-key-vault/src/test/java/com/quorum/tessera/key/vault/hashicorp/HashicorpKeyVaultServiceTest.java @@ -1,9 +1,9 @@ package com.quorum.tessera.key.vault.hashicorp; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.catchThrowable; +import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; +import com.quorum.tessera.key.vault.SetSecretResponse; import java.util.Map; import org.junit.After; import org.junit.Before; @@ -175,10 +175,15 @@ public void setSecretReturnsMetadataObject() { vaultOperations, "engine")) .thenReturn(vaultVersionedKeyValueTemplate); - Object result = keyVaultService.setSecret(setSecretData); + Versioned.Version version = mock(Versioned.Version.class); + when(version.getVersion()).thenReturn(22); - assertThat(result).isInstanceOf(Versioned.Metadata.class); - assertThat(result).isEqualTo(metadata); + when(metadata.getVersion()).thenReturn(version); + + SetSecretResponse result = keyVaultService.setSecret(setSecretData); + + assertThat(result.getProperties()).size().isEqualTo(1); + assertThat(result.getProperties()).contains(entry("version", "22")); verify(vaultVersionedKeyValueTemplateFactory) .createVaultVersionedKeyValueTemplate(vaultOperations, "engine"); diff --git a/key-vault/key-vault-api/src/main/java/com/quorum/tessera/key/vault/KeyVaultService.java b/key-vault/key-vault-api/src/main/java/com/quorum/tessera/key/vault/KeyVaultService.java index c0c01fc019..dfafa8ab4f 100644 --- a/key-vault/key-vault-api/src/main/java/com/quorum/tessera/key/vault/KeyVaultService.java +++ b/key-vault/key-vault-api/src/main/java/com/quorum/tessera/key/vault/KeyVaultService.java @@ -6,5 +6,5 @@ public interface KeyVaultService { String getSecret(Map getSecretData); - Object setSecret(Map setSecretData); + SetSecretResponse setSecret(Map setSecretData); } diff --git a/key-vault/key-vault-api/src/main/java/com/quorum/tessera/key/vault/SetSecretResponse.java b/key-vault/key-vault-api/src/main/java/com/quorum/tessera/key/vault/SetSecretResponse.java new file mode 100644 index 0000000000..aa2bbe33d1 --- /dev/null +++ b/key-vault/key-vault-api/src/main/java/com/quorum/tessera/key/vault/SetSecretResponse.java @@ -0,0 +1,20 @@ +package com.quorum.tessera.key.vault; + +import java.util.Map; + +public class SetSecretResponse { + + Map properties; + + public SetSecretResponse(Map properties) { + this.properties = properties; + } + + public Map getProperties() { + return properties; + } + + public String getProperty(String name) { + return properties.get(name); + } +}