/* * Copyright (C) 2017 Schürmann & Breitmoser GbR * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.securitytoken; import android.nfc.Tag; import androidx.annotation.Nullable; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.securitytoken.SecurityTokenInfo.TokenType; import org.sufficientlysecure.keychain.securitytoken.SecurityTokenInfo.TransportType; import java.io.IOException; import nordpol.IsoCard; import nordpol.android.AndroidCard; import timber.log.Timber; import static org.bouncycastle.util.encoders.Hex.toHexString; public class NfcTransport implements Transport { // timeout is set to 100 seconds to avoid cancellation during calculation private static final int TIMEOUT = 100 * 1000; private final Tag mTag; private IsoCard mIsoCard; public static class IsoDepNotSupportedException extends IOException { IsoDepNotSupportedException(String detailMessage) { super(detailMessage); } } public NfcTransport(Tag tag) { this.mTag = tag; } /** * Transmit and receive data * @param data data to transmit * @return received data * @throws IOException */ @Override public ResponseApdu transceive(final CommandApdu data) throws IOException { byte[] rawCommand = data.toBytes(); if (Constants.DEBUG) { Timber.d("nfc out: " + toHexString(rawCommand)); } byte[] rawResponse = mIsoCard.transceive(rawCommand); if (Constants.DEBUG) { Timber.d("nfc in: " + toHexString(rawResponse)); } return ResponseApdu.fromBytes(rawResponse); } /** * Disconnect and release connection */ @Override public void release() { // Not supported } @Override public boolean isConnected() { return mIsoCard != null && mIsoCard.isConnected(); } /** * Check if Transport supports persistent connections e.g connections which can * handle multiple operations in one session * @return true if transport supports persistent connections */ @Override public boolean isPersistentConnectionAllowed() { return false; } /** * Connect to NFC device. *

* On general communication, see also * http://www.cardwerk.com/smartcards/smartcard_standard_ISO7816-4_annex-a.aspx *

* References to pages are generally related to the OpenPGP Application * on ISO SmartCard Systems specification. */ @Override public void connect() throws IOException { mIsoCard = AndroidCard.get(mTag); if (mIsoCard == null) { throw new IsoDepNotSupportedException("Tag does not support ISO-DEP (ISO 14443-4)"); } mIsoCard.setTimeout(TIMEOUT); mIsoCard.connect(); } @Override public TransportType getTransportType() { return TransportType.NFC; } @Nullable @Override public TokenType getTokenTypeIfAvailable() { // Sadly, the NFC transport has no direct information about the token type. return null; } @Override public boolean equals(final Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; final NfcTransport that = (NfcTransport) o; if (mTag != null ? !mTag.equals(that.mTag) : that.mTag != null) return false; if (mIsoCard != null ? !mIsoCard.equals(that.mIsoCard) : that.mIsoCard != null) return false; return true; } @Override public int hashCode() { int result = mTag != null ? mTag.hashCode() : 0; result = 31 * result + (mIsoCard != null ? mIsoCard.hashCode() : 0); return result; } }