Skip to content

macOS: Support intermediate certificates #5

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
/target
hs_err_pid*.log
9 changes: 8 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -182,5 +182,12 @@
<version>4.13.2</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
<version>3.0</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
</project>
93 changes: 93 additions & 0 deletions src/main/java/org/jetbrains/nativecerts/mac/CoreFoundationExt.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.platform.mac.CoreFoundation;

public interface CoreFoundationExt extends Library {
Expand All @@ -16,5 +17,97 @@ public interface CoreFoundationExt extends Library {
*/
CoreFoundation.CFIndex CFDictionaryGetCount(CoreFoundation.CFDictionaryRef theDict);

/**
* Creates an immutable dictionary containing the specified key-value pairs.
*
* @see <a href="https://developer.apple.com/documentation/corefoundation/1516782-cfdictionarycreate">https://developer.apple.com/documentation/corefoundation/1516782-cfdictionarycreate</a>
* @return A new dictionary, or NULL if there was a problem creating the object.
* Ownership follows the <a href="https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFMemoryMgmt/Concepts/Ownership.html">Create Rule</a>.
*/
CoreFoundation.CFDictionaryRef CFDictionaryCreate(
CoreFoundation.CFAllocatorRef allocator,
CoreFoundation.CFTypeRef[] keys,
CoreFoundation.CFTypeRef[] values,
CoreFoundation.CFIndex numValues,
Pointer keyCallBacks,
Pointer valueCallBacks
);

void CFShow(CoreFoundation.CFTypeRef obj);

/**
* Creates a new immutable array with the given values.
* <p>
* This reference must be released with {@link CoreFoundation#CFRelease} to avoid leaking
* references.
* <p>
* This is like {@link CoreFoundation#CFArrayCreate(CoreFoundation.CFAllocatorRef, Pointer, CoreFoundation.CFIndex, Pointer)}
* but it takes a {@code Pointer[]} instead.
*
* @see <a href="https://developer.apple.com/documentation/corefoundation/1388741-cfarraycreate">https://developer.apple.com/documentation/corefoundation/1388741-cfarraycreate</a>
*
* @param alloc
* The allocator to use to allocate memory for the new array and its
* storage for values. Pass {@code null} or
* {@code kCFAllocatorDefault} to use the current default allocator.
* @param values
* A C array of the pointer-sized values to be in the new array. The
* values in the new array are ordered in the same order in which
* they appear in this C array. This value may be {@code null} if
* {@code numValues} is 0. This C array is not changed or freed by
* this function. If {@code values} is not a valid pointer to a C
* array of at least {@code numValues} elements, the behavior is
* undefined.
* @param numValues
* The number of values to copy from the {@code values} C array into
* the new array. This number will be the count of the new array—it
* must not be negative or greater than the number of elements in
* values.
* @param callBacks
* A pointer to a {@code CFArrayCallBacks} structure initialized with
* the callbacks for the array to use on each value in the
* collection. The retain callback is used within this function, for
* example, to retain all of the new values from the {@code values} C
* array. A copy of the contents of the callbacks structure is made,
* so that a pointer to a structure on the stack can be passed in or
* can be reused for multiple collection creations.
* <p>
* This value may be {@code null}, which is treated as if a valid
* structure of version 0 with all fields {@code null} had been
* passed in.
* @return A new immutable array containing {@code numValues} from
* {@code values}, or {@code null} if there was a problem creating the
* object.
*/
CoreFoundation.CFArrayRef CFArrayCreate(CoreFoundation.CFAllocatorRef alloc, Pointer[] values, CoreFoundation.CFIndex numValues, Pointer callBacks);

/**
* Determines whether two Core Foundation objects are considered equal.
*
* @see <a href="https://developer.apple.com/documentation/corefoundation/1521287-cfequal">https://developer.apple.com/documentation/corefoundation/1521287-cfequal</a>
* @param cf1 A CFType object to compare to cf2.
* @param cf2 A CFType object to compare to cf1.
* @return true if cf1 and cf2 are of the same type and considered equal, otherwise false.
*/
boolean CFEqual(CoreFoundation.CFTypeRef cf1, CoreFoundation.CFTypeRef cf2);

/**
* Boolean false value.
* @see <a href="https://developer.apple.com/documentation/corefoundation/kcfbooleanfalse">https://developer.apple.com/documentation/corefoundation/kcfbooleanfalse</a>
*/
CoreFoundation.CFBooleanRef kCFBooleanFalse = resolveBoolean("kCFBooleanFalse", false);
/**
* Boolean true value.
* @see <a href="https://developer.apple.com/documentation/corefoundation/kcfbooleantrue">https://developer.apple.com/documentation/corefoundation/kcfbooleantrue</a>
*/
CoreFoundation.CFBooleanRef kCFBooleanTrue = resolveBoolean("kCFBooleanTrue", true);

private static CoreFoundation.CFBooleanRef resolveBoolean(String name, boolean expectedValue) {
Pointer pointer = Native.getNativeLibrary(CoreFoundation.INSTANCE).getGlobalVariableAddress(name);
CoreFoundation.CFBooleanRef cfBoolean = new CoreFoundation.CFBooleanRef(pointer.getPointer(0));
if (cfBoolean.booleanValue() != expectedValue) {
throw new IllegalStateException("Expected " + name + " to be " + expectedValue + ", but got " + cfBoolean.booleanValue());
}
return cfBoolean;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.jetbrains.nativecerts.mac;

import com.sun.jna.platform.mac.CoreFoundation;
import com.sun.jna.platform.mac.CoreFoundation.CFTypeRef;

import java.util.Map;

public class CoreFoundationExtUtil {
private CoreFoundationExtUtil() {
}

public static CoreFoundation.CFDictionaryRef createDictionary(Map<CFTypeRef, CFTypeRef> map) {
int mapSize = map.size();

CFTypeRef[] keys = new CFTypeRef[mapSize];
CFTypeRef[] values = new CFTypeRef[mapSize];

int i = 0;
for (Map.Entry<CFTypeRef, CFTypeRef> entry : map.entrySet()) {
keys[i] = entry.getKey();
values[i] = entry.getValue();
i++;
}

return CoreFoundationExt.INSTANCE.CFDictionaryCreate(
null,
keys, values, new CoreFoundation.CFIndex(mapSize), null, null);
}
}
189 changes: 189 additions & 0 deletions src/main/java/org/jetbrains/nativecerts/mac/SecurityFramework.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.sun.jna.*;
import com.sun.jna.platform.mac.CoreFoundation;
import com.sun.jna.ptr.PointerByReference;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

Expand All @@ -10,6 +11,44 @@ public interface SecurityFramework extends Library {

SecurityFramework INSTANCE = Native.load("Security", SecurityFramework.class);

/**
* A dictionary key whose value is the item’s class.
*
* @see <a href="https://developer.apple.com/documentation/security/ksecclass">https://developer.apple.com/documentation/security/ksecclass</a>
*/
CoreFoundation.CFStringRef kSecClass = resolveSecurityFrameworkString("kSecClass");
/**
* A key whose value indicates the match limit.
*
* @see <a href="https://developer.apple.com/documentation/security/kSecMatchLimit">https://developer.apple.com/documentation/security/kSecMatchLimit</a>
*/
CoreFoundation.CFStringRef kSecMatchLimit = resolveSecurityFrameworkString("kSecMatchLimit");

/**
* A key whose value indicates a list of items to search.
*
* @see <a href="https://developer.apple.com/documentation/security/kSecMatchSearchList">https://developer.apple.com/documentation/security/kSecMatchSearchList</a>
*/
CoreFoundation.CFStringRef kSecMatchSearchList = resolveSecurityFrameworkString("kSecMatchSearchList");
/**
* A value that corresponds to matching an unlimited number of items.
*
* @see <a href="https://developer.apple.com/documentation/security/kSecMatchLimitAll">https://developer.apple.com/documentation/security/kSecMatchLimitAll</a>
*/
CoreFoundation.CFStringRef kSecMatchLimitAll = resolveSecurityFrameworkString("kSecMatchLimitAll");
/**
* A key whose value is a Boolean indicating whether or not to return a reference to an item.
*
* @see <a href="https://developer.apple.com/documentation/security/kSecReturnRef">https://developer.apple.com/documentation/security/kSecReturnRef</a>
*/
CoreFoundation.CFStringRef kSecReturnRef = resolveSecurityFrameworkString("kSecReturnRef");
/**
* The value that indicates a certificate item.
*
* @see <a href="https://developer.apple.com/documentation/security/kSecClassCertificate">https://developer.apple.com/documentation/security/kSecClassCertificate</a>
*/
CoreFoundation.CFStringRef kSecClassCertificate = resolveSecurityFrameworkString("kSecClassCertificate");

/**
* Returns a string explaining the meaning of a security result code.
*
Expand Down Expand Up @@ -46,6 +85,21 @@ public interface SecurityFramework extends Library {
@NotNull
OSStatus SecTrustSettingsCopyCertificates(@NotNull SecTrustSettingsDomain domain, @NotNull CFArrayRefByReference certArray);

/**
* Returns one or more keychain items that match a search query, or copies attributes of specific keychain items.
* @see <a href="https://developer.apple.com/documentation/security/secitemcopymatching(_:_:)">https://developer.apple.com/documentation/security/secitemcopymatching(_:_:)</a>
*/
@NotNull
OSStatus SecItemCopyMatching(@NotNull CoreFoundation.CFDictionaryRef query, CFArrayRefByReference result);

/**
* Opens a keychain.
* @param pathName A constant character string representing the POSIX path to the keychain to open.
* @param keychain On return, a pointer to the keychain object. You must call the CFRelease function to release this object when you are finished using it.
* @return A result code. See {@link OSStatus}
*/
OSStatus SecKeychainOpen(String pathName, SecKeychainRefByReference keychain);

/**
* Retrieves the common name of the subject of a certificate.
*
Expand All @@ -64,8 +118,24 @@ public interface SecurityFramework extends Library {
CoreFoundation.CFTypeID SecCertificateGetTypeID();
CoreFoundation.CFTypeID SecPolicyGetTypeID();

/**
* Returns the unique identifier of the opaque type to which a keychain object belongs.
*/
CoreFoundation.CFTypeID SecKeychainGetTypeID();

/**
* Returns the unique identifier of the opaque type to which a trust object belongs
*
* @see <a href="https://developer.apple.com/documentation/security/sectrustgettypeid()">https://developer.apple.com/documentation/security/sectrustgettypeid()</a>
*
* @return A value that identifies the opaque type of a SecTrustRef object.
*/
CoreFoundation.CFTypeID SecTrustGetTypeID();

CoreFoundation.CFTypeID SEC_CERTIFICATE_TYPE_ID = INSTANCE.SecCertificateGetTypeID();
CoreFoundation.CFTypeID SEC_SEC_KEYCHAIN_REF_TYPE_ID = INSTANCE.SecKeychainGetTypeID();
CoreFoundation.CFTypeID SEC_POLICY_TYPE_ID = INSTANCE.SecPolicyGetTypeID();
CoreFoundation.CFTypeID SEC_TRUST_TYPE_ID = INSTANCE.SecTrustGetTypeID();

/**
* An abstract Core Foundation-type object representing an X.509 certificate.
Expand All @@ -84,6 +154,42 @@ public SecCertificateRef(Pointer p) {
}
}

/**
* An opaque type that represents a keychain.
*
* @see <a href="https://developer.apple.com/documentation/security/SecKeychainRef">developer.apple.com</a>
*/
class SecKeychainRef extends CoreFoundation.CFTypeRef {
public SecKeychainRef() {
}

public SecKeychainRef(Pointer p) {
super(p);
if (!isTypeID(SEC_SEC_KEYCHAIN_REF_TYPE_ID)) {
throw new ClassCastException("Unable to cast to SecCertificateRef. Type ID: " + getTypeID());
}
}
}

class SecKeychainRefByReference extends PointerByReference {
public SecKeychainRefByReference() {
}

public SecKeychainRefByReference(SecKeychainRef value) {
super(value.getPointer());
}

@Nullable
public SecKeychainRef getSecKeychainRef() {
Pointer value = super.getValue();
if (value == null) {
return null;
}

return new SecKeychainRef(value);
}
}

/**
* An object that represents a trust policy.
*
Expand All @@ -101,6 +207,53 @@ public SecPolicyRef(Pointer p) {
}
}

/**
* An object used to evaluate trust.
*
* @see <a href="https://developer.apple.com/documentation/security/sectrust">developer.apple.com</a>
*/
class SecTrustRef extends CoreFoundation.CFTypeRef {
public SecTrustRef() {
}

public SecTrustRef(Pointer p) {
super(p);
if (!isTypeID(SEC_TRUST_TYPE_ID)) {
throw new ClassCastException("Unable to cast to SecTrustRef. Type ID: " + getTypeID());
}
}
}

class SecTrustRefByReference extends PointerByReference {
public SecTrustRefByReference() {
}

public SecTrustRefByReference(SecTrustRef value) {
super(value.getPointer());
}

@Nullable
public SecTrustRef getSecTrustRef() {
Pointer value = super.getValue();
if (value == null) {
return null;
}

return new SecTrustRef(value);
}
}

/**
* Returns a policy object for evaluating SSL certificate chains.
*
* @see <a href="https://developer.apple.com/documentation/security/secpolicycreatessl(_:_:)">https://developer.apple.com/documentation/security/secpolicycreatessl(_:_:)</a>
*
* @param server Specify true on the client side to return a policy for SSL server certificates.
* @param hostname If you specify a value for this parameter, the policy will require the specified value to match the host name in the leaf certificate.
* @return The policy object. In Objective-C, call the CFRelease function to release the object when you are finished with it.
*/
SecPolicyRef SecPolicyCreateSSL(boolean server, CoreFoundation.CFStringRef hostname);

/**
* Returns a dictionary containing a policy’s properties.
*
Expand Down Expand Up @@ -194,6 +347,37 @@ public SecPolicyRef(Pointer p) {
*/
OSStatus SecTrustSettingsCopyTrustSettings(SecCertificateRef certRef, SecTrustSettingsDomain domain, CFArrayRefByReference trustSettings);

/**
* Creates a trust management object based on certificates and policies.
*
* @see <a href="https://developer.apple.com/documentation/security/sectrustcreatewithcertificates(_:_:_:)">https://developer.apple.com/documentation/security/sectrustcreatewithcertificates(_:_:_:)</a>
*
* @param certificates The certificate to be verified, plus any other certificates you think might be useful for verifying the certificate. The certificate to be verified must be the first in the array. If you want to specify only one certificate, you can pass a SecCertificateRef object; otherwise, pass an array of SecCertificateRef objects.
* @param policies References to one or more policies to be evaluated. You can pass a single SecPolicyRef object, or an array of one or more SecPolicyRef objects. If you pass in multiple policies, all policies must verify for the certificate chain to be considered valid. You typically use one of the standard policies, like the one returned by SecPolicyCreateBasicX509.
* @param trust On return, points to the newly created trust management object. In Objective-C, call the CFRelease function to release this object when you are finished with it.
* @return A result code. See <a href="https://developer.apple.com/documentation/security/security-framework-result-codes">Security Framework Result Codes</a>.
*/
OSStatus SecTrustCreateWithCertificates(CoreFoundation.CFArrayRef certificates, SecPolicyRef policies, SecTrustRefByReference trust);

/**
* Evaluates trust for the specified certificate and policies.
* @param trust The trust management object to evaluate. A trust management object includes the certificate to be verified plus the policy or policies to be used in evaluating trust. It can optionally also include other certificates to be used in verifying the first certificate. Use the SecTrustCreateWithCertificates function to create a trust management object.
* @param error An error pointer the method uses to return an error when trust evaluation fails. Set to nil to ignore the error.
* @return true if the certificate is trusted; otherwise, false.
*/
boolean SecTrustEvaluateWithError(SecTrustRef trust, Pointer error);

/**
* Retrieves the keychain search list for a specified preference domain.
*
* @see <a href="https://developer.apple.com/documentation/security/seckeychaincopydomainsearchlist(_:_:">https://developer.apple.com/documentation/security/seckeychaincopydomainsearchlist(_:_:</a>
*
* @param domain The preference domain from which you wish to retrieve the keychain search list. See {@link SecTrustSettingsDomain} for possible domain values.
* @param searchList On return, a pointer to the keychain search list of the specified preference domain.
* @return A result code. See {@link OSStatus}
*/
OSStatus SecKeychainCopyDomainSearchList(SecTrustSettingsDomain domain, CFArrayRefByReference searchList);

/**
* Trust settings returned in usage constraints dictionaries.
*
Expand Down Expand Up @@ -333,4 +517,9 @@ public SecTrustSettingsDomain(long value) {
super(value);
}
}

private static CoreFoundation.CFStringRef resolveSecurityFrameworkString(String name) {
Pointer pointer = Native.getNativeLibrary(SecurityFramework.INSTANCE).getGlobalVariableAddress(name);
return new CoreFoundation.CFStringRef(pointer.getPointer(0));
}
}
Loading
Loading