confium 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +152 -0
- data/Cargo.lock +2634 -0
- data/Cargo.toml +9 -0
- data/README.adoc +114 -14
- data/Rakefile +11 -6
- data/confium.gemspec +50 -29
- data/ext/confium_native/Cargo.toml +63 -0
- data/ext/confium_native/build.rs +72 -0
- data/ext/confium_native/extconf.rb +10 -0
- data/ext/confium_native/src/attributes.rs +88 -0
- data/ext/confium_native/src/audit.rs +169 -0
- data/ext/confium_native/src/composite.rs +302 -0
- data/ext/confium_native/src/deployment.rs +176 -0
- data/ext/confium_native/src/ers.rs +93 -0
- data/ext/confium_native/src/lib.rs +56 -0
- data/ext/confium_native/src/openpgp.rs +55 -0
- data/ext/confium_native/src/path.rs +118 -0
- data/ext/confium_native/src/pki.rs +431 -0
- data/ext/confium_native/src/tc.rs +420 -0
- data/ext/confium_native/src/transparency.rs +343 -0
- data/ext/confium_native/src/util.rs +201 -0
- data/lib/confium/audit.rb +125 -0
- data/lib/confium/cfm.rb +4 -5
- data/lib/confium/crypto.rb +50 -0
- data/lib/confium/digest.rb +11 -9
- data/lib/confium/errors/coerce.rb +47 -0
- data/lib/confium/errors/crypto_error.rb +15 -0
- data/lib/confium/errors/index_error.rb +15 -0
- data/lib/confium/errors/not_found_error.rb +15 -0
- data/lib/confium/errors/parse_error.rb +15 -0
- data/lib/confium/errors/policy_violation_error.rb +15 -0
- data/lib/confium/errors/threshold_error.rb +16 -0
- data/lib/confium/errors/unresolved_signer_error.rb +14 -0
- data/lib/confium/errors/validation_error.rb +17 -0
- data/lib/confium/errors/verification_error.rb +15 -0
- data/lib/confium/errors.rb +26 -0
- data/lib/confium/ffi.rb +23 -0
- data/lib/confium/lib.rb +18 -56
- data/lib/confium/openpgp.rb +34 -0
- data/lib/confium/pki/certificate_builder.rb +60 -0
- data/lib/confium/pki/cms/signed_data_builder.rb +92 -0
- data/lib/confium/pki/cms.rb +15 -0
- data/lib/confium/pki/cnml.rb +80 -0
- data/lib/confium/pki.rb +13 -0
- data/lib/confium/policy.rb +138 -0
- data/lib/confium/secure_bytes.rb +126 -0
- data/lib/confium/tc/coordinator.rb +68 -0
- data/lib/confium/tc/session.rb +51 -0
- data/lib/confium/tc/session_stub.rb +43 -0
- data/lib/confium/tc/share_file.rb +87 -0
- data/lib/confium/tc.rb +17 -0
- data/lib/confium/transparency/ots.rb +63 -0
- data/lib/confium/version.rb +1 -1
- data/lib/confium.rb +50 -20
- metadata +142 -25
- data/CODE_OF_CONDUCT.md +0 -84
- data/Gemfile +0 -10
- data/sig/confium.rbs +0 -4
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
//! Confium::ERS — Evidence Record Syntax (RFC 4998) for long-term archival.
|
|
2
|
+
|
|
3
|
+
use confium_transparency::ers::{
|
|
4
|
+
build_initial_evidence_record, renew_evidence_record, renewal_count,
|
|
5
|
+
EvidenceRecord, HashAlgorithm,
|
|
6
|
+
};
|
|
7
|
+
use magnus::{
|
|
8
|
+
exception, function, method, typed_data::Obj,
|
|
9
|
+
DataTypeFunctions, Error, Module, Object, Ruby, TryConvert, TypedData, Value,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
use crate::util::bytes_from_value;
|
|
13
|
+
|
|
14
|
+
#[derive(TypedData, DataTypeFunctions)]
|
|
15
|
+
#[magnus(class = "Confium::ERS::EvidenceRecord", size)]
|
|
16
|
+
pub struct ErsRecord {
|
|
17
|
+
inner: std::cell::RefCell<EvidenceRecord>,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
impl ErsRecord {
|
|
21
|
+
fn build_initial(args: &[Value]) -> Result<Obj<Self>, Error> {
|
|
22
|
+
let first = args.first().ok_or_else(|| {
|
|
23
|
+
Error::new(exception::arg_error(), "data_hash required")
|
|
24
|
+
})?;
|
|
25
|
+
let data_hash = bytes_from_value(*first)?;
|
|
26
|
+
if data_hash.len() != 32 {
|
|
27
|
+
return Err(Error::new(
|
|
28
|
+
exception::arg_error(),
|
|
29
|
+
format!("data_hash must be 32 bytes, got {}", data_hash.len()),
|
|
30
|
+
));
|
|
31
|
+
}
|
|
32
|
+
let mut hash = [0u8; 32];
|
|
33
|
+
hash.copy_from_slice(&data_hash);
|
|
34
|
+
let tsa_id: String = match args.get(1) {
|
|
35
|
+
Some(v) => TryConvert::try_convert(*v)?,
|
|
36
|
+
None => String::new(),
|
|
37
|
+
};
|
|
38
|
+
let token_bytes = match args.get(2) {
|
|
39
|
+
Some(v) => bytes_from_value(*v)?,
|
|
40
|
+
None => Vec::new(),
|
|
41
|
+
};
|
|
42
|
+
let record =
|
|
43
|
+
build_initial_evidence_record(hash, HashAlgorithm::Sha256, tsa_id, token_bytes);
|
|
44
|
+
let ruby = Ruby::get()
|
|
45
|
+
.map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
|
|
46
|
+
Ok(ruby.obj_wrap(Self {
|
|
47
|
+
inner: std::cell::RefCell::new(record),
|
|
48
|
+
}))
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
fn renew(&self, args: &[Value]) -> Result<Obj<Self>, Error> {
|
|
52
|
+
let first = args.first().ok_or_else(|| {
|
|
53
|
+
Error::new(exception::arg_error(), "new_hash required")
|
|
54
|
+
})?;
|
|
55
|
+
let new_hash_bytes = bytes_from_value(*first)?;
|
|
56
|
+
if new_hash_bytes.len() != 32 {
|
|
57
|
+
return Err(Error::new(
|
|
58
|
+
exception::arg_error(),
|
|
59
|
+
format!("new_hash must be 32 bytes, got {}", new_hash_bytes.len()),
|
|
60
|
+
));
|
|
61
|
+
}
|
|
62
|
+
let mut hash = [0u8; 32];
|
|
63
|
+
hash.copy_from_slice(&new_hash_bytes);
|
|
64
|
+
let tsa_id: String = match args.get(1) {
|
|
65
|
+
Some(v) => TryConvert::try_convert(*v)?,
|
|
66
|
+
None => String::new(),
|
|
67
|
+
};
|
|
68
|
+
let token_bytes = match args.get(2) {
|
|
69
|
+
Some(v) => bytes_from_value(*v)?,
|
|
70
|
+
None => Vec::new(),
|
|
71
|
+
};
|
|
72
|
+
let mut cloned = self.inner.borrow().clone();
|
|
73
|
+
renew_evidence_record(&mut cloned, HashAlgorithm::Sha256, hash, tsa_id, token_bytes);
|
|
74
|
+
let ruby = Ruby::get()
|
|
75
|
+
.map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
|
|
76
|
+
Ok(ruby.obj_wrap(Self {
|
|
77
|
+
inner: std::cell::RefCell::new(cloned),
|
|
78
|
+
}))
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
fn renewal_count(&self) -> u32 {
|
|
82
|
+
renewal_count(&self.inner.borrow())
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
pub fn init(ruby: &Ruby, parent: magnus::RModule) -> Result<(), Error> {
|
|
87
|
+
let ers = parent.define_module("ERS")?;
|
|
88
|
+
let cls = ers.define_class("EvidenceRecord", ruby.class_object())?;
|
|
89
|
+
cls.define_singleton_method("build_initial", function!(ErsRecord::build_initial, -1))?;
|
|
90
|
+
cls.define_method("renew", method!(ErsRecord::renew, -1))?;
|
|
91
|
+
cls.define_method("renewal_count", method!(ErsRecord::renewal_count, 0))?;
|
|
92
|
+
Ok(())
|
|
93
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
//! confium_native — Rust native extension for the `confium` Ruby gem.
|
|
2
|
+
//!
|
|
3
|
+
//! Pattern follows parsanol-ruby: this cdylib is loaded by Ruby via
|
|
4
|
+
//! `rb_sys`, and exposes a `Confium::Native` submodule whose functions
|
|
5
|
+
//! are the Rust-backed implementation of the gem's API.
|
|
6
|
+
|
|
7
|
+
mod audit;
|
|
8
|
+
mod attributes;
|
|
9
|
+
mod composite;
|
|
10
|
+
mod deployment;
|
|
11
|
+
mod ers;
|
|
12
|
+
mod openpgp;
|
|
13
|
+
mod path;
|
|
14
|
+
mod pki;
|
|
15
|
+
mod tc;
|
|
16
|
+
mod transparency;
|
|
17
|
+
mod util;
|
|
18
|
+
|
|
19
|
+
use magnus::{function, Error, Module, Ruby};
|
|
20
|
+
|
|
21
|
+
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
|
22
|
+
|
|
23
|
+
fn native_version() -> &'static str {
|
|
24
|
+
VERSION
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
fn native_loaded() -> bool {
|
|
28
|
+
true
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
fn core_version() -> &'static str {
|
|
32
|
+
// Set by build.rs at compile time from Cargo.lock. Always matches the
|
|
33
|
+
// confium-core crate version the extension was built against.
|
|
34
|
+
env!("CONFIUM_CORE_VERSION")
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
#[magnus::init]
|
|
38
|
+
fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
39
|
+
let confium = ruby.define_module("Confium")?;
|
|
40
|
+
let native = confium.define_module("Native")?;
|
|
41
|
+
native.define_module_function("version", function!(native_version, 0))?;
|
|
42
|
+
native.define_module_function("loaded?", function!(native_loaded, 0))?;
|
|
43
|
+
confium.define_module_function("core_version", function!(core_version, 0))?;
|
|
44
|
+
|
|
45
|
+
transparency::init(ruby, confium)?;
|
|
46
|
+
composite::init(ruby, confium)?;
|
|
47
|
+
attributes::init(ruby, confium)?;
|
|
48
|
+
pki::init(ruby, confium)?;
|
|
49
|
+
path::init(ruby, confium)?;
|
|
50
|
+
deployment::init(ruby, confium)?;
|
|
51
|
+
tc::init(ruby, confium)?;
|
|
52
|
+
audit::init(ruby, confium)?;
|
|
53
|
+
ers::init(ruby, confium)?;
|
|
54
|
+
openpgp::init(ruby, confium)?;
|
|
55
|
+
Ok(())
|
|
56
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
//! Confium::OpenPGP — OpenPGP (RFC 9580) operations via bundled rnp-rs.
|
|
2
|
+
//!
|
|
3
|
+
//! Confium ships RNP functionality baked into the native extension.
|
|
4
|
+
|
|
5
|
+
use magnus::{exception, function, prelude::*, Error, Module, RModule, Ruby, TryConvert, Value};
|
|
6
|
+
use rnp::ops::ArmorType;
|
|
7
|
+
use rnp::{armor_bytes, dearmor_bytes};
|
|
8
|
+
|
|
9
|
+
use crate::util::{bytes_from_value, bytes_to_rstring};
|
|
10
|
+
|
|
11
|
+
/// Native: `Confium::OpenPGP._native_armor(data, type_str)` — 2 args.
|
|
12
|
+
/// The Ruby wrapper `Confium::OpenPGP.armor(data, type = MESSAGE)`
|
|
13
|
+
/// provides the default.
|
|
14
|
+
fn native_armor(ruby: &Ruby, data: Value, type_str: Value) -> Result<magnus::RString, Error> {
|
|
15
|
+
let bytes = bytes_from_value(data)?;
|
|
16
|
+
let ty = if type_str.is_nil() {
|
|
17
|
+
ArmorType::Message
|
|
18
|
+
} else {
|
|
19
|
+
let s: String = TryConvert::try_convert(type_str)?;
|
|
20
|
+
match s.as_str() {
|
|
21
|
+
"public key" => ArmorType::PublicKey,
|
|
22
|
+
"secret key" => ArmorType::SecretKey,
|
|
23
|
+
"signature" => ArmorType::Signature,
|
|
24
|
+
"cleartext signed message" | "cleartext" => ArmorType::Cleartext,
|
|
25
|
+
_ => ArmorType::Message,
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
let armored = armor_bytes(&bytes, ty)
|
|
29
|
+
.map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
|
|
30
|
+
Ok(bytes_to_rstring(ruby, &armored))
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/// Native: `Confium::OpenPGP._native_dearmor(data)` — 1 arg.
|
|
34
|
+
fn native_dearmor(ruby: &Ruby, data: Value) -> Result<magnus::RString, Error> {
|
|
35
|
+
let bytes = bytes_from_value(data)?;
|
|
36
|
+
let raw = dearmor_bytes(&bytes)
|
|
37
|
+
.map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
|
|
38
|
+
Ok(bytes_to_rstring(ruby, &raw))
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/// Initialize the `Confium::OpenPGP` module.
|
|
42
|
+
pub fn init(_ruby: &Ruby, parent: RModule) -> Result<(), Error> {
|
|
43
|
+
let openpgp = parent.define_module("OpenPGP")?;
|
|
44
|
+
|
|
45
|
+
openpgp.define_singleton_method("_native_armor", function!(native_armor, 2))?;
|
|
46
|
+
openpgp.define_singleton_method("_native_dearmor", function!(native_dearmor, 1))?;
|
|
47
|
+
|
|
48
|
+
openpgp.const_set("MESSAGE", "message")?;
|
|
49
|
+
openpgp.const_set("PUBLIC_KEY", "public key")?;
|
|
50
|
+
openpgp.const_set("SECRET_KEY", "secret key")?;
|
|
51
|
+
openpgp.const_set("SIGNATURE", "signature")?;
|
|
52
|
+
openpgp.const_set("CLEARTEXT", "cleartext signed message")?;
|
|
53
|
+
|
|
54
|
+
Ok(())
|
|
55
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
//! Confium::PKI::PathValidator — Ruby surface for `confium_pki::path`.
|
|
2
|
+
//!
|
|
3
|
+
//! Validates a certificate chain from leaf to trusted root. Checks
|
|
4
|
+
//! time validity at each link, basic constraints, and (when a
|
|
5
|
+
//! verifier is available) signature validity.
|
|
6
|
+
|
|
7
|
+
use chrono::Utc;
|
|
8
|
+
use confium_pki::{
|
|
9
|
+
cert::Certificate as RustCert,
|
|
10
|
+
path::{validate_path, CertPath},
|
|
11
|
+
result::VerificationResult as PathVerificationResult,
|
|
12
|
+
};
|
|
13
|
+
use magnus::{exception, function, method, prelude::*, DataTypeFunctions, Error, Module, Ruby, TypedData, Value};
|
|
14
|
+
|
|
15
|
+
/// Wraps a confium_pki::path::VerificationResult.
|
|
16
|
+
#[derive(TypedData, DataTypeFunctions)]
|
|
17
|
+
#[magnus(class = "Confium::PKI::PathValidationResult", size)]
|
|
18
|
+
pub struct PathResult {
|
|
19
|
+
pub inner: PathVerificationResult,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
impl PathResult {
|
|
23
|
+
fn valid(&self) -> bool {
|
|
24
|
+
self.inner.valid
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
fn checks_json(&self) -> String {
|
|
28
|
+
let entries: Vec<String> = self
|
|
29
|
+
.inner
|
|
30
|
+
.checks
|
|
31
|
+
.iter()
|
|
32
|
+
.map(|c| format!("{:?}", c))
|
|
33
|
+
.collect();
|
|
34
|
+
format!("[{}]", entries.join(","))
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
fn check_count(&self) -> usize {
|
|
38
|
+
self.inner.checks.len()
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/// Validate a certificate path (leaf -> intermediates -> root).
|
|
43
|
+
///
|
|
44
|
+
/// Ruby signature:
|
|
45
|
+
/// Confium::PKI::PathValidator.validate(
|
|
46
|
+
/// leaf: cert, intermediates: [cert, ...], root: cert, now: iso8601
|
|
47
|
+
/// )
|
|
48
|
+
///
|
|
49
|
+
/// All cert arguments are Confium::PKI::Certificate instances.
|
|
50
|
+
/// Returns a Confium::PKI::PathValidationResult.
|
|
51
|
+
fn validate(args: &[Value]) -> Result<magnus::typed_data::Obj<PathResult>, Error> {
|
|
52
|
+
use magnus::scan_args;
|
|
53
|
+
let scanned = scan_args::scan_args::<(Value, Value, Value), (Option<String>,), (), (), (), ()>(args)?;
|
|
54
|
+
let leaf_value = scanned.required.0;
|
|
55
|
+
let intermediates_value = scanned.required.1;
|
|
56
|
+
let root_value = scanned.required.2;
|
|
57
|
+
let now_iso8601 = scanned.optional.0;
|
|
58
|
+
|
|
59
|
+
let leaf = extract_cert(leaf_value, "leaf")?;
|
|
60
|
+
let root = extract_cert(root_value, "root")?;
|
|
61
|
+
|
|
62
|
+
let intermediates = if intermediates_value.is_nil() {
|
|
63
|
+
Vec::new()
|
|
64
|
+
} else {
|
|
65
|
+
let arr = magnus::RArray::try_convert(intermediates_value)
|
|
66
|
+
.map_err(|e| Error::new(exception::arg_error(), e.to_string()))?;
|
|
67
|
+
let mut out = Vec::with_capacity(arr.len());
|
|
68
|
+
for v in arr.each() {
|
|
69
|
+
out.push(extract_cert(v?, "intermediate")?);
|
|
70
|
+
}
|
|
71
|
+
out
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
let now = match now_iso8601 {
|
|
75
|
+
Some(s) => chrono::DateTime::parse_from_rfc3339(&s)
|
|
76
|
+
.map_err(|e| Error::new(exception::arg_error(), format!("invalid time: {e}")))?
|
|
77
|
+
.with_timezone(&Utc),
|
|
78
|
+
None => Utc::now(),
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
let path = CertPath {
|
|
82
|
+
leaf: &leaf,
|
|
83
|
+
intermediates: intermediates.iter().collect(),
|
|
84
|
+
root: &root,
|
|
85
|
+
};
|
|
86
|
+
let result = validate_path(&path, now);
|
|
87
|
+
|
|
88
|
+
let ruby = Ruby::get()
|
|
89
|
+
.map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
|
|
90
|
+
Ok(ruby.obj_wrap(PathResult { inner: result }))
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/// Extract a confium_pki::Certificate from a Ruby Confium::PKI::Certificate
|
|
94
|
+
/// instance. The Ruby object wraps an Obj<Certificate> whose inner field
|
|
95
|
+
/// holds the RustCert. We re-parse from DER to avoid lifetime issues.
|
|
96
|
+
fn extract_cert(value: Value, label: &str) -> Result<RustCert, Error> {
|
|
97
|
+
// The Ruby Certificate object exposes #to_der which returns binary bytes.
|
|
98
|
+
// We re-parse those bytes into a fresh RustCert.
|
|
99
|
+
let der_value: Value = value
|
|
100
|
+
.funcall("to_der", ())
|
|
101
|
+
.map_err(|e| Error::new(exception::runtime_error(), format!("{}: cannot get DER: {e}", label)))?;
|
|
102
|
+
let der = crate::util::bytes_from_value(der_value)?;
|
|
103
|
+
RustCert::from_der(&der)
|
|
104
|
+
.map_err(|e| Error::new(exception::runtime_error(), format!("{}: invalid DER: {e}", label)))
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
pub fn init(ruby: &Ruby, parent: magnus::RModule) -> Result<(), Error> {
|
|
108
|
+
let pki = parent.define_module("PKI")?;
|
|
109
|
+
let validator = pki.define_module("PathValidator")?;
|
|
110
|
+
validator.define_module_function("validate", function!(validate, -1))?;
|
|
111
|
+
|
|
112
|
+
let result_class = pki.define_class("PathValidationResult", ruby.class_object())?;
|
|
113
|
+
result_class.define_method("valid?", method!(PathResult::valid, 0))?;
|
|
114
|
+
result_class.define_method("check_count", method!(PathResult::check_count, 0))?;
|
|
115
|
+
result_class.define_method("checks_json", method!(PathResult::checks_json, 0))?;
|
|
116
|
+
|
|
117
|
+
Ok(())
|
|
118
|
+
}
|