use crate::tls::Error;
use crate::x509::{Certificate, oid};
use alloc::vec::Vec;
type Oid = Vec<u64>;
#[derive(Clone, Debug, Default)]
pub struct PolicyOptions {
pub initial_policies: Option<Vec<Oid>>,
pub require_explicit_policy: bool,
pub inhibit_policy_mapping: bool,
pub inhibit_any_policy: bool,
}
impl PolicyOptions {
pub fn none() -> Self {
PolicyOptions::default()
}
pub fn require(policies: &[&[u64]]) -> Self {
PolicyOptions {
initial_policies: Some(policies.iter().map(|p| p.to_vec()).collect()),
require_explicit_policy: true,
inhibit_policy_mapping: false,
inhibit_any_policy: false,
}
}
pub(crate) fn policy_processing_enabled(&self) -> bool {
self.require_explicit_policy || self.initial_policies.is_some()
}
}
#[derive(Clone, Debug)]
struct Node {
valid_policy: Oid,
expected_policy_set: Vec<Oid>,
parent: usize,
depth: usize,
}
struct PolicyTree {
nodes: Vec<Node>,
depth: usize,
}
impl PolicyTree {
fn initial() -> Self {
PolicyTree {
nodes: alloc::vec![Node {
valid_policy: oid::ANY_POLICY.to_vec(),
expected_policy_set: alloc::vec![oid::ANY_POLICY.to_vec()],
parent: usize::MAX,
depth: 0,
}],
depth: 0,
}
}
fn leaf_indices(&self) -> Vec<usize> {
let d = self.depth;
(0..self.nodes.len())
.filter(|&i| self.nodes[i].depth == d)
.collect()
}
fn prune(&mut self) {
let mut keep = alloc::vec![false; self.nodes.len()];
for i in 0..self.nodes.len() {
if self.nodes[i].depth == self.depth {
let mut cur = i;
while cur != usize::MAX && !keep[cur] {
keep[cur] = true;
cur = self.nodes[cur].parent;
}
}
}
let mut remap = alloc::vec![usize::MAX; self.nodes.len()];
let mut new_nodes = Vec::new();
for (old, node) in self.nodes.iter().enumerate() {
if keep[old] {
remap[old] = new_nodes.len();
new_nodes.push(node.clone());
}
}
for node in &mut new_nodes {
if node.parent != usize::MAX {
node.parent = remap[node.parent];
}
}
self.nodes = new_nodes;
}
}
pub(crate) fn check_policies(path: &[Certificate], opts: &PolicyOptions) -> Result<(), Error> {
if !opts.policy_processing_enabled() {
return Ok(());
}
let n = path.len();
if n == 0 {
return Err(Error::BadCertificate);
}
let mut tree = PolicyTree::initial();
let mut explicit_policy: usize = if opts.require_explicit_policy {
0
} else {
n + 1
};
let mut inhibit_any_policy: usize = if opts.inhibit_any_policy { 0 } else { n + 1 };
let mut policy_mapping: usize = if opts.inhibit_policy_mapping {
0
} else {
n + 1
};
for cert_index in 1..=n {
let cert = &path[n - cert_index];
let is_final = cert_index == n;
process_certificate_policies(&mut tree, cert, inhibit_any_policy)?;
let policies = cert
.certificate_policies()
.map_err(|_| Error::BadCertificate)?;
if policies.is_none() {
tree.nodes.clear();
} else {
tree.prune();
}
if !is_final {
prepare_for_next(
&mut tree,
cert,
&mut explicit_policy,
&mut policy_mapping,
&mut inhibit_any_policy,
)?;
}
}
if let Some((require, _inhibit, _crit)) = path[0]
.policy_constraints()
.map_err(|_| Error::BadCertificate)?
&& let Some(r) = require
&& (r as usize) == 0
{
explicit_policy = 0;
}
let acceptable = intersect_with_user_set(&tree, opts.initial_policies.as_deref());
if explicit_policy == 0 && acceptable.is_empty() {
return Err(Error::BadCertificate);
}
Ok(())
}
fn process_certificate_policies(
tree: &mut PolicyTree,
cert: &Certificate,
inhibit_any_policy: usize,
) -> Result<(), Error> {
let policies = cert
.certificate_policies()
.map_err(|_| Error::BadCertificate)?;
let Some((policy_oids, _critical)) = policies else {
return Ok(());
};
let parent_leaves = tree.leaf_indices();
let parent_depth = tree.depth;
let new_depth = parent_depth + 1;
let has_any = policy_oids.iter().any(|p| p.as_slice() == oid::ANY_POLICY);
let concrete: Vec<&Oid> = policy_oids
.iter()
.filter(|p| p.as_slice() != oid::ANY_POLICY)
.collect();
let mut new_nodes: Vec<Node> = Vec::new();
for p in &concrete {
let mut matched = false;
for &pi in &parent_leaves {
if tree.nodes[pi]
.expected_policy_set
.iter()
.any(|e| e.as_slice() == p.as_slice())
{
matched = true;
new_nodes.push(Node {
valid_policy: (*p).clone(),
expected_policy_set: alloc::vec![(*p).clone()],
parent: pi,
depth: new_depth,
});
}
}
if !matched {
for &pi in &parent_leaves {
if tree.nodes[pi].valid_policy.as_slice() == oid::ANY_POLICY {
new_nodes.push(Node {
valid_policy: (*p).clone(),
expected_policy_set: alloc::vec![(*p).clone()],
parent: pi,
depth: new_depth,
});
}
}
}
}
if has_any && inhibit_any_policy > 0 {
for &pi in &parent_leaves {
for e in tree.nodes[pi].expected_policy_set.clone() {
let already = new_nodes
.iter()
.any(|nn| nn.parent == pi && nn.valid_policy == e);
if !already {
new_nodes.push(Node {
valid_policy: e.clone(),
expected_policy_set: alloc::vec![e.clone()],
parent: pi,
depth: new_depth,
});
}
}
}
}
for node in new_nodes {
tree.nodes.push(node);
}
tree.depth = new_depth;
Ok(())
}
fn prepare_for_next(
tree: &mut PolicyTree,
cert: &Certificate,
explicit_policy: &mut usize,
policy_mapping: &mut usize,
inhibit_any_policy: &mut usize,
) -> Result<(), Error> {
if let Some((mappings, _crit)) = cert.policy_mappings().map_err(|_| Error::BadCertificate)? {
if *policy_mapping > 0 {
apply_policy_mappings(tree, &mappings);
} else {
let d = tree.depth;
let mapped: Vec<&Oid> = mappings.iter().map(|(i, _)| i).collect();
let keep: Vec<bool> = tree
.nodes
.iter()
.map(|node| {
!(node.depth == d
&& mapped
.iter()
.any(|m| m.as_slice() == node.valid_policy.as_slice()))
})
.collect();
let mut remap = alloc::vec![usize::MAX; tree.nodes.len()];
let mut survive = Vec::new();
for (old, node) in tree.nodes.drain(..).enumerate() {
if keep[old] {
remap[old] = survive.len();
survive.push(node);
}
}
for node in &mut survive {
if node.parent != usize::MAX {
node.parent = remap.get(node.parent).copied().unwrap_or(usize::MAX);
}
}
tree.nodes = survive;
tree.prune();
}
}
*explicit_policy = explicit_policy.saturating_sub(1);
*policy_mapping = policy_mapping.saturating_sub(1);
*inhibit_any_policy = inhibit_any_policy.saturating_sub(1);
if let Some((require, inhibit, _crit)) = cert
.policy_constraints()
.map_err(|_| Error::BadCertificate)?
{
if let Some(r) = require {
*explicit_policy = (*explicit_policy).min(r as usize);
}
if let Some(im) = inhibit {
*policy_mapping = (*policy_mapping).min(im as usize);
}
}
if let Some((skip, _crit)) = cert
.inhibit_any_policy()
.map_err(|_| Error::BadCertificate)?
{
*inhibit_any_policy = (*inhibit_any_policy).min(skip as usize);
}
Ok(())
}
fn apply_policy_mappings(tree: &mut PolicyTree, mappings: &[(Oid, Oid)]) {
let d = tree.depth;
for i in 0..tree.nodes.len() {
if tree.nodes[i].depth != d {
continue;
}
let vp = tree.nodes[i].valid_policy.clone();
let subjects: Vec<Oid> = mappings
.iter()
.filter(|(idp, _)| idp.as_slice() == vp.as_slice())
.map(|(_, sdp)| sdp.clone())
.collect();
if !subjects.is_empty() {
tree.nodes[i].expected_policy_set = subjects;
}
}
}
fn intersect_with_user_set(tree: &PolicyTree, user_set: Option<&[Oid]>) -> Vec<Oid> {
let domain_depth = if tree.depth >= 1 { 1 } else { tree.depth };
let domain_indices: Vec<usize> = (0..tree.nodes.len())
.filter(|&i| tree.nodes[i].depth == domain_depth)
.collect();
let any_present = domain_indices
.iter()
.any(|&i| tree.nodes[i].valid_policy.as_slice() == oid::ANY_POLICY);
let mut effective: Vec<Oid> = domain_indices
.iter()
.map(|&i| tree.nodes[i].valid_policy.clone())
.filter(|p| p.as_slice() != oid::ANY_POLICY)
.collect();
match user_set {
None => {
if any_present && effective.is_empty() {
effective.push(oid::ANY_POLICY.to_vec());
}
effective
}
Some(set) => {
let mut acceptable: Vec<Oid> = effective
.iter()
.filter(|p| set.iter().any(|u| u.as_slice() == p.as_slice()))
.cloned()
.collect();
if any_present {
for u in set {
if !acceptable.iter().any(|p| p.as_slice() == u.as_slice()) {
acceptable.push(u.clone());
}
}
}
acceptable
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::rsa::BoxedRsaPrivateKey;
use crate::signature_registry::SignaturePolicy;
use crate::test_util::{rsa_test_key_a, rsa_test_key_b};
use crate::tls::pki::store::RootCertStore;
use crate::tls::pki::verify::{ChainPurpose, verify_chain_with_policy};
use crate::x509::extension;
use crate::x509::{CertSigner, Certificate, DistinguishedName, Extension, Time, Validity};
use alloc::vec;
use alloc::vec::Vec;
const POLICY_A: &[u64] = &[2, 23, 140, 1, 2, 1]; const POLICY_B: &[u64] = &[1, 3, 6, 1, 4, 1, 99999, 1];
const POLICY_C: &[u64] = &[1, 3, 6, 1, 4, 1, 99999, 2];
fn validity() -> Validity {
Validity::new(
Time::utc(2024, 1, 1, 0, 0, 0),
Time::utc(2034, 1, 1, 0, 0, 0),
)
}
fn boxed(k: &crate::rsa::RsaPrivateKey<32>) -> BoxedRsaPrivateKey {
BoxedRsaPrivateKey::from_pkcs1_der(&k.to_pkcs1_der()).unwrap()
}
fn build_chain(
int_exts: &[Extension],
leaf_exts: &[Extension],
) -> (RootCertStore, Vec<Vec<u8>>) {
let root_k = rsa_test_key_a();
let int_k = rsa_test_key_b();
let leaf_k = rsa_test_key_b();
let root_b = boxed(&root_k);
let int_b = boxed(&int_k);
let root_name = DistinguishedName::common_name("Policy Root");
let int_name = DistinguishedName::common_name("Policy Intermediate");
let leaf_name = DistinguishedName::common_name("leaf.example");
let root = Certificate::self_signed(&root_k, &root_name, &validity(), 1, true).unwrap();
let mut int_all = vec![extension::basic_constraints(true, None)];
int_all.extend_from_slice(int_exts);
let intermediate = Certificate::issue_with_extensions(
&CertSigner::Rsa(&root_b),
&root_name,
&int_name,
&crate::x509::AnyPublicKey::Rsa(int_b.public_key()),
&validity(),
2,
&int_all,
)
.unwrap();
let mut leaf_all = vec![
extension::basic_constraints(false, None),
extension::subject_alt_name(&[crate::x509::GeneralName::Dns("leaf.example".into())]),
];
leaf_all.extend_from_slice(leaf_exts);
let leaf = Certificate::issue_with_extensions(
&CertSigner::Rsa(&int_b),
&int_name,
&leaf_name,
&crate::x509::AnyPublicKey::Rsa(boxed(&leaf_k).public_key()),
&validity(),
3,
&leaf_all,
)
.unwrap();
let mut store = RootCertStore::new();
store.add_der(root.to_der().to_vec()).unwrap();
let chain = vec![leaf.to_der().to_vec(), intermediate.to_der().to_vec()];
(store, chain)
}
fn verify(
store: &RootCertStore,
chain: &[Vec<u8>],
opts: &PolicyOptions,
) -> Result<(), crate::tls::Error> {
let empty = crate::tls::pki::CrlStore::new();
let now = Time::utc(2026, 1, 1, 0, 0, 0);
verify_chain_with_policy(
store,
&empty,
chain,
Some(&now),
&SignaturePolicy::modern(),
ChainPurpose::Server,
opts,
)
.map(|_| ())
}
#[test]
fn default_options_are_noop() {
let (store, chain) = build_chain(&[], &[]);
verify(&store, &chain, &PolicyOptions::none()).unwrap();
}
#[test]
fn matching_required_policy_passes() {
let (store, chain) = build_chain(
&[extension::certificate_policies(&[POLICY_A])],
&[extension::certificate_policies(&[POLICY_A])],
);
verify(&store, &chain, &PolicyOptions::require(&[POLICY_A])).unwrap();
}
#[test]
fn leaf_missing_required_policy_is_rejected() {
let (store, chain) = build_chain(
&[extension::certificate_policies(&[POLICY_A])],
&[extension::certificate_policies(&[POLICY_B])],
);
assert!(verify(&store, &chain, &PolicyOptions::require(&[POLICY_A])).is_err());
}
#[test]
fn absent_policies_with_explicit_required_is_rejected() {
let (store, chain) = build_chain(&[], &[]);
assert!(verify(&store, &chain, &PolicyOptions::require(&[POLICY_A])).is_err());
}
#[test]
fn any_policy_in_chain_satisfies_required_policy() {
let (store, chain) = build_chain(
&[extension::certificate_policies(&[oid::ANY_POLICY])],
&[extension::certificate_policies(&[POLICY_A])],
);
verify(&store, &chain, &PolicyOptions::require(&[POLICY_A])).unwrap();
}
#[test]
fn inhibit_any_policy_blocks_any_policy_expansion() {
let int_exts = [extension::certificate_policies(&[oid::ANY_POLICY])];
let leaf_exts = [extension::certificate_policies(&[POLICY_A])];
let (store, chain) = build_chain(&int_exts, &leaf_exts);
let mut opts = PolicyOptions::require(&[POLICY_A]);
opts.inhibit_any_policy = true;
assert!(verify(&store, &chain, &opts).is_err());
let (store2, chain2) = build_chain(&int_exts, &leaf_exts);
verify(&store2, &chain2, &PolicyOptions::require(&[POLICY_A])).unwrap();
}
#[test]
fn policy_mapping_translates() {
let (store, chain) = build_chain(
&[
extension::certificate_policies(&[POLICY_B]),
extension::policy_mappings(&[(POLICY_B, POLICY_C)]),
],
&[extension::certificate_policies(&[POLICY_C])],
);
verify(&store, &chain, &PolicyOptions::require(&[POLICY_B])).unwrap();
let (store2, chain2) = build_chain(
&[
extension::certificate_policies(&[POLICY_B]),
extension::policy_mappings(&[(POLICY_B, POLICY_C)]),
],
&[extension::certificate_policies(&[POLICY_C])],
);
assert!(verify(&store2, &chain2, &PolicyOptions::require(&[POLICY_C])).is_err());
}
#[test]
fn inhibit_policy_mapping_blocks_translation() {
let (store, chain) = build_chain(
&[
extension::certificate_policies(&[POLICY_B]),
extension::policy_mappings(&[(POLICY_B, POLICY_C)]),
],
&[extension::certificate_policies(&[POLICY_C])],
);
let mut opts = PolicyOptions::require(&[POLICY_B]);
opts.inhibit_policy_mapping = true;
assert!(verify(&store, &chain, &opts).is_err());
}
#[test]
fn require_explicit_via_cert_policy_constraints() {
let (store, chain) = build_chain(
&[
extension::certificate_policies(&[POLICY_A]),
extension::policy_constraints(Some(0), None),
],
&[extension::certificate_policies(&[POLICY_B])],
);
let opts = PolicyOptions {
initial_policies: Some(vec![POLICY_A.to_vec()]),
require_explicit_policy: false,
inhibit_policy_mapping: false,
inhibit_any_policy: false,
};
assert!(verify(&store, &chain, &opts).is_err());
let (store2, chain2) = build_chain(
&[
extension::certificate_policies(&[POLICY_A]),
extension::policy_constraints(Some(0), None),
],
&[extension::certificate_policies(&[POLICY_A])],
);
verify(&store2, &chain2, &opts).unwrap();
}
#[test]
fn user_set_disjoint_from_chain_is_rejected() {
let (store, chain) = build_chain(
&[extension::certificate_policies(&[POLICY_A])],
&[extension::certificate_policies(&[POLICY_A])],
);
assert!(verify(&store, &chain, &PolicyOptions::require(&[POLICY_C])).is_err());
}
#[test]
fn inhibited_mapping_prune_remaps_parent_indices() {
let mut tree = PolicyTree {
nodes: vec![
Node {
valid_policy: POLICY_A.to_vec(),
expected_policy_set: vec![POLICY_A.to_vec()],
parent: usize::MAX,
depth: 0,
},
Node {
valid_policy: POLICY_B.to_vec(),
expected_policy_set: vec![POLICY_B.to_vec()],
parent: 0,
depth: 1,
},
Node {
valid_policy: POLICY_C.to_vec(),
expected_policy_set: vec![POLICY_C.to_vec()],
parent: 0,
depth: 1,
},
],
depth: 1,
};
let d = tree.depth;
let mapped = [POLICY_B];
let keep: Vec<bool> = tree
.nodes
.iter()
.map(|node| !(node.depth == d && mapped.contains(&node.valid_policy.as_slice())))
.collect();
let mut remap = alloc::vec![usize::MAX; tree.nodes.len()];
let mut survive = Vec::new();
for (old, node) in tree.nodes.drain(..).enumerate() {
if keep[old] {
remap[old] = survive.len();
survive.push(node);
}
}
for node in &mut survive {
if node.parent != usize::MAX {
node.parent = remap.get(node.parent).copied().unwrap_or(usize::MAX);
}
}
tree.nodes = survive;
assert_eq!(tree.nodes.len(), 2);
assert_eq!(tree.nodes[1].valid_policy, POLICY_C.to_vec());
assert_eq!(tree.nodes[1].parent, 0);
assert_eq!(tree.nodes[0].valid_policy, POLICY_A.to_vec());
assert_eq!(tree.nodes[0].parent, usize::MAX);
tree.prune();
assert!(
tree.nodes
.iter()
.any(|n| n.valid_policy == POLICY_C.to_vec())
);
assert!(
tree.nodes
.iter()
.any(|n| n.valid_policy == POLICY_A.to_vec())
);
for n in &tree.nodes {
assert!(n.parent == usize::MAX || n.parent < tree.nodes.len());
}
}
#[test]
fn critical_policy_constraints_only_accepted_when_processing() {
let crit_pc = {
let mut e = extension::policy_constraints(Some(0), None);
e.critical = true;
e
};
let (store, chain) = build_chain(
&[
extension::certificate_policies(&[POLICY_A]),
crit_pc.clone(),
],
&[extension::certificate_policies(&[POLICY_A])],
);
assert!(verify(&store, &chain, &PolicyOptions::none()).is_err());
verify(&store, &chain, &PolicyOptions::require(&[POLICY_A])).unwrap();
}
}