CallerIdentity.swift
95 lines 3.4 KB
Raw
sha256:bd1eb24434d281fe65142038c1bf217493421f5a86880c22dff7e6037d2d4700 feat(companion): add production macOS distribution path Human minor ⚠ breaking 25 days ago
1 import Foundation
2 import Security
3 import XPC
4
5 public struct CallerIdentity: Equatable, Sendable {
6 public let identifier: String
7 public let teamIdentifier: String
8
9 public init(identifier: String, teamIdentifier: String) {
10 self.identifier = identifier
11 self.teamIdentifier = teamIdentifier
12 }
13 }
14
15 public enum CallerIdentityError: Error, Equatable {
16 case unsignedHost
17 case missingAuditToken
18 case invalidSignature
19 case missingSigningInformation
20 case teamMismatch
21 case identifierDenied
22 }
23
24 public struct CallerIdentityPolicy: Sendable {
25 public let teamIdentifier: String
26 public let allowedIdentifiers: Set<String>
27
28 public init(teamIdentifier: String, allowedIdentifiers: Set<String>) {
29 self.teamIdentifier = teamIdentifier
30 self.allowedIdentifiers = allowedIdentifiers
31 }
32
33 public func evaluate(_ identity: CallerIdentity) throws -> CallerIdentity {
34 guard identity.teamIdentifier == teamIdentifier else {
35 throw CallerIdentityError.teamMismatch
36 }
37 guard allowedIdentifiers.contains(identity.identifier) else {
38 throw CallerIdentityError.identifierDenied
39 }
40 return identity
41 }
42 }
43
44 public enum CallerIdentityValidator {
45 public static func hostTeamIdentifier() throws -> String {
46 var selfCode: SecCode?
47 guard SecCodeCopySelf([], &selfCode) == errSecSuccess, let selfCode else {
48 throw CallerIdentityError.unsignedHost
49 }
50 return try signingIdentity(for: selfCode).teamIdentifier
51 }
52
53 public static func validate(
54 message: xpc_object_t,
55 allowedIdentifiers: Set<String> = KnowtationReleaseContract.allowedCallerIdentifiers
56 ) throws -> CallerIdentity {
57 let teamIdentifier = try hostTeamIdentifier()
58 var guestCode: SecCode?
59 let guestStatus = SecCodeCreateWithXPCMessage(message, [], &guestCode)
60 guard guestStatus == errSecSuccess, let guestCode else {
61 throw CallerIdentityError.invalidSignature
62 }
63
64 guard SecCodeCheckValidity(guestCode, [], nil) == errSecSuccess else {
65 throw CallerIdentityError.invalidSignature
66 }
67 let identity = try signingIdentity(for: guestCode)
68 return try CallerIdentityPolicy(
69 teamIdentifier: teamIdentifier,
70 allowedIdentifiers: allowedIdentifiers
71 ).evaluate(identity)
72 }
73
74 public static func signingIdentity(for code: SecCode) throws -> CallerIdentity {
75 var staticCode: SecStaticCode?
76 guard SecCodeCopyStaticCode(code, [], &staticCode) == errSecSuccess, let staticCode else {
77 throw CallerIdentityError.missingSigningInformation
78 }
79 var information: CFDictionary?
80 let status = SecCodeCopySigningInformation(
81 staticCode,
82 SecCSFlags(rawValue: kSecCSSigningInformation),
83 &information
84 )
85 guard status == errSecSuccess,
86 let dictionary = information as? [String: Any],
87 let identifier = dictionary[kSecCodeInfoIdentifier as String] as? String,
88 let teamIdentifier = dictionary[kSecCodeInfoTeamIdentifier as String] as? String,
89 !identifier.isEmpty,
90 !teamIdentifier.isEmpty else {
91 throw CallerIdentityError.missingSigningInformation
92 }
93 return CallerIdentity(identifier: identifier, teamIdentifier: teamIdentifier)
94 }
95 }
File History 1 commit
sha256:bd1eb24434d281fe65142038c1bf217493421f5a86880c22dff7e6037d2d4700 feat(companion): add production macOS distribution path Human minor ⚠ 25 days ago