OXYGENHUB
# b8a0399319fc oxygen / crates / oxy-core / src / identity.rs
13.3 KiB · Rust · last changed Trust policy: policy objects, init/grant/revoke, enforcement on push and fetch, propagation with downgrade protection (spec 0.6) 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
    Policy,
94
}
95
96
impl Domain {
97
    pub fn name(self) -> &'static str {
98
        match self {
99
            Domain::Change => "change",
100
            Domain::Attest => "attest",
101
            Domain::Policy => "policy",
102
        }
103
    }
104
}
105
106
/// `"oxy1 sig " || domain || 0x00 || KeyId || target` (spec §14.3).
107
pub fn signing_message(domain: Domain, key: &KeyId, target: &ObjectRef) -> Vec<u8> {
108
    let mut m = Vec::with_capacity(80);
109
    m.extend_from_slice(b"oxy1 sig ");
110
    m.extend_from_slice(domain.name().as_bytes());
111
    m.push(0);
112
    m.extend_from_slice(&key.0);
113
    m.extend_from_slice(&target.0);
114
    m
115
}
116
117
#[derive(Clone, PartialEq, Eq)]
118
pub struct Signature {
119
    pub key: KeyId,
120
    pub ed: [u8; ED25519_SIG_LEN],
121
    pub ml: Vec<u8>,
122
}
123
124
impl std::fmt::Debug for Signature {
125
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126
        write!(f, "Signature(key {:?})", self.key)
127
    }
128
}
129
130
impl Signature {
131
    pub fn to_value(&self) -> Value {
132
        Value::map([
133
            (0, Value::Bytes(self.key.0.to_vec())),
134
            (1, Value::Uint(ALG_COMPOSITE)),
135
            (2, Value::Bytes(self.ed.to_vec())),
136
            (3, Value::Bytes(self.ml.clone())),
137
        ])
138
    }
139
    pub fn from_value(v: &Value) -> Result<Signature, ObjectError> {
140
        let m = v.as_map().ok_or_else(|| ObjectError::Schema("signature: expected map".into()))?;
141
        let alg = uint(field(m, 1, "alg")?, "alg")?;
142
        if alg != ALG_COMPOSITE {
143
            return Err(ObjectError::Schema(format!("unknown signature algorithm {alg}")));
144
        }
145
        let ed = bytes(field(m, 2, "ed")?, "ed")?;
146
        let ml = bytes(field(m, 3, "ml")?, "ml")?;
147
        if ed.len() != ED25519_SIG_LEN || ml.len() != MLDSA65_SIG_LEN {
148
            return Err(ObjectError::Schema("signature: wrong length".into()));
149
        }
150
        Ok(Signature { key: oref(field(m, 0, "key")?, "key")?, ed: ed.try_into().unwrap(), ml: ml.to_vec() })
151
    }
152
    pub fn encode_list(sigs: &[Signature]) -> Vec<u8> {
153
        cbor::encode(&Value::Array(sigs.iter().map(|s| s.to_value()).collect()))
154
    }
155
    pub fn decode_list(b: &[u8]) -> Result<Vec<Signature>, ObjectError> {
156
        let v = cbor::decode(b)?;
157
        array(&v, "signatures")?.iter().map(Signature::from_value).collect()
158
    }
159
}
160
161
#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
162
pub enum VerifyError {
163
    #[error("signature names a different key than the identity")]
164
    KeyMismatch,
165
    #[error("identity holds a malformed public key")]
166
    BadKey,
167
    #[error("Ed25519 signature invalid")]
168
    Ed25519,
169
    #[error("ML-DSA-65 signature invalid")]
170
    MlDsa,
171
}
172
173
/// Verify a composite signature by `identity` over `target` (spec §14.3).
174
pub fn verify(identity: &Identity, sig: &Signature, domain: Domain, target: &ObjectRef) -> Result<(), VerifyError> {
175
    let key = identity.hash();
176
    if sig.key != key {
177
        return Err(VerifyError::KeyMismatch);
178
    }
179
    let msg = signing_message(domain, &key, target);
180
    let ed_sig = EdSignature::from_bytes(&sig.ed);
181
    identity.ed_key()?.verify_strict(&msg, &ed_sig).map_err(|_| VerifyError::Ed25519)?;
182
    let enc = EncodedSignature::<MlDsa65>::try_from(sig.ml.as_slice()).map_err(|_| VerifyError::MlDsa)?;
183
    let ml_sig = MlSignature::<MlDsa65>::decode(&enc).ok_or(VerifyError::MlDsa)?;
184
    if !identity.ml_key()?.verify_with_context(&msg, b"", &ml_sig) {
185
        return Err(VerifyError::MlDsa);
186
    }
187
    Ok(())
188
}
189
190
/// The seeds of a composite key pair. Stored only in the user's key directory.
191
#[derive(Clone)]
192
pub struct SecretKey {
193
    pub ed_seed: [u8; 32],
194
    pub ml_seed: [u8; 32],
195
}
196
197
impl std::fmt::Debug for SecretKey {
198
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
199
        f.write_str("SecretKey(..)")
200
    }
201
}
202
203
impl SecretKey {
204
    pub fn generate() -> SecretKey {
205
        let mut ed_seed = [0u8; 32];
206
        let mut ml_seed = [0u8; 32];
207
        rand::rngs::OsRng.fill_bytes(&mut ed_seed);
208
        rand::rngs::OsRng.fill_bytes(&mut ml_seed);
209
        SecretKey { ed_seed, ml_seed }
210
    }
211
212
    fn ed(&self) -> EdSigningKey {
213
        EdSigningKey::from_bytes(&self.ed_seed)
214
    }
215
    fn ml(&self) -> MlSigningKey<MlDsa65> {
216
        MlSigningKey::from_seed(&Seed::from(self.ml_seed))
217
    }
218
219
    /// The identity object for these keys.
220
    pub fn identity(&self, name: &str, email: &str, created: u64) -> Identity {
221
        Identity {
222
            ed25519_pk: self.ed().verifying_key().to_bytes(),
223
            mldsa65_pk: self.ml().expanded_key().verifying_key().encode().as_slice().to_vec(),
224
            name: name.into(),
225
            email: email.into(),
226
            created,
227
            expires: None,
228
            previous: None,
229
            delegates: vec![],
230
        }
231
    }
232
233
    pub fn sign(&self, domain: Domain, key: &KeyId, target: &ObjectRef) -> Signature {
234
        let msg = signing_message(domain, key, target);
235
        let ed = self.ed().sign(&msg).to_bytes();
236
        let ml = self.ml().expanded_key().sign_deterministic(&msg, b"").expect("empty context is valid").encode().as_slice().to_vec();
237
        Signature { key: *key, ed, ml }
238
    }
239
240
    pub fn encode(&self, key: &KeyId) -> Vec<u8> {
241
        cbor::encode(&Value::map([
242
            (0, Value::Uint(FORMAT_VERSION)),
243
            (1, Value::Bytes(key.0.to_vec())),
244
            (2, Value::Bytes(self.ed_seed.to_vec())),
245
            (3, Value::Bytes(self.ml_seed.to_vec())),
246
        ]))
247
    }
248
249
    pub fn decode(b: &[u8]) -> Result<(KeyId, SecretKey), ObjectError> {
250
        let v = cbor::decode(b)?;
251
        let m = v.as_map().ok_or_else(|| ObjectError::Schema("secret key: expected map".into()))?;
252
        check_version(m)?;
253
        let seed = |k: u64| -> Result<[u8; 32], ObjectError> {
254
            bytes(field(m, k, "seed")?, "seed")?.try_into().map_err(|_| ObjectError::Schema("seed: expected 32 bytes".into()))
255
        };
256
        Ok((oref(field(m, 1, "key")?, "key")?, SecretKey { ed_seed: seed(2)?, ml_seed: seed(3)? }))
257
    }
258
}
259
260
#[cfg(test)]
261
mod tests {
262
    use super::*;
263
264
    fn fixed_key() -> SecretKey {
265
        SecretKey { ed_seed: [1; 32], ml_seed: [2; 32] }
266
    }
267
268
    #[test]
269
    fn identity_round_trip_and_sizes() {
270
        let sk = fixed_key();
271
        let id = sk.identity("Ada", "[email protected]", 1_700_000_000_000);
272
        assert_eq!(id.mldsa65_pk.len(), MLDSA65_PK_LEN);
273
        let d = Identity::decode(&id.encode()).unwrap();
274
        assert_eq!(d, id);
275
        assert_eq!(d.hash(), id.hash());
276
        // Deterministic from seeds.
277
        assert_eq!(fixed_key().identity("Ada", "[email protected]", 1_700_000_000_000).hash(), id.hash());
278
        let mut other = id.clone();
279
        other.delegates.push(ObjectRef([9; 32]));
280
        assert_ne!(other.hash(), id.hash());
281
    }
282
283
    #[test]
284
    fn sign_and_verify() {
285
        let sk = fixed_key();
286
        let id = sk.identity("Ada", "[email protected]", 1);
287
        let key = id.hash();
288
        let target = ObjectRef([7; 32]);
289
        let sig = sk.sign(Domain::Change, &key, &target);
290
        assert_eq!(sig.ml.len(), MLDSA65_SIG_LEN);
291
        assert_eq!(verify(&id, &sig, Domain::Change, &target), Ok(()));
292
        // Different target, different domain: invalid.
293
        assert_eq!(verify(&id, &sig, Domain::Change, &ObjectRef([8; 32])), Err(VerifyError::Ed25519));
294
        assert_eq!(verify(&id, &sig, Domain::Attest, &target), Err(VerifyError::Ed25519));
295
        // Deterministic signatures.
296
        assert_eq!(sk.sign(Domain::Change, &key, &target), sig);
297
        // Signature list round trip.
298
        let l = Signature::decode_list(&Signature::encode_list(std::slice::from_ref(&sig))).unwrap();
299
        assert_eq!(l, vec![sig]);
300
    }
301
302
    #[test]
303
    fn stripping_or_forging_either_half_fails() {
304
        let sk = fixed_key();
305
        let id = sk.identity("Ada", "a@b", 1);
306
        let key = id.hash();
307
        let target = ObjectRef([7; 32]);
308
        let good = sk.sign(Domain::Change, &key, &target);
309
310
        let mut no_ml = good.clone();
311
        no_ml.ml = vec![0; MLDSA65_SIG_LEN];
312
        assert_eq!(verify(&id, &no_ml, Domain::Change, &target), Err(VerifyError::MlDsa));
313
314
        let mut bad_ed = good.clone();
315
        bad_ed.ed[10] ^= 1;
316
        assert_eq!(verify(&id, &bad_ed, Domain::Change, &target), Err(VerifyError::Ed25519));
317
318
        // A signature made for another KeyId over the same target does not transfer.
319
        let other = SecretKey { ed_seed: [3; 32], ml_seed: [4; 32] };
320
        let other_id = other.identity("Bob", "b@b", 1);
321
        let theirs = other.sign(Domain::Change, &other_id.hash(), &target);
322
        assert_eq!(verify(&id, &theirs, Domain::Change, &target), Err(VerifyError::KeyMismatch));
323
        let mut relabelled = theirs.clone();
324
        relabelled.key = key;
325
        assert_eq!(verify(&id, &relabelled, Domain::Change, &target), Err(VerifyError::Ed25519));
326
327
        // Wrong length is rejected at decode time.
328
        let mut short = good.to_value();
329
        if let Value::Map(m) = &mut short {
330
            m.insert(3, Value::Bytes(vec![0; 10]));
331
        }
332
        assert!(Signature::from_value(&short).is_err());
333
    }
334
335
    #[test]
336
    fn secret_key_round_trip() {
337
        let sk = fixed_key();
338
        let key = sk.identity("x", "y", 0).hash();
339
        let (k, d) = SecretKey::decode(&sk.encode(&key)).unwrap();
340
        assert_eq!(k, key);
341
        assert_eq!(d.ed_seed, sk.ed_seed);
342
        assert_eq!(d.ml_seed, sk.ml_seed);
343
        assert_ne!(SecretKey::generate().ed_seed, SecretKey::generate().ed_seed);
344
    }
345
}