-
Notifications
You must be signed in to change notification settings - Fork 73
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(jans-fido2): none assertiona and unit test for none attestation (#…
…7199) * feat(jans-fido2): added residentKey in attestation Signed-off-by: Milton Ch <[email protected]> * feat(jans-fido2): none assertion test and unit test for none attestation Signed-off-by: Milton Ch <[email protected]> --------- Signed-off-by: Milton Ch <[email protected]>
- Loading branch information
Showing
5 changed files
with
375 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
113 changes: 113 additions & 0 deletions
113
...src/main/java/io/jans/fido2/service/processor/assertion/NoneAssertionFormatProcessor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
/* | ||
* Janssen Project software is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text. | ||
* | ||
* Copyright (c) 2020, Janssen Project | ||
*/ | ||
|
||
/* | ||
* Copyright (c) 2018 Mastercard | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and limitations under the License. | ||
*/ | ||
|
||
package io.jans.fido2.service.processor.assertion; | ||
|
||
import com.fasterxml.jackson.databind.JsonNode; | ||
import io.jans.fido2.ctap.AttestationFormat; | ||
import io.jans.fido2.exception.Fido2CompromisedDevice; | ||
import io.jans.fido2.exception.Fido2RuntimeException; | ||
import io.jans.fido2.model.auth.AuthData; | ||
import io.jans.fido2.service.AuthenticatorDataParser; | ||
import io.jans.fido2.service.Base64Service; | ||
import io.jans.fido2.service.CoseService; | ||
import io.jans.fido2.service.DataMapperService; | ||
import io.jans.fido2.service.processors.AssertionFormatProcessor; | ||
import io.jans.fido2.service.verifier.AuthenticatorDataVerifier; | ||
import io.jans.fido2.service.verifier.CommonVerifiers; | ||
import io.jans.fido2.service.verifier.UserVerificationVerifier; | ||
import io.jans.orm.model.fido2.Fido2AuthenticationData; | ||
import io.jans.orm.model.fido2.Fido2RegistrationData; | ||
import jakarta.enterprise.context.ApplicationScoped; | ||
import jakarta.inject.Inject; | ||
import org.apache.commons.codec.binary.Hex; | ||
import org.apache.commons.codec.digest.DigestUtils; | ||
import org.slf4j.Logger; | ||
|
||
import java.security.PublicKey; | ||
|
||
/** | ||
* Class which processes assertions of "none" fmt (attestation type) | ||
*/ | ||
@ApplicationScoped | ||
public class NoneAssertionFormatProcessor implements AssertionFormatProcessor { | ||
|
||
@Inject | ||
private Logger log; | ||
|
||
@Inject | ||
private CoseService coseService; | ||
|
||
@Inject | ||
private CommonVerifiers commonVerifiers; | ||
|
||
@Inject | ||
private AuthenticatorDataVerifier authenticatorDataVerifier; | ||
|
||
@Inject | ||
private UserVerificationVerifier userVerificationVerifier; | ||
|
||
@Inject | ||
private AuthenticatorDataParser authenticatorDataParser; | ||
|
||
@Inject | ||
private DataMapperService dataMapperService; | ||
|
||
@Inject | ||
private Base64Service base64Service; | ||
|
||
@Override | ||
public AttestationFormat getAttestationFormat() { | ||
return AttestationFormat.none; | ||
} | ||
|
||
@Override | ||
public void process(String base64AuthenticatorData, String signature, String clientDataJson, Fido2RegistrationData registration, | ||
Fido2AuthenticationData authenticationEntity) { | ||
log.debug("Registration: {}", registration); | ||
|
||
AuthData authData = authenticatorDataParser.parseAssertionData(base64AuthenticatorData); | ||
commonVerifiers.verifyRpIdHash(authData, registration.getDomain()); | ||
|
||
log.debug("User verification option: {}", authenticationEntity.getUserVerificationOption()); | ||
userVerificationVerifier.verifyUserVerificationOption(authenticationEntity.getUserVerificationOption(), authData); | ||
|
||
byte[] clientDataHash = DigestUtils.getSha256Digest().digest(base64Service.urlDecode(clientDataJson)); | ||
|
||
try { | ||
int counter = authenticatorDataParser.parseCounter(authData.getCounters()); | ||
commonVerifiers.verifyCounter(registration.getCounter(), counter); | ||
registration.setCounter(counter); | ||
|
||
JsonNode uncompressedECPointNode = dataMapperService.cborReadTree(base64Service.urlDecode(registration.getUncompressedECPoint())); | ||
PublicKey publicKey = coseService.createUncompressedPointFromCOSEPublicKey(uncompressedECPointNode); | ||
|
||
log.debug("Uncompressed ECpoint node: {}", uncompressedECPointNode); | ||
log.debug("EC Public key hex: {}", Hex.encodeHexString(publicKey.getEncoded())); | ||
log.debug("Registration algorithm: {}, default use: -7", registration.getSignatureAlgorithm()); | ||
authenticatorDataVerifier.verifyAssertionSignature(authData, clientDataHash, signature, publicKey, -7); | ||
|
||
} catch (Fido2CompromisedDevice ex) { | ||
log.error("Error compromised device: {}", ex.getMessage()); | ||
throw ex; | ||
} catch (Exception ex) { | ||
log.error("Error to check none assertion: {}", ex.getMessage()); | ||
throw new Fido2RuntimeException("Failed to check none assertion: {}", ex.getMessage(), ex); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
163 changes: 163 additions & 0 deletions
163
...test/java/io/jans/fido2/service/processor/assertion/NoneAssertionFormatProcessorTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
package io.jans.fido2.service.processor.assertion; | ||
|
||
import com.fasterxml.jackson.databind.JsonNode; | ||
import io.jans.fido2.exception.Fido2CompromisedDevice; | ||
import io.jans.fido2.exception.Fido2RuntimeException; | ||
import io.jans.fido2.model.auth.AuthData; | ||
import io.jans.fido2.service.AuthenticatorDataParser; | ||
import io.jans.fido2.service.Base64Service; | ||
import io.jans.fido2.service.CoseService; | ||
import io.jans.fido2.service.DataMapperService; | ||
import io.jans.fido2.service.verifier.AuthenticatorDataVerifier; | ||
import io.jans.fido2.service.verifier.CommonVerifiers; | ||
import io.jans.fido2.service.verifier.UserVerificationVerifier; | ||
import io.jans.orm.model.fido2.Fido2AuthenticationData; | ||
import io.jans.orm.model.fido2.Fido2RegistrationData; | ||
import io.jans.orm.model.fido2.UserVerification; | ||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.api.extension.ExtendWith; | ||
import org.mockito.InjectMocks; | ||
import org.mockito.Mock; | ||
import org.mockito.junit.jupiter.MockitoExtension; | ||
import org.slf4j.Logger; | ||
|
||
import java.io.IOException; | ||
import java.security.PublicKey; | ||
|
||
import static org.junit.jupiter.api.Assertions.*; | ||
import static org.mockito.Mockito.*; | ||
|
||
@ExtendWith(MockitoExtension.class) | ||
class NoneAssertionFormatProcessorTest { | ||
|
||
@InjectMocks | ||
private NoneAssertionFormatProcessor noneAssertionFormatProcessor; | ||
|
||
@Mock | ||
private Logger log; | ||
|
||
@Mock | ||
private CoseService coseService; | ||
|
||
@Mock | ||
private CommonVerifiers commonVerifiers; | ||
|
||
@Mock | ||
private AuthenticatorDataVerifier authenticatorDataVerifier; | ||
|
||
@Mock | ||
private UserVerificationVerifier userVerificationVerifier; | ||
|
||
@Mock | ||
private AuthenticatorDataParser authenticatorDataParser; | ||
|
||
@Mock | ||
private DataMapperService dataMapperService; | ||
|
||
@Mock | ||
private Base64Service base64Service; | ||
|
||
@Test | ||
void getAttestationFormat_valid_none() { | ||
String fmt = noneAssertionFormatProcessor.getAttestationFormat().getFmt(); | ||
assertNotNull(fmt); | ||
assertEquals(fmt, "none"); | ||
} | ||
|
||
@Test | ||
void process_validData_success() throws IOException { | ||
String base64AuthenticatorData = "base64AuthenticatorData_test"; | ||
String signature = "signature_test"; | ||
String clientDataJson = "clientDataJson_test"; | ||
Fido2RegistrationData registration = mock(Fido2RegistrationData.class); | ||
Fido2AuthenticationData authenticationEntity = mock(Fido2AuthenticationData.class); | ||
|
||
when(authenticatorDataParser.parseAssertionData(any())).thenReturn(mock(AuthData.class)); | ||
when(base64Service.urlDecode(any(String.class))).thenReturn("decode_test".getBytes()); | ||
when(dataMapperService.cborReadTree(any())).thenReturn(mock(JsonNode.class)); | ||
PublicKey publicKey = mock(PublicKey.class); | ||
when(coseService.createUncompressedPointFromCOSEPublicKey(any())).thenReturn(publicKey); | ||
when(publicKey.getEncoded()).thenReturn("test".getBytes()); | ||
when(authenticationEntity.getUserVerificationOption()).thenReturn(UserVerification.preferred); | ||
when(registration.getDomain()).thenReturn("domain_test"); | ||
|
||
noneAssertionFormatProcessor.process(base64AuthenticatorData, signature, clientDataJson, registration, authenticationEntity); | ||
|
||
verify(log).debug(eq("Registration: {}"), any(Fido2RegistrationData.class)); | ||
verify(log).debug(eq("User verification option: {}"), any(UserVerification.class)); | ||
verify(commonVerifiers).verifyRpIdHash(any(AuthData.class), any(String.class)); | ||
verify(log).debug(eq("Uncompressed ECpoint node: {}"), any(JsonNode.class)); | ||
verify(log).debug(eq("EC Public key hex: {}"), any(String.class)); | ||
verify(log).debug(eq("Registration algorithm: {}, default use: -7"), any(Integer.class)); | ||
verify(userVerificationVerifier).verifyUserVerificationOption(any(UserVerification.class), any(AuthData.class)); | ||
verify(authenticatorDataVerifier).verifyAssertionSignature(any(AuthData.class), any(byte[].class), any(String.class), any(PublicKey.class), any(Integer.class)); | ||
|
||
verify(log, never()).error(eq("Error compromised device: {}"), any(String.class)); | ||
verify(log, never()).error(eq("Error to check none assertion: {}"), any(String.class)); | ||
verifyNoMoreInteractions(log); | ||
} | ||
|
||
@Test | ||
void process_ifVerifyCounterIsThrowException_fido2CompromisedDevice() throws Fido2CompromisedDevice { | ||
String base64AuthenticatorData = "base64AuthenticatorData_test"; | ||
String signature = "signature_test"; | ||
String clientDataJson = "clientDataJson_test"; | ||
Fido2RegistrationData registration = mock(Fido2RegistrationData.class); | ||
Fido2AuthenticationData authenticationEntity = mock(Fido2AuthenticationData.class); | ||
|
||
when(authenticationEntity.getUserVerificationOption()).thenReturn(UserVerification.preferred); | ||
when(registration.getDomain()).thenReturn("domain_test"); | ||
when(registration.getCounter()).thenReturn(100); | ||
|
||
when(authenticatorDataParser.parseAssertionData(any())).thenReturn(mock(AuthData.class)); | ||
when(base64Service.urlDecode(any(String.class))).thenReturn("decode_test".getBytes()); | ||
doThrow(new Fido2CompromisedDevice("fido2CompromisedDevice_testError")).when(commonVerifiers).verifyCounter(any(Integer.class), any(Integer.class)); | ||
|
||
Fido2CompromisedDevice ex = assertThrows(Fido2CompromisedDevice.class, () -> noneAssertionFormatProcessor.process(base64AuthenticatorData, signature, clientDataJson, registration, authenticationEntity)); | ||
assertNotNull(ex); | ||
assertEquals(ex.getMessage(), "fido2CompromisedDevice_testError"); | ||
|
||
verify(log).debug(eq("Registration: {}"), any(Fido2RegistrationData.class)); | ||
verify(log).debug(eq("User verification option: {}"), any(UserVerification.class)); | ||
verify(commonVerifiers).verifyRpIdHash(any(AuthData.class), any(String.class)); | ||
verify(authenticatorDataParser).parseCounter(any()); | ||
verify(log).error(eq("Error compromised device: {}"), any(String.class)); | ||
|
||
verify(log, never()).debug(eq("Registration algorithm: {}, default use: -7"), any(Integer.class)); | ||
verify(log, never()).error(eq("Error to check none assertion: {}"), any(String.class)); | ||
verifyNoInteractions(authenticatorDataVerifier); | ||
verifyNoMoreInteractions(log); | ||
} | ||
|
||
@Test | ||
void process_ifCborReadTreeThrowException_fido2RuntimeException() throws Fido2CompromisedDevice, IOException { | ||
String base64AuthenticatorData = "base64AuthenticatorData_test"; | ||
String signature = "signature_test"; | ||
String clientDataJson = "clientDataJson_test"; | ||
Fido2RegistrationData registration = mock(Fido2RegistrationData.class); | ||
Fido2AuthenticationData authenticationEntity = mock(Fido2AuthenticationData.class); | ||
|
||
when(authenticationEntity.getUserVerificationOption()).thenReturn(UserVerification.preferred); | ||
when(registration.getDomain()).thenReturn("domain_test"); | ||
when(registration.getCounter()).thenReturn(100); | ||
when(registration.getUncompressedECPoint()).thenReturn("uncompressedECPoint_test"); | ||
|
||
when(authenticatorDataParser.parseAssertionData(any())).thenReturn(mock(AuthData.class)); | ||
when(base64Service.urlDecode(any(String.class))).thenReturn("decode_test".getBytes()); | ||
when(dataMapperService.cborReadTree(any(byte[].class))).thenThrow(new IOException("IOException_test")); | ||
|
||
Fido2RuntimeException ex = assertThrows(Fido2RuntimeException.class, () -> noneAssertionFormatProcessor.process(base64AuthenticatorData, signature, clientDataJson, registration, authenticationEntity)); | ||
assertNotNull(ex); | ||
assertEquals(ex.getMessage(), "IOException_test"); | ||
|
||
verify(log).debug(eq("Registration: {}"), any(Fido2RegistrationData.class)); | ||
verify(log).debug(eq("User verification option: {}"), any(UserVerification.class)); | ||
verify(commonVerifiers).verifyRpIdHash(any(AuthData.class), any(String.class)); | ||
verify(authenticatorDataParser).parseCounter(any()); | ||
verify(log).error(eq("Error to check none assertion: {}"), any(String.class)); | ||
|
||
verify(log, never()).error(eq("Error compromised device: {}"), any(String.class)); | ||
verifyNoInteractions(coseService, authenticatorDataVerifier); | ||
verifyNoMoreInteractions(log); | ||
} | ||
} |
Oops, something went wrong.