Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add NonBlockingSocketFactory #4832

Merged
merged 1 commit into from
Aug 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}
}
Loading