//! Cryptographic key module. use std::collections::BTreeMap; use std::fmt; use std::io::Cursor; use anyhow::{Context as _, Result, bail, ensure}; use base64::Engine as _; use deltachat_contact_tools::EmailAddress; use pgp::composed::{Deserializable, SignedKeyDetails}; pub use pgp::composed::{SignedPublicKey, SignedSecretKey}; use pgp::crypto::aead::AeadAlgorithm; use pgp::crypto::hash::HashAlgorithm; use pgp::crypto::sym::SymmetricKeyAlgorithm; use pgp::packet::{ Features, KeyFlags, Notation, PacketTrait as _, SignatureConfig, SignatureType, Subpacket, SubpacketData, }; use pgp::ser::Serialize; use pgp::types::{CompressionAlgorithm, KeyDetails, KeyVersion}; use rand_old::thread_rng; use tokio::runtime::Handle; use crate::context::Context; use crate::events::EventType; use crate::log::LogExt; use crate::tools::{self, time_elapsed}; /// Convenience trait for working with keys. /// /// This trait is implemented for rPGP's [SignedPublicKey] and /// [SignedSecretKey] types and makes working with them a little /// easier in the deltachat world. pub trait DcKey: Serialize + Deserializable + Clone { /// Create a key from some bytes. fn from_slice(bytes: &[u8]) -> Result { let res = ::from_bytes(Cursor::new(bytes)); if let Ok(res) = res { return Ok(res); } // Workaround for keys imported using // Delta Chat core < 1.0.0. // Old Delta Chat core had a bug // that resulted in treating CRC24 checksum // as part of the key when reading ASCII Armor. // Some users that started using Delta Chat in 2019 // have such corrupted keys with garbage bytes at the end. // // Garbage is at least 3 bytes long // and may be longer due to padding // at the end of the real key data // and importing the key multiple times. // // If removing 10 bytes is not enough, // the key is likely actually corrupted. for garbage_bytes in 3..std::cmp::min(bytes.len(), 10) { let res = ::from_bytes(Cursor::new( bytes .get(..bytes.len().saturating_sub(garbage_bytes)) .unwrap_or_default(), )); if let Ok(res) = res { return Ok(res); } } // Removing garbage bytes did not help, return the error. Ok(res?) } /// Create a key from a base64 string. fn from_base64(data: &str) -> Result { // strip newlines and other whitespace let cleaned: String = data.split_whitespace().collect(); let bytes = base64::engine::general_purpose::STANDARD.decode(cleaned.as_bytes())?; Self::from_slice(&bytes) } /// Create a key from an ASCII-armored string. fn from_asc(data: &str) -> Result { let bytes = data.as_bytes(); let res = Self::from_armor_single(Cursor::new(bytes)); let (key, _headers) = match res { Err(pgp::errors::Error::NoMatchingPacket { .. }) => match Self::is_private() { true => bail!("No private key packet found"), false => bail!("No public key packet found"), }, _ => res.context("rPGP error")?, }; Ok(key) } /// Serialise the key as bytes. fn to_bytes(&self) -> Vec { // Not using Serialize::to_bytes() to make clear *why* it is // safe to ignore this error. // Because we write to a Vec the io::Write impls never // fail and we can hide this error. let mut buf = Vec::new(); self.to_writer(&mut buf).unwrap(); buf } /// Serialise the key to a base64 string. fn to_base64(&self) -> String { base64::engine::general_purpose::STANDARD.encode(DcKey::to_bytes(self)) } /// Serialise the key to ASCII-armored representation. /// /// Each header line must be terminated by `\r\n`. Only allows setting one /// header as a simplification since that's the only way it's used so far. // Since .to_armored_string() are actual methods on SignedPublicKey and // SignedSecretKey we can not generically implement this. fn to_asc(&self, header: Option<(&str, &str)>) -> String; /// The fingerprint for the key. fn dc_fingerprint(&self) -> Fingerprint; /// Whether the key is private (or public). fn is_private() -> bool; } /// Converts secret key to public key. pub(crate) fn secret_key_to_public_key( context: &Context, mut signed_secret_key: SignedSecretKey, timestamp: u32, addr: &str, relay_addrs: &str, ) -> Result { info!(context, "Converting secret key to public key."); let timestamp = pgp::types::Timestamp::from_secs(timestamp); // Subpackets that we want to share between DKS and User ID signature. let common_subpackets = || -> Result> { let keyflags = { let mut keyflags = KeyFlags::default(); keyflags.set_certify(true); keyflags.set_sign(true); keyflags }; let features = { let mut features = Features::default(); features.set_seipd_v1(true); features.set_seipd_v2(true); features }; Ok(vec![ Subpacket::regular(SubpacketData::SignatureCreationTime(timestamp))?, Subpacket::regular(SubpacketData::IssuerFingerprint( signed_secret_key.fingerprint(), ))?, Subpacket::regular(SubpacketData::KeyFlags(keyflags))?, Subpacket::regular(SubpacketData::Features(features))?, Subpacket::regular(SubpacketData::PreferredSymmetricAlgorithms(smallvec![ SymmetricKeyAlgorithm::AES256, SymmetricKeyAlgorithm::AES192, SymmetricKeyAlgorithm::AES128 ]))?, Subpacket::regular(SubpacketData::PreferredHashAlgorithms(smallvec![ HashAlgorithm::Sha256, HashAlgorithm::Sha384, HashAlgorithm::Sha512, HashAlgorithm::Sha224, ]))?, Subpacket::regular(SubpacketData::PreferredCompressionAlgorithms(smallvec![ CompressionAlgorithm::ZLIB, CompressionAlgorithm::ZIP, ]))?, Subpacket::regular(SubpacketData::PreferredAeadAlgorithms(smallvec![( SymmetricKeyAlgorithm::AES256, AeadAlgorithm::Ocb )]))?, Subpacket::regular(SubpacketData::IsPrimary(true))?, ]) }; // RFC 4880 required that Transferrable Public Key (aka OpenPGP Certificate) // contains at least one User ID: // // RFC 9580 does not require User ID even for V4 certificates anymore: // // // We do not use and do not expect User ID in any keys, // but nevertheless include User ID in V4 keys for compatibility with clients that follow RFC 4880. // RFC 9580 also recommends including User ID into V4 keys: // // // We do not support keys older than V4 and are not going // to include User ID in newer V6 keys as all clients that support V6 // should support keys without User ID. let users = if signed_secret_key.version() == KeyVersion::V4 { let user_id = format!("<{addr}>"); let mut rng = thread_rng(); // Self-signature is a "positive certification", // see . let mut user_id_signature_config = SignatureConfig::from_key( &mut rng, &signed_secret_key.primary_key, SignatureType::CertPositive, )?; user_id_signature_config.hashed_subpackets = common_subpackets()?; user_id_signature_config.unhashed_subpackets = vec![Subpacket::regular( SubpacketData::IssuerKeyId(signed_secret_key.legacy_key_id()), )?]; let user_id_packet = pgp::packet::UserId::from_str(pgp::types::PacketHeaderVersion::New, &user_id)?; let signature = user_id_signature_config.sign_certification( &signed_secret_key.primary_key, &signed_secret_key.primary_key.public_key(), &pgp::types::Password::empty(), user_id_packet.tag(), &user_id_packet, )?; vec![user_id_packet.into_signed(signature)] } else { vec![] }; let direct_signatures = { let mut rng = thread_rng(); let mut direct_key_signature_config = SignatureConfig::from_key( &mut rng, &signed_secret_key.primary_key, SignatureType::Key, )?; direct_key_signature_config.hashed_subpackets = common_subpackets()?; let notation = Notation { readable: true, name: "relays@chatmail.at".into(), value: relay_addrs.to_string().into(), }; direct_key_signature_config .hashed_subpackets .push(Subpacket::regular(SubpacketData::Notation(notation))?); let direct_key_signature = direct_key_signature_config.sign_key( &signed_secret_key.primary_key, &pgp::types::Password::empty(), signed_secret_key.primary_key.public_key(), )?; vec![direct_key_signature] }; signed_secret_key.details = SignedKeyDetails { revocation_signatures: vec![], direct_signatures, users, user_attributes: vec![], }; Ok(signed_secret_key.to_public_key()) } /// Attempts to load own public key. /// /// Returns `None` if no secret key is generated yet. pub(crate) async fn load_self_public_key_opt(context: &Context) -> Result> { let mut lock = context.self_public_key.lock().await; if let Some(ref public_key) = *lock { return Ok(Some(public_key.clone())); } let Some(secret_key_bytes) = context .sql .query_row_optional( "SELECT private_key FROM keypairs WHERE id=(SELECT value FROM config WHERE keyname='key_id')", (), |row| { let bytes: Vec = row.get(0)?; Ok(bytes) }, ) .await? else { return Ok(None); }; let signed_secret_key = SignedSecretKey::from_slice(&secret_key_bytes)?; let timestamp = context .sql .query_get_value::( "SELECT MAX(timestamp) FROM (SELECT add_timestamp AS timestamp FROM transports UNION ALL SELECT remove_timestamp AS timestamp FROM removed_transports)", (), ) .await? .context("No transports configured")?; let addr = context.get_primary_self_addr().await?; let all_addrs = context.get_published_self_addrs().await?.join(","); let signed_public_key = secret_key_to_public_key(context, signed_secret_key, timestamp, &addr, &all_addrs)?; *lock = Some(signed_public_key.clone()); Ok(Some(signed_public_key)) } /// Loads own public key. /// /// If no key is generated yet, generates a new one. pub(crate) async fn load_self_public_key(context: &Context) -> Result { match load_self_public_key_opt(context).await? { Some(public_key) => Ok(public_key), None => { generate_keypair(context).await?; let public_key = load_self_public_key_opt(context) .await? .context("Secret key generated, but public key cannot be created")?; Ok(public_key) } } } /// Returns our own public keyring. /// /// No keys are generated and at most one key is returned. pub(crate) async fn load_self_public_keyring(context: &Context) -> Result> { if let Some(public_key) = load_self_public_key_opt(context).await? { Ok(vec![public_key]) } else { Ok(vec![]) } } /// Returns own public key fingerprint in (not human-readable) hex representation. /// This is the fingerprint format that is used in the database. /// /// If no key is generated yet, generates a new one. /// /// For performance reasons, the fingerprint is cached after the first invocation. pub(crate) async fn self_fingerprint(context: &Context) -> Result<&str> { if let Some(fp) = context.self_fingerprint.get() { Ok(fp) } else { let fp = load_self_public_key(context).await?.dc_fingerprint().hex(); Ok(context.self_fingerprint.get_or_init(|| fp)) } } /// Returns own public key fingerprint in (not human-readable) hex representation. /// This is the fingerprint format that is used in the database. /// /// Returns `None` if no key is generated yet. /// /// For performance reasons, the fingerprint is cached after the first invocation. pub(crate) async fn self_fingerprint_opt(context: &Context) -> Result> { if let Some(fp) = context.self_fingerprint.get() { Ok(Some(fp)) } else if let Some(key) = load_self_public_key_opt(context).await? { let fp = key.dc_fingerprint().hex(); Ok(Some(context.self_fingerprint.get_or_init(|| fp))) } else { Ok(None) } } pub(crate) async fn load_self_secret_key(context: &Context) -> Result { let private_key = context .sql .query_row_optional( "SELECT private_key FROM keypairs WHERE id=(SELECT value FROM config WHERE keyname='key_id')", (), |row| { let bytes: Vec = row.get(0)?; Ok(bytes) }, ) .await?; match private_key { Some(bytes) => SignedSecretKey::from_slice(&bytes), None => { let secret = generate_keypair(context).await?; Ok(secret) } } } pub(crate) async fn load_self_secret_keyring(context: &Context) -> Result> { let keys = context .sql .query_map_vec( r#"SELECT private_key FROM keypairs ORDER BY id=(SELECT value FROM config WHERE keyname='key_id') DESC"#, (), |row| { let bytes: Vec = row.get(0)?; Ok(bytes) }, ) .await? .into_iter() .filter_map(|bytes| SignedSecretKey::from_slice(&bytes).log_err(context).ok()) .collect(); Ok(keys) } impl DcKey for SignedPublicKey { fn to_asc(&self, header: Option<(&str, &str)>) -> String { // Not using .to_armored_string() to make clear *why* it is // safe to ignore this error. // Because we write to a Vec the io::Write impls never // fail and we can hide this error. let headers = header.map(|(key, value)| BTreeMap::from([(key.to_string(), vec![value.to_string()])])); let mut buf = Vec::new(); self.to_armored_writer(&mut buf, headers.as_ref().into()) .unwrap_or_default(); std::string::String::from_utf8(buf).unwrap_or_default() } fn is_private() -> bool { false } fn dc_fingerprint(&self) -> Fingerprint { self.fingerprint().into() } } impl DcKey for SignedSecretKey { fn to_asc(&self, header: Option<(&str, &str)>) -> String { // Not using .to_armored_string() to make clear *why* it is // safe to do these unwraps. // Because we write to a Vec the io::Write impls never // fail and we can hide this error. The string is always ASCII. let headers = header.map(|(key, value)| BTreeMap::from([(key.to_string(), vec![value.to_string()])])); let mut buf = Vec::new(); self.to_armored_writer(&mut buf, headers.as_ref().into()) .unwrap_or_default(); std::string::String::from_utf8(buf).unwrap_or_default() } fn is_private() -> bool { true } fn dc_fingerprint(&self) -> Fingerprint { self.fingerprint().into() } } async fn generate_keypair(context: &Context) -> Result { let addr = context.get_primary_self_addr().await?; let addr = EmailAddress::new(&addr)?; let _public_key_guard = context.self_public_key.lock().await; // Check if the key appeared while we were waiting on the lock. match load_keypair(context).await? { Some(key_pair) => Ok(key_pair), None => { let start = tools::Time::now(); info!(context, "Generating keypair."); let keypair = Handle::current() .spawn_blocking(move || crate::pgp::create_keypair(addr)) .await??; store_self_keypair(context, &keypair).await?; info!( context, "Keypair generated in {:.3}s.", time_elapsed(&start).as_secs(), ); Ok(keypair) } } } pub(crate) async fn load_keypair(context: &Context) -> Result> { let res = context .sql .query_row_optional( "SELECT private_key FROM keypairs WHERE id=(SELECT value FROM config WHERE keyname='key_id')", (), |row| { let sec_bytes: Vec = row.get(0)?; Ok(sec_bytes) }, ) .await?; let signed_secret_key = if let Some(sec_bytes) = res { Some(SignedSecretKey::from_slice(&sec_bytes)?) } else { None }; Ok(signed_secret_key) } /// Stores own keypair in the database and sets it as a default. /// /// Fails if we already have a key, so it is not possible to /// have more than one key for new setups. Existing setups /// may still have more than one key for compatibility. pub(crate) async fn store_self_keypair( context: &Context, signed_secret_key: &SignedSecretKey, ) -> Result<()> { // This public key is stored in the database // only for backwards compatibility. // // It should not be used e.g. in Autocrypt headers or vCards. // Use `secret_key_to_public_key()` function instead, // which adds relay list to the signature. let signed_public_key = signed_secret_key.to_public_key(); let mut config_cache_lock = context.sql.config_cache.write().await; let new_key_id = context .sql .transaction(|transaction| { let public_key = DcKey::to_bytes(&signed_public_key); let secret_key = DcKey::to_bytes(signed_secret_key); // private_key and public_key columns // are UNIQUE since migration 107, // so this fails if we already have this key. transaction .execute( "INSERT INTO keypairs (public_key, private_key) VALUES (?,?)", (&public_key, &secret_key), ) .context("Failed to insert keypair")?; let new_key_id = transaction.last_insert_rowid(); // This will fail if we already have `key_id`. // // Setting default key is only possible if we don't // have a key already. transaction.execute( "INSERT INTO config (keyname, value) VALUES ('key_id', ?)", (new_key_id,), )?; Ok(new_key_id) }) .await?; context.emit_event(EventType::AccountsItemChanged); config_cache_lock.insert("key_id".to_string(), Some(new_key_id.to_string())); Ok(()) } /// Saves a keypair as the default keys. /// /// This API is used for testing purposes /// to avoid generating the key in tests. /// Use import/export APIs instead. pub async fn preconfigure_keypair(context: &Context, secret_data: &str) -> Result<()> { let secret = SignedSecretKey::from_asc(secret_data)?; store_self_keypair(context, &secret).await?; Ok(()) } /// A key fingerprint #[derive(Clone, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)] pub struct Fingerprint(Vec); impl Fingerprint { /// Creates new 160-bit (20 bytes) fingerprint. pub fn new(v: Vec) -> Fingerprint { debug_assert_eq!(v.len(), 20); Fingerprint(v) } /// Make a hex string from the fingerprint. /// /// Use [std::fmt::Display] or [ToString::to_string] to get a /// human-readable formatted string. pub fn hex(&self) -> String { hex::encode_upper(&self.0) } } impl From for Fingerprint { fn from(fingerprint: pgp::types::Fingerprint) -> Fingerprint { Self::new(fingerprint.as_bytes().into()) } } impl fmt::Debug for Fingerprint { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Fingerprint") .field("hex", &self.hex()) .finish() } } /// Make a human-readable fingerprint. impl fmt::Display for Fingerprint { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // Split key into chunks of 4 with space and newline at 20 chars for (i, c) in self.hex().chars().enumerate() { if i > 0 && i % 20 == 0 { writeln!(f)?; } else if i > 0 && i % 4 == 0 { write!(f, " ")?; } write!(f, "{c}")?; } Ok(()) } } /// Parse a human-readable or otherwise formatted fingerprint. impl std::str::FromStr for Fingerprint { type Err = anyhow::Error; fn from_str(input: &str) -> Result { let hex_repr: String = input .to_uppercase() .chars() .filter(|&c| c.is_ascii_hexdigit()) .collect(); let v: Vec = hex::decode(&hex_repr)?; ensure!(v.len() == 20, "wrong fingerprint length: {hex_repr}"); let fp = Fingerprint::new(v); Ok(fp) } } #[cfg(test)] mod tests { use std::sync::{Arc, LazyLock}; use super::*; use crate::config::Config; use crate::test_utils::{TestContext, alice_keypair}; static KEYPAIR: LazyLock = LazyLock::new(alice_keypair); #[test] fn test_from_armored_string() { let private_key = SignedSecretKey::from_asc( "-----BEGIN PGP PRIVATE KEY BLOCK----- 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 7yPJeQ== =KZk/ -----END PGP PRIVATE KEY BLOCK-----", ) .expect("failed to decode"); let binary = DcKey::to_bytes(&private_key); SignedSecretKey::from_slice(&binary).expect("invalid private key"); } #[test] fn test_asc_roundtrip() { let key = KEYPAIR.clone().to_public_key(); let asc = key.to_asc(Some(("spam", "ham"))); let key2 = SignedPublicKey::from_asc(&asc).unwrap(); assert_eq!(key, key2); let key = KEYPAIR.clone(); let asc = key.to_asc(Some(("spam", "ham"))); let key2 = SignedSecretKey::from_asc(&asc).unwrap(); assert_eq!(key, key2); } #[test] fn test_from_slice_roundtrip() { let private_key = KEYPAIR.clone(); let public_key = KEYPAIR.clone().to_public_key(); let binary = DcKey::to_bytes(&public_key); let public_key2 = SignedPublicKey::from_slice(&binary).expect("invalid public key"); assert_eq!(public_key, public_key2); let binary = DcKey::to_bytes(&private_key); let private_key2 = SignedSecretKey::from_slice(&binary).expect("invalid private key"); assert_eq!(private_key, private_key2); } #[test] fn test_from_slice_bad_data() { let mut bad_data: [u8; 4096] = [0; 4096]; for (i, v) in bad_data.iter_mut().enumerate() { *v = (i & 0xff) as u8; } for j in 0..(4096 / 40) { let slice = &bad_data.get(j..j + 4096 / 2 + j).unwrap(); assert!(SignedPublicKey::from_slice(slice).is_err()); assert!(SignedSecretKey::from_slice(slice).is_err()); } } /// Tests workaround for Delta Chat core < 1.0.0 /// which parsed CRC24 at the end of ASCII Armor /// as the part of the key. /// Depending on the alignment and the number of /// `=` characters at the end of the key, /// this resulted in various number of garbage /// octets at the end of the key, starting from 3 octets, /// but possibly 4 or 5 and maybe more octets /// if the key is imported or transferred /// using Autocrypt Setup Message multiple times. #[test] fn test_ignore_trailing_garbage() { // Test several variants of garbage. for garbage in [ b"\x02\xfc\xaa\x38\x4b\x5c".as_slice(), b"\x02\xfc\xaa".as_slice(), b"\x01\x02\x03\x04\x05".as_slice(), ] { let private_key = KEYPAIR.clone(); let mut binary = DcKey::to_bytes(&private_key); binary.extend(garbage); let private_key2 = SignedSecretKey::from_slice(&binary).expect("Failed to ignore garbage"); assert_eq!(private_key.dc_fingerprint(), private_key2.dc_fingerprint()); } } #[test] fn test_base64_roundtrip() { let key = KEYPAIR.clone().to_public_key(); let base64 = key.to_base64(); let key2 = SignedPublicKey::from_base64(&base64).unwrap(); assert_eq!(key, key2); } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_load_self_generate_public() { let t = TestContext::new().await; t.set_config(Config::ConfiguredAddr, Some("alice@example.org")) .await .unwrap(); let key = load_self_public_key(&t).await; assert!(key.is_ok()); } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_load_self_generate_secret() { let t = TestContext::new().await; t.set_config(Config::ConfiguredAddr, Some("alice@example.org")) .await .unwrap(); let key = load_self_secret_key(&t).await; assert!(key.is_ok()); } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_load_self_generate_concurrent() { use std::thread; let t = TestContext::new().await; t.set_config(Config::ConfiguredAddr, Some("alice@example.org")) .await .unwrap(); let thr0 = { let ctx = t.clone(); thread::spawn(move || { tokio::runtime::Runtime::new() .unwrap() .block_on(load_self_public_key(&ctx)) }) }; let thr1 = { let ctx = t.clone(); thread::spawn(move || { tokio::runtime::Runtime::new() .unwrap() .block_on(load_self_public_key(&ctx)) }) }; let res0 = thr0.join().unwrap(); let res1 = thr1.join().unwrap(); assert_eq!(res0.unwrap(), res1.unwrap()); } /// Tests that setting a default key second time is not allowed. #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_save_self_key_twice() { // Saving the same key twice should result in only one row in // the keypairs table. let t = TestContext::new().await; let ctx = Arc::new(t); let nrows = || async { ctx.sql .count("SELECT COUNT(*) FROM keypairs;", ()) .await .unwrap() }; assert_eq!(nrows().await, 0); store_self_keypair(&ctx, &KEYPAIR).await.unwrap(); assert_eq!(nrows().await, 1); // Saving a second key fails. let res = store_self_keypair(&ctx, &KEYPAIR).await; assert!(res.is_err()); assert_eq!(nrows().await, 1); } #[test] fn test_fingerprint_from_str() { let res = Fingerprint::new(vec![ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ]); let fp: Fingerprint = "0102030405060708090A0B0c0d0e0F1011121314".parse().unwrap(); assert_eq!(fp, res); let fp: Fingerprint = "zzzz 0102 0304 0506\n0708090a0b0c0D0E0F1011121314 yyy" .parse() .unwrap(); assert_eq!(fp, res); assert!("1".parse::().is_err()); } #[test] fn test_fingerprint_hex() { let fp = Fingerprint::new(vec![ 1, 2, 4, 8, 16, 32, 64, 128, 255, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ]); assert_eq!(fp.hex(), "0102040810204080FF0A0B0C0D0E0F1011121314"); } #[test] fn test_fingerprint_to_string() { let fp = Fingerprint::new(vec![ 1, 2, 4, 8, 16, 32, 64, 128, 255, 1, 2, 4, 8, 16, 32, 64, 128, 255, 19, 20, ]); assert_eq!( fp.to_string(), "0102 0408 1020 4080 FF01\n0204 0810 2040 80FF 1314" ); } }