From 78729379e471bbb3d969409be6a8c3d24af84220 Mon Sep 17 00:00:00 2001 From: ghidra1 Date: Tue, 9 Dec 2025 18:56:51 -0500 Subject: [PATCH] GP-6155 Refactor of PKI framework support --- .../bsim/query/BSimControlLaunchable.java | 11 +- .../bsim/query/ingest/BSimLaunchable.java | 4 +- .../HeadlessBSimApplicationConfiguration.java | 8 +- .../query/facade/TestNearestVectorResult.java | 6 +- .../query/facade/TestSimilarityResult.java | 6 +- ...ibreTranslateStringTranslationService.java | 3 +- .../util/opinion/AbstractProgramLoader.java | 2 +- .../formats/gfilesystem/FSUtilities.java | 1 + .../ghidra/formats/gfilesystem/FileCache.java | 1 + ...eadlessGhidraApplicationConfiguration.java | 8 +- .../file/formats/cart/CartFileSystem.java | 2 +- .../file/formats/cart/CartV1Constants.java | 6 +- .../file/formats/cart/CartV1FileTest.java | 6 +- .../main/java/ghidra/server/ServerAdmin.java | 5 +- .../main/java/ghidra/server/UserManager.java | 5 +- .../ghidra/server/remote/GhidraServer.java | 15 +- .../security/PKIAuthenticationModule.java | 13 +- .../security/SSHAuthenticationModule.java | 32 +- .../pdb/symbolserver/HttpSymbolServer.java | 14 +- .../ghidra/framework/client/ClientUtil.java | 3 +- .../client/HeadlessClientAuthenticator.java | 4 +- .../client/PasswordClientAuthenticator.java | 4 +- .../client/RepositoryServerAdapter.java | 6 +- .../framework/client/ServerConnectTask.java | 9 +- .../remote/RepositoryServerHandle.java | 6 +- .../main/java/ghidra/util/MD5Utilities.java | 12 +- .../java/generic/hash}/HashUtilities.java | 7 +- .../net/ApplicationKeyManagerFactory.java | 611 +++--------------- .../java/ghidra/net/ApplicationKeyStore.java | 244 ------- .../net/ApplicationSSLSocketFactory.java | 92 --- .../ghidra/net/DefaultKeyManagerFactory.java | 512 +++++++++++++++ ...java => DefaultSSLContextInitializer.java} | 38 +- ...y.java => DefaultTrustManagerFactory.java} | 200 +++--- .../src/main/java/ghidra/net/HttpClients.java | 8 +- ...tionKeyManagerUtils.java => PKIUtils.java} | 432 ++++++++----- .../main/java/ghidra/net/http/HttpUtil.java | 15 +- .../net/ApplicationKeyManagerFactoryTest.java | 39 +- .../framework/main/EditActionManager.java | 36 +- .../framework/main/ProjectActionManager.java | 1 + .../GhidraServerSerialFilterFailureTest.java | 15 +- .../ghidra/server/remote/ServerTestUtil.java | 28 +- 41 files changed, 1177 insertions(+), 1293 deletions(-) rename Ghidra/Framework/{FileSystem/src/main/java/ghidra/util => Generic/src/main/java/generic/hash}/HashUtilities.java (99%) delete mode 100644 Ghidra/Framework/Generic/src/main/java/ghidra/net/ApplicationKeyStore.java delete mode 100644 Ghidra/Framework/Generic/src/main/java/ghidra/net/ApplicationSSLSocketFactory.java create mode 100644 Ghidra/Framework/Generic/src/main/java/ghidra/net/DefaultKeyManagerFactory.java rename Ghidra/Framework/Generic/src/main/java/ghidra/net/{SSLContextInitializer.java => DefaultSSLContextInitializer.java} (79%) rename Ghidra/Framework/Generic/src/main/java/ghidra/net/{ApplicationTrustManagerFactory.java => DefaultTrustManagerFactory.java} (54%) rename Ghidra/Framework/Generic/src/main/java/ghidra/net/{ApplicationKeyManagerUtils.java => PKIUtils.java} (56%) diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimControlLaunchable.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimControlLaunchable.java index 71c22d0119..d6d70a5b65 100644 --- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimControlLaunchable.java +++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimControlLaunchable.java @@ -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); diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/ingest/BSimLaunchable.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/ingest/BSimLaunchable.java index 38ac030c6f..1ed9d2ed09 100644 --- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/ingest/BSimLaunchable.java +++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/ingest/BSimLaunchable.java @@ -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(); diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/ingest/HeadlessBSimApplicationConfiguration.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/ingest/HeadlessBSimApplicationConfiguration.java index 3c14e7ccc4..d2df0bb25c 100644 --- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/ingest/HeadlessBSimApplicationConfiguration.java +++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/ingest/HeadlessBSimApplicationConfiguration.java @@ -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; } diff --git a/Ghidra/Features/BSim/src/test/java/ghidra/features/bsim/query/facade/TestNearestVectorResult.java b/Ghidra/Features/BSim/src/test/java/ghidra/features/bsim/query/facade/TestNearestVectorResult.java index 2ae399c0b1..0e028c5ae2 100644 --- a/Ghidra/Features/BSim/src/test/java/ghidra/features/bsim/query/facade/TestNearestVectorResult.java +++ b/Ghidra/Features/BSim/src/test/java/ghidra/features/bsim/query/facade/TestNearestVectorResult.java @@ -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 { diff --git a/Ghidra/Features/BSim/src/test/java/ghidra/features/bsim/query/facade/TestSimilarityResult.java b/Ghidra/Features/BSim/src/test/java/ghidra/features/bsim/query/facade/TestSimilarityResult.java index 30ae1dddc5..ee0ed96083 100755 --- a/Ghidra/Features/BSim/src/test/java/ghidra/features/bsim/query/facade/TestSimilarityResult.java +++ b/Ghidra/Features/BSim/src/test/java/ghidra/features/bsim/query/facade/TestSimilarityResult.java @@ -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(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/string/translate/libretranslate/LibreTranslateStringTranslationService.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/string/translate/libretranslate/LibreTranslateStringTranslationService.java index 11d181616d..2b090947db 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/string/translate/libretranslate/LibreTranslateStringTranslationService.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/string/translate/libretranslate/LibreTranslateStringTranslationService.java @@ -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 { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractProgramLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractProgramLoader.java index 5814c95c35..0a3429487b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractProgramLoader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractProgramLoader.java @@ -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; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FSUtilities.java b/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FSUtilities.java index f0b3a8b352..6938b30e13 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FSUtilities.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FSUtilities.java @@ -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.fileinfo.FileType; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FileCache.java b/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FileCache.java index adf205dad8..3e9e14bde5 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FileCache.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FileCache.java @@ -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; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/framework/HeadlessGhidraApplicationConfiguration.java b/Ghidra/Features/Base/src/main/java/ghidra/framework/HeadlessGhidraApplicationConfiguration.java index 7086678cb3..219f0d9009 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/framework/HeadlessGhidraApplicationConfiguration.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/framework/HeadlessGhidraApplicationConfiguration.java @@ -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; } diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cart/CartFileSystem.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cart/CartFileSystem.java index 79d0b8e48d..b6560306a6 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cart/CartFileSystem.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cart/CartFileSystem.java @@ -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; diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cart/CartV1Constants.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cart/CartV1Constants.java index 0dfd1ac1ac..7c2be290a7 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cart/CartV1Constants.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cart/CartV1Constants.java @@ -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 diff --git a/Ghidra/Features/FileFormats/src/test/java/ghidra/file/formats/cart/CartV1FileTest.java b/Ghidra/Features/FileFormats/src/test/java/ghidra/file/formats/cart/CartV1FileTest.java index 9bed6b0ca2..46f7c767ef 100644 --- a/Ghidra/Features/FileFormats/src/test/java/ghidra/file/formats/cart/CartV1FileTest.java +++ b/Ghidra/Features/FileFormats/src/test/java/ghidra/file/formats/cart/CartV1FileTest.java @@ -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; diff --git a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/ServerAdmin.java b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/ServerAdmin.java index f74c604142..2526b30467 100644 --- a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/ServerAdmin.java +++ b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/ServerAdmin.java @@ -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; diff --git a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/UserManager.java b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/UserManager.java index aaca24b44e..634734ff32 100644 --- a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/UserManager.java +++ b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/UserManager.java @@ -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.*; diff --git a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/remote/GhidraServer.java b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/remote/GhidraServer.java index bbd374ff10..70839d598f 100644 --- a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/remote/GhidraServer.java +++ b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/remote/GhidraServer.java @@ -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; diff --git a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/security/PKIAuthenticationModule.java b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/security/PKIAuthenticationModule.java index df2abce005..3e160abb21 100644 --- a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/security/PKIAuthenticationModule.java +++ b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/security/PKIAuthenticationModule.java @@ -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) { diff --git a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/security/SSHAuthenticationModule.java b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/security/SSHAuthenticationModule.java index 3fd2ab4df1..ab6fab8c31 100644 --- a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/security/SSHAuthenticationModule.java +++ b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/security/SSHAuthenticationModule.java @@ -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) { diff --git a/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/HttpSymbolServer.java b/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/HttpSymbolServer.java index e424ed8ca5..a711b068d3 100644 --- a/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/HttpSymbolServer.java +++ b/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/HttpSymbolServer.java @@ -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> 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 { diff --git a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/ClientUtil.java b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/ClientUtil.java index ab6bc6ebb1..be41582f44 100644 --- a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/ClientUtil.java +++ b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/ClientUtil.java @@ -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 '" + diff --git a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/HeadlessClientAuthenticator.java b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/HeadlessClientAuthenticator.java index 5150e8a552..0d38cd7bb3 100644 --- a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/HeadlessClientAuthenticator.java +++ b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/HeadlessClientAuthenticator.java @@ -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); diff --git a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/PasswordClientAuthenticator.java b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/PasswordClientAuthenticator.java index 33cc27e35a..bf8cf09153 100644 --- a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/PasswordClientAuthenticator.java +++ b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/PasswordClientAuthenticator.java @@ -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; /** * PasswordClientAuthenticator 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 { diff --git a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/RepositoryServerAdapter.java b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/RepositoryServerAdapter.java index ff21415bab..30c7471d8c 100644 --- a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/RepositoryServerAdapter.java +++ b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/RepositoryServerAdapter.java @@ -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 { diff --git a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/ServerConnectTask.java b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/ServerConnectTask.java index b367cb3028..a7353c6864 100644 --- a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/ServerConnectTask.java +++ b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/ServerConnectTask.java @@ -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"); } diff --git a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/remote/RepositoryServerHandle.java b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/remote/RepositoryServerHandle.java index abbd8fb831..0e1737e6b2 100644 --- a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/remote/RepositoryServerHandle.java +++ b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/remote/RepositoryServerHandle.java @@ -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; diff --git a/Ghidra/Framework/FileSystem/src/main/java/ghidra/util/MD5Utilities.java b/Ghidra/Framework/FileSystem/src/main/java/ghidra/util/MD5Utilities.java index e63ea65e8f..5417961c26 100644 --- a/Ghidra/Framework/FileSystem/src/main/java/ghidra/util/MD5Utilities.java +++ b/Ghidra/Framework/FileSystem/src/main/java/ghidra/util/MD5Utilities.java @@ -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); } /** diff --git a/Ghidra/Framework/FileSystem/src/main/java/ghidra/util/HashUtilities.java b/Ghidra/Framework/Generic/src/main/java/generic/hash/HashUtilities.java similarity index 99% rename from Ghidra/Framework/FileSystem/src/main/java/ghidra/util/HashUtilities.java rename to Ghidra/Framework/Generic/src/main/java/generic/hash/HashUtilities.java index ed6900fe04..14444fd10f 100644 --- a/Ghidra/Framework/FileSystem/src/main/java/ghidra/util/HashUtilities.java +++ b/Ghidra/Framework/Generic/src/main/java/generic/hash/HashUtilities.java @@ -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 { diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/net/ApplicationKeyManagerFactory.java b/Ghidra/Framework/Generic/src/main/java/ghidra/net/ApplicationKeyManagerFactory.java index fbbc77c5dc..b14b5af2db 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/net/ApplicationKeyManagerFactory.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/net/ApplicationKeyManagerFactory.java @@ -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; /** - * ApplicationKeyManagerFactory 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 customPasswordProvider - * 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 subjectAlternativeNames; - private static ApplicationKeyManagerFactory instance; + // X509KeyManager cached keyed by file hash concatenated with its canonical path + private static HashMap 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 ghidra.keystore 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 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(); - - /** - * ApplicationKeyManagerFactory constructor - */ - private ApplicationKeyManagerFactory() { - } - - /** - * Get key managers - * @return key managers - */ - KeyManager[] getKeyManagers() { - return new KeyManager[] { keyManagerWrapper }; - } - - /** - * ProtectedKeyStoreData 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 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; } - /** - * ApplicationKeyManager 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 x509KeyManager already exists, this method has no affect. If the - * keystorePath has not already been set, the getPreferredKeyStore() - * method will be invoked to obtain the keystore which should be used in establishing the - * wrappedKeyManager. 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 x509KeyManager 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; - } - } - } diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/net/ApplicationKeyStore.java b/Ghidra/Framework/Generic/src/main/java/ghidra/net/ApplicationKeyStore.java deleted file mode 100644 index 36373183be..0000000000 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/net/ApplicationKeyStore.java +++ /dev/null @@ -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; - -/** - * ApplicationKeyStore 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 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()); - } - } - -} diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/net/ApplicationSSLSocketFactory.java b/Ghidra/Framework/Generic/src/main/java/ghidra/net/ApplicationSSLSocketFactory.java deleted file mode 100644 index db1b09496c..0000000000 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/net/ApplicationSSLSocketFactory.java +++ /dev/null @@ -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; - -/** - * ApplicationSSLSocketFactory provides a replacement for the default - * SSLSocketFactory 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); - } -} diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/net/DefaultKeyManagerFactory.java b/Ghidra/Framework/Generic/src/main/java/ghidra/net/DefaultKeyManagerFactory.java new file mode 100644 index 0000000000..830a6abdbf --- /dev/null +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/net/DefaultKeyManagerFactory.java @@ -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)}. + *

+ *

+ * 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 customPasswordProvider + * 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 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 ghidra.keystore 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; + } + + /** + * DefaultKeyManager 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 x509KeyManager already exists, this method has no affect. If the + * keystorePath has not already been set, the getPreferredKeyStore() + * method will be invoked to obtain the keystore which should be used in establishing the + * wrappedKeyManager. 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 x509KeyManager 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); + } +} diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/net/SSLContextInitializer.java b/Ghidra/Framework/Generic/src/main/java/ghidra/net/DefaultSSLContextInitializer.java similarity index 79% rename from Ghidra/Framework/Generic/src/main/java/ghidra/net/SSLContextInitializer.java rename to Ghidra/Framework/Generic/src/main/java/ghidra/net/DefaultSSLContextInitializer.java index fd74f17fa2..1d7bd884fd 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/net/SSLContextInitializer.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/net/DefaultSSLContextInitializer.java @@ -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. *

* The property jdk.tls.client.protocols should be set to restrict secure * client connections to a specific set of enabled TLS protocols (e.g., TLSv1.2,TLSv1.3). * See JDK and JRE Cryptographic Algorithms * 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; diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/net/ApplicationTrustManagerFactory.java b/Ghidra/Framework/Generic/src/main/java/ghidra/net/DefaultTrustManagerFactory.java similarity index 54% rename from Ghidra/Framework/Generic/src/main/java/ghidra/net/ApplicationTrustManagerFactory.java rename to Ghidra/Framework/Generic/src/main/java/ghidra/net/DefaultTrustManagerFactory.java index 4e34f4cbca..b9c19d9c52 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/net/ApplicationTrustManagerFactory.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/net/DefaultTrustManagerFactory.java @@ -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; /** - * ApplicationTrustManagerFactory provides the ability to establish - * acceptable certificate authorities to be used with SSL connections and PKI - * authentication. + * DefaultTrustManagerFactory provides the ability to establish + * acceptable certificate authorities to be used with the default SSLContext + * as established by {@link DefaultSSLContextInitializer}. *

* 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; *

* The application may choose to set the file path automatically based upon the presence of * a cacerts file at a predetermined location. + *

+ * 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(); /** * ApplicationTrustManagerFactory 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 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"); + } + } /** diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/net/HttpClients.java b/Ghidra/Framework/Generic/src/main/java/ghidra/net/HttpClients.java index 985e28516b..e113987e0e 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/net/HttpClients.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/net/HttpClients.java @@ -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"); } } diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/net/ApplicationKeyManagerUtils.java b/Ghidra/Framework/Generic/src/main/java/ghidra/net/PKIUtils.java similarity index 56% rename from Ghidra/Framework/Generic/src/main/java/ghidra/net/ApplicationKeyManagerUtils.java rename to Ghidra/Framework/Generic/src/main/java/ghidra/net/PKIUtils.java index e86696c0fd..d4e38868e6 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/net/ApplicationKeyManagerUtils.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/net/PKIUtils.java @@ -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; /** - * ApplicationKeyManagerUtils 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. *

* 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 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 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()); + } + } } diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/net/http/HttpUtil.java b/Ghidra/Framework/Generic/src/main/java/ghidra/net/http/HttpUtil.java index 2f406ccdb2..a7ca34f27b 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/net/http/HttpUtil.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/net/http/HttpUtil.java @@ -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"); } diff --git a/Ghidra/Framework/Generic/src/test/java/ghidra/net/ApplicationKeyManagerFactoryTest.java b/Ghidra/Framework/Generic/src/test/java/ghidra/net/ApplicationKeyManagerFactoryTest.java index 064cb8d7a7..59d16c3345 100644 --- a/Ghidra/Framework/Generic/src/test/java/ghidra/net/ApplicationKeyManagerFactoryTest.java +++ b/Ghidra/Framework/Generic/src/test/java/ghidra/net/ApplicationKeyManagerFactoryTest.java @@ -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)); diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/EditActionManager.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/EditActionManager.java index b63efcd412..15a08c1e67 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/EditActionManager.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/EditActionManager.java @@ -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; } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/ProjectActionManager.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/ProjectActionManager.java index 770d0e93d7..5394486678 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/ProjectActionManager.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/ProjectActionManager.java @@ -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.*; diff --git a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/framework/client/GhidraServerSerialFilterFailureTest.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/framework/client/GhidraServerSerialFilterFailureTest.java index 82125297eb..0542367773 100644 --- a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/framework/client/GhidraServerSerialFilterFailureTest.java +++ b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/framework/client/GhidraServerSerialFilterFailureTest.java @@ -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); } - + } } diff --git a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/server/remote/ServerTestUtil.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/server/remote/ServerTestUtil.java index 661c2e3c35..2728663074 100644 --- a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/server/remote/ServerTestUtil.java +++ b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/server/remote/ServerTestUtil.java @@ -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 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()); } /**