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

PVA: Support EPICS_PVAS_TLS_OPTIONS #2811

Merged
merged 2 commits into from
Sep 7, 2023
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
23 changes: 19 additions & 4 deletions core/pva/TLS.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,17 @@ they are compatible with generic open SSL tools:
openssl pkcs12 -info -in KEYSTORE -nodes
```

The essential commands are also in `make_tls_simple.sh`


Step 3: Configure and run the demo server
-------

Set environment variables to inform the server about its keystore.
Set environment variable `EPICS_PVAS_TLS_KEYCHAIN` to inform the server about its keystore.
The format of this setting is `/path/to/file;password`.
Then run a demo server:
If you used `make_tls_simple.sh`, that would be `demo/KEYSTORE;changeit`.

Then run a demo server:

```
export EPICS_PVAS_TLS_KEYCHAIN="/path/to/KEYSTORE;changeit"
Expand All @@ -78,7 +81,8 @@ java -cp target/classes org.epics.pva.server.ServerDemo
Step 4: Configure and run the demo client
-------

Set environment variables to inform the client about its truststore.
Set environment variable `EPICS_PVA_TLS_KEYCHAIN` to inform the client about its truststore.
If you used `make_tls_simple.sh`, that would be `demo/TRUSTSTORE;changeit`.
Then run a demo client (`-v 5` to see protocol detail):

```
Expand All @@ -87,6 +91,7 @@ java -cp target/classes org.epics.pva.client.PVAClientMain get -v 5 demo
# Or: ./pvaclient get -v 5 demo
```

On both the server and the client note how they mention "TLS" in their log messages.

Logging
-------
Expand Down Expand Up @@ -192,6 +197,7 @@ keytool -list -v -keystore ioc.p12 -storepass changeit
We can now run the server with `EPICS_PVAS_TLS_KEYCHAIN=/path/to/ioc.p12;changeit` and clients with
`EPICS_PVA_TLS_KEYCHAIN=/path/to/trust_ca.p12;changeit`.

See `make_tls_ca.sh` for a copy of the essential commands.
You can create additional server files `ioc1.p12`, `ioc2.p12` and have each IOC use its own key pair.
Clients will trust them without any changes to the `trust_ca.p12`
as long as the IOC certificates are signed by your CA.
Expand Down Expand Up @@ -237,6 +243,12 @@ We can now run the server with `EPICS_PVAS_TLS_KEYCHAIN=/path/to/ioc.p12;changei
`EPICS_PVA_TLS_KEYCHAIN=/path/to/client.p12;changeit`.
The server will identify the client as "Fred F.".

By default, the server supports clients with certificate and x509 authentication,
but client certificates are not required.
By setting `EPICS_PVAS_TLS_OPTIONS="client_cert=require"`, the server will
abort the initial TLS handshake for clients that do not have a certificate.


In total, we now have the following:

* `KEYSTORE`, `TRUSTSTORE`:
Expand Down Expand Up @@ -276,7 +288,10 @@ In total, we now have the following:
providing a client name for x509 authentication.
Clients can set their `EPICS_PVA_TLS_KEYCHAIN` to this file to
communicate with IOCs using x509 authentication.

If the server runs with `EPICS_PVAS_TLS_OPTIONS="client_cert=require"`,
the clients MUST use a client certificate like `client.p12`.
Clients with just `trust_ca.p12` will fail during the initial TLS handshake.

* `myclient.cer`, `myclient.csr`:
Intermediate files used to sign the client certificate.
May be deleted.
Expand Down
10 changes: 7 additions & 3 deletions core/pva/make_tls_ca → core/pva/make_tls_ca.sh
Original file line number Diff line number Diff line change
@@ -1,38 +1,42 @@
# More elaborate example, see TLS.md "Use a Certification Authority"
rm -rf demo
mkdir demo
cd demo

# Create our own CA, and make its public certificate available as `myca.cer`:
keytool -genkeypair -alias myca -keystore ca.p12 -storepass changeit -dname "CN=myca" -keyalg RSA -ext BasicConstraints=ca:true
keytool -list -v -keystore ca.p12 -storepass changeit
keytool -exportcert -alias myca -keystore ca.p12 -storepass changeit -rfc -file myca.cer
keytool -printcert -file myca.cer

# For clients, create a truststore that holds the public certificate of our CA
keytool -importcert -alias myca -keystore trust_ca.p12 -storepass changeit -file myca.cer -noprompt
keytool -list -v -keystore trust_ca.p12 -storepass changeit

# Now create a server keypair for use by the IOC:
keytool -genkeypair -alias myioc -keystore ioc.p12 -storepass changeit -dname "CN=myioc" -keyalg RSA
keytool -list -v -keystore ioc.p12 -storepass changeit

# Create signing request, sign with our CA, import signed cert into ioc.p12
keytool -certreq -alias myioc -keystore ioc.p12 -storepass changeit -file myioc.csr
keytool -gencert -alias myca -keystore ca.p12 -storepass changeit -ext SubjectAlternativeName=DNS:myioc -ext KeyUsage=digitalSignature -ext ExtendedKeyUsage=serverAuth,clientAuth -infile myioc.csr -outfile myioc.cer
keytool -printcert -file myioc.cer

keytool -importcert -alias myca -keystore ioc.p12 -storepass changeit -file myca.cer -noprompt
keytool -importcert -alias myioc -keystore ioc.p12 -storepass changeit -file myioc.cer
keytool -list -v -keystore ioc.p12 -storepass changeit

# Create client keypair so client can authenticate as "Fred F."
keytool -genkeypair -alias myclient -keystore client.p12 -storepass changeit -dname "CN=Fred F." -keyalg RSA
keytool -list -v -keystore client.p12 -storepass changeit

# Sign client certificate with our CA
keytool -certreq -alias myclient -keystore client.p12 -storepass changeit -file myclient.csr
keytool -gencert -alias myca -keystore ca.p12 -storepass changeit -ext SubjectAlternativeName=DNS:client -ext KeyUsage=digitalSignature -ext ExtendedKeyUsage=serverAuth,clientAuth -infile myclient.csr -outfile myclient.cer
keytool -printcert -file myclient.cer

keytool -importcert -alias myca -keystore client.p12 -storepass changeit -file myca.cer -noprompt
keytool -importcert -alias myclient -keystore client.p12 -storepass changeit -file myclient.cer
keytool -list -v -keystore client.p12 -storepass changeit


echo "*************************************************************"
echo "***************** trust_ca **********************************"
echo "*************************************************************"
Expand Down
7 changes: 7 additions & 0 deletions core/pva/make_tls_simple → core/pva/make_tls_simple.sh
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
# Minimal example, see TLS.md steps 1-4

rm -rf demo
mkdir demo
cd demo


# Creates a key pair for the server
keytool -genkey -alias mykey -dname "CN=server" -keystore KEYSTORE -storepass changeit -keyalg RSA
# Export the public key
keytool -export -alias mykey -keystore KEYSTORE -storepass changeit -rfc -file mykey.cer

# Create a trust store for the client to make it aware of the server's public key
keytool -import -alias mykey -file mykey.cer -keystore TRUSTSTORE -storepass changeit -noprompt

echo "*************************************************************"
Expand Down
20 changes: 20 additions & 0 deletions core/pva/src/main/java/org/epics/pva/PVASettings.java
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,24 @@ public class PVASettings
*/
public static String EPICS_PVAS_TLS_KEYCHAIN = "";

/** Secure server options
*
* <ul>
* <li><code>client_cert=optional</code>:
* Default; clients can provide certificate for "X509" authentication,
* but may also use "ca" or "anonymous" authentication
* <li><code>client_cert=require</code>:
* Clients must provide certificate for "X509" authentication.
* Socket with otherwise be closed during initial handshake.
* Server will log "SSLHandshakeException: Empty client certificate chain",
* client will log "SSLHandshakeException: Received fatal alert: bad_certificate"
* </ul>
*/
public static String EPICS_PVAS_TLS_OPTIONS = "";

/** Does EPICS_PVAS_TLS_OPTIONS contain "client_cert=require"? */
public static boolean require_client_cert;

/** Path to PVA client keystore and truststore, a PKCS12 file that contains the certificates or root CA
* that the client will trust when verifying a server certificate,
* and optional client certificate used with x509 authentication to establish the client's name.
Expand Down Expand Up @@ -237,6 +255,8 @@ public class PVASettings
EPICS_PVA_CONN_TMO = get("EPICS_PVA_CONN_TMO", EPICS_PVA_CONN_TMO);
EPICS_PVA_MAX_ARRAY_FORMATTING = get("EPICS_PVA_MAX_ARRAY_FORMATTING", EPICS_PVA_MAX_ARRAY_FORMATTING);
EPICS_PVAS_TLS_KEYCHAIN = get("EPICS_PVAS_TLS_KEYCHAIN", EPICS_PVAS_TLS_KEYCHAIN);
EPICS_PVAS_TLS_OPTIONS = get("EPICS_PVAS_TLS_OPTIONS", EPICS_PVAS_TLS_OPTIONS);
require_client_cert = EPICS_PVAS_TLS_OPTIONS.contains("client_cert=require");
EPICS_PVA_TLS_KEYCHAIN = get("EPICS_PVA_TLS_KEYCHAIN", EPICS_PVA_TLS_KEYCHAIN);
EPICS_PVA_SEND_BUFFER_SIZE = get("EPICS_PVA_SEND_BUFFER_SIZE", EPICS_PVA_SEND_BUFFER_SIZE);
EPICS_PVA_FAST_BEACON_MIN = get("EPICS_PVA_FAST_BEACON_MIN", EPICS_PVA_FAST_BEACON_MIN);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2019-2022 Oak Ridge National Laboratory.
* Copyright (c) 2019-2023 Oak Ridge National Laboratory.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
Expand All @@ -16,6 +16,7 @@
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;

import org.epics.pva.PVASettings;
import org.epics.pva.data.PVAData;
Expand Down Expand Up @@ -59,6 +60,7 @@ private static void help()
private static void setLogLevel(final Level level)
{
PVASettings.logger.setLevel(level);
Logger.getLogger("jdk.event.security").setLevel(level);
}

/** Get info for each PV on the list, then close PV
Expand Down
21 changes: 17 additions & 4 deletions core/pva/src/main/java/org/epics/pva/common/SecureSockets.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ private static SSLContext createContext(final String keychain_setting) throws Ex
pass = "".toCharArray();
}

logger.log(Level.INFO, () -> "Loading keychain '" + path + "'");
logger.log(Level.CONFIG, () -> "Loading keychain '" + path + "'");

final KeyStore key_store = KeyStore.getInstance("PKCS12");
key_store.load(new FileInputStream(path), pass);
Expand Down Expand Up @@ -119,10 +119,19 @@ public static ServerSocket createServerSocket(final InetSocketAddress address, f
if (tls_server_sockets == null)
throw new Exception("TLS is not supported. Configure EPICS_PVAS_TLS_KEYCHAIN");
socket = tls_server_sockets.createServerSocket();
final SSLServerSocket ssl = (SSLServerSocket) socket;

// Request, but don't require, client's certificate with 'principal' name for x509 authentication
((SSLServerSocket) socket).setWantClientAuth(true);
((SSLServerSocket) socket).setEnabledProtocols(PROTOCOLS);
// Do we require client's certificate with 'principal' name for x509 authentication,
// and initial handshake will otherwise fail?
// Or do we support a client certificate, but it's not essential?
if (PVASettings.require_client_cert)
{
ssl.setNeedClientAuth(true);
logger.log(Level.FINE, "Server requires client certificate");
}
else
ssl.setWantClientAuth(true);
ssl.setEnabledProtocols(PROTOCOLS);
}
else
socket = new ServerSocket();
Expand Down Expand Up @@ -204,6 +213,10 @@ public static TLSHandshakeInfo fromSocket(final SSLSocket socket) throws Excepti
// "This method is synchronous for the initial handshake on a connection
// and returns when the negotiated handshake is complete",
// so no need to addHandshakeCompletedListener()

// If server socket was configured to require client authentication,
// there will be an SSLHandshakeException with message "Empty client certificate chain",
// but no obvious way to catch that
socket.startHandshake();

try
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;

import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLSocket;

import org.epics.pva.PVASettings;
Expand Down Expand Up @@ -219,7 +220,15 @@ private void listen()
if (client instanceof SSLSocket)
{
logger.log(Level.FINE, () -> Thread.currentThread().getName() + " accepted TLS client " + client.getRemoteSocketAddress());
tls_info = TLSHandshakeInfo.fromSocket((SSLSocket) client);
try
{
tls_info = TLSHandshakeInfo.fromSocket((SSLSocket) client);
}
catch (SSLHandshakeException ssl)
{
logger.log(Level.FINE, "SSL Handshake error for " + client.getRemoteSocketAddress(), ssl);
continue;
}
}
else
logger.log(Level.WARNING, () -> Thread.currentThread().getName() + " expected TLS client " + client.getRemoteSocketAddress() + " but did not get SSLSocket");
Expand Down
Loading