Skip to content

Commit 6687411

Browse files
committed
fix: add code signing requirements to xpc connections
1 parent eebf562 commit 6687411

File tree

5 files changed

+132
-124
lines changed

5 files changed

+132
-124
lines changed

‎Coder-Desktop/Coder-Desktop/AppHelperXPCClient.swift‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import VPNLib
3737
_ =self.connect()
3838
}
3939
logger.info("connecting to \(helperAppMachServiceName)")
40+
connection.setCodeSigningRequirement(SignatureValidator.xpcPeerRequirement)
4041
connection.resume()
4142
self.connection = connection
4243
return connection

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ class HelperNEXPCServer: NSObject, NSXPCListenerDelegate, @unchecked Sendable{
3232
conns.removeAll{ $0 == newConnection }
3333
logger.debug("connection interrupted")
3434
}
35+
newConnection.setCodeSigningRequirement(SignatureValidator.xpcPeerRequirement)
3536
newConnection.resume()
3637
conns.append(newConnection)
3738
returntrue
@@ -145,6 +146,7 @@ class HelperAppXPCServer: NSObject, NSXPCListenerDelegate, @unchecked Sendable{
145146
conns.removeAll{ $0 == newConnection }
146147
logger.debug("app connection invalidated")
147148
}
149+
newConnection.setCodeSigningRequirement(SignatureValidator.xpcPeerRequirement)
148150
newConnection.resume()
149151
conns.append(newConnection)
150152
returntrue

‎Coder-Desktop/VPN/NEHelperXPCClient.swift‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ final class HelperXPCClient: @unchecked Sendable{
2929
connection.interruptionHandler ={[weak self]in
3030
self?.connection =nil
3131
}
32+
connection.setCodeSigningRequirement(SignatureValidator.xpcPeerRequirement)
3233
connection.resume()
3334
self.connection = connection
3435
return connection

‎Coder-Desktop/VPNLib/Download.swift‎

Lines changed: 0 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -1,130 +1,6 @@
11
import CryptoKit
22
import Foundation
33

4-
publicenumValidationError:Error{
5-
case fileNotFound
6-
case unableToCreateStaticCode
7-
case invalidSignature
8-
case unableToRetrieveInfo
9-
case invalidIdentifier(identifier:String?)
10-
case invalidTeamIdentifier(identifier:String?)
11-
case missingInfoPList
12-
case invalidVersion(version:String?)
13-
case belowMinimumCoderVersion
14-
15-
publicvardescription:String{
16-
switchself{
17-
case.fileNotFound:
18-
"The file does not exist."
19-
case.unableToCreateStaticCode:
20-
"Unable to create a static code object."
21-
case.invalidSignature:
22-
"The file's signature is invalid."
23-
case.unableToRetrieveInfo:
24-
"Unable to retrieve signing information."
25-
caselet.invalidIdentifier(identifier):
26-
"Invalid identifier: \(identifier ??"unknown")."
27-
caselet.invalidVersion(version):
28-
"Invalid runtime version: \(version ??"unknown")."
29-
caselet.invalidTeamIdentifier(identifier):
30-
"Invalid team identifier: \(identifier ??"unknown")."
31-
case.missingInfoPList:
32-
"Info.plist is not embedded within the dylib."
33-
case.belowMinimumCoderVersion:
34-
"""
35-
The Coder deployment must be version \(SignatureValidator.minimumCoderVersion)
36-
or higher to use Coder Desktop.
37-
"""
38-
}
39-
}
40-
41-
publicvarlocalizedDescription:String{ description }
42-
}
43-
44-
publicclassSignatureValidator{
45-
// Whilst older dylibs exist, this app assumes v2.20 or later.
46-
publicstaticletminimumCoderVersion="2.20.0"
47-
48-
privatestaticletexpectedName="CoderVPN"
49-
privatestaticletexpectedIdentifier="com.coder.Coder-Desktop.VPN.dylib"
50-
privatestaticletexpectedTeamIdentifier="4399GN35BJ"
51-
52-
privatestaticletinfoIdentifierKey="CFBundleIdentifier"
53-
privatestaticletinfoNameKey="CFBundleName"
54-
privatestaticletinfoShortVersionKey="CFBundleShortVersionString"
55-
56-
privatestaticletsignInfoFlags:SecCSFlags=.init(rawValue: kSecCSSigningInformation)
57-
58-
// `expectedVersion` must be of the form `[0-9]+.[0-9]+.[0-9]+`
59-
publicstaticfunc validate(path:URL, expectedVersion:String)throws(ValidationError){
60-
guardFileManager.default.fileExists(atPath: path.path)else{
61-
throw.fileNotFound
62-
}
63-
64-
varstaticCode:SecStaticCode?
65-
letstatus=SecStaticCodeCreateWithPath(path asCFURL,SecCSFlags(),&staticCode)
66-
guard status == errSecSuccess,let code = staticCode else{
67-
throw.unableToCreateStaticCode
68-
}
69-
70-
letvalidateStatus=SecStaticCodeCheckValidity(code,SecCSFlags(),nil)
71-
guard validateStatus == errSecSuccess else{
72-
throw.invalidSignature
73-
}
74-
75-
varinformation:CFDictionary?
76-
letinfoStatus=SecCodeCopySigningInformation(code, signInfoFlags,&information)
77-
guard infoStatus == errSecSuccess,let info = information as?[String:Any]else{
78-
throw.unableToRetrieveInfo
79-
}
80-
81-
guardlet identifier =info[kSecCodeInfoIdentifier asString]as?String,
82-
identifier == expectedIdentifier
83-
else{
84-
throw.invalidIdentifier(identifier:info[kSecCodeInfoIdentifier asString]as?String)
85-
}
86-
87-
guardlet teamIdentifier =info[kSecCodeInfoTeamIdentifier asString]as?String,
88-
teamIdentifier == expectedTeamIdentifier
89-
else{
90-
throw.invalidTeamIdentifier(
91-
identifier:info[kSecCodeInfoTeamIdentifier asString]as?String
92-
)
93-
}
94-
95-
guardlet infoPlist =info[kSecCodeInfoPList asString]as?[String:AnyObject]else{
96-
throw.missingInfoPList
97-
}
98-
99-
tryvalidateInfo(infoPlist: infoPlist, expectedVersion: expectedVersion)
100-
}
101-
102-
privatestaticfunc validateInfo(infoPlist:[String:AnyObject], expectedVersion:String)throws(ValidationError){
103-
guardlet plistIdent =infoPlist[infoIdentifierKey]as?String, plistIdent == expectedIdentifier else{
104-
throw.invalidIdentifier(identifier:infoPlist[infoIdentifierKey]as?String)
105-
}
106-
107-
guardlet plistName =infoPlist[infoNameKey]as?String, plistName == expectedName else{
108-
throw.invalidIdentifier(identifier:infoPlist[infoNameKey]as?String)
109-
}
110-
111-
// Downloaded dylib must match the version of the server
112-
guardlet dylibVersion =infoPlist[infoShortVersionKey]as?String,
113-
expectedVersion == dylibVersion
114-
else{
115-
throw.invalidVersion(version:infoPlist[infoShortVersionKey]as?String)
116-
}
117-
118-
// Downloaded dylib must be at least the minimum Coder server version
119-
guardlet dylibVersion =infoPlist[infoShortVersionKey]as?String,
120-
// x.compare(y) is .orderedDescending if x > y
121-
minimumCoderVersion.compare(dylibVersion, options:.numeric)!=.orderedDescending
122-
else{
123-
throw.belowMinimumCoderVersion
124-
}
125-
}
126-
}
127-
1284
publicfunc download(
1295
src:URL,
1306
dest:URL,
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import Foundation
2+
3+
publicenumValidationError:Error{
4+
case fileNotFound
5+
case unableToCreateStaticCode
6+
case invalidSignature
7+
case unableToRetrieveInfo
8+
case invalidIdentifier(identifier:String?)
9+
case invalidTeamIdentifier(identifier:String?)
10+
case missingInfoPList
11+
case invalidVersion(version:String?)
12+
case belowMinimumCoderVersion
13+
14+
publicvardescription:String{
15+
switchself{
16+
case.fileNotFound:
17+
"The file does not exist."
18+
case.unableToCreateStaticCode:
19+
"Unable to create a static code object."
20+
case.invalidSignature:
21+
"The file's signature is invalid."
22+
case.unableToRetrieveInfo:
23+
"Unable to retrieve signing information."
24+
caselet.invalidIdentifier(identifier):
25+
"Invalid identifier: \(identifier ??"unknown")."
26+
caselet.invalidVersion(version):
27+
"Invalid runtime version: \(version ??"unknown")."
28+
caselet.invalidTeamIdentifier(identifier):
29+
"Invalid team identifier: \(identifier ??"unknown")."
30+
case.missingInfoPList:
31+
"Info.plist is not embedded within the dylib."
32+
case.belowMinimumCoderVersion:
33+
"""
34+
The Coder deployment must be version \(SignatureValidator.minimumCoderVersion)
35+
or higher to use Coder Desktop.
36+
"""
37+
}
38+
}
39+
40+
publicvarlocalizedDescription:String{ description }
41+
}
42+
43+
publicclassSignatureValidator{
44+
// Whilst older dylibs exist, this app assumes v2.20 or later.
45+
publicstaticletminimumCoderVersion="2.20.0"
46+
47+
privatestaticletexpectedName="CoderVPN"
48+
privatestaticletexpectedIdentifier="com.coder.Coder-Desktop.VPN.dylib"
49+
privatestaticletexpectedTeamIdentifier="4399GN35BJ"
50+
51+
privatestaticletinfoIdentifierKey="CFBundleIdentifier"
52+
privatestaticletinfoNameKey="CFBundleName"
53+
privatestaticletinfoShortVersionKey="CFBundleShortVersionString"
54+
55+
privatestaticletsignInfoFlags:SecCSFlags=.init(rawValue: kSecCSSigningInformation)
56+
57+
// `expectedVersion` must be of the form `[0-9]+.[0-9]+.[0-9]+`
58+
publicstaticfunc validate(path:URL, expectedVersion:String)throws(ValidationError){
59+
guardFileManager.default.fileExists(atPath: path.path)else{
60+
throw.fileNotFound
61+
}
62+
63+
varstaticCode:SecStaticCode?
64+
letstatus=SecStaticCodeCreateWithPath(path asCFURL,SecCSFlags(),&staticCode)
65+
guard status == errSecSuccess,let code = staticCode else{
66+
throw.unableToCreateStaticCode
67+
}
68+
69+
letvalidateStatus=SecStaticCodeCheckValidity(code,SecCSFlags(),nil)
70+
guard validateStatus == errSecSuccess else{
71+
throw.invalidSignature
72+
}
73+
74+
varinformation:CFDictionary?
75+
letinfoStatus=SecCodeCopySigningInformation(code, signInfoFlags,&information)
76+
guard infoStatus == errSecSuccess,let info = information as?[String:Any]else{
77+
throw.unableToRetrieveInfo
78+
}
79+
80+
guardlet identifier =info[kSecCodeInfoIdentifier asString]as?String,
81+
identifier == expectedIdentifier
82+
else{
83+
throw.invalidIdentifier(identifier:info[kSecCodeInfoIdentifier asString]as?String)
84+
}
85+
86+
guardlet teamIdentifier =info[kSecCodeInfoTeamIdentifier asString]as?String,
87+
teamIdentifier == expectedTeamIdentifier
88+
else{
89+
throw.invalidTeamIdentifier(
90+
identifier:info[kSecCodeInfoTeamIdentifier asString]as?String
91+
)
92+
}
93+
94+
guardlet infoPlist =info[kSecCodeInfoPList asString]as?[String:AnyObject]else{
95+
throw.missingInfoPList
96+
}
97+
98+
tryvalidateInfo(infoPlist: infoPlist, expectedVersion: expectedVersion)
99+
}
100+
101+
publicstaticletxpcPeerRequirement="anchor apple generic"+ // Apple-issued certificate chain
102+
" and certificate leaf[subject.OU] = \""+ expectedTeamIdentifier +"\"" // Signed by the Coder team
103+
104+
privatestaticfunc validateInfo(infoPlist:[String:AnyObject], expectedVersion:String)throws(ValidationError){
105+
guardlet plistIdent =infoPlist[infoIdentifierKey]as?String, plistIdent == expectedIdentifier else{
106+
throw.invalidIdentifier(identifier:infoPlist[infoIdentifierKey]as?String)
107+
}
108+
109+
guardlet plistName =infoPlist[infoNameKey]as?String, plistName == expectedName else{
110+
throw.invalidIdentifier(identifier:infoPlist[infoNameKey]as?String)
111+
}
112+
113+
// Downloaded dylib must match the version of the server
114+
guardlet dylibVersion =infoPlist[infoShortVersionKey]as?String,
115+
expectedVersion == dylibVersion
116+
else{
117+
throw.invalidVersion(version:infoPlist[infoShortVersionKey]as?String)
118+
}
119+
120+
// Downloaded dylib must be at least the minimum Coder server version
121+
guardlet dylibVersion =infoPlist[infoShortVersionKey]as?String,
122+
// x.compare(y) is .orderedDescending if x > y
123+
minimumCoderVersion.compare(dylibVersion, options:.numeric)!=.orderedDescending
124+
else{
125+
throw.belowMinimumCoderVersion
126+
}
127+
}
128+
}

0 commit comments

Comments
(0)