Updated on 2026-08-14

This commit is contained in:
Tangem 2019-11-21 15:38:19 +03:00
parent dbcbc6b558
commit ea39fd060d
10 changed files with 355 additions and 22 deletions

View file

@ -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<CheckWalletResponse>() {

View file

@ -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

View file

@ -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) {

View file

@ -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()

View file

@ -121,7 +121,7 @@ class TlvMapper(val tlvList: List<Tlv>) {
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()}")
}
}

View file

@ -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

View file

@ -58,7 +58,6 @@ internal class ScanTask : Task<ScanEvent>() {
val challenge = CryptoUtils.generateRandomBytes(16)
val checkWalletCommand = CheckWalletCommand(
cardEnvironment.pin1,
card.cardId,
challenge)

View file

@ -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

View file

@ -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<MissingTagException> {
tlvMapper.map<String>(TlvTag.TokenSymbol)
}
}
@Test
fun `map optional to wrong type throws WrongTypeException`() {
assertThrows<WrongTypeException> {
tlvMapper.mapOptional<String?>(TlvTag.CardData)
}
}
@Test
fun `map to wrong type throws WrongTypeException`() {
assertThrows<WrongTypeException> {
tlvMapper.map<String>(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<ConversionException> {
localMapper.map<ProductMask>(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<ConversionException> {
localMapper.map<Int>(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")
}
}

View file

@ -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")
}
}