1
1
import Foundation
2
+ import Subprocess
2
3
3
4
public enum ValidationError : Error {
4
5
case fileNotFound
@@ -7,7 +8,9 @@ public enum ValidationError: Error {
7
8
case unableToRetrieveSignature
8
9
case invalidIdentifier( identifier: String ? )
9
10
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 )
11
14
12
15
public var description : String {
13
16
switch self {
@@ -21,10 +24,14 @@ public enum ValidationError: Error {
21
24
" Unable to retrieve signing information. "
22
25
case let . invalidIdentifier( identifier) :
23
26
" 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 ) . "
26
29
case let . invalidTeamIdentifier( identifier) :
27
30
" 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) . "
28
35
}
29
36
}
30
37
@@ -37,22 +44,32 @@ public class Validator {
37
44
public static let minimumCoderVersion = " 2.24.2 "
38
45
39
46
private static let expectedIdentifier = " com.coder.cli "
47
+ // The Coder team identifier
40
48
private static let expectedTeamIdentifier = " 4399GN35BJ "
41
49
50
+ // Apple-issued certificate chain
51
+ public static let anchorRequirement = " anchor apple generic "
52
+
42
53
private static let signInfoFlags : SecCSFlags = . init( rawValue: kSecCSSigningInformation)
43
54
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 {
46
57
throw . fileNotFound
47
58
}
48
59
49
60
var staticCode : SecStaticCode ?
50
- let status = SecStaticCodeCreateWithPath ( path as CFURL , SecCSFlags ( ) , & staticCode)
61
+ let status = SecStaticCodeCreateWithPath ( binaryPath as CFURL , SecCSFlags ( ) , & staticCode)
51
62
guard status == errSecSuccess, let code = staticCode else {
52
63
throw . unableToCreateStaticCode
53
64
}
54
65
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)
56
73
guard validateStatus == errSecSuccess else {
57
74
throw . invalidSignature
58
75
}
@@ -78,6 +95,29 @@ public class Validator {
78
95
}
79
96
}
80
97
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 +
82
122
" and certificate leaf[subject.OU] = \" " + expectedTeamIdentifier + " \" " // Signed by the Coder team
83
123
}
0 commit comments