diff options
Diffstat (limited to 'eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/builder')
2 files changed, 769 insertions, 0 deletions
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/builder/AbstractAuthenticationDataBuilder.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/builder/AbstractAuthenticationDataBuilder.java new file mode 100644 index 00000000..561f6f32 --- /dev/null +++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/builder/AbstractAuthenticationDataBuilder.java @@ -0,0 +1,467 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.core.impl.idp.auth.builder; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.Base64Utils; +import org.w3c.dom.DOMException; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import at.gv.egiz.eaaf.core.api.IRequest; +import at.gv.egiz.eaaf.core.api.data.EAAFConstants; +import at.gv.egiz.eaaf.core.api.data.PVPAttributeDefinitions; +import at.gv.egiz.eaaf.core.api.idp.IAuthenticationDataBuilder; +import at.gv.egiz.eaaf.core.api.idp.IConfiguration; +import at.gv.egiz.eaaf.core.api.idp.ISPConfiguration; +import at.gv.egiz.eaaf.core.api.idp.auth.data.IAuthProcessDataContainer; +import at.gv.egiz.eaaf.core.api.idp.auth.data.IIdentityLink; +import at.gv.egiz.eaaf.core.exceptions.EAAFBuilderException; +import at.gv.egiz.eaaf.core.exceptions.EAAFConfigurationException; +import at.gv.egiz.eaaf.core.exceptions.EAAFParserException; +import at.gv.egiz.eaaf.core.exceptions.XPathException; +import at.gv.egiz.eaaf.core.impl.data.Pair; +import at.gv.egiz.eaaf.core.impl.idp.AuthenticationData; +import at.gv.egiz.eaaf.core.impl.idp.auth.data.SimpleIdentityLinkAssertionParser; +import at.gv.egiz.eaaf.core.impl.utils.XPathUtils; + + +public abstract class AbstractAuthenticationDataBuilder implements IAuthenticationDataBuilder { + private static final Logger log = LoggerFactory.getLogger(AbstractAuthenticationDataBuilder.class); + protected Collection<String> includedToGenericAuthData = null; + @Autowired protected IConfiguration basicConfig; + + protected void generateBasicAuthData(AuthenticationData authData, IRequest pendingReq, + IAuthProcessDataContainer authProcessData) throws EAAFBuilderException, EAAFConfigurationException, XPathException, DOMException, EAAFParserException { + + if (authProcessData.getGenericSessionDataStorage() != null && + !authProcessData.getGenericSessionDataStorage().isEmpty()) + includedToGenericAuthData = authProcessData.getGenericSessionDataStorage().keySet(); + else + includedToGenericAuthData = new ArrayList<String>(); + + //#################################################### + //set general authData info's + authData.setAuthenticationIssuer(pendingReq.getAuthURL()); + authData.setSsoSession(pendingReq.needSingleSignOnFunctionality()); + authData.setBaseIDTransferRestrication(pendingReq.getServiceProviderConfiguration().hasBaseIdTransferRestriction()); + + + //#################################################### + //parse user info's from identityLink + IIdentityLink idlFromPVPAttr = null; + IIdentityLink identityLink = authProcessData.getIdentityLink(); + if (identityLink != null) { + parseBasicUserInfosFromIDL(authData, identityLink, includedToGenericAuthData); + + } else { + // identityLink is not direct in MOASession + String pvpAttrIDL = authProcessData.getGenericDataFromSession(PVPAttributeDefinitions.EID_IDENTITY_LINK_NAME, String.class); + //find PVP-Attr. which contains the IdentityLink + if (StringUtils.isNotEmpty(pvpAttrIDL)) { + log.debug("Find PVP-Attr: " + PVPAttributeDefinitions.EID_IDENTITY_LINK_FRIENDLY_NAME + + " --> Parse basic user info's from that attribute."); + InputStream idlStream = null; + try { + idlStream = new ByteArrayInputStream(Base64Utils.decodeFromString(pvpAttrIDL)); + idlFromPVPAttr = new SimpleIdentityLinkAssertionParser(idlStream).parseIdentityLink(); + parseBasicUserInfosFromIDL(authData, idlFromPVPAttr, includedToGenericAuthData); + + //set identitylink into AuthProcessData + authProcessData.setIdentityLink(idlFromPVPAttr);; + + } catch (EAAFParserException e) { + log.warn("Received IdentityLink is not valid", e); + + } catch (Exception e) { + log.warn("Received IdentityLink is not valid", e); + + } finally { + try { + includedToGenericAuthData.remove(PVPAttributeDefinitions.EID_IDENTITY_LINK_NAME); + if (idlStream != null) + idlStream.close(); + + } catch (IOException e) { + log.warn("Close InputStream FAILED.", e); + + } + } + } + + //if no basic user info's are set yet, parse info's single PVP-Attributes + if (StringUtils.isEmpty(authData.getFamilyName())) { + log.debug("No IdentityLink found or not parseable --> Parse basic user info's from single PVP-Attributes."); + authData.setFamilyName(authProcessData.getGenericDataFromSession(PVPAttributeDefinitions.PRINCIPAL_NAME_NAME, String.class)); + authData.setGivenName(authProcessData.getGenericDataFromSession(PVPAttributeDefinitions.GIVEN_NAME_NAME, String.class)); + authData.setDateOfBirth(authProcessData.getGenericDataFromSession(PVPAttributeDefinitions.BIRTHDATE_NAME, String.class)); + authData.setIdentificationValue(authProcessData.getGenericDataFromSession(PVPAttributeDefinitions.EID_SOURCE_PIN_NAME, String.class)); + authData.setIdentificationType(authProcessData.getGenericDataFromSession(PVPAttributeDefinitions.EID_SOURCE_PIN_TYPE_NAME, String.class)); + + //remove corresponding keys from genericSessionData if exists + includedToGenericAuthData.remove(PVPAttributeDefinitions.PRINCIPAL_NAME_NAME); + includedToGenericAuthData.remove(PVPAttributeDefinitions.GIVEN_NAME_NAME); + includedToGenericAuthData.remove(PVPAttributeDefinitions.BIRTHDATE_NAME); + includedToGenericAuthData.remove(PVPAttributeDefinitions.EID_SOURCE_PIN_NAME); + includedToGenericAuthData.remove(PVPAttributeDefinitions.EID_SOURCE_PIN_TYPE_NAME); + } + + } + + if (authData.getIdentificationType() != null && + !authData.getIdentificationType().equals(EAAFConstants.URN_PREFIX_BASEID)) { + log.trace("IdentificationType is not a baseID --> clear it. "); + authData.setBPK(authData.getIdentificationValue()); + authData.setBPKType(authData.getIdentificationType()); + + authData.setIdentificationValue(null); + authData.setIdentificationType(null); + } + + //#################################################### + //set QAA level + includedToGenericAuthData.remove(PVPAttributeDefinitions.EID_CITIZEN_EIDAS_QAA_LEVEL_NAME); + String currentLoA = null; + if (StringUtils.isNotEmpty(authProcessData.getQAALevel())) + currentLoA = authProcessData.getQAALevel(); + else { + currentLoA = authProcessData.getGenericDataFromSession(PVPAttributeDefinitions.EID_CITIZEN_EIDAS_QAA_LEVEL_NAME, String.class); + if (StringUtils.isNotEmpty(currentLoA)) { + log.debug("Find PVP-Attr '" + PVPAttributeDefinitions.EID_CITIZEN_EIDAS_QAA_LEVEL_FRIENDLY_NAME + "':" + currentLoA + + " --> Parse QAA-Level from that attribute."); + + } + } + if (StringUtils.isNotEmpty(currentLoA)) { + if (currentLoA.startsWith(EAAFConstants.EIDAS_QAA_PREFIX)) { + authData.seteIDASLoA(currentLoA); + + } else + log.info("Only eIDAS LoAs are supported by this implementation"); + + } else { + log.info("No QAA level found. Set to default level " + EAAFConstants.EIDAS_QAA_LOW); + authData.seteIDASLoA(EAAFConstants.EIDAS_QAA_LOW); + + } + + //#################################################### + //set isForeigner flag + //TODO: change to new eIDAS-token attribute identifier + if (authProcessData.getGenericDataFromSession(PVPAttributeDefinitions.EID_STORK_TOKEN_NAME) != null) { + log.debug("Find PVP-Attr: " + PVPAttributeDefinitions.EID_STORK_TOKEN_FRIENDLY_NAME + + " --> Set 'isForeigner' flag to TRUE"); + authData.setForeigner(true); + + } else { + authData.setForeigner(authProcessData.isForeigner()); + + } + + //#################################################### + //set citizen country-code + includedToGenericAuthData.remove(PVPAttributeDefinitions.EID_ISSUING_NATION_NAME); + String pvpCCCAttr = authProcessData.getGenericDataFromSession(PVPAttributeDefinitions.EID_ISSUING_NATION_NAME, String.class); + if (StringUtils.isNotEmpty(pvpCCCAttr)) { + authData.setCiticenCountryCode(pvpCCCAttr); + log.debug("Find PVP-Attr: " + PVPAttributeDefinitions.EID_ISSUING_NATION_FRIENDLY_NAME); + + } else { + if (authData.isForeigner()) { + //TODO!!!! + + } else { + authData.setCiticenCountryCode(basicConfig.getBasicConfiguration( + IConfiguration.CONFIG_PROPS_AUTH_DEFAULT_COUNTRYCODE, + EAAFConstants.COUNTRYCODE_AUSTRIA)); + + } + } + + + //#################################################### + // set bPK and IdentityLink + String pvpbPKValue = getbPKValueFromPVPAttribute(authProcessData); + String pvpbPKTypeAttr = getbPKTypeFromPVPAttribute(authProcessData); + Pair<String, String> pvpEncbPKAttr = getEncryptedbPKFromPVPAttribute(authProcessData, authData, pendingReq.getServiceProviderConfiguration()); + + //check if a unique ID for this citizen exists + if (StringUtils.isEmpty(authData.getIdentificationValue()) && + StringUtils.isEmpty(pvpbPKValue) && StringUtils.isEmpty(authData.getBPK()) && + pvpEncbPKAttr == null) { + log.info("Can not build authData, because moaSession include no bPK, encrypted bPK or baseID"); + throw new EAAFBuilderException("builder.08", new Object[]{"No " + PVPAttributeDefinitions.BPK_FRIENDLY_NAME + + " or " + PVPAttributeDefinitions.EID_SOURCE_PIN_FRIENDLY_NAME + + " or " + PVPAttributeDefinitions.ENC_BPK_LIST_FRIENDLY_NAME}, + "No " + PVPAttributeDefinitions.BPK_FRIENDLY_NAME + + " or " + PVPAttributeDefinitions.EID_SOURCE_PIN_FRIENDLY_NAME + + " or " + PVPAttributeDefinitions.ENC_BPK_LIST_FRIENDLY_NAME); + + } + + // baseID is in MOASesson --> calculate bPK directly + if (StringUtils.isNotEmpty(authData.getIdentificationValue())) { + log.debug("Citizen baseID is in MOASession --> calculate bPK from this."); + Pair<String, String> result = buildOAspecificbPK(pendingReq, authData); + authData.setBPK(result.getFirst()); + authData.setBPKType(result.getSecond()); + + //check if bPK already added to AuthData matches OA + } else if (StringUtils.isNotEmpty(authData.getBPK()) + && matchsReceivedbPKToOnlineApplication(pendingReq.getServiceProviderConfiguration(), authData.getBPKType()) ) { + log.debug("Correct bPK is already included in AuthData."); + + //check if bPK received by PVP-Attribute matches OA + } else if (StringUtils.isNotEmpty(pvpbPKValue) && + matchsReceivedbPKToOnlineApplication(pendingReq.getServiceProviderConfiguration(), pvpbPKTypeAttr)) { + log.debug("Receive correct bPK from PVP-Attribute"); + authData.setBPK(pvpbPKValue); + authData.setBPKType(pvpbPKTypeAttr); + + //check if decrypted bPK exists + } else if (pvpEncbPKAttr != null) { + log.debug("Receive bPK as encrypted bPK and decryption was possible."); + authData.setBPK(pvpEncbPKAttr.getFirst()); + authData.setBPKType(pvpEncbPKAttr.getSecond()); + + //ask SZR to get bPK + } else { + String notValidbPK = authData.getBPK(); + String notValidbPKType = authData.getBPKType(); + if (StringUtils.isEmpty(notValidbPK) && + StringUtils.isEmpty(notValidbPKType)) { + notValidbPK = pvpbPKValue; + notValidbPKType = pvpbPKTypeAttr; + + if (StringUtils.isEmpty(notValidbPK) && + StringUtils.isEmpty(notValidbPKType)) { + log.error("No bPK in MOASession. THIS error should not occur any more."); + throw new NullPointerException("No bPK in MOASession. THIS error should not occur any more."); + } + } + + Pair<String, String> baseIDFromSZR = getbaseIDFromSZR(authData, notValidbPK, notValidbPKType); + if (baseIDFromSZR != null) { + log.info("Receive citizen baseID from SRZ. Authentication can be completed"); + authData.setIdentificationValue(baseIDFromSZR.getFirst()); + authData.setIdentificationType(baseIDFromSZR.getSecond()); + Pair<String, String> result = buildOAspecificbPK(pendingReq, authData); + authData.setBPK(result.getFirst()); + authData.setBPKType(result.getSecond()); + + } else { + log.warn("Can not build authData, because moaSession include no valid bPK, encrypted bPK or baseID"); + throw new EAAFBuilderException("builder.08", new Object[]{"No valid " + PVPAttributeDefinitions.BPK_FRIENDLY_NAME + + " or " + PVPAttributeDefinitions.EID_SOURCE_PIN_FRIENDLY_NAME + + " or " + PVPAttributeDefinitions.ENC_BPK_LIST_FRIENDLY_NAME}, + "No valid " + PVPAttributeDefinitions.BPK_FRIENDLY_NAME + + " or " + PVPAttributeDefinitions.EID_SOURCE_PIN_FRIENDLY_NAME + + " or " + PVPAttributeDefinitions.ENC_BPK_LIST_FRIENDLY_NAME); + + } + } + + //build IdentityLink + if (authProcessData.getIdentityLink() != null) + authData.setIdentityLink(buildOAspecificIdentityLink( + pendingReq.getServiceProviderConfiguration(), + authProcessData.getIdentityLink(), + authData.getBPK(), + authData.getBPKType())); + else + log.info("Can NOT set IdentityLink. Msg: No IdentityLink found"); + + } + + //extract a encrypted bPK from PVP attrobute + protected abstract Pair<String, String> getEncryptedbPKFromPVPAttribute(IAuthProcessDataContainer authProcessDataContainer, + AuthenticationData authData, ISPConfiguration spConfig) throws EAAFBuilderException; + + //request baseId from SRZ + protected abstract Pair<String, String> getbaseIDFromSZR(AuthenticationData authData, String notValidbPK, + String notValidbPKType); + + + protected Pair<String, String> buildOAspecificbPK(IRequest pendingReq, AuthenticationData authData) throws EAAFBuilderException { + ISPConfiguration oaParam = pendingReq.getServiceProviderConfiguration(); + + String baseID = authData.getIdentificationValue(); + String baseIDType = authData.getIdentificationType(); + Pair<String, String> sectorSpecId = null; + + if (EAAFConstants.URN_PREFIX_BASEID.equals(baseIDType)) { + //SAML1 legacy target parameter work-around + String spTargetId = oaParam.getAreaSpecificTargetIdentifier(); + log.debug("Use OA target identifier '" + spTargetId + "' from configuration"); + + //calculate sector specific unique identifier + sectorSpecId = new BPKBuilder().generateAreaSpecificPersonIdentifier(baseID, spTargetId); + + } else { + log.error("!!!baseID-element does not include a baseID. This should not be happen any more!!!"); + sectorSpecId = Pair.newInstance(baseID, baseIDType); + + } + + log.trace("Authenticate user with bPK:" + sectorSpecId.getFirst() + " Type:" + sectorSpecId.getSecond()); + return sectorSpecId; + + } + + protected IIdentityLink buildOAspecificIdentityLink(ISPConfiguration spConfig, IIdentityLink idl, String bPK, String bPKType) throws EAAFConfigurationException, XPathException, DOMException, EAAFParserException { + if (spConfig.hasBaseIdTransferRestriction()) { + log.debug("SP: " + spConfig.getUniqueIdentifier() + " has baseId transfer restriction. Remove baseId from IDL ..."); + Element idlassertion = idl.getSamlAssertion(); + //set bpk/wpbk; + Node prIdentification = XPathUtils.selectSingleNode(idlassertion, SimpleIdentityLinkAssertionParser.PERSON_IDENT_VALUE_XPATH); + prIdentification.getFirstChild().setNodeValue(bPK); + //set bkp/wpbk type + Node prIdentificationType = XPathUtils.selectSingleNode(idlassertion, SimpleIdentityLinkAssertionParser.PERSON_IDENT_TYPE_XPATH); + prIdentificationType.getFirstChild().setNodeValue(bPKType); + + SimpleIdentityLinkAssertionParser idlparser = new SimpleIdentityLinkAssertionParser(idlassertion); + return idlparser.parseIdentityLink(); + + } else + return idl; + + } + + /** + * Check a bPK-Type against a Service-Provider configuration <br> + * If bPK-Type is <code>null</code> the result is <code>false</code>. + * + * @param oaParam Service-Provider configuration, never null + * @param bPKType bPK-Type to check + * @return true, if bPK-Type matchs to Service-Provider configuration, otherwise false + */ + private boolean matchsReceivedbPKToOnlineApplication(ISPConfiguration oaParam, String bPKType) { + return oaParam.getAreaSpecificTargetIdentifier().equals(bPKType); + + } + + /** + * Parse information from an IdentityLink into AuthData object + * + * @param authData + * @param identityLink + * @param includedGenericSessionData + */ + private void parseBasicUserInfosFromIDL(AuthenticationData authData, IIdentityLink identityLink, Collection<String> includedGenericSessionData) { + authData.setIdentificationValue(identityLink.getIdentificationValue()); + authData.setIdentificationType(identityLink.getIdentificationType()); + + authData.setGivenName(identityLink.getGivenName()); + authData.setFamilyName(identityLink.getFamilyName()); + authData.setDateOfBirth(identityLink.getDateOfBirth()); + + //remove corresponding keys from genericSessionData if exists + includedGenericSessionData.remove(PVPAttributeDefinitions.PRINCIPAL_NAME_NAME); + includedGenericSessionData.remove(PVPAttributeDefinitions.GIVEN_NAME_NAME); + includedGenericSessionData.remove(PVPAttributeDefinitions.BIRTHDATE_NAME); + includedGenericSessionData.remove(PVPAttributeDefinitions.EID_SOURCE_PIN_NAME); + includedGenericSessionData.remove(PVPAttributeDefinitions.EID_SOURCE_PIN_TYPE_NAME); + + } + + /** + * Get bPK from PVP Attribute 'BPK_NAME', which could be exist in + * MOASession as 'GenericData' <br> <pre><code>session.getGenericDataFromSession(PVPConstants.BPK_NAME, String.class)</code></pre> + * + * @param session MOASession, but never null + * @return bPK, which was received by PVP-Attribute, or <code>null</code> if no attribute exists + */ + private String getbPKValueFromPVPAttribute(IAuthProcessDataContainer session) { + String pvpbPKValueAttr = session.getGenericDataFromSession(PVPAttributeDefinitions.BPK_NAME, String.class); + if (StringUtils.isNotEmpty(pvpbPKValueAttr)) { + + //fix a wrong bPK-value prefix, which was used in some PVP Standardportal implementations + if (pvpbPKValueAttr.startsWith("bPK:")) { + log.warn("Attribute " + PVPAttributeDefinitions.BPK_NAME + + " contains a not standardize prefix! Staring attribute value correction process ..."); + pvpbPKValueAttr = pvpbPKValueAttr.substring("bPK:".length()); + + } + + String[] spitted = pvpbPKValueAttr.split(":"); + if (spitted.length != 2) { + log.warn("Attribute " + PVPAttributeDefinitions.BPK_NAME + " has a wrong encoding and can NOT be USED!" + + " Value:" + pvpbPKValueAttr); + return null; + + } + log.debug("Find PVP-Attr: " + PVPAttributeDefinitions.BPK_FRIENDLY_NAME); + return spitted[1]; + + } + + return null; + } + + /** + * Get bPK-Type from PVP Attribute 'EID_SECTOR_FOR_IDENTIFIER_NAME', which could be exist in + * MOASession as 'GenericData' <br> <pre><code>session.getGenericDataFromSession(PVPConstants.EID_SECTOR_FOR_IDENTIFIER_NAME, String.class)</code></pre> + * + * @param session MOASession, but never null + * @return bPKType, which was received by PVP-Attribute, or <code>null</code> if no attribute exists + */ + private String getbPKTypeFromPVPAttribute(IAuthProcessDataContainer session) { + String pvpbPKTypeAttr = session.getGenericDataFromSession(PVPAttributeDefinitions.EID_SECTOR_FOR_IDENTIFIER_NAME, String.class); + if (StringUtils.isNotEmpty(pvpbPKTypeAttr)) { + + //fix a wrong bPK-Type encoding, which was used in some PVP Standardportal implementations + if (pvpbPKTypeAttr.startsWith(EAAFConstants.URN_PREFIX_CDID) && + !pvpbPKTypeAttr.substring(EAAFConstants.URN_PREFIX_CDID.length(), + EAAFConstants.URN_PREFIX_CDID.length() + 1).equals("+")) { + log.warn("Receive uncorrect encoded bBKType attribute " + pvpbPKTypeAttr + " Starting attribute value correction ... "); + pvpbPKTypeAttr = EAAFConstants.URN_PREFIX_CDID + "+" + pvpbPKTypeAttr.substring(EAAFConstants.URN_PREFIX_CDID.length() + 1); + + } + log.debug("Find PVP-Attr: " + PVPAttributeDefinitions.EID_SECTOR_FOR_IDENTIFIER_FRIENDLY_NAME); + return pvpbPKTypeAttr; + } + + return null; + + + /* + * INFO: This code could be used to extract the bPKType from 'PVPConstants.BPK_NAME', + * because the prefix of BPK_NAME attribute contains the postfix of the bPKType + * + * Now, all PVP Standardportals should be able to send 'EID_SECTOR_FOR_IDENTIFIER' + * PVP attributes + */ +// String pvpbPKValueAttr = session.getGenericDataFromSession(PVPConstants.BPK_NAME, String.class); +// String[] spitted = pvpbPKValueAttr.split(":"); +// if (MiscUtil.isEmpty(authData.getBPKType())) { +// Logger.debug("PVP assertion contains NO bPK/wbPK target attribute. " + +// "Starting target extraction from bPK/wbPK prefix ..."); +// //exract bPK/wbPK type from bpk attribute value prefix if type is +// //not transmitted as single attribute +// Pattern pattern = Pattern.compile("[a-zA-Z]{2}(-[a-zA-Z]+)?"); +// Matcher matcher = pattern.matcher(spitted[0]); +// if (matcher.matches()) { +// //find public service bPK +// authData.setBPKType(Constants.URN_PREFIX_CDID + "+" + spitted[0]); +// Logger.debug("Found bPK prefix. Set target to " + authData.getBPKType()); +// +// } else { +// //find business service wbPK +// authData.setBPKType(Constants.URN_PREFIX_WBPK+ "+" + spitted[0]); +// Logger.debug("Found wbPK prefix. Set target to " + authData.getBPKType()); +// +// } +// } + + } + +} diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/builder/BPKBuilder.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/builder/BPKBuilder.java new file mode 100644 index 00000000..62a57dd1 --- /dev/null +++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/builder/BPKBuilder.java @@ -0,0 +1,302 @@ +/******************************************************************************* + * Copyright 2014 Federal Chancellery Austria + * MOA-ID has been developed in a cooperation between BRZ, the Federal + * Chancellery Austria - ICT staff unit, and Graz University of Technology. + * + * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by + * the European Commission - subsequent versions of the EUPL (the "Licence"); + * You may not use this work except in compliance with the Licence. + * You may obtain a copy of the Licence at: + * http://www.osor.eu/eupl/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the Licence is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Licence for the specific language governing permissions and + * limitations under the Licence. + * + * 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. + ******************************************************************************/ +/* + * Copyright 2003 Federal Chancellery Austria + * MOA-ID has been developed in a cooperation between BRZ, the Federal + * Chancellery Austria - ICT staff unit, and Graz University of Technology. + * + * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by + * the European Commission - subsequent versions of the EUPL (the "Licence"); + * You may not use this work except in compliance with the Licence. + * You may obtain a copy of the Licence at: + * http://www.osor.eu/eupl/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the Licence is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Licence for the specific language governing permissions and + * limitations under the Licence. + * + * 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.gv.egiz.eaaf.core.impl.idp.auth.builder; + +import java.security.InvalidKeyException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.text.SimpleDateFormat; +import java.util.Date; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.Base64Utils; + +import at.gv.egiz.eaaf.core.api.data.EAAFConstants; +import at.gv.egiz.eaaf.core.exceptions.EAAFBuilderException; +import at.gv.egiz.eaaf.core.impl.data.Pair; + +/** + * Builder for the bPK, as defined in + * <code>"Ableitung f¨r die bereichsspezifische Personenkennzeichnung"</code> + * version <code>1.0.1</code> from <code>"reference.e-government.gv.at"</code>. + * + */ +public class BPKBuilder { + private static final Logger log = LoggerFactory.getLogger(BPKBuilder.class); + + /** + * Calculates an area specific unique person-identifier from a baseID + * + * @param baseID baseId from user but never null + * @param targetIdentifier target identifier for area specific identifier calculation but never null + * @return Pair<unique person identifier for this target, targetArea> but never null + * @throws EAAFBuilderException if some input data are not valid + */ + public Pair<String, String> generateAreaSpecificPersonIdentifier(String baseID, String targetIdentifier) throws EAAFBuilderException { + return generateAreaSpecificPersonIdentifier(baseID, EAAFConstants.URN_PREFIX_BASEID, targetIdentifier); + + } + + /** + * Calculates an area specific unique person-identifier from an unique identifier with a specific type + * + * @param baseID baseId from user but never null + * @param baseIdType Type of the baseID but never null + * @param targetIdentifier target identifier for area specific identifier calculation but never null + * @return Pair<unique person identifier for this target, targetArea> but never null + * @throws EAAFBuilderException if some input data are not valid + */ + public Pair<String, String> generateAreaSpecificPersonIdentifier(String baseID, String baseIdType, String targetIdentifier) throws EAAFBuilderException{ + if (StringUtils.isEmpty(baseID)) + throw new EAAFBuilderException("builder.00", new Object[]{"baseID is empty or null"}, + "BaseId is empty or null"); + + if (StringUtils.isEmpty(baseIdType)) + throw new EAAFBuilderException("builder.00", new Object[]{"the type of baseID is empty or null"}, + "Type of baseId is empty or null"); + + if (StringUtils.isEmpty(targetIdentifier)) + throw new EAAFBuilderException("builder.00", new Object[]{"SP specific target identifier is empty or null"}, + "SP specific target identifier is empty or null"); + + if (baseIdType.equals(EAAFConstants.URN_PREFIX_BASEID)) { + log.trace("Find baseID. Starting unique identifier caluclation for this target"); + + if (targetIdentifier.startsWith(EAAFConstants.URN_PREFIX_CDID) || + targetIdentifier.startsWith(EAAFConstants.URN_PREFIX_WBPK)) { + log.trace("Calculate bPK, wbPK, or STORK identifier for target: " + targetIdentifier); + return Pair.newInstance(calculatebPKwbPK(baseID + "+" + targetIdentifier), targetIdentifier); + + } else if (targetIdentifier.startsWith(EAAFConstants.URN_PREFIX_EIDAS)) { + log.trace("Calculate eIDAS identifier for target: " + targetIdentifier); + String[] splittedTarget = targetIdentifier.split("\\+"); + String cititzenCountryCode = splittedTarget[1]; + String eIDASOutboundCountry = splittedTarget[2]; + + if (cititzenCountryCode.equalsIgnoreCase(eIDASOutboundCountry)) { + log.warn("Suspect configuration FOUND!!! CitizenCountry equals DestinationCountry"); + + } + return buildeIDASIdentifer(baseID, baseIdType, cititzenCountryCode, eIDASOutboundCountry); + + + } else + throw new EAAFBuilderException("builder.00", + new Object[]{"Target identifier: " + targetIdentifier + " is NOT allowed or unknown"}, + "Target identifier: " + targetIdentifier + " is NOT allowed or unknown"); + + } else { + log.trace("BaseID is not of type " + EAAFConstants.URN_PREFIX_BASEID + ". Check type against requested target ..."); + if (baseIdType.equals(targetIdentifier)) { + log.debug("Unique identifier is already area specific. Is nothing todo"); + return Pair.newInstance(baseID, targetIdentifier); + + } else { + log.warn("Get unique identifier for target: " + baseIdType + " but target: " + targetIdentifier + " is required!"); + throw new EAAFBuilderException("builder.00", + new Object[]{"Get unique identifier for target: " + baseIdType + " but target: " + targetIdentifier + " is required"}, + "Get unique identifier for target: " + baseIdType + " but target: " + targetIdentifier + " is required"); + + } + } + } + + + /** + * Builds the eIDAS from the given parameters. + * + * @param baseID baseID of the citizen + * @param baseIDType Type of the baseID + * @param sourceCountry CountryCode of that country, which build the eIDAs ID + * @param destinationCountry CountryCode of that country, which receives the eIDAs ID + * + * @return Pair<eIDAs, bPKType> in a BASE64 encoding + * @throws EAAFBuilderException if some input data are not valid + */ + private Pair<String, String> buildeIDASIdentifer(String baseID, String baseIDType, String sourceCountry, String destinationCountry) + throws EAAFBuilderException { + String bPK = null; + String bPKType = null; + + // check if we have been called by public sector application + if (baseIDType.startsWith(EAAFConstants.URN_PREFIX_BASEID)) { + bPKType = EAAFConstants.URN_PREFIX_EIDAS + "+" + sourceCountry + "+" + destinationCountry; + log.debug("Building eIDAS identification from: [identValue]+" + bPKType); + bPK = calculatebPKwbPK(baseID + "+" + bPKType); + + } else { // if not, sector identification value is already calculated by BKU + log.debug("eIDAS eIdentifier already provided by BKU"); + bPK = baseID; + } + + if ((StringUtils.isEmpty(bPK) || + StringUtils.isEmpty(sourceCountry) || + StringUtils.isEmpty(destinationCountry))) { + throw new EAAFBuilderException("builder.00", + new Object[]{"eIDAS-ID", "Unvollständige Parameterangaben: identificationValue=" + + bPK + ", Zielland=" + destinationCountry + ", Ursprungsland=" + sourceCountry} + ,"eIDAS-ID: Unvollständige Parameterangaben: identificationValue=" + + bPK + ", Zielland=" + destinationCountry + ", Ursprungsland=" + sourceCountry); + } + + log.debug("Building eIDAS identification from: " + sourceCountry+"/"+destinationCountry+"/" + "[identValue]"); + String eIdentifier = sourceCountry + "/" + destinationCountry + "/" + bPK; + + return Pair.newInstance(eIdentifier, bPKType); + } + + public static String encryptBPK(String bpk, String target, PublicKey publicKey) throws EAAFBuilderException { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + if (target.startsWith(EAAFConstants.URN_PREFIX_CDID + "+")) + target = target.substring((EAAFConstants.URN_PREFIX_CDID + "+").length()); + + String input = "V1::urn:publicid:gv.at:cdid+" + target + "::" + + bpk + "::" + + sdf.format(new Date()); + System.out.println(input); + byte[] result; + try { + byte[] inputBytes = input.getBytes("ISO-8859-1"); + result = encrypt(inputBytes, publicKey); + return new String(Base64Utils.encode(result), "ISO-8859-1").replaceAll("\r\n", ""); + //return new String(Base64Utils.encode(result, "ISO-8859-1")).replaceAll("\r\n", ""); + + + } catch (Exception e) { + throw new EAAFBuilderException("bPK encryption FAILED", null, + e.getMessage(), e); + + } + } + + public static String decryptBPK(String encryptedBpk, String target, PrivateKey privateKey) throws EAAFBuilderException { + String decryptedString; + try { + //byte[] encryptedBytes = Base64Utils.decode(encryptedBpk, false, "ISO-8859-1"); + byte[] encryptedBytes = Base64Utils.decode(encryptedBpk.getBytes("ISO-8859-1")); + byte[] decryptedBytes = decrypt(encryptedBytes, privateKey); + decryptedString = new String(decryptedBytes, "ISO-8859-1"); + + } catch (Exception e) { + throw new EAAFBuilderException("bPK decryption FAILED", null, + e.getMessage(), e); + + } + + String tmp = decryptedString.substring(decryptedString.indexOf('+') + 1); + String sector = tmp.substring(0, tmp.indexOf("::")); + tmp = tmp.substring(tmp.indexOf("::") + 2); + String bPK = tmp.substring(0, tmp.indexOf("::")); + + if (target.startsWith(EAAFConstants.URN_PREFIX_CDID + "+")) + target = target.substring((EAAFConstants.URN_PREFIX_CDID + "+").length()); + + if (target.equals(sector)) + return bPK; + + else { + log.error("Decrypted bPK does not match to request bPK target."); + return null; + } + } + + private String calculatebPKwbPK(String basisbegriff) throws EAAFBuilderException { + try { + MessageDigest md = MessageDigest.getInstance("SHA-1"); + byte[] hash = md.digest(basisbegriff.getBytes("ISO-8859-1")); + String hashBase64 = new String(Base64Utils.encode(hash), "ISO-8859-1").replaceAll("\r\n", ""); //Base64Utils.encode(hash); + return hashBase64; + + } catch (Exception ex) { + throw new EAAFBuilderException("builder.00", new Object[]{"bPK/wbPK", ex.toString()}, + ex.getMessage(), ex); + + } + + } + + private static byte[] encrypt(byte[] inputBytes, PublicKey publicKey) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException { + byte[] result; + Cipher cipher = null; + try { + cipher = Cipher.getInstance("RSA/ECB/OAEPPadding"); // try with bouncycastle + + } catch(NoSuchAlgorithmException e) { + cipher = Cipher.getInstance("RSA/ECB/OAEP"); // try with iaik provider + } + cipher.init(Cipher.ENCRYPT_MODE, publicKey); + result = cipher.doFinal(inputBytes); + + return result; + } + + private static byte[] decrypt(byte[] encryptedBytes, PrivateKey privateKey) + throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException{ + byte[] result; + Cipher cipher = null; + try { + cipher = Cipher.getInstance("RSA/ECB/OAEPPadding"); // try with bouncycastle + + } catch(NoSuchAlgorithmException e) { + cipher = Cipher.getInstance("RSA/ECB/OAEP"); // try with iaik provider + + } + cipher.init(Cipher.DECRYPT_MODE, privateKey); + result = cipher.doFinal(encryptedBytes); + return result; + + } +} |