Skip to main content

wire_e2e_identity/
pki.rs

1use std::str::FromStr as _;
2
3use ecdsa::SignatureEncoding as _;
4use openmls_traits::{crypto::OpenMlsCrypto, types::SignatureScheme};
5use spki::{SignatureAlgorithmIdentifier, der::referenced::RefToOwned};
6use x509_cert::{
7    builder::{Builder as _, CertificateBuilder, profile::BuilderProfile},
8    name::Name,
9    time::Validity,
10};
11
12use crate::error::{E2eIdentityError, E2eIdentityResult};
13
14#[derive(Clone)]
15pub enum PkiKeypair {
16    P256(p256::ecdsa::SigningKey),
17    P384(p384::ecdsa::SigningKey),
18    P521(p521::ecdsa::SigningKey),
19    Ed25519(ed25519_dalek::SigningKey),
20}
21
22impl std::fmt::Debug for PkiKeypair {
23    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24        f.debug_struct("PkiKeypair")
25            .field(
26                "type",
27                &match self {
28                    Self::P256(_k) => "P256",
29                    Self::P384(_k) => "P384",
30                    Self::P521(_k) => "P521",
31                    Self::Ed25519(_k) => "Ed25519",
32                },
33            )
34            .field("key", &"[REDACTED]")
35            .finish()
36    }
37}
38
39impl PkiKeypair {
40    pub fn signing_key_bytes(&self) -> Vec<u8> {
41        match self {
42            Self::P256(sk) => sk.to_bytes().to_vec(),
43            Self::P384(sk) => sk.to_bytes().to_vec(),
44            Self::P521(sk) => sk.to_bytes().to_vec(),
45            Self::Ed25519(sk) => sk.to_bytes().to_vec(),
46        }
47    }
48
49    pub fn public_key_bytes(&self) -> Vec<u8> {
50        match self {
51            Self::P256(sk) => sk.verifying_key().to_sec1_bytes().to_vec(),
52            Self::P384(sk) => sk.verifying_key().to_sec1_bytes().to_vec(),
53            Self::P521(sk) => sk.verifying_key().to_sec1_bytes().to_vec(),
54            Self::Ed25519(sk) => sk.verifying_key().to_bytes().to_vec(),
55        }
56    }
57
58    pub fn public_key_identifier(&self) -> Vec<u8> {
59        use sha1::Digest as _;
60        sha1::Sha1::digest(self.public_key_bytes()).to_vec()
61    }
62}
63
64#[derive(Debug)]
65pub struct CertificateGenerationArgs<'a> {
66    pub signature_scheme: SignatureScheme,
67    pub issuer: Option<String>,
68    pub serial: u64,
69    /// Duration since UNIX EPOCH
70    pub validity_start: Option<std::time::Duration>,
71    /// Duration relative to `validity_start` if present. Otherwise relative to now
72    pub validity_from_start: std::time::Duration,
73    pub org: &'a str,
74    pub common_name: Option<&'a str>,
75    pub alternative_names: Option<&'a [&'a str]>,
76    pub domain: Option<&'a str>,
77    pub crl_dps: Option<&'a [&'a str]>,
78    pub signer: Option<&'a PkiKeypair>,
79    pub is_ca: bool,
80    pub is_root: bool,
81}
82
83fn get_extended_keyusage(is_ca: bool) -> x509_cert::ext::pkix::ExtendedKeyUsage {
84    let mut ext_keyusages = vec![];
85    if !is_ca {
86        ext_keyusages.push(x509_cert::der::oid::db::rfc5280::ID_KP_CLIENT_AUTH);
87    }
88
89    x509_cert::ext::pkix::ExtendedKeyUsage(ext_keyusages)
90}
91
92fn subject(args: &CertificateGenerationArgs) -> E2eIdentityResult<Name> {
93    let mut subject_fmt = String::new();
94    if let Some(cn) = args.common_name {
95        subject_fmt.push_str(&format!("CN={cn},"));
96    }
97    subject_fmt.push_str(&format!("O={},C=DE", args.org));
98    Name::from_str(&subject_fmt).map_err(|_| E2eIdentityError::CertificateGenerationError)
99}
100
101fn validity(args: &CertificateGenerationArgs) -> E2eIdentityResult<Validity> {
102    let validity_start = if let Some(validity_start) = args.validity_start {
103        validity_start
104    } else {
105        web_time::SystemTime::now()
106            .duration_since(web_time::UNIX_EPOCH)
107            .map_err(|_| E2eIdentityError::CertificateGenerationError)?
108    } - std::time::Duration::from_secs(1); // to prevent time clipping
109
110    let not_before = x509_cert::der::asn1::GeneralizedTime::from_unix_duration(validity_start)
111        .map_err(|_| E2eIdentityError::CertificateGenerationError)?
112        .into();
113    let not_after =
114        x509_cert::der::asn1::GeneralizedTime::from_unix_duration(validity_start + args.validity_from_start)
115            .map_err(|_| E2eIdentityError::CertificateGenerationError)?
116            .into();
117    Ok(Validity::new(not_before, not_after))
118}
119
120fn add_crl_distribution_points<P: BuilderProfile>(
121    builder: &mut CertificateBuilder<P>,
122    args: &CertificateGenerationArgs,
123) -> E2eIdentityResult<()> {
124    if let Some(crl_dps) = args.crl_dps {
125        let mut crl_distribution_points = vec![];
126        for dp in crl_dps {
127            crl_distribution_points.push(x509_cert::ext::pkix::crl::dp::DistributionPoint {
128                distribution_point: Some(x509_cert::ext::pkix::name::DistributionPointName::FullName(vec![
129                    x509_cert::ext::pkix::name::GeneralName::UniformResourceIdentifier(
130                        dp.to_string()
131                            .try_into()
132                            .map_err(|_| E2eIdentityError::CertificateGenerationError)?,
133                    ),
134                ])),
135                crl_issuer: None,
136                reasons: None,
137            });
138        }
139        builder
140            .add_extension(&x509_cert::ext::pkix::CrlDistributionPoints(crl_distribution_points))
141            .map_err(|_| E2eIdentityError::CertificateGenerationError)?;
142    }
143    Ok(())
144}
145
146fn generate_cert_root<Signature, Signer>(
147    args: &CertificateGenerationArgs,
148    issuer_spki: spki::SubjectPublicKeyInfoOwned,
149    keypair: Signer,
150) -> E2eIdentityResult<x509_cert::Certificate>
151where
152    Signature: spki::SignatureBitStringEncoding,
153    Signer: signature::Signer<Signature> + spki::SignatureAlgorithmIdentifier + signature::KeypairRef,
154    Signer::VerifyingKey: spki::EncodePublicKey,
155{
156    let subject = subject(args)?;
157    let validity = validity(args)?;
158    let serial_number = x509_cert::serial_number::SerialNumber::from(args.serial);
159
160    let profile = x509_cert::builder::profile::cabf::Root::new(false, subject).expect("create root profile");
161    let mut builder = CertificateBuilder::new(profile, serial_number, validity, issuer_spki)
162        .map_err(|_| E2eIdentityError::CertificateGenerationError)?;
163
164    builder
165        .add_extension(&args.signer.unwrap().akid()?)
166        .map_err(|_| E2eIdentityError::CertificateGenerationError)?;
167    builder
168        .add_extension(&get_extended_keyusage(true))
169        .map_err(|_| E2eIdentityError::CertificateGenerationError)?;
170
171    let mut permitted_subtrees = vec![
172        x509_cert::ext::pkix::name::GeneralName::UniformResourceIdentifier(
173            args.org
174                .to_string()
175                .try_into()
176                .map_err(|_| E2eIdentityError::CertificateGenerationError)?,
177        ),
178        x509_cert::ext::pkix::name::GeneralName::UniformResourceIdentifier(
179            args.org
180                .to_string()
181                .try_into()
182                .map_err(|_| E2eIdentityError::CertificateGenerationError)?,
183        ),
184    ];
185
186    if let Some(domain) = args.domain {
187        // Add Domain DNS SAN
188        builder
189            .add_extension(&x509_cert::ext::pkix::SubjectAltName(vec![
190                x509_cert::ext::pkix::name::GeneralName::DnsName(
191                    domain
192                        .to_string()
193                        .try_into()
194                        .map_err(|_| E2eIdentityError::CertificateGenerationError)?,
195                ),
196            ]))
197            .map_err(|_| E2eIdentityError::CertificateGenerationError)?;
198
199        permitted_subtrees.push(x509_cert::ext::pkix::name::GeneralName::DnsName(
200            domain
201                .to_string()
202                .try_into()
203                .map_err(|_| E2eIdentityError::CertificateGenerationError)?,
204        ));
205    }
206
207    add_crl_distribution_points(&mut builder, args)?;
208
209    builder
210        .build::<_, Signature>(&keypair)
211        .map_err(|_| E2eIdentityError::CertificateGenerationError)
212}
213
214fn generate_cert_intermediate<Signature, Signer>(
215    args: &CertificateGenerationArgs,
216    issuer: Name,
217    issuer_spki: spki::SubjectPublicKeyInfoOwned,
218    keypair: Signer,
219) -> E2eIdentityResult<x509_cert::Certificate>
220where
221    Signature: spki::SignatureBitStringEncoding,
222    Signer: signature::Signer<Signature> + spki::SignatureAlgorithmIdentifier + signature::KeypairRef,
223    Signer::VerifyingKey: spki::EncodePublicKey,
224{
225    let subject = subject(args)?;
226    let validity = validity(args)?;
227    let serial_number = x509_cert::serial_number::SerialNumber::from(args.serial);
228
229    let profile = x509_cert::builder::profile::cabf::tls::Subordinate {
230        issuer,
231        subject,
232        path_len_constraint: Some(1),
233        emits_ocsp_response: false,
234        client_auth: false,
235    };
236    let mut builder = CertificateBuilder::new(profile, serial_number, validity, issuer_spki)
237        .map_err(|_| E2eIdentityError::CertificateGenerationError)?;
238
239    builder
240        .add_extension(&get_extended_keyusage(true))
241        .map_err(|_| E2eIdentityError::CertificateGenerationError)?;
242
243    let mut permitted_subtrees = vec![
244        x509_cert::ext::pkix::name::GeneralName::UniformResourceIdentifier(
245            args.org
246                .to_string()
247                .try_into()
248                .map_err(|_| E2eIdentityError::CertificateGenerationError)?,
249        ),
250        x509_cert::ext::pkix::name::GeneralName::UniformResourceIdentifier(
251            args.org
252                .to_string()
253                .try_into()
254                .map_err(|_| E2eIdentityError::CertificateGenerationError)?,
255        ),
256    ];
257
258    if let Some(domain) = args.domain {
259        // Add Domain DNS SAN
260        builder
261            .add_extension(&x509_cert::ext::pkix::SubjectAltName(vec![
262                x509_cert::ext::pkix::name::GeneralName::DnsName(
263                    domain
264                        .to_string()
265                        .try_into()
266                        .map_err(|_| E2eIdentityError::CertificateGenerationError)?,
267                ),
268            ]))
269            .map_err(|_| E2eIdentityError::CertificateGenerationError)?;
270
271        permitted_subtrees.push(x509_cert::ext::pkix::name::GeneralName::DnsName(
272            domain
273                .to_string()
274                .try_into()
275                .map_err(|_| E2eIdentityError::CertificateGenerationError)?,
276        ));
277    }
278
279    builder
280        .add_extension(&x509_cert::ext::pkix::NameConstraints {
281            permitted_subtrees: Some(
282                permitted_subtrees
283                    .into_iter()
284                    .map(|base| x509_cert::ext::pkix::constraints::name::GeneralSubtree {
285                        base,
286                        minimum: 0,
287                        maximum: None,
288                    })
289                    .collect(),
290            ),
291
292            excluded_subtrees: None,
293        })
294        .map_err(|_| E2eIdentityError::CertificateGenerationError)?;
295
296    add_crl_distribution_points(&mut builder, args)?;
297
298    builder
299        .build::<_, Signature>(&keypair)
300        .map_err(|_| E2eIdentityError::CertificateGenerationError)
301}
302
303struct EndEntity {
304    pub issuer: Name,
305    pub subject: Name,
306}
307
308use x509_cert::ext::{
309    Extension, ToExtension,
310    pkix::{AuthorityKeyIdentifier, KeyUsage, KeyUsages},
311};
312
313impl BuilderProfile for EndEntity {
314    fn get_issuer(&self, _subject: &Name) -> Name {
315        self.issuer.clone()
316    }
317
318    fn get_subject(&self) -> Name {
319        self.subject.clone()
320    }
321
322    fn build_extensions(
323        &self,
324        _spk: spki::SubjectPublicKeyInfoRef<'_>,
325        issuer_spk: spki::SubjectPublicKeyInfoRef<'_>,
326        tbs: &x509_cert::TbsCertificate,
327    ) -> x509_cert::builder::Result<Vec<Extension>> {
328        let mut extensions: Vec<Extension> = vec![];
329
330        let akid = AuthorityKeyIdentifier::try_from(issuer_spk.clone())?;
331        extensions.push(akid.to_extension(tbs.subject(), &extensions)?);
332
333        let key_usage = KeyUsage(KeyUsages::DigitalSignature.into());
334        extensions.push(key_usage.to_extension(tbs.subject(), &extensions)?);
335
336        Ok(extensions)
337    }
338}
339
340fn generate_cert_end_entity<Signature, Signer>(
341    args: &CertificateGenerationArgs,
342    issuer: Name,
343    issuer_spki: spki::SubjectPublicKeyInfoOwned,
344    keypair: Signer,
345) -> E2eIdentityResult<x509_cert::Certificate>
346where
347    Signature: spki::SignatureBitStringEncoding,
348    Signer: signature::Signer<Signature> + spki::SignatureAlgorithmIdentifier + signature::KeypairRef,
349    Signer::VerifyingKey: spki::EncodePublicKey,
350{
351    let subject = subject(args)?;
352    let validity = validity(args)?;
353    let serial_number = x509_cert::serial_number::SerialNumber::from(args.serial);
354
355    let profile = EndEntity { subject, issuer };
356    let mut builder = CertificateBuilder::new(profile, serial_number, validity, issuer_spki)
357        .map_err(|_| E2eIdentityError::CertificateGenerationError)?;
358
359    builder
360        .add_extension(&get_extended_keyusage(false))
361        .map_err(|_| E2eIdentityError::CertificateGenerationError)?;
362
363    if let Some(alt_names) = args.alternative_names {
364        let mut alt_names_list = vec![];
365        for alt_name in alt_names {
366            alt_names_list.push(x509_cert::ext::pkix::name::GeneralName::UniformResourceIdentifier(
367                alt_name
368                    .to_string()
369                    .try_into()
370                    .map_err(|_| E2eIdentityError::CertificateGenerationError)?,
371            ));
372        }
373
374        builder
375            .add_extension(&x509_cert::ext::pkix::SubjectAltName(alt_names_list))
376            .map_err(|_| E2eIdentityError::CertificateGenerationError)?;
377    }
378
379    add_crl_distribution_points(&mut builder, args)?;
380
381    builder
382        .build::<_, Signature>(&keypair)
383        .map_err(|_| E2eIdentityError::CertificateGenerationError)
384}
385
386impl PkiKeypair {
387    pub fn new(signature_scheme: SignatureScheme, sk: Vec<u8>) -> E2eIdentityResult<Self> {
388        match signature_scheme {
389            SignatureScheme::ECDSA_SECP256R1_SHA256 => Ok(PkiKeypair::P256(
390                p256::ecdsa::SigningKey::from_slice(sk.as_slice())
391                    .map_err(|_| E2eIdentityError::CertificateGenerationError)?,
392            )),
393            SignatureScheme::ECDSA_SECP384R1_SHA384 => Ok(PkiKeypair::P384(
394                p384::ecdsa::SigningKey::from_slice(sk.as_slice())
395                    .map_err(|_| E2eIdentityError::CertificateGenerationError)?,
396            )),
397            SignatureScheme::ECDSA_SECP521R1_SHA512 => Ok(PkiKeypair::P521(
398                p521::ecdsa::SigningKey::from_slice(sk.as_slice())
399                    .map_err(|_| E2eIdentityError::CertificateGenerationError)?,
400            )),
401            SignatureScheme::ED25519 => Ok(PkiKeypair::Ed25519(ed25519_dalek::SigningKey::from_bytes(
402                sk.as_slice()
403                    .try_into()
404                    .expect("private key must be exactly {ed25519_dalek::SECRET_KEY_LENGTH} bytes"),
405            ))),
406            _ => Err(E2eIdentityError::UnsupportedSignatureScheme),
407        }
408    }
409
410    pub fn signature_algorithm(&self) -> spki::AlgorithmIdentifierRef<'_> {
411        match self {
412            Self::P256(_) => p256::ecdsa::SigningKey::SIGNATURE_ALGORITHM_IDENTIFIER,
413            Self::P384(_) => p384::ecdsa::SigningKey::SIGNATURE_ALGORITHM_IDENTIFIER,
414            Self::P521(_) => spki::AlgorithmIdentifierRef {
415                oid: ecdsa::ECDSA_SHA512_OID,
416                parameters: None,
417            },
418            Self::Ed25519(_) => ed25519_dalek::pkcs8::ALGORITHM_ID,
419        }
420    }
421
422    pub fn spki(&self) -> E2eIdentityResult<spki::SubjectPublicKeyInfoOwned> {
423        match self {
424            Self::P256(sk) => Ok(spki::SubjectPublicKeyInfoOwned::from_key(sk.verifying_key())
425                .map_err(|_| E2eIdentityError::CertificateGenerationError)?),
426            Self::P384(sk) => Ok(spki::SubjectPublicKeyInfoOwned::from_key(sk.verifying_key())
427                .map_err(|_| E2eIdentityError::CertificateGenerationError)?),
428            Self::P521(sk) => Ok(spki::SubjectPublicKeyInfoOwned::from_key(sk.verifying_key())
429                .map_err(|_| E2eIdentityError::CertificateGenerationError)?),
430            Self::Ed25519(sk) => Ok(spki::SubjectPublicKeyInfoOwned::from_key(&sk.verifying_key())
431                .map_err(|_| E2eIdentityError::CertificateGenerationError)?),
432        }
433    }
434
435    pub fn akid(&self) -> E2eIdentityResult<x509_cert::ext::pkix::AuthorityKeyIdentifier> {
436        Ok(x509_cert::ext::pkix::AuthorityKeyIdentifier {
437            key_identifier: Some(
438                spki::der::asn1::OctetString::new(self.public_key_identifier())
439                    .map_err(|_| E2eIdentityError::CertificateGenerationError)?,
440            ),
441            authority_cert_issuer: None,
442            authority_cert_serial_number: None,
443        })
444    }
445
446    pub fn revoke_certs(
447        &self,
448        issuer_cert: &x509_cert::Certificate,
449        revoked_cert_serial_numbers: Vec<Vec<u8>>,
450    ) -> E2eIdentityResult<x509_cert::crl::CertificateList> {
451        let signature_algorithm = self.signature_algorithm();
452        let now = web_time::SystemTime::now()
453            .duration_since(web_time::UNIX_EPOCH)
454            .map_err(|_| E2eIdentityError::CertificateGenerationError)?;
455        let now = x509_cert::der::asn1::GeneralizedTime::from_unix_duration(now)
456            .map_err(|_| E2eIdentityError::CertificateGenerationError)?;
457        let now = x509_cert::time::Time::GeneralTime(now);
458
459        let revoked_certificates = revoked_cert_serial_numbers
460            .into_iter()
461            .map(|serial_number| x509_cert::crl::RevokedCert {
462                serial_number: x509_cert::serial_number::SerialNumber::new(&serial_number)
463                    .expect("Non-positive serial number"),
464                revocation_date: now,
465                crl_entry_extensions: None,
466            })
467            .collect();
468
469        let tbs_cert_list = x509_cert::crl::TbsCertList {
470            version: x509_cert::Version::V3,
471            signature: signature_algorithm.ref_to_owned(),
472            issuer: issuer_cert.tbs_certificate().subject().clone(),
473            this_update: now,
474            next_update: None,
475            revoked_certificates: Some(revoked_certificates),
476            crl_extensions: None,
477        };
478
479        use spki::der::Encode as _;
480
481        let tbs = tbs_cert_list
482            .to_der()
483            .map_err(|_| E2eIdentityError::CertificateGenerationError)?;
484
485        use signature::Signer as _;
486        let signature: Vec<u8> = match self {
487            PkiKeypair::P256(sk) => {
488                let signature: p256::ecdsa::Signature = sk.sign(&tbs);
489                signature.to_der().to_vec()
490            }
491            PkiKeypair::P384(sk) => {
492                let signature: p384::ecdsa::Signature = sk.sign(&tbs);
493                signature.to_der().to_vec()
494            }
495            PkiKeypair::P521(sk) => {
496                let signature: p521::ecdsa::Signature = sk.sign(&tbs);
497                signature.to_der().to_vec()
498            }
499            PkiKeypair::Ed25519(sk) => {
500                let signature = sk.sign(&tbs);
501                signature.to_vec()
502            }
503        };
504
505        let signature =
506            spki::der::asn1::BitString::new(0, signature).map_err(|_| E2eIdentityError::CertificateGenerationError)?;
507
508        Ok(x509_cert::crl::CertificateList {
509            tbs_cert_list,
510            signature_algorithm: signature_algorithm.ref_to_owned(),
511            signature,
512        })
513    }
514
515    pub fn generate_cert<'a>(
516        &'a self,
517        mut args: CertificateGenerationArgs<'a>,
518    ) -> E2eIdentityResult<x509_cert::Certificate> {
519        if args.signer.is_none() {
520            args.signer = Some(self)
521        }
522        let signer = args.signer.unwrap();
523
524        let spki = self.spki()?;
525        let issuer = if let Some(ref issuer) = args.issuer {
526            Name::from_str(issuer.as_ref()).unwrap()
527        } else {
528            Name::default()
529        };
530
531        let cert = match (args.is_root, args.is_ca) {
532            (true, false) => unreachable!("cannot be a root CA without being a CA"),
533            (true, true) => match signer {
534                PkiKeypair::Ed25519(kp) => generate_cert_root(&args, spki, kp.clone())?,
535                PkiKeypair::P256(kp) => {
536                    generate_cert_root::<p256::ecdsa::DerSignature, p256::ecdsa::SigningKey>(&args, spki, kp.clone())?
537                }
538                PkiKeypair::P384(kp) => {
539                    generate_cert_root::<p384::ecdsa::DerSignature, p384::ecdsa::SigningKey>(&args, spki, kp.clone())?
540                }
541                PkiKeypair::P521(kp) => {
542                    generate_cert_root::<p521::ecdsa::DerSignature, p521::ecdsa::SigningKey>(&args, spki, kp.clone())?
543                }
544            },
545            (false, true) => {
546                match signer {
547                    PkiKeypair::Ed25519(kp) => generate_cert_intermediate(&args, issuer, spki, kp.clone())?,
548                    PkiKeypair::P256(kp) => generate_cert_intermediate::<
549                        p256::ecdsa::DerSignature,
550                        p256::ecdsa::SigningKey,
551                    >(&args, issuer, spki, kp.clone())?,
552                    PkiKeypair::P384(kp) => generate_cert_intermediate::<
553                        p384::ecdsa::DerSignature,
554                        p384::ecdsa::SigningKey,
555                    >(&args, issuer, spki, kp.clone())?,
556                    PkiKeypair::P521(kp) => generate_cert_intermediate::<
557                        p521::ecdsa::DerSignature,
558                        p521::ecdsa::SigningKey,
559                    >(&args, issuer, spki, kp.clone())?,
560                }
561            }
562            (false, false) => {
563                match signer {
564                    PkiKeypair::Ed25519(kp) => generate_cert_end_entity(&args, issuer, spki, kp.clone())?,
565                    PkiKeypair::P256(kp) => generate_cert_end_entity::<
566                        p256::ecdsa::DerSignature,
567                        p256::ecdsa::SigningKey,
568                    >(&args, issuer, spki, kp.clone())?,
569                    PkiKeypair::P384(kp) => generate_cert_end_entity::<
570                        p384::ecdsa::DerSignature,
571                        p384::ecdsa::SigningKey,
572                    >(&args, issuer, spki, kp.clone())?,
573                    PkiKeypair::P521(kp) => generate_cert_end_entity::<
574                        p521::ecdsa::DerSignature,
575                        p521::ecdsa::SigningKey,
576                    >(&args, issuer, spki, kp.clone())?,
577                }
578            }
579        };
580
581        Ok(cert)
582    }
583
584    pub fn rand(alg: SignatureScheme, crypto: &impl OpenMlsCrypto) -> super::E2eIdentityResult<Self> {
585        Self::new(
586            alg,
587            crypto
588                .signature_key_gen(alg)
589                .map_err(|_| super::E2eIdentityError::SignatureKeyGenerationFailed)?
590                .0,
591        )
592    }
593}