-
Notifications
You must be signed in to change notification settings - Fork 708
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[FAB-10230] Add self-signed TLS cert generation
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
Showing
3 changed files
with
443 additions
and
0 deletions.
There are no files selected for viewing
195 changes: 195 additions & 0 deletions
195
src/main/java/org/hyperledger/fabric/sdk/security/certgen/TLSCertificateBuilder.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} | ||
} |
98 changes: 98 additions & 0 deletions
98
src/main/java/org/hyperledger/fabric/sdk/security/certgen/TLSCertificateKeyPair.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
Oops, something went wrong.