-
Notifications
You must be signed in to change notification settings - Fork 24.9k
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
solved issue "certutil: large passwords not set" #30944 #36689
Changes from 7 commits
9f2be48
51cfd77
c0943be
de70a40
1da475e
2e58e7a
6fe7e60
b885cae
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -108,6 +108,11 @@ public class CertificateTool extends LoggingAwareMultiCommand { | |||||||||||||||||||||
Pattern.compile("[a-zA-Z0-9!@#$%^&{}\\[\\]()_+\\-=,.~'` ]{1," + MAX_FILENAME_LENGTH + "}"); | ||||||||||||||||||||||
private static final int DEFAULT_KEY_SIZE = 2048; | ||||||||||||||||||||||
|
||||||||||||||||||||||
// Older versions of OpenSSL had a max internal password length. | ||||||||||||||||||||||
// We issue warnings when writing files with passwords that would not be usable in those versions of OpenSSL. | ||||||||||||||||||||||
static final String OLD_OPENSSL_VERSION = "1.1.0"; | ||||||||||||||||||||||
static final int MAX_PASSWORD_OLD_OPENSSL = 50; | ||||||||||||||||||||||
|
||||||||||||||||||||||
/** | ||||||||||||||||||||||
* Wraps the certgen object parser. | ||||||||||||||||||||||
*/ | ||||||||||||||||||||||
|
@@ -349,9 +354,8 @@ CAInfo getCAInfo(Terminal terminal, OptionSet options, Environment env) throws E | |||||||||||||||||||||
private CAInfo loadPkcs12CA(Terminal terminal, OptionSet options, Environment env) throws Exception { | ||||||||||||||||||||||
Path path = resolvePath(options, caPkcs12PathSpec); | ||||||||||||||||||||||
char[] passwordOption = getChars(caPasswordSpec.value(options)); | ||||||||||||||||||||||
|
||||||||||||||||||||||
Map<Certificate, Key> keys = withPassword("CA (" + path + ")", passwordOption, | ||||||||||||||||||||||
terminal, password -> CertParsingUtils.readPkcs12KeyPairs(path, password, a -> password)); | ||||||||||||||||||||||
Map<Certificate, Key> keys = withPassword("CA (" + path + ")", passwordOption, terminal, false, | ||||||||||||||||||||||
password -> CertParsingUtils.readPkcs12KeyPairs(path, password, a -> password)); | ||||||||||||||||||||||
|
||||||||||||||||||||||
if (keys.size() != 1) { | ||||||||||||||||||||||
throw new IllegalArgumentException("expected a single key in file [" + path.toAbsolutePath() + "] but found [" + | ||||||||||||||||||||||
|
@@ -391,10 +395,11 @@ CAInfo generateCA(Terminal terminal, OptionSet options) throws Exception { | |||||||||||||||||||||
|
||||||||||||||||||||||
if (options.hasArgument(caPasswordSpec)) { | ||||||||||||||||||||||
char[] password = getChars(caPasswordSpec.value(options)); | ||||||||||||||||||||||
checkPasswordLengthForOpenSSLCompatibility(password, terminal, false); | ||||||||||||||||||||||
return new CAInfo(caCert, keyPair.getPrivate(), true, password); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
if (options.has(caPasswordSpec)) { | ||||||||||||||||||||||
return withPassword("CA Private key", null, terminal, p -> new CAInfo(caCert, keyPair.getPrivate(), true, p.clone())); | ||||||||||||||||||||||
return withPassword("CA Private key", null, terminal, true, p -> new CAInfo(caCert, keyPair.getPrivate(), true, p.clone())); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
return new CAInfo(caCert, keyPair.getPrivate(), true, null); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
@@ -541,9 +546,9 @@ static void writePkcs12(String fileName, OutputStream output, String alias, Cert | |||||||||||||||||||||
char[] password, Terminal terminal) throws Exception { | ||||||||||||||||||||||
final KeyStore pkcs12 = KeyStore.getInstance("PKCS12"); | ||||||||||||||||||||||
pkcs12.load(null); | ||||||||||||||||||||||
withPassword(fileName, password, terminal, p12Password -> { | ||||||||||||||||||||||
withPassword(fileName, password, terminal, true, p12Password -> { | ||||||||||||||||||||||
if (isAscii(p12Password)) { | ||||||||||||||||||||||
pkcs12.setKeyEntry(alias, pair.key, p12Password, new Certificate[]{pair.cert}); | ||||||||||||||||||||||
pkcs12.setKeyEntry(alias, pair.key, p12Password, new Certificate[] { pair.cert }); | ||||||||||||||||||||||
if (caCert != null) { | ||||||||||||||||||||||
pkcs12.setCertificateEntry("ca", caCert); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
@@ -784,7 +789,7 @@ void generateAndWriteSignedCertificates(Path output, boolean writeZipFile, Optio | |||||||||||||||||||||
final String keyFileName = entryBase + ".key"; | ||||||||||||||||||||||
outputStream.putNextEntry(new ZipEntry(keyFileName)); | ||||||||||||||||||||||
if (usePassword) { | ||||||||||||||||||||||
withPassword(keyFileName, outputPassword, terminal, password -> { | ||||||||||||||||||||||
withPassword(keyFileName, outputPassword, terminal, true, password -> { | ||||||||||||||||||||||
pemWriter.writeObject(pair.key, getEncrypter(password)); | ||||||||||||||||||||||
return null; | ||||||||||||||||||||||
}); | ||||||||||||||||||||||
|
@@ -924,16 +929,43 @@ static PEMEncryptor getEncrypter(char[] password) { | |||||||||||||||||||||
return new JcePEMEncryptorBuilder("AES-128-CBC").setProvider(BC_PROV).build(password); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
private static <T, E extends Exception> T withPassword(String description, char[] password, Terminal terminal, | ||||||||||||||||||||||
static boolean checkPasswordLengthForOpenSSLCompatibility(char[] password, Terminal terminal, boolean confirm) { | ||||||||||||||||||||||
if (password.length > MAX_PASSWORD_OLD_OPENSSL) { | ||||||||||||||||||||||
terminal.println( | ||||||||||||||||||||||
Verbosity.SILENT, | ||||||||||||||||||||||
"Warning: Your password exceeds " | ||||||||||||||||||||||
+ MAX_PASSWORD_OLD_OPENSSL | ||||||||||||||||||||||
+ " characters. Versions of OpenSSL older than " | ||||||||||||||||||||||
+ OLD_OPENSSL_VERSION | ||||||||||||||||||||||
+ " may not be able to read this file." | ||||||||||||||||||||||
Comment on lines
+942
to
+946
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why? They're constants, so we don't need the braces in order to see where the variable part is substituted in. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought the braces are for emphasis. But if they are just for variables, then you are right that we don't need them here. |
||||||||||||||||||||||
); | ||||||||||||||||||||||
if (confirm) { | ||||||||||||||||||||||
if (terminal.promptYesNo("Do you want to continue?", true) == false) { | ||||||||||||||||||||||
return false; | ||||||||||||||||||||||
} | ||||||||||||||||||||||
} | ||||||||||||||||||||||
tvernum marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||
} | ||||||||||||||||||||||
return true; | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
private static <T, E extends Exception> T withPassword(String description, char[] password, Terminal terminal, boolean checkLength, | ||||||||||||||||||||||
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); | ||||||||||||||||||||||
while (true) { | ||||||||||||||||||||||
char[] promptedValue = terminal.readSecret("Enter password for " + description + " : "); | ||||||||||||||||||||||
if (checkLength && checkPasswordLengthForOpenSSLCompatibility(promptedValue, terminal, true) == false) { | ||||||||||||||||||||||
continue; | ||||||||||||||||||||||
} | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The formatting of thise code (spaces around brackets etc) is different to the way the rest of this source code is formatted. By the time we get to the end of this review process, we'll want to have it formatted in the same style as the surrounding code. |
||||||||||||||||||||||
try { | ||||||||||||||||||||||
return body.apply(promptedValue); | ||||||||||||||||||||||
} finally { | ||||||||||||||||||||||
Arrays.fill(promptedValue, (char) 0); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The We need to only perform this check/warning if the password is being applied to a new file. |
||||||||||||||||||||||
} | ||||||||||||||||||||||
} else { | ||||||||||||||||||||||
if (checkLength) { | ||||||||||||||||||||||
checkPasswordLengthForOpenSSLCompatibility(password, terminal, false); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
return body.apply(password); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -141,7 +141,7 @@ public void testGenerateSingleCertificateSigningRequest() throws Exception { | |
|
||
terminal.addTextInput(randomBoolean() ? "n" : ""); // don't change advanced settings | ||
|
||
final String password = randomPassword(); | ||
final String password = randomPassword(false); | ||
terminal.addSecretInput(password); | ||
if ("".equals(password) == false) { | ||
terminal.addSecretInput(password); | ||
|
@@ -268,10 +268,13 @@ public void testGenerateSingleCertificateWithExistingCA() throws Exception { | |
|
||
terminal.addTextInput(randomBoolean() ? "n" : ""); // don't change advanced settings | ||
|
||
final String password = randomPassword(); | ||
final String password = randomPassword(randomBoolean()); | ||
terminal.addSecretInput(password); | ||
if ("".equals(password) == false) { | ||
terminal.addSecretInput(password); | ||
if (password.length() > 50) { | ||
terminal.addTextInput("y"); // Accept OpenSSL issue | ||
} | ||
} // confirm | ||
|
||
terminal.addTextInput(outFile.toString()); | ||
|
@@ -365,10 +368,20 @@ public void testGenerateMultipleCertificateWithNewCA() throws Exception { | |
caKeySize = HttpCertificateCommand.DEFAULT_CA_KEY_SIZE; | ||
} | ||
|
||
final String caPassword = randomPassword(); | ||
final String caPassword = randomPassword(randomBoolean()); | ||
// randomly enter a long password here, and then say "no" on the warning prompt | ||
if (randomBoolean()) { | ||
String longPassword = randomAlphaOfLengthBetween(60, 120); | ||
terminal.addSecretInput(longPassword); | ||
terminal.addSecretInput(longPassword); | ||
terminal.addTextInput("n"); // Change our mind | ||
} | ||
terminal.addSecretInput(caPassword); | ||
if ("".equals(caPassword) == false) { | ||
terminal.addSecretInput(caPassword); | ||
if (caPassword.length() > 50) { | ||
terminal.addTextInput("y"); // Acknowledge possible OpenSSL issue | ||
} | ||
} // confirm | ||
|
||
final int certYears = randomIntBetween(1, 8); | ||
|
@@ -398,7 +411,13 @@ public void testGenerateMultipleCertificateWithNewCA() throws Exception { | |
terminal.addTextInput("n"); // no more certs | ||
|
||
|
||
final String password = randomPassword(); | ||
final String password = randomPassword(false); | ||
// randomly enter an incorrect password here which will fail the "enter twice" check and prompt to try again | ||
if (randomBoolean()) { | ||
String wrongPassword = randomAlphaOfLengthBetween(8, 20); | ||
terminal.addSecretInput(wrongPassword); | ||
terminal.addSecretInput("__" + wrongPassword); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This isn't really related to this PR, but I noticed it was a gap in testing. |
||
terminal.addSecretInput(password); | ||
if ("".equals(password) == false) { | ||
terminal.addSecretInput(password); | ||
|
@@ -410,6 +429,9 @@ public void testGenerateMultipleCertificateWithNewCA() throws Exception { | |
final OptionSet options = command.getParser().parse(new String[0]); | ||
command.execute(terminal, options, env); | ||
|
||
if (caPassword.length() > 50) { | ||
assertThat(terminal.getOutput(), containsString("OpenSSL")); | ||
} | ||
tvernum marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Path zipRoot = getZipRoot(outFile); | ||
|
||
// Should have a CA directory with the generated CA. | ||
|
@@ -619,12 +641,12 @@ private List<String> randomHostNames() { | |
return hostNames; | ||
} | ||
|
||
private String randomPassword() { | ||
private String randomPassword(boolean longPassword) { | ||
// We want to assert that this password doesn't end up in any output files, so we need to make sure we | ||
// don't randomly generate a real word. | ||
return randomFrom( | ||
"", | ||
randomAlphaOfLength(4) + randomFrom('~', '*', '%', '$', '|') + randomAlphaOfLength(4) | ||
randomAlphaOfLengthBetween(4, 8) + randomFrom('~', '*', '%', '$', '|') + randomAlphaOfLength(longPassword ? 100 : 4) | ||
); | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit:
This method and parameter names confused me for a bit. They made me think that the behaviour when
confirm
isfalse
should bereturn false
if password is too long. But instead the default behaviour isreturn true
unless user says otherwise andconfirm
is to let user have that say. Maybe:It is quite verbose, but it feels more accurate to me. I am also happy if you can come up with something better.