Skip to content

Commit 1bb9b7a

Browse files
authored
Restrict COSE algorithms to allowed list during attestation registration (#78)
1 parent 4b02a0f commit 1bb9b7a

14 files changed

Lines changed: 583 additions & 111 deletions

File tree

build.gradle.kts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,10 @@ subprojects {
4444
dependencies {
4545
"compileOnly"("org.projectlombok:lombok:$lombokVersion")
4646
"annotationProcessor"("org.projectlombok:lombok:$lombokVersion")
47+
48+
"testImplementation"("org.mockito:mockito-core:5.23.0")
49+
// mockito-core depends on byte-buddy. Override spring boot's dependency version in test.
50+
"testImplementation"("net.bytebuddy:byte-buddy:1.17.7")
51+
"testImplementation"("net.bytebuddy:byte-buddy-agent:1.17.7")
4752
}
4853
}

fido2-core/src/main/java/com/linecorp/line/auth/fido/fido2/server/error/InternalErrorCode.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2024 LY Corporation
2+
* Copyright 2024-2026 LY Corporation
33
*
44
* LY Corporation licenses this file to you under the Apache License,
55
* version 2.0 (the "License"); you may not use this file except in compliance
@@ -107,10 +107,9 @@ public enum InternalErrorCode {
107107
ASSERTION_SIGNATURE_VERIFICATION_FAIL(56),
108108
METADATA_JSON_PARSING_FAIL(57),
109109
INVALID_ORIGIN(80),
110-
111-
CROSS_ORIGIN_NOT_ALLOWED(82),
112-
113110
ECDAA_ALGORITHM_NOT_SUPPORTED(81),
111+
CROSS_ORIGIN_NOT_ALLOWED(82),
112+
NOT_ALLOWED_COSE_ALGORITHM(83),
114113

115114
//U2F
116115
APPID_NOT_FOUND(100),
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright 2026 LY Corporation
3+
*
4+
* LY Corporation licenses this file to you under the Apache License,
5+
* version 2.0 (the "License"); you may not use this file except in compliance
6+
* with the License. You may obtain a copy of the License at:
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations
14+
* under the License.
15+
*/
16+
17+
package com.linecorp.line.auth.fido.fido2.server.property;
18+
19+
import java.util.List;
20+
21+
import org.springframework.boot.context.properties.ConfigurationProperties;
22+
import org.springframework.boot.context.properties.ConstructorBinding;
23+
import org.springframework.boot.context.properties.NestedConfigurationProperty;
24+
25+
import lombok.AllArgsConstructor;
26+
import lombok.Value;
27+
28+
@ConfigurationProperties("fido.fido2")
29+
@AllArgsConstructor
30+
@ConstructorBinding
31+
@Value
32+
public class Fido2Properties {
33+
long sessionTtlMillis;
34+
boolean acceptUnregisteredAuthenticators;
35+
boolean denyCrossOrigin;
36+
@NestedConfigurationProperty
37+
RegistrationProperties registration;
38+
39+
@AllArgsConstructor
40+
@Value
41+
public static class RegistrationProperties {
42+
List<String> allowedAlgorithms;
43+
}
44+
}

fido2-core/src/main/java/com/linecorp/line/auth/fido/fido2/server/service/AttestationService.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2024 LY Corporation
2+
* Copyright 2024-2026 LY Corporation
33
*
44
* LY Corporation licenses this file to you under the Apache License,
55
* version 2.0 (the "License"); you may not use this file except in compliance
@@ -16,15 +16,18 @@
1616

1717
package com.linecorp.line.auth.fido.fido2.server.service;
1818

19+
import java.util.List;
20+
1921
import com.linecorp.line.auth.fido.fido2.common.AuthenticatorSelectionCriteria;
2022
import com.linecorp.line.auth.fido.fido2.common.CredentialMediationRequirement;
23+
import com.linecorp.line.auth.fido.fido2.common.PublicKeyCredentialParameters;
2124
import com.linecorp.line.auth.fido.fido2.common.server.ServerAuthenticatorAttestationResponse;
2225
import com.linecorp.line.auth.fido.fido2.server.attestation.AttestationVerificationResult;
2326
import com.linecorp.line.auth.fido.fido2.server.model.AttestationObject;
2427

2528
public interface AttestationService {
2629
AttestationVerificationResult verifyAttestation(byte[] clientDataHsh, AttestationObject attestationObject);
2730
AttestationObject getAttestationObject(ServerAuthenticatorAttestationResponse attestationResponse);
28-
void attestationObjectValidationCheck(String rpId, AuthenticatorSelectionCriteria authenticatorSelection, AttestationObject attestationObject, CredentialMediationRequirement mediation);
31+
void attestationObjectValidationCheck(String rpId, AuthenticatorSelectionCriteria authenticatorSelection, AttestationObject attestationObject,CredentialMediationRequirement mediation , List<PublicKeyCredentialParameters> publicKeyCredentialParameters);
2932
void verifyAttestationCertificate(AttestationObject attestationObject, AttestationVerificationResult attestationVerificationResult);
3033
}

fido2-core/src/main/java/com/linecorp/line/auth/fido/fido2/server/service/AttestationServiceImpl.java

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,27 +31,30 @@
3131
import org.bouncycastle.asn1.x509.Extension;
3232
import org.bouncycastle.x509.extension.X509ExtensionUtil;
3333
import org.springframework.beans.factory.annotation.Autowired;
34-
import org.springframework.beans.factory.annotation.Value;
3534
import org.springframework.stereotype.Service;
3635

3736
import com.fasterxml.jackson.databind.ObjectMapper;
3837
import com.fasterxml.jackson.dataformat.cbor.CBORFactory;
3938

4039
import com.linecorp.line.auth.fido.fido2.common.AuthenticatorSelectionCriteria;
4140
import com.linecorp.line.auth.fido.fido2.common.CredentialMediationRequirement;
41+
import com.linecorp.line.auth.fido.fido2.common.PublicKeyCredentialParameters;
4242
import com.linecorp.line.auth.fido.fido2.common.UserVerificationRequirement;
4343
import com.linecorp.line.auth.fido.fido2.common.crypto.Digests;
4444
import com.linecorp.line.auth.fido.fido2.common.mdsv3.metadata.MetadataStatement;
4545
import com.linecorp.line.auth.fido.fido2.common.server.AttestationType;
46+
import com.linecorp.line.auth.fido.fido2.common.server.COSEAlgorithm;
4647
import com.linecorp.line.auth.fido.fido2.common.server.ServerAuthenticatorAttestationResponse;
4748
import com.linecorp.line.auth.fido.fido2.server.attestation.AttestationVerificationResult;
4849
import com.linecorp.line.auth.fido.fido2.server.attestation.AttestationVerifierFactory;
4950
import com.linecorp.line.auth.fido.fido2.server.attestation.android.keyattestation.AdditionalRevokeChecker;
5051
import com.linecorp.line.auth.fido.fido2.server.attestation.android.keyattestation.RevokeCheckerClient;
5152
import com.linecorp.line.auth.fido.fido2.server.error.InternalErrorCode;
5253
import com.linecorp.line.auth.fido.fido2.server.exception.FIDO2ServerRuntimeException;
54+
import com.linecorp.line.auth.fido.fido2.server.helper.CredentialPublicKeyHelper;
5355
import com.linecorp.line.auth.fido.fido2.server.model.AttestationObject;
5456
import com.linecorp.line.auth.fido.fido2.server.model.AttestationStatementFormatIdentifier;
57+
import com.linecorp.line.auth.fido.fido2.server.property.Fido2Properties;
5558
import com.linecorp.line.auth.fido.fido2.server.util.AaguidUtil;
5659
import com.linecorp.line.auth.fido.fido2.server.util.CertPathUtil;
5760
import com.linecorp.line.auth.fido.fido2.server.util.CertificateUtil;
@@ -66,16 +69,21 @@ public class AttestationServiceImpl implements AttestationService {
6669
private final VendorSpecificMetadataService vendorSpecificMetadataService;
6770
private final AttestationVerifierFactory attestationVerifierFactory;
6871
private final RevokeCheckerClient revokeCheckerClient;
69-
70-
@Value("${fido.fido2.accept-unregistered-authenticators}")
71-
private boolean acceptUnregisteredAuthenticators;
72+
private final Fido2Properties fido2Properties;
7273

7374
@Autowired
74-
public AttestationServiceImpl(MetadataService metadataService, VendorSpecificMetadataService vendorSpecificMetadataService, AttestationVerifierFactory attestationVerifierFactory, RevokeCheckerClient revokeCheckerClient) {
75+
public AttestationServiceImpl(
76+
MetadataService metadataService,
77+
VendorSpecificMetadataService vendorSpecificMetadataService,
78+
AttestationVerifierFactory attestationVerifierFactory,
79+
RevokeCheckerClient revokeCheckerClient,
80+
Fido2Properties fido2Properties
81+
) {
7582
this.metadataService = metadataService;
7683
this.vendorSpecificMetadataService = vendorSpecificMetadataService;
7784
this.attestationVerifierFactory = attestationVerifierFactory;
7885
this.revokeCheckerClient = revokeCheckerClient;
86+
this.fido2Properties = fido2Properties;
7987
}
8088

8189
@Override
@@ -111,7 +119,7 @@ public AttestationObject getAttestationObject(ServerAuthenticatorAttestationResp
111119
}
112120

113121
@Override
114-
public void attestationObjectValidationCheck(String rpId, AuthenticatorSelectionCriteria authenticatorSelection, AttestationObject attestationObject, CredentialMediationRequirement mediation) {
122+
public void attestationObjectValidationCheck(String rpId, AuthenticatorSelectionCriteria authenticatorSelection, AttestationObject attestationObject, CredentialMediationRequirement mediation, List<PublicKeyCredentialParameters> publicKeyCredentialParameters) {
115123
// verify attestationObject.authData.attestedCredentialData
116124
if (attestationObject.getAuthData().getAttestedCredentialData() == null) {
117125
throw new FIDO2ServerRuntimeException(InternalErrorCode.CREDENTIAL_NOT_INCLUDED);
@@ -138,6 +146,15 @@ public void attestationObjectValidationCheck(String rpId, AuthenticatorSelection
138146
!attestationObject.getAuthData().isUserVerified()) {
139147
throw new FIDO2ServerRuntimeException(InternalErrorCode.USER_VERIFICATION_FLAG_NOT_SET, "User verification flag not set", AaguidUtil.convert(attestationObject.getAuthData().getAttestedCredentialData().getAaguid()));
140148
}
149+
150+
// Verify "alg" parameter
151+
final COSEAlgorithm coseAlgorithm = CredentialPublicKeyHelper.getCOSEAlgorithm(attestationObject.getAuthData().getAttestedCredentialData().getCredentialPublicKey());
152+
final boolean algorithmExistsInRegOptionResponse = publicKeyCredentialParameters.stream().anyMatch(
153+
parameter -> parameter.getAlg().getValue() == coseAlgorithm.getValue()
154+
);
155+
if (!algorithmExistsInRegOptionResponse) {
156+
throw new FIDO2ServerRuntimeException(InternalErrorCode.NOT_ALLOWED_COSE_ALGORITHM, "Not allowed algorithm used");
157+
}
141158
}
142159

143160
@Override
@@ -167,7 +184,7 @@ public void verifyAttestationCertificate(AttestationObject attestationObject, At
167184

168185
// set attestation root certificate with metadata or vendor specific data
169186
// or skip getting metadata
170-
if (!acceptUnregisteredAuthenticators) { // throw an error if there is no metadata
187+
if (!fido2Properties.isAcceptUnregisteredAuthenticators()) { // throw an error if there is no metadata
171188
if (attestationRootCertificates == null) {
172189
throw new FIDO2ServerRuntimeException(InternalErrorCode.METADATA_NOT_FOUND);
173190
}

fido2-core/src/main/java/com/linecorp/line/auth/fido/fido2/server/service/ChallengeServiceImpl.java

Lines changed: 46 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2024 LY Corporation
2+
* Copyright 2024-2026 LY Corporation
33
*
44
* LY Corporation licenses this file to you under the Apache License,
55
* version 2.0 (the "License"); you may not use this file except in compliance
@@ -16,45 +16,55 @@
1616

1717
package com.linecorp.line.auth.fido.fido2.server.service;
1818

19+
import java.util.ArrayList;
20+
import java.util.List;
21+
import java.util.stream.Collectors;
22+
1923
import com.linecorp.line.auth.fido.fido2.common.COSEAlgorithmIdentifier;
2024
import com.linecorp.line.auth.fido.fido2.common.PublicKeyCredentialParameters;
2125
import com.linecorp.line.auth.fido.fido2.common.PublicKeyCredentialType;
2226
import com.linecorp.line.auth.fido.fido2.common.extension.AuthenticationExtensionsClientInputs;
23-
import com.linecorp.line.auth.fido.fido2.common.server.*;
27+
import com.linecorp.line.auth.fido.fido2.common.server.AuthOptionRequest;
28+
import com.linecorp.line.auth.fido.fido2.common.server.AuthOptionResponse;
29+
import com.linecorp.line.auth.fido.fido2.common.server.COSEAlgorithm;
30+
import com.linecorp.line.auth.fido.fido2.common.server.RegOptionRequest;
31+
import com.linecorp.line.auth.fido.fido2.common.server.RegOptionResponse;
32+
import com.linecorp.line.auth.fido.fido2.common.server.ServerPublicKeyCredentialDescriptor;
33+
import com.linecorp.line.auth.fido.fido2.common.server.ServerResponse;
2434
import com.linecorp.line.auth.fido.fido2.server.ServerConstant;
2535
import com.linecorp.line.auth.fido.fido2.server.error.InternalErrorCode;
2636
import com.linecorp.line.auth.fido.fido2.server.exception.FIDO2ServerRuntimeException;
2737
import com.linecorp.line.auth.fido.fido2.server.model.Session;
2838
import com.linecorp.line.auth.fido.fido2.server.model.UserKey;
39+
import com.linecorp.line.auth.fido.fido2.server.property.Fido2Properties;
2940
import com.linecorp.line.auth.fido.fido2.server.util.ChallengeGenerator;
30-
import lombok.extern.slf4j.Slf4j;
31-
import org.springframework.beans.factory.annotation.Autowired;
32-
import org.springframework.beans.factory.annotation.Value;
33-
import org.springframework.context.annotation.Primary;
34-
import org.springframework.stereotype.Service;
35-
import org.springframework.util.StringUtils;
3641

37-
import java.util.ArrayList;
38-
import java.util.List;
42+
import lombok.extern.slf4j.Slf4j;
3943

4044
@Slf4j
41-
@Primary
42-
@Service
4345
public class ChallengeServiceImpl implements ChallengeService {
4446
private final RpService rpService;
4547
private final UserKeyService userKeyService;
4648
private final SessionService sessionService;
4749

48-
@Value("${fido.fido2.session-ttl-millis}")
49-
private long sessionTtlMillis;
50+
private final long sessionTtlMillis;
51+
private final List<COSEAlgorithm> allowedAlgorithms;
5052

51-
@Autowired
5253
public ChallengeServiceImpl(RpService rpService,
5354
UserKeyService userKeyService,
54-
SessionService sessionService) {
55+
SessionService sessionService,
56+
Fido2Properties fido2Properties
57+
) {
5558
this.rpService = rpService;
5659
this.userKeyService = userKeyService;
5760
this.sessionService = sessionService;
61+
62+
sessionTtlMillis = fido2Properties.getSessionTtlMillis();
63+
allowedAlgorithms = fido2Properties.getRegistration()
64+
.getAllowedAlgorithms()
65+
.stream()
66+
.map(COSEAlgorithm::valueOf)
67+
.collect(Collectors.toList());
5868
}
5969

6070
/**
@@ -91,12 +101,11 @@ public RegOptionResponse getRegChallenge(RegOptionRequest regOptionRequest) {
91101
builder.excludeCredentials(getExcludeAndIncludeCredentials(userKeys));
92102

93103
// set public key params with all available algorithms
94-
List<PublicKeyCredentialParameters> publicKeyCredentialParameters = new ArrayList<>();
95-
for (COSEAlgorithmIdentifier identifier : COSEAlgorithmIdentifier.values()) {
96-
PublicKeyCredentialParameters parameters = new PublicKeyCredentialParameters();
97-
parameters.setType(PublicKeyCredentialType.PUBLIC_KEY);
98-
parameters.setAlg(identifier);
99-
publicKeyCredentialParameters.add(parameters);
104+
final List<PublicKeyCredentialParameters> publicKeyCredentialParameters;
105+
if (allowedAlgorithms.isEmpty()) {
106+
publicKeyCredentialParameters = createPublicKeyCredentialParameters(List.of(COSEAlgorithm.values()));
107+
} else {
108+
publicKeyCredentialParameters = createPublicKeyCredentialParameters(allowedAlgorithms);
100109
}
101110
builder.pubKeyCredParams(publicKeyCredentialParameters);
102111

@@ -244,4 +253,19 @@ private List<ServerPublicKeyCredentialDescriptor> getExcludeAndIncludeCredential
244253

245254
return publicKeyCredentialDescriptors;
246255
}
256+
257+
private static List<PublicKeyCredentialParameters> createPublicKeyCredentialParameters(
258+
List<COSEAlgorithm> coseAlgorithms
259+
) {
260+
final List<PublicKeyCredentialParameters> publicKeyCredentialParameters = new ArrayList<>();
261+
262+
for (COSEAlgorithm coseAlgorithm : coseAlgorithms) {
263+
final PublicKeyCredentialParameters parameters = new PublicKeyCredentialParameters();
264+
parameters.setType(PublicKeyCredentialType.PUBLIC_KEY);
265+
parameters.setAlg(COSEAlgorithmIdentifier.fromValue(coseAlgorithm.getValue()));
266+
publicKeyCredentialParameters.add(parameters);
267+
}
268+
269+
return publicKeyCredentialParameters;
270+
}
247271
}

fido2-core/src/main/java/com/linecorp/line/auth/fido/fido2/server/service/ResponseCommonService.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2024 LY Corporation
2+
* Copyright 2024-2026 LY Corporation
33
*
44
* LY Corporation licenses this file to you under the Apache License,
55
* version 2.0 (the "License"); you may not use this file except in compliance
@@ -21,7 +21,7 @@
2121
import java.net.URISyntaxException;
2222
import java.util.Base64;
2323

24-
import org.springframework.beans.factory.annotation.Value;
24+
import org.springframework.beans.factory.annotation.Autowired;
2525
import org.springframework.util.StringUtils;
2626

2727
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -31,14 +31,15 @@
3131
import com.linecorp.line.auth.fido.fido2.server.error.InternalErrorCode;
3232
import com.linecorp.line.auth.fido.fido2.server.exception.FIDO2ServerRuntimeException;
3333
import com.linecorp.line.auth.fido.fido2.server.model.CollectedClientData;
34+
import com.linecorp.line.auth.fido.fido2.server.property.Fido2Properties;
3435

3536
import lombok.extern.slf4j.Slf4j;
3637

3738
@Slf4j
3839
abstract public class ResponseCommonService {
3940

40-
@Value("${fido.fido2.deny-cross-origin:true}")
41-
private boolean denyCrossOrigin;
41+
@Autowired
42+
private Fido2Properties fido2Properties;
4243

4344
protected abstract void checkOrigin(URI originFromClientData, URI originFromRp, String rpId);
4445

@@ -99,7 +100,7 @@ public byte[] handleCommon(String type, String challengeSent, String base64UrlEn
99100
checkOrigin(originFromClientData, originFromRp, rpId);
100101

101102
// verify crossOrigin
102-
if (denyCrossOrigin && Boolean.TRUE.equals(collectedClientData.getCrossOrigin())) {
103+
if (fido2Properties.isDenyCrossOrigin() && Boolean.TRUE.equals(collectedClientData.getCrossOrigin())) {
103104
throw new FIDO2ServerRuntimeException(InternalErrorCode.CROSS_ORIGIN_NOT_ALLOWED,
104105
"Cross-origin WebAuthn ceremony not permitted");
105106
}

0 commit comments

Comments
 (0)