Skip to content

Instantly share code, notes, and snippets.

@NicolasCARPi
Last active June 19, 2026 07:52
Show Gist options
  • Select an option

  • Save NicolasCARPi/16869ab2e05e475d89d9e61fd8c4aab6 to your computer and use it in GitHub Desktop.

Select an option

Save NicolasCARPi/16869ab2e05e475d89d9e61fd8c4aab6 to your computer and use it in GitHub Desktop.
verify universign timestamp
#!/usr/bin/env bash
# Author: Nicolas CARPi / Deltablot
# License: WTFPL
set -euo pipefail
# get selfsigned.pem file with:
# curl -fsSLo universign-tsa-root-2019-selfsigned.pem https://www.universign.com/documents/universign-tsa-root-2019-selfsigned.pem
usage() {
cat >&2 <<'EOF'
Usage: verify-universign-ts.sh <timestamped.json> <timestamp.asn1>
Environment:
TSA_ROOT=/path/to/universign-tsa-root-2019-selfsigned.pem
Default:
TSA_ROOT=./universign-tsa-root-2019-selfsigned.pem
EOF
}
die() {
echo "ERROR: $*" >&2
exit 1
}
say() {
printf '\n==> %s\n' "$*"
}
ok() {
printf 'OK: %s\n' "$*"
}
need_cmd() {
command -v "$1" >/dev/null 2>&1 || die "missing required command: $1"
}
hex_digest() {
local alg=$1
local file=$2
if command -v xxd >/dev/null 2>&1; then
openssl dgst "-$alg" -binary "$file" | xxd -p -c 256
else
openssl dgst "-$alg" -binary "$file" | od -An -tx1 -v | tr -d ' \n'
printf '\n'
fi
}
extract_imprint_hex() {
local text_file=$1
awk '
BEGIN { in_msg = 0 }
/^[[:space:]]*Message data:/ {
in_msg = 1
next
}
in_msg == 1 && /^[[:space:]]*[0-9A-Fa-f]+ -/ {
line = $0
sub(/^[[:space:]]*[0-9A-Fa-f]+ -[[:space:]]*/, "", line)
sub(/[[:space:]]{3,}.*/, "", line)
gsub(/[[:space:]-]/, "", line)
printf "%s", tolower(line)
next
}
in_msg == 1 && /^[[:space:]]*$/ {
next
}
in_msg == 1 {
exit
}
' "$text_file"
}
parse_field() {
local field=$1
local text_file=$2
awk -F':[[:space:]]*' -v field="$field" '
$1 ~ "^[[:space:]]*" field "$" {
print $2
exit
}
' "$text_file" | sed 's/[[:space:]]*$//'
}
to_epoch_utc() {
local value=$1
if date -u -d "$value" '+%s' >/dev/null 2>&1; then
date -u -d "$value" '+%s'
return 0
fi
if date -u -j -f '%b %e %H:%M:%S %Y %Z' "$value" '+%s' >/dev/null 2>&1; then
date -u -j -f '%b %e %H:%M:%S %Y %Z' "$value" '+%s'
return 0
fi
return 1
}
if [ "$#" -ne 2 ]; then
usage
exit 2
fi
data_file=$1
asn1_file=$2
root_cert=${TSA_ROOT:-universign-tsa-root-2019-selfsigned.pem}
[ -f "$data_file" ] || die "data file not found: $data_file"
[ -f "$asn1_file" ] || die "ASN.1 timestamp file not found: $asn1_file"
[ -f "$root_cert" ] || die "trusted TSA root not found: $root_cert"
need_cmd openssl
need_cmd awk
need_cmd sed
need_cmd grep
need_cmd date
workdir=$(mktemp -d)
trap 'rm -rf "$workdir"' EXIT
token_der="$workdir/timestamp-token.der"
tst_text="$workdir/tstinfo.txt"
tstinfo_der="$workdir/tstinfo.der"
embedded_certs="$workdir/embedded-certs.pem"
signer_pem="$workdir/signer.pem"
intermediates_pem="$workdir/intermediates.pem"
response_text="$workdir/response.txt"
say 'checking the trusted TSA root certificate'
openssl x509 -in "$root_cert" -noout -subject -issuer -serial -dates -fingerprint -sha256 | sed 's/^/ /'
say 'extracting the timestamp token'
if openssl ts -reply -in "$asn1_file" -token_out -out "$token_der" >/dev/null 2>"$workdir/extract-token.err"; then
ok 'input is a full RFC 3161 TimeStampResp; extracted the embedded CMS token'
if openssl ts -reply -in "$asn1_file" -text > "$response_text" 2>/dev/null; then
echo 'Timestamp response status:'
grep -E 'Status|Failure' "$response_text" | sed 's/^/ /' || true
fi
elif openssl cms -cmsout -inform DER -in "$asn1_file" -noout >/dev/null 2>"$workdir/probe-cms.err"; then
cp "$asn1_file" "$token_der"
ok 'input is already a CMS timestamp token'
else
cat "$workdir/extract-token.err" >&2 || true
cat "$workdir/probe-cms.err" >&2 || true
die 'input is neither a readable TimeStampResp nor a readable CMS timestamp token'
fi
say 'parsing timestamp information'
openssl ts -reply -token_in -in "$token_der" -text > "$tst_text"
policy_oid=$(parse_field 'Policy OID' "$tst_text")
hash_alg=$(parse_field 'Hash Algorithm' "$tst_text" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]')
serial_number=$(parse_field 'Serial number' "$tst_text")
timestamp_time=$(parse_field 'Time stamp' "$tst_text")
token_imprint=$(extract_imprint_hex "$tst_text")
[ -n "$hash_alg" ] || die 'could not read hash algorithm from timestamp token'
[ -n "$token_imprint" ] || die 'could not read message imprint from timestamp token'
echo " Policy OID: $policy_oid"
echo " Hash algorithm: $hash_alg"
echo " Serial number: $serial_number"
echo " Time stamp: $timestamp_time"
echo " Token message imprint: $token_imprint"
say 'verifying the CMS signature using the RSA-PSS aware CMS verifier'
if cms_output=$(openssl cms -verify -inform DER -in "$token_der" -noverify -out "$tstinfo_der" -signer "$signer_pem" 2>&1); then
ok 'CMS signature is cryptographically valid'
else
printf '%s\n' "$cms_output" >&2
die 'CMS signature verification failed'
fi
if [ ! -s "$signer_pem" ]; then
die 'OpenSSL verified the CMS signature but did not output the signer certificate'
fi
say 'extracting certificates embedded in the timestamp token'
openssl pkcs7 -inform DER -in "$token_der" -print_certs -out "$embedded_certs"
cert_count=$(
awk -v dir="$workdir" '
/-----BEGIN CERTIFICATE-----/ {
n++
file = sprintf("%s/embedded-cert-%02d.pem", dir, n)
}
n > 0 {
print > file
}
/-----END CERTIFICATE-----/ {
close(file)
}
END {
print n + 0
}
' "$embedded_certs"
)
[ "$cert_count" -gt 0 ] || die 'no embedded certificates found in timestamp token'
ok "found $cert_count embedded certificate(s)"
: > "$intermediates_pem"
for cert in "$workdir"/embedded-cert-*.pem; do
if openssl x509 -in "$cert" -noout -ext basicConstraints 2>/dev/null | grep -q 'CA:TRUE'; then
cat "$cert" >> "$intermediates_pem"
fi
done
say 'checking the TSA signer certificate'
openssl x509 -in "$signer_pem" -noout -subject -issuer -serial -dates -fingerprint -sha256 -ext extendedKeyUsage -ext basicConstraints | sed 's/^/ /'
eku_text=$(openssl x509 -in "$signer_pem" -noout -ext extendedKeyUsage 2>/dev/null || true)
printf '%s\n' "$eku_text" | grep -q 'Time Stamping' || die 'signer certificate does not contain the Time Stamping extended key usage'
printf '%s\n' "$eku_text" | grep -qi 'critical' || die 'signer certificate Time Stamping EKU is not marked critical'
ok 'signer certificate has critical Time Stamping EKU'
say 'verifying the TSA signer certificate chain and timestamping purpose'
verify_cmd=(openssl verify -purpose timestampsign -CAfile "$root_cert")
if [ -n "$timestamp_time" ] && attime=$(to_epoch_utc "$timestamp_time"); then
verify_cmd+=(-attime "$attime")
echo " Verifying certificate validity at timestamp time: $timestamp_time"
else
echo ' WARNING: could not parse timestamp time; verifying certificate validity at current time'
fi
if [ -s "$intermediates_pem" ]; then
verify_cmd+=(-untrusted "$intermediates_pem")
fi
verify_cmd+=("$signer_pem")
if chain_output=$("${verify_cmd[@]}" 2>&1); then
printf ' %s\n' "$chain_output"
ok 'TSA signer certificate chains to the configured trusted root and is valid for timestamp signing'
else
printf '%s\n' "$chain_output" >&2
die 'TSA signer certificate chain or timestamping purpose verification failed'
fi
say 'computing the local digest of the JSON file'
case "$hash_alg" in
sha1|sha224|sha256|sha384|sha512|sha3-224|sha3-256|sha3-384|sha3-512)
;;
*)
die "unsupported or unexpected hash algorithm: $hash_alg"
;;
esac
local_digest=$(hex_digest "$hash_alg" "$data_file" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]')
echo " Local $hash_alg digest: $local_digest"
echo " Token message imprint: $token_imprint"
if [ "$local_digest" = "$token_imprint" ]; then
ok 'JSON digest matches the timestamp token message imprint'
else
die 'JSON digest does not match the timestamp token message imprint'
fi
say 'final result'
echo 'FINAL RESULT: OK'
echo 'The timestamp token signature is valid, the TSA signer chains to the configured trusted root,'
echo 'the signer certificate is valid for timestamp signing, and the JSON file matches the timestamp imprint.'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment