com.yubico.webauthn.RegistrationResult 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.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.yubico.internal.util.CertificateParser;
import com.yubico.webauthn.RelyingParty.RelyingPartyBuilder;
import com.yubico.webauthn.attestation.AttestationTrustSource;
import com.yubico.webauthn.data.AttestationType;
import com.yubico.webauthn.data.AuthenticatorAttachment;
import com.yubico.webauthn.data.AuthenticatorAttestationResponse;
import com.yubico.webauthn.data.AuthenticatorData;
import com.yubico.webauthn.data.AuthenticatorDataFlags;
import com.yubico.webauthn.data.AuthenticatorRegistrationExtensionOutputs;
import com.yubico.webauthn.data.AuthenticatorResponse;
import com.yubico.webauthn.data.ByteArray;
import com.yubico.webauthn.data.ClientRegistrationExtensionOutputs;
import com.yubico.webauthn.data.Extensions;
import com.yubico.webauthn.data.PublicKeyCredential;
import com.yubico.webauthn.data.PublicKeyCredentialDescriptor;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NonNull;
import lombok.Value;
/** The result of a call to {@link RelyingParty#finishRegistration(FinishRegistrationOptions)}. */
@Value
public class RegistrationResult {
@JsonProperty
@Getter(AccessLevel.NONE)
private final PublicKeyCredential<
AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs>
credential;
/**
* true
if and only if the attestation signature was successfully linked to a trusted
* attestation root.
*
* This will always be false
unless the {@link
* RelyingPartyBuilder#attestationTrustSource(AttestationTrustSource) attestationTrustSource}
* setting was configured on the {@link RelyingParty} instance.
*
*
You can ignore this if authenticator attestation is not relevant to your application.
*/
private final boolean attestationTrusted;
/**
* The attestation
* type that was used for the created credential.
*
*
You can ignore this if authenticator attestation is not relevant to your application.
*
* @see §6.4.3.
* Attestation Types
*/
@NonNull private final AttestationType attestationType;
// JavaDoc on getter
private final List attestationTrustPath;
RegistrationResult(
PublicKeyCredential
credential,
boolean attestationTrusted,
@NonNull AttestationType attestationType,
Optional> attestationTrustPath) {
this.credential = credential;
this.attestationTrusted = attestationTrusted;
this.attestationType = attestationType;
this.attestationTrustPath = attestationTrustPath.orElse(null);
}
@JsonCreator
private static RegistrationResult fromJson(
@NonNull @JsonProperty("credential")
PublicKeyCredential
credential,
@JsonProperty("attestationTrusted") boolean attestationTrusted,
@NonNull @JsonProperty("attestationType") AttestationType attestationType,
@NonNull @JsonProperty("attestationTrustPath") Optional> attestationTrustPath) {
return new RegistrationResult(
credential,
attestationTrusted,
attestationType,
attestationTrustPath.map(
atp ->
atp.stream()
.map(
pem -> {
try {
return CertificateParser.parsePem(pem);
} catch (CertificateException e) {
throw new RuntimeException(e);
}
})
.collect(Collectors.toList())));
}
/**
* Check whether the user
* verification as performed during the registration ceremony.
*
* This flag is also available via
* {@link PublicKeyCredential}.{@link PublicKeyCredential#getResponse() getResponse()}.{@link AuthenticatorResponse#getParsedAuthenticatorData() getParsedAuthenticatorData()}.{@link AuthenticatorData#getFlags() getFlags()}.{@link AuthenticatorDataFlags#UV UV}
*
.
*
* @return true
if and only if the authenticator claims to have performed user
* verification during the registration ceremony.
* @see User Verification
* @see UV flag in §6.1. Authenticator
* Data
*/
@JsonIgnore
public boolean isUserVerified() {
return credential.getResponse().getParsedAuthenticatorData().getFlags().UV;
}
/**
* Check whether the created credential is backup eligible, using the BE flag in the authenticator data.
*
*
You SHOULD store this value in your representation of a {@link RegisteredCredential}. {@link
* CredentialRepository} implementations SHOULD set this value as the {@link
* RegisteredCredential.RegisteredCredentialBuilder#backupEligible(Boolean)
* backupEligible(Boolean)} value when reconstructing that {@link RegisteredCredential}.
*
* @return true
if and only if the created credential is backup eligible. NOTE that
* this is only a hint and not a guarantee, unless backed by a trusted authenticator
* attestation.
* @see Backup Eligible in §4.
* Terminology
* @see BE flag in §6.1. Authenticator
* Data
* @deprecated EXPERIMENTAL: This feature is from a not yet mature standard; it could change as
* the standard matures.
*/
@Deprecated
@JsonIgnore
public boolean isBackupEligible() {
return credential.getResponse().getParsedAuthenticatorData().getFlags().BE;
}
/**
* Get the current backup state of the
* created credential, using the BS
* flag in the authenticator data.
*
*
You SHOULD store this value in your representation of a {@link RegisteredCredential}. {@link
* CredentialRepository} implementations SHOULD set this value as the {@link
* RegisteredCredential.RegisteredCredentialBuilder#backupState(Boolean) backupState(Boolean)}
* value when reconstructing that {@link RegisteredCredential}.
*
* @return true
if and only if the created credential is believed to currently be
* backed up. NOTE that this is only a hint and not a guarantee, unless backed by a trusted
* authenticator attestation.
* @see Backup State in §4. Terminology
* @see BS flag in §6.1. Authenticator
* Data
* @deprecated EXPERIMENTAL: This feature is from a not yet mature standard; it could change as
* the standard matures.
*/
@Deprecated
@JsonIgnore
public boolean isBackedUp() {
return credential.getResponse().getParsedAuthenticatorData().getFlags().BS;
}
/**
* The authenticator
* attachment modality in effect at the time the credential was created.
*
* @see PublicKeyCredential#getAuthenticatorAttachment()
* @deprecated EXPERIMENTAL: This feature is from a not yet mature standard; it could change as
* the standard matures.
*/
@Deprecated
@JsonIgnore
public Optional getAuthenticatorAttachment() {
return credential.getAuthenticatorAttachment();
}
/**
* The signature count returned with the created credential.
*
* This is used in {@link RelyingParty#finishAssertion(FinishAssertionOptions)} to verify the
* validity of future signature counter values.
*
* @see RegisteredCredential#getSignatureCount()
*/
@JsonIgnore
public long getSignatureCount() {
return credential.getResponse().getParsedAuthenticatorData().getSignatureCounter();
}
/**
* The credential
* ID and transports
* of the created credential.
*
* @see Credential
* ID
* @see 5.8.3.
* Credential Descriptor (dictionary PublicKeyCredentialDescriptor)
* @see PublicKeyCredential#getId()
*/
@JsonIgnore
public PublicKeyCredentialDescriptor getKeyId() {
return PublicKeyCredentialDescriptor.builder()
.id(credential.getId())
.type(credential.getType())
.transports(credential.getResponse().getTransports())
.build();
}
/**
* The aaguid
* reported in the of the
* created credential.
*
*
This MAY be an AAGUID consisting of only zeroes.
*/
@JsonIgnore
public ByteArray getAaguid() {
return credential
.getResponse()
.getAttestation()
.getAuthenticatorData()
.getAttestedCredentialData()
.get()
.getAaguid();
}
/**
* The public key of the created credential.
*
*
This is used in {@link RelyingParty#finishAssertion(FinishAssertionOptions)} to verify the
* authentication signatures.
*
* @see RegisteredCredential#getPublicKeyCose()
*/
@JsonIgnore
public ByteArray getPublicKeyCose() {
return credential
.getResponse()
.getAttestation()
.getAuthenticatorData()
.getAttestedCredentialData()
.get()
.getCredentialPublicKey();
}
/**
* The public key of the created credential, parsed as a {@link PublicKey} object.
*
* @see #getPublicKeyCose()
* @see RegisteredCredential#getParsedPublicKey()
*/
@NonNull
@JsonIgnore
public PublicKey getParsedPublicKey()
throws InvalidKeySpecException, NoSuchAlgorithmException, IOException {
return WebAuthnCodecs.importCosePublicKey(getPublicKeyCose());
}
/**
* The client
* extension outputs, if any.
*
*
This is present if and only if at least one extension output is present in the return value.
*
* @see §9.4.
* Client Extension Processing
* @see ClientRegistrationExtensionOutputs
* @see #getAuthenticatorExtensionOutputs() ()
*/
@JsonIgnore
public Optional getClientExtensionOutputs() {
return Optional.ofNullable(credential.getClientExtensionResults())
.filter(ceo -> !ceo.getExtensionIds().isEmpty());
}
/**
* The authenticator
* extension outputs, if any.
*
* This is present if and only if at least one extension output is present in the return value.
*
* @see §9.5.
* Authenticator Extension Processing
* @see AuthenticatorRegistrationExtensionOutputs
* @see #getClientExtensionOutputs()
*/
@JsonIgnore
public Optional getAuthenticatorExtensionOutputs() {
return AuthenticatorRegistrationExtensionOutputs.fromAuthenticatorData(
credential.getResponse().getParsedAuthenticatorData());
}
/**
* Try to determine whether the created credential is a discoverable
* credential, also called a passkey, using the output from the
* credProps
extension.
*
* @return A present true
if the created credential is a passkey (discoverable). A
* present
* false
if the created credential is not a passkey. An empty value if it is not known
* whether the created credential is a passkey.
* @see §10.4.
* Credential Properties Extension (credProps), "rk" output
* @see Discoverable
* Credential
* @see Passkey in passkeys.dev reference
*/
@JsonIgnore
public Optional isDiscoverable() {
return getClientExtensionOutputs()
.flatMap(outputs -> outputs.getCredProps())
.flatMap(credProps -> credProps.getRk());
}
/**
* Retrieve a suitable nickname for this credential, if one is available.
*
* This returns the authenticatorDisplayName
output from the
* credProps
extension.
*
* @return A user-chosen or vendor-default display name for the credential, if available.
* Otherwise empty.
* @see
* authenticatorDisplayName
in §10.1.3. Credential Properties Extension
* (credProps)
* @see AssertionResult#getAuthenticatorDisplayName()
* @see AssertionResultV2#getAuthenticatorDisplayName()
* @see Extensions.CredentialProperties.CredentialPropertiesOutput#getAuthenticatorDisplayName()
* @deprecated EXPERIMENTAL: This feature is from a not yet mature standard; it could change as
* the standard matures.
*/
@JsonIgnore
@Deprecated
public Optional getAuthenticatorDisplayName() {
return getClientExtensionOutputs()
.flatMap(outputs -> outputs.getCredProps())
.flatMap(credProps -> credProps.getAuthenticatorDisplayName());
}
/**
* The attestation
* trust path for the created credential, if any.
*
* If present, this may be useful for looking up attestation metadata from external sources.
* The attestation trust path has been successfully verified as trusted if and only if {@link
* #isAttestationTrusted()} is true
.
*
*
You can ignore this if authenticator attestation is not relevant to your application.
*
* @see Attestation
* trust path
*/
@JsonIgnore
public Optional> getAttestationTrustPath() {
return Optional.ofNullable(attestationTrustPath);
}
@JsonProperty("attestationTrustPath")
private Optional> getAttestationTrustPathJson() {
return getAttestationTrustPath()
.map(
x5c ->
x5c.stream()
.map(
cert -> {
try {
return new ByteArray(cert.getEncoded()).getBase64();
} catch (CertificateEncodingException e) {
throw new RuntimeException(e);
}
})
.collect(Collectors.toList()));
}
}