def __exit__(self, exc_type, exc_val, exc_tb): """ Re-encrypt. """ # Derive the key from the passphrase. derived = util.derive_passphrase(self.passphrase) # Generate two random nonces. nonce1 = random(SecretBox.NONCE_SIZE) nonce2 = random(SecretBox.NONCE_SIZE) sign_box = SecretBox(derived) enc_box = SecretBox(derived) s_p = self.sign.encode() e_p = self.encrypt.encode() s_e = sign_box.encrypt(s_p, nonce1) e_e = enc_box.encrypt(e_p, nonce2) # Update `self.key`. self.key._private_key_raw = e_e.ciphertext self.key._private_signing_key_raw = s_e.ciphertext # Bit of a mixed up name. self.key._private_nonce = e_e.nonce self.key._private_signing_nonce = s_e.nonce if exc_type is not None: raise exc_type(exc_val)
def test_secret_box_encryption_generates_different_nonces(key, nonce, plaintext, ciphertext): box = SecretBox(key, encoder=HexEncoder) nonce_0 = box.encrypt(binascii.unhexlify(plaintext), encoder=HexEncoder).nonce nonce_1 = box.encrypt(binascii.unhexlify(plaintext), encoder=HexEncoder).nonce assert nonce_0 != nonce_1
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 add_third_party_caveat(self, macaroon, location, key, key_id, **kwargs): derived_key = truncate_or_pad( generate_derived_key(convert_to_bytes(key)) ) old_key = truncate_or_pad(binascii.unhexlify(macaroon.signature_bytes)) box = SecretBox(key=old_key) verification_key_id = box.encrypt( derived_key, nonce=kwargs.get('nonce') ) caveat = Caveat( caveat_id=key_id, location=location, verification_key_id=verification_key_id, version=macaroon.version ) macaroon.caveats.append(caveat) encode_key = binascii.unhexlify(macaroon.signature_bytes) macaroon.signature = sign_third_party_caveat( encode_key, caveat._verification_key_id, caveat._caveat_id ) return macaroon
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 encrypt_data(key, plaintext): assert isinstance(key, type(b"")), type(key) assert isinstance(plaintext, type(b"")), type(plaintext) assert len(key) == SecretBox.KEY_SIZE, len(key) box = SecretBox(key) nonce = utils.random(SecretBox.NONCE_SIZE) return box.encrypt(plaintext, nonce)
def add_third_party_caveat(self, macaroon, location, key, key_id, nonce=None, **kwargs): derived_key = truncate_or_pad( generate_derived_key(convert_to_bytes(key)) ) old_key = truncate_or_pad(binascii.unhexlify(macaroon.signature_bytes)) box = SecretBox(key=old_key) nonce = nonce or nacl.utils.random(box.NONCE_SIZE) verification_key_id = box.encrypt( derived_key, nonce=nonce ) caveat = Caveat( caveat_id=key_id, location=location, verification_key_id=verification_key_id ) macaroon.caveats.append(caveat) encode_key = binascii.unhexlify(macaroon.signature_bytes) macaroon.signature = sign_third_party_caveat( encode_key, caveat._verification_key_id, caveat._caveat_id ) return macaroon
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 encrypt(message, keyPath): """ Encrypts a message given a path to a local file containing a key. :param message: The message to be encrypted. :param keyPath: A path to a file containing a 256-bit key (and nothing else). :type message: str :type keyPath: str :rtype: str A constant overhead is added to every encrypted message (for the nonce and MAC). >>> import tempfile >>> k = tempfile.mktemp() >>> with open(k, 'w') as f: ... f.write(nacl.utils.random(SecretBox.KEY_SIZE)) >>> message = 'test' >>> len(encrypt(message, k)) == encryptionOverhead + len(message) True """ 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) # We generate the nonce using secure random bits. For long enough # nonce size, the chance of a random nonce collision becomes # *much* smaller than the chance of a subtle coding error causing # a nonce reuse. Currently the nonce size is 192 bits--the chance # of a collision is astronomically low. (This approach is # recommended in the libsodium documentation.) nonce = nacl.utils.random(SecretBox.NONCE_SIZE) assert len(nonce) == SecretBox.NONCE_SIZE return str(sb.encrypt(message, nonce))
def _encrypt_data(self, key, data): assert isinstance(key, type(b"")), type(key) assert isinstance(data, type(b"")), type(data) if len(key) != SecretBox.KEY_SIZE: raise UsageError box = SecretBox(key) nonce = utils.random(SecretBox.NONCE_SIZE) return box.encrypt(data, nonce)
def _encrypt_data(self, key, data): assert isinstance(key, type(b"")), type(key) assert isinstance(data, type(b"")), type(data) assert len(key) == SecretBox.KEY_SIZE, len(key) box = SecretBox(key) nonce = utils.random(SecretBox.NONCE_SIZE) return box.encrypt(data, nonce)
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_encryption(key, nonce, plaintext, ciphertext): box = SecretBox(key, encoder=HexEncoder) encrypted = box.encrypt(binascii.unhexlify(plaintext), binascii.unhexlify(nonce), encoder=HexEncoder) expected = binascii.hexlify(binascii.unhexlify(nonce) + binascii.unhexlify(ciphertext)) assert encrypted == expected assert encrypted.nonce == nonce assert encrypted.ciphertext == ciphertext
def create_list_entry(symkey, tmppub, length, nonce=None, fetch_token=None, delete_token=None): assert len(tmppub) == 32 fetch_token = fetch_token or os.urandom(32) delete_token = delete_token or os.urandom(32) msg = "list:" + struct.pack(">32s32s32sQ", tmppub, fetch_token, delete_token, length) nonce = nonce or os.urandom(24) sbox = SecretBox(symkey) return sbox.encrypt(msg, nonce), fetch_token, delete_token
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'))
def _encrypt_data(self, key, data): # Without predefined roles, we can't derive predictably unique keys # for each side, so we use the same key for both. We use random # nonces to keep the messages distinct, and we automatically ignore # reflections. # TODO: HKDF(side, nonce, key) ?? include 'side' to prevent # reflections, since we no longer compare messages assert isinstance(key, type(b"")), type(key) assert isinstance(data, type(b"")), type(data) assert len(key) == SecretBox.KEY_SIZE, len(key) box = SecretBox(key) nonce = utils.random(SecretBox.NONCE_SIZE) return box.encrypt(data, nonce)
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': ''}
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'))
def test_out_of_order_nonce(self): # an inbound out-of-order nonce should be rejected t, c, owner = self.make_connection() inbound_records = [] c.recordReceived = inbound_records.append RECORD = b"record" send_box = SecretBox(owner._receiver_record_key()) nonce_buf = unhexlify("%048x" % 1) # first nonce must be 0 encrypted = send_box.encrypt(RECORD, nonce_buf) length = unhexlify("%08x" % len(encrypted)) # always 4 bytes long c.dataReceived(length) c.dataReceived(encrypted[:-2]) self.assertEqual(inbound_records, []) self.assertRaises(transit.BadNonce, c.dataReceived, encrypted[-2:]) self.assertEqual(inbound_records, []) # and the connection should have been dropped self.assertEqual(t._connected, False)
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 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))
def generate(cls, email: str, name: str, passphrase: str) -> typing.Tuple['Key', 'Key']: """ Generate a new public/private key pair. This handles encipherment of the private keys. :param email: The email for the initial User ID. :param name: The name for the initial User ID. :param passphrase: The passphrase to encrypt the private key with. :return: A tuple containing a public and a private key. """ privatekey = public.PrivateKey.generate() publickey = privatekey.public_key assert isinstance(privatekey, public.PrivateKey) assert isinstance(publickey, public.PublicKey) pub, priv = publickey.encode(), privatekey.encode() # Generate the signing key seed. seed = random(32) # Create the signing private key, temporary usage to extract the verify key. s_privatekey = signing.SigningKey(seed) s_publickey = s_privatekey.verify_key s_pub = s_publickey.encode() # Encrypt both private keys. key = derive_passphrase(passphrase.encode()) # Use a random nonce, for now. nonce1 = random(SecretBox.NONCE_SIZE) nonce2 = random(SecretBox.NONCE_SIZE) # Encrypt both. sbox1 = SecretBox(key) sbox2 = SecretBox(key) priv = sbox1.encrypt(priv, nonce1) # Don't encrypt the s_privatekey; encrypt the seed. s_priv = sbox2.encrypt(seed, nonce2) # Create a pair of Keys. public_key = Key(public_key=pub, public_signing_key=s_pub) private_key = Key(private_key=priv.ciphertext, private_signing_seed=s_priv.ciphertext, private_nonce=priv.nonce, private_signing_nonce=s_priv.nonce) del privatekey, priv, s_privatekey, s_priv # Force a GC collect. gc.collect() # Create the requested User ID. u = UserID(0, email, name) # We only add the user ID to the public key. public_key.userid = u # Generate the self-signature. to_hash = u.get_hash() # Sign the message using the private key. signature = private_key.sign_raw(passphrase, to_hash) public_key.self_signature = signature verified = public_key.verify_self_signature() if not verified: raise exc.BadSignature("The self-signature was invalid.") return public_key, private_key
def encrypt(self, signature, field_data): encrypt_key = truncate_or_pad(signature) box = SecretBox(key=encrypt_key) encrypted = box.encrypt(convert_to_bytes(field_data), nonce=self.nonce) return self._signifier + standard_b64encode(encrypted)
def secret_box_encrypt(key_material: bytes, salt: bytes, plaintext: bytes) -> bytes: wrapping_key = derive_wrapping_key_from_key_material(key_material, salt) secret_box = SecretBox(wrapping_key) ciphertext = secret_box.encrypt(plaintext) return ciphertext
def encrypt(secret_box: SecretBox, data: bytes): encrypted = secret_box.encrypt(data) assert len( encrypted) == len(data) + secret_box.NONCE_SIZE + secret_box.MACBYTES return encrypted
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)
def _encrypt(self, bin_data, key): key_data = self._get_key(key) public_box = SecretBox(key_data[1]) return str(key_data[0]) + ':::' + str(public_box.encrypt(bin_data, self._get_nonce()))
def encrypt_bytes(self, data: bytes, encoder=RawEncoder) -> bytes: box = SecretBox(self.key) return box.encrypt(data, None, encoder)
def secret_encrypt_content(cleartext): box = SecretBox( base64.b64decode(settings.PDK_EXTERNAL_CONTENT_SYMETRIC_KEY)) return base64.b64encode(box.encrypt(cleartext))
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 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 send_box = SecretBox(owner._receiver_record_key()) RECORD3 = b"record3" 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" 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]) # receiving two records at the same time: deliver both inbound_records[:] = [] RECORD5 = b"record5" nonce_buf = unhexlify("%048x" % 2) # nonces increment encrypted = send_box.encrypt(RECORD5, nonce_buf) length = unhexlify("%08x" % len(encrypted)) # always 4 bytes long r5 = length + encrypted RECORD6 = b"record6" nonce_buf = unhexlify("%048x" % 3) # nonces increment encrypted = send_box.encrypt(RECORD6, nonce_buf) length = unhexlify("%08x" % len(encrypted)) # always 4 bytes long r6 = length + encrypted c.dataReceived(r5 + r6) self.assertEqual(inbound_records, [RECORD5, RECORD6])
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"])), )
class _StreamingEncryptionObject(object): def __init__(self, mode, user_key, filepath): self.mode = mode self.user_key = user_key self.filepath = filepath self.key = None self.EOF = False self.index = 0 if self.mode == 'ENCRYPT': self.fd = open(filepath, 'wb') self.key = nacl_random(32) self.partial_nonce = nacl_random(16) key = _GCE.asymmetric_encrypt(self.user_key, self.key) self.fd.write(key) self.fd.write(self.partial_nonce) else: self.fd = open(filepath, 'rb') x = self.fd.read(80) self.key = _GCE.asymmetric_decrypt(self.user_key, x) self.partial_nonce = self.fd.read(16) self.box = SecretBox(self.key) def fullNonce(self, i): return self.partial_nonce + struct.pack('<Q', i) def lastFullNonce(self): return self.partial_nonce + struct.pack('>Q', 1) def getNextNonce(self, last): if last: chunkNonce = self.lastFullNonce() else: chunkNonce = self.fullNonce(self.index) self.index += 1 return chunkNonce def encrypt_chunk(self, chunk, last=0): chunkNonce = self.getNextNonce(last) self.fd.write(struct.pack('>B', last)) self.fd.write(struct.pack('>I', len(chunk))) self.fd.write(self.box.encrypt(chunk, chunkNonce)[24:]) def decrypt_chunk(self): last = struct.unpack('>B', self.fd.read(1))[0] if last: self.EOF = True chunkNonce = self.getNextNonce(last) chunkLen = struct.unpack('>I', self.fd.read(4))[0] chunk = self.fd.read(chunkLen + 16) return last, self.box.decrypt(chunk, chunkNonce) def read(self, a): if not self.EOF: return self.decrypt_chunk()[1] def close(self): if self.fd is not None: self.fd.close() self.fd = None def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): self.close() def __del__(self): self.close()
def _encrypt_data(self, key, data): assert len(key) == SecretBox.KEY_SIZE box = SecretBox(key) nonce = utils.random(SecretBox.NONCE_SIZE) return box.encrypt(data, nonce)
def persist(self): serialized_data = serialize(self.data) box = SecretBox(bytes.fromhex(self.key)) encrypted_data = box.encrypt(serialized_data) return self.persist_as_blocks(encrypted_data)
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): debug("handle %r" % (self.transport, )) 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 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): 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 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._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 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 # 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)
def encrypt_message(message, secret): message_bytes = message.encode('utf-8', 'ignore') secret_box = SecretBox(secret) nonce = random(SecretBox.NONCE_SIZE) return secret_box.encrypt(nonce=nonce, plaintext=message_bytes)
def block_encrypt(block, nextid): key = sha512(block.secret, encoder=RawEncoder) nonce = calc_nonce(key, block.N, block.offset) box = SecretBox(key[:SecretBox.KEY_SIZE]) data_padded = pack("<I", nextid) + pad_inner(block.data) return nonce, box.encrypt(data_padded, nonce).ciphertext
class NACL: KEY_SIZE = 32 def __init__(self, private_key=None, symmetric_key=None): """Constructor for nacl object Args: private_key (bytes, optional): The private key used to sign and encrypt the data. Generated randomly if not given. symmetric_key (bytes, optional): The key used for symmetric encryption. Generated randomly if not given. """ self.private_key = PrivateKey.generate( ) if private_key is None else PrivateKey(private_key) private_key = self.get_private_key() self.public_key = self.private_key.public_key self.symmetric_key = nacl.utils.random( NACL.KEY_SIZE) if symmetric_key is None else symmetric_key self.signing_key = SigningKey(private_key) self.symmetric_box = SecretBox(self.symmetric_key) def encrypt(self, message, reciever_public_key): """Encrypt the message to send to a receiver. (public key encryption) Args: message (bytes): The message to be encrypted. reciever_public_key (bytes): The receiver's public key. Returns: bytes: The encrypted message """ return Box(self.private_key, PublicKey(reciever_public_key)).encrypt(message) def decrypt(self, message, sender_public_key): """Decrypt a received message. (public key encryption) Args: message (bytes): The encrypted message. sender_public_key (bytes): The public key of the sender. Returns: bytes: The decrypted message """ return Box(self.private_key, PublicKey(sender_public_key)).decrypt(message) def encrypt_symmetric(self, message): """Encrypt the message to send to a receiver. (secret key encryption) Args: message (bytes): The message to be encrypted. Returns: bytes: The encrypted message """ return self.symmetric_box.encrypt(message) def decrypt_symmetric(self, message, symmetric_key): """Decrypt the receiver message. (secret key encryption) Args: message (bytes): The message to be decrypted. Returns: bytes: The decrypted message """ return SecretBox(symmetric_key).decrypt(message) def sign(self, message): """Sign the message and return the messsage and the signature. Args: message (bytes): The message to be signed Returns: bytes, bytes: The message and the signature. """ signed = self.signing_key.sign(message) return message, signed.signature def verify(self, message, signature, verification_key): """Verify that the signature using the verification key Args: message (bytes): The received message. signature (bytes): The recieved signature. verification_key (bytes): The verification key. Returns: bool: True if the verification succeeds. """ try: VerifyKey(verification_key).verify(message, signature) return True except BadSignatureError: return False def get_signing_seed(self): """Returns the signing seed (same as the private key). Returns: bytes: The 32-bit signing key. """ return bytes(self.signing_key._seed) def get_verification_key(self): """Returns the verification key. Returns: bytes: The verification key. """ return bytes(self.signing_key.verify_key) def get_public_key(self): """Getter for the public key. Returns: bytes: The public key. """ return bytes(self.public_key) def get_private_key(self): """Getter for the private key. Returns: bytes: The private key. """ return bytes(self.private_key) def get_symmetric_key(self): """Getter for the symmetric key. Returns: bytes: The symmetric key. """ return bytes(self.symmetric_key)
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 handle(self, *args, **options): # pylint: disable=too-many-locals, too-many-statements, too-many-branches here_tz = pytz.timezone(settings.TIME_ZONE) parameters = {} if options['start_date'] is not None: components = options['start_date'].split('-') start_date = datetime.datetime(int(components[0]), int(components[1]), int(components[2]), 0, 0, 0, 0, here_tz) parameters['start_date'] = start_date if options['end_date'] is not None: components = options['end_date'].split('-') end_date = datetime.datetime(int(components[0]), int( components[1]), int(components[2]), 0, 0, 0, 0, here_tz) + datetime.timedelta(days=1) parameters['end_date'] = end_date parameters['clear_archived'] = options['clear_archived'] key = None try: key = base64.b64decode(settings.PDK_BACKUP_KEY) except AttributeError: print 'Please define PDK_BACKUP_KEY in the settings.' sys.exit(1) destinations = None try: destinations = settings.PDK_BACKUP_DESTINATIONS except AttributeError: print 'Please define PDK_BACKUP_DESTINATIONS in the settings.' sys.exit(1) for app in settings.INSTALLED_APPS: try: pdk_api = importlib.import_module(app + '.pdk_api') to_transmit, to_clear = pdk_api.incremental_backup(parameters) for destination in destinations: destination_url = urlparse.urlparse(destination) if destination_url.scheme == 'file': dest_path = destination_url.path final_folder = self.folder_for_options(options) if final_folder is not None: dest_path = os.path.join(dest_path, final_folder) if os.path.exists(dest_path) is False: print 'Creating folder for archive storage: ' + dest_path os.makedirs(dest_path) for path in to_transmit: box = SecretBox(key) with open(path, 'rb') as backup_file: encrypted_str = box.encrypt(backup_file.read()) filename = os.path.basename( path) + '.encrypted' encrypted_path = os.path.join( dest_path, filename) print 'Writing to filesystem: ' + encrypted_path with open(encrypted_path, 'wb') as encrypted_file: encrypted_file.write(encrypted_str) os.remove(path) elif destination_url.scheme == 'dropbox': access_token = destination_url.netloc client = dropbox.Dropbox(access_token) for path in to_transmit: box = SecretBox(key) with open(path, 'rb') as backup_file: encrypted_io = StringIO.StringIO() encrypted_io.write(backup_file.read()) encrypted_io.seek(0) filename = os.path.basename( path) + '.encrypted' final_folder = self.folder_for_options(options) dropbox_path = os.path.join( destination_url.path, final_folder + '/' + filename) print 'Uploading to Dropbox: ' + dropbox_path client.files_upload(encrypted_io.read(), dropbox_path) os.remove(path) else: print 'Unknown desitination: ' + destination pdk_api.clear_points(to_clear) except ImportError: pass except AttributeError: pass