diff --git a/src/ByteGuard.FileValidator/Configuration/ConfigurationValidator.cs b/src/ByteGuard.FileValidator/Configuration/ConfigurationValidator.cs index 88cfcba..bfca612 100644 --- a/src/ByteGuard.FileValidator/Configuration/ConfigurationValidator.cs +++ b/src/ByteGuard.FileValidator/Configuration/ConfigurationValidator.cs @@ -36,7 +36,7 @@ public static void ThrowIfInvalid(FileValidatorConfiguration configuration) } // Validate file type is supported by the current version of FileValidator. - if (!FileValidator.SupportedFileDefinitions.Any(f => f.FileType.Equals(fileType))) + if (!FileDefinitions.SupportedFileDefinitions.Any(f => f.FileType.Equals(fileType))) { throw new UnsupportedFileException($"File type '{fileType}' is not supported in the current version of FileValidator."); } diff --git a/src/ByteGuard.FileValidator/FileDefinitions.cs b/src/ByteGuard.FileValidator/FileDefinitions.cs new file mode 100644 index 0000000..34c6f16 --- /dev/null +++ b/src/ByteGuard.FileValidator/FileDefinitions.cs @@ -0,0 +1,316 @@ +using ByteGuard.FileValidator.Models; + +namespace ByteGuard.FileValidator; + +internal static class FileDefinitions +{ + /// + /// List of all supported valid file definitions, incl. their header signatures (magic numbers) + /// and potentially their corresponding valid subtype signatures. + /// + internal static readonly List SupportedFileDefinitions = new List + { + new FileDefinition + { + FileType = FileExtensions.Jpeg, + ValidSignatures = new List + { + new byte[] { 0xFF, 0xD8, 0xFF } // ÿØÿ + } + }, + new FileDefinition + { + FileType = FileExtensions.Jpg, + ValidSignatures = new List + { + new byte[] { 0xFF, 0xD8, 0xFF } // ÿØÿ + } + }, + new FileDefinition + { + FileType = FileExtensions.Jpe, + ValidSignatures = new List + { + new byte[] { 0xFF, 0xD8, 0xFF } // ÿØÿ + } + }, + new FileDefinition + { + FileType = FileExtensions.Pdf, + ValidSignatures = new List + { + new byte[] { 0x25, 0x50, 0x44, 0x46, 0x2D } // %PDF- + } + }, + new FileDefinition + { + FileType = FileExtensions.Png, + ValidSignatures = new List + { + new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A } // ‰PNG␍␊␚␊ + } + }, + new FileDefinition + { + FileType = FileExtensions.Bmp, + ValidSignatures = new List + { + new byte[] { 0x42, 0x4D } // BM + } + }, + new FileDefinition + { + FileType = FileExtensions.Doc, + ValidSignatures = new List + { + new byte[] { 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 } // ÐÏ␑ࡱ␚á + } + }, + new FileDefinition + { + FileType = FileExtensions.Docx, + // WARNING: This shares the same signature as .zip and could potentially allow for .zip disguised as .docx. + ValidSignatures = new List + { + new byte[] { 0x50, 0x4B, 0x03, 0x04 } // PK␃␄ + } + }, + new FileDefinition + { + FileType = FileExtensions.Odp, + // WARNING: This shares the same signature as .zip and could potentially allow for .zip disguised as .odp. + ValidSignatures = new List + { + new byte[] { 0x50, 0x4B, 0x03, 0x04 } // PK␃␄ + } + }, + new FileDefinition + { + FileType = FileExtensions.Ods, + // WARNING: This shares the same signature as .zip and could potentially allow for .zip disguised as .ods. + ValidSignatures = new List + { + new byte[] { 0x50, 0x4B, 0x03, 0x04 } // PK␃␄ + } + }, + new FileDefinition + { + FileType = FileExtensions.Odt, + // WARNING: This shares the same signature as .zip and could potentially allow for .zip disguised as .odt. + ValidSignatures = new List + { + new byte[] { 0x50, 0x4B, 0x03, 0x04 } // PK␃␄ + } + }, + new FileDefinition + { + FileType = FileExtensions.Rtf, + ValidSignatures = new List + { + new byte[] { 0x7B, 0x5C, 0x72, 0x74, 0x66, 0x31 } // {\rtf1 + } + }, + new FileDefinition + { + FileType = FileExtensions.Xlsx, + // WARNING: This shares the same signature as .zip and could potentially allow for .zip disguised as .xlsx. + ValidSignatures = new List + { + new byte[] { 0x50, 0x4B, 0x03, 0x04 } // PK␃␄ + } + }, + new FileDefinition + { + FileType = FileExtensions.Xls, + ValidSignatures = new List + { + new byte[] { 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 } // ÐÏ␑ࡱ␚á + } + }, + new FileDefinition + { + FileType = FileExtensions.Pptx, + // WARNING: This shares the same signature as .zip and could potentially allow for .zip disguised as .pptx. + ValidSignatures = new List + { + new byte[] { 0x50, 0x4B, 0x03, 0x04 } // PK␃␄ + } + }, + new FileDefinition + { + FileType = FileExtensions.M4a, + ValidSignatures = new List + { + new byte[] { 0x66, 0x74, 0x79, 0x70 } // ftyp + }, + SignatureOffset = 4, + HasSubtype = true, + SubtypeOffset = 8, + ValidSubtypeSignatures = new List + { + new byte[] { 0x4D, 0x34, 0x41, 0x20 } // M4A_ + } + }, + new FileDefinition + { + FileType = FileExtensions.Mov, + ValidSignatures = new List + { + new byte[] { 0x66, 0x74, 0x79, 0x70 } // ftyp + }, + SignatureOffset = 4, + HasSubtype = true, + SubtypeOffset = 8, + ValidSubtypeSignatures = new List + { + new byte[] { 0x71, 0x74, 0x20, 0x20 } // qt__ (Quicktime) + } + }, + new FileDefinition + { + FileType = FileExtensions.Avi, + ValidSignatures = new List + { + new byte[] { 0x52, 0x49, 0x46, 0x46 } // RIFF + }, + HasSubtype = true, + SubtypeOffset = 8, + ValidSubtypeSignatures = new List + { + new byte[] { 0x41, 0x56, 0x49, 0x20 } // AVI_ + } + }, + new FileDefinition + { + FileType = FileExtensions.Mp3, + ValidSignatures = new List + { + new byte[] { 0xFF, 0xFB }, // Without ID3 tag + new byte[] { 0xFF, 0xF2 }, // Without ID3 tag + new byte[] { 0xFF, 0xF3 }, // Without ID3 tag + new byte[] { 0x49, 0x44, 0x33 } // ID3 + } + }, + new FileDefinition + { + FileType = FileExtensions.Mp4, + ValidSignatures = new List + { + new byte[] { 0x66, 0x74, 0x79, 0x70 } // ftyp + }, + SignatureOffset = 4, + HasSubtype = true, + SubtypeOffset = 8, + ValidSubtypeSignatures = new List + { + new byte[] { 0x6D, 0x6D, 0x70, 0x34 }, // mmp4 (MP4) + new byte[] { 0x6D, 0x70, 0x34, 0x32 }, // mp42 (MP4 v2) + new byte[] { 0x69, 0x73, 0x6F, 0x6D }, // isom (ISO Base Media File) + new byte[] { 0x4D, 0x53, 0x4E, 0x56 } // MSNV (MPEG-4) + } + }, + new FileDefinition + { + FileType = FileExtensions.Wav, + ValidSignatures = new List + { + new byte[] { 0x52, 0x49, 0x46, 0x46 } // RIFF + }, + HasSubtype = true, + SubtypeOffset = 8, + ValidSubtypeSignatures = new List + { + new byte[] { 0x57, 0x41, 0x56, 0x45 } // WAVE + } + }, + new FileDefinition + { + FileType = FileExtensions.Txt, + AllowMissingSignature = true + }, + new FileDefinition + { + FileType = FileExtensions.Csv, + AllowMissingSignature = true + }, + new FileDefinition + { + FileType = FileExtensions.Ico, + ValidSignatures = new List + { + new byte[] { 0x00, 0x00, 0x01, 0x00 }, // ␀␀␁␀ + } + }, + new FileDefinition + { + FileType = FileExtensions.Heic, + ValidSignatures = new List + { + new byte[] { 0x66, 0x74, 0x79, 0x70, 0x68, 0x65, 0x69, 0x63 } // ftypheic + }, + SignatureOffset = 4 + }, + new FileDefinition + { + FileType = FileExtensions.Gif, + ValidSignatures = new List + { + new byte[] { 0x47, 0x49, 0x46, 0x38 }, // GIF8 + } + }, + new FileDefinition + { + FileType = FileExtensions.Tif, + ValidSignatures = new List + { + new byte[] { 0x49, 0x49, 0x2A, 0x00 }, // II*␀ (Intel byte order) + new byte[] { 0x4D, 0x4D, 0x00, 0x2A }, // MM␀* (Motorola byte order) + new byte[] { 0x49, 0x49, 0x2B, 0x00 }, // II+␀ (BigTIFF, Intel byte order) + new byte[] { 0x4D, 0x4D, 0x00, 0x2B } // MM␀+ (BigTIFF, Motorola byte order) + } + }, + new FileDefinition + { + FileType = FileExtensions.Tiff, + ValidSignatures = new List + { + new byte[] { 0x49, 0x49, 0x2A, 0x00 }, // II*␀ (Intel byte order) + new byte[] { 0x4D, 0x4D, 0x00, 0x2A }, // MM␀* (Motorola byte order) + new byte[] { 0x49, 0x49, 0x2B, 0x00 }, // II+␀ (BigTIFF, Intel byte order) + new byte[] { 0x4D, 0x4D, 0x00, 0x2B } // MM␀+ (BigTIFF, Motorola byte order) + } + }, + new FileDefinition + { + FileType = FileExtensions.Ogg, + ValidSignatures = new List + { + new byte[] { 0x4F, 0x67, 0x67, 0x53 } // OggS + } + }, + new FileDefinition + { + FileType = FileExtensions.Oga, + ValidSignatures = new List + { + new byte[] { 0x4F, 0x67, 0x67, 0x53 } // OggS + } + }, + new FileDefinition + { + FileType = FileExtensions.Ogv, + ValidSignatures = new List + { + new byte[] { 0x4F, 0x67, 0x67, 0x53 } // OggS + } + }, + new FileDefinition + { + FileType = FileExtensions.Ogx, + ValidSignatures = new List + { + new byte[] { 0x4F, 0x67, 0x67, 0x53 } // OggS + } + } + }; +} diff --git a/src/ByteGuard.FileValidator/FileValidator.cs b/src/ByteGuard.FileValidator/FileValidator.cs index 11040ab..77333c0 100644 --- a/src/ByteGuard.FileValidator/FileValidator.cs +++ b/src/ByteGuard.FileValidator/FileValidator.cs @@ -1,7 +1,6 @@ using DocumentFormat.OpenXml.Packaging; using ByteGuard.FileValidator.Configuration; using ByteGuard.FileValidator.Exceptions; -using ByteGuard.FileValidator.Models; using ByteGuard.FileValidator.Validators; using ByteGuard.FileValidator.Scanners; @@ -12,316 +11,6 @@ namespace ByteGuard.FileValidator /// public class FileValidator { - /// - /// List of all supported valid file definitions, incl. their header signatures (magic numbers) - /// and potentially their corresponding valid subtype signatures. - /// - internal static readonly List SupportedFileDefinitions = new List - { - new FileDefinition - { - FileType = FileExtensions.Jpeg, - ValidSignatures = new List - { - new byte[] { 0xFF, 0xD8, 0xFF } // ÿØÿ - } - }, - new FileDefinition - { - FileType = FileExtensions.Jpg, - ValidSignatures = new List - { - new byte[] { 0xFF, 0xD8, 0xFF } // ÿØÿ - } - }, - new FileDefinition - { - FileType = FileExtensions.Jpe, - ValidSignatures = new List - { - new byte[] { 0xFF, 0xD8, 0xFF } // ÿØÿ - } - }, - new FileDefinition - { - FileType = FileExtensions.Pdf, - ValidSignatures = new List - { - new byte[] { 0x25, 0x50, 0x44, 0x46, 0x2D } // %PDF- - } - }, - new FileDefinition - { - FileType = FileExtensions.Png, - ValidSignatures = new List - { - new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A } // ‰PNG␍␊␚␊ - } - }, - new FileDefinition - { - FileType = FileExtensions.Bmp, - ValidSignatures = new List - { - new byte[] { 0x42, 0x4D } // BM - } - }, - new FileDefinition - { - FileType = FileExtensions.Doc, - ValidSignatures = new List - { - new byte[] { 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 } // ÐÏ␑ࡱ␚á - } - }, - new FileDefinition - { - FileType = FileExtensions.Docx, - // WARNING: This shares the same signature as .zip and could potentially allow for .zip disguised as .docx. - ValidSignatures = new List - { - new byte[] { 0x50, 0x4B, 0x03, 0x04 } // PK␃␄ - } - }, - new FileDefinition - { - FileType = FileExtensions.Odp, - // WARNING: This shares the same signature as .zip and could potentially allow for .zip disguised as .odp. - ValidSignatures = new List - { - new byte[] { 0x50, 0x4B, 0x03, 0x04 } // PK␃␄ - } - }, - new FileDefinition - { - FileType = FileExtensions.Ods, - // WARNING: This shares the same signature as .zip and could potentially allow for .zip disguised as .ods. - ValidSignatures = new List - { - new byte[] { 0x50, 0x4B, 0x03, 0x04 } // PK␃␄ - } - }, - new FileDefinition - { - FileType = FileExtensions.Odt, - // WARNING: This shares the same signature as .zip and could potentially allow for .zip disguised as .odt. - ValidSignatures = new List - { - new byte[] { 0x50, 0x4B, 0x03, 0x04 } // PK␃␄ - } - }, - new FileDefinition - { - FileType = FileExtensions.Rtf, - ValidSignatures = new List - { - new byte[] { 0x7B, 0x5C, 0x72, 0x74, 0x66, 0x31 } // {\rtf1 - } - }, - new FileDefinition - { - FileType = FileExtensions.Xlsx, - // WARNING: This shares the same signature as .zip and could potentially allow for .zip disguised as .xlsx. - ValidSignatures = new List - { - new byte[] { 0x50, 0x4B, 0x03, 0x04 } // PK␃␄ - } - }, - new FileDefinition - { - FileType = FileExtensions.Xls, - ValidSignatures = new List - { - new byte[] { 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 } // ÐÏ␑ࡱ␚á - } - }, - new FileDefinition - { - FileType = FileExtensions.Pptx, - // WARNING: This shares the same signature as .zip and could potentially allow for .zip disguised as .pptx. - ValidSignatures = new List - { - new byte[] { 0x50, 0x4B, 0x03, 0x04 } // PK␃␄ - } - }, - new FileDefinition - { - FileType = FileExtensions.M4a, - ValidSignatures = new List - { - new byte[] { 0x66, 0x74, 0x79, 0x70 } // ftyp - }, - SignatureOffset = 4, - HasSubtype = true, - SubtypeOffset = 8, - ValidSubtypeSignatures = new List - { - new byte[] { 0x4D, 0x34, 0x41, 0x20 } // M4A_ - } - }, - new FileDefinition - { - FileType = FileExtensions.Mov, - ValidSignatures = new List - { - new byte[] { 0x66, 0x74, 0x79, 0x70 } // ftyp - }, - SignatureOffset = 4, - HasSubtype = true, - SubtypeOffset = 8, - ValidSubtypeSignatures = new List - { - new byte[] { 0x71, 0x74, 0x20, 0x20 } // qt__ (Quicktime) - } - }, - new FileDefinition - { - FileType = FileExtensions.Avi, - ValidSignatures = new List - { - new byte[] { 0x52, 0x49, 0x46, 0x46 } // RIFF - }, - HasSubtype = true, - SubtypeOffset = 8, - ValidSubtypeSignatures = new List - { - new byte[] { 0x41, 0x56, 0x49, 0x20 } // AVI_ - } - }, - new FileDefinition - { - FileType = FileExtensions.Mp3, - ValidSignatures = new List - { - new byte[] { 0xFF, 0xFB }, // Without ID3 tag - new byte[] { 0xFF, 0xF2 }, // Without ID3 tag - new byte[] { 0xFF, 0xF3 }, // Without ID3 tag - new byte[] { 0x49, 0x44, 0x33 } // ID3 - } - }, - new FileDefinition - { - FileType = FileExtensions.Mp4, - ValidSignatures = new List - { - new byte[] { 0x66, 0x74, 0x79, 0x70 } // ftyp - }, - SignatureOffset = 4, - HasSubtype = true, - SubtypeOffset = 8, - ValidSubtypeSignatures = new List - { - new byte[] { 0x6D, 0x6D, 0x70, 0x34 }, // mmp4 (MP4) - new byte[] { 0x6D, 0x70, 0x34, 0x32 }, // mp42 (MP4 v2) - new byte[] { 0x69, 0x73, 0x6F, 0x6D }, // isom (ISO Base Media File) - new byte[] { 0x4D, 0x53, 0x4E, 0x56 } // MSNV (MPEG-4) - } - }, - new FileDefinition - { - FileType = FileExtensions.Wav, - ValidSignatures = new List - { - new byte[] { 0x52, 0x49, 0x46, 0x46 } // RIFF - }, - HasSubtype = true, - SubtypeOffset = 8, - ValidSubtypeSignatures = new List - { - new byte[] { 0x57, 0x41, 0x56, 0x45 } // WAVE - } - }, - new FileDefinition - { - FileType = FileExtensions.Txt, - AllowMissingSignature = true - }, - new FileDefinition - { - FileType = FileExtensions.Csv, - AllowMissingSignature = true - }, - new FileDefinition - { - FileType = FileExtensions.Ico, - ValidSignatures = new List - { - new byte[] { 0x00, 0x00, 0x01, 0x00 }, // ␀␀␁␀ - } - }, - new FileDefinition - { - FileType = FileExtensions.Heic, - ValidSignatures = new List - { - new byte[] { 0x66, 0x74, 0x79, 0x70, 0x68, 0x65, 0x69, 0x63 } // ftypheic - }, - SignatureOffset = 4 - }, - new FileDefinition - { - FileType = FileExtensions.Gif, - ValidSignatures = new List - { - new byte[] { 0x47, 0x49, 0x46, 0x38 }, // GIF8 - } - }, - new FileDefinition - { - FileType = FileExtensions.Tif, - ValidSignatures = new List - { - new byte[] { 0x49, 0x49, 0x2A, 0x00 }, // II*␀ (Intel byte order) - new byte[] { 0x4D, 0x4D, 0x00, 0x2A }, // MM␀* (Motorola byte order) - new byte[] { 0x49, 0x49, 0x2B, 0x00 }, // II+␀ (BigTIFF, Intel byte order) - new byte[] { 0x4D, 0x4D, 0x00, 0x2B } // MM␀+ (BigTIFF, Motorola byte order) - } - }, - new FileDefinition - { - FileType = FileExtensions.Tiff, - ValidSignatures = new List - { - new byte[] { 0x49, 0x49, 0x2A, 0x00 }, // II*␀ (Intel byte order) - new byte[] { 0x4D, 0x4D, 0x00, 0x2A }, // MM␀* (Motorola byte order) - new byte[] { 0x49, 0x49, 0x2B, 0x00 }, // II+␀ (BigTIFF, Intel byte order) - new byte[] { 0x4D, 0x4D, 0x00, 0x2B } // MM␀+ (BigTIFF, Motorola byte order) - } - }, - new FileDefinition - { - FileType = FileExtensions.Ogg, - ValidSignatures = new List - { - new byte[] { 0x4F, 0x67, 0x67, 0x53 } // OggS - } - }, - new FileDefinition - { - FileType = FileExtensions.Oga, - ValidSignatures = new List - { - new byte[] { 0x4F, 0x67, 0x67, 0x53 } // OggS - } - }, - new FileDefinition - { - FileType = FileExtensions.Ogv, - ValidSignatures = new List - { - new byte[] { 0x4F, 0x67, 0x67, 0x53 } // OggS - } - }, - new FileDefinition - { - FileType = FileExtensions.Ogx, - ValidSignatures = new List - { - new byte[] { 0x4F, 0x67, 0x67, 0x53 } // OggS - } - } - }; - /// /// Specific file extensions for files that are Open XML documents. /// These require extra care when validating, as Open XML files are ZIP-archives and can contain potentially harmful content. @@ -543,7 +232,7 @@ public bool IsValidFileType(string fileName) { var extension = Path.GetExtension(fileName).ToLowerInvariant(); var isSupported = _configuration.SupportedFileTypes.Contains(extension, StringComparer.InvariantCultureIgnoreCase) && - SupportedFileDefinitions.Any(fd => fd.FileType.Equals(extension, StringComparison.InvariantCultureIgnoreCase)); + FileDefinitions.SupportedFileDefinitions.Any(fd => fd.FileType.Equals(extension, StringComparison.InvariantCultureIgnoreCase)); if (!isSupported && _configuration.ThrowExceptionOnInvalidFile) { @@ -598,7 +287,7 @@ public bool HasValidSignature(string fileName, Stream stream) throw new InvalidOperationException("Stream is not seekable."); } - var fileDefinition = SupportedFileDefinitions.FirstOrDefault(fd => + var fileDefinition = FileDefinitions.SupportedFileDefinitions.FirstOrDefault(fd => fd.FileType.Equals(extension, StringComparison.InvariantCultureIgnoreCase)); if (fileDefinition == null)