def test_plaintext_insert(self): setup_test() message = 'hello world' bl = onionrblocks.insert(message) self.assertTrue(bl.startswith('0')) self.assertIn(bytesconverter.str_to_bytes(message), onionrstorage.getData(bl))
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 generate_deterministic(passphrase, bypassCheck=False): """Generate a Ed25519 public key pair from a phase. not intended for human-generated key """ passStrength = onionrvalues.PASSWORD_LENGTH # Convert to bytes if not already passphrase = bytesconverter.str_to_bytes(passphrase) # Validate passphrase length if not bypassCheck: if len(passphrase) < passStrength: raise onionrexceptions.PasswordStrengthError( "Passphase must be at least %s characters" % (passStrength, )) # KDF values kdf = nacl.pwhash.argon2id.kdf # Does not need to be secret, but must be 16 bytes salt = b"U81Q7llrQcdTP0Ux" ops = nacl.pwhash.argon2id.OPSLIMIT_SENSITIVE mem = nacl.pwhash.argon2id.MEMLIMIT_SENSITIVE # Generate seed for ed25519 key key = kdf(32, passphrase, salt, opslimit=ops, memlimit=mem) key = nacl.signing.SigningKey(key) return (key.verify_key.encode(nacl.encoding.Base32Encoder).decode(), key.encode(nacl.encoding.Base32Encoder).decode())
def getDifficultyForNewBlock(data): ''' Get difficulty for block. Accepts size in integer, Block instance, or str/bytes full block contents ''' if isinstance(data, onionrblockapi.Block): dataSizeInBytes = len(bytesconverter.str_to_bytes(data.getRaw())) else: dataSizeInBytes = len(bytesconverter.str_to_bytes(data)) minDifficulty = config.get('general.minimum_send_pow', 4) totalDifficulty = max(minDifficulty, math.floor( dataSizeInBytes / 1000000.0)) + getDifficultyModifier() return totalDifficulty return retData
def pub_key_encrypt(data, pubkey, encodedData=False): '''Encrypt to a public key (Curve25519, taken from base32 Ed25519 pubkey)''' pubkey = unpaddedbase32.repad(bytesconverter.str_to_bytes(pubkey)) retVal = '' box = None data = bytesconverter.str_to_bytes(data) pubkey = nacl.signing.VerifyKey( pubkey, encoder=nacl.encoding.Base32Encoder()).to_curve25519_public_key() if encodedData: encoding = nacl.encoding.Base64Encoder else: encoding = nacl.encoding.RawEncoder box = nacl.public.SealedBox(pubkey) retVal = box.encrypt(data, encoder=encoding) return retVal
def handle_announce(request): ''' accept announcement posts, validating POW clientAPI should be an instance of the clientAPI server running, request is a instance of a flask request ''' resp = 'failure' powHash = '' randomData = '' newNode = '' try: newNode = request.form['node'].encode() except KeyError: logger.warn('No node specified for upload') pass else: try: randomData = request.form['random'] randomData = base64.b64decode(randomData) except KeyError: logger.warn('No random data specified for upload') else: nodes = newNode + bytesconverter.str_to_bytes( gettransports.get()[0]) nodes = crypto.hashers.blake2b_hash(nodes) powHash = crypto.hashers.blake2b_hash(randomData + nodes) try: powHash = powHash.decode() except AttributeError: pass if powHash.startswith('0' * onionrvalues.ANNOUNCE_POW): newNode = bytesconverter.bytes_to_str(newNode) announce_queue = deadsimplekv.DeadSimpleKV( filepaths.announce_cache) announce_queue_list = announce_queue.get('new_peers') if announce_queue_list is None: announce_queue_list = [] if stringvalidators.validate_transport( newNode) and not newNode in announce_queue_list: #clientAPI.onionrInst.communicatorInst.newPeers.append(newNode) g.shared_state.get( OnionrCommunicatorDaemon).newPeers.append(newNode) announce_queue.put('new_peers', announce_queue_list.append(newNode)) announce_queue.flush() resp = 'Success' else: logger.warn(newNode.decode() + ' failed to meet POW: ' + powHash) resp = Response(resp) if resp == 'failure': return resp, 406 return resp
def ed_sign(data, key, encodeResult=False): '''Ed25519 sign data''' key = unpaddedbase32.repad(bytesconverter.str_to_bytes(key)) try: data = data.encode() except AttributeError: pass key = nacl.signing.SigningKey(seed=key, encoder=nacl.encoding.Base32Encoder) retData = '' if encodeResult: retData = key.sign( data, encoder=nacl.encoding.Base64Encoder).signature.decode( ) # .encode() is not the same as nacl.encoding else: retData = key.sign(data).signature return retData
def get_block_data(publicAPI, data): """data is the block hash in hex""" resp = '' if stringvalidators.validate_hash(data): if not config.get('general.hide_created_blocks', True) or data not in publicAPI.hideBlocks: if data in publicAPI._too_many.get(BlockList).get(): block = apiutils.GetBlockData().get_block_data(data, raw=True, decrypt=False) try: block = block.encode('utf-8') # Encode in case data is binary except AttributeError: if len(block) == 0: abort(404) block = bytesconverter.str_to_bytes(block) resp = block if len(resp) == 0: abort(404) resp = "" # Has to be octet stream, otherwise binary data fails hash check return Response(resp, mimetype='application/octet-stream')
def validate_pub_key(key): ''' Validate if a string is a valid base32 encoded Ed25519 key ''' if type(key) is type(None): return False # Accept keys that have no = padding key = unpaddedbase32.repad(bytesconverter.str_to_bytes(key)) retVal = False try: nacl.signing.SigningKey(seed=key, encoder=nacl.encoding.Base32Encoder) except nacl.exceptions.ValueError: pass except base64.binascii.Error as err: pass else: retVal = True return retVal
def __init__(self, publicKey, saveUser=False, recordExpireSeconds=5): try: if mnemonickeys.DELIMITER in publicKey: publicKey = mnemonickeys.get_base32(publicKey) #publicKey = unpaddedbase32.b32encode(bytesconverter.str_to_bytes(publicKey)) except ValueError: pass publicKey = bytesconverter.bytes_to_str( unpaddedbase32.repad(bytesconverter.str_to_bytes(publicKey))) super(ContactManager, self).__init__(publicKey, saveUser=saveUser) home = identifyhome.identify_home() self.dataDir = home + '/contacts/' self.dataFile = '%s/contacts/%s.json' % (home, publicKey) self.lastRead = 0 self.recordExpire = recordExpireSeconds self.data = self._loadData() self.deleted = False if not os.path.exists(self.dataDir): os.mkdir(self.dataDir)
def __init__(self, publicKey, saveUser=False): """ OnionrUser is an abstraction for "users" of the network. Takes a base32 encoded ed25519 public key, and a bool saveUser saveUser determines if we should add a user to our peer database or not. """ publicKey = unpaddedbase32.repad( bytesconverter.str_to_bytes(publicKey)).decode() self.trust = 0 self.publicKey = publicKey if saveUser and not publicKey == getourkeypair.get_keypair(): try: keydb.addkeys.add_peer(publicKey) except (AssertionError, ValueError) as _: pass self.trust = keydb.userinfo.get_user_info(self.publicKey, 'trust') return
def set_data(data) -> str: ''' Set the data assciated with a hash ''' storage_counter = storagecounter.StorageCounter() data = data dataSize = sys.getsizeof(data) nonce_hash = crypto.hashers.sha3_hash( bytesconverter.str_to_bytes( blockmetadata.fromdata.get_block_metadata_from_data(data)[2])) nonce_hash = bytesconverter.bytes_to_str(nonce_hash) if not type(data) is bytes: data = data.encode() dataHash = crypto.hashers.sha3_hash(data) if type(dataHash) is bytes: dataHash = dataHash.decode() blockFileName = filepaths.block_data_location + dataHash + '.dat' try: onionrstorage.getData(dataHash) except onionrexceptions.NoDataAvailable: if storage_counter.add_bytes(dataSize) != False: onionrstorage.store(data, blockHash=dataHash) conn = sqlite3.connect(dbfiles.block_meta_db, timeout=30) c = conn.cursor() c.execute("UPDATE hashes SET dataSaved=1 WHERE hash = ?;", (dataHash, )) conn.commit() conn.close() with open(filepaths.data_nonce_file, 'a') as nonceFile: nonceFile.write(nonce_hash + '\n') else: raise onionrexceptions.DiskAllocationReached else: raise onionrexceptions.DataExists("Data is already set for " + dataHash) return dataHash
def __init__(self, data, metadata, subproc_count=None): """ Onionr proof of work using multiple processes Accepts block data, block metadata if subproc_count is not set, os.cpu_count() is used to determine the number of processes Due to Python GIL multiprocessing/use of external libraries is necessary to accelerate CPU bound tasks """ # No known benefit to using more processes than there are cores. # Note: os.cpu_count perhaps not always accurate if subproc_count is None: subproc_count = os.cpu_count() self.subproc_count = subproc_count self.result = '' self.shutdown = False self.data = data self.metadata = metadata """dump dict to measure bytes of json metadata Cannot reuse later bc the pow token must be added """ json_metadata = json.dumps(metadata).encode() self.data = bytesconverter.str_to_bytes(data) compiled_data = bytes(json_metadata + b'\n' + self.data) # Calculate difficulty. May use better algorithm in the future. self.difficulty = onionrproofs.getDifficultyForNewBlock(compiled_data) logger.info('Computing POW (difficulty: %s)...' % (self.difficulty,)) self.main_hash = '0' * 64 self.puzzle = self.main_hash[0:min(self.difficulty, len(self.main_hash))] self.shutdown = False self.payload = None
def ed_verify(data, key, sig, encodedData=True): '''Verify signed data (combined in nacl) to an ed25519 key''' key = unpaddedbase32.repad(bytesconverter.str_to_bytes(key)) try: key = nacl.signing.VerifyKey(key=key, encoder=nacl.encoding.Base32Encoder) except nacl.exceptions.ValueError: return False except binascii.Error: logger.warn('Could not load key for verification, invalid padding') return False retData = False sig = base64.b64decode(sig) try: data = data.encode() except AttributeError: pass try: retData = key.verify(data, sig) # .encode() is not the same as nacl.encoding except nacl.exceptions.BadSignatureError: pass return retData
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 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 import_block_from_data(content): blacklist = onionrblacklist.OnionrBlackList() ret_data = False content = bytesconverter.str_to_bytes(content) data_hash = crypto.hashers.sha3_hash(content) if blacklist.inBlacklist(data_hash): raise BlacklistedBlock(f'%s is a blacklisted block {data_hash}') # returns tuple(metadata, meta), meta is also in metadata metas = blockmetadata.get_block_metadata_from_data(content) metadata = metas[0] # check if metadata is valid if validatemetadata.validate_metadata(metadata, metas[2]): # check if POW is enough/correct if crypto.cryptoutils.verify_POW(content): logger.info(f'Imported block passed proof, saving: {data_hash}.', terminal=True) try: block_hash = onionrstorage.set_data(content) except DiskAllocationReached: logger.warn('Failed to save block due to full disk allocation') raise else: blockmetadb.add_to_block_DB(block_hash, dataSaved=True) # caches block metadata values to block database blockmetadata.process_block_metadata(block_hash) ret_data = block_hash else: raise InvalidProof else: raise InvalidMetadata return ret_data
def extract_ed25519_from_onion_address( address: 'OnionAddressString') -> 'Ed25519PublicKeyBytes': address = str_to_bytes(address).replace(b'.onion', b'').upper() ed25519 = b32decode(address)[:-3] return ed25519
def test_encrypted_insert(self): setup_test() message = 'hello world2' bl = onionrblocks.insert(message, asymPeer=onionrcrypto.pub_key) self.assertIn(bytesconverter.str_to_bytes(message), onionrblockapi.Block(bl, decrypt=True).bcontent)
def get_motd()->Response: motds = blockmetadb.get_blocks_by_type("motd") newest_time = 0 message = "No MOTD currently present." for x in motds: bl = onionrblocks.onionrblockapi.Block(x) if not bl.verifySig() or bl.signer != bytesconverter.bytes_to_str(unpaddedbase32.repad(bytesconverter.str_to_bytes(signer))): continue if not bl.isSigner(signer): continue if bl.claimedTime > newest_time: newest_time = bl.claimedTime message = bl.bcontent return Response(message, headers={"Content-Type": "text/plain"})
def getHumanReadable(name): name = unpaddedbase32.repad(bytesconverter.str_to_bytes(name)) return Response(mnemonickeys.get_human_readable_ID(name))