All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.yubico.webauthn.data.AuthenticatorData Maven / Gradle / Ivy

The newest version!
// 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.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.upokecenter.cbor.CBORException;
import com.upokecenter.cbor.CBORObject;
import com.yubico.internal.util.BinaryUtil;
import com.yubico.internal.util.ExceptionUtil;
import com.yubico.internal.util.JacksonCodecs;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Optional;
import lombok.NonNull;
import lombok.Value;

/**
 * The authenticator data structure is a byte array of 37 bytes or more. This class presents the
 * authenticator data decoded as a high-level object.
 *
 * 

The authenticator data structure encodes contextual bindings made by the authenticator. These * bindings are controlled by the authenticator itself, and derive their trust from the WebAuthn * Relying Party's assessment of the security properties of the authenticator. In one extreme case, * the authenticator may be embedded in the client, and its bindings may be no more trustworthy than * the client data. At the other extreme, the authenticator may be a discrete entity with * high-security hardware and software, connected to the client over a secure channel. In both * cases, the Relying Party receives the authenticator data in the same format, and uses its * knowledge of the authenticator to make trust decisions. * * @see §6.1. * Authenticator Data */ @Value @JsonSerialize(using = AuthenticatorData.JsonSerializer.class) public class AuthenticatorData { /** * The original raw byte array that this object is decoded from. This is a byte array of 37 bytes * or more. * * @see §6.1. * Authenticator Data */ @NonNull private final ByteArray bytes; /** The flags bit field. */ @NonNull private final transient AuthenticatorDataFlags flags; /** * Attested credential data, if present. * *

This member is present if and only if the {@link AuthenticatorDataFlags#AT} flag is set. * * @see #flags */ @JsonIgnore private final transient AttestedCredentialData attestedCredentialData; @JsonIgnore private final transient CBORObject extensions; private static final int RP_ID_HASH_INDEX = 0; private static final int RP_ID_HASH_END = RP_ID_HASH_INDEX + 32; private static final int FLAGS_INDEX = RP_ID_HASH_END; private static final int FLAGS_END = FLAGS_INDEX + 1; private static final int COUNTER_INDEX = FLAGS_END; private static final int COUNTER_END = COUNTER_INDEX + 4; private static final int FIXED_LENGTH_PART_END_INDEX = COUNTER_END; /** Decode an {@link AuthenticatorData} object from a raw authenticator data byte array. */ @JsonCreator public AuthenticatorData(@NonNull ByteArray bytes) { ExceptionUtil.assertTrue( bytes.size() >= FIXED_LENGTH_PART_END_INDEX, "%s byte array must be at least %d bytes, was %d: %s", AuthenticatorData.class.getSimpleName(), FIXED_LENGTH_PART_END_INDEX, bytes.size(), bytes.getBase64Url()); this.bytes = bytes; final byte[] rawBytes = bytes.getBytes(); this.flags = new AuthenticatorDataFlags(rawBytes[FLAGS_INDEX]); if (flags.AT) { VariableLengthParseResult parseResult = parseAttestedCredentialData( flags, Arrays.copyOfRange(rawBytes, FIXED_LENGTH_PART_END_INDEX, rawBytes.length)); attestedCredentialData = parseResult.getAttestedCredentialData(); extensions = parseResult.getExtensions(); } else if (flags.ED) { attestedCredentialData = null; extensions = parseExtensions( Arrays.copyOfRange(rawBytes, FIXED_LENGTH_PART_END_INDEX, rawBytes.length)); } else { attestedCredentialData = null; extensions = null; } } /** The SHA-256 hash of the RP ID the credential is scoped to. */ @JsonProperty("rpIdHash") public ByteArray getRpIdHash() { return new ByteArray(Arrays.copyOfRange(bytes.getBytes(), RP_ID_HASH_INDEX, RP_ID_HASH_END)); } /** The 32-bit unsigned signature counter. */ public long getSignatureCounter() { return BinaryUtil.getUint32(Arrays.copyOfRange(bytes.getBytes(), COUNTER_INDEX, COUNTER_END)); } private static VariableLengthParseResult parseAttestedCredentialData( AuthenticatorDataFlags flags, byte[] bytes) { final int AAGUID_INDEX = 0; final int AAGUID_END = AAGUID_INDEX + 16; final int CREDENTIAL_ID_LENGTH_INDEX = AAGUID_END; final int CREDENTIAL_ID_LENGTH_END = CREDENTIAL_ID_LENGTH_INDEX + 2; ExceptionUtil.assertTrue( bytes.length >= CREDENTIAL_ID_LENGTH_END, "Attested credential data must contain at least %d bytes, was %d: %s", CREDENTIAL_ID_LENGTH_END, bytes.length, new ByteArray(bytes)); byte[] credentialIdLengthBytes = Arrays.copyOfRange(bytes, CREDENTIAL_ID_LENGTH_INDEX, CREDENTIAL_ID_LENGTH_END); final int L; try { L = BinaryUtil.getUint16(credentialIdLengthBytes); } catch (IllegalArgumentException e) { throw new IllegalArgumentException( "Invalid credential ID length bytes: " + Arrays.asList(credentialIdLengthBytes), e); } final int CREDENTIAL_ID_INDEX = CREDENTIAL_ID_LENGTH_END; final int CREDENTIAL_ID_END = CREDENTIAL_ID_INDEX + L; final int CREDENTIAL_PUBLIC_KEY_INDEX = CREDENTIAL_ID_END; final int CREDENTIAL_PUBLIC_KEY_AND_EXTENSION_DATA_END = bytes.length; ExceptionUtil.assertTrue( bytes.length >= CREDENTIAL_ID_END, "Expected credential ID of length %d, but attested credential data and extension data is only %d bytes: %s", CREDENTIAL_ID_END, bytes.length, new ByteArray(bytes)); ByteArrayInputStream indefiniteLengthBytes = new ByteArrayInputStream( Arrays.copyOfRange( bytes, CREDENTIAL_PUBLIC_KEY_INDEX, CREDENTIAL_PUBLIC_KEY_AND_EXTENSION_DATA_END)); final CBORObject credentialPublicKey = CBORObject.Read(indefiniteLengthBytes); final CBORObject extensions; if (indefiniteLengthBytes.available() > 0) { if (flags.ED) { try { extensions = CBORObject.Read(indefiniteLengthBytes); } catch (CBORException e) { throw new IllegalArgumentException("Failed to parse extension data", e); } } else { throw new IllegalArgumentException( String.format( "Flags indicate no extension data, but %d bytes remain after attested credential data.", indefiniteLengthBytes.available())); } } else { if (flags.ED) { throw new IllegalArgumentException( "Flags indicate there should be extension data, but no bytes remain after attested credential data."); } else { extensions = null; } } return new VariableLengthParseResult( AttestedCredentialData.builder() .aaguid(new ByteArray(Arrays.copyOfRange(bytes, AAGUID_INDEX, AAGUID_END))) .credentialId( new ByteArray(Arrays.copyOfRange(bytes, CREDENTIAL_ID_INDEX, CREDENTIAL_ID_END))) .credentialPublicKey(new ByteArray(credentialPublicKey.EncodeToBytes())) .build(), extensions); } private static CBORObject parseExtensions(byte[] bytes) { try { return CBORObject.DecodeFromBytes(bytes); } catch (CBORException e) { throw new IllegalArgumentException("Failed to parse extension data", e); } } @Value private static class VariableLengthParseResult { AttestedCredentialData attestedCredentialData; CBORObject extensions; } /** * Attested credential data, if present. * *

This member is present if and only if the {@link AuthenticatorDataFlags#AT} flag is set. * * @see #flags */ public Optional getAttestedCredentialData() { return Optional.ofNullable(attestedCredentialData); } /** * Extension-defined authenticator data, if present. * *

This member is present if and only if the {@link AuthenticatorDataFlags#ED} flag is set. * *

Changes to the returned value are not reflected in the {@link AuthenticatorData} object. * * @see #flags */ public Optional getExtensions() { return Optional.ofNullable(extensions).map(JacksonCodecs::deepCopy); } static class JsonSerializer extends com.fasterxml.jackson.databind.JsonSerializer { @Override public void serialize( AuthenticatorData value, JsonGenerator gen, SerializerProvider serializers) throws IOException { gen.writeString(value.getBytes().getBase64Url()); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy