44
55#include " wireguardutilsmacos.h"
66
7+ #include < Security/SecCertificate.h>
8+ #include < Security/SecRequirement.h>
9+ #include < Security/SecStaticCode.h>
10+ #include < Security/SecTask.h>
711#include < errno.h>
812#include < net/route.h>
913
1014#include < QByteArray>
1115#include < QDir>
1216#include < QFile>
1317#include < QLocalSocket>
18+ #include < QSysInfo>
1419#include < QTimer>
20+ #include < QVersionNumber>
1521
1622#include " leakdetector.h"
1723#include " logger.h"
@@ -65,6 +71,144 @@ void WireguardUtilsMacos::tunnelFinished(int exitCode,
6571 }
6672}
6773
74+ // static
75+ QString WireguardUtilsMacos::wireguardGoPath () {
76+ QString osVersion = QSysInfo::productVersion ();
77+ if (QVersionNumber::fromString (osVersion) >= QVersionNumber (13 , 0 )) {
78+ // For macOS 13 and later this can be a relative path to the daemon.
79+ QDir appPath (QCoreApplication::applicationDirPath ());
80+ appPath.cdUp ();
81+ appPath.cdUp ();
82+ appPath.cd (" Resources" );
83+ appPath.cd (" utils" );
84+ return appPath.filePath (" wireguard-go" );
85+ }
86+
87+ // For earlier versions of macOS - this must be a fixed path
88+ return " /Applications/Mozilla VPN.app/Contents/Resources/utils/wireguard-go" ;
89+ }
90+
91+ // static
92+ QString WireguardUtilsMacos::wireguardGoRequirements () {
93+ static QString requirements;
94+ if (!requirements.isEmpty ()) {
95+ return requirements;
96+ }
97+
98+ OSStatus status = errSecSuccess;
99+ SecCodeRef code = nullptr ;
100+ CFDictionaryRef dict = nullptr ;
101+ auto guard = qScopeGuard ([&]() {
102+ if (status != errSecSuccess) {
103+ CFStringRef msg = SecCopyErrorMessageString (status, nullptr );
104+ logger.warning () << " Requirements failed:" << msg;
105+ CFRelease (msg);
106+ }
107+ CFRelease (code);
108+ CFRelease (dict);
109+ });
110+
111+ status = SecCodeCopySelf (kSecCSDefaultFlags , &code);
112+ if (status != errSecSuccess) {
113+ return QString ();
114+ }
115+ status = SecCodeCopySigningInformation (code, kSecCSSigningInformation , &dict);
116+ if (status != errSecSuccess) {
117+ return QString ();
118+ }
119+
120+ // Build the signing requirements.
121+ QStringList reqList (" anchor apple generic" );
122+ CFTypeRef value;
123+ value = CFDictionaryGetValue (dict, kSecCodeInfoTeamIdentifier );
124+ if ((value != nullptr ) && (CFGetTypeID (value) == CFStringGetTypeID ())) {
125+ QString team = QString::fromCFString (static_cast <CFStringRef>(value));
126+ reqList << QString (" certificate leaf[subject.OU] = \" %1\" " ).arg (team);
127+ }
128+
129+ value = CFDictionaryGetValue (dict, kSecCodeInfoCertificates );
130+ if ((value != nullptr ) && (CFGetTypeID (value) == CFArrayGetTypeID ()) &&
131+ (CFArrayGetCount (static_cast <CFArrayRef>(value)) != 0 )) {
132+ CFTypeRef leaf = CFArrayGetValueAtIndex (static_cast <CFArrayRef>(value), 0 );
133+ if ((leaf != nullptr ) && (CFGetTypeID (leaf) == SecCertificateGetTypeID ())) {
134+ CFStringRef name;
135+ QString nameReqTemplate = " certificate leaf[subject.CN] = \" %1\" " ;
136+ SecCertificateCopyCommonName ((SecCertificateRef)leaf, &name);
137+ reqList << nameReqTemplate.arg (QString::fromCFString (name));
138+ CFRelease (name);
139+ }
140+ }
141+
142+ requirements = reqList.join (" and " );
143+ return requirements;
144+ }
145+
146+ // static
147+ bool WireguardUtilsMacos::wireguardGoCodesign (const QProcess& process) {
148+ // No need to verify the codesign on macOS >= 13.0 as the daemon is running
149+ // as a part of the bundle, so we ought to get codesign verification for free
150+ QString osVersion = QSysInfo::productVersion ();
151+ if (QVersionNumber::fromString (osVersion) >= QVersionNumber (13 , 0 )) {
152+ return true ;
153+ }
154+
155+ QString requirements = wireguardGoRequirements ();
156+ if (requirements.isEmpty ()) {
157+ // If the daemon is not signed, then we shouldn't expect wireguard-go to be.
158+ return true ;
159+ }
160+
161+ OSStatus status = errSecSuccess;
162+ CFErrorRef err = nullptr ;
163+ CFURLRef url = nullptr ;
164+ SecRequirementRef req = nullptr ;
165+ SecStaticCodeRef code = nullptr ;
166+ auto guard = qScopeGuard ([&]() {
167+ if (err != nullptr ) {
168+ logger.warning () << " Codesign failed:" << err;
169+ CFRelease (err);
170+ } else if (status != errSecSuccess) {
171+ CFStringRef msg = SecCopyErrorMessageString (status, nullptr );
172+ logger.warning () << " Codesign failed:" << msg;
173+ CFRelease (msg);
174+ }
175+ CFRelease (url);
176+ CFRelease (req);
177+ CFRelease (code);
178+ });
179+
180+ // Get the URL to the QProcess's program
181+ CFStringRef urlString = process.program ().toCFString ();
182+ url = CFURLCreateWithFileSystemPath (kCFAllocatorDefault , urlString,
183+ kCFURLPOSIXPathStyle , false );
184+ CFRelease (urlString);
185+ if (!url) {
186+ logger.warning () << " Unable to generate URL for" << process.program ();
187+ return false ;
188+ }
189+ logger.debug () << " Codesign verifying:" << CFURLGetString (url);
190+ logger.debug () << " Codesign requirements:" << requirements;
191+
192+ // Prepare the codesign requirements.
193+ CFStringRef reqString = requirements.toCFString ();
194+ status = SecRequirementCreateWithString (reqString, kSecCSDefaultFlags , &req);
195+ CFRelease (reqString);
196+ if (status != errSecSuccess) {
197+ return false ;
198+ }
199+
200+ // Validate the codesign.
201+ logger.debug () << " Codesign get code object" ;
202+ status = SecStaticCodeCreateWithPath (url, kSecCSDefaultFlags , &code);
203+ if (status != errSecSuccess) {
204+ return false ;
205+ }
206+ logger.debug () << " Codesign verify code object" ;
207+ status =
208+ SecStaticCodeCheckValidityWithErrors (code, kSecCSDefaultFlags , req, &err);
209+ return (status == errSecSuccess);
210+ }
211+
68212bool WireguardUtilsMacos::addInterface (const InterfaceConfig& config) {
69213 Q_UNUSED (config);
70214 if (m_tunnel.state () != QProcess::NotRunning) {
@@ -84,14 +228,14 @@ bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) {
84228 pe.insert (" LOG_LEVEL" , " debug" );
85229#endif
86230 m_tunnel.setProcessEnvironment (pe);
231+ m_tunnel.setProgram (wireguardGoPath ());
232+ m_tunnel.setArguments (QStringList ({" -f" , " utun" }));
233+ if (!wireguardGoCodesign (m_tunnel)) {
234+ logger.error () << " Unable to validate tunnel process code signature" ;
235+ return false ;
236+ }
87237
88- QDir appPath (QCoreApplication::applicationDirPath ());
89- appPath.cdUp ();
90- appPath.cdUp ();
91- appPath.cd (" Resources" );
92- appPath.cd (" utils" );
93- QStringList wgArgs = {" -f" , " utun" };
94- m_tunnel.start (appPath.filePath (" wireguard-go" ), wgArgs);
238+ m_tunnel.start ();
95239 if (!m_tunnel.waitForStarted (WG_TUN_PROC_TIMEOUT)) {
96240 logger.error () << " Unable to start tunnel process due to timeout" ;
97241 m_tunnel.kill ();
0 commit comments