我正在编写一个Java应用程序,与远程Https网站rest Api调用。远程站点由受信任的证书签名。它在Windows上运行良好,但由于SSL证书问题,它在OS X上运行有问题。
我做了一些调查,发现原因与我如何使用getInstance调用在代码中初始化KeyStore对象有关。它只从“系统”密钥链读取证书,而不从“系统根”密钥链读取证书。下面是打印密钥库中所有证书的代码片段。
// In windows use "WINDOWS-ROOT"
KeyStore osTrustManager = KeyStore.getInstance("KeychainStore");
osTrustManager.load(null, null);
Enumeration<String> enumerator = osTrustManager.aliases();
while (enumerator.hasMoreElements()) {
String alias = enumerator.nextElement();
if (osTrustManager.isCertificateEntry(alias)) {
m_logger.info(String.format("%s (certificate)\n", alias));
}
}如何更改代码才能实现这一点?如果有人能参与进来,我将不胜感激。
这是“系统根”Screenshot from OS X下的证书示例
发布于 2020-08-06 06:21:11
我不知道是否有某种KeyStore允许您访问Mac系统根证书,但您可以尝试另一种方式。
在Mac中,您可以使用security命令从任何密钥链获取证书列表。
例如,此命令将为您提供有关安装在系统根密钥链中的不同证书的信息:
security find-certificate -a "/System/Library/Keychains/SystemRootCertificates.keychain"此实用程序有两个标志,-p和-a,前者将每个证书输出为PEM编码,后者允许我们按名称过滤结果-由于系统中安装了大量CA,这可能很方便。
我们的想法是使用Java中的这个实用程序。
不久前,我偶然发现了一个名为clienteafirma的库,它是用来处理数字签名的。
这个库有一个名为AppleScript的类。这个类基本上是Process的包装器,它允许我们运行任意命令。
例如,下面的代码使用该类和security命令来获取由VeriSign颁发的所有证书
public static void main(String... args) {
// Keychains that we can use
final String KEYCHAIN_PATH = "/Library/Keychains/System.keychain";
final String SYSTEM_KEYCHAIN_PATH = "/System/Library/Keychains/SystemRootCertificates.keychain";
// Show me only certificates from VeriSign
final String AUTHORITY = "VeriSign";
final String OSX_SEC_COMMAND = "security find-certificate -a -p -c %AUTHORITY% %KEYCHAIN%";
final String cmd = OSX_SEC_COMMAND.replace("%AUTHORITY%", AUTHORITY).replace("%KEYCHAIN%", SYSTEM_KEYCHAIN_PATH);
System.out.println(cmd);
System.out.println();
final AppleScript script = new AppleScript(cmd);
InputStream certificateStream = null;
try {
// Run script
final String result = script.run();
certificateStream = new ByteArrayInputStream(result.getBytes());
// Process the output of the command
final CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
final Collection<X509Certificate> certificates = (Collection<X509Certificate>) certificateFactory.generateCertificates(certificateStream);
// Use the certificates as you need
for (X509Certificate certificate : certificates) {
String alias = certificate.getSubjectX500Principal().getName();
System.out.println("Certificate: " + alias);
}
} catch (Throwable t) {
t.printStackTrace();
} finally {
if (certificateStream != null) {
try {
certificateStream.close();
} catch (IOException io) {
io.printStackTrace();
}
}
}
}发布于 2020-08-06 23:42:26
需求
可能的解决方案
据我所知,目前还没有纯Java方法来解决这一问题。但是,您可以创建一个本机C库,它通过特定于操作系统的调用检索证书,并通过JNI将它们返回给Java。
从macOS 10.3开始,安全框架中有一个函数SecTrustCopyAnchorCertificates,该函数
检索由macOS存储的锚(根)证书。
要构造Java X509Certificate实例,您需要DER编码格式的证书数据,请参见https://docs.oracle.com/javase/8/docs/api/java/security/cert/CertificateFactory.html#generateCertificate-java.io.InputStream-
在macOS端,您通过SecCertificateCopyData获取DER编码的证书数据。
注意:由于函数SecTrustCopyAnchorCertificates和SecCertificateCopyData都包含“Copy”一词,因此必须在使用后调用CFRelease,以避免造成内存泄漏。
每个证书的数据可以存储在Java字节数组中,并返回给Java调用方。
在Java端,您可以通过调用CertificateFactory.getInstance("X.509")来获取CertificateFactory。然后,您可以通过调用certFactory.generateCertificate(in)将字节转换为X509Certificate,其中in是一个ByteArrayInputStream,其中证书字节实际上来自本机C。
下面是一个自包含的示例:
本机C库
#include <stdio.h>
#include <string.h>
#include <CoreFoundation/CoreFoundation.h>
#include <Security/Security.h>
#include "com_software7_test_MacOSX509Certificates.h"
JNIEXPORT jobjectArray JNICALL Java_com_software7_test_MacOSX509Certificates_retrieveCertificates
(JNIEnv *env, jobject obj) {
CFArrayRef certs = NULL;
OSStatus status = SecTrustCopyAnchorCertificates(&certs);
if (status != noErr) {
jclass rte = (*env)->FindClass(env, "java/lang/RuntimeException");
if (rte != NULL)
(*env)->ThrowNew(env, rte, "error retrieving anchor certificates");
(*env)->DeleteLocalRef(env, rte);
}
CFIndex ncerts = CFArrayGetCount(certs);
jclass byteArrayClass = (*env)->FindClass(env, "[B");
jobjectArray array = (*env)->NewObjectArray(env, ncerts, byteArrayClass, (*env)->NewByteArray(env, 0));
for (int i = 0; i < ncerts; i++) {
SecCertificateRef certRef = (SecCertificateRef)CFArrayGetValueAtIndex(certs, i);
CFDataRef certData = SecCertificateCopyData(certRef);
int numBytes = CFDataGetLength(certData);
jbyteArray jCert = (*env)->NewByteArray(env, numBytes);
(*env)->SetByteArrayRegion(env, jCert, 0, numBytes, (const jbyte *)CFDataGetBytePtr(certData));
CFRelease(certData);
(*env)->SetObjectArrayElement(env, array, i, jCert);
(*env)->DeleteLocalRef(env, jCert);
}
CFRelease(certs);
return array;
}Java
package com.software7.test;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class MacOSX509Certificates {
static {
System.loadLibrary("maccerts");
}
private native byte[][] retrieveCertificates();
public static void main(String[] args) {
MacOSX509Certificates mc = new MacOSX509Certificates();
mc.retrieveAndPrint();
}
private void retrieveAndPrint() {
List<X509Certificate> x509Certificates = retrieve();
for (X509Certificate x509c : x509Certificates) {
System.out.println(x509c.getSubjectX500Principal());
}
}
private List<X509Certificate> retrieve() {
byte[][] certs = retrieveCertificates();
return Arrays.stream(certs)
.<X509Certificate>map(MacOSX509Certificates::convertToX509Certificate)
.collect(Collectors.toList());
}
@SuppressWarnings("unchecked")
private static <X509Certificate> X509Certificate convertToX509Certificate(byte[] bytes) {
try {
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
try (InputStream in = new ByteArrayInputStream(bytes)) {
return (X509Certificate) certFactory.generateCertificate(in);
}
} catch (CertificateException | IOException e) {
throw new RuntimeException(e);
}
}
}Build
构建过程可能由以下步骤组成:
export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk-14.0.1.jdk/Contents/Home/
javac -h . com/software7/test/MacOSX509Certificates.java
clang -c -fPIC -I$JAVA_HOME/include -I$JAVA_HOME/include/darwin com_software7_test_MacOSX509Certificates.c -o com_software7_test_MacOSX509Certificates.o
clang -dynamiclib -o libmaccerts.dylib com_software7_test_MacOSX509Certificates.o -lc -framework CoreFoundation -framework Security
mv libmaccerts.dylib ../out/production/read_mac_system_certs
rm com_software7_test_MacOSX509Certificates.o
rm com/software7/test/MacOSX509Certificates.class测试
如果在Mac上运行此示例,它将返回:
CN=Go Daddy Root Certificate Authority - G2, O="GoDaddy.com, Inc.", L=Scottsdale, ST=Arizona, C=US
CN=SwissSign Platinum CA - G2, O=SwissSign AG, C=CH
CN=AddTrust Class 1 CA Root, OU=AddTrust TTP Network, O=AddTrust AB, C=SE
CN=Global Chambersign Root, OU=http://www.chambersign.org, O=AC Camerfirma SA CIF A82743287, C=EU
...https://stackoverflow.com/questions/46986355
复制相似问题