procfs_core/
crypto.rs

1use crate::{expect, FromBufRead, ProcError, ProcResult};
2
3#[cfg(feature = "serde1")]
4use serde::{Deserialize, Serialize};
5use std::{
6    collections::HashMap,
7    convert::TryFrom,
8    io::BufRead,
9    iter::{once, Peekable},
10    str::FromStr,
11};
12
13/// Represents the data from `/proc/crypto`.
14///
15/// Each block represents a cryptographic implementation that has been registered with the kernel.
16#[derive(Debug, Clone)]
17#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
18pub struct CryptoTable {
19    pub crypto_blocks: HashMap<String, Vec<CryptoBlock>>,
20}
21
22/// Format of a crypto implementation represented in /proc/crypto.
23#[derive(Debug, Clone)]
24#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
25pub struct CryptoBlock {
26    pub name: String,
27    pub driver: String,
28    pub module: String,
29    pub priority: isize,
30    pub ref_count: isize,
31    pub self_test: SelfTest,
32    pub internal: bool,
33    pub fips_enabled: bool,
34    pub crypto_type: Type,
35}
36
37impl FromBufRead for CryptoTable {
38    fn from_buf_read<R: BufRead>(r: R) -> ProcResult<Self> {
39        let mut lines = r.lines().peekable();
40        let mut crypto_blocks: HashMap<String, Vec<CryptoBlock>> = HashMap::new();
41        while let Some(line) = lines.next() {
42            let line = line?;
43            // Just skip empty lines
44            if !line.is_empty() {
45                let mut split = line.split(':');
46                let name = expect!(split.next());
47                if name.trim() == "name" {
48                    let name = expect!(split.next()).trim().to_string();
49                    let block = CryptoBlock::from_iter(&mut lines, name.as_str())?;
50                    let blocks = crypto_blocks.entry(name).or_insert(Vec::new());
51                    blocks.push(block);
52                }
53            }
54        }
55
56        Ok(CryptoTable { crypto_blocks })
57    }
58}
59
60impl CryptoTable {
61    pub fn get<T: AsRef<str>>(&self, target: T) -> Option<&Vec<CryptoBlock>> {
62        self.crypto_blocks.get(target.as_ref())
63    }
64}
65
66impl CryptoBlock {
67    fn from_iter<T: Iterator<Item = Result<String, std::io::Error>>>(
68        iter: &mut Peekable<T>,
69        name: &str,
70    ) -> ProcResult<Self> {
71        let driver = parse_line(iter, "driver", name)?;
72        let module = parse_line(iter, "module", name)?;
73        let priority = from_str!(isize, &parse_line(iter, "priority", name)?);
74        let ref_count = from_str!(isize, &parse_line(iter, "refcnt", name)?);
75        let self_test = SelfTest::try_from(parse_line(iter, "selftest", name)?.as_str())?;
76        let internal = parse_bool(iter, "internal", name)?;
77        let fips_enabled = parse_fips(iter, name)?;
78        let crypto_type = Type::from_iter(iter, name)?;
79        Ok(CryptoBlock {
80            name: name.to_string(),
81            driver,
82            module,
83            priority,
84            ref_count,
85            self_test,
86            internal,
87            fips_enabled,
88            crypto_type,
89        })
90    }
91}
92
93/// Potential results for selftest.
94#[derive(Debug, Clone, PartialEq)]
95#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
96pub enum SelfTest {
97    Passed,
98    Unknown,
99}
100
101impl TryFrom<&str> for SelfTest {
102    type Error = ProcError;
103
104    fn try_from(value: &str) -> Result<Self, ProcError> {
105        Ok(match value {
106            "passed" => Self::Passed,
107            "unknown" => Self::Unknown,
108            _ => {
109                return Err(build_internal_error!(format!(
110                    "Could not recognise self test string {value}"
111                )))
112            }
113        })
114    }
115}
116
117/// Enumeration of potential types and their associated data. Unknown at end to catch unrecognised types.
118#[derive(Debug, Clone, PartialEq)]
119#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
120pub enum Type {
121    /// Symmetric Key Cipher
122    Skcipher(Skcipher),
123    /// Single Block Cipher
124    Cipher(Cipher),
125    /// Syncronous Hash
126    Shash(Shash),
127    /// Asyncronous Hash
128    Ahash(Ahash),
129    /// Authenticated Encryption with Associated Data
130    Aead(Aead),
131    /// Random Number Generator
132    Rng(Rng),
133    /// Test algorithm
134    Larval(Larval),
135    /// Synchronous Compression
136    Scomp,
137    /// General Compression
138    Compression,
139    /// Asymmetric Cipher
140    AkCipher,
141    /// Key-agreement Protocol Primitive
142    Kpp,
143    /// Signature
144    Sig,
145    /// Unrecognised type, associated data collected in to a hash map
146    Unknown(Unknown),
147}
148
149impl Type {
150    fn from_iter<T: Iterator<Item = Result<String, std::io::Error>>>(
151        iter: &mut Peekable<T>,
152        name: &str,
153    ) -> ProcResult<Self> {
154        let type_name = parse_line(iter, "type", name)?;
155        Ok(match type_name.as_str() {
156            "skcipher" => Self::Skcipher(Skcipher::parse(iter, name)?),
157            "cipher" => Self::Cipher(Cipher::parse(iter, name)?),
158            "shash" => Self::Shash(Shash::parse(iter, name)?),
159            "scomp" => Self::Scomp,
160            "compression" => Self::Compression,
161            "akcipher" => Self::AkCipher,
162            "kpp" => Self::Kpp,
163            "ahash" => Self::Ahash(Ahash::parse(iter, name)?),
164            "aead" => Self::Aead(Aead::parse(iter, name)?),
165            "rng" => Self::Rng(Rng::parse(iter, name)?),
166            "larval" => Self::Larval(Larval::parse(iter, name)?),
167            "sig" => Self::Sig,
168            unknown_name => Self::Unknown(Unknown::parse(iter, unknown_name)),
169        })
170    }
171}
172
173#[derive(Debug, Clone, PartialEq)]
174#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
175pub struct Skcipher {
176    pub async_capable: bool,
177    pub block_size: usize,
178    pub min_key_size: usize,
179    pub max_key_size: usize,
180    pub iv_size: usize,
181    pub chunk_size: usize,
182    pub walk_size: usize,
183}
184
185impl Skcipher {
186    fn parse<T: Iterator<Item = Result<String, std::io::Error>>>(iter: &mut T, name: &str) -> ProcResult<Self> {
187        let async_capable = parse_bool(iter, "async", name)?;
188        let block_size = from_str!(usize, &parse_line(iter, "blocksize", name)?);
189        let min_key_size = from_str!(usize, &parse_line(iter, "min keysize", name)?);
190        let max_key_size = from_str!(usize, &parse_line(iter, "max keysize", name)?);
191        let iv_size = from_str!(usize, &parse_line(iter, "ivsize", name)?);
192        let chunk_size = from_str!(usize, &parse_line(iter, "chunksize", name)?);
193        let walk_size = from_str!(usize, &parse_line(iter, "walksize", name)?);
194        Ok(Self {
195            async_capable,
196            block_size,
197            min_key_size,
198            max_key_size,
199            iv_size,
200            chunk_size,
201            walk_size,
202        })
203    }
204}
205
206#[derive(Debug, Clone, PartialEq)]
207#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
208pub struct Cipher {
209    pub block_size: usize,
210    pub min_key_size: usize,
211    pub max_key_size: usize,
212}
213
214impl Cipher {
215    fn parse<T: Iterator<Item = Result<String, std::io::Error>>>(iter: &mut T, name: &str) -> ProcResult<Self> {
216        let block_size = from_str!(usize, &parse_line(iter, "blocksize", name)?);
217        let min_key_size = from_str!(usize, &parse_line(iter, "min keysize", name)?);
218        let max_key_size = from_str!(usize, &parse_line(iter, "max keysize", name)?);
219        Ok(Self {
220            block_size,
221            min_key_size,
222            max_key_size,
223        })
224    }
225}
226
227#[derive(Debug, Clone, PartialEq)]
228#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
229pub struct Shash {
230    pub block_size: usize,
231    pub digest_size: usize,
232}
233
234impl Shash {
235    fn parse<T: Iterator<Item = Result<String, std::io::Error>>>(iter: &mut T, name: &str) -> ProcResult<Self> {
236        let block_size = from_str!(usize, &parse_line(iter, "blocksize", name)?);
237        let digest_size = from_str!(usize, &parse_line(iter, "digestsize", name)?);
238        Ok(Self {
239            block_size,
240            digest_size,
241        })
242    }
243}
244
245#[derive(Debug, Clone, PartialEq)]
246#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
247pub struct Ahash {
248    pub async_capable: bool,
249    pub block_size: usize,
250    pub digest_size: usize,
251}
252
253impl Ahash {
254    fn parse<T: Iterator<Item = Result<String, std::io::Error>>>(iter: &mut T, name: &str) -> ProcResult<Self> {
255        let async_capable = parse_bool(iter, "async", name)?;
256        let block_size = from_str!(usize, &parse_line(iter, "blocksize", name)?);
257        let digest_size = from_str!(usize, &parse_line(iter, "digestsize", name)?);
258        Ok(Self {
259            async_capable,
260            block_size,
261            digest_size,
262        })
263    }
264}
265
266#[derive(Debug, Clone, PartialEq)]
267#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
268pub struct Aead {
269    pub async_capable: bool,
270    pub block_size: usize,
271    pub iv_size: usize,
272    pub max_auth_size: usize,
273    pub gen_iv: Option<usize>,
274}
275
276impl Aead {
277    fn parse<T: Iterator<Item = Result<String, std::io::Error>>>(
278        iter: &mut Peekable<T>,
279        name: &str,
280    ) -> ProcResult<Self> {
281        let async_capable = parse_bool(iter, "async", name)?;
282        let block_size = from_str!(usize, &parse_line(iter, "blocksize", name)?);
283        let iv_size = from_str!(usize, &parse_line(iter, "ivsize", name)?);
284        let max_auth_size = from_str!(usize, &parse_line(iter, "maxauthsize", name)?);
285        let gen_iv = parse_gen_iv(iter, name)?;
286        Ok(Self {
287            async_capable,
288            block_size,
289            iv_size,
290            max_auth_size,
291            gen_iv,
292        })
293    }
294}
295
296#[derive(Debug, Clone, PartialEq)]
297#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
298pub struct Rng {
299    pub seed_size: usize,
300}
301
302impl Rng {
303    fn parse<T: Iterator<Item = Result<String, std::io::Error>>>(iter: &mut T, name: &str) -> ProcResult<Self> {
304        let seed_size = from_str!(usize, &parse_line(iter, "seedsize", name)?);
305        Ok(Self { seed_size })
306    }
307}
308
309#[derive(Debug, Clone, PartialEq)]
310#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
311pub struct Larval {
312    pub flags: u32,
313}
314
315impl Larval {
316    fn parse<T: Iterator<Item = Result<String, std::io::Error>>>(iter: &mut T, name: &str) -> ProcResult<Self> {
317        let flags = from_str!(u32, &parse_line(iter, "flags", name)?);
318        Ok(Self { flags })
319    }
320}
321
322#[derive(Debug, Clone, PartialEq)]
323#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
324pub struct Unknown {
325    pub fields: HashMap<String, String>,
326}
327
328impl Unknown {
329    fn parse<T: Iterator<Item = Result<String, std::io::Error>>>(iter: &mut T, unknown_name: &str) -> Self {
330        let fields = iter
331            .map_while(|line| {
332                let line = match line {
333                    Ok(line) => line,
334                    Err(_) => return None,
335                };
336                (!line.is_empty()).then(|| {
337                    line.split_once(':')
338                        .map(|(k, v)| (k.trim().to_string(), v.trim().to_string()))
339                })
340            })
341            .flatten()
342            .chain(once((String::from("name"), unknown_name.to_string())))
343            .collect();
344        Self { fields }
345    }
346}
347
348fn parse_line<T: Iterator<Item = Result<String, std::io::Error>>>(
349    iter: &mut T,
350    to_find: &str,
351    name: &str,
352) -> ProcResult<String> {
353    let line = expect!(iter.next())?;
354    let (key, val) = expect!(line.split_once(':'));
355    if key.trim() != to_find {
356        return Err(build_internal_error!(format!(
357            "could not locate {to_find} in /proc/crypto, block {name}"
358        )));
359    }
360    Ok(val.trim().to_string())
361}
362
363fn parse_fips<T: Iterator<Item = Result<String, std::io::Error>>>(
364    iter: &mut Peekable<T>,
365    name: &str,
366) -> ProcResult<bool> {
367    if iter
368        .peek()
369        .map(|line| line.as_ref().is_ok_and(|line| line.contains("fips")))
370        .unwrap_or(false)
371    {
372        let fips = parse_line(iter, "fips", name)?;
373        if fips == "yes" {
374            return Ok(true);
375        }
376    }
377    Ok(false)
378}
379
380fn parse_bool<T: Iterator<Item = Result<String, std::io::Error>>>(
381    iter: &mut T,
382    to_find: &str,
383    name: &str,
384) -> ProcResult<bool> {
385    match parse_line(iter, to_find, name)?.as_str() {
386        "yes" => Ok(true),
387        "no" => Ok(false),
388        _ => Err(build_internal_error!(format!(
389            "{to_find} for {name} was unrecognised term"
390        ))),
391    }
392}
393
394fn parse_gen_iv<T: Iterator<Item = Result<String, std::io::Error>>>(
395    iter: &mut Peekable<T>,
396    name: &str,
397) -> ProcResult<Option<usize>> {
398    if iter
399        .peek()
400        .map(|line| line.as_ref().is_ok_and(|line| line.contains("geniv")))
401        .unwrap_or(false)
402    {
403        let val = parse_line(iter, "geniv", name)?;
404        if val != "<none>" {
405            return Ok(Some(expect!(usize::from_str(&val))));
406        }
407    }
408    Ok(None)
409}
410
411#[cfg(test)]
412mod test {
413    use super::*;
414
415    #[test]
416    fn parse_line_correct() {
417        let line = Ok("name         : ghash".to_string());
418        let mut iter = std::iter::once(line);
419        let val = match parse_line(&mut iter, "name", "parse_line_correct") {
420            Ok(val) => val,
421            Err(e) => panic!("{}", e),
422        };
423        assert_eq!("ghash", val);
424    }
425
426    #[test]
427    fn parse_line_incorrect() {
428        let line = Ok("name         : ghash".to_string());
429        let mut iter = std::iter::once(line);
430        let val = match parse_line(&mut iter, "name", "parse_line_incorrect") {
431            Ok(val) => val,
432            Err(e) => panic!("{}", e),
433        };
434        assert_ne!("hash", val);
435    }
436
437    #[test]
438    fn parse_block() {
439        let block = r#"driver       : deflate-generic
440module       : kernel
441priority     : 0
442refcnt       : 2
443selftest     : passed
444internal     : no
445type         : compression"#;
446        let mut iter = block.lines().map(|s| Ok(s.to_string())).peekable();
447        let block = CryptoBlock::from_iter(&mut iter, "deflate");
448        let block = block.expect("Should be have read one block");
449        assert_eq!(block.name, "deflate");
450        assert_eq!(block.driver, "deflate-generic");
451        assert_eq!(block.module, "kernel");
452        assert_eq!(block.priority, 0);
453        assert_eq!(block.ref_count, 2);
454        assert_eq!(block.self_test, SelfTest::Passed);
455        assert_eq!(block.internal, false);
456        assert_eq!(block.crypto_type, Type::Compression);
457    }
458
459    #[test]
460    fn parse_bad_block() {
461        let block = r#"driver       : deflate-generic
462module       : kernel
463priority     : 0
464refcnt       : 2
465selftest     : passed
466internal     : no
467type         : aead"#;
468        let mut iter = block.lines().map(|s| Ok(s.to_string())).peekable();
469        let block = CryptoBlock::from_iter(&mut iter, "deflate");
470        eprintln!("{block:?}");
471        assert!(block.is_err());
472    }
473
474    #[test]
475    fn parse_two() {
476        let block = r#"name         : ccm(aes)
477driver       : ccm_base(ctr(aes-aesni),cbcmac(aes-aesni))
478module       : ccm
479priority     : 300
480refcnt       : 4
481selftest     : passed
482internal     : no
483type         : aead
484async        : no
485blocksize    : 1
486ivsize       : 16
487maxauthsize  : 16
488geniv        : <none>
489
490name         : ctr(aes)
491driver       : ctr(aes-aesni)
492module       : kernel
493priority     : 300
494refcnt       : 4
495selftest     : passed
496internal     : no
497type         : skcipher
498async        : no
499blocksize    : 1
500min keysize  : 16
501max keysize  : 32
502ivsize       : 16
503chunksize    : 16
504walksize     : 16
505
506"#;
507        let blocks = CryptoTable::from_buf_read(block.as_bytes());
508        let blocks = blocks.expect("Should be have read two blocks");
509        assert_eq!(blocks.crypto_blocks.len(), 2);
510    }
511
512    #[test]
513    fn parse_duplicate_name() {
514        let block = r#"name         : deflate
515driver       : deflate-generic
516module       : kernel
517priority     : 0
518refcnt       : 2
519selftest     : passed
520internal     : no
521type         : compression
522
523name         : deflate
524driver       : deflate-non-generic
525module       : kernel
526priority     : 0
527refcnt       : 2
528selftest     : passed
529internal     : no
530type         : compression
531"#;
532        let blocks = CryptoTable::from_buf_read(block.as_bytes());
533        let blocks = blocks.expect("Should be have read two blocks");
534        assert_eq!(blocks.crypto_blocks.len(), 1);
535        let deflate_vec = blocks
536            .crypto_blocks
537            .get("deflate")
538            .expect("Should have created a vec of deflates");
539        assert_eq!(deflate_vec.len(), 2);
540    }
541
542    #[test]
543    fn parse_unknown() {
544        let block = r#"driver       : ccm_base(ctr(aes-aesni),cbcmac(aes-aesni))
545module       : ccm
546priority     : 300
547refcnt       : 4
548selftest     : passed
549internal     : no
550type         : unknown
551key          : val
552key2         : val2
553"#;
554        let mut iter = block.lines().map(|s| Ok(s.to_string())).peekable();
555        let block = CryptoBlock::from_iter(&mut iter, "ccm(aes)");
556        let block = block.expect("Should be have read one block");
557        let mut compare = HashMap::new();
558        compare.insert(String::from("key"), String::from("val"));
559        compare.insert(String::from("key2"), String::from("val2"));
560        compare.insert(String::from("name"), String::from("unknown"));
561        assert_eq!(block.crypto_type, Type::Unknown(Unknown { fields: compare }));
562    }
563
564    #[test]
565    fn parse_unknown_top() {
566        let block = r#"name         : ccm(aes)
567driver       : ccm_base(ctr(aes-aesni),cbcmac(aes-aesni))
568module       : ccm
569priority     : 300
570refcnt       : 4
571selftest     : passed
572internal     : no
573type         : unknown
574key          : val
575key2         : val2
576
577name         : ctr(aes)
578driver       : ctr(aes-aesni)
579module       : kernel
580priority     : 300
581refcnt       : 4
582selftest     : passed
583internal     : no
584type         : skcipher
585async        : no
586blocksize    : 1
587min keysize  : 16
588max keysize  : 32
589ivsize       : 16
590chunksize    : 16
591walksize     : 16
592"#;
593        let blocks = CryptoTable::from_buf_read(block.as_bytes());
594        let blocks = blocks.expect("Should be have read one block");
595        assert_eq!(blocks.crypto_blocks.len(), 2);
596    }
597}