Check digital signature of OSX (.dmg) file in Windows
Asked Answered
C

2

6

I am currently checking that the digital signature of windows installer files (.msi) is valid in C# using the WinVerifyTrust api. I am also verifying that the thumbprint from the signature is from a known list.

I need to do the same for Mac OSX files (.dmg) in C# (on Windows). Is there any way to do this?

Considerable answered 24/2, 2017 at 9:35 Comment(8)
look into github.com/quamotion/discutils this library can read dmg contents. remains to figure out if, where, and how the signature (certificate?) is stored in dmg.Urethra
Do you have a sample .dmg with signature available for download?Heavily
@SimonMourier I don't have a sample I can post online unfortunatelyConsiderable
@dlatikay - I'll have a look at that library and report back. ThanksConsiderable
w/o a sample DMG, you'll won't be able to test anythingHeavily
I have a signed dmg (well, I'm not sure if it's the dmg or the pkg file inside that's signed at the moment), but I'm not able to publicly share it.Considerable
that's quite different. dmg can be signed with macOS 10.11.5 and later, but many aren't, only executables inside are.Heavily
It looks like I'll have to just look for the developer ID in the plain text of the file and assume that any tampering will result in the signature being invalidated. It's not ideal but there doesn't appear to be a way to verify Mac signatures on WindowsConsiderable
P
1

Any DMG file has 'Koly' block, I don't think you will easily find ready to go code on windows capable to read it in C#...But take a look here http://newosxbook.com/DMG.html

What you practically interested in is last 512 bytes of a file.

typedef struct {
        char     Signature[4];          // Magic ('koly')
        uint32_t Version;               // Current version is 4
        uint32_t HeaderSize;            // sizeof(this), always 512
        uint32_t Flags;                 // Flags
        uint64_t RunningDataForkOffset; //
        uint64_t DataForkOffset;        // Data fork offset (usually 0, beginning of file)
        uint64_t DataForkLength;        // Size of data fork (usually up to the XMLOffset, below)
        uint64_t RsrcForkOffset;        // Resource fork offset, if any
        uint64_t RsrcForkLength;        // Resource fork length, if any
        uint32_t SegmentNumber;         // Usually 1, may be 0
        uint32_t SegmentCount;          // Usually 1, may be 0
        uuid_t   SegmentID;             // 128-bit GUID identifier of segment (if SegmentNumber !=0)

    uint32_t DataChecksumType;      // Data fork 
        uint32_t DataChecksumSize;      //  Checksum Information
        uint32_t DataChecksum[32];      // Up to 128-bytes (32 x 4) of checksum

        uint64_t XMLOffset;             // Offset of property list in DMG, from beginning
        uint64_t XMLLength;             // Length of property list
        uint8_t  Reserved1[120];        // 120 reserved bytes - zeroed

    uint32_t ChecksumType;          // Master
        uint32_t ChecksumSize;          //  Checksum information
        uint32_t Checksum[32];          // Up to 128-bytes (32 x 4) of checksum

        uint32_t ImageVariant;          // Commonly 1
        uint64_t SectorCount;           // Size of DMG when expanded, in sectors

        uint32_t reserved2;             // 0
        uint32_t reserved3;             // 0 
        uint32_t reserved4;             // 0

} __attribute__((__packed__)) UDIFResourceFile;

Consider the following lines of code as example of reading bytes

public static uint ToUInt32BigEndian(byte[] buffer, int offset)
        {
            uint val = (uint)(((buffer[offset + 0] << 24) & 0xFF000000U) | ((buffer[offset + 1] << 16) & 0x00FF0000U)
                              | ((buffer[offset + 2] << 8) & 0x0000FF00U) | ((buffer[offset + 3] << 0) & 0x000000FFU));
            return val;
        }

        public static ulong ToUInt64BigEndian(byte[] buffer, int offset)
        {
            return ((ulong)ToUInt32BigEndian(buffer, offset + 0) << 32) | ToUInt32BigEndian(buffer, offset + 4);
        }


internal class UdifChecksum : IByteArraySerializable
    {
        public uint ChecksumSize;
        public byte[] Data;
        public uint Type;

        public int Size
        {
            get { return 136; }
        }

        public int ReadFrom(byte[] buffer, int offset)
        {
            Type = EndianUtilities.ToUInt32BigEndian(buffer, offset + 0);
            ChecksumSize = EndianUtilities.ToUInt32BigEndian(buffer, offset + 4);
            Data = EndianUtilities.ToByteArray(buffer, offset + 8, 128);

            return 136;
        }

        public void WriteTo(byte[] buffer, int offset)
        {
            throw new NotImplementedException();
        }
    }

Here you can see example of reading all properties from file header

internal class UdifResourceFile : IByteArraySerializable
    {
        public UdifChecksum DataForkChecksum;
        public ulong DataForkLength;
        public ulong DataForkOffset;
        public uint Flags;
        public uint HeaderSize;
        public uint ImageVariant;

        public UdifChecksum MasterChecksum;
        public ulong RsrcForkLength;
        public ulong RsrcForkOffset;

        public ulong RunningDataForkOffset;
        public long SectorCount;
        public uint SegmentCount;
        public Guid SegmentGuid;

        public uint SegmentNumber;
        public uint Signature;
        public uint Version;
        public ulong XmlLength;
        public ulong XmlOffset;

        public bool SignatureValid
        {
            get { return Signature == 0x6B6F6C79; }
        }

        public int Size
        {
            get { return 512; }
        }

        public int ReadFrom(byte[] buffer, int offset)
        {
            Signature = EndianUtilities.ToUInt32BigEndian(buffer, offset + 0);
            Version = EndianUtilities.ToUInt32BigEndian(buffer, offset + 4);
            HeaderSize = EndianUtilities.ToUInt32BigEndian(buffer, offset + 8);
            Flags = EndianUtilities.ToUInt32BigEndian(buffer, offset + 12);
            RunningDataForkOffset = EndianUtilities.ToUInt64BigEndian(buffer, offset + 16);
            DataForkOffset = EndianUtilities.ToUInt64BigEndian(buffer, offset + 24);
            DataForkLength = EndianUtilities.ToUInt64BigEndian(buffer, offset + 32);
            RsrcForkOffset = EndianUtilities.ToUInt64BigEndian(buffer, offset + 40);
            RsrcForkLength = EndianUtilities.ToUInt64BigEndian(buffer, offset + 48);
            SegmentNumber = EndianUtilities.ToUInt32BigEndian(buffer, offset + 56);
            SegmentCount = EndianUtilities.ToUInt32BigEndian(buffer, offset + 60);
            SegmentGuid = EndianUtilities.ToGuidBigEndian(buffer, offset + 64);

            DataForkChecksum = EndianUtilities.ToStruct<UdifChecksum>(buffer, offset + 80);
            XmlOffset = EndianUtilities.ToUInt64BigEndian(buffer, offset + 216);
            XmlLength = EndianUtilities.ToUInt64BigEndian(buffer, offset + 224);

            MasterChecksum = EndianUtilities.ToStruct<UdifChecksum>(buffer, offset + 352);
            ImageVariant = EndianUtilities.ToUInt32BigEndian(buffer, offset + 488);
            SectorCount = EndianUtilities.ToInt64BigEndian(buffer, offset + 492);

            return Size;
        }

        public void WriteTo(byte[] buffer, int offset)
        {
            throw new NotImplementedException();
        }
    }

More details for code here (https://github.com/DiscUtils/DiscUtils)

If you do read XML definition of file (check XMLOffset and XMLLength) you should be able to identify file structure and extract files (and it's properties).

The XML Property list (which is uncompressed and easily viewable by seeking to the DOCTYPE declaration using more(1) or using tail(1)) is technically the resource fork of the DMG. The property list file contains, at a minimum, a "blkx" key, though it may contain other key/values, most commonly "plst", and sometimes a service level agreement (SLA) which will be displayed by the OS (specifically, /System/Library/PrivateFrameworks/DiskImages.framework/Versions/A/Resources/DiskImages UI Agent.app/Contents/MacOS/DiskImages UI Agent) as a pre-requisite to attaching the DMG*. Due to XML parser restrictions, data in the property list is 7-bit. This forces all binary (8-bit) data to be encoded using Base-64 encoding (a wiser choice would have been using CDATA blocks)

Since MAC is signing files (as far as I remember) the signature could be taken from there. Checksum at top level is protecting from package modification and should be made with same certificate that is used for signing.

Hope this helps.

Palmy answered 13/7, 2022 at 21:55 Comment(0)
G
0

Use OpenSsl, Bouncy Castle or System.Security.Cryptography (sha256 checksum or similar) to check the checksum using C# on your system. If you are the supplier you can generate a hash first and publish it on the download page for both .msi and .dmg files. I have tried this before and it works well. However, I have no code to attach to this answer at the moment, use ComputeHash function in System.Security.Cryptography.

If you don't have direct access to the files, you can download .dmg and create a hash from it using C#. A hash that will be correctly verified when checked unless manipulated that is. Creating a hash from all bytes of the file is way more secure than trust embedded data which can be replaced and signed to appear valid, unless you cross-check everything with the creator(s).

Grantham answered 19/3, 2017 at 16:23 Comment(1)
I appreciate the response, but it's not practical for us to use this approach instead (we need to check files signed from our company from many different departments with many different releases - we would have to maintain massive lists of file hashes etc).Considerable

© 2022 - 2024 — McMap. All rights reserved.