#!/usr/bin/perl use strict; use warnings; use Getopt::Long; use Pod::Usage; use File::Spec; use FindBin; use lib "$FindBin::Bin/../lib"; use Crypt::TSD; use LWP::UserAgent; use Digest::SHA qw(sha512); # Command line options my $help = 0; my $version = 0; my $output_file; my $hash_algorithm = 'sha512'; my $verbose = 0; # Parse command line options GetOptions( 'help|h' => \$help, 'version' => \$version, 'output|o=s' => \$output_file, 'algorithm|a=s' => \$hash_algorithm, 'verbose|v' => \$verbose, ) or pod2usage(2); # Show help if ($help) { pod2usage(-verbose => 2); exit 0; } # Show version if ($version) { print "tsd-timestamp version $Crypt::TSD::VERSION\n"; exit 0; } # Get input file from command line my $input_file = shift @ARGV; if (!$input_file) { print "Error: Input file required\n"; pod2usage(1); } # Check if input file exists unless (-f $input_file) { die "Error: Input file '$input_file' not found\n"; } # Generate output filename if not provided unless ($output_file) { $output_file = $input_file; $output_file =~ s/\.([^.]+)$/.tsr/; } print "Getting timestamp from FreeTSA...\n" if $verbose; print "Input file: $input_file\n" if $verbose; print "Output file: $output_file\n" if $verbose; print "Hash algorithm: $hash_algorithm\n" if $verbose; # Read input file my $file_data; { open my $fh, '<:raw', $input_file or die "Cannot open input file '$input_file': $!\n"; local $/; $file_data = <$fh>; close $fh; } print "File size: " . length($file_data) . " bytes\n" if $verbose; # Create timestamp request my $tsq_file = $input_file . '.tsq'; my $tsq_cmd = "openssl ts -query -data '$input_file' -no_nonce -$hash_algorithm -cert -out '$tsq_file'"; print "Creating timestamp request...\n" if $verbose; print "Command: $tsq_cmd\n" if $verbose; system($tsq_cmd) == 0 or die "Failed to create timestamp request: $!\n"; # Send request to FreeTSA print "Sending request to FreeTSA...\n" if $verbose; my $ua = LWP::UserAgent->new(timeout => 30); my $response = $ua->post('https://freetsa.org/tsr', 'Content-Type' => 'application/timestamp-query', Content => do { open my $fh, '<:raw', $tsq_file or die "Cannot read TSQ file: $!\n"; local $/; <$fh>; } ); unless ($response->is_success) { die "Failed to get timestamp from FreeTSA: " . $response->status_line . "\n"; } # Save timestamp response open my $fh, '>:raw', $output_file or die "Cannot create output file '$output_file': $!\n"; print $fh $response->content; close $fh; print "Timestamp received and saved to: $output_file\n"; print "Timestamp size: " . length($response->content) . " bytes\n" if $verbose; # Clean up TSQ file unlink $tsq_file if -f $tsq_file; print "Done!\n"; __END__ =head1 NAME tsd-timestamp - Get a timestamp from FreeTSA for a file =head1 SYNOPSIS tsd-timestamp [options] input_file Options: -o, --output FILE Output .tsr file (default: input_file.tsr) -a, --algorithm ALG Hash algorithm (default: sha512) -v, --verbose Verbose output -h, --help Show this help --version Show version =head1 DESCRIPTION Gets a timestamp from FreeTSA.org for a file. The file is hashed using the specified algorithm and sent to FreeTSA to get a timestamp token. =head1 EXAMPLES # Get timestamp for a file tsd-timestamp document.pdf # Specify output file tsd-timestamp -o my_timestamp.tsr document.pdf # Use different hash algorithm tsd-timestamp -a sha256 document.pdf # Verbose output tsd-timestamp -v document.pdf =head1 SEE ALSO L, L =cut