Do authentication challenge NSURLAuthenticationMethodClientCertificate with crt and pem - nsurlsession

I need to implement authentication challenge on iOS 14, I was given a crt and a private key in form of a pem file.
I do know how to create a SecKey and SecCertificate out of it, but for the challenge I need an SecIdentity:
public init(identity: SecIdentity, certificates certArray: [Any]?, persistence: URLCredential.Persistence)
How to achieve this?
I also tried to generate a .p12 out of those two files, then import that via SecPKCS12Import to get identities and certificate from there, but it still doesn't work. Is there something special I need to consider when trying to do
URLCredential(identity: ..., certificates: [myCertificate], persistence: .none)
This is how I get certificate and identity I send to the server:
var certificateRef: SecCertificate? = nil
var items : CFArray?
let securityError: OSStatus = SecPKCS12Import(decodedData, [kSecImportExportPassphrase as NSString : psswd] as CFDictionary, &items)
let theArray: CFArray = items!
if securityError == noErr && CFArrayGetCount(theArray) > 0 {
let newArray = theArray as [AnyObject] as NSArray
let dictionary = newArray.object(at: 0)
let secIdentity = (dictionary as AnyObject)[kSecImportItemIdentity as String] as! SecIdentity
let securityError = SecIdentityCopyCertificate(secIdentity , &certificateRef)
if securityError != noErr {
certificateRef = nil
}
return secIdentity
}

Related

How to upgrade from using deprecated SecDigestGetData method?

At some point, it seems that Apple has removed the SecDigestGetData method from the Security framework. I'm trying to upgrade some old code that compares a certificate digest to see if we've found the right one. The original code looks like this:
uint8 candidate_sha1_hash[20];
CSSM_DATA digest;
digest.Length = sizeof(candidate_sha1_hash);
digest.Data = candidate_sha1_hash;
if ((SecDigestGetData(CSSM_ALGID_SHA1, &digest, &certData) == CSSM_OK) &&
(hashData.Length == digest.Length) &&
(!memcmp(hashData.Data, digest.Data, digest.Length))) {
found = TRUE;
break;
}
I get this error when it is compiled:
export_private_key.c:103:10: error: implicit declaration of function 'SecDigestGetData' is invalid in C99 [-Werror,-Wimplicit-function-declaration]
if ((SecDigestGetData(CSSM_ALGID_SHA1, &digest, &certData) == CSSM_OK) &&
^
How can I upgrade this code?
You can calculate the fingerprint yourself by extracting the DER data and hashing it. In C it would look like this.
#include <Security/SecCertificate.h>
#include <CommonCrypto/CommonDigest.h>
CFDataRef _Nonnull digest_from_certificate(SecCertificateRef _Nonnull certificate) {
const CFDataRef certificateData = SecCertificateCopyData(certificate);
uint8 digest [CC_SHA1_DIGEST_LENGTH];
CC_SHA1(CFDataGetBytePtr(certificateData), (CC_LONG) CFDataGetLength(certificateData), digest);
return CFDataCreate(NULL, digest, CC_SHA1_DIGEST_LENGTH);
}
In Swift the implementation would look like this.
// Extracting the DER data.
let certificate: SecCertificate = ...
let certificateData = SecCertificateCopyData(certificate) as Data
Hashing can be done with CommonCrypto or, since iOS 13, with CryptoKit.
import CommonCrypto
let certificateBytes = Array(certificateData)
var digest = [UInt8](repeating: 0, count: Int(CC_SHA1_DIGEST_LENGTH))
_ = CC_SHA1(certificateBytes, CC_LONG(certificateBytes.count), &digest)
print(Data(digest) as NSData)
import CryptoKit
Insecure.SHA1.hash(data: SecCertificateCopyData(certificate) as Data)
An alternative approach would be to use SecCertificateCopyValues.
if let values = SecCertificateCopyValues(certificate, ["Fingerprints"] as CFArray, &error) as? [String: Any] {
if let fingerprints = (values["Fingerprints"] as? [CFString: Any])?[kSecPropertyKeyValue] as? [[CFString: Any]] {
if let sha1 = fingerprints.first(where: { ($0[kSecPropertyKeyLabel] as? String) == "SHA-1" }).map({ $0[kSecPropertyKeyValue] as? Data }) {
print(sha1 as NSData)
}
}
}
SecCertificateCopyValues returns a dictionary described by certificate property keys. The value of such dictionary can be another dictionary or an array of dictionaries. Now, as far as I know, certificates should have SHA-256 as a minimum (SHA-1 is merely used for backwards compatibility), meaning you'll likely get an array of fingerprints, hence my example.

Get a macOS Keychain certificate's SHA1 hash in Swift

I've retrieved the set of certificates in my keychain using this code:
let query: [String: Any] = [
kSecClass as String: kSecClassCertificate,
kSecMatchLimit as String: kSecMatchLimitAll,
kSecReturnAttributes as String: false,
kSecReturnData as String: true
]
var result: CFTypeRef?
var results : Set<CertsResult> = []
let status = SecItemCopyMatching(query as CFDictionary, &result)
//[Check status]
guard let certificateData = result as? [CFData] else {
//[Handle]
}
From here, I loop through certificateData and gather information about the certificates, but I need to get the SHA1 hash of the certificates as well. I've gathered from researching that I need to use import CommonCrypto and CC_SHA1, but what I've read doesn't use a CFData.
Is there a good way to get from this point to its SHA1?
You can achieve it by performing the hash yourself. The fingerprints are not part of the certificate itself. More info about that over here.
import CryptoKit
let certificate = ...
let der = SecCertificateCopyData(certificate) as Data
let sha1 = Insecure.SHA1.hash(data: der)
let sha256 = SHA256.hash(data: der)
This can be created in an extension too. I've used CommonCrypto in the extension.
import CommonCrypto
extension SecCertificate {
var sha1: Data {
var digest = [UInt8](repeating: 0, count: Int(CC_SHA1_DIGEST_LENGTH))
let der = SecCertificateCopyData(self) as Data
_ = CC_SHA1(Array(der), CC_LONG(der.count), &digest)
return Data(digest)
}
var sha256: Data {
var digest = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
let der = SecCertificateCopyData(self) as Data
_ = CC_SHA256(Array(der), CC_LONG(der.count), &digest)
return Data(digest)
}
}
I'd like to mention that SHA-1 hashes of certificates are deprecated since like 2017 and websites and tech giants are starting to drop support for them.
Playground example
import CryptoKit
import Foundation
class CertificateStuff: NSObject, URLSessionDelegate {
func urlSession(
_ session: URLSession,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: #escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
) {
guard let serverTrust = challenge.protectionSpace.serverTrust else {
completionHandler(.rejectProtectionSpace, nil)
return
}
for index in 0 ..< SecTrustGetCertificateCount(serverTrust) {
let certificate = SecTrustGetCertificateAtIndex(serverTrust, index)!
let der = SecCertificateCopyData(certificate)
let sha1 = Insecure.SHA1.hash(data: der as Data)
let sha256 = SHA256.hash(data: der as Data)
print(certificate)
print(sha1)
print(sha256)
print()
}
completionHandler(.performDefaultHandling, nil)
}
func request(_ done: #escaping (Result<Data, Error>) -> Void) {
let url = URL(string: "https://security.stackexchange.com/questions/14330/what-is-the-actual-value-of-a-certificate-fingerprint")!
let request = URLRequest(url: url)
URLSession(configuration: .default, delegate: self, delegateQueue: nil).dataTask(with: request) { (d, r, e) in
if let e = e {
print(e)
return
}
print(d!)
}.resume()
}
}
CertificateStuff().request { result in print(result) }

OSX: Export system certificates from keychain in PEM format programmatically

How can I extract all root CA certificates from all the keychains on OSX programmatically in pem format?
Keychain programming services should allow this but how?
Any help would be appreciable.
security find-certificate -a -p /System/Library/Keychains/SystemRootCertificates.keychain >certs-roots.pem
security find-certificate -a -p /Library/Keychains/System.keychain >certs-system.pem
security find-certificate -a -p ~/Library/Keychains/login.keychain-db >certs-user.pem
BTW: You can see those paths in Keychain Access when you hover over the Keychains list (top/left).
You can combine system and user pems by using the default certificate source
security find-certificate -a -p >certs.pem
This is super useful for node.js, when you want to use require('https').request on typical corporate internal stuff without having to resort to hacks like accepting any certificate without checking. You don't need to include the system roots since nodejs has you covered already for those.
NODE_EXTRA_CA_CERTS=certs.pem node
Answering my own question:
On OSX you can invoke a NSTask to get response from the security command line utility:
security find-certificate -a -p /System/Library/Keychains/SystemCACertificates.keychain > allcerts.pem
Hey I know I'm late to this but I came across the same problem today and spent many hours figuring out how to do this. I know that the original poster might not need to know this anymore but hopefully this helps someone.
The following is my code to exactly replicate what you've done without using the command line.
+ (NSURL *)createCertsFileInDirectory:(NSURL *)directory {
NSString *outPath = [directory path];
if (!outPath) {
return nil;
}
outPath = [outPath stringByAppendingPathComponent:#"allcerts.pem"];
NSURL * outURL = [NSURL fileURLWithPath:outPath];
SecKeychainRef keychain;
if (SecKeychainOpen("/System/Library/Keychains/SystemCACertificates.keychain", &keychain) != errSecSuccess) {
return nil;
}
CFMutableArrayRef searchList = CFArrayCreateMutable(kCFAllocatorDefault, 1, &kCFTypeArrayCallBacks);
CFArrayAppendValue(searchList, keychain);
CFTypeRef keys[] = { kSecClass, kSecMatchLimit, kSecAttrCanVerify, kSecMatchSearchList };
CFTypeRef values[] = { kSecClassCertificate, kSecMatchLimitAll, kCFBooleanTrue, searchList };
CFDictionaryRef dict = CFDictionaryCreate(kCFAllocatorDefault, keys, values, 4, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFTypeRef results;
OSStatus status = SecItemCopyMatching(dict, &results);
CFArrayRef arr = (CFArrayRef) results;
NSLog(#"total item count = %ld", CFArrayGetCount(arr));
CFRelease(dict);
CFRelease(searchList);
CFRelease(keychain);
if (status != errSecSuccess) {
return nil;
}
CFDataRef certsData;
status = SecItemExport(results, kSecFormatPEMSequence, kSecItemPemArmour, NULL, &certsData);
CFRelease(results);
if (status != errSecSuccess) {
return nil;
}
NSData *topLevelData = (NSData *) CFBridgingRelease(certsData);
if (![topLevelData writeToURL:outURL atomically:YES]) {
return nil;
}
return outURL;
}

Type 'CFStringRef' does not conform to protocol 'Hashable' in Xcode 6.1

In my app i have a Keychain access class that was working in Xcode 6 but now in Xcode 6.1 i get some errors this is the first one: the Type 'CFStringRef' does not conform to protocol 'Hashable':
private class func updateData(value: NSData, forKey keyName: String) -> Bool {
let keychainQueryDictionary: NSMutableDictionary = self.setupKeychainQueryDictionaryForKey(keyName)
let updateDictionary = [kSecValueData:value] //HERE IS THE ERROR
// Update
let status: OSStatus = SecItemUpdate(keychainQueryDictionary, updateDictionary)
if status == errSecSuccess {
return true
} else {
return false
}
}
I also get a error similar to the the first one but it is: Type 'CFStringRef' does not conform to protocol 'NSCopying' here is the part where i get this error:
private class func setupKeychainQueryDictionaryForKey(keyName: String) -> NSMutableDictionary {
// Setup dictionary to access keychain and specify we are using a generic password (rather than a certificate, internet password, etc)
var keychainQueryDictionary: NSMutableDictionary = [kSecClass:kSecClassGenericPassword]
// HERE IS THE ERROR ↑↑↑
// Uniquely identify this keychain accessor
keychainQueryDictionary[kSecAttrService as String] = KeychainWrapper.serviceName
// Uniquely identify the account who will be accessing the keychain
var encodedIdentifier: NSData? = keyName.dataUsingEncoding(NSUTF8StringEncoding)
keychainQueryDictionary[kSecAttrGeneric as String] = encodedIdentifier
keychainQueryDictionary[kSecAttrAccount as String] = encodedIdentifier
return keychainQueryDictionary
}
Can somebody tells me how to solve these error please.
CFStringRef is bridged with NSString which is bridged with String. The simplest solution is to just cast kSecValueData and kSecClass to Strings or NSStrings:
Here:
let updateDictionary = [kSecValueData as String: value]
And here:
var keychainQueryDictionary: NSMutableDictionary = [kSecClass as NSString: kSecClassGenericPassword]
I think it will be more readable
let keychainQueryDictionary : [String: AnyObject] = [
kSecClass : kSecClassGenericPassword,
kSecAttrService : serviceIdentifier,
kSecAttrAccount : accountName
]

How do I get current network location name?

In system network preference there are some location names.How to get the current or active network location name and list of all network locations? I guess SystemConfiguration.framework supports this but i didn't get exactly which API to use.Thanks in advance for your answer. RegardsDevara Gudda
You can use SCPreferencesCreate to get the preferences, then SCNetworkSetCopyAll to get just the network locations. SCNetworkSetGetName will get the name of a location.
SCPreferencesRef prefs = SCPreferencesCreate(NULL, #"SystemConfiguration", NULL);
NSArray *locations = (NSArray *)SCNetworkSetCopyAll(prefs);
for (id item in locations) {
NSString *name = (NSString *)SCNetworkSetGetName((SCNetworkSetRef)item);
...
}
CFRelease(locations);
CFRelease(prefs);
Read "System Configuration Programming Guidelines" for more.
This is a swift 5.1 implementation to get list of all available network location
func getAvailabeNetworkLocation() -> [String]
{
var results = [String]()
var prefs : SCPreferences
prefs = SCPreferencesCreate (nil, "Softech" as CFString, nil)!
var sets : CFArray
sets = SCNetworkSetCopyAll (prefs)!
let count = CFArrayGetCount(sets) as Int
var newSet : SCNetworkSet? = nil
var bFound : Bool = false
for nIndex in 0..<count {
let mySet = CFArrayGetValueAtIndex (sets, nIndex)
let key = Unmanaged<SCNetworkSet>.fromOpaque(mySet!)
newSet = key.takeUnretainedValue()
let name : String = SCNetworkSetGetName(newSet!)! as String
print("Network location name: \(name)")
results.append(name)
}
return results
}

Resources