/* * 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. */ package at.gv.egovernment.moa.id.auth.modules.bkamobileauthtests.tasks; import java.io.ByteArrayInputStream; import java.io.IOException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.spec.InvalidKeySpecException; import java.security.spec.KeySpec; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.SecretKeySpec; import javax.security.cert.CertificateException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.bouncycastle.asn1.cms.CMSObjectIdentifiers; import org.bouncycastle.cms.CMSSignedData; import org.joda.time.DateTime; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import com.google.gson.JsonParser; import at.gv.egiz.eaaf.core.api.IRequest; import at.gv.egiz.eaaf.core.api.idp.auth.data.IIdentityLink; import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext; import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException; import at.gv.egiz.eaaf.core.impl.idp.auth.modules.AbstractAuthServletTask; import at.gv.egovernment.moa.id.auth.data.AuthenticationSessionWrapper; import at.gv.egovernment.moa.id.auth.invoke.SignatureVerificationInvoker; import at.gv.egovernment.moa.id.auth.parser.IdentityLinkAssertionParser; import at.gv.egovernment.moa.id.commons.api.AuthConfiguration; import at.gv.egovernment.moa.id.commons.api.exceptions.MOAIDException; import at.gv.egovernment.moa.id.protocols.pvp2x.PVPConstants; import at.gv.egovernment.moa.logging.Logger; import at.gv.egovernment.moa.spss.api.cmsverify.VerifyCMSSignatureRequest; import at.gv.egovernment.moa.spss.api.cmsverify.VerifyCMSSignatureResponse; import at.gv.egovernment.moa.spss.api.cmsverify.VerifyCMSSignatureResponseElement; import at.gv.egovernment.moa.spss.api.common.SignerInfo; import at.gv.egovernment.moa.spss.api.impl.VerifyCMSSignatureRequestImpl; import at.gv.egovernment.moa.util.Base64Utils; import at.gv.egovernment.moa.util.MiscUtil; /** * @author tlenz * */ @Component("FirstBKAMobileAuthTask") public class FirstBKAMobileAuthTask extends AbstractAuthServletTask { private static final String CONF_MOASPSS_TRUSTPROFILE = "modules.bkamobileAuth.verify.trustprofile"; private static final String CONF_SIGNING_TIME_JITTER = "modules.bkamobileAuth.verify.time.jitter"; private static final String CONF_EID_TOKEN_ENCRYPTION_KEY = "modules.bkamobileAuth.eIDtoken.encryption.pass"; private static final String EIDCONTAINER_KEY_SALT = "salt"; private static final String EIDCONTAINER_KEY_IV = "iv"; private static final String EIDCONTAINER_EID = "eid"; private static final String EIDCONTAINER_KEY_IDL = "idl"; private static final String EIDCONTAINER_KEY_BINDINGCERT = "cert"; public static final String REQ_PARAM_eID_BLOW = "eidToken"; @Autowired(required=true) private AuthConfiguration authConfig; /* (non-Javadoc) * @see at.gv.egovernment.moa.id.auth.modules.AbstractAuthServletTask#execute(at.gv.egovernment.moa.id.process.api.ExecutionContext, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) */ @Override public void execute(ExecutionContext executionContext, HttpServletRequest request, HttpServletResponse response) throws TaskExecutionException { try { String eIDBlobRawB64 = request.getParameter(REQ_PARAM_eID_BLOW); if (MiscUtil.isEmpty(eIDBlobRawB64)) { //TODO: add dummy-auth functionality Logger.warn("NO eID data blob included!"); throw new MOAIDException("NO eID data blob included!", null); } parseDemoValuesIntoMOASession(pendingReq, eIDBlobRawB64); } catch (MOAIDException e) { throw new TaskExecutionException(pendingReq, e.getMessage(), e); } catch (Exception e) { throw new TaskExecutionException(pendingReq, e.getMessage(), e); } //Logger.info("Redirect to Second BKA Mobile Auth task"); //performRedirectToItself(pendingReq, response, GeneralProcessEngineSignalController.ENDPOINT_GENERIC); } /** * @param pendingReq * @param moaSession * @param eIDBlobRaw * @throws MOAIDException * @throws IOException */ private void parseDemoValuesIntoMOASession(IRequest pendingReq, String eIDBlobRawB64) throws MOAIDException, IOException { Logger.debug("Check eID blob signature ... "); byte[] eIDBlobRaw = Base64Utils.decode(eIDBlobRawB64.trim(), false); VerifyCMSSignatureResponse cmsResp = SignatureVerificationInvoker.getInstance().verifyCMSSignature( createCMSVerificationReq(eIDBlobRaw)); if (cmsResp.getResponseElements().isEmpty()) { Logger.warn("No CMS signature-verification response"); throw new MOAIDException("Signature verification FAILED: No response", null); } VerifyCMSSignatureResponseElement sigVerifyResp = (VerifyCMSSignatureResponseElement) cmsResp.getResponseElements().get(0); analyseCMSSignatureVerificationResponse(sigVerifyResp); Logger.info("eID blob signature is VALID!"); byte[] decRawEidBlob = null; byte[] signedData = null; try { Logger.debug("Starting eID information extraction ... "); CMSSignedData cmsContent = new CMSSignedData(eIDBlobRaw); signedData = (byte[])cmsContent.getSignedContent().getContent(); if (!cmsContent.getSignedContent().getContentType().equals(CMSObjectIdentifiers.data)) { Logger.warn("Signature contains NO 'data' OID 1.2.840.113549.1.7.1"); throw new MOAIDException("Signature contains NO 'data' OID 1.2.840.113549.1.7.1", null); } if (signedData == null) { Logger.warn("CMS SignedData is empty or null"); throw new MOAIDException("CMS SignedData is empty or null", null); } Logger.info("Signed content extracted"); Logger.debug("Starting signed content decryption ... "); JsonParser parser = new JsonParser(); JsonObject signedDataJson = (JsonObject) parser.parse(new String(signedData, "UTF-8")); byte[] salt = Base64Utils.decode(signedDataJson.get(EIDCONTAINER_KEY_SALT).getAsString(), false); byte[] ivraw = Base64Utils.decode(signedDataJson.get(EIDCONTAINER_KEY_IV).getAsString(), false); byte[] encRawEidBlob = Base64Utils.decode(signedDataJson.get(EIDCONTAINER_EID).getAsString(), false); SecretKey seckey = generateDecryptionKey(salt); IvParameterSpec iv = new IvParameterSpec(ivraw); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.DECRYPT_MODE, seckey, iv); decRawEidBlob = cipher.doFinal(encRawEidBlob); Logger.info("eID data decryption completed"); Logger.debug("Starting eID-blob parsing ..."); JsonObject eIDBlobJson = (JsonObject) parser.parse(new String(decRawEidBlob, "UTF-8")); String idlB64 = eIDBlobJson.get( EIDCONTAINER_KEY_IDL).getAsString(); String bindingCertB64 = eIDBlobJson.get( EIDCONTAINER_KEY_BINDINGCERT).getAsString(); javax.security.cert.X509Certificate bindingCert = javax.security.cert.X509Certificate.getInstance(Base64Utils.decode(bindingCertB64, false)); if (!sigVerifyResp.getSignerInfo().getSignerCertificate().equals(bindingCert)) { Logger.error("eID-blob signing certificate DOES NOT match to binding certificate included in eID blob!"); Logger.info("BindingCert: " + bindingCert.toString()); Logger.info("SigningCert: " + sigVerifyResp.getSignerInfo().getSignerCertificate().toString()); throw new MOAIDException("eID-blob signing certificate DOES NOT match to binding certificate included in eID blob!", null); } Logger.info("eID-blob parsing completed"); Logger.debug("Parse eID information into MOA-Session ..."); byte[] rawIDL = Base64Utils.decode(idlB64, false); IIdentityLink identityLink = new IdentityLinkAssertionParser(new ByteArrayInputStream(rawIDL)).parseIdentityLink(); AuthenticationSessionWrapper moaSession = pendingReq.getSessionData(AuthenticationSessionWrapper.class); moaSession.setIdentityLink(identityLink); moaSession.setUseMandates(false); moaSession.setForeigner(false); moaSession.setBkuURL("http://egiz.gv.at/BKA_MobileAuthTest"); moaSession.setQAALevel(PVPConstants.EIDAS_QAA_SUBSTANTIAL); Logger.info("Session Restore completed"); } catch (MOAIDException e) { throw e; } catch (JsonParseException e) { if (decRawEidBlob != null) Logger.error("eID-blob parse error! blob: " + new String(decRawEidBlob, "UTF-8"), e); if (signedData != null) Logger.error("eID-blob parse error! blob: " + new String(signedData, "UTF-8"), e); if (decRawEidBlob == null && signedData == null) Logger.error("eID-blob parse error!", e); throw new MOAIDException("eID-blob parse error!", null); } catch (org.bouncycastle.cms.CMSException e) { Logger.error("Can not parse CMS signature.", e); throw new MOAIDException("Can not parse CMS signature.", null, e); } catch (InvalidAlgorithmParameterException| NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) { Logger.error("Can not decrypte eID data.", e); throw new MOAIDException("Can not decrypte eID data", null, e); } catch (CertificateException e) { Logger.error("Can not extract mobile-app binding-certificate from eID blob.", e); throw new MOAIDException("Can not extract mobile-app binding-certificate from eID blob.", null, e); } finally { } } private SecretKey generateDecryptionKey(byte[] salt) throws MOAIDException { String decryptionPassPhrase = authConfig.getBasicConfiguration(CONF_EID_TOKEN_ENCRYPTION_KEY, "DEFAULTPASSWORD"); try { SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); KeySpec spec = new PBEKeySpec(decryptionPassPhrase.toCharArray(), salt, 2000, 128); SecretKey derivedKey = factory.generateSecret(spec); SecretKeySpec symKeySpec = new SecretKeySpec(derivedKey.getEncoded(), "AES"); return symKeySpec; } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { Logger.error("Mobile-Auth Module has an internal errror.", e); throw new MOAIDException("Mobile-Auth Module has an internal errror.", null, e); } } /** * @throws MOAIDException * */ private void analyseCMSSignatureVerificationResponse(VerifyCMSSignatureResponseElement verifySigResult) throws MOAIDException { //validate CMS signature verification response if (verifySigResult.getSignatureCheck().getCode() != 0) { Logger.warn("CMS signature verification FAILED with StatusCode: " + verifySigResult.getSignatureCheck().getCode()); throw new MOAIDException("CMS signature verification FAILED with StatusCode: " + verifySigResult.getSignatureCheck().getCode(), null); } if (verifySigResult.getCertificateCheck().getCode() != 0) { Logger.warn("CMS certificate verification FAILED with StatusCode: " + verifySigResult.getCertificateCheck().getCode()); throw new MOAIDException("CMS certificate verification FAILED with StatusCode: " + verifySigResult.getCertificateCheck().getCode(), null); } SignerInfo signerInfos = verifySigResult.getSignerInfo(); DateTime date = new DateTime(signerInfos.getSigningTime().getTime()); Integer signingTimeJitter = Integer.valueOf(authConfig.getBasicConfiguration(CONF_SIGNING_TIME_JITTER, "5")); if (date.plusMinutes(signingTimeJitter).isBeforeNow()) { Logger.warn("CMS signature-time is before: " + date.plusMinutes(signingTimeJitter)); throw new MOAIDException("CMS signature-time is before: " + date.plusMinutes(signingTimeJitter), null); } } private VerifyCMSSignatureRequest createCMSVerificationReq(byte[] eIDBlobRaw) { VerifyCMSSignatureRequestImpl cmsSigVerifyReq = new VerifyCMSSignatureRequestImpl(); cmsSigVerifyReq.setSignatories(VerifyCMSSignatureRequestImpl.ALL_SIGNATORIES); cmsSigVerifyReq.setExtended(false); cmsSigVerifyReq.setPDF(false); cmsSigVerifyReq.setTrustProfileId(authConfig.getBasicConfiguration(CONF_MOASPSS_TRUSTPROFILE, "!!NOT SET!!!")); cmsSigVerifyReq.setCMSSignature(new ByteArrayInputStream(eIDBlobRaw)); return cmsSigVerifyReq; } }