blob: dc1d0c9e4442ace73978b60e03bc4a0f187baf80 [file]
// Copyright 2022, The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Helper functionality for working with tags.
use crate::{
crypto,
crypto::{rsa::DecryptionMode, *},
km_err, try_to_vec, vec_try_with_capacity, Error, FallibleAllocExt,
};
use alloc::vec::Vec;
use kmr_wire::{
keymint::{
Algorithm, BlockMode, Digest, EcCurve, ErrorCode, KeyCharacteristics, KeyFormat, KeyParam,
KeyPurpose, PaddingMode, SecurityLevel, Tag, DEFAULT_CERT_SERIAL, DEFAULT_CERT_SUBJECT,
},
KeySizeInBits,
};
use log::{info, warn};
mod info;
pub use info::*;
pub mod legacy;
#[cfg(test)]
mod tests;
/// The set of tags that are directly copied from key generation/import parameters to
/// key characteristics without being checked.
pub const UNPOLICED_COPYABLE_TAGS: &[Tag] = &[
Tag::RollbackResistance,
Tag::EarlyBootOnly,
Tag::MaxUsesPerBoot,
Tag::UserSecureId, // repeatable
Tag::NoAuthRequired,
Tag::UserAuthType,
Tag::AuthTimeout,
Tag::TrustedUserPresenceRequired,
Tag::TrustedConfirmationRequired,
Tag::UnlockedDeviceRequired,
Tag::StorageKey,
];
/// Indication of whether secure storage is available.
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum SecureStorage {
/// Device has secure storage.
Available,
/// Device does not have secure storage.
Unavailable,
}
/// Macro to retrieve a copy of the (single) value of a tag in a collection of `KeyParam`s. There
/// can be only one. Only works for variants whose data type implements `Copy`.
#[macro_export]
macro_rules! get_tag_value {
{ $params:expr, $variant:ident, $err:expr } => {
{
let mut result = None;
let mut count = 0;
for param in $params {
if let kmr_wire::keymint::KeyParam::$variant(v) = param {
count += 1;
result = Some(*v);
}
}
match count {
0 => Err($crate::km_verr!($err, "missing tag {}", stringify!($variant))),
1 => Ok(result.unwrap()), /* safe: count=1 => exists */
_ => Err($crate::km_verr!($err, "duplicate tag {}", stringify!($variant))),
}
}
}
}
/// Macro to retrieve the value of an optional single-valued tag in a collection of `KeyParam`s. It
/// may or may not be present, but multiple instances of the tag are assumed to be invalid.
#[macro_export]
macro_rules! get_opt_tag_value {
{ $params:expr, $variant:ident } => {
get_opt_tag_value!($params, $variant, InvalidTag)
};
{ $params:expr, $variant:ident, $dup_error:ident } => {
{
let mut result = None;
let mut count = 0;
for param in $params {
if let kmr_wire::keymint::KeyParam::$variant(v) = param {
count += 1;
result = Some(v);
}
}
match count {
0 => Ok(None),
1 => Ok(Some(result.unwrap())), /* safe: count=1 => exists */
_ => Err($crate::km_err!($dup_error, "duplicate tag {}", stringify!($variant))),
}
}
}
}
/// Macro to retrieve a `bool` tag value, returning `false` if the tag is absent
#[macro_export]
macro_rules! get_bool_tag_value {
{ $params:expr, $variant:ident } => {
{
let mut count = 0;
for param in $params {
if let kmr_wire::keymint::KeyParam::$variant = param {
count += 1;
}
}
match count {
0 => Ok(false),
1 => Ok(true),
_ => Err($crate::km_err!(InvalidTag, "duplicate tag {}", stringify!($variant))),
}
}
}
}
/// Macro to check a collection of `KeyParam`s holds a value matching the given value.
#[macro_export]
macro_rules! contains_tag_value {
{ $params:expr, $variant:ident, $value:expr } => {
{
let mut found = false;
for param in $params {
if let kmr_wire::keymint::KeyParam::$variant(v) = param {
if *v == $value {
found = true;
}
}
}
found
}
}
}
/// Check that a set of [`KeyParam`]s is valid when considered as key characteristics.
pub fn characteristics_valid(characteristics: &[KeyParam]) -> Result<(), Error> {
let mut dup_checker = DuplicateTagChecker::default();
for param in characteristics {
let tag = param.tag();
dup_checker.add(tag)?;
if info(tag)?.characteristic == Characteristic::NotKeyCharacteristic {
return Err(
km_err!(InvalidKeyBlob, "tag {:?} is not a valid key characteristic", tag,),
);
}
}
Ok(())
}
/// Copy anything in `src` that matches `tags` into `dest`. Fails if any non-repeatable
/// tags occur more than once with a different value.
pub fn transcribe_tags(
dest: &mut Vec<KeyParam>,
src: &[KeyParam],
tags: &[Tag],
) -> Result<(), Error> {
let mut dup_checker = DuplicateTagChecker::default();
for param in src {
let tag = param.tag();
dup_checker.add(tag)?;
if tags.iter().any(|t| *t == tag) {
dest.try_push(param.clone())?;
}
}
Ok(())
}
/// Get the configured algorithm from a set of parameters.
pub fn get_algorithm(params: &[KeyParam]) -> Result<Algorithm, Error> {
get_tag_value!(params, Algorithm, ErrorCode::UnsupportedAlgorithm)
}
/// Get the configured block mode from a set of parameters.
pub fn get_block_mode(params: &[KeyParam]) -> Result<BlockMode, Error> {
get_tag_value!(params, BlockMode, ErrorCode::UnsupportedBlockMode)
}
/// Get the configured padding mode from a set of parameters.
pub fn get_padding_mode(params: &[KeyParam]) -> Result<PaddingMode, Error> {
get_tag_value!(params, Padding, ErrorCode::UnsupportedPaddingMode)
}
/// Get the configured digest from a set of parameters.
pub fn get_digest(params: &[KeyParam]) -> Result<Digest, Error> {
get_tag_value!(params, Digest, ErrorCode::UnsupportedDigest)
}
/// Get the configured elliptic curve from a set of parameters.
pub fn get_ec_curve(params: &[KeyParam]) -> Result<EcCurve, Error> {
get_tag_value!(params, EcCurve, ErrorCode::UnsupportedKeySize)
}
/// Get the configured MGF digest from a set of parameters. If no MGF digest is specified,
/// a default value of SHA1 is returned.
pub fn get_mgf_digest(params: &[KeyParam]) -> Result<Digest, Error> {
Ok(*get_opt_tag_value!(params, RsaOaepMgfDigest)?.unwrap_or(&Digest::Sha1))
}
/// Get the certificate serial number from a set of parameters, falling back to default value of 1
/// if not specified
pub fn get_cert_serial(params: &[KeyParam]) -> Result<&[u8], Error> {
Ok(get_opt_tag_value!(params, CertificateSerial)?
.map(Vec::as_ref)
.unwrap_or(DEFAULT_CERT_SERIAL))
}
/// Return the set of key parameters at the provided security level.
pub fn characteristics_at(
chars: &[KeyCharacteristics],
sec_level: SecurityLevel,
) -> Result<&[KeyParam], Error> {
let mut result: Option<&[KeyParam]> = None;
for chars in chars {
if chars.security_level != sec_level {
continue;
}
if result.is_none() {
result = Some(&chars.authorizations);
} else {
return Err(km_err!(InvalidKeyBlob, "multiple key characteristics at {:?}", sec_level));
}
}
result.ok_or_else(|| {
km_err!(InvalidKeyBlob, "no parameters at security level {:?} found", sec_level)
})
}
/// Get the certificate subject from a set of parameters, falling back to a default if not
/// specified.
pub fn get_cert_subject(params: &[KeyParam]) -> Result<&[u8], Error> {
Ok(get_opt_tag_value!(params, CertificateSubject)?
.map(Vec::as_ref)
.unwrap_or(DEFAULT_CERT_SUBJECT))
}
/// Build the parameters that are used as the hidden input to KEK derivation calculations:
/// - `ApplicationId(data)` if present
/// - `ApplicationData(data)` if present
/// - `RootOfTrust(rot)` where `rot` is a hardcoded root of trust
pub fn hidden(params: &[KeyParam], rot: &[u8]) -> Result<Vec<KeyParam>, Error> {
let mut results = vec_try_with_capacity!(3)?;
if let Ok(Some(app_id)) = get_opt_tag_value!(params, ApplicationId) {
results.push(KeyParam::ApplicationId(try_to_vec(app_id)?));
}
if let Ok(Some(app_data)) = get_opt_tag_value!(params, ApplicationData) {
results.push(KeyParam::ApplicationData(try_to_vec(app_data)?));
}
results.push(KeyParam::RootOfTrust(try_to_vec(rot)?));
Ok(results)
}
/// Build the set of key characteristics for a key that is about to be generated,
/// checking parameter validity along the way. Also return the information needed for key
/// generation.
pub fn extract_key_gen_characteristics(
secure_storage: SecureStorage,
params: &[KeyParam],
sec_level: SecurityLevel,
) -> Result<(Vec<KeyCharacteristics>, KeyGenInfo), Error> {
let keygen_info = match get_algorithm(params)? {
Algorithm::Rsa => check_rsa_gen_params(params, sec_level),
Algorithm::Ec => check_ec_gen_params(params, sec_level),
Algorithm::Aes => check_aes_gen_params(params, sec_level),
Algorithm::TripleDes => check_3des_gen_params(params),
Algorithm::Hmac => check_hmac_gen_params(params, sec_level),
}?;
Ok((extract_key_characteristics(secure_storage, params, &[], sec_level)?, keygen_info))
}
/// Build the set of key characteristics for a key that is about to be imported,
/// checking parameter validity along the way.
pub fn extract_key_import_characteristics(
imp: &crypto::Implementation,
secure_storage: SecureStorage,
params: &[KeyParam],
sec_level: SecurityLevel,
key_format: KeyFormat,
key_data: &[u8],
) -> Result<(Vec<KeyCharacteristics>, KeyMaterial), Error> {
let (deduced_params, key_material) = match get_algorithm(params)? {
Algorithm::Rsa => {
check_rsa_import_params(&*imp.rsa, params, sec_level, key_format, key_data)
}
Algorithm::Ec => check_ec_import_params(&*imp.ec, params, sec_level, key_format, key_data),
Algorithm::Aes => {
check_aes_import_params(&*imp.aes, params, sec_level, key_format, key_data)
}
Algorithm::TripleDes => check_3des_import_params(&*imp.des, params, key_format, key_data),
Algorithm::Hmac => {
check_hmac_import_params(&*imp.hmac, params, sec_level, key_format, key_data)
}
}?;
Ok((
extract_key_characteristics(secure_storage, params, &deduced_params, sec_level)?,
key_material,
))
}
/// Build the set of key characteristics for a key that is about to be generated or imported,
/// checking parameter validity along the way. The `extra_params` argument provides additional
/// parameters on top of `params`, such as those deduced from imported key material.
fn extract_key_characteristics(
secure_storage: SecureStorage,
params: &[KeyParam],
extra_params: &[KeyParam],
sec_level: SecurityLevel,
) -> Result<Vec<KeyCharacteristics>, Error> {
// Separately accumulate any characteristics that are policed by Keystore.
let mut chars = Vec::new();
let mut keystore_chars = Vec::new();
for param in params.iter().chain(extra_params) {
let tag = param.tag();
// Input params should not contain anything that KeyMint adds itself.
if AUTO_ADDED_CHARACTERISTICS.contains(&tag) {
return Err(km_err!(InvalidTag, "KeyMint-added tag included on key generation/import"));
}
if sec_level == SecurityLevel::Strongbox
&& [Tag::MaxUsesPerBoot, Tag::RollbackResistance].contains(&tag)
{
// StrongBox does not support tags that require per-key storage.
return Err(km_err!(InvalidTag, "tag {:?} not allowed in StrongBox", param.tag()));
}
// UsageCountLimit is peculiar. If its value is > 1, it should be Keystore-enforced.
// If its value is = 1, then it is KeyMint-enforced if secure storage is available,
// and Keystore-enforced otherwise.
if let KeyParam::UsageCountLimit(use_limit) = param {
match (use_limit, secure_storage) {
(1, SecureStorage::Available) => {
chars.try_push(KeyParam::UsageCountLimit(*use_limit))?
}
(1, SecureStorage::Unavailable) | (_, _) => {
keystore_chars.try_push(KeyParam::UsageCountLimit(*use_limit))?
}
}
}
if KEYMINT_ENFORCED_CHARACTERISTICS.contains(&tag) {
chars.try_push(param.clone())?;
} else if KEYSTORE_ENFORCED_CHARACTERISTICS.contains(&tag) {
keystore_chars.try_push(param.clone())?;
}
}
reject_incompatible_auth(&chars)?;
// Use the same sort order for tags as was previously used.
chars.sort_by(legacy::param_compare);
keystore_chars.sort_by(legacy::param_compare);
let mut result = Vec::new();
result.try_push(KeyCharacteristics { security_level: sec_level, authorizations: chars })?;
if !keystore_chars.is_empty() {
result.try_push(KeyCharacteristics {
security_level: SecurityLevel::Keystore,
authorizations: keystore_chars,
})?;
}
Ok(result)
}
/// Check that an RSA key size is valid.
fn check_rsa_key_size(key_size: KeySizeInBits, sec_level: SecurityLevel) -> Result<(), Error> {
// StrongBox only supports 2048-bit keys.
match key_size {
KeySizeInBits(512) if sec_level != SecurityLevel::Strongbox => Ok(()),
KeySizeInBits(768) if sec_level != SecurityLevel::Strongbox => Ok(()),
KeySizeInBits(1024) if sec_level != SecurityLevel::Strongbox => Ok(()),
KeySizeInBits(2048) => Ok(()),
KeySizeInBits(3072) if sec_level != SecurityLevel::Strongbox => Ok(()),
KeySizeInBits(4096) if sec_level != SecurityLevel::Strongbox => Ok(()),
_ => Err(km_err!(UnsupportedKeySize, "unsupported KEY_SIZE {:?} bits for RSA", key_size)),
}
}
/// Check RSA key generation parameter validity.
fn check_rsa_gen_params(
params: &[KeyParam],
sec_level: SecurityLevel,
) -> Result<KeyGenInfo, Error> {
// For key generation, size and public exponent must be explicitly specified.
let key_size = get_tag_value!(params, KeySize, ErrorCode::UnsupportedKeySize)?;
check_rsa_key_size(key_size, sec_level)?;
let public_exponent = get_tag_value!(params, RsaPublicExponent, ErrorCode::InvalidArgument)?;
check_rsa_params(params)?;
Ok(KeyGenInfo::Rsa(key_size, public_exponent))
}
/// Check RSA key import parameter validity. Return the key material along with any key generation
/// parameters that have been deduced from the key material (but which are not present in the input
/// key parameters).
fn check_rsa_import_params(
rsa: &dyn Rsa,
params: &[KeyParam],
sec_level: SecurityLevel,
key_format: KeyFormat,
key_data: &[u8],
) -> Result<(Vec<KeyParam>, KeyMaterial), Error> {
// Deduce key size and exponent from import data.
if key_format != KeyFormat::Pkcs8 {
return Err(km_err!(
UnsupportedKeyFormat,
"unsupported import format {:?}, expect PKCS8",
key_format
));
}
let (key, key_size, public_exponent) = rsa.import_pkcs8_key(key_data, params)?;
// If key size or exponent are explicitly specified, they must match. If they were not
// specified, we emit them.
let mut deduced_chars = Vec::new();
match get_opt_tag_value!(params, KeySize)? {
Some(param_key_size) => {
if *param_key_size != key_size {
return Err(km_err!(
ImportParameterMismatch,
"specified KEY_SIZE {:?} bits != actual key size {:?} for PKCS8 import",
param_key_size,
key_size
));
}
}
None => deduced_chars.try_push(KeyParam::KeySize(key_size))?,
}
match get_opt_tag_value!(params, RsaPublicExponent)? {
Some(param_public_exponent) => {
if *param_public_exponent != public_exponent {
return Err(km_err!(
ImportParameterMismatch,
"specified RSA_PUBLIC_EXPONENT {:?} != actual exponent {:?} for PKCS8 import",
param_public_exponent,
public_exponent,
));
}
}
None => deduced_chars.try_push(KeyParam::RsaPublicExponent(public_exponent))?,
}
check_rsa_key_size(key_size, sec_level)?;
check_rsa_params(params)?;
Ok((deduced_chars, key))
}
/// Check the parameter validity for an RSA key that is about to be generated or imported.
fn check_rsa_params(params: &[KeyParam]) -> Result<(), Error> {
let mut seen_attest = false;
let mut seen_non_attest = false;
for param in params {
if let KeyParam::Purpose(purpose) = param {
match purpose {
KeyPurpose::Sign | KeyPurpose::Decrypt | KeyPurpose::WrapKey => {
seen_non_attest = true
}
KeyPurpose::AttestKey => seen_attest = true,
KeyPurpose::Verify | KeyPurpose::Encrypt => {} // public key operations
KeyPurpose::AgreeKey => {
warn!("Generating RSA key with invalid purpose {:?}", purpose)
}
}
}
}
if seen_attest && seen_non_attest {
return Err(km_err!(
IncompatiblePurpose,
"keys with ATTEST_KEY must have no other purpose"
));
}
Ok(())
}
/// Check EC key generation parameter validity.
fn check_ec_gen_params(params: &[KeyParam], sec_level: SecurityLevel) -> Result<KeyGenInfo, Error> {
// For key generation, the curve must be explicitly specified.
let ec_curve = get_ec_curve(params)?;
let purpose = check_ec_params(ec_curve, params, sec_level)?;
let keygen_info = match (ec_curve, purpose) {
(EcCurve::Curve25519, Some(KeyPurpose::Sign)) => KeyGenInfo::Ed25519,
(EcCurve::Curve25519, Some(KeyPurpose::AttestKey)) => KeyGenInfo::Ed25519,
(EcCurve::Curve25519, Some(KeyPurpose::AgreeKey)) => KeyGenInfo::X25519,
(EcCurve::Curve25519, _) => {
return Err(km_err!(
IncompatiblePurpose,
"curve25519 keys with invalid purpose {:?}",
purpose
))
}
(EcCurve::P224, _) => KeyGenInfo::NistEc(ec::NistCurve::P224),
(EcCurve::P256, _) => KeyGenInfo::NistEc(ec::NistCurve::P256),
(EcCurve::P384, _) => KeyGenInfo::NistEc(ec::NistCurve::P384),
(EcCurve::P521, _) => KeyGenInfo::NistEc(ec::NistCurve::P521),
};
Ok(keygen_info)
}
/// Find the first purpose value in the parameters.
pub fn primary_purpose(params: &[KeyParam]) -> Result<KeyPurpose, Error> {
params
.iter()
.find_map(
|param| if let KeyParam::Purpose(purpose) = param { Some(*purpose) } else { None },
)
.ok_or_else(|| km_err!(IncompatiblePurpose, "no purpose found for key!"))
}
/// Check EC key import parameter validity. Return the key material along with any key generation
/// parameters that have been deduced from the key material (but which are not present in the input
/// key parameters).
fn check_ec_import_params(
ec: &dyn Ec,
params: &[KeyParam],
sec_level: SecurityLevel,
key_format: KeyFormat,
key_data: &[u8],
) -> Result<(Vec<KeyParam>, KeyMaterial), Error> {
// Curve25519 can be imported as PKCS8 or raw; all other curves must be PKCS8.
// If we need to disinguish between Ed25519 and X25519, we need to examine the purpose for the
// key -- look for `AgreeKey` as it cannot be combined with other purposes.
let (key, curve) = match key_format {
KeyFormat::Raw if get_ec_curve(params)? == EcCurve::Curve25519 => {
// Raw key import must specify the curve (and the only valid option is Curve25519
// currently).
if primary_purpose(params)? == KeyPurpose::AgreeKey {
(ec.import_raw_x25519_key(key_data, params)?, EcCurve::Curve25519)
} else {
(ec.import_raw_ed25519_key(key_data, params)?, EcCurve::Curve25519)
}
}
KeyFormat::Pkcs8 => {
let key = ec.import_pkcs8_key(key_data, params)?;
let curve = match &key {
KeyMaterial::Ec(curve, CurveType::Nist, _) => *curve,
KeyMaterial::Ec(EcCurve::Curve25519, CurveType::EdDsa, _) => {
if primary_purpose(params)? == KeyPurpose::AgreeKey {
return Err(km_err!(
IncompatiblePurpose,
"can't use EdDSA key for key agreement"
));
}
EcCurve::Curve25519
}
KeyMaterial::Ec(EcCurve::Curve25519, CurveType::Xdh, _) => {
if primary_purpose(params)? != KeyPurpose::AgreeKey {
return Err(km_err!(IncompatiblePurpose, "can't use XDH key for signing"));
}
EcCurve::Curve25519
}
_ => {
return Err(km_err!(
ImportParameterMismatch,
"unexpected key type from EC import"
))
}
};
(key, curve)
}
_ => {
return Err(km_err!(
UnsupportedKeyFormat,
"invalid import format ({:?}) for EC key",
key_format,
));
}
};
// If curve was explicitly specified, it must match. If not specified, populate it in the
// deduced characteristics.
let mut deduced_chars = Vec::new();
match get_opt_tag_value!(params, EcCurve)? {
Some(specified_curve) => {
if *specified_curve != curve {
return Err(km_err!(
ImportParameterMismatch,
"imported EC key claimed curve {:?} but is {:?}",
specified_curve,
curve
));
}
}
None => deduced_chars.try_push(KeyParam::EcCurve(curve))?,
}
// If key size was explicitly specified, it must match. If not specified, populate it in the
// deduced characteristics.
let key_size = ec::curve_to_key_size(curve);
match get_opt_tag_value!(params, KeySize)? {
Some(param_key_size) => {
if *param_key_size != key_size {
return Err(km_err!(
ImportParameterMismatch,
"specified KEY_SIZE {:?} bits != actual key size {:?} for PKCS8 import",
param_key_size,
key_size
));
}
}
None => deduced_chars.try_push(KeyParam::KeySize(key_size))?,
}
check_ec_params(curve, params, sec_level)?;
Ok((deduced_chars, key))
}
/// Check the parameter validity for an EC key that is about to be generated or imported.
fn check_ec_params(
curve: EcCurve,
params: &[KeyParam],
sec_level: SecurityLevel,
) -> Result<Option<KeyPurpose>, Error> {
if sec_level == SecurityLevel::Strongbox && curve != EcCurve::P256 {
return Err(km_err!(UnsupportedEcCurve, "invalid curve ({:?}) for StrongBox", curve));
}
// Key size is not needed, but if present should match the curve.
if let Some(key_size) = get_opt_tag_value!(params, KeySize)? {
match curve {
EcCurve::P224 if *key_size == KeySizeInBits(224) => {}
EcCurve::P256 if *key_size == KeySizeInBits(256) => {}
EcCurve::P384 if *key_size == KeySizeInBits(384) => {}
EcCurve::P521 if *key_size == KeySizeInBits(521) => {}
EcCurve::Curve25519 if *key_size == KeySizeInBits(256) => {}
_ => {
return Err(km_err!(
InvalidArgument,
"invalid curve ({:?}) / key size ({:?}) combination",
curve,
key_size
))
}
}
}
let mut seen_attest = false;
let mut seen_sign = false;
let mut seen_agree = false;
let mut primary_purpose = None;
for param in params {
if let KeyParam::Purpose(purpose) = param {
match purpose {
KeyPurpose::Sign => seen_sign = true,
KeyPurpose::AgreeKey => seen_agree = true,
KeyPurpose::AttestKey => seen_attest = true,
KeyPurpose::Verify => {}
_ => warn!("Generating EC key with invalid purpose {:?}", purpose),
}
if primary_purpose.is_none() {
primary_purpose = Some(*purpose);
}
}
}
// Keys with Purpose::ATTEST_KEY must have no other purpose.
if seen_attest && (seen_sign || seen_agree) {
return Err(km_err!(
IncompatiblePurpose,
"keys with ATTEST_KEY must have no other purpose"
));
}
// Curve25519 keys must be either signing/attesting keys (Ed25519), or key agreement
// keys (X25519), not both.
if curve == EcCurve::Curve25519 && seen_agree && (seen_sign || seen_attest) {
return Err(km_err!(
IncompatiblePurpose,
"curve25519 keys must be either SIGN/ATTEST_KEY or AGREE_KEY, not both"
));
}
Ok(primary_purpose)
}
/// Check AES key generation parameter validity.
fn check_aes_gen_params(
params: &[KeyParam],
sec_level: SecurityLevel,
) -> Result<KeyGenInfo, Error> {
// For key generation, the size must be explicitly specified.
let key_size = get_tag_value!(params, KeySize, ErrorCode::UnsupportedKeySize)?;
let keygen_info = match key_size {
KeySizeInBits(128) => KeyGenInfo::Aes(aes::Variant::Aes128),
KeySizeInBits(256) => KeyGenInfo::Aes(aes::Variant::Aes256),
KeySizeInBits(192) if sec_level != SecurityLevel::Strongbox => {
KeyGenInfo::Aes(aes::Variant::Aes192)
}
_ => {
return Err(km_err!(
UnsupportedKeySize,
"unsupported KEY_SIZE {:?} bits for AES",
key_size
))
}
};
check_aes_params(params)?;
Ok(keygen_info)
}
/// Check AES key import parameter validity. Return the key material along with any key generation
/// parameters that have been deduced from the key material (but which are not present in the input
/// key parameters).
fn check_aes_import_params(
aes: &dyn Aes,
params: &[KeyParam],
sec_level: SecurityLevel,
key_format: KeyFormat,
key_data: &[u8],
) -> Result<(Vec<KeyParam>, KeyMaterial), Error> {
require_raw(key_format)?;
let (key, key_size) = aes.import_key(key_data, params)?;
if key_size == KeySizeInBits(192) && sec_level == SecurityLevel::Strongbox {
return Err(km_err!(
UnsupportedKeySize,
"unsupported KEY_SIZE=192 bits for AES on StrongBox",
));
}
let deduced_chars = require_matching_key_size(params, key_size)?;
check_aes_params(params)?;
Ok((deduced_chars, key))
}
/// Check the parameter validity for an AES key that is about to be generated or imported.
fn check_aes_params(params: &[KeyParam]) -> Result<(), Error> {
let gcm_support = params.iter().any(|p| *p == KeyParam::BlockMode(BlockMode::Gcm));
if gcm_support {
let min_mac_len = get_tag_value!(params, MinMacLength, ErrorCode::MissingMinMacLength)?;
if (min_mac_len % 8 != 0) || !(96..=128).contains(&min_mac_len) {
return Err(km_err!(
UnsupportedMinMacLength,
"unsupported MIN_MAC_LENGTH {} bits",
min_mac_len
));
}
}
Ok(())
}
/// Check triple DES key generation parameter validity.
fn check_3des_gen_params(params: &[KeyParam]) -> Result<KeyGenInfo, Error> {
// For key generation, the size (168) must be explicitly specified.
let key_size = get_tag_value!(params, KeySize, ErrorCode::UnsupportedKeySize)?;
if key_size != KeySizeInBits(168) {
return Err(km_err!(
UnsupportedKeySize,
"unsupported KEY_SIZE {:?} bits for TRIPLE_DES",
key_size
));
}
Ok(KeyGenInfo::TripleDes)
}
/// Check triple DES key import parameter validity. Return the key material along with any key
/// generation parameters that have been deduced from the key material (but which are not present in
/// the input key parameters).
fn check_3des_import_params(
des: &dyn Des,
params: &[KeyParam],
key_format: KeyFormat,
key_data: &[u8],
) -> Result<(Vec<KeyParam>, KeyMaterial), Error> {
require_raw(key_format)?;
let key = des.import_key(key_data, params)?;
// If the key size is specified as a parameter, it must be 168. Note that this
// is not equal to 8 x 24 (the data size).
let deduced_chars = require_matching_key_size(params, des::KEY_SIZE_BITS)?;
Ok((deduced_chars, key))
}
/// Check HMAC key generation parameter validity.
fn check_hmac_gen_params(
params: &[KeyParam],
sec_level: SecurityLevel,
) -> Result<KeyGenInfo, Error> {
// For key generation the size must be explicitly specified.
let key_size = get_tag_value!(params, KeySize, ErrorCode::UnsupportedKeySize)?;
check_hmac_params(params, sec_level, key_size)?;
Ok(KeyGenInfo::Hmac(key_size))
}
/// Build the set of key characteristics for an HMAC key that is about to be imported,
/// checking parameter validity along the way.
fn check_hmac_import_params(
hmac: &dyn Hmac,
params: &[KeyParam],
sec_level: SecurityLevel,
key_format: KeyFormat,
key_data: &[u8],
) -> Result<(Vec<KeyParam>, KeyMaterial), Error> {
require_raw(key_format)?;
let (key, key_size) = hmac.import_key(key_data, params)?;
let deduced_chars = require_matching_key_size(params, key_size)?;
check_hmac_params(params, sec_level, key_size)?;
Ok((deduced_chars, key))
}
/// Check the parameter validity for an HMAC key that is about to be generated or imported.
fn check_hmac_params(
params: &[KeyParam],
sec_level: SecurityLevel,
key_size: KeySizeInBits,
) -> Result<(), Error> {
if sec_level == SecurityLevel::Strongbox {
hmac::valid_strongbox_hal_size(key_size)?;
} else {
hmac::valid_hal_size(key_size)?;
}
let digest = get_tag_value!(params, Digest, ErrorCode::UnsupportedDigest)?;
if digest == Digest::None {
return Err(km_err!(UnsupportedDigest, "unsupported digest {:?}", digest));
}
let min_mac_len = get_tag_value!(params, MinMacLength, ErrorCode::MissingMinMacLength)?;
if (min_mac_len % 8 != 0) || !(64..=512).contains(&min_mac_len) {
return Err(km_err!(
UnsupportedMinMacLength,
"unsupported MIN_MAC_LENGTH {:?} bits",
min_mac_len
));
}
Ok(())
}
/// Check for `KeyFormat::RAW`.
fn require_raw(key_format: KeyFormat) -> Result<(), Error> {
if key_format != KeyFormat::Raw {
return Err(km_err!(
UnsupportedKeyFormat,
"unsupported import format {:?}, expect RAW",
key_format
));
}
Ok(())
}
/// Check or populate a `Tag::KEY_SIZE` value.
fn require_matching_key_size(
params: &[KeyParam],
key_size: KeySizeInBits,
) -> Result<Vec<KeyParam>, Error> {
let mut deduced_chars = Vec::new();
match get_opt_tag_value!(params, KeySize)? {
Some(param_key_size) => {
if *param_key_size != key_size {
return Err(km_err!(
ImportParameterMismatch,
"specified KEY_SIZE {:?} bits != actual key size {:?}",
param_key_size,
key_size
));
}
}
None => deduced_chars.try_push(KeyParam::KeySize(key_size))?,
}
Ok(deduced_chars)
}
/// Return an error if any of the `exclude` tags are found in `params`.
fn reject_tags(params: &[KeyParam], exclude: &[Tag]) -> Result<(), Error> {
for param in params {
if exclude.contains(&param.tag()) {
return Err(km_err!(InvalidTag, "tag {:?} not allowed", param.tag()));
}
}
Ok(())
}
/// Return an error if non-None padding found.
fn reject_some_padding(params: &[KeyParam]) -> Result<(), Error> {
if let Some(padding) = get_opt_tag_value!(params, Padding)? {
if *padding != PaddingMode::None {
return Err(km_err!(InvalidTag, "padding {:?} not allowed", padding));
}
}
Ok(())
}
/// Return an error if non-None digest found.
fn reject_some_digest(params: &[KeyParam]) -> Result<(), Error> {
if let Some(digest) = get_opt_tag_value!(params, Digest)? {
if *digest != Digest::None {
return Err(km_err!(InvalidTag, "digest {:?} not allowed", digest));
}
}
Ok(())
}
/// Reject incompatible combinations of authentication tags.
fn reject_incompatible_auth(params: &[KeyParam]) -> Result<(), Error> {
let mut seen_user_secure_id = false;
let mut seen_auth_type = false;
let mut seen_no_auth = false;
for param in params {
match param {
KeyParam::UserSecureId(_sid) => seen_user_secure_id = true,
KeyParam::UserAuthType(_atype) => seen_auth_type = true,
KeyParam::NoAuthRequired => seen_no_auth = true,
_ => {}
}
}
if seen_no_auth {
if seen_user_secure_id {
return Err(km_err!(InvalidTag, "found both NO_AUTH_REQUIRED and USER_SECURE_ID"));
}
if seen_auth_type {
return Err(km_err!(InvalidTag, "found both NO_AUTH_REQUIRED and USER_AUTH_TYPE"));
}
}
if seen_user_secure_id && !seen_auth_type {
return Err(km_err!(InvalidTag, "found USER_SECURE_ID but no USER_AUTH_TYPE"));
}
Ok(())
}
/// Indication of which parameters on a `begin` need to be checked against key authorizations.
struct BeginParamsToCheck {
block_mode: bool,
padding: bool,
digest: bool,
mgf_digest: bool,
}
/// Check that an operation with the given `purpose` and `params` can validly be started
/// using a key with characteristics `chars`.
pub fn check_begin_params(
chars: &[KeyParam],
purpose: KeyPurpose,
params: &[KeyParam],
) -> Result<(), Error> {
// General checks for all algorithms.
let algo = get_algorithm(chars)?;
let valid_purpose = matches!(
(algo, purpose),
(Algorithm::Aes, KeyPurpose::Encrypt)
| (Algorithm::Aes, KeyPurpose::Decrypt)
| (Algorithm::TripleDes, KeyPurpose::Encrypt)
| (Algorithm::TripleDes, KeyPurpose::Decrypt)
| (Algorithm::Hmac, KeyPurpose::Sign)
| (Algorithm::Hmac, KeyPurpose::Verify)
| (Algorithm::Ec, KeyPurpose::Sign)
| (Algorithm::Ec, KeyPurpose::AttestKey)
| (Algorithm::Ec, KeyPurpose::AgreeKey)
| (Algorithm::Rsa, KeyPurpose::Sign)
| (Algorithm::Rsa, KeyPurpose::Decrypt)
| (Algorithm::Rsa, KeyPurpose::AttestKey)
);
if !valid_purpose {
return Err(km_err!(
UnsupportedPurpose,
"invalid purpose {:?} for {:?} key",
purpose,
algo
));
}
if !contains_tag_value!(chars, Purpose, purpose) {
return Err(km_err!(
IncompatiblePurpose,
"purpose {:?} not in key characteristics",
purpose
));
}
if get_bool_tag_value!(chars, StorageKey)? {
return Err(km_err!(StorageKeyUnsupported, "attempt to use storage key",));
}
let nonce = get_opt_tag_value!(params, Nonce)?;
if get_bool_tag_value!(chars, CallerNonce)? {
// Caller-provided nonces are allowed.
} else if nonce.is_some() && purpose == KeyPurpose::Encrypt {
return Err(km_err!(CallerNonceProhibited, "caller nonce not allowed for encryption"));
}
// Further algorithm-specific checks.
let check = match algo {
Algorithm::Rsa => check_begin_rsa_params(chars, purpose, params),
Algorithm::Ec => check_begin_ec_params(chars, purpose, params),
Algorithm::Aes => check_begin_aes_params(chars, params, nonce.map(|v| v.as_ref())),
Algorithm::TripleDes => check_begin_3des_params(params, nonce.map(|v| v.as_ref())),
Algorithm::Hmac => check_begin_hmac_params(chars, purpose, params),
}?;
// For various parameters, if they are specified in the begin parameters and they
// are relevant for the algorithm, then the same value must also exist in the key
// characteristics. Also, there can be only one distinct value in the parameters.
if check.block_mode {
if let Some(bmode) = get_opt_tag_value!(params, BlockMode, UnsupportedBlockMode)? {
if !contains_tag_value!(chars, BlockMode, *bmode) {
return Err(km_err!(
IncompatibleBlockMode,
"block mode {:?} not in key characteristics {:?}",
bmode,
chars,
));
}
}
}
if check.padding {
if let Some(pmode) = get_opt_tag_value!(params, Padding, UnsupportedPaddingMode)? {
if !contains_tag_value!(chars, Padding, *pmode) {
return Err(km_err!(
IncompatiblePaddingMode,
"padding mode {:?} not in key characteristics {:?}",
pmode,
chars,
));
}
}
}
if check.digest {
if let Some(digest) = get_opt_tag_value!(params, Digest, UnsupportedDigest)? {
if !contains_tag_value!(chars, Digest, *digest) {
return Err(km_err!(
IncompatibleDigest,
"digest {:?} not in key characteristics",
digest,
));
}
}
}
if check.mgf_digest {
let mut mgf_digest_to_find =
get_opt_tag_value!(params, RsaOaepMgfDigest, UnsupportedMgfDigest)?;
let chars_have_mgf_digest =
chars.iter().any(|param| matches!(param, KeyParam::RsaOaepMgfDigest(_)));
if chars_have_mgf_digest && mgf_digest_to_find.is_none() {
// The key characteristics include an explicit set of MGF digests, but the begin()
// operation is using the default SHA1. Check that this default is in the
// characteristics.
mgf_digest_to_find = Some(&Digest::Sha1);
}
if let Some(mgf_digest) = mgf_digest_to_find {
if !contains_tag_value!(chars, RsaOaepMgfDigest, *mgf_digest) {
return Err(km_err!(
IncompatibleMgfDigest,
"MGF digest {:?} not in key characteristics",
mgf_digest,
));
}
}
}
Ok(())
}
/// Indicate whether a [`KeyPurpose`] is for encryption/decryption.
fn for_encryption(purpose: KeyPurpose) -> bool {
purpose == KeyPurpose::Encrypt
|| purpose == KeyPurpose::Decrypt
|| purpose == KeyPurpose::WrapKey
}
/// Indicate whether a [`KeyPurpose`] is for signing.
fn for_signing(purpose: KeyPurpose) -> bool {
purpose == KeyPurpose::Sign
}
/// Check that an RSA operation with the given `purpose` and `params` can validly be started
/// using a key with characteristics `chars`.
fn check_begin_rsa_params(
chars: &[KeyParam],
purpose: KeyPurpose,
params: &[KeyParam],
) -> Result<BeginParamsToCheck, Error> {
let padding = get_padding_mode(params)?;
let mut digest = None;
if for_signing(purpose) || (for_encryption(purpose) && padding == PaddingMode::RsaOaep) {
digest = Some(get_digest(params)?);
}
if for_signing(purpose) && padding == PaddingMode::None && digest != Some(Digest::None) {
return Err(km_err!(
IncompatibleDigest,
"unpadded RSA sign requires Digest::None not {:?}",
digest
));
}
match padding {
PaddingMode::None => {}
PaddingMode::RsaOaep if for_encryption(purpose) => {
if digest.is_none() || digest == Some(Digest::None) {
return Err(km_err!(IncompatibleDigest, "digest required for RSA-OAEP"));
}
let mgf_digest = get_mgf_digest(params)?;
if mgf_digest == Digest::None {
return Err(km_err!(
UnsupportedMgfDigest,
"MGF digest cannot be NONE for RSA-OAEP"
));
}
}
PaddingMode::RsaPss if for_signing(purpose) => {
if let Some(digest) = digest {
let key_size_bits = get_tag_value!(chars, KeySize, ErrorCode::InvalidArgument)?;
let d = digest_len(digest)?;
if key_size_bits < KeySizeInBits(2 * d + 9) {
return Err(km_err!(
IncompatibleDigest,
"key size {:?} < 2*8*D={} + 9",
key_size_bits,
d
));
}
} else {
return Err(km_err!(IncompatibleDigest, "digest required for RSA-PSS"));
}
}
PaddingMode::RsaPkcs115Encrypt if for_encryption(purpose) => {
if digest.is_some() && digest != Some(Digest::None) {
warn!(
"ignoring digest {:?} provided for PKCS#1 v1.5 encryption/decryption",
digest
);
}
}
PaddingMode::RsaPkcs115Sign if for_signing(purpose) => {
if digest.is_none() {
return Err(km_err!(IncompatibleDigest, "digest required for RSA-PKCS_1_5_SIGN"));
}
}
_ => {
return Err(km_err!(
UnsupportedPaddingMode,
"purpose {:?} incompatible with padding {:?}",
purpose,
padding
))
}
}
Ok(BeginParamsToCheck { block_mode: false, padding: true, digest: true, mgf_digest: true })
}
/// Check that an EC operation with the given `purpose` and `params` can validly be started
/// using a key with characteristics `chars`.
fn check_begin_ec_params(
chars: &[KeyParam],
purpose: KeyPurpose,
params: &[KeyParam],
) -> Result<BeginParamsToCheck, Error> {
let curve = get_ec_curve(chars)?;
if purpose == KeyPurpose::Sign {
let digest = get_digest(params)?;
if digest == Digest::Md5 {
return Err(km_err!(UnsupportedDigest, "Digest::MD5 unsupported for EC signing"));
}
if curve == EcCurve::Curve25519 && digest != Digest::None {
return Err(km_err!(
UnsupportedDigest,
"Ed25519 only supports Digest::None not {:?}",
digest
));
}
}
Ok(BeginParamsToCheck { block_mode: false, padding: false, digest: true, mgf_digest: false })
}
/// Check that an AES operation with the given `purpose` and `params` can validly be started
/// using a key with characteristics `chars`.
fn check_begin_aes_params(
chars: &[KeyParam],
params: &[KeyParam],
caller_nonce: Option<&[u8]>,
) -> Result<BeginParamsToCheck, Error> {
reject_tags(params, &[Tag::RsaOaepMgfDigest])?;
reject_some_digest(params)?;
let bmode = get_block_mode(params)?;
let padding = get_padding_mode(params)?;
if bmode == BlockMode::Gcm {
let mac_len = get_tag_value!(params, MacLength, ErrorCode::MissingMacLength)?;
if mac_len % 8 != 0 || mac_len > 128 {
return Err(km_err!(UnsupportedMacLength, "invalid mac len {}", mac_len));
}
let min_mac_len = get_tag_value!(chars, MinMacLength, ErrorCode::MissingMinMacLength)?;
if mac_len < min_mac_len {
return Err(km_err!(
InvalidMacLength,
"mac len {} less than min {}",
mac_len,
min_mac_len
));
}
}
match bmode {
BlockMode::Gcm | BlockMode::Ctr => match padding {
PaddingMode::None => {}
_ => {
return Err(km_err!(
IncompatiblePaddingMode,
"padding {:?} not valid for AES GCM/CTR",
padding
))
}
},
BlockMode::Ecb | BlockMode::Cbc => match padding {
PaddingMode::None | PaddingMode::Pkcs7 => {}
_ => {
return Err(km_err!(
IncompatiblePaddingMode,
"padding {:?} not valid for AES GCM/CTR",
padding
))
}
},
}
if let Some(nonce) = caller_nonce {
match bmode {
BlockMode::Cbc if nonce.len() == 16 => {}
BlockMode::Ctr if nonce.len() == 16 => {}
BlockMode::Gcm if nonce.len() == 12 => {}
_ => {
return Err(km_err!(
InvalidNonce,
"invalid caller nonce len {} for {:?}",
nonce.len(),
bmode
))
}
}
}
Ok(BeginParamsToCheck { block_mode: true, padding: true, digest: false, mgf_digest: false })
}
/// Check that a 3-DES operation with the given `purpose` and `params` can validly be started
/// using a key with characteristics `chars`.
fn check_begin_3des_params(
params: &[KeyParam],
caller_nonce: Option<&[u8]>,
) -> Result<BeginParamsToCheck, Error> {
reject_tags(params, &[Tag::RsaOaepMgfDigest])?;
reject_some_digest(params)?;
let bmode = get_block_mode(params)?;
let _padding = get_padding_mode(params)?;
match bmode {
BlockMode::Cbc | BlockMode::Ecb => {}
_ => {
return Err(km_err!(UnsupportedBlockMode, "block mode {:?} not valid for 3-DES", bmode))
}
}
if let Some(nonce) = caller_nonce {
match bmode {
BlockMode::Cbc if nonce.len() == 8 => {}
_ => {
return Err(km_err!(
InvalidNonce,
"invalid caller nonce len {} for {:?}",
nonce.len(),
bmode
))
}
}
}
Ok(BeginParamsToCheck { block_mode: true, padding: true, digest: false, mgf_digest: false })
}
/// Check that an HMAC operation with the given `purpose` and `params` can validly be started
/// using a key with characteristics `chars`.
fn check_begin_hmac_params(
chars: &[KeyParam],
purpose: KeyPurpose,
params: &[KeyParam],
) -> Result<BeginParamsToCheck, Error> {
reject_tags(params, &[Tag::BlockMode, Tag::RsaOaepMgfDigest])?;
reject_some_padding(params)?;
let digest = get_digest(params)?;
if purpose == KeyPurpose::Sign {
let mac_len = get_tag_value!(params, MacLength, ErrorCode::MissingMacLength)?;
if mac_len % 8 != 0 || mac_len > digest_len(digest)? {
return Err(km_err!(UnsupportedMacLength, "invalid mac len {}", mac_len));
}
let min_mac_len = get_tag_value!(chars, MinMacLength, ErrorCode::MissingMinMacLength)?;
if mac_len < min_mac_len {
return Err(km_err!(
InvalidMacLength,
"mac len {} less than min {}",
mac_len,
min_mac_len
));
}
}
Ok(BeginParamsToCheck { block_mode: false, padding: false, digest: true, mgf_digest: false })
}
/// Return the length in bits of a [`Digest`] function.
pub fn digest_len(digest: Digest) -> Result<u32, Error> {
match digest {
Digest::Md5 => Ok(128),
Digest::Sha1 => Ok(160),
Digest::Sha224 => Ok(224),
Digest::Sha256 => Ok(256),
Digest::Sha384 => Ok(384),
Digest::Sha512 => Ok(512),
_ => Err(km_err!(IncompatibleDigest, "invalid digest {:?}", digest)),
}
}
/// Check the required key params for an RSA wrapping key used in secure import and return the
/// [`DecryptionMode`] constructed from the processed key characteristics.
pub fn check_rsa_wrapping_key_params(
chars: &[KeyParam],
params: &[KeyParam],
) -> Result<DecryptionMode, Error> {
// Check the purpose of the wrapping key
if !contains_tag_value!(chars, Purpose, KeyPurpose::WrapKey) {
return Err(km_err!(IncompatiblePurpose, "no wrap key purpose for the wrapping key"));
}
let padding_mode = get_tag_value!(params, Padding, ErrorCode::IncompatiblePaddingMode)?;
if padding_mode != PaddingMode::RsaOaep {
return Err(km_err!(
IncompatiblePaddingMode,
"invalid padding mode {:?} for RSA wrapping key",
padding_mode
));
}
let msg_digest = get_tag_value!(params, Digest, ErrorCode::IncompatibleDigest)?;
if msg_digest != Digest::Sha256 {
return Err(km_err!(
IncompatibleDigest,
"invalid digest {:?} for RSA wrapping key",
padding_mode
));
}
let opt_mgf_digest = get_opt_tag_value!(params, RsaOaepMgfDigest)?;
if opt_mgf_digest == Some(&Digest::None) {
return Err(km_err!(UnsupportedMgfDigest, "MGF digest cannot be NONE for RSA-OAEP"));
}
if !contains_tag_value!(chars, Padding, padding_mode) {
return Err(km_err!(
IncompatiblePaddingMode,
"padding mode {:?} not in key characteristics {:?}",
padding_mode,
chars,
));
}
if !contains_tag_value!(chars, Digest, msg_digest) {
return Err(km_err!(
IncompatibleDigest,
"digest {:?} not in key characteristics {:?}",
msg_digest,
chars,
));
}
if let Some(mgf_digest) = opt_mgf_digest {
// MGF digest explicitly specified, check it is in key characteristics.
if !contains_tag_value!(chars, RsaOaepMgfDigest, *mgf_digest) {
return Err(km_err!(
IncompatibleDigest,
"MGF digest {:?} not in key characteristics {:?}",
mgf_digest,
chars,
));
}
}
let mgf_digest = opt_mgf_digest.unwrap_or(&Digest::Sha1);
let rsa_oaep_decrypt_mode = DecryptionMode::OaepPadding { msg_digest, mgf_digest: *mgf_digest };
Ok(rsa_oaep_decrypt_mode)
}
/// Calculate the [Luhn checksum](https://en.wikipedia.org/wiki/Luhn_algorithm) of the given number.
fn luhn_checksum(mut val: u64) -> u64 {
let mut ii = 0;
let mut sum_digits = 0;
while val != 0 {
let curr_digit = val % 10;
let multiplier = if ii % 2 == 0 { 2 } else { 1 };
let digit_multiplied = curr_digit * multiplier;
sum_digits += (digit_multiplied % 10) + (digit_multiplied / 10);
val /= 10;
ii += 1;
}
(10 - (sum_digits % 10)) % 10
}
/// Derive an IMEI value from a first IMEI value, by incrementing by one and re-calculating
/// the Luhn checksum. Return an empty vector on any failure.
pub fn increment_imei(imei: &[u8]) -> Vec<u8> {
if imei.is_empty() {
info!("empty IMEI");
return Vec::new();
}
// Expect ASCII digits.
let imei: &str = match core::str::from_utf8(imei) {
Ok(v) => v,
Err(_) => {
warn!("IMEI is not UTF-8");
return Vec::new();
}
};
let imei: u64 = match imei.parse() {
Ok(v) => v,
Err(_) => {
warn!("IMEI is not numeric");
return Vec::new();
}
};
// Drop trailing checksum digit, increment, and restore checksum.
let imei2 = (imei / 10) + 1;
let imei2 = (imei2 * 10) + luhn_checksum(imei2);
// Convert back to bytes.
alloc::format!("{}", imei2).into_bytes()
}