com.vmware.xenon.common.http.netty.Netty2WaySslAuthTest.java Source code

Java tutorial

Introduction

Here is the source code for com.vmware.xenon.common.http.netty.Netty2WaySslAuthTest.java

Source

/*
 * Copyright (c) 2014-2015 VMware, Inc. 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 com.vmware.xenon.common.http.netty;

import static org.junit.Assert.assertEquals;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.security.KeyStore;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;

import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;

import com.vmware.xenon.common.CommandLineArgumentParser;
import com.vmware.xenon.common.Operation;
import com.vmware.xenon.common.ServiceClient;
import com.vmware.xenon.common.ServiceDocument;
import com.vmware.xenon.common.ServiceHost;
import com.vmware.xenon.common.ServiceHost.ServiceHostState.SslClientAuthMode;
import com.vmware.xenon.common.StatelessService;
import com.vmware.xenon.common.UriUtils;
import com.vmware.xenon.common.Utils;
import com.vmware.xenon.common.test.VerificationHost;
import com.vmware.xenon.services.common.NodeGroupService.NodeGroupState;
import com.vmware.xenon.services.common.NodeState;
import com.vmware.xenon.services.common.ServiceUriPaths;

/**
 * DCP Host with 2-way SSL certificate authentication test.
 * <p/>
 * Server uses server.crt as a certificate and server.pem as private key and trustedcerts.jks as a keystore of trusted
 * certificates used to validate client's certificate.
 * <p/>
 * Client uses client.p12 as client key and certificate and trustedcerts.jks as a keystore of trusted certificates used
 * to validate client's certificate
 */
public class Netty2WaySslAuthTest {
    public static final String JAVAX_NET_SSL_TRUST_STORE = "javax.net.ssl.trustStore";
    public static final String JAVAX_NET_SSL_TRUST_STORE_PASSWORD = "javax.net.ssl.trustStorePassword";

    public int securePort = 0;
    private VerificationHost host;
    private TemporaryFolder temporaryFolder;
    private static String savedTrustStore;
    private static String savedTrustStorePassword;

    @BeforeClass
    public static void setUpClass() throws Exception {
        savedTrustStore = System.getProperty(JAVAX_NET_SSL_TRUST_STORE);
        savedTrustStorePassword = System.getProperty(JAVAX_NET_SSL_TRUST_STORE_PASSWORD);
        System.setProperty(JAVAX_NET_SSL_TRUST_STORE,
                getCanonicalFileForResource("/ssl/trustedcerts.jks").getCanonicalPath());
        System.setProperty(JAVAX_NET_SSL_TRUST_STORE_PASSWORD, "changeit");
    }

    @AfterClass
    public static void tearDownClass() throws Exception {
        if (savedTrustStore == null) {
            System.clearProperty(JAVAX_NET_SSL_TRUST_STORE);
        } else {
            System.setProperty(JAVAX_NET_SSL_TRUST_STORE, savedTrustStore);
        }
        if (savedTrustStorePassword == null) {
            System.clearProperty(JAVAX_NET_SSL_TRUST_STORE_PASSWORD);
        } else {
            System.setProperty(JAVAX_NET_SSL_TRUST_STORE_PASSWORD, savedTrustStorePassword);
        }
    }

    @Before
    public void setUp() throws Throwable {
        CommandLineArgumentParser.parseFromProperties(this);
        this.temporaryFolder = new TemporaryFolder();
        this.temporaryFolder.create();
        this.host = new VerificationHost();
        ServiceHost.Arguments args = new ServiceHost.Arguments();
        args.securePort = this.securePort;
        args.port = 0;
        args.keyFile = getCanonicalFileForResource("/ssl/server.pem").toPath();
        args.certificateFile = getCanonicalFileForResource("/ssl/server.crt").toPath();
        args.sslClientAuthMode = SslClientAuthMode.WANT;
        args.sandbox = this.temporaryFolder.getRoot().toPath();
        args.bindAddress = ServiceHost.LOOPBACK_ADDRESS;

        if (args.securePort != 0) {
            args.port = 0;
            args.peerNodes = new String[] { "https://127.0.0.1:" + args.securePort };
        }

        this.host.initialize(args);
        this.host.start();

        if (args.securePort == 0) {
            return;
        }

        // verify that the self URI supplied, even if its a HTTPS, was filtered out of the
        // peer list
        assertEquals(this.host.getInitialPeerHosts().size(), 0);

        // verify quorum is set to 1, since we supplied just self as peer
        this.host.waitFor("quorum not set", () -> {
            NodeGroupState ngs = this.host.getServiceState(null, NodeGroupState.class,
                    UriUtils.buildUri(this.host, ServiceUriPaths.DEFAULT_NODE_GROUP));
            NodeState self = ngs.nodes.get(this.host.getId());
            if (self == null) {
                return false;
            }
            if (self.membershipQuorum != 1) {
                return false;
            }
            return true;
        });
    }

    @After
    public void tearDown() {
        this.host.stop();
        this.temporaryFolder.delete();
    }

    @Test
    public void testCustomSslContext() throws Throwable {
        Path keyFile = getCanonicalFileForResource("/ssl/server.pem").toPath();
        Path certificateFile = getCanonicalFileForResource("/ssl/server.crt").toPath();
        SslContext customContext = SslContextBuilder.forServer(certificateFile.toFile(), keyFile.toFile(), null)
                .build();
        NettyHttpListener l = new NettyHttpListener(this.host);
        l.setSSLContext(customContext);

        // verify that we can not set context after host is started
        NettyHttpListener hostListener = (NettyHttpListener) this.host.getListener();
        try {
            hostListener.setSSLContext(customContext);
            throw new RuntimeException("call should have thrown an exception");
        } catch (IllegalStateException e) {

        }

        // now stop the host, replace the listener, and restart
        this.host.stop();
        this.host.setPort(0);
        this.host.setSecurePort(0);
        this.host.setListener(l);
        this.host.start();
        assertEquals(l, this.host.getListener());
        hostListener = (NettyHttpListener) this.host.getListener();
        assertEquals(hostListener.getSSLContext(), customContext);

        test2WaySsl();
    }

    @Test
    public void test2WaySsl() throws Throwable {
        // Start sample test service
        this.host.testStart(1);
        URI testServiceUri = UriUtils.buildUri(this.host, TestService.SELF_LINK);
        Operation post = Operation.createPost(testServiceUri)
                .setCompletion((o, e) -> this.host.completeIteration());
        this.host.startService(post, new TestService());
        this.host.testWait();

        // Create ServiceClient with client SSL auth support
        ServiceClient client = NettyHttpServiceClient.create(getClass().getCanonicalName(),
                Executors.newFixedThreadPool(4), Executors.newScheduledThreadPool(1));

        SSLContext clientContext = SSLContext.getInstance(ServiceClient.TLS_PROTOCOL_NAME);
        TrustManagerFactory trustManagerFactory = TrustManagerFactory
                .getInstance(TrustManagerFactory.getDefaultAlgorithm());
        trustManagerFactory.init((KeyStore) null);
        KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        try (InputStream stream = Netty2WaySslAuthTest.class.getResourceAsStream("/ssl/client.p12")) {
            keyStore.load(stream, "changeit".toCharArray());
        }
        kmf.init(keyStore, "changeit".toCharArray());
        clientContext.init(kmf.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
        client.setSSLContext(clientContext);
        client.start();

        // Perform request and validate that detected principal is correct
        this.host.testStart(1);
        AtomicReference<String> result = new AtomicReference<>();
        Operation get = Operation.createGet(UriUtils.buildUri(this.host.getSecureUri(), TestService.SELF_LINK))
                .setReferer(this.host.getPublicUri()).setCompletion((o, e) -> {
                    if (e == null) {
                        result.set(o.getBody(TestServiceResponse.class).principal);
                    } else {
                        this.host.log(Level.SEVERE, "Operation failed: %s", Utils.toString(e));
                    }
                    this.host.completeIteration();
                });
        client.send(get);
        this.host.testWait();
        Assert.assertNotNull("Peer principal", result.get());
        Assert.assertEquals("Peer principal", "CN=agent-461b1767-ea89-4452-9408-283d0752fe40", result.get());
        client.stop();
    }

    private static File getCanonicalFileForResource(String resourceName) throws IOException, URISyntaxException {
        return new File(new URI(Netty2WaySslAuthTest.class.getResource(resourceName).toString()))
                .getCanonicalFile();
    }

    public static class TestServiceResponse {
        public String principal;
    }

    /**
     * This test service returns client's SSL principal name on each GET request.
     */
    public static class TestService extends StatelessService {
        public static final String SELF_LINK = "/ssl_test";

        public TestService() {
            super(ServiceDocument.class);
        }

        @Override
        public void handleGet(Operation get) {
            try {
                TestServiceResponse resp = new TestServiceResponse();
                resp.principal = get.getPeerPrincipal().toString();
                get.setBody(resp);
                get.complete();
            } catch (Exception e) {
                get.fail(e);
            }
        }
    }
}