def test_deterministic(self): password = os.urandom(32) gen = crypto.generate_deterministic(password) self.assertTrue(stringvalidators.validate_pub_key(gen[0])) try: crypto.generate_deterministic('weakpassword') except onionrexceptions.PasswordStrengthError: pass else: self.assertFalse(True) try: crypto.generate_deterministic(None) except TypeError: pass else: self.assertFalse(True) gen = crypto.generate_deterministic('weakpassword', bypassCheck=True) password = base64.b64encode(os.urandom(32)) gen1 = crypto.generate_deterministic(password) gen2 = crypto.generate_deterministic(password) self.assertFalse(gen == gen1) self.assertTrue(gen1 == gen2) self.assertTrue(stringvalidators.validate_pub_key(gen1[0]))
def add_peer(peerID, name=''): """Add a public key to the key database (misleading function name).""" if peerID in listkeys.list_peers() or peerID == onionrcrypto.pub_key: raise ValueError("specified id is already known") # This function simply adds a peer to the DB if not stringvalidators.validate_pub_key(peerID): return False conn = sqlite3.connect(dbfiles.user_id_info_db, timeout=onionrvalues.DATABASE_LOCK_TIMEOUT) hashID = "" c = conn.cursor() t = (peerID, name, 'unknown', hashID, 0) for i in c.execute("SELECT * FROM peers WHERE id = ?;", (peerID, )): try: if i[0] == peerID: conn.close() return False except ValueError: pass except IndexError: pass c.execute( 'INSERT INTO peers (id, name, dateSeen, hashID, trust) VALUES(?, ?, ?, ?, ?);', t) conn.commit() conn.close() return True
def addForwardKey(self, newKey, expire=DEFAULT_KEY_EXPIRE): newKey = bytesconverter.bytes_to_str( unpaddedbase32.repad(bytesconverter.str_to_bytes(newKey))) if not stringvalidators.validate_pub_key(newKey): # Do not add if something went wrong with the key raise onionrexceptions.InvalidPubkey(newKey) conn = sqlite3.connect(dbfiles.user_id_info_db, timeout=DATABASE_LOCK_TIMEOUT) c = conn.cursor() # Get the time we're inserting the key at timeInsert = epoch.get_epoch() # Look at our current keys for duplicate key data or time for entry in self._getForwardKeys(): if entry[0] == newKey: return False if entry[1] == timeInsert: timeInsert += 1 # Sleep if our time is the same to prevent dupe time records time.sleep(1) # Add a forward secrecy key for the peer # Prepare the insert command = (self.publicKey, newKey, timeInsert, timeInsert + expire) c.execute("INSERT INTO forwardKeys VALUES(?, ?, ?, ?);", command) conn.commit() conn.close() return True
def site_file(name: str, file: str)->Response: """Accept a site 'name', if pubkey then show multi-page site, if hash show single page site""" resp: str = 'Not Found' mime_type = mimetypes.MimeTypes().guess_type(file)[0] # If necessary convert the name to base32 from mnemonic if mnemonickeys.DELIMITER in name: name = mnemonickeys.get_base32(name) # Now make sure the key is regardless a valid base32 format ed25519 key (readding padding if necessary) if stringvalidators.validate_pub_key(name): name = unpaddedbase32.repad(name) resp = sitefiles.get_file(name, file) elif stringvalidators.validate_hash(name): try: resp = onionrblockapi.Block(name).bcontent except onionrexceptions.NoDataAvailable: abort(404) except TypeError: pass try: resp = base64.b64decode(resp) except binascii.Error: pass if resp == 'Not Found' or not resp: abort(404) return Response(resp, mimetype=mime_type)
def get_block_data(self, bHash, decrypt=False, raw=False, headerOnly=False): if not stringvalidators.validate_hash(bHash): raise onionrexceptions.InvalidHexHash( "block hash not valid hash format") bl = onionrblockapi.Block(bHash) if decrypt: bl.decrypt() if bl.isEncrypted and not bl.decrypted: raise ValueError if not raw: if not headerOnly: retData = {'meta':bl.bheader, 'metadata': bl.bmetadata, 'content': bl.bcontent} for x in list(retData.keys()): try: retData[x] = retData[x].decode() except AttributeError: pass else: validSig = False signer = bytesconverter.bytes_to_str(bl.signer) if bl.isSigned() and stringvalidators.validate_pub_key(signer) and bl.isSigner(signer): validSig = True bl.bheader['validSig'] = validSig bl.bheader['meta'] = '' retData = {'meta': bl.bheader, 'metadata': bl.bmetadata} return json.dumps(retData) else: return bl.raw
def test_pubkey_validator(self): # Test ed25519 public key validity valids = [ 'JZ5VE72GUS3C7BOHDRIYZX4B5U5EJMCMLKHLYCVBQQF3UKHYIRRQ====', 'JZ5VE72GUS3C7BOHDRIYZX4B5U5EJMCMLKHLYCVBQQF3UKHYIRRQ' ] invalid = [ None, '', ' ', 'dfsg', '\n', 'JZ5VE72GUS3C7BOHDRIYZX4B5U5EJMCMLKHLYCVBQQF3UKHYIR$Q====' ] for valid in valids: print('testing', valid) self.assertTrue(stringvalidators.validate_pub_key(valid)) for x in invalid: #print('testing', x) self.assertFalse(stringvalidators.validate_pub_key(x))
def _processForwardKey(api, myBlock): ''' Get the forward secrecy key specified by the user for us to use ''' peer = onionrusers.OnionrUser(myBlock.signer) key = myBlock.getMetadata('newFSKey') # We don't need to validate here probably, but it helps if stringvalidators.validate_pub_key(key): peer.addForwardKey(key) else: raise onionrexceptions.InvalidPubkey("%s is not a valid pubkey key" % (key, ))
def forwardEncrypt(self, data): deleteTheirExpiredKeys(self.publicKey) deleteExpiredKeys() retData = '' forwardKey = self._getLatestForwardKey() if stringvalidators.validate_pub_key(forwardKey[0]): retData = onionrcrypto.encryption.pub_key_encrypt(data, forwardKey[0], encodedData=True) else: raise onionrexceptions.InvalidPubkey( "No valid forward secrecy key available for this user") return (retData, forwardKey[0], forwardKey[1])
def encrypt(self): # peer, data plaintext = "" encrypted = "" # detect if signing is enabled sign = True try: if sys.argv[3].lower() == 'false': sign = False except IndexError: pass try: if not stringvalidators.validate_pub_key(sys.argv[2]): raise onionrexceptions.InvalidPubkey except (ValueError, IndexError) as e: logger.error("Peer public key not specified", terminal=True) except onionrexceptions.InvalidPubkey: logger.error("Invalid public key", terminal=True) else: pubkey = sys.argv[2] # Encrypt if public key is valid logger.info("Please enter your message (ctrl-d or -q to stop):", terminal=True) try: for line in sys.stdin: if line == '-q\n': break plaintext += line except KeyboardInterrupt: sys.exit(1) # Build Message to encrypt data = {} myPub = keypair[0] if sign: data['sig'] = signing.ed_sign(plaintext, key=keypair[1], encodeResult=True) data['sig'] = bytesconverter.bytes_to_str(data['sig']) data['signer'] = myPub data['data'] = plaintext data = json.dumps(data) plaintext = data encrypted = encryption.pub_key_encrypt(plaintext, pubkey, encodedData=True) encrypted = bytesconverter.bytes_to_str(encrypted) logger.info( 'Encrypted Message: \n\nONIONR ENCRYPTED DATA %s END ENCRYPTED DATA' % (encrypted, ), terminal=True)
def remove_user(pubkey: str) -> bool: ''' Remove a user from the user database ''' pubkey = mnemonickeys.get_base32(pubkey) if stringvalidators.validate_pub_key(pubkey): conn = sqlite3.connect(dbfiles.user_id_info_db, timeout=onionrvalues.DATABASE_LOCK_TIMEOUT) c = conn.cursor() t = (pubkey, ) c.execute('Delete from peers where id=?;', t) conn.commit() conn.close() return True else: return False
def find_site(user_id: str) -> Union[BlockHash, None]: """Returns block hash str for latest block for a site by a given user id""" # If mnemonic delim in key, convert to base32 version if mnemonickeys.DELIMITER in user_id: user_id = mnemonickeys.get_base32(user_id) if not stringvalidators.validate_pub_key(user_id): raise onionrexceptions.InvalidPubkey found_site = None sites = blockmetadb.get_blocks_by_type('zsite') # Find site by searching all site blocks. eww O(N) ☹️, TODO: event based for site in sites: site = Block(site) if site.isSigner(user_id) and site.verifySig(): found_site = site.hash return found_site
def change_ID(): key_manager = keymanager.KeyManager() try: key = sys.argv[2] key = unpaddedbase32.repad(key.encode()).decode() except IndexError: logger.warn('Specify pubkey to use', terminal=True) else: if stringvalidators.validate_pub_key(key): key_list = key_manager.getPubkeyList() if key in key_list or key.replace('=', '') in key_list: config.set('general.public_key', key) config.save() logger.info('Set active key to: %s' % (key, ), terminal=True) logger.info('Restart Onionr if it is running.', terminal=True) else: logger.warn('That key does not exist', terminal=True) else: logger.warn('Invalid key %s' % (key, ), terminal=True)
def service_creator(daemon): assert isinstance(daemon, communicator.OnionrCommunicatorDaemon) # Find socket connection blocks # TODO cache blocks and only look at recently received ones con_blocks = blockmetadb.get_blocks_by_type('con') for b in con_blocks: if not b in daemon.active_services: bl = onionrblockapi.Block(b, decrypt=True) bs = bytesconverter.bytes_to_str(bl.bcontent) + '.onion' if server_exists(bl.signer): continue if stringvalidators.validate_pub_key(bl.signer) and stringvalidators.validate_transport(bs): signer = bytesconverter.bytes_to_str(bl.signer) daemon.active_services.append(b) daemon.active_services.append(signer) if not daemon.services.create_server(signer, bs, daemon): daemon.active_services.remove(b) daemon.active_services.remove(signer) daemon.decrementThreadCount('service_creator')
def isSigner(self, signer, encodedData=True): """ Checks if the block was signed by the signer inputted Inputs: - signer (str): the public key of the signer to check against - encodedData (bool): whether or not the `signer` argument is base64 encoded Outputs: - (bool): whether or not the signer of the block is the signer inputted """ signer = unpaddedbase32.repad(bytesconverter.str_to_bytes(signer)) try: if (not self.isSigned()) or ( not stringvalidators.validate_pub_key(signer)): return False return bool( signing.ed_verify(self.getSignedData(), signer, self.getSignature(), encodedData=encodedData)) except: return False
def pub_key_decrypt(data, pubkey='', privkey='', encodedData=False): '''pubkey decrypt (Curve25519, taken from Ed25519 pubkey)''' if pubkey != '': pubkey = unpaddedbase32.repad(bytesconverter.str_to_bytes(pubkey)) decrypted = False if encodedData: encoding = nacl.encoding.Base64Encoder else: encoding = nacl.encoding.RawEncoder if privkey == '': privkey = our_priv_key ownKey = nacl.signing.SigningKey( seed=privkey, encoder=nacl.encoding.Base32Encoder()).to_curve25519_private_key() if stringvalidators.validate_pub_key(privkey): privkey = nacl.signing.SigningKey( seed=privkey, encoder=nacl.encoding.Base32Encoder()).to_curve25519_private_key() anonBox = nacl.public.SealedBox(privkey) else: anonBox = nacl.public.SealedBox(ownKey) decrypted = anonBox.decrypt(data, encoder=encoding) return decrypted
def test_pub_from_priv(self): priv = nacl.signing.SigningKey.generate().encode(encoder=nacl.encoding.Base32Encoder) pub = crypto.cryptoutils.getpubfrompriv.get_pub_key_from_priv(priv) self.assertTrue(stringvalidators.validate_pub_key(pub))
def __init__(self, peer, address, comm_inst=None): if not stringvalidators.validate_pub_key(peer): raise ValueError('Peer must be valid base32 ed25519 public key') socks = config.get( 'tor.socksport') # Load config for Tor socks port for proxy service_app = Flask(__name__) # Setup Flask app for server. service_port = get_open_port() service_ip = apiutils.setbindip.set_bind_IP() http_server = WSGIServer(('127.0.0.1', service_port), service_app, log=None) comm_inst.service_greenlets.append(http_server) key_store = simplekv.DeadSimpleKV(filepaths.cached_storage) # TODO define basic endpoints useful for direct connections like stats httpapi.load_plugin_blueprints(service_app, blueprint='direct_blueprint') @service_app.route('/ping') def get_ping(): return "pong!" @service_app.route('/close') def shutdown_server(): comm_inst.service_greenlets.remove(http_server) http_server.stop() return Response('goodbye') @service_app.after_request def afterReq(resp): # Security headers resp = httpheaders.set_default_onionr_http_headers(resp) return resp with Controller.from_port( port=config.get('tor.controlPort')) as controller: # Connect to the Tor process for Onionr controller.authenticate(config.get('tor.controlpassword')) # Create the v3 onion service for the peer to connect to response = controller.create_ephemeral_hidden_service( {80: service_port}, await_publication=True, key_type='NEW', key_content='ED25519-V3') try: for x in range(3): attempt = basicrequests.do_post_request( 'http://' + address + '/bs/' + response.service_id, port=socks) if attempt == 'success': break else: raise ConnectionError except ConnectionError: # Re-raise raise ConnectionError( 'Could not reach %s bootstrap address %s' % (peer, address)) else: # If no connection error, create the service and save it to local global key store peer = bytesconverter.bytes_to_str(peer) key_store.put('dc-' + peer, response.service_id) key_store.flush() logger.info('hosting on %s with %s' % (response.service_id, peer)) http_server.serve_forever() http_server.stop() key_store.delete('dc-' + peer)
def insert_block(data: Union[str, bytes], header: str = 'txt', sign: bool = False, encryptType: str = '', symKey: str = '', asymPeer: str = '', meta: dict = {}, expire: Union[int, None] = None, disableForward: bool = False, signing_key: UserIDSecretKey = '') -> Union[str, bool]: """ Create and insert a block into the network. encryptType must be specified to encrypt a block if expire is less than date, assumes seconds into future. if not assume exact epoch """ our_private_key = crypto.priv_key our_pub_key = crypto.pub_key storage_counter = storagecounter.StorageCounter() allocationReachedMessage = 'Cannot insert block, disk allocation reached.' if storage_counter.is_full(): logger.error(allocationReachedMessage) raise onionrexceptions.DiskAllocationReached if signing_key != '': # if it was specified to use an alternative private key our_private_key = signing_key our_pub_key = bytesconverter.bytes_to_str( crypto.cryptoutils.get_pub_key_from_priv(our_private_key)) retData = False if type(data) is None: raise ValueError('Data cannot be none') createTime = epoch.get_epoch() dataNonce = bytesconverter.bytes_to_str(crypto.hashers.sha3_hash(data)) try: with open(filepaths.data_nonce_file, 'r') as nonces: if dataNonce in nonces: return retData except FileNotFoundError: pass # record nonce with open(filepaths.data_nonce_file, 'a') as nonceFile: nonceFile.write(dataNonce + '\n') plaintext = data plaintextMeta = {} plaintextPeer = asymPeer retData = '' signature = '' signer = '' metadata = {} # metadata is full block metadata # meta is internal, user specified metadata # only use header if not set in provided meta meta['type'] = str(header) if encryptType in ('asym', 'sym'): metadata['encryptType'] = encryptType else: if not config.get('general.store_plaintext_blocks', True): raise onionrexceptions.InvalidMetadata( "Plaintext blocks are disabled, " + "yet a plaintext block was being inserted") if encryptType not in ('', None): raise onionrexceptions.InvalidMetadata( 'encryptType must be asym or sym, or blank') try: data = data.encode() except AttributeError: pass if encryptType == 'asym': # Duplicate the time in encrypted messages to help prevent replays meta['rply'] = createTime if sign and asymPeer != our_pub_key: try: forwardEncrypted = onionrusers.OnionrUser( asymPeer).forwardEncrypt(data) data = forwardEncrypted[0] meta['forwardEnc'] = True # Expire time of key. no sense keeping block after that expire = forwardEncrypted[2] except onionrexceptions.InvalidPubkey: pass if not disableForward: fsKey = onionrusers.OnionrUser(asymPeer).generateForwardKey() meta['newFSKey'] = fsKey jsonMeta = json.dumps(meta) plaintextMeta = jsonMeta if sign: signature = crypto.signing.ed_sign(jsonMeta.encode() + data, key=our_private_key, encodeResult=True) signer = our_pub_key if len(jsonMeta) > 1000: raise onionrexceptions.InvalidMetadata( 'meta in json encoded form must not exceed 1000 bytes') # encrypt block metadata/sig/content if encryptType == 'sym': raise NotImplementedError("not yet implemented") elif encryptType == 'asym': if stringvalidators.validate_pub_key(asymPeer): # Encrypt block data with forward secrecy key first, but not meta jsonMeta = json.dumps(meta) jsonMeta = crypto.encryption.pub_key_encrypt( jsonMeta, asymPeer, encodedData=True).decode() data = crypto.encryption.pub_key_encrypt(data, asymPeer, encodedData=False) signature = crypto.encryption.pub_key_encrypt( signature, asymPeer, encodedData=True).decode() signer = crypto.encryption.pub_key_encrypt( signer, asymPeer, encodedData=True).decode() try: onionrusers.OnionrUser(asymPeer, saveUser=True) except ValueError: # if peer is already known pass else: raise onionrexceptions.InvalidPubkey( asymPeer + ' is not a valid base32 encoded ed25519 key') # compile metadata metadata['meta'] = jsonMeta if len(signature) > 0: # I don't like not pattern metadata['sig'] = signature metadata['signer'] = signer metadata['time'] = createTime # ensure expire is integer and of sane length if type(expire) is not type(None): # noqa if not len(str(int(expire))) < 20: raise ValueError( 'expire must be valid int less than 20 digits in length') # if expire is less than date, assume seconds into future if expire < epoch.get_epoch(): expire = epoch.get_epoch() + expire metadata['expire'] = expire # send block data (and metadata) to POW module to get tokenized block data payload = subprocesspow.SubprocessPOW(data, metadata).start() if payload != False: # noqa try: retData = onionrstorage.set_data(payload) except onionrexceptions.DiskAllocationReached: logger.error(allocationReachedMessage) retData = False else: if disableForward: logger.warn( f'{retData} asym encrypted block created w/o ephemerality') """ Tell the api server through localCommand to wait for the daemon to upload this block to make statistical analysis more difficult """ spawn(localcommand.local_command, '/daemon-event/upload_event', post=True, is_json=True, post_data={ 'block': retData }).get(timeout=5) coredb.blockmetadb.add.add_to_block_DB(retData, selfInsert=True, dataSaved=True) if expire is None: coredb.blockmetadb.update_block_info( retData, 'expire', createTime + min( onionrvalues.DEFAULT_EXPIRE, config.get('general.max_block_age', onionrvalues.DEFAULT_EXPIRE))) else: coredb.blockmetadb.update_block_info(retData, 'expire', expire) blockmetadata.process_block_metadata(retData) if retData != False: # noqa if plaintextPeer == onionrvalues.DENIABLE_PEER_ADDRESS: events.event('insertdeniable', { 'content': plaintext, 'meta': plaintextMeta, 'hash': retData, 'peer': bytesconverter.bytes_to_str(asymPeer) }, threaded=True) else: events.event('insertblock', { 'content': plaintext, 'meta': plaintextMeta, 'hash': retData, 'peer': bytesconverter.bytes_to_str(asymPeer) }, threaded=True) spawn(localcommand.local_command, '/daemon-event/remove_from_insert_queue_wrapper', post=True, post_data={ 'block_hash': retData }, is_json=True).get(timeout=5) return retData
def bootstrap_client_service(peer, comm_inst=None, bootstrap_timeout=300): ''' Bootstrap client services ''' if not stringvalidators.validate_pub_key(peer): raise ValueError('Peer must be valid base32 ed25519 public key') connection_pool = None # here we use a lambda for the timeout thread to set to true timed_out = lambda: None timed_out.timed_out = False bootstrap_port = get_open_port() bootstrap_app = Flask(__name__) bootstrap_app.config['MAX_CONTENT_LENGTH'] = 1 * 1024 http_server = WSGIServer(('127.0.0.1', bootstrap_port), bootstrap_app, log=None) try: if comm_inst is None: raise ValueError except (AttributeError, ValueError) as e: pass else: comm_inst.service_greenlets.append(http_server) connection_pool = comm_inst.shared_state.get(pool.ServicePool) bootstrap_address = '' shutdown = False bs_id = str(uuid.uuid4()) key_store = simplekv.DeadSimpleKV(filepaths.cached_storage) @bootstrap_app.route('/ping') def get_ping(): return "pong!" @bootstrap_app.after_request def afterReq(resp): # Security headers resp = httpheaders.set_default_onionr_http_headers(resp) return resp @bootstrap_app.route('/bs/<address>', methods=['POST']) def get_bootstrap(address): if stringvalidators.validate_transport(address + '.onion'): # Set the bootstrap address then close the server bootstrap_address = address + '.onion' key_store.put(bs_id, bootstrap_address) http_server.stop() return Response("success") else: return Response("") with Controller.from_port( port=config.get('tor.controlPort')) as controller: if not connection_pool is None: connection_pool.bootstrap_pending.append(peer) # Connect to the Tor process for Onionr controller.authenticate(config.get('tor.controlpassword')) # Create the v3 onion service response = controller.create_ephemeral_hidden_service( {80: bootstrap_port}, key_type='NEW', key_content='ED25519-V3', await_publication=True) onionrblocks.insert(response.service_id, header='con', sign=True, encryptType='asym', asymPeer=peer, disableForward=True, expire=(epoch.get_epoch() + bootstrap_timeout)) threading.Thread(target=__bootstrap_timeout, args=[http_server, bootstrap_timeout, timed_out], daemon=True).start() # Run the bootstrap server try: http_server.serve_forever() except TypeError: pass # This line reached when server is shutdown by being bootstrapped # Add the address to the client pool if not comm_inst is None: connection_pool.bootstrap_pending.remove(peer) if timed_out.timed_out: logger.warn('Could not connect to %s due to timeout' % (peer, )) return None comm_inst.direct_connection_clients[peer] = response.service_id # Now that the bootstrap server has received a server, return the address return key_store.get(bs_id)
def test_sane_default(self): self.assertGreaterEqual(len(pub_key), 52) self.assertLessEqual(len(pub_key), 56) self.assertEqual(pub_key, keymanager.KeyManager().getPubkeyList()[0]) stringvalidators.validate_pub_key(pub_key)
def valid_default_id(self): self.assertTrue(stringvalidators.validate_pub_key(crypto.pub_key))
def test_change(self): new_key = keymanager.KeyManager().addKey()[0] self.assertNotEqual(new_key, pub_key) self.assertEqual(new_key, keymanager.KeyManager().getPubkeyList()[1]) stringvalidators.validate_pub_key(new_key)