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.ByteArrayInputStream; 017import java.io.IOException; 018import java.io.InputStream; 019import java.io.InputStreamReader; 020import java.math.BigInteger; 021import java.nio.charset.StandardCharsets; 022import java.security.InvalidKeyException; 023import java.security.KeyPair; 024import java.security.NoSuchAlgorithmException; 025import java.security.PrivateKey; 026import java.security.PublicKey; 027import java.security.cert.CertificateException; 028import java.security.cert.CertificateFactory; 029import java.security.cert.X509Certificate; 030import java.time.Duration; 031import java.time.Instant; 032import java.util.Date; 033import java.util.Objects; 034import java.util.function.Function; 035 036import org.bouncycastle.asn1.ASN1Encodable; 037import org.bouncycastle.asn1.ASN1ObjectIdentifier; 038import org.bouncycastle.asn1.DEROctetString; 039import org.bouncycastle.asn1.pkcs.Attribute; 040import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; 041import org.bouncycastle.asn1.x500.X500Name; 042import org.bouncycastle.asn1.x509.Extension; 043import org.bouncycastle.asn1.x509.Extensions; 044import org.bouncycastle.asn1.x509.GeneralName; 045import org.bouncycastle.asn1.x509.GeneralNames; 046import org.bouncycastle.cert.CertIOException; 047import org.bouncycastle.cert.X509CertificateHolder; 048import org.bouncycastle.cert.jcajce.JcaX509v1CertificateBuilder; 049import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; 050import org.bouncycastle.openssl.PEMParser; 051import org.bouncycastle.operator.ContentSigner; 052import org.bouncycastle.operator.OperatorCreationException; 053import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; 054import org.bouncycastle.pkcs.PKCS10CertificationRequest; 055import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest; 056import org.shredzone.acme4j.Identifier; 057import org.shredzone.acme4j.challenge.TlsAlpn01Challenge; 058 059/** 060 * Utility class offering convenience methods for certificates. 061 * <p> 062 * Requires {@code Bouncy Castle}. This class is part of the {@code acme4j-utils} module. 063 */ 064public final class CertificateUtils { 065 066 /** 067 * The {@code acmeValidation} object identifier. 068 * 069 * @since 2.1 070 */ 071 public static final ASN1ObjectIdentifier ACME_VALIDATION = 072 new ASN1ObjectIdentifier(TlsAlpn01Challenge.ACME_VALIDATION_OID).intern(); 073 074 private CertificateUtils() { 075 // utility class without constructor 076 } 077 078 /** 079 * Reads a CSR PEM file. 080 * 081 * @param in 082 * {@link InputStream} to read the CSR from. The {@link InputStream} is 083 * closed after use. 084 * @return CSR that was read 085 */ 086 public static PKCS10CertificationRequest readCSR(InputStream in) throws IOException { 087 try (PEMParser pemParser = new PEMParser(new InputStreamReader(in, StandardCharsets.US_ASCII))) { 088 Object parsedObj = pemParser.readObject(); 089 if (!(parsedObj instanceof PKCS10CertificationRequest)) { 090 throw new IOException("Not a PKCS10 CSR"); 091 } 092 return (PKCS10CertificationRequest) parsedObj; 093 } 094 } 095 096 /** 097 * Creates a self-signed {@link X509Certificate} that can be used for the 098 * {@link TlsAlpn01Challenge}. The certificate is valid for 7 days. 099 * 100 * @param keypair 101 * A domain {@link KeyPair} to be used for the challenge 102 * @param id 103 * The {@link Identifier} that is to be validated 104 * @param acmeValidation 105 * The value that is returned by 106 * {@link TlsAlpn01Challenge#getAcmeValidation()} 107 * @return Created certificate 108 * @since 2.6 109 */ 110 public static X509Certificate createTlsAlpn01Certificate(KeyPair keypair, Identifier id, byte[] acmeValidation) 111 throws IOException { 112 Objects.requireNonNull(keypair, "keypair"); 113 Objects.requireNonNull(id, "id"); 114 if (acmeValidation == null || acmeValidation.length != 32) { 115 throw new IllegalArgumentException("Bad acmeValidation parameter"); 116 } 117 118 final long now = System.currentTimeMillis(); 119 120 X500Name issuer = new X500Name("CN=acme.invalid"); 121 BigInteger serial = BigInteger.valueOf(now); 122 Instant notBefore = Instant.ofEpochMilli(now); 123 Instant notAfter = notBefore.plus(Duration.ofDays(7)); 124 125 JcaX509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder( 126 issuer, serial, Date.from(notBefore), Date.from(notAfter), 127 issuer, keypair.getPublic()); 128 129 GeneralName[] gns = new GeneralName[1]; 130 131 switch (id.getType()) { 132 case Identifier.TYPE_DNS: 133 gns[0] = new GeneralName(GeneralName.dNSName, id.getDomain()); 134 break; 135 136 case Identifier.TYPE_IP: 137 gns[0] = new GeneralName(GeneralName.iPAddress, id.getIP().getHostAddress()); 138 break; 139 140 default: 141 throw new IllegalArgumentException("Unsupported Identifier type " + id.getType()); 142 } 143 certBuilder.addExtension(Extension.subjectAlternativeName, false, new GeneralNames(gns)); 144 certBuilder.addExtension(ACME_VALIDATION, true, new DEROctetString(acmeValidation)); 145 146 return buildCertificate(certBuilder::build, keypair.getPrivate()); 147 } 148 149 /** 150 * Creates a self-signed root certificate. 151 * <p> 152 * The generated certificate is only meant for testing purposes! 153 * 154 * @param subject 155 * This certificate's subject X.500 name. 156 * @param notBefore 157 * {@link Instant} before which the certificate is not valid. 158 * @param notAfter 159 * {@link Instant} after which the certificate is not valid. 160 * @param keypair 161 * {@link KeyPair} that is to be used for this certificate. 162 * @return Generated {@link X509Certificate} 163 * @since 2.8 164 */ 165 public static X509Certificate createTestRootCertificate(String subject, 166 Instant notBefore, Instant notAfter, KeyPair keypair) { 167 Objects.requireNonNull(subject, "subject"); 168 Objects.requireNonNull(notBefore, "notBefore"); 169 Objects.requireNonNull(notAfter, "notAfter"); 170 Objects.requireNonNull(keypair, "keypair"); 171 172 JcaX509v1CertificateBuilder certBuilder = new JcaX509v1CertificateBuilder( 173 new X500Name(subject), 174 BigInteger.valueOf(System.currentTimeMillis()), 175 Date.from(notBefore), 176 Date.from(notAfter), 177 new X500Name(subject), 178 keypair.getPublic() 179 ); 180 181 return buildCertificate(certBuilder::build, keypair.getPrivate()); 182 } 183 184 /** 185 * Creates an intermediate certificate that is signed by an issuer. 186 * <p> 187 * The generated certificate is only meant for testing purposes! 188 * 189 * @param subject 190 * This certificate's subject X.500 name. 191 * @param notBefore 192 * {@link Instant} before which the certificate is not valid. 193 * @param notAfter 194 * {@link Instant} after which the certificate is not valid. 195 * @param intermediatePublicKey 196 * {@link PublicKey} of this certificate 197 * @param issuer 198 * The issuer's {@link X509Certificate}. 199 * @param issuerPrivateKey 200 * {@link PrivateKey} of the issuer. This is not the private key of this 201 * intermediate certificate. 202 * @return Generated {@link X509Certificate} 203 * @since 2.8 204 */ 205 public static X509Certificate createTestIntermediateCertificate(String subject, 206 Instant notBefore, Instant notAfter, PublicKey intermediatePublicKey, 207 X509Certificate issuer, PrivateKey issuerPrivateKey) { 208 Objects.requireNonNull(subject, "subject"); 209 Objects.requireNonNull(notBefore, "notBefore"); 210 Objects.requireNonNull(notAfter, "notAfter"); 211 Objects.requireNonNull(intermediatePublicKey, "intermediatePublicKey"); 212 Objects.requireNonNull(issuer, "issuer"); 213 Objects.requireNonNull(issuerPrivateKey, "issuerPrivateKey"); 214 215 JcaX509v1CertificateBuilder certBuilder = new JcaX509v1CertificateBuilder( 216 new X500Name(issuer.getIssuerX500Principal().getName()), 217 BigInteger.valueOf(System.currentTimeMillis()), 218 Date.from(notBefore), 219 Date.from(notAfter), 220 new X500Name(subject), 221 intermediatePublicKey 222 ); 223 224 return buildCertificate(certBuilder::build, issuerPrivateKey); 225 } 226 227 /** 228 * Creates a signed end entity certificate from the given CSR. 229 * <p> 230 * This method is only meant for testing purposes! Do not use it in a real-world CA 231 * implementation. 232 * <p> 233 * Do not assume that real-world certificates have a similar structure. It's up to the 234 * discretion of the CA which distinguished names, validity dates, extensions and 235 * other parameters are transferred from the CSR to the generated certificate. 236 * 237 * @param csr 238 * CSR to create the certificate from 239 * @param notBefore 240 * {@link Instant} before which the certificate is not valid. 241 * @param notAfter 242 * {@link Instant} after which the certificate is not valid. 243 * @param issuer 244 * The issuer's {@link X509Certificate}. 245 * @param issuerPrivateKey 246 * {@link PrivateKey} of the issuer. This is not the private key the CSR was 247 * signed with. 248 * @return Generated {@link X509Certificate} 249 * @since 2.8 250 */ 251 public static X509Certificate createTestCertificate(PKCS10CertificationRequest csr, 252 Instant notBefore, Instant notAfter, X509Certificate issuer, PrivateKey issuerPrivateKey) { 253 Objects.requireNonNull(csr, "csr"); 254 Objects.requireNonNull(notBefore, "notBefore"); 255 Objects.requireNonNull(notAfter, "notAfter"); 256 Objects.requireNonNull(issuer, "issuer"); 257 Objects.requireNonNull(issuerPrivateKey, "issuerPrivateKey"); 258 259 try { 260 JcaPKCS10CertificationRequest jcaCsr = new JcaPKCS10CertificationRequest(csr); 261 262 JcaX509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder( 263 new X500Name(issuer.getIssuerX500Principal().getName()), 264 BigInteger.valueOf(System.currentTimeMillis()), 265 Date.from(notBefore), 266 Date.from(notAfter), 267 csr.getSubject(), 268 jcaCsr.getPublicKey()); 269 270 Attribute[] attr = csr.getAttributes(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest); 271 if (attr.length > 0) { 272 ASN1Encodable[] extensions = attr[0].getAttrValues().toArray(); 273 if (extensions.length > 0 && extensions[0] instanceof Extensions) { 274 GeneralNames san = GeneralNames.fromExtensions((Extensions) extensions[0], Extension.subjectAlternativeName); 275 certBuilder.addExtension(Extension.subjectAlternativeName, false, san); 276 } 277 } 278 279 return buildCertificate(certBuilder::build, issuerPrivateKey); 280 } catch (NoSuchAlgorithmException | InvalidKeyException | CertIOException ex) { 281 throw new IllegalArgumentException("Invalid CSR", ex); 282 } 283 } 284 285 /** 286 * Build a {@link X509Certificate} from a builder. 287 * 288 * @param builder 289 * Builder method that receives a {@link ContentSigner} and returns a {@link 290 * X509CertificateHolder}. 291 * @param privateKey 292 * {@link PrivateKey} to sign the certificate with 293 * @return The generated {@link X509Certificate} 294 */ 295 private static X509Certificate buildCertificate(Function<ContentSigner, X509CertificateHolder> builder, PrivateKey privateKey) { 296 try { 297 JcaContentSignerBuilder signerBuilder = new JcaContentSignerBuilder("SHA256withRSA"); 298 byte[] cert = builder.apply(signerBuilder.build(privateKey)).getEncoded(); 299 CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); 300 return (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(cert)); 301 } catch (CertificateException | OperatorCreationException | IOException ex) { 302 throw new IllegalArgumentException("Could not build certificate", ex); 303 } 304 } 305 306}