def handshake_initiate(private_key, redis_client): try: request = expect_json_request(bottle.request, INITIATE_SCHEMA) symmetric_key = redis_get_cookie( redis_client, request[INITIATE_COOKIE_FIELD]) cookie_sbox = SecretBox(symmetric_key) cookie = cookie_sbox.decrypt( str(request[INITIATE_COOKIE_FIELD]), encoder=Base64Encoder) if len(cookie) != 2 * CURVE25519_KEY_BYTES: bottle.response.status = HTTP_INTERNAL_SERVER_ERROR return {'error': 'An invalid cookie was sent to the client.'} client_transient_pkey = PublicKey(cookie[0:CURVE25519_KEY_BYTES]) transient_skey = PrivateKey(cookie[CURVE25519_KEY_BYTES:]) if request[INITIATE_CLIENT_TRANSIENT_PKEY_FIELD] != \ client_transient_pkey.encode(Base64Encoder): raise InvalidClientRequest( 'Initiate: non matching transient public keys.') vouch_json = open_box(request[INITIATE_VOUCH_FIELD], transient_skey, client_transient_pkey) vouch = parse_and_verify_json(vouch_json, VOUCH_SCHEMA) client_pkey = PublicKey( str(vouch[VOUCH_CLIENT_PKEY_FIELD]), encoder=Base64Encoder) vouch_for_transient_pkey = open_box( vouch[VOUCH_TRANSIENT_KEY_BOX_FIELD], private_key, client_pkey) if vouch_for_transient_pkey != client_transient_pkey.encode(): raise InvalidClientRequest( 'Initiate: non matching transient public keys.') resp = 'I believe you are {} and you want {}'.format( client_pkey.encode(Base64Encoder), vouch[VOUCH_MESSAGE_FIELD]) print(resp) response_nonce = nacl.utils.random(Box.NONCE_SIZE) response_box = Box(transient_skey, client_transient_pkey) response_box_cipher = response_box.encrypt( resp, response_nonce, encoder=Base64Encoder) return {'response': response_box_cipher} except jsonschema.ValidationError as e: log.exception(e) bottle.response.status = HTTP_BAD_REQUEST return {'error': str(e)} except InvalidClientRequest as e: log.exception(e) bottle.response.status = HTTP_BAD_REQUEST return {'error': str(e)} except MissingCookie as e: log.exception(e) bottle.response.status = HTTP_BAD_REQUEST return {'error': str(e)} except CryptoError as e: log.exception(e) bottle.response.status = HTTP_BAD_REQUEST return {'error': 'Bad encryption in handshake.'} return {'error': ''}
def nacl_public_PublicKey(): bytes32 = random(size=32) print(len(bytes32)) pubkey1 = PublicKey(bytes32) pubkey2 = PublicKey(base64.b64encode(bytes32), encoder=Base64Encoder) print(pubkey1.encode()) print(pubkey2.encode())
def auth(): auth_host = '10.50.8.128' #with open('private_key', 'rb') as f: #f.write('Hello\n') with open('private_key') as f: encoded_private_key = f.read() with open('public_key') as f: encoded_public_key = f.read() loaded_public_key = PublicKey(encoded_public_key, encoder=nacl.encoding.Base64Encoder) loaded_private_key = PrivateKey(encoded_private_key, encoder=nacl.encoding.Base64Encoder) # assert loaded_public_key.encode() == loaded_private_key.public_key.encode() #assert loaded_public_key.encode() == loaded_private_key.public_key.encode() print(loaded_public_key.encode()) print(loaded_private_key.public_key.encode()) private_key = loaded_private_key public_key = loaded_public_key # return 1 # private_key = private_key_b64.decode('base64') # public_key = public_key_b64.decode('base64') # with open('public_key', 'rb') as f: # f.write(encoded_public_key) # with open('x.py') as f: s = f.read() # # Generate Bob's private key, as we've done in the Box example # skbob = PrivateKey.generate() # pkbob = skbob.public_key #return private_key # Alice wishes to send a encrypted message to Bob, # but prefers the message to be untraceable #sealed_box = SealedBox(pkbob) sealed_box = SealedBox(private_key) # This is Alice's message message = b"Kill all kittens" # Encrypt the message, it will carry the ephemeral key public part # to let Bob decrypt it encrypted = sealed_box.encrypt(message) msg = base64.b64encode(encrypted) msg = encoded_public_key #data = {'name': 'jtest', 'ipaddr': '192.168.0.2' 'public_key': b'\x8e\x05{\xe2\xcby:\x0b\xeb\xe69\xac|\x96\xff\xa4\xdaE\x89^\xa7\xaf\x90\x83\x14)bP\x0c\n\x85l'} #data = {'msg': ''} data = {'msg': msg} print(data) #return 1 #r = requests.post('https://stats.rchain.me:30443/auth', data = data) r = requests.post(f'https://{auth_host}:30443/auth', data=data) print(r) text = r.text content = r.content print(text) print(content)
def on_post(self, req, resp): configuration = req.context['configuration'] if req.content_length: data = json.load(req.bounded_stream) else: raise Exception("No data.") if 'public_key' not in data: raise Exception("No public key.") db_session = req.context['db_session'] user_public_key = PublicKey(data['public_key'].encode('utf-8'), encoder=nacl.encoding.Base64Encoder) user = db_session.query( db.User).filter(db.User.public_key == user_public_key.encode( nacl.encoding.RawEncoder)).first() if user is None: raise Exception("Public key unknown.") challenge_answer = nacl.utils.random(Box.NONCE_SIZE) challenge_uuid = str(uuid.uuid4()) box = Box(configuration['server_key_pair'], user_public_key) challenge_box = box.encrypt(plaintext=challenge_answer, encoder=nacl.encoding.Base64Encoder) challenge = db.Challenge(uuid=challenge_uuid, user=user, answer_hash=nacl.hash.sha256( challenge_answer, encoder=nacl.encoding.RawEncoder)) db_session.add(challenge) db_session.commit() resp.status = falcon.HTTP_200 resp.body = json.dumps({ 'uuid': challenge_uuid, 'nonce': str(challenge_box.nonce.decode('utf-8')), 'challenge': str(challenge_box.ciphertext.decode('utf-8')) })
def handshake_hello(private_key, redis_client): try: request = expect_json_request(bottle.request, HELLO_SCHEMA) client_transient_pkey = PublicKey( str(request[HELLO_CLIENT_TRANSIENT_PKEY_FIELD]), Base64Encoder) zeros = open_box(request[HELLO_ZEROS_BOX_FIELD], private_key, client_transient_pkey) if len(zeros) != HELLO_PADDING_BYTES: raise InvalidClientRequest( 'zeros_box should contain exactly %d bytes of padding' % HELLO_PADDING_BYTES) transient_skey = PrivateKey.generate() cookie_plain = client_transient_pkey.encode() + \ transient_skey.encode() cookie_nonce = nacl.utils.random(SecretBox.NONCE_SIZE) symmetric_key = nacl.utils.random(SecretBox.KEY_SIZE) cookie_sbox = SecretBox(symmetric_key) cookie = cookie_sbox.encrypt( cookie_plain, cookie_nonce, encoder=Base64Encoder) redis_set_cookie(redis_client, cookie, symmetric_key) cookie_box = Box(private_key, client_transient_pkey) cookie_box_nonce = nacl.utils.random(Box.NONCE_SIZE) server_tpkey = transient_skey.public_key.encode(Base64Encoder) cookie_box_cipher = cookie_box.encrypt(json.dumps({ COOKIE_SERVER_TRANSIENT_PKEY_FIELD: server_tpkey, COOKIE_COOKIE_FIELD: cookie }), cookie_box_nonce, encoder=Base64Encoder) response = {COOKIE_COOKIE_BOX_FIELD: cookie_box_cipher} jsonschema.validate(response, COOKIE_SCHEMA) return response except jsonschema.ValidationError: log.exception(e) bottle.response.status = HTTP_INTERNAL_SERVER_ERROR return {'error': 'A packet with an invalid JSON schema was generated.'} except InvalidClientRequest as e: log.exception(e) bottle.response.status = HTTP_BAD_REQUEST return {'error': str(e)} except CryptoError as e: log.exception(e) bottle.response.status = HTTP_BAD_REQUEST return {'error': 'bad encryption'} return {'error': ''}
def on_post(self, req, resp): configuration = req.context['configuration'] if req.content_length: data = json.load(req.bounded_stream) else: raise Exception("No data.") if 'public_key' not in data: raise Exception("No public key.") db_session = req.context['db_session'] user_public_key = PublicKey(data['public_key'].encode('utf-8'), encoder = nacl.encoding.Base64Encoder) user = db_session.query(db.User).filter(db.User.public_key == user_public_key.encode(nacl.encoding.RawEncoder)).first() if user is None: raise Exception("Public key unknown.") challenge_answer = nacl.utils.random(Box.NONCE_SIZE) challenge_uuid = str(uuid.uuid4()) box = Box(configuration['server_key_pair'], user_public_key) challenge_box = box.encrypt(plaintext = challenge_answer, encoder = nacl.encoding.Base64Encoder) challenge = db.Challenge( uuid = challenge_uuid, user = user, answer_hash = nacl.hash.sha256(challenge_answer, encoder = nacl.encoding.RawEncoder) ) db_session.add(challenge) db_session.commit() resp.status = falcon.HTTP_200 resp.body = json.dumps({ 'uuid': challenge_uuid, 'nonce': str(challenge_box.nonce.decode('utf-8')), 'challenge': str(challenge_box.ciphertext.decode('utf-8')) })
#PKs2_full = loads('{"kty":"EC", "crv":"Curve25519", "x":""}', object_pairs_hook=OrderedDict) #PKp2_full = loads('{"kty":"EC", "crv":"Curve25519", "x":""}', object_pairs_hook=OrderedDict) PKp_full['x'] = PKp_b64 PKs_full['x'] = PKs_b64 #PKs2_full['x'] = PKs2_b64 #PKp2_full['x'] = PKp2_b64 ## Load peer and server private keys SK_peer = PrivateKey(bytes.fromhex(SKp)) SK_server = PrivateKey(bytes.fromhex(SKs)) #SK2_peer = PrivateKey(bytes.fromhex(SKp2)) #SK2_server = PrivateKey(bytes.fromhex(SKs2)) ## Derive shared secret Z = scalarmult(SK_peer.encode(), PK_server.encode()) assert (Z == scalarmult(SK_server.encode(), PK_peer.encode())) #Z2 = scalarmult(SK2_peer.encode(), PK2_server.encode()) #assert(Z2 == scalarmult(SK2_server.encode(), PK2_peer.encode())) ## KDF for completion exchange. Uses NIST Concat KDF. KDF_input = b'EAP-NOOB' + base64url_decode(Np_b64) + base64url_decode( Ns_b64) + base64url_decode(Noob_b64) KDF_out = KDF(algorithm=SHA256(), length=320, otherinfo=KDF_input, backend=default_backend()).derive(Z) Kms = KDF_out[224:256] Kmp = KDF_out[256:288] Kz = KDF_out[288:320]
def encode_tansmission_public_key(key: PublicKey): return key.encode(encoder=URLSafeBase64Encoder)
class Invitation: # This has a brief lifetime: one is created in response to the rendezvous # client discovering new messages for us, used for one reactor tick, then # dereferenced. It holds onto a few values during that tick (which may # process multiple messages for a single invitation, e.g. A's second poll # will receive both B-m1 and B-m2 together). But all persistent state # beyond that one tick is stored in the database. def __init__(self, iid, db, manager): self.iid = iid self.db = db self.manager = manager c = self.db.execute("SELECT petname, inviteID, inviteKey," # 0,1,2 " theirTempPubkey," # 3 " nextExpectedMessage," # 4 " myMessages," # 5 " theirMessages" # 6 " FROM invitations WHERE id = ?", (iid,)) res = c.fetchone() if not res: raise KeyError("no pending Invitation for '%d'" % iid) self.petname = res[0] self.inviteID = res[1] self.inviteKey = SigningKey(res[2].decode("hex")) self.theirTempPubkey = None if res[3]: self.theirTempPubkey = PublicKey(res[3].decode("hex")) self.nextExpectedMessage = int(res[4]) self.myMessages = splitMessages(res[5]) self.theirMessages = splitMessages(res[6]) def getAddressbookID(self): c = self.db.execute("SELECT addressbook_id FROM invitations" " WHERE id = ?", (self.iid,)) return c.fetchone()[0] def getMyTempPrivkey(self): c = self.db.execute("SELECT myTempPrivkey FROM invitations" " WHERE id = ?", (self.iid,)) return PrivateKey(c.fetchone()[0].decode("hex")) def getMySigningKey(self): c = self.db.execute("SELECT mySigningKey FROM invitations" " WHERE id = ?", (self.iid,)) return SigningKey(c.fetchone()[0].decode("hex")) def getMyPublicChannelRecord(self): c = self.db.execute("SELECT my_channel_record FROM invitations" " WHERE id = ?", (self.iid,)) return c.fetchone()[0] def getMyPrivateChannelData(self): c = self.db.execute("SELECT my_private_channel_data FROM invitations" " WHERE id = ?", (self.iid,)) return json.loads(c.fetchone()[0]) def sendFirstMessage(self): pub = self.getMyTempPrivkey().public_key.encode() self.send("i0:m1:"+pub) self.db.update("UPDATE invitations SET myMessages=? WHERE id=?", (",".join(self.myMessages), self.iid), "invitations", self.iid) # that will be commited by our caller def processMessages(self, messages): # These messages are neither version-checked nor signature-checked. # Also, we may have already processed some of them. #print "processMessages", messages #print " my", self.myMessages #print " theirs", self.theirMessages assert isinstance(messages, set), type(messages) assert None not in messages, messages assert None not in self.myMessages, self.myMessages assert None not in self.theirMessages, self.theirMessages # Send anything that didn't make it to the server. This covers the # case where we commit our outbound message in send() but crash # before finishing delivery. for m in self.myMessages - messages: #print "resending", m self.manager.sendToAll(self.inviteID, m) newMessages = messages - self.myMessages - self.theirMessages #print " %d new messages" % len(newMessages) if not newMessages: print " huh, no new messages, stupid rendezvous client" # check signatures, extract bodies. invalid messages kill the channel # and the invitation. MAYBE TODO: lose the one channel, keep using # the others. bodies = set() for m in newMessages: #print " new inbound message", m try: if not m.startswith("r0:"): print "unrecognized rendezvous message prefix" if not VALID_MESSAGE.search(m): raise CorruptChannelError() m = m[len("r0:"):].decode("hex") bodies.add(self.inviteKey.verify_key.verify(m)) except (BadSignatureError, CorruptChannelError) as e: print "channel %s is corrupt" % self.inviteID if isinstance(e, BadSignatureError): print " (bad sig)" self.unsubscribe(self.inviteID) # TODO: mark invitation as failed, destroy it return #print " new inbound bodies:", ", ".join([repr(b[:10])+" ..." for b in bodies]) # these handlers will update self.myMessages with sent messages, and # will increment self.nextExpectedMessage. We can handle multiple # (sequential) messages in a single pass. if self.nextExpectedMessage == 1: self.findPrefixAndCall("i0:m1:", bodies, self.processM1) # no elif here: self.nextExpectedMessage may have incremented if self.nextExpectedMessage == 2: self.findPrefixAndCall("i0:m2:", bodies, self.processM2) if self.nextExpectedMessage == 3: self.findPrefixAndCall("i0:m3:", bodies, self.processM3) self.db.update("UPDATE invitations SET" " myMessages=?," " theirMessages=?," " nextExpectedMessage=?" " WHERE id=?", (",".join(self.myMessages), ",".join(self.theirMessages | newMessages), self.nextExpectedMessage, self.iid), "invitations", self.iid) #print " db.commit" self.db.commit() def findPrefixAndCall(self, prefix, bodies, handler): for msg in bodies: if msg.startswith(prefix): return handler(msg[len(prefix):]) return None def send(self, msg, persist=True): #print "send", repr(msg[:10]), "..." signed = "r0:%s" % self.inviteKey.sign(msg).encode("hex") if persist: # m4-destroy is not persistent self.myMessages.add(signed) # will be persisted by caller # This will be added to the DB, and committed, by our caller, to # get it into the same transaction as the update to which inbound # messages we've processed. assert VALID_MESSAGE.search(signed), signed self.manager.sendToAll(self.inviteID, signed) def processM1(self, msg): #print "processM1", self.petname self.theirTempPubkey = PublicKey(msg) self.db.update("UPDATE invitations SET theirTempPubkey=?" " WHERE id=?", (self.theirTempPubkey.encode(Hex), self.iid), "invitations", self.iid) # theirTempPubkey will committed by our caller, in the same txn as # the message send my_privkey = self.getMyTempPrivkey() my_channel_record = self.getMyPublicChannelRecord() b = Box(my_privkey, self.theirTempPubkey) signedBody = b"".join([self.theirTempPubkey.encode(), my_privkey.public_key.encode(), my_channel_record.encode("utf-8")]) my_sign = self.getMySigningKey() body = b"".join([b"i0:m2a:", my_sign.verify_key.encode(), my_sign.sign(signedBody) ]) nonce = os.urandom(Box.NONCE_SIZE) nonce_and_ciphertext = b.encrypt(body, nonce) #print "ENCRYPTED n+c", len(nonce_and_ciphertext), nonce_and_ciphertext.encode("hex") #print " nonce", nonce.encode("hex") msg2 = "i0:m2:"+nonce_and_ciphertext self.send(msg2) self.nextExpectedMessage = 2 def processM2(self, msg): #print "processM2", repr(msg[:10]), "...", self.petname assert self.theirTempPubkey nonce_and_ciphertext = msg my_privkey = self.getMyTempPrivkey() b = Box(my_privkey, self.theirTempPubkey) #nonce = msg[:Box.NONCE_SIZE] #ciphertext = msg[Box.NONCE_SIZE:] #print "DECRYPTING n+ct", len(msg), msg.encode("hex") body = b.decrypt(nonce_and_ciphertext) if not body.startswith("i0:m2a:"): raise ValueError("expected i0:m2a:, got '%r'" % body[:20]) verfkey_and_signedBody = body[len("i0:m2a:"):] theirVerfkey = VerifyKey(verfkey_and_signedBody[:32]) signedBody = verfkey_and_signedBody[32:] body = theirVerfkey.verify(signedBody) check_myTempPubkey = body[:32] check_theirTempPubkey = body[32:64] their_channel_record_json = body[64:].decode("utf-8") #print " binding checks:" #print " check_myTempPubkey", check_myTempPubkey.encode("hex") #print " my real tempPubkey", my_privkey.public_key.encode(Hex) #print " check_theirTempPubkey", check_theirTempPubkey.encode("hex") #print " first theirTempPubkey", self.theirTempPubkey.encode(Hex) if check_myTempPubkey != my_privkey.public_key.encode(): raise ValueError("binding failure myTempPubkey") if check_theirTempPubkey != self.theirTempPubkey.encode(): raise ValueError("binding failure theirTempPubkey") them = json.loads(their_channel_record_json) me = self.getMyPrivateChannelData() addressbook_id = self.db.insert( "INSERT INTO addressbook" " (petname, acked," " next_outbound_seqnum, my_signkey," " their_channel_record_json," " my_CID_key, next_CID_token," " highest_inbound_seqnum," " my_old_channel_privkey, my_new_channel_privkey," " they_used_new_channel_key, their_verfkey)" " VALUES (?,?, " " ?,?," " ?," " ?,?," # my_CID_key, next_CID_token " ?," # highest_inbound_seqnum " ?,?," " ?,?)", (self.petname, 0, 1, me["my_signkey"], json.dumps(them), me["my_CID_key"], None, 0, me["my_old_channel_privkey"], me["my_new_channel_privkey"], 0, theirVerfkey.encode(Hex) ), "addressbook") self.db.update("UPDATE invitations SET addressbook_id=?" " WHERE id=?", (addressbook_id, self.iid), "invitations", self.iid) msg3 = "i0:m3:ACK-"+os.urandom(16) self.send(msg3) self.nextExpectedMessage = 3 def processM3(self, msg): #print "processM3", repr(msg[:10]), "..." if not msg.startswith("ACK-"): raise ValueError("bad ACK") cid = self.getAddressbookID() self.db.update("UPDATE addressbook SET acked=1 WHERE id=?", (cid,), "addressbook", cid ) self.db.delete("DELETE FROM invitations WHERE id=?", (self.iid,), "invitations", self.iid) # we no longer care about the channel msg4 = "i0:destroy:"+os.urandom(16) self.send(msg4, persist=False) self.manager.unsubscribe(self.inviteID)
def on_post(self, req, resp): configuration = req.context['configuration'] if req.content_length: data = json.load(req.bounded_stream) else: raise Exception("No data.") if 'public_key' not in data: raise Exception("No public key.") if 'captcha' not in data: raise Exception("No captcha.") if 'uuid' not in data['captcha']: raise Exception("No captcha UUID.") if 'encrypted_answer' not in data['captcha']: raise Exception("No captcha encrypted answer.") if 'nonce' not in data['captcha']: raise Exception("No captcha nonce.") # Decrypt captcha answer user_public_key = PublicKey(data['public_key'].encode('utf-8'), encoder=nacl.encoding.Base64Encoder) captcha_encrypted_answer = base64.b64decode( data['captcha']['encrypted_answer'].encode('utf-8')) captcha_nonce = base64.b64decode( data['captcha']['nonce'].encode('utf-8')) box = Box(configuration['server_key_pair'], user_public_key) captcha_answer = box.decrypt(captcha_encrypted_answer, captcha_nonce).decode('utf-8').lower() # Get captcha from database db_session = req.context['db_session'] captcha = db_session.query(db.Captcha).filter( db.Captcha.uuid == data['captcha']['uuid']).first() # Whatever happens, delete the captcha from database db_session.delete(captcha) db_session.commit() # Check the IP hash of the user who requested the captcha matches the current client's IP hash ip_address_hash = nacl.hash.sha256(str.encode(captcha.uuid + req.remote_addr), encoder=nacl.encoding.RawEncoder) user_agent_hash = nacl.hash.sha256(str.encode(captcha.uuid + req.user_agent), encoder=nacl.encoding.RawEncoder) answer_hash = nacl.hash.sha256(str.encode(captcha.uuid + captcha_answer), encoder=nacl.encoding.RawEncoder) if (ip_address_hash != captcha.ip_address_hash) or ( user_agent_hash != captcha.user_agent_hash): raise Exception("Captcha answer incorrect.") # Check the captcha answer if captcha.answer_hash != answer_hash: raise Exception("Captcha answer incorrect.") # Create new user user = db.User( public_key=user_public_key.encode(nacl.encoding.RawEncoder)) db_session.add(user) db_session.commit() resp.status = falcon.HTTP_201 resp.body = json.dumps({})
def _serialize_public_key(key: public.PublicKey) -> str: str_key = key.encode(encoder=nacl.encoding.HexEncoder).decode('ascii') return str_key
# PKp2_full = loads('{"kty":"EC", "crv":"Curve25519", "x":""}', \ # object_pairs_hook=OrderedDict) PKp_full['x'] = PKp_b64 PKs_full['x'] = PKs_b64 #PKs2_full['x'] = PKs2_b64 #PKp2_full['x'] = PKp2_b64 ## Load peer and server private keys SK_peer = PrivateKey(bytes.fromhex(SKp)) SK_server = PrivateKey(bytes.fromhex(SKs)) #SK2_peer = PrivateKey(bytes.fromhex(SKp2)) #SK2_server = PrivateKey(bytes.fromhex(SKs2)) ## Derive shared secret Z = scalarmult(SK_peer.encode(), PK_server.encode()) assert(Z == scalarmult(SK_server.encode(), PK_peer.encode())) #Z2 = scalarmult(SK2_peer.encode(), PK2_server.encode()) #assert(Z2 == scalarmult(SK2_server.encode(), PK2_peer.encode())) ## KDF for completion exchange. Uses NIST Concat KDF. KDF_input = b'EAP-NOOB' + base64url_decode(Np_b64) + base64url_decode(Ns_b64) +\ base64url_decode(Noob_b64) KDF_out = KDF(algorithm=SHA256(), length=320, otherinfo=KDF_input, backend=default_backend()).derive(Z) Kms = KDF_out[224:256] Kmp = KDF_out[256:288] Kz = KDF_out[288:320] # KDF - for reconnect exchange. Uses NIST Concat KDF. This sample script does # not exchange new keys in the reconnect exchange and uses the Kz from the
class Invitation: # This has a brief lifetime: one is created in response to the rendezvous # client discovering new messages for us, used for one reactor tick, then # dereferenced. It holds onto a few values during that tick (which may # process multiple messages for a single invitation, e.g. A's second poll # will receive both B-m1 and B-m2 together). But all persistent state # beyond that one tick is stored in the database. def __init__(self, iid, db, manager): self.iid = iid self.db = db self.manager = manager c = self.db.execute( "SELECT petname, inviteID, inviteKey," # 0,1,2 " theirTempPubkey," # 3 " nextExpectedMessage," # 4 " myMessages," # 5 " theirMessages" # 6 " FROM invitations WHERE id = ?", (iid, )) res = c.fetchone() if not res: raise KeyError("no pending Invitation for '%d'" % iid) self.petname = res[0] self.inviteID = res[1] self.inviteKey = SigningKey(res[2].decode("hex")) self.theirTempPubkey = None if res[3]: self.theirTempPubkey = PublicKey(res[3].decode("hex")) self.nextExpectedMessage = int(res[4]) self.myMessages = splitMessages(res[5]) self.theirMessages = splitMessages(res[6]) def getAddressbookID(self): c = self.db.execute( "SELECT addressbook_id FROM invitations" " WHERE id = ?", (self.iid, )) return c.fetchone()[0] def getMyTempPrivkey(self): c = self.db.execute( "SELECT myTempPrivkey FROM invitations" " WHERE id = ?", (self.iid, )) return PrivateKey(c.fetchone()[0].decode("hex")) def getMySigningKey(self): c = self.db.execute( "SELECT mySigningKey FROM invitations" " WHERE id = ?", (self.iid, )) return SigningKey(c.fetchone()[0].decode("hex")) def getMyPublicChannelRecord(self): c = self.db.execute( "SELECT my_channel_record FROM invitations" " WHERE id = ?", (self.iid, )) return c.fetchone()[0] def getMyPrivateChannelData(self): c = self.db.execute( "SELECT my_private_channel_data FROM invitations" " WHERE id = ?", (self.iid, )) return json.loads(c.fetchone()[0]) def sendFirstMessage(self): pub = self.getMyTempPrivkey().public_key.encode() self.send("i0:m1:" + pub) self.db.update("UPDATE invitations SET myMessages=? WHERE id=?", (",".join(self.myMessages), self.iid), "invitations", self.iid) # that will be commited by our caller def processMessages(self, messages): # These messages are neither version-checked nor signature-checked. # Also, we may have already processed some of them. #print "processMessages", messages #print " my", self.myMessages #print " theirs", self.theirMessages assert isinstance(messages, set), type(messages) assert None not in messages, messages assert None not in self.myMessages, self.myMessages assert None not in self.theirMessages, self.theirMessages # Send anything that didn't make it to the server. This covers the # case where we commit our outbound message in send() but crash # before finishing delivery. for m in self.myMessages - messages: #print "resending", m self.manager.sendToAll(self.inviteID, m) newMessages = messages - self.myMessages - self.theirMessages #print " %d new messages" % len(newMessages) if not newMessages: print " huh, no new messages, stupid rendezvous client" # check signatures, extract bodies. invalid messages kill the channel # and the invitation. MAYBE TODO: lose the one channel, keep using # the others. bodies = set() for m in newMessages: #print " new inbound message", m try: if not m.startswith("r0:"): print "unrecognized rendezvous message prefix" if not VALID_MESSAGE.search(m): raise CorruptChannelError() m = m[len("r0:"):].decode("hex") bodies.add(self.inviteKey.verify_key.verify(m)) except (BadSignatureError, CorruptChannelError) as e: print "channel %s is corrupt" % self.inviteID if isinstance(e, BadSignatureError): print " (bad sig)" self.unsubscribe(self.inviteID) # TODO: mark invitation as failed, destroy it return #print " new inbound bodies:", ", ".join([repr(b[:10])+" ..." for b in bodies]) # these handlers will update self.myMessages with sent messages, and # will increment self.nextExpectedMessage. We can handle multiple # (sequential) messages in a single pass. if self.nextExpectedMessage == 1: self.findPrefixAndCall("i0:m1:", bodies, self.processM1) # no elif here: self.nextExpectedMessage may have incremented if self.nextExpectedMessage == 2: self.findPrefixAndCall("i0:m2:", bodies, self.processM2) if self.nextExpectedMessage == 3: self.findPrefixAndCall("i0:m3:", bodies, self.processM3) self.db.update( "UPDATE invitations SET" " myMessages=?," " theirMessages=?," " nextExpectedMessage=?" " WHERE id=?", (",".join( self.myMessages), ",".join(self.theirMessages | newMessages), self.nextExpectedMessage, self.iid), "invitations", self.iid) #print " db.commit" self.db.commit() def findPrefixAndCall(self, prefix, bodies, handler): for msg in bodies: if msg.startswith(prefix): return handler(msg[len(prefix):]) return None def send(self, msg, persist=True): #print "send", repr(msg[:10]), "..." signed = "r0:%s" % self.inviteKey.sign(msg).encode("hex") if persist: # m4-destroy is not persistent self.myMessages.add(signed) # will be persisted by caller # This will be added to the DB, and committed, by our caller, to # get it into the same transaction as the update to which inbound # messages we've processed. assert VALID_MESSAGE.search(signed), signed self.manager.sendToAll(self.inviteID, signed) def processM1(self, msg): #print "processM1", self.petname self.theirTempPubkey = PublicKey(msg) self.db.update( "UPDATE invitations SET theirTempPubkey=?" " WHERE id=?", (self.theirTempPubkey.encode(Hex), self.iid), "invitations", self.iid) # theirTempPubkey will committed by our caller, in the same txn as # the message send my_privkey = self.getMyTempPrivkey() my_channel_record = self.getMyPublicChannelRecord() b = Box(my_privkey, self.theirTempPubkey) signedBody = b"".join([ self.theirTempPubkey.encode(), my_privkey.public_key.encode(), my_channel_record.encode("utf-8") ]) my_sign = self.getMySigningKey() body = b"".join([ b"i0:m2a:", my_sign.verify_key.encode(), my_sign.sign(signedBody) ]) nonce = os.urandom(Box.NONCE_SIZE) nonce_and_ciphertext = b.encrypt(body, nonce) #print "ENCRYPTED n+c", len(nonce_and_ciphertext), nonce_and_ciphertext.encode("hex") #print " nonce", nonce.encode("hex") msg2 = "i0:m2:" + nonce_and_ciphertext self.send(msg2) self.nextExpectedMessage = 2 def processM2(self, msg): #print "processM2", repr(msg[:10]), "...", self.petname assert self.theirTempPubkey nonce_and_ciphertext = msg my_privkey = self.getMyTempPrivkey() b = Box(my_privkey, self.theirTempPubkey) #nonce = msg[:Box.NONCE_SIZE] #ciphertext = msg[Box.NONCE_SIZE:] #print "DECRYPTING n+ct", len(msg), msg.encode("hex") body = b.decrypt(nonce_and_ciphertext) if not body.startswith("i0:m2a:"): raise ValueError("expected i0:m2a:, got '%r'" % body[:20]) verfkey_and_signedBody = body[len("i0:m2a:"):] theirVerfkey = VerifyKey(verfkey_and_signedBody[:32]) signedBody = verfkey_and_signedBody[32:] body = theirVerfkey.verify(signedBody) check_myTempPubkey = body[:32] check_theirTempPubkey = body[32:64] their_channel_record_json = body[64:].decode("utf-8") #print " binding checks:" #print " check_myTempPubkey", check_myTempPubkey.encode("hex") #print " my real tempPubkey", my_privkey.public_key.encode(Hex) #print " check_theirTempPubkey", check_theirTempPubkey.encode("hex") #print " first theirTempPubkey", self.theirTempPubkey.encode(Hex) if check_myTempPubkey != my_privkey.public_key.encode(): raise ValueError("binding failure myTempPubkey") if check_theirTempPubkey != self.theirTempPubkey.encode(): raise ValueError("binding failure theirTempPubkey") them = json.loads(their_channel_record_json) me = self.getMyPrivateChannelData() addressbook_id = self.db.insert( "INSERT INTO addressbook" " (petname, acked," " next_outbound_seqnum, my_signkey," " their_channel_record_json," " my_CID_key, next_CID_token," " highest_inbound_seqnum," " my_old_channel_privkey, my_new_channel_privkey," " they_used_new_channel_key, their_verfkey)" " VALUES (?,?, " " ?,?," " ?," " ?,?," # my_CID_key, next_CID_token " ?," # highest_inbound_seqnum " ?,?," " ?,?)", (self.petname, 0, 1, me["my_signkey"], json.dumps(them), me["my_CID_key"], None, 0, me["my_old_channel_privkey"], me["my_new_channel_privkey"], 0, theirVerfkey.encode(Hex)), "addressbook") self.db.update("UPDATE invitations SET addressbook_id=?" " WHERE id=?", (addressbook_id, self.iid), "invitations", self.iid) msg3 = "i0:m3:ACK-" + os.urandom(16) self.send(msg3) self.nextExpectedMessage = 3 def processM3(self, msg): #print "processM3", repr(msg[:10]), "..." if not msg.startswith("ACK-"): raise ValueError("bad ACK") cid = self.getAddressbookID() self.db.update("UPDATE addressbook SET acked=1 WHERE id=?", (cid, ), "addressbook", cid) self.db.delete("DELETE FROM invitations WHERE id=?", (self.iid, ), "invitations", self.iid) # we no longer care about the channel msg4 = "i0:destroy:" + os.urandom(16) self.send(msg4, persist=False) self.manager.unsubscribe(self.inviteID)
def on_post(self, req, resp): configuration = req.context['configuration'] if req.content_length: data = json.load(req.bounded_stream) else: raise Exception("No data.") if 'public_key' not in data: raise Exception("No public key.") if 'captcha' not in data: raise Exception("No captcha.") if 'uuid' not in data['captcha']: raise Exception("No captcha UUID.") if 'encrypted_answer' not in data['captcha']: raise Exception("No captcha encrypted answer.") if 'nonce' not in data['captcha']: raise Exception("No captcha nonce.") # Decrypt captcha answer user_public_key = PublicKey(data['public_key'].encode('utf-8'), encoder = nacl.encoding.Base64Encoder) captcha_encrypted_answer = base64.b64decode(data['captcha']['encrypted_answer'].encode('utf-8')) captcha_nonce = base64.b64decode(data['captcha']['nonce'].encode('utf-8')) box = Box(configuration['server_key_pair'], user_public_key) captcha_answer = box.decrypt(captcha_encrypted_answer, captcha_nonce).decode('utf-8').lower() # Get captcha from database db_session = req.context['db_session'] captcha = db_session.query(db.Captcha).filter(db.Captcha.uuid == data['captcha']['uuid']).first() # Whatever happens, delete the captcha from database db_session.delete(captcha) db_session.commit() # Check the IP hash of the user who requested the captcha matches the current client's IP hash ip_address_hash = nacl.hash.sha256(str.encode(captcha.uuid + req.remote_addr), encoder = nacl.encoding.RawEncoder) user_agent_hash = nacl.hash.sha256(str.encode(captcha.uuid + req.user_agent), encoder = nacl.encoding.RawEncoder) answer_hash = nacl.hash.sha256(str.encode(captcha.uuid + captcha_answer), encoder = nacl.encoding.RawEncoder) if (ip_address_hash != captcha.ip_address_hash) or (user_agent_hash != captcha.user_agent_hash): raise Exception("Captcha answer incorrect.") # Check the captcha answer if captcha.answer_hash != answer_hash: raise Exception("Captcha answer incorrect.") # Create new user user = db.User( public_key = user_public_key.encode(nacl.encoding.RawEncoder) ) db_session.add(user) db_session.commit() resp.status = falcon.HTTP_201 resp.body = json.dumps({})