def _try_dbus_auth(self, restaurant_identifier: str) -> Optional[bytes]: eprint("Trying D-Bus Secret Service") try: with secretstorage.dbus_init() as connection: collection = secretstorage.get_default_collection(connection) attributes = { "application": "Scone", "restaurant": restaurant_identifier, } items = list(collection.search_items(attributes)) if items: eprint("Found secret sauce for this Restaurant, unlocking…") items[0].unlock() return URLSafeBase64Encoder.decode(items[0].get_secret()) else: eprint("Did not find secret sauce for this Restaurant.") eprint("Enter it and I will try and store it...") secret = self._try_manual_entry() if secret is not None: collection.create_item( f"scone({restaurant_identifier}): secret sauce", attributes, URLSafeBase64Encoder.encode(secret), ) return secret return None except EOFError: # XXX what happens with no D-Bus return None
def recv_msg(self, user, packet): ''' INPUT * user : user message is from * packet : encrypted message received OUTPUT * Decrypted message with appropriate ''' try: dec_packet = encoder.decode(packet) self.send_ratc[user] = PublicKey(dec_packet[:32]) mac_packet = dec_packet[32:64] enc_msg = dec_packet[64:] dh_sec = get_DH_secret(self.recv_ratc[user], self.send_ratc[user]) salt = hashlib.md5(dh_sec).digest() self.root_keys[user] = kdf(self.root_keys[user], slt=salt) box = SecretBox(self.root_keys[user]) msg = box.decrypt(enc_msg) mac = hashlib.pbkdf2_hmac('sha256', msg, self.ad_number[user], 1414) if mac == mac_packet: return msg else: raise Exception('VERIFICATION FAILED: Connection to [' + user + '] aborted.') except Exception as e: print '[SALSAJAR: recv_msg] ' + str(e)
def generate_new(self): eprint("Generating a new freezer key...") self.key = nacl.utils.random(SecretBox.KEY_SIZE) key_b64 = URLSafeBase64Encoder.encode(self.key) eprint("Your new key is: " + key_b64.decode()) eprint("Pretty please store it in a safe place!") if not self.restaurant_identifier: eprint("No RI; not saving to SS") return eprint("Attempting to save it to the secret service...") eprint("(save it yourself anyway!)") with secretstorage.dbus_init() as connection: collection = secretstorage.get_default_collection(connection) attributes = { "application": "Scone", "restaurant": self.restaurant_identifier, } items = list(collection.search_items(attributes)) if items: eprint( "Found secret sauce for this Restaurant already!" " Will not overwrite." ) else: eprint("Storing secret sauce for this Restaurant...") collection.create_item( f"scone({self.restaurant_identifier}): secret sauce", attributes, key_b64, ) eprint("OK!")
def _try_manual_entry(self) -> Optional[bytes]: eprint("Manual entry required. Enter password for this restaurant: ", end="") key = URLSafeBase64Encoder.decode(input().encode()) if len(key) != SecretBox.KEY_SIZE: eprint("Wrong size!") return None else: return key
def get_follow_params(self, user): ''' INPUT * user : user that client is performing x3dh with OUTPUT * Saves used keys to dictionary specific to 'user' so that keys can be used again * generates new ephemeral keys for new requests * Returns keys necessary for x3dh_follow by user ''' tmp_pub = self.ek_pub tmp_priv = self.ek_priv self.used_keys[user] = tmp_priv self.ek_priv, self.ek_pub = gen_DH() return encoder.encode(self.ik_pub + tmp_pub)
def get_init_params(self, user): ''' INPUT * user : user that server is performing x3dh with OUTPUT * Saves used keys to dictionary specific to 'user' so that keys can be used again * generates new one-time pre-keys for new requests * Returns keys necessary for x3dh_init by user ''' tmp_pub = self.opk_pub tmp_priv = self.opk_priv self.used_keys[user] = tmp_priv self.opk_priv, self.opk_pub = gen_DH() return encoder.encode(self.sign_key.verify_key.encode() + self.ik_pub + self.spk_pub + tmp_pub)
def create_msg(self, user, msg): ''' INPUT * user : user message is being sent to * msg : unencrypted message being sent OUTPUT * Encoded(DH_pub key + hmac(MtE) + Encrypted(msg)) ''' self.recv_ratc[user], pub_key = gen_DH() dh_sec = get_DH_secret(self.recv_ratc[user], self.send_ratc[user]) salt = hashlib.md5(dh_sec).digest() self.root_keys[user] = kdf(self.root_keys[user], slt=salt) box = SecretBox(self.root_keys[user]) enc_msg = box.encrypt(msg) mac = hashlib.pbkdf2_hmac('sha256', msg, self.ad_number[user], 1414) return encoder.encode(pub_key + mac + enc_msg)
def close(self, password, filename): salt = os.urandom(32) key = hashlib.pbkdf2_hmac('sha256', password, salt, 1414) out_filename = filename + '.LOCK' box = SecretBox(key) try: with open(filename, 'rb') as infile: content = infile.read() enc_content = box.encrypt(content) with open(out_filename, 'wb') as outfile: outfile.write(encoder.encode(salt + enc_content)) outfile.close() except Exception as e: if hasattr(e, 'message'): print e.message() else: print e
def open(self, password, filename): if '.LOCK' == filename[len(filename) - 5:]: out_filename = filename[:len(filename) - 5] + '.UNLOCK' else: out_filename = filename + '.UNLOCK' try: with open(filename, 'rb') as infile: encoded_content = infile.read() decoded_content = encoder.decode(encoded_content) salt = decoded_content[:32] key = hashlib.pbkdf2_hmac('sha256', password, salt, 1414) box = SecretBox(key) dec_content = box.decrypt(decoded_content[32:]) with open(out_filename, 'wb') as outfile: outfile.write(dec_content) outfile.close() except Exception as e: if hasattr(e, 'message'): print e.message() else: print e
def renew_endpoint(): """ Renews a JWT that has not expired. """ if request.method == 'POST': if not app.config["JWT"]: return jsonify(message="JWT renewal is not enabled"), 501 request_json = request.get_json(force=True, cache=False) token = request_json.get('jwt', None) username = request_json.get('username', None) if token is None or username is None: return jsonify(message="No JWT or username provided"), 400 sanitized_username = str(escape(username)) if not validate_username(sanitized_username): structured_log(level='warning', msg="Invalid username provided", user=f"'{sanitized_username}'") return jsonify(message="Invalid username provided"), 400 try: claims = jwt.get_unverified_claims(token) except JWTError: return jsonify(message="Invalid JWT"), 400 try: subject = claims["sub"].encode('utf-8') salt = claims["x"].encode('utf-8') except KeyError: return jsonify(message="Invalid claims in JWT"), 401 if sanitized_username != claims["sub"]: return jsonify(message="Invalid subject in JWT claim"), 400 master_key = app.config["JWT_MASTER_KEY"] algorithm = app.config["JWT_ALGORITHM"] issuer = app.config['APP_NAME'] # jwt.decode() requires secret_key to be a str, so it must be decoded secret_key = blake2b(b'', key=master_key, salt=salt, person=subject).decode('utf-8') # exception is raised if token has expired, signature verification fails, etc. try: payload = jwt.decode(token=token, key=secret_key, algorithms=algorithm, issuer=issuer) except Exception as err: structured_log(level='info', msg="Failed to renew JWT", error=err) return jsonify(message="Failed to renew invalid JWT"), 401 issue_time = time.time() expiry_time = issue_time + app.config['JWT_VALIDITY_PERIOD'] salt = URLSafeBase64Encoder.encode(nacl.utils.random(12)) payload['iat'] = issue_time payload['exp'] = expiry_time payload['x'] = salt.decode('utf-8') # compute a new secret key for the regenerated JWT new_secret_key = blake2b(b'', key=master_key, salt=salt, person=subject).decode('utf-8') new_token = jwt.encode(claims=payload, key=new_secret_key, algorithm=algorithm) statsd.client.incr("jwt_renewed") structured_log(level='info', msg="JWT successfully renewed", user="******".format(sanitized_username)) return jsonify(message="JWT successfully renewed", jwt=new_token), 200
def auth_endpoint(): """ Authenticates users using PAM. If authentication is successful, returns a list of groups the user is a member of. Returns a short-lived JSON Web Token if JWT_MASTER_KEY is set. """ if request.method == 'POST': request_json = request.get_json(force=True, cache=False) username = request_json.get('username', None) password = request_json.get('password', None) if username is None or password is None: return jsonify(message="No username or password provided"), 400 sanitized_username = str(escape(username)) if not validate_username(sanitized_username): structured_log(level='warning', msg="Invalid username provided", user=f"'{sanitized_username}'") return jsonify(message="Invalid username provided"), 400 pam_service = app.config['PAM_SERVICE'] with statsd.client.timer("pam_auth"): if pam().authenticate(sanitized_username, password, service=pam_service): authenticated = True auth_message = "Authentication successful" statsd.client.incr("auth_success") else: authenticated = False auth_message = "Authentication failed" statsd.client.incr("auth_failed") structured_log(level='info', msg=auth_message, user=f"'{sanitized_username}'") if authenticated: groups = get_group_membership(sanitized_username) token = None if app.config["JWT"]: issuer = app.config['APP_NAME'] issue_time = time.time() expiry_time = issue_time + app.config['JWT_VALIDITY_PERIOD'] subject = sanitized_username.encode('utf-8') # generate a unique salt for each JWT, needs to be encoded to include as a claim salt = URLSafeBase64Encoder.encode(nacl.utils.random(12)) claims = { "iss": issuer, "iat": issue_time, "exp": expiry_time, "sub": sanitized_username, "groups": groups, "x": salt.decode('utf-8') } master_key = app.config["JWT_MASTER_KEY"] algorithm = app.config["JWT_ALGORITHM"] # generate a unique secret key for each JWT secret_key = blake2b(b'', key=master_key, salt=salt, person=subject).decode('utf-8') token = jwt.encode(claims=claims, key=secret_key, algorithm=algorithm) statsd.client.incr("jwt_generated") return jsonify(message=f"{auth_message}", auth=True, groups=groups, jwt=token), 200 else: return jsonify(message=f"{auth_message}", auth=False), 401
def base64url_decode(string): return URLSafeBase64Encoder.decode(fix_base64url_decode(string))
def base64url_encode(data): return fix_base64url_encode(URLSafeBase64Encoder.encode(data))