From f678c01be8e1c68d48be3bcd8a61f47e59045684 Mon Sep 17 00:00:00 2001 From: Gari Singh Date: Mon, 6 Nov 2017 13:44:00 -0500 Subject: [PATCH] [FAB-6887] Support mutual TLS for peer/orderer Adds support for configuring client certificates when connecting via TLS to a peer or orderer. Change-Id: Id9200822f266d79672294180bca34c9b724c9f0d Signed-off-by: Gari Singh --- .../org/hyperledger/fabric/sdk/Endpoint.java | 101 +++++++---- .../org/hyperledger/fabric/sdk/HFClient.java | 52 +++--- .../fabric/sdk/security/CryptoPrimitives.java | 24 +++ .../hyperledger/fabric/sdk/EndpointTest.java | 165 ++++++++++++++++++ .../sdk/security/CryptoPrimitivesTest.java | 20 +++ src/test/resources/tls-client.crt | 13 ++ src/test/resources/tls-client.key | 5 + 7 files changed, 323 insertions(+), 57 deletions(-) create mode 100644 src/test/resources/tls-client.crt create mode 100644 src/test/resources/tls-client.key diff --git a/src/main/java/org/hyperledger/fabric/sdk/Endpoint.java b/src/main/java/org/hyperledger/fabric/sdk/Endpoint.java index 58738dda..5b726bcc 100644 --- a/src/main/java/org/hyperledger/fabric/sdk/Endpoint.java +++ b/src/main/java/org/hyperledger/fabric/sdk/Endpoint.java @@ -23,6 +23,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.security.PrivateKey; import java.security.cert.X509Certificate; import java.util.Collections; import java.util.HashMap; @@ -47,8 +48,12 @@ import org.bouncycastle.asn1.x500.style.BCStyle; import org.bouncycastle.asn1.x500.style.IETFUtils; import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder; -import org.hyperledger.fabric.sdk.security.CryptoPrimitives; +import org.bouncycastle.openssl.PEMKeyPair; +import org.bouncycastle.openssl.PEMParser; +import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; +import org.hyperledger.fabric.sdk.exception.CryptoException; +import org.hyperledger.fabric.sdk.security.CryptoPrimitives; import static org.hyperledger.fabric.sdk.helper.Utils.parseGrpcUrl; class Endpoint { @@ -62,15 +67,14 @@ class Endpoint { private static final Map CN_CACHE = Collections.synchronizedMap(new HashMap<>()); Endpoint(String url, Properties properties) { - logger.trace(String.format("Creating endpoint for url %s", url)); this.url = url; - String cn = null; String sslp = null; String nt = null; byte[] pemBytes = null; - + X509Certificate[] clientCert = new X509Certificate[] {}; + PrivateKey clientKey = null; Properties purl = parseGrpcUrl(url); String protocol = purl.getProperty("protocol"); this.addr = purl.getProperty("host"); @@ -78,6 +82,12 @@ class Endpoint { if (properties != null) { if ("grpcs".equals(protocol)) { + CryptoPrimitives cp; + try { + cp = new CryptoPrimitives(); + } catch (Exception e) { + throw new RuntimeException(e); + } if (properties.containsKey("pemFile") && properties.containsKey("pemBytes")) { throw new RuntimeException("Properties \"pemBytes\" and \"pemFile\" can not be both set."); } @@ -94,25 +104,54 @@ class Endpoint { if (null != pemBytes) { try { cn = properties.getProperty("hostnameOverride"); - if (cn == null && "true".equals(properties.getProperty("trustServerCertificate"))) { final String cnKey = new String(pemBytes, StandardCharsets.UTF_8); - cn = CN_CACHE.get(cnKey); if (cn == null) { - CryptoPrimitives cp = new CryptoPrimitives(); - - X500Name x500name = new JcaX509CertificateHolder((X509Certificate) cp.bytesToCertificate(pemBytes)).getSubject(); + X500Name x500name = new JcaX509CertificateHolder( + (X509Certificate) cp.bytesToCertificate(pemBytes)).getSubject(); RDN rdn = x500name.getRDNs(BCStyle.CN)[0]; cn = IETFUtils.valueToString(rdn.getFirst().getValue()); CN_CACHE.put(cnKey, cn); } - } } catch (Exception e) { /// Mostly a development env. just log it. - logger.error("Error getting Subject CN from certificate. Try setting it specifically with hostnameOverride property. " + e.getMessage()); - + logger.error( + "Error getting Subject CN from certificate. Try setting it specifically with hostnameOverride property. " + + e.getMessage()); + } + } + // check for mutual TLS - both clientKey and clientCert must be present + byte[] ckb = null, ccb = null; + if (properties.containsKey("clientKeyFile") && properties.containsKey("clientKeyBytes")) { + throw new RuntimeException("Properties \"clientKeyFile\" and \"clientKeyBytes\" must cannot both be set"); + } else if (properties.containsKey("clientCertFile") && properties.containsKey("clientCertBytes")) { + throw new RuntimeException("Properties \"clientCertFile\" and \"clientCertBytes\" must cannot both be set"); + } else if (properties.containsKey("clientKeyFile") || properties.containsKey("clientCertFile")) { + if ((properties.getProperty("clientKeyFile") != null) && (properties.getProperty("clientCertFile") != null)) { + try { + ckb = Files.readAllBytes(Paths.get(properties.getProperty("clientKeyFile"))); + ccb = Files.readAllBytes(Paths.get(properties.getProperty("clientCertFile"))); + } catch (IOException e) { + throw new RuntimeException("Failed to parse TLS client key and/or cert", e); + } + } else { + throw new RuntimeException("Properties \"clientKeyFile\" and \"clientCertFile\" must both be set or both be null"); + } + } else if (properties.containsKey("clientKeyBytes") || properties.containsKey("clientCertBytes")) { + ckb = (byte[]) properties.get("clientKeyBytes"); + ccb = (byte[]) properties.get("clientCertBytes"); + if ((ckb == null) || (ccb == null)) { + throw new RuntimeException("Properties \"clientKeyBytes\" and \"clientCertBytes\" must both be set or both be null"); + } + } + if ((ckb != null) && (ccb != null)) { + try { + clientKey = cp.bytesToPrivateKey(ckb); + clientCert = new X509Certificate[] {(X509Certificate) cp.bytesToCertificate(ccb)}; + } catch (CryptoException e) { + throw new RuntimeException("Failed to parse TLS client key and/or certificate", e); } } @@ -132,13 +171,11 @@ class Endpoint { throw new RuntimeException("Property of negotiationType has to be either TLS or plainText"); } } - } try { if (protocol.equalsIgnoreCase("grpc")) { - this.channelBuilder = NettyChannelBuilder.forAddress(addr, port) - .usePlaintext(true); + this.channelBuilder = NettyChannelBuilder.forAddress(addr, port).usePlaintext(true); addNettyBuilderProps(channelBuilder, properties); } else if (protocol.equalsIgnoreCase("grpcs")) { if (pemBytes == null) { @@ -152,12 +189,9 @@ class Endpoint { NegotiationType ntype = nt.equals("TLS") ? NegotiationType.TLS : NegotiationType.PLAINTEXT; InputStream myInputStream = new ByteArrayInputStream(pemBytes); - SslContext sslContext = GrpcSslContexts.forClient() - .trustManager(myInputStream) - .sslProvider(sslprovider) - .build(); - this.channelBuilder = NettyChannelBuilder.forAddress(addr, port) - .sslContext(sslContext) + SslContext sslContext = GrpcSslContexts.forClient().trustManager(myInputStream) + .sslProvider(sslprovider).keyManager(clientKey, clientCert).build(); + this.channelBuilder = NettyChannelBuilder.forAddress(addr, port).sslContext(sslContext) .negotiationType(ntype); if (cn != null) { channelBuilder.overrideAuthority(cn); @@ -177,24 +211,16 @@ class Endpoint { logger.error(e); throw new RuntimeException(e); } - } private static final Pattern METHOD_PATTERN = Pattern.compile("grpc\\.NettyChannelBuilderOption\\.([^.]*)$"); - private static final Map, Class> WRAPPERS_TO_PRIM - = new ImmutableMap.Builder, Class>() - .put(Boolean.class, boolean.class) - .put(Byte.class, byte.class) - .put(Character.class, char.class) - .put(Double.class, double.class) - .put(Float.class, float.class) - .put(Integer.class, int.class) - .put(Long.class, long.class) - .put(Short.class, short.class) - .put(Void.class, void.class) - .build(); - - private void addNettyBuilderProps(NettyChannelBuilder channelBuilder, Properties props) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + private static final Map, Class> WRAPPERS_TO_PRIM = new ImmutableMap.Builder, Class>() + .put(Boolean.class, boolean.class).put(Byte.class, byte.class).put(Character.class, char.class) + .put(Double.class, double.class).put(Float.class, float.class).put(Integer.class, int.class) + .put(Long.class, long.class).put(Short.class, short.class).put(Void.class, void.class).build(); + + private void addNettyBuilderProps(NettyChannelBuilder channelBuilder, Properties props) + throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { if (props == null) { return; @@ -267,7 +293,8 @@ private void addNettyBuilderProps(NettyChannelBuilder channelBuilder, Properties sep = ", "; } - logger.trace(String.format("Endpoint with url: %s set managed channel builder method %s (%s) ", url, method, sb.toString())); + logger.trace(String.format("Endpoint with url: %s set managed channel builder method %s (%s) ", url, + method, sb.toString())); } diff --git a/src/main/java/org/hyperledger/fabric/sdk/HFClient.java b/src/main/java/org/hyperledger/fabric/sdk/HFClient.java index f6433863..cc59e87c 100644 --- a/src/main/java/org/hyperledger/fabric/sdk/HFClient.java +++ b/src/main/java/org/hyperledger/fabric/sdk/HFClient.java @@ -48,10 +48,8 @@ public class HFClient { private CryptoSuite cryptoSuite; - static { - if (null == System.getProperty("org.hyperledger.fabric.sdk.logGRPC")) { // Turn this off by default! Logger.getLogger("io.netty").setLevel(Level.OFF); @@ -96,19 +94,18 @@ public void setCryptoSuite(CryptoSuite cryptoSuite) throws CryptoException, Inva throw new InvalidArgumentException("CryptoSuite may only be set once."); } -// if (cryptoSuiteFactory == null) { -// cryptoSuiteFactory = cryptoSuite.getCryptoSuiteFactory(); -// } else { -// if (cryptoSuiteFactory != cryptoSuite.getCryptoSuiteFactory()) { -// throw new InvalidArgumentException("CryptoSuite is not derivied from cryptosuite factory"); -// } -// } + // if (cryptoSuiteFactory == null) { + // cryptoSuiteFactory = cryptoSuite.getCryptoSuiteFactory(); + // } else { + // if (cryptoSuiteFactory != cryptoSuite.getCryptoSuiteFactory()) { + // throw new InvalidArgumentException("CryptoSuite is not derivied from cryptosuite factory"); + // } + // } this.cryptoSuite = cryptoSuite; } - /** * createNewInstance create a new instance of the HFClient * @@ -160,7 +157,8 @@ public Channel newChannel(String name) throws InvalidArgumentException { * @throws InvalidArgumentException */ - public Channel newChannel(String name, Orderer orderer, ChannelConfiguration channelConfiguration, byte[]... channelConfigurationSignatures) throws TransactionException, InvalidArgumentException { + public Channel newChannel(String name, Orderer orderer, ChannelConfiguration channelConfiguration, + byte[]... channelConfigurationSignatures) throws TransactionException, InvalidArgumentException { clientCheck(); if (Utils.isNullOrEmpty(name)) { @@ -175,7 +173,8 @@ public Channel newChannel(String name, Orderer orderer, ChannelConfiguration cha logger.trace("Creating channel :" + name); - Channel newChannel = Channel.createNewInstance(name, this, orderer, channelConfiguration, channelConfigurationSignatures); + Channel newChannel = Channel.createNewInstance(name, this, orderer, channelConfiguration, + channelConfigurationSignatures); channels.put(name, newChannel); return newChannel; @@ -213,7 +212,8 @@ public Channel deSerializeChannel(File file) throws IOException, ClassNotFoundEx * @throws InvalidArgumentException */ - public Channel deSerializeChannel(byte[] channelBytes) throws IOException, ClassNotFoundException, InvalidArgumentException { + public Channel deSerializeChannel(byte[] channelBytes) + throws IOException, ClassNotFoundException, InvalidArgumentException { Channel channel; ObjectInputStream in = null; @@ -260,6 +260,10 @@ public Channel deSerializeChannel(byte[] channelBytes) throws IOException, Class * useful in development to get past default server hostname verification during * TLS handshake, when the server host name does not match the certificate. * + *
  • clientKeyFile - File location for private key pem for mutual TLS
  • + *
  • clientCertFile - File location for x509 pem certificate for mutual TLS
  • + *
  • clientKeyBytes - Private key pem bytes for mutual TLS
  • + *
  • clientCertBytes - x509 pem certificate bytes for mutual TLS
  • *
  • hostnameOverride - Specify the certificates CN -- for development only. *
  • sslProvider - Specify the SSL provider, openSSL or JDK.
  • *
  • negotiationType - Specify the type of negotiation, TLS or plainText.
  • @@ -365,7 +369,8 @@ public void setUserContext(User userContext) throws InvalidArgumentException { userContextCheck(userContext); this.userContext = userContext; - logger.debug(format("Setting user context to MSPID: %s user: %s", userContext.getMspId(), userContext.getName())); + logger.debug( + format("Setting user context to MSPID: %s user: %s", userContext.getMspId(), userContext.getName())); } @@ -384,6 +389,8 @@ public void setUserContext(User userContext) throws InvalidArgumentException { * useful in development to get past default server hostname verification during * TLS handshake, when the server host name does not match the certificate. * + *
  • clientKeyFile - File location for PKCS8-encoded private key pem for mutual TLS
  • + *
  • clientCertFile - File location for x509 pem certificate for mutual TLS
  • *
  • hostnameOverride - Specify the certificates CN -- for development only. *
  • sslProvider - Specify the SSL provider, openSSL or JDK.
  • *
  • negotiationType - Specify the type of negotiation, TLS or plainText.
  • @@ -449,6 +456,10 @@ public Orderer newOrderer(String name, String grpcURL) throws InvalidArgumentExc * useful in development to get past default server hostname verification during * TLS handshake, when the server host name does not match the certificate. * + *
  • clientKeyFile - File location for private key pem for mutual TLS
  • + *
  • clientCertFile - File location for x509 pem certificate for mutual TLS
  • + *
  • clientKeyBytes - Private key pem bytes for mutual TLS
  • + *
  • clientCertBytes - x509 pem certificate bytes for mutual TLS
  • *
  • sslProvider - Specify the SSL provider, openSSL or JDK.
  • *
  • negotiationType - Specify the type of negotiation, TLS or plainText.
  • *
  • hostnameOverride - Specify the certificates CN -- for development only. @@ -500,7 +511,7 @@ public Set queryChannels(Peer peer) throws InvalidArgumentException, Pro return systemChannel.queryChannels(peer); } catch (InvalidArgumentException e) { - throw e; //dont log + throw e; //dont log } catch (ProposalException e) { logger.error(format("queryChannels for peer %s failed." + e.getMessage(), peer.getName()), e); throw e; @@ -549,7 +560,8 @@ public List queryInstalledChaincodes(Peer peer) throws InvalidArg * @throws InvalidArgumentException */ - public byte[] getChannelConfigurationSignature(ChannelConfiguration channelConfiguration, User signer) throws InvalidArgumentException { + public byte[] getChannelConfigurationSignature(ChannelConfiguration channelConfiguration, User signer) + throws InvalidArgumentException { clientCheck(); @@ -567,7 +579,8 @@ public byte[] getChannelConfigurationSignature(ChannelConfiguration channelConfi * @throws InvalidArgumentException */ - public byte[] getUpdateChannelConfigurationSignature(UpdateChannelConfiguration updateChannelConfiguration, User signer) throws InvalidArgumentException { + public byte[] getUpdateChannelConfigurationSignature(UpdateChannelConfiguration updateChannelConfiguration, + User signer) throws InvalidArgumentException { clientCheck(); @@ -586,8 +599,8 @@ public byte[] getUpdateChannelConfigurationSignature(UpdateChannelConfiguration * @throws ProposalException */ - public Collection sendInstallProposal(InstallProposalRequest installProposalRequest, Collection peers) - throws ProposalException, InvalidArgumentException { + public Collection sendInstallProposal(InstallProposalRequest installProposalRequest, + Collection peers) throws ProposalException, InvalidArgumentException { clientCheck(); @@ -604,7 +617,6 @@ private void clientCheck() throws InvalidArgumentException { throw new InvalidArgumentException("No cryptoSuite has been set."); } - userContextCheck(userContext); } diff --git a/src/main/java/org/hyperledger/fabric/sdk/security/CryptoPrimitives.java b/src/main/java/org/hyperledger/fabric/sdk/security/CryptoPrimitives.java index c3bac7c8..b1190d88 100755 --- a/src/main/java/org/hyperledger/fabric/sdk/security/CryptoPrimitives.java +++ b/src/main/java/org/hyperledger/fabric/sdk/security/CryptoPrimitives.java @@ -73,6 +73,9 @@ import org.bouncycastle.crypto.digests.SHA256Digest; import org.bouncycastle.crypto.digests.SHA3Digest; import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.openssl.PEMKeyPair; +import org.bouncycastle.openssl.PEMParser; +import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; import org.bouncycastle.openssl.jcajce.JcaPEMWriter; import org.bouncycastle.operator.ContentSigner; import org.bouncycastle.operator.OperatorCreationException; @@ -258,6 +261,27 @@ private X509Certificate getX509Certificate(byte[] pemCertificate) throws CryptoE } + /** + * Return PrivateKey from pem bytes. + * + * @param pemKey pem-encoded private key + * @return + */ + public PrivateKey bytesToPrivateKey(byte[] pemKey) throws CryptoException { + PrivateKey pk = null; + CryptoException ce = null; + + try { + PEMParser pem = new PEMParser(new StringReader(new String(pemKey))); + PEMKeyPair kp = (PEMKeyPair) pem.readObject(); + pk = new JcaPEMKeyConverter().getPrivateKey(kp.getPrivateKeyInfo()); + } catch (Exception e) { + throw new CryptoException("Failed to convert private key bytes", e); + } + + return pk; + } + @Override public boolean verify(byte[] pemCertificate, String signatureAlgorithm, byte[] signature, byte[] plainText) throws CryptoException { boolean isVerified = false; diff --git a/src/test/java/org/hyperledger/fabric/sdk/EndpointTest.java b/src/test/java/org/hyperledger/fabric/sdk/EndpointTest.java index 0d0b40f3..4bd8bd0d 100644 --- a/src/test/java/org/hyperledger/fabric/sdk/EndpointTest.java +++ b/src/test/java/org/hyperledger/fabric/sdk/EndpointTest.java @@ -14,6 +14,8 @@ package org.hyperledger.fabric.sdk; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.Properties; import org.junit.Assert; @@ -124,4 +126,167 @@ public void testExtractCommonName() { Assert.assertSame(new Endpoint("grpcs://localhost:594", testprops).getClass(), Endpoint.class); } + + @Test + public void testNullPropertyClientKeyFile() { + thrown.expect(RuntimeException.class); + thrown.expectMessage("Properties \"clientKeyFile\" and \"clientCertFile\" must both be set or both be null"); + + Properties testprops = new Properties(); + testprops.setProperty("trustServerCertificate", "true"); + testprops.setProperty("pemFile", System.getProperty("user.dir") + "/src/test/resources/keypair-signed.crt"); + testprops.setProperty("sslProvider", "openSSL"); + testprops.setProperty("hostnameOverride", "override"); + testprops.setProperty("negotiationType", "TLS"); + testprops.setProperty("clientCertFile", "clientCertFile"); + + new Endpoint("grpcs://localhost:594", testprops); + } + + @Test + public void testNullPropertyClientKeyBytes() { + thrown.expect(RuntimeException.class); + thrown.expectMessage("Properties \"clientKeyBytes\" and \"clientCertBytes\" must both be set or both be null"); + + Properties testprops = new Properties(); + testprops.setProperty("trustServerCertificate", "true"); + testprops.setProperty("pemFile", System.getProperty("user.dir") + "/src/test/resources/keypair-signed.crt"); + testprops.setProperty("sslProvider", "openSSL"); + testprops.setProperty("hostnameOverride", "override"); + testprops.setProperty("negotiationType", "TLS"); + testprops.put("clientCertBytes", new byte[100]); + + new Endpoint("grpcs://localhost:594", testprops); + } + + @Test + public void testNullPropertyClientCertFile() { + thrown.expect(RuntimeException.class); + thrown.expectMessage("Properties \"clientKeyFile\" and \"clientCertFile\" must both be set or both be null"); + + Properties testprops = new Properties(); + testprops.setProperty("trustServerCertificate", "true"); + testprops.setProperty("pemFile", System.getProperty("user.dir") + "/src/test/resources/keypair-signed.crt"); + testprops.setProperty("sslProvider", "openSSL"); + testprops.setProperty("hostnameOverride", "override"); + testprops.setProperty("negotiationType", "TLS"); + testprops.setProperty("clientKeyFile", "clientKeyFile"); + + new Endpoint("grpcs://localhost:594", testprops); + } + + @Test + public void testNullPropertyClientCertBytes() { + thrown.expect(RuntimeException.class); + thrown.expectMessage("Properties \"clientKeyBytes\" and \"clientCertBytes\" must both be set or both be null"); + + Properties testprops = new Properties(); + testprops.setProperty("trustServerCertificate", "true"); + testprops.setProperty("pemFile", System.getProperty("user.dir") + "/src/test/resources/keypair-signed.crt"); + testprops.setProperty("sslProvider", "openSSL"); + testprops.setProperty("hostnameOverride", "override"); + testprops.setProperty("negotiationType", "TLS"); + testprops.put("clientKeyBytes", new byte[100]); + + new Endpoint("grpcs://localhost:594", testprops); + } + + @Test + public void testBadClientKeyFile() { + thrown.expect(RuntimeException.class); + thrown.expectMessage("Failed to parse TLS client key and/or cert"); + + Properties testprops = new Properties(); + testprops.setProperty("trustServerCertificate", "true"); + testprops.setProperty("pemFile", System.getProperty("user.dir") + "/src/test/resources/keypair-signed.crt"); + testprops.setProperty("sslProvider", "openSSL"); + testprops.setProperty("hostnameOverride", "override"); + testprops.setProperty("negotiationType", "TLS"); + testprops.setProperty("clientKeyFile", System.getProperty("user.dir") + "/src/test/resources/bad-ca.crt"); + testprops.setProperty("clientCertFile", System.getProperty("user.dir") + "/src/test/resources/tls-client.crt"); + + new Endpoint("grpcs://localhost:594", testprops); + } + + @Test + public void testBadClientCertFile() { + thrown.expect(RuntimeException.class); + thrown.expectMessage("Failed to parse TLS client key and/or cert"); + + Properties testprops = new Properties(); + testprops.setProperty("trustServerCertificate", "true"); + testprops.setProperty("pemFile", System.getProperty("user.dir") + "/src/test/resources/keypair-signed.crt"); + testprops.setProperty("sslProvider", "openSSL"); + testprops.setProperty("hostnameOverride", "override"); + testprops.setProperty("negotiationType", "TLS"); + testprops.setProperty("clientKeyFile", System.getProperty("user.dir") + "/src/test/resources/tls-client.key"); + testprops.setProperty("clientCertFile", System.getProperty("user.dir") + "/src/test/resources/bad-ca.crt"); + + new Endpoint("grpcs://localhost:594", testprops); + } + + @Test + public void testClientTLSInvalidProperties() { + Properties testprops = new Properties(); + testprops.setProperty("trustServerCertificate", "true"); + testprops.setProperty("pemFile", System.getProperty("user.dir") + "/src/test/resources/keypair-signed.crt"); + testprops.setProperty("sslProvider", "openSSL"); + testprops.setProperty("hostnameOverride", "override"); + testprops.setProperty("negotiationType", "TLS"); + + testprops.setProperty("clientKeyFile", System.getProperty("user.dir") + "/src/test/resources/tls-client.key"); + testprops.put("clientKeyBytes", new byte[100]); + try { + new Endpoint("grpcs://localhost:594", testprops); + } catch (RuntimeException e) { + Assert.assertEquals("Properties \"clientKeyFile\" and \"clientKeyBytes\" must cannot both be set", e.getMessage()); + } + + testprops.remove("clientKeyFile"); + testprops.remove("clientKeyBytes"); + testprops.setProperty("clientCertFile", System.getProperty("user.dir") + "/src/test/resources/tls-client.crt"); + testprops.put("clientCertBytes", new byte[100]); + try { + new Endpoint("grpcs://localhost:594", testprops); + } catch (RuntimeException e) { + Assert.assertEquals("Properties \"clientCertFile\" and \"clientCertBytes\" must cannot both be set", e.getMessage()); + } + + testprops.remove("clientCertFile"); + testprops.put("clientKeyBytes", new byte[100]); + testprops.put("clientCertBytes", new byte[100]); + try { + new Endpoint("grpcs://localhost:594", testprops); + } catch (RuntimeException e) { + Assert.assertEquals("Failed to parse TLS client key and/or certificate", e.getMessage()); + } + } + + @Test + public void testClientTLSProperties() { + + Properties testprops = new Properties(); + + testprops.setProperty("trustServerCertificate", "true"); + testprops.setProperty("pemFile", System.getProperty("user.dir") + "/src/test/resources/keypair-signed.crt"); + testprops.setProperty("sslProvider", "openSSL"); + testprops.setProperty("hostnameOverride", "override"); + testprops.setProperty("negotiationType", "TLS"); + testprops.setProperty("clientKeyFile", System.getProperty("user.dir") + "/src/test/resources/tls-client.key"); + testprops.setProperty("clientCertFile", System.getProperty("user.dir") + "/src/test/resources/tls-client.crt"); + new Endpoint("grpcs://localhost:594", testprops); + + byte[] ckb = null, ccb = null; + try { + ckb = Files.readAllBytes(Paths.get(System.getProperty("user.dir") + "/src/test/resources/tls-client.key")); + ccb = Files.readAllBytes(Paths.get(System.getProperty("user.dir") + "/src/test/resources/tls-client.crt")); + } catch (Exception e) { + Assert.fail("failed to read tls client key or cert: " + e.toString()); + } + testprops.remove("clientKeyFile"); + testprops.remove("clientCertFile"); + testprops.put("clientKeyBytes", ckb); + testprops.put("clientCertBytes", ccb); + new Endpoint("grpcs://localhost:594", testprops); + } } diff --git a/src/test/java/org/hyperledger/fabric/sdk/security/CryptoPrimitivesTest.java b/src/test/java/org/hyperledger/fabric/sdk/security/CryptoPrimitivesTest.java index ffca0af5..0839529e 100755 --- a/src/test/java/org/hyperledger/fabric/sdk/security/CryptoPrimitivesTest.java +++ b/src/test/java/org/hyperledger/fabric/sdk/security/CryptoPrimitivesTest.java @@ -490,6 +490,26 @@ public void testBytesToCertificateNullBytes() throws CryptoException { crypto.bytesToCertificate(null); } + @Test (expected = CryptoException.class) + public void testBytesToPrivateKeyInvalidBytes() throws CryptoException { + crypto.bytesToPrivateKey(INVALID_PEM_CERT.getBytes()); + } + + @Test (expected = CryptoException.class) + public void testBytesToPrivateKeyNullBytes() throws CryptoException { + crypto.bytesToPrivateKey(null); + } + + @Test + public void testBytesToPrivateKey() { + try { + byte[] bytes = Files.readAllBytes(Paths.get(System.getProperty("user.dir") + "/src/test/resources/tls-client.key")); + PrivateKey pk = crypto.bytesToPrivateKey(bytes); + } catch (Exception e) { + Assert.fail("failed to parse private key bytes: " + e.toString()); + } + } + @Test public void testValidateNotSignedCertificate() { try { diff --git a/src/test/resources/tls-client.crt b/src/test/resources/tls-client.crt new file mode 100644 index 00000000..97450827 --- /dev/null +++ b/src/test/resources/tls-client.crt @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIIB9DCCAZqgAwIBAgIRANRocgthhzNb+AG+Kor1a9swCgYIKoZIzj0EAwIwWDEL +MAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG +cmFuY2lzY28xDTALBgNVBAoTBE9yZzExDTALBgNVBAMTBE9yZzEwHhcNMTYxMjMw +MTQwOTAxWhcNMjYxMjI4MTQwOTAxWjBoMQswCQYDVQQGEwJVUzETMBEGA1UECBMK +Q2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEVMBMGA1UEChMMT3Jn +MS1jbGllbnQxMRUwEwYDVQQDEwxPcmcxLWNsaWVudDEwWTATBgcqhkjOPQIBBggq +hkjOPQMBBwNCAARn1VE6aKXiOUOb8nnIKjXuw3iqbbDlPRyyEJE3jiN4xejPd33z +vqUOoYZzc0i4O8naEQ+wji3uiPHRbhxsiGKJozUwMzAOBgNVHQ8BAf8EBAMCBaAw +EwYDVR0lBAwwCgYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAKBggqhkjOPQQDAgNI +ADBFAiEAvO4Q7FQQjSIaGZ9ErlVMggwjCclTjHuoD+OTGPKq2kUCIBaJcSBDvnpe +B+kwGPDFlQEiUJqClG5xr6e70Nun9YT2 +-----END CERTIFICATE----- diff --git a/src/test/resources/tls-client.key b/src/test/resources/tls-client.key new file mode 100644 index 00000000..1862b6c4 --- /dev/null +++ b/src/test/resources/tls-client.key @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEILEK9gVnh8MFQ/haNGYxE3jpOGes/KWxPBB772JzEXXhoAoGCCqGSM49 +AwEHoUQDQgAEZ9VROmil4jlDm/J5yCo17sN4qm2w5T0cshCRN44jeMXoz3d9876l +DqGGc3NIuDvJ2hEPsI4t7ojx0W4cbIhiiQ== +-----END EC PRIVATE KEY-----