Changeset 6754


Ignore:
Timestamp:
May 31, 2022, 12:01:30 PM (16 months ago)
Author:
Nicklas Nordborg
Message:

References #1396: Implement an login extension for WebAuthn?

Cleaned up the login process by moving most of the code to the LoginProcessHandler. An instance of this object is created in the first step by the PreLoginAuthenticationManager and is used to generate a challenge for the browser. The response from the browser is then processed by the same instance in the second step by the WebAuthnAuthenticationManager.

Location:
extensions/net.sf.basedb.webauthn/trunk
Files:
1 added
4 edited

Legend:

Unmodified
Added
Removed
  • extensions/net.sf.basedb.webauthn/trunk/src/net/sf/basedb/webauthn/PreLoginAuthenticationManager.java

    r6752 r6754  
    11package net.sf.basedb.webauthn;
    22
    3 import java.util.Collections;
    4 import java.util.Optional;
    5 import java.util.Set;
     3import com.yubico.webauthn.AssertionRequest;
    64
    7 
    8 import com.yubico.webauthn.AssertionRequest;
    9 import com.yubico.webauthn.CredentialRepository;
    10 import com.yubico.webauthn.RegisteredCredential;
    11 import com.yubico.webauthn.RelyingParty;
    12 import com.yubico.webauthn.StartAssertionOptions;
    13 import com.yubico.webauthn.data.ByteArray;
    14 import com.yubico.webauthn.data.PublicKeyCredentialDescriptor;
    15 import com.yubico.webauthn.data.RelyingPartyIdentity;
    16 
    17 import net.sf.basedb.core.Application;
    185import net.sf.basedb.core.AuthenticationContext;
    196import net.sf.basedb.core.authentication.AuthenticatedUser;
     
    218import net.sf.basedb.core.authentication.LoginException;
    229import net.sf.basedb.core.authentication.LoginRequest;
     10import net.sf.basedb.core.authentication.UnknownLoginException;
    2311import net.sf.basedb.core.data.UserData;
    2412
     
    5240    String login = request.getLogin();
    5341
    54     // Check if the user exists
     42    // Check if the user exists and has configured a security key
    5543    UserData user = context.getUserByLogin(login);
    56     if (user == null)
    57     {
    58       throw new LoginException("Unknown username '" + login + "'.");
    59     }
     44    if (user == null) throw new UnknownLoginException(login);
    6045    if (user.getExtended("webAuthnCredentialId") == null)
    6146    {
    62       throw new LoginException("User '"+login+"' has not configured a security key.");
     47      throw new LoginException("User '"+login+"' has not configured a WebAuthn Security Key.");
    6348    }
    6449   
    65     RelyingPartyIdentity rpi = RelyingPartyIdentity.builder()
    66       .id(request.getAttribute("serverName"))  // TODO -- config option
    67       .name(Application.getTitle())
    68       .build();
    69        
    70     RelyingParty rp = RelyingParty.builder()
    71       .identity(rpi)
    72       .credentialRepository(new NoCredentialRepository(user))
    73       .allowOriginPort(true) // TODO -- config option?
    74       .build();
    75 
    76     AssertionRequest assertionRequest = rp.startAssertion(StartAssertionOptions.builder()
    77        .username(login)
    78        .build());
     50    String serverName = request.getAttribute("serverName");
     51    LoginProcessHandler handler = new LoginProcessHandler(user, serverName);
     52    AssertionRequest assertionRequest = handler.getAssertionRequest();
    7953   
    80     context.getSessionControl().setSessionSetting("webauthn-assertion-request", assertionRequest);
    81    
     54    context.getSessionControl().setSessionSetting("webauthn-login-handler", handler);
    8255    throw new AssertionRequestException(assertionRequest);
    83   }
    84 
    85 
    86   static class NoCredentialRepository
    87     implements CredentialRepository
    88   {
    89    
    90     private final UserData user;
    91    
    92     NoCredentialRepository(UserData user)
    93     {
    94       this.user = user;
    95     }
    96    
    97     @Override
    98     public Set<PublicKeyCredentialDescriptor> getCredentialIdsForUsername(String username)
    99     {
    100       System.out.println("getCredentialIdsForUsername: "+username);
    101       String credentialId = (String)user.getExtended("webAuthnCredentialId");
    102       PublicKeyCredentialDescriptor pk = PublicKeyCredentialDescriptor.builder()
    103         .id(ByteArray.fromBase64(credentialId))
    104         .build();
    105       return Collections.singleton(pk);
    106     }
    107 
    108     @Override
    109     public Optional<ByteArray> getUserHandleForUsername(String username)
    110     {
    111       System.out.println("getUserHandleForUsername: "+username);
    112       String userHandle = (String)user.getExtended("webAuthnUserHandle");
    113       return Optional.of(ByteArray.fromBase64(userHandle));
    114     }
    115 
    116     @Override
    117     public Optional<String> getUsernameForUserHandle(ByteArray userHandle)
    118     {
    119       System.out.println("getUsernameForUserHandle: "+userHandle);
    120       return Optional.empty();
    121     }
    122 
    123     @Override
    124     public Optional<RegisteredCredential> lookup(ByteArray credentialId, ByteArray userHandle)
    125     {
    126       System.out.println("lookup: "+credentialId+"; "+userHandle);
    127      
    128       String publicKey = (String)user.getExtended("webAuthnPublicKey");
    129       Integer count = (Integer)user.getExtended("webAuthnSignatureCount");
    130       return Optional.of(RegisteredCredential.builder()
    131         .credentialId(credentialId)
    132         .userHandle(userHandle)
    133         .publicKeyCose(ByteArray.fromBase64(publicKey))
    134         .signatureCount(count == null ? 0 : count)
    135         .build());
    136     }
    137 
    138     @Override
    139     public Set<RegisteredCredential> lookupAll(ByteArray credentialId)
    140     {
    141       System.out.println("lookupAll: "+credentialId);
    142       return Collections.emptySet();
    143     }
    144 
    14556  }
    14657   
  • extensions/net.sf.basedb.webauthn/trunk/src/net/sf/basedb/webauthn/WebAuthn.java

    r6753 r6754  
    99import java.util.Set;
    1010
     11import com.yubico.webauthn.CredentialRepository;
     12import com.yubico.webauthn.RelyingParty;
     13import com.yubico.webauthn.data.RelyingPartyIdentity;
     14
     15import net.sf.basedb.core.Application;
    1116import net.sf.basedb.core.ConfigurationException;
    1217import net.sf.basedb.core.authentication.AuthenticationMethod;
    1318import net.sf.basedb.util.FileUtil;
     19import net.sf.basedb.util.Values;
    1420
    1521/**
     
    157163    return allowedAuthenticationMethods.contains("*") || allowedAuthenticationMethods.contains(auth.getMethod());
    158164  }
     165 
     166  /**
     167    Create a RelyingParty instance.
     168    @param id The ID taken from HTTP request parameters
     169    @param cred A credentials repository implementation
     170  */
     171  public static RelyingParty createRelyingParty(String id, CredentialRepository cred)
     172  {
     173    Properties cfg = getConfig(true);
     174    id = cfg.getProperty("relying-party-id", id);
     175    boolean port = Values.getBoolean(cfg.getProperty("relying-party-allow-origin-port"));
     176    boolean subdomain = Values.getBoolean(cfg.getProperty("relying-party-allow-origin-subdomain"));
     177    boolean disableCounter = Values.getBoolean(cfg.getProperty("relying-party-disable-signature-counter"));
     178   
     179    RelyingPartyIdentity rpi = RelyingPartyIdentity.builder()
     180      .id(id)
     181      .name(Application.getTitle())
     182      .build();
     183         
     184    RelyingParty rp = RelyingParty.builder()
     185      .identity(rpi)
     186      .credentialRepository(cred)
     187      .allowOriginPort(port)
     188      .allowOriginSubdomain(subdomain)
     189      .validateSignatureCounter(!disableCounter)
     190      .build();
     191    return rp;
     192  }
    159193
    160194}
  • extensions/net.sf.basedb.webauthn/trunk/src/net/sf/basedb/webauthn/WebAuthnAuthenticationManager.java

    r6753 r6754  
    11package net.sf.basedb.webauthn;
    22
    3 import com.yubico.webauthn.AssertionRequest;
     3
    44import com.yubico.webauthn.AssertionResult;
    5 import com.yubico.webauthn.FinishAssertionOptions;
    6 import com.yubico.webauthn.RelyingParty;
    7 import com.yubico.webauthn.data.AuthenticatorAssertionResponse;
    8 import com.yubico.webauthn.data.ClientAssertionExtensionOutputs;
    9 import com.yubico.webauthn.data.PublicKeyCredential;
    10 import com.yubico.webauthn.data.RelyingPartyIdentity;
    115
    12 import net.sf.basedb.core.Application;
    136import net.sf.basedb.core.AuthenticationContext;
    147import net.sf.basedb.core.authentication.AuthenticatedUser;
     
    1710import net.sf.basedb.core.authentication.LoginRequest;
    1811import net.sf.basedb.core.data.UserData;
    19 import net.sf.basedb.webauthn.PreLoginAuthenticationManager.NoCredentialRepository;
    2012
    2113/**
    22   Authentication
     14  Authentication manager for the final step in the WebAuthn authentication
     15  process. It will first verify that the login+password is correct and
     16  then verify the response from the security key.
    2317 
    2418  @author nicklas
     
    6054    }
    6155
     56    // Check the password with the internal authentication
    6257    AuthenticatedUser auth = context.verifyUserInternal(request);
    6358    UserData user = context.getUserById(auth.getInternalId());
     
    7368        throw new LoginException("User '" + login + "' has not configured a WebAuthn Security Key.");
    7469      }
     70      return null;
    7571    }
    76     else
     72   
     73    // Get and clear the stored login handler
     74    LoginProcessHandler handler = context.getSessionControl().setSessionSetting("webauthn-login-handler", null);
     75    if (handler == null)
    7776    {
    78      
    79       try
    80       {
    81         PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> pkc = PublicKeyCredential.parseAssertionResponseJson(assertionResponse);
    82         AssertionRequest ar = context.getSessionControl().getSessionSetting("webauthn-assertion-request");
    83         FinishAssertionOptions ass = FinishAssertionOptions.builder()
    84             .request(ar)
    85             .response(pkc)
    86             .build();
    87  
    88         RelyingPartyIdentity rpi = RelyingPartyIdentity.builder()
    89             .id("localhost")  // TODO -- config option
    90             .name(Application.getTitle())
    91             .build();
    92            
    93         RelyingParty rp = RelyingParty.builder()
    94           .identity(rpi)
    95           .credentialRepository(new NoCredentialRepository(user))
    96           .allowOriginPort(true) // TODO -- config option?
    97           .build();
    98    
    99         AssertionResult asResult = rp.finishAssertion(ass);
    100        
    101         if (!asResult.isSuccess())
    102         {
    103           System.out.println("webautn no success");
    104          
    105           throw new LoginException("Login failed");
    106         }
    107         Integer count = (Integer)user.getExtended("webAuthnSignatureCount");
    108         user.setExtended("webAuthnSignatureCount", count+1);
    109        
    110       }
    111       catch (Exception ex)
    112       {
    113         ex.printStackTrace(System.out);
    114         throw new LoginException("Login failed", ex);
    115       }
    116      
    117       auth = new AuthenticatedUser(WebAuthn.AUTHENTICATION_METHOD, user);
     77      throw new LoginException("No login handler exists for user '"+login+"'");
    11878    }
     79    AssertionResult result = handler.processAssertionResponse(assertionResponse);
     80    user.setExtended("webAuthnSignatureCount", (int)result.getSignatureCount());
     81    auth = new AuthenticatedUser(WebAuthn.AUTHENTICATION_METHOD, user);
    11982    return auth;
    12083  }
  • extensions/net.sf.basedb.webauthn/trunk/webauthn.properties

    r6753 r6754  
    2727## override the 'no-webauthn' or 'require-webauthn' settings.
    2828# allow-other-authentication =
     29
     30## The ID of the RelyingParty is normally taken from the HTTP
     31## request headers. If, for some reason, that doesn't work as
     32## expected it is possible manually configure it here
     33# relying-party-id = localhost
     34
     35## A flag that can be set to allow the RelyingParty to match
     36## against any port number. Should not be needed except for
     37## developers that use non-standard ports.
     38# relying-party-allow-origin-port = 1
     39
     40## A flag that can be set to allow the RelyingParty to match
     41## against any subdomain to the id.
     42# relying-party-allow-origin-subdomain = 1
     43
     44## A flag for disabling validation of the signature counter
     45## This counter is an increasing number intended to prevent
     46## replay attacks. It is recommended to keep this enabled,
     47## unless it is causing problems with security keys that
     48## don't support the counter
     49# relying-party-disable-signature-counter = 1
Note: See TracChangeset for help on using the changeset viewer.