mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-01-07 21:24:00 -05:00
Merge remote-tracking branch 'origin/patch'
This commit is contained in:
@@ -38,7 +38,7 @@ import ghidra.GhidraLaunchable;
|
||||
import ghidra.features.bsim.query.ingest.BSimLaunchable;
|
||||
import ghidra.framework.*;
|
||||
import ghidra.framework.client.ClientUtil;
|
||||
import ghidra.net.ApplicationKeyManagerUtils;
|
||||
import ghidra.net.PKIUtils;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.exception.AssertException;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
@@ -390,7 +390,7 @@ public class BSimControlLaunchable implements GhidraLaunchable {
|
||||
if (line == null) {
|
||||
break;
|
||||
}
|
||||
if (line.startsWith(ApplicationKeyManagerUtils.BEGIN_CERT)) {
|
||||
if (line.startsWith(PKIUtils.BEGIN_CERT)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -557,11 +557,10 @@ public class BSimControlLaunchable implements GhidraLaunchable {
|
||||
PasswordProtection pp = new PasswordProtection(password);
|
||||
try {
|
||||
// TODO: should subjectAlternativeNames be supported?
|
||||
KeyStore keyStore = ApplicationKeyManagerUtils.createKeyStore(alias, "CN=BSimServer",
|
||||
365 * 2, null, null, "JKS", null, password);
|
||||
KeyStore keyStore = PKIUtils.createKeyStore(alias, "CN=BSimServer", 365 * 2, null,
|
||||
null, "JKS", null, password);
|
||||
|
||||
ApplicationKeyManagerUtils.exportX509Certificates(keyStore.getCertificateChain(alias),
|
||||
certFile);
|
||||
PKIUtils.exportX509Certificates(keyStore.getCertificateChain(alias), certFile);
|
||||
Key key = keyStore.getKey(alias, password);
|
||||
|
||||
try (FileOutputStream fout = new FileOutputStream(passFile);
|
||||
|
||||
@@ -34,7 +34,7 @@ import ghidra.framework.*;
|
||||
import ghidra.framework.client.ClientUtil;
|
||||
import ghidra.framework.client.HeadlessClientAuthenticator;
|
||||
import ghidra.framework.protocol.ghidra.GhidraURL;
|
||||
import ghidra.net.SSLContextInitializer;
|
||||
import ghidra.net.DefaultSSLContextInitializer;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.SystemUtilities;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
@@ -1111,7 +1111,7 @@ public class BSimLaunchable implements GhidraLaunchable {
|
||||
|
||||
Application.initializeApplication(layout, config);
|
||||
|
||||
SSLContextInitializer.initialize();
|
||||
DefaultSSLContextInitializer.initialize();
|
||||
ghidra.framework.protocol.ghidra.Handler.registerHandler();
|
||||
ghidra.features.bsim.query.postgresql.Handler.registerHandler();
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -20,7 +20,7 @@ import java.util.List;
|
||||
|
||||
import generic.jar.ResourceFile;
|
||||
import ghidra.framework.*;
|
||||
import ghidra.net.ApplicationTrustManagerFactory;
|
||||
import ghidra.net.DefaultTrustManagerFactory;
|
||||
import ghidra.util.classfinder.ClassSearcher;
|
||||
|
||||
public class HeadlessBSimApplicationConfiguration extends ApplicationConfiguration {
|
||||
@@ -46,7 +46,7 @@ public class HeadlessBSimApplicationConfiguration extends ApplicationConfigurati
|
||||
for (ResourceFile appRoot : Application.getApplicationRootDirectories()) {
|
||||
File cacertsFile = new File(appRoot.getAbsolutePath(), "cacerts");
|
||||
if (cacertsFile.isFile()) {
|
||||
System.setProperty(ApplicationTrustManagerFactory.GHIDRA_CACERTS_PATH_PROPERTY,
|
||||
System.setProperty(DefaultTrustManagerFactory.GHIDRA_CACERTS_PATH_PROPERTY,
|
||||
cacertsFile.getAbsolutePath());
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -19,10 +19,10 @@ import java.io.*;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import generic.hash.HashUtilities;
|
||||
import generic.lsh.vector.*;
|
||||
import ghidra.features.bsim.query.description.*;
|
||||
import ghidra.features.bsim.query.protocol.SimilarityVectorResult;
|
||||
import ghidra.util.HashUtilities;
|
||||
import ghidra.xml.XmlPullParser;
|
||||
|
||||
public class TestNearestVectorResult extends SimilarityVectorResult {
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -19,10 +19,10 @@ import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.util.Date;
|
||||
|
||||
import generic.hash.HashUtilities;
|
||||
import ghidra.features.bsim.query.description.ExecutableRecord;
|
||||
import ghidra.features.bsim.query.description.FunctionDescription;
|
||||
import ghidra.features.bsim.query.protocol.SimilarityResult;
|
||||
import ghidra.util.HashUtilities;
|
||||
|
||||
public class TestSimilarityResult extends SimilarityResult {
|
||||
// protected static Random random = new Random();
|
||||
|
||||
@@ -401,8 +401,7 @@ public class LibreTranslateStringTranslationService implements StringTranslation
|
||||
// if possible, unwrap the exception that happened inside the future
|
||||
Throwable cause = e.getCause();
|
||||
Msg.error(this, "Error during HTTP request [%s]".formatted(request.uri()), cause);
|
||||
throw (cause instanceof IOException)
|
||||
? (IOException) cause
|
||||
throw (cause instanceof IOException) ? (IOException) cause
|
||||
: new IOException("Error during HTTP request", cause);
|
||||
}
|
||||
finally {
|
||||
|
||||
@@ -19,6 +19,7 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.*;
|
||||
|
||||
import generic.hash.HashUtilities;
|
||||
import ghidra.app.plugin.processors.generic.MemoryBlockDefinition;
|
||||
import ghidra.app.util.Option;
|
||||
import ghidra.app.util.OptionUtils;
|
||||
@@ -36,7 +37,6 @@ import ghidra.program.model.mem.*;
|
||||
import ghidra.program.model.symbol.*;
|
||||
import ghidra.program.util.DefaultLanguageService;
|
||||
import ghidra.program.util.GhidraProgramUtilities;
|
||||
import ghidra.util.HashUtilities;
|
||||
import ghidra.util.MD5Utilities;
|
||||
import ghidra.util.exception.*;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
@@ -31,6 +31,7 @@ import java.util.Map.Entry;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
|
||||
import docking.widgets.OptionDialog;
|
||||
import generic.hash.HashUtilities;
|
||||
import ghidra.app.util.bin.ByteProvider;
|
||||
import ghidra.formats.gfilesystem.annotations.FileSystemInfo;
|
||||
import ghidra.formats.gfilesystem.factory.FileSystemFactoryDependencyException;
|
||||
|
||||
@@ -23,6 +23,7 @@ import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.commons.collections4.map.ReferenceMap;
|
||||
|
||||
import generic.hash.HashUtilities;
|
||||
import ghidra.app.util.bin.*;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -21,7 +21,7 @@ import java.util.List;
|
||||
import generic.jar.ResourceFile;
|
||||
import ghidra.GhidraClassLoader;
|
||||
import ghidra.framework.preferences.Preferences;
|
||||
import ghidra.net.ApplicationTrustManagerFactory;
|
||||
import ghidra.net.DefaultTrustManagerFactory;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.classfinder.ClassSearcher;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
@@ -88,7 +88,7 @@ public class HeadlessGhidraApplicationConfiguration extends ApplicationConfigura
|
||||
for (ResourceFile appRoot : Application.getApplicationRootDirectories()) {
|
||||
File cacertsFile = new File(appRoot.getAbsolutePath(), "cacerts");
|
||||
if (cacertsFile.isFile()) {
|
||||
System.setProperty(ApplicationTrustManagerFactory.GHIDRA_CACERTS_PATH_PROPERTY,
|
||||
System.setProperty(DefaultTrustManagerFactory.GHIDRA_CACERTS_PATH_PROPERTY,
|
||||
cacertsFile.getAbsolutePath());
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import org.apache.commons.text.StringEscapeUtils;
|
||||
|
||||
import com.google.gson.*;
|
||||
|
||||
import generic.hash.HashUtilities;
|
||||
import ghidra.app.util.bin.ByteProvider;
|
||||
import ghidra.app.util.bin.ByteProviderWrapper;
|
||||
import ghidra.formats.gfilesystem.*;
|
||||
@@ -30,7 +31,6 @@ import ghidra.formats.gfilesystem.annotations.FileSystemInfo;
|
||||
import ghidra.formats.gfilesystem.crypto.CryptoSession;
|
||||
import ghidra.formats.gfilesystem.fileinfo.*;
|
||||
import ghidra.framework.generic.auth.Password;
|
||||
import ghidra.util.HashUtilities;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -22,7 +22,7 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import ghidra.util.HashUtilities;
|
||||
import generic.hash.HashUtilities;
|
||||
|
||||
/**
|
||||
* Helper class from providing all the constants required for parsing a CaRT
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -22,9 +22,9 @@ import java.io.IOException;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import generic.hash.HashUtilities;
|
||||
import ghidra.app.util.bin.BinaryReader;
|
||||
import ghidra.app.util.bin.ByteArrayProvider;
|
||||
import ghidra.util.HashUtilities;
|
||||
|
||||
public class CartV1FileTest {
|
||||
CartV1File cartFile;
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -20,6 +20,7 @@ import java.util.*;
|
||||
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
|
||||
import generic.hash.HashUtilities;
|
||||
import generic.jar.ResourceFile;
|
||||
import ghidra.GhidraApplicationLayout;
|
||||
import ghidra.GhidraLaunchable;
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -25,6 +25,7 @@ import javax.security.auth.x500.X500Principal;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import generic.hash.HashUtilities;
|
||||
import ghidra.framework.remote.User;
|
||||
import ghidra.framework.store.local.LocalFileSystem;
|
||||
import ghidra.util.*;
|
||||
|
||||
@@ -45,8 +45,8 @@ import generic.random.SecureRandomFactory;
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.framework.ApplicationConfiguration;
|
||||
import ghidra.framework.remote.*;
|
||||
import ghidra.net.ApplicationKeyManagerFactory;
|
||||
import ghidra.net.SSLContextInitializer;
|
||||
import ghidra.net.DefaultKeyManagerFactory;
|
||||
import ghidra.net.DefaultSSLContextInitializer;
|
||||
import ghidra.server.RepositoryManager;
|
||||
import ghidra.server.UserManager;
|
||||
import ghidra.server.security.*;
|
||||
@@ -731,7 +731,7 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
|
||||
Application.initializeLogging(serverLogFile, serverLogFile);
|
||||
|
||||
// In the absence of module initialization - we must invoke directly
|
||||
SSLContextInitializer.initialize();
|
||||
DefaultSSLContextInitializer.initialize();
|
||||
|
||||
log = LogManager.getLogger(GhidraServer.class); // init log *after* initializing log system
|
||||
|
||||
@@ -752,13 +752,12 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
|
||||
// Ensure that remote access hostname is properly set for RMI registration
|
||||
String hostname = initRemoteAccessHostname();
|
||||
|
||||
if (ApplicationKeyManagerFactory.getPreferredKeyStore() == null) {
|
||||
if (DefaultKeyManagerFactory.getPreferredKeyStore() == null) {
|
||||
// keystore has not been identified - use self-signed certificate
|
||||
ApplicationKeyManagerFactory
|
||||
.setDefaultIdentity(new X500Principal("CN=GhidraServer"));
|
||||
ApplicationKeyManagerFactory.addSubjectAlternativeName(hostname);
|
||||
DefaultKeyManagerFactory.setDefaultIdentity(new X500Principal("CN=GhidraServer"));
|
||||
DefaultKeyManagerFactory.addSubjectAlternativeName(hostname);
|
||||
}
|
||||
if (!ApplicationKeyManagerFactory.initialize()) {
|
||||
if (!DefaultKeyManagerFactory.initialize()) {
|
||||
log.fatal("Failed to initialize PKI/SSL keystore");
|
||||
System.exit(0);
|
||||
return;
|
||||
|
||||
@@ -50,7 +50,7 @@ public class PKIAuthenticationModule implements AuthenticationModule {
|
||||
public PKIAuthenticationModule(boolean anonymousAllowed)
|
||||
throws IOException, CertificateException {
|
||||
this.anonymousAllowed = anonymousAllowed;
|
||||
authorities = ApplicationKeyManagerUtils.getTrustedIssuers();
|
||||
authorities = DefaultTrustManagerFactory.getTrustedIssuers();
|
||||
if (authorities == null || authorities.length == 0) {
|
||||
throw new IOException("trusted PKI Certificate Authorities have not been configured");
|
||||
}
|
||||
@@ -72,8 +72,8 @@ public class PKIAuthenticationModule implements AuthenticationModule {
|
||||
try {
|
||||
byte[] token = TokenGenerator.getNewToken(TOKEN_SIZE);
|
||||
boolean usingSelfSignedCert =
|
||||
ApplicationKeyManagerFactory.usingGeneratedSelfSignedCertificate();
|
||||
SignedToken signedToken = ApplicationKeyManagerUtils
|
||||
DefaultKeyManagerFactory.usingGeneratedSelfSignedCertificate();
|
||||
SignedToken signedToken = DefaultKeyManagerFactory
|
||||
.getSignedToken(usingSelfSignedCert ? null : authorities, token);
|
||||
sigCb = new SignatureCallback(authorities, token, signedToken.signature);
|
||||
}
|
||||
@@ -127,8 +127,8 @@ public class PKIAuthenticationModule implements AuthenticationModule {
|
||||
}
|
||||
|
||||
boolean usingSelfSignedCert =
|
||||
ApplicationKeyManagerFactory.usingGeneratedSelfSignedCertificate();
|
||||
if (!ApplicationKeyManagerUtils.isMySignature(usingSelfSignedCert ? null : authorities,
|
||||
DefaultKeyManagerFactory.usingGeneratedSelfSignedCertificate();
|
||||
if (!DefaultKeyManagerFactory.isMySignature(usingSelfSignedCert ? null : authorities,
|
||||
token, sigCb.getServerSignature())) {
|
||||
throw new FailedLoginException("Invalid Signature callback");
|
||||
}
|
||||
@@ -138,8 +138,7 @@ public class PKIAuthenticationModule implements AuthenticationModule {
|
||||
throw new FailedLoginException("user certificate not provided");
|
||||
}
|
||||
|
||||
ApplicationKeyManagerUtils.validateClient(certChain,
|
||||
ApplicationKeyManagerUtils.RSA_TYPE);
|
||||
DefaultTrustManagerFactory.validateClient(certChain, PKIUtils.RSA_TYPE);
|
||||
|
||||
byte[] sigBytes = sigCb.getSignature();
|
||||
if (sigBytes != null) {
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -75,9 +75,9 @@ public class SSHAuthenticationModule {
|
||||
byte[] token = TokenGenerator.getNewToken(TOKEN_SIZE);
|
||||
try {
|
||||
boolean usingSelfSignedCert =
|
||||
ApplicationKeyManagerFactory.usingGeneratedSelfSignedCertificate();
|
||||
SignedToken signedToken = ApplicationKeyManagerUtils.getSignedToken(
|
||||
usingSelfSignedCert ? null : ApplicationKeyManagerUtils.getTrustedIssuers(), token);
|
||||
DefaultKeyManagerFactory.usingGeneratedSelfSignedCertificate();
|
||||
SignedToken signedToken = DefaultKeyManagerFactory.getSignedToken(
|
||||
usingSelfSignedCert ? null : DefaultTrustManagerFactory.getTrustedIssuers(), token);
|
||||
list.add(new SSHSignatureCallback(token, signedToken.signature));
|
||||
}
|
||||
catch (Exception e) {
|
||||
@@ -88,9 +88,9 @@ public class SSHAuthenticationModule {
|
||||
|
||||
public boolean hasSignedSSHCallback(Callback[] callbacks) {
|
||||
if (callbacks != null) {
|
||||
for (int i = 0; i < callbacks.length; i++) {
|
||||
if (callbacks[i] instanceof SSHSignatureCallback) {
|
||||
SSHSignatureCallback sshCb = (SSHSignatureCallback) callbacks[i];
|
||||
for (Callback callback : callbacks) {
|
||||
if (callback instanceof SSHSignatureCallback) {
|
||||
SSHSignatureCallback sshCb = (SSHSignatureCallback) callback;
|
||||
return sshCb.isSigned();
|
||||
}
|
||||
}
|
||||
@@ -154,12 +154,12 @@ public class SSHAuthenticationModule {
|
||||
NameCallback nameCb = null;
|
||||
SSHSignatureCallback sshCb = null;
|
||||
if (callbacks != null) {
|
||||
for (int i = 0; i < callbacks.length; i++) {
|
||||
if (callbacks[i] instanceof NameCallback) {
|
||||
nameCb = (NameCallback) callbacks[i];
|
||||
for (Callback callback : callbacks) {
|
||||
if (callback instanceof NameCallback) {
|
||||
nameCb = (NameCallback) callback;
|
||||
}
|
||||
if (callbacks[i] instanceof SSHSignatureCallback) {
|
||||
sshCb = (SSHSignatureCallback) callbacks[i];
|
||||
if (callback instanceof SSHSignatureCallback) {
|
||||
sshCb = (SSHSignatureCallback) callback;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -197,9 +197,9 @@ public class SSHAuthenticationModule {
|
||||
boolean isValid = false;
|
||||
try {
|
||||
boolean usingSelfSignedCert =
|
||||
ApplicationKeyManagerFactory.usingGeneratedSelfSignedCertificate();
|
||||
isValid = ApplicationKeyManagerUtils.isMySignature(
|
||||
usingSelfSignedCert ? null : ApplicationKeyManagerUtils.getTrustedIssuers(), token,
|
||||
DefaultKeyManagerFactory.usingGeneratedSelfSignedCertificate();
|
||||
isValid = DefaultKeyManagerFactory.isMySignature(
|
||||
usingSelfSignedCert ? null : DefaultTrustManagerFactory.getTrustedIssuers(), token,
|
||||
sshCb.getServerSignature());
|
||||
}
|
||||
catch (Exception e) {
|
||||
|
||||
@@ -155,10 +155,10 @@ public class HttpSymbolServer extends AbstractSymbolServer implements MutableTru
|
||||
@Override
|
||||
public boolean exists(String filename, TaskMonitor monitor) {
|
||||
try {
|
||||
HttpRequest request = request(filename)
|
||||
.timeout(Duration.ofMillis(HTTP_REQUEST_TIMEOUT_MS))
|
||||
.method("HEAD", BodyPublishers.noBody())
|
||||
.build();
|
||||
HttpRequest request =
|
||||
request(filename).timeout(Duration.ofMillis(HTTP_REQUEST_TIMEOUT_MS))
|
||||
.method("HEAD", BodyPublishers.noBody())
|
||||
.build();
|
||||
|
||||
Msg.debug(this,
|
||||
logPrefix() + ": Checking exist for [" + filename + "]: " + request.toString());
|
||||
@@ -182,8 +182,7 @@ public class HttpSymbolServer extends AbstractSymbolServer implements MutableTru
|
||||
monitor.setMessage("Connecting to " + serverURI);
|
||||
|
||||
HttpRequest request = request(filename).GET().build();
|
||||
Msg.debug(this,
|
||||
logPrefix() + ": Getting file [" + filename + "]: " + request.toString());
|
||||
Msg.debug(this, logPrefix() + ": Getting file [" + filename + "]: " + request.toString());
|
||||
CompletableFuture<HttpResponse<InputStream>> futureResponse =
|
||||
HttpClients.getHttpClient().sendAsync(request, BodyHandlers.ofInputStream());
|
||||
CancelledListener l = () -> futureResponse.cancel(true);
|
||||
@@ -215,8 +214,7 @@ public class HttpSymbolServer extends AbstractSymbolServer implements MutableTru
|
||||
// if possible, unwrap the exception that happened inside the future
|
||||
Throwable cause = e.getCause();
|
||||
Msg.error(this, "Error during HTTP get", cause);
|
||||
throw (cause instanceof IOException)
|
||||
? (IOException) cause
|
||||
throw (cause instanceof IOException) ? (IOException) cause
|
||||
: new IOException("Error during HTTP get", cause);
|
||||
}
|
||||
finally {
|
||||
|
||||
@@ -26,6 +26,7 @@ import java.util.Hashtable;
|
||||
import javax.security.auth.callback.*;
|
||||
import javax.security.auth.login.LoginException;
|
||||
|
||||
import generic.hash.HashUtilities;
|
||||
import ghidra.framework.model.ServerInfo;
|
||||
import ghidra.framework.remote.*;
|
||||
import ghidra.framework.remote.security.SSHKeyManager;
|
||||
@@ -444,7 +445,7 @@ public class ClientUtil {
|
||||
static void processSignatureCallback(String serverName, SignatureCallback sigCb)
|
||||
throws IOException {
|
||||
try {
|
||||
SignedToken signedToken = ApplicationKeyManagerUtils
|
||||
SignedToken signedToken = DefaultKeyManagerFactory
|
||||
.getSignedToken(sigCb.getRecognizedAuthorities(), sigCb.getToken());
|
||||
sigCb.sign(signedToken.certChain, signedToken.signature);
|
||||
Msg.info(ClientUtil.class, "PKI Authenticating to " + serverName + " as user '" +
|
||||
|
||||
@@ -27,7 +27,7 @@ import org.apache.commons.lang3.StringUtils;
|
||||
import ghidra.framework.remote.AnonymousCallback;
|
||||
import ghidra.framework.remote.SSHSignatureCallback;
|
||||
import ghidra.framework.remote.security.SSHKeyManager;
|
||||
import ghidra.net.ApplicationKeyManagerFactory;
|
||||
import ghidra.net.DefaultKeyManagerFactory;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
@@ -169,7 +169,7 @@ public class HeadlessClientAuthenticator implements ClientAuthenticator {
|
||||
}
|
||||
catch (InvalidKeyException e) { // keyfile is not a valid SSH private key format
|
||||
// does not appear to be an SSH private key - try PKI keystore parse
|
||||
if (ApplicationKeyManagerFactory.setKeyStore(keystorePath, false)) {
|
||||
if (DefaultKeyManagerFactory.setDefaultKeyStore(keystorePath, false)) {
|
||||
success = true;
|
||||
Msg.info(HeadlessClientAuthenticator.class,
|
||||
"Loaded PKI keystore: " + keystorePath);
|
||||
|
||||
@@ -23,7 +23,7 @@ import javax.security.auth.callback.*;
|
||||
|
||||
import ghidra.framework.remote.AnonymousCallback;
|
||||
import ghidra.framework.remote.SSHSignatureCallback;
|
||||
import ghidra.net.ApplicationKeyManagerFactory;
|
||||
import ghidra.net.DefaultKeyManagerFactory;
|
||||
|
||||
/**
|
||||
* <code>PasswordClientAuthenticator</code> provides a fixed username/password
|
||||
@@ -38,7 +38,7 @@ import ghidra.net.ApplicationKeyManagerFactory;
|
||||
* for accessing SSH keys or server password authentication. In such headless situations,
|
||||
* the PKI certificate path/password should be specified via a property since it is unlikely
|
||||
* that the same password will apply.
|
||||
* @see ApplicationKeyManagerFactory
|
||||
* @see DefaultKeyManagerFactory
|
||||
*/
|
||||
public class PasswordClientAuthenticator implements ClientAuthenticator {
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -486,7 +486,7 @@ public class RepositoryServerAdapter {
|
||||
* @throws IOException if user data can't be written to file
|
||||
* @throws NotConnectedException if server connection is down (user already informed)
|
||||
* @see ghidra.framework.remote.RemoteRepositoryServerHandle#setPassword(char[])
|
||||
* @see ghidra.util.HashUtilities#getSaltedHash(String, char[]) HashUtilities.getSaltedHash("SHA-256", char[])
|
||||
* @see generic.hash.HashUtilities#getSaltedHash(String, char[]) HashUtilities.getSaltedHash("SHA-256", char[])
|
||||
*/
|
||||
public synchronized boolean setPassword(char[] saltedSHA256PasswordHash)
|
||||
throws IOException, NotConnectedException {
|
||||
|
||||
@@ -38,7 +38,7 @@ import javax.security.auth.login.LoginException;
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.framework.model.ServerInfo;
|
||||
import ghidra.framework.remote.*;
|
||||
import ghidra.net.ApplicationKeyManagerFactory;
|
||||
import ghidra.net.DefaultKeyManagerFactory;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.*;
|
||||
@@ -130,7 +130,7 @@ class ServerConnectTask extends Task {
|
||||
|
||||
private static boolean isSSLHandshakeCancelled(SSLHandshakeException e) throws IOException {
|
||||
if (e.getMessage().indexOf("bad_certificate") > 0) {
|
||||
if (ApplicationKeyManagerFactory.getPreferredKeyStore() == null) {
|
||||
if (DefaultKeyManagerFactory.getPreferredKeyStore() == null) {
|
||||
throw new IOException("User PKI Certificate not installed", e);
|
||||
}
|
||||
// assume user cancelled connect attempt when prompted for cert password
|
||||
@@ -291,13 +291,12 @@ class ServerConnectTask extends Task {
|
||||
// if anonymous access allowed, let server validate certificate
|
||||
// first and assume anonymous access if user unknown but cert is valid
|
||||
|
||||
if (!ApplicationKeyManagerFactory.initialize()) {
|
||||
if (!DefaultKeyManagerFactory.initialize()) {
|
||||
throw new IOException(
|
||||
"Client PKI certificate has not been installed");
|
||||
}
|
||||
|
||||
if (ApplicationKeyManagerFactory
|
||||
.usingGeneratedSelfSignedCertificate()) {
|
||||
if (DefaultKeyManagerFactory.usingGeneratedSelfSignedCertificate()) {
|
||||
Msg.warn(this,
|
||||
"Server connect - client is using self-signed PKI certificate");
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -104,7 +104,7 @@ public interface RepositoryServerHandle {
|
||||
* @param saltedSHA256PasswordHash SHA256 salted password hash
|
||||
* @return true if password changed
|
||||
* @throws IOException if an IO error occurs
|
||||
* @see ghidra.util.HashUtilities#getSaltedHash(String, char[]) HashUtilities.getSaltedHash("SHA-256", char[])
|
||||
* @see generic.hash.HashUtilities#getSaltedHash(String, char[]) HashUtilities.getSaltedHash("SHA-256", char[])
|
||||
*/
|
||||
boolean setPassword(char[] saltedSHA256PasswordHash) throws IOException;
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -18,6 +18,8 @@ package ghidra.util;
|
||||
import java.io.*;
|
||||
import java.util.List;
|
||||
|
||||
import generic.hash.HashUtilities;
|
||||
|
||||
public class MD5Utilities {
|
||||
|
||||
public static final int SALT_LENGTH = HashUtilities.SALT_LENGTH;
|
||||
@@ -54,11 +56,7 @@ public class MD5Utilities {
|
||||
* returned as a prefix to the hash
|
||||
*/
|
||||
public static char[] getSaltedMD5Hash(char[] msg) {
|
||||
char[] salt = new char[HashUtilities.SALT_LENGTH];
|
||||
for (int i = 0; i < salt.length; i++) {
|
||||
salt[i] = HashUtilities.getRandomLetterOrDigit();
|
||||
}
|
||||
return getSaltedMD5Hash(salt, msg);
|
||||
return HashUtilities.getSaltedHash(HashUtilities.MD5_ALGORITHM, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,16 +4,16 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.util;
|
||||
package generic.hash;
|
||||
|
||||
import java.io.*;
|
||||
import java.security.MessageDigest;
|
||||
@@ -22,6 +22,7 @@ import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import generic.random.SecureRandomFactory;
|
||||
import ghidra.util.NumericUtilities;
|
||||
|
||||
public class HashUtilities {
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -15,74 +15,32 @@
|
||||
*/
|
||||
package ghidra.net;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.net.Socket;
|
||||
import java.io.*;
|
||||
import java.security.*;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.*;
|
||||
|
||||
import javax.net.ssl.*;
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import ghidra.framework.preferences.Preferences;
|
||||
import generic.hash.HashUtilities;
|
||||
import ghidra.security.KeyStorePasswordProvider;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.SystemUtilities;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
|
||||
/**
|
||||
* <code>ApplicationKeyManagerFactory</code> provides application keystore management
|
||||
* functionality and the ability to generate X509KeyManager's for use with an SSLContext
|
||||
* or other PKI related operations. Access to keystore data (other than keystore path)
|
||||
* is restricted to package access. Certain public operations are exposed via the
|
||||
* {@link ApplicationKeyManagerUtils} class.
|
||||
* {@link ApplicationKeyManagerFactory} provides a factory for using and caching X509 keystores.
|
||||
*/
|
||||
public class ApplicationKeyManagerFactory {
|
||||
|
||||
/**
|
||||
* Keystore path system property or user preference. Setting the system
|
||||
* property will take precedence over the user preference.
|
||||
*/
|
||||
public static final String KEYSTORE_PATH_PROPERTY = "ghidra.keystore";
|
||||
|
||||
/**
|
||||
* Password system property may be set. If set, this password will be used
|
||||
* when accessing the keystore before attempting to use <code>customPasswordProvider</code>
|
||||
* if it has been set.
|
||||
*/
|
||||
public static final String KEYSTORE_PASSWORD_PROPERTY = "ghidra.password";
|
||||
|
||||
public static final String DEFAULT_PASSWORD = "changeme";
|
||||
|
||||
private static final int SELF_SIGNED_DURATION_DAYS = 2 * 365; // 2-years
|
||||
|
||||
private static KeyStorePasswordProvider customPasswordProvider;
|
||||
private static X500Principal defaultIdentity;
|
||||
private static List<String> subjectAlternativeNames;
|
||||
|
||||
private static ApplicationKeyManagerFactory instance;
|
||||
// X509KeyManager cached keyed by file hash concatenated with its canonical path
|
||||
private static HashMap<String, X509KeyManager> keyStoreCache = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Get ApplicationKeyManager singleton
|
||||
* @return application X509KeyManager
|
||||
*/
|
||||
static synchronized ApplicationKeyManagerFactory getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new ApplicationKeyManagerFactory();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* get the single key manager instance associated with the factory.
|
||||
* @return key manager instance
|
||||
*/
|
||||
private static ApplicationKeyManager getKeyManagerWrapper() {
|
||||
return getInstance().keyManagerWrapper;
|
||||
private ApplicationKeyManagerFactory() {
|
||||
// no construct
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -94,220 +52,49 @@ public class ApplicationKeyManagerFactory {
|
||||
}
|
||||
|
||||
/**
|
||||
* Prune path to trim leading and trailing white space. A null will be
|
||||
* returned if the pruned path is null or the empty string.
|
||||
*
|
||||
* @param path
|
||||
* @return pruned path or null if path was null or pruned path was the empty
|
||||
* string
|
||||
* Clear all cached key managers.
|
||||
* NOTE: This is primarily intended for test use only.
|
||||
*/
|
||||
private static String prunePath(String path) {
|
||||
if (path != null) {
|
||||
path = path.trim();
|
||||
if (path.length() == 0) {
|
||||
path = null;
|
||||
}
|
||||
}
|
||||
return path;
|
||||
public static synchronized void clearKeyManagerCache() {
|
||||
keyStoreCache.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set user keystore file path (e.g., certificate file with private key).
|
||||
* This method will have no effect if the keystore had been set via the system
|
||||
* property and an error will be displayed. Otherwise, the keystore will
|
||||
* be updated and the key manager re-initialized. The user preference will be
|
||||
* updated unless a failure occurred while attempting to open the keystore.
|
||||
* This change will take immediate effect for the current executing application,
|
||||
* however, it may still be superseded by a system property setting when running
|
||||
* the application in the future. See {@link #getKeyStore()}.
|
||||
* @param path keystore file path or null to clear current key store and preference.
|
||||
* @param savePreference if true will be saved as user preference
|
||||
* @return true if successful else false if error occured (see log).
|
||||
*/
|
||||
public static synchronized boolean setKeyStore(String path, boolean savePreference) {
|
||||
|
||||
if (System.getProperty(KEYSTORE_PATH_PROPERTY) != null) {
|
||||
Msg.showError(ApplicationKeyManagerFactory.class, null, "Set KeyStore Failed",
|
||||
"PKI KeyStore was set via system property and can not be changed");
|
||||
return false;
|
||||
}
|
||||
|
||||
path = prunePath(path);
|
||||
|
||||
try {
|
||||
boolean keyInitialized = getKeyManagerWrapper().init(path);
|
||||
|
||||
if (savePreference && (path == null || keyInitialized)) {
|
||||
Preferences.setProperty(KEYSTORE_PATH_PROPERTY, path);
|
||||
Preferences.store();
|
||||
}
|
||||
return keyInitialized;
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
// ignore - keystore left unchanged
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the keystore path associated with the active key manager or the
|
||||
* preferred keystore path if not yet initialized.
|
||||
*/
|
||||
public static synchronized String getKeyStore() {
|
||||
return getKeyManagerWrapper().getKeyStore();
|
||||
}
|
||||
|
||||
/**
|
||||
* If the system property <i>ghidra.keystore</i> takes precedence in establishing
|
||||
* the keystore. If using a GUI and the system property has not been set, the
|
||||
* user preference with the same name will be used.
|
||||
* @return active keystore path or null if currently not running with a keystore or
|
||||
* one has not been set.
|
||||
*/
|
||||
public static synchronized String getPreferredKeyStore() {
|
||||
String path = prunePath(System.getProperty(KEYSTORE_PATH_PROPERTY));
|
||||
if (path == null && !SystemUtilities.isInHeadlessMode()) {
|
||||
path = prunePath(Preferences.getProperty(KEYSTORE_PATH_PROPERTY));
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if active key manager is utilizing a generated self-signed certificate.
|
||||
* @return true if using self-signed certificate.
|
||||
*/
|
||||
public static synchronized boolean usingGeneratedSelfSignedCertificate() {
|
||||
return getKeyManagerWrapper().usingGeneratedSelfSignedCertificate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default self-signed principal identity to be used during initialization
|
||||
* if no keystore defined. Current application key manager will be invalidated.
|
||||
* (NOTE: this is intended for server use only when client will not be performing
|
||||
* CA validation).
|
||||
* @param identity if not null and a KeyStore path has not be set, this
|
||||
* identity will be used to generate a self-signed certificate and private key
|
||||
*/
|
||||
public synchronized static void setDefaultIdentity(X500Principal identity) {
|
||||
defaultIdentity = identity;
|
||||
getKeyManagerWrapper().invalidateKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the optional self-signed subject alternative name to be used during initialization
|
||||
* if no keystore defined. Current application key manager will be invalidated.
|
||||
* (NOTE: this is intended for server use only when client will not be performing
|
||||
* CA validation).
|
||||
* @param subjectAltName name to be added to the current list of alternative subject names.
|
||||
* A null value will clear all names currently set.
|
||||
* name will be used to generate a self-signed certificate and private key
|
||||
*/
|
||||
public synchronized static void addSubjectAlternativeName(String subjectAltName) {
|
||||
if (subjectAltName == null) {
|
||||
subjectAlternativeNames = null;
|
||||
}
|
||||
else {
|
||||
if (subjectAlternativeNames == null) {
|
||||
subjectAlternativeNames = new ArrayList<>();
|
||||
}
|
||||
subjectAlternativeNames.add(subjectAltName);
|
||||
}
|
||||
getKeyManagerWrapper().invalidateKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current list of subject alternative names to be used for a self-signed certificate
|
||||
* if no keystore defined.
|
||||
* @return list of subject alternative names to be used for a self-signed certificate
|
||||
* if no keystore defined.
|
||||
*/
|
||||
public synchronized static List<String> getSubjectAlternativeName() {
|
||||
return Collections.unmodifiableList(subjectAlternativeNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize key manager if needed. Doing this explicitly independent of an SSL connection
|
||||
* allows application to bail before initiating connection. This will get handshake failure
|
||||
* if user forgets keystore password or other keystore problem.
|
||||
* @return true if key manager initialized, otherwise false
|
||||
*/
|
||||
public synchronized static boolean initialize() {
|
||||
try {
|
||||
return getKeyManagerWrapper().init();
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate the key managers associated with this factory
|
||||
*/
|
||||
public synchronized static void invalidateKeyManagers() {
|
||||
getKeyManagerWrapper().invalidateKey();
|
||||
}
|
||||
|
||||
// Factory maintains a single X509 key manager
|
||||
private ApplicationKeyManager keyManagerWrapper = new ApplicationKeyManager();
|
||||
|
||||
/**
|
||||
* <code>ApplicationKeyManagerFactory</code> constructor
|
||||
*/
|
||||
private ApplicationKeyManagerFactory() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get key managers
|
||||
* @return key managers
|
||||
*/
|
||||
KeyManager[] getKeyManagers() {
|
||||
return new KeyManager[] { keyManagerWrapper };
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>ProtectedKeyStoreData</code> provides a container for a keystore
|
||||
* which has been successfully accessed using the specified password.
|
||||
*/
|
||||
private static class ProtectedKeyStoreData {
|
||||
KeyStore keyStore;
|
||||
char[] password;
|
||||
|
||||
ProtectedKeyStoreData(KeyStore keyStore, char[] password) {
|
||||
this.keyStore = keyStore;
|
||||
this.password = password != null ? password.clone() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispose this keystore data wrapper and the retained password
|
||||
*/
|
||||
void dispose() {
|
||||
if (password != null) {
|
||||
Arrays.fill(password, ' ');
|
||||
}
|
||||
keyStore = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
dispose();
|
||||
super.finalize();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get protected keystore data for specified keystorePath. Caller is responsible for
|
||||
* properly disposing returned object.
|
||||
* Get key manager for specified JKS or PKCS12 keystore file path. The user may be prompted
|
||||
* for a password if required which will block the invocation of this synchronized method.
|
||||
* If successfully opened, the resulting key manager instance will be cached for subsequent
|
||||
* re-use of the same keystore.
|
||||
*
|
||||
* @param keystorePath protected keystore path
|
||||
* @return protected keystore data
|
||||
* @param defaultPasswd default password (e.g., supplied by property) or null
|
||||
* @return key manager
|
||||
* @throws CancelledException password entry was cancelled by user
|
||||
* @throws KeyStoreException error occurred opening/processing keystore
|
||||
*/
|
||||
private static ProtectedKeyStoreData getProtectedKeyStoreData(String keystorePath)
|
||||
throws CancelledException, KeyStoreException {
|
||||
public synchronized static X509KeyManager getKeyManager(String keystorePath,
|
||||
String defaultPasswd) throws CancelledException, KeyStoreException {
|
||||
|
||||
Msg.info(ApplicationKeyManagerFactory.class, "Using certificate keystore: " + keystorePath);
|
||||
String hashKey;
|
||||
|
||||
String keystorePwd = System.getProperty(KEYSTORE_PASSWORD_PROPERTY);
|
||||
try {
|
||||
if (PKIUtils.detectKeyStoreType(keystorePath) == null) {
|
||||
throw new KeyStoreException("Unsupported PKI key store file type: " + keystorePath);
|
||||
}
|
||||
// Obtain canonical path for cache use
|
||||
File keystoreFile = new File(keystorePath);
|
||||
keystorePath = (keystoreFile).getCanonicalPath();
|
||||
|
||||
hashKey = HashUtilities.getHash(HashUtilities.MD5_ALGORITHM, keystoreFile) + "_" +
|
||||
keystorePath;
|
||||
|
||||
X509KeyManager keyManager = keyStoreCache.get(hashKey);
|
||||
if (keyManager != null) {
|
||||
return keyManager;
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new KeyStoreException("Failed to examine keystore: " + keystorePath, e);
|
||||
}
|
||||
|
||||
int tryCount = 0;
|
||||
|
||||
@@ -319,20 +106,12 @@ public class ApplicationKeyManagerFactory {
|
||||
// try no password first
|
||||
}
|
||||
else if (tryCount == 1) {
|
||||
// try default password
|
||||
Msg.debug(ApplicationKeyManagerFactory.class,
|
||||
"Attempting to load keystore without password...");
|
||||
password = DEFAULT_PASSWORD.toCharArray();
|
||||
}
|
||||
else if (tryCount == 2) {
|
||||
// try specified password
|
||||
if (keystorePwd != null) {
|
||||
Msg.debug(ApplicationKeyManagerFactory.class,
|
||||
"Attempting to load keystore with property-based password...");
|
||||
password = keystorePwd.toCharArray();
|
||||
if (defaultPasswd != null) {
|
||||
password = defaultPasswd.toCharArray();
|
||||
}
|
||||
else {
|
||||
// no system property password provided
|
||||
// no default password was provided
|
||||
++tryCount;
|
||||
continue;
|
||||
}
|
||||
@@ -341,7 +120,7 @@ public class ApplicationKeyManagerFactory {
|
||||
disposePassword(oldPassword);
|
||||
oldPassword = password;
|
||||
password =
|
||||
customPasswordProvider.getKeyStorePassword(keystorePath, tryCount != 3);
|
||||
customPasswordProvider.getKeyStorePassword(keystorePath, tryCount != 2);
|
||||
if (password == null) {
|
||||
throw new CancelledException();
|
||||
}
|
||||
@@ -358,24 +137,24 @@ public class ApplicationKeyManagerFactory {
|
||||
}
|
||||
++tryCount;
|
||||
|
||||
// we only support a single password for both keystore and private key
|
||||
KeyStore keyStore = ApplicationKeyStore.getKeyStoreInstance(keystorePath, password);
|
||||
return new ProtectedKeyStoreData(keyStore, password);
|
||||
// NOTE: we only support a single password for both keystore and private key
|
||||
KeyStore keyStore = PKIUtils.getKeyStoreInstance(keystorePath, password);
|
||||
X509KeyManager keyManager = getKeyManagerFromKeyStore(keyStore, password);
|
||||
keyStoreCache.put(hashKey, keyManager);
|
||||
return keyManager;
|
||||
}
|
||||
catch (NoSuchAlgorithmException | CertificateException | FileNotFoundException e) {
|
||||
catch (UnrecoverableKeyException | NoSuchAlgorithmException | CertificateException
|
||||
| FileNotFoundException e) {
|
||||
throw new KeyStoreException("Failed to process keystore: " + keystorePath, e);
|
||||
}
|
||||
catch (KeyStoreException | IOException e) {
|
||||
// Java Bug: JDK-6974037 : PKCS12 Keystore load with invalid passwords generates different exceptions
|
||||
Exception ioException = getIOException(e);
|
||||
if (ioException != null) {
|
||||
// Assume all IOExceptions are the result of improperly decrypted keystore
|
||||
// since arbitrary and inconsistent errors are produced when attempting to
|
||||
// load an encrypted keystore without a password or with the wrong password.
|
||||
continue;
|
||||
if (ioException != null &&
|
||||
ioException.getCause() instanceof UnrecoverableKeyException) {
|
||||
continue; // Assume incorrect password was specified
|
||||
}
|
||||
throw new KeyStoreException(
|
||||
"Failed to process keystore (" + tryCount + "): " + keystorePath, e);
|
||||
throw new KeyStoreException("Failed to open keystore: " + keystorePath, e);
|
||||
}
|
||||
finally {
|
||||
disposePassword(oldPassword);
|
||||
@@ -385,6 +164,47 @@ public class ApplicationKeyManagerFactory {
|
||||
throw new KeyStoreException("Failed to unlock key storage: " + keystorePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get key manager for specified JKS or PKCS12 keystore. The keystore must only contain a
|
||||
* single X509 certificate and corresponding private key which has a usage of digital signing.
|
||||
* @param keyStore JKS or PKCS12 keystore file
|
||||
* @param password password for accessing keystore and private key.
|
||||
* @return X509 key manager instance
|
||||
* @throws NoSuchAlgorithmException if the specified algorithm is not available from the
|
||||
* specified provider.
|
||||
* @throws UnrecoverableKeyException if the key cannot be recovered
|
||||
* (e.g. the given password is wrong).
|
||||
* @throws KeyStoreException if a general failure occurs while accessing keystore
|
||||
*/
|
||||
static X509KeyManager getKeyManagerFromKeyStore(KeyStore keyStore, char[] password)
|
||||
throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException {
|
||||
|
||||
// Assume cert of interest is the first one
|
||||
Enumeration<String> aliases = keyStore.aliases();
|
||||
if (!aliases.hasMoreElements()) {
|
||||
throw new KeyStoreException("PKI key store is empty");
|
||||
}
|
||||
Certificate certificate = keyStore.getCertificate(aliases.nextElement());
|
||||
if (!(certificate instanceof X509Certificate x509Cert)) {
|
||||
throw new KeyStoreException("PKI X509 Certificate not found");
|
||||
}
|
||||
boolean[] keyUsage = x509Cert.getKeyUsage();
|
||||
if (!keyUsage[0]) {
|
||||
throw new KeyStoreException("PKI key store must contain Digital Signing certificate");
|
||||
}
|
||||
|
||||
KeyManagerFactory kmf =
|
||||
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||
kmf.init(keyStore, password);
|
||||
|
||||
// Assume that one keystore will produce at most one KeyManager
|
||||
KeyManager[] keyManagers = kmf.getKeyManagers();
|
||||
if (keyManagers.length == 1 && (keyManagers[0] instanceof X509KeyManager)) {
|
||||
return (X509KeyManager) keyManagers[0];
|
||||
}
|
||||
throw new KeyStoreException("Unsupported keystore");
|
||||
}
|
||||
|
||||
private static void disposePassword(char[] password) {
|
||||
if (password != null) {
|
||||
Arrays.fill(password, (char) 0);
|
||||
@@ -403,237 +223,4 @@ public class ApplicationKeyManagerFactory {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>ApplicationKeyManager</code> provides a wrapper for the X509 wrappedKeyManager whose
|
||||
* instantiation is delayed until needed. When a wrapper method is first invoked, the
|
||||
* {@link ApplicationKeyManagerFactory#init()} method is called to open the keystore
|
||||
* (which may require a password prompt) and establish the underlying X509KeyManager.
|
||||
*/
|
||||
private class ApplicationKeyManager extends X509ExtendedKeyManager {
|
||||
|
||||
private X509KeyManager wrappedKeyManager;
|
||||
private String keystorePath;
|
||||
private boolean isSelfSigned = false;
|
||||
|
||||
@Override
|
||||
public String chooseEngineServerAlias(String keyType, Principal[] issuers,
|
||||
SSLEngine engine) {
|
||||
return super.chooseEngineServerAlias(keyType, issuers, engine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String chooseEngineClientAlias(String[] keyType, Principal[] issuers,
|
||||
SSLEngine engine) {
|
||||
return super.chooseEngineClientAlias(keyType, issuers, engine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized String chooseClientAlias(String[] keyType, Principal[] issuers,
|
||||
Socket socket) {
|
||||
try {
|
||||
init();
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
// ignore
|
||||
}
|
||||
if (wrappedKeyManager == null) {
|
||||
return null;
|
||||
}
|
||||
return wrappedKeyManager.chooseClientAlias(keyType, issuers, socket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized String chooseServerAlias(String keyType, Principal[] issuers,
|
||||
Socket socket) {
|
||||
try {
|
||||
init();
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
// ignore
|
||||
}
|
||||
if (wrappedKeyManager == null) {
|
||||
return null;
|
||||
}
|
||||
return wrappedKeyManager.chooseServerAlias(keyType, issuers, socket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getClientAliases(String keyType, Principal[] issuers) {
|
||||
try {
|
||||
init();
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
// ignore
|
||||
}
|
||||
if (wrappedKeyManager == null) {
|
||||
return null;
|
||||
}
|
||||
return wrappedKeyManager.getClientAliases(keyType, issuers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getServerAliases(String keyType, Principal[] issuers) {
|
||||
try {
|
||||
init();
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
// ignore
|
||||
}
|
||||
if (wrappedKeyManager == null) {
|
||||
return null;
|
||||
}
|
||||
return wrappedKeyManager.getServerAliases(keyType, issuers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate[] getCertificateChain(String alias) {
|
||||
if (wrappedKeyManager == null) {
|
||||
return null;
|
||||
}
|
||||
return wrappedKeyManager.getCertificateChain(alias);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrivateKey getPrivateKey(String alias) {
|
||||
if (wrappedKeyManager == null) {
|
||||
return null;
|
||||
}
|
||||
return wrappedKeyManager.getPrivateKey(alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate the active keystore and key manager
|
||||
*/
|
||||
private synchronized void invalidateKey() {
|
||||
wrappedKeyManager = null;
|
||||
keystorePath = null;
|
||||
isSelfSigned = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return active keystore path or preferred keystore path if not yet initialized.
|
||||
* @return active keystore path or preferred keystore path if not yet initialized.
|
||||
*/
|
||||
private synchronized String getKeyStore() {
|
||||
return wrappedKeyManager != null ? keystorePath : getPreferredKeyStore();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if active key manager is utilizing a generated self-signed certificate.
|
||||
* @return true if using self-signed certificate.
|
||||
*/
|
||||
private synchronized boolean usingGeneratedSelfSignedCertificate() {
|
||||
return wrappedKeyManager != null && isSelfSigned;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the x509KeyManager associated with the active keystore setting.
|
||||
* If the <code>x509KeyManager</code> already exists, this method has no affect. If the
|
||||
* <code>keystorePath</code> has not already been set, the <code>getPreferredKeyStore()</code>
|
||||
* method will be invoked to obtain the keystore which should be used in establishing the
|
||||
* <code>wrappedKeyManager</code>. If no keystore has been identified and the Default Identity
|
||||
* has been set, a self-signed certificate will be generated. If nothing has been set, the
|
||||
* wrappedKeyManager will remain null and false will be returned. If an error occurs it
|
||||
* will be logged and key managers will remain uninitialized.
|
||||
* @return true if key manager initialized successfully or was previously initialized, else
|
||||
* false if keystore path has not been set and default identity for self-signed certificate
|
||||
* has not be established (see {@link ApplicationKeyManagerFactory#setDefaultIdentity(X500Principal)}).
|
||||
* @throws CancelledException user cancelled keystore password entry request
|
||||
*/
|
||||
private synchronized boolean init() throws CancelledException {
|
||||
if (wrappedKeyManager != null) {
|
||||
return true;
|
||||
}
|
||||
return init(getPreferredKeyStore());
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the x509KeyManager.
|
||||
* If the <code>x509KeyManager</code> already exists for the specified keystore path,
|
||||
* this method has no affect. If no keystore has been identified and the Default Identity
|
||||
* has been set, a self-signed certificate will be generated. If nothing has been set, the
|
||||
* wrappedKeyManager will remain null and false will be returned. If an error occurs it
|
||||
* will be logged and key managers will remain uninitialized.
|
||||
* @param newKeystorePath specifies the keystore to be opened or null for no keystore
|
||||
* @return true if key manager initialized successfully or was previously initialized, else
|
||||
* false if new keystore path was not specified and default identity for self-signed certificate
|
||||
* has not be established (see {@link ApplicationKeyManagerFactory#setDefaultIdentity(X500Principal)}).
|
||||
* @throws CancelledException user cancelled keystore password entry request
|
||||
*/
|
||||
private synchronized boolean init(String newKeystorePath) throws CancelledException {
|
||||
|
||||
if (wrappedKeyManager != null) {
|
||||
if (StringUtils.equals(keystorePath, newKeystorePath)) {
|
||||
return true;
|
||||
}
|
||||
invalidateKey();
|
||||
}
|
||||
|
||||
ProtectedKeyStoreData keystoreData = null;
|
||||
try {
|
||||
if (newKeystorePath != null && newKeystorePath.length() != 0) {
|
||||
keystoreData = getProtectedKeyStoreData(newKeystorePath);
|
||||
}
|
||||
else if (defaultIdentity != null) {
|
||||
// use self-signed keystore as fallback (intended for server use only)
|
||||
Msg.info(this, "Using self-signed certificate: " + defaultIdentity.getName());
|
||||
char[] pwd = DEFAULT_PASSWORD.toCharArray();
|
||||
KeyStore selfSignedKeyStore =
|
||||
ApplicationKeyManagerUtils.createKeyStore("defaultSigKey",
|
||||
defaultIdentity.getName(), SELF_SIGNED_DURATION_DAYS, null, null, "JKS",
|
||||
subjectAlternativeNames, pwd);
|
||||
keystoreData = new ProtectedKeyStoreData(selfSignedKeyStore, pwd);
|
||||
isSelfSigned = true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
|
||||
ApplicationKeyStore.logCerts(keystoreData.keyStore);
|
||||
|
||||
KeyManagerFactory kmf =
|
||||
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||
kmf.init(keystoreData.keyStore, keystoreData.password);
|
||||
|
||||
// Assume that one keystore will produce at most one KeyManager
|
||||
KeyManager[] keyManagers = kmf.getKeyManagers();
|
||||
if (keyManagers.length == 1 && (keyManagers[0] instanceof X509KeyManager)) {
|
||||
wrappedKeyManager = (X509KeyManager) keyManagers[0];
|
||||
keystorePath = newKeystorePath; // update current keystore path
|
||||
return true;
|
||||
}
|
||||
|
||||
isSelfSigned = false;
|
||||
|
||||
if (keyManagers.length == 0) {
|
||||
Msg.showError(this, null, "PKI Keystore Failure",
|
||||
"Failed to create PKI key manager: failed to process keystore (no keys processed)");
|
||||
}
|
||||
else if (keyManagers.length == 1) {
|
||||
Msg.showError(this, null, "PKI Keystore Failure",
|
||||
"Failed to create PKI key manager: failed to process keystore (expected X.509)");
|
||||
}
|
||||
else {
|
||||
// Unexpected condition
|
||||
Msg.showError(this, null, "PKI Keystore Failure",
|
||||
"Failed to create PKI key manager: unsupported keystore produced multiple KeyManagers");
|
||||
}
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
throw e;
|
||||
}
|
||||
catch (Exception e) {
|
||||
Msg.showError(this, null, "PKI Keystore Failure",
|
||||
"Failed to create PKI key manager: " + e.getMessage(), e);
|
||||
}
|
||||
finally {
|
||||
if (keystoreData != null) {
|
||||
keystoreData.dispose();
|
||||
keystoreData = null;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,244 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.net;
|
||||
|
||||
import java.io.*;
|
||||
import java.security.*;
|
||||
import java.security.cert.*;
|
||||
import java.security.cert.Certificate;
|
||||
import java.util.Date;
|
||||
import java.util.Enumeration;
|
||||
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
* <code>ApplicationKeyStore</code> provides the ability to read X.509 certificates and
|
||||
* keystores in various formats. Certificate files (e.g., cacerts) may be in a standard
|
||||
* X.509 form (*.pem, *.crt, *.cer, *.der) or Java JKS (*.jks) form, while keystores
|
||||
* for client/server may be in a PKCS12 form (*.p12, *.pks, *.pfx) or Java JKS (*.jks) form.
|
||||
*/
|
||||
public class ApplicationKeyStore {
|
||||
|
||||
private ApplicationKeyStore() {
|
||||
// no instantiation - static methods only
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the specified X.509 certificate authority store in a standard
|
||||
* X.509 form (*.pem, *.crt, *.cer, *.der) or Java JKS (*.jks) form.
|
||||
* @param cacertsPath certificate store file path
|
||||
* @return KeyStore containing ingested certificates
|
||||
* @throws IOException
|
||||
* @throws KeyStoreException
|
||||
* @throws NoSuchAlgorithmException
|
||||
* @throws CertificateException
|
||||
*/
|
||||
public static KeyStore getCertificateStoreInstance(String cacertsPath)
|
||||
throws IOException, KeyStoreException, NoSuchAlgorithmException, CertificateException {
|
||||
|
||||
int certCount = 0;
|
||||
|
||||
KeyStore store = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||
store.load(null);
|
||||
|
||||
InputStream fis = new FileInputStream(cacertsPath);
|
||||
BufferedInputStream bis = new BufferedInputStream(fis);
|
||||
|
||||
try {
|
||||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||
while (bis.available() > 0) {
|
||||
try {
|
||||
Certificate cert = cf.generateCertificate(bis);
|
||||
if (cert instanceof X509Certificate) {
|
||||
X509Certificate x509Cert = (X509Certificate) cert;
|
||||
String name = getCommonName(x509Cert.getSubjectDN());
|
||||
store.setCertificateEntry(name, cert);
|
||||
++certCount;
|
||||
}
|
||||
}
|
||||
catch (CertificateException e) {
|
||||
// Must handle blank lines at bottom of file
|
||||
Throwable cause = e.getCause();
|
||||
if (cause != null && "Empty input".equals(cause.getMessage())) {
|
||||
break; // end of file
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
bis.close();
|
||||
}
|
||||
if (certCount == 0) {
|
||||
// Processing JKS files above produce "Empty input", if no certs read
|
||||
// try reading as keystore without password
|
||||
return getKeyStoreInstance(cacertsPath, null);
|
||||
}
|
||||
return store;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to load a client/server keystore in a PKCS12 form (*.p12, *.pks, *.pfx) or
|
||||
* Java JKS (*.jks) form.
|
||||
* @param pwd keystore password
|
||||
* @return keystore instance
|
||||
* @throws IOException
|
||||
* @throws KeyStoreException
|
||||
* @throws NoSuchAlgorithmException
|
||||
* @throws CertificateException
|
||||
*/
|
||||
public static KeyStore getKeyStoreInstance(String keystorePath, char[] pwd)
|
||||
throws IOException, KeyStoreException, NoSuchAlgorithmException, CertificateException {
|
||||
|
||||
File keystoreFile = new File(keystorePath);
|
||||
boolean isPKCS12 = ApplicationKeyManagerUtils.PKCS_FILENAME_FILTER.accept(keystoreFile);
|
||||
String type = isPKCS12 ? "PKCS12" : "JKS"; // JKS assumed if not PKCS
|
||||
KeyStore ks = KeyStore.getInstance(type);
|
||||
|
||||
InputStream fis = new FileInputStream(keystorePath);
|
||||
BufferedInputStream bis = new BufferedInputStream(fis);
|
||||
try {
|
||||
ks.load(bis, pwd);
|
||||
}
|
||||
finally {
|
||||
bis.close();
|
||||
}
|
||||
return ks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to detect PKI KeyStore type ("JKS" or "PKCS12") for the specified file.
|
||||
* @param keystorePath key store file path
|
||||
* @return "JKS", "PKCS12" or null
|
||||
* @throws IOException if file read error occurs
|
||||
*/
|
||||
public static String detectKeyStoreType(String keystorePath) throws IOException {
|
||||
try (FileInputStream fis = new FileInputStream(keystorePath)) {
|
||||
byte[] header = new byte[4];
|
||||
int read = fis.read(header);
|
||||
if (read < 4) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check for JKS magic number: FEEDFEED
|
||||
if ((header[0] & 0xFF) == 0xFE && (header[1] & 0xFF) == 0xED &&
|
||||
(header[2] & 0xFF) == 0xFE && (header[3] & 0xFF) == 0xED) {
|
||||
return "JKS";
|
||||
}
|
||||
|
||||
// Check for PKCS12: starts with 0x30 0x82
|
||||
if ((header[0] & 0xFF) == 0x30 && (header[1] & 0xFF) == 0x82) {
|
||||
return "PKCS12";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract Common Name (CN) from specified principal subject Distinguished Name (DN)
|
||||
* @param subject X.509 certificate subject
|
||||
* @return Common Name or full subject name if unable to extract CN from DN
|
||||
*/
|
||||
private static String getCommonName(Principal subject) {
|
||||
|
||||
// Subject name should be distinguished-name (DN) which starts with common-name (CN)
|
||||
String name = subject.getName();
|
||||
int commaIndex = name.indexOf(',');
|
||||
String firstElement = commaIndex < 0 ? name : name.substring(0, commaIndex);
|
||||
|
||||
int equalsIndex = firstElement.indexOf('=');
|
||||
if (equalsIndex <= 0) {
|
||||
return name; // bad common name
|
||||
}
|
||||
|
||||
String fieldName = firstElement.substring(0, equalsIndex).trim();
|
||||
String fieldValue = firstElement.substring(equalsIndex + 1).trim();
|
||||
|
||||
if (!fieldName.equalsIgnoreCase("CN")) {
|
||||
return name; // bad common name
|
||||
}
|
||||
|
||||
return fieldValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log all X509 certificates contained within keystore
|
||||
* @param keyStore certificate keystore
|
||||
*/
|
||||
static void logCerts(KeyStore keyStore) {
|
||||
try {
|
||||
Enumeration<String> aliases = keyStore.aliases();
|
||||
while (aliases.hasMoreElements()) {
|
||||
String alias = aliases.nextElement();
|
||||
Certificate certificate = keyStore.getCertificate(alias);
|
||||
if (certificate == null) {
|
||||
continue;
|
||||
}
|
||||
else if (certificate instanceof X509Certificate) {
|
||||
logCert(alias, (X509Certificate) certificate);
|
||||
}
|
||||
else {
|
||||
Msg.warn(ApplicationKeyStore.class, "Ignore unrecognized certificate: alias=" +
|
||||
alias + ", type=" + certificate.getType());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (KeyStoreException e) {
|
||||
Msg.error(ApplicationKeyStore.class, "KeyStore failure", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log all X509 certificates contained within array
|
||||
* @param x509Certs array of certificates
|
||||
*/
|
||||
public static void logCerts(X509Certificate[] x509Certs) {
|
||||
for (X509Certificate x509Cert : x509Certs) {
|
||||
logCert(null, x509Cert);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log specified X509 certificate details
|
||||
* @param alias certificate alias or null if not applicable
|
||||
* @param x509Cert X509 certificate
|
||||
*/
|
||||
static void logCert(String alias, X509Certificate x509Cert) {
|
||||
|
||||
X500Principal subj = x509Cert.getSubjectX500Principal();
|
||||
X500Principal issuer = x509Cert.getIssuerX500Principal();
|
||||
|
||||
Date now = new Date();
|
||||
|
||||
String label = alias != null ? (alias + ": ") : "";
|
||||
if (now.compareTo(x509Cert.getNotAfter()) > 0) {
|
||||
Msg.warn(ApplicationKeyStore.class,
|
||||
" " + label + getCommonName(subj) + ", issued by " + getCommonName(issuer) +
|
||||
", S/N " + x509Cert.getSerialNumber().toString(16) + ", expired " +
|
||||
x509Cert.getNotAfter() + " **EXPIRED**");
|
||||
}
|
||||
else {
|
||||
Msg.info(ApplicationKeyStore.class,
|
||||
" " + label + getCommonName(subj) + ", issued by " + getCommonName(issuer) +
|
||||
", S/N " + x509Cert.getSerialNumber().toString(16) + ", expires " +
|
||||
x509Cert.getNotAfter());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.net;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.*;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
* <code>ApplicationSSLSocketFactory</code> provides a replacement for the default
|
||||
* <code>SSLSocketFactory</code> which utilizes the default SSLContext established
|
||||
* by {@link SSLContextInitializer}.
|
||||
*/
|
||||
public class ApplicationSSLSocketFactory extends SSLSocketFactory {
|
||||
|
||||
private final SSLSocketFactory socketFactory;
|
||||
|
||||
/**
|
||||
* ApplicationSSLSocketFactory constructor.
|
||||
* SSLContext initialization will be performed using {@link SSLContextInitializer}.
|
||||
*/
|
||||
public ApplicationSSLSocketFactory() {
|
||||
SSLSocketFactory factory = null;
|
||||
try {
|
||||
if (SSLContextInitializer.initialize()) {
|
||||
factory = SSLContext.getDefault().getSocketFactory();
|
||||
}
|
||||
}
|
||||
catch (NoSuchAlgorithmException e) {
|
||||
Msg.error(this, "Failed to employ default SSLContext: " + e.toString(), e);
|
||||
}
|
||||
this.socketFactory =
|
||||
factory != null ? factory : (SSLSocketFactory) SSLSocketFactory.getDefault();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(Socket s, String host, int port, boolean autoClose)
|
||||
throws IOException {
|
||||
return socketFactory.createSocket(s, host, port, autoClose);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getDefaultCipherSuites() {
|
||||
return socketFactory.getDefaultCipherSuites();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getSupportedCipherSuites() {
|
||||
return socketFactory.getSupportedCipherSuites();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
|
||||
return socketFactory.createSocket(host, port);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(InetAddress host, int port) throws IOException {
|
||||
return socketFactory.createSocket(host, port);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(String host, int port, InetAddress localHost, int localPort)
|
||||
throws IOException, UnknownHostException {
|
||||
return socketFactory.createSocket(host, port, localHost, localPort);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(InetAddress address, int port, InetAddress localAddress,
|
||||
int localPort)
|
||||
throws IOException {
|
||||
return socketFactory.createSocket(address, port, localAddress, localPort);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,512 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.net;
|
||||
|
||||
import java.net.Socket;
|
||||
import java.security.*;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.*;
|
||||
|
||||
import javax.net.ssl.*;
|
||||
import javax.rmi.ssl.SslRMIClientSocketFactory;
|
||||
import javax.security.auth.DestroyFailedException;
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import ghidra.framework.preferences.Preferences;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.SystemUtilities;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
|
||||
/**
|
||||
* {@link DefaultKeyManagerFactory} provides access to the default application key manager
|
||||
* associated with the preferred keystore file specified by the {@link #KEYSTORE_PATH_PROPERTY}
|
||||
* system property or set with {@link #setDefaultKeyStore(String, boolean)}.
|
||||
* <p>
|
||||
* <p>
|
||||
* NOTE: Since {@link SslRMIClientSocketFactory} and {@link SSLServerSocketFactory} employ a
|
||||
* static cache of a default {@link SSLSocketFactory}, with its default {@link SSLContext}, we
|
||||
* must utilize a wrapped implementation of the associated {@link X509ExtendedKeyManager} so that
|
||||
* an updated keystore is used by the existing default {@link SSLSocketFactory}.
|
||||
*/
|
||||
public class DefaultKeyManagerFactory {
|
||||
|
||||
/**
|
||||
* Keystore path system property or user preference. Setting the system
|
||||
* property will take precedence over the user preference.
|
||||
*/
|
||||
public static final String KEYSTORE_PATH_PROPERTY = "ghidra.keystore";
|
||||
|
||||
/**
|
||||
* Password system property may be set. If set, this password will be used
|
||||
* when accessing the keystore before attempting to use <code>customPasswordProvider</code>
|
||||
* if it has been set.
|
||||
*/
|
||||
public static final String KEYSTORE_PASSWORD_PROPERTY = "ghidra.password";
|
||||
|
||||
public static final String DEFAULT_PASSWORD = "changeme";
|
||||
|
||||
private static final int SELF_SIGNED_DURATION_DAYS = 2 * 365; // 2-years
|
||||
|
||||
// Certificate info when self-signed cert is used
|
||||
private static X500Principal defaultIdentity;
|
||||
private static List<String> defaultSubjectAlternativeNames;
|
||||
|
||||
// Factory maintains a single X509 key manager
|
||||
private static final DefaultX509KeyManager keyManagerWrapper = new DefaultX509KeyManager();
|
||||
|
||||
/**
|
||||
* Prune path to trim leading and trailing white space. A null will be
|
||||
* returned if the pruned path is null or the empty string.
|
||||
*
|
||||
* @param path file path to be pruned (may be null)
|
||||
* @return pruned path or null if path was null or pruned path was the empty
|
||||
* string
|
||||
*/
|
||||
private static String prunePath(String path) {
|
||||
if (path != null) {
|
||||
path = path.trim();
|
||||
if (path.length() == 0) {
|
||||
path = null;
|
||||
}
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set default user keystore file path (e.g., certificate file with private key).
|
||||
* This method will have no effect if the keystore had been set via the system
|
||||
* property and an error will be displayed. Otherwise, the keystore will
|
||||
* be updated and the key manager re-initialized. The user preference will be
|
||||
* updated unless a failure occurred while attempting to open the keystore.
|
||||
* This change will take immediate effect for the current executing application,
|
||||
* however, it may still be superseded by a system property setting when running
|
||||
* the application in the future. See {@link #getKeyStore()}.
|
||||
*
|
||||
* @param path keystore file path or null to clear current key store and preference.
|
||||
* @param savePreference if true will be saved as user preference
|
||||
* @return true if successful else false if error occured (see log).
|
||||
*/
|
||||
public static synchronized boolean setDefaultKeyStore(String path, boolean savePreference) {
|
||||
|
||||
if (System.getProperty(KEYSTORE_PATH_PROPERTY) != null) {
|
||||
Msg.showError(DefaultKeyManagerFactory.class, null, "Set KeyStore Failed",
|
||||
"PKI KeyStore was set via system property and can not be changed");
|
||||
return false;
|
||||
}
|
||||
|
||||
path = prunePath(path);
|
||||
|
||||
try {
|
||||
boolean keyInitialized = keyManagerWrapper.init(path);
|
||||
|
||||
if (savePreference && (path == null || keyInitialized)) {
|
||||
Preferences.setProperty(KEYSTORE_PATH_PROPERTY, path);
|
||||
Preferences.store();
|
||||
}
|
||||
return keyInitialized;
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
// ignore - keystore left unchanged
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if active key manager is utilizing a generated self-signed certificate.
|
||||
*
|
||||
* @return true if using self-signed certificate.
|
||||
*/
|
||||
public static synchronized boolean usingGeneratedSelfSignedCertificate() {
|
||||
return keyManagerWrapper.usingGeneratedSelfSignedCertificate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default self-signed principal identity to be used during initialization
|
||||
* if no keystore defined. Current application key manager will be invalidated.
|
||||
* (NOTE: this is intended for server use only when client will not be performing
|
||||
* CA validation).
|
||||
*
|
||||
* @param identity if not null and a KeyStore path has not be set, this
|
||||
* identity will be used to generate a self-signed certificate and private key
|
||||
*/
|
||||
public synchronized static void setDefaultIdentity(X500Principal identity) {
|
||||
defaultIdentity = identity;
|
||||
keyManagerWrapper.invalidateKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the optional self-signed subject alternative name to be used during initialization
|
||||
* if no keystore defined. Current application key manager will be invalidated.
|
||||
* (NOTE: this is intended for server use only when client will not be performing
|
||||
* CA validation).
|
||||
* @param subjectAltName name to be added to the current list of alternative subject names.
|
||||
* A null value will clear all names currently set.
|
||||
* name will be used to generate a self-signed certificate and private key
|
||||
*/
|
||||
public synchronized static void addSubjectAlternativeName(String subjectAltName) {
|
||||
if (subjectAltName == null) {
|
||||
defaultSubjectAlternativeNames = null;
|
||||
}
|
||||
else {
|
||||
if (defaultSubjectAlternativeNames == null) {
|
||||
defaultSubjectAlternativeNames = new ArrayList<>();
|
||||
}
|
||||
defaultSubjectAlternativeNames.add(subjectAltName);
|
||||
}
|
||||
keyManagerWrapper.invalidateKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize key manager if needed. Doing this explicitly independent of an SSL connection
|
||||
* allows application to bail before initiating connection. This will get handshake failure
|
||||
* if user forgets keystore password or other keystore problem.
|
||||
* @return true if key manager initialized, otherwise false
|
||||
*/
|
||||
public synchronized static boolean initialize() {
|
||||
try {
|
||||
return keyManagerWrapper.init();
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate the existing default key manager.
|
||||
*/
|
||||
public synchronized static void invalidateKeyManager() {
|
||||
keyManagerWrapper.invalidateKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* If the system property <i>ghidra.keystore</i> takes precedence in establishing
|
||||
* the keystore. If using a GUI and the system property has not been set, the
|
||||
* user preference with the same name will be used.
|
||||
* @return active keystore path or null if currently not running with a keystore or
|
||||
* one has not been set.
|
||||
*/
|
||||
public static synchronized String getPreferredKeyStore() {
|
||||
String path = prunePath(System.getProperty(KEYSTORE_PATH_PROPERTY));
|
||||
if (path == null && !SystemUtilities.isInHeadlessMode()) {
|
||||
path = prunePath(Preferences.getProperty(KEYSTORE_PATH_PROPERTY));
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default/preferred key store path.
|
||||
* @return default key store path or null if not set
|
||||
*/
|
||||
public static synchronized String getKeyStore() {
|
||||
return keyManagerWrapper.getKeyStore();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the lazy default key manager associated with the preferred key store.
|
||||
* @return default key manager or null if not initialized
|
||||
*/
|
||||
public static synchronized X509ExtendedKeyManager getKeyManager() {
|
||||
return keyManagerWrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>DefaultKeyManager</code> provides a wrapper for the X509 wrappedKeyManager whose
|
||||
* instantiation is delayed until needed. When a wrapper method is first invoked, the
|
||||
* {@link DefaultX509KeyManager#init()} method is called to open the keystore
|
||||
* (which may require a password prompt) and establish the underlying X509KeyManager.
|
||||
*/
|
||||
private static class DefaultX509KeyManager extends X509ExtendedKeyManager {
|
||||
|
||||
private X509KeyManager wrappedKeyManager;
|
||||
private String keystorePath;
|
||||
private boolean isSelfSigned = false;
|
||||
|
||||
@Override
|
||||
public String chooseEngineServerAlias(String keyType, Principal[] issuers,
|
||||
SSLEngine engine) {
|
||||
return super.chooseEngineServerAlias(keyType, issuers, engine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String chooseEngineClientAlias(String[] keyType, Principal[] issuers,
|
||||
SSLEngine engine) {
|
||||
return super.chooseEngineClientAlias(keyType, issuers, engine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized String chooseClientAlias(String[] keyType, Principal[] issuers,
|
||||
Socket socket) {
|
||||
try {
|
||||
init();
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
// ignore
|
||||
}
|
||||
if (wrappedKeyManager == null) {
|
||||
return null;
|
||||
}
|
||||
return wrappedKeyManager.chooseClientAlias(keyType, issuers, socket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized String chooseServerAlias(String keyType, Principal[] issuers,
|
||||
Socket socket) {
|
||||
try {
|
||||
init();
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
// ignore
|
||||
}
|
||||
if (wrappedKeyManager == null) {
|
||||
return null;
|
||||
}
|
||||
return wrappedKeyManager.chooseServerAlias(keyType, issuers, socket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getClientAliases(String keyType, Principal[] issuers) {
|
||||
try {
|
||||
init();
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
// ignore
|
||||
}
|
||||
if (wrappedKeyManager == null) {
|
||||
return null;
|
||||
}
|
||||
return wrappedKeyManager.getClientAliases(keyType, issuers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getServerAliases(String keyType, Principal[] issuers) {
|
||||
try {
|
||||
init();
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
// ignore
|
||||
}
|
||||
if (wrappedKeyManager == null) {
|
||||
return null;
|
||||
}
|
||||
return wrappedKeyManager.getServerAliases(keyType, issuers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate[] getCertificateChain(String alias) {
|
||||
if (wrappedKeyManager == null) {
|
||||
return null;
|
||||
}
|
||||
return wrappedKeyManager.getCertificateChain(alias);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrivateKey getPrivateKey(String alias) {
|
||||
if (wrappedKeyManager == null) {
|
||||
return null;
|
||||
}
|
||||
return wrappedKeyManager.getPrivateKey(alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate the active keystore and key manager
|
||||
*/
|
||||
private synchronized void invalidateKey() {
|
||||
wrappedKeyManager = null;
|
||||
keystorePath = null;
|
||||
isSelfSigned = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return active keystore path or preferred keystore path if not yet initialized.
|
||||
* @return active keystore path or preferred keystore path if not yet initialized.
|
||||
*/
|
||||
private synchronized String getKeyStore() {
|
||||
return keystorePath != null ? keystorePath : getPreferredKeyStore();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if active key manager is utilizing a generated self-signed certificate.
|
||||
* @return true if using self-signed certificate.
|
||||
*/
|
||||
private synchronized boolean usingGeneratedSelfSignedCertificate() {
|
||||
return wrappedKeyManager != null && isSelfSigned;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the default x509KeyManager singleton wrappedKeyManager associated with the
|
||||
* preferred keystore path.
|
||||
* If the <code>x509KeyManager</code> already exists, this method has no affect. If the
|
||||
* <code>keystorePath</code> has not already been set, the <code>getPreferredKeyStore()</code>
|
||||
* method will be invoked to obtain the keystore which should be used in establishing the
|
||||
* <code>wrappedKeyManager</code>. If no keystore has been identified and the Default Identity
|
||||
* has been set, a self-signed certificate will be generated. If nothing has been set, the
|
||||
* wrappedKeyManager will remain null and false will be returned. If an error occurs it
|
||||
* will be logged and key managers will remain uninitialized.
|
||||
* @return true if key manager initialized successfully or was previously initialized, else
|
||||
* false if keystore path has not been set and default identity for self-signed certificate
|
||||
* has not be established (see {@link DefaultKeyManagerFactory#setDefaultIdentity(X500Principal)}).
|
||||
* @throws CancelledException user cancelled keystore password entry request
|
||||
*/
|
||||
private synchronized boolean init() throws CancelledException {
|
||||
if (wrappedKeyManager != null) {
|
||||
return true;
|
||||
}
|
||||
return init(getPreferredKeyStore());
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the default x509KeyManager singleton wrappedKeyManager using the specified path.
|
||||
* If the <code>x509KeyManager</code> already exists for the specified keystore path,
|
||||
* this method has no affect. If no keystore has been identified and the Default Identity
|
||||
* has been set, a self-signed certificate will be generated. If nothing has been set, the
|
||||
* wrappedKeyManager will remain null and false will be returned. If an error occurs it
|
||||
* will be logged and key managers will remain uninitialized.
|
||||
* @param newKeystorePath specifies the keystore to be opened or null for no keystore
|
||||
* @return true if key manager initialized successfully or was previously initialized, else
|
||||
* false if new keystore path was not specified and default identity for self-signed certificate
|
||||
* has not be established (see {@link DefaultKeyManagerFactory#setDefaultIdentity(X500Principal)}).
|
||||
* @throws CancelledException user cancelled keystore password entry request
|
||||
*/
|
||||
private synchronized boolean init(String newKeystorePath) throws CancelledException {
|
||||
|
||||
if (wrappedKeyManager != null) {
|
||||
if (StringUtils.equals(keystorePath, newKeystorePath)) {
|
||||
return true;
|
||||
}
|
||||
invalidateKey();
|
||||
}
|
||||
|
||||
isSelfSigned = false;
|
||||
try {
|
||||
if (newKeystorePath != null && newKeystorePath.length() != 0) {
|
||||
Msg.info(DefaultKeyManagerFactory.class,
|
||||
"Using certificate keystore: " + newKeystorePath);
|
||||
// Password optionally specified via property
|
||||
String keystorePwd = System.getProperty(KEYSTORE_PASSWORD_PROPERTY);
|
||||
wrappedKeyManager =
|
||||
ApplicationKeyManagerFactory.getKeyManager(newKeystorePath, keystorePwd);
|
||||
keystorePath = newKeystorePath; // update current keystore path
|
||||
}
|
||||
else if (defaultIdentity != null) {
|
||||
// use self-signed keystore as fallback (intended for server use only)
|
||||
Msg.info(this, "Using self-signed certificate: " + defaultIdentity.getName());
|
||||
char[] pwd = DEFAULT_PASSWORD.toCharArray();
|
||||
KeyStore selfSignedKeyStore = PKIUtils.createKeyStore("defaultSigKey",
|
||||
defaultIdentity.getName(), SELF_SIGNED_DURATION_DAYS, null, null, "JKS",
|
||||
defaultSubjectAlternativeNames, pwd);
|
||||
wrappedKeyManager = ApplicationKeyManagerFactory
|
||||
.getKeyManagerFromKeyStore(selfSignedKeyStore, pwd);
|
||||
isSelfSigned = true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
throw e;
|
||||
}
|
||||
catch (Exception e) {
|
||||
Msg.showError(this, null, "PKI Keystore Failure",
|
||||
"Failed to create PKI key manager: " + e.getMessage(), e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign the supplied token byte array using an installed certificate from
|
||||
* one of the specified authorities
|
||||
* @param authorities trusted certificate authorities used to constrain client certificate
|
||||
* (may be null or empty array if CA constraint does not matter).
|
||||
* @param token token byte array
|
||||
* @return signed token object
|
||||
* @throws NoSuchAlgorithmException algorithm associated within signing certificate not found
|
||||
* @throws SignatureException failed to generate SignedToken
|
||||
* @throws CertificateException error associated with signing certificate
|
||||
*/
|
||||
public static SignedToken getSignedToken(Principal[] authorities, byte[] token)
|
||||
throws NoSuchAlgorithmException, SignatureException, CertificateException {
|
||||
|
||||
PrivateKey privateKey = null;
|
||||
X509Certificate[] certificateChain = null;
|
||||
try {
|
||||
X509ExtendedKeyManager x509KeyManager = getKeyManager();
|
||||
if (x509KeyManager != null) {
|
||||
String alias = x509KeyManager.chooseClientAlias(new String[] { PKIUtils.RSA_TYPE },
|
||||
authorities, null);
|
||||
if (alias != null) {
|
||||
privateKey = x509KeyManager.getPrivateKey(alias);
|
||||
certificateChain = x509KeyManager.getCertificateChain(alias);
|
||||
}
|
||||
}
|
||||
if (privateKey == null || certificateChain == null) {
|
||||
CertificateException e =
|
||||
new CertificateException("suitable PKI certificate not found");
|
||||
e.printStackTrace();
|
||||
throw e;
|
||||
}
|
||||
|
||||
//
|
||||
// See JAVA Examples in a Nutshell (p.358) for use of Signer and
|
||||
// IdentityScope classes
|
||||
//
|
||||
|
||||
String algorithm = certificateChain[0].getSigAlgName();
|
||||
Signature sig = Signature.getInstance(algorithm);
|
||||
try {
|
||||
sig.initSign(privateKey);
|
||||
}
|
||||
catch (InvalidKeyException e) {
|
||||
throw new CertificateException("suitable PKI certificate not found", e);
|
||||
}
|
||||
sig.update(token);
|
||||
|
||||
return new SignedToken(token, sig.sign(), certificateChain, algorithm);
|
||||
}
|
||||
finally {
|
||||
if (privateKey != null) {
|
||||
// Note: Keystore destroy only supported in Java 1.8
|
||||
try {
|
||||
privateKey.destroy();
|
||||
}
|
||||
catch (DestroyFailedException e) {
|
||||
// ignore - may not be supported by all keystores
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the specified sigBytes reflect my signature of the specified token.
|
||||
* @param authorities trusted certificate authorities used to constrain client certificate
|
||||
* (may be null or empty array if CA constraint does not matter).
|
||||
* @param token byte array token
|
||||
* @param signature token signature
|
||||
* @return true if signature is my signature
|
||||
* @throws NoSuchAlgorithmException algorithym associated within signing certificate not found
|
||||
* @throws SignatureException failed to generate SignedToken
|
||||
* @throws CertificateException error associated with signing certificate
|
||||
*/
|
||||
public static boolean isMySignature(Principal[] authorities, byte[] token, byte[] signature)
|
||||
throws NoSuchAlgorithmException, SignatureException, CertificateException {
|
||||
SignedToken signedToken = getSignedToken(authorities, token);
|
||||
return Arrays.equals(signature, signedToken.signature);
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -26,21 +26,17 @@ import ghidra.util.Msg;
|
||||
/**
|
||||
* Initialize the default SSLContext for use by all SSL connections (e.g., https).
|
||||
* It is the responsibility of the Application to properly invoke this initializer
|
||||
* to ensure that the default SSLContext is properly established. While HTTPS URL connections
|
||||
* will make use of this default SSLContext, other SSL connections may need to
|
||||
* specify the {@link ApplicationSSLSocketFactory} to leverage the applications
|
||||
* default SSLContext.
|
||||
* to ensure that the default SSLContext is properly established.
|
||||
* <p>
|
||||
* The property <code>jdk.tls.client.protocols</code> should be set to restrict secure
|
||||
* client connections to a specific set of enabled TLS protocols (e.g., TLSv1.2,TLSv1.3).
|
||||
* See <A href="https://java.com/en/configure_crypto.html">JDK and JRE Cryptographic Algorithms</A>
|
||||
* for details.
|
||||
*
|
||||
* @see ApplicationTrustManagerFactory
|
||||
* @see ApplicationKeyManagerFactory
|
||||
* @see ApplicationKeyManagerUtils
|
||||
* @see DefaultTrustManagerFactory
|
||||
* @see DefaultKeyManagerFactory
|
||||
*/
|
||||
public class SSLContextInitializer implements ModuleInitializer {
|
||||
public class DefaultSSLContextInitializer implements ModuleInitializer {
|
||||
|
||||
private static final String DEFAULT_SSL_PROTOCOL = "TLS";
|
||||
|
||||
@@ -56,8 +52,8 @@ public class SSLContextInitializer implements ModuleInitializer {
|
||||
public static synchronized boolean initialize(boolean reset) {
|
||||
if (reset) {
|
||||
sslContext = null;
|
||||
ApplicationTrustManagerFactory.invalidateTrustManagers();
|
||||
ApplicationKeyManagerFactory.invalidateKeyManagers();
|
||||
DefaultTrustManagerFactory.invalidateTrustManagers();
|
||||
DefaultKeyManagerFactory.invalidateKeyManager();
|
||||
}
|
||||
return initialize();
|
||||
}
|
||||
@@ -73,15 +69,25 @@ public class SSLContextInitializer implements ModuleInitializer {
|
||||
return true;
|
||||
}
|
||||
|
||||
Msg.info(SSLContextInitializer.class, "Initializing SSL Context");
|
||||
Msg.info(DefaultSSLContextInitializer.class, "Initializing SSL Context");
|
||||
|
||||
KeyManager[] keyManagers = ApplicationKeyManagerFactory.getInstance().getKeyManagers();
|
||||
KeyManager keyManager = DefaultKeyManagerFactory.getKeyManager();
|
||||
|
||||
try {
|
||||
// Use new instance of SSLContext to avoid adopting CA certs provided with Java
|
||||
sslContext = SSLContext.getInstance(DEFAULT_SSL_PROTOCOL);
|
||||
SecureRandom random = SecureRandomFactory.getSecureRandom();
|
||||
sslContext.init(keyManagers, ApplicationTrustManagerFactory.getTrustManagers(), random);
|
||||
sslContext.init(new KeyManager[] { keyManager },
|
||||
DefaultTrustManagerFactory.getTrustManagers(), random);
|
||||
|
||||
//
|
||||
// NOTE: The SslRMIClientSocketFactory and SslRMIServerSocketFactory both statically
|
||||
// cache their 'defaultSocketFactory' which prevents it from utilizing a new
|
||||
// default SSLContext. This requires us to employ a wrapped TrustManager and
|
||||
// wrapped KeyManager to allow for them to possibly be revised after first use
|
||||
// (e.g., JUnit testing, new user PKI Certificate, etc.).
|
||||
//
|
||||
|
||||
SSLContext.setDefault(sslContext);
|
||||
|
||||
// Must install default HostnameVerifier - otherwise all traffic will fail
|
||||
@@ -101,7 +107,7 @@ public class SSLContextInitializer implements ModuleInitializer {
|
||||
|
||||
}
|
||||
catch (Exception e) {
|
||||
Msg.error(SSLContextInitializer.class,
|
||||
Msg.error(DefaultSSLContextInitializer.class,
|
||||
"SSL Context initialization failed: " + e.getMessage(), e);
|
||||
}
|
||||
return false;
|
||||
@@ -20,16 +20,20 @@ import java.security.GeneralSecurityException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.net.ssl.*;
|
||||
import javax.rmi.ssl.SslRMIClientSocketFactory;
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
|
||||
import ghidra.framework.preferences.Preferences;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
* <code>ApplicationTrustManagerFactory</code> provides the ability to establish
|
||||
* acceptable certificate authorities to be used with SSL connections and PKI
|
||||
* authentication.
|
||||
* <code>DefaultTrustManagerFactory</code> provides the ability to establish
|
||||
* acceptable certificate authorities to be used with the default SSLContext
|
||||
* as established by {@link DefaultSSLContextInitializer}.
|
||||
* <p>
|
||||
* The default behavior is for no trust authority to be established, in which case
|
||||
* SSL peers will not be authenticated. If CA certificates have been set, all SSL
|
||||
@@ -47,8 +51,13 @@ import ghidra.util.Msg;
|
||||
* <p>
|
||||
* The application may choose to set the file path automatically based upon the presence of
|
||||
* a <i>cacerts</i> file at a predetermined location.
|
||||
* <p>
|
||||
* NOTE: Since {@link SslRMIClientSocketFactory} and {@link SSLServerSocketFactory} employ a
|
||||
* static cache of a default {@link SSLSocketFactory}, with its default {@link SSLContext}, we
|
||||
* must utilize a wrapped implementation of the associated {@link X509TrustManager} so that any
|
||||
* changes are used by the existing default {@link SSLSocketFactory}.
|
||||
*/
|
||||
public class ApplicationTrustManagerFactory {
|
||||
public class DefaultTrustManagerFactory {
|
||||
|
||||
/**
|
||||
* The X509 cacerts file to be used when authenticating remote
|
||||
@@ -57,22 +66,20 @@ public class ApplicationTrustManagerFactory {
|
||||
*/
|
||||
public static final String GHIDRA_CACERTS_PATH_PROPERTY = "ghidra.cacerts";
|
||||
|
||||
private static final X509Certificate[] NO_CERTS = new X509Certificate[0];
|
||||
|
||||
/**
|
||||
* Use a singleton wrappedTrustManager so we can alter the true trustManager
|
||||
* as needed. Once the installed trust manager is consumed by the SSL Engine,
|
||||
* we are unable to get it to use a new one. Use of a wrapper solves this
|
||||
* issue which occurs during testing.
|
||||
*/
|
||||
private static X509TrustManager trustManager;
|
||||
private static TrustManager[] wrappedTrustManagers;
|
||||
|
||||
private static boolean hasCAs;
|
||||
private static Exception caError;
|
||||
private static final WrappedTrustManager wrappedTrustManager = new WrappedTrustManager();
|
||||
|
||||
/**
|
||||
* <code>ApplicationTrustManagerFactory</code> constructor
|
||||
*/
|
||||
private ApplicationTrustManagerFactory() {
|
||||
private DefaultTrustManagerFactory() {
|
||||
// no instantiation - static methods only
|
||||
}
|
||||
|
||||
@@ -83,138 +90,169 @@ public class ApplicationTrustManagerFactory {
|
||||
*/
|
||||
private static void init() {
|
||||
|
||||
if (wrappedTrustManagers == null) {
|
||||
wrappedTrustManagers = new WrappedTrustManager[] { new WrappedTrustManager() };
|
||||
}
|
||||
|
||||
String cacertsPath = System.getProperty(GHIDRA_CACERTS_PATH_PROPERTY);
|
||||
if (cacertsPath == null || cacertsPath.length() == 0) {
|
||||
// check user preferences if cacerts not set via system property
|
||||
cacertsPath = Preferences.getProperty(GHIDRA_CACERTS_PATH_PROPERTY);
|
||||
if (cacertsPath == null || cacertsPath.length() == 0) {
|
||||
Msg.info(ApplicationTrustManagerFactory.class,
|
||||
Msg.info(DefaultTrustManagerFactory.class,
|
||||
"Trust manager disabled, cacerts have not been set");
|
||||
trustManager = new OpenTrustManager();
|
||||
wrappedTrustManager.setTrustManager(new OpenTrustManager());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
Msg.info(ApplicationTrustManagerFactory.class,
|
||||
Msg.info(DefaultTrustManagerFactory.class,
|
||||
"Trust manager initializing with cacerts: " + cacertsPath);
|
||||
KeyStore keyStore = ApplicationKeyStore.getCertificateStoreInstance(cacertsPath);
|
||||
KeyStore keyStore = PKIUtils.loadCertificateStore(cacertsPath);
|
||||
TrustManagerFactory tmf =
|
||||
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||
tmf.init(keyStore);
|
||||
X509TrustManager x509TrustMgr = null;
|
||||
TrustManager[] trustManagers = tmf.getTrustManagers();
|
||||
for (TrustManager trustManager2 : trustManagers) {
|
||||
if (trustManager2 instanceof X509TrustManager) {
|
||||
X509TrustManager mgr = (X509TrustManager) trustManager2;
|
||||
ApplicationKeyStore.logCerts(mgr.getAcceptedIssuers());
|
||||
trustManager = mgr;
|
||||
for (TrustManager trustMgr : trustManagers) {
|
||||
if (trustMgr instanceof X509TrustManager) {
|
||||
x509TrustMgr = (X509TrustManager) trustMgr;
|
||||
wrappedTrustManager.setTrustManager(x509TrustMgr);
|
||||
PKIUtils.logCerts(x509TrustMgr.getAcceptedIssuers());
|
||||
break;
|
||||
}
|
||||
}
|
||||
hasCAs = true;
|
||||
if (x509TrustMgr == null) {
|
||||
throw new CertificateException("Failed to load any X509 certificates");
|
||||
}
|
||||
}
|
||||
catch (GeneralSecurityException | IOException e) {
|
||||
caError = e;
|
||||
wrappedTrustManager.setTrustManagerError(e);
|
||||
String msg = e.getMessage();
|
||||
if (msg == null) {
|
||||
msg = e.toString();
|
||||
}
|
||||
Msg.error(ApplicationTrustManagerFactory.class,
|
||||
Msg.error(DefaultTrustManagerFactory.class,
|
||||
"Failed to process cacerts (" + cacertsPath + "): " + msg, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if certificate authorities are in place. If no certificate authorities
|
||||
* have been specified via the "ghidra.cacerts" property, all certificates will be
|
||||
* trusted.
|
||||
* @return true if certificate authorities are in place, else false.
|
||||
*/
|
||||
public static boolean hasCertificateAuthorities() {
|
||||
return hasCAs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a CA cert initialization error occurred
|
||||
* @return true if error occurred (see {@link #getCertError()})
|
||||
*/
|
||||
static boolean hasCertError() {
|
||||
return caError != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the CA cert initialization error which occurred
|
||||
* during initialization
|
||||
* @return error object or null if not applicable
|
||||
*/
|
||||
static Exception getCertError() {
|
||||
return caError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get trust managers after performing any necessary initialization.
|
||||
* Get trust manager after performing any necessary initialization.
|
||||
*
|
||||
* @return trust managers
|
||||
*/
|
||||
static synchronized TrustManager[] getTrustManagers() {
|
||||
if (trustManager == null) {
|
||||
public static synchronized TrustManager[] getTrustManagers() {
|
||||
if (wrappedTrustManager.trustManager == null) {
|
||||
init();
|
||||
}
|
||||
return wrappedTrustManagers.clone();
|
||||
return new TrustManager[] { wrappedTrustManager };
|
||||
}
|
||||
|
||||
/**
|
||||
* Get trust manager after performing any necessary initialization.
|
||||
* @return trust managers
|
||||
*/
|
||||
public static synchronized X509TrustManager getTrustManager() {
|
||||
if (trustManager == null) {
|
||||
init();
|
||||
}
|
||||
return trustManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate the active keystore and key manager
|
||||
* Invalidate the active keystore and key manager.
|
||||
*
|
||||
* NOTE: This should only be invoked by {@link DefaultSSLContextInitializer}.
|
||||
*/
|
||||
static synchronized void invalidateTrustManagers() {
|
||||
trustManager = null;
|
||||
caError = null;
|
||||
wrappedTrustManager.invalidate();
|
||||
}
|
||||
|
||||
private static final X509Certificate[] NO_CERTS = new X509Certificate[0];
|
||||
/**
|
||||
* Returns a list of trusted issuers (i.e., CA certificates) as established
|
||||
* by the {@link DefaultTrustManagerFactory}.
|
||||
*
|
||||
* @return array of trusted Certificate Authorities
|
||||
* @throws CertificateException if failed to properly initialize trust manager
|
||||
* due to CA certificate error(s).
|
||||
*/
|
||||
public static X500Principal[] getTrustedIssuers() throws CertificateException {
|
||||
return wrappedTrustManager.getTrustedIssuers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a client certificate ensuring that it is not expired and is
|
||||
* trusted based upon the active trust managers.
|
||||
* @param certChain X509 certificate chain
|
||||
* @param authType authentication type (i.e., "RSA")
|
||||
* @throws CertificateException if certificate validation fails
|
||||
*/
|
||||
public static void validateClient(X509Certificate[] certChain, String authType)
|
||||
throws CertificateException {
|
||||
wrappedTrustManager.checkClientTrusted(certChain, authType);
|
||||
}
|
||||
|
||||
private static class WrappedTrustManager implements X509TrustManager {
|
||||
|
||||
private X509TrustManager trustManager;
|
||||
private Exception caError;
|
||||
|
||||
WrappedTrustManager() {
|
||||
invalidate();
|
||||
}
|
||||
|
||||
void invalidate() {
|
||||
this.trustManager = null;
|
||||
this.caError = null;
|
||||
}
|
||||
|
||||
synchronized void setTrustManager(X509TrustManager trustManager) {
|
||||
this.trustManager = trustManager;
|
||||
this.caError = null;
|
||||
}
|
||||
|
||||
synchronized void setTrustManagerError(Exception caError) {
|
||||
this.trustManager = null;
|
||||
this.caError = caError;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkClientTrusted(X509Certificate[] chain, String authType)
|
||||
public synchronized void checkClientTrusted(X509Certificate[] chain, String authType)
|
||||
throws CertificateException {
|
||||
if (trustManager == null) {
|
||||
throw new CertificateException("Trust manager not properly initialized");
|
||||
}
|
||||
checkTrustManager();
|
||||
trustManager.checkClientTrusted(chain, authType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate[] chain, String authType)
|
||||
public synchronized void checkServerTrusted(X509Certificate[] chain, String authType)
|
||||
throws CertificateException {
|
||||
if (trustManager == null) {
|
||||
throw new CertificateException("Trust manager not properly initialized");
|
||||
}
|
||||
checkTrustManager();
|
||||
trustManager.checkServerTrusted(chain, authType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
public synchronized X509Certificate[] getAcceptedIssuers() {
|
||||
if (trustManager == null) {
|
||||
return NO_CERTS;
|
||||
}
|
||||
return trustManager.getAcceptedIssuers();
|
||||
}
|
||||
|
||||
synchronized X500Principal[] getTrustedIssuers() throws CertificateException {
|
||||
|
||||
WrappedTrustManager trustMgr = wrappedTrustManager;
|
||||
trustMgr.checkTrustManager();
|
||||
|
||||
X509Certificate[] acceptedIssuers = trustMgr.getAcceptedIssuers();
|
||||
if (acceptedIssuers == null || acceptedIssuers.length == 0) {
|
||||
return null; // trust all authorities
|
||||
}
|
||||
|
||||
Set<X500Principal> set = new HashSet<>();
|
||||
for (X509Certificate trustedCert : acceptedIssuers) {
|
||||
set.add(trustedCert.getSubjectX500Principal());
|
||||
}
|
||||
X500Principal[] principals = new X500Principal[set.size()];
|
||||
return set.toArray(principals);
|
||||
}
|
||||
|
||||
private void checkTrustManager() throws CertificateException {
|
||||
if (trustManager != null) {
|
||||
return;
|
||||
}
|
||||
if (caError != null) {
|
||||
throw new CertificateException("Failed to load CA certs", caError);
|
||||
}
|
||||
throw new CertificateException("Trust manager not properly initialized");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -47,8 +47,8 @@ public class HttpClients {
|
||||
* @throws IOException if error in PKI settings or crypto configuration
|
||||
*/
|
||||
public static HttpClient.Builder newHttpClientBuilder() throws IOException {
|
||||
if (!ApplicationKeyManagerFactory.initialize()) {
|
||||
if (ApplicationKeyManagerFactory.getKeyStore() != null) {
|
||||
if (!DefaultKeyManagerFactory.initialize()) {
|
||||
if (DefaultKeyManagerFactory.getKeyStore() != null) {
|
||||
throw new IOException("Failed to initialize PKI certificate keystore");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,15 +42,13 @@ import org.bouncycastle.util.IPAddress;
|
||||
import generic.random.SecureRandomFactory;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.exception.AssertException;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
|
||||
/**
|
||||
* <code>ApplicationKeyManagerUtils</code> provides public methods for utilizing
|
||||
* the application PKI key management, including access to trusted issuers
|
||||
* (i.e., CA certificates), token signing and validation, and the ability to
|
||||
* generate keystores for testing or when a self-signed certificate will
|
||||
* suffice.
|
||||
* {@link PKIUtils} provides supporting utilities for creating and accessing X509 certificate
|
||||
* keystore files.
|
||||
*/
|
||||
public class ApplicationKeyManagerUtils {
|
||||
public class PKIUtils {
|
||||
|
||||
public static final String RSA_TYPE = "RSA";
|
||||
|
||||
@@ -69,193 +67,61 @@ public class ApplicationKeyManagerUtils {
|
||||
|
||||
static {
|
||||
/**
|
||||
* Bouncy Castle uses its BCStyle for X500Names which reverses Distingushed Name ordering.
|
||||
* Bouncy Castle uses its BCStyle for X500Names which reverses Distinguished Name ordering.
|
||||
* This is resolved by setting the default to RFC4519 style to ensure compatibility with
|
||||
* Java's internal implementation of X500Name.
|
||||
* <p>
|
||||
* Note that this could become an issue if this static default is adjusted elsewhere.
|
||||
* It may be neccessary to set this at the start of all methods which rely on any of the
|
||||
* It may be necessary to set this at the start of all methods which rely on any of the
|
||||
* BC code for X500 certificate processing.
|
||||
*
|
||||
*/
|
||||
X500Name.setDefaultStyle(RFC4519Style.INSTANCE);
|
||||
}
|
||||
|
||||
private ApplicationKeyManagerUtils() {
|
||||
// no instantiation - static methods only
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign the supplied token byte array using an installed certificate from
|
||||
* one of the specified authorities
|
||||
* @param authorities trusted certificate authorities used to constrain client certificate
|
||||
* (may be null or empty array if CA constraint does not matter).
|
||||
* @param token token byte array
|
||||
* @return signed token object
|
||||
* @throws NoSuchAlgorithmException algorithym associated within signing certificate not found
|
||||
* @throws SignatureException failed to generate SignedToken
|
||||
* @throws CertificateException error associated with signing certificate
|
||||
* Establish X509TrustManager for the specified CA certificate storage.
|
||||
*
|
||||
* @param caCertsFile CA certificates storage file
|
||||
* @return X509TrustManager
|
||||
* @throws CancelledException if password entry was cancelled
|
||||
* @throws GeneralSecurityException if error occured during truststore initialization
|
||||
* @throws IOException if file read error occurs
|
||||
*/
|
||||
public static SignedToken getSignedToken(Principal[] authorities, byte[] token)
|
||||
throws NoSuchAlgorithmException, SignatureException, CertificateException {
|
||||
public static X509TrustManager getTrustManager(File caCertsFile)
|
||||
throws CancelledException, GeneralSecurityException, IOException {
|
||||
|
||||
PrivateKey privateKey = null;
|
||||
X509Certificate[] certificateChain = null;
|
||||
try {
|
||||
ApplicationKeyManagerFactory keyManagerFactory =
|
||||
ApplicationKeyManagerFactory.getInstance();
|
||||
for (KeyManager keyManager : keyManagerFactory.getKeyManagers()) {
|
||||
if (!(keyManager instanceof X509KeyManager)) {
|
||||
continue;
|
||||
}
|
||||
X509KeyManager x509KeyManager = (X509KeyManager) keyManager;
|
||||
String alias =
|
||||
x509KeyManager.chooseClientAlias(new String[] { RSA_TYPE }, authorities, null);
|
||||
if (alias != null) {
|
||||
privateKey = x509KeyManager.getPrivateKey(alias);
|
||||
certificateChain = x509KeyManager.getCertificateChain(alias);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (privateKey == null || certificateChain == null) {
|
||||
CertificateException e =
|
||||
new CertificateException("suitable PKI certificate not found");
|
||||
e.printStackTrace();
|
||||
throw e;
|
||||
}
|
||||
|
||||
//
|
||||
// See JAVA Examples in a Nutshell (p.358) for use of Signer and
|
||||
// IdentityScope classes
|
||||
//
|
||||
|
||||
String algorithm = certificateChain[0].getSigAlgName();
|
||||
Signature sig = Signature.getInstance(algorithm);
|
||||
try {
|
||||
sig.initSign(privateKey);
|
||||
}
|
||||
catch (InvalidKeyException e) {
|
||||
throw new CertificateException("suitable PKI certificate not found", e);
|
||||
}
|
||||
sig.update(token);
|
||||
|
||||
return new SignedToken(token, sig.sign(), certificateChain, algorithm);
|
||||
}
|
||||
finally {
|
||||
if (privateKey != null) {
|
||||
// Note: Keystore destroy only supported in Java 1.8
|
||||
try {
|
||||
privateKey.destroy();
|
||||
}
|
||||
catch (DestroyFailedException e) {
|
||||
// ignore - may not be supported by all keystores
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the specified sigBytes reflect my signature of the specified token.
|
||||
* @param authorities trusted certificate authorities used to constrain client certificate
|
||||
* (may be null or empty array if CA constraint does not matter).
|
||||
* @param token byte array token
|
||||
* @param signature token signature
|
||||
* @return true if signature is my signature
|
||||
* @throws NoSuchAlgorithmException algorithym associated within signing certificate not found
|
||||
* @throws SignatureException failed to generate SignedToken
|
||||
* @throws CertificateException error associated with signing certificate
|
||||
*/
|
||||
public static boolean isMySignature(Principal[] authorities, byte[] token, byte[] signature)
|
||||
throws NoSuchAlgorithmException, SignatureException, CertificateException {
|
||||
SignedToken signedToken = getSignedToken(authorities, token);
|
||||
return Arrays.equals(signature, signedToken.signature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of trusted issuers (i.e., CA certificates) as established
|
||||
* by the {@link ApplicationTrustManagerFactory}.
|
||||
* @return array of trusted Certificate Authorities
|
||||
* @throws CertificateException if failed to properly initialize trust manager
|
||||
* due to CA certificate error(s).
|
||||
*/
|
||||
public static X500Principal[] getTrustedIssuers() throws CertificateException {
|
||||
|
||||
TrustManager[] trustManagers = ApplicationTrustManagerFactory.getTrustManagers();
|
||||
if (ApplicationTrustManagerFactory.hasCertError()) {
|
||||
throw new CertificateException("failed to load CA certs",
|
||||
ApplicationTrustManagerFactory.getCertError());
|
||||
if (!caCertsFile.isFile()) {
|
||||
throw new FileNotFoundException(
|
||||
"CA Certificates file not found: " + caCertsFile.getAbsolutePath());
|
||||
}
|
||||
|
||||
Set<X500Principal> set = new HashSet<>();
|
||||
KeyStore keyStore = PKIUtils.loadCertificateStore(caCertsFile.getAbsolutePath());
|
||||
TrustManagerFactory tmf =
|
||||
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||
tmf.init(keyStore);
|
||||
|
||||
boolean openTrust = true;
|
||||
for (TrustManager trustManager : trustManagers) {
|
||||
if (!(trustManager instanceof X509TrustManager)) {
|
||||
Msg.warn(ApplicationKeyManagerUtils.class,
|
||||
"Unexpected trust manager implementation: " +
|
||||
trustManager.getClass().getName());
|
||||
openTrust = false;
|
||||
continue;
|
||||
}
|
||||
X509TrustManager x509TrustManager = (X509TrustManager) trustManager;
|
||||
X509Certificate[] acceptedIssuers = x509TrustManager.getAcceptedIssuers();
|
||||
if (acceptedIssuers != null && acceptedIssuers.length != 0) {
|
||||
openTrust = false;
|
||||
for (X509Certificate trustedCert : acceptedIssuers) {
|
||||
set.add(trustedCert.getSubjectX500Principal());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (openTrust) {
|
||||
return null;// trust all authorities
|
||||
}
|
||||
|
||||
X500Principal[] principals = new X500Principal[set.size()];
|
||||
return set.toArray(principals);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a client certificate ensuring that it is not expired and is
|
||||
* trusted based upon the active trust managers.
|
||||
* @param certChain X509 certificate chain
|
||||
* @param authType authentication type (i.e., "RSA")
|
||||
* @throws CertificateException if certificate validation fails
|
||||
*/
|
||||
public static void validateClient(X509Certificate[] certChain, String authType)
|
||||
throws CertificateException {
|
||||
|
||||
CertificateException checkFailure = null;
|
||||
|
||||
TrustManager[] trustManagers = ApplicationTrustManagerFactory.getTrustManagers();
|
||||
if (ApplicationTrustManagerFactory.hasCertError()) {
|
||||
throw new CertificateException("failed to load CA certs",
|
||||
ApplicationTrustManagerFactory.getCertError());
|
||||
}
|
||||
|
||||
for (TrustManager trustManager : trustManagers) {
|
||||
if (!(trustManager instanceof X509TrustManager)) {
|
||||
continue;
|
||||
}
|
||||
X509TrustManager x509TrustManager = (X509TrustManager) trustManager;
|
||||
|
||||
try {
|
||||
x509TrustManager.checkClientTrusted(certChain, authType);
|
||||
checkFailure = null;
|
||||
X509TrustManager trustManager = null;
|
||||
TrustManager[] trustManagers = tmf.getTrustManagers();
|
||||
for (TrustManager trustManager2 : trustManagers) {
|
||||
if (trustManager2 instanceof X509TrustManager mgr) {
|
||||
//ApplicationKeyStore.logCerts(mgr.getAcceptedIssuers());
|
||||
trustManager = mgr;
|
||||
break;
|
||||
}
|
||||
catch (CertificateException e) {
|
||||
checkFailure = e;
|
||||
}
|
||||
}
|
||||
if (checkFailure != null) {
|
||||
throw checkFailure;// check failed - throw last failure
|
||||
|
||||
if (trustManager == null) {
|
||||
throw new CertStoreException(
|
||||
"Failed to load X509 TrustManager from " + caCertsFile.getAbsolutePath());
|
||||
}
|
||||
|
||||
return trustManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pack ordered list of certs to create a certificate chain array
|
||||
*
|
||||
* @param cert primary certificate
|
||||
* @param caCerts CA certificate chain.
|
||||
* @return ordered certificate chain
|
||||
@@ -269,6 +135,7 @@ public class ApplicationKeyManagerUtils {
|
||||
|
||||
/**
|
||||
* Export X.509 certificates to the specified outFile.
|
||||
*
|
||||
* @param certificates certificates to be stored
|
||||
* @param outFile output file
|
||||
* @throws IOException if error occurs writing to outFile
|
||||
@@ -300,6 +167,7 @@ public class ApplicationKeyManagerUtils {
|
||||
/**
|
||||
* Generate a new {@link X509Certificate} with RSA {@link KeyPair} and create/update a {@link KeyStore}
|
||||
* optionally backed by a keyFile.
|
||||
*
|
||||
* @param alias entry alias with keystore
|
||||
* @param dn distinguished name (e.g., "CN=Ghidra Test, O=Ghidra, OU=Test, C=US" )
|
||||
* @param durationDays number of days which generated certificate should remain valid
|
||||
@@ -407,7 +275,7 @@ public class ApplicationKeyManagerUtils {
|
||||
keyStore.store(out, protectedPassphrase);
|
||||
out.flush();
|
||||
out.getFD().sync();
|
||||
Msg.debug(ApplicationKeyManagerUtils.class,
|
||||
Msg.debug(PKIUtils.class,
|
||||
out.getChannel().size() + " bytes written to key/cert file: " + keyFile);
|
||||
}
|
||||
catch (SyncFailedException e) {
|
||||
@@ -420,8 +288,7 @@ public class ApplicationKeyManagerUtils {
|
||||
keyFile.setWritable(false);
|
||||
}
|
||||
|
||||
Msg.debug(ApplicationKeyManagerUtils.class,
|
||||
"Certificate Generated (" + alias + "): " + dn);
|
||||
Msg.debug(PKIUtils.class, "Certificate Generated (" + alias + "): " + dn);
|
||||
|
||||
return keyStore;
|
||||
}
|
||||
@@ -441,6 +308,7 @@ public class ApplicationKeyManagerUtils {
|
||||
/**
|
||||
* Generate a new {@link X509Certificate} with RSA {@link KeyPair} and create/update a {@link KeyStore}
|
||||
* optionally backed by a keyFile.
|
||||
*
|
||||
* @param alias entry alias with keystore
|
||||
* @param dn distinguished name (e.g., "CN=Ghidra Test, O=Ghidra, OU=Test, C=US" )
|
||||
* @param durationDays number of days which generated certificate should remain valid
|
||||
@@ -477,4 +345,224 @@ public class ApplicationKeyManagerUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the all certificates from the specified certificate store in a standard
|
||||
* X.509 form (e.g., concatenation of Base64 encoded certificates: *.pem, *.crt, *.cer, *.der)
|
||||
* or Java JKS (*.jks) form.
|
||||
*
|
||||
* @param certsPath certificate(s) storage file path
|
||||
* @return KeyStore containing certificates
|
||||
* @throws IOException if failure occurred reading and processing keystore file.
|
||||
* @throws NoSuchAlgorithmException if the algorithm used to check the integrity of the
|
||||
* keystore cannot be found
|
||||
* @throws CertificateException if any of the certificates in the keystore could not be loaded
|
||||
* @throws KeyStoreException if a general error occurred opening/processing keystore
|
||||
*/
|
||||
public static KeyStore loadCertificateStore(String certsPath)
|
||||
throws IOException, KeyStoreException, NoSuchAlgorithmException, CertificateException {
|
||||
|
||||
int certCount = 0;
|
||||
|
||||
KeyStore store = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||
store.load(null);
|
||||
|
||||
// Attempt to read certificates in Base64 encoded form
|
||||
InputStream fis = new FileInputStream(certsPath);
|
||||
BufferedInputStream bis = new BufferedInputStream(fis);
|
||||
|
||||
try {
|
||||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||
while (bis.available() > 0) {
|
||||
try {
|
||||
Certificate cert = cf.generateCertificate(bis);
|
||||
if (cert instanceof X509Certificate) {
|
||||
X509Certificate x509Cert = (X509Certificate) cert;
|
||||
String name = getCommonName(x509Cert.getSubjectX500Principal());
|
||||
store.setCertificateEntry(name, cert);
|
||||
++certCount;
|
||||
}
|
||||
}
|
||||
catch (CertificateException e) {
|
||||
// Must handle blank lines at bottom of file
|
||||
Throwable cause = e.getCause();
|
||||
if (cause != null && "Empty input".equals(cause.getMessage())) {
|
||||
break; // end of file
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
bis.close();
|
||||
}
|
||||
|
||||
if (certCount == 0) {
|
||||
// Processing JKS files above produce "Empty input", if no certs read
|
||||
// try reading as keystore without password
|
||||
return getKeyStoreInstance(certsPath, null);
|
||||
}
|
||||
return store;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to load a client/server keystore in a PKCS12 form (*.p12, *.pks, *.pfx) or
|
||||
* Java JKS (*.jks) form.
|
||||
*
|
||||
* @param keystorePath JKS or PKCS12 keystore file path
|
||||
* @param password keystore password
|
||||
* @return keystore instance
|
||||
* @throws IOException if failure occurred reading and processing keystore file or if the
|
||||
* given password was incorrect. If the error is due to a wrong password, the
|
||||
* {@link Throwable#getCause cause} of the {@code IOException} should be an
|
||||
* {@code UnrecoverableKeyException}
|
||||
* @throws NoSuchAlgorithmException if the algorithm used to check the integrity of the
|
||||
* keystore cannot be found
|
||||
* @throws CertificateException if any of the certificates in the keystore could not be loaded
|
||||
* @throws KeyStoreException if a general error occurred opening/processing keystore
|
||||
*/
|
||||
public static synchronized KeyStore getKeyStoreInstance(String keystorePath, char[] password)
|
||||
throws IOException, KeyStoreException, NoSuchAlgorithmException, CertificateException {
|
||||
|
||||
String type = PKIUtils.detectKeyStoreType(keystorePath);
|
||||
if (type == null) {
|
||||
throw new KeyStoreException("Unsupported PKI key store file type: " + keystorePath);
|
||||
}
|
||||
|
||||
KeyStore ks = KeyStore.getInstance(type);
|
||||
|
||||
InputStream fis = new FileInputStream(keystorePath);
|
||||
BufferedInputStream bis = new BufferedInputStream(fis);
|
||||
try {
|
||||
ks.load(bis, password);
|
||||
}
|
||||
finally {
|
||||
bis.close();
|
||||
}
|
||||
return ks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to detect PKI KeyStore type ("JKS" or "PKCS12") for the specified file.
|
||||
*
|
||||
* @param keystorePath key store file path
|
||||
* @return "JKS", "PKCS12" or null
|
||||
* @throws IOException if file read error occurs
|
||||
*/
|
||||
public static String detectKeyStoreType(String keystorePath) throws IOException {
|
||||
try (FileInputStream fis = new FileInputStream(keystorePath)) {
|
||||
byte[] header = new byte[4];
|
||||
int read = fis.read(header);
|
||||
if (read < 4) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check for JKS magic number: FEEDFEED
|
||||
if ((header[0] & 0xFF) == 0xFE && (header[1] & 0xFF) == 0xED &&
|
||||
(header[2] & 0xFF) == 0xFE && (header[3] & 0xFF) == 0xED) {
|
||||
return "JKS";
|
||||
}
|
||||
|
||||
// Check for PKCS12: starts with 0x30 0x82
|
||||
if ((header[0] & 0xFF) == 0x30 && (header[1] & 0xFF) == 0x82) {
|
||||
return "PKCS12";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract Common Name (CN) from specified principal subject Distinguished Name (DN)
|
||||
*
|
||||
* @param subject X.509 certificate subject
|
||||
* @return Common Name or full subject name if unable to extract CN from DN
|
||||
*/
|
||||
private static String getCommonName(Principal subject) {
|
||||
|
||||
// Subject name should be distinguished-name (DN) which starts with common-name (CN)
|
||||
String name = subject.getName();
|
||||
int commaIndex = name.indexOf(',');
|
||||
String firstElement = commaIndex < 0 ? name : name.substring(0, commaIndex);
|
||||
|
||||
int equalsIndex = firstElement.indexOf('=');
|
||||
if (equalsIndex <= 0) {
|
||||
return name; // bad common name
|
||||
}
|
||||
|
||||
String fieldName = firstElement.substring(0, equalsIndex).trim();
|
||||
String fieldValue = firstElement.substring(equalsIndex + 1).trim();
|
||||
|
||||
if (!fieldName.equalsIgnoreCase("CN")) {
|
||||
return name; // bad common name
|
||||
}
|
||||
|
||||
return fieldValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log all X509 certificates contained within keystore
|
||||
*
|
||||
* @param keyStore certificate keystore
|
||||
*/
|
||||
static void logCerts(KeyStore keyStore) {
|
||||
try {
|
||||
Enumeration<String> aliases = keyStore.aliases();
|
||||
while (aliases.hasMoreElements()) {
|
||||
String alias = aliases.nextElement();
|
||||
Certificate certificate = keyStore.getCertificate(alias);
|
||||
if (certificate == null) {
|
||||
continue;
|
||||
}
|
||||
else if (certificate instanceof X509Certificate) {
|
||||
logCert(alias, (X509Certificate) certificate);
|
||||
}
|
||||
else {
|
||||
Msg.warn(PKIUtils.class, "Ignore unrecognized certificate: alias=" + alias +
|
||||
", type=" + certificate.getType());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (KeyStoreException e) {
|
||||
Msg.error(PKIUtils.class, "KeyStore failure", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log all X509 certificates contained within array
|
||||
*
|
||||
* @param x509Certs array of certificates
|
||||
*/
|
||||
public static void logCerts(X509Certificate[] x509Certs) {
|
||||
for (X509Certificate x509Cert : x509Certs) {
|
||||
logCert(null, x509Cert);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log specified X509 certificate details
|
||||
*
|
||||
* @param alias certificate alias or null if not applicable
|
||||
* @param x509Cert X509 certificate
|
||||
*/
|
||||
static void logCert(String alias, X509Certificate x509Cert) {
|
||||
|
||||
X500Principal subj = x509Cert.getSubjectX500Principal();
|
||||
X500Principal issuer = x509Cert.getIssuerX500Principal();
|
||||
|
||||
Date now = new Date();
|
||||
|
||||
String label = alias != null ? (alias + ": ") : "";
|
||||
if (now.compareTo(x509Cert.getNotAfter()) > 0) {
|
||||
Msg.warn(PKIUtils.class,
|
||||
" " + label + getCommonName(subj) + ", issued by " + getCommonName(issuer) +
|
||||
", S/N " + x509Cert.getSerialNumber().toString(16) + ", expired " +
|
||||
x509Cert.getNotAfter() + " **EXPIRED**");
|
||||
}
|
||||
else {
|
||||
Msg.info(PKIUtils.class,
|
||||
" " + label + getCommonName(subj) + ", issued by " + getCommonName(issuer) +
|
||||
", S/N " + x509Cert.getSerialNumber().toString(16) + ", expires " +
|
||||
x509Cert.getNotAfter());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -19,12 +19,11 @@ import java.io.*;
|
||||
import java.net.*;
|
||||
import java.util.Properties;
|
||||
|
||||
import ghidra.net.ApplicationKeyManagerFactory;
|
||||
import ghidra.net.DefaultKeyManagerFactory;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
public class HttpUtil {
|
||||
|
||||
|
||||
/**
|
||||
* Execute an HTTP/HTTPS GET request and return the resulting HttpURLConnection.
|
||||
* @param httpUrlString HTTP/HTTPS URL
|
||||
@@ -35,16 +34,16 @@ public class HttpUtil {
|
||||
* @throws IOException if an error occurs while executing request
|
||||
*/
|
||||
public static HttpURLConnection getContent(String httpUrlString,
|
||||
Properties httpRequestProperties, boolean allowRedirect) throws MalformedURLException,
|
||||
IOException {
|
||||
Properties httpRequestProperties, boolean allowRedirect)
|
||||
throws MalformedURLException, IOException {
|
||||
|
||||
URL url = new URL(httpUrlString);
|
||||
String protocol = url.getProtocol();
|
||||
|
||||
if ("https".equals(protocol)) {
|
||||
// force password prompt before connecting
|
||||
if (!ApplicationKeyManagerFactory.initialize()) {
|
||||
if (ApplicationKeyManagerFactory.getKeyStore() != null) {
|
||||
if (!DefaultKeyManagerFactory.initialize()) {
|
||||
if (DefaultKeyManagerFactory.getKeyStore() != null) {
|
||||
// Report error condition?
|
||||
throw new IOException("Failed to initialize PKI certificate keystore");
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -21,7 +21,6 @@ import java.io.File;
|
||||
import java.security.*;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import javax.net.ssl.KeyManager;
|
||||
import javax.net.ssl.X509ExtendedKeyManager;
|
||||
|
||||
import org.junit.*;
|
||||
@@ -73,8 +72,8 @@ public class ApplicationKeyManagerFactoryTest extends AbstractGenericTest {
|
||||
keystoreFile = createTempFile("test-key", ".p12");
|
||||
keystoreFile.delete();
|
||||
|
||||
ApplicationKeyManagerUtils.createKeyStore(ALIAS, TEST_IDENTITY, 2, null, keystoreFile,
|
||||
"PKCS12", null, TEST_PWD.toCharArray());
|
||||
PKIUtils.createKeyStore(ALIAS, TEST_IDENTITY, 2, null, keystoreFile, "PKCS12", null,
|
||||
TEST_PWD.toCharArray());
|
||||
|
||||
ApplicationKeyManagerFactory.setKeyStorePasswordProvider(passwordProvider);
|
||||
}
|
||||
@@ -89,12 +88,9 @@ public class ApplicationKeyManagerFactoryTest extends AbstractGenericTest {
|
||||
@Test
|
||||
public void testCancelledPasswordOnSetCertificate() throws Exception {
|
||||
|
||||
assertNull(ApplicationKeyManagerFactory.getKeyStore());
|
||||
ApplicationKeyManagerFactory instance = ApplicationKeyManagerFactory.getInstance();
|
||||
KeyManager[] keyManagers = instance.getKeyManagers();
|
||||
assertEquals(1, keyManagers.length);
|
||||
assertTrue("", keyManagers[0] instanceof X509ExtendedKeyManager);
|
||||
X509ExtendedKeyManager keyManager = (X509ExtendedKeyManager) keyManagers[0];
|
||||
assertNull(DefaultKeyManagerFactory.getKeyStore());
|
||||
X509ExtendedKeyManager keyManager = DefaultKeyManagerFactory.getKeyManager();
|
||||
assertNotNull(keyManager);
|
||||
|
||||
// verify that no certs are installed
|
||||
assertNull(keyManager.getCertificateChain(ALIAS));
|
||||
@@ -102,10 +98,10 @@ public class ApplicationKeyManagerFactoryTest extends AbstractGenericTest {
|
||||
|
||||
passwordProvider.cancelNextEntry();
|
||||
|
||||
ApplicationKeyManagerFactory.setKeyStore(keystoreFile.getAbsolutePath(), false);
|
||||
DefaultKeyManagerFactory.setDefaultKeyStore(keystoreFile.getAbsolutePath(), false);
|
||||
|
||||
// verify that no certs are installed
|
||||
assertEquals(null, ApplicationKeyManagerFactory.getKeyStore());
|
||||
assertEquals(null, DefaultKeyManagerFactory.getKeyStore());
|
||||
assertNull(keyManager.getCertificateChain(ALIAS));
|
||||
assertNull(keyManager.getClientAliases("RSA", null));
|
||||
}
|
||||
@@ -113,21 +109,18 @@ public class ApplicationKeyManagerFactoryTest extends AbstractGenericTest {
|
||||
@Test
|
||||
public void testSetClearCertificate() throws Exception {
|
||||
|
||||
assertNull(ApplicationKeyManagerFactory.getKeyStore());
|
||||
ApplicationKeyManagerFactory instance = ApplicationKeyManagerFactory.getInstance();
|
||||
KeyManager[] keyManagers = instance.getKeyManagers();
|
||||
assertEquals(1, keyManagers.length);
|
||||
assertTrue("", keyManagers[0] instanceof X509ExtendedKeyManager);
|
||||
X509ExtendedKeyManager keyManager = (X509ExtendedKeyManager) keyManagers[0];
|
||||
assertNull(DefaultKeyManagerFactory.getKeyStore());
|
||||
X509ExtendedKeyManager keyManager = DefaultKeyManagerFactory.getKeyManager();
|
||||
assertNotNull(keyManager);
|
||||
|
||||
// verify that no certs are installed
|
||||
assertNull(keyManager.getCertificateChain(ALIAS));
|
||||
assertNull(keyManager.getClientAliases("RSA", null));
|
||||
|
||||
ApplicationKeyManagerFactory.setKeyStore(keystoreFile.getAbsolutePath(), false);
|
||||
DefaultKeyManagerFactory.setDefaultKeyStore(keystoreFile.getAbsolutePath(), false);
|
||||
|
||||
// verify that generated cert is installed
|
||||
assertEquals(keystoreFile.getAbsolutePath(), ApplicationKeyManagerFactory.getKeyStore());
|
||||
assertEquals(keystoreFile.getAbsolutePath(), DefaultKeyManagerFactory.getKeyStore());
|
||||
X509Certificate[] chain = keyManager.getCertificateChain(ALIAS);
|
||||
assertNotNull(chain);
|
||||
String[] aliases = keyManager.getClientAliases("RSA", new Principal[0]); // any CA allowed
|
||||
@@ -158,10 +151,10 @@ public class ApplicationKeyManagerFactoryTest extends AbstractGenericTest {
|
||||
}
|
||||
|
||||
// clear keystore
|
||||
ApplicationKeyManagerFactory.setKeyStore(null, false);
|
||||
DefaultKeyManagerFactory.setDefaultKeyStore(null, false);
|
||||
|
||||
// verify that no certs are installed
|
||||
assertNull(ApplicationKeyManagerFactory.getKeyStore());
|
||||
assertNull(DefaultKeyManagerFactory.getKeyStore());
|
||||
assertNull(keyManager.getCertificateChain(ALIAS));
|
||||
assertNull(keyManager.getClientAliases("RSA", null));
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -24,8 +24,8 @@ import docking.tool.ToolConstants;
|
||||
import docking.widgets.OptionDialog;
|
||||
import docking.widgets.filechooser.GhidraFileChooser;
|
||||
import docking.widgets.filechooser.GhidraFileChooserMode;
|
||||
import ghidra.net.ApplicationKeyManagerFactory;
|
||||
import ghidra.net.ApplicationKeyManagerUtils;
|
||||
import ghidra.net.DefaultKeyManagerFactory;
|
||||
import ghidra.net.PKIUtils;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.filechooser.ExtensionFileFilter;
|
||||
import ghidra.util.filechooser.GhidraFileFilter;
|
||||
@@ -38,7 +38,7 @@ class EditActionManager {
|
||||
* PKCS Private Key/Certificate File Filter
|
||||
*/
|
||||
public static final GhidraFileFilter CERTIFICATE_FILE_FILTER =
|
||||
new ExtensionFileFilter(ApplicationKeyManagerUtils.PKCS_FILE_EXTENSIONS, "PKCS Key File");
|
||||
new ExtensionFileFilter(PKIUtils.PKCS_FILE_EXTENSIONS, "PKCS Key File");
|
||||
|
||||
private FrontEndPlugin plugin;
|
||||
private FrontEndTool tool;
|
||||
@@ -68,8 +68,8 @@ class EditActionManager {
|
||||
// ACTIONS - auto generated
|
||||
editPluginPathAction.setEnabled(true);
|
||||
|
||||
editPluginPathAction.setMenuBarData(new MenuData(new String[] { ToolConstants.MENU_EDIT,
|
||||
"Plugin Path..." }, "GEdit"));
|
||||
editPluginPathAction.setMenuBarData(
|
||||
new MenuData(new String[] { ToolConstants.MENU_EDIT, "Plugin Path..." }, "GEdit"));
|
||||
|
||||
editCertPathAction = new DockingAction("Set PKI Certificate", plugin.getName()) {
|
||||
@Override
|
||||
@@ -80,8 +80,8 @@ class EditActionManager {
|
||||
// ACTIONS - auto generated
|
||||
editCertPathAction.setEnabled(true);
|
||||
|
||||
editCertPathAction.setMenuBarData(new MenuData(new String[] { ToolConstants.MENU_EDIT,
|
||||
"Set PKI Certificate..." }, "PKI"));
|
||||
editCertPathAction.setMenuBarData(new MenuData(
|
||||
new String[] { ToolConstants.MENU_EDIT, "Set PKI Certificate..." }, "PKI"));
|
||||
|
||||
clearCertPathAction = new DockingAction("Clear PKI Certificate", plugin.getName()) {
|
||||
@Override
|
||||
@@ -90,13 +90,13 @@ class EditActionManager {
|
||||
}
|
||||
};
|
||||
// ACTIONS - auto generated
|
||||
clearCertPathAction.setEnabled(ApplicationKeyManagerFactory.getKeyStore() != null);
|
||||
clearCertPathAction.setEnabled(DefaultKeyManagerFactory.getKeyStore() != null);
|
||||
|
||||
clearCertPathAction.setMenuBarData(new MenuData(new String[] { ToolConstants.MENU_EDIT,
|
||||
"Clear PKI Certificate..." }, "PKI"));
|
||||
clearCertPathAction.setMenuBarData(new MenuData(
|
||||
new String[] { ToolConstants.MENU_EDIT, "Clear PKI Certificate..." }, "PKI"));
|
||||
|
||||
clearCertPathAction.setHelpLocation(new HelpLocation("FrontEndPlugin",
|
||||
"Set_PKI_Certificate"));
|
||||
clearCertPathAction
|
||||
.setHelpLocation(new HelpLocation("FrontEndPlugin", "Set_PKI_Certificate"));
|
||||
tool.addAction(editCertPathAction);
|
||||
tool.addAction(clearCertPathAction);
|
||||
tool.addAction(editPluginPathAction);
|
||||
@@ -112,7 +112,7 @@ class EditActionManager {
|
||||
|
||||
private void clearCertPath() {
|
||||
|
||||
String path = ApplicationKeyManagerFactory.getKeyStore();
|
||||
String path = DefaultKeyManagerFactory.getKeyStore();
|
||||
if (path == null) {
|
||||
// unexpected
|
||||
clearCertPathAction.setEnabled(false);
|
||||
@@ -124,7 +124,7 @@ class EditActionManager {
|
||||
return;
|
||||
}
|
||||
|
||||
ApplicationKeyManagerFactory.setKeyStore(null, true);
|
||||
DefaultKeyManagerFactory.setDefaultKeyStore(null, true);
|
||||
clearCertPathAction.setEnabled(false);
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ class EditActionManager {
|
||||
|
||||
File dir = null;
|
||||
File oldFile = null;
|
||||
String path = ApplicationKeyManagerFactory.getKeyStore();
|
||||
String path = DefaultKeyManagerFactory.getKeyStore();
|
||||
if (path != null) {
|
||||
oldFile = new File(path);
|
||||
dir = oldFile.getParentFile();
|
||||
@@ -163,7 +163,7 @@ class EditActionManager {
|
||||
if (file == null) {
|
||||
return; // cancelled
|
||||
}
|
||||
ApplicationKeyManagerFactory.setKeyStore(file.getAbsolutePath(), true);
|
||||
DefaultKeyManagerFactory.setDefaultKeyStore(file.getAbsolutePath(), true);
|
||||
clearCertPathAction.setEnabled(true);
|
||||
validInput = true;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import docking.tool.ToolConstants;
|
||||
import docking.widgets.OptionDialog;
|
||||
import docking.widgets.PasswordChangeDialog;
|
||||
import docking.widgets.filechooser.GhidraFileChooser;
|
||||
import generic.hash.HashUtilities;
|
||||
import ghidra.framework.client.ClientUtil;
|
||||
import ghidra.framework.client.RepositoryAdapter;
|
||||
import ghidra.framework.model.*;
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -33,7 +33,7 @@ import org.junit.experimental.categories.Category;
|
||||
import generic.test.category.PortSensitiveCategory;
|
||||
import ghidra.framework.model.ServerInfo;
|
||||
import ghidra.framework.remote.GhidraServerHandle;
|
||||
import ghidra.net.ApplicationKeyManagerFactory;
|
||||
import ghidra.net.DefaultKeyManagerFactory;
|
||||
import ghidra.server.remote.ServerTestUtil;
|
||||
import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
@@ -46,7 +46,7 @@ public class GhidraServerSerialFilterFailureTest extends AbstractGhidraHeadlessI
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
System.clearProperty(ApplicationKeyManagerFactory.KEYSTORE_PATH_PROPERTY);
|
||||
System.clearProperty(DefaultKeyManagerFactory.KEYSTORE_PATH_PROPERTY);
|
||||
}
|
||||
|
||||
@After
|
||||
@@ -79,7 +79,6 @@ public class GhidraServerSerialFilterFailureTest extends AbstractGhidraHeadlessI
|
||||
enableAnonymous);
|
||||
}
|
||||
|
||||
|
||||
static class BogusPrincipal implements Principal, java.io.Serializable {
|
||||
|
||||
private String username;
|
||||
@@ -110,10 +109,10 @@ public class GhidraServerSerialFilterFailureTest extends AbstractGhidraHeadlessI
|
||||
startServer(-1, false, false, false);
|
||||
|
||||
ServerInfo server = new ServerInfo("localhost", ServerTestUtil.GHIDRA_TEST_SERVER_PORT);
|
||||
|
||||
|
||||
GhidraServerHandle serverHandle =
|
||||
ServerConnectTask.getGhidraServerHandle(server, TaskMonitor.DUMMY);
|
||||
|
||||
|
||||
try {
|
||||
serverHandle.getRepositoryServer(getBogusUserSubject(), new Callback[0]);
|
||||
fail("serial filter rejection failed to perform");
|
||||
@@ -125,7 +124,7 @@ public class GhidraServerSerialFilterFailureTest extends AbstractGhidraHeadlessI
|
||||
assertTrue("expected remote invalid class exceptionn",
|
||||
cause instanceof InvalidClassException);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import javax.rmi.ssl.SslRMIClientSocketFactory;
|
||||
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
|
||||
import generic.hash.HashUtilities;
|
||||
import generic.test.*;
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.framework.client.*;
|
||||
@@ -424,9 +425,9 @@ public class ServerTestUtil {
|
||||
" Enable Anonymous Login: " + enableAnonymousAuthentication);
|
||||
|
||||
// Force client-side use of newly generated CA certificates
|
||||
System.setProperty(ApplicationTrustManagerFactory.GHIDRA_CACERTS_PATH_PROPERTY,
|
||||
System.setProperty(DefaultTrustManagerFactory.GHIDRA_CACERTS_PATH_PROPERTY,
|
||||
getTestPkiCACertsPath());
|
||||
SSLContextInitializer.initialize(true);
|
||||
DefaultSSLContextInitializer.initialize(true);
|
||||
|
||||
ArrayList<String> argList = new ArrayList<>();
|
||||
String javaCommand =
|
||||
@@ -441,11 +442,11 @@ public class ServerTestUtil {
|
||||
argList.add("-Xdebug");
|
||||
argList.add("-Xnoagent");
|
||||
argList.add("-Djava.compiler=NONE");
|
||||
argList.add("-D" + ApplicationTrustManagerFactory.GHIDRA_CACERTS_PATH_PROPERTY + "=" +
|
||||
argList.add("-D" + DefaultTrustManagerFactory.GHIDRA_CACERTS_PATH_PROPERTY + "=" +
|
||||
getTestPkiCACertsPath());
|
||||
argList.add("-D" + ApplicationKeyManagerFactory.KEYSTORE_PATH_PROPERTY + "=" +
|
||||
argList.add("-D" + DefaultKeyManagerFactory.KEYSTORE_PATH_PROPERTY + "=" +
|
||||
getTestPkiServerKeystorePath());
|
||||
argList.add("-D" + ApplicationKeyManagerFactory.KEYSTORE_PASSWORD_PROPERTY + "=" +
|
||||
argList.add("-D" + DefaultKeyManagerFactory.KEYSTORE_PASSWORD_PROPERTY + "=" +
|
||||
TEST_PKI_SERVER_PASSPHRASE);
|
||||
argList.add("-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=*:18202"); // *: for remote debug support
|
||||
argList.add("-DSystemUtilities.isTesting=true");
|
||||
@@ -622,7 +623,7 @@ public class ServerTestUtil {
|
||||
|
||||
public static synchronized void disposeServer() {
|
||||
|
||||
System.setProperty(ApplicationTrustManagerFactory.GHIDRA_CACERTS_PATH_PROPERTY, "");
|
||||
System.setProperty(DefaultTrustManagerFactory.GHIDRA_CACERTS_PATH_PROPERTY, "");
|
||||
|
||||
if (serverProcess != null) {
|
||||
|
||||
@@ -939,23 +940,22 @@ public class ServerTestUtil {
|
||||
|
||||
// Generate CA certificate and keystore
|
||||
Msg.info(ServerTestUtil.class, "Generating self-signed CA cert: " + caPath);
|
||||
PrivateKeyEntry caEntry =
|
||||
ApplicationKeyManagerUtils.createKeyEntry("test-CA", TEST_PKI_CA_DN, 2, null, null,
|
||||
"PKCS12", null, ApplicationKeyManagerFactory.DEFAULT_PASSWORD.toCharArray());
|
||||
ApplicationKeyManagerUtils.exportX509Certificates(caEntry.getCertificateChain(), caFile);
|
||||
PrivateKeyEntry caEntry = PKIUtils.createKeyEntry("test-CA", TEST_PKI_CA_DN, 2, null, null,
|
||||
"PKCS12", null, DefaultKeyManagerFactory.DEFAULT_PASSWORD.toCharArray());
|
||||
PKIUtils.exportX509Certificates(caEntry.getCertificateChain(), caFile);
|
||||
|
||||
// Generate User/Client certificate and keystore
|
||||
Msg.info(ServerTestUtil.class, "Generating test user key/cert (signed by test-CA, pwd: " +
|
||||
TEST_PKI_USER_PASSPHRASE + "): " + userKeystorePath);
|
||||
ApplicationKeyManagerUtils.createKeyEntry("test-sig", TEST_PKI_USER_DN, 2, caEntry,
|
||||
userKeystoreFile, "PKCS12", null, TEST_PKI_USER_PASSPHRASE.toCharArray());
|
||||
PKIUtils.createKeyEntry("test-sig", TEST_PKI_USER_DN, 2, caEntry, userKeystoreFile,
|
||||
"PKCS12", null, TEST_PKI_USER_PASSPHRASE.toCharArray());
|
||||
|
||||
// Generate Server certificate and keystore
|
||||
Msg.info(ServerTestUtil.class, "Generating test server key/cert (signed by test-CA, pwd: " +
|
||||
TEST_PKI_SERVER_PASSPHRASE + "): " + serverKeystorePath);
|
||||
|
||||
ApplicationKeyManagerUtils.createKeyEntry("test-sig", TEST_PKI_SERVER_DN, 2, caEntry,
|
||||
serverKeystoreFile, "PKCS12", null, TEST_PKI_SERVER_PASSPHRASE.toCharArray());
|
||||
PKIUtils.createKeyEntry("test-sig", TEST_PKI_SERVER_DN, 2, caEntry, serverKeystoreFile,
|
||||
"PKCS12", null, TEST_PKI_SERVER_PASSPHRASE.toCharArray());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user