Skip to content

Commit d159df1

Browse files
committed
Implement new signature verification
1 parent 55e41c8 commit d159df1

File tree

8 files changed

+163
-58
lines changed

8 files changed

+163
-58
lines changed

Misc/VPK.bt

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -213,15 +213,28 @@ typedef struct {
213213

214214
// Signature Section
215215
typedef struct {
216-
int PublicKeySize <comment="Size of public key">;
216+
int PublicKeySize <comment="Size of public key or magic number">;
217+
218+
// New format with magic number
219+
if (PublicKeySize == VPK_MAGIC) {
220+
uint SignatureType <comment="Signature format version (0=RSALegacy, 1=RSA4096)">;
221+
int ActualPublicKeySize <comment="Actual size of public key">;
222+
int SignatureSize <comment="Size of signature">;
223+
int Reserved <comment="Reserved field">;
224+
225+
if (ActualPublicKeySize > 0 && SignatureSize > 0) {
226+
ubyte PublicKey[ActualPublicKeySize] <comment="Public key data">;
227+
ubyte Signature[SignatureSize] <comment="Signature data">;
228+
}
229+
return;
230+
}
217231

218-
// CS2 has a special case where SignatureSectionSize is 20 and PublicKeySize equals MAGIC
219-
if (header.SignatureSectionSize == 20 && PublicKeySize == VPK_MAGIC) {
220-
// CS2 special case - just skip the rest
221-
ubyte Padding[16];
232+
// Old format - check for empty signature section
233+
if (header.SignatureSectionSize == 20) {
222234
return;
223235
}
224236

237+
// Old format with data
225238
if (PublicKeySize > 0) {
226239
ubyte PublicKey[PublicKeySize] <comment="Public key data">;
227240
}
Binary file not shown.

ValvePak/ValvePak.Test/PackageTest.cs

Lines changed: 45 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ public void FindEntryDeep()
6767
using var package = new Package();
6868
package.Read(path);
6969

70-
Assert.Multiple(() =>
70+
using (Assert.EnterMultipleScope())
7171
{
7272
Assert.That(package.FindEntry("addons\\chess\\chess.vdf")?.CRC32, Is.EqualTo(0xA4115395));
7373
Assert.That(package.FindEntry("addons/chess\\chess.vdf")?.CRC32, Is.EqualTo(0xA4115395));
@@ -78,7 +78,7 @@ public void FindEntryDeep()
7878
Assert.That(package.FindEntry("\\addons/hello_github_reader/chess.vdf"), Is.Null);
7979
Assert.That(package.FindEntry(string.Empty), Is.Null);
8080
Assert.That(package.FindEntry(" "), Is.Null);
81-
});
81+
}
8282
}
8383

8484
[Test]
@@ -90,7 +90,7 @@ public void TestBinarySearch()
9090
package.OptimizeEntriesForBinarySearch();
9191
package.Read(path);
9292

93-
Assert.Multiple(() =>
93+
using (Assert.EnterMultipleScope())
9494
{
9595
Assert.That(package.FindEntry("addons\\chess\\chess.vdf")?.CRC32, Is.EqualTo(0xA4115395));
9696
Assert.That(package.FindEntry("addons/chess\\chess.vdf")?.CRC32, Is.EqualTo(0xA4115395));
@@ -101,7 +101,7 @@ public void TestBinarySearch()
101101
Assert.That(package.FindEntry("\\addons/hello_github_reader/chess.vdf"), Is.Null);
102102
Assert.That(package.FindEntry(string.Empty), Is.Null);
103103
Assert.That(package.FindEntry(" "), Is.Null);
104-
});
104+
}
105105

106106
foreach (var extension in package.Entries!.Values)
107107
{
@@ -121,7 +121,7 @@ public void TestBinarySearchCaseInsensitive()
121121
package.OptimizeEntriesForBinarySearch(StringComparison.OrdinalIgnoreCase);
122122
package.Read(path);
123123

124-
Assert.Multiple(() =>
124+
using (Assert.EnterMultipleScope())
125125
{
126126
Assert.That(package.FindEntry("ADDONS\\chess\\chess.vdf")?.CRC32, Is.EqualTo(0xA4115395));
127127
Assert.That(package.FindEntry("addons/CHESS\\chess.vdf")?.CRC32, Is.EqualTo(0xA4115395));
@@ -131,7 +131,7 @@ public void TestBinarySearchCaseInsensitive()
131131

132132
Assert.That(package.FindEntry("\\addons/CHESS/hello_github_reader.vdf"), Is.Null);
133133
Assert.That(package.FindEntry("\\addons/hello_github_reader/CHESS.vdf"), Is.Null);
134-
});
134+
}
135135

136136
foreach (var extension in package.Entries!.Values)
137137
{
@@ -150,13 +150,13 @@ public void FindEntryRoot()
150150
using var package = new Package();
151151
package.Read(path);
152152

153-
Assert.Multiple(() =>
153+
using (Assert.EnterMultipleScope())
154154
{
155155
Assert.That(package.FindEntry("kitten.jpg")?.CRC32, Is.EqualTo(0x9C800116));
156156
Assert.That(package.FindEntry("\\kitten.jpg")?.CRC32, Is.EqualTo(0x9C800116));
157157
Assert.That(package.FindEntry("/kitten.jpg")?.CRC32, Is.EqualTo(0x9C800116));
158158
Assert.That(package.FindEntry("\\/kitten.jpg")?.CRC32, Is.EqualTo(0x9C800116));
159-
});
159+
}
160160
}
161161

162162
[Test]
@@ -206,7 +206,7 @@ public void FindEntrySpacesAndExtensionless()
206206
using var package = new Package();
207207
package.Read(path);
208208

209-
Assert.Multiple(() =>
209+
using (Assert.EnterMultipleScope())
210210
{
211211
Assert.That(package.FindEntry("test")?.CRC32, Is.EqualTo(0x0BA144CC));
212212
Assert.That(package.FindEntry("folder with space/test")?.CRC32, Is.EqualTo(0xBF108706));
@@ -218,7 +218,7 @@ public void FindEntrySpacesAndExtensionless()
218218
Assert.That(package.FindEntry("uppercasefolder/UpperCaseFile.txt"), Is.Null);
219219
Assert.That(package.FindEntry("uppercasefolder/bad_file_forfun.TXT"), Is.Null);
220220
Assert.That(package.FindEntry("uppercasefolder/bad_file_forfun.txt2"), Is.Null);
221-
});
221+
}
222222
}
223223

224224
[Test]
@@ -258,7 +258,7 @@ public void TestGetFullPath()
258258
using var package = new Package();
259259
package.Read(path);
260260

261-
Assert.Multiple(() =>
261+
using (Assert.EnterMultipleScope())
262262
{
263263
Assert.That(package.FindEntry("test")?.GetFullPath(), Is.EqualTo("test"));
264264
Assert.That(package.FindEntry("folder with space/test")?.GetFullPath(), Is.EqualTo("folder with space/test"));
@@ -270,7 +270,7 @@ public void TestGetFullPath()
270270
Assert.That(package.FindEntry("folder with space/test")?.GetFileName(), Is.EqualTo("test"));
271271
Assert.That(package.FindEntry("folder with space\\space_extension. txt")?.GetFileName(), Is.EqualTo("space_extension. txt"));
272272
Assert.That(package.FindEntry("uppercasefolder/bad_file_forfun.txt")?.GetFileName(), Is.EqualTo("bad_file_forfun.txt"));
273-
});
273+
}
274274
}
275275

276276
[Test]
@@ -281,14 +281,14 @@ public void TestPackageEntryToString()
281281
using var package = new Package();
282282
package.Read(path);
283283

284-
Assert.Multiple(() =>
284+
using (Assert.EnterMultipleScope())
285285
{
286286
Assert.That(package.FindEntry("test")?.ToString(), Is.EqualTo("test crc=0xba144cc metadatasz=0 fnumber=0 ofs=0x00 sz=39"));
287287
Assert.That(package.FindEntry("folder with space/test")?.ToString(), Is.EqualTo("folder with space/test crc=0xbf108706 metadatasz=0 fnumber=0 ofs=0x52 sz=41"));
288288
Assert.That(package.FindEntry("folder with space\\space_extension. txt")?.ToString(), Is.EqualTo("folder with space/space_extension. txt crc=0x9321fc0 metadatasz=0 fnumber=0 ofs=0x7b sz=30"));
289289
Assert.That(package.FindEntry("uppercasefolder/bad_file_forfun.txt")?.ToString(), Is.EqualTo("uppercasefolder/bad_file_forfun.txt crc=0x15c1490f metadatasz=0 fnumber=0 ofs=0xa2 sz=2"));
290290
Assert.That(package.FindEntry("UpperCaseFolder/UpperCaseFile.txt")?.ToString(), Is.EqualTo("UpperCaseFolder/UpperCaseFile.txt crc=0x32cff012 metadatasz=0 fnumber=0 ofs=0x27 sz=43"));
291-
});
291+
}
292292
}
293293

294294
[Test]
@@ -315,7 +315,7 @@ public void TestFileReadWithPreloadedBytes()
315315

316316
package.ReadEntry(file, out var allBytes);
317317

318-
Assert.Multiple(() =>
318+
using (Assert.EnterMultipleScope())
319319
{
320320
Assert.That(file.ToString(), Is.EqualTo("lorem.txt crc=0xf2cafa54 metadatasz=56 fnumber=32767 ofs=0x00 sz=588"));
321321
Assert.That(file.CRC32, Is.EqualTo(0xF2CAFA54));
@@ -330,7 +330,7 @@ public void TestFileReadWithPreloadedBytes()
330330
"faucibus erat, vel fringilla purus scelerisque tempor. Proin feugiat blandit sapien eget tempus. Praesent gravida in " +
331331
"risus a accumsan. Praesent egestas tincidunt dui nec laoreet. Sed ac lacus non tortor consectetur consectetur a ac " +
332332
"lacus. In rhoncus turpis a nisl volutpat, nec cursus urna tincidunt.\n")));
333-
});
333+
}
334334
}
335335

336336
[Test]
@@ -395,7 +395,7 @@ public void TestFileChecksums()
395395
}
396396

397397
[Test]
398-
public void ParsesCS2VPKWithInvalidSignature()
398+
public void ParsesCS2VPKWithRSA4096Signature()
399399
{
400400
var path = Path.Combine(TestContext.CurrentContext.TestDirectory, "Files", "cs2_new_signature.vpk");
401401

@@ -404,12 +404,34 @@ public void ParsesCS2VPKWithInvalidSignature()
404404

405405
package.VerifyHashes();
406406

407-
Assert.Multiple(() =>
407+
using (Assert.EnterMultipleScope())
408408
{
409409
Assert.That(package.Signature, Is.Null);
410410
Assert.That(package.PublicKey, Is.Null);
411+
Assert.That(package.SignatureType, Is.EqualTo(ESignatureType.OnlyFileChecksum));
412+
Assert.That(package.IsSignatureValid(), Is.True);
413+
}
414+
}
415+
416+
[Test]
417+
public void ReadCSGOPak01WithRSA4096Signature()
418+
{
419+
var path = Path.Combine(TestContext.CurrentContext.TestDirectory, "Files", "cs2_new_signature_actually_signed.vpk");
420+
421+
using var package = new Package();
422+
package.Read(path);
423+
424+
package.VerifyHashes();
425+
426+
using (Assert.EnterMultipleScope())
427+
{
428+
Assert.That(package.SignatureType, Is.EqualTo(ESignatureType.OnlyFileChecksum));
429+
Assert.That(package.PublicKey, Is.Not.Null);
430+
Assert.That(package.Signature, Is.Not.Null);
431+
Assert.That(package.PublicKey!, Has.Length.EqualTo(550));
432+
Assert.That(package.Signature!, Has.Length.EqualTo(512));
411433
Assert.That(package.IsSignatureValid(), Is.True);
412-
});
434+
}
413435
}
414436

415437
[Test]
@@ -538,20 +560,20 @@ private static void TestVPKExtraction(string path)
538560
}
539561
}
540562

541-
Assert.Multiple(() =>
563+
using (Assert.EnterMultipleScope())
542564
{
543565
Assert.That(data, Has.Count.EqualTo(3));
544566
Assert.That(data["kitten.jpg"], Is.EqualTo("1C03B452FEE5274B0BC1FA1A866EE6C8FA0D43AA464C6BCFB3AB531F6E813081"));
545567
Assert.That(data["steammessages_base.proto"], Is.EqualTo("FCC96AE59EE6BB9EEC4E16A50C928EFD3FB16E1CCA49E38BD2FA8391AB7936BE"));
546568
Assert.That(data["steammessages_clientserver.proto"], Is.EqualTo("1F90C38527D0853B4713942668F2DC83F433DBE919C002825A4526138A200428"));
547-
});
569+
}
548570

549-
Assert.Multiple(() =>
571+
using (Assert.EnterMultipleScope())
550572
{
551573
Assert.That(flatEntries["kitten"].TotalLength, Is.EqualTo(16361));
552574
Assert.That(flatEntries["steammessages_base"].TotalLength, Is.EqualTo(2563));
553575
Assert.That(flatEntries["steammessages_clientserver"].TotalLength, Is.EqualTo(39177));
554-
});
576+
}
555577
}
556578
}
557579
}

ValvePak/ValvePak.Test/WriteTest.cs

Lines changed: 32 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -42,21 +42,21 @@ public void CreateNewPackage()
4242
var newEntry = packageWritten.FindEntry(newName);
4343

4444
Assert.That(newEntry, Is.Not.Null);
45-
Assert.Multiple(() =>
45+
using (Assert.EnterMultipleScope())
4646
{
4747
Assert.That(newEntry.CRC32, Is.EqualTo(0x9C800116));
4848
Assert.That(newEntry.ArchiveIndex, Is.EqualTo(0x7FFF));
4949
Assert.That(newEntry.DirectoryName, Is.EqualTo("path/to"));
5050
Assert.That(newEntry.TypeName, Is.EqualTo("jpg"));
5151
Assert.That(newEntry.FileName, Is.EqualTo("cool kitty"));
52-
});
52+
}
5353

5454
packageWritten.ReadEntry(newEntry, out var newFileData);
55-
Assert.Multiple(() =>
55+
using (Assert.EnterMultipleScope())
5656
{
5757
Assert.That(newFileData, Is.EqualTo(fileData));
5858
Assert.That(packageWritten.FindEntry("valvepak")!.CRC32, Is.EqualTo(0xF14F273C));
59-
});
59+
}
6060
}
6161

6262
[Test]
@@ -92,74 +92,77 @@ public void AddAndRemoveFiles()
9292
package.AddFile("test3.txt", []);
9393
package.AddFile("test4.txt", []);
9494

95-
Assert.That(package.Entries!.ContainsKey("txt"), Is.True);
96-
Assert.That(package.Entries["txt"], Has.Count.EqualTo(4));
97-
Assert.That(package.RemoveFile(package.FindEntry("test2.txt")!), Is.True);
98-
Assert.That(package.FindEntry("test2.txt"), Is.Null);
99-
Assert.That(package.FindEntry("test1.txt"), Is.Not.Null);
100-
Assert.That(package.RemoveFile(new PackageEntry
95+
using (Assert.EnterMultipleScope())
10196
{
102-
FileName = "test5",
103-
TypeName = "txt",
104-
DirectoryName = " ",
105-
}), Is.False);
106-
Assert.That(package.Entries["txt"], Has.Count.EqualTo(3));
107-
Assert.That(package.RemoveFile(package.FindEntry("test4.txt")!), Is.True);
108-
Assert.That(package.RemoveFile(package.FindEntry("test3.txt")!), Is.True);
109-
Assert.That(package.RemoveFile(package.FindEntry("test1.txt")!), Is.True);
110-
Assert.That(package.Entries, Is.Empty);
97+
Assert.That(package.Entries!.ContainsKey("txt"), Is.True);
98+
Assert.That(package.Entries["txt"], Has.Count.EqualTo(4));
99+
Assert.That(package.RemoveFile(package.FindEntry("test2.txt")!), Is.True);
100+
Assert.That(package.FindEntry("test2.txt"), Is.Null);
101+
Assert.That(package.FindEntry("test1.txt"), Is.Not.Null);
102+
Assert.That(package.RemoveFile(new PackageEntry
103+
{
104+
FileName = "test5",
105+
TypeName = "txt",
106+
DirectoryName = " ",
107+
}), Is.False);
108+
Assert.That(package.Entries["txt"], Has.Count.EqualTo(3));
109+
Assert.That(package.RemoveFile(package.FindEntry("test4.txt")!), Is.True);
110+
Assert.That(package.RemoveFile(package.FindEntry("test3.txt")!), Is.True);
111+
Assert.That(package.RemoveFile(package.FindEntry("test1.txt")!), Is.True);
112+
Assert.That(package.Entries, Is.Empty);
113+
}
111114
}
112115

113116
[Test]
114117
public void SetsSpaces()
115118
{
116119
using var package = new Package();
117120
var file = package.AddFile("", []);
118-
Assert.Multiple(() =>
121+
using (Assert.EnterMultipleScope())
119122
{
120123
Assert.That(file.TypeName, Is.EqualTo(" "));
121124
Assert.That(file.DirectoryName, Is.EqualTo(" "));
122125
Assert.That(file.FileName, Is.EqualTo(""));
123126
Assert.That(package.Entries!.ContainsKey(" "), Is.True);
124127
Assert.That(package.Entries[" "][0], Is.EqualTo(file));
125-
});
128+
}
126129

127130
var file2 = package.AddFile("hello", []);
128-
Assert.Multiple(() =>
131+
using (Assert.EnterMultipleScope())
129132
{
130133
Assert.That(file2.TypeName, Is.EqualTo(" "));
131134
Assert.That(file2.DirectoryName, Is.EqualTo(" "));
132135
Assert.That(file2.FileName, Is.EqualTo("hello"));
133-
});
136+
}
134137

135138
var file3 = package.AddFile("hello.txt", []);
136-
Assert.Multiple(() =>
139+
using (Assert.EnterMultipleScope())
137140
{
138141
Assert.That(file3.TypeName, Is.EqualTo("txt"));
139142
Assert.That(file3.DirectoryName, Is.EqualTo(" "));
140143
Assert.That(file3.FileName, Is.EqualTo("hello"));
141-
});
144+
}
142145

143146
var file4 = package.AddFile("folder/hello", []);
144-
Assert.Multiple(() =>
147+
using (Assert.EnterMultipleScope())
145148
{
146149
Assert.That(file4.TypeName, Is.EqualTo(" "));
147150
Assert.That(file4.DirectoryName, Is.EqualTo("folder"));
148151
Assert.That(file4.FileName, Is.EqualTo("hello"));
149-
});
152+
}
150153
}
151154

152155
[Test]
153156
public void NormalizesSlashes()
154157
{
155158
using var package = new Package();
156159
var file = package.AddFile("a/b\\c\\d.txt", []);
157-
Assert.Multiple(() =>
160+
using (Assert.EnterMultipleScope())
158161
{
159162
Assert.That(file.TypeName, Is.EqualTo("txt"));
160163
Assert.That(file.DirectoryName, Is.EqualTo("a/b/c"));
161164
Assert.That(file.FileName, Is.EqualTo("d"));
162-
});
165+
}
163166
}
164167
}
165168
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
namespace SteamDatabase.ValvePak
2+
{
3+
/// <summary>
4+
/// Represents the signature type used in VPK archives.
5+
/// </summary>
6+
public enum ESignatureType
7+
{
8+
/// <summary>
9+
/// Type not yet determined.
10+
/// </summary>
11+
Unknown = -1,
12+
13+
/// <summary>
14+
/// RSA legacy signature format.
15+
/// </summary>
16+
FullFile = 0,
17+
18+
/// <summary>
19+
/// RSA-4096 signature format (version 1).
20+
/// </summary>
21+
OnlyFileChecksum = 1,
22+
}
23+
}

0 commit comments

Comments
 (0)