openpgp_card/ocard/data/
extended_cap.rs

1// SPDX-FileCopyrightText: 2021 Heiko Schaefer <heiko@schaefer.name>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! 4.4.3.7 Extended Capabilities
5
6use std::convert::TryFrom;
7
8use crate::ocard::data::ExtendedCapabilities;
9use crate::Error;
10
11impl ExtendedCapabilities {
12    /// Secure Messaging supported.
13    ///
14    /// (This feature is currently only available in the SmartPGP implementation)
15    pub fn secure_messaging(&self) -> bool {
16        self.secure_messaging
17    }
18
19    /// Support for GET CHALLENGE.
20    ///
21    /// (GET CHALLENGE generates a random number of a specified length on the smart card)
22    pub fn get_challenge(&self) -> bool {
23        self.get_challenge
24    }
25
26    /// Maximum length of random number that can be requested from the card
27    /// (if GET CHALLENGE is supported).
28    ///
29    /// If GET CHALLENGE is not supported, the coding is 0
30    pub fn max_len_challenge(&self) -> u16 {
31        self.max_len_challenge
32    }
33
34    /// Support for Key Import
35    pub fn key_import(&self) -> bool {
36        self.key_import
37    }
38
39    /// PW Status changeable
40    /// (also see [`crate::ocard::data::PWStatusBytes`])
41    pub fn pw_status_change(&self) -> bool {
42        self.pw_status_change
43    }
44
45    /// Support for Private use DOs
46    pub fn private_use_dos(&self) -> bool {
47        self.private_use_dos
48    }
49
50    /// Algorithm attributes changeable
51    /// (also see [`crate::ocard::algorithm::AlgorithmAttributes`])
52    pub fn algo_attrs_changeable(&self) -> bool {
53        self.algo_attrs_changeable
54    }
55
56    /// Support for encryption/decryption with AES
57    pub fn aes(&self) -> bool {
58        self.aes
59    }
60
61    /// KDF-related functionality available
62    pub fn kdf_do(&self) -> bool {
63        self.kdf_do
64    }
65
66    /// Maximum length of Cardholder Certificates
67    pub fn max_len_cardholder_cert(&self) -> u16 {
68        self.max_len_cardholder_cert
69    }
70
71    /// Maximum length of "special DOs"
72    /// (Private Use, Login data, URL, Algorithm attributes, KDF etc.)
73    ///
74    /// (OpenPGP card version 3.x only)
75    pub fn max_len_special_do(&self) -> Option<u16> {
76        self.max_len_special_do
77    }
78
79    /// (Private Use, Login data, URL, Algorithm attributes, KDF etc.)
80    ///
81    /// (OpenPGP card version 3.x only)
82    pub fn pin_block_2_format_support(&self) -> Option<bool> {
83        self.pin_block_2_format_support
84    }
85
86    /// MANAGE SECURITY ENVIRONMENT supported (for DEC and AUT keys).
87    /// (See [`crate::ocard::Transaction::manage_security_environment`])
88    ///
89    /// (OpenPGP card version 3.x only)
90    pub fn mse_command_support(&self) -> Option<bool> {
91        self.mse_command_support
92    }
93
94    /// Only available in OpenPGP card version 2.x
95    ///
96    /// (For OpenPGP card version 3.x, see
97    /// [`crate::ocard::data::ExtendedLengthInfo`])
98    pub(crate) fn max_cmd_len(&self) -> Option<u16> {
99        self.max_cmd_len
100    }
101
102    /// Only available in OpenPGP card version 2.x
103    ///
104    /// (For OpenPGP card version 3.x, see
105    /// [`crate::ocard::data::ExtendedLengthInfo`])
106    pub(crate) fn max_resp_len(&self) -> Option<u16> {
107        self.max_resp_len
108    }
109}
110
111impl TryFrom<(&[u8], u16)> for ExtendedCapabilities {
112    type Error = Error;
113
114    fn try_from((input, version): (&[u8], u16)) -> Result<Self, Self::Error> {
115        // FIXME: check that this fn is not called excessively often
116
117        let version = ((version >> 8) as u8, (version & 0xff) as u8);
118
119        // We only support versions 2.x and 3.x
120        //
121        // Earlier versions have shorter extended capabilities, and are currently unsupported
122        if version.0 != 2 && version.0 != 3 {
123            return Err(Error::UnsupportedFeature(format!(
124                "Card version {:?} is unsupported",
125                version
126            )));
127        }
128
129        // v2.x and v3.x should have 10 byte sized extended caps
130        if input.len() != 10 {
131            return Err(Error::InternalError(format!(
132                "Unexpected ExtendedCapabilities length {}",
133                input.len()
134            )));
135        }
136
137        let b = input[0];
138
139        let secure_messaging = b & 0x80 != 0;
140        let get_challenge = b & 0x40 != 0;
141        let key_import = b & 0x20 != 0;
142        let pw_status_change = b & 0x10 != 0;
143        let private_use_dos = b & 0x08 != 0;
144        let algo_attrs_changeable = b & 0x04 != 0;
145        let aes = b & 0x02 != 0;
146        let kdf_do = b & 0x01 != 0;
147
148        let sm_algo = input[1];
149
150        let max_len_challenge = input[2] as u16 * 256 + input[3] as u16;
151        let max_len_cardholder_cert = input[4] as u16 * 256 + input[5] as u16;
152
153        let mut max_cmd_len = None;
154        let mut max_resp_len = None;
155
156        let mut max_len_special_do = None;
157        let mut pin_block_2_format_support = None;
158        let mut mse_command_support = None;
159
160        if version.0 == 2 {
161            // v2.0 until v2.2
162            max_cmd_len = Some(input[6] as u16 * 256 + input[7] as u16);
163            max_resp_len = Some(input[8] as u16 * 256 + input[9] as u16);
164        } else {
165            // from v3.0
166            max_len_special_do = Some(input[6] as u16 * 256 + input[7] as u16);
167
168            let i8 = input[8];
169            let i9 = input[9];
170
171            if i8 > 1 {
172                return Err(Error::ParseError(format!(
173                    "Illegal value '{i8}' for pin_block_2_format_support"
174                )));
175            }
176
177            pin_block_2_format_support = Some(i8 != 0);
178
179            if i9 > 1 {
180                return Err(Error::ParseError(format!(
181                    "Illegal value '{i9}' for mse_command_support"
182                )));
183            }
184            mse_command_support = Some(i9 != 0);
185        }
186
187        Ok(Self {
188            secure_messaging,
189            get_challenge,
190            key_import,
191            pw_status_change,
192            private_use_dos,
193            algo_attrs_changeable,
194            aes,
195            kdf_do,
196
197            sm_algo,
198            max_len_challenge,
199            max_len_cardholder_cert,
200
201            max_cmd_len,  // v2
202            max_resp_len, // v2
203
204            max_len_special_do,         // v3
205            pin_block_2_format_support, // v3
206            mse_command_support,        // v3
207        })
208    }
209}
210
211#[cfg(test)]
212mod test {
213    use std::convert::TryFrom;
214
215    use hex_literal::hex;
216
217    use crate::ocard::data::extended_cap::ExtendedCapabilities;
218
219    #[test]
220    fn test_yk5() {
221        // YubiKey 5
222        let data = hex!("7d 00 0b fe 08 00 00 ff 00 00");
223
224        let ec = ExtendedCapabilities::try_from((&data[..], 0x0304)).unwrap();
225
226        assert_eq!(
227            ec,
228            ExtendedCapabilities {
229                secure_messaging: false,
230                get_challenge: true,
231                key_import: true,
232                pw_status_change: true,
233                private_use_dos: true,
234                algo_attrs_changeable: true,
235                aes: false,
236                kdf_do: true,
237                sm_algo: 0x0,
238                max_len_challenge: 0xbfe,
239                max_len_cardholder_cert: 0x800,
240                max_cmd_len: None,
241                max_resp_len: None,
242                max_len_special_do: Some(0xff),
243                pin_block_2_format_support: Some(false),
244                mse_command_support: Some(false),
245            }
246        );
247    }
248
249    #[test]
250    fn test_floss21() {
251        // FLOSS shop2.1
252        let data = hex!("7c 00 08 00 08 00 08 00 08 00");
253
254        let ec = ExtendedCapabilities::try_from((&data[..], 0x0201)).unwrap();
255
256        assert_eq!(
257            ec,
258            ExtendedCapabilities {
259                secure_messaging: false,
260                get_challenge: true,
261                key_import: true,
262                pw_status_change: true,
263                private_use_dos: true,
264                algo_attrs_changeable: true,
265                aes: false,
266                kdf_do: false,
267                sm_algo: 0,
268                max_len_challenge: 2048,
269                max_len_cardholder_cert: 2048,
270                max_cmd_len: Some(2048),
271                max_resp_len: Some(2048),
272                max_len_special_do: None,
273                pin_block_2_format_support: None,
274                mse_command_support: None,
275            }
276        );
277    }
278}