Skip to content

Commit

Permalink
Merge PR #107.
Browse files Browse the repository at this point in the history
  • Loading branch information
AdamVe committed Oct 31, 2023
2 parents 6d2c4f9 + 2666e4e commit 461171a
Show file tree
Hide file tree
Showing 9 changed files with 450 additions and 64 deletions.
24 changes: 12 additions & 12 deletions core/src/main/java/com/yubico/yubikit/core/fido/FidoProtocol.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,18 @@

public class FidoProtocol implements Closeable {

public static final byte TYPE_INIT = (byte) 0x80;

public static final byte CTAPHID_PING = TYPE_INIT | 0x01;
public static final byte CTAPHID_MSG = TYPE_INIT | 0x03;
public static final byte CTAPHID_LOCK = TYPE_INIT | 0x04;
public static final byte CTAPHID_INIT = TYPE_INIT | 0x06;
public static final byte CTAPHID_WINK = TYPE_INIT | 0x08;
public static final byte CTAPHID_CBOR = TYPE_INIT | 0x10;
public static final byte CTAPHID_CANCEL = TYPE_INIT | 0x11;

public static final byte CTAPHID_ERROR = TYPE_INIT | 0x3f;
public static final byte CTAPHID_KEEPALIVE = TYPE_INIT | 0x3b;
private static final byte TYPE_INIT = (byte) 0x80;

private static final byte CTAPHID_PING = TYPE_INIT | 0x01;
private static final byte CTAPHID_MSG = TYPE_INIT | 0x03;
private static final byte CTAPHID_LOCK = TYPE_INIT | 0x04;
private static final byte CTAPHID_INIT = TYPE_INIT | 0x06;
private static final byte CTAPHID_WINK = TYPE_INIT | 0x08;
private static final byte CTAPHID_CBOR = TYPE_INIT | 0x10;
private static final byte CTAPHID_CANCEL = TYPE_INIT | 0x11;

private static final byte CTAPHID_ERROR = TYPE_INIT | 0x3f;
private static final byte CTAPHID_KEEPALIVE = TYPE_INIT | 0x3b;

private final CommandState defaultState = new CommandState();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@
import com.yubico.yubikit.fido.ctap.CredentialManagement;
import com.yubico.yubikit.fido.ctap.Ctap2Session;
import com.yubico.yubikit.fido.ctap.PinUvAuthDummyProtocol;
import com.yubico.yubikit.fido.ctap.PinUvAuthProtocol;
import com.yubico.yubikit.fido.ctap.PinUvAuthProtocolV1;
import com.yubico.yubikit.fido.ctap.PinUvAuthProtocolV2;
import com.yubico.yubikit.fido.webauthn.AttestationConveyancePreference;
import com.yubico.yubikit.fido.webauthn.AttestationObject;
import com.yubico.yubikit.fido.webauthn.AuthenticatorAttestationResponse;
import com.yubico.yubikit.fido.webauthn.AuthenticatorSelectionCriteria;
Expand Down Expand Up @@ -75,9 +77,9 @@ public class BasicWebAuthnClient implements Closeable {
private static final String OPTION_RESIDENT_KEY = "rk";
private static final String OPTION_EP = "ep";

private final Ctap2Session ctap;
private final UserAgentConfiguration userAgentConfiguration = new UserAgentConfiguration();

private final List<String> transports;
private final Ctap2Session ctap;

private final boolean pinSupported;
private final boolean uvSupported;
Expand All @@ -87,51 +89,52 @@ public class BasicWebAuthnClient implements Closeable {
private boolean pinConfigured;
private final boolean uvConfigured;

final private boolean epSupported;
final private boolean enterpriseAttestationSupported;

private static final org.slf4j.Logger logger = LoggerFactory.getLogger(BasicWebAuthnClient.class);

public static class UserAgentConfiguration {
private List<String> epSupportedRpIds = new ArrayList<>();

public void setEpSupportedRpIds(List<String> epSupportedRpIds) {
this.epSupportedRpIds = epSupportedRpIds;
}

boolean supportsEpForRpId(@Nullable String rpId) {
return epSupportedRpIds.contains(rpId);
}
}

public BasicWebAuthnClient(Ctap2Session session) throws IOException, CommandException {
this.ctap = session;
Ctap2Session.InfoData info = ctap.getInfo();

transports = info.getTransports();

Map<String, ?> options = info.getOptions();

Boolean clientPin = (Boolean) options.get(OPTION_CLIENT_PIN);
pinSupported = clientPin != null;
final Boolean optionClientPin = (Boolean) options.get(OPTION_CLIENT_PIN);
pinSupported = optionClientPin != null;

final List<Integer> pinUvAuthProtocols = info.getPinUvAuthProtocols();
if (pinUvAuthProtocols.size() > 0) {
// List of supported PIN/UV auth protocols in order of decreasing authenticator
// preference. MUST NOT contain duplicate values nor be empty if present.
int preferredPinUvAuthProtocol = pinUvAuthProtocols.get(0);
this.clientPin =
new ClientPin(ctap, getPreferredPinUvAuthProtocol(info.getPinUvAuthProtocols()));

if (pinSupported && preferredPinUvAuthProtocol == PinUvAuthProtocolV2.VERSION) {
this.clientPin = new ClientPin(ctap, new PinUvAuthProtocolV2());
} else if (pinSupported && preferredPinUvAuthProtocol == PinUvAuthProtocolV1.VERSION) {
this.clientPin = new ClientPin(ctap, new PinUvAuthProtocolV1());
} else {
this.clientPin = new ClientPin(ctap, new PinUvAuthDummyProtocol());
}
} else {
this.clientPin = new ClientPin(ctap, new PinUvAuthDummyProtocol());
}
pinConfigured = pinSupported && clientPin;
pinConfigured = pinSupported && Boolean.TRUE.equals(optionClientPin);

Boolean uv = (Boolean) options.get(OPTION_USER_VERIFICATION);
uvSupported = uv != null;
uvConfigured = uvSupported && uv;

epSupported = Boolean.TRUE.equals(options.get(OPTION_EP));
enterpriseAttestationSupported = Boolean.TRUE.equals(options.get(OPTION_EP));
}

@Override
public void close() throws IOException {
ctap.close();
}

public UserAgentConfiguration getUserAgentConfiguration() {
return userAgentConfiguration;
}

/**
* Create a new WebAuthn credential.
* <p>
Expand Down Expand Up @@ -163,15 +166,15 @@ public PublicKeyCredential makeCredential(
options,
effectiveDomain,
pin,
epSupported ? enterpriseAttestation : null,
enterpriseAttestation,
state
);

final AttestationObject attestationObject = AttestationObject.fromCredential(credential);

AuthenticatorAttestationResponse response = new AuthenticatorAttestationResponse(
clientDataJson,
getTransports(),
ctap.getCachedInfo().getTransports(),
attestationObject
);

Expand All @@ -182,7 +185,7 @@ public PublicKeyCredential makeCredential(
);
} catch (CtapException e) {
if (e.getCtapError() == CtapException.ERR_PIN_INVALID) {
throw new PinInvalidClientError(e, clientPin.getPinRetries().first);
throw new PinInvalidClientError(e, clientPin.getPinRetries().getCount());
}
throw ClientError.wrapCtapException(e);
}
Expand Down Expand Up @@ -238,7 +241,7 @@ public PublicKeyCredential getAssertion(

} catch (CtapException e) {
if (e.getCtapError() == CtapException.ERR_PIN_INVALID) {
throw new PinInvalidClientError(e, clientPin.getPinRetries().first);
throw new PinInvalidClientError(e, clientPin.getPinRetries().getCount());
}
throw ClientError.wrapCtapException(e);
}
Expand Down Expand Up @@ -269,8 +272,8 @@ public boolean isPinConfigured() {
* attestation is enabled.
* @see <a href="https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#sctn-feature-descriptions-enterp-attstn">Enterprise Attestation</a>
*/
public boolean isEpSupported() {
return epSupported;
public boolean isEnterpriseAttestationSupported() {
return enterpriseAttestationSupported;
}

/**
Expand Down Expand Up @@ -445,6 +448,15 @@ protected Ctap2Session.CredentialData ctapMakeCredential(
}
}

@Nullable Integer validatedEnterpriseAttestation = null;
if (isEnterpriseAttestationSupported() &&
AttestationConveyancePreference.ENTERPRISE.equals(options.getAttestation()) &&
userAgentConfiguration.supportsEpForRpId(rpId) &&
enterpriseAttestation != null &&
(enterpriseAttestation == 1 || enterpriseAttestation == 2)) {
validatedEnterpriseAttestation = enterpriseAttestation;
}

return ctap.makeCredential(
clientDataHash,
rp,
Expand All @@ -455,7 +467,7 @@ protected Ctap2Session.CredentialData ctapMakeCredential(
ctapOptions.isEmpty() ? null : ctapOptions,
pinUvAuthParam,
pinUvAuthProtocol,
enterpriseAttestation,
validatedEnterpriseAttestation,
state
);
} finally {
Expand Down Expand Up @@ -537,7 +549,7 @@ protected List<Ctap2Session.AssertionData> ctapGetAssertions(
);
} catch (CtapException e) {
if (e.getCtapError() == CtapException.ERR_PIN_INVALID) {
throw new PinInvalidClientError(e, clientPin.getPinRetries().first);
throw new PinInvalidClientError(e, clientPin.getPinRetries().getCount());
}
throw ClientError.wrapCtapException(e);
} finally {
Expand All @@ -547,16 +559,6 @@ protected List<Ctap2Session.AssertionData> ctapGetAssertions(
}
}

/**
* Returns list of transports the authenticator is believed to support. This can be empty if
* the information is not available.
*
* @return list of transports
*/
protected List<String> getTransports() {
return transports;
}

/*
* Calculates what the CTAP "uv" option should be based on the configuration of the authenticator,
* the UserVerification parameter to the request, and whether or not a PIN was provided.
Expand Down Expand Up @@ -601,6 +603,27 @@ private boolean getCtapUv(String userVerification, boolean pinProvided) throws C
}
}

/**
* Calculates the preferred pinUvAuth protocol for authenticator provided list.
* Returns PinUvAuthDummyProtocol if the authenticator does not support any of the SDK
* supported protocols.
*/
private PinUvAuthProtocol getPreferredPinUvAuthProtocol(List<Integer> pinUvAuthProtocols) {
if (pinSupported) {
for (int protocol : pinUvAuthProtocols) {
if (protocol == PinUvAuthProtocolV1.VERSION) {
return new PinUvAuthProtocolV1();
}

if (protocol == PinUvAuthProtocolV2.VERSION) {
return new PinUvAuthProtocolV2();
}
}
}

return new PinUvAuthDummyProtocol();
}

private static boolean isPublicKeyCredentialTypeSupported(String type) {
return PublicKeyCredentialType.PUBLIC_KEY.equals(type);
}
Expand Down
26 changes: 23 additions & 3 deletions fido/src/main/java/com/yubico/yubikit/fido/ctap/ClientPin.java
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,26 @@ public class ClientPin {

private static final org.slf4j.Logger logger = LoggerFactory.getLogger(ClientPin.class);

public static class PinRetries {
final int count;
@Nullable
final Boolean powerCycleState;

PinRetries(int count, @Nullable Boolean powerCycleState) {
this.count = count;
this.powerCycleState = powerCycleState;
}

public int getCount() {
return count;
}

@Nullable
public Boolean getPowerCycleState() {
return powerCycleState;
}
}

/**
* Construct a new ClientPin object using a specified PIN/UV Auth protocol.
*
Expand Down Expand Up @@ -236,7 +256,7 @@ public byte[] getUvToken(@Nullable Integer permissions,
* @throws IOException A communication error in the transport layer.
* @throws CommandException A communication in the protocol layer.
*/
public Pair<Integer, Integer> getPinRetries() throws IOException, CommandException {
public PinRetries getPinRetries() throws IOException, CommandException {
Logger.debug(logger, "Getting PIN retries");
Map<Integer, ?> result = ctap.clientPin(
pinUvAuth.getVersion(),
Expand All @@ -250,9 +270,9 @@ public Pair<Integer, Integer> getPinRetries() throws IOException, CommandExcepti
null
);

return new Pair<>(
return new PinRetries(
Objects.requireNonNull((Integer) result.get(RESULT_RETRIES)),
(Integer) result.get(RESULT_POWER_CYCLE_STATE));
(Boolean) result.get(RESULT_POWER_CYCLE_STATE));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,8 @@ private Ctap2Session(FidoProtocol protocol) throws IOException, CommandException
this(protocol.getVersion(), new Backend<FidoProtocol>(protocol) {
@Override
byte[] sendCbor(byte[] data, @Nullable CommandState state) throws IOException {
return delegate.sendAndReceive(FidoProtocol.CTAPHID_CBOR, data, state);
byte CTAPHID_CBOR = (byte) 0x80 | 0x10;
return delegate.sendAndReceive(CTAPHID_CBOR, data, state);
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
* @see <a href="https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#authnrClientPin-puaprot-abstract-dfn">PIN/UV Auth Protocol Abstract Definition</a>.
*/
public class PinUvAuthDummyProtocol implements PinUvAuthProtocol {

@Override
public int getVersion() {
throw new UnsupportedPinUvAuthProtocolError();
Expand Down
Loading

0 comments on commit 461171a

Please sign in to comment.