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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +183 -0
- data/Cargo.lock +2635 -0
- data/Cargo.toml +9 -0
- data/LICENSE.txt +21 -0
- data/README.adoc +139 -0
- data/Rakefile +30 -0
- data/confium.gemspec +56 -0
- data/lib/confium/audit.rb +125 -0
- data/lib/confium/cfm.rb +23 -0
- data/lib/confium/crypto.rb +50 -0
- data/lib/confium/digest.rb +62 -0
- 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 +33 -0
- 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 +5 -0
- data/lib/confium.rb +71 -0
- data/lib/confium_native/3.1/confium_native.so +0 -0
- data/lib/confium_native/3.2/confium_native.so +0 -0
- data/lib/confium_native/3.3/confium_native.so +0 -0
- metadata +178 -0
data/Cargo.toml
ADDED
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
|
data/lib/confium/cfm.rb
ADDED
|
@@ -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
|