/**************************************************************************** * Copyright (C) 2012-2015 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.binding.tctoken; import org.openecard.binding.tctoken.ex.InvalidTCTokenElement; import org.openecard.binding.tctoken.ex.InvalidTCTokenUrlException; import org.openecard.binding.tctoken.ex.InvalidTCTokenException; import org.openecard.binding.tctoken.ex.SecurityViolationException; import org.openecard.binding.tctoken.ex.AuthServerException; import org.openecard.binding.tctoken.ex.MissingActivationParameterException; import org.openecard.binding.tctoken.ex.InvalidRedirectUrlException; import generated.TCTokenType; import iso.std.iso_iec._24727.tech.schema.ConnectionHandleType; import iso.std.iso_iec._24727.tech.schema.ConnectionHandleType.RecognitionInfo; import java.math.BigInteger; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.annotation.Nonnull; import org.openecard.addon.Context; import org.openecard.addons.tr03124.gui.CardMonitorTask; import org.openecard.addons.tr03124.gui.CardSelectionAction; import org.openecard.addons.tr03124.gui.CardSelectionStep; import org.openecard.binding.tctoken.ex.InvalidAddressException; import org.openecard.bouncycastle.crypto.tls.Certificate; import org.openecard.common.util.Pair; import org.openecard.common.util.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static org.openecard.binding.tctoken.ex.ErrorTranslations.*; import org.openecard.binding.tctoken.ex.UserCancellationException; import org.openecard.common.DynamicContext; import org.openecard.common.I18n; import org.openecard.common.enums.EventType; import org.openecard.common.sal.util.InsertCardDialog; import org.openecard.common.util.ByteUtils; import org.openecard.common.util.UrlBuilder; import org.openecard.gui.ResultStatus; import org.openecard.gui.UserConsent; import org.openecard.gui.UserConsentNavigator; import org.openecard.gui.definition.UserConsentDescription; import org.openecard.gui.executor.ExecutionEngine; import org.openecard.recognition.CardRecognition; /** * This class represents a TC Token request to the client. It contains the {@link TCTokenType} and situational parts * like the ifdName or the server certificates received while retrieving the TC Token. * * @author Moritz Horsch * @author Dirk Petrautzki * @author Hans-Martin Haase */ public class TCTokenRequest { private static final Logger logger = LoggerFactory.getLogger(TCTokenRequest.class); private static final I18n lang = I18n.getTranslation("tr03112"); private TCToken token; private String ifdName; private BigInteger slotIndex; private byte[] contextHandle; private String cardType = "http://bsi.bund.de/cif/npa.xml"; private boolean tokenFromObject; private List<Pair<URL, Certificate>> certificates; private URL tcTokenURL; private TCTokenContext tokenCtx; /** * Check and evaluate the request parameters and wrap the result in a {@code TCTokenRequest} class. * * @param parameters The request parameters. * @param ctx Addon {@link Context} used for the communication with the core. * @return A TCTokenRequest wrapping the parameters. * @throws InvalidTCTokenException * @throws MissingActivationParameterException * @throws AuthServerException * @throws InvalidRedirectUrlException * @throws InvalidTCTokenElement * @throws InvalidTCTokenUrlException * @throws SecurityViolationException * @throws InvalidAddressException */ public static TCTokenRequest convert(Map<String, String> parameters, Context ctx) throws InvalidTCTokenException, MissingActivationParameterException, AuthServerException, InvalidRedirectUrlException, InvalidTCTokenElement, InvalidTCTokenUrlException, SecurityViolationException, InvalidAddressException, UserCancellationException { TCTokenRequest result; if (parameters.containsKey("tcTokenURL")) { result = parseTCTokenRequestURI(parameters, ctx); result.tokenFromObject = false; return result; } else if (parameters.containsKey("activationObject")) { result = parseObjectURI(parameters); result.tokenFromObject = true; return result; } throw new MissingActivationParameterException(NO_PARAMS); } private static TCTokenRequest parseTCTokenRequestURI(Map<String, String> queries, Context ctx) throws InvalidTCTokenException, MissingActivationParameterException, AuthServerException, InvalidRedirectUrlException, InvalidTCTokenElement, InvalidTCTokenUrlException, SecurityViolationException, InvalidAddressException, UserCancellationException { TCTokenRequest tcTokenRequest = new TCTokenRequest(); try { if (queries.containsKey("cardTypes") || queries.containsKey("cardType")) { String[] types; if (queries.containsKey("cardType")) { types = new String[]{queries.get("cardType")}; } else { types = queries.get("cardTypes").split(","); } ConnectionHandleType handle = findCard(types, ctx); setIfdName(queries, handle.getIFDName()); setContextHandle(queries, handle.getContextHandle()); setSlotIndex(queries, handle.getSlotIndex()); addTokenUrlParameter(queries, handle.getRecognitionInfo()); } else { String[] types = new String[]{tcTokenRequest.cardType}; ConnectionHandleType handle = findCard(types, ctx); setIfdName(queries, handle.getIFDName()); setContextHandle(queries, handle.getContextHandle()); setSlotIndex(queries, handle.getSlotIndex()); } } catch (UserCancellationException ex) { if (queries.containsKey("cardTypes")) { addTokenUrlParameter(queries, queries.get("cardTypes").split(",")[0]); } logger.warn("The user aborted the CardInsertion dialog.", ex); DynamicContext dynCtx = DynamicContext.getInstance(TR03112Keys.INSTANCE_KEY); dynCtx.put(TR03112Keys.CARD_SELECTION_CANCELLATION, ex); } String activationTokenUrl = null; for (Map.Entry<String, String> next : queries.entrySet()) { String k = next.getKey(); k = k == null ? "" : k; String v = next.getValue(); if (v == null || v.isEmpty()) { logger.info("Skipping query parameter '{}' because it does not contain a value.", k); } else { switch (k) { case "tcTokenURL": activationTokenUrl = v; break; case "ifdName": tcTokenRequest.ifdName = v; break; case "contextHandle": tcTokenRequest.contextHandle = StringUtils.toByteArray(v); break; case "slotIndex": tcTokenRequest.slotIndex = new BigInteger(v); break; case "cardType": tcTokenRequest.cardType = v; break; default: logger.info("Unknown query element: {}", k); break; } } } // cardType determined! set in dynamic context, so the information is available in ResourceContext DynamicContext dynCtx = DynamicContext.getInstance(TR03112Keys.INSTANCE_KEY); dynCtx.put(TR03112Keys.ACTIVATION_CARD_TYPE, tcTokenRequest.cardType); if (activationTokenUrl != null) { try { URL tokenUrl = new URL(activationTokenUrl); TCTokenContext tokenCtx = TCTokenContext.generateTCToken(tokenUrl); tcTokenRequest.tokenCtx = tokenCtx; tcTokenRequest.token = tokenCtx.getToken(); tcTokenRequest.certificates = tokenCtx.getCerts(); tcTokenRequest.tcTokenURL = tokenUrl; } catch (MalformedURLException ex) { // TODO: check if the error type is correct, was WRONG_PARAMETER before throw new InvalidTCTokenUrlException(INVALID_TCTOKEN_URL, ex, activationTokenUrl); } } if (tcTokenRequest.token == null) { throw new MissingActivationParameterException(NO_TOKEN); } return tcTokenRequest; } private static TCTokenRequest parseObjectURI(Map<String, String> queries) throws InvalidTCTokenException, MissingActivationParameterException, AuthServerException, InvalidRedirectUrlException, InvalidTCTokenElement, InvalidTCTokenUrlException, SecurityViolationException, UserCancellationException { // TODO: get rid of this crap as soon as possible TCTokenRequest tcTokenRequest = new TCTokenRequest(); for (Map.Entry<String, String> next : queries.entrySet()) { String k = next.getKey(); k = k == null ? "" : k; String v = next.getValue(); if (v == null || v.isEmpty()) { logger.info("Skipping query parameter '{}' because it does not contain a value.", k); } else { switch (k) { case "activationObject": TCTokenContext tcToken = TCTokenContext.generateTCToken(v); tcTokenRequest.token = tcToken.getToken(); break; case "serverCertificate": // TODO: convert base64 and url encoded certificate to Certificate object break; default: logger.info("Unknown query element: {}", k); break; } } } if (tcTokenRequest.token == null) { throw new MissingActivationParameterException(NO_TOKEN); } return tcTokenRequest; } /** * Finds a card which matches one of the give types. * * @param types String array containing valid card types. * @param disp Dispatcher used to query cards and terminals. * @param gui User consent to display messages to the user. * @return ConnectionHandleType object of the chosen card. */ private static ConnectionHandleType findCard(@Nonnull String[] types, @Nonnull Context ctx) throws MissingActivationParameterException, UserCancellationException { CardRecognition rec = ctx.getRecognition(); Map<String, String> namesAndType = new HashMap<>(); for (String type : types) { namesAndType.put(rec.getTranslatedCardName(type), type); } InsertCardDialog insCardDiag = new InsertCardDialog(ctx.getUserConsent(), ctx.getCardStates(), namesAndType, ctx.getEventManager()); List<ConnectionHandleType> usableCards = insCardDiag.show(); if (usableCards == null) { // user aborted the card insertion dialog throw new UserCancellationException(null, lang.translationForKey(CARD_INSERTION_ABORT)); } ConnectionHandleType handle; if (usableCards.size() > 1) { UserConsentDescription ucd = new UserConsentDescription(lang.translationForKey("card.selection.heading.uc")); String stepTitle = lang.translationForKey("card.selection.heading.step"); CardSelectionStep step = new CardSelectionStep(stepTitle, usableCards, ctx.getRecognition()); ArrayList<String> types2 = new ArrayList<>(); types2.addAll(namesAndType.values()); CardMonitorTask task = new CardMonitorTask(types2, step); List<EventType> events = new ArrayList<>(); events.add(EventType.CARD_REMOVED); events.add(EventType.CARD_RECOGNIZED); ctx.getEventManager().register(task, events); step.setBackgroundTask(task); CardSelectionAction action = new CardSelectionAction(step, usableCards, types2, ctx); step.setAction(action); ucd.getSteps().add(step); UserConsent uc = ctx.getUserConsent(); UserConsentNavigator ucNav = uc.obtainNavigator(ucd); ExecutionEngine exec = new ExecutionEngine(ucNav); ResultStatus resStatus = exec.process(); if (resStatus != ResultStatus.OK) { throw new MissingActivationParameterException(CARD_SELECTION_ABORT); } handle = action.getResult(); ctx.getEventManager().unregister(task); } else { handle = usableCards.get(0); } return handle; } /** * Sets the IFDName in the given map. * * @param queries Map which shall contain the new IFDName value. * @param ifdName The new IFDName to set. */ private static void setIfdName(@Nonnull Map<String, String> queries, @Nonnull String ifdName) { if (! ifdName.isEmpty()) { queries.put("ifdName", ifdName); } } /** * Sets the ContextHandle in the given Map. * * @param queries Map which shall contain the new ContextHandle value. * @param contextHandle The new ContextHandle to set. */ private static void setContextHandle(@Nonnull Map<String, String> queries, @Nonnull byte[] contextHandle) { if (contextHandle.length > 0) { queries.put("contextHandle", ByteUtils.toHexString(contextHandle)); } } /** * Sets the SlotIndex in the given Map. * * @param queries Map which shall contain the new SlotIndex. * @param index The new SlotIndex to set. */ private static void setSlotIndex(@Nonnull Map<String, String> queries, @Nonnull BigInteger index) { queries.put("slotIndex", index.toString()); } /** * Adds the card type given in the given RecognitionInfo object as type to the tcTokenURL contained in the given map. * * @param queries Map which contains the tcTokenURL and shall contain the new cardType. * @param recInfo RecognitionInfo object containing the cardType or type parameter. */ private static void addTokenUrlParameter(@Nonnull Map<String, String> queries, @Nonnull RecognitionInfo recInfo) { if (queries.containsKey("tcTokenURL")) { String tcTokenURL = queries.get("tcTokenURL"); try { UrlBuilder builder = UrlBuilder.fromUrl(tcTokenURL); // url encoding is done by the builder builder = builder.queryParam("type", recInfo.getCardType(), true); queries.put("tcTokenURL", builder.build().toString()); queries.put("cardType", recInfo.getCardType()); } catch (URISyntaxException ex) { // ignore if this happens the authentication will fail at all. } } } /** * Adds the card type given in the given RecognitionInfo object as type to the tcTokenURL contained in the given map. * * @param queries Map which contains the tcTokenURL and shall contain the new cardType. * @param recInfo RecognitionInfo object containing the cardType or type parameter. */ private static void addTokenUrlParameter(@Nonnull Map<String, String> queries, @Nonnull String selectedCardType) { if (queries.containsKey("tcTokenURL")) { String tcTokenURL = queries.get("tcTokenURL"); try { UrlBuilder builder = UrlBuilder.fromUrl(tcTokenURL); // url encoding is done by the builder builder = builder.queryParam("type", selectedCardType, true); queries.put("tcTokenURL", builder.build().toString()); queries.put("cardType", selectedCardType); } catch (URISyntaxException ex) { // ignore if this happens the authentication will fail at all. } } } /** * Returns the TCToken. * * @return TCToken */ public TCToken getTCToken() { return token; } /** * Returns the IFD name. * * @return IFD name */ public String getIFDName() { return ifdName; } /** * Returns the context handle. * * @return Context handle */ public byte[] getContextHandle() { return contextHandle; } /** * Returns the slot index. * * @return Slot index */ public BigInteger getSlotIndex() { return slotIndex; } /** * Returns the card type selected for this authentication process. * Defaults to the nPA identifier to provide a fallback. * * @return Card type */ public String getCardType() { return cardType; } /** * Gets whether the token was created from an object tag or fetched from a URL. * * @return {@code true} when the token was created from an object tag, {@code false} otherwise. */ public boolean isTokenFromObject() { return tokenFromObject; } /** * Gets the certificates of the servers that have been passed while the TCToken was retrieved. * * @return List of the X509 server certificates and the requested URLs. May be null under certain circumstances * (e.g. legacy activation). */ public List<Pair<URL, Certificate>> getCertificates() { return certificates; } /** * Gets the TC Token URL. * * @return TC Token URL */ public URL getTCTokenURL() { return tcTokenURL; } public TCTokenContext getTokenContext() { return tokenCtx; } }