Class: Protocol::ZMTP::Mechanism::Blake3
- Inherits:
-
Object
- Object
- Protocol::ZMTP::Mechanism::Blake3
- Defined in:
- lib/omq/blake3zmq/mechanism.rb
Overview
BLAKE3ZMQ security mechanism.
Provides X25519 key exchange, ChaCha20-BLAKE3 AEAD encryption, and BLAKE3 transcript hashing for ZMTP 3.1 connections.
Crypto-backend-agnostic: pass any module that provides the required interface via the crypto: parameter.
The crypto backend must provide:
backend::PrivateKey.generate / .new(bytes)
#public_key -> PublicKey, #to_s -> 32 bytes, #diffie_hellman(pub) -> 32 bytes
backend::PublicKey.new(bytes)
#to_s -> 32 bytes
backend::Cipher.new(key)
#encrypt(nonce, plaintext, aad:) -> ciphertext+tag
#decrypt(nonce, ciphertext+tag, aad:) -> plaintext
backend::Stream.new(key, nonce)
#encrypt(plaintext, aad:) -> ciphertext+tag
#decrypt(ciphertext+tag, aad:) -> plaintext
backend::Hash.digest(input) -> 32 bytes
backend::Hash.derive_key(context, material) -> 32 bytes
backend::Hash.derive_key(context, material, size: n) -> n bytes
backend.random_bytes(n) -> n bytes
backend::CryptoError (exception class)
backend::TAG_SIZE = 32
Constant Summary collapse
- MECHANISM_NAME =
"BLAKE3"- PROTOCOL_ID =
"BLAKE3ZMQ-1.0"- TAG_SIZE =
32- KEY_SIZE =
32- NONCE_SIZE =
24
Class Method Summary collapse
-
.client(server_key:, crypto: OMQ::Blake3ZMQ::Crypto, public_key: nil, secret_key: nil) ⇒ Blake3
Creates a BLAKE3 client mechanism.
-
.server(public_key:, secret_key:, crypto: OMQ::Blake3ZMQ::Crypto, authenticator: nil) ⇒ Blake3
Creates a BLAKE3 server mechanism.
Instance Method Summary collapse
-
#decrypt(frame) ⇒ Codec::Frame
Decrypts an encrypted ZMTP frame.
-
#encrypt(body, more: false, command: false) ⇒ String
Encrypts a ZMTP frame body for transmission.
-
#encrypted? ⇒ Boolean
Whether this mechanism encrypts traffic.
-
#handshake!(io, as_server:, socket_type:, identity:, metadata: nil) ⇒ Hash
Performs the BLAKE3ZMQ handshake over the given IO.
-
#initialize(public_key: nil, secret_key: nil, server_key: nil, crypto: OMQ::Blake3ZMQ::Crypto, as_server: false, authenticator: nil) ⇒ Blake3
constructor
Initializes a new BLAKE3 mechanism instance.
-
#initialize_dup(source) ⇒ Object
Resets stream state when duplicating the mechanism.
-
#maintenance ⇒ Hash?
Returns a maintenance task that rotates the server cookie key.
Constructor Details
#initialize(public_key: nil, secret_key: nil, server_key: nil, crypto: OMQ::Blake3ZMQ::Crypto, as_server: false, authenticator: nil) ⇒ Blake3
Initializes a new BLAKE3 mechanism instance.
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
# File 'lib/omq/blake3zmq/mechanism.rb', line 74 def initialize(public_key: nil, secret_key: nil, server_key: nil, crypto: OMQ::Blake3ZMQ::Crypto, as_server: false, authenticator: nil) @crypto = crypto @as_server = as_server @authenticator = authenticator if as_server validate_key!(public_key, "public_key") validate_key!(secret_key, "secret_key") @permanent_public = crypto::PublicKey.new(public_key.b) @permanent_secret = crypto::PrivateKey.new(secret_key.b) @cookie_key = crypto.random_bytes(KEY_SIZE) else validate_key!(server_key, "server_key") @server_public = crypto::PublicKey.new(server_key.b) if public_key && secret_key validate_key!(public_key, "public_key") validate_key!(secret_key, "secret_key") @permanent_public = crypto::PublicKey.new(public_key.b) @permanent_secret = crypto::PrivateKey.new(secret_key.b) else @permanent_secret = crypto::PrivateKey.generate @permanent_public = @permanent_secret.public_key end end @send_stream = nil @recv_stream = nil end |
Class Method Details
.client(server_key:, crypto: OMQ::Blake3ZMQ::Crypto, public_key: nil, secret_key: nil) ⇒ Blake3
Creates a BLAKE3 client mechanism.
61 62 63 |
# File 'lib/omq/blake3zmq/mechanism.rb', line 61 def self.client(server_key:, crypto: OMQ::Blake3ZMQ::Crypto, public_key: nil, secret_key: nil) new(public_key:, secret_key:, server_key:, crypto:, as_server: false) end |
.server(public_key:, secret_key:, crypto: OMQ::Blake3ZMQ::Crypto, authenticator: nil) ⇒ Blake3
Creates a BLAKE3 server mechanism.
49 50 51 |
# File 'lib/omq/blake3zmq/mechanism.rb', line 49 def self.server(public_key:, secret_key:, crypto: OMQ::Blake3ZMQ::Crypto, authenticator: nil) new(public_key:, secret_key:, crypto:, as_server: true, authenticator:) end |
Instance Method Details
#decrypt(frame) ⇒ Codec::Frame
Decrypts an encrypted ZMTP frame.
The AAD is reconstructed from the exact wire bytes the codec parsed: the flags byte (with LONG bit if the frame was long) and the length encoding that followed it.
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 |
# File 'lib/omq/blake3zmq/mechanism.rb', line 202 def decrypt(frame) flags = 0 flags |= 0x01 if frame.more? flags |= 0x04 if frame.command? frame_size = frame.body.bytesize if frame_size > 255 wire_flags = (flags | 0x02).chr length_bytes = [frame_size].pack("Q>") else wire_flags = flags.chr length_bytes = frame_size.chr end aad = wire_flags + length_bytes begin pt = @recv_stream.decrypt(frame.body, aad: aad) rescue @crypto::CryptoError raise Error, "decryption failed" end Codec::Frame.new(pt, more: frame.more?, command: frame.command?) end |
#encrypt(body, more: false, command: false) ⇒ String
Encrypts a ZMTP frame body for transmission.
The AEAD AAD per RFC §10.3 is ‘flags_byte || length_bytes` —every wire byte that is not itself encrypted. This binds the full wire envelope (including the LONG bit and the size field) so any single-bit modification fails verification.
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 |
# File 'lib/omq/blake3zmq/mechanism.rb', line 169 def encrypt(body, more: false, command: false) flags = 0 flags |= 0x01 if more flags |= 0x04 if command # ChaCha20-BLAKE3 ciphertext is plaintext + 32-byte tag. frame_size = body.bytesize + TAG_SIZE if frame_size > 255 wire_flags = (flags | 0x02).chr length_bytes = [frame_size].pack("Q>") else wire_flags = flags.chr length_bytes = frame_size.chr end aad = wire_flags + length_bytes ct = @send_stream.encrypt(body, aad: aad) wire = String.new(encoding: Encoding::BINARY, capacity: aad.bytesize + frame_size) wire << aad << ct end |
#encrypted? ⇒ Boolean
Whether this mechanism encrypts traffic.
121 122 123 |
# File 'lib/omq/blake3zmq/mechanism.rb', line 121 def encrypted? true end |
#handshake!(io, as_server:, socket_type:, identity:, metadata: nil) ⇒ Hash
Performs the BLAKE3ZMQ handshake over the given IO.
Delegates to the client or server handshake depending on role.
149 150 151 152 153 154 155 |
# File 'lib/omq/blake3zmq/mechanism.rb', line 149 def handshake!(io, as_server:, socket_type:, identity:, metadata: nil) if @as_server server_handshake!(io, socket_type:, identity:, metadata:) else client_handshake!(io, socket_type:, identity:, metadata:) end end |
#initialize_dup(source) ⇒ Object
Resets stream state when duplicating the mechanism.
111 112 113 114 115 |
# File 'lib/omq/blake3zmq/mechanism.rb', line 111 def initialize_dup(source) super @send_stream = nil @recv_stream = nil end |
#maintenance ⇒ Hash?
Returns a maintenance task that rotates the server cookie key.
129 130 131 132 133 134 135 136 |
# File 'lib/omq/blake3zmq/mechanism.rb', line 129 def maintenance return unless @as_server { interval: 60, task: -> { @cookie_key = @crypto.random_bytes(KEY_SIZE) } }.freeze end |