ouisync/network/
runtime_id.rs

1//! These structures are used to generate ephemeral id that uniquely identifies a replica. Changes
2//! every time the replica is restarted. The cryptography involved is to ensure one replica can't
3//! claim to be another one.
4
5use crate::crypto::{
6    sign::{Keypair, PublicKey, Signature},
7    Digest, Hashable,
8};
9use ouisync_macros::api;
10use rand::{rngs::OsRng, CryptoRng, Rng};
11use serde::{Deserialize, Serialize};
12use std::io;
13use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
14
15pub struct SecretRuntimeId {
16    keypair: Keypair,
17}
18
19impl SecretRuntimeId {
20    pub fn generate<R: Rng + CryptoRng>(rng: &mut R) -> Self {
21        Self {
22            keypair: Keypair::generate(rng),
23        }
24    }
25
26    pub fn random() -> Self {
27        Self {
28            keypair: Keypair::random(),
29        }
30    }
31
32    pub fn public(&self) -> PublicRuntimeId {
33        PublicRuntimeId {
34            public: self.keypair.public_key(),
35        }
36    }
37}
38
39impl From<Keypair> for SecretRuntimeId {
40    fn from(keypair: Keypair) -> Self {
41        Self { keypair }
42    }
43}
44
45#[derive(PartialEq, Eq, Ord, PartialOrd, Hash, Clone, Copy, Deserialize, Serialize, Debug)]
46#[serde(transparent)]
47#[api(repr(Bytes))]
48pub struct PublicRuntimeId {
49    public: PublicKey,
50}
51
52impl PublicRuntimeId {
53    async fn read_from<R>(io: &mut R) -> io::Result<Self>
54    where
55        R: AsyncRead + Unpin,
56    {
57        let bytes = read_bytes::<{ PublicKey::SIZE }, R>(io).await?;
58        Ok(Self {
59            public: bytes
60                .as_slice()
61                .try_into()
62                .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?,
63        })
64    }
65
66    async fn write_into<W>(&self, io: &mut W) -> io::Result<()>
67    where
68        W: AsyncWrite + Unpin,
69    {
70        io.write_all(self.public.as_ref()).await
71    }
72
73    pub fn as_public_key(&self) -> &PublicKey {
74        &self.public
75    }
76}
77
78impl AsRef<[u8]> for PublicRuntimeId {
79    fn as_ref(&self) -> &[u8] {
80        self.public.as_ref()
81    }
82}
83
84impl Hashable for PublicRuntimeId {
85    fn update_hash<S: Digest>(&self, state: &mut S) {
86        self.public.update_hash(state)
87    }
88}
89
90pub async fn exchange<W, R>(
91    our_runtime_id: &SecretRuntimeId,
92    writer: &mut W,
93    reader: &mut R,
94) -> io::Result<PublicRuntimeId>
95where
96    W: AsyncWrite + Unpin,
97    R: AsyncRead + Unpin,
98{
99    let our_challenge: [u8; 32] = OsRng.r#gen();
100
101    writer.write_all(&our_challenge).await?;
102    our_runtime_id.public().write_into(writer).await?;
103
104    let their_challenge: [_; 32] = read_bytes(reader).await?;
105    let their_runtime_id = PublicRuntimeId::read_from(reader).await?;
106
107    let our_signature = our_runtime_id.keypair.sign(&to_sign(&their_challenge));
108
109    writer.write_all(&our_signature.to_bytes()).await?;
110
111    let their_signature: [_; Signature::SIZE] = read_bytes(reader).await?;
112    let their_signature = Signature::from(&their_signature);
113
114    if !their_runtime_id
115        .public
116        .verify(&to_sign(&our_challenge), &their_signature)
117    {
118        return Err(io::Error::other("Failed to verify runtime ID"));
119    }
120
121    Ok(their_runtime_id)
122}
123
124const TO_SIGN_PREFIX: &[u8; 10] = b"runtime-id";
125
126fn to_sign(buf: &[u8; 32]) -> [u8; 32 + TO_SIGN_PREFIX.len()] {
127    let mut out = [0u8; 32 + TO_SIGN_PREFIX.len()];
128    out[..TO_SIGN_PREFIX.len()].clone_from_slice(TO_SIGN_PREFIX);
129    out[TO_SIGN_PREFIX.len()..].clone_from_slice(buf);
130    out
131}
132
133async fn read_bytes<const N: usize, R>(io: &mut R) -> io::Result<[u8; N]>
134where
135    R: AsyncRead + Unpin,
136{
137    let mut out = [0u8; N];
138    io.read_exact(&mut out).await?;
139    Ok(out)
140}