preface

Let’s start with a few questions:

  1. Is your Web system really secure?
  2. Do users really want to use their brains to remember each system’s account password?
  3. How do we ensure that the account and password will not be stolen in transit?

Think about it before you read…

Web Traditional login mode

1. Account password

This is the most common user login method. Before a user enters the system, the server verifies the user login status. According to the status, the server notifies the client whether to redirect the user to the login page.

2. SMS verification code

Users in the login page to enter a phone number, enter a phone number click on send verification code, the client will need to verify the phone number is sent to the server, the server receives the request randomly generated a set of Numbers, have sent text messages to the user’s mobile phone, the server will be the verification code in the cache with the account mapping relation, The user only needs to enter the verification code in the form;

(Mail and TWO-DIMENSIONAL code login although different in technical implementation, but in the form and SMS similar, so do not repeat here)

3. Account password + SMS or email etc…

This method is also common, also known as multi-factor authentication, usually after you submit the correct account password, still need to send you a verification message, the common is SMS or email or U shield or security certificate and other things.

Is that really all there is?

Of course not!

There’s also a cool way to log in — WebAuthn;

So what is WebAuthn?

The Web Authentication API (also known as WebAuthn) uses Asymmetric public-key cryptography instead of passwords or SMS messages to register, log in, and second-factor on websites Authentication (two-factor authentication). The same solves the major security issues such as PHishing, data destruction, SMS text attacks, other two-factor verification, while significantly improving the ease of use (because users don’t have to manage many increasingly complex passwords). Quote from MDN

In fact, it is very simple. It calls the validator (TouchID, FaceID, Windows Hello, USB disk validator, etc.) on the user’s device through API, and adopts asymmetric encryption to cancel the transmission of password and avoid the risk of plaintext transmission of password. It also saves users from having to remember passwords for annoying traditional operations;

While we enjoy the convenience brought by the Internet, we also need to use the services provided by different suppliers, and each service needs its own account system (after all, everyone is Xiao Ma Ge, a QQ number can eat the game). Due to the number of accounts, you need to set up different accounts and passwords, and some services with higher security level also need you to change the password regularly (for example, the password of the enterprise email, every time you change the password is really racking your brains…) .

At this point, your brain will need to remember a lot of passwords (except SSO, no bars, you win if you want bars).

When did WebAuthn appear?

WebAuthn was released as a W3C recommendation on March 4, 2019, just two years ago. The Level 2 specification is under development.

What problem does it solve?

The first is to reduce the leakage of the password leading to theft. Under normal circumstances, our password leakage will be in the following ways:

  1. Phishing;
  2. Keyboard recording;
  3. Data breaches;

Generally, 91% of information security attacks begin with phishing, and 80% of attacks on enterprises include phishing.

Let’s look at a set of data, the trend chart of phishing replacing exploitation-based malware:

Take advantage of weekly detected malware and phishing sites

In fact, a well-designed phishing site has a 43% success rate, and 76% of accounts are stolen due to weak passwords;

So at this point, the W3C came up with a standard API, with the involvement of Google, Mozilla, Microsoft, Yubico, and others. The API allows the server to register and authenticate users using public key encryption instead of a password.

It allows the server to integrate with strong authenticators that are now built into the device, such as Windows Hello or Apple Touch ID. A pair of public and private keys (called credentials) rather than passwords are created for the web site. The private key is securely stored on the user’s device; The public key and the randomly generated credential ID are sent to the server for storage, which can then use the public key to prove the user’s identity.

What does webAuthn do for security?

I think there are three elements:

  1. Hardware guarantee: it is supported by a hardware security module that can securely store the private key and perform the encryption required by WebAuthn;
  2. Clear scope: Key pairs are only useful to specific sources, such as browser cookies. Key pairs registered in’ webAuthn. juejin’ cannot be used in’ evil-webAuthn. juejin’ to mitigate phishing threats;
  3. Proof of reliability: Authenticators can provide a certificate that helps servers verify that the public key really comes from a validator they trust, and not from a spurious source.

How does it work?

Let’s take a closer look at how WebAuthn works and how it can verify that you are logged in without transferring your password over the network.

registered

Image from MDN. (so good, I can’t think of a more understandable way to express it, lazy (#^.^#))

To start, we need to verify that the current client supports the API:

PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()

PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()

// The API returns a Boolean to tell you whether the current client supports this functionality
Copy the code

First, if the support, we will launch a registration request to the server, usually to bring our account user ID, the server to retrieve database, to find whether have the same ID, if there is no so congratulations you, you can use your fancy ID, and at the same time the server will respond to this request, It will return the user a JSON that looks something like this.

const publicKeyCredentialCreationOptions = {
  challenge: Uint8Array.from(
        randomStringFromServer, c= > c.charCodeAt(0)),
  rp: {
    name: "Example CORP".id  : "login.example.com"
  },
  user: {
    id: new Uint8Array(16),
    name: "[email protected]".displayName: "John Doe"
  },
  pubKeyCredParams: [{type: "public-key".alg: -7}].timeout: 60000.attestation: "direct"
};

Copy the code

Challenge: The challenge is an encrypted random byte buffer generated on the server to protect against “replay attacks”. The W3C specification.

Rp: This stands for “dependent party”; It can be viewed as describing the organization responsible for registering and authenticating users. The id must currently be a subset of the domain in the browser. For example, ID The valid value of this page is. The W3C specification.

User: This is information about the current registered user. The authenticator uses to associate id credentials with the user. It is recommended not to use personally identifiable information as the ID, as it may be stored in the authenticator. The W3C specification.

PubKeyCredParams: This is an array of objects describing which public key types the server can accept. The ALG is described in a number of COSE registries; For example, -7 indicates that the server accepts an elliptic curve public key using the SHA-256 signature algorithm. The W3C specification.

AuthenticatorSelection: This optional object helps dependencies further restrict the types of authenticators allowed to be registered. In this example, we indicate that we want to register a cross-platform authenticator (such as Yubikey) rather than a Platform authenticator like Windows Hello or Touch ID. The W3C specification.

Timeout: The time (in milliseconds) in which the user must respond to a registration prompt before an error is returned. The W3C specification.

Attestation: The proof data returned from the validator has information that can be used to track the user. This option allows the server to indicate the importance of the certification data to this registration event. The value “None” indicates that the server does not care about proof. The value “indirect” indicates that the server will allow anonymous proof data. Direct means that the server wants to receive proof data from the validator. The W3C specification.

Normally we don’t need to modify the JSON. Once we get the JSON, we call the validator:

const credential = await navigator.credentials.create({
    publicKey: publicKeyCredentialCreationOptions
});
Copy the code

The browser to the certifier call authenticatorMakeCredential (), inside the browser, the browser will validate parameters with default values completion lacking, these parameters will then become AuthenticatorResponse. ClientDataJSON. One of the most important parameters is Origin, which is part of clientData, and the server will be able to verify it later. The arguments to call create() are passed to the authenticator along with clientDataJSON’s SHA-256 hash (only the hash is sent because the connection to the authenticator may be a low-bandwidth NFC or Bluetooth connection, after which the authenticator only needs to sign the hash to ensure that it cannot be tampered with).

Authenticator creates a new key pair and proof – Before proceeding to the next step, authenticators usually require user confirmation in some form, such as entering a PIN, using a fingerprint, performing an iris scan, etc., to prove that the user is present and agrees to register. After that, the authenticator creates a new asymmetric key pair and securely stores the private key for future authentication. The public key will become part of the proof and will be signed by a private key burned into the authenticator during production, which will have a certificate chain that can be verified.

The call to create() returns a credential to the browser, which is an object containing the public key and other attributes used to validate the registration event:

console.log(credential);

PublicKeyCredential {
    id: 'ADSUllKQmbqdGtpu4sjseh4cg2TxSvrbcHDTBsv4NSSX9... '.rawId: ArrayBuffer(59),
    response: AuthenticatorAttestationResponse {
        clientDataJSON: ArrayBuffer(121),
        attestationObject: ArrayBuffer(306),},type: 'public-key'
}
Copy the code

Id: id of the newly generated certificate; It will be used to identify credentials when authenticating the user, where the ID is provided as a Base64 encoded string. The W3C specification

RawId: ID again, but in binary form. The W3C specification

ClientDataJSON: This represents data passed from the browser to the authenticator to associate the new credentials with the server and browser. The authenticator provides this as a UTF-8 byte array. The W3C specification

Example: Resolving clientDataJSON

const utf8Decoder = new TextDecoder('utf-8');
const decodedClientData = utf8Decoder.decode(
    credential.response.clientDataJSON)

const clientDataObj = JSON.parse(decodedClientData);

console.log(clientDataObj)

{
    challenge: "p5aV2uHXr0AOqUk7HQitvi-Ny1....".origin: "https://juejin.cn".type: "juejin.cn"
}
Copy the code

The utF-8 byte array transform in clientDataJSON parses the strings provided by the authentication into JSON-parsable strings. On this server, PublicKeyCredential validates this (and other data) to ensure that the registration event is valid.

Challenge: This is the same challenge passed to the create() call. The server must verify that the returned challenge matches the one generated for this registration event. Origin: The server must verify that this “source” string matches the source of the application. Type: The server verifies that the string is actually “webAuthn.create”. If another string is provided, the authenticator performed the wrong operation.

AttestationObject: This object contains the credential public key or optional proof certificate and other metadata that is also used to validate registration events. It is binary data encoded in CBOR. The W3C specification

Example: Parse attestationObject

const decodedAttestationObj = CBOR.decode(
    credential.response.attestationObject);

console.log(decodedAttestationObject);
{
    authData: Uint8Array(196),
    fmt: "fido-u2f".attStmt: {
        sig: Uint8Array(70),
        x5c: Array(1),}}Copy the code

AuthData: The authenticator data here is a byte array that contains metadata about the registration event and the public key that we will use for future authentication.

FMT: This indicates the proof format. The certifier can provide certification data in a variety of ways; This indicates how the server should parse and validate the proof data.

AttStmt: This is the proof statement. Depending on the indicated proof format, this object will look different. In this case, we get a signed SIG and certificate X5C. The server uses this data to encrypt the credential public key from the authenticator. In addition, the server can use certificates to reject authenticators that are considered weak.

The validator returns this set of data to the client, and finally:

Server validates data and completes registration – The server performs a series of checks to ensure registration is complete and data has not been tampered with. The steps include:

  1. Verify that the received challenge is the same as the sent challenge
  2. Make sure origin is consistent with expectations
  3. Verify clientDataHash’s signature and proof using the certificate chain corresponding to the authenticator type number

A complete list of validation steps can be found in the WebAuthn specification. Once authentication is successful, the server associates the new public key with the user account for future use when the user wishes to use the public key for authentication.

landing

The old rules:

The same humble assault delete…. !

When logging in, the user line requests the server to say that the baby is logging in, and the server issues a challenge and the public key associated with the previous account to the client.

After the client receives the server’s data started calling validator: the navigator. Credentials. The get () during the period of authentication, user prove that they have registered their private key. Them by providing a an assertion, this is by calling the navigator. Credentials. The get () generated by the client. This retrieves the credentials containing the signature generated during registration.

const credential = await navigator.credentials.get({
    publicKey: publicKeyCredentialRequestOptions
});

Copy the code

The publicKeyCredentialCreationOptions object contains a number of required and optional fields, server specify the field for the user to create a new certificate.

const publicKeyCredentialRequestOptions = {
    challenge: Uint8Array.from(
        randomStringFromServer, c= > c.charCodeAt(0)),
    allowCredentials: [{
        id: Uint8Array.from(
            credentialId, c= > c.charCodeAt(0)),
        type: 'public-key'.transports: ['usb'.'ble'.'nfc'],}],timeout: 60000,}const assertion = await navigator.credentials.get({
    publicKey: publicKeyCredentialRequestOptions
});
Copy the code

Challenge: Just as during registration, this must be an encrypted random byte generated on the server. The W3C specification

AllowCredentials: This array tells the browser which credentials the server wants the user to use for authentication. Saved during the credentialId retrieval and pass registration here. The server can choose to indicate its preferred transport mode, such as USB, NFC, and Bluetooth. The W3C specification

Timeout: As during registration, this optionally indicates the time (in milliseconds) at which the user must respond to authentication prompts. The W3C specification

Assertion The object returned from the call is another object. Slightly different from what we received when we registered; In particular, it includes a member and not a public key. get() PublicKeyCredentialsignature

console.log(assertion);

PublicKeyCredential {
    id: 'ADSUllKQmbqdGtpu4sjseh4cg2TxSvrbcHDTBsv4NSSX9... '.rawId: ArrayBuffer(59),
    response: AuthenticatorAssertionResponse {
        authenticatorData: ArrayBuffer(191),
        clientDataJSON: ArrayBuffer(118),
        signature: ArrayBuffer(70),
        userHandle: ArrayBuffer(10),},type: 'public-key'
}
Copy the code

Id: Identifier of the credential used to generate the authentication assertion. The W3C specification

RawId: Identifier again, but in binary form. The W3C specification

AuthenticatorData: This authentication data similar to authData is received when registered, with one notable exception the public key is not included. It is another item used as a source byte during authentication to generate the assertion signature. The W3C specification

ClientDataJSON: A collection of data passed from the browser to the authenticator during registration. It is one of the items used as the source byte to generate the signature during authentication. The W3C specification

Signature: The signature generated by the private key associated with this credential. On the server, the public key will be used to verify that this signature is valid. The W3C specification

UserHandle: This field is optionally provided by the authenticator and represents that User.id was provided during registration. It can be used to associate this assertion with a user on the server. It is encoded here as a UTF-8 byte array. The W3C specification

Once the assertion is obtained, it is sent to the server for validation. After the validation data is fully validated, the signature is validated using the public key stored in the database during registration.

Example: Verify assertion signature on server (pseudocode)

const storedCredential = await getCredentialFromDatabase(
    userHandle, credentialId);

const signedData = (
    authenticatorDataBytes +
    hashedClientDataJSON);

const signatureIsValid = storedCredential.publicKey.verify(
    signature, signedData);

if (signatureIsValid) {
    return "Verification successful! 🎉";
} else {
    return "Verification failed. 😭"
}
Copy the code

Authentication can look different depending on the language and cryptographic library used on the server. However, the general procedure remains the same.

  • The server retrieves the public key object associated with the user;
  • The server uses the public key to verify the signature, which is clientDataJSON generated using the authenticatorData byte and sha-256 hash;

This is the user and login process.

conclusion

While Web authentication is an important tool, it is important to remember that security is not a single technology; It is a way of thinking that should be integrated into every step of software design and development. Web authentication can be an important part of this process, forcing 80% of attacks to adapt or disappear.

reference

There are some good documents for you to refer to:

  • Docs. Cotter. App/quickstart -…
  • Docs. Cotter. App/SDK – referen…

For accuracy, I refer to these very good articles to write this article:

  • Developer.mozilla.org/zh-CN/docs/…
  • En.wikipedia.org/wiki/WebAut…
  • webauthn.guide/
  • w3c.github.io/webauthn/

Finally, I recommend a very good blog:

Juejin. Cn/post / 691389…