security-rs 0.4.2

Safe Rust bindings for Apple's Security framework — keychain, identity, certificates, trust, authorization, CMS, SecureTransport, and cryptographic primitives on macOS
Documentation
import Foundation
import Security

private func keyTypeConstant(_ rawValue: UInt32) -> CFString? {
    switch rawValue {
    case 0:
        return kSecAttrKeyTypeRSA
    case 1:
        return kSecAttrKeyTypeECSECPrimeRandom
    default:
        return nil
    }
}

private func signatureAlgorithm(_ rawValue: UInt32) -> SecKeyAlgorithm? {
    switch rawValue {
    case 0:
        return .rsaSignatureMessagePKCS1v15SHA256
    case 1:
        return .rsaSignatureDigestPKCS1v15SHA256
    case 2:
        return .rsaSignatureMessagePSSSHA256
    case 3:
        return .ecdsaSignatureMessageX962SHA256
    case 4:
        return .ecdsaSignatureDigestX962SHA256
    default:
        return nil
    }
}

private func encryptionAlgorithm(_ rawValue: UInt32) -> SecKeyAlgorithm? {
    switch rawValue {
    case 0:
        return .rsaEncryptionRaw
    case 1:
        return .rsaEncryptionPKCS1
    case 2:
        return .rsaEncryptionOAEPSHA1
    case 3:
        return .rsaEncryptionOAEPSHA224
    case 4:
        return .rsaEncryptionOAEPSHA256
    case 5:
        return .rsaEncryptionOAEPSHA384
    case 6:
        return .rsaEncryptionOAEPSHA512
    case 7:
        return .rsaEncryptionOAEPSHA1AESGCM
    case 8:
        return .rsaEncryptionOAEPSHA224AESGCM
    case 9:
        return .rsaEncryptionOAEPSHA256AESGCM
    case 10:
        return .rsaEncryptionOAEPSHA384AESGCM
    case 11:
        return .rsaEncryptionOAEPSHA512AESGCM
    default:
        return nil
    }
}

@_cdecl("security_key_get_type_id")
public func securityKeyGetTypeID() -> UInt {
    SecKeyGetTypeID()
}

@_cdecl("security_private_key_create_with_data")
public func securityPrivateKeyCreateWithData(
    _ dataPointer: UnsafeRawPointer?,
    _ dataLength: Int,
    _ keyTypeRawValue: UInt32,
    _ keySizeBits: Int,
    _ statusOut: UnsafeMutablePointer<Int32>?,
    _ errorOut: UnsafeMutablePointer<UnsafeMutableRawPointer?>?
) -> UnsafeMutableRawPointer? {
    clearError(errorOut)
    setStatus(statusOut, errSecSuccess)

    guard let keyData = dataFromPointer(dataPointer, length: dataLength),
          let keyType = keyTypeConstant(keyTypeRawValue),
          keySizeBits > 0
    else {
        setStatus(statusOut, errSecParam)
        setError(errorOut, "key data, key type, and key size are required")
        return nil
    }

    let attributes: [CFString: Any] = [
        kSecAttrKeyType: keyType,
        kSecAttrKeyClass: kSecAttrKeyClassPrivate,
        kSecAttrKeySizeInBits: NSNumber(value: keySizeBits),
    ]

    var error: Unmanaged<CFError>?
    guard let key = SecKeyCreateWithData(keyData as CFData, attributes as CFDictionary, &error) else {
        setStatus(statusOut, errSecParam)
        setError(errorOut, error)
        return nil
    }

    return retain(key)
}

@_cdecl("security_private_key_import_item")
public func securityPrivateKeyImportItem(
    _ dataPointer: UnsafeRawPointer?,
    _ dataLength: Int,
    _ fileNamePointer: UnsafePointer<CChar>?,
    _ formatRawValue: UInt32,
    _ itemTypeRawValue: UInt32,
    _ statusOut: UnsafeMutablePointer<Int32>?,
    _ errorOut: UnsafeMutablePointer<UnsafeMutableRawPointer?>?
) -> UnsafeMutableRawPointer? {
    clearError(errorOut)
    setStatus(statusOut, errSecSuccess)

    guard let importedData = dataFromPointer(dataPointer, length: dataLength),
          var inputFormat = SecExternalFormat(rawValue: formatRawValue),
          var itemType = SecExternalItemType(rawValue: itemTypeRawValue)
    else {
        setStatus(statusOut, errSecParam)
        setError(errorOut, "import data, format, and item type are required")
        return nil
    }

    let fileName = stringFromCString(fileNamePointer) as CFString?
    var items: CFArray?
    let status = SecItemImport(
        importedData as CFData,
        fileName,
        &inputFormat,
        &itemType,
        SecItemImportExportFlags(),
        nil,
        nil,
        &items
    )
    guard status == errSecSuccess else {
        setStatus(statusOut, status)
        setError(errorOut, "SecItemImport failed: \(statusMessage(status))")
        return nil
    }

    guard let importedItems = items as? [SecKey],
          let privateKey = importedItems.first
    else {
        setStatus(statusOut, errSecItemNotFound)
        setError(errorOut, "SecItemImport returned no SecKey result")
        return nil
    }

    return retain(privateKey)
}

@_cdecl("security_private_key_create_signature")
public func securityPrivateKeyCreateSignature(
    _ keyPointer: UnsafeMutableRawPointer?,
    _ algorithmRawValue: UInt32,
    _ dataPointer: UnsafeRawPointer?,
    _ dataLength: Int,
    _ statusOut: UnsafeMutablePointer<Int32>?,
    _ errorOut: UnsafeMutablePointer<UnsafeMutableRawPointer?>?
) -> UnsafeMutableRawPointer? {
    clearError(errorOut)
    setStatus(statusOut, errSecSuccess)

    guard let key = unbox(keyPointer, as: SecKey.self),
          let algorithm = signatureAlgorithm(algorithmRawValue),
          let data = dataFromPointer(dataPointer, length: dataLength)
    else {
        setStatus(statusOut, errSecParam)
        setError(errorOut, "private key, signature algorithm, and data are required")
        return nil
    }

    guard SecKeyIsAlgorithmSupported(key, .sign, algorithm) else {
        setStatus(statusOut, errSecParam)
        setError(errorOut, "SecKeyCreateSignature algorithm is not supported by this key")
        return nil
    }

    var error: Unmanaged<CFError>?
    guard let signature = SecKeyCreateSignature(key, algorithm, data as CFData, &error) as Data? else {
        setStatus(statusOut, errSecParam)
        setError(errorOut, error)
        return nil
    }

    return dataHandle(signature)
}

@_cdecl("security_public_key_verify_signature")
public func securityPublicKeyVerifySignature(
    _ keyPointer: UnsafeMutableRawPointer?,
    _ algorithmRawValue: UInt32,
    _ signedDataPointer: UnsafeRawPointer?,
    _ signedDataLength: Int,
    _ signaturePointer: UnsafeRawPointer?,
    _ signatureLength: Int,
    _ statusOut: UnsafeMutablePointer<Int32>?,
    _ errorOut: UnsafeMutablePointer<UnsafeMutableRawPointer?>?
) -> Bool {
    clearError(errorOut)
    setStatus(statusOut, errSecSuccess)

    guard let key = unbox(keyPointer, as: SecKey.self),
          let algorithm = signatureAlgorithm(algorithmRawValue),
          let signedData = dataFromPointer(signedDataPointer, length: signedDataLength),
          let signature = dataFromPointer(signaturePointer, length: signatureLength)
    else {
        setStatus(statusOut, errSecParam)
        setError(errorOut, "public key, signature algorithm, signed data, and signature are required")
        return false
    }

    guard SecKeyIsAlgorithmSupported(key, .verify, algorithm) else {
        setStatus(statusOut, errSecParam)
        setError(errorOut, "SecKeyVerifySignature algorithm is not supported by this key")
        return false
    }

    var error: Unmanaged<CFError>?
    let isValid = SecKeyVerifySignature(key, algorithm, signedData as CFData, signature as CFData, &error)
    if !isValid, let error {
        _ = error.takeRetainedValue()
    }
    return isValid
}

@_cdecl("security_key_get_block_size")
public func securityKeyGetBlockSize(_ keyPointer: UnsafeMutableRawPointer?) -> Int {
    guard let key = unbox(keyPointer, as: SecKey.self) else {
        return 0
    }

    return SecKeyGetBlockSize(key)
}

@_cdecl("security_key_copy_external_representation")
public func securityKeyCopyExternalRepresentation(
    _ keyPointer: UnsafeMutableRawPointer?,
    _ statusOut: UnsafeMutablePointer<Int32>?,
    _ errorOut: UnsafeMutablePointer<UnsafeMutableRawPointer?>?
) -> UnsafeMutableRawPointer? {
    clearError(errorOut)
    setStatus(statusOut, errSecSuccess)

    guard let key = unbox(keyPointer, as: SecKey.self) else {
        setStatus(statusOut, errSecParam)
        setError(errorOut, "key handle is required")
        return nil
    }

    var error: Unmanaged<CFError>?
    guard let representation = SecKeyCopyExternalRepresentation(key, &error) as Data? else {
        setStatus(statusOut, errSecParam)
        setError(errorOut, error)
        return nil
    }

    return dataHandle(representation)
}

@_cdecl("security_public_key_create_encrypted_data")
public func securityPublicKeyCreateEncryptedData(
    _ keyPointer: UnsafeMutableRawPointer?,
    _ algorithmRawValue: UInt32,
    _ dataPointer: UnsafeRawPointer?,
    _ dataLength: Int,
    _ statusOut: UnsafeMutablePointer<Int32>?,
    _ errorOut: UnsafeMutablePointer<UnsafeMutableRawPointer?>?
) -> UnsafeMutableRawPointer? {
    clearError(errorOut)
    setStatus(statusOut, errSecSuccess)

    guard let key = unbox(keyPointer, as: SecKey.self),
          let algorithm = encryptionAlgorithm(algorithmRawValue),
          let plaintext = dataFromPointer(dataPointer, length: dataLength)
    else {
        setStatus(statusOut, errSecParam)
        setError(errorOut, "public key, encryption algorithm, and plaintext are required")
        return nil
    }

    guard SecKeyIsAlgorithmSupported(key, .encrypt, algorithm) else {
        setStatus(statusOut, errSecParam)
        setError(errorOut, "SecKeyCreateEncryptedData algorithm is not supported by this key")
        return nil
    }

    var error: Unmanaged<CFError>?
    guard let ciphertext = SecKeyCreateEncryptedData(key, algorithm, plaintext as CFData, &error) as Data? else {
        setStatus(statusOut, errSecParam)
        setError(errorOut, error)
        return nil
    }

    return dataHandle(ciphertext)
}

@_cdecl("security_private_key_create_decrypted_data")
public func securityPrivateKeyCreateDecryptedData(
    _ keyPointer: UnsafeMutableRawPointer?,
    _ algorithmRawValue: UInt32,
    _ dataPointer: UnsafeRawPointer?,
    _ dataLength: Int,
    _ statusOut: UnsafeMutablePointer<Int32>?,
    _ errorOut: UnsafeMutablePointer<UnsafeMutableRawPointer?>?
) -> UnsafeMutableRawPointer? {
    clearError(errorOut)
    setStatus(statusOut, errSecSuccess)

    guard let key = unbox(keyPointer, as: SecKey.self),
          let algorithm = encryptionAlgorithm(algorithmRawValue),
          let ciphertext = dataFromPointer(dataPointer, length: dataLength)
    else {
        setStatus(statusOut, errSecParam)
        setError(errorOut, "private key, decryption algorithm, and ciphertext are required")
        return nil
    }

    guard SecKeyIsAlgorithmSupported(key, .decrypt, algorithm) else {
        setStatus(statusOut, errSecParam)
        setError(errorOut, "SecKeyCreateDecryptedData algorithm is not supported by this key")
        return nil
    }

    var error: Unmanaged<CFError>?
    guard let plaintext = SecKeyCreateDecryptedData(key, algorithm, ciphertext as CFData, &error) as Data? else {
        setStatus(statusOut, errSecParam)
        setError(errorOut, error)
        return nil
    }

    return dataHandle(plaintext)
}