Bug 1428916 - WebAuthn: Draft Attestation Preference r=smaug,ttaubert
authorJ.C. Jones <jjones@mozilla.com>
Tue, 23 Jan 2018 12:21:15 -0700
changeset 400481 c2e41df3f41f38fe9a38282610f7c1daf519f87c
parent 400480 c72a74847882f1177460172f824227b356f2bc36
child 400482 0e62eb7804c00c0996a9bdde5350328a384fb7af
child 400487 b529d05259c5f20af811903842655c5061ffb951
push id33307
push userebalazs@mozilla.com
push dateWed, 24 Jan 2018 10:08:03 +0000
treeherdermozilla-central@0e62eb7804c0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug, ttaubert
bugs1428916, 1430150, 1416056
milestone60.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1428916 - WebAuthn: Draft Attestation Preference r=smaug,ttaubert The WebAuthn spec lets RPs ask to specifically get direct attestation certificates during credential creation using the "Attestation Conveyance Preference" [1]. This change adds that field into the WebIDL and ignores it for now. This is pre-work to Bug #1430150 which will make this useful (which in turn requires Bug #1416056's support for anonymizing those attestation certificates). [1] https://www.w3.org/TR/webauthn/#attestation-convey MozReview-Commit-ID: 763vaAMv48z
dom/webauthn/WebAuthnManager.cpp
dom/webauthn/tests/mochitest.ini
dom/webauthn/tests/test_webauthn_attestation_conveyance.html
dom/webidl/WebAuthentication.webidl
--- a/dom/webauthn/WebAuthnManager.cpp
+++ b/dom/webauthn/WebAuthnManager.cpp
@@ -375,25 +375,35 @@ WebAuthnManager::MakeCredential(const Ma
     return promise.forget();
   }
 
   // TODO: Add extension list building
   nsTArray<WebAuthnExtension> extensions;
 
   const auto& selection = aOptions.mAuthenticatorSelection;
   const auto& attachment = selection.mAuthenticatorAttachment;
+  const AttestationConveyancePreference& attestation = aOptions.mAttestation;
 
   // Does the RP require attachment == "platform"?
   bool requirePlatformAttachment =
     attachment.WasPassed() && attachment.Value() == AuthenticatorAttachment::Platform;
 
   // Does the RP require user verification?
   bool requireUserVerification =
     selection.mUserVerification == UserVerificationRequirement::Required;
 
+  // Does the RP desire direct attestation? Indirect attestation is not
+  // implemented, and thus is equivilent to None.
+  bool requestDirectAttestation =
+    attestation == AttestationConveyancePreference::Direct;
+
+  // In Bug 1430150, if requestDirectAttestation is true, we will need to prompt
+  // the user for permission to proceed. For now, we ignore it.
+  Unused << requestDirectAttestation;
+
   // Create and forward authenticator selection criteria.
   WebAuthnAuthenticatorSelection authSelection(selection.mRequireResidentKey,
                                                requireUserVerification,
                                                requirePlatformAttachment);
 
   WebAuthnMakeCredentialInfo info(rpIdHash,
                                   clientDataHash,
                                   adjustedTimeout,
--- a/dom/webauthn/tests/mochitest.ini
+++ b/dom/webauthn/tests/mochitest.ini
@@ -2,16 +2,17 @@
 support-files =
   cbor/*
   pkijs/*
   u2futil.js
 skip-if = !e10s
 scheme = https
 
 [test_webauthn_abort_signal.html]
+[test_webauthn_attestation_conveyance.html]
 [test_webauthn_authenticator_selection.html]
 [test_webauthn_authenticator_transports.html]
 [test_webauthn_loopback.html]
 [test_webauthn_no_token.html]
 [test_webauthn_make_credential.html]
 [test_webauthn_get_assertion.html]
 [test_webauthn_override_request.html]
 [test_webauthn_store_credential.html]
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/tests/test_webauthn_attestation_conveyance.html
@@ -0,0 +1,113 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<head>
+  <title>W3C Web Authentication - Attestation Conveyance</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+  <script type="text/javascript" src="u2futil.js"></script>
+  <script type="text/javascript" src="pkijs/common.js"></script>
+  <script type="text/javascript" src="pkijs/asn1.js"></script>
+  <script type="text/javascript" src="pkijs/x509_schema.js"></script>
+  <script type="text/javascript" src="pkijs/x509_simpl.js"></script>
+  <script type="text/javascript" src="cbor/cbor.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+  <h1>W3C Web Authentication - Attestation Conveyance</h1>
+  <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1428916">Mozilla Bug 1428916</a>
+  <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1416056">Mozilla Bug 1416056</a>
+
+  <script class="testbody" type="text/javascript">
+    "use strict";
+
+    function getAttestationCertFromAttestationBuffer(aAttestationBuffer) {
+      return webAuthnDecodeCBORAttestation(aAttestationBuffer)
+      .then((aAttestationObj) => {
+        let attestationCertDER = aAttestationObj.attStmt.x5c[0];
+        let certDERBuffer = attestationCertDER.slice(0, attestationCertDER.byteLength).buffer;
+        let certAsn1 = org.pkijs.fromBER(certDERBuffer);
+        return new org.pkijs.simpl.CERT({ schema: certAsn1.result });
+      });
+    }
+
+    function verifyAnonymizedCertificate(aResult) {
+      // TODO: Update this logic with Bug 1430150.
+      // Until then, all certificates are direct.
+      return verifyDirectCertificate(aResult);
+    }
+
+    function verifyDirectCertificate(aResult) {
+      return getAttestationCertFromAttestationBuffer(aResult.response.attestationObject)
+      .then((attestationCert) => {
+        let subject = attestationCert.subject.types_and_values[0].value.value_block.value;
+        is(subject, "Firefox U2F Soft Token", "Subject name matches the direct Soft Token")
+      });
+    }
+
+    function arrivingHereIsBad(aResult) {
+      ok(false, "Bad result! Received a: " + aResult);
+    }
+
+    function expectTypeError(aResult) {
+      ok(aResult.toString().startsWith("TypeError"), "Expecting a TypeError, got " + aResult);
+    }
+
+    add_task(() => {
+      // Enable the softtoken.
+      return SpecialPowers.pushPrefEnv({"set": [
+        ["security.webauth.webauthn", true],
+        ["security.webauth.webauthn_enable_softtoken", true],
+        ["security.webauth.webauthn_enable_usbtoken", false],
+      ]});
+    });
+
+    // Start a new MakeCredential() request.
+    function requestMakeCredential(attestation) {
+      let publicKey = {
+        rp: {id: document.domain, name: "none", icon: "none"},
+        user: {id: new Uint8Array(), name: "none", icon: "none", displayName: "none"},
+        challenge: crypto.getRandomValues(new Uint8Array(16)),
+        timeout: 5000, // the minimum timeout is actually 15 seconds
+        pubKeyCredParams: [{type: "public-key", alg: cose_alg_ECDSA_w_SHA256}],
+        attestation,
+      };
+
+      return navigator.credentials.create({publicKey});
+    }
+
+    // Test success cases for make credential.
+    add_task(async () => {
+      // No selection criteria should be equal to none, which means anonymized
+      await requestMakeCredential()
+        .then(verifyAnonymizedCertificate)
+        .catch(arrivingHereIsBad);
+
+      // Request no attestation.
+      await requestMakeCredential("none")
+        .then(verifyAnonymizedCertificate)
+        .catch(arrivingHereIsBad);
+
+      // Request indirect attestation, which is the same as none.
+      await requestMakeCredential("indirect")
+        .then(verifyAnonymizedCertificate)
+        .catch(arrivingHereIsBad);
+
+      // Request direct attestation, which should prompt for user intervention,
+      // once 1430150 lands.
+      await requestMakeCredential("direct")
+        .then(verifyDirectCertificate)
+        .catch(arrivingHereIsBad);
+    });
+
+    // Test failure cases for make credential.
+    add_task(async () => {
+      // Request a platform authenticator.
+      await requestMakeCredential("unknown")
+        .then(arrivingHereIsBad)
+        .catch(expectTypeError);
+    });
+  </script>
+
+</body>
+</html>
--- a/dom/webidl/WebAuthentication.webidl
+++ b/dom/webidl/WebAuthentication.webidl
@@ -49,16 +49,17 @@ dictionary MakePublicKeyCredentialOption
     required PublicKeyCredentialUserEntity user;
 
     required BufferSource                            challenge;
     required sequence<PublicKeyCredentialParameters> pubKeyCredParams;
 
     unsigned long                                timeout;
     sequence<PublicKeyCredentialDescriptor>      excludeCredentials = [];
     AuthenticatorSelectionCriteria               authenticatorSelection;
+    AttestationConveyancePreference              attestation = "none";
     // Extensions are not supported yet.
     // AuthenticationExtensions                  extensions; // Add in Bug 1406458
 };
 
 dictionary PublicKeyCredentialEntity {
     required DOMString    name;
     USVString             icon;
 };
@@ -78,16 +79,22 @@ dictionary AuthenticatorSelectionCriteri
     UserVerificationRequirement  userVerification = "preferred";
 };
 
 enum AuthenticatorAttachment {
     "platform",       // Platform attachment
     "cross-platform"  // Cross-platform attachment
 };
 
+enum AttestationConveyancePreference {
+    "none",
+    "indirect",
+    "direct"
+};
+
 enum UserVerificationRequirement {
     "required",
     "preferred",
     "discouraged"
 };
 
 dictionary PublicKeyCredentialRequestOptions {
     required BufferSource                challenge;