def get_session_keys(username=None, user_uid=None, session_uid=None, identity_url=None): """Function to return the session keys for the specified user""" if username is None and user_uid is None: raise ValueError("You must supply either the username or user_uid!") if session_uid is None: raise ValueError("You must supply a valid UID for a login session") if identity_url is None: identity_url = _get_identity_url() response = _call_function(identity_url, "whois", username=username, user_uid=user_uid, session_uid=session_uid) try: response["public_key"] = _PublicKey.from_data(response["public_key"]) except: pass try: response["public_cert"] = _PublicKey.from_data(response["public_cert"]) except: pass return response
def run(args): """This function will allow a user to request a new session that will be validated by the passed public key and public signing certificate. This will return a URL that the user must connect to to then log in and validate that request. Args: args (dict): containing login data such as username, password etc Returns: dict: containing status of login attempt """ username = args["username"] public_key = PublicKey.from_data(args["public_key"]) public_cert = PublicKey.from_data(args["public_certificate"]) try: scope = args["scope"] except: scope = None try: permissions = args["permissions"] except: permissions = None try: hostname = args["hostname"] except: hostname = None try: ipaddr = args["ipaddr"] except: ipaddr = None try: login_message = args["login_message"] except: login_message = None # Generate a login session for this request login_session = LoginSession(username=username, public_key=public_key, public_cert=public_cert, ipaddr=ipaddr, hostname=hostname, login_message=login_message, scope=scope, permissions=permissions) return_value = {} return_value["login_url"] = login_session.login_url() return_value["short_uid"] = login_session.short_uid() return_value["session_uid"] = login_session.uid() return return_value
def test_keys(): privkey = PrivateKey() pubkey = privkey.public_key() message = "Hello World" sig = privkey.sign(message.encode("utf-8")) pubkey.verify(sig, message.encode("utf-8")) c = pubkey.encrypt(message.encode("utf-8")) m = privkey.decrypt(c).decode("utf-8") assert (m == message) privkey2 = PrivateKey() sig2 = privkey2.sign(message.encode("utf-8")) with pytest.raises(SignatureVerificationError): pubkey.verify(sig2, message.encode("utf-8")) bytes = privkey.bytes("testPass32") PrivateKey.read_bytes(bytes, "testPass32") privkey.write("test.pem", "testPass32") PrivateKey.read("test.pem", "testPass32") bytes = pubkey.bytes() pubkey2 = PublicKey.read_bytes(bytes) assert (bytes == pubkey2.bytes()) long_message = str([random.getrandbits(8) for _ in range(4096)]).encode("utf-8") c = pubkey.encrypt(long_message) m = privkey.decrypt(c) assert (m == long_message) os.unlink("test.pem") data = pubkey.to_data() pubkey2 = PublicKey.from_data(data) assert (pubkey.bytes() == pubkey2.bytes()) data = privkey.to_data("testPass33") privkey2 = PrivateKey.from_data(data, "testPass33") assert (privkey == privkey2)
def from_data(data, password=None): """Deserialise this object from the passed data. This will only deserialise the private key, private certificate, and OTP if a valid password and passcode is supplied """ service = Service() if password: # get the private info... service._privkey = _PrivateKey.from_data(data["private_key"], password) service._privcert = _PrivateKey.from_data( data["private_certificate"], password) service._otpsecret = _string_to_bytes(data["otpsecret"]) service._admin_password = _string_to_bytes(data["admin_password"]) else: service._privkey = None service._privcert = None service._otpsecret = None service._uuid = data["uuid"] service._service_type = data["service_type"] service._service_url = data["service_url"] service._canonical_url = service._service_url service._pubkey = _PublicKey.from_data(data["public_key"]) service._pubcert = _PublicKey.from_data(data["public_certificate"]) if service.is_identity_service(): from Acquire.Identity import IdentityService as _IdentityService return _IdentityService(service) elif service.is_access_service(): from Acquire.Access import AccessService as _AccessService return _AccessService(service) elif service.is_accounting_service(): from Acquire.Accounting import AccountingService \ as _AccountingService return _AccountingService(service) else: return service
def run(args): """Open and return a new ChunkUploader that can be used to upload a file in lots of chunks """ filename = str(args["filename"]) drive_uid = str(args["drive_uid"]) try: aclrules = ACLRules.from_data(args["aclrules"]) except: aclrules = None try: authorisation = Authorisation.from_data(args["authorisation"]) except: authorisation = None try: par_uid = str(args["par_uid"]) except: par_uid = None try: secret = str(args["secret"]) except: secret = None try: pubkey = PublicKey.from_data(args["encryption_key"]) except: pubkey = None if par_uid is not None: registry = PARRegistry() (par, identifiers) = registry.load(par_uid=par_uid, secret=secret) else: par = None identifiers = None drive = DriveInfo(drive_uid=drive_uid) (filemeta, uploader) = drive.open_uploader(filename=filename, aclrules=aclrules, authorisation=authorisation, par=par, identifiers=identifiers) return { "filemeta": filemeta.to_data(), "uploader": uploader.to_data(pubkey=pubkey) }
def get_public_certs(identity_url, username, session_uid): """Call the identity_url to obtain the public keys and certificates of the user with 'username' logged in using the specified session_uid """ response = _call_function(identity_url, "get_keys", username=username, session_uid=session_uid) public_key = None public_cert = None if "public_key" in response: public_key = _PublicKey.read_bytes( _string_to_bytes(response["public_key"])) if "public_cert" in response: public_cert = _PublicKey.read_bytes( _string_to_bytes(response["public_cert"])) return (public_key, public_cert)
def run(args): """This function return the status and service info Args: args (dict): containing data on the service we want to trust Returns: dict: containing status, status message and passed in args """ try: service_url = args["service_url"] except: service_url = None try: public_cert = PublicKey.from_data(args["public_certificate"]) except: public_cert = None try: authorisation = Authorisation.from_data(args["authorisation"]) except: authorisation = None if service_url is not None: service = get_checked_remote_service(service_url, public_cert) else: service = None if service is not None: trust_service(service, authorisation) return_value = {} return_value["args"] = args return return_value
def _get_key(key, fingerprint=None): """The user may pass the key in multiple ways. It could just be a key. Or it could be a function that gets the key on demand. Or it could be a dictionary that has the key stored under "encryption_public_key" """ from Acquire.Crypto import PublicKey as _PublicKey from Acquire.Crypto import PrivateKey as _PrivateKey if key is None: return None elif isinstance(key, _PublicKey) or isinstance(key, _PrivateKey): key = key elif isinstance(key, dict): try: key = key["encryption_public_key"] except: key = None if key is not None: from Acquire.ObjectStore import string_to_bytes as _string_to_bytes key = _PublicKey.read_bytes(_string_to_bytes(key)) else: key = key(fingerprint=fingerprint) if fingerprint is not None: if key is None: from Acquire.Crypto import KeyManipulationError raise KeyManipulationError( "Cannot find the key with fingerprint %s!" % fingerprint) elif key.fingerprint() != fingerprint: from Acquire.Crypto import KeyManipulationError raise KeyManipulationError( "Cannot find a key with the required fingerprint (%s). " "The only key has fingerprint %s" % (fingerprint, key.fingerprint())) return key
async def handler(ctx, data=None, loop=None): """This function just reflects back the json that is supplied. This is useful for debugging clients that are calling this server """ result = {} try: data = json.loads(data) privkey = get_service_private_key() encrypted = string_to_bytes(data["data"]) decrypted = privkey.decrypt(encrypted).decode("utf-8") data = json.loads(decrypted) pem = data["encryption_public_key"] key = PublicKey.from_data(pem) result["key"] = str(key.to_data()) result["status"] = -1 except Exception as e: message = {"status": -1, "message": "Error packing results: %s" % e} return json.dumps(message) except: message = { "status": -1, "message": "Error packing results: Unknown error" } return json.dumps(message) return json.dumps(result)
def get_session_info(identity_url, session_uid, scope=None, permissions=None): """Call the identity_url to obtain information about the specified login session_uid. Optionally limit the scope and permissions for which these certs would be valid """ from Acquire.Service import get_trusted_service as _get_trusted_service service = _get_trusted_service(identity_url) args = {"session_uid": session_uid} if scope is not None: args["scope"] = scope if permissions is not None: args["permissions"] = permissions response = service.call_function(function="get_session_info", args=args) try: del response["status"] except: pass try: del response["message"] except: pass from Acquire.Crypto import PublicKey as _PublicKey for key in response.keys(): if key in ["public_key", "public_certificate"]: response[key] = _PublicKey.from_data(response[key]) return response
def _get_key(key): """The user may pass the key in multiple ways. It could just be a key. Or it could be a function that gets the key on demand. Or it could be a dictionary that has the key stored under "encryption_public_key" """ if key is None: return None elif isinstance(key, _PublicKey): return key elif isinstance(key, dict): try: key = key["encryption_public_key"] except: return None key = _PublicKey.read_bytes(_string_to_bytes(key)) else: try: key = key() except: pass return key
def get_trusted_registry_service(registry_uid=None, service_uid=None, service_url=None): """Return the trusted service info for the registry with specified registry_uid, or get any trusted registry service using either 'service_uid' or 'service_url' as a starting hint to locate a suitable registry """ if service_uid is not None: # for the moment, just try to get one registry. Eventually we should # try to get several in case this one is down registry_uid = get_primary_registry_uid(service_uid) return get_trusted_registry_service(registry_uid=registry_uid) if service_url is not None: if service_url.find(".") != -1: # try the main acquire registry first return get_trusted_registry_service(registry_uid="a0-a0") else: # this must be testing... return get_trusted_registry_service(registry_uid="Z9-Z9") if registry_uid is None: raise PermissionError( "You must specify one of registry_uid, service_uid " "or service_url") from Acquire.Service import get_trusted_service as _get_trusted_service try: registry_service = _get_trusted_service(service_uid=registry_uid, autofetch=False) except: registry_service = None if registry_service is not None: if not registry_service.is_registry_service(): from Acquire.Service import ServiceError raise ServiceError("The requested service (%s) for %s is NOT a " "registry service!" % (registry_service, registry_uid)) if registry_service.uid() != registry_uid: from Acquire.Service import ServiceError raise ServiceError( "Disagreement of UID (%s) is NOT the right registry service!" % registry_service) # everything is ok - we have seen this registry before return registry_service # boostrapping from Acquire.Registry import get_registry_details \ as _get_registry_details details = _get_registry_details(registry_uid) from Acquire.Service import call_function as _call_function from Acquire.Service import Service as _Service from Acquire.Crypto import get_private_key as _get_private_key from Acquire.Crypto import PrivateKey as _PrivateKey from Acquire.Crypto import PublicKey as _PublicKey from Acquire.ObjectStore import bytes_to_string as _bytes_to_string from Acquire.ObjectStore import string_to_bytes as _string_to_bytes privkey = _get_private_key(key="registry") pubkey = _PublicKey.from_data(details["public_key"]) pubcert = _PublicKey.from_data(details["public_certificate"]) # ask the registry to return to us their latest details - use # a challenge-response to make sure that the response is # properly returned challenge = _PrivateKey.random_passphrase() encrypted_challenge = _bytes_to_string(pubkey.encrypt(challenge)) args = { "challenge": encrypted_challenge, "fingerprint": pubkey.fingerprint() } result = _call_function(service_url=details["canonical_url"], function=None, args=args, args_key=pubkey, response_key=privkey, public_cert=pubcert) if result["response"] != challenge: from Acquire.Service import ServiceError raise ServiceError( "The requested service (%s) failed to respond to the challenge!" % registry_service) registry_service = _Service.from_data(result["service_info"]) if not registry_service.is_registry_service(): from Acquire.Service import ServiceError raise ServiceError( "The requested service (%s) is NOT a registry service!" % registry_service) if registry_service.uid() != details["uid"]: from Acquire.Service import ServiceError raise ServiceError( "Disagreement of UID (%s) is NOT the right registry service!" % registry_service) # ok - we've got the registry - add this to the set of # trusted services so that we don't need to bootstrap from # the registry details again from Acquire.Service import trust_service as _trust_service _trust_service(registry_service) return registry_service
def run(args): """This function will allow a user to register an account with a username and password""" status = 0 message = None provisioning_uri = None username = args["username"] password = args["password"] # generate a sanitised version of the username user_account = UserAccount(username) # generate the encryption keys and otp secret privkey = PrivateKey() pubkey = privkey.public_key() otp = OTP() provisioning_uri = otp.provisioning_uri(username) # save the encrypted private key (encrypted using the user's password) # and encrypted OTP secret (encrypted using the public key) user_account.set_keys(privkey.bytes(password), pubkey.bytes(), otp.encrypt(pubkey)) # remove the key and password from memory privkey = None password = None # now log into the central identity account to either register # the user, or to update to a new password bucket = login_to_service_account() account_key = "accounts/%s" % user_account.sanitised_name() try: existing_data = ObjectStore.get_object_from_json(bucket, account_key) except: existing_data = None message = "Created a new account for '%s'" % username status = 0 if existing_data is None: # save the new account details ObjectStore.set_object_from_json(bucket, account_key, user_account.to_data()) # need to update the "whois" database with the uuid of this user ObjectStore.set_string_object(bucket, "whois/%s" % user_account.uuid(), user_account.username()) else: # The account already exists. See if this is a password change # request old_password = None try: old_password = args["old_password"] except: raise ExistingAccountError( "An account by this name already exists!") if old_password != password: # this is a change of password request - validate that # the existing password unlocks the existing key user_account = UserAccount.from_data(existing_data) testkey = PrivateKey.read_bytes(user_account.private_key(), old_password) # decrypt the old secret old_secret = testkey.decrypt(user_account.otp_secret()) # now encrypt the secret with the new key new_key = PublicKey.read_bytes(pubkey) new_secret = new_key.encrypt(old_secret) user_account.set_keys(privkey, pubkey, new_secret) # save the new account details ObjectStore.set_object_from_json(bucket, account_key, user_account.to_data()) message = "Updated the password for '%s'" % username else: message = "No need to change account '%s'" % username return_value = create_return_value(status, message) if provisioning_uri: return_value["provisioning_uri"] = provisioning_uri return return_value
def whois(self, username=None, user_uid=None, session_uid=None): """Do a whois lookup to map from username to user_uid or vice versa. If 'session_uid' is provided, then also validate that this is a correct login session, and return also the public key and signing certificate for this login session. This should return a dictionary with the following keys optionally contained; username = name of the user user_uid = uid of the user public_key = public key for the session with uid 'session_uid' public_cert = public certificate for that login session """ if (username is None) and (user_uid is None): raise IdentityServiceError("You must supply either a username " "or a user's UID for a lookup") key = _PrivateKey() response = None if session_uid is None: args = {} else: args = {"session_uid": str(session_uid)} try: if username: args["username"] = str(username) response = _call_function( self.service_url(), "whois", public_cert=self.public_certificate(), response_key=key, args=args) lookup_uid = response["user_uid"] else: lookup_uid = None if user_uid: args["user_uid"] = str(user_uid) response = _call_function( self.service_url(), "whois", public_cert=self.public_certificate(), response_key=key, args=args) lookup_username = response["username"] else: lookup_username = None except Exception as e: raise IdentityServiceError("Failed whois lookup: %s" % str(e)) if username is None: username = lookup_username elif (lookup_username is not None) and (username != lookup_username): raise IdentityServiceError( "Disagreement of the user who matches " "UID=%s. We think '%s', but the identity service says '%s'" % (user_uid, username, lookup_username)) if user_uid is None: user_uid = lookup_uid elif (lookup_uid is not None) and (user_uid != lookup_uid): raise IdentityServiceError( "Disagreement of the user's UID for user " "'%s'. We think %s, but the identity service says %s" % (username, user_uid, lookup_uid)) result = response try: result["public_key"] = _PublicKey.from_data(response["public_key"]) except: pass try: result["public_cert"] = _PublicKey.from_data( response["public_cert"]) except: pass return result
def _get_user_public_cert(self, scope=None, permissions=None): """Internal function that returns the public certificate of the user who signed this authorisation. This will check that the authorisation was not signed after the user logged out, as well as validating the services that provide the user session keys etc. """ must_fetch = False try: if scope != self._scope or permissions != self._permissions: must_fetch = True except: must_fetch = True if self._pubcert is not None: if not must_fetch: try: return self._pubcert except: pass try: testing_key = self._testing_key except: testing_key = None if testing_key is not None: if not self._is_testing: raise PermissionError( "You cannot pass a test key to a non-testing " "Authorisation") return testing_key # we need to get the public signing key for this session from Acquire.Service import get_trusted_service \ as _get_trusted_service from Acquire.ObjectStore import get_datetime_now \ as _get_datetime_now try: identity_service = _get_trusted_service(self._identity_url) except: raise PermissionError( "Unable to verify the authorisation as we do not trust " "the identity service at %s" % self._identity_url) if not identity_service.can_identify_users(): raise PermissionError( "Cannot verify an Authorisation that does not use a " "valid identity service") if identity_service.uid() != self._identity_uid: raise PermissionError( "Cannot auth_once this Authorisation as the actual UID of " "the identity service at '%s' (%s) does not match " "the UID of the service that signed this authorisation " "(%s)" % (self._identity_url, identity_service.uid(), self._identity_uid)) response = identity_service.get_session_info( session_uid=self._session_uid, scope=scope, permissions=permissions) try: user_uid = response["user_uid"] except: pass if self._user_uid != user_uid: raise PermissionError( "Cannot verify the authorisation as there is " "disagreement over the UID of the user who signed " "the authorisation. %s versus %s" % (self._user_uid, user_uid)) try: logout_datetime = _string_to_datetime(response["logout_datetime"]) except: logout_datetime = None if logout_datetime: # the user has logged out from this session - ensure that # the authorisation was created before the user logged out if logout_datetime < self.signature_time(): raise PermissionError( "This authorisation was signed after the user logged " "out. This means that the authorisation is not valid. " "Please log in again and create a new authorisation.") from Acquire.Crypto import PublicKey as _PublicKey pubcert = _PublicKey.from_data(response["public_cert"]) self._pubcert = pubcert self._scope = scope self._permissions = permissions return pubcert
def from_data(data): """Return a LoginSession constructed from the passed data (dictionary) """ l = LoginSession() if data is not None and len(data) > 0: from Acquire.ObjectStore import string_to_datetime \ as _string_to_datetime from Acquire.Crypto import PublicKey as _PublicKey l._uid = data["uid"] l._username = data["username"] l._request_datetime = _string_to_datetime(data["request_datetime"]) l._pubcert = _PublicKey.from_data(data["public_certificate"]) l._status = data["status"] try: l._pubkey = _PublicKey.from_data(data["public_key"]) except: l._pubkey = None try: l._login_datatime = _string_to_datetime(data["login_datetime"]) except: pass try: l._logout_datetime = _string_to_datetime( data["logout_datetime"]) except: pass try: l._ipaddr = data["ipaddr"] except: pass try: l._hostname = data["hostname"] except: pass try: l._login_message = data["login_message"] except: pass try: l._scope = data["scope"] except: pass try: l._permissions = data["permissions"] except: pass try: l._user_uid = data["user_uid"] except: pass try: l._device_uid = data["device_uid"] except: pass return l
def run(args): """Call this function to initiate the two-step file-download process. Step 1: download - tells the service to download the file. If the file is small then the file will be in the response. Otherwise a OSPar will be returned that will let you download the file. If this is the case, then you must call step 2... Step 2: downloaded - after you have downloaded the file from the OSPar call OSPar.close() so that the service knows that the OSPar is no longer needed and can be deleted """ drive_uid = args["drive_uid"] filename = args["filename"] try: authorisation = Authorisation.from_data(args["authorisation"]) except: authorisation = None try: par_uid = args["par_uid"] except: par_uid = None try: secret = args["secret"] except: secret = None public_key = PublicKey.from_data(args["encryption_key"]) if "version" in args: version = str(args["version"]) else: version = None if "force_par" in args: force_par = args["force_par"] else: force_par = None if "must_chunk" in args: must_chunk = args["must_chunk"] else: must_chunk = False if must_chunk: must_chunk = True if force_par: force_par = True if par_uid is not None: registry = PARRegistry() (par, identifiers) = registry.load(par_uid=par_uid, secret=secret) else: par = None identifiers = None drive = DriveInfo(drive_uid=drive_uid) return_value = {} (filemeta, filedata, par, downloader) = drive.download(filename=filename, version=version, authorisation=authorisation, encrypt_key=public_key, force_par=force_par, must_chunk=must_chunk, par=par, identifiers=identifiers) if filemeta is not None: return_value["filemeta"] = filemeta.to_data() if filedata is not None: from Acquire.ObjectStore import bytes_to_string as _bytes_to_string return_value["filedata"] = _bytes_to_string(filedata) if par is not None: return_value["download_par"] = par.to_data() if downloader is not None: return_value["downloader"] = downloader.to_data(pubkey=public_key) return return_value
def run(args): """Call this function to initiate the two-step file-upload process. Step 1: upload - tells the service that a file of specific size and checksum will be uploaded. This gives the service the chance to decide whether this will be allowed. If the file is small, and was thus included in the FileHandle, then it is uploaded immediately and the operation completes. If the file is large, then we now returns a OSPar that can be used for this upload (Step 2) Step 2: after the user has used the OSPar to upload the file, they should call OSPar.close() to notify the service that the file has been successfully uploaded. This will verify that the file has been uploaded correctly, will receipt the storage cost and will delete the OSPar """ filehandle = FileHandle.from_data(args["filehandle"]) try: authorisation = Authorisation.from_data(args["authorisation"]) except: authorisation = None try: par_uid = args["par_uid"] except: par_uid = None try: secret = args["secret"] except: secret = None try: public_key = PublicKey.from_data(args["encryption_key"]) except: public_key = None if par_uid is not None: registry = PARRegistry() (par, identifiers) = registry.load(par_uid=par_uid, secret=secret) else: par = None identifiers = None drive_uid = filehandle.drive_uid() drive = DriveInfo(drive_uid=drive_uid) return_value = {} (filemeta, par) = drive.upload(filehandle=filehandle, authorisation=authorisation, encrypt_key=public_key, par=par, identifiers=identifiers) if filemeta is not None: return_value["filemeta"] = filemeta.to_data() if par is not None: return_value["upload_par"] = par.to_data() return return_value
def run(args): """This function will allow a user to request a new session that will be validated by the passed public key and public signing certificate. This will return a URL that the user must connect to to then log in and validate that request. """ status = 0 message = None login_url = None login_uid = None user_uid = None username = args["username"] public_key = PublicKey.from_data(args["public_key"]) public_cert = PublicKey.from_data(args["public_certificate"]) ip_addr = None hostname = None login_message = None try: ip_addr = args["ipaddr"] except: pass try: hostname = args["hostname"] except: pass try: login_message = args["message"] except: pass # generate a sanitised version of the username user_account = UserAccount(username) # Now generate a login session for this request login_session = LoginSession(public_key, public_cert, ip_addr, hostname, login_message) # now log into the central identity account to record # that a request to open a login session has been opened bucket = login_to_service_account() # first, make sure that the user exists... account_key = "accounts/%s" % user_account.sanitised_name() try: existing_data = ObjectStore.get_object_from_json(bucket, account_key) except: existing_data = None if existing_data is None: raise InvalidLoginError("There is no user with name '%s'" % username) user_account = UserAccount.from_data(existing_data) user_uid = user_account.uid() # first, make sure that the user doens't have too many open # login sessions at once - this prevents denial of service user_session_root = "sessions/%s" % user_account.sanitised_name() open_sessions = ObjectStore.get_all_object_names(bucket, user_session_root) # take the opportunity to prune old user login sessions prune_expired_sessions(bucket, user_account, user_session_root, open_sessions) # this is the key for the session in the object store user_session_key = "%s/%s" % (user_session_root, login_session.uuid()) ObjectStore.set_object_from_json(bucket, user_session_key, login_session.to_data()) # we will record a pointer to the request using the short # UUID. This way we can give a simple URL. If there is a clash, # then we will use the username provided at login to find the # correct request from a much smaller pool (likely < 3) request_key = "requests/%s/%s" % (login_session.short_uuid(), login_session.uuid()) ObjectStore.set_string_object(bucket, request_key, user_account.name()) status = 0 # the login URL is the URL of this identity service plus the # short UID of the session login_url = "%s/s?id=%s" % (get_service_info().service_url(), login_session.short_uuid()) login_uid = login_session.uuid() message = "Success: Login via %s" % login_url return_value = create_return_value(status, message) if login_uid: return_value["session_uid"] = login_uid if login_url: return_value["login_url"] = login_url else: return_value["login_url"] = None if user_uid: return_value["user_uid"] = user_uid return return_value