Netty client does not send client certificate during SSL handshake that requires mutual authentication - ssl

I'm new to Netty and I try to write an echo server and client that uses mutual authentication. Unfortunately, it's not working, the client doesn't send its client certificate and the server disconnects as expected. Below an overview of what I've done so far and the client side code - that probably contains some bug or I missed something important. Thanks for going through all this!
That is what I have:
Netty version 4.1.0.CR1
Valid keystores, truststores and CRL for download on server
A complete implementation of echo server and client using JSSE directly (that is working as expected)
A working implementation of the echo server using Netty (it's working fine when used with the JSSE based client)
A client based on Netty that does not send a client certificate
Client code:
The channel handler:
package info.junius.tutorial.echo.netty.tls;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;
public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf>
{
#Override
public void channelRead0(ChannelHandlerContext ctx, ByteBuf in)
{
System.out.println("CLIENT: Received echo from server:\n" + in.toString(CharsetUtil.UTF_8));
}
#Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
{
cause.printStackTrace();
ctx.close();
}
}
The channel initialiser:
package info.junius.tutorial.echo.netty.tls;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.handler.ssl.SslContext;
public class ClientChannelInitializer extends ChannelInitializer<Channel>
{
private final SslContext context;
private final String peerHost;
private final int peerPort;
public ClientChannelInitializer(SslContext context, String peerHost, int peerPort)
{
this.context = context;
this.peerHost = peerHost;
this.peerPort = peerPort;
}
#Override
protected void initChannel(Channel channel) throws Exception
{
// Add SSL handler first to encrypt and decrypt everything.
channel.pipeline().addLast(this.context.newHandler(channel.alloc(), this.peerHost, this.peerPort));
// and then business logic.
channel.pipeline().addLast(new EchoClientHandler());
}
}
The echo client:
package info.junius.tutorial.echo.netty.tls;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
public class EchoClient
{
private final String host;
private final int port;
public EchoClient(String host, int port)
{
super();
this.host = host;
this.port = port;
}
public static void main(String[] args) throws Exception
{
if (args.length != 2)
{
System.err.println("Usage: " + EchoClient.class.getSimpleName() + " <host> <port>");
}
else
{
// Security.addProvider(new BouncyCastleProvider());
String host = args[0];
int port = Integer.parseInt(args[1]);
new EchoClient(host, port).start();
}
}
public void start() throws Exception
{
TlsContextUtil tlsContextUtil = new TlsContextUtil();
ChannelInitializer<Channel> channelInitializer = new ClientChannelInitializer(tlsContextUtil.getClientContext(), this.host, this.port);
EventLoopGroup group = new NioEventLoopGroup();
try
{
Bootstrap b = new Bootstrap();
b.group(group).channel(NioSocketChannel.class).handler(channelInitializer);
Channel channel = b.connect(this.host, this.port).sync().channel();
ChannelFuture writeFuture = channel.writeAndFlush("Hello from netty client!\n");
// channel.closeFuture().sync();
writeFuture.sync();
}
finally
{
group.shutdownGracefully().sync();
}
}
}
And a utility class that returns an SslContext:
...
public SslContext getClientContext() throws IOException
{
SslContext sslContext = null;
try
{
// truststore
TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX", "SunJSSE");
tmf.init(this.getKeystore(TRUSTSTORE));
// keystore holding client certificate
KeyManagerFactory kmf = KeyManagerFactory.getInstance("PKIX", "SunJSSE");
kmf.init(this.getKeystore(CLIENT_KEYSTORE), KEYSTORE_PW);
SslContextBuilder builder = SslContextBuilder.forClient().keyManager(kmf).trustManager(tmf).ciphers(PFS_CIPHERS);
// build context
sslContext = builder.build();
}
catch (NoSuchAlgorithmException
| NoSuchProviderException
| KeyStoreException
| IllegalStateException
| UnrecoverableKeyException e)
{
throw new IOException("Unable to create client TLS context", e);
}
return sslContext;
}
...
VM arguments:
-Djavax.net.debug=all -Djava.security.debug="certpath crl" -Dcom.sun.net.ssl.checkRevocation=true -Dcom.sun.security.enableCRLDP=true
I'm quite confident that my mistake must be in the Netty client code, because the system works fine when using JSSE only. Any help is highly appreciated!
Cheers,
Andy

OK, I've got it to work. It was actually my client code that was wrong (the code was based on the secure chat example that comes with Netty). So I changed it to the version used in the echo example:
EchoClientHandler:
#Override
public void channelActive(ChannelHandlerContext ctx)
{
// When notified that the channel is active send a message.
System.out.println("CLIENT: Sending request to server...");
ctx.writeAndFlush(Unpooled.copiedBuffer("Mein Schnitzel ist kaputt!\n", CharsetUtil.UTF_8));
}
and the EchoClient:
try
{
Bootstrap b = new Bootstrap();
b.group(group).channel(NioSocketChannel.class).handler(channelInitializer);
ChannelFuture f = b.connect(this.host, this.port).sync();
f.channel().closeFuture().sync();
}
finally
{
group.shutdownGracefully().sync();
}
The previous code just disconnected too early, so that the handshake never completed.

Related

Intercept SSL/TLS requests in HTTPS Grizzly server

I have set up an HTTPS server using grizzly 2.3.30 and jersey 2.25.1, which can be found here.
The server works well and I can curl to it with certificate-authority, certificate and key:
curl -v --cacert $CERTS/myCA.pem --key $CERTS/grizzly.key --cert $CERTS/grizzly.crt https://localhost:9999/hello
I want to intercept TLS/SSL requests, so I can log which ones fail like for example:
curl -v https://localhost:9999/hello
I am using Grizzly Http Server Framework with Jersey in this fashion:
public class MyGrizzlyServer {
public static void main(String[] args) throws Exception {
System.out.println("Hello main!");
String uriStr = "https://0.0.0.0:9999/";
URI uri = URI.create(uriStr);
final ResourceConfig rc = new ResourceConfig().packages("org");
HttpServer server = GrizzlyHttpServerFactory.createHttpServer(uri, rc, false);
SSLEngineConfigurator engineConfig = getSslEngineConfig();
for (NetworkListener listener : server.getListeners()) {
listener.setSecure(true);
listener.setSSLEngineConfig(engineConfig);
}
HttpHandler handler = server.getHttpHandler();
System.out.println("Http server start...");
server.start();
System.out.println("Hit enter to stop it...");
System.in.read();
server.shutdownNow();
}
private static SSLEngineConfigurator getSslEngineConfig() {
SSLContextConfigurator sslConfigurator = new SSLContextConfigurator();
sslConfigurator.setKeyStoreFile("./mycerts/grizzly.jks");
sslConfigurator.setKeyStorePass("awesome");
sslConfigurator.setTrustStoreFile("./mycerts/myCA.jks");
sslConfigurator.setTrustStorePass("mycapass");
sslConfigurator.setSecurityProtocol("TLS");
SSLContext context = sslConfigurator.createSSLContext(true);
SSLEngineConfigurator sslEngineConfigurator = new SSLEngineConfigurator(context);
sslEngineConfigurator.setNeedClientAuth(true);
sslEngineConfigurator.setClientMode(false);
return sslEngineConfigurator;
}
}
I have been reading Grizzly documentation to get familiarized with its internals.
Grizzly seems to pile filter chains for transport, ssl, http, etc.
I am experimenting with this, but haven't figured out how to achieve it yet.
Any hint will be appreciated.
After playing a bit with filter chains, I was able to remove default SSLBaseFilter and add a custom SSL Filter inherited from SSLBaseFilter.
That way I could captured exceptions thrown by failed TLS/SSL requests.
In MyGrizzlyServer server:
server.start();
NetworkListener listener = server.getListener("grizzly");
FilterChain filterChain = listener.getFilterChain();
int sslBaseFilterIndex = filterChain.indexOfType(SSLBaseFilter.class);
filterChain.remove(sslBaseFilterIndex);
MySslFilter sslFilter = new MySslFilter(sslEngineConfig);
filterChain.add(sslBaseFilterIndex, sslFilter);
With custom SSL filter:
public class MySslFilter extends SSLBaseFilter {
MySslFilter(SSLEngineConfigurator configurator) {
super(configurator);
}
#Override
public NextAction handleRead(FilterChainContext ctx) throws IOException {
NextAction nextAction = null;
try {
System.out.println(" *** MySslFilter handleRead ***" );
nextAction = super.handleRead(ctx);
} catch (IOException e) {
System.out.println(" *** MySslFilter Exception ***" );
e.printStackTrace();
}
return nextAction;
}
}

RabbitMQ MQTT Adapter and Paho MQTT client

I’m using RabbitMQ MQTT Adapter and Paho MQTT client.
RabbitMQ version: {rabbitmq_mqtt,"RabbitMQ MQTT Adapter","3.2.1"}
Paho MQTT client version:
<dependency>
<groupId>org.eclipse.paho</groupId>
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
<version>RELEASE</version>
</dependency>
Please see code inline.
I’m trying to understand if, the subscriber queue can be durable without expiration time. And If the messages can be durable also.
As I understood from RabbitMQ documentation, each time a subscriber subscribes to a topic
RabbitMQ will create a queue with this naming convention:
mqtt-subscription-<ClientName>qos<ClientQOS>
This queue has an expiration time, how can I create a queue without an expiration time? Can I change this queue expiration time to infinite?
As for now each time I run this command: “service rabbitmq-server restart”
The messages in the queue get deleted.
How can I prevent this? Is there a way I can keep the messages in the queue after restart?
In RabbitMQ management UI, I can see under “Publish message” -> “Delivery mode:” which can be “2-persistent”.
If I use management UI to publish messages with Delivery mode = 2-persistent. The messages will be in the queue after service restart.
How can I achieve the same using Paho MQTT Client?
// Heavily based on RabbitMQ MQTT adapter test case code!
// first, import the RabbitMQ Java client
// and the Paho MQTT client classes, plus any other
// requirements
import com.rabbitmq.client.*;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.MqttTopic;
import org.eclipse.paho.client.mqttv3.internal.NetworkModule;
import org.eclipse.paho.client.mqttv3.internal.TCPNetworkModule;
// import org.eclipse.paho.client.mqttv3.internal.trace.Trace;
import org.eclipse.paho.client.mqttv3.internal.wire.MqttOutputStream;
import org.eclipse.paho.client.mqttv3.internal.wire.MqttPublish;
import javax.net.SocketFactory;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.util.*;
/***
* MQTT v3.1 tests
* TODO: synchronise access to variables
*/
public class MqttTestClean implements MqttCallback {
// setup some variables which define where the MQTT broker is
private final String host = "0.0.0.0";
private final int port = 1883;
private final String brokerUrl = "tcp://" + host + ":" + port;
private String clientId;
private String clientId3;
private MqttClient client;
private MqttClient client3;
private MqttConnectOptions conOpt;
private ArrayList<MqttMessage> receivedMessages;
// specify a message payload - doesn't matter what this says, but since MQTT expects a byte array
// we convert it from string to byte array here
private final byte[] payload = "This payload was published on MQTT and read using AMQP.".getBytes();
// specify the topic to be used
private final String topic = "topic/proxy/1.0.0/Report/*";
private int testDelay = 2000;
private long lastReceipt;
private boolean expectConnectionFailure;
private ConnectionFactory connectionFactory;
private Connection conn;
private Channel ch;
// override 10s limit
private class MyConnOpts extends MqttConnectOptions {
private int keepAliveInterval = 60;
#Override
public void setKeepAliveInterval(int keepAliveInterval) {
this.keepAliveInterval = keepAliveInterval;
}
#Override
public int getKeepAliveInterval() {
return keepAliveInterval;
}
}
public void setUpMqtt() throws MqttException {
clientId = getClass().getSimpleName() + ((int) (10000*Math.random()));
client = new MqttClient(brokerUrl, clientId);
conOpt = new MyConnOpts();
setConOpts(conOpt);
receivedMessages = new ArrayList<MqttMessage>();
expectConnectionFailure = false;
}
public void tearDownMqtt() throws MqttException {
try {
client.disconnect();
} catch (Exception _) {}
}
private void setUpAmqp() throws Exception {
connectionFactory = new ConnectionFactory();
connectionFactory.setHost(host);
conn = connectionFactory.newConnection();
ch = conn.createChannel();
}
private void tearDownAmqp() throws IOException {
conn.close();
}
private void setConOpts(MqttConnectOptions conOpts) {
conOpts.setCleanSession(true);
conOpts.setKeepAliveInterval(60);
}
private void publish(MqttClient client, String topicName, int qos, byte[] payload) throws MqttException {
MqttTopic topic = client.getTopic(topicName);
MqttMessage message = new MqttMessage(payload);
message.setQos(qos);
MqttDeliveryToken token = topic.publish(message);
token.waitForCompletion();
}
public void connectionLost(Throwable cause) {
if (!expectConnectionFailure)
System.out.println("Connection unexpectedly lost");
}
public void messageArrived(String topic, MqttMessage message) throws Exception {
lastReceipt = System.currentTimeMillis();
System.out.println("-------------------------------------------------");
System.out.println("------------------" + lastReceipt + "-------------------------------");
System.out.println("------------------" + message.toString() + "-------------------------------");
receivedMessages.add(message);
}
public void deliveryComplete(IMqttDeliveryToken token) {
}
public void run() {
try {
setUpMqtt(); // initialise the MQTT connection
setUpAmqp(); // initialise the AMQP connection
connect();
//String queue = ch.queueDeclare().getQueue();
// String queue = ch.queueDeclare("mqtt-subscription-Snabel-3qos1", true, false, false, null).getQueue();
//ch.queueBind(queue, "amq.topic", "sci-topic.sc.proxy_1393.1.0.0.ApReport.*"/*topic*/);
client.connect(conOpt);
publish(client, "topic/proxy/1.0.0/Report/123456789",1, payload); // publish the MQTT message
client.disconnect();
Thread.sleep(testDelay);
tearDownAmqp(); // cleanup AMQP resources
tearDownMqtt(); // cleanup MQTT resources*/
disConnect();
} catch (Exception mqe) {
mqe.printStackTrace();
}
}
private void connect() throws Exception {
clientId3 = "Test-3";
client3 = new MqttClient(brokerUrl, clientId3);
MqttConnectOptions connOpts = new MqttConnectOptions();
connOpts.setCleanSession(false);
client3.connect(connOpts);
client3.setCallback(this);
client3.subscribe(topic);
if(!client3.isConnected()){
System.out.println("Not Connected");
return;
}
System.out.println("Connected");
}
private void disConnect() throws Exception {
try {
client3.disconnect();
} catch (Exception _) {}
}
public static void main(String[] args) {
MqttTest mqt = new MqttTest();
mqt.run();
}
}
This was a RabbitMQ bug:
http://rabbitmq.1065348.n5.nabble.com/MQTT-plugin-message-delivery-mode-td32925.html
It was fixed in:
http://www.rabbitmq.com/release-notes/README-3.2.4.txt

Safari Push Notifications

I would like to implement push notifications for my website (obviously only in compatible browser as Safari 7).
I have read the Apple documentation and I have successfully created my package containing my icon.iconset, my certificate.p12, manifest.json and a website.json.
Now I would like to ask the permission to the user when I first visit the website. If he allows it, I should send the package.
Everything is pretty clear but I don't know how to go on.
How do I create my push package out of my files? How do I precisely sign it? The package should be always the same so I could sign it on my mac and upload to my server only one package.
If you have experience with this technology, please let me know :)
I have successfully create push package to ask permission on safari web push notifications using REST API in java. Also I have follow steps which provide by Apple officials on there sites.
Please follow below steps to create push package.
Create your web push notification P12 certificate from your apple account.
Create your REST API using https for pushPackage which contain icon.iconset, your certificate.p12, manifest.json and a website.json. p12 certificate must be web push notification certificate.
How to create push package:- Please refer below java code. I have create push package using java servlet. which provide 2 web service end points.
v1/pushpackage/webpushID
v1/log
Servlet which process your push package request which send by safari push notification method
SafariPushPakageAPI.java
/*
Push Package REST API handler
*/
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import com.safari.Packager;
public class SafariPushPakageAPI extends HttpServlet{
/**
*
*/
private static final long serialVersionUID = 1L;
public static String ServerPath = null;
private static final String REQUEST_PERMISSION = "/v1/pushPackages/YOUR_WEB_PUSH_ID";
private static final String REQUEST_ERRORLOG = "/v1/log";
#Override
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doRequest(request, response);
}
// /v1/pushPackages/webpushID
// /v1/log
#Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doRequest(request, response);
}
private void doRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("===>> SAFARI PUSH NOTIFICATION REQUEST");
String path = request.getPathInfo();
System.out.println("PATH ===>> "+path);
if(path == null){
doRequestPermission(request, response);
}else if (path.equalsIgnoreCase(REQUEST_PERMISSION)){
doRequestPermission(request, response);
}else if (path.equalsIgnoreCase(REQUEST_ERRORLOG)){
doRequestShowErrorLog(request, response);
}else{
doRequestPermission(request, response);
}
}
private void doRequestPermission(HttpServletRequest request,HttpServletResponse response) {
try{
System.out.println("INSIDE REQUEST PERMISSION ==>>>");
System.out.println(IOUtils.toString(request.getReader()));
String authToken = StringUtils.isBlank(request.getParameter("token")) ? "UserTokenRT124DFGH" : StringUtils.trimToEmpty(request.getParameter("token"));
System.out.println("=>>>>>>>>>> USER TOKEN =>>>>>>>>>> "+authToken);
#SuppressWarnings("deprecation")
String packagePath =request.getRealPath("pushPackage.raw/icon.iconset/"); // LOCATION WHERE YOUR PUSH PACKAGE FOLDER CONTAIN LOGOS AND website.json file
response.setContentType("application/zip");
response.setHeader("Content-Disposition", "attachment;filename=\"pushpackage.zip\"");
OutputStream out = response.getOutputStream();
out.write(Packager.createPackageFile(authToken,packagePath));
response.flushBuffer();
}catch(IOException ioe){
ioe.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
private void doRequestShowErrorLog(HttpServletRequest request,HttpServletResponse response) {
try{
System.out.println("ERROR LOG STARTED");
System.out.println(IOUtils.toString(request.getReader()));
System.out.println("END");
}catch(Exception e){
e.printStackTrace();
}
}
}
Packager.java
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import org.apache.commons.io.IOUtils;
import org.json.JSONArray;
import org.json.JSONObject;
/**
*
* #author Ritesh
*/
public class Packager {
final static String CERTIFICATE_PATH="PATH TO YOUR 12 CERTIFICATE";
final static String CERTIFICATE_PASS="PASSWORD";
static String getJSON(String authenticationToken) throws Exception {
JSONObject obj = new JSONObject();
obj.put("websiteName", "WEB SITE NAME");
obj.put("websitePushID", "WEB PUSH ID");
obj.put("allowedDomains", new JSONArray());
obj.getJSONArray("allowedDomains").put("https://TEST.EXAMPLE.net");//LIST OF DOMAINS ALLOW
obj.put("urlFormatString", "https://TEST.EXAMPLE.net/%#");
obj.put("authenticationToken", authenticationToken);
obj.put("webServiceURL", "https://API.EXAMPLE.COM");//callback URL WITHOUT WEB SERVICE ENDPOINT NAME
return obj.toString();
}
public static byte[] createPackageFile(String authenticationToken, String path) throws Exception {
System.out.println("packaging safari file with token: " + authenticationToken);
ZipHandler zip = new ZipHandler();
File dir = new File(path);
for (File file : dir.listFiles()) {
InputStream is = new FileInputStream(file);
byte[] bytes = IOUtils.toByteArray(is);
zip.addFile("icon.iconset", file.getName(),bytes );
}
zip.addFile("", "website.json", getJSON(authenticationToken).getBytes());
byte[] manifest = zip.manifest();
zip.addFile("", "manifest.json", manifest);
zip.addFile("", "signature", sign(manifest));
return zip.getBytes();
}
static byte[] sign(byte bytesToSign[]) throws Exception {
return new PKCS7Signer().sign(CERTIFICATE_PATH,CERTIFICATE_PASS, bytesToSign);
}
/**
* Servlet handler , should listen on the callback URL (as in webServiceURL)
* #param requestPath
* #param req
* #param servletRequest
* #param servletResponse
* #throws Exception
*/
public static void main(String[] args) throws Exception {
Packager.createPackageFile("SafriNotifcation","");
}
}
PKCS7Signer.java which create your signature file.
import java.io.FileInputStream;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Security;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaCertStore;
import org.bouncycastle.cms.CMSProcessableByteArray;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.CMSSignedDataGenerator;
import org.bouncycastle.cms.CMSTypedData;
import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
import org.bouncycastle.util.Store;
public final class PKCS7Signer {
static {
try{
Security.addProvider(new BouncyCastleProvider());
}catch(Exception e){
e.printStackTrace();
}
}
private KeyStore getKeystore(String storeLocation, String storePasswd) throws Exception {
if (storeLocation == null) {
System.out.println("Could not find store file (.p12)");
return null;
}
// First load the keystore object by providing the p12 file path
KeyStore clientStore = KeyStore.getInstance("PKCS12");
// replace testPass with the p12 password/pin
clientStore.load(new FileInputStream(storeLocation), storePasswd.toCharArray());
return clientStore;
}
private X509CertificateHolder getCert(KeyStore keystore, String alias) throws Exception {
java.security.cert.Certificate c = keystore.getCertificate(alias);
return new X509CertificateHolder(c.getEncoded());
}
private PrivateKey getPrivateKey(KeyStore keystore, String alias, String storePasswd) throws Exception {
return (PrivateKey) keystore.getKey(alias, storePasswd.toCharArray());
}
public byte[] sign(String storeLocation, String storePasswd, byte[] dataToSign) throws Exception {
KeyStore clientStore = getKeystore(storeLocation, storePasswd);
if (clientStore == null) {
return null;
}
Enumeration aliases = clientStore.aliases();
String alias = "";
while (aliases.hasMoreElements()) {
alias = (String) aliases.nextElement();
if (clientStore.isKeyEntry(alias)) {
break;
}
}
CMSTypedData msg = new CMSProcessableByteArray(dataToSign); // Data to sign
X509CertificateHolder x509Certificate = getCert(clientStore, alias);
List certList = new ArrayList();
certList.add(x509Certificate); // Adding the X509 Certificate
Store certs = new JcaCertStore(certList);
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
// Initializing the the BC's Signer
ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(
getPrivateKey(clientStore, alias, storePasswd));
gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder()
.setProvider("BC").build()).build(sha1Signer, x509Certificate));
// adding the certificate
gen.addCertificates(certs);
// Getting the signed data
CMSSignedData sigData = gen.generate(msg, false);
return sigData.getEncoded();
}
}
please note used latest bouncy castle jars for create signature file
bcprov-jdk15on-157.jar
bcpkix-jdk15on-157.jar
Write your javascript on your client side page.it contains simple java script to get permissions.
///
// For safari
var domain="YOUR WEB PUSH ID";
function safariIniti() {
var pResult = window.safari.pushNotification.permission(domain);
if(pResult.permission === 'default') {
//request permission
requestPermissions();
} else if (pResult.permission === 'granted') {
console.log("Permission for " + domain + " is " + pResult.permission);
var token = pResult.deviceToken;
// Show subscription for debug
console.log('Subscription details:'+token);
} else if(pResult.permission === 'denied') {
console.log("Permission for " + domain + " is " + pResult.permission);
}
}
function getToken(){
// always start with a letter (for DOM friendlyness)
var idstr=String.fromCharCode(Math.floor((Math.random()*25)+65));
do {
// between numbers and characters (48 is 0 and 90 is Z (42-48 = 90)
var ascicode=Math.floor((Math.random()*42)+48);
if (ascicode<58 || ascicode>64){
// exclude all chars between : (58) and # (64)
idstr+=String.fromCharCode(ascicode);
}
} while (idstr.length<32);
return (idstr);
}
function requestPermissions() {
var tokenVal = getToken();
window.safari.pushNotification.requestPermission('WEb service url without end points',domain,{token:tokenVal},
function(subscription) {
console.log(subscription.permission);
console.log("PERMISSION ====>> "+subscription.permission);
if(subscription.permission === 'granted') {
//TODO
}
else if(subscription.permission === 'denied') {
// TODO:
}
});
}
Apple provides a php file that you can use to create your push package including the signature. https://developer.apple.com/library/mac/documentation/NetworkingInternet/Conceptual/NotificationProgrammingGuideForWebsites/CompanionFile.zip
Alternatively, you can use the push_package gem, https://github.com/SymmetricInfinity/push_package, which we developed when implementing safari push notifications for zeropush.com. More details are available at https://zeropush.com/blog/implementing-safari-push-notifications-in-osx-mavericks.
Follow this apple documentation and github repo, they include sufficient information required to create a safari push notifications.

SSL connection getting closed

I have solved the problem. You need to create an instance of SSLEngine and add it to the pipeline of handlers for each clinent request. I have done this by adding the handler in the channelConnected event and removing the ssl handler in the channel disconnected. This make sure for each channel connected it will be added new.
Below is the code of the handler. Is this the right approach for doing persistent socket connection with SSL support?
package server;
import static org.jboss.netty.buffer.ChannelBuffers.dynamicBuffer;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
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.ChannelFutureListener;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;
import org.jboss.netty.channel.group.ChannelGroup;
import org.jboss.netty.channel.group.DefaultChannelGroup;
import org.jboss.netty.handler.ssl.SslHandler;
import ssl.SslContextFactory;
import ssl.SslKeyStore;
public class ServerHandler extends SimpleChannelHandler {
private static final String ECHORES = "0057081082200000000000000400000000000000070612201399966400301";
#Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
System.out.println("Inside ServerHandler.messageReceived");
ChannelBuffer buffer = (ChannelBuffer) e.getMessage();
ChannelBuffer temp = dynamicBuffer();
temp.writeBytes(buffer);
if (temp.readableBytes() >= 4) {
byte messageLen[] = new byte[4];
temp.readBytes(messageLen);
int len = Integer.parseInt(new String(messageLen));
System.out.println("Length of the message is : " + len);
if (temp.readableBytes() >= len) {
byte[] message = new byte[len];
temp.readBytes(message);
System.out.println("Input message is : " + new String(message));
Channel channel = e.getChannel();
buffer = ChannelBuffers.copiedBuffer(ECHORES.getBytes());
ChannelFuture future = channel.write(buffer);
future.addListener(ChannelFutureListener.CLOSE);
}
}
}
#Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
e.getCause().printStackTrace();
Channel channel = e.getChannel();
channel.close();
}
#Override
public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
String file = "test.jks";
SSLContext sslCtx = SslContextFactory.getServerContext(new SslKeyStore(file));
final SSLEngine sslEngine =sslCtx.createSSLEngine();
sslEngine.setNeedClientAuth(false);
sslEngine.setUseClientMode(false);
final SslHandler sslHandler = new SslHandler(sslEngine);
ctx.getPipeline().addFirst("ssl", sslHandler);
ChannelFuture handshakeFuture = sslHandler.handshake();
handshakeFuture.addListener(new ChannelFutureListener() {
#Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
System.out.println("SSL/TLS session established");
System.out.println("Your session is protected by "+ sslHandler.getEngine().
getSession().getCipherSuite() + " cipher suite.\n");
} else {
future.getChannel().close();
}
}
});
}
#Override
public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
System.out.println("Inside ServerHandler.channelDisconnected");
ctx.getPipeline().remove("ssl");
}
}
I am getting the following exception while using netty with ssl. My first transaction and handshake goes fine. If I send a new message to teh server again I am getting this exception.
"javax.net.ssl.SSLException: SSLEngine is closing/closed"
What could be going wrong here. How to keep the esatablished TLS/SSL session? This error happens at org.jboss.netty.handler.ssl.SslHandler.handshake(SslHandler.java:358).
Intention is to keep the server running with a persistent TLS socket connection , so that clients can send messages.
-TK

SSL Server in Java - javax.net.ssl.SSLException

I am trying to create a server using SSL but I keep getting the following error:
"Server aborted:javax.net.ssl.SSLException: No available certificate or key corresponds to the SSL cipher suites which are enabled."
I am not sure if I am creating the certificates correctly. Here is my code.
I am converting an old TCP Server to an SSL Server
// SSL Server
import java.net.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.net.ServerSocketFactory;
import javax.net.ssl.SSLServerSocketFactory;
public class SSL_Server {
public static void main(String[] args) {
int port = 2018;
ServerSocketFactory ssocketFactory = SSLServerSocketFactory.getDefault();
ServerSocket ssocket = null;
System.out.println("SSL_Server started");
System.setProperty("javax.net.ssl.keyStore","mySrvKeystore");
System.setProperty("javax.net.ssl.keyStorePassword","123456");
final ExecutorService threadPool = Executors.newCachedThreadPool();
try {
ssocket = ssocketFactory.createServerSocket(port);
InetAddress myIP =InetAddress.getLocalHost();
System.out.println(myIP.getHostAddress());
while(true){
Socket aClient = ssocket.accept();
//create a new thread for every client
threadPool.submit(new SSL_ClientHandler(aClient));
}
}
catch(Exception e) {
System.err.println("Server aborted:" + e);
} finally {
try{
ssocket.close();
} catch (Exception e){
System.err.println("could not close connection properly" + e);
}
}
System.out.println("connection was closed successfully");
}
}
System.setProperty("javax.net.ssl. keyStore","mySrvKeystore"); System.setProperty("javax.net.ssl. keyStorePassword","123456");
ServerSocketFactory ssocketFactory = SSLServerSocketFactory.getDefault();
ServerSocket ssocket = null;
System.out.println("SSL_Server started");
You should set the properties that configure the default SSLContext (and thus the default SSLServerSocketFactory) before getting it, since it will configure it then.
System.setProperty("javax.net.ssl.keyStore","mySrvKeystore");
System.setProperty("javax.net.ssl.keyStorePassword","123456");
ServerSocketFactory ssocketFactory = SSLServerSocketFactory.getDefault();
ServerSocket ssocket = null;
System.out.println("SSL_Server started");