Skip to content
Merged
1 change: 1 addition & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

* Added overloaded setter `RelyingPartyBuilder.origins(Optional<Set<String>>)`.
* Added support for the CTAP2 `credProtect` extension.
* Added support for the `prf` extension.
* (Experimental) Added a new suite of interfaces, starting with
`CredentialRepositoryV2`. `RelyingParty` can now be configured with a
`CredentialRepositoryV2` instance instead of a `CredentialRepository`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import com.yubico.webauthn.StartAssertionOptions;
import com.yubico.webauthn.extension.appid.AppId;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import lombok.Builder;
Expand All @@ -55,15 +56,18 @@ public class AssertionExtensionInputs implements ExtensionInputs {

private final AppId appid;
private final Extensions.LargeBlob.LargeBlobAuthenticationInput largeBlob;
private final Extensions.Prf.PrfAuthenticationInput prf;
private final Boolean uvm;

@JsonCreator
private AssertionExtensionInputs(
@JsonProperty("appid") AppId appid,
@JsonProperty("largeBlob") Extensions.LargeBlob.LargeBlobAuthenticationInput largeBlob,
@JsonProperty("prf") Extensions.Prf.PrfAuthenticationInput prf,
@JsonProperty("uvm") Boolean uvm) {
this.appid = appid;
this.largeBlob = largeBlob;
this.prf = prf;
this.uvm = (uvm != null && uvm) ? true : null;
}

Expand All @@ -78,6 +82,7 @@ public AssertionExtensionInputs merge(AssertionExtensionInputs other) {
return new AssertionExtensionInputs(
this.appid != null ? this.appid : other.appid,
this.largeBlob != null ? this.largeBlob : other.largeBlob,
this.prf != null ? this.prf : other.prf,
this.uvm != null ? this.uvm : other.uvm);
}

Expand All @@ -95,6 +100,9 @@ public Set<String> getExtensionIds() {
if (largeBlob != null) {
ids.add(Extensions.LargeBlob.EXTENSION_ID);
}
if (prf != null) {
ids.add(Extensions.Prf.EXTENSION_ID);
}
if (getUvm()) {
ids.add(Extensions.Uvm.EXTENSION_ID);
}
Expand Down Expand Up @@ -172,6 +180,37 @@ public AssertionExtensionInputsBuilder largeBlob(
return this;
}

/**
* Enable the Pseudo-random function extension (<code>prf</code>).
*
* <p>This extension allows a Relying Party to evaluate outputs from a pseudo-random function
* (PRF) associated with a credential.
*
* <p>Use the {@link com.yubico.webauthn.data.Extensions.Prf.PrfAuthenticationInput} factory
* functions to construct the argument:
*
* <ul>
* <li>Use {@link Extensions.Prf.PrfAuthenticationInput#eval(Extensions.Prf.PrfValues)} to use
* the same PRF input for all credentials.
* <li>Use {@link Extensions.Prf.PrfAuthenticationInput#evalByCredential(Map)} to use
* different PRF inputs for different credentials.
* <li>Use {@link Extensions.Prf.PrfAuthenticationInput#evalByCredentialWithFallback(Map,
* Extensions.Prf.PrfValues)} to use different PRF inputs for different credentials, but
* with a "fallback" input for credentials without their own input.
* </ul>
*
* @see Extensions.Prf.PrfAuthenticationInput#eval(Extensions.Prf.PrfValues)
* @see Extensions.Prf.PrfAuthenticationInput#evalByCredential(Map)
* @see Extensions.Prf.PrfAuthenticationInput#evalByCredentialWithFallback(Map,
* Extensions.Prf.PrfValues)
* @see <a href="https://www.w3.org/TR/2025/WD-webauthn-3-20250127/#prf-extension">§10.1.4.
* Pseudo-random function extension (prf)</a>
*/
public AssertionExtensionInputsBuilder prf(Extensions.Prf.PrfAuthenticationInput prf) {
this.prf = prf;
return this;
}

/**
* Enable the User Verification Method Extension (<code>uvm</code>).
*
Expand Down Expand Up @@ -233,6 +272,31 @@ private Extensions.LargeBlob.LargeBlobAuthenticationInput getLargeBlobJson() {
: null;
}

/**
* The input to the Pseudo-random function extension (<code>prf</code>), if any.
*
* <p>This extension allows a Relying Party to evaluate outputs from a pseudo-random function
* (PRF) associated with a credential.
*
* @see Extensions.Prf.PrfAuthenticationInput#eval(Extensions.Prf.PrfValues)
* @see Extensions.Prf.PrfAuthenticationInput#evalByCredential(Map)
* @see Extensions.Prf.PrfAuthenticationInput#evalByCredentialWithFallback(Map,
* Extensions.Prf.PrfValues)
* @see <a href="https://www.w3.org/TR/2025/WD-webauthn-3-20250127/#prf-extension">§10.1.4.
* Pseudo-random function extension (prf)</a>
*/
public Optional<Extensions.Prf.PrfAuthenticationInput> getPrf() {
return Optional.ofNullable(prf);
}

/** For JSON serialization, to omit false and null values. */
@JsonProperty("prf")
private Extensions.Prf.PrfAuthenticationInput getPrfJson() {
return prf != null && (prf.getEval().isPresent() || prf.getEvalByCredential().isPresent())
? prf
: null;
}

/**
* @return <code>true</code> if the User Verification Method Extension (<code>uvm</code>) is
* enabled, <code>false</code> otherwise.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
/**
* Contains <a
* href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#authenticator-extension-output">authenticator
* extension outputs</a> from a <code>navigator.credentials.create()</code> operation.
* extension outputs</a> from a <code>navigator.credentials.get()</code> operation.
*
* <p>Note that there is no guarantee that any extension input present in {@link
* RegistrationExtensionInputs} will have a corresponding output present here.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,16 @@ public class ClientAssertionExtensionOutputs implements ClientExtensionOutputs {

private final Extensions.LargeBlob.LargeBlobAuthenticationOutput largeBlob;

private final Extensions.Prf.PrfAuthenticationOutput prf;

@JsonCreator
private ClientAssertionExtensionOutputs(
@JsonProperty("appid") Boolean appid,
@JsonProperty("largeBlob") Extensions.LargeBlob.LargeBlobAuthenticationOutput largeBlob) {
@JsonProperty("largeBlob") Extensions.LargeBlob.LargeBlobAuthenticationOutput largeBlob,
@JsonProperty("prf") Extensions.Prf.PrfAuthenticationOutput prf) {
this.appid = appid;
this.largeBlob = largeBlob;
this.prf = prf;
}

@Override
Expand All @@ -84,6 +88,9 @@ public Set<String> getExtensionIds() {
if (largeBlob != null) {
ids.add(Extensions.LargeBlob.EXTENSION_ID);
}
if (prf != null) {
ids.add(Extensions.Prf.EXTENSION_ID);
}
return ids;
}

Expand All @@ -105,7 +112,7 @@ public Optional<Boolean> getAppid() {
* href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-large-blob-extension">Large blob
* storage (<code>largeBlob</code>) extension</a>, if any.
*
* @see com.yubico.webauthn.data.Extensions.LargeBlob.LargeBlobRegistrationOutput
* @see com.yubico.webauthn.data.Extensions.LargeBlob.LargeBlobAuthenticationOutput
* @see <a
* href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-large-blob-extension">§10.5.Large
* blob storage extension (largeBlob)</a>
Expand All @@ -114,6 +121,19 @@ public Optional<Extensions.LargeBlob.LargeBlobAuthenticationOutput> getLargeBlob
return Optional.ofNullable(largeBlob);
}

/**
* The extension output for the <a
* href="https://www.w3.org/TR/2025/WD-webauthn-3-20250127/#prf-extension">Pseudo-random function
* (<code>prf</code>) extension</a>, if any.
*
* @see com.yubico.webauthn.data.Extensions.Prf.PrfAuthenticationOutput
* @see <a href="https://www.w3.org/TR/2025/WD-webauthn-3-20250127/#prf-extension">§10.1.4.
* Pseudo-random function extension (prf)</a>
*/
public Optional<Extensions.Prf.PrfAuthenticationOutput> getPrf() {
return Optional.ofNullable(prf);
}

public static class ClientAssertionExtensionOutputsBuilder {

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,19 @@ public class ClientRegistrationExtensionOutputs implements ClientExtensionOutput

private final Extensions.LargeBlob.LargeBlobRegistrationOutput largeBlob;

private final Extensions.Prf.PrfRegistrationOutput prf;

@JsonCreator
private ClientRegistrationExtensionOutputs(
@JsonProperty("appidExclude") Boolean appidExclude,
@JsonProperty("credProps")
Extensions.CredentialProperties.CredentialPropertiesOutput credProps,
@JsonProperty("largeBlob") Extensions.LargeBlob.LargeBlobRegistrationOutput largeBlob) {
@JsonProperty("largeBlob") Extensions.LargeBlob.LargeBlobRegistrationOutput largeBlob,
@JsonProperty("prf") Extensions.Prf.PrfRegistrationOutput prf) {
this.appidExclude = appidExclude;
this.credProps = credProps;
this.largeBlob = largeBlob;
this.prf = prf;
}

@Override
Expand All @@ -82,6 +86,9 @@ public Set<String> getExtensionIds() {
if (largeBlob != null) {
ids.add(Extensions.LargeBlob.EXTENSION_ID);
}
if (prf != null) {
ids.add(Extensions.Prf.EXTENSION_ID);
}
return ids;
}

Expand Down Expand Up @@ -127,4 +134,17 @@ public Optional<Extensions.CredentialProperties.CredentialPropertiesOutput> getC
public Optional<Extensions.LargeBlob.LargeBlobRegistrationOutput> getLargeBlob() {
return Optional.ofNullable(largeBlob);
}

/**
* The extension output for the <a
* href="https://www.w3.org/TR/2025/WD-webauthn-3-20250127/#prf-extension">Pseudo-random function
* (<code>prf</code>) extension</a>, if any.
*
* @see com.yubico.webauthn.data.Extensions.Prf.PrfRegistrationOutput
* @see <a href="https://www.w3.org/TR/2025/WD-webauthn-3-20250127/#prf-extension">§10.1.4.
* Pseudo-random function extension (prf)</a>
*/
public Optional<Extensions.Prf.PrfRegistrationOutput> getPrf() {
return Optional.ofNullable(prf);
}
}
Loading