Skip to content

Commit 5b12bd1

Browse files
krisztian-kovacsJonas Maier
authored andcommitted
openssl/ts: add timestamping functionality
This change adds a partial wrapper for the Time-Stamp Protocol (RFC 3161) implementation in OpenSSL. The aim is to have enough coverage to create a client that can interact with a Time Stamp Authority by creating time-stamp requests and verifying responses returned by the authority.
1 parent 43fab3b commit 5b12bd1

File tree

5 files changed

+304
-0
lines changed

5 files changed

+304
-0
lines changed

openssl-sys/src/ts.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ extern "C" {
6161
pub fn TS_REQ_free(a: *mut TS_REQ);
6262
pub fn d2i_TS_REQ(a: *mut *mut TS_REQ, pp: *mut *const c_uchar, length: c_long) -> *mut TS_REQ;
6363
pub fn i2d_TS_REQ(a: *const TS_REQ, pp: *mut *mut c_uchar) -> c_int;
64+
pub fn TS_REQ_set_version(a: *mut TS_REQ, version: c_long) -> c_int;
6465
pub fn TS_REQ_set_msg_imprint(a: *mut TS_REQ, msg_imprint: *mut TS_MSG_IMPRINT) -> c_int;
6566
pub fn TS_REQ_set_nonce(a: *mut TS_REQ, nonce: *const ASN1_INTEGER) -> c_int;
6667
pub fn TS_REQ_set_cert_req(a: *mut TS_REQ, cert_req: c_int) -> c_int;

openssl/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ pub mod ssl;
193193
pub mod stack;
194194
pub mod string;
195195
pub mod symm;
196+
pub mod ts;
196197
pub mod version;
197198
pub mod x509;
198199

openssl/src/ts.rs

Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
//! Partial interface to OpenSSL Time-Stamp Protocol (RFC 3161) implementation.
2+
//!
3+
//! This module provides a partial interface to OpenSSL's TSP implementation.
4+
//! The aim is to provide enough functionality for a client to request and
5+
//! verify timestamps returned by a Time Stamp Authority.
6+
use bitflags::bitflags;
7+
use foreign_types::{ForeignType, ForeignTypeRef};
8+
use libc::{c_int, c_long, c_uint};
9+
10+
use std::ptr;
11+
12+
use crate::asn1::{Asn1IntegerRef, Asn1ObjectRef};
13+
use crate::error::ErrorStack;
14+
use crate::hash::MessageDigest;
15+
use crate::x509::X509Algorithm;
16+
use crate::{cvt, cvt_p};
17+
18+
foreign_type_and_impl_send_sync! {
19+
type CType = ffi::TS_MSG_IMPRINT;
20+
fn drop = ffi::TS_MSG_IMPRINT_free;
21+
22+
/// A message imprint contains the has of the data to be timestamped.
23+
pub struct TsMsgImprint;
24+
25+
/// Reference to `TsMsgImprint`.
26+
pub struct TsMsgImprintRef;
27+
}
28+
29+
impl TsMsgImprint {
30+
/// Creates a new message imprint.
31+
///
32+
/// This corresponds to `TS_MSG_IMPRINT_new`.
33+
pub fn new() -> Result<TsMsgImprint, ErrorStack> {
34+
unsafe {
35+
ffi::init();
36+
let imprint: *mut ffi::TS_MSG_IMPRINT = cvt_p(ffi::TS_MSG_IMPRINT_new())?;
37+
Ok(TsMsgImprint::from_ptr(imprint))
38+
}
39+
}
40+
41+
/// Sets the algorithm identifier of the message digest algorithm.
42+
///
43+
/// This corresponds to `TS_MSG_IMPRINT_set_algo`.
44+
pub fn set_algo(&mut self, digest: &MessageDigest) -> Result<(), ErrorStack> {
45+
unsafe {
46+
let algorithm = X509Algorithm::from_ptr(cvt_p(ffi::X509_ALGOR_new())?);
47+
ffi::X509_ALGOR_set_md(algorithm.as_ptr(), digest.as_ptr());
48+
cvt(ffi::TS_MSG_IMPRINT_set_algo(
49+
self.as_ptr(),
50+
algorithm.as_ptr(),
51+
))
52+
.map(|_| ())
53+
}
54+
}
55+
56+
/// Sets the message digest of the data to be timestamped.
57+
///
58+
/// This corresponds to `TS_MSG_IMPRINT_set_msg`.
59+
pub fn set_msg(&mut self, digest: &[u8]) -> Result<(), ErrorStack> {
60+
let length = convert_digest_length_to_int(digest.len());
61+
unsafe {
62+
cvt(ffi::TS_MSG_IMPRINT_set_msg(
63+
self.as_ptr(),
64+
digest.as_ptr() as *mut _,
65+
length,
66+
))
67+
.map(|_| ())
68+
}
69+
}
70+
}
71+
72+
fn convert_digest_length_to_int(len: usize) -> c_int {
73+
if len > std::i32::MAX as usize {
74+
panic!("Digest length is too large");
75+
} else {
76+
len as i32
77+
}
78+
}
79+
80+
foreign_type_and_impl_send_sync! {
81+
type CType = ffi::TS_REQ;
82+
fn drop = ffi::TS_REQ_free;
83+
84+
/// A timestamp request.
85+
pub struct TsReq;
86+
87+
/// Reference to `TsReq`.
88+
pub struct TsReqRef;
89+
}
90+
91+
impl TsReq {
92+
from_der! {
93+
/// Deserializes a DER-encoded TimeStampReq structure.
94+
///
95+
/// This corresponds to [`d2i_TS_REQ`].
96+
///
97+
/// [`d2i_TS_REQ`]: https://www.openssl.org/docs/man1.1.0/man3/d2i_TS_REQ.html
98+
from_der,
99+
TsReq,
100+
ffi::d2i_TS_REQ
101+
}
102+
}
103+
104+
impl TsReqRef {
105+
to_der! {
106+
/// Serializes the timestamp request into a DER-encoded TimeStampReq structure.
107+
///
108+
/// This corresponds to [`i2d_TS_REQ`].
109+
///
110+
/// [`i2d_TS_REQ`]: https://www.openssl.org/docs/man1.1.0/man3/i2d_TS_REQ.html
111+
to_der,
112+
ffi::i2d_TS_REQ
113+
}
114+
}
115+
116+
impl TsReq {
117+
/// Creates a new timestamp request.
118+
///
119+
/// This corresponds to `TS_REQ_new`.
120+
pub fn new() -> Result<TsReq, ErrorStack> {
121+
unsafe {
122+
ffi::init();
123+
let req: *mut ffi::TS_REQ = cvt_p(ffi::TS_REQ_new())?;
124+
Ok(TsReq::from_ptr(req))
125+
}
126+
}
127+
128+
/// Set the version of the timestamp request.
129+
///
130+
/// RFC 3161 requires this to be 1.
131+
///
132+
/// This corresponds to `TS_REQ_set_version`.
133+
pub fn set_version(&mut self, version: c_long) -> Result<(), ErrorStack> {
134+
unsafe { cvt(ffi::TS_REQ_set_version(self.as_ptr(), version)).map(|_| ()) }
135+
}
136+
137+
/// Set the message imprint.
138+
///
139+
/// This corresponds to `TS_REQ_set_msg_imprint`.
140+
pub fn set_msg_imprint(&mut self, imprint: &TsMsgImprintRef) -> Result<(), ErrorStack> {
141+
unsafe { cvt(ffi::TS_REQ_set_msg_imprint(self.as_ptr(), imprint.as_ptr())).map(|_| ()) }
142+
}
143+
144+
/// Sets the OID of the policy under which we're requesting the timestamp.
145+
///
146+
/// This corresponds to `TS_REQ_set_policy_id`.
147+
pub fn set_policy_id(&mut self, policy: &Asn1ObjectRef) -> Result<(), ErrorStack> {
148+
unsafe { cvt(ffi::TS_REQ_set_policy_id(self.as_ptr(), policy.as_ptr())).map(|_| ()) }
149+
}
150+
151+
/// Sets the nonce.
152+
///
153+
/// This corresopnds to `TS_REQ_set_nonce`.
154+
pub fn set_nonce(&mut self, nonce: &Asn1IntegerRef) -> Result<(), ErrorStack> {
155+
unsafe { cvt(ffi::TS_REQ_set_nonce(self.as_ptr(), nonce.as_ptr())).map(|_| ()) }
156+
}
157+
158+
/// Sets whether to request the public key certificate in the response.
159+
///
160+
/// This corresponds to `TS_REQ_set_cert_req`.
161+
pub fn set_cert_req(&mut self, cert_req: bool) -> Result<(), ErrorStack> {
162+
unsafe { cvt(ffi::TS_REQ_set_cert_req(self.as_ptr(), cert_req as c_int)).map(|_| ()) }
163+
}
164+
}
165+
166+
foreign_type_and_impl_send_sync! {
167+
type CType = ffi::TS_RESP;
168+
fn drop = ffi::TS_RESP_free;
169+
170+
/// A time-stamping response.
171+
pub struct TsResp;
172+
173+
/// Reference to `TsResp`.
174+
pub struct TsRespRef;
175+
}
176+
177+
impl TsResp {
178+
from_der! {
179+
/// Deserializes a DER-encoded TimeStampResp structure.
180+
///
181+
/// This corresponds to [`d2i_TS_RESP`].
182+
///
183+
/// [`d2i_TS_RESP`]: https://www.openssl.org/docs/man1.1.0/man3/d2i_TS_RESP.html
184+
from_der,
185+
TsResp,
186+
ffi::d2i_TS_RESP
187+
}
188+
}
189+
190+
impl TsRespRef {
191+
to_der! {
192+
/// Serializes the timestamp request into a DER-encoded TimeStampResp structure.
193+
///
194+
/// This corresponds to [`i2d_TS_RESP`].
195+
///
196+
/// [`i2d_TS_RESP`]: https://www.openssl.org/docs/man1.1.0/man3/i2d_TS_RESP.html
197+
to_der,
198+
ffi::i2d_TS_RESP
199+
}
200+
201+
/// Verifies a timestamp response.
202+
///
203+
/// This corresponds to `TS_RESP_verify_response`.
204+
pub fn verify(&self, context: &TsVerifyContext) -> Result<(), ErrorStack> {
205+
unsafe {
206+
cvt(ffi::TS_RESP_verify_response(
207+
context.as_ptr(),
208+
self.as_ptr(),
209+
))
210+
.map(|_| ())
211+
}
212+
}
213+
}
214+
215+
bitflags! {
216+
/// Flags controlling timestamp verification behaviour.
217+
pub struct VerifyFlags: c_uint {
218+
const SIGNATURE = ffi::TS_VFY_SIGNATURE;
219+
const VERSION = ffi::TS_VFY_VERSION;
220+
const POLICY = ffi::TS_VFY_POLICY;
221+
const IMPRINT = ffi::TS_VFY_IMPRINT;
222+
const DATA = ffi::TS_VFY_DATA;
223+
const NONCE = ffi::TS_VFY_NONCE;
224+
const SIGNER = ffi::TS_VFY_SIGNER;
225+
const TSA_NAME = ffi::TS_VFY_TSA_NAME;
226+
227+
const ALL_IMPRINT = ffi::TS_VFY_ALL_IMPRINT;
228+
const ALL_DATA = ffi::TS_VFY_ALL_DATA;
229+
}
230+
}
231+
232+
foreign_type_and_impl_send_sync! {
233+
type CType = ffi::TS_VERIFY_CTX;
234+
fn drop = ffi::TS_VERIFY_CTX_free;
235+
236+
/// A context object specifying time-stamping response verification parameters.
237+
pub struct TsVerifyContext;
238+
239+
/// Reference to `TsVerifyContext`.
240+
pub struct TsVerifyContextRef;
241+
}
242+
243+
impl TsVerifyContext {
244+
/// Construct a verify context from a timestamping request.
245+
///
246+
/// Corresponds to `TS_REQ_to_TS_VERIFY_CTX`.
247+
pub fn from_req(request: &TsReqRef) -> Result<TsVerifyContext, ErrorStack> {
248+
unsafe {
249+
let ctx = cvt_p(ffi::TS_REQ_to_TS_VERIFY_CTX(
250+
request.as_ptr(),
251+
ptr::null_mut(),
252+
))?;
253+
Ok(TsVerifyContext::from_ptr(ctx))
254+
}
255+
}
256+
}
257+
258+
#[cfg(test)]
259+
mod tests {
260+
use super::*;
261+
262+
use crate::asn1::Asn1Integer;
263+
use crate::bn::BigNum;
264+
use crate::sha::sha512;
265+
266+
#[test]
267+
fn test_request() {
268+
let mut imprint = TsMsgImprint::new().unwrap();
269+
imprint.set_algo(&MessageDigest::sha512()).unwrap();
270+
imprint.set_msg(&sha512(b"BLAHBLAHBLAH\n")).unwrap();
271+
272+
let mut request = TsReq::new().unwrap();
273+
request.set_version(1).unwrap();
274+
request.set_msg_imprint(&imprint).unwrap();
275+
request.set_cert_req(true).unwrap();
276+
let nonce =
277+
Asn1Integer::from_bn(&BigNum::from_hex_str("F3AA393032C93DC1").unwrap()).unwrap();
278+
request.set_nonce(&nonce).unwrap();
279+
280+
let der = request.to_der().unwrap();
281+
282+
let request = TsReq::from_der(&der).unwrap();
283+
assert_eq!(request.to_der().unwrap(), der);
284+
}
285+
286+
#[test]
287+
fn test_response_der_serialization() {
288+
let original_der = include_bytes!("../test/ts-response.der").to_vec();
289+
let response = TsResp::from_der(&original_der).unwrap();
290+
let der = response.to_der().unwrap();
291+
assert_eq!(der, original_der);
292+
}
293+
294+
#[test]
295+
fn test_verify() {
296+
let request = TsReq::from_der(include_bytes!("../test/ts-request.der")).unwrap();
297+
let response = TsResp::from_der(include_bytes!("../test/ts-response.der")).unwrap();
298+
299+
let context = TsVerifyContext::from_req(&request).unwrap();
300+
response.verify(&context).unwrap();
301+
}
302+
}

openssl/test/ts-request.der

102 Bytes
Binary file not shown.

openssl/test/ts-response.der

4.18 KB
Binary file not shown.

0 commit comments

Comments
 (0)