1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
use simple_asn1::{ASN1Block, BigUint, OID};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum TimestampApiError {
#[error("http client failure: {}", _0)]
HttpClient(#[source] reqwest::Error),
#[error("api rejected request: {}", _0)]
Remote(#[source] reqwest::Error),
#[error("failed to encore timestamp request: {}", _0)]
RequestEncoding(#[from] simple_asn1::ASN1EncodeErr),
#[error("failure receiving response: {}", _0)]
Response(#[source] reqwest::Error),
}
#[cfg(feature = "file")]
#[derive(Debug, Error)]
pub enum TimestampFileError {
#[error("failed to read file: {}", _0)]
FileIo(#[source] std::io::Error),
#[error("{}", _0)]
Api(#[from] TimestampApiError),
}
#[cfg(feature = "file")]
pub async fn timestamp_file(
path: impl AsRef<std::path::Path>,
) -> Result<TimestampResponse, TimestampFileError> {
use sha2::{Digest, Sha512};
let file = tokio::fs::read(path)
.await
.map_err(TimestampFileError::FileIo)?;
let mut hasher = Sha512::new();
hasher.update(file);
let hash = hasher.finalize();
Ok(timestamp_hash(hash.to_vec()).await?)
}
pub async fn timestamp_hash(hash: Vec<u8>) -> Result<TimestampResponse, TimestampApiError> {
let sha512_oid: Vec<BigUint> = [2u16, 16, 840, 1, 101, 3, 4, 2, 3]
.into_iter()
.map(Into::into)
.collect::<Vec<_>>();
let req = ASN1Block::Sequence(
3,
vec![
ASN1Block::Integer(1, 1.into()),
ASN1Block::Sequence(
2,
vec![
ASN1Block::Sequence(
2,
vec![
ASN1Block::ObjectIdentifier(1, OID::new(sha512_oid)),
ASN1Block::Null(1),
],
),
ASN1Block::OctetString(1, hash),
],
),
ASN1Block::Boolean(1, true),
],
);
let req = simple_asn1::to_der(&req)?;
let client = reqwest::ClientBuilder::new()
.build()
.map_err(TimestampApiError::HttpClient)?;
let response = client
.post("https://freetsa.org/tsr")
.header("content-type", "application/timestamp-query")
.body(req.clone())
.send()
.await
.map_err(TimestampApiError::Remote)?;
let payload = response
.bytes()
.await
.map_err(TimestampApiError::Response)?;
Ok(TimestampResponse {
query: req,
reply: payload.into(),
})
}
pub struct TimestampResponse {
pub query: Vec<u8>,
pub reply: Vec<u8>,
}
pub mod prelude {
pub use super::timestamp_hash;
pub use super::TimestampResponse;
#[cfg(feature = "file")]
pub use super::timestamp_file;
}