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

org.eclipse.ditto.connectivity.service.messaging.internal.ssl.Keys Maven / Gradle / Ivy

/*
 * Copyright (c) 2021 Contributors to the Eclipse Foundation
 *
 * See the NOTICE file(s) distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0
 *
 * SPDX-License-Identifier: EPL-2.0
 */
package org.eclipse.ditto.connectivity.service.messaging.internal.ssl;

import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.ditto.base.model.exceptions.DittoRuntimeExceptionBuilder;

/**
 * Helper class to load certificates, private and public keys.
 */
final class Keys {

    private static final String PRIVATE_KEY_LABEL = "PRIVATE KEY";
    private static final Pattern PRIVATE_KEY_REGEX = Pattern.compile(pemRegex(PRIVATE_KEY_LABEL));
    private static final String PUBLIC_KEY_LABEL = "PUBLIC KEY";
    private static final Pattern PUBLIC_KEY_REGEX = Pattern.compile(pemRegex(PUBLIC_KEY_LABEL));

    private static final KeyFactory RSA_KEY_FACTORY;
    private static final CertificateFactory X509_CERTIFICATE_FACTORY;

    static {
        try {
            RSA_KEY_FACTORY = KeyFactory.getInstance("RSA");
            X509_CERTIFICATE_FACTORY = CertificateFactory.getInstance("X.509");
        } catch (final NoSuchAlgorithmException e) {
            throw new Error("FATAL: failed to load RSA key or key manager factory", e);
        } catch (final CertificateException e) {
            throw new Error("FATAL: failed to load X.509 certificate factory", e);
        }
    }

    private Keys() {}

    /**
     * Loads a {@link java.security.PrivateKey} from the given string. The private key must be given in PKCS#8 format.
     *
     * @param privateKeyPem private key in PKCS#8 format
     * @param exceptionMapper maps ssl exceptions to ditto exceptions providing context information
     * @return the private key
     */
    static PrivateKey getPrivateKey(final String privateKeyPem, final ExceptionMapper exceptionMapper) {
        final Matcher matcher = PRIVATE_KEY_REGEX.matcher(privateKeyPem);
        final Supplier> errorSupplier =
                () -> exceptionMapper.badPrivateKeyFormat(PRIVATE_KEY_LABEL, "PKCS #8")
                        .description("Please format your client key as PEM-encoded unencrypted PKCS #8.");
        try {
            return RSA_KEY_FACTORY.generatePrivate(matchKey(matcher, PKCS8EncodedKeySpec::new, errorSupplier));
        } catch (final InvalidKeySpecException e) {
            throw errorSupplier.get().cause(e).build();
        }
    }

    /**
     * Loads a {@link java.security.PublicKey} from the given string. The public key must be given in PKCS#8 format.
     *
     * @param publicKeyPem public key in PKCS#8 format
     * @param exceptionMapper maps ssl exceptions to ditto exceptions providing context information
     * @return the public key
     */
    static PublicKey getPublicKey(final String publicKeyPem, final ExceptionMapper exceptionMapper) {
        final Matcher matcher = PUBLIC_KEY_REGEX.matcher(publicKeyPem);
        final Supplier> errorSupplier =
                () -> exceptionMapper.badPublicKeyFormat(PUBLIC_KEY_LABEL, "PKCS #8")
                        .description("Please format your public key as PEM-encoded unencrypted PKCS #8.");
        try {
            return RSA_KEY_FACTORY.generatePublic(matchKey(matcher, X509EncodedKeySpec::new, errorSupplier));
        } catch (final InvalidKeySpecException e) {
            throw errorSupplier.get().cause(e).build();
        }
    }

    private static  T matchKey(final Matcher matcher, final Function toKeySpec,
            final Supplier> errorSupplier) {
        if (!matcher.matches()) {
            throw errorSupplier.get().build();
        } else {
            final String content = matcher.group(1).replaceAll("\\s", "");
            final byte[] bytes = decodeBase64(content);
            return toKeySpec.apply(bytes);
        }
    }

    /**
     * Loads a {@link java.security.cert.Certificate} from the given string. The private key must be given in PKCS#8 format.
     *
     * @param certificatePem certificate in X.509 format
     * @param exceptionMapper maps ssl exceptions to ditto exceptions providing context information
     * @return the certificate
     */
    static Certificate getCertificate(final String certificatePem, final ExceptionMapper exceptionMapper) {
        final byte[] asciiBytes = certificatePem.getBytes(StandardCharsets.US_ASCII);
        try {
            return X509_CERTIFICATE_FACTORY.generateCertificate(new ByteArrayInputStream(asciiBytes));
        } catch (final CertificateException e) {
            throw exceptionMapper.badCertificateFormat("CERTIFICATE", "DER").build();
        }
    }

    /**
     * Create PEM regex specified by RFC-7468 section 3 "ABNF".
     *
     * @param label the label.
     * @return regex to capture base64text of textualmsg.
     */
    private static String pemRegex(final String label) {
        final String preeb = String.format("\\s*+-----BEGIN %s-----", label);
        final String posteb = String.format("-----END %s-----\\s*+", label);
        final String contentWhitespaceGroup = "([A-Za-z0-9+/=\\s]*+)";
        return String.format("%s%s%s", preeb, contentWhitespaceGroup, posteb);
    }

    private static byte[] decodeBase64(final String content) {
        return Base64.getDecoder().decode(content.replace("\\s", ""));
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy