/****************************************************************************
* Copyright (C) 2013 ecsec GmbH.
* All rights reserved.
* Contact: ecsec GmbH (info@ecsec.de)
*
* This file is part of the Open eCard App.
*
* GNU General Public License Usage
* This file may be used under the terms of the GNU General Public
* License version 3.0 as published by the Free Software Foundation
* and appearing in the file LICENSE.GPL included in the packaging of
* this file. Please review the following information to ensure the
* GNU General Public License version 3.0 requirements will be met:
* http://www.gnu.org/copyleft/gpl.html.
*
* Other Usage
* Alternatively, this file may be used in accordance with the terms
* and conditions contained in a signed written agreement between
* you and ecsec GmbH.
*
***************************************************************************/
package org.openecard.control.module.tctoken;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.openecard.bouncycastle.crypto.tls.Certificate;
import org.openecard.common.DynamicContext;
import org.openecard.common.I18n;
import org.openecard.common.util.Promise;
import org.openecard.common.util.TR03112Utils;
import org.openecard.crypto.common.asn1.cvc.CertificateDescription;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Implementation performing the redirect checks according to TR-03112.
* The checks are described in BSI TR-03112 sec. 3.4.5.
*
* @author Tobias Wich <tobias.wich@ecsec.de>
*/
public class RedirectCertificateVerifier implements CertificateVerifier {
private static final Logger logger = LoggerFactory.getLogger(RedirectCertificateVerifier.class);
private final I18n lang = I18n.getTranslation("tctoken");
private final Promise<Object> descPromise;
private final boolean redirectChecks;
private boolean firstInvocation;
private boolean lastRedirect;
/**
* Creates an object of this class bound to the values in the current dynamic context.
*
* @param redirectChecks True if the TR-03112 checks must be performed.
*/
public RedirectCertificateVerifier(boolean redirectChecks) {
DynamicContext dynCtx = DynamicContext.getInstance(TR03112Keys.INSTANCE_KEY);
descPromise = dynCtx.getPromise(TR03112Keys.ESERVICE_CERTIFICATE_DESC);
this.redirectChecks = redirectChecks;
firstInvocation = true;
lastRedirect = false;
}
@Override
public VerifierResult verify(URL url, Certificate cert) {
try {
// disable certificate checks according to BSI TR03112-7 in some situations
if (redirectChecks) {
CertificateDescription desc = null;
// wait for certificate description
try {
desc = (CertificateDescription) descPromise.deref(60, TimeUnit.SECONDS);
// no error assumes that the promise is set a meaningful value
} catch (InterruptedException ex) {
String msg = "Couldn't retrieve the CertificateDescription from the DynamicContext.";
logger.error(msg);
throw new ControlException(msg);
} catch (TimeoutException ex) {
String msg = "Couldn't retrieve the CertificateDescription from the DynamicContext.";
logger.error(msg);
throw new ControlException(msg);
}
// check points certificate
if (! TR03112Utils.isInCommCertificates(cert, desc.getCommCertificates())) {
logger.error("The retrieved server certificate is NOT contained in the CommCertificates of " +
"the CertificateDescription extension of the eService certificate.");
throw new ControlException(lang.translationForKey("invalid_redirect"));
}
// check if we match the SOP
URL subjectUrl = new URL(desc.getSubjectURL());
boolean SOP = TR03112Utils.checkSameOriginPolicy(url, subjectUrl);
if (! SOP) {
firstInvocation = false;
// there is more to come
return VerifierResult.CONTINE;
} else {
// if the refresh address is SOP, then no redirect is expected
if (firstInvocation) {
return VerifierResult.FINISH;
}
if (lastRedirect) {
// stop execution
return VerifierResult.FINISH;
} else {
// same origin satisfied, memorize this state and stop execution in the next invocation
lastRedirect = true;
return VerifierResult.CONTINE;
}
}
} else {
// without the nPA there is no sensible exit point and as a result the last call is executed twice
// in that case its equally valid to let the browser do the redirects
return VerifierResult.FINISH;
}
} catch (MalformedURLException ex) {
throw new ControlException("Failed to convert SubjectURL to URL class.", ex);
}
}
}