Skip to main content

isideload_apple_codesign/
cryptography.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! Common cryptography primitives.
6
7use {
8    crate::{
9        AppleCodesignError,
10        remote_signing::{RemoteSignError, session_negotiation::PublicKeyPeerDecrypt},
11    },
12    apple_xar::table_of_contents::ChecksumType as XarChecksumType,
13    aws_lc_rs::signature::{Ed25519KeyPair, KeyPair},
14    bytes::Bytes,
15    clap::ValueEnum,
16    der::{Decode, Document, Encode, SecretDocument, asn1},
17    digest::DynDigest,
18    elliptic_curve::{
19        AffinePoint, Curve, CurveArithmetic, FieldBytesSize, SecretKey as ECSecretKey,
20        sec1::{FromEncodedPoint, ModulusSize, ToEncodedPoint},
21    },
22    oid_registry::{
23        OID_EC_P256, OID_KEY_TYPE_EC_PUBLIC_KEY, OID_PKCS1_RSAENCRYPTION, OID_SIG_ED25519,
24    },
25    p256::NistP256,
26    pkcs1::RsaPrivateKey,
27    pkcs8::{EncodePrivateKey, ObjectIdentifier, PrivateKeyInfo},
28    rsa::{BigUint, Oaep, RsaPrivateKey as RsaConstructedKey, pkcs1::DecodeRsaPrivateKey},
29    signature::Signer,
30    spki::AlgorithmIdentifier,
31    std::{
32        borrow::Cow,
33        cmp::Ordering,
34        fmt::{Display, Formatter},
35        path::Path,
36    },
37    subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption},
38    x509_certificate::{
39        CapturedX509Certificate, DigestAlgorithm, EcdsaCurve, InMemorySigningKeyPair, KeyAlgorithm,
40        KeyInfoSigner, Sign, Signature, SignatureAlgorithm, X509CertificateError,
41    },
42    zeroize::Zeroizing,
43};
44
45/// A supertrait generically describing a private key capable of signing and possibly decryption.
46pub trait PrivateKey: KeyInfoSigner {
47    fn as_key_info_signer(&self) -> &dyn KeyInfoSigner;
48
49    fn to_public_key_peer_decrypt(
50        &self,
51    ) -> Result<Box<dyn PublicKeyPeerDecrypt>, AppleCodesignError>;
52
53    /// Signals the end of operations on the private key.
54    ///
55    /// Implementations can use this to do things like destroy private key matter, disconnect
56    /// from a hardware device, etc.
57    fn finish(&self) -> Result<(), AppleCodesignError>;
58}
59
60#[derive(Clone, Debug)]
61pub struct InMemoryRsaKey {
62    // Validated at construction time to be DER for an RsaPrivateKey.
63    private_key: SecretDocument,
64}
65
66impl InMemoryRsaKey {
67    /// Construct a new instance from DER data, validating DER in process.
68    fn from_der(der_data: &[u8]) -> Result<Self, der::Error> {
69        RsaPrivateKey::from_der(der_data)?;
70
71        let private_key = Document::from_der(der_data)?.into_secret();
72
73        Ok(Self { private_key })
74    }
75
76    fn rsa_private_key(&self) -> RsaPrivateKey<'_> {
77        RsaPrivateKey::from_der(self.private_key.as_bytes())
78            .expect("internal content should be PKCS#1 DER private key data")
79    }
80}
81
82impl From<&InMemoryRsaKey> for RsaConstructedKey {
83    fn from(key: &InMemoryRsaKey) -> Self {
84        let key = key.rsa_private_key();
85
86        let n = BigUint::from_bytes_be(key.modulus.as_bytes());
87        let e = BigUint::from_bytes_be(key.public_exponent.as_bytes());
88        let d = BigUint::from_bytes_be(key.private_exponent.as_bytes());
89        let prime1 = BigUint::from_bytes_be(key.prime1.as_bytes());
90        let prime2 = BigUint::from_bytes_be(key.prime2.as_bytes());
91        let primes = vec![prime1, prime2];
92
93        Self::from_components(n, e, d, primes).expect("inputs valid")
94    }
95}
96
97impl TryFrom<InMemoryRsaKey> for InMemorySigningKeyPair {
98    type Error = AppleCodesignError;
99
100    fn try_from(value: InMemoryRsaKey) -> Result<Self, Self::Error> {
101        Ok(Self::from_pkcs8_der(
102            value
103                .to_pkcs8_der()
104                .map_err(|e| {
105                    AppleCodesignError::CertificateGeneric(format!(
106                        "error converting RSA key to DER: {}",
107                        e
108                    ))
109                })?
110                .as_bytes(),
111        )?)
112    }
113}
114
115impl EncodePrivateKey for InMemoryRsaKey {
116    fn to_pkcs8_der(&self) -> pkcs8::Result<SecretDocument> {
117        // We don't need to store the public key because it can always be
118        // derived from the private key for RSA keys.
119        let raw = PrivateKeyInfo::new(pkcs1::ALGORITHM_ID, self.private_key.as_bytes()).to_der()?;
120
121        Ok(Document::from_der(&raw)?.into_secret())
122    }
123}
124
125impl PublicKeyPeerDecrypt for InMemoryRsaKey {
126    fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>, RemoteSignError> {
127        let key = RsaConstructedKey::from_pkcs1_der(self.private_key.as_bytes())
128            .map_err(|e| RemoteSignError::Crypto(format!("failed to parse RSA key: {e}")))?;
129
130        let padding = Oaep::new::<sha2::Sha256>();
131
132        let plaintext = key
133            .decrypt(padding, ciphertext)
134            .map_err(|e| RemoteSignError::Crypto(format!("RSA decryption failure: {e}")))?;
135
136        Ok(plaintext)
137    }
138}
139
140#[derive(Clone, Debug)]
141pub struct InMemoryEcdsaKey<C>
142where
143    C: Curve + CurveArithmetic,
144    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
145    FieldBytesSize<C>: ModulusSize,
146{
147    curve: ObjectIdentifier,
148    secret_key: ECSecretKey<C>,
149}
150
151impl<C> InMemoryEcdsaKey<C>
152where
153    C: Curve + CurveArithmetic,
154    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
155    FieldBytesSize<C>: ModulusSize,
156{
157    pub fn curve(&self) -> Result<EcdsaCurve, AppleCodesignError> {
158        match self.curve.as_bytes() {
159            x if x == OID_EC_P256.as_bytes() => Ok(EcdsaCurve::Secp256r1),
160            _ => Err(AppleCodesignError::CertificateGeneric(format!(
161                "unknown ECDSA curve: {}",
162                self.curve
163            ))),
164        }
165    }
166}
167
168impl<C> TryFrom<InMemoryEcdsaKey<C>> for InMemorySigningKeyPair
169where
170    C: Curve + CurveArithmetic,
171    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
172    FieldBytesSize<C>: ModulusSize,
173{
174    type Error = AppleCodesignError;
175
176    fn try_from(key: InMemoryEcdsaKey<C>) -> Result<Self, Self::Error> {
177        Ok(Self::from_pkcs8_der(
178            key.to_pkcs8_der()
179                .map_err(|e| {
180                    AppleCodesignError::CertificateGeneric(format!(
181                        "error converting ECDSA key to DER: {}",
182                        e
183                    ))
184                })?
185                .as_bytes(),
186        )?)
187    }
188}
189
190impl<C> EncodePrivateKey for InMemoryEcdsaKey<C>
191where
192    C: Curve + CurveArithmetic,
193    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
194    FieldBytesSize<C>: ModulusSize,
195{
196    fn to_pkcs8_der(&self) -> pkcs8::Result<SecretDocument> {
197        let private_key = self.secret_key.to_sec1_der()?;
198
199        PrivateKeyInfo {
200            algorithm: AlgorithmIdentifier {
201                oid: ObjectIdentifier::from_bytes(OID_KEY_TYPE_EC_PUBLIC_KEY.as_bytes())
202                    .expect("OID construction should work"),
203                parameters: Some(asn1::AnyRef::from(&self.curve)),
204            },
205            private_key: private_key.as_ref(),
206            public_key: None,
207        }
208        .try_into()
209    }
210}
211
212impl<C> PublicKeyPeerDecrypt for InMemoryEcdsaKey<C>
213where
214    C: Curve + CurveArithmetic,
215    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
216    FieldBytesSize<C>: ModulusSize,
217{
218    fn decrypt(&self, _ciphertext: &[u8]) -> Result<Vec<u8>, RemoteSignError> {
219        Err(RemoteSignError::Crypto(
220            "decryption using ECDSA keys is not yet implemented".into(),
221        ))
222    }
223}
224
225#[derive(Clone, Debug)]
226pub struct InMemoryEd25519Key {
227    private_key: Zeroizing<Vec<u8>>,
228}
229
230impl TryFrom<InMemoryEd25519Key> for InMemorySigningKeyPair {
231    type Error = AppleCodesignError;
232
233    fn try_from(key: InMemoryEd25519Key) -> Result<Self, Self::Error> {
234        Ok(Self::from_pkcs8_der(
235            key.to_pkcs8_der()
236                .map_err(|e| {
237                    AppleCodesignError::CertificateGeneric(format!(
238                        "error converting ED25519 key to DER: {}",
239                        e
240                    ))
241                })?
242                .as_bytes(),
243        )?)
244    }
245}
246
247impl EncodePrivateKey for InMemoryEd25519Key {
248    fn to_pkcs8_der(&self) -> pkcs8::Result<SecretDocument> {
249        let algorithm = AlgorithmIdentifier {
250            oid: ObjectIdentifier::from_bytes(OID_SIG_ED25519.as_bytes()).expect("OID is valid"),
251            parameters: None,
252        };
253
254        let key_ref: &[u8] = self.private_key.as_ref();
255        let value = Zeroizing::new(asn1::OctetString::new(key_ref)?.to_der()?);
256
257        let mut pki = PrivateKeyInfo::new(algorithm, value.as_ref());
258
259        let public_key =
260            if let Ok(key) = Ed25519KeyPair::from_seed_unchecked(self.private_key.as_ref()) {
261                Bytes::copy_from_slice(key.public_key().as_ref())
262            } else {
263                Bytes::new()
264            };
265
266        pki.public_key = Some(public_key.as_ref());
267
268        pki.try_into()
269    }
270}
271
272impl PublicKeyPeerDecrypt for InMemoryEd25519Key {
273    fn decrypt(&self, _ciphertext: &[u8]) -> Result<Vec<u8>, RemoteSignError> {
274        Err(RemoteSignError::Crypto(
275            "decryption using ED25519 keys is not yet implemented".into(),
276        ))
277    }
278}
279
280/// Holds a private key in memory.
281#[derive(Clone, Debug)]
282pub enum InMemoryPrivateKey {
283    /// ECDSA private key using Nist P256 curve.
284    EcdsaP256(InMemoryEcdsaKey<NistP256>),
285    /// ED25519 private key.
286    Ed25519(InMemoryEd25519Key),
287    /// RSA private key.
288    Rsa(InMemoryRsaKey),
289}
290
291impl<'a> TryFrom<PrivateKeyInfo<'a>> for InMemoryPrivateKey {
292    type Error = pkcs8::Error;
293
294    fn try_from(value: PrivateKeyInfo<'a>) -> Result<Self, Self::Error> {
295        match value.algorithm.oid {
296            x if x.as_bytes() == OID_PKCS1_RSAENCRYPTION.as_bytes() => {
297                Ok(Self::Rsa(InMemoryRsaKey::from_der(value.private_key)?))
298            }
299            x if x.as_bytes() == OID_KEY_TYPE_EC_PUBLIC_KEY.as_bytes() => {
300                let curve_oid = value.algorithm.parameters_oid()?;
301
302                match curve_oid.as_bytes() {
303                    x if x == OID_EC_P256.as_bytes() => {
304                        let secret_key = ECSecretKey::<NistP256>::try_from(value)?;
305
306                        Ok(Self::EcdsaP256(InMemoryEcdsaKey {
307                            curve: curve_oid,
308                            secret_key,
309                        }))
310                    }
311                    _ => Err(pkcs8::Error::ParametersMalformed),
312                }
313            }
314            x if x.as_bytes() == OID_SIG_ED25519.as_bytes() => {
315                // The private key seed should start at byte offset 2.
316                Ok(Self::Ed25519(InMemoryEd25519Key {
317                    private_key: Zeroizing::new((value.private_key[2..]).to_vec()),
318                }))
319            }
320            _ => Err(pkcs8::Error::KeyMalformed),
321        }
322    }
323}
324
325impl TryFrom<InMemoryPrivateKey> for InMemorySigningKeyPair {
326    type Error = AppleCodesignError;
327
328    fn try_from(key: InMemoryPrivateKey) -> Result<Self, Self::Error> {
329        match key {
330            InMemoryPrivateKey::Rsa(key) => key.try_into(),
331            InMemoryPrivateKey::EcdsaP256(key) => key.try_into(),
332            InMemoryPrivateKey::Ed25519(key) => key.try_into(),
333        }
334    }
335}
336
337impl EncodePrivateKey for InMemoryPrivateKey {
338    fn to_pkcs8_der(&self) -> pkcs8::Result<SecretDocument> {
339        match self {
340            Self::EcdsaP256(key) => key.to_pkcs8_der(),
341            Self::Ed25519(key) => key.to_pkcs8_der(),
342            Self::Rsa(key) => key.to_pkcs8_der(),
343        }
344    }
345}
346
347impl Signer<Signature> for InMemoryPrivateKey {
348    fn try_sign(&self, msg: &[u8]) -> Result<Signature, signature::Error> {
349        let key_pair = InMemorySigningKeyPair::try_from(self.clone())
350            .map_err(signature::Error::from_source)?;
351
352        key_pair.try_sign(msg)
353    }
354}
355
356impl Sign for InMemoryPrivateKey {
357    fn sign(&self, message: &[u8]) -> Result<(Vec<u8>, SignatureAlgorithm), X509CertificateError> {
358        let algorithm = self.signature_algorithm()?;
359
360        Ok((self.try_sign(message)?.into(), algorithm))
361    }
362
363    fn key_algorithm(&self) -> Option<KeyAlgorithm> {
364        Some(match self {
365            Self::EcdsaP256(_) => KeyAlgorithm::Ecdsa(EcdsaCurve::Secp256r1),
366            Self::Ed25519(_) => KeyAlgorithm::Ed25519,
367            Self::Rsa(_) => KeyAlgorithm::Rsa,
368        })
369    }
370
371    fn public_key_data(&self) -> Bytes {
372        match self {
373            Self::EcdsaP256(key) => Bytes::copy_from_slice(
374                key.secret_key
375                    .public_key()
376                    .to_encoded_point(false)
377                    .as_bytes(),
378            ),
379            Self::Ed25519(key) => {
380                if let Ok(key) = Ed25519KeyPair::from_seed_unchecked(key.private_key.as_ref()) {
381                    Bytes::copy_from_slice(key.public_key().as_ref())
382                } else {
383                    Bytes::new()
384                }
385            }
386            Self::Rsa(key) => {
387                let key = key.rsa_private_key();
388
389                Bytes::copy_from_slice(
390                    key.public_key()
391                        .to_der()
392                        .expect("RSA public key DER encoding should not fail")
393                        .as_ref(),
394                )
395            }
396        }
397    }
398
399    fn signature_algorithm(&self) -> Result<SignatureAlgorithm, X509CertificateError> {
400        Ok(match self {
401            Self::EcdsaP256(_) => SignatureAlgorithm::EcdsaSha256,
402            Self::Ed25519(_) => SignatureAlgorithm::Ed25519,
403            Self::Rsa(_) => SignatureAlgorithm::RsaSha256,
404        })
405    }
406
407    fn private_key_data(&self) -> Option<Zeroizing<Vec<u8>>> {
408        match self {
409            Self::EcdsaP256(key) => Some(Zeroizing::new(key.secret_key.to_bytes().to_vec())),
410            Self::Ed25519(key) => Some(Zeroizing::new((*key.private_key).clone())),
411            Self::Rsa(key) => Some(Zeroizing::new(key.private_key.as_bytes().to_vec())),
412        }
413    }
414
415    fn rsa_primes(
416        &self,
417    ) -> Result<Option<(Zeroizing<Vec<u8>>, Zeroizing<Vec<u8>>)>, X509CertificateError> {
418        if let Self::Rsa(key) = self {
419            let key = key.rsa_private_key();
420
421            Ok(Some((
422                Zeroizing::new(key.prime1.as_bytes().to_vec()),
423                Zeroizing::new(key.prime2.as_bytes().to_vec()),
424            )))
425        } else {
426            Ok(None)
427        }
428    }
429}
430
431impl KeyInfoSigner for InMemoryPrivateKey {}
432
433impl PublicKeyPeerDecrypt for InMemoryPrivateKey {
434    fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>, RemoteSignError> {
435        match self {
436            Self::Rsa(key) => key.decrypt(ciphertext),
437            Self::EcdsaP256(key) => key.decrypt(ciphertext),
438            Self::Ed25519(key) => key.decrypt(ciphertext),
439        }
440    }
441}
442
443impl PrivateKey for InMemoryPrivateKey {
444    fn as_key_info_signer(&self) -> &dyn KeyInfoSigner {
445        self
446    }
447
448    fn to_public_key_peer_decrypt(
449        &self,
450    ) -> Result<Box<dyn PublicKeyPeerDecrypt>, AppleCodesignError> {
451        Ok(Box::new(self.clone()))
452    }
453
454    fn finish(&self) -> Result<(), AppleCodesignError> {
455        Ok(())
456    }
457}
458
459impl InMemoryPrivateKey {
460    /// Construct an instance by parsing PKCS#1 DER data.
461    pub fn from_pkcs1_der(data: impl AsRef<[u8]>) -> Result<Self, AppleCodesignError> {
462        let key = InMemoryRsaKey::from_der(data.as_ref()).map_err(|e| {
463            AppleCodesignError::CertificateGeneric(format!("when parsing PKCS#1 data: {e}"))
464        })?;
465
466        Ok(Self::Rsa(key))
467    }
468
469    /// Construct an instance by parsing PKCS#8 DER data.
470    pub fn from_pkcs8_der(data: impl AsRef<[u8]>) -> Result<Self, AppleCodesignError> {
471        let pki = PrivateKeyInfo::try_from(data.as_ref()).map_err(|e| {
472            AppleCodesignError::CertificateGeneric(format!("when parsing PKCS#8 data: {e}"))
473        })?;
474
475        pki.try_into().map_err(|e| {
476            AppleCodesignError::CertificateGeneric(format!(
477                "when converting parsed PKCS#8 to a private key: {e}"
478            ))
479        })
480    }
481}
482
483/// Represents a digest type encountered in code signature data structures.
484#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum, Default)]
485pub enum DigestType {
486    None,
487    Sha1,
488    #[default]
489    Sha256,
490    Sha256Truncated,
491    Sha384,
492    Sha512,
493    #[value(skip)]
494    Unknown(u8),
495}
496
497impl TryFrom<DigestType> for DigestAlgorithm {
498    type Error = AppleCodesignError;
499
500    fn try_from(value: DigestType) -> Result<DigestAlgorithm, Self::Error> {
501        match value {
502            DigestType::Sha1 => Ok(DigestAlgorithm::Sha1),
503            DigestType::Sha256 => Ok(DigestAlgorithm::Sha256),
504            DigestType::Sha256Truncated => Ok(DigestAlgorithm::Sha256),
505            DigestType::Sha384 => Ok(DigestAlgorithm::Sha384),
506            DigestType::Sha512 => Ok(DigestAlgorithm::Sha512),
507            DigestType::Unknown(_) => Err(AppleCodesignError::DigestUnknownAlgorithm),
508            DigestType::None => Err(AppleCodesignError::DigestUnsupportedAlgorithm),
509        }
510    }
511}
512
513impl PartialOrd for DigestType {
514    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
515        Some(self.cmp(other))
516    }
517}
518
519impl Ord for DigestType {
520    fn cmp(&self, other: &Self) -> Ordering {
521        u8::from(*self).cmp(&u8::from(*other))
522    }
523}
524
525impl Display for DigestType {
526    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
527        match self {
528            DigestType::None => f.write_str("none"),
529            DigestType::Sha1 => f.write_str("sha1"),
530            DigestType::Sha256 => f.write_str("sha256"),
531            DigestType::Sha256Truncated => f.write_str("sha256-truncated"),
532            DigestType::Sha384 => f.write_str("sha384"),
533            DigestType::Sha512 => f.write_str("sha512"),
534            DigestType::Unknown(v) => f.write_fmt(format_args!("unknown: {v}")),
535        }
536    }
537}
538
539impl TryFrom<&str> for DigestType {
540    type Error = AppleCodesignError;
541
542    fn try_from(s: &str) -> Result<Self, Self::Error> {
543        match s {
544            "none" => Ok(Self::None),
545            "sha1" => Ok(Self::Sha1),
546            "sha256" => Ok(Self::Sha256),
547            "sha256-truncated" => Ok(Self::Sha256Truncated),
548            "sha384" => Ok(Self::Sha384),
549            "sha512" => Ok(Self::Sha512),
550            _ => Err(AppleCodesignError::DigestUnknownAlgorithm),
551        }
552    }
553}
554
555impl TryFrom<XarChecksumType> for DigestType {
556    type Error = AppleCodesignError;
557
558    fn try_from(c: XarChecksumType) -> Result<Self, Self::Error> {
559        match c {
560            XarChecksumType::None => Ok(Self::None),
561            XarChecksumType::Sha1 => Ok(Self::Sha1),
562            XarChecksumType::Sha256 => Ok(Self::Sha256),
563            XarChecksumType::Sha512 => Ok(Self::Sha512),
564            XarChecksumType::Md5 => Err(AppleCodesignError::DigestUnsupportedAlgorithm),
565        }
566    }
567}
568
569impl DigestType {
570    /// Obtain the size of hashes for this hash type.
571    pub fn hash_len(&self) -> Result<usize, AppleCodesignError> {
572        Ok(self.digest_data(&[])?.len())
573    }
574
575    /// Obtain a hasher for this digest type.
576    pub fn as_hasher(&self) -> Result<aws_lc_rs::digest::Context, AppleCodesignError> {
577        match self {
578            Self::None => Err(AppleCodesignError::DigestUnknownAlgorithm),
579            Self::Sha1 => Ok(aws_lc_rs::digest::Context::new(
580                &aws_lc_rs::digest::SHA1_FOR_LEGACY_USE_ONLY,
581            )),
582            Self::Sha256 | Self::Sha256Truncated => {
583                Ok(aws_lc_rs::digest::Context::new(&aws_lc_rs::digest::SHA256))
584            }
585            Self::Sha384 => Ok(aws_lc_rs::digest::Context::new(&aws_lc_rs::digest::SHA384)),
586            Self::Sha512 => Ok(aws_lc_rs::digest::Context::new(&aws_lc_rs::digest::SHA512)),
587            Self::Unknown(_) => Err(AppleCodesignError::DigestUnknownAlgorithm),
588        }
589    }
590
591    /// Digest data given the configured hasher.
592    pub fn digest_data(&self, data: &[u8]) -> Result<Vec<u8>, AppleCodesignError> {
593        let mut hasher = self.as_hasher()?;
594
595        hasher.update(data);
596        let mut hash = hasher.finish().as_ref().to_vec();
597
598        if matches!(self, Self::Sha256Truncated) {
599            hash.truncate(20);
600        }
601
602        Ok(hash)
603    }
604}
605
606pub struct Digest<'a> {
607    pub data: Cow<'a, [u8]>,
608}
609
610impl<'a> Digest<'a> {
611    /// Whether this is the null hash (all 0s).
612    pub fn is_null(&self) -> bool {
613        self.data.iter().all(|b| *b == 0)
614    }
615
616    pub fn to_vec(&self) -> Vec<u8> {
617        self.data.to_vec()
618    }
619
620    pub fn to_owned(&self) -> Digest<'static> {
621        Digest {
622            data: Cow::Owned(self.data.clone().into_owned()),
623        }
624    }
625
626    pub fn as_hex(&self) -> String {
627        hex::encode(&self.data)
628    }
629}
630
631impl<'a> std::fmt::Debug for Digest<'a> {
632    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
633        f.write_str(&hex::encode(&self.data))
634    }
635}
636
637impl<'a> From<Vec<u8>> for Digest<'a> {
638    fn from(v: Vec<u8>) -> Self {
639        Self { data: v.into() }
640    }
641}
642
643/// Holds multiple computed digests for content.
644pub struct MultiDigest {
645    pub sha1: Digest<'static>,
646    pub sha256: Digest<'static>,
647}
648
649impl MultiDigest {
650    /// Compute the multi digests for any stream reader.
651    ///
652    /// This will read the stream until EOF.
653    pub fn from_reader(mut reader: impl std::io::Read) -> Result<Self, AppleCodesignError> {
654        let mut sha1 = DigestType::Sha1.as_hasher()?;
655        let mut sha256 = DigestType::Sha256.as_hasher()?;
656
657        let mut buffer = [0u8; 16384];
658
659        loop {
660            let read = reader.read(&mut buffer)?;
661            if read == 0 {
662                break;
663            }
664
665            sha1.update(&buffer[0..read]);
666            sha256.update(&buffer[0..read]);
667        }
668
669        let sha1 = sha1.finish().as_ref().to_vec();
670        let sha256 = sha256.finish().as_ref().to_vec();
671
672        Ok(Self {
673            sha1: sha1.into(),
674            sha256: sha256.into(),
675        })
676    }
677
678    /// Compute the multi digest of a filesystem path.
679    pub fn from_path(path: impl AsRef<Path>) -> Result<Self, AppleCodesignError> {
680        let fh = std::fs::File::open(path.as_ref())?;
681        Self::from_reader(fh)
682    }
683}
684
685fn bmp_string(s: &str) -> Vec<u8> {
686    let utf16: Vec<u16> = s.encode_utf16().collect();
687
688    let mut bytes = Vec::with_capacity(utf16.len() * 2 + 2);
689    for c in utf16 {
690        bytes.push((c / 256) as u8);
691        bytes.push((c % 256) as u8);
692    }
693    bytes.push(0x00);
694    bytes.push(0x00);
695
696    bytes
697}
698
699/// Parse PFX data into a key pair.
700///
701/// PFX data is commonly encountered in `.p12` files, such as those created
702/// when exporting certificates from Apple's `Keychain Access` application.
703///
704/// The contents of the PFX file require a password to decrypt. However, if
705/// no password was provided to create the PFX data, this password may be the
706/// empty string.
707pub fn parse_pfx_data(
708    data: &[u8],
709    password: &str,
710) -> Result<(CapturedX509Certificate, InMemoryPrivateKey), AppleCodesignError> {
711    let pfx = p12::PFX::parse(data).map_err(|e| {
712        AppleCodesignError::PfxParseError(format!("data does not appear to be PFX: {e:?}"))
713    })?;
714
715    if !pfx.verify_mac(password) {
716        return Err(AppleCodesignError::PfxBadPassword);
717    }
718
719    // Apple's certificate export format consists of regular data content info
720    // with inner ContentInfo components holding the key and certificate.
721    let data = match pfx.auth_safe {
722        p12::ContentInfo::Data(data) => data,
723        _ => {
724            return Err(AppleCodesignError::PfxParseError(
725                "unexpected PFX content info".to_string(),
726            ));
727        }
728    };
729
730    let content_infos = yasna::parse_der(&data, |reader| {
731        reader.collect_sequence_of(p12::ContentInfo::parse)
732    })
733    .map_err(|e| {
734        AppleCodesignError::PfxParseError(format!("failed parsing inner ContentInfo: {e:?}"))
735    })?;
736
737    let bmp_password = bmp_string(password);
738
739    let mut certificate = None;
740    let mut signing_key = None;
741
742    for content in content_infos {
743        let bags_data = match content {
744            p12::ContentInfo::Data(inner) => inner,
745            p12::ContentInfo::EncryptedData(encrypted) => {
746                encrypted.data(&bmp_password).ok_or_else(|| {
747                    AppleCodesignError::PfxParseError(
748                        "failed decrypting inner EncryptedData".to_string(),
749                    )
750                })?
751            }
752            p12::ContentInfo::OtherContext(_) => {
753                return Err(AppleCodesignError::PfxParseError(
754                    "unexpected OtherContent content in inner PFX data".to_string(),
755                ));
756            }
757        };
758
759        let bags = yasna::parse_ber(&bags_data, |reader| {
760            reader.collect_sequence_of(p12::SafeBag::parse)
761        })
762        .map_err(|e| {
763            AppleCodesignError::PfxParseError(format!(
764                "failed parsing SafeBag within inner Data: {e:?}"
765            ))
766        })?;
767
768        for bag in bags {
769            match bag.bag {
770                p12::SafeBagKind::CertBag(cert_bag) => match cert_bag {
771                    p12::CertBag::X509(cert_data) => {
772                        certificate = Some(CapturedX509Certificate::from_der(cert_data)?);
773                    }
774                    p12::CertBag::SDSI(_) => {
775                        return Err(AppleCodesignError::PfxParseError(
776                            "unexpected SDSI certificate data".to_string(),
777                        ));
778                    }
779                },
780                p12::SafeBagKind::Pkcs8ShroudedKeyBag(key_bag) => {
781                    let decrypted = key_bag.decrypt(&bmp_password).ok_or_else(|| {
782                        AppleCodesignError::PfxParseError(
783                            "error decrypting PKCS8 shrouded key bag; is the password correct?"
784                                .to_string(),
785                        )
786                    })?;
787
788                    signing_key = Some(InMemoryPrivateKey::from_pkcs8_der(decrypted)?);
789                }
790                p12::SafeBagKind::OtherBagKind(_) => {
791                    return Err(AppleCodesignError::PfxParseError(
792                        "unexpected bag type in inner PFX content".to_string(),
793                    ));
794                }
795            }
796        }
797    }
798
799    match (certificate, signing_key) {
800        (Some(certificate), Some(signing_key)) => Ok((certificate, signing_key)),
801        (None, Some(_)) => Err(AppleCodesignError::PfxParseError(
802            "failed to find x509 certificate in PFX data".to_string(),
803        )),
804        (_, None) => Err(AppleCodesignError::PfxParseError(
805            "failed to find signing key in PFX data".to_string(),
806        )),
807    }
808}
809
810/// RSA OAEP post decrypt depadding.
811///
812/// This implements the procedure described by RFC 3447 Section 7.1.2
813/// starting at Step 3 (after the ciphertext has been fed into the low-level
814/// RSA decryption.
815///
816/// This implementation has NOT been audited and shouldn't be used. It only
817/// exists here because we need it to support RSA decryption using YubiKeys.
818/// https://github.com/RustCrypto/RSA/issues/159 is fixed to hopefully get this
819/// exposed as an API on the rsa crate.
820#[allow(unused)]
821pub(crate) fn rsa_oaep_post_decrypt_decode(
822    modulus_length_bytes: usize,
823    mut em: Vec<u8>,
824    digest: &mut dyn digest::DynDigest,
825    mgf_digest: &mut dyn digest::DynDigest,
826    label: Option<String>,
827) -> Result<Vec<u8>, rsa::errors::Error> {
828    let k = modulus_length_bytes;
829    let digest_len = digest.output_size();
830
831    // 3. EME_OAEP decoding.
832
833    // 3a.
834    let label = label.unwrap_or_default();
835    digest.update(label.as_bytes());
836    let label_digest = digest.finalize_reset();
837
838    // 3b.
839    let (y, remaining) = em.split_at_mut(1);
840    let (masked_seed, masked_db) = remaining.split_at_mut(digest_len);
841
842    if masked_seed.len() != digest_len || masked_db.len() != k - digest_len - 1 {
843        return Err(rsa::errors::Error::Decryption);
844    }
845
846    // 3c - 3f.
847    mgf1_xor(masked_seed, mgf_digest, masked_db);
848    mgf1_xor(masked_db, mgf_digest, masked_seed);
849
850    // 3g.
851    //
852    // We need to split into padding string (all zeroes) and message M with a
853    // 0x01 between them. The padding string should be all zeroes. And this should
854    // execute in constant time, which makes it tricky.
855
856    let digests_equivalent = masked_db[0..digest_len].ct_eq(label_digest.as_ref());
857
858    let mut looking_for_index = Choice::from(1u8);
859    let mut index = 0u32;
860    let mut padding_invalid = Choice::from(0u8);
861
862    for (i, value) in masked_db.iter().skip(digest_len).enumerate() {
863        let is_zero = value.ct_eq(&0u8);
864        let is_one = value.ct_eq(&1u8);
865
866        index.conditional_assign(&(i as u32), looking_for_index & is_one);
867        looking_for_index &= !is_one;
868        padding_invalid |= looking_for_index & !is_zero;
869    }
870
871    let y_is_zero = y[0].ct_eq(&0u8);
872
873    let valid = y_is_zero & digests_equivalent & !padding_invalid & !looking_for_index;
874
875    let res = CtOption::new((em, index + 2 + (digest_len * 2) as u32), valid);
876
877    if res.is_none().into() {
878        return Err(rsa::errors::Error::Decryption);
879    }
880
881    let (out, index) = res.unwrap();
882
883    Ok(out[index as usize..].to_vec())
884}
885
886fn inc_counter(counter: &mut [u8; 4]) {
887    for i in (0..4).rev() {
888        counter[i] = counter[i].wrapping_add(1);
889        if counter[i] != 0 {
890            // No overflow
891            return;
892        }
893    }
894}
895
896fn mgf1_xor(out: &mut [u8], digest: &mut dyn DynDigest, seed: &[u8]) {
897    let mut counter = [0u8; 4];
898    let mut i = 0;
899
900    const MAX_LEN: u64 = core::u32::MAX as u64 + 1;
901    assert!(out.len() as u64 <= MAX_LEN);
902
903    while i < out.len() {
904        let mut digest_input = vec![0u8; seed.len() + 4];
905        digest_input[0..seed.len()].copy_from_slice(seed);
906        digest_input[seed.len()..].copy_from_slice(&counter);
907
908        digest.update(digest_input.as_slice());
909        let digest_output = &*digest.finalize_reset();
910        let mut j = 0;
911        loop {
912            if j >= digest_output.len() || i >= out.len() {
913                break;
914            }
915
916            out[i] ^= digest_output[j];
917            j += 1;
918            i += 1;
919        }
920        inc_counter(&mut counter);
921    }
922}
923
924#[cfg(test)]
925mod test {
926    use {
927        super::*,
928        aws_lc_rs::signature::{EcdsaKeyPair, KeyPair, RsaKeyPair},
929        x509_certificate::Sign,
930    };
931
932    const RSA_2048_PKCS8_DER: &[u8] = include_bytes!("testdata/rsa-2048.pk8");
933    const ED25519_PKCS8_DER: &[u8] = include_bytes!("testdata/ed25519.pk8");
934    const SECP256_PKCS8_DER: &[u8] = include_bytes!("testdata/secp256r1.pk8");
935
936    #[test]
937    fn parse_keychain_p12_export() {
938        let data = include_bytes!("apple-codesign-testuser.p12");
939
940        let err = parse_pfx_data(data, "bad-password").unwrap_err();
941        assert!(matches!(err, AppleCodesignError::PfxBadPassword));
942
943        parse_pfx_data(data, "password123").unwrap();
944    }
945
946    #[test]
947    fn rsa_key_operations() -> Result<(), AppleCodesignError> {
948        let ring_key = RsaKeyPair::from_pkcs8(RSA_2048_PKCS8_DER).unwrap();
949        let ring_public_key_data = ring_key.public_key().as_ref();
950
951        let pki = PrivateKeyInfo::from_der(RSA_2048_PKCS8_DER).unwrap();
952        let key = InMemoryPrivateKey::try_from(pki).unwrap();
953
954        assert_eq!(key.to_pkcs8_der().unwrap().as_bytes(), RSA_2048_PKCS8_DER);
955
956        let our_key = InMemorySigningKeyPair::try_from(key)?;
957        let our_public_key = our_key.public_key_data();
958
959        assert_eq!(our_public_key.as_ref(), ring_public_key_data);
960
961        InMemoryPrivateKey::from_pkcs8_der(RSA_2048_PKCS8_DER)?;
962
963        let random_key = rsa::RsaPrivateKey::new(&mut rand::thread_rng(), 2048).unwrap();
964        let random_key_pkcs8 = random_key.to_pkcs8_der().unwrap();
965        InMemorySigningKeyPair::from_pkcs8_der(random_key_pkcs8.as_bytes())?;
966
967        Ok(())
968    }
969
970    #[test]
971    fn ed25519_key_operations() -> Result<(), AppleCodesignError> {
972        let pki = PrivateKeyInfo::from_der(ED25519_PKCS8_DER).unwrap();
973        let seed = &pki.private_key[2..];
974        let key = InMemoryPrivateKey::try_from(pki).unwrap();
975
976        assert!(
977            InMemorySigningKeyPair::from_pkcs8_der(ED25519_PKCS8_DER).is_err(),
978            "stored key doesn't have public key, which ring rejects loading"
979        );
980
981        // But out PKCS#8 export includes it so it can round trip.
982        InMemorySigningKeyPair::from_pkcs8_der(key.to_pkcs8_der().unwrap().as_bytes()).unwrap();
983
984        let our_key = InMemorySigningKeyPair::try_from(key)?;
985        let our_public_key = our_key.public_key_data();
986
987        let ring_key = Ed25519KeyPair::from_seed_unchecked(seed).unwrap();
988        let ring_public_key_data = ring_key.public_key().as_ref();
989
990        assert_eq!(our_public_key.as_ref(), ring_public_key_data);
991
992        InMemoryPrivateKey::from_pkcs8_der(ED25519_PKCS8_DER)?;
993
994        Ok(())
995    }
996
997    #[test]
998    fn ecdsa_key_operations_secp256() -> Result<(), AppleCodesignError> {
999        let ring_key = EcdsaKeyPair::from_pkcs8(
1000            &aws_lc_rs::signature::ECDSA_P256_SHA256_ASN1_SIGNING,
1001            SECP256_PKCS8_DER,
1002        )
1003        .unwrap();
1004        let ring_public_key_data = ring_key.public_key().as_ref();
1005
1006        let pki = PrivateKeyInfo::from_der(SECP256_PKCS8_DER).unwrap();
1007        let key = InMemoryPrivateKey::try_from(pki).unwrap();
1008
1009        assert_eq!(key.to_pkcs8_der().unwrap().as_bytes(), SECP256_PKCS8_DER);
1010
1011        InMemorySigningKeyPair::from_pkcs8_der(SECP256_PKCS8_DER)?;
1012        let our_key = InMemorySigningKeyPair::try_from(key)?;
1013        let our_public_key = our_key.public_key_data();
1014
1015        assert_eq!(our_public_key.as_ref(), ring_public_key_data);
1016
1017        InMemoryPrivateKey::from_pkcs8_der(SECP256_PKCS8_DER)?;
1018
1019        Ok(())
1020    }
1021}