1#![crate_type = "staticlib"]
2#![allow(non_camel_case_types)]
3
4use core::ffi::c_char;
5use std::ffi::CStr;
6use std::panic::catch_unwind;
7use std::path::Path;
8use std::slice;
9
10use rustls_pki_types::CertificateDer;
11use upki::revocation::{self, Manifest, RevocationCheckInput, RevocationStatus};
12use upki::{Config, Error};
13
14#[unsafe(no_mangle)]
27pub unsafe extern "C" fn upki_check_revocation(
28 config: *const upki_config,
29 certificates: *const upki_certificate_der,
30 certificates_len: usize,
31) -> upki_result {
32 catch_unwind(|| {
33 if config.is_null() || certificates.is_null() {
34 return upki_result::UPKI_ERR_NULL_POINTER;
35 }
36
37 let config = unsafe { &(*config).0 };
38 let certificates = unsafe { slice::from_raw_parts(certificates, certificates_len) };
39
40 let certs = certificates
41 .iter()
42 .map(|c| CertificateDer::from(unsafe { slice::from_raw_parts(c.data, c.len) }))
43 .collect::<Vec<_>>();
44
45 let input = match RevocationCheckInput::from_certificates(&certs) {
46 Ok(input) => input,
47 Err(err) => return Error::Revocation(err).into(),
48 };
49
50 let manifest = match Manifest::from_config(config) {
51 Ok(manifest) => manifest,
52 Err(err) => return Error::Revocation(err).into(),
53 };
54
55 match manifest.check(&input, config) {
56 Ok(status) => match status {
57 RevocationStatus::NotCoveredByRevocationData => {
58 upki_result::UPKI_REVOCATION_NOT_COVERED
59 }
60 RevocationStatus::CertainlyRevoked => upki_result::UPKI_REVOCATION_REVOKED,
61 RevocationStatus::NotRevoked => upki_result::UPKI_REVOCATION_NOT_REVOKED,
62 },
63 Err(err) => Error::Revocation(err).into(),
64 }
65 })
66 .unwrap_or(upki_result::UPKI_ERR_PANICKED)
67}
68
69pub struct upki_config(Config);
71
72#[unsafe(no_mangle)]
82pub unsafe extern "C" fn upki_config_from_file(
83 path: *const c_char,
84 out: *mut *mut upki_config,
85) -> upki_result {
86 catch_unwind(|| {
87 if path.is_null() || out.is_null() {
88 return upki_result::UPKI_ERR_NULL_POINTER;
89 }
90
91 let path = unsafe { CStr::from_ptr(path) };
92 let Ok(path) = path.to_str() else {
93 return upki_result::UPKI_ERR_CONFIG_PATH;
94 };
95
96 match Config::from_file(Path::new(path)) {
97 Ok(config) => {
98 unsafe { *out = Box::into_raw(Box::new(upki_config(config))) };
99 upki_result::UPKI_OK
100 }
101 Err(err) => err.into(),
102 }
103 })
104 .unwrap_or(upki_result::UPKI_ERR_PANICKED)
105}
106
107#[unsafe(no_mangle)]
116pub unsafe extern "C" fn upki_config_new(out: *mut *mut upki_config) -> upki_result {
117 catch_unwind(|| {
118 if out.is_null() {
119 return upki_result::UPKI_ERR_NULL_POINTER;
120 }
121
122 match Config::try_default() {
123 Ok(config) => {
124 unsafe { *out = Box::into_raw(Box::new(upki_config(config))) };
125 upki_result::UPKI_OK
126 }
127 Err(err) => err.into(),
128 }
129 })
130 .unwrap_or(upki_result::UPKI_ERR_PANICKED)
131}
132
133#[unsafe(no_mangle)]
140pub unsafe extern "C" fn upki_config_free(config: *mut upki_config) {
141 if !config.is_null() {
142 drop(unsafe { Box::from_raw(config) });
143 }
144}
145
146#[repr(C)]
148pub struct upki_certificate_der {
149 pub data: *const u8,
151 pub len: usize,
153}
154
155#[repr(C)]
160pub enum upki_result {
161 UPKI_OK = 0,
163 UPKI_REVOCATION_NOT_COVERED = 1,
165 UPKI_REVOCATION_REVOKED = 2,
167 UPKI_REVOCATION_NOT_REVOKED = 3,
169
170 UPKI_ERR_NULL_POINTER = 16,
172 UPKI_ERR_CONFIG_PATH = 17,
174 UPKI_ERR_UNKNOWN = 18,
176 UPKI_ERR_PANICKED = 19,
178
179 UPKI_ERR_CONFIG_DECODE = 32,
182 UPKI_ERR_CONFIG_READ = 33,
184 UPKI_ERR_NO_CACHE_DIR = 34,
186 UPKI_ERR_NO_CONFIG_DIR = 35,
188 UPKI_ERR_NO_HOME_DIR = 36,
190
191 UPKI_ERR_REVOCATION_CREATE_DIR = 64,
194 UPKI_ERR_REVOCATION_FILE_WRITE = 65,
196 UPKI_ERR_REVOCATION_FILTER_DECODE = 66,
198 UPKI_ERR_REVOCATION_FILTER_READ = 67,
200 UPKI_ERR_REVOCATION_HASH_MISMATCH = 68,
202 UPKI_ERR_REVOCATION_HTTP_FETCH = 69,
204 UPKI_ERR_REVOCATION_INVALID_BASE64 = 70,
206 UPKI_ERR_REVOCATION_INVALID_END_ENTITY_CERT = 71,
208 UPKI_ERR_REVOCATION_INVALID_INTERMEDIATE_CERT = 72,
210 UPKI_ERR_REVOCATION_INVALID_LENGTH = 73,
212 UPKI_ERR_REVOCATION_INVALID_SCT_ENCODING = 74,
214 UPKI_ERR_REVOCATION_INVALID_SCT_IN_CERT = 75,
216 UPKI_ERR_REVOCATION_INVALID_TIMESTAMP = 76,
218 UPKI_ERR_REVOCATION_MANIFEST_DECODE = 77,
220 UPKI_ERR_REVOCATION_MANIFEST_ENCODE = 78,
222 UPKI_ERR_REVOCATION_MANIFEST_READ = 79,
224 UPKI_ERR_REVOCATION_MANIFEST_WRITE = 80,
226 UPKI_ERR_REVOCATION_NO_ISSUER = 81,
228 UPKI_ERR_REVOCATION_OUTDATED = 82,
230 UPKI_ERR_REVOCATION_REMOVE_FILE = 83,
232 UPKI_ERR_REVOCATION_TOO_FEW_CERTS = 84,
234}
235
236impl From<Error> for upki_result {
237 fn from(err: Error) -> Self {
238 match err {
239 Error::ConfigError { .. } => Self::UPKI_ERR_CONFIG_DECODE,
240 Error::FileRead { .. } => Self::UPKI_ERR_CONFIG_READ,
241 Error::NoCacheDirectoryFound => Self::UPKI_ERR_NO_CACHE_DIR,
242 Error::NoConfigDirectoryFound => Self::UPKI_ERR_NO_CONFIG_DIR,
243 Error::NoValidHomeDirectory => Self::UPKI_ERR_NO_HOME_DIR,
244 Error::Revocation(revocation::Error::CreateDirectory { .. }) => {
245 Self::UPKI_ERR_REVOCATION_CREATE_DIR
246 }
247 Error::Revocation(revocation::Error::FileWrite { .. }) => {
248 Self::UPKI_ERR_REVOCATION_FILE_WRITE
249 }
250 Error::Revocation(revocation::Error::FilterDecode { .. }) => {
251 Self::UPKI_ERR_REVOCATION_FILTER_DECODE
252 }
253 Error::Revocation(revocation::Error::FilterRead { .. }) => {
254 Self::UPKI_ERR_REVOCATION_FILTER_READ
255 }
256 Error::Revocation(revocation::Error::HashMismatch(_)) => {
257 Self::UPKI_ERR_REVOCATION_HASH_MISMATCH
258 }
259 Error::Revocation(revocation::Error::HttpFetch { .. }) => {
260 Self::UPKI_ERR_REVOCATION_HTTP_FETCH
261 }
262 Error::Revocation(revocation::Error::InvalidBase64 { .. }) => {
263 Self::UPKI_ERR_REVOCATION_INVALID_BASE64
264 }
265 Error::Revocation(revocation::Error::InvalidEndEntityCertificate(_)) => {
266 Self::UPKI_ERR_REVOCATION_INVALID_END_ENTITY_CERT
267 }
268 Error::Revocation(revocation::Error::InvalidIntermediateCertificate { .. }) => {
269 Self::UPKI_ERR_REVOCATION_INVALID_INTERMEDIATE_CERT
270 }
271 Error::Revocation(revocation::Error::InvalidLength { .. }) => {
272 Self::UPKI_ERR_REVOCATION_INVALID_LENGTH
273 }
274 Error::Revocation(revocation::Error::InvalidSctEncoding) => {
275 Self::UPKI_ERR_REVOCATION_INVALID_SCT_ENCODING
276 }
277 Error::Revocation(revocation::Error::InvalidSctInCertificate(_)) => {
278 Self::UPKI_ERR_REVOCATION_INVALID_SCT_IN_CERT
279 }
280 Error::Revocation(revocation::Error::InvalidTimestamp { .. }) => {
281 Self::UPKI_ERR_REVOCATION_INVALID_TIMESTAMP
282 }
283 Error::Revocation(revocation::Error::ManifestDecode { .. }) => {
284 Self::UPKI_ERR_REVOCATION_MANIFEST_DECODE
285 }
286 Error::Revocation(revocation::Error::ManifestEncode { .. }) => {
287 Self::UPKI_ERR_REVOCATION_MANIFEST_ENCODE
288 }
289 Error::Revocation(revocation::Error::ManifestRead { .. }) => {
290 Self::UPKI_ERR_REVOCATION_MANIFEST_READ
291 }
292 Error::Revocation(revocation::Error::ManifestWrite { .. }) => {
293 Self::UPKI_ERR_REVOCATION_MANIFEST_WRITE
294 }
295 Error::Revocation(revocation::Error::NoIssuer) => Self::UPKI_ERR_REVOCATION_NO_ISSUER,
296 Error::Revocation(revocation::Error::Outdated(_)) => Self::UPKI_ERR_REVOCATION_OUTDATED,
297 Error::Revocation(revocation::Error::RemoveFile { .. }) => {
298 Self::UPKI_ERR_REVOCATION_REMOVE_FILE
299 }
300 Error::Revocation(revocation::Error::TooFewCertificates) => {
301 Self::UPKI_ERR_REVOCATION_TOO_FEW_CERTS
302 }
303 _ => Self::UPKI_ERR_UNKNOWN,
304 }
305 }
306}