Skip to main content

structured_cms/
personal.rs

1//! Provider-neutral personal key containers (RFC7292 / RFC5958).
2//!
3//! Reading is inspection, never authentication or permission to use a key.
4//! [`process()`] authenticates the complete original safe before extraction;
5//! passwords, cryptographic engines, key selection and trust remain explicit
6//! caller contracts. Private-key values are borrowed, not decoded by a backend.
7//! No type containing private plaintext implements `Debug` or allocating Clone.
8
9use crate::{ContentInfo, Error, ErrorCode, Octets, Result, SignedData};
10use crate::{attribute::Attribute, enveloped::EncodedValue, signer::required};
11use structured_asn1::memory::{OwnedVec, Work};
12use structured_asn1::{Element, Integer, Oid, Reader, Tag, universal};
13use structured_pkix::AlgorithmIdentifier;
14
15mod process;
16pub mod protection;
17pub use process::{
18    Backend, Cause, Decision, Failure, Fragments, Origin, Plaintext, Source, legacy_password,
19    process,
20};
21
22const FIELD: &str = "personal";
23const DATA: Oid<'static> = Oid::from_bytes(&[42, 134, 72, 134, 247, 13, 1, 7, 1]);
24const SIGNED: Oid<'static> = Oid::from_bytes(&[42, 134, 72, 134, 247, 13, 1, 7, 2]);
25const ENCRYPTED: Oid<'static> = Oid::from_bytes(&[42, 134, 72, 134, 247, 13, 1, 7, 6]);
26
27/// Original PFX integrity mode. Absence of a MAC is not authenticated data.
28pub enum Integrity<'a> {
29    /// Password integrity over the exact logical `AuthenticatedSafe` octets.
30    Password {
31        /// BER `AuthenticatedSafe` encoding, excluding OCTET STRING headers.
32        safe: Octets<'a>,
33        /// Optional original MAC; None remains unauthenticated inspection.
34        mac: Option<MacData<'a>>,
35    },
36    /// Public-key integrity. The existing CMS processor owns signature rules.
37    Signed(SignedData<'a>),
38}
39
40/// Original PFX v3. Parsing does not invoke passwords or provider operations.
41pub struct Pfx<'a> {
42    integrity: Integrity<'a>,
43    original: EncodedValue<'a>,
44}
45impl<'a> Pfx<'a> {
46    /// Reads one complete BER/DER PFX at the producing source position.
47    pub fn read(parent: &mut Reader<'a>) -> Result<Self> {
48        let element = required(parent, FIELD)?;
49        let value = Self::read_element(parent, &element)?;
50        end(parent)?;
51        Ok(value)
52    }
53    /// Reads an embedded original PFX, without rejecting enclosing siblings.
54    pub fn read_element(parent: &Reader<'a>, element: &Element<'a>) -> Result<Self> {
55        let mut inside = sequence(parent, element)?;
56        let version = number(&mut inside)?;
57        if version.as_u64() != Some(3) {
58            return Err(invalid(ErrorCode::UnsupportedVersion, element));
59        }
60        let envelope = required(&mut inside, FIELD)?;
61        let (info, reader) = ContentInfo::read_element(&inside, &envelope)?;
62        let mac = inside
63            .next_element()
64            .map_err(encoding)?
65            .map(|value| MacData::read(&inside, &value))
66            .transpose()?;
67        end(&inside)?;
68        let integrity = if equal_oid(&inside, info.kind(), DATA)? {
69            Integrity::Password {
70                safe: Octets::read(&reader, info.content(), FIELD)?,
71                mac,
72            }
73        } else if equal_oid(&inside, info.kind(), SIGNED)? {
74            // RFC7292 §4: MacData occurs ONLY in password integrity mode.
75            // https://www.rfc-editor.org/rfc/rfc7292#section-4
76            if mac.is_some() {
77                return Err(invalid(ErrorCode::FieldOrder, element));
78            }
79            let signed = SignedData::read_element(&reader, info.content())?;
80            if !equal_oid(&reader, signed.content().kind(), DATA)? {
81                return Err(invalid(ErrorCode::UnexpectedContentType, info.content()));
82            }
83            if signed.content().content().is_none() {
84                return Err(invalid(ErrorCode::DetachedContent, info.content()));
85            }
86            Integrity::Signed(signed)
87        } else {
88            return Err(invalid(ErrorCode::UnexpectedContentType, &envelope));
89        };
90        Ok(Self {
91            integrity,
92            original: EncodedValue::new(parent, *element),
93        })
94    }
95    /// Whole original PFX and its lazy original/current reader boundary.
96    #[must_use]
97    pub fn original(&self) -> &EncodedValue<'a> {
98        &self.original
99    }
100    /// Original immutable integrity mode and its exact protected data.
101    #[must_use]
102    pub fn integrity(&self) -> &Integrity<'a> {
103        &self.integrity
104    }
105    pub(crate) fn safe(&self) -> &Octets<'a> {
106        match &self.integrity {
107            Integrity::Password { safe, .. } => safe,
108            Integrity::Signed(signed) => signed.content().content().expect("checked attached PFX"),
109        }
110    }
111}
112
113/// Original `MacData`, including ignored legacy fields in PBMAC1 mode.
114pub struct MacData<'a> {
115    /// Original digest/MAC algorithm; PBMAC1 is distinguished explicitly.
116    pub algorithm: AlgorithmIdentifier<'a>,
117    /// Encoded expected MAC octets.
118    pub digest: Octets<'a>,
119    /// Original legacy salt. RFC9879 §4 requires ignoring it for PBMAC1.
120    pub salt: Octets<'a>,
121    /// Original legacy iteration count. None denotes schema DEFAULT 1.
122    /// In PBMAC1 mode this field is ignored, not used as a KDF limit/input.
123    pub iterations: Option<Integer<'a>>,
124    /// Validated PBMAC1 parameters, or None for the legacy MAC scheme.
125    pub pbmac1: Option<protection::Pair<'a>>,
126}
127impl<'a> MacData<'a> {
128    /// Reads `MacData` and RFC9879 mandatory PBMAC1 parameter constraints.
129    pub fn read(parent: &Reader<'a>, element: &Element<'a>) -> Result<Self> {
130        let mut inside = sequence(parent, element)?;
131        let digest_info = required(&mut inside, FIELD)?;
132        let mut digest_info = sequence(&inside, &digest_info)?;
133        let algorithm = algorithm(&mut digest_info)?;
134        let value = required(&mut digest_info, FIELD)?;
135        let digest = Octets::read(&digest_info, &value, FIELD)?;
136        end(&digest_info)?;
137        let value = required(&mut inside, FIELD)?;
138        let salt = Octets::read(&inside, &value, FIELD)?;
139        let iterations = if inside.is_empty() {
140            None
141        } else {
142            Some(number(&mut inside)?)
143        };
144        end(&inside)?;
145        // RFC9879 §4 ignores this field for MAC computation, not for DER
146        // encoding. X.690 §11.5 still omits the schema DEFAULT in either mode.
147        if let Some(rounds) = iterations {
148            default_one(parent, rounds, element)?;
149        }
150        let pbmac1 = if equal_oid(parent, algorithm.algorithm(), protection::PBMAC1)? {
151            let pair = protection::Pair::read(algorithm, parent)?;
152            if let Some(width) = protection::hmac_width(pair.scheme, parent)?
153                && digest.len() != width
154            {
155                return Err(invalid(ErrorCode::DigestWidth, element));
156            }
157            if let Some(kdf) = pair.pbkdf2.as_ref()
158                && kdf.key_length.is_none()
159            {
160                return Err(invalid(ErrorCode::MissingField, element));
161            }
162            Some(pair)
163        } else {
164            match structured_pkix::algorithm::digest::algorithm(&algorithm, &parent.execution())
165                .map_err(Error::pkix)?
166            {
167                structured_asn1::digest::Resolution::Supported(hash)
168                    if digest.len() != hash.width() =>
169                {
170                    return Err(invalid(ErrorCode::DigestWidth, element));
171                }
172                structured_asn1::digest::Resolution::Malformed => {
173                    return Err(invalid(ErrorCode::AlgorithmSyntax, element));
174                }
175                _ => {}
176            }
177            if let Some(rounds) = iterations {
178                positive(rounds, element)?;
179            }
180            None
181        };
182        Ok(Self {
183            algorithm,
184            digest,
185            salt,
186            iterations,
187            pbmac1,
188        })
189    }
190}
191
192/// One protected `AuthenticatedSafe` member. Unrecognized content stays explicit.
193pub enum Safe<'a> {
194    /// Unencrypted `SafeContents` BER encoding, including its SEQUENCE TLV.
195    Data(Octets<'a>),
196    /// Password-encrypted `SafeContents`.
197    Encrypted(EncryptedData<'a>),
198    /// Public-key encrypted `SafeContents`, reusing all common recipient types.
199    Enveloped(crate::enveloped::EnvelopedData<'a>),
200    /// An open content type without a schema owned by this format.
201    Other {
202        /// Original content type.
203        kind: Oid<'a>,
204        /// Original inner value and producing context.
205        value: EncodedValue<'a>,
206    },
207}
208impl<'a> Safe<'a> {
209    /// Reads one original `ContentInfo` member; no cryptography is performed.
210    pub fn read(parent: &Reader<'a>, element: &Element<'a>) -> Result<Self> {
211        let (info, reader) = ContentInfo::read_element(parent, element)?;
212        if equal_oid(parent, info.kind(), DATA)? {
213            Ok(Self::Data(Octets::read(&reader, info.content(), FIELD)?))
214        } else if equal_oid(parent, info.kind(), ENCRYPTED)? {
215            Ok(Self::Encrypted(EncryptedData::read(
216                &reader,
217                info.content(),
218            )?))
219        } else if equal_oid(parent, info.kind(), crate::enveloped::IDENTIFIER)? {
220            Ok(Self::Enveloped(
221                crate::enveloped::EnvelopedData::read_element(&reader, info.content())?,
222            ))
223        } else {
224            Ok(Self::Other {
225                kind: info.kind(),
226                value: EncodedValue::new(&reader, *info.content()),
227            })
228        }
229    }
230}
231
232/// CMS `EncryptedData`, RFC5652 §8, used by PKCS#12 password privacy.
233pub struct EncryptedData<'a> {
234    /// Original content algorithm and attached/detached encrypted octets.
235    pub content: crate::enveloped::EncryptedContent<'a>,
236    /// Every original unprotected attribute, not authenticated bag metadata.
237    pub attributes: Option<OwnedVec<Attribute<'a>>>,
238    /// Parsed password-protection parameters, prepared once before callbacks.
239    pub protection: protection::Protection<'a>,
240}
241impl<'a> EncryptedData<'a> {
242    /// Reads a complete original `EncryptedData` SEQUENCE and version rule.
243    pub fn read(parent: &Reader<'a>, element: &Element<'a>) -> Result<Self> {
244        let mut inside = sequence(parent, element)?;
245        let version = number(&mut inside)?;
246        let info = required(&mut inside, FIELD)?;
247        let content = crate::enveloped::encrypted_content(&inside, &info)?;
248        let attributes = inside
249            .next_element()
250            .map_err(encoding)?
251            .map(|set| attributes(&inside, &set, Tag::context(true, 1)))
252            .transpose()?;
253        end(&inside)?;
254        if version.as_u64() != Some(if attributes.is_some() { 2 } else { 0 }) {
255            return Err(invalid(ErrorCode::VersionMismatch, element));
256        }
257        if attributes.as_ref().is_some_and(|a| a.is_empty()) {
258            return Err(invalid(ErrorCode::EmptyAttributes, element));
259        }
260        let protection = protection::Protection::read(content.algorithm, parent)?;
261        Ok(Self {
262            content,
263            attributes,
264            protection,
265        })
266    }
267}
268
269/// RFC5958 `OneAsymmetricKey` / PKCS#8 `PrivateKeyInfo`.
270pub struct PrivateKey<'a> {
271    /// Zero is v1; one is v2. Validated against public-key presence.
272    pub version: u64,
273    /// Original algorithm, not a key/backend selection.
274    pub algorithm: AlgorithmIdentifier<'a>,
275    /// Original secret key contents. Borrowed, including BER fragments.
276    pub secret: Octets<'a>,
277    /// Original optional key attributes, distinct from bag attributes.
278    pub attributes: Option<OwnedVec<Attribute<'a>>>,
279    /// Validated original IMPLICIT BIT STRING, including BER segmentation.
280    pub public_key: Option<EncodedValue<'a>>,
281}
282impl<'a> PrivateKey<'a> {
283    /// Reads the original key container without normalizing or copying secrets.
284    pub fn read(parent: &Reader<'a>, element: &Element<'a>) -> Result<Self> {
285        let mut inside = sequence(parent, element)?;
286        let version = number(&mut inside)?
287            .as_u64()
288            .ok_or_else(|| invalid(ErrorCode::ValueTooLarge, element))?;
289        let algorithm = algorithm(&mut inside)?;
290        let value = required(&mut inside, FIELD)?;
291        let secret = Octets::read(&inside, &value, FIELD)?;
292        let mut next = inside.next_element().map_err(encoding)?;
293        let attributes = if let Some(set) = next.filter(|v| v.tag() == Tag::context(true, 0)) {
294            let attributes = attributes(&inside, &set, Tag::context(true, 0))?;
295            next = inside.next_element().map_err(encoding)?;
296            Some(attributes)
297        } else {
298            None
299        };
300        let public_key = if let Some(bits) = next {
301            if bits.tag() != Tag::context(false, 1) && bits.tag() != Tag::context(true, 1) {
302                return Err(invalid(ErrorCode::UnexpectedTag, &bits));
303            }
304            structured_pkix::der::implicit_bit_string(&inside, &bits, FIELD)
305                .map_err(Error::pkix)?;
306            Some(EncodedValue::new(&inside, bits))
307        } else {
308            None
309        };
310        end(&inside)?;
311        if version != u64::from(public_key.is_some()) {
312            return Err(invalid(ErrorCode::VersionMismatch, element));
313        }
314        Ok(Self {
315            version,
316            algorithm,
317            secret,
318            attributes,
319            public_key,
320        })
321    }
322}
323
324/// `EncryptedPrivateKeyInfo`, with original protection parameters/ciphertext.
325pub struct ShroudedKey<'a> {
326    /// Original protection algorithm.
327    pub algorithm: AlgorithmIdentifier<'a>,
328    /// Original encrypted private-key contents.
329    pub encrypted: Octets<'a>,
330    /// Structural password-protection interpretation; unknown remains open.
331    pub protection: protection::Protection<'a>,
332}
333impl<'a> ShroudedKey<'a> {
334    /// Reads the whole original encrypted key, without KDF/decryption work.
335    pub fn read(parent: &Reader<'a>, element: &Element<'a>) -> Result<Self> {
336        let mut inside = sequence(parent, element)?;
337        let algorithm = algorithm(&mut inside)?;
338        let value = required(&mut inside, FIELD)?;
339        let encrypted = Octets::read(&inside, &value, FIELD)?;
340        end(&inside)?;
341        let protection = protection::Protection::read(algorithm, parent)?;
342        Ok(Self {
343            algorithm,
344            encrypted,
345            protection,
346        })
347    }
348}
349
350/// A typed original bag value, including open types and nested bags.
351#[expect(
352    clippy::large_enum_variant,
353    reason = "one bag is decoded/visited at a time; inline protection facts avoid an extra heap owner per shrouded key"
354)]
355pub enum BagValue<'a> {
356    /// Plain PKCS#8 key.
357    Key(PrivateKey<'a>),
358    /// Shrouded PKCS#8 key.
359    Shrouded(ShroudedKey<'a>),
360    /// X.509 certificate bytes (not implicitly parsed/authenticated).
361    Certificate(Octets<'a>),
362    /// SDSI base64 certificate text with shared IA5 syntax validation.
363    Sdsi(Octets<'a>),
364    /// X.509 CRL bytes (not implicitly authenticated).
365    Crl(Octets<'a>),
366    /// A registered or unknown secret value.
367    Secret {
368        /// Original secret-type OID.
369        kind: Oid<'a>,
370        /// Whole original open value.
371        value: EncodedValue<'a>,
372    },
373    /// An open bag/certificate/CRL type, without guessed normalization.
374    Other {
375        /// Original type OID.
376        kind: Oid<'a>,
377        /// Whole original open value.
378        value: EncodedValue<'a>,
379    },
380    /// Nested `SafeContents`, visited iteratively by the processor.
381    Contents(EncodedValue<'a>),
382}
383/// `SafeBag` with original identity/attributes and decoded value.
384pub struct Bag<'a> {
385    /// Original bag identifier, including unknown extensions.
386    pub kind: Oid<'a>,
387    /// Original whole bag encoding and producing context.
388    pub original: EncodedValue<'a>,
389    /// Parsed original value, never a product-selected key.
390    pub value: BagValue<'a>,
391    /// Optional original attributes, including repeated/empty unknown values.
392    pub attributes: Option<OwnedVec<Attribute<'a>>>,
393}
394impl<'a> Bag<'a> {
395    /// Reads one complete original `SafeBag`. All six standard alternatives use
396    /// the existing shared field kernels, not a second BER grammar.
397    pub fn read(parent: &Reader<'a>, element: &Element<'a>) -> Result<Self> {
398        let mut inside = sequence(parent, element)?;
399        let kind = inside.read_oid().map_err(encoding)?;
400        let wrapper = inside.expect(Tag::context(true, 0)).map_err(encoding)?;
401        let mut explicit = inside.enter(&wrapper).map_err(encoding)?;
402        let value = required(&mut explicit, FIELD)?;
403        end(&explicit)?;
404        let attributes = inside
405            .next_element()
406            .map_err(encoding)?
407            .map(|set| attributes(&inside, &set, Tag::constructed(universal::SET)))
408            .transpose()?;
409        end(&inside)?;
410        // RFC7292 §4.2: BAG-TYPE is extensible. Unknown does not mean KeyBag.
411        let value = if bag_id(parent, kind, 1)? {
412            BagValue::Key(PrivateKey::read(&explicit, &value)?)
413        } else if bag_id(parent, kind, 2)? {
414            BagValue::Shrouded(ShroudedKey::read(&explicit, &value)?)
415        } else if bag_id(parent, kind, 6)? {
416            tag(&value, Tag::constructed(universal::SEQUENCE))?;
417            BagValue::Contents(EncodedValue::new(&explicit, value))
418        } else if bag_id(parent, kind, 3)? || bag_id(parent, kind, 4)? || bag_id(parent, kind, 5)? {
419            let mut typed = sequence(&explicit, &value)?;
420            let inner_kind = typed.read_oid().map_err(encoding)?;
421            let wrapper = typed.expect(Tag::context(true, 0)).map_err(encoding)?;
422            let mut inner = typed.enter(&wrapper).map_err(encoding)?;
423            let content = required(&mut inner, FIELD)?;
424            end(&inner)?;
425            end(&typed)?;
426            let certificate = Oid::from_bytes(&[42, 134, 72, 134, 247, 13, 1, 9, 22, 1]);
427            let sdsi = Oid::from_bytes(&[42, 134, 72, 134, 247, 13, 1, 9, 22, 2]);
428            let crl = Oid::from_bytes(&[42, 134, 72, 134, 247, 13, 1, 9, 23, 1]);
429            if bag_id(parent, kind, 5)? {
430                structured_pkix::der::conforms(&inner, &content, FIELD).map_err(Error::pkix)?;
431                BagValue::Secret {
432                    kind: inner_kind,
433                    value: EncodedValue::new(&inner, content),
434                }
435            } else if bag_id(parent, kind, 3)? && equal_oid(parent, inner_kind, certificate)? {
436                BagValue::Certificate(Octets::read(&inner, &content, FIELD)?)
437            } else if bag_id(parent, kind, 3)? && equal_oid(parent, inner_kind, sdsi)? {
438                BagValue::Sdsi(Octets::read_ia5(&inner, &content, FIELD)?)
439            } else if bag_id(parent, kind, 4)? && equal_oid(parent, inner_kind, crl)? {
440                BagValue::Crl(Octets::read(&inner, &content, FIELD)?)
441            } else {
442                structured_pkix::der::conforms(&inner, &content, FIELD).map_err(Error::pkix)?;
443                BagValue::Other {
444                    kind: inner_kind,
445                    value: EncodedValue::new(&inner, content),
446                }
447            }
448        } else {
449            structured_pkix::der::conforms(&explicit, &value, FIELD).map_err(Error::pkix)?;
450            BagValue::Other {
451                kind,
452                value: EncodedValue::new(&explicit, value),
453            }
454        };
455        Ok(Self {
456            kind,
457            original: EncodedValue::new(parent, *element),
458            value,
459            attributes,
460        })
461    }
462}
463
464fn bag_id(parent: &Reader<'_>, oid: Oid<'_>, last: u8) -> Result<bool> {
465    equal_oid(
466        parent,
467        oid,
468        Oid::from_bytes(&[42, 134, 72, 134, 247, 13, 1, 12, 10, 1, last]),
469    )
470}
471pub(super) fn equal_oid(parent: &Reader<'_>, left: Oid<'_>, right: Oid<'_>) -> Result<bool> {
472    parent.charge(1).map_err(encoding)?;
473    if left.as_bytes().len() != right.as_bytes().len() {
474        return Ok(false);
475    }
476    parent
477        .charge(left.as_bytes().len() as u64)
478        .map_err(encoding)?;
479    Ok(left == right)
480}
481fn attributes<'a>(
482    parent: &Reader<'a>,
483    set: &Element<'a>,
484    expected: Tag,
485) -> Result<OwnedVec<Attribute<'a>>> {
486    tag(set, expected)?;
487    let mut inside = parent.enter(set).map_err(encoding)?;
488    let execution = parent.execution();
489    let mut out = execution.vector();
490    let mut previous = None;
491    while let Some(value) = inside.next_element().map_err(encoding)? {
492        if inside.encoding().is_der() {
493            if let Some(bytes) = previous
494                && !structured_pkix::der::set_ordered(&inside, bytes, &value, FIELD)
495                    .map_err(Error::pkix)?
496            {
497                return Err(invalid(ErrorCode::SetOrder, &value));
498            }
499            previous = Some(value.as_bytes());
500        }
501        let attribute = Attribute::read(&inside, &value, FIELD)?;
502        // DER values were already walked by Attribute::read. BER inspection
503        // must reopen the saved value context: the list cursor is two levels
504        // above attrValues and would grant nested values those levels for free.
505        if !inside.encoding().is_der() {
506            for (ordinal, item) in attribute.values().iter().enumerate() {
507                let reader = attribute
508                    .value_reader_in(ordinal, &inside, FIELD)?
509                    .expect("ordinal comes from this attribute's original values");
510                structured_pkix::der::conforms(&reader, item, FIELD).map_err(Error::pkix)?;
511            }
512        }
513        out.try_push(attribute)
514            .map_err(|e| Error::resource(e, FIELD, value.span().start))?;
515    }
516    Ok(out.into_owned())
517}
518pub(super) fn sequence<'a>(parent: &Reader<'a>, element: &Element<'a>) -> Result<Reader<'a>> {
519    tag(element, Tag::constructed(universal::SEQUENCE))?;
520    parent.enter(element).map_err(encoding)
521}
522pub(super) fn tag(element: &Element<'_>, expected: Tag) -> Result<()> {
523    if element.tag() != expected {
524        return Err(invalid(ErrorCode::UnexpectedTag, element));
525    }
526    Ok(())
527}
528pub(super) fn end(parent: &Reader<'_>) -> Result<()> {
529    if !parent.is_empty() {
530        return Err(Error::schema(
531            ErrorCode::TrailingData,
532            FIELD,
533            parent.offset(),
534        ));
535    }
536    Ok(())
537}
538pub(super) fn number<'a>(parent: &mut Reader<'a>) -> Result<Integer<'a>> {
539    parent.read_integer().map_err(encoding)
540}
541pub(super) fn positive(value: Integer<'_>, element: &Element<'_>) -> Result<()> {
542    if value.is_negative() || value.as_u64() == Some(0) {
543        return Err(invalid(ErrorCode::MalformedValue, element));
544    }
545    Ok(())
546}
547fn default_one(parent: &Reader<'_>, value: Integer<'_>, element: &Element<'_>) -> Result<()> {
548    if parent.encoding().is_der() && value.as_u64() == Some(1) {
549        return Err(invalid(ErrorCode::DefaultWritten, element));
550    }
551    Ok(())
552}
553pub(super) fn algorithm<'a>(parent: &mut Reader<'a>) -> Result<AlgorithmIdentifier<'a>> {
554    let element = required(parent, FIELD)?;
555    structured_pkix::algorithm::read(parent, &element, FIELD).map_err(Error::pkix)
556}
557pub(super) fn invalid(code: ErrorCode, element: &Element<'_>) -> Error {
558    Error::schema(code, FIELD, element.span().start)
559}
560pub(super) fn encoding(error: structured_asn1::Error) -> Error {
561    Error::encoding(error, FIELD)
562}