From 37c68616bad4a09514fe8385a809d914cb17d46a Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 19 Jan 2020 14:37:37 +0300 Subject: [PATCH 01/13] Updated on 2026-08-14 --- .../fragment/wallet/LoadedWalletFragment.kt | 75 +++++ build.gradle | 2 +- .../tangem/tangem_card/data/TangemCard.java | 258 +++++++----------- .../tangem_card/reader/CardProtocol.java | 192 +++++++++++++ .../tangem_card/reader/ProductMask.java | 25 ++ .../tangem_card/reader/SettingsMask.java | 26 ++ .../com/tangem/tangem_card/reader/TLV.java | 75 ++++- .../tangem/tangem_card/reader/TLVList.java | 4 + .../tangem_card/tasks/CustomReadCardTask.java | 7 + .../tangem_card/tasks/VerifyCardTask.java | 4 + .../tangem_card/tasks/WriteIDCardTask.java | 36 +++ .../tangem_sdk/data/TangemCardExtensions.kt | 15 + 12 files changed, 553 insertions(+), 166 deletions(-) create mode 100644 tangem-card-old/src/main/java/com/tangem/tangem_card/reader/ProductMask.java create mode 100644 tangem-card-old/src/main/java/com/tangem/tangem_card/tasks/WriteIDCardTask.java diff --git a/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt b/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt index 31ca17f0dd..db7e155e92 100644 --- a/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt +++ b/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt @@ -30,6 +30,7 @@ import com.tangem.server_android.model.CardVerifyAndGetInfo import com.tangem.tangem_card.data.TangemCard import com.tangem.tangem_card.reader.CardProtocol import com.tangem.tangem_card.tasks.VerifyCardTask +import com.tangem.tangem_card.tasks.WriteIDCardTask import com.tangem.tangem_card.util.Util import com.tangem.tangem_sdk.android.reader.NfcReader import com.tangem.tangem_sdk.data.EXTRA_TANGEM_CARD @@ -55,6 +56,8 @@ import kotlinx.android.synthetic.main.layout_tangem_card.* import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import java.io.InputStream +import java.nio.charset.StandardCharsets +import java.text.SimpleDateFormat import java.util.* import kotlin.concurrent.timerTask @@ -525,6 +528,19 @@ class LoadedWalletFragment : BaseFragment(), NavigationResultListener, NfcAdapte else updateViews() + // TODO - remove, only for test&demo reason! + // TODO - begin + if( cardProtocol.card.isIDCard) + { + if( cardProtocol.card.hasIDCardData() ) + { + Toast.makeText(activity, "ID card: ${cardProtocol.card.idCardData.fullName}", Toast.LENGTH_SHORT).show() + }else{ + Toast.makeText(activity, "Empty ID card", Toast.LENGTH_SHORT).show() + } + } + // TODO - end + mpSecondScanSound.start() } } else { @@ -789,6 +805,65 @@ class LoadedWalletFragment : BaseFragment(), NavigationResultListener, NfcAdapte val uid = tag?.id val sUID = Util.byteArrayToHexString(uid) if (ctx.card.uid != sUID || cardProtocol != null) { + + //TODO - remove, only for test + //TODO - begin + if( ctx.card.isIDCard && !ctx.card.hasIDCardData() ) + { + val idCardData=TangemCard.IDCardData() + idCardData.fullName="John Doe" + idCardData.birthday="1980-1-1" + idCardData.gender="M" + val dt= Date() + idCardData.issueDate= SimpleDateFormat ("yyyy-MM-dd", Locale.US).format(dt) + dt.year = dt.year + 10 + idCardData.expireDate=SimpleDateFormat("yyyy-MM-dd", Locale.US).format(dt) + idCardData.trustedAddress="GCOQLQT32RVH2MTWQGK5K3GIGT3STYG5323KMNTTFHIU2T5TFOGR4KDT" // ask D.Baturin + idCardData.photo=Util.hexToBytes("FFD8FFE000104A46494600010101012C012C0000FFDB0043000A07070807060A0808080B0A0A0B0E18100E0D0D0E1D15161118231F2524221F2221262B372F26293429212230413134393B3E3E3E252E4449433C48373D3E3BFFDB0043010A0B0B0E0D0E1C10101C3B2822283B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3BFFC000110800C8007903012200021101031101FFC4001C0000020203010100000000000000000000000706080103050402FFC4003F100001030301050505060406020300000001020304000511060712213141135161718114223291A115235262B1C1428292D10816333472A2175393D2E1FFC4001801000301010000000000000000000000000002030104FFC4002111000202030100020301000000000000000001021103213112132232415142FFDA000C03010002110311003F007351451400562B35A83ED29F53097505D424294807DE4839C123B8E0FCA8016BB4CD712AC97FB642B72FDF8AA12A4273C179C84A0F811BC7D45306CF768B7BB547B9425EFB0FA3793DE3BC1F10720D579D75297335C5E1D5924A64A9B19EE4FBA3F4AECECDB5B7F96AE2604E708B64B57BC4F2617C82FC8F23E87A54164FB6C771D0FBA2BE50B4AD0149214923208E44566AE219A28A2800A28A2800A28A2800A28A2800A28A2803E5440049E02ABEFF009F26C5DA0C9D44CA94B69C74B6A673C16C03809F900478D580791DAB2B6F38DF494E7CEAA9BAD165E5B4AF89B5149F307151CADAAA1A28ECEB35B0FEAB9D322381C8D31424B4B1D52B483F30720F8835C4A3248009381CBC28AE76ED9444C7496D2AEDA65B44375227C04F04B2E2B0A6C7E45741E07879533AD7B55D2B7040ED662A0B879A2520A47F50C8FAD2028A78E468571459E6F53D85E46FB77AB7A93DE24A3FBD706EBB54D2D6DCA5B98A9CE0FE18A8DE1FD4709FAD57EC03D07CAB34CF3333C967B4E5F19D47638D7561B53687C1FBB5104A48241071E55EE932E3C3614FCA7DB61A4FC4E38A0948F5355F6C1B43BBE9BB02ED36F6D8CA9E538979D05451903202797319E3DFCAB8574BD5CEF6FF006D739CF4B5E78768AC84F90E43D053FCAA8CF2588B36AFB46A0B9C8856A7952BD99016E3C9410D8C9C0009E679F2E1C2BBB4BED8EDA7D8B4A393D69C393DE2A07F227DD4FD778FAD306A916DAB6630A28A298C31466B54A694FC575943CB654B414A5C47C4824731E2290978D5FAE2C976936C977B901E8EB2927711850E8A1EEF22307D69252F26A564CEFF00B49BCE92D48FDB6E36D8F2E383DA30EB6A2D296D9E5DE091C41F2A52DD65353AEF32630D29A6A43EB752DA8E4A428938FAD6FBBDFEEB7E5B4BBACD5CA532084296948290798E0077573AB9A53B2895051582A006490078D7C7B4320E3B44FCE9526CD3651520B268ABA6A0483024DB5448CEE19C82BF54A72457A6E9B37D51698AECA760B6FB2CA0AD6A61E4A8800649C7034530B22D45789570E3EEB7F335F22E0BEADA4F91A7F8A467A47BE83E15E76A634E1C1F70F8D7A291C5AE9A9D8D7B0ED76D36BB544B6AACB2D0DC6692D02D3885E70319C1C73A6258AFE9BEB1DBB76DB8446F190A96C86F7BCB8E4D56C813E55B26B53613CA62432ADE42D38C83EB4E2D15B556EF1218B5DE582CCD748436F3292A43A7C40E293F4F2ABC277A62490C8A2B159AB8862951B68B75B0B712E224B2D5C53F765927DF79BEFC7E53D4F426999738D2E5C25330E7182EAB876C9682D491E00F0CF8F1A40EBEB55AECB75EC5ABFBD75B82944CB5BC527B33D0139E2AF0E953C9C1A3D22F5A24C8EC123032A3CBBAB7020F2391E15BA2C11739B1E0938F68790D820711BCA033F5AE68D5EC77C3970E15C2F32D31E1467A5BEAE4DB482A3F21D2A52C6C8F5A3CD05FD9496F3C92E486D27E59A7BE9ED3569D236AF6580D25A42465E7DC237DC3F894AFDB90AC2F58E986DEEC57A86D895E71BA6523FBD59E47FE5095FD10917671AA23DFE0439B6A931DB7E4210A7D1EF21033C4EF24903866AC9169B2D762520B653BBBA78E538C63E55861F664B21E8EF21D695C96DA82927D452FB5D6D43EC49E6C7608E275D490859DD2A4B4A3FC21238A95E1C878F2A46DCDD1BA479E06C32C2D2D6B9B3A649CA894A1B21B4A46780EA4FCC56ABEEC36D521ADFB24C7A23C07FA7215DA215EB8C8FAD781AD21B56D449126E17E55B82F88695254D91FC8D8C0A1CD27B58D3A3B7817D55C528E3D9A649709FE57471F4AAF99FF004CB42E351E8FBDE977B72E509686C9C21E4FBCDABC94387EF5E78A9712C80E1C9E80F414D381B4C45E62CBD2FACA10B7497DB5305F282942564606FA4F141CE0E797952CD68536B5217F12490AF31CEA7372AA66C523E69F7B33B169F896166E76B57B5497D387A4B806FA55D518FE1C1E9D79E4D2523E9BD473612AE10EC52DF86919ED529F8C0E6523991E40D31F64F11C5155CAC3770EC65283770B74B6F716D9E8A041209EE381919071D371C5A7612636EB358159AE810876D3F53BDA6748BAEC3514CD96B11E3A8734920E543C40071E24579F44681B6E9EB434E4B88D49BA3C90B92FBC80B2147894A73C80E5E278D7236C984CCD2AE3BFEDD370FBDCF2E68E7E80D320F33E751CADF068A10FB53B4C5B56AF262212DA2530878B6848012AC949C01DFBB9F9D462D17362CD798572928538DC5792E96D3CD653C401EB8A97ED817BDACDB4FE086D8FAA8D426CD6A3A8355DBACFBC52890F250B23984F3511E80D4A11F5219BA44D615A358ED764AA6CE99EC1670B3B8307B3183C908CFBE47551F9F4AF5DE767DB39D33B912F5A9673531C00E13BAA207794A50703CE9CD162B16F84DC68ACA5A61840436DA0704A40E005548BD5CA45DEF32EE129654F48754B513D38F01E4070F4AEBAA264F275B6FDB305C7BEE9DBBA6E165998DD71232DAB23805A738F250EA3A72AEF6C36CAD4D72E5A9660EDE507BB1696BE25248DE5ABCCEF019F3EFAD5616C9FF0F574F6FE2CEF3AA8FBDD3DF4EEE3F9F35AF613A8988F26769F7D610B92A1223E7F89406149F3C007D0D0074F52EDBBEC8D46F5BADF6B6E5468AE169D756E9495A81C2B7703000391939CD31B4E5FE16A7B247BAC1512D3C38A55F121438149F106AB56BBB1CAB06AFB846908210E3CA759591C16DA89208EFE783E20D3736131E533A3E53AF052587A6294CE7AE1290A23C3231E8680241AFB41C1D636A5E1B437736924C6918C1CFE051EA93F4E755CD80F34F3B0DE4143CD28A0A55CC107047A1AB7279555FD6FD89DA4DE7D9F1B9ED4BCE3F163DEFF00B66926AE2CD5D2C6DAA208369870C0C7B3B086FE490297D15B1A676EC23C61D9C5BEC52B5B63E10BC28938FF009209FE6358D9AEBF125B458AF2F80F21388D21C57FA807F0289EA0723D478F3F88529BD5FB6F6675BD41E83648A52A7D1C50A5614381EBEF2CE3BF74D47174690D8159AC0ACD748847B5B694635869D76D8EB9D93A141C61DC6771C1C8F910483E06A0D17556BDD22C2605F74BBD7669801089914925491C8920107D403DF4DAAC1AC693E8595A3575FD7A9750BD725C572265296C30E1CA91BA3183E39CD78ECD25FD35A82D1A95D654A862491BC3AEEE02C79EEAABEF51C77226A3B8B0F496E53A990BED1D6F3BAA51393CFC4E3D29A7A2B4BDBB516CB99B7DC992B69F7DD752A4F0536ADE202927A1E15CD19799146AD0C5833A2DCA135321BE87E3BC90A6DC41C850A5DDD7629619B7B76E7EDF222C57165D7632027033C4E147E11E87151FFF00C6DAEF4BBEBFF2B5F7B48CA39DD4BDD913FF00242BDD27C73515D6E35D4211A2EA6B9BCEFB58514474C90A07040C94A78733C2BA1493E13A247AF351C7BF983A0B46321D8ADAD285167E070A79241EA91CCABA919E993D0D47B227A1DAA04ED32E94DD203490E84AB74BEB1C77D27A2B3D3A8C75E732D13A12D9A420A16D35DA5C1D6C07E439C559C64A53DC9CFCFAD4AAA32C8EF43A889DB6ED3AD5706D169DA1D8D0B9118E0BEE460BC1EF5208CA4F7E39F753021EBED12DC34262DF6DECB084E10D85767BA3B827031F2AF6DE74C593502026ED6D625103016A4E163C943047CE92BB50D176BD2371B649B7B0E88127783AD29D2AC292412013C46527E9548E452D18D5135D5BB67B6468AB89A68AA7CD706EA1EECC86DB27A8CF151EE0063F4A80CDD0D3ECFA3D3A8AEFBE89F2E5A53D8B87DE4A141472AFCC4E0E3A0F3A72E9DD17A5ECA96A65A6D8D071690B4487097178232082ACE3D315AF5E69995AAACCCC18D25A8FD9BE1E5A9C04E40491818F3A9CF25E91A908BB346B5CC9E98F769CEC261CE01F4341C4A4FE6191C3C45582D17A6E1699B0B70E1A98782CEFAA4328DDEDF3C94789CF0F1C776290B61F6190B7ED53FB3684C012CCB50FF6EF0CEE927F01CEEABCC1E94E6D9DE96BE69A82A6AE972438CAC6510D037C327A90B3FA018ADC46489AD158ACD5C50A8D6BBD4A9D33A69F94850F6B7BEE62A7BD67AFA0C9F4A92D41B6A3A818B1D89296D0DAAE52829A8CB29054D24FC6B07A70C0F323BA964E91A8439254A2A512493924F326AC2ECF59EC7425A138C6F31BFF00D4A27F7AAF237411BD9DDEB8EEAB31A762A6169CB6C54AD2B0D456D2149E4AF747115C654E8D26F5AA7EDADB7D9ADABE2DB1D82549F0C9715F4A7252727FDCFF0088C8CB74E12B537BB9F16303EB54C7D62C871F3E3DF59AC0E559A98C14BADB6C44BFA25A7F1EF4798820F8282927F6A62D2EB6D935B8FA25B8C4FBF265A02478241513FA7CE9E1F9215F092E8394A99A12CAF2CE55EC88493DFBBEEFED5DD75412DA94790493F4AE0E828AB85A12CCCB80857B225641E9BDEF7EF5EBD513D36BD2F729AA38ECA32F77FE44607D48A57D3570AD8A3BCA51E849A766CAB5A7DAF6FF00B127BB99B111F74A51E2F343F529E47C307BE92206001DD5E9B7CF936AB8313E1BA5A911D616DA8743FD8F235B097960D596A6B35C4D29A9236A8B1B370630959F75E6B3C5A58E63F71E04576EBAD3B247CA88032780155B3596A273536A491394486527B28E83FC2D83C3D4F127CE9F7AC2E3F65692BA4D070A6E32820FE623753F522AB3018E1DD51CCFF43C4CD3E76617B4DDB4830C2D797E01F677013C7747141FE9E1E8690D5DDD21AAA4E93BB898D23B561C1B9219CE37D3E1DC474FFF006B9C72C6528B6C76D936BBCDA757C2202D85A1A59CF25A49520F8E4647A577646D92C0DC7DE8F1273CEE3820A12819F1564FE94B2D57AB2E1ABA6076684B6C3790CC647C280799F127BE9E32A7663563CB4BEAEB5EACB737260BE80F1482EC62A1DA34AEA08EEEE3C8D77307B8FCAAA608AA69C0E47794D287220E08F222BD2A9B7B5A77177896A477190B23E59A6A8BE3336593BDEA8B2E9D8EA76E97165820706F7B79C578048E26945DA4FDB16B76886571EC9031BC09E286C9C9C9FC6BC63872C7852FC420A5153AE2964F3F1F5A9468BD52EE8FBA990D33DAC6753B8FB20E37873041EF1FDE8B8C79D0A6CB148425B42508484A523000E400E94ABDAF6A76D486F4EC570294141D9641F871F0A3CFA9F4AF8BFED8FB786A62C509D61D58C191237728FF008A46727C4FCA960E38E3CEADD756A71C5A8A94B51C9513CC935318F9A28A2B00636CD6DFAAECD3A3DCE2DB1C91699C901EDD751EF273C16015734FE9914EAE3E1503D8F5C7DAF46988A56550A42DBC7E557BC3F53F2A9ED76415224FA2F76CD70F66D28C4249C2A6494823BD281BC7EBBB490A636DA6E3DBEA285012ACA62C6DF50EE52CFF00648A5CD73E4772291E05145153342B5B921B68E16AC1EEC56CAF9536851CA90924778AD55FB035A65B0A380B03CC62B766B52E3B2B182D8F4E15AA3EF32FA982494E32934F49AD196CF551451533428A28A0028A283C050032B62971EC6F770B7295812180EA47E641C1FA2BE94E7CD577D1E6569BD79685CB41692FA90013F0ADB75380A07A8E23E5561BDEEEAEAC4FEB44E5D14ACE8E7B5DEB6B85F26A94DD9848DC694382A4A5184809EE4F0E2AF9782BA404894E840C2438A091DC327156A5C09663AB7404A508380060000555351DE515779CD4F2248D8B31451454470A28A2800A31451400514514005145140051451400E6D2F6585ADB6690187CF6732DEA5B6C4948CA995A5594F98C6EE454DF7F50FF00E9B77FF22FFF00AD2F762170CB374B6A95F0A90FA0798293FA0A6BD76436AC93E9E4B9AFB3B5CB5FE16167FEA6AAEC3892273A962334A75D282ADD4F3C049513E8013566F512FB3D37735E71BB0DD3FF0043506D9368F4C1B3AAF539A1ED13DBDD692A1F0327F7573F2C52CE3E9A46A7484C03919A2BD57582AB65DA64058C18CFADAF92881F4C5796B95940A28A2800A28A2800A28A2800A28A2803D56CB6C8BBDC1B811021521EC86D2B504EF9033BA09E1938E15F1360CBB74A5459B19D8CFA3E26DD4949AF88D25E8729A951D5B8F32B0E36A1D140E47E95635B8D65D75A6A24B9B09A92CC9682D3BC3DE6D5D40573041C8E1DD548414918DD0A2D935C3D8B5CB0D156133195B27CF1BC3EA9FAD3F37877D2AA4ECAE6586FB0EF1A7A47B5371A421D319E2038003C40572570CF3C7AD357753DD57C69A54C9B354B8ACCE88EC5909DF65E414389CFC493C08AD8942508094A425206001C80AFBAC550C2BFED56DDEC1AEA4B813BA898DA1F1E646E9FAA6A1D4DADB75BB2D5AEE694FC2A5C759F31BC9FD15503D21A4A66ADBA88AC65B8CDE15224638369F0EF51E83F6AE49C7ED48A27A38C224930CCC0C39ECC970345EDD3BA16464273DF8AD5565D5A4ED074C2B4EA6304C12DEE6E8F8B3F8F3F8B3C73DF55EF50D865E9BBD3F6C9832A6CE50B0301C41E4A1E7F439144F1F904ECE6515BE14291719CCC288D175F7D610DA075269FB6ED9D5958D22DD866C74C827EF1D7C7059748E2A49E631C8780AC841C8D6E8AF4738E0327A0A91EAAD1772D2A63BB2125D8B21092979238256464A15DC471C778AEF3BB31B95A758DB23A92A976C7E52712529F8520EF10B1D0E0791FA53A2E16F897582EC29CC25F8EF2775685F223F63E34F1C769D8AE4557A9AECE34546D5AEDC173CBA88CC361085B6AC10E2B883E3800F0F115E4D73A1A56919BDA2379FB6BCAFB97C8E293F817DC7C7AD37B67164FB1346C36DC4EEBF247B43BDF957103D13814421F6A66B7A14FAAB66B7AD39BF21A419F053C7B7653EF207E74F31E6323CAA5BB16BF769165D89D5E4B47DA18C9FE13C163D0E0FF0035353151D73455ADBD431EFB6F47B0CD6944B8591843E9230A0A4F2E3DE3AF7D51429DA16F448AB358159AA8A1451450047B5BE9B56A9D3ABB6B4E21A77B66DC42D7C9385713FD2555EDD3F6083A6ED4D5BA037BADA38A967E2715D54A3D49A28ACA5760752A21B43D1C9D5566DF8E948B8C50551D5CB7FBD04F71E9DC71E34514356A80E1ECA345AADB14DFAE2C944B7D2531DB58C169BEA48E855FA79D32A8A2B22A91AC3145145318689B062DC62391263087D874616DAC642856F4A424000600E94514019A28A2800A28A2803FFFD9") + + val writeTask=WriteIDCardTask(ctx.card, cardProtocol!!.reader,App.localStorage, App.pinStorage, idCardData, ctx.card.issuer.privateDataKey, + object: CardProtocol.Notifications{ + override fun onReadProgress(cardProtocol: CardProtocol?, progress: Int) { + LOG.i("WriteIDCardTask","onReadProgress: ${progress}%") + } + override fun onReadWait(msec: Int) { + } + override fun onReadStart(cardProtocol: CardProtocol?) { + LOG.i("WriteIDCardTask","onReadStart") + rlProgressBar?.post { rlProgressBar?.visibility = View.VISIBLE } + + } + override fun onReadCancel() { + LOG.i("WriteIDCardTask","onReadCancel") + rlProgressBar?.postDelayed({ + try { + rlProgressBar?.visibility = View.GONE + } catch (e: Exception) { + e.printStackTrace() + } + }, 500) + + } + override fun onReadFinish(cardProtocol: CardProtocol?) { + LOG.i("WriteIDCardTask","onReadFinish") + rlProgressBar?.postDelayed({ + try { + rlProgressBar?.visibility = View.GONE + } catch (e: Exception) { + e.printStackTrace() + } + }, 500) + + } + override fun onReadAfterRequest() { + } + override fun onReadBeforeRequest(timeout: Int) { + } + }) + writeTask.start() + return + } + //TODO end (activity as MainActivity).nfcManager.ignoreTag(isoDep.tag) return } diff --git a/build.gradle b/build.gradle index c4edca4c38..dde59cbded 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.3.60' + ext.kotlin_version = '1.3.61' repositories { google() jcenter() diff --git a/tangem-card-old/src/main/java/com/tangem/tangem_card/data/TangemCard.java b/tangem-card-old/src/main/java/com/tangem/tangem_card/data/TangemCard.java index 8fe19575d6..0fad9c1874 100644 --- a/tangem-card-old/src/main/java/com/tangem/tangem_card/data/TangemCard.java +++ b/tangem-card-old/src/main/java/com/tangem/tangem_card/data/TangemCard.java @@ -1,9 +1,14 @@ package com.tangem.tangem_card.data; import com.tangem.tangem_card.reader.CardProtocol; +import com.tangem.tangem_card.reader.ProductMask; import com.tangem.tangem_card.reader.SettingsMask; +import com.tangem.tangem_card.reader.TLV; +import com.tangem.tangem_card.reader.TLVList; import com.tangem.tangem_card.util.Util; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.Date; import java.util.EnumSet; @@ -288,6 +293,28 @@ public class TangemCard { return ""; } + private byte[] issuerDataEx; + private byte[] issuerDataExSignature; + private int issuerDataExCounter; + + public byte[] getIssuerDataEx() { + return issuerDataEx; + } + + public byte[] getIssuerDataExSignature() { + return issuerDataExSignature; + } + + public int getIssuerDataExCounter() { + return issuerDataExCounter; + } + + public void setIssuerDataEx(byte[] value, byte[] signature, int counter) { + issuerDataEx = value; + issuerDataExSignature = signature; + issuerDataExCounter = counter; + } + private int pauseBeforePIN2 = 0; public void setPauseBeforePIN2(int value) { @@ -579,6 +606,79 @@ public class TangemCard { this.UID = UID; } + public int productMask = ProductMask.Note; + + public void setProductMask(int productMask) { + this.productMask = productMask; + } + + public boolean isIDCard() { + try { + return (productMask & ProductMask.IdCard) != 0; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + TLVList tlvIDCardData = null; + + public boolean hasIDCardData() { + if (tlvIDCardData == null) { + if (issuerDataEx == null) return false; + try { + tlvIDCardData = TLVList.fromBytes(issuerDataEx); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + return tlvIDCardData.hasTag(TLV.Tag.TAG_FullName) && tlvIDCardData.hasTag(TLV.Tag.TAG_Birthday) && tlvIDCardData.hasTag(TLV.Tag.TAG_Gender) && tlvIDCardData.hasTag(TLV.Tag.TAG_Photo) + && tlvIDCardData.hasTag(TLV.Tag.TAG_IssueDate) && tlvIDCardData.hasTag(TLV.Tag.TAG_ExpireDate) && tlvIDCardData.hasTag(TLV.Tag.TAG_TrustedAddress); + } + + public static class IDCardData + { + public String fullName; + public String birthday; + public String gender; + public byte[] photo; + public String issueDate; + public String expireDate; + public String trustedAddress; + + public TLVList toTLVList() { + TLVList tlvIDCardData=new TLVList(); + tlvIDCardData.add(new TLV(TLV.Tag.TAG_FullName,fullName.getBytes(StandardCharsets.UTF_8))); + tlvIDCardData.add(new TLV(TLV.Tag.TAG_Birthday,birthday.getBytes(StandardCharsets.UTF_8))); + tlvIDCardData.add(new TLV(TLV.Tag.TAG_Gender, gender.getBytes(StandardCharsets.UTF_8))); + tlvIDCardData.add(new TLV(TLV.Tag.TAG_Photo, photo)); + tlvIDCardData.add(new TLV(TLV.Tag.TAG_IssueDate,issueDate.getBytes(StandardCharsets.UTF_8))); + tlvIDCardData.add(new TLV(TLV.Tag.TAG_ExpireDate,expireDate.getBytes(StandardCharsets.UTF_8))); + tlvIDCardData.add(new TLV(TLV.Tag.TAG_TrustedAddress,trustedAddress.getBytes(StandardCharsets.UTF_8))); + return tlvIDCardData; + } + + public static IDCardData fromTLVList(TLVList tlvIDCardData) + { + IDCardData result=new IDCardData(); + result.fullName=tlvIDCardData.getTLV(TLV.Tag.TAG_FullName).getAsString(); + result.birthday=tlvIDCardData.getTLV(TLV.Tag.TAG_Birthday).getAsString(); + result.gender=tlvIDCardData.getTLV(TLV.Tag.TAG_Gender).getAsString(); + result.photo=tlvIDCardData.getTLV(TLV.Tag.TAG_Photo).Value; + result.issueDate=tlvIDCardData.getTLV(TLV.Tag.TAG_IssueDate).getAsString(); + result.expireDate=tlvIDCardData.getTLV(TLV.Tag.TAG_ExpireDate).getAsString(); + result.trustedAddress=tlvIDCardData.getTLV(TLV.Tag.TAG_TrustedAddress).getAsString(); + return result; + } + } + + public IDCardData getIDCardData() + { + if( !hasIDCardData() ) return null; + return IDCardData.fromTLVList(tlvIDCardData); + } private byte[] offlineBalance; @@ -638,164 +738,6 @@ public class TangemCard { this.tagSignature = tagSignature; } -// public Bundle getAsBundle() { -// Bundle B = new Bundle(); -// saveToBundle(B); -// return B; -// } -// -// public void saveToBundle(Bundle B) { -// try { -// B.putString("UID", UID); -// B.putByteArray("CID", CID); -// B.putString("PIN", PIN); -// B.putString("PIN2", PIN2.name()); -// B.putString("Status", status.name()); -// B.putString("Blockchain", blockchainID); -// B.putString("BlockchainName", blockchainName); -// B.putInt("TokensDecimal", tokensDecimal); -// B.putString("TokenSymbol", tokenSymbol); -// B.putString("ContractAddress", contractAddress); -// if (dtPersonalization != null) B.putLong("dtPersonalization", dtPersonalization.getTime()); -// B.putInt("RemainingSignatures", remainingSignatures); -// B.putInt("MaxSignatures", maxSignatures); -// B.putInt("Health", health); -// if (settingsMask != null) B.putInt("settingsMask", settingsMask); -// B.putInt("pauseBeforePIN2", pauseBeforePIN2); -// if (signingMethod != null) B.putString("signingMethod", signingMethod.name()); -// if (manufacturer != null) B.putString("Manufacturer", manufacturer.name()); -// if (encryptionMode != null) B.putString("EncryptionMode", encryptionMode.name()); -// if (issuer != null) B.putString("Issuer", issuer.getID()); -// if (issuerPublicDataKey != null) B.putByteArray("IssuerPublicDataKey", issuerPublicDataKey); -// if (firmwareVersion != null) B.putString("FirmwareVersion", firmwareVersion); -// if (batch != null) B.putString("Batch", batch); -// B.putBoolean("ManufacturerConfirmed", manufacturerConfirmed); -// B.putBoolean("CardPublicKeyValid", isCardPublicKeyValid()); -// B.putByteArray("CardPublicKey", getCardPublicKey()); -// -// B.putInt("SignedHashes", getSignedHashes()); -// B.putBoolean("WalletPublicKeyValid", isWalletPublicKeyValid()); -// if (pbWalletKey != null) -// B.putByteArray("PublicKey", pbWalletKey); -// if (pbWalletKeyRar != null) -// B.putByteArray("PublicKeyRar", pbWalletKeyRar); -// -// if (getOfflineBalance() != null) B.putByteArray("OfflineBalance", getOfflineBalance()); -// -// if (getDenomination() != null) B.putByteArray("Denomination", getDenomination()); -// if (getDenominationText() != null) B.putString("DenominationText", getDenominationText()); -// -// if (getIssuerData() != null && getIssuerDataSignature() != null) { -// B.putByteArray("IssuerData", getIssuerData()); -// B.putByteArray("IssuerDataSignature", getIssuerDataSignature()); -// B.putBoolean("NeedWriteIssuerData", getNeedWriteIssuerData()); -// } -// -// if (codeConfirmed != null) -// B.putBoolean("codeConfirmed", codeConfirmed); -// -// if (codeConfirmed != null) -// B.putBoolean("codeConfirmed", codeConfirmed); -// -// if (onlineVerified != null) -// B.putBoolean("onlineVerified", onlineVerified); -// -// if (onlineValidated != null) -// B.putBoolean("onlineValidated", onlineValidated); -// -// if (codeConfirmed != null) -// B.putBoolean("codeConfirmed", codeConfirmed); -// -// if (codeConfirmed != null) -// B.putBoolean("codeConfirmed", codeConfirmed); -// -// if (onlineVerified != null) -// B.putBoolean("onlineVerified", onlineVerified); -// -// if (onlineValidated != null) -// B.putBoolean("onlineValidated", onlineValidated); -// } catch (Exception e) { -// Log.e("Can't save to bundle ", e.getMessage()); -// } -// -// } -// -// public void loadFromBundle(Bundle B) { -// UID = B.getString("UID"); -// CID = B.getByteArray("CID"); -// PIN = B.getString("PIN"); -// PIN2 = PIN2_Mode.valueOf(B.getString("PIN2")); -// status = Status.valueOf(B.getString("Status")); -// blockchainID = B.getString("Blockchain"); -// tokensDecimal = B.getInt("TokensDecimal", 18); -// tokenSymbol = B.getString("TokenSymbol", ""); -// contractAddress = B.getString("ContractAddress", ""); -// if (B.containsKey("BlockchainName")) -// blockchainName = B.getString("BlockchainName", ""); -// if (B.containsKey("dtPersonalization")) { -// dtPersonalization = new Date(B.getLong("dtPersonalization")); -// } -// remainingSignatures = B.getInt("RemainingSignatures"); -// maxSignatures = B.getInt("MaxSignatures"); -// health = B.getInt("health"); -// if (B.containsKey("settingsMask")) settingsMask = B.getInt("settingsMask"); -// pauseBeforePIN2 = B.getInt("pauseBeforePIN2"); -// if (B.containsKey("signingMethod")) -// signingMethod = SigningMethod.valueOf(B.getString("signingMethod")); -// if (B.containsKey("Manufacturer")) -// manufacturer = Manufacturer.valueOf(B.getString("Manufacturer")); -// manufacturerConfirmed = B.getBoolean("ManufacturerConfirmed"); -// if (B.containsKey("EncryptionMode")) -// encryptionMode = EncryptionMode.valueOf(B.getString("EncryptionMode")); -// else -// encryptionMode = null; -// -// if (B.containsKey("SignedHashes")) setSignedHashes(B.getInt("SignedHashes")); -// -// if (B.containsKey("Issuer")) issuer = Issuer.FindIssuer(B.getString("Issuer")); -// if (B.containsKey("IssuerPublicDataKey")) -// issuerPublicDataKey = B.getByteArray("IssuerPublicDataKey"); -// -// if (B.containsKey("FirmwareVersion")) firmwareVersion = B.getString("FirmwareVersion"); -// if (B.containsKey("Batch")) batch = B.getString("Batch"); -// -// cardPublicKeyValid = B.getBoolean("CardPublicKeyValid"); -// if (B.containsKey("CardPublicKey")) setCardPublicKey(B.getByteArray("CardPublicKey")); -// -// if (B.containsKey("OfflineBalance")) setOfflineBalance(B.getByteArray("OfflineBalance")); -// else clearOfflineBalance(); -// -// if (B.containsKey("Denomination") && B.containsKey("DenominationText")) { -// setDenomination(B.getByteArray("Denomination"), B.getString("DenominationText")); -// } else if (B.containsKey("Denomination")) { -// setDenomination(B.getByteArray("Denomination"), "N/A"); -// } else clearDenomination(); -// -// if (B.containsKey("IssuerData") && B.containsKey("IssuerDataSignature")) -// setIssuerData(B.getByteArray("IssuerData"), B.getByteArray("IssuerDataSignature")); -// else setIssuerData(null, null); -// -// if (B.containsKey("NeedWriteIssuerData")) -// setNeedWriteIssuerData(B.getBoolean("NeedWriteIssuerData")); -// -// walletPublicKeyValid = B.getBoolean("WalletPublicKeyValid"); -// if (B.containsKey("PublicKey")) { -// pbWalletKey = B.getByteArray("PublicKey"); -// } -// if (B.containsKey("PublicKeyRar")) { -// pbWalletKeyRar = B.getByteArray("PublicKeyRar"); -// } -// -// if (B.containsKey("codeConfirmed")) -// codeConfirmed = B.getBoolean("codeConfirmed"); -// -// if (B.containsKey("onlineVerified")) -// onlineVerified = B.getBoolean("onlineVerified"); -// -// if (B.containsKey("onlineValidated")) -// onlineValidated = B.getBoolean("onlineValidated"); -// } - public enum EncryptionMode { None((byte) 0x0), Fast((byte) 0x1), Strong((byte) 0x2); diff --git a/tangem-card-old/src/main/java/com/tangem/tangem_card/reader/CardProtocol.java b/tangem-card-old/src/main/java/com/tangem/tangem_card/reader/CardProtocol.java index 94e1a7b799..518318a219 100644 --- a/tangem-card-old/src/main/java/com/tangem/tangem_card/reader/CardProtocol.java +++ b/tangem-card-old/src/main/java/com/tangem/tangem_card/reader/CardProtocol.java @@ -1,5 +1,6 @@ package com.tangem.tangem_card.reader; +import com.tangem.tangem_card.data.Issuer; import com.tangem.tangem_card.data.Manufacturer; import com.tangem.tangem_card.data.TangemCard; import com.tangem.tangem_card.util.Log; @@ -17,6 +18,7 @@ import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.spec.ECGenParameterSpec; +import java.util.Arrays; import javax.crypto.KeyAgreement; @@ -99,6 +101,11 @@ public class CardProtocol { private NfcReader mIsoDep; + public NfcReader getReader() + { + return mIsoDep; + } + public static final String DefaultPIN = "000000"; public static final String DefaultPIN2 = "000"; @@ -1095,6 +1102,12 @@ public class CardProtocol { if (issuerData == null || issuerDataSignature == null) throw new TangemException("Invalid answer format (GetIssuerData)"); + if( issuerData.Value.length==0 && issuerDataSignature.Value.length==0 ) + { + mCard.setIssuerData(null, null); + return new TLVList(); + } + ByteArrayOutputStream bsDataToVerify = new ByteArrayOutputStream(); bsDataToVerify.write(mCard.getCID()); bsDataToVerify.write(issuerData.Value); @@ -1117,6 +1130,185 @@ public class CardProtocol { } } + /** + * Series of GET_ISSUER_DATA command to read extra data and verify issuer signature of returned extra data + * See [1] 3.3, 8.7 + * This command returns Issuer_Extra_Data data block and its issuer’s signature. + * + * @return TLVList with issuerData (if success read and verify) + * @throws Exception - if something went wrong + */ + public TLVList run_GetIssuerDataEx(Notifications notifications) throws Exception { + boolean needReadIssuerDataCounter = (readResult.getTagAsInt(TLV.Tag.TAG_SettingsMask) & SettingsMask.ProtectIssuerDataAgainstReplay) != 0; + TLV issuerDataSignature = null; + TLV issuerDataCounter = null; + TLV issuerDataSize = null; + ByteArrayOutputStream bsIssuerDataEx = new ByteArrayOutputStream(); + do { + + CommandApdu rqApdu = StartPrepareCommand(INS.GetIssuerData); + rqApdu.addTLV_U8(TLV.Tag.TAG_Mode, 0x01); + rqApdu.addTLV_U16(TLV.Tag.TAG_Offset, bsIssuerDataEx.size()); + + Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" "))); + + ResponseApdu rspApdu = SendAndReceive(rqApdu, false); + + if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) { + Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" "))); + TLV issuerData = rspApdu.getTLVs().getTLV(TLV.Tag.TAG_Issuer_Data); + if (issuerData != null && issuerData.Value != null) + bsIssuerDataEx.write(issuerData.Value); + issuerDataSignature = rspApdu.getTLVs().getTLV(TLV.Tag.TAG_Issuer_Data_Signature); + issuerDataCounter = rspApdu.getTLVs().getTLV(TLV.Tag.TAG_Issuer_Data_Counter); + if (issuerDataSize == null) + issuerDataSize = rspApdu.getTLVs().getTLV(TLV.Tag.TAG_Size); + } else { + throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2())); + } + if (issuerDataSize != null && issuerDataSize.getAsInt() != 0) { + if (notifications != null) + notifications.onReadProgress(this, (int) Math.round(bsIssuerDataEx.size() * 100.0 / issuerDataSize.getAsInt())); + Log.i(logTag, String.format("Read %d/%d bytes", bsIssuerDataEx.size(), issuerDataSize.getAsInt())); + } + + } while ((issuerDataSignature == null) || (needReadIssuerDataCounter && (issuerDataCounter == null))); + boolean protectIssuerDataAgainstReplay = (readResult.getTagAsInt(TLV.Tag.TAG_SettingsMask) & SettingsMask.ProtectIssuerDataAgainstReplay) != 0; + + if (notifications != null) notifications.onReadProgress(this, 100); + + if (bsIssuerDataEx.size() > 0 && issuerDataSignature != null && issuerDataSignature.Value != null) { + ByteArrayOutputStream bsDataToVerify = new ByteArrayOutputStream(); + bsDataToVerify.write(mCard.getCID()); + bsDataToVerify.write(bsIssuerDataEx.toByteArray()); + if (protectIssuerDataAgainstReplay) { + bsDataToVerify.write(issuerDataCounter.Value); + } + try { + if (CardCrypto.VerifySignature(mCard.getIssuerPublicDataKey(), bsDataToVerify.toByteArray(), issuerDataSignature.Value)) { + mCard.setIssuerDataEx(bsIssuerDataEx.toByteArray(), issuerDataSignature.Value, issuerDataCounter.getAsInt()); + return TLVList.fromBytes(bsIssuerDataEx.toByteArray()); + } else { + throw new TangemException("Invalid issuer data read (signature verification failed)"); + } + } catch (Exception e) { + e.printStackTrace(); + throw new TangemException("Invalid issuer data read"); + } + } else { + mCard.setIssuerDataEx(null, null, issuerDataCounter.getAsInt()); + return null; + } + } + + /** + * Series of WRITE_ISSUER_DATA command to write extra data: + * 1. start write procedure + * 2. series of writing data blocks + * 3. finalize write procedure + * This command make signatures with provided issuer privateDataKey + * - to start write procedure (sign CID|Counter|Size) + * - to finalize write procedure (sign CID|dataEx|Counter) + * See [1] 3.3, 8.8 + * + * @throws Exception - if something went wrong + */ + public void run_WriteIssuerDataEx(Notifications notifications, byte[] dataEx, byte[] issuerPrivateDataKey) throws Exception { + boolean needCounter = readResult.getTLV(TLV.Tag.TAG_SettingsMask) != null && (readResult.getTagAsInt(TLV.Tag.TAG_SettingsMask) & SettingsMask.ProtectIssuerDataAgainstReplay) != 0; + + Log.i(logTag, "WriteIssuerDataEx - Start"); + + int issuerDataCounter = 0; + ByteArrayOutputStream osDataToSign = new ByteArrayOutputStream(); + osDataToSign.write(mCard.getCID()); + if (needCounter) { + issuerDataCounter = mCard.getIssuerDataExCounter() + 1; + osDataToSign.write(Util.intToByteArray4(issuerDataCounter)); + } + osDataToSign.write(Util.intToByteArray2(dataEx.length)); + + byte[] issuerDataSignature = CardCrypto.Signature(issuerPrivateDataKey, osDataToSign.toByteArray()); + + CommandApdu rqApdu = StartPrepareCommand(INS.WriteIssuerData); + rqApdu.addTLV_U8(TLV.Tag.TAG_Mode, 0x01); + if (needCounter) { + rqApdu.addTLV_U32(TLV.Tag.TAG_Issuer_Data_Counter, issuerDataCounter); + } + rqApdu.addTLV_U16(TLV.Tag.TAG_Size, dataEx.length); + rqApdu.addTLV(TLV.Tag.TAG_Issuer_Data_Signature, issuerDataSignature); + + Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" "))); + + ResponseApdu rspApdu = SendAndReceive(rqApdu, true); + + if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) { + Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" "))); + } else { + throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2())); + } + + if (notifications != null) + notifications.onReadProgress(this, 10); + + + Log.i(logTag, "WriteIssuerDataEx - Send data"); + int offset = 0; + int partSize = 1524; + while (offset < dataEx.length) { + + if (dataEx.length - offset < partSize) { + partSize = dataEx.length - offset; + } + byte[] part = Arrays.copyOfRange(dataEx, offset, offset + partSize); + + rqApdu = StartPrepareCommand(INS.WriteIssuerData); + rqApdu.addTLV_U8(TLV.Tag.TAG_Mode, 0x02); + rqApdu.addTLV_U16(TLV.Tag.TAG_Offset, offset); + rqApdu.addTLV(TLV.Tag.TAG_Issuer_Data, part); + + Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" "))); + + rspApdu = SendAndReceive(rqApdu, true); + + if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) { + Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" "))); + } else { + throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2())); + } + + offset += partSize; + if (notifications != null) + notifications.onReadProgress(this, 10+(int) Math.round(offset * 80.0 / dataEx.length)); + } + + Log.i(logTag, "WriteIssuerDataEx - Finalize"); + + osDataToSign = new ByteArrayOutputStream(); + osDataToSign.write(mCard.getCID()); + osDataToSign.write(dataEx); + if (needCounter) { + osDataToSign.write(Util.intToByteArray4(issuerDataCounter)); + } + issuerDataSignature = CardCrypto.Signature(issuerPrivateDataKey, osDataToSign.toByteArray()); + + rqApdu = StartPrepareCommand(INS.WriteIssuerData); + rqApdu.addTLV_U8(TLV.Tag.TAG_Mode, 0x03); + rqApdu.addTLV(TLV.Tag.TAG_Issuer_Data_Signature, issuerDataSignature); + + Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" "))); + + rspApdu = SendAndReceive(rqApdu, true); + + if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) { + Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" "))); + } else { + throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2())); + } + if (notifications != null) + notifications.onReadProgress(this, 100); + } + + /** * Execute consecutive READ commands with increasing encryption level from EncryptionMode.None to EncryptionMode.Strong, see {@link TangemCard.EncryptionMode} * If card requires stricter encryption level it returns SW.NEED_ENCRYPTION status word diff --git a/tangem-card-old/src/main/java/com/tangem/tangem_card/reader/ProductMask.java b/tangem-card-old/src/main/java/com/tangem/tangem_card/reader/ProductMask.java new file mode 100644 index 0000000000..15516edaab --- /dev/null +++ b/tangem-card-old/src/main/java/com/tangem/tangem_card/reader/ProductMask.java @@ -0,0 +1,25 @@ +package com.tangem.tangem_card.reader; + +/** + * Created by dvol on 15.07.2019. + */ + +public class ProductMask { + public static final int Note = 0x0001; + public static final int Tag = 0x0002; + public static final int IdCard = 0x0004; + + public static String getDescription(int iValue) { + StringBuilder sb = new StringBuilder(); + sb.append("["); + if ((iValue & ProductMask.Note) != 0) sb.append("Note, "); + if ((iValue & ProductMask.Tag) != 0) + sb.append("Tag, "); + if ((iValue & ProductMask.IdCard) != 0) + sb.append("IdCard, "); + + if (sb.length() > 1) sb.delete(sb.length() - 2, sb.length()); + sb.append("]"); + return sb.toString(); + } +} diff --git a/tangem-card-old/src/main/java/com/tangem/tangem_card/reader/SettingsMask.java b/tangem-card-old/src/main/java/com/tangem/tangem_card/reader/SettingsMask.java index 81470d9586..0513936d3d 100644 --- a/tangem-card-old/src/main/java/com/tangem/tangem_card/reader/SettingsMask.java +++ b/tangem-card-old/src/main/java/com/tangem/tangem_card/reader/SettingsMask.java @@ -29,8 +29,16 @@ public class SettingsMask { public static final int DisablePrecomputedNDEF = 0x00010000; + public static final int SkipSecurityDelayIfValidatedByIssuer = 0x00020000; + public static final int SkipCheckPIN2andCVCIfValidatedByIssuer = 0x00040000; public static final int SkipSecurityDelayIfValidatedByLinkedTerminal = 0x00080000; + public static final int RestrictOverwriteIssuerDataEx = 0x00100000; + + public static final int RequireTermTxSignature = 0x01000000; + public static final int RequireTermCertSignature = 0x02000000; + public static final int CheckPIN3onCard = 0x04000000; + public static String getDescription(int iValue) { StringBuilder sb=new StringBuilder(); sb.append("["); @@ -63,6 +71,24 @@ public class SettingsMask { if ((iValue & SettingsMask.SkipSecurityDelayIfValidatedByLinkedTerminal) != 0) sb.append("SkipSecurityDelayIfValidatedByLinkedTerminal, "); + + if ((iValue & SettingsMask.SkipSecurityDelayIfValidatedByIssuer) != 0) + sb.append("SkipSecurityDelayIfValidatedByIssuer, "); + if ((iValue & SettingsMask.SkipCheckPIN2andCVCIfValidatedByIssuer) != 0) + sb.append("SkipCheckPIN2andCVCIfValidatedByIssuer, "); + + if ((iValue & SettingsMask.RestrictOverwriteIssuerDataEx) != 0) + sb.append("RestrictOverwriteIssuerDataEx, "); + + if ((iValue & SettingsMask.RequireTermTxSignature) != 0) + sb.append("RequireTermTxSignature, "); + + if ((iValue & SettingsMask.RequireTermCertSignature) != 0) + sb.append("RequireTermCertSignature, "); + + if ((iValue & SettingsMask.CheckPIN3onCard) != 0) + sb.append("CheckPIN3onCard, "); + if (sb.length() > 1) sb.delete(sb.length() - 2, sb.length()); sb.append("]"); return sb.toString(); diff --git a/tangem-card-old/src/main/java/com/tangem/tangem_card/reader/TLV.java b/tangem-card-old/src/main/java/com/tangem/tangem_card/reader/TLV.java index 4188e0ca54..4e704fe1f0 100644 --- a/tangem-card-old/src/main/java/com/tangem/tangem_card/reader/TLV.java +++ b/tangem-card-old/src/main/java/com/tangem/tangem_card/reader/TLV.java @@ -44,15 +44,27 @@ public class TLV { TAG_Session_Key_B(0x1B), TAG_Pause(0x1C), + TAG_PIN3(0x1D), + TAG_NewPIN3(0x1E), + TAG_CrEx_Key(0x1F), + TAG_Manufacture_ID(0x20), TAG_Manufacturer_Signature(0x21), + TAG_Mode(0x23), + TAG_Offset(0x24), + TAG_Size(0x25), + + TAG_User_Data(0x2A), + TAG_User_ProtectedData(0x2B), + TAG_Issuer_Data_PublicKey(0x30), TAG_Issuer_Transaction_PublicKey(0x31), TAG_Issuer_Data(0x32), TAG_Issuer_Data_Signature(0x33), TAG_Issuer_Transaction_Signature(0x34), TAG_Issuer_Data_Counter(0x35), + TAG_Acquirer_PublicKey(0x37), TAG_IsActivated(0x3A), TAG_ActivationSeed(0x3B), @@ -65,12 +77,38 @@ public class TLV { TAG_TrOut_Hash(0x50), TAG_TrOut_HashSize(0x51), TAG_TrOut_Raw(0x52), + TAG_TrOut_Amount(0x53), + + TAG_Terminal_PaymentFlowVersion(0x54), + TAG_Terminal_Certificate(0x55), + // hash of extra data to transaction (for SIGN_HASH_EXTERNAL) + TAG_TrOut_Extra_Hash(0x56), + TAG_Terminal_Transaction_Signature(0x57), + TAG_Terminal_Certificate_Id(0x5A), + TAG_Terminal_Certificate_Param(0x5B), + TAG_Terminal_Certificate_PublicKey(0x5C), + TAG_Terminal_Certificate_Signature(0x5D), TAG_Wallet_PublicKey(0x60), TAG_Signature(0x61), TAG_RemainingSignatures(0x62), TAG_SignedHashes(0x63), + TAG_CheckWalletCounter(0x64), + + TAG_CrEx_Salt(0x65), + TAG_CrEx_Message(0x66), + TAG_CrEx_CryptedMessage(0x67), + TAG_CrEx_HMAC(0x68), + + TAG_CrEx_RequirePIN3(0x69), + TAG_TrOut_Extra_Signature(0x6A), + TAG_SignCommand_ChecksPassed(0x6B), + + TAG_Wallet_PrivateKey(0x70), + TAG_Card_PrivateKey(0x71), + TAG_Block_Reason(0x72), + TAG_Firmware(0x80), TAG_Batch(0x81), TAG_ManufactureDateTime(0x82), @@ -79,6 +117,10 @@ public class TLV { TAG_Manufacturer_PublicKey(0x85), TAG_CardID_Manufacturer_Signature(0x86), + TAG_ProductMask(0x8A), + + TAG_PinLessFloorLimit(0x90), + TAG_Token_Symbol(0xA0), TAG_Token_Contract_Address(0xA1), TAG_Token_Decimal(0xA2), @@ -87,6 +129,14 @@ public class TLV { TAG_LastSign_Date(0xC2), TAG_DenominationText(0xC3), + TAG_FullName (0xD0), + TAG_Birthday (0xD1), + TAG_Photo (0xD2), + TAG_Gender(0xD3), + TAG_IssueDate (0xD4), + TAG_ExpireDate(0xD5), + TAG_TrustedAddress (0xD6), + TAG_Terminal_IsLinked(0x58), TAG_Terminal_PublicKey(0x5C), TAG_Terminal_TransactionSignature(0x57); @@ -172,7 +222,7 @@ public class TLV { } public String getAsString() { - if( Value.length==0 ) return ""; + if (Value.length == 0) return ""; if (Value[Value.length - 1] == 0) { String s1 = new String(Arrays.copyOfRange(Value, 0, Value.length - 1), Charset.forName("utf-8")); @@ -214,18 +264,29 @@ public class TLV { return String.format("%s[]: [[NULL]]", tag.name()); } case TAG_SettingsMask: { - StringBuilder sb=new StringBuilder(); - if( Value!=null ) { + StringBuilder sb = new StringBuilder(); + if (Value != null) { try { int iValue = Util.byteArrayToInt(Value); return String.format("%s[%d]: %s (%s)", tag.name(), Value.length, Util.bytesToHex(Value), SettingsMask.getDescription(iValue)); - } - catch (Exception e) - { + } catch (Exception e) { e.printStackTrace(); return String.format("%s[%d]: %s", tag.name(), Value.length, Util.bytesToHex(Value)); } - }else{ + } else { + return String.format("%s[]: [[NULL]]", tag.name()); + } + } + case TAG_ProductMask: { + if (Value != null) { + try { + int iValue = Util.byteArrayToInt(Value); + return String.format("%s[%d]: %s (%s)", tag.name(), Value.length, Util.bytesToHex(Value), ProductMask.getDescription(iValue)); + } catch (Exception e) { + e.printStackTrace(); + return String.format("%s[%d]: %s (%s)", tag.name(), Value.length, Util.bytesToHex(Value)); + } + } else { return String.format("%s[]: [[NULL]]", tag.name()); } } diff --git a/tangem-card-old/src/main/java/com/tangem/tangem_card/reader/TLVList.java b/tangem-card-old/src/main/java/com/tangem/tangem_card/reader/TLVList.java index 40c36a777b..8f7ef8875f 100644 --- a/tangem-card-old/src/main/java/com/tangem/tangem_card/reader/TLVList.java +++ b/tangem-card-old/src/main/java/com/tangem/tangem_card/reader/TLVList.java @@ -85,4 +85,8 @@ public class TLVList extends ArrayList { while (tlv != null); return tlvList; } + + public boolean hasTag(TLV.Tag tag) { + return getTLV(tag)!=null; + } } diff --git a/tangem-card-old/src/main/java/com/tangem/tangem_card/tasks/CustomReadCardTask.java b/tangem-card-old/src/main/java/com/tangem/tangem_card/tasks/CustomReadCardTask.java index 453ecd2b28..d0f5274e93 100644 --- a/tangem-card-old/src/main/java/com/tangem/tangem_card/tasks/CustomReadCardTask.java +++ b/tangem-card-old/src/main/java/com/tangem/tangem_card/tasks/CustomReadCardTask.java @@ -7,6 +7,7 @@ import com.tangem.tangem_card.data.external.PINsProvider; import com.tangem.tangem_card.reader.CardCrypto; import com.tangem.tangem_card.reader.CardProtocol; import com.tangem.tangem_card.reader.NfcReader; +import com.tangem.tangem_card.reader.ProductMask; import com.tangem.tangem_card.reader.TLV; import com.tangem.tangem_card.reader.TLVException; import com.tangem.tangem_card.reader.TLVList; @@ -137,6 +138,12 @@ public class CustomReadCardTask extends Thread { mCard.setBatch(tlvCardData.getTLV(TLV.Tag.TAG_Batch).getAsHexString()); + if( tlvCardData.hasTag(TLV.Tag.TAG_ProductMask)) { + mCard.setProductMask(tlvCardData.getTagAsInt(TLV.Tag.TAG_ProductMask)); + }else{ + mCard.setProductMask(ProductMask.Note); + } + TLV tokenSymbol = tlvCardData.getTLV(TLV.Tag.TAG_Token_Symbol); TLV contractAddress = tlvCardData.getTLV(TLV.Tag.TAG_Token_Contract_Address); TLV tokens_decimal = tlvCardData.getTLV(TLV.Tag.TAG_Token_Decimal); diff --git a/tangem-card-old/src/main/java/com/tangem/tangem_card/tasks/VerifyCardTask.java b/tangem-card-old/src/main/java/com/tangem/tangem_card/tasks/VerifyCardTask.java index 57cb73c822..4b70353a3b 100644 --- a/tangem-card-old/src/main/java/com/tangem/tangem_card/tasks/VerifyCardTask.java +++ b/tangem-card-old/src/main/java/com/tangem/tangem_card/tasks/VerifyCardTask.java @@ -46,6 +46,10 @@ public class VerifyCardTask extends CustomReadCardTask { } mNotifications.onReadProgress(protocol, 90); + if( protocol.getCard().isIDCard() ) + { + protocol.run_GetIssuerDataEx(mNotifications); + } } } \ No newline at end of file diff --git a/tangem-card-old/src/main/java/com/tangem/tangem_card/tasks/WriteIDCardTask.java b/tangem-card-old/src/main/java/com/tangem/tangem_card/tasks/WriteIDCardTask.java new file mode 100644 index 0000000000..9329607641 --- /dev/null +++ b/tangem-card-old/src/main/java/com/tangem/tangem_card/tasks/WriteIDCardTask.java @@ -0,0 +1,36 @@ +package com.tangem.tangem_card.tasks; + +import com.tangem.tangem_card.data.TangemCard; +import com.tangem.tangem_card.data.external.CardDataSubstitutionProvider; +import com.tangem.tangem_card.data.external.PINsProvider; +import com.tangem.tangem_card.reader.CardProtocol; +import com.tangem.tangem_card.reader.NfcReader; + +/** + * Created by dvol on 19.01.2020. + */ + +public class WriteIDCardTask extends CustomReadCardTask { + public static final String TAG = WriteIDCardTask.class.getSimpleName(); + + private TangemCard.IDCardData idCardData; + private byte[] issuerPrivateDataKey; + public WriteIDCardTask(TangemCard card, NfcReader reader, CardDataSubstitutionProvider cardDataSubstitutionProvider, PINsProvider pinsProvider, TangemCard.IDCardData idCardData, byte[] issuerPrivateDataKey, CardProtocol.Notifications notifications) { + super(card, reader, cardDataSubstitutionProvider, pinsProvider, notifications); + this.idCardData=idCardData; + this.issuerPrivateDataKey=issuerPrivateDataKey; + } + + @Override + public void run_Task() throws Exception { + mNotifications.onReadProgress(protocol, 0); + if (isCancelled) return; + + if( !protocol.getCard().isIDCard() ) + throw new Exception("It's not an IDCard!"); + + protocol.run_WriteIssuerDataEx(mNotifications,idCardData.toTLVList().toBytes(),issuerPrivateDataKey); + mNotifications.onReadProgress(protocol, 100); + } + +} \ No newline at end of file diff --git a/tangem-sdk-old/src/main/java/com/tangem/tangem_sdk/data/TangemCardExtensions.kt b/tangem-sdk-old/src/main/java/com/tangem/tangem_sdk/data/TangemCardExtensions.kt index 9c6356dd4e..2f0cd24013 100644 --- a/tangem-sdk-old/src/main/java/com/tangem/tangem_sdk/data/TangemCardExtensions.kt +++ b/tangem-sdk-old/src/main/java/com/tangem/tangem_sdk/data/TangemCardExtensions.kt @@ -61,6 +61,13 @@ fun TangemCard.loadFromBundle(B: Bundle) { setIssuerData(B.getByteArray("IssuerData"), B.getByteArray("IssuerDataSignature")) else setIssuerData(null, null) + if (B.containsKey("IssuerDataEx") && B.containsKey("IssuerDataExSignature") && B.containsKey("IssuerDataExCounter")) + setIssuerDataEx(B.getByteArray("IssuerDataEx"), B.getByteArray("IssuerDataExSignature"), B.getInt("IssuerDataExCounter")) + else setIssuerDataEx(null, null, 0) + + if( B.containsKey("ProductMask") ) + setProductMask(B.getInt("ProductMask")) + if (B.containsKey("NeedWriteIssuerData")) needWriteIssuerData = B.getBoolean("NeedWriteIssuerData") @@ -149,6 +156,14 @@ fun TangemCard.saveToBundle(B: Bundle) { B.putBoolean("NeedWriteIssuerData", needWriteIssuerData) } + if (issuerDataEx != null && issuerDataExSignature != null) { + B.putByteArray("IssuerDataEx", issuerDataEx) + B.putByteArray("IssuerDataExSignature", issuerDataExSignature) + B.putInt("IssuerDataExCounter", issuerDataExCounter) + } + + B.putInt("ProductMask",productMask) + if (isCodeConfirmed != null) B.putBoolean("codeConfirmed", isCodeConfirmed) From a06d4f0cb585d15e353c7ef263731b76e5770a8d Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 20 Jan 2020 11:23:10 +0300 Subject: [PATCH 02/13] Updated on 2026-08-14 --- .../data/network/model/BlockcypherResponse.kt | 5 +- .../com/tangem/wallet/ethID/EthIdData.java | 33 + .../com/tangem/wallet/ethID/EthIdEngine.java | 663 ++++++++++++++++++ 3 files changed, 700 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/com/tangem/wallet/ethID/EthIdData.java create mode 100644 app/src/main/java/com/tangem/wallet/ethID/EthIdEngine.java diff --git a/app/src/main/java/com/tangem/data/network/model/BlockcypherResponse.kt b/app/src/main/java/com/tangem/data/network/model/BlockcypherResponse.kt index b470cba5ed..484526bc4d 100644 --- a/app/src/main/java/com/tangem/data/network/model/BlockcypherResponse.kt +++ b/app/src/main/java/com/tangem/data/network/model/BlockcypherResponse.kt @@ -35,7 +35,10 @@ data class BlockcypherTxref( data class BlockcypherTx( @SerializedName("hex") - var hex: String? = null + var hex: String? = null, + + @SerializedName("addresses") + var addesses: List? = null ) data class BlockcypherFee( diff --git a/app/src/main/java/com/tangem/wallet/ethID/EthIdData.java b/app/src/main/java/com/tangem/wallet/ethID/EthIdData.java new file mode 100644 index 0000000000..a3197b35d0 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/ethID/EthIdData.java @@ -0,0 +1,33 @@ +package com.tangem.wallet.ethID; + +import com.tangem.wallet.CoinData; + +public class EthIdData extends CoinData { + private boolean hasApprovalTx = false; + private long approvalAddressNonce; + private byte[] CKDpub; + + public boolean isHasApprovalTx() { + return hasApprovalTx; + } + + public void setHasApprovalTx(boolean hasApprovalTx) { + this.hasApprovalTx = hasApprovalTx; + } + + public long getApprovalAddressNonce() { + return approvalAddressNonce; + } + + public void setApprovalAddressNonce(long approvalAddressNonce) { + this.approvalAddressNonce = approvalAddressNonce; + } + + public byte[] getCKDpub() { + return CKDpub; + } + + public void setCKDpub(byte[] CKDpub) { + this.CKDpub = CKDpub; + } +} diff --git a/app/src/main/java/com/tangem/wallet/ethID/EthIdEngine.java b/app/src/main/java/com/tangem/wallet/ethID/EthIdEngine.java new file mode 100644 index 0000000000..d5f5522635 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/ethID/EthIdEngine.java @@ -0,0 +1,663 @@ +package com.tangem.wallet.ethID; + +import android.net.Uri; +import android.text.InputFilter; +import android.util.Log; + +import com.google.common.primitives.Bytes; +import com.tangem.App; +import com.tangem.Constant; +import com.tangem.data.Blockchain; +import com.tangem.data.network.ServerApiBlockcypher; +import com.tangem.data.network.ServerApiInfura; +import com.tangem.data.network.model.BlockcypherFee; +import com.tangem.data.network.model.BlockcypherResponse; +import com.tangem.data.network.model.BlockcypherTx; +import com.tangem.data.network.model.BlockcypherTxref; +import com.tangem.data.network.model.InfuraResponse; +import com.tangem.tangem_card.data.TangemCard; +import com.tangem.tangem_card.reader.CardProtocol; +import com.tangem.tangem_card.tasks.SignTask; +import com.tangem.util.CryptoUtil; +import com.tangem.util.DecimalDigitsInputFilter; +import com.tangem.wallet.BTCUtils; +import com.tangem.wallet.BalanceValidator; +import com.tangem.wallet.BuildConfig; +import com.tangem.wallet.CoinData; +import com.tangem.wallet.CoinEngine; +import com.tangem.wallet.ECDSASignatureETH; +import com.tangem.wallet.EthTransaction; +import com.tangem.wallet.Keccak256; +import com.tangem.wallet.R; +import com.tangem.wallet.TangemContext; +import com.tangem.wallet.eth.EthData; + +import org.bitcoin.Secp256k1Context; +import org.bitcoinj.core.ECKey; +import org.bitcoinj.core.SignatureDecodeException; +import org.bitcoinj.crypto.ChildNumber; +import org.bitcoinj.crypto.DeterministicKey; +import org.bitcoinj.crypto.HDKeyDerivation; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.RoundingMode; +import java.util.Arrays; + +import io.github.novacrypto.bip32.ExtendedPublicKey; + +public class EthIdEngine extends CoinEngine { + + private static final String TAG = com.tangem.wallet.eth.EthEngine.class.getSimpleName(); + public EthIdData coinData = null; + String approvalAddress = ""; //TODO + byte[] approvalPubKey = new byte[1]; //TODO + + public EthIdEngine(TangemContext ctx) throws Exception { + super(ctx); + if (ctx.getCoinData() == null) { + coinData = new EthIdData(); + ctx.setCoinData(coinData); + } else if (ctx.getCoinData() instanceof EthData) { + coinData = (EthIdData) ctx.getCoinData(); + } else { + throw new Exception("Invalid type of Blockchain data for " + this.getClass().getSimpleName()); + } + } + + public EthIdEngine() { + super(); + } + + private static int getDecimals() { + return 18; + } + + private int getChainIdNum() { + return ctx.getBlockchain() == Blockchain.Ethereum ? EthTransaction.ChainEnum.Mainnet.getValue() : EthTransaction.ChainEnum.Rinkeby.getValue(); + } + + @Override + public boolean awaitingConfirmation() { + return !coinData.getUnconfirmedTXCount().equals(coinData.getConfirmedTXCount()) || App.pendingTransactionsStorage.hasTransactions(ctx.getCard()); + } + + @Override + public Amount getBalance() { + if (!hasBalanceInfo()) { + return null; + } + return convertToAmount(coinData.getBalanceInInternalUnits()); + } + + @Override + public String getBalanceHTML() { + Amount balance = getBalance(); + if (balance != null) { + return balance.toDescriptionString(getDecimals()); + } else { + return ""; + } + } + + @Override + public String getBalanceCurrency() { + return Blockchain.Ethereum.getCurrency(); + } + + @Override + public boolean isBalanceNotZero() { + if (coinData == null) return false; + if (coinData.getBalanceInInternalUnits() == null) return false; + return coinData.getBalanceInInternalUnits().notZero(); + } + + @Override + public String getFeeCurrency() { + return Blockchain.Ethereum.getCurrency(); + } + + public boolean isNeedCheckNode() { + return false; + } + + + @Override + public CoinData createCoinData() { + return new EthData(); + } + + @Override + public String getUnspentInputsDescription() { + return ""; + } + +// BigDecimal convertToEth(String value) { +// BigInteger m = new BigInteger(value, 10); +// BigDecimal n = new BigDecimal(m); +// BigDecimal d = n.divide(new BigDecimal("1000000000000000000")); +// d = d.setScale(8, RoundingMode.DOWN); +// return d; +// } + + @Override + public boolean validateAddress(String address) { + if (address == null || address.isEmpty()) { + return false; + } + + if (!address.startsWith("0x") && !address.startsWith("0X")) { + return false; + } + + if (address.length() != 42) { + return false; + } + + return true; + } + +// public String getBalanceValue(TangemCard mCard) { +// String dec = coinData.getBalanceInInternalUnits(); +// BigDecimal d = convertToEth(dec); +// String s = d.toString(); +// +// String pattern = "#0.##################"; // If you like 4 zeros +// DecimalFormat myFormatter = new DecimalFormat(pattern); +// String output = myFormatter.format(d); +// return output; +// } + +// public static String getAmountEquivalentDescription(Amount amount, double rateValue) { +// if (amount == null || amount.compareTo(BigDecimal.ZERO) == 0) +// return ""; +// +// if (rateValue > 0) { +// BigDecimal biRate = new BigDecimal(rateValue); +// BigDecimal exchangeCurs = biRate.multiply(amount); +// exchangeCurs = exchangeCurs.setScale(2, RoundingMode.DOWN); +// return "≈ USD  " + exchangeCurs.toString(); +// } else { +// return ""; +// } +// } + +// public static String getAmountEquivalentDescriptionETH(Double amount, float rate) { +// if (amount == 0) +// return ""; +// amount = amount / 100000; +// if (rate > 0) { +// return String.format("≈ USD %.2f", amount * rate); +// } else { +// return ""; +// } +// +// } + + + @Override + public String getBalanceEquivalent() { + Amount balance = getBalance(); + if (balance == null) return ""; + return balance.toEquivalentString(coinData.getRate()); + } + + @Override + public Amount convertToAmount(InternalAmount internalAmount) { + BigDecimal d = internalAmount.divide(new BigDecimal("1000000000000000000"), getDecimals(), RoundingMode.DOWN); + return new Amount(d, ctx.getBlockchain().getCurrency()); + } + + @Override + public Amount convertToAmount(String strAmount, String currency) { + return new Amount(strAmount, currency); + } + + @Override + public InternalAmount convertToInternalAmount(Amount amount) { + return new InternalAmount(amount.multiply(new BigDecimal("1000000000000000000")), "wei"); + } + + @Override + public InternalAmount convertToInternalAmount(byte[] bytes) { + //throw new Exception("Not implemented"); + return null; + } + + + @Override + public byte[] convertToByteArray(InternalAmount amount) throws Exception { + throw new Exception("Not implemented"); + } + + @Override + public boolean hasBalanceInfo() { + return coinData.getBalanceInInternalUnits() != null; + } + + @Override + public Uri getShareWalletUri() { + if (ctx.getCard().getDenomination() != null) { + return Uri.parse("ethereum:" + ctx.getCoinData().getWallet());// + "?value=" + mCard.getDenomination() +"e18"); + } else { + return Uri.parse("ethereum:" + ctx.getCoinData().getWallet()); + } + } + + @Override + public Uri getWalletExplorerUri() { + if (ctx.getBlockchain() == Blockchain.EthereumTestNet) + return Uri.parse("https://rinkeby.etherscan.io/address/" + ctx.getCoinData().getWallet()); + else + return Uri.parse("https://etherscan.io/address/" + ctx.getCoinData().getWallet()); + } + + @Override + public boolean isExtractPossible() { + if (!hasBalanceInfo()) { + ctx.setMessage(R.string.loaded_wallet_error_obtaining_blockchain_data); + } else if (!isBalanceNotZero()) { + ctx.setMessage(R.string.general_wallet_empty); + } else if (awaitingConfirmation()) { + ctx.setMessage(R.string.loaded_wallet_message_wait); + } else { + return true; + } + return false; + } + + @Override + public InputFilter[] getAmountInputFilters() { + return new InputFilter[]{new DecimalDigitsInputFilter(getDecimals())}; + } + + @Override + public boolean checkNewTransactionAmount(Amount amount) { + if (BuildConfig.FLAVOR == Constant.FLAVOR_TANGEM_CARDANO) { + return true; + } + Amount balance = getBalance(); + if (balance == null || amount.compareTo(balance) > 0) { + return false; + } + return true; + } + + @Override + public boolean checkNewTransactionAmountAndFee(Amount amount, Amount fee, Boolean isFeeIncluded) { +// Long fee = null; +// Long amount = null; +// try { +// amount = mCard.internalUnitsFromString(amountValue); +// fee = mCard.internalUnitsFromString(feeValue); +// } catch (Exception e) { +// e.printStackTrace(); +// return false; +// } +// +// if (fee == null || amount == null) +// return false; +// +// if (fee == 0 || amount == 0) +// return false; +// +// +// if (fee < minFeeInInternalUnits) +// return false; + + try { + BigDecimal cardBalance = getBalance(); + + if (isFeeIncluded && (amount.compareTo(cardBalance) > 0 || amount.compareTo(fee) < 0)) + return false; + + if (!isFeeIncluded && amount.add(fee).compareTo(cardBalance) > 0) + return false; + + } catch (NumberFormatException e) { + e.printStackTrace(); + } + + return true; + } + + @Override + public boolean validateBalance(BalanceValidator balanceValidator) { + if (getBalance() == null) { + balanceValidator.setScore(0); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_unknown_balance); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_unverified_balance); + return false; + } + + if (!coinData.getUnconfirmedTXCount().equals(coinData.getConfirmedTXCount())) { + balanceValidator.setScore(0); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_transaction_in_progress); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_wait_for_confirmation); + return false; + } + + if (coinData.isBalanceReceived()) { + balanceValidator.setScore(100); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_balance); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_confirmed_in_blockchain); + if (getBalance().isZero()) { + balanceValidator.setFirstLine(R.string.balance_validator_first_line_empty_wallet); + balanceValidator.setSecondLine(R.string.empty_string); + } + } + + if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && (ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) && getBalance().notZero()) { + balanceValidator.setScore(80); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_offline); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_internet_to_verify_online); + } + + return true; + + } + + @Override + public String evaluateFeeEquivalent(String fee) { + try { + Amount feeValue = new Amount(fee, ctx.getBlockchain().getCurrency()); + return feeValue.toEquivalentString(coinData.getRate()); + } catch (Exception e) { + e.printStackTrace(); + return ""; + } + } + + @Override + public String calculateAddress(byte[] pkUncompressed) { + Keccak256 kec = new Keccak256(); + int lenPk = pkUncompressed.length; + if (lenPk < 2) { + throw new IllegalArgumentException("Uncompress public key length is invalid"); + } + byte[] cleanKey = new byte[lenPk - 1]; + for (int i = 0; i < cleanKey.length; ++i) { + cleanKey[i] = pkUncompressed[i + 1]; + } + byte[] r = kec.digest(cleanKey); + + byte[] address = new byte[20]; + for (int i = 0; i < 20; ++i) { + address[i] = r[i + 12]; + } + + return String.format("0x%s", BTCUtils.toHex(address)); + } + + @Override + public SignTask.TransactionToSign constructTransaction(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) { + + Log.e(TAG, "Construct transaction " + amountValue.toString() + " with fee " + feeValue.toString() + (IncFee ? " including" : " excluding")); + + BigInteger nonceValue = coinData.getConfirmedTXCount(); + byte[] pbKey = ctx.getCard().getWalletPublicKey(); + + BigInteger weiFee = convertToInternalAmount(feeValue).toBigIntegerExact(); + BigInteger weiAmount = convertToInternalAmount(amountValue).toBigIntegerExact(); + + if (IncFee) { + weiAmount = weiAmount.subtract(weiFee); + } + + BigInteger gasPrice = weiFee.divide(BigInteger.valueOf(21000)); + BigInteger gasLimit = BigInteger.valueOf(21000); + Integer chainId = this.getChainIdNum(); + + String to = targetAddress; + + if (to.startsWith("0x") || to.startsWith("0X")) { + to = to.substring(2); + } + + final EthTransaction tx = EthTransaction.create(to, weiAmount, nonceValue, gasPrice, gasLimit, chainId); + + return new SignTask.TransactionToSign() { + @Override + public boolean isSigningMethodSupported(TangemCard.SigningMethod signingMethod) { + return signingMethod == TangemCard.SigningMethod.Sign_Hash; + } + + @Override + public byte[][] getHashesToSign() { + byte[][] hashesForSign = new byte[1][]; + hashesForSign[0] = tx.getRawHash(); + return hashesForSign; + } + + @Override + public byte[] getRawDataToSign() throws Exception { + throw new Exception("Signing of raw transaction not supported for " + this.getClass().getSimpleName()); + } + + @Override + public String getHashAlgToSign() throws Exception { + throw new Exception("Signing of raw transaction not supported for " + this.getClass().getSimpleName()); + } + + @Override + public byte[] getIssuerTransactionSignature(byte[] dataToSignByIssuer) throws Exception { + throw new Exception("Transaction validation by issuer not supported in this version"); + } + + @Override + public byte[] onSignCompleted(byte[] signFromCard) throws Exception { + byte[] for_hash = tx.getRawHash(); + BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, 0, 32)); + BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32, 64)); + s = CryptoUtil.toCanonicalised(s); + + boolean f = ECKey.verify(for_hash, new ECKey.ECDSASignature(r, s), pbKey); + + if (!f) { + Log.e(this.getClass().getSimpleName() + "-CHECK", "sign Failed."); + } + + tx.signature = new ECDSASignatureETH(r, s); + int v = tx.BruteRecoveryID2(tx.signature, for_hash, pbKey); + if (v != 27 && v != 28) { + Log.e(this.getClass().getSimpleName(), "invalid v"); + throw new Exception("Error in " + this.getClass().getSimpleName() + " - invalid v"); + } + tx.signature.v = (byte) v; + Log.e(this.getClass().getSimpleName(), " V: " + String.valueOf(v)); + + byte[] txForSend = tx.getEncoded(); + notifyOnNeedSendTransaction(txForSend); + return txForSend; + } + }; + } + + @Override + public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) { + + final ServerApiBlockcypher serverApiBlockcypher = new ServerApiBlockcypher(); + + ServerApiBlockcypher.ResponseListener blockcypherListener = new ServerApiBlockcypher.ResponseListener() { + @Override + public void onSuccess(String method, BlockcypherResponse blockcypherResponse) { + Log.i(TAG, "onSuccess: " + method); + try { + //TODO: change request logic, 2000 tx max + if (blockcypherResponse.getTxrefs() != null) { + for (BlockcypherTxref txref : blockcypherResponse.getTxrefs()) { + serverApiBlockcypher.requestData(ctx.getBlockchain().getID(), ServerApiBlockcypher.BLOCKCYPHER_TXS, "", txref.getTx_hash()); + } + + if (serverApiBlockcypher.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(!ctx.hasError());//serverApiElectrum.isErrorOccurred(), serverApiElectrum.getError()); + } else { + blockchainRequestsCallbacks.onProgress(); + } + } + } catch (Exception e) { + e.printStackTrace(); + Log.e(TAG, "FAIL BLOCKCYPHER_ADDRESS Exception"); + } + } + + public void onSuccess(String method, BlockcypherFee blockcypherFee) { + Log.e(TAG, "Wrong response type for requestBalanceAndUnspentTransactions"); + } + + @Override + public void onFail(String method, String message) { + Log.i(TAG, "onFail: " + method + " " + message); + } + }; + + ServerApiBlockcypher.TxResponseListener txListener = new ServerApiBlockcypher.TxResponseListener() { + @Override + public void onSuccess(BlockcypherTx response) { + Log.i(TAG, "onSuccess: BlockcypherTx"); + try { + if (response.getAddesses().contains(approvalAddress)) { + coinData.setHasApprovalTx(true); + } + } catch (Exception e) { + Log.e(TAG, "onFail: BlockcypherTx" + response); + } + if (serverApiBlockcypher.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(!ctx.hasError()); + } else { + blockchainRequestsCallbacks.onProgress(); + } + } + + @Override + public void onFail(String message) { + Log.i(TAG, "onFail: BlockcypherTx " + message); + if (serverApiBlockcypher.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(!ctx.hasError());//serverApiElectrum.isErrorOccurred(), serverApiElectrum.getError()); + } else { + blockchainRequestsCallbacks.onProgress(); + } + } + }; + + serverApiBlockcypher.setResponseListener(blockcypherListener); + serverApiBlockcypher.setTxResponseListener(txListener); + + serverApiBlockcypher.requestData(ctx.getBlockchain().getID(), ServerApiBlockcypher.BLOCKCYPHER_ADDRESS, ctx.getCoinData().getWallet(), ""); + requestApprovalAddressNonce(); + } + + @Override + public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) { + blockchainRequestsCallbacks.onComplete(true); + } + + @Override + public void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) { + + String txStr = String.format("0x%s", BTCUtils.toHex(txForSend)); + + ServerApiInfura serverApiInfura = new ServerApiInfura(); + // request requestData eth gasPrice listener + ServerApiInfura.ResponseListener responseListener = new ServerApiInfura.ResponseListener() { + @Override + public void onSuccess(String method, InfuraResponse infuraResponse) { + if (method.equals(ServerApiInfura.INFURA_ETH_SEND_RAW_TRANSACTION)) { + if (infuraResponse.getResult() == null || infuraResponse.getResult().isEmpty()) { + ctx.setError("Rejected by node: " + infuraResponse.getError()); + blockchainRequestsCallbacks.onComplete(false); + } else { + ctx.setError(null); + blockchainRequestsCallbacks.onComplete(true); + } + } + } + + @Override + public void onFail(String method, String message) { + if (method.equals(ServerApiInfura.INFURA_ETH_SEND_RAW_TRANSACTION)) { + ctx.setError(message); + blockchainRequestsCallbacks.onComplete(false); + } + } + }; + + serverApiInfura.setResponseListener(responseListener); + + serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_SEND_RAW_TRANSACTION, 67, coinData.getWallet(), "", txStr); + } + + public int pendingTransactionTimeoutInSeconds() { + return 10; + } + + @Override + public boolean needMultipleLinesForBalance() { + return true; + } + + @Override + public void defineWallet() throws CardProtocol.TangemException { + try { + String wallet = calculateAddress(calculateCKDpub(ctx.getIdHash)); //TODO + ctx.getCoinData().setWallet(wallet); + } catch (Exception e) { + ctx.getCoinData().setWallet("ERROR"); + throw new CardProtocol.TangemException("Can't define wallet address"); + } + + } + + private byte[] calculateCKDpub(byte[] idHash) { + DeterministicKey masterPubKey = HDKeyDerivation.createMasterPubKeyFromBytes(ctx.getCard().getWalletPublicKeyRar(), idHash); + DeterministicKey childPubKey = HDKeyDerivation.deriveChildKey(masterPubKey, new ChildNumber(1, false)); + return childPubKey.getPubKey(); + } + + private EthTransaction constructIdTxForSign(long nonce) { + BigInteger nonceValue = BigInteger.valueOf(nonce); + + BigInteger gasPrice = BigInteger.valueOf(10000000000L); + BigInteger gasLimit = BigInteger.valueOf(21000); + + BigInteger weiAmount = gasPrice.multiply(gasLimit).add(BigInteger.ONE); + + Integer chainId = this.getChainIdNum(); + + String to = coinData.getWallet(); + + if (to.startsWith("0x") || to.startsWith("0X")) { + to = to.substring(2); + } + + return EthTransaction.create(to, weiAmount, nonceValue, gasPrice, gasLimit, chainId); + } + + private boolean checkSignature(byte[] signature, long nonce) throws SignatureDecodeException { + byte[] hashForSign = constructIdTxForSign(nonce).getRawHash(); +// new Secp256k1Context(); + return ECKey.verify(hashForSign, signature, coinData.getCKDpub()); + } + + private void requestApprovalAddressNonce() { + final ServerApiInfura serverApiInfura = new ServerApiInfura(); + + ServerApiInfura.ResponseListener responseListener = new ServerApiInfura.ResponseListener() { + @Override + public void onSuccess(String method, InfuraResponse infuraResponse) { + String pending = infuraResponse.getResult(); + pending = pending.substring(2); +// BigInteger count = new BigInteger(pending, 16); + Long count = Long.valueOf(pending, 16); + coinData.setApprovalAddressNonce(count); + } + @Override + public void onFail(String method, String message) { + Log.e(TAG, "onFail: " + method + " " + message); + } + }; + serverApiInfura.setResponseListener(responseListener); + + serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_GET_PENDING_COUNT, 67, coinData.getWallet(), "", ""); + } +} From 07b30e1f02681e622f8e175ba23dd64bcff5c672 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 20 Jan 2020 12:48:05 +0300 Subject: [PATCH 03/13] Updated on 2026-08-14 --- .../com/tangem/wallet/ethID/EthIdData.java | 39 ++++++++++++++++++- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/tangem/wallet/ethID/EthIdData.java b/app/src/main/java/com/tangem/wallet/ethID/EthIdData.java index a3197b35d0..6cdf9fcf0b 100644 --- a/app/src/main/java/com/tangem/wallet/ethID/EthIdData.java +++ b/app/src/main/java/com/tangem/wallet/ethID/EthIdData.java @@ -1,11 +1,14 @@ package com.tangem.wallet.ethID; +import android.os.Bundle; +import android.util.Log; + import com.tangem.wallet.CoinData; public class EthIdData extends CoinData { private boolean hasApprovalTx = false; - private long approvalAddressNonce; - private byte[] CKDpub; + private Long approvalAddressNonce; + private byte[] CKDpub = null; public boolean isHasApprovalTx() { return hasApprovalTx; @@ -30,4 +33,36 @@ public class EthIdData extends CoinData { public void setCKDpub(byte[] CKDpub) { this.CKDpub = CKDpub; } + + @Override + public void clearInfo() { + super.clearInfo(); + hasApprovalTx = false; + approvalAddressNonce = null; + } + + @Override + public void loadFromBundle(Bundle B) { + super.loadFromBundle(B); + + if (B.containsKey("ApprovalAddressNonce")) { + approvalAddressNonce = B.getLong("ApprovalAddressNonce"); + } + if (B.containsKey("CKDpub")) CKDpub = B.getByteArray("CKDpub"); + hasApprovalTx = B.getBoolean("HasApprovalTx"); + } + + @Override + public void saveToBundle(Bundle B) { + super.saveToBundle(B); + try { + if (approvalAddressNonce != null) B.putLong("ApprovalAddressNonce", approvalAddressNonce); + if (CKDpub != null) B.putByteArray("CKDpub", CKDpub); + B.putBoolean("HasApprovalTx", hasApprovalTx); + + } catch (Exception e) { + Log.e("Can't save to bundle ", e.getMessage()); + } + + } } From a4de1112906dd4554f453899924715d3383666e2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 20 Jan 2020 19:27:46 +0300 Subject: [PATCH 04/13] Updated on 2026-08-14 --- .idea/navEditor.xml | 195 ++++++-- app/build.gradle | 3 + .../main/java/com/tangem/data/Blockchain.java | 1 + .../com/tangem/ui/fragment/MainFragment.kt | 21 +- .../tangem/ui/fragment/id/CameraFragment.kt | 46 ++ .../com/tangem/ui/fragment/id/IdFragment.kt | 465 ++++++++++++++++++ .../ui/fragment/id/IssueNewIdFragment.kt | 253 ++++++++++ .../ui/fragment/id/ValidateIdFragment.kt | 323 ++++++++++++ .../tangem/ui/fragment/id/WriteIdFragment.kt | 285 +++++++++++ .../com/tangem/wallet/CoinEngineFactory.kt | 4 + .../java/com/tangem/wallet/TangemContext.java | 3 + .../com/tangem/wallet/ethID/EthIdEngine.java | 209 ++------ app/src/main/res/drawable/ic_add_a_photo.xml | 10 + app/src/main/res/drawable/ic_flip_camera.xml | 9 + app/src/main/res/drawable/ic_person.xml | 4 + app/src/main/res/drawable/ic_take_picture.xml | 9 + app/src/main/res/layout/fragment_camera.xml | 52 ++ app/src/main/res/layout/fragment_id.xml | 242 +++++++++ .../main/res/layout/fragment_issue_new_id.xml | 144 ++++++ .../main/res/layout/fragment_validate_id.xml | 51 ++ app/src/main/res/layout/fragment_write_id.xml | 61 +++ app/src/main/res/layout/layout_id.xml | 59 +++ app/src/main/res/layout/layout_touch_card.xml | 1 + app/src/main/res/navigation/main_graph.xml | 41 ++ app/src/main/res/values/strings.xml | 4 + .../tangem/tangem_card/data/ProductMask.kt | 12 + .../tangem/tangem_card/data/TangemCard.java | 75 ++- .../tangem_card/reader/CardProtocol.java | 8 +- .../tangem/tangem_card/reader/TLVList.java | 3 +- .../tangem_sdk/data/TangemCardExtensions.kt | 6 + .../src/main/res/drawable/id_card_default.xml | 6 + 31 files changed, 2365 insertions(+), 240 deletions(-) create mode 100644 app/src/main/java/com/tangem/ui/fragment/id/CameraFragment.kt create mode 100644 app/src/main/java/com/tangem/ui/fragment/id/IdFragment.kt create mode 100644 app/src/main/java/com/tangem/ui/fragment/id/IssueNewIdFragment.kt create mode 100644 app/src/main/java/com/tangem/ui/fragment/id/ValidateIdFragment.kt create mode 100644 app/src/main/java/com/tangem/ui/fragment/id/WriteIdFragment.kt create mode 100644 app/src/main/res/drawable/ic_add_a_photo.xml create mode 100644 app/src/main/res/drawable/ic_flip_camera.xml create mode 100644 app/src/main/res/drawable/ic_person.xml create mode 100644 app/src/main/res/drawable/ic_take_picture.xml create mode 100644 app/src/main/res/layout/fragment_camera.xml create mode 100644 app/src/main/res/layout/fragment_id.xml create mode 100644 app/src/main/res/layout/fragment_issue_new_id.xml create mode 100644 app/src/main/res/layout/fragment_validate_id.xml create mode 100644 app/src/main/res/layout/fragment_write_id.xml create mode 100644 app/src/main/res/layout/layout_id.xml create mode 100644 tangem-card-old/src/main/java/com/tangem/tangem_card/data/ProductMask.kt create mode 100644 tangem-sdk-old/src/main/res/drawable/id_card_default.xml diff --git a/.idea/navEditor.xml b/.idea/navEditor.xml index 4548c6069e..09d3bc259e 100644 --- a/.idea/navEditor.xml +++ b/.idea/navEditor.xml @@ -8,13 +8,25 @@ @@ -200,8 +274,8 @@ @@ -212,8 +286,8 @@ @@ -224,8 +298,8 @@ @@ -236,8 +310,8 @@