Last active
July 19, 2018 15:16
-
-
Save grantr/4757832 to your computer and use it in GitHub Desktop.
Revisions
-
grantr revised this gist
Feb 12, 2013 . 1 changed file with 93 additions and 6 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -6,6 +6,61 @@ # messages can be exchanged (3 before the server can send application messages) # * Does not require the server to keep protocol state between handshake messages. # # An overview of the protocol: # # Definitions: # S : Server long term public key # S': Server short term public key # s': Server short term private key # C : Client long term public key # C': Client short term public key # V : Vouch: # 16 byte nonce + Box to S from C containing C' # K : Cookie: # 16 byte nonce + SecretBox under minute-key containing C' and s' # minute-key: A 32-byte random string rotated every minute (the current and # previous key are both valid) # # Prerequisites: # Client knows S and the domain name of the server # # The protocol flow: # # (Note some elements of messages are omitted here for clarity, see the CurveCP # site for details) # # 1. Client sends HelloMessage # - C' # - 8 byte nonce # - 64 null bytes encrypted with a Box to S from C' # # 2. Server sends CookieMessage # - 16 byte nonce # - Box to C' from S containing S' and K # # 3. Client sends InitiateMessage # - C' # - K # - 8-byte nonce # - Box to S' from C' containing: # - C # - V # - server's domain name # - a message (optional) # # The handshake has concluded at this point, and both server and client are # free to send messages. # # 4. Server sends Message # - 8 byte nonce # - Box to C' from S' containing a message # # 5. Client sends Message # - C' # - 8 byte nonce # - Box to S' from C' containing a message # # # ALERT This implementation has not been inspected or verified by cryptography # experts. Additionally, the CurveCP protocol itself is a work in progress. While # the handshake protocol uses only proven primitives from the NaCL library, it @@ -104,6 +159,12 @@ def rotate_minute_key def accept accept_hello # ALERT For testing if block_given? yield end accept_initiate end @@ -260,7 +321,8 @@ def nonce_string # ALERT In real life, Hello messages should be constructed so their length # is greater than or equal to the length of Cookie messages. This is to # avoid an amplification attack whereby a client can use small bandwidth to # overwhelm a server with larger bandwidth. def valid?(server_long_term_privkey) string = Crypto::Box.new(@client_short_term_pubkey, server_long_term_privkey).open(nonce_string, @ciphertext) zeros = Crypto::Util.zeros(32) @@ -439,14 +501,39 @@ def connect(options={}) end it 'should raise if the server domain is incorrect' do client = Client.new server = Server.new server.domain_name = "foobar.com" connected = client.future.connect(server, domain_name: "foobaz.com") lambda { server.accept }.must_raise(RuntimeError) end it 'should not raise if the minute key has rotated once' do client = Client.new server = Server.new connected = client.future.connect(server) server.accept do server.rotate_minute_key end end it 'should raise if the minute key has rotated twice' do client = Client.new server = Server.new connected = client.future.connect(server) lambda { server.accept do server.rotate_minute_key server.rotate_minute_key end }.must_raise(Crypto::CryptoError) end end end -
grantr revised this gist
Feb 12, 2013 . 1 changed file with 55 additions and 12 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -30,9 +30,15 @@ def initialize(long_term_key=Crypto::PrivateKey.generate) end # returns a Connection def connect(server, options={}) # ALERT Normally the server long term key would be pre-distributed server_long_term_pubkey = options[:server_key] || server.long_term_public_key # ALERT Normally the domain name would be pre-distributed domain_name = options[:domain_name] || server.domain_name # The initial message is optional initial_message = options[:initial_message] # Generate a client short term key short_term_key = Crypto::PrivateKey.generate @@ -57,9 +63,8 @@ def connect(server, initial_message=nil) vouch = Vouch.generate(server_long_term_pubkey, long_term_key, short_term_key.public_key) # Generate an initiate message and send it to the server # This contains the initial message initiate_message = InitiateMessage.new(server_short_term_pubkey, short_term_key, cookie, long_term_key.public_key, vouch, domain_name, initial_message) server.mailbox << initiate_message # Now the connection can be used to send further messages @@ -144,7 +149,7 @@ def accept_initiate # If the current minute key doesn't work, try the previous one boxed_client_short_term_pubkey, short_term_key = begin cookie.open(minute_key) rescue Crypto::CryptoError cookie.open(prev_minute_key) end @@ -392,18 +397,56 @@ def open(box) require 'minitest/autorun' include CurveCPHandshake Celluloid.logger = nil def connect(options={}) client = Client.new server = Server.new connected = client.future.connect(server, options) accepted = server.future.accept client_connection = connected.value(0.1) server_connection, initial_message = accepted.value(0.1) [client_connection, server_connection, initial_message] end describe CurveCPHandshake do it 'should transmit an initial message' do _, _, initial_message = connect(initial_message: "hello!") initial_message.must_equal "hello!" end it 'should exchange further messages' do client_conn, server_conn = connect m1 = client_conn.box("message 1") m2 = server_conn.box("message 2") server_conn.open(m1).must_equal "message 1" client_conn.open(m2).must_equal "message 2" end it 'should raise if the server long term key is incorrect' do lambda { client = Client.new server = Server.new connected = client.future.connect(server, server_key: Crypto::PrivateKey.generate) server.accept }.must_raise(Crypto::CryptoError) end it 'should raise if the server domain is incorrect' do lambda { client = Client.new server = Server.new server.domain_name = "foobar.com" connected = client.future.connect(server, domain_name: "foobaz.com") server.accept }.must_raise(RuntimeError) end end end -
grantr revised this gist
Feb 11, 2013 . 1 changed file with 3 additions and 3 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -31,7 +31,7 @@ def initialize(long_term_key=Crypto::PrivateKey.generate) # returns a Connection def connect(server, initial_message=nil) # ALERT Normally this would be pre-distributed server_long_term_pubkey = server.long_term_public_key # Generate a client short term key @@ -41,7 +41,7 @@ def connect(server, initial_message=nil) hello_message = HelloMessage.new(server_long_term_pubkey, short_term_key) # add our mailbox so the server can reply # ALERT Normally this would be handled by the transport layer hello_message.reply_mailbox = Actor.current.mailbox # send the hello message to the server @@ -57,7 +57,7 @@ def connect(server, initial_message=nil) vouch = Vouch.generate(server_long_term_pubkey, long_term_key, short_term_key.public_key) # Generate an initiate message and send it to the server # ALERT Normally the domain name would be pre-distributed # This contains the initial message initiate_message = InitiateMessage.new(server_short_term_pubkey, short_term_key, cookie, long_term_key.public_key, vouch, server.domain_name, initial_message) server.mailbox << initiate_message -
grantr revised this gist
Feb 11, 2013 . 1 changed file with 88 additions and 13 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,3 +1,21 @@ # A demonstration of the CurveCP handshake protocol. This protocol has many # favorable security properties described at http://curvecp.org. # # In addition to its security advantages, it has the following favorable properties: # * Needs only 2 messages (1 from client, 1 from server) before application # messages can be exchanged (3 before the server can send application messages) # * Does not require the server to keep protocol state between handshake messages. # # ALERT This implementation has not been inspected or verified by cryptography # experts. Additionally, the CurveCP protocol itself is a work in progress. While # the handshake protocol uses only proven primitives from the NaCL library, it # is possible that weaknesses will be discovered. See the CurveCP website above # for more information. # # Finally, ALERT comments throughout the code denote parts of the implementation # that are noncompliant or not ready for production use. Read these carefully # before basing an implementation on this code. require 'celluloid' require 'rbnacl' @@ -79,24 +97,38 @@ def rotate_minute_key self.minute_key = Crypto::Random.random_bytes(32) end def accept accept_hello accept_initiate end # returns a connection and initial message def accept_hello # Wait for a hello from a client hello_message = receive { |msg| msg.is_a?(HelloMessage) } # The client short term public key is sent in the clear client_short_term_pubkey = hello_message.client_short_term_pubkey # Ensure the hello message is valid, that is, the sender has access to # the client short term private key and the server long term public key raise "invalid hello message" unless hello_message.valid?(long_term_key) # Generate a server short term key short_term_key = Crypto::PrivateKey.generate # Generate a cookie for the client to authenticate # The cookie is also a state storage mechanism. It allows the handshake # protocol to be stateless so that different threads can handle hello and # initiate messages. cookie = Cookie.generate(client_short_term_pubkey, short_term_key, minute_key) # Generate a cookie message and send it to the client cookie_message = CookieMessage.new(client_short_term_pubkey, long_term_key, short_term_key.public_key, cookie.to_bytes) hello_message.reply_mailbox << cookie_message end def accept_initiate # Wait for an initiate from a client initiate_message = receive { |msg| msg.is_a?(InitiateMessage) } @@ -105,11 +137,40 @@ def accept # The cookie is also sent in the clear # This was sent to the server by the client and is returned unchanged cookie = Cookie.new(initiate_message.cookie) # Open the cookie to retrieve the boxed client short term public key and # the server short term private key # If the current minute key doesn't work, try the previous one boxed_client_short_term_pubkey, short_term_key = begin cookie.open(minute_key) rescue CryptoError cookie.open(prev_minute_key) end # Ensure the boxed public key matches the one sent in the clear # This is safe because Crypto::PublicKey implements constant-time # equality raise "boxed client key does not match" unless client_short_term_pubkey == boxed_client_short_term_pubkey # Extract the client's long term public key, vouch, domain name, and initial message client_long_term_pubkey, vouch, sent_domain_name, initial_message = initiate_message.open(short_term_key) # Ensure the sent domain name matches our domain name # ALERT This is potentially vulnerable to timing attacks. Constant-time # comparison would probably be more secure. raise "domain names do not match (#{sent_domain_name}, #{domain_name})" unless sent_domain_name == domain_name # Open the vouch to retrieve the boxed client short term public key vouched_client_short_term_pubkey = vouch.open(client_long_term_pubkey, long_term_key) # Ensure the vouched public key matches the one sent in the clear raise "vouched client key does not match" unless client_short_term_pubkey == vouched_client_short_term_pubkey # ALERT Any application-specific logic for authorizing the client long # term key would go here. # The initiate message is valid, return a new Connection and the initial message [Connection.new(client_short_term_pubkey, short_term_key, :server), initial_message] end end @@ -134,6 +195,9 @@ def open(message) module NonceGenerator # Nonces can never be used more than once for a particular key! # # ALERT In real life, you would use a generator for each key so that # information about the number of clients is not leaked. # Rules for short term nonces: # Must be 8 bytes @@ -154,6 +218,10 @@ def short_term_nonce # mention two possible strategies for dealing with this: persistent counters # and timestamps. # ALERT In real life, long term nonce generators must be persisted. Even if # the timestamp strategy is used, the timestamp must be persisted to ensure # the clock never runs backwards. # counter strategy def long_term_nonce_counter short_term_nonce + Crypto::Random.random_bytes(8) @@ -185,6 +253,9 @@ def nonce_string "CurveCP-client-H" + @nonce end # ALERT In real life, Hello messages should be constructed so their length # is greater than or equal to the length of Cookie messages. This is to # avoid an amplification attack. def valid?(server_long_term_privkey) string = Crypto::Box.new(@client_short_term_pubkey, server_long_term_privkey).open(nonce_string, @ciphertext) zeros = Crypto::Util.zeros(32) @@ -261,7 +332,8 @@ def nonce_string def open(server_short_term_privkey) plaintext = Crypto::Box.new(@client_short_term_pubkey, server_short_term_privkey).open(nonce_string, ciphertext) # Use A256 to unpack the domain name so null padding is not retained client_long_term_pubkey, vouch, domain_name, message = plaintext.unpack("a32a64A256a*") [Crypto::PublicKey.new(client_long_term_pubkey), Vouch.new(vouch), domain_name, message] end end @@ -316,19 +388,22 @@ def open(box) end if $0 == __FILE__ require 'minitest/spec' require 'minitest/autorun' include CurveCPHandshake describe CurveCPHandshake do it 'should transmit an initial message' do client = Client.new server = Server.new accepted = server.future.accept client_connection = client.connect(server, "hello!") server_connection, initial_message = accepted.value initial_message.must_equal "hello!" end end end -
grantr revised this gist
Feb 11, 2013 . 1 changed file with 61 additions and 29 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -119,27 +119,41 @@ class Connection def initialize(public_key, private_key, type) raise "invalid type" unless [:server, :client].include?(type) @box = Crypto::Box.new(public_key, private_key) @type = type end def box(bytes) Message.new(@box, @type, bytes) end def open(message) message.open(@box) end end module NonceGenerator # Nonces can never be used more than once for a particular key! # Rules for short term nonces: # Must be 8 bytes # Nonces must strictly increase for a particular short term key # Not required to start at 0 # Not required to increase by 1 def short_term_nonce @counter = (@counter ? @counter + 1 : 0) # CurveCP specifies little-endian [@counter].pack("Q<") end # Rules for long term nonces: # Must be 16 bytes # Not required to start at 0 # Not required to strictly increase # Must not be used more than once, even if the process restarts. The docs # mention two possible strategies for dealing with this: persistent counters # and timestamps. # counter strategy def long_term_nonce_counter short_term_nonce + Crypto::Random.random_bytes(8) @@ -157,22 +171,22 @@ def long_term_nonce_timestamp class HelloMessage extend NonceGenerator attr_accessor :client_short_term_pubkey attr_accessor :nonce, :ciphertext attr_accessor :reply_mailbox def initialize(server_long_term_pubkey, client_short_term_privkey) @client_short_term_pubkey = client_short_term_privkey.public_key @nonce = self.class.short_term_nonce @ciphertext = Crypto::Box.new(server_long_term_pubkey, client_short_term_privkey).box(nonce_string, Crypto::Util.zeros(64)) end def nonce_string "CurveCP-client-H" + @nonce end def valid?(server_long_term_privkey) string = Crypto::Box.new(@client_short_term_pubkey, server_long_term_privkey).open(nonce_string, @ciphertext) zeros = Crypto::Util.zeros(32) Crypto::Util.verify32(string[0, 32], zeros) && Crypto::Util.verify32(string[32, 32], zeros) end @@ -181,20 +195,20 @@ def valid?(server_long_term_privkey) class CookieMessage extend NonceGenerator attr_accessor :nonce, :ciphertext def initialize(client_short_term_pubkey, server_long_term_privkey, server_short_term_pubkey, cookie) @nonce = self.class.long_term_nonce_counter @ciphertext = Crypto::Box.new(client_short_term_pubkey, server_long_term_privkey).box(nonce_string, server_short_term_pubkey.to_bytes + cookie) end def nonce_string "CurveCPK" + @nonce end def open(server_long_term_pubkey, client_short_term_privkey) plaintext = Crypto::Box.new(server_long_term_pubkey, client_short_term_privkey).open(nonce_string, @ciphertext) server_short_term_pubkey, cookie = plaintext.unpack("a32a96") [Crypto::PublicKey.new(server_short_term_pubkey), cookie] end end @@ -206,19 +220,19 @@ class Cookie def self.generate(client_short_term_pubkey, server_short_term_privkey, minute_key) nonce = long_term_nonce_counter nonce_string = NONCE_PREFIX + nonce ciphertext = Crypto::SecretBox.new(minute_key).box(nonce_string, client_short_term_pubkey.to_bytes + server_short_term_privkey.to_bytes) new(nonce + ciphertext) end def initialize(bytes) @cookie = bytes end def open(minute_key) nonce, ciphertext = @cookie.unpack("a16a80") nonce_string = NONCE_PREFIX + nonce plaintext = Crypto::SecretBox.new(minute_key).open(nonce_string, ciphertext) client_short_term_pubkey, server_short_term_privkey = plaintext.unpack("a32a32") [Crypto::PublicKey.new(client_short_term_pubkey), Crypto::PrivateKey.new(server_short_term_privkey)] end @@ -232,22 +246,22 @@ class InitiateMessage attr_accessor :client_short_term_pubkey attr_accessor :cookie attr_accessor :nonce, :ciphertext def initialize(server_short_term_pubkey, client_short_term_privkey, cookie, client_long_term_pubkey, vouch, domain_name, message) @client_short_term_pubkey = client_short_term_privkey.public_key @cookie = cookie @nonce = self.class.short_term_nonce @ciphertext = Crypto::Box.new(server_short_term_pubkey, client_short_term_privkey).box(nonce_string, [client_long_term_pubkey.to_bytes, vouch.to_bytes, domain_name, message].pack("a32a64a256a*")) end def nonce_string "CurveCP-client-I" + @nonce end def open(server_short_term_privkey) plaintext = Crypto::Box.new(@client_short_term_pubkey, server_short_term_privkey).open(nonce_string, ciphertext) client_long_term_pubkey, vouch, domain_name, message = plaintext.unpack("a32a64a256a*") [Crypto::PublicKey.new(client_long_term_pubkey), Vouch.new(vouch), domain_name, message] end end @@ -259,18 +273,18 @@ class Vouch def self.generate(server_long_term_pubkey, client_long_term_privkey, client_short_term_pubkey) nonce = long_term_nonce_counter nonce_string = NONCE_PREFIX + nonce ciphertext = Crypto::Box.new(server_long_term_pubkey, client_long_term_privkey).box(nonce_string, client_short_term_pubkey.to_bytes) new(nonce + ciphertext) end def initialize(bytes) @vouch = bytes end def open(client_long_term_pubkey, server_long_term_privkey) nonce, ciphertext = @vouch.unpack("a16a48") nonce_string = NONCE_PREFIX + nonce client_short_term_pubkey = Crypto::Box.new(client_long_term_pubkey, server_long_term_privkey).open(nonce_string, ciphertext) Crypto::PublicKey.new(client_short_term_pubkey) end @@ -284,19 +298,37 @@ class Message attr_accessor :nonce, :box def initialize(box, type, bytes) raise "invalid type" unless [:server, :client].include?(type) @nonce = self.class.short_term_nonce @type = type @ciphertext = box.box(nonce_string, bytes) end def nonce_string "CurveCP-#{@type}-M" + @nonce end def open(box) box.open(nonce_string, @ciphertext) end end end if $0 == __FILE__ #require 'minitest/spec' #require 'minitest/autorun' include CurveCPHandshake client = Client.new server = Server.new accepted = server.future.accept client_connection = client.connect(server, "hello!") server_connection, initial_message = accepted.value puts "initial message received: #{initial_message}" end -
grantr created this gist
Feb 11, 2013 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,302 @@ require 'celluloid' require 'rbnacl' module CurveCPHandshake class Client include Celluloid attr_accessor :long_term_key def initialize(long_term_key=Crypto::PrivateKey.generate) @long_term_key = long_term_key end # returns a Connection def connect(server, initial_message=nil) # Normally this would be pre-distributed server_long_term_pubkey = server.long_term_public_key # Generate a client short term key short_term_key = Crypto::PrivateKey.generate # Generate a hello message hello_message = HelloMessage.new(server_long_term_pubkey, short_term_key) # add our mailbox so the server can reply # Normally this would be handled by the transport layer hello_message.reply_mailbox = Actor.current.mailbox # send the hello message to the server server.mailbox << hello_message # Wait for a cookie from the server cookie_message = receive { |msg| msg.is_a?(CookieMessage) } # Extract the server's short term pubkey and cookie server_short_term_pubkey, cookie = cookie_message.open(server_long_term_pubkey, short_term_key) # Generate a vouch so the server knows we are authentic vouch = Vouch.generate(server_long_term_pubkey, long_term_key, short_term_key.public_key) # Generate an initiate message and send it to the server # Normally the domain name would be pre-distributed # This contains the initial message initiate_message = InitiateMessage.new(server_short_term_pubkey, short_term_key, cookie, long_term_key.public_key, vouch, server.domain_name, initial_message) server.mailbox << initiate_message # Now the connection can be used to send further messages Connection.new(server_short_term_pubkey, short_term_key, :client) end end class Server include Celluloid attr_accessor :long_term_key attr_accessor :minute_key, :prev_minute_key attr_accessor :domain_name def initialize(long_term_key=Crypto::PrivateKey.generate) @long_term_key = long_term_key @client_connections = {} # generate minute keys and rotate them rotate_minute_key every(60) { rotate_minute_key } end def domain_name @domain_name ||= "example.com" end def long_term_public_key long_term_key.public_key end def rotate_minute_key self.prev_minute_key = minute_key || Crypto::Random.random_bytes(32) self.minute_key = Crypto::Random.random_bytes(32) end # returns a connection and initial message def accept # Wait for a hello from a client hello_message = receive { |msg| msg.is_a?(HelloMessage) } # The client short term public key is sent in the clear client_short_term_pubkey = hello_message.client_short_term_pubkey # Generate a server short term key short_term_key = Crypto::PrivateKey.generate # Generate a cookie for the client to authenticate cookie = Cookie.generate(client_short_term_pubkey, short_term_key, minute_key) # Generate a cookie message and send it to the client cookie_message = CookieMessage.new(client_short_term_pubkey, long_term_key, short_term_key.public_key, cookie.to_bytes) hello_message.reply_mailbox << cookie_message # Wait for an initiate from a client initiate_message = receive { |msg| msg.is_a?(InitiateMessage) } # The client short term public key is sent in the clear client_short_term_pubkey = initiate_message.client_short_term_pubkey # The cookie is also sent in the clear # This was sent to the server by the client and is returned unchanged received_cookie = Cookie.new(initiate_message.cookie) # Extract the client's long term public key, vouch, domain name, and initial message client_long_term_pubkey, vouch, domain_name, initial_message = initiate_message.open(short_term_key) [Connection.new(client_short_term_pubkey, short_term_key, :server), initial_message] end end class Connection attr_accessor :public_key, :private_key, :type def initialize(public_key, private_key, type) raise "invalid type" unless [:server, :client].include?(type) @public_key = public_key @private_key = private_key @type = type end def box(bytes) Message.new(@public_key, @private_key, @type, bytes) end def open(message) message.open(@public_key, @private_key) end end module NonceGenerator def short_term_nonce @counter = (@counter ? @counter + 1 : 0) # CurveCP specifies little-endian [@counter].pack("Q<") end # counter strategy def long_term_nonce_counter short_term_nonce + Crypto::Random.random_bytes(8) end # timestamp strategy def long_term_nonce_timestamp # microseconds since epoch timestamp = (Time.now.to_f*1_000_000).to_i # CurveCP specifies little-endian timestamp.pack("Q<") + Crypto::Random_bytes(8) end end class HelloMessage extend NonceGenerator attr_accessor :client_short_term_pubkey attr_accessor :nonce, :box attr_accessor :reply_mailbox def initialize(server_long_term_pubkey, client_short_term_privkey) @client_short_term_pubkey = client_short_term_privkey.public_key @nonce = self.class.short_term_nonce @box = Crypto::Box.new(server_long_term_pubkey, client_short_term_privkey).box(nonce_string, Crypto::Util.zeros(64)) end def nonce_string "CurveCP-client-H" + @nonce end def valid?(server_long_term_privkey) string = Crypto::Box.new(@client_short_term_pubkey, server_long_term_privkey).open(nonce_string, @box) zeros = Crypto::Util.zeros(32) Crypto::Util.verify32(string[0, 32], zeros) && Crypto::Util.verify32(string[32, 32], zeros) end end class CookieMessage extend NonceGenerator attr_accessor :nonce, :box def initialize(client_short_term_pubkey, server_long_term_privkey, server_short_term_pubkey, cookie) @nonce = self.class.long_term_nonce_counter @box = Crypto::Box.new(client_short_term_pubkey, server_long_term_privkey).box(nonce_string, server_short_term_pubkey.to_bytes + cookie) end def nonce_string "CurveCPK" + @nonce end def open(server_long_term_pubkey, client_short_term_privkey) plaintext = Crypto::Box.new(server_long_term_pubkey, client_short_term_privkey).open(nonce_string, @box) server_short_term_pubkey, cookie = plaintext.unpack("A32A96") [Crypto::PublicKey.new(server_short_term_pubkey), cookie] end end class Cookie extend NonceGenerator NONCE_PREFIX = "minute-k" def self.generate(client_short_term_pubkey, server_short_term_privkey, minute_key) nonce = long_term_nonce_counter nonce_string = NONCE_PREFIX + nonce box = Crypto::SecretBox.new(minute_key).box(nonce_string, client_short_term_pubkey.to_bytes + server_short_term_privkey.to_bytes) new(nonce + box) end def initialize(bytes) @cookie = bytes end def open(minute_key) nonce, box = @cookie.unpack("A16A80") nonce_string = NONCE_PREFIX + nonce plaintext = Crypto::SecretBox.new(minute_key).open(nonce_string, box) client_short_term_pubkey, server_short_term_privkey = plaintext.unpack("A32A32") [Crypto::PublicKey.new(client_short_term_pubkey), Crypto::PrivateKey.new(server_short_term_privkey)] end def to_bytes @cookie end end class InitiateMessage extend NonceGenerator attr_accessor :client_short_term_pubkey attr_accessor :cookie attr_accessor :nonce, :box def initialize(server_short_term_pubkey, client_short_term_privkey, cookie, client_long_term_pubkey, vouch, domain_name, message) @client_short_term_pubkey = client_short_term_privkey.public_key @cookie = cookie @nonce = self.class.short_term_nonce @box = Crypto::Box.new(server_short_term_pubkey, client_short_term_privkey).box(nonce_string, [client_long_term_pubkey.to_bytes, vouch.to_bytes, domain_name, message].pack("A32A64A256A*")) end def nonce_string "CurveCP-client-I" + @nonce end def open(server_short_term_privkey) plaintext = Crypto::Box.new(@client_short_term_pubkey, server_short_term_privkey).open(nonce_string, box) client_long_term_pubkey, vouch, domain_name, message = plaintext.unpack("A32A64A256A*") [Crypto::PublicKey.new(client_long_term_pubkey), Vouch.new(vouch), domain_name, message] end end class Vouch extend NonceGenerator NONCE_PREFIX = "CurveCPV" def self.generate(server_long_term_pubkey, client_long_term_privkey, client_short_term_pubkey) nonce = long_term_nonce_counter nonce_string = NONCE_PREFIX + nonce box = Crypto::Box.new(server_long_term_pubkey, client_long_term_privkey).box(nonce_string, client_short_term_pubkey.to_bytes) new(nonce + box) end def initialize(bytes) @vouch = bytes end def open(client_long_term_pubkey, server_long_term_privkey) nonce, box = @vouch.unpack("A16A48") nonce_string = NONCE_PREFIX + nonce client_short_term_pubkey = Crypto::Box.new(client_long_term_pubkey, server_long_term_privkey).open(nonce_string, box) Crypto::PublicKey.new(client_short_term_pubkey) end def to_bytes @vouch end end class Message extend NonceGenerator attr_accessor :nonce, :box def initialize(public_key, private_key, type, bytes) raise "invalid type" unless [:server, :client].include?(type) @nonce = self.class.short_term_nonce @type = type @box = Crypto::Box.new(public_key, private_key).box(nonce_string, bytes) end def nonce_string "CurveCP-#{@type}-M" + @nonce end def open(public_key, private_key) Crypto::Box.new(public_key, private_key).open(nonce_string, @box) end end end