Class: Protocol::ZMTP::Mechanism::Curve
- Inherits:
-
Object
- Object
- Protocol::ZMTP::Mechanism::Curve
- Defined in:
- lib/protocol/zmtp/mechanism/curve.rb
Overview
CurveZMQ security mechanism (RFC 26).
Provides Curve25519-XSalsa20-Poly1305 encryption and authentication for ZMTP 3.1 connections.
Crypto-backend-agnostic: pass any module that provides the NaCl API (RbNaCl or Nuckle) via the crypto: parameter.
The crypto backend must provide:
backend::PrivateKey.new(bytes) / .generate
backend::PublicKey.new(bytes)
backend::Box.new(peer_pub, my_secret) → #encrypt(nonce, pt) / #decrypt(nonce, ct)
backend::SecretBox.new(key) → #encrypt(nonce, pt) / #decrypt(nonce, ct)
backend::Random.random_bytes(n)
backend::Util.verify32(a, b) / .verify64(a, b)
backend::CryptoError (exception class)
Constant Summary collapse
- MECHANISM_NAME =
"CURVE"- NONCE_PREFIX_HELLO =
Nonce prefixes.
"CurveZMQHELLO---"- NONCE_PREFIX_WELCOME =
"WELCOME-"- NONCE_PREFIX_INITIATE =
"CurveZMQINITIATE"- NONCE_PREFIX_READY =
"CurveZMQREADY---"- NONCE_PREFIX_MESSAGE_C =
"CurveZMQMESSAGEC"- NONCE_PREFIX_MESSAGE_S =
"CurveZMQMESSAGES"- NONCE_PREFIX_VOUCH =
"VOUCH---"- NONCE_PREFIX_COOKIE =
"COOKIE--"- BOX_OVERHEAD =
16- MAX_NONCE =
(2**64) - 1
- MESSAGE_PREFIX =
"\x07MESSAGE".b.freeze
- MESSAGE_PREFIX_SIZE =
MESSAGE_PREFIX.bytesize
Class Method Summary collapse
-
.client(server_key:, crypto:, public_key: nil, secret_key: nil) ⇒ Curve
Creates a CURVE client mechanism.
-
.server(public_key:, secret_key:, crypto:, authenticator: nil) ⇒ Curve
Creates a CURVE server mechanism.
Instance Method Summary collapse
-
#decrypt(frame) ⇒ Codec::Frame
Decrypts a CURVE MESSAGE command frame back into a plaintext frame.
-
#encrypt(body, more: false, command: false) ⇒ String
Encrypts a frame body into a CURVE MESSAGE command on the wire.
-
#encrypted? ⇒ Boolean
True – CURVE always encrypts frames.
-
#handshake!(io, as_server:, socket_type:, identity:, metadata: nil) ⇒ Hash
Performs the full CurveZMQ handshake (HELLO/WELCOME/INITIATE/READY).
-
#initialize(public_key: nil, secret_key: nil, server_key: nil, crypto:, as_server: false, authenticator: nil) ⇒ Curve
constructor
A new instance of Curve.
-
#initialize_dup(source) ⇒ void
Resets session state when duplicating (e.g. for a new connection).
-
#maintenance ⇒ Hash?
Returns a periodic maintenance task for rotating the cookie key (server only).
Constructor Details
#initialize(public_key: nil, secret_key: nil, server_key: nil, crypto:, as_server: false, authenticator: nil) ⇒ Curve
Returns a new instance of Curve.
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 |
# File 'lib/protocol/zmtp/mechanism/curve.rb', line 74 def initialize(public_key: nil, secret_key: nil, server_key: nil, 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.random_bytes(32) 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 @session_box = nil @send_nonce = 0 @recv_nonce = -1 end |
Class Method Details
.client(server_key:, crypto:, public_key: nil, secret_key: nil) ⇒ Curve
Creates a CURVE client mechanism.
63 64 65 |
# File 'lib/protocol/zmtp/mechanism/curve.rb', line 63 def self.client(server_key:, 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:, authenticator: nil) ⇒ Curve
Creates a CURVE server mechanism.
51 52 53 |
# File 'lib/protocol/zmtp/mechanism/curve.rb', line 51 def self.server(public_key:, secret_key:, crypto:, authenticator: nil) new(public_key:, secret_key:, crypto:, as_server: true, authenticator:) end |
Instance Method Details
#decrypt(frame) ⇒ Codec::Frame
Decrypts a CURVE MESSAGE command frame back into a plaintext frame.
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 |
# File 'lib/protocol/zmtp/mechanism/curve.rb', line 188 def decrypt(frame) body = frame.body unless body.start_with?(MESSAGE_PREFIX) raise Error, "expected MESSAGE command" end data = body.byteslice(MESSAGE_PREFIX_SIZE..) raise Error, "MESSAGE too short" if data.bytesize < 8 + BOX_OVERHEAD short_nonce = data.byteslice(0, 8) ciphertext = data.byteslice(8..) nonce_value = short_nonce.unpack1("Q>") unless nonce_value > @recv_nonce raise Error, "MESSAGE nonce not strictly incrementing" end @recv_nonce = nonce_value @recv_nonce_buf[16, 8] = short_nonce begin plaintext = @session_box.decrypt(@recv_nonce_buf, ciphertext) rescue @crypto::CryptoError raise Error, "MESSAGE decryption failed" end flags = plaintext.getbyte(0) body = plaintext.byteslice(1..) || "".b Codec::Frame.new(body, more: (flags & 0x01) != 0, command: (flags & 0x04) != 0) end |
#encrypt(body, more: false, command: false) ⇒ String
Encrypts a frame body into a CURVE MESSAGE command on the wire.
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 |
# File 'lib/protocol/zmtp/mechanism/curve.rb', line 155 def encrypt(body, more: false, command: false) flags = 0 flags |= 0x01 if more flags |= 0x04 if command plaintext = String.new(encoding: Encoding::BINARY, capacity: 1 + body.bytesize) plaintext << flags << body nonce = make_send_nonce ciphertext = @session_box.encrypt(nonce, plaintext) short_nonce = nonce.byteslice(16, 8) msg_body_size = 16 + ciphertext.bytesize if msg_body_size > 255 wire = String.new(encoding: Encoding::BINARY, capacity: 9 + msg_body_size) wire << "\x02" << [msg_body_size].pack("Q>") else wire = String.new(encoding: Encoding::BINARY, capacity: 2 + msg_body_size) wire << "\x00" << msg_body_size end wire << "\x07MESSAGE" << short_nonce << ciphertext end |
#encrypted? ⇒ Boolean
Returns true – CURVE always encrypts frames.
120 |
# File 'lib/protocol/zmtp/mechanism/curve.rb', line 120 def encrypted? = true |
#handshake!(io, as_server:, socket_type:, identity:, metadata: nil) ⇒ Hash
Performs the full CurveZMQ handshake (HELLO/WELCOME/INITIATE/READY).
140 141 142 143 144 145 146 |
# File 'lib/protocol/zmtp/mechanism/curve.rb', line 140 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) ⇒ void
This method returns an undefined value.
Resets session state when duplicating (e.g. for a new connection).
109 110 111 112 113 114 115 116 |
# File 'lib/protocol/zmtp/mechanism/curve.rb', line 109 def initialize_dup(source) super @session_box = nil @send_nonce = 0 @recv_nonce = -1 @send_nonce_buf = nil @recv_nonce_buf = nil end |
#maintenance ⇒ Hash?
Returns a periodic maintenance task for rotating the cookie key (server only).
125 126 127 128 |
# File 'lib/protocol/zmtp/mechanism/curve.rb', line 125 def maintenance return unless @as_server { interval: 60, task: -> { @cookie_key = @crypto::Random.random_bytes(32) } }.freeze end |