From 451a594e89e4a4ae3df86f28b65572a6dd212fab Mon Sep 17 00:00:00 2001 From: Dima Gutzeit <11374909+innovationhub-asia@users.noreply.github.com> Date: Mon, 20 Nov 2017 23:05:37 +0800 Subject: [PATCH 01/10] CryptoClient implementation - Add support for creating keys - Add support for querying for symmetic keys - Add support for querying asymmetric keys --- build.gradle | 7 +- gradle/dependencies.gradle | 4 +- gradle/wrapper/gradle-wrapper.properties | 2 +- .../nike/vault/client/VaultAdminClient.java | 14 -- .../com/nike/vault/client/VaultClient.java | 14 ++ .../nike/vault/client/VaultClientFactory.java | 183 ++++++++++++++++++ .../nike/vault/client/VaultCryptoClient.java | 99 ++++++++++ .../model/VaultAsymmetricKeyResponse.java | 94 +++++++++ .../client/model/VaultCreateKeyRequest.java | 87 +++++++++ .../vault/client/model/VaultKeyResponse.java | 133 +++++++++++++ .../model/VaultSymmetricKeyResponse.java | 60 ++++++ .../vault/client/VaultCryptoClientTest.java | 120 ++++++++++++ .../com/nike/vault/client/asymmetric_key.json | 1 + .../com/nike/vault/client/symmetric_key.json | 1 + 14 files changed, 799 insertions(+), 20 deletions(-) create mode 100644 src/main/java/com/nike/vault/client/VaultCryptoClient.java create mode 100644 src/main/java/com/nike/vault/client/model/VaultAsymmetricKeyResponse.java create mode 100644 src/main/java/com/nike/vault/client/model/VaultCreateKeyRequest.java create mode 100644 src/main/java/com/nike/vault/client/model/VaultKeyResponse.java create mode 100644 src/main/java/com/nike/vault/client/model/VaultSymmetricKeyResponse.java create mode 100644 src/test/java/com/nike/vault/client/VaultCryptoClientTest.java create mode 100644 src/test/resources/com/nike/vault/client/asymmetric_key.json create mode 100644 src/test/resources/com/nike/vault/client/symmetric_key.json diff --git a/build.gradle b/build.gradle index 6e9beaf..52e99a9 100644 --- a/build.gradle +++ b/build.gradle @@ -22,9 +22,10 @@ apply plugin: 'java' apply plugin: 'maven' apply plugin: 'com.jfrog.bintray' apply plugin: 'maven-publish' +apply plugin: 'idea' -sourceCompatibility = 1.7 -targetCompatibility = 1.7 +sourceCompatibility = 1.8 +targetCompatibility = 1.8 task copyProjectVersion() { def releaseVersion = version @@ -39,4 +40,4 @@ apply from: file('gradle/check.gradle') apply from: file('gradle/integration.gradle') apply from: file('gradle/bintray.gradle') -group = groupId \ No newline at end of file +group = groupId diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index 018e90b..b8f8e5d 100644 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -20,7 +20,7 @@ repositories { dependencies { compile "org.apache.commons:commons-lang3:3.4" - compile "com.squareup.okhttp3:okhttp:3.9.0" + compile "com.squareup.okhttp3:okhttp:3.9.1" compile "com.google.code.gson:gson:2.5" compile "com.google.code.findbugs:jsr305:3.0.1" compile "org.slf4j:slf4j-api:1.7.25" @@ -32,4 +32,4 @@ dependencies { testCompile "org.assertj:assertj-core:2.3.0" testCompile "com.squareup.okhttp3:mockwebserver:3.7.0" testCompile "commons-io:commons-io:2.4" -} \ No newline at end of file +} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 3b1fc4f..c6d4df5 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -19,4 +19,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.14-bin.zip +distributionUrl=http\://services.gradle.org/distributions/gradle-4.1-bin.zip diff --git a/src/main/java/com/nike/vault/client/VaultAdminClient.java b/src/main/java/com/nike/vault/client/VaultAdminClient.java index bd8fa63..e970a32 100644 --- a/src/main/java/com/nike/vault/client/VaultAdminClient.java +++ b/src/main/java/com/nike/vault/client/VaultAdminClient.java @@ -337,18 +337,4 @@ public void disableAuditBackend(final String path) { parseAndThrowErrorResponse(response); } } - - /** - * Barebones method that can be used to make any call to Vault. The caller is responsible for interpreting - * and de-serializing the response. The Gson instance used by the client is accessible via {@link #getGson()} - * - * @param path Path to the resource - * @param method HTTP method - * @param requestBody Request body to be serialized as JSON. Set to null if no request body - * @return HTTP response object - */ - public Response execute(final String path, final String method, final Object requestBody) { - final HttpUrl url = buildUrl("", path); - return execute(url, method, requestBody); - } } diff --git a/src/main/java/com/nike/vault/client/VaultClient.java b/src/main/java/com/nike/vault/client/VaultClient.java index 5e5fd85..f4f935e 100644 --- a/src/main/java/com/nike/vault/client/VaultClient.java +++ b/src/main/java/com/nike/vault/client/VaultClient.java @@ -424,4 +424,18 @@ protected String responseBodyAsString(Response response) { return "ERROR failed to print response body as str: " + ioe.getMessage(); } } + + /** + * Barebones method that can be used to make any call to Vault. The caller is responsible for interpreting + * and de-serializing the response. The Gson instance used by the client is accessible via {@link #getGson()} + * + * @param path Path to the resource + * @param method HTTP method + * @param requestBody Request body to be serialized as JSON. Set to null if no request body + * @return HTTP response object + */ + public Response execute(final String path, final String method, final Object requestBody) { + final HttpUrl url = buildUrl("", path); + return execute(url, method, requestBody); + } } diff --git a/src/main/java/com/nike/vault/client/VaultClientFactory.java b/src/main/java/com/nike/vault/client/VaultClientFactory.java index 3946b49..4dcfcc1 100644 --- a/src/main/java/com/nike/vault/client/VaultClientFactory.java +++ b/src/main/java/com/nike/vault/client/VaultClientFactory.java @@ -308,4 +308,187 @@ public static VaultAdminClient getAdminClient(final UrlResolver vaultUrlResolver .build(), headers.build()); } + + + + + /** + * Basic factory method that will build a Vault admin client that + * looks up the Vault URL from one of the following places: + * + * Default recommended credential provider and http client are used. + * + * @return Vault admin client + */ + public static VaultCryptoClient getCryptoClient() { + return getCryptoClient(new DefaultVaultUrlResolver(), + new DefaultVaultCredentialsProviderChain(), + DEFAULT_MAX_REQUESTS, + DEFAULT_MAX_REQUESTS, + DEFAULT_TIMEOUT, + DEFAULT_TIMEOUT, + DEFAULT_TIMEOUT, + DEFAULT_HEADERS + ); + } + + /** + * Factory method allows setting of the Vault URL resolver, but will use + * the default recommended credentials provider chain and http client. + * + * @param vaultUrlResolver URL resolver for Vault + * @return Vault admin client + */ + public static VaultCryptoClient getCryptoClient(final UrlResolver vaultUrlResolver) { + return getCryptoClient(vaultUrlResolver, + new DefaultVaultCredentialsProviderChain(), + DEFAULT_MAX_REQUESTS, + DEFAULT_MAX_REQUESTS, + DEFAULT_TIMEOUT, + DEFAULT_TIMEOUT, + DEFAULT_TIMEOUT, + DEFAULT_HEADERS + ); + } + + /** + * Factory method that allows for a user defined Vault URL resolver and credentials provider. + * + * @param vaultUrlResolver URL resolver for Vault + * @param vaultCredentialsProvider Credential provider for acquiring a token for interacting with Vault + * @return Vault admin client + */ + public static VaultCryptoClient getCryptoClient(final UrlResolver vaultUrlResolver, + final VaultCredentialsProvider vaultCredentialsProvider) { + return getCryptoClient(vaultUrlResolver, + vaultCredentialsProvider, + DEFAULT_MAX_REQUESTS, + DEFAULT_MAX_REQUESTS, + DEFAULT_TIMEOUT, + DEFAULT_TIMEOUT, + DEFAULT_TIMEOUT, + DEFAULT_HEADERS + ); + } + + /** + * Factory method that allows for a user defined Vault URL resolver and credentials provider. + * + * @param vaultUrlResolver URL resolver for Vault + * @param vaultCredentialsProvider Credential provider for acquiring a token for interacting with Vault + * @param maxRequestsPerHost Max Requests per Host used by the dispatcher + * @return Vault admin client + */ + public static VaultCryptoClient getCryptoClient(final UrlResolver vaultUrlResolver, + final VaultCredentialsProvider vaultCredentialsProvider, + final int maxRequestsPerHost) { + return getCryptoClient(vaultUrlResolver, + vaultCredentialsProvider, + DEFAULT_MAX_REQUESTS, + maxRequestsPerHost, + DEFAULT_TIMEOUT, + DEFAULT_TIMEOUT, + DEFAULT_TIMEOUT, + DEFAULT_HEADERS); + } + + /** + * Factory method that allows a user to define default HTTP headers to be added to every HTTP request made from the + * VaultClient. The user can also define their Vault URL resolver and credentials provider. + * + * @param vaultUrlResolver URL resolver for Vault + * @param vaultCredentialsProvider Credential provider for acquiring a token for interacting with Vault + * @param defaultHeaders Map of default header names and values to add to every HTTP request + * @return Vault client + */ + public static VaultCryptoClient getCryptoClient(final UrlResolver vaultUrlResolver, + final VaultCredentialsProvider vaultCredentialsProvider, + final Map defaultHeaders) { + return getCryptoClient(vaultUrlResolver, + vaultCredentialsProvider, + DEFAULT_MAX_REQUESTS, + DEFAULT_MAX_REQUESTS, + DEFAULT_TIMEOUT, + DEFAULT_TIMEOUT, + DEFAULT_TIMEOUT, + defaultHeaders); + } + + /** + * Factory method that allows the user to completely configure the VaultClient. + * + * @param vaultUrlResolver URL resolver for Vault + * @param vaultCredentialsProvider Credential provider for acquiring a token for interacting with Vault + * @param maxRequestsPerHost Max Requests per Host used by the dispatcher + * @param defaultHeaders Map of default header names and values to add to every HTTP request + * @return Vault admin client + */ + public static VaultCryptoClient getCryptoClient(final UrlResolver vaultUrlResolver, + final VaultCredentialsProvider vaultCredentialsProvider, + final int maxRequestsPerHost, + final Map defaultHeaders) { + return getCryptoClient(vaultUrlResolver, + vaultCredentialsProvider, + DEFAULT_MAX_REQUESTS, + maxRequestsPerHost, + DEFAULT_TIMEOUT, + DEFAULT_TIMEOUT, + DEFAULT_TIMEOUT, + defaultHeaders); + } + + /** + * Factory method that allows the user to completely configure the VaultClient. + * + * @param vaultUrlResolver URL resolver for Vault + * @param vaultCredentialsProvider Credential provider for acquiring a token for interacting with Vault + * @param maxRequests Max HTTP Requests allowed in-flight + * @param maxRequestsPerHost Max HTTP Requests per Host + * @param connectTimeoutMillis HTTP connect timeout in milliseconds + * @param readTimeoutMillis HTTP read timeout in milliseconds + * @param writeTimeoutMillis HTTP write timeout in milliseconds + * @param defaultHeaders Map of default header names and values to add to every HTTP request + * @return Vault admin client + */ + public static VaultCryptoClient getCryptoClient(final UrlResolver vaultUrlResolver, + final VaultCredentialsProvider vaultCredentialsProvider, + final int maxRequests, + final int maxRequestsPerHost, + final int connectTimeoutMillis, + final int readTimeoutMillis, + final int writeTimeoutMillis, + final Map defaultHeaders) { + if (defaultHeaders == null) { + throw new IllegalArgumentException("Default headers cannot be null."); + } + + Dispatcher dispatcher = new Dispatcher(); + dispatcher.setMaxRequests(maxRequests); + dispatcher.setMaxRequestsPerHost(maxRequestsPerHost); + + + List connectionSpecs = new ArrayList<>(); + connectionSpecs.add(TLS_1_2_OR_NEWER); + // for unit tests + connectionSpecs.add(CLEARTEXT); + + Headers.Builder headers = new Headers.Builder(); + for (Map.Entry header : defaultHeaders.entrySet()) { + headers.add(header.getKey(), header.getValue()); + } + + return new VaultCryptoClient(vaultUrlResolver, + vaultCredentialsProvider, + new OkHttpClient.Builder() + .connectTimeout(connectTimeoutMillis, DEFAULT_TIMEOUT_UNIT) + .writeTimeout(writeTimeoutMillis, DEFAULT_TIMEOUT_UNIT) + .readTimeout(readTimeoutMillis, DEFAULT_TIMEOUT_UNIT) + .dispatcher(dispatcher) + .connectionSpecs(connectionSpecs) + .build(), + headers.build()); + } } diff --git a/src/main/java/com/nike/vault/client/VaultCryptoClient.java b/src/main/java/com/nike/vault/client/VaultCryptoClient.java new file mode 100644 index 0000000..485e88b --- /dev/null +++ b/src/main/java/com/nike/vault/client/VaultCryptoClient.java @@ -0,0 +1,99 @@ +package com.nike.vault.client; + +import static com.nike.vault.client.model.VaultCreateKeyRequest.TYPE_AES256_GCM96; + +import com.google.gson.reflect.TypeToken; +import com.nike.vault.client.auth.VaultCredentialsProvider; +import com.nike.vault.client.http.HttpMethod; +import com.nike.vault.client.http.HttpStatus; +import com.nike.vault.client.model.VaultAsymmetricKeyResponse; +import com.nike.vault.client.model.VaultCreateKeyRequest; +import com.nike.vault.client.model.VaultKeyResponse; +import com.nike.vault.client.model.VaultSymmetricKeyResponse; +import java.lang.reflect.Type; +import java.util.Map; +import javax.annotation.Nonnull; +import okhttp3.Headers; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Response; + +public class VaultCryptoClient extends VaultClient { + + private static final String TRANSIT_PATH_PREFIX = "v1/transit/"; + + /** + * Explicit constructor that allows for full control over construction of the Vault client. + * + * @param vaultUrlResolver URL resolver for Vault + * @param credentialsProvider Credential provider for acquiring a token for interacting with Vault + * @param httpClient HTTP client for calling Vault + */ + public VaultCryptoClient(final UrlResolver vaultUrlResolver, + final VaultCredentialsProvider credentialsProvider, + final OkHttpClient httpClient) { + super(vaultUrlResolver, credentialsProvider, httpClient); + } + + /** + * Explicit constructor that allows for full control over construction of the Vault client. + * + * @param vaultUrlResolver URL resolver for Vault + * @param credentialsProvider Credential provider for acquiring a token for interacting with Vault + * @param httpClient HTTP client for calling Vault + * @param defaultHeaders Default HTTP headers to be included in each request made by the returned VaultClient + */ + public VaultCryptoClient(final UrlResolver vaultUrlResolver, + final VaultCredentialsProvider credentialsProvider, + final OkHttpClient httpClient, + final Headers defaultHeaders) { + super(vaultUrlResolver, credentialsProvider, httpClient, defaultHeaders); + } + + /** + * Creates a new key. + * + * @param name Key name + * @param vaultCreateKeyRequest Request object with optional parameters + * @return Auth response with the token and details + */ + public void createKey(@Nonnull final String name, @Nonnull final VaultCreateKeyRequest vaultCreateKeyRequest) { + final HttpUrl url = buildUrl(TRANSIT_PATH_PREFIX, "keys/" + name); + final Response response = execute(url, HttpMethod.POST, vaultCreateKeyRequest); + + if (response.code() != HttpStatus.NO_CONTENT) { + parseAndThrowErrorResponse(response); + } + } + + /** + * Retrieve key information + * + * @param name Name of the key to lookup + * @return Key information response, can be either Symmetric or Asymmetric keys + */ + public VaultKeyResponse getKeyInfo(@Nonnull final String name) { + final HttpUrl url = buildUrl(TRANSIT_PATH_PREFIX, "keys/" + name); + final Response response = execute(url, HttpMethod.GET, null); + + if (response.code() != HttpStatus.OK) { + parseAndThrowErrorResponse(response); + } + + final Type mapType = new TypeToken>() { + }.getType(); + final Map rootData = parseResponseBody(response, mapType); + VaultKeyResponse keyResponse = getGson() + .fromJson(getGson().toJson(rootData.get("data")), VaultKeyResponse.class); + switch (keyResponse.getType()) { + case TYPE_AES256_GCM96: + return getGson() + .fromJson(getGson().toJson(rootData.get("data")), VaultSymmetricKeyResponse.class); + default: + return getGson() + .fromJson(getGson().toJson(rootData.get("data")), VaultAsymmetricKeyResponse.class); + } + + } + +} diff --git a/src/main/java/com/nike/vault/client/model/VaultAsymmetricKeyResponse.java b/src/main/java/com/nike/vault/client/model/VaultAsymmetricKeyResponse.java new file mode 100644 index 0000000..3d758cb --- /dev/null +++ b/src/main/java/com/nike/vault/client/model/VaultAsymmetricKeyResponse.java @@ -0,0 +1,94 @@ +package com.nike.vault.client.model; + +import com.google.gson.annotations.SerializedName; +import org.apache.commons.lang3.builder.ToStringBuilder; + +public class VaultAsymmetricKeyResponse extends VaultKeyResponse { + public class AsymmetricKeyInformation { + public class AsymmetricKey { + private String creationTime; + private String name; + private String publicKey; + + public String getCreationTime() { + return creationTime; + } + + public void setCreationTime(String creationTime) { + this.creationTime = creationTime; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("creationTime", creationTime) + .append("name", name) + .append("publicKey", publicKey) + .toString(); + } + } + + @SerializedName("1") + private AsymmetricKey keyData; + + public AsymmetricKey getKeyData() { + return keyData; + } + + public void setKeyData( + AsymmetricKey keyData) { + this.keyData = keyData; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("keyData", keyData) + .toString(); + } + } + private AsymmetricKeyInformation keys; + + public AsymmetricKeyInformation getKeys() { + return keys; + } + + public void setKeys(AsymmetricKeyInformation keys) { + this.keys = keys; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("keys", keys) + .append("deletionAllowed", deletionAllowed) + .append("derived", derived) + .append("exportable", exportable) + .append("latestVersion", latestVersion) + .append("minDecryptionVersion", minDecryptionVersion) + .append("minEncryptionVersion", minEncryptionVersion) + .append("name", name) + .append("supportsDecryption", supportsDecryption) + .append("supportsDerivation", supportsDerivation) + .append("supportsEncryption", supportsEncryption) + .append("supportsSigning", supportsSigning) + .append("type", type) + .toString(); + } +} diff --git a/src/main/java/com/nike/vault/client/model/VaultCreateKeyRequest.java b/src/main/java/com/nike/vault/client/model/VaultCreateKeyRequest.java new file mode 100644 index 0000000..64c7ef2 --- /dev/null +++ b/src/main/java/com/nike/vault/client/model/VaultCreateKeyRequest.java @@ -0,0 +1,87 @@ +package com.nike.vault.client.model; + +import com.google.gson.annotations.SerializedName; +import org.apache.commons.lang3.builder.ToStringBuilder; + +public class VaultCreateKeyRequest { + + /** + * AES-256 wrapped with GCM using a 12-byte nonce size (symmetric, supports derivation) + */ + public static final String TYPE_AES256_GCM96 = "aes256-gcm96"; + /** + * ECDSA using the P-256 elliptic curve (asymmetric) + */ + public static final String TYPE_ACDSA_P256 = "ecdsa-p256 "; + /** + * ED25519 (asymmetric, supports derivation) + */ + public static final String TYPE_ED25519 = "ed25519 "; + /** + * RSA with bit size of 2048 (asymmetric) + */ + public static final String TYPE_RSA_2048 = "rsa-2048 "; + /** + * RSA with bit size of 4096 (asymmetric) + */ + public static final String TYPE_RSA_4096 = "rsa-4096 "; + + private boolean convergentEncryption; + private boolean derived; + private boolean exportable; + private String type; + + /** + * If enabled, the key will support convergent encryption, where the same plaintext creates the + * same ciphertext. This requires derived to be set to true. When enabled, each + * encryption(/decryption/rewrap/datakey) operation will derive a nonce value rather than randomly + * generate it. Note that while this is useful for particular situations, all nonce values used + * with a given context value must be unique or it will compromise the security of your key, and + * the key space for nonces is 96 bit -- not as large as the AES key itself. + */ + public boolean isConvergentEncryption() { + return convergentEncryption; + } + + public void setConvergentEncryption(boolean convergentEncryption) { + this.convergentEncryption = convergentEncryption; + } + + /** + * Specifies if key derivation is to be used. If enabled, all encrypt/decrypt requests to this + * named key must provide a context which is used for key derivation. + */ + public boolean isDerived() { + return derived; + } + + public void setDerived(boolean derived) { + this.derived = derived; + } + + public boolean isExportable() { + return exportable; + } + + public void setExportable(boolean exportable) { + this.exportable = exportable; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("convergentEncryption", convergentEncryption) + .append("derived", derived) + .append("exportable", exportable) + .append("type", type) + .toString(); + } +} diff --git a/src/main/java/com/nike/vault/client/model/VaultKeyResponse.java b/src/main/java/com/nike/vault/client/model/VaultKeyResponse.java new file mode 100644 index 0000000..8f2eb69 --- /dev/null +++ b/src/main/java/com/nike/vault/client/model/VaultKeyResponse.java @@ -0,0 +1,133 @@ +package com.nike.vault.client.model; + +import com.google.gson.annotations.SerializedName; +import org.apache.commons.lang3.builder.ToStringBuilder; + +public class VaultKeyResponse { + protected boolean deletionAllowed; + protected boolean derived; + protected boolean exportable; + protected int latestVersion; + protected int minDecryptionVersion; + protected int minEncryptionVersion; + protected String name; + protected boolean supportsDecryption; + protected boolean supportsDerivation; + protected boolean supportsEncryption; + protected boolean supportsSigning; + protected String type; + + public boolean isDeletionAllowed() { + return deletionAllowed; + } + + public void setDeletionAllowed(boolean deletionAllowed) { + this.deletionAllowed = deletionAllowed; + } + + public boolean isDerived() { + return derived; + } + + public void setDerived(boolean derived) { + this.derived = derived; + } + + public boolean isExportable() { + return exportable; + } + + public void setExportable(boolean exportable) { + this.exportable = exportable; + } + + public int getLatestVersion() { + return latestVersion; + } + + public void setLatestVersion(int latestVersion) { + this.latestVersion = latestVersion; + } + + public int getMinDecryptionVersion() { + return minDecryptionVersion; + } + + public void setMinDecryptionVersion(int minDecryptionVersion) { + this.minDecryptionVersion = minDecryptionVersion; + } + + public int getMinEncryptionVersion() { + return minEncryptionVersion; + } + + public void setMinEncryptionVersion(int minEncryptionVersion) { + this.minEncryptionVersion = minEncryptionVersion; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public boolean isSupportsDecryption() { + return supportsDecryption; + } + + public void setSupportsDecryption(boolean supportsDecryption) { + this.supportsDecryption = supportsDecryption; + } + + public boolean isSupportsDerivation() { + return supportsDerivation; + } + + public void setSupportsDerivation(boolean supportsDerivation) { + this.supportsDerivation = supportsDerivation; + } + + public boolean isSupportsEncryption() { + return supportsEncryption; + } + + public void setSupportsEncryption(boolean supportsEncryption) { + this.supportsEncryption = supportsEncryption; + } + + public boolean isSupportsSigning() { + return supportsSigning; + } + + public void setSupportsSigning(boolean supportsSigning) { + this.supportsSigning = supportsSigning; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("deletionAllowed", deletionAllowed) + .append("derived", derived) + .append("exportable", exportable) + .append("latestVersion", latestVersion) + .append("minDecryptionVersion", minDecryptionVersion) + .append("minEncryptionVersion", minEncryptionVersion) + .append("name", name) + .append("supportsDecryption", supportsDecryption) + .append("supportsDerivation", supportsDerivation) + .append("supportsEncryption", supportsEncryption) + .append("supportsSigning", supportsSigning) + .append("type", type) + .toString(); + } +} diff --git a/src/main/java/com/nike/vault/client/model/VaultSymmetricKeyResponse.java b/src/main/java/com/nike/vault/client/model/VaultSymmetricKeyResponse.java new file mode 100644 index 0000000..a152393 --- /dev/null +++ b/src/main/java/com/nike/vault/client/model/VaultSymmetricKeyResponse.java @@ -0,0 +1,60 @@ +package com.nike.vault.client.model; + +import com.google.gson.annotations.SerializedName; +import org.apache.commons.lang3.builder.ToStringBuilder; + +public class VaultSymmetricKeyResponse extends VaultKeyResponse { + + public class SymmetricKeyInformation { + + @SerializedName("1") + private long keyData; + + public long getKeyData() { + return keyData; + } + + public void setKeyData( + long keyData) { + this.keyData = keyData; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("keyData", keyData) + .toString(); + } + } + + private SymmetricKeyInformation keys; + + public SymmetricKeyInformation getKeys() { + return keys; + } + + public void setKeys(SymmetricKeyInformation keys) { + this.keys = keys; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("keys", keys) + .append("deletionAllowed", deletionAllowed) + .append("derived", derived) + .append("exportable", exportable) + .append("latestVersion", latestVersion) + .append("minDecryptionVersion", minDecryptionVersion) + .append("minEncryptionVersion", minEncryptionVersion) + .append("name", name) + .append("supportsDecryption", supportsDecryption) + .append("supportsDerivation", supportsDerivation) + .append("supportsEncryption", supportsEncryption) + .append("supportsSigning", supportsSigning) + .append("type", type) + .toString(); + } + + +} diff --git a/src/test/java/com/nike/vault/client/VaultCryptoClientTest.java b/src/test/java/com/nike/vault/client/VaultCryptoClientTest.java new file mode 100644 index 0000000..70e6332 --- /dev/null +++ b/src/test/java/com/nike/vault/client/VaultCryptoClientTest.java @@ -0,0 +1,120 @@ +package com.nike.vault.client; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.nike.vault.client.auth.VaultCredentials; +import com.nike.vault.client.auth.VaultCredentialsProvider; +import com.nike.vault.client.http.HttpStatus; +import com.nike.vault.client.model.VaultAsymmetricKeyResponse; +import com.nike.vault.client.model.VaultCreateKeyRequest; +import com.nike.vault.client.model.VaultKeyResponse; +import com.nike.vault.client.model.VaultSymmetricKeyResponse; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import org.apache.commons.io.IOUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class VaultCryptoClientTest { + + private VaultCryptoClient vaultClient; + + private MockWebServer mockWebServer; + + @Before + public void setup() throws IOException { + mockWebServer = new MockWebServer(); + mockWebServer.start(); + final String vaultUrl = "http://localhost:" + mockWebServer.getPort(); + final VaultCredentialsProvider vaultCredentialsProvider = mock(VaultCredentialsProvider.class); + vaultClient = VaultClientFactory.getCryptoClient(new StaticVaultUrlResolver(vaultUrl), + vaultCredentialsProvider); + + when(vaultCredentialsProvider.getCredentials()).thenReturn(new TestVaultCredentials()); + } + + @After + public void teardown() throws IOException { + mockWebServer.shutdown(); + } + + @Test + public void create_key_returns_204_when_successful() { + final MockResponse response = new MockResponse(); + response.setResponseCode(HttpStatus.NO_CONTENT); + mockWebServer.enqueue(response); + + final VaultCreateKeyRequest createKeyRequest = new VaultCreateKeyRequest(); + createKeyRequest.setConvergentEncryption(true); + createKeyRequest.setDerived(true); + createKeyRequest.setExportable(true); + createKeyRequest.setType(VaultCreateKeyRequest.TYPE_ACDSA_P256); + + vaultClient.createKey("test-key", createKeyRequest); + + // Silence is success! + } + + @Test + public void query_key_information_symmetric_returns_ok_if_created() { + final MockResponse response = new MockResponse(); + response.setResponseCode(HttpStatus.OK); + response.setBody(getResponseJson("symmetric_key")); + mockWebServer.enqueue(response); + + VaultKeyResponse actualResponse = vaultClient.getKeyInfo("test-key"); + + assertThat(actualResponse).isNotNull(); + assertThat(actualResponse instanceof VaultSymmetricKeyResponse); + VaultSymmetricKeyResponse symmetricResponse = (VaultSymmetricKeyResponse) actualResponse; + assertThat(symmetricResponse.getKeys().getKeyData()).isEqualTo(1511100122l); + assertThat(symmetricResponse.getMinDecryptionVersion() == 1).isTrue(); + assertThat(symmetricResponse.isSupportsSigning()).isFalse(); + } + + @Test + public void query_key_information_asymmetric_returns_ok_if_created() { + final MockResponse response = new MockResponse(); + response.setResponseCode(HttpStatus.OK); + response.setBody(getResponseJson("asymmetric_key")); + mockWebServer.enqueue(response); + + VaultKeyResponse actualResponse = vaultClient.getKeyInfo("test-key"); + + assertThat(actualResponse).isNotNull(); + assertThat(actualResponse instanceof VaultAsymmetricKeyResponse); + VaultAsymmetricKeyResponse asymmetricResponse = (VaultAsymmetricKeyResponse) actualResponse; + assertThat(asymmetricResponse.getKeys().getKeyData().getName().equalsIgnoreCase("rsa-4096")); + assertThat(asymmetricResponse.getKeys().getKeyData().getCreationTime() + .equalsIgnoreCase("2017-11-19T20:20:15.854167+08:00")); + assertThat(asymmetricResponse.getMinDecryptionVersion() == 1).isTrue(); + assertThat(asymmetricResponse.isSupportsSigning()).isTrue(); + } + + private String getResponseJson(final String title) { + InputStream inputStream = getClass().getResourceAsStream( + String.format("/com/nike/vault/client/%s.json", title)); + try { + return IOUtils.toString(inputStream, Charset.forName("UTF-8")); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + IOUtils.closeQuietly(inputStream); + } + } + + private static class TestVaultCredentials implements VaultCredentials { + + @Override + public String getToken() { + return "TOKEN"; + } + } + +} diff --git a/src/test/resources/com/nike/vault/client/asymmetric_key.json b/src/test/resources/com/nike/vault/client/asymmetric_key.json new file mode 100644 index 0000000..ee3eba9 --- /dev/null +++ b/src/test/resources/com/nike/vault/client/asymmetric_key.json @@ -0,0 +1 @@ +{"request_id":"d607de77-58f3-9874-656d-46c620568d73","lease_id":"","renewable":false,"lease_duration":0,"data":{"deletion_allowed":false,"derived":false,"exportable":true,"keys":{"1":{"creation_time":"2017-11-19T20:20:15.854167+08:00","name":"rsa-4096","public_key":"-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvFIMubJbyTOb227rO+l7\nmNDLLJalrjq6X2DKGsa0l4wq4k7ONyvbNogTSPDobmh2unJHuB1uvJR8BslwsR2X\nTpGYUdNcA/9ZY+racwtJy2pQEfev+fBjv8HpZiw22COL/SHZ/vh7UWoGYvMeQJ7o\nUOiZvAsNfBu2vu6HCjnoNXLuva7qHTGEPm+TOWFtHbLq8kt+R7y+Feh6d0PhdBdj\neMzJadjX6rPkPC1fyWNPhR9KIEW/uX8V141BN+Vc4WC5SHMKN/Ef84YC2UzE8Bsc\nG8mvCLAX7c4lRJUFlaeyPo/LAVW51S/hLb84WrhDPemrpk4mQY2VnRmcLyg3VK62\nD17tLlhZxnnEligHZHj6QBZtIXZASeMSaXCpBWwN96glwGp9AFFRRzXfzqLOfoCO\nqaoyPoLZD0t6sSyCQCO+619xFi7yoVbvHdw92N133Zbc6eRfwPs4SeQd995ytJBo\nb3+W7zhXRzzrUpo+wUxBxcz249Fb054JA0b3dk5k4IDPYLTdwg2GF/n0+9cj74+a\n+HQe7JQIFSrx8oEw4+X7l0ZOIm9p21WVkjcJMlkX5Ba/8cFpF2TXsQS4bSvDrq/a\nBkdlN5oaA+JqH4TCq0bjdg3M2dZ/Qv0MtLrxXdJbPixFRSa/9/O7W5+fURghd/Rs\nMYsOFFthKdg06JZLm8Nb/VMCAwEAAQ==\n-----END PUBLIC KEY-----\n"}},"latest_version":1,"min_decryption_version":1,"min_encryption_version":0,"name":"test-key-2","supports_decryption":true,"supports_derivation":false,"supports_encryption":true,"supports_signing":true,"type":"rsa-4096"},"wrap_info":null,"warnings":null,"auth":null} \ No newline at end of file diff --git a/src/test/resources/com/nike/vault/client/symmetric_key.json b/src/test/resources/com/nike/vault/client/symmetric_key.json new file mode 100644 index 0000000..468ef1b --- /dev/null +++ b/src/test/resources/com/nike/vault/client/symmetric_key.json @@ -0,0 +1 @@ +{"request_id":"edbef751-178a-6393-1a6d-ece5d06f7d59","lease_id":"","renewable":false,"lease_duration":0,"data":{"deletion_allowed":false,"derived":false,"exportable":true,"keys":{"1":1511100122},"latest_version":1,"min_decryption_version":1,"min_encryption_version":0,"name":"test-key-3","supports_decryption":true,"supports_derivation":true,"supports_encryption":true,"supports_signing":false,"type":"aes256-gcm96"},"wrap_info":null,"warnings":null,"auth":null} \ No newline at end of file From 72d83c1e45e3ee5d2ca940c8cb6be50ff610846f Mon Sep 17 00:00:00 2001 From: Dima Gutzeit <11374909+innovationhub-asia@users.noreply.github.com> Date: Sun, 3 Dec 2017 14:28:30 +0800 Subject: [PATCH 02/10] Support for more methods - Add support for encryption using named keys - Add support for decryption using named keys - Refactoring, code duplication removal --- .../nike/vault/client/VaultAdminClient.java | 5 +- .../com/nike/vault/client/VaultClient.java | 18 ++++--- .../nike/vault/client/VaultCryptoClient.java | 40 ++++++++++++++ .../client/model/VaultDecryptDataRequest.java | 40 ++++++++++++++ .../model/VaultDecryptDataResponse.java | 22 ++++++++ .../client/model/VaultEncryptDataRequest.java | 54 +++++++++++++++++++ .../model/VaultEncryptDataResponse.java | 22 ++++++++ .../vault/client/VaultCryptoClientTest.java | 36 +++++++++++++ .../com/nike/vault/client/decrypt.json | 1 + .../com/nike/vault/client/encrypt.json | 1 + 10 files changed, 227 insertions(+), 12 deletions(-) create mode 100644 src/main/java/com/nike/vault/client/model/VaultDecryptDataRequest.java create mode 100644 src/main/java/com/nike/vault/client/model/VaultDecryptDataResponse.java create mode 100644 src/main/java/com/nike/vault/client/model/VaultEncryptDataRequest.java create mode 100644 src/main/java/com/nike/vault/client/model/VaultEncryptDataResponse.java create mode 100644 src/test/resources/com/nike/vault/client/decrypt.json create mode 100644 src/test/resources/com/nike/vault/client/encrypt.json diff --git a/src/main/java/com/nike/vault/client/VaultAdminClient.java b/src/main/java/com/nike/vault/client/VaultAdminClient.java index e970a32..23447b0 100644 --- a/src/main/java/com/nike/vault/client/VaultAdminClient.java +++ b/src/main/java/com/nike/vault/client/VaultAdminClient.java @@ -302,10 +302,7 @@ public VaultClientTokenResponse lookupToken(final String token) { parseAndThrowErrorResponse(response); } - final Type mapType = new TypeToken>() { - }.getType(); - final Map rootData = parseResponseBody(response, mapType); - return getGson().fromJson(getGson().toJson(rootData.get("data")), VaultClientTokenResponse.class); + return parseResponse(response, VaultClientTokenResponse.class); } /** diff --git a/src/main/java/com/nike/vault/client/VaultClient.java b/src/main/java/com/nike/vault/client/VaultClient.java index f4f935e..c9383cb 100644 --- a/src/main/java/com/nike/vault/client/VaultClient.java +++ b/src/main/java/com/nike/vault/client/VaultClient.java @@ -152,10 +152,7 @@ public VaultListResponse list(final String path) { parseAndThrowErrorResponse(response); } - final Type mapType = new TypeToken>() { - }.getType(); - final Map rootData = parseResponseBody(response, mapType); - return gson.fromJson(gson.toJson(rootData.get("data")), VaultListResponse.class); + return parseResponse(response, VaultListResponse.class); } /** @@ -234,10 +231,7 @@ public VaultClientTokenResponse lookupSelf() { parseAndThrowErrorResponse(response); } - final Type mapType = new TypeToken>() { - }.getType(); - final Map rootData = parseResponseBody(response, mapType); - return gson.fromJson(gson.toJson(rootData.get("data")), VaultClientTokenResponse.class); + return parseResponse(response, VaultClientTokenResponse.class); } /** @@ -405,6 +399,14 @@ protected void parseAndThrowErrorResponse(final Response response) { } } + protected T parseResponse(Response response, Class responseType) { + final Type mapType = new TypeToken>() { + }.getType(); + final Map rootData = parseResponseBody(response, mapType); + return getGson() + .fromJson(getGson().toJson(rootData.get("data")), responseType); + } + /** * POJO for representing error response body from Vault. */ diff --git a/src/main/java/com/nike/vault/client/VaultCryptoClient.java b/src/main/java/com/nike/vault/client/VaultCryptoClient.java index 485e88b..b592ebc 100644 --- a/src/main/java/com/nike/vault/client/VaultCryptoClient.java +++ b/src/main/java/com/nike/vault/client/VaultCryptoClient.java @@ -8,6 +8,10 @@ import com.nike.vault.client.http.HttpStatus; import com.nike.vault.client.model.VaultAsymmetricKeyResponse; import com.nike.vault.client.model.VaultCreateKeyRequest; +import com.nike.vault.client.model.VaultDecryptDataRequest; +import com.nike.vault.client.model.VaultDecryptDataResponse; +import com.nike.vault.client.model.VaultEncryptDataRequest; +import com.nike.vault.client.model.VaultEncryptDataResponse; import com.nike.vault.client.model.VaultKeyResponse; import com.nike.vault.client.model.VaultSymmetricKeyResponse; import java.lang.reflect.Type; @@ -96,4 +100,40 @@ public VaultKeyResponse getKeyInfo(@Nonnull final String name) { } + /** + * Encrypt data with a key stored in Vault + * @param keyName Name of the key to use for the cryptographic operation + * @param encryptRequest Encryption data + * @return Encryption response + */ + @Nonnull + public VaultEncryptDataResponse encrypt(@Nonnull String keyName, @Nonnull VaultEncryptDataRequest encryptRequest) { + final HttpUrl url = buildUrl(TRANSIT_PATH_PREFIX, "encrypt/" + keyName); + final Response response = execute(url, HttpMethod.POST, encryptRequest); + + if (response.code() != HttpStatus.OK) { + parseAndThrowErrorResponse(response); + } + + return parseResponse(response, VaultEncryptDataResponse.class); + } + + /** + * Decrypt data with a key stored in Vault + * @param keyName Name of the key to use for the cryptographic operation + * @param decryptRequest Decryption data + * @return Decryption response + */ + @Nonnull + public VaultDecryptDataResponse decrypt(@Nonnull String keyName, @Nonnull VaultDecryptDataRequest decryptRequest) { + final HttpUrl url = buildUrl(TRANSIT_PATH_PREFIX, "decrypt/" + keyName); + final Response response = execute(url, HttpMethod.POST, decryptRequest); + + if (response.code() != HttpStatus.OK) { + parseAndThrowErrorResponse(response); + } + + return parseResponse(response, VaultDecryptDataResponse.class); + } + } diff --git a/src/main/java/com/nike/vault/client/model/VaultDecryptDataRequest.java b/src/main/java/com/nike/vault/client/model/VaultDecryptDataRequest.java new file mode 100644 index 0000000..a2d528c --- /dev/null +++ b/src/main/java/com/nike/vault/client/model/VaultDecryptDataRequest.java @@ -0,0 +1,40 @@ +package com.nike.vault.client.model; + +import org.apache.commons.lang3.builder.ToStringBuilder; + +public class VaultDecryptDataRequest { + + private String ciphertext; + private String context; + + /** + * Specifies the ciphertext to decrypt. + */ + public String getCiphertext() { + return ciphertext; + } + + public void setCiphertext(String ciphertext) { + this.ciphertext = ciphertext; + } + + /** + * Specifies the base64 encoded context for key derivation. This is required if key derivation is + * enabled. + */ + public String getContext() { + return context; + } + + public void setContext(String context) { + this.context = context; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("ciphertext", ciphertext) + .append("context", context) + .toString(); + } +} diff --git a/src/main/java/com/nike/vault/client/model/VaultDecryptDataResponse.java b/src/main/java/com/nike/vault/client/model/VaultDecryptDataResponse.java new file mode 100644 index 0000000..20a46fc --- /dev/null +++ b/src/main/java/com/nike/vault/client/model/VaultDecryptDataResponse.java @@ -0,0 +1,22 @@ +package com.nike.vault.client.model; + +import org.apache.commons.lang3.builder.ToStringBuilder; + +public class VaultDecryptDataResponse { + private String plaintext; + + public String getPlaintext() { + return plaintext; + } + + public void setPlaintext(String plaintext) { + this.plaintext = plaintext; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("plaintext", plaintext) + .toString(); + } +} diff --git a/src/main/java/com/nike/vault/client/model/VaultEncryptDataRequest.java b/src/main/java/com/nike/vault/client/model/VaultEncryptDataRequest.java new file mode 100644 index 0000000..3db6620 --- /dev/null +++ b/src/main/java/com/nike/vault/client/model/VaultEncryptDataRequest.java @@ -0,0 +1,54 @@ +package com.nike.vault.client.model; + +import org.apache.commons.lang3.builder.ToStringBuilder; + +public class VaultEncryptDataRequest { + + private String plaintext; + private String context; + private int keyVersion; + + /** + * Specifies base64 encoded plaintext to be encoded. + */ + public String getPlaintext() { + return plaintext; + } + + public void setPlaintext(String plaintext) { + this.plaintext = plaintext; + } + + /** + * Specifies the base64 encoded context for key derivation. This is required if key derivation is + * enabled. + */ + public String getContext() { + return context; + } + + public void setContext(String context) { + this.context = context; + } + + /** + * Specifies the version of the key to use for encryption. If not set, uses the latest version. + * Must be greater than or equal to the key's min_encryption_version, if set. + */ + public int getKeyVersion() { + return keyVersion; + } + + public void setKeyVersion(int keyVersion) { + this.keyVersion = keyVersion; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("plaintext", plaintext) + .append("context", context) + .append("keyVersion", keyVersion) + .toString(); + } +} diff --git a/src/main/java/com/nike/vault/client/model/VaultEncryptDataResponse.java b/src/main/java/com/nike/vault/client/model/VaultEncryptDataResponse.java new file mode 100644 index 0000000..f4d7169 --- /dev/null +++ b/src/main/java/com/nike/vault/client/model/VaultEncryptDataResponse.java @@ -0,0 +1,22 @@ +package com.nike.vault.client.model; + +import org.apache.commons.lang3.builder.ToStringBuilder; + +public class VaultEncryptDataResponse { + private String ciphertext; + + public String getCiphertext() { + return ciphertext; + } + + public void setCiphertext(String ciphertext) { + this.ciphertext = ciphertext; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("ciphertext", ciphertext) + .toString(); + } +} diff --git a/src/test/java/com/nike/vault/client/VaultCryptoClientTest.java b/src/test/java/com/nike/vault/client/VaultCryptoClientTest.java index 70e6332..9b00743 100644 --- a/src/test/java/com/nike/vault/client/VaultCryptoClientTest.java +++ b/src/test/java/com/nike/vault/client/VaultCryptoClientTest.java @@ -9,6 +9,10 @@ import com.nike.vault.client.http.HttpStatus; import com.nike.vault.client.model.VaultAsymmetricKeyResponse; import com.nike.vault.client.model.VaultCreateKeyRequest; +import com.nike.vault.client.model.VaultDecryptDataRequest; +import com.nike.vault.client.model.VaultDecryptDataResponse; +import com.nike.vault.client.model.VaultEncryptDataRequest; +import com.nike.vault.client.model.VaultEncryptDataResponse; import com.nike.vault.client.model.VaultKeyResponse; import com.nike.vault.client.model.VaultSymmetricKeyResponse; import java.io.IOException; @@ -97,6 +101,38 @@ public void query_key_information_asymmetric_returns_ok_if_created() { assertThat(asymmetricResponse.isSupportsSigning()).isTrue(); } + @Test + public void encrypt_returns_ok_if_created() { + final MockResponse response = new MockResponse(); + response.setResponseCode(HttpStatus.OK); + response.setBody(getResponseJson("encrypt")); + mockWebServer.enqueue(response); + + VaultEncryptDataRequest request = new VaultEncryptDataRequest(); + request.setPlaintext("QWxsIHdlIGFyZSBidXQgc3BlY2tzIGluIGEgaHVnZSB1bml2ZXJzZSAuLi4="); + + VaultEncryptDataResponse actualResponse = vaultClient.encrypt("test-key", request); + + assertThat(actualResponse).isNotNull(); + assertThat(actualResponse.getCiphertext()).isEqualTo("vault:v1:ZqkozlOaopGR4jf+/OhVS1iHyczM19Ax6ku1VJq4il7X7Wrqqc21dqKN8MTiOV4Iku8784X12NvOIfQcSvIWV2mW44q0HMlG"); + } + + @Test + public void decrypt_returns_ok_if_created() { + final MockResponse response = new MockResponse(); + response.setResponseCode(HttpStatus.OK); + response.setBody(getResponseJson("decrypt")); + mockWebServer.enqueue(response); + + VaultDecryptDataRequest request = new VaultDecryptDataRequest(); + request.setCiphertext("vault:v1:ZqkozlOaopGR4jf+/OhVS1iHyczM19Ax6ku1VJq4il7X7Wrqqc21dqKN8MTiOV4Iku8784X12NvOIfQcSvIWV2mW44q0HMlG"); + + VaultDecryptDataResponse actualResponse = vaultClient.decrypt("test-key", request); + + assertThat(actualResponse).isNotNull(); + assertThat(actualResponse.getPlaintext()).isEqualTo("QWxsIHdlIGFyZSBidXQgc3BlY2tzIGluIGEgaHVnZSB1bml2ZXJzZSAuLi4="); + } + private String getResponseJson(final String title) { InputStream inputStream = getClass().getResourceAsStream( String.format("/com/nike/vault/client/%s.json", title)); diff --git a/src/test/resources/com/nike/vault/client/decrypt.json b/src/test/resources/com/nike/vault/client/decrypt.json new file mode 100644 index 0000000..0f59e81 --- /dev/null +++ b/src/test/resources/com/nike/vault/client/decrypt.json @@ -0,0 +1 @@ +{"request_id":"2e0ebbd5-cdb5-3e65-6e7e-e1e55b3e9345","lease_id":"","renewable":false,"lease_duration":0,"data":{"plaintext":"QWxsIHdlIGFyZSBidXQgc3BlY2tzIGluIGEgaHVnZSB1bml2ZXJzZSAuLi4="},"wrap_info":null,"warnings":null,"auth":null} \ No newline at end of file diff --git a/src/test/resources/com/nike/vault/client/encrypt.json b/src/test/resources/com/nike/vault/client/encrypt.json new file mode 100644 index 0000000..de36cf2 --- /dev/null +++ b/src/test/resources/com/nike/vault/client/encrypt.json @@ -0,0 +1 @@ +{"request_id":"5ccc2bde-882a-583f-56ce-dd6d7afbd314","lease_id":"","renewable":false,"lease_duration":0,"data":{"ciphertext":"vault:v1:ZqkozlOaopGR4jf+/OhVS1iHyczM19Ax6ku1VJq4il7X7Wrqqc21dqKN8MTiOV4Iku8784X12NvOIfQcSvIWV2mW44q0HMlG"},"wrap_info":null,"warnings":null,"auth":null} \ No newline at end of file From 941b67e7617c91cc348ca4a7e4fb6b960ee87376 Mon Sep 17 00:00:00 2001 From: Dima Gutzeit <11374909+innovationhub-asia@users.noreply.github.com> Date: Sun, 3 Dec 2017 21:10:39 +0800 Subject: [PATCH 03/10] Fixed javadoc violations --- .circleci/config.yml | 43 +++++++++++++++++++ gradle.properties | 2 +- gradle/dependencies.gradle | 2 +- .../nike/vault/client/VaultCryptoClient.java | 1 - .../client/model/VaultCreateKeyRequest.java | 4 ++ .../client/model/VaultDecryptDataRequest.java | 5 +-- .../client/model/VaultEncryptDataRequest.java | 6 +-- 7 files changed, 54 insertions(+), 9 deletions(-) create mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..04cd4a6 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,43 @@ +# Java Gradle CircleCI 2.0 configuration file +# +# Check https://circleci.com/docs/2.0/language-java/ for more details +# +version: 2 +jobs: + build: + docker: + # specify the version you desire here + - image: circleci/openjdk:8-jdk + + # Specify service dependencies here if necessary + # CircleCI maintains a library of pre-built images + # documented at https://circleci.com/docs/2.0/circleci-images/ + # - image: circleci/postgres:9.4 + + working_directory: ~/repo + + environment: + # Customize the JVM maximum heap limit + JVM_OPTS: -Xmx3200m + TERM: dumb + + steps: + - checkout + + # Download and cache dependencies + - restore_cache: + keys: + - v1-dependencies-{{ checksum "build.gradle" }} + # fallback to using the latest cache if no exact match is found + - v1-dependencies- + + - run: gradle dependencies + + - save_cache: + paths: + - ~/.m2 + key: v1-dependencies-{{ checksum "build.gradle" }} + + # run tests! + - run: gradle test + diff --git a/gradle.properties b/gradle.properties index 62042c2..782e941 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,6 +14,6 @@ # limitations under the License. # -version=2.0.1 +version=2.0.2 groupId=com.nike artifactId=vault-client diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index b8f8e5d..9db9cc9 100644 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -22,7 +22,7 @@ dependencies { compile "org.apache.commons:commons-lang3:3.4" compile "com.squareup.okhttp3:okhttp:3.9.1" compile "com.google.code.gson:gson:2.5" - compile "com.google.code.findbugs:jsr305:3.0.1" + compile "com.google.code.findbugs:jsr305:3.0.2" compile "org.slf4j:slf4j-api:1.7.25" testCompile "junit:junit:4.12" diff --git a/src/main/java/com/nike/vault/client/VaultCryptoClient.java b/src/main/java/com/nike/vault/client/VaultCryptoClient.java index b592ebc..e3a9443 100644 --- a/src/main/java/com/nike/vault/client/VaultCryptoClient.java +++ b/src/main/java/com/nike/vault/client/VaultCryptoClient.java @@ -59,7 +59,6 @@ public VaultCryptoClient(final UrlResolver vaultUrlResolver, * * @param name Key name * @param vaultCreateKeyRequest Request object with optional parameters - * @return Auth response with the token and details */ public void createKey(@Nonnull final String name, @Nonnull final VaultCreateKeyRequest vaultCreateKeyRequest) { final HttpUrl url = buildUrl(TRANSIT_PATH_PREFIX, "keys/" + name); diff --git a/src/main/java/com/nike/vault/client/model/VaultCreateKeyRequest.java b/src/main/java/com/nike/vault/client/model/VaultCreateKeyRequest.java index 64c7ef2..1dbd7df 100644 --- a/src/main/java/com/nike/vault/client/model/VaultCreateKeyRequest.java +++ b/src/main/java/com/nike/vault/client/model/VaultCreateKeyRequest.java @@ -38,6 +38,8 @@ public class VaultCreateKeyRequest { * generate it. Note that while this is useful for particular situations, all nonce values used * with a given context value must be unique or it will compromise the security of your key, and * the key space for nonces is 96 bit -- not as large as the AES key itself. + * + * @return convergent flag */ public boolean isConvergentEncryption() { return convergentEncryption; @@ -50,6 +52,8 @@ public void setConvergentEncryption(boolean convergentEncryption) { /** * Specifies if key derivation is to be used. If enabled, all encrypt/decrypt requests to this * named key must provide a context which is used for key derivation. + * + * @return Derriviation flag */ public boolean isDerived() { return derived; diff --git a/src/main/java/com/nike/vault/client/model/VaultDecryptDataRequest.java b/src/main/java/com/nike/vault/client/model/VaultDecryptDataRequest.java index a2d528c..0bf1da5 100644 --- a/src/main/java/com/nike/vault/client/model/VaultDecryptDataRequest.java +++ b/src/main/java/com/nike/vault/client/model/VaultDecryptDataRequest.java @@ -8,7 +8,7 @@ public class VaultDecryptDataRequest { private String context; /** - * Specifies the ciphertext to decrypt. + * @return Ciphertext to decrypt. */ public String getCiphertext() { return ciphertext; @@ -19,8 +19,7 @@ public void setCiphertext(String ciphertext) { } /** - * Specifies the base64 encoded context for key derivation. This is required if key derivation is - * enabled. + * @return Base64 encoded context for key derivation. This is required if key derivation is enabled. */ public String getContext() { return context; diff --git a/src/main/java/com/nike/vault/client/model/VaultEncryptDataRequest.java b/src/main/java/com/nike/vault/client/model/VaultEncryptDataRequest.java index 3db6620..62813e6 100644 --- a/src/main/java/com/nike/vault/client/model/VaultEncryptDataRequest.java +++ b/src/main/java/com/nike/vault/client/model/VaultEncryptDataRequest.java @@ -9,7 +9,7 @@ public class VaultEncryptDataRequest { private int keyVersion; /** - * Specifies base64 encoded plaintext to be encoded. + * @return Base64 encoded plaintext to be encoded. */ public String getPlaintext() { return plaintext; @@ -20,7 +20,7 @@ public void setPlaintext(String plaintext) { } /** - * Specifies the base64 encoded context for key derivation. This is required if key derivation is + * @return Base64 encoded context for key derivation. This is required if key derivation is * enabled. */ public String getContext() { @@ -32,7 +32,7 @@ public void setContext(String context) { } /** - * Specifies the version of the key to use for encryption. If not set, uses the latest version. + * @return Version of the key to use for encryption. If not set, uses the latest version. * Must be greater than or equal to the key's min_encryption_version, if set. */ public int getKeyVersion() { From 3590b7439a1a9c93c2b7a37d58a512fdbf86ed2e Mon Sep 17 00:00:00 2001 From: nguquen Date: Mon, 4 Dec 2017 12:04:54 +0700 Subject: [PATCH 04/10] gradle: publish to github --- build.gradle | 23 +++++++++++++++++++++-- gradle/buildscript.gradle | 1 + 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 52e99a9..a0d553e 100644 --- a/build.gradle +++ b/build.gradle @@ -20,13 +20,33 @@ buildscript { apply plugin: 'java' apply plugin: 'maven' -apply plugin: 'com.jfrog.bintray' apply plugin: 'maven-publish' apply plugin: 'idea' +apply plugin: 'git-repo' sourceCompatibility = 1.8 targetCompatibility = 1.8 +gitPublishConfig { + org = "LeapXpert" + repo = "lxp-artifacts" + branch = "master" +} + +publishing { + publications { + mavenJava(MavenPublication) { + from components.java + } + } + + repositories { + maven { + url "file://${gitPublishConfig.home}/${gitPublishConfig.org}/${gitPublishConfig.repo}/releases" + } + } +} + task copyProjectVersion() { def releaseVersion = version doLast { @@ -38,6 +58,5 @@ tasks.jar.dependsOn copyProjectVersion apply from: file('gradle/dependencies.gradle') apply from: file('gradle/check.gradle') apply from: file('gradle/integration.gradle') -apply from: file('gradle/bintray.gradle') group = groupId diff --git a/gradle/buildscript.gradle b/gradle/buildscript.gradle index 2b57116..57b01b5 100644 --- a/gradle/buildscript.gradle +++ b/gradle/buildscript.gradle @@ -22,4 +22,5 @@ dependencies { classpath "net.saliman:gradle-cobertura-plugin:2.3.0" classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.6' classpath 'org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.6.3' + classpath group: 'com.layer', name: 'gradle-git-repo-plugin', version: '2.0.2' } From a3af7e4f13dd488c12b50a7b143c976067feb466 Mon Sep 17 00:00:00 2001 From: nguquen Date: Mon, 4 Dec 2017 17:55:25 +0700 Subject: [PATCH 05/10] CircleCI: config to publish artifact on staging branch --- .circleci/config.yml | 75 ++++++++++++++++++++++++-------------------- build.gradle | 11 +++++++ 2 files changed, 52 insertions(+), 34 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 04cd4a6..577c9bf 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,43 +1,50 @@ -# Java Gradle CircleCI 2.0 configuration file -# -# Check https://circleci.com/docs/2.0/language-java/ for more details -# +cached_key: &cached_key gradle-cached-{{ checksum "build.gradle" }} + +default_job: &default_job + docker: + - image: circleci/openjdk:8-jdk + version: 2 jobs: build: - docker: - # specify the version you desire here - - image: circleci/openjdk:8-jdk - - # Specify service dependencies here if necessary - # CircleCI maintains a library of pre-built images - # documented at https://circleci.com/docs/2.0/circleci-images/ - # - image: circleci/postgres:9.4 - - working_directory: ~/repo - - environment: - # Customize the JVM maximum heap limit - JVM_OPTS: -Xmx3200m - TERM: dumb - + <<: *default_job steps: - checkout - - # Download and cache dependencies - restore_cache: - keys: - - v1-dependencies-{{ checksum "build.gradle" }} - # fallback to using the latest cache if no exact match is found - - v1-dependencies- - - - run: gradle dependencies - + key: *cached_key + - run: ./gradlew resolveAllDependencies - save_cache: + key: *cached_key paths: - - ~/.m2 - key: v1-dependencies-{{ checksum "build.gradle" }} - - # run tests! - - run: gradle test + - ~/.gradle + test: + <<: *default_job + steps: + - checkout + - restore_cache: + key: *cached_key + - run: ./gradlew test + deploy: + <<: *default_job + steps: + - checkout + - restore_cache: + key: *cached_key + - run: ./gradlew publishToGithub +# workflows +workflows: + version: 2 + build-test-deploy: + jobs: + - build + - test: + requires: + - build + - deploy: + requires: + - test + filters: + branches: + only: + - staging diff --git a/build.gradle b/build.gradle index a0d553e..eed6b97 100644 --- a/build.gradle +++ b/build.gradle @@ -53,6 +53,17 @@ task copyProjectVersion() { ant.replace(file: "$buildDir/resources/main/java-vault-client.properties", token: "@@VAULT_CLIENT_RELEASE@@", value: releaseVersion) } } + +task resolveAllDependencies { + doLast { + configurations.all { + if (it.canBeResolved) { + it.resolve() + } + } + } +} + tasks.jar.dependsOn copyProjectVersion apply from: file('gradle/dependencies.gradle') From ef976eadf9c9df4fe7cc909609cc909b9a6638bb Mon Sep 17 00:00:00 2001 From: Dima Gutzeit <11374909+innovationhub-asia@users.noreply.github.com> Date: Mon, 11 Dec 2017 22:25:28 +0800 Subject: [PATCH 06/10] Removing errouneous spaces --- .../nike/vault/client/model/VaultCreateKeyRequest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/nike/vault/client/model/VaultCreateKeyRequest.java b/src/main/java/com/nike/vault/client/model/VaultCreateKeyRequest.java index 1dbd7df..96dc79c 100644 --- a/src/main/java/com/nike/vault/client/model/VaultCreateKeyRequest.java +++ b/src/main/java/com/nike/vault/client/model/VaultCreateKeyRequest.java @@ -12,19 +12,19 @@ public class VaultCreateKeyRequest { /** * ECDSA using the P-256 elliptic curve (asymmetric) */ - public static final String TYPE_ACDSA_P256 = "ecdsa-p256 "; + public static final String TYPE_ACDSA_P256 = "ecdsa-p256"; /** * ED25519 (asymmetric, supports derivation) */ - public static final String TYPE_ED25519 = "ed25519 "; + public static final String TYPE_ED25519 = "ed25519"; /** * RSA with bit size of 2048 (asymmetric) */ - public static final String TYPE_RSA_2048 = "rsa-2048 "; + public static final String TYPE_RSA_2048 = "rsa-2048"; /** * RSA with bit size of 4096 (asymmetric) */ - public static final String TYPE_RSA_4096 = "rsa-4096 "; + public static final String TYPE_RSA_4096 = "rsa-4096"; private boolean convergentEncryption; private boolean derived; From fe5f875c67065f7c78d12f138549cbdf00a75e63 Mon Sep 17 00:00:00 2001 From: nguquen Date: Tue, 12 Dec 2017 09:08:26 +0700 Subject: [PATCH 07/10] Publish artifact when commit to master branch --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 577c9bf..8a3e6a9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -48,3 +48,4 @@ workflows: branches: only: - staging + - master From f04ecff6e6ca672825ce2934cc7ff29b8cb4298e Mon Sep 17 00:00:00 2001 From: Dima Gutzeit Date: Thu, 28 Dec 2017 18:09:31 +0800 Subject: [PATCH 08/10] Implemented a method to export keys - New method implemented - Test coverage remains at high levels Jira: LEB-17 --- .../nike/vault/client/VaultCryptoClient.java | 24 +++++++ .../client/model/VaultKeyExportResponse.java | 68 +++++++++++++++++++ .../vault/client/model/VaultKeyResponse.java | 1 - .../vault/client/VaultCryptoClientTest.java | 29 ++++++++ .../nike/vault/client/asymmetric_export.json | 1 + .../nike/vault/client/symmetric_export.json | 1 + 6 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/nike/vault/client/model/VaultKeyExportResponse.java create mode 100644 src/test/resources/com/nike/vault/client/asymmetric_export.json create mode 100644 src/test/resources/com/nike/vault/client/symmetric_export.json diff --git a/src/main/java/com/nike/vault/client/VaultCryptoClient.java b/src/main/java/com/nike/vault/client/VaultCryptoClient.java index e3a9443..6594772 100644 --- a/src/main/java/com/nike/vault/client/VaultCryptoClient.java +++ b/src/main/java/com/nike/vault/client/VaultCryptoClient.java @@ -12,6 +12,7 @@ import com.nike.vault.client.model.VaultDecryptDataResponse; import com.nike.vault.client.model.VaultEncryptDataRequest; import com.nike.vault.client.model.VaultEncryptDataResponse; +import com.nike.vault.client.model.VaultKeyExportResponse; import com.nike.vault.client.model.VaultKeyResponse; import com.nike.vault.client.model.VaultSymmetricKeyResponse; import java.lang.reflect.Type; @@ -26,6 +27,10 @@ public class VaultCryptoClient extends VaultClient { private static final String TRANSIT_PATH_PREFIX = "v1/transit/"; + public static final String KEY_TYPE_ENCRYPTION_KEY = "encryption-key"; + public static final String KEY_TYPE_SIGNING_KEY = "signing-key"; + public static final String KEY_TYPE_HMAC_KEY = "hmac-key"; + /** * Explicit constructor that allows for full control over construction of the Vault client. * @@ -99,6 +104,25 @@ public VaultKeyResponse getKeyInfo(@Nonnull final String name) { } + /** + * Retrieve key information + * + * @param name Name of the key to lookup + * @param type Type of key to export - KEY_TYPE_ENCRYPTION_KEY/KEY_TYPE_SIGNING_KEY/KEY_TYPE_HMAC_KEY + * @return Raw encryption key, if key was created with permission to export + */ + public VaultKeyExportResponse exportKey(@Nonnull final String name, @Nonnull final String type) { + final HttpUrl url = buildUrl(TRANSIT_PATH_PREFIX, "export/" + type + "/" + name); + final Response response = execute(url, HttpMethod.GET, null); + + if (response.code() != HttpStatus.OK) { + parseAndThrowErrorResponse(response); + } + + return parseResponse(response, VaultKeyExportResponse.class); + } + + /** * Encrypt data with a key stored in Vault * @param keyName Name of the key to use for the cryptographic operation diff --git a/src/main/java/com/nike/vault/client/model/VaultKeyExportResponse.java b/src/main/java/com/nike/vault/client/model/VaultKeyExportResponse.java new file mode 100644 index 0000000..41b3c94 --- /dev/null +++ b/src/main/java/com/nike/vault/client/model/VaultKeyExportResponse.java @@ -0,0 +1,68 @@ +package com.nike.vault.client.model; + +import com.google.gson.annotations.SerializedName; +import org.apache.commons.lang3.builder.ToStringBuilder; + +/** + * Created by gutzeit on 28/12/2017. All rights reserved to LeapXpert. + */ +public class VaultKeyExportResponse { + public class KeyInformation { + + @SerializedName("1") + private String keyData; + + public String getKeyData() { + return keyData; + } + + public void setKeyData( + String keyData) { + this.keyData = keyData; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("keyData", keyData) + .toString(); + } + } + + private KeyInformation keys; + private String name; + private String type; + + public KeyInformation getKeys() { + return keys; + } + + public void setKeys(KeyInformation keys) { + this.keys = keys; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("keys", keys) + .append("name", name) + .append("type", type) + .toString(); + } +} diff --git a/src/main/java/com/nike/vault/client/model/VaultKeyResponse.java b/src/main/java/com/nike/vault/client/model/VaultKeyResponse.java index 8f2eb69..b59d317 100644 --- a/src/main/java/com/nike/vault/client/model/VaultKeyResponse.java +++ b/src/main/java/com/nike/vault/client/model/VaultKeyResponse.java @@ -1,6 +1,5 @@ package com.nike.vault.client.model; -import com.google.gson.annotations.SerializedName; import org.apache.commons.lang3.builder.ToStringBuilder; public class VaultKeyResponse { diff --git a/src/test/java/com/nike/vault/client/VaultCryptoClientTest.java b/src/test/java/com/nike/vault/client/VaultCryptoClientTest.java index 9b00743..26c2cc5 100644 --- a/src/test/java/com/nike/vault/client/VaultCryptoClientTest.java +++ b/src/test/java/com/nike/vault/client/VaultCryptoClientTest.java @@ -13,6 +13,7 @@ import com.nike.vault.client.model.VaultDecryptDataResponse; import com.nike.vault.client.model.VaultEncryptDataRequest; import com.nike.vault.client.model.VaultEncryptDataResponse; +import com.nike.vault.client.model.VaultKeyExportResponse; import com.nike.vault.client.model.VaultKeyResponse; import com.nike.vault.client.model.VaultSymmetricKeyResponse; import java.io.IOException; @@ -48,6 +49,34 @@ public void teardown() throws IOException { mockWebServer.shutdown(); } + @Test + public void export_symmetric_returns_ok_if_created() { + final MockResponse response = new MockResponse(); + response.setResponseCode(HttpStatus.OK); + response.setBody(getResponseJson("symmetric_export")); + mockWebServer.enqueue(response); + + VaultKeyExportResponse actualResponse = + vaultClient.exportKey("test-key", VaultCryptoClient.KEY_TYPE_ENCRYPTION_KEY); + + assertThat(actualResponse).isNotNull(); + assertThat(actualResponse.getKeys().getKeyData()).isNotEmpty(); + } + + @Test + public void export_asymmetric_returns_ok_if_created() { + final MockResponse response = new MockResponse(); + response.setResponseCode(HttpStatus.OK); + response.setBody(getResponseJson("asymmetric_export")); + mockWebServer.enqueue(response); + + VaultKeyExportResponse actualResponse = + vaultClient.exportKey("test-key", VaultCryptoClient.KEY_TYPE_ENCRYPTION_KEY); + + assertThat(actualResponse).isNotNull(); + assertThat(actualResponse.getKeys().getKeyData()).isNotEmpty(); + } + @Test public void create_key_returns_204_when_successful() { final MockResponse response = new MockResponse(); diff --git a/src/test/resources/com/nike/vault/client/asymmetric_export.json b/src/test/resources/com/nike/vault/client/asymmetric_export.json new file mode 100644 index 0000000..d849d81 --- /dev/null +++ b/src/test/resources/com/nike/vault/client/asymmetric_export.json @@ -0,0 +1 @@ +{"request_id":"e8cae20b-dd66-7530-7ac8-3b616bfa3f08","lease_id":"","renewable":false,"lease_duration":0,"data":{"keys":{"1":"-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEA4PsBLbgfIIXfYAV0brI/f4/zlciwJv2hgv9aQf63dS14erVa\nuP5NwqEJtEvPvUIk1RQQyKyiST14IQDLDg8pFiAsbTshda1e/pjIjtsAEnDUFW1V\nRRnqc4Ex8vRaT6stbZLGCQlWUnBO5zRCoRZZk/XnPXOIyg8qnvSnDUkqptL50IiK\nNXXNHN6jEicLKIKQ4JaSy2zZuDKM+wDpbrjMLeL5nvVS2E4mV8ilmA+5LRudoomk\n7fBie3iD92z/EHeXyczQ1y0kZsOBb7bWKI3YDyr/ol1bTPp0mcE1IxsL8n1mhzPm\njDW0NHlUtIhpCFq86zm/a0lGBf0dNxliHnB+5QIDAQABAoIBAFFRaHOmAVo4CS0j\ny5nXQ6xP07Nn/oOZWS+ILI9+CcGI1etEca47/M9EdcV9QXEe30FFJ2vhOidO2ITV\nTI+gWzFsH6K7pLRsHdHYV2WLMtN3hLDZ++AmJd/p6qvuNlZlgN4CFyJdBZ52iY54\nDT08XtRkJVjI0cB3Cui5dUgQEiKJoZvChkVo/xTbEGyQcIYvbC01iMj+iM7TrF4k\nlxIlfJnFCzOSV34OfkyrN2WJxQ7STkOIRctAfscwfh9toUuhOR9pYceC1LvLU9hv\nOq+5LWKk5DmzSTUvDapHWcWNIJ0pkK4E4gEnw/Jxxwiw21EKmLfJa2/RU++rfSaH\nz8VuQ2ECgYEA5B2aePUe2AXI/XaGqnYi91vj58gkhbiOOI2fZU6WGiGz5KYYV38H\n0YZ4Gj5zRS99t0VKzhDSwLv/BeiyxUryE257lcdjsK7A6xHThB2u51GA2rtrDAdJ\n9f93CWqnlzOYETE/vmuOuRWqL5jofWCT2ob3N72zFeJJp/kJtPWL62cCgYEA/HtL\nEs7qZ6tV6f/oV3XwvGWkKjUW2uaFV/PPx31Jh0Jf9ohSh9ZmckPwys0xIa1OC3pP\nmQI5cvpcIECSntOWPQXIs6jwfB64f1QVxx3vqGvZ950eBo57z69q5PILHzb2IR67\n33AsgJYlEUhTBEZy1C/X+tNnSaIxuE+7OdsEH9MCgYB5IsdPCEvyx0+uWWy1xLpY\nxPFHul66rADKQ1qrv4myIseW2iT/AbQzLcdFmHg5+zg1RJSuzPw94RdSGfolDuFy\nNC9ooFNuFb7YlcTO2bxxljRo8zGrV4uNGLYrx/lrL7jHGHITOqa13q1bTUXYnpql\nZxqM/S3Gpz4Z9wOSlvpQbQKBgD2pxw4S5wDmwZbi31XtAhyhHlUInkpcHpj6fPaV\nzM1yEondhXqTjHW/ziFZt/QnpXX8K1CNUIaaSsG9w5Fyz7Cbpwbp7cICpsDCQodc\n8llJ7fQhtWGYjviMOSktTDYVcEtqfCv384Z8JRVxeoUCx6y2+qLR2toK+OWw42Mf\n8IPtAoGAZU9buC06zJLIQGZCXZMOAln7mdqxc9M6E1rTE11bDIKJJN4kvzyO3h1W\nG451nG+xnC96oIOrhmOU80s2J956qfhJRiwWVDxJjslEkH+cLjhMBId36inepL1P\nKPGeRlK5QW7MOhhQuOnMJiRL1OrOKLCRvMKQWmoNuiktKA0A4EA=\n-----END RSA PRIVATE KEY-----\n"},"name":"my-key-asymmetric","type":"rsa-2048"},"wrap_info":null,"warnings":null,"auth":null} \ No newline at end of file diff --git a/src/test/resources/com/nike/vault/client/symmetric_export.json b/src/test/resources/com/nike/vault/client/symmetric_export.json new file mode 100644 index 0000000..2aaca63 --- /dev/null +++ b/src/test/resources/com/nike/vault/client/symmetric_export.json @@ -0,0 +1 @@ +{"request_id":"4ce7d18e-deef-a633-eac2-766094a6558d","lease_id":"","renewable":false,"lease_duration":0,"data":{"keys":{"1":"1WTeJCE75wZs/hwrywrVrEUbB4UJXlsappeJHcPXNDY="},"name":"my-key-symmetric","type":"aes256-gcm96"},"wrap_info":null,"warnings":null,"auth":null} \ No newline at end of file From 7e0219240bdbeca4ffa27bed688e70aadab55748 Mon Sep 17 00:00:00 2001 From: nguquen Date: Wed, 4 Apr 2018 15:23:26 +0700 Subject: [PATCH 09/10] Delete transits key endpoint --- .../java/com/nike/vault/client/VaultCryptoClient.java | 8 ++++++++ .../com/nike/vault/client/VaultCryptoClientTest.java | 10 ++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/main/java/com/nike/vault/client/VaultCryptoClient.java b/src/main/java/com/nike/vault/client/VaultCryptoClient.java index 6594772..8ddaaa2 100644 --- a/src/main/java/com/nike/vault/client/VaultCryptoClient.java +++ b/src/main/java/com/nike/vault/client/VaultCryptoClient.java @@ -122,6 +122,14 @@ public VaultKeyExportResponse exportKey(@Nonnull final String name, @Nonnull fin return parseResponse(response, VaultKeyExportResponse.class); } + public void deleteKey(@Nonnull final String name) { + final HttpUrl url = buildUrl(TRANSIT_PATH_PREFIX, "keys/" + name); + final Response response = execute(url, HttpMethod.DELETE, null); + + if (response.code() != HttpStatus.NO_CONTENT) { + parseAndThrowErrorResponse(response); + } + } /** * Encrypt data with a key stored in Vault diff --git a/src/test/java/com/nike/vault/client/VaultCryptoClientTest.java b/src/test/java/com/nike/vault/client/VaultCryptoClientTest.java index 26c2cc5..be962c2 100644 --- a/src/test/java/com/nike/vault/client/VaultCryptoClientTest.java +++ b/src/test/java/com/nike/vault/client/VaultCryptoClientTest.java @@ -162,6 +162,16 @@ public void decrypt_returns_ok_if_created() { assertThat(actualResponse.getPlaintext()).isEqualTo("QWxsIHdlIGFyZSBidXQgc3BlY2tzIGluIGEgaHVnZSB1bml2ZXJzZSAuLi4="); } + @Test + public void delete_key_returns_204_when_successful() { + final MockResponse response = new MockResponse(); + response.setResponseCode(HttpStatus.NO_CONTENT); + mockWebServer.enqueue(response); + + vaultClient.deleteKey("test-key"); + // Silence is success! + } + private String getResponseJson(final String title) { InputStream inputStream = getClass().getResourceAsStream( String.format("/com/nike/vault/client/%s.json", title)); From ae8e254c9d0e5cfad8a00f4696f07f49bb6c8589 Mon Sep 17 00:00:00 2001 From: nguquen Date: Wed, 4 Apr 2018 15:29:55 +0700 Subject: [PATCH 10/10] Use gradle-git-repo-plugin forked version --- gradle/buildscript.gradle | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gradle/buildscript.gradle b/gradle/buildscript.gradle index 57b01b5..342f41f 100644 --- a/gradle/buildscript.gradle +++ b/gradle/buildscript.gradle @@ -16,11 +16,12 @@ repositories { jcenter() + maven { url "https://jitpack.io" } } dependencies { classpath "net.saliman:gradle-cobertura-plugin:2.3.0" classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.6' classpath 'org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.6.3' - classpath group: 'com.layer', name: 'gradle-git-repo-plugin', version: '2.0.2' + classpath group: 'com.github.LeapXpert', name: 'gradle-git-repo-plugin', version: '2.0.2-FORK' }