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.InputStream;
018import java.io.InputStreamReader;
019import java.io.OutputStream;
020import java.io.OutputStreamWriter;
021import java.io.Writer;
022import java.security.cert.CertificateException;
023import java.security.cert.CertificateFactory;
024import java.security.cert.X509Certificate;
025
026import org.bouncycastle.openssl.PEMParser;
027import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
028import org.bouncycastle.pkcs.PKCS10CertificationRequest;
029import org.shredzone.acme4j.Certificate;
030
031/**
032 * Utility class offering convenience methods for certificates.
033 * <p>
034 * Requires {@code Bouncy Castle}. This class is part of the {@code acme4j-utils} module.
035 */
036public final class CertificateUtils {
037
038    private CertificateUtils() {
039        // utility class without constructor
040    }
041
042    /**
043     * Reads an {@link X509Certificate} PEM file from an {@link InputStream}.
044     *
045     * @param in
046     *            {@link InputStream} to read the certificate from. The
047     *            {@link InputStream} is closed after use.
048     * @return {@link X509Certificate} that was read
049     */
050    public static X509Certificate readX509Certificate(InputStream in) throws IOException {
051        try (InputStream uin = in) {
052            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
053            return (X509Certificate) certificateFactory.generateCertificate(uin);
054        } catch (CertificateException ex) {
055            throw new IOException(ex);
056        }
057    }
058
059    /**
060     * Writes an X.509 certificate PEM file.
061     *
062     * @param cert
063     *            {@link X509Certificate} to write
064     * @param out
065     *            {@link OutputStream} to write the PEM file to. The {@link OutputStream}
066     *            is closed after use.
067     */
068    public static void writeX509Certificate(X509Certificate cert, OutputStream out) throws IOException {
069        writeX509Certificate(cert, new OutputStreamWriter(out, "utf-8"));
070    }
071
072    /**
073     * Writes an X.509 certificate PEM file.
074     *
075     * @param cert
076     *            {@link X509Certificate} to write
077     * @param w
078     *            {@link Writer} to write the PEM file to. The {@link Writer} is closed
079     *            after use.
080     */
081    public static void writeX509Certificate(X509Certificate cert, Writer w) throws IOException {
082        writeX509Certificates(w, cert);
083    }
084
085    /**
086     * Writes a X.509 certificate chain to a PEM file.
087     *
088     * @param w
089     *            {@link Writer} to write the certificate chain to. The {@link Writer} is
090     *            closed after use.
091     * @param cert
092     *            {@link X509Certificate} to write, {@code null} to skip this certificate
093     * @param chain
094     *            {@link X509Certificate} chain to add to the certificate. {@code null}
095     *            values are ignored, array may be empty.
096     * @deprecated use {@link Certificate#downloadFullChain()} and
097     *             {@link #writeX509Certificates(Writer, X509Certificate[])} instead
098     */
099    @Deprecated
100    public static void writeX509CertificateChain(Writer w, X509Certificate cert, X509Certificate... chain)
101                throws IOException {
102        X509Certificate[] certs = new X509Certificate[chain.length + 1];
103        certs[0] = cert;
104        System.arraycopy(chain, 0, certs, 1, chain.length);
105        writeX509Certificates(w, certs);
106    }
107
108    /**
109     * Writes multiple X.509 certificates to a PEM file.
110     *
111     * @param w
112     *            {@link Writer} to write the certificate chain to. The {@link Writer} is
113     *            closed after use.
114     * @param certs
115     *            {@link X509Certificate} certificates to add to the certificate.
116     *            {@code null} values are ignored, array may be empty.
117     * @since 1.1
118     */
119    public static void writeX509Certificates(Writer w, X509Certificate... certs)
120                throws IOException {
121        try (JcaPEMWriter jw = new JcaPEMWriter(w)) {
122            for (X509Certificate c : certs) {
123                if (c != null) {
124                    jw.writeObject(c);
125                }
126            }
127        }
128    }
129
130    /**
131     * Reads a CSR PEM file.
132     *
133     * @param in
134     *            {@link InputStream} to read the CSR from. The {@link InputStream} is
135     *            closed after use.
136     * @return CSR that was read
137     */
138    public static PKCS10CertificationRequest readCSR(InputStream in) throws IOException {
139        try (PEMParser pemParser = new PEMParser(new InputStreamReader(in))) {
140            Object parsedObj = pemParser.readObject();
141            if (!(parsedObj instanceof PKCS10CertificationRequest)) {
142                throw new IOException("Not a PKCS10 CSR");
143            }
144            return (PKCS10CertificationRequest) parsedObj;
145        }
146    }
147
148}