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 +4 -4
- data/README.md +118 -141
- data/lib/hide_protocol/binding.rb +7 -3
- data/lib/hide_protocol/libhide_ffi.so +0 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cc6c7917200f35b460601ae243fbb5d0c2d9ea72c9c38e405a51da5f929aaef8
|
|
4
|
+
data.tar.gz: 13a067070d1cfdd059e9b0daf67d6e147bf256daae5684a8991b78cf63ac7893
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 494aa783321268b9ed8d96563e21cd0def993acf97634078df8371f546268f6018e539ec0b40a4b7fb487097c4db1cf91047be79594e0a8f4eb09e57bc6d6c09
|
|
7
|
+
data.tar.gz: 1e8dbc3a262ad94c23a342493935a0fc447a3dd02027eb2c4ed5449f7271d5435c5734a5091f2e92e8e78e8c1c19c43c41cd9cec09493881f947225c549125c3
|
data/README.md
CHANGED
|
@@ -1,177 +1,154 @@
|
|
|
1
|
-
# hide-protocol
|
|
1
|
+
# hide-protocol for Ruby
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
12
|
+
gem install hide-protocol
|
|
38
13
|
```
|
|
39
14
|
|
|
40
|
-
|
|
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
|
-
|
|
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
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
75
|
-
a
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
`
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
134
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
155
|
-
|
|
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
|
-
|
|
158
|
-
|
|
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
|
-
|
|
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
|
-
|
|
133
|
+
## Key material
|
|
163
134
|
|
|
164
|
-
`
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
##
|
|
142
|
+
## Limits
|
|
170
143
|
|
|
171
|
-
|
|
172
|
-
|
|
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
|
-
##
|
|
149
|
+
## Links
|
|
176
150
|
|
|
177
|
-
|
|
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
|
-
|
|
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
|
-
|
|
70
|
-
|
|
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.
|
|
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: []
|