Skip to content

Commit 916c619

Browse files
committed
refactor credential manager support
1 parent 0638153 commit 916c619

File tree

5 files changed

+97
-71
lines changed

5 files changed

+97
-71
lines changed

fido/src/main/java/com/yubico/yubikit/fido/ctap/CredentialManagement.java

+2-14
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ public class CredentialManagement {
6262
private final Ctap2Session ctap;
6363
private final PinUvAuthProtocol pinUvAuth;
6464
private final byte[] pinUvToken;
65-
private final byte cmd;
6665

6766
/**
6867
* Construct a new CredentialManagement object.
@@ -76,20 +75,10 @@ public CredentialManagement(
7675
PinUvAuthProtocol pinUvAuth,
7776
byte[] pinUvToken
7877
) {
79-
this.ctap = ctap;
80-
Ctap2Session.InfoData ctapInfo = ctap.getCachedInfo();
81-
Map<String, ?> options = ctapInfo.getOptions();
82-
83-
if (Boolean.TRUE.equals(options.get("credMgmt"))) {
84-
cmd = Ctap2Session.CMD_CREDENTIAL_MANAGEMENT;
85-
} else if (ctapInfo.getVersions().contains("FIDO_2_1_PRE")
86-
&&
87-
Boolean.TRUE.equals(options.get("credentialMgmtPreview"))) {
88-
cmd = Ctap2Session.CMD_CREDENTIAL_MANAGEMENT_PRE;
89-
} else {
78+
if (!ctap.isCredentialManagerSupported()) {
9079
throw new IllegalStateException("Credential manager not supported");
9180
}
92-
81+
this.ctap = ctap;
9382
this.pinUvAuth = pinUvAuth;
9483
this.pinUvToken = pinUvToken;
9584
}
@@ -110,7 +99,6 @@ public CredentialManagement(
11099
}
111100

112101
return ctap.credentialManagement(
113-
cmd,
114102
subCommand,
115103
subCommandParams,
116104
pinUvAuth.getVersion(),

fido/src/main/java/com/yubico/yubikit/fido/ctap/Ctap2Session.java

+90-40
Original file line numberDiff line numberDiff line change
@@ -62,23 +62,35 @@ public class Ctap2Session extends ApplicationSession<Ctap2Session> {
6262

6363
private static final byte NFCCTAP_MSG = 0x10;
6464

65-
public static final byte CMD_MAKE_CREDENTIAL = 0x01;
66-
public static final byte CMD_GET_ASSERTION = 0x02;
67-
public static final byte CMD_GET_INFO = 0x04;
68-
public static final byte CMD_CLIENT_PIN = 0x06;
69-
public static final byte CMD_RESET = 0x07;
70-
public static final byte CMD_GET_NEXT_ASSERTION = 0x08;
71-
public static final byte CMD_BIO_ENROLLMENT = 0x09;
72-
public static final byte CMD_CREDENTIAL_MANAGEMENT = 0x0A;
73-
public static final byte CMD_SELECTION = 0x0B;
74-
public static final byte CMD_LARGE_BLOBS = 0x0C;
75-
public static final byte CMD_CONFIG = 0x0D;
76-
public static final byte CMD_BIO_ENROLLMENT_PRE = 0x40;
77-
public static final byte CMD_CREDENTIAL_MANAGEMENT_PRE = 0x41;
65+
private static final byte CMD_MAKE_CREDENTIAL = 0x01;
66+
private static final byte CMD_GET_ASSERTION = 0x02;
67+
private static final byte CMD_GET_INFO = 0x04;
68+
private static final byte CMD_CLIENT_PIN = 0x06;
69+
private static final byte CMD_RESET = 0x07;
70+
private static final byte CMD_GET_NEXT_ASSERTION = 0x08;
71+
private static final byte CMD_BIO_ENROLLMENT = 0x09;
72+
private static final byte CMD_CREDENTIAL_MANAGEMENT = 0x0A;
73+
private static final byte CMD_SELECTION = 0x0B;
74+
private static final byte CMD_LARGE_BLOBS = 0x0C;
75+
private static final byte CMD_CONFIG = 0x0D;
76+
private static final byte CMD_BIO_ENROLLMENT_PRE = 0x40;
77+
private static final byte CMD_CREDENTIAL_MANAGEMENT_PRE = 0x41;
78+
79+
private enum CredentialManagerSupport {
80+
NONE((byte) 0x00),
81+
PREVIEW(CMD_CREDENTIAL_MANAGEMENT_PRE),
82+
FULL(CMD_CREDENTIAL_MANAGEMENT);
83+
84+
final byte command;
85+
CredentialManagerSupport(byte command) {
86+
this.command = command;
87+
}
88+
}
7889

7990
private final Version version;
8091
private final Backend<?> backend;
8192
private final InfoData info;
93+
private final CredentialManagerSupport credentialManagerSupport;
8294

8395
private static final org.slf4j.Logger logger = LoggerFactory.getLogger(Ctap2Session.class);
8496

@@ -99,36 +111,47 @@ public static void create(YubiKeyDevice device, Callback<Result<Ctap2Session, Ex
99111
}
100112

101113
public Ctap2Session(SmartCardConnection connection)
102-
throws IOException, ApplicationNotAvailableException, CommandException {
103-
SmartCardProtocol protocol = new SmartCardProtocol(connection);
114+
throws IOException, CommandException {
115+
this(new Version(0, 0, 0), getSmartCardBackend(connection));
116+
Logger.debug(logger, "Ctap2Session session initialized for connection={}",
117+
connection.getClass().getSimpleName());
118+
}
119+
120+
public Ctap2Session(FidoConnection connection) throws IOException, CommandException {
121+
this(new FidoProtocol(connection));
122+
Logger.debug(logger, "Ctap2Session session initialized for connection={}, version={}",
123+
connection.getClass().getSimpleName(),
124+
version);
125+
}
126+
127+
private Ctap2Session(Version version, Backend<?> backend)
128+
throws IOException, CommandException {
129+
this.version = version;
130+
this.backend = backend;
131+
this.info = getInfo();
132+
this.credentialManagerSupport = getCredentialManagementSupport(info);
133+
}
134+
135+
private static Backend<SmartCardProtocol> getSmartCardBackend(SmartCardConnection connection)
136+
throws IOException, ApplicationNotAvailableException {
137+
final SmartCardProtocol protocol = new SmartCardProtocol(connection);
104138
protocol.select(AppId.FIDO);
105-
// it is not possible to get the applet version over NFC/CCID
106-
version = new Version(0, 0, 0);
107-
backend = new Backend<SmartCardProtocol>(protocol) {
108-
@Override
109-
byte[] sendCbor(byte[] data, @Nullable CommandState state) throws IOException, CommandException {
139+
return new Backend<SmartCardProtocol>(protocol) {
140+
byte[] sendCbor(byte[] data, @Nullable CommandState state)
141+
throws IOException, CommandException {
110142
//Cancellation is not implemented for NFC, and most likely not needed.
111143
return delegate.sendAndReceive(new Apdu(0x80, NFCCTAP_MSG, 0x00, 0x00, data));
112144
}
113145
};
114-
info = getInfo();
115-
Logger.debug(logger, "Ctap2Session session initialized for connection={}",
116-
connection.getClass().getSimpleName());
117146
}
118147

119-
public Ctap2Session(FidoConnection connection) throws IOException, CommandException {
120-
FidoProtocol protocol = new FidoProtocol(connection);
121-
version = protocol.getVersion();
122-
backend = new Backend<FidoProtocol>(protocol) {
148+
private Ctap2Session(FidoProtocol protocol) throws IOException, CommandException {
149+
this(protocol.getVersion(), new Backend<FidoProtocol>(protocol) {
123150
@Override
124151
byte[] sendCbor(byte[] data, @Nullable CommandState state) throws IOException {
125152
return delegate.sendAndReceive(FidoProtocol.CTAPHID_CBOR, data, state);
126153
}
127-
};
128-
info = getInfo();
129-
Logger.debug(logger, "Ctap2Session session initialized for connection={}, version={}",
130-
connection.getClass().getSimpleName(),
131-
version);
154+
});
132155
}
133156

134157
/**
@@ -172,6 +195,29 @@ byte[] sendCbor(byte[] data, @Nullable CommandState state) throws IOException {
172195
}
173196
}
174197

198+
private CredentialManagerSupport getCredentialManagementSupport(InfoData info) {
199+
final Map<String, ?> options = info.getOptions();
200+
if (Boolean.TRUE.equals(options.get("credMgmt"))) {
201+
return CredentialManagerSupport.FULL;
202+
} else if (info.getVersions().contains("FIDO_2_1_PRE") &&
203+
Boolean.TRUE.equals(options.get("credentialMgmtPreview"))) {
204+
return CredentialManagerSupport.PREVIEW;
205+
}
206+
207+
return CredentialManagerSupport.NONE;
208+
}
209+
210+
/**
211+
* Get information about authenticator support of credential manager commands.
212+
* @return true if the authenticator supports credential manager or credential manager preview
213+
* commands are supported.
214+
* @see <a href="https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#ref-for-getinfo-credmgmt">credMgmt option</a>
215+
* @see <a href="https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#ref-for-getinfo-credentialmgmtpreview">credentialMgmtPreview option</a>
216+
*/
217+
public boolean isCredentialManagerSupported() {
218+
return credentialManagerSupport != CredentialManagerSupport.NONE;
219+
}
220+
175221
/**
176222
* This method is invoked by the host to request generation of a new credential in the
177223
* authenticator.
@@ -368,7 +414,6 @@ public void reset(@Nullable CommandState state) throws IOException, CommandExcep
368414
* This command is used by the platform to manage discoverable credentials on the
369415
* authenticator.
370416
*
371-
* @param command either CMD_CREDENTIAL_MANAGEMENT or CMD_CREDENTIAL_MANAGEMENT_PRE
372417
* @param subCommand the subCommand currently being requested
373418
* @param subCommandParams a map of subCommands parameters
374419
* @param pinUvAuthProtocol PIN/UV protocol version chosen by the platform
@@ -378,17 +423,22 @@ public void reset(@Nullable CommandState state) throws IOException, CommandExcep
378423
* @see <a href="https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#authenticatorCredentialManagement">authenticatorCredentialManagement</a>
379424
*/
380425
Map<Integer, ?> credentialManagement(
381-
byte command,
382426
int subCommand,
383427
@Nullable Map<?, ?> subCommandParams,
384428
@Nullable Integer pinUvAuthProtocol,
385429
@Nullable byte[] pinUvAuthParam
386430
) throws IOException, CommandException {
387-
return sendCbor(command, args(
388-
subCommand,
389-
subCommandParams,
390-
pinUvAuthProtocol,
391-
pinUvAuthParam), null);
431+
if (!isCredentialManagerSupported()) {
432+
throw new IllegalStateException("Authenticator does not support credential manager");
433+
}
434+
return sendCbor(
435+
credentialManagerSupport.command,
436+
args(
437+
subCommand,
438+
subCommandParams,
439+
pinUvAuthProtocol,
440+
pinUvAuthParam),
441+
null);
392442
}
393443

394444
/**
@@ -669,7 +719,7 @@ public List<Integer> getPinUvAuthProtocols() {
669719
}
670720

671721
/**
672-
* Get the aximum number of credentials supported in credentialID list
722+
* Get the maximum number of credentials supported in credentialID list
673723
* at a time by the authenticator.
674724
*
675725
* @return maximum number of credentials

testing-android/src/androidTest/java/com/yubico/yubikit/testing/fido/BasicWebAuthnClientInstrumentedTests.java

+2-4
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,8 @@ public void testClientPinManagement() throws Throwable {
101101
public void testClientCredentialManagement() throws Throwable {
102102
withCtap2Session(
103103
"Credential management or PIN/UV Auth protocol not supported",
104-
(device, session) ->
105-
Ctap2CredentialManagementInstrumentedTests
106-
.supportsCredentialManager(session) &&
107-
supportsPinUvAuthProtocol(session, pinUvAuthProtocol),
104+
(device, session) -> session.isCredentialManagerSupported() &&
105+
supportsPinUvAuthProtocol(session, pinUvAuthProtocol),
108106
BasicWebAuthnClientTests::testClientCredentialManagement,
109107
pinUvAuthProtocol);
110108
}

testing-android/src/androidTest/java/com/yubico/yubikit/testing/fido/Ctap2CredentialManagementInstrumentedTests.java

+2-12
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020

2121
import androidx.test.filters.LargeTest;
2222

23-
import com.yubico.yubikit.fido.ctap.Ctap2Session;
2423
import com.yubico.yubikit.fido.ctap.PinUvAuthProtocol;
2524
import com.yubico.yubikit.fido.ctap.PinUvAuthProtocolV1;
2625
import com.yubico.yubikit.fido.ctap.PinUvAuthProtocolV2;
@@ -33,16 +32,9 @@
3332

3433
import java.util.Arrays;
3534
import java.util.Collection;
36-
import java.util.Map;
3735

3836
@RunWith(Enclosed.class)
3937
public class Ctap2CredentialManagementInstrumentedTests {
40-
41-
public static boolean supportsCredentialManager(Ctap2Session session) {
42-
final Map<String, ?> options = session.getCachedInfo().getOptions();
43-
return options.containsKey("credMgmt") || options.containsKey("credentialMgmtPreview");
44-
}
45-
4638
@LargeTest
4739
@RunWith(Parameterized.class)
4840
public static class ParametrizedCtap2CredentialManagementTests extends FidoInstrumentedTests {
@@ -61,8 +53,7 @@ public static Collection<PinUvAuthProtocol> data() {
6153
public void testReadMetadata() throws Throwable {
6254
withCtap2Session(
6355
"Credential management or PIN/UV Auth protocol not supported",
64-
(device, session) -> Ctap2CredentialManagementInstrumentedTests
65-
.supportsCredentialManager(session) &&
56+
(device, session) -> session.isCredentialManagerSupported() &&
6657
supportsPinUvAuthProtocol(session, pinUvAuthProtocol),
6758
Ctap2CredentialManagementTests::testReadMetadata,
6859
pinUvAuthProtocol);
@@ -72,8 +63,7 @@ public void testReadMetadata() throws Throwable {
7263
public void testManagement() throws Throwable {
7364
withCtap2Session(
7465
"Credential management or PIN/UV Auth protocol not supported",
75-
(device, session) -> Ctap2CredentialManagementInstrumentedTests
76-
.supportsCredentialManager(session) &&
66+
(device, session) -> session.isCredentialManagerSupported() &&
7767
supportsPinUvAuthProtocol(session, pinUvAuthProtocol),
7868
Ctap2CredentialManagementTests::testManagement,
7969
pinUvAuthProtocol);

testing/src/main/java/com/yubico/yubikit/testing/fido/Ctap2CredentialManagementTests.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ public static void testManagement(Ctap2Session session, Object... args) throws T
101101
null,
102102
options,
103103
pinAuth,
104-
1,
104+
pinUvAuthProtocol.getVersion(),
105105
null,
106106
null
107107
);

0 commit comments

Comments
 (0)