quantum_sign/policy/
mod.rs1#![forbid(unsafe_code)]
2
3use serde::{Deserialize, Serialize};
4use sha2::{Digest, Sha256};
5use std::{fs, path::Path};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct Policy {
10 pub default_alg: String,
12 pub digest_alg: String,
14 pub allow_algs: Vec<String>,
16 pub required_signatures: RequiredSignatures,
18 pub offline_ok: bool,
20 pub require_fips_only: bool,
22 pub comments: Option<String>,
24}
25
26pub fn canonical_hash(policy: &Policy) -> [u8; 32] {
28 #[derive(Serialize)]
30 enum CanonRequiredSignatures {
31 Quorum { m: u8, n: u8 },
32 RequiredKids { required: Vec<String> },
33 }
34
35 #[derive(Serialize)]
36 struct Canon {
37 default_alg: String,
38 digest_alg: String,
39 allow_algs: Vec<String>,
40 required_signatures: CanonRequiredSignatures,
41 offline_ok: bool,
42 require_fips_only: bool,
43 }
44 let mut allow = policy.allow_algs.clone();
45 allow.sort();
46 let canon_rs = match &policy.required_signatures {
47 RequiredSignatures::Quorum { m, n } => CanonRequiredSignatures::Quorum { m: *m, n: *n },
48 RequiredSignatures::RequiredKids { required } => {
49 let mut req = required.clone();
50 req.sort();
51 req.dedup();
52 CanonRequiredSignatures::RequiredKids { required: req }
53 }
54 };
55 let canon = Canon {
56 default_alg: policy.default_alg.clone(),
57 digest_alg: policy.digest_alg.clone(),
58 allow_algs: allow,
59 required_signatures: canon_rs,
60 offline_ok: policy.offline_ok,
61 require_fips_only: policy.require_fips_only,
62 };
63 let json = serde_json::to_vec(&canon).expect("serialize policy");
64 let mut h = Sha256::new();
65 h.update(json);
66 let mut out = [0u8; 32];
67 out.copy_from_slice(&h.finalize());
68 out
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize)]
73#[serde(untagged)]
74pub enum RequiredSignatures {
75 Quorum { m: u8, n: u8 },
76 RequiredKids { required: Vec<String> },
77}
78
79
80#[derive(Debug)]
82pub enum Error {
83 Io(std::io::Error),
84 Parse(String),
85 Unsupported(&'static str),
86}
87
88impl From<std::io::Error> for Error {
89 fn from(err: std::io::Error) -> Self {
90 Error::Io(err)
91 }
92}
93
94#[derive(Debug)]
96pub enum ValidationError {
97 FipsRequired,
98 InvalidQuorum {
99 m: u8,
100 n: u8,
101 },
102 QuorumUnsatisfied {
103 required_m: u8,
104 total_n: u8,
105 collected: usize,
106 },
107 Level5Requirement(String),
108}
109
110#[derive(Debug, Clone, Copy, PartialEq, Eq)]
112pub enum Format {
113 Json,
114 Yaml,
115}
116
117pub fn load_policy_str(contents: &str, fmt: Option<Format>) -> Result<Policy, Error> {
119 match fmt.unwrap_or(Format::Json) {
120 Format::Json => {
121 #[cfg(feature = "json")]
122 {
123 serde_json::from_str::<Policy>(contents).map_err(|e| Error::Parse(e.to_string()))
124 }
125 #[cfg(not(feature = "json"))]
126 {
127 Err(Error::Unsupported("json feature disabled"))
128 }
129 }
130 Format::Yaml => {
131 #[cfg(feature = "yaml")]
132 {
133 serde_yaml::from_str::<Policy>(contents).map_err(|e| Error::Parse(e.to_string()))
134 }
135 #[cfg(not(feature = "yaml"))]
136 {
137 Err(Error::Unsupported("yaml feature disabled"))
138 }
139 }
140 }
141}
142
143pub fn load_policy_file(path: &Path) -> Result<Policy, Error> {
145 let data = fs::read_to_string(path)?;
146 let fmt = match path.extension().and_then(|s| s.to_str()) {
147 Some("yaml") | Some("yml") => Some(Format::Yaml),
148 _ => Some(Format::Json),
149 };
150 load_policy_str(&data, fmt)
151}
152
153