The DSIG table holds a digital signature over the whole font, so that
software can check who made the font and that it has not been changed since, including
its embedding permissions. It works the same in fonts with TrueType, CFF or CFF2
outlines, and in font collections. The table is
optional; whether unsigned fonts are allowed is left to the system using them. All
numbers are big-endian; the types are those on the
table directory page.
Header
| Offset | Type | Name | Description |
|---|---|---|---|
0 | uint32 | version | 1 (0x00000001). Versions count up from 0; 1 is the only one in use. |
4 | uint16 | numSignatures | The number of signatures. |
6 | uint16 | flags | Permission flags. |
8 | record × numSignatures | signatureRecords | One 12-byte signature record per signature. |
Flags
| Bit | Mask | Meaning |
|---|---|---|
| 0 | 0x0001 | Cannot be resigned. When set, no one else may add a signature over the existing ones (a counter-signature); a signer sets it to make sure theirs is the last. When clear, further signatures may be added. |
| 1–7 | 0x00FE | Reserved, set to 0. |
The specification describes only bits 0 to 7 of the 16-bit field.
Signature record
| Offset | Type | Name | Description |
|---|---|---|---|
0 | uint32 | format | The format of the signature block, which also fixes the hashing method. Only format 1 is defined. |
4 | uint32 | length | The length of the signature block in bytes. |
8 | Offset32 | signatureBlockOffset | Where the signature block starts, from the start of the DSIG table. |
Signature block, format 1
| Offset | Type | Name | Description |
|---|---|---|---|
0 | uint16 | reserved1 | 0. |
2 | uint16 | reserved2 | 0. |
4 | uint32 | signatureLength | The length of the PKCS#7 packet in bytes. |
8 | uint8 × signatureLength | signature | A PKCS#7 signed-data packet (RFC 2315) in DER encoding; counter-signatures follow PKCS#9 (RFC 2985). |
Format 1 is for whole fonts with TrueType outlines, CFF or CFF2 data, or both. It is also the only format allowed in a collection.
Making and checking the signature
The signed digest covers the font as it would be without its DSIG
table. To sign:
- If the font already has a
DSIGtable, remove the table and its table record, move the other tables’ offsets to match, and recalculatechecksumAdjustmentinhead. - Append the chosen
flagsvalue to a copy of that font data. - Hash the result with a secure one-way hash (the specification gives MD5 as an example) to get the content digest.
- Make the PKCS#7 signature block from the digest.
- Make a new
DSIGtable holding the block. - Add it to the font, moving table offsets as needed.
- Add its table record to the table directory.
- Recalculate
checksumAdjustmentinhead.
To check, software repeats steps 1 to 4 on a copy in memory, using the
flags value stored in the font. The final change to
checksumAdjustment does not break the signature, because step 1 undoes
it.
Before signing, the font must be tidy, or the digest will not match after a reader rebuilds it:
- the magic number in
headis right, and the table directory’s search fields agree withnumTables; - table records are in tag order with no duplicate tags;
- every table starts on a multiple of 4, and the first table comes straight after the directory;
- sorted by offset, tables do not overlap and have at most 3 bytes of padding between
them (
offset[i]+length[i]≤offset[i+1]and ≥offset[i+1]− 3), and the padding bytes are zero; - the last table ends within the file;
- every table checksum and
checksumAdjustmentare correct.
Collections
- A signed collection has one
DSIGtable for the whole file, which must be the last table in the file. It is found throughdsigTag,dsigLengthanddsigOffsetin a version 2.0 collection header, not through a table directory. - The signature covers the entire file, so nothing can be added to it unnoticed. Fonts inside a collection should not be signed one by one, since building the collection would break their signatures.
- The steps above change: in step 1, an existing
DSIGis removed and the header’s three signature fields set to 0, with nochecksumAdjustmentto recalculate; in steps 6 and 7 the table is added to the file and the header fields point to it; step 8 does not apply. - Signatures in a collection must be format 1.
Example
The DSIG table of Georgia (Georgia.ttf, as shipped with
macOS), 6,008 bytes at offset 373,580, starts:
0005B34C 00 00 00 01 00 01 00 01 00 00 00 01 00 00 17 64 ...............d
0005B35C 00 00 00 14 00 00 00 00 00 00 17 5C 30 82 17 58 ...........\0..X
0005B36C 06 09 2A 86 48 86 F7 0D 01 07 02 A0 82 17 49 30 ..*.H.........I0
| Bytes | Field | Value |
|---|---|---|
00 00 00 01 | version | 1 |
00 01 | numSignatures | 1 |
00 01 | flags | Bit 0: cannot be resigned |
00 00 00 01 | format | 1 |
00 00 17 64 | length | 5,988 bytes |
00 00 00 14 | signatureBlockOffset | 20, straight after the 8-byte header and one 12-byte record |
00 00 00 00 | reserved1, reserved2 | 0 |
00 00 17 5C | signatureLength | 5,980 bytes: the block’s 5,988 less its 8-byte header |
30 82 17 58 … | signature | A DER sequence of 5,976 bytes (5,980 with its 4-byte tag and length) whose first item is the object identifier 1.2.840.113549.1.7.2, PKCS#7 signed data |
Further in, the packet names SHA-1 (1.3.14.3.2.26) as its digest method and carries Microsoft’s Authenticode content type 1.3.6.1.4.1.311.2.1.4. The OpenType specification itself does not say which content type the packet must use.