com.yubico.webauthn.RelyingPartyV2 Maven / Gradle / Ivy
Show all versions of webauthn-server-core Show documentation
// Copyright (c) 2018, Yubico AB
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package com.yubico.webauthn;
import com.yubico.internal.util.CollectionUtil;
import com.yubico.internal.util.OptionalUtil;
import com.yubico.webauthn.attestation.AttestationTrustSource;
import com.yubico.webauthn.data.AssertionExtensionInputs;
import com.yubico.webauthn.data.AttestationConveyancePreference;
import com.yubico.webauthn.data.AuthenticatorData;
import com.yubico.webauthn.data.ByteArray;
import com.yubico.webauthn.data.CollectedClientData;
import com.yubico.webauthn.data.PublicKeyCredentialCreationOptions;
import com.yubico.webauthn.data.PublicKeyCredentialCreationOptions.PublicKeyCredentialCreationOptionsBuilder;
import com.yubico.webauthn.data.PublicKeyCredentialParameters;
import com.yubico.webauthn.data.PublicKeyCredentialRequestOptions;
import com.yubico.webauthn.data.PublicKeyCredentialRequestOptions.PublicKeyCredentialRequestOptionsBuilder;
import com.yubico.webauthn.data.RegistrationExtensionInputs;
import com.yubico.webauthn.data.RelyingPartyIdentity;
import com.yubico.webauthn.exception.AssertionFailedException;
import com.yubico.webauthn.exception.InvalidSignatureCountException;
import com.yubico.webauthn.exception.RegistrationFailedException;
import com.yubico.webauthn.extension.appid.AppId;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.KeyFactory;
import java.security.SecureRandom;
import java.security.Signature;
import java.time.Clock;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.Builder;
import lombok.NonNull;
import lombok.Value;
import lombok.extern.slf4j.Slf4j;
/**
* Encapsulates the four basic Web Authentication operations - start/finish registration,
* start/finish authentication - along with overall operational settings for them.
*
* This class has no mutable state. An instance of this class may therefore be thought of as a
* container for specialized versions (function closures) of these four operations rather than a
* stateful object.
*/
@Slf4j
@Builder(toBuilder = true)
@Value
public class RelyingPartyV2 {
private static final SecureRandom random = new SecureRandom();
/**
* The {@link RelyingPartyIdentity} that will be set as the {@link
* PublicKeyCredentialCreationOptions#getRp() rp} parameter when initiating registration
* operations, and which {@link AuthenticatorData#getRpIdHash()} will be compared against. This is
* a required parameter.
*
* A successful registration or authentication operation requires {@link
* AuthenticatorData#getRpIdHash()} to exactly equal the SHA-256 hash of this member's {@link
* RelyingPartyIdentity#getId() id} member. Alternatively, it may instead equal the SHA-256 hash
* of {@link #getAppId() appId} if the latter is present.
*
* @see #startRegistration(StartRegistrationOptions)
* @see PublicKeyCredentialCreationOptions
*/
@NonNull private final RelyingPartyIdentity identity;
/**
* The allowed origins that returned authenticator responses will be compared against.
*
*
The default is the set containing only the string
* "https://" + {@link #getIdentity()}.getId()
.
*
*
If {@link RelyingPartyV2Builder#allowOriginPort(boolean) allowOriginPort} and {@link
* RelyingPartyV2Builder#allowOriginSubdomain(boolean) allowOriginSubdomain} are both false
*
(the default), then a successful registration or authentication operation requires
* {@link CollectedClientData#getOrigin()} to exactly equal one of these values.
*
*
If {@link RelyingPartyV2Builder#allowOriginPort(boolean) allowOriginPort} is true
*
, then the above rule is relaxed to allow any port number in {@link
* CollectedClientData#getOrigin()}, regardless of any port specified.
*
*
If {@link RelyingPartyV2Builder#allowOriginSubdomain(boolean) allowOriginSubdomain} is
*
* true
, then the above rule is relaxed to allow any subdomain, of any depth, of any of
* these values.
*
*
For either of the above relaxations to take effect, both the allowed origin and the client
* data origin must be valid URLs. Origins that are not valid URLs are matched only by exact
* string equality.
*
* @see #getIdentity()
*/
@NonNull private final Set origins;
/**
* An abstract database which can look up credentials, usernames and user handles from usernames,
* user handles and credential IDs. This is a required parameter.
*
* This is used to look up:
*
*
* - the user handle for a user logging in via user name
*
- the user name for a user logging in via user handle
*
- the credential IDs to include in {@link
* PublicKeyCredentialCreationOptions#getExcludeCredentials()}
*
- the credential IDs to include in {@link
* PublicKeyCredentialRequestOptions#getAllowCredentials()}
*
- that the correct user owns the credential when verifying an assertion
*
- the public key to use to verify an assertion
*
- the stored signature counter when verifying an assertion
*
*/
@NonNull private final CredentialRepositoryV2 credentialRepository;
/**
* Enable support for identifying users by username.
*
* If set, then {@link #startAssertion(StartAssertionOptions)} allows setting the {@link
* StartAssertionOptions.StartAssertionOptionsBuilder#username(String) username} parameter when
* starting an assertion.
*
*
By default, this is not set.
*
* @deprecated EXPERIMENTAL: This is an experimental feature. It is likely to change or be deleted
* before reaching a mature release.
*/
@Deprecated private final UsernameRepository usernameRepository;
/**
* The extension input to set for the appid
and appidExclude
extensions.
*
*
You do not need this extension if you have not previously supported U2F. Its purpose is to
* make already-registered U2F credentials forward-compatible with the WebAuthn API. It is not
* needed for new registrations, even of U2F authenticators.
*
*
If this member is set, {@link #startAssertion(StartAssertionOptions) startAssertion} will
* automatically set the appid
extension input, and {@link
* #finishAssertion(FinishAssertionOptions) finishAssertion} will adjust its verification logic to
* also accept this AppID as an alternative to the RP ID. Likewise, {@link
* #startRegistration(StartRegistrationOptions)} startRegistration} will automatically set the
* appidExclude
extension input.
*
*
By default, this is not set.
*
* @see AssertionExtensionInputs#getAppid()
* @see RegistrationExtensionInputs#getAppidExclude()
* @see §10.1.
* FIDO AppID Extension (appid)
* @see §10.2.
* FIDO AppID Exclusion Extension (appidExclude)
*/
@NonNull private final Optional appId;
/**
* The argument for the {@link PublicKeyCredentialCreationOptions#getAttestation() attestation}
* parameter in registration operations.
*
* Unless your application has a concrete policy for authenticator attestation, it is
* recommended to leave this parameter undefined.
*
*
If you set this, you may want to explicitly set {@link
* RelyingPartyV2Builder#allowUntrustedAttestation(boolean) allowUntrustedAttestation} and {@link
* RelyingPartyV2Builder#attestationTrustSource(AttestationTrustSource) attestationTrustSource}
* too.
*
*
By default, this is not set.
*
* @see PublicKeyCredentialCreationOptions#getAttestation()
* @see §6.4.
* Attestation
*/
@NonNull private final Optional attestationConveyancePreference;
/**
* An {@link AttestationTrustSource} instance to use for looking up trust roots for authenticator
* attestation. This matters only if {@link #getAttestationConveyancePreference()} is non-empty
* and not set to {@link AttestationConveyancePreference#NONE}.
*
* By default, this is not set.
*
* @see PublicKeyCredentialCreationOptions#getAttestation()
* @see §6.4.
* Attestation
*/
@NonNull private final Optional attestationTrustSource;
/**
* The argument for the {@link PublicKeyCredentialCreationOptions#getPubKeyCredParams()
* pubKeyCredParams} parameter in registration operations.
*
* This is a list of acceptable public key algorithms and their parameters, ordered from most
* to least preferred.
*
*
The default is the following list, in order:
*
*
* - {@link PublicKeyCredentialParameters#ES256 ES256}
*
- {@link PublicKeyCredentialParameters#EdDSA EdDSA}
*
- {@link PublicKeyCredentialParameters#ES256 ES384}
*
- {@link PublicKeyCredentialParameters#ES256 ES512}
*
- {@link PublicKeyCredentialParameters#RS256 RS256}
*
- {@link PublicKeyCredentialParameters#RS384 RS384}
*
- {@link PublicKeyCredentialParameters#RS512 RS512}
*
*
* @see PublicKeyCredentialCreationOptions#getAttestation()
* @see §6.4.
* Attestation
*/
@Builder.Default @NonNull
private final List preferredPubkeyParams =
Collections.unmodifiableList(
Arrays.asList(
PublicKeyCredentialParameters.ES256,
PublicKeyCredentialParameters.EdDSA,
PublicKeyCredentialParameters.ES384,
PublicKeyCredentialParameters.ES512,
PublicKeyCredentialParameters.RS256,
PublicKeyCredentialParameters.RS384,
PublicKeyCredentialParameters.RS512));
/**
* If true
, the origin matching rule is relaxed to allow any port number.
*
* The default is false
.
*
*
Examples with
* origins: ["https://example.org", "https://accounts.example.org", "https://acme.com:8443"]
*
*
*
* -
*
allowOriginPort: false
*
Accepted:
*
* https://example.org
* https://accounts.example.org
* https://acme.com:8443
*
* Rejected:
*
* https://example.org:8443
* https://shop.example.org
* https://acme.com
* https://acme.com:9000
*
* -
*
allowOriginPort: true
*
Accepted:
*
* https://example.org
* https://example.org:8443
* https://accounts.example.org
* https://acme.com
* https://acme.com:8443
* https://acme.com:9000
*
* Rejected:
*
* https://shop.example.org
*
*
*/
@Builder.Default private final boolean allowOriginPort = false;
/**
* If true
, the origin matching rule is relaxed to allow any subdomain, of any depth,
* of the values of {@link RelyingPartyV2Builder#origins(Set) origins}.
*
* The default is false
.
*
*
Examples with origins: ["https://example.org", "https://acme.com:8443"]
*
*
* -
*
allowOriginSubdomain: false
*
Accepted:
*
* https://example.org
* https://acme.com:8443
*
* Rejected:
*
* https://example.org:8443
* https://accounts.example.org
* https://acme.com
* https://eu.shop.acme.com:8443
*
* -
*
allowOriginSubdomain: true
*
Accepted:
*
* https://example.org
* https://accounts.example.org
* https://acme.com:8443
* https://eu.shop.acme.com:8443
*
* Rejected:
*
* https://example.org:8443
* https://acme.com
*
*
*/
@Builder.Default private final boolean allowOriginSubdomain = false;
/**
* If false
, {@link #finishRegistration(FinishRegistrationOptions)
* finishRegistration} will only allow registrations where the attestation signature can be linked
* to a trusted attestation root. This excludes none attestation, and self attestation unless the
* self attestation key is explicitly trusted.
*
* Regardless of the value of this option, invalid attestation statements of supported formats
* will always be rejected. For example, a "packed" attestation statement with an invalid
* signature will be rejected even if this option is set to true
.
*
*
The default is true
.
*/
@Builder.Default private final boolean allowUntrustedAttestation = true;
/**
* If true
, {@link #finishAssertion(FinishAssertionOptions) finishAssertion} will
* succeed only if the {@link AuthenticatorData#getSignatureCounter() signature counter value} in
* the response is strictly greater than the {@link RegisteredCredential#getSignatureCount()
* stored signature counter value}, or if both counters are exactly zero.
*
*
The default is true
.
*/
@Builder.Default private final boolean validateSignatureCounter = true;
/**
* A {@link Clock} which will be used to tell the current time while verifying attestation
* certificate chains.
*
*
This is intended primarily for testing, and relevant only if {@link
* RelyingPartyV2Builder#attestationTrustSource(AttestationTrustSource)} is set.
*
*
The default is Clock.systemUTC()
.
*/
@Builder.Default @NonNull private final Clock clock = Clock.systemUTC();
@Builder
private RelyingPartyV2(
@NonNull RelyingPartyIdentity identity,
Set origins,
@NonNull CredentialRepositoryV2 credentialRepository,
UsernameRepository usernameRepository,
@NonNull Optional appId,
@NonNull Optional attestationConveyancePreference,
@NonNull Optional attestationTrustSource,
List preferredPubkeyParams,
boolean allowOriginPort,
boolean allowOriginSubdomain,
boolean allowUntrustedAttestation,
boolean validateSignatureCounter,
Clock clock) {
this.identity = identity;
this.origins =
origins != null
? CollectionUtil.immutableSet(origins)
: Collections.singleton("https://" + identity.getId());
for (String origin : this.origins) {
try {
new URL(origin);
} catch (MalformedURLException e) {
log.warn(
"Allowed origin is not a valid URL, it will match only by exact string equality: {}",
origin);
}
}
this.credentialRepository = credentialRepository;
this.usernameRepository = usernameRepository;
this.appId = appId;
this.attestationConveyancePreference = attestationConveyancePreference;
this.attestationTrustSource = attestationTrustSource;
this.preferredPubkeyParams = filterAvailableAlgorithms(preferredPubkeyParams);
this.allowOriginPort = allowOriginPort;
this.allowOriginSubdomain = allowOriginSubdomain;
this.allowUntrustedAttestation = allowUntrustedAttestation;
this.validateSignatureCounter = validateSignatureCounter;
this.clock = clock;
}
private static ByteArray generateChallenge() {
byte[] bytes = new byte[32];
random.nextBytes(bytes);
return new ByteArray(bytes);
}
/**
* Filter pubKeyCredParams
to only contain algorithms with a {@link KeyFactory} and a
* {@link Signature} available, and log a warning for every unsupported algorithm.
*
* @return a new {@link List} containing only the algorithms supported in the current JCA context.
*/
private static List filterAvailableAlgorithms(
List pubKeyCredParams) {
return RelyingParty.filterAvailableAlgorithms(pubKeyCredParams);
}
public PublicKeyCredentialCreationOptions startRegistration(
StartRegistrationOptions startRegistrationOptions) {
PublicKeyCredentialCreationOptionsBuilder builder =
PublicKeyCredentialCreationOptions.builder()
.rp(identity)
.user(startRegistrationOptions.getUser())
.challenge(generateChallenge())
.pubKeyCredParams(preferredPubkeyParams)
.excludeCredentials(
credentialRepository
.getCredentialDescriptorsForUserHandle(
startRegistrationOptions.getUser().getId())
.stream()
.map(ToPublicKeyCredentialDescriptor::toPublicKeyCredentialDescriptor)
.collect(Collectors.toSet()))
.authenticatorSelection(startRegistrationOptions.getAuthenticatorSelection())
.extensions(
startRegistrationOptions
.getExtensions()
.merge(
RegistrationExtensionInputs.builder()
.appidExclude(appId)
.credProps()
.build()))
.timeout(startRegistrationOptions.getTimeout());
attestationConveyancePreference.ifPresent(builder::attestation);
return builder.build();
}
public RegistrationResult finishRegistration(FinishRegistrationOptions finishRegistrationOptions)
throws RegistrationFailedException {
try {
return _finishRegistration(finishRegistrationOptions).run();
} catch (IllegalArgumentException e) {
throw new RegistrationFailedException(e);
}
}
/**
* This method is NOT part of the public API.
*
* This method is called internally by {@link #finishRegistration(FinishRegistrationOptions)}.
* It is a separate method to facilitate testing; users should call {@link
* #finishRegistration(FinishRegistrationOptions)} instead of this method.
*/
FinishRegistrationSteps _finishRegistration(FinishRegistrationOptions options) {
return new FinishRegistrationSteps(this, options);
}
public AssertionRequest startAssertion(StartAssertionOptions startAssertionOptions) {
if (startAssertionOptions.getUsername().isPresent() && usernameRepository == null) {
throw new IllegalArgumentException(
"StartAssertionOptions.username must not be set when usernameRepository is not configured.");
}
PublicKeyCredentialRequestOptionsBuilder pkcro =
PublicKeyCredentialRequestOptions.builder()
.challenge(generateChallenge())
.rpId(identity.getId())
.allowCredentials(
OptionalUtil.orElseOptional(
startAssertionOptions.getUserHandle(),
() ->
Optional.ofNullable(usernameRepository)
.flatMap(
unr ->
startAssertionOptions
.getUsername()
.flatMap(unr::getUserHandleForUsername)))
.map(credentialRepository::getCredentialDescriptorsForUserHandle)
.map(
descriptors ->
descriptors.stream()
.map(
ToPublicKeyCredentialDescriptor
::toPublicKeyCredentialDescriptor)
.collect(Collectors.toList())))
.extensions(
startAssertionOptions
.getExtensions()
.merge(startAssertionOptions.getExtensions().toBuilder().appid(appId).build()))
.timeout(startAssertionOptions.getTimeout());
startAssertionOptions.getUserVerification().ifPresent(pkcro::userVerification);
return AssertionRequest.builder()
.publicKeyCredentialRequestOptions(pkcro.build())
.username(startAssertionOptions.getUsername())
.userHandle(startAssertionOptions.getUserHandle())
.build();
}
/**
* @throws InvalidSignatureCountException if {@link
* RelyingPartyV2Builder#validateSignatureCounter(boolean) validateSignatureCounter} is
* true
, the {@link AuthenticatorData#getSignatureCounter() signature count} in the
* response is less than or equal to the {@link RegisteredCredential#getSignatureCount()
* stored signature count}, and at least one of the signature count values is nonzero.
* @throws AssertionFailedException if validation fails for any other reason.
*/
public AssertionResultV2 finishAssertion(FinishAssertionOptions finishAssertionOptions)
throws AssertionFailedException {
try {
return _finishAssertion(finishAssertionOptions).runV2();
} catch (IllegalArgumentException e) {
throw new AssertionFailedException(e);
}
}
/**
* This method is NOT part of the public API.
*
* This method is called internally by {@link #finishAssertion(FinishAssertionOptions)}. It is
* a separate method to facilitate testing; users should call {@link
* #finishAssertion(FinishAssertionOptions)} instead of this method.
*/
FinishAssertionSteps _finishAssertion(FinishAssertionOptions options) {
return new FinishAssertionSteps(this, options);
}
static RelyingPartyV2Builder builder(
RelyingPartyIdentity identity, CredentialRepositoryV2 credentialRepository) {
return new RelyingPartyV2Builder()
.identity(identity)
.credentialRepository(credentialRepository);
}
public static class RelyingPartyV2Builder {
private @NonNull Optional appId = Optional.empty();
private @NonNull Optional attestationConveyancePreference =
Optional.empty();
private @NonNull Optional attestationTrustSource = Optional.empty();
/**
* The extension input to set for the appid
and appidExclude
* extensions.
*
* You do not need this extension if you have not previously supported U2F. Its purpose is to
* make already-registered U2F credentials forward-compatible with the WebAuthn API. It is not
* needed for new registrations, even of U2F authenticators.
*
*
If this member is set, {@link #startAssertion(StartAssertionOptions) startAssertion} will
* automatically set the appid
extension input, and {@link
* #finishAssertion(FinishAssertionOptions) finishAssertion} will adjust its verification logic
* to also accept this AppID as an alternative to the RP ID. Likewise, {@link
* #startRegistration(StartRegistrationOptions)} startRegistration} will automatically set the
* appidExclude
extension input.
*
*
By default, this is not set.
*
* @see AssertionExtensionInputs#getAppid()
* @see RegistrationExtensionInputs#getAppidExclude()
* @see §10.1.
* FIDO AppID Extension (appid)
* @see §10.2.
* FIDO AppID Exclusion Extension (appidExclude)
*/
public RelyingPartyV2Builder appId(@NonNull Optional appId) {
this.appId = appId;
return this;
}
/**
* The extension input to set for the appid
and appidExclude
* extensions.
*
* You do not need this extension if you have not previously supported U2F. Its purpose is to
* make already-registered U2F credentials forward-compatible with the WebAuthn API. It is not
* needed for new registrations, even of U2F authenticators.
*
*
If this member is set, {@link #startAssertion(StartAssertionOptions) startAssertion} will
* automatically set the appid
extension input, and {@link
* #finishAssertion(FinishAssertionOptions) finishAssertion} will adjust its verification logic
* to also accept this AppID as an alternative to the RP ID. Likewise, {@link
* #startRegistration(StartRegistrationOptions)} startRegistration} will automatically set the
* appidExclude
extension input.
*
*
By default, this is not set.
*
* @see AssertionExtensionInputs#getAppid()
* @see RegistrationExtensionInputs#getAppidExclude()
* @see §10.1.
* FIDO AppID Extension (appid)
* @see §10.2.
* FIDO AppID Exclusion Extension (appidExclude)
*/
public RelyingPartyV2Builder appId(@NonNull AppId appId) {
return this.appId(Optional.of(appId));
}
/**
* The argument for the {@link PublicKeyCredentialCreationOptions#getAttestation() attestation}
* parameter in registration operations.
*
* Unless your application has a concrete policy for authenticator attestation, it is
* recommended to leave this parameter undefined.
*
*
If you set this, you may want to explicitly set {@link
* RelyingPartyV2Builder#allowUntrustedAttestation(boolean) allowUntrustedAttestation} and
* {@link RelyingPartyV2Builder#attestationTrustSource(AttestationTrustSource)
* attestationTrustSource} too.
*
*
By default, this is not set.
*
* @see PublicKeyCredentialCreationOptions#getAttestation()
* @see §6.4.
* Attestation
*/
public RelyingPartyV2Builder attestationConveyancePreference(
@NonNull Optional attestationConveyancePreference) {
this.attestationConveyancePreference = attestationConveyancePreference;
return this;
}
/**
* The argument for the {@link PublicKeyCredentialCreationOptions#getAttestation() attestation}
* parameter in registration operations.
*
* Unless your application has a concrete policy for authenticator attestation, it is
* recommended to leave this parameter undefined.
*
*
If you set this, you may want to explicitly set {@link
* RelyingPartyV2Builder#allowUntrustedAttestation(boolean) allowUntrustedAttestation} and
* {@link RelyingPartyV2Builder#attestationTrustSource(AttestationTrustSource)
* attestationTrustSource} too.
*
*
By default, this is not set.
*
* @see PublicKeyCredentialCreationOptions#getAttestation()
* @see §6.4.
* Attestation
*/
public RelyingPartyV2Builder attestationConveyancePreference(
@NonNull AttestationConveyancePreference attestationConveyancePreference) {
return this.attestationConveyancePreference(Optional.of(attestationConveyancePreference));
}
/**
* An {@link AttestationTrustSource} instance to use for looking up trust roots for
* authenticator attestation. This matters only if {@link #getAttestationConveyancePreference()}
* is non-empty and not set to {@link AttestationConveyancePreference#NONE}.
*
* By default, this is not set.
*
* @see PublicKeyCredentialCreationOptions#getAttestation()
* @see §6.4.
* Attestation
*/
public RelyingPartyV2Builder attestationTrustSource(
@NonNull Optional attestationTrustSource) {
this.attestationTrustSource = attestationTrustSource;
return this;
}
/**
* An {@link AttestationTrustSource} instance to use for looking up trust roots for
* authenticator attestation. This matters only if {@link #getAttestationConveyancePreference()}
* is non-empty and not set to {@link AttestationConveyancePreference#NONE}.
*
* By default, this is not set.
*
* @see PublicKeyCredentialCreationOptions#getAttestation()
* @see §6.4.
* Attestation
*/
public RelyingPartyV2Builder attestationTrustSource(
@NonNull AttestationTrustSource attestationTrustSource) {
return this.attestationTrustSource(Optional.of(attestationTrustSource));
}
}
}