Reputation: 443
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
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
Reputation: 11148
Get a timestamp in Windows, then export the certificate chain from the timestamped executable and you have the chain.
Upvotes: 0
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