diff --git a/src/main/java/org/hyperledger/fabric/sdk/Endpoint.java b/src/main/java/org/hyperledger/fabric/sdk/Endpoint.java index bcbfbf7d..e3754fe8 100644 --- a/src/main/java/org/hyperledger/fabric/sdk/Endpoint.java +++ b/src/main/java/org/hyperledger/fabric/sdk/Endpoint.java @@ -14,6 +14,25 @@ package org.hyperledger.fabric.sdk; +import static java.lang.String.format; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.hyperledger.fabric.sdk.NetworkConfig.CLIENT_CERT_BYTES; +import static org.hyperledger.fabric.sdk.NetworkConfig.CLIENT_CERT_FILE; +import static org.hyperledger.fabric.sdk.NetworkConfig.CLIENT_KEY_BYTES; +import static org.hyperledger.fabric.sdk.NetworkConfig.CLIENT_KEY_FILE; +import static org.hyperledger.fabric.sdk.helper.Utils.parseGrpcUrl; + +import com.google.common.collect.ImmutableMap.Builder; +import io.grpc.ClientInterceptor; +import io.grpc.ManagedChannelBuilder; +import io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts; +import io.grpc.netty.shaded.io.grpc.netty.NegotiationType; +import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder; +import io.grpc.netty.shaded.io.netty.handler.ssl.SslContext; +import io.grpc.netty.shaded.io.netty.handler.ssl.SslContextBuilder; +import io.grpc.netty.shaded.io.netty.handler.ssl.SslProvider; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.grpc.v1_6.GrpcTelemetry; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; @@ -23,32 +42,22 @@ import java.nio.file.Files; import java.nio.file.Paths; import java.security.PrivateKey; +import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; -import java.util.AbstractMap; import java.util.Arrays; import java.util.Base64; import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; import java.util.Properties; import java.util.regex.Matcher; import java.util.regex.Pattern; -import javax.net.ssl.SSLException; - -import com.google.common.collect.ImmutableMap; -import io.grpc.ClientInterceptor; -import io.grpc.ManagedChannelBuilder; -import io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts; -import io.grpc.netty.shaded.io.grpc.netty.NegotiationType; -import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder; -import io.grpc.netty.shaded.io.netty.handler.ssl.SslContext; -import io.grpc.netty.shaded.io.netty.handler.ssl.SslContextBuilder; -import io.grpc.netty.shaded.io.netty.handler.ssl.SslProvider; -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.instrumentation.grpc.v1_6.GrpcTelemetry; import org.apache.commons.codec.binary.Hex; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.commons.math3.util.Pair; import org.bouncycastle.asn1.x500.RDN; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x500.style.BCStyle; @@ -61,244 +70,318 @@ import org.hyperledger.fabric.sdk.helper.Utils; import org.hyperledger.fabric.sdk.security.CryptoPrimitives; -import static java.lang.String.format; -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.hyperledger.fabric.sdk.helper.Utils.parseGrpcUrl; - class Endpoint { private static final Log logger = LogFactory.getLog(Endpoint.class); - private static final String SSLPROVIDER = Config.getConfig().getDefaultSSLProvider(); - private static final String SSLNEGOTIATION = Config.getConfig().getDefaultSSLNegotiationType(); - private static final OpenTelemetry openTelemetry = Config.getConfig().getOpenTelemetry(); - private static final GrpcTelemetry grpcTelemetry = GrpcTelemetry.create(openTelemetry); + private static final String SSL_PROVIDER = Config.getConfig().getDefaultSSLProvider(); + private static final String SSL_NEGOTIATION = Config.getConfig().getDefaultSSLNegotiationType(); + private static final OpenTelemetry OPEN_TELEMETRY = Config.getConfig().getOpenTelemetry(); + private static final GrpcTelemetry GRPC_TELEMETRY = GrpcTelemetry.create(OPEN_TELEMETRY); + + private static final Pattern PEMFILE_SPLIT_PATTERN = Pattern.compile("[ \t]*,[ \t]*"); + private static final Pattern CERT_BODY_PATTERN = Pattern.compile("-+[ \t]*(BEGIN|END)[ \t]+CERTIFICATE[ \t]*-+"); + private static final Pattern WHITESPACE_PATTERN = Pattern.compile("\\s"); + + public static final String PROPERTY_PROTOCOL = "protocol"; + public static final String PROPERTY_PORT = "port"; + public static final String PROPERTY_HOST = "host"; + public static final String PROPERTY_HOSTNAME_OVERRIDE = "hostnameOverride"; + public static final String PROPERTY_TRUST_SERVER_CERTIFICATE = "trustServerCertificate"; + public static final String PROPERTY_SSL_PROVIDER = "sslProvider"; + public static final String PROPERTY_NEGOTIATION_TYPE = "negotiationType"; + public static final String PROPERTY_PEM_FILE = "pemFile"; private final String addr; private final int port; private final String url; private byte[] clientTLSCertificateDigest; private byte[] tlsClientCertificatePEMBytes; - private NettyChannelBuilder channelBuilder; + private final NettyChannelBuilder channelBuilder; private static final Map CN_CACHE = Collections.synchronizedMap(new HashMap<>()); Endpoint(String url, Properties properties) { logger.trace(format("Creating endpoint for url %s", url)); this.url = url; - String cn = null; - String sslp = null; - String nt = null; - byte[] pemBytes = null; - X509Certificate[] clientCert = null; - PrivateKey clientKey = null; Properties purl = parseGrpcUrl(url); - String protocol = purl.getProperty("protocol"); - this.addr = purl.getProperty("host"); - this.port = Integer.parseInt(purl.getProperty("port")); + ProtocolType protocol = ProtocolType.from(purl.getProperty(PROPERTY_PROTOCOL)); + addr = purl.getProperty(PROPERTY_HOST); + port = Integer.parseInt(purl.getProperty(PROPERTY_PORT)); + + try { + ClientInterceptor clientInterceptor = GRPC_TELEMETRY.newClientInterceptor(); + switch (protocol) { + case GRPC: + channelBuilder = createGrpcChannelBuilder(properties, clientInterceptor); + break; + case GRPCS: + channelBuilder = createGrpcsChannelBuilder(url, properties, clientInterceptor); + break; + default: + // Impossible to reach, but compiler doesn't know. Fixed in Java 14. + channelBuilder = null; + } + } catch (RuntimeException e) { + logger.error(format("Endpoint %s, exception '%s'", url, e.getMessage()), e); + throw e; + } + } + + private NettyChannelBuilder createGrpcChannelBuilder(Properties properties, ClientInterceptor clientInterceptor) { + + final NettyChannelBuilder channelBuilder = NettyChannelBuilder.forAddress(addr, port) + .negotiationType(NegotiationType.PLAINTEXT) + .intercept(clientInterceptor); + addNettyBuilderProps(channelBuilder, properties); + return channelBuilder; + } + + private NettyChannelBuilder createGrpcsChannelBuilder( + String url, Properties properties, ClientInterceptor clientInterceptor) { + + final NettyChannelBuilder channelBuilder; + Optional> clientTLSPropsOpt = + readClientTLSProps(url, properties); + + Optional clientKey = clientTLSPropsOpt.map(Pair::getKey); + Optional clientCert = clientTLSPropsOpt.map(Pair::getValue); + + Optional grpcsContext = readGrpcsProps(url, properties); + Optional pemBytesOpt = grpcsContext.flatMap(GrpcsContext::getPemBytes); + Optional cnOpt = grpcsContext.flatMap(GrpcsContext::getCn); - if (properties != null) { + String sslp = readSslpProperty(url, properties); + String nt = readNtProperty(url, properties); - final AbstractMap.SimpleImmutableEntry clientTLSProps = getClientTLSProps(properties); - if (clientTLSProps != null) { - clientCert = clientTLSProps.getValue(); - clientKey = clientTLSProps.getKey(); + if (!pemBytesOpt.isPresent()) { + // use root certificate + channelBuilder = NettyChannelBuilder + .forAddress(addr, port) + .intercept(clientInterceptor); + + addNettyBuilderProps(channelBuilder, properties); + return channelBuilder; + } + + byte[] pemBytes = pemBytesOpt.get(); + try { + + logger.trace(format("Endpoint %s Negotiation type: '%s', SSLprovider: '%s'", + url, nt, sslp + )); + SslProvider sslprovider = sslp.equals("openSSL") ? SslProvider.OPENSSL : SslProvider.JDK; + NegotiationType ntype = nt.equals("TLS") ? NegotiationType.TLS : NegotiationType.PLAINTEXT; + + SslContextBuilder clientContextBuilder = getSslContextBuilder( + clientCert.orElse(null), clientKey.orElse(null), sslprovider + ); + + logger.trace( + format("Endpoint %s final server pemBytes: %s", + url, Hex.encodeHexString(pemBytes) + ) + ); + + SslContext sslContext; + try (InputStream myInputStream = new ByteArrayInputStream(pemBytes)) { + sslContext = clientContextBuilder + .trustManager(myInputStream) + .build(); } - if ("grpcs".equals(protocol)) { - CryptoPrimitives cp; - try { - cp = new CryptoPrimitives(); - } catch (Exception e) { - throw new RuntimeException(e); - } + channelBuilder = NettyChannelBuilder + .forAddress(addr, port) + .sslContext(sslContext) + .negotiationType(ntype) + .intercept(clientInterceptor); - try (ByteArrayOutputStream bis = new ByteArrayOutputStream(64000)) { - byte[] pb = (byte[]) properties.get("pemBytes"); - if (null != pb) { - bis.write(pb); - } - if (properties.containsKey("pemFile")) { + if (cnOpt.isPresent()) { + String cn = cnOpt.get(); + logger.debug(format("Endpoint %s, using CN overrideAuthority: '%s'", url, cn)); + channelBuilder.overrideAuthority(cn); + } + addNettyBuilderProps(channelBuilder, properties); + return channelBuilder; - String pemFile = properties.getProperty("pemFile"); + } catch (IOException e) { + throw new RuntimeException(e); + } + } - String[] pems = pemFile.split("[ \t]*,[ \t]*"); + private static Optional readGrpcsProps(String url, Properties properties) { + if (properties == null) { + return Optional.empty(); + } - for (String pem : pems) { - if (null != pem && !pem.isEmpty()) { - try { - bis.write(Files.readAllBytes(Paths.get(pem))); - } catch (IOException e) { - throw new RuntimeException(format("Failed to read certificate file %s", - new File(pem).getAbsolutePath()), e); - } - } - } + CryptoPrimitives cp; + try { + cp = new CryptoPrimitives(); + } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) { + throw new RuntimeException(e); + } - } - pemBytes = bis.toByteArray(); - logger.trace(format("Endpoint %s pemBytes: %s", url, Hex.encodeHexString(pemBytes))); + Optional pemBytesOpt = getPEMFile(url, properties); - if (pemBytes.length == 0) { - pemBytes = null; + String cn = null; + if (!pemBytesOpt.isPresent()) { + logger.warn(format("Endpoint %s is grpcs with no CA certificates", url)); + } else { + try { + final byte[] pemBytes = pemBytesOpt.get(); + cn = properties.getProperty(PROPERTY_HOSTNAME_OVERRIDE); + if (cn == null && "true".equals(properties.getProperty(PROPERTY_TRUST_SERVER_CERTIFICATE))) { + final String cnKey = new String(pemBytes, UTF_8); + cn = CN_CACHE.get(cnKey); + if (cn == null) { + 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 (IOException e) { - throw new RuntimeException("Failed to read CA certificates file %s", e); } + } catch (CertificateEncodingException | CryptoException e) { + /// Mostly a development env. just log it. + logger.error( + format( + "Error getting Subject CN from certificate. " + + "Try setting it specifically with hostnameOverride property. %s", e.getMessage() + ) + ); + } + } - if (pemBytes == null) { - logger.warn(format("Endpoint %s is grpcs with no CA certificates", url)); - } + return Optional.of(new GrpcsContext(cn, pemBytesOpt.orElse(null))); + } - if (null != pemBytes) { - try { - cn = properties.getProperty("hostnameOverride"); - if (cn == null && "true".equals(properties.getProperty("trustServerCertificate"))) { - final String cnKey = new String(pemBytes, UTF_8); - cn = CN_CACHE.get(cnKey); - if (cn == null) { - 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()); - } - } - // check for mutual TLS - both clientKey and clientCert must be present - byte[] ckb = null, ccb = null; - if (properties.containsKey(NetworkConfig.CLIENT_KEY_FILE) && properties.containsKey(NetworkConfig.CLIENT_KEY_BYTES)) { - throw new RuntimeException("Properties \"clientKeyFile\" and \"clientKeyBytes\" must cannot both be set"); - } else if (properties.containsKey(NetworkConfig.CLIENT_CERT_FILE) && properties.containsKey(NetworkConfig.CLIENT_CERT_BYTES)) { - throw new RuntimeException("Properties \"clientCertFile\" and \"clientCertBytes\" must cannot both be set"); - } else if (properties.containsKey(NetworkConfig.CLIENT_KEY_FILE) || properties.containsKey(NetworkConfig.CLIENT_CERT_FILE)) { - if ((properties.getProperty(NetworkConfig.CLIENT_KEY_FILE) != null) && (properties.getProperty(NetworkConfig.CLIENT_CERT_FILE) != null)) { - try { - logger.trace(format("Endpoint %s reading clientKeyFile: %s", url, properties.getProperty(NetworkConfig.CLIENT_KEY_FILE))); - ckb = Files.readAllBytes(Paths.get(properties.getProperty(NetworkConfig.CLIENT_KEY_FILE))); - logger.trace(format("Endpoint %s reading clientCertFile: %s", url, properties.getProperty(NetworkConfig.CLIENT_CERT_FILE))); - ccb = Files.readAllBytes(Paths.get(properties.getProperty(NetworkConfig.CLIENT_CERT_FILE))); - } catch (IOException e) { - throw new RuntimeException("Failed to parse TLS client key and/or cert", e); - } - } else { - throw new RuntimeException(String.format("Properties \"%s\" and \"%s\" must both be set or both be null", NetworkConfig.CLIENT_KEY_FILE, NetworkConfig.CLIENT_CERT_FILE)); - } - } else if (properties.containsKey(NetworkConfig.CLIENT_KEY_BYTES) || properties.containsKey(NetworkConfig.CLIENT_CERT_BYTES)) { - ckb = (byte[]) properties.get(NetworkConfig.CLIENT_KEY_BYTES); - ccb = (byte[]) properties.get(NetworkConfig.CLIENT_CERT_BYTES); - if ((ckb == null) || (ccb == null)) { - throw new RuntimeException(String.format("Properties \"%s\" and \"%s\" must both be set or both be null", NetworkConfig.CLIENT_KEY_BYTES, NetworkConfig.CLIENT_CERT_BYTES)); - } - } + private static String readNtProperty(String url, Properties properties) { + if (properties == null) { + return null; + } - if ((ckb != null) && (ccb != null)) { - String what = "private key"; - byte[] whatBytes = new byte[0]; - try { - logger.trace("client TLS private key bytes size:" + ckb.length); - whatBytes = ckb; - logger.trace("client TLS key bytes:" + Hex.encodeHexString(ckb)); - clientKey = cp.bytesToPrivateKey(ckb); - logger.trace("converted TLS key."); - what = "certificate"; - whatBytes = ccb; - logger.trace("client TLS certificate bytes:" + Hex.encodeHexString(ccb)); - clientCert = new X509Certificate[] {(X509Certificate) cp.bytesToCertificate(ccb)}; - logger.trace("converted client TLS certificate."); - tlsClientCertificatePEMBytes = ccb; // Save this away it's the exact pem we used. - } catch (CryptoException e) { - logger.error(format("Failed endpoint %s to parse %s TLS client %s", url, what, new String(whatBytes))); - throw new RuntimeException(format("Failed endpoint %s to parse TLS client %s", url, what), e); - } - } + String nt = properties.getProperty(PROPERTY_NEGOTIATION_TYPE); + if (null == nt) { + nt = SSL_NEGOTIATION; + logger.trace( + format("Endpoint %s specific Negotiation type not found use global value: %s ", url, SSL_NEGOTIATION)); + } - sslp = properties.getProperty("sslProvider"); + if (!"TLS".equals(nt) && !"plainText".equals(nt)) { + throw new RuntimeException( + format("Endpoint %s property of negotiationType has to be either TLS or plainText. value: '%s'", + url, nt + )); + } + return nt; + } - if (null == sslp) { - sslp = SSLPROVIDER; - logger.trace(format("Endpoint %s specific SSL provider not found use global value: %s ", url, SSLPROVIDER)); - } - if (!"openSSL".equals(sslp) && !"JDK".equals(sslp)) { - throw new RuntimeException(format("Endpoint %s property of sslProvider has to be either openSSL or JDK. value: '%s'", url, sslp)); - } + private static String readSslpProperty(String url, Properties properties) { + if (properties == null) { + return null; + } - nt = properties.getProperty("negotiationType"); - if (null == nt) { - nt = SSLNEGOTIATION; - logger.trace(format("Endpoint %s specific Negotiation type not found use global value: %s ", url, SSLNEGOTIATION)); - } + String sslp = properties.getProperty(PROPERTY_SSL_PROVIDER); - if (!"TLS".equals(nt) && !"plainText".equals(nt)) { - throw new RuntimeException(format("Endpoint %s property of negotiationType has to be either TLS or plainText. value: '%s'", url, nt)); - } - } + if (null == sslp) { + sslp = SSL_PROVIDER; + logger.trace(format("Endpoint %s specific SSL provider not found use global value: %s ", + url, SSL_PROVIDER + )); + } + if (!"openSSL".equals(sslp) && !"JDK".equals(sslp)) { + throw new RuntimeException( + format("Endpoint %s property of sslProvider has to be either openSSL or JDK. value: '%s'", + url, sslp + )); } + return sslp; + } - try { - ClientInterceptor clientInterceptor = grpcTelemetry.newClientInterceptor(); - if (protocol.equalsIgnoreCase("grpc")) { - this.channelBuilder = NettyChannelBuilder.forAddress(addr, port).negotiationType(NegotiationType.PLAINTEXT).intercept(clientInterceptor); - addNettyBuilderProps(channelBuilder, properties); - } else if (protocol.equalsIgnoreCase("grpcs")) { - if (pemBytes == null) { - // use root certificate - this.channelBuilder = NettyChannelBuilder.forAddress(addr, port).intercept(clientInterceptor); - addNettyBuilderProps(channelBuilder, properties); - } else { - try { + private static void validatePropertyExclusivity(Properties properties, String propertyKey1, String propertyKey2) { + if (properties.containsKey(propertyKey1) && properties.containsKey(propertyKey2)) { + throw new RuntimeException( + format("Properties \"%s\" and \"%s\" cannot both be set", propertyKey1, propertyKey2) + ); + } + } - logger.trace(format("Endpoint %s Negotiation type: '%s', SSLprovider: '%s'", url, nt, sslp)); - SslProvider sslprovider = sslp.equals("openSSL") ? SslProvider.OPENSSL : SslProvider.JDK; - NegotiationType ntype = nt.equals("TLS") ? NegotiationType.TLS : NegotiationType.PLAINTEXT; + private static class GrpcsContext { - SslContextBuilder clientContextBuilder = getSslContextBuilder(clientCert, clientKey, sslprovider); - SslContext sslContext; + private final String cn; + private final byte[] pemBytes; - logger.trace(format("Endpoint %s final server pemBytes: %s", url, Hex.encodeHexString(pemBytes))); + private GrpcsContext( + String cn, byte[] pemBytes) { + this.cn = cn; + this.pemBytes = pemBytes; + } - try (InputStream myInputStream = new ByteArrayInputStream(pemBytes)) { - sslContext = clientContextBuilder - .trustManager(myInputStream) - .build(); - } + public Optional getCn() { + return Optional.ofNullable(cn); + } - channelBuilder = NettyChannelBuilder - .forAddress(addr, port) - .sslContext(sslContext) - .negotiationType(ntype) - .intercept(clientInterceptor); + public Optional getPemBytes() { + return Optional.ofNullable(pemBytes); + } + } - if (cn != null) { - logger.debug(format("Endpoint %s, using CN overrideAuthority: '%s'", url, cn)); - channelBuilder.overrideAuthority(cn); - } - addNettyBuilderProps(channelBuilder, properties); - } catch (SSLException sslex) { + private enum ProtocolType { + GRPC, + GRPCS; + + private static ProtocolType from(String protocol) { + if ("grpc".equalsIgnoreCase(protocol)) { + return GRPC; + } + if ("grpcs".equalsIgnoreCase(protocol)) { + return GRPCS; + } + throw new RuntimeException("invalid protocol: " + protocol); + } + } + + private static Optional getPEMFile(String url, Properties properties) { + byte[] pemBytes; + try (ByteArrayOutputStream bis = new ByteArrayOutputStream(64000)) { + @SuppressWarnings("UseOfPropertiesAsHashtable") byte[] pb = (byte[]) properties.get("pemBytes"); + if (null != pb) { + bis.write(pb); + } + if (properties.containsKey(PROPERTY_PEM_FILE)) { + + String pemFile = properties.getProperty(PROPERTY_PEM_FILE); - throw new RuntimeException(sslex); + String[] pems = PEMFILE_SPLIT_PATTERN.split(pemFile); + + for (String pem : pems) { + if (null != pem && !pem.isEmpty()) { + try { + bis.write(Files.readAllBytes(Paths.get(pem))); + } catch (IOException e) { + throw new RuntimeException(format( + "Failed to read certificate file %s", + new File(pem).getAbsolutePath() + ), e); + } } } - } else { - throw new RuntimeException("invalid protocol: " + protocol); + } - } catch (RuntimeException e) { - logger.error(format("Endpoint %s, exception '%s'", url, e.getMessage()), e); - throw e; - } catch (Exception e) { - logger.error(format("Endpoint %s, exception '%s'", url, e.getMessage()), e); - logger.error(e); - throw new RuntimeException(e); + pemBytes = bis.toByteArray(); + logger.trace(format("Endpoint %s pemBytes: %s", url, Hex.encodeHexString(pemBytes))); + + if (pemBytes.length == 0) { + return Optional.empty(); + } + } catch (IOException e) { + throw new RuntimeException("Failed to read CA certificates file %s", e); } + return Optional.of(pemBytes); } - SslContextBuilder getSslContextBuilder(X509Certificate[] clientCert, PrivateKey clientKey, SslProvider sslprovider) { + SslContextBuilder getSslContextBuilder( + X509Certificate[] clientCert, PrivateKey clientKey, SslProvider sslprovider) { SslContextBuilder clientContextBuilder = GrpcSslContexts.configure(SslContextBuilder.forClient(), sslprovider); if (clientKey != null && clientCert != null) { clientContextBuilder = clientContextBuilder.keyManager(clientKey, clientCert); @@ -315,7 +398,7 @@ byte[] getClientTLSCertificateDigest() { String pemCert = new String(tlsClientCertificatePEMBytes, UTF_8); byte[] derBytes = Base64.getDecoder().decode( - pemCert.replaceAll("-+[ \t]*(BEGIN|END)[ \t]+CERTIFICATE[ \t]*-+", "").replaceAll("\\s", "").trim() + WHITESPACE_PATTERN.matcher(CERT_BODY_PATTERN.matcher(pemCert).replaceAll("")).replaceAll("").trim() ); Digest digest = new SHA256Digest(); @@ -328,19 +411,24 @@ byte[] getClientTLSCertificateDigest() { } 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 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) { if (props == null) { return; } - for (Map.Entry es : props.entrySet()) { + for (Entry es : props.entrySet()) { Object methodprop = es.getKey(); if (methodprop == null) { continue; @@ -360,7 +448,8 @@ private void addNettyBuilderProps(NettyChannelBuilder channelBuilder, Properties } Object parmsArrayO = es.getValue(); - Object[] parmsArray = !(parmsArrayO instanceof Object[]) ? new Object[] {parmsArrayO} : (Object[]) parmsArrayO; + Object[] parmsArray = + !(parmsArrayO instanceof Object[]) ? new Object[]{parmsArrayO} : (Object[]) parmsArrayO; Class[] classParms = new Class[parmsArray.length]; for (int i = 0; i < parmsArray.length; i++) { @@ -386,11 +475,18 @@ private void addNettyBuilderProps(NettyChannelBuilder channelBuilder, Properties } } - Utils.invokeMethod(channelBuilder, methodName, classParms, parmsArray); + try { + Utils.invokeMethod(channelBuilder, methodName, classParms, parmsArray); + } catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { + logger.error(format("Endpoint %s, exception '%s'", url, e.getMessage()), e); + logger.error(e); + throw new RuntimeException(e); + } if (logger.isTraceEnabled()) { logger.trace(format("Endpoint with url: %s set managed channel builder method %s (%s) ", url, - methodName, Arrays.toString(parmsArray))); + methodName, Arrays.toString(parmsArray) + )); } @@ -398,78 +494,103 @@ private void addNettyBuilderProps(NettyChannelBuilder channelBuilder, Properties } - AbstractMap.SimpleImmutableEntry getClientTLSProps(Properties properties) { + private Optional> readClientTLSProps(String url, Properties properties) { + if (properties == null) { + return Optional.empty(); + } // check for mutual TLS - both clientKey and clientCert must be present - byte[] ckb = null, ccb = null; - if (properties.containsKey(NetworkConfig.CLIENT_KEY_FILE) && properties.containsKey(NetworkConfig.CLIENT_KEY_BYTES)) { - throw new RuntimeException("Properties \"clientKeyFile\" and \"clientKeyBytes\" must cannot both be set"); - } else if (properties.containsKey(NetworkConfig.CLIENT_CERT_FILE) && properties.containsKey(NetworkConfig.CLIENT_CERT_BYTES)) { - throw new RuntimeException("Properties \"clientCertFile\" and \"clientCertBytes\" must cannot both be set"); - } else if (properties.containsKey(NetworkConfig.CLIENT_KEY_FILE) || properties.containsKey(NetworkConfig.CLIENT_CERT_FILE)) { - if ((properties.getProperty(NetworkConfig.CLIENT_KEY_FILE) != null) && (properties.getProperty(NetworkConfig.CLIENT_CERT_FILE) != null)) { + validatePropertyExclusivity(properties, CLIENT_KEY_FILE, CLIENT_KEY_BYTES); + validatePropertyExclusivity(properties, CLIENT_CERT_FILE, CLIENT_CERT_BYTES); + + byte[] ckb; + byte[] ccb; + if (properties.containsKey(CLIENT_KEY_FILE) || properties.containsKey(CLIENT_CERT_FILE)) { + String propertyClientKeyFile = properties.getProperty(CLIENT_KEY_FILE); + String propertyClientCertFile = properties.getProperty(CLIENT_CERT_FILE); + + if (propertyClientKeyFile != null && propertyClientCertFile != null) { try { - logger.trace(format("Endpoint %s reading clientKeyFile: %s", url, new File(properties.getProperty(NetworkConfig.CLIENT_KEY_FILE)).getAbsolutePath())); - ckb = Files.readAllBytes(Paths.get(properties.getProperty(NetworkConfig.CLIENT_KEY_FILE))); - logger.trace(format("Endpoint %s reading clientCertFile: %s", url, new File(properties.getProperty(NetworkConfig.CLIENT_CERT_FILE)).getAbsolutePath())); - ccb = Files.readAllBytes(Paths.get(properties.getProperty(NetworkConfig.CLIENT_CERT_FILE))); + logger.trace(format("Endpoint %s reading clientKeyFile: %s", url, + new File(propertyClientKeyFile).getAbsolutePath() + )); + ckb = Files.readAllBytes(Paths.get(propertyClientKeyFile)); + logger.trace(format("Endpoint %s reading clientCertFile: %s", url, + new File(propertyClientCertFile).getAbsolutePath() + )); + ccb = Files.readAllBytes(Paths.get(propertyClientCertFile)); } catch (IOException e) { throw new RuntimeException("Failed to parse TLS client key and/or cert", e); } } else { - throw new RuntimeException(String.format("Properties \"%s\" and \"%s\" must both be set or both be null", NetworkConfig.CLIENT_KEY_FILE, NetworkConfig.CLIENT_CERT_FILE)); + throw new RuntimeException(format( + "Properties \"%s\" and \"%s\" must both be set or both be null", + CLIENT_KEY_FILE, CLIENT_CERT_FILE + )); } - } else if (properties.containsKey(NetworkConfig.CLIENT_KEY_BYTES) || properties.containsKey(NetworkConfig.CLIENT_CERT_BYTES)) { - ckb = (byte[]) properties.get(NetworkConfig.CLIENT_KEY_BYTES); - ccb = (byte[]) properties.get(NetworkConfig.CLIENT_CERT_BYTES); - if ((ckb == null) || (ccb == null)) { - throw new RuntimeException(String.format("Properties \"%s\" and \"%s\" must both be set or both be null", NetworkConfig.CLIENT_KEY_BYTES, NetworkConfig.CLIENT_CERT_BYTES)); + } else if (properties.containsKey(CLIENT_KEY_BYTES) || properties.containsKey(CLIENT_CERT_BYTES)) { + //noinspection UseOfPropertiesAsHashtable + ckb = (byte[]) properties.get(CLIENT_KEY_BYTES); + //noinspection UseOfPropertiesAsHashtable + ccb = (byte[]) properties.get(CLIENT_CERT_BYTES); + + if (ckb == null || ccb == null) { + throw new RuntimeException(format( + "Properties \"%s\" and \"%s\" must both be set or both be null", + CLIENT_KEY_BYTES, CLIENT_CERT_BYTES + )); } + } else { + return Optional.empty(); } - if ((ckb != null) && (ccb != null)) { - String what = "private key"; - byte[] whatBytes = new byte[0]; - try { + return parsePrivateKey(url, ckb, ccb); + } - CryptoPrimitives cp; - try { - cp = new CryptoPrimitives(); - } catch (Exception e) { - throw new RuntimeException(e); - } + private Optional> parsePrivateKey(String url, byte[] ckb, byte[] ccb) { + String what = "private key"; + byte[] whatBytes = new byte[0]; + try { - logger.trace("client TLS private key bytes size:" + ckb.length); - whatBytes = ckb; - logger.trace("client TLS key bytes:" + Hex.encodeHexString(ckb)); - PrivateKey clientKey = cp.bytesToPrivateKey(ckb); - logger.trace("converted TLS key."); - what = "certificate"; - whatBytes = ccb; - logger.trace("client TLS certificate bytes:" + Hex.encodeHexString(ccb)); - X509Certificate[] clientCert = new X509Certificate[] {(X509Certificate) cp.bytesToCertificate(ccb)}; - logger.trace("converted client TLS certificate."); - tlsClientCertificatePEMBytes = ccb; // Save this away it's the exact pem we used. - - return new AbstractMap.SimpleImmutableEntry<>(clientKey, clientCert); - } catch (CryptoException e) { - logger.error(format("Failed endpoint %s to parse %s TLS client %s", url, what, new String(whatBytes))); - throw new RuntimeException(format("Failed endpoint %s to parse TLS client %s", url, what), e); + CryptoPrimitives cp; + try { + cp = new CryptoPrimitives(); + } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) { + throw new RuntimeException(e); } + + logger.trace("client TLS private key bytes size:" + ckb.length); + whatBytes = ckb; + logger.trace("client TLS key bytes:" + Hex.encodeHexString(ckb)); + PrivateKey clientKey = cp.bytesToPrivateKey(ckb); + logger.trace("converted TLS key."); + what = "certificate"; + whatBytes = ccb; + logger.trace("client TLS certificate bytes:" + Hex.encodeHexString(ccb)); + X509Certificate[] clientCert = {(X509Certificate) cp.bytesToCertificate(ccb)}; + logger.trace("converted client TLS certificate."); + tlsClientCertificatePEMBytes = ccb; // Save this away it's the exact pem we used. + + return Optional.of(new Pair<>(clientKey, clientCert)); + } catch (CryptoException e) { + logger.error(format("Failed endpoint %s to parse %s TLS client %s", url, what, new String( + whatBytes, + UTF_8 + ))); + throw new RuntimeException(format("Failed endpoint %s to parse TLS client %s", url, what), e); } - return null; } ManagedChannelBuilder getChannelBuilder() { - return this.channelBuilder; + return channelBuilder; } String getHost() { - return this.addr; + return addr; } int getPort() { - return this.port; + return port; } static Endpoint createEndpoint(String url, Properties properties) { diff --git a/src/test/java/org/hyperledger/fabric/sdk/EndpointTest.java b/src/test/java/org/hyperledger/fabric/sdk/EndpointTest.java index 791a011e..2fceebaa 100644 --- a/src/test/java/org/hyperledger/fabric/sdk/EndpointTest.java +++ b/src/test/java/org/hyperledger/fabric/sdk/EndpointTest.java @@ -248,7 +248,7 @@ public void testClientTLSInvalidProperties() { try { new Endpoint("grpcs://localhost:594", testprops); } catch (RuntimeException e) { - Assert.assertEquals("Properties \"clientKeyFile\" and \"clientKeyBytes\" must cannot both be set", e.getMessage()); + Assert.assertEquals("Properties \"clientKeyFile\" and \"clientKeyBytes\" cannot both be set", e.getMessage()); } testprops.remove("clientKeyFile"); @@ -258,7 +258,7 @@ public void testClientTLSInvalidProperties() { try { new Endpoint("grpcs://localhost:594", testprops); } catch (RuntimeException e) { - Assert.assertEquals("Properties \"clientCertFile\" and \"clientCertBytes\" must cannot both be set", e.getMessage()); + Assert.assertEquals("Properties \"clientCertFile\" and \"clientCertBytes\" cannot both be set", e.getMessage()); } testprops.remove("clientCertFile"); @@ -305,7 +305,7 @@ public void testClientTLSCACertProperties() throws Exception { Properties testprops = new Properties(); testprops.setProperty("pemFile", "src/test/fixture/testPems/caBundled.pems," + // has 4 certs - "src/test/fixture/testPems/AnotherUniqCA.pem"); // has 1 + "src/test/fixture/testPems/AnotherUniqCA.pem"); // has 1 testprops.put("pemBytes", Files.readAllBytes(Paths.get("src/test/fixture/testPems/Org2MSP_CA.pem"))); //Can have pem bytes too. 1 cert @@ -328,12 +328,12 @@ protected SslContextBuilder getSslContextBuilder(X509Certificate[] clientCert, P X509Certificate[] certs = (X509Certificate[]) getField(endpoint.sslContextBuilder, "trustCertCollection"); Set expected = new HashSet<>(Arrays.asList( - new BigInteger("4804555946196630157804911090140692961"), - new BigInteger("127556113420528788056877188419421545986539833585"), - new BigInteger("704500179517916368023344392810322275871763581896"), - new BigInteger("70307443136265237483967001545015671922421894552"), - new BigInteger("276393268186007733552859577416965113792"), - new BigInteger("217904166635533061823782766071154643254"))); + new BigInteger("4804555946196630157804911090140692961"), + new BigInteger("127556113420528788056877188419421545986539833585"), + new BigInteger("704500179517916368023344392810322275871763581896"), + new BigInteger("70307443136265237483967001545015671922421894552"), + new BigInteger("276393268186007733552859577416965113792"), + new BigInteger("217904166635533061823782766071154643254"))); for (X509Certificate cert : certs) { final BigInteger serialNumber = cert.getSerialNumber(); @@ -350,8 +350,8 @@ public void testClientTLSCACertPropertiesBadFile() throws Exception { Properties testprops = new Properties(); testprops.setProperty("pemFile", "src/test/fixture/testPems/caBundled.pems," + // has 3 certs - "src/test/fixture/testPems/IMBAD" + - ",src/test/fixture/testPems/Org1MSP_CA.pem"); // has 1 + "src/test/fixture/testPems/IMBAD" + + ",src/test/fixture/testPems/Org1MSP_CA.pem"); // has 1 Endpoint endpoint = new Endpoint("grpcs://localhost:594", testprops);