Get a macOS Keychain certificate's SHA1 hash in Swift

757 Views Asked by At

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?

1

There are 1 best solutions below

4
On BEST ANSWER

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