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

dk.grinn.keycloak.migration.boundary.RealmKeyController Maven / Gradle / Ivy

Go to download

Keycloak migrate extending the keycloak jpa model to include migrate versioning. And addtional REST end-points for migration purposes.

There is a newer version: 15.0.0.1
Show newest version
package dk.grinn.keycloak.migration.boundary;

/*-
 * #%L
 * Keycloak : Migrate : Spi
 * %%
 * Copyright (C) 2019 Jonas Grann & Kim Jersin
 * %%
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 * #L%
 */

import dk.grinn.keycloak.migration.entities.CreateRealmKey;
import org.jboss.logging.Logger;
import org.keycloak.common.util.CertificateUtils;
import org.keycloak.common.util.KeyUtils;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.common.util.PemUtils;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.RealmModel;

import javax.persistence.EntityManager;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.util.Base64;

/**
 * 
 * @author @author Nicolai Gjøderum <[email protected]>
 */
public class RealmKeyController {

    private static final Logger LOG = Logger.getLogger(RealmKeyController.class);
    
    protected static final String PRIVATE_KEY_CONFIG_KEY = "privateKey";
    protected static final String CERTIFICATE_CONFIG_KEY = "certificate";

    private EntityManager em;
    private GkcadmRealmAttributeController controller;

    public RealmKeyController(EntityManager em) {
        this.em = em;
        this.controller = new GkcadmRealmAttributeController(em);

    }

    public static void disableRsaGenerated(RealmModel realmModel) {
        realmModel.getComponents().stream().filter(componentModel -> componentModel.getName().equals("rsa-generated")).forEach(componentModel -> {
            componentModel.getConfig().putSingle("active", "false");
            componentModel.getConfig().putSingle("enabled", "false");
            realmModel.updateComponent(componentModel);
        });
    }

    public void setRealmKey(RealmModel realmModel, CreateRealmKey createRealmKey) {

        String realmName = realmModel.getName();

        CertAndKeyPair pair = getCertAndKeyPairFromEnvironment(createRealmKey);

        if(pair.isEmpty()){
            pair = getCertAndKeyPairFromPath(createRealmKey);
        }

        if(createRealmKey.isReuse() && pair.isEmpty()) {
            pair = getCertAndKeyPairFromRealm(realmName);
        }

        if(pair.isEmpty()) {
            pair = generateNewCertAndKeyPair(createRealmKey);
        }

        createReusableRSACertificate(realmModel, createRealmKey, realmName, pair);
    }

    private CertAndKeyPair generateNewCertAndKeyPair(CreateRealmKey createRealmKey) {
        LOG.info("Generating new realm key");
        KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048);
        Certificate genCert = CertificateUtils.generateV1SelfSignedCertificate(keyPair, createRealmKey.getSubject());
        return new CertAndKeyPair(PemUtils.encodeCertificate(genCert), PemUtils.encodeKey(keyPair.getPrivate()));
    }

    private CertAndKeyPair getCertAndKeyPairFromRealm(String realmName) {
        LOG.info("Signaled re-using realm key");
        return new CertAndKeyPair(controller.getAttribute(realmName, CERTIFICATE_CONFIG_KEY), controller.getAttribute(realmName, PRIVATE_KEY_CONFIG_KEY));
    }

    private CertAndKeyPair getCertAndKeyPairFromPath(CreateRealmKey createRealmKey) {
        char[] password = createRealmKey.getPassword();
        String alias = createRealmKey.getAlias();
        String keyPath = createRealmKey.getPath();

        if( isNullOrEmpty(keyPath) || password == null || isNullOrEmpty(alias)){
            return CertAndKeyPair.EMPTY;
        } else {
            Path path = Paths.get(createRealmKey.getPath());
            LOG.info("Using realm key from path = " + path.toString());

            KeyStore ks = getKeyStore(path, password);
            return getCertAndKeyPairFromKeystore(ks, password, alias);
        }
    }

    private CertAndKeyPair getCertAndKeyPairFromKeystore(KeyStore ks, char[] password, String alias) {
        try {
            Certificate cert = ks.getCertificate(alias);
            PrivateKey pk = (PrivateKey) ks.getKey(alias, password);

            String certificate = Base64.getEncoder().encodeToString(cert.getEncoded());
            String privateKey =  Base64.getEncoder().encodeToString(pk.getEncoded());

            return new CertAndKeyPair(certificate, privateKey);
        } catch (KeyStoreException | NoSuchAlgorithmException | UnrecoverableKeyException | CertificateEncodingException e) {
            throw new RuntimeException("Could not load certificate.");
        }
    }

    private CertAndKeyPair getCertAndKeyPairFromEnvironment(CreateRealmKey createRealmKey) {
        CertAndKeyPair pair = new CertAndKeyPair(createRealmKey.getCertificate(), createRealmKey.getPrivateKey());

        if(!pair.isEmpty()){
            LOG.info("Using realm key from ENV");
        }

        return pair;
    }

    private void createReusableRSACertificate(RealmModel realmModel, CreateRealmKey createRealmKey, String realmName, CertAndKeyPair pair) {
        controller.putAttribute(realmName, PRIVATE_KEY_CONFIG_KEY, pair.getPrivateKey());
        controller.putAttribute(realmName, CERTIFICATE_CONFIG_KEY, pair.getCertificate());

        ComponentModel rsaComponent = createRsaComponent(createRealmKey.getName(), realmModel.getId(), pair.getPrivateKey(), pair.getCertificate(), createRealmKey.getPriority());

        realmModel.addComponentModel(rsaComponent);
    }


    private ComponentModel createRsaComponent(String name, String realmId, String privateKey, String certificate, long priority) {
        ComponentModel comp = new ComponentModel();

        comp.setName(name);
        comp.setParentId(realmId);
        comp.setProviderId("rsa");
        comp.setProviderType("org.keycloak.keys.KeyProvider");
        comp.setConfig(new MultivaluedHashMap<>());

        comp.getConfig().putSingle("active", "true");
        comp.getConfig().putSingle("enabled", "true");
        comp.getConfig().putSingle("priority", String.valueOf(priority));
        comp.getConfig().putSingle(PRIVATE_KEY_CONFIG_KEY, privateKey);
        comp.getConfig().putSingle(CERTIFICATE_CONFIG_KEY, certificate);
        comp.getConfig().putSingle("algorithm", "RS256");
        return comp;
    }

    private KeyStore getKeyStore(Path path, char[] password) {
        if(Files.exists(path)){
            String type = getType(path);
            return loadKeyStore(path, type, password);
        } else {
            throw new IllegalArgumentException("The specified certificate file does not exist.");
        }
    }

    private String getType(Path path) {
        if(path.getFileName().toString().toLowerCase().endsWith(".p12")){
            return "PKCS12";
        } else if(path.getFileName().toString().toLowerCase().endsWith(".jks")){
            return KeyStore.getDefaultType();
        } else {
            throw new IllegalArgumentException("Invalid certificate type. Allowed types: [pkcs12, jks]");
        }
    }

    private KeyStore loadKeyStore(Path path, String type, char[] password) {
        try(InputStream is = Files.newInputStream(path)) {
            KeyStore ks = KeyStore.getInstance(type);
            ks.load(is, password);
            return ks;
        } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException e) {
            throw new RuntimeException("Could not load certificate " + path, e);
        }
    }

    private static boolean isNullOrEmpty(String string){
        return string == null || string.trim().isEmpty();
    }

    private static class CertAndKeyPair{

        static final CertAndKeyPair EMPTY = new CertAndKeyPair(null, null);

        private String certificate;
        private String privateKey;

        CertAndKeyPair(String certificate, String privateKey) {
            this.certificate = certificate;
            this.privateKey = privateKey;
        }

        String getCertificate() {
            return certificate;
        }

        String getPrivateKey() {
            return privateKey;
        }

        boolean isEmpty(){
            return isNullOrEmpty(certificate) || isNullOrEmpty(privateKey);
        }

    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy