OXYGENHUB
# b288cd254343 oxygen / crates / oxy-core / src / identity.rs
13.2 KiB · Rust · last changed Identities and composite Ed25519 + ML-DSA-65 signatures; oxy identity, verify, config 1 month ago source blame history raw
1
//! Identities and composite Ed25519 + ML-DSA-65 signatures (spec §14.2, §14.3).
2
//!
3
//! Both halves sign the same message, which binds the composite `KeyId`, and
4
//! verification requires both. A signature with either half missing, wrong,
5
//! or made for a different KeyId is invalid.
6
7
use crate::cbor::{self, Value};
8
use crate::hash::{hash_object, KeyId, ObjectRef, ObjectType};
9
use crate::objects::{array, bytes, check_version, field, oref, text, uint, ObjectError, FORMAT_VERSION};
10
use ed25519_dalek::ed25519::signature::Signer as _;
11
use ed25519_dalek::{Signature as EdSignature, SigningKey as EdSigningKey, VerifyingKey as EdVerifyingKey};
12
use ml_dsa::{EncodedSignature, EncodedVerifyingKey, MlDsa65, Seed, Signature as MlSignature, SigningKey as MlSigningKey, VerifyingKey as MlVerifyingKey};
13
use rand::RngCore;
14
15
pub const ED25519_PK_LEN: usize = 32;
16
pub const MLDSA65_PK_LEN: usize = 1952;
17
pub const ED25519_SIG_LEN: usize = 64;
18
pub const MLDSA65_SIG_LEN: usize = 3309;
19
/// `alg` value for Ed25519 + ML-DSA-65.
20
pub const ALG_COMPOSITE: u64 = 1;
21
22
#[derive(Clone, Debug, PartialEq, Eq)]
23
pub struct Identity {
24
    pub ed25519_pk: [u8; ED25519_PK_LEN],
25
    pub mldsa65_pk: Vec<u8>,
26
    pub name: String,
27
    pub email: String,
28
    pub created: u64,
29
    pub expires: Option<u64>,
30
    pub previous: Option<KeyId>,
31
    pub delegates: Vec<KeyId>,
32
}
33
34
impl Identity {
35
    pub fn to_value(&self) -> Value {
36
        Value::map([
37
            (0, Value::Uint(FORMAT_VERSION)),
38
            (1, Value::Bytes(self.ed25519_pk.to_vec())),
39
            (2, Value::Bytes(self.mldsa65_pk.clone())),
40
            (3, Value::Text(self.name.clone())),
41
            (4, Value::Text(self.email.clone())),
42
            (5, Value::Uint(self.created)),
43
            (6, self.expires.map(Value::Uint).unwrap_or(Value::Null)),
44
            (7, self.previous.map(|p| Value::Bytes(p.0.to_vec())).unwrap_or(Value::Null)),
45
            (8, Value::Array(self.delegates.iter().map(|d| Value::Bytes(d.0.to_vec())).collect())),
46
        ])
47
    }
48
    pub fn encode(&self) -> Vec<u8> {
49
        cbor::encode(&self.to_value())
50
    }
51
    pub fn hash(&self) -> KeyId {
52
        hash_object(ObjectType::Identity, &self.encode())
53
    }
54
    pub fn decode(b: &[u8]) -> Result<Identity, ObjectError> {
55
        let v = cbor::decode(b)?;
56
        let m = v.as_map().ok_or_else(|| ObjectError::Schema("identity: expected map".into()))?;
57
        check_version(m)?;
58
        let ed = bytes(field(m, 1, "ed25519_pk")?, "ed25519_pk")?;
59
        let ml = bytes(field(m, 2, "mldsa65_pk")?, "mldsa65_pk")?;
60
        if ed.len() != ED25519_PK_LEN || ml.len() != MLDSA65_PK_LEN {
61
            return Err(ObjectError::Schema("identity: wrong public key length".into()));
62
        }
63
        let opt_u = |v: &Value| -> Result<Option<u64>, ObjectError> { if v.is_null() { Ok(None) } else { uint(v, "expires").map(Some) } };
64
        let opt_r = |v: &Value| -> Result<Option<KeyId>, ObjectError> { if v.is_null() { Ok(None) } else { oref(v, "previous").map(Some) } };
65
        let mut delegates = array(field(m, 8, "delegates")?, "delegates")?.iter().map(|d| oref(d, "delegate")).collect::<Result<Vec<_>, _>>()?;
66
        delegates.sort();
67
        delegates.dedup();
68
        Ok(Identity {
69
            ed25519_pk: ed.try_into().unwrap(),
70
            mldsa65_pk: ml.to_vec(),
71
            name: text(field(m, 3, "name")?, "name")?.to_owned(),
72
            email: text(field(m, 4, "email")?, "email")?.to_owned(),
73
            created: uint(field(m, 5, "created")?, "created")?,
74
            expires: opt_u(field(m, 6, "expires")?)?,
75
            previous: opt_r(field(m, 7, "previous")?)?,
76
            delegates,
77
        })
78
    }
79
80
    fn ed_key(&self) -> Result<EdVerifyingKey, VerifyError> {
81
        EdVerifyingKey::from_bytes(&self.ed25519_pk).map_err(|_| VerifyError::BadKey)
82
    }
83
    fn ml_key(&self) -> Result<MlVerifyingKey<MlDsa65>, VerifyError> {
84
        let enc = EncodedVerifyingKey::<MlDsa65>::try_from(self.mldsa65_pk.as_slice()).map_err(|_| VerifyError::BadKey)?;
85
        Ok(MlVerifyingKey::decode(&enc))
86
    }
87
}
88
89
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
90
pub enum Domain {
91
    Change,
92
    Attest,
93
}
94
95
impl Domain {
96
    pub fn name(self) -> &'static str {
97
        match self {
98
            Domain::Change => "change",
99
            Domain::Attest => "attest",
100
        }
101
    }
102
}
103
104
/// `"oxy1 sig " || domain || 0x00 || KeyId || target` (spec §14.3).
105
pub fn signing_message(domain: Domain, key: &KeyId, target: &ObjectRef) -> Vec<u8> {
106
    let mut m = Vec::with_capacity(80);
107
    m.extend_from_slice(b"oxy1 sig ");
108
    m.extend_from_slice(domain.name().as_bytes());
109
    m.push(0);
110
    m.extend_from_slice(&key.0);
111
    m.extend_from_slice(&target.0);
112
    m
113
}
114
115
#[derive(Clone, PartialEq, Eq)]
116
pub struct Signature {
117
    pub key: KeyId,
118
    pub ed: [u8; ED25519_SIG_LEN],
119
    pub ml: Vec<u8>,
120
}
121
122
impl std::fmt::Debug for Signature {
123
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
124
        write!(f, "Signature(key {:?})", self.key)
125
    }
126
}
127
128
impl Signature {
129
    pub fn to_value(&self) -> Value {
130
        Value::map([
131
            (0, Value::Bytes(self.key.0.to_vec())),
132
            (1, Value::Uint(ALG_COMPOSITE)),
133
            (2, Value::Bytes(self.ed.to_vec())),
134
            (3, Value::Bytes(self.ml.clone())),
135
        ])
136
    }
137
    pub fn from_value(v: &Value) -> Result<Signature, ObjectError> {
138
        let m = v.as_map().ok_or_else(|| ObjectError::Schema("signature: expected map".into()))?;
139
        let alg = uint(field(m, 1, "alg")?, "alg")?;
140
        if alg != ALG_COMPOSITE {
141
            return Err(ObjectError::Schema(format!("unknown signature algorithm {alg}")));
142
        }
143
        let ed = bytes(field(m, 2, "ed")?, "ed")?;
144
        let ml = bytes(field(m, 3, "ml")?, "ml")?;
145
        if ed.len() != ED25519_SIG_LEN || ml.len() != MLDSA65_SIG_LEN {
146
            return Err(ObjectError::Schema("signature: wrong length".into()));
147
        }
148
        Ok(Signature { key: oref(field(m, 0, "key")?, "key")?, ed: ed.try_into().unwrap(), ml: ml.to_vec() })
149
    }
150
    pub fn encode_list(sigs: &[Signature]) -> Vec<u8> {
151
        cbor::encode(&Value::Array(sigs.iter().map(|s| s.to_value()).collect()))
152
    }
153
    pub fn decode_list(b: &[u8]) -> Result<Vec<Signature>, ObjectError> {
154
        let v = cbor::decode(b)?;
155
        array(&v, "signatures")?.iter().map(Signature::from_value).collect()
156
    }
157
}
158
159
#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
160
pub enum VerifyError {
161
    #[error("signature names a different key than the identity")]
162
    KeyMismatch,
163
    #[error("identity holds a malformed public key")]
164
    BadKey,
165
    #[error("Ed25519 signature invalid")]
166
    Ed25519,
167
    #[error("ML-DSA-65 signature invalid")]
168
    MlDsa,
169
}
170
171
/// Verify a composite signature by `identity` over `target` (spec §14.3).
172
pub fn verify(identity: &Identity, sig: &Signature, domain: Domain, target: &ObjectRef) -> Result<(), VerifyError> {
173
    let key = identity.hash();
174
    if sig.key != key {
175
        return Err(VerifyError::KeyMismatch);
176
    }
177
    let msg = signing_message(domain, &key, target);
178
    let ed_sig = EdSignature::from_bytes(&sig.ed);
179
    identity.ed_key()?.verify_strict(&msg, &ed_sig).map_err(|_| VerifyError::Ed25519)?;
180
    let enc = EncodedSignature::<MlDsa65>::try_from(sig.ml.as_slice()).map_err(|_| VerifyError::MlDsa)?;
181
    let ml_sig = MlSignature::<MlDsa65>::decode(&enc).ok_or(VerifyError::MlDsa)?;
182
    if !identity.ml_key()?.verify_with_context(&msg, b"", &ml_sig) {
183
        return Err(VerifyError::MlDsa);
184
    }
185
    Ok(())
186
}
187
188
/// The seeds of a composite key pair. Stored only in the user's key directory.
189
#[derive(Clone)]
190
pub struct SecretKey {
191
    pub ed_seed: [u8; 32],
192
    pub ml_seed: [u8; 32],
193
}
194
195
impl std::fmt::Debug for SecretKey {
196
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
197
        f.write_str("SecretKey(..)")
198
    }
199
}
200
201
impl SecretKey {
202
    pub fn generate() -> SecretKey {
203
        let mut ed_seed = [0u8; 32];
204
        let mut ml_seed = [0u8; 32];
205
        rand::rngs::OsRng.fill_bytes(&mut ed_seed);
206
        rand::rngs::OsRng.fill_bytes(&mut ml_seed);
207
        SecretKey { ed_seed, ml_seed }
208
    }
209
210
    fn ed(&self) -> EdSigningKey {
211
        EdSigningKey::from_bytes(&self.ed_seed)
212
    }
213
    fn ml(&self) -> MlSigningKey<MlDsa65> {
214
        MlSigningKey::from_seed(&Seed::from(self.ml_seed))
215
    }
216
217
    /// The identity object for these keys.
218
    pub fn identity(&self, name: &str, email: &str, created: u64) -> Identity {
219
        Identity {
220
            ed25519_pk: self.ed().verifying_key().to_bytes(),
221
            mldsa65_pk: self.ml().expanded_key().verifying_key().encode().as_slice().to_vec(),
222
            name: name.into(),
223
            email: email.into(),
224
            created,
225
            expires: None,
226
            previous: None,
227
            delegates: vec![],
228
        }
229
    }
230
231
    pub fn sign(&self, domain: Domain, key: &KeyId, target: &ObjectRef) -> Signature {
232
        let msg = signing_message(domain, key, target);
233
        let ed = self.ed().sign(&msg).to_bytes();
234
        let ml = self.ml().expanded_key().sign_deterministic(&msg, b"").expect("empty context is valid").encode().as_slice().to_vec();
235
        Signature { key: *key, ed, ml }
236
    }
237
238
    pub fn encode(&self, key: &KeyId) -> Vec<u8> {
239
        cbor::encode(&Value::map([
240
            (0, Value::Uint(FORMAT_VERSION)),
241
            (1, Value::Bytes(key.0.to_vec())),
242
            (2, Value::Bytes(self.ed_seed.to_vec())),
243
            (3, Value::Bytes(self.ml_seed.to_vec())),
244
        ]))
245
    }
246
247
    pub fn decode(b: &[u8]) -> Result<(KeyId, SecretKey), ObjectError> {
248
        let v = cbor::decode(b)?;
249
        let m = v.as_map().ok_or_else(|| ObjectError::Schema("secret key: expected map".into()))?;
250
        check_version(m)?;
251
        let seed = |k: u64| -> Result<[u8; 32], ObjectError> {
252
            bytes(field(m, k, "seed")?, "seed")?.try_into().map_err(|_| ObjectError::Schema("seed: expected 32 bytes".into()))
253
        };
254
        Ok((oref(field(m, 1, "key")?, "key")?, SecretKey { ed_seed: seed(2)?, ml_seed: seed(3)? }))
255
    }
256
}
257
258
#[cfg(test)]
259
mod tests {
260
    use super::*;
261
262
    fn fixed_key() -> SecretKey {
263
        SecretKey { ed_seed: [1; 32], ml_seed: [2; 32] }
264
    }
265
266
    #[test]
267
    fn identity_round_trip_and_sizes() {
268
        let sk = fixed_key();
269
        let id = sk.identity("Ada", "[email protected]", 1_700_000_000_000);
270
        assert_eq!(id.mldsa65_pk.len(), MLDSA65_PK_LEN);
271
        let d = Identity::decode(&id.encode()).unwrap();
272
        assert_eq!(d, id);
273
        assert_eq!(d.hash(), id.hash());
274
        // Deterministic from seeds.
275
        assert_eq!(fixed_key().identity("Ada", "[email protected]", 1_700_000_000_000).hash(), id.hash());
276
        let mut other = id.clone();
277
        other.delegates.push(ObjectRef([9; 32]));
278
        assert_ne!(other.hash(), id.hash());
279
    }
280
281
    #[test]
282
    fn sign_and_verify() {
283
        let sk = fixed_key();
284
        let id = sk.identity("Ada", "[email protected]", 1);
285
        let key = id.hash();
286
        let target = ObjectRef([7; 32]);
287
        let sig = sk.sign(Domain::Change, &key, &target);
288
        assert_eq!(sig.ml.len(), MLDSA65_SIG_LEN);
289
        assert_eq!(verify(&id, &sig, Domain::Change, &target), Ok(()));
290
        // Different target, different domain: invalid.
291
        assert_eq!(verify(&id, &sig, Domain::Change, &ObjectRef([8; 32])), Err(VerifyError::Ed25519));
292
        assert_eq!(verify(&id, &sig, Domain::Attest, &target), Err(VerifyError::Ed25519));
293
        // Deterministic signatures.
294
        assert_eq!(sk.sign(Domain::Change, &key, &target), sig);
295
        // Signature list round trip.
296
        let l = Signature::decode_list(&Signature::encode_list(&[sig.clone()])).unwrap();
297
        assert_eq!(l, vec![sig]);
298
    }
299
300
    #[test]
301
    fn stripping_or_forging_either_half_fails() {
302
        let sk = fixed_key();
303
        let id = sk.identity("Ada", "a@b", 1);
304
        let key = id.hash();
305
        let target = ObjectRef([7; 32]);
306
        let good = sk.sign(Domain::Change, &key, &target);
307
308
        let mut no_ml = good.clone();
309
        no_ml.ml = vec![0; MLDSA65_SIG_LEN];
310
        assert_eq!(verify(&id, &no_ml, Domain::Change, &target), Err(VerifyError::MlDsa));
311
312
        let mut bad_ed = good.clone();
313
        bad_ed.ed[10] ^= 1;
314
        assert_eq!(verify(&id, &bad_ed, Domain::Change, &target), Err(VerifyError::Ed25519));
315
316
        // A signature made for another KeyId over the same target does not transfer.
317
        let other = SecretKey { ed_seed: [3; 32], ml_seed: [4; 32] };
318
        let other_id = other.identity("Bob", "b@b", 1);
319
        let theirs = other.sign(Domain::Change, &other_id.hash(), &target);
320
        assert_eq!(verify(&id, &theirs, Domain::Change, &target), Err(VerifyError::KeyMismatch));
321
        let mut relabelled = theirs.clone();
322
        relabelled.key = key;
323
        assert_eq!(verify(&id, &relabelled, Domain::Change, &target), Err(VerifyError::Ed25519));
324
325
        // Wrong length is rejected at decode time.
326
        let mut short = good.to_value();
327
        if let Value::Map(m) = &mut short {
328
            m.insert(3, Value::Bytes(vec![0; 10]));
329
        }
330
        assert!(Signature::from_value(&short).is_err());
331
    }
332
333
    #[test]
334
    fn secret_key_round_trip() {
335
        let sk = fixed_key();
336
        let key = sk.identity("x", "y", 0).hash();
337
        let (k, d) = SecretKey::decode(&sk.encode(&key)).unwrap();
338
        assert_eq!(k, key);
339
        assert_eq!(d.ed_seed, sk.ed_seed);
340
        assert_eq!(d.ml_seed, sk.ml_seed);
341
        assert_ne!(SecretKey::generate().ed_seed, SecretKey::generate().ed_seed);
342
    }
343
}