/* * 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.ssotransfer.task; import java.io.BufferedReader; import java.io.IOException; import java.io.PrintWriter; import java.math.BigInteger; import java.security.MessageDigest; import javax.crypto.Cipher; import javax.crypto.spec.DHPublicKeySpec; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.BooleanUtils; import org.joda.time.DateTime; import org.opensaml.saml2.core.Response; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.google.common.net.MediaType; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import at.gv.egiz.eaaf.core.api.gui.IGUIFormBuilder; 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.egiz.eaaf.core.impl.utils.HTTPUtils; import at.gv.egiz.eaaf.modules.pvp2.sp.impl.utils.AssertionAttributeExtractor; import at.gv.egovernment.moa.id.auth.data.AuthenticationSessionWrapper; import at.gv.egovernment.moa.id.auth.modules.ssotransfer.SSOTransferConstants; import at.gv.egovernment.moa.id.auth.modules.ssotransfer.data.SSOTransferContainer; import at.gv.egovernment.moa.id.auth.modules.ssotransfer.utils.GUIUtils; import at.gv.egovernment.moa.id.auth.modules.ssotransfer.utils.SSOContainerUtils; import at.gv.egovernment.moa.id.commons.api.exceptions.MOAIDException; import at.gv.egovernment.moa.id.config.auth.AuthConfigurationProviderFactory; import at.gv.egovernment.moa.id.protocols.pvp2x.PVPConstants; import at.gv.egovernment.moa.logging.Logger; import at.gv.egovernment.moa.util.Base64Utils; import at.gv.egovernment.moa.util.MiscUtil; import iaik.x509.X509Certificate; /** * @author tlenz * */ @Component("RestoreSSOSessionTask") public class RestoreSSOSessionTask extends AbstractAuthServletTask { @Autowired SSOContainerUtils ssoTransferUtils; @Autowired IGUIFormBuilder guiBuilder; /* (non-Javadoc) * @see at.gv.egovernment.moa.id.process.springweb.MoaIdTask#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 { Logger.debug("Receive " + this.getClass().getName() + " request"); StringBuffer sb = new StringBuffer(); String receivedPostMessage = null; String authURL =null; try { BufferedReader reader = request.getReader(); String line = null; while ((line = reader.readLine()) != null) { sb.append(line); } receivedPostMessage = sb.toString(); } catch (IOException e) { Logger.warn("Received POST-message produce an ERROR.", e); } String nonce = pendingReq.getRawData(SSOTransferConstants.PENDINGREQ_NONCE, String.class); SSOTransferContainer container = pendingReq.getRawData( SSOTransferConstants.PENDINGREQ_DH, SSOTransferContainer.class); if (container == null) { throw new TaskExecutionException(pendingReq, "NO DH-Params in pending-request", new MOAIDException("NO DH-Params in pending-request", null)); } if (MiscUtil.isNotEmpty(receivedPostMessage)) { Logger.debug("Receive POST-Message data. Start data-validation process ... "); JsonObject responseMsg = new JsonObject(); try { Logger.debug("Unformated Msg:" + receivedPostMessage); JsonParser parser = new JsonParser(); JsonObject receivedData = (JsonObject) parser.parse(sb.toString()); JsonObject receivedSession = receivedData.get( SSOTransferConstants.SSOCONTAINER_KEY_SESSION).getAsJsonObject(); Logger.debug("Received Session-Object:"+ receivedSession.toString()); String signature = receivedData.get( SSOTransferConstants.SSOCONTAINER_KEY_SIGNATURE).getAsString(); String mobilePubKeyBase64 = receivedData.get( SSOTransferConstants.SSOCONTAINER_KEY_DH_PUBKEY).getAsString(); String respNonce = receivedSession.get( SSOTransferConstants.PENDINGREQ_NONCE).getAsString(); String encSessionBlobBase64 = receivedSession.get( SSOTransferConstants.SSOCONTAINER_KEY_BLOB).getAsString(); Logger.debug("Receive PubKey:" +mobilePubKeyBase64 + " | SessionBlob:" + encSessionBlobBase64 + " | Nonce:" + respNonce + " | Signature:" + signature + " | SignedData:" + receivedSession.toString()); if (MiscUtil.isEmpty(respNonce) || !respNonce.equals(nonce)) { Logger.warn("Received 'nonce':" + respNonce + " does not match to stored 'nonce':" + nonce); throw new TaskExecutionException(pendingReq, "Received 'nonce':" + respNonce + " does not match to stored 'nonce':" + nonce, new MOAIDException("Received 'nonce':" + respNonce + " does not match to stored 'nonce':" + nonce, null)); } //finish DH key agreement BigInteger mobilePubKey = new BigInteger(Base64Utils.decode(mobilePubKeyBase64, true)); DHPublicKeySpec mobilePubKeySpec = new DHPublicKeySpec(mobilePubKey, container.getDhParams().getF().getP(), container.getDhParams().getF().getG()); byte[] sharedSecret = ssoTransferUtils.getSecret(mobilePubKeySpec, container.getDhParams().getS()); //build ASE256 key MessageDigest digest = MessageDigest.getInstance("SHA-256"); digest.reset(); byte[] hashedSecret = digest.digest(sharedSecret); //decrypt CSR Logger.debug("Finished Diffie-Hellman key exchange. --> Starting SessionBlob decryption ..."); byte[] encryptedSessionBlob = Base64Utils.decode(encSessionBlobBase64, true); Logger.debug("EncSessionBlob:" + Base64Utils.encode(encryptedSessionBlob) + " | Key:" + Base64Utils.encode(hashedSecret)); byte[] sessionBlobArray = ssoTransferUtils.enOrDeCryptCSR(encryptedSessionBlob, hashedSecret, Cipher.DECRYPT_MODE); String sessionBlob = new String(sessionBlobArray, "UTF-8"); Logger.debug("DecSessionBlob:" + sessionBlob); //parse SAML2 assertion Response ssoInformation = ssoTransferUtils.validateReceivedSSOContainer(sessionBlob); //validate signature AssertionAttributeExtractor attributeExtractor = new AssertionAttributeExtractor(ssoInformation); String holderOfKeyCertBase64 = attributeExtractor.getSingleAttributeValue(PVPConstants.PVP_HOLDEROFKEY_NAME); byte[] holderOfKeyCertEncoded = Base64Utils.decode(holderOfKeyCertBase64, false); X509Certificate holderOfKeyCert = new X509Certificate(holderOfKeyCertEncoded); Logger.debug("Found HolderOfKey Certificate:" + holderOfKeyCert.getSubjectDN().toString()); //TODO: implement Signature validation Logger.debug("MobileDevice is valid. --> Starting session reconstruction ..."); //transfer SSO Assertion into MOA-Session AuthenticationSessionWrapper moaSession = pendingReq.getSessionData(AuthenticationSessionWrapper.class); ssoTransferUtils.parseSSOContainerToMOASessionDataObject(pendingReq, moaSession, attributeExtractor); //set NeedConsent to false, because user gives consont during authentication pendingReq.setNeedUserConsent(false); // store MOASession into database requestStoreage.storePendingRequest(pendingReq); executionContext.put(SSOTransferConstants.FLAG_SSO_SESSION_RESTORED, true); executionContext.put("sessionRestoreFinished", false); responseMsg.addProperty( SSOTransferConstants.SSOCONTAINER_KEY_STATUS, "OK"); response.setStatus(HttpServletResponse.SC_OK); response.setContentType(MediaType.HTML_UTF_8.toString()); PrintWriter out = new PrintWriter(response.getOutputStream()); out.print(responseMsg.toString()); out.flush(); // Logger.info("Received SSO session-data is from IDP: " + entityID // + ". Start inderfederation process to restore SSO session ... "); // //change to inderfederated session reconstruction // // Logger.warn("Device Session Transfer with interfederation is not implemented, yet!!!!"); } catch (Exception e) { Logger.error("Parse reveived JSON data-object " + sb.toString() + " FAILED!", e); //throw new TaskExecutionException(pendingReq, "JSON data is not parseable.", e); try { responseMsg.addProperty( SSOTransferConstants.SSOCONTAINER_KEY_STATUS, "FAILED"); response.setStatus(HttpServletResponse.SC_OK); response.setContentType("text/html;charset=UTF-8"); PrintWriter out = new PrintWriter(response.getOutputStream()); out.print(responseMsg.toString()); out.flush(); } catch (IOException e1) { e1.printStackTrace(); } } } else { Logger.debug("Reveive NO POST-message data. Start check-session process ... "); boolean isSSOSessionRestored = BooleanUtils.isTrue((Boolean) executionContext.get(SSOTransferConstants.FLAG_SSO_SESSION_RESTORED)); if (isSSOSessionRestored) { Logger.info("Found restored SSO session. Resume authentication process ..."); executionContext.remove(SSOTransferConstants.FLAG_SSO_SESSION_RESTORED); executionContext.put("sessionRestoreFinished", true); } else { //session is valid --> load MOASession object AuthenticationSessionWrapper moasession = pendingReq.getSessionData(AuthenticationSessionWrapper.class); DateTime moaSessionCreated = new DateTime(moasession.getSessionCreated().getTime()); if (moaSessionCreated.plusMinutes(1).isBeforeNow()) { Logger.warn("No SSO session-container received. Stop authentication process after time-out."); throw new TaskExecutionException(pendingReq, "No SSO container received from smartphone app.", new MOAIDException("No SSO container received from smartphone app.", null)); } else { Logger.debug("No restored SSO session found --> Wait a few minutes and check again."); executionContext.put("sessionRestoreFinished", false); try { //create first step of SSO Transfer GUI authURL = HTTPUtils.extractAuthURLFromRequest(request); if (!AuthConfigurationProviderFactory.getInstance().getPublicURLPrefix(). contains(authURL)) { Logger.warn("Requested URL is not allowed.");; response.sendError(500, "Requested URL is not allowed."); } GUIUtils.buildSSOTransferGUI(guiBuilder, request, response, authURL, pendingReq.getPendingRequestId(), nonce, container.getDhParams().getF()); } catch (IOException | MOAIDException e) { throw new TaskExecutionException(pendingReq, e.getMessage(), e); } } } } } }