async def read(self): if self.closed: return None try: data = await self.reader.readexactly(HEADER_LENGTH) except IncompleteReadError: self.closed = True return None box = SecretBox(self.key) header = box.decrypt(data, self.nonce) if header == TERMINATION_HEADER: self.closed = True return None length = struct.unpack('>H', header[:2])[0] mac = header[2:] data = await self.reader.readexactly(length) body = box.decrypt(mac + data, inc_nonce(self.nonce)) self.nonce = inc_nonce(inc_nonce(self.nonce)) return body
def __enter__(self) -> typing.Tuple[PrivateKey, PrivateKey]: """ Provides a pair of private keys. """ # Derive the key from the passphrase. derived = util.derive_passphrase(self.passphrase) sign_box = SecretBox(derived) enc_box = SecretBox(derived) # Decrypt, using the two nonces. s_d = sign_box.decrypt(self.key._private_signing_seed, self.key._private_signing_nonce) e_d = enc_box.decrypt(self.key._private_key_raw, self.key._private_nonce) # Generate a SigningKey out of the seed. self.sign = SigningKey(s_d) self.encrypt = PrivateKey(e_d) # Update the key's public keys. if self.key._public_key is None: self.key._public_key = self.encrypt.public_key if self.key._public_signing_key is None: self.key._public_signing_key = self.sign.verify_key return self.encrypt, self.sign
def test_records_good(self): # now make sure that outbound records are encrypted properly t, c, owner = self.make_connection() RECORD1 = b"record" c.send_record(RECORD1) buf = t.read_buf() expected = ("%08x" % (24+len(RECORD1)+16)).encode("ascii") self.assertEqual(hexlify(buf[:4]), expected) encrypted = buf[4:] receive_box = SecretBox(owner._sender_record_key()) nonce_buf = encrypted[:SecretBox.NONCE_SIZE] # assume it's prepended nonce = int(hexlify(nonce_buf), 16) self.assertEqual(nonce, 0) # first message gets nonce 0 decrypted = receive_box.decrypt(encrypted) self.assertEqual(decrypted, RECORD1) # second message gets nonce 1 RECORD2 = b"record2" c.send_record(RECORD2) buf = t.read_buf() expected = ("%08x" % (24+len(RECORD2)+16)).encode("ascii") self.assertEqual(hexlify(buf[:4]), expected) encrypted = buf[4:] receive_box = SecretBox(owner._sender_record_key()) nonce_buf = encrypted[:SecretBox.NONCE_SIZE] # assume it's prepended nonce = int(hexlify(nonce_buf), 16) self.assertEqual(nonce, 1) decrypted = receive_box.decrypt(encrypted) self.assertEqual(decrypted, RECORD2) # and that we can receive records properly inbound_records = [] c.recordReceived = inbound_records.append RECORD3 = b"record3" send_box = SecretBox(owner._receiver_record_key()) nonce_buf = unhexlify("%048x" % 0) # first nonce must be 0 encrypted = send_box.encrypt(RECORD3, nonce_buf) length = unhexlify("%08x" % len(encrypted)) # always 4 bytes long c.dataReceived(length[:2]) c.dataReceived(length[2:]) c.dataReceived(encrypted[:-2]) self.assertEqual(inbound_records, []) c.dataReceived(encrypted[-2:]) self.assertEqual(inbound_records, [RECORD3]) RECORD4 = b"record4" send_box = SecretBox(owner._receiver_record_key()) nonce_buf = unhexlify("%048x" % 1) # nonces increment encrypted = send_box.encrypt(RECORD4, nonce_buf) length = unhexlify("%08x" % len(encrypted)) # always 4 bytes long c.dataReceived(length[:2]) c.dataReceived(length[2:]) c.dataReceived(encrypted[:-2]) self.assertEqual(inbound_records, [RECORD3]) c.dataReceived(encrypted[-2:]) self.assertEqual(inbound_records, [RECORD3, RECORD4])
def test_secret_box_wrong_lengths(): with pytest.raises(ValueError): SecretBox(b"") box = SecretBox(b"ec2bee2d5be613ca82e377c96a0bf2220d823ce980cdff6279473edc52862798", encoder=HexEncoder) with pytest.raises(ValueError): box.encrypt(b"", b"") with pytest.raises(ValueError): box.decrypt(b"", b"")
def test_secret_box_wrong_lengths(): with pytest.raises(ValueError): SecretBox(b"") box = SecretBox( b"ec2bee2d5be613ca82e377c96a0bf2220d823ce980cdff6279473edc52862798", encoder=HexEncoder, ) with pytest.raises(ValueError): box.encrypt(b"", b"") with pytest.raises(ValueError): box.decrypt(b"", b"")
def decrypt_data(key, encrypted): assert isinstance(key, type(b"")), type(key) assert isinstance(encrypted, type(b"")), type(encrypted) assert len(key) == SecretBox.KEY_SIZE, len(key) box = SecretBox(key) data = box.decrypt(encrypted) return data
def retrieve(sid, key): # Try to rename this sid's directory. This is an atomic operation on # POSIX file systems, meaning two concurrent requests cannot rename # the same directory -- for one of them, it will look like the # source directory does not exist. This also implicitly covers the # case where we try to retrieve an invalid sid. locked_sid = sid + '_locked' try: rename(join(DATA, sid), join(DATA, locked_sid)) except OSError: return None, ALREADY_REVEALED # Now that we have "locked" this sid, we can safely read it and then # destroy it. with open(join(DATA, locked_sid, 'secret'), 'rb') as fp: secret_bytes = fp.read() run(['/usr/bin/shred', join(DATA, locked_sid, 'secret')]) rmtree(join(DATA, locked_sid)) # Restore padding. (No point in using something like a while loop # here, we checked for an explicit length earlier.) key += '=' key = key.replace('_', '/') key_bytes = b64decode(key.encode('ASCII')) try: box = SecretBox(key_bytes) decrypted_bytes = box.decrypt(secret_bytes) except: return None, WRONG_KEY return decrypted_bytes.decode('UTF-8'), OK
def decrypt(ciphertext, keyPath): """ Decrypts a given message that was encrypted with the encrypt() method. :param ciphertext: The encrypted message (as a string). :param keyPath: A path to a file containing a 256-bit key (and nothing else). :type keyPath: str :rtype: str Raises an error if ciphertext was modified >>> import tempfile >>> k = tempfile.mktemp() >>> with open(k, 'w') as f: ... f.write(nacl.utils.random(SecretBox.KEY_SIZE)) >>> ciphertext = encrypt("testMessage", k) >>> ciphertext = chr(ord(ciphertext[0]) ^ 1) + ciphertext[1:] >>> decrypt(ciphertext, k) Traceback (most recent call last): ... CryptoError: Decryption failed. Ciphertext failed verification Otherwise works correctly >>> decrypt(encrypt("testMessage", k), k) 'testMessage' """ with open(keyPath) as f: key = f.read() if len(key) != SecretBox.KEY_SIZE: raise ValueError("Key is %d bytes, but must be exactly %d bytes" % (len(key), SecretBox.KEY_SIZE)) sb = SecretBox(key) # The nonce is kept with the message. return sb.decrypt(ciphertext)
def decrypt(self, edata: Tuple[bytes, bytes], privkey: bytes = None) -> bytes: """ Decrypt data encrypted by ECIES edata = (ekey, edata) ekey is needed to reconstruct a DH secret edata encrypted by the block cipher privkey is optional private key if we want to use something else than what keypair uses """ if isinstance(edata[0], tuple) and isinstance(edata[1], tuple): # In case it was re-encrypted data return self.decrypt_reencrypted(edata) ekey, edata = edata # When it comes to decrypt(), ekey[1] is always None # we could use that and save 2 bytes, # but it makes the code less readable ekey = umbral.EncryptedKey(ekey=ec.deserialize(API.PRE.ecgroup, ekey[0]), re_id=ekey[1]) if privkey is None: privkey = self._priv_key else: privkey = ec.deserialize(API.PRE.ecgroup, privkey) key = self.pre.decapsulate(privkey, ekey) cipher = SecretBox(key) return cipher.decrypt(edata)
class RecordPipe: def __init__(self, skt, send_key, receive_key): self.skt = skt self.send_box = SecretBox(send_key) self.send_nonce = 0 self.receive_buf = ReceiveBuffer(self.skt) self.receive_box = SecretBox(receive_key) self.next_receive_nonce = 0 def send_record(self, record): if not isinstance(record, type(b"")): raise UsageError assert SecretBox.NONCE_SIZE == 24 assert self.send_nonce < 2**(8*24) assert len(record) < 2**(8*4) nonce = unhexlify("%048x" % self.send_nonce) # big-endian self.send_nonce += 1 encrypted = self.send_box.encrypt(record, nonce) length = unhexlify("%08x" % len(encrypted)) # always 4 bytes long send_to(self.skt, length) send_to(self.skt, encrypted) def receive_record(self): length_buf = self.receive_buf.read(4) length = int(hexlify(length_buf), 16) encrypted = self.receive_buf.read(length) nonce_buf = encrypted[:SecretBox.NONCE_SIZE] # assume it's prepended nonce = int(hexlify(nonce_buf), 16) if nonce != self.next_receive_nonce: raise BadNonce("received out-of-order record") self.next_receive_nonce += 1 record = self.receive_box.decrypt(encrypted) return record def close(self): self.skt.close()
def _decrypt_data(self, key, encrypted): assert isinstance(key, type(b"")), type(key) assert isinstance(encrypted, type(b"")), type(encrypted) if len(key) != SecretBox.KEY_SIZE: raise UsageError box = SecretBox(key) data = box.decrypt(encrypted) return data
def decrypt(self, ciphered: bytes) -> bytes: """ Raises: CryptoError: if key is invalid. """ box = SecretBox(self) return box.decrypt(ciphered)
def read_data(self, escrowed_data, sign_key, layer_count=None): self.log.info('reading data from %d escrowed bytes', len(escrowed_data)) # TODO: check that we're logged in post_data = { 'escrow_data': escrowed_data, 'sign_key': serial.dumps(sign_key) } if layer_count is not None: post_data['layer_count'] = layer_count self.log.debug('requesting read_data') r = requests.post( self.url + 'data', params=self.get_auth_params(), data=post_data) boxed_data = r.content self.log.debug('got %d byte response from read_data', len(boxed_data)) self.log.debug('unboxing response') key = bcrypt.kdf( self.password.encode('utf-8'), self.challenge, SecretBox.KEY_SIZE, ITERATIONS ) box = SecretBox(key) data = box.decrypt(boxed_data) self.log.debug('unboxed response, got %d bytes', len(data)) return data
def open_with_key(cls, key, multihash): box = SecretBox(bytes.fromhex(key)) encrypted_data = cls.get_data(multihash) serialized_data = box.decrypt(encrypted_data) return cls(json.loads(serialized_data), key)
def recv_msg(self, user, packet): ''' INPUT * user : user message is from * packet : encrypted message received OUTPUT * Decrypted message with appropriate ''' try: dec_packet = encoder.decode(packet) self.send_ratc[user] = PublicKey(dec_packet[:32]) mac_packet = dec_packet[32:64] enc_msg = dec_packet[64:] dh_sec = get_DH_secret(self.recv_ratc[user], self.send_ratc[user]) salt = hashlib.md5(dh_sec).digest() self.root_keys[user] = kdf(self.root_keys[user], slt=salt) box = SecretBox(self.root_keys[user]) msg = box.decrypt(enc_msg) mac = hashlib.pbkdf2_hmac('sha256', msg, self.ad_number[user], 1414) if mac == mac_packet: return msg else: raise Exception('VERIFICATION FAILED: Connection to [' + user + '] aborted.') except Exception as e: print '[SALSAJAR: recv_msg] ' + str(e)
def unpack_packet(self, data: bytes) -> Tuple[int, int, int, bytes]: """ Unpacks a voice packet received from Discord. :param data: The data to unpack. :return: A tuple of (ssrc, sequence, timestamp, data). """ header = data[:12] encrypted_data = data[12:] # unpack header data type_ = header[0] version = header[1] sequence = struct.unpack(">H", header[2:4])[0] timestamp = struct.unpack(">I", header[4:8])[0] ssrc = struct.unpack(">I", header[8:12])[0] # okay, for some reason discord sends malformed packets # 0x90 as type means we need to chop off the first 8 bytes from the decrypted data # because it's invalid opus # first, decrypt the data nonce = bytearray(24) nonce[:12] = header encryptor = SecretBox(bytes(self.vs_ws.secret_key)) decrypted = encryptor.decrypt(encrypted_data, nonce=bytes(nonce)) if type_ == 0x90: decrypted = decrypted[8:] pcm_frames = self.decoder.decode(decrypted, 960) return ssrc, sequence, timestamp, pcm_frames
def encrypt_blocks(datablocks, debug): blockid_to_chunk = {} for V, block in enumerate( [datablocks[N] for N in range(0, len(datablocks))]): key = "%08x%08x" % (block.fileno, block.N) blockid_to_chunk[key] = V output = [] phys_offs = 0 for P, block in enumerate(datablocks): next_chunkid = blockid_to_chunk.get( "%08x%08x" % (block[0], block[1] + 1), 0xFFFFFFFF) nonce, encrypted_data = block_encrypt(block, nextid=next_chunkid) assert len(encrypted_data) == MAX_OUTER_LEN output.append(encrypted_data) if debug: key = sha512(block.secret, encoder=RawEncoder) box = SecretBox(key[:SecretBox.KEY_SIZE]) data = box.decrypt(encrypted_data, nonce) assert len(data) == FRAME_LEN eprint("%4X %8X %s %4X %s %s %8X %s" % (P, phys_offs, hexlify(encrypted_data[:4]), block.N, sha512(block.secret)[:8], hexlify( nonce[:4]), block.offset, sha512(data)[:8])) phys_offs += len(encrypted_data) return output
def decrypt(self, data, password, salt): salt = self._fix_salt(salt) key = hash_password_raw(password, hash_len=32, salt=salt) box = SecretBox(key) data = box.decrypt(data, encoder=nacl.encoding.Base64Encoder) return data
def test_secret_box_decryption_combined(key, nonce, plaintext, ciphertext): box = SecretBox(key, encoder=HexEncoder) combined = binascii.hexlify(binascii.unhexlify(nonce) + binascii.unhexlify(ciphertext)) decrypted = binascii.hexlify(box.decrypt(combined, encoder=HexEncoder)) assert decrypted == plaintext
def _decrypt_data(self, key, encrypted): assert isinstance(key, type(b"")), type(key) assert isinstance(encrypted, type(b"")), type(encrypted) assert len(key) == SecretBox.KEY_SIZE, len(key) box = SecretBox(key) data = box.decrypt(encrypted) return data
class EncryptedSerializer(object): """Encrypt session state using PyNaCl. :type secret: bytes :param secret: a 32-byte random secret for encrypting/decrypting the pickled session state. :param serializer: An object with two methods: ``loads`` and ``dumps``. The ``loads`` method should accept bytes and return a Python object. The ``dumps`` method should accept a Python object and return bytes. A ``ValueError`` should be raised for malformed inputs. Default: ``None``, which will use :class:`pyramid.session.PickleSerializer`. """ def __init__(self, secret, serializer=None): if len(secret) != SecretBox.KEY_SIZE: raise ValueError( "Secret should be a random bytes string of length %d" % SecretBox.KEY_SIZE) self.box = SecretBox(secret) if serializer is None: serializer = PickleSerializer() self.serializer = serializer def loads(self, bstruct): """Decrypt session state. :type encrypted_state: bytes :param encrypted_state: the encrypted session state. :rtype: :class:`dict` / picklable mapping :returns: the decrypted, unpickled session state, as passed as ``session_state`` to :meth:`dumps`. """ try: b64padding = b'=' * (-len(bstruct) % 4) fstruct = urlsafe_b64decode(bstruct + b64padding) except (binascii.Error, TypeError) as e: raise ValueError('Badly formed base64 data: %s' % e) try: payload = self.box.decrypt(fstruct) except CryptoError as e: raise ValueError('Possible tampering: %s' % e) return self.serializer.loads(payload) def dumps(self, session_state): """Encrypt session state. :type session_state: :class:`dict` / picklable mapping :param session_state: the session state to be encrypted. :rtype: bytes :returns: the encrypted session state """ cstruct = self.serializer.dumps(session_state) nonce = random(SecretBox.NONCE_SIZE) fstruct = self.box.encrypt(cstruct, nonce) return urlsafe_b64encode(fstruct).rstrip(b'=')
def decryptFile(self, inpPath, outPath, hexKey, chunkSize=65536): """Decrypt the data in the input file store the result in the output file. Args: inpPath ([type]): input file path (encrypted) outPath ([type]): output file path hexKey ([type]): encryption key chunkSize (int, optional): the size of incremental copy operations. Defaults to 65536. Returns: (bool): True for success or False otherwise """ try: box = SecretBox(hexKey.encode("utf-8"), encoder=HexEncoder) tL = [] with open(inpPath, "rb") as ifh: while True: chunk = ifh.read(chunkSize) if not chunk: break tL.append(chunk) # enctxt = b"".join(tL) # txt = box.decrypt(enctxt) tL = [txt[i:i + chunkSize] for i in range(0, len(txt), chunkSize)] logger.debug("Chunks %d size %d", len(tL), len(txt)) # with open(outPath, "wb") as ofh: for tV in tL: ofh.write(tV) return True except Exception as e: logger.exception("Failing with %r", str(e)) return False
def run_exchange(transport, key, nonce): box = SecretBox(key) line = transport.receive_line() decrypted = box.decrypt(util.hexstr_to_bytes(line)) transport.send_line(decrypted.decode('utf-8')) encrypted = util.bytes_to_hexstr(box.encrypt(decrypted, nonce)) transport.send_line(encrypted)
def handle(self, *args, **options): key = base64.b64decode(settings.PDK_BACKUP_KEY) for app in settings.INSTALLED_APPS: try: pdk_api = importlib.import_module(app + '.pdk_api') for encrypted_file in options['file']: if os.path.exists(encrypted_file): filename = os.path.basename(encrypted_file) box = SecretBox(key) with open(encrypted_file, 'rb') as backup_file: content = box.decrypt(backup_file.read()) decompressed = bz2.decompress(content) pdk_api.load_backup(filename, decompressed) else: raise RuntimeError(file + ' does not exist.') except ImportError: pass except AttributeError: pass
class BoxStream(object): """ i am a helper class for boxing datagrams in a unidirectional stream """ key = attr.ib(validator=is_32bytes) initial_nonce = attr.ib(validator=is_24bytes) nonce = attr.ib(init=False, default=None) MAX_LEN = 4096 def __attrs_post_init__(self): self.nonce = NonceCounter(self.initial_nonce, int(SecretBox.NONCE_SIZE)) self.box = SecretBox(self.key) def encrypt(self, datagram): """ SecretBox-encrypt the datagram and return ciphertext """ assert len(datagram) < self.MAX_LEN encrypted_body = self.box.encrypt(datagram, nonce=self.nonce()) return encrypted_body.ciphertext def decrypt(self, datagram): """ given SecretBox`ed ciphertext, decrypt and return plaintext """ assert len(datagram) < self.MAX_LEN payload = self.box.decrypt(datagram, nonce=self.nonce()) return payload
class RecordPipe: def __init__(self, skt, send_key, receive_key): self.skt = skt self.send_box = SecretBox(send_key) self.send_nonce = 0 self.receive_buf = ReceiveBuffer(self.skt) self.receive_box = SecretBox(receive_key) self.next_receive_nonce = 0 def send_record(self, record): if not isinstance(record, type(b"")): raise UsageError assert SecretBox.NONCE_SIZE == 24 assert self.send_nonce < 2**(8 * 24) assert len(record) < 2**(8 * 4) nonce = unhexlify("%048x" % self.send_nonce) # big-endian self.send_nonce += 1 encrypted = self.send_box.encrypt(record, nonce) length = unhexlify("%08x" % len(encrypted)) # always 4 bytes long send_to(self.skt, length) send_to(self.skt, encrypted) def receive_record(self): length_buf = self.receive_buf.read(4) length = int(hexlify(length_buf), 16) encrypted = self.receive_buf.read(length) nonce_buf = encrypted[:SecretBox.NONCE_SIZE] # assume it's prepended nonce = int(hexlify(nonce_buf), 16) if nonce != self.next_receive_nonce: raise BadNonce("received out-of-order record") self.next_receive_nonce += 1 record = self.receive_box.decrypt(encrypted) return record def close(self): self.skt.close()
def test_secret_box_decryption(key, nonce, plaintext, ciphertext): box = SecretBox(key, encoder=HexEncoder) nonce = binascii.unhexlify(nonce) decrypted = binascii.hexlify( box.decrypt(ciphertext, nonce, encoder=HexEncoder), ) assert decrypted == plaintext
def test_secret_box_optional_nonce(key, nonce, plaintext, ciphertext): box = SecretBox(key, encoder=HexEncoder) encrypted = box.encrypt(binascii.unhexlify(plaintext), encoder=HexEncoder) decrypted = binascii.hexlify(box.decrypt(encrypted, encoder=HexEncoder)) assert decrypted == plaintext
def test_secret_box_decryption_combined(key, nonce, plaintext, ciphertext): box = SecretBox(key, encoder=HexEncoder) combined = binascii.hexlify( binascii.unhexlify(nonce) + binascii.unhexlify(ciphertext), ) decrypted = binascii.hexlify(box.decrypt(combined, encoder=HexEncoder)) assert decrypted == plaintext
def decrypt_list_entry(boxed, symkey, tmppub): sbox = SecretBox(symkey) msg = remove_prefix(sbox.decrypt(boxed), "list:", NotListResponseError) (got_tmppub, fetch_token, delete_token, length) = struct.unpack(">32s32s32sQ", msg) if not equal(got_tmppub, tmppub): raise WrongPubkeyError return fetch_token, delete_token, length
def decrypt(ciphertext, key): with open(key, mode="rb") as key_file: key_bytes = key_file.read() with open(ciphertext, mode="rb") as ciphertext_file: ciphertext = ciphertext_file.read() secret_box = SecretBox(key_bytes) plaintext = secret_box.decrypt(ciphertext) print(f"Plaintext: {plaintext.decode('ascii')}")
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 decrypt(ciphertext, key): """ Decrypt a bytestring with the given key. :param ciphertext: a bytestring to decrypt :param key: a 32 byte long bytestring :return: resulting plaintext """ sb = SecretBox(key) return sb.decrypt(ciphertext)
def secret_box_decrypt(key_material: bytes, salt: bytes, ciphertext: bytes) -> bytes: wrapping_key = derive_wrapping_key_from_key_material(key_material, salt) secret_box = SecretBox(wrapping_key) try: plaintext = secret_box.decrypt(ciphertext) except CryptoError as e: raise SecretBoxAuthenticationError from e return plaintext
def descifra_mensaje(self, mensaje): """ Descrifra un mensaje con una Sbox :param mensaje: mensaje cifrado en base 64 y con Sbox :return: el mensaje descifrado """ sb = SecretBox(self.krecib) dec = sb.decrypt(base64.b64decode(mensaje)) return dec
def decrypt(data, privkey): j,enc = data.split(sep=_separator, maxsplit=1) jsondata = json.loads(j.decode("utf-8")) key = base64.b64decode(jsondata["key"].encode("utf-8")) version = jsondata["version"] if version == "0.1": box = SecretBox(key) return box.decrypt(enc).decode("utf-8") else: raise NotImplementedError("Filetype not supported.")
def symm_decrypt(key: bytes, ciphertext: bytes) -> bytes: """ Decrypts ciphertext performed with nacl.SecretBox. :param key: Key to decrypt with :param ciphertext: Nacl.SecretBox ciphertext to decrypt :return: Decrypted Plaintext """ cipher = SecretBox(key) return cipher.decrypt(ciphertext)
def test_secret_box_bad_decryption(): box = SecretBox(b"\x11" * 32) ciphertext = box.encrypt(b"hello") with pytest.raises(CryptoError): # changes the nonce box.decrypt(flip_byte(ciphertext, 0)) with pytest.raises(CryptoError): # changes ciphertext box.decrypt(flip_byte(ciphertext, 24)) with pytest.raises(CryptoError): # changes MAC tag box.decrypt(flip_byte(ciphertext, len(ciphertext) - 1)) with pytest.raises(CryptoError): # completely changes ciphertext and tag box.decrypt(ciphertext + b"\x00") with pytest.raises(CryptoError): # completely changes everything box.decrypt(b"\x00" + ciphertext)
class PassphraseBox(object): ITERATIONS = 90000 # Given passphrase and plaintext as strings, returns a dict # containing the ciphertext and other values needed for later # decryption. Binary values are encoded as hexadecimal strings. @classmethod def encrypt(cls, passphrase, plaintext): box = cls(passphrase) return box._encrypt(plaintext) # encrypted = dict(salt=salt, nonce=nonce, ciphertext=ciphertext) # PassphraseBox.decrypt("my great password", encrypted) @classmethod def decrypt(cls, passphrase, encrypted): salt = encrypted['salt'] iterations = encrypted['iterations'] return cls(passphrase, salt, iterations)._decrypt( encrypted['ciphertext'], encrypted['nonce']) # Initialize with an existing salt and iterations to allow # decryption. Otherwise, creates new values for these, meaning # it creates an entirely new secret box. def __init__(self, passphrase, salt=None, iterations=None): passphrase = passphrase.encode('utf-8') self.salt = salt.decode('hex') if salt else urandom(16) if iterations: self.iterations = iterations else: # per OWASP, use a random number of iterations between 90k and 110k self.iterations = self.ITERATIONS + randint(0,20000) key = pbkdf2_bin(passphrase, salt=self.salt, iterations=self.iterations, keylen=32) self.box = SecretBox(key) def _encrypt(self, plaintext): plaintext = plaintext.encode('utf-8') nonce = urandom(SecretBox.NONCE_SIZE) encrypted = self.box.encrypt(plaintext, nonce) ciphertext = encrypted.ciphertext return dict( salt=self.salt.encode('hex'), iterations=self.iterations, nonce=nonce.encode('hex'), ciphertext=ciphertext.encode('hex') ) def _decrypt(self, ciphertext, nonce): return self.box.decrypt(ciphertext.decode('hex'), nonce.decode('hex'))
class PassphraseBox: ITERATIONS = 10000 @classmethod def encrypt(cls, passphrase, plaintext): box = cls(passphrase) return box._encrypt(plaintext) @classmethod def decrypt(cls, passphrase, encrypted): salt = encrypted['salt'] iterations = encrypted['iterations'] ppbox = cls(passphrase, salt, iterations) return ppbox._decrypt(encrypted['ciphertext'], encrypted['nonce']) def __init__(self, passphrase, salt=None, iterations=None): passphrase = passphrase.encode('utf-8') if salt is None: salt = random(16) iterations = self.ITERATIONS else: salt = salt.decode('hex') key = PBKDF2(passphrase, salt, 32, iterations) self.salt = salt self.iterations = iterations self.box = SecretBox(key) def _encrypt(self, plaintext): plaintext = plaintext.encode('utf-8') nonce = random(SecretBox.NONCE_SIZE) encrypted = self.box.encrypt(plaintext, nonce) ciphertext = encrypted.ciphertext return dict( salt=self.salt.encode('hex'), iterations=self.iterations, nonce=nonce.encode('hex'), ciphertext=ciphertext.encode('hex') ) def _decrypt(self, ciphertext, nonce): return self.box.decrypt(ciphertext.decode('hex'), nonce.decode('hex'))
class EncryptingPickleSerializer(object): """Encrypt pickled session state using PyNaCl. :type secret: bytes :param secret: a 32-byte random secret for encrypting/decrypting the pickled session state. """ def __init__(self, secret): if len(secret) != SecretBox.KEY_SIZE: raise ValueError( "Secret should be a random bytes string of length %d" % SecretBox.KEY_SIZE) self.box = SecretBox(secret) def loads(self, encrypted_state): """Decrypt session state. :type encrypted_state: bytes :param encrypted_state: the encrypted session state. :rtype: :class:`dict` / picklable mapping :returns: the decrypted, unpickled session state, as passed as ``session_state`` to :meth:`dumps`. """ payload = self.box.decrypt(urlsafe_b64decode(encrypted_state)) return pickle.loads(payload) def dumps(self, session_state): """Encrypt session state. :type session_state: :class:`dict` / picklable mapping :param session_state: the session state to be encrypted. :rtype: bytes :returns: the encrypted session state """ pickled = pickle.dumps(session_state) nonce = random(SecretBox.NONCE_SIZE) return urlsafe_b64encode(self.box.encrypt(pickled, nonce))
class NaclPassphraseBox(object): # FIXME: PassphraseBox in Ruby has the default iterations set # to 100,000. One or the other needs to change. ITERATIONS = 10000 # encrypted = dict(salt=salt, nonce=nonce, ciphertext=ciphertext) # PassphraseBox.decrypt("my great password", encrypted) @classmethod def decrypt(cls, passphrase, encrypted): salt = encrypted['salt'] iterations = encrypted['iterations'] ppbox = cls(passphrase, salt, iterations) return ppbox._decrypt(encrypted['ciphertext'], encrypted['nonce']) # Initialize with an existing salt and iterations to allow # decryption. Otherwise, creates new values for these, meaning # it creates an entirely new secret box. def __init__(self, passphrase, salt=None, iterations=None): warn(("CoinOp no longer uses libsodium - you should only see this " "message in migration utilities."), DeprecationWarning) passphrase = passphrase.encode('utf-8') if salt is None: salt = random(16) iterations = self.ITERATIONS else: salt = salt.decode('hex') key = PBKDF2(passphrase, salt, 32, iterations) self.salt = salt self.iterations = iterations self.box = SecretBox(key) def _decrypt(self, ciphertext, nonce): return self.box.decrypt(ciphertext.decode('hex'), nonce.decode('hex'))
def init(server, username, keydir, action, message, recipients): """ SHSM CLI client. """ global serverurl serverurl = server if action == "register": master_signing_key = SigningKey.generate() device_signing_key = SigningKey.generate() device_private_key = PrivateKey.generate() enc_master_verify_key = master_signing_key.verify_key.encode(encoder=HexEncoder) register(username, enc_master_verify_key) # TODO: make sure keydir exists save_key(master_signing_key.encode(encoder=HexEncoder), keydir + "/master_signing_key") save_key(device_signing_key.encode(encoder=HexEncoder), keydir + "/device_signing_key") save_key(device_private_key.encode(encoder=HexEncoder), keydir + "/device_private_key") else: try: master_signing_key = SigningKey(load_key(keydir + "/master_signing_key"), encoder=HexEncoder) device_signing_key = SigningKey(load_key(keydir + "/device_signing_key"), encoder=HexEncoder) device_private_key = PrivateKey(load_key(keydir + "/device_private_key"), encoder=HexEncoder) except TypeError: print "bad key, exiting." exit() if action == "add-device": enc_device_verify_key = device_signing_key.verify_key.encode(encoder=HexEncoder) enc_signed_device_verify_key = b64encode(master_signing_key.sign(enc_device_verify_key)) enc_device_public_key = device_private_key.public_key.encode(encoder=HexEncoder) enc_signed_device_public_key = b64encode(master_signing_key.sign(enc_device_public_key)) add_device(username, enc_signed_device_verify_key, enc_signed_device_public_key) if action == "send-message": ephemeral_key = PrivateKey.generate() enc_ephemeral_public_key = b64encode( device_signing_key.sign(ephemeral_key.public_key.encode(encoder=HexEncoder)) ) # TODO:: should sign binary text, no? b"bob" destination_usernames = recipients.split(",") enc_dest_usernames = b64encode( device_signing_key.sign(json.dumps({"destination_usernames": destination_usernames})) ) symmetric_key = random(SecretBox.KEY_SIZE) symmetric_box = SecretBox(symmetric_key) nonce = random(SecretBox.NONCE_SIZE) msg_manifest = {} msg_manifest["recipients"] = {} msg_manifest["msg"] = b64encode(symmetric_box.encrypt(str(message), nonce)) for dest_user in destination_usernames: msg_manifest["recipients"][dest_user] = {} for recipient_key in get_recipient_keys( device_signing_key.verify_key.encode(encoder=HexEncoder), b64encode(device_signing_key.sign(str(dest_user))), ): # TODO:: should sign binary text, no? crypt_box = Box(ephemeral_key, recipient_key) nonce = random(Box.NONCE_SIZE) crypt_key = b64encode(crypt_box.encrypt(symmetric_key, nonce)) dest_key = recipient_key.encode(encoder=HexEncoder) msg_manifest["recipients"][dest_user][dest_key] = crypt_key enc_signed_crypt_msg = b64encode(device_signing_key.sign(json.dumps(msg_manifest))) send_message( device_signing_key.verify_key.encode(encoder=HexEncoder), enc_dest_usernames, enc_signed_crypt_msg, enc_ephemeral_public_key, ) if action == "get-messages": enc_device_verify_key = device_signing_key.verify_key.encode(encoder=HexEncoder) enc_signed_device_verify_key = b64encode(device_signing_key.sign(enc_device_verify_key)) messages = get_messages(enc_device_verify_key, enc_signed_device_verify_key) for message_public_key in messages["messages"].keys(): try: crypto_box = Box(device_private_key, PublicKey(b64decode(message_public_key), encoder=HexEncoder)) except TypeError: print "not a valid public key" exit() packed_msg = json.loads(messages["messages"][message_public_key]) msg_manifest = json.loads(b64decode(packed_msg["message_manifest"])) dest_pub_key = device_private_key.public_key.encode(encoder=HexEncoder) symmetric_key = crypto_box.decrypt(b64decode(msg_manifest["recipients"][username][dest_pub_key])) symmetric_box = SecretBox(symmetric_key) print ("From: %s\nMessage: %s") % ( packed_msg["reply_to"], symmetric_box.decrypt(b64decode(msg_manifest["msg"])), )
def _extract_caveat_key(self, signature, caveat): key = truncate_or_pad(signature) box = SecretBox(key=key) decrypted = box.decrypt(caveat._verification_key_id) return decrypted
class Connection(protocol.Protocol, policies.TimeoutMixin): def __init__(self, owner, relay_handshake, start): self.state = "too-early" self.buf = b"" self.owner = owner self.relay_handshake = relay_handshake self.start = start self._negotiation_d = defer.Deferred(self._cancel) self._error = None self._consumer = None self._inbound_records = collections.deque() self._waiting_reads = collections.deque() def connectionMade(self): debug("handle %r" % (self.transport,)) self.setTimeout(TIMEOUT) # does timeoutConnection() when it expires self.factory.connectionWasMade(self, self.transport.getPeer()) def startNegotiation(self, description): self.description = description if self.relay_handshake is not None: self.transport.write(self.relay_handshake) self.state = "relay" else: self.state = "start" self.dataReceived(b"") # cycle the state machine return self._negotiation_d def _cancel(self, d): self.state = "hung up" # stop reacting to anything further self._error = defer.CancelledError() self.transport.loseConnection() # if connectionLost isn't called synchronously, then our # self._negotiation_d will have been errbacked by Deferred.cancel # (which is our caller). So if it's still around, clobber it if self._negotiation_d: self._negotiation_d = None def dataReceived(self, data): try: self._dataReceived(data) except Exception as e: self.setTimeout(None) self._error = e self.transport.loseConnection() self.state = "hung up" if not isinstance(e, BadHandshake): raise def _check_and_remove(self, expected): # any divergence is a handshake error if not self.buf.startswith(expected[:len(self.buf)]): raise BadHandshake("got %r want %r" % (self.buf, expected)) if len(self.buf) < len(expected): return False # keep waiting self.buf = self.buf[len(expected):] return True def _dataReceived(self, data): # protocol is: # (maybe: send relay handshake, wait for ok) # send (send|receive)_handshake # wait for (receive|send)_handshake # sender: decide, send "go" or hang up # receiver: wait for "go" self.buf += data assert self.state != "too-early" if self.state == "relay": if not self._check_and_remove(b"ok\n"): return self.state = "start" if self.state == "start": self.transport.write(self.owner._send_this()) self.state = "handshake" if self.state == "handshake": if not self._check_and_remove(self.owner._expect_this()): return self.state = self.owner.connection_ready(self, self.description) # If we're the receiver, we'll be moved to state # "wait-for-decision", which means we're waiting for the other # side (the sender) to make a decision. If we're the sender, # we'll either be moved to state "go" (send GO and move directly # to state "records") or state "nevermind" (send NEVERMIND and # hang up). if self.state == "wait-for-decision": if not self._check_and_remove(b"go\n"): return self._negotiationSuccessful() if self.state == "go": GO = b"go\n" self.transport.write(GO) self._negotiationSuccessful() if self.state == "nevermind": self.transport.write(b"nevermind\n") raise BadHandshake("abandoned") if self.state == "records": return self.dataReceivedRECORDS() if isinstance(self.state, Exception): # for tests raise self.state def _negotiationSuccessful(self): self.state = "records" self.setTimeout(None) send_key = self.owner._sender_record_key() self.send_box = SecretBox(send_key) self.send_nonce = 0 receive_key = self.owner._receiver_record_key() self.receive_box = SecretBox(receive_key) self.next_receive_nonce = 0 d, self._negotiation_d = self._negotiation_d, None d.callback(self) def dataReceivedRECORDS(self): if len(self.buf) < 4: return length = int(hexlify(self.buf[:4]), 16) if len(self.buf) < 4+length: return encrypted, self.buf = self.buf[4:4+length], self.buf[4+length:] record = self._decrypt_record(encrypted) self.recordReceived(record) def _decrypt_record(self, encrypted): nonce_buf = encrypted[:SecretBox.NONCE_SIZE] # assume it's prepended nonce = int(hexlify(nonce_buf), 16) if nonce != self.next_receive_nonce: raise BadNonce("received out-of-order record: got %d, expected %d" % (nonce, self.next_receive_nonce)) self.next_receive_nonce += 1 record = self.receive_box.decrypt(encrypted) return record def send_record(self, record): if not isinstance(record, type(b"")): raise UsageError assert SecretBox.NONCE_SIZE == 24 assert self.send_nonce < 2**(8*24) assert len(record) < 2**(8*4) nonce = unhexlify("%048x" % self.send_nonce) # big-endian self.send_nonce += 1 encrypted = self.send_box.encrypt(record, nonce) length = unhexlify("%08x" % len(encrypted)) # always 4 bytes long self.transport.write(length) self.transport.write(encrypted) def recordReceived(self, record): if self._consumer: self._consumer.write(record) return self._inbound_records.append(record) self._deliverRecords() def receive_record(self): d = defer.Deferred() self._waiting_reads.append(d) self._deliverRecords() return d def _deliverRecords(self): while self._inbound_records and self._waiting_reads: r = self._inbound_records.popleft() d = self._waiting_reads.popleft() d.callback(r) def close(self): self.transport.loseConnection() while self._waiting_reads: d = self._waiting_reads.popleft() d.errback(error.ConnectionClosed()) def timeoutConnection(self): self._error = BadHandshake("timeout") self.transport.loseConnection() def connectionLost(self, reason=None): self.setTimeout(None) d, self._negotiation_d = self._negotiation_d, None # the Deferred is only relevant until negotiation finishes, so skip # this if it's alredy been fired if d: # Each call to loseConnection() sets self._error first, so we can # deliver useful information to the Factory that's waiting on # this (although they'll generally ignore the specific error, # except for logging unexpected ones). The possible cases are: # # cancel: defer.CancelledError # far-end disconnect: BadHandshake("connection lost") # handshake error (something we didn't like): BadHandshake(what) # other error: some other Exception # timeout: BadHandshake("timeout") d.errback(self._error or BadHandshake("connection lost")) # IConsumer methods, for outbound flow-control. We pass these through to # the transport. The 'producer' is something like a t.p.basic.FileSender def registerProducer(self, producer, streaming): assert interfaces.IConsumer.providedBy(self.transport) self.transport.registerProducer(producer, streaming) def unregisterProducer(self): self.transport.unregisterProducer() def write(self, data): self.send_record(data) # IProducer methods, for inbound flow-control. We pass these through to # the transport. def stopProducing(self): self.transport.stopProducing() def pauseProducing(self): self.transport.pauseProducing() def resumeProducing(self): self.transport.resumeProducing() # Helper method to glue an instance of e.g. t.p.ftp.FileConsumer to us. # Inbound records will be written as bytes to the consumer. def connectConsumer(self, consumer): if self._consumer: raise RuntimeError("A consumer is already attached: %r" % self._consumer) self._consumer = consumer # drain any pending records while self._inbound_records: r = self._inbound_records.popleft() consumer.write(r) consumer.registerProducer(self, True) def disconnectConsumer(self): self._consumer.unregisterProducer() self._consumer = None
def _decrypt_data(self, key, encrypted): assert len(key) == SecretBox.KEY_SIZE box = SecretBox(key) data = box.decrypt(encrypted) return data
def decrypt(self, signature, field_data): key = truncate_or_pad(signature) box = SecretBox(key=key) encoded = convert_to_bytes(field_data[len(self.signifier):]) decrypted = box.decrypt(standard_b64decode(encoded)) return convert_to_string(decrypted)
class CurveCPServerDispatcher(DatagramProtocol): def __init__(self, reactor, serverKey, factory): self.reactor = reactor self.serverKey = serverKey self.factory = factory self.transports = {} self._secretBox = SecretBox(os.urandom(SecretBox.KEY_SIZE)) def _replyWithCookie(self, data, host_port): if len(data) != _helloStruct.size: return serverExtension, clientExtension, clientShortPubkey, nonce, encrypted = _helloStruct.unpack(data) serverLongClientShort = Box(self.serverKey.key, PublicKey(clientShortPubkey)) try: serverLongClientShort.decrypt(encrypted, 'CurveCP-client-H' + nonce) except CryptoError: return serverShortKey = PrivateKey.generate() unencryptedCookie = clientShortPubkey + str(serverShortKey) cookieNonce = self.serverKey.nonce(longterm=True) cookie = cookieNonce + self._secretBox.encrypt(unencryptedCookie, 'c' * 8 + cookieNonce).ciphertext boxData = str(serverShortKey.public_key) + cookie cookiePacket = ( 'RL3aNMXK' + clientExtension + serverExtension + cookieNonce + serverLongClientShort.encrypt(boxData, 'CurveCPK' + cookieNonce).ciphertext) self.transport.write(cookiePacket, host_port) def _checkInitiate(self, clientID, data, host_port): cookieNonce, encryptedCookie, nonce = _initiateStruct.unpack_from(data) try: decryptedCookie = self._secretBox.decrypt(encryptedCookie, 'c' * 8 + cookieNonce) except CryptoError: return clientShortPubkey = PublicKey(decryptedCookie[:32]) serverShortKey = PrivateKey(decryptedCookie[32:]) serverShortClientShort = Box(serverShortKey, clientShortPubkey) try: decrypted = serverShortClientShort.decrypt(data[176:], 'CurveCP-client-I' + nonce) except CryptoError: return clientPubkeyString, vouchNonce, encryptedVouch, serverDomain = _initiateInnerStruct.unpack_from(decrypted) clientPubkey = PublicKey(clientPubkeyString) serverLongClientLong = Box(self.serverKey.key, clientPubkey) try: vouchKey = serverLongClientLong.decrypt(encryptedVouch, 'CurveCPV' + vouchNonce) except CryptoError: return if vouchKey != str(clientShortPubkey): return transport = CurveCPServerTransport( self.reactor, self.serverKey, self.factory, clientID, clientPubkey, host_port, serverShortClientShort, dnsToName(serverDomain)) return transport, decrypted[352:] def datagramReceived(self, data, host_port): l = len(data) if l < 80 or l > 1184 or l & 0xf: return if data[:8] == 'QvnQ5XlH': self._replyWithCookie(data, host_port) return clientID = data[8:72] if clientID in self.transports: self.transports[clientID].datagramReceived(data, host_port) return if data[:8] != 'QvnQ5XlI': return result = self._checkInitiate(clientID, data, host_port) if result is None: return log.msg('new client: %s' % clientID.encode('hex'), category='success') transport, message = result self.transports[clientID] = transport transport.transport = self.transport transport.startProtocol() transport._parseMessage(transport._now(), message) transport.notifyFinish().addCallback(self._clientFinished, clientID) def _clientFinished(self, ign, clientID): del self.transports[clientID]
def _decrypt(self, bin_data, key): bin_data = bin_data.split(':::') derived_key = self._get_key(key, bin_data[0]) private_box = SecretBox(derived_key[1]) return str(private_box.decrypt(bin_data[1]))
class Connection(protocol.Protocol, policies.TimeoutMixin): def __init__(self, owner, relay_handshake, start, description): self.state = "too-early" self.buf = b"" self.owner = owner self.relay_handshake = relay_handshake self.start = start self._description = description self._negotiation_d = defer.Deferred(self._cancel) self._error = None self._consumer = None self._consumer_bytes_written = 0 self._consumer_bytes_expected = None self._consumer_deferred = None self._inbound_records = deque() self._waiting_reads = deque() def connectionMade(self): self.setTimeout(TIMEOUT) # does timeoutConnection() when it expires self.factory.connectionWasMade(self) def startNegotiation(self): if self.relay_handshake is not None: self.transport.write(self.relay_handshake) self.state = "relay" else: self.state = "start" self.dataReceived(b"") # cycle the state machine return self._negotiation_d def _cancel(self, d): self.state = "hung up" # stop reacting to anything further self._error = defer.CancelledError() self.transport.loseConnection() # if connectionLost isn't called synchronously, then our # self._negotiation_d will have been errbacked by Deferred.cancel # (which is our caller). So if it's still around, clobber it if self._negotiation_d: self._negotiation_d = None def dataReceived(self, data): try: self._dataReceived(data) except Exception as e: self.setTimeout(None) self._error = e self.transport.loseConnection() self.state = "hung up" if not isinstance(e, BadHandshake): raise def _check_and_remove(self, expected): # any divergence is a handshake error if not self.buf.startswith(expected[:len(self.buf)]): raise BadHandshake("got %r want %r" % (self.buf, expected)) if len(self.buf) < len(expected): return False # keep waiting self.buf = self.buf[len(expected):] return True def _dataReceived(self, data): # protocol is: # (maybe: send relay handshake, wait for ok) # send (send|receive)_handshake # wait for (receive|send)_handshake # sender: decide, send "go" or hang up # receiver: wait for "go" self.buf += data assert self.state != "too-early" if self.state == "relay": if not self._check_and_remove(b"ok\n"): return self.state = "start" if self.state == "start": self.transport.write(self.owner._send_this()) self.state = "handshake" if self.state == "handshake": if not self._check_and_remove(self.owner._expect_this()): return self.state = self.owner.connection_ready(self) # If we're the receiver, we'll be moved to state # "wait-for-decision", which means we're waiting for the other # side (the sender) to make a decision. If we're the sender, # we'll either be moved to state "go" (send GO and move directly # to state "records") or state "nevermind" (send NEVERMIND and # hang up). if self.state == "wait-for-decision": if not self._check_and_remove(b"go\n"): return self._negotiationSuccessful() if self.state == "go": GO = b"go\n" self.transport.write(GO) self._negotiationSuccessful() if self.state == "nevermind": self.transport.write(b"nevermind\n") raise BadHandshake("abandoned") if self.state == "records": return self.dataReceivedRECORDS() if self.state == "hung up": return if isinstance(self.state, Exception): # for tests raise self.state raise ValueError("internal error: unknown state %s" % (self.state,)) def _negotiationSuccessful(self): self.state = "records" self.setTimeout(None) send_key = self.owner._sender_record_key() self.send_box = SecretBox(send_key) self.send_nonce = 0 receive_key = self.owner._receiver_record_key() self.receive_box = SecretBox(receive_key) self.next_receive_nonce = 0 d, self._negotiation_d = self._negotiation_d, None d.callback(self) def dataReceivedRECORDS(self): while True: if len(self.buf) < 4: return length = int(hexlify(self.buf[:4]), 16) if len(self.buf) < 4+length: return encrypted, self.buf = self.buf[4:4+length], self.buf[4+length:] record = self._decrypt_record(encrypted) self.recordReceived(record) def _decrypt_record(self, encrypted): nonce_buf = encrypted[:SecretBox.NONCE_SIZE] # assume it's prepended nonce = int(hexlify(nonce_buf), 16) if nonce != self.next_receive_nonce: raise BadNonce("received out-of-order record: got %d, expected %d" % (nonce, self.next_receive_nonce)) self.next_receive_nonce += 1 record = self.receive_box.decrypt(encrypted) return record def describe(self): return self._description def send_record(self, record): if not isinstance(record, type(b"")): raise InternalError assert SecretBox.NONCE_SIZE == 24 assert self.send_nonce < 2**(8*24) assert len(record) < 2**(8*4) nonce = unhexlify("%048x" % self.send_nonce) # big-endian self.send_nonce += 1 encrypted = self.send_box.encrypt(record, nonce) length = unhexlify("%08x" % len(encrypted)) # always 4 bytes long self.transport.write(length) self.transport.write(encrypted) def recordReceived(self, record): if self._consumer: self._writeToConsumer(record) return self._inbound_records.append(record) self._deliverRecords() def receive_record(self): d = defer.Deferred() self._waiting_reads.append(d) self._deliverRecords() return d def _deliverRecords(self): while self._inbound_records and self._waiting_reads: r = self._inbound_records.popleft() d = self._waiting_reads.popleft() d.callback(r) def close(self): self.transport.loseConnection() while self._waiting_reads: d = self._waiting_reads.popleft() d.errback(error.ConnectionClosed()) def timeoutConnection(self): self._error = BadHandshake("timeout") self.transport.loseConnection() def connectionLost(self, reason=None): self.setTimeout(None) d, self._negotiation_d = self._negotiation_d, None # the Deferred is only relevant until negotiation finishes, so skip # this if it's alredy been fired if d: # Each call to loseConnection() sets self._error first, so we can # deliver useful information to the Factory that's waiting on # this (although they'll generally ignore the specific error, # except for logging unexpected ones). The possible cases are: # # cancel: defer.CancelledError # far-end disconnect: BadHandshake("connection lost") # handshake error (something we didn't like): BadHandshake(what) # other error: some other Exception # timeout: BadHandshake("timeout") d.errback(self._error or BadHandshake("connection lost")) if self._consumer_deferred: self._consumer_deferred.errback(error.ConnectionClosed()) # IConsumer methods, for outbound flow-control. We pass these through to # the transport. The 'producer' is something like a t.p.basic.FileSender def registerProducer(self, producer, streaming): assert interfaces.IConsumer.providedBy(self.transport) self.transport.registerProducer(producer, streaming) def unregisterProducer(self): self.transport.unregisterProducer() def write(self, data): self.send_record(data) # IProducer methods, for inbound flow-control. We pass these through to # the transport. def stopProducing(self): self.transport.stopProducing() def pauseProducing(self): self.transport.pauseProducing() def resumeProducing(self): self.transport.resumeProducing() # Helper methods def connectConsumer(self, consumer, expected=None): """Helper method to glue an instance of e.g. t.p.ftp.FileConsumer to us. Inbound records will be written as bytes to the consumer. Set 'expected' to an integer to automatically disconnect when at least that number of bytes have been written. This function will then return a Deferred (that fires with the number of bytes actually received). If the connection is lost while this Deferred is outstanding, it will errback. If 'expected' is 0, the Deferred will fire right away. If 'expected' is None, then this function returns None instead of a Deferred, and you must call disconnectConsumer() when you are done.""" if self._consumer: raise RuntimeError("A consumer is already attached: %r" % self._consumer) # be aware of an ordering hazard: when we call the consumer's # .registerProducer method, they are likely to immediately call # self.resumeProducing, which we'll deliver to self.transport, which # might call our .dataReceived, which may cause more records to be # available. By waiting to set self._consumer until *after* we drain # any pending records, we avoid delivering records out of order, # which would be bad. consumer.registerProducer(self, True) # There might be enough data queued to exceed 'expected' before we # leave this function. We must be sure to register the producer # before it gets unregistered. self._consumer = consumer self._consumer_bytes_written = 0 self._consumer_bytes_expected = expected d = None if expected is not None: d = defer.Deferred() self._consumer_deferred = d if expected == 0: # write empty record to kick consumer into shutdown self._writeToConsumer(b"") # drain any pending records while self._consumer and self._inbound_records: r = self._inbound_records.popleft() self._writeToConsumer(r) return d def _writeToConsumer(self, record): self._consumer.write(record) self._consumer_bytes_written += len(record) if self._consumer_bytes_expected is not None: if self._consumer_bytes_written >= self._consumer_bytes_expected: d = self._consumer_deferred self.disconnectConsumer() d.callback(self._consumer_bytes_written) def disconnectConsumer(self): self._consumer.unregisterProducer() self._consumer = None self._consumer_bytes_expected = None self._consumer_deferred = None # Helper method to write a known number of bytes to a file. This has no # flow control: the filehandle cannot push back. 'progress' is an # optional callable which will be called on each write (with the number # of bytes written). Returns a Deferred that fires (with the number of # bytes written) when the count is reached or the RecordPipe is closed. def writeToFile(self, f, expected, progress=None, hasher=None): fc = FileConsumer(f, progress, hasher) return self.connectConsumer(fc, expected)