Skip to content

Commit cefa787

Browse files
fix(NetSSLeay): avoid double-invoking passwd callback in CTX_use_PrivateKey_file
CTX_use_PrivateKey_file previously called loadPrivateKeyFile (which invokes the password callback) and then re-opened the PEM and re-invoked the callback to populate ctxState.loadedPrivateKey for buildSslContext. This broke t/local/05_passwd_cb.t, which counts callback invocations: not ok 17 - different cbs per ctx work # each cb called 2x, expected 1 not ok 21 - callback1 called 2 times # got: '3' expected: '2' Plus spurious "Bad plan: planned 36 tests but ran 40" because the extra callback calls ran their own is()/ok() assertions. Fix: loadPrivateKeyFile now takes an optional SslCtxState and populates loadedPrivateKey + clears the cached SSLContext in a single pass. The callback runs exactly once per load. use_PrivateKey_file (SSL-level) benefits too — it now also populates the underlying CTX's key so the KeyManager sees it. Test results on t/local/05_passwd_cb.t: before: Tests: 40 Failed: 6 Parse errors: planned 36 ran 40 after: Tests: 36 Failed: 0 All tests successful. Full Net-SSLeay bundled suite: 47 files, 2326 tests, all pass. ./jcpan -t Digest::SHA3: 33/33 still green. make: green. Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1 parent dc78c62 commit cefa787

2 files changed

Lines changed: 25 additions & 36 deletions

File tree

src/main/java/org/perlonjava/core/Configuration.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public final class Configuration {
3333
* Automatically populated by Gradle/Maven during build.
3434
* DO NOT EDIT MANUALLY - this value is replaced at build time.
3535
*/
36-
public static final String gitCommitId = "2313fda82";
36+
public static final String gitCommitId = "dc78c6298";
3737

3838
/**
3939
* Git commit date of the build (ISO format: YYYY-MM-DD).
@@ -48,7 +48,7 @@ public final class Configuration {
4848
* Parsed by App::perlbrew and other tools via: perl -V | grep "Compiled at"
4949
* DO NOT EDIT MANUALLY - this value is replaced at build time.
5050
*/
51-
public static final String buildTimestamp = "Apr 21 2026 12:38:06";
51+
public static final String buildTimestamp = "Apr 21 2026 13:06:01";
5252

5353
// Prevent instantiation
5454
private Configuration() {

src/main/java/org/perlonjava/runtime/perlmodule/NetSSLeay.java

Lines changed: 23 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4876,33 +4876,10 @@ public static RuntimeList CTX_use_PrivateKey_file(RuntimeArray args, int ctx) {
48764876
String filename = args.get(1).toString();
48774877
SslCtxState ctxState = CTX_HANDLES.get(ctxHandle);
48784878
if (ctxState == null) return new RuntimeScalar(0).getList();
4879-
RuntimeList r = loadPrivateKeyFile(filename, ctxState.passwdCb, ctxState.passwdUserdata);
4880-
if (r.size() > 0 && r.getFirst().getLong() == 1) {
4881-
// Load succeeded; parse again into the CTX so the KeyManager
4882-
// factory has the key at buildSslContext time.
4883-
try {
4884-
byte[] fileData = Files.readAllBytes(RuntimeIO.resolvePath(filename));
4885-
String pem = new String(fileData, StandardCharsets.ISO_8859_1);
4886-
String pass = null;
4887-
if (ctxState.passwdCb != null && ctxState.passwdCb.type == RuntimeScalarType.CODE) {
4888-
RuntimeArray cbArgs = new RuntimeArray();
4889-
cbArgs.push(new RuntimeScalar(0));
4890-
cbArgs.push(ctxState.passwdUserdata != null ? ctxState.passwdUserdata
4891-
: new RuntimeScalar());
4892-
pass = RuntimeCode.apply(ctxState.passwdCb, cbArgs,
4893-
RuntimeContextType.SCALAR).getFirst().toString();
4894-
}
4895-
byte[] der = parsePemPrivateKey(pem, pass);
4896-
if (der != null) {
4897-
PrivateKey pk = parsePrivateKeyDer(der);
4898-
if (pk != null) {
4899-
ctxState.loadedPrivateKey = pk;
4900-
ctxState.sslContext = null; // force rebuild
4901-
}
4902-
}
4903-
} catch (Exception ignored) {}
4904-
}
4905-
return r;
4879+
// Pass ctxState so the successful-parse path populates the KeyManager
4880+
// state in one pass; avoids re-invoking the password callback, which
4881+
// broke t/local/05_passwd_cb.t (callback counted an extra call per load).
4882+
return loadPrivateKeyFile(filename, ctxState.passwdCb, ctxState.passwdUserdata, ctxState);
49064883
}
49074884

49084885
// SSL-level password callback functions
@@ -4933,23 +4910,30 @@ public static RuntimeList use_PrivateKey_file(RuntimeArray args, int ctx) {
49334910
// SSL-level callback takes precedence over CTX-level
49344911
RuntimeScalar cb = ssl.passwdCb;
49354912
RuntimeScalar ud = ssl.passwdUserdata;
4913+
SslCtxState ctxStateForKey = CTX_HANDLES.get(ssl.ctxHandle);
49364914
if (cb == null) {
49374915
// Fall back to CTX-level callback
4938-
SslCtxState ctxState = CTX_HANDLES.get(ssl.ctxHandle);
4939-
if (ctxState != null) {
4940-
cb = ctxState.passwdCb;
4941-
ud = ctxState.passwdUserdata;
4916+
if (ctxStateForKey != null) {
4917+
cb = ctxStateForKey.passwdCb;
4918+
ud = ctxStateForKey.passwdUserdata;
49424919
}
49434920
}
4944-
return loadPrivateKeyFile(filename, cb, ud);
4921+
return loadPrivateKeyFile(filename, cb, ud, ctxStateForKey);
49454922
}
49464923

4947-
private static RuntimeList loadPrivateKeyFile(String filename, RuntimeScalar cb, RuntimeScalar ud) {
4924+
/**
4925+
* @param ctxStateForKey if non-null and the PEM parses successfully,
4926+
* the parsed {@link PrivateKey} is stored on this context so
4927+
* {@code buildSslContext} can pick it up without re-invoking
4928+
* the password callback.
4929+
*/
4930+
private static RuntimeList loadPrivateKeyFile(String filename, RuntimeScalar cb, RuntimeScalar ud,
4931+
SslCtxState ctxStateForKey) {
49484932
try {
49494933
byte[] fileData = Files.readAllBytes(RuntimeIO.resolvePath(filename));
49504934
String pem = new String(fileData, StandardCharsets.ISO_8859_1);
49514935

4952-
// Get password via callback
4936+
// Get password via callback (invoked exactly once per call)
49534937
String password = null;
49544938
if (cb != null && cb.type == RuntimeScalarType.CODE) {
49554939
RuntimeArray cbArgs = new RuntimeArray();
@@ -4969,6 +4953,11 @@ private static RuntimeList loadPrivateKeyFile(String filename, RuntimeScalar cb,
49694953
PrivateKey privKey = parsePrivateKeyDer(derBytes);
49704954
if (privKey == null) return new RuntimeScalar(0).getList();
49714955

4956+
if (ctxStateForKey != null) {
4957+
ctxStateForKey.loadedPrivateKey = privKey;
4958+
ctxStateForKey.sslContext = null; // force rebuild
4959+
}
4960+
49724961
return new RuntimeScalar(1).getList(); // success
49734962
} catch (Exception e) {
49744963
return new RuntimeScalar(0).getList(); // failure

0 commit comments

Comments
 (0)