/* * eID Applet Project. * Copyright (C) 2009 FedICT. * Copyright (C) 2014 e-Contract.be BVBA. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version * 3.0 as published by the Free Software Foundation. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, see * http://www.gnu.org/licenses/. */ package be.fedict.eid.applet.service.impl; import java.io.Serializable; import java.security.SecureRandom; import java.util.Date; import javax.servlet.http.HttpSession; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * Authentication Challenge. Manages challenge freshness and randomness. * * @author Frank Cornelis * */ public class AuthenticationChallenge implements Serializable { private static final long serialVersionUID = 1L; private static final Log LOG = LogFactory.getLog(AuthenticationChallenge.class); public static final String AUTHN_CHALLENGE_SESSION_ATTRIBUTE = AuthenticationChallenge.class.getName(); /** * The default maximum allowed maturity of the challenge in milliseconds. */ public static final long DEFAULT_MAX_MATURITY = 1000 * 60 * 5; private final byte[] challenge; private final Date timestamp; private static final SecureRandom secureRandom; static { secureRandom = new SecureRandom(); /* * We put some initial seed. */ secureRandom.setSeed(System.currentTimeMillis()); } private AuthenticationChallenge() { /* * Since SHA-1 is 20 bytes, we also take 20 here. More bytes wouldn't * bring us anything. */ this.challenge = new byte[20]; secureRandom.nextBytes(this.challenge); /* * Next should make it pretty non-deterministic. */ secureRandom.setSeed(System.currentTimeMillis()); this.timestamp = new Date(); } /** * Generates a challenge and stores it in the given HTTP session for later * consumption. * * @param session * @return the challenge. */ public static byte[] generateChallenge(HttpSession session) { AuthenticationChallenge authenticationChallenge = new AuthenticationChallenge(); if (null != session.getAttribute(AUTHN_CHALLENGE_SESSION_ATTRIBUTE)) { LOG.warn("overwriting a previous authentication challenge"); } session.setAttribute(AUTHN_CHALLENGE_SESSION_ATTRIBUTE, authenticationChallenge); byte[] challenge = authenticationChallenge.getChallenge(); return challenge; } private byte[] getChallenge() { /* * This method indeed is private. We want controlled consumption of the * authentication challenge. */ return this.challenge; } private Date getTimestamp() { return this.timestamp; } /** * Gives back the authentication challenge. This challenge is checked for * freshness and can be consumed only once. * * @param session * @param maxMaturity * @return */ public static byte[] getAuthnChallenge(HttpSession session, Long maxMaturity) { AuthenticationChallenge authenticationChallenge = (AuthenticationChallenge) session .getAttribute(AUTHN_CHALLENGE_SESSION_ATTRIBUTE); if (null == authenticationChallenge) { throw new SecurityException("no challenge in session"); } session.removeAttribute(AUTHN_CHALLENGE_SESSION_ATTRIBUTE); Date now = new Date(); if (null == maxMaturity) { maxMaturity = DEFAULT_MAX_MATURITY; } long dt = now.getTime() - authenticationChallenge.getTimestamp().getTime(); if (dt > maxMaturity) { throw new SecurityException("maximum challenge maturity reached"); } byte[] challenge = authenticationChallenge.getChallenge(); return challenge; } /** * Gives back the authentication challenge. This challenge is checked for * freshness and can be consumed only once. * * @param session * @return */ public static byte[] getAuthnChallenge(HttpSession session) { return getAuthnChallenge(session, null); } }