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

be.atbash.ee.security.octopus.keys.reader.KeyReaderPEM Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2017-2022 Rudy De Busscher (https://www.atbash.be)
 *
 * 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 be.atbash.ee.security.octopus.keys.reader;

import be.atbash.ee.security.octopus.exception.MissingPasswordException;
import be.atbash.ee.security.octopus.exception.MissingPasswordLookupException;
import be.atbash.ee.security.octopus.exception.ResourceNotFoundException;
import be.atbash.ee.security.octopus.keys.AtbashKey;
import be.atbash.ee.security.octopus.keys.reader.password.KeyResourcePasswordLookup;
import be.atbash.ee.security.octopus.nimbus.jose.crypto.bc.BouncyCastleProviderSingleton;
import be.atbash.util.StringUtils;
import be.atbash.util.exception.AtbashUnexpectedException;
import be.atbash.util.resource.ResourceUtil;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.DERNull;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.pkcs.RSAPrivateKey;
import org.bouncycastle.asn1.pkcs.RSAPublicKey;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.openssl.PEMDecryptorProvider;
import org.bouncycastle.openssl.PEMEncryptedKeyPair;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;
import org.bouncycastle.operator.InputDecryptorProvider;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
import org.bouncycastle.pkcs.PKCSException;

import java.io.*;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.List;

public class KeyReaderPEM {
    public List readResource(String path, KeyResourcePasswordLookup passwordLookup) {
        List result;

        try {
            ResourceUtil resourceUtil = ResourceUtil.getInstance();
            if (!resourceUtil.resourceExists(path)) {
                throw new ResourceNotFoundException(path);
            }

            InputStream inputStream = resourceUtil.getStream(path);
            if (inputStream == null) {
                throw new KeyResourceNotFoundException(path);
            }
            Reader reader = new InputStreamReader(inputStream);

            result = parseContent(reader, path, passwordLookup);
        } catch (IOException | PKCSException | OperatorCreationException e) {
            throw new AtbashUnexpectedException(e);
        }

        return result;
    }

    public List parseContent(String content) {
        return parseContent(content, null);
    }

    public List parseContent(String content, KeyResourcePasswordLookup passwordLookup) {
        StringReader reader = new StringReader(content);
        List result;
        try {
            result = parseContent(reader, "inline", passwordLookup);
        } catch (IOException | PKCSException | OperatorCreationException e) {
            throw new AtbashUnexpectedException(e);
        }

        return result;
    }

    /**
     * Parses the content trying supporting different PEM based encodings.  If the content is not
     * a PEM based encoding, it returns an empty list.  The method can throw also various BouncyCastle
     * Exception to indicate problems with the PEM byteds.
     *
     * @param reader         Reader providing the contents.
     * @param path           The Path or identification of the content that will be used by the passwordLookup if needed.
     * @param passwordLookup Provides the password or passphrase if the PEM data is encoded.
     * @return List of found Keys or null when not a valid PEM content
     * @throws IOException               problem during the read of the content
     * @throws OperatorCreationException
     * @throws PKCSException
     */
    protected List parseContent(Reader reader, String path, KeyResourcePasswordLookup passwordLookup) throws IOException, OperatorCreationException, PKCSException {

        List result = new ArrayList<>();

        PEMParser pemParser = new PEMParser(reader);
        Object pemData = pemParser.readObject();
        reader.close();

        Provider provider = BouncyCastleProviderSingleton.getInstance();
        JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(provider);
        if (pemData instanceof PEMEncryptedKeyPair) {
            if (passwordLookup == null) {
                throw new MissingPasswordLookupException();
            }

            // Encrypted key - we will use provided password
            PEMEncryptedKeyPair ckp = (PEMEncryptedKeyPair) pemData;

            char[] passphrase = passwordLookup.getResourcePassword(path);
            if (StringUtils.isEmpty(passphrase)) {
                throw new MissingPasswordException(MissingPasswordException.ObjectType.STORE, path);
            }

            PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().setProvider(provider).build(passphrase);

            // Untested for EC key due to https://github.com/kaikramer/keystore-explorer/issues/119
            PEMKeyPair keyPair = ckp.decryptKeyPair(decProv);
            KeyPair pair = converter.getKeyPair(keyPair);
            result.add(new AtbashKey(path, pair.getPrivate()));
            result.add(new AtbashKey(path, pair.getPublic()));
        }
        if (pemData instanceof PKCS8EncryptedPrivateKeyInfo) {
            if (passwordLookup == null) {
                throw new MissingPasswordLookupException();
            }

            PKCS8EncryptedPrivateKeyInfo privateKeyInfo = (PKCS8EncryptedPrivateKeyInfo) pemData;

            JceOpenSSLPKCS8DecryptorProviderBuilder providerBuilder = new JceOpenSSLPKCS8DecryptorProviderBuilder();
            providerBuilder.setProvider(provider);
            char[] passphrase = passwordLookup.getResourcePassword(path);

            if (StringUtils.isEmpty(passphrase)) {
                throw new MissingPasswordException(MissingPasswordException.ObjectType.STORE, path);
            }

            InputDecryptorProvider inputDecryptorProvider = providerBuilder.build(passphrase);
            PrivateKeyInfo info = privateKeyInfo.decryptPrivateKeyInfo(inputDecryptorProvider);
            PrivateKey privateKey = converter.getPrivateKey(info);
            result.add(new AtbashKey(path, privateKey));

        }
        if (pemData instanceof SubjectPublicKeyInfo) {
            // Unencrypted key - no password needed
            PublicKey publicKey = converter.getPublicKey((SubjectPublicKeyInfo) pemData);
            result.add(new AtbashKey(path, publicKey));

        }
        if (pemData instanceof PrivateKeyInfo) {
            // Unencrypted key
            PrivateKeyInfo info = (PrivateKeyInfo) pemData;
            pemData = convertPrivateKeyFromPKCS8ToPKCS1(info);

        }

        if (pemData instanceof PEMKeyPair) {
            PEMKeyPair keyPair = (PEMKeyPair) pemData;
            PrivateKey privateKey = converter.getPrivateKey(keyPair.getPrivateKeyInfo());
            PublicKey publicKey = converter.getPublicKey(keyPair.getPublicKeyInfo());
            result.add(new AtbashKey(path, privateKey));
            result.add(new AtbashKey(path, publicKey));
        }

        return result;
    }

    private static PEMKeyPair convertPrivateKeyFromPKCS8ToPKCS1(PrivateKeyInfo privateKeyInfo) throws IOException {
        // Parse the key wrapping to determine the internal key structure
        ASN1Encodable asn1PrivateKey = privateKeyInfo.parsePrivateKey();
        // Convert the parsed key to an RSA private key
        RSAPrivateKey keyStruct = RSAPrivateKey.getInstance(asn1PrivateKey);
        // Create the RSA public key from the modulus and exponent
        RSAPublicKey pubSpec = new RSAPublicKey(
                keyStruct.getModulus(), keyStruct.getPublicExponent());
        // Create an algorithm identifier for forming the key pair
        AlgorithmIdentifier algId = new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE);

        // Create the key pair container
        return new PEMKeyPair(new SubjectPublicKeyInfo(algId, pubSpec), new PrivateKeyInfo(algId, keyStruct));
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy