Skip to content
Open
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
34 changes: 34 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 @@ -153,6 +153,22 @@
*/
public static String EPICS_PVAS_TLS_KEYCHAIN = "";

/** Path to a file containing the password for the PVA server keychain.
*
* <p>Alternative to embedding the password in {@link #EPICS_PVAS_TLS_KEYCHAIN}
* using the "/path/to/file;password" syntax.
* When set, the password is read from this file instead, with leading and trailing
* whitespace stripped.
* Takes precedence over an inline password in {@link #EPICS_PVAS_TLS_KEYCHAIN}
* when no ";" separator is present in that setting.
*
* <p>Intended for environments where secrets are mounted as files,
* for example Kubernetes pods using a {@code Secret} volume.
*
* <p>When empty, no password file is used.
*/
public static String EPICS_PVAS_TLS_KEYCHAIN_PWD_FILE = "";

Check warning on line 170 in core/pva/src/main/java/org/epics/pva/PVASettings.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Make EPICS_PVAS_TLS_KEYCHAIN_PWD_FILE a static final constant or non-public and provide accessors if needed.

See more on https://sonarcloud.io/project/issues?id=ControlSystemStudio_phoebus&issues=AZ2R3p3UAHiTsgjSl_q6&open=AZ2R3p3UAHiTsgjSl_q6&pullRequest=3783

Check warning on line 170 in core/pva/src/main/java/org/epics/pva/PVASettings.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename this field "EPICS_PVAS_TLS_KEYCHAIN_PWD_FILE" to match the regular expression '^[a-z][a-zA-Z0-9]*$'.

See more on https://sonarcloud.io/project/issues?id=ControlSystemStudio_phoebus&issues=AZ2R3p3UAHiTsgjSl_q-&open=AZ2R3p3UAHiTsgjSl_q-&pullRequest=3783

Check warning on line 170 in core/pva/src/main/java/org/epics/pva/PVASettings.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Make this "public static EPICS_PVAS_TLS_KEYCHAIN_PWD_FILE" field final

See more on https://sonarcloud.io/project/issues?id=ControlSystemStudio_phoebus&issues=AZ2R3p3UAHiTsgjSl_q8&open=AZ2R3p3UAHiTsgjSl_q8&pullRequest=3783

/** Secure server options
*
* <ul>
Expand Down Expand Up @@ -183,6 +199,22 @@
*/
public static String EPICS_PVA_TLS_KEYCHAIN = "";

/** Path to a file containing the password for the PVA client keychain.
*
* <p>Alternative to embedding the password in {@link #EPICS_PVA_TLS_KEYCHAIN}
* using the "/path/to/file;password" syntax.
* When set, the password is read from this file instead, with leading and trailing
* whitespace stripped.
* Takes precedence over an inline password in {@link #EPICS_PVA_TLS_KEYCHAIN}
* when no ";" separator is present in that setting.
*
* <p>Intended for environments where secrets are mounted as files,
* for example Kubernetes pods using a {@code Secret} volume.
*
* <p>When empty, no password file is used.
*/
public static String EPICS_PVA_TLS_KEYCHAIN_PWD_FILE = "";

Check warning on line 216 in core/pva/src/main/java/org/epics/pva/PVASettings.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Make EPICS_PVA_TLS_KEYCHAIN_PWD_FILE a static final constant or non-public and provide accessors if needed.

See more on https://sonarcloud.io/project/issues?id=ControlSystemStudio_phoebus&issues=AZ2R3p3UAHiTsgjSl_q7&open=AZ2R3p3UAHiTsgjSl_q7&pullRequest=3783

Check warning on line 216 in core/pva/src/main/java/org/epics/pva/PVASettings.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Make this "public static EPICS_PVA_TLS_KEYCHAIN_PWD_FILE" field final

See more on https://sonarcloud.io/project/issues?id=ControlSystemStudio_phoebus&issues=AZ2R3p3UAHiTsgjSl_q9&open=AZ2R3p3UAHiTsgjSl_q9&pullRequest=3783

Check warning on line 216 in core/pva/src/main/java/org/epics/pva/PVASettings.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename this field "EPICS_PVA_TLS_KEYCHAIN_PWD_FILE" to match the regular expression '^[a-z][a-zA-Z0-9]*$'.

See more on https://sonarcloud.io/project/issues?id=ControlSystemStudio_phoebus&issues=AZ2R3p3UAHiTsgjSl_q_&open=AZ2R3p3UAHiTsgjSl_q_&pullRequest=3783

/** TCP buffer size for sending data
*
* <p>Messages are constructed within this buffer,
Expand Down Expand Up @@ -281,9 +313,11 @@
EPICS_PVA_TCP_SOCKET_TMO = get("EPICS_PVA_TCP_SOCKET_TMO", EPICS_PVA_TCP_SOCKET_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_KEYCHAIN_PWD_FILE = get("EPICS_PVAS_TLS_KEYCHAIN_PWD_FILE", EPICS_PVAS_TLS_KEYCHAIN_PWD_FILE);
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_TLS_KEYCHAIN_PWD_FILE = get("EPICS_PVA_TLS_KEYCHAIN_PWD_FILE", EPICS_PVA_TLS_KEYCHAIN_PWD_FILE);
if (EPICS_PVA_TLS_KEYCHAIN.isEmpty() && !EPICS_PVAS_TLS_KEYCHAIN.isEmpty())
{
EPICS_PVA_TLS_KEYCHAIN = EPICS_PVAS_TLS_KEYCHAIN;
Expand Down
38 changes: 32 additions & 6 deletions core/pva/src/main/java/org/epics/pva/common/SecureSockets.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.KeyStore;
import java.security.Principal;
import java.security.cert.Certificate;
Expand Down Expand Up @@ -63,16 +65,18 @@
/** X509 certificates loaded from the keychain mapped by principal name of the certificate */
public static Map<String, X509Certificate> keychain_x509_certificates = new ConcurrentHashMap<>();

/** @param keychain_setting "/path/to/keychain;password"
/** @param keychain_setting "/path/to/keychain", "/path/to/keychain;password",
* or just "/path/to/keychain" with password in a separate *_PWD_FILE
* @param is_server true for server keychain (uses EPICS_PVAS_TLS_KEYCHAIN_PWD_FILE),
* false for client (uses EPICS_PVA_TLS_KEYCHAIN_PWD_FILE)
* @return {@link SSLContext} with 'keystore' and 'truststore' set to content of keystore
* @throws Exception on error
*/
private static SSLContext createContext(final String keychain_setting) throws Exception
private static SSLContext createContext(final String keychain_setting, final boolean is_server) throws Exception

Check warning on line 75 in core/pva/src/main/java/org/epics/pva/common/SecureSockets.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace generic exceptions with specific library exceptions or a custom exception.

See more on https://sonarcloud.io/project/issues?id=ControlSystemStudio_phoebus&issues=AZ2RmaDmAfTdVnF5O6vE&open=AZ2RmaDmAfTdVnF5O6vE&pullRequest=3783
{
final String path;
final char[] pass;

// We support the default "" empty as well as actual passwords, but not null for no password
final int sep = keychain_setting.indexOf(';');
if (sep > 0)
{
Expand All @@ -82,7 +86,7 @@
else
{
path = keychain_setting;
pass = "".toCharArray();
pass = readKeychainPassword(is_server);
}

logger.log(Level.FINE, () -> "Loading keychain '" + path + "'");
Expand Down Expand Up @@ -131,20 +135,42 @@
return context;
}

private static char[] readKeychainPassword(final boolean is_server)
{
final String pwd_file = is_server ? PVASettings.EPICS_PVAS_TLS_KEYCHAIN_PWD_FILE
: PVASettings.EPICS_PVA_TLS_KEYCHAIN_PWD_FILE;
if (! pwd_file.isEmpty())
{
try
{
final String password = Files.readString(Path.of(pwd_file)).trim();
logger.log(Level.FINE, () -> "Read keychain password from " + pwd_file);
return password.toCharArray();
}
catch (Exception ex)
{
logger.log(Level.WARNING, "Error reading password file " + pwd_file, ex);

Check warning on line 152 in core/pva/src/main/java/org/epics/pva/common/SecureSockets.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Lambda should be used to defer string concatenation.

See more on https://sonarcloud.io/project/issues?id=ControlSystemStudio_phoebus&issues=AZ2RmaDmAfTdVnF5O6vF&open=AZ2RmaDmAfTdVnF5O6vF&pullRequest=3783
}
}
// Java PKCS12: null skips encrypted sections (loses CA certs).
// Empty array attempts decryption with retry via NUL char fallback.
return new char[0];
}

private static synchronized void initialize() throws Exception
{
if (initialized)
return;

if (! PVASettings.EPICS_PVAS_TLS_KEYCHAIN.isBlank())
{
final SSLContext context = createContext(PVASettings.EPICS_PVAS_TLS_KEYCHAIN);
final SSLContext context = createContext(PVASettings.EPICS_PVAS_TLS_KEYCHAIN, true);
tls_server_sockets = context.getServerSocketFactory();
}

if (! PVASettings.EPICS_PVA_TLS_KEYCHAIN.isBlank())
{
final SSLContext context = createContext(PVASettings.EPICS_PVA_TLS_KEYCHAIN);
final SSLContext context = createContext(PVASettings.EPICS_PVA_TLS_KEYCHAIN, false);
tls_client_sockets = context.getSocketFactory();
}
initialized = true;
Expand Down
Loading