简体   繁体   English

(Java) 在 Mac OS X 上的“系统根目录”下以编程方式访问 SSL 证书

[英](Java) Programmatically access SSL certificates under “System Roots” on Mac OS X

I am writing a Java application that does rest Api calls with remote Https site.我正在编写一个 Java 应用程序,该应用程序使用远程 Https 站点进行 Rest Api 调用。 The remote site is signed by trusted certificates.远程站点由受信任的证书签名。 It runs well on Windows, however, it has trouble to run on OS X due to SSL certificate issues.它在 Windows 上运行良好,但是,由于 SSL 证书问题,它在 OS X 上运行有问题。

I did some digging and found out that the reason is related to how I initialize KeyStore object in my code with getInstance call.我做了一些挖掘,发现原因与我如何使用 getInstance 调用在代码中初始化 KeyStore 对象有关。 It only reads certificates from "System" keychain, but not from "System Roots" keychain.它只从“系统”钥匙串中读取证书,而不是从“系统根”钥匙串中读取证书。 Below is the code snippet to print out all certificates from the key store.下面是从密钥库中打印出所有证书的代码片段。

// 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));
    }
}

How can the code be changed to achieve that?如何更改代码以实现这一目标? Appreciate if anyone can chime in.感谢有人可以插话。

This is a sample of certificates under "System Roots" Screenshot from OS X这是来自 OS X 的“系统根”屏幕截图下的证书示例

I do not know if there is some kind of KeyStore that allows you to access the Mac OS X System Roots certificates, but you can try another way.我不知道是否有某种KeyStore允许您访问 Mac OS X 系统根证书,但您可以尝试另一种方式。

In Mac OS X you can obtain a list of certificates from any keychain with the security command.在 Mac OS X 中,您可以使用security命令从任何钥匙串获取证书列表。

For instance, this command will give you information about the different certificates installed in the System Roots keychain:例如,此命令将为您提供有关安装在 System Roots 钥匙串中的不同证书的信息:

security find-certificate -a "/System/Library/Keychains/SystemRootCertificates.keychain"

This utility has two flags, -p , which will output each certificate as PEM encoded, and -a , which allows us to filter the results by name - which can be convenient due to the great number of CA installed in the system.这个实用程序有两个标志, -p ,它将每个证书输出为 PEM 编码,以及-a ,它允许我们按名称过滤结果 - 由于系统中安装了大量 CA,这很方便。

The idea is use this utility from Java.这个想法是从 Java 使用这个实用程序。

Not a long time ago, I came across a library called clienteafirma designed to deal with digital signatures.不久前,我遇到了一个名为clienteafirma的库,旨在处理数字签名。

This library has a class called AppleScript .这个库有一个叫做AppleScript的类。 This class basically is a wrapper around Process that allows us to run arbitrary commands.这个类基本上是一个围绕Process的包装器,它允许我们运行任意命令。

The following code uses that class and the security command to obtain all the certificates issued by, for instance, VeriSign :以下代码使用该类和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();
      }
    }
  }
}

Requirements要求

  • retrieve macOS anchor root certificates检索 macOS 锚根证书
  • get a Java X509Certificate instance for each of it为每个实例获取一个 Java X509Certificate 实例

Possible Solution可能的解决方案

As far as I know, there is no pure Java approach to this.据我所知,对此没有纯 Java 方法。 However, you can create a native C library that retrieves the certificates via operating system specific calls and returns them to Java via JNI.但是,您可以创建一个本机 C 库,该库通过操作系统特定调用检索证书并通过 JNI 将它们返回给 Java。

Since macOS 10.3 there is a function SecTrustCopyAnchorCertificates in the Security framework that从 macOS 10.3 开始,Security 框架中有一个函数 SecTrustCopyAnchorCertificates

retrieves the anchor (root) certificates stored by macOS.检索由 macOS 存储的锚(根)证书。

see https://developer.apple.com/documentation/security/1401507-sectrustcopyanchorcertificates?language=objc请参阅https://developer.apple.com/documentation/security/1401507-sectrustcopyanchorcertificates?language=objc

To construct a Java X509Certificate instance you need the certificate data in a DER-encoded format, see https://docs.oracle.com/javase/8/docs/api/java/security/cert/CertificateFactory.html#generateCertificate-java.io.InputStream-要构建 Java X509Certificate 实例,您需要 DER 编码格式的证书数据,请参阅https://docs.oracle.com/javase/8/docs/api/java/security/cert/CertificateFactory.html#generateCertificate-java .io.InputStream-

On macOS side you get the DER encoded certificate data via SecCertificateCopyData.在 macOS 端,您可以通过 SecCertificateCopyData 获得 DER 编码的证书数据。

Note: Since both functions SecTrustCopyAnchorCertificates and SecCertificateCopyData contain the word 'Copy', you must call CFRelease after use to avoid creating memory leaks.注意:由于 SecTrustCopyAnchorCertificates 和 SecCertificateCopyData 两个函数都包含“Copy”一词,因此您必须在使用后调用 CFRelease 以避免造成内存泄漏。

The data of each certificate can be stored in a Java byte array and returned to the Java caller side.每个证书的数据可以存储在一个Java字节数组中,并返回给Java调用方。

On the Java side, you can get a CertificateFactory by calling CertificateFactory.getInstance("X.509") .在 Java 端,您可以通过调用CertificateFactory.getInstance("X.509")来获取 CertificateFactory。 Then you can convert the bytes to an X509Certificate by calling certFactory.generateCertificate(in) , where in is a ByteArrayInputStream where the certificate bytes actually come from the native C lib.然后,您可以通过调用certFactory.generateCertificate(in)将字节转换为 X509Certificate,其中in是一个 ByteArrayInputStream,其中证书字节实际上来自本机 C 库。

Here is a self-contained example:这是一个独立的示例:

Native C Library本机 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建造

A build process could consist of the following steps:构建过程可以包括以下步骤:

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

Test测试

If you run this example on a Mac it returns:如果您在 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
...

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM