rc-crypto 0.1.0

Crypto library for the RC X509 platform
Documentation
// Copyright 2026-Present Datadog, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::fmt::Display;

use thiserror::Error;
use valuable::Valuable;
use x509_parser::extensions::ParsedExtension;
use x509_parser::prelude::X509Certificate;

use crate::certificate::id::{CertId, DangerousComparableId, InvalidLength};

/// No Authority Key Identifier extension was found in the certificate.
#[derive(Debug, Error)]
#[error("no Authority Key Identifier found")]
pub struct ErrorNoAKI;

/// Error extracting an [`IssuerCertId`] from an [`X509Certificate`].
#[derive(Debug, Error)]
pub enum InvalidIssuerCertId {
    /// No Authority Key Identifier extension was found in the certificate.
    #[error(transparent)]
    NoAKI(#[from] ErrorNoAKI),

    /// Certificate ID was an invalid length.
    #[error(transparent)]
    InvalidLength(#[from] InvalidLength),
}

/// An opaque identifier that describes the [`Certificate`] of the issuer (CA)
/// that issued the [`Certificate`] this value was extracted from.
///
/// This is an untrusted value, and can be set to anything the cert issuer
/// wishes. Derived values such as a [`KeyId`] or certificate [`Fingerprint`])
/// SHOULD be preferred for general use. The
/// [`IssuerCertId::into_dangerous_comparable()`] method can be used to obtain a
/// handle that implements [`PartialEq`].
///
/// The [`IssuerCertId`] is a user friendly rename of the [Authority Key
/// Identifier] (commonly abbreviated AKI) within an X509 certificate. While the
/// AKI claims to be an identifier of the key in the cert, it does not always
/// identify the key material specifically (`hash(cert_dn + cert_serial)` is not
/// uncommon).
///
/// [`KeyId`]: crate::keys::KeyId
/// [`Certificate`]: crate::certificate::Certificate
/// [`Fingerprint`]: crate::certificate::Fingerprint
/// [Authority Key Identifier]:
///     https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.1
#[derive(Debug, Hash, Clone)] // NOTE: no PartialEq - not trusted, do not compare.
pub struct IssuerCertId(CertId);

impl IssuerCertId {
    /// Render this value following the conventions of OpenSSL's colon-delimited
    /// string representation.
    pub fn as_hex_str(&self) -> &str {
        self.0.as_hex_str()
    }

    /// Return the raw bytes for this ID (private to this module).
    pub(super) fn as_bytes(&self) -> &[u8] {
        self.0.as_bytes()
    }

    /// Return a reference to the inner [`CertId`], allowing this issuer
    /// identifier to be used as a lookup key in collections indexed by
    /// [`CertId`].
    pub fn as_cert_id(&self) -> &CertId {
        &self.0
    }

    /// Obtain a borrowed wrapper type that has a [`PartialEq`] implementation,
    /// allowing this value to be compared to other values with the correctness
    /// caveats documented for this type.
    pub fn as_dangerous_comparable(&self) -> DangerousComparableId<'_, Self> {
        DangerousComparableId::from(self)
    }
}

impl Display for IssuerCertId {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(self.as_hex_str())
    }
}

impl Valuable for IssuerCertId {
    fn as_value(&self) -> valuable::Value<'_> {
        self.0.as_value()
    }

    fn visit(&self, visit: &mut dyn valuable::Visit) {
        self.0.visit(visit);
    }
}

impl TryFrom<&[u8]> for IssuerCertId {
    type Error = InvalidLength;

    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
        Ok(IssuerCertId(CertId::try_from(value)?))
    }
}

impl TryFrom<Vec<u8>> for IssuerCertId {
    type Error = InvalidLength;

    fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
        Ok(IssuerCertId(CertId::try_from(value)?))
    }
}

impl<'a> TryFrom<&X509Certificate<'a>> for IssuerCertId {
    type Error = InvalidIssuerCertId;

    fn try_from(cert: &X509Certificate<'a>) -> Result<Self, Self::Error> {
        let bytes = cert
            .iter_extensions()
            .find_map(|v| match v.parsed_extension() {
                ParsedExtension::AuthorityKeyIdentifier(aki) => {
                    aki.key_identifier.as_ref().map(|kid| kid.0)
                }
                _ => None,
            })
            .ok_or(ErrorNoAKI)?;
        Ok(IssuerCertId(CertId::try_from(bytes)?))
    }
}

#[cfg(test)]
mod tests {
    use rc_x509_test_helpers::assert_valuable_repr;
    use static_assertions::assert_not_impl_any;
    use x509_parser::prelude::FromDer as _;

    use super::*;

    use crate::certificate::tests::cert_fixture;

    const FIXTURE_AKI_STR: &str = "20:6c:8e:cf:e4:21:a7:ff:ed:23:c8:3d:37:0f:77:81:84:71:0e:15";

    // Why: an IssuerCertId can be set to anything by the issuer, making it
    // unreliable as a unique identifier, and should not be used to compare two
    // certificates for equality (outside of chain building which is then
    // cryptographically verified).
    assert_not_impl_any!(IssuerCertId: PartialEq, Eq);

    fn fixture_aki() -> IssuerCertId {
        let der = cert_fixture().as_der();
        let cert = X509Certificate::from_der(&der).expect("valid DER").1;

        IssuerCertId::try_from(&cert).expect("extract AKI")
    }

    #[test]
    fn test_fixture() {
        let aki = fixture_aki();

        assert_eq!(aki.as_hex_str(), FIXTURE_AKI_STR,);
        assert_eq!(aki.as_cert_id().as_hex_str(), FIXTURE_AKI_STR);
    }

    #[test]
    fn test_valuable_repr() {
        let aki = fixture_aki();

        assert_valuable_repr(&aki, FIXTURE_AKI_STR);
    }

    #[test]
    fn test_danger_eq() {
        let aki = fixture_aki();

        assert_eq!(aki.as_dangerous_comparable(), aki);
    }
}