hide-protocol 0.6.2-aarch64-linux → 0.7.0-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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9f82621482c96139fd036e0512c8e889566fda3ba7514c83cfab6b75e9e23b0d
4
- data.tar.gz: ff82406671ed56e31794f49933846bf0ec0c630327f962bbd342942ffb96d074
3
+ metadata.gz: cc6c7917200f35b460601ae243fbb5d0c2d9ea72c9c38e405a51da5f929aaef8
4
+ data.tar.gz: 13a067070d1cfdd059e9b0daf67d6e147bf256daae5684a8991b78cf63ac7893
5
5
  SHA512:
6
- metadata.gz: e906dad85bcf6a1d36668a7c9c3827c310fcea6c17320d341e028148ef6895995e5852a7defbc14d6a95867de029eea6e1a6460b209b32f331415945312991d4
7
- data.tar.gz: 77862f96820bdbff67af02a14a9267463d9076faec7ed03269968c06fb08e30761319094371f873bfa0dca15588c0769e15eb020ac61c92e5748301a208ecf31
6
+ metadata.gz: 494aa783321268b9ed8d96563e21cd0def993acf97634078df8371f546268f6018e539ec0b40a4b7fb487097c4db1cf91047be79594e0a8f4eb09e57bc6d6c09
7
+ data.tar.gz: 1e8dbc3a262ad94c23a342493935a0fc447a3dd02027eb2c4ed5449f7271d5435c5734a5091f2e92e8e78e8c1c19c43c41cd9cec09493881f947225c549125c3
data/README.md CHANGED
@@ -1,177 +1,154 @@
1
- # hide-protocol (Ruby)
1
+ # hide-protocol for Ruby
2
2
 
3
- Ruby bindings to the HIDE core: hybrid post-quantum encryption for files and
4
- messages (X25519 + ML-KEM-768).
3
+ *Experimental and unaudited. HIDE is hybrid post-quantum (X25519 + ML-KEM-768, Ed25519 + ML-DSA-65). See the [security policy](https://github.com/hide-protocol/hide/blob/main/SECURITY.md).*
5
4
 
6
- ## Read this before using it
5
+ Ruby binding, through stdlib `fiddle`, to the same Rust core (`crates/hide-ffi`)
6
+ that the CLI and every other HIDE SDK use. This gem contains no cryptography of
7
+ its own, no compiled extension and no runtime gem dependency.
7
8
 
8
- **This is experimental and unaudited.** Do not use it to protect data you
9
- cannot afford to lose or expose. The format may change.
10
-
11
- **A successful decryption proves the data was not altered. It does *not* prove
12
- who sent it.** Anyone holding your public key can produce a container that
13
- decrypts for you. If you need to know the sender, sign the message with
14
- `Hide::SigningIdentity` and verify it separately.
15
-
16
- A decrypted filename is attacker-controlled. Never use it to choose an output
17
- path.
18
-
19
- ## How it binds
20
-
21
- There is no Ruby cryptography here, and no compiled extension. The gem calls
22
- the same Rust core as the CLI through its C ABI, using stdlib `fiddle` — so
23
- installing it needs no compiler and adds no runtime gem dependency.
24
-
25
- The native library is found in this order:
26
-
27
- 1. `HIDE_LIBRARY`, if set — the full path to the shared library.
28
- 2. Beside the gem, in `lib/hide_protocol/`, where the release workflow puts it.
29
- 3. The system loader.
30
-
31
- Platform names are `hide_ffi.dll` on Windows, `libhide_ffi.dylib` on macOS and
32
- `libhide_ffi.so` elsewhere.
33
-
34
- ### Running against a local build
9
+ ## Install
35
10
 
36
11
  ```sh
37
- cargo build -p hide-ffi
12
+ gem install hide-protocol
38
13
  ```
39
14
 
40
- Then point `HIDE_LIBRARY` at the result:
15
+ The compiled core ships as seven platform gems, so `gem install` fetches only
16
+ the binary for your machine: `x86_64-linux`, `aarch64-linux`,
17
+ `x86_64-linux-musl`, `x64-mingw-ucrt`, `aarch64-mingw-ucrt`, `arm64-darwin`,
18
+ `x86_64-darwin`. On any other platform, build the core yourself and point the
19
+ binding at it (see [Native library](#native-library)).
41
20
 
42
- ```sh
43
- # Linux
44
- export HIDE_LIBRARY="$PWD/target/debug/libhide_ffi.so"
45
- # macOS
46
- export HIDE_LIBRARY="$PWD/target/debug/libhide_ffi.dylib"
47
- ```
48
-
49
- ```powershell
50
- # Windows
51
- $env:HIDE_LIBRARY = "$PWD\target\debug\hide_ffi.dll"
52
- ```
53
-
54
- ## Use
21
+ ## Quick start
55
22
 
56
23
  ```ruby
57
24
  require "hide_protocol"
58
25
 
59
26
  Hide::SecretKey.generate do |secret|
60
- box = Hide.encrypt(
61
- File.binread("salarii.csv"),
62
- recipients: [secret.public_key],
63
- filename: "salarii.csv",
64
- media_type: "text/csv"
65
- )
66
-
27
+ box = Hide.encrypt("hello", recipients: [secret.public_key], filename: "note.txt")
67
28
  opened = Hide.decrypt(box, secret)
68
- opened.plaintext # => the original bytes
69
- opened.filename # => "salarii.csv"
70
- opened.media_type # => "text/csv"
29
+ puts "#{opened.plaintext} #{opened.filename}" # hello note.txt
30
+
31
+ tampered = box.dup
32
+ tampered.setbyte(-1, tampered.getbyte(-1) ^ 1)
33
+ begin
34
+ Hide.decrypt(tampered, secret)
35
+ rescue Hide::AuthenticationError => e
36
+ puts "refused: #{e.message}" # refused: authentication failed; the data was altered
37
+ end
71
38
  end
72
39
  ```
73
40
 
74
- The block form always closes the key, including when the block raises. Without
75
- a block, call `#close` yourself.
76
-
77
- ### Keys
41
+ `Hide.encrypt(plaintext, recipients:, filename: nil, media_type: nil)` takes
42
+ the recipients as a **keyword** argument: 1..64 public keys, each exactly
43
+ `Hide::PUBLIC_KEY_LEN` (1216) bytes. `Hide.decrypt(container, secret)` is
44
+ positional and returns a `Hide::Decrypted` with `plaintext`, `filename` and
45
+ `media_type`; nothing is returned unless the whole payload authenticates. The
46
+ filename is attacker-controlled: never use it to choose an output path. Binary
47
+ values in and out are ASCII-8BIT Strings; metadata comes back as UTF-8.
48
+
49
+ Keys on disk: `secret.protect(passphrase)` returns a sealed key file
50
+ (`Hide::MIN_PASSPHRASE_LEN` is 8, and there is no escrow);
51
+ `Hide::SecretKey.open(bytes, passphrase)` opens one; `Hide.inspect_key(bytes)`
52
+ reports `"raw"` or `"protected"` without the passphrase.
53
+ `Hide.armor_public_key` / `Hide.dearmor_public_key` give a public key a
54
+ pasteable text form.
55
+
56
+ Errors: everything raises a subclass of `Hide::Error` — `AuthenticationError`
57
+ (altered, or not a container), its subclass `MalformedError` (did not decode at
58
+ all), `WrongPassphraseError`, `NoMatchingRecipientError`, `NotAKeyError`,
59
+ `TooLargeError`, `ClosedKeyError`, `ChallengeExpiredError`,
60
+ `ChallengeReplayedError`, and `InvalidArgumentError` for arguments this binding
61
+ rejects before calling the core.
62
+
63
+ ## Signing and verification
64
+
65
+ One seed backs both encryption and signing, so there is a single thing to back
66
+ up.
78
67
 
79
68
  ```ruby
80
- secret = Hide::SecretKey.generate
81
- public_key = secret.public_key # 1216 bytes, binary
82
-
83
- sealed = secret.protect("a long passphrase") # for writing to disk
84
- Hide.inspect_key(sealed) # => "protected"
85
-
86
- reopened = Hide::SecretKey.open(sealed, "a long passphrase")
87
- ```
88
-
89
- A passphrase must be at least `Hide::MIN_PASSPHRASE_LEN` (8) characters. A
90
- forgotten passphrase cannot be recovered: there is no escrow.
91
-
92
- Secret key material never crosses into Ruby. `inspect` and `to_s` report only
93
- whether the key is open or closed, and a closed key raises on any use.
94
-
95
- ### Sharing a public key
96
-
97
- ```ruby
98
- text = Hide.armor_public_key(public_key) # "hide-public-key:..."
99
- Hide.dearmor_public_key(text) # back to bytes
100
- ```
101
-
102
- ### Signing
103
-
104
- Encryption and signing share one seed, so there is a single thing to back up.
105
-
106
- ```ruby
107
- sealed = Hide::SigningIdentity.generate("a long passphrase") # write this to disk
108
-
109
- Hide::SigningIdentity.open(sealed, "a long passphrase") do |signer|
110
- public_key = signer.public_key # 1984 bytes, shareable
111
- signature = signer.sign("myapp/v1 release", bytes) # 3373 bytes
112
- Hide.verify(public_key, "myapp/v1 release", bytes, signature)
69
+ sealed = Hide::SigningIdentity.generate("correct horse battery") # store this
70
+
71
+ Hide::SigningIdentity.open(sealed, "correct horse battery") do |signer|
72
+ context = "myapp/v1 release"
73
+ message = "payload"
74
+ signature = signer.sign(context, message) # 3373 bytes
75
+ Hide.verify(signer.public_key, context, message, signature) # nil, or raises
76
+
77
+ # Challenge/response: good once, here, now.
78
+ now = Time.now.to_i
79
+ challenge = Hide.new_challenge("app.example", now, 60)
80
+ answer = signer.answer(challenge)
81
+ Hide::SpentNonces.open do |spent| # must outlive one request
82
+ spent.accept(challenge, answer, signer.public_key, now)
83
+ spent.accept(challenge, answer, signer.public_key, now) # Hide::ChallengeReplayedError
84
+ end
113
85
  end
114
86
  ```
115
87
 
116
- `verify` returns `nil` and raises on failure, rather than returning a boolean a
117
- caller could forget to test. The context string separates uses of one identity:
118
- never let a remote party choose it.
119
-
120
- ### Proving possession live
88
+ `context` separates uses of one identity so a signature made for one purpose
89
+ cannot be replayed as another; never let a remote party choose it.
90
+ `Hide.verify` returns `nil` and raises `Hide::AuthenticationError` on failure
91
+ rather than returning a boolean a caller could forget to check. A key file
92
+ written before signatures existed carries no signing seed and raises
93
+ `Hide::NotAKeyError`.
121
94
 
122
- A detached signature proves possession at some point, to nobody in particular,
123
- and can be replayed. A challenge binds a nonce, an audience and an expiry.
95
+ ## Identity logs, epoch chains, transparency proofs
124
96
 
125
- ```ruby
126
- challenge = Hide.new_challenge("ssh://host.example", Time.now.to_i, 60)
127
- answer = signer.answer(challenge)
128
-
129
- spent = Hide::SpentNonces.new # must outlive the request
130
- spent.accept(challenge, answer, public_key, Time.now.to_i)
131
- ```
97
+ | Method | Returns |
98
+ | --- | --- |
99
+ | `Hide.verify_identity(log, recovery_key)` | `Integer` — how many devices the log trusts now |
100
+ | `Hide.identity_trusts_device(log, recovery_key, device_public_key)` | `true`/`false` — membership, after verifying the log |
101
+ | `Hide.identity_head(log, recovery_key)` | 32 bytes naming this exact history |
102
+ | `Hide.verify_epoch_chain(chain)` | `Integer` — how many epochs it holds |
103
+ | `Hide.epoch_public_key(chain, epoch)` | the public key to encrypt to for `epoch` |
104
+ | `Hide.verify_inclusion(leaf, index, size, path, root)` | `nil` |
105
+ | `Hide.verify_consistency(old_size, new_size, path, old_root, new_root)` | `nil` |
132
106
 
133
- The second acceptance of the same answer raises `ChallengeReplayedError`, and
134
- one presented after the window raises `ChallengeExpiredError`.
107
+ A cryptographic verify **raises** on failure (`MalformedError` if the bytes did
108
+ not decode, `AuthenticationError` if they decoded but did not verify) and never
109
+ returns `false`. The one boolean is `identity_trusts_device`: the log is
110
+ verified first, so `false` means "not a member", never "did not verify".
135
111
 
136
- ## API
112
+ ## Native library
137
113
 
138
- | | |
139
- |---|---|
140
- | `Hide.version` | version of the native core |
141
- | `Hide.encrypt(plaintext, recipients:, filename: nil, media_type: nil)` | binary String |
142
- | `Hide.decrypt(container, secret)` | `Decrypted` with `plaintext`, `filename`, `media_type` |
143
- | `Hide.armor_public_key` / `Hide.dearmor_public_key` | text form of a public key |
144
- | `Hide.inspect_key(bytes)` | `"raw"` or `"protected"` |
145
- | `Hide::SecretKey.generate` / `.open(bytes, passphrase = nil)` | keys |
146
- | `Hide::SigningIdentity.generate(passphrase)` | sealed key file bytes |
147
- | `Hide::SigningIdentity.open(bytes, passphrase = nil)` | `#public_key`, `#sign`, `#answer`, `#close` |
148
- | `Hide.verify(public_key, context, message, signature)` | raises unless it verifies |
149
- | `Hide.new_challenge(audience, now, valid_for)` | challenge bytes |
150
- | `Hide::SpentNonces#accept(challenge, signature, public_key, now)` | accepts once |
151
- | `Hide::PUBLIC_KEY_LEN` (1216), `Hide::MIN_PASSPHRASE_LEN` (8) | constants |
152
- | `Hide::SIGNATURE_LEN` (3373), `Hide::VERIFYING_KEY_LEN` (1984), `Hide::NONCE_LEN` (32) | constants |
114
+ The core is located in this order:
153
115
 
154
- Encryption takes between 1 and 64 recipients, each public key exactly
155
- `Hide::PUBLIC_KEY_LEN` bytes.
116
+ 1. `HIDE_LIBRARY`, if it names a file **and** `HIDE_ALLOW_LIBRARY_OVERRIDE=1`
117
+ is also set;
118
+ 2. beside the gem, in `lib/hide_protocol/`, where the platform gem puts it;
119
+ 3. the system loader, by name (`hide_ffi.dll`, `libhide_ffi.dylib`,
120
+ `libhide_ffi.so`).
156
121
 
157
- All binary values in and out are ASCII-8BIT (binary) Strings. Metadata comes
158
- back as UTF-8.
122
+ `HIDE_LIBRARY` is a development override: it replaces the entire cryptographic
123
+ core, so a single settable environment variable must not be enough to redirect
124
+ it. Against a local build:
159
125
 
160
- ### Errors
126
+ ```sh
127
+ cargo build --release -p hide-ffi
128
+ export HIDE_LIBRARY="$PWD/target/release/libhide_ffi.so" # hide_ffi.dll / libhide_ffi.dylib
129
+ export HIDE_ALLOW_LIBRARY_OVERRIDE=1
130
+ ruby -Ilib -Itest test/test_hide.rb
131
+ ```
161
132
 
162
- Everything raises a subclass of `Hide::Error`:
133
+ ## Key material
163
134
 
164
- `InvalidArgumentError`, `AuthenticationError` (altered data, or not a
165
- container), `WrongPassphraseError`, `NoMatchingRecipientError` (this key was
166
- not a recipient), `NotAKeyError`, `TooLargeError`, `ClosedKeyError`,
167
- `ChallengeExpiredError`, `ChallengeReplayedError`.
135
+ `Hide::SecretKey`, `Hide::SigningIdentity` and `Hide::SpentNonces` are opaque
136
+ handles. The seed bytes never cross into Ruby and this gem exposes no accessor
137
+ for them; `inspect` and `to_s` show only whether the handle is open. Release
138
+ with `#close`, or use the block form of `.generate` / `.open`, which always
139
+ closes — including when the block raises. A closed handle raises
140
+ `Hide::ClosedKeyError` on use.
168
141
 
169
- ## Tests
142
+ ## Limits
170
143
 
171
- ```sh
172
- HIDE_LIBRARY=/path/to/libhide_ffi.so ruby -Ilib -Itest test/test_hide.rb
173
- ```
144
+ - Unaudited. Do not protect data you cannot afford to lose or expose.
145
+ - An identity is a key, not a person: a verified signature proves possession of
146
+ a seed, nothing about who holds it.
147
+ - Full threat model: [docs/threat-model.md](https://github.com/hide-protocol/hide/blob/main/docs/threat-model.md).
174
148
 
175
- ## Licence
149
+ ## Links
176
150
 
177
- Apache-2.0.
151
+ - Repository: <https://github.com/hide-protocol/hide>
152
+ - Documentation: [docs/](https://github.com/hide-protocol/hide/tree/main/docs)
153
+ - Specification: [spec/hide-0.1.md](https://github.com/hide-protocol/hide/blob/main/spec/hide-0.1.md)
154
+ - [CHANGELOG](https://github.com/hide-protocol/hide/blob/main/CHANGELOG.md)
@@ -55,7 +55,10 @@ module Hide
55
55
  candidates = library_names.map { |name| File.join(here, name) }
56
56
 
57
57
  override = ENV["HIDE_LIBRARY"]
58
- candidates.unshift(override) if override && !override.empty?
58
+ # HIDE_LIBRARY replaces the whole cryptographic core, so one settable env
59
+ # var must not be enough: it is honoured only with an explicit second opt-in.
60
+ allowed = ENV["HIDE_ALLOW_LIBRARY_OVERRIDE"] == "1"
61
+ candidates.unshift(override) if allowed && override && !override.empty?
59
62
 
60
63
  candidates.each do |candidate|
61
64
  return Fiddle.dlopen(candidate) if File.exist?(candidate)
@@ -66,8 +69,9 @@ module Hide
66
69
  rescue Fiddle::DLError
67
70
  raise LibraryNotFound,
68
71
  "the HIDE native library was not found. Install a gem that " \
69
- "bundles it, or set HIDE_LIBRARY to the path of " \
70
- "#{library_names.first} built by `cargo build -p hide-ffi`."
72
+ "bundles it. Developers: set HIDE_LIBRARY to the path of " \
73
+ "#{library_names.first} built by `cargo build -p hide-ffi` " \
74
+ "AND HIDE_ALLOW_LIBRARY_OVERRIDE=1."
71
75
  end
72
76
  end
73
77
 
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hide-protocol
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.2
4
+ version: 0.7.0
5
5
  platform: aarch64-linux
6
6
  authors:
7
7
  - HIDE contributors
@@ -44,6 +44,8 @@ metadata:
44
44
  homepage_uri: https://github.com/hide-protocol/hide
45
45
  source_code_uri: https://github.com/hide-protocol/hide
46
46
  bug_tracker_uri: https://github.com/hide-protocol/hide/issues
47
+ changelog_uri: https://github.com/hide-protocol/hide/blob/main/CHANGELOG.md
48
+ documentation_uri: https://github.com/hide-protocol/hide/blob/main/sdk/ruby/README.md
47
49
  rubygems_mfa_required: 'true'
48
50
  post_install_message:
49
51
  rdoc_options: []