diff --git a/class/Ntag424.js b/class/Ntag424.js new file mode 100644 index 0000000000..96e8d432be --- /dev/null +++ b/class/Ntag424.js @@ -0,0 +1,907 @@ +import {Platform} from 'react-native'; +import NfcManager, {NfcTech, Ndef} from 'react-native-nfc-manager'; +import {randomBytes} from 'crypto'; +import crc from 'crc'; +import errorCodes, {isoSelectErrorCodes, changeKeyErrorCodes, changeFileSettingsErrorCodes} from '../constants/ErrorCodes'; + +var CryptoJS = require('../utils/Cmac'); +var AES = require('crypto-js/aes'); + +var Ntag424 = NfcManager; +Ntag424.ti = null; +Ntag424.sesAuthEncKey = null; +Ntag424.sesAuthMacKey = null; +Ntag424.cmdCtrDec = null; +Ntag424.util = {}; + +const hexToBytes = Ntag424.util.hexToBytes = (hex) => { + let bytes = []; + for (let c = 0; c < hex.length; c += 2) + bytes.push(parseInt(hex.substr(c, 2), 16)); + return bytes; +} + +// Convert a byte array to a hex string +const bytesToHex = Ntag424.util.bytesToHex = (bytes) => { + let hex = []; + for (let i = 0; i < bytes.length; i++) { + let current = bytes[i] < 0 ? bytes[i] + 256 : bytes[i]; + hex.push((current >>> 4).toString(16)); + hex.push((current & 0xf).toString(16)); + } + return hex.join(''); +} + +function leftRotate(bytesArr, rotatebit = 1) { + let first = bytesArr.shift(); + bytesArr.push(first); + return bytesArr; +} + +//Encrypted IV +function ivEncryption(ti, cmdCtr, sesAuthEncKey) { + const ivData = AES.encrypt( + CryptoJS.enc.Hex.parse('A55A' + ti + cmdCtr + '0000000000000000'), + CryptoJS.enc.Hex.parse(sesAuthEncKey), + { + mode: CryptoJS.mode.ECB, + // iv: CryptoJS.enc.Hex.parse("00000000000000000000000000000000"), + keySize: 128 / 8, + padding: CryptoJS.pad.NoPadding, + }, + ); + return ivData.ciphertext.toString(CryptoJS.enc.Hex); +} + +function ivEncryptionResponse(ti, cmdCtr, sesAuthEncKey) { + const ivData = AES.encrypt( + CryptoJS.enc.Hex.parse('5AA5' + ti + cmdCtr + '0000000000000000'), + CryptoJS.enc.Hex.parse(sesAuthEncKey), + { + mode: CryptoJS.mode.ECB, + // iv: CryptoJS.enc.Hex.parse("00000000000000000000000000000000"), + keySize: 128 / 8, + padding: CryptoJS.pad.NoPadding, + }, + ); + return ivData.ciphertext.toString(CryptoJS.enc.Hex); +} + +function padForEnc(data, byteLen) { + console.log('padforenc', data, data.length, byteLen); + var paddedData = data; + if (data.length < byteLen * 2) { + console.log('padforEnc22', byteLen * 2); + paddedData += '80'; + paddedData = paddedData.padEnd(byteLen * 2, '00'); + } + return paddedData; +} +/** + * Decimal to Hex Least sig bytes first + * @param {int} dec decimal value + * @param {int} bytes how many bytes you want the hex to be + * @returns + */ +function decToHexLsbFirst(dec, bytes) { + //lsb first + return dec + .toString(16) + .padStart(2, '0') + .padEnd(bytes * 2, '0'); +} + +/** + * Sends the ADPU command using appropriate function for ios / android + * creates the same return object for each platform + * + * @param {byte[]} commandBytes + * @returns {response, sw1, sw2} + */ +Ntag424.sendAPDUCommand = async function (commandBytes) { + const response = + Platform.OS == 'ios' + ? await NfcManager.sendCommandAPDUIOS(commandBytes) + : await NfcManager.transceive(commandBytes); + var newResponse = response; + if (Platform.OS == 'android') { + newResponse = {}; + newResponse.response = response.slice(0, -2); + newResponse.sw1 = response.slice(-2, -1); + newResponse.sw2 = response.slice(-1); + } + return newResponse; +}; + +/** + * Selects the application file + * @returns + */ +Ntag424.isoSelectFileApplication = async function () { + //For selecting the application immediately, the ISO/IEC 7816-4 DF name D2760000850101h can be used. + const isoSelectFileBytes = hexToBytes('00A4040007D276000085010100'); + const isoSelectRes = await Ntag424.sendAPDUCommand(isoSelectFileBytes); + console.log( + 'isoSelectRes: ', + bytesToHex([isoSelectRes.sw1, isoSelectRes.sw2]), + ); + const resultHex = bytesToHex([isoSelectRes.sw1, isoSelectRes.sw2]); + if(resultHex == '9000') { + return Promise.resolve(resultHex); + } else { + return Promise.reject('ISO Select File Failed, code ' +resultHex + ' ' + isoSelectErrorCodes[resultHex] ); + } +} + +/** + * AuthEv2First + * COMMMODE N/A + * @param {string} keyNo key number in hex (1 byte) + * @param {string} pKey key value in hex (16 bytes) + * @returns + * + * CommMode N/A + */ +Ntag424.AuthEv2First = async function (keyNo, pKey) { + //iso select file before auth + try { + + await Ntag424.isoSelectFileApplication(); + + const bytes = hexToBytes('9071000005' + keyNo + '0300000000'); + const Result = await Ntag424.sendAPDUCommand(bytes); + console.warn('Result: ', bytesToHex([Result.sw1, Result.sw2])); + const resultData = bytesToHex(Result.response); + //91AF is the successful code + const resultCode = bytesToHex([Result.sw1, Result.sw2]); + if (resultCode == '91af') { + const key = CryptoJS.enc.Hex.parse(pKey); + const iv = CryptoJS.enc.Hex.parse('00000000000000000000000000000000'); + const aesEncryptOption = { + padding: CryptoJS.pad.NoPadding, + mode: CryptoJS.mode.CBC, + iv: iv, + keySize: 128 / 8, + }; + const RndBDec = AES.decrypt( + {ciphertext: CryptoJS.enc.Hex.parse(resultData)}, + key, + aesEncryptOption, + ); + const RndB = CryptoJS.enc.Hex.stringify(RndBDec); + const RndABytes = randomBytes(16); + const RndA = bytesToHex(RndABytes); + const RndBRotlBytes = leftRotate(hexToBytes(RndB)); + const RndBRotl = bytesToHex(RndBRotlBytes); + + const RndARndBRotl = RndA + RndBRotl; + const RndARndBEncData = AES.encrypt( + CryptoJS.enc.Hex.parse(RndARndBRotl), + key, + aesEncryptOption, + ); + const RndARndBEnc = RndARndBEncData.ciphertext.toString(CryptoJS.enc.Hex); + + const secondAuthBytes = hexToBytes('90AF000020' + RndARndBEnc + '00'); + const secondAuthRes = await Ntag424.sendAPDUCommand(secondAuthBytes); + console.warn( + 'Result: ', + bytesToHex([secondAuthRes.sw1, secondAuthRes.sw2]), + ); + //9100 is the successful code + const secondAuthResultCode = bytesToHex([ + secondAuthRes.sw1, + secondAuthRes.sw2, + ]); + if (secondAuthResultCode == '9100') { + //auth successful + const secondAuthResultData = bytesToHex(secondAuthRes.response); + const secondAuthResultDataDec = AES.decrypt( + {ciphertext: CryptoJS.enc.Hex.parse(secondAuthResultData)}, + key, + aesEncryptOption, + ); + const secondAuthResultDataDecStr = CryptoJS.enc.Hex.stringify( + secondAuthResultDataDec, + ); + + const tiBytes = hexToBytes(secondAuthResultDataDecStr).slice(0, 4); + const ti = bytesToHex(tiBytes); + + var WordArray = CryptoJS.lib.WordArray; + const xor = CryptoJS.ext.xor( + new WordArray.init(hexToBytes(RndA.slice(4, 16))), + new WordArray.init(hexToBytes(RndB.slice(0, 12))), + ); + let svPost = RndA.slice(0, 4); + svPost += bytesToHex(xor.words); + svPost += RndB.slice(12, 32) + RndA.slice(16, 32); + //SV1 = A5h||5Ah||00h||01h||00h||80h||RndA[15..14]|| ( RndA[13..8] # RndB[15..10])||RndB[9..0]||RndA[7..0] + let sv1 = 'A55A00010080'; + sv1 += svPost; + const sesAuthEnc = CryptoJS.CMAC(key, CryptoJS.enc.Hex.parse(sv1)); + const sesAuthEncKey = sesAuthEnc.toString(); + + //SV2 = 5Ah||A5h||00h||01h||00h||80h||RndA[15..14]|| ( RndA[13..8] # RndB[15..10])||RndB[9..0]||RndA[7..0] + //# == XOR-operator + + let sv2 = '5AA500010080'; + sv2 += svPost; + const sesAuthMac = CryptoJS.CMAC(key, CryptoJS.enc.Hex.parse(sv2)); + const sesAuthMacKey = sesAuthMac.toString(); + Ntag424.ti = ti; + Ntag424.sesAuthMacKey = sesAuthMacKey; + Ntag424.sesAuthEncKey = sesAuthEncKey; + Ntag424.cmdCtrDec = 0; + return Promise.resolve({sesAuthEncKey, sesAuthMacKey, ti}); + } else { + //auth failed + return Promise.reject('Auth Failed, code ' +secondAuthResultCode + ' ' + errorCodes[secondAuthResultCode] ); + } + } else { + //auth failed + return Promise.reject('Auth Failed, code ' +resultCode + ' ' + errorCodes[resultCode] ); + } + } catch (ex) { + return Promise.reject(ex); + } +}; + +/** + * AuthEv2NonFirst + * CommMode N/A + * @param {string} keyNo key number in hex (1 byte) + * @param {string} pKey key value in hex (16 bytes) + * @returns + */ +Ntag424.AuthEv2NonFirst = async (keyNo, pKey) => { + const bytes = hexToBytes('9077000001' + keyNo + '00'); + const Result = await Ntag424.sendAPDUCommand(bytes); + console.warn( + 'auth ev2 non first part 1 Result: ', + bytesToHex([Result.sw1, Result.sw2]), + ); + const resultData = bytesToHex(Result.response); + //91AF is the successful code + const resultCode = bytesToHex([Result.sw1, Result.sw2]); + if (resultCode == '91af') { + const key = CryptoJS.enc.Hex.parse(pKey); + const iv = CryptoJS.enc.Hex.parse('00000000000000000000000000000000'); + const aesEncryptOption = { + padding: CryptoJS.pad.NoPadding, + mode: CryptoJS.mode.CBC, + iv: iv, + keySize: 128 / 8, + }; + const RndBDec = AES.decrypt( + {ciphertext: CryptoJS.enc.Hex.parse(resultData)}, + key, + aesEncryptOption, + ); + const RndB = CryptoJS.enc.Hex.stringify(RndBDec); + const RndABytes = randomBytes(16); + const RndA = bytesToHex(RndABytes); + const RndBRotlBytes = leftRotate(hexToBytes(RndB)); + const RndBRotl = bytesToHex(RndBRotlBytes); + + const RndARndBRotl = RndA + RndBRotl; + const RndARndBEncData = AES.encrypt( + CryptoJS.enc.Hex.parse(RndARndBRotl), + key, + aesEncryptOption, + ); + const RndARndBEnc = RndARndBEncData.ciphertext.toString(CryptoJS.enc.Hex); + + const secondAuthBytes = hexToBytes('90AF000020' + RndARndBEnc + '00'); + const secondAuthRes = await Ntag424.sendAPDUCommand(secondAuthBytes); + console.warn( + 'auth ev2 non first part 2 Result: ', + bytesToHex([secondAuthRes.sw1, secondAuthRes.sw2]), + ); + //9100 is the successful code + const secondAuthResultCode = bytesToHex([ + secondAuthRes.sw1, + secondAuthRes.sw2, + ]); + if (secondAuthResultCode == '9100') { + //auth successful + return Promise.resolve('Successful'); + } else { + //auth failed + return Promise.reject('Auth Failed, code ' +secondAuthResultCode + ' ' + errorCodes[secondAuthResultCode] ); + } + } else { + //auth failed + return Promise.reject('Auth Failed, code ' +resultCode + ' ' + errorCodes[resultCode] ); + } +}; + +/** + * MACs the data and returns as a hex string + * + * @param {byte[]} commandData data to MAC + * @returns + */ +Ntag424.calcMac = function (commandData) { + + const commandMac = CryptoJS.CMAC( + CryptoJS.enc.Hex.parse(Ntag424.sesAuthMacKey), + CryptoJS.enc.Hex.parse(commandData), + ); + const commandMacHex = commandMac.toString(); + + const truncatedMacBytes = hexToBytes(commandMacHex).filter(function ( + element, + index, + array, + ) { + return (index + 1) % 2 === 0; + }); + return bytesToHex(truncatedMacBytes); +} + +/** + * Encrypts the data for CommMode.FULL + * @param {string} cmdDataPadd Hex string of command data padded. + * @param {byte[]} cmdCtr + * @returns + */ +Ntag424.encData = function (cmdDataPadd, cmdCtr) { + const iv = ivEncryption(Ntag424.ti, cmdCtr, Ntag424.sesAuthEncKey); + const aesEncryptOption = { + mode: CryptoJS.mode.CBC, + iv: CryptoJS.enc.Hex.parse(iv), + keySize: 128 / 8, + padding: CryptoJS.pad.NoPadding, + }; + + return AES.encrypt( + CryptoJS.enc.Hex.parse(cmdDataPadd), + CryptoJS.enc.Hex.parse(Ntag424.sesAuthEncKey), + aesEncryptOption, + ).ciphertext.toString(CryptoJS.enc.Hex); +} +/** + * Sets standard bolt card file settings using the picc offset and mac offset + * CommMode Full + * + * @param {int} piccOffset picc offset + * @param {int} macOffset mac offset + * @returns + */ +Ntag424.setBoltCardFileSettings = ( + piccOffset, + macOffset, +) => { + //File Option SDM and mirroring enabled, CommMode: plain + var cmdData = '40'; + //Access rights (FileAR.ReadWrite: 0x0, FileAR.Change: 0x0, FileAR.Read: 0xE, FileAR.Write; 0x0) + cmdData += '00E0'; + //UID mirror: 1 + //SDMReadCtr: 1 + //SDMReadCtrLimit: 0 + //SDMENCFileData: 0 + //ASCII Encoding mode: 1 + cmdData += 'C1'; + //sdm access rights + //RFU: 0F + //CtrRet: 0F + //MetaRead: 01 + //FileRead: 02 + cmdData += 'FF12'; + //ENCPICCDataOffset + cmdData += piccOffset.toString(16).padEnd(6, '0'); + //SDMMACOffset + cmdData += macOffset.toString(16).padEnd(6, '0'); + //SDMMACInputOffset + cmdData += macOffset.toString(16).padEnd(6, '0'); + return Ntag424.changeFileSettings(cmdData); +} + +/** + * Clears the file settings back to default + * + * @returns + */ +Ntag424.resetFileSettings = () => { + //File Option SDM and mirroring enabled, CommMode: plain + var cmdData = '40'; + //Access rights (FileAR.ReadWrite: 0xE, FileAR.Change: 0x0, FileAR.Read: 0xE, FileAR.Write; 0xE) + cmdData += 'E0EE'; + + //UID mirror: 0 + // SDMReadCtr: 0 + // SDMReadCtrLimit: 0 + // SDMENCFileData: 0 + // ASCII Encoding mode: 1 + cmdData += '01'; + //sdm access rights + //RFU: 0F + //CtrRet: 0F + //MetaRead: 0F + //FileRead: 0F + cmdData += 'FFFF'; + //no picc offset and mac offset + return Ntag424.changeFileSettings(cmdData); +} +/** + * Change File Settings + * CommMode Full + * + * @param {int} piccOffset picc offset + * @param {int} macOffset mac offset + * @returns + */ +Ntag424.changeFileSettings = async (cmdData) => { + const cmdHeader = '905F0000'; + + const fileNo = '02'; + + const cmdDataPadd = padForEnc(cmdData, 16); + + const cmdCtr = decToHexLsbFirst(Ntag424.cmdCtrDec++, 2); + + const encKeyData = Ntag424.encData(cmdDataPadd, cmdCtr); + + const commandData = '5F' + cmdCtr + Ntag424.ti + fileNo + encKeyData; + + const truncatedMac = Ntag424.calcMac(commandData) + + const data = encKeyData + truncatedMac; + const lc = (data.length / 2 + 1).toString(16); + const changeFileSettingsHex = cmdHeader + lc + fileNo + encKeyData + truncatedMac + '00'; + + const changeFileSettingsRes = await Ntag424.sendAPDUCommand( + hexToBytes(changeFileSettingsHex), + ); + const resCode = bytesToHex([ + changeFileSettingsRes.sw1, + changeFileSettingsRes.sw2, + ]); + console.warn('changeFileSettingsRes Result: ', resCode); + if (resCode == '9100') { + return Promise.resolve('Successful'); + } else { + + + return Promise.reject('Change file settings Failed, code ' +resCode + ' ' + changeFileSettingsErrorCodes[resCode] ); + } +}; + + +/** + * Change Key + * CommMode full + * + * @param {string} sesAuthEncKey hex string (16 bytes) + * @param {string} sesAuthMacKey hex string (16 bytes) + * @param {string} ti hex string ( 4bytes) + * @param {string} keyNo key number in hex (1 byte) + * @param {string} key old key value in hex (16 bytes) + * @param {string} newKey new key value in hex (16 bytes) + * @param {string} keyVersion new key version in hex (1 byte) + * @returns + */ +Ntag424.changeKey = async ( + keyNo, + key, + newKey, + keyVersion, +) => { + const cmdCtr = decToHexLsbFirst(Ntag424.cmdCtrDec++, 2); + console.log('cmdCtr', cmdCtr); + + var keyData = ''; + const newKeyBytes = hexToBytes(newKey); + if (keyNo == '00') { + //if key 0 is to be changed + //keyData = NewKey || KeyVer 17 byte + // 0000000000000000000000000000 + // 0000000000000000000000000000 + keyData = padForEnc(newKey + keyVersion, 32); //32 byte + } else { + //if key 1 to 4 are to be changed + //keyData = (NewKey XOR OldKey) || KeyVer || CRC32NK + // crc32 + var WordArray = CryptoJS.lib.WordArray; + + const oldNewXorBytes = CryptoJS.ext.xor( + new WordArray.init(hexToBytes(key)), + new WordArray.init(newKeyBytes), + ).words; + const oldNewXor = bytesToHex(oldNewXorBytes); + const crc32Reversed = (crc.crcjam(newKeyBytes).toString(16)).padStart(8, "0"); + const crc32 = bytesToHex(hexToBytes(crc32Reversed).reverse()); + keyData = padForEnc(oldNewXor + keyVersion + crc32, 32); //32 bytes + } + + const encKeyData = Ntag424.encData(keyData, cmdCtr); + + const truncatedMac = Ntag424.calcMac('C4' + cmdCtr + Ntag424.ti + keyNo + encKeyData) + + + const data = encKeyData + truncatedMac; + const lc = (data.length / 2 + 1).toString(16); + const changeKeyHex = + '90C40000' + lc + keyNo + encKeyData + truncatedMac + '00'; + + const changeKeyRes = await Ntag424.sendAPDUCommand(hexToBytes(changeKeyHex)); + + const resCode = bytesToHex([changeKeyRes.sw1, changeKeyRes.sw2]); + console.warn('changeKeyRes Result: ', resCode); + if (resCode == '9100') { + return Promise.resolve('Successful'); + } else { + + return Promise.reject('Change Key Failed, code ' +resCode + ' ' + changeKeyErrorCodes[resCode] ); + } +}; + +/** + * Get Card UID + * CommMode Full + * + * @returns + */ +Ntag424.getCardUid = async () => { + var cmdCtr = decToHexLsbFirst(Ntag424.cmdCtrDec++, 2); + const commandMac = CryptoJS.CMAC( + CryptoJS.enc.Hex.parse(Ntag424.sesAuthMacKey), + CryptoJS.enc.Hex.parse('51' + cmdCtr + Ntag424.ti), + ); + const commandMacHex = commandMac.toString(); + + const truncatedMacBytes = hexToBytes(commandMacHex).filter(function ( + element, + index, + array, + ) { + return (index + 1) % 2 === 0; + }); + const truncatedMac = bytesToHex(truncatedMacBytes); + + const getCardUidBytes = hexToBytes('9051000008' + truncatedMac + '00'); + const getCardUidRes = await Ntag424.sendAPDUCommand(getCardUidBytes); + + const responseAPDU = bytesToHex(getCardUidRes.response); + const resCode = bytesToHex([getCardUidRes.sw1, getCardUidRes.sw2]); + + const resMacT = responseAPDU.slice(-16); + cmdCtr = decToHexLsbFirst(Ntag424.cmdCtrDec, 2); + + const iv = ivEncryptionResponse(Ntag424.ti, cmdCtr, Ntag424.sesAuthEncKey); + + // console.log('test iv ', ivEncryption("2B4D963C014DC36F24F69A50A394F875")) + const resDataEnc = responseAPDU.slice(0, -16); + + const resDataDec = AES.decrypt( + {ciphertext: CryptoJS.enc.Hex.parse(resDataEnc)}, + CryptoJS.enc.Hex.parse(Ntag424.sesAuthEncKey), + { + padding: CryptoJS.pad.NoPadding, + mode: CryptoJS.mode.CBC, + iv: CryptoJS.enc.Hex.parse(iv), + keySize: 128 / 8, + }, + ); + const resData = CryptoJS.enc.Hex.stringify(resDataDec); + const uid = resData.slice(0, 14); + + if (resCode == '9100') { + return Promise.resolve(uid); + } else { + return Promise.reject('Get Card UID Failed, code ' +resCode + ' ' + errorCodes[resCode] ); + } +}; + +/** + * Write NDEF message + * CommMode Plain + * + * @param {[]byte} ndefMessageByte ndef message in byte array (up to 248 byte) + * @returns + */ +Ntag424.setNdefMessage = async (ndefMessageByte) => { + try { + await Ntag424.isoSelectFileApplication(); + + const secondISO = await Ntag424.sendAPDUCommand(hexToBytes('00A4000002E10300')); + console.log( + '2nd isoSelectRes: ', + bytesToHex([secondISO.sw1, secondISO.sw2]), + ); + + const isoSelectFileBytes = hexToBytes('00A4000002E10400'); + const isoSelectRes = await Ntag424.sendAPDUCommand(isoSelectFileBytes); + console.log( + '3rd isoSelectRes: ', + bytesToHex([isoSelectRes.sw1, isoSelectRes.sw2]), + ); + const resultHex = bytesToHex([isoSelectRes.sw1, isoSelectRes.sw2]); + if(resultHex == '9000') { + } else { + return Promise.reject('ISO Select File Failed, code ' +resultHex + ' ' + errorCodes[resultHex] ); + } + + + const ndefMessage = bytesToHex(ndefMessageByte); + const ndefLength = ((ndefMessageByte.length).toString(16)).padStart(4, "0"); + const lc = ndefMessageByte.length + 2; + const lcHex = (lc.toString(16)).padStart(2, "0"); + //ndef message (up to 248 byte including secure messaging) + const isoUpdateBinary = "00D60000"+lcHex+ndefLength+ndefMessage; + console.log('isoUpdateBinaryHex', isoUpdateBinary); + const isoUpdateBinaryRes = await Ntag424.sendAPDUCommand(hexToBytes(isoUpdateBinary)); + const resCode = bytesToHex([isoUpdateBinaryRes.sw1, isoUpdateBinaryRes.sw2]); + console.log( + 'isoUpdateBinaryRes Res: ', + resCode, + ); + if(resCode == "9000") { + return Promise.resolve(resCode); + } else { + return Promise.reject('Set NDEF Message Failed, code ' +resCode + ' ' + errorCodes[resCode] ); + } + } catch(e) { + return Promise.reject('setNdefMessage Failed: ', e); + } +} + +/** + * Read NDEF message + * CommMode Plain + * + * @param {string} offset + * @returns + */ +Ntag424.readData = async (offset) => { + //read the entire StandardData file, starting from the position specified in the offset value. + const length = "000000"; + + const readDataHex = "90AD000007"+"02"+offset+length+"00"; + console.log('readData', readDataHex); + const readDataRes = await Ntag424.sendAPDUCommand(hexToBytes(readDataHex)); + const resData = readDataRes.response; + const resCode = bytesToHex([readDataRes.sw1, readDataRes.sw2]); + console.warn( + 'readData Res: ', + resCode, + resData + ); + if(resCode == "9100") { + return Promise.resolve(resData); + } else { + return Promise.reject('Read Data Failed, code ' +resCode + ' ' + errorCodes[resCode] ); + } +} + +/** + * Read NDEF message + * CommMode Plain + * + * @param {string} offset 1byte hex + * @returns + */ +Ntag424.isoReadBinary = async (offset) => { + await Ntag424.isoSelectFileApplication(); + + const isoSelectFileBytes = hexToBytes('00A4000002E10400'); + const isoSelectRes = await Ntag424.sendAPDUCommand(isoSelectFileBytes); + console.warn( + 'isoSelectRes: ', + bytesToHex([isoSelectRes.sw1, isoSelectRes.sw2]), + ); + const resultHex = bytesToHex([isoSelectRes.sw1, isoSelectRes.sw2]); + if(resultHex == '9000') { + } else { + return Promise.reject('ISO Select File Failed, code ' +resultHex + ' ' + errorCodes[resultHex] ); + } + + const cmdHex = "00B00000"+offset; + const res = await Ntag424.sendAPDUCommand(hexToBytes(cmdHex)); + const resData = res.response; + const resCode = bytesToHex([res.sw1, res.sw2]); + console.warn( + 'isoReadBinary Res: ', + resCode, + resData + ); + if(resCode == "9000") { + return Promise.resolve(resData); + } else { + return Promise.reject('isoReadBinary Failed, code ' +resCode + ' ' + errorCodes[resCode] ); + } +} + +/** + * Get Key Version + * CommMode Plain + * + * @param {string} keyNo key number in hex (1 byte) + * @returns + */ +Ntag424.getKeyVersion = async (keyNo) => { + var cmdHex = "9064000001"+keyNo+"00"; + console.log('getkeyversion hex', cmdHex) + const res = await Ntag424.sendAPDUCommand(hexToBytes(cmdHex)); + const resData = res.response; + const resCode = bytesToHex([res.sw1, res.sw2]); + const keyVersion = bytesToHex(resData); + if(resCode == "9100") { + return Promise.resolve(keyVersion); + } else { + return Promise.reject('Get Key Version Failed, code ' +resCode + ' ' + errorCodes[resCode] ); + } +} + +/** + * The GetVersion command returns manufacturing related data of NTAG 424 DNA (NT4H2421Gx). + * No parameters are required for this command. + * The version data is return over three frames. + * Part1 returns the hardware-related information, + * Part2 returns the software-related information and + * Part3 and last frame returns the production-related information. + * + * CommMode Mac + * + * @param {string} keyNo key number in hex (1 byte) + * @returns + */ +Ntag424.getVersion = async () => { + //first part + + const firstHex = "9060000000"; + const firstRes = await Ntag424.sendAPDUCommand(hexToBytes(firstHex)); + const firstResData = bytesToHex(firstRes.response); + + const gerVersionErrorCodes = new Object(); + gerVersionErrorCodes['91ca'] = 'COMMAND_ABORTED Chained command or multiple pass command ongoing.'; + gerVersionErrorCodes['911e'] = 'INTEGRITY_ERROR Invalid secure messaging MAC (only).'; + gerVersionErrorCodes['917e'] = 'LENGTH_ERROR Command size not allowed.'; + gerVersionErrorCodes['91ee'] = 'MEMORY_ERROR Failure when reading or writing to non-volatile memory.'; + + var resCode = bytesToHex([firstRes.sw1, firstRes.sw2]); + if(resCode == '91af') { + //second part + const secondHex = "90AF000000"; + const secondRes = await Ntag424.sendAPDUCommand(hexToBytes(secondHex)); + const secondResData = bytesToHex(secondRes.response); + resCode = bytesToHex([secondRes.sw1, secondRes.sw2]); + if(resCode == '91af') { + //third part + const thirdHex = "90AF000000"; + const thirdRes = await Ntag424.sendAPDUCommand(hexToBytes(thirdHex)); + const thirdResData = bytesToHex(thirdRes.response); + resCode = bytesToHex([thirdRes.sw1, thirdRes.sw2]); + if(resCode == '9100') { + return Promise.resolve({ + 'VendorID':firstResData.slice(0, 2), + 'HWType': firstResData.slice(2, 4), + 'HWSubType': firstResData.slice(4, 6), + 'HWMajorVersion': firstResData.slice(6, 8), + 'HWMinorVersion': firstResData.slice(8, 10), + 'HWStorageSize': firstResData.slice(10, 12), + 'HWProtocol': firstResData.slice(12, 14), + 'SWType': secondResData.slice(0,2), + 'SWSubType': secondResData.slice(4, 6), + 'SWMajorVersion': secondResData.slice(6, 8), + 'SWMinorVersion': secondResData.slice(8, 10), + 'SWStorageSize': secondResData.slice(10, 12), + 'SWProtocol': secondResData.slice(12, 14), + 'UID': thirdResData.slice(0,14), + 'BatchNo': thirdResData.slice(14, 22), + 'FabKey': thirdResData.slice(22, 24), + 'CWProd': thirdResData.slice(24, 26), + 'YearProd': thirdResData.slice(26, 28), + 'FabKeyID': thirdResData.slice(28, 30) + }) + } + } + } + return Promise.reject('Get Version Failed, code ' +resCode + ' ' + gerVersionErrorCodes[resCode] ); +} + +/** + * SetConfiguration + * + * CommMode Full + * + * @param {string} option Configuration Option. (1 byte) + * @param {string} data Data content depends on option values. (Up to 10 bytes) + * + * @returns + */ +Ntag424.setConfiguration = async (option, configData) => { + const cmdHeader = '905c0000'; + const cmdData = configData; + + const cmdDataPadd = padForEnc(cmdData, 16); + + const cmdCtr = decToHexLsbFirst(Ntag424.cmdCtrDec++, 2); + + const encKeyData = Ntag424.encData(cmdDataPadd, cmdCtr); + + const commandData = '5c' + cmdCtr + Ntag424.ti + option + encKeyData; + + const truncatedMac = Ntag424.calcMac(commandData) + + const data = encKeyData + truncatedMac; + const lc = (data.length / 2 + 1).toString(16); + const apduHex = cmdHeader + lc + option + encKeyData + truncatedMac + '00'; + + const res = await Ntag424.sendAPDUCommand( + hexToBytes(apduHex), + ); + const resCode = bytesToHex([ + res.sw1, + res.sw2, + ]); + console.warn('setConfiguration Result: ', resCode); + if (resCode == '9100') { + return Promise.resolve('Successful'); + } else { + return Promise.reject('SetConfiguration Failed, code ' +resCode + ' ' + changeFileSettingsErrorCodes[resCode] ); + } +} + +/** + * Enabling random uid. This is non-reversable + * + * + * @returns Promise + */ +Ntag424.setPrivateUid = () => { + return Ntag424.setConfiguration("00", "02"); +} + +/** + * Test p and c values in ndef message + * + * + * @param {string} pVal p value + * @param {string} cVal c value + * @param {string} uid uid of the card in hex + * @param {string} piccKey key 1 + * @param {string} macKey key 2 + * @returns + */ +Ntag424.testPAndC = async (pVal, cVal, uid, piccKey, macKey) => { + var result = {'pTest': false, 'cTest': false}; + const decPiccData = AES.decrypt( + {ciphertext: CryptoJS.enc.Hex.parse(pVal)}, + CryptoJS.enc.Hex.parse(piccKey), + { + padding: CryptoJS.pad.NoPadding, + mode: CryptoJS.mode.CBC, + iv: CryptoJS.enc.Hex.parse('00000000000000000000000000000000'), + keySize: 128 / 8, + } + ); + const decryptedPiccData = CryptoJS.enc.Hex.stringify(decPiccData); + if(decryptedPiccData.startsWith("c7"+uid)) result.pTest = true; + + const sdmReadCtr = decryptedPiccData.slice(16, 22); + const sv2 = "3cc300010080" + uid + sdmReadCtr; + const sesSdmFileReadMAC = CryptoJS.CMAC( + CryptoJS.enc.Hex.parse(macKey), + CryptoJS.enc.Hex.parse(sv2), + ); + const sesSdmFileReadMACHex = sesSdmFileReadMAC.toString(); + const sdmMac = CryptoJS.CMAC( + CryptoJS.enc.Hex.parse(sesSdmFileReadMACHex), + ); + const sdmMacHex = sdmMac.toString(); + + const truncatedMacBytes = hexToBytes(sdmMacHex).filter(function ( + element, + index, + array, + ) { + return (index + 1) % 2 === 0; + }); + const truncatedSdmMacHex = bytesToHex(truncatedMacBytes); + if(truncatedSdmMacHex == cVal.toLowerCase()) result.cTest = true; + return Promise.resolve(result); +} + +export default Ntag424; diff --git a/constants/ErrorCodes.js b/constants/ErrorCodes.js new file mode 100644 index 0000000000..73244f782f --- /dev/null +++ b/constants/ErrorCodes.js @@ -0,0 +1,56 @@ +export default errorCodes = { + '6700': 'Wrong or inconsistent APDU length.', + '6985': 'Wrapped chained command or multiple pass command ongoing.', + '6a82': 'Application or file not found, currently selected application remains selected.', + '6a86': 'Wrong parameter P1 and/or P2', + '6a87': 'Wrong parameter Lc inconsistent with P1-P2', + '6e00': 'Wrong CLA', + '6581': 'Memory failure', + '6982': 'Security status not satisfied', + '91ca': 'COMMAND_ABORTED', + '917e': 'LENGTH_ERROR', + '919e': 'PARAMETER_ERROR', + '9140': 'NO_SUCH_KEY', + '919d': 'PERMISSION_DENIED', + '91ad': 'AUTHENTICATION_DELAY', + '911e': 'INTEGRITY_ERROR', + '91f0': 'FILE_NOT_FOUND', + '91ae': 'AUTHENTICATION_ERROR', + '91ee': 'MEMORY_ERROR', + '91be': 'BOUNDARY_ERROR' + }; + + //NOTE: when new APDU command gets added make sure the error codes are added here + + + export const isoSelectErrorCodes = { + '6700' : 'Wrong or inconsistent APDU length.', + '6985' : 'Wrapped chained command or multiple pass command ongoing.', + '6a82' : 'Application or file not found, currently selected application remains selected.', + '6a86' : 'Wrong parameter P1 and/or P2', + '6a87' : 'Wrong parameter Lc inconsistent with P1-P2', + '6e00' : 'Wrong CLA', + }; + + + export const changeKeyErrorCodes = { + '91ca' : 'COMMAND_ABORTED Chained command or multiple pass command ongoing.', + '911e' : 'INTEGRITY_ERROR Integrity error in cryptogram or Invalid Secure Messaging MAC (only).', + '917e' : 'LENGTH_ERROR Command size not allowed.', + '919e' : 'PARAMETER_ERROR Parameter value not allowed', + '919d' : 'PERMISSION_DENIED PICC level (MF) is selected. access right Change of targeted file has access conditions set to Fh. Enabling Secure Dynamic Messaging (FileOption Bit 6 set to 1b) is only allowed for FileNo 02h.', + '91ae' : 'AUTHENTICATION_ERROR At application level, missing active authentication with AppMasterKey while targeting any AppKey.', + '91ee' : 'MEMORY_ERROR Failure when reading or writing to non-volatile memory.', + }; + + + export const changeFileSettingsErrorCodes = { + '91ca' : 'COMMAND_ABORTED chained command or multiple pass command ongoing.', + '911e' : 'INTEGRITY_ERROR Integrity error in cryptogram. Invalid Secure Messaging MAC (only).', + '917e' : 'LENGTH_ERROR Command size not allowed.', + '919e' : 'PARAMETER_ERROR Parameter value not allowed', + '919d' : 'PERMISSION_DENIED PICC level (MF) is selected. access right Change of targeted file has access conditions set to Fh. Enabling Secure Dynamic Messaging (FileOption Bit 6 set to 1b) is only allowed for FileNo 02h.', + '91f0' : 'FILE_NOT_FOUND F0h File with targeted FileNo does not exist for the targeted application. ', + '91ae' : 'AUTHENTICATION_ERROR AEh File access right Change of targeted file not granted as there is no active authentication with the required key while the access conditions is different from Fh.', + '91ee' : 'MEMORY_ERROR EEh Failure when reading or writing to non-volatile memory.', + }; \ No newline at end of file diff --git a/img/bolt-card-link_black.png b/img/bolt-card-link_black.png new file mode 100644 index 0000000000..1111ca991e Binary files /dev/null and b/img/bolt-card-link_black.png differ diff --git a/ios/BoltCardWallet.xcodeproj/project.pbxproj b/ios/BoltCardWallet.xcodeproj/project.pbxproj index add7bebb9f..0e17c4388b 100644 --- a/ios/BoltCardWallet.xcodeproj/project.pbxproj +++ b/ios/BoltCardWallet.xcodeproj/project.pbxproj @@ -1131,7 +1131,7 @@ CODE_SIGN_ENTITLEMENTS = BoltCardWallet/BoltCardWallet.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 6; + CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = G2LFSW78NA; ENABLE_BITCODE = NO; @@ -1154,7 +1154,7 @@ "$(inherited)", "$(PROJECT_DIR)", ); - MARKETING_VERSION = 0.1.1; + MARKETING_VERSION = 0.2.1; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -1185,7 +1185,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 6; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = G2LFSW78NA; ENABLE_BITCODE = NO; "ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = YES; @@ -1202,7 +1202,7 @@ "$(inherited)", "$(PROJECT_DIR)", ); - MARKETING_VERSION = 0.1.1; + MARKETING_VERSION = 0.2.1; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", diff --git a/ios/BoltCardWallet/BoltCardWallet.entitlements b/ios/BoltCardWallet/BoltCardWallet.entitlements index ba2d45858a..a1b55e65ee 100644 --- a/ios/BoltCardWallet/BoltCardWallet.entitlements +++ b/ios/BoltCardWallet/BoltCardWallet.entitlements @@ -4,6 +4,11 @@ aps-environment development + com.apple.developer.nfc.readersession.formats + + TAG + NDEF + com.apple.security.app-sandbox com.apple.security.application-groups diff --git a/ios/BoltCardWallet/BoltCardWalletRelease.entitlements b/ios/BoltCardWallet/BoltCardWalletRelease.entitlements index cbda592bcb..c0ec88b59c 100644 --- a/ios/BoltCardWallet/BoltCardWalletRelease.entitlements +++ b/ios/BoltCardWallet/BoltCardWalletRelease.entitlements @@ -4,6 +4,11 @@ aps-environment development + com.apple.developer.nfc.readersession.formats + + TAG + NDEF + com.apple.security.app-sandbox com.apple.security.application-groups diff --git a/ios/BoltCardWallet/Info.plist b/ios/BoltCardWallet/Info.plist index 7ca91f2294..b6db3d3cdd 100644 --- a/ios/BoltCardWallet/Info.plist +++ b/ios/BoltCardWallet/Info.plist @@ -103,6 +103,8 @@ LSSupportsOpeningDocumentsInPlace + NFCReaderUsageDescription + NFC scanning is used to read/program your boltcard NSAppTransportSecurity NSAllowsArbitraryLoads @@ -316,5 +318,10 @@ apiKey 736dc945917207ed3993518c9110bbd9 + com.apple.developer.nfc.readersession.iso7816.select-identifiers + + D2760000850100 + D2760000850101 + diff --git a/ios/Podfile b/ios/Podfile index 9a491c8e30..459a8d5698 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -11,6 +11,8 @@ target 'BoltCardWallet' do # to enable hermes on iOS, change `false` to `true` and then install pods :hermes_enabled => false ) + + pod 'RNFS', :path => '../node_modules/react-native-fs' # Enables Flipper. # diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 819d9e112b..b45a054619 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -323,6 +323,8 @@ PODS: - React-Core - react-native-ios-context-menu (1.15.3): - React-Core + - react-native-nfc-manager (3.14.5): + - React-Core - react-native-qrcode-local-image (1.0.4): - React - react-native-randombytes (3.6.1): @@ -555,6 +557,7 @@ DEPENDENCIES: - react-native-idle-timer (from `../node_modules/react-native-idle-timer`) - react-native-image-picker (from `../node_modules/react-native-image-picker`) - react-native-ios-context-menu (from `../node_modules/react-native-ios-context-menu`) + - react-native-nfc-manager (from `../node_modules/react-native-nfc-manager`) - "react-native-qrcode-local-image (from `../node_modules/@remobile/react-native-qrcode-local-image`)" - react-native-randombytes (from `../node_modules/react-native-randombytes`) - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) @@ -683,6 +686,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-image-picker" react-native-ios-context-menu: :path: "../node_modules/react-native-ios-context-menu" + react-native-nfc-manager: + :path: "../node_modules/react-native-nfc-manager" react-native-qrcode-local-image: :path: "../node_modules/@remobile/react-native-qrcode-local-image" react-native-randombytes: @@ -827,6 +832,7 @@ SPEC CHECKSUMS: react-native-idle-timer: 97b8283237d45146a7a5c25bdebe9e1e85f3687b react-native-image-picker: 24a36044140202085113ce99b57bf52c62dc339e react-native-ios-context-menu: b522377ce20e859c96d4d508703483f028ae1844 + react-native-nfc-manager: 00700a5cd081bf78e2f33fbf84b8610aef4a832c react-native-qrcode-local-image: 35ccb306e4265bc5545f813e54cc830b5d75bcfc react-native-randombytes: 5fc412efe7b5c55b9002c0004d75fe5fabcaa507 react-native-safe-area-context: 5b8a418400eb3d8364aa87300e865c0262cc17b9 @@ -876,6 +882,6 @@ SPEC CHECKSUMS: Yoga: 5ed1699acbba8863755998a4245daa200ff3817b YogaKit: f782866e155069a2cca2517aafea43200b01fd5a -PODFILE CHECKSUM: 18cca50594797cb5feb4d92634ca0f147993b45b +PODFILE CHECKSUM: a0db116dd5af0f09d150a5ce801116d7fdaf48a6 COCOAPODS: 1.10.2 diff --git a/package-lock.json b/package-lock.json index 9d8d4eeed9..48a063d1c2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "BoltCardWallet", - "version": "0.1.7", + "version": "0.1.8", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "BoltCardWallet", - "version": "0.1.7", + "version": "0.1.8", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -60,7 +60,7 @@ "react-native-blue-crypto": "github:BlueWallet/react-native-blue-crypto#3cb5442", "react-native-camera-kit": "13.0.0", "react-native-config": "^1.5.0", - "react-native-crypto": "2.2.0", + "react-native-crypto": "^2.2.0", "react-native-default-preference": "1.4.4", "react-native-device-info": "8.7.1", "react-native-dialog": "^9.3.0", @@ -79,6 +79,7 @@ "react-native-localize": "2.2.5", "react-native-modal": "13.0.1", "react-native-navigation-bar-color": "https://github.com/BlueWallet/react-native-navigation-bar-color#3b2894ae62fbce99a3bd24105f0921cebaef5c94", + "react-native-nfc-manager": "^3.14.5", "react-native-obscure": "https://github.com/BlueWallet/react-native-obscure.git#f4b83b4a261e39b1f5ed4a45ac5bcabc8a59eadb", "react-native-passcode-auth": "https://github.com/BlueWallet/react-native-passcode-auth#a2ff977ba92b36f8d0a5567f59c05cc608e8bd12", "react-native-privacy-snapshot": "https://github.com/BlueWallet/react-native-privacy-snapshot#529e4627d93f67752a27e82a040ff7b64dca0783", @@ -104,7 +105,7 @@ "readable-stream": "3.6.0", "realm": "11.5.1", "rn-ldk": "github:BlueWallet/rn-ldk#v0.8.4", - "rn-nodeify": "10.3.0", + "rn-nodeify": "^10.3.0", "scryptsy": "2.1.0", "slip39": "https://github.com/BlueWallet/slip39-js", "stream-browserify": "3.0.0", @@ -2182,6 +2183,232 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@expo/config-plugins": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-6.0.2.tgz", + "integrity": "sha512-Cn01fXMHwjU042EgO9oO3Mna0o/UCrW91MQLMbJa4pXM41CYGjNgVy1EVXiuRRx/upegHhvltBw5D+JaUm8aZQ==", + "dependencies": { + "@expo/config-types": "^48.0.0", + "@expo/json-file": "~8.2.37", + "@expo/plist": "^0.0.20", + "@expo/sdk-runtime-versions": "^1.0.0", + "@react-native/normalize-color": "^2.0.0", + "chalk": "^4.1.2", + "debug": "^4.3.1", + "find-up": "~5.0.0", + "getenv": "^1.0.0", + "glob": "7.1.6", + "resolve-from": "^5.0.0", + "semver": "^7.3.5", + "slash": "^3.0.0", + "xcode": "^3.0.1", + "xml2js": "0.4.23" + } + }, + "node_modules/@expo/config-plugins/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@expo/config-plugins/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@expo/config-plugins/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@expo/config-plugins/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/@expo/config-plugins/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@expo/config-plugins/node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@expo/config-plugins/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@expo/config-plugins/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@expo/config-plugins/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@expo/config-plugins/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@expo/config-plugins/node_modules/semver": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@expo/config-plugins/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@expo/config-plugins/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/@expo/config-types": { + "version": "48.0.0", + "resolved": "https://registry.npmjs.org/@expo/config-types/-/config-types-48.0.0.tgz", + "integrity": "sha512-DwyV4jTy/+cLzXGAo1xftS6mVlSiLIWZjl9DjTCLPFVgNYQxnh7htPilRv4rBhiNs7KaznWqKU70+4zQoKVT9A==" + }, + "node_modules/@expo/json-file": { + "version": "8.2.37", + "resolved": "https://registry.npmjs.org/@expo/json-file/-/json-file-8.2.37.tgz", + "integrity": "sha512-YaH6rVg11JoTS2P6LsW7ybS2CULjf40AbnAHw2F1eDPuheprNjARZMnyHFPkKv7GuxCy+B9GPcbOKgc4cgA80Q==", + "dependencies": { + "@babel/code-frame": "~7.10.4", + "json5": "^2.2.2", + "write-file-atomic": "^2.3.0" + } + }, + "node_modules/@expo/json-file/node_modules/@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dependencies": { + "@babel/highlight": "^7.10.4" + } + }, + "node_modules/@expo/json-file/node_modules/write-file-atomic": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", + "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", + "dependencies": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "node_modules/@expo/plist": { + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/@expo/plist/-/plist-0.0.20.tgz", + "integrity": "sha512-UXQ4LXCfTZ580LDHGJ5q62jSTwJFFJ1GqBu8duQMThiHKWbMJ+gajJh6rsB6EJ3aLUr9wcauxneL5LVRFxwBEA==", + "dependencies": { + "@xmldom/xmldom": "~0.7.7", + "base64-js": "^1.2.3", + "xmlbuilder": "^14.0.0" + } + }, + "node_modules/@expo/sdk-runtime-versions": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@expo/sdk-runtime-versions/-/sdk-runtime-versions-1.0.0.tgz", + "integrity": "sha512-Doz2bfiPndXYFPMRwPyGa1k5QaKDVpY806UJj570epIiMzWaYyCtobasyfC++qfIXVb5Ocy7r3tP9d62hAQ7IQ==" + }, "node_modules/@hapi/hoek": { "version": "9.3.0", "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" @@ -7822,6 +8049,14 @@ "version": "2.0.0", "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==" }, + "node_modules/big-integer": { + "version": "1.6.51", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", + "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==", + "engines": { + "node": ">=0.6" + } + }, "node_modules/bigi": { "version": "1.4.2", "integrity": "sha512-ddkU+dFIuEIW8lE7ZwdIAf2UPoM90eaprg5m3YXAVVTmKlqV/9BX4A2M8BOK2yOq6/VgZFVhK6QAxJebhlbhzw==" @@ -8051,6 +8286,25 @@ "version": "3.2.0", "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==" }, + "node_modules/bplist-creator": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.1.0.tgz", + "integrity": "sha512-sXaHZicyEEmY86WyueLTQesbeoH/mquvarJaQNbjuOQO+7gbFcDEWqKmcWA4cOTLzFlfgvkiVxolk1k5bBIpmg==", + "dependencies": { + "stream-buffers": "2.2.x" + } + }, + "node_modules/bplist-parser": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.3.1.tgz", + "integrity": "sha512-PyJxiNtA5T2PlLIeBot4lbp7rj4OadzjnMZD/G5zuBNt8ei/yCU7+wW0h2bag9vr8c+/WuRWmSxbqAl9hL1rBA==", + "dependencies": { + "big-integer": "1.6.x" + }, + "engines": { + "node": ">= 5.10.0" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", @@ -11904,6 +12158,14 @@ "node": ">=0.10.0" } }, + "node_modules/getenv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/getenv/-/getenv-1.0.0.tgz", + "integrity": "sha512-7yetJWqbS9sbn0vIfliPsFgoXMKn/YMF+Wuiog97x+urnSRRRZ7xB+uVkwGKzRgq9CDFfMQnE9ruL5DHv9c6Xg==", + "engines": { + "node": ">=6" + } + }, "node_modules/getpass": { "version": "0.1.7", "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", @@ -19428,6 +19690,35 @@ "node": ">=8" } }, + "node_modules/plist": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz", + "integrity": "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==", + "dependencies": { + "@xmldom/xmldom": "^0.8.8", + "base64-js": "^1.5.1", + "xmlbuilder": "^15.1.1" + }, + "engines": { + "node": ">=10.4.0" + } + }, + "node_modules/plist/node_modules/@xmldom/xmldom": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.8.tgz", + "integrity": "sha512-0LNz4EY8B/8xXY86wMrQ4tz6zEHZv9ehFMJPm8u2gq5lQ71cfRKdaKyxfJAx5aUoyzx0qzgURblTisPGgz3d+Q==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/plist/node_modules/xmlbuilder": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", + "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", + "engines": { + "node": ">=8.0" + } + }, "node_modules/pngjs": { "version": "5.0.0", "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", @@ -20028,6 +20319,7 @@ }, "node_modules/react-native-crypto": { "version": "2.2.0", + "resolved": "https://registry.npmjs.org/react-native-crypto/-/react-native-crypto-2.2.0.tgz", "integrity": "sha512-eZu9Y8pa8BN9FU2pIex7MLRAi+Cd1Y6bsxfiufKh7sfraAACJvjQTeW7/zcQAT93WMfM+D0OVk+bubvkrbrUkw==", "dependencies": { "browserify-cipher": "^1.0.0", @@ -20265,6 +20557,14 @@ "integrity": "sha512-7UeNSU7FTdgyDzN/SX1fAa9D6FjvKmj4+HjIxyl75Tb1Zex3RTTjBsfAGiG4z/RIIH3Glmw3qQcAYLVAO58E0Q==", "license": "MIT" }, + "node_modules/react-native-nfc-manager": { + "version": "3.14.5", + "resolved": "https://registry.npmjs.org/react-native-nfc-manager/-/react-native-nfc-manager-3.14.5.tgz", + "integrity": "sha512-HzWD/AGDFPvZSKkZCHD/PVryjc/HGIZLuZfhNwxJqXqA713EP9b9JVWPXU0xNPEFQuihvNzYg/ZdULs2ERtNDA==", + "dependencies": { + "@expo/config-plugins": "^6.0.1" + } + }, "node_modules/react-native-obscure": { "name": "@talaikis/react-native-obscure", "version": "0.0.3", @@ -22877,6 +23177,7 @@ }, "node_modules/rn-nodeify": { "version": "10.3.0", + "resolved": "https://registry.npmjs.org/rn-nodeify/-/rn-nodeify-10.3.0.tgz", "integrity": "sha512-EZB3M4M5i8yySCWF7AAZ31xU7cpdLuIKMlVxXji9t0aY8Ojy3BAyRt1sTp0OwBgy1ejShmlIu2L4f8mToJ+uvg==", "dependencies": { "@yarnpkg/lockfile": "^1.0.0", @@ -23001,6 +23302,11 @@ "truncate-utf8-bytes": "^1.0.0" } }, + "node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, "node_modules/scheduler": { "version": "0.23.0", "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", @@ -23293,6 +23599,16 @@ "simple-concat": "^1.0.0" } }, + "node_modules/simple-plist": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/simple-plist/-/simple-plist-1.3.1.tgz", + "integrity": "sha512-iMSw5i0XseMnrhtIzRb7XpQEXepa9xhWxGUojHBL43SIpQuDQkh3Wpy67ZbDzZVr6EKxvwVChnVpdl8hEVLDiw==", + "dependencies": { + "bplist-creator": "0.1.0", + "bplist-parser": "0.3.1", + "plist": "^3.0.5" + } + }, "node_modules/simple-swizzle": { "version": "0.2.2", "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", @@ -23748,6 +24064,14 @@ "readable-stream": "^3.5.0" } }, + "node_modules/stream-buffers": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-2.2.0.tgz", + "integrity": "sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg==", + "engines": { + "node": ">= 0.10.0" + } + }, "node_modules/stream-chain": { "version": "2.2.5", "resolved": "https://registry.npmjs.org/stream-chain/-/stream-chain-2.2.5.tgz", @@ -25358,6 +25682,26 @@ } } }, + "node_modules/xcode": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/xcode/-/xcode-3.0.1.tgz", + "integrity": "sha512-kCz5k7J7XbJtjABOvkc5lJmkiDh8VhjVCGNiqdKCscmVpdVUpEAyXv1xmCLkQJ5dsHqx3IPO4XW+NTDhU/fatA==", + "dependencies": { + "simple-plist": "^1.1.0", + "uuid": "^7.0.3" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/xcode/node_modules/uuid": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz", + "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/xml-formatter": { "version": "2.6.1", "integrity": "sha512-dOiGwoqm8y22QdTNI7A+N03tyVfBlQ0/oehAzxIZtwnFAHGeSlrfjF73YQvzSsa/Kt6+YZasKsrdu6OIpuBggw==", @@ -25393,6 +25737,34 @@ "version": "2.0.0", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/xml2js": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xml2js/node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/xmlbuilder": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-14.0.0.tgz", + "integrity": "sha512-ts+B2rSe4fIckR6iquDjsKbQFK2NlUk6iG5nf14mDEyldgoc2nEKZ3jZWMPTxGQwVgToSjt6VGIho1H8/fNFTg==", + "engines": { + "node": ">=8.0" + } + }, "node_modules/xpath": { "version": "0.0.27", "integrity": "sha512-fg03WRxtkCV6ohClePNAECYsmpKKTv5L8y/X3Dn1hQrec3POx2jHZ/0P2qQ6HvsrU1BmeqXcof3NGGueG6LxwQ==", @@ -26810,6 +27182,182 @@ } } }, + "@expo/config-plugins": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-6.0.2.tgz", + "integrity": "sha512-Cn01fXMHwjU042EgO9oO3Mna0o/UCrW91MQLMbJa4pXM41CYGjNgVy1EVXiuRRx/upegHhvltBw5D+JaUm8aZQ==", + "requires": { + "@expo/config-types": "^48.0.0", + "@expo/json-file": "~8.2.37", + "@expo/plist": "^0.0.20", + "@expo/sdk-runtime-versions": "^1.0.0", + "@react-native/normalize-color": "^2.0.0", + "chalk": "^4.1.2", + "debug": "^4.3.1", + "find-up": "~5.0.0", + "getenv": "^1.0.0", + "glob": "7.1.6", + "resolve-from": "^5.0.0", + "semver": "^7.3.5", + "slash": "^3.0.0", + "xcode": "^3.0.1", + "xml2js": "0.4.23" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "requires": { + "p-locate": "^5.0.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "requires": { + "p-limit": "^3.0.2" + } + }, + "semver": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "requires": { + "lru-cache": "^6.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } + }, + "@expo/config-types": { + "version": "48.0.0", + "resolved": "https://registry.npmjs.org/@expo/config-types/-/config-types-48.0.0.tgz", + "integrity": "sha512-DwyV4jTy/+cLzXGAo1xftS6mVlSiLIWZjl9DjTCLPFVgNYQxnh7htPilRv4rBhiNs7KaznWqKU70+4zQoKVT9A==" + }, + "@expo/json-file": { + "version": "8.2.37", + "resolved": "https://registry.npmjs.org/@expo/json-file/-/json-file-8.2.37.tgz", + "integrity": "sha512-YaH6rVg11JoTS2P6LsW7ybS2CULjf40AbnAHw2F1eDPuheprNjARZMnyHFPkKv7GuxCy+B9GPcbOKgc4cgA80Q==", + "requires": { + "@babel/code-frame": "~7.10.4", + "json5": "^2.2.2", + "write-file-atomic": "^2.3.0" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "write-file-atomic": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", + "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + } + } + }, + "@expo/plist": { + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/@expo/plist/-/plist-0.0.20.tgz", + "integrity": "sha512-UXQ4LXCfTZ580LDHGJ5q62jSTwJFFJ1GqBu8duQMThiHKWbMJ+gajJh6rsB6EJ3aLUr9wcauxneL5LVRFxwBEA==", + "requires": { + "@xmldom/xmldom": "~0.7.7", + "base64-js": "^1.2.3", + "xmlbuilder": "^14.0.0" + } + }, + "@expo/sdk-runtime-versions": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@expo/sdk-runtime-versions/-/sdk-runtime-versions-1.0.0.tgz", + "integrity": "sha512-Doz2bfiPndXYFPMRwPyGa1k5QaKDVpY806UJj570epIiMzWaYyCtobasyfC++qfIXVb5Ocy7r3tP9d62hAQ7IQ==" + }, "@hapi/hoek": { "version": "9.3.0", "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" @@ -31080,6 +31628,11 @@ "version": "2.0.0", "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==" }, + "big-integer": { + "version": "1.6.51", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", + "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==" + }, "bigi": { "version": "1.4.2", "integrity": "sha512-ddkU+dFIuEIW8lE7ZwdIAf2UPoM90eaprg5m3YXAVVTmKlqV/9BX4A2M8BOK2yOq6/VgZFVhK6QAxJebhlbhzw==" @@ -31282,6 +31835,22 @@ "version": "3.2.0", "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==" }, + "bplist-creator": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.1.0.tgz", + "integrity": "sha512-sXaHZicyEEmY86WyueLTQesbeoH/mquvarJaQNbjuOQO+7gbFcDEWqKmcWA4cOTLzFlfgvkiVxolk1k5bBIpmg==", + "requires": { + "stream-buffers": "2.2.x" + } + }, + "bplist-parser": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.3.1.tgz", + "integrity": "sha512-PyJxiNtA5T2PlLIeBot4lbp7rj4OadzjnMZD/G5zuBNt8ei/yCU7+wW0h2bag9vr8c+/WuRWmSxbqAl9hL1rBA==", + "requires": { + "big-integer": "1.6.x" + } + }, "brace-expansion": { "version": "1.1.11", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", @@ -34087,6 +34656,11 @@ "version": "2.0.6", "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==" }, + "getenv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/getenv/-/getenv-1.0.0.tgz", + "integrity": "sha512-7yetJWqbS9sbn0vIfliPsFgoXMKn/YMF+Wuiog97x+urnSRRRZ7xB+uVkwGKzRgq9CDFfMQnE9ruL5DHv9c6Xg==" + }, "getpass": { "version": "0.1.7", "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", @@ -39726,6 +40300,28 @@ "find-up": "^4.0.0" } }, + "plist": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz", + "integrity": "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==", + "requires": { + "@xmldom/xmldom": "^0.8.8", + "base64-js": "^1.5.1", + "xmlbuilder": "^15.1.1" + }, + "dependencies": { + "@xmldom/xmldom": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.8.tgz", + "integrity": "sha512-0LNz4EY8B/8xXY86wMrQ4tz6zEHZv9ehFMJPm8u2gq5lQ71cfRKdaKyxfJAx5aUoyzx0qzgURblTisPGgz3d+Q==" + }, + "xmlbuilder": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", + "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==" + } + } + }, "pngjs": { "version": "5.0.0", "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==" @@ -40241,6 +40837,7 @@ }, "react-native-crypto": { "version": "2.2.0", + "resolved": "https://registry.npmjs.org/react-native-crypto/-/react-native-crypto-2.2.0.tgz", "integrity": "sha512-eZu9Y8pa8BN9FU2pIex7MLRAi+Cd1Y6bsxfiufKh7sfraAACJvjQTeW7/zcQAT93WMfM+D0OVk+bubvkrbrUkw==", "requires": { "browserify-cipher": "^1.0.0", @@ -40385,6 +40982,14 @@ "integrity": "sha512-7UeNSU7FTdgyDzN/SX1fAa9D6FjvKmj4+HjIxyl75Tb1Zex3RTTjBsfAGiG4z/RIIH3Glmw3qQcAYLVAO58E0Q==", "from": "react-native-navigation-bar-color@https://github.com/BlueWallet/react-native-navigation-bar-color#3b2894ae62fbce99a3bd24105f0921cebaef5c94" }, + "react-native-nfc-manager": { + "version": "3.14.5", + "resolved": "https://registry.npmjs.org/react-native-nfc-manager/-/react-native-nfc-manager-3.14.5.tgz", + "integrity": "sha512-HzWD/AGDFPvZSKkZCHD/PVryjc/HGIZLuZfhNwxJqXqA713EP9b9JVWPXU0xNPEFQuihvNzYg/ZdULs2ERtNDA==", + "requires": { + "@expo/config-plugins": "^6.0.1" + } + }, "react-native-obscure": { "version": "git+ssh://git@github.com/BlueWallet/react-native-obscure.git#f4b83b4a261e39b1f5ed4a45ac5bcabc8a59eadb", "integrity": "sha512-bmzbnlXII8hW7steqwouzQW9cJ+mdA8HJ8pWkb0KD60jC5dYgJ0e66wgUiSTDNFPrvlEKriE4eEanoJFj7Q9pg==", @@ -42385,6 +42990,7 @@ }, "rn-nodeify": { "version": "10.3.0", + "resolved": "https://registry.npmjs.org/rn-nodeify/-/rn-nodeify-10.3.0.tgz", "integrity": "sha512-EZB3M4M5i8yySCWF7AAZ31xU7cpdLuIKMlVxXji9t0aY8Ojy3BAyRt1sTp0OwBgy1ejShmlIu2L4f8mToJ+uvg==", "requires": { "@yarnpkg/lockfile": "^1.0.0", @@ -42471,6 +43077,11 @@ "truncate-utf8-bytes": "^1.0.0" } }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, "scheduler": { "version": "0.23.0", "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", @@ -42676,6 +43287,16 @@ "simple-concat": "^1.0.0" } }, + "simple-plist": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/simple-plist/-/simple-plist-1.3.1.tgz", + "integrity": "sha512-iMSw5i0XseMnrhtIzRb7XpQEXepa9xhWxGUojHBL43SIpQuDQkh3Wpy67ZbDzZVr6EKxvwVChnVpdl8hEVLDiw==", + "requires": { + "bplist-creator": "0.1.0", + "bplist-parser": "0.3.1", + "plist": "^3.0.5" + } + }, "simple-swizzle": { "version": "0.2.2", "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", @@ -43042,6 +43663,11 @@ "readable-stream": "^3.5.0" } }, + "stream-buffers": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-2.2.0.tgz", + "integrity": "sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg==" + }, "stream-chain": { "version": "2.2.5", "resolved": "https://registry.npmjs.org/stream-chain/-/stream-chain-2.2.5.tgz", @@ -44266,6 +44892,22 @@ "version": "7.5.9", "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==" }, + "xcode": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/xcode/-/xcode-3.0.1.tgz", + "integrity": "sha512-kCz5k7J7XbJtjABOvkc5lJmkiDh8VhjVCGNiqdKCscmVpdVUpEAyXv1xmCLkQJ5dsHqx3IPO4XW+NTDhU/fatA==", + "requires": { + "simple-plist": "^1.1.0", + "uuid": "^7.0.3" + }, + "dependencies": { + "uuid": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz", + "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==" + } + } + }, "xml-formatter": { "version": "2.6.1", "integrity": "sha512-dOiGwoqm8y22QdTNI7A+N03tyVfBlQ0/oehAzxIZtwnFAHGeSlrfjF73YQvzSsa/Kt6+YZasKsrdu6OIpuBggw==", @@ -44297,6 +44939,27 @@ "version": "3.2.0", "integrity": "sha512-8LRU6cq+d7mVsoDaMhnkkt3CTtAs4153p49fRo+HIB3I1FD1o5CeXRjRH29sQevIfVJIcPjKSsPU/+Ujhq09Rg==" }, + "xml2js": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "dependencies": { + "xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" + } + } + }, + "xmlbuilder": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-14.0.0.tgz", + "integrity": "sha512-ts+B2rSe4fIckR6iquDjsKbQFK2NlUk6iG5nf14mDEyldgoc2nEKZ3jZWMPTxGQwVgToSjt6VGIho1H8/fNFTg==" + }, "xpath": { "version": "0.0.27", "integrity": "sha512-fg03WRxtkCV6ohClePNAECYsmpKKTv5L8y/X3Dn1hQrec3POx2jHZ/0P2qQ6HvsrU1BmeqXcof3NGGueG6LxwQ==" diff --git a/package.json b/package.json index ab96823c6f..19d4dcdb72 100644 --- a/package.json +++ b/package.json @@ -149,7 +149,7 @@ "react-native-blue-crypto": "github:BlueWallet/react-native-blue-crypto#3cb5442", "react-native-camera-kit": "13.0.0", "react-native-config": "^1.5.0", - "react-native-crypto": "2.2.0", + "react-native-crypto": "^2.2.0", "react-native-default-preference": "1.4.4", "react-native-device-info": "8.7.1", "react-native-dialog": "^9.3.0", @@ -168,6 +168,7 @@ "react-native-localize": "2.2.5", "react-native-modal": "13.0.1", "react-native-navigation-bar-color": "https://github.com/BlueWallet/react-native-navigation-bar-color#3b2894ae62fbce99a3bd24105f0921cebaef5c94", + "react-native-nfc-manager": "^3.14.5", "react-native-obscure": "https://github.com/BlueWallet/react-native-obscure.git#f4b83b4a261e39b1f5ed4a45ac5bcabc8a59eadb", "react-native-passcode-auth": "https://github.com/BlueWallet/react-native-passcode-auth#a2ff977ba92b36f8d0a5567f59c05cc608e8bd12", "react-native-privacy-snapshot": "https://github.com/BlueWallet/react-native-privacy-snapshot#529e4627d93f67752a27e82a040ff7b64dca0783", @@ -193,7 +194,7 @@ "readable-stream": "3.6.0", "realm": "11.5.1", "rn-ldk": "github:BlueWallet/rn-ldk#v0.8.4", - "rn-nodeify": "10.3.0", + "rn-nodeify": "^10.3.0", "scryptsy": "2.1.0", "slip39": "https://github.com/BlueWallet/slip39-js", "stream-browserify": "3.0.0", diff --git a/screen/boltcard/create.js b/screen/boltcard/create.js index 3a6fe14fbb..d4ec76bea4 100644 --- a/screen/boltcard/create.js +++ b/screen/boltcard/create.js @@ -13,9 +13,10 @@ import { Text, View } from 'react-native'; -import { Icon, ListItem, Tooltip } from 'react-native-elements'; +import {Icon, ListItem, CheckBox} from 'react-native-elements'; import Ionicons from 'react-native-vector-icons/Ionicons'; -import QRCodeComponent from '../../components/QRCodeComponent'; +import NfcManager, { NfcTech, Ndef} from 'react-native-nfc-manager'; +import Ntag424 from '../../class/Ntag424'; import { BlueButton, @@ -95,6 +96,8 @@ const BoltCardCreate = () => { const [testc, setTestc] = useState(); const [testBolt, setTestBolt] = useState(); + const [writingCard, setWritingCard] = useState(false); + const [enhancedPrivacy, setEnhancedPrivacy] = useState(false); useEffect(() => { @@ -102,85 +105,12 @@ const BoltCardCreate = () => { setKeys([cardDetails.k0,cardDetails.k1,cardDetails.k2,cardDetails.k3,cardDetails.k4]) setlnurlw_base(cardDetails.lnurlw_base) setCardName(cardDetails.card_name) - - console.log('native', NativeModules, NativeModules.MyReactModule); - NativeModules.MyReactModule.changeKeys( - cardDetails.lnurlw_base, - cardDetails.k0, - cardDetails.k1, - cardDetails.k2, - cardDetails.k3, - cardDetails.k4, - cardDetails.uid_privacy != undefined && cardDetails.uid_privacy == "Y", - (response) => { - console.log('Change keys response', response) - if (response == "Success") { - setLoading(false); - setWriteMode(true); - } - NativeModules.MyReactModule.setCardMode('createBoltcard'); - } - ); resetOutput(); backupCardKeys(); } }, [cardDetails]); - useEffect(() => { - BackHandler.addEventListener('hardwareBackPress', handleBackButton); - - let boltCardEventListener; - if(Platform.OS == 'android') { - const eventEmitter = new NativeEventEmitter(); - boltCardEventListener = eventEmitter.addListener('CreateBoltCard', (event) => { - console.log('CREATE BOLTCARD LISTENER') - if(event.tagTypeError) setTagTypeError(event.tagTypeError); - if(event.cardUID) setCardUID(event.cardUID); - if(event.tagname) setTagname(event.tagname); - - if(event.key0Changed) setKey0Changed(event.key0Changed); - if(event.key1Changed) setKey1Changed(event.key1Changed); - if(event.key2Changed) setKey2Changed(event.key2Changed); - if(event.key3Changed) setKey3Changed(event.key3Changed); - if(event.key4Changed) setKey4Changed(event.key4Changed); - if(event.uid_privacy) setPrivateUID(event.uid_privacy == 'Y'); - - if(event.ndefWritten) setNdefWritten(event.ndefWritten); - if(event.writekeys) setWriteKeys(event.writekeys); - - if(event.readNDEF) { - setNdefRead(event.readNDEF) - //we have the latest read from the card fire it off to the server. - const httpsLNURL = event.readNDEF.replace("lnurlw://", "https://"); - fetch(httpsLNURL) - .then((response) => response.json()) - .then((json) => { - setTestBolt("success"); - }) - .catch(error => { - setTestBolt("Error: "+error.message); - }); - } - - if(event.testp) setTestp(event.testp); - if(event.testc) setTestc(event.testc); - - NativeModules.MyReactModule.setCardMode('read'); - setWriteMode(false); - //testc is the last value returned from "CreateBoltCard" listner function - //if testc is returned, it means the card has been written successfully - if(event.testc) setCardWritten('success'); - - }); - } - - return () => { - if(boltCardEventListener) boltCardEventListener.remove(); - BackHandler.removeEventListener('hardwareBackPress', handleBackButton); - }; - }, []); - const getCardKeys = (wallet) => { wallet .getcardkeys() @@ -189,29 +119,13 @@ const BoltCardCreate = () => { setCardDetails(keys); wallet.setWipeData(null); saveToDisk(); - }) - .catch(err => { - console.log('ERROR', err.message); - alert(err.message); - goBack(); - }); - } - - const getCreateUrl = (wallet) => { - wallet - .createcard() - .then(url => { - console.log('URL', url); - setCreateUrl(url); - wallet.setWipeData(null); - saveToDisk(); setLoading(false); }) .catch(err => { console.log('ERROR', err.message); alert(err.message); goBack(); - }) + }); } const setCardWritten = async (status) => { @@ -224,32 +138,21 @@ const BoltCardCreate = () => { useEffect(() => { if(wallet) { - if(Platform.OS == 'ios') { - getCreateUrl(wallet); - } else { - getCardKeys(wallet); - } + getCardKeys(wallet); } }, [walletID]); const updateNodeUrl = text => { setNodeURL(text); - if(Platform.OS == 'android') { - NativeModules.MyReactModule.setNodeURL(text); - } } - - useFocusEffect( - React.useCallback(() => { - if(Platform.OS == 'android') NativeModules.MyReactModule.setCardMode("read"); - }, []) - ); const handleBackButton = () => { goBack(null); return true; }; + const delay = ms => new Promise(res => setTimeout(res, ms)); + const resetOutput = () => { setTagTypeError(null); setTagname(null); @@ -263,12 +166,155 @@ const BoltCardCreate = () => { setWriteKeys(null); } - const writeAgain = () => { + const writeAgain = async () => { resetOutput(); - if(Platform.OS == 'android') { - NativeModules.MyReactModule.setCardMode('createBoltcard'); - } setWriteMode(true); + setWritingCard(true); + try { + // register for the NFC tag with NDEF in it + await NfcManager.requestTechnology(NfcTech.IsoDep, { + alertMessage: "Ready to write card. Hold NFC card to phone until all keys are changed." + }); + + //set ndef + const ndefMessage = lnurlw_base.includes('?') + ? lnurlw_base + '&p=00000000000000000000000000000000&c=0000000000000000' + : lnurlw_base + + '?p=00000000000000000000000000000000&c=0000000000000000'; + + + const message = [Ndef.uriRecord(ndefMessage)]; + const bytes = Ndef.encodeMessage(message); + + await Ntag424.setNdefMessage(bytes); + setNdefWritten('success'); + + const key0 = '00000000000000000000000000000000'; + // //auth first + await Ntag424.AuthEv2First( + '00', + key0, + ); + + if(enhancedPrivacy) { + await Ntag424.setPrivateUid(); + } + + const piccOffset = ndefMessage.indexOf('p=') + 9; + const macOffset = ndefMessage.indexOf('c=') + 9; + //change file settings + await Ntag424.setBoltCardFileSettings( + piccOffset, + macOffset, + ); + //get uid + const uid = await Ntag424.getCardUid(); + setCardUID(uid); + + //change keys + console.log('key1', keys[1]); + await Ntag424.changeKey( + '01', + key0, + keys[1], + '01', + ); + setKey1Changed('yes'); + console.log('key2', keys[2]); + await Ntag424.changeKey( + '02', + key0, + keys[2], + '01', + ); + setKey2Changed('yes'); + console.log('key3', keys[3]); + await Ntag424.changeKey( + '03', + key0, + keys[3], + '01', + ); + setKey3Changed('yes'); + console.log('key4', keys[4]); + await Ntag424.changeKey( + '04', + key0, + keys[4], + '01', + ); + setKey4Changed('yes'); + console.log('key0', keys[0]); + await Ntag424.changeKey( + '00', + key0, + keys[0], + '01', + ); + setKey0Changed('yes'); + setWriteKeys('success'); + + //set offset for ndef header + const ndef = await Ntag424.readData("060000"); + const setNdefMessage = Ndef.uri.decodePayload(ndef); + setNdefRead(setNdefMessage); + + //we have the latest read from the card fire it off to the server. + const httpsLNURL = setNdefMessage.replace('lnurlw://', 'https://'); + fetch(httpsLNURL) + .then(response => response.json()) + .then(json => { + setTestBolt('success'); + }) + .catch(error => { + setTestBolt('Error: ' + error.message); + }); + + await Ntag424.AuthEv2First( + '00', + keys[0], + ); + + const params = {}; + setNdefMessage.replace(/[?&]+([^=&]+)=([^&]*)/gi, + function(m,key,value) { + params[key] = value; + } + ); + if(!"p" in params) { + setTestp("no p value to test") + return; + } + if(!"c" in params) { + setTestc("no c value to test") + return; + } + + const pVal = params['p']; + const cVal = params['c'].slice(0,16); + + const testResult = await Ntag424.testPAndC(pVal, cVal, uid, keys[1], keys[2]); + setTestp(testResult.pTest ? 'ok' : 'decrypt with key failed'); + setTestc(testResult.cTest ? 'ok' : 'decrypt with key failed'); + setCardWritten('success'); + + + } catch (ex) { + console.error('Oops!', ex, ex.message); + var error = ex; + if(typeof ex === 'object') { + error = "NFC Error: "+(ex.message? ex.message : ex.constructor.name); + } + setTagTypeError(error); + } finally { + // stop the nfc scanning + await NfcManager.cancelTechnologyRequest(); + setWritingCard(false); + //delay 1.5 sec after canceling to prevent users calling the requestTechnology function right away. + //if the request function gets called right after the cancel call, it returns duplicate registration error later + await delay(1500); + setWriteMode(false); + } } const showTickOrError = (good) => { @@ -276,33 +322,15 @@ const BoltCardCreate = () => { } const togglePrivacy = () => { - if(Platform.OS == 'android') { - NativeModules.MyReactModule.changeKeys( - cardDetails.lnurlw_base, - cardDetails.k0, - cardDetails.k1, - cardDetails.k2, - cardDetails.k3, - cardDetails.k4, - !enhancedPrivacy, - (response) => { - console.log('Change keys response', response) - if (response == "Success") { - setLoading(false); - setWriteMode(true); - } - NativeModules.MyReactModule.setCardMode('createBoltcard'); - } - ); - } setEnhancedPrivacy(!enhancedPrivacy) } const backupCardKeys = () => { let filename = `bolt_card_${(new Date().toJSON().slice(0,19).replaceAll(':','-'))}.json.txt` let filename2 = `bolt_card_${(new Date().toJSON().slice(0,19).replaceAll(':','-'))}-wipe.json.txt` - var path = RNFS.DownloadDirectoryPath + '/'+filename; - var path2 = RNFS.DownloadDirectoryPath + '/'+filename2; + var baseDirectoryPath = Platform.OS == "ios" ? RNFS.LibraryDirectoryPath : RNFS.DownloadDirectoryPath; + var path = baseDirectoryPath + '/'+filename; + var path2 = baseDirectoryPath + '/'+filename2; console.log('path', path); // write the create card key file RNFS.writeFile(path, JSON.stringify(wallet.cardKeys), 'utf8') @@ -318,7 +346,7 @@ const BoltCardCreate = () => { k4: wallet.cardKeys.k4 }), 'utf8') .then((success) => { - alert('Card keys saved to downloads folder with filenames: \r\n\r\n'+filename+'\r\n'+filename2); + alert(`Card keys saved to ${Platform.OS == 'android' ? 'downloads' : 'library'} folder with filenames: \r\n\r\n`+filename+'\r\n'+filename2); }) .catch((err) => { console.log(err.message); @@ -337,81 +365,6 @@ const BoltCardCreate = () => { const key3display = keys[3] ? keys[3].substring(0, 4)+"............"+ keys[3].substring(28) : "pending..."; const key4display = keys[4] ? keys[4].substring(0, 4)+"............"+ keys[4].substring(28) : "pending..."; - const CreateBoltCardQRCode = () => { - if(createUrl) { - return ( - <> - - - - The QR Code can be scanned only once. Make sure you have your card ready to be written. - - { - navigate("BoltCardCreateHelp") - }} - backgroundColor={colors.lightButton} - /> - - - { - try { - const card = await wallet.getCardDetails(true); - if(card && card.uid) { - console.log('Connected'); - alert('Card connected to the wallet'); - goBack(); - } else { - alert("Check if your card has been connected correctly."); - } - - } catch(e) { - alert("Check if your card has been connected correctly."); - } - }} - /> - - - { - setLoading(true); - try { - const newUrl = await wallet.regenerateCardUrl(); - setCreateUrl(newUrl); - wallet.setWipeData(null); - saveToDisk(); - setLoading(false); - } catch(e) { - console.log(e); - setLoading(false); - alert(e.message); - } - }} - backgroundColor={colors.redBG} - /> - - Click this button if you have already scanned the QR code once and are getting an error message "one time code was used"} - width={250} - height={100} - backgroundColor={colors.mainColor} - > - - - - - - ); - } - return null; - } return( @@ -425,92 +378,20 @@ const BoltCardCreate = () => { : <> - { - Platform.OS == 'ios' ? - CreateBoltCardQRCode() + {cardUID ? + <> + + {writingCard ? + + + Programming your bolt card... + : - null - } - {writeMode ? - <> - - { - return require('../../img/bolt-card-link.png'); - })()} style={{width: 40, height: 30, marginTop:20}} - /> - - Hold your nfc card to the reader. - - Hold card steady. - - - Do not remove your card until writing is complete. - - - - setShowDetails(!showDetails)} - /> - {showDetails && <> - togglePrivacy()}> - togglePrivacy()} /> - Enable Private UID (Hides card UID. One-way operation, can't undo) - - - - lnurl: - {lnurlw_base} - Private UID: {enhancedPrivacy ? "yes" : "no"} - Key 0: {key0display} - Key 1: {key1display} - Key 2: {key2display} - Key 3: {key3display} - Key 4: {key4display} - - - } - {__DEV__ && - <> - { - setCardWritten('success') - NativeModules.MyReactModule.setCardMode('read'); - setWriteMode(false); - setWriteKeys("success"); - setCardUID('simulated write'); - setTestc('ok'); - }} - title="Simulate write success" - /> - { - NativeModules.MyReactModule.setCardMode('read'); - setWriteMode(false); - setWriteKeys("fail"); - setCardUID('simulated write'); - setTestc('fail'); - }} - title="Simulate write failure" - /> - - } - - : - null - } - {cardUID && - <> - + <> {testc && testc == "ok" ? <> - + Card Connected @@ -518,7 +399,7 @@ const BoltCardCreate = () => { <> - + Card Write Failed @@ -550,19 +431,116 @@ const BoltCardCreate = () => { } - - {writekeys == "success" ? - - : - + + } + + {writekeys == "success" ? + + : + + } + + : + <> + { + cardDetails ? + <> + + { + return require('../../img/bolt-card-link.png'); + })()} style={{width: 40, height: 30, marginTop:20}} + /> + + {writeMode + ? + <> + Hold your nfc card to the reader. + + Hold card steady. + + + Do not remove your card until writing is complete. + + + + : + <> + + + } + + setShowDetails(!showDetails)} + /> + {showDetails && <> + + + + + + lnurl: + {lnurlw_base} + Private UID: {enhancedPrivacy ? "yes" : "no"} + Key 0: {key0display} + Key 1: {key1display} + Key 2: {key2display} + Key 3: {key3display} + Key 4: {key4display} + + + } + {__DEV__ && + <> + + { + setCardWritten('success') + NativeModules.MyReactModule.setCardMode('read'); + setWriteMode(false); + setWriteKeys("success"); + setCardUID('simulated write'); + setTestc('ok'); + }} + title="Simulate write success" + /> + + { + NativeModules.MyReactModule.setCardMode('read'); + setWriteMode(false); + setWriteKeys("fail"); + setCardUID('simulated write'); + setTestc('fail'); + }} + title="Simulate write failure" + /> + + } + + + : + Error getting bolt card details. } - + } diff --git a/screen/boltcard/disconnect.js b/screen/boltcard/disconnect.js index a31d132784..b9faf5afd9 100644 --- a/screen/boltcard/disconnect.js +++ b/screen/boltcard/disconnect.js @@ -14,8 +14,9 @@ import { View, } from 'react-native'; import Dialog from 'react-native-dialog'; +import NfcManager, { NfcTech, Ndef} from 'react-native-nfc-manager'; +import Ntag424 from '../../class/Ntag424'; import { Icon } from 'react-native-elements'; -import QRCodeComponent from '../../components/QRCodeComponent'; import { BlueButton, @@ -103,29 +104,9 @@ const BoltCardDisconnect = () => { error = error + ' Some keys missing, proceed with caution'; } setKeyJsonError(error ? error : false) - if(Platform.OS == 'android') enableResetMode(wipeCardDetails.k0, wipeCardDetails.k1, wipeCardDetails.k2, wipeCardDetails.k3, wipeCardDetails.k4, wipeCardDetails.uid); } }, [wipeCardDetails]); - useEffect(() => { - if(Platform.OS == 'android') { - const eventEmitter = new NativeEventEmitter(NativeModules.ToastExample); - const eventListener = eventEmitter.addListener('ChangeKeysResult', (event) => { - console.log('CHANGE KEYS', event); - if(event.status == 'success') { - setCardWiped(); - } - setWriteKeysOutput(event.output); - - //@todo: ensure card wipe has worked. - }); - - return () => { - eventListener.remove(); - }; - } - }, []); - const getWipeKeys = async (wallet) => { try { const data = await wallet.wipecard(); @@ -142,12 +123,6 @@ const BoltCardDisconnect = () => { } }, [walletID]); - useFocusEffect( - React.useCallback(() => { - if(Platform.OS == 'android') NativeModules.MyReactModule.setCardMode("read"); - }, []) - ); - const setCardWiped = async () => { console.log('setCardWiped'); if(wallet) { @@ -156,72 +131,97 @@ const BoltCardDisconnect = () => { } } - const enableResetMode = (k0, k1, k2, k3, k4, carduid) => { - NativeModules.MyReactModule.setCardMode("resetkeys"); - if (k0 && k1 && k2 && k3 && k4 && carduid) { - NativeModules.MyReactModule.setResetKeys(k0,k1,k2,k3,k4,carduid, ()=> { - //callback - console.log("reset keys set"); - }); - } - else { - NativeModules.MyReactModule.setResetKeys(key0,key1,key2,key3,key4,uid, ()=> { - //callback - console.log("reset keys set"); - }); - } + const delay = ms => new Promise(res => setTimeout(res, ms)); + + const enableResetMode = async () => { setWriteKeysOutput(null) setResetNow(true); + var result = []; + console.log('key0', key0); + try { + // register for the NFC tag with NDEF in it + await NfcManager.requestTechnology(NfcTech.IsoDep, { + alertMessage: "Ready to write card. Hold NFC card to phone until all keys are changed." + }); + + const defaultKey = '00000000000000000000000000000000'; + + // //auth first + await Ntag424.AuthEv2First( + '00', + key0, + ); + + //reset file settings + await Ntag424.resetFileSettings(); + + //change keys + await Ntag424.changeKey( + '01', + key1, + defaultKey, + '00', + ); + result.push("Change Key1: Success"); + console.log('changekey 2') + await Ntag424.changeKey( + '02', + key2, + defaultKey, + '00', + ); + result.push("Change Key2: Success"); + console.log('changekey 3') + await Ntag424.changeKey( + '03', + key3, + defaultKey, + '00', + ); + result.push("Change Key3: Success"); + await Ntag424.changeKey( + '04', + key4, + defaultKey, + '00', + ); + result.push("Change Key4: Success"); + await Ntag424.changeKey( + '00', + key0, + defaultKey, + '00', + ); + result = ["Change Key0: Success", ...result]; + + const message = [Ndef.uriRecord('')]; + const bytes = Ndef.encodeMessage(message); + await Ntag424.setNdefMessage(bytes); + + result.push("NDEF and SUN/SDM cleared"); + setCardWiped(); + + } catch (ex) { + console.error('Oops!', ex, ex.constructor.name); + var error = ex; + if(typeof ex === 'object') { + error = "NFC Error: "+(ex.message? ex.message : ex.constructor.name); + } + result.push(error); + setWriteKeysOutput(error); + } finally { + // stop the nfc scanning + NfcManager.cancelTechnologyRequest(); + delay(1500); + setWriteKeysOutput(result.join('\r\n')); + // setResetNow(false); + } } const disableResetMode = () => { - NativeModules.MyReactModule.setCardMode("read"); setResetNow(false); } - - const disconnectQRCode = () => { - const disconnect = wipeCardDetails; - console.log('QRCODE', disconnect); - return ( - <> - - - - - { - navigate("BoltCardDisconnectHelp") - }} - backgroundColor={colors.lightButton} - /> - - { - Alert.alert('I\'ve disconnected my bolt card', 'Please make sure you\'ve disconnected your card before pressing the "OK" button. You won\'t be able to get the wipe key details after clicking this.', [ - { - text: 'Cancel', - onPress: () => console.log('Cancel Pressed'), - style: 'cancel', - }, - {text: 'OK', onPress: () =>{ - setCardWiped(); - popToTop(); - goBack(); - }}, - ]); - - }} - // backgroundColor={colors.redBG} - /> - - ); - } - + return( @@ -229,15 +229,17 @@ const BoltCardDisconnect = () => { - Hold NFC Card + Hold NFC Card {!writeKeysOutput && Hold the bolt card to the reader until the reset has completed } - - {writeKeysOutput ? writeKeysOutput : } - + + + {writeKeysOutput ? writeKeysOutput : } + + {__DEV__ && { setCardWiped(); @@ -280,9 +282,6 @@ const BoltCardDisconnect = () => { : <> - {Platform.OS == 'ios' && - disconnectQRCode() - } {__DEV__ && { setCardWiped(); @@ -319,13 +318,11 @@ const BoltCardDisconnect = () => { } - { Platform.OS == 'android' && - - } + } diff --git a/utils/Cmac.js b/utils/Cmac.js new file mode 100644 index 0000000000..c03004124e --- /dev/null +++ b/utils/Cmac.js @@ -0,0 +1,440 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 artjomb + * https://github.com/artjomb/cryptojs-extension/blob/master/src/cmac.js + */ +// Shortcuts + +const C = require("crypto-js"); + +var Base = C.lib.Base; +var WordArray = C.lib.WordArray; +var AES = C.algo.AES; +var ext = C.ext = {}; + +// Constants +ext.const_Zero = new WordArray.init([0x00000000, 0x00000000, 0x00000000, 0x00000000]); +ext.const_One = new WordArray.init([0x00000000, 0x00000000, 0x00000000, 0x00000001]); +ext.const_Rb = new WordArray.init([0x00000000, 0x00000000, 0x00000000, 0x00000087]); // 00..0010000111 +ext.const_Rb_Shifted = new WordArray.init([0x80000000, 0x00000000, 0x00000000, 0x00000043]); // 100..001000011 +ext.const_nonMSB = new WordArray.init([0xFFFFFFFF, 0xFFFFFFFF, 0x7FFFFFFF, 0x7FFFFFFF]); // 1^64 || 0^1 || 1^31 || 0^1 || 1^31 + +/** + * Looks into the object to see if it is a WordArray. + * + * @param obj Some object + * + * @returns {boolean} + */ +ext.isWordArray = function(obj) { + return obj && typeof obj.clamp === "function" && typeof obj.concat === "function" && typeof obj.words === "array"; +} + +/** + * This padding is a 1 bit followed by as many 0 bits as needed to fill + * up the block. This implementation doesn't work on bits directly, + * but on bytes. Therefore the granularity is much bigger. + */ +C.pad.OneZeroPadding = { + pad: function (data, blocksize) { + // Shortcut + var blockSizeBytes = blocksize * 4; + + // Count padding bytes + var nPaddingBytes = blockSizeBytes - data.sigBytes % blockSizeBytes; + + // Create padding + var paddingWords = []; + for (var i = 0; i < nPaddingBytes; i += 4) { + var paddingWord = 0x00000000; + if (i === 0) { + paddingWord = 0x80000000; + } + paddingWords.push(paddingWord); + } + var padding = new WordArray.init(paddingWords, nPaddingBytes); + + // Add padding + data.concat(padding); + }, + unpad: function () { + // TODO: implement + } +}; + +/** + * No padding is applied. This is necessary for streaming cipher modes + * like CTR. + */ +C.pad.NoPadding = { + pad: function () {}, + unpad: function () {} +}; + +/** + * Shifts the array by n bits to the left. Zero bits are added as the + * least significant bits. This operation modifies the current array. + * + * @param {WordArray} wordArray WordArray to work on + * @param {int} n Bits to shift by + * + * @returns the WordArray that was passed in + */ +ext.bitshift = function(wordArray, n){ + var carry = 0, + words = wordArray.words, + wres, + skipped = 0, + carryMask; + if (n > 0) { + while(n > 31) { + // delete first element: + words.splice(0, 1); + + // add `0` word to the back + words.push(0); + + n -= 32; + skipped++; + } + if (n == 0) { + // 1. nothing to shift if the shift amount is on a word boundary + // 2. This has to be done, because the following algorithm computes + // wrong values only for n==0 + return carry; + } + for(var i = words.length - skipped - 1; i >= 0; i--) { + wres = words[i]; + words[i] <<= n; + words[i] |= carry; + carry = wres >>> (32 - n); + } + } else if (n < 0) { + while(n < -31) { + // insert `0` word to the front: + words.splice(0, 0, 0); + + // remove last element: + words.length--; + + n += 32; + skipped++; + } + if (n == 0) { + // nothing to shift if the shift amount is on a word boundary + return carry; + } + n = -n; + carryMask = (1 << n) - 1; + for(var i = skipped; i < words.length; i++) { + wres = words[i] & carryMask; + words[i] >>>= n; + words[i] |= carry; + carry = wres << (32 - n); + } + } + return carry; +}; + +/** + * Applies XOR on both given word arrays and returns a third resulting + * WordArray. The initial word arrays must have the same length + * (significant bytes). + * + * @param {WordArray} wordArray1 WordArray + * @param {WordArray} wordArray2 WordArray + * + * @returns first passed WordArray (modified) + */ + ext.xor = function(wordArray1, wordArray2){ + for(var i = 0; i < wordArray1.words.length; i++) { + wordArray1.words[i] ^= wordArray2.words[i]; + } + return wordArray1; +}; + +/** + * Returns the n leftmost bytes of the WordArray. + * + * @param {WordArray} wordArray WordArray to work on + * @param {int} n Bytes to retrieve + * + * @returns new WordArray + */ +ext.leftmostBytes = function(wordArray, n){ + var lmArray = wordArray.clone(); + lmArray.sigBytes = n; + lmArray.clamp(); + return lmArray; +}; + +/** + * Returns the n rightmost bytes of the WordArray. + * + * @param {WordArray} wordArray WordArray to work on + * @param {int} n Bytes to retrieve (must be positive) + * + * @returns new WordArray + */ +ext.rightmostBytes = function(wordArray, n){ + wordArray.clamp(); + var wordSize = 32; + var rmArray = wordArray.clone(); + var bitsToShift = (rmArray.sigBytes - n) * 8; + if (bitsToShift >= wordSize) { + var popCount = Math.floor(bitsToShift/wordSize); + bitsToShift -= popCount * wordSize; + rmArray.words.splice(0, popCount); + rmArray.sigBytes -= popCount * wordSize / 8; + } + if (bitsToShift > 0) { + ext.bitshift(rmArray, bitsToShift); + rmArray.sigBytes -= bitsToShift / 8; + } + return rmArray; +}; + +/** + * Returns the n rightmost words of the WordArray. It assumes + * that the current WordArray has at least n words. + * + * @param {WordArray} wordArray WordArray to work on + * @param {int} n Words to retrieve (must be positive) + * + * @returns popped words as new WordArray + */ +ext.popWords = function(wordArray, n){ + var left = wordArray.words.splice(0, n); + wordArray.sigBytes -= n * 4; + return new WordArray.init(left); +}; + +/** + * Shifts the array to the left and returns the shifted dropped elements + * as WordArray. The initial WordArray must contain at least n bytes and + * they have to be significant. + * + * @param {WordArray} wordArray WordArray to work on (is modified) + * @param {int} n Bytes to shift (must be positive, default 16) + * + * @returns new WordArray + */ +ext.shiftBytes = function(wordArray, n){ + n = n || 16; + var r = n % 4; + n -= r; + + var shiftedArray = new WordArray.init(); + for(var i = 0; i < n; i += 4) { + shiftedArray.words.push(wordArray.words.shift()); + wordArray.sigBytes -= 4; + shiftedArray.sigBytes += 4; + } + if (r > 0) { + shiftedArray.words.push(wordArray.words[0]); + shiftedArray.sigBytes += r; + + ext.bitshift(wordArray, r * 8); + wordArray.sigBytes -= r; + } + return shiftedArray; +}; + +/** + * XORs arr2 to the end of arr1 array. This doesn't modify the current + * array aside from clamping. + * + * @param {WordArray} arr1 Bigger array + * @param {WordArray} arr2 Smaller array to be XORed to the end + * + * @returns new WordArray + */ +ext.xorendBytes = function(arr1, arr2){ + // TODO: more efficient + return ext.leftmostBytes(arr1, arr1.sigBytes-arr2.sigBytes) + .concat(ext.xor(ext.rightmostBytes(arr1, arr2.sigBytes), arr2)); +}; + +/** + * Doubling operation on a 128-bit value. This operation modifies the + * passed array. + * + * @param {WordArray} wordArray WordArray to work on + * + * @returns passed WordArray + */ +ext.dbl = function(wordArray){ + var carry = ext.msb(wordArray); + ext.bitshift(wordArray, 1); + ext.xor(wordArray, carry === 1 ? ext.const_Rb : ext.const_Zero); + return wordArray; +}; + +/** + * Inverse operation on a 128-bit value. This operation modifies the + * passed array. + * + * @param {WordArray} wordArray WordArray to work on + * + * @returns passed WordArray + */ +ext.inv = function(wordArray){ + var carry = wordArray.words[4] & 1; + ext.bitshift(wordArray, -1); + ext.xor(wordArray, carry === 1 ? ext.const_Rb_Shifted : ext.const_Zero); + return wordArray; +}; + +/** + * Check whether the word arrays are equal. + * + * @param {WordArray} arr1 Array 1 + * @param {WordArray} arr2 Array 2 + * + * @returns boolean + */ +ext.equals = function(arr1, arr2){ + if (!arr2 || !arr2.words || arr1.sigBytes !== arr2.sigBytes) { + return false; + } + arr1.clamp(); + arr2.clamp(); + var equal = 0; + for(var i = 0; i < arr1.words.length; i++) { + equal |= arr1.words[i] ^ arr2.words[i]; + } + return equal === 0; +}; + +/** + * Retrieves the most significant bit of the WordArray as an Integer. + * + * @param {WordArray} arr + * + * @returns Integer + */ +ext.msb = function(arr) { + return arr.words[0] >>> 31; +} + + +var OneZeroPadding = C.pad.OneZeroPadding; + + +var CMAC = C.algo.CMAC = Base.extend({ + /** + * Initializes a newly created CMAC + * + * @param {WordArray} key The secret key + * + * @example + * + * var cmacer = CryptoJS.algo.CMAC.create(key); + */ + init: function(key){ + // generate sub keys... + this._aes = AES.createEncryptor(key, { iv: new WordArray.init(), padding: C.pad.NoPadding }); + + // Step 1 + var L = this._aes.finalize(ext.const_Zero); + + // Step 2 + var K1 = L.clone(); + ext.dbl(K1); + + // Step 3 + if (!this._isTwo) { + var K2 = K1.clone(); + ext.dbl(K2); + } else { + var K2 = L.clone(); + ext.inv(K2); + } + + this._K1 = K1; + this._K2 = K2; + + this._const_Bsize = 16; + + this.reset(); + }, + + reset: function () { + this._x = ext.const_Zero.clone(); + this._counter = 0; + this._buffer = new WordArray.init(); + }, + + update: function (messageUpdate) { + if (!messageUpdate) { + return this; + } + + // Shortcuts + var buffer = this._buffer; + var bsize = this._const_Bsize; + + if (typeof messageUpdate === "string") { + messageUpdate = C.enc.Utf8.parse(messageUpdate); + } + + buffer.concat(messageUpdate); + + while(buffer.sigBytes > bsize){ + var M_i = ext.shiftBytes(buffer, bsize); + ext.xor(this._x, M_i); + this._x.clamp(); + this._aes.reset(); + this._x = this._aes.finalize(this._x); + this._counter++; + } + + // Chainable + return this; + }, + + finalize: function (messageUpdate) { + this.update(messageUpdate); + + // Shortcuts + var buffer = this._buffer; + var bsize = this._const_Bsize; + + var M_last = buffer.clone(); + if (buffer.sigBytes === bsize) { + ext.xor(M_last, this._K1); + } else { + OneZeroPadding.pad(M_last, bsize/4); + ext.xor(M_last, this._K2); + } + + ext.xor(M_last, this._x); + + this.reset(); // Can be used immediately afterwards + + this._aes.reset(); + return this._aes.finalize(M_last); + }, + + _isTwo: false +}); + +/** + * Directly invokes the CMAC and returns the calculated MAC. + * + * @param {WordArray} key The key to be used for CMAC + * @param {WordArray|string} message The data to be MAC'ed (either WordArray or UTF-8 encoded string) + * + * @returns {WordArray} MAC + */ +C.CMAC = function(key, message){ + return CMAC.create(key).finalize(message); +}; + +C.algo.OMAC1 = CMAC; +C.algo.OMAC2 = CMAC.extend({ + _isTwo: true +}); + +module.exports = C; \ No newline at end of file