ATR Authentication and download protection specification

By Steven J Tucker - classics@nacs.net

APE Home Page: http://www.nacs.net/~classics

October 20, 1996


** NOTICE **
This specification was created for the benefit of the Atari
 8-bit community and the information contained within may be used
 as part of any project, wither for profit or not, and without
 notice or payment to the author.

This document must be distributed only in its original form.

--

                   *  Why do we need authentication? *

Most major Atari archives make ATR images available for download
 in UNCOMPRESSED form to allow compatibility with all platforms
 including the Atari itself.

Unlike compression envelopes like PKZIP, the ATR format provides
 no protection for its contents.  If an ATR image becomes damaged
 at the archive, or in transit there is no way to tell other
 than the image might not work right away.  (even worse, it might
 bug out after you spent 4 hours playing that game of Spellbreaker)

This specification provides a 32-Bit CRC like that used in
 professional compression programs such as PKZIP.

As of version 1.10 APE fully supports validation of images
 marked as authenticated.

--

                 ** How does ATR Authentication work? **

The SIGNATR program included with this archive (or any program that
 follows this specification) is used by your Archivist to sign
 known-good disk images with a 32-Bit CRC that covers the total contents
 and the fixed header information.

These ATR images are now considered to be 'authentic in their
 unmodified form.  When you download an image, APE (or your
 favorite emulator that supports authentication) takes special steps
 when the image is loaded:


Step 1: Check to see if image is a SEALED & authenticated image
Step 2: Read the CRC32 stored in the ATR header by SIGNATR
Step 3: Calculate the actual CRC32 of the Image

If the CRC32 is good, the program quietly breaks the
 authentication seal (by setting a bit in the ATR header) and
 goes about its business.

Since the authentication seal is
 now broken, any program loading the file need never take
 the time to authenticate it again, its loaded as quickly
 as any other non-authenticated image.

If the CRC is good, the entire process is transparent to the user,
 and takes less than 1/2 a second on a 486/50.

If the file was damaged, the program can take appropriate action
 to let you know.

Because this method uses only extra bytes/bits in the ATR
 header, it is transparent to utilities that are unaware
 of authentication.

--

            * Ok, How do I authenticate images for download? *

The program included, SIGNATR will do all the work for you.  Just
 run it with no parameters for help.  Once authenticated, you
 can let users having trouble with your images use the same
 freeware program to check to see if the image was corrupted.

Download the free Sign_ATR package, including this document and free authentication utility.
--

                           * Technical stuff *

The 'authenticated image' flag is stored in BIT 1 of BYTE 15 of
 the ATR header.  This is the last user byte in the header.

Note: This byte should NOT be clobbered, the other bits
 are in use or reserved for future use.

The CRC32 is a 32 bit signed integer stored in bytes 8, 9, 10, and 11
 of the ATR header.  A signed CRC with the hex representation of
 $ABCDEF01 is stored in the header as:

8  9  10 11
-----------
01 EF CD AB

The CRC is generated using all bytes of the ATR including the
 header.  Bytes 8-15 of the header are evaluated as 0, and
 NOT skipped.

The steps for signing a unsigned file are:

1) Calculate the CRC32 using the code below
2) Store the CRC32 in the header as described above
3) Set the Authentication Envelope bit as described above.

The steps for handling a authenticated file the first time it
 is accessed by a user program:

1) Check to see if its an authenticated ATR (if byte 15/bit 1 = 1)
2) If not load as normal ATR and exit

3) Read CRC stored in header
4) Calculate the actual crc using the method below
5) If the values match CLEAR THE ENVELOPE BIT and load ATR

6) If CRC is wrong do whatever you want, warn user, etc.

The header is generated using a method common to many programs.  Similar
 source code snippets are available for all languages on the
 net.

Complete pascal source for creating a valid CRC32 of an ATR
 file is included below.  If you write a C version of the
 code please send me a copy so I can include it in this
 document.

This code is not optimal, just quick stuff and it does
 not check for memory, disk errors, etc.  Check it over
 if you use it.

const crc32tab:array[0..255] of longint=(
        $00000000, $77073096, $ee0e612c, $990951ba, $076dc419, $706af48f,
        $e963a535, $9e6495a3, $0edb8832, $79dcb8a4, $e0d5e91e, $97d2d988,
        $09b64c2b, $7eb17cbd, $e7b82d07, $90bf1d91, $1db71064, $6ab020f2,
        $f3b97148, $84be41de, $1adad47d, $6ddde4eb, $f4d4b551, $83d385c7,
        $136c9856, $646ba8c0, $fd62f97a, $8a65c9ec, $14015c4f, $63066cd9,
        $fa0f3d63, $8d080df5, $3b6e20c8, $4c69105e, $d56041e4, $a2677172,
        $3c03e4d1, $4b04d447, $d20d85fd, $a50ab56b, $35b5a8fa, $42b2986c,
        $dbbbc9d6, $acbcf940, $32d86ce3, $45df5c75, $dcd60dcf, $abd13d59,
        $26d930ac, $51de003a, $c8d75180, $bfd06116, $21b4f4b5, $56b3c423,
        $cfba9599, $b8bda50f, $2802b89e, $5f058808, $c60cd9b2, $b10be924,
        $2f6f7c87, $58684c11, $c1611dab, $b6662d3d, $76dc4190, $01db7106,
        $98d220bc, $efd5102a, $71b18589, $06b6b51f, $9fbfe4a5, $e8b8d433,
        $7807c9a2, $0f00f934, $9609a88e, $e10e9818, $7f6a0dbb, $086d3d2d,
        $91646c97, $e6635c01, $6b6b51f4, $1c6c6162, $856530d8, $f262004e,
        $6c0695ed, $1b01a57b, $8208f4c1, $f50fc457, $65b0d9c6, $12b7e950,
        $8bbeb8ea, $fcb9887c, $62dd1ddf, $15da2d49, $8cd37cf3, $fbd44c65,
        $4db26158, $3ab551ce, $a3bc0074, $d4bb30e2, $4adfa541, $3dd895d7,
        $a4d1c46d, $d3d6f4fb, $4369e96a, $346ed9fc, $ad678846, $da60b8d0,
        $44042d73, $33031de5, $aa0a4c5f, $dd0d7cc9, $5005713c, $270241aa,
        $be0b1010, $c90c2086, $5768b525, $206f85b3, $b966d409, $ce61e49f,
        $5edef90e, $29d9c998, $b0d09822, $c7d7a8b4, $59b33d17, $2eb40d81,
        $b7bd5c3b, $c0ba6cad, $edb88320, $9abfb3b6, $03b6e20c, $74b1d29a,
        $ead54739, $9dd277af, $04db2615, $73dc1683, $e3630b12, $94643b84,
        $0d6d6a3e, $7a6a5aa8, $e40ecf0b, $9309ff9d, $0a00ae27, $7d079eb1,
        $f00f9344, $8708a3d2, $1e01f268, $6906c2fe, $f762575d, $806567cb,
        $196c3671, $6e6b06e7, $fed41b76, $89d32be0, $10da7a5a, $67dd4acc,
        $f9b9df6f, $8ebeeff9, $17b7be43, $60b08ed5, $d6d6a3e8, $a1d1937e,
        $38d8c2c4, $4fdff252, $d1bb67f1, $a6bc5767, $3fb506dd, $48b2364b,
        $d80d2bda, $af0a1b4c, $36034af6, $41047a60, $df60efc3, $a867df55,
        $316e8eef, $4669be79, $cb61b38c, $bc66831a, $256fd2a0, $5268e236,
        $cc0c7795, $bb0b4703, $220216b9, $5505262f, $c5ba3bbe, $b2bd0b28,
        $2bb45a92, $5cb36a04, $c2d7ffa7, $b5d0cf31, $2cd99e8b, $5bdeae1d,
        $9b64c2b0, $ec63f226, $756aa39c, $026d930a, $9c0906a9, $eb0e363f,
        $72076785, $05005713, $95bf4a82, $e2b87a14, $7bb12bae, $0cb61b38,
        $92d28e9b, $e5d5be0d, $7cdcefb7, $0bdbdf21, $86d3d2d4, $f1d4e242,
        $68ddb3f8, $1fda836e, $81be16cd, $f6b9265b, $6fb077e1, $18b74777,
        $88085ae6, $ff0f6a70, $66063bca, $11010b5c, $8f659eff, $f862ae69,
        $616bffd3, $166ccf45, $a00ae278, $d70dd2ee, $4e048354, $3903b3c2,
        $a7672661, $d06016f7, $4969474d, $3e6e77db, $aed16a4a, $d9d65adc,
        $40df0b66, $37d83bf0, $a9bcae53, $debb9ec5, $47b2cf7f, $30b5ffe9,
        $bdbdf21c, $cabac28a, $53b39330, $24b4a3a6, $bad03605, $cdd70693,
        $54de5729, $23d967bf, $b3667a2e, $c4614ab8, $5d681b02, $2a6f2b94,
        $b40bbe37, $c30c8ea1, $5a05df1b, $2d02ef8d);

type
 tChunk = Array[1..60000] of Byte;

var
 Blocks   : Word;
 Remain   : Word;
 Loop     : Word;
 Work     : Word;
 Chunk    : ^tChunk;
 Crc      : Longint;
 fCheck   : File;

Function FileCrc(Filename: String): Longint;
 Begin
  New(Chunk);

  Assign(fCheck, Filename);
  Reset(fCheck, 1);

  Blocks := FileSize(fCheck) div Sizeof(Chunk^);
  Remain := FileSize(fCheck) - (Blocks * SizeOf(Chunk^));

  Crc := $ffffffff;

  For Loop := 1 to Blocks do
  Begin
   Blockread(fCheck, Chunk^[1], SizeOf(Chunk^));

   If Loop = 1 then
    For Work := 8 to 16 do
     Chunk^[Work] := 0;

   For Work := 1 to SizeOf(Chunk^) do
    Crc := crc32tab[byte(crc xor longint(Chunk^[Work]))] xor ((crc shr 8) and $00ffffff);
  End;

  Blockread(fCheck, Chunk^[1], Remain);

  For Work := 1 to Remain do
   Crc := crc32tab[byte(crc xor longint(Chunk^[Work]))] xor ((crc shr 8) and $00ffffff);

  Crc := Crc xor $ffffffff;

  FileCRC := Crc;

  Close(fCheck);
  Dispose(Chunk);
End;