All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.helidon.integrations.vault.secrets.transit.TransitSecurityProvider Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2021, 2023 Oracle and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.helidon.integrations.vault.secrets.transit;

import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;

import io.helidon.common.Base64Value;
import io.helidon.common.config.Config;
import io.helidon.integrations.vault.Vault;
import io.helidon.security.SecurityException;
import io.helidon.security.spi.DigestProvider;
import io.helidon.security.spi.EncryptionProvider;
import io.helidon.security.spi.ProviderConfig;

/**
 * Integration with Helidon Security.
 */
public class TransitSecurityProvider implements EncryptionProvider,
                                                DigestProvider {
    private final TransitSecrets transit;

    TransitSecurityProvider(Vault vault) {
        this.transit = vault.secrets(TransitSecrets.ENGINE);
    }

    @Override
    public EncryptionSupport encryption(Config config) {
        return encryption(TransitEncryptionConfig.create(config));
    }

    @Override
    public EncryptionSupport encryption(TransitEncryptionConfig providerConfig) {
        Function encrypt = bytes ->
                transit.encrypt(providerConfig.encryptionRequest()
                                        .data(Base64Value.create(bytes)))
                        .encrypted()
                        .cipherText();

        Function decrypt = encrypted ->
                transit.decrypt(providerConfig.decryptionRequest()
                                        .cipherText(encrypted))
                        .decrypted()
                        .toBytes();
        return EncryptionSupport.create(encrypt, decrypt);
    }

    @Override
    public DigestSupport digest(Config config) {
        return digest(TransitDigestConfig.create(config));
    }

    @Override
    public DigestSupport digest(TransitDigestConfig providerConfig) {
        if (providerConfig.isSignature) {
            return signature(providerConfig);
        } else {
            return hmac(providerConfig);
        }
    }

    private DigestSupport signature(TransitDigestConfig providerConfig) {
        DigestFunction digestFunction = (data, preHashed) -> {
            Sign.Request request = providerConfig.signRequest()
                    .data(Base64Value.create(data))
                    .preHashed(preHashed);

            return transit.sign(request).signature();
        };

        VerifyFunction verifyFunction = (data, preHashed, digest) -> {
            Verify.Request verifyRequest = providerConfig.verifyRequest()
                    .data(Base64Value.create(data))
                    .preHashed(preHashed)
                    .signature(digest);

            return transit.verify(verifyRequest).isValid();
        };

        return DigestSupport.create(digestFunction, verifyFunction);
    }

    private DigestSupport hmac(TransitDigestConfig providerConfig) {
        DigestFunction digestFunction = (data, preHashed) -> {
            Hmac.Request request = providerConfig.hmacRequest()
                    .data(Base64Value.create(data));

            return transit.hmac(request).hmac();
        };

        VerifyFunction verifyFunction = (data, preHashed, digest) -> {
            Verify.Request verifyRequest = providerConfig.verifyRequest()
                    .data(Base64Value.create(data))
                    .preHashed(preHashed)
                    .hmac(digest);

            return transit.verify(verifyRequest).isValid();
        };

        return DigestSupport.create(digestFunction, verifyFunction);
    }

    /**
     * Configuration of a digest when using programmatic setup of security digests.
     */
    public static class TransitDigestConfig implements ProviderConfig {
        private final String keyName;
        private final Optional keyVersion;
        private final Optional context;
        private final Optional signatureAlgorithm;
        private final Optional marshalingAlgorithm;
        private final Optional hashAlgorithm;

        private final boolean isSignature;

        private TransitDigestConfig(Builder builder) {
            this.keyName = builder.keyName;
            this.keyVersion = Optional.ofNullable(builder.keyVersion);
            this.context = Optional.ofNullable(builder.context);
            this.signatureAlgorithm = Optional.ofNullable(builder.signatureAlgorithm);
            this.marshalingAlgorithm = Optional.ofNullable(builder.marshalingAlgorithm);
            this.hashAlgorithm = Optional.ofNullable(builder.hashAlgorithm);
            this.isSignature = builder.isSignature;
        }

        /**
         * A new builder for {@link io.helidon.integrations.vault.secrets.transit.TransitSecurityProvider.TransitDigestConfig}.
         *
         * @return a new builder
         */
        public static Builder builder() {
            return new Builder();
        }

        /**
         * Create a new digest configuration from config.
         *
         * @param config config to use
         * @return a new digest configuration
         */
        public static TransitDigestConfig create(Config config) {
            return builder().config(config).build();
        }

        Sign.Request signRequest() {
            Sign.Request request = Sign.Request.builder()
                    .signatureKeyName(keyName);

            keyVersion.ifPresent(request::signatureKeyVersion);
            context.ifPresent(request::context);
            signatureAlgorithm.ifPresent(request::signatureAlgorithm);
            marshalingAlgorithm.ifPresent(request::marshalingAlgorithm);
            hashAlgorithm.ifPresent(request::hashAlgorithm);

            return request;
        }

        Verify.Request verifyRequest() {
            Verify.Request request = Verify.Request.builder()
                    .digestKeyName(keyName);

            context.ifPresent(request::context);
            signatureAlgorithm.ifPresent(request::signatureAlgorithm);
            marshalingAlgorithm.ifPresent(request::marshalingAlgorithm);
            hashAlgorithm.ifPresent(request::hashAlgorithm);

            return request;
        }

        Hmac.Request hmacRequest() {
            Hmac.Request request = Hmac.Request.builder()
                    .hmacKeyName(keyName);

            keyVersion.ifPresent(request::hmacKeyVersion);
            hashAlgorithm.ifPresent(request::hashAlgorithm);

            return request;
        }

        /**
         * Fluent API builder for
         * {@link io.helidon.integrations.vault.secrets.transit.TransitSecurityProvider.TransitDigestConfig}.
         */
        public static class Builder implements io.helidon.common.Builder {
            /**
             * Digest is a signature.
             */
            public static final String TYPE_SIGNATURE = "signature";
            /**
             * Digest is an HMAC.
             */
            public static final String TYPE_HMAC = "hmac";
            private static final String CONFIG_KEY_KEY_NAME = "key-name";
            private boolean isSignature = true;
            private String keyName;
            private Integer keyVersion;
            private Base64Value context;
            private String signatureAlgorithm;
            private String marshalingAlgorithm;
            private String hashAlgorithm;

            private Builder() {
            }

            @Override
            public TransitDigestConfig build() {
                Objects.requireNonNull(keyName, "Key ID must be defined for digest, configuration key \""
                        + CONFIG_KEY_KEY_NAME + "\"");

                return new TransitDigestConfig(this);
            }

            /**
             * Update this builder from configuration.
             * Only {@value CONFIG_KEY_KEY_NAME} is mandatory.
             *
             * 

* Configuration options: *

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Secret configuration
keydescriptionbuilder method
{@value CONFIG_KEY_KEY_NAME}Name of the key used for this digest operation{@link #keyName(String)}
{@code key-version}Version of the key to use{@link #keyVersion(int)}
{@code context}Context as base64 encoded text.{@link #context(Base64Value)}
{@code signature-algorithm}Signature algorithm.{@link #signatureAlgorithm(String)}
{@code marshalling-algorithm}Marshalling algorithm.{@link #marshalingAlgorithm(String)}
{@code hash-algorithm}Hash algorithm.{@link #hashAlgorithm(String)}
{@code type}Type of digest, defaults to {@value #TYPE_SIGNATURE}.{@link #type(String)}
* * @param config config to use * @return updated builder */ public Builder config(Config config) { config.get(CONFIG_KEY_KEY_NAME).asString().ifPresent(this::keyName); config.get("key-version").asInt().ifPresent(this::keyVersion); config.get("context").asString().map(Base64Value::createFromEncoded).ifPresent(this::context); config.get("signature-algorithm").asString().ifPresent(this::signatureAlgorithm); config.get("marshaling-algorithm").asString().ifPresent(this::marshalingAlgorithm); config.get("hash-algorithm").asString().ifPresent(this::hashAlgorithm); config.get("type").asString().ifPresent(this::type); return this; } /** * Type of digest, either {@link #TYPE_SIGNATURE} or {@link #TYPE_HMAC}. * Defaults to {@link #TYPE_SIGNATURE}. * * @param type type to use * @return updated builder */ public Builder type(String type) { switch (type) { case TYPE_HMAC: isSignature = false; break; case TYPE_SIGNATURE: isSignature = true; break; default: throw new SecurityException("Only " + TYPE_SIGNATURE + ", and " + TYPE_HMAC + " digest types are supported"); } return this; } /** * Name of the key (Vault server side) used for this digest. * Note that key type must be valid for the type used. Signatures require an asymmetric key, HMAC requires * a symmetric key. * * @param keyName name of the key * @return updated builder */ public Builder keyName(String keyName) { this.keyName = keyName; return this; } /** * Specifies the version of the key to use for digest. If not set, uses the latest version. * Must be greater than or equal to the key's {@code min_encryption_version}, if set. * Optional. * * @param keyVersion key version * @return updated request */ public Builder keyVersion(int keyVersion) { this.keyVersion = keyVersion; return this; } /** * Specifies the context for key derivation. This is required if key derivation is enabled for this key; currently * only available with ed25519 keys. * * @param context context * @return updated request */ public Builder context(Base64Value context) { this.context = context; return this; } /** * When using a RSA key, specifies the RSA signature algorithm to use for signing. Supported signature types are: * *

* pss * pkcs1v15 * *

* See signature algorithm constants on this class. * * @param signatureAlgorithm signature algorithm to use * @return updated request * @see Sign.Request#SIGNATURE_ALGORITHM_PSS * @see Sign.Request#SIGNATURE_ALGORITHM_PKCS1_V15 */ public Builder signatureAlgorithm(String signatureAlgorithm) { this.signatureAlgorithm = signatureAlgorithm; return this; } /** * Specifies the way in which the signature should be marshaled. This currently only applies to ECDSA keys. Supported * types are: * asn1: The default, used by OpenSSL and X.509 * jws: The version used by JWS (and thus for JWTs). Selecting this will also change the output encoding to URL-safe * Base64 encoding instead of standard Base64-encoding. * * @param marshalingAlgorithm marshaling algorithm * @return updated request * @see Sign.Request#MARSHALLING_ALGORITHM_ASN_1 * @see Sign.Request#MARSHALLING_ALGORITHM_JWS */ public Builder marshalingAlgorithm(String marshalingAlgorithm) { this.marshalingAlgorithm = marshalingAlgorithm; return this; } /** * Specifies the hash algorithm to use for supporting key types (notably, not including ed25519 which specifies its * own * hash algorithm). * See hash algorithm constants on this class. * * @param hashAlgorithm algorithm to use * @return updated request * @see Sign.Request#HASH_ALGORITHM_SHA2_224 * @see Sign.Request#HASH_ALGORITHM_SHA2_256 * @see Sign.Request#HASH_ALGORITHM_SHA2_384 * @see Sign.Request#HASH_ALGORITHM_SHA2_512 * @see Hmac.Request#HASH_ALGORITHM_SHA2_224 * @see Hmac.Request#HASH_ALGORITHM_SHA2_256 * @see Hmac.Request#HASH_ALGORITHM_SHA2_384 * @see Hmac.Request#HASH_ALGORITHM_SHA2_512 */ public Builder hashAlgorithm(String hashAlgorithm) { this.hashAlgorithm = hashAlgorithm; return this; } } } /** * Configuration of encryption when using programmatic setup of security. */ public static class TransitEncryptionConfig implements ProviderConfig { private final String keyName; private final Optional keyVersion; private final Optional encryptionKeyType; private final Optional convergentEncryption; private final Optional context; private TransitEncryptionConfig(Builder builder) { this.keyName = builder.keyName; this.keyVersion = Optional.ofNullable(builder.keyVersion); this.encryptionKeyType = Optional.ofNullable(builder.encryptionKeyType); this.convergentEncryption = Optional.ofNullable(builder.convergentEncryption); this.context = Optional.ofNullable(builder.context); } /** * A new builder for * {@link io.helidon.integrations.vault.secrets.transit.TransitSecurityProvider.TransitEncryptionConfig}. * * @return a new builder */ public static Builder builder() { return new Builder(); } /** * Create a new encryption configuration from config. * * @param config config to use * @return a new encryption configuration */ public static TransitEncryptionConfig create(Config config) { return builder().config(config).build(); } Encrypt.Request encryptionRequest() { Encrypt.Request builder = Encrypt.Request.builder() .encryptionKeyName(keyName); keyVersion.ifPresent(builder::encryptionKeyVersion); encryptionKeyType.ifPresent(builder::encryptionKeyType); context.ifPresent(builder::context); convergentEncryption.ifPresent(builder::convergentEncryption); return builder; } Decrypt.Request decryptionRequest() { Decrypt.Request builder = Decrypt.Request.builder() .encryptionKeyName(keyName); context.ifPresent(builder::context); return builder; } /** * Fluent API builder for * {@link io.helidon.integrations.vault.secrets.transit.TransitSecurityProvider.TransitEncryptionConfig}. */ public static class Builder implements io.helidon.common.Builder { private static final String CONFIG_KEY_KEY_NAME = "key-name"; private String keyName; private Base64Value context; private Integer keyVersion; private String encryptionKeyType; private String convergentEncryption; private Builder() { } @Override public TransitEncryptionConfig build() { Objects.requireNonNull(keyName, "Key ID must be defined for encryption, configuration key \"" + CONFIG_KEY_KEY_NAME + "\""); return new TransitEncryptionConfig(this); } /** * Update this builder from configuration. * Only {@value CONFIG_KEY_KEY_NAME} is mandatory. * *

* Configuration options: *

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Secret configuration
keydescriptionbuilder method
{@value CONFIG_KEY_KEY_NAME}Name of the key used for this digest operation{@link #keyName(String)}
{@code context}Context as base64 encoded text.{@link #context(Base64Value)}
{@code key-version}Version of the key to use{@link #keyVersion(int)}
{@code key-type}Type of the key to use{@link #keyVersion(int)}
{@code convergent}Convergent encryption{@link #convergent(String)}
* * @param config config to use * @return updated builder */ public Builder config(Config config) { config.get(CONFIG_KEY_KEY_NAME).asString().ifPresent(this::keyName); config.get("context").asString().map(Base64Value::createFromEncoded).ifPresent(this::context); config.get("key-version").asInt().ifPresent(this::keyVersion); config.get("key-type").asString().ifPresent(this::keyType); config.get("convergent").asString().ifPresent(this::convergent); return this; } /** * Specifies the name of the encryption key to encrypt/decrypt against. * Required. * * @param keyName name of the key * @return updated request */ public Builder keyName(String keyName) { this.keyName = keyName; return this; } /** * Specifies the context for key derivation. This is required if key derivation is enabled for this key. * * @param context context * @return updated request */ public Builder context(Base64Value context) { this.context = context; return this; } /** * Version of the key used to encrypt the data. * * @param keyVersion version of the key * @return updated builder */ public Builder keyVersion(int keyVersion) { this.keyVersion = keyVersion; return this; } /** * This parameter is required when encryption key is expected to be created. When performing an upsert operation, * the type of key to create. *

* Defaults to {@code aes256-gcm96}. * * @param encryptionKeyType type of the encryption key * @return updated request */ public Builder keyType(String encryptionKeyType) { this.encryptionKeyType = encryptionKeyType; return this; } /** * This parameter will only be used when a key is expected to be created. Whether to support convergent encryption. * This is only supported when using a key with key derivation enabled and will require all requests to carry both a * context and 96-bit (12-byte) nonce. The given nonce will be used in place of a randomly generated nonce. As a * result, when the same context and nonce are supplied, the same ciphertext is generated. It is very important when * using this mode that you ensure that all nonces are unique for a given context. Failing to do so will severely * impact the ciphertext's security. * * @param convergentEncryption convergent encryption * @return updated request */ public Builder convergent(String convergentEncryption) { this.convergentEncryption = convergentEncryption; return this; } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy