package at.gv.egovernment.moa.id.auth;
import iaik.pki.PKIException;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.w3c.dom.Element;
import at.gv.egovernment.moa.id.AuthenticationException;
import at.gv.egovernment.moa.id.BuildException;
import at.gv.egovernment.moa.id.ParseException;
import at.gv.egovernment.moa.id.ServiceException;
import at.gv.egovernment.moa.id.auth.builder.AuthenticationBlockAssertionBuilder;
import at.gv.egovernment.moa.id.auth.builder.AuthenticationDataAssertionBuilder;
import at.gv.egovernment.moa.id.auth.builder.CertInfoVerifyXMLSignatureRequestBuilder;
import at.gv.egovernment.moa.id.auth.builder.CreateXMLSignatureRequestBuilder;
import at.gv.egovernment.moa.id.auth.builder.DataURLBuilder;
import at.gv.egovernment.moa.id.auth.builder.GetIdentityLinkFormBuilder;
import at.gv.egovernment.moa.id.auth.builder.InfoboxReadRequestBuilder;
import at.gv.egovernment.moa.id.auth.builder.PersonDataBuilder;
import at.gv.egovernment.moa.id.auth.builder.SAMLArtifactBuilder;
import at.gv.egovernment.moa.id.auth.builder.SelectBKUFormBuilder;
import at.gv.egovernment.moa.id.auth.builder.VPKBuilder;
import at.gv.egovernment.moa.id.auth.builder.VerifyXMLSignatureRequestBuilder;
import at.gv.egovernment.moa.id.auth.data.AuthenticationSession;
import at.gv.egovernment.moa.id.auth.data.CreateXMLSignatureResponse;
import at.gv.egovernment.moa.id.auth.data.IdentityLink;
import at.gv.egovernment.moa.id.auth.data.VerifyXMLSignatureResponse;
import at.gv.egovernment.moa.id.auth.invoke.SignatureVerificationInvoker;
import at.gv.egovernment.moa.id.auth.parser.CreateXMLSignatureResponseParser;
import at.gv.egovernment.moa.id.auth.parser.InfoboxReadResponseParser;
import at.gv.egovernment.moa.id.auth.parser.SAMLArtifactParser;
import at.gv.egovernment.moa.id.auth.parser.VerifyXMLSignatureResponseParser;
import at.gv.egovernment.moa.id.auth.servlet.AuthServlet;
import at.gv.egovernment.moa.id.auth.validator.CreateXMLSignatureResponseValidator;
import at.gv.egovernment.moa.id.auth.validator.IdentityLinkValidator;
import at.gv.egovernment.moa.id.auth.validator.ValidateException;
import at.gv.egovernment.moa.id.auth.validator.VerifyXMLSignatureResponseValidator;
import at.gv.egovernment.moa.id.config.ConfigurationException;
import at.gv.egovernment.moa.id.config.ConfigurationProvider;
import at.gv.egovernment.moa.id.config.ConnectionParameter;
import at.gv.egovernment.moa.id.config.auth.AuthConfigurationProvider;
import at.gv.egovernment.moa.id.config.auth.OAAuthParameter;
import at.gv.egovernment.moa.id.data.AuthenticationData;
import at.gv.egovernment.moa.id.util.MOAIDMessageProvider;
import at.gv.egovernment.moa.id.util.Random;
import at.gv.egovernment.moa.id.util.SSLUtils;
import at.gv.egovernment.moa.logging.Logger;
import at.gv.egovernment.moa.util.BoolUtils;
import at.gv.egovernment.moa.util.DOMUtils;
import at.gv.egovernment.moa.util.DateTimeUtils;
import at.gv.egovernment.moa.util.FileUtils;
/**
* API for MOA ID Authentication Service.
* {@link AuthenticationSession} is stored in a session store and retrieved
* by giving the session ID.
*
* @author Paul Ivancsics
* @version $Id$
*/
public class AuthenticationServer implements MOAIDAuthConstants {
/** single instance */
private static AuthenticationServer instance;
/** session data store (session ID -> AuthenticationSession) */
private static Map sessionStore = new HashMap();
/** authentication data store (assertion handle -> AuthenticationData) */
private static Map authenticationDataStore = new HashMap();
/**
* time out in milliseconds used by {@link cleanup} for session store
*/
private long sessionTimeOut = 10 * 60 * 1000; // default 10 minutes
/**
* time out in milliseconds used by {@link cleanup} for authentication data store
*/
private long authDataTimeOut = 2 * 60 * 1000; // default 2 minutes
/**
* Returns the single instance of AuthenticationServer
.
*
* @return the single instance of AuthenticationServer
*/
public static AuthenticationServer getInstance() {
if (instance == null)
instance = new AuthenticationServer();
return instance;
}
/**
* Constructor for AuthenticationServer.
*/
public AuthenticationServer() {
super();
}
/**
* Processes request to select a BKU.
*
Processing depends on value of {@link AuthConfigurationProvider#getBKUSelectionType}.
*
For bkuSelectionType==HTMLComplete
, a returnURI
for the
* "BKU Auswahl" service is returned.
*
For bkuSelectionType==HTMLSelect
, an HTML form for BKU selection is returned.
* @param authURL base URL of MOA-ID Auth component
* @param target "Geschäftsbereich"
* @param oaURL online application URL requested
* @param bkuSelectionTemplateURL template for BKU selection form to be used
* in case of HTMLSelect
; may be null
* @param templateURL URL providing an HTML template for the HTML form to be used
* for call startAuthentication
* @return for bkuSelectionType==HTMLComplete
, the returnURI
for the
* "BKU Auswahl" service;
* for bkuSelectionType==HTMLSelect
, an HTML form for BKU selection
* @throws WrongParametersException upon missing parameters
* @throws AuthenticationException when the configured BKU selection service cannot be reached,
* and when the given bkuSelectionTemplateURL cannot be reached
* @throws ConfigurationException on missing configuration data
* @throws BuildException while building the HTML form
*/
public String selectBKU(
String authURL,
String target,
String oaURL,
String bkuSelectionTemplateURL,
String templateURL)
throws WrongParametersException, AuthenticationException, ConfigurationException, BuildException {
//check if HTTP Connection may be allowed (through FRONTEND_SERVLETS_ENABLE_HTTP_CONNECTION_PROPERTY)
String boolStr = AuthConfigurationProvider.getInstance().getGenericConfigurationParameter(
AuthConfigurationProvider.FRONTEND_SERVLETS_ENABLE_HTTP_CONNECTION_PROPERTY);
if ((!authURL.startsWith("https:")) && (false == BoolUtils.valueOf(boolStr)))
throw new AuthenticationException("auth.07", new Object[] { authURL + "*" });
if (isEmpty(authURL))
throw new WrongParametersException("StartAuthentication", "AuthURL");
if (isEmpty(target))
throw new WrongParametersException("StartAuthentication", PARAM_TARGET);
if (isEmpty(oaURL))
throw new WrongParametersException("StartAuthentication", PARAM_OA);
ConnectionParameter bkuConnParam =
AuthConfigurationProvider.getInstance().getBKUConnectionParameter();
if (bkuConnParam == null)
throw new ConfigurationException(
"config.08",
new Object[] { "BKUSelection/ConnectionParameter" });
OAAuthParameter oaParam =
AuthConfigurationProvider.getInstance().getOnlineApplicationParameter(oaURL);
if (oaParam == null)
throw new AuthenticationException("auth.00", new Object[] { oaURL });
AuthenticationSession session = newSession();
Logger.info("MOASession " + session.getSessionID() + " angelegt");
session.setTarget(target);
session.setOAURLRequested(oaURL);
session.setPublicOAURLPrefix(oaParam.getPublicURLPrefix());
session.setAuthURL(authURL);
session.setTemplateURL(templateURL);
String returnURL =
new DataURLBuilder().buildDataURL(authURL, REQ_START_AUTHENTICATION, session.getSessionID());
String bkuSelectionType = AuthConfigurationProvider.getInstance().getBKUSelectionType();
if (bkuSelectionType.equals(AuthConfigurationProvider.BKU_SELECTION_TYPE_HTMLCOMPLETE)) {
// bkuSelectionType==HTMLComplete
String redirectURL = bkuConnParam.getUrl() + "?" + AuthServlet.PARAM_RETURN + "=" + returnURL;
return redirectURL;
} else {
// bkuSelectionType==HTMLSelect
String bkuSelectTag;
try {
bkuSelectTag = readBKUSelectTag(AuthConfigurationProvider.getInstance(), bkuConnParam);
} catch (Throwable ex) {
throw new AuthenticationException(
"auth.03",
new Object[] { bkuConnParam.getUrl(), ex.toString()},
ex);
}
String bkuSelectionTemplate = null;
if (bkuSelectionTemplateURL != null) {
try {
bkuSelectionTemplate = new String(FileUtils.readURL(bkuSelectionTemplateURL));
} catch (IOException ex) {
throw new AuthenticationException(
"auth.03",
new Object[] { bkuSelectionTemplateURL, ex.toString()},
ex);
}
}
String htmlForm =
new SelectBKUFormBuilder().build(bkuSelectionTemplate, returnURL, bkuSelectTag);
return htmlForm;
}
}
/**
* Method readBKUSelectTag.
* @param conf the ConfigurationProvider
* @param connParam the ConnectionParameter for that connection
* @return String
* @throws ConfigurationException on config-errors
* @throws PKIException on PKI errors
* @throws IOException on any data error
* @throws GeneralSecurityException on security errors
*/
private String readBKUSelectTag(ConfigurationProvider conf, ConnectionParameter connParam)
throws ConfigurationException, PKIException, IOException, GeneralSecurityException {
if (connParam.isHTTPSURL())
return new String(SSLUtils.readHttpsURL(conf, connParam));
else
return new String(FileUtils.readURL(connParam.getUrl()));
}
/**
* Processes the beginning of an authentication session.
*
<InfoboxReadRequest>
<InfoboxReadRequest>
null
; in this case, the default location will be used
* @param templateURL URL providing an HTML template for the HTML form generated
* @return HTML form
* @throws AuthenticationException
* @see GetIdentityLinkFormBuilder
* @see InfoboxReadRequestBuilder
*/
public String startAuthentication(
String authURL,
String target,
String oaURL,
String templateURL,
String bkuURL,
String sessionID)
throws WrongParametersException, AuthenticationException, ConfigurationException, BuildException {
if (isEmpty(sessionID)) {
if (isEmpty(authURL))
throw new WrongParametersException("StartAuthentication", "AuthURL");
//check if HTTP Connection may be allowed (through FRONTEND_SERVLETS_ENABLE_HTTP_CONNECTION_PROPERTY)
String boolStr =
AuthConfigurationProvider.getInstance().getGenericConfigurationParameter(
AuthConfigurationProvider.FRONTEND_SERVLETS_ENABLE_HTTP_CONNECTION_PROPERTY);
if ((!authURL.startsWith("https:")) && (false == BoolUtils.valueOf(boolStr)))
throw new AuthenticationException("auth.07", new Object[] { authURL + "*" });
if (isEmpty(target))
throw new WrongParametersException("StartAuthentication", PARAM_TARGET);
if (isEmpty(oaURL))
throw new WrongParametersException("StartAuthentication", PARAM_OA);
}
AuthenticationSession session;
if (sessionID != null)
session = getSession(sessionID);
else {
OAAuthParameter oaParam =
AuthConfigurationProvider.getInstance().getOnlineApplicationParameter(oaURL);
if (oaParam == null)
throw new AuthenticationException("auth.00", new Object[] { oaURL });
session = newSession();
Logger.info("MOASession " + session.getSessionID() + " angelegt");
session.setTarget(target);
session.setOAURLRequested(oaURL);
session.setPublicOAURLPrefix(oaParam.getPublicURLPrefix());
session.setAuthURL(authURL);
session.setTemplateURL(templateURL);
}
String infoboxReadRequest = new InfoboxReadRequestBuilder().build();
String dataURL =
new DataURLBuilder().buildDataURL(
session.getAuthURL(),
REQ_VERIFY_IDENTITY_LINK,
session.getSessionID());
String template = null;
if (session.getTemplateURL() != null) {
try {
template = new String(FileUtils.readURL(session.getTemplateURL()));
} catch (IOException ex) {
throw new AuthenticationException(
"auth.03",
new Object[] { session.getTemplateURL(), ex.toString()},
ex);
}
}
String certInfoRequest = new CertInfoVerifyXMLSignatureRequestBuilder().build();
String certInfoDataURL =
new DataURLBuilder().buildDataURL(
session.getAuthURL(),
REQ_START_AUTHENTICATION,
session.getSessionID());
String htmlForm =
new GetIdentityLinkFormBuilder().build(
template,
bkuURL,
infoboxReadRequest,
dataURL,
certInfoRequest,
certInfoDataURL);
return htmlForm;
}
/**
* Processes an <InfoboxReadResponse>
sent by the
* security layer implementation.<InfoboxReadResponse>
<InfoboxReadResponse>
<CreateXMLSignatureRequest>
* containg the authentication block, meant to be returned to the
* security layer implementation<InfoboxReadResponse>
* @return String representation of the <CreateXMLSignatureRequest>
*/
public String verifyIdentityLink(String sessionID, String xmlInfoboxReadResponse)
throws
AuthenticationException,
ParseException,
ConfigurationException,
ValidateException,
ServiceException {
if (isEmpty(sessionID))
throw new AuthenticationException("auth.10", new Object[] { REQ_VERIFY_IDENTITY_LINK, PARAM_SESSIONID});
if (isEmpty(xmlInfoboxReadResponse))
throw new AuthenticationException("auth.10", new Object[] { REQ_VERIFY_IDENTITY_LINK, PARAM_XMLRESPONSE});
AuthenticationSession session = getSession(sessionID);
if (session.getTimestampIdentityLink() != null)
throw new AuthenticationException("auth.01", new Object[] { sessionID });
session.setTimestampIdentityLink();
AuthConfigurationProvider authConf = AuthConfigurationProvider.getInstance();
// parses the <saml:Assertion>
from given session data.
* @param session authentication session
* @return <saml:Assertion>
as a String
*/
private String buildAuthenticationBlock(AuthenticationSession session) {
IdentityLink identityLink = session.getIdentityLink();
String issuer = identityLink.getGivenName() + " " + identityLink.getFamilyName();
String issueInstant = DateTimeUtils.buildDateTime(Calendar.getInstance());
String authURL = session.getAuthURL();
String target = session.getTarget();
String oaURL = session.getPublicOAURLPrefix();
String authBlock =
new AuthenticationBlockAssertionBuilder().build(issuer, issueInstant, authURL, target, oaURL);
return authBlock;
}
/**
* Processes a <CreateXMLSignatureResponse>
sent by the
* security layer implementation.<CreateXMLSignatureResponse>
<CreateXMLSignatureResponse>
for error codes<CreateXMLSignatureResponse>
<CreateXMLSignatureResponse>
* @return SAML artifact needed for retrieving authentication data, encoded BASE64
*/
public String verifyAuthenticationBlock(
String sessionID,
String xmlCreateXMLSignatureReadResponse)
throws
AuthenticationException,
BuildException,
ParseException,
ConfigurationException,
ServiceException,
ValidateException {
if (isEmpty(sessionID))
throw new AuthenticationException("auth.10", new Object[] { REQ_VERIFY_AUTH_BLOCK, PARAM_SESSIONID});
if (isEmpty(xmlCreateXMLSignatureReadResponse))
throw new AuthenticationException("auth.10", new Object[] { REQ_VERIFY_AUTH_BLOCK, PARAM_XMLRESPONSE});
AuthenticationSession session = getSession(sessionID);
AuthConfigurationProvider authConf = AuthConfigurationProvider.getInstance();
// parses <saml:Assertion>
* @param session authentication session
* @param verifyXMLSigResp VerifyXMLSignatureResponse from MOA-SP
* @return AuthenticationData object
* @throws ConfigurationException while accessing configuration data
* @throws BuildException while building the <saml:Assertion>
*/
private AuthenticationData buildAuthenticationData(
AuthenticationSession session,
VerifyXMLSignatureResponse verifyXMLSigResp)
throws ConfigurationException, BuildException {
IdentityLink identityLink = session.getIdentityLink();
AuthenticationData authData = new AuthenticationData();
authData.setMajorVersion(1);
authData.setMinorVersion(0);
authData.setAssertionID(Random.nextRandom());
authData.setIssuer(session.getAuthURL());
authData.setIssueInstant(DateTimeUtils.buildDateTime(Calendar.getInstance()));
String vpkBase64 =
new VPKBuilder().buildVPK(
identityLink.getIdentificationValue(),
identityLink.getDateOfBirth(),
session.getTarget());
authData.setVPK(vpkBase64);
authData.setGivenName(identityLink.getGivenName());
authData.setFamilyName(identityLink.getFamilyName());
authData.setDateOfBirth(identityLink.getDateOfBirth());
authData.setQualifiedCertificate(verifyXMLSigResp.isQualifiedCertificate());
authData.setPublicAuthority(verifyXMLSigResp.isPublicAuthority());
authData.setPublicAuthorityCode(verifyXMLSigResp.getPublicAuthorityCode());
OAAuthParameter oaParam =
AuthConfigurationProvider.getInstance().getOnlineApplicationParameter(
session.getPublicOAURLPrefix());
String prPerson = new PersonDataBuilder().build(identityLink, oaParam.getProvideZMRZahl());
try {
String ilAssertion =
oaParam.getProvideIdentityLink()
? DOMUtils.serializeNode(identityLink.getSamlAssertion())
: "";
String authBlock = oaParam.getProvideAuthBlock() ? session.getAuthBlock() : "";
String samlAssertion =
new AuthenticationDataAssertionBuilder().build(authData, prPerson, authBlock, ilAssertion);
authData.setSamlAssertion(samlAssertion);
return authData;
} catch (Throwable ex) {
throw new BuildException(
"builder.00",
new Object[] { "AuthenticationData", ex.toString()},
ex);
}
}
/**
* Retrieves AuthenticationData
indexed by the SAML artifact.
* The AuthenticationData
is deleted from the store upon end of this call.
*
* @return AuthenticationData
*/
public AuthenticationData getAuthenticationData(String samlArtifact)
throws AuthenticationException {
String assertionHandle;
try {
assertionHandle = new SAMLArtifactParser(samlArtifact).parseAssertionHandle();
} catch (ParseException ex) {
throw new AuthenticationException("1205", new Object[] { samlArtifact, ex.toString()});
}
AuthenticationData authData = null;
synchronized (authenticationDataStore) {
authData = (AuthenticationData) authenticationDataStore.get(assertionHandle);
if (authData == null) {
Logger.error("Assertion not found for SAML Artifact: " + samlArtifact);
throw new AuthenticationException("1206", new Object[] { samlArtifact });
}
authenticationDataStore.remove(assertionHandle);
}
long now = new Date().getTime();
if (now - authData.getTimestamp().getTime() > authDataTimeOut)
throw new AuthenticationException("1207", new Object[] { samlArtifact });
Logger.debug("Assertion delivered for SAML Artifact: " + samlArtifact);
return authData;
}
/**
* Stores authentication data indexed by the assertion handle contained in the
* given saml artifact.
* @param samlArtifact SAML artifact
* @param authData authentication data
* @throws AuthenticationException when SAML artifact is invalid
*/
private void storeAuthenticationData(String samlArtifact, AuthenticationData authData)
throws AuthenticationException {
try {
SAMLArtifactParser parser = new SAMLArtifactParser(samlArtifact);
// check type code 0x0001
byte[] typeCode = parser.parseTypeCode();
if (typeCode[0] != 0 || typeCode[1] != 1)
throw new AuthenticationException("auth.06", new Object[] { samlArtifact });
String assertionHandle = parser.parseAssertionHandle();
synchronized (authenticationDataStore) {
Logger.debug("Assertion stored for SAML Artifact: " + samlArtifact);
authenticationDataStore.put(assertionHandle, authData);
}
} catch (AuthenticationException ex) {
throw ex;
} catch (Throwable ex) {
throw new AuthenticationException("auth.06", new Object[] { samlArtifact });
}
}
/**
* Creates a new session and puts it into the session store.
*
* @param id Session ID
* @return AuthenticationSession created
* @exception AuthenticationException
* thrown when an AuthenticationSession
is running
* already for the given session ID
*/
private static AuthenticationSession newSession() throws AuthenticationException {
String sessionID = Random.nextRandom();
AuthenticationSession newSession = new AuthenticationSession(sessionID);
synchronized (sessionStore) {
AuthenticationSession session = (AuthenticationSession) sessionStore.get(sessionID);
if (session != null)
throw new AuthenticationException("auth.01", new Object[] { sessionID });
sessionStore.put(sessionID, newSession);
}
return newSession;
}
/**
* Retrieves a session from the session store.
*
* @param id session ID
* @return AuthenticationSession
stored with given session ID,
* null
if session ID unknown
*/
public static AuthenticationSession getSession(String id) throws AuthenticationException {
AuthenticationSession session = (AuthenticationSession) sessionStore.get(id);
if (session == null)
throw new AuthenticationException("auth.02", new Object[] { id });
return session;
}
/**
* Cleans up expired session and authentication data stores.
*/
public void cleanup() {
long now = new Date().getTime();
synchronized (sessionStore) {
Set keys = new HashSet(sessionStore.keySet());
for (Iterator iter = keys.iterator(); iter.hasNext();) {
String sessionID = (String) iter.next();
AuthenticationSession session = (AuthenticationSession) sessionStore.get(sessionID);
if (now - session.getTimestampStart().getTime() > sessionTimeOut) {
Logger.info(
MOAIDMessageProvider.getInstance().getMessage(
"cleaner.02",
new Object[] { sessionID }));
sessionStore.remove(sessionID);
}
}
}
synchronized (authenticationDataStore) {
Set keys = new HashSet(authenticationDataStore.keySet());
for (Iterator iter = keys.iterator(); iter.hasNext();) {
String samlArtifact = (String) iter.next();
AuthenticationData authData =
(AuthenticationData) authenticationDataStore.get(samlArtifact);
if (now - authData.getTimestamp().getTime() > authDataTimeOut) {
Logger.info(
MOAIDMessageProvider.getInstance().getMessage(
"cleaner.03",
new Object[] { samlArtifact }));
authenticationDataStore.remove(samlArtifact);
}
}
}
}
/**
* Sets the sessionTimeOut.
* @param sessionTimeOut time out in seconds
*/
public void setSecondsSessionTimeOut(long seconds) {
sessionTimeOut = 1000 * seconds;
}
/**
* Sets the authDataTimeOut.
* @param authDataTimeOut time out in seconds
*/
public void setSecondsAuthDataTimeOut(long seconds) {
authDataTimeOut = 1000 * seconds;
}
/**
* Checks a parameter.
* @param param parameter
* @return true if the parameter is null or empty
*/
private boolean isEmpty(String param) {
return param == null || param.length() == 0;
}
/**
* Writes an XML structure to file for debugging purposes, encoding UTF-8.
*
* @param filename file name
* @param rootElem root element in DOM tree
*/
public static void debugOutputXMLFile(String filename, Element rootElem) {
if (Logger.isDebugEnabled(DEBUG_OUTPUT_HIERARCHY)) {
try {
String xmlString = new String(DOMUtils.serializeNode(rootElem));
debugOutputXMLFile(filename, xmlString);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
/**
* Writes an XML structure to file for debugging purposes, encoding UTF-8.
*
* @param filename file name
* @param xmlString XML string
*/
public static void debugOutputXMLFile(String filename, String xmlString) {
if (Logger.isDebugEnabled(DEBUG_OUTPUT_HIERARCHY)) {
try {
java.io.OutputStream fout = new java.io.FileOutputStream(filename);
byte[] xmlData = xmlString.getBytes("UTF-8");
fout.write(xmlData);
fout.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}