Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update test suite for webauthn4j changes #2250

Merged
merged 1 commit into from
Dec 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -539,8 +539,7 @@
<module>security/keycloak-oidc-client-reactive-extended</module>
<module>security/vertx-jwt</module>
<module>security/oidc-client-mutual-tls</module>
<!-- FIXME: mvavrik deal with this ASAP! -->
<!-- <module>security/webauthn</module> -->
<module>security/webauthn</module>
</modules>
</profile>
<profile>
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,73 +1,38 @@
package io.quarkus.ts.security.webauthn.model;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

import jakarta.persistence.Entity;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Id;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;

import io.quarkus.hibernate.reactive.panache.PanacheEntity;
import io.quarkus.hibernate.reactive.panache.PanacheEntityBase;
import io.quarkus.security.webauthn.WebAuthnCredentialRecord;
import io.quarkus.security.webauthn.WebAuthnCredentialRecord.RequiredPersistedData;
import io.smallrye.mutiny.Uni;
import io.vertx.ext.auth.webauthn.Authenticator;
import io.vertx.ext.auth.webauthn.PublicKeyCredential;

@Table(uniqueConstraints = @UniqueConstraint(columnNames = { "userName", "credID" }))
@Entity
public class WebAuthnCredential extends PanacheEntity {
/**
* The username linked to this authenticator
*/
public String userName;

/**
* The type of key (must be "public-key")
*/
public String type = "public-key";

public class WebAuthnCredential extends PanacheEntityBase {
/**
* The non user identifiable id for the authenticator
*/
@Id
public String credID;

/**
* The public key associated with this authenticator
*/
public String publicKey;
public byte[] publicKey;

public long publicKeyAlgorithm;

/**
* The signature counter of the authenticator to prevent replay attacks
*/
public long counter;

public String aaguid;

/**
* The Authenticator attestation certificates object, a JSON like:
*
* <pre>{@code
* {
* "alg": "string",
* "x5c": [
* "base64"
* ]
* }
* }</pre>
*/
/**
* The algorithm used for the public credential
*/
public PublicKeyCredential alg;

/**
* The list of X509 certificates encoded as base64url.
*/
@OneToMany(mappedBy = "webAuthnCredential")
public List<WebAuthnCertificate> webAuthnx509Certificates = new ArrayList<>();

public String fmt;
public UUID aaguid;

// owning side
@OneToOne
Expand All @@ -76,43 +41,29 @@ public class WebAuthnCredential extends PanacheEntity {
public WebAuthnCredential() {
}

public WebAuthnCredential(Authenticator authenticator, User user) {
aaguid = authenticator.getAaguid();
if (authenticator.getAttestationCertificates() != null)
alg = authenticator.getAttestationCertificates().getAlg();
counter = authenticator.getCounter();
credID = authenticator.getCredID();
fmt = authenticator.getFmt();
publicKey = authenticator.getPublicKey();
type = authenticator.getType();
userName = authenticator.getUserName();
if (authenticator.getAttestationCertificates() != null
&& authenticator.getAttestationCertificates().getX5c() != null) {
for (String x509VCertificate : authenticator.getAttestationCertificates().getX5c()) {
WebAuthnCertificate cert = new WebAuthnCertificate();
cert.base64X509Certificate = x509VCertificate;
cert.webAuthnCredential = this;
this.webAuthnx509Certificates.add(cert);
}
}
public WebAuthnCredential(WebAuthnCredentialRecord credentialRecord, User user) {
RequiredPersistedData requiredPersistedData = credentialRecord.getRequiredPersistedData();
aaguid = requiredPersistedData.aaguid();
counter = requiredPersistedData.counter();
credID = requiredPersistedData.credentialId();
publicKey = requiredPersistedData.publicKey();
publicKeyAlgorithm = requiredPersistedData.publicKeyAlgorithm();
this.user = user;
user.webAuthnCredential = this;
}

public static Uni<WebAuthnCredential> createWebAuthnCredential(Authenticator authenticator, User user) {
WebAuthnCredential credential = new WebAuthnCredential(authenticator, user);
credential.persistAndFlush();
user.webAuthnCredential = credential;
user.persistAndFlush();
return Uni.createFrom().item(credential);
public WebAuthnCredentialRecord toWebAuthnCredentialRecord() {
return WebAuthnCredentialRecord
.fromRequiredPersistedData(
new RequiredPersistedData(user.userName, credID, aaguid, publicKey, publicKeyAlgorithm, counter));
}

public static Uni<List<WebAuthnCredential>> findByUserName(String userName) {
return list("userName", userName);
return list("user.userName", userName);
}

public static Uni<List<WebAuthnCredential>> findByCredID(String credID) {
return list("credID", credID);
public static Uni<WebAuthnCredential> findByCredentialId(String credID) {
return findById(credID);
}

public <T> Uni<T> fetch(T association) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package io.quarkus.ts.security.webauthn.security;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
Expand All @@ -9,79 +8,46 @@
import jakarta.enterprise.context.ApplicationScoped;

import io.quarkus.hibernate.reactive.panache.common.WithTransaction;
import io.quarkus.security.webauthn.WebAuthnCredentialRecord;
import io.quarkus.security.webauthn.WebAuthnUserProvider;
import io.quarkus.ts.security.webauthn.model.User;
import io.quarkus.ts.security.webauthn.model.WebAuthnCredential;
import io.smallrye.mutiny.Uni;
import io.vertx.ext.auth.webauthn.AttestationCertificates;
import io.vertx.ext.auth.webauthn.Authenticator;

@ApplicationScoped
public class MyWebAuthnSetup implements WebAuthnUserProvider {

@WithTransaction
@Override
public Uni<List<Authenticator>> findWebAuthnCredentialsByUserName(String userName) {
public Uni<List<WebAuthnCredentialRecord>> findByUserName(String userName) {
return WebAuthnCredential.findByUserName(userName)
.flatMap(MyWebAuthnSetup::toAuthenticators);
.map(list -> list.stream().map(WebAuthnCredential::toWebAuthnCredentialRecord).toList());
}

@WithTransaction
@Override
public Uni<List<Authenticator>> findWebAuthnCredentialsByCredID(String credID) {
return WebAuthnCredential.findByCredID(credID)
.flatMap(MyWebAuthnSetup::toAuthenticators);
public Uni<WebAuthnCredentialRecord> findByCredentialId(String credentialId) {
return WebAuthnCredential.findByCredentialId(credentialId)
.onItem().ifNull().failWith(() -> new RuntimeException("No such credentials"))
.map(WebAuthnCredential::toWebAuthnCredentialRecord);
}

@WithTransaction
@Override
public Uni<Void> updateOrStoreWebAuthnCredentials(Authenticator authenticator) {
return User.findByUserName(authenticator.getUserName())
.flatMap(user -> {
// new user
if (user == null) {
User newUser = new User();
newUser.userName = authenticator.getUserName();
WebAuthnCredential credential = new WebAuthnCredential(authenticator, newUser);
return credential.persist()
.flatMap(c -> newUser.persist())
.onItem().ignore().andContinueWithNull();
} else {

// existing user
user.webAuthnCredential.counter = authenticator.getCounter();
return Uni.createFrom().nullItem();
}
});
}

private static Uni<List<Authenticator>> toAuthenticators(List<WebAuthnCredential> dbs) {
// can't call combine/uni on empty list
if (dbs.isEmpty())
return Uni.createFrom().item(Collections.emptyList());
List<Uni<Authenticator>> ret = new ArrayList<>(dbs.size());
for (WebAuthnCredential db : dbs) {
ret.add(toAuthenticator(db));
}
return Uni.combine().all().unis(ret).with(f -> (List) f);
public Uni<Void> store(WebAuthnCredentialRecord credentialRecord) {
User newUser = new User();
newUser.userName = credentialRecord.getUserName();
WebAuthnCredential credential = new WebAuthnCredential(credentialRecord, newUser);
return credential.persist()
.flatMap(c -> newUser.persist())
.onItem().ignore().andContinueWithNull();
}

private static Uni<Authenticator> toAuthenticator(WebAuthnCredential credential) {
return credential.fetch(credential.webAuthnx509Certificates)
.map(x5c -> {
Authenticator ret = new Authenticator();
ret.setAaguid(credential.aaguid);
AttestationCertificates attestationCertificates = new AttestationCertificates();
attestationCertificates.setAlg(credential.alg);
ret.setAttestationCertificates(attestationCertificates);
ret.setCounter(credential.counter);
ret.setCredID(credential.credID);
ret.setFmt(credential.fmt);
ret.setPublicKey(credential.publicKey);
ret.setType(credential.type);
ret.setUserName(credential.userName);
return ret;
});
@WithTransaction
@Override
public Uni<Void> update(String credentialId, long counter) {
return WebAuthnCredential.findByCredentialId(credentialId)
.onItem().ignore().andContinueWithNull();
}

@Override
Expand Down
3 changes: 1 addition & 2 deletions security/webauthn/src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
quarkus.hibernate-orm.database.generation=drop-and-create


quarkus.webauthn.enable-registration-endpoint=true
Loading
Loading