timestamp.sh

A script that will use multiple, publicly available RFC 3161-compatible services for timestamping files.

Requires:

  • openssl openssl
  • curl curl

Optional: Offline verification

For the optional offline timestamp verification, a directory structure containing the CA root certificates is required. Example:

~/certificates/
β”œβ”€β”€ digicert
β”‚   └── DigiCertAssuredIDRootCA_comb.crt.pem
β”œβ”€β”€ freeTSA
β”‚   └── cacert.pem
└── globalsign
    └── root-r6.pem

For this example, the certificates can be downloaded from:

These timestamping services are configured near the top of the script – please modify where needed. If your chosen service does not offer PEM-format certificates, use this to convert:

openssl x509 -inform DER -outform PEM -in certificate.crt -out certificate.pem

Calling the script

What the script will do:

  1. Create timestamping queries (.tsq), one for each file to be time stamped (skipped if the respective query already exists)
  2. Request and retrieve time stamp responses (.tsr) from all configured time stamping services, (skipped if the respective response has already been retrieved)
  3. Optional: quick or extended offline verification of the time stamps of each time-stamped file. If needed, tries to extract the certificate chain from the time stamping response (.tsr). The quick verification is performed against the file hash value stored in the query file (.tsq); the extended verification is performed against the hash value re-calculated by reading the complete file, again (the .tsq query file will not be overwritten).

Typical calls:

timestamp.sh -e -c ~/certificates file.pdf
timestamp.sh -e -c ~/certificates -l filepaths
To download this script, first hover with your mouse over the listing below and press Β»Open code in new windowΒ«. Copying and pasting the colored and formatted listing below, as-is, won’t work.
#!/bin/bash

certificates_basedir=~/certificates
file_list=
verify_timestamps=
extended_verify_timestamps=
error=0

function usage() {
    echo "Usage:"
    echo "$(basename $0) [-e] [-v] [-c certificatesBasedir] [-l listOfFiles] [fileToBeTimestamped]"
    echo
    echo "Options:"
    echo " -c certificatesBasedir, base directory for CA certificates, one subdir per TSA"
    echo " -e, extended verification of timestamps, recalculates hash of source file (implies -v)"
    echo " -l listOfFiles, list of files to be timestamped, one file path per line"
    echo " -v, quick verification of timestamps, using only hash values from tsr"
    echo " [fileToBeTimestamped], single file to be timestamped (ignored if -l is given)"
    echo
    echo "Examples:"
    echo "$(basename $0) data.bin"
    echo "$(basename $0) -l ./tmp/filelist.txt"
    echo "$(basename $0) -e -l ./tmp/filelist.txt -c ~/certificates"
}

while getopts ":evc:l:" opt; do
  case $opt in
    c) certificates_basedir="$OPTARG"
    ;;
    e) extended_verify_timestamps=1
       verify_timestamps=1
    ;;
    l) file_list="$OPTARG"
    ;;
    v) verify_timestamps=1
    ;;
    \?) echo "Invalid option -$OPTARG" >&2
        usage
    exit 1
    ;;
  esac
done
shift $((OPTIND-1))

# see https://github.com/Manouchehri/rfc3161-servers/blob/master/server_list.txt
declare -A TSA
declare -A CAFILE

# Commercial, provides full certificate chain file including root certificate
TSA[digicert]="http://timestamp.digicert.com"
CAFILE[digicert]="DigiCertAssuredIDRootCA_comb.crt.pem"

# Non-commercial, uses self-signed root certificate
TSA[freeTSA]="https://freetsa.org/tsr"
CAFILE[freeTSA]="cacert.pem"

# Commercial, provides root-only certificate, chain must be retrieved from timestamp response
TSA[globalsign]="http://aatl-timestamp.globalsign.com/tsa/aohfewat2389535fnasgnlg5m23"
CAFILE[globalsign]="root-r6.pem"


function timestamp_file() {
    if [ -f "$1" ]; then
        verify=$2
        extended_verify=$3

        current_file_path="$1"
        echo
        echo Processing "$current_file_path" ...

        generic_query_file="$current_file_path.tsq"

        if [ ! -f "$generic_query_file" ]; then
            echo Creating generic time stamp query file ...
            openssl ts -query -data "$current_file_path" -cert -no_nonce -sha512 -out "$generic_query_file"
        else
            echo Request file exists, skipping query generation.
        fi

        echo Checking time stamps ...
        echo
        for current_tsa in "${!TSA[@]}"; do
            current_tsa_response_file="$current_file_path.$current_tsa.tsr"
            CAFile="$certificates_basedir/$current_tsa/${CAFILE[$current_tsa]}"

            if [ ! -f "$current_tsa_response_file" ]; then
                echo
                echo "TSA: $current_tsa"

                url="${TSA[$current_tsa]}"
                echo "URL: $url"
                echo "CAfile: $CAFile"

                curl --http2 -s -H "Content-Type: application/timestamp-query" --data-binary @"$generic_query_file" "$url" > "$current_tsa_response_file"

                echo Time stamp issued:
                openssl ts -reply -in "$current_tsa_response_file" -text
            else
                echo $current_tsa response file exists, skipping request.
            fi

            if [ $verify ]; then
                try_chain=0
                openssl ts -verify -x509_strict -in "$current_tsa_response_file" -queryfile "$generic_query_file" -CAfile "$CAFile" >/dev/null 2>&1
                EC=$?
                if [ $EC -ne 0 ]; then
                    try_chain=1
                    openssl ts -reply -in "$current_tsa_response_file" -token_out | openssl pkcs7 -inform der -print_certs | sed -n '/-----BEGIN/,/-----END/p' > "$certificates_basedir/$current_tsa/chain.pem"
                    openssl ts -verify -x509_strict -in "$current_tsa_response_file" -queryfile "$generic_query_file" -CAfile "$CAFile" -untrusted "$certificates_basedir/$current_tsa/chain.pem" >/dev/null 2>&1
                    EC=$?
                    if [ $EC -ne 0 ]; then
                        error=1
                        echo ERROR: Quick verification against request failed, exit code $EC for file "$current_file_path"
                    else
                        echo OK: Quick verification against request
                    fi
                else
                    echo OK: Quick verification against request
                fi

                if [ $extended_verify ]; then
                    if [ $try_chain -ne 0 ]; then
                        openssl ts -verify -x509_strict -data "$current_file_path" -in "$current_tsa_response_file" -CAfile "$CAFile" -untrusted "$certificates_basedir/$current_tsa/chain.pem" >/dev/null 2>&1
                    else
                        openssl ts -verify -x509_strict -data "$current_file_path" -in "$current_tsa_response_file" -CAfile "$CAFile" >/dev/null 2>&1
                    fi

                    EC=$?
                    if [ $EC -ne 0 ]; then
                        error=
                        echo ERROR: Full verification against recalculated file hash failed, exit code $EC for file "$current_file_path"
                    else
                        echo OK: Full verification against recalculated file hash
                    fi
                fi
                echo
            fi
        done
    else
        echo Cannot find $1 - skipping.
    fi
}

if [ -f "$file_list" ]
then
    while read current_file_path  ; do
        timestamp_file "$current_file_path" $verify_timestamps $extended_verify_timestamps
    done < "$file_list"
else
    if [ -f "$1" ]; then
        timestamp_file "$1" $verify_timestamps $extended_verify_timestamps
    else
        error=
        echo No file given.
    fi
fi

exit $error

Image Credits:
Papirus icon for Terminal (modified) | GNU General Public License, version 3

Licensing:
This content is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.
For your attributions to us please use the word Β»tuxwiseΒ«, and the link https://tuxwise.net.