All Downloads are FREE. Search and download functionalities are using the official Maven repository.

okhttp3.tls.HeldCertificate Maven / Gradle / Ivy

There is a newer version: 5.0.0-alpha.14
Show newest version
/*
 * Copyright (C) 2016 Square, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package okhttp3.tls;

import java.io.IOException;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import javax.security.auth.x500.X500Principal;
import okio.ByteString;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.X509Extensions;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.x509.X509V3CertificateGenerator;

import static okhttp3.internal.Util.verifyAsIpAddress;

/**
 * A certificate and its private key. These are some properties of certificates that are used with
 * TLS:
 *
 * 
    *
  • A common name. This is a string identifier for the certificate. It usually * describes the purpose of the certificate like "Entrust Root Certification Authority - G2" * or "www.squareup.com". *
  • A set of hostnames. These are in the certificate's subject alternative * name (SAN) extension. A subject alternative name is either a literal hostname ({@code * squareup.com}), a literal IP address ({@code 74.122.190.80}), or a hostname pattern ({@code * *.api.squareup.com}). *
  • A validity interval. A certificate should not be used before its validity * interval starts or after it ends. *
  • A public key. This cryptographic key is used for asymmetric encryption * digital signatures. Note that the private key is not a part of the certificate! *
  • A signature issued by another certificate's private key. This mechanism * allows a trusted third-party to endorse a certificate. Third parties should only endorse * certificates once they've confirmed that the owner of the private key is also the owner of * the certificate's other properties. *
* *

Certificates are signed by other certificates and a sequence of them is called a certificate * chain. The chain terminates in a self-signed "root" certificate. Signing certificates in the * middle of the chain are called "intermediates". Organizations that offer certificate signing are * called certificate authorities (CAs). * *

Browsers and other HTTP clients need a set of trusted root certificates to authenticate their * peers. Sets of root certificates are managed by either the HTTP client (like Firefox), or the * host platform (like Android). In July 2018 Android had 134 trusted root certificates for its HTTP * clients to trust. * *

For example, in order to establish a secure connection to {@code https://www.squareup.com/}, * these three certificates are used.

{@code
 *
 * www.squareup.com certificate:
 *
 *   Common Name: www.squareup.com
 *   Subject Alternative Names: www.squareup.com, squareup.com, account.squareup.com...
 *   Validity: 2018-07-03T20:18:17Z – 2019-08-01T20:48:15Z
 *   Public Key: d107beecc17325f55da976bcbab207ba4df68bd3f8fce7c3b5850311128264fd53e1baa342f58d93...
 *   Signature: 1fb0e66fac05322721fe3a3917f7c98dee1729af39c99eab415f22d8347b508acdf0bab91781c3720...
 *
 * signed by intermediate certificate:
 *
 *   Common Name: Entrust Certification Authority - L1M
 *   Subject Alternative Names: none
 *   Validity: 2014-12-15T15:25:03Z – 2030-10-15T15:55:03Z
 *   Public Key: d081c13923c2b1d1ecf757dd55243691202248f7fcca520ab0ab3f33b5b08407f6df4e7ab0fb9822...
 *   Signature: b487c784221a29c0a478ecf54f1bb484976f77eed4cf59afa843962f1d58dea6f3155b2ed9439c4c4...
 *
 * signed by root certificate:
 *
 *   Common Name: Entrust Root Certification Authority - G2
 *   Subject Alternative Names: none
 *   Validity: 2009-07-07T17:25:54Z – 2030-12-07T17:55:54Z
 *   Public Key: ba84b672db9e0c6be299e93001a776ea32b895411ac9da614e5872cffef68279bf7361060aa527d8...
 *   Self-signed Signature: 799f1d96c6b6793f228d87d3870304606a6b9a2e59897311ac43d1f513ff8d392bc0f...
 *
 * }
* *

In this example the HTTP client already knows and trusts the last certificate, "Entrust Root * Certification Authority - G2". That certificate is used to verify the signature of the * intermediate certificate, "Entrust Certification Authority - L1M". The intermediate certificate * is used to verify the signature of the "www.squareup.com" certificate. * *

This roles are reversed for client authentication. In that case the client has a private key * and a chain of certificates. The server uses a set of trusted root certificates to authenticate * the client. Subject alternative names are not used for client authentication. */ public final class HeldCertificate { private final X509Certificate certificate; private final KeyPair keyPair; public HeldCertificate(KeyPair keyPair, X509Certificate certificate) { if (keyPair == null) throw new NullPointerException("keyPair == null"); if (certificate == null) throw new NullPointerException("certificate == null"); this.certificate = certificate; this.keyPair = keyPair; } public X509Certificate certificate() { return certificate; } public KeyPair keyPair() { return keyPair; } /** * Returns the certificate encoded in PEM * format. */ public String certificatePem() { try { StringBuilder result = new StringBuilder(); result.append("-----BEGIN CERTIFICATE-----\n"); encodeBase64Lines(result, ByteString.of(certificate.getEncoded())); result.append("-----END CERTIFICATE-----\n"); return result.toString(); } catch (CertificateEncodingException e) { throw new AssertionError(e); } } /** * Returns the RSA private key encoded in PKCS * #8 PEM format. */ public String privateKeyPkcs8Pem() { StringBuilder result = new StringBuilder(); result.append("-----BEGIN PRIVATE KEY-----\n"); encodeBase64Lines(result, ByteString.of(keyPair.getPrivate().getEncoded())); result.append("-----END PRIVATE KEY-----\n"); return result.toString(); } /** * Returns the RSA private key encoded in PKCS * #1 PEM format. */ public String privateKeyPkcs1Pem() { if (!(keyPair.getPrivate() instanceof RSAPrivateKey)) { throw new IllegalStateException("PKCS1 only supports RSA keys"); } StringBuilder result = new StringBuilder(); result.append("-----BEGIN RSA PRIVATE KEY-----\n"); encodeBase64Lines(result, pkcs1Bytes()); result.append("-----END RSA PRIVATE KEY-----\n"); return result.toString(); } private ByteString pkcs1Bytes() { try { PrivateKeyInfo privateKeyInfo = PrivateKeyInfo.getInstance(keyPair.getPrivate().getEncoded()); return ByteString.of(privateKeyInfo.parsePrivateKey().toASN1Primitive().getEncoded()); } catch (IOException e) { throw new AssertionError(e); } } private void encodeBase64Lines(StringBuilder out, ByteString data) { String base64 = data.base64(); for (int i = 0; i < base64.length(); i += 64) { out.append(base64, i, Math.min(i + 64, base64.length())).append('\n'); } } /** Build a held certificate with reasonable defaults. */ public static final class Builder { private static final long DEFAULT_DURATION_MILLIS = 1000L * 60 * 60 * 24; // 24 hours. static { Security.addProvider(new BouncyCastleProvider()); } private long notBefore = -1L; private long notAfter = -1L; private String cn; private String ou; private final List altNames = new ArrayList<>(); private BigInteger serialNumber; private KeyPair keyPair; private HeldCertificate signedBy; private int maxIntermediateCas = -1; private String keyAlgorithm; private int keySize; public Builder() { ecdsa256(); } /** * Sets the certificate to be valid in {@code [notBefore..notAfter]}. Both endpoints are * specified in the format of {@link System#currentTimeMillis()}. Specify -1L for both values * to use the default interval, 24 hours starting when the certificate is created. */ public Builder validityInterval(long notBefore, long notAfter) { if (notBefore > notAfter || (notBefore == -1L) != (notAfter == -1L)) { throw new IllegalArgumentException("invalid interval: " + notBefore + ".." + notAfter); } this.notBefore = notBefore; this.notAfter = notAfter; return this; } /** * Sets the certificate to be valid immediately and until the specified duration has elapsed. * The precision of this field is seconds; further precision will be truncated. */ public Builder duration(long duration, TimeUnit unit) { long now = System.currentTimeMillis(); return validityInterval(now, now + unit.toMillis(duration)); } /** * Adds a subject alternative name (SAN) to the certificate. This is usually a literal hostname, * a literal IP address, or a hostname pattern. If no subject alternative names are added that * extension will be omitted. */ public Builder addSubjectAlternativeName(String altName) { if (altName == null) throw new NullPointerException("altName == null"); altNames.add(altName); return this; } /** * Set this certificate's common name (CN). Historically this held the hostname of TLS * certificate, but that practice was deprecated by RFC 2818 and replaced with {@link * #addSubjectAlternativeName(String) subject alternative names}. If unset a random string will * be used. */ public Builder commonName(String cn) { this.cn = cn; return this; } /** Sets the certificate's organizational unit (OU). If unset this field will be omitted. */ public Builder organizationalUnit(String ou) { this.ou = ou; return this; } /** Sets this certificate's serial number. If unset the serial number will be 1. */ public Builder serialNumber(BigInteger serialNumber) { this.serialNumber = serialNumber; return this; } /** Sets this certificate's serial number. If unset the serial number will be 1. */ public Builder serialNumber(long serialNumber) { return serialNumber(BigInteger.valueOf(serialNumber)); } /** * Sets the public/private key pair used for this certificate. If unset a key pair will be * generated. */ public Builder keyPair(KeyPair keyPair) { this.keyPair = keyPair; return this; } /** * Sets the public/private key pair used for this certificate. If unset a key pair will be * generated. */ public Builder keyPair(PublicKey publicKey, PrivateKey privateKey) { return keyPair(new KeyPair(publicKey, privateKey)); } /** * Set the certificate that will issue this certificate. If unset the certificate will be * self-signed. */ public Builder signedBy(HeldCertificate signedBy) { this.signedBy = signedBy; return this; } /** * Set this certificate to be a signing certificate, with up to {@code maxIntermediateCas} * intermediate signing certificates beneath it. * *

By default this certificate cannot not sign other certificates. Set this to 0 so this * certificate can sign other certificates (but those certificates cannot themselves sign * certificates). Set this to 1 so this certificate can sign intermediate certificates that can * themselves sign certificates. Add one for each additional layer of intermediates to permit. */ public Builder certificateAuthority(int maxIntermediateCas) { if (maxIntermediateCas < 0) { throw new IllegalArgumentException("maxIntermediateCas < 0: " + maxIntermediateCas); } this.maxIntermediateCas = maxIntermediateCas; return this; } /** * Configure the certificate to generate a 256-bit ECDSA key, which provides about 128 bits of * security. ECDSA keys are noticeably faster than RSA keys. * *

This is the default configuration and has been since this API was introduced in OkHttp * 3.11.0. Note that the default may change in future releases. */ public Builder ecdsa256() { keyAlgorithm = "ECDSA"; keySize = 256; return this; } /** * Configure the certificate to generate a 2048-bit RSA key, which provides about 112 bits of * security. RSA keys are interoperable with very old clients that don't support ECDSA. */ public Builder rsa2048() { keyAlgorithm = "RSA"; keySize = 2048; return this; } public HeldCertificate build() { // Subject, public & private keys for this certificate. KeyPair heldKeyPair = keyPair != null ? keyPair : generateKeyPair(); X500Principal subject = buildSubject(); // Subject, public & private keys for this certificate's signer. It may be self signed! KeyPair signedByKeyPair; X500Principal signedByPrincipal; if (signedBy != null) { signedByKeyPair = signedBy.keyPair; signedByPrincipal = signedBy.certificate.getSubjectX500Principal(); } else { signedByKeyPair = heldKeyPair; signedByPrincipal = subject; } // Generate & sign the certificate. long notBefore = this.notBefore != -1L ? this.notBefore : System.currentTimeMillis(); long notAfter = this.notAfter != -1L ? this.notAfter : notBefore + DEFAULT_DURATION_MILLIS; BigInteger serialNumber = this.serialNumber != null ? this.serialNumber : BigInteger.ONE; X509V3CertificateGenerator generator = new X509V3CertificateGenerator(); generator.setSerialNumber(serialNumber); generator.setIssuerDN(signedByPrincipal); generator.setNotBefore(new Date(notBefore)); generator.setNotAfter(new Date(notAfter)); generator.setSubjectDN(subject); generator.setPublicKey(heldKeyPair.getPublic()); generator.setSignatureAlgorithm(signedByKeyPair.getPrivate() instanceof RSAPrivateKey ? "SHA256WithRSAEncryption" : "SHA256withECDSA"); if (maxIntermediateCas != -1) { generator.addExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(maxIntermediateCas)); } if (!altNames.isEmpty()) { ASN1Encodable[] encodableAltNames = new ASN1Encodable[altNames.size()]; for (int i = 0, size = altNames.size(); i < size; i++) { String altName = altNames.get(i); int tag = verifyAsIpAddress(altName) ? GeneralName.iPAddress : GeneralName.dNSName; encodableAltNames[i] = new GeneralName(tag, altName); } generator.addExtension(X509Extensions.SubjectAlternativeName, true, new DERSequence(encodableAltNames)); } try { X509Certificate certificate = generator.generateX509Certificate( signedByKeyPair.getPrivate(), "BC"); return new HeldCertificate(heldKeyPair, certificate); } catch (GeneralSecurityException e) { throw new AssertionError(e); } } private X500Principal buildSubject() { StringBuilder nameBuilder = new StringBuilder(); if (cn != null) { nameBuilder.append("CN=").append(cn); } else { nameBuilder.append("CN=").append(UUID.randomUUID()); } if (ou != null) { nameBuilder.append(", OU=").append(ou); } return new X500Principal(nameBuilder.toString()); } private KeyPair generateKeyPair() { try { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(keyAlgorithm, "BC"); keyPairGenerator.initialize(keySize, new SecureRandom()); return keyPairGenerator.generateKeyPair(); } catch (GeneralSecurityException e) { throw new AssertionError(e); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy