示例#1
0
def remove_device_session(db, devid: str) -> RetVal:
    '''
	Removes an authorized device from the workspace. Returns a boolean success code.
	'''
    cursor = db.cursor()
    cursor.execute("SELECT devid FROM sessions WHERE devid=?", (devid, ))
    results = cursor.fetchone()
    if not results or not results[0]:
        return RetVal(ResourceNotFound)

    cursor.execute("DELETE FROM sessions WHERE devid=?", (devid, ))
    db.commit()
    return RetVal()
示例#2
0
	def write(self, text: str) -> RetVal:
		'''Sends a string over a socket'''

		if not self.socket:
			return RetVal(NetworkError, 'Invalid connection')
		
		try:
			self.socket.send(text.encode())
		except Exception as exc:
			self.socket.close()
			return RetVal(ExceptionThrown, exc.__str__())
		
		return RetVal()
示例#3
0
	def decrypt(self, data : str) -> RetVal:
		'''Decrypt the passed data using the private key and return the raw data in the field 
		'data'. Base85 decoding of the data is optional, but enabled by default.'''
		if not isinstance(data, str):
			return RetVal(BadParameterType, 'string expected')
		
		try:
			sealedbox = nacl.public.SealedBox(nacl.public.PrivateKey(self.private.raw_data()))
			decrypted_data = sealedbox.decrypt(data.encode(), Base85Encoder)
		except Exception as e:
			return RetVal(ExceptionThrown, str(e))
		
		return RetVal().set_value('data', decrypted_data.decode())
示例#4
0
	def sign(self, data : bytes) -> RetVal:
		'''Return a Base85-encoded signature for the supplied data in the field 'signature'.'''
		if not isinstance(data, bytes):
			return RetVal(BadParameterType, 'bytes expected for data')
		
		key = nacl.signing.SigningKey(self.private.raw_data())

		try:
			signed = key.sign(data, Base85Encoder)
		except Exception as e:
			return RetVal(ExceptionThrown, e)
		
		return RetVal().set_value('signature', 'ED25519:' + signed.signature.decode())
示例#5
0
	def encrypt(self, data : bytes) -> RetVal:
		'''Encrypt the passed data using the public key and return the Base85-encoded data in the 
		field 'data'.'''
		if not isinstance(data, bytes):
			return RetVal(BadParameterType, 'bytes expected')
		
		try:
			sealedbox = nacl.public.SealedBox(nacl.public.PublicKey(self.public.raw_data()))
			encrypted_data = sealedbox.encrypt(data, Base85Encoder).decode()
		except Exception as e:
			return RetVal(ExceptionThrown, str(e))
		
		return RetVal().set_value('data', encrypted_data)
示例#6
0
def load_encryptionpair(path: str) -> RetVal:
	'''Instantiates a keypair from a file'''
	if not path:
		return RetVal(BadParameterValue, 'path may not be empty')
	
	if not os.path.exists(path):
		return RetVal(ResourceNotFound, '%s exists' % path)
	
	indata = None
	try:
		with open(path, "r") as fhandle:
			indata = json.load(fhandle)
	
	except Exception as e:
		return RetVal(ExceptionThrown, e)
	
	if not isinstance(indata, dict):
		return RetVal(BadData, 'File does not contain an Anselus JSON keypair')

	try:
		jsonschema.validate(indata, __encryption_pair_schema)
	except jsonschema.ValidationError:
		return RetVal(BadData, "file data does not validate")
	except jsonschema.SchemaError:
		return RetVal(InternalError, "BUG: invalid EncryptionPair schema")

	public_key = CryptoString(indata['PublicKey'])
	private_key = CryptoString(indata['PrivateKey'])
	if not public_key.is_valid() or not private_key.is_valid():
		return RetVal(BadData, 'Failure to base85 decode key data')
	
	return RetVal().set_value('keypair', EncryptionPair(public_key, private_key))
示例#7
0
	def send_message(self, command : dict) -> RetVal:
		'''Sends a message to the server with command sent as JSON data'''
		cmdstr = json.dumps(command) + '\r\n'
		
		if not self.socket:
			return RetVal(NetworkError, 'not connected')
		
		try:
			self.socket.send(cmdstr.encode())
		except Exception as e:
			self.socket.close()
			return RetVal(ExceptionThrown, e)
		
		return RetVal()
示例#8
0
def load_secretkey(path: str) -> RetVal:
	'''Instantiates a secret key from a file'''
	if not path:
		return RetVal(BadParameterValue, 'path may not be empty')
	
	if not os.path.exists(path):
		return RetVal(ResourceNotFound, '%s exists' % path)
	
	indata = None
	try:
		with open(path, "r") as fhandle:
			indata = json.load(fhandle)
	
	except Exception as e:
		return RetVal(ExceptionThrown, e)
	
	if not isinstance(indata, dict):
		return RetVal(BadData, 'File does not contain an Anselus JSON secret key')

	try:
		jsonschema.validate(indata, __secret_key_schema)
	except jsonschema.ValidationError:
		return RetVal(BadData, "file data does not validate")
	except jsonschema.SchemaError:
		return RetVal(InternalError, "BUG: invalid SecretKey schema")

	key = CryptoString(indata['SecretKey'])
	if not key.is_valid():
		return RetVal(BadData, 'Failure to base85 decode key data')
	
	return RetVal().set_value('key', SecretKey(key))
示例#9
0
def add_device_session(db,
                       address: str,
                       devid: str,
                       enctype: str,
                       public_key: str,
                       private_key: str,
                       devname='') -> RetVal:
    '''Adds a device to a workspace'''

    if not address or not devid or not enctype or not public_key or not private_key:
        return RetVal(BadParameterValue, "Empty parameter")

    if enctype != 'curve25519':
        return RetVal(BadParameterValue, "enctype must be 'curve25519'")

    # Normally we don't validate the input, relying on the caller to ensure valid data because
    # in most cases, bad data just corrupts the database integrity, not crash the program.
    # We have to do some here to ensure there isn't a crash when the address is split.
    parts = utils.split_address(address)
    if parts.error():
        return parts

    # address has to be valid and existing already
    cursor = db.cursor()
    cursor.execute("SELECT wid FROM workspaces WHERE wid=?", (parts['wid'], ))
    results = cursor.fetchone()
    if not results or not results[0]:
        return RetVal(ResourceNotFound)

    # Can't have a session on the server already
    cursor.execute("SELECT address FROM sessions WHERE address=?", (address, ))
    results = cursor.fetchone()
    if results:
        return RetVal(ResourceExists)

    cursor = db.cursor()
    if devname:
        cursor.execute(
            '''INSERT INTO sessions(
				address, devid, enctype, public_key, private_key, devname) 
				VALUES(?,?,?,?,?,?)''',
            (address, devid, enctype, public_key, private_key, devname))
    else:
        cursor.execute(
            '''INSERT INTO sessions(
				address, devid, enctype, public_key, private_key) 
				VALUES(?,?,?,?,?)''', (address, devid, enctype, public_key, private_key))
    db.commit()
    return RetVal()
示例#10
0
    def verify(self) -> RetVal:
        '''Verifies the card's entire chain of entries'''

        if len(self.entries) == 0:
            return RetVal(ResourceNotFound, 'keycard contains no entries')

        if len(self.entries) == 1:
            return RetVal()

        for i in range(len(self.entries) - 1):
            status = self.entries[i + 1].verify_chain(self.entries[i])
            if status.error():
                return status

        return RetVal()
示例#11
0
    def add_to_db(self, pw: encryption.Password) -> RetVal:
        '''Adds a workspace to the storage database'''

        cursor = self.db.cursor()
        cursor.execute("SELECT wid FROM workspaces WHERE wid=?", (self.wid, ))
        results = cursor.fetchone()
        if results:
            return RetVal(ResourceExists, self.wid)

        cursor.execute(
            '''INSERT INTO workspaces(wid,domain,password,pwhashtype,type)
			VALUES(?,?,?,?,?)''',
            (self.wid, self.domain, pw.hashstring, pw.hashtype, self.type))
        self.db.commit()
        return RetVal()
示例#12
0
def regcode(conn: ServerConnection, regid: str, code: str, pwhash: str, devid: str, 
	devpair: EncryptionPair, domain: str) -> RetVal:
	'''Finishes registration of a workspace'''
	
	request = {
		'Action':'REGCODE',
		'Data':{
			'Reg-Code': code,
			'Password-Hash':pwhash,
			'Device-ID':devid,
			'Device-Key':devpair.public.as_string()
		}
	}

	if domain:
		request['Data']['Domain'] = domain

	if utils.validate_uuid(regid):
		request['Data']['Workspace-ID'] = regid
	else:
		request['Data']['User-ID'] = regid
	
	status = conn.send_message(request)
	if status.error():
		return status
	
	response = conn.read_response(server_response)
	if response.error():
		return response
	
	if response['Code'] != 201:
		return wrap_server_error(response)
	
	return RetVal()
示例#13
0
def init_user(conn: serverconn.ServerConnection, config: dict) -> RetVal:
    '''Creates a test user for command testing'''

    userwid = '33333333-3333-3333-3333-333333333333'
    status = serverconn.preregister(conn, userwid, 'csimons', 'example.net')
    assert not status.error(), "init_user(): uid preregistration failed"
    assert status['domain'] == 'example.net' and 'wid' in status and 'regcode' in status and \
     status['uid'] == 'csimons', "init_user(): failed to return expected data"

    regdata = status
    password = Password('MyS3cretPassw*rd')
    devpair = EncryptionPair()
    devid = '11111111-1111-1111-1111-111111111111'
    status = serverconn.regcode(conn, 'csimons', regdata['regcode'],
                                password.hashstring, devid, devpair,
                                'example.net')
    assert not status.error(), "init_user(): uid regcode failed"

    config['user_wid'] = userwid
    config['user_uid'] = regdata['uid']
    config['user_domain'] = regdata['domain']
    config['user_devid'] = devid
    config['user_devpair'] = devpair
    config['user_password'] = password

    return RetVal()
示例#14
0
	def verify(self, data : bytes, data_signature : CryptoString) -> RetVal:
		'''Return a Base85-encoded signature for the supplied data in the field 'signature'.'''
		
		if not isinstance(data, bytes):
			return RetVal(BadParameterType, 'bytes expected for data')
		if not isinstance(data_signature, CryptoString):
			return RetVal(BadParameterType, 'signature parameter must be a CryptoString')
		
		key = nacl.signing.VerifyKey(self.public.raw_data())

		try:
			key.verify(data, data_signature.raw_data())
		except Exception as e:
			return RetVal(VerificationError, e)
		
		return RetVal()
示例#15
0
    def set_expiration(self, numdays=-1) -> RetVal:
        '''Sets the expiration field to the number of days specified after the current date'''
        if numdays < 0:
            if self.type == 'Organization':
                numdays = 365
            elif self.type == 'User':
                numdays = 90
            else:
                return RetVal(UnsupportedKeycardType)

        # An expiration date can be no longer than 3 years
        if numdays > 1095:
            numdays = 1095

        expiration = datetime.datetime.utcnow() + datetime.timedelta(numdays)
        self.fields['Expires'] = expiration.strftime("%Y%m%d")
        return RetVal()
示例#16
0
	def Assign(self, pwhash) -> RetVal:
		'''
		Takes a PHC hash format string and assigns the password object to it.
		Returns: [dict]
		error : string
		'''
		self.hashstring = pwhash
		return RetVal()
示例#17
0
def device(conn: ServerConnection, devid: str, devpair: EncryptionPair) -> RetVal:
	'''Completes the login process by submitting device ID and its session string.'''
	if not utils.validate_uuid(devid):
		return RetVal(AnsBadRequest, 'Invalid device ID').set_value('status', 400)

	conn.send_message({
		'Action' : "DEVICE",
		'Data' : { 
			'Device-ID' : devid,
			'Device-Key' : devpair.public.as_string()
		}
	})

	# Receive, decrypt, and return the server challenge
	response = conn.read_response(server_response)
	if response.error():
		return response
	
	if response['Code'] != 100:
		return wrap_server_error(response)

	if 'Challenge' not in response['Data']:
		return RetVal(ServerError, 'server did not return a device challenge')
	
	status = devpair.decrypt(response['Data']['Challenge'])
	if status.error():
		cancel(conn)
		return RetVal(DecryptionFailure, 'failed to decrypt device challenge')

	conn.send_message({
		'Action' : "DEVICE",
		'Data' : { 
			'Device-ID' : devid,
			'Device-Key' : devpair.public.as_string(),
			'Response' : status['data']
		}
	})

	response = conn.read_response(None)
	if response.error():
		return response
	
	if response['Code'] == 200:
		return RetVal()
	
	return wrap_server_error(response)
示例#18
0
    def verify_chain(self, previous: EntryBase) -> RetVal:
        '''Verifies the chain of custody between the provided previous entry and the current one.'''

        if previous.type != 'User':
            return RetVal(BadParameterValue, 'entry type mismatch')

        if 'Custody' not in self.signatures or not self.signatures['Custody']:
            return RetVal(ResourceNotFound, 'custody signature missing')

        if 'Contact-Request-Verification-Key' not in previous.fields or \
          not previous.fields['Contact-Request-Verification-Key']:
            return RetVal(ResourceNotFound, 'signing key missing')

        status = self.verify_signature(
            CryptoString(previous.fields['Contact-Request-Verification-Key']),
            'Custody')
        return status
示例#19
0
def remove_key(db: sqlite3.Connection, keyid: str) -> RetVal:
    '''Deletes an encryption key from a workspace.
	Parameters:
	keyid : uuid

	Returns:
	error : string
	'''
    cursor = db.cursor()
    cursor.execute("SELECT keyid FROM keys WHERE keyid=?", (keyid, ))
    results = cursor.fetchone()
    if not results or not results[0]:
        return RetVal(ResourceNotFound)

    cursor.execute("DELETE FROM keys WHERE keyid=?", (keyid, ))
    db.commit()
    return RetVal()
示例#20
0
def set_credentials(db, wid: str, domain: str,
                    pw: encryption.Password) -> RetVal:
    '''Sets the password and hash type for the specified workspace. A boolean success 
	value is returned.'''
    cursor = db.cursor()
    cursor.execute("SELECT wid FROM workspaces WHERE wid=? AND domain=?",
                   (wid, domain))
    results = cursor.fetchone()
    if not results or not results[0]:
        return RetVal(ResourceNotFound)

    cursor = db.cursor()
    cursor.execute(
        "UPDATE workspaces SET password=?,pwhashtype=? WHERE wid=? AND domain=?",
        (pw.hashstring, pw.hashtype, wid, domain))
    db.commit()
    return RetVal()
示例#21
0
    def remove_workspace_entry(self, wid: str, domain: str) -> RetVal:
        '''
		Removes a workspace from the storage database.
		NOTE: this only removes the workspace entry itself. It does not remove keys, sessions,
		or other associated data.
		'''
        cursor = self.db.cursor()
        cursor.execute("SELECT wid FROM workspaces WHERE wid=? AND domain=?",
                       (wid, domain))
        results = cursor.fetchone()
        if not results or not results[0]:
            return RetVal(ResourceNotFound, "%s/%s not found" % (wid, domain))

        cursor.execute("DELETE FROM workspaces WHERE wid=? AND domain=?",
                       (wid, domain))
        self.db.commit()
        return RetVal()
示例#22
0
    def remove_folder(self, fid: encryption.FolderMapping) -> RetVal:
        '''Deletes a folder mapping.
		Parameters:
		fid : uuid

		Returns:
		error : string
		'''
        cursor = self.db.cursor()
        cursor.execute("SELECT fid FROM folders WHERE fid=?", (fid, ))
        results = cursor.fetchone()
        if not results or not results[0]:
            return RetVal(ResourceNotFound, fid)

        cursor.execute("DELETE FROM folders WHERE fid=?", (fid, ))
        self.db.commit()
        return RetVal()
示例#23
0
    def set_userid(self, userid: str) -> RetVal:
        '''set_userid() sets the human-friendly name for the workspace'''

        if ' ' or '"' in userid:
            return RetVal(BadParameterValue, '" and space not permitted')

        cursor = self.db.cursor()
        sqlcmd = '''
		UPDATE workspaces
		SET userid=?
		WHERE wid=? and domain=?
		'''
        cursor.execute(sqlcmd, (userid, self.wid, self.domain))
        self.db.commit()
        self.uid = userid

        return RetVal()
示例#24
0
文件: rpc.py 项目: darkwyrm/pyanselus
    def connect(self, host: str, port) -> RetVal:
        '''Creates a connection to the server.'''
        try:
            self.__sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            # Set a short timeout in case the server doesn't respond immediately,
            # which is the expectation as soon as a client connects.
            self.__sock.settimeout(10.0)
        except:
            return RetVal(NetworkError, "Couldn't create a socket")

        out_data = RetVal()
        out_data.set_value('socket', self.__sock)

        try:
            self.ip = socket.gethostbyname(host)
        except socket.gaierror:
            self.disconnect()
            return RetVal(ResourceNotFound, "Couldn't locate host %s" % host)

        try:
            self.__sock.connect((self.ip, port))
            self.port = port

            status = self.read_msg(pyanselus.rpc_schemas.greeting)
            if not status.error():
                self.version = status['msg']['version'].strip()

        except Exception as exc:
            self.disconnect()
            return RetVal(NetworkError,
                          f"Couldn't connect to host {host}: {exc}")

        # Set a timeout of 30 minutes
        self.__sock.settimeout(1800.0)
        return out_data
示例#25
0
    def verify_signature(self, verify_key: CryptoString,
                         sigtype: str) -> RetVal:
        '''Verifies a signature, given a verification key'''

        if not verify_key.is_valid():
            return RetVal(BadParameterValue, 'bad verify key')

        sig_names = [x['name'] for x in self.signature_info]
        if sigtype not in sig_names:
            return RetVal(BadParameterValue, 'bad signature type')

        if verify_key.prefix != 'ED25519':
            return RetVal(UnsupportedEncryptionType, verify_key.prefix)

        if sigtype in self.signatures and not self.signatures[sigtype]:
            return RetVal(NotCompliant, 'empty signature ' + sigtype)

        sig = CryptoString()
        status = sig.set(self.signatures[sigtype])
        if status.error():
            return status

        try:
            vkey = nacl.signing.VerifyKey(verify_key.raw_data())
        except Exception as e:
            return RetVal(ExceptionThrown, e)

        try:
            data = self.make_bytestring(sig_names.index(sigtype))
            vkey.verify(data, sig.raw_data())
        except nacl.exceptions.BadSignatureError:
            return RetVal(InvalidKeycard)

        return RetVal()
示例#26
0
def password(conn: ServerConnection, wid: str, pwhash: str) -> RetVal:
	'''Continues the login process sending a password hash to the server.'''
	if not password or not utils.validate_uuid(wid):
		return RetVal(BadParameterValue)
	
	conn.send_message({
		'Action' : "PASSWORD",
		'Data' : { 'Password-Hash' : pwhash }
	})

	response = conn.read_response(server_response)
	if response.error():
		return response
	
	if response['Code'] != 100:
		return wrap_server_error(response)
	
	return RetVal()
示例#27
0
    def add_folder(self, folder: encryption.FolderMapping) -> RetVal:
        '''
		Adds a mapping of a folder ID to a specific path in the workspace.
		Parameters:
		folder : FolderMapping object
		'''
        cursor = self.db.cursor()
        cursor.execute("SELECT fid FROM folders WHERE fid=?", (folder.fid, ))
        results = cursor.fetchone()
        if results:
            return RetVal(ResourceExists, folder.fid)

        cursor.execute(
            '''INSERT INTO folders(fid,address,keyid,path,permissions)
			VALUES(?,?,?,?,?)''', (folder.fid, folder.address, folder.keyid,
                          folder.path, folder.permissions))
        self.db.commit()
        return RetVal()
示例#28
0
    def save(self, path: str, clobber=False) -> RetVal:
        '''Saves to the specified path, forcing CRLF line endings to prevent any weird behavior 
		caused by line endings invalidating signatures.'''

        if not path:
            return RetVal(BadParameterValue, 'path may not be empty')

        if os.path.exists(path) and not clobber:
            return RetVal(ResourceExists)

        try:
            with open(path, 'wb') as f:
                f.write(self.make_bytestring(-1))

        except Exception as e:
            return RetVal(ExceptionThrown, str(e))

        return RetVal()
示例#29
0
    def rename_profile(self, oldname: str, newname: str) -> RetVal:
        '''Renames the specified profile'''
        status = self.fs.pman.rename_profile(oldname, newname)
        if status.error() != '':
            return status

        if self.active_profile == oldname:
            self.active_profile = newname
        return RetVal()
示例#30
0
def test_count():
    '''Tests count()'''
    r = RetVal()
    assert r.count() == 0, '''Unused RetVal is not empty'''
    r['foo'] = 'bar'
    assert r.count() == 1, '''Incorrect item count in RetVal'''
    r.empty()
    assert r.count() == 0, '''Emptied RetVal is not empty'''