Class PasskeyService

java.lang.Object
com.iizix.server.user.passkey.PasskeyService

public final class PasskeyService extends Object
The passkey ceremonies of the server (CC-LOGIN-L8-PASSKEYS): the options a ceremony starts with, the verification it ends with, and what the server keeps in between - the challenge, the sign-in that waits for a passkey, and what a client reported about itself. One instance per server (getInstance()); everything it keeps is in memory, bound to the key of a client session, limited in time and in number.

The class knows no session object and no application: the user authentication interface (UserAuthenticationUIImpl) resolves them, checks the access and the throttle, and records the user; the settings of the application come as PasskeySettings. That is what lets the tests run every ceremony on recorded authenticator responses.

The options. A registration asks for a discoverable credential with user verification, offers ES256, EdDSA and RS256, asks for no attestation, and excludes the passkeys the user has. A sign-in asks for user verification; it names the passkeys of the user when the user is known to the server already (a sign-in that waits for a passkey, a signed-in session), and none when it is not - nothing a stranger types makes the server name a credential, so nothing tells whether an account exists or has passkeys.

Author:
Christopher Mindus
  • Field Details

    • CHALLENGE_MS

      public static final long CHALLENGE_MS
      The time a challenge is valid: 5 minutes; 15 minutes for the autofill of the user name field, which waits on the page until the user picks a passkey.
      See Also:
    • CONDITIONAL_CHALLENGE_MS

      public static final long CONDITIONAL_CHALLENGE_MS
      The time a challenge is valid: 5 minutes; 15 minutes for the autofill of the user name field, which waits on the page until the user picks a passkey.
      See Also:
    • PENDING_MS

      public static final long PENDING_MS
      The time a sign-in waits for its passkey: 5 minutes.
      See Also:
    • RECOVERY_WAIT_MS

      public static final long RECOVERY_WAIT_MS
      The time a passkey that was registered through an account recovery is no proof of a second factor, from its registration: 24 hours (CC-LOGIN-PASSKEY-SECOND-FACTOR, T1-039; D-27 of the SignIn survey). Until then it is a first factor, as a password is: where a second factor is due, the sign-in with it is asked for one.
      See Also:
    • CAPABILITIES_MS

      public static final long CAPABILITIES_MS
      The time the report of a client about itself is kept: 12 hours.
      See Also:
    • CLIENT_TIMEOUT_MS

      public static final int CLIENT_TIMEOUT_MS
      The timeout the options give the client for the ceremony, in milliseconds.
      See Also:
    • MAX_PASSKEYS

      public static final int MAX_PASSKEYS
      The most passkeys a user may have for one relying party.
      See Also:
    • MAX_PENDING

      public static final int MAX_PENDING
      The most sign-ins that wait, and the most client reports that are kept.
      See Also:
    • MAX_CAPABILITIES

      public static final int MAX_CAPABILITIES
      The most sign-ins that wait, and the most client reports that are kept.
      See Also:
    • ALGORITHMS

      public static final Set<Integer> ALGORITHMS
      The COSE algorithms offered, the preferred first: ES256, EdDSA, RS256.
  • Constructor Details

    • PasskeyService

      public PasskeyService(PasskeyChallenges challenges, LongSupplier clock)
      Creates a service on a challenge store and a clock: for tests.
      Parameters:
      challenges - The challenge store.
      clock - The clock, milliseconds.
      Throws:
      NullPointerException - If a parameter is null.
  • Method Details

    • getInstance

      public static PasskeyService getInstance()
      Gets the service of the server.
      Returns:
      The instance.
    • getChallengeValidity

      public long getChallengeValidity(boolean conditional)
      The time a challenge the server makes is valid, in milliseconds (CC-FIX-PASSKEY-RETRY-CHALLENGE): what startAssertion(String, PasskeySettings, long, boolean) and startRegistration(String, PasskeySettings, long, String, String) give it. The arming of a passkey ceremony renews the challenge on the client before this time runs out, so that a button left waiting never runs a ceremony against a challenge the server has already forgotten.
      Parameters:
      conditional - true for the challenge of the user-name autofill, which waits on the page and is kept longer.
      Returns:
      The time in milliseconds.
    • renewChallenge

      public boolean renewChallenge(String session, PasskeyChallenges.Kind kind)
      Extends the challenge of a ceremony under way so that a passkey button left waiting does not run its ceremony against a challenge the server has forgotten (CC-FIX-PASSKEY-RETRY-CHALLENGE): the SignIn module renews the challenge of an armed button before getChallengeValidity(boolean) runs out. The challenge bytes are unchanged and still single-use.
      Parameters:
      session - The key of the client session.
      kind - The kind of ceremony: PasskeyChallenges.Kind.ASSERTION or PasskeyChallenges.Kind.REGISTRATION.
      Returns:
      true when a challenge of the session and kind was under way and was extended.
      Throws:
      NullPointerException - If a parameter is null.
    • setCapabilities

      public void setCapabilities(String session, boolean webAuthn, boolean platformAuthenticator, boolean conditionalMediation)
      Records what a client reports about itself.
      Parameters:
      session - The key of the client session.
      webAuthn - The client can do WebAuthn.
      platformAuthenticator - The device has a user-verifying platform authenticator.
      conditionalMediation - The client can offer passkeys in the autofill of a field.
      Throws:
      NullPointerException - If session is null.
    • getCapabilities

      public PasskeyService.Capabilities getCapabilities(String session)
      Gets what a client reported about itself.
      Parameters:
      session - The key of the client session.
      Returns:
      The report; one that says no to everything when the client reported nothing or its report is older than CAPABILITIES_MS.
      Throws:
      NullPointerException - If session is null.
    • isOffered

      public boolean isOffered(PasskeySettings settings, String session)
      Checks if passkeys are offered to a client session: the application offers them and the client reported that it can do WebAuthn.
      Parameters:
      settings - The settings of the application.
      session - The key of the client session.
      Returns:
      true when a ceremony can be started.
      Throws:
      NullPointerException - If a parameter is null.
    • isRequired

      public boolean isRequired(PasskeySettings settings, String session)
      Checks if the policy "require a passkey where available" applies to a client session: the application offers passkeys and has the policy on, and the client reported WebAuthn and a user-verifying platform authenticator. The report is the client's own statement: a client that says it has no authenticator is not asked for a passkey, which is what "where available" means - the policy that holds for every client is the second factor.
      Parameters:
      settings - The settings of the application.
      session - The key of the client session.
      Returns:
      true when a sign-in with a password must be completed with a passkey.
      Throws:
      NullPointerException - If a parameter is null.
    • hold

      Holds a sign-in whose password was accepted until its passkey arrives. A sign-in of the client session that waits already is replaced.
      Parameters:
      session - The key of the client session.
      settings - The settings of the application.
      info - The authentication the password check made, successful, with its user.
      Returns:
      The sign-in that waits.
      Throws:
      NullPointerException - If a parameter is null.
      IllegalArgumentException - If the authentication is not a successful one with a user.
      PasskeyException - If too many sign-ins wait.
      SQLException - For SQL errors.
    • hold

      public PasskeyService.PendingSignIn hold(String session, PasskeySettings settings, UserAuthenticationInfo info, String secondFactor) throws PasskeyException, SQLException
      Holds a sign-in whose password was accepted, and whose second factor when one was due, until its passkey arrives (CC-SIGNIN-MERGE-L5-L6-L8). A sign-in of the client session that waits already is replaced.
      Parameters:
      session - The key of the client session.
      settings - The settings of the application.
      info - The authentication the password check made, successful, with its user.
      secondFactor - The method of the second factor the sign-in verified before it waits, null when it verified none.
      Returns:
      The sign-in that waits.
      Throws:
      NullPointerException - If session, settings or info is null.
      IllegalArgumentException - If the authentication is not a successful one with a user.
      PasskeyException - If too many sign-ins wait.
      SQLException - For SQL errors.
    • getPending

      public PasskeyService.PendingSignIn getPending(String session)
      Gets the sign-in of a client session that waits for its passkey.
      Parameters:
      session - The key of the client session.
      Returns:
      The sign-in, null when none waits or its time ran out.
      Throws:
      NullPointerException - If session is null.
    • release

      public void release(String session)
      Ends the wait of a client session's sign-in: it was completed, or another sign-in took its place.
      Parameters:
      session - The key of the client session.
      Throws:
      NullPointerException - If session is null.
    • startAssertion

      public String startAssertion(String session, PasskeySettings settings, long userID, boolean conditional) throws PasskeyException, SQLException
      Starts a sign-in or a confirmation with a passkey.
      Parameters:
      session - The key of the client session.
      settings - The settings of the application, with passkeys enabled.
      userID - The user the server knows already - of the sign-in that waits, or of the signed-in session - or zero for none.
      conditional - true for the autofill of the user name field.
      Returns:
      The JSON text of the PublicKeyCredentialRequestOptionsJSON.
      Throws:
      NullPointerException - If session or settings is null.
      PasskeyException - If the application offers no passkeys, or too many challenges are under way.
      SQLException - For SQL errors.
    • startAssertion

      public String startAssertion(String session, PasskeySettings settings, long userID, boolean conditional, boolean secondFactor) throws PasskeyException, SQLException
      Starts a sign-in or a confirmation with a passkey, or the second factor of a sign-in that waits for one (CC-LOGIN-PASSKEY-SECOND-FACTOR, T1-055).
      Parameters:
      session - The key of the client session.
      settings - The settings of the application, with passkeys enabled.
      userID - The user the server knows already - of the sign-in that waits, or of the signed-in session - or zero for none.
      conditional - true for the autofill of the user name field.
      secondFactor - true when the passkey is asked for as the second factor of the user: the request names the passkeys that are the proof of one (getSecondFactors(PasskeySettings, long)), not the ones that wait after a recovery.
      Returns:
      The JSON text of the PublicKeyCredentialRequestOptionsJSON.
      Throws:
      NullPointerException - If session or settings is null.
      PasskeyException - If the application offers no passkeys, or too many challenges are under way.
      SQLException - For SQL errors.
    • finishAssertion

      public PasskeyService.Asserted finishAssertion(String session, PasskeySettings settings, String responseJSON) throws PasskeyException, SQLException
      Finishes a sign-in or a confirmation with a passkey: the answer is verified against the challenge of the client session and the stored public key, and the use is recorded.
      Parameters:
      session - The key of the client session.
      settings - The settings of the application, with passkeys enabled.
      responseJSON - The JSON text of the client's AuthenticationResponseJSON.
      Returns:
      The verified sign-in: the passkey, whose owner is the user who signed in.
      Throws:
      NullPointerException - If a parameter is null.
      PasskeyService.ExpiredException - If no challenge is under way for the client session.
      PasskeyException - If the answer is refused; the message says why.
      SQLException - For SQL errors.
    • waitsAfterRecovery

      public boolean waitsAfterRecovery(StoredPasskey passkey)
      Checks if a passkey waits after the account recovery it was registered through (CC-LOGIN-PASSKEY-SECOND-FACTOR, T1-039): for RECOVERY_WAIT_MS from its registration such a passkey is no proof of a second factor. Its answers verify as those of every passkey do; what an answer is worth is the caller's to decide (PasskeyService.Asserted.recoveryWait).
      Parameters:
      passkey - The passkey.
      Returns:
      true when the passkey was registered through a recovery less than 24 hours ago.
      Throws:
      NullPointerException - If passkey is null.
    • getSecondFactors

      public List<StoredPasskey> getSecondFactors(PasskeySettings settings, long userID) throws SQLException
      Gets the passkeys of a user that are the proof of a second factor: the passkeys of the user for the relying party of the application, but the ones that wait after a recovery.
      Parameters:
      settings - The settings of the application.
      userID - The user ID.
      Returns:
      The passkeys, the newest first; empty when the user has none, and when the application has no relying party ID.
      Throws:
      NullPointerException - If settings is null.
      SQLException - For SQL errors.
    • startRegistration

      public String startRegistration(String session, PasskeySettings settings, long userID, String userName, String displayName) throws PasskeyException, SQLException
      Starts the registration of a passkey for a user the server has authenticated.
      Parameters:
      session - The key of the client session.
      settings - The settings of the application, with passkeys enabled.
      userID - The user ID.
      userName - The user name: the name of the account the authenticator shows.
      displayName - The name of the person, null or blank for the user name.
      Returns:
      The JSON text of the PublicKeyCredentialCreationOptionsJSON.
      Throws:
      NullPointerException - If session, settings or userName is null.
      PasskeyService.LimitException - If the user has MAX_PASSKEYS passkeys.
      PasskeyException - If the application offers no passkeys, or too many challenges are under way.
      SQLException - For SQL errors.
    • finishRegistration

      public StoredPasskey finishRegistration(String session, PasskeySettings settings, long userID, String responseJSON, String nickname) throws PasskeyException, SQLException
      Finishes the registration of a passkey: the answer is verified against the challenge of the client session, and the passkey is stored for the user.
      Parameters:
      session - The key of the client session.
      settings - The settings of the application, with passkeys enabled.
      userID - The user the server has authenticated NOW, as when the registration was started.
      responseJSON - The JSON text of the client's RegistrationResponseJSON.
      nickname - The name the user gives the passkey, null or blank for the default.
      Returns:
      The stored passkey.
      Throws:
      NullPointerException - If session, settings or responseJSON is null.
      PasskeyService.ExpiredException - If no challenge is under way for the client session.
      PasskeyException - If the answer is refused; the message says why.
      SQLException - For SQL errors.
    • finishRegistration

      public StoredPasskey finishRegistration(String session, PasskeySettings settings, long userID, String responseJSON, String nickname, boolean recovery) throws PasskeyException, SQLException
      Finishes the registration of a passkey, as finishRegistration(String,PasskeySettings,long,String,String) does, and marks a passkey that is registered through an account recovery (CC-LOGIN-PASSKEY-SECOND-FACTOR, T1-039).
      Parameters:
      session - The key of the client session.
      settings - The settings of the application, with passkeys enabled.
      userID - The user the server has authenticated NOW, as when the registration was started.
      responseJSON - The JSON text of the client's RegistrationResponseJSON.
      nickname - The name the user gives the passkey, null or blank for the default.
      recovery - true when the user got here through an account recovery: the passkey is stored with the mark, and is no proof of a second factor for RECOVERY_WAIT_MS (waitsAfterRecovery(StoredPasskey)).
      Returns:
      The stored passkey.
      Throws:
      NullPointerException - If session, settings or responseJSON is null.
      PasskeyService.ExpiredException - If no challenge is under way for the client session.
      PasskeyException - If the answer is refused; the message says why.
      SQLException - For SQL errors.
    • nickname

      public static String nickname(String nickname, long now)
      Makes the name of a passkey as it is stored: without control characters, cut to 100 characters; the default is the date of the registration.
      Parameters:
      nickname - The name the user gave, null or blank for the default.
      now - The time of the registration, epoch milliseconds.
      Returns:
      The name, never empty.
    • getCounts

      public Map<String,Integer> getCounts()
      Gets the counts of what the service keeps, for the tests and the log.
      Returns:
      The challenges under way, the sign-ins that wait, the client reports.