def test_hmac_sha256_vectors(self): self.assertEqual(hmac_sha256( b'\x0b'*20, b'Hi There' ), a2b_hex(b'b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7')) # noqa self.assertEqual(hmac_sha256( b'Jefe', b'what do ya want for nothing?' ), a2b_hex(b'5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843')) # noqa
def test_hmac_sha256_vectors(self): self.assertEqual( hmac_sha256(b"\x0b" * 20, b"Hi There"), bytes.fromhex( "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7" ), ) self.assertEqual( hmac_sha256(b"Jefe", b"what do ya want for nothing?"), bytes.fromhex( "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843" ), )
def sign_hash(self, credential_id, dgst, pin): ctap2 = CTAP2(self.get_current_hid_device()) client = self.get_current_fido_client() if pin: pin_token = client.client_pin.get_pin_token(pin) pin_auth = hmac_sha256(pin_token, dgst)[:16] return ctap2.send_cbor( 0x50, { 1: dgst, 2: { "id": credential_id, "type": "public-key" }, 3: pin_auth }, ) else: return ctap2.send_cbor(0x50, { 1: dgst, 2: { "id": credential_id, "type": "public-key" } })
def _sign_hash(self, cred_id, dgst): if self.pin: pin_token = self.client.pin_protocol.get_pin_token(self.pin) pin_auth = hmac_sha256(pin_token, dgst)[:16] ret = self.ctap2.send_cbor(0x50, { 1: dgst, 2: { "id": cred_id, "type": "public-key" }, 3: pin_auth }) else: ret = self.ctap2.send_cbor(0x50, { 1: dgst, 2: { "id": cred_id, "type": "public-key" } }) der_sig = ret[1] # Extract 'r' and 's' from the DER signature as described here: # https://crypto.stackexchange.com/questions/1795/how-can-i-convert-a-der-ecdsa-signature-to-asn-1 r_len = der_sig[3] r = der_sig[4:4 + r_len] s = der_sig[6 + r_len:] if len(r) > 32: r = r[-32:] if len(s) > 32: s = s[-32:] return r, s
def get_salt_params(salts): enc = cipher.encryptor() salt_enc = b"" for salt in salts: salt_enc += enc.update(salt) salt_enc += enc.finalize() salt_auth = hmac_sha256(shared_secret, salt_enc)[:16] return salt_enc, salt_auth
def _ctap2_make_credential( self, client_data, rp, user, key_params, exclude_list, extensions, rk, uv, pin, event, on_keepalive, ): pin_auth = None pin_protocol = None if pin: pin_protocol = self.pin_protocol.VERSION pin_token = self.pin_protocol.get_pin_token(pin) pin_auth = hmac_sha256(pin_token, client_data.hash)[:16] elif self.info.options.get("clientPin") and not uv: raise ClientError.ERR.BAD_REQUEST("PIN required but not provided") if not (rk or uv): options = None else: options = {} if rk: options["rk"] = True if uv: options["uv"] = True if exclude_list: # Filter out credential IDs which are too long max_len = self.info.max_cred_id_length if max_len: exclude_list = [e for e in exclude_list if len(e) <= max_len] # Reject the request if too many credentials remain. max_creds = self.info.max_creds_in_list if max_creds and len(exclude_list) > max_creds: raise ClientError.ERR.BAD_REQUEST("exclude_list too long") return self.ctap2.make_credential( client_data.hash, rp, user, key_params, exclude_list if exclude_list else None, extensions, options, pin_auth, pin_protocol, event, on_keepalive, )
def sendGA(self, *args, **kwargs): if len(args) > 9: # Add additional arg to calculate pin auth on demand pin = args[-1] args = list(args[:-1]) if args[5] == None and args[6] == None: pin_token = self.client.pin_protocol.get_pin_token(pin) pin_auth = hmac_sha256(pin_token, args[1])[:16] args[5] = pin_auth args[6] = 1 return self.ctap2.get_assertion(*args, **kwargs)
def test_backup_credential_is_generated_correctly( self, solo, device, ): import seedweed from binascii import hexlify key_A = b"A" * 32 ext_state = b"I'm a dicekey key!" version = b"\x01" ext_key_cmd = 0x62 print("Enter user presence THREE times.") solo.send_data_hid(ext_key_cmd, version + key_A + ext_state) # New credential works. mc_A_req = FidoRequest() mc_A_res = device.sendMC(*mc_A_req.toMC()) rpIdHash = sha256(mc_A_req.rp["id"].encode("utf8")) credId = mc_A_res.auth_data.credential_data.credential_id ( uniqueId, extStateInCredId, credMacInCredId, ) = seedweed.nonce_extstate_mac_from_credential_id(credId) seedweed.validate_credential_id(key_A, credId, rpIdHash) credMac = hmac_sha256(key_A, rpIdHash + version + uniqueId + ext_state) allow_list = [{ "id": mc_A_res.auth_data.credential_data.credential_id, "type": "public-key", }] ga_req = FidoRequest(allow_list=allow_list) ga_res = device.sendGA(*ga_req.toGA()) verify(mc_A_res, ga_res, ga_req.cdh) # Independently create the key and verify _, _, keypair, iterations = seedweed.keypair_from_seed_mac( key_A, credMac) assert iterations == 1 keypair.verifying_key.verify( ga_res.signature, ga_res.auth_data + ga_req.cdh, sigdecode=ecdsa.util.sigdecode_der, hashfunc=hashlib.sha256, )
def test_hmac_secret_different_with_uv(self, device, MCHmacSecret, cipher, sharedSecret): salts = [salt1] key_agreement, shared_secret = sharedSecret salt_enc, salt_auth = get_salt_params(cipher, shared_secret, salts) req = FidoRequest(extensions={ "hmac-secret": { 1: key_agreement, 2: salt_enc, 3: salt_auth } }) auth_no_uv = device.sendGA(*req.toGA()) assert (auth_no_uv.auth_data.flags & (1 << 2)) == 0 ext_no_uv = auth_no_uv.auth_data.extensions assert ext_no_uv assert "hmac-secret" in ext_no_uv assert isinstance(ext_no_uv["hmac-secret"], bytes) assert len(ext_no_uv["hmac-secret"]) == len(salts) * 32 verify(MCHmacSecret, auth_no_uv, req.cdh) # Now get same auth with UV pin = '1234' device.client.pin_protocol.set_pin(pin) pin_token = device.client.pin_protocol.get_pin_token(pin) pin_auth = hmac_sha256(pin_token, req.cdh)[:16] req = FidoRequest(req, pin_protocol=1, pin_auth=pin_auth, extensions={ "hmac-secret": { 1: key_agreement, 2: salt_enc, 3: salt_auth } }) auth_uv = device.sendGA(*req.toGA()) assert auth_uv.auth_data.flags & (1 << 2) ext_uv = auth_uv.auth_data.extensions assert ext_uv assert "hmac-secret" in ext_uv assert isinstance(ext_uv["hmac-secret"], bytes) assert len(ext_uv["hmac-secret"]) == len(salts) * 32 verify(MCHmacSecret, auth_uv, req.cdh) # Now see if the hmac-secrets are different assert ext_no_uv['hmac-secret'] != ext_uv['hmac-secret']
def _ctap2_get_assertion(self, client_data, rp_id, allow_list, extensions, uv, up, pin, event, on_keepalive): pin_auth = None pin_protocol = None if pin: pin_protocol = self.pin_protocol.VERSION pin_token = self.pin_protocol.get_pin_token(pin) pin_auth = hmac_sha256(pin_token, client_data.hash)[:16] elif self.info.options.get("clientPin") and not uv: raise ClientError.ERR.BAD_REQUEST("PIN required but not provided") if uv: options = {"uv": True} if not up: options = {"uv": True, "up": False} else: options = None if not up: options = {"up": False} if allow_list: # Filter out credential IDs which are too long max_len = self.info.max_cred_id_length if max_len: allow_list = [e for e in allow_list if len(e) <= max_len] if not allow_list: raise CtapError(CtapError.ERR.NO_CREDENTIALS) # Reject the request if too many credentials remain. max_creds = self.info.max_creds_in_list if max_creds and len(allow_list) > max_creds: raise ClientError.ERR.BAD_REQUEST("allow_list too long") return self.ctap2.get_assertions( rp_id, client_data.hash, allow_list if allow_list else None, extensions, options, pin_auth, pin_protocol, event, on_keepalive, )
def sendMC(self, *args, **kwargs): if len(args) > 11: # Add additional arg to calculate pin auth on demand pin = args[-1] args = list(args[:-1]) if args[7] == None and args[8] == None: pin_token = self.client.pin_protocol.get_pin_token(pin) pin_auth = hmac_sha256(pin_token, args[0])[:16] args[7] = pin_auth args[8] = 1 attestation_object = self.ctap2.make_credential(*args, **kwargs) if attestation_object: verifier = Attestation.for_type(attestation_object.fmt) client_data = args[0] verifier().verify( attestation_object.att_statement, attestation_object.auth_data, client_data, ) return attestation_object
def sign_hash(self, credential_id, dgst, pin): if pin: pin_token = self.client.pin_protocol.get_pin_token(pin) pin_auth = hmac_sha256(pin_token, dgst)[:16] return self.ctap2.send_cbor( 0x50, { 1: dgst, 2: { "id": credential_id, "type": "public-key" }, 3: pin_auth }, ) else: return self.ctap2.send_cbor(0x50, { 1: dgst, 2: { "id": credential_id, "type": "public-key" } })
def test_extensions(self, ): salt1 = b"\x5a" * 32 salt2 = b"\x96" * 32 salt3 = b"\x03" * 32 # self.testReset() with Test("Get info has hmac-secret"): info = self.ctap.get_info() assert "hmac-secret" in info.extensions reg = self.testMC( "Send MC with hmac-secret ext set to true, expect SUCCESS", cdh, rp, user, key_params, expectedError=CtapError.ERR.SUCCESS, other={ "extensions": { "hmac-secret": True }, "options": { "rk": True } }, ) with Test( "Check 'hmac-secret' is set to true in auth_data extensions"): assert reg.auth_data.extensions assert "hmac-secret" in reg.auth_data.extensions assert reg.auth_data.extensions["hmac-secret"] == True reg = self.testMC( "Send MC with fake extension set to true, expect SUCCESS", cdh, rp, user, key_params, expectedError=CtapError.ERR.SUCCESS, other={"extensions": { "tetris": True }}, ) with Test("Get shared secret"): key_agreement, shared_secret = ( self.client.pin_protocol._init_shared_secret()) cipher = Cipher( algorithms.AES(shared_secret), modes.CBC(b"\x00" * 16), default_backend(), ) def get_salt_params(salts): enc = cipher.encryptor() salt_enc = b"" for salt in salts: salt_enc += enc.update(salt) salt_enc += enc.finalize() salt_auth = hmac_sha256(shared_secret, salt_enc)[:16] return salt_enc, salt_auth for salt_list in ((salt1, ), (salt1, salt2)): salt_enc, salt_auth = get_salt_params(salt_list) auth = self.testGA( "Send GA request with %d salts hmac-secret, expect success" % len(salt_list), rp["id"], cdh, other={ "extensions": { "hmac-secret": { 1: key_agreement, 2: salt_enc, 3: salt_auth } } }, expectedError=CtapError.ERR.SUCCESS, ) with Test( "Check that hmac-secret is in auth_data extensions and has %d bytes" % (len(salt_list) * 32)): ext = auth.auth_data.extensions assert ext assert "hmac-secret" in ext assert isinstance(ext["hmac-secret"], bytes) assert len(ext["hmac-secret"]) == len(salt_list) * 32 with Test("Check that shannon_entropy of hmac-secret is good"): ext = auth.auth_data.extensions dec = cipher.decryptor() key = dec.update(ext["hmac-secret"]) + dec.finalize() print(shannon_entropy(ext["hmac-secret"])) if len(salt_list) == 1: assert shannon_entropy(ext["hmac-secret"]) > 4.6 assert shannon_entropy(key) > 4.6 if len(salt_list) == 2: assert shannon_entropy(ext["hmac-secret"]) > 5.4 assert shannon_entropy(key) > 5.4 salt_enc, salt_auth = get_salt_params((salt3, )) auth = self.testGA( "Send GA request with hmac-secret missing keyAgreement, expect error", rp["id"], cdh, other={"extensions": { "hmac-secret": { 2: salt_enc, 3: salt_auth } }}, ) auth = self.testGA( "Send GA request with hmac-secret missing saltAuth, expect MISSING_PARAMETER", rp["id"], cdh, other={ "extensions": { "hmac-secret": { 1: key_agreement, 2: salt_enc } } }, expectedError=CtapError.ERR.MISSING_PARAMETER, ) auth = self.testGA( "Send GA request with hmac-secret missing saltEnc, expect MISSING_PARAMETER", rp["id"], cdh, other={ "extensions": { "hmac-secret": { 1: key_agreement, 3: salt_auth } } }, expectedError=CtapError.ERR.MISSING_PARAMETER, ) bad_auth = list(salt_auth[:]) bad_auth[len(bad_auth) // 2] = bad_auth[len(bad_auth) // 2] ^ 1 bad_auth = bytes(bad_auth) auth = self.testGA( "Send GA request with hmac-secret containing bad saltAuth, expect EXTENSION_FIRST", rp["id"], cdh, other={ "extensions": { "hmac-secret": { 1: key_agreement, 2: salt_enc, 3: bad_auth } } }, expectedError=CtapError.ERR.EXTENSION_FIRST, ) salt4 = b"\x5a" * 16 salt5 = b"\x96" * 64 for salt_list in ((salt4, ), (salt4, salt5)): salt_enc, salt_auth = get_salt_params(salt_list) salt_auth = hmac_sha256(shared_secret, salt_enc)[:16] auth = self.testGA( "Send GA request with incorrect salt length %d, expect INVALID_LENGTH" % len(salt_enc), rp["id"], cdh, other={ "extensions": { "hmac-secret": { 1: key_agreement, 2: salt_enc, 3: salt_auth } } }, expectedError=CtapError.ERR.INVALID_LENGTH, )
def test_client_pin(self, ): pin1 = "1234567890" self.test_rk(pin1) # PinProtocolV1 res = self.testCP( "Test getKeyAgreement, expect SUCCESS", pin_protocol, PinProtocolV1.CMD.GET_KEY_AGREEMENT, expectedError=CtapError.ERR.SUCCESS, ) with Test("Test getKeyAgreement has appropriate fields"): key = res[1] assert "Is public key" and key[1] == 2 assert "Is P256" and key[-1] == 1 assert "Is ALG_ECDH_ES_HKDF_256" and key[3] == -25 assert "Right key" and len(key[-3]) == 32 and isinstance( key[-3], bytes) with Test("Test setting a new pin"): pin2 = "qwertyuiop\x11\x22\x33\x00123" self.client.pin_protocol.change_pin(pin1, pin2) with Test("Test getting new pin_auth"): pin_token = self.client.pin_protocol.get_pin_token(pin2) pin_auth = hmac_sha256(pin_token, cdh)[:16] res_mc = self.testMC( "Send MC request with new pin auth", cdh, rp, user, key_params, other={ "pin_auth": pin_auth, "pin_protocol": pin_protocol }, expectedError=CtapError.ERR.SUCCESS, ) with Test("Check UV flag is set"): assert res_mc.auth_data.flags & (1 << 2) res_ga = self.testGA( "Send GA request with pinAuth, expect SUCCESS", rp["id"], cdh, [{ "type": "public-key", "id": res_mc.auth_data.credential_data.credential_id, }], other={ "pin_auth": pin_auth, "pin_protocol": pin_protocol }, expectedError=CtapError.ERR.SUCCESS, ) with Test("Check UV flag is set"): assert res_ga.auth_data.flags & (1 << 2) res_ga = self.testGA( "Send GA request with no pinAuth, expect SUCCESS", rp["id"], cdh, [{ "type": "public-key", "id": res_mc.auth_data.credential_data.credential_id, }], expectedError=CtapError.ERR.SUCCESS, ) with Test("Check UV flag is NOT set"): assert not (res_ga.auth_data.flags & (1 << 2)) self.testReset() with Test("Test sending zero-length pin_auth, expect PIN_NOT_SET"): self.testMC( "Send MC request with new pin auth", cdh, rp, user, key_params, other={ "pin_auth": b"", "pin_protocol": pin_protocol }, expectedError=CtapError.ERR.PIN_NOT_SET, ) self.testGA( "Send MC request with new pin auth", rp["id"], cdh, other={ "pin_auth": b"", "pin_protocol": pin_protocol }, expectedError=CtapError.ERR.PIN_NOT_SET, ) with Test("Setting pin code, expect SUCCESS"): self.client.pin_protocol.set_pin(pin1) with Test("Test sending zero-length pin_auth, expect PIN_INVALID"): self.testMC( "Send MC request with new pin auth", cdh, rp, user, key_params, other={ "pin_auth": b"", "pin_protocol": pin_protocol }, expectedError=CtapError.ERR.PIN_INVALID, ) self.testGA( "Send MC request with new pin auth", rp["id"], cdh, other={ "pin_auth": b"", "pin_protocol": pin_protocol }, expectedError=CtapError.ERR.PIN_INVALID, ) self.testReset() with Test("Setting pin code >63 bytes, expect POLICY_VIOLATION "): try: self.client.pin_protocol.set_pin("A" * 64) assert 0 except CtapError as e: assert e.code == CtapError.ERR.PIN_POLICY_VIOLATION with Test("Get pin token when no pin is set, expect PIN_NOT_SET"): try: self.client.pin_protocol.get_pin_token(pin1) assert 0 except CtapError as e: assert e.code == CtapError.ERR.PIN_NOT_SET with Test("Get change pin when no pin is set, expect PIN_NOT_SET"): try: self.client.pin_protocol.change_pin(pin1, "1234") assert 0 except CtapError as e: assert e.code == CtapError.ERR.PIN_NOT_SET with Test("Setting pin code and get pin_token, expect SUCCESS"): self.client.pin_protocol.set_pin(pin1) pin_token = self.client.pin_protocol.get_pin_token(pin1) pin_auth = hmac_sha256(pin_token, cdh)[:16] with Test("Get info and assert that clientPin is set to true"): info = self.ctap.get_info() assert info.options["clientPin"] with Test("Test setting pin again fails"): try: self.client.pin_protocol.set_pin(pin1) assert 0 except CtapError as e: print(e) res_mc = self.testMC( "Send MC request with no pin_auth, expect PIN_REQUIRED", cdh, rp, user, key_params, expectedError=CtapError.ERR.PIN_REQUIRED, ) res_mc = self.testGA( "Send GA request with no pin_auth, expect NO_CREDENTIALS", rp["id"], cdh, expectedError=CtapError.ERR.NO_CREDENTIALS, ) res = self.testCP( "Test getRetries, expect SUCCESS", pin_protocol, PinProtocolV1.CMD.GET_RETRIES, expectedError=CtapError.ERR.SUCCESS, ) with Test("Check there is 8 pin attempts left"): assert res[3] == 8 # Flip 1 bit pin_wrong = list(pin1) c = pin1[len(pin1) // 2] pin_wrong[len(pin1) // 2] = chr(ord(c) ^ 1) pin_wrong = "".join(pin_wrong) for i in range(1, 3): self.testPP( "Get pin_token with wrong pin code, expect PIN_INVALID (%d/2)" % i, pin_wrong, expectedError=CtapError.ERR.PIN_INVALID, ) print("Check there is %d pin attempts left" % (8 - i)) res = self.ctap.client_pin(pin_protocol, PinProtocolV1.CMD.GET_RETRIES) assert res[3] == (8 - i) print("Pass") for i in range(1, 3): self.testPP( "Get pin_token with wrong pin code, expect PIN_AUTH_BLOCKED %d/2" % i, pin_wrong, expectedError=CtapError.ERR.PIN_AUTH_BLOCKED, ) self.reboot() with Test("Get pin_token, expect SUCCESS"): pin_token = self.client.pin_protocol.get_pin_token(pin1) pin_auth = hmac_sha256(pin_token, cdh)[:16] res_mc = self.testMC( "Send MC request with correct pin_auth", cdh, rp, user, key_params, other={ "pin_auth": pin_auth, "pin_protocol": pin_protocol }, expectedError=CtapError.ERR.SUCCESS, ) with Test("Test getRetries resets to 8"): res = self.ctap.client_pin(pin_protocol, PinProtocolV1.CMD.GET_RETRIES) assert res[3] == (8) for i in range(1, 10): err = CtapError.ERR.PIN_INVALID if i in (3, 6): err = CtapError.ERR.PIN_AUTH_BLOCKED elif i >= 8: err = [CtapError.ERR.PIN_BLOCKED, CtapError.ERR.PIN_INVALID] self.testPP( "Lock out authentictor and check correct error codes %d/9" % i, pin_wrong, expectedError=err, ) attempts = 8 - i if i > 8: attempts = 0 with Test("Check there is %d pin attempts left" % attempts): res = self.ctap.client_pin(pin_protocol, PinProtocolV1.CMD.GET_RETRIES) assert res[3] == attempts if err == CtapError.ERR.PIN_AUTH_BLOCKED: self.reboot() res_mc = self.testMC( "Send MC request with correct pin_auth, expect error", cdh, rp, user, key_params, other={ "pin_auth": pin_auth, "pin_protocol": pin_protocol }, ) self.reboot() self.testPP( "Get pin_token with correct pin code, expect PIN_BLOCKED", pin1, expectedError=CtapError.ERR.PIN_BLOCKED, )
def test_rk(self, pin_code=None): pin_auth = None if pin_code: pin_protocol = 1 else: pin_protocol = None if pin_code: with Test("Set pin code"): self.client.pin_protocol.set_pin(pin_code) pin_token = self.client.pin_protocol.get_pin_token(pin_code) pin_auth = hmac_sha256(pin_token, cdh)[:16] self.testMC( "Send MC request with rk option set to true, expect SUCCESS", cdh, rp, user, key_params, other={ "options": { "rk": True }, "pin_auth": pin_auth, "pin_protocol": pin_protocol, }, expectedError=CtapError.ERR.SUCCESS, ) with Test("Get info"): info = self.ctap.get_info() options = {"rk": True} if "uv" in info.options and info.options["uv"]: options["uv"] = False for i, x in enumerate([user1, user2, user3]): self.testMC( "Send MC request with rk option set to true, expect SUCCESS %d/3" % (i + 1), cdh, rp2, x, key_params, other={ "options": options, "pin_auth": pin_auth, "pin_protocol": pin_protocol, }, expectedError=CtapError.ERR.SUCCESS, ) auth1 = self.testGA( "Send GA request with no allow_list, expect SUCCESS", rp2["id"], cdh, other={ "pin_auth": pin_auth, "pin_protocol": pin_protocol }, expectedError=CtapError.ERR.SUCCESS, ) with Test("Check that there are 3 credentials returned"): assert auth1.number_of_credentials == 3 with Test("Get the next 2 assertions"): auth2 = self.ctap.get_next_assertion() auth3 = self.ctap.get_next_assertion() if not pin_code: with Test("Check only the user ID was returned"): assert "id" in auth1.user.keys() and len( auth1.user.keys()) == 1 assert "id" in auth2.user.keys() and len( auth2.user.keys()) == 1 assert "id" in auth3.user.keys() and len( auth3.user.keys()) == 1 else: with Test("Check that all user info was returned"): for x in (auth1, auth2, auth3): for y in ("name", "icon", "displayName", "id"): if y not in x.user.keys(): print("FAIL: %s was not in user: "******"Send an extra getNextAssertion request, expect error"): try: self.ctap.get_next_assertion() assert 0 except CtapError as e: print(e)