001/*
002 * acme4j - Java ACME client
003 *
004 * Copyright (C) 2015 Richard "Shred" Körber
005 *   http://acme4j.shredzone.org
006 *
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
013 */
014package org.shredzone.acme4j.util;
015
016import java.io.IOException;
017import java.io.Reader;
018import java.io.Writer;
019import java.security.InvalidAlgorithmParameterException;
020import java.security.KeyPair;
021import java.security.KeyPairGenerator;
022import java.security.NoSuchAlgorithmException;
023import java.security.NoSuchProviderException;
024import java.security.PublicKey;
025import java.security.SecureRandom;
026
027import org.bouncycastle.jce.ECNamedCurveTable;
028import org.bouncycastle.jce.provider.BouncyCastleProvider;
029import org.bouncycastle.openssl.PEMException;
030import org.bouncycastle.openssl.PEMKeyPair;
031import org.bouncycastle.openssl.PEMParser;
032import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
033import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
034
035/**
036 * Utility class offering convenience methods for {@link KeyPair}.
037 * <p>
038 * Requires {@code Bouncy Castle}.
039 */
040public class KeyPairUtils {
041
042    private KeyPairUtils() {
043        // utility class without constructor
044    }
045
046    /**
047     * Creates a new standard {@link KeyPair}.
048     * <p>
049     * This method can be used if no specific key type is required. It returns a
050     * "secp384r1" ECDSA key pair.
051     *
052     * @return Generated {@link KeyPair}
053     * @since 2.8
054     */
055    public static KeyPair createKeyPair() {
056        return createECKeyPair("secp384r1");
057    }
058
059    /**
060     * Creates a new RSA {@link KeyPair}.
061     *
062     * @param keysize
063     *            Key size
064     * @return Generated {@link KeyPair}
065     */
066    public static KeyPair createKeyPair(int keysize) {
067        try {
068            var keyGen = KeyPairGenerator.getInstance("RSA");
069            keyGen.initialize(keysize);
070            return keyGen.generateKeyPair();
071        } catch (NoSuchAlgorithmException ex) {
072            throw new IllegalStateException(ex);
073        }
074    }
075
076    /**
077     * Creates a new elliptic curve {@link KeyPair}.
078     *
079     * @param name
080     *            ECDSA curve name (e.g. "secp256r1")
081     * @return Generated {@link KeyPair}
082     */
083    public static KeyPair createECKeyPair(String name) {
084        try {
085            var ecSpec = ECNamedCurveTable.getParameterSpec(name);
086            var g = KeyPairGenerator.getInstance("ECDSA", BouncyCastleProvider.PROVIDER_NAME);
087            g.initialize(ecSpec, new SecureRandom());
088            return g.generateKeyPair();
089        } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException ex) {
090            throw new IllegalArgumentException("Invalid curve name " + name, ex);
091        } catch (NoSuchProviderException ex) {
092            throw new IllegalStateException(ex);
093        }
094    }
095
096    /**
097     * Reads a {@link KeyPair} from a PEM file.
098     *
099     * @param r
100     *            {@link Reader} to read the PEM file from. The {@link Reader} is closed
101     *            after use.
102     * @return {@link KeyPair} read
103     */
104    public static KeyPair readKeyPair(Reader r) throws IOException {
105        try (var parser = new PEMParser(r)) {
106            var keyPair = (PEMKeyPair) parser.readObject();
107            return new JcaPEMKeyConverter().getKeyPair(keyPair);
108        } catch (PEMException ex) {
109            throw new IOException("Invalid PEM file", ex);
110        }
111    }
112
113    /**
114     * Writes a {@link KeyPair} PEM file.
115     *
116     * @param keypair
117     *            {@link KeyPair} to write
118     * @param w
119     *            {@link Writer} to write the PEM file to. The {@link Writer} is closed
120     *            after use.
121     */
122    public static void writeKeyPair(KeyPair keypair, Writer w) throws IOException {
123        try (var jw = new JcaPEMWriter(w)) {
124            jw.writeObject(keypair);
125        }
126    }
127
128    /**
129     * Writes a {@link PublicKey} as PEM file.
130     *
131     * @param key
132     *            {@link PublicKey}
133     * @param w
134     *            {@link Writer} to write the PEM file to. The {@link Writer} is closed
135     *            after use.
136     * @since 3.0.0
137     */
138    public static void writePublicKey(PublicKey key, Writer w) throws IOException {
139        try (var jw = new JcaPEMWriter(w)) {
140            jw.writeObject(key);
141        }
142    }
143
144}