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

com.metaeffekt.artifact.analysis.flow.ng.KeypairGenerator Maven / Gradle / Ivy

There is a newer version: 0.126.0
Show newest version
/*
 * Copyright 2021-2024 the original author or authors.
 *
 * 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 com.metaeffekt.artifact.analysis.flow.ng;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.metaeffekt.artifact.analysis.flow.ng.keyholder.UserKeysForConsumer;
import com.metaeffekt.artifact.analysis.flow.ng.keyholder.UserKeysForSupplier;
import com.metaeffekt.artifact.analysis.flow.ng.keyholder.UserKeysStorage;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.generators.RSAKeyPairGenerator;
import org.bouncycastle.crypto.params.RSAKeyGenerationParameters;
import org.bouncycastle.crypto.params.RSAKeyParameters;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import java.security.SecureRandom;
import java.time.Instant;
import java.util.HashMap;
import java.util.UUID;

public class KeypairGenerator {

    // WARNING: changing these constants may influence security properties.
    // also, changing these may cause incompatibility.
    private static final BigInteger publicExponent = BigInteger.valueOf(65535);
    private static final int keySize = 4096;

    public static final String supplierKeysFileEnding = ".supplier.keys.json";
    public static final String consumerKeysFileEnding = ".consumer.keys";
    public static final String consumerKeysMetadataFileEnding = ".meta.json";

    private static final Logger LOG = LoggerFactory.getLogger(KeypairGenerator.class);

    public static String generateUserKeysPassword(int length) {
        SecureRandom random = new SecureRandom();

        StringBuilder passwordBuilder = new StringBuilder();

        // count and limit generation attempts as a cheap sanity-check at runtime
        long failureCounter = 0;
        long maxFailures = length * 128L;

        while (passwordBuilder.length() < length) {
            // get random integer truncated to 8 bits to only cover the ascii range
            int randInt = random.nextInt(256);

            // clip to ascii ranges
            if (('a' < randInt && randInt < 'z') || ('A' < randInt && randInt < 'Z')) {
                passwordBuilder.appendCodePoint(randInt);
            } else {
                failureCounter++;
                if (failureCounter > maxFailures) {
                    throw new RuntimeException("should never happen: failure count exceeded while generating password");
                }
            }
        }

        return passwordBuilder.toString();
    }

    /**
     * Generates a new keypair using the supplied random.
* Returns a pair where left is the "ForSupplier" part and right is "ForConsumer". * @param random the random to use in generation. * @return returns a keypair. Left is "ForSupplier", right "ForConsumer". */ public static Pair createUserKeyPair(SecureRandom random) { RSAKeyPairGenerator gen = new RSAKeyPairGenerator(); gen.init(new RSAKeyGenerationParameters(publicExponent, random, keySize, 256)); AsymmetricCipherKeyPair kp = gen.generateKeyPair(); RSAKeyParameters publicKey = (RSAKeyParameters) kp.getPublic(); RSAKeyParameters privateKey = (RSAKeyParameters) kp.getPrivate(); byte[] hmacSecretKey = new byte[32]; random.nextBytes(hmacSecretKey); UserKeysForSupplier keysForSupplier = new UserKeysForSupplier(publicKey, hmacSecretKey); UserKeysForConsumer keysForConsumer = new UserKeysForConsumer(privateKey, hmacSecretKey); return Pair.of(keysForSupplier, keysForConsumer); } public static void writeMetadataFile(String keyUuid, String password, File outputFile) throws IOException { HashMap interMap = new HashMap<>(); interMap.put("userPassword", password); interMap.put("creationDate", Instant.now().toString()); interMap.put("keyUuid", keyUuid); try (OutputStream fos = Files.newOutputStream(outputFile.toPath(), StandardOpenOption.CREATE_NEW); OutputStreamWriter writer = new OutputStreamWriter(fos, StandardCharsets.UTF_8)) { new ObjectMapper().writerWithDefaultPrettyPrinter().writeValue(writer, interMap); } } public static void generateNewUserKeypair(KeypairGeneratorSpec spec) throws IOException { // filename as a user identifier. intended to uniquely associate a keypair with its user. final String keyUuid = UUID.randomUUID().toString(); // ascii password used to encrypt the user keyfile. final String consumerKeyfileEncryptionPassword = generateUserKeysPassword(spec.getPasswordLength()); // basic checks, abort early if anything is amiss if (StringUtils.isBlank(spec.getReadableIdentifier())) { throw new IllegalArgumentException("The readable identifier must not be blank."); } if (StringUtils.isBlank(consumerKeyfileEncryptionPassword)) { throw new IllegalArgumentException("Generation requires a non-blank password."); } // i doubt anyone would configure the length this low but let's implement some warnings if (consumerKeyfileEncryptionPassword.length() < 8) { LOG.warn("Password is very short!"); } if (consumerKeyfileEncryptionPassword.length() < 23) { LOG.warn("Password doesn't contain good security (should have at least 23 characters, 24 recommended)."); } final File identifiableKeyOutputDir = new File(spec.getKeyOutputDir(), spec.getReadableIdentifier()); // where supplier's and consumer's keys will be put final File forSupplierDir = new File(identifiableKeyOutputDir, "supplier"); final File forConsumerDir = new File(identifiableKeyOutputDir, "consumer"); // create files final File supplierOutputFile = new File(forSupplierDir, keyUuid + supplierKeysFileEnding); final File consumerEncryptedOutputFile = new File(forConsumerDir, keyUuid + consumerKeysFileEnding); final File consumerPasswordOutputFile = new File(forConsumerDir, keyUuid + consumerKeysMetadataFileEnding); // refuse output if any part of a keypair of the exact same identifier is already present if (supplierOutputFile.exists()) { throw new IllegalStateException("Won't write keys: supplier key file with specified id exists!"); } if (consumerEncryptedOutputFile.exists()) { throw new IllegalStateException("Won't write keys: consumer key file with specified id exists!"); } if (consumerPasswordOutputFile.exists()) { throw new IllegalStateException("Won't write keys: consumer password file with specified id exists!"); } // generate the keypair SecureRandom random = new SecureRandom(); LOG.info("Beginning generation for uuid '" + keyUuid + "'"); Pair keypair = createUserKeyPair(random); UserKeysForSupplier keysForSupplier = keypair.getLeft(); UserKeysForConsumer keysForConsumer = keypair.getRight(); LOG.info("Beginning write for uuid '" + keyUuid + "'"); // create directories if (!forSupplierDir.isDirectory()) { if (!forSupplierDir.mkdirs()) { LOG.error("Couldn't create dir at '" + forSupplierDir.getPath() + "'."); throw new IOException("Could not create forSupplierDir."); } } if (!forConsumerDir.isDirectory()) { if (!forConsumerDir.mkdirs()) { LOG.error("Couldn't create dir at '" + forConsumerDir.getPath() + "'."); throw new IOException("Could not create forConsumerDir."); } } // write keys UserKeysStorage.writeUserKeys(keysForSupplier, supplierOutputFile); // write the consumer key to the output file (encrypted with the password) UserKeysStorage.writeUserKeys(keysForConsumer, consumerKeyfileEncryptionPassword, consumerEncryptedOutputFile); // write the password to the output directory writeMetadataFile(keyUuid, consumerKeyfileEncryptionPassword, consumerPasswordOutputFile); LOG.info("Successfully wrote key '" + keyUuid + "'"); // check that the output keys are readable UserKeysStorage.readUserKeysForConsumer(consumerEncryptedOutputFile, consumerKeyfileEncryptionPassword, true); UserKeysStorage.readUserKeysForPublisher(supplierOutputFile, true); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy