Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit7fc6398

Browse files
authored
chore: enable tunnel binary verification (#36)
- Enables assembly version verification- Enables authenticode verification- Adds local machine registry config options to enable/disable either oftheseCloses#41Closes#45
1 parenta57c8fb commit7fc6398

26 files changed

+629
-58
lines changed

‎App/App.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
</PropertyGroup>
3131

3232
<ItemGroup>
33-
<ContentInclude="coder.ico" />
33+
<ContentInclude="coder.ico" />
3434
</ItemGroup>
3535

3636
<ItemGroup>

‎Installer/Installer.csproj

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
11
<ProjectSdk="Microsoft.NET.Sdk">
22

3-
<PropertyGroup>
4-
<AssemblyName>Coder.Desktop.Installer</AssemblyName>
5-
<RootNamespace>Coder.Desktop.Installer</RootNamespace>
6-
<OutputType>Exe</OutputType>
7-
<TargetFramework>net481</TargetFramework>
8-
<LangVersion>13.0</LangVersion>
9-
</PropertyGroup>
3+
<PropertyGroup>
4+
<AssemblyName>Coder.Desktop.Installer</AssemblyName>
5+
<RootNamespace>Coder.Desktop.Installer</RootNamespace>
6+
<OutputType>Exe</OutputType>
7+
<TargetFramework>net481</TargetFramework>
8+
<LangVersion>13.0</LangVersion>
9+
</PropertyGroup>
1010

11-
<ItemGroup>
12-
<NoneRemove="*.msi" />
13-
<NoneRemove="*.exe" />
14-
<NoneRemove="*.wxs" />
15-
<NoneRemove="*.wixpdb" />
16-
<NoneRemove="*.wixobj" />
17-
</ItemGroup>
11+
<ItemGroup>
12+
<NoneRemove="*.msi" />
13+
<NoneRemove="*.exe" />
14+
<NoneRemove="*.wxs" />
15+
<NoneRemove="*.wixpdb" />
16+
<NoneRemove="*.wixobj" />
17+
</ItemGroup>
1818

19-
<ItemGroup>
20-
<PackageReferenceInclude="WixSharp_wix4"Version="2.6.0" />
21-
<PackageReferenceInclude="WixSharp_wix4.bin"Version="2.6.0" />
22-
<PackageReferenceInclude="CommandLineParser"Version="2.9.1" />
23-
</ItemGroup>
19+
<ItemGroup>
20+
<PackageReferenceInclude="WixSharp_wix4"Version="2.6.0" />
21+
<PackageReferenceInclude="WixSharp_wix4.bin"Version="2.6.0" />
22+
<PackageReferenceInclude="CommandLineParser"Version="2.9.1" />
23+
</ItemGroup>
2424
</Project>

‎Installer/Program.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,13 +250,17 @@ private static int BuildMsiPackage(MsiOptions opts)
250250
programFiles64Folder.AddDir(installDir);
251251
project.AddDir(programFiles64Folder);
252252

253-
// Add registry values that are consumed by the manager.
253+
// Add registry values that are consumed by the manager. Note that these
254+
// should not be changed. See Vpn.Service/Program.cs and
255+
// Vpn.Service/ManagerConfig.cs for more details.
254256
project.AddRegValues(
255257
newRegValue(RegistryHive,RegistryKey,"Manager:ServiceRpcPipeName","Coder.Desktop.Vpn"),
256258
newRegValue(RegistryHive,RegistryKey,"Manager:TunnelBinaryPath",
257259
$"[INSTALLFOLDER]{opts.VpnDir}\\coder-vpn.exe"),
258260
newRegValue(RegistryHive,RegistryKey,"Manager:LogFileLocation",
259-
@"[INSTALLFOLDER]coder-desktop-service.log"));
261+
@"[INSTALLFOLDER]coder-desktop-service.log"),
262+
newRegValue(RegistryHive,RegistryKey,"Manager:TunnelBinarySignatureSigner","Coder Technologies Inc."),
263+
newRegValue(RegistryHive,RegistryKey,"Manager:TunnelBinaryAllowVersionMismatch","false"));
260264

261265
// Note: most of this control panel info will not be visible as this
262266
// package is usually hidden in favor of the bootstrapper showing

‎README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#Coder Desktop for Windows
2+
3+
This repo contains the C# source code for Coder Desktop for Windows. You can
4+
download the latest version from the GitHub releases.
5+
6+
###Contributing
7+
8+
You will need:
9+
10+
- Visual Studio 2022
11+
- .NET desktop development
12+
- WinUI application development
13+
- Windows 10 SDK (10.0.19041.0)
14+
- Wix Toolset 5.0.2 (if building the installer)
15+
16+
It's also recommended to use JetBrains Rider (or VS + ReSharper) for a better
17+
experience.
18+
19+
###License
20+
21+
The Coder Desktop for Windows source is licensed under the GNU Affero General
22+
Public License v3.0 (AGPL-3.0).
23+
24+
Some vendored files in this repo are licensed separately. The license for these
25+
files can be found in the same directory as the files.
26+
27+
The binary distributions of Coder Desktop for Windows have some additional
28+
license disclaimers that can be found in
29+
[scripts/files/License.txt](scripts/files/License.txt) or during installation.

‎Tests.Vpn.Service/DownloaderTest.cs

Lines changed: 118 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
usingSystem.Reflection;
12
usingSystem.Security.Cryptography;
3+
usingSystem.Security.Cryptography.X509Certificates;
24
usingSystem.Text;
35
usingCoder.Desktop.Vpn.Service;
46
usingMicrosoft.Extensions.Logging.Abstractions;
@@ -27,40 +29,102 @@ public class AuthenticodeDownloadValidatorTest
2729
[CancelAfter(30_000)]
2830
publicvoidUnsigned(CancellationTokenct)
2931
{
30-
// TODO: this
32+
vartestBinaryPath=Path.Combine(TestContext.CurrentContext.TestDirectory,"testdata","hello.exe");
33+
varex=Assert.ThrowsAsync<Exception>(()=>
34+
AuthenticodeDownloadValidator.Coder.ValidateAsync(testBinaryPath,ct));
35+
Assert.That(ex.Message,
36+
Does.Contain(
37+
"File is not signed and trusted with an Authenticode signature: State=Unsigned, StateReason=None"));
3138
}
3239

3340
[Test(Description="Test an untrusted binary")]
3441
[CancelAfter(30_000)]
3542
publicvoidUntrusted(CancellationTokenct)
3643
{
37-
// TODO: this
44+
vartestBinaryPath=
45+
Path.Combine(TestContext.CurrentContext.TestDirectory,"testdata","hello-self-signed.exe");
46+
varex=Assert.ThrowsAsync<Exception>(()=>
47+
AuthenticodeDownloadValidator.Coder.ValidateAsync(testBinaryPath,ct));
48+
Assert.That(ex.Message,
49+
Does.Contain(
50+
"File is not signed and trusted with an Authenticode signature: State=Unsigned, StateReason=UntrustedRoot"));
3851
}
3952

4053
[Test(Description="Test an binary with a detached signature (catalog file)")]
4154
[CancelAfter(30_000)]
4255
publicvoidDifferentCertTrusted(CancellationTokenct)
4356
{
44-
//notepad.exe uses a catalog file for its signature.
57+
//rundll32.exe uses a catalog file for its signature.
4558
varex=Assert.ThrowsAsync<Exception>(()=>
46-
AuthenticodeDownloadValidator.Coder.ValidateAsync(@"C:\Windows\System32\notepad.exe",ct));
59+
AuthenticodeDownloadValidator.Coder.ValidateAsync(@"C:\Windows\System32\rundll32.exe",ct));
4760
Assert.That(ex.Message,
4861
Does.Contain("File is not signed with an embedded Authenticode signature: Kind=Catalog"));
4962
}
5063

51-
[Test(Description="Test a binary signed by a different certificate")]
64+
[Test(Description="Test a binary signed by a non-EV certificate")]
65+
[CancelAfter(30_000)]
66+
publicvoidNonEvCert(CancellationTokenct)
67+
{
68+
// dotnet.exe is signed by .NET. During tests we can be pretty sure
69+
// this is installed.
70+
varex=Assert.ThrowsAsync<Exception>(()=>
71+
AuthenticodeDownloadValidator.Coder.ValidateAsync(@"C:\Program Files\dotnet\dotnet.exe",ct));
72+
Assert.That(ex.Message,
73+
Does.Contain(
74+
"File is not signed with an Extended Validation Code Signing certificate"));
75+
}
76+
77+
[Test(Description="Test a binary signed by an EV certificate with a different name")]
5278
[CancelAfter(30_000)]
53-
publicvoidDifferentCertUntrusted(CancellationTokenct)
79+
publicvoidEvDifferentCertName(CancellationTokenct)
5480
{
55-
// TODO: this
81+
vartestBinaryPath=Path.Combine(TestContext.CurrentContext.TestDirectory,"testdata",
82+
"hello-versioned-signed.exe");
83+
varex=Assert.ThrowsAsync<Exception>(()=>
84+
newAuthenticodeDownloadValidator("Acme Corporation").ValidateAsync(testBinaryPath,ct));
85+
Assert.That(ex.Message,
86+
Does.Contain(
87+
"File is signed by an unexpected certificate: ExpectedName='Acme Corporation', ActualName='Coder Technologies Inc.'"));
5688
}
5789

5890
[Test(Description="Test a binary signed by Coder's certificate")]
5991
[CancelAfter(30_000)]
6092
publicasyncTaskCoderSigned(CancellationTokenct)
6193
{
62-
// TODO: this
63-
awaitTask.CompletedTask;
94+
vartestBinaryPath=Path.Combine(TestContext.CurrentContext.TestDirectory,"testdata",
95+
"hello-versioned-signed.exe");
96+
awaitAuthenticodeDownloadValidator.Coder.ValidateAsync(testBinaryPath,ct);
97+
}
98+
99+
[Test(Description="Test if the EV check works")]
100+
publicvoidIsEvCert()
101+
{
102+
// To avoid potential API misuse the function is private.
103+
varmethod=typeof(AuthenticodeDownloadValidator).GetMethod("IsExtendedValidationCertificate",
104+
BindingFlags.NonPublic|BindingFlags.Static);
105+
Assert.That(method,Is.Not.Null,"Could not find IsExtendedValidationCertificate method");
106+
107+
// Call it with various certificates.
108+
varcerts=newList<(string,bool)>
109+
{
110+
// EV:
111+
(Path.Combine(TestContext.CurrentContext.TestDirectory,"testdata","coder-ev.crt"),true),
112+
(Path.Combine(TestContext.CurrentContext.TestDirectory,"testdata","google-llc-ev.crt"),true),
113+
(Path.Combine(TestContext.CurrentContext.TestDirectory,"testdata","self-signed-ev.crt"),true),
114+
// Not EV:
115+
(Path.Combine(TestContext.CurrentContext.TestDirectory,"testdata","mozilla-corporation.crt"),false),
116+
(Path.Combine(TestContext.CurrentContext.TestDirectory,"testdata","self-signed.crt"),false),
117+
};
118+
119+
foreach(var(certPath,isEv)incerts)
120+
{
121+
varx509Cert=newX509Certificate2(certPath);
122+
varresult=(bool?)method!.Invoke(null,[x509Cert]);
123+
Assert.That(result,Is.Not.Null,
124+
$"IsExtendedValidationCertificate returned null for{Path.GetFileName(certPath)}");
125+
Assert.That(result,Is.EqualTo(isEv),
126+
$"IsExtendedValidationCertificate returned wrong result for{Path.GetFileName(certPath)}");
127+
}
64128
}
65129
}
66130

@@ -71,22 +135,60 @@ public class AssemblyVersionDownloadValidatorTest
71135
[CancelAfter(30_000)]
72136
publicvoidNoVersion(CancellationTokenct)
73137
{
74-
// TODO: this
138+
vartestBinaryPath=Path.Combine(TestContext.CurrentContext.TestDirectory,"testdata","hello.exe");
139+
varex=Assert.ThrowsAsync<Exception>(()=>
140+
newAssemblyVersionDownloadValidator(1,2,3,4).ValidateAsync(testBinaryPath,ct));
141+
Assert.That(ex.Message,Does.Contain("File ProductVersion is empty or null"));
142+
}
143+
144+
[Test(Description="Invalid version on binary")]
145+
[CancelAfter(30_000)]
146+
publicvoidInvalidVersion(CancellationTokenct)
147+
{
148+
vartestBinaryPath=
149+
Path.Combine(TestContext.CurrentContext.TestDirectory,"testdata","hello-invalid-version.exe");
150+
varex=Assert.ThrowsAsync<Exception>(()=>
151+
newAssemblyVersionDownloadValidator(1,2,3,4).ValidateAsync(testBinaryPath,ct));
152+
Assert.That(ex.Message,Does.Contain("File ProductVersion '1-2-3-4' is not a valid version string"));
75153
}
76154

77-
[Test(Description="Version mismatch")]
155+
[Test(Description="Version mismatch with full version check")]
78156
[CancelAfter(30_000)]
79-
publicvoidVersionMismatch(CancellationTokenct)
157+
publicvoidVersionMismatchFull(CancellationTokenct)
80158
{
81-
// TODO: this
159+
vartestBinaryPath=Path.Combine(TestContext.CurrentContext.TestDirectory,"testdata",
160+
"hello-versioned-signed.exe");
161+
162+
// Try changing each version component one at a time
163+
varexpectedVersions=new[]{1,2,3,4};
164+
for(vari=0;i<4;i++)
165+
{
166+
vartestVersions=(int[])expectedVersions.Clone();
167+
testVersions[i]++;// Increment this component to make it wrong
168+
169+
varex=Assert.ThrowsAsync<Exception>(()=>
170+
newAssemblyVersionDownloadValidator(
171+
testVersions[0],testVersions[1],testVersions[2],testVersions[3]
172+
).ValidateAsync(testBinaryPath,ct));
173+
174+
Assert.That(ex.Message,Does.Contain(
175+
$"File ProductVersion does not match expected version: Actual='1.2.3.4', Expected='{string.Join(".",testVersions)}'"));
176+
}
82177
}
83178

84-
[Test(Description="Version match")]
179+
[Test(Description="Version match with and without partial version check")]
85180
[CancelAfter(30_000)]
86181
publicasyncTaskVersionMatch(CancellationTokenct)
87182
{
88-
// TODO: this
89-
awaitTask.CompletedTask;
183+
vartestBinaryPath=Path.Combine(TestContext.CurrentContext.TestDirectory,"testdata",
184+
"hello-versioned-signed.exe");
185+
186+
// Test with just major.minor
187+
awaitnewAssemblyVersionDownloadValidator(1,2).ValidateAsync(testBinaryPath,ct);
188+
// Test with major.minor.patch
189+
awaitnewAssemblyVersionDownloadValidator(1,2,3).ValidateAsync(testBinaryPath,ct);
190+
// Test with major.minor.patch.build
191+
awaitnewAssemblyVersionDownloadValidator(1,2,3,4).ValidateAsync(testBinaryPath,ct);
90192
}
91193
}
92194

‎Tests.Vpn.Service/Tests.Vpn.Service.csproj

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,36 @@
1212
<IsTestProject>true</IsTestProject>
1313
</PropertyGroup>
1414

15+
<ItemGroup>
16+
<NoneUpdate="testdata\hello.exe">
17+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
18+
</None>
19+
<NoneUpdate="testdata\hello-invalid-version.exe">
20+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
21+
</None>
22+
<NoneUpdate="testdata\hello-self-signed.exe">
23+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
24+
</None>
25+
<NoneUpdate="testdata\hello-versioned-signed.exe">
26+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
27+
</None>
28+
<NoneUpdate="testdata\coder-ev.crt">
29+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
30+
</None>
31+
<NoneUpdate="testdata\google-llc-ev.crt">
32+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
33+
</None>
34+
<NoneUpdate="testdata\mozilla-corporation.crt">
35+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
36+
</None>
37+
<NoneUpdate="testdata\self-signed.crt">
38+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
39+
</None>
40+
<NoneUpdate="testdata\self-signed-ev.crt">
41+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
42+
</None>
43+
</ItemGroup>
44+
1545
<ItemGroup>
1646
<PackageReferenceInclude="coverlet.collector"Version="6.0.4">
1747
<PrivateAssets>all</PrivateAssets>

‎Tests.Vpn.Service/testdata/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
*.go
2+
*.pfx
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
$errorActionPreference="Stop"
2+
3+
Set-Location$PSScriptRoot
4+
5+
# If hello.go does not exist, write it. We don't check it into the repo to avoid
6+
# GitHub showing that the repo contains Go code.
7+
if (-not (Test-Path"hello.go")) {
8+
$helloGo=@"
9+
package main
10+
11+
func main() {
12+
println("Hello, World!")
13+
}
14+
"@
15+
Set-Content-Path"hello.go"-Value$helloGo
16+
}
17+
18+
&go.exe build-ldflags'-w -s'-ohello.exe hello.go
19+
if ($LASTEXITCODE-ne0) {throw"Failed to build hello.exe" }
20+
21+
# hello-invalid-version.exe is used for testing versioned binaries with an
22+
# invalid version.
23+
Copy-Itemhello.exehello-invalid-version.exe
24+
&go-winres.exe patch--in winres.json--delete--no-backup--product-version1-2-3-4--file-version1-2-3-4hello-invalid-version.exe
25+
if ($LASTEXITCODE-ne0) {throw"Failed to patch hello-invalid-version.exe with go-winres" }
26+
27+
# hello-self-signed.exe is used for testing untrusted binaries.
28+
Copy-Itemhello.exehello-self-signed.exe
29+
$helloSelfSignedPath= (Get-Itemhello-self-signed.exe).FullName
30+
31+
# Create a self signed certificate for signing and then delete it.
32+
$certStoreLocation="Cert:\CurrentUser\My"
33+
$password="password"
34+
$cert=New-SelfSignedCertificate`
35+
-CertStoreLocation$certStoreLocation`
36+
-DnsNamecoder.com`
37+
-Subject"CN=coder-desktop-windows-self-signed-cert"`
38+
-Type CodeSigningCert`
39+
-KeyUsage DigitalSignature`
40+
-NotAfter (Get-Date).AddDays(3650)
41+
$pfxPath=Join-Path$PSScriptRoot"cert.pfx"
42+
try {
43+
$securePassword=ConvertTo-SecureString-String$password-Force-AsPlainText
44+
Export-PfxCertificate-Cert$cert-FilePath$pfxPath-Password$securePassword
45+
46+
# Sign hello-self-signed.exe with the self signed certificate
47+
&"${env:ProgramFiles(x86)}\Windows Kits\10\bin\10.0.19041.0\x64\signtool.exe" sign/debug/f$pfxPath/p$password/tr"http://timestamp.digicert.com"/td sha256/fd sha256$helloSelfSignedPath
48+
if ($LASTEXITCODE-ne0) {throw"Failed to sign hello-self-signed.exe with signtool" }
49+
}finally {
50+
if ($cert.Thumbprint) {
51+
Remove-Item-Path (Join-Path$certStoreLocation$cert.Thumbprint)-Force
52+
}
53+
if (Test-Path$pfxPath) {
54+
Remove-Item-Path$pfxPath-Force
55+
}
56+
}
57+
58+
# hello-versioned-signed.exe is used for testing versioned binaries and
59+
# binaries signed by a real EV certificate.
60+
Copy-Itemhello.exehello-versioned-signed.exe
61+
62+
&go-winres.exe patch--in winres.json--delete--no-backup--product-version1.2.3.4--file-version1.2.3.4hello-versioned-signed.exe
63+
if ($LASTEXITCODE-ne0) {throw"Failed to patch hello-versioned-signed.exe with go-winres" }
64+
65+
# Then sign hello-versioned-signed.exe with the same EV cert as our real
66+
# binaries. Since this is a bit more complicated and requires some extra
67+
# permissions, we don't do this in the build script.
68+
Write-Host"Don't forget to sign hello-versioned-signed.exe with the EV cert!"

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp