2021-10-05 23:52:27 +03:00
package com.tarkvaraprojekt.mobileauthapp.NFC ;
2021-10-04 23:50:14 +03:00
import android.nfc.tech.IsoDep ;
import android.util.Log ;
import org.bouncycastle.crypto.BlockCipher ;
import org.bouncycastle.crypto.engines.AESEngine ;
import org.bouncycastle.crypto.macs.CMac ;
import org.bouncycastle.crypto.params.KeyParameter ;
import org.bouncycastle.jce.ECNamedCurveTable ;
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec ;
import org.bouncycastle.math.ec.ECPoint ;
import org.bouncycastle.util.encoders.Hex ;
import java.io.IOException ;
import java.math.BigInteger ;
import java.nio.charset.StandardCharsets ;
import java.security.InvalidAlgorithmParameterException ;
import java.security.InvalidKeyException ;
import java.security.MessageDigest ;
import java.security.NoSuchAlgorithmException ;
import java.security.SecureRandom ;
import java.util.Arrays ;
2021-11-08 22:41:09 +02:00
import java.util.Base64 ;
2021-10-04 23:50:14 +03:00
import javax.crypto.BadPaddingException ;
import javax.crypto.Cipher ;
import javax.crypto.IllegalBlockSizeException ;
import javax.crypto.NoSuchPaddingException ;
import javax.crypto.spec.IvParameterSpec ;
import javax.crypto.spec.SecretKeySpec ;
public class Comms {
2021-10-14 19:50:53 +03:00
private static final byte [ ] selectMaster = Hex . decode ( " 00a4040c10a000000077010800070000fe00000100 " ) ;
2021-10-04 23:50:14 +03:00
2021-10-14 19:50:53 +03:00
private static final byte [ ] MSESetAT = Hex . decode ( " 0022c1a40f800a04007f0007020204020483010200 " ) ;
2021-10-04 23:50:14 +03:00
2021-10-14 19:50:53 +03:00
private static final byte [ ] GAGetNonce = Hex . decode ( " 10860000027c0000 " ) ;
2021-10-04 23:50:14 +03:00
2021-10-14 19:50:53 +03:00
private static final byte [ ] GAMapNonceIncomplete = Hex . decode ( " 10860000457c438141 " ) ;
2021-10-05 19:58:27 +03:00
2021-10-14 19:50:53 +03:00
private static final byte [ ] GAKeyAgreementIncomplete = Hex . decode ( " 10860000457c438341 " ) ;
2021-10-04 23:50:14 +03:00
2021-10-14 19:50:53 +03:00
private static final byte [ ] GAMutualAuthenticationIncomplete = Hex . decode ( " 008600000c7c0a8508 " ) ;
2021-10-04 23:50:14 +03:00
2021-10-14 19:50:53 +03:00
private static final byte [ ] dataForMACIncomplete = Hex . decode ( " 7f494f060a04007f000702020402048641 " ) ;
2021-10-04 23:50:14 +03:00
2021-10-14 19:50:53 +03:00
private static final byte [ ] selectFile = Hex . decode ( " 0ca4010c1d871101 " ) ;
2021-10-12 00:34:06 +03:00
2021-10-14 19:50:53 +03:00
private static final byte [ ] readFile = Hex . decode ( " 0cb000000d970100 " ) ;
2021-10-04 23:50:14 +03:00
2021-10-14 19:50:53 +03:00
private static final byte [ ] verifyPIN1 = Hex . decode ( " 0c2000011d871101 " ) ;
2021-10-04 23:50:14 +03:00
2021-10-14 19:50:53 +03:00
private static final byte [ ] verifyPIN2 = Hex . decode ( " 0c2000851d871101 " ) ;
2021-10-14 03:58:49 +03:00
2021-10-19 00:58:53 +03:00
private static final byte [ ] MSESetEnv = Hex . decode ( " 0c2241A41d871101 " ) ;
private static final byte [ ] Env = Hex . decode ( " 8004FF200800840181 " ) ;
private static final byte [ ] InternalAuthenticate = Hex . decode ( " 0c8800001d871101 " ) ;
2021-10-14 19:50:53 +03:00
private static final byte [ ] IASECCFID = { 0x3f , 0x00 } ;
private static final byte [ ] personalDF = { 0x50 , 0x00 } ;
private static final byte [ ] AWP = { ( byte ) 0xad , ( byte ) 0xf1 } ;
private static final byte [ ] QSCD = { ( byte ) 0xad , ( byte ) 0xf2 } ;
private static final byte [ ] authCert = { 0x34 , 0x01 } ;
private static final byte [ ] signCert = { 0x34 , 0x1f } ;
2021-10-14 03:58:49 +03:00
2021-10-12 00:34:06 +03:00
private final IsoDep idCard ;
2021-10-05 19:58:27 +03:00
private final byte [ ] keyEnc ;
private final byte [ ] keyMAC ;
private byte ssc ; // Send sequence counter.
/ * *
* The constructor performs PACE and stores the session keys
*
* @param idCard link to the card
* @param CAN the card authentication number
* /
public Comms ( IsoDep idCard , String CAN ) throws IOException , NoSuchPaddingException , InvalidKeyException , NoSuchAlgorithmException , IllegalBlockSizeException , BadPaddingException , InvalidAlgorithmParameterException {
idCard . connect ( ) ;
this . idCard = idCard ;
2021-10-19 00:58:53 +03:00
byte [ ] [ ] keys = PACE ( CAN . getBytes ( StandardCharsets . UTF_8 ) ) ;
2021-10-05 19:58:27 +03:00
keyEnc = keys [ 0 ] ;
keyMAC = keys [ 1 ] ;
}
2021-10-04 23:50:14 +03:00
/ * *
* Calculates the message authentication code
2021-10-05 19:58:27 +03:00
*
* @param APDU the byte array on which the CMAC algorithm is performed
* @param keyMAC the key for performing CMAC
2021-10-04 23:50:14 +03:00
* @return MAC
* /
2021-10-05 19:58:27 +03:00
private byte [ ] getMAC ( byte [ ] APDU , byte [ ] keyMAC ) {
2021-10-04 23:50:14 +03:00
BlockCipher blockCipher = new AESEngine ( ) ;
CMac cmac = new CMac ( blockCipher ) ;
cmac . init ( new KeyParameter ( keyMAC ) ) ;
cmac . update ( APDU , 0 , APDU . length ) ;
byte [ ] MAC = new byte [ cmac . getMacSize ( ) ] ;
cmac . doFinal ( MAC , 0 ) ;
return Arrays . copyOf ( MAC , 8 ) ;
}
/ * *
* Creates an application protocol data unit
2021-10-05 19:58:27 +03:00
*
2021-10-04 23:50:14 +03:00
* @param template the byte array to be used as a template
2021-10-05 19:58:27 +03:00
* @param data the data necessary for completing the APDU
* @param extra the missing length of the APDU being created
2021-10-04 23:50:14 +03:00
* @return the complete APDU
* /
private byte [ ] createAPDU ( byte [ ] template , byte [ ] data , int extra ) {
byte [ ] APDU = Arrays . copyOf ( template , template . length + extra ) ;
System . arraycopy ( data , 0 , APDU , template . length , data . length ) ;
return APDU ;
}
/ * *
* Creates a cipher key
2021-10-05 19:58:27 +03:00
*
2021-10-04 23:50:14 +03:00
* @param unpadded the array to be used as the basis for the key
2021-10-05 19:58:27 +03:00
* @param last the last byte in the appended padding
2021-10-04 23:50:14 +03:00
* @return the constructed key
* /
private byte [ ] createKey ( byte [ ] unpadded , byte last ) throws NoSuchAlgorithmException {
byte [ ] padded = Arrays . copyOf ( unpadded , unpadded . length + 4 ) ;
padded [ padded . length - 1 ] = last ;
MessageDigest messageDigest = MessageDigest . getInstance ( " SHA-256 " ) ;
return messageDigest . digest ( padded ) ;
}
/ * *
* Decrypts the nonce
2021-10-05 19:58:27 +03:00
*
2021-10-04 23:50:14 +03:00
* @param encryptedNonce the encrypted nonce received from the chip
2021-10-05 19:58:27 +03:00
* @param CAN the card access number provided by the user
2021-10-04 23:50:14 +03:00
* @return the decrypted nonce
* /
2021-10-19 00:58:53 +03:00
private byte [ ] decryptNonce ( byte [ ] encryptedNonce , byte [ ] CAN ) throws NoSuchPaddingException , InvalidKeyException , NoSuchAlgorithmException , IllegalBlockSizeException , BadPaddingException , InvalidAlgorithmParameterException {
byte [ ] decryptionKey = createKey ( CAN , ( byte ) 3 ) ;
2021-10-04 23:50:14 +03:00
Cipher cipher = Cipher . getInstance ( " AES/CBC/NoPadding " ) ;
cipher . init ( Cipher . DECRYPT_MODE , new SecretKeySpec ( decryptionKey , " AES " ) , new IvParameterSpec ( new byte [ 16 ] ) ) ;
return cipher . doFinal ( encryptedNonce ) ;
}
2021-10-19 00:58:53 +03:00
/ * *
* Communicates with the card and logs the response
*
* @param APDU The command
* @param log Information for logging
* @return The response
* /
private byte [ ] getResponse ( byte [ ] APDU , String log ) throws IOException {
byte [ ] response = idCard . transceive ( APDU ) ;
if ( response [ response . length - 2 ] ! = ( byte ) 0x90 | | response [ response . length - 1 ] ! = 0x00 ) {
throw new RuntimeException ( String . format ( " %s failed. " , log ) ) ;
}
Log . i ( log , Hex . toHexString ( response ) ) ;
return response ;
}
2021-10-04 23:50:14 +03:00
/ * *
* Attempts to use the PACE protocol to create a secure channel with an Estonian ID - card
2021-10-05 19:58:27 +03:00
*
* @param CAN the card access number
2021-10-04 23:50:14 +03:00
* /
2021-10-19 00:58:53 +03:00
private byte [ ] [ ] PACE ( byte [ ] CAN ) throws IOException , NoSuchPaddingException , InvalidAlgorithmParameterException , NoSuchAlgorithmException , IllegalBlockSizeException , BadPaddingException , InvalidKeyException {
2021-10-04 23:50:14 +03:00
2021-10-12 00:34:06 +03:00
// select the IAS-ECC application on the chip
2021-10-19 00:58:53 +03:00
getResponse ( selectMaster , " Select the master application " ) ;
2021-10-04 23:50:14 +03:00
// initiate PACE
2021-10-19 00:58:53 +03:00
getResponse ( MSESetAT , " Set authentication template " ) ;
2021-10-04 23:50:14 +03:00
// get nonce
2021-10-19 00:58:53 +03:00
byte [ ] response = getResponse ( GAGetNonce , " Get nonce " ) ;
2021-10-05 19:58:27 +03:00
byte [ ] decryptedNonce = decryptNonce ( Arrays . copyOfRange ( response , 4 , response . length - 2 ) , CAN ) ;
2021-10-04 23:50:14 +03:00
// generate an EC keypair and exchange public keys with the chip
2021-10-05 19:58:27 +03:00
ECNamedCurveParameterSpec spec = ECNamedCurveTable . getParameterSpec ( " secp256r1 " ) ;
BigInteger privateKey = new BigInteger ( 255 , new SecureRandom ( ) ) . add ( BigInteger . ONE ) ; // should be in [1, spec.getN()-1], but this is good enough for this application
ECPoint publicKey = spec . getG ( ) . multiply ( privateKey ) . normalize ( ) ;
2021-10-19 00:58:53 +03:00
response = getResponse ( createAPDU ( GAMapNonceIncomplete , publicKey . getEncoded ( false ) , 66 ) , " Map nonce " ) ;
2021-10-05 19:58:27 +03:00
ECPoint cardPublicKey = spec . getCurve ( ) . decodePoint ( Arrays . copyOfRange ( response , 4 , 69 ) ) ;
2021-10-04 23:50:14 +03:00
// calculate the new base point, use it to generate a new keypair, and exchange public keys
2021-10-05 19:58:27 +03:00
ECPoint sharedSecret = cardPublicKey . multiply ( privateKey ) ;
ECPoint mappedECBasePoint = spec . getG ( ) . multiply ( new BigInteger ( 1 , decryptedNonce ) ) . add ( sharedSecret ) . normalize ( ) ;
2021-10-04 23:50:14 +03:00
privateKey = new BigInteger ( 255 , new SecureRandom ( ) ) . add ( BigInteger . ONE ) ;
publicKey = mappedECBasePoint . multiply ( privateKey ) . normalize ( ) ;
2021-10-19 00:58:53 +03:00
response = getResponse ( createAPDU ( GAKeyAgreementIncomplete , publicKey . getEncoded ( false ) , 66 ) , " Key agreement " ) ;
2021-10-04 23:50:14 +03:00
cardPublicKey = spec . getCurve ( ) . decodePoint ( Arrays . copyOfRange ( response , 4 , 69 ) ) ;
// generate the session keys and exchange MACs to verify them
2021-10-19 00:58:53 +03:00
byte [ ] secret = cardPublicKey . multiply ( privateKey ) . normalize ( ) . getAffineXCoord ( ) . getEncoded ( ) ;
byte [ ] keyEnc = createKey ( secret , ( byte ) 1 ) ;
byte [ ] keyMAC = createKey ( secret , ( byte ) 2 ) ;
byte [ ] MAC = getMAC ( createAPDU ( dataForMACIncomplete , cardPublicKey . getEncoded ( false ) , 65 ) , keyMAC ) ;
response = getResponse ( createAPDU ( GAMutualAuthenticationIncomplete , MAC , 9 ) , " Mutual authentication " ) ;
// verify chip's MAC and return session keys
MAC = getMAC ( createAPDU ( dataForMACIncomplete , publicKey . getEncoded ( false ) , 65 ) , keyMAC ) ;
2021-10-05 19:58:27 +03:00
if ( ! Hex . toHexString ( response , 4 , 8 ) . equals ( Hex . toHexString ( MAC ) ) ) {
2021-10-13 02:27:19 +03:00
throw new RuntimeException ( " Could not verify chip's MAC. " ) ; // *Should* never happen.
2021-10-05 19:58:27 +03:00
}
2021-10-04 23:50:14 +03:00
return new byte [ ] [ ] { keyEnc , keyMAC } ;
}
2021-10-12 00:34:06 +03:00
/ * *
* Selects a file and reads its contents
*
* @param FID file identifier of the required file
2021-10-14 03:58:49 +03:00
* @param info string for logging
2021-10-12 00:34:06 +03:00
* @return decrypted file contents
* /
2021-10-14 03:58:49 +03:00
private byte [ ] readFile ( byte [ ] FID , String info ) throws NoSuchPaddingException , InvalidKeyException , NoSuchAlgorithmException , IllegalBlockSizeException , BadPaddingException , InvalidAlgorithmParameterException , IOException {
selectFile ( FID , info ) ;
2021-10-19 00:58:53 +03:00
byte [ ] response = getResponse ( new byte [ 0 ] , readFile , " Read binary " ) ;
if ( response [ response . length - 2 ] ! = ( byte ) 0x90 | | response [ response . length - 1 ] ! = 0x00 ) {
2021-10-14 03:58:49 +03:00
throw new RuntimeException ( String . format ( " Could not read %s " , info ) ) ;
}
2021-10-12 00:34:06 +03:00
return encryptDecryptData ( Arrays . copyOfRange ( response , 3 , 19 ) , Cipher . DECRYPT_MODE ) ;
}
2021-10-04 23:50:14 +03:00
/ * *
* Encrypts or decrypts the APDU data
2021-10-05 19:58:27 +03:00
*
* @param data the array containing the data to be processed
* @param mode indicates whether to en - or decrypt the data
2021-10-04 23:50:14 +03:00
* @return the result of encryption or decryption
* /
2021-10-05 19:58:27 +03:00
private byte [ ] encryptDecryptData ( byte [ ] data , int mode ) throws NoSuchPaddingException , NoSuchAlgorithmException , InvalidKeyException , BadPaddingException , IllegalBlockSizeException , InvalidAlgorithmParameterException {
2021-10-04 23:50:14 +03:00
SecretKeySpec secretKeySpec = new SecretKeySpec ( keyEnc , " AES " ) ;
Cipher cipher = Cipher . getInstance ( " AES/ECB/PKCS5Padding " ) ;
cipher . init ( Cipher . ENCRYPT_MODE , secretKeySpec ) ;
byte [ ] iv = Arrays . copyOf ( cipher . doFinal ( new byte [ ] { 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , ssc } ) , 16 ) ;
cipher = Cipher . getInstance ( " AES/CBC/NoPadding " ) ;
cipher . init ( mode , secretKeySpec , new IvParameterSpec ( iv ) ) ;
return cipher . doFinal ( data ) ;
}
/ * *
* Constructs APDUs suitable for the secure channel .
2021-10-05 19:58:27 +03:00
*
* @param data the data to be encrypted
2021-10-04 23:50:14 +03:00
* @param incomplete the array to be used as a template
* @return the constructed APDU
* /
2021-10-05 19:58:27 +03:00
private byte [ ] createSecureAPDU ( byte [ ] data , byte [ ] incomplete ) throws NoSuchPaddingException , InvalidAlgorithmParameterException , NoSuchAlgorithmException , IllegalBlockSizeException , BadPaddingException , InvalidKeyException {
2021-10-04 23:50:14 +03:00
ssc + + ;
byte [ ] encryptedData = new byte [ 0 ] ;
int length = 16 * ( 1 + data . length / 16 ) ;
// construct the required array and calculate the MAC based on it
byte [ ] macData = new byte [ data . length > 0 ? 48 + length : 48 ] ;
macData [ 15 ] = ssc ; // first block contains the ssc
System . arraycopy ( incomplete , 0 , macData , 16 , 4 ) ; // second block has the command
2021-10-19 00:58:53 +03:00
macData [ 20 ] = ( byte ) 0x80 ; // elements are terminated by 0x80 and zero-padded to the next block
2021-10-04 23:50:14 +03:00
System . arraycopy ( incomplete , 5 , macData , 32 , 3 ) ; // third block contains appropriately encapsulated data/Le
if ( data . length > 0 ) { // if the APDU has data, add padding and encrypt it
byte [ ] paddedData = Arrays . copyOf ( data , length ) ;
2021-10-19 00:58:53 +03:00
paddedData [ data . length ] = ( byte ) 0x80 ;
2021-10-05 19:58:27 +03:00
encryptedData = encryptDecryptData ( paddedData , Cipher . ENCRYPT_MODE ) ;
2021-10-04 23:50:14 +03:00
System . arraycopy ( encryptedData , 0 , macData , 35 , encryptedData . length ) ;
}
2021-10-19 00:58:53 +03:00
macData [ 35 + encryptedData . length ] = ( byte ) 0x80 ;
2021-10-05 19:58:27 +03:00
byte [ ] MAC = getMAC ( macData , keyMAC ) ;
2021-10-04 23:50:14 +03:00
// construct the APDU using the encrypted data and the MAC
2021-10-12 00:34:06 +03:00
byte [ ] APDU = Arrays . copyOf ( incomplete , incomplete . length + encryptedData . length + MAC . length + 3 ) ;
2021-10-04 23:50:14 +03:00
if ( encryptedData . length > 0 ) {
System . arraycopy ( encryptedData , 0 , APDU , incomplete . length , encryptedData . length ) ;
}
2021-10-14 19:50:53 +03:00
System . arraycopy ( new byte [ ] { ( byte ) 0x8E , 0x08 } , 0 , APDU , incomplete . length + encryptedData . length , 2 ) ; // MAC is encapsulated using the tag 0x8E
2021-10-04 23:50:14 +03:00
System . arraycopy ( MAC , 0 , APDU , incomplete . length + encryptedData . length + 2 , MAC . length ) ;
ssc + + ;
return APDU ;
}
2021-10-12 00:34:06 +03:00
/ * *
2021-10-14 03:58:49 +03:00
* Selects a FILE by its identifier
*
2021-10-12 00:34:06 +03:00
* /
2021-10-14 03:58:49 +03:00
private void selectFile ( byte [ ] FID , String info ) throws NoSuchPaddingException , InvalidKeyException , NoSuchAlgorithmException , IllegalBlockSizeException , BadPaddingException , InvalidAlgorithmParameterException , IOException {
2021-10-19 00:58:53 +03:00
byte [ ] response = getResponse ( FID , selectFile , String . format ( " Select %s " , info ) ) ;
if ( response [ response . length - 2 ] ! = ( byte ) 0x90 | | response [ response . length - 1 ] ! = 0x00 ) {
2021-10-14 03:58:49 +03:00
throw new RuntimeException ( String . format ( " Could not select %s " , info ) ) ;
2021-10-12 00:34:06 +03:00
}
}
2021-10-04 23:50:14 +03:00
/ * *
2021-10-05 19:58:27 +03:00
* Gets the contents of the personal data dedicated file
*
2021-10-12 00:34:06 +03:00
* @param lastBytes the last bytes of the personal data file identifiers ( 0 < x < 16 )
* @return array containing the corresponding data strings
2021-10-04 23:50:14 +03:00
* /
2021-10-12 00:34:06 +03:00
public String [ ] readPersonalData ( byte [ ] lastBytes ) throws NoSuchPaddingException , InvalidKeyException , NoSuchAlgorithmException , IllegalBlockSizeException , BadPaddingException , InvalidAlgorithmParameterException , IOException {
String [ ] personalData = new String [ lastBytes . length ] ;
int stringIndex = 0 ;
2021-10-04 23:50:14 +03:00
2021-10-14 03:58:49 +03:00
// select the master application
selectFile ( IASECCFID , " the master application " ) ;
2021-10-04 23:50:14 +03:00
// select the personal data dedicated file
2021-10-14 03:58:49 +03:00
selectFile ( personalDF , " the personal data DF " ) ;
2021-10-04 23:50:14 +03:00
2021-10-14 03:58:49 +03:00
byte [ ] FID = Arrays . copyOf ( personalDF , personalDF . length ) ;
2021-10-12 00:34:06 +03:00
// select and read the personal data elementary files
for ( byte index : lastBytes ) {
2021-10-04 23:50:14 +03:00
2021-10-05 19:58:27 +03:00
if ( index > 15 | | index < 1 ) throw new RuntimeException ( " Invalid personal data FID. " ) ;
2021-10-12 00:34:06 +03:00
FID [ 1 ] = index ;
2021-10-04 23:50:14 +03:00
// store the decrypted datum
2021-10-14 03:58:49 +03:00
byte [ ] response = readFile ( FID , " a personal data EF " ) ;
2021-10-12 00:34:06 +03:00
int indexOfTerminator = Hex . toHexString ( response ) . lastIndexOf ( " 80 " ) / 2 ;
personalData [ stringIndex + + ] = new String ( Arrays . copyOfRange ( response , 0 , indexOfTerminator ) ) ;
2021-10-05 19:58:27 +03:00
2021-10-04 23:50:14 +03:00
}
return personalData ;
}
2021-10-12 00:34:06 +03:00
/ * *
2021-10-14 03:58:49 +03:00
* Attempts to verify the selected PIN
2021-10-12 00:34:06 +03:00
*
2021-10-14 03:58:49 +03:00
* @param PIN user - provided PIN
* @param oneOrTwo true for PIN1 , false for PIN2
2021-10-12 00:34:06 +03:00
* /
2021-10-19 00:58:53 +03:00
private void verifyPIN ( byte [ ] PIN , boolean oneOrTwo ) throws NoSuchPaddingException , InvalidAlgorithmParameterException , NoSuchAlgorithmException , IllegalBlockSizeException , BadPaddingException , InvalidKeyException , IOException {
2021-10-13 02:27:19 +03:00
2021-10-14 03:58:49 +03:00
selectFile ( IASECCFID , " the master application " ) ;
if ( ! oneOrTwo ) {
selectFile ( QSCD , " the application " ) ;
}
2021-10-13 02:27:19 +03:00
2021-10-14 03:58:49 +03:00
// pad the PIN and use the chip for verification
2021-10-19 00:58:53 +03:00
byte [ ] paddedPIN = Hex . decode ( " ffffffffffffffffffffffff " ) ;
System . arraycopy ( PIN , 0 , paddedPIN , 0 , PIN . length ) ;
byte [ ] response = getResponse ( paddedPIN , oneOrTwo ? verifyPIN1 : verifyPIN2 , " PIN verification " ) ;
2021-10-13 02:27:19 +03:00
2021-10-19 00:58:53 +03:00
if ( response [ response . length - 2 ] ! = ( byte ) 0x90 | | response [ response . length - 1 ] ! = 0x00 ) {
if ( response [ response . length - 2 ] = = 0x69 & & response [ response . length - 1 ] = = ( byte ) 0x83 ) {
2021-10-14 03:58:49 +03:00
throw new RuntimeException ( " Invalid PIN. Authentication method blocked. " ) ;
2021-10-13 02:27:19 +03:00
} else {
2021-10-19 00:58:53 +03:00
throw new RuntimeException ( String . format ( " Invalid PIN. Attempts left: %d. " , response [ response . length - 1 ] + 64 ) ) ;
2021-10-13 02:27:19 +03:00
}
2021-10-12 00:34:06 +03:00
}
}
2021-10-12 00:36:08 +03:00
/ * *
2021-10-14 03:58:49 +03:00
* Retrieves the authentication or signature certificate from the chip
2021-10-12 00:36:08 +03:00
*
2021-10-14 03:58:49 +03:00
* @param authOrSign true for auth , false for sign cert
* @return the requested certificate
2021-10-12 00:36:08 +03:00
* /
2021-10-14 03:58:49 +03:00
public byte [ ] getCertificate ( boolean authOrSign ) throws NoSuchPaddingException , InvalidAlgorithmParameterException , NoSuchAlgorithmException , IllegalBlockSizeException , BadPaddingException , InvalidKeyException , IOException {
2021-10-12 00:36:08 +03:00
2021-10-14 03:58:49 +03:00
selectFile ( IASECCFID , " the master application " ) ;
2021-10-12 12:18:06 +03:00
2021-10-14 03:58:49 +03:00
selectFile ( authOrSign ? AWP : QSCD , " the application " ) ;
2021-10-12 12:18:06 +03:00
2021-10-14 03:58:49 +03:00
selectFile ( authOrSign ? authCert : signCert , " the certificate " ) ;
2021-10-12 12:18:06 +03:00
2021-10-13 02:27:19 +03:00
byte [ ] certificate = new byte [ 0 ] ;
2021-10-14 19:50:53 +03:00
byte [ ] readCert = Arrays . copyOf ( readFile , readFile . length ) ;
2021-10-13 02:27:19 +03:00
// Construct the certificate byte array n=indexOfTerminator bytes at a time
for ( int i = 0 ; i < 16 ; i + + ) {
2021-10-12 12:18:06 +03:00
2021-10-13 02:27:19 +03:00
// Set the P1/P2 values to incrementally read the certificate
readCert [ 2 ] = ( byte ) ( certificate . length / 256 ) ;
readCert [ 3 ] = ( byte ) ( certificate . length % 256 ) ;
2021-10-19 00:58:53 +03:00
byte [ ] response = getResponse ( new byte [ 0 ] , readCert , " Read the certificate " ) ;
if ( response [ response . length - 2 ] = = 0x6b & & response [ response . length - 1 ] = = 0x00 ) {
2021-10-13 02:27:19 +03:00
throw new RuntimeException ( " Wrong read parameters. " ) ;
2021-10-12 12:18:06 +03:00
}
2021-10-13 02:27:19 +03:00
// Set the range containing a portion of the certificate and decrypt it
int start = response [ 2 ] = = 1 ? 3 : 4 ;
int end = start + ( response [ start - 2 ] + 256 ) % 256 - 1 ;
byte [ ] decrypted = encryptDecryptData ( Arrays . copyOfRange ( response , start , end ) , Cipher . DECRYPT_MODE ) ;
int indexOfTerminator = Hex . toHexString ( decrypted ) . lastIndexOf ( " 80 " ) / 2 ;
certificate = Arrays . copyOf ( certificate , certificate . length + indexOfTerminator ) ;
System . arraycopy ( decrypted , 0 , certificate , certificate . length - indexOfTerminator , indexOfTerminator ) ;
2021-10-12 12:18:06 +03:00
2021-10-19 00:58:53 +03:00
if ( response [ response . length - 2 ] = = ( byte ) 0x90 & & response [ response . length - 1 ] = = 0x00 ) {
2021-10-13 02:27:19 +03:00
break ;
}
}
2021-10-12 12:18:06 +03:00
2021-10-13 02:27:19 +03:00
return certificate ;
2021-10-12 00:36:08 +03:00
}
2021-10-19 00:58:53 +03:00
/ * *
* Signs the authentication token hash
*
* @param PIN1 PIN1
* @param token the token hash to be signed
* @return authentication token hash signature
* /
public byte [ ] authenticate ( String PIN1 , byte [ ] token ) throws NoSuchPaddingException , InvalidKeyException , NoSuchAlgorithmException , IllegalBlockSizeException , BadPaddingException , InvalidAlgorithmParameterException , IOException {
verifyPIN ( PIN1 . getBytes ( StandardCharsets . UTF_8 ) , true ) ;
selectFile ( AWP , " the AWP application " ) ;
byte [ ] response = getResponse ( Env , MSESetEnv , " Set environment " ) ;
if ( response [ response . length - 2 ] ! = ( byte ) 0x90 | | response [ response . length - 1 ] ! = 0x00 ) {
throw new RuntimeException ( " Setting the environment failed. " ) ;
}
InternalAuthenticate [ 4 ] = ( byte ) ( 0x1d + 16 * ( token . length / 16 ) ) ;
InternalAuthenticate [ 6 ] = ( byte ) ( 0x11 + 16 * ( token . length / 16 ) ) ;
response = getResponse ( token , InternalAuthenticate , " Internal Authenticate " ) ;
2021-11-08 17:30:56 +02:00
2021-10-19 00:58:53 +03:00
if ( response [ response . length - 2 ] ! = ( byte ) 0x90 | | response [ response . length - 1 ] ! = 0x00 ) {
throw new RuntimeException ( " Signing the token failed. " ) ;
}
byte [ ] signature = encryptDecryptData ( Arrays . copyOfRange ( response , 3 , 115 ) , Cipher . DECRYPT_MODE ) ;
int indexOfTerminator = Hex . toHexString ( signature ) . lastIndexOf ( " 80 " ) / 2 ;
return Arrays . copyOf ( signature , indexOfTerminator ) ;
}
2021-11-08 17:30:56 +02:00
2021-10-19 00:58:53 +03:00
private byte [ ] getResponse ( byte [ ] data , byte [ ] command , String log ) throws NoSuchPaddingException , InvalidKeyException , NoSuchAlgorithmException , IllegalBlockSizeException , BadPaddingException , InvalidAlgorithmParameterException , IOException {
byte [ ] response = idCard . transceive ( createSecureAPDU ( data , command ) ) ;
Log . i ( log , Hex . toHexString ( response ) ) ;
return response ;
}
2021-10-04 23:50:14 +03:00
}