From cad328c0e3d081485a56106dee94a419a20969a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20=C3=89pardaud?= Date: Thu, 24 Oct 2024 15:20:23 -0400 Subject: [PATCH] Updated webauthn code --- .../acme/security/webauthn/LoginResource.java | 27 ++--- .../security/webauthn/MyWebAuthnSetup.java | 70 ++++------- .../webauthn/WebAuthnCertificate.java | 18 --- .../security/webauthn/WebAuthnCredential.java | 107 +++++------------ .../resources/META-INF/resources/index.html | 6 +- .../webauthn/test/TestUserProvider.java | 36 ++++++ .../webauthn/test/WebAuthnResourceTest.java | 16 ++- security-webauthn-reactive-quickstart/pom.xml | 2 +- .../acme/security/webauthn/LoginResource.java | 23 ++-- .../security/webauthn/MyWebAuthnSetup.java | 82 ++++--------- .../webauthn/WebAuthnCertificate.java | 19 --- .../security/webauthn/WebAuthnCredential.java | 111 +++++------------- .../resources/META-INF/resources/index.html | 6 +- .../webauthn/test/TestUserProvider.java | 38 ++++++ .../webauthn/test/WebAuthnResourceTest.java | 16 ++- 15 files changed, 221 insertions(+), 356 deletions(-) delete mode 100644 security-webauthn-quickstart/src/main/java/org/acme/security/webauthn/WebAuthnCertificate.java create mode 100644 security-webauthn-quickstart/src/test/java/org/acme/security/webauthn/test/TestUserProvider.java delete mode 100644 security-webauthn-reactive-quickstart/src/main/java/org/acme/security/webauthn/WebAuthnCertificate.java create mode 100644 security-webauthn-reactive-quickstart/src/test/java/org/acme/security/webauthn/test/TestUserProvider.java diff --git a/security-webauthn-quickstart/src/main/java/org/acme/security/webauthn/LoginResource.java b/security-webauthn-quickstart/src/main/java/org/acme/security/webauthn/LoginResource.java index 9d5eb02243..b44217a5b2 100644 --- a/security-webauthn-quickstart/src/main/java/org/acme/security/webauthn/LoginResource.java +++ b/security-webauthn-quickstart/src/main/java/org/acme/security/webauthn/LoginResource.java @@ -1,5 +1,12 @@ package org.acme.security.webauthn; +import org.jboss.resteasy.reactive.RestForm; + +import io.quarkus.security.webauthn.WebAuthnCredentialRecord; +import io.quarkus.security.webauthn.WebAuthnLoginResponse; +import io.quarkus.security.webauthn.WebAuthnRegisterResponse; +import io.quarkus.security.webauthn.WebAuthnSecurity; +import io.vertx.ext.web.RoutingContext; import jakarta.inject.Inject; import jakarta.transaction.Transactional; import jakarta.ws.rs.BeanParam; @@ -8,14 +15,6 @@ import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response.Status; -import org.jboss.resteasy.reactive.RestForm; - -import io.quarkus.security.webauthn.WebAuthnLoginResponse; -import io.quarkus.security.webauthn.WebAuthnRegisterResponse; -import io.quarkus.security.webauthn.WebAuthnSecurity; -import io.vertx.ext.auth.webauthn.Authenticator; -import io.vertx.ext.web.RoutingContext; - @Path("") public class LoginResource { @@ -39,11 +38,11 @@ public Response login(@RestForm String userName, return Response.status(Status.BAD_REQUEST).build(); } try { - Authenticator authenticator = this.webAuthnSecurity.login(webAuthnResponse, ctx).await().indefinitely(); + WebAuthnCredentialRecord credentialRecord = this.webAuthnSecurity.login(webAuthnResponse, ctx).await().indefinitely(); // bump the auth counter - user.webAuthnCredential.counter = authenticator.getCounter(); + user.webAuthnCredential.counter = credentialRecord.getCounter(); // make a login cookie - this.webAuthnSecurity.rememberUser(authenticator.getUserName(), ctx); + this.webAuthnSecurity.rememberUser(credentialRecord.getUserName(), ctx); return Response.ok().build(); } catch (Exception exception) { // handle login failure - make a proper error response @@ -69,10 +68,10 @@ public Response register(@RestForm String userName, } try { // store the user - Authenticator authenticator = this.webAuthnSecurity.register(webAuthnResponse, ctx).await().indefinitely(); + WebAuthnCredentialRecord credentialRecord = this.webAuthnSecurity.register(webAuthnResponse, ctx).await().indefinitely(); User newUser = new User(); - newUser.userName = authenticator.getUserName(); - WebAuthnCredential credential = new WebAuthnCredential(authenticator, newUser); + newUser.userName = credentialRecord.getUserName(); + WebAuthnCredential credential = new WebAuthnCredential(credentialRecord, newUser); credential.persist(); newUser.persist(); // make a login cookie diff --git a/security-webauthn-quickstart/src/main/java/org/acme/security/webauthn/MyWebAuthnSetup.java b/security-webauthn-quickstart/src/main/java/org/acme/security/webauthn/MyWebAuthnSetup.java index 4a98c60ca7..4c83484e4b 100644 --- a/security-webauthn-quickstart/src/main/java/org/acme/security/webauthn/MyWebAuthnSetup.java +++ b/security-webauthn-quickstart/src/main/java/org/acme/security/webauthn/MyWebAuthnSetup.java @@ -3,76 +3,52 @@ import java.util.Collections; import java.util.List; import java.util.Set; -import java.util.stream.Collectors; - -import io.smallrye.common.annotation.Blocking; -import jakarta.enterprise.context.ApplicationScoped; +import io.quarkus.security.webauthn.WebAuthnCredentialRecord; import io.quarkus.security.webauthn.WebAuthnUserProvider; +import io.smallrye.common.annotation.Blocking; import io.smallrye.mutiny.Uni; -import io.vertx.ext.auth.webauthn.AttestationCertificates; -import io.vertx.ext.auth.webauthn.Authenticator; +import jakarta.enterprise.context.ApplicationScoped; import jakarta.transaction.Transactional; -import static org.acme.security.webauthn.WebAuthnCredential.findByCredID; -import static org.acme.security.webauthn.WebAuthnCredential.findByUserName; - @Blocking @ApplicationScoped public class MyWebAuthnSetup implements WebAuthnUserProvider { @Transactional @Override - public Uni> findWebAuthnCredentialsByUserName(String userName) { - return Uni.createFrom().item(toAuthenticators(findByUserName(userName))); + public Uni> findByUserName(String userId) { + return Uni.createFrom().item(WebAuthnCredential.findByUserName(userId).stream().map(WebAuthnCredential::toWebAuthnCredentialRecord).toList()); } @Transactional @Override - public Uni> findWebAuthnCredentialsByCredID(String credID) { - return Uni.createFrom().item(toAuthenticators(findByCredID(credID))); + public Uni findByCredentialId(String credId) { + WebAuthnCredential creds = WebAuthnCredential.findByCredentialId(credId); + if(creds == null) + return Uni.createFrom().failure(new RuntimeException("No such credential ID")); + return Uni.createFrom().item(creds.toWebAuthnCredentialRecord()); } @Transactional @Override - public Uni updateOrStoreWebAuthnCredentials(Authenticator authenticator) { - // leave the scooby user to the manual endpoint, because if we do it here it will be created/updated twice - if(!authenticator.getUserName().equals("scooby")) { - User user = User.findByUserName(authenticator.getUserName()); - if(user == null) { - // new user - User newUser = new User(); - newUser.userName = authenticator.getUserName(); - WebAuthnCredential credential = new WebAuthnCredential(authenticator, newUser); - credential.persist(); - newUser.persist(); - } else { - // existing user - user.webAuthnCredential.counter = authenticator.getCounter(); - } - } - return Uni.createFrom().nullItem(); + public Uni store(WebAuthnCredentialRecord credentialRecord) { + User newUser = new User(); + newUser.userName = credentialRecord.getUserName(); + WebAuthnCredential credential = new WebAuthnCredential(credentialRecord, newUser); + credential.persist(); + newUser.persist(); + return Uni.createFrom().voidItem(); } - private static List toAuthenticators(List dbs) { - return dbs.stream().map(MyWebAuthnSetup::toAuthenticator).collect(Collectors.toList()); + @Transactional + @Override + public Uni update(String credentialId, long counter) { + WebAuthnCredential credential = WebAuthnCredential.findByCredentialId(credentialId); + credential.counter = counter; + return Uni.createFrom().voidItem(); } - private static Authenticator toAuthenticator(WebAuthnCredential credential) { - 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; - } - @Override public Set getRoles(String userId) { if(userId.equals("admin")) { diff --git a/security-webauthn-quickstart/src/main/java/org/acme/security/webauthn/WebAuthnCertificate.java b/security-webauthn-quickstart/src/main/java/org/acme/security/webauthn/WebAuthnCertificate.java deleted file mode 100644 index a39aeb55eb..0000000000 --- a/security-webauthn-quickstart/src/main/java/org/acme/security/webauthn/WebAuthnCertificate.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.acme.security.webauthn; - -import io.quarkus.hibernate.orm.panache.PanacheEntity; -import jakarta.persistence.Entity; -import jakarta.persistence.ManyToOne; - - -@Entity -public class WebAuthnCertificate extends PanacheEntity { - - @ManyToOne - public WebAuthnCredential webAuthnCredential; - - /** - * The list of X509 certificates encoded as base64url. - */ - public String x5c; -} diff --git a/security-webauthn-quickstart/src/main/java/org/acme/security/webauthn/WebAuthnCredential.java b/security-webauthn-quickstart/src/main/java/org/acme/security/webauthn/WebAuthnCredential.java index fcc7e3f282..47a303dad6 100644 --- a/security-webauthn-quickstart/src/main/java/org/acme/security/webauthn/WebAuthnCredential.java +++ b/security-webauthn-quickstart/src/main/java/org/acme/security/webauthn/WebAuthnCredential.java @@ -1,72 +1,25 @@ package org.acme.security.webauthn; -import java.util.ArrayList; import java.util.List; +import java.util.UUID; +import io.quarkus.hibernate.orm.panache.PanacheEntityBase; +import io.quarkus.security.webauthn.WebAuthnCredentialRecord; +import io.quarkus.security.webauthn.WebAuthnCredentialRecord.RequiredPersistedData; 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.orm.panache.PanacheEntity; -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 { +public class WebAuthnCredential extends PanacheEntityBase { - /** - * The username linked to this authenticator - */ - public String userName; - - /** - * The type of key (must be "public-key") - */ - public String type = "public-key"; - - /** - * The non user identifiable id for the authenticator - */ - public String credID; + @Id + public String credentialId; - /** - * The public key associated with this authenticator - */ - public String publicKey; - - /** - * The signature counter of the authenticator to prevent replay attacks - */ + public byte[] publicKey; + public long publicKeyAlgorithm; public long counter; - - public String aaguid; - - /** - * The Authenticator attestation certificates object, a JSON like: - *
{@code
-     *   {
-     *     "alg": "string",
-     *     "x5c": [
-     *       "base64"
-     *     ]
-     *   }
-     * }
- */ - /** - * The algorithm used for the public credential - */ - public PublicKeyCredential alg; - - /** - * The list of X509 certificates encoded as base64url. - */ - @OneToMany(mappedBy = "webAuthnCredential") - public List x5c = new ArrayList<>(); - - public String fmt; + public UUID aaguid; // owning side @OneToOne @@ -75,34 +28,28 @@ 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 x5c : authenticator.getAttestationCertificates().getX5c()) { - WebAuthnCertificate cert = new WebAuthnCertificate(); - cert.x5c = x5c; - cert.webAuthnCredential = this; - this.x5c.add(cert); - } - } + public WebAuthnCredential(WebAuthnCredentialRecord credentialRecord, User user) { + RequiredPersistedData requiredPersistedData = credentialRecord.getRequiredPersistedData(); + aaguid = requiredPersistedData.aaguid(); + counter = requiredPersistedData.counter(); + credentialId = requiredPersistedData.credentialId(); + publicKey = requiredPersistedData.publicKey(); + publicKeyAlgorithm = requiredPersistedData.publicKeyAlgorithm(); this.user = user; user.webAuthnCredential = this; } + public WebAuthnCredentialRecord toWebAuthnCredentialRecord() { + return WebAuthnCredentialRecord + .fromRequiredPersistedData( + new RequiredPersistedData(user.userName, credentialId, aaguid, publicKey, publicKeyAlgorithm, counter)); + } + public static List findByUserName(String userName) { - return list("userName", userName); + return list("user.userName", userName); } - public static List findByCredID(String credID) { - return list("credID", credID); + public static WebAuthnCredential findByCredentialId(String credentialId) { + return findById(credentialId); } } diff --git a/security-webauthn-quickstart/src/main/resources/META-INF/resources/index.html b/security-webauthn-quickstart/src/main/resources/META-INF/resources/index.html index 76e5a7aa88..6f50d95d2f 100644 --- a/security-webauthn-quickstart/src/main/resources/META-INF/resources/index.html +++ b/security-webauthn-quickstart/src/main/resources/META-INF/resources/index.html @@ -73,11 +73,7 @@

Register