-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Setup NFC, select applet, read TOTP code
- Loading branch information
Showing
1 changed file
with
101 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,7 +18,7 @@ | |
import nordpol.android.OnDiscoveredTagListener; | ||
|
||
@EActivity(R.layout.activity_main) | ||
public class MainActivity extends AppCompatActivity { | ||
public class MainActivity extends AppCompatActivity implements OnDiscoveredTagListener { | ||
|
||
private static final String TAG = "MainActivity"; | ||
|
||
|
@@ -28,6 +28,9 @@ public class MainActivity extends AppCompatActivity { | |
//Hardcoded TOTP key put onto the OTP applet beforehand | ||
private static final String TOTP_CODE_NAME = "FidesmoOTPTutorial:[email protected]"; | ||
|
||
// The TagDispatcher is responsible for managing the NFC for the activity | ||
private TagDispatcher tagDispatcher; | ||
|
||
// UI elements | ||
@ViewById | ||
TextView mainText; | ||
|
@@ -47,16 +50,113 @@ void setMainMessage(String text) { | |
@Override | ||
protected void onResume() { | ||
super.onResume(); | ||
// The first argument is the activity for which the NFC is managed | ||
// The second argument is the OnDiscoveredTagListener which is also | ||
// implemented by this activity | ||
// This means that the method tagDiscovered will be called whenever a new tag appears | ||
tagDispatcher = TagDispatcher.get(this, this); | ||
// Start listening on the NFC interface when the app gains focus. | ||
tagDispatcher.enableExclusiveNfc(); | ||
} | ||
|
||
@Override | ||
public void onPause() { | ||
super.onPause(); | ||
// Stop listening on the NFC interface if the app loses focus | ||
tagDispatcher.disableExclusiveNfc(); | ||
} | ||
|
||
/** | ||
* This method is called when a contactless device is detected at the NFC interface | ||
* @param intent the PendingIntent declared in onResume | ||
*/ | ||
@Override | ||
protected void onNewIntent(Intent intent) { | ||
tagDispatcher.interceptIntent(intent); | ||
} | ||
|
||
//Called whenever a tag is discovered on the NFC interface | ||
@Override | ||
public void tagDiscovered(Tag tag) { | ||
setMainMessage("Reading card..."); | ||
/** | ||
* As the user might remove the card before we are done communicating with it we have to | ||
* make sure to catch that exception if it happens. We do this with a | ||
* try { ... } catch { ... } | ||
*/ | ||
try { | ||
/** | ||
* Create an IsoCard that we can communicate with. Nordpols version of IsoCard | ||
* AndroidCard has a get method to create an IsoCard from a tag. | ||
*/ | ||
IsoCard isoCard = AndroidCard.get(tag); | ||
//Send on the IsoCard | ||
communicateWithCard(isoCard); | ||
} catch(IOException e) { | ||
e.printStackTrace(); | ||
} | ||
} | ||
|
||
protected void communicateWithCard(IsoCard isoCard) { | ||
/** | ||
* The user might still remove the card before we are done with it, remember? Try catch | ||
* that functionality! | ||
*/ | ||
try { | ||
//Open a connection to the card | ||
isoCard.connect(); | ||
/** | ||
* Send the first command to the card. This command has to be a select command to ensure | ||
* that we are communicating with the correct applet on the card. The OTP_AID is | ||
* the identifier of the OTP applet installed on the card. The Nordpol method | ||
* transceiveAndRequireOk() ensures that the command specified returned | ||
* success (OK_APDU) otherwise we continue to catch {}. | ||
*/ | ||
Apdu.transceiveAndRequireOk(Apdu.select(OTP_AID), isoCard); | ||
setMainMessage("Select OK"); | ||
|
||
//Get a time stamp for creating a TOTP code APDU from | ||
long timeStamp = getTimeStamp(); | ||
|
||
/** | ||
* Get the APDU command copied from Yubicos open source app for generating TOTP codes. | ||
* Specify that the TOTP code with the name TOTP_CODE_NAME is fetched. This "name" | ||
* should ideally by generated and not hardcoded and supplied when the TOTP token is | ||
* input into the OTP applet. Knowing this, let's hardcode it for the sake of this | ||
* tutorial with the value that we scanned/input into the applet before using some | ||
* other app like Yubico Authenticator. | ||
*/ | ||
byte[] totpCodeApdu = Otp.totpCodeApdu(TOTP_CODE_NAME, timeStamp); | ||
|
||
/** | ||
* Send the command to the IsoCard using Nordpol method transceiveAndGetResponse. | ||
* This method returns the response and automatically batches long messages. | ||
* The first parameter is the APDU command. The second parameter is the IsoCard to | ||
* communicate with. The third parameter is an optional and applet specific APDU | ||
* command for requesting more content from the applet. I found this on in the open | ||
* source code of Yubico Authenticator | ||
*/ | ||
byte[] rawTotpCode = Apdu.transceiveAndGetResponse(totpCodeApdu, isoCard, Otp.SEND_REMAINING_APDU); | ||
/** | ||
* Check the result of the communication with the Nordpol method hasStatus. Supply the | ||
* the response APDU and the expected APDU as parameters. If the ending of the first | ||
* parameter matches the second parameter it is considered a match. | ||
*/ | ||
if(Apdu.hasStatus(rawTotpCode, Apdu.OK_APDU)){ | ||
/** | ||
* Decipher the TOTP code from the gibberish the response consists of (using | ||
* Yubicos decipher methods). | ||
*/ | ||
String totpCode = Otp.decipherTotpCode(rawTotpCode); | ||
//Append the TOTP code to the TextView | ||
setMainMessage("TOTP code is: " + totpCode); | ||
} | ||
|
||
//Close the card communication. | ||
isoCard.close(); | ||
} catch (IOException e) { | ||
e.printStackTrace(); | ||
} | ||
} | ||
|
||
private static long getTimeStamp() { | ||
|