diff --git a/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt b/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt index dc4d4c9229..1fc2521d2d 100644 --- a/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt @@ -33,7 +33,6 @@ class CheckWalletResponse( * @property challenge Random challenge generated by application */ class CheckWalletCommand( - private val pin1: String, private val cardId: String, private val challenge: ByteArray ) : CommandSerializer() { diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt index bbee7926b4..d12548c92f 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt @@ -14,18 +14,24 @@ import java.util.* /** * Determines which type of data is required for signing. */ -enum class SigningMethod(val code: Int) { - SignHash(0), - SignRaw(1), - SignHashValidatedByIssuer(2), - SignRawValidatedByIssuer(3), - SignHashValidatedByIssuerAndWriteIssuerData(4), - SignRawValidatedByIssuerAndWriteIssuerData(5), - SignPos(6); +data class SigningMethod(val rawValue: Int) { + + fun contains(value: Int): Boolean { + return if (rawValue and 0x80 == 0) { + value == rawValue + } else { + rawValue and (0x01 shl value) != 0 + } + } companion object { - private val values = values() - fun byCode(code: Int): SigningMethod? = values.find { it.code == code } + const val signHash = 0 + const val signRaw = 1 + const val signHashValidatedByIssuer = 2 + const val signRawValidatedByIssuer = 3 + const val signHashValidatedByIssuerAndWriteIssuerData = 4 + const val signRawValidatedByIssuerAndWriteIssuerData = 5 + const val signPos = 6 } } @@ -76,6 +82,8 @@ enum class ProductMask(val code: Byte) { */ data class SettingsMask(val rawValue: Int) { + fun contains(value: Int): Boolean = (rawValue and value) != 0 + companion object { const val isReusable = 0x0001 const val useActivation = 0x0002 diff --git a/tangem-core/src/main/java/com/tangem/common/extensions/ByteArray.kt b/tangem-core/src/main/java/com/tangem/common/extensions/ByteArray.kt index 928f31fa69..7b611ad93b 100644 --- a/tangem-core/src/main/java/com/tangem/common/extensions/ByteArray.kt +++ b/tangem-core/src/main/java/com/tangem/common/extensions/ByteArray.kt @@ -9,14 +9,9 @@ import kotlin.experimental.and * Extension functions for [ByteArray]. */ -fun ByteArray.toHexString() = joinToString("") { "%02x".format(it) } - -fun ByteArray.toUtf8(): String { - val string = String(this) - if (this.isNotEmpty() && this.last() == 0.toByte()) return string.dropLast(1) - return string -} +fun ByteArray.toHexString(): String = joinToString("") { "%02x".format(it) } +fun ByteArray.toUtf8(): String = String(this).removeSuffix("\u0000") fun ByteArray.toInt(): Int { return when (this.size) { diff --git a/tangem-core/src/main/java/com/tangem/common/tlv/Tlv.kt b/tangem-core/src/main/java/com/tangem/common/tlv/Tlv.kt index 15297b0150..f17ab7e3cb 100644 --- a/tangem-core/src/main/java/com/tangem/common/tlv/Tlv.kt +++ b/tangem-core/src/main/java/com/tangem/common/tlv/Tlv.kt @@ -27,7 +27,7 @@ class Tlv { companion object { - fun tlvFromBytes(stream: ByteArrayInputStream): Tlv? { + private fun tlvFromBytes(stream: ByteArrayInputStream): Tlv? { val code = stream.read() if (code == -1) return null var len = stream.read() diff --git a/tangem-core/src/main/java/com/tangem/common/tlv/TlvMapper.kt b/tangem-core/src/main/java/com/tangem/common/tlv/TlvMapper.kt index fe153c9d3d..6171dca7d9 100644 --- a/tangem-core/src/main/java/com/tangem/common/tlv/TlvMapper.kt +++ b/tangem-core/src/main/java/com/tangem/common/tlv/TlvMapper.kt @@ -121,7 +121,7 @@ class TlvMapper(val tlvList: List) { TlvValueType.SigningMethod -> { if (T::class != SigningMethod::class) throw WrongTypeException("Mapping error. Type for tag: $tag must be ${tag.valueType()}. It is ${T::class}") - SigningMethod.byCode(tlvValue.toInt()) as T + SigningMethod(tlvValue.toInt()) as T ?: throw ConversionException("Unknown Signing Method with code of: ${tlvValue.toInt()}") } } diff --git a/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt b/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt index 0b6933f175..491509ae08 100644 --- a/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt +++ b/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt @@ -113,7 +113,7 @@ enum class TlvTag(val code: Int) { TlvValueType.Utf8String CurveId -> TlvValueType.EllipticCurve MaxSignatures, PauseBeforePin2, RemainingSignatures, - SignedHashes, Health, TokenDecimal, UserCounter -> TlvValueType.IntValue + SignedHashes, Health, TokenDecimal, UserCounter, IssuerDataCounter -> TlvValueType.IntValue IsActivated, TerminalIsLinked -> TlvValueType.BoolValue ManufactureDateTime -> TlvValueType.DateTime ProductMask -> TlvValueType.ProductMask diff --git a/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt b/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt index 10625f335d..e80da5189f 100644 --- a/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt +++ b/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt @@ -58,7 +58,6 @@ internal class ScanTask : Task() { val challenge = CryptoUtils.generateRandomBytes(16) val checkWalletCommand = CheckWalletCommand( - cardEnvironment.pin1, card.cardId, challenge) diff --git a/tangem-core/src/test/java/com/tangem/common/extensions/ByteArrayExtensionsTest.kt b/tangem-core/src/test/java/com/tangem/common/extensions/ByteArrayExtensionsTest.kt index 730576b898..224144afdb 100644 --- a/tangem-core/src/test/java/com/tangem/common/extensions/ByteArrayExtensionsTest.kt +++ b/tangem-core/src/test/java/com/tangem/common/extensions/ByteArrayExtensionsTest.kt @@ -32,6 +32,14 @@ class ByteArrayExtensionsTest { .matches(expected) } + @Test + fun `empty byteArray to Utf8 returns empty String`() { + val bytes = byteArrayOf() + val expected = "" + assertThat(bytes.toUtf8()) + .matches(expected) + } + @Test fun `blockchain name to Utf8`() { val bytes = byteArrayOf(69, 84, 72) @@ -47,6 +55,11 @@ class ByteArrayExtensionsTest { val expected = 158211 assertThat(bytes.toInt()) .isEqualTo(expected) + + val bytes1 = byteArrayOf(0, 0, 0, 13) + val expected1 = 13 + assertThat(bytes1.toInt()) + .isEqualTo(expected1) } @Test diff --git a/tangem-core/src/test/java/com/tangem/common/tlv/TlvMapperTest.kt b/tangem-core/src/test/java/com/tangem/common/tlv/TlvMapperTest.kt new file mode 100644 index 0000000000..3666e03120 --- /dev/null +++ b/tangem-core/src/test/java/com/tangem/common/tlv/TlvMapperTest.kt @@ -0,0 +1,183 @@ +package com.tangem.common.tlv + +import com.google.common.truth.Truth.assertThat +import com.tangem.commands.* +import com.tangem.common.extensions.hexToBytes +import org.junit.Test +import org.junit.jupiter.api.assertThrows +import java.util.* + +class TlvMapperTest { + + private val rawData = byteArrayOf(1, 8, -53, 34, 0, 0, 0, 2, 115, 116, 32, 11, 83, 77, 65, 82, 84, 32, 67, 65, 83, 72, 0, 2, 1, 2, -128, 6, 50, 46, 49, 49, 114, 0, 3, 65, 4, -49, 11, -50, -66, -121, -25, -2, 65, 65, -13, 14, 49, 27, -82, -33, -85, -113, 65, 20, 8, -39, -75, 57, 45, 65, -31, 35, 44, 38, 40, 63, -44, 113, -45, -75, -95, -118, 118, 29, 65, 117, -24, -53, 82, -72, 91, -20, -96, -77, -103, -14, -63, 52, -127, -123, -27, -16, -128, -67, -3, -104, -26, -22, 65, 10, 4, 0, 0, 126, 33, 12, 90, -127, 2, 0, 41, -126, 4, 7, -29, 5, 2, -125, 7, 84, 65, 78, 71, 69, 77, 0, -124, 3, 69, 84, 72, -122, 64, 111, -103, 48, -114, -40, 18, -103, 26, -102, -12, -38, -78, -90, -9, -98, 88, -47, -100, -24, 24, -105, -70, -72, 6, 94, -96, -77, 11, -123, -28, -118, 37, 63, 107, -55, -11, 23, -12, 13, -23, -121, -63, 36, -59, 70, 116, 91, -125, -34, -69, 23, -112, 6, 17, 4, -49, 68, -56, 29, -45, 81, 10, 97, 83, 48, 65, 4, -127, -106, -86, 75, 65, 10, -60, 74, 59, -100, -50, 24, -25, -66, 34, 106, -22, 7, 10, -52, -125, -87, -49, 103, 84, 15, -84, 73, -81, 37, 18, -97, 106, 83, -118, 40, -83, 99, 65, 53, -114, 60, 79, -103, 99, 6, 79, 126, 54, 83, 114, -90, 81, -45, 116, -27, -62, 60, -35, 55, -3, 9, -101, -14, 5, 10, 115, 101, 99, 112, 50, 53, 54, 107, 49, 0, 8, 4, 0, 15, 66, 64, 7, 1, 0, 9, 2, 11, -72, 96, 65, 4, -42, -5, -41, -84, -23, 88, 2, 86, -63, -118, -123, -10, -66, -82, -107, -68, -93, 111, 47, 93, -20, -86, 74, 28, 21, 81, 93, -21, -124, -57, -102, 55, 17, 84, -66, -68, -22, -128, 126, -99, -65, -54, -42, 59, -25, -21, -124, 5, 59, -16, -72, 73, 48, 16, -27, 103, -112, -73, 2, 96, -51, 41, -42, 116, 98, 4, 0, 15, 66, 52, 99, 4, 0, 0, 0, 13, 15, 1, 0) + + private val tlvData = Tlv.tlvListFromBytes(rawData) + + private val tlvMapper = TlvMapper(tlvData!!) + + private val cardDataRaw: ByteArray = tlvMapper.map(TlvTag.CardData) + private val cardDataMapper = TlvMapper(Tlv.tlvListFromBytes(cardDataRaw)!!) + + @Test + fun `map optional when value is present`() { + val settingsMask: SettingsMask? = tlvMapper.mapOptional(TlvTag.SettingsMask) + assertThat(settingsMask) + .isNotNull() + } + + @Test + fun `map optional when no tag returns null`() { + val tokenSymbol: String? = tlvMapper.mapOptional(TlvTag.TokenSymbol) + assertThat(tokenSymbol) + .isNull() + } + + @Test + fun `map when value is null throws MissingTagException`() { + assertThrows { + tlvMapper.map(TlvTag.TokenSymbol) + } + } + + @Test + fun `map optional to wrong type throws WrongTypeException`() { + assertThrows { + tlvMapper.mapOptional(TlvTag.CardData) + } + } + + @Test + fun `map to wrong type throws WrongTypeException`() { + assertThrows { + tlvMapper.map(TlvTag.CardData) + } + } + + @Test + fun `map boolean missing flag returns false`() { + val terminalIsLinked: Boolean = tlvMapper.map(TlvTag.TerminalIsLinked) + assertThat(terminalIsLinked) + .isFalse() + } + + @Test + fun `map SettingsMask returns correct value`() { + val settingsMask: SettingsMask = tlvMapper.map(TlvTag.SettingsMask) + assertThat(settingsMask) + .isNotNull() + assertThat(settingsMask.rawValue) + .isEqualTo(32289) + assertThat(settingsMask.contains(SettingsMask.skipSecurityDelayIfValidatedByLinkedTerminal)) + .isFalse() + assertThat(settingsMask.contains(SettingsMask.isReusable)) + .isTrue() + assertThat(settingsMask.contains(SettingsMask.allowSwapPIN2)) + .isTrue() + assertThat(settingsMask.contains(SettingsMask.useDynamicNdef)) + .isTrue() + assertThat(settingsMask.contains(SettingsMask.forbidPurgeWallet)) + .isFalse() + } + + @Test + fun `map SigningMethods single value returns correct value`() { + val signingMethods: SigningMethod = tlvMapper.map(TlvTag.SigningMethod) + assertThat(signingMethods.contains(SigningMethod.signHash)) + .isTrue() + } + + @Test + fun `map SigningMethods set of methods returns correct value`() { + val localMapper = TlvMapper(Tlv.tlvListFromBytes("070195".hexToBytes())!!) + + val signingMethod: SigningMethod = localMapper.map(TlvTag.SigningMethod) + assertThat(signingMethod.contains(SigningMethod.signHash)) + .isTrue() + assertThat(signingMethod.contains(SigningMethod.signHashValidatedByIssuer)) + .isTrue() + assertThat(signingMethod.contains(SigningMethod.signHashValidatedByIssuerAndWriteIssuerData)) + .isTrue() + assertThat(signingMethod.contains(SigningMethod.signRaw)) + .isFalse() + assertThat(signingMethod.contains(SigningMethod.signRawValidatedByIssuer)) + .isFalse() + assertThat(signingMethod.contains(SigningMethod.signRawValidatedByIssuerAndWriteIssuerData)) + .isFalse() + assertThat(signingMethod.contains(SigningMethod.signPos)) + .isFalse() + } + + @Test + fun `map CardStatus returns correct value`() { + val cardStatus: CardStatus = tlvMapper.map(TlvTag.Status) + assertThat(cardStatus) + .isEqualTo(CardStatus.Loaded) + } + + @Test + fun `map ProductMask returns correct value`() { + val localMapper = TlvMapper(listOf(Tlv(TlvTag.ProductMask, byteArrayOf(4)))) + val productMask: ProductMask = localMapper.map(TlvTag.ProductMask) + assertThat(productMask) + .isEqualTo(ProductMask.Card) + } + + @Test + fun `map Enum with unknown code throws ConversionException error`() { + val localMapper = TlvMapper(listOf(Tlv(TlvTag.ProductMask, byteArrayOf(5)))) + assertThrows { + localMapper.map(TlvTag.ProductMask) + } + } + + @Test + fun `map DateTime returns correct value`() { + val date: Date = cardDataMapper.map(TlvTag.ManufactureDateTime) + val expected = Calendar.getInstance().apply { this.set(2019, 4, 2, 0, 0, 0) }.time + assertThat(date.toString()) + .isEqualTo(expected.toString()) + } + + @Test + fun `map EllipticCurve returns correct value`() { + val ellipticCurve: EllipticCurve = tlvMapper.map(TlvTag.CurveId) + assertThat(ellipticCurve) + .isEqualTo(EllipticCurve.Secp256k1) + } + + @Test + fun `map ByteArray returns correctly`() { + val cardPublicKey: ByteArray = tlvMapper.map(TlvTag.CardPublicKey) + assertThat(cardPublicKey) + .isInstanceOf(ByteArray::class.java) + } + + @Test + fun `map Int returns correct value`() { + val signedHashes: Int = tlvMapper.map(TlvTag.SignedHashes) + assertThat(signedHashes) + .isEqualTo(13) + } + + @Test + fun `map Int with wrong value throws ConversionException`() { + val localMapper = TlvMapper(listOf(Tlv(TlvTag.SignedHashes, byteArrayOf(1, 2, 3, 4, 5)))) + assertThrows { + localMapper.map(TlvTag.SignedHashes) + } + } + + @Test + fun `map UTF8 returns correct value`() { + val blockchainId: String = cardDataMapper.map(TlvTag.BlockchainId) + assertThat(blockchainId) + .isEqualTo("ETH") + } + + @Test + fun `map Hex returns correct value`() { + val cardId: String = tlvMapper.map(TlvTag.CardId) + assertThat(cardId) + .isEqualTo("cb22000000027374") + } +} \ No newline at end of file diff --git a/tangem-core/src/test/java/com/tangem/common/tlv/TlvTest.kt b/tangem-core/src/test/java/com/tangem/common/tlv/TlvTest.kt new file mode 100644 index 0000000000..11f7079ffe --- /dev/null +++ b/tangem-core/src/test/java/com/tangem/common/tlv/TlvTest.kt @@ -0,0 +1,136 @@ +package com.tangem.common.tlv + +import com.google.common.truth.Truth.assertThat +import com.tangem.common.extensions.calculateSha256 +import com.tangem.common.extensions.hexToBytes +import com.tangem.common.extensions.toHexString +import com.tangem.crypto.CryptoUtils +import org.junit.Test +import org.junit.jupiter.api.Assertions.* +import javax.crypto.Cipher +import javax.crypto.SecretKey +import javax.crypto.spec.IvParameterSpec +import javax.crypto.spec.SecretKeySpec + +class TlvTest { + + @Test + fun `TLVs to bytes, only PIN`() { + val tlvs = listOf( + Tlv(TlvTag.Pin, "000000".calculateSha256()) + ) + val expected = byteArrayOf(16, 32, -111, -76, -47, 66, -126, 63, 125, 32, -59, -16, -115, + -10, -111, 34, -34, 67, -13, 95, 5, 122, -104, -115, -106, 25, -10, -45, 19, -124, + -123, -55, -94, 3) + + assertThat(tlvs.toBytes()) + .isEqualTo(expected) + } + + @Test + fun `TLVs to bytes, check wallet`() { + val tlvs = listOf( + Tlv(TlvTag.Pin, "000000".calculateSha256()), + Tlv(TlvTag.CardId, "cb22000000027374".hexToBytes()), + Tlv(TlvTag.Challenge, byteArrayOf(-82, -78, -31, 34, 66, -19, -86, -1, 26, 8, 100, -126, -74, 20, -28, 83)) + ) + + val expected = byteArrayOf(16, 32, -111, -76, -47, 66, -126, 63, 125, 32, -59, -16, -115, -10, + -111, 34, -34, 67, -13, 95, 5, 122, -104, -115, -106, 25, -10, -45, 19, -124, -123, + -55, -94, 3, 1, 8, -53, 34, 0, 0, 0, 2, 115, 116, 22, 16, -82, -78, -31, 34, 66, -19, + -86, -1, 26, 8, 100, -126, -74, 20, -28, 83) + + assertThat(tlvs.toBytes()) + .isEqualTo(expected) + } + + @Test + fun `Bytes to Tlvs, only PIN`() { + val bytes = byteArrayOf(16, 32, -111, -76, -47, 66, -126, 63, 125, 32, -59, -16, -115, + -10, -111, 34, -34, 67, -13, 95, 5, 122, -104, -115, -106, 25, -10, -45, 19, -124, + -123, -55, -94, 3) + + val tlvs = Tlv.tlvListFromBytes(bytes) + + assertThat(tlvs) + .isNotNull() + assertThat(tlvs) + .isNotEmpty() + + val pin = tlvs!!.find { it.tag == TlvTag.Pin }?.value + val pinExpected = "000000".calculateSha256() + + assertThat(pin) + .isEqualTo(pinExpected) + } + + @Test + fun `Bytes to TLVs, check wallet TLVs`() { + val bytes = byteArrayOf(16, 32, -111, -76, -47, 66, -126, 63, 125, 32, -59, -16, -115, -10, + -111, 34, -34, 67, -13, 95, 5, 122, -104, -115, -106, 25, -10, -45, 19, -124, -123, + -55, -94, 3, 1, 8, -53, 34, 0, 0, 0, 2, 115, 116, 22, 16, -82, -78, -31, 34, 66, -19, + -86, -1, 26, 8, 100, -126, -74, 20, -28, 83) + + val tlvs = Tlv.tlvListFromBytes(bytes) + + assertThat(tlvs) + .isNotNull() + assertThat(tlvs) + .isNotEmpty() + + val pin = tlvs!!.find { it.tag == TlvTag.Pin }?.value + val pinExpected = "000000".calculateSha256() + assertThat(pin) + .isEqualTo(pinExpected) + + val cardId = tlvs.find { it.tag == TlvTag.CardId }?.value + val cardIdExpected = "cb22000000027374".hexToBytes() + assertThat(cardId) + .isEqualTo(cardIdExpected) + + val challenge = tlvs.find { it.tag == TlvTag.Challenge }?.value + val challengeExpected = byteArrayOf(-82, -78, -31, 34, 66, -19, -86, -1, 26, 8, 100, -126, -74, 20, -28, 83) + assertThat(challenge) + .isEqualTo(challengeExpected) + } + + @Test + fun `Bytes to TLVs, wrong values`() { + val bytes = byteArrayOf(0) + val tlvs = Tlv.tlvListFromBytes(bytes) + assertThat(tlvs) + .isNull() + + val bytes1 = byteArrayOf(0, 0, 0, 0, 0, 0, 0) + val tlvs1 = Tlv.tlvListFromBytes(bytes1) + assertThat(tlvs1) + .isNull() + } + +// @Test + fun `sldfj dslkf`() { + val privateKey = "C9A8827B83D554C2ED6C92A958A75691221A951775C36EBA858C039564ECDC3E" + val password = "theXlPiP42".calculateSha256() + + val originalKey: SecretKey = SecretKeySpec(password, 0, password.size, "AES") + + CryptoUtils.initCrypto() + + fun decrypt(yourKey: SecretKey): ByteArray { + val decrypted: ByteArray + val cipher = Cipher.getInstance("AES", "SC") + cipher.init(Cipher.DECRYPT_MODE, yourKey, IvParameterSpec(ByteArray(cipher.blockSize))) + decrypted = cipher.doFinal(privateKey.hexToBytes()) + return decrypted + } + + + val publicKey = CryptoUtils.generatePublicKey( + decrypt(originalKey) + ) + + assertThat(publicKey.toHexString()) + .isEqualTo("041D020A29983E2BC36E6CEAD9D648A71AA54AD328F00D6336DD699BCEBD99B5F4DAAAAC5E8E6D72D0A94EA86CF0A41D367FE05DE4A1BD84C86D51CCA4AF357182") + } + +} \ No newline at end of file