TM-SGNL-iOS/Signal/DeviceTransfer/SelfSignedIdentity.swift
TeleMessage developers dde0620daf initial commit
2025-05-03 12:28:28 -07:00

116 lines
4.4 KiB
Swift

//
// Copyright 2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import CryptoKit
import LibSignalClient
import SignalServiceKit
struct SelfSignedIdentity {
private static let temporaryIdentityKeychainIdentifier = "org.signal.temporaryIdentityKeychainIdentifier"
static func create(name: String, validForDays: Int) throws -> SecIdentity {
let (certificate, key) = try createSelfSignedCertificate(name: name, validForDays: validForDays)
// If there's an existing identity by this name in the keychain,
// delete it. There should only be one.
try deleteSelfSignedIdentityFromKeychain()
// Add the certificate to the keychain
do {
let addquery: [CFString: Any] = [
kSecClass: kSecClassCertificate,
kSecValueRef: certificate,
kSecAttrLabel: temporaryIdentityKeychainIdentifier
]
guard SecItemAdd(addquery as CFDictionary, nil) == errSecSuccess else {
throw OWSAssertionError("failed to add certificate to keychain")
}
}
// Add the private key to the keychain
do {
let addquery: [CFString: Any] = [
kSecClass: kSecClassKey,
kSecAttrKeyClass: kSecAttrKeyClassPrivate,
kSecValueRef: key,
kSecAttrLabel: temporaryIdentityKeychainIdentifier
]
guard SecItemAdd(addquery as CFDictionary, nil) == errSecSuccess else {
throw OWSAssertionError("failed to add private key to keychain")
}
}
// Fetch the composed identity from the keychain
let identity: SecIdentity = try {
let copyQuery: [CFString: Any] = [
kSecClass: kSecClassIdentity,
kSecReturnRef: true,
kSecAttrLabel: temporaryIdentityKeychainIdentifier
]
var typeRef: CFTypeRef?
guard SecItemCopyMatching(copyQuery as CFDictionary, &typeRef) == errSecSuccess else {
throw OWSAssertionError("failed to fetch identity from keychain")
}
return (typeRef as! SecIdentity)
}()
// We don't actually want to persist the identity, but it needs to go
// through the keychain in order to create it. We can delete it once
// we've got the ref.
try deleteSelfSignedIdentityFromKeychain()
return identity
}
private static func deleteSelfSignedIdentityFromKeychain() throws {
let deleteQuery: [CFString: Any] = [
kSecClass: kSecClassIdentity,
kSecAttrLabel: temporaryIdentityKeychainIdentifier
]
let status = SecItemDelete(deleteQuery as CFDictionary)
guard [errSecSuccess, errSecItemNotFound].contains(status) else {
throw OWSAssertionError("failed to delete existing identity")
}
}
private static func createSelfSignedCertificate(name: String, validForDays days: Int = 365) throws -> (SecCertificate, SecKey) {
let deviceTransferKey = DeviceTransferKey.generate(formattedAs: .keySpecific)
let certificateData = deviceTransferKey.generateCertificate(name, days)
// Convert to Security refs
guard let certificate = SecCertificateCreateWithData(nil, Data(certificateData) as CFData) else {
throw OWSAssertionError("Failed to initialize SecCertificate")
}
guard let privateKey = SecKeyCreateWithData(
Data(deviceTransferKey.privateKey) as CFData,
[
kSecAttrKeyType: kSecAttrKeyTypeRSA,
kSecAttrKeyClass: kSecAttrKeyClassPrivate,
kSecAttrKeySizeInBits: 4096
] as [CFString: Any] as CFDictionary,
nil
) else {
throw OWSAssertionError("Failed to initialize SecKey")
}
return (certificate, privateKey)
}
}
extension SecIdentity {
func computeCertificateHash() throws -> Data {
var optionalCertificate: SecCertificate?
guard SecIdentityCopyCertificate(self, &optionalCertificate) == errSecSuccess, let certificate = optionalCertificate else {
throw OWSAssertionError("failed to copy certificate from identity")
}
let certificateData = SecCertificateCopyData(certificate) as Data
return Data(SHA256.hash(data: certificateData))
}
}