def test_compares(self): assert constant_time.bytes_eq(b"foo", b"foo") is True assert constant_time.bytes_eq(b"foo", b"bar") is False assert constant_time.bytes_eq(b"foobar", b"foo") is False assert constant_time.bytes_eq(b"foo", b"foobar") is False
def decrypt(data: bytes, privkey: datatypes.PrivateKey, shared_mac_data: bytes = b'') -> bytes: """Decrypt data with ECIES method using the given private key 1) generate shared-secret = kdf( ecdhAgree(myPrivKey, msg[1:65]) ) 2) verify tag 3) decrypt ecdhAgree(r, recipientPublic) == ecdhAgree(recipientPrivate, R) [where R = r*G, and recipientPublic = recipientPrivate*G] """ if data[:1] != b'\x04': raise DecryptionError("wrong ecies header") # 1) generate shared-secret = kdf( ecdhAgree(myPrivKey, msg[1:65]) ) shared = data[1:1 + PUBKEY_LEN] key_material = ecdh_agree(privkey, keys.PublicKey(shared)) key = kdf(key_material) key_enc, key_mac = key[:KEY_LEN // 2], key[KEY_LEN // 2:] key_mac = sha256(key_mac).digest() tag = data[-KEY_LEN:] # 2) Verify tag expected_tag = hmac_sha256(key_mac, data[1 + PUBKEY_LEN:- KEY_LEN] + shared_mac_data) if not bytes_eq(expected_tag, tag): raise DecryptionError("Failed to verify tag") # 3) Decrypt algo = CIPHER(key_enc) blocksize = algo.block_size // 8 iv = data[1 + PUBKEY_LEN:1 + PUBKEY_LEN + blocksize] ciphertext = data[1 + PUBKEY_LEN + blocksize:- KEY_LEN] ctx = Cipher(algo, MODE(iv), default_backend()).decryptor() return ctx.update(ciphertext) + ctx.finalize()
def decrypt(self, k, a, iv, e, t): """ Decrypt according to the selected encryption and hashing functions. :param k: Encryption key (optional) :param a: Additional Authenticated Data :param iv: Initialization Vector :param e: Ciphertext :param t: Authentication Tag Returns plaintext or raises an error """ hkey = k[:self.keysize] dkey = k[self.keysize:] # verify mac if not constant_time.bytes_eq(t, self._mac(hkey, a, iv, e)): raise InvalidJWEData('Failed to verify MAC') # decrypt cipher = Cipher(algorithms.AES(dkey), modes.CBC(iv), backend=self.backend) decryptor = cipher.decryptor() d = decryptor.update(e) + decryptor.finalize() unpadder = PKCS7(self.blocksize).unpadder() return unpadder.update(d) + unpadder.finalize()
def decrypt(self, data, associated_data=b""): decoded_data = base64.urlsafe_b64decode(data) mac = decoded_data[-16:] iv = decoded_data[0:16] cipher_text = decoded_data[16:-16] associated_data_length = struct.pack(">Q", len(associated_data) * 8) h = hmac.HMAC(self.mac_key, hashes.SHA256(), self.backend) h.update(associated_data) h.update(iv) h.update(cipher_text) h.update(associated_data_length) if not constant_time.bytes_eq(mac, h.finalize()[:16]): raise ValueError("data provided has an invalid signature.") cipher = Cipher( algorithms.AES(self.encryption_key), modes.CBC(iv), self.backend ) decryptor = cipher.decryptor() plain_text = decryptor.update(cipher_text) + decryptor.finalize() unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder() unpadded_data = unpadder.update(plain_text) + unpadder.finalize() return unpadded_data
def parse_endpoint(self, token, version="v0", public_key=None): """Parse an endpoint into component elements of UAID, CHID and optional key hash if v2 :param token: The obscured subscription data. :param version: This is the API version of the token. :param public_key: the public key (from Encryption-Key: p256ecdsa=) :raises ValueError: In the case of a malformed endpoint. :returns: a tuple containing the (UAID, CHID) """ token = self.fernet.decrypt(token.encode('utf8')) if version == 'v0': if not VALID_V0_TOKEN.match(token): raise InvalidTokenException("Corrupted push token") return tuple(token.split(':')) if version == 'v1' and len(token) != 32: raise InvalidTokenException("Corrupted push token") if version == 'v2': if len(token) != 64: raise InvalidTokenException("Corrupted push token") if not public_key: raise InvalidTokenException("Invalid key data") if not constant_time.bytes_eq(sha256(public_key).digest(), token[32:]): raise InvalidTokenException("Key mismatch") return (token[:16].encode('hex'), token[16:32].encode('hex'))
def handle(self, request): name = request['headers'].get(self.id_header, None) key = request['headers'].get(self.key_header, None) if name is None and key is None: self.logger.debug('Ignoring request no relevant headers provided') return None validated = False try: val = self.store.get(self._db_key(name)) if val is None: raise ValueError("No such ID") if constant_time.bytes_eq(val.encode('utf-8'), key.encode('utf-8')): validated = True except Exception: # pylint: disable=broad-except self.audit_svc_access(log.AUDIT_SVC_AUTH_FAIL, request['client_id'], name) return False if validated: self.audit_svc_access(log.AUDIT_SVC_AUTH_PASS, request['client_id'], name) request['remote_user'] = name return True self.audit_svc_access(log.AUDIT_SVC_AUTH_FAIL, request['client_id'], name) return False
def constant_time_compare(val1, val2): """ :type val1: any :type val2: any :rtype: bool """ return constant_time.bytes_eq(force_bytes(val1), force_bytes(val2))
def aes_key_unwrap(wrapping_key, wrapped_key, backend): if len(wrapped_key) < 24: raise ValueError("Must be at least 24 bytes") if len(wrapped_key) % 8 != 0: raise ValueError("The wrapped key must be a multiple of 8 bytes") if len(wrapping_key) not in [16, 24, 32]: raise ValueError("The wrapping key must be a valid AES key length") # Implement RFC 3394 Key Unwrap - 2.2.2 (index method) decryptor = Cipher(AES(wrapping_key), ECB(), backend).decryptor() aiv = b"\xa6\xa6\xa6\xa6\xa6\xa6\xa6\xa6" r = [wrapped_key[i:i + 8] for i in range(0, len(wrapped_key), 8)] a = r.pop(0) n = len(r) for j in reversed(range(6)): for i in reversed(range(n)): # pack/unpack are safe as these are always 64-bit chunks atr = struct.pack( ">Q", struct.unpack(">Q", a)[0] ^ ((n * j) + i + 1) ) + r[i] # every decryption operation is a discrete 16 byte chunk so # it is safe to reuse the decryptor for the entire operation b = decryptor.update(atr) a = b[:8] r[i] = b[-8:] assert decryptor.finalize() == b"" if not bytes_eq(a, aiv): raise InvalidUnwrap() return b"".join(r)
def decrypt(self, key, alg): # iv, ivl2, pt = super(IntegrityProtectedSKEDataV1, self).decrypt(key, alg) pt = _decrypt(bytes(self.ct), bytes(key), alg) # do the MDC checks _expected_mdcbytes = b'\xd3\x14' + hashlib.new('SHA1', pt[:-20]).digest() if not constant_time.bytes_eq(bytes(pt[-22:]), _expected_mdcbytes): raise PGPDecryptionError("Decryption failed") # pragma: no cover iv = bytes(pt[:alg.block_size // 8]) del pt[:alg.block_size // 8] ivl2 = bytes(pt[:2]) del pt[:2] if not constant_time.bytes_eq(iv[-2:], ivl2): raise PGPDecryptionError("Decryption failed") # pragma: no cover return pt
def aes_key_unwrap_with_padding(wrapping_key: bytes, wrapped_key: bytes, backend=None) -> bytes: backend = _get_backend(backend) if len(wrapped_key) < 16: raise InvalidUnwrap("Must be at least 16 bytes") if len(wrapping_key) not in [16, 24, 32]: raise ValueError("The wrapping key must be a valid AES key length") if len(wrapped_key) == 16: # RFC 5649 - 4.2 - exactly two 64-bit blocks decryptor = Cipher(AES(wrapping_key), ECB(), backend).decryptor() b = decryptor.update(wrapped_key) assert decryptor.finalize() == b"" a = b[:8] data = b[8:] n = 1 else: r = [wrapped_key[i:i + 8] for i in range(0, len(wrapped_key), 8)] encrypted_aiv = r.pop(0) n = len(r) a, r = _unwrap_core(wrapping_key, encrypted_aiv, r, backend) data = b"".join(r) # 1) Check that MSB(32,A) = A65959A6. # 2) Check that 8*(n-1) < LSB(32,A) <= 8*n. If so, let # MLI = LSB(32,A). # 3) Let b = (8*n)-MLI, and then check that the rightmost b octets of # the output data are zero. (mli, ) = struct.unpack(">I", a[4:]) b = (8 * n) - mli if (not bytes_eq(a[:4], b"\xa6\x59\x59\xa6") or not 8 * (n - 1) < mli <= 8 * n or (b != 0 and not bytes_eq(data[-b:], b"\x00" * b))): raise InvalidUnwrap() if b == 0: return data else: return data[:-b]
def aes_key_unwrap_with_padding(wrapping_key, wrapped_key, backend): if len(wrapped_key) < 16: raise ValueError("Must be at least 16 bytes") if len(wrapping_key) not in [16, 24, 32]: raise ValueError("The wrapping key must be a valid AES key length") if len(wrapped_key) == 16: # RFC 5649 - 4.2 - exactly two 64-bit blocks decryptor = Cipher(AES(wrapping_key), ECB(), backend).decryptor() b = decryptor.update(wrapped_key) assert decryptor.finalize() == b"" a = b[:8] data = b[8:] n = 1 else: r = [wrapped_key[i:i + 8] for i in range(0, len(wrapped_key), 8)] encrypted_aiv = r.pop(0) n = len(r) a, r = _unwrap_core(wrapping_key, encrypted_aiv, r, backend) data = b"".join(r) # 1) Check that MSB(32,A) = A65959A6. # 2) Check that 8*(n-1) < LSB(32,A) <= 8*n. If so, let # MLI = LSB(32,A). # 3) Let b = (8*n)-MLI, and then check that the rightmost b octets of # the output data are zero. (mli,) = struct.unpack(">I", a[4:]) b = (8 * n) - mli if ( not bytes_eq(a[:4], b"\xa6\x59\x59\xa6") or not 8 * (n - 1) < mli <= 8 * n or ( b != 0 and not bytes_eq(data[-b:], b"\x00" * b) ) ): raise InvalidUnwrap() if b == 0: return data else: return data[:-b]
def decrypt(self, private_key, cipher_text): """Ecies decrypt cipher text. First restore the ephemeral public key from bytes(97 bytes for 384, 65 bytes for 256). Then derived a shared key based ecdh, using the key based hkdf to generate aes key and hmac key, using hmac-sha3 to verify the hmac bytes. Last using aes-256-cfb to decrypt the bytes. :param private_key: private key :param cipher_text: cipher text :return: plain text """ key_len = private_key.curve.key_size if key_len != self.curve.key_size: raise ValueError( "Invalid key. Input security level {} does not " "match the current security level {}".format( key_len, self.curve.key_size)) d_len = key_len >> 3 rb_len = ((key_len + 7) // 8) * 2 + 1 ct_len = len(cipher_text) if ct_len <= rb_len + d_len: raise ValueError( "Illegal cipherText length: cipher text length {} " "must be > rb length plus d_len {}".format(ct_len, rb_len + d_len) ) rb = cipher_text[:rb_len] em = cipher_text[rb_len:ct_len - d_len] d = cipher_text[ct_len - d_len:ct_len] ephemeral_public_key = EllipticCurvePublicNumbers \ .from_encoded_point(self.curve(), rb) \ .public_key(default_backend()) z = private_key.exchange(ec.ECDH(), ephemeral_public_key) hkdf_output = Hkdf(salt=None, input_key_material=z, hash=self.hash) \ .expand(length=AES_KEY_LENGTH + HMAC_KEY_LENGTH) aes_key = hkdf_output[:AES_KEY_LENGTH] hmac_key = hkdf_output[AES_KEY_LENGTH:AES_KEY_LENGTH + HMAC_KEY_LENGTH] mac = hmac.new(hmac_key, em, self.hash) recovered_d = mac.digest() if not constant_time.bytes_eq(recovered_d, d): raise ValueError("Hmac verify failed.") iv = em[:IV_LENGTH] aes_cipher = AES.new(key=aes_key, mode=AES.MODE_CFB, iv=iv) return aes_cipher.decrypt(em[IV_LENGTH:len(em)])
def validate(self, text): """ compare a given string to the code Performs a constant time comparison that is case insensitive. returns true when the given text matches the code """ expected = self.code.lower().encode("utf-8") actual = text.lower().encode("utf-8") return bytes_eq(expected, actual)
def decrypt(self, private_key, cipher_text): """Ecies decrypt cipher text. First restore the ephemeral public key from bytes(97 bytes for 384, 65 bytes for 256). Then derived a shared key based ecdh, using the key based hkdf to generate aes key and hmac key, using hmac-sha3 to verify the hmac bytes. Last using aes-256-cfb to decrypt the bytes. :param private_key: private key :param cipher_text: cipher text :Returns: plain text """ key_len = private_key.curve.key_size if key_len != self.curve.key_size: raise ValueError( "Invalid key. Input security level {} does not " "match the current security level {}".format( key_len, self.curve.key_size)) d_len = key_len >> 3 rb_len = ((key_len + 7) // 8) * 2 + 1 ct_len = len(cipher_text) if ct_len <= rb_len + d_len: raise ValueError( "Illegal cipherText length: cipher text length {} " "must be > rb length plus d_len {}".format(ct_len, rb_len + d_len) ) rb = cipher_text[:rb_len] em = cipher_text[rb_len:ct_len - d_len] d = cipher_text[ct_len - d_len:ct_len] ephemeral_public_key = EllipticCurvePublicNumbers \ .from_encoded_point(self.curve(), rb) \ .public_key(default_backend()) z = private_key.exchange(ec.ECDH(), ephemeral_public_key) hkdf_output = Hkdf(salt=None, input_key_material=z, hash=self.hash) \ .expand(length=AES_KEY_LENGTH + HMAC_KEY_LENGTH) aes_key = hkdf_output[:AES_KEY_LENGTH] hmac_key = hkdf_output[AES_KEY_LENGTH:AES_KEY_LENGTH + HMAC_KEY_LENGTH] mac = hmac.new(hmac_key, em, self.hash) recovered_d = mac.digest() if not constant_time.bytes_eq(recovered_d, d): raise ValueError("Hmac verify failed.") iv = em[:IV_LENGTH] aes_cipher = AES.new(key=aes_key, mode=AES.MODE_CFB, iv=iv) return aes_cipher.decrypt(em[IV_LENGTH:len(em)])
def register_complete(self, challenge, client_data, attestation_object): if client_data.get('type') != WEBAUTHN_TYPE.MAKE_CREDENTIAL: raise ValueError('Incorrect type in ClientData.') if not self._verify(client_data.get('origin')): raise ValueError('Invalid origin in ClientData.') if not constant_time.bytes_eq(challenge, client_data.challenge): raise ValueError('Wrong challenge in response.') if not constant_time.bytes_eq(sha256(self.rp['id'].encode()), attestation_object.auth_data.rp_id_hash): raise ValueError('Wrong RP ID hash in response.') if attestation_object.fmt == ATTESTATION.NONE \ and self.attestation != ATTESTATION.NONE: raise ValueError('Attestation required, but not provided.') attestation_object.verify(client_data.hash) if self.user_verification is USER_VERIFICATION.REQUIRED and \ not attestation_object.auth_data.is_user_verified(): raise ValueError( 'User verification required, but User verified flag not set.') return attestation_object.auth_data
def checkMessageSignature(self, message): """Given a message with a signature, calculate a new signature and return whether it matches the signature in the message. @raises ValueError: if the message has no signature or no signature can be calculated for it. """ message_sig = message.getArg(OPENID_NS, 'sig') if not message_sig: raise ValueError("%s has no sig." % (message,)) calculated_sig = self.getMessageSignature(message) return bytes_eq(calculated_sig.encode('utf-8'), message_sig.encode('utf-8'))
def decrypt(key, header, ciphertext): assert isinstance(key, bytes) assert len(key) == 32 assert isinstance(header, bytes) assert isinstance(ciphertext, bytes) if len(ciphertext) < 32: raise Exception tag = ciphertext[:32] payload = _stream_xor(key, tag, ciphertext[32:]) if not bytes_eq(tag, _auth(key, header, payload)): raise Exception return payload
def register_complete(self, state, client_data, attestation_object): """Verify the correctness of the registration data received from the client. :param state: The state data returned by the corresponding `register_begin`. :param client_data: The client data. :param attestation_object: The attestation object. :return: The authenticator data""" if client_data.get('type') != WEBAUTHN_TYPE.MAKE_CREDENTIAL: raise ValueError('Incorrect type in ClientData.') if not self._verify(client_data.get('origin')): raise ValueError('Invalid origin in ClientData.') if not constant_time.bytes_eq(websafe_decode(state['challenge']), client_data.challenge): raise ValueError('Wrong challenge in response.') if not constant_time.bytes_eq(self.rp.id_hash, attestation_object.auth_data.rp_id_hash): raise ValueError('Wrong RP ID hash in response.') if state['user_verification'] is USER_VERIFICATION.REQUIRED and \ not attestation_object.auth_data.is_user_verified(): raise ValueError( 'User verification required, but User verified flag not set.') if self.attestation != ATTESTATION.NONE: att_verifier = UnsupportedAttestation() for at in self._attestation_types: if getattr(at, 'FORMAT', None) == attestation_object.fmt: att_verifier = at break # An unsupported format causes an exception to be thrown, which # includes the auth_data. The caller may choose to handle this case # and allow the registration. att_verifier.verify(attestation_object.att_statement, attestation_object.auth_data, client_data.hash) # We simply ignore attestation if self.attestation == 'none', as not all # clients strip the attestation. return attestation_object.auth_data
def finalize(self): tag_size = self._cipher.block_size // 8 tag_buf = self._backend._ffi.new("unsigned char[]", tag_size) tag_len = self._backend._ffi.new("size_t *", tag_size) res = backend._lib.CCCryptorGCMFinal(self._ctx[0], tag_buf, tag_len) self._backend._check_response(res) _release_cipher_ctx(self._ctx) self._tag = self._backend._ffi.buffer(tag_buf)[:] if (self._operation == self._backend._lib.kCCDecrypt and not constant_time.bytes_eq(self._tag[:len(self._mode.tag)], self._mode.tag)): raise InvalidTag return b""
def check_xsrf_cookie(self): """ Override needed to change name of header name """ token = self.request.headers.get("X-XSRF-TOKEN") if not token: token = self.get_argument('xsrf-token', default=None) if not token: raise HTTPError(403, "X-XSRF-TOKEN argument missing from POST") # This is a constant time comparison provided by cryptography package if not bytes_eq(self.xsrf_token.encode('utf-8'), token.encode('utf-8')): raise HTTPError(403, "XSRF cookie does not match POST argument")
def finalize(self): tag_size = self._cipher.block_size // 8 tag_buf = self._backend._ffi.new("unsigned char[]", tag_size) tag_len = self._backend._ffi.new("size_t *", tag_size) res = backend._lib.CCCryptorGCMFinal(self._ctx[0], tag_buf, tag_len) self._backend._check_response(res) _release_cipher_ctx(self._ctx) self._tag = self._backend._ffi.buffer(tag_buf)[:] if self._operation == self._backend._lib.kCCDecrypt and not constant_time.bytes_eq( self._tag[: len(self._mode.tag)], self._mode.tag ): raise InvalidTag return b""
def decrypt(self, key, alg): # pragma: no cover pt = _decrypt(bytes(self.ct), bytes(key), alg) iv = bytes(pt[:alg.block_size // 8]) del pt[:alg.block_size // 8] ivl2 = bytes(pt[:2]) del pt[:2] if not constant_time.bytes_eq(iv[-2:], ivl2): raise PGPDecryptionError("Decryption failed") return pt
def _open_aes_ctr(key, nonce, ciphertext, expected_hmac, digest_method): data_key, hmac_key = _halve_key(key) hmac = _get_hmac(hmac_key, ciphertext, digest_method) # Check the HMAC before we decrypt to verify ciphertext integrity if not constant_time.bytes_eq(hmac, expected_hmac): raise IntegrityError("Computed HMAC on %s does not match stored HMAC") decryptor = Cipher( algorithms.AES(data_key), modes.CTR(nonce), backend=default_backend() ).decryptor() return decryptor.update(ciphertext) + decryptor.finalize()
def decrypt_header(self, data: bytes) -> bytes: if len(data) != HEADER_LEN + MAC_LEN: raise ValueError("Unexpected header length: {}".format(len(data))) header_ciphertext = data[:HEADER_LEN] header_mac = data[HEADER_LEN:] mac_secret = self.ingress_mac.digest()[:HEADER_LEN] aes = self.mac_enc(mac_secret)[:HEADER_LEN] self.ingress_mac.update(sxor(aes, header_ciphertext)) expected_header_mac = self.ingress_mac.digest()[:HEADER_LEN] if not bytes_eq(expected_header_mac, header_mac): raise AuthenticationError('Invalid header mac') return self.aes_dec.update(header_ciphertext)
def decrypt_header(self, data: bytes) -> bytes: if len(data) != HEADER_LEN + MAC_LEN: raise ValueError("Unexpected header length: {}".format(len(data))) header_ciphertext = data[:HEADER_LEN] header_mac = data[HEADER_LEN:] mac_secret = self.ingress_mac.digest()[:HEADER_LEN] aes = self.mac_enc(mac_secret)[:HEADER_LEN] self.ingress_mac.update(sxor(aes, header_ciphertext)) expected_header_mac = self.ingress_mac.digest()[:HEADER_LEN] if not bytes_eq(expected_header_mac, header_mac): raise DecryptionError('Invalid header mac') return self.aes_dec.update(header_ciphertext)
def check_password(password, salt, password_hash): """ @param password: the user provided password @param salt: The salt to be used for password hashing @param password_hash: the expected hash @return: the scrypt hash in base64 of the new password """ if isinstance(password_hash, text_type): password_hash = password_hash.encode() return constant_time.bytes_eq(hash_password(password, salt), password_hash)
def jenkins_ci_notification(repo, pagure_ci_token, username=None, namespace=None): """ Jenkins Build Notification -------------------------- At the end of a build on Jenkins, this URL is used (if the project is rightly configured) to flag a pull-request with the result of the build. :: POST /api/0/ci/jenkins/<repo>/<token>/build-finished """ project = pagure.lib.get_project(SESSION, repo, user=username, namespace=namespace) if repo is None: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) if not constant_time.bytes_eq(to_bytes(pagure_ci_token), to_bytes(project.ci_hook.pagure_ci_token)): raise pagure.exceptions.APIError(401, error_code=APIERROR.EINVALIDTOK) data = flask.request.get_json() if not data: APP.logger.debug("Bad Request: No JSON retrieved") raise pagure.exceptions.APIError(400, error_code=APIERROR.EINVALIDREQ) build_id = data.get('build', {}).get('number') if not build_id: APP.logger.debug("Bad Request: No build ID retrieved") raise pagure.exceptions.APIError(400, error_code=APIERROR.EINVALIDREQ) try: lib_ci.process_jenkins_build( SESSION, project, build_id, requestfolder=APP.config['REQUESTS_FOLDER']) except pagure.exceptions.PagureException as err: APP.logger.error('Error processing jenkins notification', exc_info=err) raise pagure.exceptions.APIError(400, error_code=APIERROR.ENOCODE, error=str(err)) APP.logger.info('Successfully proccessed jenkins notification') return ('', 204)
def _check_mac(shared_secret, timestamp, nonce, body, message_hmac, http_verb=None, http_resource_uri=None, headers=None): """ Computes HMAC and compares expected and obtained values. Performs constant time comparison. Raises AuthenticationException. """ # compute the expected hmac expected_hmac = _compute_mac(shared_secret, timestamp, nonce, body, http_verb=http_verb, http_resource_uri=http_resource_uri, headers=headers) # constant time check of expected againstreceived hmac if not constant_time.bytes_eq(to_utf8(message_hmac), expected_hmac): raise AuthenticationException('signature header value does not match computed value')
def _verify_challenge(received_challenge, sent_challenge): if not isinstance(received_challenge, six.string_types): return False if not isinstance(sent_challenge, six.string_types): return False if not received_challenge: return False if not sent_challenge: return False if not constant_time.bytes_eq(bytes(sent_challenge, encoding='utf-8'), bytes(received_challenge, encoding='utf-8')): return False return True
def test_reject_unicode(self): with pytest.raises(TypeError): constant_time.bytes_eq(b"foo", "foo") with pytest.raises(TypeError): constant_time.bytes_eq("foo", b"foo") with pytest.raises(TypeError): constant_time.bytes_eq("foo", "foo")
def register_complete(self, state, client_data, attestation_object): """Verify the correctness of the registration data received from the client. :param state: The state data returned by the corresponding `register_begin`. :param client_data: The client data. :param attestation_object: The attestation object. :return: The authenticator data""" if client_data.get('type') != WEBAUTHN_TYPE.MAKE_CREDENTIAL: raise ValueError('Incorrect type in ClientData.') if not self._verify(client_data.get('origin')): raise ValueError('Invalid origin in ClientData.') if not constant_time.bytes_eq(state['challenge'], client_data.challenge): raise ValueError('Wrong challenge in response.') if not constant_time.bytes_eq(self.rp.id_hash, attestation_object.auth_data.rp_id_hash): raise ValueError('Wrong RP ID hash in response.') if attestation_object.fmt == ATTESTATION.NONE \ and self.attestation != ATTESTATION.NONE: raise ValueError('Attestation required, but not provided.') for at in self._attestation_types: if getattr(at, 'FORMAT', None) == attestation_object.fmt: at.verify(attestation_object.att_statement, attestation_object.auth_data, client_data.hash) break else: raise ValueError('Unsupported attestation type: %s' % attestation_object.fmt) if state['user_verification'] is USER_VERIFICATION.REQUIRED and \ not attestation_object.auth_data.is_user_verified(): raise ValueError( 'User verification required, but User verified flag not set.') return attestation_object.auth_data
def test_reject_unicode(self): with pytest.raises(TypeError): constant_time.bytes_eq(b"foo", "foo") # type: ignore[arg-type] with pytest.raises(TypeError): constant_time.bytes_eq("foo", b"foo") # type: ignore[arg-type] with pytest.raises(TypeError): constant_time.bytes_eq("foo", "foo") # type: ignore[arg-type]
def test_reject_unicode(self): with pytest.raises(TypeError): constant_time.bytes_eq(b"foo", six.u("foo")) with pytest.raises(TypeError): constant_time.bytes_eq(six.u("foo"), b"foo") with pytest.raises(TypeError): constant_time.bytes_eq(six.u("foo"), six.u("foo"))
def parse_endpoint(self, token, version="v1", ckey_header=None, auth_header=None): """Parse an endpoint into component elements of UAID, CHID and optional key hash if v2 :param token: The obscured subscription data. :param version: This is the API version of the token. :param ckey_header: the Crypto-Key header bearing the public key (from Crypto-Key: p256ecdsa=) :param auth_header: The Authorization header bearing the VAPID info :raises ValueError: In the case of a malformed endpoint. :returns: a dict containing (uaid=UAID, chid=CHID, public_key=KEY) """ token = self.fernet.decrypt(repad(token).encode('utf8')) public_key = None if ckey_header: try: crypto_key = CryptoKey(ckey_header) except CryptoKeyException: raise InvalidTokenException("Invalid key data") public_key = crypto_key.get_label('p256ecdsa') if version == 'v1' and len(token) != 32: raise InvalidTokenException("Corrupted push token") if version == 'v2': if not auth_header: raise VapidAuthException("Missing Authorization Header") if len(token) != 64: raise InvalidTokenException("Corrupted push token") if not public_key: raise VapidAuthException("Invalid key data") try: decoded_key = base64url_decode(public_key) except TypeError: raise VapidAuthException("Invalid key data") if not constant_time.bytes_eq( sha256(decoded_key).digest(), token[32:]): raise VapidAuthException("Key mismatch") return dict(uaid=token[:16].encode('hex'), chid=token[16:32].encode('hex'), version=version, public_key=public_key)
def validate(self, previous_entry: "LogEntry") -> bool: """Validate the hash of a single log entry. Validates the hash of this entry with regard to the previous entry's hash. The previous entry is the LogEntry with the previous number, previous_entry.number == self.number - 1 :param previous_entry: The previous log entry to validate against. :return: True if the digest is correct, False if not. """ if (self.number - previous_entry.number) & 0xFFFF != 1: raise ValueError("previous_entry has wrong number!") digest = sha256(self.data + previous_entry.digest).digest()[:16] return constant_time.bytes_eq(self.digest, digest)
def get_api_session(self): token = '' if b'x-api-token' in self.request.headers: token = bytes(self.request.headers[b'x-api-token']) # Assert the input is okay and the api_token state is acceptable if self.request.tid != 1 or \ self.state.api_token_session is None or \ not self.state.tenant_cache[self.request.tid].admin_api_token_digest: return stored_token_hash = self.state.tenant_cache[ self.request.tid].admin_api_token_digest.encode() if constant_time.bytes_eq(sha256(token), stored_token_hash): return self.state.api_token_session
def _check_mac(shared_secret, timestamp, nonce, body, message_hmac, http_verb=None, http_resource_uri=None, headers=None): """ Computes HMAC and compares expected and obtained values. Performs constant time comparison. Raises AuthenticationException. """ # compute the expected hmac expected_hmac = _compute_mac(shared_secret, timestamp, nonce, body, http_verb=http_verb, http_resource_uri=http_resource_uri, headers=headers) # constant time check of expected againstreceived hmac if not constant_time.bytes_eq(to_binary(message_hmac), expected_hmac): raise AuthenticationException('signature header value does not match computed value')
def decrypt_body(self, data: bytes, body_size: int) -> bytes: read_size = roundup_16(body_size) if len(data) < read_size + MAC_LEN: raise ValueError('Insufficient body length; Got {}, wanted {}'.format( len(data), (read_size + MAC_LEN))) frame_ciphertext = data[:read_size] frame_mac = data[read_size:read_size + MAC_LEN] self.ingress_mac.update(frame_ciphertext) fmac_seed = self.ingress_mac.digest()[:MAC_LEN] self.ingress_mac.update(sxor(self.mac_enc(fmac_seed), fmac_seed)) expected_frame_mac = self.ingress_mac.digest()[:MAC_LEN] if not bytes_eq(expected_frame_mac, frame_mac): raise DecryptionError('Invalid frame mac') return self.aes_dec.update(frame_ciphertext)[:body_size]
def decrypt_body(self, data: bytes, body_size: int) -> bytes: read_size = roundup_16(body_size) if len(data) < read_size + MAC_LEN: raise ValueError('Insufficient body length; Got {}, wanted {}'.format( len(data), (read_size + MAC_LEN))) frame_ciphertext = data[:read_size] frame_mac = data[read_size:read_size + MAC_LEN] self.ingress_mac.update(frame_ciphertext) fmac_seed = self.ingress_mac.digest()[:MAC_LEN] self.ingress_mac.update(sxor(self.mac_enc(fmac_seed), fmac_seed)) expected_frame_mac = self.ingress_mac.digest()[:MAC_LEN] if not bytes_eq(expected_frame_mac, frame_mac): raise AuthenticationError('Invalid frame mac') return self.aes_dec.update(frame_ciphertext)[:body_size]
def ValidateCSRFTokenOrRaise(request): """Decorator for WSGI handler that checks CSRF cookie against the request.""" # CSRF check doesn't make sense for GET/HEAD methods, because they can # (and are) used when downloading files through <a href> links - and # there's no way to set X-CSRFToken header in this case. if request.method in ("GET", "HEAD"): return # In the ideal world only JavaScript can be used to add a custom header, and # only within its origin. By default, browsers don't allow JavaScript to # make cross origin requests. # # Unfortunately, in the real world due to bugs in browsers plugins, it can't # be guaranteed that a page won't set an HTTP request with a custom header # set. That's why we also check the contents of a header via an HMAC check # with a server-stored secret. # # See for more details: # https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet # (Protecting REST Services: Use of Custom Request Headers). csrf_token = utils.SmartStr(request.headers.get("X-CSRFToken", "")) if not csrf_token: logging.info("Did not find headers CSRF token for: %s", request.path) raise werkzeug_exceptions.Forbidden("CSRF token is missing") try: decoded = base64.urlsafe_b64decode(csrf_token + "==") digest, token_time = decoded.rsplit(CSRF_DELIMITER, 1) token_time = long(token_time) except (TypeError, ValueError): logging.info("Malformed CSRF token for: %s", request.path) raise werkzeug_exceptions.Forbidden("Malformed CSRF token") if len(digest) != hashlib.sha256().digest_size: logging.info("Invalid digest size for: %s", request.path) raise werkzeug_exceptions.Forbidden("Malformed CSRF token digest") expected = GenerateCSRFToken(request.user, token_time) if not constant_time.bytes_eq(csrf_token, expected): logging.info("Non-matching CSRF token for: %s", request.path) raise werkzeug_exceptions.Forbidden("Non-matching CSRF token") current_time = rdfvalue.RDFDatetime.Now().AsMicrosecondsSinceEpoch() if current_time - token_time > CSRF_TOKEN_DURATION.microseconds: logging.info("Expired CSRF token for: %s", request.path) raise werkzeug_exceptions.Forbidden("Expired CSRF token")
def ValidateCSRFTokenOrRaise(request): """Decorator for WSGI handler that checks CSRF cookie against the request.""" # CSRF check doesn't make sense for GET/HEAD methods, because they can # (and are) used when downloading files through <a href> links - and # there's no way to set X-CSRFToken header in this case. if request.method in ("GET", "HEAD"): return # In the ideal world only JavaScript can be used to add a custom header, and # only within its origin. By default, browsers don't allow JavaScript to # make cross origin requests. # # Unfortunately, in the real world due to bugs in browsers plugins, it can't # be guaranteed that a page won't set an HTTP request with a custom header # set. That's why we also check the contents of a header via an HMAC check # with a server-stored secret. # # See for more details: # https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet # (Protecting REST Services: Use of Custom Request Headers). csrf_token = utils.SmartStr(request.headers.get("X-CSRFToken", "")) if not csrf_token: logging.info("Did not find headers CSRF token for: %s", request.path) raise werkzeug_exceptions.Forbidden("CSRF token is missing") try: decoded = base64.urlsafe_b64decode(csrf_token + "==") digest, token_time = decoded.rsplit(CSRF_DELIMITER, 1) token_time = int(token_time) except (TypeError, ValueError): logging.info("Malformed CSRF token for: %s", request.path) raise werkzeug_exceptions.Forbidden("Malformed CSRF token") if len(digest) != hashlib.sha256().digest_size: logging.info("Invalid digest size for: %s", request.path) raise werkzeug_exceptions.Forbidden("Malformed CSRF token digest") expected = GenerateCSRFToken(request.user, token_time) if not constant_time.bytes_eq(csrf_token, expected): logging.info("Non-matching CSRF token for: %s", request.path) raise werkzeug_exceptions.Forbidden("Non-matching CSRF token") current_time = rdfvalue.RDFDatetime.Now().AsMicrosecondsSinceEpoch() if current_time - token_time > CSRF_TOKEN_DURATION.microseconds: logging.info("Expired CSRF token for: %s", request.path) raise werkzeug_exceptions.Forbidden("Expired CSRF token")
def authenticate_complete( self, state, credentials: Sequence[AttestedCredentialData], credential_id: bytes, client_data: CollectedClientData, auth_data: AuthenticatorData, signature: bytes, ) -> AttestedCredentialData: """Verify the correctness of the assertion data received from the client. :param state: The state data returned by the corresponding `register_begin`. :param credentials: The list of previously registered credentials. :param credential_id: The credential id from the client response. :param client_data: The client data. :param auth_data: The authenticator data. :param signature: The signature provided by the client.""" if client_data.type != CollectedClientData.TYPE.GET: raise ValueError("Incorrect type in CollectedClientData.") if not self._verify(client_data.origin): raise ValueError("Invalid origin in CollectedClientData.") if websafe_decode(state["challenge"]) != client_data.challenge: raise ValueError("Wrong challenge in response.") if not constant_time.bytes_eq(self.rp.id_hash, auth_data.rp_id_hash): raise ValueError("Wrong RP ID hash in response.") if not auth_data.is_user_present(): raise ValueError("User Present flag not set.") if ( state["user_verification"] == UserVerificationRequirement.REQUIRED and not auth_data.is_user_verified() ): raise ValueError( "User verification required, but user verified flag not set." ) for cred in credentials: if cred.credential_id == credential_id: try: cred.public_key.verify(auth_data + client_data.hash, signature) except _InvalidSignature: raise ValueError("Invalid signature.") logger.info(f"Credential authenticated: {credential_id.hex()}") return cred raise ValueError("Unknown credential ID.")
def parse_endpoint(self, token, version="v0", ckey_header=None): """Parse an endpoint into component elements of UAID, CHID and optional key hash if v2 :param token: The obscured subscription data. :param version: This is the API version of the token. :param ckey_header: the Crypto-Key header bearing the public key (from Crypto-Key: p256ecdsa=) :raises ValueError: In the case of a malformed endpoint. :returns: a dict containing (uaid=UAID, chid=CHID, public_key=KEY) """ token = self.fernet.decrypt(token.encode('utf8')) public_key = None if ckey_header: try: crypto_key = CryptoKey(ckey_header) except CryptoKeyException: raise InvalidTokenException("Invalid key data") label = crypto_key.get_label('p256ecdsa') try: public_key = base64url_decode(label) except: # Ignore missing and malformed app server keys. pass if version == 'v0': if not VALID_V0_TOKEN.match(token): raise InvalidTokenException("Corrupted push token") items = token.split(':') return dict(uaid=items[0], chid=items[1], public_key=public_key) if version == 'v1' and len(token) != 32: raise InvalidTokenException("Corrupted push token") if version == 'v2': if len(token) != 64: raise InvalidTokenException("Corrupted push token") if not public_key: raise InvalidTokenException("Invalid key data") if not constant_time.bytes_eq( sha256(public_key).digest(), token[32:]): raise InvalidTokenException("Key mismatch") return dict(uaid=token[:16].encode('hex'), chid=token[16:32].encode('hex'), public_key=public_key)
def parse_endpoint(self, token, version="v0", ckey_header=None): """Parse an endpoint into component elements of UAID, CHID and optional key hash if v2 :param token: The obscured subscription data. :param version: This is the API version of the token. :param ckey_header: the Crypto-Key header bearing the public key (from Crypto-Key: p256ecdsa=) :raises ValueError: In the case of a malformed endpoint. :returns: a dict containing (uaid=UAID, chid=CHID, public_key=KEY) """ token = self.fernet.decrypt(token.encode('utf8')) public_key = None if ckey_header: try: crypto_key = CryptoKey(ckey_header) except CryptoKeyException: raise InvalidTokenException("Invalid key data") label = crypto_key.get_label('p256ecdsa') try: public_key = base64url_decode(label) except: # Ignore missing and malformed app server keys. pass if version == 'v0': if not VALID_V0_TOKEN.match(token): raise InvalidTokenException("Corrupted push token") items = token.split(':') return dict(uaid=items[0], chid=items[1], public_key=public_key) if version == 'v1' and len(token) != 32: raise InvalidTokenException("Corrupted push token") if version == 'v2': if len(token) != 64: raise InvalidTokenException("Corrupted push token") if not public_key: raise InvalidTokenException("Invalid key data") if not constant_time.bytes_eq(sha256(public_key).digest(), token[32:]): raise InvalidTokenException("Key mismatch") return dict(uaid=token[:16].encode('hex'), chid=token[16:32].encode('hex'), public_key=public_key)
def verify(self, statement, auth_data, client_data_hash): jwt = statement['response'] header, payload, sig = (websafe_decode(x) for x in jwt.split(b'.')) data = json.loads(payload.decode('utf8')) if not self.allow_rooted and data['ctsProfileMatch'] is not True: raise InvalidData('ctsProfileMatch must be true!') expected_nonce = sha256(auth_data + client_data_hash) if not bytes_eq(expected_nonce, websafe_decode(data['nonce'])): raise InvalidData('Nonce does not match!') data = json.loads(header.decode('utf8')) certs = [ x509.load_der_x509_certificate( websafe_decode(x), default_backend()) for x in data['x5c'] ] certs.append(self._ca) cert = certs.pop(0) cn = cert.subject.get_attributes_for_oid(x509.NameOID.COMMON_NAME) if cn[0].value != 'attest.android.com': raise InvalidData('Certificate not issued to attest.android.com!') CoseKey.for_name( data['alg'] ).from_cryptography_key( cert.public_key() ).verify(jwt.rsplit(b'.', 1)[0], sig) while certs: child = cert cert = certs.pop(0) pub = cert.public_key() if isinstance(pub, rsa.RSAPublicKey): pub.verify( child.signature, child.tbs_certificate_bytes, padding.PKCS1v15(), child.signature_hash_algorithm ) elif isinstance(pub, ec.EllipticCurvePublicKey): pub.verify( child.signature, child.tbs_certificate_bytes, ec.ECDSA(child.signature_hash_algorithm) )
def open(self, ciphertext, associated_data=None): """Verify and decrypt an AES-SIV ciphertext, authenticating and the associated data""" if not isinstance(ciphertext, bytes): raise TypeError("ciphertext must be bytes") if associated_data is None: associated_data = [] v = ciphertext[0:block.SIZE] ciphertext = ciphertext[block.SIZE:] plaintext = self.__transform(v, ciphertext) t = self.__s2v(associated_data, plaintext) if not constant_time.bytes_eq(t, v): raise exceptions.IntegrityError("ciphertext verification failure!") return plaintext
def aes_key_unwrap(wrapping_key, wrapped_key, aiv): if len(wrapped_key) < 24: raise InvalidUnwrap("Must be at least 24 bytes") if len(wrapped_key) % 8 != 0: raise InvalidUnwrap("The wrapped key must be a multiple of 8 bytes") if len(wrapping_key) not in [16, 24, 32]: raise ValueError("The wrapping key must be a valid AES key length") r = [wrapped_key[i:i + 8] for i in range(0, len(wrapped_key), 8)] a = r.pop(0) a, r = unwrap_core(wrapping_key, a, r) if not bytes_eq(a, aiv): raise InvalidUnwrap() return b"".join(r)
def get_api_session(self): token = '' if b'api-token' in self.request.args: token = binary_type(self.request.args[b'api-token'][0]) elif b'x-api-token' in self.request.headers: token = binary_type(self.request.headers[b'x-api-token']) # Assert the input is okay and the api_token state is acceptable if self.request.tid != 1 or \ self.state.api_token_session is None or \ not self.state.tenant_cache[self.request.tid].admin_api_token_digest: return stored_token_hash = self.state.tenant_cache[self.request.tid].admin_api_token_digest.encode() if constant_time.bytes_eq(sha512(token), stored_token_hash): return self.state.api_token_session
def decrypt_blob(self, nonce: bytes, salt: bytes, enc_blob: bytes) -> bytes: ''' Decrypts `enc_blob` using nonce and the initialized key. Expects `self.key` to be a 32-byte value. Expects the message-encoded `salt` to equal `self.salt`. Returns the decrypted blob. ''' if not self.salt: raise ValueError('Salt must be set') if not bytes_eq(salt, self.salt): raise ValueError('Salts do not match.') algo = ChaCha20Poly1305(self.key) return algo.decrypt(nonce, enc_blob, None)
def authenticate(self, key, touch_callback=None): ct1 = self.send_cmd(INS.AUTHENTICATE, ALGO.TDES, SLOT.CARD_MANAGEMENT, Tlv(TAG.DYN_AUTH, Tlv(0x80)))[4:12] backend = default_backend() try: cipher_key = algorithms.TripleDES(key) except ValueError: raise BadFormat( 'Management key must be exactly 24 bytes long, ' 'was: {}'.format(len(key)), None) cipher = Cipher(cipher_key, modes.ECB(), backend) decryptor = cipher.decryptor() pt1 = decryptor.update(ct1) + decryptor.finalize() ct2 = os.urandom(8) if touch_callback is not None: touch_timer = Timer(0.500, touch_callback) touch_timer.start() try: pt2 = self.send_cmd( INS.AUTHENTICATE, ALGO.TDES, SLOT.CARD_MANAGEMENT, Tlv(TAG.DYN_AUTH, Tlv(0x80, pt1) + Tlv(0x81, ct2)))[4:12] except APDUError as e: if e.sw == SW.SECURITY_CONDITION_NOT_SATISFIED: raise AuthenticationFailed('Incorrect management key', e.sw, self.version) logger.error('Failed to authenticate management key.', exc_info=e) raise except Exception as e: logger.error('Failed to authenticate management key.', exc_info=e) raise finally: if touch_callback is not None: touch_timer.cancel() encryptor = cipher.encryptor() pt2_cmp = encryptor.update(ct2) + encryptor.finalize() if not bytes_eq(pt2, pt2_cmp): raise ValueError('Device challenge did not match!') self._authenticated = True
def jenkins_ci_notification(repo, pagure_ci_token, username=None): """ Jenkins Build Notification -------------------------- At the end of a build on Jenkins, this URL is used (if the project is rightly configured) to flag a pull-request with the result of the build. :: POST /api/0/ci/jenkins/<token>/build-finished """ project = pagure.lib.get_project(SESSION, repo, user=username) if repo is None: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) if not constant_time.bytes_eq( to_bytes(pagure_ci_token), to_bytes(project.ci_hook[0].pagure_ci_token)): raise pagure.exceptions.APIError(401, error_code=APIERROR.EINVALIDTOK) data = flask.request.get_json() if not data: APP.logger.debug("Bad Request: No JSON retrieved") raise pagure.exceptions.APIError(400, error_code=APIERROR.EINVALIDREQ) build_id = data.get('build', {}).get('number') if not build_id: APP.logger.debug("Bad Request: No build ID retrieved") raise pagure.exceptions.APIError(400, error_code=APIERROR.EINVALIDREQ) try: lib_ci.process_jenkins_build( SESSION, project, build_id, requestfolder=APP.config['REQUESTS_FOLDER'] ) except pagure.exceptions.PagureException as err: APP.logger.error('Error processing jenkins notification', exc_info=err) raise pagure.exceptions.APIError( 400, error_code=APIERROR.ENOCODE, error=str(err)) APP.logger.info('Successfully proccessed jenkins notification') return ('', 204)
def authenticate(self, key, touch_callback=None): ct1 = self.send_cmd(INS.AUTHENTICATE, ALGO.TDES, SLOT.CARD_MANAGEMENT, Tlv(TAG.DYN_AUTH, Tlv(0x80)))[4:12] backend = default_backend() try: cipher_key = algorithms.TripleDES(key) except ValueError: raise BadFormat('Management key must be exactly 24 bytes long, ' 'was: {}'.format(len(key)), None) cipher = Cipher(cipher_key, modes.ECB(), backend) decryptor = cipher.decryptor() pt1 = decryptor.update(ct1) + decryptor.finalize() ct2 = os.urandom(8) if touch_callback is not None: touch_timer = Timer(0.500, touch_callback) touch_timer.start() try: pt2 = self.send_cmd( INS.AUTHENTICATE, ALGO.TDES, SLOT.CARD_MANAGEMENT, Tlv(TAG.DYN_AUTH, Tlv(0x80, pt1) + Tlv(0x81, ct2)) )[4:12] except APDUError as e: if e.sw == SW.SECURITY_CONDITION_NOT_SATISFIED: raise AuthenticationFailed( 'Incorrect management key', e.sw, self.version) logger.error('Failed to authenticate management key.', exc_info=e) raise except Exception as e: logger.error('Failed to authenticate management key.', exc_info=e) raise finally: if touch_callback is not None: touch_timer.cancel() encryptor = cipher.encryptor() pt2_cmp = encryptor.update(ct2) + encryptor.finalize() if not bytes_eq(pt2, pt2_cmp): raise ValueError('Device challenge did not match!') self._authenticated = True
def aes_key_unwrap(wrapping_key, wrapped_key, backend): if len(wrapped_key) < 24: raise ValueError("Must be at least 24 bytes") if len(wrapped_key) % 8 != 0: raise ValueError("The wrapped key must be a multiple of 8 bytes") if len(wrapping_key) not in [16, 24, 32]: raise ValueError("The wrapping key must be a valid AES key length") aiv = b"\xa6\xa6\xa6\xa6\xa6\xa6\xa6\xa6" r = [wrapped_key[i:i + 8] for i in range(0, len(wrapped_key), 8)] a = r.pop(0) a, r = _unwrap_core(wrapping_key, a, r, backend) if not bytes_eq(a, aiv): raise InvalidUnwrap() return b"".join(r)
def validate(self, reference, attempt): """Validate the provided password string. Reference is the correct password, which may be encrypted; attempt is clear text password attempt. """ try: iterations_str, salt_b64, password_hash_b64 = reference.split('$') iterations = int(iterations_str) salt = base64.b64decode(salt_b64) password_hash = base64.b64decode(password_hash_b64) except (ValueError, TypeError): return False if iterations <= 0: return False attempt_hash = _encrypt(attempt, salt, iterations, len(password_hash)) return constant_time.bytes_eq(attempt_hash, password_hash)
def check_password(entered_password, user_password, seed=None): """ Version checking and returning the password :arg entered_password: password entered by the user. :type entered_password: str (Python 3) or unicode (Python 2) :arg user_password: the hashed string fetched from the database. :type user_password: bytes :return: a Boolean depending upon the entered_password, True if the password matches """ if not isinstance(entered_password, six.text_type): raise ValueError("Entered password is not unicode text") if isinstance(user_password, six.text_type): user_password = user_password.encode("utf-8") if not user_password.count(b"$") >= 2: raise pagure.exceptions.PagureException( "Password of unknown version found in the database" ) _, version, user_password = user_password.split(b"$", 2) if version == b"2": password = bcrypt.hashpw( entered_password.encode("utf-8"), user_password ) elif version == b"1": password = "******" % (entered_password, seed) password = ( hashlib.sha512(password.encode("utf-8")) .hexdigest() .encode("utf-8") ) else: raise pagure.exceptions.PagureException( "Password of unknown version found in the database" ) return constant_time.bytes_eq(password, user_password)
def finalize(self): # CommonCrypto has a yet another bug where you must make at least one # call to update. If you pass just AAD and call finalize without a call # to update you'll get null bytes for tag. The following update call # prevents this issue, which is present in at least 10.8 and 10.9. # Filed as rdar://18314580 self.update(b"") tag_size = self._cipher.block_size // 8 tag_buf = self._backend._ffi.new("unsigned char[]", tag_size) tag_len = self._backend._ffi.new("size_t *", tag_size) res = self._backend._lib.CCCryptorGCMFinal( self._ctx[0], tag_buf, tag_len ) self._backend._check_cipher_response(res) self._backend._release_cipher_ctx(self._ctx) self._tag = self._backend._ffi.buffer(tag_buf)[:] if (self._operation == self._backend._lib.kCCDecrypt and not constant_time.bytes_eq( self._tag[:len(self._mode.tag)], self._mode.tag )): raise InvalidTag return b""
def _a128cbc_hs256_decrypt(key, iv, ciphertext, authdata, authtag): if not key or not len(key) >= 32: raise ValueError('key must be at least 256 bits for algorithm "A128CBC-HS256"') if not iv or len(iv) != 16: raise ValueError('iv must be 128 bits for algorithm "A128CBC-HS256"') if not ciphertext: raise ValueError('ciphertext must be specified') if not authdata: raise ValueError('authdata must be specified') if not authtag or len(authtag) != 16: raise ValueError('authtag must be be 128 bits for algorithm "A128CBC-HS256"') hmac_key = key[:16] aes_key = key[16:32] auth_data_length = _int_to_bigendian_8_bytes(len(authdata) * 8) # ensure the authtag is the expected length for SHA256 hash if not len(authtag) == 16: raise ValueError('invalid tag') hashdata = authdata + iv + ciphertext + auth_data_length hmac_hash = hmac.HMAC(hmac_key, hashes.SHA256(), backend=default_backend()) hmac_hash.update(hashdata) tag = hmac_hash.finalize()[:16] if not constant_time.bytes_eq(tag, authtag): raise ValueError('"ciphertext" is not authentic') cipher = Cipher(algorithms.AES(aes_key), modes.CBC(iv), backend=default_backend()) decryptor = cipher.decryptor() plaintext = decryptor.update(ciphertext) + decryptor.finalize() # unpad the decrypted plaintext padder = padding.PKCS7(128).unpadder() plaintext = padder.update(plaintext) + padder.finalize() return plaintext