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

Fix certutil http for empty password with JDK 11 and lower #55437

Merged
merged 3 commits into from
Apr 22, 2020

Conversation

ywangd
Copy link
Member

@ywangd ywangd commented Apr 20, 2020

This PR fix elasticseaerch-certutil http command so that it correctly accepts empty keystore password for JDK version 11 and lower.

Resovles: #55386

@ywangd ywangd added >bug :Security/Security Security issues without another label v8.0.0 v7.8.0 labels Apr 20, 2020
@ywangd ywangd requested a review from albertzaharovits April 20, 2020 05:44
@elasticmachine
Copy link
Collaborator

Pinging @elastic/es-security (:Security/Security)

Comment on lines +566 to +569
final String caPassword = randomFrom("", randomAlphaOfLengthBetween(4, 16));
final String node1Password = randomFrom("", randomAlphaOfLengthBetween(4, 16));
final String node2Password = randomFrom("", randomAlphaOfLengthBetween(4, 16));
final String node3Password = randomFrom("", randomAlphaOfLengthBetween(4, 16));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commands other than http do not have issue with empty password because of the withPassword(...) wrapper. These empty strings are just added here for completeness.

private static <T, E extends Exception> T withPassword(String description, char[] password, Terminal terminal,
CheckedFunction<char[], T, E> body) throws E {
if (password == null) {
char[] promptedValue = terminal.readSecret("Enter password for " + description + " : ");
try {
return body.apply(promptedValue);
} finally {
Arrays.fill(promptedValue, (char) 0);
}
} else {
return body.apply(password);
}
}

@@ -864,7 +864,7 @@ private boolean askCertSigningRequest(Terminal terminal) {
terminal.println("IT IS IMPORTANT THAT YOU REMEMBER THIS PASSWORD AND KEEP IT SECURE");
terminal.println("");
final char[] password = readPassword(terminal, "CA password: ", true);
return new CertificateTool.CAInfo(caCert, keyPair.getPrivate(), true, password.length == 0 ? null : password);
return new CertificateTool.CAInfo(caCert, keyPair.getPrivate(), true, password);
Copy link
Member Author

@ywangd ywangd Apr 20, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is the fix. But is there any reason this was intentionally set to null for empty char array? I wonder whether the change would introduce any subtle side effect. @tvernum

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inspecting the uses of this returned CAInfo instance, I am pretty sure this change is not impactful.

However, looking at this other use of CAInfo, there seems to be a pattern where a null value is used to signal that a password prompt is further required. I believe the original code here intended for this same behavior.

@ywangd
Copy link
Member Author

ywangd commented Apr 21, 2020

Btw, I checked all usages of PKCS12KeyStore#engineStore and this is the only place that could get a null password.

Tag #55386 here so the issue is correctly referenced here (forgot to do when creating the PR and subsequent editing does not count).

Copy link
Contributor

@albertzaharovits albertzaharovits left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

I would've opted for parametrized test methods, to explicitly cover the empty password test cases, instead of relying on randomization for that. So, for example, there would be private void testGenerateSingleCertificateSigningRequest(boolean withEmptyPassword), together with two public test calls.
But I think this is acceptable nonetheless.

@@ -864,7 +864,7 @@ private boolean askCertSigningRequest(Terminal terminal) {
terminal.println("IT IS IMPORTANT THAT YOU REMEMBER THIS PASSWORD AND KEEP IT SECURE");
terminal.println("");
final char[] password = readPassword(terminal, "CA password: ", true);
return new CertificateTool.CAInfo(caCert, keyPair.getPrivate(), true, password.length == 0 ? null : password);
return new CertificateTool.CAInfo(caCert, keyPair.getPrivate(), true, password);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inspecting the uses of this returned CAInfo instance, I am pretty sure this change is not impactful.

However, looking at this other use of CAInfo, there seems to be a pattern where a null value is used to signal that a password prompt is further required. I believe the original code here intended for this same behavior.

private void runForNonEmptyPattern(String pattern, Runnable runnable) {
if ("".equals(pattern) == false) {
runnable.run();
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My preference would have been a simple if in the code.

Copy link
Member Author

@ywangd ywangd Apr 22, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure I removed it and replaced with if check in the call sites.


assertThat(kibanaYml, containsString("elasticsearch.ssl.certificateAuthorities: [ \"config/elasticsearch-ca.pem\" ]"));
assertThat(kibanaYml, containsString("https://"));
shouldNotContain.forEach(s -> assertThat(kibanaYml, not(containsString(s))));
shouldNotContain.forEach(s -> runForNonEmptyPattern(s, () -> assertThat(kibanaYml, not(containsString(s)))));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would've not included the empty password in the list of shouldNotContain, but it's just a personal preference.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. The list is not filtered before passed in.

@albertzaharovits
Copy link
Contributor

I would also suggest changing the title from Support empty p12 keystore password for JDK 11 and lower to Fix certutil http empty password for JDK 11 and lower as well as the description to better reflect what this PR does not so much of how.

@ywangd ywangd changed the title Support empty p12 keystore password for JDK 11 and lower Fix certutil http for empty password with JDK 11 and lower Apr 22, 2020
@ywangd
Copy link
Member Author

ywangd commented Apr 22, 2020

Thanks @albertzaharovits I have made changes based on your comments. One thing I didn't change is having empty password has part of randomPassword() call instead of creating a separate method to test for empty password. The main reason is that both ca password and node password can be empty. So it is easier to test with randomness for better coverage.

@ywangd ywangd merged commit 00b4d3d into elastic:master Apr 22, 2020
ywangd added a commit to ywangd/elasticsearch that referenced this pull request Apr 22, 2020
…5437)

 Fix elasticseaerch-certutil http command so that it correctly accepts empty keystore password with JDK version 11 and lower.
ywangd added a commit that referenced this pull request Apr 22, 2020
…55565)

 Fix elasticseaerch-certutil http command so that it correctly accepts empty keystore password with JDK version 11 and lower.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
>bug :Security/Security Security issues without another label v7.8.0 v8.0.0-alpha1
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants