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

de.dentrassi.crypto.pem.PemReader Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2024 Red Hat Inc and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Domenico Francesco Bruscino - initial PemReader implementation
 */

package de.dentrassi.crypto.pem;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Reader;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.interfaces.ECPrivateKey;
import java.security.spec.DSAPrivateKeySpec;
import java.security.spec.DSAPublicKeySpec;
import java.security.spec.ECPoint;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;

import com.hierynomus.asn1.ASN1InputStream;
import com.hierynomus.asn1.ASN1OutputStream;
import com.hierynomus.asn1.encodingrules.der.DERDecoder;
import com.hierynomus.asn1.encodingrules.der.DEREncoder;
import com.hierynomus.asn1.types.ASN1Object;
import com.hierynomus.asn1.types.constructed.ASN1Sequence;
import com.hierynomus.asn1.types.constructed.ASN1TaggedObject;
import com.hierynomus.asn1.types.primitive.ASN1Integer;
import com.hierynomus.asn1.types.primitive.ASN1ObjectIdentifier;
import com.hierynomus.asn1.types.string.ASN1BitString;
import com.hierynomus.asn1.types.string.ASN1OctetString;

public class PemReader extends BufferedReader {

    private static final String BEGIN = "-----BEGIN ";
    private static final String CERTIFICATE = "CERTIFICATE";
    private static final String X509_CERTIFICATE = "X509 CERTIFICATE";
    private static final String EC_PRIVATE_KEY = "EC PRIVATE KEY";
    private static final String EC_PUBLIC_KEY_OBJ_ID = "1.2.840.10045.2.1";
    private static final String DSA_PRIVATE_KEY = "DSA PRIVATE KEY";
    private static final String RSA_PRIVATE_KEY = "RSA PRIVATE KEY";
    private static final String PRIVATE_KEY = "PRIVATE KEY";
    private static final String END = "-----END ";
    private static final List KEY_ALGORITHMS = Arrays.asList("RSA", "DSA", "EC");

    private static List keyFactories;

    private static List getKeyFactories() {
        if (keyFactories == null) {
            keyFactories = new ArrayList<>();

            KEY_ALGORITHMS.forEach(s -> {
                try {
                    keyFactories.add(KeyFactory.getInstance(s));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }

        return keyFactories;
    }

    public PemReader(Reader in) {
        super(in);
    }

    public Object readObject() throws CertificateException, IOException {
        byte[] objectContent = null;
        String objectType = null;

        String line = readLine();

        while (line != null && !line.startsWith(BEGIN)) {
            line = readLine();
        }

        if (line != null) {
            line = line.substring(BEGIN.length()).trim();
            int index = line.indexOf('-');

            if (index > 0 && line.endsWith("-----") && (line.length() - index) == 5) {
                objectType = line.substring(0, index);

                StringBuffer buffer = new StringBuffer();
                String endMarker = END + objectType + "-----";
                while ((line = readLine()) != null && line.indexOf(endMarker) != 0) {
                    if (line.indexOf(':') < 0) {
                        buffer.append(line.trim());
                    }
                }
                objectContent = Base64.getDecoder().decode(buffer.toString());
            }
        }

        if (objectContent != null) {
            if (CERTIFICATE.equals(objectType) || X509_CERTIFICATE.equals(objectType)) {
                CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
                try (ByteArrayInputStream contentInputStream = new ByteArrayInputStream(objectContent)) {
                    return certificateFactory.generateCertificate(contentInputStream);
                }
            } else if (EC_PRIVATE_KEY.equals(objectType)) {
            /*
            https://datatracker.ietf.org/doc/html/rfc5915
            ECPrivateKey ::= SEQUENCE {
              version        INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
              privateKey     OCTET STRING,
              parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
              publicKey  [1] BIT STRING OPTIONAL
            }
            */
                try (ASN1InputStream asn1In = new ASN1InputStream(new DERDecoder(), objectContent)) {
                    ASN1Sequence pkcs1Sequence = asn1In.readObject();
                    try (ByteArrayOutputStream pkcs8Out = new ByteArrayOutputStream()) {
                        try (ASN1OutputStream asn1Out = new ASN1OutputStream(new DEREncoder(), pkcs8Out)) {
                            List outObjects = new ArrayList<>();
                            outObjects.add(new ASN1Integer(0));
                            outObjects.add(new ASN1Sequence(Arrays.asList(new ASN1ObjectIdentifier(EC_PUBLIC_KEY_OBJ_ID), ((ASN1TaggedObject) pkcs1Sequence.get(2)).getObject())));
                            outObjects.add(new ASN1OctetString(objectContent));
                            asn1Out.writeObject(new ASN1Sequence(outObjects));
                        }

                        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pkcs8Out.toByteArray());
                        try {
                            KeyFactory ecKeyFactory = KeyFactory.getInstance("EC");

                            PublicKey ecPublicKey = null;
                            ECPrivateKey ecPrivateKey = (ECPrivateKey) ecKeyFactory.generatePrivate(keySpec);

                            if (pkcs1Sequence.size() > 3) {
                                byte[] ecPointBytes = ((ASN1BitString) ((ASN1TaggedObject) pkcs1Sequence.get(3)).getObject()).getValueBytes();
                                if (ecPointBytes[0] == 4) {
                                    byte[] ecPointXBytes = new byte[32];
                                    byte[] ecPointYBytes = new byte[32];
                                    System.arraycopy(ecPointBytes, 1, ecPointXBytes, 0, 32);
                                    System.arraycopy(ecPointBytes, 33, ecPointYBytes, 0, 32);
                                    ECPoint ecPoint = new ECPoint(new BigInteger(1, ecPointXBytes), new BigInteger(1, ecPointYBytes));
                                    ECPublicKeySpec publicKeySpec = new ECPublicKeySpec(ecPoint, ecPrivateKey.getParams());
                                    ecPublicKey = ecKeyFactory.generatePublic(publicKeySpec);
                                }
                            }

                            return new KeyPair(ecPublicKey, ecPrivateKey);
                        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
                            throw new IOException(e);
                        }
                    }
                }
            } else if (DSA_PRIVATE_KEY.equals(objectType)) {
            /*
            https://datatracker.ietf.org/doc/html/draft-woodhouse-cert-best-practice-01
            DSAPrivateKey ::= SEQUENCE {
                version  INTEGER, -- should be zero
                p        INTEGER,
                q        INTEGER,
                g        INTEGER,
                pub      INTEGER, -- public
                priv     INTEGER, -- private
            }
            */
                try (ASN1InputStream asn1InputStream = new ASN1InputStream(new DERDecoder(), objectContent)) {
                    ASN1Sequence pkcs1Sequence = asn1InputStream.readObject();

                    //BigInteger y, BigInteger p, BigInteger q, BigInteger g)
                    DSAPublicKeySpec publicKeySpec = new DSAPublicKeySpec(((ASN1Integer) pkcs1Sequence.get(4)).getValue(), ((ASN1Integer) pkcs1Sequence.get(1)).getValue(), ((ASN1Integer) pkcs1Sequence.get(2)).getValue(), ((ASN1Integer) pkcs1Sequence.get(3)).getValue());

                    //BigInteger x, BigInteger p, BigInteger q, BigInteger g
                    DSAPrivateKeySpec privateKeySpec = new DSAPrivateKeySpec(((ASN1Integer) pkcs1Sequence.get(5)).getValue(), ((ASN1Integer) pkcs1Sequence.get(1)).getValue(), ((ASN1Integer) pkcs1Sequence.get(2)).getValue(), ((ASN1Integer) pkcs1Sequence.get(3)).getValue());

                    try {
                        KeyFactory dsaKeyFactory = KeyFactory.getInstance("DSA");
                        PublicKey dsaPublicKey = dsaKeyFactory.generatePublic(publicKeySpec);
                        PrivateKey dsaPrivateKey = dsaKeyFactory.generatePrivate(privateKeySpec);
                        return new KeyPair(dsaPublicKey, dsaPrivateKey);
                    } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
                        throw new IOException(e);
                    }
                }
            } else if (RSA_PRIVATE_KEY.equals(objectType)) {
            /*
            https://datatracker.ietf.org/doc/html/rfc8017
            RSAPrivateKey ::= SEQUENCE {
                version           Version,
                modulus           INTEGER,  -- n
                publicExponent    INTEGER,  -- e
                privateExponent   INTEGER,  -- d
                prime1            INTEGER,  -- p
                prime2            INTEGER,  -- q
                exponent1         INTEGER,  -- d mod (p-1)
                exponent2         INTEGER,  -- d mod (q-1)
                coefficient       INTEGER,  -- (inverse of q) mod p
                otherPrimeInfos   OtherPrimeInfos OPTIONAL
            }
            */
                try (ASN1InputStream asn1InputStream = new ASN1InputStream(new DERDecoder(), objectContent)) {
                    ASN1Sequence pkcs1Sequence = asn1InputStream.readObject();

                    //BigInteger modulus, BigInteger publicExponent
                    RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(((ASN1Integer) pkcs1Sequence.get(1)).getValue(), ((ASN1Integer) pkcs1Sequence.get(2)).getValue());

                    //BigInteger modulus, BigInteger publicExponent, BigInteger privateExponent, BigInteger primeP, BigInteger primeQ, BigInteger primeExponentP, BigInteger primeExponentQ, BigInteger crtCoefficient
                    RSAPrivateCrtKeySpec privateKeySpec = new RSAPrivateCrtKeySpec(((ASN1Integer) pkcs1Sequence.get(1)).getValue(), ((ASN1Integer) pkcs1Sequence.get(2)).getValue(), ((ASN1Integer) pkcs1Sequence.get(3)).getValue(), ((ASN1Integer) pkcs1Sequence.get(4)).getValue(), ((ASN1Integer) pkcs1Sequence.get(5)).getValue(), ((ASN1Integer) pkcs1Sequence.get(6)).getValue(), ((ASN1Integer) pkcs1Sequence.get(7)).getValue(), ((ASN1Integer) pkcs1Sequence.get(8)).getValue());

                    try {
                        KeyFactory rsaKeyFactory = KeyFactory.getInstance("RSA");
                        PublicKey rsaPublicKey = rsaKeyFactory.generatePublic(publicKeySpec);
                        PrivateKey rsaPrivateKey = rsaKeyFactory.generatePrivate(privateKeySpec);
                        return new KeyPair(rsaPublicKey, rsaPrivateKey);
                    } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
                        throw new IOException(e);
                    }
                }
            } else if (PRIVATE_KEY.equals(objectType)) {
                PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(objectContent);

                InvalidKeySpecException firstException = null;
                for (KeyFactory factory : getKeyFactories()) {
                    try {
                        return factory.generatePrivate(keySpec);
                    } catch (InvalidKeySpecException e) {
                        if (firstException == null)
                            firstException = e;
                    }
                }
                throw new IOException("Private key could not be loaded", firstException);
            } else {
                throw new IOException("Invalid object: " + objectType);
            }
        }

        return null;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy