#[cfg(unix)]
fn main() {
unix::run();
}
#[cfg(not(unix))]
fn main() {
eprintln!("this example uses unix sockets to model the device; unix only");
}
#[cfg(unix)]
mod unix {
use std::io::{Read, Write};
use std::os::fd::AsRawFd;
use std::os::unix::net::UnixStream;
use std::sync::Arc;
use purecrypto::ec::{BoxedEcdsaPrivateKey, CurveId};
use purecrypto::hash::Sha256;
use purecrypto::rng::HmacDrbg;
use purecrypto::tls::{
Config, Connection, HandshakeSigner, Readiness, SignOp, SignProgress, Step,
};
use purecrypto::x509::{CertSigner, Certificate, DistinguishedName, Time, Validity};
const ECDSA_SECP256R1_SHA256: u16 = 0x0403;
struct DeviceKey {
key: BoxedEcdsaPrivateKey,
}
impl HandshakeSigner for DeviceKey {
fn schemes(&self) -> Vec<u16> {
vec![ECDSA_SECP256R1_SHA256]
}
fn start_sign(
&self,
_scheme: u16,
message: &[u8],
) -> Result<Box<dyn SignOp>, purecrypto::tls::Error> {
let (near, far) = UnixStream::pair().expect("socketpair");
near.set_nonblocking(true).expect("nonblocking");
let key = self.key.clone();
let msg = message.to_vec();
std::thread::spawn(move || {
let mut far = far;
std::thread::sleep(std::time::Duration::from_millis(5));
let sig = key
.sign::<Sha256>(&msg)
.expect("device sign")
.to_der(CurveId::P256);
let len = u16::try_from(sig.len()).expect("sig fits u16");
let _ = far.write_all(&len.to_be_bytes());
let _ = far.write_all(&sig);
});
Ok(Box::new(DeviceOp {
near,
buf: Vec::new(),
}))
}
}
struct DeviceOp {
near: UnixStream,
buf: Vec<u8>,
}
impl SignOp for DeviceOp {
fn resume(&mut self) -> Result<SignProgress, purecrypto::tls::Error> {
let mut chunk = [0u8; 256];
loop {
match self.near.read(&mut chunk) {
Ok(0) => break, Ok(n) => self.buf.extend_from_slice(&chunk[..n]),
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => break,
Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
Err(_) => return Err(purecrypto::tls::Error::HandshakeFailure),
}
}
if self.buf.len() < 2 {
return Ok(SignProgress::Pending);
}
let len = u16::from_be_bytes([self.buf[0], self.buf[1]]) as usize;
if self.buf.len() < 2 + len {
return Ok(SignProgress::Pending); }
Ok(SignProgress::Done(self.buf[2..2 + len].to_vec()))
}
fn readiness(&self) -> Option<Readiness> {
Some(Readiness::from_raw_fd(self.near.as_raw_fd()))
}
}
pub(crate) fn run() {
let mut kg = HmacDrbg::<Sha256>::new(b"driven-example", b"nonce", &[]);
let key = BoxedEcdsaPrivateKey::generate(CurveId::P256, &mut kg);
let name = DistinguishedName::common_name("device.example");
let validity = Validity::new(
Time::utc(2024, 1, 1, 0, 0, 0),
Time::utc(2034, 1, 1, 0, 0, 0),
);
let cert = Certificate::self_signed_general(
&CertSigner::Ecdsa(&key),
&name,
&validity,
1,
false,
&["device.example"],
)
.unwrap();
let cert_der = cert.to_der().to_vec();
let server_cfg = Config::builder()
.rng(std::sync::Arc::new(purecrypto::rng::OsRng))
.tls_only()
.private_key(vec![cert_der], Arc::new(DeviceKey { key }))
.build();
let mut server = Connection::server(&server_cfg).expect("server config");
let client_cfg = Config::builder()
.rng(std::sync::Arc::new(purecrypto::rng::OsRng))
.tls_only()
.verify_certificates(false)
.server_name("device.example")
.build();
let mut client = Connection::client(&client_cfg).expect("client config");
for _ in 0..32 {
loop {
let out = client.pop().unwrap();
if out.is_empty() {
break;
}
server.feed(&out).unwrap();
}
loop {
match server.drive().expect("drive") {
Step::WantWrite => {
let out = server.pop().unwrap();
if out.is_empty() {
break;
}
client.feed(&out).unwrap();
}
Step::WantSigner(Some(r)) => {
println!("server waiting on signing device (fd {})", r.as_raw_fd());
r.wait().expect("wait on device");
}
Step::WantSigner(None) => {}
Step::WantRead | Step::Complete => break,
_ => break,
}
}
if client.is_handshake_complete() && server.is_handshake_complete() {
break;
}
}
assert!(client.is_handshake_complete() && server.is_handshake_complete());
println!("handshake complete — identity signed by the (mock) device");
client.send(b"ping").unwrap();
let req = client.pop().unwrap();
server.feed(&req).unwrap();
println!(
"server received: {:?}",
String::from_utf8_lossy(&server.recv().unwrap_or_default())
);
}
}