Class: DEVp2p::RLPxSession
- Inherits:
-
Object
- Object
- DEVp2p::RLPxSession
- Extended by:
- Configurable
- Defined in:
- lib/devp2p/rlpx_session.rb
Constant Summary collapse
- SUPPORTED_RLPX_VERSION =
4- ENC_CIPHER =
'AES-256-CTR'- MAC_CIPHER =
'AES-256-ECB'
Instance Attribute Summary collapse
-
#ecc ⇒ Object
readonly
Returns the value of attribute ecc.
-
#ephemeral_ecc ⇒ Object
readonly
Returns the value of attribute ephemeral_ecc.
-
#initiator_nonce ⇒ Object
readonly
Returns the value of attribute initiator_nonce.
-
#remote_ephemeral_pubkey ⇒ Object
readonly
Returns the value of attribute remote_ephemeral_pubkey.
-
#remote_pubkey ⇒ Object
readonly
Returns the value of attribute remote_pubkey.
-
#remote_version ⇒ Object
readonly
Returns the value of attribute remote_version.
-
#responder_nonce ⇒ Object
readonly
Returns the value of attribute responder_nonce.
Instance Method Summary collapse
- #aes_dec(data = '') ⇒ Object
- #aes_enc(data = '') ⇒ Object
-
#create_auth_ack_message(ephemeral_pubkey = nil, nonce = nil, version = SUPPORTED_RLPX_VERSION, eip8 = false) ⇒ Object
authRecipient = E(remote-pubk, remote-ephemeral-pubk || nonce || 0x1) // token found authRecipient = E(remote-pubk, remote-ephemeral-pubk || nonce || 0x0) // token not found.
-
#create_auth_message(remote_pubkey, ephemeral_privkey = nil, nonce = nil) ⇒ Object
1.
- #create_eip8_auth_ack_message(ephemeral_pubkey, nonce, version) ⇒ Object
- #decode_auth_ack_message(ciphertext) ⇒ Object
-
#decode_authentication(ciphertext) ⇒ Object
3.
- #decrypt(data) ⇒ Object
- #decrypt_body(data, body_size) ⇒ Object
- #decrypt_header(data) ⇒ Object
- #egress_mac(data = '') ⇒ Object
-
#encrypt(header, frame) ⇒ Object
Frame Handling.
- #encrypt_auth_ack_message(ack_message, eip8 = false, remote_pubkey = nil) ⇒ Object
- #encrypt_auth_message(auth_message, remote_pubkey = nil) ⇒ Object
- #ingress_mac(data = '') ⇒ Object
-
#initialize(ecc, is_initiator = false, ephemeral_privkey = nil) ⇒ RLPxSession
constructor
A new instance of RLPxSession.
- #initiator? ⇒ Boolean
- #mac_enc(data) ⇒ Object
-
#ready? ⇒ Boolean
Helpers.
-
#setup_cipher ⇒ Object
Handshake Key Derivation.
Methods included from Configurable
Constructor Details
#initialize(ecc, is_initiator = false, ephemeral_privkey = nil) ⇒ RLPxSession
Returns a new instance of RLPxSession.
39 40 41 42 43 44 45 46 |
# File 'lib/devp2p/rlpx_session.rb', line 39 def initialize(ecc, is_initiator=false, ephemeral_privkey=nil) @ecc = ecc @is_initiator = is_initiator @ephemeral_ecc = Crypto::ECCx.new ephemeral_privkey @ready = false @got_eip8_auth, @got_eip8_ack = false, false end |
Instance Attribute Details
#ecc ⇒ Object (readonly)
Returns the value of attribute ecc.
35 36 37 |
# File 'lib/devp2p/rlpx_session.rb', line 35 def ecc @ecc end |
#ephemeral_ecc ⇒ Object (readonly)
Returns the value of attribute ephemeral_ecc.
35 36 37 |
# File 'lib/devp2p/rlpx_session.rb', line 35 def ephemeral_ecc @ephemeral_ecc end |
#initiator_nonce ⇒ Object (readonly)
Returns the value of attribute initiator_nonce.
35 36 37 |
# File 'lib/devp2p/rlpx_session.rb', line 35 def initiator_nonce @initiator_nonce end |
#remote_ephemeral_pubkey ⇒ Object (readonly)
Returns the value of attribute remote_ephemeral_pubkey.
35 36 37 |
# File 'lib/devp2p/rlpx_session.rb', line 35 def remote_ephemeral_pubkey @remote_ephemeral_pubkey end |
#remote_pubkey ⇒ Object (readonly)
Returns the value of attribute remote_pubkey.
35 36 37 |
# File 'lib/devp2p/rlpx_session.rb', line 35 def remote_pubkey @remote_pubkey end |
#remote_version ⇒ Object (readonly)
Returns the value of attribute remote_version.
35 36 37 |
# File 'lib/devp2p/rlpx_session.rb', line 35 def remote_version @remote_version end |
#responder_nonce ⇒ Object (readonly)
Returns the value of attribute responder_nonce.
35 36 37 |
# File 'lib/devp2p/rlpx_session.rb', line 35 def responder_nonce @responder_nonce end |
Instance Method Details
#aes_dec(data = '') ⇒ Object
335 336 337 |
# File 'lib/devp2p/rlpx_session.rb', line 335 def aes_dec(data='') @aes_dec.update data end |
#aes_enc(data = '') ⇒ Object
331 332 333 |
# File 'lib/devp2p/rlpx_session.rb', line 331 def aes_enc(data='') @aes_enc.update data end |
#create_auth_ack_message(ephemeral_pubkey = nil, nonce = nil, version = SUPPORTED_RLPX_VERSION, eip8 = false) ⇒ Object
authRecipient = E(remote-pubk, remote-ephemeral-pubk || nonce || 0x1) // token found authRecipient = E(remote-pubk, remote-ephemeral-pubk || nonce || 0x0) // token not found
nonce, ephemeral_pubkey, version are local
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 |
# File 'lib/devp2p/rlpx_session.rb', line 208 def (ephemeral_pubkey=nil, nonce=nil, version=SUPPORTED_RLPX_VERSION, eip8=false) raise RLPxSessionError, 'must not be initiator' if initiator? ephemeral_pubkey = ephemeral_pubkey || @ephemeral_ecc.raw_pubkey @responder_nonce = nonce || Crypto.keccak256(Utils.int_to_big_endian(SecureRandom.random_number(TT256))) if eip8 || @got_eip8_auth msg = ephemeral_pubkey, @responder_nonce, version raise RLPxSessionError, 'invalid msg size' unless msg.size > 97 else msg = "#{ephemeral_pubkey}#{@responder_nonce}\x00" raise RLPxSessionError, 'invalid msg size' unless msg.size == 97 end msg end |
#create_auth_message(remote_pubkey, ephemeral_privkey = nil, nonce = nil) ⇒ Object
-
initiator generates ecdhe-random and nonce and creates auth
-
initiator connects to remote and sends auth
New:
E(remote-pubk,
S(ephemeral-privk, ecdh-shared-secret ^ nonce) ||
H(ephemeral-pubk) || pubk || nonce || 0x0
)
Known:
E(remote-pubk,
S(ephemeral-privk, token ^ nonce) ||
H(ephemeral-pubk) || pubk || nonce || 0x1
)
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
# File 'lib/devp2p/rlpx_session.rb', line 128 def (remote_pubkey, ephemeral_privkey=nil, nonce=nil) raise RLPxSessionError, 'must be initiator' unless initiator? raise InvalidKeyError, 'invalid remote pubkey' unless Crypto::ECCx.valid_key?(remote_pubkey) @remote_pubkey = remote_pubkey token = @ecc.get_ecdh_key remote_pubkey flag = 0x0 @initiator_nonce = nonce || Crypto.keccak256(Utils.int_to_big_endian(SecureRandom.random_number(TT256))) raise RLPxSessionError, 'invalid nonce length' unless @initiator_nonce.size == 32 token_xor_nonce = Utils.sxor token, @initiator_nonce raise RLPxSessionError, 'invalid token xor nonce length' unless token_xor_nonce.size == 32 ephemeral_pubkey = @ephemeral_ecc.raw_pubkey raise InvalidKeyError, 'invalid ephemeral pubkey' unless ephemeral_pubkey.size == 512 / 8 && Crypto::ECCx.valid_key?(ephemeral_pubkey) sig = @ephemeral_ecc.sign token_xor_nonce raise RLPxSessionError, 'invalid signature' unless sig.size == 65 = "#{sig}#{Crypto.keccak256(ephemeral_pubkey)}#{@ecc.raw_pubkey}#{@initiator_nonce}#{flag.chr}" raise RLPxSessionError, 'invalid auth message length' unless .size == 194 end |
#create_eip8_auth_ack_message(ephemeral_pubkey, nonce, version) ⇒ Object
225 226 227 228 229 |
# File 'lib/devp2p/rlpx_session.rb', line 225 def (ephemeral_pubkey, nonce, version) data = RLP.encode [ephemeral_pubkey, nonce, version], sedes: eip8_ack_sedes pad = SecureRandom.random_bytes(SecureRandom.random_number(151)+100) # (100..150) random bytes "#{data}#{pad}" end |
#decode_auth_ack_message(ciphertext) ⇒ Object
248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 |
# File 'lib/devp2p/rlpx_session.rb', line 248 def (ciphertext) raise RLPxSessionError, 'must be initiator' unless initiator? raise ArgumentError, 'invalid ciphertext length' unless ciphertext.size >= 210 result = nil begin result = decode_ack_plain ciphertext rescue AuthenticationError result = decode_ack_eip8 ciphertext @got_eip8_ack = true end size, ephemeral_pubkey, nonce, version = result @auth_ack = ciphertext[0,size] @remote_ephemeral_pubkey = ephemeral_pubkey[0,64] @responder_nonce = nonce @remote_version = version raise InvalidKeyError, 'invalid remote ephemeral pubkey' unless Crypto::ECCx.valid_key?(@remote_ephemeral_pubkey) ciphertext[size..-1] end |
#decode_authentication(ciphertext) ⇒ Object
-
optionally, remote decrypts and verifies auth (checks that recovery of
signature == H(ephemeral-pubk))
-
remote generates authAck from remote-ephemeral-pubk and nonce (authAck
= authRecipient handshake)
optional: remote derives secrets and preemptively sends protocol-handshake (steps 9,11,8,10)
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 |
# File 'lib/devp2p/rlpx_session.rb', line 174 def decode_authentication(ciphertext) raise RLPxSessionError, 'must not be initiator' if initiator? raise ArgumentError, 'invalid ciphertext length' unless ciphertext.size >= 307 result = nil begin result = decode_auth_plain ciphertext rescue AuthenticationError result = decode_auth_eip8 ciphertext @got_eip8_auth = true end size, sig, initiator_pubkey, nonce, version = result @auth_init = ciphertext[0, size] token = @ecc.get_ecdh_key initiator_pubkey @remote_ephemeral_pubkey = Crypto.ecdsa_recover(Utils.sxor(token, nonce), sig) raise InvalidKeyError, 'invalid remote ephemeral pubkey' unless Crypto::ECCx.valid_key?(@remote_ephemeral_pubkey) @initiator_nonce = nonce @remote_pubkey = initiator_pubkey @remote_version = version ciphertext[size..-1] end |
#decrypt(data) ⇒ Object
97 98 99 100 101 102 103 104 105 106 |
# File 'lib/devp2p/rlpx_session.rb', line 97 def decrypt(data) header = decrypt_header data[0,32] body_size = Frame.decode_body_size header len = 32 + Utils.ceil16(body_size) + 16 raise FormatError, 'insufficient body length' unless data.size >= len frame = decrypt_body data[32..-1], body_size {header: header, frame: frame, bytes_read: len} end |
#decrypt_body(data, body_size) ⇒ Object
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/devp2p/rlpx_session.rb', line 80 def decrypt_body(data, body_size) raise RLPxSessionError, 'not ready' unless ready? read_size = Utils.ceil16 body_size raise FormatError, 'insufficient body length' unless data.size >= read_size + 16 frame_ciphertext = data[0, read_size] frame_mac = data[read_size, 16] raise RLPxSessionError, 'invalid frame mac length' unless frame_mac.size == 16 fmac_seed = ingress_mac frame_ciphertext expected_frame_mac = ingress_mac(Utils.sxor(mac_enc(ingress_mac[0,16]), fmac_seed[0,16]))[0,16] raise AuthenticationError, 'invalid frame mac' unless expected_frame_mac == frame_mac aes_dec(frame_ciphertext)[0,body_size] end |
#decrypt_header(data) ⇒ Object
67 68 69 70 71 72 73 74 75 76 77 78 |
# File 'lib/devp2p/rlpx_session.rb', line 67 def decrypt_header(data) raise RLPxSessionError, 'not ready' unless ready? raise ArgumentError, 'invalid data length' unless data.size == 32 header_ciphertext = data[0,16] header_mac = data[16,16] expected_header_mac = ingress_mac(Utils.sxor(mac_enc(ingress_mac[0,16]), header_ciphertext))[0,16] raise AuthenticationError, 'invalid header mac' unless expected_header_mac == header_mac aes_dec header_ciphertext end |
#egress_mac(data = '') ⇒ Object
339 340 341 342 |
# File 'lib/devp2p/rlpx_session.rb', line 339 def egress_mac(data='') @egress_mac.update data return @egress_mac.digest end |
#encrypt(header, frame) ⇒ Object
Frame Handling
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
# File 'lib/devp2p/rlpx_session.rb', line 50 def encrypt(header, frame) raise RLPxSessionError, 'not ready' unless ready? raise ArgumentError, 'invalid header length' unless header.size == 16 raise ArgumentError, 'invalid frame padding' unless frame.size % 16 == 0 header_ciphertext = aes_enc header raise RLPxSessionError unless header_ciphertext.size == header.size header_mac = egress_mac(Utils.sxor(mac_enc(egress_mac[0,16]), header_ciphertext))[0,16] frame_ciphertext = aes_enc frame raise RLPxSessionError unless frame_ciphertext.size == frame.size fmac_seed = egress_mac frame_ciphertext frame_mac = egress_mac(Utils.sxor(mac_enc(egress_mac[0,16]), fmac_seed[0,16]))[0,16] header_ciphertext + header_mac + frame_ciphertext + frame_mac end |
#encrypt_auth_ack_message(ack_message, eip8 = false, remote_pubkey = nil) ⇒ Object
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 |
# File 'lib/devp2p/rlpx_session.rb', line 231 def (, eip8=false, remote_pubkey=nil) raise RLPxSessionError, 'must not be initiator' if initiator? remote_pubkey ||= @remote_pubkey if eip8 || @got_eip8_auth # The EIP-8 version has an authenticated length prefix prefix = [.size + Crypto::ECIES::ENCRYPT_OVERHEAD_LENGTH].pack("S>") @auth_ack = "#{prefix}#{@ecc.ecies_encrypt(ack_message, remote_pubkey, prefix)}" else @auth_ack = @ecc.ecies_encrypt , remote_pubkey raise RLPxSessionError, 'invalid auth ack message length' unless @auth_ack.size == 210 end @auth_ack end |
#encrypt_auth_message(auth_message, remote_pubkey = nil) ⇒ Object
155 156 157 158 159 160 161 162 163 |
# File 'lib/devp2p/rlpx_session.rb', line 155 def (, remote_pubkey=nil) raise RLPxSessionError, 'must be initiator' unless initiator? remote_pubkey ||= @remote_pubkey @auth_init = @ecc.ecies_encrypt , remote_pubkey raise RLPxSessionError, 'invalid encrypted auth message length' unless @auth_init.size == 307 @auth_init end |
#ingress_mac(data = '') ⇒ Object
344 345 346 347 |
# File 'lib/devp2p/rlpx_session.rb', line 344 def ingress_mac(data='') @ingress_mac.update data return @ingress_mac.digest end |
#initiator? ⇒ Boolean
323 324 325 |
# File 'lib/devp2p/rlpx_session.rb', line 323 def initiator? @is_initiator end |
#mac_enc(data) ⇒ Object
327 328 329 |
# File 'lib/devp2p/rlpx_session.rb', line 327 def mac_enc(data) @mac_enc.update data end |
#ready? ⇒ Boolean
Helpers
319 320 321 |
# File 'lib/devp2p/rlpx_session.rb', line 319 def ready? @ready end |
#setup_cipher ⇒ Object
Handshake Key Derivation
273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 |
# File 'lib/devp2p/rlpx_session.rb', line 273 def setup_cipher raise RLPxSessionError, 'missing responder nonce' unless @responder_nonce raise RLPxSessionError, 'missing initiator_nonce' unless @initiator_nonce raise RLPxSessionError, 'missing auth_init' unless @auth_init raise RLPxSessionError, 'missing auth_ack' unless @auth_ack raise RLPxSessionError, 'missing remote ephemeral pubkey' unless @remote_ephemeral_pubkey raise InvalidKeyError, 'invalid remote ephemeral pubkey' unless Crypto::ECCx.valid_key?(@remote_ephemeral_pubkey) # derive base secrets from ephemeral key agreement # ecdhe-shared-secret = ecdh.agree(ephemeral-privkey, remote-ephemeral-pubk) @ecdhe_shared_secret = @ephemeral_ecc.get_ecdh_key(@remote_ephemeral_pubkey) @shared_secret = Crypto.keccak256("#{@ecdhe_shared_secret}#{Crypto.keccak256(@responder_nonce + @initiator_nonce)}") @token = Crypto.keccak256 @shared_secret @aes_secret = Crypto.keccak256 "#{@ecdhe_shared_secret}#{@shared_secret}" @mac_secret = Crypto.keccak256 "#{@ecdhe_shared_secret}#{@aes_secret}" mac1 = keccak256 "#{Utils.sxor(@mac_secret, @responder_nonce)}#{@auth_init}" mac2 = keccak256 "#{Utils.sxor(@mac_secret, @initiator_nonce)}#{@auth_ack}" if initiator? @egress_mac, @ingress_mac = mac1, mac2 else @egress_mac, @ingress_mac = mac2, mac1 end iv = "\x00" * 16 @aes_enc = OpenSSL::Cipher.new(ENC_CIPHER).tap do |c| c.encrypt c.iv = iv c.key = @aes_secret end @aes_dec = OpenSSL::Cipher.new(ENC_CIPHER).tap do |c| c.decrypt c.iv = iv c.key = @aes_secret end @mac_enc = OpenSSL::Cipher.new(MAC_CIPHER).tap do |c| c.encrypt c.key = @mac_secret end @ready = true end |