rexroni
rexroni

Reputation: 443

Time stamp validation, missing certificate

What I need: I need a verifiable time stamp on a file called notes, on my Ubuntu computer using openssl.

What my problem is: I can get a signed timestamp from http://timestamp.globalsign.com/scripts/timstamp.dll but I can't verify it. When I run

openssl ts -verify -data notes -in test.tsr

I get the two-line output (extra line breaks inserted for readability)

Verification: FAILED

3073500860:error:2F06D064:time stamp routines:
TS_VERIFY_CERT:certificate verify error:ts_rsp_verify.c:246:
Verify error:unable to get local issuer certificate

I think what this means is that I need a certificate named "GlobalSign TSA for Standard - G2" (see "output of script" section below), but I have no idea where to find it. I grep'd my /usr, /etc, and /home directories for that string, so I'm pretty confident I actually don't have it. I can't find it on the google. Does anybody know where to get this certificate, or maybe I'm doing something else wrong? And also, why is this so difficult? I see lots of people talking about using this server on various stack exchanges, but I seem to be the only one with this problem.


More info

I am using the default openssl config file at /etc/ssl/openssl.cnf.

I use the following short script to generate a time stamp query test.tsq, then send it to the time stamp server, receive a time stamp reply test.tsr, then try to validate it.

#!/bin/bash
#make time stamp query
openssl ts -query -data notes -sha256 -cert -out test.tsq

#write query to stdout in text format
openssl ts -query -in test.tsq -text

echo ""

#use tsget to get the request, since I don't know how to do it with curl
tsget -h http://timestamp.globalsign.com/scripts/timstamp.dll test.tsq

#write the reply to stdout in text form
openssl ts -reply -in test.tsr -text

echo ""

#verify the timestamp
openssl ts -verify -data notes -in test.tsr

The perl script tsget came with the openssl package I think, and I found it at /usr/lib/ssl/misc/tsget, but I'll include it here for troubleshooting:

#!/usr/bin/perl -w
# Written by Zoltan Glozik <[email protected]>.
# Copyright (c) 2002 The OpenTSA Project.  All rights reserved.
$::version = '$Id: tsget,v 1.3 2009/09/07 17:57:18 steve Exp $';

use strict;
use IO::Handle;
use Getopt::Std;
use File::Basename;
use WWW::Curl::Easy;

use vars qw(%options);

# Callback for reading the body.
sub read_body {
    my ($maxlength, $state) = @_;
    my $return_data = "";
    my $data_len = length ${$state->{data}};
    if ($state->{bytes} < $data_len) {
    $data_len = $data_len - $state->{bytes};
    $data_len = $maxlength if $data_len > $maxlength;
    $return_data = substr ${$state->{data}}, $state->{bytes}, $data_len;
    $state->{bytes} += $data_len;
    }
    return $return_data;
}

# Callback for writing the body into a variable.
sub write_body {
    my ($data, $pointer) = @_;
    ${$pointer} .= $data;
    return length($data);
}

# Initialise a new Curl object.
sub create_curl {
    my $url = shift;

    # Create Curl object.
    my $curl = WWW::Curl::Easy::new();

    # Error-handling related options.
    $curl->setopt(CURLOPT_VERBOSE, 1) if $options{d};
    $curl->setopt(CURLOPT_FAILONERROR, 1);
    $curl->setopt(CURLOPT_USERAGENT, "OpenTSA tsget.pl/" . (split / /, $::version)[2]);

    # Options for POST method.
    $curl->setopt(CURLOPT_UPLOAD, 1);
    $curl->setopt(CURLOPT_CUSTOMREQUEST, "POST");
    $curl->setopt(CURLOPT_HTTPHEADER,
        ["Content-Type: application/timestamp-query",
        "Accept: application/timestamp-reply,application/timestamp-response"]);
    $curl->setopt(CURLOPT_READFUNCTION, \&read_body);
    $curl->setopt(CURLOPT_HEADERFUNCTION, sub { return length($_[0]); });

    # Options for getting the result.
    $curl->setopt(CURLOPT_WRITEFUNCTION, \&write_body);

    # SSL related options.
    $curl->setopt(CURLOPT_SSLKEYTYPE, "PEM");
    $curl->setopt(CURLOPT_SSL_VERIFYPEER, 1);   # Verify server's certificate.
    $curl->setopt(CURLOPT_SSL_VERIFYHOST, 2);   # Check server's CN.
    $curl->setopt(CURLOPT_SSLKEY, $options{k}) if defined($options{k});
    $curl->setopt(CURLOPT_SSLKEYPASSWD, $options{p}) if defined($options{p});
    $curl->setopt(CURLOPT_SSLCERT, $options{c}) if defined($options{c});
    $curl->setopt(CURLOPT_CAINFO, $options{C}) if defined($options{C});
    $curl->setopt(CURLOPT_CAPATH, $options{P}) if defined($options{P});
    $curl->setopt(CURLOPT_RANDOM_FILE, $options{r}) if defined($options{r});
    $curl->setopt(CURLOPT_EGDSOCKET, $options{g}) if defined($options{g});

    # Setting destination.
    $curl->setopt(CURLOPT_URL, $url);

    return $curl;
}

# Send a request and returns the body back.
sub get_timestamp {
    my $curl = shift;
    my $body = shift;
    my $ts_body;
    local $::error_buf;

    # Error-handling related options.
    $curl->setopt(CURLOPT_ERRORBUFFER, "::error_buf");

    # Options for POST method.
    $curl->setopt(CURLOPT_INFILE, {data => $body, bytes => 0});
    $curl->setopt(CURLOPT_INFILESIZE, length(${$body}));

    # Options for getting the result.
    $curl->setopt(CURLOPT_FILE, \$ts_body);

    # Send the request...
    my $error_code = $curl->perform();
    my $error_string;
    if ($error_code != 0) {
        my $http_code = $curl->getinfo(CURLINFO_HTTP_CODE);
    $error_string = "could not get timestamp";
    $error_string .= ", http code: $http_code" unless $http_code == 0;
    $error_string .= ", curl code: $error_code";
    $error_string .= " ($::error_buf)" if defined($::error_buf);
    } else {
        my $ct = $curl->getinfo(CURLINFO_CONTENT_TYPE);
    if (lc($ct) ne "application/timestamp-reply"
        && lc($ct) ne "application/timestamp-response") {
        $error_string = "unexpected content type returned: $ct";
        }
    }
    return ($ts_body, $error_string);

}

# Print usage information and exists.
sub usage {

    print STDERR "usage: $0 -h <server_url> [-e <extension>] [-o <output>] ";
    print STDERR "[-v] [-d] [-k <private_key.pem>] [-p <key_password>] ";
    print STDERR "[-c <client_cert.pem>] [-C <CA_certs.pem>] [-P <CA_path>] ";
    print STDERR "[-r <file:file...>] [-g <EGD_socket>] [<request>]...\n";
    exit 1;
}

# ----------------------------------------------------------------------
#   Main program
# ----------------------------------------------------------------------

# Getting command-line options (default comes from TSGET environment variable).
my $getopt_arg =  "h:e:o:vdk:p:c:C:P:r:g:";
if (exists $ENV{TSGET}) {
    my @old_argv = @ARGV;
    @ARGV = split /\s+/, $ENV{TSGET};
    getopts($getopt_arg, \%options) or usage;
    @ARGV = @old_argv;
}
getopts($getopt_arg, \%options) or usage;

# Checking argument consistency.
if (!exists($options{h}) || (@ARGV == 0 && !exists($options{o}))
    || (@ARGV > 1 && exists($options{o}))) {
    print STDERR "Inconsistent command line options.\n";
    usage;
}
# Setting defaults.
@ARGV = ("-") unless @ARGV != 0;
$options{e} = ".tsr" unless defined($options{e});

# Processing requests.
my $curl = create_curl $options{h};
undef $/;   # For reading whole files.
REQUEST: foreach (@ARGV) {
    my $input = $_;
    my ($base, $path) = fileparse($input, '\.[^.]*');
    my $output_base = $base . $options{e};
    my $output = defined($options{o}) ? $options{o} : $path . $output_base;

    STDERR->printflush("$input: ") if $options{v};
    # Read request.
    my $body;
    if ($input eq "-") {
    # Read the request from STDIN;
    $body = <STDIN>;
    } else {
    # Read the request from file.
        open INPUT, "<" . $input
        or warn("$input: could not open input file: $!\n"), next REQUEST;
        $body = <INPUT>;
        close INPUT
        or warn("$input: could not close input file: $!\n"), next REQUEST;
    }

    # Send request.
    STDERR->printflush("sending request") if $options{v};

    my ($ts_body, $error) = get_timestamp $curl, \$body;
    if (defined($error)) {
    die "$input: fatal error: $error\n";
    }
    STDERR->printflush(", reply received") if $options{v};

    # Write response.
    if ($output eq "-") {
    # Write to STDOUT.
        print $ts_body;
    } else {
    # Write to file.
        open OUTPUT, ">", $output
        or warn("$output: could not open output file: $!\n"), next REQUEST;
        print OUTPUT $ts_body;
        close OUTPUT
        or warn("$output: could not close output file: $!\n"), next REQUEST;
    }
    STDERR->printflush(", $output written.\n") if $options{v};
}
$curl->cleanup();
WWW::Curl::Easy::global_cleanup();

Output of Script

Version: 1
Hash Algorithm: sha256
Message data:
    0000 - a8 31 60 9c a3 fe 14 74-05 f1 be 78 89 5c a6 a5   .1`....t...x.\..
    0010 - d3 b7 4a 7d 18 b9 d0 f9-39 fc a8 d6 e2 be 2e 27   ..J}....9......'
Policy OID: unspecified
Nonce: 0x533AC264C90C4EEE
Certificate required: yes
Extensions:

Undefined subroutine &WWW::Curl::Easy::global_cleanup called at /usr/local/bin/tsget line 196.
Status info:
Status: Granted.
Status description: unspecified
Failure info: unspecified

TST info:
Version: 1
Policy OID: 1.3.6.1.4.1.4146.2.2
Hash Algorithm: sha256
Message data:
    0000 - a8 31 60 9c a3 fe 14 74-05 f1 be 78 89 5c a6 a5   .1`....t...x.\..
    0010 - d3 b7 4a 7d 18 b9 d0 f9-39 fc a8 d6 e2 be 2e 27   ..J}....9......'
Serial number: 0x3A668E2441A3707CB495191DC3EF2D717C49DD30
Time stamp: Sep 25 16:55:34 2015 GMT
Accuracy: unspecified
Ordering: no
Nonce: 0x533AC264C90C4EEE
TSA: DirName:/C=SG/O=GMO GlobalSign Pte Ltd/CN=GlobalSign TSA for Standard - G2
Extensions:

Verification: FAILED
3073967804:error:2F06D064:time stamp routines:TS_VERIFY_CERT:certificate verify error:ts_rsp_verify.c:246:Verify error:unable to get local issuer certificate

Upvotes: 1

Views: 4633

Answers (3)

fred727
fred727

Reputation: 2819

Here is the "GlobalSign Root CA" (found here and here on the official site as "R1 GlobalSign Root Certificate") you need to validate your TSR :

-----BEGIN CERTIFICATE-----
MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG
A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv
b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw
MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i
YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT
aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ
jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp
xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp
1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG
snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ
U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8
9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E
BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B
AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz
yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE
38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP
AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad
DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME
HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==
-----END CERTIFICATE-----

Save it to root.pem

Moreover you need to extract too all the chain of the intermediate certificates used to sign the timestamp response.

First of all, make a request with the -cert option (to include certificate chain) :

#make time stamp query
openssl ts -query -data notes -sha256 -cert -out test.tsq

To get the TSR, Ask the TSA with :

curl -H 'Content-Type: application/timestamp-query' --data-binary '@test.tsq' http://timestamp.globalsign.com/scripts/timstamp.dll > test.tsr

Then extract certificates with :

openssl ts -reply -in test.tsr -token_out | openssl pkcs7 -inform der -print_certs | sed -n '/-----BEGIN/,/-----END/p' > chain.pem

Then you can verify your timestamp response with :

#verify the timestamp
openssl ts -verify -data notes -in test.tsr -CAfile root.pem -untrusted chain.pem

Upvotes: 4

Michael Chourdakis
Michael Chourdakis

Reputation: 11148

Get a timestamp in Windows, then export the certificate chain from the timestamped executable and you have the chain.

Upvotes: 0

rexroni
rexroni

Reputation: 443

Ok, I did find the answer: http://tsa.safecreative.org/

After much googling, I started to get the impression that although posts like this one this one and this one and this one and especially this one made it seem like GlobalSign and Verisign and friends each run a free timestamping server, I am now under the impression that they're not really free. I think it's a free "add on" to some other products they sell, perhaps. It is possible for anybody to get a timestamp from their servers, but I can't validate that timestamp without their certificate, which does not seem to be freely available. If anybody knows otherwise, they are free to correct me.

On the other hand, http://tsa.safecreative.org/ is an actually free website (or, up to 5 stamps per day per IP address free), where anybody can download their certificate to verify the timestamp. That's exactly what I was looking for.

Upvotes: 0

Related Questions