xref: /aosp_15_r20/system/security/keystore2/src/sw_keyblob.rs (revision e1997b9af69e3155ead6e072d106a0077849ffba)
1 // Copyright 2023, The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 //! Code for parsing software-backed keyblobs, as emitted by the C++ reference implementation of
16 //! KeyMint.
17 
18 use crate::error::Error;
19 use crate::ks_err;
20 use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
21     Algorithm::Algorithm, BlockMode::BlockMode, Digest::Digest, EcCurve::EcCurve,
22     ErrorCode::ErrorCode, HardwareAuthenticatorType::HardwareAuthenticatorType,
23     KeyFormat::KeyFormat, KeyOrigin::KeyOrigin, KeyParameter::KeyParameter,
24     KeyParameterValue::KeyParameterValue, KeyPurpose::KeyPurpose, PaddingMode::PaddingMode,
25     Tag::Tag, TagType::TagType,
26 };
27 use anyhow::Result;
28 use keystore2_crypto::hmac_sha256;
29 use std::mem::size_of;
30 
31 #[cfg(test)]
32 mod tests;
33 
34 /// Root of trust value.
35 const SOFTWARE_ROOT_OF_TRUST: &[u8] = b"SW";
36 
37 /// Error macro.
38 macro_rules! bloberr {
39     { $($arg:tt)+ } => {
40         anyhow::Error::new(Error::Km(ErrorCode::INVALID_KEY_BLOB)).context(ks_err!($($arg)+))
41     };
42 }
43 
44 /// Get the `KeyParameterValue` associated with a tag from a collection of `KeyParameter`s.
get_tag_value(params: &[KeyParameter], tag: Tag) -> Option<&KeyParameterValue>45 fn get_tag_value(params: &[KeyParameter], tag: Tag) -> Option<&KeyParameterValue> {
46     params.iter().find_map(|kp| if kp.tag == tag { Some(&kp.value) } else { None })
47 }
48 
49 /// Get the [`TagType`] for a [`Tag`].
tag_type(tag: &Tag) -> TagType50 fn tag_type(tag: &Tag) -> TagType {
51     TagType((tag.0 as u32 & 0xf0000000) as i32)
52 }
53 
54 /// Extract key material and combined key characteristics from a legacy authenticated keyblob.
export_key( data: &[u8], params: &[KeyParameter], ) -> Result<(KeyFormat, Vec<u8>, Vec<KeyParameter>)>55 pub fn export_key(
56     data: &[u8],
57     params: &[KeyParameter],
58 ) -> Result<(KeyFormat, Vec<u8>, Vec<KeyParameter>)> {
59     let hidden = hidden_params(params, &[SOFTWARE_ROOT_OF_TRUST]);
60     let KeyBlob { key_material, hw_enforced, sw_enforced } =
61         KeyBlob::new_from_serialized(data, &hidden)?;
62 
63     let mut combined = hw_enforced;
64     combined.extend_from_slice(&sw_enforced);
65 
66     let algo_val =
67         get_tag_value(&combined, Tag::ALGORITHM).ok_or_else(|| bloberr!("No algorithm found!"))?;
68 
69     let format = match algo_val {
70         KeyParameterValue::Algorithm(Algorithm::AES)
71         | KeyParameterValue::Algorithm(Algorithm::TRIPLE_DES)
72         | KeyParameterValue::Algorithm(Algorithm::HMAC) => KeyFormat::RAW,
73         KeyParameterValue::Algorithm(Algorithm::RSA)
74         | KeyParameterValue::Algorithm(Algorithm::EC) => KeyFormat::PKCS8,
75         _ => return Err(bloberr!("Unexpected algorithm {:?}", algo_val)),
76     };
77 
78     let key_material = match (format, algo_val) {
79         (KeyFormat::PKCS8, KeyParameterValue::Algorithm(Algorithm::EC)) => {
80             // Key material format depends on the curve.
81             let curve = get_tag_value(&combined, Tag::EC_CURVE)
82                 .ok_or_else(|| bloberr!("Failed to determine curve for EC key!"))?;
83             match curve {
84                 KeyParameterValue::EcCurve(EcCurve::CURVE_25519) => key_material,
85                 KeyParameterValue::EcCurve(EcCurve::P_224) => {
86                     pkcs8_wrap_nist_key(&key_material, EcCurve::P_224)?
87                 }
88                 KeyParameterValue::EcCurve(EcCurve::P_256) => {
89                     pkcs8_wrap_nist_key(&key_material, EcCurve::P_256)?
90                 }
91                 KeyParameterValue::EcCurve(EcCurve::P_384) => {
92                     pkcs8_wrap_nist_key(&key_material, EcCurve::P_384)?
93                 }
94                 KeyParameterValue::EcCurve(EcCurve::P_521) => {
95                     pkcs8_wrap_nist_key(&key_material, EcCurve::P_521)?
96                 }
97                 _ => {
98                     return Err(bloberr!("Unexpected EC curve {curve:?}"));
99                 }
100             }
101         }
102         (KeyFormat::RAW, _) => key_material,
103         (format, algo) => {
104             return Err(bloberr!(
105                 "Unsupported combination of {format:?} format for {algo:?} algorithm"
106             ));
107         }
108     };
109     Ok((format, key_material, combined))
110 }
111 
112 /// DER-encoded `AlgorithmIdentifier` for a P-224 key.
113 const DER_ALGORITHM_ID_P224: &[u8] = &[
114     0x30, 0x10, // SEQUENCE (AlgorithmIdentifier) {
115     0x06, 0x07, // OBJECT IDENTIFIER (algorithm)
116     0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, // 1.2.840.10045.2.1 (ecPublicKey)
117     0x06, 0x05, // OBJECT IDENTIFIER (param)
118     0x2b, 0x81, 0x04, 0x00, 0x21, //  1.3.132.0.33 (secp224r1) }
119 ];
120 
121 /// DER-encoded `AlgorithmIdentifier` for a P-256 key.
122 const DER_ALGORITHM_ID_P256: &[u8] = &[
123     0x30, 0x13, // SEQUENCE (AlgorithmIdentifier) {
124     0x06, 0x07, // OBJECT IDENTIFIER (algorithm)
125     0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, // 1.2.840.10045.2.1 (ecPublicKey)
126     0x06, 0x08, // OBJECT IDENTIFIER (param)
127     0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, //  1.2.840.10045.3.1.7 (secp256r1) }
128 ];
129 
130 /// DER-encoded `AlgorithmIdentifier` for a P-384 key.
131 const DER_ALGORITHM_ID_P384: &[u8] = &[
132     0x30, 0x10, // SEQUENCE (AlgorithmIdentifier) {
133     0x06, 0x07, // OBJECT IDENTIFIER (algorithm)
134     0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, // 1.2.840.10045.2.1 (ecPublicKey)
135     0x06, 0x05, // OBJECT IDENTIFIER (param)
136     0x2b, 0x81, 0x04, 0x00, 0x22, //  1.3.132.0.34 (secp384r1) }
137 ];
138 
139 /// DER-encoded `AlgorithmIdentifier` for a P-384 key.
140 const DER_ALGORITHM_ID_P521: &[u8] = &[
141     0x30, 0x10, // SEQUENCE (AlgorithmIdentifier) {
142     0x06, 0x07, // OBJECT IDENTIFIER (algorithm)
143     0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, // 1.2.840.10045.2.1 (ecPublicKey)
144     0x06, 0x05, // OBJECT IDENTIFIER (param)
145     0x2b, 0x81, 0x04, 0x00, 0x23, //  1.3.132.0.35 (secp521r1) }
146 ];
147 
148 /// DER-encoded integer value zero.
149 const DER_VERSION_0: &[u8] = &[
150     0x02, // INTEGER
151     0x01, // len
152     0x00, // value 0
153 ];
154 
155 /// Given a NIST curve EC key in the form of a DER-encoded `ECPrivateKey`
156 /// (RFC 5915 s3), wrap it in a DER-encoded PKCS#8 format (RFC 5208 s5).
pkcs8_wrap_nist_key(nist_key: &[u8], curve: EcCurve) -> Result<Vec<u8>>157 fn pkcs8_wrap_nist_key(nist_key: &[u8], curve: EcCurve) -> Result<Vec<u8>> {
158     let der_alg_id = match curve {
159         EcCurve::P_224 => DER_ALGORITHM_ID_P224,
160         EcCurve::P_256 => DER_ALGORITHM_ID_P256,
161         EcCurve::P_384 => DER_ALGORITHM_ID_P384,
162         EcCurve::P_521 => DER_ALGORITHM_ID_P521,
163         _ => return Err(bloberr!("unknown curve {curve:?}")),
164     };
165 
166     // Output format is:
167     //
168     //    PrivateKeyInfo ::= SEQUENCE {
169     //        version                   INTEGER,
170     //        privateKeyAlgorithm       AlgorithmIdentifier,
171     //        privateKey                OCTET STRING,
172     //    }
173     //
174     // Start by building the OCTET STRING so we know its length.
175     let mut nist_key_octet_string = Vec::new();
176     nist_key_octet_string.push(0x04); // OCTET STRING
177     add_der_len(&mut nist_key_octet_string, nist_key.len())?;
178     nist_key_octet_string.extend_from_slice(nist_key);
179 
180     let mut buf = Vec::new();
181     buf.push(0x30); // SEQUENCE
182     add_der_len(&mut buf, DER_VERSION_0.len() + der_alg_id.len() + nist_key_octet_string.len())?;
183     buf.extend_from_slice(DER_VERSION_0);
184     buf.extend_from_slice(der_alg_id);
185     buf.extend_from_slice(&nist_key_octet_string);
186     Ok(buf)
187 }
188 
189 /// Append a DER-encoded length value to the given buffer.
add_der_len(buf: &mut Vec<u8>, len: usize) -> Result<()>190 fn add_der_len(buf: &mut Vec<u8>, len: usize) -> Result<()> {
191     if len <= 0x7f {
192         buf.push(len as u8)
193     } else if len <= 0xff {
194         buf.push(0x81); // One length octet to come
195         buf.push(len as u8);
196     } else if len <= 0xffff {
197         buf.push(0x82); // Two length octets to come
198         buf.push((len >> 8) as u8);
199         buf.push((len & 0xff) as u8);
200     } else {
201         return Err(bloberr!("Unsupported DER length {len}"));
202     }
203     Ok(())
204 }
205 
206 /// Plaintext key blob, with key characteristics.
207 #[derive(PartialEq, Eq)]
208 struct KeyBlob {
209     /// Raw key material.
210     key_material: Vec<u8>,
211     /// Hardware-enforced key characteristics.
212     hw_enforced: Vec<KeyParameter>,
213     /// Software-enforced key characteristics.
214     sw_enforced: Vec<KeyParameter>,
215 }
216 
217 impl KeyBlob {
218     /// Key blob version.
219     const KEY_BLOB_VERSION: u8 = 0;
220 
221     /// Hard-coded HMAC key used for keyblob authentication.
222     const LEGACY_HMAC_KEY: &'static [u8] = b"IntegrityAssuredBlob0\0";
223 
224     /// Size (in bytes) of appended MAC.
225     const MAC_LEN: usize = 8;
226 
227     /// Parse a serialized [`KeyBlob`].
new_from_serialized(mut data: &[u8], hidden: &[KeyParameter]) -> Result<Self>228     fn new_from_serialized(mut data: &[u8], hidden: &[KeyParameter]) -> Result<Self> {
229         // Keyblob needs to be at least long enough for:
230         // - version byte,
231         // - 4-byte len for key material
232         // - 4-byte len for hw_enforced params
233         // - 4-byte len for sw_enforced params
234         // - MAC tag.
235         if data.len() < (1 + 3 * size_of::<u32>() + Self::MAC_LEN) {
236             return Err(bloberr!("blob not long enough (len = {})", data.len()));
237         }
238 
239         // Check the HMAC in the last 8 bytes before doing anything else.
240         let mac = &data[data.len() - Self::MAC_LEN..];
241         let computed_mac = Self::compute_hmac(&data[..data.len() - Self::MAC_LEN], hidden)?;
242         if mac != computed_mac {
243             return Err(bloberr!("invalid key blob"));
244         }
245 
246         let version = consume_u8(&mut data)?;
247         if version != Self::KEY_BLOB_VERSION {
248             return Err(bloberr!("unexpected blob version {}", version));
249         }
250         let key_material = consume_vec(&mut data)?;
251         let hw_enforced = deserialize_params(&mut data)?;
252         let sw_enforced = deserialize_params(&mut data)?;
253 
254         // Should just be the (already-checked) MAC left.
255         let rest = &data[Self::MAC_LEN..];
256         if !rest.is_empty() {
257             return Err(bloberr!("extra data (len {})", rest.len()));
258         }
259         Ok(KeyBlob { key_material, hw_enforced, sw_enforced })
260     }
261 
262     /// Compute the authentication HMAC for a KeyBlob. This is built as:
263     ///   HMAC-SHA256(HK, data || serialize(hidden))
264     /// with HK = b"IntegrityAssuredBlob0\0".
compute_hmac(data: &[u8], hidden: &[KeyParameter]) -> Result<Vec<u8>>265     fn compute_hmac(data: &[u8], hidden: &[KeyParameter]) -> Result<Vec<u8>> {
266         let hidden_data = serialize_params(hidden)?;
267         let mut combined = data.to_vec();
268         combined.extend_from_slice(&hidden_data);
269         let mut tag = hmac_sha256(Self::LEGACY_HMAC_KEY, &combined)?;
270         tag.truncate(Self::MAC_LEN);
271         Ok(tag)
272     }
273 }
274 
275 /// Build the parameters that are used as the hidden input to HMAC calculations:
276 /// - `ApplicationId(data)` if present
277 /// - `ApplicationData(data)` if present
278 /// - (repeated) `RootOfTrust(rot)` where `rot` is a hardcoded piece of root of trust information.
hidden_params(params: &[KeyParameter], rots: &[&[u8]]) -> Vec<KeyParameter>279 fn hidden_params(params: &[KeyParameter], rots: &[&[u8]]) -> Vec<KeyParameter> {
280     let mut results = Vec::new();
281     if let Some(app_id) = get_tag_value(params, Tag::APPLICATION_ID) {
282         results.push(KeyParameter { tag: Tag::APPLICATION_ID, value: app_id.clone() });
283     }
284     if let Some(app_data) = get_tag_value(params, Tag::APPLICATION_DATA) {
285         results.push(KeyParameter { tag: Tag::APPLICATION_DATA, value: app_data.clone() });
286     }
287     for rot in rots {
288         results.push(KeyParameter {
289             tag: Tag::ROOT_OF_TRUST,
290             value: KeyParameterValue::Blob(rot.to_vec()),
291         });
292     }
293     results
294 }
295 
296 /// Retrieve a `u8` from the start of the given slice, if possible.
consume_u8(data: &mut &[u8]) -> Result<u8>297 fn consume_u8(data: &mut &[u8]) -> Result<u8> {
298     match data.first() {
299         Some(b) => {
300             *data = &(*data)[1..];
301             Ok(*b)
302         }
303         None => Err(bloberr!("failed to find 1 byte")),
304     }
305 }
306 
307 /// Move past a bool value from the start of the given slice, if possible.
308 /// Bool values should only be included if `true`, so fail if the value
309 /// is anything other than 1.
consume_bool(data: &mut &[u8]) -> Result<bool>310 fn consume_bool(data: &mut &[u8]) -> Result<bool> {
311     let b = consume_u8(data)?;
312     if b == 0x01 {
313         Ok(true)
314     } else {
315         Err(bloberr!("bool value other than 1 encountered"))
316     }
317 }
318 
319 /// Retrieve a (host-ordered) `u32` from the start of the given slice, if possible.
consume_u32(data: &mut &[u8]) -> Result<u32>320 fn consume_u32(data: &mut &[u8]) -> Result<u32> {
321     const LEN: usize = size_of::<u32>();
322     if data.len() < LEN {
323         return Err(bloberr!("failed to find {LEN} bytes"));
324     }
325     let chunk: [u8; LEN] = data[..LEN].try_into().unwrap(); // safe: just checked
326     *data = &(*data)[LEN..];
327     Ok(u32::from_ne_bytes(chunk))
328 }
329 
330 /// Retrieve a (host-ordered) `i32` from the start of the given slice, if possible.
consume_i32(data: &mut &[u8]) -> Result<i32>331 fn consume_i32(data: &mut &[u8]) -> Result<i32> {
332     const LEN: usize = size_of::<i32>();
333     if data.len() < LEN {
334         return Err(bloberr!("failed to find {LEN} bytes"));
335     }
336     let chunk: [u8; LEN] = data[..LEN].try_into().unwrap(); // safe: just checked
337     *data = &(*data)[4..];
338     Ok(i32::from_ne_bytes(chunk))
339 }
340 
341 /// Retrieve a (host-ordered) `i64` from the start of the given slice, if possible.
consume_i64(data: &mut &[u8]) -> Result<i64>342 fn consume_i64(data: &mut &[u8]) -> Result<i64> {
343     const LEN: usize = size_of::<i64>();
344     if data.len() < LEN {
345         return Err(bloberr!("failed to find {LEN} bytes"));
346     }
347     let chunk: [u8; LEN] = data[..LEN].try_into().unwrap(); // safe: just checked
348     *data = &(*data)[LEN..];
349     Ok(i64::from_ne_bytes(chunk))
350 }
351 
352 /// Retrieve a vector of bytes from the start of the given slice, if possible,
353 /// with the length of the data expected to appear as a host-ordered `u32` prefix.
consume_vec(data: &mut &[u8]) -> Result<Vec<u8>>354 fn consume_vec(data: &mut &[u8]) -> Result<Vec<u8>> {
355     let len = consume_u32(data)? as usize;
356     if len > data.len() {
357         return Err(bloberr!("failed to find {} bytes", len));
358     }
359     let result = data[..len].to_vec();
360     *data = &(*data)[len..];
361     Ok(result)
362 }
363 
364 /// Retrieve the contents of a tag of `TagType::Bytes`.  The `data` parameter holds
365 /// the as-yet unparsed data, and a length and offset are read from this (and consumed).
366 /// This length and offset refer to a location in the combined `blob_data`; however,
367 /// the offset is expected to be the next unconsumed chunk of `blob_data`, as indicated
368 /// by `next_blob_offset` (which itself is updated as a result of consuming the data).
consume_blob( data: &mut &[u8], next_blob_offset: &mut usize, blob_data: &[u8], ) -> Result<Vec<u8>>369 fn consume_blob(
370     data: &mut &[u8],
371     next_blob_offset: &mut usize,
372     blob_data: &[u8],
373 ) -> Result<Vec<u8>> {
374     let data_len = consume_u32(data)? as usize;
375     let data_offset = consume_u32(data)? as usize;
376     // Expect the blob data to come from the next offset in the initial blob chunk.
377     if data_offset != *next_blob_offset {
378         return Err(bloberr!("got blob offset {} instead of {}", data_offset, next_blob_offset));
379     }
380     if (data_offset + data_len) > blob_data.len() {
381         return Err(bloberr!(
382             "blob at offset [{}..{}+{}] goes beyond blob data size {}",
383             data_offset,
384             data_offset,
385             data_len,
386             blob_data.len(),
387         ));
388     }
389 
390     let slice = &blob_data[data_offset..data_offset + data_len];
391     *next_blob_offset += data_len;
392     Ok(slice.to_vec())
393 }
394 
395 /// Deserialize a collection of [`KeyParam`]s in legacy serialized format. The provided slice is
396 /// modified to contain the unconsumed part of the data.
deserialize_params(data: &mut &[u8]) -> Result<Vec<KeyParameter>>397 fn deserialize_params(data: &mut &[u8]) -> Result<Vec<KeyParameter>> {
398     let blob_data_size = consume_u32(data)? as usize;
399     if blob_data_size > data.len() {
400         return Err(bloberr!(
401             "blob data size {} bigger than data (len={})",
402             blob_data_size,
403             data.len()
404         ));
405     }
406 
407     let blob_data = &data[..blob_data_size];
408     let mut next_blob_offset = 0;
409 
410     // Move past the blob data.
411     *data = &data[blob_data_size..];
412 
413     let param_count = consume_u32(data)? as usize;
414     let param_size = consume_u32(data)? as usize;
415     if param_size > data.len() {
416         return Err(bloberr!(
417             "size mismatch 4+{}+4+4+{} > {}",
418             blob_data_size,
419             param_size,
420             data.len()
421         ));
422     }
423 
424     let mut results = Vec::new();
425     for _i in 0..param_count {
426         let tag_num = consume_u32(data)? as i32;
427         let tag = Tag(tag_num);
428         let value = match tag_type(&tag) {
429             TagType::INVALID => return Err(bloberr!("invalid tag {:?} encountered", tag)),
430             TagType::ENUM | TagType::ENUM_REP => {
431                 let val = consume_i32(data)?;
432                 match tag {
433                     Tag::ALGORITHM => KeyParameterValue::Algorithm(Algorithm(val)),
434                     Tag::BLOCK_MODE => KeyParameterValue::BlockMode(BlockMode(val)),
435                     Tag::PADDING => KeyParameterValue::PaddingMode(PaddingMode(val)),
436                     Tag::DIGEST | Tag::RSA_OAEP_MGF_DIGEST => {
437                         KeyParameterValue::Digest(Digest(val))
438                     }
439                     Tag::EC_CURVE => KeyParameterValue::EcCurve(EcCurve(val)),
440                     Tag::ORIGIN => KeyParameterValue::Origin(KeyOrigin(val)),
441                     Tag::PURPOSE => KeyParameterValue::KeyPurpose(KeyPurpose(val)),
442                     Tag::USER_AUTH_TYPE => {
443                         KeyParameterValue::HardwareAuthenticatorType(HardwareAuthenticatorType(val))
444                     }
445                     _ => KeyParameterValue::Integer(val),
446                 }
447             }
448             TagType::UINT | TagType::UINT_REP => KeyParameterValue::Integer(consume_i32(data)?),
449             TagType::ULONG | TagType::ULONG_REP => {
450                 KeyParameterValue::LongInteger(consume_i64(data)?)
451             }
452             TagType::DATE => KeyParameterValue::DateTime(consume_i64(data)?),
453             TagType::BOOL => KeyParameterValue::BoolValue(consume_bool(data)?),
454             TagType::BIGNUM | TagType::BYTES => {
455                 KeyParameterValue::Blob(consume_blob(data, &mut next_blob_offset, blob_data)?)
456             }
457             _ => return Err(bloberr!("unexpected tag type for {:?}", tag)),
458         };
459         results.push(KeyParameter { tag, value });
460     }
461 
462     Ok(results)
463 }
464 
465 /// Serialize a collection of [`KeyParameter`]s into a format that is compatible with previous
466 /// implementations:
467 ///
468 /// ```text
469 /// [0..4]              Size B of `TagType::Bytes` data, in host order.
470 /// [4..4+B]      (*)   Concatenated contents of each `TagType::Bytes` tag.
471 /// [4+B..4+B+4]        Count N of the number of parameters, in host order.
472 /// [8+B..8+B+4]        Size Z of encoded parameters.
473 /// [12+B..12+B+Z]      Serialized parameters one after another.
474 /// ```
475 ///
476 /// Individual parameters are serialized in the last chunk as:
477 ///
478 /// ```text
479 /// [0..4]              Tag number, in host order.
480 /// Followed by one of the following depending on the tag's `TagType`; all integers in host order:
481 ///   [4..5]            Bool value (`TagType::Bool`)
482 ///   [4..8]            i32 values (`TagType::Uint[Rep]`, `TagType::Enum[Rep]`)
483 ///   [4..12]           i64 values, in host order (`TagType::UlongRep`, `TagType::Date`)
484 ///   [4..8] + [8..12]  Size + offset of data in (*) above (`TagType::Bytes`, `TagType::Bignum`)
485 /// ```
serialize_params(params: &[KeyParameter]) -> Result<Vec<u8>>486 fn serialize_params(params: &[KeyParameter]) -> Result<Vec<u8>> {
487     // First 4 bytes are the length of the combined [`TagType::Bytes`] data; come back to set that
488     // in a moment.
489     let mut result = vec![0; 4];
490 
491     // Next append the contents of all of the [`TagType::Bytes`] data.
492     let mut blob_size = 0u32;
493     for param in params {
494         let tag_type = tag_type(&param.tag);
495         if let KeyParameterValue::Blob(v) = &param.value {
496             if tag_type != TagType::BIGNUM && tag_type != TagType::BYTES {
497                 return Err(bloberr!("unexpected tag type for tag {:?} with blob", param.tag));
498             }
499             result.extend_from_slice(v);
500             blob_size += v.len() as u32;
501         }
502     }
503     // Go back and fill in the combined blob length in native order at the start.
504     result[..4].clone_from_slice(&blob_size.to_ne_bytes());
505 
506     result.extend_from_slice(&(params.len() as u32).to_ne_bytes());
507 
508     let params_size_offset = result.len();
509     result.extend_from_slice(&[0u8; 4]); // placeholder for size of elements
510     let first_param_offset = result.len();
511     let mut blob_offset = 0u32;
512     for param in params {
513         result.extend_from_slice(&(param.tag.0 as u32).to_ne_bytes());
514         match &param.value {
515             KeyParameterValue::Invalid(_v) => {
516                 return Err(bloberr!("invalid tag found in {:?}", param))
517             }
518 
519             // Enum-holding variants.
520             KeyParameterValue::Algorithm(v) => {
521                 result.extend_from_slice(&(v.0 as u32).to_ne_bytes())
522             }
523             KeyParameterValue::BlockMode(v) => {
524                 result.extend_from_slice(&(v.0 as u32).to_ne_bytes())
525             }
526             KeyParameterValue::PaddingMode(v) => {
527                 result.extend_from_slice(&(v.0 as u32).to_ne_bytes())
528             }
529             KeyParameterValue::Digest(v) => result.extend_from_slice(&(v.0 as u32).to_ne_bytes()),
530             KeyParameterValue::EcCurve(v) => result.extend_from_slice(&(v.0 as u32).to_ne_bytes()),
531             KeyParameterValue::Origin(v) => result.extend_from_slice(&(v.0 as u32).to_ne_bytes()),
532             KeyParameterValue::KeyPurpose(v) => {
533                 result.extend_from_slice(&(v.0 as u32).to_ne_bytes())
534             }
535             KeyParameterValue::HardwareAuthenticatorType(v) => {
536                 result.extend_from_slice(&(v.0 as u32).to_ne_bytes())
537             }
538 
539             // Value-holding variants.
540             KeyParameterValue::Integer(v) => result.extend_from_slice(&(*v as u32).to_ne_bytes()),
541             KeyParameterValue::BoolValue(_v) => result.push(0x01u8),
542             KeyParameterValue::LongInteger(v) | KeyParameterValue::DateTime(v) => {
543                 result.extend_from_slice(&(*v as u64).to_ne_bytes())
544             }
545             KeyParameterValue::Blob(v) => {
546                 let blob_len = v.len() as u32;
547                 result.extend_from_slice(&blob_len.to_ne_bytes());
548                 result.extend_from_slice(&blob_offset.to_ne_bytes());
549                 blob_offset += blob_len;
550             }
551 
552             _ => return Err(bloberr!("unknown value found in {:?}", param)),
553         }
554     }
555     let serialized_size = (result.len() - first_param_offset) as u32;
556 
557     // Go back and fill in the total serialized size.
558     result[params_size_offset..params_size_offset + 4]
559         .clone_from_slice(&serialized_size.to_ne_bytes());
560     Ok(result)
561 }
562