Get a macOS Keychain certificate's SHA1 hash in Swift

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