Skip to content

Commit 8fceeab

Browse files
committed
chore: ensure downloaded slim binary version matches server
1 parent 02b3369 commit 8fceeab

File tree

2 files changed

+53
-9
lines changed

2 files changed

+53
-9
lines changed

‎Coder-Desktop/Coder-DesktopHelper/Manager.swift‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,8 @@ actor Manager{
7676
}
7777
pushProgress(stage:.validating)
7878
do{
79-
tryValidator.validate(path: dest)
79+
tryValidator.validateSignature(binaryPath: dest)
80+
tryawaitValidator.validateVersion(binaryPath: dest, serverVersion: buildInfo.version)
8081
}catch{
8182
// Cleanup unvalid binary
8283
try?FileManager.default.removeItem(at: dest)

‎Coder-Desktop/VPNLib/Validate.swift‎

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

34
publicenumValidationError: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(anyError)
12+
case binaryVersionMismatch(binaryVersion:String, serverVersion:String)
13+
case internalError(OSStatus)
1114

1215
publicvardescription:String{
1316
switchself{
@@ -21,10 +24,14 @@ public enum ValidationError: Error{
2124
"Unable to retrieve signing information."
2225
caselet.invalidIdentifier(identifier):
2326
"Invalid identifier: \(identifier ??"unknown")."
24-
caselet.invalidVersion(version):
25-
"Invalid runtime version: \(version ??"unknown")."
27+
caselet.binaryVersionMismatch(binaryVersion, serverVersion):
28+
"Binary version does not match server. Binary: \(binaryVersion), Server: \(serverVersion)."
2629
caselet.invalidTeamIdentifier(identifier):
2730
"Invalid team identifier: \(identifier ??"unknown")."
31+
caselet.unableToReadVersion(error):
32+
"Unable to execute the binary to read version: \(error.localizedDescription)"
33+
caselet.internalError(status):
34+
"Internal error with OSStatus code: \(status)."
2835
}
2936
}
3037

@@ -37,22 +44,32 @@ public class Validator{
3744
publicstaticletminimumCoderVersion="2.24.2"
3845

3946
privatestaticletexpectedIdentifier="com.coder.cli"
47+
// The Coder team identifier
4048
privatestaticletexpectedTeamIdentifier="4399GN35BJ"
4149

50+
// Apple-issued certificate chain
51+
publicstaticletanchorRequirement="anchor apple generic"
52+
4253
privatestaticletsignInfoFlags:SecCSFlags=.init(rawValue: kSecCSSigningInformation)
4354

44-
publicstaticfuncvalidate(path:URL)throws(ValidationError){
45-
guardFileManager.default.fileExists(atPath:path.path)else{
55+
publicstaticfuncvalidateSignature(binaryPath:URL)throws(ValidationError){
56+
guardFileManager.default.fileExists(atPath:binaryPath.path)else{
4657
throw.fileNotFound
4758
}
4859

4960
varstaticCode:SecStaticCode?
50-
letstatus=SecStaticCodeCreateWithPath(pathasCFURL,SecCSFlags(),&staticCode)
61+
letstatus=SecStaticCodeCreateWithPath(binaryPathasCFURL,SecCSFlags(),&staticCode)
5162
guard status == errSecSuccess,let code = staticCode else{
5263
throw.unableToCreateStaticCode
5364
}
5465

55-
letvalidateStatus=SecStaticCodeCheckValidity(code,SecCSFlags(),nil)
66+
varrequirement:SecRequirement?
67+
letreqStatus=SecRequirementCreateWithString(anchorRequirement asCFString,SecCSFlags(),&requirement)
68+
guard reqStatus == errSecSuccess,let requirement else{
69+
throw.internalError(OSStatus(reqStatus))
70+
}
71+
72+
letvalidateStatus=SecStaticCodeCheckValidity(code,SecCSFlags(), requirement)
5673
guard validateStatus == errSecSuccess else{
5774
throw.invalidSignature
5875
}
@@ -78,6 +95,32 @@ public class Validator{
7895
}
7996
}
8097

81-
publicstaticletxpcPeerRequirement="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+
publicstaticfunc validateVersion(binaryPath:URL, serverVersion:String)asyncthrows(ValidationError){
101+
guardFileManager.default.fileExists(atPath: binaryPath.path)else{
102+
throw.fileNotFound
103+
}
104+
105+
letversion:String
106+
do{
107+
trychmodX(at: binaryPath)
108+
letversionOutput=tryawaitSubprocess.data(for:[binaryPath.path,"version","--output=json"])
109+
letparsed:VersionOutput=tryJSONDecoder().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+
structVersionOutput:Codable{
121+
letversion:String
122+
}
123+
124+
publicstaticletxpcPeerRequirement= anchorRequirement +
82125
" and certificate leaf[subject.OU] = \""+ expectedTeamIdentifier +"\"" // Signed by the Coder team
83126
}

0 commit comments

Comments
(0)