11import Foundation
2+ import Subprocess
23
34public enum ValidationError : Error {
45case fileNotFound
@@ -7,7 +8,9 @@ public enum ValidationError: Error{
78case unableToRetrieveSignature
89case invalidIdentifier( identifier: String ? )
910case 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
1215public var description : String {
1316switch self {
@@ -21,10 +24,14 @@ public enum ValidationError: Error{
2124" Unable to retrieve signing information. "
2225case 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 ) . "
2629case let . invalidTeamIdentifier( identifier) :
2730" Invalid team identifier: \( identifier ?? " unknown " ) . "
31+ case let . unableToReadVersion( error) :
32+ " Unable to execute the binary to read version: \( error. localizedDescription) "
33+ case let . internalError( status) :
34+ " Internal error with OSStatus code: \( status) . "
2835}
2936}
3037
@@ -37,22 +44,32 @@ public class Validator{
3744public static let minimumCoderVersion = " 2.24.2 "
3845
3946private static let expectedIdentifier = " com.coder.cli "
47+ // The Coder team identifier
4048private static let expectedTeamIdentifier = " 4399GN35BJ "
4149
50+ // Apple-issued certificate chain
51+ public static let anchorRequirement = " anchor apple generic "
52+
4253private 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 {
4657throw . fileNotFound
4758}
4859
4960var staticCode : SecStaticCode ?
50- let status = SecStaticCodeCreateWithPath ( path as CFURL , SecCSFlags ( ) , & staticCode)
61+ let status = SecStaticCodeCreateWithPath ( binaryPath as CFURL , SecCSFlags ( ) , & staticCode)
5162guard status == errSecSuccess, let code = staticCode else {
5263throw . 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)
5673guard validateStatus == errSecSuccess else {
5774throw . invalidSignature
5875}
@@ -78,6 +95,32 @@ public class Validator{
7895}
7996}
8097
81- public static let xpcPeerRequirement = " anchor apple generic " + // Apple-issued certificate chain
98+ // This function executes the binary to read its version, and so it assumes
99+ // the signature has already been validated.
100+ public static func validateVersion( binaryPath: URL , serverVersion: String ) async throws ( ValidationError) {
101+ guard FileManager . default. fileExists ( atPath: binaryPath. path) else {
102+ throw . fileNotFound
103+ }
104+
105+ let version : String
106+ do {
107+ try chmodX ( at: binaryPath)
108+ let versionOutput = try await Subprocess . data ( for: [ binaryPath. path, " version " , " --output=json " ] )
109+ let parsed : VersionOutput = try JSONDecoder ( ) . decode ( VersionOutput . self, from: versionOutput)
110+ version = parsed. version
111+ } catch {
112+ throw . unableToReadVersion( error)
113+ }
114+
115+ guard version == serverVersion else {
116+ throw . binaryVersionMismatch( binaryVersion: version, serverVersion: serverVersion)
117+ }
118+ }
119+
120+ struct VersionOutput : Codable {
121+ let version : String
122+ }
123+
124+ public static let xpcPeerRequirement = anchorRequirement +
82125" and certificate leaf[subject.OU] = \" " + expectedTeamIdentifier + " \" " // Signed by the Coder team
83126}
0 commit comments