/* * Copyright 2018 A-SIT Plus GmbH * AT-specific eIDAS Connector has been developed in a cooperation between EGIZ, * A-SIT Plus GmbH, A-SIT, and Graz University of Technology. * * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by * the European Commission - subsequent versions of the EUPL (the "License"); * You may not use this work except in compliance with the License. * You may obtain a copy of the License at: * https://joinup.ec.europa.eu/news/understanding-eupl-v12 * * 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. * * This product combines work with different licenses. See the "NOTICE" text * file for details on the various modules and licenses. * The "NOTICE" text file is part of the distribution. Any derivative works * that you distribute must include a readable copy of the "NOTICE" text file. */ package at.asitplus.eidas.specific.modules.auth.eidas.v2.validator; import java.util.List; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.ImmutableList; import at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants; import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidasValidationException; import at.asitplus.eidas.specific.modules.auth.eidas.v2.service.EidasAttributeRegistry; import at.asitplus.eidas.specific.modules.auth.eidas.v2.utils.EidasResponseUtils; import at.gv.egiz.eaaf.core.api.IRequest; import at.gv.egiz.eaaf.core.impl.data.Trible; import eu.eidas.auth.commons.attribute.AttributeDefinition; import eu.eidas.auth.commons.attribute.AttributeValue; import eu.eidas.auth.commons.light.ILightResponse; import eu.eidas.auth.commons.protocol.eidas.LevelOfAssurance; /** * eIDAS Response validator implementation. * * @author tlenz * */ public class EidasResponseValidator { private static final Logger log = LoggerFactory.getLogger(EidasResponseValidator.class); /** * Validate an eIDAS Response according to internal state. * * @param pendingReq Current pending request * @param eidasResponse eIDAS response object * @param spCountry Country-Code of the Service Provider * @param citizenCountryCode Country-Code of the Citizen * @param attrRegistry eIDAS Attribute registry implementation * @throws EidasValidationException In case of an validation error */ public static void validateResponse(IRequest pendingReq, ILightResponse eidasResponse, String spCountry, String citizenCountryCode, EidasAttributeRegistry attrRegistry) throws EidasValidationException { /*-----------------------------------------------------| * validate received LoA against minimum required LoA | *_____________________________________________________| */ final LevelOfAssurance respLoA = LevelOfAssurance.fromString(eidasResponse.getLevelOfAssurance()); final List allowedLoAs = pendingReq.getServiceProviderConfiguration().getRequiredLoA(); boolean loaValid = false; for (final String allowedLoaString : allowedLoAs) { final LevelOfAssurance allowedLoa = LevelOfAssurance.fromString(allowedLoaString); if (respLoA.numericValue() >= allowedLoa.numericValue()) { log.debug("Response contains valid LoA. Resume process ... "); loaValid = true; break; } else { log.trace("Allowed LoA: " + allowedLoaString + " DOES NOT match response LoA: " + eidasResponse .getLevelOfAssurance()); } } if (!loaValid) { log.error("eIDAS Response LevelOfAssurance is lower than the required! " + "(Resp-LoA:{} Req-LoA:{} )", respLoA.getValue(), allowedLoAs.toArray()); throw new EidasValidationException("eidas.06", new Object[] { respLoA.getValue() }); } /*-----------------------------------------------------| * validate 'PersonalIdentifier' attribute | *_____________________________________________________| */ final AttributeDefinition attrDefinition = attrRegistry.getCoreAttributeRegistry().getByFriendlyName( Constants.eIDAS_ATTR_PERSONALIDENTIFIER).first(); final ImmutableList> attributeValues = eidasResponse.getAttributes() .getAttributeMap().get(attrDefinition).asList(); final List personalIdObj = EidasResponseUtils.translateStringListAttribute(attrDefinition, attributeValues); // check if attribute exists if (personalIdObj == null || personalIdObj.isEmpty()) { log.warn("eIDAS Response include NO 'PersonalIdentifier' attriubte " + ".... That can be a BIG problem in further processing steps"); throw new EidasValidationException("eidas.05", new Object[] { "NO 'PersonalIdentifier' attriubte" }); } else if (personalIdObj.size() > 1) { log.warn("eIDAS Response include MORE THAN ONE 'PersonalIdentifier' attriubtes " + ".... That can be a BIG problem in further processing steps"); throw new EidasValidationException("eidas.05", new Object[] { "MORE THAN ONE 'PersonalIdentifier' attriubtes" }); } else { final String natPersId = personalIdObj.get(0); // validate attribute value format final Trible split = EidasResponseUtils.parseEidasPersonalIdentifier(natPersId); if (split == null) { throw new EidasValidationException("eidas.07", new Object[] { Constants.eIDAS_ATTR_PERSONALIDENTIFIER, "Wrong identifier format" }); } else { // validation according to eIDAS SAML Attribute Profile, Section 2.2.3 if (StringUtils.isEmpty(split.getSecond())) { log.warn("eIDAS attribute value for " + Constants.eIDAS_ATTR_PERSONALIDENTIFIER + " includes NO destination country. Value:" + natPersId); throw new EidasValidationException("eidas.07", new Object[] { Constants.eIDAS_ATTR_PERSONALIDENTIFIER, "No or empty destination country" }); } if (!split.getSecond().equalsIgnoreCase(spCountry)) { log.warn("eIDAS attribute value for " + Constants.eIDAS_ATTR_PERSONALIDENTIFIER + " includes wrong destination country. Value:" + natPersId + " SP-Country:" + spCountry); throw new EidasValidationException("eidas.07", new Object[] { Constants.eIDAS_ATTR_PERSONALIDENTIFIER, "Destination country does not match to SP country" }); } if (StringUtils.isEmpty(split.getFirst())) { log.warn("eIDAS attribute value for " + Constants.eIDAS_ATTR_PERSONALIDENTIFIER + " includes NO citizen country. Value:" + natPersId); throw new EidasValidationException("eidas.07", new Object[] { Constants.eIDAS_ATTR_PERSONALIDENTIFIER, "No or empty citizen country" }); } if (!split.getFirst().equalsIgnoreCase(citizenCountryCode)) { log.warn("eIDAS attribute value for " + Constants.eIDAS_ATTR_PERSONALIDENTIFIER + " includes a citizen country that does not match to service-provider country. " + " Value:" + natPersId + " citiczen Country:" + spCountry); throw new EidasValidationException("eidas.07", new Object[] { Constants.eIDAS_ATTR_PERSONALIDENTIFIER, "Citizen country does not match to eIDAS-node country that generates the response" }); } } } } }