有没有办法使用 SunMSCAPI 通过 Java 访问 Windows 本地计算机个人密钥存储?
通常,您可以使用WINDOWS-ROOT(大致相当于Java中的可信存储)或WINDOWS-MY(其中包含个人证书,大致相当于Java中的密钥存储)来检索来自 Windows 证书存储的证书。
这对于用户来说效果很好,但即使在模拟 SYSTEM 用户时,我也无法检索本地计算机的个人证书。
存在一些与此相关的问题使用JNA(我想避免,特别是因为从那里检索私钥似乎很复杂)。
此外,有人使用 psexec 来模拟 SYSTEM 用户(使用 psexec -s)。我也尝试过,但没有成功。
最后,Java Bug 系统中还有一个open bug。
如果有人知道如何使用 Java 从 Windows 中的本地计算机个人存储中检索证书,我将不胜感激。
经过 14 年的 bug 跟踪,JDK-6782021 终于在 Java 19(2022 年春季)中得到修复。这意味着这不再是问题。
wcsa
实用程序,它将拦截 JVM 对 Windows Crypto API 的调用,并让您访问本地计算机凭据。这当然是围绕十年前报道的真正问题JDK-6782021进行的黑客攻击。但它确实可以让您以非常轻松的方式访问本地计算机证书!
所以是的,可以访问它们,但是不行,不可能使用普通的 Java API 来访问它们。不过,您可以使用商业产品JCAPI使用普通Java访问它们,但对于大多数用途来说,
wcsa
实用程序就可以了。
Open JDK 维护者愿意打补丁,所以也许你可以尝试使用
wcsa
存储库中提供的代码来修复它:)
这是一个独立于 Java 版本工作的替代方案,无论是 https://bugs.openjdk.org/browse/JDK-6782021 还是 https://bugs.openjdk.org/browse/JDK-8313367是固定的。它使用一个小的 Powershell 脚本将证书转储到一个临时文件夹,Java 可以在其中轻松读取它们。
import java.io.File;
import java.io.FileInputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.apache.commons.io.FileUtils;
public class WindowsCertificateService {
private static List<X509Certificate> getWindowsLocalMachineCertificates(String certType) {
// Theoretically we could use KeyStore.getInstance("Windows-ROOT-LOCALMACHINE") and
// KeyStore.getInstance("Windows-MY-LOCALMACHINE") to accomplish this, which were added as part of
// https://bugs.openjdk.org/browse/JDK-6782021. Unfortunately, we'll get an Access Denied error due to
// https://bugs.openjdk.org/browse/JDK-8313367.
Path tempFolder;
List<X509Certificate> certificates = new ArrayList<>();
try {
tempFolder = Files.createTempDirectory("seeq-link-getWindowsLocalMachineCertificates");
} catch (Throwable e) {
LOG.error("Error creating temporary folder for Windows certificates", e);
return Collections.emptyList();
}
try {
String powershellScript = String.format("dir cert:\\localmachine\\%s | Foreach-Object { [system.IO" +
".file]::WriteAllBytes(\".\\$($_.Thumbprint).cer\", ($_.Export('CERT', 'secret')) ) }", certType);
Path scriptLocation = tempFolder.resolve("getWindowsLocalMachineCertificates.ps1");
Files.write(scriptLocation, powershellScript.getBytes());
ProcessBuilder processBuilder = new ProcessBuilder(
"powershell.exe", "-ExecutionPolicy", "Bypass", "-File", scriptLocation.toString());
processBuilder.directory(tempFolder.toFile());
processBuilder.redirectErrorStream(true);
Process process = processBuilder.start();
process.waitFor();
if (process.exitValue() != 0) {
LOG.warn("Error retrieving local machine Windows certificates for type '{}': Powershell script " +
"returned {} exit code. Script contents:\n{}", certType, process.exitValue(), powershellScript);
return Collections.emptyList();
}
Collection<File> cerFiles = FileUtils.listFiles(tempFolder.toFile(), new String[] { "cer" }, false);
for (File cerFile : cerFiles) {
try (FileInputStream fis = new FileInputStream(cerFile)) {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate cert = cf.generateCertificate(fis);
if (cert instanceof X509Certificate) {
certificates.add((X509Certificate) cert);
}
} catch (Exception e) {
LOG.warn("Error reading Windows certificate from '{}'", cerFile, e);
}
}
return certificates;
} catch (Throwable e) {
LOG.error("Error retrieving local machine Windows certificates", e);
return Collections.emptyList();
} finally {
if (tempFolder != null) {
try {
FileUtils.deleteDirectory(tempFolder.toFile());
} catch (Exception e) {
LOG.error("Error deleting temporary folder for Windows certificates", e);
}
}
}
}
}