Skip to main content

rc_crypto/certificate/id/
issuer_cert_id.rs

1// Copyright 2026-Present Datadog, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::fmt::Display;
16
17use thiserror::Error;
18use valuable::Valuable;
19use x509_parser::extensions::ParsedExtension;
20use x509_parser::prelude::X509Certificate;
21
22use crate::certificate::id::{CertId, DangerousComparableId, InvalidLength};
23
24/// No Authority Key Identifier extension was found in the certificate.
25#[derive(Debug, Error)]
26#[error("no Authority Key Identifier found")]
27pub struct ErrorNoAKI;
28
29/// Error extracting an [`IssuerCertId`] from an [`X509Certificate`].
30#[derive(Debug, Error)]
31pub enum InvalidIssuerCertId {
32    /// No Authority Key Identifier extension was found in the certificate.
33    #[error(transparent)]
34    NoAKI(#[from] ErrorNoAKI),
35
36    /// Certificate ID was an invalid length.
37    #[error(transparent)]
38    InvalidLength(#[from] InvalidLength),
39}
40
41/// An opaque identifier that describes the [`Certificate`] of the issuer (CA)
42/// that issued the [`Certificate`] this value was extracted from.
43///
44/// This is an untrusted value, and can be set to anything the cert issuer
45/// wishes. Derived values such as a [`KeyId`] or certificate [`Fingerprint`])
46/// SHOULD be preferred for general use. The
47/// [`IssuerCertId::into_dangerous_comparable()`] method can be used to obtain a
48/// handle that implements [`PartialEq`].
49///
50/// The [`IssuerCertId`] is a user friendly rename of the [Authority Key
51/// Identifier] (commonly abbreviated AKI) within an X509 certificate. While the
52/// AKI claims to be an identifier of the key in the cert, it does not always
53/// identify the key material specifically (`hash(cert_dn + cert_serial)` is not
54/// uncommon).
55///
56/// [`KeyId`]: crate::keys::KeyId
57/// [`Certificate`]: crate::certificate::Certificate
58/// [`Fingerprint`]: crate::certificate::Fingerprint
59/// [Authority Key Identifier]:
60///     https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.1
61#[derive(Debug, Hash, Clone)] // NOTE: no PartialEq - not trusted, do not compare.
62pub struct IssuerCertId(CertId);
63
64impl IssuerCertId {
65    /// Render this value following the conventions of OpenSSL's colon-delimited
66    /// string representation.
67    pub fn as_hex_str(&self) -> &str {
68        self.0.as_hex_str()
69    }
70
71    /// Return the raw bytes for this ID (private to this module).
72    pub(super) fn as_bytes(&self) -> &[u8] {
73        self.0.as_bytes()
74    }
75
76    /// Return a reference to the inner [`CertId`], allowing this issuer
77    /// identifier to be used as a lookup key in collections indexed by
78    /// [`CertId`].
79    pub fn as_cert_id(&self) -> &CertId {
80        &self.0
81    }
82
83    /// Obtain a borrowed wrapper type that has a [`PartialEq`] implementation,
84    /// allowing this value to be compared to other values with the correctness
85    /// caveats documented for this type.
86    pub fn as_dangerous_comparable(&self) -> DangerousComparableId<'_, Self> {
87        DangerousComparableId::from(self)
88    }
89}
90
91impl Display for IssuerCertId {
92    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
93        f.write_str(self.as_hex_str())
94    }
95}
96
97impl Valuable for IssuerCertId {
98    fn as_value(&self) -> valuable::Value<'_> {
99        self.0.as_value()
100    }
101
102    fn visit(&self, visit: &mut dyn valuable::Visit) {
103        self.0.visit(visit);
104    }
105}
106
107impl TryFrom<&[u8]> for IssuerCertId {
108    type Error = InvalidLength;
109
110    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
111        Ok(IssuerCertId(CertId::try_from(value)?))
112    }
113}
114
115impl TryFrom<Vec<u8>> for IssuerCertId {
116    type Error = InvalidLength;
117
118    fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
119        Ok(IssuerCertId(CertId::try_from(value)?))
120    }
121}
122
123impl<'a> TryFrom<&X509Certificate<'a>> for IssuerCertId {
124    type Error = InvalidIssuerCertId;
125
126    fn try_from(cert: &X509Certificate<'a>) -> Result<Self, Self::Error> {
127        let bytes = cert
128            .iter_extensions()
129            .find_map(|v| match v.parsed_extension() {
130                ParsedExtension::AuthorityKeyIdentifier(aki) => {
131                    aki.key_identifier.as_ref().map(|kid| kid.0)
132                }
133                _ => None,
134            })
135            .ok_or(ErrorNoAKI)?;
136        Ok(IssuerCertId(CertId::try_from(bytes)?))
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use rc_x509_test_helpers::assert_valuable_repr;
143    use static_assertions::assert_not_impl_any;
144    use x509_parser::prelude::FromDer as _;
145
146    use super::*;
147
148    use crate::certificate::tests::cert_fixture;
149
150    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";
151
152    // Why: an IssuerCertId can be set to anything by the issuer, making it
153    // unreliable as a unique identifier, and should not be used to compare two
154    // certificates for equality (outside of chain building which is then
155    // cryptographically verified).
156    assert_not_impl_any!(IssuerCertId: PartialEq, Eq);
157
158    fn fixture_aki() -> IssuerCertId {
159        let der = cert_fixture().as_der();
160        let cert = X509Certificate::from_der(&der).expect("valid DER").1;
161
162        IssuerCertId::try_from(&cert).expect("extract AKI")
163    }
164
165    #[test]
166    fn test_fixture() {
167        let aki = fixture_aki();
168
169        assert_eq!(aki.as_hex_str(), FIXTURE_AKI_STR,);
170        assert_eq!(aki.as_cert_id().as_hex_str(), FIXTURE_AKI_STR);
171    }
172
173    #[test]
174    fn test_valuable_repr() {
175        let aki = fixture_aki();
176
177        assert_valuable_repr(&aki, FIXTURE_AKI_STR);
178    }
179
180    #[test]
181    fn test_danger_eq() {
182        let aki = fixture_aki();
183
184        assert_eq!(aki.as_dangerous_comparable(), aki);
185    }
186}