Skip to content

Commit 262c4d1

Browse files
committed
chore: ensure downloaded slim binary version matches server
1 parent e9e15db commit 262c4d1

File tree

2 files changed

+50
-9
lines changed

2 files changed

+50
-9
lines changed

Coder-Desktop/Coder-DesktopHelper/Manager.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@ actor Manager {
7474
}
7575
pushProgress(stage: .validating)
7676
do {
77-
try Validator.validate(path: dest)
77+
try Validator.validateSignature(binaryPath: dest)
78+
try await Validator.validateVersion(binaryPath: dest, serverVersion: buildInfo.version)
7879
} catch {
7980
throw .validation(error)
8081
}

Coder-Desktop/VPNLib/Validate.swift

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Foundation
2+
import Subprocess
23

34
public 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

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy