confium 0.3.2-aarch64-linux

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 (45) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +183 -0
  3. data/Cargo.lock +2635 -0
  4. data/Cargo.toml +9 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.adoc +139 -0
  7. data/Rakefile +30 -0
  8. data/confium.gemspec +56 -0
  9. data/lib/confium/audit.rb +125 -0
  10. data/lib/confium/cfm.rb +23 -0
  11. data/lib/confium/crypto.rb +50 -0
  12. data/lib/confium/digest.rb +62 -0
  13. data/lib/confium/errors/coerce.rb +47 -0
  14. data/lib/confium/errors/crypto_error.rb +15 -0
  15. data/lib/confium/errors/index_error.rb +15 -0
  16. data/lib/confium/errors/not_found_error.rb +15 -0
  17. data/lib/confium/errors/parse_error.rb +15 -0
  18. data/lib/confium/errors/policy_violation_error.rb +15 -0
  19. data/lib/confium/errors/threshold_error.rb +16 -0
  20. data/lib/confium/errors/unresolved_signer_error.rb +14 -0
  21. data/lib/confium/errors/validation_error.rb +17 -0
  22. data/lib/confium/errors/verification_error.rb +15 -0
  23. data/lib/confium/errors.rb +26 -0
  24. data/lib/confium/ffi.rb +23 -0
  25. data/lib/confium/lib.rb +33 -0
  26. data/lib/confium/openpgp.rb +34 -0
  27. data/lib/confium/pki/certificate_builder.rb +60 -0
  28. data/lib/confium/pki/cms/signed_data_builder.rb +92 -0
  29. data/lib/confium/pki/cms.rb +15 -0
  30. data/lib/confium/pki/cnml.rb +80 -0
  31. data/lib/confium/pki.rb +13 -0
  32. data/lib/confium/policy.rb +138 -0
  33. data/lib/confium/secure_bytes.rb +126 -0
  34. data/lib/confium/tc/coordinator.rb +68 -0
  35. data/lib/confium/tc/session.rb +51 -0
  36. data/lib/confium/tc/session_stub.rb +43 -0
  37. data/lib/confium/tc/share_file.rb +87 -0
  38. data/lib/confium/tc.rb +17 -0
  39. data/lib/confium/transparency/ots.rb +63 -0
  40. data/lib/confium/version.rb +5 -0
  41. data/lib/confium.rb +71 -0
  42. data/lib/confium_native/3.1/confium_native.so +0 -0
  43. data/lib/confium_native/3.2/confium_native.so +0 -0
  44. data/lib/confium_native/3.3/confium_native.so +0 -0
  45. metadata +178 -0
data/Cargo.toml ADDED
@@ -0,0 +1,9 @@
1
+ [workspace]
2
+ resolver = "2"
3
+ members = ["ext/confium_native"]
4
+
5
+ [profile.release]
6
+ opt-level = 3
7
+ lto = "off"
8
+ codegen-units = 1
9
+ debug = true
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2022 Ribose Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.adoc ADDED
@@ -0,0 +1,139 @@
1
+ = Confium Ruby bindings
2
+ :toc: macro
3
+ :toclevels: 2
4
+
5
+ Ruby bindings for the Confium open-source framework for multi-stakeholder threshold cryptography.
6
+
7
+ Confium supports three deployment modes:
8
+
9
+ * **Mode 1 — Peer-to-peer threshold cryptography**: nodes do TC directly (MPC, distributed custody, BFT)
10
+ * **Mode 2 — TC PKI replacement**: drop-in for existing PKI consumers (PKCS#11 server, OpenSSL 3.0 provider, JCE)
11
+ * **Mode 3 — TC Certificate PKI**: institutional deployments with custom certificate formats (OIML CNML, BIPM, pharma, accreditation)
12
+
13
+ This gem wraps the high-value Confium subsystems via a Rust native extension (built at `gem install` time with `rb_sys` + `magnus`). No separate C ABI library to install; everything is statically linked into the extension.
14
+
15
+ == Installation
16
+
17
+ Add to your Gemfile:
18
+
19
+ [source,ruby]
20
+ ----
21
+ gem "confium", "~> 0.1"
22
+ ----
23
+
24
+ Or install directly:
25
+
26
+ [source,sh]
27
+ ----
28
+ $ gem install confium
29
+ ----
30
+
31
+ === Prerequisites
32
+
33
+ * Ruby ≥ 3.1
34
+ * Rust stable toolchain (`rustup default stable`)
35
+ * C toolchain (clang/gcc/Xcode CLT)
36
+
37
+ The gem compiles a Rust cdylib at install time and links it into Ruby as a native extension. There is no separate `libconfium` to install.
38
+
39
+ == Quick start
40
+
41
+ [source,ruby]
42
+ ----
43
+ require "confium"
44
+
45
+ # Transparency log
46
+ tree = Confium::Transparency::MerkleTree.new
47
+ seq = tree.append(artifact_hash_bytes_32)
48
+ root = tree.root # binary String, 32 bytes
49
+ proof = tree.inclusion_proof(seq)
50
+ proof.verify(root) # => true
51
+
52
+ # Composite signature (PQ migration)
53
+ kp = Confium::Composite.generate_ed25519_keypair
54
+ component = Confium::Composite.sign_ed25519(kp["private_key"], "message")
55
+ sig = Confium::Composite::Signature.new([component])
56
+ result = sig.verify("message")
57
+ result.all_verified? # => true
58
+
59
+ # Attribute-based threshold policy
60
+ pred = Confium::Attributes.parse(%q{and(min_count("role:director", 3), min_distinct("region", 3))})
61
+ alice = Confium::Attributes::Signer.new
62
+ alice.add("role:director", "yes")
63
+ alice.add("region", "europe")
64
+ # ... bob, carol similarly
65
+ pred.satisfied_by?([alice, bob, carol]) # => true
66
+
67
+ # X.509 certificate
68
+ cert = Confium::PKI::Certificate.from_pem(File.read("cert.pem"))
69
+ puts cert.fingerprint_sha256
70
+ puts cert.valid_at?(Time.now.utc.iso8601) # => true
71
+
72
+ # Real threshold cryptography: P-256 Shamir
73
+ kp = Confium::TC::FrostP256.generate_keypair
74
+ shares = Confium::TC::FrostP256.split_secret(kp["private_key"], 3, 5)
75
+ recovered = Confium::TC::FrostP256.recover_secret(shares.first(3).map { |s| { "x" => s.x, "y" => s.y_bytes } })
76
+ recovered == kp["private_key"] # => true
77
+ ----
78
+
79
+ == API surface (v0.1.0)
80
+
81
+ === `Confium::Transparency`
82
+ - `MerkleTree.new` / `#append(artifact_hash)` / `#root` / `#length` / `#empty?` / `#inclusion_proof(seq)`
83
+ - `InclusionProof#sequence` / `#steps` / `#verify(root)`
84
+
85
+ === `Confium::Composite` — PQ migration
86
+ - `.generate_ed25519_keypair` → `{ private_key:, public_key: }`
87
+ - `.sign_ed25519(private_key, message)` → component Hash
88
+ - `Signature.new(components)` / `#verify(message)` / `#component_count` / `#algorithms`
89
+ - `VerificationResult#all_verified?` / `#per_component`
90
+
91
+ === `Confium::Attributes` — threshold policy DSL
92
+ - `.parse(dsl_expr)` → `Predicate`
93
+ - `Predicate#satisfied_by?(signers)`
94
+ - `Signer.new` / `#add(key, value)` / `#has?(key)` / `#values(key)`
95
+ - DSL: `min_count("attr", n)`, `min_distinct("attr", n)`, `any("attr")`, `all("attr")`, `none("attr")`, `and(...)`, `or(...)`, `not(p)`
96
+
97
+ === `Confium::PKI`
98
+ - `Certificate.from_der(bytes)` / `.from_pem(str)` / `#to_der` / `#to_pem` / `#fingerprint_sha256` / `#serial_hex` / `#not_before` / `#not_after` / `#valid_at?(iso8601)` / `#public_key_bytes`
99
+ - `CSR.from_der` / `.from_pem` / `#to_der` / `#to_pem`
100
+ - `CMS::SignedData.from_json` / `#to_json` / `#signer_count` / `#content_type` / `#content` / `#certificate_count` / `#certificate_at(i)`
101
+ - `CMS::Content#bytes` / `#length`
102
+ - `XMLDSig.canonicalize(xml)` / `.canonicalize_exclusive(xml)` (Canonical XML RFC 3076 + Exclusive C14N)
103
+
104
+ === `Confium::Identity` — actor roles
105
+ - `.actor_types` → `["manufacturer", "testing_lab", "issuing_authority_officer", "biml_director", "quorum_coordinator", "verifier"]`
106
+ - `Actor.from_json(json)` / `#to_json` / `#actor_id` / `#actor_type` / `#quorum_id` / `#registered_at` / `#expires_at` / `#certificate_count`
107
+
108
+ === `Confium::Config` — deployment manifest
109
+ - `Manifest.from_toml(toml_str)` / `#deployment_name` / `#operator` / `#manifest_version` / `#tier_count` / `#tier_name_at(i)` / `#quorum_count` / `#validate` / `#valid?`
110
+
111
+ === `Confium::TC::FrostP256` — real P-256 Shamir + ECDSA
112
+ - `.generate_keypair` → `{ private_key: (32 bytes), public_key: (65 bytes SEC1) }`
113
+ - `.split_secret(secret, t, n)` → array of `Share` with `#x`, `#y_bytes`
114
+ - `.recover_secret([{x:, y:}, ...])` → secret bytes
115
+ - `.sign(private_key, message)` → `{ der:, fixed: }`
116
+
117
+ === `Confium::TC::ElGamalP256` — threshold ElGamal-P256 KEM
118
+ - `.encapsulate(public_key_bytes)` → `{ ciphertext: { c1:, c2: }, shared_secret: }`
119
+ - `.partial_decrypt(party_index, share_bytes, ciphertext)` → `{ party_index:, bytes: }`
120
+ - `.aggregate_partials(partials, threshold, ciphertext)` → shared_secret bytes
121
+
122
+ == Development
123
+
124
+ After checking out the repo:
125
+
126
+ [source,sh]
127
+ ----
128
+ $ bundle install
129
+ $ bundle exec rake compile # build the Rust extension
130
+ $ bundle exec rspec # 75+ specs
131
+ ----
132
+
133
+ == Architecture
134
+
135
+ The native extension lives at `ext/confium_native/` as a Cargo workspace. It depends on the `confium-*` crates from crates.io (not on the local Rust workspace). The Ruby-side API is defined entirely in the extension via magnus — `lib/confium.rb` just requires the compiled `.bundle`.
136
+
137
+ == License
138
+
139
+ BSD-2-Clause, same as the rest of the Confium workspace.
data/Rakefile ADDED
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rake/clean'
4
+ require 'bundler/gem_tasks'
5
+
6
+ require 'rb_sys/extensiontask'
7
+ gemspec = Gem::Specification.load('confium.gemspec')
8
+ RbSys::ExtensionTask.new('confium_native', gemspec) do |ext|
9
+ ext.lib_dir = 'lib/confium_native'
10
+ end
11
+
12
+ # Tooling tasks are dev-only and are not needed to build the
13
+ # extension. Guard the requires so minimal environments (e.g. release
14
+ # tooling containers) can still load this Rakefile.
15
+ begin
16
+ require 'rspec/core/rake_task'
17
+ RSpec::Core::RakeTask.new(:spec)
18
+ rescue LoadError
19
+ nil
20
+ end
21
+
22
+ begin
23
+ require 'rubocop/rake_task'
24
+ RuboCop::RakeTask.new
25
+ rescue LoadError
26
+ nil
27
+ end
28
+
29
+ task compile: :clean
30
+ task default: %i[compile spec rubocop]
data/confium.gemspec ADDED
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/confium/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'confium'
7
+ spec.version = Confium::VERSION
8
+ spec.platform = Gem::Platform::RUBY
9
+
10
+ spec.authors = ['Ribose Open']
11
+ spec.email = ['open.source@ribose.com']
12
+
13
+ spec.summary = 'Ruby bindings for the Confium multi-stakeholder threshold cryptography framework.'
14
+ spec.description = 'Confium provides threshold cryptography for Ruby: FROST / CMP20 / GG18 ' \
15
+ 'signing sessions, X.509 certificate issuance (CNML-ready), composite ' \
16
+ 'PQ-migration signatures, and transparency-log anchoring. Powered by a ' \
17
+ 'Rust native extension (magnus + rb_sys) — no separate C ABI to install.'
18
+ spec.homepage = 'https://www.confium.org'
19
+ spec.license = 'BSD-2-Clause'
20
+
21
+ spec.metadata = {
22
+ 'bug_tracker_uri' => 'https://github.com/confium/confium-ruby/issues',
23
+ 'changelog_uri' => 'https://github.com/confium/confium-ruby/blob/main/CHANGELOG.md',
24
+ 'homepage_uri' => spec.homepage,
25
+ 'source_code_uri' => 'https://github.com/confium/confium-ruby',
26
+ 'rubygems_mfa_required' => 'true'
27
+ }
28
+
29
+ # Rust extension — compiled at gem install via rb_sys.
30
+ spec.extensions = ['ext/confium_native/extconf.rb']
31
+
32
+ spec.files = Dir.glob('{lib,ext}/**/*') + %w[
33
+ CHANGELOG.md
34
+ LICENSE.txt
35
+ Rakefile
36
+ README.adoc
37
+ confium.gemspec
38
+ Cargo.toml
39
+ Cargo.lock
40
+ ]
41
+ spec.files.reject! { |f| File.directory?(f) }
42
+ spec.files.reject! { |f| f =~ /\.(dll|so|dylib|lib|bundle)\Z/ }
43
+ spec.require_paths = ['lib']
44
+
45
+ spec.required_ruby_version = '>= 3.1.0'
46
+
47
+ # Required for the Rust extension.
48
+ spec.add_dependency 'rb_sys', '~> 0.9.39'
49
+
50
+ spec.add_development_dependency 'rake', '~> 13.0'
51
+ spec.add_development_dependency 'rake-compiler', '~> 1.3.0'
52
+ spec.add_development_dependency 'rake-compiler-dock', '~> 1.3'
53
+ spec.add_development_dependency 'rspec', '~> 3.0'
54
+ spec.add_development_dependency 'rubocop', '~> 1.0'
55
+ spec.add_development_dependency 'steep', '~> 1.5'
56
+ end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Audit log sink hierarchy.
4
+ #
5
+ # Sinks receive an audit record Hash for every signed event. The Rust
6
+ # extension fires events on every signing / verification / encryption
7
+ # op; the Ruby side routes them to the configured sink via
8
+ # {Confium::Audit.sink=}.
9
+ #
10
+ # Three reference sinks are shipped: {FileSink} (append-only file),
11
+ # {MemorySink} (in-memory, for testing), {StderrSink} (one-line JSON
12
+ # per record). Custom sinks can subclass {Sink} for HTTP / syslog /
13
+ # Kafka backends.
14
+
15
+ require 'json'
16
+ require 'time'
17
+
18
+ module Confium
19
+ module Audit
20
+ # Base class for audit sinks. Subclasses override {#write} and
21
+ # optionally {#close}. The contract:
22
+ #
23
+ # - #write(record) — synchronously emit the record. Raise on
24
+ # failure; the exception propagates back through the caller.
25
+ # - #close — flush and release resources. Safe to call multiple
26
+ # times.
27
+ class Sink
28
+ # Persist an audit record Hash. The Hash has these keys
29
+ # (all Strings):
30
+ #
31
+ # - `"timestamp"` — ISO8601 UTC, e.g. `"2026-07-30T22:00:00Z"`
32
+ # - `"operation"` — short slug like `"composite_sign"`
33
+ # - `"actor"` — optional String
34
+ # - `"algorithm"` — optional String
35
+ # - `"payload_hash"` — hex SHA-256 of the signed bytes
36
+ # - `"result"` — `"success"` or `"failure"`
37
+ # - `"error"` — optional String
38
+ def write(_record)
39
+ raise NotImplementedError, "#{self.class} must implement #write"
40
+ end
41
+
42
+ # Treat the Sink as a callable — delegates to {#write}. The Rust
43
+ # extension's audit module fires events by calling `.call(record)`
44
+ # on whatever is in `Confium::Audit.sink`, so this lets both
45
+ # Proc-based and Object-based sinks work through the same
46
+ # dispatch point.
47
+ def call(record)
48
+ write(record)
49
+ end
50
+
51
+ def close
52
+ # default no-op
53
+ end
54
+ end
55
+
56
+ # In-memory sink. Records are collected in `#records` and inspected
57
+ # in specs. Implements `#clear` for resetting between tests.
58
+ #
59
+ # Includes Enumerable so callers can iterate, filter, and reduce
60
+ # over recorded events directly:
61
+ #
62
+ # sink.select { |r| r["operation"] == "composite_sign" }
63
+ # sink.count { |r| r["result"] == "failure" }
64
+ # sink.find { |r| r["actor"] == "director-1" }
65
+ class MemorySink < Sink
66
+ include Enumerable
67
+
68
+ attr_reader :records
69
+
70
+ def initialize
71
+ @records = []
72
+ end
73
+
74
+ def write(record)
75
+ @records << record
76
+ self
77
+ end
78
+
79
+ def clear
80
+ @records.clear
81
+ end
82
+
83
+ # Enumerable contract: yield each record in insertion order.
84
+ def each(&)
85
+ @records.each(&)
86
+ end
87
+ end
88
+
89
+ # Stderr sink — emits each record as a one-line JSON object.
90
+ # Suitable for development and CI; for production prefer
91
+ # {FileSink} or a structured-logging sink.
92
+ class StderrSink < Sink
93
+ def initialize(io: $stderr)
94
+ @io = io
95
+ end
96
+
97
+ def write(record)
98
+ @io.puts(JSON.generate(record))
99
+ end
100
+ end
101
+
102
+ # Append-only file sink. The path is opened in append mode; each
103
+ # record is written as a single line followed by a newline.
104
+ # Concurrent writes from multiple processes are NOT supported —
105
+ # serialize audit traffic through this sink to a single writer for
106
+ # multi-process deployments.
107
+ class FileSink < Sink
108
+ attr_reader :path
109
+
110
+ def initialize(path)
111
+ @path = path
112
+ @io = File.open(path, 'a')
113
+ @io.sync = true
114
+ end
115
+
116
+ def write(record)
117
+ @io.puts(JSON.generate(record))
118
+ end
119
+
120
+ def close
121
+ @io.close unless @io.closed?
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ffi'
4
+
5
+ module Confium
6
+ class CFM
7
+ attr_reader :ptr
8
+
9
+ def initialize
10
+ pptr = ::FFI::MemoryPointer.new(:pointer)
11
+ Confium.call_ffi(:cfm_create, pptr)
12
+ @ptr = ::FFI::AutoPointer.new(pptr.read_pointer, self.class.method(:destroy))
13
+ end
14
+
15
+ def self.destroy(ptr)
16
+ Confium::Lib.cfm_destroy(ptr)
17
+ end
18
+
19
+ def load_plugin(name, path)
20
+ Confium.call_ffi(:cfm_plugin_load, @ptr, name, path, nil, nil)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ # {Confium::Crypto} is the registry namespace for Confium's cryptographic
4
+ # interfaces.
5
+ #
6
+ # Mirroring the Rust plugin-interface registry (TODO #02), each Ruby
7
+ # interface module (Digest, and future Cipher, AEAD, KDF, RNG, Signature,
8
+ # KEM, ...) registers itself here on load rather than being enumerated in
9
+ # a central case statement. This keeps the registry open for extension
10
+ # (OCP) and makes the set of available interfaces a single source of
11
+ # truth.
12
+ #
13
+ # Example:
14
+ #
15
+ # module Confium
16
+ # module Digest
17
+ # Confium::Crypto.register(:hash, self)
18
+ # end
19
+ # end
20
+ #
21
+ # Confium::Crypto.lookup(:hash) # => Confium::Digest
22
+ module Confium
23
+ module Crypto
24
+ @interfaces = {}
25
+
26
+ class << self
27
+ # Register an interface +klass+ under the symbolic +name+. Adding a
28
+ # new interface is a one-line registration; no central switch needs
29
+ # editing.
30
+ def register(name, klass)
31
+ @interfaces[name] = klass
32
+ klass
33
+ end
34
+
35
+ # Resolve a registered interface by +name+. Raises ArgumentError if
36
+ # nothing has been registered under that name.
37
+ def lookup(name)
38
+ @interfaces.fetch(name) do
39
+ raise ArgumentError, "unknown interface #{name.inspect}"
40
+ end
41
+ end
42
+
43
+ # Enumerate the names of every registered interface. Primarily for
44
+ # introspection and tooling.
45
+ def interfaces
46
+ @interfaces.keys.dup
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ffi'
4
+ require 'digest'
5
+
6
+ module Confium
7
+ class Digest < ::Digest::Class
8
+ attr_reader :name, :ptr
9
+
10
+ def initialize(cfm, name)
11
+ @name = name
12
+ pptr = ::FFI::MemoryPointer.new(:pointer)
13
+ Confium.call_ffi(:cfm_hash_create, cfm.ptr, pptr, name, nil, nil, nil)
14
+ ptr = pptr.read_pointer
15
+ raise if ptr.null?
16
+
17
+ @ptr = ::FFI::AutoPointer.new(ptr, self.class.method(:destroy))
18
+ end
19
+
20
+ def initialize_copy(source)
21
+ @name = source.name
22
+ pptr = ::FFI::MemoryPointer.new(:pointer)
23
+ Confium.call_ffi(:cfm_hash_clone, source.ptr, pptr)
24
+ ptr = pptr.read_pointer
25
+ @ptr = ::FFI::AutoPointer.new(ptr, self.class.method(:destroy))
26
+ end
27
+
28
+ def self.destroy(ptr)
29
+ Confium::Lib.cfm_hash_destroy(ptr)
30
+ end
31
+
32
+ def block_length
33
+ plength = ::FFI::MemoryPointer.new(:uint32)
34
+ Confium.call_ffi(:cfm_hash_block_size, @ptr, plength)
35
+ plength.read(:uint32)
36
+ end
37
+
38
+ def digest_length
39
+ plength = ::FFI::MemoryPointer.new(:uint32)
40
+ Confium.call_ffi(:cfm_hash_output_size, @ptr, plength)
41
+ plength.read(:uint32)
42
+ end
43
+
44
+ def update(data)
45
+ Confium.call_ffi(:cfm_hash_update, @ptr, data, data.bytesize)
46
+ self
47
+ end
48
+
49
+ def reset
50
+ Confium.call_ffi(:cfm_hash_reset, @ptr)
51
+ self
52
+ end
53
+
54
+ def finish
55
+ buf = ::FFI::MemoryPointer.new(:uint8, digest_length)
56
+ Confium.call_ffi(:cfm_hash_finalize, @ptr, buf, buf.size)
57
+ buf.read_bytes(buf.size)
58
+ end
59
+
60
+ alias << update
61
+ end
62
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Shared coercion helper for the typed-error hierarchy.
4
+ #
5
+ # Every `Confium::*Error` subclass accepts the same three calling
6
+ # conventions (keyword form, positional-Hash form from the native
7
+ # extension, hash-only form). Before this module existed, each class
8
+ # duplicated the same 8-line coercion preamble. Centralizing it here
9
+ # keeps the per-class initializer focused on its own field extraction.
10
+ #
11
+ # Usage in a subclass:
12
+ #
13
+ # def initialize(message = nil, details_hash = nil, **kwargs)
14
+ # message, kwargs = Coerce.args(message, details_hash, kwargs)
15
+ # @have_count = kwargs.delete(:have_count)
16
+ # @need_count = kwargs.delete(:need_count)
17
+ # super(message, details: { have_count: @have_count, need_count: @need_count, **kwargs })
18
+ # end
19
+ module Confium
20
+ module Errors
21
+ module Coerce
22
+ module_function
23
+
24
+ # Normalize the (message, details_hash, kwargs) triple that every
25
+ # typed-error initializer receives. Returns `[message, kwargs]`
26
+ # where:
27
+ #
28
+ # - `message` is `nil` or a String (never a Hash)
29
+ # - `kwargs` is a Hash with symbol keys, containing every key
30
+ # from the original `details_hash` and `**kwargs`, plus
31
+ # anything that was tucked inside `message` (when the caller
32
+ # passed a Hash as the first arg).
33
+ #
34
+ # The subclass initializer then `kwargs.delete(:specific_field)`
35
+ # to pull its own fields out, and the leftover `**kwargs` flows
36
+ # to `super` as `details:`.
37
+ def args(message, details_hash, kwargs)
38
+ if message.is_a?(Hash)
39
+ kwargs = message.transform_keys(&:to_sym).merge(kwargs)
40
+ message = kwargs.delete(:message)
41
+ end
42
+ kwargs = details_hash.transform_keys(&:to_sym).merge(kwargs) if details_hash.is_a?(Hash)
43
+ [message, kwargs]
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Raised when a primitive-level crypto operation fails (invalid scalar,
4
+ # bad key derivation).
5
+ module Confium
6
+ class CryptoError < Confium::Error
7
+ attr_reader :primitive
8
+
9
+ def initialize(message = nil, details_hash = nil, **kwargs)
10
+ message, kwargs = Confium::Errors::Coerce.args(message, details_hash, kwargs)
11
+ @primitive = kwargs.delete(:primitive)
12
+ super(message, details: { primitive: @primitive, **kwargs })
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Raised when an out-of-range index is supplied.
4
+ module Confium
5
+ class IndexError < Confium::Error
6
+ attr_reader :index, :valid_range
7
+
8
+ def initialize(message = nil, details_hash = nil, **kwargs)
9
+ message, kwargs = Confium::Errors::Coerce.args(message, details_hash, kwargs)
10
+ @index = kwargs.delete(:index)
11
+ @valid_range = kwargs.delete(:valid_range)
12
+ super(message, details: { index: @index, valid_range: @valid_range, **kwargs })
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Raised when a referenced slot/cert/share is not present.
4
+ module Confium
5
+ class NotFoundError < Confium::Error
6
+ attr_reader :kind, :identifier
7
+
8
+ def initialize(message = nil, details_hash = nil, **kwargs)
9
+ message, kwargs = Confium::Errors::Coerce.args(message, details_hash, kwargs)
10
+ @kind = kwargs.delete(:kind)
11
+ @identifier = kwargs.delete(:identifier)
12
+ super(message, details: { kind: @kind, identifier: @identifier, **kwargs })
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Raised when input cannot be parsed (bad JSON, malformed PEM, etc.).
4
+ module Confium
5
+ class ParseError < Confium::Error
6
+ attr_reader :format, :offset
7
+
8
+ def initialize(message = nil, details_hash = nil, **kwargs)
9
+ message, kwargs = Confium::Errors::Coerce.args(message, details_hash, kwargs)
10
+ @format = kwargs.delete(:format)
11
+ @offset = kwargs.delete(:offset)
12
+ super(message, details: { format: @format, offset: @offset, **kwargs })
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Raised when a FIPS / jurisdictional policy is violated.
4
+ module Confium
5
+ class PolicyViolationError < Confium::Error
6
+ attr_reader :policy, :violation
7
+
8
+ def initialize(message = nil, details_hash = nil, **kwargs)
9
+ message, kwargs = Confium::Errors::Coerce.args(message, details_hash, kwargs)
10
+ @policy = kwargs.delete(:policy)
11
+ @violation = kwargs.delete(:violation)
12
+ super(message, details: { policy: @policy, violation: @violation, **kwargs })
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Raised when a Shamir/threshold operation fails (insufficient shares,
4
+ # duplicate coordinates, etc.).
5
+ module Confium
6
+ class ThresholdError < Confium::Error
7
+ attr_reader :have_count, :need_count
8
+
9
+ def initialize(message = nil, details_hash = nil, **kwargs)
10
+ message, kwargs = Confium::Errors::Coerce.args(message, details_hash, kwargs)
11
+ @have_count = kwargs.delete(:have_count)
12
+ @need_count = kwargs.delete(:need_count)
13
+ super(message, details: { have_count: @have_count, need_count: @need_count, **kwargs })
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Raised when a CMS signer_info cannot be resolved to a certificate.
4
+ module Confium
5
+ class UnresolvedSignerError < Confium::Error
6
+ attr_reader :signer_index
7
+
8
+ def initialize(message = nil, details_hash = nil, **kwargs)
9
+ message, kwargs = Confium::Errors::Coerce.args(message, details_hash, kwargs)
10
+ @signer_index = kwargs.delete(:signer_index)
11
+ super(message, details: { signer_index: @signer_index, **kwargs })
12
+ end
13
+ end
14
+ end