Java tutorial
/* * acme4j - Java ACME client * * Copyright (C) 2015 Richard "Shred" Krber * http://acme4j.shredzone.org * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ package org.shredzone.acme4j.util; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.math.BigInteger; import java.security.KeyPair; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.Date; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x509.Extension; import org.bouncycastle.asn1.x509.GeneralName; import org.bouncycastle.asn1.x509.GeneralNames; import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; import org.bouncycastle.openssl.PEMParser; import org.bouncycastle.openssl.jcajce.JcaPEMWriter; import org.bouncycastle.operator.OperatorCreationException; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import org.bouncycastle.pkcs.PKCS10CertificationRequest; import org.shredzone.acme4j.challenge.TlsSni02Challenge; /** * Utility class offering convenience methods for certificates. * <p> * Requires {@code Bouncy Castle}. * * @author Richard "Shred" Krber */ public final class CertificateUtils { private CertificateUtils() { // utility class without constructor } /** * Reads an {@link X509Certificate} PEM file from an {@link InputStream}. * * @param in * {@link InputStream} to read the certificate from. * @return {@link X509Certificate} that was read */ public static X509Certificate readX509Certificate(InputStream in) throws IOException { try (InputStream uin = in) { CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); return (X509Certificate) certificateFactory.generateCertificate(uin); } catch (CertificateException ex) { throw new IOException(ex); } } /** * Writes an X.509 certificate PEM file. * * @param cert * {@link X509Certificate} to write * @param out * {@link OutputStream} to write the PEM file to */ public static void writeX509Certificate(X509Certificate cert, OutputStream out) throws IOException { writeX509Certificate(cert, new OutputStreamWriter(out, "utf-8")); } /** * Writes an X.509 certificate PEM file. * * @param cert * {@link X509Certificate} to write * @param w * {@link Writer} to write the PEM file to */ public static void writeX509Certificate(X509Certificate cert, Writer w) throws IOException { try (JcaPEMWriter jw = new JcaPEMWriter(w)) { jw.writeObject(cert); } } /** * Writes an X.509 certificate chain PEM file. * * @param chain * {@link X509Certificate[]} to write * @param w * {@link Writer} to write the PEM file to */ public static void writeX509CertificateChain(X509Certificate[] chain, Writer w) throws IOException { try (JcaPEMWriter jw = new JcaPEMWriter(w)) { for (X509Certificate cert : chain) { jw.writeObject(cert); } } } /** * Reads a CSR PEM file. * * @param in * {@link InputStream} to read the CSR from. * @return CSR that was read */ public static PKCS10CertificationRequest readCSR(InputStream in) throws IOException { try (PEMParser pemParser = new PEMParser(new InputStreamReader(in))) { Object parsedObj = pemParser.readObject(); if (!(parsedObj instanceof PKCS10CertificationRequest)) { throw new IOException("Not a PKCS10 CSR"); } return (PKCS10CertificationRequest) parsedObj; } } /** * Creates a self-signed {@link X509Certificate} that can be used for * {@link org.shredzone.acme4j.challenge.TlsSni01Challenge}. The certificate is valid * for 7 days. * * @param keypair * A domain {@link KeyPair} to be used for the challenge * @param subject * Subject to create a certificate for * @return Created certificate * @deprecated Will be removed when * {@link org.shredzone.acme4j.challenge.TlsSni01Challenge} is removed */ @Deprecated public static X509Certificate createTlsSniCertificate(KeyPair keypair, String subject) throws IOException { final long now = System.currentTimeMillis(); final long validSpanMs = 7 * 24 * 60 * 60 * 1000L; final String signatureAlg = "SHA256withRSA"; try { X500Name issuer = new X500Name("CN=acme.invalid"); BigInteger serial = BigInteger.valueOf(now); Date notBefore = new Date(now); Date notAfter = new Date(now + validSpanMs); JcaX509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(issuer, serial, notBefore, notAfter, issuer, keypair.getPublic()); GeneralName[] gns = new GeneralName[1]; gns[0] = new GeneralName(GeneralName.dNSName, subject); certBuilder.addExtension(Extension.subjectAlternativeName, false, new GeneralNames(gns)); JcaContentSignerBuilder signerBuilder = new JcaContentSignerBuilder(signatureAlg); byte[] cert = certBuilder.build(signerBuilder.build(keypair.getPrivate())).getEncoded(); CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); return (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(cert)); } catch (CertificateException | OperatorCreationException ex) { throw new IOException(ex); } } /** * Creates a self-signed {@link X509Certificate} that can be used for * {@link TlsSni02Challenge}. The certificate is valid for 7 days. * * @param keypair * A domain {@link KeyPair} to be used for the challenge * @param sanA * SAN-A to be used in the certificate * @param sanB * SAN-B to be used in the certificate * @return Created certificate */ public static X509Certificate createTlsSni02Certificate(KeyPair keypair, String sanA, String sanB) throws IOException { final long now = System.currentTimeMillis(); final long validSpanMs = 7 * 24 * 60 * 60 * 1000L; final String signatureAlg = "SHA256withRSA"; try { X500Name issuer = new X500Name("CN=acme.invalid"); BigInteger serial = BigInteger.valueOf(now); Date notBefore = new Date(now); Date notAfter = new Date(now + validSpanMs); JcaX509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(issuer, serial, notBefore, notAfter, issuer, keypair.getPublic()); GeneralName[] gns = new GeneralName[2]; gns[0] = new GeneralName(GeneralName.dNSName, sanA); gns[1] = new GeneralName(GeneralName.dNSName, sanB); certBuilder.addExtension(Extension.subjectAlternativeName, false, new GeneralNames(gns)); JcaContentSignerBuilder signerBuilder = new JcaContentSignerBuilder(signatureAlg); byte[] cert = certBuilder.build(signerBuilder.build(keypair.getPrivate())).getEncoded(); CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); return (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(cert)); } catch (CertificateException | OperatorCreationException ex) { throw new IOException(ex); } } }