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

io.helidon.security.providers.oidc.common.OidcEncryption Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2021, 2024 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.security.providers.oidc.common;

import java.io.IOException;
import java.lang.System.Logger.Level;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.PosixFilePermission;
import java.util.Set;
import java.util.UUID;

import io.helidon.common.Base64Value;
import io.helidon.common.context.Contexts;
import io.helidon.common.crypto.SymmetricCipher;
import io.helidon.security.Security;
import io.helidon.security.spi.EncryptionProvider.EncryptionSupport;

final class OidcEncryption {
    private static final System.Logger LOGGER = System.getLogger(OidcEncryption.class.getName());

    private OidcEncryption() {
    }

    static EncryptionSupport create(String type,
                                    String encryptionConfigurationName,
                                    char[] encryptionPassword) {
        EncryptionSupport found = null;

        if (encryptionConfigurationName != null) {
            found = nameBasedCipher(encryptionConfigurationName);
        }

        char[] masterPassword = encryptionPassword;
        if (encryptionPassword == null && found == null) {
            masterPassword = generateMasterPassword();
        }

        if (found != null && masterPassword != null) {
            throw new SecurityException("Cannot define both name based encryption and password based encryption for " + type);
        }

        return symmetricCipher(masterPassword);
    }

    private static EncryptionSupport symmetricCipher(char[] masterPassword) {
        SymmetricCipher cipher = SymmetricCipher.create(masterPassword);
        return EncryptionSupport.create(
                bytes -> cipher.encrypt(Base64Value.create(bytes)).toBase64(),
                cipherText -> cipher.decrypt(Base64Value.createFromEncoded(cipherText)).toBytes()
        );
    }

    private static EncryptionSupport nameBasedCipher(String encryptionConfigurationName) {
        return EncryptionSupport.create(
                bytes -> securityFromContext().encrypt(encryptionConfigurationName, bytes),
                cipherText -> securityFromContext().decrypt(encryptionConfigurationName, cipherText)
        );
    }

    private static char[] generateMasterPassword() {
        Path path = Paths.get(".helidon-oidc-secret");
        if (!Files.exists(path)) {

            String password = UUID.randomUUID().toString();
            try {
                Files.writeString(path, password, StandardCharsets.UTF_8, StandardOpenOption.CREATE_NEW);
                if (path.getFileSystem().supportedFileAttributeViews().contains("posix")) {
                    Files.setPosixFilePermissions(path, Set.of(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE));
                }
            } catch (IOException e) {
                throw new SecurityException("Failed to create OIDC secret " + path.toAbsolutePath(), e);
            }
            LOGGER.log(Level.WARNING, "OIDC requires encryption configuration which was not provided. We will generate"
                                   + " a password that will only work for the current service instance. To disable encryption,"
                                   + " use cookie-encryption-enabled: false configuration, to configure master password, use"
                                   + " cookie-encryption-password: my-master-password (must be configured to same value on all"
                                   + " instances that share the cookie), to configure encryption using security"
                                   + " (support for vaults), use"
                                   + " cookie-encryption-name: name (must have corresponding encryption provider and"
                                   + " configuration with the provided name in security), this also requires Security to be"
                                   + " registered with current or global Context (this works automatically in Helidon MP)."
                                   + " This message is logged just once, before generating the master password");

        }

        try {
            // to be consistent, I always read the content from the file, even when creating it
            return Files.readString(path, StandardCharsets.UTF_8).toCharArray();
        } catch (IOException e) {
            throw new SecurityException("Cannot read OIDC secret file: " + path.toAbsolutePath(), e);
        }
    }

    private static Security securityFromContext() {
        return Contexts.context()
                .orElseGet(Contexts::globalContext)
                .get(Security.class)
                .orElseThrow(() -> new SecurityException("When using encryption configuration name for OIDC,"
                                                                 + " Security must be registered with current or"
                                                                 + " global context"));
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy