com.yubico.webauthn.data.PublicKeyCredential 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.data;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.type.TypeReference;
import com.yubico.internal.util.JacksonCodecs;
import com.yubico.webauthn.AssertionResult;
import com.yubico.webauthn.FinishAssertionOptions;
import com.yubico.webauthn.FinishRegistrationOptions;
import com.yubico.webauthn.RegistrationResult;
import com.yubico.webauthn.RelyingParty;
import java.io.IOException;
import java.util.Optional;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.NonNull;
import lombok.Value;
/**
* The PublicKeyCredential interface inherits from Credential [CREDENTIAL-MANAGEMENT-1], and contains
* the attributes that are returned to the caller when a new credential is created, or a new
* assertion is requested.
*
* @see §5.1.
* PublicKeyCredential Interface
*/
@Value
@Builder(toBuilder = true)
public class PublicKeyCredential<
A extends AuthenticatorResponse, B extends ClientExtensionOutputs> {
/**
* The raw Credential ID of this credential, corresponding to the rawId
attribute in
* the WebAuthn API.
*/
@NonNull private final ByteArray id;
/**
* The authenticator's response to the client’s request to either create a public key credential,
* or generate an authentication assertion.
*
* If the {@link PublicKeyCredential} was created in response to
* navigator.credentials.create()
, this attribute’s value will be an {@link
* AuthenticatorAttestationResponse}, otherwise, the {@link PublicKeyCredential} was created in
* response to navigator.credentials.get()
, and this attribute’s value will be an
* {@link AuthenticatorAssertionResponse}.
*/
@NonNull private final A response;
private final AuthenticatorAttachment authenticatorAttachment;
/**
* A map containing extension identifier → client extension output entries produced by the
* extension’s client extension processing.
*/
@NonNull private final B clientExtensionResults;
/** The {@link PublicKeyCredential}'s type value is the string "public-key". */
@NonNull @Builder.Default
private final PublicKeyCredentialType type = PublicKeyCredentialType.PUBLIC_KEY;
@JsonCreator
private PublicKeyCredential(
@JsonProperty("id") ByteArray id,
@JsonProperty("rawId") ByteArray rawId,
@NonNull @JsonProperty("response") A response,
@JsonProperty("authenticatorAttachment") AuthenticatorAttachment authenticatorAttachment,
@NonNull @JsonProperty("clientExtensionResults") B clientExtensionResults,
@NonNull @JsonProperty("type") PublicKeyCredentialType type) {
if (id == null && rawId == null) {
throw new NullPointerException("At least one of \"id\" and \"rawId\" must be non-null.");
}
if (id != null && rawId != null && !id.equals(rawId)) {
throw new IllegalArgumentException(
String.format("\"id\" and \"rawId\" are not equal: %s != %s", id, rawId));
}
this.id = id == null ? rawId : id;
this.response = response;
this.authenticatorAttachment = authenticatorAttachment;
this.clientExtensionResults = clientExtensionResults;
this.type = type;
}
private PublicKeyCredential(
ByteArray id,
@NonNull A response,
AuthenticatorAttachment authenticatorAttachment,
@NonNull B clientExtensionResults,
@NonNull PublicKeyCredentialType type) {
this(id, null, response, authenticatorAttachment, clientExtensionResults, type);
}
/**
* The authenticator
* attachment modality in effect at the time the credential was created or used.
*
*
If parsed from JSON, this will be present if and only if the input was a valid value of
* {@link AuthenticatorAttachment}.
*
*
The same value will also be available via {@link
* RegistrationResult#getAuthenticatorAttachment()} or {@link
* AssertionResult#getAuthenticatorAttachment()} on the result from {@link
* RelyingParty#finishRegistration(FinishRegistrationOptions)} or {@link
* RelyingParty#finishAssertion(FinishAssertionOptions)}.
*
* @see RegistrationResult#getAuthenticatorAttachment()
* @see AssertionResult#getAuthenticatorAttachment()
* @deprecated EXPERIMENTAL: This feature is from a not yet mature standard; it could change as
* the standard matures.
*/
@Deprecated
public Optional getAuthenticatorAttachment() {
return Optional.ofNullable(authenticatorAttachment);
}
public static
PublicKeyCredentialBuilder.MandatoryStages builder() {
return new PublicKeyCredentialBuilder().start();
}
public static class PublicKeyCredentialBuilder<
A extends AuthenticatorResponse, B extends ClientExtensionOutputs> {
private MandatoryStages start() {
return new MandatoryStages(this);
}
@AllArgsConstructor
public class MandatoryStages {
private final PublicKeyCredentialBuilder builder;
/**
* {@link PublicKeyCredentialBuilder#id(ByteArray) id} is a required parameter.
*
* @see PublicKeyCredentialBuilder#id(ByteArray)
*/
public Step2 id(ByteArray id) {
builder.id(id);
return new Step2();
}
public class Step2 {
/**
* {@link PublicKeyCredentialBuilder#response(AuthenticatorResponse) response} is a required
* parameter.
*
* @see PublicKeyCredentialBuilder#response(AuthenticatorResponse)
*/
public Step3 response(A response) {
builder.response(response);
return new Step3();
}
}
public class Step3 {
/**
* {@link PublicKeyCredentialBuilder#clientExtensionResults(ClientExtensionOutputs)
* clientExtensionResults} is a required parameter.
*
* @see PublicKeyCredentialBuilder#clientExtensionResults(ClientExtensionOutputs)
*/
public PublicKeyCredentialBuilder clientExtensionResults(B clientExtensionResults) {
return builder.clientExtensionResults(clientExtensionResults);
}
}
}
}
/**
* Parse a {@link PublicKeyCredential} object from JSON.
*
* The json
should be of the following format:
*
*
* {
* "id": "(resp.id)",
* "response": {
* "attestationObject": "(Base64Url encoded resp.attestationObject)",
* "clientDataJSON": "(Base64Url encoded resp.clientDataJSON)"
* },
* "clientExtensionResults": { (resp.getClientExtensionResults()) },
* "type": "public-key"
* }
*
*
*
* - resp:
*
- The PublicKeyCredential
* object returned from a registration ceremony.
*
- id:
*
- The string value of
resp.id
* - response.attestationObject:
*
- The value of
resp.attestationObject
, Base64Url encoded as a string
* - response.clientDataJSON:
*
- The value of
resp.clientDataJSON
, Base64Url encoded as a string
* - clientExtensionResults:
*
- The return value of
resp.getClientExtensionResults()
* - type:
*
- The literal string value
"public-key"
*
*
* @param json a JSON string of the above format
* @throws IOException if the json
is invalid or cannot be decoded as a {@link
* PublicKeyCredential}
*/
public static PublicKeyCredential<
AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs>
parseRegistrationResponseJson(String json) throws IOException {
return JacksonCodecs.json()
.readValue(
json,
new TypeReference<
PublicKeyCredential<
AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs>>() {});
}
/**
* Parse a {@link PublicKeyCredential} object from JSON.
*
* The json
should be of the following format:
*
*
* {
* "id": "(resp.id)",
* "response": {
* "authenticatorData": "(Base64Url encoded resp.authenticatorData)",
* "signature": "(Base64Url encoded resp.signature)",
* "clientDataJSON": "(Base64Url encoded resp.clientDataJSON)",
* "userHandle": "(null, undefined or Base64Url encoded resp.userHandle)"
* },
* "clientExtensionResults": { (resp.getClientExtensionResults()) },
* "type": "public-key"
* }
*
*
*
* - resp:
*
- The PublicKeyCredential
* object returned from an authentication ceremony.
*
- id:
*
- The string value of
resp.id
* - response.authenticatorData:
*
- The value of
resp.authenticatorData
, Base64Url encoded as a string
* - response.signature:
*
- The value of
resp.signature
, Base64Url encoded as a string
* - response.clientDataJSON:
*
- The value of
resp.clientDataJSON
, Base64Url encoded as a string
* - response.userHandle:
*
- The value of
resp.userHandle
Base64Url encoded as a string if present,
* otherwise null
or undefined
* - clientExtensionResults:
*
- The return value of
resp.getClientExtensionResults()
* - type:
*
- The literal string value
"public-key"
*
*
* @param json a JSON string of the above format
* @throws IOException if the json
is invalid or cannot be decoded as a {@link
* PublicKeyCredential}
*/
public static PublicKeyCredential
parseAssertionResponseJson(String json) throws IOException {
return JacksonCodecs.json()
.readValue(
json,
new TypeReference<
PublicKeyCredential<
AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs>>() {});
}
}