Repository: privacyidea/privacyidea

Issue:
Support webauthn token without sign_count
Some webauthn tokens (f.e. Apple devices) do not implement a sign_count. We do not currently support this.


Discussion:

Resolving patch:
diff --git a/privacyidea/lib/tokens/webauthn.py b/privacyidea/lib/tokens/webauthn.py
--- a/privacyidea/lib/tokens/webauthn.py
+++ b/privacyidea/lib/tokens/webauthn.py
@@ -1659,11 +1659,7 @@ def verify(self):
             #             case, or not, or fails the authentication ceremony
             #             or not, is Relying Party-specific.
             sign_count = struct.unpack('!I', a_data[33:37])[0]
-            if not sign_count:
-                raise AuthenticationRejectedException('Unable to parse sign_count.')
-            if not isinstance(self.webauthn_user.sign_count, int) or self.webauthn_user.sign_count < 0:
-                raise WebAuthnUserDataMissing('sign_count missing from WebAuthnUser.')
-            if sign_count <= self.webauthn_user.sign_count:
+            if (sign_count != 0 or self.webauthn_user.sign_count != 0) and sign_count <= self.webauthn_user.sign_count:
                 raise AuthenticationRejectedException('Duplicate authentication detected.')
 
             # Step 18.


Test patch:
diff --git a/tests/test_lib_tokens_webauthn.py b/tests/test_lib_tokens_webauthn.py
--- a/tests/test_lib_tokens_webauthn.py
+++ b/tests/test_lib_tokens_webauthn.py
@@ -108,6 +108,7 @@
     'signature': b'MEUCIEp28FzVKneM3U3xVl4ABOXMHq02BBnQ9cOgFDvzfn8VAiEAkytcMIpWDP5PJEIUhDB1uQSz7aZO'
                  b'hdZGYqgRmMOGzd4='
 }
+ASSERTION_RESPONSE_SIGN_COUNT = 637
 CRED_KEY = {
     'alg': -7,
     'type': 'public-key'
@@ -448,11 +449,24 @@ def test_05_no_user_presence_fail_assertion(self):
         auth_data = auth_data[:32] + struct.pack('!B', flags) + auth_data[33:]
         webauthn_assertion_response.assertion_response['authData'] = webauthn_b64_encode(auth_data)
 
-        # FIXME This *should* fail because UP=0, but will fail anyway later on because the signature is invalid.
-        # TODO Build a mock Authenticator implementation, to be able to sign arbitrary authenticator data statements.
-        # TODO Sign an authenticator data statement with UP=0 and test against that so that the signature is valid.
+        # FIXME: This *should* fail because UP=0, but will fail anyway later on because the
+        #  signature is invalid.
+        # TODO: Build a mock Authenticator implementation, to be able to sign arbitrary
+        #  authenticator data statements.
+        # TODO: Sign an authenticator data statement with UP=0 and test against that so that the
+        #  signature is valid.
         with self.assertRaises(AuthenticationRejectedException):
             webauthn_assertion_response.verify()
 
-    def test_06_webauthn_b64_decode(self):
+    def test_06_duplicate_authentication_fail_assertion(self):
+        webauthn_assertion_response = self.getAssertionResponse()
+        webauthn_assertion_response.webauthn_user.sign_count = ASSERTION_RESPONSE_SIGN_COUNT
+
+        with self.assertRaisesRegexp(AuthenticationRejectedException,
+                                     'Duplicate authentication detected.'):
+            webauthn_assertion_response.verify()
+        # TODO: we should add a test for a missing sign_count (or 0) but we need
+        #  to change the auth data for that.
+
+    def test_07_webauthn_b64_decode(self):
         self.assertEqual(webauthn_b64_decode(URL_DECODE_TEST_STRING), URL_DECODE_EXPECTED_RESULT)