1#![allow(missing_docs)]
2use std::{fmt, str::FromStr};
8
9use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
10
11use crate::serialization::b64_encode;
12use crate::{
13 Algorithm, EncodingKey,
14 errors::{self, Error, ErrorKind},
15};
16
17#[cfg(feature = "aws_lc_rs")]
18use aws_lc_rs::{digest, signature as aws_sig};
19#[cfg(feature = "aws_lc_rs")]
20use aws_sig::KeyPair;
21#[cfg(feature = "rust_crypto")]
22use p256::{ecdsa::SigningKey as P256SigningKey, pkcs8::DecodePrivateKey};
23#[cfg(feature = "rust_crypto")]
24use p384::ecdsa::SigningKey as P384SigningKey;
25#[cfg(feature = "rust_crypto")]
26use rsa::{RsaPrivateKey, pkcs1::DecodeRsaPrivateKey, traits::PublicKeyParts};
27#[cfg(feature = "rust_crypto")]
28use sha2::{Digest, Sha256, Sha384, Sha512};
29
30#[derive(Clone, Debug, Eq, PartialEq, Hash)]
32pub enum PublicKeyUse {
33 Signature,
35 Encryption,
37 Other(String),
39}
40
41impl Serialize for PublicKeyUse {
42 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
43 where
44 S: Serializer,
45 {
46 let string = match self {
47 PublicKeyUse::Signature => "sig",
48 PublicKeyUse::Encryption => "enc",
49 PublicKeyUse::Other(other) => other,
50 };
51
52 serializer.serialize_str(string)
53 }
54}
55
56impl<'de> Deserialize<'de> for PublicKeyUse {
57 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
58 where
59 D: Deserializer<'de>,
60 {
61 struct PublicKeyUseVisitor;
62 impl de::Visitor<'_> for PublicKeyUseVisitor {
63 type Value = PublicKeyUse;
64
65 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
66 write!(formatter, "a string")
67 }
68
69 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
70 where
71 E: de::Error,
72 {
73 Ok(match v {
74 "sig" => PublicKeyUse::Signature,
75 "enc" => PublicKeyUse::Encryption,
76 other => PublicKeyUse::Other(other.to_string()),
77 })
78 }
79 }
80
81 deserializer.deserialize_string(PublicKeyUseVisitor)
82 }
83}
84
85#[derive(Clone, Debug, Eq, PartialEq, Hash)]
87pub enum KeyOperations {
88 Sign,
90 Verify,
92 Encrypt,
94 Decrypt,
96 WrapKey,
98 UnwrapKey,
100 DeriveKey,
102 DeriveBits,
104 Other(String),
106}
107
108impl Serialize for KeyOperations {
109 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
110 where
111 S: Serializer,
112 {
113 let string = match self {
114 KeyOperations::Sign => "sign",
115 KeyOperations::Verify => "verify",
116 KeyOperations::Encrypt => "encrypt",
117 KeyOperations::Decrypt => "decrypt",
118 KeyOperations::WrapKey => "wrapKey",
119 KeyOperations::UnwrapKey => "unwrapKey",
120 KeyOperations::DeriveKey => "deriveKey",
121 KeyOperations::DeriveBits => "deriveBits",
122 KeyOperations::Other(other) => other,
123 };
124
125 serializer.serialize_str(string)
126 }
127}
128
129impl<'de> Deserialize<'de> for KeyOperations {
130 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
131 where
132 D: Deserializer<'de>,
133 {
134 struct KeyOperationsVisitor;
135 impl de::Visitor<'_> for KeyOperationsVisitor {
136 type Value = KeyOperations;
137
138 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
139 write!(formatter, "a string")
140 }
141
142 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
143 where
144 E: de::Error,
145 {
146 Ok(match v {
147 "sign" => KeyOperations::Sign,
148 "verify" => KeyOperations::Verify,
149 "encrypt" => KeyOperations::Encrypt,
150 "decrypt" => KeyOperations::Decrypt,
151 "wrapKey" => KeyOperations::WrapKey,
152 "unwrapKey" => KeyOperations::UnwrapKey,
153 "deriveKey" => KeyOperations::DeriveKey,
154 "deriveBits" => KeyOperations::DeriveBits,
155 other => KeyOperations::Other(other.to_string()),
156 })
157 }
158 }
159
160 deserializer.deserialize_string(KeyOperationsVisitor)
161 }
162}
163
164#[allow(non_camel_case_types, clippy::upper_case_acronyms)]
166#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone, Serialize, Deserialize)]
167pub enum KeyAlgorithm {
168 HS256,
170 HS384,
172 HS512,
174
175 ES256,
177 ES384,
179
180 RS256,
182 RS384,
184 RS512,
186
187 PS256,
189 PS384,
191 PS512,
193
194 EdDSA,
196
197 RSA1_5,
199
200 #[serde(rename = "RSA-OAEP")]
202 RSA_OAEP,
203
204 #[serde(rename = "RSA-OAEP-256")]
206 RSA_OAEP_256,
207
208 #[serde(other)]
210 UNKNOWN_ALGORITHM,
211}
212
213impl FromStr for KeyAlgorithm {
214 type Err = Error;
215 fn from_str(s: &str) -> errors::Result<Self> {
216 match s {
217 "HS256" => Ok(KeyAlgorithm::HS256),
218 "HS384" => Ok(KeyAlgorithm::HS384),
219 "HS512" => Ok(KeyAlgorithm::HS512),
220 "ES256" => Ok(KeyAlgorithm::ES256),
221 "ES384" => Ok(KeyAlgorithm::ES384),
222 "RS256" => Ok(KeyAlgorithm::RS256),
223 "RS384" => Ok(KeyAlgorithm::RS384),
224 "PS256" => Ok(KeyAlgorithm::PS256),
225 "PS384" => Ok(KeyAlgorithm::PS384),
226 "PS512" => Ok(KeyAlgorithm::PS512),
227 "RS512" => Ok(KeyAlgorithm::RS512),
228 "EdDSA" => Ok(KeyAlgorithm::EdDSA),
229 "RSA1_5" => Ok(KeyAlgorithm::RSA1_5),
230 "RSA-OAEP" => Ok(KeyAlgorithm::RSA_OAEP),
231 "RSA-OAEP-256" => Ok(KeyAlgorithm::RSA_OAEP_256),
232 _ => Err(ErrorKind::InvalidAlgorithmName.into()),
233 }
234 }
235}
236
237impl fmt::Display for KeyAlgorithm {
238 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
239 write!(f, "{:?}", self)
240 }
241}
242
243impl KeyAlgorithm {
244 fn to_algorithm(self) -> errors::Result<Algorithm> {
245 Algorithm::from_str(self.to_string().as_str())
246 }
247}
248
249#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Default, Hash)]
251pub struct CommonParameters {
252 #[serde(rename = "use", skip_serializing_if = "Option::is_none", default)]
255 pub public_key_use: Option<PublicKeyUse>,
256
257 #[serde(rename = "key_ops", skip_serializing_if = "Option::is_none", default)]
264 pub key_operations: Option<Vec<KeyOperations>>,
265
266 #[serde(rename = "alg", skip_serializing_if = "Option::is_none", default)]
268 pub key_algorithm: Option<KeyAlgorithm>,
269
270 #[serde(rename = "kid", skip_serializing_if = "Option::is_none", default)]
272 pub key_id: Option<String>,
273
274 #[serde(rename = "x5u", skip_serializing_if = "Option::is_none")]
278 pub x509_url: Option<String>,
279
280 #[serde(rename = "x5c", skip_serializing_if = "Option::is_none")]
284 pub x509_chain: Option<Vec<String>>,
285
286 #[serde(rename = "x5t", skip_serializing_if = "Option::is_none")]
290 pub x509_sha1_fingerprint: Option<String>,
291
292 #[serde(rename = "x5t#S256", skip_serializing_if = "Option::is_none")]
296 pub x509_sha256_fingerprint: Option<String>,
297}
298
299#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Hash)]
302pub enum EllipticCurveKeyType {
303 #[default]
305 EC,
306}
307
308#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Hash)]
311pub enum EllipticCurve {
312 #[serde(rename = "P-256")]
314 #[default]
315 P256,
316 #[serde(rename = "P-384")]
318 P384,
319 #[serde(rename = "P-521")]
321 P521,
322 #[serde(rename = "Ed25519")]
324 Ed25519,
325}
326
327#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Default, Hash)]
329pub struct EllipticCurveKeyParameters {
330 #[serde(rename = "kty")]
332 pub key_type: EllipticCurveKeyType,
333 #[serde(rename = "crv")]
336 pub curve: EllipticCurve,
337 pub x: String,
340 pub y: String,
343}
344
345#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Hash)]
348pub enum RSAKeyType {
349 #[default]
351 RSA,
352}
353
354#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Default, Hash)]
356pub struct RSAKeyParameters {
357 #[serde(rename = "kty")]
359 pub key_type: RSAKeyType,
360
361 pub n: String,
364
365 pub e: String,
368}
369
370#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Hash)]
373pub enum OctetKeyType {
374 #[serde(rename = "oct")]
376 #[default]
377 Octet,
378}
379
380#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Default, Hash)]
382pub struct OctetKeyParameters {
383 #[serde(rename = "kty")]
385 pub key_type: OctetKeyType,
386 #[serde(rename = "k")]
388 pub value: String,
389}
390
391#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Hash)]
394pub enum OctetKeyPairType {
395 #[serde(rename = "OKP")]
397 #[default]
398 OctetKeyPair,
399}
400
401#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Default, Hash)]
403pub struct OctetKeyPairParameters {
404 #[serde(rename = "kty")]
406 pub key_type: OctetKeyPairType,
407 #[serde(rename = "crv")]
410 pub curve: EllipticCurve,
411 pub x: String,
413}
414
415#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)]
417#[serde(untagged)]
418pub enum AlgorithmParameters {
419 EllipticCurve(EllipticCurveKeyParameters),
420 RSA(RSAKeyParameters),
421 OctetKey(OctetKeyParameters),
422 OctetKeyPair(OctetKeyPairParameters),
423}
424
425#[derive(Debug, Clone, Eq, PartialEq)]
427pub enum ThumbprintHash {
428 SHA256,
429 SHA384,
430 SHA512,
431}
432
433#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)]
434pub struct Jwk {
435 #[serde(flatten)]
436 pub common: CommonParameters,
437 #[serde(flatten)]
439 pub algorithm: AlgorithmParameters,
440}
441
442#[cfg(feature = "aws_lc_rs")]
443fn extract_rsa_public_key_components(key_content: &[u8]) -> errors::Result<(Vec<u8>, Vec<u8>)> {
444 let key_pair = aws_sig::RsaKeyPair::from_der(key_content)
445 .map_err(|e| ErrorKind::InvalidRsaKey(e.to_string()))?;
446 let public = key_pair.public_key();
447 let components = aws_sig::RsaPublicKeyComponents::<Vec<u8>>::from(public);
448 Ok((components.n, components.e))
449}
450
451#[cfg(feature = "rust_crypto")]
452fn extract_rsa_public_key_components(key_content: &[u8]) -> errors::Result<(Vec<u8>, Vec<u8>)> {
453 let private_key = RsaPrivateKey::from_pkcs1_der(key_content)
454 .map_err(|e| ErrorKind::InvalidRsaKey(e.to_string()))?;
455 let public_key = private_key.to_public_key();
456 Ok((public_key.n().to_bytes_be(), public_key.e().to_bytes_be()))
457}
458
459#[cfg(feature = "aws_lc_rs")]
460fn extract_ec_public_key_coordinates(
461 key_content: &[u8],
462 alg: Algorithm,
463) -> errors::Result<(EllipticCurve, Vec<u8>, Vec<u8>)> {
464 use aws_lc_rs::signature::{
465 ECDSA_P256_SHA256_FIXED_SIGNING, ECDSA_P384_SHA384_FIXED_SIGNING, EcdsaKeyPair,
466 };
467
468 let (signing_alg, curve, pub_elem_bytes) = match alg {
469 Algorithm::ES256 => (&ECDSA_P256_SHA256_FIXED_SIGNING, EllipticCurve::P256, 32),
470 Algorithm::ES384 => (&ECDSA_P384_SHA384_FIXED_SIGNING, EllipticCurve::P384, 48),
471 _ => return Err(ErrorKind::InvalidEcdsaKey.into()),
472 };
473
474 let key_pair = EcdsaKeyPair::from_pkcs8(signing_alg, key_content)
475 .map_err(|_| ErrorKind::InvalidEcdsaKey)?;
476
477 let pub_bytes = key_pair.public_key().as_ref();
478 if pub_bytes[0] != 4 {
479 return Err(ErrorKind::InvalidEcdsaKey.into());
480 }
481
482 let (x, y) = pub_bytes[1..].split_at(pub_elem_bytes);
483 Ok((curve, x.to_vec(), y.to_vec()))
484}
485
486#[cfg(feature = "rust_crypto")]
487fn extract_ec_public_key_coordinates(
488 key_content: &[u8],
489 alg: Algorithm,
490) -> errors::Result<(EllipticCurve, Vec<u8>, Vec<u8>)> {
491 match alg {
492 Algorithm::ES256 => {
493 let signing_key = P256SigningKey::from_pkcs8_der(key_content)
494 .map_err(|_| ErrorKind::InvalidEcdsaKey)?;
495 let public_key = signing_key.verifying_key();
496 let encoded = public_key.to_encoded_point(false);
497 match encoded.coordinates() {
498 p256::elliptic_curve::sec1::Coordinates::Uncompressed { x, y } => {
499 Ok((EllipticCurve::P256, x.to_vec(), y.to_vec()))
500 }
501 _ => Err(ErrorKind::InvalidEcdsaKey.into()),
502 }
503 }
504 Algorithm::ES384 => {
505 let signing_key = P384SigningKey::from_pkcs8_der(key_content)
506 .map_err(|_| ErrorKind::InvalidEcdsaKey)?;
507 let public_key = signing_key.verifying_key();
508 let encoded = public_key.to_encoded_point(false);
509 match encoded.coordinates() {
510 p384::elliptic_curve::sec1::Coordinates::Uncompressed { x, y } => {
511 Ok((EllipticCurve::P384, x.to_vec(), y.to_vec()))
512 }
513 _ => Err(ErrorKind::InvalidEcdsaKey.into()),
514 }
515 }
516 _ => Err(ErrorKind::InvalidEcdsaKey.into()),
517 }
518}
519
520#[cfg(feature = "aws_lc_rs")]
521fn compute_digest(data: &[u8], hash_function: ThumbprintHash) -> Vec<u8> {
522 let algorithm = match hash_function {
523 ThumbprintHash::SHA256 => &digest::SHA256,
524 ThumbprintHash::SHA384 => &digest::SHA384,
525 ThumbprintHash::SHA512 => &digest::SHA512,
526 };
527 digest::digest(algorithm, data).as_ref().to_vec()
528}
529
530#[cfg(feature = "rust_crypto")]
531fn compute_digest(data: &[u8], hash_function: ThumbprintHash) -> Vec<u8> {
532 match hash_function {
533 ThumbprintHash::SHA256 => Sha256::digest(data).to_vec(),
534 ThumbprintHash::SHA384 => Sha384::digest(data).to_vec(),
535 ThumbprintHash::SHA512 => Sha512::digest(data).to_vec(),
536 }
537}
538
539impl Jwk {
540 pub fn is_supported(&self) -> bool {
542 match self.common.key_algorithm {
543 Some(alg) => alg.to_algorithm().is_ok(),
544 _ => false,
545 }
546 }
547 pub fn from_encoding_key(key: &EncodingKey, alg: Algorithm) -> crate::errors::Result<Self> {
548 Ok(Self {
549 common: CommonParameters {
550 key_algorithm: Some(match alg {
551 Algorithm::HS256 => KeyAlgorithm::HS256,
552 Algorithm::HS384 => KeyAlgorithm::HS384,
553 Algorithm::HS512 => KeyAlgorithm::HS512,
554 Algorithm::ES256 => KeyAlgorithm::ES256,
555 Algorithm::ES384 => KeyAlgorithm::ES384,
556 Algorithm::RS256 => KeyAlgorithm::RS256,
557 Algorithm::RS384 => KeyAlgorithm::RS384,
558 Algorithm::RS512 => KeyAlgorithm::RS512,
559 Algorithm::PS256 => KeyAlgorithm::PS256,
560 Algorithm::PS384 => KeyAlgorithm::PS384,
561 Algorithm::PS512 => KeyAlgorithm::PS512,
562 Algorithm::EdDSA => KeyAlgorithm::EdDSA,
563 }),
564 ..Default::default()
565 },
566 algorithm: match key.family {
567 crate::algorithms::AlgorithmFamily::Hmac => {
568 AlgorithmParameters::OctetKey(OctetKeyParameters {
569 key_type: OctetKeyType::Octet,
570 value: b64_encode(&key.content),
571 })
572 }
573 crate::algorithms::AlgorithmFamily::Rsa => {
574 let (n, e) = extract_rsa_public_key_components(&key.content)?;
575 AlgorithmParameters::RSA(RSAKeyParameters {
576 key_type: RSAKeyType::RSA,
577 n: b64_encode(n),
578 e: b64_encode(e),
579 })
580 }
581 crate::algorithms::AlgorithmFamily::Ec => {
582 let (curve, x, y) = extract_ec_public_key_coordinates(&key.content, alg)?;
583 AlgorithmParameters::EllipticCurve(EllipticCurveKeyParameters {
584 key_type: EllipticCurveKeyType::EC,
585 curve,
586 x: b64_encode(x),
587 y: b64_encode(y),
588 })
589 }
590 crate::algorithms::AlgorithmFamily::Ed => {
591 unimplemented!();
592 }
593 },
594 })
595 }
596
597 pub fn thumbprint(&self, hash_function: ThumbprintHash) -> String {
601 let pre = match &self.algorithm {
602 AlgorithmParameters::EllipticCurve(a) => match a.curve {
603 EllipticCurve::P256 | EllipticCurve::P384 | EllipticCurve::P521 => {
604 format!(
605 r#"{{"crv":{},"kty":{},"x":"{}","y":"{}"}}"#,
606 serde_json::to_string(&a.curve).unwrap(),
607 serde_json::to_string(&a.key_type).unwrap(),
608 a.x,
609 a.y,
610 )
611 }
612 EllipticCurve::Ed25519 => panic!("EllipticCurve can't contain this curve type"),
613 },
614 AlgorithmParameters::RSA(a) => {
615 format!(
616 r#"{{"e":"{}","kty":{},"n":"{}"}}"#,
617 a.e,
618 serde_json::to_string(&a.key_type).unwrap(),
619 a.n,
620 )
621 }
622 AlgorithmParameters::OctetKey(a) => {
623 format!(
624 r#"{{"k":"{}","kty":{}}}"#,
625 a.value,
626 serde_json::to_string(&a.key_type).unwrap()
627 )
628 }
629 AlgorithmParameters::OctetKeyPair(a) => match a.curve {
630 EllipticCurve::P256 | EllipticCurve::P384 | EllipticCurve::P521 => {
631 panic!("OctetKeyPair can't contain this curve type")
632 }
633 EllipticCurve::Ed25519 => {
634 format!(
635 r#"{{crv:{},"kty":{},"x":"{}"}}"#,
636 serde_json::to_string(&a.curve).unwrap(),
637 serde_json::to_string(&a.key_type).unwrap(),
638 a.x,
639 )
640 }
641 },
642 };
643 b64_encode(compute_digest(pre.as_bytes(), hash_function))
644 }
645}
646
647#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
649pub struct JwkSet {
650 pub keys: Vec<Jwk>,
651}
652
653impl JwkSet {
654 pub fn find(&self, kid: &str) -> Option<&Jwk> {
656 self.keys
657 .iter()
658 .find(|jwk| jwk.common.key_id.is_some() && jwk.common.key_id.as_ref().unwrap() == kid)
659 }
660}
661
662#[cfg(test)]
663mod tests {
664 use serde_json::json;
665 use wasm_bindgen_test::wasm_bindgen_test;
666
667 use crate::Algorithm;
668 use crate::jwk::{
669 AlgorithmParameters, Jwk, JwkSet, KeyAlgorithm, OctetKeyType, RSAKeyParameters,
670 ThumbprintHash,
671 };
672 use crate::serialization::b64_encode;
673
674 #[test]
675 #[wasm_bindgen_test]
676 fn check_hs256() {
677 let key = b64_encode("abcdefghijklmnopqrstuvwxyz012345");
678 let jwks_json = json!({
679 "keys": [
680 {
681 "kty": "oct",
682 "alg": "HS256",
683 "kid": "abc123",
684 "k": key
685 }
686 ]
687 });
688
689 let set: JwkSet = serde_json::from_value(jwks_json).expect("Failed HS256 check");
690 assert_eq!(set.keys.len(), 1);
691 let key = &set.keys[0];
692 assert_eq!(key.common.key_id, Some("abc123".to_string()));
693 let algorithm = key.common.key_algorithm.unwrap().to_algorithm().unwrap();
694 assert_eq!(algorithm, Algorithm::HS256);
695
696 match &key.algorithm {
697 AlgorithmParameters::OctetKey(key) => {
698 assert_eq!(key.key_type, OctetKeyType::Octet);
699 assert_eq!(key.value, key.value)
700 }
701 _ => panic!("Unexpected key algorithm"),
702 }
703 }
704
705 #[test]
706 fn deserialize_unknown_key_algorithm() {
707 let key_alg_json = json!("");
708 let key_alg_result: KeyAlgorithm =
709 serde_json::from_value(key_alg_json).expect("Could not deserialize json");
710 assert_eq!(key_alg_result, KeyAlgorithm::UNKNOWN_ALGORITHM);
711 }
712
713 #[test]
714 #[wasm_bindgen_test]
715 fn check_thumbprint() {
716 let tp = Jwk {
717 common: crate::jwk::CommonParameters { key_id: Some("2011-04-29".to_string()), ..Default::default() },
718 algorithm: AlgorithmParameters::RSA(RSAKeyParameters {
719 key_type: crate::jwk::RSAKeyType::RSA,
720 n: "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw".to_string(),
721 e: "AQAB".to_string(),
722 }),
723 }
724 .thumbprint(ThumbprintHash::SHA256);
725 assert_eq!(tp.as_str(), "NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs");
726 }
727}