From a38414d3c555a3528607cc73ed24f880a060869a Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Mon, 2 Oct 2017 15:01:01 +0200 Subject: [PATCH] Support ecdsa and ed25519 transit key metadata. We now support key metadata retrieval via VaultTransitKey. Vault reports a different metadata structure depending on the key type. Fixes gh-149. --- .../vault/core/VaultTransitTemplate.java | 2 +- .../vault/support/VaultTransitKey.java | 4 +- .../VaultTransitKeyCreationRequest.java | 16 +- .../VaultTemplateTransitIntegrationTests.java | 85 --------- .../VaultTransitTemplateIntegrationTests.java | 180 ++++++++++++++++-- 5 files changed, 184 insertions(+), 103 deletions(-) diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultTransitTemplate.java b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultTransitTemplate.java index a6cbc6a0..73ef8d57 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultTransitTemplate.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultTransitTemplate.java @@ -454,7 +454,7 @@ public class VaultTransitTemplate implements VaultTransitOperations { private boolean exportable; - private Map keys = Collections.emptyMap(); + private Map keys = Collections.emptyMap(); @JsonProperty("latest_version") private boolean latestVersion; diff --git a/spring-vault-core/src/main/java/org/springframework/vault/support/VaultTransitKey.java b/spring-vault-core/src/main/java/org/springframework/vault/support/VaultTransitKey.java index 0d435080..31b1ade4 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/support/VaultTransitKey.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/support/VaultTransitKey.java @@ -42,9 +42,9 @@ public interface VaultTransitKey { boolean isExportable(); /** - * @return a {@link Map} of key version to its creation timestamp. + * @return a {@link Map} of key version to its Vault-specific representation. */ - Map getKeys(); + Map getKeys(); /** * @return {@literal true} if the key represents the latest version. diff --git a/spring-vault-core/src/main/java/org/springframework/vault/support/VaultTransitKeyCreationRequest.java b/spring-vault-core/src/main/java/org/springframework/vault/support/VaultTransitKeyCreationRequest.java index 02cbb806..73630a77 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/support/VaultTransitKeyCreationRequest.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/support/VaultTransitKeyCreationRequest.java @@ -45,6 +45,18 @@ public class VaultTransitKeyCreationRequest { this.exportable = exportable; } + /** + * Create a new {@link VaultTransitKeyCreationRequest} specifically for a {@code type} + * . + * @param the key type to use, must not be {@literal null} or empty. + * @return a new {@link VaultTransitKeyCreationRequest} for the given key {@code type} + * . + * @since 2.0 + */ + public static VaultTransitKeyCreationRequest ofKeyType(String type) { + return builder().type(type).build(); + } + /** * @return a new {@link VaultTransitKeyCreationRequestBuilder}. */ @@ -99,14 +111,14 @@ public class VaultTransitKeyCreationRequest { } /** - * Configure key derivation. + * Configure the key type. * * @param type the type of key to create, must not be empty or {@literal null}. * @return {@code this} {@link VaultTransitKeyCreationRequestBuilder}. */ public VaultTransitKeyCreationRequestBuilder type(String type) { - Assert.hasText(type, "Type must not be empty"); + Assert.hasText(type, "Type must not be null or empty"); this.type = type; return this; diff --git a/spring-vault-core/src/test/java/org/springframework/vault/core/VaultTemplateTransitIntegrationTests.java b/spring-vault-core/src/test/java/org/springframework/vault/core/VaultTemplateTransitIntegrationTests.java index 36c52a6b..d4558f4d 100644 --- a/spring-vault-core/src/test/java/org/springframework/vault/core/VaultTemplateTransitIntegrationTests.java +++ b/spring-vault-core/src/test/java/org/springframework/vault/core/VaultTemplateTransitIntegrationTests.java @@ -27,19 +27,13 @@ import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.vault.VaultException; -import org.springframework.vault.support.RawTransitKey; -import org.springframework.vault.support.TransitKeyType; import org.springframework.vault.support.VaultMount; import org.springframework.vault.support.VaultResponse; -import org.springframework.vault.support.VaultTransitKey; import org.springframework.vault.support.VaultTransitKeyConfiguration; -import org.springframework.vault.support.VaultTransitKeyCreationRequest; import org.springframework.vault.util.IntegrationTestSupport; import org.springframework.vault.util.Version; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assume.assumeTrue; /** * Integration tests for {@link VaultTemplate} using the {@code transit} backend. @@ -51,7 +45,6 @@ import static org.junit.Assume.assumeTrue; @ContextConfiguration(classes = VaultIntegrationTestConfiguration.class) public class VaultTemplateTransitIntegrationTests extends IntegrationTestSupport { - @Autowired private VaultOperations vaultOperations; @@ -71,10 +64,6 @@ public class VaultTemplateTransitIntegrationTests extends IntegrationTestSupport removeKeys(); vaultOperations.write("transit/keys/mykey", null); - vaultOperations.write("transit/keys/derived", - Collections.singletonMap("derived", true)); - vaultOperations.write("transit/keys/export", - Collections.singletonMap("exportable", true)); } @After @@ -106,8 +95,6 @@ public class VaultTemplateTransitIntegrationTests extends IntegrationTestSupport } else { deleteKey("mykey"); - deleteKey("derived"); - deleteKey("export"); } } @@ -138,76 +125,4 @@ public class VaultTemplateTransitIntegrationTests extends IntegrationTestSupport assertThat((String) decrypted.getData().get("plaintext")).isEqualTo( Base64.encodeBase64String("that message is secret".getBytes())); } - - @Test - public void shouldCreateNewExportableKey() { - - assumeTrue(vaultVersion.isGreaterThanOrEqualTo(Version.parse("0.6.5"))); - - VaultTransitOperations vaultTransitOperations = vaultOperations.opsForTransit(); - VaultTransitKeyCreationRequest vaultTransitKeyCreationRequest = VaultTransitKeyCreationRequest - .builder().exportable(true).derived(true).build(); - - vaultTransitOperations.createKey("export-test", vaultTransitKeyCreationRequest); - - VaultTransitKey vaultTransitKey = vaultTransitOperations.getKey("export-test"); - - assertThat(vaultTransitKey.getName()).isEqualTo("export-test"); - assertThat(vaultTransitKey.isExportable()).isTrue(); - } - - @Test - public void shouldCreateNotExportableKeyByDefault() { - - assumeTrue(vaultVersion.isGreaterThanOrEqualTo(Version.parse("0.6.5"))); - - VaultTransitOperations vaultTransitOperations = vaultOperations.opsForTransit(); - - vaultTransitOperations.createKey("no-export"); - - VaultTransitKey vaultTransitKey = vaultTransitOperations.getKey("no-export"); - - assertThat(vaultTransitKey.getName()).isEqualTo("no-export"); - assertThat(vaultTransitKey.isExportable()).isFalse(); - } - - @Test - public void shouldExportEncryptionKey() { - - assumeTrue(vaultVersion.isGreaterThanOrEqualTo(Version.parse("0.6.5"))); - - VaultTransitOperations vaultTransitOperations = vaultOperations.opsForTransit(); - - RawTransitKey rawTransitKey = vaultTransitOperations.exportKey("export", - TransitKeyType.ENCRYPTION_KEY); - - assertThat(rawTransitKey.getName()).isEqualTo("export"); - assertThat(rawTransitKey.getKeys()).isNotEmpty(); - assertThat(rawTransitKey.getKeys().get("1")).isNotBlank(); - } - - @Test(expected = VaultException.class) - public void shouldNotAllowExportSigningKey() { - - assumeTrue(vaultVersion.isGreaterThanOrEqualTo(Version.parse("0.6.5"))); - - VaultTransitOperations vaultTransitOperations = vaultOperations.opsForTransit(); - - vaultTransitOperations.exportKey("export", TransitKeyType.SIGNING_KEY); - } - - @Test - public void shouldExportHmacKey() { - - assumeTrue(vaultVersion.isGreaterThanOrEqualTo(Version.parse("0.6.5"))); - - VaultTransitOperations vaultTransitOperations = vaultOperations.opsForTransit(); - - RawTransitKey rawTransitKey = vaultTransitOperations.exportKey("export", - TransitKeyType.HMAC_KEY); - - assertThat(rawTransitKey.getName()).isEqualTo("export"); - assertThat(rawTransitKey.getKeys()).isNotEmpty(); - assertThat(rawTransitKey.getKeys().get("1")).isNotBlank(); - } } diff --git a/spring-vault-core/src/test/java/org/springframework/vault/core/VaultTransitTemplateIntegrationTests.java b/spring-vault-core/src/test/java/org/springframework/vault/core/VaultTransitTemplateIntegrationTests.java index 3fd647ab..8b6ee37e 100644 --- a/spring-vault-core/src/test/java/org/springframework/vault/core/VaultTransitTemplateIntegrationTests.java +++ b/spring-vault-core/src/test/java/org/springframework/vault/core/VaultTransitTemplateIntegrationTests.java @@ -16,6 +16,7 @@ package org.springframework.vault.core; import java.util.Arrays; +import java.util.Collections; import java.util.List; import org.junit.After; @@ -29,6 +30,8 @@ import org.springframework.test.context.junit4.SpringRunner; import org.springframework.vault.VaultException; import org.springframework.vault.support.Ciphertext; import org.springframework.vault.support.Plaintext; +import org.springframework.vault.support.RawTransitKey; +import org.springframework.vault.support.TransitKeyType; import org.springframework.vault.support.VaultDecryptionResult; import org.springframework.vault.support.VaultEncryptionResult; import org.springframework.vault.support.VaultMount; @@ -58,8 +61,11 @@ public class VaultTransitTemplateIntegrationTests extends IntegrationTestSupport @Autowired private VaultOperations vaultOperations; + private VaultTransitOperations transitOperations; + private Version vaultVersion; + @Before public void before() { @@ -69,6 +75,8 @@ public class VaultTransitTemplateIntegrationTests extends IntegrationTestSupport vaultOperations.opsForSys().mount("transit", VaultMount.create("transit")); } + vaultVersion = prepare().getVersion(); + removeKeys(); } @@ -95,7 +103,7 @@ public class VaultTransitTemplateIntegrationTests extends IntegrationTestSupport private void removeKeys() { - if (prepare().getVersion().isGreaterThanOrEqualTo(Version.parse("0.6.4"))) { + if (vaultVersion.isGreaterThanOrEqualTo(Version.parse("0.6.4"))) { List keys = vaultOperations.opsForTransit().getKeys(); for (String keyName : keys) { deleteKey(keyName); @@ -103,7 +111,9 @@ public class VaultTransitTemplateIntegrationTests extends IntegrationTestSupport } else { deleteKey("mykey"); - deleteKey("derived"); + deleteKey("export"); + deleteKey("ecdsa-key"); + deleteKey("ed-key"); } } @@ -123,6 +133,38 @@ public class VaultTransitTemplateIntegrationTests extends IntegrationTestSupport assertThat(mykey.isLatestVersion()).isTrue(); } + @Test + public void createKeyShouldCreateEcDsaKey() { + + assumeTrue(vaultVersion.isGreaterThanOrEqualTo(Version.parse("0.6.4"))); + + VaultTransitKeyCreationRequest request = VaultTransitKeyCreationRequest + .ofKeyType("ecdsa-p256"); + + transitOperations.createKey("ecdsa-key", request); + + VaultTransitKey mykey = transitOperations.getKey("ecdsa-key"); + + assertThat(mykey.getType()).startsWith("ecdsa"); + assertThat(mykey.getKeys()).isNotEmpty(); + } + + @Test + public void createKeyShouldCreateEdKey() { + + assumeTrue(vaultVersion.isGreaterThanOrEqualTo(Version.parse("0.6.4"))); + + VaultTransitKeyCreationRequest request = VaultTransitKeyCreationRequest + .ofKeyType("ed25519"); + + transitOperations.createKey("ed-key", request); + + VaultTransitKey mykey = transitOperations.getKey("ed-key"); + + assertThat(mykey.getType()).startsWith("ed"); + assertThat(mykey.getKeys()).isNotEmpty(); + } + @Test public void createKeyShouldCreateKeyWithOptions() { @@ -145,7 +187,7 @@ public class VaultTransitTemplateIntegrationTests extends IntegrationTestSupport @Test public void shouldEnumerateKey() { - assumeTrue(prepare().getVersion().isGreaterThanOrEqualTo(Version.parse("0.6.4"))); + assumeTrue(vaultVersion.isGreaterThanOrEqualTo(Version.parse("0.6.4"))); assertThat(transitOperations.getKeys()).isEmpty(); @@ -326,8 +368,7 @@ public class VaultTransitTemplateIntegrationTests extends IntegrationTestSupport @Test public void shouldBatchEncrypt() { - assumeTrue(prepare().getVersion().isGreaterThanOrEqualTo( - BATCH_INTRODUCED_IN_VERSION)); + assumeTrue(vaultVersion.isGreaterThanOrEqualTo(BATCH_INTRODUCED_IN_VERSION)); transitOperations.createKey("mykey"); @@ -341,8 +382,7 @@ public class VaultTransitTemplateIntegrationTests extends IntegrationTestSupport @Test public void shouldBatchDecrypt() { - assumeTrue(prepare().getVersion().isGreaterThanOrEqualTo( - BATCH_INTRODUCED_IN_VERSION)); + assumeTrue(vaultVersion.isGreaterThanOrEqualTo(BATCH_INTRODUCED_IN_VERSION)); transitOperations.createKey("mykey"); @@ -364,8 +404,7 @@ public class VaultTransitTemplateIntegrationTests extends IntegrationTestSupport @Test public void shouldBatchEncryptWithContext() { - assumeTrue(prepare().getVersion().isGreaterThanOrEqualTo( - BATCH_INTRODUCED_IN_VERSION)); + assumeTrue(vaultVersion.isGreaterThanOrEqualTo(BATCH_INTRODUCED_IN_VERSION)); VaultTransitKeyCreationRequest request = VaultTransitKeyCreationRequest.builder() // .derived(true) // @@ -389,8 +428,7 @@ public class VaultTransitTemplateIntegrationTests extends IntegrationTestSupport @Test public void shouldBatchDecryptWithContext() { - assumeTrue(prepare().getVersion().isGreaterThanOrEqualTo( - BATCH_INTRODUCED_IN_VERSION)); + assumeTrue(vaultVersion.isGreaterThanOrEqualTo(BATCH_INTRODUCED_IN_VERSION)); VaultTransitKeyCreationRequest request = VaultTransitKeyCreationRequest.builder() // .derived(true) // @@ -416,8 +454,7 @@ public class VaultTransitTemplateIntegrationTests extends IntegrationTestSupport @Test public void shouldBatchDecryptWithWrongContext() { - assumeTrue(prepare().getVersion().isGreaterThanOrEqualTo( - BATCH_INTRODUCED_IN_VERSION)); + assumeTrue(vaultVersion.isGreaterThanOrEqualTo(BATCH_INTRODUCED_IN_VERSION)); VaultTransitKeyCreationRequest request = VaultTransitKeyCreationRequest.builder() // .derived(true) // @@ -446,4 +483,121 @@ public class VaultTransitTemplateIntegrationTests extends IntegrationTestSupport assertThat(decrypted.get(1).isSuccessful()).isEqualTo(false); assertThat(decrypted.get(1).getCause()).isInstanceOf(VaultException.class); } + + @Test + public void shouldCreateNewExportableKey() { + + assumeTrue(vaultVersion.isGreaterThanOrEqualTo(Version.parse("0.6.5"))); + + VaultTransitOperations vaultTransitOperations = vaultOperations.opsForTransit(); + VaultTransitKeyCreationRequest vaultTransitKeyCreationRequest = VaultTransitKeyCreationRequest + .builder().exportable(true).derived(true).build(); + + vaultTransitOperations.createKey("export-test", vaultTransitKeyCreationRequest); + + VaultTransitKey vaultTransitKey = vaultTransitOperations.getKey("export-test"); + + assertThat(vaultTransitKey.getName()).isEqualTo("export-test"); + assertThat(vaultTransitKey.isExportable()).isTrue(); + } + + @Test + public void shouldCreateNotExportableKeyByDefault() { + + assumeTrue(vaultVersion.isGreaterThanOrEqualTo(Version.parse("0.6.5"))); + + VaultTransitOperations vaultTransitOperations = vaultOperations.opsForTransit(); + + vaultTransitOperations.createKey("no-export"); + + VaultTransitKey vaultTransitKey = vaultTransitOperations.getKey("no-export"); + + assertThat(vaultTransitKey.getName()).isEqualTo("no-export"); + assertThat(vaultTransitKey.isExportable()).isFalse(); + } + + @Test + public void shouldExportEncryptionKey() { + + assumeTrue(vaultVersion.isGreaterThanOrEqualTo(Version.parse("0.6.5"))); + + vaultOperations.write("transit/keys/export", + Collections.singletonMap("exportable", true)); + + RawTransitKey rawTransitKey = transitOperations.exportKey("export", + TransitKeyType.ENCRYPTION_KEY); + + assertThat(rawTransitKey.getName()).isEqualTo("export"); + assertThat(rawTransitKey.getKeys()).isNotEmpty(); + assertThat(rawTransitKey.getKeys().get("1")).isNotBlank(); + } + + @Test(expected = VaultException.class) + public void shouldNotAllowExportSigningKey() { + + assumeTrue(vaultVersion.isGreaterThanOrEqualTo(Version.parse("0.6.5"))); + + vaultOperations.write("transit/keys/export", + Collections.singletonMap("exportable", true)); + + transitOperations.exportKey("export", TransitKeyType.SIGNING_KEY); + } + + @Test + public void shouldExportHmacKey() { + + assumeTrue(vaultVersion.isGreaterThanOrEqualTo(Version.parse("0.6.5"))); + + vaultOperations.write("transit/keys/export", + Collections.singletonMap("exportable", true)); + + RawTransitKey rawTransitKey = transitOperations.exportKey("export", + TransitKeyType.HMAC_KEY); + + assertThat(rawTransitKey.getName()).isEqualTo("export"); + assertThat(rawTransitKey.getKeys()).isNotEmpty(); + assertThat(rawTransitKey.getKeys().get("1")).isNotBlank(); + } + + @Test + public void shouldExportEcDsaKey() { + + assumeTrue(vaultVersion.isGreaterThanOrEqualTo(Version.parse("0.6.5"))); + + VaultTransitOperations transitOperations = vaultOperations.opsForTransit(); + + VaultTransitKeyCreationRequest request = VaultTransitKeyCreationRequest.builder() + .type("ecdsa-p256").exportable(true).build(); + + transitOperations.createKey("ecdsa-key", request); + + RawTransitKey hmacKey = transitOperations.exportKey("ecdsa-key", + TransitKeyType.HMAC_KEY); + RawTransitKey signingKey = transitOperations.exportKey("ecdsa-key", + TransitKeyType.SIGNING_KEY); + + assertThat(hmacKey.getKeys()).isNotEmpty(); + assertThat(signingKey.getKeys()).isNotEmpty(); + } + + @Test + public void shouldExportEdKey() { + + assumeTrue(vaultVersion.isGreaterThanOrEqualTo(Version.parse("0.6.5"))); + + VaultTransitOperations transitOperations = vaultOperations.opsForTransit(); + + VaultTransitKeyCreationRequest request = VaultTransitKeyCreationRequest.builder() + .type("ed25519").exportable(true).build(); + + transitOperations.createKey("ed-key", request); + + RawTransitKey hmacKey = transitOperations.exportKey("ed-key", + TransitKeyType.HMAC_KEY); + RawTransitKey signingKey = transitOperations.exportKey("ed-key", + TransitKeyType.SIGNING_KEY); + + assertThat(hmacKey.getKeys()).isNotEmpty(); + assertThat(signingKey.getKeys()).isNotEmpty(); + } }