From 07318e70ea3a9d25ca945c391820149183c61ca0 Mon Sep 17 00:00:00 2001 From: Thomas <> Date: Mon, 8 Feb 2021 18:47:34 +0100 Subject: add Binding Public-Key into technical AuthBlock in case of an ID Austria process --- .../verification/AuthnRequestValidator.java | 114 +++++++++++++-------- .../test/utils/AuthnRequestValidatorTest.java | 5 + connector/src/test/resources/data/pvp2_authn_3.xml | 3 + .../specific/connector/MsEidasNodeConstants.java | 3 + .../eidas/v2/service/AuthBlockSigningService.java | 12 +++ .../tasks/CreateIdentityLinkTaskEidNewTest.java | 25 ++++- 6 files changed, 116 insertions(+), 46 deletions(-) diff --git a/connector/src/main/java/at/asitplus/eidas/specific/connector/verification/AuthnRequestValidator.java b/connector/src/main/java/at/asitplus/eidas/specific/connector/verification/AuthnRequestValidator.java index a9eb06be..881eeb8a 100644 --- a/connector/src/main/java/at/asitplus/eidas/specific/connector/verification/AuthnRequestValidator.java +++ b/connector/src/main/java/at/asitplus/eidas/specific/connector/verification/AuthnRequestValidator.java @@ -75,7 +75,7 @@ public class AuthnRequestValidator implements IAuthnRequestPostProcessor { if (nameIdPolicy != null) { final String nameIdFormat = nameIdPolicy.getFormat(); if (nameIdFormat != null) { - if (!(NameIDType.TRANSIENT.equals(nameIdFormat) + if (!(NameIDType.TRANSIENT.equals(nameIdFormat) || NameIDType.PERSISTENT.equals(nameIdFormat))) { throw new NameIdFormatNotSupportedException(nameIdFormat); @@ -114,10 +114,10 @@ public class AuthnRequestValidator implements IAuthnRequestPostProcessor { // post-process requested LoA comparison-level pendingReq.getServiceProviderConfiguration(ServiceProviderConfiguration.class).setLoAMachtingMode( extractComparisonLevel(authnReq)); - - //extract information from requested attributes + + // extract information from requested attributes extractFromRequestedAttriutes(pendingReq, authnReq); - + } catch (final EaafStorageException e) { log.info("Can NOT store Authn. Req. data into pendingRequest.", e); throw new AuthnRequestValidatorException("internal.02", null, e); @@ -126,14 +126,14 @@ public class AuthnRequestValidator implements IAuthnRequestPostProcessor { } - private void extractFromRequestedAttriutes(IRequest pendingReq, AuthnRequest authnReq) - throws AuthnRequestValidatorException { + private void extractFromRequestedAttriutes(IRequest pendingReq, AuthnRequest authnReq) + throws AuthnRequestValidatorException, EaafStorageException { // validate and process requested attributes boolean sectorDetected = false; - + final ServiceProviderConfiguration spConfig = pendingReq.getServiceProviderConfiguration( ServiceProviderConfiguration.class); - + if (authnReq.getExtensions() != null) { final List requestedAttributes = authnReq.getExtensions().getUnknownXMLObjects(); for (final XMLObject reqAttrObj : requestedAttributes) { @@ -143,77 +143,101 @@ public class AuthnRequestValidator implements IAuthnRequestPostProcessor { for (final EaafRequestedAttribute el : reqAttr.getAttributes()) { log.trace("Processing req. attribute '" + el.getName() + "' ... "); if (el.getName().equals(PvpAttributeDefinitions.EID_SECTOR_FOR_IDENTIFIER_NAME)) { - sectorDetected = extractBpkTargetIdentifier(el, spConfig); - + sectorDetected = extractBpkTargetIdentifier(el, spConfig); + } else if (el.getName().equals(ExtendedPvpAttributeDefinitions.EID_TRANSACTION_ID_NAME)) { extractUniqueTransactionId(el, pendingReq); - + + } else if (el.getName().equals(MsEidasNodeConstants.EID_BINDING_PUBLIC_KEY_NAME)) { + extractBindingPublicKey(el, pendingReq); + } else { log.debug("Ignore req. attribute: " + el.getName()); - + } } } else { log.debug("No requested Attributes in Authn. Request"); - + } } else { log.info("Ignore unknown requested attribute: " + reqAttrObj.getElementQName().toString()); - + } } } - + if (!sectorDetected) { log.warn("Authn.Req validation FAILED. Reason: Contains NO or NO VALID target-sector information."); throw new AuthnRequestValidatorException("pvp2.22", new Object[] { "NO or NO VALID target-sector information" }); } - + + } + + private void extractBindingPublicKey(EaafRequestedAttribute el, IRequest pendingReq) + throws EaafStorageException { + if (el.getAttributeValues() != null && el.getAttributeValues().size() == 1) { + final String bindingPubKey = el.getAttributeValues().get(0).getDOM().getTextContent(); + pendingReq.setRawDataToTransaction(MsEidasNodeConstants.EID_BINDING_PUBLIC_KEY_NAME, bindingPubKey); + log.info("Find Binding Public-Key. eIDAS authentication will be used to create an ID Austria Binding"); + + } else { + log.warn( + "Req. attribute '{}' contains NO or MORE THEN ONE attribute-values. Ignore full req. attribute", + el.getName()); + + } } /** * Extract unique transactionId from AuthnRequest. - * - * @param el Requested attribute from AuthnRequest - * @param pendingReq Current pendingRequest object (has to be of type {@link RequestImpl}) - * @return true if transactionId extraction was successful, otherwise false + * + * @param el Requested attribute from AuthnRequest + * @param pendingReq Current pendingRequest object (has to be of type + * {@link RequestImpl}) + * @return true if transactionId extraction was successful, + * otherwise false */ private boolean extractUniqueTransactionId(EaafRequestedAttribute el, IRequest pendingReq) { if (!(pendingReq instanceof RequestImpl)) { - log.warn("Can NOT set unique transactionId from AuthnRequest,because 'PendingRequest' is NOT from Type: {}", + log.warn( + "Can NOT set unique transactionId from AuthnRequest,because 'PendingRequest' is NOT from Type: {}", RequestImpl.class.getName()); - - } else { + + } else { if (el.getAttributeValues() != null && el.getAttributeValues().size() == 1) { - final String transactionId = el.getAttributeValues().get(0).getDOM().getTextContent(); - ((RequestImpl)pendingReq).setUniqueTransactionIdentifier(transactionId); + final String transactionId = el.getAttributeValues().get(0).getDOM().getTextContent(); + ((RequestImpl) pendingReq).setUniqueTransactionIdentifier(transactionId); return true; } else { - log.warn("Req. attribute '{}' contains NO or MORE THEN ONE attribute-values. Ignore full req. attribute", + log.warn( + "Req. attribute '{}' contains NO or MORE THEN ONE attribute-values. Ignore full req. attribute", el.getName()); - + } - + } - + return false; } /** * Extract the bPK target from requested attribute. - * - * @param el Requested attribute from AuthnRequest + * + * @param el Requested attribute from AuthnRequest * @param spConfig Service-Provider configuration for current process - * @return true if bPK target extraction was successful, otherwise false + * @return true if bPK target extraction was successful, otherwise + * false */ - private boolean extractBpkTargetIdentifier(EaafRequestedAttribute el, ServiceProviderConfiguration spConfig) { + private boolean extractBpkTargetIdentifier(EaafRequestedAttribute el, + ServiceProviderConfiguration spConfig) { if (el.getAttributeValues() != null && el.getAttributeValues().size() == 1) { - final String sectorId = el.getAttributeValues().get(0).getDOM().getTextContent(); + final String sectorId = el.getAttributeValues().get(0).getDOM().getTextContent(); try { spConfig.setBpkTargetIdentifier(sectorId); return true; @@ -227,16 +251,16 @@ public class AuthnRequestValidator implements IAuthnRequestPostProcessor { log.warn("Req. attribute '" + el.getName() + "' contains NO or MORE THEN ONE attribute-values. Ignore full req. attribute"); } - + return false; - + } - - private void postprocessLoaLevel(IRequest pendingReq, AuthnRequest authnReq) + + private void postprocessLoaLevel(IRequest pendingReq, AuthnRequest authnReq) throws AuthnRequestValidatorException { final List reqLoA = extractLoA(authnReq); - log.trace("SP requests LoA with: {}", String.join(", ",reqLoA)); - + log.trace("SP requests LoA with: {}", String.join(", ", reqLoA)); + LevelOfAssurance minimumLoAFromConfig = LevelOfAssurance.fromString(basicConfig.getBasicConfiguration( MsEidasNodeConstants.PROP_EIDAS_REQUEST_LOA_MINIMUM_LEVEL, EaafConstants.EIDAS_LOA_HIGH)); @@ -246,15 +270,15 @@ public class AuthnRequestValidator implements IAuthnRequestPostProcessor { minimumLoAFromConfig = LevelOfAssurance.HIGH; } - + log.trace("Validate requested LoA to connector configuration minimum LoA: {} ...", - minimumLoAFromConfig); + minimumLoAFromConfig); final List allowedLoA = new ArrayList<>(); for (final String loa : reqLoA) { try { final LevelOfAssurance intLoa = LevelOfAssurance.fromString(loa); String selectedLoA = EaafConstants.EIDAS_LOA_HIGH; - if (intLoa != null + if (intLoa != null && intLoa.numericValue() <= minimumLoAFromConfig.numericValue()) { log.info("Client: {} requested LoA: {} will be upgraded to: {}", pendingReq.getServiceProviderConfiguration().getUniqueIdentifier(), @@ -281,7 +305,7 @@ public class AuthnRequestValidator implements IAuthnRequestPostProcessor { pendingReq.getServiceProviderConfiguration(ServiceProviderConfiguration.class).setRequiredLoA( allowedLoA); - + } private String extractComparisonLevel(AuthnRequest authnReq) { @@ -335,7 +359,7 @@ public class AuthnRequestValidator implements IAuthnRequestPostProcessor { private String extractScopeRequsterId(AuthnRequest authnReq) { if (authnReq.getScoping() != null) { final Scoping scoping = authnReq.getScoping(); - if (scoping.getRequesterIDs() != null + if (scoping.getRequesterIDs() != null && scoping.getRequesterIDs().size() > 0) { if (scoping.getRequesterIDs().size() == 1) { return scoping.getRequesterIDs().get(0).getRequesterID(); diff --git a/connector/src/test/java/at/asitplus/eidas/specific/connector/test/utils/AuthnRequestValidatorTest.java b/connector/src/test/java/at/asitplus/eidas/specific/connector/test/utils/AuthnRequestValidatorTest.java index 9aafb4b6..c57515a0 100644 --- a/connector/src/test/java/at/asitplus/eidas/specific/connector/test/utils/AuthnRequestValidatorTest.java +++ b/connector/src/test/java/at/asitplus/eidas/specific/connector/test/utils/AuthnRequestValidatorTest.java @@ -214,6 +214,11 @@ public class AuthnRequestValidatorTest { Assert.assertEquals("wrong transactionId", "transId_11223344556677aabbcc", pendingReq.getUniqueTransactionIdentifier()); + + Assert.assertEquals("wrong binding pubkey", "binding_pubKey_1144225247125dsfasfasdf", + pendingReq.getRawData(MsEidasNodeConstants.EID_BINDING_PUBLIC_KEY_NAME, String.class)); + + } diff --git a/connector/src/test/resources/data/pvp2_authn_3.xml b/connector/src/test/resources/data/pvp2_authn_3.xml index 35e49b0f..5352c441 100644 --- a/connector/src/test/resources/data/pvp2_authn_3.xml +++ b/connector/src/test/resources/data/pvp2_authn_3.xml @@ -31,6 +31,9 @@ transId_11223344556677aabbcc + + binding_pubKey_1144225247125dsfasfasdf + diff --git a/connector_lib/src/main/java/at/asitplus/eidas/specific/connector/MsEidasNodeConstants.java b/connector_lib/src/main/java/at/asitplus/eidas/specific/connector/MsEidasNodeConstants.java index 133f104d..027d0832 100644 --- a/connector_lib/src/main/java/at/asitplus/eidas/specific/connector/MsEidasNodeConstants.java +++ b/connector_lib/src/main/java/at/asitplus/eidas/specific/connector/MsEidasNodeConstants.java @@ -169,6 +169,9 @@ public class MsEidasNodeConstants { public static final List COUNTRY_SELECTION_PARAM_WHITELIST = Arrays.asList(REQ_PARAM_SELECTED_COUNTRY, REQ_PARAM_SELECTED_ENVIRONMENT); + + public static final String EID_BINDING_PUBLIC_KEY_NAME = "urn:eidgvat:attributes.binding.pubkey"; + private MsEidasNodeConstants() { //hidden Constructor for class with static values only. } diff --git a/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/service/AuthBlockSigningService.java b/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/service/AuthBlockSigningService.java index 234d52dd..a2af4342 100644 --- a/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/service/AuthBlockSigningService.java +++ b/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/service/AuthBlockSigningService.java @@ -18,6 +18,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; @@ -81,6 +82,14 @@ public class AuthBlockSigningService { authBlock.setTimestamp(LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS)); authBlock.setUniqueId(pendingReq.getRawData(MsEidasNodeConstants.DATA_REQUESTERID, String.class)); authBlock.setPiiTransactionId(pendingReq.getUniquePiiTransactionIdentifier()); + + //set Binding PublicKey if available + Object bindingPubKey = pendingReq.getRawData(MsEidasNodeConstants.EID_BINDING_PUBLIC_KEY_NAME); + if (bindingPubKey instanceof String) { + authBlock.setBindingPublicKey((String) bindingPubKey); + + } + String jwsPayload = mapper.writeValueAsString(authBlock); log.debug("Building and sign authBlock with data: {}", jwsPayload); @@ -172,6 +181,7 @@ public class AuthBlockSigningService { * */ @Data + @JsonInclude(JsonInclude.Include.NON_NULL) private static class EidasAuchBlock { @JsonProperty("challenge") @@ -189,6 +199,8 @@ public class AuthBlockSigningService { @JsonProperty("piiTransactionId") private String piiTransactionId; + @JsonProperty("bindingPublicKey") + private String bindingPublicKey; } diff --git a/eidas_modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/tasks/CreateIdentityLinkTaskEidNewTest.java b/eidas_modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/tasks/CreateIdentityLinkTaskEidNewTest.java index 2e6790c5..0621081a 100644 --- a/eidas_modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/tasks/CreateIdentityLinkTaskEidNewTest.java +++ b/eidas_modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/tasks/CreateIdentityLinkTaskEidNewTest.java @@ -221,6 +221,7 @@ public class CreateIdentityLinkTaskEidNewTest { Assert.assertEquals("appId", randomTestSp, authBlockJson.get("appId").asText()); Assert.assertFalse("'challenge' is null", authBlockJson.get("challenge").asText().isEmpty()); Assert.assertFalse("'timestamp' is null", authBlockJson.get("timestamp").asText().isEmpty()); + Assert.assertFalse("binding pubKey", authBlockJson.has("bindingPublicKey")); // check vsz request @@ -312,9 +313,11 @@ public class CreateIdentityLinkTaskEidNewTest { signContentEntry.setValue(RandomStringUtils.randomAlphanumeric(10)); signContentResp.getOut().add(signContentEntry); when(szrMock, "signContent", any(), any(), any()).thenReturn(signContentResp); - + String randomTestSp = RandomStringUtils.randomAlphabetic(10); + String bindingPubKey = RandomStringUtils.randomAlphabetic(10); pendingReq.setRawDataToTransaction(MsEidasNodeConstants.DATA_REQUESTERID, randomTestSp); + pendingReq.setRawDataToTransaction(MsEidasNodeConstants.EID_BINDING_PUBLIC_KEY_NAME, bindingPubKey); //perform test task.execute(pendingReq, executionContext); @@ -329,8 +332,28 @@ public class CreateIdentityLinkTaskEidNewTest { Assert.assertNotNull("AuthProcessData", authProcessData); Assert.assertNotNull("eidasBind", authProcessData.getGenericDataFromSession(Constants.EIDAS_BIND, String.class)); + // check authblock signature String authBlock = authProcessData.getGenericDataFromSession(Constants.SZR_AUTHBLOCK, String.class); Assert.assertNotNull("AuthBlock", authBlock); + final AlgorithmConstraints constraints = new AlgorithmConstraints(ConstraintType.PERMIT, + BINDING_AUTH_ALGORITHM_WHITELIST_SIGNING.toArray(new String[BINDING_AUTH_ALGORITHM_WHITELIST_SIGNING.size()])); + Pair keyStore = getKeyStore(); + X509Certificate[] trustedCerts = EaafKeyStoreUtils + .getPrivateKeyAndCertificates(keyStore.getFirst(), ALIAS, PW.toCharArray(), true, "junit").getSecond(); + JwsResult result = JoseUtils.validateSignature(authBlock, Arrays.asList(trustedCerts), constraints); + Assert.assertTrue("AuthBlock not valid", result.isValid()); + JsonNode authBlockJson = mapper.readTree(result.getPayLoad()); + Assert.assertNotNull("deserialized AuthBlock", authBlockJson); + + Assert.assertNotNull("no piiTransactionId in pendingRequesdt", + storedPendingReq.getUniquePiiTransactionIdentifier()); + Assert.assertEquals("piiTransactionId", storedPendingReq.getUniquePiiTransactionIdentifier(), + authBlockJson.get("piiTransactionId").asText()); + Assert.assertEquals("appId", randomTestSp, authBlockJson.get("appId").asText()); + Assert.assertFalse("'challenge' is null", authBlockJson.get("challenge").asText().isEmpty()); + Assert.assertFalse("'timestamp' is null", authBlockJson.get("timestamp").asText().isEmpty()); + Assert.assertTrue("binding pubKey", authBlockJson.has("bindingPublicKey")); + Assert.assertEquals("binding PubKey", bindingPubKey, authBlockJson.get("bindingPublicKey").asText()); Assert.assertTrue("EID process", authProcessData.isEidProcess()); Assert.assertTrue("foreigner process", authProcessData.isForeigner()); -- cgit v1.2.3