Skip to content

Commit

Permalink
self review
Browse files Browse the repository at this point in the history
  • Loading branch information
AdamVe committed Nov 20, 2024
1 parent 59719bc commit abac677
Show file tree
Hide file tree
Showing 21 changed files with 195 additions and 125 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,9 @@ public BasicWebAuthnClient(Ctap2Session session) throws IOException, CommandExce
this(session, getDefaultExtensions(session));
}

public BasicWebAuthnClient(Ctap2Session session, List<Extension> extensions) throws IOException, CommandException {
public BasicWebAuthnClient(Ctap2Session session, @Nullable List<Extension> extensions) throws IOException, CommandException {
this.ctap = session;
this.extensions = extensions;
this.extensions = extensions != null ? extensions : Collections.emptyList();

Ctap2Session.InfoData info = ctap.getInfo();

Expand Down Expand Up @@ -390,7 +390,7 @@ public CredentialManager getCredentialManager(char[] pin)
}
}

static public class WithExtensionResults<T> {
protected static class WithExtensionResults<T> {
final T data;
final ClientExtensionResults clientExtensionResults;

Expand All @@ -399,11 +399,11 @@ static public class WithExtensionResults<T> {
this.clientExtensionResults = clientExtensionResults;
}

public T getData() {
T getData() {
return data;
}

public ClientExtensionResults getClientExtensionResults() {
ClientExtensionResults getClientExtensionResults() {
return clientExtensionResults;
}
}
Expand Down Expand Up @@ -907,15 +907,14 @@ static PublicKeyCredentialDescriptor filterCreds(
* @return new list of Credential descriptors for CBOR serialization.
*/
@Nullable
static List<Map<String, ?>> getCredentialList(@Nullable List<PublicKeyCredentialDescriptor> descriptors) {
static List<Map<String, ?>> getCredentialList(
@Nullable List<PublicKeyCredentialDescriptor> descriptors) {
if (descriptors == null || descriptors.isEmpty()) {
return null;
}
List<Map<String, ?>> list = new ArrayList<>();
for (PublicKeyCredentialDescriptor credential : descriptors) {
list.add(credential.toMap(SerializationType.CBOR));
}
return list;
return descriptors.stream()
.map(d -> d.toMap(SerializationType.CBOR))
.collect(Collectors.toList());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import com.yubico.yubikit.fido.ctap.Ctap2Session;
import com.yubico.yubikit.fido.ctap.PinUvAuthProtocol;
import com.yubico.yubikit.fido.webauthn.Extensions;
import com.yubico.yubikit.fido.webauthn.PublicKeyCredentialCreationOptions;
import com.yubico.yubikit.fido.webauthn.PublicKeyCredentialRequestOptions;

Expand All @@ -40,17 +41,25 @@ public RegistrationProcessor makeCredential(
PublicKeyCredentialCreationOptions options,
PinUvAuthProtocol pinUvAuthProtocol) {

if (isSupported(ctap)) {
String b64Blob = (String) options.getExtensions().get("credBlob");
if (b64Blob != null) {
byte[] blob = fromUrlSafeString(b64Blob);
if (blob.length <= ctap.getCachedInfo().getMaxCredBlobLength()) {
return new RegistrationProcessor(
pinToken -> Collections.singletonMap(name, blob)
);
}
if (!isSupported(ctap)) {
return null;
}

Extensions extensions = options.getExtensions();
if (extensions == null) {
return null;
}

String b64Blob = (String) extensions.get("credBlob");
if (b64Blob != null) {
byte[] blob = fromUrlSafeString(b64Blob);
if (blob.length <= ctap.getCachedInfo().getMaxCredBlobLength()) {
return new RegistrationProcessor(
pinToken -> Collections.singletonMap(name, blob)
);
}
}

return null;
}

Expand All @@ -60,8 +69,13 @@ public AuthenticationProcessor getAssertion(
Ctap2Session ctap,
PublicKeyCredentialRequestOptions options,
PinUvAuthProtocol pinUvAuthProtocol) {

Extensions extensions = options.getExtensions();
if (extensions == null) {
return null;
}
if (isSupported(ctap) &&
Boolean.TRUE.equals(options.getExtensions().get("getCredBlob"))) {
Boolean.TRUE.equals(extensions.get("getCredBlob"))) {
return new AuthenticationProcessor(
(AuthenticationInput) (selected, pinToken) -> Collections.singletonMap(name, true)
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.yubico.yubikit.fido.ctap.Ctap2Session;
import com.yubico.yubikit.fido.ctap.PinUvAuthProtocol;
import com.yubico.yubikit.fido.webauthn.AuthenticatorSelectionCriteria;
import com.yubico.yubikit.fido.webauthn.Extensions;
import com.yubico.yubikit.fido.webauthn.PublicKeyCredentialCreationOptions;
import com.yubico.yubikit.fido.webauthn.ResidentKeyRequirement;

Expand All @@ -39,7 +40,12 @@ public RegistrationProcessor makeCredential(
PublicKeyCredentialCreationOptions options,
PinUvAuthProtocol pinUvAuthProtocol) {

if (options.getExtensions().has(name)) {
Extensions extensions = options.getExtensions();
if (extensions == null) {
return null;
}

if (extensions.has(name)) {
AuthenticatorSelectionCriteria authenticatorSelection = options.getAuthenticatorSelection();
String optionsRk = authenticatorSelection != null
? authenticatorSelection.getResidentKey()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@

public class CredProtectExtension extends Extension {

static final String OPTIONAL = "userVerificationOptional";
static final String OPTIONAL_WITH_LIST = "userVerificationOptionalWithCredentialIDList";
static final String REQUIRED = "userVerificationRequired";
private static final String POLICY = "credentialProtectionPolicy";
private static final String OPTIONAL = "userVerificationOptional";
private static final String OPTIONAL_WITH_LIST = "userVerificationOptionalWithCredentialIDList";
private static final String REQUIRED = "userVerificationRequired";
private static final String ENFORCE = "enforceCredentialProtectionPolicy";

public CredProtectExtension() {
super("credProtect");
Expand All @@ -43,22 +45,25 @@ public RegistrationProcessor makeCredential(
PinUvAuthProtocol pinUvAuthProtocol) {

Extensions extensions = options.getExtensions();
String credentialProtectionPolicy = (String) extensions.get("credentialProtectionPolicy");
if (extensions == null) {
return null;
}

String credentialProtectionPolicy = (String) extensions.get(POLICY);
if (credentialProtectionPolicy == null) {
return null;
}

Integer credProtect = credProtectValue(credentialProtectionPolicy);
Boolean enforce = (Boolean) extensions.get("enforceCredentialProtectionPolicy");
Boolean enforce = (Boolean) extensions.get(ENFORCE);
if (Boolean.TRUE.equals(enforce) &&
!isSupported(ctap) &&
credProtect != null &&
credProtect > 0x01) {
throw new IllegalArgumentException("Authenticator does not support Credential Protection");
throw new IllegalArgumentException("No Credential Protection support");
}
return credProtect != null
? new RegistrationProcessor(
pinToken -> Collections.singletonMap(name, credProtect))
? new RegistrationProcessor(pinToken -> Collections.singletonMap(name, credProtect))
: null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,30 +55,6 @@ public class HmacSecretExtension extends Extension {
private static final org.slf4j.Logger logger = LoggerFactory.getLogger(HmacSecretExtension.class);
private static final int SALT_LEN = 32;

private static class Salts {
byte[] salt1;
byte[] salt2;

Salts(byte[] salt1, @Nullable byte[] salt2) {
this.salt1 = salt1;
this.salt2 = salt2 != null ? salt2 : new byte[0];
}
}

private byte[] prfSalt(byte[] secret) {
try {
return MessageDigest.getInstance("SHA-256").digest(
ByteBuffer
.allocate(13 + secret.length)
.put("WebAuthn PRF".getBytes(StandardCharsets.US_ASCII))
.put((byte) 0x00)
.put(secret)
.array());
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("SHA-256 missing", e);
}
}

public HmacSecretExtension() {
super("hmac-secret");
}
Expand All @@ -89,6 +65,9 @@ public RegistrationProcessor makeCredential(
PublicKeyCredentialCreationOptions options,
PinUvAuthProtocol pinUvAuthProtocol) {
Extensions extensions = options.getExtensions();
if (extensions == null) {
return null;
}
if (Boolean.TRUE.equals(extensions.get("hmacCreateSecret"))) {
return new RegistrationProcessor(
pinToken -> Collections.singletonMap(name, true),
Expand Down Expand Up @@ -122,15 +101,17 @@ public AuthenticationProcessor getAssertion(
}

final Inputs inputs = Inputs.fromExtensions(options.getExtensions());
if (inputs == null) {
return null;
}
final AuthenticationInput prepareInput = (selected, pinToken) -> {
Salts salts;
if (inputs.prf != null) {
Map<String, Object> secrets = inputs.prf.eval;
Map<String, Object> evalByCredential = inputs.prf.evalByCredential;

if (evalByCredential != null) {
List<PublicKeyCredentialDescriptor> allowCredentials =
options.getAllowCredentials();
List<PublicKeyCredentialDescriptor> allowCredentials = options.getAllowCredentials();

if (allowCredentials.isEmpty()) {
throw new IllegalArgumentException("evalByCredential needs allow list");
Expand Down Expand Up @@ -275,6 +256,31 @@ Map<String, Object> registrationOutput(AttestationObject attestationObject, bool
: Collections.singletonMap("hmacCreateSecret", enabled);
}


private static class Salts {
byte[] salt1;
byte[] salt2;

Salts(byte[] salt1, @Nullable byte[] salt2) {
this.salt1 = salt1;
this.salt2 = salt2 != null ? salt2 : new byte[0];
}
}

private byte[] prfSalt(byte[] secret) {
try {
return MessageDigest.getInstance("SHA-256").digest(
ByteBuffer
.allocate(13 + secret.length)
.put("WebAuthn PRF".getBytes(StandardCharsets.US_ASCII))
.put((byte) 0x00)
.put(secret)
.array());
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("SHA-256 missing", e);
}
}

private static class PrfInputs {
@Nullable final Map<String, Object> eval;
@Nullable final Map<String, Object> evalByCredential;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ public class LargeBlobExtension extends Extension {
private static final String ACTION_WRITE = "write";
private static final String WRITTEN = "written";
private static final String SUPPORT = "support";
private static final String SUPPORTED = "supported";
private static final String REQUIRED = "required";
private static final String BLOB = "blob";
private static final org.slf4j.Logger logger = LoggerFactory.getLogger(LargeBlobExtension.class);

public LargeBlobExtension() {
Expand All @@ -53,8 +56,8 @@ public LargeBlobExtension() {

@Override
protected boolean isSupported(Ctap2Session ctap) {
return super.isSupported(ctap) && ctap.getCachedInfo().getOptions()
.containsKey(LARGE_BLOBS);
return super.isSupported(ctap) &&
ctap.getCachedInfo().getOptions().containsKey(LARGE_BLOBS);
}

@Nullable
Expand All @@ -68,15 +71,14 @@ public RegistrationProcessor makeCredential(
if (inputs.read != null || inputs.write != null) {
throw new IllegalArgumentException("Invalid set of parameters");
}
if ("required".equals(inputs.support) && !isSupported(ctap)) {
throw new IllegalArgumentException("Authenticator does not support large" +
" blob storage");
if (REQUIRED.equals(inputs.support) && !isSupported(ctap)) {
throw new IllegalArgumentException("Authenticator does not support large blob storage");
}
return new RegistrationProcessor(
pinToken -> Collections.singletonMap(LARGE_BLOB, true),
(attestationObject, pinToken) ->
Collections.singletonMap(LARGE_BLOB,
Collections.singletonMap("supported",
Collections.singletonMap(SUPPORTED,
attestationObject.getLargeBlobKey() != null))
);
}
Expand Down Expand Up @@ -124,7 +126,7 @@ Map<String, Object> read(
LargeBlobs largeBlobs = new LargeBlobs(ctap);
byte[] blob = largeBlobs.getBlob(largeBlobKey);
return Collections.singletonMap(LARGE_BLOB, blob != null
? Collections.singletonMap("blob", toUrlSafeString(blob))
? Collections.singletonMap(BLOB, toUrlSafeString(blob))
: Collections.emptyMap());
} catch (IOException | CommandException e) {
Logger.error(logger, "LargeBlob processing failed: ", e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,11 @@ public class LargeBlobs {
this(session, null, null);
}

LargeBlobs(
Ctap2Session session,
@Nullable
PinUvAuthProtocol pinUvAuthProtocol,
@Nullable
byte[] pinUvAuthToken) {
LargeBlobs(Ctap2Session session,
@Nullable
PinUvAuthProtocol pinUvAuthProtocol,
@Nullable
byte[] pinUvAuthToken) {

final Ctap2Session.InfoData info = session.getCachedInfo();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import com.yubico.yubikit.fido.ctap.Ctap2Session;
import com.yubico.yubikit.fido.ctap.PinUvAuthProtocol;
import com.yubico.yubikit.fido.webauthn.Extensions;
import com.yubico.yubikit.fido.webauthn.PublicKeyCredentialCreationOptions;

import java.util.Collections;
Expand All @@ -32,8 +33,8 @@ public MinPinLengthExtension() {

@Override
protected boolean isSupported(Ctap2Session ctap) {
return super.isSupported(ctap) && ctap.getCachedInfo().getOptions()
.containsKey("setMinPINLength");
return super.isSupported(ctap) &&
ctap.getCachedInfo().getOptions().containsKey("setMinPINLength");
}

@Nullable
Expand All @@ -46,7 +47,13 @@ public RegistrationProcessor makeCredential(
if (!isSupported(ctap)) {
return null;
}
Boolean input = (Boolean) options.getExtensions().get(name);

Extensions extensions = options.getExtensions();
if (extensions == null) {
return null;
}

Boolean input = (Boolean) extensions.get(name);
if (input == null) {
return null;
}
Expand Down
Loading

0 comments on commit abac677

Please sign in to comment.