confium 0.2.0 → 0.3.0

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.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +126 -0
  3. data/Cargo.lock +2534 -0
  4. data/Cargo.toml +9 -0
  5. data/README.adoc +114 -14
  6. data/Rakefile +8 -3
  7. data/confium.gemspec +42 -21
  8. data/ext/confium_native/Cargo.toml +62 -0
  9. data/ext/confium_native/build.rs +72 -0
  10. data/ext/confium_native/extconf.rb +10 -0
  11. data/ext/confium_native/src/attributes.rs +88 -0
  12. data/ext/confium_native/src/audit.rs +169 -0
  13. data/ext/confium_native/src/composite.rs +302 -0
  14. data/ext/confium_native/src/deployment.rs +176 -0
  15. data/ext/confium_native/src/ers.rs +93 -0
  16. data/ext/confium_native/src/lib.rs +56 -0
  17. data/ext/confium_native/src/openpgp.rs +55 -0
  18. data/ext/confium_native/src/path.rs +118 -0
  19. data/ext/confium_native/src/pki.rs +431 -0
  20. data/ext/confium_native/src/tc.rs +420 -0
  21. data/ext/confium_native/src/transparency.rs +341 -0
  22. data/ext/confium_native/src/util.rs +196 -0
  23. data/lib/confium/audit.rb +125 -0
  24. data/lib/confium/cfm.rb +2 -4
  25. data/lib/confium/crypto.rb +50 -0
  26. data/lib/confium/digest.rb +7 -7
  27. data/lib/confium/errors/coerce.rb +49 -0
  28. data/lib/confium/errors/crypto_error.rb +13 -0
  29. data/lib/confium/errors/index_error.rb +13 -0
  30. data/lib/confium/errors/not_found_error.rb +13 -0
  31. data/lib/confium/errors/parse_error.rb +13 -0
  32. data/lib/confium/errors/policy_violation_error.rb +13 -0
  33. data/lib/confium/errors/threshold_error.rb +14 -0
  34. data/lib/confium/errors/unresolved_signer_error.rb +12 -0
  35. data/lib/confium/errors/validation_error.rb +15 -0
  36. data/lib/confium/errors/verification_error.rb +13 -0
  37. data/lib/confium/errors.rb +26 -0
  38. data/lib/confium/ffi.rb +23 -0
  39. data/lib/confium/lib.rb +2 -39
  40. data/lib/confium/openpgp.rb +34 -0
  41. data/lib/confium/pki/certificate_builder.rb +60 -0
  42. data/lib/confium/pki/cms/signed_data_builder.rb +92 -0
  43. data/lib/confium/pki/cms.rb +15 -0
  44. data/lib/confium/pki/cnml.rb +80 -0
  45. data/lib/confium/pki.rb +13 -0
  46. data/lib/confium/policy.rb +138 -0
  47. data/lib/confium/secure_bytes.rb +124 -0
  48. data/lib/confium/tc/coordinator.rb +67 -0
  49. data/lib/confium/tc/session.rb +49 -0
  50. data/lib/confium/tc/session_stub.rb +43 -0
  51. data/lib/confium/tc/share_file.rb +87 -0
  52. data/lib/confium/tc.rb +17 -0
  53. data/lib/confium/transparency/ots.rb +63 -0
  54. data/lib/confium/version.rb +1 -1
  55. data/lib/confium.rb +50 -20
  56. metadata +142 -25
  57. data/CODE_OF_CONDUCT.md +0 -84
  58. data/Gemfile +0 -10
  59. data/sig/confium.rbs +0 -4
@@ -0,0 +1,302 @@
1
+ //! Confium::Composite — Ruby surface for `confium_composite`.
2
+ //!
3
+ //! Exposes PQ-migration composite signatures (Ed25519 + ML-DSA-65 etc.).
4
+ //!
5
+ //! - `Confium::Composite::Signature` — a composite signature with multiple
6
+ //! algorithm components over the same message. Wraps
7
+ //! `confium_composite::CompositeSignature`.
8
+ //! - `Confium::Composite.sign_ed25519(private_key_bytes, message)` —
9
+ //! helper that builds a real Ed25519 component using
10
+ //! `ed25519_dalek::SigningKey`.
11
+ //! - `Confium::Composite::VerificationResult` — result of `#verify(message)`,
12
+ //! with `#all_verified?` and `#per_component` accessors.
13
+
14
+ use confium_composite::{CompositeSignature, ComponentSignature, VerificationResult};
15
+ use ed25519_dalek::{Signer, SigningKey};
16
+ use magnus::{
17
+ exception, function, method, prelude::*, scan_args,
18
+ typed_data::Obj, DataTypeFunctions, Error, IntoValue,
19
+ Module, Object, RHash, Ruby, TryConvert, TypedData, Value,
20
+ };
21
+ use crate::util::{bytes_from_value, bytes_to_rstring, confium_error, new_details};
22
+ use rand_core::OsRng;
23
+
24
+ #[derive(TypedData, DataTypeFunctions)]
25
+ #[magnus(class = "Confium::Composite::Signature", size)]
26
+ pub struct CompositeSig {
27
+ inner: std::cell::RefCell<CompositeSignature>,
28
+ }
29
+
30
+ impl CompositeSig {
31
+ fn new(components: Value) -> Result<Self, Error> {
32
+ let comps = parse_components(components)?;
33
+ Ok(Self {
34
+ inner: std::cell::RefCell::new(CompositeSignature::new(comps)),
35
+ })
36
+ }
37
+
38
+ fn component_count(&self) -> usize {
39
+ self.inner.borrow().component_count()
40
+ }
41
+
42
+ fn algorithms(&self) -> Vec<String> {
43
+ self.inner
44
+ .borrow()
45
+ .algorithms()
46
+ .into_iter()
47
+ .map(String::from)
48
+ .collect()
49
+ }
50
+
51
+ /// Verify all components against `message`. Built-in verifiers cover
52
+ /// Ed25519 + ECDSA-P256. Caller-supplied verifiers (a Ruby Hash
53
+ /// `algorithm_string -> Proc(public_key, message, signature)`) plug
54
+ /// in for any other algorithm. Returns a typed VerificationResult.
55
+ fn verify(&self, args: &[Value]) -> Result<Obj<VerificationResultWrap>, Error> {
56
+ let scanned = scan_args::scan_args::<(Value,), (Option<Value>,), (), (), (), ()>(args)?;
57
+ let message = scanned.required.0;
58
+ let verifiers_value = scanned.optional.0;
59
+ let msg = bytes_from_value(message)?;
60
+ let caller_verifiers = match verifiers_value {
61
+ Some(v) => parse_caller_verifiers(v)?,
62
+ None => std::collections::HashMap::new(),
63
+ }; let result = self
64
+ .inner
65
+ .borrow()
66
+ .verify(&msg, |algorithm, public_key, m, signature| {
67
+ if algorithm == confium_composite::ED25519 {
68
+ confium_composite::ed25519_verifier(algorithm, public_key, m, signature)
69
+ } else if algorithm == confium_composite::ECDSA_P256 || algorithm == "ECDSA" {
70
+ confium_composite::p256_verifier(algorithm, public_key, m, signature)
71
+ } else if let Some(callback) = caller_verifiers.get(algorithm) {
72
+ invoke_caller_verifier(callback, public_key, m, signature)
73
+ } else {
74
+ Err(format!("unsupported algorithm: {algorithm}"))
75
+ }
76
+ })
77
+ .map_err(|e| {
78
+ let ruby = Ruby::get().expect("Ruby must be available");
79
+ let details = new_details(&ruby);
80
+ confium_error(e.to_string(), "VerificationError", details)
81
+ })?;
82
+ let ruby = Ruby::get().map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
83
+ Ok(ruby.obj_wrap(VerificationResultWrap { inner: result }))
84
+ }
85
+ }
86
+
87
+ /// Caller-supplied verifier callbacks keyed by algorithm string.
88
+ /// Each value is a Ruby Proc taking (public_key_bytes, message_bytes,
89
+ /// signature_bytes) and returning true/false.
90
+ type CallerVerifiers = std::collections::HashMap<String, magnus::Value>;
91
+
92
+ fn parse_caller_verifiers(v: Value) -> Result<CallerVerifiers, Error> {
93
+ let hash: RHash = RHash::try_convert(v)?;
94
+ let mut out = CallerVerifiers::new();
95
+ hash.foreach(|k: Value, val: Value| {
96
+ let key: String = String::try_convert(k)?;
97
+ out.insert(key, val);
98
+ Ok(magnus::r_hash::ForEach::Continue)
99
+ })?;
100
+ Ok(out)
101
+ }
102
+
103
+ fn invoke_caller_verifier(
104
+ callback: &magnus::Value,
105
+ public_key: &[u8],
106
+ message: &[u8],
107
+ signature: &[u8],
108
+ ) -> Result<(), String> {
109
+ let ruby = Ruby::get().map_err(|e| e.to_string())?;
110
+ let pk = ruby.str_new(std::str::from_utf8(public_key).unwrap_or(""));
111
+ let _ = pk;
112
+ // Use Vec<u8> arguments — magnus converts them to Ruby Array<Integer>.
113
+ let result: magnus::Value = callback
114
+ .funcall("call", (public_key.to_vec(), message.to_vec(), signature.to_vec()))
115
+ .map_err(|e| format!("caller verifier raised: {e}"))?;
116
+ let ok: bool = bool::try_convert(result).map_err(|e| format!("caller verifier returned non-bool: {e}"))?;
117
+ if ok {
118
+ Ok(())
119
+ } else {
120
+ Err("caller verifier returned false".into())
121
+ }
122
+ }
123
+
124
+ #[derive(TypedData, DataTypeFunctions)]
125
+ #[magnus(class = "Confium::Composite::VerificationResult", size)]
126
+ pub struct VerificationResultWrap {
127
+ inner: VerificationResult,
128
+ }
129
+
130
+ impl VerificationResultWrap {
131
+ fn all_verified(&self) -> bool {
132
+ self.inner.all_verified
133
+ }
134
+
135
+ fn per_component(&self) -> Result<Value, Error> {
136
+ let ruby = Ruby::get().map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
137
+ let result = ruby.hash_new();
138
+ for c in &self.inner.per_component {
139
+ let entry = ruby.hash_new();
140
+ entry.aset("algorithm", c.algorithm.clone())?;
141
+ entry.aset("verified", c.verified)?;
142
+ if let Some(err) = &c.error {
143
+ entry.aset("error", err.clone())?;
144
+ }
145
+ result.aset(c.index, entry)?;
146
+ }
147
+ Ok(result.into_value_with(&ruby))
148
+ }
149
+ }
150
+
151
+ /// Build a real Ed25519 component signature.
152
+ ///
153
+ /// `Confium::Composite.sign_ed25519(private_key_bytes, message)` returns
154
+ /// a Hash with `algorithm`, `public_key`, `signature` keys.
155
+ fn sign_ed25519(ruby: &Ruby, private_key: Value, message: Value) -> Result<RHash, Error> {
156
+ let pk_bytes = bytes_from_value(private_key)?;
157
+ let msg = bytes_from_value(message)?;
158
+ if pk_bytes.len() != 32 {
159
+ return Err(Error::new(
160
+ exception::arg_error(),
161
+ format!("Ed25519 private key must be 32 bytes, got {}", pk_bytes.len()),
162
+ ));
163
+ }
164
+ let mut pk_arr = [0u8; 32];
165
+ pk_arr.copy_from_slice(&pk_bytes);
166
+ let signing = SigningKey::from_bytes(&pk_arr);
167
+ let component = match confium_composite::build_ed25519_component(&signing, &msg) {
168
+ Ok(c) => c,
169
+ Err(e) => {
170
+ crate::audit::fire_event(
171
+ "composite_sign_ed25519",
172
+ "failure",
173
+ Some("Ed25519"),
174
+ Some(&msg),
175
+ Some(&e.to_string()),
176
+ );
177
+ return Err(Error::new(exception::runtime_error(), e.to_string()));
178
+ }
179
+ };
180
+
181
+ crate::audit::fire_event(
182
+ "composite_sign_ed25519",
183
+ "success",
184
+ Some("Ed25519"),
185
+ Some(&msg),
186
+ None,
187
+ );
188
+
189
+ let result = ruby.hash_new();
190
+ result.aset("algorithm", component.algorithm)?;
191
+ result.aset("public_key", bytes_to_rstring(ruby, &component.public_key))?;
192
+ result.aset("signature", bytes_to_rstring(ruby, &component.signature))?;
193
+ Ok(result)
194
+ }
195
+
196
+ /// Generate a fresh Ed25519 keypair.
197
+ ///
198
+ /// `Confium::Composite.generate_ed25519_keypair` returns `[private_key, public_key]`
199
+ /// as binary strings (32 bytes each).
200
+ /// Build a real ECDSA-P256 component signature. Useful when the caller
201
+ /// holds a P-256 signing key and wants to participate in a composite
202
+ /// signature alongside e.g. Ed25519 or ML-DSA-65.
203
+ fn sign_p256(ruby: &Ruby, private_key: Value, message: Value) -> Result<RHash, Error> {
204
+ let pk_bytes = bytes_from_value(private_key)?;
205
+ let msg = bytes_from_value(message)?;
206
+ if pk_bytes.len() != 32 {
207
+ return Err(Error::new(
208
+ exception::arg_error(),
209
+ format!("P-256 private key must be 32 bytes, got {}", pk_bytes.len()),
210
+ ));
211
+ }
212
+ let mut arr = [0u8; 32];
213
+ arr.copy_from_slice(&pk_bytes);
214
+ use p256::ecdsa::{Signature, SigningKey, signature::Signer};
215
+ let signing = SigningKey::from_bytes(&arr.into())
216
+ .map_err(|e| Error::new(exception::arg_error(), format!("invalid P-256 private key: {e}")))?;
217
+ let sig: Signature = match signing.try_sign(msg.as_slice()) {
218
+ Ok(s) => s,
219
+ Err(e) => {
220
+ crate::audit::fire_event(
221
+ "composite_sign_p256",
222
+ "failure",
223
+ Some("ECDSA-P256"),
224
+ Some(&msg),
225
+ Some(&format!("sign error: {e}")),
226
+ );
227
+ return Err(Error::new(exception::runtime_error(), format!("sign error: {e}")));
228
+ }
229
+ };
230
+ let verifying = signing.verifying_key();
231
+ let verifying_bytes: Vec<u8> = verifying.to_sec1_bytes().to_vec();
232
+ let sig_bytes: Vec<u8> = sig.to_der().to_bytes().to_vec();
233
+
234
+ crate::audit::fire_event(
235
+ "composite_sign_p256",
236
+ "success",
237
+ Some("ECDSA-P256"),
238
+ Some(&msg),
239
+ None,
240
+ );
241
+
242
+ let result = ruby.hash_new();
243
+ result.aset("algorithm", "ECDSA-P256")?;
244
+ result.aset("public_key", bytes_to_rstring(ruby, &verifying_bytes))?;
245
+ result.aset("signature", bytes_to_rstring(ruby, &sig_bytes))?;
246
+ Ok(result)
247
+ }
248
+
249
+ fn generate_ed25519_keypair(ruby: &Ruby) -> Result<RHash, Error> {
250
+ let mut rng = OsRng;
251
+ let signing = SigningKey::generate(&mut rng);
252
+ let verifying = signing.verifying_key();
253
+ let result = ruby.hash_new();
254
+ result.aset("private_key", bytes_to_rstring(ruby, &signing.to_bytes()))?;
255
+ result.aset("public_key", bytes_to_rstring(ruby, &verifying.to_bytes()))?;
256
+ Ok(result)
257
+ }
258
+
259
+ fn parse_components(value: Value) -> Result<Vec<ComponentSignature>, Error> {
260
+ let arr = magnus::RArray::try_convert(value)?;
261
+ let mut out = Vec::with_capacity(arr.len());
262
+ for v in arr.each() {
263
+ let h: RHash = RHash::try_convert(v?)?;
264
+ let algorithm: String = h.fetch::<_, String>("algorithm")?;
265
+ let public_key: Value = h.fetch::<_, Value>("public_key")?;
266
+ let signature: Value = h.fetch::<_, Value>("signature")?;
267
+ out.push(ComponentSignature {
268
+ algorithm,
269
+ public_key: bytes_from_value(public_key)?,
270
+ signature: bytes_from_value(signature)?,
271
+ });
272
+ }
273
+ Ok(out)
274
+ }
275
+
276
+ pub fn init(ruby: &Ruby, parent: magnus::RModule) -> Result<(), Error> {
277
+ let composite = parent.define_module("Composite")?;
278
+ composite.define_module_function(
279
+ "sign_ed25519",
280
+ function!(sign_ed25519, 2),
281
+ )?;
282
+ composite.define_module_function(
283
+ "sign_p256",
284
+ function!(sign_p256, 2),
285
+ )?;
286
+ composite.define_module_function(
287
+ "generate_ed25519_keypair",
288
+ function!(generate_ed25519_keypair, 0),
289
+ )?;
290
+
291
+ let sig_class = composite.define_class("Signature", ruby.class_object())?;
292
+ sig_class.define_singleton_method("new", function!(CompositeSig::new, 1))?;
293
+ sig_class.define_method("component_count", method!(CompositeSig::component_count, 0))?;
294
+ sig_class.define_method("algorithms", method!(CompositeSig::algorithms, 0))?;
295
+ sig_class.define_method("verify", method!(CompositeSig::verify, -1))?;
296
+
297
+ let result_class = composite.define_class("VerificationResult", ruby.class_object())?;
298
+ result_class.define_method("all_verified?", method!(VerificationResultWrap::all_verified, 0))?;
299
+ result_class.define_method("per_component", method!(VerificationResultWrap::per_component, 0))?;
300
+
301
+ Ok(())
302
+ }
@@ -0,0 +1,176 @@
1
+ //! Confium::Identity + Confium::Config — Ruby surface for the deployment
2
+ //! crate's identity + manifest types.
3
+ //!
4
+ //! Phase 1C-2 scope:
5
+ //! - `Confium::Identity::Actor` — wraps `confium_deployment::identity::ActorIdentity`.
6
+ //! - `Confium::Identity::ACTOR_TYPES` — array of valid actor-type strings.
7
+ //! - `Confium::Config::Manifest` — parse + validate deployment manifest TOML.
8
+
9
+ use confium_deployment::{
10
+ identity::{ActorIdentity, ActorType},
11
+ manifest::{parse_manifest, Manifest as RustManifest},
12
+ validate::validate_manifest,
13
+ };
14
+ use crate::util::enforce_size;
15
+ use magnus::{exception, function, method, prelude::*, typed_data::Obj, DataTypeFunctions, Error, Module, Object, Ruby, TypedData};
16
+
17
+ #[derive(TypedData, DataTypeFunctions)]
18
+ #[magnus(class = "Confium::Identity::Actor", size)]
19
+ pub struct Actor {
20
+ pub inner: std::cell::RefCell<ActorIdentity>,
21
+ }
22
+
23
+ impl Actor {
24
+ fn from_json(json: String) -> Result<Obj<Self>, Error> {
25
+ enforce_size(json.len())?;
26
+ let actor: ActorIdentity = serde_json::from_str(&json)
27
+ .map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
28
+ let ruby = Ruby::get().map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
29
+ Ok(ruby.obj_wrap(Self {
30
+ inner: std::cell::RefCell::new(actor),
31
+ }))
32
+ }
33
+
34
+ fn to_json(&self) -> Result<String, Error> {
35
+ serde_json::to_string(&*self.inner.borrow())
36
+ .map_err(|e| Error::new(exception::runtime_error(), e.to_string()))
37
+ }
38
+
39
+ fn actor_id(&self) -> String {
40
+ self.inner.borrow().actor_id.clone()
41
+ }
42
+
43
+ fn actor_type(&self) -> String {
44
+ actor_type_str(self.inner.borrow().actor_type).to_string()
45
+ }
46
+
47
+ fn quorum_id(&self) -> Option<String> {
48
+ self.inner.borrow().quorum_id.clone()
49
+ }
50
+
51
+ fn registered_at_iso8601(&self) -> String {
52
+ self.inner.borrow().registered_at.to_rfc3339()
53
+ }
54
+
55
+ fn expires_at_iso8601(&self) -> Option<String> {
56
+ self.inner.borrow().expires_at.map(|t| t.to_rfc3339())
57
+ }
58
+
59
+ fn certificate_count(&self) -> usize {
60
+ self.inner.borrow().certificate_chain_der.len()
61
+ }
62
+ }
63
+
64
+ fn actor_type_str(t: ActorType) -> &'static str {
65
+ match t {
66
+ ActorType::Manufacturer => "manufacturer",
67
+ ActorType::TestingLab => "testing_lab",
68
+ ActorType::IssuingAuthorityOfficer => "issuing_authority_officer",
69
+ ActorType::BimlDirector => "biml_director",
70
+ ActorType::QuorumCoordinator => "quorum_coordinator",
71
+ ActorType::Verifier => "verifier",
72
+ }
73
+ }
74
+
75
+ fn actor_types() -> Vec<&'static str> {
76
+ vec![
77
+ actor_type_str(ActorType::Manufacturer),
78
+ actor_type_str(ActorType::TestingLab),
79
+ actor_type_str(ActorType::IssuingAuthorityOfficer),
80
+ actor_type_str(ActorType::BimlDirector),
81
+ actor_type_str(ActorType::QuorumCoordinator),
82
+ actor_type_str(ActorType::Verifier),
83
+ ]
84
+ }
85
+
86
+ #[derive(TypedData, DataTypeFunctions)]
87
+ #[magnus(class = "Confium::Config::Manifest", size)]
88
+ pub struct Manifest {
89
+ pub inner: std::cell::RefCell<RustManifest>,
90
+ }
91
+
92
+ impl Manifest {
93
+ fn from_toml(toml_str: String) -> Result<Obj<Self>, Error> {
94
+ enforce_size(toml_str.len())?;
95
+ let manifest = parse_manifest(&toml_str)
96
+ .map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
97
+ let ruby = Ruby::get().map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
98
+ Ok(ruby.obj_wrap(Self {
99
+ inner: std::cell::RefCell::new(manifest),
100
+ }))
101
+ }
102
+
103
+ fn deployment_name(&self) -> String {
104
+ self.inner.borrow().deployment.name.clone()
105
+ }
106
+
107
+ fn operator(&self) -> String {
108
+ self.inner.borrow().deployment.operator.clone()
109
+ }
110
+
111
+ fn manifest_version(&self) -> u32 {
112
+ self.inner.borrow().deployment.manifest_version
113
+ }
114
+
115
+ fn tier_count(&self) -> usize {
116
+ self.inner.borrow().tiers.len()
117
+ }
118
+
119
+ fn tier_name_at(&self, index: usize) -> Result<String, Error> {
120
+ self.inner
121
+ .borrow()
122
+ .tiers
123
+ .get(index)
124
+ .map(|t| t.name.clone())
125
+ .ok_or_else(|| {
126
+ Error::new(
127
+ exception::index_error(),
128
+ format!("tier index {index} out of range"),
129
+ )
130
+ })
131
+ }
132
+
133
+ fn quorum_count(&self) -> usize {
134
+ self.inner.borrow().quorums.len()
135
+ }
136
+
137
+ fn validate(&self) -> Result<Vec<String>, Error> {
138
+ let m = self.inner.borrow();
139
+ let report = validate_manifest(&m);
140
+ Ok(report.errors)
141
+ }
142
+
143
+ fn is_valid(&self) -> Result<bool, Error> {
144
+ let m = self.inner.borrow();
145
+ let report = validate_manifest(&m);
146
+ Ok(report.errors.is_empty())
147
+ }
148
+ }
149
+
150
+ pub fn init(ruby: &Ruby, parent: magnus::RModule) -> Result<(), Error> {
151
+ let identity = parent.define_module("Identity")?;
152
+ identity.define_module_function("actor_types", function!(actor_types, 0))?;
153
+ let actor_class = identity.define_class("Actor", ruby.class_object())?;
154
+ actor_class.define_singleton_method("from_json", function!(Actor::from_json, 1))?;
155
+ actor_class.define_method("to_json", method!(Actor::to_json, 0))?;
156
+ actor_class.define_method("actor_id", method!(Actor::actor_id, 0))?;
157
+ actor_class.define_method("actor_type", method!(Actor::actor_type, 0))?;
158
+ actor_class.define_method("quorum_id", method!(Actor::quorum_id, 0))?;
159
+ actor_class.define_method("registered_at", method!(Actor::registered_at_iso8601, 0))?;
160
+ actor_class.define_method("expires_at", method!(Actor::expires_at_iso8601, 0))?;
161
+ actor_class.define_method("certificate_count", method!(Actor::certificate_count, 0))?;
162
+
163
+ let config = parent.define_module("Config")?;
164
+ let manifest_class = config.define_class("Manifest", ruby.class_object())?;
165
+ manifest_class.define_singleton_method("from_toml", function!(Manifest::from_toml, 1))?;
166
+ manifest_class.define_method("deployment_name", method!(Manifest::deployment_name, 0))?;
167
+ manifest_class.define_method("operator", method!(Manifest::operator, 0))?;
168
+ manifest_class.define_method("manifest_version", method!(Manifest::manifest_version, 0))?;
169
+ manifest_class.define_method("tier_count", method!(Manifest::tier_count, 0))?;
170
+ manifest_class.define_method("tier_name_at", method!(Manifest::tier_name_at, 1))?;
171
+ manifest_class.define_method("quorum_count", method!(Manifest::quorum_count, 0))?;
172
+ manifest_class.define_method("validate", method!(Manifest::validate, 0))?;
173
+ manifest_class.define_method("valid?", method!(Manifest::is_valid, 0))?;
174
+
175
+ Ok(())
176
+ }
@@ -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, prelude::*, 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
+ }