feat: Verify key is unencrypted using two random passphrases

This commit is contained in:
Magnus Leßmann (@MarkL4YG) 2024-11-23 12:36:38 +01:00
parent 334d97c883
commit 5da1e5894d
Signed by: Mark.TwoFive
GPG key ID: 5B5EBCBE331F1E6F

View file

@ -15,6 +15,7 @@ import java.nio.file.Path;
import java.nio.file.StandardOpenOption; import java.nio.file.StandardOpenOption;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@ -25,7 +26,6 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.lang.NonNull; import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.zeroturnaround.exec.ProcessExecutor; import org.zeroturnaround.exec.ProcessExecutor;
@ -135,6 +135,7 @@ public class OpenSSLCertificateCreator {
"genrsa", "genrsa",
"-out", "-out",
keyFile.toString(), keyFile.toString(),
"-aes256",
"-passout", "-passout",
"env:KEY_PASS", "env:KEY_PASS",
Integer.toString(request.getRequestedKeyLength()) Integer.toString(request.getRequestedKeyLength())
@ -282,20 +283,28 @@ public class OpenSSLCertificateCreator {
} }
} }
/**
* Checks whether the provided key file is encrypted using a passphrase
*/
public boolean isKeyEncrypted(@NonNull Path keyFile) throws InterruptedException, CommandLineOperationException { public boolean isKeyEncrypted(@NonNull Path keyFile) throws InterruptedException, CommandLineOperationException {
// If any random passphrase works, the key is not encrypted. // If the key is not encrypted, any passphrase will work -> so generate a random one to check.
return !verifyKeyPassphrase(keyFile, null); String passphrase = UUID.randomUUID().toString();
boolean firstPass = verifyKeyPassphrase(keyFile, passphrase);
if (firstPass) {
// Try with another random passphrase in case we randomly got lucky guessing the passphrase the first time.
passphrase = UUID.randomUUID().toString();
return !verifyKeyPassphrase(keyFile, passphrase);
}
return true;
} }
/** /**
* Verifies a passphrase against a provided key. * Verifies a passphrase against a provided key.
* @implNote Due to the implementation of the OpenSSL cli, any password will be valid for unencrypted keys. (Check with {@link #isKeyEncrypted(Path) or by passing {@code null} as the passphrase.) * @implNote Due to the implementation of the OpenSSL cli, <em>any password</em> will be valid for unencrypted keys. (Check with {@link #isKeyEncrypted(Path).)
*/ */
public boolean verifyKeyPassphrase(@NonNull Path keyFile, @Nullable String passphrase) public boolean verifyKeyPassphrase(@NonNull Path keyFile, @NonNull String passphrase)
throws CommandLineOperationException, InterruptedException { throws CommandLineOperationException, InterruptedException {
// Run OpenSSL command: openssl rsa -check -in <keyFile> -passin pass:<randomKey> -noout and check exit code
try { try {
String keyPass = passphrase != null ? passphrase : passwordProvider.generateNewPassword();
StartedProcess verifyCommand = new ProcessExecutor() StartedProcess verifyCommand = new ProcessExecutor()
.command( .command(
resolveOpenSSL(), resolveOpenSSL(),
@ -304,16 +313,14 @@ public class OpenSSLCertificateCreator {
"-in", "-in",
keyFile.toString(), keyFile.toString(),
"-passin", "-passin",
"pass:" + keyPass, "pass:" + passphrase,
"-noout" "-noout"
) )
.redirectOutput(Slf4jStream.ofCaller().asError()) .redirectOutput(Slf4jStream.ofCaller().asError())
.redirectError(Slf4jStream.ofCaller().asError()) .redirectError(Slf4jStream.ofCaller().asError())
.start(); .start();
var verifyResult = verifyCommand.getFuture().get(); var verifyResult = verifyCommand.getFuture().get();
boolean commandSuccess = verifyResult.getExitValue() == 0; return verifyResult.getExitValue() == 0;
log.trace("Key check {} with passphrase provided={}", commandSuccess, passphrase != null);
return commandSuccess;
} catch (IOException | InterruptedException | ExecutionException e) { } catch (IOException | InterruptedException | ExecutionException e) {
throw new CommandLineOperationException("Failed to verify key encryption", e); throw new CommandLineOperationException("Failed to verify key encryption", e);
} }