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        remote_signing::{session_negotiation::PublicKeyPeerDecrypt, RemoteSignError},
10        AppleCodesignError,
11    },
12    apple_xar::table_of_contents::ChecksumType as XarChecksumType,
13    bytes::Bytes,
14    clap::ValueEnum,
15    der::{asn1, Decode, Document, Encode, SecretDocument},
16    digest::DynDigest,
17    elliptic_curve::{
18        sec1::{FromEncodedPoint, ModulusSize, ToEncodedPoint},
19        AffinePoint, Curve, CurveArithmetic, FieldBytesSize, SecretKey as ECSecretKey,
20    },
21    oid_registry::{
22        OID_EC_P256, OID_KEY_TYPE_EC_PUBLIC_KEY, OID_PKCS1_RSAENCRYPTION, OID_SIG_ED25519,
23    },
24    p256::NistP256,
25    pkcs1::RsaPrivateKey,
26    pkcs8::{EncodePrivateKey, ObjectIdentifier, PrivateKeyInfo},
27    ring::signature::{Ed25519KeyPair, KeyPair},
28    rsa::{pkcs1::DecodeRsaPrivateKey, BigUint, Oaep, RsaPrivateKey as RsaConstructedKey},
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)]
485pub enum DigestType {
486    None,
487    Sha1,
488    Sha256,
489    Sha256Truncated,
490    Sha384,
491    Sha512,
492    #[value(skip)]
493    Unknown(u8),
494}
495
496impl Default for DigestType {
497    fn default() -> Self {
498        Self::Sha256
499    }
500}
501
502impl TryFrom<DigestType> for DigestAlgorithm {
503    type Error = AppleCodesignError;
504
505    fn try_from(value: DigestType) -> Result<DigestAlgorithm, Self::Error> {
506        match value {
507            DigestType::Sha1 => Ok(DigestAlgorithm::Sha1),
508            DigestType::Sha256 => Ok(DigestAlgorithm::Sha256),
509            DigestType::Sha256Truncated => Ok(DigestAlgorithm::Sha256),
510            DigestType::Sha384 => Ok(DigestAlgorithm::Sha384),
511            DigestType::Sha512 => Ok(DigestAlgorithm::Sha512),
512            DigestType::Unknown(_) => Err(AppleCodesignError::DigestUnknownAlgorithm),
513            DigestType::None => Err(AppleCodesignError::DigestUnsupportedAlgorithm),
514        }
515    }
516}
517
518impl PartialOrd for DigestType {
519    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
520        Some(self.cmp(other))
521    }
522}
523
524impl Ord for DigestType {
525    fn cmp(&self, other: &Self) -> Ordering {
526        u8::from(*self).cmp(&u8::from(*other))
527    }
528}
529
530impl Display for DigestType {
531    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
532        match self {
533            DigestType::None => f.write_str("none"),
534            DigestType::Sha1 => f.write_str("sha1"),
535            DigestType::Sha256 => f.write_str("sha256"),
536            DigestType::Sha256Truncated => f.write_str("sha256-truncated"),
537            DigestType::Sha384 => f.write_str("sha384"),
538            DigestType::Sha512 => f.write_str("sha512"),
539            DigestType::Unknown(v) => f.write_fmt(format_args!("unknown: {v}")),
540        }
541    }
542}
543
544impl TryFrom<&str> for DigestType {
545    type Error = AppleCodesignError;
546
547    fn try_from(s: &str) -> Result<Self, Self::Error> {
548        match s {
549            "none" => Ok(Self::None),
550            "sha1" => Ok(Self::Sha1),
551            "sha256" => Ok(Self::Sha256),
552            "sha256-truncated" => Ok(Self::Sha256Truncated),
553            "sha384" => Ok(Self::Sha384),
554            "sha512" => Ok(Self::Sha512),
555            _ => Err(AppleCodesignError::DigestUnknownAlgorithm),
556        }
557    }
558}
559
560impl TryFrom<XarChecksumType> for DigestType {
561    type Error = AppleCodesignError;
562
563    fn try_from(c: XarChecksumType) -> Result<Self, Self::Error> {
564        match c {
565            XarChecksumType::None => Ok(Self::None),
566            XarChecksumType::Sha1 => Ok(Self::Sha1),
567            XarChecksumType::Sha256 => Ok(Self::Sha256),
568            XarChecksumType::Sha512 => Ok(Self::Sha512),
569            XarChecksumType::Md5 => Err(AppleCodesignError::DigestUnsupportedAlgorithm),
570        }
571    }
572}
573
574impl DigestType {
575    /// Obtain the size of hashes for this hash type.
576    pub fn hash_len(&self) -> Result<usize, AppleCodesignError> {
577        Ok(self.digest_data(&[])?.len())
578    }
579
580    /// Obtain a hasher for this digest type.
581    pub fn as_hasher(&self) -> Result<ring::digest::Context, AppleCodesignError> {
582        match self {
583            Self::None => Err(AppleCodesignError::DigestUnknownAlgorithm),
584            Self::Sha1 => Ok(ring::digest::Context::new(
585                &ring::digest::SHA1_FOR_LEGACY_USE_ONLY,
586            )),
587            Self::Sha256 | Self::Sha256Truncated => {
588                Ok(ring::digest::Context::new(&ring::digest::SHA256))
589            }
590            Self::Sha384 => Ok(ring::digest::Context::new(&ring::digest::SHA384)),
591            Self::Sha512 => Ok(ring::digest::Context::new(&ring::digest::SHA512)),
592            Self::Unknown(_) => Err(AppleCodesignError::DigestUnknownAlgorithm),
593        }
594    }
595
596    /// Digest data given the configured hasher.
597    pub fn digest_data(&self, data: &[u8]) -> Result<Vec<u8>, AppleCodesignError> {
598        let mut hasher = self.as_hasher()?;
599
600        hasher.update(data);
601        let mut hash = hasher.finish().as_ref().to_vec();
602
603        if matches!(self, Self::Sha256Truncated) {
604            hash.truncate(20);
605        }
606
607        Ok(hash)
608    }
609}
610
611pub struct Digest<'a> {
612    pub data: Cow<'a, [u8]>,
613}
614
615impl<'a> Digest<'a> {
616    /// Whether this is the null hash (all 0s).
617    pub fn is_null(&self) -> bool {
618        self.data.iter().all(|b| *b == 0)
619    }
620
621    pub fn to_vec(&self) -> Vec<u8> {
622        self.data.to_vec()
623    }
624
625    pub fn to_owned(&self) -> Digest<'static> {
626        Digest {
627            data: Cow::Owned(self.data.clone().into_owned()),
628        }
629    }
630
631    pub fn as_hex(&self) -> String {
632        hex::encode(&self.data)
633    }
634}
635
636impl<'a> std::fmt::Debug for Digest<'a> {
637    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
638        f.write_str(&hex::encode(&self.data))
639    }
640}
641
642impl<'a> From<Vec<u8>> for Digest<'a> {
643    fn from(v: Vec<u8>) -> Self {
644        Self { data: v.into() }
645    }
646}
647
648/// Holds multiple computed digests for content.
649pub struct MultiDigest {
650    pub sha1: Digest<'static>,
651    pub sha256: Digest<'static>,
652}
653
654impl MultiDigest {
655    /// Compute the multi digests for any stream reader.
656    ///
657    /// This will read the stream until EOF.
658    pub fn from_reader(mut reader: impl std::io::Read) -> Result<Self, AppleCodesignError> {
659        let mut sha1 = DigestType::Sha1.as_hasher()?;
660        let mut sha256 = DigestType::Sha256.as_hasher()?;
661
662        let mut buffer = [0u8; 16384];
663
664        loop {
665            let read = reader.read(&mut buffer)?;
666            if read == 0 {
667                break;
668            }
669
670            sha1.update(&buffer[0..read]);
671            sha256.update(&buffer[0..read]);
672        }
673
674        let sha1 = sha1.finish().as_ref().to_vec();
675        let sha256 = sha256.finish().as_ref().to_vec();
676
677        Ok(Self {
678            sha1: sha1.into(),
679            sha256: sha256.into(),
680        })
681    }
682
683    /// Compute the multi digest of a filesystem path.
684    pub fn from_path(path: impl AsRef<Path>) -> Result<Self, AppleCodesignError> {
685        let fh = std::fs::File::open(path.as_ref())?;
686        Self::from_reader(fh)
687    }
688}
689
690fn bmp_string(s: &str) -> Vec<u8> {
691    let utf16: Vec<u16> = s.encode_utf16().collect();
692
693    let mut bytes = Vec::with_capacity(utf16.len() * 2 + 2);
694    for c in utf16 {
695        bytes.push((c / 256) as u8);
696        bytes.push((c % 256) as u8);
697    }
698    bytes.push(0x00);
699    bytes.push(0x00);
700
701    bytes
702}
703
704/// Parse PFX data into a key pair.
705///
706/// PFX data is commonly encountered in `.p12` files, such as those created
707/// when exporting certificates from Apple's `Keychain Access` application.
708///
709/// The contents of the PFX file require a password to decrypt. However, if
710/// no password was provided to create the PFX data, this password may be the
711/// empty string.
712pub fn parse_pfx_data(
713    data: &[u8],
714    password: &str,
715) -> Result<(CapturedX509Certificate, InMemoryPrivateKey), AppleCodesignError> {
716    let pfx = p12::PFX::parse(data).map_err(|e| {
717        AppleCodesignError::PfxParseError(format!("data does not appear to be PFX: {e:?}"))
718    })?;
719
720    if !pfx.verify_mac(password) {
721        return Err(AppleCodesignError::PfxBadPassword);
722    }
723
724    // Apple's certificate export format consists of regular data content info
725    // with inner ContentInfo components holding the key and certificate.
726    let data = match pfx.auth_safe {
727        p12::ContentInfo::Data(data) => data,
728        _ => {
729            return Err(AppleCodesignError::PfxParseError(
730                "unexpected PFX content info".to_string(),
731            ));
732        }
733    };
734
735    let content_infos = yasna::parse_der(&data, |reader| {
736        reader.collect_sequence_of(p12::ContentInfo::parse)
737    })
738    .map_err(|e| {
739        AppleCodesignError::PfxParseError(format!("failed parsing inner ContentInfo: {e:?}"))
740    })?;
741
742    let bmp_password = bmp_string(password);
743
744    let mut certificate = None;
745    let mut signing_key = None;
746
747    for content in content_infos {
748        let bags_data = match content {
749            p12::ContentInfo::Data(inner) => inner,
750            p12::ContentInfo::EncryptedData(encrypted) => {
751                encrypted.data(&bmp_password).ok_or_else(|| {
752                    AppleCodesignError::PfxParseError(
753                        "failed decrypting inner EncryptedData".to_string(),
754                    )
755                })?
756            }
757            p12::ContentInfo::OtherContext(_) => {
758                return Err(AppleCodesignError::PfxParseError(
759                    "unexpected OtherContent content in inner PFX data".to_string(),
760                ));
761            }
762        };
763
764        let bags = yasna::parse_ber(&bags_data, |reader| {
765            reader.collect_sequence_of(p12::SafeBag::parse)
766        })
767        .map_err(|e| {
768            AppleCodesignError::PfxParseError(format!(
769                "failed parsing SafeBag within inner Data: {e:?}"
770            ))
771        })?;
772
773        for bag in bags {
774            match bag.bag {
775                p12::SafeBagKind::CertBag(cert_bag) => match cert_bag {
776                    p12::CertBag::X509(cert_data) => {
777                        certificate = Some(CapturedX509Certificate::from_der(cert_data)?);
778                    }
779                    p12::CertBag::SDSI(_) => {
780                        return Err(AppleCodesignError::PfxParseError(
781                            "unexpected SDSI certificate data".to_string(),
782                        ));
783                    }
784                },
785                p12::SafeBagKind::Pkcs8ShroudedKeyBag(key_bag) => {
786                    let decrypted = key_bag.decrypt(&bmp_password).ok_or_else(|| {
787                        AppleCodesignError::PfxParseError(
788                            "error decrypting PKCS8 shrouded key bag; is the password correct?"
789                                .to_string(),
790                        )
791                    })?;
792
793                    signing_key = Some(InMemoryPrivateKey::from_pkcs8_der(decrypted)?);
794                }
795                p12::SafeBagKind::OtherBagKind(_) => {
796                    return Err(AppleCodesignError::PfxParseError(
797                        "unexpected bag type in inner PFX content".to_string(),
798                    ));
799                }
800            }
801        }
802    }
803
804    match (certificate, signing_key) {
805        (Some(certificate), Some(signing_key)) => Ok((certificate, signing_key)),
806        (None, Some(_)) => Err(AppleCodesignError::PfxParseError(
807            "failed to find x509 certificate in PFX data".to_string(),
808        )),
809        (_, None) => Err(AppleCodesignError::PfxParseError(
810            "failed to find signing key in PFX data".to_string(),
811        )),
812    }
813}
814
815/// RSA OAEP post decrypt depadding.
816///
817/// This implements the procedure described by RFC 3447 Section 7.1.2
818/// starting at Step 3 (after the ciphertext has been fed into the low-level
819/// RSA decryption.
820///
821/// This implementation has NOT been audited and shouldn't be used. It only
822/// exists here because we need it to support RSA decryption using YubiKeys.
823/// https://github.com/RustCrypto/RSA/issues/159 is fixed to hopefully get this
824/// exposed as an API on the rsa crate.
825#[allow(unused)]
826pub(crate) fn rsa_oaep_post_decrypt_decode(
827    modulus_length_bytes: usize,
828    mut em: Vec<u8>,
829    digest: &mut dyn digest::DynDigest,
830    mgf_digest: &mut dyn digest::DynDigest,
831    label: Option<String>,
832) -> Result<Vec<u8>, rsa::errors::Error> {
833    let k = modulus_length_bytes;
834    let digest_len = digest.output_size();
835
836    // 3. EME_OAEP decoding.
837
838    // 3a.
839    let label = label.unwrap_or_default();
840    digest.update(label.as_bytes());
841    let label_digest = digest.finalize_reset();
842
843    // 3b.
844    let (y, remaining) = em.split_at_mut(1);
845    let (masked_seed, masked_db) = remaining.split_at_mut(digest_len);
846
847    if masked_seed.len() != digest_len || masked_db.len() != k - digest_len - 1 {
848        return Err(rsa::errors::Error::Decryption);
849    }
850
851    // 3c - 3f.
852    mgf1_xor(masked_seed, mgf_digest, masked_db);
853    mgf1_xor(masked_db, mgf_digest, masked_seed);
854
855    // 3g.
856    //
857    // We need to split into padding string (all zeroes) and message M with a
858    // 0x01 between them. The padding string should be all zeroes. And this should
859    // execute in constant time, which makes it tricky.
860
861    let digests_equivalent = masked_db[0..digest_len].ct_eq(label_digest.as_ref());
862
863    let mut looking_for_index = Choice::from(1u8);
864    let mut index = 0u32;
865    let mut padding_invalid = Choice::from(0u8);
866
867    for (i, value) in masked_db.iter().skip(digest_len).enumerate() {
868        let is_zero = value.ct_eq(&0u8);
869        let is_one = value.ct_eq(&1u8);
870
871        index.conditional_assign(&(i as u32), looking_for_index & is_one);
872        looking_for_index &= !is_one;
873        padding_invalid |= looking_for_index & !is_zero;
874    }
875
876    let y_is_zero = y[0].ct_eq(&0u8);
877
878    let valid = y_is_zero & digests_equivalent & !padding_invalid & !looking_for_index;
879
880    let res = CtOption::new((em, index + 2 + (digest_len * 2) as u32), valid);
881
882    if res.is_none().into() {
883        return Err(rsa::errors::Error::Decryption);
884    }
885
886    let (out, index) = res.unwrap();
887
888    Ok(out[index as usize..].to_vec())
889}
890
891fn inc_counter(counter: &mut [u8; 4]) {
892    for i in (0..4).rev() {
893        counter[i] = counter[i].wrapping_add(1);
894        if counter[i] != 0 {
895            // No overflow
896            return;
897        }
898    }
899}
900
901fn mgf1_xor(out: &mut [u8], digest: &mut dyn DynDigest, seed: &[u8]) {
902    let mut counter = [0u8; 4];
903    let mut i = 0;
904
905    const MAX_LEN: u64 = core::u32::MAX as u64 + 1;
906    assert!(out.len() as u64 <= MAX_LEN);
907
908    while i < out.len() {
909        let mut digest_input = vec![0u8; seed.len() + 4];
910        digest_input[0..seed.len()].copy_from_slice(seed);
911        digest_input[seed.len()..].copy_from_slice(&counter);
912
913        digest.update(digest_input.as_slice());
914        let digest_output = &*digest.finalize_reset();
915        let mut j = 0;
916        loop {
917            if j >= digest_output.len() || i >= out.len() {
918                break;
919            }
920
921            out[i] ^= digest_output[j];
922            j += 1;
923            i += 1;
924        }
925        inc_counter(&mut counter);
926    }
927}
928
929#[cfg(test)]
930mod test {
931    use {
932        super::*,
933        ring::signature::{EcdsaKeyPair, KeyPair, RsaKeyPair},
934        x509_certificate::Sign,
935    };
936
937    const RSA_2048_PKCS8_DER: &[u8] = include_bytes!("testdata/rsa-2048.pk8");
938    const ED25519_PKCS8_DER: &[u8] = include_bytes!("testdata/ed25519.pk8");
939    const SECP256_PKCS8_DER: &[u8] = include_bytes!("testdata/secp256r1.pk8");
940
941    #[test]
942    fn parse_keychain_p12_export() {
943        let data = include_bytes!("apple-codesign-testuser.p12");
944
945        let err = parse_pfx_data(data, "bad-password").unwrap_err();
946        assert!(matches!(err, AppleCodesignError::PfxBadPassword));
947
948        parse_pfx_data(data, "password123").unwrap();
949    }
950
951    #[test]
952    fn rsa_key_operations() -> Result<(), AppleCodesignError> {
953        let ring_key = RsaKeyPair::from_pkcs8(RSA_2048_PKCS8_DER).unwrap();
954        let ring_public_key_data = ring_key.public_key().as_ref();
955
956        let pki = PrivateKeyInfo::from_der(RSA_2048_PKCS8_DER).unwrap();
957        let key = InMemoryPrivateKey::try_from(pki).unwrap();
958
959        assert_eq!(key.to_pkcs8_der().unwrap().as_bytes(), RSA_2048_PKCS8_DER);
960
961        let our_key = InMemorySigningKeyPair::try_from(key)?;
962        let our_public_key = our_key.public_key_data();
963
964        assert_eq!(our_public_key.as_ref(), ring_public_key_data);
965
966        InMemoryPrivateKey::from_pkcs8_der(RSA_2048_PKCS8_DER)?;
967
968        let random_key = rsa::RsaPrivateKey::new(&mut rand::thread_rng(), 2048).unwrap();
969        let random_key_pkcs8 = random_key.to_pkcs8_der().unwrap();
970        InMemorySigningKeyPair::from_pkcs8_der(random_key_pkcs8.as_bytes())?;
971
972        Ok(())
973    }
974
975    #[test]
976    fn ed25519_key_operations() -> Result<(), AppleCodesignError> {
977        let pki = PrivateKeyInfo::from_der(ED25519_PKCS8_DER).unwrap();
978        let seed = &pki.private_key[2..];
979        let key = InMemoryPrivateKey::try_from(pki).unwrap();
980
981        assert!(
982            InMemorySigningKeyPair::from_pkcs8_der(ED25519_PKCS8_DER).is_err(),
983            "stored key doesn't have public key, which ring rejects loading"
984        );
985
986        // But out PKCS#8 export includes it so it can round trip.
987        InMemorySigningKeyPair::from_pkcs8_der(key.to_pkcs8_der().unwrap().as_bytes()).unwrap();
988
989        let our_key = InMemorySigningKeyPair::try_from(key)?;
990        let our_public_key = our_key.public_key_data();
991
992        let ring_key = Ed25519KeyPair::from_seed_unchecked(seed).unwrap();
993        let ring_public_key_data = ring_key.public_key().as_ref();
994
995        assert_eq!(our_public_key.as_ref(), ring_public_key_data);
996
997        InMemoryPrivateKey::from_pkcs8_der(ED25519_PKCS8_DER)?;
998
999        Ok(())
1000    }
1001
1002    #[test]
1003    fn ecdsa_key_operations_secp256() -> Result<(), AppleCodesignError> {
1004        let ring_key = EcdsaKeyPair::from_pkcs8(
1005            &ring::signature::ECDSA_P256_SHA256_ASN1_SIGNING,
1006            SECP256_PKCS8_DER,
1007            &ring::rand::SystemRandom::new(),
1008        )
1009        .unwrap();
1010        let ring_public_key_data = ring_key.public_key().as_ref();
1011
1012        let pki = PrivateKeyInfo::from_der(SECP256_PKCS8_DER).unwrap();
1013        let key = InMemoryPrivateKey::try_from(pki).unwrap();
1014
1015        assert_eq!(key.to_pkcs8_der().unwrap().as_bytes(), SECP256_PKCS8_DER);
1016
1017        InMemorySigningKeyPair::from_pkcs8_der(SECP256_PKCS8_DER)?;
1018        let our_key = InMemorySigningKeyPair::try_from(key)?;
1019        let our_public_key = our_key.public_key_data();
1020
1021        assert_eq!(our_public_key.as_ref(), ring_public_key_data);
1022
1023        InMemoryPrivateKey::from_pkcs8_der(SECP256_PKCS8_DER)?;
1024
1025        Ok(())
1026    }
1027}