diff --git a/hooks/post-commit b/hooks/post-commit new file mode 100644 index 0000000..40daa9d --- /dev/null +++ b/hooks/post-commit @@ -0,0 +1,131 @@ +#!/bin/bash +# +# RFC3161 and RFC5816 Timestamping for git repositories. +# +# Copyright (c) 2020 Mabulous GmbH +# Authors: Matthias Bühlmann +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# The interactive user interfaces in modified source and object code versions +# of this program must display Appropriate Legal Notices, as required under +# Section 5 of the GNU Affero General Public License version 3. +# +# You can be released from the requirements of the license by purchasing +# a commercial license. Buying such a license is mandatory as soon as you +# develop commercial activities involving this software without +# disclosing the source code of your own applications. +# These activities include: offering paid services to customers as an ASP, +# providing data storage and archival services, shipping this software with a +# closed source product. +# +# For more information, please contact Mabulous GmbH at this +# address: info@mabulous.com +# + +DIR="${BASH_SOURCE%/*}" +if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi +. "$DIR/timestamping" + +extended_exit_trap() { + local EXIT_CODE="$?" + if [ "$EXIT_CODE" -gt 0 ]; then + echo_error "Aborting commit." + git reset --soft HEAD^ + fi + rm -rf -- "$TMP_DIR" + exit "$EXIT_CODE" +} +trap "extended_exit_trap" EXIT + +COMMIT_MSG=$(git show --pretty=format:"%B" --no-patch HEAD) + +#avoid recursion and validate timestamp +if [[ "$COMMIT_MSG" == "$SUBJECT_LINE"* ]]; then + log "exiting recursion" + exit 0 +fi + +echo_info "Adding Timestamp commit" + +#prepare commit message +COMMIT_MSG_FILE="$TMP_DIR"/commit_msg.txt +echo "$SUBJECT_LINE" > "$COMMIT_MSG_FILE" + +#get hash of unstamped commit +DIGEST=$(git rev-parse --verify HEAD) +#request a timestamp token for each TSA defined +TSA_IDX=-1 +while : ; do + ((TSA_IDX++)) + TSA_URL=$(git config timestamping.tsa"$TSA_IDX".url) + if [ -z "$TSA_URL" ]; then + if [ "$TSA_IDX" -eq 0 ]; then + echo_error "Error: no TSA url set. Define at least an url for TSA0 using git config timestamp.tsa0.url ... to set TSA URL". + exit 1 + else + break + fi + fi + echo_info "for TSA $TSA_URL" + TOKEN_OPTIONAL=$(git config --type=bool timestamping.tsa"$TSA_IDX".optional) + #retrieve token + TOKEN_FILE="$TMP_DIR"/token.tst + if ! request_token "$TSA_URL" "$DIGEST" false "$TOKEN_FILE"; then + if [ ! "$TOKEN_OPTIONAL" ]; then + echo_error "Error: Retrieving timestamp token for critical TSA$TSA_IDX failed." + exit 1 + else + echo_warning "Warning: Retrieving timestamp token for optional TSA$TSA_IDX failed. Token won't be added." + continue + fi + fi + #validate token and download LTV data + if ! verify_token_and_add_ltv_data "$TOKEN_FILE" "$DIGEST" "$TSA_URL"; then + if [ ! "$TOKEN_OPTIONAL" ]; then + echo_error "Error: Validating timestamp token for critical TSA$TSA_IDX failed." + exit 1 + else + echo_warning "Warning: Validating timestamp token for optional TSA$TSA_IDX failed. Token won't be added." + continue + fi + fi + #add token to commit message + openssl ts -reply -token_in -in "$TOKEN_FILE" -token_out -text -out "$TMP_DIR"/token.txt &> "$OUT_STREAM" + TOKENBASE64=$(openssl base64 -in "$TOKEN_FILE") + TOKENTEXT=$(cat "$TMP_DIR"/token.txt) + INFO="Info: Token digest is hash of parent commit." + TRAILER_VALUE="$TSA_URL"$'\n'"$INFO"$'\n\n'"$TOKENTEXT"$'\n\n'"$TOKEN_HEADER"$'\n'"$TOKENBASE64"$'\n'"$TOKEN_FOOTER" + #fold + TRAILER_VALUE=$(echo -n "$TRAILER_VALUE" | sed -e 's/^/ /') + git interpret-trailers --where end --if-exists addIfDifferent --no-divider --trailer "Timestamp:$TRAILER_VALUE" --in-place "$COMMIT_MSG_FILE" +done + +#add all ltv files +if [ ! -d "$LTV_DIR"/certs ]; then + mkdir -p "$LTV_DIR"/certs +fi +if [ ! -d "$LTV_DIR"/crls ]; then + mkdir -p "$LTV_DIR"/crls +fi +ls "$TMP_LTV_DIR"/*/* | while read SOURCE_FILE; do + TARGET_FILE="$LTV_DIR"${SOURCE_FILE#"$TMP_LTV_DIR"} + mv -f "$SOURCE_FILE" "$TARGET_FILE" + git add "$TARGET_FILE" +done + +git commit --allow-empty --quiet -F "$COMMIT_MSG_FILE" + +echo_info "Timestamping complete" +exit 0 \ No newline at end of file diff --git a/hooks/timestamping b/hooks/timestamping new file mode 100644 index 0000000..17f2c31 --- /dev/null +++ b/hooks/timestamping @@ -0,0 +1,379 @@ +#!/bin/bash +# +# RFC3161 and RFC5816 Timestamping for git repositories. +# +# Copyright (c) 2020 Mabulous GmbH +# Authors: Matthias Bühlmann +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# The interactive user interfaces in modified source and object code versions +# of this program must display Appropriate Legal Notices, as required under +# Section 5 of the GNU Affero General Public License version 3. +# +# You can be released from the requirements of the license by purchasing +# a commercial license. Buying such a license is mandatory as soon as you +# develop commercial activities involving this software without +# disclosing the source code of your own applications. +# These activities include: offering paid services to customers as an ASP, +# providing data storage and archival services, shipping this software with a +# closed source product. +# +# For more information, please contact Mabulous GmbH at this +# address: info@mabulous.com +# + +#set exit trap to clean up temporary files +TMP_DIR="$(mktemp -d)" +mkdir -p "$TMP_DIR"/ltvdir/certs +mkdir -p "$TMP_DIR"/ltvdir/crls +TMP_LTV_DIR="$TMP_DIR"/ltvdir + +OUT_STREAM=/dev/null +#OUT_STREAM=/dev/stdout + +#echo red text +echo_error() { + local RED='\033[0;31m' + local NO_COLOR='\033[0m' + echo -e "${RED}$1${NO_COLOR}" +} + +#echo yellow text +echo_warning() { + local YELLOW='\033[1;33m' + local NO_COLOR='\033[0m' + echo -e "${YELLOW}$1${NO_COLOR}" +} + +#echo light blue text +echo_info() { + local LIGHT_BLUE='\033[1;34m' + local NO_COLOR='\033[0m' + echo -e "${LIGHT_BLUE}$1${NO_COLOR}" +} + +#echo dark gray text to OUT_STREAM +log() { + local DARK_GRAY='\033[1;30m' + local NO_COLOR='\033[0m' + echo -e "${DARK_GRAY}$1${NO_COLOR}" > "$OUT_STREAM" +} + +exit_trap() { + local EXIT_CODE="$?" + rm -rf -- "$TMP_DIR" + exit "$EXIT_CODE" +} +trap "exit_trap" EXIT + +TOKEN_HEADER="-----BEGIN RFC3161 TOKEN-----" +TOKEN_FOOTER="-----END RFC3161 TOKEN-----" +SUBJECT_LINE="-----TIMESTAMP COMMIT-----" +#get hashing algorithm used by repo +ALGO=$(git rev-parse --show-object-format) +#get directory to store validation data +LTV_DIR=$(git rev-parse --git-dir)/../.timestampltv +#get directory for trusted RootCA certificates +CA_PATH=$(git rev-parse --git-path hooks/trustanchors) +if [ ! -d "$CA_PATH" ]; then + mkdir -p "$CA_PATH" +fi + +# function to extract ESSCertID or ESSCertIDv2 of TSA from token +# param1: path to token in DER encoding +# param2: OUT variable, the ID +get_tsa_cert_id() { + local TOKEN_FILE="$1" + local -n CERT_ID="$2" + log "get_tsa_cert_id for $TOKEN_FILE" + + #this works for both ESSCertID as well as ESSCerrtIDv2 since the version2 identifier is id-smime-aa-signingCertificateV2 + CERT_ID=$(openssl asn1parse -inform DER -in "$TOKEN_FILE" \ + | awk '/:id-smime-aa-signingCertificate/{f=1} f && /\[HEX DUMP\]:/ {print; exit}' \ + | sed 's/^.*\[HEX DUMP\]://1') + if [ -z "$CERT_ID" ]; then + echo "Token $TOKEN_FILE does not contain ESSCertID or ESSCertIDv2 of issuer." + return 1 + fi + return 0 +} + +# function to extract hashing algorithm used in the ESSCertID (always sha1) or ESSCertIDv2 +# param1: path to token in DER encoding +# param2: OUT variable, the hashing algorithm string +get_cert_id_hash_agorithm() { + local TOKEN_FILE="$1" + local -n ALGO_NAME="$2" + log "get_cert_id_hash_agorithm for $TOKEN_FILE" + + local PARSED=$(openssl asn1parse -inform DER -in "$TOKEN_FILE") + + if [[ "$PARSED" == *":id-smime-aa-signingCertificateV2"* ]]; then + #TODO: extract non-default hashing algorithms + ALGO_NAME="sha256" + elif [[ "$PARSED" == *":id-smime-aa-signingCertificate"* ]]; then + ALGO_NAME="sha1" + else + ALGO_NAME="unknown" + fi + return 0 +} + +#function to request a timestamp for a specified digest +# param1: tsa url +# param2: digest +# param3: whether to request certificates to be included (true or false) +# param4: the file to output the token to +request_token() { + local TSA_URL="$1" + local DIGEST="$2" + local REQUEST_CERTS="$3" + local OUTPUT_FILE="$4" + log "request_token for digest $DIGEST from url $TSA_URL. REQUEST_CERTS=$REQUEST_CERTS" + + local CONTENT_TYPE="Content-Type: application/timestamp-query" + local ACCEPT_TYPE="Accept: application/timestamp-reply" + + local REQ_FILE="$TMP_DIR"/token_req.tsq + if [ "$REQUEST_CERTS" = true ]; then + if ! openssl ts -query -cert -digest "$DIGEST" -"$ALGO" -out "$REQ_FILE" &> "$OUT_STREAM"; then + echo "Failed to create token query" + return 1 + fi + else + if ! openssl ts -query -digest "$DIGEST" -"$ALGO" -out "$REQ_FILE" &> "$OUT_STREAM"; then + echo "Failed to create token query" + return 1 + fi + fi + local RESPONSE_FILE="$TMP_DIR"/response.tsr + if ! curl "$TSA_URL" -H "$CONTENT_TYPE" -H "$ACCEPT_TYPE" --data-binary @"$REQ_FILE" --output "$RESPONSE_FILE" &> "$OUT_STREAM"; then + echo "Failed to get response from $TSA_URL" + return 1 + fi + if ! openssl ts -reply -in "$RESPONSE_FILE" -token_out -out "$OUTPUT_FILE" &> "$OUT_STREAM"; then + echo "Not a valid TSA response in file $RESPONSE_FILE" + return 1 + fi + return 0 +} + +#builds a certificate chain for token. The passed token must have been requested with -cert option +# and with matching digest. +# param1: token file. Token must have been requested with -cert option and with digest of param2 +# param2: the digest the token was requested for +# param3: the output file for the chain. It contains all certificates in order, with the first +# one being the TSA cetificate and the last one the self-signed root certificate. +build_certificate_chain_for_token() { + local TOKEN_FILE="$1" + local DIGEST="$2" + local TSA_URL="$3" + local CERT_FILE="$4" + log "build_certificate_chain_for_token for token $TOKEN_FILE with digest $DIGEST and certificate file $CERT_FILE" + + local DUMMY_TOKEN="$TMP_DIR"/dummy_token.tst + local ALL_EXTRACTED_CERTS="$TMP_DIR"/extracted_certs.pem + local CHAIN=() + #if the TSA uses multiple certificates to sign tokens it may take a few attempts to get one containint the proper signer + #TODO: allow to set maximum retry attempts + local SIGNING_CERT_ID="" + get_tsa_cert_id "$TOKEN_FILE" SIGNING_CERT_ID + local CERT_ID_HASH_ALGO="" + get_cert_id_hash_agorithm "$TOKEN_FILE" CERT_ID_HASH_ALGO + for i in {1..10} ;do + #request dummy token. Use current commit digest + request_token "$TSA_URL" "$DIGEST" true "$DUMMY_TOKEN" + + #extract certifcates + openssl pkcs7 -inform DER -in "$DUMMY_TOKEN" -print_certs -outform PEM -out "$ALL_EXTRACTED_CERTS" &> "$OUT_STREAM" + + #remove files from previous runs + rm -f "$TMP_DIR"/*.extracted.pem.cer + rm -f "$TMP_DIR"/cert_chain_*.pem.cer + + #extract all individual certificates from ALL_EXTRACTED_CERTS + cat "$ALL_EXTRACTED_CERTS" \ + | awk '/-----BEGIN CERTIFICATE-----/ { i++; } /-----BEGIN CERTIFICATE-----/, /-----END CERTIFICATE-----/ \ + { print > tmpdir i ".extracted.pem.cer" }' tmpdir="$TMP_DIR/" + + #find cetificate that signed token + while read EXTRACTED_CERT; do + local CERT_ID=$(openssl x509 -inform PEM -in "$EXTRACTED_CERT" -outform DER | openssl dgst -"$CERT_ID_HASH_ALGO" -binary | xxd -p -c 256) + #if openssl ts -verify -digest "$DIGEST" -in "$TOKEN_FILE" -token_in -partial_chain -CAfile "$EXTRACTED_CERT" &> "$OUT_STREAM"; then + if [ "${SIGNING_CERT_ID,,}" == "${CERT_ID,,}" ]; then + #found the signer certificate + CHAIN+=("$TMP_DIR"/cert_chain_"${#CHAIN[@]}".pem.cer) + mv -f "$EXTRACTED_CERT" "${CHAIN[-1]}" + break 2 + fi + done <<< $(ls "$TMP_DIR"/*.extracted.pem.cer) + done + + if [ ${#CHAIN[@]} -eq 0 ]; then + echo "Unable to download token that contains signing cert for this token:" + openssl ts -reply -token_in -token_out -in "$TOKEN_FILE" -text + return 1 + fi + + #iterate until self-signed certificate is reached + while ! openssl verify -CAfile "${CHAIN[-1]}" "${CHAIN[-1]}" &> "$OUT_STREAM"; do + #try to find parent certificate in extracted certs + if ls "$TMP_DIR"/*.extracted.pem.cer &> "$OUT_STREAM"; then + while read EXTRACTED_CERT; do + if openssl verify -partial_chain -CAfile "$EXTRACTED_CERT" "${CHAIN[-1]}" &> "$OUT_STREAM"; then + CHAIN+=("$TMP_DIR"/cert_chain_"${#CHAIN[@]}".pem.cer) + mv -f "$EXTRACTED_CERT" "${CHAIN[-1]}" + continue 2 + fi + done <<< $(ls "$TMP_DIR"/*.extracted.pem.cer) + fi + + #otherwise try to find in trust store + if ls "$CA_PATH"/*.0 &> "$OUT_STREAM"; then + while read TRUSTED_CERT; do + if openssl verify -partial_chain -CAfile "$TRUSTED_CERT" "${CHAIN[-1]}" &> "$OUT_STREAM"; then + CHAIN+=("$TRUSTED_CERT") + continue 2 + fi + done <<< $(ls "$CA_PATH"/*.0) + fi + + #otherwise try to download + local URL=$(openssl x509 -inform PEM -noout -text -in "${CHAIN[-1]}" \ + | awk '/Authority Information Access:/{f=1} f && /CA Issuers - URI:/ {print; exit}' \ + | sed 's/^.*CA Issuers - URI://1') + if [ -z "$URL" ]; then + echo "Certificate ${CHAIN[-1]} does not contain Authority Information Access extension with CA issuer URL. Can't build certificate chain." + return 1 + fi + CHAIN+=("$TMP_DIR"/cert_chain_"${#CHAIN[@]}".pem.cer) + local TMP_DOWNLOAD="$TMP_DIR"/tmp_download.crt + if ! curl "$URL" --output "$TMP_DOWNLOAD" &> "$OUT_STREAM"; then + echo "Failed to download issuer certificate from $URL" + return 1 + fi + #convert from DER to PEM if necessary + if openssl x509 -inform PEM -in "$TMP_DOWNLOAD" -noout &> "$OUT_STREAM"; then + openssl x509 -inform PEM -in "$TMP_DOWNLOAD" -outform PEM -out "${CHAIN[-1]}" + elif openssl x509 -inform DER -in "$TMP_DOWNLOAD" -noout &> "$OUT_STREAM"; then + openssl x509 -inform DER -in "$TMP_DOWNLOAD" -outform PEM -out "${CHAIN[-1]}" + else + echo "Unknown certificate file format for $URL" + return 1 + fi + done + + echo -n > "$CERT_FILE" + for CERT in "${CHAIN[@]}"; do + openssl x509 -in "$CERT" -noout -subject >> "$CERT_FILE" + echo '' >> "$CERT_FILE" + openssl x509 -in "$CERT" -noout -issuer >> "$CERT_FILE" + echo '' >> "$CERT_FILE" + cat "$CERT" >> "$CERT_FILE" + echo '' >> "$CERT_FILE" + done + return 0 +} + +# Tries to download CRLs for the entire chain and store them together in PEM encoding in an output file. +# param1: path to the certificate chain in PEM format +# param2: path to output file +# TODO: performance of this could be improved by using OCSPs to check for changes first +download_crls_for_chain() { + local CERT_FILE="$1" + local OUTPUT_FILE="$2" + log "download_crls_for_chain for certificate file $CERT_FILE and store to $OUTPUT_FILE" + + echo -n > "$OUTPUT_FILE" + local CRL_TMP="$TMP_DIR"/crl_tmp.crl + #remove files from possible previous runs + rm -f "$TMP_DIR"/*.extracted.pem.cer + #extract all contained certificates into separate files + cat "$CERT_FILE" \ + | awk '/-----BEGIN CERTIFICATE-----/ { i++; } /-----BEGIN CERTIFICATE-----/, /-----END CERTIFICATE-----/ \ + { print > tmpdir i ".extracted.pem.cer" }' tmpdir="$TMP_DIR/" + + #iterate over certificates. Ignore self-signed certificates + ls "$TMP_DIR"/*.extracted.pem.cer | while read EXTRACTED_CERT; do + if ! openssl verify -CAfile "$EXTRACTED_CERT" "$EXTRACTED_CERT" &> "$OUT_STREAM"; then + local URL=$(openssl x509 -inform PEM -in $EXTRACTED_CERT -text -noout \ + | awk '/CRL Distribution Points:/{f=1} f && /URI:/ {print; exit}' \ + | sed 's/^.*URI://1') + if curl "$URL" --output "$CRL_TMP" &> "$OUT_STREAM"; then + if openssl crl -in "$CRL_TMP" -inform DER -noout &> "$OUT_STREAM"; then + openssl crl -in "$CRL_TMP" -inform DER >> "$OUTPUT_FILE" + elif openssl crl -in "$CRL_TMP" -inform PEM -noout &> "$OUT_STREAM"; then + openssl crl -in "$CRL_TMP" -inform PEM >> "$OUTPUT_FILE" + else + echo "Unknown CRL file format for $URL" + return 1 + fi + else + echo "Failed to download CRL from $URL" + return 1 + fi + fi + done + return 0 +} + +# Check whether the file containing the certificates to verify the token are available and if not, +# request and add them to the commit. +# param1: path to token in DER encoding +# param2: digest to verify +# param3: tsa url +verify_token_and_add_ltv_data() { + local TOKEN_FILE="$1" + local DIGEST="$2" + local TSA_URL="$3" + log "verify_token_and_add_ltv_data for token $TOKEN_FILE and digest $DIGEST from url $TSA_URL" + + local SIGNING_CERT_ID='' + get_tsa_cert_id "$TOKEN_FILE" SIGNING_CERT_ID + local CERT_CHAIN_FILE="$LTV_DIR"/certs/"$SIGNING_CERT_ID".cer + if [ ! -f "$CERT_CHAIN_FILE" ]; then + CERT_CHAIN_FILE="$TMP_LTV_DIR"/certs/"$SIGNING_CERT_ID".cer + #try to build full chain. + if ! build_certificate_chain_for_token "$TOKEN_FILE" "$DIGEST" "$TSA_URL" "$CERT_CHAIN_FILE"; then + echo "Unable to build certificate chain." + return 1 + fi + if ! openssl verify --CApath "$CA_PATH" -untrusted "$CERT_CHAIN_FILE" "$CERT_CHAIN_FILE" &> "$OUT_STREAM"; then + echo "TSA certificate from $TSA_URL is not trusted. Check your trustanchors in $CA_PATH" + return 1 + fi + fi + #verify token and download CRL data + local CRL_CHAIN_FILE="$TMP_LTV_DIR"/crls/"$SIGNING_CERT_ID.crl" + download_crls_for_chain "$CERT_CHAIN_FILE" "$CRL_CHAIN_FILE" + #verify signing certificate + local TOKEN_TIMESTAMP=$(openssl ts -reply -in "$TOKEN_FILE" -token_in -token_out -text 2> "$OUT_STREAM" \ + | awk '/Time stamp:/{f=1} f {print; exit}' \ + | sed 's/^.*Time stamp: //1') + local TOKEN_UNIXTIME=$(date "+%s" -d "$TOKEN_TIMESTAMP") + #validate signing certificate + if ! openssl verify -attime "$TOKEN_UNIXTIME" -CApath "$CA_PATH" -CRLfile "$CRL_CHAIN_FILE" \ + -crl_check_all -untrusted "$CERT_CHAIN_FILE" "$CERT_CHAIN_FILE" &> "$OUT_STREAM"; then + echo "TSA certificate from $TSA_URL could not be validated." + return 1 + fi + #validate token + if ! openssl ts -verify -digest "$DIGEST" -in "$TOKEN_FILE" -token_in -attime "$TOKEN_UNIXTIME" \ + -CApath "$CA_PATH" -untrusted "$CERT_CHAIN_FILE" 2> "$OUT_STREAM"; then + echo "Token from $TSA_URL could not be validated." + return 1 + fi + return 0 +} \ No newline at end of file diff --git a/hooks/trust.sh b/hooks/trust.sh new file mode 100644 index 0000000..7510137 --- /dev/null +++ b/hooks/trust.sh @@ -0,0 +1,96 @@ +#!/bin/bash +# +# RFC3161 and RFC5816 Timestamping for git repositories. +# +# Copyright (c) 2020 Mabulous GmbH +# Authors: Matthias Bühlmann +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# The interactive user interfaces in modified source and object code versions +# of this program must display Appropriate Legal Notices, as required under +# Section 5 of the GNU Affero General Public License version 3. +# +# You can be released from the requirements of the license by purchasing +# a commercial license. Buying such a license is mandatory as soon as you +# develop commercial activities involving this software without +# disclosing the source code of your own applications. +# These activities include: offering paid services to customers as an ASP, +# providing data storage and archival services, shipping this software with a +# closed source product. +# +# For more information, please contact Mabulous GmbH at this +# address: info@mabulous.com +# + +DIR="${BASH_SOURCE%/*}" +if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi +. "$DIR/timestamping" + +TSA_URL="$1" + +print_usage() { + echo " helper script to add the root certificate of a TSA to the trustanchors" + echo " usage: ./trust.sh " + echo " example: ./trust.sh https://freetsa.org/tsr" + echo " This will add the root certificate for freetsa.org to the trusted" + echo " root certificates. This trust is local to this repository" +} + +if [ -z "$TSA_URL" ]; then + print_usage + exit 1 +fi + +DUMMY_DIGEST=$(echo "0" | git hash-object --stdin) +DUMMY_TOKEN="$TMP_DIR"/token.tst + +request_token "$TSA_URL" "$DUMMY_DIGEST" false "$DUMMY_TOKEN" + +CERTIFICATES="$TMP_DIR"/certificates.pem +build_certificate_chain_for_token "$DUMMY_TOKEN" "$DUMMY_DIGEST" "$TSA_URL" "$CERTIFICATES" + +#extract all individual certificates from CERTIFICATES +NUM_CERTS=$(cat "$CERTIFICATES" \ + | awk '/-----BEGIN CERTIFICATE-----/ { i++; } /-----BEGIN CERTIFICATE-----/, /-----END CERTIFICATE-----/ \ + { print > tmpdir i ".extracted.pem.cer" } END {print i}' tmpdir="$TMP_DIR/") + +ROOT_CERT="$TMP_DIR"/"$NUM_CERTS".extracted.pem.cer + +echo "Verifying that $ROOT_CERT is self signed" +if ! openssl verify -CAfile "$ROOT_CERT" "$ROOT_CERT" &> "$OUT_STREAM"; then + echo_error "Error: could not find root certificate for $TSA_URL" + exit 1 +fi + +HASH=$(openssl x509 -inform PEM -in "$ROOT_CERT" -noout -subject_hash) +TARGET_FILE="$CA_PATH"/"$HASH".0 + +echo_warning "This will add the following certificate to $CA_PATH and it will subsequently be trusted for timestamp tokens." +openssl x509 -inform PEM -in "$ROOT_CERT" -noout -text + +read -r -p "Are you sure? [y/N] " RESPONSE +if [[ "$RESPONSE" =~ ^([yY][eE][sS]|[yY])$ ]]; then + echo -n > "$TARGET_FILE" + openssl x509 -in "$ROOT_CERT" -noout -subject >> "$TARGET_FILE" + echo '' >> "$TARGET_FILE" + openssl x509 -in "$ROOT_CERT" -noout -issuer >> "$TARGET_FILE" + echo '' >> "$TARGET_FILE" + cat "$ROOT_CERT" >> "$TARGET_FILE" + echo '' >> "$TARGET_FILE" + echo_warning "Added cetificate as $TARGET_FILE" +else + echo_warning "Aborted." +fi +exit 0