[ 
https://issues.apache.org/jira/browse/ZOOKEEPER-236?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=15968204#comment-15968204
 ] 

ASF GitHub Bot commented on ZOOKEEPER-236:
------------------------------------------

Github user hanm commented on a diff in the pull request:

    https://github.com/apache/zookeeper/pull/184#discussion_r111487159
  
    --- Diff: src/java/test/org/apache/zookeeper/test/QuorumSSLTest.java ---
    @@ -0,0 +1,668 @@
    +/**
    + * Licensed to the Apache Software Foundation (ASF) under one
    + * or more contributor license agreements.  See the NOTICE file
    + * distributed with this work for additional information
    + * regarding copyright ownership.  The ASF licenses this file
    + * to you 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
    + * <p/>
    + * http://www.apache.org/licenses/LICENSE-2.0
    + * <p/>
    + * 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.apache.zookeeper.test;
    +
    +import com.sun.net.httpserver.Headers;
    +import com.sun.net.httpserver.HttpHandler;
    +import com.sun.net.httpserver.HttpServer;
    +import org.apache.zookeeper.PortAssignment;
    +import org.apache.zookeeper.client.ZKClientConfig;
    +import org.apache.zookeeper.common.QuorumX509Util;
    +import org.apache.zookeeper.server.ServerCnxnFactory;
    +import org.apache.zookeeper.server.quorum.QuorumPeerTestBase;
    +import org.bouncycastle.asn1.ocsp.OCSPResponse;
    +import org.bouncycastle.asn1.ocsp.OCSPResponseStatus;
    +import org.bouncycastle.asn1.x500.X500Name;
    +import org.bouncycastle.asn1.x500.X500NameBuilder;
    +import org.bouncycastle.asn1.x500.style.BCStyle;
    +import org.bouncycastle.asn1.x509.AuthorityInformationAccess;
    +import org.bouncycastle.asn1.x509.BasicConstraints;
    +import org.bouncycastle.asn1.x509.CRLDistPoint;
    +import org.bouncycastle.asn1.x509.CRLNumber;
    +import org.bouncycastle.asn1.x509.CRLReason;
    +import org.bouncycastle.asn1.x509.DistributionPoint;
    +import org.bouncycastle.asn1.x509.DistributionPointName;
    +import org.bouncycastle.asn1.x509.Extension;
    +import org.bouncycastle.asn1.x509.GeneralName;
    +import org.bouncycastle.asn1.x509.GeneralNames;
    +import org.bouncycastle.asn1.x509.KeyUsage;
    +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
    +import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
    +import org.bouncycastle.cert.X509CRLHolder;
    +import org.bouncycastle.cert.X509CertificateHolder;
    +import org.bouncycastle.cert.X509ExtensionUtils;
    +import org.bouncycastle.cert.X509v2CRLBuilder;
    +import org.bouncycastle.cert.X509v3CertificateBuilder;
    +import org.bouncycastle.cert.bc.BcX509ExtensionUtils;
    +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
    +import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
    +import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
    +import org.bouncycastle.cert.jcajce.JcaX509v2CRLBuilder;
    +import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
    +import org.bouncycastle.cert.ocsp.BasicOCSPResp;
    +import org.bouncycastle.cert.ocsp.BasicOCSPRespBuilder;
    +import org.bouncycastle.cert.ocsp.CertificateID;
    +import org.bouncycastle.cert.ocsp.CertificateStatus;
    +import org.bouncycastle.cert.ocsp.OCSPException;
    +import org.bouncycastle.cert.ocsp.OCSPReq;
    +import org.bouncycastle.cert.ocsp.OCSPResp;
    +import org.bouncycastle.cert.ocsp.OCSPRespBuilder;
    +import org.bouncycastle.cert.ocsp.Req;
    +import org.bouncycastle.cert.ocsp.UnknownStatus;
    +import org.bouncycastle.cert.ocsp.jcajce.JcaBasicOCSPRespBuilder;
    +import org.bouncycastle.cert.ocsp.jcajce.JcaCertificateID;
    +import org.bouncycastle.crypto.util.PublicKeyFactory;
    +import org.bouncycastle.crypto.util.SubjectPublicKeyInfoFactory;
    +import org.bouncycastle.jce.provider.BouncyCastleProvider;
    +import org.bouncycastle.openssl.MiscPEMGenerator;
    +import org.bouncycastle.operator.ContentSigner;
    +import org.bouncycastle.operator.DigestCalculator;
    +import org.bouncycastle.operator.OperatorException;
    +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
    +import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
    +import org.bouncycastle.util.io.pem.PemWriter;
    +import org.junit.After;
    +import org.junit.Assert;
    +import org.junit.Before;
    +import org.junit.Test;
    +
    +import java.io.FileOutputStream;
    +import java.io.FileWriter;
    +import java.io.IOException;
    +import java.io.InputStream;
    +import java.io.OutputStream;
    +import java.math.BigInteger;
    +import java.net.InetSocketAddress;
    +import java.security.KeyPair;
    +import java.security.KeyPairGenerator;
    +import java.security.KeyStore;
    +import java.security.NoSuchAlgorithmException;
    +import java.security.NoSuchProviderException;
    +import java.security.PrivateKey;
    +import java.security.Security;
    +import java.security.cert.Certificate;
    +import java.security.cert.CertificateEncodingException;
    +import java.security.cert.X509Certificate;
    +import java.util.ArrayList;
    +import java.util.Calendar;
    +import java.util.Date;
    +import java.util.HashMap;
    +import java.util.List;
    +import java.util.Map;
    +import java.util.Random;
    +
    +import static org.apache.zookeeper.test.ClientBase.CONNECTION_TIMEOUT;
    +import static org.apache.zookeeper.test.ClientBase.createTmpDir;
    +
    +public class QuorumSSLTest extends QuorumPeerTestBase {
    +
    +    private static final String SSL_QUORUM_ENABLED = "sslQuorum=true\n";
    +    private static final String PORT_UNIFICATION_ENABLED = 
"portUnification=true\n";
    +    private static final String PORT_UNIFICATION_DISABLED = 
"portUnification=false\n";
    +
    +    private static final char[] PASSWORD = "testpass".toCharArray();
    +    private static final String HOSTNAME = "localhost";
    +
    +    private QuorumX509Util quorumX509Util = new QuorumX509Util();
    +
    +    private MainThread q1;
    +    private MainThread q2;
    +    private MainThread q3;
    +
    +    private int clientPortQp1;
    +    private int clientPortQp2;
    +    private int clientPortQp3;
    +
    +    private String tmpDir;
    +
    +    private String quorumConfiguration;
    +    private String validKeystorePath;
    +    private String truststorePath;
    +
    +    private KeyPair rootKeyPair;
    +    private X509Certificate rootCertificate;
    +
    +    private KeyPair defaultKeyPair;
    +
    +    private ContentSigner contentSigner;
    +
    +    @Before
    +    public void setup() throws Exception {
    +        ClientBase.setupTestEnv();
    +
    +        tmpDir = createTmpDir().getAbsolutePath();
    +
    +        clientPortQp1 = PortAssignment.unique();
    +        clientPortQp2 = PortAssignment.unique();
    +        clientPortQp3 = PortAssignment.unique();
    +
    +        validKeystorePath = tmpDir + "/valid.jks";
    +        truststorePath = tmpDir + "/truststore.jks";
    +
    +        quorumConfiguration = generateQuorumConfiguration();
    +
    +        Security.addProvider(new BouncyCastleProvider());
    +
    +        rootKeyPair = createKeyPair();
    +        contentSigner = new 
JcaContentSignerBuilder("SHA256WithRSAEncryption").build(rootKeyPair.getPrivate());
    +        rootCertificate = createSelfSignedCertifcate(rootKeyPair);
    +
    +        // Write the truststore
    +        KeyStore trustStore = 
KeyStore.getInstance(KeyStore.getDefaultType());
    +        trustStore.load(null, PASSWORD);
    +        
trustStore.setCertificateEntry(rootCertificate.getSubjectDN().toString(), 
rootCertificate);
    +        FileOutputStream outputStream = new 
FileOutputStream(truststorePath);
    +        trustStore.store(outputStream, PASSWORD);
    +        outputStream.flush();
    +        outputStream.close();
    +
    +        defaultKeyPair = createKeyPair();
    +        X509Certificate validCertificate = 
buildEndEntityCert(defaultKeyPair, rootCertificate, rootKeyPair.getPrivate(),
    +                HOSTNAME, null, null, null);
    +        writeKeystore(validCertificate, defaultKeyPair, validKeystorePath);
    +
    +        setSSLSystemProperties();
    +    }
    +
    +    private void writeKeystore(X509Certificate certificate, KeyPair 
entityKeyPair, String path) throws Exception {
    +        KeyStore keyStore = 
KeyStore.getInstance(KeyStore.getDefaultType());
    +        keyStore.load(null, PASSWORD);
    +        keyStore.setKeyEntry("alias", entityKeyPair.getPrivate(), 
PASSWORD, new Certificate[] { certificate });
    +        FileOutputStream outputStream = new FileOutputStream(path);
    +        keyStore.store(outputStream, PASSWORD);
    +        outputStream.flush();
    +        outputStream.close();
    +    }
    +
    +
    +    private class OCSPHandler implements HttpHandler {
    +
    +        private X509Certificate revokedCert;
    +
    +        // Builds an OCSPHandler that responds with a good status for all 
certificates
    +        // except revokedCert.
    +        public OCSPHandler(X509Certificate revokedCert) {
    +            this.revokedCert = revokedCert;
    +        }
    +
    +        @Override
    +        public void handle(com.sun.net.httpserver.HttpExchange 
httpExchange) throws IOException {
    +            byte[] responseBytes;
    +            try {
    +                InputStream request = httpExchange.getRequestBody();
    +                byte[] requestBytes = new byte[10000];
    +                request.read(requestBytes);
    +
    +                OCSPReq ocspRequest = new OCSPReq(requestBytes);
    +                Req[] requestList = ocspRequest.getRequestList();
    +
    +                DigestCalculator digestCalculator = new 
JcaDigestCalculatorProviderBuilder().build().get(CertificateID.HASH_SHA1);
    +
    +                BasicOCSPRespBuilder responseBuilder = new 
JcaBasicOCSPRespBuilder(rootKeyPair.getPublic(), digestCalculator);
    +                for ( Req req : requestList ) {
    +                    CertificateID certId = req.getCertID();
    +                    CertificateID revokedCertId = new 
JcaCertificateID(digestCalculator, rootCertificate, 
revokedCert.getSerialNumber());
    +                    CertificateStatus certificateStatus;
    +                    if (revokedCertId.equals(certId)) {
    +                        certificateStatus = new UnknownStatus();
    +                    } else {
    +                        certificateStatus = CertificateStatus.GOOD;
    +                    }
    +
    +                    responseBuilder.addResponse(certId, 
certificateStatus,null);
    +                }
    +
    +                X509CertificateHolder[] chain = new 
X509CertificateHolder[] { new JcaX509CertificateHolder(rootCertificate) };
    +                ContentSigner signer = new 
JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(rootKeyPair.getPrivate());
    +                BasicOCSPResp ocspResponse = responseBuilder.build(signer, 
chain, Calendar.getInstance().getTime() );
    +
    +                responseBytes = new 
OCSPRespBuilder().build(OCSPRespBuilder.SUCCESSFUL, ocspResponse).getEncoded();
    +            } catch (OperatorException | CertificateEncodingException | 
OCSPException exception) {
    +                responseBytes = new OCSPResp(new OCSPResponse(new 
OCSPResponseStatus(OCSPRespBuilder.INTERNAL_ERROR), null)).getEncoded();
    +            }
    +
    +            Headers rh = httpExchange.getResponseHeaders();
    +            rh.set("Content-Type", "application/ocsp-response");
    +            httpExchange.sendResponseHeaders(200, responseBytes.length);
    +
    +            OutputStream os = httpExchange.getResponseBody();
    +            os.write(responseBytes);
    +            os.close();
    +        }
    +    }
    +
    +    private X509Certificate createSelfSignedCertifcate(KeyPair keyPair) 
throws Exception {
    +        X500NameBuilder nameBuilder = new 
X500NameBuilder(BCStyle.INSTANCE);
    +        nameBuilder.addRDN(BCStyle.CN, HOSTNAME);
    +        Date notBefore = new Date();              // time from which 
certificate is valid
    +        Calendar cal = Calendar.getInstance();
    +        cal.setTime(notBefore);
    +        cal.add(Calendar.YEAR, 1);
    +        Date notAfter = cal.getTime();
    +        BigInteger serialNumber = new BigInteger(128, new Random());
    +
    +        X509v3CertificateBuilder certificateBuilder =
    +                new JcaX509v3CertificateBuilder(nameBuilder.build(), 
serialNumber, notBefore, notAfter, nameBuilder.build(), keyPair.getPublic())
    +                .addExtension(Extension.basicConstraints, true, new 
BasicConstraints(0))
    +                .addExtension(Extension.keyUsage, true, new 
KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyCertSign | KeyUsage.cRLSign));
    +
    +        return new 
JcaX509CertificateConverter().getCertificate(certificateBuilder.build(contentSigner));
    +    }
    +
    +    private void buildCRL(X509Certificate x509Certificate, String crlPath) 
throws Exception {
    +        X509v2CRLBuilder builder = new 
JcaX509v2CRLBuilder(x509Certificate.getIssuerX500Principal(), new Date());
    +        Date notBefore = new Date();
    +        Calendar cal = Calendar.getInstance();
    +        cal.setTime(notBefore);
    +        cal.add(Calendar.YEAR, 1);
    +        Date notAfter = cal.getTime();
    +        builder.setNextUpdate(notAfter);
    +        builder.addCRLEntry(x509Certificate.getSerialNumber(), new Date(), 
CRLReason.cACompromise);
    +        builder.addExtension(Extension.authorityKeyIdentifier, false, new 
JcaX509ExtensionUtils().createAuthorityKeyIdentifier(rootCertificate));
    +        builder.addExtension(Extension.cRLNumber, false, new CRLNumber(new 
BigInteger("1000")));
    +
    +        X509CRLHolder cRLHolder = builder.build(contentSigner);
    +
    +        PemWriter pemWriter = new PemWriter(new FileWriter(crlPath));
    +        pemWriter.writeObject(new MiscPEMGenerator(cRLHolder));
    +        pemWriter.flush();
    +        pemWriter.close();
    +    }
    +
    +    public X509Certificate buildEndEntityCert(KeyPair keyPair, 
X509Certificate caCert, PrivateKey caPrivateKey,
    +                                              String hostname, String 
ipAddress, String crlPath, Integer ocspPort) throws Exception {
    +        X509CertificateHolder holder = new 
JcaX509CertificateHolder(caCert);
    +        ContentSigner signer =new 
JcaContentSignerBuilder("SHA256WithRSAEncryption").build(caPrivateKey);
    +
    +        List<GeneralName> generalNames = new ArrayList<>();
    +        if (hostname != null) {
    +            generalNames.add(new GeneralName(GeneralName.dNSName, 
hostname));
    +        }
    +
    +        if (ipAddress != null) {
    +            generalNames.add(new GeneralName(GeneralName.iPAddress, 
ipAddress));
    +        }
    +
    +        SubjectPublicKeyInfo entityKeyInfo =
    +                
SubjectPublicKeyInfoFactory.createSubjectPublicKeyInfo(PublicKeyFactory.createKey(keyPair.getPublic().getEncoded()));
    +        X509ExtensionUtils extensionUtils = new BcX509ExtensionUtils();
    +        X509v3CertificateBuilder certificateBuilder = new 
JcaX509v3CertificateBuilder(holder.getSubject(), new BigInteger(128, new 
Random()),
    +                new Date(System.currentTimeMillis()), new 
Date(System.currentTimeMillis() + 100000),
    +                new X500Name("CN=Test End Entity Certificate"), 
keyPair.getPublic())
    +                .addExtension(Extension.authorityKeyIdentifier, false, 
extensionUtils.createAuthorityKeyIdentifier(holder))
    +                .addExtension(Extension.subjectKeyIdentifier, false, 
extensionUtils.createSubjectKeyIdentifier(entityKeyInfo))
    +                .addExtension(Extension.basicConstraints, true, new 
BasicConstraints(false))
    +                .addExtension(Extension.keyUsage, true, new 
KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment));
    +
    +        if (!generalNames.isEmpty()) {
    +            
certificateBuilder.addExtension(Extension.subjectAlternativeName,  true,  new 
GeneralNames(generalNames.toArray(new GeneralName[] {})));
    +        }
    +
    +        if (crlPath != null) {
    +            DistributionPointName distPointOne = new 
DistributionPointName(new GeneralNames(
    +                    new 
GeneralName(GeneralName.uniformResourceIdentifier,"file://" + crlPath)));
    +
    +            
certificateBuilder.addExtension(Extension.cRLDistributionPoints, false,
    +                    new CRLDistPoint(new DistributionPoint[] { new 
DistributionPoint(distPointOne, null, null) }));
    +        }
    +
    +        if (ocspPort != null) {
    +            certificateBuilder.addExtension(Extension.authorityInfoAccess, 
false, new AuthorityInformationAccess(X509ObjectIdentifiers.ocspAccessMethod,
    +                    new GeneralName(GeneralName.uniformResourceIdentifier, 
"http://"; + hostname + ":" + ocspPort)));
    +        }
    +
    +        return new 
JcaX509CertificateConverter().getCertificate(certificateBuilder.build(signer));
    +    }
    +
    +
    +    private KeyPair createKeyPair() throws NoSuchProviderException, 
NoSuchAlgorithmException {
    +        KeyPairGenerator keyPairGenerator = 
KeyPairGenerator.getInstance("RSA", BouncyCastleProvider.PROVIDER_NAME);
    +        keyPairGenerator.initialize(4096);
    +        KeyPair keyPair = keyPairGenerator.genKeyPair();
    +        return keyPair;
    +    }
    +
    +    private String generateQuorumConfiguration() {
    +        int portQp1 = PortAssignment.unique();
    +        int portQp2 = PortAssignment.unique();
    +        int portQp3 = PortAssignment.unique();
    +
    +        int portLe1 = PortAssignment.unique();
    +        int portLe2 = PortAssignment.unique();
    +        int portLe3 = PortAssignment.unique();
    +
    +
    +
    +        return "server.1=127.0.0.1:" + (portQp1) + ":" + (portLe1) + ";" + 
 clientPortQp1 + "\n" +
    +               "server.2=127.0.0.1:" + (portQp2) + ":" + (portLe2) + ";" + 
clientPortQp2 + "\n" +
    +               "server.3=127.0.0.1:" + (portQp3) + ":" + (portLe3) + ";" + 
clientPortQp3;
    +    }
    +
    +
    +    public void setSSLSystemProperties() {
    +        
System.setProperty(ServerCnxnFactory.ZOOKEEPER_SERVER_CNXN_FACTORY, 
"org.apache.zookeeper.server.NettyServerCnxnFactory");
    +        System.setProperty(ZKClientConfig.ZOOKEEPER_CLIENT_CNXN_SOCKET, 
"org.apache.zookeeper.ClientCnxnSocketNetty");
    +        
System.setProperty(quorumX509Util.getSslKeystoreLocationProperty(), 
validKeystorePath);
    +        System.setProperty(quorumX509Util.getSslKeystorePasswdProperty(), 
"testpass");
    +        
System.setProperty(quorumX509Util.getSslTruststoreLocationProperty(), 
truststorePath);
    +        
System.setProperty(quorumX509Util.getSslTruststorePasswdProperty(), "testpass");
    +        
System.setProperty(quorumX509Util.getSslHostnameVerificationEnabledProperty(), 
"false");
    +    }
    +
    +    @After
    +    public void cleanUp() throws Exception {
    +        clearSSLSystemProperties();
    +        q1.shutdown();
    +        q2.shutdown();
    +        q3.shutdown();
    +
    +        Security.removeProvider("BC");
    +    }
    +
    +    private void clearSSLSystemProperties() {
    +        
System.clearProperty(quorumX509Util.getSslKeystoreLocationProperty());
    +        
System.clearProperty(quorumX509Util.getSslKeystorePasswdProperty());
    +        
System.clearProperty(quorumX509Util.getSslTruststoreLocationProperty());
    +        
System.clearProperty(quorumX509Util.getSslTruststorePasswdProperty());
    +        
System.clearProperty(quorumX509Util.getSslHostnameVerificationEnabledProperty());
    +        System.clearProperty(quorumX509Util.getSslOcspEnabledProperty());
    +        System.clearProperty(quorumX509Util.getSslCrlEnabledProperty());
    +    }
    +
    +    @Test(timeout = 300000)
    +    public void testQuorumSSL() throws Exception {
    +        q1 = new MainThread(1, clientPortQp1, quorumConfiguration, 
SSL_QUORUM_ENABLED);
    +        q2 = new MainThread(2, clientPortQp2, quorumConfiguration, 
SSL_QUORUM_ENABLED);
    +
    +
    +        q1.start();
    +        q2.start();
    +
    +        Assert.assertTrue(ClientBase.waitForServerUp("127.0.0.1:" + 
clientPortQp1, CONNECTION_TIMEOUT));
    +        Assert.assertTrue(ClientBase.waitForServerUp("127.0.0.1:" + 
clientPortQp2, CONNECTION_TIMEOUT));
    +
    +        clearSSLSystemProperties();
    +
    +        // This server should fail to join the quorum as it is not using 
ssl.
    +        q3 = new MainThread(3, clientPortQp3, quorumConfiguration);
    +        q3.start();
    +
    +        Assert.assertFalse(ClientBase.waitForServerUp("127.0.0.1:" + 
clientPortQp3, CONNECTION_TIMEOUT));
    +    }
    +
    +    @Test(timeout = 300000)
    +    public void testRollingUpgrade() throws Exception {
    --- End diff --
    
    This test is pretty flaky - please check internal Jenkins to diagnose. 


> SSL Support for Atomic Broadcast protocol
> -----------------------------------------
>
>                 Key: ZOOKEEPER-236
>                 URL: https://issues.apache.org/jira/browse/ZOOKEEPER-236
>             Project: ZooKeeper
>          Issue Type: New Feature
>          Components: quorum, server
>            Reporter: Benjamin Reed
>            Assignee: Abraham Fine
>            Priority: Minor
>
> We should have the ability to use SSL to authenticate and encrypt the traffic 
> between ZooKeeper servers. For the most part this is a very easy change. We 
> would probably only want to support this for TCP based leader elections.



--
This message was sent by Atlassian JIRA
(v6.3.15#6346)

Reply via email to