11import Foundation
2+ import Subprocess
23
34public enum ValidationError : Error {
45 case fileNotFound
@@ -7,7 +8,9 @@ public enum ValidationError: Error {
78 case unableToRetrieveSignature
89 case invalidIdentifier( identifier: String ? )
910 case invalidTeamIdentifier( identifier: String ? )
10- case invalidVersion( version: String ? )
11+ case unableToReadVersion( any Error )
12+ case binaryVersionMismatch( binaryVersion: String , serverVersion: String )
13+ case internalError( OSStatus )
1114
1215 public var description : String {
1316 switch self {
@@ -21,10 +24,14 @@ public enum ValidationError: Error {
2124 " Unable to retrieve signing information. "
2225 case let . invalidIdentifier( identifier) :
2326 " Invalid identifier: \( identifier ?? " unknown " ) . "
24- case let . invalidVersion ( version ) :
25- " Invalid runtime version: \( version ?? " unknown " ) . "
27+ case let . binaryVersionMismatch ( binaryVersion , serverVersion ) :
28+ " Binary version does not match server. Binary : \( binaryVersion ) , Server: \( serverVersion ) . "
2629 case let . invalidTeamIdentifier( identifier) :
2730 " Invalid team identifier: \( identifier ?? " unknown " ) . "
31+ case . unableToReadVersion:
32+ " Unable to execute the binary to read version "
33+ case let . internalError( status) :
34+ " Internal error with OSStatus code: \( status) . "
2835 }
2936 }
3037
@@ -37,22 +44,32 @@ public class Validator {
3744 public static let minimumCoderVersion = " 2.24.2 "
3845
3946 private static let expectedIdentifier = " com.coder.cli "
47+ // The Coder team identifier
4048 private static let expectedTeamIdentifier = " 4399GN35BJ "
4149
50+ // Apple-issued certificate chain
51+ public static let anchorRequirement = " anchor apple generic "
52+
4253 private static let signInfoFlags : SecCSFlags = . init( rawValue: kSecCSSigningInformation)
4354
44- public static func validate ( path : URL ) throws ( ValidationError) {
45- guard FileManager . default. fileExists ( atPath: path . path) else {
55+ public static func validateSignature ( binaryPath : URL ) throws ( ValidationError) {
56+ guard FileManager . default. fileExists ( atPath: binaryPath . path) else {
4657 throw . fileNotFound
4758 }
4859
4960 var staticCode : SecStaticCode ?
50- let status = SecStaticCodeCreateWithPath ( path as CFURL , SecCSFlags ( ) , & staticCode)
61+ let status = SecStaticCodeCreateWithPath ( binaryPath as CFURL , SecCSFlags ( ) , & staticCode)
5162 guard status == errSecSuccess, let code = staticCode else {
5263 throw . unableToCreateStaticCode
5364 }
5465
55- let validateStatus = SecStaticCodeCheckValidity ( code, SecCSFlags ( ) , nil )
66+ var requirement : SecRequirement ?
67+ let reqStatus = SecRequirementCreateWithString ( anchorRequirement as CFString , SecCSFlags ( ) , & requirement)
68+ guard reqStatus == errSecSuccess, let requirement else {
69+ throw . internalError( OSStatus ( reqStatus) )
70+ }
71+
72+ let validateStatus = SecStaticCodeCheckValidity ( code, SecCSFlags ( ) , requirement)
5673 guard validateStatus == errSecSuccess else {
5774 throw . invalidSignature
5875 }
@@ -78,6 +95,29 @@ public class Validator {
7895 }
7996 }
8097
81- public static let xpcPeerRequirement = " anchor apple generic " + // Apple-issued certificate chain
98+ public static func validateVersion( binaryPath: URL , serverVersion: String ) async throws ( ValidationError) {
99+ guard FileManager . default. fileExists ( atPath: binaryPath. path) else {
100+ throw . fileNotFound
101+ }
102+
103+ let version : String
104+ do {
105+ let versionOutput = try await Subprocess . data ( for: [ binaryPath. path, " version " , " --output=json " ] )
106+ let parsed : VersionOutput = try JSONDecoder ( ) . decode ( VersionOutput . self, from: versionOutput)
107+ version = parsed. version
108+ } catch {
109+ throw . unableToReadVersion( error)
110+ }
111+
112+ guard version == serverVersion else {
113+ throw . binaryVersionMismatch( binaryVersion: version, serverVersion: serverVersion)
114+ }
115+ }
116+
117+ struct VersionOutput : Codable {
118+ let version : String
119+ }
120+
121+ public static let xpcPeerRequirement = anchorRequirement +
82122 " and certificate leaf[subject.OU] = \" " + expectedTeamIdentifier + " \" " // Signed by the Coder team
83123}
0 commit comments