/**************************************************************************** * Copyright (C) 2012-2014 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.sal.protocol.eac; import iso.std.iso_iec._24727.tech.schema.ConnectionHandleType; import iso.std.iso_iec._24727.tech.schema.DIDAuthenticate; import iso.std.iso_iec._24727.tech.schema.DIDAuthenticateResponse; import iso.std.iso_iec._24727.tech.schema.DIDAuthenticationDataType; import iso.std.iso_iec._24727.tech.schema.DIDStructureType; import iso.std.iso_iec._24727.tech.schema.GetIFDCapabilities; import iso.std.iso_iec._24727.tech.schema.GetIFDCapabilitiesResponse; import iso.std.iso_iec._24727.tech.schema.InputAPDUInfoType; import iso.std.iso_iec._24727.tech.schema.SlotCapabilityType; import iso.std.iso_iec._24727.tech.schema.Transmit; import iso.std.iso_iec._24727.tech.schema.TransmitResponse; import java.lang.reflect.InvocationTargetException; import java.net.MalformedURLException; import java.net.URL; import java.security.cert.CertificateException; import java.util.Arrays; import java.util.List; import java.util.Map; import javax.smartcardio.ResponseAPDU; import oasis.names.tc.dss._1_0.core.schema.Result; import org.openecard.addon.sal.FunctionType; import org.openecard.addon.sal.ProtocolStep; import org.openecard.binding.tctoken.TR03112Keys; import org.openecard.bouncycastle.crypto.tls.Certificate; import org.openecard.common.DynamicContext; import org.openecard.common.ECardConstants; import org.openecard.common.I18n; import org.openecard.common.WSHelper; import org.openecard.common.anytype.AuthDataMap; import org.openecard.common.ifd.PACECapabilities; import org.openecard.common.interfaces.Dispatcher; import org.openecard.common.interfaces.DispatcherException; import org.openecard.common.interfaces.EventManager; import org.openecard.common.interfaces.ObjectSchemaValidator; import org.openecard.common.interfaces.ObjectValidatorException; import org.openecard.common.sal.state.CardStateEntry; import org.openecard.common.util.ByteUtils; import org.openecard.common.util.Pair; import org.openecard.common.util.Promise; import org.openecard.common.util.TR03112Utils; import org.openecard.crypto.common.asn1.cvc.CHAT; import org.openecard.crypto.common.asn1.cvc.CHATVerifier; import org.openecard.crypto.common.asn1.cvc.CardVerifiableCertificate; import org.openecard.crypto.common.asn1.cvc.CardVerifiableCertificateChain; import org.openecard.crypto.common.asn1.cvc.CardVerifiableCertificateVerifier; import org.openecard.crypto.common.asn1.cvc.CertificateDescription; import org.openecard.crypto.common.asn1.eac.AuthenticatedAuxiliaryData; import org.openecard.crypto.common.asn1.eac.SecurityInfos; 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.sal.protocol.eac.anytype.EAC1InputType; import org.openecard.sal.protocol.eac.anytype.EAC1OutputType; import org.openecard.sal.protocol.eac.anytype.ElementParsingException; import org.openecard.sal.protocol.eac.anytype.PACEMarkerType; import org.openecard.sal.protocol.eac.anytype.PACEOutputType; import org.openecard.sal.protocol.eac.anytype.PasswordID; import org.openecard.sal.protocol.eac.gui.CHATStep; import org.openecard.sal.protocol.eac.gui.CVCStep; import org.openecard.sal.protocol.eac.gui.CVCStepAction; import org.openecard.sal.protocol.eac.gui.CardMonitor; import org.openecard.sal.protocol.eac.gui.CardRemovedFilter; import org.openecard.sal.protocol.eac.gui.PINStep; import org.openecard.sal.protocol.eac.gui.ProcessingStep; import org.openecard.sal.protocol.eac.gui.ProcessingStepAction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Implements PACE protocol step according to BSI TR-03112-7. * * @see "BSI-TR-03112, version 1.1.2., part 7, section 4.6.5." * @author Tobias Wich * @author Moritz Horsch * @author Dirk Petrautzki */ public class PACEStep implements ProtocolStep<DIDAuthenticate, DIDAuthenticateResponse> { private static final Logger logger = LoggerFactory.getLogger(PACEStep.class.getName()); private static final I18n lang = I18n.getTranslation("eac"); private static final I18n langPace = I18n.getTranslation("pace"); // GUI translation constants private static final String TITLE = "eac_user_consent_title"; private final Dispatcher dispatcher; private final UserConsent gui; private final EventManager eventManager; /** * Creates a new PACE protocol step. * * @param dispatcher Dispatcher * @param gui GUI * @param eventManager */ public PACEStep(Dispatcher dispatcher, UserConsent gui, EventManager eventManager) { this.dispatcher = dispatcher; this.gui = gui; this.eventManager = eventManager; } @Override public FunctionType getFunctionType() { return FunctionType.DIDAuthenticate; } @Override public DIDAuthenticateResponse perform(DIDAuthenticate request, Map<String, Object> internalData) { // get context to save values in DynamicContext dynCtx = DynamicContext.getInstance(TR03112Keys.INSTANCE_KEY); DIDAuthenticate didAuthenticate = request; DIDAuthenticateResponse response = new DIDAuthenticateResponse(); ConnectionHandleType conHandle = (ConnectionHandleType) dynCtx.get(TR03112Keys.CONNECTION_HANDLE); try { ObjectSchemaValidator valid = (ObjectSchemaValidator) dynCtx.getPromise(EACProtocol.SCHEMA_VALIDATOR).deref(); boolean messageValid = valid.validateObject(request); if (! messageValid) { String msg = "Validation of the EAC1InputType message failed."; logger.error(msg); dynCtx.put(EACProtocol.AUTHENTICATION_FAILED, true); response.setResult(WSHelper.makeResultError(ECardConstants.Minor.App.INCORRECT_PARM, msg)); return response; } } catch (ObjectValidatorException ex) { String msg = "Validation of the EAC1InputType message failed due to invalid input data."; logger.error(msg, ex); dynCtx.put(EACProtocol.AUTHENTICATION_FAILED, true); response.setResult(WSHelper.makeResultError(ECardConstants.Minor.App.INT_ERROR, msg)); return response; } catch (InterruptedException ex) { String msg = "Thread interrupted while waiting for schema validator instance."; logger.error(msg, ex); dynCtx.put(EACProtocol.AUTHENTICATION_FAILED, true); response.setResult(WSHelper.makeResultError(ECardConstants.Minor.App.INT_ERROR, msg)); return response; } if (! ByteUtils.compare(conHandle.getSlotHandle(), didAuthenticate.getConnectionHandle().getSlotHandle())) { String msg = "Invalid connection handle given in DIDAuthenticate message."; Result r = WSHelper.makeResultError(ECardConstants.Minor.SAL.UNKNOWN_HANDLE, msg); response.setResult(r); dynCtx.put(EACProtocol.AUTHENTICATION_FAILED, true); return response; } byte[] slotHandle = conHandle.getSlotHandle(); dynCtx.put(EACProtocol.SLOT_HANDLE, slotHandle); dynCtx.put(EACProtocol.DISPATCHER, dispatcher); try { EAC1InputType eac1Input = new EAC1InputType(didAuthenticate.getAuthenticationProtocolData()); EAC1OutputType eac1Output = eac1Input.getOutputType(); AuthenticatedAuxiliaryData aad = new AuthenticatedAuxiliaryData(eac1Input.getAuthenticatedAuxiliaryData()); byte pinID = PasswordID.valueOf(didAuthenticate.getDIDName()).getByte(); final String passwordType = PasswordID.parse(pinID).getString(); // determine PACE capabilities of the terminal boolean nativePace = genericPACESupport(conHandle); dynCtx.put(EACProtocol.IS_NATIVE_PACE, nativePace); // Certificate chain CardVerifiableCertificateChain certChain = new CardVerifiableCertificateChain(eac1Input.getCertificates()); byte[] rawCertificateDescription = eac1Input.getCertificateDescription(); CertificateDescription certDescription = CertificateDescription.getInstance(rawCertificateDescription); // put CertificateDescription into DynamicContext which is needed for later checks dynCtx.put(TR03112Keys.ESERVICE_CERTIFICATE_DESC, certDescription); // according to BSI-INSTANCE_KEY-7 we MUST perform some checks immediately after receiving the eService cert Result activationChecksResult = performChecks(certDescription, dynCtx); if (! ECardConstants.Major.OK.equals(activationChecksResult.getResultMajor())) { response.setResult(activationChecksResult); dynCtx.put(EACProtocol.AUTHENTICATION_FAILED, true); return response; } CHAT requiredCHAT = new CHAT(eac1Input.getRequiredCHAT()); CHAT optionalCHAT = new CHAT(eac1Input.getOptionalCHAT()); // get the PACEMarker CardStateEntry cardState = (CardStateEntry) internalData.get(EACConstants.IDATA_CARD_STATE_ENTRY); PACEMarkerType paceMarker = getPaceMarker(cardState, passwordType); dynCtx.put(EACProtocol.PACE_MARKER, paceMarker); // Verify that the certificate description matches the terminal certificate CardVerifiableCertificate taCert = certChain.getTerminalCertificate(); CardVerifiableCertificateVerifier.verify(taCert, certDescription); // Verify that the required CHAT matches the terminal certificate's CHAT CHAT taCHAT = taCert.getCHAT(); // Check that we got an authentication terminal terminal certificate. We abort the process in case there is // an other role. if (taCHAT.getRole() != CHAT.Role.AUTHENTICATION_TERMINAL) { String msg = "Unsupported terminal type in Terminal Certificate referenced. Refernced terminal type is " + taCHAT.getRole().toString() + "."; response.setResult(WSHelper.makeResultError(ECardConstants.Minor.App.INCORRECT_PARM, msg)); dynCtx.put(EACProtocol.AUTHENTICATION_FAILED, true); return response; } CHATVerifier.verfiy(taCHAT, requiredCHAT); // remove overlapping values from optional chat optionalCHAT.restrictAccessRights(taCHAT); // Prepare data in DIDAuthenticate for GUI final EACData eacData = new EACData(); eacData.didRequest = didAuthenticate; eacData.certificate = certChain.getTerminalCertificate(); eacData.certificateDescription = certDescription; eacData.rawCertificateDescription = rawCertificateDescription; eacData.transactionInfo = eac1Input.getTransactionInfo(); eacData.requiredCHAT = requiredCHAT; eacData.optionalCHAT = optionalCHAT; eacData.selectedCHAT = requiredCHAT; eacData.aad = aad; eacData.pinID = pinID; eacData.passwordType = passwordType; dynCtx.put(EACProtocol.EAC_DATA, eacData); // get initial pin status InputAPDUInfoType input = new InputAPDUInfoType(); input.setInputAPDU(new byte[] {(byte) 0x00, (byte) 0x22, (byte) 0xC1, (byte) 0xA4, (byte) 0x0F, (byte) 0x80, (byte) 0x0A, (byte) 0x04, (byte) 0x00, (byte) 0x7F, (byte) 0x00, (byte) 0x07, (byte) 0x02, (byte) 0x02, (byte) 0x04, (byte) 0x02, (byte) 0x02, (byte) 0x83, (byte) 0x01, (byte) 0x03}); input.getAcceptableStatusCode().add(new byte[] {(byte) 0x90, (byte) 0x00}); // pin activated 3 tries left input.getAcceptableStatusCode().add(new byte[] {(byte) 0x63, (byte) 0xC2}); // pin activated 2 tries left input.getAcceptableStatusCode().add(new byte[] {(byte) 0x63, (byte) 0xC1}); // pin suspended 1 try left CAN // needs to be entered input.getAcceptableStatusCode().add(new byte[] {(byte) 0x63, (byte) 0xC0}); // pin blocked 0 tries left input.getAcceptableStatusCode().add(new byte[] {(byte) 0x62, (byte) 0x83}); // pin deaktivated Transmit transmit = new Transmit(); transmit.setSlotHandle(slotHandle); transmit.getInputAPDUInfo().add(input); TransmitResponse pinCheckResponse = (TransmitResponse) dispatcher.deliver(transmit); byte[] output = pinCheckResponse.getOutputAPDU().get(0); ResponseAPDU outputApdu = new ResponseAPDU(output); byte[] status = {(byte) outputApdu.getSW1(), (byte) outputApdu.getSW2()}; dynCtx.put(EACProtocol.PIN_STATUS_BYTES, status); boolean pinUsable = ! Arrays.equals(status, new byte[]{(byte) 0x63, (byte) 0xC0}); // define GUI depending on the PIN status final UserConsentDescription uc = new UserConsentDescription(lang.translationForKey(TITLE)); if (pinUsable) { // create GUI and init executor CardMonitor cardMon = new CardMonitor(); CardRemovedFilter filter = new CardRemovedFilter(conHandle.getIFDName(), conHandle.getSlotIndex()); eventManager.register(cardMon, filter); CVCStep cvcStep = new CVCStep(eacData); cvcStep.setBackgroundTask(cardMon); CVCStepAction cvcStepAction = new CVCStepAction(cvcStep); cvcStep.setAction(cvcStepAction); uc.getSteps().add(cvcStep); uc.getSteps().add(CHATStep.createDummy()); uc.getSteps().add(PINStep.createDummy(passwordType)); ProcessingStep procStep = new ProcessingStep(); ProcessingStepAction procStepAction = new ProcessingStepAction(procStep); procStep.setAction(procStepAction); uc.getSteps().add(procStep); } else { // ErrorStep is currently not used and needs to be reworked in 1.3.X // disable the step here completely to avoid flashing of the step ui. // String pin = langPace.translationForKey("pin"); // String puk = langPace.translationForKey("puk"); // String title = langPace.translationForKey("step_error_title_blocked", pin); // String errorMsg = langPace.translationForKey("step_error_pin_blocked", pin, pin, puk, pin); // ErrorStep eStep = new ErrorStep(title, errorMsg); // uc.getSteps().add(eStep); dynCtx.put(EACProtocol.PACE_EXCEPTION, WSHelper.createException(WSHelper.makeResultError( ECardConstants.Minor.IFD.PASSWORD_BLOCKED, "The PIN is blocked."))); } Thread guiThread = new Thread(new Runnable() { @Override public void run() { // get context here because it is thread local DynamicContext dynCtx = DynamicContext.getInstance(TR03112Keys.INSTANCE_KEY); if (!uc.getSteps().isEmpty()) { UserConsentNavigator navigator = gui.obtainNavigator(uc); dynCtx.put(TR03112Keys.OPEN_USER_CONSENT_NAVIGATOR, navigator); ExecutionEngine exec = new ExecutionEngine(navigator); ResultStatus guiResult = exec.process(); dynCtx.put(EACProtocol.GUI_RESULT, guiResult); if (guiResult == ResultStatus.CANCEL) { Promise<Object> pPaceSuccessful = dynCtx.getPromise(EACProtocol.PACE_EXCEPTION); if (!pPaceSuccessful.isDelivered()) { pPaceSuccessful.deliver(WSHelper.createException(WSHelper.makeResultError( ECardConstants.Minor.SAL.CANCELLATION_BY_USER, "User canceled the PACE dialog."))); } } } } }, "EAC-GUI"); guiThread.start(); // wait for PACE to finish Promise<Object> pPaceException = dynCtx.getPromise(EACProtocol.PACE_EXCEPTION); Object pPaceError = pPaceException.deref(); if (pPaceError != null) { if (pPaceError instanceof WSHelper.WSException) { response.setResult(((WSHelper.WSException) pPaceError).getResult()); return response; } else if (pPaceError instanceof DispatcherException | pPaceError instanceof InvocationTargetException) { String msg = "Internal error while PACE authentication."; Result r = WSHelper.makeResultError(ECardConstants.Minor.App.INT_ERROR, msg); response.setResult(r); return response; } else { String msg = "Unknown error while PACE authentication."; Result r = WSHelper.makeResultError(ECardConstants.Minor.App.UNKNOWN_ERROR, msg); response.setResult(r); return response; } } // get challenge from card TerminalAuthentication ta = new TerminalAuthentication(dispatcher, slotHandle); byte[] challenge = ta.getChallenge(); // prepare DIDAuthenticationResponse DIDAuthenticationDataType data = eacData.paceResponse.getAuthenticationProtocolData(); AuthDataMap paceOutputMap = new AuthDataMap(data); //int retryCounter = Integer.valueOf(paceOutputMap.getContentAsString(PACEOutputType.RETRY_COUNTER)); byte[] efCardAccess = paceOutputMap.getContentAsBytes(PACEOutputType.EF_CARD_ACCESS); byte[] currentCAR = paceOutputMap.getContentAsBytes(PACEOutputType.CURRENT_CAR); byte[] previousCAR = paceOutputMap.getContentAsBytes(PACEOutputType.PREVIOUS_CAR); byte[] idpicc = paceOutputMap.getContentAsBytes(PACEOutputType.ID_PICC); // Store SecurityInfos SecurityInfos securityInfos = SecurityInfos.getInstance(efCardAccess); internalData.put(EACConstants.IDATA_SECURITY_INFOS, securityInfos); // Store additional data internalData.put(EACConstants.IDATA_AUTHENTICATED_AUXILIARY_DATA, aad); internalData.put(EACConstants.IDATA_CERTIFICATES, certChain); internalData.put(EACConstants.IDATA_CURRENT_CAR, currentCAR); internalData.put(EACConstants.IDATA_PREVIOUS_CAR, previousCAR); internalData.put(EACConstants.IDATA_CHALLENGE, challenge); // Create response //eac1Output.setRetryCounter(retryCounter); eac1Output.setCHAT(eacData.selectedCHAT.toByteArray()); eac1Output.setCurrentCAR(currentCAR); eac1Output.setPreviousCAR(previousCAR); eac1Output.setEFCardAccess(efCardAccess); eac1Output.setIDPICC(idpicc); eac1Output.setChallenge(challenge); response.setResult(WSHelper.makeResultOK()); response.setAuthenticationProtocolData(eac1Output.getAuthDataType()); } catch (CertificateException ex) { logger.error(ex.getMessage(), ex); String msg = ex.getMessage(); response.setResult(WSHelper.makeResultError(ECardConstants.Minor.SAL.EAC.DOC_VALID_FAILED, msg)); dynCtx.put(EACProtocol.AUTHENTICATION_FAILED, true); } catch (WSHelper.WSException e) { logger.error(e.getMessage(), e); response.setResult(e.getResult()); dynCtx.put(EACProtocol.AUTHENTICATION_FAILED, true); } catch (ElementParsingException ex) { logger.error(ex.getMessage(), ex); response.setResult(WSHelper.makeResultError(ECardConstants.Minor.App.INCORRECT_PARM, ex.getMessage())); dynCtx.put(EACProtocol.AUTHENTICATION_FAILED, true); } catch (Exception e) { logger.error(e.getMessage(), e); response.setResult(WSHelper.makeResultUnknownError(e.getMessage())); dynCtx.put(EACProtocol.AUTHENTICATION_FAILED, true); } return response; } private PACEMarkerType getPaceMarker(CardStateEntry cardState, String pinType) { // TODO: replace with DIDGet call byte[] applicationIdentifier = cardState.getCurrentCardApplication().getApplicationIdentifier(); DIDStructureType didStructure = cardState.getDIDStructure(pinType, applicationIdentifier); iso.std.iso_iec._24727.tech.schema.PACEMarkerType didMarker; didMarker = (iso.std.iso_iec._24727.tech.schema.PACEMarkerType) didStructure.getDIDMarker(); return new PACEMarkerType(didMarker); } private boolean convertToBoolean(Object o) { if (o instanceof Boolean) { return ((Boolean) o); } else { return false; } } /** * Perform all checks as described in BSI TR-03112-7 3.4.4. * * @param certDescription CertificateDescription of the eService Certificate * @param dynCtx Dynamic Context * @return a {@link Result} set according to the results of the checks */ private Result performChecks(CertificateDescription certDescription, DynamicContext dynCtx) { Object objectActivation = dynCtx.get(TR03112Keys.OBJECT_ACTIVATION); Object tokenChecks = dynCtx.get(TR03112Keys.TCTOKEN_CHECKS); boolean checkPassed; // omit these checks if explicitly disabled if (convertToBoolean(tokenChecks)) { checkPassed = checkEserviceCertificate(certDescription, dynCtx); if (! checkPassed) { String msg = "Hash of eService certificate is NOT contained in the CertificateDescription."; // TODO check for the correct minor type Result r = WSHelper.makeResultError(ECardConstants.Minor.SAL.PREREQUISITES_NOT_SATISFIED, msg); return r; } // only perform the following checks if new activation is used if (! convertToBoolean(objectActivation)) { checkPassed = checkTCTokenServerCertificates(certDescription, dynCtx); if (! checkPassed) { String msg = "Hash of the TCToken server certificate is NOT contained in the CertificateDescription."; // TODO check for the correct minor type Result r = WSHelper.makeResultError(ECardConstants.Minor.SAL.PREREQUISITES_NOT_SATISFIED, msg); return r; } checkPassed = checkTCTokenAndSubjectURL(certDescription, dynCtx); if (! checkPassed) { String msg = "TCToken does not come from the server to which the authorization certificate was issued."; // TODO check for the correct minor type Result r = WSHelper.makeResultError(ECardConstants.Minor.SAL.PREREQUISITES_NOT_SATISFIED, msg); return r; } } else { logger.warn("Checks according to BSI TR03112 3.4.4 (TCToken specific) skipped."); } } else { logger.warn("Checks according to BSI TR03112 3.4.4 skipped."); } // all checks passed return WSHelper.makeResultOK(); } private boolean checkTCTokenAndSubjectURL(CertificateDescription certDescription, DynamicContext dynCtx) { Object o = dynCtx.get(TR03112Keys.TCTOKEN_URL); if (o instanceof URL) { URL tcTokenURL = (URL) o; try { URL subjectURL = new URL(certDescription.getSubjectURL()); return TR03112Utils.checkSameOriginPolicy(tcTokenURL, subjectURL); } catch (MalformedURLException e) { logger.error("SubjectURL in CertificateDescription is not a well formed URL."); return false; } } else { logger.error("No TC Token URL set in Dynamic Context."); return false; } } private boolean checkEserviceCertificate(CertificateDescription certDescription, DynamicContext dynCtx) { Object o = dynCtx.get(TR03112Keys.ESERVICE_CERTIFICATE); if (o instanceof Certificate) { Certificate certificate = (Certificate) o; return TR03112Utils.isInCommCertificates(certificate, certDescription.getCommCertificates()); } else { logger.error("No eService TLS Certificate set in Dynamic Context."); return false; } } private boolean checkTCTokenServerCertificates(CertificateDescription certDescription, DynamicContext dynCtx) { Object o = dynCtx.get(TR03112Keys.TCTOKEN_SERVER_CERTIFICATES); if (o instanceof List) { List<?> certificates = (List<?>) o; for (Object cert : certificates) { if (cert instanceof Pair) { Pair<?, ?> p = (Pair<?, ?>) cert; if (p.p2 instanceof Certificate) { Certificate bcCert = (Certificate) p.p2; if (! TR03112Utils.isInCommCertificates(bcCert, certDescription.getCommCertificates())) { return false; } } } } return true; } else { logger.error("No TC Token server certificates set in Dynamic Context."); return false; } } /** * Check if the selected card reader supports PACE. * In that case, the reader is a standard or comfort reader. * * @param connectionHandle Handle describing the IFD and reader. * @return true when card reader supports genericPACE, false otherwise. * @throws Exception */ private boolean genericPACESupport(ConnectionHandleType connectionHandle) throws Exception { // Request terminal capabilities GetIFDCapabilities capabilitiesRequest = new GetIFDCapabilities(); capabilitiesRequest.setContextHandle(connectionHandle.getContextHandle()); capabilitiesRequest.setIFDName(connectionHandle.getIFDName()); GetIFDCapabilitiesResponse capabilitiesResponse = (GetIFDCapabilitiesResponse) dispatcher.deliver(capabilitiesRequest); WSHelper.checkResult(capabilitiesResponse); if (capabilitiesResponse.getIFDCapabilities() != null) { List<SlotCapabilityType> capabilities = capabilitiesResponse.getIFDCapabilities().getSlotCapability(); // Check all capabilities for generic PACE final String genericPACE = PACECapabilities.PACECapability.GenericPACE.getProtocol(); for (SlotCapabilityType capability : capabilities) { if (capability.getIndex().equals(connectionHandle.getSlotIndex())) { for (String protocol : capability.getProtocol()) { if (protocol.equals(genericPACE)) { return true; } } } } } // No PACE capability found return false; } }