Skip to content

Commit

Permalink
Add NonBlockingSocketFactory
Browse files Browse the repository at this point in the history
The NonBlockingSocketFactory has been added to provide a
non-blocking socket factory for PKIConnection. Eventually
it will replace the DefaultSocketFactory once the support
for OCSP and CRL has been added into JSSTrustManager.

The test for HTTPS connector with NSS has been updated to
use the non-blocking socket factory and validate the new
error messages generated by JSSTrustManager. The test for
HTTPS connector with PKCS #12 file will continue to use
the blocking socket factory to prevent regressions.
  • Loading branch information
edewata committed Aug 23, 2024
1 parent b4097be commit 2117466
Show file tree
Hide file tree
Showing 3 changed files with 231 additions and 13 deletions.
52 changes: 39 additions & 13 deletions .github/workflows/server-https-nss-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,10 @@ jobs:
- name: Check PKI CLI with unknown issuer
run: |
# run PKI CLI but don't trust the cert
echo n | docker exec -i client pki -U https://pki.example.com:8443 info \
echo n | docker exec -i client pki \
-D org.dogtagpki.client.socketFactory=org.dogtagpki.client.NonBlockingSocketFactory \
-U https://pki.example.com:8443 \
info \
> >(tee stdout) 2> >(tee stderr >&2) || true
# check stdout
Expand All @@ -179,10 +182,13 @@ jobs:
# check stderr
cat > expected << EOF
WARNING: UNKNOWN_ISSUER encountered on 'CN=pki.example.com' indicates an unknown CA cert 'CN=CA Signing Certificate'
Trust this certificate (y/N)? SEVERE: FATAL: SSL alert sent: BAD_CERTIFICATE
IOException: Unable to write to socket: Failed to write to socket: (-5987) Invalid function argument.
Trust this certificate (y/N)? IOException: Unable to write to socket: Unable to validate CN=pki.example.com: Unknown issuer: CN=CA Signing Certificate
EOF
# TODO: Update the expected stderr once the missing SSL alert is fixed
# Trust this certificate (y/N)? SEVERE: FATAL: SSL alert sent: UNKNOWN_CA
# IOException: Unable to write to socket: Unable to validate CN=pki.example.com: Unknown issuer: CN=CA Signing Certificate
diff expected stderr
# the cert should not be stored
Expand All @@ -193,7 +199,10 @@ jobs:
- name: Check PKI CLI with unknown issuer with wrong hostname
run: |
# run PKI CLI with wrong hostname
echo n | docker exec -i client pki -U https://server.example.com:8443 info \
echo n | docker exec -i client pki \
-D org.dogtagpki.client.socketFactory=org.dogtagpki.client.NonBlockingSocketFactory \
-U https://server.example.com:8443 \
info \
> >(tee stdout) 2> >(tee stderr >&2) || true
# check stdout
Expand All @@ -205,18 +214,24 @@ jobs:
# check stderr
cat > expected << EOF
WARNING: UNKNOWN_ISSUER encountered on 'CN=pki.example.com' indicates an unknown CA cert 'CN=CA Signing Certificate'
WARNING: BAD_CERT_DOMAIN encountered on 'CN=pki.example.com' indicates a common-name mismatch
Trust this certificate (y/N)? SEVERE: FATAL: SSL alert sent: BAD_CERTIFICATE
IOException: Unable to write to socket: Failed to write to socket: (-12276) Unable to communicate securely with peer: requested domain name does not match the server's certificate.
WARNING: UNKNOWN_ISSUER encountered on 'CN=pki.example.com' indicates an unknown CA cert 'CN=CA Signing Certificate'
Trust this certificate (y/N)? IOException: Unable to write to socket: Unable to validate CN=pki.example.com: Bad certificate domain: CN=pki.example.com
EOF
# TODO: Update the expected stderr once the missing SSL alert is fixed
# Trust this certificate (y/N)? SEVERE: FATAL: SSL alert sent: ACCESS_DENIED
# IOException: Unable to write to socket: Unable to validate CN=pki.example.com: Bad certificate domain: CN=pki.example.com
diff expected stderr
- name: Check PKI CLI with newly trusted server cert
run: |
# run PKI CLI and trust the cert
echo y | docker exec -i client pki -U https://pki.example.com:8443 info \
echo y | docker exec -i client pki \
-D org.dogtagpki.client.socketFactory=org.dogtagpki.client.NonBlockingSocketFactory \
-U https://pki.example.com:8443 \
info \
> >(tee stdout) 2> >(tee stderr >&2) || true
# check stdout
Expand Down Expand Up @@ -262,7 +277,10 @@ jobs:
- name: Check PKI CLI with trusted server cert with wrong hostname
run: |
# run PKI CLI with wrong hostname
docker exec client pki -U https://server.example.com:8443 info \
docker exec client pki \
-D org.dogtagpki.client.socketFactory=org.dogtagpki.client.NonBlockingSocketFactory \
-U https://server.example.com:8443 \
info \
> >(tee stdout) 2> >(tee stderr >&2) || true
# check stdout
Expand All @@ -283,7 +301,10 @@ jobs:
- name: Check PKI CLI with already trusted server cert
run: |
# run PKI CLI with correct hostname
docker exec client pki -U https://pki.example.com:8443 info \
docker exec client pki \
-D org.dogtagpki.client.socketFactory=org.dogtagpki.client.NonBlockingSocketFactory \
-U https://pki.example.com:8443 \
info \
> >(tee stdout) 2> >(tee stderr >&2) || true
# check stdout
Expand All @@ -301,7 +322,10 @@ jobs:
run: |
sleep 120
docker exec client pki -U https://pki.example.com:8443 info \
docker exec client pki \
-D org.dogtagpki.client.socketFactory=org.dogtagpki.client.NonBlockingSocketFactory \
-U https://pki.example.com:8443 \
info \
> >(tee stdout) 2> >(tee stderr >&2) || true
# check stdout
Expand All @@ -314,10 +338,12 @@ jobs:
# check stderr
cat > expected << EOF
ERROR: EXPIRED_CERTIFICATE encountered on 'CN=pki.example.com' results in a denied SSL server cert!
SEVERE: FATAL: SSL alert sent: BAD_CERTIFICATE
IOException: Unable to write to socket: Failed to write to socket: (-5987) Invalid function argument.
IOException: Unable to write to socket: Unable to validate CN=pki.example.com: Expired certificate: CN=pki.example.com
EOF
# TODO: Update the expected stderr once the missing SSL alert is fixed
# SEVERE: FATAL: SSL alert sent: CERTIFICATE_EXPIRED
diff expected stderr
- name: Stop PKI server
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@

import com.netscape.certsrv.client.PKIConnection;

/**
* This class provides blocking socket factory for PKIConnection.
*/
public class DefaultSocketFactory implements SchemeLayeredSocketFactory {

PKIConnection connection;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
//
// Copyright Red Hat, Inc.
//
// SPDX-License-Identifier: GPL-2.0-or-later
//
package org.dogtagpki.client;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Arrays;

import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;

import org.apache.http.conn.scheme.SchemeLayeredSocketFactory;
import org.apache.http.params.HttpParams;
import org.mozilla.jss.CryptoManager;
import org.mozilla.jss.provider.javax.crypto.JSSTrustManager;
import org.mozilla.jss.ssl.SSLAlertDescription;
import org.mozilla.jss.ssl.SSLAlertEvent;
import org.mozilla.jss.ssl.SSLAlertLevel;
import org.mozilla.jss.ssl.SSLHandshakeCompletedEvent;
import org.mozilla.jss.ssl.SSLSocketListener;
import org.mozilla.jss.ssl.javax.JSSSocket;

import com.netscape.certsrv.client.PKIConnection;

/**
* This class provides non-blocking socket factory for PKIConnection.
*/
public class NonBlockingSocketFactory implements SchemeLayeredSocketFactory {

public static org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(NonBlockingSocketFactory.class);

PKIConnection connection;

public NonBlockingSocketFactory(PKIConnection connection) {
this.connection = connection;
}

@Override
public Socket createSocket(HttpParams params) throws IOException {
return null;
}

@Override
public Socket connectSocket(Socket socket,
InetSocketAddress remoteAddress,
InetSocketAddress localAddress,
HttpParams params)
throws IOException,
UnknownHostException {

String hostname = null;
int port = 0;
if (remoteAddress != null) {
hostname = remoteAddress.getHostName();
port = remoteAddress.getPort();
}

int localPort = 0;
InetAddress localAddr = null;

if (localAddress != null) {
localPort = localAddress.getPort();
localAddr = localAddress.getAddress();
}

SSLSocketFactory socketFactory;
try {
CryptoManager.getInstance();

KeyManagerFactory kmf = KeyManagerFactory.getInstance("NssX509", "Mozilla-JSS");
KeyManager[] kms = kmf.getKeyManagers();

// Create JSSTrustManager since the default JSSNativeTrustManager
// does not support hostname validation and cert approval callback.
//
// JSSTrustManager currently does not support cert validation with
// OCSP and CRL.
//
// TODO: Fix JSSTrustManager to support OCSP and CRL, then replace
// DefaultSocketFactory with this class.

JSSTrustManager trustManager = new JSSTrustManager();
trustManager.setHostname(hostname);
trustManager.setCallback(connection.getCallback());

TrustManager[] tms = new TrustManager[] { trustManager };

SSLContext ctx = SSLContext.getInstance("TLS", "Mozilla-JSS");
ctx.init(kms, tms, null);

socketFactory = ctx.getSocketFactory();

} catch (Exception e) {
throw new IOException("Unable to create SSL socket factory: " + e.getMessage(), e);
}

JSSSocket jssSocket;
try {
if (socket == null) {
logger.info("Creating new SSL socket");
jssSocket = (JSSSocket) socketFactory.createSocket(
InetAddress.getByName(hostname),
port,
localAddr,
localPort);

} else {
logger.info("Creating SSL socket with existing socket");
jssSocket = (JSSSocket) socketFactory.createSocket(
socket,
hostname,
port,
true);
}

} catch (Exception e) {
throw new IOException("Unable to create SSL socket: " + e.getMessage(), e);
}

jssSocket.setUseClientMode(true);

String certNickname = connection.getConfig().getCertNickname();
if (certNickname != null) {
logger.info("Client certificate: "+certNickname);
jssSocket.setCertFromAlias(certNickname);
}

jssSocket.setListeners(Arrays.asList(new SSLSocketListener() {

@Override
public void alertReceived(SSLAlertEvent event) {

int intLevel = event.getLevel();
SSLAlertLevel level = SSLAlertLevel.valueOf(intLevel);

int intDescription = event.getDescription();
SSLAlertDescription description = SSLAlertDescription.valueOf(intDescription);

if (level == SSLAlertLevel.FATAL || logger.isInfoEnabled()) {
logger.error(level + ": SSL alert received: " + description);
}
}

@Override
public void alertSent(SSLAlertEvent event) {

int intLevel = event.getLevel();
SSLAlertLevel level = SSLAlertLevel.valueOf(intLevel);

int intDescription = event.getDescription();
SSLAlertDescription description = SSLAlertDescription.valueOf(intDescription);

if (level == SSLAlertLevel.FATAL || logger.isInfoEnabled()) {
logger.error(level + ": SSL alert sent: " + description);
}
}

@Override
public void handshakeCompleted(SSLHandshakeCompletedEvent event) {
}
}));

jssSocket.startHandshake();

return jssSocket;
}

@Override
public boolean isSecure(Socket sock) {
// We only use this factory in the case of SSL Connections.
return true;
}

@Override
public Socket createLayeredSocket(Socket socket, String target, int port, HttpParams params)
throws IOException, UnknownHostException {
// This method implementation is required to get SSL working.
return null;
}
}

0 comments on commit 2117466

Please sign in to comment.