cryptsetup_rs/
luks2_meta.rs

1//! LUKS2 JSON metadata parsing
2
3use std::collections::HashMap;
4use std::convert::TryFrom;
5use std::error;
6
7use base64::STANDARD;
8use serde::{Deserialize, Serialize};
9use serde_repr::{Deserialize_repr, Serialize_repr};
10use serde_with::skip_serializing_none;
11
12base64_serde_type!(Base64Standard, STANDARD);
13
14#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
15#[serde(rename_all = "kebab-case")]
16pub enum Luks2ConfigFlag {
17    AllowDiscards,
18    SameCpuCrypt,
19    SubmitFromCryptCpus,
20    NoJournal,
21    #[serde(other, skip_serializing)]
22    Unknown,
23}
24
25#[skip_serializing_none]
26#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
27pub struct Luks2Config {
28    #[serde(with = "serde_with::rust::display_fromstr")]
29    pub json_size: u64,
30    #[serde(with = "serde_with::rust::display_fromstr")]
31    pub keyslots_size: u64,
32    pub flags: Option<Vec<Luks2ConfigFlag>>,
33    pub requirements: Option<Vec<String>>,
34}
35
36#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
37#[serde(rename_all = "snake_case")]
38pub enum Luks2DigestType {
39    Pbkdf2,
40}
41
42#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
43pub struct Luks2Digest {
44    #[serde(rename = "type")]
45    pub type_: Luks2DigestType,
46    pub keyslots: Vec<String>,
47    pub segments: Vec<String>,
48    #[serde(with = "Base64Standard")]
49    pub salt: Vec<u8>,
50    #[serde(with = "Base64Standard")]
51    pub digest: Vec<u8>,
52    // only pbkdf2 is supported currently
53    pub hash: String,
54    pub iterations: u64,
55}
56
57#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
58#[serde(rename_all = "snake_case")]
59pub enum Luks2KeyslotType {
60    Luks2,
61}
62
63#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
64#[serde(rename_all = "snake_case")]
65pub enum Luks2KeyslotAfType {
66    Luks1,
67}
68
69#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
70pub struct Luks2KeyslotAf {
71    #[serde(rename = "type")]
72    pub type_: Luks2KeyslotAfType,
73    pub stripes: u32,
74    pub hash: String,
75}
76
77#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
78#[serde(rename_all = "snake_case")]
79pub enum Luks2KeyslotAreaType {
80    Raw,
81}
82
83#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
84pub struct Luks2KeyslotArea {
85    #[serde(rename = "type")]
86    pub type_: Luks2KeyslotAreaType,
87    #[serde(with = "serde_with::rust::display_fromstr")]
88    pub offset: u64,
89    #[serde(with = "serde_with::rust::display_fromstr")]
90    pub size: u64,
91    pub encryption: String,
92    pub key_size: u16,
93}
94
95#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
96#[serde(tag = "type", rename_all = "snake_case")]
97pub enum Luks2KeyslotKdf {
98    Pbkdf2 {
99        #[serde(with = "Base64Standard")]
100        salt: Vec<u8>,
101        hash: String,
102        iterations: u32,
103    },
104    Argon2i {
105        #[serde(with = "Base64Standard")]
106        salt: Vec<u8>,
107        time: u32,
108        memory: u32,
109        cpus: u32,
110    },
111    Argon2id {
112        #[serde(with = "Base64Standard")]
113        salt: Vec<u8>,
114        time: u32,
115        memory: u32,
116        cpus: u32,
117    },
118}
119
120#[derive(Serialize_repr, Deserialize_repr, Debug, PartialEq, Clone)]
121#[repr(u8)]
122pub enum Luks2KeyslotPriority {
123    Ignore = 0,
124    Normal = 1,
125    High = 2,
126}
127
128#[skip_serializing_none]
129#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
130pub struct Luks2Keyslot {
131    #[serde(rename = "type")]
132    pub type_: Luks2KeyslotType,
133    pub key_size: u32,
134    pub area: Luks2KeyslotArea,
135    pub kdf: Luks2KeyslotKdf,
136    pub af: Luks2KeyslotAf,
137    pub priority: Option<Luks2KeyslotPriority>,
138}
139
140#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
141#[serde(rename_all = "snake_case")]
142pub enum Luks2SegmentType {
143    Crypt,
144}
145
146#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
147pub struct Luks2SegmentIntegrity {
148    #[serde(rename = "type")]
149    pub type_: String,
150    pub journal_encryption: String,
151    pub journal_integrity: String,
152}
153
154#[skip_serializing_none]
155#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
156pub struct Luks2Segment {
157    #[serde(rename = "type")]
158    pub type_: Luks2SegmentType,
159    #[serde(with = "serde_with::rust::display_fromstr")]
160    pub offset: u64,
161    pub size: String, // TODO: custom serialization
162    #[serde(with = "serde_with::rust::display_fromstr")]
163    pub iv_tweak: u64,
164    pub encryption: String,
165    pub sector_size: u32,
166    pub integrity: Option<Luks2SegmentIntegrity>,
167    pub flags: Option<Vec<String>>,
168}
169
170#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
171pub struct Luks2Token {
172    #[serde(rename = "type")]
173    pub type_: String,
174    pub keyslots: Vec<String>,
175    #[serde(flatten)]
176    pub other: serde_json::Map<String, serde_json::Value>,
177}
178
179impl TryFrom<&str> for Luks2Token {
180    type Error = Box<dyn error::Error>;
181
182    fn try_from(value: &str) -> Result<Self, Self::Error> {
183        let res = serde_json::from_str(value)?;
184        Ok(res)
185    }
186}
187
188impl TryFrom<&Luks2Token> for String {
189    type Error = Box<dyn error::Error>;
190
191    fn try_from(value: &Luks2Token) -> Result<Self, Self::Error> {
192        let res = serde_json::to_string(value)?;
193        Ok(res)
194    }
195}
196
197#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
198pub struct Luks2Metadata {
199    pub config: Luks2Config,
200    pub digests: HashMap<u16, Luks2Digest>,
201    pub keyslots: HashMap<u16, Luks2Keyslot>,
202    pub segments: HashMap<u16, Luks2Segment>,
203    pub tokens: HashMap<u16, Luks2Token>,
204}
205
206impl TryFrom<&str> for Luks2Metadata {
207    type Error = Box<dyn error::Error>;
208
209    fn try_from(value: &str) -> Result<Self, Self::Error> {
210        let res = serde_json::from_str(value)?;
211        Ok(res)
212    }
213}
214
215impl TryFrom<&Luks2Metadata> for String {
216    type Error = Box<dyn error::Error>;
217
218    fn try_from(m: &Luks2Metadata) -> Result<Self, Self::Error> {
219        let res = serde_json::to_string(m)?;
220        Ok(res)
221    }
222}
223
224#[cfg(test)]
225mod tests {
226    use super::*;
227
228    #[test]
229    fn test_json_config_flag_from() {
230        assert_eq!(
231            serde_json::from_str::<Luks2ConfigFlag>(r#""allow-discards""#).unwrap(),
232            Luks2ConfigFlag::AllowDiscards
233        );
234        assert_eq!(
235            serde_json::from_str::<Luks2ConfigFlag>(r#""same-cpu-crypt""#).unwrap(),
236            Luks2ConfigFlag::SameCpuCrypt
237        );
238        assert_eq!(
239            serde_json::from_str::<Luks2ConfigFlag>(r#""submit-from-crypt-cpus""#).unwrap(),
240            Luks2ConfigFlag::SubmitFromCryptCpus
241        );
242        assert_eq!(
243            serde_json::from_str::<Luks2ConfigFlag>(r#""no-journal""#).unwrap(),
244            Luks2ConfigFlag::NoJournal
245        );
246        assert_eq!(
247            serde_json::from_str::<Luks2ConfigFlag>(r#""random-flag""#).unwrap(),
248            Luks2ConfigFlag::Unknown
249        );
250    }
251
252    #[test]
253    fn test_json_config_flag_to() {
254        assert_eq!(
255            r#""allow-discards""#,
256            serde_json::to_string(&Luks2ConfigFlag::AllowDiscards).unwrap()
257        );
258        assert_eq!(
259            r#""same-cpu-crypt""#,
260            serde_json::to_string(&Luks2ConfigFlag::SameCpuCrypt).unwrap()
261        );
262        assert_eq!(
263            r#""submit-from-crypt-cpus""#,
264            serde_json::to_string(&Luks2ConfigFlag::SubmitFromCryptCpus).unwrap()
265        );
266        assert_eq!(
267            r#""no-journal""#,
268            serde_json::to_string(&Luks2ConfigFlag::NoJournal).unwrap()
269        );
270        assert_eq!(true, serde_json::to_string(&Luks2ConfigFlag::Unknown).is_err());
271    }
272
273    #[test]
274    fn test_json_config() {
275        let js = r#"{"json_size":"12288","keyslots_size":"16744448"}"#;
276        let c: Luks2Config = serde_json::from_str(js).unwrap();
277        assert_eq!(c.json_size, 12288);
278        assert_eq!(c.keyslots_size, 16744448);
279        assert_eq!(c.flags, None);
280        assert_eq!(c.requirements, None);
281
282        let to_js = serde_json::to_string(&c).unwrap();
283        assert_eq!(to_js, js);
284    }
285
286    #[test]
287    fn test_json_digests() {
288        let js = r#"{"type":"pbkdf2","keyslots":["0"],"segments":["0"],"salt":"WYkbZOppCHRvwDvrVIbxKimZ4qjXDSizlcMRvyE7EM0=","digest":"SH2Ks6EOcW9r8Q82mLQG8+5H3TvAYLdLw8VuP7Vo5eM=","hash":"sha256","iterations":223672}"#;
289        let d: Luks2Digest = serde_json::from_str(js).unwrap();
290
291        assert_eq!(d.type_, Luks2DigestType::Pbkdf2);
292        assert_eq!(d.keyslots, vec!("0"));
293        assert_eq!(d.segments, vec!("0"));
294        assert_eq!(d.hash, "sha256");
295        assert_eq!(d.iterations, 223672);
296        assert_eq!(
297            d.salt,
298            [
299                89u8, 137, 27, 100, 234, 105, 8, 116, 111, 192, 59, 235, 84, 134, 241, 42, 41, 153, 226, 168, 215, 13,
300                40, 179, 149, 195, 17, 191, 33, 59, 16, 205
301            ]
302        );
303        assert_eq!(
304            d.digest,
305            [
306                72u8, 125, 138, 179, 161, 14, 113, 111, 107, 241, 15, 54, 152, 180, 6, 243, 238, 71, 221, 59, 192, 96,
307                183, 75, 195, 197, 110, 63, 181, 104, 229, 227
308            ]
309        );
310
311        let to_js = serde_json::to_string(&d).unwrap();
312        assert_eq!(to_js, js);
313    }
314
315    #[test]
316    fn test_json_keyslot_af() {
317        let js = r#"{"type":"luks1","stripes":4000,"hash":"sha256"}"#;
318        let a: Luks2KeyslotAf = serde_json::from_str(js).unwrap();
319
320        assert_eq!(a.type_, Luks2KeyslotAfType::Luks1);
321        assert_eq!(a.stripes, 4000);
322        assert_eq!(a.hash, "sha256");
323
324        let to_js = serde_json::to_string(&a).unwrap();
325        assert_eq!(to_js, js);
326    }
327
328    #[test]
329    fn test_json_keyslot_area() {
330        let js = r#"{"type":"raw","offset":"32768","size":"258048","encryption":"aes-xts-plain64","key_size":64}"#;
331        let a: Luks2KeyslotArea = serde_json::from_str(js).unwrap();
332
333        assert_eq!(a.type_, Luks2KeyslotAreaType::Raw);
334        assert_eq!(a.offset, 32768);
335        assert_eq!(a.size, 258048);
336        assert_eq!(a.encryption, "aes-xts-plain64");
337        assert_eq!(a.key_size, 64);
338
339        let to_js = serde_json::to_string(&a).unwrap();
340        assert_eq!(to_js, js);
341    }
342
343    #[test]
344    fn test_json_keyslot_kdf_pbkdf2() {
345        let js = r#"{"type":"pbkdf2","salt":"SH2Ks6EOcW9r8Q82mLQG8+5H3TvAYLdLw8VuP7Vo5eM=","hash":"sha256","iterations":1234}"#;
346        let k: Luks2KeyslotKdf = serde_json::from_str(js).unwrap();
347
348        match &k {
349            Luks2KeyslotKdf::Pbkdf2 { salt, hash, iterations } => {
350                assert_eq!(
351                    salt[..],
352                    [
353                        72u8, 125, 138, 179, 161, 14, 113, 111, 107, 241, 15, 54, 152, 180, 6, 243, 238, 71, 221, 59,
354                        192, 96, 183, 75, 195, 197, 110, 63, 181, 104, 229, 227
355                    ]
356                );
357                assert_eq!(hash, "sha256");
358                assert_eq!(*iterations, 1234);
359            }
360            _ => assert!(false, "expected pbkdf2"),
361        }
362
363        let to_js = serde_json::to_string(&k).unwrap();
364        assert_eq!(to_js, js);
365    }
366
367    #[test]
368    fn test_json_keyslot_kdf_argon2i() {
369        let js = r#"{"type":"argon2i","salt":"cNqP5YVtK2DRlLvTPZU8LXy4jWi1+QJPH+Gz3WouBTI=","time":8,"memory":1048576,"cpus":4}"#;
370        let k: Luks2KeyslotKdf = serde_json::from_str(js).unwrap();
371
372        match &k {
373            Luks2KeyslotKdf::Argon2i {
374                salt,
375                time,
376                memory,
377                cpus,
378            } => {
379                assert_eq!(
380                    salt[..],
381                    [
382                        112u8, 218, 143, 229, 133, 109, 43, 96, 209, 148, 187, 211, 61, 149, 60, 45, 124, 184, 141,
383                        104, 181, 249, 2, 79, 31, 225, 179, 221, 106, 46, 5, 50
384                    ]
385                );
386                assert_eq!(*time, 8);
387                assert_eq!(*memory, 1048576);
388                assert_eq!(*cpus, 4);
389            }
390            _ => assert!(false, "expected argon2i"),
391        }
392
393        let to_js = serde_json::to_string(&k).unwrap();
394        assert_eq!(to_js, js);
395    }
396
397    #[test]
398    fn test_json_keyslot_kdf_argon2id() {
399        let js = r#"{"type":"argon2id","salt":"cNqP5YVtK2DRlLvTPZU8LXy4jWi1+QJPH+Gz3WouBTI=","time":8,"memory":1048576,"cpus":4}"#;
400        let k: Luks2KeyslotKdf = serde_json::from_str(js).unwrap();
401
402        match &k {
403            Luks2KeyslotKdf::Argon2id {
404                salt,
405                time,
406                memory,
407                cpus,
408            } => {
409                assert_eq!(
410                    salt[..],
411                    [
412                        112u8, 218, 143, 229, 133, 109, 43, 96, 209, 148, 187, 211, 61, 149, 60, 45, 124, 184, 141,
413                        104, 181, 249, 2, 79, 31, 225, 179, 221, 106, 46, 5, 50
414                    ]
415                );
416                assert_eq!(*time, 8);
417                assert_eq!(*memory, 1048576);
418                assert_eq!(*cpus, 4);
419            }
420            _ => assert!(false, "expected argon2id"),
421        }
422
423        let to_js = serde_json::to_string(&k).unwrap();
424        assert_eq!(to_js, js);
425    }
426
427    #[test]
428    fn test_json_segment() {
429        let js = r#"{"type":"crypt","offset":"16777216","size":"dynamic","iv_tweak":"0","encryption":"aes-xts-plain64","sector_size":512}"#;
430        let s: Luks2Segment = serde_json::from_str(js).unwrap();
431
432        assert_eq!(s.type_, Luks2SegmentType::Crypt);
433        assert_eq!(s.offset, 16777216);
434        assert_eq!(s.size, "dynamic");
435        assert_eq!(s.iv_tweak, 0);
436        assert_eq!(s.encryption, "aes-xts-plain64");
437        assert_eq!(s.sector_size, 512);
438
439        let to_js = serde_json::to_string(&s).unwrap();
440        assert_eq!(to_js, js);
441    }
442
443    #[test]
444    fn test_json_token() {
445        let js = r#"{"type":"luks2-keyring","keyslots":["0","1"],"key_description":"my:key"}"#;
446        let t: Luks2Token = serde_json::from_str(js).unwrap();
447
448        let mut m = serde_json::Map::new();
449        let _ = m.insert(
450            "key_description".to_owned(),
451            serde_json::Value::String("my:key".to_owned()),
452        );
453
454        assert_eq!(t.type_, "luks2-keyring");
455        assert_eq!(t.keyslots, ["0", "1"]);
456        assert_eq!(t.other, m);
457
458        let to_js = serde_json::to_string(&t).unwrap();
459        assert_eq!(to_js, js);
460    }
461
462    #[test]
463    fn test_json_metadata_simple() {
464        let js = r#"{"config":{"json_size":"12288","keyslots_size":"16744448"},"digests":{"0":{"type":"pbkdf2","keyslots":["0"],"segments":["0"],"salt":"WYkbZOppCHRvwDvrVIbxKimZ4qjXDSizlcMRvyE7EM0=","digest":"SH2Ks6EOcW9r8Q82mLQG8+5H3TvAYLdLw8VuP7Vo5eM=","hash":"sha256","iterations":223672}},"keyslots":{"0":{"type":"luks2","key_size":64,"area":{"type":"raw","offset":"32768","size":"258048","encryption":"aes-xts-plain64","key_size":64},"kdf":{"type":"argon2i","salt":"cNqP5YVtK2DRlLvTPZU8LXy4jWi1+QJPH+Gz3WouBTI=","time":8,"memory":1048576,"cpus":4},"af":{"type":"luks1","stripes":4000,"hash":"sha256"}}},"segments":{"0":{"type":"crypt","offset":"16777216","size":"dynamic","iv_tweak":"0","encryption":"aes-xts-plain64","sector_size":512}},"tokens":{}}"#;
465        let m: Luks2Metadata = serde_json::from_str(js).unwrap();
466
467        let expected = Luks2Metadata {
468            config: Luks2Config {
469                json_size: 12288,
470                keyslots_size: 16744448,
471                flags: None,
472                requirements: None,
473            },
474            digests: [(
475                0u16,
476                Luks2Digest {
477                    type_: Luks2DigestType::Pbkdf2,
478                    keyslots: vec!["0".to_owned()],
479                    segments: vec!["0".to_owned()],
480                    salt: vec![
481                        89, 137, 27, 100, 234, 105, 8, 116, 111, 192, 59, 235, 84, 134, 241, 42, 41, 153, 226, 168,
482                        215, 13, 40, 179, 149, 195, 17, 191, 33, 59, 16, 205,
483                    ],
484                    digest: vec![
485                        72, 125, 138, 179, 161, 14, 113, 111, 107, 241, 15, 54, 152, 180, 6, 243, 238, 71, 221, 59,
486                        192, 96, 183, 75, 195, 197, 110, 63, 181, 104, 229, 227,
487                    ],
488                    hash: "sha256".to_string(),
489                    iterations: 223672,
490                },
491            )]
492            .iter()
493            .cloned()
494            .collect(),
495            keyslots: [(
496                0u16,
497                Luks2Keyslot {
498                    type_: Luks2KeyslotType::Luks2,
499                    key_size: 64,
500                    area: Luks2KeyslotArea {
501                        type_: Luks2KeyslotAreaType::Raw,
502                        offset: 32768,
503                        size: 258048,
504                        encryption: "aes-xts-plain64".to_string(),
505                        key_size: 64,
506                    },
507                    kdf: Luks2KeyslotKdf::Argon2i {
508                        salt: vec![
509                            112, 218, 143, 229, 133, 109, 43, 96, 209, 148, 187, 211, 61, 149, 60, 45, 124, 184, 141,
510                            104, 181, 249, 2, 79, 31, 225, 179, 221, 106, 46, 5, 50,
511                        ],
512                        time: 8,
513                        memory: 1048576,
514                        cpus: 4,
515                    },
516                    af: Luks2KeyslotAf {
517                        type_: Luks2KeyslotAfType::Luks1,
518                        stripes: 4000,
519                        hash: "sha256".to_string(),
520                    },
521                    priority: None,
522                },
523            )]
524            .iter()
525            .cloned()
526            .collect(),
527            segments: [(
528                0u16,
529                Luks2Segment {
530                    type_: Luks2SegmentType::Crypt,
531                    offset: 16777216,
532                    size: "dynamic".to_string(),
533                    iv_tweak: 0,
534                    encryption: "aes-xts-plain64".to_string(),
535                    sector_size: 512,
536                    integrity: None,
537                    flags: None,
538                },
539            )]
540            .iter()
541            .cloned()
542            .collect(),
543            tokens: HashMap::new(),
544        };
545
546        assert_eq!(m, expected);
547
548        let to_js = serde_json::to_string(&m).unwrap();
549        assert_eq!(to_js, js);
550    }
551
552    #[test]
553    fn test_json_metadata_example() {
554        let js = r#"{
555          "keyslots":{
556            "0":{
557              "type":"luks2",
558              "key_size":32,
559              "af":{
560                "type":"luks1",
561                "stripes":4000,
562                "hash":"sha256"
563              },
564              "area":{
565                "type":"raw",
566                "encryption":"aes-xts-plain64",
567                "key_size":32,
568                "offset":"32768",
569                "size":"131072"
570              },
571              "kdf":{
572                "type":"argon2i",
573                "time":4,
574                "memory":235980,
575                "cpus":2,
576                "salt":"z6vz4xK7cjan92rDA5JF8O6Jk2HouV0O8DMB6GlztVk="
577              }
578            },
579            "1":{
580              "type":"luks2",
581              "key_size":32,
582              "af":{
583                "type":"luks1",
584                "stripes":4000,
585                "hash":"sha256"
586              },
587              "area":{
588                "type":"raw",
589                "encryption":"aes-xts-plain64",
590                "key_size":32,
591                "offset":"163840",
592                "size":"131072"
593              },
594              "kdf":{
595                "type":"pbkdf2",
596                "hash":"sha256",
597                "iterations":1774240,
598                "salt":"vWcwY3rx2fKpXW2Q6oSCNf8j5bvdJyEzB6BNXECGDsI="
599              }
600            }
601          },
602          "tokens":{
603            "0":{
604              "type":"luks2-keyring",
605              "keyslots":[
606                "1"
607              ],
608              "key_description":"MyKeyringKeyID"
609            }
610          },
611          "segments":{
612            "0":{
613              "type":"crypt",
614              "offset":"4194304",
615              "iv_tweak":"0",
616              "size":"dynamic",
617              "encryption":"aes-xts-plain64",
618              "sector_size":512
619            }
620          },
621          "digests":{
622            "0":{
623              "type":"pbkdf2",
624              "keyslots":[
625                "0",
626                "1"
627              ],
628              "segments":[
629                "0"
630              ],
631              "hash":"sha256",
632              "iterations":110890,
633              "salt":"G8gqtKhS96IbogHyJLO+t9kmjLkx+DM3HHJqQtgc2Dk=",
634              "digest":"C9JWko5m+oYmjg6R0t/98cGGzLr/4UaG3hImSJMivfc="
635            }
636          },
637          "config":{
638            "json_size":"12288",
639            "keyslots_size":"4161536",
640            "flags":[
641              "allow-discards"
642            ]
643          }
644        }
645        "#;
646        let m: Luks2Metadata = serde_json::from_str(js).unwrap();
647
648        // just check that it parses
649        assert_eq!(m.config.flags, Some(vec!(Luks2ConfigFlag::AllowDiscards)));
650    }
651}