Skip to content

Commit 21da529

Browse files
authored
Merge pull request #1470 from FroMage/webauthn4j
Updated webauthn code
2 parents 1963cae + 8604794 commit 21da529

File tree

17 files changed

+262
-403
lines changed

17 files changed

+262
-403
lines changed

security-webauthn-quickstart/src/main/java/org/acme/security/webauthn/LoginResource.java

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
package org.acme.security.webauthn;
22

3+
import org.jboss.resteasy.reactive.RestForm;
4+
5+
import io.quarkus.security.webauthn.WebAuthnCredentialRecord;
6+
import io.quarkus.security.webauthn.WebAuthnLoginResponse;
7+
import io.quarkus.security.webauthn.WebAuthnRegisterResponse;
8+
import io.quarkus.security.webauthn.WebAuthnSecurity;
9+
import io.vertx.ext.web.RoutingContext;
310
import jakarta.inject.Inject;
411
import jakarta.transaction.Transactional;
512
import jakarta.ws.rs.BeanParam;
@@ -8,14 +15,6 @@
815
import jakarta.ws.rs.core.Response;
916
import jakarta.ws.rs.core.Response.Status;
1017

11-
import org.jboss.resteasy.reactive.RestForm;
12-
13-
import io.quarkus.security.webauthn.WebAuthnLoginResponse;
14-
import io.quarkus.security.webauthn.WebAuthnRegisterResponse;
15-
import io.quarkus.security.webauthn.WebAuthnSecurity;
16-
import io.vertx.ext.auth.webauthn.Authenticator;
17-
import io.vertx.ext.web.RoutingContext;
18-
1918
@Path("")
2019
public class LoginResource {
2120

@@ -25,25 +24,24 @@ public class LoginResource {
2524
@Path("/login")
2625
@POST
2726
@Transactional
28-
public Response login(@RestForm String userName,
29-
@BeanParam WebAuthnLoginResponse webAuthnResponse,
27+
public Response login(@BeanParam WebAuthnLoginResponse webAuthnResponse,
3028
RoutingContext ctx) {
3129
// Input validation
32-
if(userName == null || userName.isEmpty() || !webAuthnResponse.isSet() || !webAuthnResponse.isValid()) {
30+
if(!webAuthnResponse.isSet() || !webAuthnResponse.isValid()) {
3331
return Response.status(Status.BAD_REQUEST).build();
3432
}
3533

36-
User user = User.findByUserName(userName);
37-
if(user == null) {
38-
// Invalid user
39-
return Response.status(Status.BAD_REQUEST).build();
40-
}
4134
try {
42-
Authenticator authenticator = this.webAuthnSecurity.login(webAuthnResponse, ctx).await().indefinitely();
35+
WebAuthnCredentialRecord credentialRecord = this.webAuthnSecurity.login(webAuthnResponse, ctx).await().indefinitely();
36+
User user = User.findByUserName(credentialRecord.getUserName());
37+
if(user == null) {
38+
// Invalid user
39+
return Response.status(Status.BAD_REQUEST).build();
40+
}
4341
// bump the auth counter
44-
user.webAuthnCredential.counter = authenticator.getCounter();
42+
user.webAuthnCredential.counter = credentialRecord.getCounter();
4543
// make a login cookie
46-
this.webAuthnSecurity.rememberUser(authenticator.getUserName(), ctx);
44+
this.webAuthnSecurity.rememberUser(credentialRecord.getUserName(), ctx);
4745
return Response.ok().build();
4846
} catch (Exception exception) {
4947
// handle login failure - make a proper error response
@@ -69,10 +67,10 @@ public Response register(@RestForm String userName,
6967
}
7068
try {
7169
// store the user
72-
Authenticator authenticator = this.webAuthnSecurity.register(webAuthnResponse, ctx).await().indefinitely();
70+
WebAuthnCredentialRecord credentialRecord = this.webAuthnSecurity.register(userName, webAuthnResponse, ctx).await().indefinitely();
7371
User newUser = new User();
74-
newUser.userName = authenticator.getUserName();
75-
WebAuthnCredential credential = new WebAuthnCredential(authenticator, newUser);
72+
newUser.userName = credentialRecord.getUserName();
73+
WebAuthnCredential credential = new WebAuthnCredential(credentialRecord, newUser);
7674
credential.persist();
7775
newUser.persist();
7876
// make a login cookie

security-webauthn-quickstart/src/main/java/org/acme/security/webauthn/MyWebAuthnSetup.java

Lines changed: 23 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -3,76 +3,52 @@
33
import java.util.Collections;
44
import java.util.List;
55
import java.util.Set;
6-
import java.util.stream.Collectors;
7-
8-
import io.smallrye.common.annotation.Blocking;
9-
import jakarta.enterprise.context.ApplicationScoped;
106

7+
import io.quarkus.security.webauthn.WebAuthnCredentialRecord;
118
import io.quarkus.security.webauthn.WebAuthnUserProvider;
9+
import io.smallrye.common.annotation.Blocking;
1210
import io.smallrye.mutiny.Uni;
13-
import io.vertx.ext.auth.webauthn.AttestationCertificates;
14-
import io.vertx.ext.auth.webauthn.Authenticator;
11+
import jakarta.enterprise.context.ApplicationScoped;
1512
import jakarta.transaction.Transactional;
1613

17-
import static org.acme.security.webauthn.WebAuthnCredential.findByCredID;
18-
import static org.acme.security.webauthn.WebAuthnCredential.findByUserName;
19-
2014
@Blocking
2115
@ApplicationScoped
2216
public class MyWebAuthnSetup implements WebAuthnUserProvider {
2317

2418
@Transactional
2519
@Override
26-
public Uni<List<Authenticator>> findWebAuthnCredentialsByUserName(String userName) {
27-
return Uni.createFrom().item(toAuthenticators(findByUserName(userName)));
20+
public Uni<List<WebAuthnCredentialRecord>> findByUserName(String userId) {
21+
return Uni.createFrom().item(WebAuthnCredential.findByUserName(userId).stream().map(WebAuthnCredential::toWebAuthnCredentialRecord).toList());
2822
}
2923

3024
@Transactional
3125
@Override
32-
public Uni<List<Authenticator>> findWebAuthnCredentialsByCredID(String credID) {
33-
return Uni.createFrom().item(toAuthenticators(findByCredID(credID)));
26+
public Uni<WebAuthnCredentialRecord> findByCredentialId(String credId) {
27+
WebAuthnCredential creds = WebAuthnCredential.findByCredentialId(credId);
28+
if(creds == null)
29+
return Uni.createFrom().failure(new RuntimeException("No such credential ID"));
30+
return Uni.createFrom().item(creds.toWebAuthnCredentialRecord());
3431
}
3532

3633
@Transactional
3734
@Override
38-
public Uni<Void> updateOrStoreWebAuthnCredentials(Authenticator authenticator) {
39-
// leave the scooby user to the manual endpoint, because if we do it here it will be created/updated twice
40-
if(!authenticator.getUserName().equals("scooby")) {
41-
User user = User.findByUserName(authenticator.getUserName());
42-
if(user == null) {
43-
// new user
44-
User newUser = new User();
45-
newUser.userName = authenticator.getUserName();
46-
WebAuthnCredential credential = new WebAuthnCredential(authenticator, newUser);
47-
credential.persist();
48-
newUser.persist();
49-
} else {
50-
// existing user
51-
user.webAuthnCredential.counter = authenticator.getCounter();
52-
}
53-
}
54-
return Uni.createFrom().nullItem();
35+
public Uni<Void> store(WebAuthnCredentialRecord credentialRecord) {
36+
User newUser = new User();
37+
newUser.userName = credentialRecord.getUserName();
38+
WebAuthnCredential credential = new WebAuthnCredential(credentialRecord, newUser);
39+
credential.persist();
40+
newUser.persist();
41+
return Uni.createFrom().voidItem();
5542
}
5643

57-
private static List<Authenticator> toAuthenticators(List<WebAuthnCredential> dbs) {
58-
return dbs.stream().map(MyWebAuthnSetup::toAuthenticator).collect(Collectors.toList());
44+
@Transactional
45+
@Override
46+
public Uni<Void> update(String credentialId, long counter) {
47+
WebAuthnCredential credential = WebAuthnCredential.findByCredentialId(credentialId);
48+
credential.counter = counter;
49+
return Uni.createFrom().voidItem();
5950
}
6051

61-
private static Authenticator toAuthenticator(WebAuthnCredential credential) {
62-
Authenticator ret = new Authenticator();
63-
ret.setAaguid(credential.aaguid);
64-
AttestationCertificates attestationCertificates = new AttestationCertificates();
65-
attestationCertificates.setAlg(credential.alg);
66-
ret.setAttestationCertificates(attestationCertificates);
67-
ret.setCounter(credential.counter);
68-
ret.setCredID(credential.credID);
69-
ret.setFmt(credential.fmt);
70-
ret.setPublicKey(credential.publicKey);
71-
ret.setType(credential.type);
72-
ret.setUserName(credential.userName);
73-
return ret;
74-
}
75-
7652
@Override
7753
public Set<String> getRoles(String userId) {
7854
if(userId.equals("admin")) {

security-webauthn-quickstart/src/main/java/org/acme/security/webauthn/WebAuthnCertificate.java

Lines changed: 0 additions & 18 deletions
This file was deleted.
Lines changed: 27 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,25 @@
11
package org.acme.security.webauthn;
22

3-
import java.util.ArrayList;
43
import java.util.List;
4+
import java.util.UUID;
55

6+
import io.quarkus.hibernate.orm.panache.PanacheEntityBase;
7+
import io.quarkus.security.webauthn.WebAuthnCredentialRecord;
8+
import io.quarkus.security.webauthn.WebAuthnCredentialRecord.RequiredPersistedData;
69
import jakarta.persistence.Entity;
7-
import jakarta.persistence.OneToMany;
10+
import jakarta.persistence.Id;
811
import jakarta.persistence.OneToOne;
9-
import jakarta.persistence.Table;
10-
import jakarta.persistence.UniqueConstraint;
1112

12-
import io.quarkus.hibernate.orm.panache.PanacheEntity;
13-
import io.vertx.ext.auth.webauthn.Authenticator;
14-
import io.vertx.ext.auth.webauthn.PublicKeyCredential;
15-
16-
@Table(uniqueConstraints = @UniqueConstraint(columnNames = {"userName", "credID"}))
1713
@Entity
18-
public class WebAuthnCredential extends PanacheEntity {
14+
public class WebAuthnCredential extends PanacheEntityBase {
1915

20-
/**
21-
* The username linked to this authenticator
22-
*/
23-
public String userName;
24-
25-
/**
26-
* The type of key (must be "public-key")
27-
*/
28-
public String type = "public-key";
29-
30-
/**
31-
* The non user identifiable id for the authenticator
32-
*/
33-
public String credID;
16+
@Id
17+
public String credentialId;
3418

35-
/**
36-
* The public key associated with this authenticator
37-
*/
38-
public String publicKey;
39-
40-
/**
41-
* The signature counter of the authenticator to prevent replay attacks
42-
*/
19+
public byte[] publicKey;
20+
public long publicKeyAlgorithm;
4321
public long counter;
44-
45-
public String aaguid;
46-
47-
/**
48-
* The Authenticator attestation certificates object, a JSON like:
49-
* <pre>{@code
50-
* {
51-
* "alg": "string",
52-
* "x5c": [
53-
* "base64"
54-
* ]
55-
* }
56-
* }</pre>
57-
*/
58-
/**
59-
* The algorithm used for the public credential
60-
*/
61-
public PublicKeyCredential alg;
62-
63-
/**
64-
* The list of X509 certificates encoded as base64url.
65-
*/
66-
@OneToMany(mappedBy = "webAuthnCredential")
67-
public List<WebAuthnCertificate> x5c = new ArrayList<>();
68-
69-
public String fmt;
22+
public UUID aaguid;
7023

7124
// owning side
7225
@OneToOne
@@ -75,34 +28,28 @@ public class WebAuthnCredential extends PanacheEntity {
7528
public WebAuthnCredential() {
7629
}
7730

78-
public WebAuthnCredential(Authenticator authenticator, User user) {
79-
aaguid = authenticator.getAaguid();
80-
if(authenticator.getAttestationCertificates() != null)
81-
alg = authenticator.getAttestationCertificates().getAlg();
82-
counter = authenticator.getCounter();
83-
credID = authenticator.getCredID();
84-
fmt = authenticator.getFmt();
85-
publicKey = authenticator.getPublicKey();
86-
type = authenticator.getType();
87-
userName = authenticator.getUserName();
88-
if(authenticator.getAttestationCertificates() != null
89-
&& authenticator.getAttestationCertificates().getX5c() != null) {
90-
for (String x5c : authenticator.getAttestationCertificates().getX5c()) {
91-
WebAuthnCertificate cert = new WebAuthnCertificate();
92-
cert.x5c = x5c;
93-
cert.webAuthnCredential = this;
94-
this.x5c.add(cert);
95-
}
96-
}
31+
public WebAuthnCredential(WebAuthnCredentialRecord credentialRecord, User user) {
32+
RequiredPersistedData requiredPersistedData = credentialRecord.getRequiredPersistedData();
33+
aaguid = requiredPersistedData.aaguid();
34+
counter = requiredPersistedData.counter();
35+
credentialId = requiredPersistedData.credentialId();
36+
publicKey = requiredPersistedData.publicKey();
37+
publicKeyAlgorithm = requiredPersistedData.publicKeyAlgorithm();
9738
this.user = user;
9839
user.webAuthnCredential = this;
9940
}
10041

42+
public WebAuthnCredentialRecord toWebAuthnCredentialRecord() {
43+
return WebAuthnCredentialRecord
44+
.fromRequiredPersistedData(
45+
new RequiredPersistedData(user.userName, credentialId, aaguid, publicKey, publicKeyAlgorithm, counter));
46+
}
47+
10148
public static List<WebAuthnCredential> findByUserName(String userName) {
102-
return list("userName", userName);
49+
return list("user.userName", userName);
10350
}
10451

105-
public static List<WebAuthnCredential> findByCredID(String credID) {
106-
return list("credID", credID);
52+
public static WebAuthnCredential findByCredentialId(String credentialId) {
53+
return findById(credentialId);
10754
}
10855
}

security-webauthn-quickstart/src/main/resources/META-INF/resources/index.html

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ <h1>Status</h1>
5858
<div class="item">
5959
<h1>Login</h1>
6060
<p>
61-
<input id="userNameLogin" placeholder="User name"/><br/>
6261
<button id="login">Login</button>
6362
</p>
6463
</div>
@@ -73,11 +72,7 @@ <h1>Register</h1>
7372
</div>
7473
</div>
7574
<script type="text/javascript">
76-
const webAuthn = new WebAuthn({
77-
callbackPath: '/q/webauthn/callback',
78-
registerPath: '/q/webauthn/register',
79-
loginPath: '/q/webauthn/login'
80-
});
75+
const webAuthn = new WebAuthn();
8176

8277
const result = document.getElementById('result');
8378

@@ -88,10 +83,11 @@ <h1>Register</h1>
8883
const loginButton = document.getElementById('login');
8984

9085
loginButton.addEventListener("click", (e) => {
91-
var userName = document.getElementById('userNameLogin').value;
9286
result.replaceChildren();
93-
webAuthn.login({ name: userName })
94-
.then(body => {
87+
webAuthn.login()
88+
.then(x => fetch('/api/public/me'))
89+
.then(response => response.text())
90+
.then(userName => {
9591
result.append("User: "+userName);
9692
})
9793
.catch(err => {

security-webauthn-quickstart/src/main/resources/application.properties

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,6 @@
55

66
quarkus.hibernate-orm.database.generation=drop-and-create
77

8-
quarkus.webauthn.login-page=/
8+
quarkus.webauthn.login-page=/
9+
quarkus.webauthn.enable-login-endpoint=true
10+
quarkus.webauthn.enable-registration-endpoint=true

0 commit comments

Comments
 (0)