com.yubico.webauthn.data.Extensions Maven / Gradle / Ivy
Show all versions of webauthn-server-core Show documentation
package com.yubico.webauthn.data;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonValue;
import com.upokecenter.cbor.CBORObject;
import com.upokecenter.cbor.CBORType;
import com.yubico.webauthn.StartRegistrationOptions;
import com.yubico.webauthn.extension.uvm.KeyProtectionType;
import com.yubico.webauthn.extension.uvm.MatcherProtectionType;
import com.yubico.webauthn.extension.uvm.UserVerificationMethod;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import lombok.NonNull;
import lombok.Value;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
/** Definitions for WebAuthn extensions. */
@Slf4j
@UtilityClass
public class Extensions {
/**
* Definitions for the FIDO AppID Extension (appid
).
*
* @see §10.1.
* FIDO AppID Extension (appid)
*/
public static class Appid {
static final String EXTENSION_ID = "appid";
}
/**
* Definitions for the 10.2. FIDO AppID Exclusion Extension (appidExclude
).
*
* @see 10.2.
* FIDO AppID Exclusion Extension (appidExclude)
*/
public static class AppidExclude {
static final String EXTENSION_ID = "appidExclude";
}
/**
* Definitions for the Credential Properties Extension (credProps
).
*
* @see §10.4.
* Credential Properties Extension (credProps)
*/
public static class CredentialProperties {
static final String EXTENSION_ID = "credProps";
/**
* Extension outputs for the Credential Properties Extension (credProps
).
*
* @see §10.4.
* Credential Properties Extension (credProps)
*/
@Value
public static class CredentialPropertiesOutput {
@JsonProperty("rk")
private final Boolean rk;
@JsonCreator
CredentialPropertiesOutput(@JsonProperty("rk") Boolean rk) {
this.rk = rk;
}
/**
* This OPTIONAL property, known abstractly as the resident key credential property
* (i.e., client-side discoverable credential property), is a Boolean value indicating
* whether the {@link PublicKeyCredential} returned as a result of a registration ceremony is
* a client-side discoverable credential (passkey).
*
* If this is true
, the credential is a discoverable credential
* (passkey).
*
*
If this is false
, the credential is a server-side credential.
*
*
If this is not present, it is not known whether the credential is a discoverable
* credential or a server-side credential.
*
* @see §10.4.
* Credential Properties Extension (credProps)
* @see Client-side
* discoverable Credential
* @see Server-side
* Credential
* @see Passkey in passkeys.dev reference
*/
public Optional getRk() {
return Optional.ofNullable(rk);
}
}
}
/**
* Definitions for the Large blob storage extension (largeBlob
).
*
* @see §10.5.
* Large blob storage extension (largeBlob)
*/
public static class LargeBlob {
static final String EXTENSION_ID = "largeBlob";
/**
* Extension inputs for the Large blob storage extension (largeBlob
) in
* registration ceremonies.
*
* @see §10.5.
* Large blob storage extension (largeBlob)
*/
@Value
public static class LargeBlobRegistrationInput {
/**
* The Relying Party's preference of whether the created credential should support the
* largeBlob
extension.
*/
@JsonProperty private final LargeBlobSupport support;
@JsonCreator
public LargeBlobRegistrationInput(
/**
* The Relying Party's preference of whether the created credential should support the
*
* largeBlob
extension.
*
* Currently the only valid values are {@link LargeBlobSupport#REQUIRED} and {@link
* LargeBlobSupport#PREFERRED}, but custom values MAY be constructed in case more values
* are added in future revisions of the extension.
*/
@JsonProperty("support") LargeBlobSupport support) {
this.support = support;
}
/**
* The known valid arguments for the Large blob storage extension (largeBlob
)
* input in registration ceremonies.
*
*
Currently the only valid values are {@link LargeBlobSupport#REQUIRED} and {@link
* LargeBlobSupport#PREFERRED}, but custom values MAY be constructed in case more values are
* added in future revisions of the extension.
*
* @see #REQUIRED
* @see #PREFERRED
* @see §10.5.
* Large blob storage extension (largeBlob)
*/
@Value
public static class LargeBlobSupport {
/**
* The authenticator used for registration MUST support the largeBlob
* extension.
*
*
Note: If the client does not support the largeBlob
extension, this
* requirement MAY be ignored.
*
*
Note: CTAP authenticators only support largeBlob
in combination with
* {@link AuthenticatorSelectionCriteria#getResidentKey()} set to REQUIRED
in
* {@link StartRegistrationOptions#getAuthenticatorSelection()}.
*
* @see §10.5.
* Large blob storage extension (largeBlob)
*/
public static final LargeBlobSupport REQUIRED = new LargeBlobSupport("required");
/**
* If the authenticator used for registration supports the largeBlob
extension,
* it will be enabled for the created credential. If not supported, the credential will be
* created without large blob support.
*
*
Note: CTAP authenticators only support largeBlob
in combination with
* {@link AuthenticatorSelectionCriteria#getResidentKey()} set to REQUIRED
in
* {@link StartRegistrationOptions#getAuthenticatorSelection()}.
*
* @see §10.5.
* Large blob storage extension (largeBlob)
*/
public static final LargeBlobSupport PREFERRED = new LargeBlobSupport("preferred");
/**
* The underlying string value of this {@link LargeBlobSupport} value.
*
* @see #REQUIRED
* @see #PREFERRED
*/
@JsonValue private final String value;
/**
* Returns a new {@link Set} containing the {@link #REQUIRED} and {@link #PREFERRED} values.
*/
public static Set values() {
return Stream.of(REQUIRED, PREFERRED).collect(Collectors.toSet());
}
}
}
/**
* Extension inputs for the Large blob storage extension (largeBlob
) in
* authentication ceremonies.
*
* @see §10.5.
* Large blob storage extension (largeBlob)
*/
@Value
public static class LargeBlobAuthenticationInput {
/**
* If true
, indicates that the Relying Party would like to fetch the
* previously-written blob associated with the asserted credential.
*
* @see #read()
* @see §10.5.
* Large blob storage extension (largeBlob)
*/
@JsonProperty private final boolean read;
/**
* An opaque byte string that the Relying Party wishes to store with the existing credential.
*
* @see #write(ByteArray)
* @see §10.5.
* Large blob storage extension (largeBlob)
*/
@JsonProperty private final ByteArray write;
@JsonCreator
private LargeBlobAuthenticationInput(
@JsonProperty("read") final Boolean read, @JsonProperty("write") final ByteArray write) {
if (read != null && read && write != null) {
throw new IllegalArgumentException(
"Parameters \"read\" and \"write\" of largeBlob extension must not both be present.");
}
this.read = read != null && read;
this.write = write;
}
/**
* Configure the Large blob storage extension (largeBlob
) to fetch the
* previously-written blob associated with the asserted credential.
*
* Mutually exclusive with {@link #write(ByteArray)}.
*
* @see §10.5.
* Large blob storage extension (largeBlob)
*/
public static LargeBlobAuthenticationInput read() {
return new LargeBlobAuthenticationInput(true, null);
}
/**
* Configure the Large blob storage extension (largeBlob
) to store the given byte
* array with the existing credential.
*
*
Mutually exclusive with {@link #read()}.
*
* @see §10.5.
* Large blob storage extension (largeBlob)
*/
public static LargeBlobAuthenticationInput write(@NonNull final ByteArray write) {
return new LargeBlobAuthenticationInput(false, write);
}
/**
* @return true
if the read
property is set to true
,
* false
otherwise.
* @see #read()
* @see §10.5.
* Large blob storage extension (largeBlob)
*/
public boolean getRead() {
return read;
}
/**
* @return The value of the write
property if configured, empty otherwise.
* @see #write(ByteArray)
* @see §10.5.
* Large blob storage extension (largeBlob)
*/
public Optional getWrite() {
return Optional.ofNullable(write);
}
}
/**
* Extension outputs for the Large blob storage extension (largeBlob
) in
* registration ceremonies.
*
* @see §10.5.
* Large blob storage extension (largeBlob)
*/
@Value
public static class LargeBlobRegistrationOutput {
/**
* true
if, and only if, the created credential supports storing large blobs.
*
* @see §10.5.
* Large blob storage extension (largeBlob)
* @see LargeBlobRegistrationInput#getSupport()
*/
@JsonProperty private final boolean supported;
@JsonCreator
LargeBlobRegistrationOutput(@JsonProperty("supported") boolean supported) {
this.supported = supported;
}
}
/**
* Extension outputs for the Large blob storage extension (largeBlob
) in
* authentication ceremonies.
*
* @see §10.5.
* Large blob storage extension (largeBlob)
*/
@Value
public static class LargeBlobAuthenticationOutput {
@JsonProperty private final ByteArray blob;
@JsonProperty private final Boolean written;
@JsonCreator
LargeBlobAuthenticationOutput(
@JsonProperty("blob") ByteArray blob, @JsonProperty("written") Boolean written) {
this.blob = blob;
this.written = written;
}
/**
* The opaque byte string that was associated with the credential identified by {@link
* PublicKeyCredential#getId()}. Only valid if {@link LargeBlobAuthenticationInput#getRead()}
* was true
.
*
* @return A present {@link Optional} if {@link LargeBlobAuthenticationInput#getRead()} was
* true
and the blob content was successfully read. Otherwise (if {@link
* LargeBlobAuthenticationInput#getRead()} was false
or the content failed to
* be read) an empty {@link Optional}.
* @see §10.5.
* Large blob storage extension (largeBlob)
*/
public Optional getBlob() {
return Optional.ofNullable(blob);
}
/**
* A boolean that indicates that the contents of {@link
* LargeBlob.LargeBlobAuthenticationInput#write(ByteArray)
* LargeBlobAuthenticationInput#write(ByteArray)} were successfully stored on the
* authenticator, associated with the specified credential.
*
* @return Empty if {@link LargeBlobAuthenticationInput#getWrite()} was not present. Otherwise
* true
if and only if the value of {@link
* LargeBlobAuthenticationInput#getWrite()} was successfully stored by the authenticator.
* @see §10.5.
* Large blob storage extension (largeBlob)
*/
public Optional getWritten() {
return Optional.ofNullable(written);
}
}
}
/**
* Definitions for the User Verification Method (uvm
) Extension.
*
* @see §10.3.
* User Verification Method Extension (uvm)
*/
public static class Uvm {
static final String EXTENSION_ID = "uvm";
/**
* A uvmEntry
as defined in §10.3. User
* Verification Method Extension (uvm).
*
* @see §10.3.
* User Verification Method Extension (uvm)
* @see UserVerificationMethod
* @see KeyProtectionType
* @see MatcherProtectionType
*/
@Value
public static class UvmEntry {
private final UserVerificationMethod userVerificationMethod;
private final KeyProtectionType keyProtectionType;
private final MatcherProtectionType matcherProtectionType;
public UvmEntry(
@JsonProperty("userVerificationMethod") UserVerificationMethod userVerificationMethod,
@JsonProperty("keyProtectionType") KeyProtectionType keyProtectionType,
@JsonProperty("matcherProtectionType") MatcherProtectionType matcherProtectionType) {
this.userVerificationMethod = userVerificationMethod;
this.keyProtectionType = keyProtectionType;
this.matcherProtectionType = matcherProtectionType;
}
}
static Optional> parseAuthenticatorExtensionOutput(CBORObject cbor) {
if (validateAuthenticatorExtensionOutput(cbor)) {
return Optional.of(
cbor.get(EXTENSION_ID).getValues().stream()
.map(
uvmEntry ->
new UvmEntry(
UserVerificationMethod.fromValue(uvmEntry.get(0).AsInt32Value()),
KeyProtectionType.fromValue(
uvmEntry.get(1).AsNumber().ToInt16IfExact()),
MatcherProtectionType.fromValue(
uvmEntry.get(2).AsNumber().ToInt16IfExact())))
.collect(Collectors.toList()));
} else {
return Optional.empty();
}
}
private static boolean validateAuthenticatorExtensionOutput(CBORObject extensions) {
if (!extensions.ContainsKey(EXTENSION_ID)) {
return false;
}
CBORObject uvm = extensions.get(EXTENSION_ID);
if (uvm.getType() != CBORType.Array) {
log.debug(
"Invalid CBOR type for \"{}\" extension output: expected array, was: {}",
EXTENSION_ID,
uvm.getType());
return false;
}
if (uvm.size() < 1 || uvm.size() > 3) {
log.debug(
"Invalid length \"{}\" extension output array: expected 1 to 3 (inclusive), was: {}",
EXTENSION_ID,
uvm.size());
return false;
}
for (CBORObject entry : uvm.getValues()) {
if (entry.getType() != CBORType.Array) {
log.debug("Invalid CBOR type for uvmEntry: expected array, was: {}", entry.getType());
return false;
}
if (entry.size() != 3) {
log.debug("Invalid length for uvmEntry: expected 3, was: {}", entry.size());
return false;
}
for (CBORObject i : entry.getValues()) {
if (!(i.isNumber() && i.AsNumber().IsInteger())) {
log.debug("Invalid type for uvmEntry element: expected integer, was: {}", i.getType());
return false;
}
}
}
return true;
}
}
}