Skip to main content

embedded_tls/
pki.rs

1use crate::TlsError;
2use crate::config::{Certificate, TlsCipherSuite, TlsClock, TlsVerifier};
3#[cfg(feature = "p384")]
4use crate::der_certificate::ECDSA_SHA384;
5#[cfg(feature = "ed25519")]
6use crate::der_certificate::ED25519;
7use crate::der_certificate::{
8    DecodedCertificate, ECDSA_SHA256, HOSTNAME_MAXLEN, MAX_SAN_DNS_NAMES, Time,
9    extract_common_name, extract_san_dns_names,
10};
11#[cfg(feature = "rsa")]
12use crate::der_certificate::{RSA_PKCS1_SHA256, RSA_PKCS1_SHA384, RSA_PKCS1_SHA512};
13use crate::extensions::extension_data::signature_algorithms::SignatureScheme;
14use crate::handshake::{
15    certificate::{
16        Certificate as OwnedCertificate, CertificateEntryRef, CertificateRef as ServerCertificate,
17    },
18    certificate_verify::CertificateVerifyRef,
19};
20use crate::parse_buffer::ParseError;
21use core::marker::PhantomData;
22#[cfg(feature = "defmt")]
23use defmt::Debug2Format;
24use der::Decode;
25use digest::Digest;
26use heapless::Vec;
27
28pub struct CertificateNames {
29    pub common_name: Option<heapless::String<HOSTNAME_MAXLEN>>,
30    pub san_dns_names: heapless::Vec<heapless::String<HOSTNAME_MAXLEN>, MAX_SAN_DNS_NAMES>,
31}
32
33pub struct CertificateChain<'a> {
34    prev: &'a CertificateEntryRef<'a>,
35    chain: &'a ServerCertificate<'a>,
36    idx: isize,
37}
38
39impl<'a> CertificateChain<'a> {
40    pub fn new(ca: &'a CertificateEntryRef, chain: &'a ServerCertificate<'a>) -> Self {
41        Self {
42            prev: ca,
43            chain,
44            idx: chain.entries.len() as isize - 1,
45        }
46    }
47}
48
49impl<'a> Iterator for CertificateChain<'a> {
50    type Item = (&'a CertificateEntryRef<'a>, &'a CertificateEntryRef<'a>);
51
52    fn next(&mut self) -> Option<Self::Item> {
53        if self.idx < 0 {
54            return None;
55        }
56
57        let cur = &self.chain.entries[self.idx as usize];
58        let out = (self.prev, cur);
59
60        self.prev = cur;
61        self.idx -= 1;
62
63        Some(out)
64    }
65}
66
67pub struct CertVerifier<'a, CipherSuite, Clock, const CERT_SIZE: usize>
68where
69    Clock: TlsClock,
70    CipherSuite: TlsCipherSuite,
71{
72    ca: Certificate<&'a [u8]>,
73    host: Option<heapless::String<64>>,
74    certificate_transcript: Option<CipherSuite::Hash>,
75    certificate: Option<OwnedCertificate<CERT_SIZE>>,
76    _clock: PhantomData<Clock>,
77}
78
79impl<'a, CipherSuite, Clock, const CERT_SIZE: usize> CertVerifier<'a, CipherSuite, Clock, CERT_SIZE>
80where
81    Clock: TlsClock,
82    CipherSuite: TlsCipherSuite,
83{
84    #[must_use]
85    pub fn new(ca: Certificate<&'a [u8]>) -> Self {
86        Self {
87            ca,
88            host: None,
89            certificate_transcript: None,
90            certificate: None,
91            _clock: PhantomData,
92        }
93    }
94}
95
96impl<CipherSuite, Clock, const CERT_SIZE: usize> TlsVerifier<CipherSuite>
97    for CertVerifier<'_, CipherSuite, Clock, CERT_SIZE>
98where
99    CipherSuite: TlsCipherSuite,
100    Clock: TlsClock,
101{
102    fn set_hostname_verification(&mut self, hostname: &str) -> Result<(), TlsError> {
103        self.host.replace(
104            heapless::String::try_from(hostname).map_err(|_| TlsError::InsufficientSpace)?,
105        );
106        Ok(())
107    }
108
109    fn verify_certificate(
110        &mut self,
111        transcript: &CipherSuite::Hash,
112        cert: ServerCertificate,
113    ) -> Result<(), TlsError> {
114        let mut names = CertificateNames {
115            common_name: None,
116            san_dns_names: heapless::Vec::new(),
117        };
118
119        for (p, q) in CertificateChain::new(&(&self.ca).into(), &cert) {
120            names = verify_certificate(p, q, Clock::now())?;
121        }
122
123        if !tls_hostname_match(&names, &self.host) {
124            error!(
125                "Hostname ({:?}) does not match certificate names (CN={:?}, SANs={:?})",
126                self.host, names.common_name, names.san_dns_names
127            );
128            return Err(TlsError::InvalidCertificate);
129        }
130
131        self.certificate.replace(cert.try_into()?);
132        self.certificate_transcript.replace(transcript.clone());
133        Ok(())
134    }
135
136    fn verify_signature(&mut self, verify: CertificateVerifyRef) -> Result<(), TlsError> {
137        let handshake_hash = unwrap!(self.certificate_transcript.take());
138        let ctx_str = b"TLS 1.3, server CertificateVerify\x00";
139        let mut msg: Vec<u8, 146> = Vec::new();
140        msg.resize(64, 0x20).map_err(|_| TlsError::EncodeError)?;
141        msg.extend_from_slice(ctx_str)
142            .map_err(|_| TlsError::EncodeError)?;
143        msg.extend_from_slice(&handshake_hash.finalize())
144            .map_err(|_| TlsError::EncodeError)?;
145
146        let certificate = unwrap!(self.certificate.as_ref()).try_into()?;
147        verify_signature(&msg[..], &certificate, &verify)?;
148        Ok(())
149    }
150}
151
152fn verify_signature(
153    message: &[u8],
154    certificate: &ServerCertificate,
155    verify: &CertificateVerifyRef,
156) -> Result<(), TlsError> {
157    let verified;
158
159    let certificate =
160        if let Some(CertificateEntryRef::X509(certificate)) = certificate.entries.first() {
161            certificate
162        } else {
163            return Err(TlsError::DecodeError);
164        };
165
166    let certificate =
167        DecodedCertificate::from_der(certificate).map_err(|_| TlsError::DecodeError)?;
168
169    let public_key = certificate
170        .tbs_certificate
171        .subject_public_key_info
172        .public_key
173        .as_bytes()
174        .ok_or(TlsError::DecodeError)?;
175
176    match verify.signature_scheme {
177        SignatureScheme::EcdsaSecp256r1Sha256 => {
178            use p256::ecdsa::{Signature, VerifyingKey, signature::Verifier};
179            let verifying_key =
180                VerifyingKey::from_sec1_bytes(public_key).map_err(|_| TlsError::DecodeError)?;
181            let signature =
182                Signature::from_der(&verify.signature).map_err(|_| TlsError::DecodeError)?;
183            verified = verifying_key.verify(message, &signature).is_ok();
184        }
185        #[cfg(feature = "p384")]
186        SignatureScheme::EcdsaSecp384r1Sha384 => {
187            use p384::ecdsa::{Signature, VerifyingKey, signature::Verifier};
188            let verifying_key =
189                VerifyingKey::from_sec1_bytes(public_key).map_err(|_| TlsError::DecodeError)?;
190            let signature =
191                Signature::from_der(&verify.signature).map_err(|_| TlsError::DecodeError)?;
192            verified = verifying_key.verify(message, &signature).is_ok();
193        }
194        #[cfg(feature = "ed25519")]
195        SignatureScheme::Ed25519 => {
196            use ed25519_dalek::{Signature, Verifier, VerifyingKey};
197            let verifying_key: VerifyingKey =
198                VerifyingKey::from_bytes(public_key.try_into().unwrap())
199                    .map_err(|_| TlsError::DecodeError)?;
200            let signature =
201                Signature::try_from(verify.signature).map_err(|_| TlsError::DecodeError)?;
202            verified = verifying_key.verify(message, &signature).is_ok();
203        }
204        #[cfg(feature = "rsa")]
205        SignatureScheme::RsaPssRsaeSha256 => {
206            use rsa::{
207                RsaPublicKey,
208                pkcs1::DecodeRsaPublicKey,
209                pss::{Signature, VerifyingKey},
210                signature::Verifier,
211            };
212            use sha2::Sha256;
213
214            let der_pubkey = RsaPublicKey::from_pkcs1_der(public_key).unwrap();
215            let verifying_key = VerifyingKey::<Sha256>::from(der_pubkey);
216
217            let signature =
218                Signature::try_from(verify.signature).map_err(|_| TlsError::DecodeError)?;
219            verified = verifying_key.verify(message, &signature).is_ok();
220        }
221        #[cfg(feature = "rsa")]
222        SignatureScheme::RsaPssRsaeSha384 => {
223            use rsa::{
224                RsaPublicKey,
225                pkcs1::DecodeRsaPublicKey,
226                pss::{Signature, VerifyingKey},
227                signature::Verifier,
228            };
229            use sha2::Sha384;
230
231            let der_pubkey =
232                RsaPublicKey::from_pkcs1_der(public_key).map_err(|_| TlsError::DecodeError)?;
233            let verifying_key = VerifyingKey::<Sha384>::from(der_pubkey);
234
235            let signature =
236                Signature::try_from(verify.signature).map_err(|_| TlsError::DecodeError)?;
237            verified = verifying_key.verify(message, &signature).is_ok();
238        }
239        #[cfg(feature = "rsa")]
240        SignatureScheme::RsaPssRsaeSha512 => {
241            use rsa::{
242                RsaPublicKey,
243                pkcs1::DecodeRsaPublicKey,
244                pss::{Signature, VerifyingKey},
245                signature::Verifier,
246            };
247            use sha2::Sha512;
248
249            let der_pubkey =
250                RsaPublicKey::from_pkcs1_der(public_key).map_err(|_| TlsError::DecodeError)?;
251            let verifying_key = VerifyingKey::<Sha512>::from(der_pubkey);
252
253            let signature =
254                Signature::try_from(verify.signature).map_err(|_| TlsError::DecodeError)?;
255            verified = verifying_key.verify(message, &signature).is_ok();
256        }
257        _ => {
258            error!(
259                "InvalidSignatureScheme: {:?} Are you missing a feature?",
260                verify.signature_scheme
261            );
262            return Err(TlsError::InvalidSignatureScheme);
263        }
264    }
265
266    if !verified {
267        return Err(TlsError::InvalidSignature);
268    }
269    Ok(())
270}
271
272fn get_certificate_tlv_bytes<'a>(input: &[u8]) -> der::Result<&[u8]> {
273    use der::{Decode, Reader, SliceReader};
274
275    let mut reader = SliceReader::new(input)?;
276    let top_header = der::Header::decode(&mut reader)?;
277    top_header.tag().assert_eq(der::Tag::Sequence)?;
278
279    let header = der::Header::peek(&mut reader)?;
280    header.tag().assert_eq(der::Tag::Sequence)?;
281
282    reader.tlv_bytes()
283}
284
285fn get_cert_time(time: Time) -> u64 {
286    match time {
287        Time::UtcTime(utc_time) => utc_time.to_unix_duration().as_secs(),
288        Time::GeneralTime(generalized_time) => generalized_time.to_unix_duration().as_secs(),
289    }
290}
291
292fn verify_certificate(
293    verifier: &CertificateEntryRef,
294    certificate: &CertificateEntryRef,
295    now: Option<u64>,
296) -> Result<CertificateNames, TlsError> {
297    let mut verified = false;
298    let mut common_name = None;
299    let mut san_dns_names = heapless::Vec::new();
300
301    let ca_certificate = if let CertificateEntryRef::X509(verifier) = verifier {
302        DecodedCertificate::from_der(verifier).map_err(|_| TlsError::DecodeError)?
303    } else {
304        return Err(TlsError::DecodeError);
305    };
306
307    if let CertificateEntryRef::X509(certificate) = certificate {
308        let parsed_certificate =
309            DecodedCertificate::from_der(certificate).map_err(|_| TlsError::DecodeError)?;
310
311        let ca_public_key = ca_certificate
312            .tbs_certificate
313            .subject_public_key_info
314            .public_key
315            .as_bytes()
316            .ok_or(TlsError::DecodeError)?;
317
318        common_name = extract_common_name(&parsed_certificate.tbs_certificate)
319            .map_err(|_| TlsError::DecodeError)?;
320        debug!("CommonName: {:?}", common_name);
321
322        san_dns_names = extract_san_dns_names(&parsed_certificate.tbs_certificate)
323            .map_err(|_| TlsError::DecodeError)?;
324        debug!("SANs: {:?}", san_dns_names);
325
326        if let Some(now) = now {
327            if get_cert_time(parsed_certificate.tbs_certificate.validity.not_before) > now
328                || get_cert_time(parsed_certificate.tbs_certificate.validity.not_after) < now
329            {
330                return Err(TlsError::InvalidCertificate);
331            }
332            debug!("Epoch is {} and certificate is valid!", now)
333        }
334
335        let certificate_data =
336            get_certificate_tlv_bytes(certificate).map_err(|_| TlsError::DecodeError)?;
337
338        match parsed_certificate.signature_algorithm {
339            ECDSA_SHA256 => {
340                use p256::ecdsa::{Signature, VerifyingKey, signature::Verifier};
341                let verifying_key = VerifyingKey::from_sec1_bytes(ca_public_key)
342                    .map_err(|_| TlsError::DecodeError)?;
343
344                let signature = Signature::from_der(
345                    parsed_certificate
346                        .signature
347                        .as_bytes()
348                        .ok_or(TlsError::ParseError(ParseError::InvalidData))?,
349                )
350                .map_err(|_| TlsError::ParseError(ParseError::InvalidData))?;
351
352                verified = verifying_key.verify(&certificate_data, &signature).is_ok();
353            }
354            #[cfg(feature = "p384")]
355            ECDSA_SHA384 => {
356                use p384::ecdsa::{Signature, VerifyingKey, signature::Verifier};
357                let verifying_key = VerifyingKey::from_sec1_bytes(ca_public_key)
358                    .map_err(|_| TlsError::DecodeError)?;
359
360                let signature = Signature::from_der(
361                    parsed_certificate
362                        .signature
363                        .as_bytes()
364                        .ok_or(TlsError::ParseError(ParseError::InvalidData))?,
365                )
366                .map_err(|_| TlsError::ParseError(ParseError::InvalidData))?;
367
368                verified = verifying_key.verify(&certificate_data, &signature).is_ok();
369            }
370            #[cfg(feature = "ed25519")]
371            ED25519 => {
372                use ed25519_dalek::{Signature, Verifier, VerifyingKey};
373                let verifying_key: VerifyingKey =
374                    VerifyingKey::from_bytes(ca_public_key.try_into().unwrap())
375                        .map_err(|_| TlsError::DecodeError)?;
376
377                let signature = Signature::try_from(
378                    parsed_certificate
379                        .signature
380                        .as_bytes()
381                        .ok_or(TlsError::ParseError(ParseError::InvalidData))?,
382                )
383                .map_err(|_| TlsError::ParseError(ParseError::InvalidData))?;
384
385                verified = verifying_key.verify(certificate_data, &signature).is_ok();
386            }
387            #[cfg(feature = "rsa")]
388            a if a == RSA_PKCS1_SHA256 => {
389                use rsa::{
390                    pkcs1::DecodeRsaPublicKey,
391                    pkcs1v15::{Signature, VerifyingKey},
392                    signature::Verifier,
393                };
394                use sha2::Sha256;
395
396                let verifying_key =
397                    VerifyingKey::<Sha256>::from_pkcs1_der(ca_public_key).map_err(|e| {
398                        #[cfg(feature = "defmt")]
399                        error!("VerifyingKey: {:?}", Debug2Format(&e));
400                        #[cfg(not(feature = "defmt"))]
401                        error!("VerifyingKey: {}", e);
402                        TlsError::DecodeError
403                    })?;
404
405                let signature = Signature::try_from(
406                    parsed_certificate
407                        .signature
408                        .as_bytes()
409                        .ok_or(TlsError::ParseError(ParseError::InvalidData))?,
410                )
411                .map_err(|e| {
412                    #[cfg(feature = "defmt")]
413                    error!("Signature: {:?}", Debug2Format(&e));
414                    #[cfg(not(feature = "defmt"))]
415                    error!("Signature: {}", e);
416                    TlsError::ParseError(ParseError::InvalidData)
417                })?;
418
419                verified = verifying_key.verify(certificate_data, &signature).is_ok();
420            }
421            #[cfg(feature = "rsa")]
422            a if a == RSA_PKCS1_SHA384 => {
423                use rsa::{
424                    pkcs1::DecodeRsaPublicKey,
425                    pkcs1v15::{Signature, VerifyingKey},
426                    signature::Verifier,
427                };
428                use sha2::Sha384;
429
430                let verifying_key = VerifyingKey::<Sha384>::from_pkcs1_der(ca_public_key)
431                    .map_err(|_| TlsError::DecodeError)?;
432
433                let signature = Signature::try_from(
434                    parsed_certificate
435                        .signature
436                        .as_bytes()
437                        .ok_or(TlsError::ParseError(ParseError::InvalidData))?,
438                )
439                .map_err(|_| TlsError::ParseError(ParseError::InvalidData))?;
440
441                verified = verifying_key.verify(certificate_data, &signature).is_ok();
442            }
443            #[cfg(feature = "rsa")]
444            a if a == RSA_PKCS1_SHA512 => {
445                use rsa::{
446                    pkcs1::DecodeRsaPublicKey,
447                    pkcs1v15::{Signature, VerifyingKey},
448                    signature::Verifier,
449                };
450                use sha2::Sha512;
451
452                let verifying_key = VerifyingKey::<Sha512>::from_pkcs1_der(ca_public_key)
453                    .map_err(|_| TlsError::DecodeError)?;
454
455                let signature = Signature::try_from(
456                    parsed_certificate
457                        .signature
458                        .as_bytes()
459                        .ok_or(TlsError::ParseError(ParseError::InvalidData))?,
460                )
461                .map_err(|_| TlsError::ParseError(ParseError::InvalidData))?;
462
463                verified = verifying_key.verify(certificate_data, &signature).is_ok();
464            }
465            _ => {
466                error!(
467                    "Unsupported signature alg: {:?}",
468                    parsed_certificate.signature_algorithm
469                );
470                return Err(TlsError::InvalidSignatureScheme);
471            }
472        }
473    }
474
475    if !verified {
476        return Err(TlsError::InvalidCertificate);
477    }
478
479    Ok(CertificateNames {
480        common_name,
481        san_dns_names,
482    })
483}
484
485/// Match a hostname against the certificate's names.
486///
487/// Per RFC 6125 Section 6.4.4, if the certificate contains Subject Alternative
488/// Names (SANs), only the SANs are used for matching and the Common Name (CN)
489/// is ignored. If no SANs are present, the CN is used as a fallback.
490fn tls_hostname_match(
491    names: &CertificateNames,
492    hostname: &Option<heapless::String<HOSTNAME_MAXLEN>>,
493) -> bool {
494    let hostname = match hostname.as_ref() {
495        Some(h) => h,
496        None => {
497            return names.common_name.is_none() && names.san_dns_names.is_empty();
498        }
499    };
500
501    for san in &names.san_dns_names {
502        if tls_hostname_match_impl(san.as_bytes(), hostname.as_bytes()) {
503            return true;
504        }
505    }
506
507    match names.common_name.as_ref() {
508        Some(cn) => tls_hostname_match_impl(cn.as_bytes(), hostname.as_bytes()),
509        None => false,
510    }
511}
512
513fn tls_hostname_match_impl(cn: &[u8], host: &[u8]) -> bool {
514    let mut cn_labels = 1;
515    let mut host_labels = 1;
516    let mut stars = 0;
517
518    for &b in cn {
519        match b {
520            b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'-' | b'.' | b'*' => {}
521            _ => return false,
522        }
523        if b == b'.' {
524            cn_labels += 1;
525        }
526        if b == b'*' {
527            stars += 1;
528        }
529    }
530
531    for &b in host {
532        match b {
533            b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'-' | b'.' => {}
534            _ => return false,
535        }
536        if b == b'.' {
537            host_labels += 1;
538        }
539    }
540
541    if stars == 0 {
542        if cn.len() != host.len() {
543            return false;
544        }
545        for i in 0..cn.len() {
546            if cn[i].to_ascii_lowercase() != host[i].to_ascii_lowercase() {
547                return false;
548            }
549        }
550        return true;
551    }
552
553    // RFC 6125 wildcard rules
554    if stars != 1 {
555        return false;
556    }
557    if !cn.starts_with(b"*.") {
558        return false;
559    }
560    if cn_labels < 3 {
561        return false;
562    }
563    if cn_labels != host_labels {
564        return false;
565    }
566
567    let suffix = &cn[2..];
568    let mut dot_idx = None;
569    for i in 0..host.len() {
570        if host[i] == b'.' {
571            dot_idx = Some(i);
572            break;
573        }
574    }
575    let dot_idx = match dot_idx {
576        Some(i) => i,
577        None => return false,
578    };
579    let host_suffix = &host[dot_idx + 1..];
580
581    if suffix.len() != host_suffix.len() {
582        return false;
583    }
584
585    for i in 0..suffix.len() {
586        if suffix[i].to_ascii_lowercase() != host_suffix[i].to_ascii_lowercase() {
587            return false;
588        }
589    }
590
591    true
592}
593
594#[cfg(test)]
595mod tests {
596    use super::tls_hostname_match_impl;
597
598    #[test]
599    fn exact_match() {
600        assert!(tls_hostname_match_impl(b"example.com", b"example.com"));
601        assert!(tls_hostname_match_impl(b"EXAMPLE.COM", b"example.com"));
602        assert!(tls_hostname_match_impl(b"example.com", b"EXAMPLE.COM"));
603    }
604
605    #[test]
606    fn exact_mismatch() {
607        assert!(!tls_hostname_match_impl(b"example.com", b"example.org"));
608        assert!(!tls_hostname_match_impl(b"example.com", b"sub.example.com"));
609    }
610
611    #[test]
612    fn valid_wildcard_match() {
613        assert!(tls_hostname_match_impl(
614            b"*.example.com",
615            b"api.example.com"
616        ));
617        assert!(tls_hostname_match_impl(
618            b"*.example.com",
619            b"WWW.example.com"
620        ));
621    }
622
623    #[test]
624    fn wildcard_single_label_only() {
625        assert!(!tls_hostname_match_impl(
626            b"*.example.com",
627            b"a.b.example.com"
628        ));
629    }
630
631    #[test]
632    fn wildcard_requires_same_label_count() {
633        assert!(!tls_hostname_match_impl(b"*.example.com", b"example.com"));
634        assert!(!tls_hostname_match_impl(
635            b"*.example.com",
636            b"deep.api.example.com"
637        ));
638    }
639
640    #[test]
641    fn wildcard_must_be_leftmost_label() {
642        assert!(!tls_hostname_match_impl(
643            b"api.*.example.com",
644            b"api.test.example.com"
645        ));
646        assert!(!tls_hostname_match_impl(
647            b"foo*.example.xx",
648            b"foobar.example.xx"
649        ));
650    }
651
652    #[test]
653    fn wildcard_requires_minimum_three_labels() {
654        assert!(!tls_hostname_match_impl(b"*.com", b"example.com"));
655        assert!(!tls_hostname_match_impl(b"*.org", b"test.org"));
656    }
657
658    #[test]
659    fn multiple_wildcards_rejected() {
660        assert!(!tls_hostname_match_impl(
661            b"*.*.example.com",
662            b"a.b.example.com"
663        ));
664        assert!(!tls_hostname_match_impl(
665            b"**.example.com",
666            b"api.example.com"
667        ));
668    }
669
670    #[test]
671    fn idna_a_label_supported() {
672        assert!(tls_hostname_match_impl(
673            b"xn--bcher-kva.example",
674            b"xn--bcher-kva.example"
675        ));
676
677        assert!(tls_hostname_match_impl(
678            b"*.xn--bcher-kva.example",
679            b"api.xn--bcher-kva.example"
680        ));
681    }
682
683    #[test]
684    fn unicode_rejected() {
685        assert!(!tls_hostname_match_impl(
686            "bücher.example".as_bytes(),
687            "bücher.example".as_bytes()
688        ));
689        assert!(!tls_hostname_match_impl(
690            "*.bücher.example".as_bytes(),
691            "api.bücher.example".as_bytes()
692        ));
693    }
694
695    #[test]
696    fn invalid_characters_rejected() {
697        assert!(!tls_hostname_match_impl(b"example!.com", b"example!.com"));
698        assert!(!tls_hostname_match_impl(b"example.com", b"exa mple.com"));
699    }
700}