1#![cfg_attr(not(feature = "std"), no_std)]
52#![forbid(unsafe_code)]
53#![deny(missing_docs)]
54#![warn(clippy::all)]
55
56#[cfg(feature = "std")]
57extern crate std;
58
59pub mod profile;
60pub mod query;
61pub mod registry;
62pub mod rules;
63pub mod select;
64pub mod standards;
65pub mod types;
66
67#[cfg(feature = "std")]
68pub mod export;
69pub mod frameworks;
70
71pub mod runtime;
72
73pub use query::{all, get, related, Query};
74pub use registry::REGISTRY;
75pub use rules::{Rule, RULES};
76pub use select::{recommend, Intent, NoRecommendation, Policy, Recommendation};
77pub use types::{
78 Class, Constraint, Edge, Entry, FipsStatus, ImplStatus, Param, Performance, Purpose, Relation,
79 Severity, Strength, Unit,
80};
81
82pub const ONTOLOGY_VERSION: &str = "1.0";
87
88pub mod errors {
93 use ic_core::ErrorKind;
94
95 #[derive(Debug, Clone, Copy)]
97 pub struct ErrorDoc {
98 pub id: &'static str,
100 pub meaning: &'static str,
102 pub recovery: &'static str,
104 pub retryable: bool,
106 pub caller_correctable: bool,
108 }
109
110 const KINDS: &[(ErrorKind, &str, &str)] = &[
111 (
112 ErrorKind::InvalidLength,
113 "A buffer was the wrong size for the algorithm's contract.",
114 "Read the parameter bounds from the ontology entry and resize the buffer.",
115 ),
116 (
117 ErrorKind::InvalidParameter,
118 "A key, nonce, or parameter was structurally unacceptable.",
119 "Check the entry's parameters and constraints; do not retry unchanged.",
120 ),
121 (
122 ErrorKind::AuthenticationFailed,
123 "A MAC, AEAD tag, or signature did not verify.",
124 "Treat the data as hostile. Do not use any plaintext produced alongside it, and do not \
125 report which byte differed.",
126 ),
127 (
128 ErrorKind::Unsupported,
129 "The algorithm is described by the ontology but not implemented in this build.",
130 "Query the ontology for an available alternative, or obtain the algorithm elsewhere.",
131 ),
132 (
133 ErrorKind::NotApprovedInFipsMode,
134 "The operation is not permitted while the module is in approved mode.",
135 "Select an approved algorithm, or leave approved mode deliberately and record that \
136 decision.",
137 ),
138 (
139 ErrorKind::SelfTestFailed,
140 "A known-answer test failed; the module has entered its error state.",
141 "Do not retry. The binary is corrupt or the build is broken; stop and investigate.",
142 ),
143 (
144 ErrorKind::ModuleErrorState,
145 "The module is latched in its error state and refuses all service.",
146 "Restart the process. A latched error state is not clearable at runtime by design.",
147 ),
148 (
149 ErrorKind::EntropyFailure,
150 "The entropy source failed or was unavailable.",
151 "Retry; if it persists, seed the DRBG from a source you supply.",
152 ),
153 (
154 ErrorKind::CounterExhausted,
155 "A DRBG reseed interval, GCM invocation limit, or sequence number ran out.",
156 "Reseed or rekey. Continuing past the limit voids the security claim.",
157 ),
158 (
159 ErrorKind::MalformedEncoding,
160 "Input could not be decoded, or was encoded non-canonically.",
161 "Fix the encoding. A non-canonical encoding is often an attack, not a bug.",
162 ),
163 (
164 ErrorKind::ProviderUnavailable,
165 "Whatever holds the key -- a token, a TPM, a key service -- could not be reached or \
166 could not answer. The inputs were not at fault.",
167 "Retry, with a delay and a limit. If it persists, the device or the service needs \
168 attention; changing the inputs will not help.",
169 ),
170 (
171 ErrorKind::ProviderRefused,
172 "Whatever holds the key answered, and refused: a PIN expired or locked, a policy, a \
173 missing permission. The inputs were not at fault.",
174 "Do not retry: a repeated attempt can lock a token. Report it to whoever administers \
175 the key.",
176 ),
177 (
178 ErrorKind::Internal,
179 "A library invariant was violated.",
180 "This is a bug in IronCrypto. Report it with the context string.",
181 ),
182 ];
183
184 pub fn catalog() -> impl Iterator<Item = ErrorDoc> {
186 KINDS.iter().map(|(kind, meaning, recovery)| ErrorDoc {
187 id: kind.id(),
188 meaning,
189 recovery,
190 retryable: kind.retryable(),
191 caller_correctable: kind.caller_correctable(),
192 })
193 }
194
195 pub fn get(id: &str) -> Option<ErrorDoc> {
197 catalog().find(|d| d.id == id)
198 }
199
200 #[cfg(test)]
201 mod tests {
202 use super::*;
203
204 #[test]
216 fn every_error_kind_is_documented() {
217 for kind in ErrorKind::ALL {
218 assert!(
219 get(kind.id()).is_some(),
220 "{} has no catalog entry, so an agent that hits it gets an identifier and no guidance",
221 kind.id()
222 );
223 }
224 assert_eq!(
225 catalog().count(),
226 ErrorKind::ALL.len(),
227 "the catalog and the variant list disagree on how many kinds there are"
228 );
229 }
230
231 #[test]
232 fn catalog_is_complete_and_unique() {
233 let docs: std::vec::Vec<_> = catalog().collect();
234 assert_eq!(docs.len(), KINDS.len());
235 for (i, a) in docs.iter().enumerate() {
236 for b in docs.iter().skip(i + 1) {
237 assert_ne!(a.id, b.id);
238 }
239 assert!(!a.meaning.is_empty());
240 assert!(!a.recovery.is_empty());
241 }
242 }
243
244 #[test]
245 fn authentication_failure_is_not_retryable_or_correctable() {
246 let d = get("authentication-failed").unwrap();
247 assert!(!d.retryable);
248 assert!(!d.caller_correctable);
249 }
250
251 #[test]
252 fn what_is_retryable_is_what_may_pass_next_time() {
253 assert!(get("entropy-failure").unwrap().retryable);
254 assert!(get("provider-unavailable").unwrap().retryable);
255 assert!(!get("provider-refused").unwrap().retryable);
257 assert!(!get("internal").unwrap().retryable);
258 }
259 }
260}
261
262#[cfg(test)]
263mod tests {
264 use super::*;
265
266 #[test]
267 fn every_available_entry_names_a_real_module_path() {
268 let prefixes = [
270 "ic_hash::",
271 "ic_mac::",
272 "ic_cipher::",
273 "ic_kdf::",
274 "ic_drbg::",
275 "ic_ec::",
276 "ic_rsa::",
277 "ic_mlkem::",
278 "ic_mldsa::",
279 "ic_hpke::",
280 "ic_lms::",
281 "ic_slhdsa::",
282 ];
283 for e in REGISTRY
284 .iter()
285 .filter(|e| matches!(e.status, ImplStatus::Available | ImplStatus::Experimental))
286 {
287 assert!(
288 prefixes.iter().any(|p| e.rust_path.starts_with(p)),
289 "{} points at {}, which is not a workspace crate",
290 e.id,
291 e.rust_path
292 );
293 }
294 }
295
296 #[test]
297 fn ontology_version_is_set() {
298 assert!(!ONTOLOGY_VERSION.is_empty());
299 }
300}