openpgp_card/ocard/
kdf.rs

1// SPDX-FileCopyrightText: 2024 Heiko Schaefer <heiko@schaefer.name>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! Handle transformation of user-provided PINs according to the KDF configuration on the card,
5//! if any.
6
7use secrecy::{ExposeSecret, SecretString, SecretVec};
8use sha2::Digest;
9
10use crate::ocard::data::KdfDo;
11use crate::{Error, PinType};
12
13trait Hasher {
14    /// Update the hash with the given value.
15    fn update(&mut self, _: &[u8]);
16
17    /// Finalize the hash and return the result.
18    fn finish(self: Box<Self>) -> Vec<u8>;
19}
20
21#[derive(Default)]
22pub struct Sha2_256 {
23    inner: sha2::Sha256,
24}
25
26impl Hasher for Sha2_256 {
27    fn update(&mut self, data: &[u8]) {
28        self.inner.update(data);
29    }
30
31    fn finish(self: Box<Self>) -> Vec<u8> {
32        self.inner.finalize().as_slice().to_vec()
33    }
34}
35
36#[derive(Default)]
37pub struct Sha2_512 {
38    inner: sha2::Sha512,
39}
40
41impl Hasher for crate::ocard::kdf::Sha2_512 {
42    fn update(&mut self, data: &[u8]) {
43        self.inner.update(data);
44    }
45
46    fn finish(self: Box<Self>) -> Vec<u8> {
47        self.inner.finalize().as_slice().to_vec()
48    }
49}
50
51/// Map user-provided pw/pin value to a `SecretVec<u8>`.
52///
53/// This performs a KDF transformation, if the KDF mode is enabled on the card.
54pub(crate) fn map_pin(
55    pw: SecretString,
56    pin_type: PinType,
57    kdf_do: Option<&KdfDo>,
58) -> Result<SecretVec<u8>, Error> {
59    match kdf_do {
60        None => {
61            // KDF DO is not set at all -> use the raw pw bytes as PIN
62            Ok(pw.expose_secret().as_bytes().to_vec().into())
63        }
64        Some(kdf) if kdf.kdf_algo() == 0 => {
65            //  KDF algo is "0" -> use the raw pw bytes as PIN
66            Ok(pw.expose_secret().as_bytes().to_vec().into())
67        }
68        Some(kdf) => {
69            // KDF transformation needs to be applied to PIN
70
71            match kdf.kdf_algo() {
72                3 => Ok(itersalt(
73                    pw.expose_secret(),
74                    kdf.hash_algo(),
75                    kdf.iter_count(),
76                    match pin_type {
77                        PinType::Pw1 => kdf.salt_pw1(),
78                        PinType::Rc => kdf.salt_rc(),
79                        PinType::Pw3 => kdf.salt_pw3(),
80                    },
81                )?
82                .into()),
83                _ => Err(Error::UnsupportedFeature(
84                    "The KDF mode on the card is currently unsupported".to_string(),
85                )),
86            }
87        }
88    }
89}
90
91/// see https://www.rfc-editor.org/rfc/rfc4880.html#section-3.7.1.3
92pub(crate) fn itersalt(
93    pw: &str,
94    hash_algo: Option<u8>,
95    count: Option<u32>,
96    salt: Option<&[u8]>,
97) -> Result<Vec<u8>, Error> {
98    let hash_algo = match hash_algo {
99        Some(hash_algo) => hash_algo,
100        None => {
101            return Err(Error::InternalError(
102                "No KDF hash algorithm setting found".to_string(),
103            ))
104        }
105    };
106
107    // number of bytes that should be hashed
108    let mut count = match count {
109        Some(count) => count,
110        None => {
111            return Err(Error::InternalError(
112                "No KDF iteration count setting found".to_string(),
113            ))
114        }
115    } as usize;
116
117    let salt = match salt {
118        Some(salt) => salt,
119        None => {
120            return Err(Error::InternalError(
121                "No KDF salt setting found".to_string(),
122            ))
123        }
124    };
125
126    // set up hasher
127    let mut hasher: Box<dyn Hasher> = match hash_algo {
128        0x08 => Box::<Sha2_256>::default(),
129        0x0A => Box::<Sha2_512>::default(),
130        _ => {
131            return Err(Error::InternalError(
132                "KDF: unsupported hash algorithm setting".to_string(),
133            ))
134        }
135    };
136
137    if count < salt.len() + pw.len() {
138        return Err(Error::InternalError(
139            "KDF: dubiously small count".to_string(),
140        ));
141    }
142
143    // salt and pw must be hashed complete, at least once
144    hasher.update(salt);
145    count -= salt.len();
146
147    hasher.update(pw.as_bytes());
148    count -= pw.len();
149
150    loop {
151        if count >= salt.len() {
152            hasher.update(salt);
153            count -= salt.len();
154        } else {
155            hasher.update(&salt[0..count]);
156            break;
157        }
158
159        if count >= pw.len() {
160            hasher.update(pw.as_bytes());
161            count -= pw.len();
162        } else {
163            hasher.update(&pw.as_bytes()[0..count]);
164            break;
165        }
166    }
167
168    Ok(hasher.finish())
169}
170
171#[test]
172fn test_itersalt() {
173    // Examples from OpenPGP card 3.4.1 "Functional Specification" pdf, page 20
174
175    let user = itersalt(
176        "123456",
177        Some(0x8),
178        Some(100000),
179        Some(&[0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37]),
180    )
181    .expect("itersalt");
182    assert_eq!(
183        user,
184        vec![
185            0x77, 0x37, 0x84, 0xA6, 0x02, 0xB6, 0xC8, 0x1E, 0x3F, 0x09, 0x2F, 0x4D, 0x7D, 0x00,
186            0xE1, 0x7C, 0xC8, 0x22, 0xD8, 0x8F, 0x73, 0x60, 0xFC, 0xF2, 0xD2, 0xEF, 0x2D, 0x9D,
187            0x90, 0x1F, 0x44, 0xB6
188        ]
189    );
190
191    let admin = itersalt(
192        "12345678",
193        Some(0x8),
194        Some(100000),
195        Some(&[0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48]),
196    )
197    .expect("itersalt");
198    assert_eq!(
199        admin,
200        vec![
201            0x26, 0x75, 0xD6, 0x16, 0x4A, 0x0D, 0x48, 0x27, 0xD1, 0xD0, 0x0C, 0x7E, 0xEA, 0x62,
202            0x0D, 0x01, 0x5C, 0x00, 0x03, 0x0A, 0x1C, 0xAB, 0x38, 0xB4, 0xD0, 0xDD, 0x60, 0x0B,
203            0x27, 0xDC, 0x96, 0x30
204        ]
205    );
206}