Skip to content

Commit

Permalink
largeBlob
Browse files Browse the repository at this point in the history
  • Loading branch information
AdamVe committed Oct 11, 2024
1 parent f03d145 commit f626d4d
Show file tree
Hide file tree
Showing 11 changed files with 1,076 additions and 325 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@
*/
public class MultipleAssertionsAvailable extends Throwable {
private final byte[] clientDataJson;
private final List<Ctap2Session.AssertionData> assertions;
private final List<BasicWebAuthnClient.WithExtensionResults<Ctap2Session.AssertionData>> assertions;

MultipleAssertionsAvailable(byte[] clientDataJson, List<Ctap2Session.AssertionData> assertions) {
MultipleAssertionsAvailable(byte[] clientDataJson, List<BasicWebAuthnClient.WithExtensionResults<Ctap2Session.AssertionData>> assertions) {
super("Request returned multiple assertions");

this.clientDataJson = clientDataJson;
Expand Down Expand Up @@ -65,10 +65,10 @@ public int getAssertionCount() {
*/
public List<PublicKeyCredentialUserEntity> getUsers() throws UserInformationNotAvailableError {
List<PublicKeyCredentialUserEntity> users = new ArrayList<>();
for (Ctap2Session.AssertionData assertion : assertions) {
for (BasicWebAuthnClient.WithExtensionResults<Ctap2Session.AssertionData> assertion : assertions) {
try {
users.add(PublicKeyCredentialUserEntity.fromMap(
Objects.requireNonNull(assertion.getUser()),
Objects.requireNonNull(assertion.data.getUser()),
SerializationType.CBOR
));
} catch (NullPointerException e) {
Expand All @@ -89,20 +89,20 @@ public PublicKeyCredential select(int index) {
if (assertions.isEmpty()) {
throw new IllegalStateException("Assertion has already been selected");
}
Ctap2Session.AssertionData assertion = assertions.get(index);
BasicWebAuthnClient.WithExtensionResults<Ctap2Session.AssertionData> assertion = assertions.get(index);
assertions.clear();

final Map<String, ?> user = Objects.requireNonNull(assertion.getUser());
final Map<String, ?> credential = Objects.requireNonNull(assertion.getCredential());
final Map<String, ?> user = Objects.requireNonNull(assertion.data.getUser());
final Map<String, ?> credential = Objects.requireNonNull(assertion.data.getCredential());
final byte[] credentialId = Objects.requireNonNull((byte[]) credential.get(PublicKeyCredentialDescriptor.ID));
return new PublicKeyCredential(
credentialId,
new AuthenticatorAssertionResponse(
clientDataJson,
assertion.getAuthenticatorData(),
assertion.getSignature(),
assertion.data.getAuthenticatorData(),
assertion.data.getSignature(),
Objects.requireNonNull((byte[]) user.get(PublicKeyCredentialUserEntity.ID))
),
null);
assertion.clientExtensionResults);
}
}
119 changes: 114 additions & 5 deletions fido/src/main/java/com/yubico/yubikit/fido/ctap/Ctap2Session.java
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,40 @@ public void selection(@Nullable CommandState state) throws IOException, CommandE
sendCbor(CMD_SELECTION, null, state);
}

/**
* This command allows a platform to store a larger amount of information associated with a credential.
*
* @param offset the byte offset at which to read/write
* @param get the number of bytes requested to read, must not be present if set is present
* @param set a fragment to write, must not be present if get is present
* @param length the total length of a write operation, present if, and only if, set is present
* and offset is zero
* @param pinUvAuthParam first 16 bytes of HMAC-SHA-256 of contents using pinUvAuthToken
* @param pinUvAuthProtocol PIN/UV protocol version chosen by the platform
* @throws IOException A communication error in the transport layer.
* @throws CommandException A communication in the protocol layer.
* @see <a href="https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#authenticatorLargeBlobs">authenticatorLargeBlobs</a>
*/
public Map<Integer, ?> largeBlobs(
int offset,
@Nullable Integer get,
@Nullable byte[] set,
@Nullable Integer length,
@Nullable byte[] pinUvAuthParam,
@Nullable Integer pinUvAuthProtocol
) throws IOException, CommandException {
return sendCbor(
CMD_LARGE_BLOBS,
args(
get,
set,
offset,
length,
pinUvAuthParam,
pinUvAuthParam != null ? pinUvAuthProtocol : null),
null);
}

/**
* This command is used to configure various authenticator features through the use of its
* subcommands.
Expand Down Expand Up @@ -1050,35 +1084,63 @@ public byte[] getLargeBlobKey() {

/**
* Data class holding the result of getAssertion.
*
* @see <a href="https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#authenticatorgetassertion-response-structure">authenticatorGetAssertion response structure</a>.
*/
public static class AssertionData {
private final static int RESULT_CREDENTIAL = 1;
private final static int RESULT_AUTH_DATA = 2;
private final static int RESULT_SIGNATURE = 3;
private final static int RESULT_USER = 4;
private final static int RESULT_N_CREDS = 5;
private final static int RESULT_USER_SELECTED = 6;
private final static int RESULT_LARGE_BLOB_KEY = 7;

@Nullable
private final Map<String, ?> credential;
private final byte[] authenticatorData;
private final byte[] signature;
@Nullable
private final Map<String, ?> user;
private final byte[] signature;
private final byte[] authenticatorData;
@Nullable
private final Integer numberOfCredentials;
@Nullable
private final Boolean userSelected;
@Nullable
private final byte[] largeBlobKey;
@Nullable
private final Map<String, ?> extensionOutput;

private AssertionData(@Nullable Map<String, ?> credential, @Nullable Map<String, ?> user, byte[] signature, byte[] authenticatorData) {
private AssertionData(
@Nullable Map<String, ?> credential,
byte[] authenticatorData,
byte[] signature,
@Nullable Map<String, ?> user,
@Nullable Integer numberOfCredentials,
@Nullable Boolean userSelected,
@Nullable byte[] largeBlobKey,
@Nullable Map<String, ?> extensionOutput) {
this.credential = credential;
this.user = user;
this.signature = signature;
this.authenticatorData = authenticatorData;
this.numberOfCredentials = numberOfCredentials;
this.userSelected = userSelected;
this.largeBlobKey = largeBlobKey;
this.extensionOutput = extensionOutput;
}

@SuppressWarnings("unchecked")
private static AssertionData fromData(Map<Integer, ?> data) {
return new AssertionData(
(Map<String, ?>) data.get(RESULT_CREDENTIAL),
(Map<String, ?>) data.get(RESULT_USER),
Objects.requireNonNull((byte[]) data.get(RESULT_AUTH_DATA)),
Objects.requireNonNull((byte[]) data.get(RESULT_SIGNATURE)),
Objects.requireNonNull((byte[]) data.get(RESULT_AUTH_DATA))
(Map<String, ?>) data.get(RESULT_USER),
(Integer) data.get(RESULT_N_CREDS),
(Boolean) data.get(RESULT_USER_SELECTED),
(byte[]) data.get(RESULT_LARGE_BLOB_KEY),
null
);
}

Expand Down Expand Up @@ -1121,6 +1183,53 @@ public byte[] getAuthenticatorData() {
return authenticatorData;
}

/**
* Total number of account credentials for the RP. Optional; defaults to one.
* This member is required when more than one credential is found for an RP, and
* the authenticator does not have a display or the UV/UP flags are false.
* <p>
* Omitted when returned for the authenticatorGetNextAssertion method.
*
* @return Total number of account credentials for the RP.
* @see <a href="https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#authenticatorgetassertion-response-structure">authenticatorGetAssertion response structure</a>.
*/
@Nullable
public Integer getNumberOfCredentials() {
return numberOfCredentials;
}

/**
* Indicates that a credential was selected by the user via interaction directly with
* the authenticator, and thus the platform does not need to confirm the credential.
* <p>
* Optional; defaults to false.
* <p>
* MUST NOT be present in response to a request where an allowList was given,
* where numberOfCredentials is greater than one, nor in response to
* an authenticatorGetNextAssertion request.
*
* @return True if the credential was selected by the user via interaction directly with
* the authenticator.
* @see <a href="https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#authenticatorgetassertion-response-structure">authenticatorGetAssertion response structure</a>.
*/
@Nullable
public Boolean getUserSelected() {
return userSelected;
}

/**
* The contents of the associated largeBlobKey if present for the asserted credential,
* and if largeBlobKey was true in the extensions input.
*
* @return The contents of the associated largeBlobKey.
* @see <a href="https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#authenticatorgetassertion-response-structure">authenticatorGetAssertion response structure</a>.
* @see <a href="https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#sctn-largeBlobKey-extension">Large Blob Key Extension</a>.
*/
@Nullable
public byte[] getLargeBlobKey() {
return largeBlobKey;
}

/**
* Helper function for obtaining credential id for AssertionData with help of allowCredentials.
*
Expand Down
Loading

0 comments on commit f626d4d

Please sign in to comment.