/*
* X509CertificateServerTest.java
*
* Created on May 14, 2010, 3:32:25 PM
*
* Description: .
*
* Copyright (C) May 14, 2010, Stephen L. Reed.
*
* This program is free software; you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.texai.x509certificateservertest;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyPair;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
import org.jboss.netty.handler.codec.http.DefaultHttpRequest;
import org.jboss.netty.handler.codec.http.HttpHeaders;
import org.jboss.netty.handler.codec.http.HttpMethod;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.HttpVersion;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.texai.network.netty.handler.AbstractHTTPResponseHandler;
import org.texai.network.netty.handler.HTTPRequestHandler;
import org.texai.network.netty.handler.PortUnificationHandler;
import org.texai.network.netty.pipeline.HTTPClientPipelineFactory;
import org.texai.network.netty.pipeline.PortUnificationChannelPipelineFactory;
import org.texai.network.netty.pipeline.SSLPipelineFactory;
import org.texai.ssl.TexaiSSLContextFactory;
import org.texai.util.Base64Coder;
import org.texai.util.ByteUtils;
import org.texai.util.EnvironmentUtils;
import org.texai.util.NetworkUtils;
import org.texai.x509.KeyStoreTestUtils;
import org.texai.x509.X509SecurityInfo;
import org.texai.x509.X509Utils;
import static org.junit.Assert.*;
/**
*
* @author reed
*/
public class X509CertificateServerTest {
/** the logger */
private static final Logger LOGGER = Logger.getLogger(X509CertificateServerTest.class);
/** the server port */
private static final int SERVER_PORT = 443;
/** the number certificates served */
private final AtomicInteger nbrKeyPairsGenerated = new AtomicInteger(0);
/** the certificate generation duration milliseconds */
private final AtomicLong keyPairGenerationDurationMillis = new AtomicLong(0L);
// for SSL debugging
// static {
// System.setProperty("javax.net.debug", "all");
// }
/** Constructs a new X509CertificateServerTest instance. */
public X509CertificateServerTest() {
}
@BeforeClass
public static void setUpClass() throws Exception {
/** sets debugging */
}
@AfterClass
public static void tearDownClass() throws Exception {
}
@Before
public void setUp() {
}
@After
public void tearDown() {
}
/**
* Test of class ChatServer.
*/
@Test
public void testX509CertificateServer() {
LOGGER.info("testX509CertificateServer");
Logger.getLogger(PortUnificationHandler.class).setLevel(Level.WARN);
Logger.getLogger(PortUnificationChannelPipelineFactory.class).setLevel(Level.WARN);
Logger.getLogger(HTTPRequestHandler.class).setLevel(Level.WARN);
Logger.getLogger(TexaiSSLContextFactory.class).setLevel(Level.WARN);
Logger.getLogger(SSLPipelineFactory.class).setLevel(Level.WARN);
Logger.getLogger(HTTPClientPipelineFactory.class).setLevel(Level.WARN);
final String host = EnvironmentUtils.certificateServerHost();
LOGGER.info("certificate server host: " + host);
if (!NetworkUtils.isHostAvailable(host, SERVER_PORT)) {
LOGGER.info("bypassing test of the unavailable X.509 certificate server");
return;
}
assertTrue(NetworkUtils.isHostAvailable(host, SERVER_PORT));
// test chat server with mock clients
int nbrThreads;
if (X509Utils.isTrustedDevelopmentSystem()) {
nbrThreads = Runtime.getRuntime().availableProcessors();
LOGGER.info("launching " + nbrThreads + " test thread(s");
} else {
nbrThreads = 1;
LOGGER.info("launching 1 test thread");
}
final CountDownLatch countDownLatch = new CountDownLatch(nbrThreads);
for (int i = 0; i < nbrThreads; i++) {
final MockHTTPClientTask mockHTTPClientTask = new MockHTTPClientTask(host, countDownLatch, i + 1);
new Thread(mockHTTPClientTask).start();
}
try {
countDownLatch.await();
} catch (InterruptedException ex) {
LOGGER.info(ex.getMessage());
}
LOGGER.info("nbr of key pairs generated: " + nbrKeyPairsGenerated.toString());
LOGGER.info("key pair duration milliseconds: " + keyPairGenerationDurationMillis.toString());
final long averageKeyPairGenerationMillis =
keyPairGenerationDurationMillis.get() / nbrKeyPairsGenerated.longValue();
LOGGER.info("average duration per key pair generated: " + averageKeyPairGenerationMillis);
}
/** Provides a task that runs a mock HTTP client for a certain number of iterations. */
class MockHTTPClientTask implements Runnable {
final String host;
final CountDownLatch countDownLatch;
final int threadNbr;
int requestNbr = 0;
MockHTTPClientTask(
final String host,
final CountDownLatch countDownLatch,
final int threadNbr) {
this.host = host;
this.countDownLatch = countDownLatch;
this.threadNbr = threadNbr;
}
@Override
public void run() {
Thread.currentThread().setName("thread " + threadNbr);
LOGGER.info("**** starting " + Thread.currentThread().getName());
//for (int i = 0; i < 1; i++) {
for (int i = 0; i < 20; i++) {
mockHTTPClient();
LOGGER.debug(Thread.currentThread().getName() + " generated certificate " + String.valueOf(i + 1));
}
LOGGER.info("**** finishing " + Thread.currentThread().getName());
countDownLatch.countDown();
}
/** Tests the HTTP request and response messages. */
@SuppressWarnings({"ThrowableResultIgnored", "null"})
private void mockHTTPClient() {
final ClientBootstrap clientBootstrap = new ClientBootstrap(new NioClientSocketChannelFactory(
Executors.newCachedThreadPool(),
Executors.newCachedThreadPool()));
// configure the client pipeline
final Object clientResume_lock = new Object();
final AbstractHTTPResponseHandler httpResponseHandler = new MockHTTPResponseHandler(clientResume_lock);
final X509SecurityInfo x509SecurityInfo = KeyStoreTestUtils.getClientX509SecurityInfo();
try {
LOGGER.debug("trusted certificate: " + x509SecurityInfo.getTrustStore().getCertificate(X509Utils.TRUSTSTORE_ENTRY_ALIAS).toString());
} catch (KeyStoreException ex) {
ex.printStackTrace();
fail(ex.getMessage());
}
final ChannelPipeline channelPipeline = HTTPClientPipelineFactory.getPipeline(
httpResponseHandler,
x509SecurityInfo);
clientBootstrap.setPipeline(channelPipeline);
LOGGER.debug("client pipeline: " + channelPipeline.toString());
// start the connection attempt
SocketAddress socketAddress = new InetSocketAddress(host, SERVER_PORT);
ChannelFuture channelFuture = clientBootstrap.connect(socketAddress);
// wait until the connection attempt succeeds or fails
final Channel channel = channelFuture.awaitUninterruptibly().getChannel();
if (!channelFuture.isSuccess()) {
channelFuture.getCause().printStackTrace();
fail(channelFuture.getCause().getMessage());
}
LOGGER.debug(Thread.currentThread().getName() + " connected to " + socketAddress);
URI uri = null;
HttpRequest httpRequest;
// send the certificate request
try {
uri = new URI("https://" + host + ":" + SERVER_PORT + "/CA/certificate-request");
} catch (URISyntaxException ex) {
fail(ex.getMessage());
}
httpRequest = new DefaultHttpRequest(
HttpVersion.HTTP_1_1,
HttpMethod.POST,
uri.toASCIIString());
httpRequest.setHeader(HttpHeaders.Names.HOST, host);
httpRequest.setHeader(HttpHeaders.Names.CONTENT_TYPE, "application/octet-stream");
httpRequest.setHeader(HttpHeaders.Names.CONTENT_TRANSFER_ENCODING, HttpHeaders.Values.BINARY);
httpRequest.setHeader(HttpHeaders.Names.USER_AGENT, Thread.currentThread().getName());
final long startTimeMillis = System.currentTimeMillis();
KeyPair clientKeyPair = null;
try {
clientKeyPair = X509Utils.generateRSAKeyPair2048();
} catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidAlgorithmParameterException ex) {
fail(ex.getMessage());
}
assertNotNull(clientKeyPair);
nbrKeyPairsGenerated.getAndIncrement();
keyPairGenerationDurationMillis.addAndGet(System.currentTimeMillis() - startTimeMillis);
final byte[] serializedClientPublicKey = ByteUtils.serialize(clientKeyPair.getPublic());
final char[] base64SerializedClientPublicKey = Base64Coder.encode(serializedClientPublicKey);
LOGGER.debug("base64SerializedClientPublicKey length: " + base64SerializedClientPublicKey.length);
final String base64SerializedClientPublicKeyString = new String(base64SerializedClientPublicKey);
final ChannelBuffer channelBuffer = ChannelBuffers.copiedBuffer(base64SerializedClientPublicKeyString.getBytes());
LOGGER.debug("content: " + new String(channelBuffer.array()));
LOGGER.debug("content length: " + channelBuffer.array().length);
httpRequest.setContent(channelBuffer);
httpRequest.setHeader(HttpHeaders.Names.CONTENT_LENGTH, String.valueOf(channelBuffer.array().length));
channel.write(httpRequest);
// wait for the request message to be sent
channelFuture.awaitUninterruptibly();
if (!channelFuture.isSuccess()) {
channelFuture.getCause().printStackTrace();
fail(channelFuture.getCause().getMessage());
}
// the message response handler will signal this thread when the test exchange is completed
LOGGER.debug(Thread.currentThread().getName() + " client waiting for server to process the request");
synchronized (clientResume_lock) {
try {
clientResume_lock.wait();
} catch (InterruptedException ex) {
}
}
LOGGER.debug("client releasing HTTP resources");
channel.close();
clientBootstrap.releaseExternalResources();
}
}
}