Tobias Thierer | 6c251e2 | 2016-06-24 19:04:17 +0100 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2016 Square, Inc. |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | package com.squareup.okhttp.internal; |
| 17 | |
| 18 | import java.math.BigInteger; |
| 19 | import java.security.GeneralSecurityException; |
| 20 | import java.security.KeyPair; |
| 21 | import java.security.KeyPairGenerator; |
| 22 | import java.security.SecureRandom; |
| 23 | import java.security.Security; |
| 24 | import java.security.cert.X509Certificate; |
| 25 | import java.util.Date; |
| 26 | import java.util.UUID; |
| 27 | import javax.security.auth.x500.X500Principal; |
| 28 | import org.bouncycastle.asn1.x509.BasicConstraints; |
| 29 | import org.bouncycastle.asn1.x509.X509Extensions; |
| 30 | import org.bouncycastle.jce.provider.BouncyCastleProvider; |
| 31 | import org.bouncycastle.x509.X509V3CertificateGenerator; |
| 32 | |
| 33 | /** |
| 34 | * A certificate and its private key. This can be used on the server side by HTTPS servers, or on |
| 35 | * the client side to verify those HTTPS servers. A held certificate can also be used to sign other |
| 36 | * held certificates, as done in practice by certificate authorities. |
| 37 | */ |
| 38 | public final class HeldCertificate { |
| 39 | public final X509Certificate certificate; |
| 40 | public final KeyPair keyPair; |
| 41 | |
| 42 | public HeldCertificate(X509Certificate certificate, KeyPair keyPair) { |
| 43 | this.certificate = certificate; |
| 44 | this.keyPair = keyPair; |
| 45 | } |
| 46 | |
| 47 | public static final class Builder { |
| 48 | static { |
| 49 | Security.addProvider(new BouncyCastleProvider()); |
| 50 | } |
| 51 | |
| 52 | private final long duration = 1000L * 60 * 60 * 24; // One day. |
| 53 | private String hostname; |
| 54 | private String serialNumber = "1"; |
| 55 | private KeyPair keyPair; |
| 56 | private HeldCertificate issuedBy; |
| 57 | private int maxIntermediateCas; |
| 58 | |
| 59 | public Builder serialNumber(String serialNumber) { |
| 60 | this.serialNumber = serialNumber; |
| 61 | return this; |
| 62 | } |
| 63 | |
| 64 | /** |
| 65 | * Set this certificate's name. Typically this is the URL hostname for TLS certificates. This is |
| 66 | * the CN (common name) in the certificate. Will be a random string if no value is provided. |
| 67 | */ |
| 68 | public Builder commonName(String hostname) { |
| 69 | this.hostname = hostname; |
| 70 | return this; |
| 71 | } |
| 72 | |
| 73 | public Builder keyPair(KeyPair keyPair) { |
| 74 | this.keyPair = keyPair; |
| 75 | return this; |
| 76 | } |
| 77 | |
| 78 | /** |
| 79 | * Set the certificate that signs this certificate. If unset, a self-signed certificate will be |
| 80 | * generated. |
| 81 | */ |
| 82 | public Builder issuedBy(HeldCertificate signedBy) { |
| 83 | this.issuedBy = signedBy; |
| 84 | return this; |
| 85 | } |
| 86 | |
| 87 | /** |
| 88 | * Set this certificate to be a certificate authority, with up to {@code maxIntermediateCas} |
| 89 | * intermediate certificate authorities beneath it. |
| 90 | */ |
| 91 | public Builder ca(int maxIntermediateCas) { |
| 92 | this.maxIntermediateCas = maxIntermediateCas; |
| 93 | return this; |
| 94 | } |
| 95 | |
| 96 | public HeldCertificate build() throws GeneralSecurityException { |
| 97 | // Subject, public & private keys for this certificate. |
| 98 | KeyPair heldKeyPair = keyPair != null |
| 99 | ? keyPair |
| 100 | : generateKeyPair(); |
| 101 | X500Principal subject = hostname != null |
| 102 | ? new X500Principal("CN=" + hostname) |
| 103 | : new X500Principal("CN=" + UUID.randomUUID()); |
| 104 | |
| 105 | // Subject, public & private keys for this certificate's signer. It may be self signed! |
| 106 | KeyPair signedByKeyPair; |
| 107 | X500Principal signedByPrincipal; |
| 108 | if (issuedBy != null) { |
| 109 | signedByKeyPair = issuedBy.keyPair; |
| 110 | signedByPrincipal = issuedBy.certificate.getSubjectX500Principal(); |
| 111 | } else { |
| 112 | signedByKeyPair = heldKeyPair; |
| 113 | signedByPrincipal = subject; |
| 114 | } |
| 115 | |
| 116 | // Generate & sign the certificate. |
| 117 | long now = System.currentTimeMillis(); |
| 118 | X509V3CertificateGenerator generator = new X509V3CertificateGenerator(); |
| 119 | generator.setSerialNumber(new BigInteger(serialNumber)); |
| 120 | generator.setIssuerDN(signedByPrincipal); |
| 121 | generator.setNotBefore(new Date(now)); |
| 122 | generator.setNotAfter(new Date(now + duration)); |
| 123 | generator.setSubjectDN(subject); |
| 124 | generator.setPublicKey(heldKeyPair.getPublic()); |
| 125 | generator.setSignatureAlgorithm("SHA256WithRSAEncryption"); |
| 126 | |
| 127 | if (maxIntermediateCas > 0) { |
| 128 | generator.addExtension(X509Extensions.BasicConstraints, true, |
| 129 | new BasicConstraints(maxIntermediateCas)); |
| 130 | } |
| 131 | |
Adam Vartanian | a17e2d0 | 2017-10-12 15:24:05 +0100 | [diff] [blame^] | 132 | // Android-changed: Use AndroidOpenSSL provider instead of BC. |
Tobias Thierer | 6c251e2 | 2016-06-24 19:04:17 +0100 | [diff] [blame] | 133 | X509Certificate certificate = generator.generateX509Certificate( |
Adam Vartanian | a17e2d0 | 2017-10-12 15:24:05 +0100 | [diff] [blame^] | 134 | signedByKeyPair.getPrivate(), "AndroidOpenSSL"); |
Tobias Thierer | 6c251e2 | 2016-06-24 19:04:17 +0100 | [diff] [blame] | 135 | return new HeldCertificate(certificate, heldKeyPair); |
| 136 | } |
| 137 | |
| 138 | public KeyPair generateKeyPair() throws GeneralSecurityException { |
Adam Vartanian | a17e2d0 | 2017-10-12 15:24:05 +0100 | [diff] [blame^] | 139 | // Android-changed: Don't specify provider for KeyPairGenerator instance. |
| 140 | KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); |
Tobias Thierer | 6c251e2 | 2016-06-24 19:04:17 +0100 | [diff] [blame] | 141 | keyPairGenerator.initialize(1024, new SecureRandom()); |
| 142 | return keyPairGenerator.generateKeyPair(); |
| 143 | } |
| 144 | } |
| 145 | } |