mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-02-11 15:12:44 +00:00
core/webauthn: Truncate names in credential data to at most 100 bytes.
This commit is contained in:
parent
ccffefd667
commit
0af0e06d5b
@ -18,6 +18,10 @@ _CRED_ID_VERSION = b"\xf1\xd0\x02\x00"
|
|||||||
_CRED_ID_MIN_LENGTH = const(33)
|
_CRED_ID_MIN_LENGTH = const(33)
|
||||||
_KEY_HANDLE_LENGTH = const(64)
|
_KEY_HANDLE_LENGTH = const(64)
|
||||||
|
|
||||||
|
# Maximum supported length of the RP name, user name or user displayName in bytes.
|
||||||
|
# Note: The WebAuthn spec allows authenticators to truncate to 64 bytes or more.
|
||||||
|
NAME_MAX_LENGTH = const(100)
|
||||||
|
|
||||||
# Credential ID keys
|
# Credential ID keys
|
||||||
_CRED_ID_RP_ID = const(1)
|
_CRED_ID_RP_ID = const(1)
|
||||||
_CRED_ID_RP_NAME = const(2)
|
_CRED_ID_RP_NAME = const(2)
|
||||||
@ -208,6 +212,18 @@ class Fido2Credential(Credential):
|
|||||||
|
|
||||||
return cred
|
return cred
|
||||||
|
|
||||||
|
def truncate_names(self) -> None:
|
||||||
|
if self.rp_name:
|
||||||
|
self.rp_name = utils.truncate_utf8(self.rp_name, NAME_MAX_LENGTH)
|
||||||
|
|
||||||
|
if self.user_name:
|
||||||
|
self.user_name = utils.truncate_utf8(self.user_name, NAME_MAX_LENGTH)
|
||||||
|
|
||||||
|
if self.user_display_name:
|
||||||
|
self.user_display_name = utils.truncate_utf8(
|
||||||
|
self.user_display_name, NAME_MAX_LENGTH
|
||||||
|
)
|
||||||
|
|
||||||
def check_required_fields(self) -> bool:
|
def check_required_fields(self) -> bool:
|
||||||
return (
|
return (
|
||||||
self.rp_id is not None
|
self.rp_id is not None
|
||||||
|
@ -1355,6 +1355,7 @@ def cbor_make_credential(req: Cmd, dialog_mgr: DialogManager) -> Optional[Cmd]:
|
|||||||
cred.user_id = user["id"]
|
cred.user_id = user["id"]
|
||||||
cred.user_name = user.get("name", None)
|
cred.user_name = user.get("name", None)
|
||||||
cred.user_display_name = user.get("displayName", None)
|
cred.user_display_name = user.get("displayName", None)
|
||||||
|
cred.truncate_names()
|
||||||
|
|
||||||
# Check if any of the credential descriptors in the exclude list belong to this authenticator.
|
# Check if any of the credential descriptors in the exclude list belong to this authenticator.
|
||||||
exclude_list = param.get(_MAKECRED_CMD_EXCLUDE_LIST, [])
|
exclude_list = param.get(_MAKECRED_CMD_EXCLUDE_LIST, [])
|
||||||
|
@ -138,3 +138,17 @@ def obj_repr(o: object) -> str:
|
|||||||
else:
|
else:
|
||||||
d = o.__dict__
|
d = o.__dict__
|
||||||
return "<%s: %s>" % (o.__class__.__name__, d)
|
return "<%s: %s>" % (o.__class__.__name__, d)
|
||||||
|
|
||||||
|
|
||||||
|
def truncate_utf8(string: str, max_bytes: int) -> str:
|
||||||
|
"""Truncate the codepoints of a string so that its UTF-8 encoding is at most `max_bytes` in length."""
|
||||||
|
data = string.encode()
|
||||||
|
if len(data) <= max_bytes:
|
||||||
|
return string
|
||||||
|
|
||||||
|
# Find the starting position of the last codepoint in data[0 : max_bytes + 1].
|
||||||
|
i = max_bytes
|
||||||
|
while i >= 0 and data[i] & 0xC0 == 0x80:
|
||||||
|
i -= 1
|
||||||
|
|
||||||
|
return data[:i].decode()
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from common import *
|
from common import *
|
||||||
import storage
|
import storage
|
||||||
from apps.common import mnemonic
|
from apps.common import mnemonic
|
||||||
from apps.webauthn.credential import Fido2Credential
|
from apps.webauthn.credential import Fido2Credential, NAME_MAX_LENGTH
|
||||||
from trezor.crypto.curve import nist256p1
|
from trezor.crypto.curve import nist256p1
|
||||||
from trezor.crypto.hashlib import sha256
|
from trezor.crypto.hashlib import sha256
|
||||||
|
|
||||||
@ -59,5 +59,20 @@ class TestCredential(unittest.TestCase):
|
|||||||
self.assertEqual(hexlify(cred.hmac_secret_key()), cred_random)
|
self.assertEqual(hexlify(cred.hmac_secret_key()), cred_random)
|
||||||
self.assertEqual(hexlify(cred.public_key()), public_key)
|
self.assertEqual(hexlify(cred.public_key()), public_key)
|
||||||
|
|
||||||
|
def test_truncation(self):
|
||||||
|
cred = Fido2Credential()
|
||||||
|
cred.truncate_names()
|
||||||
|
self.assertIsNone(cred.rp_name)
|
||||||
|
self.assertIsNone(cred.user_name)
|
||||||
|
self.assertIsNone(cred.user_display_name)
|
||||||
|
|
||||||
|
cred.rp_name = "a" * (NAME_MAX_LENGTH - 2) + "\u0123"
|
||||||
|
cred.user_name = "a" * (NAME_MAX_LENGTH - 1) + "\u0123"
|
||||||
|
cred.user_display_name = "a" * NAME_MAX_LENGTH + "\u0123"
|
||||||
|
cred.truncate_names()
|
||||||
|
self.assertEqual(cred.rp_name, "a" * (NAME_MAX_LENGTH - 2) + "\u0123")
|
||||||
|
self.assertEqual(cred.user_name, "a" * (NAME_MAX_LENGTH - 1))
|
||||||
|
self.assertEqual(cred.user_display_name, "a" * NAME_MAX_LENGTH)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@ -13,6 +13,28 @@ class TestUtils(unittest.TestCase):
|
|||||||
self.assertEqual(c[i].stop, 100 if (i == 14) else (i + 1) * 7)
|
self.assertEqual(c[i].stop, 100 if (i == 14) else (i + 1) * 7)
|
||||||
self.assertEqual(c[i].step, 1)
|
self.assertEqual(c[i].step, 1)
|
||||||
|
|
||||||
|
def test_truncate_utf8(self):
|
||||||
|
self.assertEqual(utils.truncate_utf8("", 3), "")
|
||||||
|
self.assertEqual(utils.truncate_utf8("a", 3), "a")
|
||||||
|
self.assertEqual(utils.truncate_utf8("ab", 3), "ab")
|
||||||
|
self.assertEqual(utils.truncate_utf8("abc", 3), "abc")
|
||||||
|
self.assertEqual(utils.truncate_utf8("abcd", 3), "abc")
|
||||||
|
self.assertEqual(utils.truncate_utf8("abcde", 3), "abc")
|
||||||
|
self.assertEqual(utils.truncate_utf8("a\u0123", 3), "a\u0123") # b'a\xc4\xa3'
|
||||||
|
self.assertEqual(utils.truncate_utf8("a\u1234", 3), "a") # b'a\xe1\x88\xb4'
|
||||||
|
self.assertEqual(utils.truncate_utf8("ab\u0123", 3), "ab") # b'ab\xc4\xa3'
|
||||||
|
self.assertEqual(utils.truncate_utf8("ab\u1234", 3), "ab") # b'ab\xe1\x88\xb4'
|
||||||
|
self.assertEqual(utils.truncate_utf8("abc\u0123", 3), "abc") # b'abc\xc4\xa3'
|
||||||
|
self.assertEqual(utils.truncate_utf8("abc\u1234", 3), "abc") # b'abc\xe1\x88\xb4'
|
||||||
|
self.assertEqual(utils.truncate_utf8("\u1234\u5678", 0), "") # b'\xe1\x88\xb4\xe5\x99\xb8
|
||||||
|
self.assertEqual(utils.truncate_utf8("\u1234\u5678", 1), "") # b'\xe1\x88\xb4\xe5\x99\xb8
|
||||||
|
self.assertEqual(utils.truncate_utf8("\u1234\u5678", 2), "") # b'\xe1\x88\xb4\xe5\x99\xb8
|
||||||
|
self.assertEqual(utils.truncate_utf8("\u1234\u5678", 3), "\u1234") # b'\xe1\x88\xb4\xe5\x99\xb8
|
||||||
|
self.assertEqual(utils.truncate_utf8("\u1234\u5678", 4), "\u1234") # b'\xe1\x88\xb4\xe5\x99\xb8
|
||||||
|
self.assertEqual(utils.truncate_utf8("\u1234\u5678", 5), "\u1234") # b'\xe1\x88\xb4\xe5\x99\xb8
|
||||||
|
self.assertEqual(utils.truncate_utf8("\u1234\u5678", 6), "\u1234\u5678") # b'\xe1\x88\xb4\xe5\x99\xb8
|
||||||
|
self.assertEqual(utils.truncate_utf8("\u1234\u5678", 7), "\u1234\u5678") # b'\xe1\x88\xb4\xe5\x99\xb8
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
Loading…
Reference in New Issue
Block a user