Skip to content

Commit c039546

Browse files
Merge pull request #68 from EvotecIT/codex/fileinspectorx-pkcs7-hardening
[codex] Harden PKCS#7 and certificate detection
2 parents 823d7ca + 2dd6168 commit c039546

16 files changed

+1107
-285
lines changed

FileInspectorX.Tests/CryptoDetectionsTests.cs

Lines changed: 386 additions & 67 deletions
Large diffs are not rendered by default.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using Xunit;
2+
3+
namespace FileInspectorX.Tests;
4+
5+
// This collection serializes tests that temporarily lower Settings.DetectionReadBudgetBytes.
6+
[CollectionDefinition(nameof(DetectionSettingsCollection), DisableParallelization = true)]
7+
public sealed class DetectionSettingsCollection : ICollectionFixture<DetectionSettingsFixture>
8+
{
9+
}
10+
11+
public sealed class DetectionSettingsFixture
12+
{
13+
}

FileInspectorX.Tests/DetectorTests.cs

Lines changed: 76 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -556,20 +556,30 @@ public void Detect_MachO_64LE() {
556556
} finally { if (File.Exists(p)) File.Delete(p); }
557557
}
558558

559-
[Fact]
560-
public void Detect_Msg_Basic() {
561-
var p = Path.GetTempFileName();
562-
try {
563-
var list = new List<byte>();
564-
list.AddRange(new byte[] { 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 });
565-
list.AddRange(new byte[128]);
566-
list.AddRange(System.Text.Encoding.ASCII.GetBytes("__substg1.0_007D"));
567-
File.WriteAllBytes(p, list.ToArray());
568-
var res = FI.Detect(p);
569-
Assert.NotNull(res);
570-
Assert.Equal("msg", res!.Extension);
571-
} finally { if (File.Exists(p)) File.Delete(p); }
572-
}
559+
[Fact]
560+
public void Detect_Msg_Basic() {
561+
var p = Path.GetTempFileName();
562+
try {
563+
WriteSyntheticOleDirectoryFile(p, "__substg1.0_007D", "__properties_version1.0");
564+
var res = FI.Detect(p);
565+
Assert.NotNull(res);
566+
Assert.Equal("msg", res!.Extension);
567+
} finally { if (File.Exists(p)) File.Delete(p); }
568+
}
569+
570+
[Fact]
571+
public void Detect_Msg_DoesNotMatch_OleFile_With_Marker_Bytes_Outside_DirectoryEntries() {
572+
var p = Path.GetTempFileName();
573+
try {
574+
var list = new List<byte>();
575+
list.AddRange(new byte[] { 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 });
576+
list.AddRange(new byte[128]);
577+
list.AddRange(System.Text.Encoding.ASCII.GetBytes("__substg1.0_007D"));
578+
File.WriteAllBytes(p, list.ToArray());
579+
var res = FI.Detect(p);
580+
Assert.True(res == null || !string.Equals(res.Extension, "msg", StringComparison.OrdinalIgnoreCase));
581+
} finally { if (File.Exists(p)) File.Delete(p); }
582+
}
573583

574584
[Fact]
575585
public void Classify_ContentKind_Works() {
@@ -599,7 +609,7 @@ public void Guess_Zip_Subtypes_Jar() {
599609
}
600610

601611
[Fact]
602-
public void Guess_Zip_Subtypes_Epub() {
612+
public void Guess_Zip_Subtypes_Epub() {
603613
var p = Path.GetTempFileName();
604614
var zip = p + ".zip";
605615
try {
@@ -633,6 +643,57 @@ public void Guess_Zip_Subtypes_Vsix() {
633643
} finally { if (File.Exists(p)) File.Delete(p); if (File.Exists(zip)) File.Delete(zip); }
634644
}
635645

646+
private static void WriteSyntheticOleDirectoryFile(string path, params string[] directoryNames)
647+
{
648+
var header = new byte[512];
649+
var sig = new byte[]{0xD0,0xCF,0x11,0xE0,0xA1,0xB1,0x1A,0xE1};
650+
Array.Copy(sig, 0, header, 0, sig.Length);
651+
header[0x1E] = 0x09;
652+
header[0x1F] = 0x00;
653+
// Our mini CFBF reader maps sector N to offset 512 + ((N + 1) * 512),
654+
// so FAT sector SID 0 lives at 1024 and directory sector SID 2 lives at 2048.
655+
WriteLe32(header, 0x2C, 1);
656+
WriteLe32(header, 0x30, 2);
657+
WriteLe32(header, 0x4C, 0);
658+
659+
var fat = new byte[512];
660+
const int ENDOFCHAIN = unchecked((int)0xFFFFFFFE);
661+
WriteLe32(fat, 2 * 4, ENDOFCHAIN);
662+
663+
var dir = new byte[512];
664+
for (int i = 0; i < directoryNames.Length && i < 4; i++)
665+
{
666+
WriteDirName(dir, i * 128, directoryNames[i]);
667+
}
668+
669+
using var fs = File.Create(path);
670+
fs.Write(header, 0, header.Length);
671+
fs.Position = 1024;
672+
fs.Write(fat, 0, fat.Length);
673+
fs.Position = 2048;
674+
fs.Write(dir, 0, dir.Length);
675+
676+
static void WriteDirName(byte[] buf, int offset, string name)
677+
{
678+
var bytes = System.Text.Encoding.Unicode.GetBytes(name + "\0");
679+
Array.Copy(bytes, 0, buf, offset, Math.Min(bytes.Length, 64));
680+
ushort len = (ushort)Math.Min(bytes.Length, 128);
681+
buf[offset + 0x40] = (byte)(len & 0xFF);
682+
buf[offset + 0x41] = (byte)((len >> 8) & 0xFF);
683+
}
684+
685+
static void WriteLe32(byte[] buffer, int offset, int value)
686+
{
687+
unchecked
688+
{
689+
buffer[offset] = (byte)(value & 0xFF);
690+
buffer[offset + 1] = (byte)((value >> 8) & 0xFF);
691+
buffer[offset + 2] = (byte)((value >> 16) & 0xFF);
692+
buffer[offset + 3] = (byte)((value >> 24) & 0xFF);
693+
}
694+
}
695+
}
696+
636697
[Fact]
637698
public void Analyze_Zip_With_Inner_Zip_Flags_Nested_Archive_Subtype() {
638699
var p = Path.GetTempFileName();
Binary file not shown.
Binary file not shown.
Binary file not shown.

FileInspectorX.Tests/Fixtures/crypto/pkcs12-missing-authsafe-wrapper.p12

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
00  *�H��
Binary file not shown.
Binary file not shown.

FileInspectorX/Analysis/FileAnalysis.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -175,13 +175,13 @@ public class FileAnalysis {
175175
public CertificateInfo? Certificate { get; set; }
176176

177177
/// <summary>
178-
/// For PKCS#7 certificate bundles (.p7b/.spc): number of certificates and their subjects (best-effort).
179-
/// </summary>
180-
public int? CertificateBundleCount { get; set; }
181-
/// <summary>
182-
/// Subjects present in a PKCS#7 certificate bundle, when parsed.
183-
/// </summary>
184-
public IReadOnlyList<string>? CertificateBundleSubjects { get; set; }
178+
/// For PKCS#7 certificate/signature payloads (.p7b/.spc/.p7s): number of certificates and their subjects (best-effort).
179+
/// </summary>
180+
public int? CertificateBundleCount { get; set; }
181+
/// <summary>
182+
/// Subjects present in a PKCS#7 certificate/signature payload, when parsed.
183+
/// </summary>
184+
public IReadOnlyList<string>? CertificateBundleSubjects { get; set; }
185185

186186
/// <summary>
187187
/// When content appears encoded (e.g., base64, hex), indicates the encoding kind.

0 commit comments

Comments
 (0)