Skip to content

Commit

Permalink
[FAB-10230] Add self-signed TLS cert generation
Browse files Browse the repository at this point in the history
This change set adds a utility to generate self-signed
client (and server) TLS certificates.

It is useful for users that don't have a TLS certificate,
but they want to use service discovery which requires mutual TLS.

When the peer is running in the mode which it doesn't verify the client
certificate, but still asks for it - the client can use a self-signed cert
in order to connect to the discovery service.

Change-Id: I734b47198b43c17daaa90dec711758ff035980e5
Signed-off-by: yacovm <yacovm@il.ibm.com>
  • Loading branch information
yacovm authored and cr22rc committed May 22, 2018
1 parent d78b4ee commit cf57b40
Show file tree
Hide file tree
Showing 3 changed files with 443 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
/*
*
* Copyright 2018 IBM - All Rights Reserved.
*
* 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 org.hyperledger.fabric.sdk.security.certgen;

import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.UUID;

import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.X500NameBuilder;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.KeyPurposeId;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.cert.CertIOException;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;

/****
* Creates both client and server TLS certificates
*/
public class TLSCertificateBuilder {
private static final SecureRandom rand = new SecureRandom();
private static final String defaultSignatureAlgorithm = "SHA256withECDSA";
private static final String defaultKeyType = "EC";

private String commonName;
private String signatureAlgorithm;
private String keyType;

/***
* Creates a TLSCertificateBuilder, which is used for creating certificates
*/
public TLSCertificateBuilder() {
// Initialize a default random common name
commonName = UUID.randomUUID().toString();
// Initialize the signature algorithm to be ECDSA over SHA256 by default
signatureAlgorithm = defaultSignatureAlgorithm;
// Initialize the key type to be elliptic curve by default
keyType = defaultKeyType;
}

/***
* Creates a TLS client certificate key pair
* @return a TLSCertificateKeyPair
*/
public TLSCertificateKeyPair clientCert() {
try {
return createCert(CertType.CLIENT, null);
} catch (Exception e) {
throw new RuntimeException(e);
}
}

/***
* Creates a TLS server certificate key pair with the given DNS subject alternative name
* @param subjectAlternativeName the DNS SAN to be encoded in the certificate
* @return a TLSCertificateKeyPair
*/
public TLSCertificateKeyPair serverCert(String subjectAlternativeName) {
try {
return createCert(CertType.SERVER, subjectAlternativeName);
} catch (Exception e) {
throw new RuntimeException(e);
}
}

private TLSCertificateKeyPair createCert(CertType certType, String subjectAlternativeName) throws Exception {
KeyPair keyPair = createKeyPair();
X509Certificate cert = createSelfSignedCertificate(certType, keyPair, subjectAlternativeName);
return TLSCertificateKeyPair.fromX509CertKeyPair(cert, keyPair);
}

private X509Certificate createSelfSignedCertificate(CertType certType, KeyPair keyPair, String san) throws Exception {
X509v3CertificateBuilder certBuilder = createCertBuilder(keyPair);

// Basic constraints
BasicConstraints constraints = new BasicConstraints(false);
certBuilder.addExtension(
Extension.basicConstraints,
true,
constraints.getEncoded());
// Key usage
KeyUsage usage = new KeyUsage(KeyUsage.keyEncipherment | KeyUsage.digitalSignature);
certBuilder.addExtension(Extension.keyUsage, false, usage.getEncoded());
// Extended key usage
certBuilder.addExtension(
Extension.extendedKeyUsage,
false,
certType.keyUsage().getEncoded());

if (san != null) {
addSAN(certBuilder, san);
}

ContentSigner signer = new JcaContentSignerBuilder(signatureAlgorithm)
.build(keyPair.getPrivate());
X509CertificateHolder holder = certBuilder.build(signer);

JcaX509CertificateConverter converter = new JcaX509CertificateConverter();
converter.setProvider(new BouncyCastleProvider());
return converter.getCertificate(holder);
}

private void addSAN(X509v3CertificateBuilder certBuilder, String san) throws CertIOException {
ASN1Encodable[] subjectAlternativeNames = new ASN1Encodable[]{new GeneralName(GeneralName.dNSName, san)};
certBuilder.addExtension(Extension.subjectAlternativeName, false, new DERSequence(subjectAlternativeNames));
}

private X509v3CertificateBuilder createCertBuilder(KeyPair keyPair) {
X500Name subject = new X500NameBuilder(BCStyle.INSTANCE)
.addRDN(BCStyle.CN, commonName)
.build();

Calendar notBefore = new GregorianCalendar();
notBefore.add(Calendar.DAY_OF_MONTH, -1);
Calendar notAfter = new GregorianCalendar();
notAfter.add(Calendar.YEAR, 10);

return new JcaX509v3CertificateBuilder(
subject,
new BigInteger(160, rand),
notBefore.getTime(),
notAfter.getTime(),
subject,
keyPair.getPublic());
}

private KeyPair createKeyPair() throws NoSuchAlgorithmException {
KeyPairGenerator keypairGen = KeyPairGenerator.getInstance(keyType);
keypairGen.initialize(256, rand);
return keypairGen.generateKeyPair();
}


private enum CertType {
CLIENT, SERVER;

ExtendedKeyUsage keyUsage() {
KeyPurposeId[] kpid = new KeyPurposeId[]{KeyPurposeId.id_kp_clientAuth};
if (this.ordinal() == 1) {
kpid[0] = KeyPurposeId.id_kp_serverAuth;
}
return new ExtendedKeyUsage(kpid);
}
}

private static class SelfSignedKeyIdentifier {
private static SecureRandom rand = new SecureRandom();

private byte[] bytes;

SelfSignedKeyIdentifier() {
bytes = new byte[20];
rand.nextBytes(bytes);
}

byte[] authorityKeyIdentifier() {
return bytes;
}

byte[] subjectKeyIdentifier() {
return bytes;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
*
* Copyright 2018 IBM - All Rights Reserved.
*
* 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 org.hyperledger.fabric.sdk.security.certgen;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.security.KeyPair;
import java.security.cert.X509Certificate;

import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;

/**
* Holds PEM encoded TLS certificate key pairs.
*/
public class TLSCertificateKeyPair {
private byte[] certPemBytes;
private byte[] certDerBytes;
private byte[] keyPemBytes;

private TLSCertificateKeyPair(byte[] certPemBytes, byte[] certDerBytes, byte[] keyPemBytes) {
this.certPemBytes = certPemBytes;
this.certDerBytes = certDerBytes;
this.keyPemBytes = keyPemBytes;
}

/***
* Creates a TLSCertificateKeyPair out of the given {@link X509Certificate} and {@link KeyPair}
* encoded in PEM and also in DER for the certificate
* @param x509Cert the certificate to process
* @param keyPair the key pair to process
* @return a TLSCertificateKeyPair
* @throws IOException upon failure
*/
static TLSCertificateKeyPair fromX509CertKeyPair(X509Certificate x509Cert, KeyPair keyPair) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PrintWriter writer = new PrintWriter(baos);
JcaPEMWriter w = new JcaPEMWriter(writer);
w.writeObject(x509Cert);
w.flush();
w.close();
byte[] pemBytes = baos.toByteArray();

InputStreamReader isr = new InputStreamReader(new ByteArrayInputStream(pemBytes));
PemReader pr = new PemReader(isr);
PemObject pem = pr.readPemObject();
byte[] derBytes = pem.getContent();

baos = new ByteArrayOutputStream();
PrintWriter wr = new PrintWriter(baos);
wr.println("-----BEGIN PRIVATE KEY-----");
wr.println(new String(Base64.encodeBase64(keyPair.getPrivate().getEncoded())));
wr.println("-----END PRIVATE KEY-----");
wr.flush();
wr.close();
byte[] keyBytes = baos.toByteArray();
return new TLSCertificateKeyPair(pemBytes, derBytes, keyBytes);
}

/***
* @return the certificate, in PEM encoding
*/
public byte[] getCertPEMBytes() {
return certPemBytes;
}

/***
* @return the certificate, in DER encoding
*/
public byte[] getCertDERBytes() {
return certDerBytes;
}

/***
* @return the key, in PEM encoding
*/
public byte[] getKeyPemBytes() {
return keyPemBytes;
}
}
Loading

0 comments on commit cf57b40

Please sign in to comment.