-
Notifications
You must be signed in to change notification settings - Fork 44
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #182 from walt-id/increase-oidc-configurability
OIDC Unique Subject Authentication Strategy, alternative authentication header (see description for more)
- Loading branch information
Showing
42 changed files
with
1,545 additions
and
75 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
54 changes: 54 additions & 0 deletions
54
...wallet-api/src/main/kotlin/id/walt/webwallet/service/account/OidcUniqueSubjectStrategy.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package id.walt.webwallet.service.account | ||
|
||
import id.walt.webwallet.db.models.Accounts | ||
import id.walt.webwallet.db.models.OidcLogins | ||
import id.walt.webwallet.utils.JwkUtils.verifyToken | ||
import id.walt.webwallet.web.model.OidcUniqueSubjectRequest | ||
import kotlinx.datetime.Clock | ||
import kotlinx.datetime.toJavaInstant | ||
import kotlinx.uuid.UUID | ||
import kotlinx.uuid.generateUUID | ||
import org.jetbrains.exposed.sql.insert | ||
import org.jetbrains.exposed.sql.transactions.transaction | ||
|
||
object OidcUniqueSubjectStrategy : AccountStrategy<OidcUniqueSubjectRequest>("oidc-unique-subject") { | ||
override suspend fun register(tenant: String, request: OidcUniqueSubjectRequest): Result<RegistrationResult> { | ||
val jwt = verifyToken(request.token) | ||
val sub = jwt.subject | ||
|
||
if (AccountsService.hasAccountOidcId(sub)) { | ||
throw IllegalArgumentException("Account already exists with OIDC id: ${request.token}") | ||
} | ||
|
||
val createdAccountId = transaction { | ||
val accountId = Accounts.insert { | ||
it[Accounts.tenant] = tenant | ||
it[id] = UUID.generateUUID() | ||
it[name] = sub | ||
it[email] = sub | ||
it[createdOn] = Clock.System.now().toJavaInstant() | ||
}[Accounts.id] | ||
|
||
OidcLogins.insert { | ||
it[OidcLogins.tenant] = tenant | ||
it[OidcLogins.accountId] = accountId | ||
it[oidcId] = jwt.subject | ||
} | ||
|
||
accountId | ||
} | ||
|
||
return Result.success(RegistrationResult(createdAccountId)) | ||
} | ||
|
||
override suspend fun authenticate(tenant: String, request: OidcUniqueSubjectRequest): AuthenticatedUser { | ||
val jwt = verifyToken(request.token) | ||
|
||
val registeredUserId = if (AccountsService.hasAccountOidcId(jwt.subject)) { | ||
AccountsService.getAccountByOidcId(jwt.subject)!!.id | ||
} else { | ||
AccountsService.register(tenant, request).getOrThrow().id | ||
} | ||
return AuthenticatedUser(registeredUserId, jwt.subject) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
153 changes: 153 additions & 0 deletions
153
waltid-wallet-api/src/main/kotlin/id/walt/webwallet/service/webauthn/WebauthnService.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
package id.walt.webwallet.service.webauthn | ||
|
||
import com.webauthn4j.WebAuthnManager | ||
import com.webauthn4j.authenticator.Authenticator | ||
import com.webauthn4j.authenticator.AuthenticatorImpl | ||
import com.webauthn4j.converter.exception.DataConversionException | ||
import com.webauthn4j.data.* | ||
import com.webauthn4j.data.client.Origin | ||
import com.webauthn4j.data.client.challenge.Challenge | ||
import com.webauthn4j.data.client.challenge.DefaultChallenge | ||
import com.webauthn4j.server.ServerProperty | ||
import com.webauthn4j.validator.exception.ValidationException | ||
|
||
|
||
object WebauthnService { | ||
|
||
private val webAuthnManager = WebAuthnManager.createNonStrictWebAuthnManager() | ||
|
||
fun register() { | ||
val randomChallenge = DefaultChallenge() | ||
} | ||
|
||
fun attestationVerification() { | ||
// Client properties | ||
val attestationObject: ByteArray? = null /* set attestationObject */ | ||
val clientDataJSON: ByteArray? = null /* set clientDataJSON */ | ||
val clientExtensionJSON: String? = null /* set clientExtensionJSON */ | ||
val transports: Set<String>? = null /* set transports */ | ||
|
||
|
||
// Server properties | ||
val origin: Origin? = null /* set origin */ | ||
val rpId: String? = null /* set rpId */ | ||
val challenge: Challenge? = null /* set challenge */ | ||
|
||
|
||
val tokenBindingId: ByteArray? = null /* set tokenBindingId */ | ||
val serverProperty: ServerProperty = ServerProperty(origin!!, rpId!!, challenge, tokenBindingId) | ||
|
||
|
||
// expectations | ||
val userVerificationRequired = false | ||
val userPresenceRequired = true | ||
|
||
val registrationRequest = RegistrationRequest(attestationObject, clientDataJSON, clientExtensionJSON, transports) | ||
val registrationParameters = RegistrationParameters(serverProperty, null, userVerificationRequired, userPresenceRequired) | ||
val registrationData: RegistrationData | ||
|
||
try { | ||
registrationData = webAuthnManager.parse(registrationRequest) | ||
} catch (e: DataConversionException) { | ||
// If you would like to handle WebAuthn data structure parse error, please catch DataConversionException | ||
throw e | ||
} | ||
|
||
try { | ||
webAuthnManager.validate(registrationData, registrationParameters) | ||
} catch (e: ValidationException) { | ||
// If you would like to handle WebAuthn data validation error, please catch ValidationException | ||
throw e | ||
} | ||
|
||
|
||
// please persist Authenticator object, which will be used in the authentication process. | ||
val authenticator: Authenticator = | ||
AuthenticatorImpl( // You may create your own Authenticator implementation to save friendly authenticator name | ||
registrationData.attestationObject!!.authenticatorData.attestedCredentialData!!, | ||
registrationData.attestationObject!!.attestationStatement, | ||
registrationData.attestationObject!!.authenticatorData.signCount | ||
) | ||
|
||
save(authenticator) // please persist authenticator in your manner | ||
} | ||
|
||
private val TEMPORARY_STORE = HashMap<ByteArray, Authenticator>() | ||
|
||
fun save(authenticator: Authenticator) { | ||
TEMPORARY_STORE[authenticator.attestedCredentialData.credentialId] = authenticator | ||
} | ||
|
||
fun load(credentialId: ByteArray): Authenticator? { | ||
return TEMPORARY_STORE[credentialId] | ||
} | ||
|
||
fun updateCounter(credentialId: ByteArray, signCount: Long) { | ||
TEMPORARY_STORE[credentialId]!!.counter = signCount | ||
} | ||
|
||
fun assertionVerification() { | ||
// Client properties | ||
val credentialId: ByteArray? = null /* set credentialId */ | ||
val userHandle: ByteArray? = null /* set userHandle */ | ||
val authenticatorData: ByteArray? = null /* set authenticatorData */ | ||
val clientDataJSON: ByteArray? = null /* set clientDataJSON */ | ||
val clientExtensionJSON: String? = null /* set clientExtensionJSON */ | ||
val signature: ByteArray? = null /* set signature */ | ||
|
||
|
||
// Server properties | ||
val origin: Origin? = null /* set origin */ | ||
val rpId: String? = null /* set rpId */ | ||
val challenge: Challenge? = null /* set challenge */ | ||
val tokenBindingId: ByteArray? = null /* set tokenBindingId */ | ||
val serverProperty = ServerProperty(origin!!, rpId!!, challenge, tokenBindingId) | ||
|
||
|
||
// expectations | ||
val allowCredentials: List<ByteArray>? = null | ||
val userVerificationRequired = true | ||
val userPresenceRequired = true | ||
|
||
val authenticator: Authenticator = | ||
load(credentialId!!)!! // please load authenticator object persisted in the registration process in your manner | ||
|
||
val authenticationRequest = | ||
AuthenticationRequest( | ||
credentialId, | ||
userHandle, | ||
authenticatorData, | ||
clientDataJSON, | ||
clientExtensionJSON, | ||
signature | ||
) | ||
val authenticationParameters = | ||
AuthenticationParameters( | ||
serverProperty, | ||
authenticator, | ||
allowCredentials, | ||
userVerificationRequired, | ||
userPresenceRequired | ||
) | ||
|
||
val authenticationData: AuthenticationData | ||
try { | ||
authenticationData = webAuthnManager.parse(authenticationRequest) | ||
} catch (e: DataConversionException) { | ||
// If you would like to handle WebAuthn data structure parse error, please catch DataConversionException | ||
throw e | ||
} | ||
try { | ||
webAuthnManager.validate(authenticationData, authenticationParameters) | ||
} catch (e: ValidationException) { | ||
// If you would like to handle WebAuthn data validation error, please catch ValidationException | ||
throw e | ||
} | ||
|
||
// please update the counter of the authenticator record | ||
updateCounter( | ||
authenticationData.credentialId, | ||
authenticationData.authenticatorData!!.signCount | ||
) | ||
} | ||
} |
Oops, something went wrong.